Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.opcodes_unittest.py @ 5fa3d337

History | View | Annotate | Download (14.5 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.OpBackupExport,
42
  opcodes.OpBackupQuery,
43
  opcodes.OpBackupRemove,
44
  opcodes.OpClusterQuery,
45
  opcodes.OpGroupQuery,
46
  opcodes.OpInstanceQuery,
47
  opcodes.OpInstanceQueryData,
48
  opcodes.OpNodeQuery,
49
  opcodes.OpNodeQueryStorage,
50
  opcodes.OpOsDiagnose,
51
  opcodes.OpQuery,
52
  opcodes.OpQueryFields,
53
  opcodes.OpTagsDel,
54
  opcodes.OpTagsGet,
55
  opcodes.OpTagsSearch,
56
  opcodes.OpTagsSet,
57
  opcodes.OpTestAllocator,
58
  opcodes.OpTestDelay,
59
  opcodes.OpTestDummy,
60
  opcodes.OpTestJqueue,
61
  ])
62

    
63

    
64
class TestOpcodes(unittest.TestCase):
65
  def test(self):
66
    self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, None)
67
    self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, "")
68
    self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, {})
69
    self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, {"OP_ID": ""})
70

    
71
    for cls in opcodes.OP_MAPPING.values():
72
      self.assert_(cls.OP_ID.startswith("OP_"))
73
      self.assert_(len(cls.OP_ID) > 3)
74
      self.assertEqual(cls.OP_ID, cls.OP_ID.upper())
75
      self.assertEqual(cls.OP_ID, opcodes._NameToId(cls.__name__))
76
      self.assertFalse(compat.any(cls.OP_ID.startswith(prefix)
77
                                  for prefix in opcodes._SUMMARY_PREFIX.keys()))
78
      if cls in MISSING_RESULT_CHECK:
79
        self.assertTrue(cls.OP_RESULT is None,
80
                        msg=("%s is listed to not have a result check" %
81
                             cls.OP_ID))
82
      else:
83
        self.assertTrue(callable(cls.OP_RESULT),
84
                        msg=("%s should have a result check" % cls.OP_ID))
85

    
86
      self.assertRaises(TypeError, cls, unsupported_parameter="some value")
87

    
88
      args = [
89
        # No variables
90
        {},
91

    
92
        # Variables supported by all opcodes
93
        {"dry_run": False, "debug_level": 0, },
94

    
95
        # All variables
96
        dict([(name, False) for name in cls._all_slots()])
97
        ]
98

    
99
      for i in args:
100
        op = cls(**i)
101

    
102
        self.assertEqual(op.OP_ID, cls.OP_ID)
103
        self._checkSummary(op)
104

    
105
        # Try a restore
106
        state = op.__getstate__()
107
        self.assert_(isinstance(state, dict))
108

    
109
        restored = opcodes.OpCode.LoadOpCode(state)
110
        self.assert_(isinstance(restored, cls))
111
        self._checkSummary(restored)
112

    
113
        for name in ["x_y_z", "hello_world"]:
114
          assert name not in cls._all_slots()
115
          for value in [None, True, False, [], "Hello World"]:
116
            self.assertRaises(AttributeError, setattr, op, name, value)
117

    
118
  def _checkSummary(self, op):
119
    summary = op.Summary()
120

    
121
    if hasattr(op, "OP_DSC_FIELD"):
122
      self.assert_(("OP_%s" % summary).startswith("%s(" % op.OP_ID))
123
      self.assert_(summary.endswith(")"))
124
    else:
125
      self.assertEqual("OP_%s" % summary, op.OP_ID)
126

    
127
  def testSummary(self):
128
    class OpTest(opcodes.OpCode):
129
      OP_DSC_FIELD = "data"
130
      OP_PARAMS = [
131
        ("data", ht.NoDefault, ht.TString, None),
132
        ]
133

    
134
    self.assertEqual(OpTest(data="").Summary(), "TEST()")
135
    self.assertEqual(OpTest(data="Hello World").Summary(),
136
                     "TEST(Hello World)")
137
    self.assertEqual(OpTest(data="node1.example.com").Summary(),
138
                     "TEST(node1.example.com)")
139

    
140
  def testTinySummary(self):
