except ImportError:
T, G = object, object
+
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]'.
+ 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"
"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"
+ "myhost:1.2.3.0/24=name"
+ "myhost:[v2:1.2.3.4:3000]=name"
+ "myhost:[v2:1.2.3.4:3000,v1:1.2.3.4:6789]=name"
"""
# Matches from start to : or = or until end of string
host_re = r'^(.*?)(:|=|$)'
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))
+ ip_match = re.search(ip_re, host)
+ if ip_match:
+ host_spec = host_spec._replace(network=ip_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]
+ 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:
+ networks = [x for x in network.split(',')]
+ else:
+ networks.append(network)
+ for network in networks:
+ # only if we have versioned network configs
+ if network.startswith('v') or network.startswith('[v'):
+ network = network.split(':')[1]
+ try:
# 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")
+ except ValueError as e:
+ # logging?
+ raise e
return host_spec
+
class OrchestratorError(Exception):
"""
General orchestrator specific error.
def __init__(self, label=None, nodes=[]):
self.label = label
- self.nodes = list(map(split_host, nodes))
+ self.nodes = [parse_host_specs(x, require_network=False) for x in nodes]
def handle_type_error(method):
@wraps(method)
return self._create_daemon('mon', name or host, host, keyring,
extra_args=extra_args)
- def update_mons(self, num, hosts):
+ def update_mons(self, num, host_specs):
"""
Adjust the number of cluster monitors.
"""
if num < num_mons:
raise NotImplementedError("Removing monitors is not supported.")
- # check that all the hostnames are registered
- self._require_hosts(map(lambda h: h[0], hosts))
+ self.log.debug("Trying to update monitors on: {}".format(host_specs))
+ # check that all the hosts are registered
+ [self._require_hosts(host.hostname) for host in host_specs]
# current support requires a network to be specified
- for host, network, name in hosts:
+ for host, network, _ in host_specs:
if not network:
- raise RuntimeError("Host '{}' missing network part".format(host))
+ raise RuntimeError("Host '{}' is missing a network spec".format(host))
daemons = self._get_services('mon')
- for _, _, name in hosts:
+ for _, _, name in host_specs:
if name and len([d for d in daemons if d.service_instance == name]):
raise RuntimeError('name %s alrady exists', name)
# explicit placement: enough hosts provided?
num_new_mons = num - num_mons
- if len(hosts) < num_new_mons:
+ if len(host_specs) < num_new_mons:
raise RuntimeError("Error: {} hosts provided, expected {}".format(
- len(hosts), num_new_mons))
+ len(host_specs), num_new_mons))
self.log.info("creating {} monitors on hosts: '{}'".format(
- num_new_mons, ",".join(map(lambda h: ":".join(h), hosts))))
+ num_new_mons, ",".join(map(lambda h: ":".join(h), host_specs))))
# TODO: we may want to chain the creation of the monitors so they join
# the quorum one at a time.
results = []
- for host, network, name in hosts:
+ for host, network, name in host_specs:
result = self._worker_pool.apply_async(self._create_mon,
(host, network, name))
+
results.append(result)
return SSHWriteCompletion(results)
return self._create_daemon('mgr', name, host, keyring)
- def update_mgrs(self, num, hosts):
+ def update_mgrs(self, num, host_specs):
"""
Adjust the number of cluster managers.
"""
if num == num_mgrs:
return SSHWriteCompletionReady("The requested number of managers exist.")
+ self.log.debug("Trying to update managers on: {}".format(host_specs))
# check that all the hosts are registered
- self._require_hosts(map(lambda h: h[0], hosts))
+ [self._require_hosts(host.hostname) for host in host_specs]
results = []
if num < num_mgrs:
# cluster doesn't see
connected = []
mgr_map = self.get("mgr_map")
- if mgr_map["active_name"]:
- connected.append(mgr_map['active_name'])
- for standby in mgr_map['standbys']:
- connected.append(standby['name'])
+ if mgr_map.get("active_name", {}):
+ connected.append(mgr_map.get('active_name', ''))
+ for standby in mgr_map.get('standbys', []):
+ connected.append(standby.get('name', ''))
for d in daemons:
if d.service_instance not in connected:
result = self._worker_pool.apply_async(
# we assume explicit placement by which there are the same number of
# hosts specified as the size of increase in number of daemons.
num_new_mgrs = num - num_mgrs
- if len(hosts) < num_new_mgrs:
+ if len(host_specs) < num_new_mgrs:
raise RuntimeError(
"Error: {} hosts provided, expected {}".format(
- len(hosts), num_new_mgrs))
+ len(host_specs), num_new_mgrs))
+
+ for host_spec in host_specs:
+ if host_spec.name and len([d for d in daemons if d.service_instance == host_spec.name]):
+ raise RuntimeError('name %s alrady exists', host_spec.name)
- for _, name in hosts:
- if name and len([d for d in daemons if d.service_instance == name]):
- raise RuntimeError('name %s alrady exists', name)
+ for host_spec in host_specs:
+ if host_spec.name and len([d for d in daemons if d.service_instance == host_spec.name]):
+ raise RuntimeError('name %s alrady exists', host_spec.name)
self.log.info("creating {} managers on hosts: '{}'".format(
- num_new_mgrs, ",".join(map(lambda h: h[0], hosts))))
+ num_new_mgrs, ",".join([spec.hostname for spec in host_specs])))
- for i in range(num_new_mgrs):
- host, name = hosts[i]
- if not name:
- name = self.get_unique_name(daemons)
+ for host_spec in host_specs:
+ name = host_spec.name or self.get_unique_name(daemons)
result = self._worker_pool.apply_async(self._create_mgr,
- (host, name))
+ (host_spec.hostname, name))
results.append(result)
return SSHWriteCompletion(results)
daemons = self._get_services('mds')
results = []
num_added = 0
- for host, name in spec.placement.nodes:
+ for host, _, name in spec.placement.nodes:
if num_added >= spec.count:
break
mds_id = self.get_unique_name(daemons, spec.name, name)
daemons = self._get_services('rgw')
results = []
num_added = 0
- for host, name in spec.placement.nodes:
+ for host, _, name in spec.placement.nodes:
if num_added >= spec.count:
break
rgw_id = self.get_unique_name(daemons, spec.name, name)
daemons = self._get_services('rbd-mirror')
results = []
num_added = 0
- for host, name in spec.placement.nodes:
+ for host, _, name in spec.placement.nodes:
if num_added >= spec.count:
break
daemon_id = self.get_unique_name(daemons, None, name)
from orchestrator import ReadCompletion, raise_if_exception, RGWSpec
from orchestrator import InventoryNode, ServiceDescription
from orchestrator import OrchestratorValidationError
+from orchestrator import parse_host_specs
+
+
+@pytest.mark.parametrize("test_input,expected, require_network",
+ [("myhost", ('myhost', '', ''), False),
+ ("myhost=sname", ('myhost', '', 'sname'), False),
+ ("myhost:10.1.1.10", ('myhost', '10.1.1.10', ''), True),
+ ("myhost:10.1.1.10=sname", ('myhost', '10.1.1.10', 'sname'), True),
+ ("myhost:10.1.1.0/32", ('myhost', '10.1.1.0/32', ''), True),
+ ("myhost:10.1.1.0/32=sname", ('myhost', '10.1.1.0/32', 'sname'), True),
+ ("myhost:[v1:10.1.1.10:6789]", ('myhost', '[v1:10.1.1.10:6789]', ''), True),
+ ("myhost:[v1:10.1.1.10:6789]=sname", ('myhost', '[v1:10.1.1.10:6789]', 'sname'), True),
+ ("myhost:[v1:10.1.1.10:6789,v2:10.1.1.11:3000]", ('myhost', '[v1:10.1.1.10:6789,v2:10.1.1.11:3000]', ''), True),
+ ("myhost:[v1:10.1.1.10:6789,v2:10.1.1.11:3000]=sname", ('myhost', '[v1:10.1.1.10:6789,v2:10.1.1.11:3000]', 'sname'), True),
+ ])
+def test_parse_host_specs(test_input, expected, require_network):
+ ret = parse_host_specs(test_input, require_network=require_network)
+ assert ret == expected
+
+@pytest.mark.parametrize("test_input",
+ # wrong subnet
+ [("myhost:1.1.1.1/24"),
+ # wrong ip format
+ ("myhost:1"),
+ # empty string
+ ("myhost=1"),
+ ])
+def test_parse_host_specs_raises(test_input):
+ with pytest.raises(ValueError):
+ ret = parse_host_specs(test_input)
def _test_resource(data, resource_class, extra=None):