Revision 48436b97

b/lib/rapi/client.py
60 60
# Internal constants
61 61
_REQ_DATA_VERSION_FIELD = "__version__"
62 62
_INST_CREATE_REQV1 = "instance-create-reqv1"
63
_INST_NIC_PARAMS = frozenset(["mac", "ip", "mode", "link", "bridge"])
64
_INST_CREATE_V0_DISK_PARAMS = frozenset(["size"])
65
_INST_CREATE_V0_PARAMS = frozenset([
66
  "os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check",
67
  "hypervisor", "file_storage_dir", "file_driver", "dry_run",
68
  ])
69
_INST_CREATE_V0_DPARAMS = frozenset(["beparams", "hvparams"])
63 70

  
64 71

  
65 72
class Error(Exception):
......
676 683
      body.update((key, value) for key, value in kwargs.iteritems()
677 684
                  if key != "dry_run")
678 685
    else:
679
      # TODO: Implement instance creation request data version 0
680
      # When implementing version 0, care should be taken to refuse unknown
681
      # parameters and invalid values. The interface of this function must stay
686
      # Old request format (version 0)
687

  
688
      # The following code must make sure that an exception is raised when an
689
      # unsupported setting is requested by the caller. Otherwise this can lead
690
      # to bugs difficult to find. The interface of this function must stay
682 691
      # exactly the same for version 0 and 1 (e.g. they aren't allowed to
683 692
      # require different data types).
684
      raise NotImplementedError("Support for instance creation request data"
685
                                " version 0 is not yet implemented")
693

  
694
      # Validate disks
695
      for idx, disk in enumerate(disks):
696
        unsupported = set(disk.keys()) - _INST_CREATE_V0_DISK_PARAMS
697
        if unsupported:
698
          raise GanetiApiError("Server supports request version 0 only, but"
699
                               " disk %s specifies the unsupported parameters"
700
                               " %s, allowed are %s" %
701
                               (idx, unsupported,
702
                                list(_INST_CREATE_V0_DISK_PARAMS)))
703

  
704
      assert (len(_INST_CREATE_V0_DISK_PARAMS) == 1 and
705
              "size" in _INST_CREATE_V0_DISK_PARAMS)
706
      disk_sizes = [disk["size"] for disk in disks]
707

  
708
      # Validate NICs
709
      if not nics:
710
        raise GanetiApiError("Server supports request version 0 only, but"
711
                             " no NIC specified")
712
      elif len(nics) > 1:
713
        raise GanetiApiError("Server supports request version 0 only, but"
714
                             " more than one NIC specified")
715

  
716
      assert len(nics) == 1
717

  
718
      unsupported = set(nics[0].keys()) - _INST_NIC_PARAMS
719
      if unsupported:
720
        raise GanetiApiError("Server supports request version 0 only, but"
721
                             " NIC 0 specifies the unsupported parameters %s,"
722
                             " allowed are %s" %
723
                             (unsupported, list(_INST_NIC_PARAMS)))
724

  
725
      # Validate other parameters
726
      unsupported = (set(kwargs.keys()) - _INST_CREATE_V0_PARAMS -
727
                     _INST_CREATE_V0_DPARAMS)
728
      if unsupported:
729
        allowed = _INST_CREATE_V0_PARAMS.union(_INST_CREATE_V0_DPARAMS)
730
        raise GanetiApiError("Server supports request version 0 only, but"
731
                             " the following unsupported parameters are"
732
                             " specified: %s, allowed are %s" %
733
                             (unsupported, list(allowed)))
734

  
735
      # All required fields for request data version 0
736
      body = {
737
        _REQ_DATA_VERSION_FIELD: 0,
738
        "name": name,
739
        "disk_template": disk_template,
740
        "disks": disk_sizes,
741
        }
742

  
743
      # NIC fields
744
      assert len(nics) == 1
745
      assert not (set(body.keys()) & set(nics[0].keys()))
746
      body.update(nics[0])
747

  
748
      # Copy supported fields
749
      assert not (set(body.keys()) & set(kwargs.keys()))
750
      body.update(dict((key, value) for key, value in kwargs.items()
751
                       if key in _INST_CREATE_V0_PARAMS))
752

  
753
      # Merge dictionaries
754
      for i in (value for key, value in kwargs.items()
755
                if key in _INST_CREATE_V0_DPARAMS):
756
        assert not (set(body.keys()) & set(i.keys()))
757
        body.update(i)
758

  
759
      assert not (set(kwargs.keys()) -
760
                  (_INST_CREATE_V0_PARAMS | _INST_CREATE_V0_DPARAMS))
761
      assert not (set(body.keys()) & _INST_CREATE_V0_DPARAMS)
686 762

  
687 763
    return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION,
688 764
                             query, body)
b/test/ganeti.rapi.client_unittest.py
26 26
import unittest
27 27
import warnings
28 28

  
29
from ganeti import constants
29 30
from ganeti import http
30 31
from ganeti import serializer
31 32

  
......
89 90
  def AddResponse(self, response, code=200):
90 91
    self._responses.insert(0, (code, response))
91 92

  
93
  def CountPending(self):
94
    return len(self._responses)
95

  
92 96
  def GetLastHandler(self):
93 97
    return self._last_handler
94 98

  
......
111 115
    return code, response
112 116

  
113 117

  
118
class TestConstants(unittest.TestCase):
119
  def test(self):
