Revision 620a85fd

b/lib/cmdlib.py
2618 2618
  """
2619 2619
  _OP_REQP = ["nodes", "storage_type", "output_fields"]
2620 2620
  REQ_BGL = False
2621
  _FIELDS_STATIC = utils.FieldSet("node")
2621
  _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
2622 2622

  
2623 2623
  def ExpandNames(self):
2624 2624
    storage_type = self.op.storage_type
2625 2625

  
2626
    if storage_type not in constants.VALID_STORAGE_FIELDS:
2626
    if storage_type not in constants.VALID_STORAGE_TYPES:
2627 2627
      raise errors.OpPrereqError("Unknown storage type: %s" % storage_type)
2628 2628

  
2629
    dynamic_fields = constants.VALID_STORAGE_FIELDS[storage_type]
2630

  
2631 2629
    _CheckOutputFields(static=self._FIELDS_STATIC,
2632
                       dynamic=utils.FieldSet(*dynamic_fields),
2630
                       dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
2633 2631
                       selected=self.op.output_fields)
2634 2632

  
2635 2633
    self.needed_locks = {}
......
2661 2659
    else:
2662 2660
      fields = [constants.SF_NAME] + self.op.output_fields
2663 2661

  
2664
    # Never ask for node as it's only known to the LU
2665
    while "node" in fields:
2666
      fields.remove("node")
2662
    # Never ask for node or type as it's only known to the LU
2663
    for extra in [constants.SF_NODE, constants.SF_TYPE]:
2664
      while extra in fields:
2665
        fields.remove(extra)
2667 2666

  
2668 2667
    field_idx = dict([(name, idx) for (idx, name) in enumerate(fields)])
2669 2668
    name_idx = field_idx[constants.SF_NAME]
......
2693 2692
        out = []
2694 2693

  
2695 2694
        for field in self.op.output_fields:
2696
          if field == "node":
2695
          if field == constants.SF_NODE:
2697 2696
            val = node
2697
          elif field == constants.SF_TYPE:
2698
            val = self.op.storage_type
2698 2699
          elif field in field_idx:
2699 2700
            val = row[field_idx[field]]
2700 2701
          else:
......
2722 2723
    self.op.node_name = node_name
2723 2724

  
2724 2725
    storage_type = self.op.storage_type
2725
    if storage_type not in constants.VALID_STORAGE_FIELDS:
2726
    if storage_type not in constants.VALID_STORAGE_TYPES:
2726 2727
      raise errors.OpPrereqError("Unknown storage type: %s" % storage_type)
2727 2728

  
2728 2729
  def ExpandNames(self):
b/lib/constants.py
196 196
ST_LVM_VG = "lvm-vg"
197 197

  
198 198
# Storage fields
199
# first two are valid in LU context only, not passed to backend
200
SF_NODE = "node"
201
SF_TYPE = "type"
202
# and the rest are valid in backend
199 203
SF_NAME = "name"
200 204
SF_SIZE = "size"
201 205
SF_FREE = "free"
......
206 210
SO_FIX_CONSISTENCY = "fix-consistency"
207 211

  
208 212
# Available fields per storage type
209
VALID_STORAGE_FIELDS = {
210
  ST_FILE: frozenset([SF_NAME, SF_USED, SF_FREE]),
211
  ST_LVM_PV: frozenset([SF_NAME, SF_SIZE, SF_USED, SF_FREE, SF_ALLOCATABLE]),
212
  ST_LVM_VG: frozenset([SF_NAME, SF_SIZE]),
213
  }
213
VALID_STORAGE_FIELDS = frozenset([SF_NAME, SF_TYPE, SF_SIZE,
214
                                  SF_USED, SF_FREE, SF_ALLOCATABLE])
215

  
216
VALID_STORAGE_TYPES = frozenset([ST_FILE, ST_LVM_PV, ST_LVM_VG])
214 217

  
215 218
MODIFIABLE_STORAGE_FIELDS = {
216 219
  ST_LVM_PV: frozenset([SF_ALLOCATABLE]),
b/lib/storage.py
125 125
    else:
126 126
      dirsize = None
127 127

  
128
    if constants.SF_FREE in fields:
129
      fsfree = utils.GetFreeFilesystemSpace(path)
128
    if constants.SF_FREE in fields or constants.SF_SIZE in fields:
129
      fsstats = utils.GetFilesystemStats(path)
130 130
    else:
131
      fsfree = None
131
      fsstats = None
132 132

  
133 133
    # Make sure to update constants.VALID_STORAGE_FIELDS when changing fields.
134 134
    for field_name in fields:
......
139 139
        values.append(dirsize)
140 140

  
141 141
      elif field_name == constants.SF_FREE:
142
        values.append(fsfree)
142
        values.append(fsstats[1])
143

  
144
      elif field_name == constants.SF_SIZE:
145
        values.append(fsstats[0])
146

  
147
      elif field_name == constants.SF_ALLOCATABLE:
148
        values.append(True)
143 149

  
144 150
      else:
145 151
        raise errors.StorageError("Unknown field: %r" % field_name)
......
150 156
class _LvmBase(_Base):
151 157
  """Base class for LVM storage containers.
