def test_analyze_invalid_filter_empty_item(): # Current filter is invalid - since LVM will reject this filter anyway, we # can configure a correct filter. wanted_filter = ["a|^/dev/sda2$|", "r|.*|"] current_filter = ["a|invalid|", "r||"] with pytest.raises(lvmfilter.InvalidFilter): lvmfilter.analyze(current_filter, wanted_filter)
def test_analyze_invalid_filter_no_delimeter(): # Current filter is invalid - since LVM will reject this filter anyway, we # can configure a correct filter. wanted_filter = ["a|^/dev/sda2$|", "r|.*|"] current_filter = ["a|invalid", "r|filter/"] current_blacklist = wanted_blacklist = {"wwid1"} with pytest.raises(lvmfilter.InvalidFilter): lvmfilter.analyze(current_filter, wanted_filter, current_blacklist, wanted_blacklist)
def test_analyze_different_order(): # Same filter, order of devices does not matter. wanted_filter = ["a|^/dev/sda2$|", "a|^/dev/sdb2$|", "r|.*|"] current_filter = ["a|^/dev/sdb2$|", "a|^/dev/sda2$|", "r|.*|"] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.UNNEEDED assert advice.filter is None
def test_analyze_configure_replace_unstable_link(fake_dm_device): # Current filter is correct, but uses unstable link name to the device. wanted_filter = ["a|^{}$|".format(fake_dm_device.stable_link), "r|.*|"] current_filter = ["a|^{}$|".format(fake_dm_device.unstable_link), "r|.*|"] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.CONFIGURE assert advice.filter == wanted_filter
def test_analyze_no_filter(): # Trivial case: host does not have any filter. wanted_filter = ["a|^/dev/sda2$|", "r|.*|"] current_filter = None advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.CONFIGURE assert advice.filter == wanted_filter
def test_analyze_recommend_reg_exp_in_path(fake_device): # Current filter use unstable names and contains regular expression. wanted_filter = ["a|^{}$|".format(fake_device.stable_link), "r|.*|"] current_filter = ["a|^/dev/sda*$|", "r|.*|"] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter
def test_analyze_missing_device(): # Current filter is missing a device. Probably a user error, but the user # will have to resolve this. wanted_filter = ["a|^/dev/sda2$|", "a|^/dev/sdb2$|", "r|.*|"] current_filter = ["a|^/dev/sda2$|", "r|.*|"] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter
def test_analyze_no_anchorces(): # Curent filter uses non-strict regex witout anchores. This should work in # general, but we like to have a more strict filter. wanted_filter = ["a|^/dev/sda2$|", "r|.*|"] current_filter = ["a|/dev/sda2|", "r|.*|"] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter
def test_analyze_extra_reject(): # User wants to reject another device - does not make sense, but the user # knows better. wanted_filter = ["a|^/dev/sda2$|", "r|.*|"] current_filter = ["a|^/dev/sda2$|", "r|.*|", "r|/dev/foo|"] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter
def test_analyze_recommend_replace_unstable_device_no_anchors(fake_device): # Current filter is correct, but uses unstable device name and don't use # anchors. wanted_filter = ["a|^{}$|".format(fake_device.stable_link), "r|.*|"] current_filter = ["a|{}|".format(fake_device.device), "r|.*|"] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter
def test_analyze_recommend_added_custom_unstable_name(fake_device): # Current filter use unstable names and admin added another device with # unstable name. wanted_filter = ["a|^{}$|".format(fake_device.stable_link), "r|.*|"] current_filter = ["a|^/dev/sda1$|", "a|^/dev/sda2$|", "r|.*|"] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter
def test_analyze_unknown_device(): # Current filter includes an unknown device. This may be a user error, # removing a device without updating the filter, or maybe the user knows # better. wanted_filter = ["a|^/dev/sda2$|", "r|.*|"] current_filter = ["a|^/dev/sda2$|", "a|^/dev/sdb2$|", "r|.*|"] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter
def test_analyze_configured(): # Trivial case: host was already configured, no action needed. current_filter = wanted_filter = ["a|^/dev/sda2$|", "r|.*|"] current_blacklist = wanted_blacklist = {"wwid1"} advice = lvmfilter.analyze(current_filter, wanted_filter, current_blacklist, wanted_blacklist) assert advice.action == lvmfilter.UNNEEDED assert advice.filter is None assert advice.wwids is None
def test_analyze_different_order(): # Same filter, order of devices does not matter. wanted_filter = ["a|^/dev/sda2$|", "a|^/dev/sdb2$|", "r|.*|"] current_filter = ["a|^/dev/sdb2$|", "a|^/dev/sda2$|", "r|.*|"] current_blacklist = wanted_blacklist = {"wwid1", "wwid2"} advice = lvmfilter.analyze(current_filter, wanted_filter, current_blacklist, wanted_blacklist) assert advice.action == lvmfilter.UNNEEDED assert advice.filter is None assert advice.wwids is None
def test_analyze_missing_blacklist(): # host has right filter configured, but no blacklist to match. current_filter = wanted_filter = ["a|^/dev/sda2$|", "r|.*|"] current_blacklist = None wanted_blacklist = {"wwid1"} advice = lvmfilter.analyze(current_filter, wanted_filter, current_blacklist, wanted_blacklist) assert advice.action == lvmfilter.CONFIGURE assert advice.filter == wanted_filter assert advice.wwids == wanted_blacklist
def test_analyze_configure_replace_unstable_device(fake_device): # Current filter is correct, but uses unstable device name. wanted_filter = ["a|^{}$|".format(fake_device.stable_link), "r|.*|"] current_filter = ["a|^{}$|".format(fake_device.device), "r|.*|"] current_blacklist = wanted_blacklist = {"wwid1"} advice = lvmfilter.analyze(current_filter, wanted_filter, current_blacklist, wanted_blacklist) assert advice.action == lvmfilter.CONFIGURE assert advice.filter == wanted_filter assert advice.wwids == wanted_blacklist
def test_analyze_recommend_links_do_not_match(tmpdir, fake_device): # Stable name is a link to different device than one in the filter. other_device = str(tmpdir.join("dm-2")) open(other_device, "w").close() wanted_filter = ["a|^{}$|".format(fake_device.stable_link), "r|.*|"] current_filter = ["a|^{}$|".format(other_device), "r|.*|"] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter
def test_analyze_recommend_added_custom_device(fake_device): # Current filter use unstable names and admin added another device with # unstable name. wanted_filter = ["a|^{}$|".format(fake_device.device), "r|.*|"] current_filter = ["a|^/dev/sda1$|", "a|^/dev/sda2$|", "r|.*|"] current_blacklist = wanted_blacklist = {"wwid1"} advice = lvmfilter.analyze(current_filter, wanted_filter, current_blacklist, wanted_blacklist) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter assert advice.wwids == wanted_blacklist
def test_analyze_no_filter(): # Trivial case: host does not have any filter or blacklist. wanted_filter = ["a|^/dev/sda2$|", "r|.*|"] current_filter = None current_blacklist = None wanted_blacklist = {"wwid1"} advice = lvmfilter.analyze(current_filter, wanted_filter, current_blacklist, wanted_blacklist) assert advice.action == lvmfilter.CONFIGURE assert advice.filter == wanted_filter assert advice.wwids == wanted_blacklist
def test_analyze_recommend_added_custom_stable_name(fake_device): # Current filter use unstable names and admin added another device with # stable name. wanted_filter = ["a|^{}$|".format(fake_device.stable_link), "r|.*|"] current_filter = [ "a|^{}$|".format(fake_device.device), "a|^/dev/disk/by-id/lvm-pv-uuid-2d84b62d$|", "r|.*|", ] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter
def test_analyze_recommend_replace_unstable_link_duplicate(fake_dm_device): # Current filter uses unstable links name to the device and there's also # another link with stable name to the same device. wanted_filter = ["a|^{}$|".format(fake_dm_device.stable_link), "r|.*|"] current_filter = [ "a|^{}$|".format(fake_dm_device.unstable_link), "a|^{}$|".format(fake_dm_device.stable_link), "r|.*|", ] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter
def test_analyze_recommend_links_do_not_match(tmpdir, fake_device): # Filter includes another device. other_device = str(tmpdir.join("dm-2")) open(other_device, "w").close() wanted_filter = ["a|^{}$|".format(fake_device.device), "r|.*|"] current_filter = ["a|^{}$|".format(other_device), "r|.*|"] current_blacklist = wanted_blacklist = {"wwid1"} advice = lvmfilter.analyze(current_filter, wanted_filter, current_blacklist, wanted_blacklist) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter assert advice.wwids == wanted_blacklist
def test_analyze_recommend_replace_udev_link_duplicate(fake_dm_device): # Current filter uses devicem mapper links to the device and there's also # another udev link to the same device. We want to use the device mapper # link since udev link is not reliable during boot. wanted_filter = ["a|^{}$|".format(fake_dm_device.mapper_link), "r|.*|"] current_filter = [ "a|^{}$|".format(fake_dm_device.mapper_link), "a|^{}$|".format(fake_dm_device.udev_link), "r|.*|", ] current_blacklist = wanted_blacklist = {"wwid1"} advice = lvmfilter.analyze(current_filter, wanted_filter, current_blacklist, wanted_blacklist) assert advice.action == lvmfilter.RECOMMEND assert advice.filter == wanted_filter assert advice.wwids == wanted_blacklist
def test_analyze_configure_different_item_order(fake_device, fake_dm_device): # Current filter is correct, but has different order of items than # recommended filter. wanted_filter = [ "a|^{}$|".format(fake_device.stable_link), "a|^{}$|".format(fake_dm_device.stable_link), "r|.*|", ] current_filter = [ "a|^{}$|".format(fake_dm_device.unstable_link), "a|^{}$|".format(fake_device.device), "r|.*|", ] current_blacklist = wanted_blacklist = {"wwid1", "wwid2"} advice = lvmfilter.analyze(current_filter, wanted_filter, current_blacklist, wanted_blacklist) assert advice.action == lvmfilter.CONFIGURE assert advice.filter == wanted_filter assert advice.wwids == wanted_blacklist
def config_with_filter(args): mounts = lvmfilter.find_lvm_mounts() wanted_wwids = lvmfilter.find_wwids(mounts) current_wwids = mpathconf.read_blacklist() wanted_filter = lvmfilter.build_filter(mounts) with lvmconf.LVMConfig() as lvm_config: current_filter = lvm_config.getlist("devices", "filter") advice = lvmfilter.analyze( current_filter, wanted_filter, current_wwids, wanted_wwids) # This is the expected condition on a correctly configured host. if advice.action == lvmfilter.UNNEEDED: print("LVM filter is already configured for Vdsm") return # We need to configure LVM filter. _print_summary(mounts, current_filter, wanted_filter, advice.wwids, None) if advice.action == lvmfilter.CONFIGURE: if not args.assume_yes: if not common.confirm("Configure host? [yes,NO] "): return NEEDS_CONFIG mpathconf.configure_blacklist(advice.wwids) with lvmconf.LVMConfig() as config: config.setlist("devices", "filter", advice.filter) config.setint("devices", "use_devicesfile", 0) config.save() _print_success() elif advice.action == lvmfilter.RECOMMEND: _print_filter_warning() return CANNOT_CONFIG
def main(*args): """ config-lvm-filter Configure LVM filter allowing LVM to access only the local storage needed by the hypervisor, but not shared storage owned by Vdsm. """ print("Analyzing host...") mounts = lvmfilter.find_lvm_mounts() wanted_filter = lvmfilter.build_filter(mounts) with lvmconf.LVMConfig() as config: current_filter = config.getlist("devices", "filter") advice = lvmfilter.analyze(current_filter, wanted_filter) # This is the expected condition on a correctly configured host. if advice.action == lvmfilter.UNNEEDED: print("LVM filter is already configured for Vdsm") return # We need to configure LVM filter. print("Found these mounted logical volumes on this host:") print() for mnt in mounts: print(" logical volume: ", mnt.lv) print(" mountpoint: ", mnt.mountpoint) print(" devices: ", ", ".join(mnt.devices)) print() print("This is the recommended LVM filter for this host:") print() print(" " + lvmfilter.format_option(wanted_filter)) print() print("""\ This filter allows LVM to access the local devices used by the hypervisor, but not shared storage owned by Vdsm. If you add a new device to the volume group, you will need to edit the filter manually. """) if current_filter: print("This is the current LVM filter:") print() print(" " + lvmfilter.format_option(current_filter)) print() if advice.action == lvmfilter.CONFIGURE: if not confirm("Configure LVM filter? [yes,NO] "): return with lvmconf.LVMConfig() as config: config.setlist("devices", "filter", advice.filter) config.save() print("""\ Configuration completed successfully! Please reboot to verify the LVM configuration. """) elif advice.action == lvmfilter.RECOMMEND: print("""\ WARNING: The current LVM filter does not match the recommended filter, Vdsm cannot configure the filter automatically. Please edit /etc/lvm/lvm.conf and set the 'filter' option in the 'devices' section to the recommended value. It is recommend to reboot after changing LVM filter. """)
def main(*args): """ config-lvm-filter Configure LVM filter allowing LVM to access only the local storage needed by the hypervisor, but not shared storage owned by Vdsm. Return codes: 0 - Successful completion. 1 - Exception caught during operation. 2 - Wrong arguments. 3 - LVM filter configuration was found to be required but could not be completed since there is already another filter configured on the host. 4 - User has chosen not to allow LVM filter reconfiguration, although found as required. """ args = parse_args(args) print("Analyzing host...") mounts = lvmfilter.find_lvm_mounts() wanted_filter = lvmfilter.build_filter(mounts) wanted_wwids = lvmfilter.find_wwids(mounts) with lvmconf.LVMConfig() as config: current_filter = config.getlist("devices", "filter") current_wwids = mpathconf.read_blacklist() advice = lvmfilter.analyze( current_filter, wanted_filter, current_wwids, wanted_wwids) # This is the expected condition on a correctly configured host. if advice.action == lvmfilter.UNNEEDED: print("LVM filter is already configured for Vdsm") return # We need to configure LVM filter. print("Found these mounted logical volumes on this host:") print() for mnt in mounts: print(" logical volume: ", mnt.lv) print(" mountpoint: ", mnt.mountpoint) print(" devices: ", ", ".join(mnt.devices)) print() print("This is the recommended LVM filter for this host:") print() print(" " + lvmfilter.format_option(wanted_filter)) print() print("""\ This filter allows LVM to access the local devices used by the hypervisor, but not shared storage owned by Vdsm. If you add a new device to the volume group, you will need to edit the filter manually. """) if current_filter: print("This is the current LVM filter:") print() print(" " + lvmfilter.format_option(current_filter)) print() if advice.wwids: print("To use the recommended filter we need to add multipath") print("blacklist in /etc/multipath/conf.d/vdsm_blacklist.conf:") print() print(textwrap.indent(mpathconf.format_blacklist(advice.wwids), " ")) print() if advice.action == lvmfilter.CONFIGURE: if not args.assume_yes: if not common.confirm("Configure host? [yes,NO] "): return NEEDS_CONFIG mpathconf.configure_blacklist(advice.wwids) with lvmconf.LVMConfig() as config: config.setlist("devices", "filter", advice.filter) config.save() print("""\ Configuration completed successfully! Please reboot to verify the configuration. """) elif advice.action == lvmfilter.RECOMMEND: print("""\ WARNING: The current LVM filter does not match the recommended filter, Vdsm cannot configure the filter automatically. Please edit /etc/lvm/lvm.conf and set the 'filter' option in the 'devices' section to the recommended value. Make sure /etc/multipath/conf.d/vdsm_blacklist.conf is set with the recommended 'blacklist' section. It is recommended to reboot to verify the new configuration. """) return CANNOT_CONFIG
def main(*args): """ config-lvm-filter Configure LVM filter allowing LVM to access only the local storage needed by the hypervisor, but not shared storage owned by Vdsm. """ print("Analyzing host...") mounts = lvmfilter.find_lvm_mounts() wanted_filter = lvmfilter.build_filter(mounts) with lvmconf.LVMConfig() as config: current_filter = config.getlist("devices", "filter") advice = lvmfilter.analyze(current_filter, wanted_filter) # This is the expected condition on a correctly configured host. if advice.action == lvmfilter.UNNEEDED: print("LVM filter is already configured for Vdsm") return # We need to configure LVM filter. print("Found these mounted logical volumes on this host:") print() for mnt in mounts: print(" logical volume: ", mnt.lv) print(" mountpoint: ", mnt.mountpoint) print(" devices: ", ", ".join(mnt.devices)) print() print("This is the recommended LVM filter for this host:") print() print(" " + lvmfilter.format_option(wanted_filter)) print() print("""\ This filter allows LVM to access the local devices used by the hypervisor, but not shared storage owned by Vdsm. If you add a new device to the volume group, you will need to edit the filter manually. """) if current_filter: print("This is the current LVM filter:") print() print(" " + lvmfilter.format_option(current_filter)) print() if advice.action == lvmfilter.CONFIGURE: if not common.confirm("Configure LVM filter? [yes,NO] "): return with lvmconf.LVMConfig() as config: config.setlist("devices", "filter", advice.filter) config.save() print("""\ Configuration completed successfully! Please reboot to verify the LVM configuration. """) elif advice.action == lvmfilter.RECOMMEND: print("""\ WARNING: The current LVM filter does not match the recommended filter, Vdsm cannot configure the filter automatically. Please edit /etc/lvm/lvm.conf and set the 'filter' option in the 'devices' section to the recommended value. It is recommend to reboot after changing LVM filter. """)
def test_analyze_configured(): # Trivial case: host was already configured, no action needed. current_filter = wanted_filter = ["a|^/dev/sda2$|", "r|.*|"] advice = lvmfilter.analyze(current_filter, wanted_filter) assert advice.action == lvmfilter.UNNEEDED assert advice.filter is None