Convergence: Fix restore on rollback for server and port

Implements the special handling required for server and port resources
rollback to be possible.

Change-Id: If1b39df070f5e5394304d3e2b31ee7226ec2f270
Partial-Implements: blueprint rich-network-prop
Closes-Bug: #1498660
This commit is contained in:
Rakesh H S 2015-10-12 16:47:42 +05:30
parent 34f32eef0e
commit ddf84f8ea6
7 changed files with 180 additions and 27 deletions

View File

@ -883,6 +883,10 @@ class Resource(object):
with self.lock(engine_id):
new_temp = template.Template.load(self.context, template_id)
new_res_def = new_temp.resource_definitions(self.stack)[self.name]
if self.stack.action == self.stack.ROLLBACK and \
self.stack.status == self.stack.IN_PROGRESS \
and self.replaced_by:
self.restore_prev_rsrc(convergence=True)
runner = scheduler.TaskRunner(self.update, new_res_def)
try:
runner(timeout=timeout)
@ -947,10 +951,11 @@ class Resource(object):
except exception.UpdateReplace as ex:
# catch all UpdateReplace expections
if (self.stack.action == 'ROLLBACK' and
self.stack.status == 'IN_PROGRESS'):
self.stack.status == 'IN_PROGRESS' and
not cfg.CONF.convergence_engine):
# handle case, when it's rollback and we should restore
# old resource
self.restore_after_rollback()
self.restore_prev_rsrc()
else:
self.prepare_for_replace()
raise ex
@ -964,7 +969,7 @@ class Resource(object):
"""
pass
def restore_after_rollback(self):
def restore_prev_rsrc(self, convergence=False):
"""Restore resource after rollback.
Some resources requires additional actions after rollback.

View File

@ -21,6 +21,7 @@ from heat.common.i18n import _LW
from heat.engine import attributes
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine.resources.openstack.neutron import neutron
from heat.engine.resources.openstack.neutron import subnet
from heat.engine import support
@ -441,16 +442,30 @@ class Port(neutron.NeutronResource):
props = {'fixed_ips': []}
self.client().update_port(self.resource_id, {'port': props})
def restore_after_rollback(self):
old_port = self.stack._backup_stack().resources.get(self.name)
fixed_ips = old_port.data().get('port_fip', [])
# restore fixed_ips for this port by setting fixed_ips to []
def restore_prev_rsrc(self, convergence=False):
# In case of convergence, during rollback, the previous rsrc is
# already selected and is being acted upon.
prev_port = self if convergence else \
self.stack._backup_stack().resources.get(self.name)
fixed_ips = prev_port.data().get('port_fip', [])
props = {'fixed_ips': []}
old_props = {'fixed_ips': jsonutils.loads(fixed_ips)}
# remove ip from new port
self.client().update_port(self.resource_id, {'port': props})
# restore ip for old port
self.client().update_port(old_port.resource_id, {'port': old_props})
if convergence:
existing_port, stack = resource.Resource.load(
prev_port.context, prev_port.replaced_by, True,
prev_port.stack.cache_data
)
existing_port_id = existing_port.resource_id
else:
existing_port_id = self.resource_id
if existing_port_id:
# reset fixed_ips to [] for new resource
self.client().update_port(existing_port_id, {'port': props})
if fixed_ips and prev_port.resource_id:
# restore ip for old port
prev_port_props = {'fixed_ips': jsonutils.loads(fixed_ips)}
self.client().update_port(prev_port.resource_id,
{'port': prev_port_props})
def resource_mapping():

View File

