Revision 6395cebb

b/Makefile.am
336 336
	test/ganeti.opcodes_unittest.py \
337 337
	test/ganeti.rapi.client_unittest.py \
338 338
	test/ganeti.rapi.resources_unittest.py \
339
	test/ganeti.rapi.rlib2_unittest.py \
339 340
	test/ganeti.serializer_unittest.py \
340 341
	test/ganeti.ssh_unittest.py \
341 342
	test/ganeti.uidpool_unittest.py \
b/doc/rapi.rst
369 369

  
370 370
Returns: a job ID that can be used later for polling.
371 371

  
372
Body parameters:
373

  
374
``__version__`` (int, required)
375
  Must be ``1`` (older Ganeti versions used a different format for
376
  instance creation requests, version ``0``, but that format is not
377
  documented).
378
``name`` (string, required)
379
  Instance name
380
``disk_template`` (string, required)
381
  Disk template for instance
382
``disks`` (list, required)
383
  List of disk definitions. Example: ``[{"size": 100}, {"size": 5}]``.
384
  Each disk definition must contain a ``size`` value and can contain an
385
  optional ``mode`` value denoting the disk access mode (``ro`` or
386
  ``rw``).
387
``nics`` (list, required)
388
  List of NIC (network interface) definitions. Example: ``[{}, {},
389
  {"ip": "1.2.3.4"}]``. Each NIC definition can contain the optional
390
  values ``ip``, ``mode``, ``link`` and ``bridge``.
391
``os`` (string)
392
  Instance operating system.
393
``force_variant`` (bool)
394
  Whether to force an unknown variant.
395
``pnode`` (string)
396
  Primary node.
397
``snode`` (string)
398
  Secondary node.
399
``src_node`` (string)
400
  Source node for import.
401
``src_path`` (string)
402
  Source directory for import.
403
``start`` (bool)
404
  Whether to start instance after creation.
405
``ip_check`` (bool)
406
  Whether to ensure instance's IP address is inactive.
407
``name_check`` (bool)
408
  Whether to ensure instance's name is resolvable.
409
``file_storage_dir`` (string)
410
  File storage directory.
411
``file_driver`` (string)
412
  File storage driver.
413
``iallocator`` (string)
414
  Instance allocator name.
415
``hypervisor`` (string)
416
  Hypervisor name.
417
``hvparams`` (dict)
418
  Hypervisor parameters, hypervisor-dependent.
419
``beparams``
420
  Backend parameters.
421

  
422

  
372 423
``/2/instances/[instance_name]``
373 424
++++++++++++++++++++++++++++++++
374 425

  
b/lib/rapi/rlib2.py
45 45
from ganeti import http
46 46
from ganeti import constants
47 47
from ganeti import cli
48
from ganeti import utils
48 49
from ganeti import rapi
49 50
from ganeti.rapi import baserlib
50 51

  
......
86 87
# Request data version field
87 88
_REQ_DATA_VERSION = "__version__"
88 89

  
90
# Feature string for instance creation request data version 1
91
_INST_CREATE_REQV1 = "instance-create-reqv1"
92

  
89 93
# Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change.
90 94
_WFJC_TIMEOUT = 10
91 95

  
......
127 131
    """Returns list of optional RAPI features implemented.
128 132

  
129 133
    """
130
    return []
134
    return [_INST_CREATE_REQV1]
131 135

  
132 136

  
133 137
class R_2_os(baserlib.R_Generic):
......
486 490
    return baserlib.SubmitJob([op])
487 491

  
488 492

  
493
def _ParseInstanceCreateRequestVersion1(data, dry_run):
494
  """Parses an instance creation request version 1.
495

  
496
  @rtype: L{opcodes.OpCreateInstance}
497
  @return: Instance creation opcode
498

  
499
  """
500
  # Disks
501
  disks_input = baserlib.CheckParameter(data, "disks", exptype=list)
502

  
503
  disks = []
504
  for idx, i in enumerate(disks_input):
505
    baserlib.CheckType(i, dict, "Disk %d specification" % idx)
506

  
507
    # Size is mandatory
508
    try:
509
      size = i["size"]
510
    except KeyError:
511
      raise http.HttpBadRequest("Disk %d specification wrong: missing disk"
512
                                " size" % idx)
513

  
514
    disk = {
515
      "size": size,
516
      }
517

  
518
    # Optional disk access mode
519
    try:
520
      disk_access = i["mode"]
521
    except KeyError:
522
      pass
523
    else:
524
      disk["mode"] = disk_access
525

  
526
    disks.append(disk)
527

  
528
  assert len(disks_input) == len(disks)
529

  
530
  # Network interfaces
531
  nics_input = baserlib.CheckParameter(data, "nics", exptype=list)
532

  
533
  nics = []
534
  for idx, i in enumerate(nics_input):
535
    baserlib.CheckType(i, dict, "NIC %d specification" % idx)
