Example #1
0
 def __init__(self, config, cloud):
     super(KeystoneIdentity, self).__init__()
     self.config = config
     self.cloud = cloud
     self.filter_tenant_id = None
     self.postman = None
     if self.config.mail.server != "-":
         self.postman = Postman(self.config['mail']['username'],
                                self.config['mail']['password'],
                                self.config['mail']['from_addr'],
                                self.config['mail']['server'])
     self.mysql_connector = cloud.mysql_connector('keystone')
     self.templater = Templater()
     self.generator = GeneratorPassword()
Example #2
0
 def __init__(self, config, cloud):
     super(KeystoneIdentity, self).__init__()
     self.config = config
     self.keystone_client = self.proxy(self.get_client(), config)
     self.mysql_connector = cloud.mysql_connector
     self.cloud = cloud
     self.postman = None
     if self.config.mail.server != "-":
         self.postman = Postman(self.config['mail']['username'],
                                self.config['mail']['password'],
                                self.config['mail']['from_addr'],
                                self.config['mail']['server'])
     self.templater = Templater()
     self.generator = GeneratorPassword()
Example #3
0
 def __init__(self, config, cloud):
     super(KeystoneIdentity, self).__init__()
     self.config = config
     self.cloud = cloud
     self.filter_tenant_id = None
     self.postman = None
     if self.config.mail.server != "-":
         self.postman = Postman(self.config['mail']['username'],
                                self.config['mail']['password'],
                                self.config['mail']['from_addr'],
                                self.config['mail']['server'])
     self.mysql_connector = cloud.mysql_connector('keystone')
     self.templater = Templater()
     self.generator = GeneratorPassword()
Example #4
0
class KeystoneIdentity(identity.Identity):
    """The main class for working with OpenStack Keystone Identity Service."""

    def __init__(self, config, cloud):
        super(KeystoneIdentity, self).__init__()
        self.config = config
        self.cloud = cloud
        self.filter_tenant_id = None
        self.postman = None
        if self.config.mail.server != "-":
            self.postman = Postman(self.config['mail']['username'],
                                   self.config['mail']['password'],
                                   self.config['mail']['from_addr'],
                                   self.config['mail']['server'])
        self.mysql_connector = cloud.mysql_connector('keystone')
        self.templater = Templater()
        self.generator = GeneratorPassword()

    @property
    def keystone_client(self):
        return self.proxy(self._get_client_by_creds(), self.config)

    @staticmethod
    def convert(identity_obj, cfg):
        """Convert OpenStack Keystone object to CloudFerry object.

        :param identity_obj:    Direct OpenStack Keystone object to convert,
                                supported objects: tenants, users and roles;
        :param cfg:             Cloud config.
        """

        if isinstance(identity_obj, keystone_client.tenants.Tenant):
            return {'tenant': {'name': identity_obj.name,
                               'id': identity_obj.id,
                               'description': identity_obj.description},
                    'meta': {}}

        elif isinstance(identity_obj, keystone_client.users.User):
            overwirte_user_passwords = cfg.migrate.overwrite_user_passwords
            return {'user': {'name': identity_obj.name,
                             'id': identity_obj.id,
                             'email': getattr(identity_obj, 'email', ''),
                             'tenantId': getattr(identity_obj, 'tenantId', '')
                             },
                    'meta': {
                        'overwrite_password': overwirte_user_passwords}}

        elif isinstance(identity_obj, keystone_client.roles.Role):
            return {'role': {'name': identity_obj.name,
                             'id': identity_obj},
                    'meta': {}}

        LOG.error('KeystoneIdentity converter has received incorrect value. '
                  'Please pass to it only tenants, users or role objects.')
        return None

    def has_tenants_by_id_cached(self):
        tenants = set([t.id for t in self.keystone_client.tenants.list()])

        def func(tenant_id):
            return tenant_id in tenants

        return func

    def read_info(self, **kwargs):
        info = {'tenants': [],
                'users': [],
                'roles': []}

        if kwargs.get('tenant_id'):
            self.filter_tenant_id = kwargs['tenant_id'][0]

        tenant_list = self.get_tenants_list()
        for tenant in tenant_list:
            tnt = self.convert(tenant, self.config)
            info['tenants'].append(tnt)
        user_list = self.get_users_list()
        has_tenants_by_id_cached = self.has_tenants_by_id_cached()
        has_roles_by_ids_cached = self._get_user_roles_cached()
        for user in user_list:
            usr = self.convert(user, self.config)
            if has_tenants_by_id_cached(getattr(user, 'tenantId', '')):
                info['users'].append(usr)
            else:
                LOG.info("User's '%s' primary tenant '%s' is deleted, "
                         "finding out if user is a member of other tenants",
                         user.name, getattr(user, 'tenantId', ''))
                for t in tenant_list:
                    roles = has_roles_by_ids_cached(user.id, t.id)
                    if roles:
                        LOG.info("Setting tenant '%s' for user '%s' as "
                                 "primary", t.name, user.name)
                        usr['user']['tenantId'] = t.id
                        info['users'].append(usr)
                        break
        for role in self.get_roles_list():
            rl = self.convert(role, self.config)
            info['roles'].append(rl)
        info['user_tenants_roles'] = \
            self._get_user_tenants_roles(tenant_list, user_list)
        if self.config['migrate']['keep_user_passwords']:
            info['user_passwords'] = self._get_user_passwords()
        return info

    def deploy(self, info):
        LOG.info("Identity objects deployment started")
        tenants = info['tenants']
        users = info['users']
        roles = info['user_tenants_roles']
        self._deploy_tenants(tenants)
        self._deploy_roles(info['roles'])
        self._deploy_users(users, tenants)
        if not self.config.migrate.migrate_users:
            users = info['users'] = self._update_users_info(users)
        if self.config['migrate']['keep_user_passwords']:
            passwords = info['user_passwords']
            self._upload_user_passwords(users, passwords)
        self._upload_user_tenant_roles(roles, users, tenants)
        LOG.info("Done")

    def get_client(self):
        """ Getting keystone client using authentication with admin auth token.

        :return: OpenStack Keystone Client instance
        """

        return self.keystone_client

    def _get_client_by_creds(self):
        """Authenticating with a user name and password.

        :return: OpenStack Keystone Client instance
        """

        kwargs = {
            "username": self.config.cloud.user,
            "password": self.config.cloud.password,
            "tenant_name": self.config.cloud.tenant,
            "auth_url": self.config.cloud.auth_url,
            "cacert": self.config.cloud.cacert,
            "insecure": self.config.cloud.insecure
        }

        if self.config.cloud.region:
            kwargs["region_name"] = self.config.cloud.region

        return keystone_client.Client(**kwargs)

    def get_endpoint_by_service_type(self, service_type, endpoint_type):
        """Getting endpoint URL by service type.

        :param service_type: OpenStack service type (image, compute etc.)
        :param endpoint_type: publicURL or internalURL

        :return: String endpoint of specified OpenStack service
        """

        kwargs = {
            "service_type": service_type,
            "endpoint_type": endpoint_type
        }

        if self.config.cloud.region:
            kwargs['region_name'] = self.config.cloud.region

        return self.keystone_client.service_catalog.url_for(**kwargs)

    def get_tenants_func(self):
        tenants = {tenant.id: tenant.name for tenant in
                   self.get_tenants_list()}

        def func(tenant_id):
            return tenants.get(tenant_id, 'admin')

        return func

    def get_tenant_id_by_name(self, name):
        """ Getting tenant ID by name from keystone. """

        return self.keystone_client.tenants.find(name=name).id

    def get_tenant_by_name(self, tenant_name):
        """ Getting tenant by name from keystone. """

        for tenant in self.get_tenants_list():
            if tenant.name == tenant_name:
                return tenant

    def get_tenant_by_id(self, tenant_id):
        """ Getting tenant by id from keystone. """

        return self.keystone_client.tenants.get(tenant_id)

    def try_get_tenant_name_by_id(self, tenant_id, default=None):
        """ Same as `get_tenant_by_id` but returns `default` in case tenant
        ID is not present """
        try:
            return self.keystone_client.tenants.get(tenant_id).name
        except keystoneclient.exceptions.NotFound:
            LOG.warning("Tenant '%s' not found, returning default value = "
                        "'%s'", tenant_id, default)
            return default

    def get_services_list(self):
        """ Getting list of available services from keystone. """

        return self.keystone_client.services.list()

    def get_tenants_list(self):
        """ Getting list of tenants from keystone. """
        result = []
        ks_tenants = self.keystone_client.tenants
        if self.filter_tenant_id:
            result.append(ks_tenants.find(id=self.filter_tenant_id))

            # public image owners must also be migrated
            image_owners = set()
            glance = self.cloud.resources[utl.IMAGE_RESOURCE]
            for image in glance.get_image_list():
                image_owners.add(image.owner)
            image_owners -= {self.filter_tenant_id}
            for owner in image_owners:
                result.append(ks_tenants.find(id=owner))
        else:
            result = ks_tenants.list()
        return result

    def get_users_list(self):
        """ Getting list of users from keystone. """

        if self.filter_tenant_id:
            tenant_id = self.filter_tenant_id
        else:
            tenant_id = None
        return self.keystone_client.users.list(tenant_id=tenant_id)

    def get_roles_list(self):
        """ Getting list of available roles from keystone. """

        return self.keystone_client.roles.list()

    def try_get_username_by_id(self, user_id, default=None):
        try:
            return self.keystone_client.users.get(user_id).name
        except keystoneclient.exceptions.NotFound:
            return default

    def try_get_user_by_id(self, user_id, default=None):
        if default is None:
            admin_usr = \
                self.try_get_user_by_name(username=self.config.cloud.user)
            default = admin_usr.id
        try:
            return self.keystone_client.users.find(id=user_id)
        except keystoneclient.exceptions.NotFound:
            LOG.warning("User '%s' has not been found, returning default "
                        "value = '%s'", user_id, default)
            return self.keystone_client.users.find(id=default)

    def try_get_user_by_name(self, username, default=None):
        try:
            return self.keystone_client.users.find(name=username)
        except keystoneclient.exceptions.NotFound:
            LOG.warning("User '%s' has not been found, returning default "
                        "value = '%s'", username, default)
            return self.keystone_client.users.find(name=default)

    def roles_for_user(self, user_id, tenant_id):
        """ Getting list of user roles for tenant """

        return self.keystone_client.roles.roles_for_user(user_id, tenant_id)

    def create_role(self, role_name):
        """ Create new role in keystone. """

        return self.keystone_client.roles.create(role_name)

    def create_tenant(self, tenant_name, description=None, enabled=True):
        """ Create new tenant in keystone. """

        return self.keystone_client.tenants.create(tenant_name=tenant_name,
                                                   description=description,
                                                   enabled=enabled)

    def create_user(self, name, password=None, email=None, tenant_id=None,
                    enabled=True):
        """ Create new user in keystone. """

        return self.keystone_client.users.create(name=name,
                                                 password=password,
                                                 email=email,
                                                 tenant_id=tenant_id,
                                                 enabled=enabled)

    def update_tenant(self, tenant_id, tenant_name=None, description=None,
                      enabled=None):
        """Update a tenant with a new name and description."""

        return self.keystone_client.tenants.update(tenant_id,
                                                   tenant_name=tenant_name,
                                                   description=description,
                                                   enabled=enabled)

    def update_user(self, user, **kwargs):
        """Update user data.

        Supported arguments include ``name``, ``email``, and ``enabled``.
        """

        return self.keystone_client.users.update(user, **kwargs)

    def get_auth_token_from_user(self):
        self.keystone_client.authenticate()
        return self.keystone_client.auth_token

    def _deploy_tenants(self, tenants):
        LOG.info('Deploying tenants...')
        dst_tenants = {tenant.name: tenant.id for tenant in
                       self.get_tenants_list()}
        for _tenant in tenants:
            tenant = _tenant['tenant']
            if tenant['name'] not in dst_tenants:
                LOG.debug("Creating tenant '%s'", tenant['name'])
                _tenant['meta']['new_id'] = self.create_tenant(
                    tenant['name'],
                    tenant['description']).id
            else:
                LOG.debug("Tenant '%s' is already present on destination, "
                          "skipping", tenant['name'])
                _tenant['meta']['new_id'] = dst_tenants[tenant['name']]

        LOG.info("Tenant deployment done.")

    def _deploy_users(self, users, tenants):
        dst_users = {user.name: user.id for user in self.get_users_list()}
        tenant_mapped_ids = {tenant['tenant']['id']: tenant['meta']['new_id']
                             for tenant in tenants}

        keep_passwd = self.config['migrate']['keep_user_passwords']
        overwrite_passwd = self.config['migrate']['overwrite_user_passwords']

        for _user in users:
            user = _user['user']
            password = self._generate_password()

            if user['name'] in dst_users:
                # Create users mapping
                _user['meta']['new_id'] = dst_users[user['name']]

                if overwrite_passwd and not keep_passwd:
                    self.update_user(_user['meta']['new_id'],
                                     password=password)
                    self._passwd_notification(user['email'], user['name'],
                                              password)
                continue

            if not self.config.migrate.migrate_users:
                continue

            tenant_id = tenant_mapped_ids[user['tenantId']]
            _user['meta']['new_id'] = self.create_user(user['name'], password,
                                                       user['email'],
                                                       tenant_id).id
            if self.config['migrate']['keep_user_passwords']:
                _user['meta']['overwrite_password'] = True
            else:
                self._passwd_notification(user['email'], user['name'],
                                          password)

    @staticmethod
    def _update_users_info(users):
        """
        Update users info.

        This method is needed for skip users, that have not been migrated to
        destination cloud and that do not exist there. So we leave information
        only about users with mapping and skip those, who don't have the same
        user on the destination cloud. This is done, because another tasks can
        use users mapping.

        :param users: OpenStack Keystone users info;
        :return: List with actual users info.
        """

        users_info = []
        for user in users:
            if user['meta'].get('new_id'):
                users_info.append(user)

        return users_info

    def _passwd_notification(self, email, name, password):
        if not self.postman:
            return
        template = 'templates/email.html'
        self._send_msg(email, 'New password notification',
                       self._render_template(template,
                                             {'name': name,
                                              'password': password}))

    def _deploy_roles(self, roles):
        LOG.info("Role deployment started...")
        dst_roles = {
            role.name.lower(): role.id for role in self.get_roles_list()}
        for _role in roles:
            role = _role['role']
            if role['name'].lower() not in dst_roles:
                LOG.debug("Creating role '%s'", role['name'])
                _role['meta']['new_id'] = self.create_role(role['name']).id
            else:
                LOG.debug("Role '%s' is already present on destination, "
                          "skipping", role['name'])
                _role['meta']['new_id'] = dst_roles[role['name'].lower()]

        LOG.info("Role deployment done.")

    def _get_user_passwords(self):
        info = {}
        for user in self.get_users_list():
            for password in self.mysql_connector.execute(
                    "SELECT password FROM user WHERE id = :user_id",
                    user_id=user.id):
                info[user.name] = password[0]
        return info

    def _get_user_tenants_roles(self, tenant_list=None, user_list=None):
        if tenant_list is None:
            tenant_list = []
        if user_list is None:
            user_list = []
        if not self.config.migrate.optimize_user_role_fetch:
            user_tenants_roles = \
                self._get_user_tenants_roles_by_api(tenant_list,
                                                    user_list)
        else:
            user_tenants_roles = \
                self._get_user_tenants_roles_by_db(tenant_list,
                                                   user_list)
        return user_tenants_roles

    def _get_roles_sql_request(self):
        res = []
        try:
            is_project_metadata = self.mysql_connector.execute(
                "SHOW TABLES LIKE 'user_project_metadata'").rowcount
            if is_project_metadata:  # for grizzly case
                return self.mysql_connector.execute(
                    "SELECT * FROM user_project_metadata")
            is_assignment = self.mysql_connector.execute(
                "SHOW TABLES LIKE 'assignment'").rowcount
            if is_assignment:  # for icehouse case
                res_raw = self.mysql_connector.execute(
                    "SELECT * FROM assignment")
                res_tmp = {}
                for (type_record, actor_id, project_id,
                     role_id, inher_tmp) in res_raw:
                    if (actor_id, project_id) not in res_tmp:
                        res_tmp[(actor_id, project_id)] = {'roles': []}
                    res_tmp[(actor_id, project_id)]['roles'].append(role_id)
                for k, v in res_tmp.iteritems():
                    res.append((k[0], k[1], str(v)))
        except ProgrammingError as e:
            LOG.warn(e.message)
        return res

    def _get_user_roles_cached(self):
        all_roles = {}
        if self.config.migrate.optimize_user_role_fetch:
            res = self._get_roles_sql_request()
            for user_id, tenant_id, roles_field in res:
                roles_ids = ast.literal_eval(roles_field)['roles']
                all_roles[user_id] = {} \
                    if user_id not in all_roles else all_roles[user_id]
                all_roles[user_id][tenant_id] = [] \
                    if tenant_id not in all_roles[user_id] \
                    else all_roles[user_id][tenant_id]
                all_roles[user_id][tenant_id].extend(roles_ids)

        def _get_user_roles(user_id, tenant_id):
            if not self.config.migrate.optimize_user_role_fetch:
                roles = self.roles_for_user(user_id, tenant_id)
            else:
                roles = all_roles.get(user_id, {}).get(tenant_id, [])
            return roles
        return _get_user_roles

    def _get_user_tenants_roles_by_db(self, tenant_list, user_list):
        user_tenants_roles = {u.name: {t.name: [] for t in tenant_list}
                              for u in user_list}
        tenant_ids = {tenant.id: tenant.name for tenant in tenant_list}
        user_ids = {user.id: user.name for user in user_list}
        roles = {r.id: r for r in self.get_roles_list()}
        res = self._get_roles_sql_request()
        for user_id, tenant_id, roles_field in res:
            # skip filtered tenants and users
            if user_id not in user_ids or tenant_id not in tenant_ids:
                continue

            roles_ids = ast.literal_eval(roles_field)['roles']
            user_tenants_roles[user_ids[user_id]][tenant_ids[tenant_id]] = \
                [{'role': {'name': roles[r].name, 'id': r}}
                 for r in roles_ids]
        return user_tenants_roles

    def _get_user_tenants_roles_by_api(self, tenant_list, user_list):
        user_tenants_roles = {u.name: {t.name: [] for t in tenant_list}
                              for u in user_list}
        for user in user_list:
            user_tenants_roles[user.name] = {}
            for tenant in tenant_list:
                roles = []
                for role in self.roles_for_user(user.id, tenant.id):
                    roles.append({'role': {'name': role.name, 'id': role.id}})
                user_tenants_roles[user.name][tenant.name] = roles
        return user_tenants_roles

    def _upload_user_passwords(self, users, user_passwords):
        for _user in users:
            user = _user['user']
            if not _user['meta']['overwrite_password']:
                continue
            self.mysql_connector.execute(
                "UPDATE user SET password = :password WHERE id = :user_id",
                user_id=_user['meta']['new_id'],
                password=user_passwords[user['name']])

    def _upload_user_tenant_roles(self, user_tenants_roles, users, tenants):
        roles_id = {role.name: role.id for role in self.get_roles_list()}
        dst_users_obj = self.get_users_list()
        dst_users = {user.name: user.id for user in dst_users_obj}
        dst_roles = {role.id: role.name for role in self.get_roles_list()}
        _get_user_roles_cached = self._get_user_roles_cached()
        for _user in users:
            user = _user['user']
            if user['name'] not in dst_users:
                continue
            for _tenant in tenants:
                tenant = _tenant['tenant']
                user_roles_objs = _get_user_roles_cached(
                    _user['meta']['new_id'],
                    _tenant['meta']['new_id'])
                exists_roles = [dst_roles[role] if not hasattr(role,
                                                               'name')
                                else role.name
                                for role in user_roles_objs]
                for _role in user_tenants_roles[user['name']][tenant['name']]:
                    role = _role['role']
                    if role['name'] in exists_roles:
                        continue
                    try:
                        self.keystone_client.roles.add_user_role(
                            _user['meta']['new_id'], roles_id[role['name']],
                            _tenant['meta']['new_id'])
                    except ks_exceptions.Conflict:
                        LOG.info("Role '%s' for user '%s' in tenant '%s' "
                                 "already exists, skipping", role['name'],
                                 user['name'], tenant['name'])

    def _generate_password(self):
        return self.generator.get_random_password()

    def _send_msg(self, to, subject, msg):
        if self.postman:
            with self.postman as p:
                p.send(to, subject, msg)

    def _render_template(self, name_file, args):
        if self.templater:
            return self.templater.render(name_file, args)
        else:
            return None

    def check_rabbitmq(self):
        credentials = pika.PlainCredentials(self.config.rabbit.user,
                                            self.config.rabbit.password)

        for host_with_port in self.config.rabbit.hosts.split(","):
            host, port = host_with_port.split(':')
            pika.BlockingConnection(
                pika.ConnectionParameters(host=host.strip(),
                                          port=int(port),
                                          credentials=credentials))