120
    self.assertEqual(client.GANETI_RAPI_PORT, constants.DEFAULT_RAPI_PORT)
121
    self.assertEqual(client.GANETI_RAPI_VERSION, constants.RAPI_VERSION)
122
    self.assertEqual(client._REQ_DATA_VERSION_FIELD, rlib2._REQ_DATA_VERSION)
123
    self.assertEqual(client._INST_CREATE_REQV1, rlib2._INST_CREATE_REQV1)
124
    self.assertEqual(client._INST_NIC_PARAMS, constants.INIC_PARAMS)
125

  
126

  
114 127
class RapiMockTest(unittest.TestCase):
115 128
  def test(self):
116 129
    rapi = RapiMock()
......
196 209
      self.assertEqual(features, self.client.GetFeatures())
197 210
      self.assertHandler(rlib2.R_2_features)
198 211

  
212
  def testGetFeaturesNotFound(self):
213
    self.rapi.AddResponse(None, code=404)
214
    self.assertEqual([], self.client.GetFeatures())
215

  
199 216
  def testGetOperatingSystems(self):
200 217
    self.rapi.AddResponse("[\"beos\"]")
201 218
    self.assertEqual(["beos"], self.client.GetOperatingSystems())
......
259 276
    self.assertQuery("static", ["1"])
260 277

  
261 278
  def testCreateInstanceOldVersion(self):
262
    self.rapi.AddResponse(serializer.DumpJson([]))
263
    self.assertRaises(NotImplementedError, self.client.CreateInstance,
264
                      "create", "inst1.example.com", "plain", [], [],
265
                      dry_run=True)
279
    # No NICs
280
    self.rapi.AddResponse(None, code=404)
281
    self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
282
                      "create", "inst1.example.com", "plain", [], [])
283
    self.assertEqual(self.rapi.CountPending(), 0)
284

  
285
    # More than one NIC
286
    self.rapi.AddResponse(None, code=404)
287
    self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
288
                      "create", "inst1.example.com", "plain", [],
289
                      [{}, {}, {}])
290
    self.assertEqual(self.rapi.CountPending(), 0)
291

  
292
    # Unsupported NIC fields
293
    self.rapi.AddResponse(None, code=404)
294
    self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
295
                      "create", "inst1.example.com", "plain", [],
296
                      [{"x": True, "y": False}])
297
    self.assertEqual(self.rapi.CountPending(), 0)
298

  
299
    # Unsupported disk fields
300
    self.rapi.AddResponse(None, code=404)
301
    self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
302
                      "create", "inst1.example.com", "plain",
303
                      [{}, {"moo": "foo",}], [{}])
304
    self.assertEqual(self.rapi.CountPending(), 0)
305

  
306
    # Unsupported fields
307
    self.rapi.AddResponse(None, code=404)
308
    self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
309
                      "create", "inst1.example.com", "plain", [], [{}],
310
                      hello_world=123)
311
    self.assertEqual(self.rapi.CountPending(), 0)
312

  
313
    self.rapi.AddResponse(None, code=404)
314
    self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
315
                      "create", "inst1.example.com", "plain", [], [{}],
316
                      memory=128)
317
    self.assertEqual(self.rapi.CountPending(), 0)
318

  
319
    # Normal creation
320
    testnics = [
321
      [{}],
322
      [{ "mac": constants.VALUE_AUTO, }],
323
      [{ "ip": "192.0.2.99", "mode": constants.NIC_MODE_ROUTED, }],
324
      ]
325

  
326
    testdisks = [
327
      [],
328
      [{ "size": 128, }],
329
      [{ "size": 321, }, { "size": 4096, }],
330
      ]
331

  
332
    for idx, nics in enumerate(testnics):
333
      for disks in testdisks:
334
        beparams = {
335
          constants.BE_MEMORY: 512,
336
          constants.BE_AUTO_BALANCE: False,
337
          }
338
        hvparams = {
339
          constants.HV_MIGRATION_PORT: 9876,
340
          constants.HV_VNC_TLS: True,
341
          }
342

  
343
        self.rapi.AddResponse(None, code=404)
344
        self.rapi.AddResponse(serializer.DumpJson(3122617 + idx))
345
        job_id = self.client.CreateInstance("create", "inst1.example.com",
346
                                            "plain", disks, nics,
347
                                            pnode="node99", dry_run=True,
348
                                            hvparams=hvparams,
349
                                            beparams=beparams)
350
        self.assertEqual(job_id, 3122617 + idx)
351
        self.assertHandler(rlib2.R_2_instances)
352
        self.assertDryRun()
353
        self.assertEqual(self.rapi.CountPending(), 0)
354

  
355
        data = serializer.LoadJson(self.http.last_request.data)
356
        self.assertEqual(data["name"], "inst1.example.com")
357
        self.assertEqual(data["disk_template"], "plain")
358
        self.assertEqual(data["pnode"], "node99")
359
        self.assertEqual(data[constants.BE_MEMORY], 512)
360
        self.assertEqual(data[constants.BE_AUTO_BALANCE], False)
361
        self.assertEqual(data[constants.HV_MIGRATION_PORT], 9876)
362
        self.assertEqual(data[constants.HV_VNC_TLS], True)
363
        self.assertEqual(data["disks"], [disk["size"] for disk in disks])
266 364

  
267 365
  def testCreateInstance(self):
268 366
    self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1]))

Also available in: Unified diff