Merge "Implement neutron pool resource"

This commit is contained in:
Jenkins 2013-08-09 03:58:27 +00:00 committed by Gerrit Code Review
commit f6b6cf8097
2 changed files with 512 additions and 11 deletions

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from heat.common import exception
from heat.engine import clients from heat.engine import clients
from heat.engine import scheduler from heat.engine import scheduler
from heat.engine.resources.neutron import neutron from heat.engine.resources.neutron import neutron
@ -44,7 +45,7 @@ class HealthMonitor(neutron.NeutronResource):
'expected_codes', 'url_path') 'expected_codes', 'url_path')
attributes_schema = { attributes_schema = {
'admin_state_up': 'the administrative state of this port', 'admin_state_up': 'the administrative state of this health monitor',
'delay': 'the minimum time in seconds between regular connections ' 'delay': 'the minimum time in seconds between regular connections '
'of the member', 'of the member',
'expected_codes': 'the list of HTTP status codes expected in ' 'expected_codes': 'the list of HTTP status codes expected in '
@ -75,8 +76,9 @@ class HealthMonitor(neutron.NeutronResource):
self.resource_id)['health_monitor'] self.resource_id)['health_monitor']
def handle_update(self, json_snippet, tmpl_diff, prop_diff): def handle_update(self, json_snippet, tmpl_diff, prop_diff):
self.neutron().update_health_monitor( if prop_diff:
self.resource_id, {'health_monitor': prop_diff}) self.neutron().update_health_monitor(
self.resource_id, {'health_monitor': prop_diff})
def handle_delete(self): def handle_delete(self):
try: try:
@ -88,10 +90,158 @@ class HealthMonitor(neutron.NeutronResource):
return scheduler.TaskRunner(self._confirm_delete)() return scheduler.TaskRunner(self._confirm_delete)()
class Pool(neutron.NeutronResource):
"""
A resource for managing load balancer pools in Neutron.
"""
vip_schema = {
'name': {'Type': 'String'},
'description': {'Type': 'String'},
'address': {'Type': 'String'},
'connection_limit': {'Type': 'Integer'},
'protocol_port': {'Type': 'Integer', 'Required': True},
'admin_state_up': {'Default': True, 'Type': 'Boolean'},
}
properties_schema = {
'protocol': {'Type': 'String', 'Required': True,
'AllowedValues': ['TCP', 'HTTP', 'HTTPS']},
'subnet_id': {'Type': 'String', 'Required': True},
'lb_method': {'Type': 'String', 'Required': True,
'AllowedValues': ['ROUND_ROBIN', 'LEAST_CONNECTIONS',
'SOURCE_IP']},
'name': {'Type': 'String'},
'description': {'Type': 'String'},
'admin_state_up': {'Default': True, 'Type': 'Boolean'},
'vip': {'Type': 'Map', 'Schema': vip_schema, 'Required': True},
'monitors': {'Type': 'List'},
}
update_allowed_keys = ('Properties',)
update_allowed_properties = ('description', 'admin_state_up', 'lb_method',
'monitors')
attributes_schema = {
'admin_state_up': 'the administrative state of this pool',
'id': 'unique identifier for this pool',
'name': 'friendly name of the pool',
'protocol': 'protocol to balance',
'subnet_id': 'the subnet on which the members of the pool '
'will be located',
'lb_method': 'the algorithm used to distribute load between the '
'members of the pool',
'description': 'description of the pool',
'tenant_id': 'tenant owning the pool',
'vip': 'ip of the pool',
}
def handle_create(self):
properties = self.prepare_properties(
self.properties,
self.physical_resource_name())
vip_properties = properties.pop('vip')
monitors = properties.pop('monitors', [])
client = self.neutron()
pool = client.create_pool({'pool': properties})['pool']
self.resource_id_set(pool['id'])
for monitor in monitors:
client.associate_health_monitor(
pool['id'], {'health_monitor': {'id': monitor}})
vip_arguments = self.prepare_properties(
vip_properties,
'%s.vip' % (self.name,))
vip_arguments['protocol'] = self.properties['protocol']
vip_arguments['subnet_id'] = self.properties['subnet_id']
vip_arguments['pool_id'] = pool['id']
vip = client.create_vip({'vip': vip_arguments})['vip']
self.metadata = {'vip': vip['id']}
def _show_resource(self):
return self.neutron().show_pool(self.resource_id)['pool']
def check_create_complete(self, data):
attributes = self._show_resource()
if attributes['status'] == 'PENDING_CREATE':
return False
elif attributes['status'] == 'ACTIVE':
vip_attributes = self.neutron().show_vip(
self.metadata['vip'])['vip']
if vip_attributes['status'] == 'PENDING_CREATE':
return False
elif vip_attributes['status'] == 'ACTIVE':
return True
raise exception.Error(
'neutron reported unexpected vip resource[%s] status[%s]' %
(vip_attributes['name'], vip_attributes['status']))
raise exception.Error(
'neutron report unexpected pool resource[%s] status[%s]' %
(attributes['name'], attributes['status']))
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
if prop_diff:
client = self.neutron()
monitors = set(prop_diff.pop('monitors', []))
if monitors:
old_monitors = set(self.t['Properties'].get('monitors', []))
for monitor in old_monitors - monitors:
client.disassociate_health_monitor(
self.resource_id, {'health_monitor': {'id': monitor}})
for monitor in monitors - old_monitors:
client.associate_health_monitor(
self.resource_id, {'health_monitor': {'id': monitor}})
if prop_diff:
client.update_pool(self.resource_id, {'pool': prop_diff})
def _resolve_attribute(self, name):
if name == 'vip':
return self.neutron().show_vip(self.metadata['vip'])['vip']
return super(Pool, self)._resolve_attribute(name)
def _confirm_vip_delete(self):
client = self.neutron()
while True:
try:
yield
client.show_vip(self.metadata['vip'])
except NeutronClientException as ex:
if ex.status_code != 404:
raise ex
break
self._delete_pool()
def _delete_pool(self):
try:
self.neutron().delete_pool(self.resource_id)
except NeutronClientException as ex:
if ex.status_code != 404:
raise ex
else:
return scheduler.TaskRunner(self._confirm_delete)()
def handle_delete(self):
if self.metadata:
try:
self.neutron().delete_vip(self.metadata['vip'])
except NeutronClientException as ex:
if ex.status_code != 404:
raise ex
self._delete_pool()
else:
return scheduler.TaskRunner(self._confirm_vip_delete)()
else:
self._delete_pool()
def resource_mapping(): def resource_mapping():
if clients.neutronclient is None: if clients.neutronclient is None:
return {} return {}
return { return {
'OS::Neutron::HealthMonitor': HealthMonitor, 'OS::Neutron::HealthMonitor': HealthMonitor,
'OS::Neutron::Pool': Pool,
} }

