def volumedriver_get(self, name):
        """
        Return volume information.

        :param unicode name: The name of the volume.

        :return: Result indicating success.
        """
        contents = json.loads(name.content.getvalue())
        volname = contents['Name']
        volinfo = self._etcd.get_vol_byname(volname)
        err = ''
        if volinfo is None:
            msg = (_LE('Volume Get: Volume name not found %s'), volname)
            LOG.warning(msg)
            response = json.dumps({u"Err": ""})
            return response

        path_info = self._etcd.get_vol_path_info(volname)
        if path_info is not None:
            mountdir = path_info['mount_dir']
        else:
            mountdir = ''

        volume = {'Name': volname, 'Mountpoint': mountdir, 'Status': {}}

        response = json.dumps({u"Err": err, u"Volume": volume})
        return response
    def volumedriver_path(self, name):
        """
        Return the path of a locally mounted volume if possible.

        :param unicode name: The name of the volume.

        :return: Result indicating success.
        """
        contents = json.loads(name.content.getvalue())
        volname = contents['Name']
        volinfo = self._etcd.get_vol_byname(volname)
        if volinfo is None:
            msg = (_LE('Volume Path: Volume name not found %s'), volname)
            LOG.warning(msg)
            response = json.dumps({u"Err": "No Mount Point",
                                   u"Mountpoint": ""})
            return response

        path_name = ''
        path_info = self._etcd.get_vol_path_info(volname)

        if path_info is not None:
            path_name = path_info['mount_dir']

        response = json.dumps({u"Err": '', u"Mountpoint": path_name})
        return response
    def volumedriver_get(self, name):
        """
        Return volume information.

        :param unicode name: The name of the volume.

        :return: Result indicating success.
        """
        contents = json.loads(name.content.getvalue())
        volname = contents['Name']
        volinfo = self._etcd.get_vol_byname(volname)
        err = ''
        if volinfo is None:
            msg = (_LE('Volume Get: Volume name not found %s'), volname)
            LOG.warning(msg)
            response = json.dumps({u"Err": ""})
            return response

        path_info = self._etcd.get_vol_path_info(volname)
        if path_info is not None:
            mountdir = path_info['mount_dir']
        else:
            mountdir = ''

        volume = {'Name': volname,
                  'Mountpoint': mountdir,
                  'Status': {}}

        response = json.dumps({u"Err": err, u"Volume": volume})
        return response
Exemplo n.º 4
0
    def _get_tiers_size(self):
        try:
            resp, body = self.service.ceph_api.osd_df(body='json',
                                                      output_method='tree')
        except IOError:
            return 0
        if not resp.ok:
            LOG.error(
                _LE("Getting the cluster usage "
                    "information failed: %(reason)s - "
                    "%(body)s") % {
                        "reason": resp.reason,
                        "body": body
                    })
            return {}

        # A node is a crushmap element: root, chassis, host, osd. Create a
        # dictionary for the nodes with the key as the id used for efficient
        # searching through nodes.
        #
        # For example: storage-0's node has one child node => OSD 0
        # {
        #     "id": -4,
        #     "name": "storage-0",
        #     "type": "host",
        #     "type_id": 1,
        #     "reweight": -1.000000,
        #     "kb": 51354096,
        #     "kb_used": 1510348,
        #     "kb_avail": 49843748,
        #     "utilization": 2.941047,
        #     "var": 1.480470,
        #     "pgs": 0,
        #     "children": [
        #         0
        #     ]
        # },
        search_tree = {}
        for node in body['output']['nodes']:
            search_tree[node['id']] = node

        # Extract the tiers as we will return a dict for the size of each tier
        tiers = {k: v for k, v in search_tree.items() if v['type'] == 'root'}

        # For each tier, traverse the heirarchy from the root->chassis->host.
        # Sum the host sizes to determine the overall size of the tier
        tier_sizes = {}
        for tier in tiers.values():
            tier_size = 0
            for chassis_id in tier['children']:
                chassis_size = 0
                chassis = search_tree[chassis_id]
                for host_id in chassis['children']:
                    host = search_tree[host_id]
                    if (chassis_size == 0 or chassis_size > host['kb']):
                        chassis_size = host['kb']
                tier_size += chassis_size / (1024**2)
            tier_sizes[tier['name']] = tier_size

        return tier_sizes
    def volumedriver_path(self, name):
        """
        Return the path of a locally mounted volume if possible.

        :param unicode name: The name of the volume.

        :return: Result indicating success.
        """
        contents = json.loads(name.content.getvalue())
        volname = contents['Name']
        volinfo = self._etcd.get_vol_byname(volname)
        if volinfo is None:
            msg = (_LE('Volume Path: Volume name not found %s'), volname)
            LOG.warning(msg)
            response = json.dumps({
                u"Err": "No Mount Point",
                u"Mountpoint": ""
            })
            return response

        path_name = ''
        path_info = self._etcd.get_vol_path_info(volname)

        if path_info is not None:
            path_name = path_info['mount_dir']

        response = json.dumps({u"Err": '', u"Mountpoint": path_name})
        return response
    def volumedriver_remove(self, name):
        """
        Remove a Docker volume.

        :param unicode name: The name of the volume.

        :return: Result indicating success.
        """
        contents = json.loads(name.content.getvalue())
        volname = contents['Name']

        # Only 1 node in a multinode cluster can try to remove the volume.
        # Grab lock for volume name. If lock is inuse, just return with no
        # error.
        self._lock_volume(volname, 'Remove')

        vol = self._etcd.get_vol_byname(volname)
        if vol is None:
            # Just log an error, but don't fail the docker rm command
            msg = (_LE('Volume remove name not found %s'), volname)
            LOG.error(msg)
            self._unlock_volume(volname)
            return json.dumps({u"Err": ''})

        try:
            self.hpeplugin_driver.delete_volume(vol)
            LOG.info(_LI('volume: %(name)s,'
                         'was successfully deleted'), {'name': volname})
        except Exception as ex:
            msg = (_LE('Err: Failed to remove volume %s, error is %s'),
                   volname, six.text_type(ex))
            LOG.error(msg)
            self._unlock_volume(volname)
            raise exception.HPEPluginRemoveException(reason=msg)

        try:
            self._etcd.delete_vol(vol)
        except KeyError:
            msg = (_LW('Warning: Failed to delete volume key: %s from '
                       'etcd due to KeyError'), volname)
            LOG.warning(msg)
            pass

        self._unlock_volume(volname)
        return json.dumps({u"Err": ''})
    def volumedriver_remove(self, name):
        """
        Remove a Docker volume.

        :param unicode name: The name of the volume.

        :return: Result indicating success.
        """
        contents = json.loads(name.content.getvalue())
        volname = contents['Name']

        # Only 1 node in a multinode cluster can try to remove the volume.
        # Grab lock for volume name. If lock is inuse, just return with no
        # error.
        self._lock_volume(volname, 'Remove')

        vol = self._etcd.get_vol_byname(volname)
        if vol is None:
            # Just log an error, but don't fail the docker rm command
            msg = (_LE('Volume remove name not found %s'), volname)
            LOG.error(msg)
            self._unlock_volume(volname)
            return json.dumps({u"Err": ''})

        try:
            self.hpeplugin_driver.delete_volume(vol)
            LOG.info(_LI('volume: %(name)s,' 'was successfully deleted'),
                     {'name': volname})
        except Exception as ex:
            msg = (_LE('Err: Failed to remove volume %s, error is %s'),
                   volname, six.text_type(ex))
            LOG.error(msg)
            self._unlock_volume(volname)
            raise exception.HPEPluginRemoveException(reason=msg)

        try:
            self._etcd.delete_vol(vol)
        except KeyError:
            msg = (_LW('Warning: Failed to delete volume key: %s from '
                       'etcd due to KeyError'), volname)
            LOG.warning(msg)
            pass

        self._unlock_volume(volname)
        return json.dumps({u"Err": ''})
Exemplo n.º 8
0
    def _select_ds_for_volume(self,
                              dc_moid,
                              cluster_moid,
                              ds_moid,
                              host_moid=None,
                              folders=None):
        """Select datastore that can accommodate the given volume's backing.

        Returns the selected datastore summary along with a compute host and
        its resource pool and folder where the volume can be created
        :return: (host, resource_pool, folder, summary)
        """
        dc_ref = utils.get_datacenter_moref(self._content, moid=dc_moid)
        if not dc_ref:
            LOG.error(_LE("No valid datacenter is available."))
            raise exception.NoValidDatacenter()

        resource_pool = None
        host_ref = None
        cluster_ref = utils.get_child_ref_by_moid(dc_ref.hostFolder,
                                                  cluster_moid)
        if not cluster_ref:
            LOG.warn(_LW("No valid cluster is available."))
            host_ref = utils.get_child_ref_by_moid(dc_ref.hostFolder,
                                                   host_moid)
            if not host_ref:
                LOG.error(_LE("No valid host is available."))
                raise exception.NoValidHost()
        else:
            resource_pool = cluster_ref.resourcePool

        host_ref = utils.get_ref_from_array_by_moid(cluster_ref.host,
                                                    host_moid)
        if not host_ref:
            LOG.warn(_LW("No valid host is specified."))

        ds_ref = utils.get_ref_from_array_by_moid(cluster_ref.datastore,
                                                  ds_moid)
        if not ds_ref:
            LOG.error(_LE("No valid datastore is available."))
            raise exception.NoValidDatastore()

        folder_ref = self._get_volume_group_folder(dc_ref, folders)

        return (resource_pool, host_ref, ds_ref, folder_ref)
Exemplo n.º 9
0
def create_inventory_folder(folder_ref, new_folder_name):
    try:
        vmfolder = folder_ref.CreateFolder(new_folder_name)
    except vim.fault.DuplicateName:
        LOG.error('Another object in the same folder has the target name.')
    except vim.fault.InvalidName:
        LOG.error(_LE('The new folder name (%s) is not a valid entity name.'),
                  new_folder_name)
    return vmfolder
Exemplo n.º 10
0
    def __init__(self, message=None, **kwargs):
        self.kwargs = kwargs
        self.kwargs['message'] = message

        if 'code' not in self.kwargs:
            try:
                self.kwargs['code'] = self.code
            except AttributeError:
                pass

        for k, v in self.kwargs.items():
            if isinstance(v, Exception):
                self.kwargs[k] = six.text_type(v)

        if self._should_format():
            try:
                message = self.message % kwargs

            except Exception:
                exc_info = sys.exc_info()
                # kwargs doesn't match a variable in the message
                # log the issue and the kwargs
                LOG.exception(_LE('Exception in string format operation'))
                for name, value in kwargs.items():
                    LOG.error(_LE("%(name)s: %(value)s"), {
                        'name': name,
                        'value': value
                    })
                if CONF.fatal_exception_format_errors:
                    six.reraise(*exc_info)
                # at least get the core message out if something happened
                message = self.message
        elif isinstance(message, Exception):
            message = six.text_type(message)

        # NOTE(luisg): We put the actual message in 'msg' so that we can access
        # it, because if we try to access the message via 'message' it will be
        # overshadowed by the class' message attribute
        self.msg = message
        super(PluginException, self).__init__(message)
