Exemple #1
0
 def list(self, diskguid=None):
     """
     Overview of all disks
     """
     if diskguid is not None:
         disk = Disk(diskguid)
         return disk.partitions
     return DiskPartitionList.get_partitions()
Exemple #2
0
 def _model_disk(cls, generic_disk_model, storagerouter):
     # type: (GenericDisk, StorageRouter) -> Disk
     """
     Models a disk
     :param generic_disk_model: The generic modeled disk (returned by Disktools)
     :type generic_disk_model: GenericDisk
     :param storagerouter: Storagerouter to which this disk belongs
     :type storagerouter: StorageRouter
     :return: The newly modeled disk
     :rtype: Disk
     """
     DiskController._logger.info('Disk {0} - Creating disk - {1}'.format(generic_disk_model.name, generic_disk_model.__dict__))
     disk = Disk()
     disk.storagerouter = storagerouter
     disk.name = generic_disk_model.name
     DiskController._update_disk(disk, generic_disk_model)
     for partition in generic_disk_model.partitions:  # type: GenericPartition
         cls._model_partition(partition, disk)
     return disk
Exemple #3
0
 def list(self, diskguid=None):
     """
     Overview of all disks
     :param diskguid: Disk guid to get the partitions from
     :type diskguid: str
     """
     if diskguid is not None:
         disk = Disk(diskguid)
         return disk.partitions
     return DiskPartitionList.get_partitions()
    def configure_disk(storagerouter_guid, disk_guid, partition_guid, offset,
                       size, roles):
        """
        Configures a partition
        :param storagerouter_guid: Guid of the StorageRouter to configure a disk on
        :type storagerouter_guid: str
        :param disk_guid: Guid of the disk to configure
        :type disk_guid: str
        :param partition_guid: Guid of the partition on the disk
        :type partition_guid: str
        :param offset: Offset for the partition
        :type offset: int
        :param size: Size of the partition
        :type size: int
        :param roles: Roles assigned to the partition
        :type roles: list
        :return: None
        :rtype: NoneType
        """
        # Validations
        storagerouter = StorageRouter(storagerouter_guid)
        for role in roles:
            if role not in DiskPartition.ROLES or role == DiskPartition.ROLES.BACKEND:
                raise RuntimeError('Invalid role specified: {0}'.format(role))
        disk = Disk(disk_guid)
        if disk.storagerouter_guid != storagerouter_guid:
            raise RuntimeError(
                'The given Disk is not on the given StorageRouter')
        for partition in disk.partitions:
            if DiskPartition.ROLES.BACKEND in partition.roles:
                raise RuntimeError('The given Disk is in use by a Backend')

        if len({DiskPartition.ROLES.DB, DiskPartition.ROLES.DTL}.intersection(
                set(roles))) > 0:
            roles_on_sr = StorageRouterController._get_roles_on_storagerouter(
                storagerouter.ip)
            for role in [DiskPartition.ROLES.DB, DiskPartition.ROLES.DTL]:
                if role in roles_on_sr and role in roles and roles_on_sr[role][
                        0] != disk.name:  # DB and DTL roles still have to be unassignable
                    raise RoleDuplicationException(
                        'Disk {0} cannot have the {1} role due to presence on disk {2}'
                        .format(disk.name, role, roles_on_sr[role][0]))

        # Create partition
        if partition_guid is None:
            StorageRouterController._logger.debug(
                'Creating new partition - Offset: {0} bytes - Size: {1} bytes - Roles: {2}'
                .format(offset, size, roles))
            with remote(storagerouter.ip, [DiskTools], username='******') as rem:
                if len(disk.aliases) == 0:
                    raise ValueError(
                        'Disk {0} does not have any aliases'.format(disk.name))
                rem.DiskTools.create_partition(disk_alias=disk.aliases[0],
                                               disk_size=disk.size,
                                               partition_start=offset,
                                               partition_size=size)
            DiskController.sync_with_reality(storagerouter_guid)
            disk = Disk(disk_guid)
            end_point = offset + size
            partition = None
            for part in disk.partitions:
                if offset < part.offset + part.size and end_point > part.offset:
                    partition = part
                    break

            if partition is None:
                raise RuntimeError(
                    'No new partition detected on disk {0} after having created 1'
                    .format(disk.name))
            StorageRouterController._logger.debug('Partition created')
        else:
            StorageRouterController._logger.debug('Using existing partition')
            partition = DiskPartition(partition_guid)
            if partition.disk_guid != disk_guid:
                raise RuntimeError(
                    'The given DiskPartition is not on the given Disk')
            if partition.filesystem in [
                    'swap', 'linux_raid_member', 'LVM2_member'
            ]:
                raise RuntimeError(
                    "It is not allowed to assign roles on partitions of type: ['swap', 'linux_raid_member', 'LVM2_member']"
                )
            metadata = StorageRouterController.get_metadata(storagerouter_guid)
            partition_info = metadata['partitions']
            removed_roles = set(partition.roles) - set(roles)
            used_roles = []
            for role in removed_roles:
                for info in partition_info[role]:
                    if info['in_use'] and info['guid'] == partition.guid:
                        used_roles.append(role)
            if len(used_roles) > 0:
                raise RuntimeError(
                    'Roles in use cannot be removed. Used roles: {0}'.format(
                        ', '.join(used_roles)))

        # Add filesystem
        if partition.filesystem is None or partition_guid is None:
            StorageRouterController._logger.debug('Creating filesystem')
            if len(partition.aliases) == 0:
                raise ValueError(
                    'Partition with offset {0} does not have any aliases'.
                    format(partition.offset))
            with remote(storagerouter.ip, [DiskTools], username='******') as rem:
                rem.DiskTools.make_fs(partition_alias=partition.aliases[0])
            DiskController.sync_with_reality(storagerouter_guid)
            partition = DiskPartition(partition.guid)
            if partition.filesystem not in ['ext4', 'xfs']:
                raise RuntimeError('Unexpected filesystem')
            StorageRouterController._logger.debug('Filesystem created')

        # Mount the partition and add to FSTab
        if partition.mountpoint is None:
            StorageRouterController._logger.debug('Configuring mount point')
            with remote(storagerouter.ip, [DiskTools], username='******') as rem:
                counter = 1
                mountpoint = '/mnt/{0}{1}'.format(
                    'ssd' if disk.is_ssd else 'hdd', counter)
                while True:
                    if not rem.DiskTools.mountpoint_exists(mountpoint):
                        break
                    counter += 1
                    mountpoint = '/mnt/{0}{1}'.format(
                        'ssd' if disk.is_ssd else 'hdd', counter)
                StorageRouterController._logger.debug(
                    'Found mount point: {0}'.format(mountpoint))
                rem.DiskTools.add_fstab(partition_aliases=partition.aliases,
                                        mountpoint=mountpoint,
                                        filesystem=partition.filesystem)
                rem.DiskTools.mount(mountpoint)
            DiskController.sync_with_reality(storagerouter_guid)
            partition = DiskPartition(partition.guid)
            if partition.mountpoint != mountpoint:
                raise RuntimeError('Unexpected mount point')
            StorageRouterController._logger.debug('Mount point configured')
        partition.roles = roles
        partition.save()
        StorageRouterController._logger.debug('Partition configured')
Exemple #5
0
    def sync_with_reality(storagerouter_guid):
        """
        Syncs the Disks from all StorageRouters with the reality.

        CHANGES MADE TO THIS CODE SHOULD BE REFLECTED IN THE ASD-MANAGER list_disks CALL TOO!!!!!!!!!!!!!!!!!!!!

        :param storagerouter_guid: Guid of the Storage Router to synchronize
        :type storagerouter_guid: str
        :return: None
        """
        storagerouter = StorageRouter(storagerouter_guid)
        try:
            client = SSHClient(storagerouter, username='******')
        except UnableToConnectException:
            DiskController._logger.exception('Could not connect to StorageRouter {0}'.format(storagerouter.ip))
            raise

        # Retrieve all symlinks for all devices
        # Example of name_alias_mapping:
        # {'/dev/md0': ['/dev/disk/by-id/md-uuid-ad2de634:26d97253:5eda0a23:96986b76', '/dev/disk/by-id/md-name-OVS-1:0'],
        #  '/dev/sda': ['/dev/disk/by-path/pci-0000:03:00.0-sas-0x5000c295fe2ff771-lun-0'],
        #  '/dev/sda1': ['/dev/disk/by-uuid/e3e0bc62-4edc-4c6b-a6ce-1f39e8f27e41', '/dev/disk/by-path/pci-0000:03:00.0-sas-0x5000c295fe2ff771-lun-0-part1']}
        name_alias_mapping = {}
        alias_name_mapping = {}
        for path_type in client.dir_list(directory='/dev/disk'):
            if path_type in ['by-uuid', 'by-partuuid']:  # UUIDs can change after creating a filesystem on a partition
                continue
            directory = '/dev/disk/{0}'.format(path_type)
            for symlink in client.dir_list(directory=directory):
                symlink_path = '{0}/{1}'.format(directory, symlink)
                link = client.file_read_link(symlink_path)
                if link not in name_alias_mapping:
                    name_alias_mapping[link] = []
                name_alias_mapping[link].append(symlink_path)
                alias_name_mapping[symlink_path] = link

        # Parse 'lsblk' output
        # --exclude 1 for RAM devices, 2 for floppy devices, 11 for CD-ROM devices (See https://www.kernel.org/doc/Documentation/devices.txt)
        command = ['lsblk', '--pairs', '--bytes', '--noheadings', '--exclude', '1,2,11']
        output = '--output=KNAME,SIZE,MODEL,STATE,MAJ:MIN,FSTYPE,TYPE,ROTA,MOUNTPOINT,LOG-SEC{0}'
        regex = '^KNAME="(?P<name>.*)" SIZE="(?P<size>\d*)" MODEL="(?P<model>.*)" STATE="(?P<state>.*)" MAJ:MIN="(?P<dev_nr>.*)" FSTYPE="(?P<fstype>.*)" TYPE="(?P<type>.*)" ROTA="(?P<rota>[0,1])" MOUNTPOINT="(?P<mtpt>.*)" LOG-SEC="(?P<sector_size>\d*)"( SERIAL="(?P<serial>.*)")?$'
        try:
            devices = client.run(command + [output.format(',SERIAL')]).splitlines()
        except:
            devices = client.run(command + [output.format('')]).splitlines()
        device_regex = re.compile(regex)
        configuration = {}
        parsed_devices = []
        for device in devices:
            match = re.match(device_regex, device)
            if match is None:
                DiskController._logger.error('Device regex did not match for {0}. Please investigate'.format(device))
                raise Exception('Failed to parse \'lsblk\' output')

            groupdict = match.groupdict()
            name = groupdict['name'].strip()
            size = groupdict['size'].strip()
            model = groupdict['model'].strip()
            state = groupdict['state'].strip()
            dev_nr = groupdict['dev_nr'].strip()
            serial = (groupdict['serial'] or '').strip()
            fs_type = groupdict['fstype'].strip()
            dev_type = groupdict['type'].strip()
            rotational = groupdict['rota'].strip()
            mount_point = groupdict['mtpt'].strip()
            sector_size = groupdict['sector_size'].strip()

            if dev_type == 'rom':
                continue

            link = client.file_read_link('/sys/block/{0}'.format(name))
            device_state = None
            friendly_path = '/dev/{0}'.format(name)
            system_aliases = name_alias_mapping.get(friendly_path, [friendly_path])
            device_is_also_partition = False
            if link is not None:  # If this returns, it means its a device and not a partition
                device_is_also_partition = mount_point != ''  # LVM, RAID1, ... have the tendency to be a device with a partition on it, but the partition is not reported by 'lsblk'
                device_state = Disk.STATES.OK if state == 'running' or dev_nr.split(':')[0] != '8' else Disk.STATES.FAILURE
                parsed_devices.append({'name': name,
                                       'state': device_state})
                configuration[name] = {'name': name,
                                       'size': int(size),
                                       'model': model if model != '' else None,
                                       'serial': serial if serial != '' else None,
                                       'state': device_state,
                                       'is_ssd': rotational == '0',
                                       'aliases': system_aliases,
                                       'partitions': {}}
            if link is None or device_is_also_partition is True:
                current_device = None
                current_device_state = None
                if device_is_also_partition is True:
                    offset = 0
                    current_device = name
                    current_device_state = device_state
                else:
                    offset = 0
                    for device_info in reversed(parsed_devices):
                        try:
                            current_device = device_info['name']
                            current_device_state = device_info['state']
                            offset = int(client.file_read('/sys/block/{0}/{1}/start'.format(current_device, name))) * int(sector_size)
                            break
                        except Exception:
                            pass
                if current_device is None:
                    raise RuntimeError('Failed to retrieve the device information for current partition')
                mount_point = mount_point if mount_point != '' else None
                partition_state = Disk.STATES.OK if current_device_state == Disk.STATES.OK else Disk.STATES.FAILURE
                if mount_point is not None and fs_type != 'swap':
                    try:
                        filename = '{0}/{1}'.format(mount_point, str(time.time()))
                        client.run(['touch', filename])
                        client.run(['rm', filename])
                    except Exception:
                        partition_state = Disk.STATES.FAILURE

                configuration[current_device]['partitions'][offset] = {'size': int(size),
                                                                       'state': partition_state,
                                                                       'offset': offset,
                                                                       'aliases': system_aliases,
                                                                       'filesystem': fs_type if fs_type != '' else None,
                                                                       'mountpoint': mount_point}

        # Sync the model
        for disk in storagerouter.disks:
            disk_info = None
            for alias in disk.aliases:
                if alias in alias_name_mapping:
                    name = alias_name_mapping[alias].replace('/dev/', '')
                    if name in configuration:
                        disk_info = configuration.pop(name)
                        break

            if disk_info is None and disk.name in configuration and (disk.name.startswith('fio') or
                                                                     disk.name.startswith('loop') or
                                                                     disk.name.startswith('nvme')):  # Partitioned loop, nvme devices no longer show up in alias_name_mapping
                disk_info = configuration.pop(disk.name)

            # Remove disk / partitions if not reported by 'lsblk'
            if disk_info is None:
                DiskController._logger.info('Disk {0} - No longer found'.format(disk.name))
                delete = True
                for partition in disk.partitions:
                    if len(partition.roles) > 0:
                        delete = False
                        DiskController._logger.warning('Disk {0} - Partition with offset {1} - Has roles, will not delete'.format(disk.name, partition.offset))
                        break
                if delete is True:
                    for partition in disk.partitions:
                        partition.delete()
                    disk.delete()
                    DiskController._logger.info('Disk {0} - Deleted'.format(disk.name))
                else:
                    for partition in disk.partitions:
                        DiskController._update_partition(partition, {'state': 'MISSING'})
                        DiskController._logger.warning('Disk {0} - Partition with offset {1} - Updated status to MISSING'.format(disk.name, partition.offset))
                    DiskController._update_disk(disk, {'state': 'MISSING'})
                    DiskController._logger.warning('Disk {0} - Updated status to MISSING'.format(disk.name))

            else:  # Update existing disks and their partitions
                DiskController._logger.info('Disk {0} - Found, updating'.format(disk.name))
                DiskController._update_disk(disk, disk_info)
                partition_info = disk_info['partitions']
                for partition in disk.partitions:
                    if partition.offset not in partition_info:
                        DiskController._logger.info('Disk {0} - Partition with offset {1} - No longer found'.format(disk.name, partition.offset))
                        if len(partition.roles) > 0:
                            DiskController._logger.warning('Disk {0} - Partition with offset {1} - Update status to MISSING'.format(disk.name, partition.offset))
                            DiskController._update_partition(partition, {'state': 'MISSING'})
                        else:
                            DiskController._logger.info('Disk {0} - Partition with offset {1} - Deleting'.format(disk.name, partition.offset))
                            partition.delete()
                    else:
                        DiskController._update_partition(partition, partition_info.pop(partition.offset))
                for partition_offset in partition_info:
                    DiskController._logger.info('Disk {0} - Creating partition - {1}'.format(disk.name, partition_info[partition_offset]))
                    DiskController._create_partition(partition_info[partition_offset], disk)
        # Create all disks and their partitions not yet modeled
        for disk_name in configuration:
            DiskController._logger.info('Disk {0} - Creating disk - {1}'.format(disk_name, configuration[disk_name]))
            disk = Disk()
            disk.storagerouter = storagerouter
            disk.name = disk_name
            DiskController._update_disk(disk, configuration[disk_name])
            partition_info = configuration[disk_name]['partitions']
            for partition_offset in partition_info:
                DiskController._create_partition(partition_info[partition_offset], disk)
Exemple #6
0
    def sync_with_reality(storagerouter_guid):
        """
        Syncs the Disks from all StorageRouters with the reality.

        CHANGES MADE TO THIS CODE SHOULD BE REFLECTED IN THE ASD-MANAGER list_disks CALL TOO!!!!!!!!!!!!!!!!!!!!

        :param storagerouter_guid: Guid of the Storage Router to synchronize
        :type storagerouter_guid: str
        :return: None
        """
        storagerouter = StorageRouter(storagerouter_guid)
        try:
            client = SSHClient(storagerouter, username='******')
        except UnableToConnectException:
            DiskController._logger.exception(
                'Could not connect to StorageRouter {0}'.format(
                    storagerouter.ip))
            raise

        # Retrieve all symlinks for all devices
        # Example of name_alias_mapping:
        # {'/dev/md0': ['/dev/disk/by-id/md-uuid-ad2de634:26d97253:5eda0a23:96986b76', '/dev/disk/by-id/md-name-OVS-1:0'],
        #  '/dev/sda': ['/dev/disk/by-path/pci-0000:03:00.0-sas-0x5000c295fe2ff771-lun-0'],
        #  '/dev/sda1': ['/dev/disk/by-uuid/e3e0bc62-4edc-4c6b-a6ce-1f39e8f27e41', '/dev/disk/by-path/pci-0000:03:00.0-sas-0x5000c295fe2ff771-lun-0-part1']}
        name_alias_mapping = {}
        alias_name_mapping = {}
        for path_type in client.dir_list(directory='/dev/disk'):
            if path_type in [
                    'by-uuid', 'by-partuuid'
            ]:  # UUIDs can change after creating a filesystem on a partition
                continue
            directory = '/dev/disk/{0}'.format(path_type)
            for symlink in client.dir_list(directory=directory):
                symlink_path = '{0}/{1}'.format(directory, symlink)
                link = client.file_read_link(symlink_path)
                if link not in name_alias_mapping:
                    name_alias_mapping[link] = []
                name_alias_mapping[link].append(symlink_path)
                alias_name_mapping[symlink_path] = link

        # Parse 'lsblk' output
        # --exclude 1 for RAM devices, 2 for floppy devices, 11 for CD-ROM devices (See https://www.kernel.org/doc/Documentation/devices.txt)
        devices = client.run([
            'lsblk', '--pairs', '--bytes', '--noheadings', '--exclude',
            '1,2,11',
            '--output=KNAME,SIZE,MODEL,STATE,MAJ:MIN,FSTYPE,TYPE,ROTA,MOUNTPOINT,LOG-SEC'
        ]).splitlines()
        device_regex = re.compile(
            '^KNAME="(?P<name>.*)" SIZE="(?P<size>\d*)" MODEL="(?P<model>.*)" STATE="(?P<state>.*)" MAJ:MIN="(?P<dev_nr>.*)" FSTYPE="(?P<fstype>.*)" TYPE="(?P<type>.*)" ROTA="(?P<rota>[0,1])" MOUNTPOINT="(?P<mtpt>.*)" LOG-SEC="(?P<sector_size>\d*)"$'
        )
        configuration = {}
        parsed_devices = []
        for device in devices:
            match = re.match(device_regex, device)
            if match is None:
                DiskController._logger.error(
                    'Device regex did not match for {0}. Please investigate'.
                    format(device))
                raise Exception('Failed to parse \'lsblk\' output')

            groupdict = match.groupdict()
            name = groupdict['name'].strip()
            size = groupdict['size'].strip()
            model = groupdict['model'].strip()
            state = groupdict['state'].strip()
            dev_nr = groupdict['dev_nr'].strip()
            fs_type = groupdict['fstype'].strip()
            dev_type = groupdict['type'].strip()
            rotational = groupdict['rota'].strip()
            mount_point = groupdict['mtpt'].strip()
            sector_size = groupdict['sector_size'].strip()

            if dev_type == 'rom':
                continue

            link = client.file_read_link('/sys/block/{0}'.format(name))
            device_state = None
            friendly_path = '/dev/{0}'.format(name)
            system_aliases = name_alias_mapping.get(friendly_path,
                                                    [friendly_path])
            device_is_also_partition = False
            if link is not None:  # If this returns, it means its a device and not a partition
                device_is_also_partition = mount_point != ''  # LVM, RAID1, ... have the tendency to be a device with a partition on it, but the partition is not reported by 'lsblk'
                device_state = Disk.STATES.OK if state == 'running' or dev_nr.split(
                    ':')[0] != '8' else Disk.STATES.FAILURE
                parsed_devices.append({'name': name, 'state': device_state})
                configuration[name] = {
                    'name': name,
                    'size': int(size),
                    'model': model if model != '' else None,
                    'state': device_state,
                    'is_ssd': rotational == '0',
                    'aliases': system_aliases,
                    'partitions': {}
                }
            if link is None or device_is_also_partition is True:
                current_device = None
                current_device_state = None
                if device_is_also_partition is True:
                    offset = 0
                    current_device = name
                    current_device_state = device_state
                else:
                    offset = 0
                    for device_info in reversed(parsed_devices):
                        try:
                            current_device = device_info['name']
                            current_device_state = device_info['state']
                            offset = int(
                                client.file_read(
                                    '/sys/block/{0}/{1}/start'.format(
                                        current_device,
                                        name))) * int(sector_size)
                            break
                        except Exception:
                            pass
                if current_device is None:
                    raise RuntimeError(
                        'Failed to retrieve the device information for current partition'
                    )
                mount_point = mount_point if mount_point != '' else None
                partition_state = Disk.STATES.OK if current_device_state == Disk.STATES.OK else Disk.STATES.FAILURE
                if mount_point is not None and fs_type != 'swap':
                    try:
                        filename = '{0}/{1}'.format(mount_point,
                                                    str(time.time()))
                        client.run(['touch', filename])
                        client.run(['rm', filename])
                    except Exception:
                        partition_state = Disk.STATES.FAILURE

                configuration[current_device]['partitions'][offset] = {
                    'size': int(size),
                    'state': partition_state,
                    'offset': offset,
                    'aliases': system_aliases,
                    'filesystem': fs_type if fs_type != '' else None,
                    'mountpoint': mount_point
                }

        # Sync the model
        for disk in storagerouter.disks:
            disk_info = None
            for alias in disk.aliases:
                if alias in alias_name_mapping:
                    name = alias_name_mapping[alias].replace('/dev/', '')
                    if name in configuration:
                        disk_info = configuration.pop(name)
                        break

            if disk_info is None and disk.name in configuration and (
                    disk.name.startswith('fio') or disk.name.startswith('loop')
                    or disk.name.startswith('nvme')
            ):  # Partitioned loop, nvme devices no longer show up in alias_name_mapping
                disk_info = configuration.pop(disk.name)

            # Remove disk / partitions if not reported by 'lsblk'
            if disk_info is None:
                DiskController._logger.info(
                    'Disk {0} - No longer found'.format(disk.name))
                delete = True
                for partition in disk.partitions:
                    if len(partition.roles) > 0:
                        delete = False
                        DiskController._logger.warning(
                            'Disk {0} - Partition with offset {1} - Has roles, will not delete'
                            .format(disk.name, partition.offset))
                        break
                if delete is True:
                    for partition in disk.partitions:
                        partition.delete()
                    disk.delete()
                    DiskController._logger.info('Disk {0} - Deleted'.format(
                        disk.name))
                else:
                    for partition in disk.partitions:
                        DiskController._update_partition(
                            partition, {'state': 'MISSING'})
                        DiskController._logger.warning(
                            'Disk {0} - Partition with offset {1} - Updated status to MISSING'
                            .format(disk.name, partition.offset))
                    DiskController._update_disk(disk, {'state': 'MISSING'})
                    DiskController._logger.warning(
                        'Disk {0} - Updated status to MISSING'.format(
                            disk.name))

            else:  # Update existing disks and their partitions
                DiskController._logger.info(
                    'Disk {0} - Found, updating'.format(disk.name))
                DiskController._update_disk(disk, disk_info)
                partition_info = disk_info['partitions']
                for partition in disk.partitions:
                    if partition.offset not in partition_info:
                        DiskController._logger.info(
                            'Disk {0} - Partition with offset {1} - No longer found'
                            .format(disk.name, partition.offset))
                        if len(partition.roles) > 0:
                            DiskController._logger.warning(
                                'Disk {0} - Partition with offset {1} - Update status to MISSING'
                                .format(disk.name, partition.offset))
                            DiskController._update_partition(
                                partition, {'state': 'MISSING'})
                        else:
                            DiskController._logger.info(
                                'Disk {0} - Partition with offset {1} - Deleting'
                                .format(disk.name, partition.offset))
                            partition.delete()
                    else:
                        DiskController._update_partition(
                            partition, partition_info.pop(partition.offset))
                for partition_offset in partition_info:
                    DiskController._logger.info(
                        'Disk {0} - Creating partition - {1}'.format(
                            disk.name, partition_info[partition_offset]))
                    DiskController._create_partition(
                        partition_info[partition_offset], disk)
        # Create all disks and their partitions not yet modeled
        for disk_name in configuration:
            DiskController._logger.info(
                'Disk {0} - Creating disk - {1}'.format(
                    disk_name, configuration[disk_name]))
            disk = Disk()
            disk.storagerouter = storagerouter
            disk.name = disk_name
            DiskController._update_disk(disk, configuration[disk_name])
            partition_info = configuration[disk_name]['partitions']
            for partition_offset in partition_info:
                DiskController._create_partition(
                    partition_info[partition_offset], disk)
    def test_happypath(self):
        """
        Validates the happy path; Hourly snapshots are taken with a few manual consistent
        every now an then. The delete policy is executed every day
        """
        # Setup
        # There are 2 machines; one with two disks, one with one disk and an additional disk
        failure_domain = FailureDomain()
        failure_domain.name = "Test"
        failure_domain.save()
        backend_type = BackendType()
        backend_type.name = "BackendType"
        backend_type.code = "BT"
        backend_type.save()
        vpool = VPool()
        vpool.name = "vpool"
        vpool.backend_type = backend_type
        vpool.save()
        pmachine = PMachine()
        pmachine.name = "PMachine"
        pmachine.username = "******"
        pmachine.ip = "127.0.0.1"
        pmachine.hvtype = "VMWARE"
        pmachine.save()
        storage_router = StorageRouter()
        storage_router.name = "storage_router"
        storage_router.ip = "127.0.0.1"
        storage_router.pmachine = pmachine
        storage_router.machine_id = System.get_my_machine_id()
        storage_router.rdma_capable = False
        storage_router.primary_failure_domain = failure_domain
        storage_router.save()
        disk = Disk()
        disk.name = "physical_disk_1"
        disk.path = "/dev/non-existent"
        disk.size = 500 * 1024 ** 3
        disk.state = "OK"
        disk.is_ssd = True
        disk.storagerouter = storage_router
        disk.save()
        disk_partition = DiskPartition()
        disk_partition.id = "disk_partition_id"
        disk_partition.disk = disk
        disk_partition.path = "/dev/disk/non-existent"
        disk_partition.size = 400 * 1024 ** 3
        disk_partition.state = "OK"
        disk_partition.offset = 1024
        disk_partition.roles = [DiskPartition.ROLES.SCRUB]
        disk_partition.mountpoint = "/var/tmp"
        disk_partition.save()
        vmachine_1 = VMachine()
        vmachine_1.name = "vmachine_1"
        vmachine_1.devicename = "dummy"
        vmachine_1.pmachine = pmachine
        vmachine_1.save()
        vdisk_1_1 = VDisk()
        vdisk_1_1.name = "vdisk_1_1"
        vdisk_1_1.volume_id = "vdisk_1_1"
        vdisk_1_1.vmachine = vmachine_1
        vdisk_1_1.vpool = vpool
        vdisk_1_1.devicename = "dummy"
        vdisk_1_1.size = 0
        vdisk_1_1.save()
        vdisk_1_1.reload_client()
        vdisk_1_2 = VDisk()
        vdisk_1_2.name = "vdisk_1_2"
        vdisk_1_2.volume_id = "vdisk_1_2"
        vdisk_1_2.vmachine = vmachine_1
        vdisk_1_2.vpool = vpool
        vdisk_1_2.devicename = "dummy"
        vdisk_1_2.size = 0
        vdisk_1_2.save()
        vdisk_1_2.reload_client()
        vmachine_2 = VMachine()
        vmachine_2.name = "vmachine_2"
        vmachine_2.devicename = "dummy"
        vmachine_2.pmachine = pmachine
        vmachine_2.save()
        vdisk_2_1 = VDisk()
        vdisk_2_1.name = "vdisk_2_1"
        vdisk_2_1.volume_id = "vdisk_2_1"
        vdisk_2_1.vmachine = vmachine_2
        vdisk_2_1.vpool = vpool
        vdisk_2_1.devicename = "dummy"
        vdisk_2_1.size = 0
        vdisk_2_1.save()
        vdisk_2_1.reload_client()
        vdisk_3 = VDisk()
        vdisk_3.name = "vdisk_3"
        vdisk_3.volume_id = "vdisk_3"
        vdisk_3.vpool = vpool
        vdisk_3.devicename = "dummy"
        vdisk_3.size = 0
        vdisk_3.save()
        vdisk_3.reload_client()

        for disk in [vdisk_1_1, vdisk_1_2, vdisk_2_1, vdisk_3]:
            [dynamic for dynamic in disk._dynamics if dynamic.name == "snapshots"][0].timeout = 0

        # Run the testing scenario
        debug = True
        amount_of_days = 50
        base = datetime.now().date()
        day = timedelta(1)
        minute = 60
        hour = minute * 60

        for d in xrange(0, amount_of_days):
            base_timestamp = DeleteSnapshots._make_timestamp(base, day * d)
            print ""
            print "Day cycle: {0}: {1}".format(d, datetime.fromtimestamp(base_timestamp).strftime("%Y-%m-%d"))

            # At the start of the day, delete snapshot policy runs at 00:30
            print "- Deleting snapshots"
            ScheduledTaskController.delete_snapshots(timestamp=base_timestamp + (minute * 30))

            # Validate snapshots
            print "- Validating snapshots"
            for vdisk in [vdisk_1_1, vdisk_1_2, vdisk_2_1, vdisk_3]:
                self._validate(vdisk, d, base, amount_of_days, debug)

            # During the day, snapshots are taken
            # - Create non consistent snapshot every hour, between 2:00 and 22:00
            # - Create consistent snapshot at 6:30, 12:30, 18:30
            print "- Creating snapshots"
            for h in xrange(2, 23):
                timestamp = base_timestamp + (hour * h)
                for vm in [vmachine_1, vmachine_2]:
                    VMachineController.snapshot(
                        machineguid=vm.guid,
                        label="ss_i_{0}:00".format(str(h)),
                        is_consistent=False,
                        timestamp=timestamp,
                    )
                    if h in [6, 12, 18]:
                        ts = timestamp + (minute * 30)
                        VMachineController.snapshot(
                            machineguid=vm.guid, label="ss_c_{0}:30".format(str(h)), is_consistent=True, timestamp=ts
                        )

                VDiskController.create_snapshot(
                    diskguid=vdisk_3.guid,
                    metadata={
                        "label": "ss_i_{0}:00".format(str(h)),
                        "is_consistent": False,
                        "timestamp": str(timestamp),
                        "machineguid": None,
                    },
                )
                if h in [6, 12, 18]:
                    ts = timestamp + (minute * 30)
                    VDiskController.create_snapshot(
                        diskguid=vdisk_3.guid,
                        metadata={
                            "label": "ss_c_{0}:30".format(str(h)),
                            "is_consistent": True,
                            "timestamp": str(ts),
                            "machineguid": None,
                        },
                    )
    def test_happypath(self):
        """
        Validates the happy path; Hourly snapshots are taken with a few manual consistent
        every now an then. The delete policy is executed every day
        """
        # Setup
        # There are 2 machines; one with two disks, one with one disk and a stand-alone additional disk
        failure_domain = FailureDomain()
        failure_domain.name = 'Test'
        failure_domain.save()
        backend_type = BackendType()
        backend_type.name = 'BackendType'
        backend_type.code = 'BT'
        backend_type.save()
        vpool = VPool()
        vpool.name = 'vpool'
        vpool.status = 'RUNNING'
        vpool.backend_type = backend_type
        vpool.save()
        pmachine = PMachine()
        pmachine.name = 'PMachine'
        pmachine.username = '******'
        pmachine.ip = '127.0.0.1'
        pmachine.hvtype = 'VMWARE'
        pmachine.save()
        storage_router = StorageRouter()
        storage_router.name = 'storage_router'
        storage_router.ip = '127.0.0.1'
        storage_router.pmachine = pmachine
        storage_router.machine_id = System.get_my_machine_id()
        storage_router.rdma_capable = False
        storage_router.primary_failure_domain = failure_domain
        storage_router.save()
        disk = Disk()
        disk.name = 'physical_disk_1'
        disk.path = '/dev/non-existent'
        disk.size = 500 * 1024 ** 3
        disk.state = 'OK'
        disk.is_ssd = True
        disk.storagerouter = storage_router
        disk.save()
        disk_partition = DiskPartition()
        disk_partition.id = 'disk_partition_id'
        disk_partition.disk = disk
        disk_partition.path = '/dev/disk/non-existent'
        disk_partition.size = 400 * 1024 ** 3
        disk_partition.state = 'OK'
        disk_partition.offset = 1024
        disk_partition.roles = [DiskPartition.ROLES.SCRUB]
        disk_partition.mountpoint = '/var/tmp'
        disk_partition.save()
        vmachine_1 = VMachine()
        vmachine_1.name = 'vmachine_1'
        vmachine_1.devicename = 'dummy'
        vmachine_1.pmachine = pmachine
        vmachine_1.save()
        vdisk_1_1 = VDisk()
        vdisk_1_1.name = 'vdisk_1_1'
        vdisk_1_1.volume_id = 'vdisk_1_1'
        vdisk_1_1.vmachine = vmachine_1
        vdisk_1_1.vpool = vpool
        vdisk_1_1.devicename = 'dummy'
        vdisk_1_1.size = 0
        vdisk_1_1.save()
        vdisk_1_1.reload_client()
        vdisk_1_2 = VDisk()
        vdisk_1_2.name = 'vdisk_1_2'
        vdisk_1_2.volume_id = 'vdisk_1_2'
        vdisk_1_2.vmachine = vmachine_1
        vdisk_1_2.vpool = vpool
        vdisk_1_2.devicename = 'dummy'
        vdisk_1_2.size = 0
        vdisk_1_2.save()
        vdisk_1_2.reload_client()
        vmachine_2 = VMachine()
        vmachine_2.name = 'vmachine_2'
        vmachine_2.devicename = 'dummy'
        vmachine_2.pmachine = pmachine
        vmachine_2.save()
        vdisk_2_1 = VDisk()
        vdisk_2_1.name = 'vdisk_2_1'
        vdisk_2_1.volume_id = 'vdisk_2_1'
        vdisk_2_1.vmachine = vmachine_2
        vdisk_2_1.vpool = vpool
        vdisk_2_1.devicename = 'dummy'
        vdisk_2_1.size = 0
        vdisk_2_1.save()
        vdisk_2_1.reload_client()
        vdisk_3 = VDisk()
        vdisk_3.name = 'vdisk_3'
        vdisk_3.volume_id = 'vdisk_3'
        vdisk_3.vpool = vpool
        vdisk_3.devicename = 'dummy'
        vdisk_3.size = 0
        vdisk_3.save()
        vdisk_3.reload_client()

        for disk in [vdisk_1_1, vdisk_1_2, vdisk_2_1, vdisk_3]:
            [dynamic for dynamic in disk._dynamics if dynamic.name == 'snapshots'][0].timeout = 0

        # Run the testing scenario
        travis = 'TRAVIS' in os.environ and os.environ['TRAVIS'] == 'true'
        if travis is True:
            print 'Running in Travis, reducing output.'
        debug = not travis
        amount_of_days = 50
        base = datetime.datetime.now().date()
        day = datetime.timedelta(1)
        minute = 60
        hour = minute * 60

        for d in xrange(0, amount_of_days):
            base_timestamp = self._make_timestamp(base, day * d)
            print ''
            print 'Day cycle: {0}: {1}'.format(d, datetime.datetime.fromtimestamp(base_timestamp).strftime('%Y-%m-%d'))

            # At the start of the day, delete snapshot policy runs at 00:30
            print '- Deleting snapshots'
            ScheduledTaskController.delete_snapshots(timestamp=base_timestamp + (minute * 30))

            # Validate snapshots
            print '- Validating snapshots'
            for vdisk in [vdisk_1_1, vdisk_1_2, vdisk_2_1, vdisk_3]:
                self._validate(vdisk, d, base, amount_of_days, debug)

            # During the day, snapshots are taken
            # - Create non consistent snapshot every hour, between 2:00 and 22:00
            # - Create consistent snapshot at 6:30, 12:30, 18:30
            print '- Creating snapshots'
            for h in xrange(2, 23):
                timestamp = base_timestamp + (hour * h)
                for vm in [vmachine_1, vmachine_2]:
                    VMachineController.snapshot(machineguid=vm.guid,
                                                label='ss_i_{0}:00'.format(str(h)),
                                                is_consistent=False,
                                                timestamp=timestamp)
                    if h in [6, 12, 18]:
                        ts = (timestamp + (minute * 30))
                        VMachineController.snapshot(machineguid=vm.guid,
                                                    label='ss_c_{0}:30'.format(str(h)),
                                                    is_consistent=True,
                                                    timestamp=ts)

                VDiskController.create_snapshot(diskguid=vdisk_3.guid,
                                                metadata={'label': 'ss_i_{0}:00'.format(str(h)),
                                                          'is_consistent': False,
                                                          'timestamp': str(timestamp),
                                                          'machineguid': None})
                if h in [6, 12, 18]:
                    ts = (timestamp + (minute * 30))
                    VDiskController.create_snapshot(diskguid=vdisk_3.guid,
                                                    metadata={'label': 'ss_c_{0}:30'.format(str(h)),
                                                              'is_consistent': True,
                                                              'timestamp': str(ts),
                                                              'machineguid': None})
Exemple #9
0
    def build_service_structure(structure, previous_structure=None):
        """
        Builds an MDS service structure
        Example:
            structure = Helper.build_service_structure(
                {'vpools': [1],
                 'domains': [],
                 'storagerouters': [1],
                 'storagedrivers': [(1, 1, 1)],  # (<id>, <vpool_id>, <storagerouter_id>)
                 'mds_services': [(1, 1)],  # (<id>, <storagedriver_id>)
                 'storagerouter_domains': []}  # (<id>, <storagerouter_id>, <domain_id>)
            )
        """
        if previous_structure is None:
            previous_structure = {}
        vdisks = previous_structure.get("vdisks", {})
        vpools = previous_structure.get("vpools", {})
        domains = previous_structure.get("domains", {})
        services = previous_structure.get("services", {})
        mds_services = previous_structure.get("mds_services", {})
        storagerouters = previous_structure.get("storagerouters", {})
        storagedrivers = previous_structure.get("storagedrivers", {})
        storagerouter_domains = previous_structure.get("storagerouter_domains", {})

        service_type = ServiceTypeList.get_by_name("MetadataServer")
        if service_type is None:
            service_type = ServiceType()
            service_type.name = "MetadataServer"
            service_type.save()
        srclients = {}
        for domain_id in structure.get("domains", []):
            if domain_id not in domains:
                domain = Domain()
                domain.name = "domain_{0}".format(domain_id)
                domain.save()
                domains[domain_id] = domain
        for vpool_id in structure.get("vpools", []):
            if vpool_id not in vpools:
                vpool = VPool()
                vpool.name = str(vpool_id)
                vpool.status = "RUNNING"
                vpool.save()
                vpools[vpool_id] = vpool
            else:
                vpool = vpools[vpool_id]
            srclients[vpool_id] = StorageRouterClient(vpool.guid, None)
        for sr_id in structure.get("storagerouters", []):
            if sr_id not in storagerouters:
                storagerouter = StorageRouter()
                storagerouter.name = str(sr_id)
                storagerouter.ip = "10.0.0.{0}".format(sr_id)
                storagerouter.rdma_capable = False
                storagerouter.node_type = "MASTER"
                storagerouter.machine_id = str(sr_id)
                storagerouter.save()
                storagerouters[sr_id] = storagerouter
                disk = Disk()
                disk.storagerouter = storagerouter
                disk.state = "OK"
                disk.name = "/dev/uda"
                disk.size = 1 * 1024 ** 4
                disk.is_ssd = True
                disk.aliases = ["/dev/uda"]
                disk.save()
                partition = DiskPartition()
                partition.offset = 0
                partition.size = disk.size
                partition.aliases = ["/dev/uda-1"]
                partition.state = "OK"
                partition.mountpoint = "/tmp/unittest/sr_{0}/disk_1/partition_1".format(sr_id)
                partition.disk = disk
                partition.roles = [DiskPartition.ROLES.DB, DiskPartition.ROLES.SCRUB]
                partition.save()
        for sd_id, vpool_id, sr_id in structure.get("storagedrivers", ()):
            if sd_id not in storagedrivers:
                storagedriver = StorageDriver()
                storagedriver.vpool = vpools[vpool_id]
                storagedriver.storagerouter = storagerouters[sr_id]
                storagedriver.name = str(sd_id)
                storagedriver.mountpoint = "/"
                storagedriver.cluster_ip = storagerouters[sr_id].ip
                storagedriver.storage_ip = "10.0.1.{0}".format(sr_id)
                storagedriver.storagedriver_id = str(sd_id)
                storagedriver.ports = {"management": 1, "xmlrpc": 2, "dtl": 3, "edge": 4}
                storagedriver.save()
                storagedrivers[sd_id] = storagedriver
                Helper._set_vpool_storage_driver_configuration(vpool=vpools[vpool_id], storagedriver=storagedriver)
        for mds_id, sd_id in structure.get("mds_services", ()):
            if mds_id not in mds_services:
                sd = storagedrivers[sd_id]
                s_id = "{0}-{1}".format(sd.storagerouter.name, mds_id)
                service = Service()
                service.name = s_id
                service.storagerouter = sd.storagerouter
                service.ports = [mds_id]
                service.type = service_type
                service.save()
                services[s_id] = service
                mds_service = MDSService()
                mds_service.service = service
                mds_service.number = 0
                mds_service.capacity = 10
                mds_service.vpool = sd.vpool
                mds_service.save()
                mds_services[mds_id] = mds_service
                StorageDriverController.add_storagedriverpartition(
                    sd,
                    {
                        "size": None,
                        "role": DiskPartition.ROLES.DB,
                        "sub_role": StorageDriverPartition.SUBROLE.MDS,
                        "partition": sd.storagerouter.disks[0].partitions[0],
                        "mds_service": mds_service,
                    },
                )
        for vdisk_id, storage_driver_id, vpool_id, mds_id in structure.get("vdisks", ()):
            if vdisk_id not in vdisks:
                vpool = vpools[vpool_id]
                devicename = "vdisk_{0}".format(vdisk_id)
                mds_backend_config = Helper._generate_mdsmetadatabackendconfig(
                    [] if mds_id is None else [mds_services[mds_id]]
                )
                volume_id = srclients[vpool_id].create_volume(devicename, mds_backend_config, 0, str(storage_driver_id))
                vdisk = VDisk()
                vdisk.name = str(vdisk_id)
                vdisk.devicename = devicename
                vdisk.volume_id = volume_id
                vdisk.vpool = vpool
                vdisk.size = 0
                vdisk.save()
                vdisk.reload_client("storagedriver")
                vdisks[vdisk_id] = vdisk
        for srd_id, sr_id, domain_id, backup in structure.get("storagerouter_domains", ()):
            if srd_id not in storagerouter_domains:
                sr_domain = StorageRouterDomain()
                sr_domain.backup = backup
                sr_domain.domain = domains[domain_id]
                sr_domain.storagerouter = storagerouters[sr_id]
                sr_domain.save()
                storagerouter_domains[srd_id] = sr_domain
        return {
            "vdisks": vdisks,
            "vpools": vpools,
            "domains": domains,
            "services": services,
            "service_type": service_type,
            "mds_services": mds_services,
            "storagerouters": storagerouters,
            "storagedrivers": storagedrivers,
            "storagerouter_domains": storagerouter_domains,
        }
Exemple #10
0
 def sync_with_reality(storagerouter_guid=None):
     """
     Syncs the Disks from all StorageRouters with the reality.
     """
     storagerouters = []
     if storagerouter_guid is not None:
         storagerouters.append(StorageRouter(storagerouter_guid))
     else:
         storagerouters = StorageRouterList.get_storagerouters()
     for storagerouter in storagerouters:
         try:
             client = SSHClient(storagerouter, username='******')
         except UnableToConnectException:
             logger.info('Could not connect to StorageRouter {0}, skipping'.format(storagerouter.ip))
             continue
         configuration = {}
         # Gather mount data
         mount_mapping = {}
         mount_data = client.run('mount')
         for mount in mount_data.splitlines():
             mount = mount.strip()
             match = re.search('/dev/(.+?) on (/.*?) type.*', mount)
             if match is not None:
                 mount_mapping[match.groups()[0]] = match.groups()[1]
         # Gather disk information
         with Remote(storagerouter.ip, [Context, os]) as remote:
             context = remote.Context()
             devices = [device for device in context.list_devices(subsystem='block')
                        if 'ID_TYPE' in device and device['ID_TYPE'] == 'disk']
             for device in devices:
                 is_partition = device['DEVTYPE'] == 'partition'
                 device_path = device['DEVNAME']
                 device_name = device_path.split('/')[-1]
                 partition_id = None
                 partition_name = None
                 extended_parition_info = None
                 if is_partition is True:
                     if 'ID_PART_ENTRY_NUMBER' in device:
                         extended_parition_info = True
                         partition_id = device['ID_PART_ENTRY_NUMBER']
                         partition_name = device_name
                         device_name = device_name[:0 - int(len(partition_id))]
                     else:
                         logger.debug('Partition {0} has no partition metadata'.format(device_path))
                         extended_parition_info = False
                         match = re.match('^(\D+?)(\d+)$', device_name)
                         if match is None:
                             logger.debug('Could not handle disk/partition {0}'.format(device_path))
                             continue  # Unable to handle this disk/partition
                         partition_name = device_name
                         partition_id = match.groups()[1]
                         device_name = match.groups()[0]
                 if device_name not in configuration:
                     configuration[device_name] = {'partitions': {}}
                 path = None
                 for path_type in ['by-id', 'by-uuid']:
                     if path is not None:
                         break
                     for item in device['DEVLINKS'].split(' '):
                         if path_type in item:
                             path = item
                 if path is None:
                     path = device_path
                 sectors = int(client.run('cat /sys/block/{0}/size'.format(device_name)))
                 sector_size = int(client.run('cat /sys/block/{0}/queue/hw_sector_size'.format(device_name)))
                 rotational = int(client.run('cat /sys/block/{0}/queue/rotational'.format(device_name)))
                 if is_partition is True:
                     if 'ID_PART_ENTRY_TYPE' in device and device['ID_PART_ENTRY_TYPE'] == '0x5':
                         continue  # This is an extended partition, let's skip that one
                     if extended_parition_info is True:
                         offset = int(device['ID_PART_ENTRY_OFFSET']) * sector_size
                         size = int(device['ID_PART_ENTRY_SIZE']) * sector_size
                     else:
                         match = re.match('^(\D+?)(\d+)$', device_path)
                         if match is None:
                             logger.debug('Could not handle disk/partition {0}'.format(device_path))
                             continue  # Unable to handle this disk/partition
                         fdisk_info = client.run('fdisk -l {0} | grep {1}'.format(match.groups()[0], device_path)).strip()
                         fdisk_data = filter(None, fdisk_info.split(' '))
                         offset = int(fdisk_data[1]) * sector_size
                         size = (int(fdisk_data[2]) - int(fdisk_data[1])) * sector_size
                     configuration[device_name]['partitions'][partition_id] = {'offset': offset,
                                                                               'size': size,
                                                                               'path': path,
                                                                               'state': 'OK'}
                     partition_data = configuration[device_name]['partitions'][partition_id]
                     if partition_name in mount_mapping:
                         mountpoint = mount_mapping[partition_name]
                         partition_data['mountpoint'] = mountpoint
                         partition_data['inode'] = remote.os.stat(mountpoint).st_dev
                         del mount_mapping[partition_name]
                         try:
                             client.run('touch {0}/{1}; rm {0}/{1}'.format(mountpoint, str(time.time())))
                         except CalledProcessError:
                             partition_data['state'] = 'FAILURE'
                             pass
                     if 'ID_FS_TYPE' in device:
                         partition_data['filesystem'] = device['ID_FS_TYPE']
                 else:
                     configuration[device_name].update({'name': device_name,
                                                        'path': path,
                                                        'vendor': device['ID_VENDOR'] if 'ID_VENDOR' in device else None,
                                                        'model': device['ID_MODEL'] if 'ID_MODEL' in device else None,
                                                        'size': sector_size * sectors,
                                                        'is_ssd': rotational == 0,
                                                        'state': 'OK'})
                 for partition_name in mount_mapping:
                     device_name = partition_name.split('/')[-1]
                     match = re.search('^(\D+?)(\d+)$', device_name)
                     if match is not None:
                         device_name = match.groups()[0]
                         partition_id = match.groups()[1]
                         if device_name not in configuration:
                             configuration[device_name] = {'partitions': {},
                                                           'state': 'MISSING'}
                         configuration[device_name]['partitions'][partition_id] = {'mountpoint': mount_mapping[partition_name],
                                                                                   'state': 'MISSING'}
         # Sync the model
         disk_names = []
         for disk in storagerouter.disks:
             if disk.name not in configuration:
                 for partition in disk.partitions:
                     partition.delete()
                 disk.delete()
             else:
                 disk_names.append(disk.name)
                 DiskController._update_disk(disk, configuration[disk.name])
                 partitions = []
                 partition_info = configuration[disk.name]['partitions']
                 for partition in disk.partitions:
                     if partition.id not in partition_info:
                         partition.delete()
                     else:
                         partitions.append(partition.id)
                         DiskController._update_partition(partition, partition_info[partition.id])
                 for partition_id in partition_info:
                     if partition_id not in partitions:
                         DiskController._create_partition(partition_id, partition_info[partition_id], disk)
         for disk_name in configuration:
             if disk_name not in disk_names and configuration[disk_name]['state'] not in ['MISSING']:
                 disk = Disk()
                 disk.storagerouter = storagerouter
                 disk.name = disk_name
                 DiskController._update_disk(disk, configuration[disk_name])
                 partition_info = configuration[disk_name]['partitions']
                 for partition_id in partition_info:
                     if partition_info[partition_id]['state'] not in ['MISSING']:
                         DiskController._create_partition(partition_id, partition_info[partition_id], disk)
Exemple #11
0
    def setUpClass(cls):
        """
        Sets up the unittest, mocking a certain set of 3rd party libraries and extensions.
        This makes sure the unittests can be executed without those libraries installed
        """
        # Load dummy stores
        PersistentFactory.store = DummyPersistentStore()
        VolatileFactory.store = DummyVolatileStore()
        # Replace mocked classes
        sys.modules[
            'ovs.extensions.storageserver.storagedriver'] = StorageDriverModule
        # Import required modules/classes after mocking is done
        from ovs.dal.hybrids.backendtype import BackendType
        from ovs.dal.hybrids.disk import Disk
        from ovs.dal.hybrids.diskpartition import DiskPartition
        from ovs.dal.hybrids.failuredomain import FailureDomain
        from ovs.dal.hybrids.pmachine import PMachine
        from ovs.dal.hybrids.storagerouter import StorageRouter
        from ovs.dal.hybrids.vdisk import VDisk
        from ovs.dal.hybrids.vmachine import VMachine
        from ovs.dal.hybrids.vpool import VPool
        from ovs.extensions.generic.volatilemutex import VolatileMutex
        from ovs.lib.vmachine import VMachineController
        from ovs.lib.vdisk import VDiskController
        from ovs.lib.scheduledtask import ScheduledTaskController
        # Globalize mocked classes
        global Disk
        global VDisk
        global VMachine
        global PMachine
        global VPool
        global BackendType
        global DiskPartition
        global FailureDomain
        global StorageRouter
        global VolatileMutex
        global VMachineController
        global VDiskController
        global ScheduledTaskController
        _ = VDisk(), VolatileMutex('dummy'), VMachine(), PMachine(), VPool(), BackendType(), FailureDomain(), \
            VMachineController, VDiskController, ScheduledTaskController, StorageRouter(), Disk(), DiskPartition()

        # Cleaning storage
        VolatileFactory.store.clean()
        PersistentFactory.store.clean()
    def test_clone_snapshot(self):
        """
        Validates that a snapshot that has clones will not be deleted while other snapshots will be deleted
        """
        # Setup
        # There are 2 disks, second one cloned from a snapshot of the first
        vpool = VPool()
        vpool.name = 'vpool'
        vpool.status = 'RUNNING'
        vpool.save()
        storage_router = StorageRouter()
        storage_router.name = 'storage_router'
        storage_router.ip = '127.0.0.1'
        storage_router.machine_id = System.get_my_machine_id()
        storage_router.rdma_capable = False
        storage_router.save()
        disk = Disk()
        disk.name = 'physical_disk_1'
        disk.aliases = ['/dev/non-existent']
        disk.size = 500 * 1024 ** 3
        disk.state = 'OK'
        disk.is_ssd = True
        disk.storagerouter = storage_router
        disk.save()
        disk_partition = DiskPartition()
        disk_partition.disk = disk
        disk_partition.aliases = ['/dev/disk/non-existent']
        disk_partition.size = 400 * 1024 ** 3
        disk_partition.state = 'OK'
        disk_partition.offset = 1024
        disk_partition.roles = [DiskPartition.ROLES.SCRUB]
        disk_partition.mountpoint = '/var/tmp'
        disk_partition.save()
        storage_driver = StorageDriver()
        storage_driver.vpool = vpool
        storage_driver.storagerouter = storage_router
        storage_driver.name = 'storage_driver_1'
        storage_driver.mountpoint = '/'
        storage_driver.cluster_ip = storage_router.ip
        storage_driver.storage_ip = '127.0.0.1'
        storage_driver.storagedriver_id = 'storage_driver_1'
        storage_driver.ports = {'management': 1,
                                'xmlrpc': 2,
                                'dtl': 3,
                                'edge': 4}
        storage_driver.save()
        service_type = ServiceType()
        service_type.name = 'MetadataServer'
        service_type.save()
        service = Service()
        service.name = 'service_1'
        service.storagerouter = storage_driver.storagerouter
        service.ports = [1]
        service.type = service_type
        service.save()
        mds_service = MDSService()
        mds_service.service = service
        mds_service.number = 0
        mds_service.capacity = 10
        mds_service.vpool = storage_driver.vpool
        mds_service.save()
        vdisk_1_1 = VDisk()
        vdisk_1_1.name = 'vdisk_1_1'
        vdisk_1_1.volume_id = 'vdisk_1_1'
        vdisk_1_1.vpool = vpool
        vdisk_1_1.devicename = 'dummy'
        vdisk_1_1.size = 0
        vdisk_1_1.save()
        vdisk_1_1.reload_client('storagedriver')

        [dynamic for dynamic in vdisk_1_1._dynamics if dynamic.name == 'snapshots'][0].timeout = 0

        travis = 'TRAVIS' in os.environ and os.environ['TRAVIS'] == 'true'
        if travis is True:
            print 'Running in Travis, reducing output.'

        base = datetime.datetime.now().date()
        day = datetime.timedelta(1)
        base_timestamp = self._make_timestamp(base, day)
        minute = 60
        hour = minute * 60
        for h in [6, 12, 18]:
            timestamp = base_timestamp + (hour * h)
            VDiskController.create_snapshot(vdisk_guid=vdisk_1_1.guid,
                                            metadata={'label': 'snapshot_{0}:30'.format(str(h)),
                                                      'is_consistent': True,
                                                      'timestamp': str(timestamp),
                                                      'machineguid': None})

        base_snapshot_guid = vdisk_1_1.snapshots[0]['guid']  # Oldest
        clone_vdisk = VDisk()
        clone_vdisk.name = 'clone_vdisk'
        clone_vdisk.volume_id = 'clone_vdisk'
        clone_vdisk.vpool = vpool
        clone_vdisk.devicename = 'dummy'
        clone_vdisk.parentsnapshot = base_snapshot_guid
        clone_vdisk.size = 0
        clone_vdisk.save()
        clone_vdisk.reload_client('storagedriver')

        for h in [6, 12, 18]:
            timestamp = base_timestamp + (hour * h)
            VDiskController.create_snapshot(vdisk_guid=clone_vdisk.guid,
                                            metadata={'label': 'snapshot_{0}:30'.format(str(h)),
                                                      'is_consistent': True,
                                                      'timestamp': str(timestamp),
                                                      'machineguid': None})

        base_timestamp = self._make_timestamp(base, day * 2)
        ScheduledTaskController.delete_snapshots(timestamp=base_timestamp + (minute * 30))
        self.assertIn(base_snapshot_guid, [snap['guid'] for snap in vdisk_1_1.snapshots], 'Snapshot was deleted while there are still clones of it')
    def test_happypath(self):
        """
        Validates the happy path; Hourly snapshots are taken with a few manual consistent
        every now and then. The delete policy is executed every day
        """
        vpool = VPool()
        vpool.name = 'vpool'
        vpool.status = 'RUNNING'
        vpool.save()
        storage_router = StorageRouter()
        storage_router.name = 'storage_router'
        storage_router.ip = '127.0.0.1'
        storage_router.machine_id = System.get_my_machine_id()
        storage_router.rdma_capable = False
        storage_router.save()
        disk = Disk()
        disk.name = 'physical_disk_1'
        disk.aliases = ['/dev/non-existent']
        disk.size = 500 * 1024 ** 3
        disk.state = 'OK'
        disk.is_ssd = True
        disk.storagerouter = storage_router
        disk.save()
        disk_partition = DiskPartition()
        disk_partition.disk = disk
        disk_partition.aliases = ['/dev/disk/non-existent']
        disk_partition.size = 400 * 1024 ** 3
        disk_partition.state = 'OK'
        disk_partition.offset = 1024
        disk_partition.roles = [DiskPartition.ROLES.SCRUB]
        disk_partition.mountpoint = '/var/tmp'
        disk_partition.save()
        vdisk_1 = VDisk()
        vdisk_1.name = 'vdisk_1'
        vdisk_1.volume_id = 'vdisk_1'
        vdisk_1.vpool = vpool
        vdisk_1.devicename = 'dummy'
        vdisk_1.size = 0
        vdisk_1.save()
        vdisk_1.reload_client('storagedriver')

        [dynamic for dynamic in vdisk_1._dynamics if dynamic.name == 'snapshots'][0].timeout = 0

        # Run the testing scenario
        travis = 'TRAVIS' in os.environ and os.environ['TRAVIS'] == 'true'
        if travis is True:
            self._print_message('Running in Travis, reducing output.')
        debug = not travis
        amount_of_days = 50
        base = datetime.datetime.now().date()
        day = datetime.timedelta(1)
        minute = 60
        hour = minute * 60

        for d in xrange(0, amount_of_days):
            base_timestamp = self._make_timestamp(base, day * d)
            self._print_message('')
            self._print_message('Day cycle: {0}: {1}'.format(d, datetime.datetime.fromtimestamp(base_timestamp).strftime('%Y-%m-%d')))

            # At the start of the day, delete snapshot policy runs at 00:30
            self._print_message('- Deleting snapshots')
            ScheduledTaskController.delete_snapshots(timestamp=base_timestamp + (minute * 30))

            # Validate snapshots
            self._print_message('- Validating snapshots')
            self._validate(vdisk_1, d, base, amount_of_days, debug)

            # During the day, snapshots are taken
            # - Create non consistent snapshot every hour, between 2:00 and 22:00
            # - Create consistent snapshot at 6:30, 12:30, 18:30
            self._print_message('- Creating snapshots')
            for h in xrange(2, 23):
                timestamp = base_timestamp + (hour * h)
                VDiskController.create_snapshot(vdisk_guid=vdisk_1.guid,
                                                metadata={'label': 'ss_i_{0}:00'.format(str(h)),
                                                          'is_consistent': False,
                                                          'timestamp': str(timestamp),
                                                          'machineguid': None})
                if h in [6, 12, 18]:
                    ts = (timestamp + (minute * 30))
                    VDiskController.create_snapshot(vdisk_guid=vdisk_1.guid,
                                                    metadata={'label': 'ss_c_{0}:30'.format(str(h)),
                                                              'is_consistent': True,
                                                              'timestamp': str(ts),
                                                              'machineguid': None})
