Add support for keystone v2 - v3
Ospurge can be used with keystone v2 and v3 with the new authentication plugin (Session), the old one is deprecated. Change-Id: Ife87da432f3f56c13679e6a93c8571620c635615
This commit is contained in:
parent
328f6d23c8
commit
aa909316b7
|
@ -21,8 +21,11 @@
|
|||
import logging
|
||||
import time
|
||||
|
||||
from keystoneclient import exceptions as api_exceptions
|
||||
from keystoneclient.v2_0 import client as keystone_client
|
||||
from keystoneauth1 import exceptions as api_exceptions
|
||||
from keystoneauth1.identity import generic as keystone_auth
|
||||
from keystoneauth1 import session as keystone_session
|
||||
from keystoneclient import client as keystone_client
|
||||
from keystoneclient import exceptions as keystone_exceptions
|
||||
|
||||
from ospurge import constants
|
||||
from ospurge import exceptions
|
||||
|
@ -74,31 +77,46 @@ class Session(object):
|
|||
"""
|
||||
|
||||
def __init__(self, username, password, project_id, auth_url,
|
||||
endpoint_type="publicURL", region_name=None, insecure=False):
|
||||
client = keystone_client.Client(
|
||||
username=username, password=password, tenant_id=project_id,
|
||||
auth_url=auth_url, region_name=region_name, insecure=insecure)
|
||||
endpoint_type="publicURL", insecure=False, **kwargs):
|
||||
|
||||
data = {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'project_id': project_id,
|
||||
'user_domain_id': kwargs.get('user_domain_id'),
|
||||
'user_domain_name': kwargs.get('user_domain_name'),
|
||||
'project_domain_id': kwargs.get('project_domain_id'),
|
||||
'project_domain_name': kwargs.get('project_domain_name'),
|
||||
'domain_id': kwargs.get('domain_id')
|
||||
}
|
||||
|
||||
auth = keystone_auth.Password(auth_url, **data)
|
||||
session = keystone_session.Session(auth=auth, verify=(not insecure))
|
||||
self.client = keystone_client.Client(session=session)
|
||||
|
||||
# Storing username, password, project_id and auth_url for
|
||||
# use by clients libraries that cannot use an existing token.
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.project_id = project_id
|
||||
self.project_id = auth.auth_ref.project_id
|
||||
self.auth_url = auth_url
|
||||
self.region_name = region_name
|
||||
self.region_name = kwargs['region_name']
|
||||
self.insecure = insecure
|
||||
# Session variables to be used by clients when possible
|
||||
self.token = client.auth_token
|
||||
self.user_id = client.user_id
|
||||
self.project_name = client.project_name
|
||||
self.token = auth.auth_ref.auth_token
|
||||
self.user_id = auth.auth_ref.user_id
|
||||
self.project_name = self.client.project_name
|
||||
self.keystone_session = session
|
||||
self.endpoint_type = endpoint_type
|
||||
self.catalog = client.service_catalog.get_endpoints()
|
||||
self.catalog = auth.auth_ref.service_catalog.get_endpoints()
|
||||
|
||||
try:
|
||||
# Detect if we are admin or not
|
||||
client.roles.list() # Only admins are allowed to do this
|
||||
self.client.roles.list() # Only admins are allowed to do this
|
||||
except (
|
||||
# The Exception Depends on OpenStack Infrastructure.
|
||||
api_exceptions.Forbidden,
|
||||
api_exceptions.ConnectionRefused, # admin URL not permitted
|
||||
keystone_exceptions.ConnectionRefused, # admin URL not permitted
|
||||
api_exceptions.Unauthorized,
|
||||
):
|
||||
self.is_admin = False
|
||||
|
@ -107,7 +125,10 @@ class Session(object):
|
|||
|
||||
def get_endpoint(self, service_type):
|
||||
try:
|
||||
return self.catalog[service_type][0][self.endpoint_type]
|
||||
if self.client.version == "v2.0":
|
||||
return self.catalog[service_type][0][self.endpoint_type]
|
||||
else:
|
||||
return self.catalog[service_type][0]['url']
|
||||
except (KeyError, IndexError):
|
||||
# Endpoint could not be found
|
||||
raise exceptions.EndpointNotFound(service_type)
|
||||
|
|
|
@ -37,8 +37,10 @@ import glanceclient.exc
|
|||
from glanceclient.v1 import client as glance_client
|
||||
from heatclient import client as heat_client
|
||||
import heatclient.openstack.common.apiclient.exceptions
|
||||
from keystoneclient import exceptions as api_exceptions
|
||||
from keystoneclient.v2_0 import client as keystone_client
|
||||
from keystoneauth1 import exceptions as api_exceptions
|
||||
from keystoneauth1.identity import generic as keystone_auth
|
||||
from keystoneauth1 import session as keystone_session
|
||||
from keystoneclient import client as keystone_client
|
||||
import neutronclient.common.exceptions
|
||||
from neutronclient.v2_0 import client as neutron_client
|
||||
from novaclient import client as nova_client
|
||||
|
@ -57,12 +59,15 @@ class SwiftResources(base.Resources):
|
|||
super(SwiftResources, self).__init__(session)
|
||||
self.endpoint = self.session.get_endpoint("object-store")
|
||||
self.token = self.session.token
|
||||
conn = swift_client.HTTPConnection(self.endpoint, insecure=self.session.insecure)
|
||||
conn = swift_client.HTTPConnection(self.endpoint,
|
||||
insecure=self.session.insecure)
|
||||
self.http_conn = conn.parsed_url, conn
|
||||
|
||||
# This method is used to retrieve Objects as well as Containers.
|
||||
def list_containers(self):
|
||||
containers = swift_client.get_account(self.endpoint, self.token, http_conn=self.http_conn)[1]
|
||||
containers = swift_client.get_account(self.endpoint,
|
||||
self.token,
|
||||
http_conn=self.http_conn)[1]
|
||||
return (cont['name'] for cont in containers)
|
||||
|
||||
|
||||
|
@ -72,7 +77,10 @@ class SwiftObjects(SwiftResources):
|
|||
swift_objects = []
|
||||
for cont in self.list_containers():
|
||||
objs = [{'container': cont, 'name': obj['name']} for obj in
|
||||
swift_client.get_container(self.endpoint, self.token, cont, http_conn=self.http_conn)[1]]
|
||||
swift_client.get_container(self.endpoint,
|
||||
self.token,
|
||||
cont,
|
||||
http_conn=self.http_conn)[1]]
|
||||
swift_objects.extend(objs)
|
||||
return swift_objects
|
||||
|
||||
|
@ -103,13 +111,8 @@ class CinderResources(base.Resources):
|
|||
|
||||
def __init__(self, session):
|
||||
super(CinderResources, self).__init__(session)
|
||||
# Cinder client library can't use an existing token. When
|
||||
# using this library, we have to reauthenticate.
|
||||
self.client = cinder_client.Client(
|
||||
session.username, session.password,
|
||||
session.project_name, session.auth_url, session.insecure,
|
||||
endpoint_type=session.endpoint_type,
|
||||
region_name=session.region_name)
|
||||
self.client = cinder_client.Client("2.1",
|
||||
session=session.keystone_session)
|
||||
|
||||
|
||||
class CinderSnapshots(CinderResources):
|
||||
|
@ -163,11 +166,7 @@ class NeutronResources(base.Resources):
|
|||
|
||||
def __init__(self, session):
|
||||
super(NeutronResources, self).__init__(session)
|
||||
self.client = neutron_client.Client(
|
||||
username=session.username, password=session.password,
|
||||
tenant_id=session.project_id, auth_url=session.auth_url,
|
||||
endpoint_type=session.endpoint_type,
|
||||
region_name=session.region_name, insecure=session.insecure)
|
||||
self.client = neutron_client.Client(session=session.keystone_session)
|
||||
self.project_id = session.project_id
|
||||
|
||||
# This method is used for routers and interfaces removal
|
||||
|
@ -443,11 +442,8 @@ class NovaServers(base.Resources):
|
|||
|
||||
def __init__(self, session):
|
||||
super(NovaServers, self).__init__(session)
|
||||
self.client = nova_client.Client(
|
||||
"2", session.username, session.password,
|
||||
session.project_name, auth_url=session.auth_url,
|
||||
endpoint_type=session.endpoint_type,
|
||||
region_name=session.region_name, insecure=session.insecure)
|
||||
self.client = nova_client.Client("2.1",
|
||||
session=session.keystone_session)
|
||||
self.project_id = session.project_id
|
||||
|
||||
"""Manage nova resources"""
|
||||
|
@ -542,14 +538,38 @@ class KeystoneManager(object):
|
|||
"""Manages Keystone queries."""
|
||||
|
||||
def __init__(self, username, password, project, auth_url, insecure,
|
||||
admin_role_name, **kwargs):
|
||||
self.client = keystone_client.Client(
|
||||
username=username, password=password,
|
||||
tenant_name=project, auth_url=auth_url,
|
||||
insecure=insecure, **kwargs)
|
||||
self.admin_role_name = admin_role_name
|
||||
**kwargs):
|
||||
data = {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'project_name': project,
|
||||
}
|
||||
|
||||
if kwargs['user_domain_name'] is not None:
|
||||
if kwargs['project_domain_name'] is None:
|
||||
kwargs['project_domain_name'] = 'Default'
|
||||
data.update({
|
||||
'domain_id': kwargs.get('domain_id'),
|
||||
'project_domain_id': kwargs.get('project_domain_id'),
|
||||
'project_domain_name': kwargs.get('project_domain_name'),
|
||||
'user_domain_id': kwargs.get('user_domain_id'),
|
||||
'user_domain_name': kwargs.get('user_domain_name')
|
||||
})
|
||||
|
||||
self.auth = keystone_auth.Password(auth_url, **data)
|
||||
session = keystone_session.Session(auth=self.auth, verify=(not insecure))
|
||||
self.client = keystone_client.Client(session=session)
|
||||
|
||||
self.admin_role_id = None
|
||||
self.tenant_info = None
|
||||
self.admin_role_name = kwargs['admin_role_name']
|
||||
self.user_id = self.auth.auth_ref.user_id
|
||||
|
||||
@property
|
||||
def client_projects(self):
|
||||
if self.client.version == "v2.0":
|
||||
return self.client.tenants
|
||||
return self.client.projects
|
||||
|
||||
def get_project_id(self, project_name_or_id=None):
|
||||
"""Get a project by its id
|
||||
|
@ -558,35 +578,34 @@ class KeystoneManager(object):
|
|||
* ID of current project if called without parameter,
|
||||
* ID of project given as parameter if one is given.
|
||||
"""
|
||||
|
||||
if project_name_or_id is None:
|
||||
return self.client.tenant_id
|
||||
return self.auth.auth_ref.project_id
|
||||
|
||||
try:
|
||||
self.tenant_info = self.client.tenants.get(project_name_or_id)
|
||||
self.tenant_info = self.client_projects.get(project_name_or_id)
|
||||
# If it doesn't raise an 404, project_name_or_id is
|
||||
# already the project's id
|
||||
project_id = project_name_or_id
|
||||
except api_exceptions.NotFound:
|
||||
try:
|
||||
# Can raise api_exceptions.Forbidden:
|
||||
tenants = self.client.tenants.list()
|
||||
tenants = self.client_projects.list()
|
||||
project_id = filter(
|
||||
lambda x: x.name == project_name_or_id, tenants)[0].id
|
||||
except IndexError:
|
||||
raise exceptions.NoSuchProject(project_name_or_id)
|
||||
|
||||
if not self.tenant_info:
|
||||
self.tenant_info = self.client.tenants.get(project_id)
|
||||
self.tenant_info = self.client_projects.get(project_id)
|
||||
return project_id
|
||||
|
||||
def enable_project(self, project_id):
|
||||
logging.info("* Enabling project {}.".format(project_id))
|
||||
self.tenant_info = self.client.tenants.update(project_id, enabled=True)
|
||||
self.tenant_info = self.client_projects.update(project_id, enabled=True)
|
||||
|
||||
def disable_project(self, project_id):
|
||||
logging.info("* Disabling project {}.".format(project_id))
|
||||
self.tenant_info = self.client.tenants.update(project_id, enabled=False)
|
||||
self.tenant_info = self.client_projects.update(project_id, enabled=False)
|
||||
|
||||
def get_admin_role_id(self):
|
||||
if not self.admin_role_id:
|
||||
|
@ -595,35 +614,45 @@ class KeystoneManager(object):
|
|||
return self.admin_role_id
|
||||
|
||||
def become_project_admin(self, project_id):
|
||||
user_id = self.client.user_id
|
||||
user_id = self.user_id
|
||||
admin_role_id = self.get_admin_role_id()
|
||||
logging.info("* Granting role admin to user {} on project {}.".format(
|
||||
user_id, project_id))
|
||||
|
||||
return self.client.roles.add_user_role(user_id, admin_role_id, project_id)
|
||||
if self.client.version == "v2.0":
|
||||
return self.client.roles.add_user_role(user_id, admin_role_id,
|
||||
project_id)
|
||||
else:
|
||||
return self.client.roles.grant(role=admin_role_id, user=user_id,
|
||||
project=project_id)
|
||||
|
||||
def undo_become_project_admin(self, project_id):
|
||||
user_id = self.client.user_id
|
||||
user_id = self.user_id
|
||||
admin_role_id = self.get_admin_role_id()
|
||||
logging.info("* Removing role admin to user {} on project {}.".format(
|
||||
user_id, project_id))
|
||||
|
||||
return self.client.roles.remove_user_role(user_id, admin_role_id, project_id)
|
||||
if self.client.version == "v2.0":
|
||||
return self.client.roles.remove_user_role(user_id,
|
||||
admin_role_id,
|
||||
project_id)
|
||||
else:
|
||||
return self.client.roles.revoke(role=admin_role_id,
|
||||
user=user_id,
|
||||
project=project_id)
|
||||
|
||||
def delete_project(self, project_id):
|
||||
logging.info("* Deleting project {}.".format(project_id))
|
||||
self.client.tenants.delete(project_id)
|
||||
self.client_projects.delete(project_id)
|
||||
|
||||
|
||||
def perform_on_project(admin_name, password, project, auth_url,
|
||||
endpoint_type='publicURL', region_name=None,
|
||||
action='dump', insecure=False):
|
||||
endpoint_type='publicURL', action='dump',
|
||||
insecure=False, **kwargs):
|
||||
"""Perform provided action on all resources of project.
|
||||
|
||||
action can be: 'purge' or 'dump'
|
||||
"""
|
||||
session = base.Session(admin_name, password, project, auth_url,
|
||||
endpoint_type, region_name, insecure)
|
||||
endpoint_type, insecure, **kwargs)
|
||||
error = None
|
||||
for rc in constants.RESOURCES_CLASSES:
|
||||
try:
|
||||
|
@ -701,7 +730,7 @@ def parse_args():
|
|||
help="The user's password. Defaults "
|
||||
"to env[OS_PASSWORD].")
|
||||
parser.add_argument("--admin-project", action=EnvDefault,
|
||||
envvar='OS_TENANT_NAME', required=True,
|
||||
envvar='OS_TENANT_NAME', required=False,
|
||||
help="Project name used for authentication. This project "
|
||||
"will be purged if --own-project is set. "
|
||||
"Defaults to env[OS_TENANT_NAME].")
|
||||
|
@ -711,6 +740,26 @@ def parse_args():
|
|||
envvar='OS_AUTH_URL', required=True,
|
||||
help="Authentication URL. Defaults to "
|
||||
"env[OS_AUTH_URL].")
|
||||
parser.add_argument("--user-domain-id", action=EnvDefault,
|
||||
envvar='OS_USER_DOMAIN_ID', required=False,
|
||||
help="User Domain ID. Defaults to "
|
||||
"env[OS_USER_DOMAIN_ID].")
|
||||
parser.add_argument("--user-domain-name", action=EnvDefault,
|
||||
envvar='OS_USER_DOMAIN_NAME', required=False,
|
||||
help="User Domain ID. Defaults to "
|
||||
"env[OS_USER_DOMAIN_NAME].")
|
||||
parser.add_argument("--project-name", action=EnvDefault,
|
||||
envvar='OS_PROJECT_NAME', required=False,
|
||||
help="Project Name. Defaults to "
|
||||
"env[OS_PROJECT_NAME].")
|
||||
parser.add_argument("--project-domain-id", action=EnvDefault,
|
||||
envvar='OS_PROJECT_DOMAIN_ID', required=False,
|
||||
help="Project Domain ID. Defaults to "
|
||||
"env[OS_PROJECT_DOMAIN_ID].")
|
||||
parser.add_argument("--project-domain-name", action=EnvDefault,
|
||||
envvar='OS_PROJECT_DOMAIN_NAME', required=False,
|
||||
help="Project Domain NAME. Defaults to "
|
||||
"env[OS_PROJECT_DOMAIN_NAME].")
|
||||
parser.add_argument("--cleanup-project", required=False, default=None,
|
||||
help="ID or Name of project to purge. Not required "
|
||||
"if --own-project has been set. Using --cleanup-project "
|
||||
|
@ -733,6 +782,8 @@ def parse_args():
|
|||
if args.cleanup_project and args.own_project:
|
||||
parser.error('Both --cleanup-project '
|
||||
'and --own-project can not be set')
|
||||
if not (args.admin_project or args.project_name):
|
||||
parser.error('--admin-project or --project-name is required')
|
||||
return args
|
||||
|
||||
|
||||
|
@ -745,11 +796,20 @@ def main():
|
|||
# Set default log level to Warning
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
data = {
|
||||
'region_name': args.region_name,
|
||||
'user_domain_id': args.user_domain_id,
|
||||
'project_domain_id': args.project_domain_id,
|
||||
'project_domain_name': args.project_domain_name,
|
||||
'user_domain_name': args.user_domain_name,
|
||||
'admin_role_name': args.admin_role_name
|
||||
}
|
||||
project = args.admin_project if args.admin_project else args.project_name
|
||||
|
||||
try:
|
||||
keystone_manager = KeystoneManager(args.username, args.password,
|
||||
args.admin_project, args.auth_url,
|
||||
args.insecure, region_name=args.region_name,
|
||||
admin_role_name=args.admin_role_name)
|
||||
project, args.auth_url,
|
||||
args.insecure, **data)
|
||||
except api_exceptions.Unauthorized as exc:
|
||||
print("Authentication failed: {}".format(str(exc)))
|
||||
sys.exit(constants.AUTHENTICATION_FAILED_ERROR_CODE)
|
||||
|
@ -786,8 +846,8 @@ def main():
|
|||
try:
|
||||
action = "dump" if args.dry_run else "purge"
|
||||
perform_on_project(args.username, args.password, cleanup_project_id,
|
||||
args.auth_url, args.endpoint_type, args.region_name,
|
||||
action, args.insecure)
|
||||
args.auth_url, args.endpoint_type, action,
|
||||
args.insecure, **data)
|
||||
except requests.exceptions.ConnectionError as exc:
|
||||
print("Connection error: {}".format(str(exc)))
|
||||
sys.exit(constants.CONNECTION_ERROR_CODE)
|
||||
|
|
|
@ -62,6 +62,8 @@ class HttpTest(testtools.TestCase):
|
|||
httpretty.register_uri(method, url, **kwargs)
|
||||
|
||||
def stub_auth(self):
|
||||
self.stub_url('GET', base_url=AUTH_URL,
|
||||
json=client_fixtures.AUTH_URL_RESPONSE)
|
||||
self.stub_url('POST', parts=['tokens'], base_url=AUTH_URL,
|
||||
json=client_fixtures.PROJECT_SCOPED_TOKEN)
|
||||
self.stub_url('GET', parts=['roles'],
|
||||
|
@ -75,7 +77,8 @@ class SessionTest(HttpTest):
|
|||
def test_init(self):
|
||||
self.stub_auth()
|
||||
session = base.Session(USERNAME, PASSWORD,
|
||||
client_fixtures.PROJECT_ID, AUTH_URL)
|
||||
client_fixtures.PROJECT_ID, AUTH_URL,
|
||||
region_name="RegionOne")
|
||||
self.assertEqual(session.token, client_fixtures.TOKEN_ID)
|
||||
self.assertEqual(session.user_id, client_fixtures.USER_ID)
|
||||
self.assertEqual(session.project_id, client_fixtures.PROJECT_ID)
|
||||
|
@ -85,7 +88,8 @@ class SessionTest(HttpTest):
|
|||
def test_get_public_endpoint(self):
|
||||
self.stub_auth()
|
||||
session = base.Session(USERNAME, PASSWORD,
|
||||
client_fixtures.PROJECT_ID, AUTH_URL)
|
||||
client_fixtures.PROJECT_ID, AUTH_URL,
|
||||
region_name="RegionOne")
|
||||
endpoint = session.get_endpoint('volume')
|
||||
self.assertEqual(endpoint, client_fixtures.VOLUME_PUBLIC_ENDPOINT)
|
||||
endpoint = session.get_endpoint('image')
|
||||
|
@ -94,8 +98,10 @@ class SessionTest(HttpTest):
|
|||
@httpretty.activate
|
||||
def test_get_internal_endpoint(self):
|
||||
self.stub_auth()
|
||||
session = base.Session(USERNAME, PASSWORD, client_fixtures.PROJECT_ID,
|
||||
AUTH_URL, endpoint_type='internalURL')
|
||||
session = base.Session(USERNAME, PASSWORD,
|
||||
client_fixtures.PROJECT_ID, AUTH_URL,
|
||||
region_name="RegionOne",
|
||||
endpoint_type='internalURL')
|
||||
endpoint = session.get_endpoint('volume')
|
||||
self.assertEqual(endpoint, client_fixtures.VOLUME_INTERNAL_ENDPOINT)
|
||||
endpoint = session.get_endpoint('image')
|
||||
|
@ -112,7 +118,8 @@ class TestResourcesBase(HttpTest):
|
|||
super(TestResourcesBase, self).setUp()
|
||||
self.stub_auth()
|
||||
self.session = base.Session(USERNAME, PASSWORD,
|
||||
client_fixtures.PROJECT_ID, AUTH_URL)
|
||||
client_fixtures.PROJECT_ID, AUTH_URL,
|
||||
region_name="RegionOne")
|
||||
# We can't add other stubs in subclasses setUp because
|
||||
# httpretty.dactivate() is called after this set_up (so during the
|
||||
# super call to this method in subclasses). and extra stubs will not
|
||||
|
|
Loading…
Reference in New Issue