Statistics
| Branch: | Tag: | Revision:

root / lib / opcodes_base.py @ bc57fa8d

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)