Exemplo n.º 11
0
    def __init__(self, message=None, **kwargs):
        self.kwargs = kwargs
        self.kwargs['message'] = message

        if 'code' not in self.kwargs:
            try:
                self.kwargs['code'] = self.code
            except AttributeError:
                pass

        for k, v in self.kwargs.items():
            if isinstance(v, Exception):
                self.kwargs[k] = six.text_type(v)

        if self._should_format():
            try:
                message = self.message % kwargs

            except Exception:
                exc_info = sys.exc_info()
                # kwargs doesn't match a variable in the message
                # log the issue and the kwargs
                LOG.exception(_LE('Exception in string format operation'))
                for name, value in kwargs.items():
                    LOG.error(_LE("%(name)s: %(value)s"),
                              {'name': name, 'value': value})
                if CONF.fatal_exception_format_errors:
                    six.reraise(*exc_info)
                # at least get the core message out if something happened
                message = self.message
        elif isinstance(message, Exception):
            message = six.text_type(message)

        # NOTE(luisg): We put the actual message in 'msg' so that we can access
        # it, because if we try to access the message via 'message' it will be
        # overshadowed by the class' message attribute
        self.msg = message
        super(PluginException, self).__init__(message)
Exemplo n.º 12
0
    def _get_osd_pool_quota(self, pool_name):
        try:
            resp, quota = self.service.ceph_api.osd_get_pool_quota(pool_name,
                                                                   body='json')
        except IOError:
            return 0

        if not resp.ok:
            LOG.error(
                _LE("Getting the quota for "
                    "%(name)s pool failed:%(reason)s)") % {
                        "name": pool_name,
                        "reason": resp.reason
                    })
            return 0
        else:
            try:
                quota_gib = int(quota["output"]["quota_max_bytes"]) / (1024**3)
                return quota_gib
            except IOError:
                return 0
    def volumedriver_mount(self, name):
        """
        Mount the volume
        Mount the volume

        NOTE: If for any reason the mount request fails, Docker
        will automatically call uMount. So, just make sure uMount
        can handle partially completed Mount requests.

        :param unicode name: The name of the volume.

        :return: Result that includes the mountpoint.
        """
        LOG.debug('In volumedriver_mount')

        # TODO: use persistent storage to lookup volume for deletion
        contents = {}
        contents = json.loads(name.content.getvalue())
        volname = contents['Name']
        vol = self._etcd.get_vol_byname(volname)
        if vol is not None:
            volid = vol['id']
        else:
            msg = (_LE('Volume mount name not found %s'), volname)
            LOG.error(msg)
            raise exception.HPEPluginMountException(reason=msg)

        vol_mount = DEFAULT_MOUNT_VOLUME
        if ('Opts' in contents and contents['Opts']
                and 'mount-volume' in contents['Opts']):
            vol_mount = str(contents['Opts']['mount-volume'])

        # Get connector info from OS Brick
        # TODO: retrieve use_multipath and enforce_multipath from config file
        root_helper = 'sudo'

        connector_info = connector.get_connector_properties(
            root_helper,
            self._my_ip,
            multipath=self.use_multipath,
            enforce_multipath=self.enforce_multipath)

        try:
            # Call driver to initialize the connection
            self.hpeplugin_driver.create_export(vol, connector_info)
            connection_info = \
                self.hpeplugin_driver.initialize_connection(
                    vol, connector_info)
            LOG.debug(
                'connection_info: %(connection_info)s, '
                'was successfully retrieved',
                {'connection_info': json.dumps(connection_info)})
        except Exception as ex:
            msg = (_('connection info retrieval failed, error is: %s'),
                   six.text_type(ex))
            LOG.error(msg)
            raise exception.HPEPluginMountException(reason=msg)

        # Call OS Brick to connect volume
        try:
            device_info = self.connector.\
                connect_volume(connection_info['data'])
        except Exception as ex:
            msg = (_('OS Brick connect volume failed, error is: %s'),
                   six.text_type(ex))
            LOG.error(msg)
            raise exception.HPEPluginMountException(reason=msg)

        # Make sure the path exists
        path = FilePath(device_info['path']).realpath()
        if path.exists is False:
            msg = (_('path: %s,  does not exist'), path)
            LOG.error(msg)
            raise exception.HPEPluginMountException(reason=msg)

        LOG.debug(
            'path for volume: %(name)s, was successfully created: '
            '%(device)s realpath is: %(realpath)s', {
                'name': volname,
                'device': device_info['path'],
                'realpath': path.path
            })

        # Create filesystem on the new device
        if fileutil.has_filesystem(path.path) is False:
            fileutil.create_filesystem(path.path)
            LOG.debug('filesystem successfully created on : %(path)s',
                      {'path': path.path})

        # Determine if we need to mount the volume
        if vol_mount is DEFAULT_MOUNT_VOLUME:
            # mkdir for mounting the filesystem
            mount_dir = fileutil.mkdir_for_mounting(device_info['path'])
            LOG.debug(
                'Directory: %(mount_dir)s, '
                'successfully created to mount: '
                '%(mount)s', {
                    'mount_dir': mount_dir,
                    'mount': device_info['path']
                })

            # mount the directory
            fileutil.mount_dir(path.path, mount_dir)
            LOG.debug('Device: %(path) successfully mounted on %(mount)s', {
                'path': path.path,
                'mount': mount_dir
            })

            # TODO: find out how to invoke mkfs so that it creates the
            # filesystem without the lost+found directory
            # KLUDGE!!!!!
            lostfound = mount_dir + '/lost+found'
            lfdir = FilePath(lostfound)
            if lfdir.exists and fileutil.remove_dir(lostfound):
                LOG.debug(
                    'Successfully removed : '
                    '%(lost)s from mount: %(mount)s', {
                        'lost': lostfound,
                        'mount': mount_dir
                    })
        else:
            mount_dir = ''

        path_info = {}
        path_info['name'] = volname
        path_info['path'] = path.path
        path_info['device_info'] = device_info
        path_info['connection_info'] = connection_info
        path_info['mount_dir'] = mount_dir

        self._etcd.update_vol(volid, 'path_info', json.dumps(path_info))

        response = json.dumps({
            u"Err": '',
            u"Name": volname,
            u"Mountpoint": mount_dir,
            u"Devicename": path.path
        })
        return response
    def volumedriver_unmount(self, name):
        """
        The Docker container is no longer using the given volume,
        so unmount it.
        NOTE: Since Docker will automatically call Unmount if the Mount
        fails, make sure we properly handle partially completed Mounts.

        :param unicode name: The name of the volume.
        :return: Result indicating success.
        """
        LOG.info(_LI('In volumedriver_unmount'))
        contents = json.loads(name.content.getvalue())
        volname = contents['Name']
        vol = self._etcd.get_vol_byname(volname)
        if vol is not None:
            volid = vol['id']
        else:
            msg = (_LE('Volume unmount name not found %s'), volname)
            LOG.error(msg)
            raise exception.HPEPluginUMountException(reason=msg)

        vol_mount = DEFAULT_MOUNT_VOLUME
        if ('Opts' in contents and contents['Opts']
                and 'mount-volume' in contents['Opts']):
            vol_mount = str(contents['Opts']['mount-volume'])

        path_info = self._etcd.get_vol_path_info(volname)
        if path_info:
            path_name = path_info['path']
            connection_info = path_info['connection_info']
            mount_dir = path_info['mount_dir']
        else:
            msg = (_LE('Volume unmount path info not found %s'), volname)
            LOG.error(msg)
            raise exception.HPEPluginUMountException(reason=msg)

        # Get connector info from OS Brick
        # TODO: retrieve use_multipath and enforce_multipath from config file
        root_helper = 'sudo'

        connector_info = connector.get_connector_properties(
            root_helper,
            self._my_ip,
            multipath=self.use_multipath,
            enforce_multipath=self.enforce_multipath)

        # Determine if we need to unmount a previously mounted volume
        if vol_mount is DEFAULT_MOUNT_VOLUME:
            # unmount directory
            fileutil.umount_dir(mount_dir)
            # remove directory
            fileutil.remove_dir(mount_dir)

        # We're deferring the execution of the disconnect_volume as it can take
        # substantial
        # time (over 2 minutes) to cleanup the iscsi files
        if connection_info:
            LOG.info(_LI('call os brick to disconnect volume'))
            d = threads.deferToThread(self.connector.disconnect_volume,
                                      connection_info['data'], None)
            d.addCallbacks(self.disconnect_volume_callback,
                           self.disconnect_volume_error_callback)

        try:
            # Call driver to terminate the connection
            self.hpeplugin_driver.terminate_connection(vol, connector_info)
            LOG.info(
                _LI('connection_info: %(connection_info)s, '
                    'was successfully terminated'),
                {'connection_info': json.dumps(connection_info)})
        except Exception as ex:
            msg = (_LE('connection info termination failed %s'),
                   six.text_type(ex))
            LOG.error(msg)
            # Not much we can do here, so just continue on with unmount
            # We need to ensure we update etcd path_info so the stale
            # path does not stay around
            # raise exception.HPEPluginUMountException(reason=msg)

        # TODO: Create path_info list as we can mount the volume to multiple
        # hosts at the same time.
        self._etcd.update_vol(volid, 'path_info', None)

        LOG.info(
            _LI('path for volume: %(name)s, was successfully removed: '
                '%(path_name)s'), {
                    'name': volname,
                    'path_name': path_name
                })

        response = json.dumps({u"Err": ''})
        return response
Exemplo n.º 15
0
    def do_disable_cache(self, new_config, applied_config, lock_ownership):
        LOG.info(
            _LI("cache_tiering_disable_cache: "
                "new_config={}, applied_config={}").format(
                    new_config, applied_config))
        with lock_ownership():
            success = False
            _exception = None
            try:
                self.config_desired.cache_enabled = False
                for pool in CEPH_POOLS:
                    if (pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_JEWEL
                            or pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_HAMMER):
                        object_pool_name = \
                          self.service.monitor._get_object_pool_name()
                        pool['pool_name'] = object_pool_name

                    with self.ignore_ceph_failure():
                        self.cache_mode_set(pool, 'forward')

                for pool in CEPH_POOLS:
                    if (pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_JEWEL
                            or pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_HAMMER):
                        object_pool_name = \
                          self.service.monitor._get_object_pool_name()
                        pool['pool_name'] = object_pool_name

                    retries_left = 3
                    while True:
                        try:
                            self.cache_flush(pool)
                            break
                        except exception.CephCacheFlushFailure:
                            retries_left -= 1
                            if not retries_left:
                                # give up
                                break
                            else:
                                time.sleep(1)
                for pool in CEPH_POOLS:
                    if (pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_JEWEL
                            or pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_HAMMER):
                        object_pool_name = \
                          self.service.monitor._get_object_pool_name()
                        pool['pool_name'] = object_pool_name

                    with self.ignore_ceph_failure():
                        self.cache_overlay_delete(pool)
                        self.cache_tier_remove(pool)
                for pool in CEPH_POOLS:
                    if (pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_JEWEL
                            or pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_HAMMER):
                        object_pool_name = \
                          self.service.monitor._get_object_pool_name()
                        pool['pool_name'] = object_pool_name

                    with self.ignore_ceph_failure():
                        self.cache_pool_delete(pool)
                success = True
            except Exception as e:
                LOG.warn(
                    _LE('Failed to disable cache: reason=%s') %
                    traceback.format_exc())
                _exception = str(e)
            finally:
                self.service.monitor.monitor_check_cache_tier(False)
                if success:
                    self.config_desired.cache_enabled = False
                    self.config_applied.cache_enabled = False
                self.service.sysinv_conductor.call(
                    {},
                    'cache_tiering_disable_cache_complete',
                    success=success,
                    exception=_exception,
                    new_config=new_config.to_dict(),
                    applied_config=applied_config.to_dict())
Exemplo n.º 16
0
    def do_enable_cache(self, new_config, applied_config, lock_ownership):
        LOG.info(
            _LI("cache_tiering_enable_cache: "
                "new_config={}, applied_config={}").format(
                    new_config.to_dict(), applied_config.to_dict()))
        _unwind_actions = []
        with lock_ownership():
            success = False
            _exception = None
            try:
                self.config_desired.cache_enabled = True
                self.update_pools_info()
                for pool in CEPH_POOLS:
                    if (pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_JEWEL
                            or pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_HAMMER):
                        object_pool_name = \
                          self.service.monitor._get_object_pool_name()
                        pool['pool_name'] = object_pool_name

                    self.cache_pool_create(pool)
                    _unwind_actions.append(
                        functools.partial(self.cache_pool_delete, pool))
                for pool in CEPH_POOLS:
                    if (pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_JEWEL
                            or pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_HAMMER):
                        object_pool_name = \
                            self.service.monitor._get_object_pool_name()
                        pool['pool_name'] = object_pool_name

                    self.cache_tier_add(pool)
                    _unwind_actions.append(
                        functools.partial(self.cache_tier_remove, pool))
                for pool in CEPH_POOLS:
                    if (pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_JEWEL
                            or pool['pool_name']
                            == constants.CEPH_POOL_OBJECT_GATEWAY_NAME_HAMMER):
                        object_pool_name = \
                          self.service.monitor._get_object_pool_name()
                        pool['pool_name'] = object_pool_name

                    self.cache_mode_set(pool, 'writeback')
                    self.cache_pool_set_config(pool, new_config)
                    self.cache_overlay_create(pool)
                success = True
            except Exception as e:
                LOG.error(
                    _LE('Failed to enable cache: reason=%s') %
                    traceback.format_exc())
                for action in reversed(_unwind_actions):
                    try:
                        action()
                    except Exception:
                        LOG.warn(
                            _LW('Failed cache enable '
                                'unwind action: reason=%s') %
                            traceback.format_exc())
                success = False
                _exception = str(e)
            finally:
                self.service.monitor.monitor_check_cache_tier(success)
                if success:
                    self.config_applied.cache_enabled = True
                self.service.sysinv_conductor.call(
                    {},
                    'cache_tiering_enable_cache_complete',
                    success=success,
                    exception=_exception,
                    new_config=new_config.to_dict(),
                    applied_config=applied_config.to_dict())
                # Run first update of periodic target_max_bytes
                self.update_cache_target_max_bytes()
