class AccountDriver(): user_manager = None image_manager = None network_manager = None core_provider = None MASTER_RULES_LIST = [ ("ICMP", -1, 255), #FTP Access ("UDP", 20, 20), # FTP data transfer ("TCP", 20, 21), # FTP control #SSH & Telnet Access ("TCP", 22, 23), ("UDP", 22, 23), # SMTP Mail #("TCP", 25, 25), # HTTP Access ("TCP", 80, 80), # POP Mail #("TCP", 109, 110), # SFTP Access ("TCP", 115, 115), # SQL Access #("TCP", 118, 118), #("UDP", 118, 118), # IMAP Access #("TCP", 143, 143), # SNMP Access #("UDP", 161, 161), # LDAP Access ("TCP", 389, 389), ("UDP", 389, 389), # HTTPS Access ("TCP", 443, 443), # LDAPS Access ("TCP", 636, 636), ("UDP", 636, 636), # Open up >1024 ("TCP", 1024, 4199), ("UDP", 1024, 4199), #SKIP PORT 4200.. See Below ("TCP", 4201, 65535), ("UDP", 4201, 65535), # Poke hole in 4200 for iPlant VMs proxy-access only (Shellinabox) ("TCP", 4200, 4200, "128.196.0.0/16"), ("UDP", 4200, 4200, "128.196.0.0/16"), ("TCP", 4200, 4200, "150.135.0.0/16"), ("UDP", 4200, 4200, "150.135.0.0/16"), ] def _init_by_provider(self, provider, *args, **kwargs): from service.driver import get_esh_driver self.core_provider = provider provider_creds = provider.get_credentials() self.provider_creds = provider_creds admin_identity = provider.get_admin_identity() admin_creds = admin_identity.get_credentials() self.admin_driver = get_esh_driver(admin_identity) admin_creds = self._libcloud_to_openstack(admin_creds) all_creds = {'location': provider.get_location()} all_creds.update(admin_creds) all_creds.update(provider_creds) return all_creds def __init__(self, provider=None, *args, **kwargs): if provider: all_creds = self._init_by_provider(provider, *args, **kwargs) else: all_creds = kwargs # Build credentials for each manager self.user_creds = self._build_user_creds(all_creds) self.image_creds = self._build_image_creds(all_creds) self.net_creds = self._build_network_creds(all_creds) #Initialize managers with respective credentials self.user_manager = UserManager(**self.user_creds) self.image_manager = ImageManager(**self.image_creds) self.network_manager = NetworkManager(**self.net_creds) def create_account(self, username, password=None, project_name=None, role_name=None, quota=None, max_quota=False): """ Create (And Update "latest changes") to an account """ if not self.core_provider: raise Exception("AccountDriver not initialized by provider," " cannot create identity. For account creation use" " build_account()") if username in self.core_provider.list_admin_names(): return (username, password, project) = self.build_account( username, password, project_name, role_name, max_quota) ident = self.create_identity(username, password, project.name, quota=quota, max_quota=max_quota) return ident def build_account(self, username, password, project_name=None, role_name=None, max_quota=False): finished = False #Attempt account creation while not finished: try: if not password: password = self.hashpass(username) if not project_name: project_name = username #1. Create Project: should exist before creating user project = self.user_manager.get_project(project_name) if not project: project = self.user_manager.create_project(project_name) # 2. Create User (And add them to the project) user = self.get_user(username) if not user: logger.info("Creating account: %s - %s - %s" % (username, password, project)) user = self.user_manager.create_user(username, password, project) # 3.1 Include the admin in the project #TODO: providercredential initialization of # "default_admin_role" self.user_manager.include_admin(project_name) # 3.2 Check the user has been given an appropriate role if not role_name: role_name = "_member_" self.user_manager.add_project_membership( project_name, username, role_name) # 4. Create a security group -- SUSPENDED.. Will occur on # instance launch instead. #self.init_security_group(user, password, project, # project.name, # self.MASTER_RULES_LIST) # 5. Create a keypair to use when launching with atmosphere self.init_keypair(user.name, password, project.name) finished = True except ConnectionError: logger.exception("Connection reset by peer. " "Waiting for one minute.") time.sleep(60) # Wait one minute except OverLimit: logger.exception("OverLimit on POST requests. " "Waiting for one minute.") time.sleep(60) # Wait one minute return (username, password, project) def init_keypair(self, username, password, project_name): keyname = settings.ATMOSPHERE_KEYPAIR_NAME with open(settings.ATMOSPHERE_KEYPAIR_FILE, "r") as pub_key_file: public_key = pub_key_file.read() return self.get_or_create_keypair(username, password, project_name, keyname, public_key) def init_security_group(self, username, password, project_name, security_group_name, rules_list): # 4.1. Update the account quota to hold a larger number of # roles than what is necessary user = self.user_manager.keystone.users.find(name=username) project = self.user_manager.keystone.tenants.find(name=project_name) nc = self.user_manager.nova rule_max = max(len(rules_list), 100) nc.quotas.update(project.id, security_group_rules=rule_max) #Change the description of the security group to match the project name try: #Create the default security group nova = self.user_manager.build_nova(username, password, project_name) sec_groups = nova.security_groups.list() if not sec_groups: nova.security_group.create("default", project_name) self.network_manager.rename_security_group(project) except ConnectionError, ce: logger.exception( "Failed to establish connection." " Security group creation FAILED") return None except NeutronClientException, nce: if nce.status_code != 404: logger.exception("Encountered unknown exception while renaming" " the security group") return None
class AccountDriver(): user_manager = None network_manager = None def __init__(self, *args, **kwargs): network_args = {} network_args.update(settings.OPENSTACK_ARGS) network_args.update(settings.OPENSTACK_NETWORK_ARGS) self.user_manager = UserManager(**settings.OPENSTACK_ARGS.copy()) self.network_manager = NetworkManager(**network_args) def get_openstack_clients(self, username, password=None, tenant_name=None): user_creds = self._get_openstack_credentials(username, password, tenant_name) neutron = self.network_manager.new_connection(**user_creds) keystone = _connect_to_keystone(*args, **kwargs) nova = _connect_to_nova(*args, **kwargs) glance = _connect_to_glance(keystone, *args, **kwargs) return { 'glance': glance, 'keystone': keystone, 'nova': nova, 'neutron': neutron, 'horizon': self._get_horizon_url(keystone.tenant_id) } def _get_horizon_url(self, tenant_id): parsed_url = urlparse(settings.OPENSTACK_AUTH_URL) return 'https://%s/horizon/auth/switch/%s/?next=/horizon/project/' %\ (parsed_url.hostname, tenant_id) def create_account(self, username, admin_role=False, max_quota=False): """ Create (And Update 'latest changes') to an account """ finished = False # Special case for admin.. Use the Openstack admin identity.. if username == 'admin': ident = self.create_openstack_identity( settings.OPENSTACK_ADMIN_KEY, settings.OPENSTACK_ADMIN_SECRET, settings.OPENSTACK_ADMIN_TENANT) return ident #Attempt account creation while not finished: try: password = self.hashpass(username) # Retrieve user, or create user & project user = self.get_or_create_user(username, password, True, admin_role) logger.debug(user) project = self.get_project(username) logger.debug(project) roles = user.list_roles(project) logger.debug(roles) if not roles: self.user_manager.add_project_member(username, username, admin_role) self.user_manager.build_security_group( user.name, self.hashpass(user.name), project.name) finished = True except OverLimit: print 'Requests are rate limited. Pausing for one minute.' time.sleep(60) # Wait one minute #(user, group) = self.create_usergroup(username) return (user, password, project) # was user # Useful methods called from above.. def get_or_create_user(self, username, password=None, usergroup=True, admin=False): user = self.get_user(username) if user: return user user = self.create_user(username, password, usergroup, admin) return user def create_user(self, username, password=None, usergroup=True, admin=False): if not password: password = self.hashpass(username) if usergroup: (project, user, role) = self.user_manager.add_usergroup(username, password, True, admin) else: user = self.user_manager.add_user(username, password) project = self.user_manager.get_project(username) #TODO: Instead, return user.get_user match, or call it if you have to.. return user def delete_user(self, username, usergroup=True, admin=False): project = self.user_manager.get_project(username) if project: self.network_manager.delete_project_network(username, project.name) if usergroup: deleted = self.user_manager.delete_usergroup(username) else: deleted = self.user_manager.delete_user(username) return deleted def hashpass(self, username): return sha1(username).hexdigest() def get_project_name_for(self, username): """ This should always map project to user For now, they are identical.. """ return username def get_project(self, project): return self.user_manager.get_project(project) def list_projects(self): return self.user_manager.list_projects() def get_user(self, user): return self.user_manager.get_user(user) def list_users(self): return self.user_manager.list_users() def list_usergroup_names(self): return [user.name for (user, project) in self.list_usergroups()] def list_usergroups(self): users = self.list_users() groups = self.list_projects() usergroups = [] for group in groups: for user in users: if user.name in group.name and\ settings.OPENSTACK_ADMIN_KEY not in user.name: usergroups.append((user, group)) break return usergroups def _get_openstack_credentials(self, username, password=None, tenant_name=None): if not tenant_name: tenant_name = self.get_project_name_for(username) if not password: password = self.hashpass(tenant_name) user_creds = {'auth_url': self.user_manager.nova.client.auth_url, 'region_name': self.user_manager.nova.client.region_name, 'username': username, 'password': password, 'tenant_name': tenant_name} return user_creds
class AccountDriver(): user_manager = None image_manager = None network_manager = None core_provider = None MASTER_RULES_LIST = [ ("ICMP", -1, 255), #FTP Access ("UDP", 20, 20), # FTP data transfer ("TCP", 20, 21), # FTP control #SSH & Telnet Access ("TCP", 22, 23), ("UDP", 22, 23), # SMTP Mail #("TCP", 25, 25), # HTTP Access ("TCP", 80, 80), # POP Mail #("TCP", 109, 110), # SFTP Access ("TCP", 115, 115), # SQL Access #("TCP", 118, 118), #("UDP", 118, 118), # IMAP Access #("TCP", 143, 143), # SNMP Access #("UDP", 161, 161), # LDAP Access ("TCP", 389, 389), ("UDP", 389, 389), # HTTPS Access ("TCP", 443, 443), # LDAPS Access ("TCP", 636, 636), ("UDP", 636, 636), # Open up >1024 ("TCP", 1024, 4199), ("UDP", 1024, 4199), #SKIP PORT 4200.. See Below ("TCP", 4201, 65535), ("UDP", 4201, 65535), # Poke hole in 4200 for iPlant VMs proxy-access only (Shellinabox) ("TCP", 4200, 4200, "128.196.0.0/16"), ("UDP", 4200, 4200, "128.196.0.0/16"), ("TCP", 4200, 4200, "150.135.0.0/16"), ("UDP", 4200, 4200, "150.135.0.0/16"), ] def _init_by_provider(self, provider, *args, **kwargs): from api import get_esh_driver self.core_provider = provider provider_creds = provider.get_credentials() self.provider_creds = provider_creds admin_identity = provider.get_admin_identity() admin_creds = admin_identity.get_credentials() self.admin_driver = get_esh_driver(admin_identity) admin_creds = self._libcloud_to_openstack(admin_creds) all_creds = {'location': provider.get_location()} all_creds.update(admin_creds) all_creds.update(provider_creds) return all_creds def __init__(self, provider=None, *args, **kwargs): if provider: all_creds = self._init_by_provider(provider, *args, **kwargs) else: all_creds = kwargs # Build credentials for each manager self.user_creds = self._build_user_creds(all_creds) self.image_creds = self._build_image_creds(all_creds) self.net_creds = self._build_network_creds(all_creds) #Initialize managers with respective credentials self.user_manager = UserManager(**self.user_creds) self.image_manager = ImageManager(**self.image_creds) self.network_manager = NetworkManager(**self.net_creds) def create_account(self, username, password=None, project_name=None, role_name=None, quota=None, max_quota=False): """ Create (And Update "latest changes") to an account """ if not self.core_provider: raise Exception("AccountDriver not initialized by provider," " cannot create identity. For account creation use" " build_account()") if username in self.core_provider.list_admin_names(): return (username, password, project) = self.build_account( username, password, project_name, role_name, max_quota) ident = self.create_identity(username, password, project.name, quota=quota, max_quota=max_quota) return ident def build_account(self, username, password, project_name=None, role_name=None, max_quota=False): finished = False #Attempt account creation while not finished: try: if not password: password = self.hashpass(username) if not project_name: project_name = username #1. Create Project: should exist before creating user project = self.user_manager.get_project(project_name) if not project: project = self.user_manager.create_project(project_name) # 2. Create User (And add them to the project) user = self.get_user(username) if not user: logger.info("Creating account: %s - %s - %s" % (username, password, project)) user = self.user_manager.create_user(username, password, project) # 3.1 Include the admin in the project #TODO: providercredential initialization of # "default_admin_role" self.user_manager.include_admin(project_name) # 3.2 Check the user has been given an appropriate role if not role_name: role_name = "_member_" self.user_manager.add_project_membership( project_name, username, role_name) # 4. Create a security group -- SUSPENDED.. Will occur on # instance launch instead. #self.init_security_group(user, password, project, # project.name, # self.MASTER_RULES_LIST) # 5. Create a keypair to use when launching with atmosphere self.init_keypair(user.name, password, project.name) finished = True except ConnectionError: logger.exception("Connection reset by peer. " "Waiting for one minute.") time.sleep(60) # Wait one minute except OverLimit: logger.exception("OverLimit on POST requests. " "Waiting for one minute.") time.sleep(60) # Wait one minute return (username, password, project) def init_keypair(self, username, password, project_name): keyname = settings.ATMOSPHERE_KEYPAIR_NAME with open(settings.ATMOSPHERE_KEYPAIR_FILE, "r") as pub_key_file: public_key = pub_key_file.read() return self.get_or_create_keypair(username, password, project_name, keyname, public_key) def init_security_group(self, username, password, project_name, security_group_name, rules_list): # 4.1. Update the account quota to hold a larger number of # roles than what is necessary user = self.user_manager.keystone.users.find(name=username) project = self.user_manager.keystone.tenants.find(name=project_name) nc = self.user_manager.nova rule_max = max(len(rules_list), 100) nc.quotas.update(project.id, security_group_rules=rule_max) #Change the description of the security group to match the project name try: #Create the default security group nova = self.user_manager.build_nova(username, password, project_name) sec_groups = nova.security_groups.list() if not sec_groups: nova.security_group.create("default", project_name) self.network_manager.rename_security_group(project) except ConnectionError, ce: logger.exception( "Failed to establish connection." " Security group creation FAILED") return None except NeutronClientException, nce: if nce.status_code != 404: logger.exception("Encountered unknown exception while renaming" " the security group") return None
class AccountDriver(BaseAccountDriver): user_manager = None image_manager = None network_manager = None core_provider = None MASTER_RULES_LIST = [ ("ICMP", -1, -1), # FTP Access ("UDP", 20, 20), # FTP data transfer ("TCP", 20, 21), # FTP control # SSH & Telnet Access ("TCP", 22, 23), ("UDP", 22, 23), # SMTP Mail # HTTP Access ("TCP", 80, 80), # POP Mail # SFTP Access ("TCP", 115, 115), # SQL Access # IMAP Access # SNMP Access # LDAP Access ("TCP", 389, 389), ("UDP", 389, 389), # HTTPS Access ("TCP", 443, 443), # LDAPS Access ("TCP", 636, 636), ("UDP", 636, 636), # Open up >1024 ("TCP", 1024, 4199), ("UDP", 1024, 4199), # SKIP PORT 4200.. See Below ("TCP", 4201, 65535), ("UDP", 4201, 65535), # Poke hole in 4200 for iPlant VMs proxy-access only (Shellinabox) ("TCP", 4200, 4200, "128.196.0.0/16"), ("UDP", 4200, 4200, "128.196.0.0/16"), ("TCP", 4200, 4200, "150.135.0.0/16"), ("UDP", 4200, 4200, "150.135.0.0/16"), ] def clear_cache(self): self.admin_driver.provider.machineCls.invalidate_provider_cache( self.admin_driver.provider) return self.admin_driver def _init_by_provider(self, provider, *args, **kwargs): from service.driver import get_esh_driver self.core_provider = provider provider_creds = provider.get_credentials() self.provider_creds = provider_creds admin_identity = provider.admin admin_creds = admin_identity.get_credentials() self.admin_driver = get_esh_driver(admin_identity) admin_creds = self._libcloud_to_openstack(admin_creds) all_creds = {'location': provider.get_location()} all_creds.update(admin_creds) all_creds.update(provider_creds) return all_creds def __init__(self, provider=None, *args, **kwargs): super(AccountDriver, self).__init__() if provider: all_creds = self._init_by_provider(provider, *args, **kwargs) else: all_creds = kwargs if 'location' in all_creds: self.namespace = "Atmosphere_OpenStack:%s" % all_creds['location'] else: logger.info("Using default namespace.. Could cause conflicts if " "switching between providers. To avoid ambiguity, " "provide the kwarg: location='provider_prefix'") # Build credentials for each manager self.credentials = all_creds ex_auth_version = all_creds.get("ex_force_auth_version", '2.0_password') if ex_auth_version.startswith('2'): self.identity_version = 2 elif ex_auth_version.startswith('3'): self.identity_version = 3 else: raise Exception("Could not determine identity_version of %s" % ex_auth_version) user_creds = self._build_user_creds(all_creds) image_creds = self._build_image_creds(all_creds) net_creds = self._build_network_creds(all_creds) sdk_creds = self._build_sdk_creds(all_creds) # Initialize managers with respective credentials self.user_manager = UserManager(**user_creds) self.image_manager = ImageManager(**image_creds) self.network_manager = NetworkManager(**net_creds) self.openstack_sdk = _connect_to_openstack_sdk(**sdk_creds) def create_account(self, username, password=None, project_name=None, role_name=None, quota=None, max_quota=False): """ Create (And Update "latest changes") to an account """ if not self.core_provider: raise Exception("AccountDriver not initialized by provider," " cannot create identity. For account creation use" " build_account()") if username in self.core_provider.list_admin_names(): return (username, password, project) = self.build_account( username, password, project_name, role_name, max_quota) ident = self.create_identity(username, password, project.name, quota=quota, max_quota=max_quota) return ident def build_account(self, username, password, project_name=None, role_name=None, max_quota=False): finished = False # Attempt account creation while not finished: try: if not password: password = self.hashpass(username) if not project_name: project_name = username # 1. Create Project: should exist before creating user project = self.user_manager.get_project(project_name) if not project: kwargs = {} if self.identity_version > 2: kwargs.update({'domain': 'default'}) project = self.user_manager.create_project(project_name, **kwargs) # 2. Create User (And add them to the project) user = self.get_user(username) if not user: logger.info("Creating account: %s - %s - %s" % (username, password, project)) kwargs = {} if self.identity_version > 2: kwargs.update({'domain': 'default'}) user = self.user_manager.create_user(username, password, project, **kwargs) # 3.1 Include the admin in the project # TODO: providercredential initialization of # "default_admin_role" self.user_manager.include_admin(project_name) # 3.2 Check the user has been given an appropriate role if not role_name: role_name = "_member_" self.user_manager.add_project_membership( project_name, username, role_name) # 4. Create a security group -- SUSPENDED.. Will occur on # instance launch instead. # self.init_security_group(user, password, project, # project.name, # self.MASTER_RULES_LIST) # 5. Create a keypair to use when launching with atmosphere self.init_keypair(user.name, password, project.name) finished = True except ConnectionError: logger.exception("Connection reset by peer. " "Waiting for one minute.") time.sleep(60) # Wait one minute except OverLimit: logger.exception("OverLimit on POST requests. " "Waiting for one minute.") time.sleep(60) # Wait one minute return (username, password, project) def init_keypair(self, username, password, project_name): keyname = settings.ATMOSPHERE_KEYPAIR_NAME with open(settings.ATMOSPHERE_KEYPAIR_FILE, "r") as pub_key_file: public_key = pub_key_file.read() return self.get_or_create_keypair(username, password, project_name, keyname, public_key) def init_security_group(self, username, password, project_name, security_group_name, rules_list): # 4.1. Update the account quota to hold a larger number of # roles than what is necessary # user = user_matches[0] # -- User:Keystone rev. kwargs = {} if self.identity_version > 2: kwargs.update({'domain': 'default'}) user_matches = [u for u in self.user_manager.keystone.users.list(**kwargs) if u.name == username] if not user_matches or len(user_matches) > 1: raise Exception("User maps to *MORE* than one account on openstack default domain! Ask a programmer for help here!") user = user_matches[0] kwargs = {} if self.identity_version > 2: kwargs.update({'domain_id': 'default'}) project = self.user_manager.keystone_projects().find(name=project_name, **kwargs) nc = self.user_manager.nova rule_max = max(len(rules_list), 100) nc.quotas.update(project.id, security_group_rules=rule_max) # Change the description of the security group to match the project # name try: # Create the default security group nova = self.user_manager.build_nova(username, password, project_name) sec_groups = nova.security_groups.list() if not sec_groups: nova.security_group.create("default", project_name) self.network_manager.rename_security_group(project) except ConnectionError as ce: logger.exception( "Failed to establish connection." " Security group creation FAILED") return None except NeutronClientException as nce: if nce.status_code != 404: logger.exception("Encountered unknown exception while renaming" " the security group") return None # Start creating security group return self.user_manager.build_security_group( user.name, password, project.name, security_group_name, rules_list) def add_rules_to_security_groups(self, core_identity_list, security_group_name, rules_list): for identity in core_identity_list: creds = self.parse_identity(identity) sec_group = self.user_manager.find_security_group( creds["username"], creds["password"], creds["tenant_name"], security_group_name) if not sec_group: raise Exception("No security gruop found matching name %s" % security_group_name) self.user_manager.add_security_group_rules( creds["username"], creds["password"], creds["tenant_name"], security_group_name, rules_list) def get_or_create_keypair(self, username, password, project_name, keyname, public_key): """ keyname - Name of the keypair public_key - Contents of public key in OpenSSH format """ clients = self.get_openstack_clients(username, password, project_name) if self.identity_version == 2: nova = clients["nova"] keypairs = nova.keypairs.list() else: osdk = clients["openstack_sdk"] keypairs = [kp for kp in osdk.compute.keypairs()] for kp in keypairs: if kp.name == keyname: if kp.public_key != public_key: raise Exception( "Mismatched public key found for keypair named: %s" ". Expected: %s Original: %s" % (keyname, public_key, kp.public_key)) return (kp, False) return (self.create_keypair( username, password, project_name, keyname, public_key), True) def create_keypair(self, username, password, project_name, keyname, public_key): """ keyname - Name of the keypair public_key - Contents of public key in OpenSSH format """ clients = self.get_openstack_clients(username, password, project_name) if self.identity_version == 2: nova = clients["nova"] keypair = nova.keypairs.create( keyname, public_key=public_key) else: osdk = clients["openstack_sdk"] keypair = osdk.compute.create_keypair( name=keyname, public_key=public_key) return keypair def accept_shared_image(self, glance_image, project_name): """ This is only required when sharing using 'the v2 api' on glance. """ # FIXME: Abusing the 'project_name' == 'username' mapping clients = self.get_openstack_clients(project_name) project = self.user_manager.get_project(project_name) glance = clients["glance"] glance.image_members.update( glance_image.id, project.id, 'accepted') def rebuild_security_groups(self, core_identity, rules_list=None): creds = self.parse_identity(core_identity) if not rules_list: rules_list = self.MASTER_RULES_LIST return self.user_manager.build_security_group( creds["username"], creds["password"], creds["tenant_name"], creds["tenant_name"], rules_list, rebuild=True) def parse_identity(self, core_identity): identity_creds = self._libcloud_to_openstack( core_identity.get_credentials()) return identity_creds def clean_credentials(self, credential_dict): """ This function cleans up a dictionary of credentials. After running this function: * Erroneous dictionary keys are removed * Missing credentials are listed """ creds = ["username", "password", "project_name"] missing_creds = [] # 1. Remove non-credential information from the dict for key in credential_dict.keys(): if key not in creds: credential_dict.pop(key) # 2. Check the dict has all the required credentials for c in creds: if not hasattr(credential_dict, c): missing_creds.append(c) return missing_creds def create_identity(self, username, password, project_name, quota=None, max_quota=False, account_admin=False): if not self.core_provider: raise Exception("AccountDriver not initialized by provider, " "cannot create identity") identity = Identity.create_identity( username, self.core_provider.location, quota=quota, # Flags.. max_quota=max_quota, account_admin=account_admin, # Pass in credentials with cred_ namespace cred_key=username, cred_secret=password, cred_ex_tenant_name=project_name, cred_ex_project_name=project_name) # Return the identity return identity def rebuild_project_network(self, username, project_name, dns_nameservers=[]): self.network_manager.delete_project_network(username, project_name) net_args = self._base_network_creds() self.network_manager.create_project_network( username, self.hashpass(username), project_name, get_unique_number=get_unique_id, dns_nameservers=dns_nameservers, **net_args) return True def delete_security_group(self, identity): identity_creds = self.parse_identity(identity) project_name = identity_creds["tenant_name"] project = self.user_manager.keystone.tenants.find(name=project_name) sec_group_r = self.network_manager.neutron.list_security_groups( tenant_id=project.id) sec_groups = sec_group_r["security_groups"] for sec_group in sec_groups: self.network_manager.neutron.delete_security_group(sec_group["id"]) return True def delete_network(self, identity, remove_network=True): # Core credentials need to be converted to openstack names identity_creds = self.parse_identity(identity) username = identity_creds["username"] project_name = identity_creds["tenant_name"] # Convert from libcloud names to openstack client names return self.network_manager.delete_project_network( username, project_name, remove_network=remove_network) def create_network(self, identity): # Core credentials need to be converted to openstack names identity_creds = self.parse_identity(identity) username = identity_creds["username"] password = identity_creds["password"] project_name = identity_creds["tenant_name"] dns_nameservers = [ dns_server.ip_address for dns_server in identity.provider.dns_server_ips.order_by('order')] # Convert from libcloud names to openstack client names net_args = self._base_network_creds() return self.network_manager.create_project_network( username, password, project_name, get_unique_number=get_unique_id, dns_nameservers=dns_nameservers, **net_args) # Useful methods called from above.. def delete_account(self, username, projectname): self.os_delete_account(username, projectname) Identity.delete_identity(username, self.core_provider.location) def os_delete_account(self, username, projectname): project = self.user_manager.get_project(projectname) # 1. Network cleanup if project: self.network_manager.delete_project_network(username, projectname) # 2. Role cleanup (Admin too) self.user_manager.delete_all_roles(username, projectname) adminuser = self.user_manager.keystone.username self.user_manager.delete_all_roles(adminuser, projectname) # 3. Project cleanup self.user_manager.delete_project(projectname) # 4. User cleanup user = self.user_manager.get_user(username) if user: self.user_manager.delete_user(username) return True def hashpass(self, username): # TODO: Must be better. return sha1(username).hexdigest() def get_project_name_for(self, username): """ This should always map project to user For now, they are identical.. TODO: Make this intelligent. use keystone. """ return username def _get_image(self, *args, **kwargs): return self.image_manager.get_image(*args, **kwargs) # For one-time caching def _list_all_images(self, *args, **kwargs): return self.image_manager.list_images(*args, **kwargs) def tenant_instances_map( self, status_list=[], match_all=False, include_empty=False): """ Maps 'Tenant' objects to all the 'owned instances' as listed by the admin driver Optional fields: * status_list (list) - If provided, only include instance if it's status/tmp_status matches a value in the list. * match_all (bool) - If True, instances must match ALL words in the list. * include_empty (bool) - If True, include ALL tenants in the map. """ all_projects = self.list_projects() all_instances = self.list_all_instances() if include_empty: project_map = {proj: [] for proj in all_projects} else: project_map = {} for instance in all_instances: try: # NOTE: will someday be 'projectId' tenant_id = instance.extra['tenantId'] project = [p for p in all_projects if p.id == tenant_id][0] except (ValueError, KeyError): raise Exception( "The implementaion for recovering a tenant id has changed. Update the code base above this line!") metadata = instance._node.extra.get('metadata', {}) instance_status = instance.extra.get('status') task = instance.extra.get('task') tmp_status = metadata.get('tmp_status', '') if status_list: if match_all: truth = all( True if ( status_name and status_name in [ instance_status, task, tmp_status]) else False for status_name in status_list) else: truth = any( True if ( status_name and status_name in [ instance_status, task, tmp_status]) else False for status_name in status_list) if not truth: logger.info( "Found an instance:%s for tenant:%s but skipped because %s could be found in the list: (%s - %s - %s)" % (instance.id, project.name, "none of the status_names" if not match_all else "not all of the status names", instance_status, task, tmp_status)) continue instance_list = project_map.get(project, []) instance_list.append(instance) project_map[project] = instance_list return project_map def list_all_instances(self, **kwargs): return self.admin_driver.list_all_instances(**kwargs) def list_all_images(self, **kwargs): return self.image_manager.list_images(**kwargs) def get_project_by_id(self, project_id): return self.user_manager.get_project_by_id(project_id) def get_project(self, project_name, **kwargs): if self.identity_version > 2: kwargs = self._parse_domain_kwargs(kwargs) return self.user_manager.get_project(project_name, **kwargs) def _make_tenant_id_map(self): all_projects = self.list_projects() tenant_id_map = {project.id: project.name for project in all_projects} return tenant_id_map def create_trust( self, trustee_project_name, trustee_username, trustee_domain_name, trustor_project_name, trustor_username, trustor_domain_name, roles=[], impersonation=True): """ Trustee == Consumer Trustor == Resource Owner Given the *names* of projects, users, and domains gather all required information for a Trust-Create create a new trust object NOTE: we set impersonation to True -- it has a 'normal default' of False! """ default_roles = [{"name": "admin"}] trustor_domain = self.openstack_sdk.identity.find_domain( trustor_domain_name) if not trustor_domain: raise ValueError("Could not find trustor domain named %s" % trustor_domain_name) trustee_domain = self.openstack_sdk.identity.find_domain( trustee_domain_name) if not trustee_domain: raise ValueError("Could not find trustee domain named %s" % trustee_domain_name) trustee_user = self.get_user( trustee_username, domain_id=trustee_domain.id) # trustee_project = self.get_project( # trustee_username, domain_name=trustee_domain.id) trustor_user = self.get_user( trustor_username, domain_id=trustor_domain.id) trustor_project = self.get_project( trustor_project_name, domain_id=trustor_domain.id) if not roles: roles = default_roles new_trust = self.openstack_sdk.identity.create_trust( impersonation=impersonation, project_id=trustor_project.id, trustor_user_id=trustor_user.id, trustee_user_id=trustee_user.id, roles=roles, domain_id=trustee_domain.id) return new_trust def list_trusts(self): return [t for t in self.openstack_sdk.identity.trusts()] def list_projects(self, **kwargs): if self.identity_version > 2: kwargs = self._parse_domain_kwargs(kwargs, domain_override='domain') return self.user_manager.list_projects(**kwargs) def list_roles(self, **kwargs): """ Keystone already accepts 'domain_name' to restrict what roles to return """ return self.user_manager.keystone.roles.list(**kwargs) def get_role(self, role_name_or_id, **list_kwargs): if self.identity_version > 2: list_kwargs = self._parse_domain_kwargs(list_kwargs) role_list = self.list_roles(**list_kwargs) found_roles = [role for role in role_list if role.id == role_name_or_id or role.name == role_name_or_id] if not found_roles: return None if len(found_roles) > 1: raise Exception("role name/id %s matched more than one value -- Fix the code" % (role_name_or_id,)) return found_roles[0] def get_user(self, user_name_or_id, **list_kwargs): if self.identity_version > 2: list_kwargs = self._parse_domain_kwargs(list_kwargs) user_list = self.list_users(**list_kwargs) found_users = [user for user in user_list if user.id == user_name_or_id or user.name == user_name_or_id] if not found_users: return None if len(found_users) > 1: raise Exception("User name/id %s matched more than one value -- Fix the code" % (user_name_or_id,)) return found_users[0] def _parse_domain_kwargs(self, kwargs, domain_override='domain_id'): """ CLI's replace domain_name with the actual domain. We replicate that functionality to avoid operator-frustration. """ domain_key = 'domain_name' domain_name_or_id = kwargs.get(domain_key) if not domain_name_or_id: return kwargs domain = self.openstack_sdk.identity.find_domain(domain_name_or_id) if not domain: raise ValueError("Could not find domain %s by name or id." % domain_name_or_id) kwargs.pop(domain_key) kwargs[domain_override] = domain.id return kwargs def list_users(self, **kwargs): if self.identity_version > 2: kwargs = self._parse_domain_kwargs(kwargs) return self.user_manager.keystone.users.list(**kwargs) def list_usergroup_names(self): return [user.name for (user, project) in self.list_usergroups()] def list_usergroups(self): """ TODO: This function is AWFUL just scrap it. """ users = self.list_users() groups = self.list_projects() usergroups = [] admin_usernames = self.core_provider.list_admin_names() for group in groups: for user in users: if user.name in admin_usernames: continue if user.name in group.name: usergroups.append((user, group)) break return usergroups def _get_horizon_url(self, tenant_id): parsed_url = urlparse(self.provider_creds["auth_url"]) return "https://%s/horizon/auth/switch/%s/?next=/horizon/project/" %\ (parsed_url.hostname, tenant_id) def get_openstack_clients(self, username, password=None, tenant_name=None): # TODO: I could replace with identity.. but should I? # Build credentials for each manager all_creds = self._get_openstack_credentials( username, password, tenant_name) # Initialize managers with respective credentials image_creds = self._build_image_creds(all_creds) net_creds = self._build_network_creds(all_creds) sdk_creds = self._build_sdk_creds(all_creds) if self.identity_version > 2: openstack_sdk = _connect_to_openstack_sdk(**sdk_creds) else: openstack_sdk = None neutron = self.network_manager.new_connection(**net_creds) keystone, nova, glance = self.image_manager._new_connection( **image_creds) return { "glance": glance, "keystone": keystone, "nova": nova, "neutron": neutron, "openstack_sdk": openstack_sdk, "horizon": self._get_horizon_url(keystone.tenant_id) } def _get_openstack_credentials(self, username, password=None, tenant_name=None): if not tenant_name: tenant_name = self.get_project_name_for(username) if not password: password = self.hashpass(tenant_name) osdk_creds = { "auth_url": self.user_manager.nova.client.auth_url.replace('/v3','').replace('/v2.0',''), "admin_url": self.user_manager.keystone._management_url.replace('/v2.0','').replace('/v3',''), "region_name": self.user_manager.nova.client.region_name, "username": username, "password": password, "tenant_name": tenant_name } return osdk_creds # Credential manipulaters def _libcloud_to_openstack(self, credentials): credentials["username"] = credentials.pop("key") credentials["password"] = credentials.pop("secret") credentials["tenant_name"] = credentials.pop("ex_tenant_name") return credentials def _base_network_creds(self): """ These credentials should be used when another user/pass/tenant combination will be used NOTE: JETSTREAM auth_url to be '/v3' """ net_args = self.provider_creds.copy() # NOTE: The neutron 'auth_url' is the ADMIN_URL net_args["auth_url"] = net_args.pop("admin_url")\ .replace("/tokens", "").replace('/v2.0', '').replace('/v3', '') if self.identity_version == 3: auth_prefix = '/v3' elif self.identity_version == 2: auth_prefix = '/v2.0' if auth_prefix not in net_args['auth_url']: net_args['auth_url'] += auth_prefix return net_args def _build_network_creds(self, credentials): """ Credentials - dict() return the credentials required to build a "NetworkManager" object NOTE: JETSTREAM auth_url to be '/v3' """ net_args = credentials.copy() # Required: net_args.get("username") net_args.get("password") net_args.get("tenant_name") net_args.get("router_name") net_args.get("region_name") # NOTE: The neutron 'auth_url' is the ADMIN_URL net_args["auth_url"] = net_args.pop("admin_url").replace("/tokens", "") # Ignored: net_args.pop("location", None) net_args.pop("ex_project_name", None) net_args.pop("ex_force_auth_version", None) auth_url = net_args.get('auth_url') if self.identity_version == 3: auth_prefix = '/v3' elif self.identity_version == 2: auth_prefix = '/v2.0' net_args["auth_url"] = auth_url.replace("/v2.0", "").replace('/v3', '').replace("/tokens", "") if auth_prefix not in net_args['auth_url']: net_args["auth_url"] += auth_prefix return net_args def _build_image_creds(self, credentials): """ Credentials - dict() return the credentials required to build a "UserManager" object NOTE: Expects auth_url to be '/v2.0/tokens' NOTE: JETSTREAM auth_url to be '/v3' """ img_args = credentials.copy() # Required: for required_arg in [ "username", "password", "tenant_name", "auth_url", "region_name"]: if required_arg not in img_args or not img_args[required_arg]: raise ValueError( "ImageManager is missing a Required Argument: %s" % required_arg) ex_auth_version = img_args.pop("ex_force_auth_version", '2.0_password') # Supports v2.0 or v3 Identity if ex_auth_version.startswith('2'): auth_url_prefix = "/v2.0/tokens" auth_version = 'v2.0' elif ex_auth_version.startswith('3'): auth_url_prefix = "/v3/tokens" auth_version = 'v3' img_args['version'] = auth_version img_args["auth_url"] = img_args.get('auth_url','').replace("/v2.0","").replace("/tokens", "").replace('/v3','') if auth_url_prefix not in img_args['auth_url']: img_args["auth_url"] += auth_url_prefix return img_args def _build_user_creds(self, credentials): """ Credentials - dict() return the credentials required to build a "UserManager" object NOTE: Expects auth_url to be '/v2.0' """ user_args = credentials.copy() # Required args: user_args.get("username") user_args.get("password") user_args.get("tenant_name") ex_auth_version = user_args.pop("ex_force_auth_version", '2.0_password') # Supports v2.0 or v3 Identity if ex_auth_version.startswith('2'): auth_url_prefix = "/v2.0/" auth_version = 'v2.0' elif ex_auth_version.startswith('3'): auth_url_prefix = "/v3/" auth_version = 'v3' auth_url = user_args.get('auth_url') user_args["auth_url"] = auth_url.replace("/v2.0", "").replace("/tokens", "") if auth_url_prefix not in user_args['auth_url']: user_args["auth_url"] += auth_url_prefix user_args.get("region_name") user_args['version'] = user_args.get("version", auth_version) # Removable args: user_args.pop("admin_url", None) user_args.pop("location", None) user_args.pop("router_name", None) user_args.pop("ex_project_name", None) return user_args def _build_sdk_creds(self, credentials): """ Credentials - dict() return the credentials required to build an "Openstack SDK" connection NOTE: Expects auth_url to be ADMIN aand be '/v3' """ os_args = credentials.copy() # Required args: os_args.get("username") os_args.get("password") if 'tenant_name' in os_args and 'project_name' not in os_args: os_args['project_name'] = os_args.get("tenant_name") os_args.get("region_name") ex_auth_version = os_args.pop("ex_force_auth_version", '2.0_password') # Supports v2.0 or v3 Identity if ex_auth_version.startswith('2'): auth_url_prefix = "/v2.0/" auth_version = 'v2.0' elif ex_auth_version.startswith('3'): auth_url_prefix = "/v3/" auth_version = 'v3' os_args["auth_url"] = os_args.get("auth_url")\ .replace("/tokens", "").replace('/v2.0', '').replace('/v3', '') # NOTE: Openstack 'auth_url' is ACTUALLY the admin url. if auth_url_prefix not in os_args['admin_url']: os_args["auth_url"] = os_args['admin_url'] + auth_url_prefix if 'project_domain_name' not in os_args: os_args['project_domain_name'] = 'default' if 'user_domain_name' not in os_args: os_args['user_domain_name'] = 'default' if 'identity_api_version' not in os_args: os_args['identity_api_version'] = 3 # Removable args: os_args.pop("ex_force_auth_version", None) os_args.pop("admin_url", None) os_args.pop("location", None) os_args.pop("router_name", None) os_args.pop("ex_project_name", None) os_args.pop("ex_tenant_name", None) os_args.pop("tenant_name", None) return os_args
class AccountDriver(BaseAccountDriver): user_manager = None image_manager = None network_manager = None core_provider = None cloud_config = {} def clear_cache(self): self.admin_driver.provider.machineCls.invalidate_provider_cache(self.admin_driver.provider) return self.admin_driver def _init_by_provider(self, provider, *args, **kwargs): from service.driver import get_esh_driver self.core_provider = provider provider_creds = provider.get_credentials() self.cloud_config = provider.cloud_config self.provider_creds = provider_creds admin_identity = provider.admin admin_creds = admin_identity.get_credentials() self.admin_driver = get_esh_driver(admin_identity) admin_creds = self._libcloud_to_openstack(admin_creds) all_creds = {"location": provider.get_location()} all_creds.update(admin_creds) all_creds.update(provider_creds) return all_creds def __init__(self, provider=None, *args, **kwargs): super(AccountDriver, self).__init__() if provider: all_creds = self._init_by_provider(provider, *args, **kwargs) else: all_creds = kwargs if "location" in all_creds: self.namespace = "Atmosphere_OpenStack:%s" % all_creds["location"] else: logger.info( "Using default namespace.. Could cause conflicts if " "switching between providers. To avoid ambiguity, " "provide the kwarg: location='provider_prefix'" ) # Build credentials for each manager self.credentials = all_creds ex_auth_version = all_creds.get("ex_force_auth_version", "2.0_password") if ex_auth_version.startswith("2"): self.identity_version = 2 elif ex_auth_version.startswith("3"): self.identity_version = 3 else: raise Exception("Could not determine identity_version of %s" % ex_auth_version) user_creds = self._build_user_creds(all_creds) image_creds = self._build_image_creds(all_creds) net_creds = self._build_network_creds(all_creds) sdk_creds = self._build_sdk_creds(all_creds) # Initialize managers with respective credentials self.user_manager = UserManager(**user_creds) self.image_manager = ImageManager(**image_creds) self.network_manager = NetworkManager(**net_creds) self.openstack_sdk = _connect_to_openstack_sdk(**sdk_creds) def create_account(self, username, password=None, project_name=None, role_name=None, quota=None, max_quota=False): """ Create (And Update "latest changes") to an account """ if not self.core_provider: raise Exception( "AccountDriver not initialized by provider," " cannot create identity. For account creation use" " build_account()" ) if username in self.core_provider.list_admin_names(): return (username, password, project) = self.build_account(username, password, project_name, role_name, max_quota) ident = self.create_identity(username, password, project.name, quota=quota, max_quota=max_quota) return ident def build_account( self, username, password, project_name=None, role_name=None, max_quota=False, domain_name="default" ): finished = False # Attempt account creation while not finished: try: if not password: password = self.hashpass(username) if not project_name: project_name = username # 1. Create Project: should exist before creating user project_kwargs = {} if self.identity_version > 2: project_kwargs.update({"domain_id": domain_name}) project = self.user_manager.get_project(project_name, **project_kwargs) if not project: if self.identity_version > 2: project_kwargs = {"domain": domain_name} project = self.user_manager.create_project(project_name, **project_kwargs) # 2. Create User (And add them to the project) user = self.get_user(username) if not user: logger.info("Creating account: %s - %s - %s" % (username, password, project)) user_kwargs = {} if self.identity_version > 2: user_kwargs.update({"domain": domain_name}) user = self.user_manager.create_user(username, password, project, **user_kwargs) # 3.1 Include the admin in the project # TODO: providercredential initialization of # "default_admin_role" self.user_manager.include_admin(project_name) # 3.2 Check the user has been given an appropriate role if not role_name: try: role_name = self.cloud_config["user"]["user_role_name"] except KeyError: logger.warn( "Cloud config ['user']['user_role_name'] is missing -- using deprecated settings.DEFAULT_KEYSTONE_ROLE" ) role_name = settings.DEFAULT_KEYSTONE_ROLE self.user_manager.add_project_membership(project_name, username, role_name) # , domain_name) # 4. Create a keypair to use when launching with atmosphere self.init_keypair(user.name, password, project.name) finished = True except ConnectionError: logger.exception("Connection reset by peer. " "Waiting for one minute.") time.sleep(60) # Wait one minute except NovaOverLimit: logger.exception("OverLimit on POST requests. " "Waiting for one minute.") time.sleep(60) # Wait one minute return (username, password, project) def change_password(self, identity, new_password, old_password=None): try: self.update_openstack_password(identity, new_password, old_password=old_password) self.update_password_credential(identity, new_password) return True except Exception: logger.exception("Could not change password") return False def update_password_credential(self, core_identity, new_password): """ """ try: password_cred = core_identity.credential_set.get(key="secret") password_cred.value = new_password password_cred.save() except ObjectDoesNotExist: raise Exception("The 'key' for a secret has changed! " "Ask a programmer for help!") def update_openstack_password(self, identity, new_password, strategy=None): identity_creds = self.parse_identity(identity) username = identity_creds["username"] if not strategy: strategy = DEFAULT_PASSWORD_UPDATE if not strategy or strategy == "keystone_password_update": return self.keystone_password_update(username, new_password) if strategy == "openstack_sdk_password_update": return self.openstack_sdk_password_update(username, new_password) else: raise ValueError("Invalid 'Update Password' strategy: %s" % strategy) def keystone_password_update(self, username, new_password): keystone = self.user_manager.keystone user = keystone.users.find(name=username) return keystone.users.update_password(user, new_password) def openstack_sdk_password_update(self, username, new_password): user_id = self.get_user(username).id return self.openstack_sdk.identity.update_user(user_id, password=new_password) def init_keypair(self, username, password, project_name): keyname = settings.ATMOSPHERE_KEYPAIR_NAME with open(settings.ATMOSPHERE_KEYPAIR_FILE, "r") as pub_key_file: public_key = pub_key_file.read() return self.get_or_create_keypair(username, password, project_name, keyname, public_key) def init_security_group(self, core_identity, security_group_name=None): # 4.1. Update the account quota to hold a larger number of # roles than what is necessary # user = user_matches[0] # -- User:Keystone rev. try: rules_list = core_identity.provider.cloud_config["network"]["default_security_rules"] except KeyError: logger.warn( "Cloud config ['user']['default_security_rules'] is missing -- using deprecated settings.DEFAULT_RULES" ) rules_list = DEFAULT_RULES identity_creds = self.parse_identity(core_identity) username = identity_creds["username"] password = identity_creds["password"] project_name = identity_creds["tenant_name"] kwargs = {} if self.identity_version > 2: kwargs.update({"domain": "default"}) user_matches = [u for u in self.user_manager.keystone.users.list(**kwargs) if u.name == username] if not user_matches or len(user_matches) > 1: raise Exception( "User maps to *MORE* than one account on openstack default domain! Ask a programmer for help here!" ) user = user_matches[0] kwargs = {} if self.identity_version > 2: kwargs.update({"domain_id": "default"}) project = self.user_manager.keystone_projects().find(name=project_name, **kwargs) nc = self.user_manager.nova rule_max = max(len(rules_list), 100) nc.quotas.update(project.id, security_group_rules=rule_max) # Change the description of the security group to match the project # name try: # Create the default security group nova = self.user_manager.build_nova(username, password, project_name) sec_groups = nova.security_groups.list() if not sec_groups: nova.security_group.create("default", project_name) self.network_manager.rename_security_group(project, security_group_name=security_group_name) except ConnectionError as ce: logger.exception("Failed to establish connection." " Security group creation FAILED") return None except NeutronClientException as nce: if nce.status_code != 404: logger.exception("Encountered unknown exception while renaming" " the security group") return None # Start creating security group return self.user_manager.build_security_group( user.name, password, project.name, security_group_name, rules_list ) def add_rules_to_security_groups(self, core_identity_list, security_group_name, rules_list): for identity in core_identity_list: creds = self.parse_identity(identity) sec_group = self.user_manager.find_security_group( creds["username"], creds["password"], creds["tenant_name"], security_group_name ) if not sec_group: raise Exception("No security gruop found matching name %s" % security_group_name) self.user_manager.add_security_group_rules( creds["username"], creds["password"], creds["tenant_name"], security_group_name, rules_list ) def get_or_create_keypair(self, username, password, project_name, keyname, public_key): """ keyname - Name of the keypair public_key - Contents of public key in OpenSSH format """ clients = self.get_openstack_clients(username, password, project_name) if self.identity_version == 2: nova = clients["nova"] keypairs = nova.keypairs.list() else: osdk = clients["openstack_sdk"] keypairs = [kp for kp in osdk.compute.keypairs()] for kp in keypairs: if kp.name == keyname: if kp.public_key != public_key: raise Exception( "Mismatched public key found for keypair named: %s" ". Expected: %s Original: %s" % (keyname, public_key, kp.public_key) ) return (kp, False) return (self.create_keypair(username, password, project_name, keyname, public_key), True) def create_keypair(self, username, password, project_name, keyname, public_key): """ keyname - Name of the keypair public_key - Contents of public key in OpenSSH format """ clients = self.get_openstack_clients(username, password, project_name) if self.identity_version == 2: nova = clients["nova"] keypair = nova.keypairs.create(keyname, public_key=public_key) else: osdk = clients["openstack_sdk"] keypair = osdk.compute.create_keypair(name=keyname, public_key=public_key) return keypair def accept_shared_image(self, glance_image, project_name): """ This is only required when sharing using 'the v2 api' on glance. """ # FIXME: Abusing the 'project_name' == 'username' mapping clients = self.get_openstack_clients(project_name) project = self.user_manager.get_project(project_name) glance = clients["glance"] glance.image_members.update(glance_image.id, project.id, "accepted") def rebuild_security_groups(self, core_identity, rules_list=None): creds = self.parse_identity(core_identity) if not rules_list: try: rules_list = self.cloud_config["network"]["default_security_rules"] except KeyError: logger.warn( "Cloud config ['network']['default_security_rules'] is missing -- using deprecated settings.DEFAULT_RULES" ) rules_list = DEFAULT_RULES return self.user_manager.build_security_group( creds["username"], creds["password"], creds["tenant_name"], creds["tenant_name"], rules_list, rebuild=True ) def parse_identity(self, core_identity): identity_creds = self._libcloud_to_openstack(core_identity.get_credentials()) return identity_creds def clean_credentials(self, credential_dict): """ This function cleans up a dictionary of credentials. After running this function: * Erroneous dictionary keys are removed * Missing credentials are listed """ creds = ["username", "password", "project_name"] missing_creds = [] # 1. Remove non-credential information from the dict for key in credential_dict.keys(): if key not in creds: credential_dict.pop(key) # 2. Check the dict has all the required credentials for c in creds: if not hasattr(credential_dict, c): missing_creds.append(c) return missing_creds def create_identity(self, username, password, project_name, quota=None, max_quota=False, account_admin=False): if not self.core_provider: raise Exception("AccountDriver not initialized by provider, " "cannot create identity") identity = Identity.create_identity( username, self.core_provider.location, quota=quota, # Flags.. max_quota=max_quota, account_admin=account_admin, # Pass in credentials with cred_ namespace cred_key=username, cred_secret=password, cred_ex_tenant_name=project_name, cred_ex_project_name=project_name, ) # Return the identity return identity def rebuild_project_network(self, username, project_name, dns_nameservers=[]): self.network_manager.delete_project_network(username, project_name) net_args = self._base_network_creds() self.network_manager.create_project_network( username, self.hashpass(username), project_name, get_unique_number=_get_unique_id, dns_nameservers=dns_nameservers, **net_args ) return True def delete_security_group(self, identity): identity_creds = self.parse_identity(identity) project_name = identity_creds["tenant_name"] project = self.user_manager.keystone.tenants.find(name=project_name) sec_group_r = self.network_manager.neutron.list_security_groups(tenant_id=project.id) sec_groups = sec_group_r["security_groups"] for sec_group in sec_groups: self.network_manager.neutron.delete_security_group(sec_group["id"]) return True def select_network_strategy(self, identity, topology_name=None): """ Select a network topology and initialize it with the identity/provider specific information required. """ # Select Cls if not topology_name: NetworkTopologyStrategyCls = ExternalRouter else: NetworkTopologyStrategyCls = get_topology_cls(topology_name) try: network_strategy = NetworkTopologyStrategyCls(identity) except: logger.exception("Error initializing Network Topology - %s + %s " % (NetworkTopologyStrategyCls, identity)) raise return network_strategy def dns_nameservers_for(self, identity): dns_nameservers = [dns_server.ip_address for dns_server in identity.provider.dns_server_ips.order_by("order")] return dns_nameservers def delete_user_network(self, identity, options={}): """ 1. Look at the provider for network topology hints 2. If no network topology exists, use the "Default network" settings. 3. Delete network based on topology """ identity_creds = self.parse_identity(identity) project_name = identity_creds["tenant_name"] neutron = self.get_openstack_client(identity, "neutron") try: topology_name = self.cloud_config["network"]["topology"] except KeyError: logger.exception( "Network topology not selected -- " "Will attempt to use the last known default: ExternalRouter." ) topology_name = None network_strategy = self.select_network_strategy(identity, topology_name) if options.get("skip_network"): return self._delete_user_subnet(network_strategy, neutron, project_name) return self._delete_user_network(network_strategy, neutron, project_name) def _delete_user_subnet(self, network_strategy, neutron, prefix_name): network_strategy.delete_router_interface( self.network_manager, neutron, "%s-router" % prefix_name, "%s-subnet" % prefix_name ) network_strategy.delete_router(self.network_manager, neutron, "%s-router" % prefix_name) network_strategy.delete_subnet(self.network_manager, neutron, "%s-subnet" % prefix_name) def _delete_user_network(self, network_strategy, neutron, prefix_name): self._delete_user_subnet(network_strategy, neutron, prefix_name) network_strategy.delete_network(self.network_manager, neutron, "%s-net" % prefix_name) return def create_user_network(self, identity): """ 1. Look at the provider for network topology hints 2. If no network topology exists, use the "Default network" settings. 3. Create network based on topology """ # Prepare args identity_creds = self.parse_identity(identity) username = identity_creds["username"] project_name = identity_creds["tenant_name"] neutron = self.get_openstack_client(identity, "neutron") dns_nameservers = self.dns_nameservers_for(identity) try: topology_name = self.cloud_config["network"]["topology"] except KeyError: logger.exception( "Network topology not selected -- " "Will attempt to use the last known default: ExternalRouter." ) topology_name = None network_strategy = self.select_network_strategy(identity, topology_name) # May raise exception network_strategy.validate(identity) network = network_strategy.get_or_create_network( self.network_manager, neutron, "%s-net" % project_name ) # NOTE: the name of the network here is a *hint* not a guarantee. # Use `network.name` from here subnet = network_strategy.get_or_create_user_subnet( self.network_manager, neutron, network["id"], username, "%s-subnet" % project_name, dns_nameservers=dns_nameservers, ) router = network_strategy.get_or_create_router(self.network_manager, neutron, "%s-router" % project_name) gateway = network_strategy.get_or_create_router_gateway(self.network_manager, neutron, router, network) interface = network_strategy.get_or_create_router_interface( self.network_manager, neutron, router, subnet, "%s-router-intf" % project_name ) network_resources = {"network": network, "subnet": subnet, "router": router, "interface": interface} return network_resources # Useful methods called from above.. def delete_account(self, username, projectname): self.os_delete_account(username, projectname) Identity.delete_identity(username, self.core_provider.location) def os_delete_account(self, username, projectname): project = self.user_manager.get_project(projectname) # 1. Network cleanup if project: self.network_manager.delete_project_network(username, projectname) # 2. Role cleanup (Admin too) self.user_manager.delete_all_roles(username, projectname) adminuser = self.user_manager.keystone.username self.user_manager.delete_all_roles(adminuser, projectname) # 3. Project cleanup self.user_manager.delete_project(projectname) # 4. User cleanup user = self.user_manager.get_user(username) if user: self.user_manager.delete_user(username) return True def hashpass(self, username): """ Create a unique password using 'username' """ cloud_pass = self.cloud_config["user"].get("secret") if not cloud_pass or len(cloud_pass) < 32: raise ValueError("Cloud config ['user']['secret'] is required and " + "must be of length 32 or more") if not username: raise ValueError("Missing username, cannot create hash") return sha256(username + cloud_pass).hexdigest() def get_project_name_for(self, username): """ This should always map project to user For now, they are identical.. TODO: Make this intelligent. use keystone. """ return username def _get_image(self, *args, **kwargs): return self.image_manager.get_image(*args, **kwargs) # For one-time caching def _list_all_images(self, *args, **kwargs): return self.image_manager.list_images(*args, **kwargs) def tenant_instances_map(self, status_list=[], match_all=False, include_empty=False): """ Maps 'Tenant' objects to all the 'owned instances' as listed by the admin driver Optional fields: * status_list (list) - If provided, only include instance if it's status/tmp_status matches a value in the list. * match_all (bool) - If True, instances must match ALL words in the list. * include_empty (bool) - If True, include ALL tenants in the map. """ all_projects = self.list_projects() all_instances = self.list_all_instances() if include_empty: project_map = {proj: [] for proj in all_projects} else: project_map = {} for instance in all_instances: try: # NOTE: will someday be 'projectId' tenant_id = instance.extra["tenantId"] project = [p for p in all_projects if p.id == tenant_id][0] except (ValueError, KeyError): raise Exception( "The implementaion for recovering a tenant id has changed. Update the code base above this line!" ) metadata = instance._node.extra.get("metadata", {}) instance_status = instance.extra.get("status") task = instance.extra.get("task") tmp_status = metadata.get("tmp_status", "") if status_list: if match_all: truth = all( True if (status_name and status_name in [instance_status, task, tmp_status]) else False for status_name in status_list ) else: truth = any( True if (status_name and status_name in [instance_status, task, tmp_status]) else False for status_name in status_list ) if not truth: logger.info( "Found an instance:%s for tenant:%s but skipped because %s could be found in the list: (%s - %s - %s)" % ( instance.id, project.name, "none of the status_names" if not match_all else "not all of the status names", instance_status, task, tmp_status, ) ) continue instance_list = project_map.get(project, []) instance_list.append(instance) project_map[project] = instance_list return project_map def list_all_instances(self, **kwargs): return self.admin_driver.list_all_instances(**kwargs) def list_all_images(self, **kwargs): return self.image_manager.list_images(**kwargs) def list_all_snapshots(self, **kwargs): return [img for img in self.list_all_images(**kwargs) if "snapshot" in img.get("image_type", "image").lower()] def get_project_by_id(self, project_id): return self.user_manager.get_project_by_id(project_id) def get_project(self, project_name, **kwargs): if self.identity_version > 2: kwargs = self._parse_domain_kwargs(kwargs) return self.user_manager.get_project(project_name, **kwargs) def _make_tenant_id_map(self): all_projects = self.list_projects() tenant_id_map = {project.id: project.name for project in all_projects} return tenant_id_map def create_trust( self, trustee_project_name, trustee_username, trustee_domain_name, trustor_project_name, trustor_username, trustor_domain_name, roles=[], impersonation=True, ): """ Trustee == Consumer Trustor == Resource Owner Given the *names* of projects, users, and domains gather all required information for a Trust-Create create a new trust object NOTE: we set impersonation to True -- it has a 'normal default' of False! """ default_roles = [{"name": "admin"}] trustor_domain = self.openstack_sdk.identity.find_domain(trustor_domain_name) if not trustor_domain: raise ValueError("Could not find trustor domain named %s" % trustor_domain_name) trustee_domain = self.openstack_sdk.identity.find_domain(trustee_domain_name) if not trustee_domain: raise ValueError("Could not find trustee domain named %s" % trustee_domain_name) trustee_user = self.get_user(trustee_username, domain_id=trustee_domain.id) # trustee_project = self.get_project( # trustee_username, domain_name=trustee_domain.id) trustor_user = self.get_user(trustor_username, domain_id=trustor_domain.id) trustor_project = self.get_project(trustor_project_name, domain_id=trustor_domain.id) if not roles: roles = default_roles new_trust = self.openstack_sdk.identity.create_trust( impersonation=impersonation, project_id=trustor_project.id, trustor_user_id=trustor_user.id, trustee_user_id=trustee_user.id, roles=roles, domain_id=trustee_domain.id, ) return new_trust def list_trusts(self): return [t for t in self.openstack_sdk.identity.trusts()] def list_projects(self, **kwargs): if self.identity_version > 2: kwargs = self._parse_domain_kwargs(kwargs, domain_override="domain") return self.user_manager.list_projects(**kwargs) def list_roles(self, **kwargs): """ Keystone already accepts 'domain_name' to restrict what roles to return """ return self.user_manager.keystone.roles.list(**kwargs) def get_role(self, role_name_or_id, **list_kwargs): if self.identity_version > 2: list_kwargs = self._parse_domain_kwargs(list_kwargs) role_list = self.list_roles(**list_kwargs) found_roles = [role for role in role_list if role.id == role_name_or_id or role.name == role_name_or_id] if not found_roles: return None if len(found_roles) > 1: raise Exception("role name/id %s matched more than one value -- Fix the code" % (role_name_or_id,)) return found_roles[0] def get_user(self, user_name_or_id, **list_kwargs): user_list = self.list_users(**list_kwargs) found_users = [user for user in user_list if user.id == user_name_or_id or user.name == user_name_or_id] if not found_users: return None if len(found_users) > 1: raise Exception("User name/id %s matched more than one value -- Fix the code" % (user_name_or_id,)) return found_users[0] def _parse_domain_kwargs(self, kwargs, domain_override="domain_id", default_domain="default"): """ CLI's replace domain_name with the actual domain. We replicate that functionality to avoid operator-frustration. """ domain_key = "domain_name" if self.identity_version <= 2: return kwargs if domain_override in kwargs: if domain_key in kwargs: kwargs.pop(domain_key) return kwargs if domain_key not in kwargs: kwargs[domain_key] = default_domain # Set to default domain domain_name_or_id = kwargs.get(domain_key) domain = self.openstack_sdk.identity.find_domain(domain_name_or_id) if not domain: raise ValueError("Could not find domain %s by name or id." % domain_name_or_id) kwargs.pop(domain_key, "") kwargs[domain_override] = domain.id return kwargs def list_users(self, **kwargs): if self.identity_version > 2: kwargs = self._parse_domain_kwargs(kwargs, domain_override="domain") return self.user_manager.keystone.users.list(**kwargs) def get_quota_limit(self, username, project_name): limits = {} abs_limits = self.get_absolute_limits() user_limits = self.get_user_limits(username, project_name) if abs_limits: limits.update(abs_limits) if user_limits: limits.update(user_limits) return limits def get_absolute_limits(self): limits = {} os_limits = self.admin_driver._connection.ex_get_limits() try: absolute_limits = os_limits["absolute"] limits["cpu"] = absolute_limits["maxTotalCores"] limits["floating_ips"] = absolute_limits["maxTotalFloatingIps"] limits["instances"] = absolute_limits["maxTotalInstances"] limits["keypairs"] = absolute_limits["maxTotalKeypairs"] limits["ram"] = absolute_limits["maxTotalRAMSize"] except: logger.exception("The method for 'reading' absolute limits has changed!") return limits def get_user_limits(self, username, project_name): limits = {} try: user_id = self.get_user(username).id except: logger.exception("Failed to find user %s" % username) raise ValueError("Unknown user %s" % username) try: project_id = self.get_project(project_name).id except: logger.exception("Failed to find project %s" % project_name) raise ValueError("Unknown project %s" % project_name) user_limits = self._ex_list_quota_for_user(user_id, project_id) if not user_limits: return limits try: user_quota = user_limits["quota_set"] limits["cpu"] = user_quota["cores"] limits["floating_ips"] = user_quota["floating_ips"] limits["instances"] = user_quota["instances"] limits["keypairs"] = user_quota["key_pairs"] limits["ram"] = user_quota["ram"] except: logger.exception("The method for 'reading' absolute limits has changed!") return limits def _ex_list_quota_for_user(self, user_id, tenant_id): """ """ server_resp = self.admin_driver._connection.connection.request( "/os-quota-sets/%s?user_id=%s" % (tenant_id, user_id) ) quota_obj = server_resp.object return quota_obj def list_usergroup_names(self): return [user.name for (user, project) in self.list_usergroups()] def list_usergroups(self): """ TODO: This function is AWFUL just scrap it. """ users = self.list_users() groups = self.list_projects() usergroups = [] admin_usernames = self.core_provider.list_admin_names() for group in groups: for user in users: if user.name in admin_usernames: continue if user.name in group.name: usergroups.append((user, group)) break return usergroups def _get_horizon_url(self, tenant_id): parsed_url = urlparse(self.provider_creds["auth_url"]) return "https://%s/horizon/auth/switch/%s/?next=/horizon/project/" % (parsed_url.hostname, tenant_id) def get_openstack_clients(self, username, password=None, tenant_name=None): # Build credentials for each manager all_creds = self._get_openstack_credentials(username, password, tenant_name) # Initialize managers with respective credentials all_clients = self.get_user_clients(all_creds) openstack_sdk = self.get_openstack_sdk_client(all_creds) neutron = self.get_neutron_client(all_creds) glance = self.get_glance_client(all_creds) all_clients.update( { "glance": glance, "neutron": neutron, "openstack_sdk": openstack_sdk, "horizon": self._get_horizon_url(all_clients["keystone"].tenant_id), } ) return all_clients def get_openstack_client(self, identity, client_name): identity_creds = self.parse_identity(identity) username = identity_creds["username"] password = identity_creds["password"] project_name = identity_creds["tenant_name"] all_creds = self._get_openstack_credentials(username, password, project_name) if client_name == "neutron": return self.get_neutron_client(all_creds) elif client_name == "glance": return self.get_glance_client(all_creds) elif client_name == "keystone": return self.get_user_clients(all_creds)["keystone"] elif client_name == "nova": return self.get_user_clients(all_creds)["nova"] elif client_name == "swift": return self.get_user_clients(all_creds)["swift"] elif client_name == "openstack": return self.get_openstack_sdk_client(all_creds) else: raise ValueError("Invalid client_name %s" % client_name) def get_glance_client(self, all_creds): image_creds = self._build_image_creds(all_creds) _, _, glance = self.image_manager._new_connection(**image_creds) return glance def get_neutron_client(self, all_creds): net_creds = self._build_network_creds(all_creds) neutron = self.network_manager.new_connection(**net_creds) return neutron def get_user_clients(self, all_creds): user_creds = self._build_user_creds(all_creds) (keystone, nova, swift) = self.user_manager.new_connection(**user_creds) return {"keystone": keystone, "nova": nova, "swift": swift} def get_openstack_sdk_client(self, all_creds): sdk_creds = self._build_sdk_creds(all_creds) openstack_sdk = _connect_to_openstack_sdk(**sdk_creds) return openstack_sdk def _get_openstack_credentials(self, username, password=None, tenant_name=None): if not tenant_name: tenant_name = self.get_project_name_for(username) if not password: password = self.hashpass(tenant_name) version = self.user_manager.keystone_version() if version == 2: ex_version = "2.0_password" elif version == 3: ex_version = "3.x_password" osdk_creds = { "auth_url": self.user_manager.nova.client.auth_url.replace("/v3", "").replace("/v2.0", ""), "admin_url": self.user_manager.keystone._management_url.replace("/v2.0", "").replace("/v3", ""), "ex_force_auth_version": ex_version, "region_name": self.user_manager.nova.client.region_name, "username": username, "password": password, "tenant_name": tenant_name, } return osdk_creds # Credential manipulaters def _libcloud_to_openstack(self, credentials): credentials["username"] = credentials.pop("key") credentials["password"] = credentials.pop("secret") credentials["tenant_name"] = credentials.pop("ex_tenant_name") return credentials def _base_network_creds(self): """ These credentials should be used when another user/pass/tenant combination will be used NOTE: JETSTREAM auth_url to be '/v3' """ net_args = self.provider_creds.copy() # NOTE: The neutron 'auth_url' is the ADMIN_URL net_args["auth_url"] = net_args.pop("admin_url").replace("/tokens", "").replace("/v2.0", "").replace("/v3", "") if self.identity_version == 3: auth_prefix = "/v3" elif self.identity_version == 2: auth_prefix = "/v2.0" if auth_prefix not in net_args["auth_url"]: net_args["auth_url"] += auth_prefix return net_args def _build_network_creds(self, credentials): """ Credentials - dict() return the credentials required to build a "NetworkManager" object NOTE: JETSTREAM auth_url to be '/v3' """ net_args = credentials.copy() # Required: net_args.get("username") net_args.get("password") net_args.get("tenant_name") net_args.get("router_name") net_args.get("region_name") # NOTE: The neutron 'auth_url' is the ADMIN_URL net_args["auth_url"] = net_args.pop("admin_url").replace("/tokens", "") # Ignored: net_args.pop("location", None) net_args.pop("ex_project_name", None) net_args.pop("ex_force_auth_version", None) auth_url = net_args.get("auth_url") if self.identity_version == 3: auth_prefix = "/v3" elif self.identity_version == 2: auth_prefix = "/v2.0" net_args["auth_url"] = auth_url.replace("/v2.0", "").replace("/v3", "").replace("/tokens", "") if auth_prefix not in net_args["auth_url"]: net_args["auth_url"] += auth_prefix return net_args def _build_image_creds(self, credentials): """ Credentials - dict() return the credentials required to build a "UserManager" object NOTE: Expects auth_url to be '/v2.0/tokens' NOTE: JETSTREAM auth_url to be '/v3' """ img_args = credentials.copy() # Required: for required_arg in ["username", "password", "tenant_name", "auth_url", "region_name"]: if required_arg not in img_args or not img_args[required_arg]: raise ValueError("ImageManager is missing a Required Argument: %s" % required_arg) ex_auth_version = img_args.get("ex_force_auth_version", "2.0_password") # Supports v2.0 or v3 Identity if ex_auth_version.startswith("2"): auth_url_prefix = "/v2.0/tokens" auth_version = "v2.0" elif ex_auth_version.startswith("3"): auth_url_prefix = "/v3/tokens" auth_version = "v3" img_args["version"] = auth_version img_args["auth_url"] = ( img_args.get("auth_url", "").replace("/v2.0", "").replace("/tokens", "").replace("/v3", "") ) if auth_url_prefix not in img_args["auth_url"]: img_args["auth_url"] += auth_url_prefix return img_args def _build_user_creds(self, credentials): """ Credentials - dict() return the credentials required to build a "UserManager" object NOTE: Expects auth_url to be '/v2.0' """ user_args = credentials.copy() # Required args: user_args.get("username") user_args.get("password") user_args.get("tenant_name") ex_auth_version = user_args.pop("ex_force_auth_version", "2.0_password") # Supports v2.0 or v3 Identity if ex_auth_version.startswith("2"): auth_url_prefix = "/v2.0/" auth_version = "v2.0" elif ex_auth_version.startswith("3"): auth_url_prefix = "/v3/" auth_version = "v3" auth_url = user_args.get("auth_url") user_args["auth_url"] = auth_url.replace("/v2.0", "").replace("/tokens", "") if auth_url_prefix not in user_args["auth_url"]: user_args["auth_url"] += auth_url_prefix user_args.get("region_name") user_args["version"] = user_args.get("version", auth_version) # Removable args: user_args.pop("admin_url", None) user_args.pop("location", None) user_args.pop("router_name", None) user_args.pop("ex_project_name", None) return user_args def _build_sdk_creds(self, credentials): """ Credentials - dict() return the credentials required to build an "Openstack SDK" connection NOTE: Expects auth_url to be ADMIN aand be '/v3' """ os_args = credentials.copy() # Required args: os_args.get("username") os_args.get("password") if "tenant_name" in os_args and "project_name" not in os_args: os_args["project_name"] = os_args.get("tenant_name") os_args.get("region_name") ex_auth_version = os_args.pop("ex_force_auth_version", "2.0_password") # Supports v2.0 or v3 Identity if ex_auth_version.startswith("2"): auth_url_prefix = "/v2.0/" auth_version = "v2.0" elif ex_auth_version.startswith("3"): auth_url_prefix = "/v3/" auth_version = "v3" os_args["auth_url"] = os_args.get("auth_url").replace("/tokens", "").replace("/v2.0", "").replace("/v3", "") # NOTE: Openstack 'auth_url' is ACTUALLY the admin url. if auth_url_prefix not in os_args["admin_url"]: os_args["auth_url"] = os_args["admin_url"] + auth_url_prefix if "project_domain_name" not in os_args: os_args["project_domain_name"] = "default" if "user_domain_name" not in os_args: os_args["user_domain_name"] = "default" if "identity_api_version" not in os_args: os_args[ "identity_api_version" ] = 3 # NOTE: this is what we use to determine whether or not to make openstack_sdk # Removable args: os_args.pop("ex_force_auth_version", None) os_args.pop("admin_url", None) os_args.pop("location", None) os_args.pop("router_name", None) os_args.pop("ex_project_name", None) os_args.pop("ex_tenant_name", None) os_args.pop("tenant_name", None) return os_args
class AccountDriver(CachedAccountDriver): user_manager = None image_manager = None network_manager = None core_provider = None MASTER_RULES_LIST = [ ("ICMP", 0, 255), # FTP Access ("UDP", 20, 20), # FTP data transfer ("TCP", 20, 21), # FTP control # SSH & Telnet Access ("TCP", 22, 23), ("UDP", 22, 23), # SMTP Mail # HTTP Access ("TCP", 80, 80), # POP Mail # SFTP Access ("TCP", 115, 115), # SQL Access # IMAP Access # SNMP Access # LDAP Access ("TCP", 389, 389), ("UDP", 389, 389), # HTTPS Access ("TCP", 443, 443), # LDAPS Access ("TCP", 636, 636), ("UDP", 636, 636), # Open up >1024 ("TCP", 1024, 4199), ("UDP", 1024, 4199), # SKIP PORT 4200.. See Below ("TCP", 4201, 65535), ("UDP", 4201, 65535), # Poke hole in 4200 for iPlant VMs proxy-access only (Shellinabox) ("TCP", 4200, 4200, "128.196.0.0/16"), ("UDP", 4200, 4200, "128.196.0.0/16"), ("TCP", 4200, 4200, "150.135.0.0/16"), ("UDP", 4200, 4200, "150.135.0.0/16"), ] def clear_cache(self): self.admin_driver.provider.machineCls.invalidate_provider_cache( self.admin_driver.provider) return self.admin_driver def _init_by_provider(self, provider, *args, **kwargs): from service.driver import get_esh_driver self.core_provider = provider provider_creds = provider.get_credentials() self.provider_creds = provider_creds admin_identity = provider.admin admin_creds = admin_identity.get_credentials() self.admin_driver = get_esh_driver(admin_identity) admin_creds = self._libcloud_to_openstack(admin_creds) all_creds = {'location': provider.get_location()} all_creds.update(admin_creds) all_creds.update(provider_creds) return all_creds def __init__(self, provider=None, *args, **kwargs): super(AccountDriver, self).__init__() if provider: all_creds = self._init_by_provider(provider, *args, **kwargs) else: all_creds = kwargs if 'location' in all_creds: self.namespace = "Atmosphere_OpenStack:%s" % all_creds['location'] else: logger.info("Using default namespace.. Could cause conflicts if " "switching between providers. To avoid ambiguity, " "provide the kwarg: location='provider_prefix'") # Build credentials for each manager self.user_creds = self._build_user_creds(all_creds) self.image_creds = self._build_image_creds(all_creds) self.net_creds = self._build_network_creds(all_creds) # Initialize managers with respective credentials self.user_manager = UserManager(**self.user_creds) self.image_manager = ImageManager(**self.image_creds) self.network_manager = NetworkManager(**self.net_creds) def create_account(self, username, password=None, project_name=None, role_name=None, quota=None, max_quota=False): """ Create (And Update "latest changes") to an account """ if not self.core_provider: raise Exception("AccountDriver not initialized by provider," " cannot create identity. For account creation use" " build_account()") if username in self.core_provider.list_admin_names(): return (username, password, project) = self.build_account( username, password, project_name, role_name, max_quota) ident = self.create_identity(username, password, project.name, quota=quota, max_quota=max_quota) return ident def build_account(self, username, password, project_name=None, role_name=None, max_quota=False): finished = False # Attempt account creation while not finished: try: if not password: password = self.hashpass(username) if not project_name: project_name = username # 1. Create Project: should exist before creating user project = self.user_manager.get_project(project_name) if not project: project = self.user_manager.create_project(project_name) # 2. Create User (And add them to the project) user = self.get_user(username) if not user: logger.info("Creating account: %s - %s - %s" % (username, password, project)) user = self.user_manager.create_user(username, password, project) # 3.1 Include the admin in the project # TODO: providercredential initialization of # "default_admin_role" self.user_manager.include_admin(project_name) # 3.2 Check the user has been given an appropriate role if not role_name: role_name = "_member_" self.user_manager.add_project_membership( project_name, username, role_name) # 4. Create a security group -- SUSPENDED.. Will occur on # instance launch instead. # self.init_security_group(user, password, project, # project.name, # self.MASTER_RULES_LIST) # 5. Create a keypair to use when launching with atmosphere self.init_keypair(user.name, password, project.name) finished = True except ConnectionError: logger.exception("Connection reset by peer. " "Waiting for one minute.") time.sleep(60) # Wait one minute except OverLimit: logger.exception("OverLimit on POST requests. " "Waiting for one minute.") time.sleep(60) # Wait one minute return (username, password, project) def init_keypair(self, username, password, project_name): keyname = settings.ATMOSPHERE_KEYPAIR_NAME with open(settings.ATMOSPHERE_KEYPAIR_FILE, "r") as pub_key_file: public_key = pub_key_file.read() return self.get_or_create_keypair(username, password, project_name, keyname, public_key) def init_security_group(self, username, password, project_name, security_group_name, rules_list): # 4.1. Update the account quota to hold a larger number of # roles than what is necessary user = self.user_manager.keystone.users.find(name=username) project = self.user_manager.keystone.tenants.find(name=project_name) nc = self.user_manager.nova rule_max = max(len(rules_list), 100) nc.quotas.update(project.id, security_group_rules=rule_max) # Change the description of the security group to match the project # name try: # Create the default security group nova = self.user_manager.build_nova(username, password, project_name) sec_groups = nova.security_groups.list() if not sec_groups: nova.security_group.create("default", project_name) self.network_manager.rename_security_group(project) except ConnectionError as ce: logger.exception( "Failed to establish connection." " Security group creation FAILED") return None except NeutronClientException as nce: if nce.status_code != 404: logger.exception("Encountered unknown exception while renaming" " the security group") return None # Start creating security group return self.user_manager.build_security_group( user.name, password, project.name, security_group_name, rules_list) def add_rules_to_security_groups(self, core_identity_list, security_group_name, rules_list): for identity in core_identity_list: creds = self.parse_identity(identity) sec_group = self.user_manager.find_security_group( creds["username"], creds["password"], creds["tenant_name"], security_group_name) if not sec_group: raise Exception("No security gruop found matching name %s" % security_group_name) self.user_manager.add_security_group_rules( creds["username"], creds["password"], creds["tenant_name"], security_group_name, rules_list) def get_or_create_keypair(self, username, password, project_name, keyname, public_key): """ keyname - Name of the keypair public_key - Contents of public key in OpenSSH format """ clients = self.get_openstack_clients(username, password, project_name) nova = clients["nova"] keypairs = nova.keypairs.list() for kp in keypairs: if kp.name == keyname: if kp.public_key != public_key: raise Exception( "Mismatched public key found for keypair named: %s" ". Expected: %s Original: %s" % (keyname, public_key, kp.public_key)) return (kp, False) return (self.create_keypair( username, password, project_name, keyname, public_key), True) def create_keypair(self, username, password, project_name, keyname, public_key): """ keyname - Name of the keypair public_key - Contents of public key in OpenSSH format """ clients = self.get_openstack_clients(username, password, project_name) nova = clients["nova"] keypair = nova.keypairs.create( keyname, public_key=public_key) return keypair def rebuild_security_groups(self, core_identity, rules_list=None): creds = self.parse_identity(core_identity) if not rules_list: rules_list = self.MASTER_RULES_LIST return self.user_manager.build_security_group( creds["username"], creds["password"], creds["tenant_name"], creds["tenant_name"], rules_list, rebuild=True) def parse_identity(self, core_identity): identity_creds = self._libcloud_to_openstack( core_identity.get_credentials()) return identity_creds def clean_credentials(self, credential_dict): """ This function cleans up a dictionary of credentials. After running this function: * Erroneous dictionary keys are removed * Missing credentials are listed """ creds = ["username", "password", "project_name"] missing_creds = [] # 1. Remove non-credential information from the dict for key in credential_dict.keys(): if key not in creds: credential_dict.pop(key) # 2. Check the dict has all the required credentials for c in creds: if not hasattr(credential_dict, c): missing_creds.append(c) return missing_creds def create_identity(self, username, password, project_name, quota=None, max_quota=False, account_admin=False): if not self.core_provider: raise Exception("AccountDriver not initialized by provider, " "cannot create identity") identity = Identity.create_identity( username, self.core_provider.location, quota=quota, # Flags.. max_quota=max_quota, account_admin=account_admin, # Pass in credentials with cred_ namespace cred_key=username, cred_secret=password, cred_ex_tenant_name=project_name, cred_ex_project_name=project_name) # Return the identity return identity def rebuild_project_network(self, username, project_name, dns_nameservers=[]): self.network_manager.delete_project_network(username, project_name) net_args = self._base_network_creds() self.network_manager.create_project_network( username, self.hashpass(username), project_name, get_unique_number=get_uid_number, dns_nameservers=dns_nameservers, **net_args) return True def delete_security_group(self, identity): identity_creds = self.parse_identity(identity) project_name = identity_creds["tenant_name"] project = self.user_manager.keystone.tenants.find(name=project_name) sec_group_r = self.network_manager.neutron.list_security_groups( tenant_id=project.id) sec_groups = sec_group_r["security_groups"] for sec_group in sec_groups: self.network_manager.neutron.delete_security_group(sec_group["id"]) return True def delete_network(self, identity, remove_network=True): # Core credentials need to be converted to openstack names identity_creds = self.parse_identity(identity) username = identity_creds["username"] project_name = identity_creds["tenant_name"] # Convert from libcloud names to openstack client names return self.network_manager.delete_project_network( username, project_name, remove_network=remove_network) def create_network(self, identity): # Core credentials need to be converted to openstack names identity_creds = self.parse_identity(identity) username = identity_creds["username"] password = identity_creds["password"] project_name = identity_creds["tenant_name"] dns_nameservers = [ dns_server.ip_address for dns_server in identity.provider.dns_server_ips.order_by('order')] # Convert from libcloud names to openstack client names net_args = self._base_network_creds() return self.network_manager.create_project_network( username, password, project_name, get_unique_number=get_uid_number, dns_nameservers=dns_nameservers, **net_args) # Useful methods called from above.. def get_or_create_user(self, username, password=None, project=None, admin=False): user = self.get_user(username) if user: return user user = self.create_user(username, password, usergroup, admin) return user def create_user(self, username, password=None, usergroup=True, admin=False): if not password: password = self.hashpass(username) if usergroup: (project, user, role) = self.user_manager.add_usergroup( username, password, True, admin) else: user = self.user_manager.add_user(username, password) project = self.user_manager.get_project(username) # TODO: Instead, return user.get_user match, or call it if you have # to.. return user def delete_account(self, username, projectname): self.os_delete_account(username, projectname) Identity.delete_identity(username, self.core_provider.location) def os_delete_account(self, username, projectname): project = self.user_manager.get_project(projectname) # 1. Network cleanup if project: self.network_manager.delete_project_network(username, projectname) # 2. Role cleanup (Admin too) self.user_manager.delete_all_roles(username, projectname) adminuser = self.user_manager.keystone.username self.user_manager.delete_all_roles(adminuser, projectname) # 3. Project cleanup self.user_manager.delete_project(projectname) # 4. User cleanup user = self.user_manager.get_user(username) if user: self.user_manager.delete_user(username) return True def hashpass(self, username): # TODO: Must be better. return sha1(username).hexdigest() def get_project_name_for(self, username): """ This should always map project to user For now, they are identical.. """ return username def _get_image(self, *args, **kwargs): return self.image_manager.get_image(*args, **kwargs) # For one-time caching def _list_all_images(self, *args, **kwargs): return self.image_manager.list_images(*args, **kwargs) def tenant_instances_map( self, status_list=[], match_all=False, include_empty=False): """ Maps 'Tenant' objects to all the 'owned instances' as listed by the admin driver Optional fields: * status_list (list) - If provided, only include instance if it's status/tmp_status matches a value in the list. * match_all (bool) - If True, instances must match ALL words in the list. * include_empty (bool) - If True, include ALL tenants in the map. """ all_projects = self.list_projects() all_instances = self.list_all_instances() if include_empty: project_map = {proj: [] for proj in all_projects} else: project_map = {} for instance in all_instances: try: # NOTE: will someday be 'projectId' tenant_id = instance.extra['tenantId'] project = [p for p in all_projects if p.id == tenant_id][0] except (ValueError, KeyError): raise Exception( "The implementaion for recovering a tenant id has changed. Update the code base above this line!") metadata = instance._node.extra.get('metadata', {}) instance_status = instance.extra.get('status') task = instance.extra.get('task') tmp_status = metadata.get('tmp_status', '') if status_list: if match_all: truth = all( True if ( status_name and status_name in [ instance_status, task, tmp_status]) else False for status_name in status_list) else: truth = any( True if ( status_name and status_name in [ instance_status, task, tmp_status]) else False for status_name in status_list) if not truth: logger.info( "Found an instance:%s for tenant:%s but skipped because %s could be found in the list: (%s - %s - %s)" % (instance.id, project.name, "none of the status_names" if not match_all else "not all of the status names", instance_status, task, tmp_status)) continue instance_list = project_map.get(project, []) instance_list.append(instance) project_map[project] = instance_list return project_map def list_all_instances(self, **kwargs): return self.admin_driver.list_all_instances(**kwargs) def list_all_images(self, **kwargs): return self.image_manager.list_images(**kwargs) def get_project_by_id(self, project_id): return self.user_manager.get_project_by_id(project_id) def get_project(self, project_name): return self.user_manager.get_project(project_name) def _make_tenant_id_map(self): all_projects = self.list_projects() tenant_id_map = {project.id: project.name for project in all_projects} return tenant_id_map def list_projects(self): return self.user_manager.list_projects() def get_user(self, user): return self.user_manager.get_user(user) def list_users(self): return self.user_manager.list_users() def list_usergroup_names(self): return [user.name for (user, project) in self.list_usergroups()] def list_usergroups(self): """ TODO: This function is AWFUL just scrap it. """ users = self.list_users() groups = self.list_projects() usergroups = [] admin_usernames = self.core_provider.list_admin_names() for group in groups: for user in users: if user.name in admin_usernames: continue if user.name in group.name: usergroups.append((user, group)) break return usergroups def _get_horizon_url(self, tenant_id): parsed_url = urlparse(self.provider_creds["auth_url"]) return "https://%s/horizon/auth/switch/%s/?next=/horizon/project/" %\ (parsed_url.hostname, tenant_id) def get_openstack_clients(self, username, password=None, tenant_name=None): # TODO: I could replace with identity.. but should I? from rtwo.drivers.common import _connect_to_openstack_sdk user_creds = self._get_openstack_credentials( username, password, tenant_name) neutron = self.network_manager.new_connection(**user_creds) keystone, nova, glance = self.image_manager._new_connection( **user_creds) openstack_sdk = _connect_to_openstack_sdk(**user_creds) return { "glance": glance, "keystone": keystone, "nova": nova, "neutron": neutron, "horizon": self._get_horizon_url(keystone.tenant_id) } def _get_openstack_credentials(self, username, password=None, tenant_name=None): if not tenant_name: tenant_name = self.get_project_name_for(username) if not password: password = self.hashpass(tenant_name) user_creds = { "auth_url": self.user_manager.nova.client.auth_url, "region_name": self.user_manager.nova.client.region_name, "username": username, "password": password, "tenant_name": tenant_name } return user_creds # Credential manipulaters def _libcloud_to_openstack(self, credentials): credentials["username"] = credentials.pop("key") credentials["password"] = credentials.pop("secret") credentials["tenant_name"] = credentials.pop("ex_tenant_name") return credentials def _base_network_creds(self): """ These credentials should be used when another user/pass/tenant combination will be used """ net_args = self.provider_creds.copy() net_args["auth_url"] = net_args.pop("admin_url").replace("/tokens", "") return net_args def _build_network_creds(self, credentials): """ Credentials - dict() return the credentials required to build a "NetworkManager" object """ net_args = credentials.copy() # Required: net_args.get("username") net_args.get("password") net_args.get("tenant_name") net_args.get("router_name") net_args.get("region_name") # Ignored: net_args["auth_url"] = net_args.pop("admin_url").replace("/tokens", "") net_args.pop("location", None) net_args.pop("ex_project_name", None) return net_args def _build_image_creds(self, credentials): """ Credentials - dict() return the credentials required to build a "UserManager" object """ img_args = credentials.copy() # Required: for required_arg in [ "username", "password", "tenant_name", "auth_url", "region_name"]: if required_arg not in img_args or not img_args[required_arg]: raise ValueError( "ImageManager is missing a Required Argument: %s" % required_arg) return img_args def _build_user_creds(self, credentials): """ Credentials - dict() return the credentials required to build a "UserManager" object """ user_args = credentials.copy() # Required args: user_args.get("username") user_args.get("password") user_args.get("tenant_name") user_args["auth_url"] = user_args.get("auth_url")\ .replace("/tokens", "") user_args.get("region_name") # Removable args: user_args.pop("admin_url", None) user_args.pop("location", None) user_args.pop("router_name", None) user_args.pop("ex_project_name", None) return user_args