#!/usr/bin/env python2
#
# Copyright (C) 2017 Red Hat, Inc.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; If not, see .
#
# Author: Gris Ge
#
import os
import errno
import json
import sys
from lsm import Client, LsmError, ErrorNumber, LocalDisk
def print_stderr(msg):
sys.stderr.write(msg)
sys.stderr.write("\n")
def is_free_disk(blk_path):
"""
The manpage of open(2):
There is one exception: on Linux 2.6 and later, O_EXCL can be used
without O_CREAT if pathname refers to a block device. If the block
device is in use by the system (e.g., mounted), open() fails with the
error EBUSY.
Even when disk has partitions, when all partitions are not used, this
function still treats it as free disk.
When specific disk or its partition is used by LVM, if there is no data
stored in it, this function still treats it as free disk.
"""
try:
fd = os.open(blk_path, os.O_EXCL)
os.close(fd)
return True
except OSError as err:
if err.errno == errno.EBUSY:
return False
elif err.errno == errno.EACCES:
raise LsmError(ErrorNumber.PERMISSION_DENIED,
"Permission deny, try sudo or run as root")
else:
raise
def get_mpath(blk_path):
"""
Use `/sys/block/sdX/holders/dm-0/dm/name` to get mpath name,
use `/sys/block/sdf/holders/dm-0/dm/uuid` to check whether holder is
multipath.
"""
blk_name = blk_path[len("/dev/"):]
sysfs_holder_folder = "/sys/block/%s/holders" % blk_name
try:
holders = os.listdir(sysfs_holder_folder)
except FileNotFoundError:
return None
if len(holders) != 1:
return None
holder_name = holders[0]
sysfs_dm_name_path = "/sys/block/%s/dm/name" % holder_name
sysfs_dm_uuid_path = "/sys/block/%s/dm/uuid" % holder_name
with open(sysfs_dm_uuid_path) as f:
if not f.read().startswith("mpath-"):
return None
with open(sysfs_dm_name_path) as f:
return f.read().strip()
def is_free_multipath(blk_path):
mpath_name = get_mpath(blk_path)
if mpath_name:
return is_free_disk("/dev/mapper/%s" % mpath_name)
return False
def find_unused_lun(c):
"""
Return a list of lsm.Volume objects.
Unused here means the LUN/volume meets one of following conditions:
* the block device representing LUN/Volume could be opened with O_EXCL,
which means its block is completely free.
* The block device is used as PV of LVM, but not been used.
"""
vols = c.volumes()
vpd83_to_vol_hash = {}
free_vol_dict = {}
for vol in vols:
if vol.vpd83:
vpd83_to_vol_hash[vol.vpd83] = vol
for disk_path in LocalDisk.list():
if is_free_disk(disk_path) or is_free_multipath(disk_path):
try:
vpd83 = LocalDisk.vpd83_get(disk_path)
except LsmError as lsm_err:
if lsm_err.code == ErrorNumber.NO_SUPPORT:
continue
raise
if vpd83 not in vpd83_to_vol_hash:
print_stderr("Found free disk %s, but not managed by "
"libstoragemgmt plugin" % disk_path)
continue
free_vol_dict[vpd83] = vpd83_to_vol_hash[vpd83]
return free_vol_dict.values()
def format_vol(vol, sys_dict):
d = {
"id": vol.id,
"name": vol.name,
"wwid": vol.vpd83,
"system_id": vol.system_id,
"system_name": sys_dict[vol.system_id].name
}
blk_paths = LocalDisk.vpd83_search(vol.vpd83)
if blk_paths:
d['blk_paths'] = blk_paths
mpath_name = get_mpath(blk_paths[0])
if mpath_name:
d['mpath_blk'] = "/dev/mapper/%s" % mpath_name
return d
def main():
uri = os.getenv('LSMCLI_URI')
password = os.getenv('LSMCLI_PASSWORD')
if uri is None:
print_stderr("LSMCLI_URI environment not defined, using 'local://'")
uri = "local://"
c = Client(uri, password)
free_vols = find_unused_lun(c)
if not free_vols:
print_stderr("Didn't find any free LUN")
exit()
syss = c.systems()
sys_dict = {}
for lsm_sys in syss:
sys_dict[lsm_sys.id] = lsm_sys
print_stderr("\nFound %d free LUN(s):\n" % len(free_vols))
for vol in free_vols:
print(json.dumps(format_vol(vol, sys_dict), indent=4))
if __name__ == '__main__':
main()