Revision 5c947f38

b/lib/cmdlib.py
3506 3506
          if not rpc.call_export_remove(node, instance.name):
3507 3507
            logger.Error("could not remove older export for instance %s"
3508 3508
                         " on node %s" % (instance.name, node))
3509

  
3510

  
3511
class TagsLU(NoHooksLU):
3512
  """Generic tags LU.
3513

  
3514
  This is an abstract class which is the parent of all the other tags LUs.
3515

  
3516
  """
3517
  def CheckPrereq(self):
3518
    """Check prerequisites.
3519

  
3520
    """
3521
    if self.op.kind == constants.TAG_CLUSTER:
3522
      self.target = self.cfg.GetClusterInfo()
3523
    elif self.op.kind == constants.TAG_NODE:
3524
      name = self.cfg.ExpandNodeName(self.op.name)
3525
      if name is None:
3526
        raise errors.OpPrereqError, ("Invalid node name (%s)" %
3527
                                     (self.op.name,))
3528
      self.op.name = name
3529
      self.target = self.cfg.GetNodeInfo(name)
3530
    elif self.op.kind == constants.TAG_INSTANCE:
3531
      name = self.cfg.ExpandInstanceName(name)
3532
      if name is None:
3533
        raise errors.OpPrereqError, ("Invalid instance name (%s)" %
3534
                                     (self.op.name,))
3535
      self.op.name = name
3536
      self.target = self.cfg.GetInstanceInfo(name)
3537
    else:
3538
      raise errors.OpPrereqError, ("Wrong tag type requested (%s)" %
3539
                                   str(self.op.kind))
3540

  
3541

  
3542
class LUGetTags(TagsLU):
3543
  """Returns the tags of a given object.
3544

  
3545
  """
3546
  _OP_REQP = ["kind", "name"]
3547

  
3548
  def Exec(self, feedback_fn):
3549
    """Returns the tag list.
3550

  
3551
    """
3552
    return self.target.GetTags()
3553

  
3554

  
3555
class LUAddTag(TagsLU):
3556
  """Sets a tag on a given object.
3557

  
3558
  """
3559
  _OP_REQP = ["kind", "name", "tag"]
3560

  
3561
  def CheckPrereq(self):
3562
    """Check prerequisites.
3563

  
3564
    This checks the type and length of the tag name and value.
3565

  
3566
    """
3567
    TagsLU.CheckPrereq(self)
3568
    objects.TaggableObject.ValidateTag(self.op.tag)
3569

  
3570
  def Exec(self, feedback_fn):
3571
    """Sets the tag.
3572

  
3573
    """
3574
    try:
3575
      self.target.AddTag(self.op.tag)
3576
    except errors.TagError, err:
3577
      raise errors.OpExecError, ("Error while setting tag: %s" % str(err))
3578
    try:
3579
      self.cfg.Update(self.target)
3580
    except errors.ConfigurationError:
3581
      raise errors.OpRetryError, ("There has been a modification to the"
3582
                                  " config file and the operation has been"
3583
                                  " aborted. Please retry.")
3584

  
3585

  
3586
class LUDelTag(TagsLU):
3587
  """Delete a tag from a given object.
3588

  
3589
  """
3590
  _OP_REQP = ["kind", "name", "tag"]
3591

  
3592
  def CheckPrereq(self):
3593
    """Check prerequisites.
3594

  
3595
    This checks that we have the given tag.
3596

  
3597
    """
3598
    TagsLU.CheckPrereq(self)
3599
    objects.TaggableObject.ValidateTag(self.op.tag)
3600
    if self.op.tag not in self.target.GetTags():
3601
      raise errors.OpPrereqError, ("Tag not found")
3602

  
3603
  def Exec(self, feedback_fn):
3604
    """Remove the tag from the object.
3605

  
3606
    """
3607
    self.target.RemoveTag(self.op.tag)
3608
    try:
3609
      self.cfg.Update(self.target)
3610
    except errors.ConfigurationError:
3611
      raise errors.OpRetryError, ("There has been a modification to the"
3612
                                  " config file and the operation has been"
3613
                                  " aborted. Please retry.")
b/lib/constants.py
85 85
# common exit codes
86 86
EXIT_NOTMASTER = 11
87 87

  
88
# tags
89
TAG_CLUSTER = "cluster"
90
TAG_NODE = "node"
91
TAG_INSTANCE = "instance"
92
MAX_TAG_LEN = 128
93
MAX_TAGS_PER_OBJ = 4096
94

  
88 95
# others
89 96
DEFAULT_BRIDGE = "xen-br0"
90 97
SYNC_SPEED = 30 * 1024
b/lib/errors.py
136 136
  """
137 137

  
138 138

  
139
class OpRetryError(OpExecError):
140
  """Error during OpCode execution, action can be retried.
141

  
142
  """
143

  
144

  
139 145
class OpCodeUnknown(GenericError):
140 146
  """Unknown opcode submitted.
141 147

  
......
173 179
class SshKeyError(GenericError):
174 180
  """Invalid SSH key.
175 181
  """