141
    self.assertFalse(utils.FindDuplicates(opcodes._SUMMARY_PREFIX.values()))
142
    self.assertTrue(compat.all(prefix.endswith("_") and supplement.endswith("_")
143
                               for (prefix, supplement) in
144
                                 opcodes._SUMMARY_PREFIX.items()))
145

    
146
    self.assertEqual(opcodes.OpClusterPostInit().TinySummary(), "C_POST_INIT")
147
    self.assertEqual(opcodes.OpNodeRemove().TinySummary(), "N_REMOVE")
148
    self.assertEqual(opcodes.OpInstanceMigrate().TinySummary(), "I_MIGRATE")
149
    self.assertEqual(opcodes.OpGroupQuery().TinySummary(), "G_QUERY")
150
    self.assertEqual(opcodes.OpTestJqueue().TinySummary(), "TEST_JQUEUE")
151

    
152
  def testListSummary(self):
153
    class OpTest(opcodes.OpCode):
154
      OP_DSC_FIELD = "data"
155
      OP_PARAMS = [
156
        ("data", ht.NoDefault, ht.TList, None),
157
        ]
158

    
159
    self.assertEqual(OpTest(data=["a", "b", "c"]).Summary(),
160
                     "TEST(a,b,c)")
161
    self.assertEqual(OpTest(data=["a", None, "c"]).Summary(),
162
                     "TEST(a,None,c)")
163
    self.assertEqual(OpTest(data=[1, 2, 3, 4]).Summary(), "TEST(1,2,3,4)")
164

    
165
  def testOpId(self):
166
    self.assertFalse(utils.FindDuplicates(cls.OP_ID
167
                                          for cls in opcodes._GetOpList()))
168
    self.assertEqual(len(opcodes._GetOpList()), len(opcodes.OP_MAPPING))
169

    
170
  def testParams(self):
171
    supported_by_all = set(["debug_level", "dry_run", "priority"])
172

    
173
    self.assertTrue(opcodes.BaseOpCode not in opcodes.OP_MAPPING.values())
174
    self.assertTrue(opcodes.OpCode not in opcodes.OP_MAPPING.values())
175

    
176
    for cls in opcodes.OP_MAPPING.values() + [opcodes.OpCode]:
177
      all_slots = cls._all_slots()
178

    
179
      self.assertEqual(len(set(all_slots) & supported_by_all), 3,
180
                       msg=("Opcode %s doesn't support all base"
181
                            " parameters (%r)" % (cls.OP_ID, supported_by_all)))
182

    
183
      # All opcodes must have OP_PARAMS
184
      self.assert_(hasattr(cls, "OP_PARAMS"),
185
                   msg="%s doesn't have OP_PARAMS" % cls.OP_ID)
186

    
187
      param_names = [name for (name, _, _, _) in cls.GetAllParams()]
188

    
189
      self.assertEqual(all_slots, param_names)
190

    
191
      # Without inheritance
192
      self.assertEqual(cls.__slots__,
193
                       [name for (name, _, _, _) in cls.OP_PARAMS])
194

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

    
201
      # Check parameter definitions
202
      for attr_name, aval, test, doc in cls.GetAllParams():
203
        self.assert_(attr_name)
204
        self.assert_(test is None or test is ht.NoType or callable(test),
205
                     msg=("Invalid type check for %s.%s" %
206
                          (cls.OP_ID, attr_name)))
207
        self.assertTrue(doc is None or isinstance(doc, basestring))
208

    
209
        if callable(aval):
210
          self.assertFalse(callable(aval()),
211
                           msg="Default value returned by function is callable")
212

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

    
218
  def testValidateNoModification(self):
219
    class OpTest(opcodes.OpCode):
220
      OP_PARAMS = [
221
        ("nodef", ht.NoDefault, ht.TMaybeString, None),
222
        ("wdef", "default", ht.TMaybeString, None),
223
        ("number", 0, ht.TInt, None),
224
        ("notype", None, ht.NoType, None),
225
        ]
226

    
227
    # Missing required parameter "nodef"
228
    op = OpTest()
229
    before = op.__getstate__()
230
    self.assertRaises(errors.OpPrereqError, op.Validate, False)
231
    self.assertFalse(hasattr(op, "nodef"))
232
    self.assertFalse(hasattr(op, "wdef"))
