Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.opcodes_unittest.py @ 6252c0bd

History | View | Annotate | Download (15 kB)

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

    
4
# Copyright (C) 2010, 2011 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 testTinySummary(self):
125
    self.assertFalse(utils.FindDuplicates(opcodes._SUMMARY_PREFIX.values()))
126
    self.assertTrue(compat.all(prefix.endswith("_") and supplement.endswith("_")
127
                               for (prefix, supplement) in
128
                                 opcodes._SUMMARY_PREFIX.items()))
129

    
130
    self.assertEqual(opcodes.OpClusterPostInit().TinySummary(), "C_POST_INIT")
131
    self.assertEqual(opcodes.OpNodeRemove().TinySummary(), "N_REMOVE")
132
    self.assertEqual(opcodes.OpInstanceMigrate().TinySummary(), "I_MIGRATE")
133
    self.assertEqual(opcodes.OpGroupQuery().TinySummary(), "G_QUERY")
134
    self.assertEqual(opcodes.OpTestJqueue().TinySummary(), "TEST_JQUEUE")
135

    
136
  def testListSummary(self):
137
    class OpTest(opcodes.OpCode):
138
      OP_DSC_FIELD = "data"
139
      OP_PARAMS = [
140
        ("data", ht.NoDefault, ht.TList, None),
141
        ]
142

    
143
    self.assertEqual(OpTest(data=["a", "b", "c"]).Summary(),
144
                     "TEST(a,b,c)")
145
    self.assertEqual(OpTest(data=["a", None, "c"]).Summary(),
146
                     "TEST(a,None,c)")
147
    self.assertEqual(OpTest(data=[1, 2, 3, 4]).Summary(), "TEST(1,2,3,4)")
148

    
149
  def testOpId(self):
150
    self.assertFalse(utils.FindDuplicates(cls.OP_ID
151
                                          for cls in opcodes._GetOpList()))
152
    self.assertEqual(len(opcodes._GetOpList()), len(opcodes.OP_MAPPING))
153

    
154
  def testParams(self):
155
    supported_by_all = set(["debug_level", "dry_run", "priority"])
156

    
157
    self.assertTrue(opcodes.BaseOpCode not in opcodes.OP_MAPPING.values())
158
    self.assertTrue(opcodes.OpCode not in opcodes.OP_MAPPING.values())
159

    
160
    for cls in opcodes.OP_MAPPING.values() + [opcodes.OpCode]:
161
      all_slots = cls.GetAllSlots()
162

    
163
      self.assertEqual(len(set(all_slots) & supported_by_all), 3,
164
                       msg=("Opcode %s doesn't support all base"
165
                            " parameters (%r)" % (cls.OP_ID, supported_by_all)))
166

    
167
      # All opcodes must have OP_PARAMS
168
      self.assert_(hasattr(cls, "OP_PARAMS"),
169
                   msg="%s doesn't have OP_PARAMS" % cls.OP_ID)
170

    
171
      param_names = [name for (name, _, _, _) in cls.GetAllParams()]
172

    
173
      self.assertEqual(all_slots, param_names)
174

    
175
      # Without inheritance
176
      self.assertEqual(cls.__slots__,
177
                       [name for (name, _, _, _) in cls.OP_PARAMS])
178

    
179
      # This won't work if parameters are converted to a dictionary
180
      duplicates = utils.FindDuplicates(param_names)
181
      self.assertFalse(duplicates,
182
                       msg=("Found duplicate parameters %r in %s" %
183
                            (duplicates, cls.OP_ID)))
184

    
185
      # Check parameter definitions
186
      for attr_name, aval, test, doc in cls.GetAllParams():
187
        self.assert_(attr_name)
188
        self.assert_(test is None or test is ht.NoType or callable(test),
189
                     msg=("Invalid type check for %s.%s" %
190
                          (cls.OP_ID, attr_name)))
191
        self.assertTrue(doc is None or isinstance(doc, basestring))
192

    
193
        if callable(aval):
194
          default_value = aval()
195
          self.assertFalse(callable(default_value),
196
                           msg="Default value returned by function is callable")
197
        else:
198
          default_value = aval
199

    
200
        if aval is not ht.NoDefault and test is not ht.NoType:
201
          self.assertTrue(test(default_value),
202
                          msg=("Default value of '%s.%s' does not verify" %
203
                               (cls.OP_ID, attr_name)))
204

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

    
210
  def testValidateNoModification(self):
211
    class OpTest(opcodes.OpCode):
212
      OP_PARAMS = [
213
        ("nodef", ht.NoDefault, ht.TMaybeString, None),
214
        ("wdef", "default", ht.TMaybeString, None),
215
        ("number", 0, ht.TInt, None),
216
        ("notype", None, ht.NoType, None),
217
        ]
218

    
219
    # Missing required parameter "nodef"
220
    op = OpTest()
221
    before = op.__getstate__()
222
    self.assertRaises(errors.OpPrereqError, op.Validate, False)
223
    self.assertFalse(hasattr(op, "nodef"))
224
    self.assertFalse(hasattr(op, "wdef"))
225
    self.assertFalse(hasattr(op, "number"))
226
    self.assertFalse(hasattr(op, "notype"))
227
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
228

    
229
    # Required parameter "nodef" is provided
230
    op = OpTest(nodef="foo")
231
    before = op.__getstate__()
232
    op.Validate(False)
233
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
234
    self.assertEqual(op.nodef, "foo")
235
    self.assertFalse(hasattr(op, "wdef"))
236
    self.assertFalse(hasattr(op, "number"))
237
    self.assertFalse(hasattr(op, "notype"))
238

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

    
247
    # Wrong type for "nodef"
248
    op = OpTest(nodef=987)
249
    before = op.__getstate__()
250
    self.assertRaises(errors.OpPrereqError, op.Validate, False)
251
    self.assertEqual(op.nodef, 987)
252
    self.assertFalse(hasattr(op, "notype"))
253
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
254

    
255
    # Testing different types for "notype"
256
    op = OpTest(nodef="foo", notype=[1, 2, 3])
257
    before = op.__getstate__()
258
    op.Validate(False)
259
    self.assertEqual(op.nodef, "foo")
260
    self.assertEqual(op.notype, [1, 2, 3])
261
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
262

    
263
    op = OpTest(nodef="foo", notype="Hello World")
264
    before = op.__getstate__()
265
    op.Validate(False)
266
    self.assertEqual(op.nodef, "foo")
267
    self.assertEqual(op.notype, "Hello World")
268
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
269

    
270
  def testValidateSetDefaults(self):
271
    class OpTest(opcodes.OpCode):
272
      OP_PARAMS = [
273
        # Static default value
274
        ("value1", "default", ht.TMaybeString, None),
275

    
276
        # Default value callback
277
        ("value2", lambda: "result", ht.TMaybeString, None),
278
        ]
279

    
280
    op = OpTest()
281
    before = op.__getstate__()
282
    op.Validate(True)
283
    self.assertNotEqual(op.__getstate__(), before,
284
                        msg="Opcode was not modified")
285
    self.assertEqual(op.value1, "default")
286
    self.assertEqual(op.value2, "result")
287
    self.assert_(op.dry_run is None)
288
    self.assert_(op.debug_level is None)
289
    self.assertEqual(op.priority, constants.OP_PRIO_DEFAULT)
290

    
291
    op = OpTest(value1="hello", value2="world", debug_level=123)
292
    before = op.__getstate__()
293
    op.Validate(True)
294
    self.assertNotEqual(op.__getstate__(), before,
295
                        msg="Opcode was not modified")
296
    self.assertEqual(op.value1, "hello")
297
    self.assertEqual(op.value2, "world")
298
    self.assertEqual(op.debug_level, 123)
299

    
300

    
301
  def testOpInstanceMultiAlloc(self):
302
    inst = dict([(name, []) for name in opcodes.OpInstanceCreate.GetAllSlots()])
303
    inst_op = opcodes.OpInstanceCreate(**inst)
304
    inst_state = inst_op.__getstate__()
305

    
306
    multialloc = opcodes.OpInstanceMultiAlloc(instances=[inst_op])
307
    state = multialloc.__getstate__()
308
    self.assertEquals(state["instances"], [inst_state])
309
    loaded_multialloc = opcodes.OpCode.LoadOpCode(state)
310
    (loaded_inst,) = loaded_multialloc.instances
311
    self.assertNotEquals(loaded_inst, inst_op)
312
    self.assertEquals(loaded_inst.__getstate__(), inst_state)
313

    
314

    
315
class TestOpcodeDepends(unittest.TestCase):
316
  def test(self):
317
    check_relative = opcodes._BuildJobDepCheck(True)
318
    check_norelative = opcodes.TNoRelativeJobDependencies
319

    
320
    for fn in [check_relative, check_norelative]:
321
      self.assertTrue(fn(None))
322
      self.assertTrue(fn([]))
323
      self.assertTrue(fn([(1, [])]))
324
      self.assertTrue(fn([(719833, [])]))
325
      self.assertTrue(fn([("24879", [])]))
326
      self.assertTrue(fn([(2028, [constants.JOB_STATUS_ERROR])]))
327
      self.assertTrue(fn([
328
        (2028, [constants.JOB_STATUS_ERROR]),
329
        (18750, []),
330
        (5063, [constants.JOB_STATUS_SUCCESS, constants.JOB_STATUS_ERROR]),
331
        ]))
332

    
333
      self.assertFalse(fn(1))
334
      self.assertFalse(fn([(9, )]))
335
      self.assertFalse(fn([(15194, constants.JOB_STATUS_ERROR)]))
336

    
337
    for i in [
338
      [(-1, [])],
339
      [(-27740, [constants.JOB_STATUS_CANCELED, constants.JOB_STATUS_ERROR]),
340
       (-1, [constants.JOB_STATUS_ERROR]),
341
       (9921, [])],
342
      ]:
343
      self.assertTrue(check_relative(i))
344
      self.assertFalse(check_norelative(i))
345

    
346

    
347
class TestResultChecks(unittest.TestCase):
348
  def testJobIdList(self):
349
    for i in [[], [(False, "error")], [(False, "")],
350
              [(True, 123), (True, "999")]]:
351
      self.assertTrue(opcodes.TJobIdList(i))
352

    
353
    for i in ["", [("x", 1)], [[], []], [[False, "", None], [True, 123]]]:
354
      self.assertFalse(opcodes.TJobIdList(i))
355

    
356
  def testJobIdListOnly(self):
357
    self.assertTrue(opcodes.TJobIdListOnly({
358
      constants.JOB_IDS_KEY: [],
359
      }))
360
    self.assertTrue(opcodes.TJobIdListOnly({
361
      constants.JOB_IDS_KEY: [(True, "9282")],
362
      }))
363

    
364
    self.assertFalse(opcodes.TJobIdListOnly({
365
      "x": None,
366
      }))
367
    self.assertFalse(opcodes.TJobIdListOnly({
368
      constants.JOB_IDS_KEY: [],
369
      "x": None,
370
      }))
371
    self.assertFalse(opcodes.TJobIdListOnly({
372
      constants.JOB_IDS_KEY: [("foo", "bar")],
373
      }))
374
    self.assertFalse(opcodes.TJobIdListOnly({
375
      constants.JOB_IDS_KEY: [("one", "two", "three")],
376
      }))
377

    
378

    
379
class TestClusterOsList(unittest.TestCase):
380
  def test(self):
381
    good = [
382
      None,
383
      [],
384
      [(constants.DDM_ADD, "dos"),
385
       (constants.DDM_REMOVE, "linux")],
386
      ]
387

    
388
    for i in good:
389
      self.assertTrue(opcodes._TestClusterOsList(i))
390

    
391
    wrong = ["", 0, "xy", ["Hello World"], object(),
392
      [("foo", "bar")],
393
      [("", "")],
394
      [[constants.DDM_ADD]],
395
      [(constants.DDM_ADD, "")],
396
      [(constants.DDM_REMOVE, "")],
397
      [(constants.DDM_ADD, None)],
398
      [(constants.DDM_REMOVE, None)],
399
      ]
400

    
401
    for i in wrong:
402
      self.assertFalse(opcodes._TestClusterOsList(i))
403

    
404

    
405
class TestOpInstanceSetParams(unittest.TestCase):
406
  def _GenericTests(self, fn):
407
    self.assertTrue(fn([]))
408
    self.assertTrue(fn([(constants.DDM_ADD, {})]))
409
    self.assertTrue(fn([(constants.DDM_REMOVE, {})]))
410
    for i in [0, 1, 2, 3, 9, 10, 1024]:
411
      self.assertTrue(fn([(i, {})]))
412

    
413
    self.assertFalse(fn(None))
414
    self.assertFalse(fn({}))
415
    self.assertFalse(fn(""))
416
    self.assertFalse(fn(0))
417
    self.assertFalse(fn([(-100, {})]))
418
    self.assertFalse(fn([(constants.DDM_ADD, 2, 3)]))
419
    self.assertFalse(fn([[constants.DDM_ADD]]))
420

    
421
  def testNicModifications(self):
422
    fn = opcodes.OpInstanceSetParams.TestNicModifications
423
    self._GenericTests(fn)
424

    
425
    for param in constants.INIC_PARAMS:
426
      self.assertTrue(fn([[constants.DDM_ADD, {param: None}]]))
427
      self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
428

    
429
  def testDiskModifications(self):
430
    fn = opcodes.OpInstanceSetParams.TestDiskModifications
431
    self._GenericTests(fn)
432

    
433
    for param in constants.IDISK_PARAMS:
434
      self.assertTrue(fn([[constants.DDM_ADD, {param: 0}]]))
435
      self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
436

    
437

    
438
if __name__ == "__main__":
439
  testutils.GanetiTestProgram()