Commit f9139cd9 authored by Klaus Aehlig's avatar Klaus Aehlig
Browse files

Merge branch 'stable-2.12' into stable-2.13



* stable-2.12
  Make WConfD's updateLocksWaiting safe
  Tests specifying safeUpdateLocksWaiting
  Provide a repeatable version of updateLocksWaiting
  Verify that updateLocks is idempotent
  Always accept no-op requests
  Allow unconditional failovers off offline nodes
  Remove now unused variable
  Fix bug in ssconf comparison, disable it for vcluster
  QA: test renewing the cluster certificate only
  QA: Assert equality of ssconf_master_candidate_certs
  QA: Add more verify steps in renew crypto QA

* stable-2.11
  (no changes)

* stable-2.10
  Substitute 'suffix' for 'revision'

Conflicts:
	qa/qa_cluster.py
	src/Ganeti/HTools/Cluster.hs
	src/Ganeti/HTools/Node.hs
(All trivial by taking the union of the changes.)
Signed-off-by: default avatarKlaus Aehlig <aehlig@google.com>
Reviewed-by: default avatarHelga Velroyen <helgav@google.com>
parents 39c54b84 3eec3aaa
......@@ -160,7 +160,7 @@ gnt-cluster upgrade
The actual upgrade process will be done by a new command ``upgrade`` to
``gnt-cluster``. If called with the option ``--to`` which take precisely
one argument, the version to
upgrade (or downgrade) to, given as full string with major, minor, suffix,
upgrade (or downgrade) to, given as full string with major, minor, revision,
and suffix. To be compatible with current configuration upgrade and downgrade
procedures, the new version must be of the same major version and
either an equal or higher minor version, or precisely the previous
......
......@@ -1180,6 +1180,38 @@ def TestClusterVersion():
AssertCommand(["gnt-cluster", "version"])
def _AssertSsconfCertFiles():
"""This asserts that all ssconf_master_candidate_certs have the same content.
"""
(vcluster_master, _) = qa_config.GetVclusterSettings()
if vcluster_master:
print "Skipping asserting SsconfCertFiles for Vcluster"
return
nodes = qa_config.get("nodes")
ssconf_file = "/var/lib/ganeti/ssconf_master_candidates_certs"
ssconf_content = {}
for node in nodes:
cmd = ["cat", ssconf_file]
print "Ssconf Master Certificates of node '%s'." % node.primary
result_output = GetCommandOutput(node.primary, utils.ShellQuoteArgs(cmd))
ssconf_content[node] = result_output
# Clean up result to make it comparable:
# remove trailing whitespace from each line, remove empty lines, sort lines
lines_node = result_output.split('\n')
lines_node = [line.strip() for line in lines_node if len(line.strip()) > 0]
lines_node.sort()
ssconf_content[node] = lines_node
first_node = nodes[0]
for node in nodes[1:]:
if not ssconf_content[node] == ssconf_content[first_node]:
raise Exception("Cert list of node '%s' differs from the list of node"
" '%s'." % (node, first_node))
def TestClusterRenewCrypto():
"""gnt-cluster renew-crypto"""
master = qa_config.GetMasterNode()
......@@ -1236,10 +1268,20 @@ def TestClusterRenewCrypto():
"--new-rapi-certificate", "--new-cluster-domain-secret",
"--new-node-certificates", "--new-ssh-keys",
"--no-ssh-key-check"])
_AssertSsconfCertFiles()
AssertCommand(["gnt-cluster", "verify"])
# Only renew node certificates
AssertCommand(["gnt-cluster", "renew-crypto", "--force",
"--new-node-certificates"])
_AssertSsconfCertFiles()
AssertCommand(["gnt-cluster", "verify"])
# Only renew cluster certificate
AssertCommand(["gnt-cluster", "renew-crypto", "--force",
"--new-cluster-certificate"])
_AssertSsconfCertFiles()
AssertCommand(["gnt-cluster", "verify"])
# Only renew SSH keys
AssertCommand(["gnt-cluster", "renew-crypto", "--force",
......@@ -1248,6 +1290,8 @@ def TestClusterRenewCrypto():
# Restore RAPI certificate
AssertCommand(["gnt-cluster", "renew-crypto", "--force",
"--rapi-certificate=%s" % rapi_cert_backup])
_AssertSsconfCertFiles()
AssertCommand(["gnt-cluster", "verify"])
finally:
AssertCommand(["rm", "-f", rapi_cert_backup])
......
......@@ -471,7 +471,8 @@ applyMoveEx force nl inst Failover =
new_nl = do -- OpResult
Node.checkMigration old_p old_s
new_p <- Node.addPriEx (Node.offline old_p || force) int_s inst
new_s <- Node.addSec int_p inst old_sdx
new_s <- Node.addSecExEx (Node.offline old_p) (Node.offline old_p)
int_p inst old_sdx
let new_inst = Instance.setBoth inst old_sdx old_pdx
return (Container.addTwo old_pdx new_s old_sdx new_p nl,
new_inst, old_sdx, old_pdx)
......
......@@ -70,6 +70,7 @@ module Ganeti.HTools.Node
, addPriEx
, addSec
, addSecEx
, addSecExEx
, checkMigration
-- * Stats
, availDisk
......@@ -607,7 +608,15 @@ addSec = addSecEx False
-- | Adds a secondary instance (extended version).
addSecEx :: Bool -> Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
addSecEx force t inst pdx =
addSecEx = addSecExEx False
-- | Adds a secondary instance (doubly extended version). The first parameter
-- tells `addSecExEx` to ignore disks completly. There is only one legitimate
-- use case for this, and this is failing over a DRBD instance where the primary
-- node is offline (and hence will become the secondary afterwards).
addSecExEx :: Bool
-> Bool -> Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
addSecExEx ignore_disks force t inst pdx =
let iname = Instance.idx inst
old_peers = peers t
old_mem = fMem t
......@@ -629,7 +638,7 @@ addSecEx force t inst pdx =
strict = not force
in case () of
_ | not (Instance.hasSecondary inst) -> Bad T.FailDisk
| new_dsk <= 0 -> Bad T.FailDisk
| not ignore_disks && new_dsk <= 0 -> Bad T.FailDisk
| new_dsk < loDsk t && strict -> Bad T.FailDisk
| exclStorage t && new_free_sp < 0 -> Bad T.FailSpindles
| new_inst_sp > hiSpindles t && strict -> Bad T.FailDisk
......
......@@ -38,6 +38,7 @@ module Ganeti.Locking.Waiting
, emptyWaiting
, updateLocks
, updateLocksWaiting
, safeUpdateLocksWaiting
, getAllocation
, getPendingOwners
, hasPendingRequest
......@@ -230,6 +231,16 @@ updateLocksWaiting' prio owner reqs state =
}
in (state'', (result, notify))
-- | Predicate whether a request is already fulfilled in a given state
-- and no requests for that owner are pending.
requestFulfilled :: (Ord a, Ord b)
=> b -> [L.LockRequest a] -> LockWaiting a b c -> Bool
requestFulfilled owner req state =
let locks = L.listLocks owner $ lwAllocation state
isFulfilled r = M.lookup (L.lockAffected r) locks
== L.lockRequestType r
in not (hasPendingRequest owner state) && all isFulfilled req
-- | Update the locks on an onwer according to the given request, if possible.
-- Additionally (if the request succeeds) fulfill any pending requests that
-- became possible through this request. Return the new state of the waiting
......@@ -237,18 +248,22 @@ updateLocksWaiting' prio owner reqs state =
-- The result is, as for lock allocation, the set of owners the request is
-- blocked on. Again, the type is chosen to be suitable for use in
-- atomicModifyIORef.
-- For convenience, fulfilled requests are always accepted.
updateLocks :: (Lock a, Ord b, Ord c)
=> b
-> [L.LockRequest a]
-> LockWaiting a b c
-> (LockWaiting a b c, (Result (S.Set b), S.Set b))
updateLocks owner req state =
second (second $ S.delete owner) $ updateLocks' owner req state
if requestFulfilled owner req state
then (state, (Ok S.empty, S.empty))
else second (second $ S.delete owner) $ updateLocks' owner req state
-- | Update locks as soon as possible. If the request cannot be fulfilled
-- immediately add the request to the waiting queue. The first argument is
-- the priority at which the owner is waiting, the remaining are as for
-- updateLocks, and so is the output.
-- For convenience, fulfilled requests are always accepted.
updateLocksWaiting :: (Lock a, Ord b, Ord c)
=> c
-> b
......@@ -256,7 +271,11 @@ updateLocksWaiting :: (Lock a, Ord b, Ord c)
-> LockWaiting a b c
-> (LockWaiting a b c, (Result (S.Set b), S.Set b))
updateLocksWaiting prio owner req state =
second (second $ S.delete owner) $ updateLocksWaiting' prio owner req state
if requestFulfilled owner req state
then (state, (Ok S.empty, S.empty))
else second (second $ S.delete owner)
$ updateLocksWaiting' prio owner req state
-- | Compute the state of a waiting after an owner gives up
-- on his pending request.
......@@ -277,6 +296,25 @@ removePendingRequest owner state =
, lwPending = pending'
}
-- | A repeatable version of `updateLocksWaiting`. If the owner has a pending
-- request and the pending request is equal to the current one, do nothing;
-- otherwise call updateLocksWaiting.
safeUpdateLocksWaiting :: (Lock a, Ord b, Ord c)
=> c
-> b
-> [L.LockRequest a]
-> LockWaiting a b c
-> (LockWaiting a b c, (Result (S.Set b), S.Set b))
safeUpdateLocksWaiting prio owner req state =
if hasPendingRequest owner state
&& S.singleton req
== (S.map (\(_, _, r) -> r)
. S.filter (\(_, b, _) -> b == owner) $ getPendingRequests state)
then let (_, answer) = updateLocksWaiting prio owner req
$ removePendingRequest owner state
in (state, answer)
else updateLocksWaiting prio owner req state
-- | Convenience function to release all pending requests and locks
-- of a given owner. Return the new configuration and the owners to
-- notify.
......
......@@ -277,7 +277,7 @@ updateLocksWaiting cid prio req =
liftM S.toList
. (>>= toErrorStr)
. modifyLockWaiting
$ LW.updateLocksWaiting prio cid (fromGanetiLockRequest req)
$ LW.safeUpdateLocksWaiting prio cid (fromGanetiLockRequest req)
-- | Tell whether a given owner has pending requests.
hasPendingRequest :: ClientId -> WConfdMonad Bool
......
......@@ -247,6 +247,24 @@ prop_PendingJustified =
in printTestCase "Pebding requests must be good and not fulfillable"
. all isJustified . S.toList $ getPendingRequests state
-- | Verify that `updateLocks` is idempotent, except that in the repetition,
-- no waiters are notified.
prop_UpdateIdempotent :: Property
prop_UpdateIdempotent =
forAll (arbitrary :: Gen (LockWaiting TestLock TestOwner Integer)) $ \state ->
forAll (arbitrary :: Gen TestOwner) $ \owner ->
forAll (arbitrary :: Gen [LockRequest TestLock]) $ \req ->
let (state', (answer', _)) = updateLocks owner req state
(state'', (answer'', nfy)) = updateLocks owner req state'
in conjoin [ printTestCase ("repeated updateLocks waiting gave different\
\ answers: " ++ show answer' ++ " /= "
++ show answer'') $ answer' == answer''
, printTestCase "updateLocks not idempotent"
$ extRepr state' == extRepr state''
, printTestCase ("notifications (" ++ show nfy ++ ") on replay")
$ S.null nfy
]
-- | Verify that extRepr . fromExtRepr = id for all valid extensional
-- representations.
prop_extReprPreserved :: Property
......@@ -292,6 +310,46 @@ prop_SimulateUpdateLocksWaiting =
, extRepr finState == extRepr finState'
]
-- | Verify that if a requestor has no pending requests, `safeUpdateWaiting`
-- conincides with `updateLocksWaiting`.
prop_SafeUpdateWaitingCorrect :: Property
prop_SafeUpdateWaitingCorrect =
forAll (arbitrary :: Gen TestOwner) $ \owner ->
forAll ((arbitrary :: Gen (LockWaiting TestLock TestOwner Integer))
`suchThat` (not . hasPendingRequest owner)) $ \state ->
forAll (arbitrary :: Gen Integer) $ \prio ->
forAll (arbitrary :: Gen [LockRequest TestLock]) $ \req ->
let (state', answer') = updateLocksWaiting prio owner req state
(state'', answer'') = safeUpdateLocksWaiting prio owner req state
in conjoin [ printTestCase ("safeUpdateLocksWaiting gave different answer: "
++ show answer' ++ " /= " ++ show answer'')
$ answer' == answer''
, printTestCase ("safeUpdateLocksWaiting gave different states\
\ after answer " ++ show answer' ++ ": "
++ show (extRepr state') ++ " /= "
++ show (extRepr state''))
$ extRepr state' == extRepr state''
]
-- | Verify that `safeUpdateLocksWaiting` is idempotent, that in the repetition
-- no notifications are done.
prop_SafeUpdateWaitingIdempotent :: Property
prop_SafeUpdateWaitingIdempotent =
forAll (arbitrary :: Gen (LockWaiting TestLock TestOwner Integer)) $ \state ->
forAll (arbitrary :: Gen TestOwner) $ \owner ->
forAll (arbitrary :: Gen Integer) $ \prio ->
forAll (arbitrary :: Gen [LockRequest TestLock]) $ \req ->
let (state', (answer', _)) = safeUpdateLocksWaiting prio owner req state
(state'', (answer'', nfy)) = safeUpdateLocksWaiting prio owner req state'
in conjoin [ printTestCase ("repeated safeUpdateLocks waiting gave different\
\ answers: " ++ show answer' ++ " /= "
++ show answer'') $ answer' == answer''
, printTestCase "safeUpdateLocksWaiting not idempotent"
$ extRepr state' == extRepr state''
, printTestCase ("notifications (" ++ show nfy ++ ") on replay")
$ S.null nfy
]
-- | Verify that for LockWaiting we have readJSON . showJSON is extensionally
-- equivalent to Ok.
prop_ReadShow :: Property
......@@ -352,9 +410,12 @@ testSuite "Locking/Waiting"
, 'prop_ProgressSound
, 'prop_PendingJustified
, 'prop_extReprPreserved
, 'prop_UpdateIdempotent
, 'prop_SimulateUpdateLocks
, 'prop_SimulateUpdateLocksWaiting
, 'prop_ReadShow
, 'prop_SafeUpdateWaitingCorrect
, 'prop_SafeUpdateWaitingIdempotent
, 'prop_OpportunisticMonotone
, 'prop_OpportunisticAnswer
]
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