root / lib / opcodes_base.py @ 912b2278
History | View | Annotate | Download (7.7 kB)
1 | 580b1fdd | Jose A. Lopes | #
|
---|---|---|---|
2 | 580b1fdd | Jose A. Lopes | #
|
3 | 580b1fdd | Jose A. Lopes | |
4 | 580b1fdd | Jose A. Lopes | # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
|
5 | 580b1fdd | Jose A. Lopes | #
|
6 | 580b1fdd | Jose A. Lopes | # This program is free software; you can redistribute it and/or modify
|
7 | 580b1fdd | Jose A. Lopes | # it under the terms of the GNU General Public License as published by
|
8 | 580b1fdd | Jose A. Lopes | # the Free Software Foundation; either version 2 of the License, or
|
9 | 580b1fdd | Jose A. Lopes | # (at your option) any later version.
|
10 | 580b1fdd | Jose A. Lopes | #
|
11 | 580b1fdd | Jose A. Lopes | # This program is distributed in the hope that it will be useful, but
|
12 | 580b1fdd | Jose A. Lopes | # WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 | 580b1fdd | Jose A. Lopes | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 | 580b1fdd | Jose A. Lopes | # General Public License for more details.
|
15 | 580b1fdd | Jose A. Lopes | #
|
16 | 580b1fdd | Jose A. Lopes | # You should have received a copy of the GNU General Public License
|
17 | 580b1fdd | Jose A. Lopes | # along with this program; if not, write to the Free Software
|
18 | 580b1fdd | Jose A. Lopes | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 | 580b1fdd | Jose A. Lopes | # 02110-1301, USA.
|
20 | 580b1fdd | Jose A. Lopes | |
21 | 580b1fdd | Jose A. Lopes | |
22 | 580b1fdd | Jose A. Lopes | """OpCodes base module
|
23 | 580b1fdd | Jose A. Lopes |
|
24 | 580b1fdd | Jose A. Lopes | This module implements part of the data structures which define the
|
25 | 580b1fdd | Jose A. Lopes | cluster operations - the so-called opcodes.
|
26 | 580b1fdd | Jose A. Lopes |
|
27 | 580b1fdd | Jose A. Lopes | Every operation which modifies the cluster state is expressed via
|
28 | 580b1fdd | Jose A. Lopes | opcodes.
|
29 | 580b1fdd | Jose A. Lopes |
|
30 | 580b1fdd | Jose A. Lopes | """
|
31 | 580b1fdd | Jose A. Lopes | |
32 | 580b1fdd | Jose A. Lopes | # this are practically structures, so disable the message about too
|
33 | 580b1fdd | Jose A. Lopes | # few public methods:
|
34 | 580b1fdd | Jose A. Lopes | # pylint: disable=R0903
|
35 | 580b1fdd | Jose A. Lopes | |
36 | 580b1fdd | Jose A. Lopes | import copy |
37 | 580b1fdd | Jose A. Lopes | import logging |
38 | 580b1fdd | Jose A. Lopes | import re |
39 | 580b1fdd | Jose A. Lopes | |
40 | 580b1fdd | Jose A. Lopes | from ganeti import constants |
41 | 580b1fdd | Jose A. Lopes | from ganeti import errors |
42 | 580b1fdd | Jose A. Lopes | from ganeti import ht |
43 | 580b1fdd | Jose A. Lopes | from ganeti import outils |
44 | 580b1fdd | Jose A. Lopes | |
45 | 580b1fdd | Jose A. Lopes | |
46 | 580b1fdd | Jose A. Lopes | #: OP_ID conversion regular expression
|
47 | 580b1fdd | Jose A. Lopes | _OPID_RE = re.compile("([a-z])([A-Z])")
|
48 | 580b1fdd | Jose A. Lopes | |
49 | 580b1fdd | Jose A. Lopes | SUMMARY_PREFIX = { |
50 | 580b1fdd | Jose A. Lopes | "CLUSTER_": "C_", |
51 | 580b1fdd | Jose A. Lopes | "GROUP_": "G_", |
52 | 580b1fdd | Jose A. Lopes | "NODE_": "N_", |
53 | 580b1fdd | Jose A. Lopes | "INSTANCE_": "I_", |
54 | 580b1fdd | Jose A. Lopes | } |
55 | 580b1fdd | Jose A. Lopes | |
56 | 580b1fdd | Jose A. Lopes | #: Attribute name for dependencies
|
57 | 580b1fdd | Jose A. Lopes | DEPEND_ATTR = "depends"
|
58 | 580b1fdd | Jose A. Lopes | |
59 | 580b1fdd | Jose A. Lopes | #: Attribute name for comment
|
60 | 580b1fdd | Jose A. Lopes | COMMENT_ATTR = "comment"
|
61 | 580b1fdd | Jose A. Lopes | |
62 | 580b1fdd | Jose A. Lopes | |
63 | 580b1fdd | Jose A. Lopes | def _NameComponents(name): |
64 | 580b1fdd | Jose A. Lopes | """Split an opcode class name into its components
|
65 | 580b1fdd | Jose A. Lopes |
|
66 | 580b1fdd | Jose A. Lopes | @type name: string
|
67 | 580b1fdd | Jose A. Lopes | @param name: the class name, as OpXxxYyy
|
68 | 580b1fdd | Jose A. Lopes | @rtype: array of strings
|
69 | 580b1fdd | Jose A. Lopes | @return: the components of the name
|
70 | 580b1fdd | Jose A. Lopes |
|
71 | 580b1fdd | Jose A. Lopes | """
|
72 | 580b1fdd | Jose A. Lopes | assert name.startswith("Op") |
73 | 580b1fdd | Jose A. Lopes | # Note: (?<=[a-z])(?=[A-Z]) would be ideal, since it wouldn't
|
74 | 580b1fdd | Jose A. Lopes | # consume any input, and hence we would just have all the elements
|
75 | 580b1fdd | Jose A. Lopes | # in the list, one by one; but it seems that split doesn't work on
|
76 | 580b1fdd | Jose A. Lopes | # non-consuming input, hence we have to process the input string a
|
77 | 580b1fdd | Jose A. Lopes | # bit
|
78 | 580b1fdd | Jose A. Lopes | name = _OPID_RE.sub(r"\1,\2", name)
|
79 | 580b1fdd | Jose A. Lopes | elems = name.split(",")
|
80 | 580b1fdd | Jose A. Lopes | return elems
|
81 | 580b1fdd | Jose A. Lopes | |
82 | 580b1fdd | Jose A. Lopes | |
83 | 580b1fdd | Jose A. Lopes | def _NameToId(name): |
84 | 580b1fdd | Jose A. Lopes | """Convert an opcode class name to an OP_ID.
|
85 | 580b1fdd | Jose A. Lopes |
|
86 | 580b1fdd | Jose A. Lopes | @type name: string
|
87 | 580b1fdd | Jose A. Lopes | @param name: the class name, as OpXxxYyy
|
88 | 580b1fdd | Jose A. Lopes | @rtype: string
|
89 | 580b1fdd | Jose A. Lopes | @return: the name in the OP_XXXX_YYYY format
|
90 | 580b1fdd | Jose A. Lopes |
|
91 | 580b1fdd | Jose A. Lopes | """
|
92 | 580b1fdd | Jose A. Lopes | if not name.startswith("Op"): |
93 | 580b1fdd | Jose A. Lopes | return None |
94 | 580b1fdd | Jose A. Lopes | return "_".join(n.upper() for n in _NameComponents(name)) |
95 | 580b1fdd | Jose A. Lopes | |
96 | 580b1fdd | Jose A. Lopes | |
97 | 580b1fdd | Jose A. Lopes | def NameToReasonSrc(name): |
98 | 580b1fdd | Jose A. Lopes | """Convert an opcode class name to a source string for the reason trail
|
99 | 580b1fdd | Jose A. Lopes |
|
100 | 580b1fdd | Jose A. Lopes | @type name: string
|
101 | 580b1fdd | Jose A. Lopes | @param name: the class name, as OpXxxYyy
|
102 | 580b1fdd | Jose A. Lopes | @rtype: string
|
103 | 580b1fdd | Jose A. Lopes | @return: the name in the OP_XXXX_YYYY format
|
104 | 580b1fdd | Jose A. Lopes |
|
105 | 580b1fdd | Jose A. Lopes | """
|
106 | 580b1fdd | Jose A. Lopes | if not name.startswith("Op"): |
107 | 580b1fdd | Jose A. Lopes | return None |
108 | 580b1fdd | Jose A. Lopes | return "%s:%s" % (constants.OPCODE_REASON_SRC_OPCODE, |
109 | 580b1fdd | Jose A. Lopes | "_".join(n.lower() for n in _NameComponents(name))) |
110 | 580b1fdd | Jose A. Lopes | |
111 | 580b1fdd | Jose A. Lopes | |
112 | 580b1fdd | Jose A. Lopes | class _AutoOpParamSlots(outils.AutoSlots): |
113 | 580b1fdd | Jose A. Lopes | """Meta class for opcode definitions.
|
114 | 580b1fdd | Jose A. Lopes |
|
115 | 580b1fdd | Jose A. Lopes | """
|
116 | 580b1fdd | Jose A. Lopes | def __new__(mcs, name, bases, attrs): |
117 | 580b1fdd | Jose A. Lopes | """Called when a class should be created.
|
118 | 580b1fdd | Jose A. Lopes |
|
119 | 580b1fdd | Jose A. Lopes | @param mcs: The meta class
|
120 | 580b1fdd | Jose A. Lopes | @param name: Name of created class
|
121 | 580b1fdd | Jose A. Lopes | @param bases: Base classes
|
122 | 580b1fdd | Jose A. Lopes | @type attrs: dict
|
123 | 580b1fdd | Jose A. Lopes | @param attrs: Class attributes
|
124 | 580b1fdd | Jose A. Lopes |
|
125 | 580b1fdd | Jose A. Lopes | """
|
126 | 580b1fdd | Jose A. Lopes | assert "OP_ID" not in attrs, "Class '%s' defining OP_ID" % name |
127 | 580b1fdd | Jose A. Lopes | |
128 | 580b1fdd | Jose A. Lopes | slots = mcs._GetSlots(attrs) |
129 | 580b1fdd | Jose A. Lopes | assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \ |
130 | 580b1fdd | Jose A. Lopes | "Class '%s' uses unknown field in OP_DSC_FIELD" % name
|
131 | 580b1fdd | Jose A. Lopes | assert ("OP_DSC_FORMATTER" not in attrs or |
132 | 580b1fdd | Jose A. Lopes | callable(attrs["OP_DSC_FORMATTER"])), \ |
133 | 580b1fdd | Jose A. Lopes | ("Class '%s' uses non-callable in OP_DSC_FORMATTER (%s)" %
|
134 | 580b1fdd | Jose A. Lopes | (name, type(attrs["OP_DSC_FORMATTER"]))) |
135 | 580b1fdd | Jose A. Lopes | |
136 | 580b1fdd | Jose A. Lopes | attrs["OP_ID"] = _NameToId(name)
|
137 | 580b1fdd | Jose A. Lopes | |
138 | 580b1fdd | Jose A. Lopes | return outils.AutoSlots.__new__(mcs, name, bases, attrs)
|
139 | 580b1fdd | Jose A. Lopes | |
140 | 580b1fdd | Jose A. Lopes | @classmethod
|
141 | 580b1fdd | Jose A. Lopes | def _GetSlots(mcs, attrs): |
142 | 580b1fdd | Jose A. Lopes | """Build the slots out of OP_PARAMS.
|
143 | 580b1fdd | Jose A. Lopes |
|
144 | 580b1fdd | Jose A. Lopes | """
|
145 | 580b1fdd | Jose A. Lopes | # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
|
146 | 580b1fdd | Jose A. Lopes | params = attrs.setdefault("OP_PARAMS", [])
|
147 | 580b1fdd | Jose A. Lopes | |
148 | 580b1fdd | Jose A. Lopes | # Use parameter names as slots
|
149 | 580b1fdd | Jose A. Lopes | return [pname for (pname, _, _, _) in params] |
150 | 580b1fdd | Jose A. Lopes | |
151 | 580b1fdd | Jose A. Lopes | |
152 | 580b1fdd | Jose A. Lopes | class BaseOpCode(outils.ValidatedSlots): |
153 | 580b1fdd | Jose A. Lopes | """A simple serializable object.
|
154 | 580b1fdd | Jose A. Lopes |
|
155 | 580b1fdd | Jose A. Lopes | This object serves as a parent class for OpCode without any custom
|
156 | 580b1fdd | Jose A. Lopes | field handling.
|
157 | 580b1fdd | Jose A. Lopes |
|
158 | 580b1fdd | Jose A. Lopes | """
|
159 | 580b1fdd | Jose A. Lopes | # pylint: disable=E1101
|
160 | 580b1fdd | Jose A. Lopes | # as OP_ID is dynamically defined
|
161 | 580b1fdd | Jose A. Lopes | __metaclass__ = _AutoOpParamSlots |
162 | 580b1fdd | Jose A. Lopes | |
163 | a9e1819b | Klaus Aehlig | def __init__(self, **kwargs): |
164 | a9e1819b | Klaus Aehlig | outils.ValidatedSlots.__init__(self, **kwargs)
|
165 | a9e1819b | Klaus Aehlig | for key, default, _, _ in self.__class__.GetAllParams(): |
166 | a9e1819b | Klaus Aehlig | if not hasattr(self, key): |
167 | a9e1819b | Klaus Aehlig | setattr(self, key, default) |
168 | a9e1819b | Klaus Aehlig | |
169 | 580b1fdd | Jose A. Lopes | def __getstate__(self): |
170 | 580b1fdd | Jose A. Lopes | """Generic serializer.
|
171 | 580b1fdd | Jose A. Lopes |
|
172 | 580b1fdd | Jose A. Lopes | This method just returns the contents of the instance as a
|
173 | 580b1fdd | Jose A. Lopes | dictionary.
|
174 | 580b1fdd | Jose A. Lopes |
|
175 | 580b1fdd | Jose A. Lopes | @rtype: C{dict}
|
176 | 580b1fdd | Jose A. Lopes | @return: the instance attributes and their values
|
177 | 580b1fdd | Jose A. Lopes |
|
178 | 580b1fdd | Jose A. Lopes | """
|
179 | 580b1fdd | Jose A. Lopes | state = {} |
180 | 580b1fdd | Jose A. Lopes | for name in self.GetAllSlots(): |
181 | 580b1fdd | Jose A. Lopes | if hasattr(self, name): |
182 | 580b1fdd | Jose A. Lopes | state[name] = getattr(self, name) |
183 | 580b1fdd | Jose A. Lopes | return state
|
184 | 580b1fdd | Jose A. Lopes | |
185 | 580b1fdd | Jose A. Lopes | def __setstate__(self, state): |
186 | 580b1fdd | Jose A. Lopes | """Generic unserializer.
|
187 | 580b1fdd | Jose A. Lopes |
|
188 | 580b1fdd | Jose A. Lopes | This method just restores from the serialized state the attributes
|
189 | 580b1fdd | Jose A. Lopes | of the current instance.
|
190 | 580b1fdd | Jose A. Lopes |
|
191 | 580b1fdd | Jose A. Lopes | @param state: the serialized opcode data
|
192 | 580b1fdd | Jose A. Lopes | @type state: C{dict}
|
193 | 580b1fdd | Jose A. Lopes |
|
194 | 580b1fdd | Jose A. Lopes | """
|
195 | 580b1fdd | Jose A. Lopes | if not isinstance(state, dict): |
196 | 580b1fdd | Jose A. Lopes | raise ValueError("Invalid data to __setstate__: expected dict, got %s" % |
197 | 580b1fdd | Jose A. Lopes | type(state))
|
198 | 580b1fdd | Jose A. Lopes | |
199 | 580b1fdd | Jose A. Lopes | for name in self.GetAllSlots(): |
200 | 580b1fdd | Jose A. Lopes | if name not in state and hasattr(self, name): |
201 | 580b1fdd | Jose A. Lopes | delattr(self, name) |
202 | 580b1fdd | Jose A. Lopes | |
203 | 580b1fdd | Jose A. Lopes | for name in state: |
204 | 580b1fdd | Jose A. Lopes | setattr(self, name, state[name]) |
205 | 580b1fdd | Jose A. Lopes | |
206 | 580b1fdd | Jose A. Lopes | @classmethod
|
207 | 580b1fdd | Jose A. Lopes | def GetAllParams(cls): |
208 | 580b1fdd | Jose A. Lopes | """Compute list of all parameters for an opcode.
|
209 | 580b1fdd | Jose A. Lopes |
|
210 | 580b1fdd | Jose A. Lopes | """
|
211 | 580b1fdd | Jose A. Lopes | slots = [] |
212 | 580b1fdd | Jose A. Lopes | for parent in cls.__mro__: |
213 | 580b1fdd | Jose A. Lopes | slots.extend(getattr(parent, "OP_PARAMS", [])) |
214 | 580b1fdd | Jose A. Lopes | return slots
|
215 | 580b1fdd | Jose A. Lopes | |
216 | 580b1fdd | Jose A. Lopes | def Validate(self, set_defaults): # pylint: disable=W0221 |
217 | 580b1fdd | Jose A. Lopes | """Validate opcode parameters, optionally setting default values.
|
218 | 580b1fdd | Jose A. Lopes |
|
219 | 580b1fdd | Jose A. Lopes | @type set_defaults: bool
|
220 | 580b1fdd | Jose A. Lopes | @param set_defaults: Whether to set default values
|
221 | 580b1fdd | Jose A. Lopes | @raise errors.OpPrereqError: When a parameter value doesn't match
|
222 | 580b1fdd | Jose A. Lopes | requirements
|
223 | 580b1fdd | Jose A. Lopes |
|
224 | 580b1fdd | Jose A. Lopes | """
|
225 | 580b1fdd | Jose A. Lopes | for (attr_name, default, test, _) in self.GetAllParams(): |
226 | 580b1fdd | Jose A. Lopes | assert callable(test) |
227 | 580b1fdd | Jose A. Lopes | |
228 | 580b1fdd | Jose A. Lopes | if hasattr(self, attr_name): |
229 | 580b1fdd | Jose A. Lopes | attr_val = getattr(self, attr_name) |
230 | 580b1fdd | Jose A. Lopes | else:
|
231 | 580b1fdd | Jose A. Lopes | attr_val = copy.deepcopy(default) |
232 | 580b1fdd | Jose A. Lopes | |
233 | 580b1fdd | Jose A. Lopes | if test(attr_val):
|
234 | 580b1fdd | Jose A. Lopes | if set_defaults:
|
235 | 580b1fdd | Jose A. Lopes | setattr(self, attr_name, attr_val) |
236 | 580b1fdd | Jose A. Lopes | elif ht.TInt(attr_val) and test(float(attr_val)): |
237 | 580b1fdd | Jose A. Lopes | if set_defaults:
|
238 | 580b1fdd | Jose A. Lopes | setattr(self, attr_name, float(attr_val)) |
239 | 580b1fdd | Jose A. Lopes | else:
|
240 | 580b1fdd | Jose A. Lopes | logging.error("OpCode %s, parameter %s, has invalid type %s/value"
|
241 | 580b1fdd | Jose A. Lopes | " '%s' expecting type %s",
|
242 | 580b1fdd | Jose A. Lopes | self.OP_ID, attr_name, type(attr_val), attr_val, test) |
243 | 580b1fdd | Jose A. Lopes | |
244 | 580b1fdd | Jose A. Lopes | if attr_val is None: |
245 | 580b1fdd | Jose A. Lopes | logging.error("OpCode %s, parameter %s, has default value None which"
|
246 | 580b1fdd | Jose A. Lopes | " is does not check against the parameter's type: this"
|
247 | 580b1fdd | Jose A. Lopes | " means this parameter is required but no value was"
|
248 | 580b1fdd | Jose A. Lopes | " given",
|
249 | 580b1fdd | Jose A. Lopes | self.OP_ID, attr_name)
|
250 | 580b1fdd | Jose A. Lopes | |
251 | 580b1fdd | Jose A. Lopes | raise errors.OpPrereqError("Parameter '%s.%s' fails validation" % |
252 | 580b1fdd | Jose A. Lopes | (self.OP_ID, attr_name),
|
253 | 580b1fdd | Jose A. Lopes | errors.ECODE_INVAL) |
254 | 580b1fdd | Jose A. Lopes | |
255 | 580b1fdd | Jose A. Lopes | |
256 | 580b1fdd | Jose A. Lopes | def BuildJobDepCheck(relative): |
257 | 580b1fdd | Jose A. Lopes | """Builds check for job dependencies (L{DEPEND_ATTR}).
|
258 | 580b1fdd | Jose A. Lopes |
|
259 | 580b1fdd | Jose A. Lopes | @type relative: bool
|
260 | 580b1fdd | Jose A. Lopes | @param relative: Whether to accept relative job IDs (negative)
|
261 | 580b1fdd | Jose A. Lopes | @rtype: callable
|
262 | 580b1fdd | Jose A. Lopes |
|
263 | 580b1fdd | Jose A. Lopes | """
|
264 | 580b1fdd | Jose A. Lopes | if relative:
|
265 | 580b1fdd | Jose A. Lopes | job_id = ht.TOr(ht.TJobId, ht.TRelativeJobId) |
266 | 580b1fdd | Jose A. Lopes | else:
|
267 | 580b1fdd | Jose A. Lopes | job_id = ht.TJobId |
268 | 580b1fdd | Jose A. Lopes | |
269 | 580b1fdd | Jose A. Lopes | job_dep = \ |
270 | 580b1fdd | Jose A. Lopes | ht.TAnd(ht.TOr(ht.TListOf(ht.TAny), ht.TTuple), |
271 | 580b1fdd | Jose A. Lopes | ht.TIsLength(2),
|
272 | 580b1fdd | Jose A. Lopes | ht.TItems([job_id, |
273 | 580b1fdd | Jose A. Lopes | ht.TListOf(ht.TElemOf(constants.JOBS_FINALIZED))])) |
274 | 580b1fdd | Jose A. Lopes | |
275 | 580b1fdd | Jose A. Lopes | return ht.TMaybe(ht.TListOf(job_dep))
|
276 | 580b1fdd | Jose A. Lopes | |
277 | 580b1fdd | Jose A. Lopes | |
278 | 580b1fdd | Jose A. Lopes | TNoRelativeJobDependencies = BuildJobDepCheck(False) |