Exemple #14
0
 def sync_with_reality(storagerouter_guid=None):
     """
     Syncs the Disks from all StorageRouters with the reality.
     """
     storagerouters = []
     if storagerouter_guid is not None:
         storagerouters.append(StorageRouter(storagerouter_guid))
     else:
         storagerouters = StorageRouterList.get_storagerouters()
     for storagerouter in storagerouters:
         try:
             client = SSHClient(storagerouter, username='******')
         except UnableToConnectException:
             logger.info(
                 'Could not connect to StorageRouter {0}, skipping'.format(
                     storagerouter.ip))
             continue
         configuration = {}
         # Gather mount data
         mount_mapping = {}
         mount_data = client.run('mount')
         for mount in mount_data.splitlines():
             mount = mount.strip()
             match = re.search('/dev/(.+?) on (/.*?) type.*', mount)
             if match is not None:
                 mount_mapping[match.groups()[0]] = match.groups()[1]
         # Gather disk information
         with Remote(storagerouter.ip, [Context, os]) as remote:
             context = remote.Context()
             devices = [
                 device
                 for device in context.list_devices(subsystem='block')
                 if 'ID_TYPE' in device and device['ID_TYPE'] == 'disk'
             ]
             for device in devices:
                 is_partition = device['DEVTYPE'] == 'partition'
                 device_path = device['DEVNAME']
                 device_name = device_path.split('/')[-1]
                 partition_id = None
                 partition_name = None
                 extended_parition_info = None
                 if is_partition is True:
                     if 'ID_PART_ENTRY_NUMBER' in device:
                         extended_parition_info = True
                         partition_id = device['ID_PART_ENTRY_NUMBER']
                         partition_name = device_name
                         device_name = device_name[:0 -
                                                   int(len(partition_id))]
                     else:
                         logger.debug(
                             'Partition {0} has no partition metadata'.
                             format(device_path))
                         extended_parition_info = False
                         match = re.match('^(\D+?)(\d+)$', device_name)
                         if match is None:
                             logger.debug(
                                 'Could not handle disk/partition {0}'.
                                 format(device_path))
                             continue  # Unable to handle this disk/partition
                         partition_name = device_name
                         partition_id = match.groups()[1]
                         device_name = match.groups()[0]
                 if device_name not in configuration:
                     configuration[device_name] = {'partitions': {}}
                 path = None
                 for path_type in ['by-id', 'by-uuid']:
                     if path is not None:
                         break
                     for item in device['DEVLINKS'].split(' '):
                         if path_type in item:
                             path = item
                 if path is None:
                     path = device_path
                 sectors = int(
                     client.run(
                         'cat /sys/block/{0}/size'.format(device_name)))
                 sector_size = int(
                     client.run(
                         'cat /sys/block/{0}/queue/hw_sector_size'.format(
                             device_name)))
                 rotational = int(
                     client.run(
                         'cat /sys/block/{0}/queue/rotational'.format(
                             device_name)))
                 if is_partition is True:
                     if 'ID_PART_ENTRY_TYPE' in device and device[
                             'ID_PART_ENTRY_TYPE'] == '0x5':
                         continue  # This is an extended partition, let's skip that one
                     if extended_parition_info is True:
                         offset = int(
                             device['ID_PART_ENTRY_OFFSET']) * sector_size
                         size = int(
                             device['ID_PART_ENTRY_SIZE']) * sector_size
                     else:
                         match = re.match('^(\D+?)(\d+)$', device_path)
                         if match is None:
                             logger.debug(
                                 'Could not handle disk/partition {0}'.
                                 format(device_path))
                             continue  # Unable to handle this disk/partition
                         fdisk_info = client.run(
                             'fdisk -l {0} | grep {1}'.format(
                                 match.groups()[0], device_path)).strip()
                         fdisk_data = filter(None, fdisk_info.split(' '))
                         offset = int(fdisk_data[1]) * sector_size
                         size = (int(fdisk_data[2]) -
                                 int(fdisk_data[1])) * sector_size
                     configuration[device_name]['partitions'][
                         partition_id] = {
                             'offset': offset,
                             'size': size,
                             'path': path,
                             'state': 'OK'
                         }
                     partition_data = configuration[device_name][
                         'partitions'][partition_id]
                     if partition_name in mount_mapping:
                         mountpoint = mount_mapping[partition_name]
                         partition_data['mountpoint'] = mountpoint
                         partition_data['inode'] = remote.os.stat(
                             mountpoint).st_dev
                         del mount_mapping[partition_name]
                         try:
                             client.run('touch {0}/{1}; rm {0}/{1}'.format(
                                 mountpoint, str(time.time())))
                         except CalledProcessError:
                             partition_data['state'] = 'ERROR'
                             pass
                     if 'ID_FS_TYPE' in device:
                         partition_data['filesystem'] = device['ID_FS_TYPE']
                 else:
                     configuration[device_name].update({
                         'name':
                         device_name,
                         'path':
                         path,
                         'vendor':
                         device['ID_VENDOR']
                         if 'ID_VENDOR' in device else None,
                         'model':
                         device['ID_MODEL']
                         if 'ID_MODEL' in device else None,
                         'size':
                         sector_size * sectors,
                         'is_ssd':
                         rotational == 0,
                         'state':
                         'OK'
                     })
                 for partition_name in mount_mapping:
                     device_name = partition_name.split('/')[-1]
                     match = re.search('^(\D+?)(\d+)$', device_name)
                     if match is not None:
                         device_name = match.groups()[0]
                         partition_id = match.groups()[1]
                         if device_name not in configuration:
                             configuration[device_name] = {
                                 'partitions': {},
                                 'state': 'MISSING'
                             }
                         configuration[device_name]['partitions'][
                             partition_id] = {
                                 'mountpoint':
                                 mount_mapping[partition_name],
                                 'state': 'MISSING'
                             }
         # Sync the model
         disk_names = []
         for disk in storagerouter.disks:
             if disk.name not in configuration:
                 for partition in disk.partitions:
                     partition.delete()
                 disk.delete()
             else:
                 disk_names.append(disk.name)
                 DiskController._update_disk(disk, configuration[disk.name])
                 partitions = []
                 partition_info = configuration[disk.name]['partitions']
                 for partition in disk.partitions:
                     if partition.id not in partition_info:
                         partition.delete()
                     else:
                         partitions.append(partition.id)
                         DiskController._update_partition(
                             partition, partition_info[partition.id])
                 for partition_id in partition_info:
                     if partition_id not in partitions:
                         DiskController._create_partition(
                             partition_id, partition_info[partition_id],
                             disk)
         for disk_name in configuration:
             if disk_name not in disk_names and configuration[disk_name][
                     'state'] not in ['MISSING']:
                 disk = Disk()
                 disk.storagerouter = storagerouter
                 disk.name = disk_name
                 DiskController._update_disk(disk, configuration[disk_name])
                 partition_info = configuration[disk_name]['partitions']
                 for partition_id in partition_info:
                     if partition_info[partition_id]['state'] not in [
                             'MISSING'
                     ]:
                         DiskController._create_partition(
                             partition_id, partition_info[partition_id],
                             disk)
Exemple #15
0
    def build_service_structure(structure, previous_structure=None):
        """
        Builds an MDS service structure
        Example:
            structure = Helper.build_service_structure(
                {'vpools': [1],
                 'domains': [],
                 'storagerouters': [1],
                 'storagedrivers': [(1, 1, 1)],  # (<id>, <vpool_id>, <storagerouter_id>)
                 'mds_services': [(1, 1)],  # (<id>, <storagedriver_id>)
                 'storagerouter_domains': []}  # (<id>, <storagerouter_id>, <domain_id>)
            )
        """
        if previous_structure is None:
            previous_structure = {}
        vdisks = previous_structure.get('vdisks', {})
        vpools = previous_structure.get('vpools', {})
        domains = previous_structure.get('domains', {})
        services = previous_structure.get('services', {})
        mds_services = previous_structure.get('mds_services', {})
        storagerouters = previous_structure.get('storagerouters', {})
        storagedrivers = previous_structure.get('storagedrivers', {})
        storagerouter_domains = previous_structure.get('storagerouter_domains',
                                                       {})

        service_type = ServiceTypeList.get_by_name('MetadataServer')
        if service_type is None:
            service_type = ServiceType()
            service_type.name = 'MetadataServer'
            service_type.save()
        srclients = {}
        for domain_id in structure.get('domains', []):
            if domain_id not in domains:
                domain = Domain()
                domain.name = 'domain_{0}'.format(domain_id)
                domain.save()
                domains[domain_id] = domain
        for vpool_id in structure.get('vpools', []):
            if vpool_id not in vpools:
                vpool = VPool()
                vpool.name = str(vpool_id)
                vpool.status = 'RUNNING'
                vpool.save()
                vpools[vpool_id] = vpool
            else:
                vpool = vpools[vpool_id]
            srclients[vpool_id] = StorageRouterClient(vpool.guid, None)
        for sr_id in structure.get('storagerouters', []):
            if sr_id not in storagerouters:
                storagerouter = StorageRouter()
                storagerouter.name = str(sr_id)
                storagerouter.ip = '10.0.0.{0}'.format(sr_id)
                storagerouter.rdma_capable = False
                storagerouter.node_type = 'MASTER'
                storagerouter.machine_id = str(sr_id)
                storagerouter.save()
                storagerouters[sr_id] = storagerouter
                disk = Disk()
                disk.storagerouter = storagerouter
                disk.state = 'OK'
                disk.name = '/dev/uda'
                disk.size = 1 * 1024**4
                disk.is_ssd = True
                disk.aliases = ['/dev/uda']
                disk.save()
                partition = DiskPartition()
                partition.offset = 0
                partition.size = disk.size
                partition.aliases = ['/dev/uda-1']
                partition.state = 'OK'
                partition.mountpoint = '/tmp/unittest/sr_{0}/disk_1/partition_1'.format(
                    sr_id)
                partition.disk = disk
                partition.roles = [
                    DiskPartition.ROLES.DB, DiskPartition.ROLES.SCRUB
                ]
                partition.save()
        for sd_id, vpool_id, sr_id in structure.get('storagedrivers', ()):
            if sd_id not in storagedrivers:
                storagedriver = StorageDriver()
                storagedriver.vpool = vpools[vpool_id]
                storagedriver.storagerouter = storagerouters[sr_id]
                storagedriver.name = str(sd_id)
                storagedriver.mountpoint = '/'
                storagedriver.cluster_ip = storagerouters[sr_id].ip
                storagedriver.storage_ip = '10.0.1.{0}'.format(sr_id)
                storagedriver.storagedriver_id = str(sd_id)
                storagedriver.ports = {
                    'management': 1,
                    'xmlrpc': 2,
                    'dtl': 3,
                    'edge': 4
                }
                storagedriver.save()
                storagedrivers[sd_id] = storagedriver
                Helper._set_vpool_storage_driver_configuration(
                    vpool=vpools[vpool_id], storagedriver=storagedriver)
        for mds_id, sd_id in structure.get('mds_services', ()):
            if mds_id not in mds_services:
                sd = storagedrivers[sd_id]
                s_id = '{0}-{1}'.format(sd.storagerouter.name, mds_id)
                service = Service()
                service.name = s_id
                service.storagerouter = sd.storagerouter
                service.ports = [mds_id]
                service.type = service_type
                service.save()
                services[s_id] = service
                mds_service = MDSService()
                mds_service.service = service
                mds_service.number = 0
                mds_service.capacity = 10
                mds_service.vpool = sd.vpool
                mds_service.save()
                mds_services[mds_id] = mds_service
                StorageDriverController.add_storagedriverpartition(
                    sd, {
                        'size': None,
                        'role': DiskPartition.ROLES.DB,
                        'sub_role': StorageDriverPartition.SUBROLE.MDS,
                        'partition': sd.storagerouter.disks[0].partitions[0],
                        'mds_service': mds_service
                    })
        for vdisk_id, storage_driver_id, vpool_id, mds_id in structure.get(
                'vdisks', ()):
            if vdisk_id not in vdisks:
                vpool = vpools[vpool_id]
                devicename = 'vdisk_{0}'.format(vdisk_id)
                mds_backend_config = Helper._generate_mdsmetadatabackendconfig(
                    [] if mds_id is None else [mds_services[mds_id]])
                volume_id = srclients[vpool_id].create_volume(
                    devicename, mds_backend_config, 0, str(storage_driver_id))
                vdisk = VDisk()
                vdisk.name = str(vdisk_id)
                vdisk.devicename = devicename
                vdisk.volume_id = volume_id
                vdisk.vpool = vpool
                vdisk.size = 0
                vdisk.save()
                vdisk.reload_client('storagedriver')
                vdisks[vdisk_id] = vdisk
        for srd_id, sr_id, domain_id, backup in structure.get(
                'storagerouter_domains', ()):
            if srd_id not in storagerouter_domains:
                sr_domain = StorageRouterDomain()
                sr_domain.backup = backup
                sr_domain.domain = domains[domain_id]
                sr_domain.storagerouter = storagerouters[sr_id]
                sr_domain.save()
                storagerouter_domains[srd_id] = sr_domain
        return {
            'vdisks': vdisks,
            'vpools': vpools,
            'domains': domains,
            'services': services,
            'service_type': service_type,
            'mds_services': mds_services,
            'storagerouters': storagerouters,
            'storagedrivers': storagedrivers,
            'storagerouter_domains': storagerouter_domains
        }
Exemple #16
0
    def sync_with_reality(storagerouter_guid=None):
        """
        Syncs the Disks from all StorageRouters with the reality.
        :param storagerouter_guid: Guid of the Storage Router to synchronize
        """
        storagerouters = []
        if storagerouter_guid is not None:
            storagerouters.append(StorageRouter(storagerouter_guid))
        else:
            storagerouters = StorageRouterList.get_storagerouters()
        for storagerouter in storagerouters:
            try:
                client = SSHClient(storagerouter, username='******')
            except UnableToConnectException:
                DiskController._logger.info(
                    'Could not connect to StorageRouter {0}, skipping'.format(
                        storagerouter.ip))
                continue
            configuration = {}
            # Gather mount data
            mount_mapping = {}
            mount_data = client.run('mount')
            for mount in mount_data.splitlines():
                mount = mount.strip()
                match = re.search('(/dev/(.+?)) on (/.*?) type.*', mount)
                if match is not None:
                    dev_name = match.groups()[0]
                    uuid = client.run(
                        'blkid -o value -s UUID {0}'.format(dev_name))
                    if uuid:
                        mount_mapping[uuid] = match.groups()[2]
                    else:
                        mount_mapping[match.groups()[1]] = match.groups()[2]
            # Gather raid information
            try:
                md_information = client.run('mdadm --detail /dev/md*',
                                            suppress_logging=True)
            except CalledProcessError:
                md_information = ''
            raid_members = []
            for member in re.findall('(?: +[0-9]+){4} +[^/]+/dev/([a-z0-9]+)',
                                     md_information):
                raid_members.append(member)
            # Gather disk information
            with remote(storagerouter.ip, [Context, os]) as rem:
                context = rem.Context()
                devices = [
                    device
                    for device in context.list_devices(subsystem='block')
                    if ('ID_TYPE' in device and device['ID_TYPE'] == 'disk') or
                    ('DEVNAME' in device and
                     ('loop' in device['DEVNAME'] or 'nvme' in
                      device['DEVNAME'] or 'md' in device['DEVNAME']))
                ]
                for device in devices:
                    is_partition = device['DEVTYPE'] == 'partition'
                    device_path = device['DEVNAME']
                    device_name = device_path.split('/')[-1]
                    partition_id = None
                    partition_name = None
                    extended_partition_info = None
                    if is_partition is True:
                        partition_name = device[
                            'ID_FS_UUID'] if 'ID_FS_UUID' in device else device_name
                        if 'ID_PART_ENTRY_NUMBER' in device:
                            extended_partition_info = True
                            partition_id = device['ID_PART_ENTRY_NUMBER']
                            if device_name.startswith(
                                    'nvme') or device_name.startswith('loop'):
                                device_name = device_name[:0 -
                                                          int(len(partition_id)
                                                              ) - 1]
                            elif device_name.startswith('md'):
                                device_name = device_name[:device_name.
                                                          index('p')]
                            else:
                                device_name = device_name[:0 -
                                                          int(len(partition_id)
                                                              )]
                        else:
                            DiskController._logger.debug(
                                'Partition {0} has no partition metadata'.
                                format(device_path))
                            extended_partition_info = False
                            match = re.match('^(\D+?)(\d+)$', device_name)
                            if match is None:
                                DiskController._logger.debug(
                                    'Could not handle disk/partition {0}'.
                                    format(device_path))
                                continue  # Unable to handle this disk/partition
                            partition_id = match.groups()[1]
                            device_name = match.groups()[0]
                    sectors = int(
                        client.run(
                            'cat /sys/block/{0}/size'.format(device_name)))
                    sector_size = int(
                        client.run(
                            'cat /sys/block/{0}/queue/hw_sector_size'.format(
                                device_name)))
                    rotational = int(
                        client.run(
                            'cat /sys/block/{0}/queue/rotational'.format(
                                device_name)))

                    if sectors == 0:
                        continue
                    if device_name in raid_members:
                        continue
                    if device_name not in configuration:
                        configuration[device_name] = {'partitions': {}}
                    path = None
                    for path_type in ['by-id', 'by-uuid']:
                        if path is not None:
                            break
                        if 'DEVLINKS' in device:
                            for item in device['DEVLINKS'].split(' '):
                                if path_type in item:
                                    path = item
                    if path is None:
                        path = device_path
                    if is_partition is True:
                        if 'ID_PART_ENTRY_TYPE' in device and device[
                                'ID_PART_ENTRY_TYPE'] == '0x5':
                            continue  # This is an extended partition, let's skip that one
                        if extended_partition_info is True:
                            offset = int(
                                device['ID_PART_ENTRY_OFFSET']) * sector_size
                            size = int(
                                device['ID_PART_ENTRY_SIZE']) * sector_size
                        else:
                            match = re.match('^(\D+?)(\d+)$', device_path)
                            if match is None:
                                DiskController._logger.debug(
                                    'Could not handle disk/partition {0}'.
                                    format(device_path))
                                continue  # Unable to handle this disk/partition
                            partitions_info = DiskTools.get_partitions_info(
                                match.groups()[0])
                            if device_path in partitions_info:
                                partition_info = partitions_info[device_path]
                                offset = int(partition_info['start'])
                                size = int(partition_info['size'])
                            else:
                                DiskController._logger.warning(
                                    'Could not retrieve partition info for disk/partition {0}'
                                    .format(device_path))
                                continue
                        configuration[device_name]['partitions'][
                            partition_id] = {
                                'offset': offset,
                                'size': size,
                                'path': path,
                                'state': 'OK'
                            }
                        partition_data = configuration[device_name][
                            'partitions'][partition_id]
                        if partition_name in mount_mapping:
                            mountpoint = mount_mapping[partition_name]
                            partition_data['mountpoint'] = mountpoint
                            partition_data['inode'] = rem.os.stat(
                                mountpoint).st_dev
                            del mount_mapping[partition_name]
                            try:
                                client.run('touch {0}/{1}; rm {0}/{1}'.format(
                                    mountpoint, str(time.time())))
                            except CalledProcessError:
                                partition_data['state'] = 'FAILURE'
                                pass
                        if 'ID_FS_TYPE' in device:
                            partition_data['filesystem'] = device['ID_FS_TYPE']
                    else:
                        configuration[device_name].update({
                            'name':
                            device_name,
                            'path':
                            path,
                            'vendor':
                            device['ID_VENDOR']
                            if 'ID_VENDOR' in device else None,
                            'model':
                            device['ID_MODEL']
                            if 'ID_MODEL' in device else None,
                            'size':
                            sector_size * sectors,
                            'is_ssd':
                            rotational == 0,
                            'state':
                            'OK'
                        })
                    for partition_name in mount_mapping:
                        device_name = partition_name.split('/')[-1]
                        match = re.search('^(\D+?)(\d+)$', device_name)
                        if match is not None:
                            device_name = match.groups()[0]
                            partition_id = match.groups()[1]
                            if device_name not in configuration:
                                configuration[device_name] = {
                                    'partitions': {},
                                    'state': 'MISSING'
                                }
                            configuration[device_name]['partitions'][
                                partition_id] = {
                                    'mountpoint':
                                    mount_mapping[partition_name],
                                    'state': 'MISSING'
                                }
            # Sync the model
            disk_names = []
            for disk in storagerouter.disks:
                if disk.name not in configuration:
                    for partition in disk.partitions:
                        partition.delete()
                    disk.delete()
                else:
                    disk_names.append(disk.name)
                    DiskController._update_disk(disk, configuration[disk.name])
                    partitions = []
                    partition_info = configuration[disk.name]['partitions']
                    for partition in disk.partitions:
                        if partition.id not in partition_info:
                            partition.delete()
                        else:
                            partitions.append(partition.id)
                            DiskController._update_partition(
                                partition, partition_info[partition.id])
                    for partition_id in partition_info:
                        if partition_id not in partitions:
                            DiskController._create_partition(
                                partition_id, partition_info[partition_id],
                                disk)
            for disk_name in configuration:
                if disk_name not in disk_names and configuration[disk_name][
                        'state'] not in ['MISSING']:
                    disk = Disk()
                    disk.storagerouter = storagerouter
                    disk.name = disk_name
                    DiskController._update_disk(disk, configuration[disk_name])
                    partition_info = configuration[disk_name]['partitions']
                    for partition_id in partition_info:
                        if partition_info[partition_id]['state'] not in [
                                'MISSING'
                        ]:
                            DiskController._create_partition(
                                partition_id, partition_info[partition_id],
                                disk)
Exemple #17
0
    def test_happypath(self):
        """
        Validates the happy path; Hourly snapshots are taken with a few manual consistent
        every now and then. The delete policy is executed every day
        """
        vpool = VPool()
        vpool.name = 'vpool'
        vpool.status = 'RUNNING'
        vpool.save()
        storage_router = StorageRouter()
        storage_router.name = 'storage_router'
        storage_router.ip = '127.0.0.1'
        storage_router.machine_id = System.get_my_machine_id()
        storage_router.rdma_capable = False
        storage_router.save()
        disk = Disk()
        disk.name = 'physical_disk_1'
        disk.aliases = ['/dev/non-existent']
        disk.size = 500 * 1024**3
        disk.state = 'OK'
        disk.is_ssd = True
        disk.storagerouter = storage_router
        disk.save()
        disk_partition = DiskPartition()
        disk_partition.disk = disk
        disk_partition.aliases = ['/dev/disk/non-existent']
        disk_partition.size = 400 * 1024**3
        disk_partition.state = 'OK'
        disk_partition.offset = 1024
        disk_partition.roles = [DiskPartition.ROLES.SCRUB]
        disk_partition.mountpoint = '/var/tmp'
        disk_partition.save()
        vdisk_1 = VDisk()
        vdisk_1.name = 'vdisk_1'
        vdisk_1.volume_id = 'vdisk_1'
        vdisk_1.vpool = vpool
        vdisk_1.devicename = 'dummy'
        vdisk_1.size = 0
        vdisk_1.save()
        vdisk_1.reload_client('storagedriver')

        [
            dynamic for dynamic in vdisk_1._dynamics
            if dynamic.name == 'snapshots'
        ][0].timeout = 0

        # Run the testing scenario
        travis = 'TRAVIS' in os.environ and os.environ['TRAVIS'] == 'true'
        if travis is True:
            self._print_message('Running in Travis, reducing output.')
        debug = not travis
        amount_of_days = 50
        base = datetime.datetime.now().date()
        day = datetime.timedelta(1)
        minute = 60
        hour = minute * 60

        for d in xrange(0, amount_of_days):
            base_timestamp = self._make_timestamp(base, day * d)
            self._print_message('')
            self._print_message('Day cycle: {0}: {1}'.format(
                d,
                datetime.datetime.fromtimestamp(base_timestamp).strftime(
                    '%Y-%m-%d')))

            # At the start of the day, delete snapshot policy runs at 00:30
            self._print_message('- Deleting snapshots')
            ScheduledTaskController.delete_snapshots(timestamp=base_timestamp +
                                                     (minute * 30))

            # Validate snapshots
            self._print_message('- Validating snapshots')
            self._validate(vdisk_1, d, base, amount_of_days, debug)

            # During the day, snapshots are taken
            # - Create non consistent snapshot every hour, between 2:00 and 22:00
            # - Create consistent snapshot at 6:30, 12:30, 18:30
            self._print_message('- Creating snapshots')
            for h in xrange(2, 23):
                timestamp = base_timestamp + (hour * h)
                VDiskController.create_snapshot(vdisk_guid=vdisk_1.guid,
                                                metadata={
                                                    'label':
                                                    'ss_i_{0}:00'.format(
                                                        str(h)),
                                                    'is_consistent':
                                                    False,
                                                    'timestamp':
                                                    str(timestamp),
                                                    'machineguid':
                                                    None
                                                })
                if h in [6, 12, 18]:
                    ts = (timestamp + (minute * 30))
                    VDiskController.create_snapshot(vdisk_guid=vdisk_1.guid,
                                                    metadata={
                                                        'label':
                                                        'ss_c_{0}:30'.format(
                                                            str(h)),
                                                        'is_consistent':
                                                        True,
                                                        'timestamp':
                                                        str(ts),
                                                        'machineguid':
                                                        None
                                                    })
