]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/orch: improve commandline parsing
authorJoshua Schmid <jschmid@suse.de>
Thu, 14 Nov 2019 16:57:31 +0000 (17:57 +0100)
committerJoshua Schmid <jschmid@suse.de>
Fri, 15 Nov 2019 11:59:58 +0000 (12:59 +0100)
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 <jschmid@suse.de>
(cherry picked from commit 4812167228813adc2fa45ff502257ca38ed53530)

src/pybind/mgr/orchestrator.py

index dbe65149a83861085429cf00ed9a05f6cd9be189..46a517eb398fe3fbd220c091cc44783057272592 100644 (file)
@@ -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):
     """