Statistics
| Branch: | Tag: | Revision:

root / qa / qa_utils.py @ 6998aefe

History | View | Annotate | Download (19.7 kB)

1
#
2
#
3

    
4
# Copyright (C) 2007, 2011, 2012, 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

    
22
"""Utilities for QA tests.
23

24
"""
25

    
26
import os
27
import re
28
import sys
29
import subprocess
30
import random
31
import tempfile
32
import operator
33

    
34
try:
35
  import functools
36
except ImportError, err:
37
  raise ImportError("Python 2.5 or higher is required: %s" % err)
38

    
39
from ganeti import utils
40
from ganeti import compat
41
from ganeti import constants
42
from ganeti import ht
43
from ganeti import pathutils
44

    
45
import qa_config
46
import qa_error
47

    
48

    
49
_INFO_SEQ = None
50
_WARNING_SEQ = None
51
_ERROR_SEQ = None
52
_RESET_SEQ = None
53

    
54
_MULTIPLEXERS = {}
55

    
56
#: Unique ID per QA run
57
_RUN_UUID = utils.NewUUID()
58

    
59
#: Path to the QA query output log file
60
_QA_OUTPUT = pathutils.GetLogFilename("qa-output")
61

    
62

    
63
(INST_DOWN,
64
 INST_UP) = range(500, 502)
65

    
66
(FIRST_ARG,
67
 RETURN_VALUE) = range(1000, 1002)
68

    
69

    
70
def _SetupColours():
71
  """Initializes the colour constants.
72

73
  """
74
  # pylint: disable=W0603
75
  # due to global usage
76
  global _INFO_SEQ, _WARNING_SEQ, _ERROR_SEQ, _RESET_SEQ
77

    
78
  # Don't use colours if stdout isn't a terminal
79
  if not sys.stdout.isatty():
80
    return
81

    
82
  try:
83
    import curses
84
  except ImportError:
85
    # Don't use colours if curses module can't be imported
86
    return
87

    
88
  curses.setupterm()
89

    
90
  _RESET_SEQ = curses.tigetstr("op")
91

    
92
  setaf = curses.tigetstr("setaf")
93
  _INFO_SEQ = curses.tparm(setaf, curses.COLOR_GREEN)
94
  _WARNING_SEQ = curses.tparm(setaf, curses.COLOR_YELLOW)
95
  _ERROR_SEQ = curses.tparm(setaf, curses.COLOR_RED)
96

    
97

    
98
_SetupColours()
99

    
100

    
101
def AssertIn(item, sequence):
102
  """Raises an error when item is not in sequence.
103

104
  """
105
  if item not in sequence:
106
    raise qa_error.Error("%r not in %r" % (item, sequence))
107

    
108

    
109
def AssertNotIn(item, sequence):
110
  """Raises an error when item is in sequence.
111

112
  """
113
  if item in sequence:
114
    raise qa_error.Error("%r in %r" % (item, sequence))
115

    
116

    
117
def AssertEqual(first, second):
118
  """Raises an error when values aren't equal.
119

120
  """
121
  if not first == second:
122
    raise qa_error.Error("%r == %r" % (first, second))
123

    
124

    
125
def AssertMatch(string, pattern):
126
  """Raises an error when string doesn't match regexp pattern.
127

128
  """
129
  if not re.match(pattern, string):
130
    raise qa_error.Error("%r doesn't match /%r/" % (string, pattern))
131

    
132

    
133
def _GetName(entity, fn):
134
  """Tries to get name of an entity.
135

136
  @type entity: string or dict
137
  @param fn: Function retrieving name from entity
138

139
  """
140
  if isinstance(entity, basestring):
141
    result = entity
142
  else:
143
    result = fn(entity)
144

    
145
  if not ht.TNonEmptyString(result):
146
    raise Exception("Invalid name '%s'" % result)
147

    
148
  return result
149

    
150

    
151
def _AssertRetCode(rcode, fail, cmdstr, nodename):
152
  """Check the return value from a command and possibly raise an exception.
153

154
  """
155
  if fail and rcode == 0:
156
    raise qa_error.Error("Command '%s' on node %s was expected to fail but"
157
                         " didn't" % (cmdstr, nodename))
158
  elif not fail and rcode != 0:
159
    raise qa_error.Error("Command '%s' on node %s failed, exit code %s" %
160
                         (cmdstr, nodename, rcode))
161

    
162

    
163
def AssertCommand(cmd, fail=False, node=None, log_cmd=True):
164
  """Checks that a remote command succeeds.
165

166
  @param cmd: either a string (the command to execute) or a list (to
167
      be converted using L{utils.ShellQuoteArgs} into a string)
168
  @type fail: boolean
169
  @param fail: if the command is expected to fail instead of succeeding
170
  @param node: if passed, it should be the node on which the command
171
      should be executed, instead of the master node (can be either a
172
      dict or a string)
173
  @param log_cmd: if False, the command won't be logged (simply passed to
174
      StartSSH)
175
  @return: the return code of the command
176
  @raise qa_error.Error: if the command fails when it shouldn't or vice versa
177

178
  """
179
  if node is None:
180
    node = qa_config.GetMasterNode()
181

    
182
  nodename = _GetName(node, operator.attrgetter("primary"))
183

    
184
  if isinstance(cmd, basestring):
185
    cmdstr = cmd
186
  else:
187
    cmdstr = utils.ShellQuoteArgs(cmd)
188

    
189
  rcode = StartSSH(nodename, cmdstr, log_cmd=log_cmd).wait()
190
  _AssertRetCode(rcode, fail, cmdstr, nodename)
191

    
192
  return rcode
193

    
194

    
195
def AssertRedirectedCommand(cmd, fail=False, node=None, log_cmd=True):
196
  """Executes a command with redirected output.
197

198
  The log will go to the qa-output log file in the ganeti log
199
  directory on the node where the command is executed. The fail and
200
  node parameters are passed unchanged to AssertCommand.
201

202
  @param cmd: the command to be executed, as a list; a string is not
203
      supported
204

205
  """
206
  if not isinstance(cmd, list):
207
    raise qa_error.Error("Non-list passed to AssertRedirectedCommand")
208
  ofile = utils.ShellQuote(_QA_OUTPUT)
209
  cmdstr = utils.ShellQuoteArgs(cmd)
210
  AssertCommand("echo ---- $(date) %s ---- >> %s" % (cmdstr, ofile),
211
                fail=False, node=node, log_cmd=False)
212
  return AssertCommand(cmdstr + " >> %s" % ofile,
213
                       fail=fail, node=node, log_cmd=log_cmd)
214

    
215

    
216
def GetSSHCommand(node, cmd, strict=True, opts=None, tty=None):
217
  """Builds SSH command to be executed.
218

219
  @type node: string
220
  @param node: node the command should run on
221
  @type cmd: string
222
  @param cmd: command to be executed in the node; if None or empty
223
      string, no command will be executed
224
  @type strict: boolean
225
  @param strict: whether to enable strict host key checking
226
  @type opts: list
227
  @param opts: list of additional options
228
  @type tty: boolean or None
229
  @param tty: if we should use tty; if None, will be auto-detected
230

231
  """
232
  args = ["ssh", "-oEscapeChar=none", "-oBatchMode=yes", "-lroot"]
233

    
234
  if tty is None:
235
    tty = sys.stdout.isatty()
236

    
237
  if tty:
238
    args.append("-t")
239

    
240
  if strict:
241
    tmp = "yes"
242
  else:
243
    tmp = "no"
244
  args.append("-oStrictHostKeyChecking=%s" % tmp)
245
  args.append("-oClearAllForwardings=yes")
246
  args.append("-oForwardAgent=yes")
247
  if opts:
248
    args.extend(opts)
249
  if node in _MULTIPLEXERS:
250
    spath = _MULTIPLEXERS[node][0]
251
    args.append("-oControlPath=%s" % spath)
252
    args.append("-oControlMaster=no")
253
  args.append(node)
254
  if cmd:
255
    args.append(cmd)
256

    
257
  return args
258

    
259

    
260
def StartLocalCommand(cmd, _nolog_opts=False, log_cmd=True, **kwargs):
261
  """Starts a local command.
262

263
  """
264
  if log_cmd:
265
    if _nolog_opts:
266
      pcmd = [i for i in cmd if not i.startswith("-")]
267
    else:
268
      pcmd = cmd
269
    print "Command: %s" % utils.ShellQuoteArgs(pcmd)
270
  return subprocess.Popen(cmd, shell=False, **kwargs)
271

    
272

    
273
def StartSSH(node, cmd, strict=True, log_cmd=True):
274
  """Starts SSH.
275

276
  """
277
  return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict),
278
                           _nolog_opts=True, log_cmd=log_cmd)
279

    
280

    
281
def StartMultiplexer(node):
282
  """Starts a multiplexer command.
283

284
  @param node: the node for which to open the multiplexer
285

286
  """
287
  if node in _MULTIPLEXERS:
288
    return
289

    
290
  # Note: yes, we only need mktemp, since we'll remove the file anyway
291
  sname = tempfile.mktemp(prefix="ganeti-qa-multiplexer.")
292
  utils.RemoveFile(sname)
293
  opts = ["-N", "-oControlPath=%s" % sname, "-oControlMaster=yes"]
294
  print "Created socket at %s" % sname
295
  child = StartLocalCommand(GetSSHCommand(node, None, opts=opts))
296
  _MULTIPLEXERS[node] = (sname, child)
297

    
298

    
299
def CloseMultiplexers():
300
  """Closes all current multiplexers and cleans up.
301

302
  """
303
  for node in _MULTIPLEXERS.keys():
304
    (sname, child) = _MULTIPLEXERS.pop(node)
305
    utils.KillProcess(child.pid, timeout=10, waitpid=True)
306
    utils.RemoveFile(sname)
307

    
308

    
309
def GetCommandOutput(node, cmd, tty=None, fail=False):
310
  """Returns the output of a command executed on the given node.
311

312
  @type node: string
313
  @param node: node the command should run on
314
  @type cmd: string
315
  @param cmd: command to be executed in the node (cannot be empty or None)
316
  @type tty: bool or None
317
  @param tty: if we should use tty; if None, it will be auto-detected
318
  @type fail: bool
319
  @param fail: whether the command is expected to fail
320
  """
321
  assert cmd
322
  p = StartLocalCommand(GetSSHCommand(node, cmd, tty=tty),
323
                        stdout=subprocess.PIPE)
324
  rcode = p.wait()
325
  _AssertRetCode(rcode, fail, node, cmd)
326
  return p.stdout.read()
327

    
328

    
329
def UploadFile(node, src):
330
  """Uploads a file to a node and returns the filename.
331

332
  Caller needs to remove the returned file on the node when it's not needed
333
  anymore.
334

335
  """
336
  # Make sure nobody else has access to it while preserving local permissions
337
  mode = os.stat(src).st_mode & 0700
338

    
339
  cmd = ('tmp=$(tempfile --mode %o --prefix gnt) && '
340
         '[[ -f "${tmp}" ]] && '
341
         'cat > "${tmp}" && '
342
         'echo "${tmp}"') % mode
343

    
344
  f = open(src, "r")
345
  try:
346
    p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False, stdin=f,
347
                         stdout=subprocess.PIPE)
348
    AssertEqual(p.wait(), 0)
349

    
350
    # Return temporary filename
351
    return p.stdout.read().strip()
352
  finally:
353
    f.close()
354

    
355

    
356
def UploadData(node, data, mode=0600, filename=None):
357
  """Uploads data to a node and returns the filename.
358

359
  Caller needs to remove the returned file on the node when it's not needed
360
  anymore.
361

362
  """
363
  if filename:
364
    tmp = "tmp=%s" % utils.ShellQuote(filename)
365
  else:
366
    tmp = "tmp=$(tempfile --mode %o --prefix gnt)" % mode
367
  cmd = ("%s && "
368
         "[[ -f \"${tmp}\" ]] && "
369
         "cat > \"${tmp}\" && "
370
         "echo \"${tmp}\"") % tmp
371

    
372
  p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False,
373
                       stdin=subprocess.PIPE, stdout=subprocess.PIPE)
374
  p.stdin.write(data)
375
  p.stdin.close()
376
  AssertEqual(p.wait(), 0)
377

    
378
  # Return temporary filename
379
  return p.stdout.read().strip()
380

    
381

    
382
def BackupFile(node, path):
383
  """Creates a backup of a file on the node and returns the filename.
384

385
  Caller needs to remove the returned file on the node when it's not needed
386
  anymore.
387

388
  """
389
  cmd = ("tmp=$(tempfile --prefix .gnt --directory=$(dirname %s)) && "
390
         "[[ -f \"$tmp\" ]] && "
391
         "cp %s $tmp && "
392
         "echo $tmp") % (utils.ShellQuote(path), utils.ShellQuote(path))
393

    
394
  # Return temporary filename
395
  return GetCommandOutput(node, cmd).strip()
396

    
397

    
398
def _ResolveName(cmd, key):
399
  """Helper function.
400

401
  """
402
  master = qa_config.GetMasterNode()
403

    
404
  output = GetCommandOutput(master.primary, utils.ShellQuoteArgs(cmd))
405
  for line in output.splitlines():
406
    (lkey, lvalue) = line.split(":", 1)
407
    if lkey == key:
408
      return lvalue.lstrip()
409
  raise KeyError("Key not found")
410

    
411

    
412
def ResolveInstanceName(instance):
413
  """Gets the full name of an instance.
414

415
  @type instance: string
416
  @param instance: Instance name
417

418
  """
419
  return _ResolveName(["gnt-instance", "info", instance],
420
                      "Instance name")
421

    
422

    
423
def ResolveNodeName(node):
424
  """Gets the full name of a node.
425

426
  """
427
  return _ResolveName(["gnt-node", "info", node.primary],
428
                      "Node name")
429

    
430

    
431
def GetNodeInstances(node, secondaries=False):
432
  """Gets a list of instances on a node.
433

434
  """
435
  master = qa_config.GetMasterNode()
436
  node_name = ResolveNodeName(node)
437

    
438
  # Get list of all instances
439
  cmd = ["gnt-instance", "list", "--separator=:", "--no-headers",
440
         "--output=name,pnode,snodes"]
441
  output = GetCommandOutput(master.primary, utils.ShellQuoteArgs(cmd))
442

    
443
  instances = []
444
  for line in output.splitlines():
445
    (name, pnode, snodes) = line.split(":", 2)
446
    if ((not secondaries and pnode == node_name) or
447
        (secondaries and node_name in snodes.split(","))):
448
      instances.append(name)
449

    
450
  return instances
451

    
452

    
453
def _SelectQueryFields(rnd, fields):
454
  """Generates a list of fields for query tests.
455

456
  """
457
  # Create copy for shuffling
458
  fields = list(fields)
459
  rnd.shuffle(fields)
460

    
461
  # Check all fields
462
  yield fields
463
  yield sorted(fields)
464

    
465
  # Duplicate fields
466
  yield fields + fields
467

    
468
  # Check small groups of fields
469
  while fields:
470
    yield [fields.pop() for _ in range(rnd.randint(2, 10)) if fields]
471

    
472

    
473
def _List(listcmd, fields, names):
474
  """Runs a list command.
475

476
  """
477
  master = qa_config.GetMasterNode()
478

    
479
  cmd = [listcmd, "list", "--separator=|", "--no-headers",
480
         "--output", ",".join(fields)]
481

    
482
  if names:
483
    cmd.extend(names)
484

    
485
  return GetCommandOutput(master.primary,
486
                          utils.ShellQuoteArgs(cmd)).splitlines()
487

    
488

    
489
def GenericQueryTest(cmd, fields, namefield="name", test_unknown=True):
490
  """Runs a number of tests on query commands.
491

492
  @param cmd: Command name
493
  @param fields: List of field names
494

495
  """
496
  rnd = random.Random(hash(cmd))
497

    
498
  fields = list(fields)
499
  rnd.shuffle(fields)
500

    
501
  # Test a number of field combinations
502
  for testfields in _SelectQueryFields(rnd, fields):
503
    AssertRedirectedCommand([cmd, "list", "--output", ",".join(testfields)])
504

    
505
  if namefield is not None:
506
    namelist_fn = compat.partial(_List, cmd, [namefield])
507

    
508
    # When no names were requested, the list must be sorted
509
    names = namelist_fn(None)
510
    AssertEqual(names, utils.NiceSort(names))
511

    
512
    # When requesting specific names, the order must be kept
513
    revnames = list(reversed(names))
514
    AssertEqual(namelist_fn(revnames), revnames)
515

    
516
    randnames = list(names)
517
    rnd.shuffle(randnames)
518
    AssertEqual(namelist_fn(randnames), randnames)
519

    
520
  if test_unknown:
521
    # Listing unknown items must fail
522
    AssertCommand([cmd, "list", "this.name.certainly.does.not.exist"],
523
                  fail=True)
524

    
525
  # Check exit code for listing unknown field
526
  AssertEqual(AssertRedirectedCommand([cmd, "list",
527
                                       "--output=field/does/not/exist"],
528
                                      fail=True),
529
              constants.EXIT_UNKNOWN_FIELD)
530

    
531

    
532
def GenericQueryFieldsTest(cmd, fields):
533
  master = qa_config.GetMasterNode()
534

    
535
  # Listing fields
536
  AssertRedirectedCommand([cmd, "list-fields"])
537
  AssertRedirectedCommand([cmd, "list-fields"] + fields)
538

    
539
  # Check listed fields (all, must be sorted)
540
  realcmd = [cmd, "list-fields", "--separator=|", "--no-headers"]
541
  output = GetCommandOutput(master.primary,
542
                            utils.ShellQuoteArgs(realcmd)).splitlines()
543
  AssertEqual([line.split("|", 1)[0] for line in output],
544
              utils.NiceSort(fields))
545

    
546
  # Check exit code for listing unknown field
547
  AssertEqual(AssertCommand([cmd, "list-fields", "field/does/not/exist"],
548
                            fail=True),
549
              constants.EXIT_UNKNOWN_FIELD)
550

    
551

    
552
def _FormatWithColor(text, seq):
553
  if not seq:
554
    return text
555
  return "%s%s%s" % (seq, text, _RESET_SEQ)
556

    
557

    
558
FormatWarning = lambda text: _FormatWithColor(text, _WARNING_SEQ)
559
FormatError = lambda text: _FormatWithColor(text, _ERROR_SEQ)
560
FormatInfo = lambda text: _FormatWithColor(text, _INFO_SEQ)
561

    
562

    
563
def AddToEtcHosts(hostnames):
564
  """Adds hostnames to /etc/hosts.
565

566
  @param hostnames: List of hostnames first used A records, all other CNAMEs
567

568
  """
569
  master = qa_config.GetMasterNode()
570
  tmp_hosts = UploadData(master.primary, "", mode=0644)
571

    
572
  data = []
573
  for localhost in ("::1", "127.0.0.1"):
574
    data.append("%s %s" % (localhost, " ".join(hostnames)))
575

    
576
  try:
577
    AssertCommand("{ cat %s && echo -e '%s'; } > %s && mv %s %s" %
578
                  (utils.ShellQuote(pathutils.ETC_HOSTS),
579
                   "\\n".join(data),
580
                   utils.ShellQuote(tmp_hosts),
581
                   utils.ShellQuote(tmp_hosts),
582
                   utils.ShellQuote(pathutils.ETC_HOSTS)))
583
  except Exception:
584
    AssertCommand(["rm", "-f", tmp_hosts])
585
    raise
586

    
587

    
588
def RemoveFromEtcHosts(hostnames):
589
  """Remove hostnames from /etc/hosts.
590

591
  @param hostnames: List of hostnames first used A records, all other CNAMEs
592

593
  """
594
  master = qa_config.GetMasterNode()
595
  tmp_hosts = UploadData(master.primary, "", mode=0644)
596
  quoted_tmp_hosts = utils.ShellQuote(tmp_hosts)
597

    
598
  sed_data = " ".join(hostnames)
599
  try:
600
    AssertCommand(("sed -e '/^\(::1\|127\.0\.0\.1\)\s\+%s/d' %s > %s"
601
                   " && mv %s %s") %
602
                   (sed_data, utils.ShellQuote(pathutils.ETC_HOSTS),
603
                    quoted_tmp_hosts, quoted_tmp_hosts,
604
                    utils.ShellQuote(pathutils.ETC_HOSTS)))
605
  except Exception:
606
    AssertCommand(["rm", "-f", tmp_hosts])
607
    raise
608

    
609

    
610
def RunInstanceCheck(instance, running):
611
  """Check if instance is running or not.
612

613
  """