Exemplo n.º 17
0
    def _report_alarm_osds_health(self):
        response, osd_tree = self.service.ceph_api.osd_tree(body='json')
        if not response.ok:
            LOG.error(
                _LE("Failed to retrieve Ceph OSD tree: "
                    "status_code: %(status_code)s, reason: %(reason)s") % {
                        "status_code": response.status_code,
                        "reason": response.reason
                    })
            return
        osd_tree = dict([(n['id'], n) for n in osd_tree['output']['nodes']])
        alarms = []

        self._check_storage_tier(osd_tree, "storage-tier",
                                 lambda *args: alarms.append(args))

        old_alarms = {}
        for alarm_id in [
                fm_constants.FM_ALARM_ID_STORAGE_CEPH_MAJOR,
                fm_constants.FM_ALARM_ID_STORAGE_CEPH_CRITICAL
        ]:
            alarm_list = self.service.fm_api.get_faults_by_id(alarm_id)
            if not alarm_list:
                continue
            for alarm in alarm_list:
                if alarm.entity_instance_id not in old_alarms:
                    old_alarms[alarm.entity_instance_id] = []
                old_alarms[alarm.entity_instance_id].append(
                    (alarm.alarm_id, alarm.reason_text))

        for peer_group, reason, severity in alarms:
            if self._current_health_alarm_equals(reason, severity):
                continue
            alarm_critical_major = fm_constants.FM_ALARM_ID_STORAGE_CEPH_MAJOR
            if severity == fm_constants.FM_ALARM_SEVERITY_CRITICAL:
                alarm_critical_major = (
                    fm_constants.FM_ALARM_ID_STORAGE_CEPH_CRITICAL)
            entity_instance_id = (self.service.entity_instance_id +
                                  '.peergroup=' + peer_group)
            alarm_already_exists = False
            if entity_instance_id in old_alarms:
                for alarm_id, old_reason in old_alarms[entity_instance_id]:
                    if (reason == old_reason
                            and alarm_id == alarm_critical_major):
                        # if the alarm is exactly the same, we don't need
                        # to recreate it
                        old_alarms[entity_instance_id].remove(
                            (alarm_id, old_reason))
                        alarm_already_exists = True
                    elif (alarm_id == alarm_critical_major):
                        # if we change just the reason, then we just remove the
                        # alarm from the list so we don't remove it at the
                        # end of the function
                        old_alarms[entity_instance_id].remove(
                            (alarm_id, old_reason))

                if (len(old_alarms[entity_instance_id]) == 0):
                    del old_alarms[entity_instance_id]

                # in case the alarm is exactly the same, we skip the alarm set
                if alarm_already_exists is True:
                    continue
            major_repair_action = constants.REPAIR_ACTION_MAJOR_CRITICAL_ALARM
            fault = fm_api.Fault(
                alarm_id=alarm_critical_major,
                alarm_type=fm_constants.FM_ALARM_TYPE_4,
                alarm_state=fm_constants.FM_ALARM_STATE_SET,
                entity_type_id=fm_constants.FM_ENTITY_TYPE_CLUSTER,
                entity_instance_id=entity_instance_id,
                severity=severity,
                reason_text=reason,
                probable_cause=fm_constants.ALARM_PROBABLE_CAUSE_15,
                proposed_repair_action=major_repair_action,
                service_affecting=constants.SERVICE_AFFECTING['HEALTH_WARN'])
            alarm_uuid = self.service.fm_api.set_fault(fault)
            if alarm_uuid:
                LOG.info(
                    _LI("Created storage alarm %(alarm_uuid)s - "
                        "severity: %(severity)s, reason: %(reason)s, "
                        "service_affecting: %(service_affecting)s") % {
                            "alarm_uuid":
                            str(alarm_uuid),
                            "severity":
                            str(severity),
                            "reason":
                            reason,
                            "service_affecting":
                            str(constants.SERVICE_AFFECTING['HEALTH_WARN'])
                        })
            else:
                LOG.error(
                    _LE("Failed to create storage alarm - "
                        "severity: %(severity)s, reason: %(reason)s, "
                        "service_affecting: %(service_affecting)s") % {
                            "severity":
                            str(severity),
                            "reason":
                            reason,
                            "service_affecting":
                            str(constants.SERVICE_AFFECTING['HEALTH_WARN'])
                        })

        for entity_instance_id in old_alarms:
            for alarm_id, old_reason in old_alarms[entity_instance_id]:
                self.service.fm_api.clear_fault(alarm_id, entity_instance_id)
