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 16d5f67ba7
started using a dedicated account configured with values
climate_username, climate_password, and climate_tenant_name. Commit
c9b7307cf3 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
16d5f67ba7, 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
This commit is contained in:
Pierre Riteau 2017-03-26 20:34:08 -05:00 committed by Masahito Muroi
parent 124ac0557a
commit 3de6f73e92
9 changed files with 114 additions and 30 deletions

View File

@ -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

View File

@ -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

View File

@ -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."""

View File

@ -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

View File

@ -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

View File

@ -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}

View File

@ -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)
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

View File

@ -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

View File

@ -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: