Use allocation name for hostname instead of a custom field
This concludes the switch to the allocation API. Change-Id: I25cdae7d17604140f728fdbcfea4110cbd222679
This commit is contained in:
parent
b1769b01cc
commit
e9c25b02e5
@ -5,7 +5,7 @@ fixtures==3.0.0
|
||||
flake8-import-order==0.13
|
||||
hacking==1.0.0
|
||||
mock==2.0
|
||||
openstacksdk==0.28.0
|
||||
openstacksdk==0.29.0
|
||||
pbr==2.0.0
|
||||
Pygments==2.2.0
|
||||
requests==2.18.4
|
||||
|
@ -82,7 +82,7 @@ class Instance(object):
|
||||
@property
|
||||
def hostname(self):
|
||||
"""Node's hostname."""
|
||||
return self._node.instance_info.get(_utils.HOSTNAME_FIELD)
|
||||
return _utils.hostname_for(self._node, self._allocation)
|
||||
|
||||
def ip_addresses(self):
|
||||
"""Returns IP addresses for this instance.
|
||||
|
@ -33,11 +33,10 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
_CREATED_PORTS = 'metalsmith_created_ports'
|
||||
_ATTACHED_PORTS = 'metalsmith_attached_ports'
|
||||
_PRESERVE_INSTANCE_INFO_KEYS = {'capabilities', 'traits',
|
||||
_utils.HOSTNAME_FIELD}
|
||||
_PRESERVE_INSTANCE_INFO_KEYS = {'capabilities', 'traits'}
|
||||
|
||||
|
||||
class Provisioner(_utils.GetNodeMixin):
|
||||
class Provisioner(object):
|
||||
"""API to deploy/undeploy nodes with OpenStack.
|
||||
|
||||
:param session: `Session` object (from ``keystoneauth``) to use when
|
||||
@ -94,7 +93,7 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
:raises: :py:class:`metalsmith.exceptions.ReservationFailed`
|
||||
"""
|
||||
capabilities = capabilities or {}
|
||||
self._check_hostname(hostname)
|
||||
_utils.check_hostname(hostname)
|
||||
|
||||
if candidates or capabilities or conductor_group or predicate:
|
||||
# Predicates, capabilities and conductor groups are not supported
|
||||
@ -107,7 +106,7 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
|
||||
node = self._reserve_node(resource_class, hostname=hostname,
|
||||
candidates=candidates, traits=traits,
|
||||
capabilities=capabilities)
|
||||
capabilities=capabilities)[0]
|
||||
return node
|
||||
|
||||
def _prefilter_nodes(self, resource_class, conductor_group, capabilities,
|
||||
@ -186,26 +185,20 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
six.reraise(*exc_info)
|
||||
|
||||
LOG.debug('Reserved node: %s', node)
|
||||
return node
|
||||
return node, allocation
|
||||
|
||||
def _patch_reserved_node(self, node, allocation, hostname, capabilities):
|
||||
"""Make required updates on a newly reserved node."""
|
||||
if not hostname:
|
||||
hostname = _utils.default_hostname(node)
|
||||
patch = [
|
||||
{'path': '/instance_info/%s' % _utils.HOSTNAME_FIELD,
|
||||
'op': 'add', 'value': hostname}
|
||||
]
|
||||
|
||||
if capabilities:
|
||||
patch.append({'path': '/instance_info/capabilities',
|
||||
'op': 'add', 'value': capabilities})
|
||||
patch = [{'path': '/instance_info/capabilities',
|
||||
'op': 'add', 'value': capabilities}]
|
||||
LOG.debug('Patching reserved node %(node)s with %(patch)s',
|
||||
{'node': _utils.log_res(node), 'patch': patch})
|
||||
return self.connection.baremetal.patch_node(node, patch)
|
||||
else:
|
||||
return node
|
||||
|
||||
LOG.debug('Patching reserved node %(node)s with %(patch)s',
|
||||
{'node': _utils.log_res(node), 'patch': patch})
|
||||
return self.connection.baremetal.patch_node(node, patch)
|
||||
|
||||
def _check_node_for_deploy(self, node):
|
||||
def _check_node_for_deploy(self, node, hostname):
|
||||
"""Check that node is ready and reserve it if needed.
|
||||
|
||||
These checks are done outside of the try..except block in
|
||||
@ -213,33 +206,6 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
Particularly, we don't want to try clean up nodes that were not
|
||||
reserved by us or are in maintenance mode.
|
||||
"""
|
||||
try:
|
||||
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_id:
|
||||
if not node.resource_class:
|
||||
raise exceptions.InvalidNode(
|
||||
'Cannot create an allocation for node %s that '
|
||||
'does not have a resource class set'
|
||||
% _utils.log_res(node))
|
||||
|
||||
if not self._dry_run:
|
||||
LOG.debug('Node %s not reserved yet, reserving',
|
||||
_utils.log_res(node))
|
||||
# Not updating instance_info since it will be updated later
|
||||
node = self._reserve_node(node.resource_class,
|
||||
candidates=[node.id],
|
||||
update_instance_info=False)
|
||||
elif node.instance_id != node.id and not node.allocation_id:
|
||||
raise exceptions.InvalidNode('Node %(node)s already reserved '
|
||||
'by instance %(inst)s outside of '
|
||||
'metalsmith, cannot deploy on it' %
|
||||
{'node': _utils.log_res(node),
|
||||
'inst': node.instance_id})
|
||||
|
||||
if node.is_maintenance:
|
||||
raise exceptions.InvalidNode('Refusing to deploy on node %(node)s '
|
||||
'which is in maintenance mode due to '
|
||||
@ -247,26 +213,98 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
{'node': _utils.log_res(node),
|
||||
'reason': node.maintenance_reason})
|
||||
|
||||
return node
|
||||
allocation = None
|
||||
|
||||
def _check_hostname(self, hostname, node=None):
|
||||
"""Check the provided host name.
|
||||
# Make sure the hostname does not correspond to an existing allocation
|
||||
# for another node.
|
||||
if hostname is not None:
|
||||
allocation = self._check_allocation_for_hostname(node, hostname)
|
||||
|
||||
:raises: ValueError on inappropriate value of ``hostname``
|
||||
"""
|
||||
if hostname is None:
|
||||
if node.allocation_id:
|
||||
if allocation is None:
|
||||
# Previously created allocation, verify/update it
|
||||
allocation = self._check_and_update_allocation_for_node(
|
||||
node, hostname)
|
||||
elif node.instance_id:
|
||||
# Old-style reservations with instance_uuid==node.uuid
|
||||
if 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_res(node), 'inst': node.instance_id})
|
||||
elif hostname:
|
||||
# We have no way to update hostname without allocations
|
||||
raise exceptions.InvalidNode(
|
||||
'Node %s does not use allocations, cannot update '
|
||||
'hostname for it' % _utils.log_res(node))
|
||||
else:
|
||||
# Node is not reserved at all - reserve it
|
||||
if not node.resource_class:
|
||||
raise exceptions.InvalidNode(
|
||||
'Cannot create an allocation for node %s that '
|
||||
'does not have a resource class set'
|
||||
% _utils.log_res(node))
|
||||
|
||||
if not self._dry_run:
|
||||
if not hostname:
|
||||
hostname = _utils.default_hostname(node)
|
||||
LOG.debug('Node %(node)s is not reserved yet, reserving for '
|
||||
'hostname %(host)s',
|
||||
{'node': _utils.log_res(node),
|
||||
'host': hostname})
|
||||
# Not updating instance_info since it will be updated later
|
||||
node, allocation = self._reserve_node(
|
||||
node.resource_class,
|
||||
hostname=hostname,
|
||||
candidates=[node.id],
|
||||
update_instance_info=False)
|
||||
|
||||
return node, allocation
|
||||
|
||||
def _check_allocation_for_hostname(self, node, hostname):
|
||||
try:
|
||||
allocation = self.connection.baremetal.get_allocation(
|
||||
hostname)
|
||||
except os_exc.ResourceNotFound:
|
||||
return
|
||||
|
||||
if not _utils.is_hostname_safe(hostname):
|
||||
raise ValueError("%s cannot be used as a hostname" % hostname)
|
||||
|
||||
existing = self._find_node_by_hostname(hostname)
|
||||
if (existing is not None and node is not None
|
||||
and existing.id != node.id):
|
||||
raise ValueError("The following node already uses hostname "
|
||||
"%(host)s: %(node)s" %
|
||||
if allocation.node_id and allocation.node_id != node.id:
|
||||
raise ValueError("The following node already uses "
|
||||
"hostname %(host)s: %(node)s" %
|
||||
{'host': hostname,
|
||||
'node': _utils.log_res(existing)})
|
||||
'node': allocation.node_id})
|
||||
else:
|
||||
return allocation
|
||||
|
||||
def _check_and_update_allocation_for_node(self, node, hostname=None):
|
||||
# No allocation with given hostname, find one corresponding to the
|
||||
# node.
|
||||
allocation = self.connection.baremetal.get_allocation(
|
||||
node.allocation_id)
|
||||
if allocation.name and hostname and allocation.name != hostname:
|
||||
# Prevent updating of an existing hostname, since we don't
|
||||
# understand the intention
|
||||
raise exceptions.InvalidNode(
|
||||
"Allocation %(alloc)s associated with node %(node)s "
|
||||
"uses hostname %(old)s that does not match the expected "
|
||||
"hostname %(new)s" %
|
||||
{'alloc': _utils.log_res(allocation),
|
||||
'node': _utils.log_res(node),
|
||||
'old': allocation.name,
|
||||
'new': hostname})
|
||||
elif not allocation.name and not self._dry_run:
|
||||
if not hostname:
|
||||
hostname = _utils.default_hostname(node)
|
||||
# Set the hostname that was not set in reserve_node.
|
||||
LOG.debug('Updating allocation %(alloc)s for node '
|
||||
'%(node)s with hostname %(host)s',
|
||||
{'alloc': _utils.log_res(allocation),
|
||||
'node': _utils.log_res(node),
|
||||
'host': hostname})
|
||||
allocation = self.connection.baremetal.update_allocation(
|
||||
allocation, name=hostname)
|
||||
|
||||
return allocation
|
||||
|
||||
def provision_node(self, node, image, nics=None, root_size_gb=None,
|
||||
swap_size_mb=None, config=None, hostname=None,
|
||||
@ -331,11 +369,18 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
if isinstance(image, six.string_types):
|
||||
image = sources.GlanceImage(image)
|
||||
|
||||
node = self._check_node_for_deploy(node)
|
||||
_utils.check_hostname(hostname)
|
||||
|
||||
try:
|
||||
node = self._get_node(node)
|
||||
except Exception as exc:
|
||||
raise exceptions.InvalidNode('Cannot find node %(node)s: %(exc)s' %
|
||||
{'node': node, 'exc': exc})
|
||||
|
||||
node, allocation = self._check_node_for_deploy(node, hostname)
|
||||
nics = _nics.NICs(self.connection, node, nics)
|
||||
|
||||
try:
|
||||
self._check_hostname(hostname, node=node)
|
||||
root_size_gb = _utils.get_root_disk(root_size_gb, node)
|
||||
|
||||
image._validate(self.connection)
|
||||
@ -357,11 +402,6 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
instance_info = self._clean_instance_info(node.instance_info)
|
||||
instance_info['root_gb'] = root_size_gb
|
||||
instance_info['capabilities'] = capabilities
|
||||
if hostname:
|
||||
instance_info[_utils.HOSTNAME_FIELD] = hostname
|
||||
elif not instance_info.get(_utils.HOSTNAME_FIELD):
|
||||
instance_info[_utils.HOSTNAME_FIELD] = _utils.default_hostname(
|
||||
node)
|
||||
|
||||
extra = node.extra.copy()
|
||||
extra[_CREATED_PORTS] = nics.created_ports
|
||||
@ -382,9 +422,10 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
|
||||
LOG.debug('Generating a configdrive for node %s',
|
||||
_utils.log_res(node))
|
||||
cd = config.generate(node, _utils.hostname_for(node, allocation))
|
||||
LOG.debug('Starting provisioning of node %s', _utils.log_res(node))
|
||||
self.connection.baremetal.set_node_provision_state(
|
||||
node, 'active', config_drive=config.generate(node))
|
||||
node, 'active', config_drive=cd)
|
||||
except Exception:
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
@ -408,8 +449,8 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
LOG.info('Deploy succeeded on node %s', _utils.log_res(node))
|
||||
else:
|
||||
# Update the node to return it's latest state
|
||||
node = self._get_node(node, refresh=True)
|
||||
instance = self._get_instance(node)
|
||||
node = self.connection.baremetal.get_node(node.id)
|
||||
instance = _instance.Instance(self.connection, node, allocation)
|
||||
|
||||
return instance
|
||||
|
||||
@ -425,10 +466,12 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
(more precisely, until the operation times out on server side).
|
||||
: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.
|
||||
:raises: `openstack.exceptions.ResourceTimeout` if deployment times
|
||||
out for any node.
|
||||
:raises: `openstack.exceptions.SDKException` if deployment fails
|
||||
for any node.
|
||||
"""
|
||||
nodes = [self._get_node(n, accept_hostname=True) for n in nodes]
|
||||
nodes = [self._find_node_and_allocation(n)[0] for n in nodes]
|
||||
nodes = self.connection.baremetal.wait_for_nodes_provision_state(
|
||||
nodes, 'active', timeout=timeout)
|
||||
# Using _get_instance in case the deployment started by something
|
||||
@ -464,7 +507,7 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
except Exception as exc:
|
||||
LOG.debug('Failed to remove allocation %(alloc)s for %(node)s:'
|
||||
' %(exc)s',
|
||||
{'alloc': node.allocaiton_id,
|
||||
{'alloc': node.allocation_id,
|
||||
'node': _utils.log_res(node), 'exc': exc})
|
||||
elif not node.allocation_id:
|
||||
# Old-style reservations have to be cleared explicitly
|
||||
@ -491,7 +534,7 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
None to return immediately.
|
||||
:return: the latest `Node` object.
|
||||
"""
|
||||
node = self._get_node(node, accept_hostname=True)
|
||||
node = self._find_node_and_allocation(node)[0]
|
||||
if self._dry_run:
|
||||
LOG.warning("Dry run, not unprovisioning")
|
||||
return
|
||||
@ -519,16 +562,6 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
"""
|
||||
return self.show_instances([instance_id])[0]
|
||||
|
||||
def _get_instance(self, ident):
|
||||
node = self._get_node(ident, accept_hostname=True)
|
||||
if node.allocation_id:
|
||||
allocation = self.connection.baremetal.get_allocation(
|
||||
node.allocation_id)
|
||||
else:
|
||||
allocation = None
|
||||
return _instance.Instance(self.connection, node,
|
||||
allocation=allocation)
|
||||
|
||||
def show_instances(self, instances):
|
||||
"""Show information about instance.
|
||||
|
||||
@ -541,8 +574,7 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
:raises: :py:class:`metalsmith.exceptions.InvalidInstance`
|
||||
if one of the instances are not valid instances.
|
||||
"""
|
||||
with self._cache_node_list_for_lookup():
|
||||
result = [self._get_instance(inst) for inst in instances]
|
||||
result = [self._get_instance(inst) for inst in instances]
|
||||
# NOTE(dtantsur): do not accept node names as valid instances if they
|
||||
# are not deployed or being deployed.
|
||||
missing = [inst for (res, inst) in zip(result, instances)
|
||||
@ -562,3 +594,45 @@ class Provisioner(_utils.GetNodeMixin):
|
||||
instances = [i for i in map(self._get_instance, nodes)
|
||||
if i.state != _instance.InstanceState.UNKNOWN]
|
||||
return instances
|
||||
|
||||
def _get_node(self, node, refresh=False):
|
||||
"""A helper to find and return a node."""
|
||||
if isinstance(node, six.string_types):
|
||||
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.id)
|
||||
else:
|
||||
return node
|
||||
|
||||
def _find_node_and_allocation(self, node, refresh=False):
|
||||
if (not isinstance(node, six.string_types)
|
||||
or not _utils.is_hostname_safe(node)):
|
||||
return self._get_node(node, refresh=refresh), None
|
||||
|
||||
try:
|
||||
allocation = self.connection.baremetal.get_allocation(node)
|
||||
except os_exc.ResourceNotFound:
|
||||
return self._get_node(node, refresh=refresh), None
|
||||
else:
|
||||
if allocation.node_id:
|
||||
return (self.connection.baremetal.get_node(allocation.node_id),
|
||||
allocation)
|
||||
else:
|
||||
raise exceptions.InvalidInstance(
|
||||
'Allocation %s exists but is not associated '
|
||||
'with a node' % node)
|
||||
|
||||
def _get_instance(self, ident):
|
||||
node, allocation = self._find_node_and_allocation(ident)
|
||||
if allocation is None and node.allocation_id:
|
||||
allocation = self.connection.baremetal.get_allocation(
|
||||
node.allocation_id)
|
||||
|
||||
return _instance.Instance(self.connection, node,
|
||||
allocation=allocation)
|
||||
|
@ -13,10 +13,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import contextlib
|
||||
import re
|
||||
|
||||
from openstack import exceptions as sdk_exc
|
||||
import six
|
||||
|
||||
from metalsmith import exceptions
|
||||
@ -90,6 +88,15 @@ def is_hostname_safe(hostname):
|
||||
return _HOSTNAME_RE.match(hostname) is not None
|
||||
|
||||
|
||||
def check_hostname(hostname):
|
||||
"""Check the provided host name.
|
||||
|
||||
:raises: ValueError on inappropriate value of ``hostname``
|
||||
"""
|
||||
if hostname is not None and not is_hostname_safe(hostname):
|
||||
raise ValueError("%s cannot be used as a hostname" % hostname)
|
||||
|
||||
|
||||
def parse_checksums(checksums):
|
||||
"""Parse standard checksums file."""
|
||||
result = {}
|
||||
@ -103,15 +110,6 @@ def parse_checksums(checksums):
|
||||
return result
|
||||
|
||||
|
||||
# NOTE(dtantsur): make this private since it will no longer be possible with
|
||||
# transition to allocation API.
|
||||
class DuplicateHostname(sdk_exc.SDKException, exceptions.Error):
|
||||
pass
|
||||
|
||||
|
||||
HOSTNAME_FIELD = 'metalsmith_hostname'
|
||||
|
||||
|
||||
def default_hostname(node):
|
||||
if node.name and is_hostname_safe(node.name):
|
||||
return node.name
|
||||
@ -119,60 +117,8 @@ def default_hostname(node):
|
||||
return node.id
|
||||
|
||||
|
||||
class GetNodeMixin(object):
|
||||
"""A helper mixin for getting nodes with hostnames."""
|
||||
|
||||
_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(HOSTNAME_FIELD) == hostname]
|
||||
if len(existing) > 1:
|
||||
raise DuplicateHostname(
|
||||
"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.id)
|
||||
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
|
||||
def hostname_for(node, allocation=None):
|
||||
if allocation is not None and allocation.name:
|
||||
return allocation.name
|
||||
else:
|
||||
return default_hostname(node)
|
||||
|
@ -68,10 +68,7 @@ class CapabilitiesNotFound(ReservationFailed):
|
||||
|
||||
|
||||
class TraitsNotFound(ReservationFailed):
|
||||
"""Requested traits do not match any nodes.
|
||||
|
||||
:ivar requested_traits: Requested node's traits.
|
||||
"""
|
||||
"""DEPRECATED."""
|
||||
|
||||
def __init__(self, message, traits):
|
||||
self.requested_traits = traits
|
||||
@ -83,10 +80,7 @@ class ValidationFailed(ReservationFailed):
|
||||
|
||||
|
||||
class NoNodesReserved(ReservationFailed):
|
||||
"""All nodes are already reserved or failed validation.
|
||||
|
||||
:ivar nodes: List of nodes that were checked.
|
||||
"""
|
||||
"""DEPRECATED."""
|
||||
|
||||
def __init__(self, nodes):
|
||||
self.nodes = nodes
|
||||
@ -112,10 +106,7 @@ class InvalidNode(Error):
|
||||
|
||||
|
||||
class DeploymentFailure(Error):
|
||||
"""One or more nodes have failed the deployment.
|
||||
|
||||
:ivar nodes: List of failed nodes.
|
||||
"""
|
||||
"""DEPRECATED."""
|
||||
|
||||
def __init__(self, message, nodes):
|
||||
self.nodes = nodes
|
||||
|
@ -44,10 +44,11 @@ class GenericConfig(object):
|
||||
self.ssh_keys = ssh_keys or []
|
||||
self.user_data = user_data
|
||||
|
||||
def generate(self, node):
|
||||
def generate(self, node, hostname=None):
|
||||
"""Generate the config drive information.
|
||||
|
||||
:param node: `Node` object.
|
||||
:param hostname: Desired hostname (defaults to node's name or ID).
|
||||
:return: configdrive contents as a dictionary with keys:
|
||||
|
||||
``meta_data``
|
||||
@ -55,7 +56,8 @@ class GenericConfig(object):
|
||||
``user_data``
|
||||
user data as a string
|
||||
"""
|
||||
hostname = node.instance_info.get(_utils.HOSTNAME_FIELD)
|
||||
if not hostname:
|
||||
hostname = _utils.default_hostname(node)
|
||||
|
||||
# NOTE(dtantsur): CirrOS does not understand lists
|
||||
if isinstance(self.ssh_keys, list):
|
||||
@ -85,19 +87,20 @@ class GenericConfig(object):
|
||||
"""
|
||||
return self.user_data
|
||||
|
||||
def build_configdrive(self, node):
|
||||
def build_configdrive(self, node, hostname=None):
|
||||
"""Make the config drive ISO.
|
||||
|
||||
Deprecated, use :py:meth:`generate` with openstacksdk's
|
||||
``openstack.baremetal.configdrive.build`` instead.
|
||||
|
||||
:param node: `Node` object.
|
||||
:param hostname: Desired hostname (defaults to node's name or ID).
|
||||
:return: configdrive contents as a base64-encoded string.
|
||||
"""
|
||||
warnings.warn("build_configdrive is deprecated, use generate with "
|
||||
"openstacksdk's openstack.baremetal.configdrive.build "
|
||||
"instead", DeprecationWarning)
|
||||
cd = self.generate(node)
|
||||
cd = self.generate(node, hostname)
|
||||
metadata = cd.pop('meta_data')
|
||||
user_data = cd.pop('user_data')
|
||||
if user_data:
|
||||
|
@ -125,12 +125,11 @@ class TestInstanceStates(test_provisioner.Base):
|
||||
def test_to_dict(self, mock_ips):
|
||||
self.node.provision_state = 'wait call-back'
|
||||
self.node.to_dict.return_value = {'node': 'dict'}
|
||||
self.node.instance_info = {'metalsmith_hostname': 'host'}
|
||||
mock_ips.return_value = {'private': ['1.2.3.4']}
|
||||
|
||||
to_dict = self.instance.to_dict()
|
||||
self.assertEqual({'allocation': None,
|
||||
'hostname': 'host',
|
||||
'hostname': self.node.name,
|
||||
'ip_addresses': {'private': ['1.2.3.4']},
|
||||
'node': {'node': 'dict'},
|
||||
'state': 'deploying',
|
||||
@ -143,9 +142,9 @@ class TestInstanceStates(test_provisioner.Base):
|
||||
def test_to_dict_with_allocation(self, mock_ips):
|
||||
self.node.provision_state = 'wait call-back'
|
||||
self.node.to_dict.return_value = {'node': 'dict'}
|
||||
self.node.instance_info = {'metalsmith_hostname': 'host'}
|
||||
mock_ips.return_value = {'private': ['1.2.3.4']}
|
||||
self.instance._allocation = mock.Mock()
|
||||
self.instance._allocation.name = 'host'
|
||||
self.instance._allocation.to_dict.return_value = {'alloc': 'dict'}
|
||||
|
||||
to_dict = self.instance.to_dict()
|
||||
|
@ -20,7 +20,6 @@ from openstack.baremetal import configdrive
|
||||
import testtools
|
||||
|
||||
import metalsmith
|
||||
from metalsmith import _utils
|
||||
from metalsmith import instance_config
|
||||
|
||||
|
||||
@ -33,21 +32,19 @@ class TestGenericConfig(testtools.TestCase):
|
||||
self.node.name = 'node name'
|
||||
|
||||
def _check(self, config, expected_metadata, expected_userdata=None,
|
||||
cloud_init=True):
|
||||
cloud_init=True, hostname=None):
|
||||
expected_m = {'public_keys': {},
|
||||
'uuid': '1234',
|
||||
'name': 'node name',
|
||||
'hostname': 'example.com',
|
||||
'uuid': self.node.id,
|
||||
'name': self.node.name,
|
||||
'hostname': self.node.id,
|
||||
'launch_index': 0,
|
||||
'availability_zone': '',
|
||||
'files': [],
|
||||
'meta': {}}
|
||||
expected_m.update(expected_metadata)
|
||||
self.node.instance_info = {_utils.HOSTNAME_FIELD:
|
||||
expected_m.get('hostname')}
|
||||
|
||||
with mock.patch.object(configdrive, 'build', autospec=True) as mb:
|
||||
result = config.build_configdrive(self.node)
|
||||
result = config.build_configdrive(self.node, hostname)
|
||||
mb.assert_called_once_with(expected_m, mock.ANY)
|
||||
self.assertIs(result, mb.return_value)
|
||||
user_data = mb.call_args[1].get('user_data')
|
||||
@ -65,6 +62,16 @@ class TestGenericConfig(testtools.TestCase):
|
||||
config = self.CLASS()
|
||||
self._check(config, {})
|
||||
|
||||
def test_name_as_hostname(self):
|
||||
self.node.name = 'example.com'
|
||||
config = self.CLASS()
|
||||
self._check(config, {'hostname': 'example.com'})
|
||||
|
||||
def test_explicit_hostname(self):
|
||||
config = self.CLASS()
|
||||
self._check(config, {'hostname': 'example.com'},
|
||||
hostname='example.com')
|
||||
|
||||
def test_ssh_keys(self):
|
||||
config = self.CLASS(ssh_keys=['abc', 'def'])
|
||||
self._check(config, {'public_keys': {'0': 'abc', '1': 'def'}})
|
||||
|
@ -21,7 +21,6 @@ import testtools
|
||||
|
||||
from metalsmith import _instance
|
||||
from metalsmith import _provisioner
|
||||
from metalsmith import _utils
|
||||
from metalsmith import exceptions
|
||||
from metalsmith import instance_config
|
||||
from metalsmith import sources
|
||||
@ -76,12 +75,8 @@ class Base(testtools.TestCase):
|
||||
fixtures.MockPatchObject(_provisioner.Provisioner, '_get_node',
|
||||
autospec=True)).mock
|
||||
self.mock_get_node.side_effect = (
|
||||
lambda self, n, refresh=False, accept_hostname=False: n
|
||||
lambda self, n, refresh=False: n
|
||||
)
|
||||
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.baremetal.patch_node.side_effect = lambda n, _p: n
|
||||
@ -95,6 +90,71 @@ class Base(testtools.TestCase):
|
||||
self.pr.connection = self.api
|
||||
|
||||
|
||||
class TestGetFindNode(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestGetFindNode, self).setUp()
|
||||
self.pr = _provisioner.Provisioner(mock.Mock())
|
||||
self.api = mock.Mock(spec=['baremetal'])
|
||||
self.pr.connection = self.api
|
||||
|
||||
def test__get_node_with_node(self):
|
||||
node = mock.Mock(spec=['id', 'name'])
|
||||
result = self.pr._get_node(node)
|
||||
self.assertIs(result, node)
|
||||
self.assertFalse(self.api.baremetal.get_node.called)
|
||||
|
||||
def test__get_node_with_node_refresh(self):
|
||||
node = mock.Mock(spec=['id', 'name'])
|
||||
result = self.pr._get_node(node, refresh=True)
|
||||
self.assertIs(result, self.api.baremetal.get_node.return_value)
|
||||
self.api.baremetal.get_node.assert_called_once_with(node.id)
|
||||
|
||||
def test__get_node_with_instance(self):
|
||||
node = mock.Mock(spec=['uuid', 'node'])
|
||||
result = self.pr._get_node(node)
|
||||
self.assertIs(result, node.node)
|
||||
self.assertFalse(self.api.baremetal.get_node.called)
|
||||
|
||||
def test__get_node_with_instance_refresh(self):
|
||||
node = mock.Mock(spec=['uuid', 'node'])
|
||||
result = self.pr._get_node(node, refresh=True)
|
||||
self.assertIs(result, self.api.baremetal.get_node.return_value)
|
||||
self.api.baremetal.get_node.assert_called_once_with(node.node.id)
|
||||
|
||||
def test__get_node_with_string(self):
|
||||
result = self.pr._get_node('node')
|
||||
self.assertIs(result, self.api.baremetal.get_node.return_value)
|
||||
self.api.baremetal.get_node.assert_called_once_with('node')
|
||||
|
||||
def test__find_node_and_allocation_by_node(self):
|
||||
node = mock.Mock(spec=['id', 'name'])
|
||||
result, alloc = self.pr._find_node_and_allocation(node)
|
||||
self.assertIs(result, node)
|
||||
self.assertIsNone(alloc)
|
||||
|
||||
def test__find_node_and_allocation_by_hostname(self):
|
||||
result, alloc = self.pr._find_node_and_allocation('node')
|
||||
self.assertIs(result, self.api.baremetal.get_node.return_value)
|
||||
self.assertIs(alloc, self.api.baremetal.get_allocation.return_value)
|
||||
self.api.baremetal.get_node.assert_called_once_with(
|
||||
self.api.baremetal.get_allocation.return_value.node_id)
|
||||
|
||||
def test__find_node_and_allocation_by_node_id(self):
|
||||
self.api.baremetal.get_allocation.side_effect = (
|
||||
os_exc.ResourceNotFound())
|
||||
result, alloc = self.pr._find_node_and_allocation('node')
|
||||
self.assertIs(result, self.api.baremetal.get_node.return_value)
|
||||
self.assertIsNone(alloc)
|
||||
self.api.baremetal.get_node.assert_called_once_with('node')
|
||||
|
||||
def test__find_node_and_allocation_by_hostname_bad_allocation(self):
|
||||
self.api.baremetal.get_allocation.return_value.node_id = None
|
||||
self.assertRaises(exceptions.InvalidInstance,
|
||||
self.pr._find_node_and_allocation, 'node')
|
||||
self.assertFalse(self.api.baremetal.get_node.called)
|
||||
|
||||
|
||||
class TestReserveNode(Base):
|
||||
|
||||
RSC = 'baremetal'
|
||||
@ -132,9 +192,8 @@ class TestReserveNode(Base):
|
||||
self.api.baremetal.create_allocation.return_value)
|
||||
self.api.baremetal.get_node.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
node, [{'path': '/instance_info/metalsmith_hostname',
|
||||
'op': 'add', 'value': node.id}])
|
||||
self.assertFalse(self.api.baremetal.patch_node.called)
|
||||
self.assertFalse(self.api.baremetal.delete_allocation.called)
|
||||
|
||||
def test_allocation_failed(self):
|
||||
self.api.baremetal.wait_for_allocation.side_effect = (
|
||||
@ -150,22 +209,22 @@ class TestReserveNode(Base):
|
||||
self.api.baremetal.create_allocation.return_value)
|
||||
self.assertFalse(self.api.baremetal.patch_node.called)
|
||||
|
||||
def test_node_update_failed(self):
|
||||
expected = self._node()
|
||||
self.api.baremetal.get_node.return_value = expected
|
||||
self.api.baremetal.patch_node.side_effect = os_exc.SDKException('boom')
|
||||
@mock.patch.object(_provisioner.LOG, 'exception', autospec=True)
|
||||
def test_allocation_failed_clean_up_failed(self, mock_log):
|
||||
self.api.baremetal.delete_allocation.side_effect = RuntimeError()
|
||||
self.api.baremetal.wait_for_allocation.side_effect = (
|
||||
os_exc.SDKException('boom'))
|
||||
|
||||
self.assertRaisesRegex(os_exc.SDKException, 'boom',
|
||||
self.assertRaisesRegex(exceptions.ReservationFailed, 'boom',
|
||||
self.pr.reserve_node, self.RSC)
|
||||
|
||||
self.api.baremetal.create_allocation.assert_called_once_with(
|
||||
name=None, candidate_nodes=None,
|
||||
resource_class=self.RSC, traits=None)
|
||||
self.api.baremetal.delete_allocation.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
expected, [{'path': '/instance_info/metalsmith_hostname',
|
||||
'op': 'add', 'value': expected.id}])
|
||||
self.api.baremetal.create_allocation.return_value)
|
||||
self.assertFalse(self.api.baremetal.patch_node.called)
|
||||
mock_log.assert_called_once_with('Failed to delete failed allocation')
|
||||
|
||||
def test_with_hostname(self):
|
||||
expected = self._node()
|
||||
@ -180,26 +239,7 @@ class TestReserveNode(Base):
|
||||
resource_class=self.RSC, traits=None)
|
||||
self.api.baremetal.get_node.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
node, [{'path': '/instance_info/metalsmith_hostname',
|
||||
'op': 'add', 'value': 'example.com'}])
|
||||
|
||||
def test_with_name_as_hostname(self):
|
||||
expected = self._node(name='example.com')
|
||||
self.api.baremetal.get_node.return_value = expected
|
||||
self.api.baremetal.nodes.return_value = [expected, self._node()]
|
||||
|
||||
node = self.pr.reserve_node(self.RSC)
|
||||
|
||||
self.assertIs(expected, node)
|
||||
self.api.baremetal.create_allocation.assert_called_once_with(
|
||||
name=None, candidate_nodes=None,
|
||||
resource_class=self.RSC, traits=None)
|
||||
self.api.baremetal.get_node.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
node, [{'path': '/instance_info/metalsmith_hostname',
|
||||
'op': 'add', 'value': 'example.com'}])
|
||||
self.assertFalse(self.api.baremetal.patch_node.called)
|
||||
|
||||
def test_with_capabilities(self):
|
||||
nodes = [
|
||||
@ -219,11 +259,29 @@ class TestReserveNode(Base):
|
||||
self.api.baremetal.get_node.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
node, [{'path': '/instance_info/metalsmith_hostname',
|
||||
'op': 'add', 'value': node.id},
|
||||
{'path': '/instance_info/capabilities',
|
||||
node, [{'path': '/instance_info/capabilities',
|
||||
'op': 'add', 'value': {'answer': '42'}}])
|
||||
|
||||
def test_node_update_failed(self):
|
||||
expected = self._node(properties={'local_gb': 100,
|
||||
'capabilities': {'answer': '42'}})
|
||||
self.api.baremetal.get_node.return_value = expected
|
||||
self.api.baremetal.nodes.return_value = [expected]
|
||||
self.api.baremetal.patch_node.side_effect = os_exc.SDKException('boom')
|
||||
|
||||
self.assertRaisesRegex(os_exc.SDKException, 'boom',
|
||||
self.pr.reserve_node, self.RSC,
|
||||
capabilities={'answer': '42'})
|
||||
|
||||
self.api.baremetal.create_allocation.assert_called_once_with(
|
||||
name=None, candidate_nodes=[expected.id],
|
||||
resource_class=self.RSC, traits=None)
|
||||
self.api.baremetal.delete_allocation.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
expected, [{'path': '/instance_info/capabilities',
|
||||
'op': 'add', 'value': {'answer': '42'}}])
|
||||
|
||||
def test_with_traits(self):
|
||||
expected = self._node(properties={'local_gb': 100},
|
||||
traits=['foo', 'answer:42'])
|
||||
@ -232,9 +290,7 @@ class TestReserveNode(Base):
|
||||
node = self.pr.reserve_node(self.RSC, traits=['foo', 'answer:42'])
|
||||
|
||||
self.assertIs(node, expected)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
node, [{'path': '/instance_info/metalsmith_hostname',
|
||||
'op': 'add', 'value': node.id}])
|
||||
self.assertFalse(self.api.baremetal.patch_node.called)
|
||||
|
||||
def test_custom_predicate(self):
|
||||
nodes = [self._node(properties={'local_gb': i})
|
||||
@ -252,9 +308,7 @@ class TestReserveNode(Base):
|
||||
resource_class=self.RSC, traits=None)
|
||||
self.api.baremetal.get_node.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
node, [{'path': '/instance_info/metalsmith_hostname',
|
||||
'op': 'add', 'value': node.id}])
|
||||
self.assertFalse(self.api.baremetal.patch_node.called)
|
||||
|
||||
def test_custom_predicate_false(self):
|
||||
nodes = [self._node() for _ in range(3)]
|
||||
@ -281,9 +335,7 @@ class TestReserveNode(Base):
|
||||
resource_class=self.RSC, traits=None)
|
||||
self.api.baremetal.get_node.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
node, [{'path': '/instance_info/metalsmith_hostname',
|
||||
'op': 'add', 'value': node.id}])
|
||||
self.assertFalse(self.api.baremetal.patch_node.called)
|
||||
|
||||
def test_provided_nodes(self):
|
||||
nodes = [self._node(id=1), self._node(id=2)]
|
||||
@ -298,9 +350,7 @@ class TestReserveNode(Base):
|
||||
resource_class=self.RSC, traits=None)
|
||||
self.api.baremetal.get_node.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
node, [{'path': '/instance_info/metalsmith_hostname',
|
||||
'op': 'add', 'value': node.id}])
|
||||
self.assertFalse(self.api.baremetal.patch_node.called)
|
||||
|
||||
def test_nodes_filtered(self):
|
||||
nodes = [self._node(resource_class='banana'),
|
||||
@ -321,9 +371,7 @@ class TestReserveNode(Base):
|
||||
self.api.baremetal.get_node.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
node, [{'path': '/instance_info/metalsmith_hostname',
|
||||
'op': 'add', 'value': node.id},
|
||||
{'path': '/instance_info/capabilities',
|
||||
node, [{'path': '/instance_info/capabilities',
|
||||
'op': 'add', 'value': {'cat': 'meow'}}])
|
||||
|
||||
def test_nodes_filtered_by_conductor_group(self):
|
||||
@ -349,9 +397,7 @@ class TestReserveNode(Base):
|
||||
self.api.baremetal.get_node.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id)
|
||||
self.api.baremetal.patch_node.assert_called_once_with(
|
||||
node, [{'path': '/instance_info/metalsmith_hostname',
|
||||
'op': 'add', 'value': node.id},
|
||||
{'path': '/instance_info/capabilities',
|
||||
node, [{'path': '/instance_info/capabilities',
|
||||
'op': 'add', 'value': {'cat': 'meow'}}])
|
||||
|
||||
def test_provided_nodes_no_match(self):
|
||||
@ -377,14 +423,18 @@ class TestProvisionNode(Base):
|
||||
def setUp(self):
|
||||
super(TestProvisionNode, self).setUp()
|
||||
self.image = self.api.image.find_image.return_value
|
||||
self.node.instance_id = self.node.id
|
||||
self.node.instance_id = '123456'
|
||||
self.node.allocation_id = '123456'
|
||||
self.allocation = mock.Mock(spec=['id', 'node_id', 'name'],
|
||||
id='123456',
|
||||
node_id=self.node.id)
|
||||
self.allocation.name = 'example.com'
|
||||
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.HOSTNAME_FIELD: 'control-0'
|
||||
}
|
||||
self.extra = {
|
||||
'metalsmith_created_ports': [
|
||||
@ -398,6 +448,9 @@ class TestProvisionNode(Base):
|
||||
fixtures.MockPatchObject(instance_config.GenericConfig,
|
||||
'generate', autospec=True)
|
||||
).mock
|
||||
self.api.baremetal.get_node.side_effect = lambda _n: self.node
|
||||
self.api.baremetal.get_allocation.side_effect = (
|
||||
lambda _a: self.allocation)
|
||||
|
||||
def test_ok(self):
|
||||
inst = self.pr.provision_node(self.node, 'image',
|
||||
@ -413,7 +466,30 @@ class TestProvisionNode(Base):
|
||||
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.configdrive_mock.assert_called_once_with(mock.ANY, self.node)
|
||||
self.configdrive_mock.assert_called_once_with(mock.ANY, self.node,
|
||||
self.allocation.name)
|
||||
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_old_style_reservation(self):
|
||||
self.node.allocation_id = None
|
||||
self.node.instance_id = self.node.id
|
||||
inst = self.pr.provision_node(self.node, 'image',
|
||||
[{'network': 'network'}])
|
||||
|
||||
self.assertEqual(inst.uuid, self.node.id)
|
||||
self.assertEqual(inst.node, self.node)
|
||||
|
||||
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.configdrive_mock.assert_called_once_with(mock.ANY, self.node,
|
||||
self.node.name)
|
||||
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)
|
||||
@ -467,7 +543,8 @@ class TestProvisionNode(Base):
|
||||
self.assertEqual(inst.uuid, self.node.id)
|
||||
self.assertEqual(inst.node, self.node)
|
||||
|
||||
config.generate.assert_called_once_with(self.node)
|
||||
config.generate.assert_called_once_with(self.node,
|
||||
self.allocation.name)
|
||||
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(
|
||||
@ -481,19 +558,29 @@ class TestProvisionNode(Base):
|
||||
self.api.baremetal.wait_for_nodes_provision_state.called)
|
||||
self.assertFalse(self.api.network.delete_port.called)
|
||||
|
||||
@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
|
||||
def test_with_hostname_override(self):
|
||||
self.allocation.name = None
|
||||
self.api.baremetal.get_allocation.side_effect = [
|
||||
os_exc.ResourceNotFound(),
|
||||
self.allocation
|
||||
]
|
||||
|
||||
def _update(allocation, name):
|
||||
allocation.name = name
|
||||
return allocation
|
||||
|
||||
self.api.baremetal.update_allocation.side_effect = _update
|
||||
hostname = 'control-0.example.com'
|
||||
inst = self.pr.provision_node(self.node, 'image',
|
||||
[{'network': 'network'}],
|
||||
hostname=hostname)
|
||||
self.instance_info[_utils.HOSTNAME_FIELD] = hostname
|
||||
|
||||
self.assertEqual(inst.uuid, self.node.id)
|
||||
self.assertEqual(inst.node, self.node)
|
||||
self.assertIs(inst.allocation, self.allocation)
|
||||
|
||||
self.api.baremetal.update_allocation.assert_called_once_with(
|
||||
self.allocation, name=hostname)
|
||||
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(
|
||||
@ -501,7 +588,8 @@ class TestProvisionNode(Base):
|
||||
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.configdrive_mock.assert_called_once_with(mock.ANY, self.node)
|
||||
self.configdrive_mock.assert_called_once_with(mock.ANY, self.node,
|
||||
hostname)
|
||||
self.api.baremetal.set_node_provision_state.assert_called_once_with(
|
||||
self.node, 'active', config_drive=mock.ANY)
|
||||
self.assertFalse(
|
||||
@ -510,14 +598,15 @@ class TestProvisionNode(Base):
|
||||
|
||||
def test_existing_hostname(self):
|
||||
hostname = 'control-0.example.com'
|
||||
self.node.instance_info[_utils.HOSTNAME_FIELD] = hostname
|
||||
self.allocation.name = hostname
|
||||
inst = self.pr.provision_node(self.node, 'image',
|
||||
[{'network': 'network'}])
|
||||
self.instance_info[_utils.HOSTNAME_FIELD] = hostname
|
||||
|
||||
self.assertEqual(inst.uuid, self.node.id)
|
||||
self.assertEqual(inst.node, self.node)
|
||||
self.assertIs(inst.allocation, self.allocation)
|
||||
|
||||
self.assertFalse(self.api.baremetal.update_allocation.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(
|
||||
@ -525,7 +614,90 @@ class TestProvisionNode(Base):
|
||||
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.configdrive_mock.assert_called_once_with(mock.ANY, self.node)
|
||||
self.configdrive_mock.assert_called_once_with(mock.ANY, self.node,
|
||||
hostname)
|
||||
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_existing_hostname_match(self):
|
||||
hostname = 'control-0.example.com'
|
||||
self.allocation.name = hostname
|
||||
inst = self.pr.provision_node(self.node, 'image',
|
||||
[{'network': 'network'}],
|
||||
hostname=hostname)
|
||||
|
||||
self.assertEqual(inst.uuid, self.node.id)
|
||||
self.assertEqual(inst.node, self.node)
|
||||
self.assertIs(inst.allocation, self.allocation)
|
||||
|
||||
self.assertFalse(self.api.baremetal.update_allocation.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.configdrive_mock.assert_called_once_with(mock.ANY, self.node,
|
||||
hostname)
|
||||
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_existing_hostname_mismatch(self):
|
||||
self.api.baremetal.get_allocation.side_effect = [
|
||||
# No allocation with requested hostname
|
||||
os_exc.ResourceNotFound(),
|
||||
# Allocation associated with the node
|
||||
self.allocation
|
||||
]
|
||||
self.allocation.name = 'control-0.example.com'
|
||||
self.assertRaisesRegex(exceptions.InvalidNode,
|
||||
'does not match the expected hostname',
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'network': 'network'}],
|
||||
hostname='control-1.example.com')
|
||||
|
||||
self.api.baremetal.get_allocation.assert_has_calls([
|
||||
mock.call('control-1.example.com'),
|
||||
mock.call(self.node.allocation_id),
|
||||
])
|
||||
self.assertFalse(self.api.baremetal.create_allocation.called)
|
||||
self.assertFalse(self.api.baremetal.update_node.called)
|
||||
self.assertFalse(self.api.baremetal.set_node_provision_state.called)
|
||||
self.assertFalse(self.api.baremetal.delete_allocation.called)
|
||||
|
||||
def test_node_name_as_hostname(self):
|
||||
self.allocation.name = None
|
||||
|
||||
def _update(allocation, name):
|
||||
allocation.name = name
|
||||
return allocation
|
||||
|
||||
self.api.baremetal.update_allocation.side_effect = _update
|
||||
inst = self.pr.provision_node(self.node, 'image',
|
||||
[{'network': 'network'}])
|
||||
|
||||
self.assertEqual(inst.uuid, self.node.id)
|
||||
self.assertEqual(inst.node, self.node)
|
||||
self.assertIs(inst.allocation, self.allocation)
|
||||
|
||||
self.api.baremetal.update_allocation.assert_called_once_with(
|
||||
self.allocation, name=self.node.name)
|
||||
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.configdrive_mock.assert_called_once_with(mock.ANY, self.node,
|
||||
self.node.name)
|
||||
self.api.baremetal.set_node_provision_state.assert_called_once_with(
|
||||
self.node, 'active', config_drive=mock.ANY)
|
||||
self.assertFalse(
|
||||
@ -534,12 +706,19 @@ class TestProvisionNode(Base):
|
||||
|
||||
def test_name_not_valid_hostname(self):
|
||||
self.node.name = 'node_1'
|
||||
self.allocation.name = None
|
||||
|
||||
def _update(allocation, name):
|
||||
allocation.name = name
|
||||
return allocation
|
||||
|
||||
self.api.baremetal.update_allocation.side_effect = _update
|
||||
inst = self.pr.provision_node(self.node, 'image',
|
||||
[{'network': 'network'}])
|
||||
self.instance_info[_utils.HOSTNAME_FIELD] = '000'
|
||||
|
||||
self.assertEqual(inst.uuid, self.node.id)
|
||||
self.assertEqual(inst.node, self.node)
|
||||
self.assertIs(inst.allocation, self.allocation)
|
||||
|
||||
self.api.network.create_port.assert_called_once_with(
|
||||
network_id=self.api.network.find_network.return_value.id)
|
||||
@ -548,6 +727,8 @@ class TestProvisionNode(Base):
|
||||
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.configdrive_mock.assert_called_once_with(mock.ANY, self.node,
|
||||
self.node.id)
|
||||
self.api.baremetal.set_node_provision_state.assert_called_once_with(
|
||||
self.node, 'active', config_drive=mock.ANY)
|
||||
self.assertFalse(
|
||||
@ -556,15 +737,21 @@ class TestProvisionNode(Base):
|
||||
|
||||
def test_unreserved(self):
|
||||
self.node.instance_id = None
|
||||
self.node.allocation_id = None
|
||||
self.api.baremetal.get_node.return_value = self.node
|
||||
|
||||
self.pr.provision_node(self.node, 'image', [{'network': 'network'}])
|
||||
|
||||
self.api.baremetal.create_allocation.assert_called_once_with(
|
||||
name=None, candidate_nodes=[self.node.id],
|
||||
name=self.node.name, candidate_nodes=[self.node.id],
|
||||
resource_class=self.node.resource_class, traits=None)
|
||||
self.api.baremetal.get_node.assert_called_once_with(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id)
|
||||
self.api.baremetal.get_node.assert_has_calls([
|
||||
# After allocation
|
||||
mock.call(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id),
|
||||
# After deployment
|
||||
mock.call(self.node.id)
|
||||
])
|
||||
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(
|
||||
@ -578,6 +765,54 @@ class TestProvisionNode(Base):
|
||||
self.api.baremetal.wait_for_nodes_provision_state.called)
|
||||
self.assertFalse(self.api.network.delete_port.called)
|
||||
|
||||
def test_unreserved_with_hostname(self):
|
||||
self.node.instance_id = None
|
||||
self.node.allocation_id = None
|
||||
self.api.baremetal.get_node.return_value = self.node
|
||||
hostname = 'control-2.example.com'
|
||||
|
||||
self.pr.provision_node(self.node, 'image', [{'network': 'network'}],
|
||||
hostname=hostname)
|
||||
|
||||
self.api.baremetal.create_allocation.assert_called_once_with(
|
||||
name=hostname, candidate_nodes=[self.node.id],
|
||||
resource_class=self.node.resource_class, traits=None)
|
||||
self.api.baremetal.get_node.assert_has_calls([
|
||||
# After allocation
|
||||
mock.call(
|
||||
self.api.baremetal.wait_for_allocation.return_value.node_id),
|
||||
# After deployment
|
||||
mock.call(self.node.id)
|
||||
])
|
||||
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_unreserved_without_resource_class(self):
|
||||
self.node.instance_id = None
|
||||
self.node.allocation_id = None
|
||||
self.node.resource_class = None
|
||||
self.api.baremetal.get_node.return_value = self.node
|
||||
|
||||
self.assertRaisesRegex(exceptions.InvalidNode,
|
||||
'does not have a resource class',
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'network': 'network'}])
|
||||
|
||||
self.assertFalse(self.api.baremetal.create_allocation.called)
|
||||
self.assertFalse(self.api.baremetal.update_node.called)
|
||||
self.assertFalse(self.api.baremetal.set_node_provision_state.called)
|
||||
self.assertFalse(self.api.baremetal.delete_allocation.called)
|
||||
|
||||
def test_with_ports(self):
|
||||
port_ids = [self.api.network.find_port.return_value.id] * 2
|
||||
|
||||
@ -1013,11 +1248,13 @@ abcd image
|
||||
|
||||
def test_unreserve_dry_run(self):
|
||||
self.pr._dry_run = True
|
||||
self.node.allocation_id = None
|
||||
self.node.instance_id = None
|
||||
|
||||
self.pr.provision_node(self.node, 'image', [{'network': 'network'}])
|
||||
|
||||
self.assertFalse(self.api.network.create_port.called)
|
||||
self.assertFalse(self.api.baremetal.create_allocation.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)
|
||||
@ -1033,6 +1270,33 @@ abcd image
|
||||
'image', [{'network': 'n1'}, {'port': 'p1'}],
|
||||
wait=3600)
|
||||
|
||||
self.api.baremetal.update_node.assert_any_call(
|
||||
self.node, extra={}, instance_info={})
|
||||
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.api.network.create_port.return_value.id),
|
||||
mock.call(self.node, self.api.network.find_port.return_value.id)
|
||||
]
|
||||
self.api.baremetal.detach_vif_from_node.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
self.api.baremetal.delete_allocation.assert_called_once_with(
|
||||
self.allocation.id)
|
||||
|
||||
def test_deploy_failure_without_allocation(self):
|
||||
self.node.instance_id = None
|
||||
self.node.allocation_id = None
|
||||
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.baremetal.update_node.assert_any_call(
|
||||
self.node, extra={}, instance_info={}, instance_id=None)
|
||||
self.assertFalse(
|
||||
@ -1049,31 +1313,6 @@ abcd image
|
||||
calls, any_order=True)
|
||||
self.assertFalse(self.api.baremetal.delete_allocation.called)
|
||||
|
||||
def test_deploy_failure_with_allocation(self):
|
||||
self.node.allocation_id = 'id2'
|
||||
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.baremetal.update_node.assert_any_call(
|
||||
self.node, extra={}, instance_info={})
|
||||
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.api.network.create_port.return_value.id),
|
||||
mock.call(self.node, self.api.network.find_port.return_value.id)
|
||||
]
|
||||
self.api.baremetal.detach_vif_from_node.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
self.api.baremetal.delete_allocation.assert_called_once_with('id2')
|
||||
|
||||
def test_deploy_failure_no_cleanup(self):
|
||||
self.node.allocation_id = 'id2'
|
||||
self.api.baremetal.set_node_provision_state.side_effect = (
|
||||
@ -1096,8 +1335,10 @@ abcd image
|
||||
self.pr.provision_node, self.node,
|
||||
'image', [{'network': 'network'}], wait=3600)
|
||||
|
||||
self.api.baremetal.delete_allocation.assert_called_once_with(
|
||||
self.allocation.id)
|
||||
self.api.baremetal.update_node.assert_called_once_with(
|
||||
self.node, extra={}, instance_info={}, instance_id=None)
|
||||
self.node, extra={}, instance_info={})
|
||||
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)
|
||||
@ -1109,8 +1350,10 @@ abcd image
|
||||
self.pr.provision_node, self.node,
|
||||
'image', [{'network': 'network'}], wait=3600)
|
||||
|
||||
self.api.baremetal.delete_allocation.assert_called_once_with(
|
||||
self.allocation.id)
|
||||
self.api.baremetal.update_node.assert_called_once_with(
|
||||
self.node, extra={}, instance_info={}, instance_id=None)
|
||||
self.node, extra={}, instance_info={})
|
||||
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,
|
||||
@ -1152,7 +1395,7 @@ abcd image
|
||||
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.api.baremetal.detach_vif_from_node.side_effect = AssertionError()
|
||||
self._test_failure_during_deploy_failure()
|
||||
|
||||
def test_update_failed_after_deploy_failure(self):
|
||||
@ -1160,6 +1403,10 @@ abcd image
|
||||
AssertionError()]
|
||||
self._test_failure_during_deploy_failure()
|
||||
|
||||
def test_deallocation_failed_after_deploy_failure(self):
|
||||
self.api.baremetal.delete_allocation.side_effect = AssertionError()
|
||||
self._test_failure_during_deploy_failure()
|
||||
|
||||
def test_wait_failure(self):
|
||||
self.api.baremetal.wait_for_nodes_provision_state.side_effect = (
|
||||
RuntimeError('boom'))
|
||||
@ -1181,7 +1428,7 @@ abcd image
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'network': 'network'}])
|
||||
self.api.baremetal.update_node.assert_called_once_with(
|
||||
self.node, extra={}, instance_info={}, instance_id=None)
|
||||
self.node, extra={}, instance_info={})
|
||||
self.assertFalse(self.api.baremetal.set_node_provision_state.called)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
@ -1207,7 +1454,7 @@ abcd and-not-image-again
|
||||
self.assertFalse(self.api.image.find_image.called)
|
||||
mock_get.assert_called_once_with('https://host/checksums')
|
||||
self.api.baremetal.update_node.assert_called_once_with(
|
||||
self.node, extra={}, instance_info={}, instance_id=None)
|
||||
self.node, extra={}, instance_info={})
|
||||
self.assertFalse(self.api.baremetal.set_node_provision_state.called)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
@ -1233,7 +1480,7 @@ abcd and-not-image-again
|
||||
self.assertFalse(self.api.image.find_image.called)
|
||||
mock_get.assert_called_once_with('https://host/checksums')
|
||||
self.api.baremetal.update_node.assert_called_once_with(
|
||||
self.node, extra={}, instance_info={}, instance_id=None)
|
||||
self.node, extra={}, instance_info={})
|
||||
self.assertFalse(self.api.baremetal.set_node_provision_state.called)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
@ -1257,7 +1504,7 @@ abcd and-not-image-again
|
||||
self.assertFalse(self.api.image.find_image.called)
|
||||
mock_get.assert_called_once_with('https://host/checksums')
|
||||
self.api.baremetal.update_node.assert_called_once_with(
|
||||
self.node, extra={}, instance_info={}, instance_id=None)
|
||||
self.node, extra={}, instance_info={})
|
||||
self.assertFalse(self.api.baremetal.set_node_provision_state.called)
|
||||
|
||||
def test_invalid_network(self):
|
||||
@ -1267,7 +1514,7 @@ abcd and-not-image-again
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'network': 'network'}])
|
||||
self.api.baremetal.update_node.assert_called_once_with(
|
||||
self.node, extra={}, instance_info={}, instance_id=None)
|
||||
self.node, extra={}, instance_info={})
|
||||
self.assertFalse(self.api.network.create_port.called)
|
||||
self.assertFalse(self.api.baremetal.set_node_provision_state.called)
|
||||
|
||||
@ -1278,7 +1525,7 @@ abcd and-not-image-again
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'port': 'port1'}])
|
||||
self.api.baremetal.update_node.assert_called_once_with(
|
||||
self.node, extra={}, instance_info={}, instance_id=None)
|
||||
self.node, extra={}, instance_info={})
|
||||
self.assertFalse(self.api.network.create_port.called)
|
||||
self.assertFalse(self.api.baremetal.set_node_provision_state.called)
|
||||
|
||||
@ -1289,7 +1536,7 @@ abcd and-not-image-again
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'subnet': 'subnet'}])
|
||||
self.api.baremetal.update_node.assert_called_once_with(
|
||||
self.node, extra={}, instance_info={}, instance_id=None)
|
||||
self.node, extra={}, instance_info={})
|
||||
self.assertFalse(self.api.network.create_port.called)
|
||||
self.assertFalse(self.api.baremetal.set_node_provision_state.called)
|
||||
|
||||
@ -1301,7 +1548,7 @@ abcd and-not-image-again
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'subnet': 'subnet'}])
|
||||
self.api.baremetal.update_node.assert_called_once_with(
|
||||
self.node, extra={}, instance_info={}, instance_id=None)
|
||||
self.node, extra={}, instance_info={})
|
||||
self.assertFalse(self.api.network.create_port.called)
|
||||
self.assertFalse(self.api.baremetal.set_node_provision_state.called)
|
||||
|
||||
@ -1378,21 +1625,28 @@ abcd and-not-image-again
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'port': 'port1'}],
|
||||
hostname='n_1')
|
||||
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)
|
||||
|
||||
@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'])
|
||||
def test_duplicate_hostname(self):
|
||||
allocation = mock.Mock(spec=['id', 'name', 'node_id'],
|
||||
node_id='another node')
|
||||
self.api.baremetal.get_allocation.side_effect = [allocation]
|
||||
self.assertRaisesRegex(ValueError, 'already uses hostname host',
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'port': 'port1'}],
|
||||
hostname='host')
|
||||
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_old_style_reservation_with_override(self):
|
||||
self.node.allocation_id = None
|
||||
self.node.instance_id = self.node.id
|
||||
self.assertRaisesRegex(exceptions.InvalidNode,
|
||||
'does not use allocations',
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'port': 'port1'}],
|
||||
hostname='host')
|
||||
self.assertFalse(self.api.network.create_port.called)
|
||||
self.assertFalse(self.api.baremetal.set_node_provision_state.called)
|
||||
|
||||
@ -1407,6 +1661,7 @@ abcd and-not-image-again
|
||||
|
||||
def test_node_with_external_instance_id(self):
|
||||
self.node.instance_id = 'nova'
|
||||
self.node.allocation_id = None
|
||||
self.assertRaisesRegex(exceptions.InvalidNode,
|
||||
'reserved by instance nova',
|
||||
self.pr.provision_node,
|
||||
@ -1551,39 +1806,56 @@ class TestUnprovisionNode(Base):
|
||||
self.assertFalse(self.api.baremetal.update_node.called)
|
||||
|
||||
|
||||
class TestShowInstance(Base):
|
||||
class TestShowInstance(testtools.TestCase):
|
||||
def setUp(self):
|
||||
super(TestShowInstance, self).setUp()
|
||||
self.node.provision_state = 'active'
|
||||
self.pr = _provisioner.Provisioner(mock.Mock())
|
||||
self.api = mock.Mock(spec=['baremetal'])
|
||||
self.pr.connection = self.api
|
||||
|
||||
self.node = mock.Mock(spec=NODE_FIELDS + ['to_dict'],
|
||||
id='000', instance_id=None,
|
||||
properties={'local_gb': 100},
|
||||
instance_info={},
|
||||
is_maintenance=False, extra={},
|
||||
provision_state='active',
|
||||
allocation_id=None)
|
||||
self.node.name = 'control-0'
|
||||
self.api.baremetal.get_node.return_value = self.node
|
||||
|
||||
def test_show_instance(self):
|
||||
self.mock_get_node.side_effect = lambda n, *a, **kw: self.node
|
||||
self.api.baremetal.get_allocation.side_effect = (
|
||||
os_exc.ResourceNotFound())
|
||||
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.id)
|
||||
self.api.baremetal.get_node.assert_called_once_with('id1')
|
||||
|
||||
def test_show_instance_with_allocation(self):
|
||||
self.node.allocation_id = 'id2'
|
||||
self.mock_get_node.side_effect = lambda n, *a, **kw: self.node
|
||||
self.api.baremetal.get_allocation.return_value.node_id = '1234'
|
||||
inst = self.pr.show_instance('id1')
|
||||
self.mock_get_node.assert_called_once_with(self.pr, 'id1',
|
||||
accept_hostname=True)
|
||||
self.api.baremetal.get_allocation.assert_called_once_with('id2')
|
||||
self.api.baremetal.get_allocation.assert_called_once_with('id1')
|
||||
self.assertIsInstance(inst, _instance.Instance)
|
||||
self.assertIs(inst.allocation,
|
||||
self.api.baremetal.get_allocation.return_value)
|
||||
self.assertIs(inst.node, self.node)
|
||||
self.assertIs(inst.uuid, self.node.id)
|
||||
self.api.baremetal.get_node.assert_called_once_with('1234')
|
||||
|
||||
def test_show_instances(self):
|
||||
self.mock_get_node.side_effect = [self.node, self.node]
|
||||
result = self.pr.show_instances(['1', '2'])
|
||||
self.mock_get_node.assert_has_calls([
|
||||
mock.call(self.pr, '1', accept_hostname=True),
|
||||
mock.call(self.pr, '2', accept_hostname=True)
|
||||
self.api.baremetal.get_allocation.side_effect = [
|
||||
os_exc.ResourceNotFound(),
|
||||
mock.Mock(node_id='4321'),
|
||||
]
|
||||
result = self.pr.show_instances(['inst-1', 'inst-2'])
|
||||
self.api.baremetal.get_node.assert_has_calls([
|
||||
mock.call('inst-1'),
|
||||
mock.call('4321'),
|
||||
])
|
||||
self.api.baremetal.get_allocation.assert_has_calls([
|
||||
mock.call('inst-1'),
|
||||
mock.call('inst-2'),
|
||||
])
|
||||
self.assertIsInstance(result, list)
|
||||
for inst in result:
|
||||
@ -1591,6 +1863,14 @@ class TestShowInstance(Base):
|
||||
self.assertIs(result[0].node, self.node)
|
||||
self.assertIs(result[0].uuid, self.node.id)
|
||||
|
||||
def test_show_instance_invalid_state(self):
|
||||
self.node.provision_state = 'manageable'
|
||||
self.api.baremetal.get_allocation.side_effect = (
|
||||
os_exc.ResourceNotFound())
|
||||
self.assertRaises(exceptions.InvalidInstance,
|
||||
self.pr.show_instance, 'id1')
|
||||
self.api.baremetal.get_node.assert_called_once_with('id1')
|
||||
|
||||
|
||||
class TestWaitForProvisioning(Base):
|
||||
|
||||
@ -1617,7 +1897,8 @@ class TestListInstances(Base):
|
||||
|
||||
def test_list(self):
|
||||
instances = self.pr.list_instances()
|
||||
self.assertTrue(isinstance(i, _instance.Instance) for i in instances)
|
||||
self.assertTrue(all(isinstance(i, _instance.Instance)
|
||||
for i in instances))
|
||||
self.assertEqual(self.nodes[:6], [i.node for i in instances])
|
||||
self.assertEqual([self.api.baremetal.get_allocation.return_value]
|
||||
+ [None] * 5,
|
||||
|
@ -13,11 +13,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from metalsmith import _utils
|
||||
from metalsmith import exceptions
|
||||
|
||||
|
||||
class TestIsHostnameSafe(testtools.TestCase):
|
||||
@ -68,74 +66,3 @@ class TestIsHostnameSafe(testtools.TestCase):
|
||||
# Need to ensure a binary response for success or fail
|
||||
self.assertIsNotNone(_utils.is_hostname_safe('spam'))
|
||||
self.assertIsNotNone(_utils.is_hostname_safe('-spam'))
|
||||
|
||||
|
||||
class TestGetNodeMixin(testtools.TestCase):
|
||||
def setUp(self):
|
||||
super(TestGetNodeMixin, self).setUp()
|
||||
self.mixin = _utils.GetNodeMixin()
|
||||
self.mixin.connection = mock.Mock(spec=['baremetal'])
|
||||
self.api = self.mixin.connection.baremetal
|
||||
|
||||
def test__get_node_with_node(self):
|
||||
node = mock.Mock(spec=['id', 'name'])
|
||||
result = self.mixin._get_node(node)
|
||||
self.assertIs(result, node)
|
||||
self.assertFalse(self.api.get_node.called)
|
||||
|
||||
def test__get_node_with_node_refresh(self):
|
||||
node = mock.Mock(spec=['id', 'name'])
|
||||
result = self.mixin._get_node(node, refresh=True)
|
||||
self.assertIs(result, self.api.get_node.return_value)
|
||||
self.api.get_node.assert_called_once_with(node.id)
|
||||
|
||||
def test__get_node_with_instance(self):
|
||||
node = mock.Mock(spec=['uuid', 'node'])
|
||||
result = self.mixin._get_node(node)
|
||||
self.assertIs(result, node.node)
|
||||
self.assertFalse(self.api.get_node.called)
|
||||
|
||||
def test__get_node_with_instance_refresh(self):
|
||||
node = mock.Mock(spec=['uuid', 'node'])
|
||||
result = self.mixin._get_node(node, refresh=True)
|
||||
self.assertIs(result, self.api.get_node.return_value)
|
||||
self.api.get_node.assert_called_once_with(node.node.id)
|
||||
|
||||
def test__get_node_with_string(self):
|
||||
result = self.mixin._get_node('node')
|
||||
self.assertIs(result, self.api.get_node.return_value)
|
||||
self.api.get_node.assert_called_once_with('node')
|
||||
|
||||
def test__get_node_with_string_hostname_allowed(self):
|
||||
nodes = [
|
||||
mock.Mock(instance_info={'metalsmith_hostname': host})
|
||||
for host in ['host1', 'host2', 'host3']
|
||||
]
|
||||
self.api.nodes.return_value = nodes
|
||||
|
||||
result = self.mixin._get_node('host2', accept_hostname=True)
|
||||
self.assertIs(result, self.api.get_node.return_value)
|
||||
self.api.get_node.assert_called_once_with(nodes[1].id)
|
||||
|
||||
def test__get_node_with_string_hostname_allowed_fallback(self):
|
||||
nodes = [
|
||||
mock.Mock(instance_info={'metalsmith_hostname': host})
|
||||
for host in ['host1', 'host2', 'host3']
|
||||
]
|
||||
self.api.nodes.return_value = nodes
|
||||
|
||||
result = self.mixin._get_node('node', accept_hostname=True)
|
||||
self.assertIs(result, self.api.get_node.return_value)
|
||||
self.api.get_node.assert_called_once_with('node')
|
||||
|
||||
def test__get_node_with_string_hostname_not_unique(self):
|
||||
nodes = [
|
||||
mock.Mock(instance_info={'metalsmith_hostname': host})
|
||||
for host in ['host1', 'host2', 'host2']
|
||||
]
|
||||
self.api.nodes.return_value = nodes
|
||||
|
||||
self.assertRaises(exceptions.Error,
|
||||
self.mixin._get_node,
|
||||
'host2', accept_hostname=True)
|
||||
self.assertFalse(self.api.get_node.called)
|
||||
|
11
releasenotes/notes/allocation-hostname-f148e8adf0aee89a.yaml
Normal file
11
releasenotes/notes/allocation-hostname-f148e8adf0aee89a.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
An allocation name is now used for hostname instead of a custom ``extra``
|
||||
field. Previously deployed instances will no longer be recognized, use
|
||||
the allocation backfilling to make them recognized again.
|
||||
deprecations:
|
||||
- |
|
||||
The exception classes ``DeploymentFailure``, ``TraitsNotFound`` and
|
||||
``NoNodesReserved`` are deprecated and no longer used after transitioning
|
||||
to the allocation API.
|
@ -2,7 +2,7 @@
|
||||
# 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.28.0 # Apache-2.0
|
||||
openstacksdk>=0.29.0 # Apache-2.0
|
||||
requests>=2.18.4 # Apache-2.0
|
||||
six>=1.10.0 # MIT
|
||||
enum34>=1.0.4;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
||||
|
Loading…
x
Reference in New Issue
Block a user