# 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 enum import warnings from metalsmith import _utils _PROGRESS_STATES = frozenset(['deploying', 'wait call-back', 'deploy complete']) _ACTIVE_STATES = frozenset(['active']) _ERROR_STATES = frozenset(['error', 'deploy failed']) _RESERVED_STATES = frozenset(['available']) class InstanceState(enum.Enum): """A state of an instance.""" DEPLOYING = 'deploying' """Provisioning is in progress. This includes the case when a node is still in the ``available`` state, but already has an instance associated with it. """ ACTIVE = 'active' """The instance is provisioned.""" MAINTENANCE = 'maintenance' """The instance is provisioned but is in the maintenance mode.""" ERROR = 'error' """The instance has a failure.""" UNKNOWN = 'unknown' """The node is in an unexpected state. It can be unprovisioned or modified by a third party. """ @property def is_deployed(self): """Whether the state designates a finished deployment.""" return self in _DEPLOYED_STATES @property def is_healthy(self): """Whether the state is considered healthy.""" return self in _HEALTHY_STATES # TODO(dtantsur): remove before 1.0 def __eq__(self, other): if isinstance(other, str): warnings.warn("Comparing states with strings is deprecated, " "use InstanceState instead", DeprecationWarning) return self.value == other else: return super(InstanceState, self).__eq__(other) def __ne__(self, other): eq = self.__eq__(other) return NotImplemented if eq is NotImplemented else not eq def __hash__(self): return hash(self.value) _HEALTHY_STATES = frozenset([InstanceState.ACTIVE, InstanceState.DEPLOYING]) _DEPLOYED_STATES = frozenset([InstanceState.ACTIVE, InstanceState.MAINTENANCE]) class Instance(object): """Instance status in metalsmith.""" 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(_utils.GetNodeMixin.HOSTNAME_FIELD) def ip_addresses(self): """Returns IP addresses for this instance. :return: dict mapping network name or ID to a list of IP addresses. """ result = {} for nic in self.nics(): net = getattr(nic.network, 'name', None) or nic.network.id result.setdefault(net, []).extend( ip['ip_address'] for ip in nic.fixed_ips if ip.get('ip_address') ) return result @property def is_deployed(self): """Whether the node is deployed.""" return self.state.is_deployed @property def is_healthy(self): """Whether the instance is not at fault or maintenance.""" return self.state.is_healthy and not self._node.is_maintenance def nics(self): """List NICs for this instance. :return: List of `Port` objects with additional ``network`` fields with full representations of their networks. """ result = [] vifs = self._connection.baremetal.list_node_vifs(self.node) for vif in vifs: port = self._connection.network.get_port(vif) port.network = self._connection.network.get_network( port.network_id) result.append(port) return result @property def node(self): """Underlying `Node` object.""" return self._node @property def state(self): """Instance state, one of :py:class:`InstanceState`.""" prov_state = self._node.provision_state if prov_state in _PROGRESS_STATES: return InstanceState.DEPLOYING # NOTE(dtantsur): include available since there is a period of time # between claiming the instance and starting the actual provisioning. elif prov_state in _RESERVED_STATES and self._node.instance_id: return InstanceState.DEPLOYING elif prov_state in _ERROR_STATES: return InstanceState.ERROR elif prov_state in _ACTIVE_STATES: if self._node.is_maintenance: return InstanceState.MAINTENANCE else: return InstanceState.ACTIVE else: return InstanceState.UNKNOWN def to_dict(self): """Convert instance to a dict.""" return { 'hostname': self.hostname, 'ip_addresses': self.ip_addresses(), 'node': self._node.to_dict(), 'state': self.state.value, 'uuid': self._uuid, } @property def uuid(self): """Instance UUID (the same as `Node` UUID for metalsmith).""" return self._uuid