Fix stack inconsistency after app deletion

To expose IP addresses of Instance in MuranoPL we place new entry to
outputs section in Heat template that reference corresponding
OS::Nova::Server resource. During clean-up stage, if some application
was deleted from environment, we remove corresponding OS::Nova::Server
resource, but reference in outputs section stays.

Stack with reference to resource that is not present in template is
invalid - environment deployment fails.

This change add code that removes references to OS::Nova::Server when
corresponding Instance is deleted.

Attention: this change fix issue that can break murano-ci gate

Closes-bug: #1339630
Co-Authored-By: Stan Lagun <slagun@mirantis.com>
Change-Id: I74d32034969dd7f554d74fac87f407388e52dd7e
This commit is contained in:
Serg Melikyan 2014-07-09 15:56:06 +04:00
parent f051ff9d77
commit 4da6c43dd2
8 changed files with 222 additions and 0 deletions

View File

@ -85,6 +85,15 @@ class Client(object):
return requests.post(endpoint, data=json.dumps(json_data),
headers=headers).json()
def delete_service(self, environment_id, session_id, service_id):
headers = self.headers.copy()
headers.update({'x-configuration-session': session_id})
endpoint = '{0}environments/{1}/services/{2}'.format(
self.endpoint, environment_id, service_id)
requests.delete(endpoint, headers=headers)
def wait_for_environment_deploy(self, environment_id):
environment = self.get_environment(environment_id)
@ -365,3 +374,63 @@ class MuranoBase(testtools.TestCase, testtools.testcase.WithAttributes,
self.assertIsNotNone(env)
self.deployment_success_check(env, 8080)
def _get_telnet_app(self):
return {
"instance": {
"?": {
"type": "io.murano.resources.LinuxMuranoInstance",
"id": str(uuid.uuid4())
},
"flavor": "m1.medium",
"image": self.linux,
"name": "instance{0}".format(uuid.uuid4().hex[:5]),
},
"name": "app{0}".format(uuid.uuid4().hex[:5]),
"?": {
"type": "io.murano.apps.linux.Telnet",
"id": str(uuid.uuid4())
}
}
def _quick_deploy(self, name, *apps):
environment = self.client.create_environment(name)
session = self.client.create_session(environment['id'])
environment_id, session_id = environment['id'], session['id']
for app in apps:
self.client.create_service(environment_id, session_id, app)
self.client.deploy_session(environment_id, session_id)
return self.client.wait_for_environment_deploy(environment_id)
def _get_stack(self, name):
by_name = {'name': name}
stack_iter = self.heat_client.stacks.list(limit=1, filters=by_name)
return next(stack_iter, None)
def test_instance_refs_are_removed_after_application_is_removed(self):
name = 'e' + str(uuid.uuid4().hex)
# create environment with telnet application
application1 = self._get_telnet_app()
application2 = self._get_telnet_app()
application_id = application1['?']['id']
instance_name = application1['instance']['name']
apps = [application1, application2]
environment_id = self._quick_deploy(name, *apps)['id']
# add environment to the list for tear-down clean-up
self.environments.append(environment_id)
# delete telnet application
session_id = self.client.create_session(environment_id)['id']
self.client.delete_service(environment_id, session_id, application_id)
self.client.deploy_session(environment_id, session_id)
self.client.wait_for_environment_deploy(environment_id)
template = self.heat_client.stacks.template(name)
ip_addresses = '{0}-assigned-ip'.format(instance_name)
floating_ip = '{0}-FloatingIPaddress'.format(instance_name)
self.assertNotIn(ip_addresses, template['outputs'])
self.assertNotIn(floating_ip, template['outputs'])
self.assertNotIn(instance_name, template['resources'])

View File

@ -215,12 +215,26 @@ Methods:
- $.setAttr(fipAssigned, true)
destroy:
# Fixme(smelikyan): We need to remove all associated resources on destroy
Body:
- $template: $.environment.stack.current()
# Remove OS::Nova::Server resource
- $patchBlock:
op: remove
path: format('/resources/{0}', $.name)
- $template: patch($template, $patchBlock)
# Remove Ip Addresses Outputs assigned to this Instance
- $assignedIpBlock:
op: remove
path: format('/outputs/{0}-assigned-ip', $.name)
- $template: patch($template, $assignedIpBlock)
# Remove Floatting IP Addresses Outputs
- If: $.getAttr(fipAssigned, false)
Then:
- $assignedFloatingIpBlock:
op: remove
path: format('/outputs/{0}-FloatingIPaddress', $.name)
- $template: patch($template, $assignedFloatingIpBlock)
- $.environment.stack.setTemplate($template)
- $.environment.stack.push()
- $.environment.instanceNotifier.untrackCloudInstance($this)

View File

@ -0,0 +1,8 @@
Namespaces:
=: io.murano.system
Name: Agent
Properties:
host:
Contract: $

View File

@ -0,0 +1,22 @@
Namespaces:
=: io.murano
res: io.murano.resources
sys: io.murano.system
Name: Environment
Properties:
stack:
Contract: $.class(sys:HeatStack)
instance:
Contract: $.class(res:Instance)
instanceNotifier:
Contract: $.class(sys:InstanceNotifier)
Usage: Runtime
Methods:
initialize:
Body:
$.instanceNotifier: new(sys:InstanceNotifier)

View File

@ -0,0 +1,18 @@
Namespaces:
=: io.murano.system
Name: HeatStack
Methods:
push:
current:
Body:
- Return: $.getAttr(stack)
setTemplate:
Arguments:
- template:
Contract: {}
Body:
- $.setAttr(stack, $template)

View File

@ -0,0 +1,10 @@
Namespaces:
=: io.murano.system
Name: InstanceNotifier
Methods:
untrackCloudInstance:
Arguments:
- instance:
Contract: $

View File

@ -0,0 +1,5 @@
Namespaces:
=: io.murano.system
Name: Resources

View File

@ -0,0 +1,76 @@
# Copyright (c) 2014 Mirantis, 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.
from murano.tests.dsl.foundation import object_model as om
from murano.tests.dsl.foundation import test_case
TEMPLATE = {
"outputs": {
"instance64464-assigned-ip": {
"value": {"get_attr": ["instance64464", "addresses"]}
},
"instance64464-FloatingIPaddress": {
"value": {"get_attr": ["instance64464", "addresses"]}
}
},
"resources": {
"instance64464": {
"type": "OS::Nova::Server",
"properties": {
"key_name": None,
"flavor": "m1.medium",
"image": "cloud-fedora-v3",
}
}
}
}
class TestDestroy(test_case.DslTestCase):
def test_destroy_removes_ip_address_from_outputs(self):
heat_stack_obj = om.Object('io.murano.system.HeatStack')
instance_obj = om.Object(
'io.murano.resources.Instance',
name='instance64464',
flavor='m1.medium',
image='cloud-fedora-v3'
)
runner = self.new_runner({
'Objects': om.Object(
'io.murano.Environment',
stack=heat_stack_obj,
instance=instance_obj
),
'Attributes': [
om.Attribute(heat_stack_obj, 'stack', TEMPLATE),
om.Attribute(instance_obj, 'fipAssigned', True)
]
})
empty_env = runner.serialized_model
empty_env['Objects']['instance'] = None
model = self.new_runner(empty_env).serialized_model
template = self.find_attribute(
model, heat_stack_obj.id, heat_stack_obj.type_name, 'stack'
)
instance_name = 'instance64464'
ip_addresses = '{0}-assigned-ip'.format(instance_name)
floating_ip = '{0}-FloatingIPaddress'.format(instance_name)
self.assertNotIn(ip_addresses, template['outputs'])
self.assertNotIn(floating_ip, template['outputs'])
self.assertNotIn(instance_name, template['resources'])