Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ 96a12113

History | View | Annotate | Download (18.8 kB)

1
#
2

    
3
# Copyright (C) 2007, 2008, 2009, 2010, 2011 Google Inc.
4
#
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
# General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18
# 02110-1301, USA.
19

    
20

    
21
"""Remote API QA tests.
22

23
"""
24

    
25
import tempfile
26
import random
27

    
28
from ganeti import utils
29
from ganeti import constants
30
from ganeti import errors
31
from ganeti import cli
32
from ganeti import rapi
33
from ganeti import objects
34
from ganeti import query
35
from ganeti import compat
36
from ganeti import qlang
37

    
38
import ganeti.rapi.client        # pylint: disable-msg=W0611
39
import ganeti.rapi.client_utils
40

    
41
import qa_config
42
import qa_utils
43
import qa_error
44

    
45
from qa_utils import (AssertEqual, AssertIn, AssertMatch, StartLocalCommand)
46

    
47

    
48
_rapi_ca = None
49
_rapi_client = None
50
_rapi_username = None
51
_rapi_password = None
52

    
53

    
54
def Setup(username, password):
55
  """Configures the RAPI client.
56

57
  """
58
  # pylint: disable-msg=W0603
59
  # due to global usage
60
  global _rapi_ca
61
  global _rapi_client
62
  global _rapi_username
63
  global _rapi_password
64

    
65
  _rapi_username = username
66
  _rapi_password = password
67

    
68
  master = qa_config.GetMasterNode()
69

    
70
  # Load RAPI certificate from master node
71
  cmd = ["cat", constants.RAPI_CERT_FILE]
72

    
73
  # Write to temporary file
74
  _rapi_ca = tempfile.NamedTemporaryFile()
75
  _rapi_ca.write(qa_utils.GetCommandOutput(master["primary"],
76
                                           utils.ShellQuoteArgs(cmd)))
77
  _rapi_ca.flush()
78

    
79
  port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
80
  cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name,
81
                                           proxy="")
82

    
83
  _rapi_client = rapi.client.GanetiRapiClient(master["primary"], port=port,
84
                                              username=username,
85
                                              password=password,
86
                                              curl_config_fn=cfg_curl)
87

    
88
  print "RAPI protocol version: %s" % _rapi_client.GetVersion()
89

    
90

    
91
INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
92
                   "admin_state",
93
                   "disk_template", "disk.sizes",
94
                   "nic.ips", "nic.macs", "nic.modes", "nic.links",
95
                   "beparams", "hvparams",
96
                   "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
97

    
98
NODE_FIELDS = ("name", "dtotal", "dfree",
99
               "mtotal", "mnode", "mfree",
100
               "pinst_cnt", "sinst_cnt", "tags")
101

    
102
GROUP_FIELDS = frozenset([
103
  "name", "uuid",
104
  "alloc_policy",
105
  "node_cnt", "node_list",
106
  ])
107

    
108
JOB_FIELDS = frozenset([
109
  "id", "ops", "status", "summary",
110
  "opstatus", "opresult", "oplog",
111
  "received_ts", "start_ts", "end_ts",
112
  ])
113

    
114
LIST_FIELDS = ("id", "uri")
115

    
116

    
117
def Enabled():
118
  """Return whether remote API tests should be run.
119

120
  """
121
  return qa_config.TestEnabled('rapi')
122

    
123

    
124
def _DoTests(uris):
125
  # pylint: disable-msg=W0212
126
  # due to _SendRequest usage
127
  results = []
128

    
129
  for uri, verify, method, body in uris:
130
    assert uri.startswith("/")
131

    
132
    print "%s %s" % (method, uri)
133
    data = _rapi_client._SendRequest(method, uri, None, body)
134

    
135
    if verify is not None:
136
      if callable(verify):
137
        verify(data)
138
      else:
139
        AssertEqual(data, verify)
140

    
141
    results.append(data)
142

    
143
  return results
144

    
145

    
146
def _VerifyReturnsJob(data):
147
  AssertMatch(data, r'^\d+$')
148

    
149

    
150
def TestVersion():
151
  """Testing remote API version.
152

153
  """
154
  _DoTests([
155
    ("/version", constants.RAPI_VERSION, 'GET', None),
156
    ])
157

    
158

    
159
def TestEmptyCluster():
160
  """Testing remote API on an empty cluster.
161

162
  """
163
  master = qa_config.GetMasterNode()
164
  master_full = qa_utils.ResolveNodeName(master)
165

    
166
  def _VerifyInfo(data):
167
    AssertIn("name", data)
168
    AssertIn("master", data)
169
    AssertEqual(data["master"], master_full)
170

    
171
  def _VerifyNodes(data):
172
    master_entry = {
173
      "id": master_full,
174
      "uri": "/2/nodes/%s" % master_full,
175
      }
176
    AssertIn(master_entry, data)
177

    
178
  def _VerifyNodesBulk(data):
179
    for node in data:
180
      for entry in NODE_FIELDS:
181
        AssertIn(entry, node)
182

    
183
  def _VerifyGroups(data):
184
    default_group = {
185
      "name": constants.INITIAL_NODE_GROUP_NAME,
186
      "uri": "/2/groups/" + constants.INITIAL_NODE_GROUP_NAME,
187
      }
188
    AssertIn(default_group, data)
189

    
190
  def _VerifyGroupsBulk(data):
191
    for group in data:
192
      for field in GROUP_FIELDS:
193
        AssertIn(field, group)
194

    
195
  _DoTests([
196
    ("/", None, 'GET', None),
197
    ("/2/info", _VerifyInfo, 'GET', None),
198
    ("/2/tags", None, 'GET', None),
199
    ("/2/nodes", _VerifyNodes, 'GET', None),
200
    ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
201
    ("/2/groups", _VerifyGroups, 'GET', None),
202
    ("/2/groups?bulk=1", _VerifyGroupsBulk, 'GET', None),
203
    ("/2/instances", [], 'GET', None),
204
    ("/2/instances?bulk=1", [], 'GET', None),
205
    ("/2/os", None, 'GET', None),
206
    ])
207

    
208
  # Test HTTP Not Found
209
  for method in ["GET", "PUT", "POST", "DELETE"]:
210
    try:
211
      _DoTests([("/99/resource/not/here/99", None, method, None)])
212
    except rapi.client.GanetiApiError, err:
213
      AssertEqual(err.code, 404)
214
    else:
215
      raise qa_error.Error("Non-existent resource didn't return HTTP 404")
216

    
217
  # Test HTTP Not Implemented
218
  for method in ["PUT", "POST", "DELETE"]:
219
    try:
220
      _DoTests([("/version", None, method, None)])
221
    except rapi.client.GanetiApiError, err:
222
      AssertEqual(err.code, 501)
223
    else:
224
      raise qa_error.Error("Non-implemented method didn't fail")
225

    
226

    
227
def TestRapiQuery():
228
  """Testing resource queries via remote API.
229

230
  """
231
  master_name = qa_utils.ResolveNodeName(qa_config.GetMasterNode())
232
  rnd = random.Random(7818)
233

    
234
  for what in constants.QR_VIA_RAPI:
235
    all_fields = query.ALL_FIELDS[what].keys()
236
    rnd.shuffle(all_fields)
237

    
238
    # No fields, should return everything
239
    result = _rapi_client.QueryFields(what)
240
    qresult = objects.QueryFieldsResponse.FromDict(result)
241
    AssertEqual(len(qresult.fields), len(all_fields))
242

    
243
    # One field
244
    result = _rapi_client.QueryFields(what, fields=["name"])
245
    qresult = objects.QueryFieldsResponse.FromDict(result)
246
    AssertEqual(len(qresult.fields), 1)
247

    
248
    # Specify all fields, order must be correct
249
    result = _rapi_client.QueryFields(what, fields=all_fields)
250
    qresult = objects.QueryFieldsResponse.FromDict(result)
251
    AssertEqual(len(qresult.fields), len(all_fields))
252
    AssertEqual([fdef.name for fdef in qresult.fields], all_fields)
253

    
254
    # Unknown field
255
    result = _rapi_client.QueryFields(what, fields=["_unknown!"])
256
    qresult = objects.QueryFieldsResponse.FromDict(result)
257
    AssertEqual(len(qresult.fields), 1)
258
    AssertEqual(qresult.fields[0].name, "_unknown!")
259
    AssertEqual(qresult.fields[0].kind, constants.QFT_UNKNOWN)
260

    
261
    # Try once more, this time without the client
262
    _DoTests([
263
      ("/2/query/%s/fields" % what, None, "GET", None),
264
      ("/2/query/%s/fields?fields=name,name,%s" % (what, all_fields[0]),
265
       None, "GET", None),
266
      ])
267

    
268
    # Try missing query argument
269
    try:
270
      _DoTests([
271
        ("/2/query/%s" % what, None, "GET", None),
272
        ])
273
    except rapi.client.GanetiApiError, err:
274
      AssertEqual(err.code, 400)
275
    else:
276
      raise qa_error.Error("Request missing 'fields' parameter didn't fail")
277

    
278
    def _Check(exp_fields, data):
279
      qresult = objects.QueryResponse.FromDict(data)
280
      AssertEqual([fdef.name for fdef in qresult.fields], exp_fields)
281
      if not isinstance(qresult.data, list):
282
        raise qa_error.Error("Query did not return a list")
283

    
284
    _DoTests([
285
      # Specify fields in query
286
      ("/2/query/%s?fields=%s" % (what, ",".join(all_fields)),
287
       compat.partial(_Check, all_fields), "GET", None),
288

    
289
      ("/2/query/%s?fields=name" % what,
290
       compat.partial(_Check, ["name"]), "GET", None),
291

    
292
      # Note the spaces
293
      ("/2/query/%s?fields=name,%%20name%%09,name%%20" % what,
294
       compat.partial(_Check, ["name"] * 3), "GET", None),
295

    
296
      # PUT with fields in query
297
      ("/2/query/%s?fields=name" % what,
298
       compat.partial(_Check, ["name"]), "PUT", {}),
299

    
300
      # Fields in body
301
      ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
302
         "fields": all_fields,
303
         }),
304

    
305
      ("/2/query/%s" % what, compat.partial(_Check, ["name"] * 4), "PUT", {
306
         "fields": ["name"] * 4,
307
         }),
308
      ])
309

    
310
    def _CheckFilter():
311
      _DoTests([
312
        # With filter
313
        ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
314
           "fields": all_fields,
315
           "filter": [qlang.OP_TRUE, "name"],
316
           }),
317
        ])
318

    
319
    if what == constants.QR_LOCK:
320
      # Locks can't be filtered
321
      try:
322
        _CheckFilter()
323
      except rapi.client.GanetiApiError, err:
324
        AssertEqual(err.code, 500)
325
      else:
326
        raise qa_error.Error("Filtering locks didn't fail")
327
    else:
328
      _CheckFilter()
329

    
330
    if what == constants.QR_NODE:
331
      # Test with filter
332
      (nodes, ) = _DoTests([("/2/query/%s" % what,
333
        compat.partial(_Check, ["name", "master"]), "PUT", {
334
        "fields": ["name", "master"],
335
        "filter": [qlang.OP_TRUE, "master"],
336
        })])
337
      qresult = objects.QueryResponse.FromDict(nodes)
338
      AssertEqual(qresult.data, [
339
        [[constants.RS_NORMAL, master_name], [constants.RS_NORMAL, True]],
340
        ])
341

    
342

    
343
def TestInstance(instance):
344
  """Testing getting instance(s) info via remote API.
345

346
  """
347
  def _VerifyInstance(data):
348
    for entry in INSTANCE_FIELDS:
349
      AssertIn(entry, data)
350

    
351
  def _VerifyInstancesList(data):
352
    for instance in data:
353
      for entry in LIST_FIELDS:
354
        AssertIn(entry, instance)
355

    
356
  def _VerifyInstancesBulk(data):
357
    for instance_data in data:
358
      _VerifyInstance(instance_data)
359

    
360
  _DoTests([
361
    ("/2/instances/%s" % instance["name"], _VerifyInstance, 'GET', None),
362
    ("/2/instances", _VerifyInstancesList, 'GET', None),
363
    ("/2/instances?bulk=1", _VerifyInstancesBulk, 'GET', None),
364
    ("/2/instances/%s/activate-disks" % instance["name"],
365
     _VerifyReturnsJob, 'PUT', None),
366
    ("/2/instances/%s/deactivate-disks" % instance["name"],
367
     _VerifyReturnsJob, 'PUT', None),
368
    ])
369

    
370
  # Test OpBackupPrepare
371
  (job_id, ) = _DoTests([
372
    ("/2/instances/%s/prepare-export?mode=%s" %
373
     (instance["name"], constants.EXPORT_MODE_REMOTE),
374
     _VerifyReturnsJob, "PUT", None),
375
    ])
376

    
377
  result = _WaitForRapiJob(job_id)[0]
378
  AssertEqual(len(result["handshake"]), 3)
379
  AssertEqual(result["handshake"][0], constants.RIE_VERSION)
380
  AssertEqual(len(result["x509_key_name"]), 3)
381
  AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
382

    
383

    
384
def TestNode(node):
385
  """Testing getting node(s) info via remote API.
386

387
  """
388
  def _VerifyNode(data):
389
    for entry in NODE_FIELDS:
390
      AssertIn(entry, data)
391

    
392
  def _VerifyNodesList(data):
393
    for node in data:
394
      for entry in LIST_FIELDS:
395
        AssertIn(entry, node)
396

    
397
  def _VerifyNodesBulk(data):
398
    for node_data in data:
399
      _VerifyNode(node_data)
400

    
401
  _DoTests([
402
    ("/2/nodes/%s" % node["primary"], _VerifyNode, 'GET', None),
403
    ("/2/nodes", _VerifyNodesList, 'GET', None),
404
    ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
405
    ])
406

    
407

    
408
def TestTags(kind, name, tags):
409
  """Tests .../tags resources.
410

411
  """
412
  if kind == constants.TAG_CLUSTER:
413
    uri = "/2/tags"
414
  elif kind == constants.TAG_NODE:
415
    uri = "/2/nodes/%s/tags" % name
416
  elif kind == constants.TAG_INSTANCE:
417
    uri = "/2/instances/%s/tags" % name
418
  elif kind == constants.TAG_NODEGROUP:
419
    uri = "/2/groups/%s/tags" % name
420
  else:
421
    raise errors.ProgrammerError("Unknown tag kind")
422

    
423
  def _VerifyTags(data):
424
    AssertEqual(sorted(tags), sorted(data))
425

    
426
  queryargs = "&".join("tag=%s" % i for i in tags)
427

    
428
  # Add tags
429
  (job_id, ) = _DoTests([
430
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "PUT", None),
431
    ])
432
  _WaitForRapiJob(job_id)
433

    
434
  # Retrieve tags
435
  _DoTests([
436
    (uri, _VerifyTags, 'GET', None),
437
    ])
438

    
439
  # Remove tags
440
  (job_id, ) = _DoTests([
441
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "DELETE", None),
442
    ])
443
  _WaitForRapiJob(job_id)
444

    
445

    
446
def _WaitForRapiJob(job_id):
447
  """Waits for a job to finish.
448

449
  """
450
  def _VerifyJob(data):
451
    AssertEqual(data["id"], job_id)
452
    for field in JOB_FIELDS:
453
      AssertIn(field, data)
454

    
455
  _DoTests([
456
    ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
457
    ])
458

    
459
  return rapi.client_utils.PollJob(_rapi_client, job_id,
460
                                   cli.StdioJobPollReportCb())
461

    
462

    
463
def TestRapiNodeGroups():
464
  """Test several node group operations using RAPI.
465

466
  """
467
  groups = qa_config.get("groups", {})
468
  group1, group2, group3 = groups.get("inexistent-groups",
469
                                      ["group1", "group2", "group3"])[:3]
470

    
471
  # Create a group with no attributes
472
  body = {
473
    "name": group1,
474
    }
475

    
476
  (job_id, ) = _DoTests([
477
    ("/2/groups", _VerifyReturnsJob, "POST", body),
478
    ])
479

    
480
  _WaitForRapiJob(job_id)
481

    
482
  # Create a group specifying alloc_policy
483
  body = {
484
    "name": group2,
485
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
486
    }
487

    
488
  (job_id, ) = _DoTests([
489
    ("/2/groups", _VerifyReturnsJob, "POST", body),
490
    ])
491

    
492
  _WaitForRapiJob(job_id)
493

    
494
  # Modify alloc_policy
495
  body = {
496
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
497
    }
498

    
499
  (job_id, ) = _DoTests([
500
    ("/2/groups/%s/modify" % group1, _VerifyReturnsJob, "PUT", body),
501
    ])
502

    
503
  _WaitForRapiJob(job_id)
504

    
505
  # Rename a group
506
  body = {
507
    "new_name": group3,
508
    }
509

    
510
  (job_id, ) = _DoTests([
511
    ("/2/groups/%s/rename" % group2, _VerifyReturnsJob, "PUT", body),
512
    ])
513

    
514
  _WaitForRapiJob(job_id)
515

    
516
  # Delete groups
517
  for group in [group1, group3]:
518
    (job_id, ) = _DoTests([
519
      ("/2/groups/%s" % group, _VerifyReturnsJob, "DELETE", None),
520
      ])
521

    
522
    _WaitForRapiJob(job_id)
523

    
524

    
525
def TestRapiInstanceAdd(node, use_client):
526
  """Test adding a new instance via RAPI"""
527
  instance = qa_config.AcquireInstance()
528
  try:
529
    memory = utils.ParseUnit(qa_config.get("mem"))
530
    disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
531
    disks = [{"size": size} for size in disk_sizes]
532
    nics = [{}]
533

    
534
    beparams = {
535
      constants.BE_MEMORY: memory,
536
      }
537

    
538
    if use_client:
539
      job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
540
                                           instance["name"],
541
                                           constants.DT_PLAIN,
542
                                           disks, nics,
543
                                           os=qa_config.get("os"),
544
                                           pnode=node["primary"],
545
                                           beparams=beparams)
546
    else:
547
      body = {
548
        "__version__": 1,
549
        "mode": constants.INSTANCE_CREATE,
550
        "name": instance["name"],
551
        "os_type": qa_config.get("os"),
552
        "disk_template": constants.DT_PLAIN,
553
        "pnode": node["primary"],
554
        "beparams": beparams,
555
        "disks": disks,
556
        "nics": nics,
557
        }
558

    
559
      (job_id, ) = _DoTests([
560
        ("/2/instances", _VerifyReturnsJob, "POST", body),
561
        ])
562

    
563
    _WaitForRapiJob(job_id)
564

    
565
    return instance
566
  except:
567
    qa_config.ReleaseInstance(instance)
568
    raise
569

    
570

    
571
def TestRapiInstanceRemove(instance, use_client):
572
  """Test removing instance via RAPI"""
573
  if use_client:
574
    job_id = _rapi_client.DeleteInstance(instance["name"])
575
  else:
576
    (job_id, ) = _DoTests([
577
      ("/2/instances/%s" % instance["name"], _VerifyReturnsJob, "DELETE", None),
578
      ])
579

    
580
  _WaitForRapiJob(job_id)
581

    
582
  qa_config.ReleaseInstance(instance)
583

    
584

    
585
def TestRapiInstanceMigrate(instance):
586
  """Test migrating instance via RAPI"""
587
  # Move to secondary node
588
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
589
  # And back to previous primary
590
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
591

    
592

    
593
def TestRapiInstanceRename(rename_source, rename_target):
594
  """Test renaming instance via RAPI"""
595
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
596

    
597

    
598
def TestRapiInstanceReinstall(instance):
599
  """Test reinstalling an instance via RAPI"""
600
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance["name"]))
601

    
602

    
603
def TestRapiInstanceModify(instance):
604
  """Test modifying instance via RAPI"""
605
  def _ModifyInstance(**kwargs):
606
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
607

    
608
  _ModifyInstance(hvparams={
609
    constants.HV_KERNEL_ARGS: "single",
610
    })
611

    
612
  _ModifyInstance(beparams={
613
    constants.BE_VCPUS: 3,
614
    })
615

    
616
  _ModifyInstance(beparams={
617
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
618
    })
619

    
620
  _ModifyInstance(hvparams={
621
    constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
622
    })
623

    
624

    
625
def TestRapiInstanceConsole(instance):
626
  """Test getting instance console information via RAPI"""
627
  result = _rapi_client.GetInstanceConsole(instance["name"])
628
  console = objects.InstanceConsole.FromDict(result)
629
  AssertEqual(console.Validate(), True)
630
  AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance["name"]))
631

    
632

    
633
def TestRapiStoppedInstanceConsole(instance):
634
  """Test getting stopped instance's console information via RAPI"""
635
  try:
636
    _rapi_client.GetInstanceConsole(instance["name"])
637
  except rapi.client.GanetiApiError, err:
638
    AssertEqual(err.code, 503)
639
  else:
640
    raise qa_error.Error("Getting console for stopped instance didn't"
641
                         " return HTTP 503")
642

    
643

    
644
def GetOperatingSystems():
645
  """Retrieves a list of all available operating systems.
646

647
  """
648
  return _rapi_client.GetOperatingSystems()
649

    
650

    
651
def TestInterClusterInstanceMove(src_instance, dest_instance,
652
                                 pnode, snode, tnode):
653
  """Test tools/move-instance"""
654
  master = qa_config.GetMasterNode()
655

    
656
  rapi_pw_file = tempfile.NamedTemporaryFile()
657
  rapi_pw_file.write(_rapi_password)
658
  rapi_pw_file.flush()
659

    
660
  # TODO: Run some instance tests before moving back
661

    
662
  if snode is None:
663
    # instance is not redundant, but we still need to pass a node
664
    # (which will be ignored)
665
    fsec = tnode
666
  else:
667
    fsec = snode
668
  # note: pnode:snode are the *current* nodes, so we move it first to
669
  # tnode:pnode, then back to pnode:snode
670
  for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
671
                          tnode["primary"], pnode["primary"]),
672
                         (dest_instance["name"], src_instance["name"],
673
                          pnode["primary"], fsec["primary"])]:
674
    cmd = [
675
      "../tools/move-instance",
676
      "--verbose",
677
      "--src-ca-file=%s" % _rapi_ca.name,
678
      "--src-username=%s" % _rapi_username,
679
      "--src-password-file=%s" % rapi_pw_file.name,
680
      "--dest-instance-name=%s" % di,
681
      "--dest-primary-node=%s" % pn,
682
      "--dest-secondary-node=%s" % sn,
683
      "--net=0:mac=%s" % constants.VALUE_GENERATE,
684
      master["primary"],
685
      master["primary"],
686
      si,
687
      ]
688

    
689
    AssertEqual(StartLocalCommand(cmd).wait(), 0)