536

  
537
    nic = {}
538

  
539
    for field in ["mode", "ip", "link", "bridge"]:
540
      try:
541
        value = i[field]
542
      except KeyError:
543
        continue
544

  
545
      nic[field] = value
546

  
547
    nics.append(nic)
548

  
549
  assert len(nics_input) == len(nics)
550

  
551
  # HV/BE parameters
552
  hvparams = baserlib.CheckParameter(data, "hvparams", default={})
553
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
554

  
555
  beparams = baserlib.CheckParameter(data, "beparams", default={})
556
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
557

  
558
  return opcodes.OpCreateInstance(
559
    mode=baserlib.CheckParameter(data, "mode"),
560
    instance_name=baserlib.CheckParameter(data, "name"),
561
    os_type=baserlib.CheckParameter(data, "os", default=None),
562
    force_variant=baserlib.CheckParameter(data, "force_variant",
563
                                          default=False),
564
    pnode=baserlib.CheckParameter(data, "pnode", default=None),
565
    snode=baserlib.CheckParameter(data, "snode", default=None),
566
    disk_template=baserlib.CheckParameter(data, "disk_template"),
567
    disks=disks,
568
    nics=nics,
569
    src_node=baserlib.CheckParameter(data, "src_node", default=None),
570
    src_path=baserlib.CheckParameter(data, "src_path", default=None),
571
    start=baserlib.CheckParameter(data, "start", default=True),
572
    wait_for_sync=True,
573
    ip_check=baserlib.CheckParameter(data, "ip_check", default=True),
574
    name_check=baserlib.CheckParameter(data, "name_check", default=True),
575
    file_storage_dir=baserlib.CheckParameter(data, "file_storage_dir",
576
                                             default=None),
577
    file_driver=baserlib.CheckParameter(data, "file_driver",
578
                                        default=constants.FD_LOOP),
579
    iallocator=baserlib.CheckParameter(data, "iallocator", default=None),
580
    hypervisor=baserlib.CheckParameter(data, "hypervisor", default=None),
581
    hvparams=hvparams,
582
    beparams=beparams,
583
    dry_run=dry_run,
584
    )
585

  
586

  
489 587
class R_2_instances(baserlib.R_Generic):
490 588
  """/2/instances resource.
491 589

  
......
509 607
  def _ParseVersion0CreateRequest(self):
510 608
    """Parses an instance creation request version 0.
511 609

  
610
    Request data version 0 is deprecated and should not be used anymore.
611

  
512 612
    @rtype: L{opcodes.OpCreateInstance}
513 613
    @return: Instance creation opcode
514 614

  
515 615
    """
616
    # Do not modify anymore, request data version 0 is deprecated
516 617
    beparams = baserlib.MakeParamsDict(self.req.request_body,
517 618
                                       constants.BES_PARAMETERS)
518 619
    hvparams = baserlib.MakeParamsDict(self.req.request_body,
......
541 642
    if fn("bridge", None) is not None:
542 643
      nics[0]["bridge"] = fn("bridge")
543 644

  
645
    # Do not modify anymore, request data version 0 is deprecated
544 646
    return opcodes.OpCreateInstance(
545 647
      mode=constants.INSTANCE_CREATE,
546 648
      instance_name=fn('name'),
......
559 661
      hvparams=hvparams,
560 662
      beparams=beparams,
561 663
      file_storage_dir=fn('file_storage_dir', None),
562
      file_driver=fn('file_driver', 'loop'),
664
      file_driver=fn('file_driver', constants.FD_LOOP),
563 665
      dry_run=bool(self.dryRun()),
564 666
      )
565 667

  
......
577 679

  
578 680
    if data_version == 0:
579 681
      op = self._ParseVersion0CreateRequest()
682
    elif data_version == 1:
683
      op = _ParseInstanceCreateRequestVersion1(self.req.request_body,
684
                                               self.dryRun())
580 685
    else:
581 686
      raise http.HttpBadRequest("Unsupported request data version %s" %
582 687
                                data_version)
b/test/ganeti.rapi.rlib2_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2010 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
"""Script for unittesting the RAPI rlib2 module
23

  
24
"""
25

  
26

  
27
import unittest
28
import tempfile
29

  
30
from ganeti import constants
31
from ganeti import opcodes
32
from ganeti import compat
33
from ganeti import http
34

  
35
from ganeti.rapi import rlib2
36

  
37
import testutils
38

  
39

  
40
class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
41
  def setUp(self):
42
    testutils.GanetiTestCase.setUp(self)
43

  
44
    self.Parse = rlib2._ParseInstanceCreateRequestVersion1
45

  
46
  def test(self):
47
    disk_variants = [
48
      # No disks
49
      [],
50

  
51
      # Two disks
52
      [{"size": 5, }, {"size": 100, }],
53

  
54
      # Disk with mode
55
      [{"size": 123, "mode": constants.DISK_RDWR, }],
56

  
57
      # With unknown setting
58
      [{"size": 123, "unknown": 999 }],
59
      ]
60

  
61
    nic_variants = [
62
      # No NIC
63
      [],
64

  
65
      # Three NICs
66
      [{}, {}, {}],
67

  
68
      # Two NICs
69
      [
70
        { "ip": "1.2.3.4", "mode": constants.NIC_MODE_ROUTED, },
71
        { "mode": constants.NIC_MODE_BRIDGED, "link": "n0", "bridge": "br1", },
72
      ],
73

  
74
      # Unknown settings
75
      [{ "unknown": 999, }, { "foobar": "Hello World", }],
76
      ]
77

  
78
    beparam_variants = [
79
      None,
80
      {},
81
      { constants.BE_VCPUS: 2, },
82
      { constants.BE_MEMORY: 123, },
83
      { constants.BE_VCPUS: 2,
84
        constants.BE_MEMORY: 1024,
85
        constants.BE_AUTO_BALANCE: True, }
86
      ]
87

  
88
    hvparam_variants = [
89
      None,
90
      { constants.HV_BOOT_ORDER: "anc", },
91
      { constants.HV_KERNEL_PATH: "/boot/fookernel",
92
        constants.HV_ROOT_PATH: "/dev/hda1", },
93
      ]
94

  
95
    for mode in [constants.INSTANCE_CREATE, constants.INSTANCE_IMPORT]:
96
      for nics in nic_variants:
97
        for disk_template in constants.DISK_TEMPLATES:
98
          for disks in disk_variants:
99
            for beparams in beparam_variants:
100
              for hvparams in hvparam_variants:
101
                data = {
102
                  "name": "inst1.example.com",
103
                  "hypervisor": constants.HT_FAKE,
104
                  "disks": disks,
105
                  "nics": nics,
106
                  "mode": mode,
107
                  "disk_template": disk_template,
108
                  }
109

  
110
                if beparams is not None:
111
                  data["beparams"] = beparams
112

  
113
                if hvparams is not None:
114
                  data["hvparams"] = hvparams
115

  
116
                for dry_run in [False, True]:
117
                  op = self.Parse(data, dry_run)
118
                  self.assert_(isinstance(op, opcodes.OpCreateInstance))
119
                  self.assertEqual(op.mode, mode)
120
                  self.assertEqual(op.disk_template, disk_template)
121
                  self.assertEqual(op.dry_run, dry_run)
122
                  self.assertEqual(len(op.disks), len(disks))
123
                  self.assertEqual(len(op.nics), len(nics))
124

  
125
                  self.assert_(compat.all(opdisk.get("size") ==
126
                                          disk.get("size") and
127
                                          opdisk.get("mode") ==
128
                                          disk.get("mode") and
129
                                          "unknown" not in opdisk
130
                                          for opdisk, disk in zip(op.disks,
131
                                                                  disks)))
132

  
133
                  self.assert_(compat.all(opnic.get("size") ==
134
                                          nic.get("size") and
135
                                          opnic.get("mode") ==
136
                                          nic.get("mode") and
137
                                          "unknown" not in opnic and
138
                                          "foobar" not in opnic
139
                                          for opnic, nic in zip(op.nics, nics)))
140

  
141
                  if beparams is None:
142
                    self.assertEqualValues(op.beparams, {})
143
                  else:
144
                    self.assertEqualValues(op.beparams, beparams)
145

  
146
                  if hvparams is None:
147
                    self.assertEqualValues(op.hvparams, {})
148
                  else:
149
                    self.assertEqualValues(op.hvparams, hvparams)
150

  
151
  def testErrors(self):
152
    # Test all required fields
153
    reqfields = {
154
      "name": "inst1.example.com",
155
      "disks": [],
156
      "nics": [],
157
      "mode": constants.INSTANCE_CREATE,
158
      "disk_template": constants.DT_PLAIN
159
      }
160

  
161
    for name in reqfields.keys():
162
      self.assertRaises(http.HttpBadRequest, self.Parse,
163
                        dict(i for i in reqfields.iteritems() if i[0] != name),
164
                        False)
165

  
166
    # Invalid disks and nics
167
    for field in ["disks", "nics"]:
168
      invalid_values = [None, 1, "", {}, [1, 2, 3], ["hda1", "hda2"]]
169

  
170
      if field == "disks":
171
        invalid_values.append([
172
          # Disks without size
173
          {},
174
          { "mode": constants.DISK_RDWR, },
175
          ])
176

  
177
      for invvalue in invalid_values:
178
        data = reqfields.copy()
179
        data[field] = invvalue
180
        self.assertRaises(http.HttpBadRequest, self.Parse, data, False)
181

  
182

  
183
if __name__ == '__main__':
184
  testutils.GanetiTestProgram()

Also available in: Unified diff