From 9ed0a3d1342ac03487e64c4b5340ba05d693dd1d Mon Sep 17 00:00:00 2001 From: Alvaro Lopez Garcia Date: Tue, 21 Jun 2016 16:44:14 +0200 Subject: [PATCH] use keystoneauth and OpenStack clients The parser should not rely on the OS_* environment variables for authentication, as it will fail with more complex setups, when using the "clouds.yaml" configuration file or when being called through osc with arguments instead of environment variables. Moreover, the client should not use "requests" directly to do GETs to access the nova flavors and glance images, but it should use the existing clients. When used through the python-openstackclient it is possible to force the client to request authentication. The authentication will be handled by the client, so that the parser does not need to worry about it (so that authentication plugins can be used). Then, the existing authentication session can be reused for all the client interaction with OpenStack to fetch the flavor and images information. Change-Id: I9f65e7d46686c37bb44ef18756ebd295ee4961de --- requirements.txt | 4 + translator/common/flavors.py | 54 +++++++ translator/common/images.py | 81 ++++++++++ .../hot/tosca/tests/test_tosca_compute.py | 103 +++++-------- translator/hot/tosca/tosca_compute.py | 139 +----------------- translator/osc/v1/tests/fakes.py | 5 + translator/osc/v1/translate.py | 8 +- translator/shell.py | 121 +++++++-------- translator/tests/test_shell.py | 91 +----------- 9 files changed, 254 insertions(+), 352 deletions(-) create mode 100644 translator/common/flavors.py create mode 100644 translator/common/images.py diff --git a/requirements.txt b/requirements.txt index b9792daf..391f84a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,7 @@ PyYAML>=3.1.0 # MIT python-dateutil>=2.4.2 # BSD six>=1.9.0 # MIT tosca-parser>=0.5.0 # Apache-2.0 +keystoneauth1>=2.7.0 # Apache-2.0 +python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 +python-heatclient>=1.1.0 # Apache-2.0 +python-glanceclient>=2.0.0 # Apache-2.0 diff --git a/translator/common/flavors.py b/translator/common/flavors.py new file mode 100644 index 00000000..543529dd --- /dev/null +++ b/translator/common/flavors.py @@ -0,0 +1,54 @@ +# 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 logging + +# NOTE(aloga): this should be safe. If we do not have the clients, we won't +# have the session below, therefore the clients won't be ever called. +try: + import novaclient.client +except ImportError: + pass + +log = logging.getLogger('heat-translator') + + +_FLAVORS = { + 'm1.xlarge': {'mem_size': 16384, 'disk_size': 160, 'num_cpus': 8}, + 'm1.large': {'mem_size': 8192, 'disk_size': 80, 'num_cpus': 4}, + 'm1.medium': {'mem_size': 4096, 'disk_size': 40, 'num_cpus': 2}, + 'm1.small': {'mem_size': 2048, 'disk_size': 20, 'num_cpus': 1}, + 'm1.tiny': {'mem_size': 512, 'disk_size': 1, 'num_cpus': 1}, + 'm1.micro': {'mem_size': 128, 'disk_size': 0, 'num_cpus': 1}, + 'm1.nano': {'mem_size': 64, 'disk_size': 0, 'num_cpus': 1} +} + +SESSION = None + + +def get_flavors(): + ret = {} + if SESSION is not None: + try: + client = novaclient.client.Client("2", session=SESSION) + except Exception as e: + # Handles any exception coming from openstack + log.warn(_('Choosing predefined flavors since received ' + 'Openstack Exception: %s') % str(e)) + else: + for flv in client.flavors.list(detailed=True): + ret[str(flv.name)] = { + "mem_size": flv.ram, + "disk_size": flv.disk, + "num_cpus": flv.vcpus + } + return ret or _FLAVORS diff --git a/translator/common/images.py b/translator/common/images.py new file mode 100644 index 00000000..9cebcef8 --- /dev/null +++ b/translator/common/images.py @@ -0,0 +1,81 @@ +# 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 logging + +# NOTE(aloga): this should be safe. If we do not have the clients, we won't +# have the session below, therefore the clients won't be ever called. +try: + import glanceclient.client +except ImportError: + pass + +log = logging.getLogger('heat-translator') + + +_IMAGES = { + 'ubuntu-software-config-os-init': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'Ubuntu', + 'version': '14.04'}, + 'ubuntu-12.04-software-config-os-init': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'Ubuntu', + 'version': '12.04'}, + 'fedora-amd64-heat-config': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'Fedora', + 'version': '18.0'}, + 'F18-x86_64-cfntools': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'Fedora', + 'version': '19'}, + 'Fedora-x86_64-20-20131211.1-sda': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'Fedora', + 'version': '20'}, + 'cirros-0.3.1-x86_64-uec': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'CirrOS', + 'version': '0.3.1'}, + 'cirros-0.3.2-x86_64-uec': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'CirrOS', + 'version': '0.3.2'}, + 'rhel-6.5-test-image': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'RHEL', + 'version': '6.5'} +} + +SESSION = None + + +def get_images(): + ret = {} + + if SESSION is not None: + try: + client = glanceclient.client.Client("2", session=SESSION) + except Exception as e: + # Handles any exception coming from openstack + log.warn(_('Choosing predefined images since received ' + 'Openstack Exception: %s') % str(e)) + else: + for image in client.images.list(): + metadata = ["architecture", "type", "distribution", "version"] + if any(key in image.keys() for key in metadata): + ret = [image["name"]] = {} + for key in metadata: + if key in image.keys(): + ret[image["name"]][key] = image[key] + return ret or _IMAGES diff --git a/translator/hot/tosca/tests/test_tosca_compute.py b/translator/hot/tosca/tests/test_tosca_compute.py index d42cdc8f..f31beb5c 100644 --- a/translator/hot/tosca/tests/test_tosca_compute.py +++ b/translator/hot/tosca/tests/test_tosca_compute.py @@ -10,9 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import json import mock -from mock import patch from toscaparser.nodetemplate import NodeTemplate from toscaparser.tests.base import TestCase @@ -198,11 +196,8 @@ class ToscaComputeTest(TestCase): tpl_snippet, expectedprops) - @patch('requests.post') - @patch('requests.get') - @patch('os.getenv') - def test_node_compute_with_nova_flavor(self, mock_os_getenv, - mock_get, mock_post): + @mock.patch('translator.common.flavors.get_flavors') + def test_node_compute_with_nova_flavor(self, mock_flavor): tpl_snippet = ''' node_templates: server: @@ -214,55 +209,19 @@ class ToscaComputeTest(TestCase): disk_size: 1 GB mem_size: 1 GB ''' - with patch('translator.common.utils.' - 'check_for_env_variables') as mock_check_env: - mock_check_env.return_value = True - mock_os_getenv.side_effect = ['demo', 'demo', - 'demo', 'http://abc.com/5000/', - 'demo', 'demo', - 'demo', 'http://abc.com/5000/'] - mock_ks_response = mock.MagicMock() - mock_ks_response.status_code = 200 - mock_ks_content = { - 'access': { - 'token': { - 'id': 'd1dfa603-3662-47e0-b0b6-3ae7914bdf76' - }, - 'serviceCatalog': [{ - 'type': 'compute', - 'endpoints': [{ - 'publicURL': 'http://abc.com' - }] - }] - } - } - mock_ks_response.content = json.dumps(mock_ks_content) - mock_nova_response = mock.MagicMock() - mock_nova_response.status_code = 200 - mock_flavor_content = { - 'flavors': [{ - 'name': 'm1.mock_flavor', - 'ram': 1024, - 'disk': 1, - 'vcpus': 1 - }] - } - mock_nova_response.content = \ - json.dumps(mock_flavor_content) - mock_post.return_value = mock_ks_response - mock_get.return_value = mock_nova_response - expectedprops = {'flavor': 'm1.mock_flavor', - 'image': None, - 'user_data_format': 'SOFTWARE_CONFIG'} - self._tosca_compute_test( - tpl_snippet, - expectedprops) + mock_flavor.return_value = { + 'm1.mock_flavor': { + 'mem_size': 1024, + 'disk_size': 1, + 'num_cpus': 1} + } + expectedprops = {'flavor': 'm1.mock_flavor', + 'image': None, + 'user_data_format': 'SOFTWARE_CONFIG'} + self._tosca_compute_test(tpl_snippet, expectedprops) - @patch('requests.post') - @patch('requests.get') - @patch('os.getenv') - def test_node_compute_without_nova_flavor(self, mock_os_getenv, - mock_get, mock_post): + @mock.patch('translator.common.images.get_images') + def test_node_compute_with_glance_image(self, mock_images): tpl_snippet = ''' node_templates: server: @@ -273,18 +232,24 @@ class ToscaComputeTest(TestCase): num_cpus: 1 disk_size: 1 GB mem_size: 1 GB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fake Distribution + version: 19.0 ''' - with patch('translator.common.utils.' - 'check_for_env_variables') as mock_check_env: - mock_check_env.return_value = True - mock_os_getenv.side_effect = ['demo', 'demo', - 'demo', 'http://abc.com/5000/'] - mock_ks_response = mock.MagicMock() - mock_ks_content = {} - mock_ks_response.content = json.dumps(mock_ks_content) - expectedprops = {'flavor': 'm1.small', - 'image': None, - 'user_data_format': 'SOFTWARE_CONFIG'} - self._tosca_compute_test( - tpl_snippet, - expectedprops) + mock_images.return_value = { + 'fake-image-foobar': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'Fake Distribution', + 'version': '19.0'}, + 'fake-image-foobar-old': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'Fake Distribution', + 'version': '18.0'} + } + expectedprops = {'flavor': 'm1.small', + 'image': 'fake-image-foobar', + 'user_data_format': 'SOFTWARE_CONFIG'} + self._tosca_compute_test(tpl_snippet, expectedprops) diff --git a/translator/hot/tosca/tosca_compute.py b/translator/hot/tosca/tosca_compute.py index b8ad83ca..1e4f061c 100755 --- a/translator/hot/tosca/tosca_compute.py +++ b/translator/hot/tosca/tosca_compute.py @@ -11,68 +11,21 @@ # License for the specific language governing permissions and limitations # under the License. -import json import logging -import requests from toscaparser.utils.gettextutils import _ +from translator.common import flavors as nova_flavors +from translator.common import images as glance_images import translator.common.utils from translator.hot.syntax.hot_resource import HotResource + log = logging.getLogger('heat-translator') # Name used to dynamically load appropriate map class. TARGET_CLASS_NAME = 'ToscaCompute' -# A design issue to be resolved is how to translate the generic TOSCA server -# properties to OpenStack flavors and images. At the Atlanta design summit, -# there was discussion on using Glance to store metadata and Graffiti to -# describe artifacts. We will follow these projects to see if they can be -# leveraged for this TOSCA translation. -# For development purpose at this time, we temporarily hardcode a list of -# flavors and images here -FLAVORS = {'m1.xlarge': {'mem_size': 16384, 'disk_size': 160, 'num_cpus': 8}, - 'm1.large': {'mem_size': 8192, 'disk_size': 80, 'num_cpus': 4}, - 'm1.medium': {'mem_size': 4096, 'disk_size': 40, 'num_cpus': 2}, - 'm1.small': {'mem_size': 2048, 'disk_size': 20, 'num_cpus': 1}, - 'm1.tiny': {'mem_size': 512, 'disk_size': 1, 'num_cpus': 1}, - 'm1.micro': {'mem_size': 128, 'disk_size': 0, 'num_cpus': 1}, - 'm1.nano': {'mem_size': 64, 'disk_size': 0, 'num_cpus': 1}} - -IMAGES = {'ubuntu-software-config-os-init': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Ubuntu', - 'version': '14.04'}, - 'ubuntu-12.04-software-config-os-init': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Ubuntu', - 'version': '12.04'}, - 'fedora-amd64-heat-config': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Fedora', - 'version': '18.0'}, - 'F18-x86_64-cfntools': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Fedora', - 'version': '19'}, - 'Fedora-x86_64-20-20131211.1-sda': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Fedora', - 'version': '20'}, - 'cirros-0.3.1-x86_64-uec': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'CirrOS', - 'version': '0.3.1'}, - 'cirros-0.3.2-x86_64-uec': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'CirrOS', - 'version': '0.3.2'}, - 'rhel-6.5-test-image': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'RHEL', - 'version': '6.5'}} - class ToscaCompute(HotResource): '''Translate TOSCA node type tosca.nodes.Compute.''' @@ -136,88 +89,10 @@ class ToscaCompute(HotResource): hot_properties['image'] = image return hot_properties - def _create_nova_flavor_dict(self): - '''Populates and returns the flavors dict using Nova ReST API''' - try: - access_dict = translator.common.utils.get_ks_access_dict() - access_token = translator.common.utils.get_token_id(access_dict) - if access_token is None: - return None - nova_url = translator.common.utils.get_url_for(access_dict, - 'compute') - if not nova_url: - return None - nova_response = requests.get(nova_url + '/flavors/detail', - headers={'X-Auth-Token': - access_token}) - if nova_response.status_code != 200: - return None - flavors = json.loads(nova_response.content)['flavors'] - flavor_dict = dict() - for flavor in flavors: - flavor_name = str(flavor['name']) - flavor_dict[flavor_name] = { - 'mem_size': flavor['ram'], - 'disk_size': flavor['disk'], - 'num_cpus': flavor['vcpus'], - } - except Exception as e: - # Handles any exception coming from openstack - log.warn(_('Choosing predefined flavors since received ' - 'Openstack Exception: %s') % str(e)) - return None - return flavor_dict - - def _populate_image_dict(self): - '''Populates and returns the images dict using Glance ReST API''' - images_dict = {} - try: - access_dict = translator.common.utils.get_ks_access_dict() - access_token = translator.common.utils.get_token_id(access_dict) - if access_token is None: - return None - glance_url = translator.common.utils.get_url_for(access_dict, - 'image') - if not glance_url: - return None - glance_response = requests.get(glance_url + '/v2/images', - headers={'X-Auth-Token': - access_token}) - if glance_response.status_code != 200: - return None - images = json.loads(glance_response.content)["images"] - for image in images: - image_resp = requests.get(glance_url + '/v2/images/' + - image["id"], - headers={'X-Auth-Token': - access_token}) - if image_resp.status_code != 200: - continue - metadata = ["architecture", "type", "distribution", "version"] - image_data = json.loads(image_resp.content) - if any(key in image_data.keys() for key in metadata): - images_dict[image_data["name"]] = dict() - for key in metadata: - if key in image_data.keys(): - images_dict[image_data["name"]][key] = \ - image_data[key] - else: - continue - - except Exception as e: - # Handles any exception coming from openstack - log.warn(_('Choosing predefined flavors since received ' - 'Openstack Exception: %s') % str(e)) - return images_dict - def _best_flavor(self, properties): log.info(_('Choosing the best flavor for given attributes.')) # Check whether user exported all required environment variables. - flavors = FLAVORS - if translator.common.utils.check_for_env_variables(): - resp = self._create_nova_flavor_dict() - if resp: - flavors = resp + flavors = nova_flavors.get_flavors() # start with all flavors match_all = flavors.keys() @@ -260,11 +135,7 @@ class ToscaCompute(HotResource): def _best_image(self, properties): # Check whether user exported all required environment variables. - images = IMAGES - if translator.common.utils.check_for_env_variables(): - resp = self._populate_image_dict() - if len(resp.keys()) > 0: - images = resp + images = glance_images.get_images() match_all = images.keys() architecture = properties.get(self.ARCHITECTURE) if architecture is None: diff --git a/translator/osc/v1/tests/fakes.py b/translator/osc/v1/tests/fakes.py index a08c3ace..3bab0b76 100644 --- a/translator/osc/v1/tests/fakes.py +++ b/translator/osc/v1/tests/fakes.py @@ -12,6 +12,8 @@ import sys +import mock + class FakeApp(object): def __init__(self): @@ -20,6 +22,9 @@ class FakeApp(object): self.stdout = sys.stdout self.stderr = sys.stderr + self.cloud = mock.Mock() + self.cloud.get_session.return_value = None + class FakeClientManager(object): def __init__(self): diff --git a/translator/osc/v1/translate.py b/translator/osc/v1/translate.py index ef005e2d..eb4de851 100644 --- a/translator/osc/v1/translate.py +++ b/translator/osc/v1/translate.py @@ -21,6 +21,8 @@ from cliff import command from toscaparser.tosca_template import ToscaTemplate from toscaparser.utils.gettextutils import _ +from translator.common import flavors +from translator.common import images from translator.common.utils import UrlUtils from translator.conf.config import ConfigProvider from translator.hot.tosca_translator import TOSCATranslator @@ -35,7 +37,7 @@ class TranslateTemplate(command.Command): """Translate a template""" - auth_required = False + auth_required = True def get_parser(self, prog_name): parser = super(TranslateTemplate, self).get_parser(prog_name) @@ -73,6 +75,10 @@ class TranslateTemplate(command.Command): '(%s).'), parsed_args) output = None + session = self.app.cloud.get_session() + flavors.SESSION = session + images.SESSION = session + if parsed_args.parameter: parsed_params = parsed_args.parameter else: diff --git a/translator/shell.py b/translator/shell.py index d5333bc0..a11d66fd 100644 --- a/translator/shell.py +++ b/translator/shell.py @@ -12,21 +12,28 @@ import argparse -import ast -import json import logging import logging.config import os -import prettytable -import requests import sys import uuid import yaml +# NOTE(aloga): As per upstream developers requirement this needs to work +# without the clients, therefore we need to pass if we cannot import them +try: + import heatclient.client + from keystoneauth1 import loading +except ImportError: + has_clients = False +else: + has_clients = True + from toscaparser.tosca_template import ToscaTemplate from toscaparser.utils.gettextutils import _ from toscaparser.utils.urlutils import UrlUtils -from translator.common import utils +from translator.common import flavors +from translator.common import images from translator.conf.config import ConfigProvider from translator.hot.tosca_translator import TOSCATranslator @@ -55,7 +62,7 @@ class TranslatorShell(object): SUPPORTED_TYPES = ['tosca'] - def get_parser(self): + def get_parser(self, argv): parser = argparse.ArgumentParser(prog="heat-translator") parser.add_argument('--template-file', @@ -91,11 +98,25 @@ class TranslatorShell(object): help=_('Whether to deploy the generated template ' 'or not.')) + self._append_global_identity_args(parser, argv) + return parser + def _append_global_identity_args(self, parser, argv): + if not has_clients: + return + + loading.register_session_argparse_arguments(parser) + + default_auth_plugin = 'password' + if 'os-token' in argv: + default_auth_plugin = 'token' + loading.register_auth_argparse_arguments( + parser, argv, default=default_auth_plugin) + def main(self, argv): - parser = self.get_parser() + parser = self.get_parser(argv) (args, args_list) = parser.parse_known_args(argv) template_file = args.template_file @@ -117,16 +138,28 @@ class TranslatorShell(object): 'validation.') % {'template_file': template_file}) print(msg) else: - heat_tpl = self._translate(template_type, template_file, - parsed_params, a_file, deploy) - if heat_tpl: - if utils.check_for_env_variables() and deploy: - try: - heatclient(heat_tpl, parsed_params) - except Exception: - log.error(_("Unable to launch the heat stack")) + hot = self._translate(template_type, template_file, + parsed_params, a_file, deploy) + if hot and deploy: + if not has_clients: + raise RuntimeError(_('Could not find OpenStack ' + 'clients and libs, aborting ')) - self._write_output(heat_tpl, output_file) + keystone_auth = ( + loading.load_auth_from_argparse_arguments(args) + ) + keystone_session = ( + loading.load_session_from_argparse_arguments( + args, + auth=keystone_auth + ) + ) + images.SESSION = keystone_session + flavors.SESSION = keystone_session + self.deploy_on_heat(keystone_session, keystone_auth, + hot, parsed_params) + + self._write_output(hot, output_file) else: msg = (_('The path %(template_file)s is not a valid ' 'file or URL.') % {'template_file': template_file}) @@ -134,6 +167,20 @@ class TranslatorShell(object): log.error(msg) raise ValueError(msg) + def deploy_on_heat(self, session, auth, template, parameters): + endpoint = auth.get_endpoint(session, service_type="orchestration") + client = heatclient.client.Client('1', + session=session, + auth=auth, + endpoint=endpoint) + + stack_name = "heat_" + str(uuid.uuid4()).split("-")[0] + tpl = yaml.load(template) + tpl['heat_template_version'] = str(tpl['heat_template_version']) + client.stacks.create(stack_name=stack_name, + template=tpl, + parameters=parameters) + def _parse_parameters(self, parameter_list): parsed_inputs = {} @@ -177,48 +224,6 @@ class TranslatorShell(object): print(output) -def heatclient(output, params): - try: - access_dict = utils.get_ks_access_dict() - endpoint = utils.get_url_for(access_dict, 'orchestration') - token = utils.get_token_id(access_dict) - except Exception as e: - log.error(e) - headers = { - 'Content-Type': 'application/json', - 'X-Auth-Token': token - } - heat_stack_name = "heat_" + str(uuid.uuid4()).split("-")[0] - output = yaml.load(output) - output['heat_template_version'] = str(output['heat_template_version']) - data = { - 'stack_name': heat_stack_name, - 'template': output, - 'parameters': params - } - response = requests.post(endpoint + '/stacks', - data=json.dumps(data), - headers=headers) - content = ast.literal_eval(response._content) - if response.status_code == 201: - stack_id = content["stack"]["id"] - get_url = endpoint + '/stacks/' + heat_stack_name + '/' + stack_id - get_stack_response = requests.get(get_url, - headers=headers) - stack_details = json.loads(get_stack_response.content)["stack"] - col_names = ["id", "stack_name", "stack_status", "creation_time", - "updated_time"] - pt = prettytable.PrettyTable(col_names) - stack_list = [] - for col in col_names: - stack_list.append(stack_details[col]) - pt.add_row(stack_list) - print(pt) - else: - err_msg = content["error"]["message"] - log(_("Unable to deploy to Heat\n%s\n") % err_msg) - - def main(args=None): if args is None: args = sys.argv[1:] diff --git a/translator/tests/test_shell.py b/translator/tests/test_shell.py index 62f35100..7f8e11ed 100644 --- a/translator/tests/test_shell.py +++ b/translator/tests/test_shell.py @@ -10,13 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -import ast -import json import os import shutil import tempfile -from mock import patch from toscaparser.common import exception from toscaparser.utils.gettextutils import _ import translator.shell as shell @@ -49,10 +46,7 @@ class ShellTest(TestCase): '--parameters=key')) def test_valid_template(self): - try: - shell.main([self.template_file, self.template_type]) - except Exception: - self.fail(self.failure_msg) + shell.main([self.template_file, self.template_type]) def test_valid_template_without_type(self): try: @@ -100,86 +94,3 @@ class ShellTest(TestCase): shutil.rmtree(temp_dir) self.assertTrue(temp_dir is None or not os.path.exists(temp_dir)) - - @patch('uuid.uuid4') - @patch('translator.common.utils.check_for_env_variables') - @patch('requests.post') - @patch('translator.common.utils.get_url_for') - @patch('translator.common.utils.get_token_id') - @patch('os.getenv') - @patch('translator.hot.tosca.tosca_compute.' - 'ToscaCompute._create_nova_flavor_dict') - @patch('translator.hot.tosca.tosca_compute.' - 'ToscaCompute._populate_image_dict') - def test_template_deploy_with_credentials(self, mock_populate_image_dict, - mock_flavor_dict, - mock_os_getenv, - mock_token, - mock_url, mock_post, - mock_env, - mock_uuid): - mock_uuid.return_value = 'abcXXX-abcXXX' - mock_env.return_value = True - mock_flavor_dict.return_value = { - 'm1.medium': {'mem_size': 4096, 'disk_size': 40, 'num_cpus': 2} - } - mock_populate_image_dict.return_value = { - "rhel-6.5-test-image": { - "version": "6.5", - "architecture": "x86_64", - "distribution": "RHEL", - "type": "Linux" - } - } - mock_url.return_value = 'http://abc.com' - mock_token.return_value = 'mock_token' - mock_os_getenv.side_effect = ['demo', 'demo', - 'demo', 'http://www.abc.com'] - try: - data = { - 'stack_name': 'heat_abcXXX', - 'parameters': {}, - 'template': { - 'outputs': {}, - 'heat_template_version': '2013-05-23', - 'description': 'Template for deploying a single server ' - 'with predefined properties.\n', - 'parameters': {}, - 'resources': { - 'my_server': { - 'type': 'OS::Nova::Server', - 'properties': { - 'flavor': 'm1.medium', - 'user_data_format': 'SOFTWARE_CONFIG', - 'image': 'rhel-6.5-test-image' - } - } - } - } - } - - mock_heat_res = { - "stack": { - "id": 1234 - } - } - headers = { - 'Content-Type': 'application/json', - 'X-Auth-Token': 'mock_token' - } - - class mock_response(object): - def __init__(self, status_code, _content): - self.status_code = status_code - self._content = _content - - mock_response_obj = mock_response(201, json.dumps(mock_heat_res)) - mock_post.return_value = mock_response_obj - shell.main([self.template_file, self.template_type, - "--deploy"]) - args, kwargs = mock_post.call_args - self.assertEqual(args[0], 'http://abc.com/stacks') - self.assertEqual(ast.literal_eval(kwargs['data']), data) - self.assertEqual(kwargs['headers'], headers) - except Exception: - self.fail(self.failure_msg)