Exemple #18
0
    def test_happypath(self):
        """
        Validates the happy path; Hourly snapshots are taken with a few manual consistent
        every now an then. The delete policy is executed every day
        """
        # Setup
        # There are 2 machines; one with two disks, one with one disk and a stand-alone additional disk
        failure_domain = FailureDomain()
        failure_domain.name = 'Test'
        failure_domain.save()
        backend_type = BackendType()
        backend_type.name = 'BackendType'
        backend_type.code = 'BT'
        backend_type.save()
        vpool = VPool()
        vpool.name = 'vpool'
        vpool.status = 'RUNNING'
        vpool.backend_type = backend_type
        vpool.save()
        pmachine = PMachine()
        pmachine.name = 'PMachine'
        pmachine.username = '******'
        pmachine.ip = '127.0.0.1'
        pmachine.hvtype = 'VMWARE'
        pmachine.save()
        storage_router = StorageRouter()
        storage_router.name = 'storage_router'
        storage_router.ip = '127.0.0.1'
        storage_router.pmachine = pmachine
        storage_router.machine_id = System.get_my_machine_id()
        storage_router.rdma_capable = False
        storage_router.primary_failure_domain = failure_domain
        storage_router.save()
        disk = Disk()
        disk.name = 'physical_disk_1'
        disk.path = '/dev/non-existent'
        disk.size = 500 * 1024**3
        disk.state = 'OK'
        disk.is_ssd = True
        disk.storagerouter = storage_router
        disk.save()
        disk_partition = DiskPartition()
        disk_partition.id = 'disk_partition_id'
        disk_partition.disk = disk
        disk_partition.path = '/dev/disk/non-existent'
        disk_partition.size = 400 * 1024**3
        disk_partition.state = 'OK'
        disk_partition.offset = 1024
        disk_partition.roles = [DiskPartition.ROLES.SCRUB]
        disk_partition.mountpoint = '/var/tmp'
        disk_partition.save()
        vmachine_1 = VMachine()
        vmachine_1.name = 'vmachine_1'
        vmachine_1.devicename = 'dummy'
        vmachine_1.pmachine = pmachine
        vmachine_1.save()
        vdisk_1_1 = VDisk()
        vdisk_1_1.name = 'vdisk_1_1'
        vdisk_1_1.volume_id = 'vdisk_1_1'
        vdisk_1_1.vmachine = vmachine_1
        vdisk_1_1.vpool = vpool
        vdisk_1_1.devicename = 'dummy'
        vdisk_1_1.size = 0
        vdisk_1_1.save()
        vdisk_1_1.reload_client()
        vdisk_1_2 = VDisk()
        vdisk_1_2.name = 'vdisk_1_2'
        vdisk_1_2.volume_id = 'vdisk_1_2'
        vdisk_1_2.vmachine = vmachine_1
        vdisk_1_2.vpool = vpool
        vdisk_1_2.devicename = 'dummy'
        vdisk_1_2.size = 0
        vdisk_1_2.save()
        vdisk_1_2.reload_client()
        vmachine_2 = VMachine()
        vmachine_2.name = 'vmachine_2'
        vmachine_2.devicename = 'dummy'
        vmachine_2.pmachine = pmachine
        vmachine_2.save()
        vdisk_2_1 = VDisk()
        vdisk_2_1.name = 'vdisk_2_1'
        vdisk_2_1.volume_id = 'vdisk_2_1'
        vdisk_2_1.vmachine = vmachine_2
        vdisk_2_1.vpool = vpool
        vdisk_2_1.devicename = 'dummy'
        vdisk_2_1.size = 0
        vdisk_2_1.save()
        vdisk_2_1.reload_client()
        vdisk_3 = VDisk()
        vdisk_3.name = 'vdisk_3'
        vdisk_3.volume_id = 'vdisk_3'
        vdisk_3.vpool = vpool
        vdisk_3.devicename = 'dummy'
        vdisk_3.size = 0
        vdisk_3.save()
        vdisk_3.reload_client()

        for disk in [vdisk_1_1, vdisk_1_2, vdisk_2_1, vdisk_3]:
            [
                dynamic for dynamic in disk._dynamics
                if dynamic.name == 'snapshots'
            ][0].timeout = 0

        # Run the testing scenario
        travis = 'TRAVIS' in os.environ and os.environ['TRAVIS'] == 'true'
        if travis is True:
            print 'Running in Travis, reducing output.'
        debug = not travis
        amount_of_days = 50
        base = datetime.datetime.now().date()
        day = datetime.timedelta(1)
        minute = 60
        hour = minute * 60

        for d in xrange(0, amount_of_days):
            base_timestamp = self._make_timestamp(base, day * d)
            print ''
            print 'Day cycle: {0}: {1}'.format(
                d,
                datetime.datetime.fromtimestamp(base_timestamp).strftime(
                    '%Y-%m-%d'))

            # At the start of the day, delete snapshot policy runs at 00:30
            print '- Deleting snapshots'
            ScheduledTaskController.delete_snapshots(timestamp=base_timestamp +
                                                     (minute * 30))

            # Validate snapshots
            print '- Validating snapshots'
            for vdisk in [vdisk_1_1, vdisk_1_2, vdisk_2_1, vdisk_3]:
                self._validate(vdisk, d, base, amount_of_days, debug)

            # During the day, snapshots are taken
            # - Create non consistent snapshot every hour, between 2:00 and 22:00
            # - Create consistent snapshot at 6:30, 12:30, 18:30
            print '- Creating snapshots'
            for h in xrange(2, 23):
                timestamp = base_timestamp + (hour * h)
                for vm in [vmachine_1, vmachine_2]:
                    VMachineController.snapshot(machineguid=vm.guid,
                                                label='ss_i_{0}:00'.format(
                                                    str(h)),
                                                is_consistent=False,
                                                timestamp=timestamp)
                    if h in [6, 12, 18]:
                        ts = (timestamp + (minute * 30))
                        VMachineController.snapshot(machineguid=vm.guid,
                                                    label='ss_c_{0}:30'.format(
                                                        str(h)),
                                                    is_consistent=True,
                                                    timestamp=ts)

                VDiskController.create_snapshot(diskguid=vdisk_3.guid,
                                                metadata={
                                                    'label':
                                                    'ss_i_{0}:00'.format(
                                                        str(h)),
                                                    'is_consistent':
                                                    False,
                                                    'timestamp':
                                                    str(timestamp),
                                                    'machineguid':
                                                    None
                                                })
                if h in [6, 12, 18]:
                    ts = (timestamp + (minute * 30))
                    VDiskController.create_snapshot(diskguid=vdisk_3.guid,
                                                    metadata={
                                                        'label':
                                                        'ss_c_{0}:30'.format(
                                                            str(h)),
                                                        'is_consistent':
                                                        True,
                                                        'timestamp':
                                                        str(ts),
                                                        'machineguid':
                                                        None
                                                    })
Exemple #19
0
    def build_dal_structure(structure, previous_structure=None):
        """
        Builds a model structure
        Example:
            structure = DalHelper.build_service_structure(
                {'vpools': [1],
                 'domains': [],
                 'storagerouters': [1],
                 'storagedrivers': [(1, 1, 1)],  # (<id>, <vpool_id>, <storagerouter_id>)
                 'mds_services': [(1, 1)],  # (<id>, <storagedriver_id>)
                 'storagerouter_domains': []}  # (<id>, <storagerouter_id>, <domain_id>)
            )
        """
        Configuration.set(key=Configuration.EDITION_KEY,
                          value=PackageFactory.EDITION_ENTERPRISE)

        if previous_structure is None:
            previous_structure = {}
        vdisks = previous_structure.get('vdisks', {})
        vpools = previous_structure.get('vpools', {})
        domains = previous_structure.get('domains', {})
        services = previous_structure.get('services', {})
        mds_services = previous_structure.get('mds_services', {})
        storagerouters = previous_structure.get('storagerouters', {})
        storagedrivers = previous_structure.get('storagedrivers', {})
        storagerouter_domains = previous_structure.get('storagerouter_domains',
                                                       {})

        service_types = {}
        for service_type_name in ServiceType.SERVICE_TYPES.values():
            service_type = ServiceTypeList.get_by_name(service_type_name)
            if service_type is None:
                service_type = ServiceType()
                service_type.name = service_type_name
                service_type.save()
            service_types[service_type_name] = service_type
        srclients = {}
        for domain_id in structure.get('domains', []):
            if domain_id not in domains:
                domain = Domain()
                domain.name = 'domain_{0}'.format(domain_id)
                domain.save()
                domains[domain_id] = domain
        for vpool_id in structure.get('vpools', []):
            if vpool_id not in vpools:
                vpool = VPool()
                vpool.name = str(vpool_id)
                vpool.status = 'RUNNING'
                vpool.metadata = {'backend': {}, 'caching_info': {}}
                vpool.metadata_store_bits = 5
                vpool.save()
                vpools[vpool_id] = vpool
            else:
                vpool = vpools[vpool_id]
            srclients[vpool_id] = StorageRouterClient(vpool.guid, None)
            Configuration.set(
                '/ovs/vpools/{0}/mds_config|mds_tlogs'.format(vpool.guid), 100)
            Configuration.set(
                '/ovs/vpools/{0}/mds_config|mds_safety'.format(vpool.guid), 2)
            Configuration.set(
                '/ovs/vpools/{0}/mds_config|mds_maxload'.format(vpool.guid),
                75)
            Configuration.set(
                '/ovs/vpools/{0}/proxies/scrub/generic_scrub'.format(
                    vpool.guid),
                json.dumps({}, indent=4),
                raw=True)
        for sr_id in structure.get('storagerouters', []):
            if sr_id not in storagerouters:
                storagerouter = StorageRouter()
                storagerouter.name = str(sr_id)
                storagerouter.ip = '10.0.0.{0}'.format(sr_id)
                storagerouter.rdma_capable = False
                storagerouter.node_type = 'MASTER'
                storagerouter.machine_id = str(sr_id)
                storagerouter.save()
                storagerouters[sr_id] = storagerouter
                disk = Disk()
                disk.storagerouter = storagerouter
                disk.state = 'OK'
                disk.name = '/dev/uda'
                disk.size = 1 * 1024**4
                disk.is_ssd = True
                disk.aliases = ['/dev/uda']
                disk.save()
                partition = DiskPartition()
                partition.offset = 0
                partition.size = disk.size
                partition.aliases = ['/dev/uda-1']
                partition.state = 'OK'
                partition.mountpoint = '/tmp/unittest/sr_{0}/disk_1/partition_1'.format(
                    sr_id)
                partition.disk = disk
                partition.roles = [
                    DiskPartition.ROLES.DB, DiskPartition.ROLES.SCRUB
                ]
                partition.save()
            else:
                storagerouter = storagerouters[sr_id]

            # noinspection PyProtectedMember
            System._machine_id[storagerouter.ip] = str(sr_id)
            mds_start = 10000 + 100 * (sr_id - 1)
            mds_end = 10000 + 100 * sr_id - 1
            arakoon_start = 20000 + 100 * (sr_id - 1)
            storagedriver_start = 30000 + 100 * (sr_id - 1)
            storagedriver_end = 30000 + 100 * sr_id - 1
            Configuration.initialize_host(
                host_id=sr_id,
                port_info={
                    'mds': [mds_start, mds_end],
                    'arakoon': arakoon_start,
                    'storagedriver': [storagedriver_start, storagedriver_end]
                })

        for sd_id, vpool_id, sr_id in structure.get('storagedrivers', ()):
            if sd_id not in storagedrivers:
                storagedriver = StorageDriver()
                storagedriver.vpool = vpools[vpool_id]
                storagedriver.storagerouter = storagerouters[sr_id]
                storagedriver.name = str(sd_id)
                storagedriver.mountpoint = '/'
                storagedriver.cluster_ip = storagerouters[sr_id].ip
                storagedriver.storage_ip = '10.0.1.{0}'.format(sr_id)
                storagedriver.storagedriver_id = str(sd_id)
                storagedriver.ports = {
                    'management': 1,
                    'xmlrpc': 2,
                    'dtl': 3,
                    'edge': 4
                }
                storagedriver.save()
                storagedrivers[sd_id] = storagedriver
                DalHelper.set_vpool_storage_driver_configuration(
                    vpool=vpools[vpool_id], storagedriver=storagedriver)
        for mds_id, sd_id in structure.get('mds_services', ()):
            if mds_id not in mds_services:
                sd = storagedrivers[sd_id]
                s_id = '{0}-{1}'.format(sd.storagerouter.name, mds_id)
                service = Service()
                service.name = s_id
                service.storagerouter = sd.storagerouter
                service.ports = [mds_id]
                service.type = service_types['MetadataServer']
                service.save()
                services[s_id] = service
                mds_service = MDSService()
                mds_service.service = service
                mds_service.number = 0
                mds_service.capacity = 10
                mds_service.vpool = sd.vpool
                mds_service.save()
                mds_services[mds_id] = mds_service
                StorageDriverController.add_storagedriverpartition(
                    sd, {
                        'size': None,
                        'role': DiskPartition.ROLES.DB,
                        'sub_role': StorageDriverPartition.SUBROLE.MDS,
                        'partition': sd.storagerouter.disks[0].partitions[0],
                        'mds_service': mds_service
                    })
        for vdisk_id, storage_driver_id, vpool_id, mds_id in structure.get(
                'vdisks', ()):
            if vdisk_id not in vdisks:
                vpool = vpools[vpool_id]
                devicename = 'vdisk_{0}'.format(vdisk_id)
                mds_backend_config = DalHelper.generate_mds_metadata_backend_config(
                    [] if mds_id is None else [mds_services[mds_id]])
                volume_id = srclients[vpool_id].create_volume(
                    devicename, mds_backend_config, 0, str(storage_driver_id))
                vdisk = VDisk()
                vdisk.name = str(vdisk_id)
                vdisk.devicename = devicename
                vdisk.volume_id = volume_id
                vdisk.vpool = vpool
                vdisk.size = 0
                vdisk.save()
                vdisk.reload_client('storagedriver')
                vdisks[vdisk_id] = vdisk
        for srd_id, sr_id, domain_id, backup in structure.get(
                'storagerouter_domains', ()):
            if srd_id not in storagerouter_domains:
                sr_domain = StorageRouterDomain()
                sr_domain.backup = backup
                sr_domain.domain = domains[domain_id]
                sr_domain.storagerouter = storagerouters[sr_id]
                sr_domain.save()
                storagerouter_domains[srd_id] = sr_domain
        return {
            'vdisks': vdisks,
            'vpools': vpools,
            'domains': domains,
            'services': services,
            'mds_services': mds_services,
            'service_types': service_types,
            'storagerouters': storagerouters,
            'storagedrivers': storagedrivers,
            'storagerouter_domains': storagerouter_domains
        }
