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