Statistics
| Branch: | Tag: | Revision:

root / lib / opcodes_base.py @ be6cdf67

History | View | Annotate | Download (7.8 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, prefix):
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
  @type prefix: string
103
  @param prefix: the prefix that will be prepended to the opcode name
104
  @rtype: string
105
  @return: the name in the OP_XXXX_YYYY format
106

107
  """
108
  if not name.startswith("Op"):
109
    return None
110
  return "%s:%s" % (prefix,
111
                    "_".join(n.lower() for n in _NameComponents(name)))
112

    
113

    
114
class _AutoOpParamSlots(outils.AutoSlots):
115
  """Meta class for opcode definitions.
116

117
  """
118
  def __new__(mcs, name, bases, attrs):
119
    """Called when a class should be created.
120

121
    @param mcs: The meta class
122
    @param name: Name of created class
123
    @param bases: Base classes
124
    @type attrs: dict
125
    @param attrs: Class attributes
126

127
    """
128
    assert "OP_ID" not in attrs, "Class '%s' defining OP_ID" % name
129

    
130
    slots = mcs._GetSlots(attrs)
131
    assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
132
      "Class '%s' uses unknown field in OP_DSC_FIELD" % name
133
    assert ("OP_DSC_FORMATTER" not in attrs or
134
            callable(attrs["OP_DSC_FORMATTER"])), \
135
      ("Class '%s' uses non-callable in OP_DSC_FORMATTER (%s)" %
136
       (name, type(attrs["OP_DSC_FORMATTER"])))
137

    
138
    attrs["OP_ID"] = _NameToId(name)
139

    
140
    return outils.AutoSlots.__new__(mcs, name, bases, attrs)
141

    
142
  @classmethod
143
  def _GetSlots(mcs, attrs):
144
    """Build the slots out of OP_PARAMS.
145

146
    """
147
    # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
148
    params = attrs.setdefault("OP_PARAMS", [])
149

    
150
    # Use parameter names as slots
151
    return [pname for (pname, _, _, _) in params]
152

    
153

    
154
class BaseOpCode(outils.ValidatedSlots):
155
  """A simple serializable object.
156

157
  This object serves as a parent class for OpCode without any custom
158
  field handling.
159

160
  """
161
  # pylint: disable=E1101
162
  # as OP_ID is dynamically defined
163
  __metaclass__ = _AutoOpParamSlots
164

    
165
  def __init__(self, **kwargs):
166
    outils.ValidatedSlots.__init__(self, **kwargs)
167
    for key, default, _, _ in self.__class__.GetAllParams():
168
      if not hasattr(self, key):
169
        setattr(self, key, default)
170

    
171
  def __getstate__(self):
172
    """Generic serializer.
173

174
    This method just returns the contents of the instance as a
175
    dictionary.
176

177
    @rtype:  C{dict}
178
    @return: the instance attributes and their values
179

180
    """
181
    state = {}
182
    for name in self.GetAllSlots():
183
      if hasattr(self, name):
184
        state[name] = getattr(self, name)
185
    return state
186

    
187
  def __setstate__(self, state):
188
    """Generic unserializer.
189

190
    This method just restores from the serialized state the attributes
191
    of the current instance.
192

193
    @param state: the serialized opcode data
194
    @type state:  C{dict}
195

196
    """
197
    if not isinstance(state, dict):
198
      raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
199
                       type(state))
200

    
201
    for name in self.GetAllSlots():
202
      if name not in state and hasattr(self, name):
203
        delattr(self, name)
204

    
205
    for name in state:
206
      setattr(self, name, state[name])
207

    
208
  @classmethod
209
  def GetAllParams(cls):
210
    """Compute list of all parameters for an opcode.
211

212
    """
213
    slots = []
214
    for parent in cls.__mro__:
215
      slots.extend(getattr(parent, "OP_PARAMS", []))
216
    return slots
217

    
218
  def Validate(self, set_defaults): # pylint: disable=W0221
219
    """Validate opcode parameters, optionally setting default values.
220

221
    @type set_defaults: bool
222
    @param set_defaults: Whether to set default values
223
    @raise errors.OpPrereqError: When a parameter value doesn't match
224
                                 requirements
225

226
    """
227
    for (attr_name, default, test, _) in self.GetAllParams():
228
      assert callable(test)
229

    
230
      if hasattr(self, attr_name):
231
        attr_val = getattr(self, attr_name)
232
      else:
233
        attr_val = copy.deepcopy(default)
234

    
235
      if test(attr_val):
236
        if set_defaults:
237
          setattr(self, attr_name, attr_val)
238
      elif ht.TInt(attr_val) and test(float(attr_val)):
239
        if set_defaults:
240
          setattr(self, attr_name, float(attr_val))
241
      else:
242
        logging.error("OpCode %s, parameter %s, has invalid type %s/value"
243
                      " '%s' expecting type %s",
244
                      self.OP_ID, attr_name, type(attr_val), attr_val, test)
245

    
246
        if attr_val is None:
247
          logging.error("OpCode %s, parameter %s, has default value None which"
248
                        " is does not check against the parameter's type: this"
249
                        " means this parameter is required but no value was"
250
                        " given",
251
                        self.OP_ID, attr_name)
252

    
253
        raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
254
                                   (self.OP_ID, attr_name),
255
                                   errors.ECODE_INVAL)
256

    
257

    
258
def BuildJobDepCheck(relative):
259
  """Builds check for job dependencies (L{DEPEND_ATTR}).
260

261
  @type relative: bool
262
  @param relative: Whether to accept relative job IDs (negative)
263
  @rtype: callable
264

265
  """
266
  if relative:
267
    job_id = ht.TOr(ht.TJobId, ht.TRelativeJobId)
268
  else:
269
    job_id = ht.TJobId
270

    
271
  job_dep = \
272
    ht.TAnd(ht.TOr(ht.TListOf(ht.TAny), ht.TTuple),
273
            ht.TIsLength(2),
274
            ht.TItems([job_id,
275
                       ht.TListOf(ht.TElemOf(constants.JOBS_FINALIZED))]))
276

    
277
  return ht.TMaybe(ht.TListOf(job_dep))
278

    
279

    
280
TNoRelativeJobDependencies = BuildJobDepCheck(False)