diff --git a/doc/source/tools/shaker-cleanup.txt b/doc/source/tools/shaker-cleanup.txt index cc12aa5..d77d20e 100644 --- a/doc/source/tools/shaker-cleanup.txt +++ b/doc/source/tools/shaker-cleanup.txt @@ -10,6 +10,7 @@ usage: shaker-cleanup [-h] [--cleanup-on-error] [--config-dir DIR] [--nowatch-log-file] [--os-auth-url <auth-url>] [--os-cacert <auth-cacert>] [--os-insecure] [--os-password <auth-password>] + [--os-project-name <auth-project-name>] [--os-region-name <auth-region-name>] [--os-tenant-name <auth-tenant-name>] [--os-username <auth-username>] @@ -84,6 +85,10 @@ optional arguments: defaults to env[OS_INSECURE]. --os-password <auth-password> Authentication password, defaults to env[OS_PASSWORD]. + --os-project-name <auth-project-name> + Another way to specify tenant name. This option is + mutually exclusive with --os-tenant-name. Defaults to + env[OS_PROJECT_NAME]. --os-region-name <auth-region-name> Authentication region name, defaults to env[OS_REGION_NAME]. diff --git a/doc/source/tools/shaker-image-builder.txt b/doc/source/tools/shaker-image-builder.txt index 08a7cb2..4ef8e04 100644 --- a/doc/source/tools/shaker-image-builder.txt +++ b/doc/source/tools/shaker-image-builder.txt @@ -12,6 +12,7 @@ usage: shaker-image-builder [-h] [--cleanup-on-error] [--config-dir DIR] [--nowatch-log-file] [--os-auth-url <auth-url>] [--os-cacert <auth-cacert>] [--os-insecure] [--os-password <auth-password>] + [--os-project-name <auth-project-name>] [--os-region-name <auth-region-name>] [--os-tenant-name <auth-tenant-name>] [--os-username <auth-username>] @@ -86,6 +87,10 @@ optional arguments: defaults to env[OS_INSECURE]. --os-password <auth-password> Authentication password, defaults to env[OS_PASSWORD]. + --os-project-name <auth-project-name> + Another way to specify tenant name. This option is + mutually exclusive with --os-tenant-name. Defaults to + env[OS_PROJECT_NAME]. --os-region-name <auth-region-name> Authentication region name, defaults to env[OS_REGION_NAME]. diff --git a/doc/source/tools/shaker.txt b/doc/source/tools/shaker.txt index 330dde1..3a13bad 100644 --- a/doc/source/tools/shaker.txt +++ b/doc/source/tools/shaker.txt @@ -10,6 +10,7 @@ usage: shaker [-h] [--agent-join-timeout AGENT_JOIN_TIMEOUT] [--noverbose] [--nowatch-log-file] [--os-auth-url <auth-url>] [--os-cacert <auth-cacert>] [--os-insecure] [--os-password <auth-password>] + [--os-project-name <auth-project-name>] [--os-region-name <auth-region-name>] [--os-tenant-name <auth-tenant-name>] [--os-username <auth-username>] [--output OUTPUT] @@ -99,6 +100,10 @@ optional arguments: defaults to env[OS_INSECURE]. --os-password <auth-password> Authentication password, defaults to env[OS_PASSWORD]. + --os-project-name <auth-project-name> + Another way to specify tenant name. This option is + mutually exclusive with --os-tenant-name. Defaults to + env[OS_PROJECT_NAME]. --os-region-name <auth-region-name> Authentication region name, defaults to env[OS_REGION_NAME]. diff --git a/etc/shaker.conf b/etc/shaker.conf index 44f3d49..606d0d6 100644 --- a/etc/shaker.conf +++ b/etc/shaker.conf @@ -112,6 +112,10 @@ # Authentication tenant name, defaults to env[OS_TENANT_NAME]. (string value) #os_tenant_name = +# Another way to specify tenant name. This option is mutually exclusive with +# --os-tenant-name. Defaults to env[OS_PROJECT_NAME]. (string value) +#os_project_name = + # Authentication username, defaults to env[OS_USERNAME]. (string value) #os_username = diff --git a/requirements.txt b/requirements.txt index 2489b3e..c326504 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,25 +2,26 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.6 +pbr>=1.6 # Apache-2.0 -iso8601>=0.1.9 -Jinja2>=2.6 # BSD License (3 clause) -oslo.concurrency>=2.3.0 # Apache-2.0 -oslo.config>=2.6.0 # Apache-2.0 -oslo.i18n>=1.5.0 # Apache-2.0 -oslo.log>=1.12.0 # Apache-2.0 +iso8601>=0.1.9 # MIT +Jinja2>=2.8 # BSD License (3 clause) +keystoneauth1>=2.1.0 # Apache-2.0 +os-client-config>=1.13.1 # Apache-2.0 +oslo.concurrency>=3.5.0 # Apache-2.0 +oslo.config>=3.9.0 # Apache-2.0 +oslo.i18n>=2.1.0 # Apache-2.0 +oslo.log>=1.14.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 -oslo.utils!=2.6.0,>=2.4.0 # Apache-2.0 -psutil<2.0.0,>=1.1.1 +oslo.utils>=3.5.0 # Apache-2.0 +psutil<2.0.0,>=1.1.1 # BSD pygal pykwalify -python-glanceclient>=0.18.0 -python-keystoneclient!=1.8.0,>=1.6.0 -python-neutronclient>=2.6.0 -python-novaclient!=2.33.0,>=2.29.0 -python-heatclient>=0.6.0 -python-subunit>=0.0.18 -PyYAML>=3.1.0 +python-glanceclient>=2.0.0 # Apache-2.0 +python-neutronclient>=4.2.0 # Apache-2.0 +python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 +python-heatclient>=0.6.0 # Apache-2.0 +python-subunit>=0.0.18 # Apache-2.0/BSD +PyYAML>=3.1.0 # MIT pyzmq>=15.2.0 -six>=1.9.0 +six>=1.9.0 # MIT diff --git a/shaker/engine/config.py b/shaker/engine/config.py index b66e9c7..da075d4 100644 --- a/shaker/engine/config.py +++ b/shaker/engine/config.py @@ -78,6 +78,12 @@ OPENSTACK_OPTS = [ sample_default='', help='Authentication tenant name, defaults to ' 'env[OS_TENANT_NAME].'), + cfg.StrOpt('os-project-name', metavar='<auth-project-name>', + default=utils.env('OS_PROJECT_NAME'), + sample_default='', + help='Another way to specify tenant name. This option is ' + 'mutually exclusive with --os-tenant-name. ' + 'Defaults to env[OS_PROJECT_NAME].'), cfg.StrOpt('os-username', metavar='<auth-username>', default=utils.env('OS_USERNAME'), sample_default='', diff --git a/shaker/engine/deploy.py b/shaker/engine/deploy.py index 03c2106..a237afd 100644 --- a/shaker/engine/deploy.py +++ b/shaker/engine/deploy.py @@ -220,15 +220,11 @@ class Deployment(object): self.has_stack = False self.privileged_mode = True - def connect_to_openstack(self, os_username, os_password, os_tenant_name, - os_auth_url, os_region_name, external_net, - flavor_name, image_name, os_cacert, os_insecure): + def connect_to_openstack(self, openstack_params, flavor_name, image_name, + external_net): LOG.debug('Connecting to OpenStack') - self.openstack_client = openstack.OpenStackClient( - username=os_username, password=os_password, - tenant_name=os_tenant_name, auth_url=os_auth_url, - region_name=os_region_name, cacert=os_cacert, insecure=os_insecure) + self.openstack_client = openstack.OpenStackClient(openstack_params) self.flavor_name = flavor_name self.image_name = image_name diff --git a/shaker/engine/image_builder.py b/shaker/engine/image_builder.py index 5386ae7..4109210 100644 --- a/shaker/engine/image_builder.py +++ b/shaker/engine/image_builder.py @@ -34,23 +34,14 @@ def init(): utils.init_config_and_logging( config.OPENSTACK_OPTS + config.IMAGE_BUILDER_OPTS) - openstack_client = None + openstack_params = utils.pack_openstack_params(cfg.CONF) try: - openstack_client = openstack.OpenStackClient( - username=cfg.CONF.os_username, password=cfg.CONF.os_password, - tenant_name=cfg.CONF.os_tenant_name, auth_url=cfg.CONF.os_auth_url, - region_name=cfg.CONF.os_region_name, cacert=cfg.CONF.os_cacert, - insecure=cfg.CONF.os_insecure - ) + return openstack.OpenStackClient(openstack_params) except Exception as e: - LOG.error('Error establishing connection to OpenStack: %s. ' - 'Please verify OpenStack credentials (--os-username, ' - '--os-password, --os-tenant-name, --os-auth-url, ' - '--os-cacert, --os-insecure)', e) + LOG.error('Failed to connect to OpenStack: %s. ' + 'Please verify parameters: %s', e, openstack_params) exit(1) - return openstack_client - def build_image(): openstack_client = init() diff --git a/shaker/engine/server.py b/shaker/engine/server.py index 4df4bae..809d853 100644 --- a/shaker/engine/server.py +++ b/shaker/engine/server.py @@ -136,7 +136,7 @@ def execute(output, quorum, execution, agents, matrix=None): def _under_openstack(): required = ['os_username', 'os_password', 'os_tenant_name', 'os_auth_url'] for param in required: - if param not in cfg.CONF or not cfg.CONF[param]: + if param not in cfg.CONF: return False return True @@ -149,12 +149,14 @@ def play_scenario(scenario): deployment = deploy.Deployment() if _under_openstack(): - deployment.connect_to_openstack( - cfg.CONF.os_username, cfg.CONF.os_password, - cfg.CONF.os_tenant_name, cfg.CONF.os_auth_url, - cfg.CONF.os_region_name, cfg.CONF.external_net, - cfg.CONF.flavor_name, cfg.CONF.image_name, - cfg.CONF.os_cacert, cfg.CONF.os_insecure) + openstack_params = utils.pack_openstack_params(cfg.CONF) + try: + deployment.connect_to_openstack( + openstack_params, cfg.CONF.flavor_name, + cfg.CONF.image_name, cfg.CONF.external_net) + except Exception as e: + LOG.warning('Failed to connect to OpenStack: %s. Please ' + 'verify parameters: %s', e, openstack_params) base_dir = os.path.dirname(scenario['file_name']) scenario_deployment = scenario.get('deployment', {}) @@ -192,8 +194,7 @@ def play_scenario(scenario): record = dict(id=utils.make_record_id(), status='interrupted') else: error_msg = 'Error while executing scenario: %s' % e - LOG.error(error_msg) - LOG.exception(e) + LOG.error(error_msg, exc_info=True) record = dict(id=utils.make_record_id(), status='error', stderr=error_msg) output['records'][record['id']] = record diff --git a/shaker/engine/utils.py b/shaker/engine/utils.py index 304110b..89c20e0 100644 --- a/shaker/engine/utils.py +++ b/shaker/engine/utils.py @@ -57,8 +57,12 @@ def init_config_and_logging(opts): conf.register_cli_opts(opts) conf.register_opts(opts) logging.register_options(conf) - logging.set_defaults( - default_log_levels=conf.default_log_levels + ['pykwalify=INFO']) + + # requests to OpenStack services should be visible at DEBUG level + default_log_levels = [l for l in conf.default_log_levels + if not l.startswith('keystoneauth')] + default_log_levels += ['pykwalify=INFO'] + logging.set_defaults(default_log_levels=default_log_levels) try: conf(project='shaker') @@ -250,3 +254,17 @@ def copy_value_by_path(src, src_param, dst, dst_param): set_value_by_path(dst, dst_param, v) return True return False + + +def pack_openstack_params(conf): + params = dict(auth=dict(username=cfg.CONF.os_username, + password=cfg.CONF.os_password, + auth_url=cfg.CONF.os_auth_url), + os_region_name=cfg.CONF.os_region_name, + os_cacert=cfg.CONF.os_cacert, + os_insecure=cfg.CONF.os_insecure) + if cfg.CONF.os_tenant_name: + params['auth']['tenant_name'] = cfg.CONF.os_tenant_name + if cfg.CONF.os_project_name: + params['auth']['project_name'] = cfg.CONF.os_project_name + return params diff --git a/shaker/openstack/clients/glance.py b/shaker/openstack/clients/glance.py index 9029d71..65b2db3 100644 --- a/shaker/openstack/clients/glance.py +++ b/shaker/openstack/clients/glance.py @@ -13,21 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from glanceclient import client as glance_client_pkg - - -GLANCE_VERSION = '1' - - -def create_client(keystone_client, os_region_name, cacert, insecure): - image_api_url = keystone_client.service_catalog.url_for( - service_type='image', region_name=os_region_name) - return glance_client_pkg.Client(GLANCE_VERSION, - endpoint=image_api_url, - token=keystone_client.auth_token, - cacert=cacert, - insecure=insecure) - def get_image(glance_client, image_name): for image in glance_client.images.list(): diff --git a/shaker/openstack/clients/heat.py b/shaker/openstack/clients/heat.py index 85673b5..fab76ba 100644 --- a/shaker/openstack/clients/heat.py +++ b/shaker/openstack/clients/heat.py @@ -15,26 +15,12 @@ import time -from heatclient import client as heat_client_pkg from oslo_log import log as logging LOG = logging.getLogger(__name__) -HEAT_VERSION = '1' - - -def create_client(keystone_client, os_region_name, cacert, insecure): - orchestration_api_url = keystone_client.service_catalog.url_for( - service_type='orchestration', region_name=os_region_name) - return heat_client_pkg.Client(HEAT_VERSION, - endpoint=orchestration_api_url, - token=keystone_client.auth_token, - ca_file=cacert, - insecure=insecure) - - def create_stack(heat_client, stack_name, template, parameters): stack_params = { 'stack_name': stack_name, diff --git a/shaker/openstack/clients/keystone.py b/shaker/openstack/clients/keystone.py deleted file mode 100644 index 0163eff..0000000 --- a/shaker/openstack/clients/keystone.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2015 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 keystoneclient.auth.identity import v2 as auth_v2 -from keystoneclient import discover as keystone_discover -from keystoneclient import session -from keystoneclient.v2_0 import client as keystone_v2 -from keystoneclient.v3 import client as keystone_v3 - - -def create_keystone_client(**kwargs): - discover = keystone_discover.Discover(**kwargs) - for version_data in discover.version_data(): - version = version_data["version"] - if version[0] <= 2: - return keystone_v2.Client(**kwargs) - elif version[0] == 3: - return keystone_v3.Client(**kwargs) - raise Exception( - 'Failed to discover keystone version for url %(auth_url)s.', **kwargs) - - -def create_keystone_session(cacert, insecure, **kwargs): - auth = auth_v2.Password(**kwargs) - return session.Session(auth=auth, cert=cacert, verify=not insecure) diff --git a/shaker/openstack/clients/neutron.py b/shaker/openstack/clients/neutron.py index bf63f70..4f20d78 100644 --- a/shaker/openstack/clients/neutron.py +++ b/shaker/openstack/clients/neutron.py @@ -13,22 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from neutronclient.neutron import client as neutron_client_pkg from oslo_log import log as logging LOG = logging.getLogger(__name__) -NEUTRON_VERSION = '2.0' - - -def create_client(keystone_session, os_region_name): - return neutron_client_pkg.Client(NEUTRON_VERSION, - session=keystone_session, - region_name=os_region_name) - - def choose_external_net(neutron_client): ext_nets = neutron_client.list_networks( **{'router:external': True})['networks'] diff --git a/shaker/openstack/clients/nova.py b/shaker/openstack/clients/nova.py index 38836be..c1ab930 100644 --- a/shaker/openstack/clients/nova.py +++ b/shaker/openstack/clients/nova.py @@ -23,18 +23,11 @@ from oslo_log import log as logging LOG = logging.getLogger(__name__) -NOVA_VERSION = '2' - class ForbiddenException(nova_client_pkg.exceptions.Forbidden): pass -def create_client(keystone_session, os_region_name): - return nova_client_pkg.Client(NOVA_VERSION, session=keystone_session, - region_name=os_region_name) - - def get_available_compute_nodes(nova_client): try: return [dict(host=svc.host, zone=svc.zone) diff --git a/shaker/openstack/clients/openstack.py b/shaker/openstack/clients/openstack.py index ff091c4..208114c 100644 --- a/shaker/openstack/clients/openstack.py +++ b/shaker/openstack/clients/openstack.py @@ -13,90 +13,27 @@ # See the License for the specific language governing permissions and # limitations under the License. -import functools -import time - -from shaker.openstack.clients import glance -from shaker.openstack.clients import heat -from shaker.openstack.clients import keystone -from shaker.openstack.clients import neutron -from shaker.openstack.clients import nova +import os_client_config +from oslo_log import log as logging -# As of now only Nova and Neutron clients support Keystone sessions. -# Thus the only way to create clients is from keystone client instance -# and auth token. The token gets expired in an hour and there is no -# way to automatically refresh it. So the current implementation is to -# recreate keystone client from scratch - -MODERN_CLIENT_MAKERS = { - 'neutron': neutron.create_client, - 'nova': nova.create_client, -} -OLD_CLIENT_MAKERS = { - 'glance': glance.create_client, - 'heat': heat.create_client, -} - -KEYSTONE_AUTH_EXPIRATION = 60 - - -class OpenStackClientProxy(object): - def __init__(self, keystone_creator, client_creator): - self.keystone_creator = keystone_creator - self.client_creator = client_creator - self.last_update_time = 0 - - def __getattribute__(self, name): - if name in ['keystone_creator', 'client_creator', - 'client', 'last_update_time']: - return super(OpenStackClientProxy, self).__getattribute__(name) - else: - now = int(time.time()) - if now > self.last_update_time + KEYSTONE_AUTH_EXPIRATION: - self.last_update_time = now - self.client = self.client_creator( - keystone_client=self.keystone_creator()) - return self.client.__getattribute__(name) +LOG = logging.getLogger(__name__) class OpenStackClient(object): - def __init__(self, username, password, tenant_name, auth_url, region_name, - cacert, insecure): - self.region_name = region_name or 'RegionOne' - self.cacert = cacert or '' - self.insecure = insecure or False - self._osc_cache = {} - self.keystone_creator = functools.partial( - keystone.create_keystone_client, - username=username, password=password, - tenant_name=tenant_name, auth_url=auth_url, cacert=cacert, - insecure=insecure) - self.session_creator = functools.partial( - keystone.create_keystone_session, cacert, - username=username, password=password, - tenant_name=tenant_name, auth_url=auth_url, - insecure=insecure) - # ping OpenStack - self.keystone_creator() + def __init__(self, openstack_params): + LOG.debug('Establishing connection to OpenStack') - def __getattribute__(self, name): - if name != '_osc_cache' and name in self._osc_cache: - return self._osc_cache[name] + config = os_client_config.OpenStackConfig() + cloud_config = config.get_one_cloud(**openstack_params) - client = None - if name in MODERN_CLIENT_MAKERS: - session = self.session_creator() - client = MODERN_CLIENT_MAKERS[name](session, self.region_name) - elif name in OLD_CLIENT_MAKERS: - client_creator = functools.partial( - OLD_CLIENT_MAKERS[name], os_region_name=self.region_name, - cacert=self.cacert, insecure=self.insecure) - client = OpenStackClientProxy(self.keystone_creator, - client_creator) + self.keystone_session = cloud_config.get_session() + self.nova = cloud_config.get_legacy_client('compute') + self.neutron = cloud_config.get_legacy_client('network') + self.glance = cloud_config.get_legacy_client('image') + self.heat = cloud_config.get_legacy_client('orchestration') - if client: - self._osc_cache[name] = client - return client - else: - return super(OpenStackClient, self).__getattribute__(name) + # Ping OpenStack + self.keystone_session.get_token() + + LOG.info('Connection to OpenStack is initialized') diff --git a/shaker/tests/test_server.py b/shaker/tests/test_server.py index b8e4099..7939580 100644 --- a/shaker/tests/test_server.py +++ b/shaker/tests/test_server.py @@ -184,10 +184,13 @@ class TestServerPlayScenario(testtools.TestCase): deploy_obj.deploy.assert_called_once_with( self.deployment, base_dir='folder', server_endpoint='127.0.0.1:5999') + openstack_params = dict( + auth=dict(username='user', password='password', + tenant_name='tenant', auth_url='auth-url'), + os_region_name='RegionOne', + os_cacert=None, os_insecure=False) deploy_obj.connect_to_openstack.assert_called_once_with( - 'user', 'password', 'tenant', 'auth-url', 'RegionOne', None, - 'shaker-flavor', 'shaker-image', None, False - ) + openstack_params, 'shaker-flavor', 'shaker-image', None) deploy_obj.cleanup.assert_called_once_with() @mock.patch('shaker.engine.deploy.Deployment') diff --git a/test-requirements.txt b/test-requirements.txt index aaa7f58..a339e79 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,12 +3,12 @@ # process, which may cause wedges in the gate later. # Hacking already pins down pep8, pyflakes and flake8 -coverage>=3.6 +coverage>=3.6 # Apache-2.0 hacking<0.11,>=0.10 -mock>=1.2 +mock>=1.2 # BSD oslotest>=1.10.0 # Apache-2.0 -sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 -sphinxcontrib-httpdomain +sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD +sphinxcontrib-httpdomain # BSD sphinx_rtd_theme -testrepository>=0.0.18 -testtools>=1.4.0 +testrepository>=0.0.18 # Apache-2.0/BSD +testtools>=1.4.0 # MIT