diff --git a/kloudbuster/credentials.py b/kloudbuster/credentials.py index 78f1ea8..c1d49db 100644 --- a/kloudbuster/credentials.py +++ b/kloudbuster/credentials.py @@ -30,35 +30,23 @@ class Credentials(object): dct['username'] = self.rc_username dct['password'] = self.rc_password dct['auth_url'] = self.rc_auth_url - dct['tenant_name'] = self.rc_tenant_name - dct['cacert'] = self.rc_cacert - dct['ca_cert'] = self.rc_cacert - return dct - - def get_nova_credentials(self): - dct = {} - dct['username'] = self.rc_username - dct['api_key'] = self.rc_password - dct['auth_url'] = self.rc_auth_url - dct['project_id'] = self.rc_tenant_name - dct['cacert'] = self.rc_cacert - return dct - - def get_nova_credentials_v2(self): - dct = self.get_nova_credentials() - dct['version'] = 2 + if self.rc_identity_api_version == 3: + dct['project_name'] = self.rc_project_name + dct['project_domain_id'] = self.rc_project_domain_id + dct['user_domain_id'] = self.rc_user_domain_id + else: + dct['tenant_name'] = self.rc_tenant_name return dct def _init_with_openrc_(self, openrc_contents): export_re = re.compile('export OS_([A-Z_]*)="?(.*)') for line in openrc_contents.splitlines(): line = line.strip() - mstr = export_re.match(line) + mstr = export_re.match(line.strip()) if mstr: - # get rid of posible trailing double quote + # get rif of posible trailing double quote # the first one was removed by the re - name = mstr.group(1) - value = mstr.group(2) + name, value = mstr.group(1), mstr.group(2) if value.endswith('"'): value = value[:-1] # get rid of password assignment @@ -67,6 +55,11 @@ class Credentials(object): # export OS_PASSWORD=$OS_PASSWORD_INPUT if value.startswith('$'): continue + # Check if api version is provided + # Default is keystone v2 + if name == 'IDENTITY_API_VERSION': + self.rc_identity_api_version = int(value) + # now match against wanted variable names if name == 'USERNAME': self.rc_username = value @@ -74,10 +67,18 @@ class Credentials(object): self.rc_auth_url = value elif name == 'TENANT_NAME': self.rc_tenant_name = value - elif name == 'CACERT': + elif name == "CACERT": self.rc_cacert = value - elif name == 'PASSWORD': + elif name == "REGION_NAME": + self.rc_region_name = value + elif name == "PASSWORD": self.rc_password = value + elif name == "PROJECT_NAME": + self.rc_project_name = value + elif name == "PROJECT_DOMAIN_ID" or name == "PROJECT_DOMAIN_NAME": + self.rc_project_domain_id = value + elif name == "USER_DOMAIN_ID" or name == "USER_DOMAIN_NAME": + self.rc_user_domain_id = value # Read a openrc file and take care of the password # The 2 args are passed from the command line and can be None @@ -86,7 +87,12 @@ class Credentials(object): self.rc_username = None self.rc_tenant_name = None self.rc_auth_url = None - self.rc_cacert = None + self.rc_cacert = False + self.rc_region_name = None + self.rc_project_name = None + self.rc_project_domain_id = None + self.rc_user_domain_id = None + self.rc_identity_api_version = 2 self.openrc_contents = openrc_contents success = True @@ -104,15 +110,37 @@ class Credentials(object): # no openrc file passed - we assume the variables have been # sourced by the calling shell # just check that they are present - for varname in ['OS_USERNAME', 'OS_AUTH_URL', 'OS_TENANT_NAME']: - if varname not in os.environ: - LOG.warning("%s is missing" % varname) - success = False - if success: - self.rc_username = os.environ['OS_USERNAME'] - self.rc_auth_url = os.environ['OS_AUTH_URL'] - self.rc_tenant_name = os.environ['OS_TENANT_NAME'] - self.rc_cacert = os.environ.get('OS_CACERT', None) + if 'OS_IDENTITY_API_VERSION' in os.environ: + self.rc_identity_api_version = int(os.environ['OS_IDENTITY_API_VERSION']) + + if self.rc_identity_api_version == 2: + for varname in ['OS_USERNAME', 'OS_AUTH_URL', 'OS_TENANT_NAME']: + if varname not in os.environ: + LOG.warning('%s is missing', varname) + success = False + if success: + self.rc_username = os.environ['OS_USERNAME'] + self.rc_auth_url = os.environ['OS_AUTH_URL'] + self.rc_tenant_name = os.environ['OS_TENANT_NAME'] + + if 'OS_REGION_NAME' in os.environ: + self.rc_region_name = os.environ['OS_REGION_NAME'] + + elif self.rc_identity_api_version == 3: + for varname in ['OS_USERNAME', 'OS_AUTH_URL', 'OS_PROJECT_NAME', + 'OS_PROJECT_DOMAIN_ID', 'OS_USER_DOMAIN_ID']: + if varname not in os.environ: + LOG.warning('%s is missing', varname) + success = False + if success: + self.rc_username = os.environ['OS_USERNAME'] + self.rc_auth_url = os.environ['OS_AUTH_URL'] + self.rc_project_name = os.environ['OS_PROJECT_NAME'] + self.rc_project_domain_id = os.environ['OS_PROJECT_DOMAIN_ID'] + self.rc_user_domain_id = os.environ['OS_USER_DOMAIN_ID'] + + if 'OS_CACERT' in os.environ: + self.rc_cacert = os.environ['OS_CACERT'] # always override with CLI argument if provided if pwd: diff --git a/kloudbuster/force_cleanup.py b/kloudbuster/force_cleanup.py index a5bb104..43889d8 100755 --- a/kloudbuster/force_cleanup.py +++ b/kloudbuster/force_cleanup.py @@ -56,8 +56,10 @@ import time # openstack python clients import cinderclient -import keystoneauth1 -from keystoneclient.v2_0 import client as keystoneclient +from keystoneclient.auth.identity import v2 as keystone_v2 +from keystoneclient.auth.identity import v3 as keystone_v3 +from keystoneclient import client as keystoneclient +from keystoneclient import session import neutronclient from novaclient.exceptions import NotFound from tabulate import tabulate @@ -137,12 +139,12 @@ class AbstractCleaner(object): pass class StorageCleaner(AbstractCleaner): - def __init__(self, creds, resources, dryrun): - from cinderclient.v2 import client as cclient - from novaclient.client import Client as nclient - creden_nova = creds.get_nova_credentials_v2() - self.nova = nclient(endpoint_type='publicURL', **creden_nova) - self.cinder = cclient.Client(endpoint_type='publicURL', **creden_nova) + def __init__(self, sess, resources, dryrun): + from cinderclient import client as cclient + from novaclient import client as nclient + + self.nova = nclient.Client('2', endpoint_type='publicURL', session=sess) + self.cinder = cclient.Client('2', endpoint_type='publicURL', session=sess) res_desc = {'volumes': [self.cinder.volumes.list, {"all_tenants": 1}]} super(StorageCleaner, self).__init__('Storage', res_desc, resources, dryrun) @@ -218,13 +220,11 @@ class StorageCleaner(AbstractCleaner): pass class ComputeCleaner(AbstractCleaner): - def __init__(self, creds, resources, dryrun): - from neutronclient.v2_0 import client as nclient - from novaclient.client import Client as novaclient - creden = creds.get_credentials() - creden_nova = creds.get_nova_credentials_v2() - self.neutron_client = nclient.Client(endpoint_type='publicURL', **creden) - self.nova_client = novaclient(endpoint_type='publicURL', **creden_nova) + def __init__(self, sess, resources, dryrun): + from neutronclient.neutron import client as nclient + from novaclient import client as novaclient + self.neutron_client = nclient.Client('2.0', endpoint_type='publicURL', session=sess) + self.nova_client = novaclient.Client('2', endpoint_type='publicURL', session=sess) res_desc = { 'instances': [self.nova_client.servers.list, {"all_tenants": 1}], 'flavors': [self.nova_client.flavors.list], @@ -321,10 +321,9 @@ class ComputeCleaner(AbstractCleaner): class NetworkCleaner(AbstractCleaner): - def __init__(self, creds, resources, dryrun): - from neutronclient.v2_0 import client as nclient - creden = creds.get_credentials() - self.neutron = nclient.Client(endpoint_type='publicURL', **creden) + def __init__(self, sess, resources, dryrun): + from neutronclient.neutron import client as nclient + self.neutron = nclient.Client('2.0', endpoint_type='publicURL', session=sess) # because the response has an extra level of indirection # we need to extract it to present the list of network or router objects @@ -430,12 +429,13 @@ class NetworkCleaner(AbstractCleaner): class KeystoneCleaner(AbstractCleaner): - def __init__(self, creds, resources, dryrun): - crd = creds.get_credentials() - self.keystone = keystoneclient.Client(endpoint_type='publicURL', **crd) + def __init__(self, sess, resources, dryrun): + self.keystone = keystoneclient.Client(endpoint_type='publicURL', session=sess) + self.tenant_api = self.keystone.tenants \ + if self.keystone.version == 'v2.0' else self.keystone.projects res_desc = { 'users': [self.keystone.users.list], - 'tenants': [self.keystone.tenants.list] + 'tenants': [self.tenant_api.list] } super(KeystoneCleaner, self).__init__('Keystone', res_desc, resources, dryrun) @@ -449,7 +449,7 @@ class KeystoneCleaner(AbstractCleaner): else: self.keystone.users.delete(id) self.report_deletion('USER', name) - except keystoneauth1.exceptions.http.NotFound: + except keystoneclient.auth.exceptions.http.NotFound: self.report_not_found('USER', name) except KeyError: pass @@ -458,21 +458,27 @@ class KeystoneCleaner(AbstractCleaner): for id, name in self.resources['tenants'].iteritems(): try: if self.dryrun: - self.keystone.tenants.get(id) + self.tenant_api.get(id) else: - self.keystone.tenants.delete(id) + self.tenant_api.delete(id) self.report_deletion('TENANT', name) - except keystoneauth1.exceptions.http.NotFound: + except keystoneclient.auth.exceptions.http.NotFound: self.report_not_found('TENANT', name) except KeyError: pass class KbCleaners(object): - def __init__(self, cred, resources, dryrun): + def __init__(self, creds_obj, resources, dryrun): self.cleaners = [] + creds = creds_obj.get_credentials() + if creds_obj.rc_identity_api_version == 3: + auth = keystone_v3.Password(**creds) + else: + auth = keystone_v2.Password(**creds) + sess = session.Session(auth=auth, verify=creds_obj.rc_cacert) for cleaner_type in [StorageCleaner, ComputeCleaner, NetworkCleaner, KeystoneCleaner]: - self.cleaners.append(cleaner_type(cred, resources, dryrun)) + self.cleaners.append(cleaner_type(sess, resources, dryrun)) def show_resources(self): table = [["Type", "Name", "UUID"]] diff --git a/kloudbuster/kloudbuster.py b/kloudbuster/kloudbuster.py index 594830b..6e5ca06 100755 --- a/kloudbuster/kloudbuster.py +++ b/kloudbuster/kloudbuster.py @@ -25,8 +25,8 @@ import webbrowser import base_compute import base_network +from glanceclient import client as glanceclient import glanceclient.exc as glance_exception -from glanceclient.v1 import client as glanceclient from kb_config import KBConfig from kb_res_logger import KBResLogger from kb_runner_base import KBException @@ -35,9 +35,12 @@ from kb_runner_multicast import KBRunner_Multicast from kb_runner_storage import KBRunner_Storage from kb_scheduler import KBScheduler import kb_vm_agent -from keystoneclient.v2_0 import client as keystoneclient +from keystoneclient.auth.identity import v2 as keystone_v2 +from keystoneclient.auth.identity import v3 as keystone_v3 +from keystoneclient import client as keystoneclient +from keystoneclient import session import log as logging -from novaclient.client import Client as novaclient +from novaclient import client as novaclient from oslo_config import cfg import pbr.version from pkg_resources import resource_filename @@ -53,12 +56,18 @@ __version__ = pbr.version.VersionInfo('kloudbuster').version_string_with_vcs() class KBVMCreationException(Exception): pass -def create_keystone_client(creds): +def create_auth_session(creds_obj): """ - Return the keystone client and auth URL given a credential + Return the authenticated session """ - creds = creds.get_credentials() - return keystoneclient.Client(endpoint_type='publicURL', **creds) + creds = creds_obj.get_credentials() + if creds_obj.rc_identity_api_version == 3: + auth = keystone_v3.Password(**creds) + else: + auth = keystone_v2.Password(**creds) + sess = session.Session(auth=auth, verify=creds_obj.rc_cacert) + + return sess class Kloud(object): def __init__(self, scale_cfg, cred, reusing_tenants, @@ -69,8 +78,7 @@ class Kloud(object): self.reusing_tenants = reusing_tenants self.storage_mode = storage_mode self.multicast_mode = multicast_mode - self.cred = cred - self.keystone = create_keystone_client(cred) + self.osclient_session = create_auth_session(cred) self.flavor_to_use = None self.vm_up_count = 0 self.res_logger = KBResLogger() @@ -85,6 +93,9 @@ class Kloud(object): if scale_cfg['availability_zone'] else None self.exc_info = None + self.keystone = keystoneclient.Client(session=self.osclient_session, + endpoint_type='publicURL') + LOG.info("Creating kloud: " + self.prefix) if self.placement_az: LOG.info('%s Availability Zone: %s' % (self.name, self.placement_az)) @@ -288,9 +299,9 @@ class KloudBuster(object): def get_hypervisor_list(self, cred): ret_list = [] - creden_nova = cred.get_nova_credentials_v2() - nova_client = novaclient(endpoint_type='publicURL', - http_log_debug=True, **creden_nova) + sess = create_auth_session(cred) + nova_client = novaclient('2', endpoint_type='publicURL', + http_log_debug=True, session=sess) for hypervisor in nova_client.hypervisors.list(): if vars(hypervisor)['status'] == 'enabled': ret_list.append(vars(hypervisor)['hypervisor_hostname']) @@ -299,9 +310,9 @@ class KloudBuster(object): def get_az_list(self, cred): ret_list = [] - creden_nova = cred.get_nova_credentials_v2() - nova_client = novaclient(endpoint_type='publicURL', - http_log_debug=True, **creden_nova) + sess = create_auth_session(cred) + nova_client = novaclient('2', endpoint_type='publicURL', + http_log_debug=True, session=sess) for az in nova_client.availability_zones.list(): zoneName = vars(az)['zoneName'] isAvail = vars(az)['zoneState']['available'] @@ -312,21 +323,14 @@ class KloudBuster(object): def check_and_upload_images(self, retry_count=150): retry = 0 - creds_list = [ - {'keystone': create_keystone_client(self.server_cred), 'cred': self.server_cred}, - {'keystone': create_keystone_client(self.client_cred), 'cred': self.client_cred} - ] + creds_list = [create_auth_session(self.server_cred), + create_auth_session(self.client_cred)] creds_dict = dict(zip(['Server kloud', 'Client kloud'], creds_list)) img_name_dict = dict(zip(['Server kloud', 'Client kloud'], [self.server_cfg.image_name, self.client_cfg.image_name])) - for kloud, creds in creds_dict.items(): - keystone = creds['keystone'] - cacert = creds['cred'].get_credentials()['cacert'] - glance_endpoint = keystone.service_catalog.url_for( - service_type='image', endpoint_type='publicURL') - glance_client = glanceclient.Client( - glance_endpoint, token=keystone.auth_token, cacert=cacert) + for kloud, sess in creds_dict.items(): + glance_client = glanceclient.Client('1', session=sess) try: # Search for the image img = glance_client.images.list(filters={'name': img_name_dict[kloud]}).next() diff --git a/kloudbuster/tenant.py b/kloudbuster/tenant.py index 2035b85..d418d75 100644 --- a/kloudbuster/tenant.py +++ b/kloudbuster/tenant.py @@ -36,6 +36,8 @@ class Tenant(object): self.kloud = kloud self.res_logger = kloud.res_logger self.tenant_name = tenant_name + self.tenant_api = self.kloud.keystone.tenants \ + if self.kloud.keystone.version == 'v2.0' else self.kloud.keystone.projects if not self.kloud.reusing_tenants: self.tenant_object = self._get_tenant() self.tenant_id = self.tenant_object.id @@ -58,9 +60,10 @@ class Tenant(object): try: LOG.info("Creating tenant: " + self.tenant_name) tenant_object = \ - self.kloud.keystone.tenants.create(tenant_name=self.tenant_name, - description="KloudBuster tenant", - enabled=True) + self.tenant_api.create(self.tenant_name, + domain="default", + description="KloudBuster tenant", + enabled=True) return tenant_object except keystone_exception.Conflict as exc: # ost likely the entry already exists: @@ -68,7 +71,7 @@ class Tenant(object): if exc.http_status != 409: raise exc LOG.info("Tenant %s already present, reusing it" % self.tenant_name) - return self.kloud.keystone.tenants.find(name=self.tenant_name) + return self.tenant_api.find(name=self.tenant_name) # Should never come here raise Exception() @@ -122,6 +125,6 @@ class Tenant(object): if not self.reusing_users: # Delete the tenant (self) - self.kloud.keystone.tenants.delete(self.tenant_id) + self.tenant_api.delete(self.tenant_id) return flag diff --git a/kloudbuster/users.py b/kloudbuster/users.py index 3fa1a32..4af9eb6 100644 --- a/kloudbuster/users.py +++ b/kloudbuster/users.py @@ -16,10 +16,10 @@ import sys import base_compute import base_network -from cinderclient.v2 import client as cinderclient +from cinderclient import client as cinderclient from keystoneclient import exceptions as keystone_exception import log as logging -from neutronclient.v2_0 import client as neutronclient +from neutronclient.neutron import client as neutronclient from novaclient import client as novaclient LOG = logging.getLogger(__name__) @@ -66,9 +66,12 @@ class User(object): if not self.tenant.reusing_users: self.user = self._get_user() current_role = self.tenant.kloud.keystone.roles.find(name=user_role) - self.tenant.kloud.keystone.roles.add_user_role(self.user, - current_role, - tenant.tenant_id) + if self.tenant.kloud.keystone.version == 'v2.0': + self.tenant.kloud.keystone.roles.add_user_role( + self.user, current_role, tenant.tenant_id) + else: + self.tenant.kloud.keystone.roles.grant( + current_role, user=self.user, project=tenant.tenant_id) else: # Only admin can retrive the object via Keystone API self.user = None @@ -190,24 +193,15 @@ class User(object): 1. Creates the routers 2. Creates the neutron and nova client objects """ - # Create a new neutron client for this User with correct credentials - creden = self.tenant.kloud.cred.get_credentials() - creden['username'] = self.user_name - creden['password'] = self.password - creden['tenant_name'] = self.tenant.tenant_name + session = self.tenant.kloud.osclient_session - # Create the neutron client to be used for all operations - self.neutron_client = neutronclient.Client(endpoint_type='publicURL', **creden) - - # Create a new nova and cinder client for this User with correct credentials - creden_nova = self.tenant.kloud.cred.get_nova_credentials_v2() - creden_nova['username'] = self.user_name - creden_nova['api_key'] = self.password - creden_nova['project_id'] = self.tenant.tenant_name - - self.nova_client = novaclient.Client(endpoint_type='publicURL', - http_log_debug=True, **creden_nova) - self.cinder_client = cinderclient.Client(endpoint_type='publicURL', **creden_nova) + # Create nova/neutron/cinder clients to be used for all operations + self.neutron_client = neutronclient.Client('2.0', endpoint_type='publicURL', + session=session) + self.nova_client = novaclient.Client('2', endpoint_type='publicURL', + http_log_debug=True, session=session) + self.cinder_client = cinderclient.Client('2', endpoint_type='publicURL', + session=session) if self.tenant.kloud.reusing_tenants: self.check_resources_quota()