182

  
183

  
184
class TagError(GenericError):
185
  """Generic tag error.
186

  
187
  The argument to this exception will show the exact error.
188

  
189
  """
b/lib/mcpu.py
82 82
    # exports lu
83 83
    opcodes.OpQueryExports: cmdlib.LUQueryExports,
84 84
    opcodes.OpExportInstance: cmdlib.LUExportInstance,
85
    # tags lu
86
    opcodes.OpGetTags: cmdlib.LUGetTags,
87
    opcodes.OpSetTag: cmdlib.LUAddTag,
88
    opcodes.OpDelTag: cmdlib.LUDelTag,
85 89
    }
86 90

  
87 91

  
b/lib/objects.py
30 30
import cPickle
31 31
from cStringIO import StringIO
32 32
import ConfigParser
33
import re
33 34

  
34 35
from ganeti import errors
36
from ganeti import constants
35 37

  
36 38

  
37 39
__all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
......
146 148
    return ConfigObject.Load(StringIO(data))
147 149

  
148 150

  
151
class TaggableObject(object):
152
  """An generic class supporting tags.
153

  
154
  """
155
  @staticmethod
156
  def ValidateTag(tag):
157
    """Check if a tag is valid.
158

  
159
    If the tag is invalid, an errors.TagError will be raised. The
160
    function has no return value.
161

  
162
    """
163
    if not isinstance(tag, basestring):
164
      raise errors.TagError, ("Invalid tag type (not a string)")
165
    if len(tag) > constants.MAX_TAG_LEN:
166
      raise errors.TagError, ("Tag too long (>%d)" %
167
                              constants.MAX_TAG_LEN)
168
    if not tag:
169
      raise errors.TagError, ("Tags cannot be empty")
170
    if not re.match("^[ \w.+*/:-]+$", tag):
171
      raise errors.TagError, ("Tag contains invalid characters")
172

  
173
  def GetTags(self):
174
    """Return the tags list.
175

  
176
    """
177
    tags = getattr(self, "tags", None)
178
    if tags is None:
179
      tags = self.tags = set()
180
    return tags
181

  
182
  def AddTag(self, tag):
183
    """Add a new tag.
184

  
185
    """
186
    self.ValidateTag(tag)
187
    tags = self.GetTags()
188
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
189
      raise errors.TagError, ("Too many tags")
190
    self.GetTags().add(tag)
191

  
192
  def RemoveTag(self, tag):
193
    """Remove a tag.
194

  
195
    """
196
    self.ValidateTag(tag)
197
    tags = self.GetTags()
198
    try:
199
      tags.remove(tag)
200
    except KeyError:
201
      raise errors.TagError, ("Tag not found")
202

  
203

  
149 204
class ConfigData(ConfigObject):
150 205
  """Top-level config object."""
151 206
  __slots__ = ["cluster", "nodes", "instances"]
......
231 286
    return result
232 287

  
233 288

  
234
class Instance(ConfigObject):
289
class Instance(ConfigObject, TaggableObject):
235 290
  """Config object representing an instance."""
236 291
  __slots__ = [
237 292
    "name",
......
243 298
    "nics",
244 299
    "disks",
245 300
    "disk_template",
301
    "tags",
246 302
    ]
247 303

  
248 304
  def _ComputeSecondaryNodes(self):
......
337 393
    ]
338 394

  
339 395

  
340
class Node(ConfigObject):
396
class Node(ConfigObject, TaggableObject):
341 397
  """Config object representing a node."""
342
  __slots__ = ["name", "primary_ip", "secondary_ip"]
398
  __slots__ = ["name", "primary_ip", "secondary_ip", "tags"]
343 399

  
344 400

  
345
class Cluster(ConfigObject):
401
class Cluster(ConfigObject, TaggableObject):
346 402
  """Config object representing the cluster."""
347 403
  __slots__ = [
348 404
    "config_version",
......
353 409
    "mac_prefix",
354 410
    "volume_group_name",
355 411
    "default_bridge",
412
    "tags",
356 413
    ]
357 414

  
415

  
358 416
class SerializableConfigParser(ConfigParser.SafeConfigParser):
359 417
  """Simple wrapper over ConfigParse that allows serialization.
360 418

  
b/lib/opcodes.py
239 239
  """Export an instance."""
240 240
  OP_ID = "OP_BACKUP_EXPORT"
241 241
  __slots__ = ["instance_name", "target_node", "shutdown"]
242

  
243

  
244
# Tags opcodes
245
class OpGetTags(OpCode):
246
  """Returns the tags of the given object."""
247
  OP_ID = "OP_TAGS_GET"
248
  __slots__ = ["kind", "name"]
249

  
250

  
251
class OpSetTag(OpCode):
252
  """Sets the value of a tag on a given object."""
253
  OP_ID = "OP_TAGS_SET"
254
  __slots__ = ["kind", "name", "tag"]
255

  
256

  
257
class OpDelTag(OpCode):
258
  """Remove a tag from a given object."""
259
  OP_ID = "OP_TAGS_DEL"
260
  __slots__ = ["kind", "name", "tag"]

Also available in: Unified diff