Support multi-project in xjob

1. What is the problem
We utilize an admin user to operate resources in asynchronous job.
Currently the project of this user is pre-configured, so when this
user try to operate resources which do not belong to the configured
project, operation will fail.

2. What is the solution for the problem
During deployment, we create an admin user and assign admin role to
it in each projects that need to be operated. And in the codes, since
now we have project_id in job table, we can use this project_id to
retrieve an admin token, instead of using the pre-configured project_id.

3. What features need to be implemented to the Tricircle to
realize the solution
Support using different project_id according to the operated resources
to get admin token when running asynchronous job.

Change-Id: Id3eb464bc0573f96cecfa4700cc977e328054d10
This commit is contained in:
zhiyuan_cai 2017-05-03 19:42:04 +08:00
parent 92c3256b0a
commit ad5e6940e3
7 changed files with 48 additions and 58 deletions

View File

@ -20,8 +20,8 @@ import six
from six.moves import xrange
import uuid
from keystoneclient.auth import token_endpoint
from keystoneclient import session
import keystoneauth1.identity.generic as auth_identity
from keystoneauth1 import session
from keystoneclient.v3 import client as keystone_client
from oslo_config import cfg
from oslo_log import log as logging
@ -92,6 +92,7 @@ def _safe_operation(operation_name):
try:
service = instance.resource_service_map[resource]
instance._ensure_endpoint_set(context, service)
instance._ensure_token_for_admin(context)
return func(*args, **kwargs)
except exceptions.EndpointNotAvailable as e:
if i == retries:
@ -211,23 +212,23 @@ class Client(object):
resource))
@staticmethod
def _get_keystone_session():
return resource_handle.ResourceHandle.get_keystone_session()
def _get_keystone_session(project_id=None):
return resource_handle.ResourceHandle.get_keystone_session(project_id)
@staticmethod
def get_admin_token():
return Client._get_admin_token()
def get_admin_token(project_id=None):
return Client._get_admin_token(project_id)
@staticmethod
def _get_admin_token():
return Client._get_keystone_session().get_token()
def _get_admin_token(project_id=None):
return Client._get_keystone_session(project_id).get_token()
def _get_admin_project_id(self):
return self._get_keystone_session().get_project_id()
def _get_endpoint_from_keystone(self, cxt):
auth = token_endpoint.Token(cfg.CONF.client.identity_url,
cxt.auth_token)
auth = auth_identity.Token(cfg.CONF.client.auth_url,
cxt.auth_token, tenant_id=cxt.tenant)
sess = session.Session(auth=auth)
cli = keystone_client.Client(session=sess)
@ -292,8 +293,8 @@ class Client(object):
:return: None
"""
if is_internal:
admin_context = tricircle_context.Context()
admin_context.auth_token = self._get_admin_token()
admin_context = tricircle_context.get_admin_context()
self._ensure_token_for_admin(admin_context)
endpoint_map = self._get_endpoint_from_keystone(admin_context)
else:
endpoint_map = self._get_endpoint_from_keystone(cxt)
@ -362,9 +363,17 @@ class Client(object):
def get_keystone_client_by_context(self, ctx):
client_session = self._get_keystone_session()
return keystone_client.Client(auth_url=cfg.CONF.client.identity_url,
return keystone_client.Client(auth_url=cfg.CONF.client.auth_url,
session=client_session)
def _ensure_token_for_admin(self, cxt):
if cxt.is_admin and not cxt.auth_token:
if cxt.tenant:
cxt.auth_token = self._get_admin_token(cxt.tenant)
else:
cxt.auth_token = self._get_admin_token()
cxt.tenant = self._get_admin_project_id()
@_safe_operation('client')
def get_native_client(self, resource, cxt):
"""Get native python client instance
@ -375,10 +384,6 @@ class Client(object):
:param cxt: resource type
:return: client instance
"""
if cxt.is_admin and not cxt.auth_token:
cxt.auth_token = self._get_admin_token()
cxt.tenant = self._get_admin_project_id()
service = self.resource_service_map[resource]
handle = self.service_handle_map[service]
return handle._get_client(cxt)
@ -401,10 +406,6 @@ class Client(object):
:return: list of dict containing resources information
:raises: EndpointNotAvailable
"""
if cxt.is_admin and not cxt.auth_token:
cxt.auth_token = self._get_admin_token()
cxt.tenant = self._get_admin_project_id()
service = self.resource_service_map[resource]
handle = self.service_handle_map[service]
filters = filters or []
@ -435,10 +436,6 @@ class Client(object):
:return: a dict containing resource information
:raises: EndpointNotAvailable
"""
if cxt.is_admin and not cxt.auth_token:
cxt.auth_token = self._get_admin_token()
cxt.tenant = self._get_admin_project_id()
service = self.resource_service_map[resource]
handle = self.service_handle_map[service]
return handle.handle_create(cxt, resource, *args, **kwargs)
@ -464,10 +461,6 @@ class Client(object):
:return: a dict containing resource information
:raises: EndpointNotAvailable
"""
if cxt.is_admin and not cxt.auth_token:
cxt.auth_token = self._get_admin_token()
cxt.tenant = self._get_admin_project_id()
service = self.resource_service_map[resource]
handle = self.service_handle_map[service]
return handle.handle_update(cxt, resource, *args, **kwargs)
@ -486,10 +479,6 @@ class Client(object):
:return: None
:raises: EndpointNotAvailable
"""
if cxt.is_admin and not cxt.auth_token:
cxt.auth_token = self._get_admin_token()
cxt.tenant = self._get_admin_project_id()
service = self.resource_service_map[resource]
handle = self.service_handle_map[service]
return handle.handle_delete(cxt, resource, resource_id)
@ -508,10 +497,6 @@ class Client(object):
:return: a dict containing resource information
:raises: EndpointNotAvailable
"""
if cxt.is_admin and not cxt.auth_token:
cxt.auth_token = self._get_admin_token()
cxt.tenant = self._get_admin_project_id()
service = self.resource_service_map[resource]
handle = self.service_handle_map[service]
return handle.handle_get(cxt, resource, resource_id)
@ -546,10 +531,6 @@ class Client(object):
:return: None
:raises: EndpointNotAvailable
"""
if cxt.is_admin and not cxt.auth_token:
cxt.auth_token = self._get_admin_token()
cxt.tenant = self._get_admin_project_id()
service = self.resource_service_map[resource]
handle = self.service_handle_map[service]
return handle.handle_action(cxt, resource, action, *args, **kwargs)

View File

@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneclient.auth.identity import v3 as auth_identity
from keystoneclient import session
import keystoneauth1.identity.generic as auth_identity
from keystoneauth1 import session
from neutronclient.common import exceptions as q_exceptions
from neutronclient.neutron import client as q_client
@ -62,19 +62,23 @@ class ResourceHandle(object):
self.endpoint_url = url
@staticmethod
def get_keystone_session():
auth = auth_identity.Password(
auth_url=cfg.CONF.client.identity_url,
username=cfg.CONF.client.admin_username,
password=cfg.CONF.client.admin_password,
project_name=cfg.CONF.client.admin_tenant,
user_domain_name=cfg.CONF.client.admin_user_domain_name,
project_domain_name=cfg.CONF.client.admin_tenant_domain_name)
def get_keystone_session(project_id=None):
kwargs = {
'auth_url': cfg.CONF.client.auth_url,
'username': cfg.CONF.client.admin_username,
'password': cfg.CONF.client.admin_password,
'user_domain_name': cfg.CONF.client.admin_user_domain_name,
'project_domain_name': cfg.CONF.client.admin_tenant_domain_name}
if not project_id:
kwargs['project_name'] = cfg.CONF.client.admin_tenant
else:
kwargs['project_id'] = project_id
auth = auth_identity.Password(**kwargs)
return session.Session(auth=auth)
@staticmethod
def get_admin_token():
return ResourceHandle.get_keystone_session().get_token()
def get_admin_token(project_id=None):
return ResourceHandle.get_keystone_session(project_id).get_token()
class NeutronResourceHandle(ResourceHandle):
@ -91,7 +95,7 @@ class NeutronResourceHandle(ResourceHandle):
def _get_client(self, cxt):
token = cxt.auth_token
if not token and cxt.is_admin:
token = self.get_admin_token()
token = self.get_admin_token(cxt.tenant)
return q_client.Client('2.0',
token=token,
auth_url=self.auth_url,

View File

@ -269,7 +269,7 @@ class TricirclePlugin(plugin.Ml2Plugin):
t_ctx = t_context.get_context_from_neutron_context(context)
if self._skip_non_api_query(t_ctx):
return b_networks
t_ctx.auth_token = client.Client.get_admin_token()
t_ctx.auth_token = client.Client.get_admin_token(context.project_id)
raw_client = self.neutron_handle._get_client(t_ctx)
params = self._construct_params(filters, sorts, limit, marker,
page_reverse)

View File

@ -2,7 +2,7 @@
DEST=$BASE/new
DEVSTACK_DIR=$DEST/devstack
source $DEVSTACK_DIR/openrc admin demo
source $DEVSTACK_DIR/openrc admin admin
unset OS_REGION_NAME
openstacktop="openstack --os-region-name CentralRegion"
@ -92,7 +92,7 @@ if [ "$DATABASE_TYPE" == "mysql" ]; then
full_count=$(echo $full_result | grep -o "[0-9]\{1,\}")
if [ $full_count -ne 0 ]; then
echo "Wait for job to finish"
sleep 5
sleep 10
else
break
fi
@ -114,7 +114,7 @@ else
full_count=$(echo $full_result | grep -o "[0-9]\{1,\}")
if [ $full_count -ne 0 ]; then
echo "Wait for job to finish"
sleep 5
sleep 10
else
break
fi

View File

@ -255,6 +255,7 @@ class ClientTest(unittest.TestCase):
api.delete_cached_endpoints(self.context, FAKE_SERVICE_ID)
self.client._get_admin_token = mock.Mock()
self.client._get_admin_project_id = mock.Mock()
self.client._get_endpoint_from_keystone = mock.Mock()
self.client._get_endpoint_from_keystone.return_value = {}
@ -294,6 +295,7 @@ class ClientTest(unittest.TestCase):
update_dict)
self.client._get_admin_token = mock.Mock()
self.client._get_admin_project_id = mock.Mock()
self.client._get_endpoint_from_keystone = mock.Mock()
self.client._get_endpoint_from_keystone.return_value = {}
# retry but still endpoint not updated

View File

@ -159,6 +159,7 @@ class FakeContext(object):
def __init__(self):
self.session = FakeSession()
self.auth_token = 'token'
self.project_id = ''
class FakeClient(object):

View File

@ -259,6 +259,8 @@ class XManager(PeriodicTasks):
'%(job_type)s',
{'status': 'new' if is_new_job else 'failed',
'resource_id': resource_id, 'job_type': job_type})
# this is an admin context, we set the correct project id
ctx.tenant = project_id
if not is_new_job:
db_api.new_job(ctx, project_id, job_type, resource_id)
self.job_handles[job_type](ctx, payload=payload)