From: Joshua Schmid Date: Thu, 14 Nov 2019 16:57:31 +0000 (+0100) Subject: mgr/orch: improve commandline parsing X-Git-Tag: v15.1.0~844^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=16f281d21f31919fdbff0925d99f4ba3420a0bb4;p=ceph.git mgr/orch: improve commandline parsing We have to parse options like hostname, network and optional name Examples: "myhost" "myhost=name" "myhost:1.2.3.4" "myhost:1.2.3.4=name" "myhost:1.2.3.0/24" "myhost:1.2.3.4/24=name" "myhost:v2:1.2.3.4:3000/24=name" "myhost:v2:1.2.3.4:3000,v1:1.2.3.4:6789/24=name" This patch improves the durability of the code by using regexes and adds validations for ip_addresses and subnets. This will now also act as the single parser for all inputs. Signed-off-by: Joshua Schmid (cherry picked from commit 4812167228813adc2fa45ff502257ca38ed53530) --- diff --git a/src/pybind/mgr/orchestrator.py b/src/pybind/mgr/orchestrator.py index dbe65149a838..46a517eb398f 100644 --- a/src/pybind/mgr/orchestrator.py +++ b/src/pybind/mgr/orchestrator.py @@ -13,6 +13,7 @@ import string import random import datetime import copy +import re from ceph.deployment import inventory @@ -28,40 +29,72 @@ try: except ImportError: T, G = object, object -def split_host(host): - """ - Split host into host and (optional) daemon name parts - e.g., - "myhost=name" -> ('myhost', 'name') - "myhost" -> ('myhost', None) - """ - # TODO: stricter validation - a = host.split('=', 1) - if len(a) == 1: - return (a[0], None) - else: - assert len(a) == 2 - return tuple(a) - -def split_host_with_network(host): +def parse_host_specs(host, require_network=True): """ Split host into host, network, and (optional) daemon name parts. The network part can be an IP, CIDR, or ceph addrvec like '[v2:1.2.3.4:3300,v1:1.2.3.4:6789]'. e.g., - "myhost:1.2.3.4=name" -> ('myhost', '1.2.3.4', 'name') - "myhost:1.2.3.0/24" -> ('myhost', '1.2.3.0/24', None) + "myhost" + "myhost=name" + "myhost:1.2.3.4" + "myhost:1.2.3.4=name" + "myhost:1.2.3.0/24" + "myhost:1.2.3.4/24=name" + "myhost:v2:1.2.3.4:3000/24=name" + "myhost:v2:1.2.3.4:3000,v1:1.2.3.4:6789/24=name" """ - # TODO: stricter validation - (host, name) = host.split('=', 1) - parts = host.split(":", 1) - if len(parts) == 1: - return (parts[0], None, name) - elif len(parts) == 2: - return (parts[0], parts[1], name) - else: - raise RuntimeError("Invalid host specification: " - "'{}'".format(host)) + # Matches from start to : or = or until end of string + host_re = r'^(.*?)(:|=|$)' + # Matches from : to = or until end of string + ip_re = r':(.*?)(=|$)' + # Matches from = to end of string + name_re = r'=(.*?)$' + + from collections import namedtuple + HostSpec = namedtuple('HostSpec', ['hostname', 'network', 'name']) + # assign defaults + host_spec = HostSpec('', '', '') + + match_host = re.search(host_re, host) + if match_host: + host_spec = host_spec._replace(hostname=match_host.group(1)) + + ip_match = re.search(ip_re, host) + if ip_match: + host_spec = host_spec._replace(network=ip_match.group(1)) + + name_match = re.search(name_re, host) + if name_match: + host_spec = host_spec._replace(name=name_match.group(1)) + + if not require_network: + return host_spec + + from ipaddress import ip_network, ip_address + try: + networks = list() + network = host_spec.network + # in case we have v2:1.2.3.4:3000,v1:1.2.3.4:6478 + if ',' in network: + net1, net2 = network.split(',') + networks.extend([net1, net2]) + # if we only have one network string + else: + networks.append(network) + for network in networks: + # only if we have versioned network configs + if network.startswith('v'): + network = network.split(':')[1] + # if subnets are defined, also verify the validity + if '/' in network: + ip_network(network) + else: + ip_address(network) + except ValueError: + raise Exception("No valid IPv4/IPv6 address provided") + + return host_spec class OrchestratorError(Exception): """