233
    self.assertFalse(hasattr(op, "number"))
234
    self.assertFalse(hasattr(op, "notype"))
235
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
236

    
237
    # Required parameter "nodef" is provided
238
    op = OpTest(nodef="foo")
239
    before = op.__getstate__()
240
    op.Validate(False)
241
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
242
    self.assertEqual(op.nodef, "foo")
243
    self.assertFalse(hasattr(op, "wdef"))
244
    self.assertFalse(hasattr(op, "number"))
245
    self.assertFalse(hasattr(op, "notype"))
246

    
247
    # Missing required parameter "nodef"
248
    op = OpTest(wdef="hello", number=999)
249
    before = op.__getstate__()
250
    self.assertRaises(errors.OpPrereqError, op.Validate, False)
251
    self.assertFalse(hasattr(op, "nodef"))
252
    self.assertFalse(hasattr(op, "notype"))
253
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
254

    
255
    # Wrong type for "nodef"
256
    op = OpTest(nodef=987)
257
    before = op.__getstate__()
258
    self.assertRaises(errors.OpPrereqError, op.Validate, False)
259
    self.assertEqual(op.nodef, 987)
260
    self.assertFalse(hasattr(op, "notype"))
261
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
262

    
263
    # Testing different types for "notype"
264
    op = OpTest(nodef="foo", notype=[1, 2, 3])
265
    before = op.__getstate__()
266
    op.Validate(False)
267
    self.assertEqual(op.nodef, "foo")
268
    self.assertEqual(op.notype, [1, 2, 3])
269
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
270

    
271
    op = OpTest(nodef="foo", notype="Hello World")
272
    before = op.__getstate__()
273
    op.Validate(False)
274
    self.assertEqual(op.nodef, "foo")
275
    self.assertEqual(op.notype, "Hello World")
276
    self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
277

    
278
  def testValidateSetDefaults(self):
279
    class OpTest(opcodes.OpCode):
280
      OP_PARAMS = [
281
        # Static default value
282
        ("value1", "default", ht.TMaybeString, None),
283

    
284
        # Default value callback
285
        ("value2", lambda: "result", ht.TMaybeString, None),
286
        ]
287

    
288
    op = OpTest()
289
    before = op.__getstate__()
290
    op.Validate(True)
291
    self.assertNotEqual(op.__getstate__(), before,
292
                        msg="Opcode was not modified")
293
    self.assertEqual(op.value1, "default")
294
    self.assertEqual(op.value2, "result")
295
    self.assert_(op.dry_run is None)
296
    self.assert_(op.debug_level is None)
297
    self.assertEqual(op.priority, constants.OP_PRIO_DEFAULT)
298

    
299
    op = OpTest(value1="hello", value2="world", debug_level=123)
300
    before = op.__getstate__()
301
    op.Validate(True)
302
    self.assertNotEqual(op.__getstate__(), before,
303
                        msg="Opcode was not modified")
304
    self.assertEqual(op.value1, "hello")
305
    self.assertEqual(op.value2, "world")
306
    self.assertEqual(op.debug_level, 123)
307

    
308

    
309
class TestOpcodeDepends(unittest.TestCase):
310
  def test(self):
311
    check_relative = opcodes._BuildJobDepCheck(True)
312
    check_norelative = opcodes.TNoRelativeJobDependencies
313

    
314
    for fn in [check_relative, check_norelative]:
315
      self.assertTrue(fn(None))
316
      self.assertTrue(fn([]))
317
      self.assertTrue(fn([(1, [])]))
318
      self.assertTrue(fn([(719833, [])]))
319
      self.assertTrue(fn([("24879", [])]))
320
      self.assertTrue(fn([(2028, [constants.JOB_STATUS_ERROR])]))
321
      self.assertTrue(fn([
322
        (2028, [constants.JOB_STATUS_ERROR]),
323
        (18750, []),
324
        (5063, [constants.JOB_STATUS_SUCCESS, constants.JOB_STATUS_ERROR]),
325
        ]))
326

    
327
      self.assertFalse(fn(1))
328
      self.assertFalse(fn([(9, )]))
329
      self.assertFalse(fn([(15194, constants.JOB_STATUS_ERROR)]))
