def _parse(self): lv = None if not self.sys_api: # if no device was found check if we are a partition partname = self.path.split('/')[-1] for device, info in sys_info.devices.items(): part = info['partitions'].get(partname, {}) if part: self.sys_api = part break if self.lvs: for _lv in self.lvs: # if the path is not absolute, we have 'vg/lv', let's use LV name # to get the LV. if self.path[0] == '/': if _lv.lv_path == self.path: lv = _lv break else: vgname, lvname = self.path.split('/') if _lv.lv_name == lvname and _lv.vg_name == vgname: lv = _lv break else: if self.path[0] == '/': lv = lvm.get_single_lv(filters={'lv_path': self.path}) else: vgname, lvname = self.path.split('/') lv = lvm.get_single_lv(filters={ 'lv_name': lvname, 'vg_name': vgname }) if lv: self.lv_api = lv self.lvs = [lv] self.path = lv.lv_path self.vg_name = lv.vg_name self.lv_name = lv.name self.ceph_device = lvm.is_ceph_device(lv) else: if self.lsblk_all: for dev in self.lsblk_all: if dev['NAME'] == os.path.basename(self.path): break else: dev = disk.lsblk(self.path) self.disk_api = dev device_type = dev.get('TYPE', '') # always check is this is an lvm member valid_types = ['part', 'disk'] if allow_loop_devices(): valid_types.append('loop') if device_type in valid_types: self._set_lvm_membership() self.ceph_device = disk.has_bluestore_label(self.path) self.ceph_disk = CephDiskDevice(self)
def safe_prepare(self, args=None): """ An intermediate step between `main()` and `prepare()` so that we can capture the `self.osd_id` in case we need to rollback :param args: Injected args, usually from `lvm create` which compounds both `prepare` and `create` """ if args is not None: self.args = args try: vgname, lvname = self.args.data.split('/') lv = api.get_single_lv(filters={ 'lv_name': lvname, 'vg_name': vgname }) except ValueError: lv = None if api.is_ceph_device(lv): logger.info("device {} is already used".format(self.args.data)) raise RuntimeError("skipping {}, it is already prepared".format( self.args.data)) try: self.prepare() except Exception: logger.exception('lvm prepare was unable to complete') logger.info('will rollback OSD ID creation') rollback_osd(self.args, self.osd_id) raise terminal.success("ceph-volume lvm prepare successful for: %s" % self.args.data)
def zap_lv(self, device): """ Device examples: vg-name/lv-name, /dev/vg-name/lv-name Requirements: Must be a logical volume (LV) """ lv = api.get_single_lv(filters={ 'lv_name': device.lv_name, 'vg_name': device.vg_name }) self.unmount_lv(lv) wipefs(device.abspath) zap_data(device.abspath) if self.args.destroy: lvs = api.get_lvs(filters={'vg_name': device.vg_name}) if lvs == []: mlogger.info('No LVs left, exiting', device.vg_name) return elif len(lvs) <= 1: mlogger.info( 'Only 1 LV left in VG, will proceed to destroy ' 'volume group %s', device.vg_name) api.remove_vg(device.vg_name) else: mlogger.info('More than 1 LV left in VG, will proceed to ' 'destroy LV only') mlogger.info('Removing LV because --destroy was given: %s', device.abspath) api.remove_lv(device.abspath) elif lv: # just remove all lvm metadata, leaving the LV around lv.clear_tags()
def single_report(self, arg): """ Generate a report for a single device. This can be either a logical volume in the form of vg/lv, a device with an absolute path like /dev/sda1 or /dev/sda, or a list of devices under same OSD ID. Return value '{}' denotes failure. """ if isinstance(arg, int) or arg.isdigit(): lv = api.get_lvs_from_osd_id(arg) elif arg[0] == '/': lv = api.get_lvs_from_path(arg) else: lv = [api.get_single_lv(filters={'lv_name': arg.split('/')[1]})] report = self.create_report(lv) if not report: # check if device is a non-lvm journals or wal/db for dev_type in ['journal', 'wal', 'db']: lvs = api.get_lvs( tags={'ceph.{}_device'.format(dev_type): arg}) if lvs: # just taking the first lv here should work lv = lvs[0] phys_dev = self.create_report_non_lv_device(lv) osd_id = lv.tags.get('ceph.osd_id') if osd_id: report[osd_id] = [phys_dev] return report
def test_get_single_lv_multiple_matches_raises_runtimeerror(self, m_get_lvs): fake_lvs = [] fake_lvs.append(api.Volume(lv_name='lv1', lv_path='/dev/vg1/lv1', vg_name='vg1', lv_tags='', lv_uuid='fake-uuid')) fake_lvs.append(api.Volume(lv_name='lv1', lv_path='/dev/vg2/lv1', vg_name='vg2', lv_tags='', lv_uuid='fake-uuid')) m_get_lvs.return_value = fake_lvs with pytest.raises(RuntimeError) as e: api.get_single_lv() assert "matched more than 1 LV present on this host" in str(e.value)
def test_get_single_lv_one_match(self, m_get_lvs): fake_lvs = [] fake_lvs.append(api.Volume(lv_name='lv1', lv_path='/dev/vg1/lv1', vg_name='vg1', lv_tags='', lv_uuid='fake-uuid')) m_get_lvs.return_value = fake_lvs lv_ = api.get_single_lv() assert isinstance(lv_, api.Volume) assert lv_.name == 'lv1'
def _parse(self): if not sys_info.devices: sys_info.devices = disk.get_devices() self.sys_api = sys_info.devices.get(self.abspath, {}) if not self.sys_api: # if no device was found check if we are a partition partname = self.abspath.split('/')[-1] for device, info in sys_info.devices.items(): part = info['partitions'].get(partname, {}) if part: self.sys_api = part break # if the path is not absolute, we have 'vg/lv', let's use LV name # to get the LV. if self.path[0] == '/': lv = lvm.get_single_lv(filters={'lv_path': self.path}) else: vgname, lvname = self.path.split('/') lv = lvm.get_single_lv(filters={'lv_name': lvname, 'vg_name': vgname}) if lv: self.lv_api = lv self.lvs = [lv] self.abspath = lv.lv_path self.vg_name = lv.vg_name self.lv_name = lv.name self.ceph_device = lvm.is_ceph_device(lv) else: dev = disk.lsblk(self.path) self.blkid_api = disk.blkid(self.path) self.disk_api = dev device_type = dev.get('TYPE', '') # always check is this is an lvm member if device_type in ['part', 'disk']: self._set_lvm_membership() out, err, rc = process.call([ 'ceph-bluestore-tool', 'show-label', '--dev', self.path], verbose_on_failure=False) if rc: self.ceph_device = True self.ceph_disk = CephDiskDevice(self)
def setup_device(self, device_type, device_name, tags, size, slots): """ Check if ``device`` is an lv, if so, set the tags, making sure to update the tags with the lv_uuid and lv_path which the incoming tags will not have. If the device is not a logical volume, then retrieve the partition UUID by querying ``blkid`` """ if device_name is None: return '', '', tags tags['ceph.type'] = device_type tags['ceph.vdo'] = api.is_vdo(device_name) try: vg_name, lv_name = device_name.split('/') lv = api.get_single_lv(filters={ 'lv_name': lv_name, 'vg_name': vg_name }) except ValueError: lv = None if lv: lv_uuid = lv.lv_uuid path = lv.lv_path tags['ceph.%s_uuid' % device_type] = lv_uuid tags['ceph.%s_device' % device_type] = path lv.set_tags(tags) elif disk.is_device(device_name): # We got a disk, create an lv lv_type = "osd-{}".format(device_type) name_uuid = system.generate_uuid() kwargs = {'device': device_name, 'tags': tags, 'slots': slots} #TODO use get_block_db_size and co here to get configured size in #conf file if size != 0: kwargs['size'] = size lv = api.create_lv(lv_type, name_uuid, **kwargs) path = lv.lv_path tags['ceph.{}_device'.format(device_type)] = path tags['ceph.{}_uuid'.format(device_type)] = lv.lv_uuid lv_uuid = lv.lv_uuid lv.set_tags(tags) else: # otherwise assume this is a regular disk partition name_uuid = self.get_ptuuid(device_name) path = device_name tags['ceph.%s_uuid' % device_type] = name_uuid tags['ceph.%s_device' % device_type] = path lv_uuid = name_uuid return path, lv_uuid, tags
def scan_device(self, path): device_metadata = {'path': None, 'uuid': None} if not path: return device_metadata if self.is_encrypted: encryption_metadata = encryption.legacy_encrypted(path) device_metadata['path'] = encryption_metadata['device'] device_metadata['uuid'] = disk.get_partuuid(encryption_metadata['device']) return device_metadata # cannot read the symlink if this is tmpfs if os.path.islink(path): device = os.readlink(path) else: device = path lvm_device = lvm.get_single_lv(filters={'lv_path': device}) if lvm_device: device_uuid = lvm_device.lv_uuid else: device_uuid = disk.get_partuuid(device) device_metadata['uuid'] = device_uuid device_metadata['path'] = device return device_metadata
def prepare(self): # FIXME we don't allow re-using a keyring, we always generate one for the # OSD, this needs to be fixed. This could either be a file (!) or a string # (!!) or some flags that we would need to compound into a dict so that we # can convert to JSON (!!!) secrets = {'cephx_secret': prepare_utils.create_key()} cephx_lockbox_secret = '' encrypted = 1 if self.args.dmcrypt else 0 cephx_lockbox_secret = '' if not encrypted else prepare_utils.create_key( ) if encrypted: secrets['dmcrypt_key'] = encryption_utils.create_dmcrypt_key() secrets['cephx_lockbox_secret'] = cephx_lockbox_secret cluster_fsid = self.get_cluster_fsid() osd_fsid = self.args.osd_fsid or system.generate_uuid() crush_device_class = self.args.crush_device_class if crush_device_class: secrets['crush_device_class'] = crush_device_class # reuse a given ID if it exists, otherwise create a new ID self.osd_id = prepare_utils.create_id(osd_fsid, json.dumps(secrets), osd_id=self.args.osd_id) tags = { 'ceph.osd_fsid': osd_fsid, 'ceph.osd_id': self.osd_id, 'ceph.cluster_fsid': cluster_fsid, 'ceph.cluster_name': conf.cluster, 'ceph.crush_device_class': crush_device_class, 'ceph.osdspec_affinity': prepare_utils.get_osdspec_affinity() } if self.args.filestore: if not self.args.journal: logger.info(('no journal was specifed, creating journal lv ' 'on {}').format(self.args.data)) self.args.journal = self.args.data self.args.journal_size = disk.Size(g=5) # need to adjust data size/slots for colocated journal if self.args.data_size: self.args.data_size -= self.args.journal_size if self.args.data_slots == 1: self.args.data_slots = 0 else: raise RuntimeError('Can\'t handle multiple filestore OSDs ' 'with colocated journals yet. Please ' 'create journal LVs manually') tags['ceph.cephx_lockbox_secret'] = cephx_lockbox_secret tags['ceph.encrypted'] = encrypted journal_device, journal_uuid, tags = self.setup_device( 'journal', self.args.journal, tags, self.args.journal_size, self.args.journal_slots) try: vg_name, lv_name = self.args.data.split('/') data_lv = api.get_single_lv(filters={ 'lv_name': lv_name, 'vg_name': vg_name }) except ValueError: data_lv = None if not data_lv: data_lv = self.prepare_data_device('data', osd_fsid) tags['ceph.data_device'] = data_lv.lv_path tags['ceph.data_uuid'] = data_lv.lv_uuid tags['ceph.vdo'] = api.is_vdo(data_lv.lv_path) tags['ceph.type'] = 'data' data_lv.set_tags(tags) if not journal_device.startswith('/'): # we got a journal lv, set rest of the tags api.get_single_lv(filters={ 'lv_name': lv_name, 'vg_name': vg_name }).set_tags(tags) prepare_filestore( data_lv.lv_path, journal_device, secrets, tags, self.osd_id, osd_fsid, ) elif self.args.bluestore: try: vg_name, lv_name = self.args.data.split('/') block_lv = api.get_single_lv(filters={ 'lv_name': lv_name, 'vg_name': vg_name }) except ValueError: block_lv = None if not block_lv: block_lv = self.prepare_data_device('block', osd_fsid) tags['ceph.block_device'] = block_lv.lv_path tags['ceph.block_uuid'] = block_lv.lv_uuid tags['ceph.cephx_lockbox_secret'] = cephx_lockbox_secret tags['ceph.encrypted'] = encrypted tags['ceph.vdo'] = api.is_vdo(block_lv.lv_path) wal_device, wal_uuid, tags = self.setup_device( 'wal', self.args.block_wal, tags, self.args.block_wal_size, self.args.block_wal_slots) db_device, db_uuid, tags = self.setup_device( 'db', self.args.block_db, tags, self.args.block_db_size, self.args.block_db_slots) tags['ceph.type'] = 'block' block_lv.set_tags(tags) prepare_bluestore( block_lv.lv_path, wal_device, db_device, secrets, tags, self.osd_id, osd_fsid, )
def test_get_single_lv_no_match_returns_none(self, m_get_lvs): m_get_lvs.return_value = [] lv = api.get_single_lv() assert lv == None