Add support for heat files

Sends all files under /Resources/HotFiles to Heat when deploying
a Murano environment, while keeping the format Heat expects

Partially implements: blueprint add-support-for-heat-environments-and-files

Change-Id: Id7574642b8e09a7f659e4d9576a95f9f6efd8792
This commit is contained in:
Michal Gershenzon 2015-07-26 22:05:44 +00:00
parent 74b3176d22
commit 6559890242
11 changed files with 154 additions and 4 deletions

View File

@ -40,6 +40,7 @@ class HeatStack(murano_object.MuranoObject):
self._name = name
self._template = None
self._parameters = {}
self._files = {}
self._applied = True
self._description = description
self._clients = helpers.get_environment(_context).clients
@ -84,6 +85,10 @@ class HeatStack(murano_object.MuranoObject):
self._parameters = parameters
self._applied = False
def setFiles(self, files):
self._files = files
self._applied = False
def updateTemplate(self, _context, template):
template_version = template.get('heat_template_version',
HEAT_TEMPLATE_VERSION)
@ -174,6 +179,7 @@ class HeatStack(murano_object.MuranoObject):
stack_name=self._name,
parameters=self._parameters,
template=template,
files=self._files,
disable_rollback=True)
self._wait_state(
@ -186,6 +192,7 @@ class HeatStack(murano_object.MuranoObject):
trust_client.stacks.update(
stack_id=self._name,
parameters=self._parameters,
files=self._files,
template=template,
disable_rollback=True)
self._wait_state(

View File

@ -94,7 +94,8 @@ class HotPackage(murano.packages.application_package.ApplicationPackage):
parameters.update(HotPackage._translate_outputs(hot))
translated['Properties'] = parameters
translated.update(HotPackage._generate_workflow(hot))
files = HotPackage._translate_files(self._source_directory)
translated.update(HotPackage._generate_workflow(hot, files))
self._translated_class = translated
@staticmethod
@ -149,6 +150,24 @@ class HotPackage(murano.packages.application_package.ApplicationPackage):
}
return result
@staticmethod
def _translate_files(source_directory):
heat_files_dir = os.path.join(source_directory, 'Resources/HotFiles')
result = {}
if os.path.isdir(heat_files_dir):
result = HotPackage._build_heat_files_dict(heat_files_dir)
return result
@staticmethod
def _build_heat_files_dict(basedir):
result = []
for root, _, files in os.walk(os.path.abspath(basedir)):
for f in files:
full_path = os.path.join(root, f)
relative_path = os.path.relpath(full_path, basedir)
result.append(relative_path)
return result
@staticmethod
def _translate_constraint(constraint):
if 'allowed_values' in constraint:
@ -200,10 +219,14 @@ class HotPackage(murano.packages.application_package.ApplicationPackage):
return str(value)
@staticmethod
def _generate_workflow(hot):
def _generate_workflow(hot, files):
template_parameters = {}
for key, value in (hot.get('parameters') or {}).items():
template_parameters[key] = YAQL("$." + key)
hot_files_map = {}
for f in files:
file_path = "$resources.string('HotFiles/%s')" % f
hot_files_map[f] = YAQL(file_path)
copy_outputs = []
for key, value in (hot.get('outputs') or {}).items():
@ -232,8 +255,10 @@ class HotPackage(murano.packages.application_package.ApplicationPackage):
{YAQL('$resources'): YAQL("new('io.murano.system.Resources')")},
{YAQL('$template'): YAQL("$resources.yaml(type($this))")},
{YAQL('$parameters'): template_parameters},
{YAQL('$files'): hot_files_map},
YAQL('$stack.setTemplate($template)'),
YAQL('$stack.setParameters($parameters)'),
YAQL('$stack.setFiles($files)'),
YAQL("$reporter.report($this, 'Stack creation has started')"),
{

View File

@ -0,0 +1,51 @@
#
# 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 os
import murano.packages.hot_package
import murano.packages.load_utils as load_utils
import murano.tests.unit.base as test_base
class TestHotPackage(test_base.MuranoTestCase):
def test_heat_files_generated(self):
package_dir = os.path.abspath(
os.path.join(__file__,
'../../test_packages/test.hot.v1.app_with_files')
)
load_utils.load_from_dir(package_dir)
result = murano.packages.hot_package.HotPackage._translate_files(
package_dir)
expected_result = [
"testHeatFile",
"middle_file/testHeatFile",
"middle_file/inner_file/testHeatFile",
"middle_file/inner_file2/testHeatFile"
]
msg = "hot files were not generated correctly"
self.assertEqual(expected_result, result, msg)
def test_heat_files_generated_empty(self):
package_dir = os.path.abspath(
os.path.join(__file__,
'../../test_packages/test.hot.v1.app')
)
load_utils.load_from_dir(package_dir)
result = murano.packages.hot_package.HotPackage._translate_files(
package_dir)
msg = "heat files were not generated correctly. Expected empty dict"
self.assertEqual(result, {}, msg)

View File

@ -0,0 +1,17 @@
Format: Heat.HOT/1.0
Type: Application
FullName: test.hot.v1.app
Name: Test HOT v1 App
Description: Test HOT v1 Application
Author: Test Runner
Tags: [Linux]
Logo: test_logo.png
Supplier:
Name: Supplier Name
CompanyUrl:
Text: Example Company
Link: http://example.com
Logo: test_supplier_logo.png
Summary: Company summary goes here
Description: Marked up company description goes here

View File

@ -0,0 +1,8 @@
heat_template_version: '2013-05-23'
resources:
test-server:
type: OS::Nova::Server
properties:
flavor: test.flavor
image: Some image name
key_name: default

View File

@ -59,6 +59,7 @@ class TestHeatStack(base.MuranoTestCase):
hs._name = 'test-stack'
hs._description = 'Generated by TestHeatStack'
hs._template = {'resources': {'test': 1}}
hs._files = {}
hs._parameters = {}
hs._applied = False
hs._clients = self.client_manager_mock
@ -73,7 +74,8 @@ class TestHeatStack(base.MuranoTestCase):
stack_name='test-stack',
disable_rollback=True,
parameters={},
template=expected_template
template=expected_template,
files={}
)
self.assertTrue(hs._applied)
@ -93,6 +95,7 @@ class TestHeatStack(base.MuranoTestCase):
hs._name = 'test-stack'
hs._description = None
hs._template = {'resources': {'test': 1}}
hs._files = {}
hs._parameters = {}
hs._applied = False
hs.push(None)
@ -105,7 +108,42 @@ class TestHeatStack(base.MuranoTestCase):
stack_name='test-stack',
disable_rollback=True,
parameters={},
template=expected_template
template=expected_template,
files={}
)
self.assertTrue(hs._applied)
def test_heat_files_are_sent(self):
"""Assert that if heat_template_version is omitted, it's added."""
# Note that the 'with x as y, a as b:' syntax was introduced in
# python 2.7, and contextlib.nested was deprecated in py2.7
with mock.patch(MOD_NAME + '.HeatStack._get_status') as status_get:
with mock.patch(MOD_NAME + '.HeatStack._wait_state') as wait_st:
status_get.return_value = 'NOT_FOUND'
wait_st.return_value = {}
hs = heat_stack.HeatStack(self.mock_murano_class,
None, self.mock_object_store, None)
hs._clients = self.client_manager_mock
hs._name = 'test-stack'
hs._description = None
hs._template = {'resources': {'test': 1}}
hs._files = {"heatFile": "file"}
hs._parameters = {}
hs._applied = False
hs.push(None)
expected_template = {
'heat_template_version': '2013-05-23',
'resources': {'test': 1}
}
self.heat_client_mock.stacks.create.assert_called_with(
stack_name='test-stack',
disable_rollback=True,
parameters={},
template=expected_template,
files={"heatFile": "file"}
)
self.assertTrue(hs._applied)