614
  instance_name = _GetName(instance, operator.attrgetter("name"))
615

    
616
  script = qa_config.GetInstanceCheckScript()
617
  if not script:
618
    return
619

    
620
  master_node = qa_config.GetMasterNode()
621

    
622
  # Build command to connect to master node
623
  master_ssh = GetSSHCommand(master_node.primary, "--")
624

    
625
  if running:
626
    running_shellval = "1"
627
    running_text = ""
628
  else:
629
    running_shellval = ""
630
    running_text = "not "
631

    
632
  print FormatInfo("Checking if instance '%s' is %srunning" %
633
                   (instance_name, running_text))
634

    
635
  args = [script, instance_name]
636
  env = {
637
    "PATH": constants.HOOKS_PATH,
638
    "RUN_UUID": _RUN_UUID,
639
    "MASTER_SSH": utils.ShellQuoteArgs(master_ssh),
640
    "INSTANCE_NAME": instance_name,
641
    "INSTANCE_RUNNING": running_shellval,
642
    }
643

    
644
  result = os.spawnve(os.P_WAIT, script, args, env)
645
  if result != 0:
646
    raise qa_error.Error("Instance check failed with result %s" % result)
647

    
648

    
649
def _InstanceCheckInner(expected, instarg, args, result):
650
  """Helper function used by L{InstanceCheck}.
651

652
  """
653
  if instarg == FIRST_ARG:
654
    instance = args[0]
655
  elif instarg == RETURN_VALUE:
656
    instance = result
657
  else:
658
    raise Exception("Invalid value '%s' for instance argument" % instarg)
659

    
660
  if expected in (INST_DOWN, INST_UP):
661
    RunInstanceCheck(instance, (expected == INST_UP))
662
  elif expected is not None:
663
    raise Exception("Invalid value '%s'" % expected)
664

    
665

    
666
def InstanceCheck(before, after, instarg):
667
  """Decorator to check instance status before and after test.
668

669
  @param before: L{INST_DOWN} if instance must be stopped before test,
670
    L{INST_UP} if instance must be running before test, L{None} to not check.
671
  @param after: L{INST_DOWN} if instance must be stopped after test,
672
    L{INST_UP} if instance must be running after test, L{None} to not check.
673
  @param instarg: L{FIRST_ARG} to use first argument to test as instance (a
674
    dictionary), L{RETURN_VALUE} to use return value (disallows pre-checks)
675

676
  """
677
  def decorator(fn):
678
    @functools.wraps(fn)
679
    def wrapper(*args, **kwargs):
680
      _InstanceCheckInner(before, instarg, args, NotImplemented)
681

    
682
      result = fn(*args, **kwargs)
683

    
684
      _InstanceCheckInner(after, instarg, args, result)
685

    
686
      return result
687
    return wrapper
688
  return decorator
689

    
690

    
691
def GetNonexistentGroups(count):
692
  """Gets group names which shouldn't exist on the cluster.
693

694
  @param count: Number of groups to get
695
  @rtype: integer
696

697
  """
698
  return GetNonexistentEntityNames(count, "groups", "group")
699

    
700

    
701
def GetNonexistentEntityNames(count, name_config, name_prefix):
702
  """Gets entity names which shouldn't exist on the cluster.
703

704
  The actualy names can refer to arbitrary entities (for example
705
  groups, networks).
706

707
  @param count: Number of names to get
708
  @rtype: integer
709
  @param name_config: name of the leaf in the config containing
710
    this entity's configuration, including a 'inexistent-'
711
    element
712
  @rtype: string
713
  @param name_prefix: prefix of the entity's names, used to compose
714
    the default values; for example for groups, the prefix is
715
    'group' and the generated names are then group1, group2, ...
716
  @rtype: string
717

718
  """
719
  entities = qa_config.get(name_config, {})
720

    
721
  default = [name_prefix + str(i) for i in range(count)]
722
  assert count <= len(default)
723

    
724
  name_config_inexistent = "inexistent-" + name_config
725
  candidates = entities.get(name_config_inexistent, default)[:count]
726

    
727
  if len(candidates) < count:
728
    raise Exception("At least %s non-existent %s are needed" %
729
                    (count, name_config))
730

    
731
  return candidates