From eee74d31b8a5666224c5ba5ed8a14e4cc28b2e16 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Thu, 22 Nov 2018 13:35:10 +0100 Subject: [PATCH] Completely switch to openstacksdk Change-Id: I1729797fa03095d200c7334281915abc284b5732 --- lower-constraints.txt | 3 +- metalsmith/_config.py | 65 +- metalsmith/_format.py | 4 +- metalsmith/_instance.py | 22 +- metalsmith/_nics.py | 36 +- metalsmith/_os_api.py | 182 ---- metalsmith/_provisioner.py | 243 ++--- metalsmith/_scheduler.py | 37 +- metalsmith/_utils.py | 74 +- metalsmith/sources.py | 20 +- metalsmith/test/test_cmd.py | 14 +- metalsmith/test/test_config.py | 33 +- metalsmith/test/test_instance.py | 14 +- metalsmith/test/test_os_api.py | 129 --- metalsmith/test/test_provisioner.py | 1483 ++++++++++++--------------- metalsmith/test/test_scheduler.py | 78 +- playbooks/integration/exercise.yaml | 8 +- requirements.txt | 3 +- 18 files changed, 1003 insertions(+), 1445 deletions(-) delete mode 100644 metalsmith/_os_api.py delete mode 100644 metalsmith/test/test_os_api.py diff --git a/lower-constraints.txt b/lower-constraints.txt index b3f120f..cf3274f 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -4,9 +4,8 @@ fixtures==3.0.0 flake8-import-order==0.13 hacking==1.0.0 mock==2.0 -openstacksdk==0.17.0 +openstacksdk==0.22.0 pbr==2.0.0 -python-ironicclient==1.14.0 Pygments==2.2.0 requests==2.18.4 six==1.10.0 diff --git a/metalsmith/_config.py b/metalsmith/_config.py index 1b5b775..0b6a1ec 100644 --- a/metalsmith/_config.py +++ b/metalsmith/_config.py @@ -13,11 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -import contextlib import json -import os -import shutil -import tempfile +import logging + +from openstack.baremetal import configdrive + +from metalsmith import _utils + + +LOG = logging.getLogger(__name__) class InstanceConfig(object): @@ -56,13 +60,12 @@ class InstanceConfig(object): kwargs.setdefault('ssh_authorized_keys', self.ssh_keys) self.users.append(kwargs) - @contextlib.contextmanager - def build_configdrive_directory(self, node, hostname): - """Build a configdrive from the provided information. + def build_configdrive(self, node, hostname): + """Make the config drive. :param node: `Node` object. :param hostname: instance hostname. - :return: a context manager yielding a directory with files + :return: configdrive contents as a base64-encoded string. """ # NOTE(dtantsur): CirrOS does not understand lists if isinstance(self.ssh_keys, list): @@ -70,33 +73,25 @@ class InstanceConfig(object): else: ssh_keys = self.ssh_keys - d = tempfile.mkdtemp() - try: - metadata = {'public_keys': ssh_keys, - 'uuid': node.uuid, - 'name': node.name, - 'hostname': hostname, - 'launch_index': 0, - 'availability_zone': '', - 'files': [], - 'meta': {}} - user_data = {} - if self.users: - user_data['users'] = self.users + metadata = {'public_keys': ssh_keys, + 'uuid': node.id, + 'name': node.name, + 'hostname': hostname, + 'launch_index': 0, + 'availability_zone': '', + 'files': [], + 'meta': {}} + user_data = {} + user_data_bin = None - for version in ('2012-08-10', 'latest'): - subdir = os.path.join(d, 'openstack', version) - if not os.path.exists(subdir): - os.makedirs(subdir) + if self.users: + user_data['users'] = self.users - with open(os.path.join(subdir, 'meta_data.json'), 'w') as fp: - json.dump(metadata, fp) + if user_data: + user_data_bin = ("#cloud-config\n" + json.dumps(user_data)).encode( + 'utf-8') - if user_data: - with open(os.path.join(subdir, 'user_data'), 'w') as fp: - fp.write("#cloud-config\n") - json.dump(user_data, fp) - - yield d - finally: - shutil.rmtree(d) + LOG.debug('Generating configdrive tree for node %(node)s with ' + 'metadata %(meta)s', {'node': _utils.log_res(node), + 'meta': metadata}) + return configdrive.build(metadata, user_data_bin) diff --git a/metalsmith/_format.py b/metalsmith/_format.py index dc6a2bc..d107941 100644 --- a/metalsmith/_format.py +++ b/metalsmith/_format.py @@ -52,12 +52,12 @@ class DefaultFormat(object): else: message = "Unprovisioning started for node %(node)s" - _print(message, node=_utils.log_node(node)) + _print(message, node=_utils.log_res(node)) def show(self, instances): for instance in instances: _print("Node %(node)s, current state is %(state)s", - node=_utils.log_node(instance.node), state=instance.state) + node=_utils.log_res(instance.node), state=instance.state) if instance.is_deployed: ips = instance.ip_addresses() diff --git a/metalsmith/_instance.py b/metalsmith/_instance.py index 3b76baa..5a3cd52 100644 --- a/metalsmith/_instance.py +++ b/metalsmith/_instance.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from metalsmith import _os_api +from metalsmith import _utils _PROGRESS_STATES = frozenset(['deploying', 'wait call-back', @@ -30,15 +30,15 @@ _HEALTHY_STATES = _PROGRESS_STATES | _ACTIVE_STATES class Instance(object): """Instance status in metalsmith.""" - def __init__(self, api, node): - self._api = api - self._uuid = node.uuid + def __init__(self, connection, node): + self._connection = connection + self._uuid = node.id self._node = node @property def hostname(self): """Node's hostname.""" - return self._node.instance_info.get(_os_api.HOSTNAME_FIELD) + return self._node.instance_info.get(_utils.GetNodeMixin.HOSTNAME_FIELD) def ip_addresses(self): """Returns IP addresses for this instance. @@ -61,12 +61,12 @@ class Instance(object): @property def _is_deployed_by_metalsmith(self): - return _os_api.HOSTNAME_FIELD in self._node.instance_info + return _utils.GetNodeMixin.HOSTNAME_FIELD in self._node.instance_info @property def is_healthy(self): """Whether the node is not at fault or maintenance.""" - return self.state in _HEALTHY_STATES and not self._node.maintenance + return self.state in _HEALTHY_STATES and not self._node.is_maintenance def nics(self): """List NICs for this instance. @@ -75,10 +75,10 @@ class Instance(object): with full representations of their networks. """ result = [] - vifs = self._api.list_node_attached_ports(self.node) + vifs = self._connection.baremetal.list_node_vifs(self.node) for vif in vifs: - port = self._api.connection.network.get_port(vif.id) - port.network = self._api.connection.network.get_network( + port = self._connection.network.get_port(vif) + port.network = self._connection.network.get_network( port.network_id) result.append(port) return result @@ -110,7 +110,7 @@ class Instance(object): elif prov_state in _ERROR_STATES: return 'error' elif prov_state in _ACTIVE_STATES: - if self._node.maintenance: + if self._node.is_maintenance: return 'maintenance' else: return 'active' diff --git a/metalsmith/_nics.py b/metalsmith/_nics.py index 785002e..8be912d 100644 --- a/metalsmith/_nics.py +++ b/metalsmith/_nics.py @@ -26,7 +26,7 @@ LOG = logging.getLogger(__name__) class NICs(object): """Requested NICs.""" - def __init__(self, api, node, nics): + def __init__(self, connection, node, nics): if nics is None: nics = [] @@ -38,7 +38,7 @@ class NICs(object): raise TypeError("Each NIC must be a dict got %s" % nic) self._node = node - self._api = api + self._connection = connection self._nics = nics self._validated = None self.created_ports = [] @@ -68,25 +68,26 @@ class NICs(object): for nic_type, nic in self._validated: if nic_type == 'network': - port = self._api.connection.network.create_port(**nic) + port = self._connection.network.create_port(**nic) self.created_ports.append(port.id) LOG.info('Created port %(port)s for node %(node)s with ' '%(nic)s', {'port': _utils.log_res(port), - 'node': _utils.log_node(self._node), + 'node': _utils.log_res(self._node), 'nic': nic}) else: port = nic - self._api.attach_port_to_node(self._node.uuid, port.id) + self._connection.baremetal.attach_vif_to_node(self._node, + port.id) LOG.info('Attached port %(port)s to node %(node)s', {'port': _utils.log_res(port), - 'node': _utils.log_node(self._node)}) + 'node': _utils.log_res(self._node)}) self.attached_ports.append(port.id) def detach_and_delete_ports(self): """Detach attached port and delete previously created ones.""" - detach_and_delete_ports(self._api, self._node, self.created_ports, - self.attached_ports) + detach_and_delete_ports(self._connection, self._node, + self.created_ports, self.attached_ports) def _get_port(self, nic): """Validate and get the NIC information for a port. @@ -100,7 +101,7 @@ class NICs(object): 'Unexpected fields for a port: %s' % ', '.join(unexpected)) try: - port = self._api.connection.network.find_port( + port = self._connection.network.find_port( nic['port'], ignore_missing=False) except Exception as exc: raise exceptions.InvalidNIC( @@ -122,7 +123,7 @@ class NICs(object): 'Unexpected fields for a network: %s' % ', '.join(unexpected)) try: - network = self._api.connection.network.find_network( + network = self._connection.network.find_network( nic['network'], ignore_missing=False) except Exception as exc: raise exceptions.InvalidNIC( @@ -136,33 +137,32 @@ class NICs(object): return port_args -def detach_and_delete_ports(api, node, created_ports, attached_ports): +def detach_and_delete_ports(connection, node, created_ports, attached_ports): """Detach attached port and delete previously created ones. - :param api: `Api` instance. + :param connection: `openstacksdk.Connection` instance. :param node: `Node` object to detach ports from. :param created_ports: List of IDs of previously created ports. :param attached_ports: List of IDs of previously attached_ports. """ for port_id in set(attached_ports + created_ports): LOG.debug('Detaching port %(port)s from node %(node)s', - {'port': port_id, 'node': node.uuid}) + {'port': port_id, 'node': _utils.log_res(node)}) try: - api.detach_port_from_node(node, port_id) + connection.baremetal.detach_vif_from_node(node, port_id) except Exception as exc: LOG.debug('Failed to remove VIF %(vif)s from node %(node)s, ' 'assuming already removed: %(exc)s', - {'vif': port_id, 'node': _utils.log_node(node), + {'vif': port_id, 'node': _utils.log_res(node), 'exc': exc}) for port_id in created_ports: LOG.debug('Deleting port %s', port_id) try: - api.connection.network.delete_port(port_id, - ignore_missing=False) + connection.network.delete_port(port_id, ignore_missing=False) except Exception as exc: LOG.warning('Failed to delete neutron port %(port)s: %(exc)s', {'port': port_id, 'exc': exc}) else: LOG.info('Deleted port %(port)s for node %(node)s', - {'port': port_id, 'node': _utils.log_node(node)}) + {'port': port_id, 'node': _utils.log_res(node)}) diff --git a/metalsmith/_os_api.py b/metalsmith/_os_api.py deleted file mode 100644 index b646516..0000000 --- a/metalsmith/_os_api.py +++ /dev/null @@ -1,182 +0,0 @@ -# Copyright 2015-2018 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import contextlib -import logging - -from ironicclient import client as ir_client -import six - -from metalsmith import _utils - - -LOG = logging.getLogger(__name__) -HOSTNAME_FIELD = 'metalsmith_hostname' - - -class _Remove(object): - """Indicator that a field should be removed.""" - - __slots__ = () - - def __repr__(self): - """Allow nicer logging.""" - return '' - - -REMOVE = _Remove() - - -class DictWithAttrs(dict): - __slots__ = () - - def __getattr__(self, attr): - try: - return self[attr] - except KeyError: - super(DictWithAttrs, self).__getattr__(attr) - - -class API(object): - """Various OpenStack API's.""" - - IRONIC_VERSION = '1' - # TODO(dtantsur): use openstacksdk and stop hardcoding this here. - # 1.46 (Rocky) adds conductor_group. - IRONIC_MICRO_VERSION = '1.46' - - _node_list = None - - def __init__(self, session, connection): - self.ironic = ir_client.get_client( - self.IRONIC_VERSION, session=session, - os_ironic_api_version=self.IRONIC_MICRO_VERSION) - self.connection = connection - - def _nodes_for_lookup(self): - return self.list_nodes(maintenance=None, - associated=None, - provision_state=None, - fields=['uuid', 'name', 'instance_info']) - - def attach_port_to_node(self, node, port_id): - self.ironic.node.vif_attach(_node_id(node), port_id) - - @contextlib.contextmanager - def cache_node_list_for_lookup(self): - if self._node_list is None: - self._node_list = self._nodes_for_lookup() - yield self._node_list - self._node_list = None - - def detach_port_from_node(self, node, port_id): - self.ironic.node.vif_detach(_node_id(node), port_id) - - def find_node_by_hostname(self, hostname): - nodes = self._node_list or self._nodes_for_lookup() - existing = [n for n in nodes - if n.instance_info.get(HOSTNAME_FIELD) == hostname] - if len(existing) > 1: - raise RuntimeError("More than one node found with hostname " - "%(host)s: %(nodes)s" % - {'host': hostname, - 'nodes': ', '.join(_utils.log_node(n) - for n in existing)}) - elif not existing: - return None - else: - # Fetch the complete node record - return self.get_node(existing[0].uuid, accept_hostname=False) - - def get_node(self, node, refresh=False, accept_hostname=False): - if isinstance(node, six.string_types): - if accept_hostname and _utils.is_hostname_safe(node): - by_hostname = self.find_node_by_hostname(node) - if by_hostname is not None: - return by_hostname - - return self.ironic.node.get(node) - elif hasattr(node, 'node'): - # Instance object - node = node.node - else: - node = node - - if refresh: - return self.ironic.node.get(node.uuid) - else: - return node - - def list_node_attached_ports(self, node): - return self.ironic.node.vif_list(_node_id(node)) - - def list_node_ports(self, node): - return self.ironic.node.list_ports(_node_id(node), limit=0) - - def list_nodes(self, maintenance=False, associated=False, - provision_state='available', **filters): - if 'fields' not in filters: - filters['detail'] = True - return self.ironic.node.list(limit=0, maintenance=maintenance, - associated=associated, - provision_state=provision_state, - **filters) - - def node_action(self, node, action, **kwargs): - self.ironic.node.set_provision_state(_node_id(node), action, **kwargs) - - def release_node(self, node): - return self.update_node(_node_id(node), instance_uuid=REMOVE) - - def reserve_node(self, node, instance_uuid): - return self.update_node(_node_id(node), instance_uuid=instance_uuid) - - def update_node(self, node, *args, **attrs): - if args: - attrs.update(args[0]) - patches = _convert_patches(attrs) - return self.ironic.node.update(_node_id(node), patches) - - def validate_node(self, node, validate_deploy=False): - ifaces = ['power', 'management'] - if validate_deploy: - ifaces += ['deploy'] - - validation = self.ironic.node.validate(_node_id(node)) - for iface in ifaces: - result = getattr(validation, iface) - if not result['result']: - raise RuntimeError('%s: %s' % (iface, result['reason'])) - - -def _node_id(node): - if isinstance(node, six.string_types): - return node - else: - return node.uuid - - -def _convert_patches(attrs): - patches = [] - for key, value in attrs.items(): - if not key.startswith('/'): - key = '/' + key - - if value is REMOVE: - patches.append({'op': 'remove', 'path': key}) - else: - patches.append({'op': 'add', 'path': key, 'value': value}) - - return patches diff --git a/metalsmith/_provisioner.py b/metalsmith/_provisioner.py index 1378975..ebc9f0f 100644 --- a/metalsmith/_provisioner.py +++ b/metalsmith/_provisioner.py @@ -16,7 +16,6 @@ import logging import random import sys -import time import warnings from openstack import connection @@ -25,7 +24,6 @@ import six from metalsmith import _config from metalsmith import _instance from metalsmith import _nics -from metalsmith import _os_api from metalsmith import _scheduler from metalsmith import _utils from metalsmith import exceptions @@ -38,7 +36,7 @@ _CREATED_PORTS = 'metalsmith_created_ports' _ATTACHED_PORTS = 'metalsmith_attached_ports' -class Provisioner(object): +class Provisioner(_utils.GetNodeMixin): """API to deploy/undeploy nodes with OpenStack. :param session: `Session` object (from ``keystoneauth``) to use when @@ -63,11 +61,7 @@ class Provisioner(object): 'but not both') else: self.connection = connection.Connection(config=cloud_region) - # NOTE(dtantsur): Connection.baremetal is a keystoneauth Adapter - # for baremetal API. - session = self.connection.baremetal - self._api = _os_api.API(session, self.connection) self._dry_run = dry_run def reserve_node(self, resource_class=None, conductor_group=None, @@ -103,13 +97,15 @@ class Provisioner(object): DeprecationWarning) if candidates: - nodes = [self._api.get_node(node) for node in candidates] + nodes = [self._get_node(node) for node in candidates] filters = [ _scheduler.NodeTypeFilter(resource_class, conductor_group), ] else: - nodes = self._api.list_nodes(resource_class=resource_class, - conductor_group=conductor_group) + nodes = list(self.connection.baremetal.nodes( + resource_class=resource_class, + conductor_group=conductor_group, + details=True)) if not nodes: raise exceptions.NodesNotFound(resource_class, conductor_group) # Ensure parallel executions don't try nodes in the same sequence @@ -124,18 +120,16 @@ class Provisioner(object): if predicate is not None: filters.append(_scheduler.CustomPredicateFilter(predicate)) - reserver = _scheduler.IronicReserver(self._api) + instance_info = {} + if capabilities: + instance_info['capabilities'] = capabilities + if traits: + instance_info['traits'] = traits + reserver = _scheduler.IronicReserver(self.connection, + instance_info) + node = _scheduler.schedule_node(nodes, filters, reserver, dry_run=self._dry_run) - - update = {} - if capabilities: - update['/instance_info/capabilities'] = capabilities - if traits: - update['/instance_info/traits'] = traits - if update: - node = self._api.update_node(node, update) - LOG.debug('Reserved node: %s', node) return node @@ -148,28 +142,29 @@ class Provisioner(object): reserved by us or are in maintenance mode. """ try: - node = self._api.get_node(node) + node = self._get_node(node) except Exception as exc: raise exceptions.InvalidNode('Cannot find node %(node)s: %(exc)s' % {'node': node, 'exc': exc}) - if not node.instance_uuid: + if not node.instance_id: if not self._dry_run: LOG.debug('Node %s not reserved yet, reserving', - _utils.log_node(node)) - self._api.reserve_node(node, instance_uuid=node.uuid) - elif node.instance_uuid != node.uuid: + _utils.log_res(node)) + self.connection.baremetal.update_node( + node, instance_id=node.id) + elif node.instance_id != node.id: raise exceptions.InvalidNode('Node %(node)s already reserved ' 'by instance %(inst)s outside of ' 'metalsmith, cannot deploy on it' % - {'node': _utils.log_node(node), - 'inst': node.instance_uuid}) + {'node': _utils.log_res(node), + 'inst': node.instance_id}) - if node.maintenance: + if node.is_maintenance: raise exceptions.InvalidNode('Refusing to deploy on node %(node)s ' 'which is in maintenance mode due to ' '%(reason)s' % - {'node': _utils.log_node(node), + {'node': _utils.log_res(node), 'reason': node.maintenance_reason}) return node @@ -187,17 +182,17 @@ class Provisioner(object): if node.name and _utils.is_hostname_safe(node.name): return node.name else: - return node.uuid + return node.id if not _utils.is_hostname_safe(hostname): raise ValueError("%s cannot be used as a hostname" % hostname) - existing = self._api.find_node_by_hostname(hostname) - if existing is not None and existing.uuid != node.uuid: + existing = self._find_node_by_hostname(hostname) + if existing is not None and existing.id != node.id: raise ValueError("The following node already uses hostname " "%(host)s: %(node)s" % {'host': hostname, - 'node': _utils.log_node(existing)}) + 'node': _utils.log_res(existing)}) return hostname @@ -256,7 +251,7 @@ class Provisioner(object): image = sources.GlanceImage(image) node = self._check_node_for_deploy(node) - nics = _nics.NICs(self._api, node, nics) + nics = _nics.NICs(self.connection, node, nics) try: hostname = self._check_hostname(node, hostname) @@ -271,62 +266,71 @@ class Provisioner(object): if self._dry_run: LOG.warning('Dry run, not provisioning node %s', - _utils.log_node(node)) + _utils.log_res(node)) return node nics.create_and_attach_ports() capabilities['boot_option'] = 'netboot' if netboot else 'local' - updates = {'/instance_info/root_gb': root_size_gb, - '/instance_info/capabilities': capabilities, - '/extra/%s' % _CREATED_PORTS: nics.created_ports, - '/extra/%s' % _ATTACHED_PORTS: nics.attached_ports, - '/instance_info/%s' % _os_api.HOSTNAME_FIELD: hostname} - updates.update(image._node_updates(self.connection)) + instance_info = node.instance_info.copy() + instance_info['root_gb'] = root_size_gb + instance_info['capabilities'] = capabilities + instance_info[self.HOSTNAME_FIELD] = hostname + extra = node.extra.copy() + extra[_CREATED_PORTS] = nics.created_ports + extra[_ATTACHED_PORTS] = nics.attached_ports + instance_info.update(image._node_updates(self.connection)) if traits is not None: - updates['/instance_info/traits'] = traits + instance_info['traits'] = traits if swap_size_mb is not None: - updates['/instance_info/swap_mb'] = swap_size_mb + instance_info['swap_mb'] = swap_size_mb - LOG.debug('Updating node %(node)s with %(updates)s', - {'node': _utils.log_node(node), 'updates': updates}) - node = self._api.update_node(node, updates) - self._api.validate_node(node, validate_deploy=True) + LOG.debug('Updating node %(node)s with instance info %(iinfo)s ' + 'and extras %(extra)s', {'node': _utils.log_res(node), + 'iinfo': instance_info, + 'extra': extra}) + node = self.connection.baremetal.update_node( + node, instance_info=instance_info, extra=extra) + self.connection.baremetal.validate_node(node) LOG.debug('Generating a configdrive for node %s', - _utils.log_node(node)) - with config.build_configdrive_directory(node, hostname) as cd: - self._api.node_action(node, 'active', - configdrive=cd) + _utils.log_res(node)) + cd = config.build_configdrive(node, hostname) + # TODO(dtantsur): move this to openstacksdk? + if not isinstance(cd, six.string_types): + cd = cd.decode('utf-8') + LOG.debug('Starting provisioning of node %s', _utils.log_res(node)) + self.connection.baremetal.set_node_provision_state( + node, 'active', config_drive=cd) except Exception: exc_info = sys.exc_info() try: LOG.error('Deploy attempt failed on node %s, cleaning up', - _utils.log_node(node)) + _utils.log_res(node)) self._clean_up(node, nics=nics) except Exception: LOG.exception('Clean up failed') six.reraise(*exc_info) - LOG.info('Provisioning started on node %s', _utils.log_node(node)) + LOG.info('Provisioning started on node %s', _utils.log_res(node)) if wait is not None: LOG.debug('Waiting for node %(node)s to reach state active ' 'with timeout %(timeout)s', - {'node': _utils.log_node(node), 'timeout': wait}) + {'node': _utils.log_res(node), 'timeout': wait}) instance = self.wait_for_provisioning([node], timeout=wait)[0] - LOG.info('Deploy succeeded on node %s', _utils.log_node(node)) + LOG.info('Deploy succeeded on node %s', _utils.log_res(node)) else: # Update the node to return it's latest state - node = self._api.get_node(node, refresh=True) - instance = _instance.Instance(self._api, node) + node = self._get_node(node, refresh=True) + instance = _instance.Instance(self.connection, node) return instance - def wait_for_provisioning(self, nodes, timeout=None, delay=15): + def wait_for_provisioning(self, nodes, timeout=None, delay=None): """Wait for nodes to be provisioned. Loops until all nodes finish provisioning. @@ -336,96 +340,46 @@ class Provisioner(object): :param timeout: How much time (in seconds) to wait for all nodes to finish provisioning. If ``None`` (the default), wait forever (more precisely, until the operation times out on server side). - :param delay: Delay (in seconds) between two provision state checks. + :param delay: DEPRECATED, do not use. :return: List of updated :py:class:`metalsmith.Instance` objects if all succeeded. :raises: :py:class:`metalsmith.exceptions.DeploymentFailure` if the deployment failed or timed out for any nodes. """ - nodes = self._wait_for_state(nodes, 'active', - timeout=timeout, delay=delay) - return [_instance.Instance(self._api, node) for node in nodes] - - def _wait_for_state(self, nodes, state, timeout, delay=15): - if timeout is not None and timeout <= 0: - raise ValueError("The timeout argument must be a positive int") - if delay < 0: - raise ValueError("The delay argument must be a non-negative int") - - failed_nodes = [] - finished_nodes = [] - - deadline = time.time() + timeout if timeout is not None else None - while timeout is None or time.time() < deadline: - remaining_nodes = [] - for node in nodes: - node = self._api.get_node(node, refresh=True, - accept_hostname=True) - if node.provision_state == state: - LOG.debug('Node %(node)s reached state %(state)s', - {'node': _utils.log_node(node), 'state': state}) - finished_nodes.append(node) - elif (node.provision_state == 'error' or - node.provision_state.endswith(' failed')): - LOG.error('Node %(node)s failed deployment: %(error)s', - {'node': _utils.log_node(node), - 'error': node.last_error}) - failed_nodes.append(node) - else: - remaining_nodes.append(node) - - if remaining_nodes: - nodes = remaining_nodes - else: - nodes = [] - break - - LOG.debug('Still waiting for the following nodes to reach state ' - '%(state)s: %(nodes)s', - {'state': state, - 'nodes': ', '.join(_utils.log_node(n) for n in nodes)}) - time.sleep(delay) - - messages = [] - if failed_nodes: - messages.append('the following nodes failed deployment: %s' % - ', '.join('%s (%s)' % (_utils.log_node(node), - node.last_error) - for node in failed_nodes)) - if nodes: - messages.append('deployment timed out for nodes %s' % - ', '.join(_utils.log_node(node) for node in nodes)) - - if messages: - raise exceptions.DeploymentFailure( - 'Deployment failed: %s' % '; '.join(messages), - failed_nodes + nodes) - else: - LOG.debug('All nodes reached state %s', state) - return finished_nodes + if delay is not None: + warnings.warn("The delay argument to wait_for_provisioning is " + "deprecated and has not effect", DeprecationWarning) + nodes = [self._get_node(n, accept_hostname=True) for n in nodes] + nodes = self.connection.baremetal.wait_for_nodes_provision_state( + nodes, 'active', timeout=timeout) + return [_instance.Instance(self.connection, node) for node in nodes] def _clean_up(self, node, nics=None): if nics is None: created_ports = node.extra.get(_CREATED_PORTS, []) attached_ports = node.extra.get(_ATTACHED_PORTS, []) - _nics.detach_and_delete_ports(self._api, node, created_ports, - attached_ports) + _nics.detach_and_delete_ports(self.connection, node, + created_ports, attached_ports) else: nics.detach_and_delete_ports() - update = {'/extra/%s' % item: _os_api.REMOVE - for item in (_CREATED_PORTS, _ATTACHED_PORTS)} - update['/instance_info/%s' % _os_api.HOSTNAME_FIELD] = _os_api.REMOVE - LOG.debug('Updating node %(node)s with %(updates)s', - {'node': _utils.log_node(node), 'updates': update}) + extra = node.extra.copy() + for item in (_CREATED_PORTS, _ATTACHED_PORTS): + extra.pop(item, None) + instance_info = node.instance_info.copy() + instance_info.pop(self.HOSTNAME_FIELD, None) + LOG.debug('Updating node %(node)s with instance info %(iinfo)s ' + 'and extras %(extra)s and releasing the lock', + {'node': _utils.log_res(node), + 'iinfo': instance_info, + 'extra': extra}) try: - self._api.update_node(node, update) + self.connection.baremetal.update_node( + node, instance_info=instance_info, extra=extra, + instance_id=None) except Exception as exc: LOG.debug('Failed to clear node %(node)s extra: %(exc)s', - {'node': _utils.log_node(node), 'exc': exc}) - - LOG.debug('Releasing lock on node %s', _utils.log_node(node)) - self._api.release_node(node) + {'node': _utils.log_res(node), 'exc': exc}) def unprovision_node(self, node, wait=None): """Unprovision a previously provisioned node. @@ -436,21 +390,23 @@ class Provisioner(object): None to return immediately. :return: the latest `Node` object. """ - node = self._api.get_node(node, accept_hostname=True) + node = self._get_node(node, accept_hostname=True) if self._dry_run: LOG.warning("Dry run, not unprovisioning") return self._clean_up(node) - self._api.node_action(node, 'deleted') + node = self.connection.baremetal.set_node_provision_state( + node, 'deleted', wait=False) - LOG.info('Deleting started for node %s', _utils.log_node(node)) + LOG.info('Deleting started for node %s', _utils.log_res(node)) if wait is not None: - self._wait_for_state([node], 'available', timeout=wait) - LOG.info('Node %s undeployed successfully', _utils.log_node(node)) + node = self.connection.baremetal.wait_for_nodes_provision_state( + [node], 'available', timeout=wait)[0] + LOG.info('Node %s undeployed successfully', _utils.log_res(node)) - return self._api.get_node(node, refresh=True) + return node def show_instance(self, instance_id): """Show information about instance. @@ -470,11 +426,11 @@ class Provisioner(object): :return: list of :py:class:`metalsmith.Instance` objects in the same order as ``instances``. """ - with self._api.cache_node_list_for_lookup(): + with self._cache_node_list_for_lookup(): return [ _instance.Instance( - self._api, - self._api.get_node(inst, accept_hostname=True)) + self.connection, + self._get_node(inst, accept_hostname=True)) for inst in instances ] @@ -483,8 +439,9 @@ class Provisioner(object): :return: list of :py:class:`metalsmith.Instance` objects. """ - nodes = self._api.list_nodes(provision_state=None, associated=True) + nodes = self.connection.baremetal.nodes(associated=True, details=True) instances = [i for i in - (_instance.Instance(self._api, node) for node in nodes) + (_instance.Instance(self.connection, node) + for node in nodes) if i._is_deployed_by_metalsmith] return instances diff --git a/metalsmith/_scheduler.py b/metalsmith/_scheduler.py index 9d1737a..91aa45d 100644 --- a/metalsmith/_scheduler.py +++ b/metalsmith/_scheduler.py @@ -17,6 +17,7 @@ import abc import collections import logging +from openstack import exceptions as sdk_exc import six from metalsmith import _utils @@ -100,13 +101,13 @@ def schedule_node(nodes, filters, reserver, dry_run=False): for node in nodes: try: result = reserver(node) - except Exception as exc: + except sdk_exc.SDKException as exc: LOG.debug('Node %(node)s was not reserved (%(exc)s), moving on ' 'to the next one', - {'node': _utils.log_node(node), 'exc': exc}) + {'node': _utils.log_res(node), 'exc': exc}) else: LOG.info('Node %s reserved for deployment', - _utils.log_node(result)) + _utils.log_res(result)) return result LOG.debug('No nodes could be reserved') @@ -149,25 +150,25 @@ class CapabilitiesFilter(Filter): caps = _utils.get_capabilities(node) except Exception: LOG.exception('Malformed capabilities on node %(node)s: %(caps)s', - {'node': _utils.log_node(node), + {'node': _utils.log_res(node), 'caps': node.properties.get('capabilities')}) return False LOG.debug('Capabilities for node %(node)s: %(caps)s', - {'node': _utils.log_node(node), 'caps': caps}) + {'node': _utils.log_res(node), 'caps': caps}) for key, value in self._capabilities.items(): try: node_value = caps[key] except KeyError: LOG.debug('Node %(node)s does not have capability %(cap)s', - {'node': _utils.log_node(node), 'cap': key}) + {'node': _utils.log_res(node), 'cap': key}) return False else: self._counter["%s=%s" % (key, node_value)] += 1 if value != node_value: LOG.debug('Node %(node)s has capability %(cap)s of ' 'value "%(node_val)s" instead of "%(expected)s"', - {'node': _utils.log_node(node), 'cap': key, + {'node': _utils.log_res(node), 'cap': key, 'node_val': node_value, 'expected': value}) return False @@ -197,14 +198,14 @@ class TraitsFilter(Filter): traits = node.traits or [] LOG.debug('Traits for node %(node)s: %(traits)s', - {'node': _utils.log_node(node), 'traits': traits}) + {'node': _utils.log_res(node), 'traits': traits}) for trait in traits: self._counter[trait] += 1 missing = set(self._traits) - set(traits) if missing: LOG.debug('Node %(node)s does not have traits %(missing)s', - {'node': _utils.log_node(node), 'missing': missing}) + {'node': _utils.log_res(node), 'missing': missing}) return False return True @@ -239,24 +240,28 @@ class CustomPredicateFilter(Filter): class IronicReserver(Reserver): - def __init__(self, api): - self._api = api + def __init__(self, connection, instance_info=None): + self._connection = connection self._failed_nodes = [] + self._iinfo = instance_info or {} def validate(self, node): try: - self._api.validate_node(node) - except RuntimeError as exc: + self._connection.baremetal.validate_node( + node, required=('power', 'management')) + except sdk_exc.SDKException as exc: message = ('Node %(node)s failed validation: %(err)s' % - {'node': _utils.log_node(node), 'err': exc}) + {'node': _utils.log_res(node), 'err': exc}) LOG.warning(message) raise exceptions.ValidationFailed(message) def __call__(self, node): try: self.validate(node) - return self._api.reserve_node(node, instance_uuid=node.uuid) - except Exception: + iinfo = dict(node.instance_info or {}, **self._iinfo) + return self._connection.baremetal.update_node( + node, instance_id=node.id, instance_info=iinfo) + except sdk_exc.SDKException: self._failed_nodes.append(node) raise diff --git a/metalsmith/_utils.py b/metalsmith/_utils.py index 9e6cb69..bb19d09 100644 --- a/metalsmith/_utils.py +++ b/metalsmith/_utils.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import contextlib import re import six @@ -20,13 +21,6 @@ import six from metalsmith import exceptions -def log_node(node): - if node.name: - return '%s (UUID %s)' % (node.name, node.uuid) - else: - return node.uuid - - def log_res(res): if getattr(res, 'name', None): return '%s (UUID %s)' % (res.name, res.id) @@ -56,12 +50,12 @@ def get_root_disk(root_size_gb, node): except KeyError: raise exceptions.UnknownRootDiskSize( 'No local_gb for node %s and no root partition size ' - 'specified' % log_node(node)) + 'specified' % log_res(node)) except (TypeError, ValueError, AssertionError): raise exceptions.UnknownRootDiskSize( 'The local_gb for node %(node)s is invalid: ' 'expected positive integer, got %(value)s' % - {'node': log_node(node), + {'node': log_res(node), 'value': node.properties['local_gb']}) # allow for partitioning and config drive @@ -104,3 +98,65 @@ def parse_checksums(checksums): result[fname.strip().lstrip('*')] = checksum.strip() return result + + +class GetNodeMixin(object): + """A helper mixin for getting nodes with hostnames.""" + + HOSTNAME_FIELD = 'metalsmith_hostname' + + _node_list = None + + def _available_nodes(self): + return self.connection.baremetal.nodes(details=True, + associated=False, + provision_state='available', + is_maintenance=False) + + def _nodes_for_lookup(self): + return self.connection.baremetal.nodes( + fields=['uuid', 'name', 'instance_info']) + + def _find_node_by_hostname(self, hostname): + """A helper to find a node by metalsmith hostname.""" + nodes = self._node_list or self._nodes_for_lookup() + existing = [n for n in nodes + if n.instance_info.get(self.HOSTNAME_FIELD) == hostname] + if len(existing) > 1: + raise RuntimeError("More than one node found with hostname " + "%(host)s: %(nodes)s" % + {'host': hostname, + 'nodes': ', '.join(log_res(n) + for n in existing)}) + elif not existing: + return None + else: + # Fetch the complete node information before returning + return self.connection.baremetal.get_node(existing[0].id) + + def _get_node(self, node, refresh=False, accept_hostname=False): + """A helper to find and return a node.""" + if isinstance(node, six.string_types): + if accept_hostname and is_hostname_safe(node): + by_hostname = self._find_node_by_hostname(node) + if by_hostname is not None: + return by_hostname + + return self.connection.baremetal.get_node(node) + elif hasattr(node, 'node'): + # Instance object + node = node.node + else: + node = node + + if refresh: + return self.connection.baremetal.get_node(node) + else: + return node + + @contextlib.contextmanager + def _cache_node_list_for_lookup(self): + if self._node_list is None: + self._node_list = list(self._nodes_for_lookup()) + yield self._node_list + self._node_list = None diff --git a/metalsmith/sources.py b/metalsmith/sources.py index 9b7f2ed..244a7b1 100644 --- a/metalsmith/sources.py +++ b/metalsmith/sources.py @@ -69,12 +69,12 @@ class GlanceImage(_Source): LOG.debug('Image: %s', self._image_obj) updates = { - '/instance_info/image_source': self._image_obj.id + 'image_source': self._image_obj.id } for prop in ('kernel', 'ramdisk'): value = getattr(self._image_obj, '%s_id' % prop, None) if value: - updates['/instance_info/%s' % prop] = value + updates[prop] = value return updates @@ -144,8 +144,8 @@ class HttpWholeDiskImage(_Source): LOG.debug('Image: %(image)s, checksum %(checksum)s', {'image': self.url, 'checksum': self.checksum}) return { - '/instance_info/image_source': self.url, - '/instance_info/image_checksum': self.checksum, + 'image_source': self.url, + 'image_checksum': self.checksum, } @@ -172,8 +172,8 @@ class HttpPartitionImage(HttpWholeDiskImage): def _node_updates(self, connection): updates = super(HttpPartitionImage, self)._node_updates(connection) - updates['/instance_info/kernel'] = self.kernel_url - updates['/instance_info/ramdisk'] = self.ramdisk_url + updates['kernel'] = self.kernel_url + updates['ramdisk'] = self.ramdisk_url return updates @@ -203,8 +203,8 @@ class FileWholeDiskImage(_Source): LOG.debug('Image: %(image)s, checksum %(checksum)s', {'image': self.location, 'checksum': self.checksum}) return { - '/instance_info/image_source': self.location, - '/instance_info/image_checksum': self.checksum, + 'image_source': self.location, + 'image_checksum': self.checksum, } @@ -239,6 +239,6 @@ class FilePartitionImage(FileWholeDiskImage): def _node_updates(self, connection): updates = super(FilePartitionImage, self)._node_updates(connection) - updates['/instance_info/kernel'] = self.kernel_location - updates['/instance_info/ramdisk'] = self.ramdisk_location + updates['kernel'] = self.kernel_location + updates['ramdisk'] = self.ramdisk_location return updates diff --git a/metalsmith/test/test_cmd.py b/metalsmith/test/test_cmd.py index a067e84..0753018 100644 --- a/metalsmith/test/test_cmd.py +++ b/metalsmith/test/test_cmd.py @@ -75,7 +75,7 @@ class TestDeploy(testtools.TestCase): instance = mock_pr.return_value.provision_node.return_value instance.create_autospec(_instance.Instance) instance.node.name = None - instance.node.uuid = '123' + instance.node.id = '123' instance.state = 'active' instance.is_deployed = True instance.ip_addresses.return_value = {'private': ['1.2.3.4']} @@ -127,7 +127,7 @@ class TestDeploy(testtools.TestCase): instance.is_deployed = True instance.ip_addresses.return_value = {} instance.node.name = None - instance.node.uuid = '123' + instance.node.id = '123' instance.state = 'active' args = ['deploy', '--network', 'mynet', '--image', 'myimg', @@ -142,7 +142,7 @@ class TestDeploy(testtools.TestCase): instance.create_autospec(_instance.Instance) instance.is_deployed = False instance.node.name = None - instance.node.uuid = '123' + instance.node.id = '123' instance.state = 'deploying' args = ['deploy', '--network', 'mynet', '--image', 'myimg', @@ -487,7 +487,7 @@ class TestUndeploy(testtools.TestCase): def test_ok(self, mock_os_conf, mock_pr): node = mock_pr.return_value.unprovision_node.return_value - node.uuid = '123' + node.id = '123' node.name = None node.provision_state = 'cleaning' @@ -506,7 +506,7 @@ class TestUndeploy(testtools.TestCase): def test_custom_wait(self, mock_os_conf, mock_pr): node = mock_pr.return_value.unprovision_node.return_value - node.uuid = '123' + node.id = '123' node.name = None node.provision_state = 'available' @@ -580,9 +580,9 @@ class TestShowWait(testtools.TestCase): for hostname in ['hostname1', 'hostname2'] ] for inst in self.instances: - inst.node.uuid = inst.uuid + inst.node.id = inst.uuid inst.node.name = 'name-%s' % inst.uuid - inst.to_dict.return_value = {inst.node.uuid: inst.node.name} + inst.to_dict.return_value = {inst.node.id: inst.node.name} def test_show(self, mock_os_conf, mock_pr): mock_pr.return_value.show_instances.return_value = self.instances diff --git a/metalsmith/test/test_config.py b/metalsmith/test/test_config.py index 24ddb5d..3ef6c14 100644 --- a/metalsmith/test/test_config.py +++ b/metalsmith/test/test_config.py @@ -14,9 +14,9 @@ # limitations under the License. import json -import os import mock +from openstack.baremetal import configdrive import testtools from metalsmith import _config @@ -25,7 +25,7 @@ from metalsmith import _config class TestInstanceConfig(testtools.TestCase): def setUp(self): super(TestInstanceConfig, self).setUp() - self.node = mock.Mock(uuid='1234') + self.node = mock.Mock(id='1234') self.node.name = 'node name' def _check(self, config, expected_metadata, expected_userdata=None): @@ -39,24 +39,19 @@ class TestInstanceConfig(testtools.TestCase): 'meta': {}} expected_m.update(expected_metadata) - with config.build_configdrive_directory(self.node, 'example.com') as d: - for version in ('2012-08-10', 'latest'): - with open(os.path.join(d, 'openstack', version, - 'meta_data.json')) as fp: - metadata = json.load(fp) + with mock.patch.object(configdrive, 'build', autospec=True) as mb: + result = config.build_configdrive(self.node, "example.com") + mb.assert_called_once_with(expected_m, mock.ANY) + self.assertIs(result, mb.return_value) + user_data = mb.call_args[0][1] - self.assertEqual(expected_m, metadata) - user_data = os.path.join(d, 'openstack', version, 'user_data') - if expected_userdata is None: - self.assertFalse(os.path.exists(user_data)) - else: - with open(user_data) as fp: - lines = list(fp) - self.assertEqual('#cloud-config\n', lines[0]) - user_data = json.loads(''.join(lines[1:])) - self.assertEqual(expected_userdata, user_data) - - self.assertFalse(os.path.exists(d)) + if expected_userdata: + self.assertIsNotNone(user_data) + user_data = user_data.decode('utf-8') + header, user_data = user_data.split('\n', 1) + self.assertEqual('#cloud-config', header) + user_data = json.loads(user_data) + self.assertEqual(expected_userdata, user_data) def test_default(self): config = _config.InstanceConfig() diff --git a/metalsmith/test/test_instance.py b/metalsmith/test/test_instance.py index f53046e..a623aaa 100644 --- a/metalsmith/test/test_instance.py +++ b/metalsmith/test/test_instance.py @@ -23,21 +23,19 @@ class TestInstanceIPAddresses(test_provisioner.Base): def setUp(self): super(TestInstanceIPAddresses, self).setUp() self.instance = _instance.Instance(self.api, self.node) - self.api.list_node_attached_ports.return_value = [ - mock.Mock(spec=['id'], id=i) for i in ('111', '222') - ] + self.api.baremetal.list_node_vifs.return_value = ['111', '222'] self.ports = [ mock.Mock(spec=['network_id', 'fixed_ips', 'network'], network_id=n, fixed_ips=[{'ip_address': ip}]) for n, ip in [('0', '192.168.0.1'), ('1', '10.0.0.2')] ] - self.conn.network.get_port.side_effect = self.ports + self.api.network.get_port.side_effect = self.ports self.nets = [ mock.Mock(spec=['id', 'name'], id=str(i)) for i in range(2) ] for n in self.nets: n.name = 'name-%s' % n.id - self.conn.network.get_network.side_effect = self.nets + self.api.network.get_network.side_effect = self.nets def test_ip_addresses(self): ips = self.instance.ip_addresses() @@ -70,7 +68,7 @@ class TestInstanceStates(test_provisioner.Base): self.assertTrue(self.instance.is_healthy) def test_state_deploying_maintenance(self): - self.node.maintenance = True + self.node.is_maintenance = True self.node.provision_state = 'wait call-back' self.assertEqual('deploying', self.instance.state) self.assertFalse(self.instance.is_deployed) @@ -83,7 +81,7 @@ class TestInstanceStates(test_provisioner.Base): self.assertTrue(self.instance.is_healthy) def test_state_maintenance(self): - self.node.maintenance = True + self.node.is_maintenance = True self.node.provision_state = 'active' self.assertEqual('maintenance', self.instance.state) self.assertTrue(self.instance.is_deployed) @@ -112,5 +110,5 @@ class TestInstanceStates(test_provisioner.Base): 'ip_addresses': {'private': ['1.2.3.4']}, 'node': {'node': 'dict'}, 'state': 'deploying', - 'uuid': self.node.uuid}, + 'uuid': self.node.id}, self.instance.to_dict()) diff --git a/metalsmith/test/test_os_api.py b/metalsmith/test/test_os_api.py deleted file mode 100644 index 49cb13e..0000000 --- a/metalsmith/test/test_os_api.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright 2018 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import fixtures -import mock -import testtools - -from metalsmith import _instance -from metalsmith import _os_api - - -class TestNodes(testtools.TestCase): - def setUp(self): - super(TestNodes, self).setUp() - self.session = mock.Mock() - self.ironic_fixture = self.useFixture( - fixtures.MockPatchObject(_os_api.ir_client, 'get_client', - autospec=True)) - self.cli = self.ironic_fixture.mock.return_value - self.api = _os_api.API(session=self.session, connection=mock.Mock()) - - def test_get_node_by_uuid(self): - res = self.api.get_node('uuid1') - self.cli.node.get.assert_called_once_with('uuid1') - self.assertIs(res, self.cli.node.get.return_value) - - def test_get_node_by_hostname(self): - self.cli.node.list.return_value = [ - mock.Mock(uuid='uuid0', instance_info={}), - mock.Mock(uuid='uuid1', - instance_info={'metalsmith_hostname': 'host1'}), - ] - res = self.api.get_node('host1', accept_hostname=True) - # Loading details - self.cli.node.get.assert_called_once_with('uuid1') - self.assertIs(res, self.cli.node.get.return_value) - - def test_get_node_by_hostname_not_found(self): - self.cli.node.list.return_value = [ - mock.Mock(uuid='uuid0', instance_info={}), - mock.Mock(uuid='uuid1', - instance_info={'metalsmith_hostname': 'host0'}), - ] - res = self.api.get_node('host1', accept_hostname=True) - # Loading details - self.cli.node.get.assert_called_once_with('host1') - self.assertIs(res, self.cli.node.get.return_value) - - def test_get_node_by_node(self): - res = self.api.get_node(mock.sentinel.node) - self.assertIs(res, mock.sentinel.node) - self.assertFalse(self.cli.node.get.called) - - def test_get_node_by_node_with_refresh(self): - res = self.api.get_node(mock.Mock(spec=['uuid'], uuid='uuid1'), - refresh=True) - self.cli.node.get.assert_called_once_with('uuid1') - self.assertIs(res, self.cli.node.get.return_value) - - def test_get_node_by_instance(self): - inst = _instance.Instance(mock.Mock(), mock.Mock()) - res = self.api.get_node(inst) - self.assertIs(res, inst.node) - self.assertFalse(self.cli.node.get.called) - - def test_get_node_by_instance_with_refresh(self): - inst = _instance.Instance(mock.Mock(), - mock.Mock(spec=['uuid'], uuid='uuid1')) - res = self.api.get_node(inst, refresh=True) - self.cli.node.get.assert_called_once_with('uuid1') - self.assertIs(res, self.cli.node.get.return_value) - - def test_find_node_by_hostname(self): - self.cli.node.list.return_value = [ - mock.Mock(uuid='uuid0', instance_info={}), - mock.Mock(uuid='uuid1', - instance_info={'metalsmith_hostname': 'host1'}), - ] - res = self.api.find_node_by_hostname('host1') - # Loading details - self.cli.node.get.assert_called_once_with('uuid1') - self.assertIs(res, self.cli.node.get.return_value) - - def test_find_node_by_hostname_cached(self): - self.cli.node.list.return_value = [ - mock.Mock(uuid='uuid0', instance_info={}), - mock.Mock(uuid='uuid1', - instance_info={'metalsmith_hostname': 'host1'}), - ] - with self.api.cache_node_list_for_lookup(): - res = self.api.find_node_by_hostname('host1') - self.assertIs(res, self.cli.node.get.return_value) - self.assertIsNone(self.api.find_node_by_hostname('host2')) - self.assertEqual(1, self.cli.node.list.call_count) - # This call is no longer cached - self.assertIsNone(self.api.find_node_by_hostname('host2')) - self.assertEqual(2, self.cli.node.list.call_count) - - def test_find_node_by_hostname_not_found(self): - self.cli.node.list.return_value = [ - mock.Mock(uuid='uuid0', instance_info={}), - mock.Mock(uuid='uuid1', - instance_info={'metalsmith_hostname': 'host1'}), - ] - self.assertIsNone(self.api.find_node_by_hostname('host0')) - self.assertFalse(self.cli.node.get.called) - - def test_find_node_by_hostname_duplicate(self): - self.cli.node.list.return_value = [ - mock.Mock(uuid='uuid0', - instance_info={'metalsmith_hostname': 'host1'}), - mock.Mock(uuid='uuid1', - instance_info={'metalsmith_hostname': 'host1'}), - ] - self.assertRaisesRegex(RuntimeError, 'More than one node', - self.api.find_node_by_hostname, 'host1') - self.assertFalse(self.cli.node.get.called) diff --git a/metalsmith/test/test_provisioner.py b/metalsmith/test/test_provisioner.py index 3741343..93d1988 100644 --- a/metalsmith/test/test_provisioner.py +++ b/metalsmith/test/test_provisioner.py @@ -21,13 +21,13 @@ import testtools from metalsmith import _config from metalsmith import _instance -from metalsmith import _os_api from metalsmith import _provisioner +from metalsmith import _utils from metalsmith import exceptions from metalsmith import sources -NODE_FIELDS = ['name', 'uuid', 'instance_info', 'instance_uuid', 'maintenance', +NODE_FIELDS = ['name', 'id', 'instance_info', 'instance_id', 'is_maintenance', 'maintenance_reason', 'properties', 'provision_state', 'extra', 'last_error', 'traits'] @@ -63,512 +63,464 @@ class Base(testtools.TestCase): self.pr = _provisioner.Provisioner(mock.Mock()) self._reset_api_mock() self.node = mock.Mock(spec=NODE_FIELDS + ['to_dict'], - uuid='000', instance_uuid=None, + id='000', instance_id=None, properties={'local_gb': 100}, instance_info={}, - maintenance=False, extra={}) + is_maintenance=False, extra={}) self.node.name = 'control-0' def _reset_api_mock(self): - self.api = mock.Mock(spec=_os_api.API) - self.api.get_node.side_effect = ( - lambda n, refresh=False, accept_hostname=False: n + self.mock_get_node = self.useFixture( + fixtures.MockPatchObject(_provisioner.Provisioner, '_get_node', + autospec=True)).mock + self.mock_get_node.side_effect = ( + lambda self, n, refresh=False, accept_hostname=False: n ) - self.api.update_node.side_effect = lambda n, _u: n - self.api.list_node_ports.return_value = [ - mock.Mock(spec=['uuid', 'pxe_enabled'], - uuid=uuid, pxe_enabled=pxe) - for (uuid, pxe) in [('000', True), ('111', False)] + self.useFixture( + fixtures.MockPatchObject(_provisioner.Provisioner, + '_cache_node_list_for_lookup', + autospec=True)) + self.api = mock.Mock(spec=['image', 'network', 'baremetal']) + self.api.baremetal.update_node.side_effect = lambda n, **kw: n + self.api.network.ports.return_value = [ + mock.Mock(spec=['id'], id=i) for i in ('000', '111') ] - self.api.find_node_by_hostname.return_value = None - self.api.cache_node_list_for_lookup = mock.MagicMock() - self.pr._api = self.api - - self.conn = mock.Mock(spec=['image', 'network', 'baremetal']) - self.pr.connection = self.conn - self.api.connection = self.conn + self.api.baremetal.set_node_provision_state.side_effect = ( + lambda node, *args, **kwargs: node) + self.api.baremetal.wait_for_nodes_provision_state.side_effect = ( + lambda nodes, *args, **kwargs: nodes) + self.pr.connection = self.api class TestReserveNode(Base): + def _node(self, **kwargs): + kwargs.setdefault('id', '000') + kwargs.setdefault('properties', {'local_gb': 100}) + kwargs.setdefault('instance_info', {}) + return mock.Mock(spec=NODE_FIELDS, **kwargs) + def test_no_nodes(self): - self.api.list_nodes.return_value = [] + self.api.baremetal.nodes.return_value = [] self.assertRaises(exceptions.NodesNotFound, self.pr.reserve_node, resource_class='control') - self.assertFalse(self.api.reserve_node.called) + self.assertFalse(self.api.baremetal.update_node.called) def test_simple_ok(self): - nodes = [ - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 100}) - ] - self.api.list_nodes.return_value = nodes - self.api.reserve_node.side_effect = lambda n, instance_uuid: n + nodes = [self._node()] + self.api.baremetal.nodes.return_value = nodes node = self.pr.reserve_node('control') self.assertIn(node, nodes) - self.assertFalse(self.api.update_node.called) + self.api.baremetal.update_node.assert_called_once_with( + node, instance_id=node.id, instance_info={}) def test_any_resource_class(self): - nodes = [ - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 100}) - ] - self.api.list_nodes.return_value = nodes - self.api.reserve_node.side_effect = lambda n, instance_uuid: n + nodes = [self._node()] + self.api.baremetal.nodes.return_value = nodes node = self.pr.reserve_node() self.assertIn(node, nodes) - self.assertFalse(self.api.update_node.called) + self.api.baremetal.update_node.assert_called_once_with( + node, instance_id=node.id, instance_info={}) def test_with_capabilities(self): nodes = [ - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 100, 'capabilities': caps}) + self._node(properties={'local_gb': 100, 'capabilities': caps}) for caps in ['answer:1', 'answer:42', None] ] expected = nodes[1] - self.api.list_nodes.return_value = nodes - self.api.reserve_node.side_effect = lambda n, instance_uuid: n + self.api.baremetal.nodes.return_value = nodes node = self.pr.reserve_node('control', capabilities={'answer': '42'}) self.assertIs(node, expected) - self.api.update_node.assert_called_once_with( - node, {'/instance_info/capabilities': {'answer': '42'}}) + self.api.baremetal.update_node.assert_called_once_with( + node, instance_id=node.id, + instance_info={'capabilities': {'answer': '42'}}) def test_with_traits(self): - nodes = [ - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 100}, traits=traits) - for traits in [['foo', 'answer:1'], ['answer:42', 'foo'], - ['answer'], None] - ] + nodes = [self._node(properties={'local_gb': 100}, traits=traits) + for traits in [['foo', 'answer:1'], ['answer:42', 'foo'], + ['answer'], None]] expected = nodes[1] - self.api.list_nodes.return_value = nodes - self.api.reserve_node.side_effect = lambda n, instance_uuid: n + self.api.baremetal.nodes.return_value = nodes node = self.pr.reserve_node(traits=['foo', 'answer:42']) self.assertIs(node, expected) - self.api.update_node.assert_called_once_with( - node, {'/instance_info/traits': ['foo', 'answer:42']}) + self.api.baremetal.update_node.assert_called_once_with( + node, instance_id=node.id, + instance_info={'traits': ['foo', 'answer:42']}) def test_custom_predicate(self): - nodes = [ - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 100}), - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 150}), - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 200}), - ] - self.api.list_nodes.return_value = nodes[:] - self.api.reserve_node.side_effect = lambda n, instance_uuid: n + nodes = [self._node(properties={'local_gb': i}) + for i in (100, 150, 200)] + self.api.baremetal.nodes.return_value = nodes[:] node = self.pr.reserve_node( predicate=lambda node: 100 < node.properties['local_gb'] < 200) self.assertEqual(node, nodes[1]) - self.assertFalse(self.api.update_node.called) + self.api.baremetal.update_node.assert_called_once_with( + node, instance_id=node.id, instance_info={}) def test_custom_predicate_false(self): - nodes = [ - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 100}), - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 150}), - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 200}), - ] - self.api.list_nodes.return_value = nodes[:] + nodes = [self._node() for _ in range(3)] + self.api.baremetal.nodes.return_value = nodes[:] self.assertRaisesRegex(exceptions.CustomPredicateFailed, 'custom predicate', self.pr.reserve_node, predicate=lambda node: False) - self.assertFalse(self.api.update_node.called) - self.assertFalse(self.api.reserve_node.called) + self.assertFalse(self.api.baremetal.update_node.called) def test_provided_node(self): - nodes = [ - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 100}) - ] - self.api.reserve_node.side_effect = lambda n, instance_uuid: n + nodes = [self._node()] node = self.pr.reserve_node(candidates=nodes) self.assertEqual(node, nodes[0]) - self.assertFalse(self.api.list_nodes.called) - self.assertFalse(self.api.update_node.called) + self.assertFalse(self.api.baremetal.nodes.called) + self.api.baremetal.update_node.assert_called_once_with( + node, instance_id=node.id, instance_info={}) def test_provided_nodes(self): - nodes = [ - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 100}), - mock.Mock(spec=['uuid', 'name', 'properties'], - properties={'local_gb': 100}) - ] - self.api.reserve_node.side_effect = lambda n, instance_uuid: n + nodes = [self._node(), self._node()] node = self.pr.reserve_node(candidates=nodes) self.assertEqual(node, nodes[0]) - self.assertFalse(self.api.list_nodes.called) - self.assertFalse(self.api.update_node.called) + self.assertFalse(self.api.baremetal.nodes.called) + self.api.baremetal.update_node.assert_called_once_with( + node, instance_id=node.id, instance_info={}) def test_nodes_filtered(self): - nodes = [ - mock.Mock(spec=['uuid', 'name', 'properties', 'resource_class'], - properties={'local_gb': 100}, resource_class='banana'), - mock.Mock(spec=['uuid', 'name', 'properties', 'resource_class'], - properties={'local_gb': 100}, resource_class='compute'), - mock.Mock(spec=['uuid', 'name', 'properties', 'resource_class'], - properties={'local_gb': 100, 'capabilities': 'cat:meow'}, - resource_class='compute'), - ] - self.api.reserve_node.side_effect = lambda n, instance_uuid: n + nodes = [self._node(resource_class='banana'), + self._node(resource_class='compute'), + self._node(properties={'local_gb': 100, + 'capabilities': 'cat:meow'}, + resource_class='compute')] node = self.pr.reserve_node('compute', candidates=nodes, capabilities={'cat': 'meow'}) self.assertEqual(node, nodes[2]) - self.assertFalse(self.api.list_nodes.called) - self.api.update_node.assert_called_once_with( - node, {'/instance_info/capabilities': {'cat': 'meow'}}) + self.assertFalse(self.api.baremetal.nodes.called) + self.api.baremetal.update_node.assert_called_once_with( + node, instance_id=node.id, + instance_info={'capabilities': {'cat': 'meow'}}) def test_nodes_filtered_by_conductor_group(self): - nodes = [ - mock.Mock(spec=['uuid', 'name', 'properties', 'conductor_group'], - properties={'local_gb': 100}, conductor_group='loc1'), - mock.Mock(spec=['uuid', 'name', 'properties', 'conductor_group'], - properties={'local_gb': 100, 'capabilities': 'cat:meow'}, - conductor_group=''), - mock.Mock(spec=['uuid', 'name', 'properties', 'conductor_group'], - properties={'local_gb': 100, 'capabilities': 'cat:meow'}, - conductor_group='loc1'), - ] - self.api.reserve_node.side_effect = lambda n, instance_uuid: n + nodes = [self._node(conductor_group='loc1'), + self._node(properties={'local_gb': 100, + 'capabilities': 'cat:meow'}, + conductor_group=''), + self._node(properties={'local_gb': 100, + 'capabilities': 'cat:meow'}, + conductor_group='loc1')] node = self.pr.reserve_node(conductor_group='loc1', candidates=nodes, capabilities={'cat': 'meow'}) self.assertEqual(node, nodes[2]) - self.assertFalse(self.api.list_nodes.called) - self.api.update_node.assert_called_once_with( - node, {'/instance_info/capabilities': {'cat': 'meow'}}) + self.assertFalse(self.api.baremetal.nodes.called) + self.api.baremetal.update_node.assert_called_once_with( + node, instance_id=node.id, + instance_info={'capabilities': {'cat': 'meow'}}) def test_provided_nodes_no_match(self): - nodes = [ - mock.Mock(spec=['uuid', 'name', 'properties', 'resource_class', - 'conductor_group'], - properties={'local_gb': 100}, resource_class='compute', - conductor_group='loc1'), - mock.Mock(spec=['uuid', 'name', 'properties', 'resource_class', - 'conductor_group'], - properties={'local_gb': 100}, resource_class='control', - conductor_group='loc2'), - ] + nodes = [self._node(resource_class='compute', conductor_group='loc1'), + self._node(resource_class='control', conductor_group='loc2')] self.assertRaises(exceptions.NodesNotFound, self.pr.reserve_node, candidates=nodes, resource_class='control', conductor_group='loc1') - self.assertFalse(self.api.list_nodes.called) - self.assertFalse(self.api.reserve_node.called) - self.assertFalse(self.api.update_node.called) - - -CLEAN_UP = { - '/extra/metalsmith_created_ports': _os_api.REMOVE, - '/extra/metalsmith_attached_ports': _os_api.REMOVE, - '/instance_info/%s' % _os_api.HOSTNAME_FIELD: _os_api.REMOVE -} + self.assertFalse(self.api.baremetal.nodes.called) + self.assertFalse(self.api.baremetal.update_node.called) class TestProvisionNode(Base): def setUp(self): super(TestProvisionNode, self).setUp() - self.image = self.conn.image.find_image.return_value - self.node.instance_uuid = self.node.uuid - self.updates = { - '/instance_info/ramdisk': self.image.ramdisk_id, - '/instance_info/kernel': self.image.kernel_id, - '/instance_info/image_source': self.image.id, - '/instance_info/root_gb': 99, # 100 - 1 - '/instance_info/capabilities': {'boot_option': 'local'}, - '/extra/metalsmith_created_ports': [ - self.conn.network.create_port.return_value.id - ], - '/extra/metalsmith_attached_ports': [ - self.conn.network.create_port.return_value.id - ], - '/instance_info/%s' % _os_api.HOSTNAME_FIELD: 'control-0' + self.image = self.api.image.find_image.return_value + self.node.instance_id = self.node.id + self.instance_info = { + 'ramdisk': self.image.ramdisk_id, + 'kernel': self.image.kernel_id, + 'image_source': self.image.id, + 'root_gb': 99, # 100 - 1 + 'capabilities': {'boot_option': 'local'}, + _utils.GetNodeMixin.HOSTNAME_FIELD: 'control-0' + } + self.extra = { + 'metalsmith_created_ports': [ + self.api.network.create_port.return_value.id + ], + 'metalsmith_attached_ports': [ + self.api.network.create_port.return_value.id + ], } - self.wait_fixture = self.useFixture( - fixtures.MockPatchObject(_provisioner.Provisioner, - '_wait_for_state', autospec=True)) - self.wait_mock = self.wait_fixture.mock - self.wait_mock.side_effect = lambda self, nodes, *a, **kw: nodes def test_ok(self): inst = self.pr.provision_node(self.node, 'image', [{'network': 'network'}]) - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, instance_info=self.instance_info, extra=self.extra) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse(self.api.network.delete_port.called) def test_ok_without_nics(self): - self.updates['/extra/metalsmith_created_ports'] = [] - self.updates['/extra/metalsmith_attached_ports'] = [] + self.extra['metalsmith_created_ports'] = [] + self.extra['metalsmith_attached_ports'] = [] inst = self.pr.provision_node(self.node, 'image') - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.conn.network.find_port.called) - self.assertFalse(self.api.attach_port_to_node.called) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.network.find_port.called) + self.assertFalse(self.api.baremetal.attach_vif_to_node.called) + self.api.baremetal.update_node.assert_called_once_with( + self.node, instance_info=self.instance_info, extra=self.extra) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_ok_with_source(self): inst = self.pr.provision_node(self.node, sources.GlanceImage('image'), [{'network': 'network'}]) - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, instance_info=self.instance_info, extra=self.extra) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_config(self): - config = mock.MagicMock(spec=_config.InstanceConfig) + config = mock.Mock(spec=_config.InstanceConfig) inst = self.pr.provision_node(self.node, 'image', [{'network': 'network'}], config=config) - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - config.build_configdrive_directory.assert_called_once_with( + config.build_configdrive.assert_called_once_with( self.node, self.node.name) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, instance_info=self.instance_info, extra=self.extra) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) - def test_with_hostname(self): + @mock.patch.object(_provisioner.Provisioner, '_find_node_by_hostname', + autospec=True) + def test_with_hostname(self, mock_find_node): + mock_find_node.return_value = None hostname = 'control-0.example.com' inst = self.pr.provision_node(self.node, 'image', [{'network': 'network'}], hostname=hostname) - self.updates['/instance_info/%s' % _os_api.HOSTNAME_FIELD] = hostname + self.instance_info[_utils.GetNodeMixin.HOSTNAME_FIELD] = hostname - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, instance_info=self.instance_info, extra=self.extra) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_name_not_valid_hostname(self): self.node.name = 'node_1' inst = self.pr.provision_node(self.node, 'image', [{'network': 'network'}]) - self.updates['/instance_info/%s' % _os_api.HOSTNAME_FIELD] = '000' + self.instance_info[_utils.GetNodeMixin.HOSTNAME_FIELD] = '000' - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_unreserved(self): - self.node.instance_uuid = None + self.node.instance_id = None self.pr.provision_node(self.node, 'image', [{'network': 'network'}]) - self.api.reserve_node.assert_called_once_with( - self.node, instance_uuid=self.node.uuid) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_has_calls([ + mock.call(self.node, instance_id=self.node.id), + mock.call(self.node, instance_info=self.instance_info, + extra=self.extra) + ]) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_ports(self): - self.updates['/extra/metalsmith_created_ports'] = [] - self.updates['/extra/metalsmith_attached_ports'] = [ - self.conn.network.find_port.return_value.id - ] * 2 + port_ids = [self.api.network.find_port.return_value.id] * 2 self.pr.provision_node(self.node, 'image', [{'port': 'port1'}, {'port': 'port2'}]) - self.assertFalse(self.conn.network.create_port.called) - self.api.attach_port_to_node.assert_called_with( - self.node.uuid, self.conn.network.find_port.return_value.id) - self.assertEqual(2, self.api.attach_port_to_node.call_count) + self.assertFalse(self.api.network.create_port.called) + self.api.baremetal.attach_vif_to_node.assert_called_with( + self.node, self.api.network.find_port.return_value.id) + self.assertEqual(2, self.api.baremetal.attach_vif_to_node.call_count) self.assertEqual([mock.call('port1', ignore_missing=False), mock.call('port2', ignore_missing=False)], - self.conn.network.find_port.call_args_list) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.find_port.call_args_list) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra={'metalsmith_created_ports': [], + 'metalsmith_attached_ports': port_ids}, + instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_ip(self): inst = self.pr.provision_node(self.node, 'image', [{'network': 'network', 'fixed_ip': '10.0.0.2'}]) - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id, + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id, fixed_ips=[{'ip_address': '10.0.0.2'}]) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_whole_disk(self): self.image.kernel_id = None self.image.ramdisk_id = None - del self.updates['/instance_info/kernel'] - del self.updates['/instance_info/ramdisk'] + del self.instance_info['kernel'] + del self.instance_info['ramdisk'] self.pr.provision_node(self.node, 'image', [{'network': 'network'}]) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_http_and_checksum_whole_disk(self): - self.updates['/instance_info/image_source'] = 'https://host/image' - self.updates['/instance_info/image_checksum'] = 'abcd' - del self.updates['/instance_info/kernel'] - del self.updates['/instance_info/ramdisk'] + self.instance_info['image_source'] = 'https://host/image' + self.instance_info['image_checksum'] = 'abcd' + del self.instance_info['kernel'] + del self.instance_info['ramdisk'] inst = self.pr.provision_node( self.node, sources.HttpWholeDiskImage('https://host/image', checksum='abcd'), [{'network': 'network'}]) - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.assertFalse(self.conn.image.find_image.called) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.assertFalse(self.api.image.find_image.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) @mock.patch.object(requests, 'get', autospec=True) def test_with_http_and_checksum_url(self, mock_get): - self.updates['/instance_info/image_source'] = 'https://host/image' - self.updates['/instance_info/image_checksum'] = 'abcd' - del self.updates['/instance_info/kernel'] - del self.updates['/instance_info/ramdisk'] + self.instance_info['image_source'] = 'https://host/image' + self.instance_info['image_checksum'] = 'abcd' + del self.instance_info['kernel'] + del self.instance_info['ramdisk'] mock_get.return_value.text = """ defg *something else abcd image @@ -580,29 +532,29 @@ abcd image checksum_url='https://host/checksums'), [{'network': 'network'}]) - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.assertFalse(self.conn.image.find_image.called) + self.assertFalse(self.api.image.find_image.called) mock_get.assert_called_once_with('https://host/checksums') - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_http_and_checksum_partition(self): - self.updates['/instance_info/image_source'] = 'https://host/image' - self.updates['/instance_info/image_checksum'] = 'abcd' - self.updates['/instance_info/kernel'] = 'https://host/kernel' - self.updates['/instance_info/ramdisk'] = 'https://host/ramdisk' + self.instance_info['image_source'] = 'https://host/image' + self.instance_info['image_checksum'] = 'abcd' + self.instance_info['kernel'] = 'https://host/kernel' + self.instance_info['ramdisk'] = 'https://host/ramdisk' inst = self.pr.provision_node( self.node, @@ -612,56 +564,56 @@ abcd image ramdisk_url='https://host/ramdisk'), [{'network': 'network'}]) - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.assertFalse(self.conn.image.find_image.called) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.assertFalse(self.api.image.find_image.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_file_whole_disk(self): - self.updates['/instance_info/image_source'] = 'file:///foo/img' - self.updates['/instance_info/image_checksum'] = 'abcd' - del self.updates['/instance_info/kernel'] - del self.updates['/instance_info/ramdisk'] + self.instance_info['image_source'] = 'file:///foo/img' + self.instance_info['image_checksum'] = 'abcd' + del self.instance_info['kernel'] + del self.instance_info['ramdisk'] inst = self.pr.provision_node( self.node, sources.FileWholeDiskImage('file:///foo/img', checksum='abcd'), [{'network': 'network'}]) - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.assertFalse(self.conn.image.find_image.called) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.assertFalse(self.api.image.find_image.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_file_partition(self): - self.updates['/instance_info/image_source'] = 'file:///foo/img' - self.updates['/instance_info/image_checksum'] = 'abcd' - self.updates['/instance_info/kernel'] = 'file:///foo/vmlinuz' - self.updates['/instance_info/ramdisk'] = 'file:///foo/initrd' + self.instance_info['image_source'] = 'file:///foo/img' + self.instance_info['image_checksum'] = 'abcd' + self.instance_info['kernel'] = 'file:///foo/vmlinuz' + self.instance_info['ramdisk'] = 'file:///foo/initrd' inst = self.pr.provision_node( self.node, @@ -671,106 +623,106 @@ abcd image checksum='abcd'), [{'network': 'network'}]) - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.assertFalse(self.conn.image.find_image.called) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.assertFalse(self.api.image.find_image.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_root_size(self): - self.updates['/instance_info/root_gb'] = 50 + self.instance_info['root_gb'] = 50 self.pr.provision_node(self.node, 'image', [{'network': 'network'}], root_size_gb=50) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_swap_size(self): - self.updates['/instance_info/swap_mb'] = 4096 + self.instance_info['swap_mb'] = 4096 self.pr.provision_node(self.node, 'image', [{'network': 'network'}], swap_size_mb=4096) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_capabilities(self): inst = self.pr.provision_node(self.node, 'image', [{'network': 'network'}], capabilities={'answer': '42'}) - self.updates['/instance_info/capabilities'] = {'boot_option': 'local', - 'answer': '42'} + self.instance_info['capabilities'] = {'boot_option': 'local', + 'answer': '42'} - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_existing_capabilities(self): self.node.instance_info['capabilities'] = {'answer': '42'} inst = self.pr.provision_node(self.node, 'image', [{'network': 'network'}]) - self.updates['/instance_info/capabilities'] = {'boot_option': 'local', - 'answer': '42'} + self.instance_info['capabilities'] = {'boot_option': 'local', + 'answer': '42'} - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_override_existing_capabilities(self): self.node.instance_info['capabilities'] = {'answer': '1', @@ -778,252 +730,241 @@ abcd image inst = self.pr.provision_node(self.node, 'image', [{'network': 'network'}], capabilities={'answer': '42'}) - self.updates['/instance_info/capabilities'] = {'boot_option': 'local', - 'answer': '42'} + self.instance_info['capabilities'] = {'boot_option': 'local', + 'answer': '42'} - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_traits(self): inst = self.pr.provision_node(self.node, 'image', [{'network': 'network'}], traits=['1', '2']) - self.updates['/instance_info/traits'] = ['1', '2'] + self.instance_info['traits'] = ['1', '2'] - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_override_existing_traits(self): self.node.traits = ['42'] inst = self.pr.provision_node(self.node, 'image', [{'network': 'network'}], traits=['1', '2']) - self.updates['/instance_info/traits'] = ['1', '2'] + self.instance_info['traits'] = ['1', '2'] - self.assertEqual(inst.uuid, self.node.uuid) + self.assertEqual(inst.uuid, self.node.id) self.assertEqual(inst.node, self.node) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_with_wait(self): - self.conn.network.find_port.return_value = mock.Mock( + self.api.network.find_port.return_value = mock.Mock( spec=['fixed_ips'], fixed_ips=[{'ip_address': '192.168.1.5'}, {}] ) self.pr.provision_node(self.node, 'image', [{'network': 'network'}], wait=3600) - self.conn.network.create_port.assert_called_once_with( - network_id=self.conn.network.find_network.return_value.id) - self.api.attach_port_to_node.assert_called_once_with( - self.node.uuid, self.conn.network.create_port.return_value.id) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.validate_node.assert_called_once_with(self.node, - validate_deploy=True) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.wait_mock.assert_called_once_with(self.pr, - [self.node], - 'active', - delay=15, - timeout=3600) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.api.network.create_port.assert_called_once_with( + network_id=self.api.network.find_network.return_value.id) + self.api.baremetal.attach_vif_to_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.validate_node.assert_called_once_with(self.node) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + wait_mock = self.api.baremetal.wait_for_nodes_provision_state + wait_mock.assert_called_once_with([self.node], 'active', + timeout=3600) + self.assertFalse(self.api.network.delete_port.called) def test_dry_run(self): self.pr._dry_run = True self.pr.provision_node(self.node, 'image', [{'network': 'network'}]) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.attach_port_to_node.called) - self.assertFalse(self.api.update_node.called) - self.assertFalse(self.api.node_action.called) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.attach_vif_to_node.called) + self.assertFalse(self.api.baremetal.update_node.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_unreserve_dry_run(self): self.pr._dry_run = True - self.node.instance_uuid = None + self.node.instance_id = None self.pr.provision_node(self.node, 'image', [{'network': 'network'}]) - self.assertFalse(self.api.reserve_node.called) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.attach_port_to_node.called) - self.assertFalse(self.api.update_node.called) - self.assertFalse(self.api.node_action.called) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.attach_vif_to_node.called) + self.assertFalse(self.api.baremetal.update_node.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) def test_deploy_failure(self): - self.api.node_action.side_effect = RuntimeError('boom') + self.api.baremetal.set_node_provision_state.side_effect = ( + RuntimeError('boom')) self.assertRaisesRegex(RuntimeError, 'boom', self.pr.provision_node, self.node, 'image', [{'network': 'n1'}, {'port': 'p1'}], wait=3600) - self.api.update_node.assert_any_call(self.node, CLEAN_UP) - self.assertFalse(self.wait_mock.called) - self.api.release_node.assert_called_once_with(self.node) - self.conn.network.delete_port.assert_called_once_with( - self.conn.network.create_port.return_value.id, + self.api.baremetal.update_node.assert_any_call( + self.node, extra={}, instance_info={}, instance_id=None) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.api.network.delete_port.assert_called_once_with( + self.api.network.create_port.return_value.id, ignore_missing=False) calls = [ mock.call(self.node, - self.conn.network.create_port.return_value.id), - mock.call(self.node, self.conn.network.find_port.return_value.id) + self.api.network.create_port.return_value.id), + mock.call(self.node, self.api.network.find_port.return_value.id) ] - self.api.detach_port_from_node.assert_has_calls(calls, any_order=True) + self.api.baremetal.detach_vif_from_node.assert_has_calls( + calls, any_order=True) def test_port_creation_failure(self): - self.conn.network.create_port.side_effect = RuntimeError('boom') + self.api.network.create_port.side_effect = RuntimeError('boom') self.assertRaisesRegex(RuntimeError, 'boom', self.pr.provision_node, self.node, 'image', [{'network': 'network'}], wait=3600) - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) - self.assertFalse(self.conn.network.delete_port.called) - self.assertFalse(self.api.detach_port_from_node.called) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra={}, instance_info={}, instance_id=None) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) + self.assertFalse(self.api.baremetal.detach_vif_from_node.called) def test_port_attach_failure(self): - self.api.attach_port_to_node.side_effect = RuntimeError('boom') + self.api.baremetal.attach_vif_to_node.side_effect = ( + RuntimeError('boom')) self.assertRaisesRegex(RuntimeError, 'boom', self.pr.provision_node, self.node, 'image', [{'network': 'network'}], wait=3600) - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) - self.conn.network.delete_port.assert_called_once_with( - self.conn.network.create_port.return_value.id, + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra={}, instance_info={}, instance_id=None) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) + self.api.network.delete_port.assert_called_once_with( + self.api.network.create_port.return_value.id, ignore_missing=False) - self.api.detach_port_from_node.assert_called_once_with( - self.node, self.conn.network.create_port.return_value.id) + self.api.baremetal.detach_vif_from_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) def test_failure_during_port_deletion(self): - self.conn.network.delete_port.side_effect = AssertionError() - self.api.node_action.side_effect = RuntimeError('boom') + self.api.network.delete_port.side_effect = AssertionError() + self.api.baremetal.set_node_provision_state.side_effect = ( + RuntimeError('boom')) self.assertRaisesRegex(RuntimeError, 'boom', self.pr.provision_node, self.node, 'image', [{'network': 'network'}], wait=3600) - self.assertFalse(self.wait_mock.called) - self.api.release_node.assert_called_once_with(self.node) - self.conn.network.delete_port.assert_called_once_with( - self.conn.network.create_port.return_value.id, + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.api.network.delete_port.assert_called_once_with( + self.api.network.create_port.return_value.id, ignore_missing=False) - self.api.detach_port_from_node.assert_called_once_with( - self.node, self.conn.network.create_port.return_value.id) + self.api.baremetal.detach_vif_from_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) - @mock.patch.object(_provisioner.LOG, 'exception', autospec=True) - def test_failure_during_deploy_failure(self, mock_log_exc): - for failed_call in ['detach_port_from_node', - 'release_node']: - self._reset_api_mock() - getattr(self.api, failed_call).side_effect = AssertionError() - self.api.node_action.side_effect = RuntimeError('boom') - self.assertRaisesRegex(RuntimeError, 'boom', - self.pr.provision_node, self.node, - 'image', [{'network': 'network'}], - wait=3600) - - self.assertFalse(self.wait_mock.called) - self.api.release_node.assert_called_once_with(self.node) - self.conn.network.delete_port.assert_called_once_with( - self.conn.network.create_port.return_value.id, - ignore_missing=False) - self.api.detach_port_from_node.assert_called_once_with( - self.node, self.conn.network.create_port.return_value.id) - self.assertEqual(mock_log_exc.called, - failed_call == 'release_node') - - def test_failure_during_extra_update_on_deploy_failure(self): - self.api.update_node.side_effect = [self.node, AssertionError()] - self.api.node_action.side_effect = RuntimeError('boom') + def _test_failure_during_deploy_failure(self): + self.api.baremetal.set_node_provision_state.side_effect = ( + RuntimeError('boom')) self.assertRaisesRegex(RuntimeError, 'boom', self.pr.provision_node, self.node, 'image', [{'network': 'network'}], wait=3600) - self.assertFalse(self.wait_mock.called) - self.api.release_node.assert_called_once_with(self.node) - self.conn.network.delete_port.assert_called_once_with( - self.conn.network.create_port.return_value.id, + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.api.network.delete_port.assert_called_once_with( + self.api.network.create_port.return_value.id, ignore_missing=False) - self.api.detach_port_from_node.assert_called_once_with( - self.node, self.conn.network.create_port.return_value.id) + self.api.baremetal.detach_vif_from_node.assert_called_once_with( + self.node, self.api.network.create_port.return_value.id) + + def test_detach_failed_after_deploy_failure(self): + self.api.baremetal.detach_port_from_node.side_effect = AssertionError() + self._test_failure_during_deploy_failure() + + def test_update_failed_after_deploy_failure(self): + self.api.baremetal.update_node.side_effect = [self.node, + AssertionError()] + self._test_failure_during_deploy_failure() def test_wait_failure(self): - self.wait_mock.side_effect = RuntimeError('boom') + self.api.baremetal.wait_for_nodes_provision_state.side_effect = ( + RuntimeError('boom')) self.assertRaisesRegex(RuntimeError, 'boom', self.pr.provision_node, self.node, 'image', [{'network': 'network'}], wait=3600) - self.api.update_node.assert_called_once_with(self.node, self.updates) - self.api.node_action.assert_called_once_with(self.node, 'active', - configdrive=mock.ANY) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) - self.assertFalse(self.api.detach_port_from_node.called) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra=self.extra, instance_info=self.instance_info) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'active', config_drive=mock.ANY) + self.assertFalse(self.api.network.delete_port.called) + self.assertFalse(self.api.baremetal.detach_vif_from_node.called) def test_missing_image(self): - self.conn.image.find_image.side_effect = os_exc.ResourceNotFound( + self.api.image.find_image.side_effect = os_exc.ResourceNotFound( 'Not found') self.assertRaisesRegex(exceptions.InvalidImage, 'Not found', self.pr.provision_node, self.node, 'image', [{'network': 'network'}]) - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra={}, instance_info={}, instance_id=None) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) @mock.patch.object(requests, 'get', autospec=True) def test_no_checksum_with_http_image(self, mock_get): - self.updates['/instance_info/image_source'] = 'https://host/image' - self.updates['/instance_info/image_checksum'] = 'abcd' - del self.updates['/instance_info/kernel'] - del self.updates['/instance_info/ramdisk'] + self.instance_info['image_source'] = 'https://host/image' + self.instance_info['image_checksum'] = 'abcd' + del self.instance_info['kernel'] + del self.instance_info['ramdisk'] mock_get.return_value.text = """ defg *something else abcd and-not-image-again @@ -1038,18 +979,18 @@ abcd and-not-image-again checksum_url='https://host/checksums'), [{'network': 'network'}]) - self.assertFalse(self.conn.image.find_image.called) + self.assertFalse(self.api.image.find_image.called) mock_get.assert_called_once_with('https://host/checksums') - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra={}, instance_info={}, instance_id=None) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) @mock.patch.object(requests, 'get', autospec=True) def test_malformed_checksum_with_http_image(self, mock_get): - self.updates['/instance_info/image_source'] = 'https://host/image' - self.updates['/instance_info/image_checksum'] = 'abcd' - del self.updates['/instance_info/kernel'] - del self.updates['/instance_info/ramdisk'] + self.instance_info['image_source'] = 'https://host/image' + self.instance_info['image_checksum'] = 'abcd' + del self.instance_info['kernel'] + del self.instance_info['ramdisk'] mock_get.return_value.text = """