Example #5
0
class KeystoneIdentity(identity.Identity):
    """The main class for working with OpenStack Keystone Identity Service."""

    def __init__(self, config, cloud):
        super(KeystoneIdentity, self).__init__()
        self.config = config
        self.cloud = cloud
        self.filter_tenant_id = None
        self.postman = None
        if self.config.mail.server != "-":
            self.postman = Postman(self.config['mail']['username'],
                                   self.config['mail']['password'],
                                   self.config['mail']['from_addr'],
                                   self.config['mail']['server'])
        self.mysql_connector = cloud.mysql_connector('keystone')
        self.templater = Templater()
        self.generator = GeneratorPassword()

    @property
    def keystone_client(self):
        return self.proxy(self._get_client_by_creds(), self.config)

    @staticmethod
    def convert(identity_obj, cfg):
        """Convert OpenStack Keystone object to CloudFerry object.

        :param identity_obj:    Direct OpenStack Keystone object to convert,
                                supported objects: tenants, users and roles;
        :param cfg:             Cloud config.
        """

        if isinstance(identity_obj, keystone_client.tenants.Tenant):
            return {'tenant': {'name': identity_obj.name,
                               'id': identity_obj.id,
                               'description': identity_obj.description},
                    'meta': {}}

        elif isinstance(identity_obj, keystone_client.users.User):
            overwirte_user_passwords = cfg.migrate.overwrite_user_passwords
            return {'user': {'name': identity_obj.name,
                             'id': identity_obj.id,
                             'email': identity_obj.email,
                             'tenantId': identity_obj.tenantId},
                    'meta': {
                        'overwrite_password': overwirte_user_passwords}}

        elif isinstance(identity_obj, keystone_client.roles.Role):
            return {'role': {'name': identity_obj.name,
                             'id': identity_obj},
                    'meta': {}}

        LOG.error('KeystoneIdentity converter has received incorrect value. '
                  'Please pass to it only tenants, users or role objects.')
        return None

    def has_tenants_by_id_cached(self):
        tenants = set([t.id for t in self.keystone_client.tenants.list()])

        def func(tenant_id):
            return tenant_id in tenants

        return func

    def read_info(self, **kwargs):
        info = {'tenants': [],
                'users': [],
                'roles': []}

        if kwargs.get('tenant_id'):
            self.filter_tenant_id = kwargs['tenant_id'][0]

        tenant_list = self.get_tenants_list()
        for tenant in tenant_list:
            tnt = self.convert(tenant, self.config)
            info['tenants'].append(tnt)
        user_list = self.get_users_list()
        has_tenants_by_id_cached = self.has_tenants_by_id_cached()
        has_roles_by_ids_cached = self._get_user_roles_cached()
        for user in user_list:
            usr = self.convert(user, self.config)
            if has_tenants_by_id_cached(user.tenantId):
                info['users'].append(usr)
            else:
                LOG.info("User's '%s' primary tenant '%s' is deleted, "
                         "finding out if user is a member of other tenants",
                         user.name, user.tenantId)
                for t in tenant_list:
                    roles = has_roles_by_ids_cached(user.id, t.id)
                    if roles:
                        LOG.info("Setting tenant '%s' for user '%s' as "
                                 "primary", t.name, user.name)
                        usr['user']['tenantId'] = t.id
                        info['users'].append(usr)
                        break
        for role in self.get_roles_list():
            rl = self.convert(role, self.config)
            info['roles'].append(rl)
        info['user_tenants_roles'] = \
            self._get_user_tenants_roles(tenant_list, user_list)
        if self.config['migrate']['keep_user_passwords']:
            info['user_passwords'] = self._get_user_passwords()
        return info

    def deploy(self, info):
        LOG.info("Identity objects deployment started")
        tenants = info['tenants']
        users = info['users']
        roles = info['user_tenants_roles']
        self._deploy_tenants(tenants)
        self._deploy_roles(info['roles'])
        self._deploy_users(users, tenants)
        if not self.config.migrate.migrate_users:
            users = info['users'] = self._update_users_info(users)
        if self.config['migrate']['keep_user_passwords']:
            passwords = info['user_passwords']
            self._upload_user_passwords(users, passwords)
        self._upload_user_tenant_roles(roles, users, tenants)
        LOG.info("Done")

    def get_client(self):
        """ Getting keystone client using authentication with admin auth token.

        :return: OpenStack Keystone Client instance
        """

        return self.keystone_client

    def _get_client_by_creds(self):
        """Authenticating with a user name and password.

        :return: OpenStack Keystone Client instance
        """

        kwargs = {
            "username": self.config.cloud.user,
            "password": self.config.cloud.password,
            "tenant_name": self.config.cloud.tenant,
            "auth_url": self.config.cloud.auth_url
        }

        if self.config.cloud.region:
            kwargs["region_name"] = self.config.cloud.region

        return keystone_client.Client(**kwargs)

    def get_endpoint_by_service_type(self, service_type, endpoint_type):
        """Getting endpoint URL by service type.

        :param service_type: OpenStack service type (image, compute etc.)
        :param endpoint_type: publicURL or internalURL

        :return: String endpoint of specified OpenStack service
        """

        kwargs = {
            "service_type": service_type,
            "endpoint_type": endpoint_type
        }

        if self.config.cloud.region:
            kwargs['region_name'] = self.config.cloud.region

        return self.keystone_client.service_catalog.url_for(**kwargs)

    def get_tenants_func(self):
        tenants = {tenant.id: tenant.name for tenant in
                   self.get_tenants_list()}

        def func(tenant_id):
            return tenants.get(tenant_id, 'admin')

        return func

    def get_tenant_id_by_name(self, name):
        """ Getting tenant ID by name from keystone. """

        return self.keystone_client.tenants.find(name=name).id

    def get_tenant_by_name(self, tenant_name):
        """ Getting tenant by name from keystone. """

        for tenant in self.get_tenants_list():
            if tenant.name == tenant_name:
                return tenant

    def get_tenant_by_id(self, tenant_id):
        """ Getting tenant by id from keystone. """

        return self.keystone_client.tenants.get(tenant_id)

    def try_get_tenant_name_by_id(self, tenant_id, default=None):
        """ Same as `get_tenant_by_id` but returns `default` in case tenant
        ID is not present """
        try:
            return self.keystone_client.tenants.get(tenant_id).name
        except keystoneclient.exceptions.NotFound:
            LOG.warning("Tenant '%s' not found, returning default value = "
                        "'%s'", tenant_id, default)
            return default

    def get_services_list(self):
        """ Getting list of available services from keystone. """

        return self.keystone_client.services.list()

    def get_tenants_list(self):
        """ Getting list of tenants from keystone. """
        result = []
        ks_tenants = self.keystone_client.tenants
        if self.filter_tenant_id:
            result.append(ks_tenants.find(id=self.filter_tenant_id))
        else:
            result = ks_tenants.list()
        return result

    def get_users_list(self):
        """ Getting list of users from keystone. """

        if self.filter_tenant_id:
            tenant_id = self.filter_tenant_id
        else:
            tenant_id = None
        return self.keystone_client.users.list(tenant_id=tenant_id)

    def get_roles_list(self):
        """ Getting list of available roles from keystone. """

        return self.keystone_client.roles.list()

    def try_get_username_by_id(self, user_id, default=None):
        try:
            return self.keystone_client.users.get(user_id).name
        except keystoneclient.exceptions.NotFound:
            return default

    def try_get_user_by_id(self, user_id, default=None):
        if default is None:
            admin_usr = \
                self.try_get_user_by_name(username=self.config.cloud.user)
            default = admin_usr.id
        try:
            return self.keystone_client.users.find(id=user_id)
        except keystoneclient.exceptions.NotFound:
            LOG.warning("User '%s' has not been found, returning default "
                        "value = '%s'", user_id, default)
            return self.keystone_client.users.find(id=default)

    def try_get_user_by_name(self, username, default=None):
        try:
            return self.keystone_client.users.find(name=username)
        except keystoneclient.exceptions.NotFound:
            LOG.warning("User '%s' has not been found, returning default "
                        "value = '%s'", username, default)
            return self.keystone_client.users.find(name=default)

    def roles_for_user(self, user_id, tenant_id):
        """ Getting list of user roles for tenant """

        return self.keystone_client.roles.roles_for_user(user_id, tenant_id)

    def create_role(self, role_name):
        """ Create new role in keystone. """

        return self.keystone_client.roles.create(role_name)

    def create_tenant(self, tenant_name, description=None, enabled=True):
        """ Create new tenant in keystone. """

        return self.keystone_client.tenants.create(tenant_name=tenant_name,
                                                   description=description,
                                                   enabled=enabled)

    def create_user(self, name, password=None, email=None, tenant_id=None,
                    enabled=True):
        """ Create new user in keystone. """

        return self.keystone_client.users.create(name=name,
                                                 password=password,
                                                 email=email,
                                                 tenant_id=tenant_id,
                                                 enabled=enabled)

    def update_tenant(self, tenant_id, tenant_name=None, description=None,
                      enabled=None):
        """Update a tenant with a new name and description."""

        return self.keystone_client.tenants.update(tenant_id,
                                                   tenant_name=tenant_name,
                                                   description=description,
                                                   enabled=enabled)

    def update_user(self, user, **kwargs):
        """Update user data.

        Supported arguments include ``name``, ``email``, and ``enabled``.
        """

        return self.keystone_client.users.update(user, **kwargs)

    def get_auth_token_from_user(self):
        self.keystone_client.authenticate()
        return self.keystone_client.auth_token

    def _deploy_tenants(self, tenants):
        LOG.info('Deploying tenants...')
        dst_tenants = {tenant.name: tenant.id for tenant in
                       self.get_tenants_list()}
        for _tenant in tenants:
            tenant = _tenant['tenant']
            if tenant['name'] not in dst_tenants:
                LOG.debug("Creating tenant '%s'", tenant['name'])
                _tenant['meta']['new_id'] = self.create_tenant(
                    tenant['name'],
                    tenant['description']).id
            else:
                LOG.debug("Tenant '%s' is already present on destination, "
                          "skipping", tenant['name'])
                _tenant['meta']['new_id'] = dst_tenants[tenant['name']]

        LOG.info("Tenant deployment done.")

    def _deploy_users(self, users, tenants):
        dst_users = {user.name: user.id for user in self.get_users_list()}
        tenant_mapped_ids = {tenant['tenant']['id']: tenant['meta']['new_id']
                             for tenant in tenants}

        keep_passwd = self.config['migrate']['keep_user_passwords']
        overwrite_passwd = self.config['migrate']['overwrite_user_passwords']

        for _user in users:
            user = _user['user']
            password = self._generate_password()

            if user['name'] in dst_users:
                # Create users mapping
                _user['meta']['new_id'] = dst_users[user['name']]

                if overwrite_passwd and not keep_passwd:
                    self.update_user(_user['meta']['new_id'],
                                     password=password)
                    self._passwd_notification(user['email'], user['name'],
                                              password)
                continue

            if not self.config.migrate.migrate_users:
                continue

            tenant_id = tenant_mapped_ids[user['tenantId']]
            _user['meta']['new_id'] = self.create_user(user['name'], password,
                                                       user['email'],
                                                       tenant_id).id
            if self.config['migrate']['keep_user_passwords']:
                _user['meta']['overwrite_password'] = True
            else:
                self._passwd_notification(user['email'], user['name'],
                                          password)

    @staticmethod
    def _update_users_info(users):
        """
        Update users info.

        This method is needed for skip users, that have not been migrated to
        destination cloud and that do not exist there. So we leave information
        only about users with mapping and skip those, who don't have the same
        user on the destination cloud. This is done, because another tasks can
        use users mapping.

        :param users: OpenStack Keystone users info;
        :return: List with actual users info.
        """

        users_info = []
        for user in users:
            if user['meta'].get('new_id'):
                users_info.append(user)

        return users_info

    def _passwd_notification(self, email, name, password):
        if not self.postman:
            return
        template = 'templates/email.html'
        self._send_msg(email, 'New password notification',
                       self._render_template(template,
                                             {'name': name,
                                              'password': password}))

    def _deploy_roles(self, roles):
        LOG.info("Role deployment started...")
        dst_roles = {
            role.name.lower(): role.id for role in self.get_roles_list()}
        for _role in roles:
            role = _role['role']
            if role['name'].lower() not in dst_roles:
                LOG.debug("Creating role '%s'", role['name'])
                _role['meta']['new_id'] = self.create_role(role['name']).id
            else:
                LOG.debug("Role '%s' is already present on destination, "
                          "skipping", role['name'])
                _role['meta']['new_id'] = dst_roles[role['name'].lower()]

        LOG.info("Role deployment done.")

    def _get_user_passwords(self):
        info = {}
        for user in self.get_users_list():
            for password in self.mysql_connector.execute(
                    "SELECT password FROM user WHERE id = :user_id",
                    user_id=user.id):
                info[user.name] = password[0]
        return info

    def _get_user_tenants_roles(self, tenant_list=None, user_list=None):
        if tenant_list is None:
            tenant_list = []
        if user_list is None:
            user_list = []
        if not self.config.migrate.optimize_user_role_fetch:
            user_tenants_roles = \
                self._get_user_tenants_roles_by_api(tenant_list,
                                                    user_list)
        else:
            user_tenants_roles = \
                self._get_user_tenants_roles_by_db(tenant_list,
                                                   user_list)
        return user_tenants_roles

    def _get_roles_sql_request(self):
        res = []
        try:
            is_project_metadata = self.mysql_connector.execute(
                "SHOW TABLES LIKE 'user_project_metadata'").rowcount
            if is_project_metadata:  # for grizzly case
                return self.mysql_connector.execute(
                    "SELECT * FROM user_project_metadata")
            is_assignment = self.mysql_connector.execute(
                "SHOW TABLES LIKE 'assignment'").rowcount
            if is_assignment:  # for icehouse case
                res_raw = self.mysql_connector.execute(
                    "SELECT * FROM assignment")
                res_tmp = {}
                for (type_record, actor_id, project_id,
                     role_id, inher_tmp) in res_raw:
                    if (actor_id, project_id) not in res_tmp:
                        res_tmp[(actor_id, project_id)] = {'roles': []}
                    res_tmp[(actor_id, project_id)]['roles'].append(role_id)
                for k, v in res_tmp.iteritems():
                    res.append((k[0], k[1], str(v)))
        except ProgrammingError as e:
            LOG.warn(e.message)
        return res

    def _get_user_roles_cached(self):
        all_roles = {}
        if self.config.migrate.optimize_user_role_fetch:
            res = self._get_roles_sql_request()
            for user_id, tenant_id, roles_field in res:
                roles_ids = ast.literal_eval(roles_field)['roles']
                all_roles[user_id] = {} \
                    if user_id not in all_roles else all_roles[user_id]
                all_roles[user_id][tenant_id] = [] \
                    if tenant_id not in all_roles[user_id] \
                    else all_roles[user_id][tenant_id]
                all_roles[user_id][tenant_id].extend(roles_ids)

        def _get_user_roles(user_id, tenant_id):
            if not self.config.migrate.optimize_user_role_fetch:
                roles = self.roles_for_user(user_id, tenant_id)
            else:
                roles = all_roles.get(user_id, {}).get(tenant_id, [])
            return roles
        return _get_user_roles

    def _get_user_tenants_roles_by_db(self, tenant_list, user_list):
        user_tenants_roles = {u.name: {t.name: [] for t in tenant_list}
                              for u in user_list}
        tenant_ids = {tenant.id: tenant.name for tenant in tenant_list}
        user_ids = {user.id: user.name for user in user_list}
        roles = {r.id: r for r in self.get_roles_list()}
        res = self._get_roles_sql_request()
        for user_id, tenant_id, roles_field in res:
            roles_ids = ast.literal_eval(roles_field)['roles']

            if tenant_id not in tenant_ids:
                LOG.debug("Tenant ID '%s' is filtered from role migration",
                          tenant_id)
                continue

            user_tenants_roles[user_ids[user_id]][tenant_ids[tenant_id]] = \
                [{'role': {'name': roles[r].name, 'id': r}}
                 for r in roles_ids]
        return user_tenants_roles

    def _get_user_tenants_roles_by_api(self, tenant_list, user_list):
        user_tenants_roles = {u.name: {t.name: [] for t in tenant_list}
                              for u in user_list}
        for user in user_list:
            user_tenants_roles[user.name] = {}
            for tenant in tenant_list:
                roles = []
                for role in self.roles_for_user(user.id, tenant.id):
                    roles.append({'role': {'name': role.name, 'id': role.id}})
                user_tenants_roles[user.name][tenant.name] = roles
        return user_tenants_roles

    def _upload_user_passwords(self, users, user_passwords):
        for _user in users:
            user = _user['user']
            if not _user['meta']['overwrite_password']:
                continue
            self.mysql_connector.execute(
                "UPDATE user SET password = :password WHERE id = :user_id",
                user_id=_user['meta']['new_id'],
                password=user_passwords[user['name']])

    def _upload_user_tenant_roles(self, user_tenants_roles, users, tenants):
        roles_id = {role.name: role.id for role in self.get_roles_list()}
        dst_users_obj = self.get_users_list()
        dst_users = {user.name: user.id for user in dst_users_obj}
        dst_roles = {role.id: role.name for role in self.get_roles_list()}
        _get_user_roles_cached = self._get_user_roles_cached()
        for _user in users:
            user = _user['user']
            if user['name'] not in dst_users:
                continue
            for _tenant in tenants:
                tenant = _tenant['tenant']
                user_roles_objs = _get_user_roles_cached(
                    _user['meta']['new_id'],
                    _tenant['meta']['new_id'])
                exists_roles = [dst_roles[role] if not hasattr(role,
                                                               'name')
                                else role.name
                                for role in user_roles_objs]
                for _role in user_tenants_roles[user['name']][tenant['name']]:
                    role = _role['role']
                    if role['name'] in exists_roles:
                        continue
                    self.keystone_client.roles.add_user_role(
                        _user['meta']['new_id'], roles_id[role['name']],
                        _tenant['meta']['new_id'])

    def _generate_password(self):
        return self.generator.get_random_password()

    def _send_msg(self, to, subject, msg):
        if self.postman:
            with self.postman as p:
                p.send(to, subject, msg)

    def _render_template(self, name_file, args):
        if self.templater:
            return self.templater.render(name_file, args)
        else:
            return None

    def check_rabbitmq(self):
        credentials = pika.PlainCredentials(self.config.rabbit.user,
                                            self.config.rabbit.password)

        for host_with_port in self.config.rabbit.hosts.split(","):
            host, port = host_with_port.split(':')
            pika.BlockingConnection(
                pika.ConnectionParameters(host=host.strip(),
                                          port=int(port),
                                          credentials=credentials))
Example #6
0
class KeystoneIdentity(identity.Identity):
    """The main class for working with OpenStack Keystone Identity Service."""

    def __init__(self, config, cloud):
        super(KeystoneIdentity, self).__init__()
        self.config = config
        self._ks_client_creds = self.proxy(self._get_client_by_creds(), config)
        self.keystone_client = self.proxy(self.get_client(), config)
        self.mysql_connector = cloud.mysql_connector
        self.cloud = cloud
        self.postman = None
        if self.config.mail.server != "-":
            self.postman = Postman(self.config['mail']['username'],
                                   self.config['mail']['password'],
                                   self.config['mail']['from_addr'],
                                   self.config['mail']['server'])
        self.templater = Templater()
        self.generator = GeneratorPassword()

    @staticmethod
    def convert(identity_obj, cfg):
        """Convert OpenStack Keystone object to CloudFerry object.

        :param identity_obj:    Direct OpenStack Keystone object to convert,
                                supported objects: tenants, users and roles;
        :param cfg:             Cloud config.
        """

        if isinstance(identity_obj, keystone_client.tenants.Tenant):
            return {'tenant': {'name': identity_obj.name,
                               'id': identity_obj.id,
                               'description': identity_obj.description},
                    'meta': {}}

        elif isinstance(identity_obj, keystone_client.users.User):
            overwirte_user_passwords = cfg.migrate.overwrite_user_passwords
            return {'user': {'name': identity_obj.name,
                             'id': identity_obj.id,
                             'email': identity_obj.email,
                             'tenantId': identity_obj.tenantId},
                    'meta': {
                        'overwrite_password': overwirte_user_passwords}}

        elif isinstance(identity_obj, keystone_client.roles.Role):
            return {'role': {'name': identity_obj.name,
                             'id': identity_obj},
                    'meta': {}}

        LOG.error('KeystoneIdentity converter has received incorrect value. '
                  'Please pass to it only tenants, users or role objects.')
        return None

    def read_info(self, **kwargs):
        info = {'tenants': [],
                'users': [],
                'roles': []}

        service_tenant_id = \
            self.get_tenant_id_by_name(self.config.cloud.service_tenant)

        for tenant in self.get_tenants_list():
            if tenant.id != service_tenant_id:
                tnt = self.convert(tenant, self.config)
                info['tenants'].append(tnt)

        for user in self.get_users_list():
            if user.tenantId != service_tenant_id:
                usr = self.convert(user, self.config)
                info['users'].append(usr)

        for role in self.get_roles_list():
            rl = self.convert(role, self.config)
            info['roles'].append(rl)

        info['user_tenants_roles'] = self._get_user_tenants_roles()
        if self.config['migrate']['keep_user_passwords']:
            info['user_passwords'] = self._get_user_passwords()
        return info

    def deploy(self, info):
        print 'Deploy started'
        tenants = info['tenants']
        users = info['users']
        roles = info['user_tenants_roles']

        self._deploy_tenants(tenants)
        self._deploy_roles(info['roles'])
        self._deploy_users(users, tenants)
        if not self.config.migrate.migrate_users:
            users = info['users'] = self._update_users_info(users)
        if self.config['migrate']['keep_user_passwords']:
            passwords = info['user_passwords']
            self._upload_user_passwords(users, passwords)
        self._upload_user_tenant_roles(roles, users, tenants)
        print 'Finished'

    def get_client(self):
        """ Getting keystone client using authentication with admin auth token.

        :return: OpenStack Keystone Client instance
        """

        return keystone_client.Client(
            token=self._ks_client_creds.auth_ref['token']['id'],
            endpoint=self.config.cloud.auth_url)

    def _get_client_by_creds(self):
        """Authenticating with a user name and password.

        :return: OpenStack Keystone Client instance
        """

        return keystone_client.Client(username=self.config.cloud.user,
                                      password=self.config.cloud.password,
                                      tenant_name=self.config.cloud.tenant,
                                      auth_url=self.config.cloud.auth_url)

    def get_endpoint_by_service_type(self, service_type, endpoint_type):
        """Getting endpoint URL by service type.

        :param service_type: OpenStack service type (image, compute etc.)
        :param endpoint_type: publicURL or internalURL

        :return: String endpoint of specified OpenStack service
        """

        return self._ks_client_creds.service_catalog.url_for(
            service_type=service_type,
            endpoint_type=endpoint_type)

    def get_tenants_func(self):
        tenants = {tenant.id: tenant.name for tenant in
                   self.get_tenants_list()}

        def func(tenant_id):
            return tenants.get(tenant_id, 'admin')

        return func

    def get_tenant_id_by_name(self, name):
        for tenant in self.get_tenants_list():
            if tenant.name == name:
                return tenant.id
        return None

    def get_tenant_by_name(self, tenant_name):
        """ Getting tenant by name from keystone. """

        for tenant in self.get_tenants_list():
            if tenant.name == tenant_name:
                return tenant

    def get_tenant_by_id(self, tenant_id):
        """ Getting tenant by id from keystone. """

        return self.keystone_client.tenants.get(tenant_id)

    def try_get_tenant_name_by_id(self, tenant_id, default=None):
        """ Same as `get_tenant_by_id` but returns `default` in case tenant
        ID is not present """
        try:
            return self.keystone_client.tenants.get(tenant_id).name
        except keystoneclient.exceptions.NotFound:
            LOG.warning("Tenant '%s' not found, returning default value = "
                        "'%s'", tenant_id, default)
            return default

    def get_services_list(self):
        """ Getting list of available services from keystone. """

        return self.keystone_client.services.list()

    def get_tenants_list(self):
        """ Getting list of tenants from keystone. """

        return self.keystone_client.tenants.list()

    def get_users_list(self):
        """ Getting list of users from keystone. """

        return self.keystone_client.users.list()

    def get_roles_list(self):
        """ Getting list of available roles from keystone. """

        return self.keystone_client.roles.list()

    def try_get_username_by_id(self, user_id, default=None):
        try:
            return self.keystone_client.users.get(user_id).name
        except keystoneclient.exceptions.NotFound:
            return default

    def try_get_user_by_name(self, username, default=None):
        try:
            return self.keystone_client.users.find(username=username)
        except keystoneclient.exceptions.NotFound:
            LOG.warning("User '%s' has not been found, returning default "
                        "value = '%s'", username, default)
            return self.keystone_client.users.find(username=default)

    def roles_for_user(self, user_id, tenant_id):
        """ Getting list of user roles for tenant """

        return self.keystone_client.roles.roles_for_user(user_id, tenant_id)

    def create_role(self, role_name):
        """ Create new role in keystone. """

        return self.keystone_client.roles.create(role_name)

    def create_tenant(self, tenant_name, description=None, enabled=True):
        """ Create new tenant in keystone. """

        return self.keystone_client.tenants.create(tenant_name=tenant_name,
                                                   description=description,
                                                   enabled=enabled)

    def create_user(self, name, password=None, email=None, tenant_id=None,
                    enabled=True):
        """ Create new user in keystone. """

        return self.keystone_client.users.create(name=name,
                                                 password=password,
                                                 email=email,
                                                 tenant_id=tenant_id,
                                                 enabled=enabled)

    def update_tenant(self, tenant_id, tenant_name=None, description=None,
                      enabled=None):
        """Update a tenant with a new name and description."""

        return self.keystone_client.tenants.update(tenant_id,
                                                   tenant_name=tenant_name,
                                                   description=description,
                                                   enabled=enabled)

    def update_user(self, user, **kwargs):
        """Update user data.

        Supported arguments include ``name``, ``email``, and ``enabled``.
        """

        return self.keystone_client.users.update(user, **kwargs)

    def get_auth_token_from_user(self):
        return self.keystone_client.auth_token_from_user

    def _deploy_tenants(self, tenants):
        dst_tenants = {tenant.name: tenant.id for tenant in
                       self.get_tenants_list()}
        for _tenant in tenants:
            tenant = _tenant['tenant']
            if tenant['name'] not in dst_tenants:
                _tenant['meta']['new_id'] = self.create_tenant(
                    tenant['name'],
                    tenant['description']).id
            else:
                _tenant['meta']['new_id'] = dst_tenants[tenant['name']]

    def _deploy_users(self, users, tenants):
        dst_users = {user.name: user.id for user in self.get_users_list()}
        tenant_mapped_ids = {tenant['tenant']['id']: tenant['meta']['new_id']
                             for tenant in tenants}

        keep_passwd = self.config['migrate']['keep_user_passwords']
        overwrite_passwd = self.config['migrate']['overwrite_user_passwords']

        for _user in users:
            user = _user['user']
            password = self._generate_password()

            if user['name'] in dst_users:
                # Create users mapping
                _user['meta']['new_id'] = dst_users[user['name']]

                if overwrite_passwd and not keep_passwd:
                    self.update_user(_user['meta']['new_id'],
                                     password=password)
                    self._passwd_notification(user['email'], user['name'],
                                              password)
                continue

            if not self.config.migrate.migrate_users:
                continue

            tenant_id = tenant_mapped_ids[user['tenantId']]
            _user['meta']['new_id'] = self.create_user(user['name'], password,
                                                       user['email'],
                                                       tenant_id).id
            if self.config['migrate']['keep_user_passwords']:
                _user['meta']['overwrite_password'] = True
            else:
                self._passwd_notification(user['email'], user['name'],
                                          password)

    @staticmethod
    def _update_users_info(users):
        """
        Update users info.

        This method is needed for skip users, that have not been migrated to
        destination cloud and that do not exist there. So we leave information
        only about users with mapping and skip those, who don't have the same
        user on the destination cloud. This is done, because another tasks can
        use users mapping.

        :param users: OpenStack Keystone users info;
        :return: List with actual users info.
        """

        users_info = []
        for user in users:
            if user['meta'].get('new_id'):
                users_info.append(user)

        return users_info

    def _passwd_notification(self, email, name, password):
        if not self.postman:
            return
        template = 'templates/email.html'
        self._send_msg(email, 'New password notification',
                       self._render_template(template,
                                             {'name': name,
                                              'password': password}))

    def _deploy_roles(self, roles):
        dst_roles = {role.name: role.id for role in self.get_roles_list()}
        for _role in roles:
            role = _role['role']
            if role['name'] not in dst_roles:
                _role['meta']['new_id'] = self.create_role(role['name']).id
            else:
                _role['meta']['new_id'] = dst_roles[role['name']]

    def _get_user_passwords(self):
        info = {}
        for user in self.get_users_list():
            for password in self.mysql_connector.execute(
                    "SELECT password FROM user WHERE id = :user_id",
                    user_id=user.id):
                info[user.name] = password[0]

        return info

    def _get_user_tenants_roles(self):
        user_tenants_roles = {}
        tenants = self.get_tenants_list()
        for user in self.get_users_list():
            user_tenants_roles[user.name] = {}
            for tenant in tenants:
                roles = []
                for role in self.roles_for_user(user.id, tenant.id):
                    roles.append({'role': {'name': role.name, 'id': role.id}})
                user_tenants_roles[user.name][tenant.name] = roles
        return user_tenants_roles

    def _upload_user_passwords(self, users, user_passwords):
        for _user in users:
            user = _user['user']
            if not _user['meta']['overwrite_password']:
                continue
            self.mysql_connector.execute(
                "UPDATE user SET password = :password WHERE id = :user_id",
                user_id=_user['meta']['new_id'],
                password=user_passwords[user['name']])

    def _upload_user_tenant_roles(self, user_tenants_roles, users, tenants):
        roles_id = {role.name: role.id for role in self.get_roles_list()}
        dst_users = {user.name: user.id for user in self.get_users_list()}

        for _user in users:
            user = _user['user']
            # FIXME should be deleted after determining how
            # to change self role without logout
            if user['name'] == self.keystone_client.username:
                continue
            if user['name'] not in dst_users:
                continue
            for _tenant in tenants:
                tenant = _tenant['tenant']
                exists_roles = [role.name for role in
                                self.roles_for_user(_user['meta']['new_id'],
                                                    _tenant['meta']['new_id'])]
                for _role in user_tenants_roles[user['name']][tenant['name']]:
                    role = _role['role']
                    if role['name'] in exists_roles:
                        continue
                    self.keystone_client.roles.add_user_role(
                        _user['meta']['new_id'], roles_id[role['name']],
                        _tenant['meta']['new_id'])

    def _generate_password(self):
        return self.generator.get_random_password()

    def _send_msg(self, to, subject, msg):
        if self.postman:
            with self.postman as p:
                p.send(to, subject, msg)

    def _render_template(self, name_file, args):
        if self.templater:
            return self.templater.render(name_file, args)
        else:
            return None

    def check_rabbitmq(self):
        credentials = pika.PlainCredentials(self.config.rabbit.user,
                                            self.config.rabbit.password)
        for host in self.config.rabbit.hosts.split(","):
            pika.BlockingConnection(pika.ConnectionParameters(
                host=host.strip(), credentials=credentials))
