Workflow updates for Create Cluster

* Apply limitations to 'master_lb_enabled' checkbox
  interactions, such as master_count.
* Basic validation for cluster name, to reduce create failures.
* Rename 'Master' to 'Control Plane' in line with upstream Kubernetes.
* Add validation for 'master_count': min/max and odd numbers.
* 'Network' tab required indicator no longer shows when the
  'create new network' checkbox is selected. This tab has no required
  options initially and the form can submit without visting the tab.
* Help text updates to reflect the updated options.

Change-Id: I7840ab606eb4a39fe34509d7b49d738391148bae
This commit is contained in:
Dale Smith 2024-05-13 15:55:32 +12:00
parent 8680420c34
commit c724172972
5 changed files with 82 additions and 32 deletions

View File

@ -1,5 +1,5 @@
<h1 class="h4" translate>Additional Labels</h1>
<p translate>Specify additional kube_labels to apply to the cluster or override labels set by the cluster template. Overriding labels set by the cluster template may result in your cluster being misconfigured, unstable or unable to be created.</p>
<p translate>Specify additional labels to apply to the cluster or override labels set by the cluster template. Overriding labels set by the cluster template may result in your cluster being misconfigured, unstable or unable to be created.</p>
<p translate>The key=value pair string is case insensitive and will be converted to lower case.</p>
<p translate>The key=value pair string is case insensitive and will be converted to lower case.</p>

View File

