Skip to content

Commit

Permalink
implement node labels (#1346)
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaofang Zhang committed Mar 4, 2020
1 parent a6a34f0 commit e6976a4
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 1 deletion.
3 changes: 3 additions & 0 deletions src/aks-preview/azext_aks_preview/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,9 @@
- name: --public-ip-per-vm
type: bool
short-summary: Each node will have a public ip.
- name: --labels
type: string
short-summary: The node labels for the node pool. You can't change the node labels through CLI after the node pool is created. See https://aka.ms/node-labels for syntax of labels.
"""

helps['aks nodepool scale'] = """
Expand Down
4 changes: 3 additions & 1 deletion src/aks-preview/azext_aks_preview/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
validate_nodepool_name, validate_vm_set_type, validate_load_balancer_sku,
validate_load_balancer_outbound_ips, validate_load_balancer_outbound_ip_prefixes,
validate_taints, validate_priority, validate_eviction_policy, validate_spot_max_price, validate_acr, validate_user,
validate_load_balancer_outbound_ports, validate_load_balancer_idle_timeout, validate_nodepool_tags)
validate_load_balancer_outbound_ports, validate_load_balancer_idle_timeout, validate_nodepool_tags, validate_nodepool_labels)
from ._consts import CONST_OUTBOUND_TYPE_LOAD_BALANCER, \
CONST_OUTBOUND_TYPE_USER_DEFINED_ROUTING, CONST_SCALE_SET_PRIORITY_REGULAR, CONST_SCALE_SET_PRIORITY_SPOT, \
CONST_SPOT_EVICTION_POLICY_DELETE, CONST_SPOT_EVICTION_POLICY_DEALLOCATE
Expand Down Expand Up @@ -53,6 +53,7 @@ def load_arguments(self, _):
c.argument('nodepool_name', type=str, default='nodepool1',
help='Node pool name, upto 12 alphanumeric characters', validator=validate_nodepool_name)
c.argument('nodepool_tags', nargs='*', validator=validate_nodepool_tags, help='space-separated tags: key[=value] [key[=value] ...]. Use "" to clear existing tags.')
c.argument('nodepool_labels', nargs='*', validator=validate_nodepool_labels, help='space-separated labels: key[=value] [key[=value] ...]. You can not change the node labels through CLI after creation. See https://aka.ms/node-labels for syntax of labels.')
c.argument('ssh_key_value', required=False, type=file_type, default=os.path.join('~', '.ssh', 'id_rsa.pub'),
completer=FilesCompleter(), validator=validate_ssh_key)
c.argument('aad_client_app_id')
Expand Down Expand Up @@ -138,6 +139,7 @@ def load_arguments(self, _):
c.argument('priority', arg_type=get_enum_type([CONST_SCALE_SET_PRIORITY_REGULAR, CONST_SCALE_SET_PRIORITY_SPOT]), validator=validate_priority)
c.argument('eviction_policy', arg_type=get_enum_type([CONST_SPOT_EVICTION_POLICY_DELETE, CONST_SPOT_EVICTION_POLICY_DEALLOCATE]), validator=validate_eviction_policy)
c.argument('spot_max_price', type=float, validator=validate_spot_max_price)
c.argument('labels', nargs='*', validator=validate_nodepool_labels)

for scope in ['aks nodepool show', 'aks nodepool delete', 'aks nodepool scale', 'aks nodepool upgrade', 'aks nodepool update']:
with self.argument_context(scope) as c:
Expand Down
72 changes: 72 additions & 0 deletions src/aks-preview/azext_aks_preview/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,75 @@ def _extract_cluster_autoscaler_params(namespace):
for item in namespace.cluster_autoscaler_profile:
params_dict.update(validate_tag(item))
namespace.cluster_autoscaler_profile = params_dict


def validate_nodepool_labels(namespace):
"""Validates that provided node labels is a valid format"""

if hasattr(namespace, 'nodepool_labels'):
labels = namespace.nodepool_labels
else:
labels = namespace.labels

if labels is None:
return