Exemple #20
0
    def sync_with_reality(storagerouter_guid=None):
        """
        Syncs the Disks from all StorageRouters with the reality.
        :param storagerouter_guid: Guid of the Storage Router to synchronize
        """
        storagerouters = []
        if storagerouter_guid is not None:
            storagerouters.append(StorageRouter(storagerouter_guid))
        else:
            storagerouters = StorageRouterList.get_storagerouters()
        for storagerouter in storagerouters:
            try:
                client = SSHClient(storagerouter, username='******')
            except UnableToConnectException:
                DiskController._logger.info('Could not connect to StorageRouter {0}, skipping'.format(storagerouter.ip))
                continue
            configuration = {}
            # Gather mount data
            mount_mapping = {}
            mount_data = client.run('mount')
            for mount in mount_data.splitlines():
                mount = mount.strip()
                match = re.search('(/dev/(.+?)) on (/.*?) type.*', mount)
                if match is not None:
                    dev_name = match.groups()[0]
                    uuid = client.run('blkid -o value -s UUID {0}'.format(dev_name))
                    if uuid:
                        mount_mapping[uuid] = match.groups()[2]
                    else:
                        mount_mapping[match.groups()[1]] = match.groups()[2]
            # Gather raid information
            try:
                md_information = client.run('mdadm --detail /dev/md*', suppress_logging=True)
            except CalledProcessError:
                md_information = ''
            raid_members = []
            for member in re.findall('(?: +[0-9]+){4} +[^/]+/dev/([a-z0-9]+)', md_information):
                raid_members.append(member)
            # Gather disk information
            with remote(storagerouter.ip, [Context, os]) as rem:
                context = rem.Context()
                devices = [device for device in context.list_devices(subsystem='block')
                           if ('ID_TYPE' in device and device['ID_TYPE'] == 'disk') or
                              ('DEVNAME' in device and ('loop' in device['DEVNAME'] or 'nvme' in device['DEVNAME'] or 'md' in device['DEVNAME']))]
                for device in devices:
                    is_partition = device['DEVTYPE'] == 'partition'
                    device_path = device['DEVNAME']
                    device_name = device_path.split('/')[-1]
                    partition_id = None
                    partition_name = None
                    extended_partition_info = None
                    if is_partition is True:
                        partition_name = device['ID_FS_UUID'] if 'ID_FS_UUID' in device else device_name
                        if 'ID_PART_ENTRY_NUMBER' in device:
                            extended_partition_info = True
                            partition_id = device['ID_PART_ENTRY_NUMBER']
                            if device_name.startswith('nvme') or device_name.startswith('loop'):
                                device_name = device_name[:0 - int(len(partition_id)) - 1]
                            elif device_name.startswith('md'):
                                device_name = device_name[:device_name.index('p')]
                            else:
                                device_name = device_name[:0 - int(len(partition_id))]
                        else:
                            DiskController._logger.debug('Partition {0} has no partition metadata'.format(device_path))
                            extended_partition_info = False
                            match = re.match('^(\D+?)(\d+)$', device_name)
                            if match is None:
                                DiskController._logger.debug('Could not handle disk/partition {0}'.format(device_path))
                                continue  # Unable to handle this disk/partition
                            partition_id = match.groups()[1]
                            device_name = match.groups()[0]
                    sectors = int(client.run('cat /sys/block/{0}/size'.format(device_name)))
                    sector_size = int(client.run('cat /sys/block/{0}/queue/hw_sector_size'.format(device_name)))
                    rotational = int(client.run('cat /sys/block/{0}/queue/rotational'.format(device_name)))

                    if sectors == 0:
                        continue
                    if device_name in raid_members:
                        continue
                    if device_name not in configuration:
                        configuration[device_name] = {'partitions': {}}
                    path = None
                    for path_type in ['by-id', 'by-uuid']:
                        if path is not None:
                            break
                        if 'DEVLINKS' in device:
                            for item in device['DEVLINKS'].split(' '):
                                if path_type in item:
                                    path = item
                    if path is None:
                        path = device_path
                    if is_partition is True:
                        if 'ID_PART_ENTRY_TYPE' in device and device['ID_PART_ENTRY_TYPE'] == '0x5':
                            continue  # This is an extended partition, let's skip that one
                        if extended_partition_info is True:
                            offset = int(device['ID_PART_ENTRY_OFFSET']) * sector_size
                            size = int(device['ID_PART_ENTRY_SIZE']) * sector_size
                        else:
                            match = re.match('^(\D+?)(\d+)$', device_path)
                            if match is None:
                                DiskController._logger.debug('Could not handle disk/partition {0}'.format(device_path))
                                continue  # Unable to handle this disk/partition
                            partitions_info = DiskTools.get_partitions_info(match.groups()[0])
                            if device_path in partitions_info:
                                partition_info = partitions_info[device_path]
                                offset = int(partition_info['start'])
                                size = int(partition_info['size'])
                            else:
                                DiskController._logger.warning('Could not retrieve partition info for disk/partition {0}'.format(device_path))
                                continue
                        configuration[device_name]['partitions'][partition_id] = {'offset': offset,
                                                                                  'size': size,
                                                                                  'path': path,
                                                                                  'state': 'OK'}
                        partition_data = configuration[device_name]['partitions'][partition_id]
                        if partition_name in mount_mapping:
                            mountpoint = mount_mapping[partition_name]
                            partition_data['mountpoint'] = mountpoint
                            partition_data['inode'] = rem.os.stat(mountpoint).st_dev
                            del mount_mapping[partition_name]
                            try:
                                client.run('touch {0}/{1}; rm {0}/{1}'.format(mountpoint, str(time.time())))
                            except CalledProcessError:
                                partition_data['state'] = 'FAILURE'
                                pass
                        if 'ID_FS_TYPE' in device:
                            partition_data['filesystem'] = device['ID_FS_TYPE']
                    else:
                        configuration[device_name].update({'name': device_name,
                                                           'path': path,
                                                           'vendor': device['ID_VENDOR'] if 'ID_VENDOR' in device else None,
                                                           'model': device['ID_MODEL'] if 'ID_MODEL' in device else None,
                                                           'size': sector_size * sectors,
                                                           'is_ssd': rotational == 0,
                                                           'state': 'OK'})
                    for partition_name in mount_mapping:
                        device_name = partition_name.split('/')[-1]
                        match = re.search('^(\D+?)(\d+)$', device_name)
                        if match is not None:
                            device_name = match.groups()[0]
                            partition_id = match.groups()[1]
                            if device_name not in configuration:
                                configuration[device_name] = {'partitions': {},
                                                              'state': 'MISSING'}
                            configuration[device_name]['partitions'][partition_id] = {'mountpoint': mount_mapping[partition_name],
                                                                                      'state': 'MISSING'}
            # Sync the model
            disk_names = []
            for disk in storagerouter.disks:
                if disk.name not in configuration:
                    for partition in disk.partitions:
                        partition.delete()
                    disk.delete()
                else:
                    disk_names.append(disk.name)
                    DiskController._update_disk(disk, configuration[disk.name])
                    partitions = []
                    partition_info = configuration[disk.name]['partitions']
                    for partition in disk.partitions:
                        if partition.id not in partition_info:
                            partition.delete()
                        else:
                            partitions.append(partition.id)
                            DiskController._update_partition(partition, partition_info[partition.id])
                    for partition_id in partition_info:
                        if partition_id not in partitions:
                            DiskController._create_partition(partition_id, partition_info[partition_id], disk)
            for disk_name in configuration:
                if disk_name not in disk_names and configuration[disk_name]['state'] not in ['MISSING']:
                    disk = Disk()
                    disk.storagerouter = storagerouter
                    disk.name = disk_name
                    DiskController._update_disk(disk, configuration[disk_name])
                    partition_info = configuration[disk_name]['partitions']
                    for partition_id in partition_info:
                        if partition_info[partition_id]['state'] not in ['MISSING']:
                            DiskController._create_partition(partition_id, partition_info[partition_id], disk)
Exemple #21
0
    def test_clone_snapshot(self):
        """
        Validates that a snapshot that has clones will not be deleted while other snapshots will be deleted
        """
        # Setup
        # There are 2 disks, second one cloned from a snapshot of the first
        vpool = VPool()
        vpool.name = 'vpool'
        vpool.status = 'RUNNING'
        vpool.save()
        storage_router = StorageRouter()
        storage_router.name = 'storage_router'
        storage_router.ip = '127.0.0.1'
        storage_router.machine_id = System.get_my_machine_id()
        storage_router.rdma_capable = False
        storage_router.save()
        disk = Disk()
        disk.name = 'physical_disk_1'
        disk.aliases = ['/dev/non-existent']
        disk.size = 500 * 1024**3
        disk.state = 'OK'
        disk.is_ssd = True
        disk.storagerouter = storage_router
        disk.save()
        disk_partition = DiskPartition()
        disk_partition.disk = disk
        disk_partition.aliases = ['/dev/disk/non-existent']
        disk_partition.size = 400 * 1024**3
        disk_partition.state = 'OK'
        disk_partition.offset = 1024
        disk_partition.roles = [DiskPartition.ROLES.SCRUB]
        disk_partition.mountpoint = '/var/tmp'
        disk_partition.save()
        storage_driver = StorageDriver()
        storage_driver.vpool = vpool
        storage_driver.storagerouter = storage_router
        storage_driver.name = 'storage_driver_1'
        storage_driver.mountpoint = '/'
        storage_driver.cluster_ip = storage_router.ip
        storage_driver.storage_ip = '127.0.0.1'
        storage_driver.storagedriver_id = 'storage_driver_1'
        storage_driver.ports = {
            'management': 1,
            'xmlrpc': 2,
            'dtl': 3,
            'edge': 4
        }
        storage_driver.save()
        service_type = ServiceType()
        service_type.name = 'MetadataServer'
        service_type.save()
        service = Service()
        service.name = 'service_1'
        service.storagerouter = storage_driver.storagerouter
        service.ports = [1]
        service.type = service_type
        service.save()
        mds_service = MDSService()
        mds_service.service = service
        mds_service.number = 0
        mds_service.capacity = 10
        mds_service.vpool = storage_driver.vpool
        mds_service.save()
        vdisk_1_1 = VDisk()
        vdisk_1_1.name = 'vdisk_1_1'
        vdisk_1_1.volume_id = 'vdisk_1_1'
        vdisk_1_1.vpool = vpool
        vdisk_1_1.devicename = 'dummy'
        vdisk_1_1.size = 0
        vdisk_1_1.save()
        vdisk_1_1.reload_client('storagedriver')

        [
            dynamic for dynamic in vdisk_1_1._dynamics
            if dynamic.name == 'snapshots'
        ][0].timeout = 0

        travis = 'TRAVIS' in os.environ and os.environ['TRAVIS'] == 'true'
        if travis is True:
            print 'Running in Travis, reducing output.'

        base = datetime.datetime.now().date()
        day = datetime.timedelta(1)
        base_timestamp = self._make_timestamp(base, day)
        minute = 60
        hour = minute * 60
        for h in [6, 12, 18]:
            timestamp = base_timestamp + (hour * h)
            VDiskController.create_snapshot(vdisk_guid=vdisk_1_1.guid,
                                            metadata={
                                                'label':
                                                'snapshot_{0}:30'.format(
                                                    str(h)),
                                                'is_consistent':
                                                True,
                                                'timestamp':
                                                str(timestamp),
                                                'machineguid':
                                                None
                                            })

        base_snapshot_guid = vdisk_1_1.snapshots[0]['guid']  # Oldest
        clone_vdisk = VDisk()
        clone_vdisk.name = 'clone_vdisk'
        clone_vdisk.volume_id = 'clone_vdisk'
        clone_vdisk.vpool = vpool
        clone_vdisk.devicename = 'dummy'
        clone_vdisk.parentsnapshot = base_snapshot_guid
        clone_vdisk.size = 0
        clone_vdisk.save()
        clone_vdisk.reload_client('storagedriver')

        for h in [6, 12, 18]:
            timestamp = base_timestamp + (hour * h)
            VDiskController.create_snapshot(vdisk_guid=clone_vdisk.guid,
                                            metadata={
                                                'label':
                                                'snapshot_{0}:30'.format(
                                                    str(h)),
                                                'is_consistent':
                                                True,
                                                'timestamp':
                                                str(timestamp),
                                                'machineguid':
                                                None
                                            })

        base_timestamp = self._make_timestamp(base, day * 2)
        ScheduledTaskController.delete_snapshots(timestamp=base_timestamp +
                                                 (minute * 30))
        self.assertIn(
            base_snapshot_guid, [snap['guid'] for snap in vdisk_1_1.snapshots],
            'Snapshot was deleted while there are still clones of it')