I am not a checksum file!

@@ -1064,18 +1005,18 @@ abcd and-not-image-again checksum_url='https://host/checksums'), [{'network': 'network'}]) - self.assertFalse(self.conn.image.find_image.called) + self.assertFalse(self.api.image.find_image.called) mock_get.assert_called_once_with('https://host/checksums') - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra={}, instance_info={}, instance_id=None) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) @mock.patch.object(requests, 'get', autospec=True) def test_cannot_download_checksum_with_http_image(self, mock_get): - self.updates['/instance_info/image_source'] = 'https://host/image' - self.updates['/instance_info/image_checksum'] = 'abcd' - del self.updates['/instance_info/kernel'] - del self.updates['/instance_info/ramdisk'] + self.instance_info['image_source'] = 'https://host/image' + self.instance_info['image_checksum'] = 'abcd' + del self.instance_info['kernel'] + del self.instance_info['ramdisk'] mock_get.return_value.raise_for_status.side_effect = ( requests.RequestException("boom")) @@ -1088,40 +1029,39 @@ abcd and-not-image-again checksum_url='https://host/checksums'), [{'network': 'network'}]) - self.assertFalse(self.conn.image.find_image.called) + self.assertFalse(self.api.image.find_image.called) mock_get.assert_called_once_with('https://host/checksums') - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra={}, instance_info={}, instance_id=None) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_invalid_network(self): - self.conn.network.find_network.side_effect = RuntimeError('Not found') + self.api.network.find_network.side_effect = RuntimeError('Not found') self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found', self.pr.provision_node, self.node, 'image', [{'network': 'network'}]) - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra={}, instance_info={}, instance_id=None) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_invalid_port(self): - self.conn.network.find_port.side_effect = RuntimeError('Not found') + self.api.network.find_port.side_effect = RuntimeError('Not found') self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found', self.pr.provision_node, self.node, 'image', [{'port': 'port1'}]) - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra={}, instance_info={}, instance_id=None) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_no_local_gb(self): self.node.properties = {} self.assertRaises(exceptions.UnknownRootDiskSize, self.pr.provision_node, self.node, 'image', [{'network': 'network'}]) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_invalid_local_gb(self): for value in (None, 'meow', -42, []): @@ -1129,9 +1069,8 @@ abcd and-not-image-again self.assertRaises(exceptions.UnknownRootDiskSize, self.pr.provision_node, self.node, 'image', [{'network': 'network'}]) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_with(self.node) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_invalid_root_size_gb(self): self.assertRaises(TypeError, @@ -1142,36 +1081,34 @@ abcd and-not-image-again self.pr.provision_node, self.node, 'image', [{'network': 'network'}], root_size_gb=0) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_with(self.node) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_invalid_nics(self): self.assertRaisesRegex(TypeError, 'must be a list', self.pr.provision_node, self.node, 'image', 42) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.attach_port_to_node.called) - self.assertFalse(self.api.node_action.called) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.attach_vif_to_node.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_invalid_nic(self): for item in ('string', ['string']): self.assertRaisesRegex(TypeError, 'must be a dict', self.pr.provision_node, self.node, 'image', item) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.attach_port_to_node.called) - self.assertFalse(self.api.node_action.called) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.attach_vif_to_node.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_invalid_nic_type(self): self.assertRaisesRegex(exceptions.InvalidNIC, 'Unknown NIC record type', self.pr.provision_node, self.node, 'image', [{'foo': 'bar'}]) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.attach_port_to_node.called) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.attach_vif_to_node.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_invalid_nic_type_fields(self): for item in ({'port': '1234', 'foo': 'bar'}, @@ -1181,65 +1118,62 @@ abcd and-not-image-again 'Unexpected fields', self.pr.provision_node, self.node, 'image', [item]) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.attach_port_to_node.called) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_with(self.node) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.attach_vif_to_node.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_invalid_hostname(self): self.assertRaisesRegex(ValueError, 'n_1 cannot be used as a hostname', self.pr.provision_node, self.node, 'image', [{'port': 'port1'}], hostname='n_1') - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra={}, instance_info={}, instance_id=None) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) - def test_duplicate_hostname(self): - self.api.find_node_by_hostname.return_value = mock.Mock(spec=['uuid', - 'name']) + @mock.patch.object(_provisioner.Provisioner, '_find_node_by_hostname', + autospec=True) + def test_duplicate_hostname(self, mock_find_node): + mock_find_node.return_value = mock.Mock(spec=['id', 'name']) self.assertRaisesRegex(ValueError, 'already uses hostname host', self.pr.provision_node, self.node, 'image', [{'port': 'port1'}], hostname='host') - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.node_action.called) - self.api.release_node.assert_called_once_with(self.node) + self.api.baremetal.update_node.assert_called_once_with( + self.node, extra={}, instance_info={}, instance_id=None) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_node_not_found(self): - self.api.get_node.side_effect = RuntimeError('not found') + self.mock_get_node.side_effect = RuntimeError('not found') self.assertRaisesRegex(exceptions.InvalidNode, 'not found', self.pr.provision_node, self.node, 'image', [{'network': 'network'}]) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.update_node.called) - self.assertFalse(self.api.node_action.called) - self.assertFalse(self.api.release_node.called) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.update_node.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) - def test_node_with_external_instance_uuid(self): - self.node.instance_uuid = 'nova' + def test_node_with_external_instance_id(self): + self.node.instance_id = 'nova' self.assertRaisesRegex(exceptions.InvalidNode, 'reserved by instance nova', self.pr.provision_node, self.node, 'image', [{'network': 'network'}]) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.update_node.called) - self.assertFalse(self.api.node_action.called) - self.assertFalse(self.api.release_node.called) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.update_node.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_node_in_maintenance(self): - self.node.maintenance = True + self.node.is_maintenance = True self.node.maintenance_reason = 'power failure' self.assertRaisesRegex(exceptions.InvalidNode, 'in maintenance mode .* power failure', self.pr.provision_node, self.node, 'image', [{'network': 'network'}]) - self.assertFalse(self.conn.network.create_port.called) - self.assertFalse(self.api.update_node.called) - self.assertFalse(self.api.node_action.called) - self.assertFalse(self.api.release_node.called) + self.assertFalse(self.api.network.create_port.called) + self.assertFalse(self.api.baremetal.update_node.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) def test_invalid_http_source(self): self.assertRaises(TypeError, sources.HttpWholeDiskImage, @@ -1258,190 +1192,103 @@ abcd and-not-image-again class TestUnprovisionNode(Base): - def setUp(self): - super(TestUnprovisionNode, self).setUp() - self.wait_fixture = self.useFixture( - fixtures.MockPatchObject(_provisioner.Provisioner, - '_wait_for_state', autospec=True)) - self.wait_mock = self.wait_fixture.mock - def test_ok(self): self.node.extra['metalsmith_created_ports'] = ['port1'] + # Check that unrelated extra fields are not touched. + self.node.extra['foo'] = 'bar' result = self.pr.unprovision_node(self.node) self.assertIs(result, self.node) - self.conn.network.delete_port.assert_called_once_with( + self.api.network.delete_port.assert_called_once_with( 'port1', ignore_missing=False) - self.api.detach_port_from_node.assert_called_once_with(self.node, - 'port1') - self.api.node_action.assert_called_once_with(self.node, 'deleted') - self.api.release_node.assert_called_once_with(self.node) - self.assertFalse(self.wait_mock.called) - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) + self.api.baremetal.detach_vif_from_node.assert_called_once_with( + self.node, 'port1') + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'deleted', wait=False) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.api.baremetal.update_node.assert_called_once_with( + self.node, instance_info={}, extra={'foo': 'bar'}, + instance_id=None) def test_with_attached(self): self.node.extra['metalsmith_created_ports'] = ['port1'] self.node.extra['metalsmith_attached_ports'] = ['port1', 'port2'] self.pr.unprovision_node(self.node) - self.conn.network.delete_port.assert_called_once_with( + self.api.network.delete_port.assert_called_once_with( 'port1', ignore_missing=False) calls = [mock.call(self.node, 'port1'), mock.call(self.node, 'port2')] - self.api.detach_port_from_node.assert_has_calls(calls, any_order=True) - self.api.node_action.assert_called_once_with(self.node, 'deleted') - self.api.release_node.assert_called_once_with(self.node) - self.assertFalse(self.wait_mock.called) - self.api.update_node.assert_called_once_with(self.node, CLEAN_UP) + self.api.baremetal.detach_vif_from_node.assert_has_calls( + calls, any_order=True) + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'deleted', wait=False) + self.assertFalse( + self.api.baremetal.wait_for_nodes_provision_state.called) + self.api.baremetal.update_node.assert_called_once_with( + self.node, instance_info={}, extra={}, instance_id=None) def test_with_wait(self): self.node.extra['metalsmith_created_ports'] = ['port1'] result = self.pr.unprovision_node(self.node, wait=3600) self.assertIs(result, self.node) - self.conn.network.delete_port.assert_called_once_with( + self.api.network.delete_port.assert_called_once_with( 'port1', ignore_missing=False) - self.api.detach_port_from_node.assert_called_once_with(self.node, - 'port1') - self.api.node_action.assert_called_once_with(self.node, 'deleted') - self.api.release_node.assert_called_once_with(self.node) - self.wait_mock.assert_called_once_with(self.pr, - [self.node], - 'available', - timeout=3600) + self.api.baremetal.detach_vif_from_node.assert_called_once_with( + self.node, 'port1') + self.api.baremetal.set_node_provision_state.assert_called_once_with( + self.node, 'deleted', wait=False) + self.api.baremetal.update_node.assert_called_once_with( + self.node, instance_info={}, extra={}, instance_id=None) + wait_mock = self.api.baremetal.wait_for_nodes_provision_state + wait_mock.assert_called_once_with([self.node], 'available', + timeout=3600) def test_dry_run(self): self.pr._dry_run = True self.node.extra['metalsmith_created_ports'] = ['port1'] self.pr.unprovision_node(self.node) - self.assertFalse(self.api.node_action.called) - self.assertFalse(self.api.release_node.called) - self.assertFalse(self.conn.network.delete_port.called) - self.assertFalse(self.api.detach_port_from_node.called) - self.assertFalse(self.wait_mock.called) - self.assertFalse(self.api.update_node.called) + self.assertFalse(self.api.baremetal.set_node_provision_state.called) + self.assertFalse(self.api.network.delete_port.called) + self.assertFalse(self.api.baremetal.detach_vif_from_node.called) + self.assertFalse(self.api.baremetal.update_node.called) class TestShowInstance(Base): def test_show_instance(self): - self.api.get_node.side_effect = lambda n, *a, **kw: self.node - inst = self.pr.show_instance('uuid1') - self.api.get_node.assert_called_once_with('uuid1', - accept_hostname=True) + self.mock_get_node.side_effect = lambda n, *a, **kw: self.node + inst = self.pr.show_instance('id1') + self.mock_get_node.assert_called_once_with(self.pr, 'id1', + accept_hostname=True) self.assertIsInstance(inst, _instance.Instance) self.assertIs(inst.node, self.node) - self.assertIs(inst.uuid, self.node.uuid) - self.api.cache_node_list_for_lookup.assert_called_once_with() + self.assertIs(inst.uuid, self.node.id) def test_show_instances(self): - self.api.get_node.side_effect = [self.node, mock.Mock()] + self.mock_get_node.side_effect = [self.node, mock.Mock()] result = self.pr.show_instances(['1', '2']) - self.api.get_node.assert_has_calls([ - mock.call('1', accept_hostname=True), - mock.call('2', accept_hostname=True) + self.mock_get_node.assert_has_calls([ + mock.call(self.pr, '1', accept_hostname=True), + mock.call(self.pr, '2', accept_hostname=True) ]) self.assertIsInstance(result, list) for inst in result: self.assertIsInstance(inst, _instance.Instance) self.assertIs(result[0].node, self.node) - self.assertIs(result[0].uuid, self.node.uuid) - self.api.cache_node_list_for_lookup.assert_called_once_with() + self.assertIs(result[0].uuid, self.node.id) -@mock.patch('time.sleep', autospec=True) -class TestWaitForState(Base): - def test_invalid_timeout(self, mock_sleep): - for invalid in (0, -42): - self.assertRaisesRegex(ValueError, - 'timeout argument must be a positive', - self.pr.wait_for_provisioning, - ['uuid1'], timeout=invalid) +class TestWaitForProvisioning(Base): - def test_invalid_delay(self, mock_sleep): - self.assertRaisesRegex(ValueError, - 'delay argument must be a non-negative', - self.pr.wait_for_provisioning, - ['uuid1'], delay=-42) + def test_success(self): + node = mock.Mock(spec=NODE_FIELDS) - def test_success_one_node(self, mock_sleep): - nodes = [ - mock.Mock(spec=NODE_FIELDS, provision_state=state) - for state in ('deploying', 'deploy wait', 'deploying', 'active') - ] - self.api.get_node.side_effect = nodes - - result = self.pr.wait_for_provisioning(['uuid1']) - self.assertEqual(nodes[-1:], [inst.node for inst in result]) + result = self.pr.wait_for_provisioning([node]) + self.assertEqual([node], [inst.node for inst in result]) self.assertIsInstance(result[0], _instance.Instance) - mock_sleep.assert_called_with(15) - self.assertEqual(3, mock_sleep.call_count) - - def test_success_several_nodes(self, mock_sleep): - nodes = [ - mock.Mock(spec=NODE_FIELDS, provision_state=state) - for state in ('deploying', 'deploy wait', # iteration 1 - 'deploying', 'active', # iteration 2 - 'active') # iteration 3 - ] - self.api.get_node.side_effect = nodes - - result = self.pr.wait_for_provisioning(['uuid1', 'uuid2']) - self.assertEqual(nodes[-2:], [inst.node for inst in result]) - for inst in result: - self.assertIsInstance(inst, _instance.Instance) - - mock_sleep.assert_called_with(15) - self.assertEqual(2, mock_sleep.call_count) - - def test_one_node_failed(self, mock_sleep): - nodes = [ - mock.Mock(spec=NODE_FIELDS, provision_state=state) - for state in ('deploying', 'deploy wait', # iteration 1 - 'deploying', 'deploy failed', # iteration 2 - 'active') # iteration 3 - ] - self.api.get_node.side_effect = nodes - - exc = self.assertRaises(exceptions.DeploymentFailure, - self.pr.wait_for_provisioning, - ['uuid1', 'uuid2']) - # The exception contains the failed node - self.assertEqual(exc.nodes, [nodes[-2]]) - - mock_sleep.assert_called_with(15) - self.assertEqual(2, mock_sleep.call_count) - - def test_timeout(self, mock_sleep): - def _fake_get(*args, **kwargs): - while True: - yield mock.Mock(spec=NODE_FIELDS, - provision_state='deploying') - - self.api.get_node.side_effect = _fake_get() - - exc = self.assertRaises(exceptions.DeploymentFailure, - self.pr.wait_for_provisioning, - ['uuid1', 'uuid2'], - timeout=0.001) - self.assertEqual(2, len(exc.nodes)) - - mock_sleep.assert_called_with(15) - - def test_custom_delay(self, mock_sleep): - nodes = [ - mock.Mock(spec=NODE_FIELDS, provision_state=state) - for state in ('deploying', 'deploy wait', 'deploying', 'active') - ] - self.api.get_node.side_effect = nodes - - result = self.pr.wait_for_provisioning(['uuid1'], delay=1) - self.assertEqual(nodes[-1:], [inst.node for inst in result]) - self.assertIsInstance(result[0], _instance.Instance) - - mock_sleep.assert_called_with(1) - self.assertEqual(3, mock_sleep.call_count) - class TestListInstances(Base): def setUp(self): @@ -1453,11 +1300,11 @@ class TestListInstances(Base): 'deploy failed', 'available') ] del self.nodes[-1].instance_info['metalsmith_hostname'] - self.api.list_nodes.return_value = self.nodes + self.api.baremetal.nodes.return_value = self.nodes def test_list(self): instances = self.pr.list_instances() self.assertTrue(isinstance(i, _instance.Instance) for i in instances) self.assertEqual(self.nodes[:5], [i.node for i in instances]) - self.api.list_nodes.assert_called_once_with(provision_state=None, - associated=True) + self.api.baremetal.nodes.assert_called_once_with(associated=True, + details=True) diff --git a/metalsmith/test/test_scheduler.py b/metalsmith/test/test_scheduler.py index 69d8df4..4299ab9 100644 --- a/metalsmith/test/test_scheduler.py +++ b/metalsmith/test/test_scheduler.py @@ -14,6 +14,7 @@ # limitations under the License. import mock +from openstack import exceptions as sdk_exc import testtools from metalsmith import _scheduler @@ -24,14 +25,14 @@ class TestScheduleNode(testtools.TestCase): def setUp(self): super(TestScheduleNode, self).setUp() - self.nodes = [mock.Mock(spec=['uuid', 'name']) for _ in range(2)] + self.nodes = [mock.Mock(spec=['id', 'name']) for _ in range(2)] self.reserver = self._reserver(lambda x: x) def _reserver(self, side_effect): reserver = mock.Mock(spec=_scheduler.Reserver) reserver.side_effect = side_effect if isinstance(side_effect, Exception): - reserver.fail.side_effect = RuntimeError('failed') + reserver.fail.side_effect = exceptions.ReservationFailed('fail') else: reserver.fail.side_effect = AssertionError('called fail') return reserver @@ -56,15 +57,16 @@ class TestScheduleNode(testtools.TestCase): self.assertFalse(self.reserver.fail.called) def test_reservation_one_failed(self): - reserver = self._reserver([Exception("boom"), self.nodes[1]]) + reserver = self._reserver([sdk_exc.SDKException("boom"), + self.nodes[1]]) result = _scheduler.schedule_node(self.nodes, [], reserver) self.assertIs(result, self.nodes[1]) self.assertEqual([mock.call(n) for n in self.nodes], reserver.call_args_list) def test_reservation_all_failed(self): - reserver = self._reserver(Exception("boom")) - self.assertRaisesRegex(RuntimeError, 'failed', + reserver = self._reserver(sdk_exc.SDKException("boom")) + self.assertRaisesRegex(exceptions.ReservationFailed, 'fail', _scheduler.schedule_node, self.nodes, [], reserver) self.assertEqual([mock.call(n) for n in self.nodes], @@ -121,7 +123,7 @@ class TestCapabilitiesFilter(testtools.TestCase): def test_nothing_requested_nothing_found(self): fltr = _scheduler.CapabilitiesFilter({}) - node = mock.Mock(properties={}, spec=['properties', 'name', 'uuid']) + node = mock.Mock(properties={}, spec=['properties', 'name', 'id']) self.assertTrue(fltr(node)) def test_matching_node(self): @@ -129,7 +131,7 @@ class TestCapabilitiesFilter(testtools.TestCase): 'foo': 'bar'}) node = mock.Mock( properties={'capabilities': 'foo:bar,profile:compute,answer:42'}, - spec=['properties', 'name', 'uuid']) + spec=['properties', 'name', 'id']) self.assertTrue(fltr(node)) def test_not_matching_node(self): @@ -137,14 +139,14 @@ class TestCapabilitiesFilter(testtools.TestCase): 'foo': 'bar'}) node = mock.Mock( properties={'capabilities': 'foo:bar,answer:42'}, - spec=['properties', 'name', 'uuid']) + spec=['properties', 'name', 'id']) self.assertFalse(fltr(node)) def test_fail_message(self): fltr = _scheduler.CapabilitiesFilter({'profile': 'compute'}) node = mock.Mock( properties={'capabilities': 'profile:control'}, - spec=['properties', 'name', 'uuid']) + spec=['properties', 'name', 'id']) self.assertFalse(fltr(node)) self.assertRaisesRegex(exceptions.CapabilitiesNotFound, 'No available nodes found with capabilities ' @@ -156,7 +158,7 @@ class TestCapabilitiesFilter(testtools.TestCase): fltr = _scheduler.CapabilitiesFilter({'profile': 'compute'}) for cap in ['foo,profile:control', 42, 'a:b:c']: node = mock.Mock(properties={'capabilities': cap}, - spec=['properties', 'name', 'uuid']) + spec=['properties', 'name', 'id']) self.assertFalse(fltr(node)) self.assertRaisesRegex(exceptions.CapabilitiesNotFound, 'No available nodes found with capabilities ' @@ -175,24 +177,24 @@ class TestTraitsFilter(testtools.TestCase): def test_no_traits(self): fltr = _scheduler.TraitsFilter([]) - node = mock.Mock(spec=['name', 'uuid']) + node = mock.Mock(spec=['name', 'id']) self.assertTrue(fltr(node)) def test_ok(self): fltr = _scheduler.TraitsFilter(['tr1', 'tr2']) - node = mock.Mock(spec=['name', 'uuid', 'traits'], + node = mock.Mock(spec=['name', 'id', 'traits'], traits=['tr3', 'tr2', 'tr1']) self.assertTrue(fltr(node)) def test_missing_one(self): fltr = _scheduler.TraitsFilter(['tr1', 'tr2']) - node = mock.Mock(spec=['name', 'uuid', 'traits'], + node = mock.Mock(spec=['name', 'id', 'traits'], traits=['tr3', 'tr1']) self.assertFalse(fltr(node)) def test_missing_all(self): fltr = _scheduler.TraitsFilter(['tr1', 'tr2']) - node = mock.Mock(spec=['name', 'uuid', 'traits'], traits=None) + node = mock.Mock(spec=['name', 'id', 'traits'], traits=None) self.assertFalse(fltr(node)) @@ -200,10 +202,12 @@ class TestIronicReserver(testtools.TestCase): def setUp(self): super(TestIronicReserver, self).setUp() - self.node = mock.Mock(spec=['uuid', 'name']) - self.api = mock.Mock(spec=['reserve_node', 'release_node', - 'validate_node']) - self.api.reserve_node.side_effect = lambda node, instance_uuid: node + self.node = mock.Mock(spec=['id', 'name', 'instance_info'], + instance_info={}) + self.api = mock.Mock(spec=['baremetal']) + self.api.baremetal = mock.Mock(spec=['update_node', 'validate_node']) + self.api.baremetal.update_node.side_effect = ( + lambda node, **kw: node) self.reserver = _scheduler.IronicReserver(self.api) def test_fail(self): @@ -213,22 +217,36 @@ class TestIronicReserver(testtools.TestCase): def test_ok(self): self.assertEqual(self.node, self.reserver(self.node)) - self.api.validate_node.assert_called_with(self.node) - self.api.reserve_node.assert_called_once_with( - self.node, instance_uuid=self.node.uuid) + self.api.baremetal.validate_node.assert_called_with( + self.node, required=('power', 'management')) + self.api.baremetal.update_node.assert_called_once_with( + self.node, instance_id=self.node.id, instance_info={}) + + def test_with_instance_info(self): + self.reserver = _scheduler.IronicReserver(self.api, + {'cat': 'meow'}) + self.assertEqual(self.node, self.reserver(self.node)) + self.api.baremetal.validate_node.assert_called_with( + self.node, required=('power', 'management')) + self.api.baremetal.update_node.assert_called_once_with( + self.node, instance_id=self.node.id, + instance_info={'cat': 'meow'}) def test_reservation_failed(self): - self.api.reserve_node.side_effect = RuntimeError('conflict') - self.assertRaisesRegex(RuntimeError, 'conflict', + self.api.baremetal.update_node.side_effect = ( + sdk_exc.SDKException('conflict')) + self.assertRaisesRegex(sdk_exc.SDKException, 'conflict', self.reserver, self.node) - self.api.validate_node.assert_called_with(self.node) - self.api.reserve_node.assert_called_once_with( - self.node, instance_uuid=self.node.uuid) + self.api.baremetal.validate_node.assert_called_with( + self.node, required=('power', 'management')) + self.api.baremetal.update_node.assert_called_once_with( + self.node, instance_id=self.node.id, instance_info={}) def test_validation_failed(self): - self.api.validate_node.side_effect = RuntimeError('fail') + self.api.baremetal.validate_node.side_effect = ( + sdk_exc.SDKException('fail')) self.assertRaisesRegex(exceptions.ValidationFailed, 'fail', self.reserver, self.node) - self.api.validate_node.assert_called_once_with(self.node) - self.assertFalse(self.api.reserve_node.called) - self.assertFalse(self.api.release_node.called) + self.api.baremetal.validate_node.assert_called_with( + self.node, required=('power', 'management')) + self.assertFalse(self.api.baremetal.update_node.called) diff --git a/playbooks/integration/exercise.yaml b/playbooks/integration/exercise.yaml index c9926d2..2622674 100644 --- a/playbooks/integration/exercise.yaml +++ b/playbooks/integration/exercise.yaml @@ -19,7 +19,7 @@ include_role: name: metalsmith_deployment vars: - metalsmith_extra_args: -vv + metalsmith_extra_args: --debug metalsmith_resource_class: baremetal metalsmith_instances: - hostname: test @@ -48,7 +48,7 @@ failed_when: instance_via_list.state != 'active' or instance_via_list.node.provision_state != 'active' - name: Show active node information - command: openstack baremetal node show {{ instance.node.uuid }} + command: openstack baremetal node show {{ instance.node.id }} - name: Get IP address set_fact: @@ -69,7 +69,7 @@ command: metalsmith --debug undeploy --wait 900 test - name: Get the current status of the deployed node - command: openstack baremetal node show {{ instance.node.uuid }} -f json + command: openstack baremetal node show {{ instance.node.id }} -f json register: undeployed_node_result - name: Parse node state @@ -87,7 +87,7 @@ when: undeployed_node.extra != {} - name: Get attached VIFs for the node - command: openstack baremetal node vif list {{ instance.node.uuid }} -f value -c ID + command: openstack baremetal node vif list {{ instance.node.id }} -f value -c ID register: vif_list_output - name: Check that no VIFs are still attached diff --git a/requirements.txt b/requirements.txt index ee27962..eced6b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 -openstacksdk>=0.17.0 # Apache-2.0 -python-ironicclient>=1.14.0 # Apache-2.0 +openstacksdk>=0.22.0 # Apache-2.0 requests>=2.18.4 # Apache-2.0 six>=1.10.0 # MIT