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