Revision 8106dd64

b/Makefile.am
421 421
	lib/storage/drbd.py \
422 422
	lib/storage/drbd_info.py \
423 423
	lib/storage/drbd_cmdgen.py \
424
	lib/storage/filestorage.py
424
	lib/storage/filestorage.py \
425
	lib/storage/gluster.py
425 426

  
426 427
rapi_PYTHON = \
427 428
	lib/rapi/__init__.py \
......
1445 1446
	test/py/ganeti.storage.container_unittest.py \
1446 1447
	test/py/ganeti.storage.drbd_unittest.py \
1447 1448
	test/py/ganeti.storage.filestorage_unittest.py \
1449
	test/py/ganeti.storage.gluster_unittest.py \
1448 1450
	test/py/ganeti.tools.burnin_unittest.py \
1449 1451
	test/py/ganeti.tools.ensure_dirs_unittest.py \
1450 1452
	test/py/ganeti.tools.node_daemon_setup_unittest.py \
b/lib/cmdlib/instance.py
849 849
      # build the full file storage dir path
850 850
      joinargs = []
851 851

  
852
      if self.op.disk_template == constants.DT_SHARED_FILE:
852
      if self.op.disk_template in (constants.DT_SHARED_FILE,
853
                                   constants.DT_GLUSTER):
853 854
        get_fsd_fn = self.cfg.GetSharedFileStorageDir
854 855
      else:
855 856
        get_fsd_fn = self.cfg.GetFileStorageDir
......
1754 1755

  
1755 1756
    for idx, dsk in enumerate(self.instance.disks):
1756 1757
      if dsk.dev_type not in (constants.DT_PLAIN, constants.DT_FILE,
1757
                              constants.DT_SHARED_FILE):
1758
                              constants.DT_SHARED_FILE, constants.DT_GLUSTER):
1758 1759
        raise errors.OpPrereqError("Instance disk %d has a complex layout,"
1759 1760
                                   " cannot copy" % idx, errors.ECODE_STATE)
1760 1761

  
b/lib/cmdlib/instance_storage.py
285 285
    constants.DT_DRBD8: _compute(disks, constants.DRBD_META_SIZE),
286 286
    constants.DT_FILE: {},
287 287
    constants.DT_SHARED_FILE: {},
288
    constants.DT_GLUSTER: {},
288 289
    }
289 290

  
290 291
  if disk_template not in req_size_dict:
b/lib/masterd/instance.py
1648 1648
      sum(d[constants.IDISK_SIZE] + constants.DRBD_META_SIZE for d in disks),
1649 1649
    constants.DT_FILE: sum(d[constants.IDISK_SIZE] for d in disks),
1650 1650
    constants.DT_SHARED_FILE: sum(d[constants.IDISK_SIZE] for d in disks),
1651
    constants.DT_GLUSTER: sum(d[constants.IDISK_SIZE] for d in disks),
1651 1652
    constants.DT_BLOCK: 0,
1652 1653
    constants.DT_RBD: sum(d[constants.IDISK_SIZE] for d in disks),
1653 1654
    constants.DT_EXT: sum(d[constants.IDISK_SIZE] for d in disks),
b/lib/objects.py
586 586
    """
587 587
    if self.dev_type in [constants.DT_PLAIN, constants.DT_FILE,
588 588
                         constants.DT_BLOCK, constants.DT_RBD,
589
                         constants.DT_EXT, constants.DT_SHARED_FILE]:
589
                         constants.DT_EXT, constants.DT_SHARED_FILE,
590
                         constants.DT_GLUSTER]:
590 591
      result = [node_uuid]
591 592
    elif self.dev_type in constants.DTS_DRBD:
592 593
      result = [self.logical_id[0], self.logical_id[1]]
......
663 664
    """
664 665
    if self.dev_type in (constants.DT_PLAIN, constants.DT_FILE,
665 666
                         constants.DT_RBD, constants.DT_EXT,
666
                         constants.DT_SHARED_FILE):
667
                         constants.DT_SHARED_FILE, constants.DT_GLUSTER):
667 668
      self.size += amount
668 669
    elif self.dev_type == constants.DT_DRBD8:
669 670
      if self.children:
b/lib/storage/bdev.py
39 39
from ganeti.storage import base
40 40
from ganeti.storage import drbd
41 41
from ganeti.storage.filestorage import FileStorage
42
from ganeti.storage.gluster import GlusterStorage
42 43

  
43 44

  
44 45
class RbdShowmappedJsonError(Exception):
......
1667 1668
  constants.DT_EXT: ExtStorageDevice,
1668 1669
  constants.DT_FILE: FileStorage,
1669 1670
  constants.DT_SHARED_FILE: FileStorage,
1671
  constants.DT_GLUSTER: GlusterStorage,
1670 1672
}
1671 1673
"""Map disk types to disk type classes.
1672 1674

  
b/lib/storage/gluster.py
1
#
2
#
3

  
4
# Copyright (C) 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21
"""Gluster storage class.
22

  
23
This class is very similar to FileStorage, given that Gluster when mounted
24
behaves essentially like a regular file system. Unlike RBD, there are no
25
special provisions for block device abstractions (yet).
26

  
27
"""
28
from ganeti import errors
29

  
30
from ganeti.storage import base
31
from ganeti.storage.filestorage import FileDeviceHelper
32

  
33

  
34
class GlusterStorage(base.BlockDev):
35
  """File device using the Gluster backend.
36

  
37
  This class represents a file storage backend device stored on Gluster. The
38
  system administrator must mount the Gluster device himself at boot time before
39
  Ganeti is run.
40

  
41
  The unique_id for the file device is a (file_driver, file_path) tuple.
42

  
43
  """
44
  def __init__(self, unique_id, children, size, params, dyn_params):
45
    """Initalizes a file device backend.
46

  
47
    """
48
    if children:
49
      base.ThrowError("Invalid setup for file device")
50
    super(GlusterStorage, self).__init__(unique_id, children, size, params,
51
                                         dyn_params)
52
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
53
      raise ValueError("Invalid configuration data %s" % str(unique_id))
54
    self.driver = unique_id[0]
55
    self.dev_path = unique_id[1]
56

  
57
    self.file = FileDeviceHelper(self.dev_path)
58

  
59
    self.Attach()
60

  
61
  def Assemble(self):
62
    """Assemble the device.
63

  
64
    Checks whether the file device exists, raises BlockDeviceError otherwise.
65

  
66
    """
67
    assert self.attached, "Gluster file assembled without being attached"
68
    self.file.Exists(assert_exists=True)
69

  
70
  def Shutdown(self):
71
    """Shutdown the device.
72

  
73
    """
74

  
75
    self.file = None
76
    self.dev_path = None
77
    self.attached = False
78

  
79
  def Open(self, force=False):
80
    """Make the device ready for I/O.
81

  
82
    This is a no-op for the file type.
83

  
84
    """
85
    assert self.attached, "Gluster file opened without being attached"
86

  
87
  def Close(self):
88
    """Notifies that the device will no longer be used for I/O.
89

  
90
    This is a no-op for the file type.
91
    """
92
    pass
93

  
94
  def Remove(self):
95
    """Remove the file backing the block device.
96

  
97
    @rtype: boolean
98
    @return: True if the removal was successful
99

  
100
    """
101
    return self.file.Remove()
102

  
103
  def Rename(self, new_id):
104
    """Renames the file.
105

  
106
    """
107
    # TODO: implement rename for file-based storage
108
    base.ThrowError("Rename is not supported for Gluster storage")
109

  
110
  def Grow(self, amount, dryrun, backingstore, excl_stor):
111
    """Grow the file
112

  
113
    @param amount: the amount (in mebibytes) to grow with
114

  
115
    """
116
    self.file.Grow(amount, dryrun, backingstore, excl_stor)
117

  
118
  def Attach(self):
119
    """Attach to an existing file.
120

  
121
    Check if this file already exists.
122

  
123
    @rtype: boolean
124
    @return: True if file exists
125

  
126
    """
127
    self.attached = self.file.Exists()
128
    return self.attached
129

  
130
  def GetActualSize(self):
131
    """Return the actual disk size.
132

  
133
    @note: the device needs to be active when this is called
134

  
135
    """
136
    return self.file.Size()
137

  
138
  @classmethod
139
  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
140
             dyn_params):
141
    """Create a new file.
142

  
143
    @param size: the size of file in MiB
144

  
145
    @rtype: L{bdev.FileStorage}
146
    @return: an instance of FileStorage
147

  
148
    """
149
    if excl_stor:
150
      raise errors.ProgrammerError("FileStorage device requested with"
151
                                   " exclusive_storage")
152
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
153
      raise ValueError("Invalid configuration data %s" % str(unique_id))
154

  
155
    dev_path = unique_id[1]
156

  
157
    FileDeviceHelper.Create(dev_path, size)
158
    return GlusterStorage(unique_id, children, size, params, dyn_params)
b/lib/tools/burnin.py
61 61
  constants.DT_SHARED_FILE,
62 62
  constants.DT_EXT,
63 63
  constants.DT_RBD,
64
  constants.DT_GLUSTER
64 65
  ])
65 66

  
66 67
_SUPPORTED_DISK_TEMPLATES = compat.UniqueFrozenset([
......
71 72
  constants.DT_PLAIN,
72 73
  constants.DT_RBD,
73 74
  constants.DT_SHARED_FILE,
75
  constants.DT_GLUSTER
74 76
  ])
75 77

  
76 78
#: Disk templates for which import/export is tested
......
78 80
  constants.DT_DISKLESS,
79 81
  constants.DT_FILE,
80 82
  constants.DT_SHARED_FILE,
83
  constants.DT_GLUSTER
81 84
  ]))
82 85

  
83 86

  
b/lib/utils/storage.py
91 91
    return (storage_type, cluster.file_storage_dir)
92 92
  elif disk_template == constants.DT_SHARED_FILE:
93 93
    return (storage_type, cluster.shared_file_storage_dir)
94
  elif disk_template == constants.DT_GLUSTER:
95
    return (storage_type, constants.GLUSTER_MOUNTPOINT)
94 96
  else:
95 97
    return (storage_type, None)
96 98

  
b/src/Ganeti/Constants.hs
789 789
dtExt :: String
790 790
dtExt = Types.diskTemplateToRaw DTExt
791 791

  
792
dtGluster :: String
793
dtGluster = Types.diskTemplateToRaw DTGluster
794

  
792 795
-- | This is used to order determine the default disk template when
793 796
-- the list of enabled disk templates is inferred from the current
794 797
-- state of the cluster.  This only happens on an upgrade from a
......
797 800
diskTemplatePreference :: [String]
798 801
diskTemplatePreference =
799 802
  map Types.diskTemplateToRaw
800
  [DTBlock, DTDiskless, DTDrbd8, DTExt, DTFile, DTPlain, DTRbd, DTSharedFile]
803
  [DTBlock, DTDiskless, DTDrbd8, DTExt, DTFile,
804
   DTPlain, DTRbd, DTSharedFile, DTGluster]
801 805

  
802 806
diskTemplates :: FrozenSet String
803 807
diskTemplates = ConstantUtils.mkSet $ map Types.diskTemplateToRaw [minBound..]
......
818 822
   (DTFile, StorageFile),
819 823
   (DTDiskless, StorageDiskless),
820 824
   (DTPlain, StorageLvmVg),
821
   (DTRbd, StorageRados)]
825
   (DTRbd, StorageRados),
826
   (DTGluster, StorageFile)]
822 827

  
823 828
-- | The set of network-mirrored disk templates
824 829
dtsIntMirror :: FrozenSet String
......
828 833
dtsExtMirror :: FrozenSet String
829 834
dtsExtMirror =
830 835
  ConstantUtils.mkSet $
831
  map Types.diskTemplateToRaw [DTDiskless, DTBlock, DTExt, DTSharedFile, DTRbd]
836
  map Types.diskTemplateToRaw
837
  [DTDiskless, DTBlock, DTExt, DTSharedFile, DTRbd, DTGluster]
832 838

  
833 839
-- | The set of non-lvm-based disk templates
834 840
dtsNotLvm :: FrozenSet String
835 841
dtsNotLvm =
836 842
  ConstantUtils.mkSet $
837 843
  map Types.diskTemplateToRaw
838
  [DTSharedFile, DTDiskless, DTBlock, DTExt, DTFile, DTRbd]
844
  [DTSharedFile, DTDiskless, DTBlock, DTExt, DTFile, DTRbd, DTGluster]
839 845

  
840 846
-- | The set of disk templates which can be grown
841 847
dtsGrowable :: FrozenSet String
842 848
dtsGrowable =
843 849
  ConstantUtils.mkSet $
844 850
  map Types.diskTemplateToRaw
845
  [DTSharedFile, DTDrbd8, DTPlain, DTExt, DTFile, DTRbd]
851
  [DTSharedFile, DTDrbd8, DTPlain, DTExt, DTFile, DTRbd, DTGluster]
846 852

  
847 853
-- | The set of disk templates that allow adoption
848 854
dtsMayAdopt :: FrozenSet String
......
860 866
-- | The set of file based disk templates
861 867
dtsFilebased :: FrozenSet String
862 868
dtsFilebased =
863
  ConstantUtils.mkSet $ map Types.diskTemplateToRaw [DTSharedFile, DTFile]
869
  ConstantUtils.mkSet $ map Types.diskTemplateToRaw
870
  [DTSharedFile, DTFile, DTGluster]
864 871

  
865 872
-- | The set of disk templates that can be moved by copying
866 873
--
......
878 885
dtsNoFreeSpaceCheck :: FrozenSet String
879 886
dtsNoFreeSpaceCheck =
880 887
  ConstantUtils.mkSet $
881
  map Types.diskTemplateToRaw [DTExt, DTSharedFile, DTFile, DTRbd]
888
  map Types.diskTemplateToRaw [DTExt, DTSharedFile, DTFile, DTRbd, DTGluster]
882 889

  
883 890
dtsBlock :: FrozenSet String
884 891
dtsBlock =
......
3763 3770
            , (ldpAccess, PyValueEx diskKernelspace)
3764 3771
            ])
3765 3772
  , (DTSharedFile, Map.empty)
3773
  , (DTGluster, Map.empty)
3766 3774
  ]
3767 3775

  
3768 3776
diskDtDefaults :: Map DiskTemplate (Map String PyValueEx)
......
3795 3803
                   , (rbdAccess, PyValueEx diskKernelspace)
3796 3804
                   ])
3797 3805
  , (DTSharedFile, Map.empty)
3806
  , (DTGluster, Map.empty)
3798 3807
  ]
3799 3808

  
3800 3809
niccDefaults :: Map String PyValueEx
......
4577 4586

  
4578 4587
jstoreJobsPerArchiveDirectory :: Int
4579 4588
jstoreJobsPerArchiveDirectory = 10000
4589

  
4590
-- * Gluster settings
4591

  
4592
-- | Where Ganeti should manage Gluster volume mountpoints
4593
glusterMountpoint :: String
4594
glusterMountpoint = "/var/run/ganeti/gluster"
b/src/Ganeti/HTools/Cluster.hs
982 982
                   failOnSecondaryChange mode dt >>
983 983
                   evacOneNodeOnly nl il inst gdx avail_nodes
984 984

  
985
nodeEvacInstance nl il mode inst@(Instance.Instance
986
                                  {Instance.diskTemplate = dt@DTGluster})
987
                 gdx avail_nodes =
988
                   failOnSecondaryChange mode dt >>
989
                   evacOneNodeOnly nl il inst gdx avail_nodes
990

  
985 991
nodeEvacInstance nl il ChangePrimary
986 992
                 inst@(Instance.Instance {Instance.diskTemplate = DTDrbd8})
987 993
                 _ _ =
b/src/Ganeti/HTools/Instance.hs
157 157
  [ T.DTDrbd8
158 158
  , T.DTBlock
159 159
  , T.DTSharedFile
160
  , T.DTGluster
160 161
  , T.DTRbd
161 162
  , T.DTExt
162 163
  ]
b/src/Ganeti/HTools/Types.hs
140 140
templateMirrorType DTDrbd8      = MirrorInternal
141 141
templateMirrorType DTRbd        = MirrorExternal
142 142
templateMirrorType DTExt        = MirrorExternal
143
templateMirrorType DTGluster    = MirrorExternal
143 144

  
144 145
-- | The resource spec type.
145 146
data RSpec = RSpec
b/src/Ganeti/Objects.hs
353 353
          path'   <- readJSON path
354 354
          return $ LIDSharedFile driver' path'
355 355
        _ -> fail "Can't read logical_id for shared file type"
356
    DTGluster ->
357
      case lid of
358
        JSArray [driver, path] -> do
359
          driver' <- readJSON driver
360
          path'   <- readJSON path
361
          return $ LIDSharedFile driver' path'
362
        _ -> fail "Can't read logical_id for shared file type"
356 363
    DTBlock ->
357 364
      case lid of
358 365
        JSArray [driver, path] -> do
b/src/Ganeti/Storage/Utils.hs
31 31
import Ganeti.Config
32 32
import Ganeti.Objects
33 33
import Ganeti.Types
34
import Ganeti.Constants
34 35
import qualified Ganeti.Types as T
35 36

  
36 37
import Control.Monad
b/src/Ganeti/Types.hs
297 297
       , ("DTDrbd8",      "drbd")
298 298
       , ("DTRbd",        "rbd")
299 299
       , ("DTExt",        "ext")
300
       , ("DTGluster",    "gluster")
300 301
       ])
301 302
$(THH.makeJSONInstance ''DiskTemplate)
302 303

  
......
530 531
diskTemplateToStorageType DTRbd = StorageRados
531 532
diskTemplateToStorageType DTDiskless = StorageDiskless
532 533
diskTemplateToStorageType DTBlock = StorageBlock
534
diskTemplateToStorageType DTGluster = StorageFile
533 535

  
534 536
-- | Equips a raw storage unit with its parameters
535 537
addParamsToStorageUnit :: SPExclusiveStorage -> StorageUnitRaw -> StorageUnit
b/test/py/ganeti.objects_unittest.py
746 746
  def testUpgradeConfigDevTypeLegacyUnchanged(self):
747 747
    dev_types = [constants.DT_FILE, constants.DT_SHARED_FILE,
748 748
                 constants.DT_BLOCK, constants.DT_EXT,
749
                 constants.DT_RBD]
749
                 constants.DT_RBD, constants.DT_GLUSTER]
750 750
    for dev_type in dev_types:
751 751
      disk = objects.Disk()
752 752
      disk.dev_type = dev_type
b/test/py/ganeti.storage.gluster_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21
"""Script for unittesting the ganeti.storage.gluster module"""
22

  
23
import testutils
24

  
25
if __name__ == "__main__":
26
  testutils.GanetiTestProgram()

Also available in: Unified diff