152 158

  
159
  @cvar LIST_FIELDS: list of tuples consisting of three elements: SF_*
160
      constants, lvm command output fields (list), and conversion
161
      function or static value (for static value, the lvm output field
162
      can be an empty list)
163

  
153 164
  """
154 165
  LIST_SEP = "|"
155 166
  LIST_COMMAND = None
......
200 211
      except IndexError:
201 212
        raise errors.StorageError("Unknown field: %r" % field_name)
202 213

  
203
      (_, lvm_name, _) = fields_def[idx]
214
      (_, lvm_names, _) = fields_def[idx]
204 215

  
205
      lvm_fields.append(lvm_name)
216
      lvm_fields.extend(lvm_names)
206 217

  
207 218
    return utils.UniqueSequence(lvm_fields)
208 219

  
......
231 242
      row = []
232 243

  
233 244
      for field_name in wanted_field_names:
234
        (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
245
        (_, lvm_names, mapper) = fields_def[field_to_idx[field_name]]
235 246

  
236
        value = raw_data[lvm_name_to_idx[lvm_name]]
247
        values = [raw_data[lvm_name_to_idx[i]] for i in lvm_names]
237 248

  
238
        if convert_fn:
239
          value = convert_fn(value)
249
        if callable(mapper):
250
          # we got a function, call it with all the declared fields
251
          val = mapper(*values)
252
        elif len(values) == 1:
253
          # we don't have a function, but we had a single field
254
          # declared, pass it unchanged
255
          val = values[0]
256
        else:
257
          # let's make sure there are no fields declared (cannot map >
258
          # 1 field without a function)
259
          assert not values, "LVM storage has multi-fields without a function"
260
          val = mapper
240 261

  
241
        row.append(value)
262
        row.append(val)
242 263

  
243 264
      data.append(row)
244 265

  
......
297 318
      fields = line.strip().split(sep)
298 319

  
299 320
      if len(fields) != fieldcount:
321
        logging.warning("Invalid line returned from lvm command: %s", line)
300 322
        continue
301 323

  
302 324
      yield fields
......
318 340
  # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
319 341
  # definitions.
320 342
  LIST_FIELDS = [
321
    (constants.SF_NAME, "pv_name", None),
322
    (constants.SF_SIZE, "pv_size", _ParseSize),
323
    (constants.SF_USED, "pv_used", _ParseSize),
324
    (constants.SF_FREE, "pv_free", _ParseSize),
325
    (constants.SF_ALLOCATABLE, "pv_attr", _GetAllocatable),
343
    (constants.SF_NAME, ["pv_name"], None),
344
    (constants.SF_SIZE, ["pv_size"], _ParseSize),
345
    (constants.SF_USED, ["pv_used"], _ParseSize),
346
    (constants.SF_FREE, ["pv_free"], _ParseSize),
347
    (constants.SF_ALLOCATABLE, ["pv_attr"], _GetAllocatable),
326 348
    ]
327 349

  
328 350
  def _SetAllocatable(self, name, allocatable):
......
372 394
  # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
373 395
  # definitions.
374 396
  LIST_FIELDS = [
375
    (constants.SF_NAME, "vg_name", None),
376
    (constants.SF_SIZE, "vg_size", _ParseSize),
397
    (constants.SF_NAME, ["vg_name"], None),
398
    (constants.SF_SIZE, ["vg_size"], _ParseSize),
399
    (constants.SF_FREE, ["vg_free"], _ParseSize),
400
    (constants.SF_USED, ["vg_size", "vg_free"],
401
     lambda x, y: _ParseSize(x) - _ParseSize(y)),
402
    (constants.SF_ALLOCATABLE, [], True),
377 403
    ]
378 404

  
379 405
  def _RemoveMissing(self, name):
b/lib/utils.py
1890 1890
  return BytesToMebibyte(size)
1891 1891

  
1892 1892

  
1893
def GetFreeFilesystemSpace(path):
1894
  """Returns the free space on a filesystem.
