Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.opcodes_unittest.py @ 8bc17ebb

History | View | Annotate | Download (15.6 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2010, 2011, 2012 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 testing ganeti.backend"""
23

    
24
import os
25
import sys
26
import unittest
27

    
28
from ganeti import utils
29
from ganeti import opcodes
30
from ganeti import ht
31
from ganeti import constants
32
from ganeti import errors
33
from ganeti import compat
34

    
35
import testutils
36

    
37

    
38
#: Unless an opcode is included in the following list it must have a result
39
#: check of some sort
40
MISSING_RESULT_CHECK = frozenset([
41
  opcodes.OpTestAllocator,
42
  opcodes.OpTestDelay,
43
  opcodes.OpTestDummy,
44
  opcodes.OpTestJqueue,
45
  ])
46

    
47

    
48
class TestOpcodes(unittest.TestCase):
49
  def test(self):
50
    self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, None)
51
    self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, "")
52
    self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, {})
53
    self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, {"OP_ID": ""})
54

    
55
    for cls in opcodes.OP_MAPPING.values():
56
      self.assert_(cls.OP_ID.startswith("OP_"))
57
      self.assert_(len(cls.OP_ID) > 3)
58
      self.assertEqual(cls.OP_ID, cls.OP_ID.upper())
59
      self.assertEqual(cls.OP_ID, opcodes._NameToId(cls.__name__))
60
      self.assertFalse(compat.any(cls.OP_ID.startswith(prefix)
61
                                  for prefix in opcodes._SUMMARY_PREFIX.keys()))
62
      if cls in MISSING_RESULT_CHECK:
63
        self.assertTrue(cls.OP_RESULT is None,
64
                        msg=("%s is listed to not have a result check" %
65
                             cls.OP_ID))
66
      else:
67
        self.assertTrue(callable(cls.OP_RESULT),
68
                        msg=("%s should have a result check" % cls.OP_ID))
69

    
70
      self.assertRaises(TypeError, cls, unsupported_parameter="some value")
71

    
72
      args = [
73
        # No variables
74
        {},
75

    
76
        # Variables supported by all opcodes
77
        {"dry_run": False, "debug_level": 0, },
78

    
79
        # All variables
80
        dict([(name, []) for name in cls.GetAllSlots()])
81
        ]
82

    
83
      for i in args:
84
        op = cls(**i)
85

    
86
        self.assertEqual(op.OP_ID, cls.OP_ID)
87
        self._checkSummary(op)
88

    
89
        # Try a restore
90
        state = op.__getstate__()
91
        self.assert_(isinstance(state, dict))
92

    
93
        restored = opcodes.OpCode.LoadOpCode(state)
94
        self.assert_(isinstance(restored, cls))
95
        self._checkSummary(restored)
96

    
97
        for name in ["x_y_z", "hello_world"]:
98
          assert name not in cls.GetAllSlots()
99
          for value in [None, True, False, [], "Hello World"]:
100
            self.assertRaises(AttributeError, setattr, op, name, value)
101

    
102
  def _checkSummary(self, op):
103
    summary = op.Summary()
104

    
105
    if hasattr(op, "OP_DSC_FIELD"):
106
      self.assert_(("OP_%s" % summary).startswith("%s(" % op.OP_ID))
107
      self.assert_(summary.endswith(")"))
108
    else:
109
      self.assertEqual("OP_%s" % summary, op.OP_ID)
110

    
111
  def testSummary(self):
112
    class OpTest(opcodes.OpCode):
113
      OP_DSC_FIELD = "data"
114
      OP_PARAMS = [
115
        ("data", ht.NoDefault, ht.TString, None),
116
        ]
117

    
118
    self.assertEqual(OpTest(data="").Summary(), "TEST()")
119
    self.assertEqual(OpTest(data="Hello World").Summary(),
120
                     "TEST(Hello World)")
121
    self.assertEqual(OpTest(data="node1.example.com").Summary(),
122
                     "TEST(node1.example.com)")
123

    
124
  def testSummaryFormatter(self):
125
    class OpTest(opcodes.OpCode):
126
      OP_DSC_FIELD = "data"
127
      OP_DSC_FORMATTER = lambda _, v: "a"
128
      OP_PARAMS = [
129
        ("data", ht.NoDefault, ht.TString, None),
130
        ]
131
    self.assertEqual(OpTest(data="").Summary(), "TEST(a)")
132
    self.assertEqual(OpTest(data="b").Summary(), "TEST(a)")
133

    
134
  def testTinySummary(self):
135
    self.assertFalse(utils.FindDuplicates(opcodes._SUMMARY_PREFIX.values()))
136
    self.assertTrue(compat.all(prefix.endswith("_") and supplement.endswith("_")
137
                               for (prefix, supplement) in
138
                                 opcodes._SUMMARY_PREFIX.items()))
139

    
140
    self.assertEqual(opcodes.OpClusterPostInit().TinySummary(), "C_POST_INIT")
141
    self.assertEqual(opcodes.OpNodeRemove().TinySummary(), "N_REMOVE")
142
    self.assertEqual(opcodes.OpInstanceMigrate().TinySummary(), "I_MIGRATE")
143
    self.assertEqual(opcodes.OpGroupQuery().TinySummary(), "G_QUERY")
144
    self.assertEqual(opcodes.OpTestJqueue().TinySummary(), "TEST_JQUEUE")
145

    
146
  def testListSummary(self):
147
    class OpTest(opcodes.OpCode):
148
      OP_DSC_FIELD = "data"
149
      OP_PARAMS = [
150
        ("data", ht.NoDefault, ht.TList, None),
151
        ]
152

    
153
    self.assertEqual(OpTest(data=["a", "b", "c"]).Summary(),
154
                     "TEST(a,b,c)")
155
    self.assertEqual(OpTest(data=["a", None, "c"]).Summary(),
156
                     "TEST(a,None,c)")
157
    self.assertEqual(OpTest(data=[1, 2, 3, 4]).Summary(), "TEST(1,2,3,4)")
158

    
159
  def testOpId(self):
160
    self.assertFalse(utils.FindDuplicates(cls.OP_ID
161
                                          for cls in opcodes._GetOpList()))
162
    self.assertEqual(len(opcodes._GetOpList()), len(opcodes.OP_MAPPING))
163

    
164
  def testParams(self):
165
    supported_by_all = set(["debug_level", "dry_run", "priority"])
166

    
167
    self.assertTrue(opcodes.BaseOpCode not in opcodes.OP_MAPPING.values())
168
    self.assertTrue(opcodes.OpCode not in opcodes.OP_MAPPING.values())
169

    
170
    for cls in opcodes.OP_MAPPING.values() + [opcodes.OpCode]:
171
      all_slots = cls.GetAllSlots()
172

    
173
      self.assertEqual(len(set(all_slots) & supported_by_all), 3,
174
                       msg=("Opcode %s doesn't support all base"
175
                            " parameters (%r)" % (cls.OP_ID, supported_by_all)))
176

    
177
      # All opcodes must have OP_PARAMS
178
      self.assert_(hasattr(cls, "OP_PARAMS"),
179
                   msg="%s doesn't have OP_PARAMS" % cls.OP_ID)
180

    
181
      param_names = [name for (name, _, _, _) in cls.GetAllParams()]
182

    
183
      self.assertEqual(all_slots, param_names)
184

    
185
      # Without inheritance
186
      self.assertEqual(cls.__slots__,
187
                       [name for (name, _, _, _) in cls.OP_PARAMS])
188

    
189
      # This won't work if parameters are converted to a dictionary
190
      duplicates = utils.FindDuplicates(param_names)
191
      self.assertFalse(duplicates,
192
                       msg=("Found duplicate parameters %r in %s" %
193
                            (duplicates, cls.OP_ID)))
194

    
195
      # Check parameter definitions
196
      for attr_name, aval, test, doc in cls.GetAllParams():
197
        self.assert_(attr_name)
198
        self.assert_(test is None or test is ht.NoType or callable(test),
199
                     msg=("Invalid type check for %s.%s" %
200
                          (cls.OP_ID, attr_name)))
201
        self.assertTrue(doc is None or isinstance(doc, basestring))
202

    
203
        if callable(aval):
204
          default_value = aval()
205
          self.assertFalse(callable(default_value),
206
                           msg=("Default value of %s.%s returned by function"
207
                                " is callable" % (cls.OP_ID, attr_name)))
208
        else:
209
          self.assertFalse(isinstance(aval, (list, dict, set)),
210
                           msg=("Default value of %s.%s is mutable (%s)" %
211
                                (cls.OP_ID, attr_name, repr(aval))))
212

    
213
          default_value = aval
214

    
215
        if aval is not ht.NoDefault and test is not ht.NoType:
216
          self.assertTrue(test(default_value),
217
                          msg=("Default value of %s.%s does not verify" %
218
                               (cls.OP_ID, attr_name)))
219

    
220
      # If any parameter has documentation, all others need to have it as well
221
      has_doc = [doc is not None for (_, _, _, doc) in cls.OP_PARAMS]
222
      self.assertTrue(not compat.any(has_doc) or compat.all(has_doc),
223
                      msg="%s does not document all parameters" % cls)
224

    
225
  def testValidateNoModification(self):
226
    class OpTest(opcodes.OpCode):
227
      OP_PARAMS = [
228
        ("nodef", ht.NoDefault, ht.TMaybeString, None),
229
        ("wdef", "default", ht.TMaybeString, None),
230
        ("number", 0, ht.TInt, None),
231
        ("notype", None, ht.NoType, None),
232
        ]
233

    
234
    # Missing required parameter "nodef"
235
    op = OpTest()
236
    before = op.__getstate__()
237
    self.assertRaises(errors.OpPrereqError, op.Validate, False)
238
    self.assertFalse(hasattr(op, "nodef"))
239
    self.assertFalse(hasattr(op, "wdef"))
240
    self.assertFalse(hasattr(op, "number"))
241
    self.assertFalse(hasattr(op, "notype"))
242
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
243

    
244
    # Required parameter "nodef" is provided
245
    op = OpTest(nodef="foo")
246
    before = op.__getstate__()
247
    op.Validate(False)
248
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
249
    self.assertEqual(op.nodef, "foo")
250
    self.assertFalse(hasattr(op, "wdef"))
251
    self.assertFalse(hasattr(op, "number"))
252
    self.assertFalse(hasattr(op, "notype"))
253

    
254
    # Missing required parameter "nodef"
255
    op = OpTest(wdef="hello", number=999)
256
    before = op.__getstate__()
257
    self.assertRaises(errors.OpPrereqError, op.Validate, False)
258
    self.assertFalse(hasattr(op, "nodef"))
259
    self.assertFalse(hasattr(op, "notype"))
260
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
261

    
262
    # Wrong type for "nodef"
263
    op = OpTest(nodef=987)
264
    before = op.__getstate__()
265
    self.assertRaises(errors.OpPrereqError, op.Validate, False)
266
    self.assertEqual(op.nodef, 987)
267
    self.assertFalse(hasattr(op, "notype"))
268
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
269

    
270
    # Testing different types for "notype"
271
    op = OpTest(nodef="foo", notype=[1, 2, 3])
272
    before = op.__getstate__()
273
    op.Validate(False)
274
    self.assertEqual(op.nodef, "foo")
275
    self.assertEqual(op.notype, [1, 2, 3])
276
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
277

    
278
    op = OpTest(nodef="foo", notype="Hello World")
279
    before = op.__getstate__()
280
    op.Validate(False)
281
    self.assertEqual(op.nodef, "foo")
282
    self.assertEqual(op.notype, "Hello World")
283
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
284

    
285
  def testValidateSetDefaults(self):
286
    class OpTest(opcodes.OpCode):
287
      OP_PARAMS = [
288
        # Static default value
289
        ("value1", "default", ht.TMaybeString, None),
290

    
291
        # Default value callback
292
        ("value2", lambda: "result", ht.TMaybeString, None),
293
        ]
294

    
295
    op = OpTest()
296
    before = op.__getstate__()
297
    op.Validate(True)
298
    self.assertNotEqual(op.__getstate__(), before,
299
                        msg="Opcode was not modified")
300
    self.assertEqual(op.value1, "default")
301
    self.assertEqual(op.value2, "result")
302
    self.assert_(op.dry_run is None)
303
    self.assert_(op.debug_level is None)
304
    self.assertEqual(op.priority, constants.OP_PRIO_DEFAULT)
305

    
306
    op = OpTest(value1="hello", value2="world", debug_level=123)
307
    before = op.__getstate__()
308
    op.Validate(True)
309
    self.assertNotEqual(op.__getstate__(), before,
310
                        msg="Opcode was not modified")
311
    self.assertEqual(op.value1, "hello")
312
    self.assertEqual(op.value2, "world")
313
    self.assertEqual(op.debug_level, 123)
314

    
315
  def testOpInstanceMultiAlloc(self):
316
    inst = dict([(name, []) for name in opcodes.OpInstanceCreate.GetAllSlots()])
317
    inst_op = opcodes.OpInstanceCreate(**inst)
318
    inst_state = inst_op.__getstate__()
319

    
320
    multialloc = opcodes.OpInstanceMultiAlloc(instances=[inst_op])
321
    state = multialloc.__getstate__()
322
    self.assertEquals(state["instances"], [inst_state])
323
    loaded_multialloc = opcodes.OpCode.LoadOpCode(state)
324
    (loaded_inst,) = loaded_multialloc.instances
325
    self.assertNotEquals(loaded_inst, inst_op)
326
    self.assertEquals(loaded_inst.__getstate__(), inst_state)
327

    
328

    
329
class TestOpcodeDepends(unittest.TestCase):
330
  def test(self):
331
    check_relative = opcodes._BuildJobDepCheck(True)
332
    check_norelative = opcodes.TNoRelativeJobDependencies
333

    
334
    for fn in [check_relative, check_norelative]:
335
      self.assertTrue(fn(None))
336
      self.assertTrue(fn([]))
337
      self.assertTrue(fn([(1, [])]))
338
      self.assertTrue(fn([(719833, [])]))
339
      self.assertTrue(fn([("24879", [])]))
340
      self.assertTrue(fn([(2028, [constants.JOB_STATUS_ERROR])]))
341
      self.assertTrue(fn([
342
        (2028, [constants.JOB_STATUS_ERROR]),
343
        (18750, []),
344
        (5063, [constants.JOB_STATUS_SUCCESS, constants.JOB_STATUS_ERROR]),
345
        ]))
346

    
347
      self.assertFalse(fn(1))
348
      self.assertFalse(fn([(9, )]))
349
      self.assertFalse(fn([(15194, constants.JOB_STATUS_ERROR)]))
350

    
351
    for i in [
352
      [(-1, [])],
353
      [(-27740, [constants.JOB_STATUS_CANCELED, constants.JOB_STATUS_ERROR]),
354
       (-1, [constants.JOB_STATUS_ERROR]),
355
       (9921, [])],
356
      ]:
357
      self.assertTrue(check_relative(i))
358
      self.assertFalse(check_norelative(i))
359

    
360

    
361
class TestResultChecks(unittest.TestCase):
362
  def testJobIdList(self):
363
    for i in [[], [(False, "error")], [(False, "")],
364
              [(True, 123), (True, "999")]]:
365
      self.assertTrue(opcodes.TJobIdList(i))
366

    
367
    for i in ["", [("x", 1)], [[], []], [[False, "", None], [True, 123]]]:
368
      self.assertFalse(opcodes.TJobIdList(i))
369

    
370
  def testJobIdListOnly(self):
371
    self.assertTrue(opcodes.TJobIdListOnly({
372
      constants.JOB_IDS_KEY: [],
373
      }))
374
    self.assertTrue(opcodes.TJobIdListOnly({
375
      constants.JOB_IDS_KEY: [(True, "9282")],
376
      }))
377

    
378
    self.assertFalse(opcodes.TJobIdListOnly({
379
      "x": None,
380
      }))
381
    self.assertFalse(opcodes.TJobIdListOnly({
382
      constants.JOB_IDS_KEY: [],
383
      "x": None,
384
      }))
385
    self.assertFalse(opcodes.TJobIdListOnly({
386
      constants.JOB_IDS_KEY: [("foo", "bar")],
387
      }))
388
    self.assertFalse(opcodes.TJobIdListOnly({
389
      constants.JOB_IDS_KEY: [("one", "two", "three")],
390
      }))
391

    
392

    
393
class TestClusterOsList(unittest.TestCase):
394
  def test(self):
395
    good = [
396
      None,
397
      [],
398
      [(constants.DDM_ADD, "dos"),
399
       (constants.DDM_REMOVE, "linux")],
400
      ]
401

    
402
    for i in good:
403
      self.assertTrue(opcodes._TestClusterOsList(i))
404

    
405
    wrong = ["", 0, "xy", ["Hello World"], object(),
406
      [("foo", "bar")],
407
      [("", "")],
408
      [[constants.DDM_ADD]],
409
      [(constants.DDM_ADD, "")],
410
      [(constants.DDM_REMOVE, "")],
411
      [(constants.DDM_ADD, None)],
412
      [(constants.DDM_REMOVE, None)],
413
      ]
414

    
415
    for i in wrong:
416
      self.assertFalse(opcodes._TestClusterOsList(i))
417

    
418

    
419
class TestOpInstanceSetParams(unittest.TestCase):
420
  def _GenericTests(self, fn):
421
    self.assertTrue(fn([]))
422
    self.assertTrue(fn([(constants.DDM_ADD, {})]))
423
    self.assertTrue(fn([(constants.DDM_REMOVE, {})]))
424
    for i in [0, 1, 2, 3, 9, 10, 1024]:
425
      self.assertTrue(fn([(i, {})]))
426

    
427
    self.assertFalse(fn(None))
428
    self.assertFalse(fn({}))
429
    self.assertFalse(fn(""))
430
    self.assertFalse(fn(0))
431
    self.assertFalse(fn([(-100, {})]))
432
    self.assertFalse(fn([(constants.DDM_ADD, 2, 3)]))
433
    self.assertFalse(fn([[constants.DDM_ADD]]))
434

    
435
  def testNicModifications(self):
436
    fn = opcodes.OpInstanceSetParams.TestNicModifications
437
    self._GenericTests(fn)
438

    
439
    for param in constants.INIC_PARAMS:
440
      self.assertTrue(fn([[constants.DDM_ADD, {param: None}]]))
441
      self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
442

    
443
  def testDiskModifications(self):
444
    fn = opcodes.OpInstanceSetParams.TestDiskModifications
445
    self._GenericTests(fn)
446

    
447
    for param in constants.IDISK_PARAMS:
448
      self.assertTrue(fn([[constants.DDM_ADD, {param: 0}]]))
449
      self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
450

    
451

    
452
if __name__ == "__main__":
453
  testutils.GanetiTestProgram()