Merge "use keystoneauth and OpenStack clients"
This commit is contained in:
commit
7d83fcc31d
@ -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
|
||||
|
54
translator/common/flavors.py
Normal file
54
translator/common/flavors.py
Normal file
@ -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
|
81
translator/common/images.py
Normal file
81
translator/common/images.py
Normal file
@ -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
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -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:]
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user