Commit 59357cd7 authored by Helga Velroyen's avatar Helga Velroyen

Merge branch 'stable-2.12' into stable-2.13

* stable-2.12
  When assigning UUIDs to disks, do so recursively
  Fix sample 2.11 configuration
  Include hypervisor parameters in SSConf
  Add SSConf keys for hypervisor parameters
  Use Hypervisor as the key in ClusterHvParams
  Re-remove final config update in renew-crypto
  Fix string formatting in private object representation
  Fix the computation of the list of reserved IP addresses

* stable-2.11
  Update configure file to version 2.11.7
  Update NEWS file for 2.11.7 release
  Add logging to RenewCrypto
  Fix format string for gnt-network info
  Replace textwrapper.wrap by a custom version for networks
  Add SSL improvements to NEWS file

* stable-2.10
  Update tag limitations
  Fix typos in doc/design-storagetypes.rst
  Make getFQDN prefer cluster protocol family
  Add version of getFQDN accepting preferences
  Make getFQDN honor vcluster

Conflicts:
  lib/cmdlib/cluster.py

Resolution:
  lib/cmdlib/cluster.py: take addition from 2.12
Signed-off-by: default avatarHelga Velroyen <helgav@google.com>
Reviewed-by: default avatarPetr Pudlak <pudlak@google.com>
parents bacd2753 0de1e688
......@@ -331,6 +331,17 @@ This was the first beta release of the 2.12 series. All important changes
are listed in the latest 2.12 entry.
Version 2.11.7
--------------
*(Released Fri, 17 Apr 2015)*
- The operation 'gnt-cluster renew-crypto --new-node-certificates' is
now more robust against intermitten reachability errors. Nodes that
are temporarily not reachable, are contacted with several retries.
Nodes which are marked as offline are omitted right away.
Version 2.11.6
--------------
......
......@@ -1557,7 +1557,7 @@ Limitations
Note that the set of characters present in a tag and the maximum tag
length are restricted. Currently the maximum length is 128 characters,
there can be at most 4096 tags per object, and the set of characters is
comprised by alphanumeric characters and additionally ``.+*/:@-``.
comprised by alphanumeric characters and additionally ``.+*/:@-_``.
Operations
++++++++++
......
......@@ -9,7 +9,7 @@ Background
Currently, there is no consistent management of different variants of storage
in Ganeti. One direct consequence is that storage space reporting is currently
broken for all storage that is not based on lvm technolgy. This design looks at
broken for all storage that is not based on lvm technology. This design looks at
the root causes and proposes a way to fix it.
Proposed changes
......@@ -233,7 +233,7 @@ displayed in ``gnt-node list-storage``. This will also adapt to the
extended storage reporting capabilities. The user can specify a storage
type using ``--storage-type``. If he requests storage information about
a storage type which does not support space reporting, a warning is
emitted. If no storage type is specified explicitely, ``gnt-node
emitted. If no storage type is specified explicitly, ``gnt-node
list-storage`` will try to report storage on the storage type of the
default disk template. If the default disk template's storage type does
not support space reporting, an error message is emitted.
......
......@@ -241,9 +241,11 @@ def ShowNetworkConfig(_, args):
ToStdout(" Free: %d (%.2f%%)", free_count,
100 * float(free_count) / float(size))
ToStdout(" Usage map:")
lenmapping = len(mapping)
idx = 0
for line in textwrap.wrap(mapping, width=64):
ToStdout(" %s %s %d", str(idx).rjust(3), line.ljust(64), idx + 63)
while idx < lenmapping:
line = mapping[idx: idx + 64]
ToStdout(" %s %s %d", str(idx).rjust(4), line.ljust(64), idx + 63)
idx += 64
ToStdout(" (X) used (.) free")
......
......@@ -140,21 +140,34 @@ class LUClusterRenewCrypto(NoHooksLU):
"""
master_uuid = self.cfg.GetMasterNode()
cluster = self.cfg.GetClusterInfo()
logging.debug("Renewing the master's SSL node certificate."
" Master's UUID: %s.", master_uuid)
server_digest = utils.GetCertificateDigest(
cert_filename=pathutils.NODED_CERT_FILE)
logging.debug("SSL digest of the node certificate: %s.", server_digest)
self.cfg.AddNodeToCandidateCerts("%s-SERVER" % master_uuid,
server_digest)
logging.debug("Added master's digest as *-SERVER entry to configuration."
" Current list of candidate certificates: %s.",
str(cluster.candidate_certs))
try:
old_master_digest = utils.GetCertificateDigest(
cert_filename=pathutils.NODED_CLIENT_CERT_FILE)
logging.debug("SSL digest of old master's SSL node certificate: %s.",
old_master_digest)
self.cfg.AddNodeToCandidateCerts("%s-OLDMASTER" % master_uuid,
old_master_digest)
logging.debug("Added old master's node certificate digest to config"
" as *-OLDMASTER. Current list of candidate certificates:"
" %s.", str(cluster.candidate_certs))
except IOError:
logging.info("No old certificate available.")
logging.info("No old master certificate available.")
last_exception = None
for _ in range(self._MAX_NUM_RETRIES):
for i in range(self._MAX_NUM_RETRIES):
try:
# Technically it should not be necessary to set the cert
# paths. However, due to a bug in the mock library, we
......@@ -163,39 +176,58 @@ class LUClusterRenewCrypto(NoHooksLU):
self, self.cfg, master_uuid,
client_cert=pathutils.NODED_CLIENT_CERT_FILE,
client_cert_tmp=pathutils.NODED_CLIENT_CERT_FILE_TMP)
logging.debug("Successfully renewed the master's node certificate.")
break
except errors.OpExecError as e:
logging.error("Renewing the master's SSL node certificate failed"
" at attempt no. %s with error '%s'", str(i), e)
last_exception = e
else:
if last_exception:
feedback_fn("Could not renew the master's client SSL certificate."
" Cleaning up. Error: %s." % last_exception)
" Cleaning up. Error: %s." % last_exception)
# Cleaning up temporary certificates
self.cfg.RemoveNodeFromCandidateCerts("%s-SERVER" % master_uuid)
self.cfg.RemoveNodeFromCandidateCerts("%s-OLDMASTER" % master_uuid)
logging.debug("Cleaned up *-SERVER and *-OLDMASTER certificate from"
" master candidate cert list. Current state of the"
" list: %s.", str(cluster.candidate_certs))
try:
utils.RemoveFile(pathutils.NODED_CLIENT_CERT_FILE_TMP)
except IOError:
pass
except IOError as e:
logging.debug("Could not clean up temporary node certificate of the"
" master node. (Possibly because it was already removed"
" properly.) Error: %s.", e)
return
node_errors = {}
nodes = self.cfg.GetAllNodesInfo()
logging.debug("Renewing non-master nodes' node certificates.")
for (node_uuid, node_info) in nodes.items():
if node_info.offline:
logging.info("* Skipping offline node %s", node_info.name)
feedback_fn("* Skipping offline node %s" % node_info.name)
logging.debug("Skipping offline node %s (UUID: %s).",
node_info.name, node_uuid)
continue
if node_uuid != master_uuid:
logging.debug("Renewing node certificate of node '%s'.", node_uuid)
last_exception = None
for _ in range(self._MAX_NUM_RETRIES):
for i in range(self._MAX_NUM_RETRIES):
try:
new_digest = CreateNewClientCert(self, node_uuid)
if node_info.master_candidate:
self.cfg.AddNodeToCandidateCerts(node_uuid,
new_digest)
logging.debug("Added the node's certificate to candidate"
" certificate list. Current list: %s.",
str(cluster.candidate_certs))
break
except errors.OpExecError as e:
last_exception = e
logging.error("Could not renew a non-master node's SSL node"
" certificate at attempt no. %s. The node's UUID"
" is %s, and the error was: %s.",
str(i), node_uuid, e)
else:
if last_exception:
node_errors[node_uuid] = last_exception
......@@ -210,6 +242,9 @@ class LUClusterRenewCrypto(NoHooksLU):
self.cfg.RemoveNodeFromCandidateCerts("%s-SERVER" % master_uuid)
self.cfg.RemoveNodeFromCandidateCerts("%s-OLDMASTER" % master_uuid)
logging.debug("Cleaned up *-SERVER and *-OLDMASTER certificate from"
" master candidate cert list. Current state of the"
" list: %s.", cluster.candidate_certs)
def _RenewSshKeys(self, feedback_fn):
"""Renew all nodes' SSH keys.
......
......@@ -253,10 +253,10 @@ class Private(object):
return self._item
def __str__(self):
return "<%s>" % self._descr
return "<%s>" % (self._descr, )
def __repr__(self):
return "Private(?, descr=%r)".format(self._descr)
return "Private(?, descr=%r)" % (self._descr, )
# pylint: disable=W0212
# If it doesn't access _item directly, the call will go through __getattr__
......@@ -280,7 +280,7 @@ class Private(object):
def __call__(self, *args, **kwargs):
return Private(self._item(*args, **kwargs),
descr="%s()" % self._descr)
descr="%s()" % (self._descr, ))
# pylint: disable=R0201
# While this could get away with being a function, it needs to be a method.
......
......@@ -318,7 +318,7 @@ getGroupInstances cfg gname =
getFilledInstHvParams :: [String] -> ConfigData -> Instance -> HvParams
getFilledInstHvParams globals cfg inst =
-- First get the defaults of the parent
let hvName = hypervisorToRaw . instHypervisor $ inst
let hvName = instHypervisor inst
hvParamMap = fromContainer . clusterHvparams $ configCluster cfg
parentHvParams = maybe M.empty fromContainer $ M.lookup hvName hvParamMap
-- Then the os defaults for the given hypervisor
......
......@@ -340,10 +340,10 @@ vClusterHostNameEnvVar :: String
vClusterHostNameEnvVar = "GANETI_HOSTNAME"
-- | Get the real full qualified host name.
getFQDN' :: IO String
getFQDN' = do
getFQDN' :: Maybe Socket.AddrInfo -> IO String
getFQDN' hints = do
hostname <- getHostName
addrInfos <- Socket.getAddrInfo Nothing (Just hostname) Nothing
addrInfos <- Socket.getAddrInfo hints (Just hostname) Nothing
let address = listToMaybe addrInfos >>= (Just . Socket.addrAddress)
case address of
Just a -> do
......@@ -351,9 +351,10 @@ getFQDN' = do
return (fromMaybe hostname fqdn)
Nothing -> return hostname
-- | Return the full qualified host name, honoring the vcluster setup.
getFQDN :: IO String
getFQDN = do
-- | Return the full qualified host name, honoring the vcluster setup
-- and hints on the preferred socket type or protocol.
getFQDNwithHints :: Maybe Socket.AddrInfo -> IO String
getFQDNwithHints hints = do
let ioErrorToNothing :: IOError -> IO (Maybe String)
ioErrorToNothing _ = return Nothing
vcluster_node <- Control.Exception.catch
......@@ -361,7 +362,16 @@ getFQDN = do
ioErrorToNothing
case vcluster_node of
Just node_name -> return node_name
Nothing -> getFQDN'
Nothing -> getFQDN' hints
-- | Return the full qualified host name, honoring the vcluster setup.
getFQDN :: IO String
getFQDN = do
familyresult <- Ssconf.getPrimaryIPFamily Nothing
getFQDNwithHints
$ genericResult (const Nothing)
(\family -> Just $ Socket.defaultHints { Socket.addrFamily = family })
familyresult
-- | Returns if the current node is the master node.
isMaster :: IO Bool
......
......@@ -891,7 +891,7 @@ ipFamilyToVersion IpFamilyV4 = C.ip4Version
ipFamilyToVersion IpFamilyV6 = C.ip6Version
-- | Cluster HvParams (hvtype to hvparams mapping).
type ClusterHvParams = Container HvParams
type ClusterHvParams = GenericContainer Hypervisor HvParams
-- | Cluster Os-HvParams (os to hvparams mapping).
type OsHvParams = Container ClusterHvParams
......
......@@ -259,7 +259,7 @@ getDefaultHypervisorSpec cfg = (hv, getHvParamsFromCluster cfg hv)
getHvParamsFromCluster :: ConfigData -> Hypervisor -> HvParams
getHvParamsFromCluster cfg hv =
fromMaybe (GenericContainer Map.empty) .
Map.lookup (hypervisorToRaw hv) .
Map.lookup hv .
fromContainer . clusterHvparams $ configCluster cfg
-- | Given an alias list and a field list, copies field definitions under a
......
......@@ -875,7 +875,7 @@ getHypervisorSpecs :: ConfigData -> [Instance] -> [(Hypervisor, HvParams)]
getHypervisorSpecs cfg instances =
let hvs = nub . map instHypervisor $ instances
hvParamMap = (fromContainer . clusterHvparams . configCluster $ cfg)
in zip hvs . map ((Map.!) hvParamMap . hypervisorToRaw) $ hvs
in zip hvs . map ((Map.!) hvParamMap) $ hvs
-- | Collect live data from RPC query if enabled.
collectLiveData :: Bool -- ^ Live queries allowed
......
......@@ -44,11 +44,12 @@ module Ganeti.Query.Network
import qualified Data.Map as Map
import Data.Maybe (fromMaybe, mapMaybe)
import Data.List (find, foldl', intercalate)
import Data.List (find, intercalate)
import Ganeti.JSON
import Ganeti.Network
import Ganeti.Objects
import qualified Ganeti.Objects.BitArray as BA
import Ganeti.Query.Language
import Ganeti.Query.Common
import Ganeti.Query.Types
......@@ -165,24 +166,17 @@ getNetworkUuid cfg name =
-- | Computes the reservations list for a network.
--
-- This doesn't use the netmask for validation of the length, instead
-- simply iterating over the reservations string.
getReservations :: Ip4Network -> String -> [Ip4Address]
getReservations net =
reverse .
fst .
foldl' (\(accu, addr) c ->
let addr' = nextIp4Address addr
accu' = case c of
'1' -> addr:accu
'0' -> accu
_ -> -- FIXME: the reservations string
-- should be a proper type
accu
in (accu', addr')) ([], ip4netAddr net)
-- simply iterating over the reservations.
getReservations :: Ip4Network -> Maybe AddressPool -> [Ip4Address]
getReservations _ Nothing = []
getReservations net (Just pool) =
map snd . filter fst
$ zip (BA.toList . apReservations $ pool)
(iterate nextIp4Address $ ip4netAddr net)
-- | Computes the external reservations as string for a network.
getExtReservationsString :: Network -> ResultEntry
getExtReservationsString net =
let addrs = getReservations (networkNetwork net)
(maybe "" show $ networkExtReservations net)
(networkExtReservations net)
in rsNormal . intercalate ", " $ map show addrs
......@@ -38,6 +38,7 @@ module Ganeti.Ssconf
( SSKey(..)
, sSKeyToRaw
, sSKeyFromRaw
, hvparamsSSKey
, getPrimaryIPFamily
, parseNodesVmCapable
, getNodesVmCapable
......@@ -53,6 +54,7 @@ module Ganeti.Ssconf
, emptySSConf
) where
import Control.Arrow ((&&&))
import Control.Applicative ((<$>))
import Control.Exception
import Control.Monad (forM, liftM)
......@@ -83,40 +85,61 @@ maxFileSize = 131072
sSFilePrefix :: FilePath
sSFilePrefix = C.ssconfFileprefix
$(declareSADT "SSKey"
[ ("SSClusterName", 'C.ssClusterName)
, ("SSClusterTags", 'C.ssClusterTags)
, ("SSFileStorageDir", 'C.ssFileStorageDir)
, ("SSSharedFileStorageDir", 'C.ssSharedFileStorageDir)
, ("SSGlusterStorageDir", 'C.ssGlusterStorageDir)
, ("SSMasterCandidates", 'C.ssMasterCandidates)
, ("SSMasterCandidatesIps", 'C.ssMasterCandidatesIps)
, ("SSMasterCandidatesCerts",'C.ssMasterCandidatesCerts)
, ("SSMasterIp", 'C.ssMasterIp)
, ("SSMasterNetdev", 'C.ssMasterNetdev)
, ("SSMasterNetmask", 'C.ssMasterNetmask)
, ("SSMasterNode", 'C.ssMasterNode)
, ("SSNodeList", 'C.ssNodeList)
, ("SSNodePrimaryIps", 'C.ssNodePrimaryIps)
, ("SSNodeSecondaryIps", 'C.ssNodeSecondaryIps)
, ("SSNodeVmCapable", 'C.ssNodeVmCapable)
, ("SSOfflineNodes", 'C.ssOfflineNodes)
, ("SSOnlineNodes", 'C.ssOnlineNodes)
, ("SSPrimaryIpFamily", 'C.ssPrimaryIpFamily)
, ("SSInstanceList", 'C.ssInstanceList)
, ("SSReleaseVersion", 'C.ssReleaseVersion)
, ("SSHypervisorList", 'C.ssHypervisorList)
, ("SSMaintainNodeHealth", 'C.ssMaintainNodeHealth)
, ("SSUidPool", 'C.ssUidPool)
, ("SSNodegroups", 'C.ssNodegroups)
, ("SSNetworks", 'C.ssNetworks)
, ("SSEnabledUserShutdown", 'C.ssEnabledUserShutdown)
])
$(declareLADT ''String "SSKey" (
[ ("SSClusterName", C.ssClusterName)
, ("SSClusterTags", C.ssClusterTags)
, ("SSFileStorageDir", C.ssFileStorageDir)
, ("SSSharedFileStorageDir", C.ssSharedFileStorageDir)
, ("SSGlusterStorageDir", C.ssGlusterStorageDir)
, ("SSMasterCandidates", C.ssMasterCandidates)
, ("SSMasterCandidatesIps", C.ssMasterCandidatesIps)
, ("SSMasterCandidatesCerts", C.ssMasterCandidatesCerts)
, ("SSMasterIp", C.ssMasterIp)
, ("SSMasterNetdev", C.ssMasterNetdev)
, ("SSMasterNetmask", C.ssMasterNetmask)
, ("SSMasterNode", C.ssMasterNode)
, ("SSNodeList", C.ssNodeList)
, ("SSNodePrimaryIps", C.ssNodePrimaryIps)
, ("SSNodeSecondaryIps", C.ssNodeSecondaryIps)
, ("SSNodeVmCapable", C.ssNodeVmCapable)
, ("SSOfflineNodes", C.ssOfflineNodes)
, ("SSOnlineNodes", C.ssOnlineNodes)
, ("SSPrimaryIpFamily", C.ssPrimaryIpFamily)
, ("SSInstanceList", C.ssInstanceList)
, ("SSReleaseVersion", C.ssReleaseVersion)
, ("SSHypervisorList", C.ssHypervisorList)
, ("SSMaintainNodeHealth", C.ssMaintainNodeHealth)
, ("SSUidPool", C.ssUidPool)
, ("SSNodegroups", C.ssNodegroups)
, ("SSNetworks", C.ssNetworks)
, ("SSEnabledUserShutdown", C.ssEnabledUserShutdown)
] ++
-- Automatically generate SSHvparamsXxx for each hypervisor type:
map ((("SSHvparams" ++) . show)
&&& ((C.ssHvparamsPref ++) . Types.hypervisorToRaw))
[minBound..maxBound]
))
instance HasStringRepr SSKey where
fromStringRepr = sSKeyFromRaw
toStringRepr = sSKeyToRaw
-- | For a given hypervisor get the corresponding SSConf key that contains its
-- parameters.
--
-- The corresponding SSKeys are generated automatically by TH, but since we
-- don't have convenient infrastructure for generating this function, it's just
-- manual. All constructors must be given explicitly so that adding another
-- hypervisor will trigger "incomplete pattern" warning and force the
-- corresponding addition.
hvparamsSSKey :: Types.Hypervisor -> SSKey
hvparamsSSKey Types.Kvm = SSHvparamsKvm
hvparamsSSKey Types.XenPvm = SSHvparamsXenPvm
hvparamsSSKey Types.Chroot = SSHvparamsChroot
hvparamsSSKey Types.XenHvm = SSHvparamsXenHvm
hvparamsSSKey Types.Lxc = SSHvparamsLxc
hvparamsSSKey Types.Fake = SSHvparamsFake
-- | Convert a ssconf key into a (full) file path.
keyToFilename :: FilePath -- ^ Config path root
-> SSKey -- ^ Ssconf key
......
......@@ -99,11 +99,10 @@ verifyConfig cd = do
reportIf (null enabledHvs)
"enabled hypervisors list doesn't have any entries"
-- we don't need to check for invalid HVS as they would fail to parse
let missingHvp = S.fromList (map hypervisorToRaw enabledHvs)
S.\\ keysSet hvParams
let missingHvp = S.fromList enabledHvs S.\\ keysSet hvParams
reportIf (not $ S.null missingHvp)
$ "hypervisor parameters missing for the enabled hypervisor(s) "
++ (commaJoin . S.toList $ missingHvp)
++ (commaJoin . map hypervisorToRaw . S.toList $ missingHvp)
let enabledDiskTemplates = clusterEnabledDiskTemplates cluster
reportIf (null enabledDiskTemplates)
......
......@@ -42,10 +42,11 @@ module Ganeti.WConfd.Ssconf
, mkSSConf
) where
import Control.Arrow ((&&&))
import Control.Arrow ((&&&), first, second)
import Data.Foldable (Foldable(..), toList)
import Data.List (partition)
import qualified Data.Map as M
import qualified Text.JSON as J
import Ganeti.BasicTypes
import Ganeti.Config
......@@ -56,8 +57,34 @@ import Ganeti.Ssconf
import Ganeti.Utils
import Ganeti.Types
eqPair :: (String, String) -> String
eqPair (x, y) = x ++ "=" ++ y
mkSSConfHvparams :: Cluster -> [(Hypervisor, [String])]
mkSSConfHvparams cluster = map (id &&& hvparams) [minBound..maxBound]
where
hvparams :: Hypervisor -> [String]
hvparams h = maybe [] hvparamsStrings
$ lookupContainer Nothing h (clusterHvparams cluster)
-- | Convert a collection of hypervisor parameters to strings in the form
-- @key=value@.
hvparamsStrings :: HvParams -> [String]
hvparamsStrings =
map (eqPair . second hvparamShow) . M.toList . fromContainer
-- | Convert a hypervisor parameter in its JSON representation to a String.
-- Strings, numbers and booleans are just printed (without quotes), booleans
-- printed as @True@/@False@ and other JSON values (should they exist) as
-- their JSON representations.
hvparamShow :: J.JSValue -> String
hvparamShow (J.JSString s) = J.fromJSString s
hvparamShow (J.JSRational _ r) = J.showJSRational r []
hvparamShow (J.JSBool b) = show b
hvparamShow x = J.encode x
mkSSConf :: ConfigData -> SSConf
mkSSConf cdata = SSConf $ M.fromList
mkSSConf cdata = SSConf . M.fromList $
[ (SSClusterName, return $ clusterClusterName cluster)
, (SSClusterTags, toList $ tagsOf cluster)
, (SSFileStorageDir, return $ clusterFileStorageDir cluster)
......@@ -99,11 +126,11 @@ mkSSConf cdata = SSConf $ M.fromList
. configNetworks $ cdata)
, (SSEnabledUserShutdown, return . show . clusterEnabledUserShutdown
$ cluster)
]
] ++
map (first hvparamsSSKey) (mkSSConfHvparams cluster)
where
mapLines :: (Foldable f) => (a -> String) -> f a -> [String]
mapLines f = map f . toList
eqPair (x, y) = x ++ "=" ++ y
spcPair (x, y) = x ++ " " ++ y
toPairs = M.assocs . fromContainer
......
......@@ -328,7 +328,8 @@
"5c390722-6a7a-4bb4-9cef-98d896a8e6b1.disk0_data"
],
"params": {},
"size": 1024
"size": 1024,
"uuid": "55b3fa41-2bfe-4aef-ac32-fbf3be06a242"
},
{
"dev_type": "plain",
......@@ -337,7 +338,8 @@
"5c390722-6a7a-4bb4-9cef-98d896a8e6b1.disk0_meta"
],
"params": {},
"size": 128
"size": 128,
"uuid": "33eff786-0152-4653-8fc8-ea280fea9297"
}
],
"dev_type": "drbd",
......
......@@ -254,6 +254,10 @@ instance Arbitrary ClusterHvParams where
instance Arbitrary OsHvParams where
arbitrary = return $ GenericContainer Map.empty
-- | No real arbitrary instance for 'GroupDiskParams' yet.
instance Arbitrary GroupDiskParams where
arbitrary = return $ GenericContainer Map.empty
instance Arbitrary ClusterNicParams where
arbitrary = (GenericContainer . Map.singleton C.ppDefault) <$> arbitrary
......
......@@ -246,6 +246,14 @@ def _ConvertNicNameToUuid(iobj, network2uuid):
nic["network"] = uuid
def AssignUuid(disk):
if not "uuid" in disk:
disk["uuid"] = utils.io.NewUUID()
if "children" in disk:
for d in disk["children"]:
AssignUuid(d)
def _ConvertDiskAndCheckMissingSpindles(iobj, instance):
missing_spindles = False
if "disks" not in iobj:
......@@ -270,8 +278,7 @@ def _ConvertDiskAndCheckMissingSpindles(iobj, instance):
if not "spindles" in dobj:
missing_spindles = True
if not "uuid" in dobj:
dobj["uuid"] = utils.io.NewUUID()
AssignUuid(dobj)
return missing_spindles
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment