From 3de6f73e920a16079b8bead3b94353ae0daf7a6d Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Sun, 26 Mar 2017 20:34:08 -0500 Subject: [PATCH] Fix physical host reservation for non-admin users Originally, Blazar was using its service user to manage objects for physical host reservation, e.g. host aggregates, which by default requires admin rights. Commit 16d5f67ba7020701edbbf09a747f5683b0840c21 started using a dedicated account configured with values climate_username, climate_password, and climate_tenant_name. Commit c9b7307cf3c97d3b48878aca6eda5b7fbc4dcfa7 removed this dedicated account and started using trusts instead, so that operations were performed on behalf of the user creating the lease (with the trustee being the blazar service user). While this works well if users creating leases are admins, non-admin users will get errors because the default Nova policy prevents them from running required operations associated with aggregates and hypervisors. Since it is not clear why a dedicated account for admin operations was required, this patch brings back the approach used before commit 16d5f67ba7020701edbbf09a747f5683b0840c21, which was to use the service account for admin operations. This allows non-admin users to create Blazar leases. The nova client setup is updated to authenticate against Keystone v3. Change-Id: Iad86bb549aec13edd662965d2f91b68c856ae06c Closes-Bug: #1663204 --- README.rst | 2 - blazar/config.py | 6 ++ blazar/plugins/oshosts/host_plugin.py | 7 ++- blazar/plugins/oshosts/nova_inventory.py | 8 ++- blazar/plugins/oshosts/reservation_pool.py | 7 ++- blazar/tests/utils/openstack/test_nova.py | 33 +++++++--- blazar/utils/openstack/nova.py | 70 +++++++++++++++++++--- devstack/plugin.sh | 3 - doc/source/userdoc/installation.guide.rst | 8 +-- 9 files changed, 114 insertions(+), 30 deletions(-) diff --git a/README.rst b/README.rst index 4dfa3e88..7b893b0f 100644 --- a/README.rst +++ b/README.rst @@ -9,8 +9,6 @@ OpenStack Reservation Service Prerequisites ------------- * Keystone v3 API endpoint -* Dedicated account for write operations on behalf of the admin - blazar_username * Service account Configuration diff --git a/blazar/config.py b/blazar/config.py index b7f72ff6..9161b03a 100644 --- a/blazar/config.py +++ b/blazar/config.py @@ -58,6 +58,12 @@ os_opts = [ cfg.StrOpt('os_auth_version', default='v2.0', help='Blazar uses API v3 to allow trusts using.'), + cfg.StrOpt('os_admin_user_domain_name', + default='Default', + help='A domain name the os_admin_username belongs to.'), + cfg.StrOpt('os_admin_project_domain_name', + default='Default', + help='A domain name the os_admin_project_name belongs to') ] CONF = cfg.CONF diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index 510b1bd6..ab22cd0c 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -58,7 +58,12 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper): pool = None def __init__(self): - super(PhysicalHostPlugin, self).__init__() + super(PhysicalHostPlugin, self).__init__( + username=CONF.os_admin_username, + password=CONF.os_admin_password, + user_domain_name=CONF.os_admin_user_domain_name, + project_name=CONF.os_admin_project_name, + project_domain_name=CONF.os_admin_user_domain_name) def create_reservation(self, values): """Create reservation.""" diff --git a/blazar/plugins/oshosts/nova_inventory.py b/blazar/plugins/oshosts/nova_inventory.py index c772533b..8861bf5a 100644 --- a/blazar/plugins/oshosts/nova_inventory.py +++ b/blazar/plugins/oshosts/nova_inventory.py @@ -14,6 +14,7 @@ # limitations under the License. from novaclient import exceptions as nova_exceptions +from oslo_config import cfg from blazar.manager import exceptions as manager_exceptions from blazar.utils.openstack import nova @@ -21,7 +22,12 @@ from blazar.utils.openstack import nova class NovaInventory(nova.NovaClientWrapper): def __init__(self): - super(NovaInventory, self).__init__() + super(NovaInventory, self).__init__( + username=cfg.CONF.os_admin_username, + password=cfg.CONF.os_admin_password, + user_domain_name=cfg.CONF.os_admin_user_domain_name, + project_name=cfg.CONF.os_admin_project_name, + project_domain_name=cfg.CONF.os_admin_user_domain_name) def get_host_details(self, host): """Get Nova capabilities of a single host diff --git a/blazar/plugins/oshosts/reservation_pool.py b/blazar/plugins/oshosts/reservation_pool.py index 213cb685..d347fb34 100644 --- a/blazar/plugins/oshosts/reservation_pool.py +++ b/blazar/plugins/oshosts/reservation_pool.py @@ -52,7 +52,12 @@ CONF.register_opts(OPTS, group=plugin.RESOURCE_TYPE) class ReservationPool(nova.NovaClientWrapper): def __init__(self): - super(ReservationPool, self).__init__() + super(ReservationPool, self).__init__( + username=CONF.os_admin_username, + password=CONF.os_admin_password, + user_domain_name=CONF.os_admin_user_domain_name, + project_name=CONF.os_admin_project_name, + project_domain_name=CONF.os_admin_user_domain_name) self.config = CONF[plugin.RESOURCE_TYPE] self.freepool_name = self.config.aggregate_freepool_name diff --git a/blazar/tests/utils/openstack/test_nova.py b/blazar/tests/utils/openstack/test_nova.py index f777f525..7f5acd7a 100644 --- a/blazar/tests/utils/openstack/test_nova.py +++ b/blazar/tests/utils/openstack/test_nova.py @@ -16,12 +16,15 @@ from keystoneauth1 import session from keystoneauth1 import token_endpoint from novaclient import client as nova_client +from oslo_config import cfg from blazar import context from blazar import tests from blazar.utils.openstack import base from blazar.utils.openstack import nova +CONF = cfg.CONF + class TestCNClient(tests.TestCase): def setUp(self): @@ -43,20 +46,34 @@ class TestCNClient(tests.TestCase): def test_client_from_kwargs(self): self.ctx.side_effect = RuntimeError - self.auth_token = 'fake_token' - self.endpoint = 'fake_endpoint' + endpoint = 'fake_endpoint' + username = 'blazar_admin' + password = 'blazar_password' + user_domain = 'User_Domain' + project_name = 'admin' + project_domain = 'Project_Domain' + auth_url = "%s://%s:%s/v3" % (CONF.os_auth_protocol, + CONF.os_auth_host, + CONF.os_auth_port) kwargs = {'version': self.version, - 'endpoint_override': self.endpoint, - 'auth_token': self.auth_token} + 'endpoint_override': endpoint, + 'username': username, + 'password': password, + 'user_domain_name': user_domain, + 'project_name': project_name, + 'project_domain_name': project_domain} self.nova.BlazarNovaClient(**kwargs) - self.auth.assert_called_once_with(self.endpoint, self.auth_token) - self.session.assert_called_once_with(auth=self.auth.return_value) self.client.assert_called_once_with(version=self.version, - endpoint_override=self.endpoint, - session=self.session.return_value) + username=username, + password=password, + user_domain_name=user_domain, + project_name=project_name, + project_domain_name=project_domain, + auth_url=auth_url, + endpoint_override=endpoint) def test_client_from_ctx(self): kwargs = {'version': self.version} diff --git a/blazar/utils/openstack/nova.py b/blazar/utils/openstack/nova.py index 292a5968..60cd3a4d 100644 --- a/blazar/utils/openstack/nova.py +++ b/blazar/utils/openstack/nova.py @@ -45,24 +45,49 @@ class BlazarNovaClient(object): def __init__(self, **kwargs): """Description - We suppose that in future we may want to use CNC in some places - where context will be available, so we create 2 different ways of - creating client from context(future) and kwargs(we use it now). + BlazarNovaClient can be used in two ways: from context or kwargs. :param version: service client version which we will use :type version: str + :param ctx: request context + :type ctx: context object + :param auth_token: keystone auth token :type auth_token: str :param endpoint_override: endpoint url which we will use :type endpoint_override: str + + :param username: username to use with nova client + :type username: str + + :param password: password to use with nova client + :type password: str + + :param user_domain_name: domain name of the user + :type user_domain_name: str + + :param project_name: project name to use with nova client + :type project_name: str + + :param project_domain_name: domain name of the project + :type project_domain_name: str + + :param auth_url: keystone url to authenticate against + :type auth_url: str """ ctx = kwargs.pop('ctx', None) auth_token = kwargs.pop('auth_token', None) endpoint_override = kwargs.pop('endpoint_override', None) version = kwargs.pop('version', cfg.CONF.nova_client_version) + username = kwargs.pop('username', None) + password = kwargs.pop('password', None) + user_domain_name = kwargs.pop('user_domain_name', None) + project_name = kwargs.pop('project_name', None) + project_domain_name = kwargs.pop('project_domain_name', None) + auth_url = kwargs.pop('auth_url', None) if ctx is None: try: @@ -74,13 +99,29 @@ class BlazarNovaClient(object): endpoint_override = endpoint_override or \ base.url_for(ctx.service_catalog, CONF.compute_service) + auth_url = base.url_for(ctx.service_catalog, CONF.identity_service) - auth = token_endpoint.Token(endpoint_override, - auth_token) - sess = session.Session(auth=auth) + if auth_url is None: + auth_url = "%s://%s:%s/v3" % (CONF.os_auth_protocol, + CONF.os_auth_host, + CONF.os_auth_port) + + if username: + kwargs.setdefault('username', username) + kwargs.setdefault('password', password) + kwargs.setdefault('project_name', project_name) + kwargs.setdefault('auth_url', auth_url) + + if "v2.0" not in auth_url: + kwargs.setdefault('project_domain_name', project_domain_name) + kwargs.setdefault('user_domain_name', user_domain_name) + else: + auth = token_endpoint.Token(endpoint_override, + auth_token) + sess = session.Session(auth=auth) + kwargs.setdefault('session', sess) kwargs.setdefault('endpoint_override', endpoint_override) - kwargs.setdefault('session', sess) kwargs.setdefault('version', version) self.nova = nova_client.Client(**kwargs) @@ -117,8 +158,21 @@ class ServerManager(servers.ServerManager): class NovaClientWrapper(object): + def __init__(self, username=None, password=None, user_domain_name=None, + project_name=None, project_domain_name=None): + self.username = username + self.password = password + self.user_domain_name = user_domain_name + self.project_name = project_name + self.project_domain_name = project_domain_name + @property def nova(self): ctx = context.current() - nova = BlazarNovaClient(ctx=ctx) + nova = BlazarNovaClient(ctx=ctx, + username=self.username, + password=self.password, + user_domain_name=self.user_domain_name, + project_name=self.project_name, + project_domain_name=self.project_domain_name) return nova diff --git a/devstack/plugin.sh b/devstack/plugin.sh index e21b20c9..e097d3ef 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -54,9 +54,6 @@ function configure_blazar { # keystone authtoken _blazar_setup_keystone $BLAZAR_CONF_FILE keystone_authtoken - iniset $BLAZAR_CONF_FILE physical:host blazar_username $BLAZAR_USER_NAME - iniset $BLAZAR_CONF_FILE physical:host blazar_password $SERVICE_PASSWORD - iniset $BLAZAR_CONF_FILE physical:host blazar_project_name $SERVICE_TENANT_NAME iniset $BLAZAR_CONF_FILE physical:host aggregate_freepool_name $BLAZAR_FREEPOOL_NAME iniset $BLAZAR_CONF_FILE DEFAULT host $HOST_IP diff --git a/doc/source/userdoc/installation.guide.rst b/doc/source/userdoc/installation.guide.rst index 8ca193dd..37dd6ed8 100644 --- a/doc/source/userdoc/installation.guide.rst +++ b/doc/source/userdoc/installation.guide.rst @@ -139,9 +139,8 @@ Then edit */etc/blazar/blazar.conf* using the following example: .. -Here *os_admin_** flags refer to the Blazar service user. *blazar_** ones - to -an admin user created specially to work with physical reservations. If you do -not have these users, create them: +*os_admin_** flags refer to the Blazar service user. If you do not have this +user, create it: .. sourcecode:: console @@ -150,9 +149,6 @@ not have these users, create them: .. -And the same procedure for special admin user to work with physical -reservations. - Next you need to configure Nova. If you want to use physical reservations, please add the following lines to nova.conf file: