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