Revision dbdb0594
b/qa/qa_config.py | ||
---|---|---|
113 | 113 |
return default |
114 | 114 |
|
115 | 115 |
|
116 |
class _QaNode(object): |
|
117 |
__slots__ = [ |
|
118 |
"primary", |
|
119 |
"secondary", |
|
120 |
"_added", |
|
121 |
"use_count", |
|
122 |
] |
|
123 |
|
|
124 |
def __init__(self, primary, secondary): |
|
125 |
"""Initializes instances of this class. |
|
126 |
|
|
127 |
""" |
|
128 |
self.primary = primary |
|
129 |
self.secondary = secondary |
|
130 |
self.use_count = 0 |
|
131 |
self._added = False |
|
132 |
|
|
133 |
@classmethod |
|
134 |
def FromDict(cls, data): |
|
135 |
"""Creates node object from JSON dictionary. |
|
136 |
|
|
137 |
""" |
|
138 |
return cls(primary=data["primary"], secondary=data.get("secondary")) |
|
139 |
|
|
140 |
def __getitem__(self, key): |
|
141 |
"""Legacy dict-like interface. |
|
142 |
|
|
143 |
""" |
|
144 |
if key == "primary": |
|
145 |
return self.primary |
|
146 |
elif key == "secondary": |
|
147 |
return self.secondary |
|
148 |
else: |
|
149 |
raise KeyError(key) |
|
150 |
|
|
151 |
def get(self, key, default): |
|
152 |
"""Legacy dict-like interface. |
|
153 |
|
|
154 |
""" |
|
155 |
try: |
|
156 |
return self[key] |
|
157 |
except KeyError: |
|
158 |
return default |
|
159 |
|
|
160 |
def Use(self): |
|
161 |
"""Marks a node as being in use. |
|
162 |
|
|
163 |
""" |
|
164 |
assert self.use_count >= 0 |
|
165 |
|
|
166 |
self.use_count += 1 |
|
167 |
|
|
168 |
return self |
|
169 |
|
|
170 |
def MarkAdded(self): |
|
171 |
"""Marks node as having been added to a cluster. |
|
172 |
|
|
173 |
""" |
|
174 |
assert not self._added |
|
175 |
self._added = True |
|
176 |
|
|
177 |
def MarkRemoved(self): |
|
178 |
"""Marks node as having been removed from a cluster. |
|
179 |
|
|
180 |
""" |
|
181 |
assert self._added |
|
182 |
self._added = False |
|
183 |
|
|
184 |
@property |
|
185 |
def added(self): |
|
186 |
"""Returns whether a node is part of a cluster. |
|
187 |
|
|
188 |
""" |
|
189 |
return self._added |
|
190 |
|
|
191 |
|
|
116 | 192 |
_RESOURCE_CONVERTER = { |
117 | 193 |
"instances": _QaInstance.FromDict, |
194 |
"nodes": _QaNode.FromDict, |
|
118 | 195 |
} |
119 | 196 |
|
120 | 197 |
|
... | ... | |
470 | 547 |
return GetConfig().IsTemplateSupported(templ) |
471 | 548 |
|
472 | 549 |
|
473 |
def AcquireNode(exclude=None): |
|
550 |
def AcquireNode(exclude=None, _cfg=None):
|
|
474 | 551 |
"""Returns the least used node. |
475 | 552 |
|
476 | 553 |
""" |
477 |
master = GetMasterNode() |
|
478 |
cfg = GetConfig() |
|
554 |
if _cfg is None: |
|
555 |
cfg = GetConfig() |
|
556 |
else: |
|
557 |
cfg = _cfg |
|
558 |
|
|
559 |
master = cfg.GetMasterNode() |
|
479 | 560 |
|
480 | 561 |
# Filter out unwanted nodes |
481 | 562 |
# TODO: Maybe combine filters |
... | ... | |
486 | 567 |
else: |
487 | 568 |
nodes = filter(lambda node: node != exclude, cfg["nodes"]) |
488 | 569 |
|
489 |
tmp_flt = lambda node: node.get("_added", False) or node == master |
|
490 |
nodes = filter(tmp_flt, nodes) |
|
491 |
del tmp_flt |
|
570 |
nodes = filter(lambda node: node.added or node == master, nodes) |
|
492 | 571 |
|
493 |
if len(nodes) == 0:
|
|
572 |
if not nodes:
|
|
494 | 573 |
raise qa_error.OutOfNodesError("No nodes left") |
495 | 574 |
|
496 | 575 |
# Get node with least number of uses |
576 |
# TODO: Switch to computing sort key instead of comparing directly |
|
497 | 577 |
def compare(a, b): |
498 |
result = cmp(a.get("_count", 0), b.get("_count", 0))
|
|
578 |
result = cmp(a.use_count, b.use_count)
|
|
499 | 579 |
if result == 0: |
500 |
result = cmp(a["primary"], b["primary"])
|
|
580 |
result = cmp(a.primary, b.primary)
|
|
501 | 581 |
return result |
502 | 582 |
|
503 | 583 |
nodes.sort(cmp=compare) |
504 | 584 |
|
505 |
node = nodes[0] |
|
506 |
node["_count"] = node.get("_count", 0) + 1 |
|
507 |
return node |
|
585 |
return nodes[0].Use() |
|
508 | 586 |
|
509 | 587 |
|
510 | 588 |
def AcquireManyNodes(num, exclude=None): |
... | ... | |
539 | 617 |
|
540 | 618 |
|
541 | 619 |
def ReleaseNode(node): |
542 |
node["_count"] = node.get("_count", 0) - 1 |
|
620 |
assert node.use_count > 0 |
|
621 |
|
|
622 |
node.use_count -= 1 |
|
543 | 623 |
|
544 | 624 |
|
545 | 625 |
def ReleaseManyNodes(nodes): |
b/qa/qa_node.py | ||
---|---|---|
36 | 36 |
|
37 | 37 |
|
38 | 38 |
def _NodeAdd(node, readd=False): |
39 |
if not readd and node.get("_added", False):
|
|
39 |
if not readd and node.added:
|
|
40 | 40 |
raise qa_error.Error("Node %s already in cluster" % node["primary"]) |
41 |
elif readd and not node.get("_added", False):
|
|
41 |
elif readd and not node.added:
|
|
42 | 42 |
raise qa_error.Error("Node %s not yet in cluster" % node["primary"]) |
43 | 43 |
|
44 | 44 |
cmd = ["gnt-node", "add", "--no-ssh-key-check"] |
... | ... | |
50 | 50 |
|
51 | 51 |
AssertCommand(cmd) |
52 | 52 |
|
53 |
node["_added"] = True |
|
53 |
if readd: |
|
54 |
assert node.added |
|
55 |
else: |
|
56 |
node.MarkAdded() |
|
54 | 57 |
|
55 | 58 |
|
56 | 59 |
def _NodeRemove(node): |
57 | 60 |
AssertCommand(["gnt-node", "remove", node["primary"]]) |
58 |
node["_added"] = False
|
|
61 |
node.MarkRemoved()
|
|
59 | 62 |
|
60 | 63 |
|
61 | 64 |
def MakeNodeOffline(node, value): |
... | ... | |
81 | 84 |
master = qa_config.GetMasterNode() |
82 | 85 |
for node in qa_config.get("nodes"): |
83 | 86 |
if node != master: |
84 |
node["_added"] = True
|
|
87 |
node.MarkAdded()
|
|
85 | 88 |
|
86 | 89 |
|
87 | 90 |
def TestNodeRemoveAll(): |
b/test/py/qa.qa_config_unittest.py | ||
---|---|---|
277 | 277 |
self.assertTrue(isinstance(self.config["instances"][0], |
278 | 278 |
qa_config._QaInstance)) |
279 | 279 |
|
280 |
def testNodeConversion(self): |
|
281 |
self.assertTrue(isinstance(self.config["nodes"][0], |
|
282 |
qa_config._QaNode)) |
|
283 |
|
|
280 | 284 |
def testAcquireAndReleaseInstance(self): |
281 | 285 |
self.assertFalse(compat.any(map(operator.attrgetter("used"), |
282 | 286 |
self.config["instances"]))) |
... | ... | |
304 | 308 |
self.assertRaises(qa_error.OutOfInstancesError, |
305 | 309 |
qa_config.AcquireInstance, _cfg=self.config) |
306 | 310 |
|
311 |
def testAcquireNodeNoneAdded(self): |
|
312 |
self.assertFalse(compat.any(map(operator.attrgetter("added"), |
|
313 |
self.config["nodes"]))) |
|
314 |
|
|
315 |
# First call must return master node |
|
316 |
node = qa_config.AcquireNode(_cfg=self.config) |
|
317 |
self.assertEqual(node, self.config.GetMasterNode()) |
|
318 |
|
|
319 |
# Next call with exclusion list fails |
|
320 |
self.assertRaises(qa_error.OutOfNodesError, qa_config.AcquireNode, |
|
321 |
exclude=[node], _cfg=self.config) |
|
322 |
|
|
323 |
def testAcquireNodeTooMany(self): |
|
324 |
# Mark all nodes as marked (master excluded) |
|
325 |
for node in self.config["nodes"]: |
|
326 |
if node != self.config.GetMasterNode(): |
|
327 |
node.MarkAdded() |
|
328 |
|
|
329 |
nodecount = len(self.config["nodes"]) |
|
330 |
|
|
331 |
self.assertTrue(nodecount > 1) |
|
332 |
|
|
333 |
acquired = [] |
|
334 |
|
|
335 |
for _ in range(nodecount): |
|
336 |
node = qa_config.AcquireNode(exclude=acquired, _cfg=self.config) |
|
337 |
if node == self.config.GetMasterNode(): |
|
338 |
self.assertFalse(node.added) |
|
339 |
else: |
|
340 |
self.assertTrue(node.added) |
|
341 |
self.assertEqual(node.use_count, 1) |
|
342 |
acquired.append(node) |
|
343 |
|
|
344 |
self.assertRaises(qa_error.OutOfNodesError, qa_config.AcquireNode, |
|
345 |
exclude=acquired, _cfg=self.config) |
|
346 |
|
|
307 | 347 |
|
308 | 348 |
if __name__ == "__main__": |
309 | 349 |
testutils.GanetiTestProgram() |
Also available in: Unified diff