@ -142,7 +142,7 @@
templateResponse.flavor_id = 'ABC';
var model = $scope.model;
model.cluster_template_id = '99'; // Triggers bussines logic revalidation
model.cluster_template_id = '99'; // Triggers business logic revalidation
$scope.$apply();
expect(model.keypair).toBe(1);
@ -168,7 +168,7 @@
templateResponse.node_count = 1;
templateResponse.flavor_id = 'ABC';
model.cluster_template_id = '99'; // Triggers bussines logic revalidation
model.cluster_template_id = '99'; // Triggers business logic revalidation
$scope.$apply();
expect(model.keypair).toBe(99);
@ -182,12 +182,12 @@
'response contains negative `master_lb_enabled` flag', function() {
$scope.model.master_count = 99;
templateResponse.master_lb_enabled = false;
$scope.model.cluster_template_id = '99'; // Triggers bussines logic revalidation
$scope.model.cluster_template_id = '99'; // Triggers business logic revalidation
$scope.$apply();
expect($scope.model.master_count).toBe(1);
$scope.model.master_count = MODEL_DEFAULTS.master_count;
$scope.model.cluster_template_id = '999'; // Triggers bussines logic revalidation
$scope.model.cluster_template_id = '999'; // Triggers business logic revalidation
$scope.$apply();
expect($scope.model.master_count).toBe(1);
});
@ -196,7 +196,7 @@
'template response', function() {
templateResponse.labels = null;
$scope.model.labels = MODEL_DEFAULTS.labels;
$scope.model.cluster_template_id = '99'; // Triggers bussines logic revalidation
$scope.model.cluster_template_id = '99'; // Triggers business logic revalidation
$scope.$apply();
expect($scope.model.labels).toEqual(MODEL_DEFAULTS.labels);
@ -229,13 +229,13 @@
it('should not fail if the cluster template response is empty', function() {
templateResponse = {};
$scope.model.cluster_template_id = '99'; // Triggers bussines logic revalidation
$scope.model.cluster_template_id = '99'; // Triggers business logic revalidation
$scope.$apply();
});
it('should not fail if the cluster template\'s labels are empty', function() {
templateResponse = {labels:{}};
$scope.model.cluster_template_id = '99'; // Triggers bussines logic revalidation
$scope.model.cluster_template_id = '99'; // Triggers business logic revalidation
$scope.$apply();
});
@ -250,7 +250,7 @@
templateResponse.labels.ingress_controller = 'c2';
$scope.model.cluster_template_id = '99'; // Triggers bussines logic revalidation
$scope.model.cluster_template_id = '99'; // Triggers business logic revalidation
$scope.$apply();
expect($scope.model.ingress_controller.labels.ingress_controller).toBe('c2');

View File

@ -1,6 +1,11 @@
<h1 class="h4" translate>Control Plane size</h1>
<p translate>Only uneven number of control plane nodes are supported. This provides the best balance of fault tolerance and cost for etcd.</p>
<h1 class="h4" translate>Auto Scaling</h1>
<p translate>If enabled, the minimum and maximum number of worker nodes must be specified.</p>
<p translate>Auto scaling requires the use of CPU and memory limits on the resource definition of Pods.</p>
<p translate>If Kubernetes is unable to schedule a Pod due to insuficient CPU or memory in the cluster, a worker node will be added, as long as the maximum number of worker nodes has not been reached.</p>
<p translate>If Kubernetes is unable to schedule a Pod due to insufficient CPU or memory in the cluster, a worker node will be added, as long as the maximum number of worker nodes has not been reached.</p>
<p translate>If the aggregate resource limits of all existing Pods is lower than 50% of the cluster capacity, a worker node will be removed, as long as the minimum number of worker nodes has not been reached.</p>

View File

@ -44,6 +44,9 @@
// comma-separated key=value with optional space after comma
var REGEXP_KEY_VALUE = /^(\w+=[^,]+,?\s?)+$/;
// Object name, must start with alphabetical character.
var REGEXP_CLUSTER_NAME = /^[a-zA-Z][a-zA-Z0-9_\-\.]*$/;
function ClusterWorkflow($q, basePath, gettext, magnum, neutron, nova) {
var workflow = {
init: init
@ -59,9 +62,9 @@
name: gettext('Choose an Availability Zone')}];
var keypairsTitleMap = [{value: '', name: gettext('Choose a Keypair')}];
var masterFlavorTitleMap = [{value: '',
name: gettext('Choose a Flavor for the Master Node')}];
name: gettext('Choose a Flavor for the Control Plane nodes')}];
var workerFlavorTitleMap = [{value: '',
name: gettext('Choose a Flavor for the Worker Node')}];
name: gettext('Choose a Flavor for the Worker nodes')}];
var networkTitleMap = [{value: '', name: gettext('Choose an existing network')}];
var subnetTitleMap = [{value: '', name: fixedSubnetsInitial}];
var ingressTitleMap = [{value: '', name: gettext('Choose an ingress controller')}];
@ -86,7 +89,8 @@
'master_count': {
type: 'number',
minimum: 1
minimum: 1,
maximum: 7,
},
'master_flavor_id': { type: 'string' },
'node_count': {
@ -101,7 +105,7 @@
},
'max_node_count': { type: 'number' },
'master_lb_enabled': {type: 'boolean'},
'master_lb_enabled': { type: 'boolean' },
'create_network': { type: 'boolean' },
'fixed_network': { type: 'string' },
'fixed_subnet': { type: 'string' },
@ -117,9 +121,17 @@
var formMasterCount = {
key: 'master_count',
title: gettext('Number of Master Nodes'),
placeholder: gettext('The number of master nodes for the cluster'),
required: true
title: gettext('Number of Control Plane nodes'),
placeholder: gettext('The number of Control Plane nodes for the cluster'),
required: true,
validationMessage: {
'mustBeUnevenNumber': 'Supported control plane sizes are 1, 3, 5 or 7.'
},
$validators: {
mustBeUnevenNumber: function(value) {
return value % 2 !== 0;
}
}
};
// Disable the Master Count field, if only a single master is allowed
@ -151,7 +163,18 @@
key: 'name',
title: gettext('Cluster Name'),
placeholder: gettext('Name of the cluster'),
required: true
required: true,
help: "Text",
validationMessage: {
'invalidFormat': 'Cluster name must begin with an alphabetical ' +
'character and only contain alphanumeric, underscore, ' +
'dash and fullstop characters.'
},
$validators: {
invalidFormat: function(value) {
return REGEXP_CLUSTER_NAME.test(value);
}
}
},
{
key: 'cluster_template_id',
@ -203,7 +226,7 @@
items: [
{
type: 'fieldset',
title: gettext('Master Nodes'),
title: gettext('Control Plane Nodes'),
items: [
formMasterCount,
// Info message explaining why only single master node is enabled
@ -211,14 +234,27 @@
type: 'template',
template: '<div class="alert alert-info">' +
'<span class="fa fa-info-circle"></span> ' +
gettext('The selected Cluster Template does not support ' +
'multiple master nodes.') +
gettext('The selected options do not support ' +
'multiple control plane nodes. A Kubernetes ' +
'API Load Balancer is required, and can be ' +
'enabled in the Network tab.') +
'</div>',
condition: 'model.isSingleMasterNode == true'
},
// Info message explaining why we allow only uneven numbers of
// control plane nodes.
{
type: 'template',
template: '<div class="alert alert-info">' +
'<span class="fa fa-info-circle"></span> ' +
gettext('Only an uneven number of control plane nodes are allowed. ' +
'This provides the best balance of fault tolerance and cost.') +
'</div>',
condition: 'false'
},
{
key: 'master_flavor_id',
title: gettext('Flavor of Master Nodes'),
title: gettext('Flavor of Control Plane Nodes'),
type: 'select',
titleMap: masterFlavorTitleMap,
required: true
@ -304,7 +340,6 @@
}
]
}
]
}
]
@ -314,7 +349,7 @@
help: basePath + 'clusters/workflow/network.help.html',
type: 'section',
htmlClass: 'row',
required: true,
required: false,
items: [
{
type: 'section',
@ -333,10 +368,11 @@
key: 'create_network',
title: gettext('Create New Network'),
onChange: function(isNewNetwork) {
if (isNewNetwork) {
model.fixed_network = MODEL_DEFAULTS.fixed_network;
model.fixed_subnet = MODEL_DEFAULTS.fixed_subnet;
}
// Reset relevant field selections
model.fixed_network = MODEL_DEFAULTS.fixed_network;
model.fixed_subnet = MODEL_DEFAULTS.fixed_subnet;
// Network tab has required fields based on this checkbox.
form[0].tabs[2].required = !isNewNetwork;
}
},
{
@ -598,7 +634,7 @@
function changeFixedNetwork(model) {
if (model.fixed_network) {
subnetTitleMap = [{value:"", name: gettext("Choose an existing Subnet")}];
subnetTitleMap = [{value: "", name: gettext("Choose an existing Subnet")}];
angular.forEach(networkTitleMap, function(network) {
if (network.value === model.fixed_network) {
angular.forEach(network.subnets, function(subnet) {
@ -607,10 +643,11 @@
}
});
} else {
fixedSubnets = [{value:"", name: fixedSubnetsInitial}];
model.fixed_subnet = "";
fixedSubnets = [{value: "", name: fixedSubnetsInitial}];
}
// NOTE(dalees): This hardcoded index could be improved by referencing an object instead.
form[0].tabs[2].items[0].items[0].items[3].titleMap = subnetTitleMap;
model.fixed_subnet = MODEL_DEFAULTS.fixed_subnet;
}
function onGetIngressControllers(response) {

View File

@ -0,0 +1,8 @@
---
features:
- |
The Cluster Create workflow has been improved to provide better input
validation.
- |
"Master" has been renamed to "Control Plane" in user facing locations
in line with Kubernetes teminology. The Magnum API still uses old terms.