Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.rapi.client_unittest.py @ f2f88abf

History | View | Annotate | Download (14.3 kB)

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 client module"""
23

    
24

    
25
try:
26
  import httplib2
27
  BaseHttp = httplib2.Http
28
  from ganeti.rapi import client
29
except ImportError:
30
  httplib2 = None
31
  BaseHttp = object
32

    
33
import re
34
import unittest
35
import warnings
36

    
37
from ganeti import http
38

    
39
from ganeti.rapi import connector
40
from ganeti.rapi import rlib2
41

    
42
import testutils
43

    
44

    
45
_URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")
46

    
47

    
48
def _GetPathFromUri(uri):
49
  """Gets the path and query from a URI.
50

51
  """
52
  match = _URI_RE.match(uri)
53
  if match:
54
    return match.groupdict()["path"]
55
  else:
56
    return None
57

    
58

    
59
class HttpResponseMock(dict):
60
  """Dumb mock of httplib2.Response.
61

62
  """
63

    
64
  def __init__(self, status):
65
    self.status = status
66
    self['status'] = status
67

    
68

    
69
class HttpMock(BaseHttp):
70
  """Mock for httplib.Http.
71

72
  """
73

    
74
  def __init__(self, rapi):
75
    self._rapi = rapi
76
    self._last_request = None
77

    
78
  last_request_url = property(lambda self: self._last_request[0])
79
  last_request_method = property(lambda self: self._last_request[1])
80
  last_request_body = property(lambda self: self._last_request[2])
81

    
82
  def request(self, url, method, body, headers):
83
    self._last_request = (url, method, body)
84
    code, resp_body = self._rapi.FetchResponse(_GetPathFromUri(url), method)
85
    return HttpResponseMock(code), resp_body
86

    
87

    
88
class RapiMock(object):
89

    
90
  def __init__(self):
91
    self._mapper = connector.Mapper()
92
    self._responses = []
93
    self._last_handler = None
94

    
95
  def AddResponse(self, response):
96
    self._responses.insert(0, response)
97

    
98
  def PopResponse(self):
99
    if len(self._responses) > 0:
100
      return self._responses.pop()
101
    else:
102
      return None
103

    
104
  def GetLastHandler(self):
105
    return self._last_handler
106

    
107
  def FetchResponse(self, path, method):
108
    code = 200
109
    response = None
110

    
111
    try:
112
      HandlerClass, items, args = self._mapper.getController(path)
113
      self._last_handler = HandlerClass(items, args, None)
114
      if not hasattr(self._last_handler, method.upper()):
115
        code = 400
116
        response = "Bad request"
117
    except http.HttpException, ex:
118
      code = ex.code
119
      response = ex.message
120

    
121
    if not response:
122
      response = self.PopResponse()
123

    
124
    return code, response
125

    
126

    
127
class RapiMockTest(unittest.TestCase):
128

    
129
  def test(self):
130
    rapi = RapiMock()
131
    path = "/version"
132
    self.assertEqual((404, None), rapi.FetchResponse("/foo", "GET"))
133
    self.assertEqual((400, "Bad request"),
134
                     rapi.FetchResponse("/version", "POST"))
135
    rapi.AddResponse("2")
136
    code, response = rapi.FetchResponse("/version", "GET")
137
    self.assertEqual(200, code)
138
    self.assertEqual("2", response)
139
    self.failUnless(isinstance(rapi.GetLastHandler(), rlib2.R_version))
140

    
141

    
142
class GanetiRapiClientTests(unittest.TestCase):
143
  """Tests for remote API client.
144

145
  """
146

    
147
  def setUp(self):
148
    self.rapi = RapiMock()
149
    self.http = HttpMock(self.rapi)
150
    self.client = client.GanetiRapiClient('master.foo.com')
151
    self.client._http = self.http
152
    # Hard-code the version for easier testing.
153
    self.client._version = 2
154

    
155
  def assertHandler(self, handler_cls):
156
    self.failUnless(isinstance(self.rapi.GetLastHandler(), handler_cls))
157

    
158
  def assertQuery(self, key, value):
159
    self.assertEqual(value, self.rapi.GetLastHandler().queryargs.get(key, None))
160

    
161
  def assertItems(self, items):
162
    self.assertEqual(items, self.rapi.GetLastHandler().items)
163

    
164
  def assertBulk(self):
165
    self.assertTrue(self.rapi.GetLastHandler().useBulk())
166

    
167
  def assertDryRun(self):
168
    self.assertTrue(self.rapi.GetLastHandler().dryRun())
169

    
170
  def testGetVersion(self):
171
    self.client._version = None
172
    self.rapi.AddResponse("2")
173
    self.assertEqual(2, self.client.GetVersion())
174
    self.assertHandler(rlib2.R_version)
175

    
176
  def testGetOperatingSystems(self):
177
    self.rapi.AddResponse("[\"beos\"]")
178
    self.assertEqual(["beos"], self.client.GetOperatingSystems())
179
    self.assertHandler(rlib2.R_2_os)
180

    
181
  def testGetClusterTags(self):
182
    self.rapi.AddResponse("[\"tag\"]")
183
    self.assertEqual(["tag"], self.client.GetClusterTags())
184
    self.assertHandler(rlib2.R_2_tags)
185

    
186
  def testAddClusterTags(self):
187
    self.rapi.AddResponse("1234")
188
    self.assertEqual(1234,
189
        self.client.AddClusterTags(["awesome"], dry_run=True))
190
    self.assertHandler(rlib2.R_2_tags)
191
    self.assertDryRun()
192
    self.assertQuery("tag", ["awesome"])
193

    
194
  def testDeleteClusterTags(self):
195
    self.client.DeleteClusterTags(["awesome"], dry_run=True)
196
    self.assertHandler(rlib2.R_2_tags)
197
    self.assertDryRun()
198
    self.assertQuery("tag", ["awesome"])
199

    
200
  def testGetInfo(self):
201
    self.rapi.AddResponse("{}")
202
    self.assertEqual({}, self.client.GetInfo())
203
    self.assertHandler(rlib2.R_2_info)
204

    
205
  def testGetInstances(self):
206
    self.rapi.AddResponse("[]")
207
    self.assertEqual([], self.client.GetInstances(bulk=True))
208
    self.assertHandler(rlib2.R_2_instances)
209
    self.assertBulk()
210

    
211
  def testGetInstanceInfo(self):
212
    self.rapi.AddResponse("[]")
213
    self.assertEqual([], self.client.GetInstanceInfo("instance"))
214
    self.assertHandler(rlib2.R_2_instances_name)
215
    self.assertItems(["instance"])
216

    
217
  def testCreateInstance(self):
218
    self.rapi.AddResponse("1234")
219
    self.assertEqual(1234, self.client.CreateInstance(dry_run=True))
220
    self.assertHandler(rlib2.R_2_instances)
221
    self.assertDryRun()
222

    
223
  def testDeleteInstance(self):
224
    self.rapi.AddResponse("1234")
225
    self.assertEqual(1234, self.client.DeleteInstance("instance", dry_run=True))
226
    self.assertHandler(rlib2.R_2_instances_name)
227
    self.assertItems(["instance"])
228
    self.assertDryRun()
229

    
230
  def testGetInstanceTags(self):
231
    self.rapi.AddResponse("[]")
232
    self.assertEqual([], self.client.GetInstanceTags("fooinstance"))
233
    self.assertHandler(rlib2.R_2_instances_name_tags)
234
    self.assertItems(["fooinstance"])
235

    
236
  def testAddInstanceTags(self):
237
    self.rapi.AddResponse("1234")
238
    self.assertEqual(1234,
239
        self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True))
240
    self.assertHandler(rlib2.R_2_instances_name_tags)
241
    self.assertItems(["fooinstance"])
242
    self.assertDryRun()
243
    self.assertQuery("tag", ["awesome"])
244

    
245
  def testDeleteInstanceTags(self):
246
    self.client.DeleteInstanceTags("foo", ["awesome"], dry_run=True)
247
    self.assertHandler(rlib2.R_2_instances_name_tags)
248
    self.assertItems(["foo"])
249
    self.assertDryRun()
250
    self.assertQuery("tag", ["awesome"])
251

    
252
  def testRebootInstance(self):
253
    self.client.RebootInstance("i-bar", reboot_type="hard",
254
                               ignore_secondaries=True, dry_run=True)
255
    self.assertHandler(rlib2.R_2_instances_name_reboot)
256
    self.assertItems(["i-bar"])
257
    self.assertDryRun()
258
    self.assertQuery("type", ["hard"])
259
    self.assertQuery("ignore_secondaries", ["True"])
260

    
261
  def testShutdownInstance(self):
262
    self.client.ShutdownInstance("foo-instance", dry_run=True)
263
    self.assertHandler(rlib2.R_2_instances_name_shutdown)
264
    self.assertItems(["foo-instance"])
265
    self.assertDryRun()
266

    
267
  def testStartupInstance(self):
268
    self.client.StartupInstance("bar-instance", dry_run=True)
269
    self.assertHandler(rlib2.R_2_instances_name_startup)
270
    self.assertItems(["bar-instance"])
271
    self.assertDryRun()
272

    
273
  def testReinstallInstance(self):
274
    self.client.ReinstallInstance("baz-instance", "DOS", no_startup=True)
275
    self.assertHandler(rlib2.R_2_instances_name_reinstall)
276
    self.assertItems(["baz-instance"])
277
    self.assertQuery("os", ["DOS"])
278
    self.assertQuery("nostartup", ["1"])
279

    
280
  def testReplaceInstanceDisks(self):
281
    self.rapi.AddResponse("999")
282
    job_id = self.client.ReplaceInstanceDisks("instance-name",
283
        ["hda", "hdc"], dry_run=True)
284
    self.assertEqual(999, job_id)
285
    self.assertHandler(rlib2.R_2_instances_name_replace_disks)
286
    self.assertItems(["instance-name"])
287
    self.assertQuery("disks", ["hda,hdc"])
288
    self.assertQuery("mode", ["replace_auto"])
289
    self.assertQuery("iallocator", ["hail"])
290
    self.assertDryRun()
291

    
292
    self.assertRaises(client.InvalidReplacementMode,
293
                      self.client.ReplaceInstanceDisks,
294
                      "instance_a", ["hda"], mode="invalid_mode")
295
    self.assertRaises(client.GanetiApiError,
296
                      self.client.ReplaceInstanceDisks,
297
                      "instance-foo", ["hda"], mode="replace_on_secondary")
298

    
299
    self.rapi.AddResponse("1000")
300
    job_id = self.client.ReplaceInstanceDisks("instance-bar",
301
        ["hda"], mode="replace_on_secondary", remote_node="foo-node",
302
        dry_run=True)
303
    self.assertEqual(1000, job_id)
304
    self.assertItems(["instance-bar"])
305
    self.assertQuery("disks", ["hda"])
306
    self.assertQuery("remote_node", ["foo-node"])
307
    self.assertDryRun()
308

    
309
  def testGetJobs(self):
310
    self.rapi.AddResponse('[ { "id": "123", "uri": "\\/2\\/jobs\\/123" },'
311
                          '  { "id": "124", "uri": "\\/2\\/jobs\\/124" } ]')
312
    self.assertEqual([123, 124], self.client.GetJobs())
313
    self.assertHandler(rlib2.R_2_jobs)
314

    
315
  def testGetJobStatus(self):
316
    self.rapi.AddResponse("{\"foo\": \"bar\"}")
317
    self.assertEqual({"foo": "bar"}, self.client.GetJobStatus(1234))
318
    self.assertHandler(rlib2.R_2_jobs_id)
319
    self.assertItems(["1234"])
320

    
321
  def testDeleteJob(self):
322
    self.client.DeleteJob(999, dry_run=True)
323
    self.assertHandler(rlib2.R_2_jobs_id)
324
    self.assertItems(["999"])
325
    self.assertDryRun()
326

    
327
  def testGetNodes(self):
328
    self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
329
                          " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
330
    self.assertEqual(["node1", "node2"], self.client.GetNodes())
331
    self.assertHandler(rlib2.R_2_nodes)
332

    
333
    self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
334
                          " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
335
    self.assertEqual([{"id": "node1", "uri": "uri1"},
336
                      {"id": "node2", "uri": "uri2"}],
337
                     self.client.GetNodes(bulk=True))
338
    self.assertHandler(rlib2.R_2_nodes)
339
    self.assertBulk()
340

    
341
  def testGetNodeInfo(self):
342
    self.rapi.AddResponse("{}")
343
    self.assertEqual({}, self.client.GetNodeInfo("node-foo"))
344
    self.assertHandler(rlib2.R_2_nodes_name)
345
    self.assertItems(["node-foo"])
346

    
347
  def testEvacuateNode(self):
348
    self.rapi.AddResponse("9876")
349
    job_id = self.client.EvacuateNode("node-1", remote_node="node-2")
350
    self.assertEqual(9876, job_id)
351
    self.assertHandler(rlib2.R_2_nodes_name_evacuate)
352
    self.assertItems(["node-1"])
353
    self.assertQuery("remote_node", ["node-2"])
354

    
355
    self.rapi.AddResponse("8888")
356
    job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True)
357
    self.assertEqual(8888, job_id)
358
    self.assertItems(["node-3"])
359
    self.assertQuery("iallocator", ["hail"])
360
    self.assertDryRun()
361

    
362
    self.assertRaises(client.GanetiApiError,
363
                      self.client.EvacuateNode,
364
                      "node-4", iallocator="hail", remote_node="node-5")
365

    
366
  def testMigrateNode(self):
367
    self.rapi.AddResponse("1111")
368
    self.assertEqual(1111, self.client.MigrateNode("node-a", dry_run=True))
369
    self.assertHandler(rlib2.R_2_nodes_name_migrate)
370
    self.assertItems(["node-a"])
371
    self.assertQuery("live", ["1"])
372
    self.assertDryRun()
373

    
374
  def testGetNodeRole(self):
375
    self.rapi.AddResponse("\"master\"")
376
    self.assertEqual("master", self.client.GetNodeRole("node-a"))
377
    self.assertHandler(rlib2.R_2_nodes_name_role)
378
    self.assertItems(["node-a"])
379

    
380
  def testSetNodeRole(self):
381
    self.rapi.AddResponse("789")
382
    self.assertEqual(789,
383
        self.client.SetNodeRole("node-foo", "master-candidate", force=True))
384
    self.assertHandler(rlib2.R_2_nodes_name_role)
385
    self.assertItems(["node-foo"])
386
    self.assertQuery("force", ["True"])
387
    self.assertEqual("\"master-candidate\"", self.http.last_request_body)
388

    
389
    self.assertRaises(client.InvalidNodeRole,
390
                      self.client.SetNodeRole, "node-bar", "fake-role")
391

    
392
  def testGetNodeStorageUnits(self):
393
    self.rapi.AddResponse("42")
394
    self.assertEqual(42,
395
        self.client.GetNodeStorageUnits("node-x", "lvm-pv", "fields"))
396
    self.assertHandler(rlib2.R_2_nodes_name_storage)
397
    self.assertItems(["node-x"])
398
    self.assertQuery("storage_type", ["lvm-pv"])
399
    self.assertQuery("output_fields", ["fields"])
400

    
401
    self.assertRaises(client.InvalidStorageType,
402
                      self.client.GetNodeStorageUnits,
403
                      "node-y", "floppy-disk", "fields")
404

    
405
  def testModifyNodeStorageUnits(self):
406
    self.rapi.AddResponse("14")
407
    self.assertEqual(14,
408
        self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda"))
409
    self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
410
    self.assertItems(["node-z"])
411
    self.assertQuery("storage_type", ["lvm-pv"])
412
    self.assertQuery("name", ["hda"])
413

    
414
    self.assertRaises(client.InvalidStorageType,
415
                      self.client.ModifyNodeStorageUnits,
416
                      "node-n", "floppy-disk", "hdc")
417

    
418
  def testGetNodeTags(self):
419
    self.rapi.AddResponse("[\"fry\", \"bender\"]")
420
    self.assertEqual(["fry", "bender"], self.client.GetNodeTags("node-k"))
421
    self.assertHandler(rlib2.R_2_nodes_name_tags)
422
    self.assertItems(["node-k"])
423

    
424
  def testAddNodeTags(self):
425
    self.rapi.AddResponse("1234")
426
    self.assertEqual(1234,
427
        self.client.AddNodeTags("node-v", ["awesome"], dry_run=True))
428
    self.assertHandler(rlib2.R_2_nodes_name_tags)
429
    self.assertItems(["node-v"])
430
    self.assertDryRun()
431
    self.assertQuery("tag", ["awesome"])
432

    
433
  def testDeleteNodeTags(self):
434
    self.client.DeleteNodeTags("node-w", ["awesome"], dry_run=True)
435
    self.assertHandler(rlib2.R_2_nodes_name_tags)
436
    self.assertItems(["node-w"])
437
    self.assertDryRun()
438
    self.assertQuery("tag", ["awesome"])
439

    
440

    
441
if __name__ == '__main__':
442
  if httplib2 is None:
443
    warnings.warn("These tests require the httplib2 library")
444
  else:
445
    testutils.GanetiTestProgram()