4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
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.
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.
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
22 """OpCodes base module
24 This module implements part of the data structures which define the
25 cluster operations - the so-called opcodes.
27 Every operation which modifies the cluster state is expressed via
32 # this are practically structures, so disable the message about too
34 # pylint: disable=R0903
40 from ganeti import constants
41 from ganeti import errors
43 from ganeti import outils
46 #: OP_ID conversion regular expression
47 _OPID_RE = re.compile("([a-z])([A-Z])")
56 #: Attribute name for dependencies
57 DEPEND_ATTR = "depends"
59 #: Attribute name for comment
60 COMMENT_ATTR = "comment"
63 def _NameComponents(name):
64 """Split an opcode class name into its components
67 @param name: the class name, as OpXxxYyy
68 @rtype: array of strings
69 @return: the components of the name
72 assert name.startswith("Op")
73 # Note: (?<=[a-z])(?=[A-Z]) would be ideal, since it wouldn't
74 # consume any input, and hence we would just have all the elements
75 # in the list, one by one; but it seems that split doesn't work on
76 # non-consuming input, hence we have to process the input string a
78 name = _OPID_RE.sub(r"\1,\2", name)
79 elems = name.split(",")
84 """Convert an opcode class name to an OP_ID.
87 @param name: the class name, as OpXxxYyy
89 @return: the name in the OP_XXXX_YYYY format
92 if not name.startswith("Op"):
94 return "_".join(n.upper() for n in _NameComponents(name))
97 def NameToReasonSrc(name):
98 """Convert an opcode class name to a source string for the reason trail
101 @param name: the class name, as OpXxxYyy
103 @return: the name in the OP_XXXX_YYYY format
106 if not name.startswith("Op"):
108 return "%s:%s" % (constants.OPCODE_REASON_SRC_OPCODE,
109 "_".join(n.lower() for n in _NameComponents(name)))
112 class _AutoOpParamSlots(outils.AutoSlots):
113 """Meta class for opcode definitions.
116 def __new__(mcs, name, bases, attrs):
117 """Called when a class should be created.
119 @param mcs: The meta class
120 @param name: Name of created class
121 @param bases: Base classes
123 @param attrs: Class attributes
126 assert "OP_ID" not in attrs, "Class '%s' defining OP_ID" % name
128 slots = mcs._GetSlots(attrs)
129 assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
130 "Class '%s' uses unknown field in OP_DSC_FIELD" % name
131 assert ("OP_DSC_FORMATTER" not in attrs or
132 callable(attrs["OP_DSC_FORMATTER"])), \
133 ("Class '%s' uses non-callable in OP_DSC_FORMATTER (%s)" %
134 (name, type(attrs["OP_DSC_FORMATTER"])))
136 attrs["OP_ID"] = _NameToId(name)
138 return outils.AutoSlots.__new__(mcs, name, bases, attrs)
141 def _GetSlots(mcs, attrs):
142 """Build the slots out of OP_PARAMS.
145 # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
146 params = attrs.setdefault("OP_PARAMS", [])
148 # Use parameter names as slots
149 return [pname for (pname, _, _, _) in params]
152 class BaseOpCode(outils.ValidatedSlots):
153 """A simple serializable object.
155 This object serves as a parent class for OpCode without any custom
159 # pylint: disable=E1101
160 # as OP_ID is dynamically defined
161 __metaclass__ = _AutoOpParamSlots
163 def __getstate__(self):
164 """Generic serializer.
166 This method just returns the contents of the instance as a
170 @return: the instance attributes and their values
174 for name in self.GetAllSlots():
175 if hasattr(self, name):
176 state[name] = getattr(self, name)
179 def __setstate__(self, state):
180 """Generic unserializer.
182 This method just restores from the serialized state the attributes
183 of the current instance.
185 @param state: the serialized opcode data
189 if not isinstance(state, dict):
190 raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
193 for name in self.GetAllSlots():
194 if name not in state and hasattr(self, name):
198 setattr(self, name, state[name])
201 def GetAllParams(cls):
202 """Compute list of all parameters for an opcode.
206 for parent in cls.__mro__:
207 slots.extend(getattr(parent, "OP_PARAMS", []))
210 def Validate(self, set_defaults): # pylint: disable=W0221
211 """Validate opcode parameters, optionally setting default values.
213 @type set_defaults: bool
214 @param set_defaults: Whether to set default values
215 @raise errors.OpPrereqError: When a parameter value doesn't match
219 for (attr_name, default, test, _) in self.GetAllParams():
220 assert callable(test)
222 if hasattr(self, attr_name):
223 attr_val = getattr(self, attr_name)
225 attr_val = copy.deepcopy(default)
229 setattr(self, attr_name, attr_val)
230 elif ht.TInt(attr_val) and test(float(attr_val)):
232 setattr(self, attr_name, float(attr_val))
234 logging.error("OpCode %s, parameter %s, has invalid type %s/value"
235 " '%s' expecting type %s",
236 self.OP_ID, attr_name, type(attr_val), attr_val, test)
239 logging.error("OpCode %s, parameter %s, has default value None which"
240 " is does not check against the parameter's type: this"
241 " means this parameter is required but no value was"
243 self.OP_ID, attr_name)
245 raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
246 (self.OP_ID, attr_name),
250 def BuildJobDepCheck(relative):
251 """Builds check for job dependencies (L{DEPEND_ATTR}).
254 @param relative: Whether to accept relative job IDs (negative)
259 job_id = ht.TOr(ht.TJobId, ht.TRelativeJobId)
264 ht.TAnd(ht.TOr(ht.TListOf(ht.TAny), ht.TTuple),
267 ht.TListOf(ht.TElemOf(constants.JOBS_FINALIZED))]))
269 return ht.TMaybe(ht.TListOf(job_dep))
272 TNoRelativeJobDependencies = BuildJobDepCheck(False)