Revision 376631d1 lib/bdev.py
b/lib/bdev.py | ||
---|---|---|
2786 | 2786 |
result.fail_reason, result.output) |
2787 | 2787 |
|
2788 | 2788 |
|
2789 |
class ExtStorageDevice(BlockDev): |
|
2790 |
"""A block device provided by an ExtStorage Provider. |
|
2791 |
|
|
2792 |
This class implements the External Storage Interface, which means |
|
2793 |
handling of the externally provided block devices. |
|
2794 |
|
|
2795 |
""" |
|
2796 |
def __init__(self, unique_id, children, size, params): |
|
2797 |
"""Attaches to an extstorage block device. |
|
2798 |
|
|
2799 |
""" |
|
2800 |
super(ExtStorageDevice, self).__init__(unique_id, children, size, params) |
|
2801 |
if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: |
|
2802 |
raise ValueError("Invalid configuration data %s" % str(unique_id)) |
|
2803 |
|
|
2804 |
self.driver, self.vol_name = unique_id |
|
2805 |
|
|
2806 |
self.major = self.minor = None |
|
2807 |
self.Attach() |
|
2808 |
|
|
2809 |
@classmethod |
|
2810 |
def Create(cls, unique_id, children, size, params): |
|
2811 |
"""Create a new extstorage device. |
|
2812 |
|
|
2813 |
Provision a new volume using an extstorage provider, which will |
|
2814 |
then be mapped to a block device. |
|
2815 |
|
|
2816 |
""" |
|
2817 |
if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: |
|
2818 |
raise errors.ProgrammerError("Invalid configuration data %s" % |
|
2819 |
str(unique_id)) |
|
2820 |
|
|
2821 |
# Call the External Storage's create script, |
|
2822 |
# to provision a new Volume inside the External Storage |
|
2823 |
_ExtStorageAction(constants.ES_ACTION_CREATE, unique_id, str(size)) |
|
2824 |
|
|
2825 |
return ExtStorageDevice(unique_id, children, size, params) |
|
2826 |
|
|
2827 |
def Remove(self): |
|
2828 |
"""Remove the extstorage device. |
|
2829 |
|
|
2830 |
""" |
|
2831 |
if not self.minor and not self.Attach(): |
|
2832 |
# The extstorage device doesn't exist. |
|
2833 |
return |
|
2834 |
|
|
2835 |
# First shutdown the device (remove mappings). |
|
2836 |
self.Shutdown() |
|
2837 |
|
|
2838 |
# Call the External Storage's remove script, |
|
2839 |
# to remove the Volume from the External Storage |
|
2840 |
_ExtStorageAction(constants.ES_ACTION_REMOVE, self.unique_id) |
|
2841 |
|
|
2842 |
def Rename(self, new_id): |
|
2843 |
"""Rename this device. |
|
2844 |
|
|
2845 |
""" |
|
2846 |
pass |
|
2847 |
|
|
2848 |
def Attach(self): |
|
2849 |
"""Attach to an existing extstorage device. |
|
2850 |
|
|
2851 |
This method maps the extstorage volume that matches our name with |
|
2852 |
a corresponding block device and then attaches to this device. |
|
2853 |
|
|
2854 |
""" |
|
2855 |
self.attached = False |
|
2856 |
|
|
2857 |
# Call the External Storage's attach script, |
|
2858 |
# to attach an existing Volume to a block device under /dev |
|
2859 |
self.dev_path = _ExtStorageAction(constants.ES_ACTION_ATTACH, |
|
2860 |
self.unique_id) |
|
2861 |
|
|
2862 |
try: |
|
2863 |
st = os.stat(self.dev_path) |
|
2864 |
except OSError, err: |
|
2865 |
logging.error("Error stat()'ing %s: %s", self.dev_path, str(err)) |
|
2866 |
return False |
|
2867 |
|
|
2868 |
if not stat.S_ISBLK(st.st_mode): |
|
2869 |
logging.error("%s is not a block device", self.dev_path) |
|
2870 |
return False |
|
2871 |
|
|
2872 |
self.major = os.major(st.st_rdev) |
|
2873 |
self.minor = os.minor(st.st_rdev) |
|
2874 |
self.attached = True |
|
2875 |
|
|
2876 |
return True |
|
2877 |
|
|
2878 |
def Assemble(self): |
|
2879 |
"""Assemble the device. |
|
2880 |
|
|
2881 |
""" |
|
2882 |
pass |
|
2883 |
|
|
2884 |
def Shutdown(self): |
|
2885 |
"""Shutdown the device. |
|
2886 |
|
|
2887 |
""" |
|
2888 |
if not self.minor and not self.Attach(): |
|
2889 |
# The extstorage device doesn't exist. |
|
2890 |
return |
|
2891 |
|
|
2892 |
# Call the External Storage's detach script, |
|
2893 |
# to detach an existing Volume from it's block device under /dev |
|
2894 |
_ExtStorageAction(constants.ES_ACTION_DETACH, self.unique_id) |
|
2895 |
|
|
2896 |
self.minor = None |
|
2897 |
self.dev_path = None |
|
2898 |
|
|
2899 |
def Open(self, force=False): |
|
2900 |
"""Make the device ready for I/O. |
|
2901 |
|
|
2902 |
""" |
|
2903 |
pass |
|
2904 |
|
|
2905 |
def Close(self): |
|
2906 |
"""Notifies that the device will no longer be used for I/O. |
|
2907 |
|
|
2908 |
""" |
|
2909 |
pass |
|
2910 |
|
|
2911 |
def Grow(self, amount, dryrun, backingstore): |
|
2912 |
"""Grow the Volume. |
|
2913 |
|
|
2914 |
@type amount: integer |
|
2915 |
@param amount: the amount (in mebibytes) to grow with |
|
2916 |
@type dryrun: boolean |
|
2917 |
@param dryrun: whether to execute the operation in simulation mode |
|
2918 |
only, without actually increasing the size |
|
2919 |
|
|
2920 |
""" |
|
2921 |
if not backingstore: |
|
2922 |
return |
|
2923 |
if not self.Attach(): |
|
2924 |
_ThrowError("Can't attach to extstorage device during Grow()") |
|
2925 |
|
|
2926 |
if dryrun: |
|
2927 |
# we do not support dry runs of resize operations for now. |
|
2928 |
return |
|
2929 |
|
|
2930 |
new_size = self.size + amount |
|
2931 |
|
|
2932 |
# Call the External Storage's grow script, |
|
2933 |
# to grow an existing Volume inside the External Storage |
|
2934 |
_ExtStorageAction(constants.ES_ACTION_GROW, self.unique_id, |
|
2935 |
str(self.size), grow=str(new_size)) |
|
2936 |
|
|
2937 |
def SetInfo(self, text): |
|
2938 |
"""Update metadata with info text. |
|
2939 |
|
|
2940 |
""" |
|
2941 |
# Replace invalid characters |
|
2942 |
text = re.sub("^[^A-Za-z0-9_+.]", "_", text) |
|
2943 |
text = re.sub("[^-A-Za-z0-9_+.]", "_", text) |
|
2944 |
|
|
2945 |
# Only up to 128 characters are allowed |
|
2946 |
text = text[:128] |
|
2947 |
|
|
2948 |
# Call the External Storage's setinfo script, |
|
2949 |
# to set metadata for an existing Volume inside the External Storage |
|
2950 |
_ExtStorageAction(constants.ES_ACTION_SETINFO, self.unique_id, |
|
2951 |
metadata=text) |
|
2952 |
|
|
2953 |
|
|
2954 |
def _ExtStorageAction(action, unique_id, size=None, grow=None, metadata=None): |
|
2955 |
"""Take an External Storage action. |
|
2956 |
|
|
2957 |
Take an External Storage action concerning or affecting |
|
2958 |
a specific Volume inside the External Storage. |
|
2959 |
|
|
2960 |
@type action: string |
|
2961 |
@param action: which action to perform. One of: |
|
2962 |
create / remove / grow / attach / detach |
|
2963 |
@type unique_id: tuple (driver, vol_name) |
|
2964 |
@param unique_id: a tuple containing the type of ExtStorage (driver) |
|
2965 |
and the Volume name |
|
2966 |
@type size: integer |
|
2967 |
@param size: the size of the Volume in mebibytes |
|
2968 |
@type grow: integer |
|
2969 |
@param grow: the new size in mebibytes (after grow) |
|
2970 |
@type metadata: string |
|
2971 |
@param metadata: metadata info of the Volume, for use by the provider |
|
2972 |
@rtype: None or a block device path (during attach) |
|
2973 |
|
|
2974 |
""" |
|
2975 |
driver, vol_name = unique_id |
|
2976 |
|
|
2977 |
# Create an External Storage instance of type `driver' |
|
2978 |
status, inst_es = ExtStorageFromDisk(driver) |
|
2979 |
if not status: |
|
2980 |
_ThrowError("%s" % inst_es) |
|
2981 |
|
|
2982 |
# Create the basic environment for the driver's scripts |
|
2983 |
create_env = _ExtStorageEnvironment(unique_id, size, grow, metadata) |
|
2984 |
|
|
2985 |
# Do not use log file for action `attach' as we need |
|
2986 |
# to get the output from RunResult |
|
2987 |
# TODO: find a way to have a log file for attach too |
|
2988 |
logfile = None |
|
2989 |
if action is not constants.ES_ACTION_ATTACH: |
|
2990 |
logfile = _VolumeLogName(action, driver, vol_name) |
|
2991 |
|
|
2992 |
# Make sure the given action results in a valid script |
|
2993 |
if action not in constants.ES_SCRIPTS: |
|
2994 |
_ThrowError("Action '%s' doesn't result in a valid ExtStorage script" % |
|
2995 |
action) |
|
2996 |
|
|
2997 |
# Find out which external script to run according the given action |
|
2998 |
script_name = action + "_script" |
|
2999 |
script = getattr(inst_es, script_name) |
|
3000 |
|
|
3001 |
# Run the external script |
|
3002 |
result = utils.RunCmd([script], env=create_env, |
|
3003 |
cwd=inst_es.path, output=logfile,) |
|
3004 |
if result.failed: |
|
3005 |
logging.error("External storage's %s command '%s' returned" |
|
3006 |
" error: %s, logfile: %s, output: %s", |
|
3007 |
action, result.cmd, result.fail_reason, |
|
3008 |
logfile, result.output) |
|
3009 |
|
|
3010 |
# If logfile is 'None' (during attach), it breaks TailFile |
|
3011 |
# TODO: have a log file for attach too |
|
3012 |
if action is not constants.ES_ACTION_ATTACH: |
|
3013 |
lines = [utils.SafeEncode(val) |
|
3014 |
for val in utils.TailFile(logfile, lines=20)] |
|
3015 |
else: |
|
3016 |
lines = result.output[-20:] |
|
3017 |
|
|
3018 |
_ThrowError("External storage's %s script failed (%s), last" |
|
3019 |
" lines of output:\n%s", |
|
3020 |
action, result.fail_reason, "\n".join(lines)) |
|
3021 |
|
|
3022 |
if action == constants.ES_ACTION_ATTACH: |
|
3023 |
return result.stdout |
|
3024 |
|
|
3025 |
|
|
3026 |
def ExtStorageFromDisk(name, base_dir=None): |
|
3027 |
"""Create an ExtStorage instance from disk. |
|
3028 |
|
|
3029 |
This function will return an ExtStorage instance |
|
3030 |
if the given name is a valid ExtStorage name. |
|
3031 |
|
|
3032 |
@type base_dir: string |
|
3033 |
@keyword base_dir: Base directory containing ExtStorage installations. |
|
3034 |
Defaults to a search in all the ES_SEARCH_PATH dirs. |
|
3035 |
@rtype: tuple |
|
3036 |
@return: True and the ExtStorage instance if we find a valid one, or |
|
3037 |
False and the diagnose message on error |
|
3038 |
|
|
3039 |
""" |
|
3040 |
if base_dir is None: |
|
3041 |
es_base_dir = pathutils.ES_SEARCH_PATH |
|
3042 |
else: |
|
3043 |
es_base_dir = [base_dir] |
|
3044 |
|
|
3045 |
es_dir = utils.FindFile(name, es_base_dir, os.path.isdir) |
|
3046 |
|
|
3047 |
if es_dir is None: |
|
3048 |
return False, ("Directory for External Storage Provider %s not" |
|
3049 |
" found in search path" % name) |
|
3050 |
|
|
3051 |
# ES Files dictionary, we will populate it with the absolute path |
|
3052 |
# names; if the value is True, then it is a required file, otherwise |
|
3053 |
# an optional one |
|
3054 |
es_files = dict.fromkeys(constants.ES_SCRIPTS, True) |
|
3055 |
|
|
3056 |
for filename in es_files: |
|
3057 |
es_files[filename] = utils.PathJoin(es_dir, filename) |
|
3058 |
|
|
3059 |
try: |
|
3060 |
st = os.stat(es_files[filename]) |
|
3061 |
except EnvironmentError, err: |
|
3062 |
return False, ("File '%s' under path '%s' is missing (%s)" % |
|
3063 |
(filename, es_dir, utils.ErrnoOrStr(err))) |
|
3064 |
|
|
3065 |
if not stat.S_ISREG(stat.S_IFMT(st.st_mode)): |
|
3066 |
return False, ("File '%s' under path '%s' is not a regular file" % |
|
3067 |
(filename, es_dir)) |
|
3068 |
|
|
3069 |
if filename in constants.ES_SCRIPTS: |
|
3070 |
if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR: |
|
3071 |
return False, ("File '%s' under path '%s' is not executable" % |
|
3072 |
(filename, es_dir)) |
|
3073 |
|
|
3074 |
es_obj = \ |
|
3075 |
objects.ExtStorage(name=name, path=es_dir, |
|
3076 |
create_script=es_files[constants.ES_SCRIPT_CREATE], |
|
3077 |
remove_script=es_files[constants.ES_SCRIPT_REMOVE], |
|
3078 |
grow_script=es_files[constants.ES_SCRIPT_GROW], |
|
3079 |
attach_script=es_files[constants.ES_SCRIPT_ATTACH], |
|
3080 |
detach_script=es_files[constants.ES_SCRIPT_DETACH], |
|
3081 |
setinfo_script=es_files[constants.ES_SCRIPT_SETINFO]) |
|
3082 |
return True, es_obj |
|
3083 |
|
|
3084 |
|
|
3085 |
def _ExtStorageEnvironment(unique_id, size=None, grow=None, metadata=None): |
|
3086 |
"""Calculate the environment for an External Storage script. |
|
3087 |
|
|
3088 |
@type unique_id: tuple (driver, vol_name) |
|
3089 |
@param unique_id: ExtStorage pool and name of the Volume |
|
3090 |
@type size: string |
|
3091 |
@param size: size of the Volume (in mebibytes) |
|
3092 |
@type grow: string |
|
3093 |
@param grow: new size of Volume after grow (in mebibytes) |
|
3094 |
@type metadata: string |
|
3095 |
@param metadata: metadata info of the Volume |
|
3096 |
@rtype: dict |
|
3097 |
@return: dict of environment variables |
|
3098 |
|
|
3099 |
""" |
|
3100 |
vol_name = unique_id[1] |
|
3101 |
|
|
3102 |
result = {} |
|
3103 |
result["VOL_NAME"] = vol_name |
|
3104 |
|
|
3105 |
if size is not None: |
|
3106 |
result["VOL_SIZE"] = size |
|
3107 |
|
|
3108 |
if grow is not None: |
|
3109 |
result["VOL_NEW_SIZE"] = grow |
|
3110 |
|
|
3111 |
if metadata is not None: |
|
3112 |
result["VOL_METADATA"] = metadata |
|
3113 |
|
|
3114 |
return result |
|
3115 |
|
|
3116 |
|
|
3117 |
def _VolumeLogName(kind, es_name, volume): |
|
3118 |
"""Compute the ExtStorage log filename for a given Volume and operation. |
|
3119 |
|
|
3120 |
@type kind: string |
|
3121 |
@param kind: the operation type (e.g. create, remove etc.) |
|
3122 |
@type es_name: string |
|
3123 |
@param es_name: the ExtStorage name |
|
3124 |
@type volume: string |
|
3125 |
@param volume: the name of the Volume inside the External Storage |
|
3126 |
|
|
3127 |
""" |
|
3128 |
# Check if the extstorage log dir is a valid dir |
|
3129 |
if not os.path.isdir(pathutils.LOG_ES_DIR): |
|
3130 |
_ThrowError("Cannot find log directory: %s", pathutils.LOG_ES_DIR) |
|
3131 |
|
|
3132 |
# TODO: Use tempfile.mkstemp to create unique filename |
|
3133 |
base = ("%s-%s-%s-%s.log" % |
|
3134 |
(kind, es_name, volume, utils.TimestampForFilename())) |
|
3135 |
return utils.PathJoin(pathutils.LOG_ES_DIR, base) |
|
3136 |
|
|
3137 |
|
|
2789 | 3138 |
DEV_MAP = { |
2790 | 3139 |
constants.LD_LV: LogicalVolume, |
2791 | 3140 |
constants.LD_DRBD8: DRBD8, |
2792 | 3141 |
constants.LD_BLOCKDEV: PersistentBlockDevice, |
2793 | 3142 |
constants.LD_RBD: RADOSBlockDevice, |
3143 |
constants.LD_EXT: ExtStorageDevice, |
|
2794 | 3144 |
} |
2795 | 3145 |
|
2796 | 3146 |
if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE: |
Also available in: Unified diff