if isinstance(labels, list):
labels_dict = {}
for item in labels:
labels_dict.update(validate_label(item))
after_validation_labels = labels_dict
else:
after_validation_labels = validate_label(labels)

if hasattr(namespace, 'nodepool_labels'):
namespace.nodepool_labels = after_validation_labels
else:
namespace.labels = after_validation_labels


def validate_label(label):
"""Validates that provided label is a valid format"""
prefix_regex = re.compile(r"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$")
name_regex = re.compile(r"^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$")
value_regex = re.compile(r"^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$")

if label == "":
return {}
kv = label.split('=')
if len(kv) != 2:
raise CLIError("Invalid label: %s. Label definition must be of format name=value." % label)
name_parts = kv[0].split('/')
if len(name_parts) == 1:
name = name_parts[0]
elif len(name_parts) == 2:
prefix = name_parts[0]
if not prefix or len(prefix) > 253:
raise CLIError("Invalid label: %s. Label prefix can't be empty or more than 253 chars." % label)
if not prefix_regex.match(prefix):
raise CLIError("Invalid label: %s. Prefix part a DNS-1123 label must consist of lower case alphanumeric "
"characters or '-', and must start and end with an alphanumeric character" % label)
name = name_parts[1]
else:
raise CLIError("Invalid label: %s. A qualified name must consist of alphanumeric characters, '-', '_' "
"or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or "
"'my.name', or '123-abc') with an optional DNS subdomain prefix and '/' "
"(e.g. 'example.com/MyName')" % label)

# validate label name
if not name or len(name) > 63:
raise CLIError("Invalid label: %s. Label name can't be empty or more than 63 chars." % label)
if not name_regex.match(name):
raise CLIError("Invalid label: %s. A qualified name must consist of alphanumeric characters, '-', '_' "
"or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or "
"'my.name', or '123-abc') with an optional DNS subdomain prefix and '/' (e.g. "
"'example.com/MyName')" % label)

# validate label value
if len(kv[1]) > 63:
raise CLIError("Invalid label: %s. Label must be more than 63 chars." % label)
if not value_regex.match(kv[1]):
raise CLIError("Invalid label: %s. A valid label must be an empty string or consist of alphanumeric "
"characters, '-', '_' or '.', and must start and end with an alphanumeric character" % label)

return {kv[0]: kv[1]}
4 changes: 4 additions & 0 deletions src/aks-preview/azext_aks_preview/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,7 @@ def aks_create(cmd, # pylint: disable=too-many-locals,too-many-statements,to
node_count=3,
nodepool_name="nodepool1",
nodepool_tags=None,
nodepool_labels=None,
service_principal=None, client_secret=None,
no_ssh_key=False,
disable_rbac=None,
Expand Down Expand Up @@ -779,6 +780,7 @@ def aks_create(cmd, # pylint: disable=too-many-locals,too-many-statements,to
agent_pool_profile = ManagedClusterAgentPoolProfile(
name=_trim_nodepoolname(nodepool_name), # Must be 12 chars or less before ACS RP adds to it
tags=nodepool_tags,
node_labels=nodepool_labels,
count=int(node_count),
vm_size=node_vm_size,
os_type="Linux",
Expand Down Expand Up @@ -2031,6 +2033,7 @@ def aks_agentpool_add(cmd, # pylint: disable=unused-argument,too-many-local
eviction_policy=CONST_SPOT_EVICTION_POLICY_DELETE,
spot_max_price=float('nan'),
public_ip_per_vm=False,
labels=None,
no_wait=False):
instances = client.list(resource_group_name, cluster_name)
for agentpool_profile in instances:
Expand All @@ -2057,6 +2060,7 @@ def aks_agentpool_add(cmd, # pylint: disable=unused-argument,too-many-local
agent_pool = AgentPool(
name=nodepool_name,
tags=tags,
node_labels=labels,
count=int(node_count),
vm_size=node_vm_size,
os_type=os_type,
Expand Down

0 comments on commit e6976a4

Please sign in to comment.