View File

@ -47,6 +47,27 @@ health_monitor_template = '''
} }
''' '''
pool_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to test load balancer resources",
"Parameters" : {},
"Resources" : {
"pool": {
"Type": "OS::Neutron::Pool",
"Properties": {
"protocol": "HTTP",
"subnet_id": "sub123",
"lb_method": "ROUND_ROBIN",
"vip": {
"protocol_port": 80
}
}
}
}
}
'''
@skipIf(neutronclient is None, 'neutronclient unavailable') @skipIf(neutronclient is None, 'neutronclient unavailable')
class HealthMonitorTest(HeatTestCase): class HealthMonitorTest(HeatTestCase):
@ -95,13 +116,16 @@ class HealthMonitorTest(HeatTestCase):
stack = utils.parse_stack(snippet) stack = utils.parse_stack(snippet)
rsrc = loadbalancer.HealthMonitor( rsrc = loadbalancer.HealthMonitor(
'monitor', snippet['Resources']['monitor'], stack) 'monitor', snippet['Resources']['monitor'], stack)
self.assertRaises(exception.ResourceFailure, error = self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(rsrc.create)) scheduler.TaskRunner(rsrc.create))
self.assertEqual(
'NeutronClientException: An unknown exception occurred.',
str(error))
self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state) self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)
self.m.VerifyAll() self.m.VerifyAll()
def test_delete(self): def test_delete(self):
neutronclient.Client.delete_health_monitor('5678').AndReturn(None) neutronclient.Client.delete_health_monitor('5678')
neutronclient.Client.show_health_monitor('5678').AndRaise( neutronclient.Client.show_health_monitor('5678').AndRaise(
loadbalancer.NeutronClientException(status_code=404)) loadbalancer.NeutronClientException(status_code=404))
@ -130,8 +154,11 @@ class HealthMonitorTest(HeatTestCase):
rsrc = self.create_health_monitor() rsrc = self.create_health_monitor()
self.m.ReplayAll() self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)() scheduler.TaskRunner(rsrc.create)()
self.assertRaises(exception.ResourceFailure, error = self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(rsrc.delete)) scheduler.TaskRunner(rsrc.delete))
self.assertEqual(
'NeutronClientException: An unknown exception occurred.',
str(error))
self.assertEqual((rsrc.DELETE, rsrc.FAILED), rsrc.state) self.assertEqual((rsrc.DELETE, rsrc.FAILED), rsrc.state)
self.m.VerifyAll() self.m.VerifyAll()
@ -150,14 +177,17 @@ class HealthMonitorTest(HeatTestCase):
rsrc = self.create_health_monitor() rsrc = self.create_health_monitor()
self.m.ReplayAll() self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)() scheduler.TaskRunner(rsrc.create)()
self.assertRaises(exception.InvalidTemplateAttribute, error = self.assertRaises(exception.InvalidTemplateAttribute,
rsrc.FnGetAtt, 'subnet_id') rsrc.FnGetAtt, 'subnet_id')
self.assertEqual(
'The Referenced Attribute (monitor subnet_id) is incorrect.',
str(error))
self.m.VerifyAll() self.m.VerifyAll()
def test_update(self): def test_update(self):
rsrc = self.create_health_monitor() rsrc = self.create_health_monitor()
neutronclient.Client.update_health_monitor( neutronclient.Client.update_health_monitor(
'5678', {'health_monitor': {'delay': 10}}).AndReturn(None) '5678', {'health_monitor': {'delay': 10}})
self.m.ReplayAll() self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)() scheduler.TaskRunner(rsrc.create)()
@ -166,3 +196,324 @@ class HealthMonitorTest(HeatTestCase):
self.assertEqual(None, rsrc.update(update_template)) self.assertEqual(None, rsrc.update(update_template))
self.m.VerifyAll() self.m.VerifyAll()
@skipIf(neutronclient is None, 'neutronclient unavailable')
class PoolTest(HeatTestCase):
def setUp(self):
super(PoolTest, self).setUp()
self.m.StubOutWithMock(neutronclient.Client, 'create_pool')
self.m.StubOutWithMock(neutronclient.Client, 'delete_pool')
self.m.StubOutWithMock(neutronclient.Client, 'show_pool')
self.m.StubOutWithMock(neutronclient.Client, 'update_pool')
self.m.StubOutWithMock(neutronclient.Client,
'associate_health_monitor')
self.m.StubOutWithMock(neutronclient.Client,
'disassociate_health_monitor')
self.m.StubOutWithMock(neutronclient.Client, 'create_vip')
self.m.StubOutWithMock(neutronclient.Client, 'delete_vip')
self.m.StubOutWithMock(neutronclient.Client, 'show_vip')
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
utils.setup_dummy_db()
def create_pool(self):
clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient())
neutronclient.Client.create_pool({
'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP',
'name': utils.PhysName('test_stack', 'pool'),
'lb_method': 'ROUND_ROBIN', 'admin_state_up': True}}
).AndReturn({'pool': {'id': '5678'}})
neutronclient.Client.create_vip({
'vip': {
'protocol': u'HTTP', 'name': 'pool.vip',
'admin_state_up': True, 'subnet_id': u'sub123',
'pool_id': '5678', 'protocol_port': 80}}
).AndReturn({'vip': {'id': 'xyz'}})
neutronclient.Client.show_pool('5678').AndReturn(
{'pool': {'status': 'ACTIVE'}})
neutronclient.Client.show_vip('xyz').AndReturn(
{'vip': {'status': 'ACTIVE'}})
snippet = template_format.parse(pool_template)
stack = utils.parse_stack(snippet)
return loadbalancer.Pool(
'pool', snippet['Resources']['pool'], stack)
def test_create(self):
rsrc = self.create_pool()
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll()
def test_create_pending(self):
clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient())
neutronclient.Client.create_pool({
'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP',
'name': utils.PhysName('test_stack', 'pool'),
'lb_method': 'ROUND_ROBIN', 'admin_state_up': True}}
).AndReturn({'pool': {'id': '5678'}})
neutronclient.Client.create_vip({
'vip': {
'protocol': u'HTTP', 'name': 'pool.vip',
'admin_state_up': True, 'subnet_id': u'sub123',
'pool_id': '5678', 'protocol_port': 80}}
).AndReturn({'vip': {'id': 'xyz'}})
neutronclient.Client.show_pool('5678').AndReturn(
{'pool': {'status': 'PENDING_CREATE'}})
neutronclient.Client.show_pool('5678').MultipleTimes().AndReturn(
{'pool': {'status': 'ACTIVE'}})
neutronclient.Client.show_vip('xyz').AndReturn(
{'vip': {'status': 'PENDING_CREATE'}})
neutronclient.Client.show_vip('xyz').AndReturn(
{'vip': {'status': 'ACTIVE'}})
snippet = template_format.parse(pool_template)
stack = utils.parse_stack(snippet)
rsrc = loadbalancer.Pool(
'pool', snippet['Resources']['pool'], stack)
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll()
def test_create_failed_unexpected_status(self):
clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient())
neutronclient.Client.create_pool({
'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP',
'name': utils.PhysName('test_stack', 'pool'),
'lb_method': 'ROUND_ROBIN', 'admin_state_up': True}}
).AndReturn({'pool': {'id': '5678'}})
neutronclient.Client.create_vip({
'vip': {
'protocol': u'HTTP', 'name': 'pool.vip',
'admin_state_up': True, 'subnet_id': u'sub123',
'pool_id': '5678', 'protocol_port': 80}}
).AndReturn({'vip': {'id': 'xyz'}})
neutronclient.Client.show_pool('5678').AndReturn(
{'pool': {'status': 'ERROR', 'name': '5678'}})
snippet = template_format.parse(pool_template)
stack = utils.parse_stack(snippet)
rsrc = loadbalancer.Pool(
'pool', snippet['Resources']['pool'], stack)
self.m.ReplayAll()
error = self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(rsrc.create))
self.assertEqual(
'Error: neutron report unexpected pool '
'resource[5678] status[ERROR]',
str(error))
self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)
self.m.VerifyAll()
def test_create_failed_unexpected_vip_status(self):
clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient())
neutronclient.Client.create_pool({
'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP',
'name': utils.PhysName('test_stack', 'pool'),
'lb_method': 'ROUND_ROBIN', 'admin_state_up': True}}
).AndReturn({'pool': {'id': '5678'}})
neutronclient.Client.create_vip({
'vip': {
'protocol': u'HTTP', 'name': 'pool.vip',
'admin_state_up': True, 'subnet_id': u'sub123',
'pool_id': '5678', 'protocol_port': 80}}
).AndReturn({'vip': {'id': 'xyz'}})
neutronclient.Client.show_pool('5678').MultipleTimes().AndReturn(
{'pool': {'status': 'ACTIVE'}})
neutronclient.Client.show_vip('xyz').AndReturn(
{'vip': {'status': 'ERROR', 'name': 'xyz'}})
snippet = template_format.parse(pool_template)
stack = utils.parse_stack(snippet)
rsrc = loadbalancer.Pool(
'pool', snippet['Resources']['pool'], stack)
self.m.ReplayAll()
error = self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(rsrc.create))
self.assertEqual(
'Error: neutron reported unexpected vip '
'resource[xyz] status[ERROR]',
str(error))
self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)
self.m.VerifyAll()
def test_create_failed(self):
clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient())
neutronclient.Client.create_pool({
'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP',
'name': utils.PhysName('test_stack', 'pool'),
'lb_method': 'ROUND_ROBIN', 'admin_state_up': True}}
).AndRaise(loadbalancer.NeutronClientException())
self.m.ReplayAll()
snippet = template_format.parse(pool_template)
stack = utils.parse_stack(snippet)
rsrc = loadbalancer.Pool(
'pool', snippet['Resources']['pool'], stack)
error = self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(rsrc.create))
self.assertEqual(
'NeutronClientException: An unknown exception occurred.',
str(error))
self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)
self.m.VerifyAll()
def test_delete(self):
rsrc = self.create_pool()
neutronclient.Client.delete_vip('xyz')
neutronclient.Client.show_vip('xyz').AndRaise(
loadbalancer.NeutronClientException(status_code=404))
neutronclient.Client.delete_pool('5678')
neutronclient.Client.show_pool('5678').AndRaise(
loadbalancer.NeutronClientException(status_code=404))
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll()
def test_delete_already_gone(self):
neutronclient.Client.delete_vip('xyz').AndRaise(
loadbalancer.NeutronClientException(status_code=404))
neutronclient.Client.delete_pool('5678').AndRaise(
loadbalancer.NeutronClientException(status_code=404))
rsrc = self.create_pool()
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll()
def test_delete_vip_failed(self):
neutronclient.Client.delete_vip('xyz').AndRaise(
loadbalancer.NeutronClientException(status_code=400))
rsrc = self.create_pool()
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
error = self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(rsrc.delete))
self.assertEqual(
'NeutronClientException: An unknown exception occurred.',
str(error))
self.assertEqual((rsrc.DELETE, rsrc.FAILED), rsrc.state)
self.m.VerifyAll()
def test_delete_failed(self):
neutronclient.Client.delete_vip('xyz').AndRaise(
loadbalancer.NeutronClientException(status_code=404))
neutronclient.Client.delete_pool('5678').AndRaise(
loadbalancer.NeutronClientException(status_code=400))
rsrc = self.create_pool()
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
error = self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(rsrc.delete))
self.assertEqual(
'NeutronClientException: An unknown exception occurred.',
str(error))
self.assertEqual((rsrc.DELETE, rsrc.FAILED), rsrc.state)
self.m.VerifyAll()
def test_attribute(self):
rsrc = self.create_pool()
neutronclient.Client.show_pool('5678').MultipleTimes(
).AndReturn(
{'pool': {'admin_state_up': True, 'lb_method': 'ROUND_ROBIN'}})
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.assertEqual(True, rsrc.FnGetAtt('admin_state_up'))
self.assertEqual('ROUND_ROBIN', rsrc.FnGetAtt('lb_method'))
self.m.VerifyAll()
def test_vip_attribute(self):
rsrc = self.create_pool()
neutronclient.Client.show_vip('xyz').AndReturn(
{'vip': {'address': '10.0.0.3', 'name': 'xyz'}})
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.assertEqual({'address': '10.0.0.3', 'name': 'xyz'},
rsrc.FnGetAtt('vip'))
self.m.VerifyAll()
def test_attribute_failed(self):
rsrc = self.create_pool()
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
error = self.assertRaises(exception.InvalidTemplateAttribute,
rsrc.FnGetAtt, 'net_id')
self.assertEqual(
'The Referenced Attribute (pool net_id) is incorrect.',
str(error))
self.m.VerifyAll()
def test_update(self):
rsrc = self.create_pool()
neutronclient.Client.update_pool(
'5678', {'pool': {'admin_state_up': False}})
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
update_template = copy.deepcopy(rsrc.t)
update_template['Properties']['admin_state_up'] = False
self.assertEqual(None, rsrc.update(update_template))
self.m.VerifyAll()
def test_update_monitors(self):
clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient())
neutronclient.Client.create_pool({
'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP',
'name': utils.PhysName('test_stack', 'pool'),
'lb_method': 'ROUND_ROBIN', 'admin_state_up': True}}
).AndReturn({'pool': {'id': '5678'}})
neutronclient.Client.associate_health_monitor(
'5678', {'health_monitor': {'id': 'mon123'}})
neutronclient.Client.associate_health_monitor(
'5678', {'health_monitor': {'id': 'mon456'}})
neutronclient.Client.create_vip({
'vip': {
'protocol': u'HTTP', 'name': 'pool.vip',
'admin_state_up': True, 'subnet_id': u'sub123',
'pool_id': '5678', 'protocol_port': 80}}
).AndReturn({'vip': {'id': 'xyz'}})
neutronclient.Client.show_pool('5678').AndReturn(
{'pool': {'status': 'ACTIVE'}})
neutronclient.Client.show_vip('xyz').AndReturn(
{'vip': {'status': 'ACTIVE'}})
neutronclient.Client.disassociate_health_monitor(
'5678', {'health_monitor': {'id': 'mon456'}})
neutronclient.Client.associate_health_monitor(
'5678', {'health_monitor': {'id': 'mon789'}})
snippet = template_format.parse(pool_template)
stack = utils.parse_stack(snippet)
snippet['Resources']['pool']['Properties']['monitors'] = [
'mon123', 'mon456']
rsrc = loadbalancer.Pool(
'pool', snippet['Resources']['pool'], stack)
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
update_template = copy.deepcopy(rsrc.t)
update_template['Properties']['monitors'] = ['mon123', 'mon789']
self.assertEqual(None, rsrc.update(update_template))
self.m.VerifyAll()