Commit 1cbef6d8 authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

Migrate code verifying opcode parameters to base class



This allows the function to be used in other places as well.
An optional parameter is added to control whether default
values should be set. Unittests are added, providing full
coverage.
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarIustin Pop <iustin@google.com>
parent dbc96028
...@@ -54,7 +54,6 @@ from ganeti import uidpool ...@@ -54,7 +54,6 @@ from ganeti import uidpool
from ganeti import compat from ganeti import compat
from ganeti import masterd from ganeti import masterd
from ganeti import netutils from ganeti import netutils
from ganeti import ht
from ganeti import query from ganeti import query
from ganeti import qlang from ganeti import qlang
from ganeti import opcodes from ganeti import opcodes
...@@ -134,32 +133,8 @@ class LogicalUnit(object): ...@@ -134,32 +133,8 @@ class LogicalUnit(object):
# Tasklets # Tasklets
self.tasklets = None self.tasklets = None
# The new kind-of-type-system # Validate opcode parameters and set defaults
op_id = self.op.OP_ID self.op.Validate(True)
for attr_name, aval, test in self.op.GetAllParams():
if not hasattr(op, attr_name):
if aval == ht.NoDefault:
raise errors.OpPrereqError("Required parameter '%s.%s' missing" %
(op_id, attr_name), errors.ECODE_INVAL)
else:
if callable(aval):
dval = aval()
else:
dval = aval
setattr(self.op, attr_name, dval)
attr_val = getattr(op, attr_name)
if test == ht.NoType:
# no tests here
continue
if not callable(test):
raise errors.ProgrammerError("Validation for parameter '%s.%s' failed,"
" given type is not a proper type (%s)" %
(op_id, attr_name, test))
if not test(attr_val):
logging.error("OpCode %s, parameter %s, has invalid type %s/value %s",
self.op.OP_ID, attr_name, type(attr_val), attr_val)
raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
(op_id, attr_name), errors.ECODE_INVAL)
self.CheckArguments() self.CheckArguments()
......
...@@ -33,6 +33,8 @@ opcodes. ...@@ -33,6 +33,8 @@ opcodes.
# few public methods: # few public methods:
# pylint: disable-msg=R0903 # pylint: disable-msg=R0903
import logging
from ganeti import constants from ganeti import constants
from ganeti import errors from ganeti import errors
from ganeti import ht from ganeti import ht
...@@ -236,6 +238,43 @@ class BaseOpCode(object): ...@@ -236,6 +238,43 @@ class BaseOpCode(object):
slots.extend(getattr(parent, "OP_PARAMS", [])) slots.extend(getattr(parent, "OP_PARAMS", []))
return slots return slots
def Validate(self, set_defaults):
"""Validate opcode parameters, optionally setting default values.
@type set_defaults: bool
@param set_defaults: Whether to set default values
@raise errors.OpPrereqError: When a parameter value doesn't match
requirements
"""
for (attr_name, default, test) in self.GetAllParams():
assert test == ht.NoType or callable(test)
if not hasattr(self, attr_name):
if default == ht.NoDefault:
raise errors.OpPrereqError("Required parameter '%s.%s' missing" %
(self.OP_ID, attr_name),
errors.ECODE_INVAL)
elif set_defaults:
if callable(default):
dval = default()
else:
dval = default
setattr(self, attr_name, dval)
if test == ht.NoType:
# no tests here
continue
if set_defaults or hasattr(self, attr_name):
attr_val = getattr(self, attr_name)
if not test(attr_val):
logging.error("OpCode %s, parameter %s, has invalid type %s/value %s",
self.OP_ID, attr_name, type(attr_val), attr_val)
raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
(self.OP_ID, attr_name),
errors.ECODE_INVAL)
class OpCode(BaseOpCode): class OpCode(BaseOpCode):
"""Abstract OpCode. """Abstract OpCode.
......
...@@ -28,6 +28,8 @@ import unittest ...@@ -28,6 +28,8 @@ import unittest
from ganeti import utils from ganeti import utils
from ganeti import opcodes from ganeti import opcodes
from ganeti import ht from ganeti import ht
from ganeti import constants
from ganeti import errors
import testutils import testutils
...@@ -131,6 +133,98 @@ class TestOpcodes(unittest.TestCase): ...@@ -131,6 +133,98 @@ class TestOpcodes(unittest.TestCase):
self.assertFalse(callable(aval()), self.assertFalse(callable(aval()),
msg="Default value returned by function is callable") msg="Default value returned by function is callable")
def testValidateNoModification(self):
class _TestOp(opcodes.OpCode):
OP_ID = "OP_TEST"
OP_PARAMS = [
("nodef", ht.NoDefault, ht.TMaybeString),
("wdef", "default", ht.TMaybeString),
("number", 0, ht.TInt),
("notype", None, ht.NoType),
]
# Missing required parameter "nodef"
op = _TestOp()
before = op.__getstate__()
self.assertRaises(errors.OpPrereqError, op.Validate, False)
self.assertFalse(hasattr(op, "nodef"))
self.assertFalse(hasattr(op, "wdef"))
self.assertFalse(hasattr(op, "number"))
self.assertFalse(hasattr(op, "notype"))
self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
# Required parameter "nodef" is provided
op = _TestOp(nodef="foo")
before = op.__getstate__()
op.Validate(False)
self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
self.assertEqual(op.nodef, "foo")
self.assertFalse(hasattr(op, "wdef"))
self.assertFalse(hasattr(op, "number"))
self.assertFalse(hasattr(op, "notype"))
# Missing required parameter "nodef"
op = _TestOp(wdef="hello", number=999)
before = op.__getstate__()
self.assertRaises(errors.OpPrereqError, op.Validate, False)
self.assertFalse(hasattr(op, "nodef"))
self.assertFalse(hasattr(op, "notype"))
self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
# Wrong type for "nodef"
op = _TestOp(nodef=987)
before = op.__getstate__()
self.assertRaises(errors.OpPrereqError, op.Validate, False)
self.assertEqual(op.nodef, 987)
self.assertFalse(hasattr(op, "notype"))
self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
# Testing different types for "notype"
op = _TestOp(nodef="foo", notype=[1, 2, 3])
before = op.__getstate__()
op.Validate(False)
self.assertEqual(op.nodef, "foo")
self.assertEqual(op.notype, [1, 2, 3])
self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
op = _TestOp(nodef="foo", notype="Hello World")
before = op.__getstate__()
op.Validate(False)
self.assertEqual(op.nodef, "foo")
self.assertEqual(op.notype, "Hello World")
self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
def testValidateSetDefaults(self):
class _TestOp(opcodes.OpCode):
OP_ID = "OP_TEST"
OP_PARAMS = [
# Static default value
("value1", "default", ht.TMaybeString),
# Default value callback
("value2", lambda: "result", ht.TMaybeString),
]
op = _TestOp()
before = op.__getstate__()
op.Validate(True)
self.assertNotEqual(op.__getstate__(), before,
msg="Opcode was not modified")
self.assertEqual(op.value1, "default")
self.assertEqual(op.value2, "result")
self.assert_(op.dry_run is None)
self.assert_(op.debug_level is None)
self.assertEqual(op.priority, constants.OP_PRIO_DEFAULT)
op = _TestOp(value1="hello", value2="world", debug_level=123)
before = op.__getstate__()
op.Validate(True)
self.assertNotEqual(op.__getstate__(), before,
msg="Opcode was not modified")
self.assertEqual(op.value1, "hello")
self.assertEqual(op.value2, "world")
self.assertEqual(op.debug_level, 123)
if __name__ == "__main__": if __name__ == "__main__":
testutils.GanetiTestProgram() testutils.GanetiTestProgram()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment