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
This commit is contained in:
parent
63d6003b3d
commit
9ed0a3d134
@ -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