mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 12:25:22 +02:00
feat(k8s/application): add/edit placement preferences/constraints (#4210)
* feat(k8s/application): create application with placement preferences/constraints * feat(k8s/application): edit application placement preferences/constraints
This commit is contained in:
parent
32bac9ffcc
commit
52bdcf2e2b
22 changed files with 451 additions and 190 deletions
|
@ -926,6 +926,102 @@
|
|||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<div ng-if="ctrl.formValues.DeploymentType === ctrl.ApplicationDeploymentTypes.REPLICATED">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Placement preferences and constraints
|
||||
</div>
|
||||
<!-- #region PLACEMENTS -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted">
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Deploy this application on nodes that respect all of the following placement rules.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Placement rules</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addPlacement()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add rule
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="placement in ctrl.formValues.Placements" style="margin-top: 2px;">
|
||||
<div class="col-sm-5 input-group" ng-class="{ striked: placement.NeedsDeletion }">
|
||||
<select
|
||||
class="form-control"
|
||||
ng-model="placement.Label"
|
||||
ng-options="label as (label.Key | kubernetesNodeLabelHumanReadbleText) for label in ctrl.nodesLabels"
|
||||
ng-change="ctrl.onPlacementLabelChange($index)"
|
||||
ng-disabled="ctrl.isEditAndNotNewPlacement($index)"
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-5 input-group" ng-class="{ striked: placement.NeedsDeletion }">
|
||||
<select
|
||||
class="form-control"
|
||||
ng-model="placement.Value"
|
||||
ng-options="value for value in placement.Label.Values"
|
||||
ng-disabled="ctrl.isEditAndNotNewPlacement($index)"
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-1 input-group">
|
||||
<button ng-if="!placement.NeedsDeletion" class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removePlacement($index)">
|
||||
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button ng-if="placement.NeedsDeletion" class="btn btn-sm btn-primary" type="button" ng-click="ctrl.restorePlacement($index)">
|
||||
<i class="fa fa-trash-restore" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="ctrl.showPlacementPolicySection()">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">Placement policy</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted">
|
||||
Specify the policy associated to the placement rules.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- placement policy options -->
|
||||
<div class="form-group" style="margin-bottom: 0;" ng-if="ctrl.formValues.Placements.length">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="placement_soft" ng-value="ctrl.ApplicationPlacementTypes.PREFERRED" ng-model="ctrl.formValues.PlacementType" />
|
||||
<label for="placement_soft">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-list" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Preferred
|
||||
</div>
|
||||
<p>Schedule this application on nodes that match the rules if possible</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="placement_hard" ng-value="ctrl.ApplicationPlacementTypes.MANDATORY" ng-model="ctrl.formValues.PlacementType" />
|
||||
<label for="placement_hard">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-tasks" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Mandatory
|
||||
</div>
|
||||
<p>Schedule this application on nodes that match the rules only</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !placement policy options -->
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Publishing the application
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
KubernetesApplicationPublishingTypes,
|
||||
KubernetesApplicationQuotaDefaults,
|
||||
KubernetesApplicationTypes,
|
||||
KubernetesApplicationPlacementTypes,
|
||||
} from 'Kubernetes/models/application/models';
|
||||
import {
|
||||
KubernetesApplicationConfigurationFormValue,
|
||||
|
@ -18,6 +19,7 @@ import {
|
|||
KubernetesApplicationFormValues,
|
||||
KubernetesApplicationPersistedFolderFormValue,
|
||||
KubernetesApplicationPublishedPortFormValue,
|
||||
KubernetesApplicationPlacementFormValue,
|
||||
} from 'Kubernetes/models/application/formValues';
|
||||
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
||||
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
|
||||
|
@ -25,6 +27,7 @@ import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceRese
|
|||
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application/index';
|
||||
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
||||
import { KubernetesNodeHelper } from 'Kubernetes/node/helper';
|
||||
|
||||
class KubernetesCreateApplicationController {
|
||||
/* #region CONSTRUCTOR */
|
||||
|
@ -66,6 +69,7 @@ class KubernetesCreateApplicationController {
|
|||
this.ApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
|
||||
this.ApplicationDataAccessPolicies = KubernetesApplicationDataAccessPolicies;
|
||||
this.ApplicationPublishingTypes = KubernetesApplicationPublishingTypes;
|
||||
this.ApplicationPlacementTypes = KubernetesApplicationPlacementTypes;
|
||||
this.ApplicationTypes = KubernetesApplicationTypes;
|
||||
this.ApplicationConfigurationFormValueOverridenKeyTypes = KubernetesApplicationConfigurationFormValueOverridenKeyTypes;
|
||||
this.ServiceTypes = KubernetesServiceTypes;
|
||||
|
@ -248,6 +252,33 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region PLACEMENT UI MANAGEMENT */
|
||||
addPlacement() {
|
||||
const placement = new KubernetesApplicationPlacementFormValue();
|
||||
const label = this.nodesLabels[0];
|
||||
placement.Label = label;
|
||||
placement.Value = label.Values[0];
|
||||
this.formValues.Placements.push(placement);
|
||||
}
|
||||
|
||||
restorePlacement(index) {
|
||||
this.formValues.Placements[index].NeedsDeletion = false;
|
||||
}
|
||||
|
||||
removePlacement(index) {
|
||||
if (this.state.isEdit && !this.formValues.Placements[index].IsNew) {
|
||||
this.formValues.Placements[index].NeedsDeletion = true;
|
||||
} else {
|
||||
this.formValues.Placements.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
onPlacementLabelChange(index) {
|
||||
this.formValues.Placements[index].Value = this.formValues.Placements[index].Label.Values[0];
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
|
||||
/* #region PUBLISHED PORTS UI MANAGEMENT */
|
||||
addPublishedPort() {
|
||||
const p = new KubernetesApplicationPublishedPortFormValue();
|
||||
|
@ -512,6 +543,15 @@ class KubernetesCreateApplicationController {
|
|||
return this.formValues.PublishingType !== KubernetesApplicationPublishingTypes.INTERNAL && toKeepPorts.length === 0;
|
||||
}
|
||||
|
||||
isEditAndNotNewPlacement(index) {
|
||||
return this.state.isEdit && !this.formValues.Placements[index].IsNew;
|
||||
}
|
||||
|
||||
showPlacementPolicySection() {
|
||||
const placements = _.filter(this.formValues.Placements, { NeedsDeletion: false });
|
||||
return placements.length !== 0;
|
||||
}
|
||||
|
||||
isNonScalable() {
|
||||
const scalable = this.supportScalableReplicaDeployment();
|
||||
const global = this.supportGlobalDeployment();
|
||||
|
@ -861,13 +901,20 @@ class KubernetesCreateApplicationController {
|
|||
this.state.nodes.memory += filesizeParser(item.Memory);
|
||||
this.state.nodes.cpu += item.CPU;
|
||||
});
|
||||
this.nodesLabels = KubernetesNodeHelper.generateNodeLabelsFromNodes(nodes);
|
||||
|
||||
const namespace = this.state.isEdit ? this.state.params.namespace : this.formValues.ResourcePool.Namespace.Name;
|
||||
await this.refreshNamespaceData(namespace);
|
||||
|
||||
if (this.state.isEdit) {
|
||||
await this.getApplication();
|
||||
this.formValues = KubernetesApplicationConverter.applicationToFormValues(this.application, this.resourcePools, this.configurations, this.persistentVolumeClaims);
|
||||
this.formValues = KubernetesApplicationConverter.applicationToFormValues(
|
||||
this.application,
|
||||
this.resourcePools,
|
||||
this.configurations,
|
||||
this.persistentVolumeClaims,
|
||||
this.nodesLabels
|
||||
);
|
||||
this.formValues.OriginalIngresses = this.filteredIngresses;
|
||||
this.savedFormValues = angular.copy(this.formValues);
|
||||
delete this.formValues.ApplicationType;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue