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