Statistics
| Branch: | Tag: | Revision:

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

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 opcodes_base
31
from ganeti import ht
32
from ganeti import constants
33
from ganeti import errors
34
from ganeti import compat
35

    
36
import testutils
37

    
38

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

    
48

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
169
    self.assertTrue(opcodes_base.BaseOpCode not in opcodes.OP_MAPPING.values())
170
    self.assertTrue(opcodes.OpCode not in opcodes.OP_MAPPING.values())
171

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

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

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

    
183
      param_names = [name for (name, _, _, _) in cls.GetAllParams()]
184

    
185
      self.assertEqual(all_slots, param_names)
186

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

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

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

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

    
215
          default_value = aval
216

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
330

    
331
class TestOpcodeDepends(unittest.TestCase):
332
  def test(self):
333
    check_relative = opcodes_base.BuildJobDepCheck(True)
334
    check_norelative = opcodes_base.TNoRelativeJobDependencies
335

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

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

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

    
362

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

    
369
    for i in ["", [("x", 1)], [[], []], [[False, "", None], [True, 123]]]:
370
      self.assertFalse(ht.TJobIdList(i))
371

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

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

    
394

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

    
404
    for i in good:
405
      self.assertTrue(opcodes._TestClusterOsList(i))
406

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

    
417
    for i in wrong:
418
      self.assertFalse(opcodes._TestClusterOsList(i))
419

    
420

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

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

    
437
  def testNicModifications(self):
438
    fn = ht.TSetParamsMods(ht.TINicParams)
439
    self._GenericTests(fn)
440

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

    
445
  def testDiskModifications(self):
446
    fn = ht.TSetParamsMods(ht.TIDiskParams)
447
    self._GenericTests(fn)
448

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

    
453

    
454
if __name__ == "__main__":
455
  testutils.GanetiTestProgram()