Handle replace and rollback cases for Server

Add support for handling replace case and rollback for Server resource
with internal port.
This patch adds ability to replace server with specified ip address and
ability to make correct rollback if updated replace failed.

Logic is similar on code introduced for Port resource, but here we
extend existing resource data - "internal ports", by adding new key
'fixed_ips' in dictinary with port 'id'.

implements bp rich-network-prop

Change-Id: Id44bb358c9c874afa6644f126d284f31b6639b63
This commit is contained in:
Sergey Kraynev 2015-09-07 10:57:22 -04:00 committed by Peter Razumovsky
parent 54c26453a0
commit 8b22a11fc6
3 changed files with 174 additions and 0 deletions

View File

@ -1409,6 +1409,12 @@ class Server(stack_user.StackUser, sh.SchedulerHintsMixin,
props[self.IMAGE] = image_id
return defn.freeze(properties=props)
def prepare_for_replace(self):
self.prepare_ports_for_replace()
def restore_after_rollback(self):
self.restore_ports_after_rollback()
def resource_mapping():
return {

View File

@ -329,3 +329,45 @@ class ServerNetworkMixin(object):
add_nets.append(handler_kwargs)
return remove_ports, add_nets
def prepare_ports_for_replace(self):
data = {'external_ports': [],
'internal_ports': []}
port_data = itertools.chain(
[('internal_ports', port) for port in self._data_get_ports()],
[('external_ports', port)
for port in self._data_get_ports('external_ports')])
for port_type, port in port_data:
# store port fixed_ips for restoring after failed update
port_details = self.client('neutron').show_port(port['id'])['port']
fixed_ips = port_details.get('fixed_ips', [])
data[port_type].append({'id': port['id'], 'fixed_ips': fixed_ips})
if data.get('internal_ports'):
self.data_set('internal_ports',
jsonutils.dumps(data['internal_ports']))
if data.get('external_ports'):
self.data_set('external_ports',
jsonutils.dumps(data['external_ports']))
# reset fixed_ips for these ports by setting for each of them
# fixed_ips to []
for port_type, port in port_data:
self.client('neutron').update_port(
port['id'], {'port': {'fixed_ips': []}})
def restore_ports_after_rollback(self):
old_server = self.stack._backup_stack().resources.get(self.name)
port_data = itertools.chain(self._data_get_ports(),
self._data_get_ports('external_ports'))
for port in port_data:
self.client('neutron').update_port(port['id'],
{'port': {'fixed_ips': []}})
old_port_data = itertools.chain(
old_server._data_get_ports(),
old_server._data_get_ports('external_ports'))
for port in old_port_data:
fixed_ips = port['fixed_ips']
self.client('neutron').update_port(
port['id'], {'port': {'fixed_ips': fixed_ips}})

View File

@ -19,6 +19,7 @@ import mox
from neutronclient.neutron import v2_0 as neutronV20
from neutronclient.v2_0 import client as neutronclient
from novaclient import exceptions as nova_exceptions
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
import six
from six.moves.urllib import parse as urlparse
@ -1924,6 +1925,7 @@ class ServersTest(common.HeatTestCase):
nova.NovaClientPlugin._create().AndReturn(self.fc)
self._mock_get_image_id_success('F17-x86_64-gold', 'image_id')
self.m.ReplayAll()
self.patchobject(servers.Server, 'prepare_for_replace')
tmpl['Resources']['WebServer']['Properties'][
'flavor_update_policy'] = 'REPLACE'
@ -1944,6 +1946,7 @@ class ServersTest(common.HeatTestCase):
self._mock_get_image_id_success('F17-x86_64-gold', 'image_id')
self.m.ReplayAll()
self.patchobject(servers.Server, 'prepare_for_replace')
resource_defns = tmpl.resource_definitions(stack)
server = servers.Server('server_server_update_flavor_replace',
resource_defns['WebServer'], stack)
@ -1960,6 +1963,7 @@ class ServersTest(common.HeatTestCase):
def test_server_update_image_replace(self):
stack_name = 'update_imgrep'
(tmpl, stack) = self._setup_test_stack(stack_name)
self.patchobject(servers.Server, 'prepare_for_replace')
tmpl.t['Resources']['WebServer']['Properties'][
'image_update_policy'] = 'REPLACE'
@ -3822,6 +3826,10 @@ class ServerInternalPortTest(common.HeatTestCase):
'create_port')
self.port_delete = self.patchobject(neutronclient.Client,
'delete_port')
self.port_show = self.patchobject(neutronclient.Client,
'show_port')
self.port_update = self.patchobject(neutronclient.Client,
'update_port')
def _return_template_stack_and_rsrc_defn(self, stack_name, temp):
templ = template.Template(template_format.parse(temp),
@ -4163,3 +4171,121 @@ class ServerInternalPortTest(common.HeatTestCase):
update_data.call_args_list[1][0])
self.assertEqual({'port_type': 'external_ports'},
update_data.call_args_list[1][1])
def test_prepare_ports_for_replace(self):
tmpl = """
heat_template_version: 2015-10-15
resources:
server:
type: OS::Nova::Server
properties:
flavor: m1.small
image: F17-x86_64-gold
networks:
- network: 4321
"""
t, stack, server = self._return_template_stack_and_rsrc_defn('test',
tmpl)
port_ids = [{'id': 1122}, {'id': 3344}]
external_port_ids = [{'id': 5566}]
server._data = {"internal_ports": jsonutils.dumps(port_ids),
"external_ports": jsonutils.dumps(external_port_ids)}
data_set = self.patchobject(server, 'data_set')
port1_fixed_ip = {
'fixed_ips': {
'subnet_id': 'test_subnet1',
'ip_address': '41.41.41.41'
}
}
port2_fixed_ip = {
'fixed_ips': {
'subnet_id': 'test_subnet2',
'ip_address': '42.42.42.42'
}
}
port3_fixed_ip = {
'fixed_ips': {
'subnet_id': 'test_subnet3',
'ip_address': '43.43.43.43'
}
}
self.port_show.side_effect = [{'port': port1_fixed_ip},
{'port': port2_fixed_ip},
{'port': port3_fixed_ip}]
server.prepare_for_replace()
# check, that data was updated
port_ids[0].update(port1_fixed_ip)
port_ids[1].update(port2_fixed_ip)
external_port_ids[0].update(port3_fixed_ip)
expected_data = jsonutils.dumps(port_ids)
expected_external_data = jsonutils.dumps(external_port_ids)
data_set.has_calls(('internal_ports', expected_data),
('external_ports', expected_external_data))
# check, that all ip were removed from ports
empty_fixed_ips = {'port': {'fixed_ips': []}}
self.port_update.has_calls((1122, empty_fixed_ips),
(3344, empty_fixed_ips),
(5566, empty_fixed_ips))
def test_restore_ports_after_rollback(self):
tmpl = """
heat_template_version: 2015-10-15
resources:
server:
type: OS::Nova::Server
properties:
flavor: m1.small
image: F17-x86_64-gold
networks:
- network: 4321
"""
t, stack, server = self._return_template_stack_and_rsrc_defn('test',
tmpl)
port_ids = [{'id': 1122}, {'id': 3344}]
external_port_ids = [{'id': 5566}]
server._data = {"internal_ports": jsonutils.dumps(port_ids),
"external_ports": jsonutils.dumps(external_port_ids)}
port1_fixed_ip = {
'fixed_ips': {
'subnet_id': 'test_subnet1',
'ip_address': '41.41.41.41'
}
}
port2_fixed_ip = {
'fixed_ips': {
'subnet_id': 'test_subnet2',
'ip_address': '42.42.42.42'
}
}
port3_fixed_ip = {
'fixed_ips': {
'subnet_id': 'test_subnet3',
'ip_address': '43.43.43.43'
}
}
port_ids[0].update(port1_fixed_ip)
port_ids[1].update(port2_fixed_ip)
external_port_ids[0].update(port3_fixed_ip)
# add data to old server in backup stack
old_server = mock.Mock()
stack._backup_stack = mock.Mock()
stack._backup_stack().resources.get.return_value = old_server
old_server._data_get_ports.side_effect = [port_ids, []]
server.restore_after_rollback()
# check, that all ip were removed from new_ports
empty_fixed_ips = {'port': {'fixed_ips': []}}
self.port_update.has_calls((1122, empty_fixed_ips),
(3344, empty_fixed_ips),
(5566, empty_fixed_ips))
# check, that all ip were restored for old_ports
self.port_update.has_calls((1122, {'port': port1_fixed_ip}),
(3344, {'port': port2_fixed_ip}),
(5566, {'port': port3_fixed_ip}))