330

    
331
    for i in [
332
      [(-1, [])],
333
      [(-27740, [constants.JOB_STATUS_CANCELED, constants.JOB_STATUS_ERROR]),
334
       (-1, [constants.JOB_STATUS_ERROR]),
335
       (9921, [])],
336
      ]:
337
      self.assertTrue(check_relative(i))
338
      self.assertFalse(check_norelative(i))
339

    
340

    
341
class TestResultChecks(unittest.TestCase):
342
  def testJobIdList(self):
343
    for i in [[], [(False, "error")], [(False, "")],
344
              [(True, 123), (True, "999")]]:
345
      self.assertTrue(opcodes.TJobIdList(i))
346

    
347
    for i in ["", [("x", 1)], [[], []], [[False, "", None], [True, 123]]]:
348
      self.assertFalse(opcodes.TJobIdList(i))
349

    
350
  def testJobIdListOnly(self):
351
    self.assertTrue(opcodes.TJobIdListOnly({
352
      constants.JOB_IDS_KEY: [],
353
      }))
354
    self.assertTrue(opcodes.TJobIdListOnly({
355
      constants.JOB_IDS_KEY: [(True, "9282")],
356
      }))
357

    
358
    self.assertFalse(opcodes.TJobIdListOnly({
359
      "x": None,
360
      }))
361
    self.assertFalse(opcodes.TJobIdListOnly({
362
      constants.JOB_IDS_KEY: [],
363
      "x": None,
364
      }))
365
    self.assertFalse(opcodes.TJobIdListOnly({
366
      constants.JOB_IDS_KEY: [("foo", "bar")],
367
      }))
368
    self.assertFalse(opcodes.TJobIdListOnly({
369
      constants.JOB_IDS_KEY: [("one", "two", "three")],
370
      }))
371

    
372

    
373
class TestClusterOsList(unittest.TestCase):
374
  def test(self):
375
    good = [
376
      None,
377
      [],
378
      [(constants.DDM_ADD, "dos"),
379
       (constants.DDM_REMOVE, "linux")],
380
      ]
381

    
382
    for i in good:
383
      self.assertTrue(opcodes._TestClusterOsList(i))
384

    
385
    wrong = ["", 0, "xy", ["Hello World"], object(),
386
      [("foo", "bar")],
387
      [("", "")],
388
      [[constants.DDM_ADD]],
389
      [(constants.DDM_ADD, "")],
390
      [(constants.DDM_REMOVE, "")],
391
      [(constants.DDM_ADD, None)],
392
      [(constants.DDM_REMOVE, None)],
393
      ]
394

    
395
    for i in wrong:
396
      self.assertFalse(opcodes._TestClusterOsList(i))
397

    
398

    
399
class TestOpInstanceSetParams(unittest.TestCase):
400
  def _GenericTests(self, fn):
401
    self.assertTrue(fn([]))
402
    self.assertTrue(fn([(constants.DDM_ADD, {})]))
403
    self.assertTrue(fn([(constants.DDM_REMOVE, {})]))
404
    for i in [0, 1, 2, 3, 9, 10, 1024]:
405
      self.assertTrue(fn([(i, {})]))
406

    
407
    self.assertFalse(fn(None))
408
    self.assertFalse(fn({}))
409
    self.assertFalse(fn(""))
410
    self.assertFalse(fn(0))
411
    self.assertFalse(fn([(-100, {})]))
412
    self.assertFalse(fn([(constants.DDM_ADD, 2, 3)]))
413
    self.assertFalse(fn([[constants.DDM_ADD]]))
414

    
415
  def testNicModifications(self):
416
    fn = opcodes.OpInstanceSetParams.TestNicModifications
417
    self._GenericTests(fn)
418

    
419
    for param in constants.INIC_PARAMS:
420
      self.assertTrue(fn([[constants.DDM_ADD, {param: None}]]))
421
      self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
422

    
423
  def testDiskModifications(self):
424
    fn = opcodes.OpInstanceSetParams.TestDiskModifications
425
    self._GenericTests(fn)
426

    
427
    for param in constants.IDISK_PARAMS:
428
      self.assertTrue(fn([[constants.DDM_ADD, {param: 0}]]))
429
      self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
430

    
431

    
432
if __name__ == "__main__":
433
  testutils.GanetiTestProgram()