Example #7
0
class KeystoneIdentity(identity.Identity):
    """The main class for working with OpenStack Keystone Identity Service."""

    def __init__(self, config, cloud):
        super(KeystoneIdentity, self).__init__()
        self.config = config
        self.keystone_client = self.proxy(self.get_client(), config)
        self.mysql_connector = cloud.mysql_connector
        self.cloud = cloud
        self.postman = None
        if self.config.mail.server != "-":
            self.postman = Postman(self.config['mail']['username'],
                                   self.config['mail']['password'],
                                   self.config['mail']['from_addr'],
                                   self.config['mail']['server'])
        self.templater = Templater()
        self.generator = GeneratorPassword()

    @staticmethod
    def convert(identity_obj, cfg):
        """Convert OpenStack Keystone object to CloudFerry object.

        :param identity_obj:    Direct OpenStack Keystone object to convert,
                                supported objects: tenants, users and roles;
        :param cfg:             Cloud config.
        """

        if isinstance(identity_obj, keystone_client.tenants.Tenant):
            return {'tenant': {'name': identity_obj.name,
                               'id': identity_obj.id,
                               'description': identity_obj.description},
                    'meta': {}}

        elif isinstance(identity_obj, keystone_client.users.User):
            overwirte_user_passwords = cfg.migrate.overwrite_user_passwords
            return {'user': {'name': identity_obj.name,
                             'id': identity_obj.id,
                             'email': identity_obj.email,
                             'tenantId': identity_obj.tenantId},
                    'meta': {
                        'overwrite_password': overwirte_user_passwords}}

        elif isinstance(identity_obj, keystone_client.roles.Role):
            return {'role': {'name': identity_obj.name,
                             'id': identity_obj},
                    'meta': {}}

        LOG.error('KeystoneIdentity converter has received incorrect value. '
                  'Please pass to it only tenants, users or role objects.')
        return None

    def read_info(self, **kwargs):
        info = {'tenants': [],
                'users': [],
                'roles': []}

        for tenant in self.get_tenants_list():
            tnt = self.convert(tenant, self.config)
            info['tenants'].append(tnt)

        for user in self.get_users_list():
            usr = self.convert(user, self.config)
            info['users'].append(usr)

        for role in self.get_roles_list():
            rl = self.convert(role, self.config)
            info['roles'].append(rl)

        info['user_tenants_roles'] = self._get_user_tenants_roles()
        if self.config['migrate']['keep_user_passwords']:
            info['user_passwords'] = self._get_user_passwords()
        return info

    def deploy(self, info):
        print 'Deploy started'
        tenants = info['tenants']
        users = info['users']
        roles = info['user_tenants_roles']

        self._deploy_tenants(tenants)
        self._deploy_roles(info['roles'])
        self._deploy_users(users, tenants)
        if self.config['migrate']['keep_user_passwords']:
            passwords = info['user_passwords']
            self._upload_user_passwords(users, passwords)
        self._upload_user_tenant_roles(roles, users, tenants)
        print 'Finished'

    def get_client(self):
        """ Getting keystone client """

        ks_client_for_token = keystone_client.Client(
            username=self.config.cloud.user,
            password=self.config.cloud.password,
            tenant_name=self.config.cloud.tenant,
            auth_url="http://%s:35357/v2.0/" % self.config.cloud.host)

        return keystone_client.Client(
            token=ks_client_for_token.auth_ref['token']['id'],
            endpoint="http://%s:35357/v2.0/" % self.config.cloud.host)

    def get_service_name_by_type(self, service_type):
        """Getting service_name from keystone. """

        for service in self.get_services_list():
            if service.type == service_type:
                return service.name
        return NOVA_SERVICE

    def get_public_endpoint_service_by_id(self, service_id):
        """Getting endpoint public URL from keystone. """

        for endpoint in self.keystone_client.endpoints.list():
            if endpoint.service_id == service_id:
                return endpoint.publicurl

    def get_service_id(self, service_name):
        """Getting service_id from keystone. """

        for service in self.get_services_list():
            if service.name == service_name:
                return service.id

    def get_endpoint_by_service_name(self, service_name):
        """ Getting endpoint public URL by service name from keystone. """

        service_id = self.get_service_id(service_name)
        return self.get_public_endpoint_service_by_id(service_id)

    def get_tenants_func(self):
        tenants = {tenant.id: tenant.name for tenant in
                   self.get_tenants_list()}

        def func(tenant_id):
            return tenants.get(tenant_id, 'admin')

        return func

    def get_tenant_id_by_name(self, name):
        for tenant in self.get_tenants_list():
            if tenant.name == name:
                return tenant.id
        return None

    def get_tenant_by_name(self, tenant_name):
        """ Getting tenant by name from keystone. """

        for tenant in self.get_tenants_list():
            if tenant.name == tenant_name:
                return tenant

    def get_tenant_by_id(self, tenant_id):
        """ Getting tenant by id from keystone. """

        return self.keystone_client.tenants.get(tenant_id)

    def get_services_list(self):
        """ Getting list of available services from keystone. """

        return self.keystone_client.services.list()

    def get_tenants_list(self):
        """ Getting list of tenants from keystone. """

        return self.keystone_client.tenants.list()

    def get_users_list(self):
        """ Getting list of users from keystone. """

        return self.keystone_client.users.list()

    def get_roles_list(self):
        """ Getting list of available roles from keystone. """

        return self.keystone_client.roles.list()

    def roles_for_user(self, user_id, tenant_id):
        """ Getting list of user roles for tenant """

        return self.keystone_client.roles.roles_for_user(user_id, tenant_id)

    def create_role(self, role_name):
        """ Create new role in keystone. """

        return self.keystone_client.roles.create(role_name)

    def create_tenant(self, tenant_name, description=None, enabled=True):
        """ Create new tenant in keystone. """

        return self.keystone_client.tenants.create(tenant_name=tenant_name,
                                                   description=description,
                                                   enabled=enabled)

    def create_user(self, name, password=None, email=None, tenant_id=None,
                    enabled=True):
        """ Create new user in keystone. """

        return self.keystone_client.users.create(name=name,
                                                 password=password,
                                                 email=email,
                                                 tenant_id=tenant_id,
                                                 enabled=enabled)

    def update_tenant(self, tenant_id, tenant_name=None, description=None,
                      enabled=None):
        """Update a tenant with a new name and description."""

        return self.keystone_client.tenants.update(tenant_id,
                                                   tenant_name=tenant_name,
                                                   description=description,
                                                   enabled=enabled)

    def update_user(self, user, **kwargs):
        """Update user data.

        Supported arguments include ``name``, ``email``, and ``enabled``.
        """

        return self.keystone_client.users.update(user, **kwargs)

    def get_auth_token_from_user(self):
        return self.keystone_client.auth_token_from_user

    def _deploy_tenants(self, tenants):
        dst_tenants = {tenant.name: tenant.id for tenant in
                       self.get_tenants_list()}
        for _tenant in tenants:
            tenant = _tenant['tenant']
            if tenant['name'] not in dst_tenants:
                _tenant['meta']['new_id'] = self.create_tenant(
                    tenant['name'],
                    tenant['description']).id
            else:
                _tenant['meta']['new_id'] = dst_tenants[tenant['name']]

    def _deploy_users(self, users, tenants):
        dst_users = {user.name: user.id for user in self.get_users_list()}
        tenant_mapped_ids = {tenant['tenant']['id']: tenant['meta']['new_id']
                             for tenant in tenants}

        keep_passwd = self.config['migrate']['keep_user_passwords']
        overwrite_passwd = self.config['migrate']['overwrite_user_passwords']
        for _user in users:
            user = _user['user']
            password = self._generate_password()

            if user['name'] in dst_users:
                _user['meta']['new_id'] = dst_users[user['name']]
                if overwrite_passwd and not keep_passwd:
                    self.update_user(_user['meta']['new_id'],
                                     password=password)
                    self._passwd_notification(user['email'], user['name'],
                                              password)
                continue

            tenant_id = tenant_mapped_ids[user['tenantId']]
            _user['meta']['new_id'] = self.create_user(user['name'], password,
                                                       user['email'],
                                                       tenant_id).id
            if self.config['migrate']['keep_user_passwords']:
                _user['meta']['overwrite_password'] = True
            else:
                self._passwd_notification(user['email'], user['name'],
                                          password)

    def _passwd_notification(self, email, name, password):
        if not self.postman:
            return
        template = 'templates/email.html'
        self._send_msg(email, 'New password notification',
                       self._render_template(template,
                                             {'name': name,
                                              'password': password}))

    def _deploy_roles(self, roles):
        dst_roles = {role.name: role.id for role in self.get_roles_list()}
        for _role in roles:
            role = _role['role']
            if role['name'] not in dst_roles:
                _role['meta']['new_id'] = self.create_role(role['name']).id
            else:
                _role['meta']['new_id'] = dst_roles[role['name']]

    def _get_user_passwords(self):
        info = {}
        for user in self.get_users_list():
            for password in self.mysql_connector.execute(
                    "SELECT password FROM user WHERE id = :user_id",
                    user_id=user.id):
                info[user.name] = password[0]

        return info

    def _get_user_tenants_roles(self):
        user_tenants_roles = {}
        tenants = self.get_tenants_list()
        for user in self.get_users_list():
            user_tenants_roles[user.name] = {}
            for tenant in tenants:
                roles = []
                for role in self.roles_for_user(user.id, tenant.id):
                    roles.append({'role': {'name': role.name, 'id': role.id}})
                user_tenants_roles[user.name][tenant.name] = roles
        return user_tenants_roles

    def _upload_user_passwords(self, users, user_passwords):
        for _user in users:
            user = _user['user']
            if not _user['meta']['overwrite_password']:
                continue
            self.mysql_connector.execute(
                "UPDATE user SET password = :password WHERE id = :user_id",
                user_id=_user['meta']['new_id'],
                password=user_passwords[user['name']])

    def _upload_user_tenant_roles(self, user_tenants_roles, users, tenants):
        roles_id = {role.name: role.id for role in self.get_roles_list()}

        for _user in users:
            user = _user['user']
            # FIXME should be deleted after determining how
            # to change self role without logout
            if user['name'] == self.keystone_client.username:
                continue
            for _tenant in tenants:
                tenant = _tenant['tenant']
                exists_roles = [role.name for role in
                                self.roles_for_user(_user['meta']['new_id'],
                                                    _tenant['meta']['new_id'])]
                for _role in user_tenants_roles[user['name']][tenant['name']]:
                    role = _role['role']
                    if role['name'] in exists_roles:
                        continue
                    self.keystone_client.roles.add_user_role(
                        _user['meta']['new_id'], roles_id[role['name']],
                        _tenant['meta']['new_id'])

    def _generate_password(self):
        return self.generator.get_random_password()

    def _send_msg(self, to, subject, msg):
        if self.postman:
            with self.postman as p:
                p.send(to, subject, msg)

    def _render_template(self, name_file, args):
        if self.templater:
            return self.templater.render(name_file, args)
        else:
            return None