1893
def GetFilesystemStats(path):
1894
  """Returns the total and free space on a filesystem.
1895 1895

  
1896 1896
  @type path: string
1897 1897
  @param path: Path on filesystem to be examined
1898 1898
  @rtype: int
1899
  @return: Free space in mebibytes
1899
  @return: tuple of (Total space, Free space) in mebibytes
1900 1900

  
1901 1901
  """
1902 1902
  st = os.statvfs(path)
1903 1903

  
1904
  return BytesToMebibyte(st.f_bavail * st.f_frsize)
1904
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
1905
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
1906
  return (tsize, fsize)
1905 1907

  
1906 1908

  
1907 1909
def LockedMethod(fn):
b/man/gnt-node.sgml
794 794
      <para>
795 795
        The <option>--storage-type</option> option can be used to choose a
796 796
        storage unit type. Possible choices are <literal>lvm-pv</literal>,
797
        <literal>lvm-vg</literal> or <literal>file</literal>. Depending on the
798
        storage type, the available output fields change.
797
        <literal>lvm-vg</literal> or <literal>file</literal>.
799 798
      </para>
800 799

  
801 800
      <para>
......
809 808
            </listitem>
810 809
          </varlistentry>
811 810
          <varlistentry>
811
            <term>type</term>
812
            <listitem>
813
              <simpara>the type of the storage unit (currently just
814
              what is passed in via
815
              <option>--storage-type</option>)</simpara>
816
            </listitem>
817
          </varlistentry>
818
          <varlistentry>
812 819
            <term>name</term>
813 820
            <listitem>
814
              <simpara>the physical drive name</simpara>
821
              <simpara>the path/identifier of the storage unit</simpara>
815 822
            </listitem>
816 823
          </varlistentry>
817 824
          <varlistentry>
818 825
            <term>size</term>
819 826
            <listitem>
820 827
              <simpara>
821
                the physical drive size
822
                (<literal>lvm-pv</literal> and <literal>lvm-vg</literal> only)
828
                total size of the unit; for the file type see a note below
823 829
              </simpara>
824 830
            </listitem>
825 831
          </varlistentry>
......
827 833
            <term>used</term>
828 834
            <listitem>
829 835
              <simpara>
830
                used disk space
831
                (<literal>lvm-pv</literal> and <literal>file</literal> only)
836
                used space in the unit; for the file type see a note below
832 837
              </simpara>
833 838
            </listitem>
834 839
          </varlistentry>
......
837 842
            <listitem>
838 843
              <simpara>
839 844
                available disk space
840
                (<literal>lvm-pv</literal> and <literal>file</literal> only)
841 845
              </simpara>
842 846
            </listitem>
843 847
          </varlistentry>
......
845 849
            <term>allocatable</term>
846 850
            <listitem>
847 851
              <simpara>
848
                whether physical volume is allocatable
849
                (<literal>lvm-pv</literal> only)
852
                whether we the unit is available for allocation
853
                (only <literal>lvm-pv</literal> can change this
854
                setting, the other types always report true)
850 855
              </simpara>
851 856
            </listitem>
852 857
          </varlistentry>
......
854 859
      </para>
855 860

  
856 861
      <para>
862
        Note that for the <quote>file</quote> type, the total disk
863
        space might not equal to the sum of used and free, due to the
864
        method Ganeti uses to compute each of them. The total and free
865
        values are computed as the total and free space values for the
866
        filesystem to which the directory belongs, but the used space
867
        is computed from the used space under that directory
868
        <emphasis>only</emphasis>, which might not be necessarily the
869
        root of the filesystem, and as such there could be files
870
        outside the file storage directory using disk space and
871
        causing a mismatch in the values.
872
      </para>
873

  
874
      <para>
857 875
        Example:
858 876
        <screen>