@ -1411,8 +1411,8 @@ class Server(stack_user.StackUser, sh.SchedulerHintsMixin,
def prepare_for_replace(self):
self.prepare_ports_for_replace()
def restore_after_rollback(self):
self.restore_ports_after_rollback()
def restore_prev_rsrc(self, convergence=False):
self.restore_ports_after_rollback(convergence=convergence)
def resource_mapping():

View File

@ -20,6 +20,7 @@ from oslo_utils import netutils
from heat.common import exception
from heat.common.i18n import _
from heat.common.i18n import _LI
from heat.engine import resource
LOG = logging.getLogger(__name__)
@ -355,22 +356,38 @@ class ServerNetworkMixin(object):
self.client('neutron').update_port(
port['id'], {'port': {'fixed_ips': []}})
def restore_ports_after_rollback(self):
def restore_ports_after_rollback(self, convergence):
if not self.is_using_neutron():
return
old_server = self.stack._backup_stack().resources.get(self.name)
# In case of convergence, during rollback, the previous rsrc is
# already selected and is being acted upon.
prev_server = self if convergence else \
self.stack._backup_stack().resources.get(self.name)
port_data = itertools.chain(self._data_get_ports(),
self._data_get_ports('external_ports'))
if convergence:
rsrc, stack = resource.Resource.load(
prev_server.context, prev_server.replaced_by, True,
prev_server.stack.cache_data
)
existing_server = rsrc
else:
existing_server = self
port_data = itertools.chain(
existing_server._data_get_ports(),
existing_server._data_get_ports('external_ports')
)
for port in port_data:
# reset fixed_ips to [] for new resource
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:
# restore ip for old port
prev_port_data = itertools.chain(
prev_server._data_get_ports(),
prev_server._data_get_ports('external_ports'))
for port in prev_port_data:
fixed_ips = port['fixed_ips']
self.client('neutron').update_port(
port['id'], {'port': {'fixed_ips': fixed_ips}})

View File

@ -748,7 +748,7 @@ class NeutronPortTest(common.HeatTestCase):
n_client.update_port.assert_called_once_with('test_res_id',
expected_props)
def test_restore_after_rollback_port(self):
def test_restore_prev_rsrc(self):
t = template_format.parse(neutron_port_template)
stack = utils.parse_stack(t)
new_port = stack['port']
@ -769,7 +769,7 @@ class NeutronPortTest(common.HeatTestCase):
new_port.client = mock.Mock(return_value=n_client)
# execute prepare_for_replace
new_port.restore_after_rollback()
new_port.restore_prev_rsrc()
# check, that ports were updated: old port get ip and
# same ip was removed from old port
@ -778,3 +778,40 @@ class NeutronPortTest(common.HeatTestCase):
n_client.update_port.assert_has_calls([
mock.call('new_res_id', expected_new_props),
mock.call('old_res_id', expected_old_props)])
def test_restore_prev_rsrc_convergence(self):
t = template_format.parse(neutron_port_template)
stack = utils.parse_stack(t)
stack.store()
# mock resource from previous template
prev_rsrc = stack['port']
prev_rsrc.resource_id = 'prev-rsrc'
# store in db
prev_rsrc.state_set(prev_rsrc.UPDATE, prev_rsrc.COMPLETE)
# mock resource from existing template and store in db
existing_rsrc = stack['port']
existing_rsrc.current_template_id = stack.t.id
existing_rsrc.resource_id = 'existing-rsrc'
existing_rsrc.state_set(existing_rsrc.UPDATE, existing_rsrc.COMPLETE)
# mock previous resource was replaced by existing resource
prev_rsrc.replaced_by = existing_rsrc.id
_value = {
'subnet_id': 'test_subnet',
'ip_address': '42.42.42.42'
}
prev_rsrc._data = {'port_fip': jsonutils.dumps(_value)}
n_client = mock.Mock()
prev_rsrc.client = mock.Mock(return_value=n_client)
# execute prepare_for_replace
prev_rsrc.restore_prev_rsrc(convergence=True)
expected_existing_props = {'port': {'fixed_ips': []}}
expected_prev_props = {'port': {'fixed_ips': _value}}
n_client.update_port.assert_has_calls([
mock.call(existing_rsrc.resource_id, expected_existing_props),
mock.call(prev_rsrc.resource_id, expected_prev_props)])

View File

@ -4275,7 +4275,86 @@ class ServerInternalPortTest(common.HeatTestCase):
stack._backup_stack().resources.get.return_value = old_server
old_server._data_get_ports.side_effect = [port_ids, external_port_ids]
server.restore_after_rollback()
server.restore_prev_rsrc()
# check, that all ip were removed from new_ports
empty_fixed_ips = {'port': {'fixed_ips': []}}
self.port_update.assert_has_calls([
mock.call(1122, empty_fixed_ips),
mock.call(3344, empty_fixed_ips),
mock.call(5566, empty_fixed_ips)])
# check, that all ip were restored for old_ports
self.port_update.assert_has_calls([
mock.call(1122, {'port': port1_fixed_ip}),
mock.call(3344, {'port': port2_fixed_ip}),
mock.call(5566, {'port': port3_fixed_ip})])
def test_restore_ports_after_rollback_convergence(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 = template_format.parse(tmpl)
stack = utils.parse_stack(t)
stack.store()
# mock resource from previous template
prev_rsrc = stack['server']
prev_rsrc.resource_id = 'prev-rsrc'
# store in db
prev_rsrc.state_set(prev_rsrc.UPDATE, prev_rsrc.COMPLETE)
# mock resource from existing template, store in db, and set _data
existing_rsrc = stack['server']
existing_rsrc.current_template_id = stack.t.id
existing_rsrc.resource_id = 'existing-rsrc'
existing_rsrc.state_set(existing_rsrc.UPDATE, existing_rsrc.COMPLETE)
port_ids = [{'id': 1122}, {'id': 3344}]
external_port_ids = [{'id': 5566}]
existing_rsrc.data_set("internal_ports", jsonutils.dumps(port_ids))
existing_rsrc.data_set("external_ports",
jsonutils.dumps(external_port_ids))
# mock previous resource was replaced by existing resource
prev_rsrc.replaced_by = existing_rsrc.id
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
prev_rsrc._data = {
"internal_ports": jsonutils.dumps(port_ids),
"external_ports": jsonutils.dumps(external_port_ids)
}
prev_rsrc.restore_prev_rsrc(convergence=True)
# check, that all ip were removed from new_ports
empty_fixed_ips = {'port': {'fixed_ips': []}}

View File

@ -406,13 +406,13 @@ class ResourceTest(common.HeatTestCase):
self.stack.state_set('ROLLBACK', 'IN_PROGRESS', 'Simulate rollback')
res = TestResource('test_resource', tmpl, self.stack)
res.restore_after_rollback = mock.Mock()
res.restore_prev_rsrc = mock.Mock()
utmpl = rsrc_defn.ResourceDefinition('test_resource', 'TestResource',
{'a_string': 'foo'})
self.assertRaises(
exception.UpdateReplace, scheduler.TaskRunner(res.update, utmpl))
self.assertTrue(res.restore_after_rollback.called)
self.assertTrue(res.restore_prev_rsrc.called)
def test_update_replace_in_failed_without_nested(self):
tmpl = rsrc_defn.ResourceDefinition('test_resource',