You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
635 lines
27 KiB
635 lines
27 KiB
# |
|
# 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 itertools |
|
|
|
import eventlet |
|
from oslo_log import log as logging |
|
from oslo_serialization import jsonutils |
|
from oslo_utils import netutils |
|
import tenacity |
|
|
|
from heat.common import exception |
|
from heat.common.i18n import _ |
|
from heat.engine import resource |
|
from heat.engine.resources.openstack.neutron import port as neutron_port |
|
|
|
LOG = logging.getLogger(__name__) |
|
|
|
|
|
class ServerNetworkMixin(object): |
|
|
|
def _validate_network(self, network): |
|
net_id = network.get(self.NETWORK_ID) |
|
port = network.get(self.NETWORK_PORT) |
|
subnet = network.get(self.NETWORK_SUBNET) |
|
fixed_ip = network.get(self.NETWORK_FIXED_IP) |
|
floating_ip = network.get(self.NETWORK_FLOATING_IP) |
|
str_network = network.get(self.ALLOCATE_NETWORK) |
|
|
|
if (net_id is None and |
|
port is None and |
|
subnet is None and |
|
not str_network): |
|
msg = _('One of the properties "%(id)s", "%(port_id)s", ' |
|
'"%(str_network)s" or "%(subnet)s" should be set for the ' |
|
'specified network of server "%(server)s".' |
|
'') % dict(id=self.NETWORK_ID, |
|
port_id=self.NETWORK_PORT, |
|
subnet=self.NETWORK_SUBNET, |
|
str_network=self.ALLOCATE_NETWORK, |
|
server=self.name) |
|
raise exception.StackValidationFailed(message=msg) |
|
# can not specify str_network with other keys of networks |
|
# at the same time |
|
has_value_keys = [k for k, v in network.items() if v is not None] |
|
if str_network and len(has_value_keys) != 1: |
|
msg = _('Can not specify "%s" with other keys of networks ' |
|
'at the same time.') % self.ALLOCATE_NETWORK |
|
raise exception.StackValidationFailed(message=msg) |
|
|
|
# Nova doesn't allow specify ip and port at the same time |
|
if fixed_ip and port is not None: |
|
raise exception.ResourcePropertyConflict( |
|
"/".join([self.NETWORKS, self.NETWORK_FIXED_IP]), |
|
"/".join([self.NETWORKS, self.NETWORK_PORT])) |
|
|
|
# if user only specifies network and floating ip, floating ip |
|
# can't be associated as the the neutron port isn't created/managed |
|
# by heat |
|
if floating_ip is not None: |
|
if net_id is not None and port is None and subnet is None: |
|
msg = _('Property "%(fip)s" is not supported if only ' |
|
'"%(net)s" is specified, because the corresponding ' |
|
'port can not be retrieved.' |
|
) % dict(fip=self.NETWORK_FLOATING_IP, |
|
net=self.NETWORK_ID) |
|
raise exception.StackValidationFailed(message=msg) |
|
|
|
def _validate_belonging_subnet_to_net(self, network): |
|
if network.get(self.NETWORK_PORT) is None: |
|
net = self._get_network_id(network) |
|
# check if there are subnet and network both specified that |
|
# subnet belongs to specified network |
|
subnet = network.get(self.NETWORK_SUBNET) |
|
if (subnet is not None and net is not None): |
|
subnet_net = self.client_plugin( |
|
'neutron').network_id_from_subnet_id(subnet) |
|
if subnet_net != net: |
|
msg = _('Specified subnet %(subnet)s does not belongs to ' |
|
'network %(network)s.') % { |
|
'subnet': subnet, |
|
'network': net} |
|
raise exception.StackValidationFailed(message=msg) |
|
|
|
def _create_internal_port(self, net_data, net_number, |
|
security_groups=None): |
|
name = _('%(server)s-port-%(number)s') % {'server': self.name, |
|
'number': net_number} |
|
|
|
kwargs = self._prepare_internal_port_kwargs(net_data, security_groups) |
|
kwargs['name'] = name |
|
|
|
port = self.client('neutron').create_port({'port': kwargs})['port'] |
|
|
|
# Store ids (used for floating_ip association, updating, etc.) |
|
# in resource's data. |
|
self._data_update_ports(port['id'], 'add') |
|
|
|
return port['id'] |
|
|
|
def _prepare_internal_port_kwargs(self, net_data, security_groups=None): |
|
kwargs = {'network_id': self._get_network_id(net_data)} |
|
fixed_ip = net_data.get(self.NETWORK_FIXED_IP) |
|
subnet = net_data.get(self.NETWORK_SUBNET) |
|
body = {} |
|
if fixed_ip: |
|
body['ip_address'] = fixed_ip |
|
if subnet: |
|
body['subnet_id'] = subnet |
|
# we should add fixed_ips only if subnet or ip were provided |
|
if body: |
|
kwargs.update({'fixed_ips': [body]}) |
|
|
|
if security_groups: |
|
sec_uuids = self.client_plugin( |
|
'neutron').get_secgroup_uuids(security_groups) |
|
kwargs['security_groups'] = sec_uuids |
|
|
|
extra_props = net_data.get(self.NETWORK_PORT_EXTRA) |
|
if extra_props is not None: |
|
specs = extra_props.pop(neutron_port.Port.VALUE_SPECS) |
|
if specs: |
|
kwargs.update(specs) |
|
port_extra_keys = list(neutron_port.Port.EXTRA_PROPERTIES) |
|
port_extra_keys.remove(neutron_port.Port.ALLOWED_ADDRESS_PAIRS) |
|
for key in port_extra_keys: |
|
if extra_props.get(key) is not None: |
|
kwargs[key] = extra_props.get(key) |
|
|
|
allowed_address_pairs = extra_props.get( |
|
neutron_port.Port.ALLOWED_ADDRESS_PAIRS) |
|
if allowed_address_pairs is not None: |
|
for pair in allowed_address_pairs: |
|
if (neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS |
|
in pair and pair.get( |
|
neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS) |
|
is None): |
|
del pair[ |
|
neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS] |
|
port_address_pairs = neutron_port.Port.ALLOWED_ADDRESS_PAIRS |
|
kwargs[port_address_pairs] = allowed_address_pairs |
|
|
|
return kwargs |
|
|
|
def _delete_internal_port(self, port_id): |
|
"""Delete physical port by id.""" |
|
with self.client_plugin('neutron').ignore_not_found: |
|
self.client('neutron').delete_port(port_id) |
|
|
|
self._data_update_ports(port_id, 'delete') |
|
|
|
def _delete_internal_ports(self): |
|
for port_data in self._data_get_ports(): |
|
self._delete_internal_port(port_data['id']) |
|
|
|
self.data_delete('internal_ports') |
|
|
|
def _data_update_ports(self, port_id, action, port_type='internal_ports'): |
|
data = self._data_get_ports(port_type) |
|
|
|
if action == 'add': |
|
data.append({'id': port_id}) |
|
elif action == 'delete': |
|
for port in data: |
|
if port_id == port['id']: |
|
data.remove(port) |
|
break |
|
|
|
self.data_set(port_type, jsonutils.dumps(data)) |
|
|
|
def _data_get_ports(self, port_type='internal_ports'): |
|
data = self.data().get(port_type) |
|
return jsonutils.loads(data) if data else [] |
|
|
|
def store_external_ports(self): |
|
"""Store in resource's data IDs of ports created by nova for server. |
|
|
|
If no port property is specified and no internal port has been created, |
|
nova client takes no port-id and calls port creating into server |
|
creating. We need to store information about that ports, so store |
|
their IDs to data with key `external_ports`. |
|
""" |
|
# check if os-attach-interfaces extension is available on this cloud. |
|
# If it's not, then novaclient's interface_list method cannot be used |
|
# to get the list of interfaces. |
|
if not self.client_plugin().has_extension('os-attach-interfaces'): |
|
return |
|
|
|
server = self.client().servers.get(self.resource_id) |
|
ifaces = server.interface_list() |
|
external_port_ids = set(iface.port_id for iface in ifaces) |
|
# need to make sure external_ports data doesn't store ids of non-exist |
|
# ports. Delete such port_id if it's needed. |
|
data_external_port_ids = set( |
|
port['id'] for port in self._data_get_ports('external_ports')) |
|
for port_id in data_external_port_ids - external_port_ids: |
|
self._data_update_ports(port_id, 'delete', |
|
port_type='external_ports') |
|
|
|
internal_port_ids = set(port['id'] for port in self._data_get_ports()) |
|
# add ids of new external ports which not contains in external_ports |
|
# data yet. Also, exclude ids of internal ports. |
|
new_ports = ((external_port_ids - internal_port_ids) - |
|
data_external_port_ids) |
|
for port_id in new_ports: |
|
self._data_update_ports(port_id, 'add', port_type='external_ports') |
|
|
|
def _build_nics(self, networks, security_groups=None): |
|
if not networks: |
|
return None |
|
|
|
str_network = self._str_network(networks) |
|
if str_network: |
|
return str_network |
|
|
|
nics = [] |
|
|
|
for idx, net in enumerate(networks): |
|
self._validate_belonging_subnet_to_net(net) |
|
nic_info = {'net-id': self._get_network_id(net)} |
|
if net.get(self.NETWORK_PORT): |
|
nic_info['port-id'] = net[self.NETWORK_PORT] |
|
elif net.get(self.NETWORK_SUBNET): |
|
nic_info['port-id'] = self._create_internal_port( |
|
net, idx, security_groups) |
|
|
|
# if nic_info including 'port-id', do not set ip for nic |
|
if not nic_info.get('port-id'): |
|
if net.get(self.NETWORK_FIXED_IP): |
|
ip = net[self.NETWORK_FIXED_IP] |
|
if netutils.is_valid_ipv6(ip): |
|
nic_info['v6-fixed-ip'] = ip |
|
else: |
|
nic_info['v4-fixed-ip'] = ip |
|
|
|
if net.get(self.NETWORK_FLOATING_IP) and nic_info.get('port-id'): |
|
floating_ip_data = {'port_id': nic_info['port-id']} |
|
if net.get(self.NETWORK_FIXED_IP): |
|
floating_ip_data.update( |
|
{'fixed_ip_address': |
|
net.get(self.NETWORK_FIXED_IP)}) |
|
self._floating_ip_neutron_associate( |
|
net.get(self.NETWORK_FLOATING_IP), floating_ip_data) |
|
|
|
if net.get(self.NIC_TAG): |
|
nic_info[self.NIC_TAG] = net.get(self.NIC_TAG) |
|
|
|
nics.append(nic_info) |
|
return nics |
|
|
|
def _floating_ip_neutron_associate(self, floating_ip, floating_ip_data): |
|
self.client('neutron').update_floatingip( |
|
floating_ip, {'floatingip': floating_ip_data}) |
|
|
|
def _floating_ip_disassociate(self, floating_ip): |
|
with self.client_plugin('neutron').ignore_not_found: |
|
self.client('neutron').update_floatingip( |
|
floating_ip, {'floatingip': {'port_id': None}}) |
|
|
|
def _find_best_match(self, existing_interfaces, specified_net): |
|
specified_net_items = set(specified_net.items()) |
|
if specified_net.get(self.NETWORK_PORT) is not None: |
|
for iface in existing_interfaces: |
|
if (iface[self.NETWORK_PORT] == |
|
specified_net[self.NETWORK_PORT] and |
|
specified_net_items.issubset(set(iface.items()))): |
|
return iface |
|
elif specified_net.get(self.NETWORK_FIXED_IP) is not None: |
|
for iface in existing_interfaces: |
|
if (iface[self.NETWORK_FIXED_IP] == |
|
specified_net[self.NETWORK_FIXED_IP] and |
|
specified_net_items.issubset(set(iface.items()))): |
|
return iface |
|
else: |
|
# Best subset intersection |
|
best, matches, num = None, 0, 0 |
|
for iface in existing_interfaces: |
|
iface_items = set(iface.items()) |
|
if specified_net_items.issubset(iface_items): |
|
num = len(specified_net_items.intersection(iface_items)) |
|
if num > matches: |
|
best, matches = iface, num |
|
return best |
|
|
|
def _exclude_not_updated_networks(self, old_nets, new_nets, interfaces): |
|
not_updated_nets = [] |
|
|
|
# Update old_nets to match interfaces |
|
self.update_networks_matching_iface_port(old_nets, interfaces) |
|
# make networks similar by adding None values for not used keys |
|
for key in self._NETWORK_KEYS: |
|
# if _net.get(key) is '', convert to None |
|
for _net in itertools.chain(new_nets, old_nets): |
|
_net[key] = _net.get(key) or None |
|
|
|
for new_net in list(new_nets): |
|
new_net_reduced = {k: v for k, v in new_net.items() |
|
if k not in self._IFACE_MANAGED_KEYS or |
|
v is not None} |
|
match = self._find_best_match(old_nets, new_net_reduced) |
|
if match is not None: |
|
not_updated_nets.append(match) |
|
new_nets.remove(new_net) |
|
old_nets.remove(match) |
|
|
|
return not_updated_nets |
|
|
|
def _get_network_id(self, net): |
|
net_id = net.get(self.NETWORK_ID) or None |
|
subnet = net.get(self.NETWORK_SUBNET) or None |
|
if not net_id and subnet: |
|
net_id = self.client_plugin( |
|
'neutron').network_id_from_subnet_id(subnet) |
|
return net_id |
|
|
|
def update_networks_matching_iface_port(self, old_nets, interfaces): |
|
|
|
def get_iface_props(iface): |
|
ipaddr = None |
|
subnet = None |
|
if len(iface.fixed_ips) > 0: |
|
ipaddr = iface.fixed_ips[0]['ip_address'] |
|
subnet = iface.fixed_ips[0]['subnet_id'] |
|
return {self.NETWORK_PORT: iface.port_id, |
|
self.NETWORK_ID: iface.net_id, |
|
self.NETWORK_FIXED_IP: ipaddr, |
|
self.NETWORK_SUBNET: subnet} |
|
|
|
interfaces_net_props = [get_iface_props(iface) for iface in interfaces] |
|
for old_net in old_nets: |
|
if old_net[self.NETWORK_PORT] is None: |
|
old_net[self.NETWORK_ID] = self._get_network_id(old_net) |
|
old_net_reduced = {k: v for k, v in old_net.items() |
|
if k in self._IFACE_MANAGED_KEYS and |
|
v is not None} |
|
match = self._find_best_match(interfaces_net_props, |
|
old_net_reduced) |
|
if match is not None: |
|
old_net.update(match) |
|
interfaces_net_props.remove(match) |
|
|
|
def _get_available_networks(self): |
|
# first we get the private networks owned by the tenant |
|
search_opts = {'tenant_id': self.context.tenant_id, 'shared': False, |
|
'admin_state_up': True, } |
|
nc = self.client('neutron') |
|
nets = nc.list_networks(**search_opts).get('networks', []) |
|
# second we get the public shared networks |
|
search_opts = {'shared': True} |
|
nets += nc.list_networks(**search_opts).get('networks', []) |
|
|
|
ids = [net['id'] for net in nets] |
|
|
|
return ids |
|
|
|
def _auto_allocate_network(self): |
|
topology = self.client('neutron').get_auto_allocated_topology( |
|
self.context.tenant_id)['auto_allocated_topology'] |
|
|
|
return topology['id'] |
|
|
|
def _calculate_using_str_network(self, ifaces, str_net, |
|
security_groups=None): |
|
add_nets = [] |
|
remove_ports = [iface.port_id for iface in ifaces or []] |
|
if str_net == self.NETWORK_AUTO: |
|
nets = self._get_available_networks() |
|
if not nets: |
|
nets = [self._auto_allocate_network()] |
|
if len(nets) > 1: |
|
msg = 'Multiple possible networks found.' |
|
raise exception.UnableToAutoAllocateNetwork(message=msg) |
|
handle_args = {'port_id': None, 'net_id': nets[0], 'fip': None} |
|
if security_groups: |
|
sg_ids = self.client_plugin( |
|
'neutron').get_secgroup_uuids(security_groups) |
|
handle_args['security_groups'] = sg_ids |
|
add_nets.append(handle_args) |
|
return remove_ports, add_nets |
|
|
|
def _calculate_using_list_networks(self, old_nets, new_nets, ifaces, |
|
security_groups): |
|
remove_ports = [] |
|
add_nets = [] |
|
# if update networks between None and empty, no need to |
|
# detach and attach, the server got first free port already. |
|
if not new_nets and not old_nets: |
|
return remove_ports, add_nets |
|
|
|
new_nets = new_nets or [] |
|
old_nets = old_nets or [] |
|
remove_ports, not_updated_nets = self._calculate_remove_ports( |
|
old_nets, new_nets, ifaces) |
|
add_nets = self._calculate_add_nets(new_nets, not_updated_nets, |
|
security_groups) |
|
|
|
return remove_ports, add_nets |
|
|
|
def _calculate_remove_ports(self, old_nets, new_nets, ifaces): |
|
remove_ports = [] |
|
not_updated_nets = [] |
|
# if old nets is empty, it means that the server got first |
|
# free port. so we should detach this interface. |
|
if not old_nets: |
|
for iface in ifaces: |
|
remove_ports.append(iface.port_id) |
|
|
|
# if we have any information in networks field, we should: |
|
# 1. find similar networks, if they exist |
|
# 2. remove these networks from new_nets and old_nets |
|
# lists |
|
# 3. detach unmatched networks, which were present in old_nets |
|
# 4. attach unmatched networks, which were present in new_nets |
|
else: |
|
# if old net is string net, remove the interfaces |
|
if self._str_network(old_nets): |
|
remove_ports = [iface.port_id for iface in ifaces or []] |
|
else: |
|
# remove not updated networks from old and new networks lists, |
|
# also get list these networks |
|
not_updated_nets = self._exclude_not_updated_networks( |
|
old_nets, new_nets, ifaces) |
|
|
|
# according to nova interface-detach command detached port |
|
# will be deleted |
|
inter_port_data = self._data_get_ports() |
|
inter_port_ids = [p['id'] for p in inter_port_data] |
|
for net in old_nets: |
|
port_id = net.get(self.NETWORK_PORT) |
|
# we can't match the port for some user case, like: |
|
# the internal port was detached in nova first, then |
|
# user update template to detach this nic. The internal |
|
# port will remains till we delete the server resource. |
|
if port_id: |
|
remove_ports.append(port_id) |
|
if port_id in inter_port_ids: |
|
# if we have internal port with such id, remove it |
|
# instantly. |
|
self._delete_internal_port(port_id) |
|
if net.get(self.NETWORK_FLOATING_IP): |
|
self._floating_ip_disassociate( |
|
net.get(self.NETWORK_FLOATING_IP)) |
|
return remove_ports, not_updated_nets |
|
|
|
def _calculate_add_nets(self, new_nets, not_updated_nets, |
|
security_groups): |
|
add_nets = [] |
|
|
|
# if new_nets is empty (including the non_updated_nets), we should |
|
# attach first free port, similar to the behavior for instance |
|
# creation |
|
if not new_nets and not not_updated_nets: |
|
handler_kwargs = {'port_id': None, 'net_id': None, 'fip': None} |
|
if security_groups: |
|
sec_uuids = self.client_plugin( |
|
'neutron').get_secgroup_uuids(security_groups) |
|
handler_kwargs['security_groups'] = sec_uuids |
|
add_nets.append(handler_kwargs) |
|
else: |
|
# attach section similar for both variants that |
|
# were mentioned above |
|
for idx, net in enumerate(new_nets): |
|
handler_kwargs = {'port_id': None, |
|
'net_id': None, |
|
'fip': None} |
|
|
|
if net.get(self.NETWORK_PORT): |
|
handler_kwargs['port_id'] = net.get(self.NETWORK_PORT) |
|
elif net.get(self.NETWORK_SUBNET): |
|
handler_kwargs['port_id'] = self._create_internal_port( |
|
net, idx, security_groups) |
|
|
|
if not handler_kwargs['port_id']: |
|
handler_kwargs['net_id'] = self._get_network_id(net) |
|
if security_groups: |
|
sec_uuids = self.client_plugin( |
|
'neutron').get_secgroup_uuids(security_groups) |
|
handler_kwargs['security_groups'] = sec_uuids |
|
if handler_kwargs['net_id']: |
|
handler_kwargs['fip'] = net.get('fixed_ip') |
|
|
|
floating_ip = net.get(self.NETWORK_FLOATING_IP) |
|
if floating_ip: |
|
flip_associate = {'port_id': handler_kwargs.get('port_id')} |
|
if net.get('fixed_ip'): |
|
flip_associate['fixed_ip_address'] = net.get( |
|
'fixed_ip') |
|
|
|
self.update_floating_ip_association(floating_ip, |
|
flip_associate) |
|
|
|
add_nets.append(handler_kwargs) |
|
|
|
return add_nets |
|
|
|
def _str_network(self, networks): |
|
# if user specify 'allocate_network', return it |
|
# otherwise we return None |
|
for net in networks or []: |
|
str_net = net.get(self.ALLOCATE_NETWORK) |
|
if str_net: |
|
return str_net |
|
|
|
def _is_nic_tagged(self, networks): |
|
# if user specify 'tag', return True |
|
# otherwise return False |
|
for net in networks or []: |
|
if net.get(self.NIC_TAG): |
|
return True |
|
|
|
return False |
|
|
|
def calculate_networks(self, old_nets, new_nets, ifaces, |
|
security_groups=None): |
|
new_str_net = self._str_network(new_nets) |
|
if new_str_net: |
|
return self._calculate_using_str_network(ifaces, new_str_net, |
|
security_groups) |
|
else: |
|
return self._calculate_using_list_networks( |
|
old_nets, new_nets, ifaces, security_groups) |
|
|
|
def update_floating_ip_association(self, floating_ip, flip_associate): |
|
if flip_associate.get('port_id'): |
|
self._floating_ip_neutron_associate(floating_ip, flip_associate) |
|
|
|
@staticmethod |
|
def get_all_ports(server): |
|
return itertools.chain( |
|
server._data_get_ports(), |
|
server._data_get_ports('external_ports') |
|
) |
|
|
|
def detach_ports(self, server): |
|
existing_server_id = server.resource_id |
|
for port in self.get_all_ports(server): |
|
detach_called = self.client_plugin().interface_detach( |
|
existing_server_id, port['id']) |
|
|
|
if not detach_called: |
|
return |
|
|
|
try: |
|
if self.client_plugin().check_interface_detach( |
|
existing_server_id, port['id']): |
|
LOG.info('Detach interface %(port)s successful from ' |
|
'server %(server)s.', |
|
{'port': port['id'], |
|
'server': existing_server_id}) |
|
except tenacity.RetryError: |
|
raise exception.InterfaceDetachFailed( |
|
port=port['id'], server=existing_server_id) |
|
|
|
def attach_ports(self, server): |
|
prev_server_id = server.resource_id |
|
|
|
for port in self.get_all_ports(server): |
|
self.client_plugin().interface_attach(prev_server_id, |
|
port['id']) |
|
try: |
|
if self.client_plugin().check_interface_attach( |
|
prev_server_id, port['id']): |
|
LOG.info('Attach interface %(port)s successful to ' |
|
'server %(server)s', |
|
{'port': port['id'], |
|
'server': prev_server_id}) |
|
except tenacity.RetryError: |
|
raise exception.InterfaceAttachFailed( |
|
port=port['id'], server=prev_server_id) |
|
|
|
def prepare_ports_for_replace(self): |
|
# Check that the interface can be detached |
|
server = None |
|
# TODO(TheJulia): Once Story #2002001 is underway, |
|
# we should be able to replace the query to nova and |
|
# the check for the failed status with just a check |
|
# to see if the resource has failed. |
|
with self.client_plugin().ignore_not_found: |
|
server = self.client().servers.get(self.resource_id) |
|
if server and server.status != 'ERROR': |
|
self.detach_ports(self) |
|
else: |
|
# If we are replacing an ERROR'ed node, we need to delete |
|
# internal ports that we have created, otherwise we can |
|
# encounter deployment issues with duplicate internal |
|
# port data attempting to be created in instances being |
|
# deployed. |
|
self._delete_internal_ports() |
|
|
|
def restore_ports_after_rollback(self, convergence): |
|
# In case of convergence, during rollback, the previous rsrc is |
|
# already selected and is being acted upon. |
|
if convergence: |
|
prev_server = self |
|
rsrc, rsrc_owning_stack, stack = resource.Resource.load( |
|
prev_server.context, prev_server.replaced_by, |
|
prev_server.stack.current_traversal, True, |
|
prev_server.stack.defn._resource_data |
|
) |
|
existing_server = rsrc |
|
else: |
|
backup_stack = self.stack._backup_stack() |
|
prev_server = backup_stack.resources.get(self.name) |
|
existing_server = self |
|
|
|
# Wait until server will move to active state. We can't |
|
# detach interfaces from server in BUILDING state. |
|
# In case of convergence, the replacement resource may be |
|
# created but never have been worked on because the rollback was |
|
# trigerred or new update was trigerred. |
|
if existing_server.resource_id is not None: |
|
try: |
|
while True: |
|
active = self.client_plugin()._check_active( |
|
existing_server.resource_id) |
|
if active: |
|
break |
|
eventlet.sleep(1) |
|
except exception.ResourceInError: |
|
pass |
|
|
|
self.store_external_ports() |
|
self.detach_ports(existing_server) |
|
|
|
self.attach_ports(prev_server)
|
|
|