859
# gnt-node list-storage node5.example.com
860
Node              Name        Size Used   Free
861
node5.example.com /dev/sda7 673.8G   0M 673.8G
862
node5.example.com /dev/sdb1 698.6G 1.3G 697.4G
877
node1# gnt-node list-storage node2
878
Node  Type   Name        Size Used   Free Allocatable
879
node2 lvm-pv /dev/sda7 673.8G 1.5G 672.3G Y
880
node2 lvm-pv /dev/sdb1 698.6G   0M 698.6G Y
863 881
        </screen>
864 882
      </para>
865 883
    </refsect2>
b/scripts/gnt-node
41 41
  "pinst_cnt", "sinst_cnt",
42 42
  ]
43 43

  
44

  
45
#: default list of field for L{ListStorage}
46
_LIST_STOR_DEF_FIELDS = [
47
  constants.SF_NODE,
48
  constants.SF_TYPE,
49
  constants.SF_NAME,
50
  constants.SF_SIZE,
51
  constants.SF_USED,
52
  constants.SF_FREE,
53
  constants.SF_ALLOCATABLE,
54
  ]
55

  
56

  
44 57
#: headers (and full field list for L{ListNodes}
45 58
_LIST_HEADERS = {
46 59
  "name": "Node", "pinst_cnt": "Pinst", "sinst_cnt": "Sinst",
......
59 72
  "ctime": "CTime", "mtime": "MTime", "uuid": "UUID"
60 73
  }
61 74

  
75

  
76
#: headers (and full field list for L{ListStorage}
77
_LIST_STOR_HEADERS = {
78
  constants.SF_NODE: "Node",
79
  constants.SF_TYPE: "Type",
80
  constants.SF_NAME: "Name",
81
  constants.SF_SIZE: "Size",
82
  constants.SF_USED: "Used",
83
  constants.SF_FREE: "Free",
84
  constants.SF_ALLOCATABLE: "Allocatable",
85
  }
86

  
87

  
62 88
#: User-facing storage unit types
63 89
_USER_STORAGE_TYPE = {
64 90
  constants.ST_FILE: "file",
......
476 502

  
477 503
  storage_type = ConvertStorageType(opts.user_storage_type)
478 504

  
479
  default_fields = {
480
    constants.ST_FILE: [
481
      constants.SF_NAME,
482
      constants.SF_USED,
483
      constants.SF_FREE,
484
      ],
485
    constants.ST_LVM_PV: [
486
      constants.SF_NAME,
487
      constants.SF_SIZE,
488
      constants.SF_USED,
489
      constants.SF_FREE,
490
      ],
491
    constants.ST_LVM_VG: [
492
      constants.SF_NAME,
493
      constants.SF_SIZE,
494
      ],
495
  }
496

  
497
  def_fields = ["node"] + default_fields[storage_type]
498 505
  if opts.output is None:
499
    selected_fields = def_fields
506
    selected_fields = _LIST_STOR_DEF_FIELDS
500 507
  elif opts.output.startswith("+"):
501
    selected_fields = def_fields + opts.output[1:].split(",")
508
    selected_fields = _LIST_STOR_DEF_FIELDS + opts.output[1:].split(",")
502 509
  else:
503 510
    selected_fields = opts.output.split(",")
504 511

  
......
509 516

  
510 517
  if not opts.no_headers:
511 518
    headers = {
512
      "node": "Node",
519
      constants.SF_NODE: "Node",
520
      constants.SF_TYPE: "Type",
513 521
      constants.SF_NAME: "Name",
514 522
      constants.SF_SIZE: "Size",
515 523
      constants.SF_USED: "Used",
......
568 576
                                     name=volume_name,
569 577
                                     changes=changes)
570 578
    SubmitOpCode(op)
579
  else:
580
    ToStderr("No changes to perform, exiting.")
571 581

  
572 582

  
573 583
def RepairStorage(opts, args):
......
683 693
  'list-storage': (
684 694
    ListStorage, ARGS_MANY_NODES,
685 695
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT],
686
    "[<node_name>...]", "List physical volumes on node(s)"),
696
    "[<node_name>...]", "List physical volumes on node(s). The available"
697
    " fields are (see the man page for details): %s." %
698
    (", ".join(_LIST_STOR_HEADERS))),
687 699
  'modify-storage': (
688 700
    ModifyStorage,
689 701
    [ArgNode(min=1, max=1),

Also available in: Unified diff