Exemplo n.º 18
0
    def _report_fault(self, health, alarm_id):
        if alarm_id == fm_constants.FM_ALARM_ID_STORAGE_CEPH:
            new_severity = constants.SEVERITY[health['health']]
            new_reason_text = self._parse_reason(health)
            new_service_affecting = \
                constants.SERVICE_AFFECTING[health['health']]

            # Raise or update alarm if necessary
            if ((not self.current_health_alarm)
                    or (self.current_health_alarm.__dict__['severity'] !=
                        new_severity)
                    or (self.current_health_alarm.__dict__['reason_text'] !=
                        new_reason_text)
                    or (self.current_health_alarm.__dict__['service_affecting']
                        != str(new_service_affecting))):

                fault = fm_api.Fault(
                    alarm_id=fm_constants.FM_ALARM_ID_STORAGE_CEPH,
                    alarm_type=fm_constants.FM_ALARM_TYPE_4,
                    alarm_state=fm_constants.FM_ALARM_STATE_SET,
                    entity_type_id=fm_constants.FM_ENTITY_TYPE_CLUSTER,
                    entity_instance_id=self.service.entity_instance_id,
                    severity=new_severity,
                    reason_text=new_reason_text,
                    probable_cause=fm_constants.ALARM_PROBABLE_CAUSE_15,
                    proposed_repair_action=constants.REPAIR_ACTION,
                    service_affecting=new_service_affecting)

                alarm_uuid = self.service.fm_api.set_fault(fault)
                if alarm_uuid:
                    LOG.info(
                        _LI("Created storage alarm %(alarm_uuid)s - "
                            "severity: %(severity)s, reason: %(reason)s, "
                            "service_affecting: %(service_affecting)s") % {
                                "alarm_uuid": alarm_uuid,
                                "severity": new_severity,
                                "reason": new_reason_text,
                                "service_affecting": new_service_affecting
                            })
                else:
                    LOG.error(
                        _LE("Failed to create storage alarm - "
                            "severity: %(severity)s, reason: %(reason)s "
                            "service_affecting: %(service_affecting)s") % {
                                "severity": new_severity,
                                "reason": new_reason_text,
                                "service_affecting": new_service_affecting
                            })

            # Log detailed reason for later analysis
            if (self.current_ceph_health != health['health']
                    or self.detailed_health_reason != health['detail']):
                LOG.info(
                    _LI("Ceph status changed: %(health)s "
                        "detailed reason: %(detail)s") % health)
                self.current_ceph_health = health['health']
                self.detailed_health_reason = health['detail']

        elif (alarm_id == fm_constants.FM_ALARM_ID_STORAGE_CEPH_FREE_SPACE
              and not health['tier_eid'] in self.current_quota_alarms):

            quota_reason_text = ("Quota/Space mismatch for the %s tier. The "
                                 "sum of Ceph pool quotas does not match the "
                                 "tier size." % health['tier_name'])
            fault = fm_api.Fault(
                alarm_id=fm_constants.FM_ALARM_ID_STORAGE_CEPH_FREE_SPACE,
                alarm_state=fm_constants.FM_ALARM_STATE_SET,
                entity_type_id=fm_constants.FM_ENTITY_TYPE_CLUSTER,
                entity_instance_id=health['tier_eid'],
                severity=fm_constants.FM_ALARM_SEVERITY_MINOR,
                reason_text=quota_reason_text,
                alarm_type=fm_constants.FM_ALARM_TYPE_7,
                probable_cause=fm_constants.ALARM_PROBABLE_CAUSE_75,
                proposed_repair_action=(
                    "Update ceph storage pool quotas to use all available "
                    "cluster space for the %s tier." % health['tier_name']),
                service_affecting=False)

            alarm_uuid = self.service.fm_api.set_fault(fault)
            if alarm_uuid:
                LOG.info(
                    _LI("Created storage quota storage alarm %(alarm_uuid)s. "
                        "Reason: %(reason)s") % {
                            "alarm_uuid": alarm_uuid,
                            "reason": quota_reason_text
                        })
            else:
                LOG.error(
                    _LE("Failed to create quota "
                        "storage alarm. Reason: %s") % quota_reason_text)
    def volumedriver_mount(self, name):
        """
        Mount the volume
        Mount the volume

        NOTE: If for any reason the mount request fails, Docker
        will automatically call uMount. So, just make sure uMount
        can handle partially completed Mount requests.

        :param unicode name: The name of the volume.

        :return: Result that includes the mountpoint.
        """
        LOG.debug('In volumedriver_mount')

        # TODO: use persistent storage to lookup volume for deletion
        contents = {}
        contents = json.loads(name.content.getvalue())
        volname = contents['Name']
        vol = self._etcd.get_vol_byname(volname)
        if vol is not None:
            volid = vol['id']
        else:
            msg = (_LE('Volume mount name not found %s'), volname)
            LOG.error(msg)
            raise exception.HPEPluginMountException(reason=msg)

        # Get connector info from OS Brick
        # TODO: retrieve use_multipath and enforce_multipath from config file
        root_helper = 'sudo'
        use_multipath = False

        connector_info = connector.get_connector_properties(
            root_helper, self._my_ip, use_multipath, enforce_multipath=False)

        try:
            # Call driver to initialize the connection
            self.hpeplugin_driver.create_export(vol, connector_info)
            connection_info = \
                self.hpeplugin_driver.initialize_connection(
                    vol, connector_info)
            LOG.debug('connection_info: %(connection_info)s, '
                      'was successfully retrieved',
                      {'connection_info': json.dumps(connection_info)})
        except Exception as ex:
            msg = (_('connection info retrieval failed, error is: %s'),
                   six.text_type(ex))
            LOG.error(msg)
            raise exception.HPEPluginMountException(reason=msg)

        # Call OS Brick to connect volume
        try:
            device_info = self.connector.\
                connect_volume(connection_info['data'])
        except Exception as ex:
            msg = (_('OS Brick connect volume failed, error is: %s'),
                   six.text_type(ex))
            LOG.error(msg)
            raise exception.HPEPluginMountException(reason=msg)

        # Make sure the path exists
        path = FilePath(device_info['path']).realpath()
        if path.exists is False:
            msg = (_('path: %s,  does not exist'), path)
            LOG.error(msg)
            raise exception.HPEPluginMountException(reason=msg)

        LOG.debug('path for volume: %(name)s, was successfully created: '
                  '%(device)s realpath is: %(realpath)s',
                  {'name': volname, 'device': device_info['path'],
                   'realpath': path.path})

        # mkdir for mounting the filesystem
        mount_dir = fileutil.mkdir_for_mounting(device_info['path'])
        LOG.debug('Directory: %(mount_dir)s, successfully created to mount: '
                  '%(mount)s',
                  {'mount_dir': mount_dir, 'mount': device_info['path']})

        # Create filesystem on the new device
        if fileutil.has_filesystem(path.path) is False:
            fileutil.create_filesystem(path.path)
            LOG.debug('filesystem successfully created on : %(path)s',
                      {'path': path.path})

        # mount the directory
        fileutil.mount_dir(path.path, mount_dir)
        LOG.debug('Device: %(path) successfully mounted on %(mount)s',
                  {'path': path.path, 'mount': mount_dir})

        # TODO: find out how to invoke mkfs so that it creates the filesystem
        # without the lost+found directory
        # KLUDGE!!!!!
        lostfound = mount_dir + '/lost+found'
        lfdir = FilePath(lostfound)
        if lfdir.exists and fileutil.remove_dir(lostfound):
            LOG.debug('Successfully removed : %(lost)s from mount: %(mount)s',
                      {'lost': lostfound, 'mount': mount_dir})

        path_info = {}
        path_info['name'] = volname
        path_info['path'] = path.path
        path_info['device_info'] = device_info
        path_info['connection_info'] = connection_info
        path_info['mount_dir'] = mount_dir

        self._etcd.update_vol(volid, 'path_info', json.dumps(path_info))

        response = json.dumps({u"Err": '', u"Mountpoint": mount_dir})
        return response
    def volumedriver_unmount(self, name):
        """
        The Docker container is no longer using the given volume,
        so unmount it.
        NOTE: Since Docker will automatically call Unmount if the Mount
        fails, make sure we properly handle partially completed Mounts.

        :param unicode name: The name of the volume.
        :return: Result indicating success.
        """
        LOG.info(_LI('In volumedriver_unmount'))
        contents = json.loads(name.content.getvalue())
        volname = contents['Name']
        vol = self._etcd.get_vol_byname(volname)
        if vol is not None:
            volid = vol['id']
        else:
            msg = (_LE('Volume unmount name not found %s'), volname)
            LOG.error(msg)
            raise exception.HPEPluginUMountException(reason=msg)

        path_info = self._etcd.get_vol_path_info(volname)
        if path_info:
            path_name = path_info['path']
            connection_info = path_info['connection_info']
            mount_dir = path_info['mount_dir']
        else:
            msg = (_LE('Volume unmount path info not found %s'), volname)
            LOG.error(msg)
            raise exception.HPEPluginUMountException(reason=msg)

        # Get connector info from OS Brick
        # TODO: retrieve use_multipath and enforce_multipath from config file
        root_helper = 'sudo'
        use_multipath = False

        connector_info = connector.get_connector_properties(
            root_helper, self._my_ip, use_multipath, enforce_multipath=False)
        # unmount directory
        fileutil.umount_dir(mount_dir)
        # remove directory
        fileutil.remove_dir(mount_dir)

        try:
            # Call driver to terminate the connection
            self.hpeplugin_driver.terminate_connection(vol, connector_info)
            LOG.info(_LI('connection_info: %(connection_info)s, '
                         'was successfully terminated'),
                     {'connection_info': json.dumps(connection_info)})
        except Exception as ex:
            msg = (_LE('connection info termination failed %s'),
                   six.text_type(ex))
            LOG.error(msg)
            raise exception.HPEPluginUMountException(reason=msg)

        # We're deferring the execution of the disconnect_volume as it can take
        # substantial
        # time (over 2 minutes) to cleanup the iscsi files
        if connection_info:
            LOG.info(_LI('call os brick to disconnect volume'))
            d = threads.deferToThread(self.connector.disconnect_volume,
                                      connection_info['data'], None)
            d.addCallbacks(self.disconnect_volume_callback,
                           self.disconnect_volume_error_callback)

        # TODO(leeantho) Without this sleep the volume is sometimes not
        # removed after the unmount. There must be a different way to fix
        # the issue?
        time.sleep(1)

        # TODO: Create path_info list as we can mount the volume to multiple
        # hosts at the same time.
        self._etcd.update_vol(volid, 'path_info', None)

        LOG.info(_LI('path for volume: %(name)s, was successfully removed: '
                     '%(path_name)s'), {'name': volname,
                                        'path_name': path_name})

        response = json.dumps({u"Err": ''})
        return response
    def volumedriver_unmount(self, name):
        """
        The Docker container is no longer using the given volume,
        so unmount it.
        NOTE: Since Docker will automatically call Unmount if the Mount
        fails, make sure we properly handle partially completed Mounts.

        :param unicode name: The name of the volume.
        :return: Result indicating success.
        """
        LOG.info(_LI('In volumedriver_unmount'))
        contents = json.loads(name.content.getvalue())
        volname = contents['Name']
        vol = self._etcd.get_vol_byname(volname)
        if vol is not None:
            volid = vol['id']
        else:
            msg = (_LE('Volume unmount name not found %s'), volname)
            LOG.error(msg)
            raise exception.HPEPluginUMountException(reason=msg)

        vol_mount = DEFAULT_MOUNT_VOLUME
        if ('Opts' in contents and contents['Opts'] and
                'mount-volume' in contents['Opts']):
            vol_mount = str(contents['Opts']['mount-volume'])

        path_info = self._etcd.get_vol_path_info(volname)
        if path_info:
            path_name = path_info['path']
            connection_info = path_info['connection_info']
            mount_dir = path_info['mount_dir']
        else:
            msg = (_LE('Volume unmount path info not found %s'), volname)
            LOG.error(msg)
            raise exception.HPEPluginUMountException(reason=msg)

        # Get connector info from OS Brick
        # TODO: retrieve use_multipath and enforce_multipath from config file
        root_helper = 'sudo'

        connector_info = connector.get_connector_properties(
            root_helper, self._my_ip, multipath=self.use_multipath,
            enforce_multipath=self.enforce_multipath)

        # Determine if we need to unmount a previously mounted volume
        if vol_mount is DEFAULT_MOUNT_VOLUME:
            # unmount directory
            fileutil.umount_dir(mount_dir)
            # remove directory
            fileutil.remove_dir(mount_dir)

        # We're deferring the execution of the disconnect_volume as it can take
        # substantial
        # time (over 2 minutes) to cleanup the iscsi files
        if connection_info:
            LOG.info(_LI('call os brick to disconnect volume'))
            d = threads.deferToThread(self.connector.disconnect_volume,
                                      connection_info['data'], None)
            d.addCallbacks(self.disconnect_volume_callback,
                           self.disconnect_volume_error_callback)

        try:
            # Call driver to terminate the connection
            self.hpeplugin_driver.terminate_connection(vol, connector_info)
            LOG.info(_LI('connection_info: %(connection_info)s, '
                         'was successfully terminated'),
                     {'connection_info': json.dumps(connection_info)})
        except Exception as ex:
            msg = (_LE('connection info termination failed %s'),
                   six.text_type(ex))
            LOG.error(msg)
            # Not much we can do here, so just continue on with unmount
            # We need to ensure we update etcd path_info so the stale
            # path does not stay around
            # raise exception.HPEPluginUMountException(reason=msg)

        # TODO: Create path_info list as we can mount the volume to multiple
        # hosts at the same time.
        self._etcd.update_vol(volid, 'path_info', None)

        LOG.info(_LI('path for volume: %(name)s, was successfully removed: '
                     '%(path_name)s'), {'name': volname,
                                        'path_name': path_name})

        response = json.dumps({u"Err": ''})
        return response