Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / network.py @ 64981f25

History | View | Annotate | Download (18.9 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
"""Logical units dealing with networks."""
23

    
24
from ganeti import constants
25
from ganeti import errors
26
from ganeti import locking
27
from ganeti import network
28
from ganeti import objects
29
from ganeti import utils
30
from ganeti.cmdlib.base import LogicalUnit
31
from ganeti.cmdlib.common import CheckNodeGroupInstances
32

    
33

    
34
def _BuildNetworkHookEnv(name, subnet, gateway, network6, gateway6,
35
                         mac_prefix, tags):
36
  """Builds network related env variables for hooks
37

38
  This builds the hook environment from individual variables.
39

40
  @type name: string
41
  @param name: the name of the network
42
  @type subnet: string
43
  @param subnet: the ipv4 subnet
44
  @type gateway: string
45
  @param gateway: the ipv4 gateway
46
  @type network6: string
47
  @param network6: the ipv6 subnet
48
  @type gateway6: string
49
  @param gateway6: the ipv6 gateway
50
  @type mac_prefix: string
51
  @param mac_prefix: the mac_prefix
52
  @type tags: list
53
  @param tags: the tags of the network
54

55
  """
56
  env = {}
57
  if name:
58
    env["NETWORK_NAME"] = name
59
  if subnet:
60
    env["NETWORK_SUBNET"] = subnet
61
  if gateway:
62
    env["NETWORK_GATEWAY"] = gateway
63
  if network6:
64
    env["NETWORK_SUBNET6"] = network6
65
  if gateway6:
66
    env["NETWORK_GATEWAY6"] = gateway6
67
  if mac_prefix:
68
    env["NETWORK_MAC_PREFIX"] = mac_prefix
69
  if tags:
70
    env["NETWORK_TAGS"] = " ".join(tags)
71

    
72
  return env
73

    
74

    
75
class LUNetworkAdd(LogicalUnit):
76
  """Logical unit for creating networks.
77

78
  """
79
  HPATH = "network-add"
80
  HTYPE = constants.HTYPE_NETWORK
81
  REQ_BGL = False
82

    
83
  def BuildHooksNodes(self):
84
    """Build hooks nodes.
85

86
    """
87
    mn = self.cfg.GetMasterNode()
88
    return ([mn], [mn])
89

    
90
  def CheckArguments(self):
91
    if self.op.mac_prefix:
92
      self.op.mac_prefix = \
93
        utils.NormalizeAndValidateThreeOctetMacPrefix(self.op.mac_prefix)
94

    
95
  def ExpandNames(self):
96
    self.network_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
97

    
98
    if self.op.conflicts_check:
99
      self.share_locks[locking.LEVEL_NODE] = 1
100
      self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
101
      self.needed_locks = {
102
        locking.LEVEL_NODE: locking.ALL_SET,
103
        locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
104
        }
105
    else:
106
      self.needed_locks = {}
107

    
108
    self.add_locks[locking.LEVEL_NETWORK] = self.network_uuid
109

    
110
  def CheckPrereq(self):
111
    if self.op.network is None:
112
      raise errors.OpPrereqError("Network must be given",
113
                                 errors.ECODE_INVAL)
114

    
115
    try:
116
      existing_uuid = self.cfg.LookupNetwork(self.op.network_name)
117
    except errors.OpPrereqError:
118
      pass
119
    else:
120
      raise errors.OpPrereqError("Desired network name '%s' already exists as a"
121
                                 " network (UUID: %s)" %
122
                                 (self.op.network_name, existing_uuid),
123
                                 errors.ECODE_EXISTS)
124

    
125
    # Check tag validity
126
    for tag in self.op.tags:
127
      objects.TaggableObject.ValidateTag(tag)
128

    
129
  def BuildHooksEnv(self):
130
    """Build hooks env.
131

132
    """
133
    args = {
134
      "name": self.op.network_name,
135
      "subnet": self.op.network,
136
      "gateway": self.op.gateway,
137
      "network6": self.op.network6,
138
      "gateway6": self.op.gateway6,
139
      "mac_prefix": self.op.mac_prefix,
140
      "tags": self.op.tags,
141
      }
142
    return _BuildNetworkHookEnv(**args) # pylint: disable=W0142
143

    
144
  def Exec(self, feedback_fn):
145
    """Add the ip pool to the cluster.
146

147
    """
148
    nobj = objects.Network(name=self.op.network_name,
149
                           network=self.op.network,
150
                           gateway=self.op.gateway,
151
                           network6=self.op.network6,
152
                           gateway6=self.op.gateway6,
153
                           mac_prefix=self.op.mac_prefix,
154
                           uuid=self.network_uuid)
155
    # Initialize the associated address pool
156
    try:
157
      pool = network.AddressPool.InitializeNetwork(nobj)
158
    except errors.AddressPoolError, err:
159
      raise errors.OpExecError("Cannot create IP address pool for network"
160
                               " '%s': %s" % (self.op.network_name, err))
161

    
162
    # Check if we need to reserve the nodes and the cluster master IP
163
    # These may not be allocated to any instances in routed mode, as
164
    # they wouldn't function anyway.
165
    if self.op.conflicts_check:
166
      for node in self.cfg.GetAllNodesInfo().values():
167
        for ip in [node.primary_ip, node.secondary_ip]:
168
          try:
169
            if pool.Contains(ip):
170
              pool.Reserve(ip)
171
              self.LogInfo("Reserved IP address of node '%s' (%s)",
172
                           node.name, ip)
173
          except errors.AddressPoolError, err:
174
            self.LogWarning("Cannot reserve IP address '%s' of node '%s': %s",
175
                            ip, node.name, err)
176

    
177
      master_ip = self.cfg.GetClusterInfo().master_ip
178
      try:
179
        if pool.Contains(master_ip):
180
          pool.Reserve(master_ip)
181
          self.LogInfo("Reserved cluster master IP address (%s)", master_ip)
182
      except errors.AddressPoolError, err:
183
        self.LogWarning("Cannot reserve cluster master IP address (%s): %s",
184
                        master_ip, err)
185

    
186
    if self.op.add_reserved_ips:
187
      for ip in self.op.add_reserved_ips:
188
        try:
189
          pool.Reserve(ip, external=True)
190
        except errors.AddressPoolError, err:
191
          raise errors.OpExecError("Cannot reserve IP address '%s': %s" %
192
                                   (ip, err))
193

    
194
    if self.op.tags:
195
      for tag in self.op.tags:
196
        nobj.AddTag(tag)
197

    
198
    self.cfg.AddNetwork(nobj, self.proc.GetECId(), check_uuid=False)
199
    del self.remove_locks[locking.LEVEL_NETWORK]
200

    
201

    
202
class LUNetworkRemove(LogicalUnit):
203
  HPATH = "network-remove"
204
  HTYPE = constants.HTYPE_NETWORK
205
  REQ_BGL = False
206

    
207
  def ExpandNames(self):
208
    self.network_uuid = self.cfg.LookupNetwork(self.op.network_name)
209

    
210
    self.share_locks[locking.LEVEL_NODEGROUP] = 1
211
    self.needed_locks = {
212
      locking.LEVEL_NETWORK: [self.network_uuid],
213
      locking.LEVEL_NODEGROUP: locking.ALL_SET,
214
      }
215

    
216
  def CheckPrereq(self):
217
    """Check prerequisites.
218

219
    This checks that the given network name exists as a network, that is
220
    empty (i.e., contains no nodes), and that is not the last group of the
221
    cluster.
222

223
    """
224
    # Verify that the network is not conncted.
225
    node_groups = [group.name
226
                   for group in self.cfg.GetAllNodeGroupsInfo().values()
227
                   if self.network_uuid in group.networks]
228

    
229
    if node_groups:
230
      self.LogWarning("Network '%s' is connected to the following"
231
                      " node groups: %s" %
232
                      (self.op.network_name,
233
                       utils.CommaJoin(utils.NiceSort(node_groups))))
234
      raise errors.OpPrereqError("Network still connected", errors.ECODE_STATE)
235

    
236
  def BuildHooksEnv(self):
237
    """Build hooks env.
238

239
    """
240
    return {
241
      "NETWORK_NAME": self.op.network_name,
242
      }
243

    
244
  def BuildHooksNodes(self):
245
    """Build hooks nodes.
246

247
    """
248
    mn = self.cfg.GetMasterNode()
249
    return ([mn], [mn])
250

    
251
  def Exec(self, feedback_fn):
252
    """Remove the network.
253

254
    """
255
    try:
256
      self.cfg.RemoveNetwork(self.network_uuid)
257
    except errors.ConfigurationError:
258
      raise errors.OpExecError("Network '%s' with UUID %s disappeared" %
259
                               (self.op.network_name, self.network_uuid))
260

    
261

    
262
class LUNetworkSetParams(LogicalUnit):
263
  """Modifies the parameters of a network.
264

265
  """
266
  HPATH = "network-modify"
267
  HTYPE = constants.HTYPE_NETWORK
268
  REQ_BGL = False
269

    
270
  def CheckArguments(self):
271
    if (self.op.gateway and
272
        (self.op.add_reserved_ips or self.op.remove_reserved_ips)):
273
      raise errors.OpPrereqError("Cannot modify gateway and reserved ips"
274
                                 " at once", errors.ECODE_INVAL)
275

    
276
  def ExpandNames(self):
277
    self.network_uuid = self.cfg.LookupNetwork(self.op.network_name)
278

    
279
    self.needed_locks = {
280
      locking.LEVEL_NETWORK: [self.network_uuid],
281
      }
282

    
283
  def CheckPrereq(self):
284
    """Check prerequisites.
285

286
    """
287
    self.network = self.cfg.GetNetwork(self.network_uuid)
288
    self.gateway = self.network.gateway
289
    self.mac_prefix = self.network.mac_prefix
290
    self.network6 = self.network.network6
291
    self.gateway6 = self.network.gateway6
292
    self.tags = self.network.tags
293

    
294
    self.pool = network.AddressPool(self.network)
295

    
296
    if self.op.gateway:
297
      if self.op.gateway == constants.VALUE_NONE:
298
        self.gateway = None
299
      else:
300
        self.gateway = self.op.gateway
301
        if self.pool.IsReserved(self.gateway):
302
          raise errors.OpPrereqError("Gateway IP address '%s' is already"
303
                                     " reserved" % self.gateway,
304
                                     errors.ECODE_STATE)
305

    
306
    if self.op.mac_prefix:
307
      if self.op.mac_prefix == constants.VALUE_NONE:
308
        self.mac_prefix = None
309
      else:
310
        self.mac_prefix = \
311
          utils.NormalizeAndValidateThreeOctetMacPrefix(self.op.mac_prefix)
312

    
313
    if self.op.gateway6:
314
      if self.op.gateway6 == constants.VALUE_NONE:
315
        self.gateway6 = None
316
      else:
317
        self.gateway6 = self.op.gateway6
318

    
319
    if self.op.network6:
320
      if self.op.network6 == constants.VALUE_NONE:
321
        self.network6 = None
322
      else:
323
        self.network6 = self.op.network6
324

    
325
  def BuildHooksEnv(self):
326
    """Build hooks env.
327

328
    """
329
    args = {
330
      "name": self.op.network_name,
331
      "subnet": self.network.network,
332
      "gateway": self.gateway,
333
      "network6": self.network6,
334
      "gateway6": self.gateway6,
335
      "mac_prefix": self.mac_prefix,
336
      "tags": self.tags,
337
      }
338
    return _BuildNetworkHookEnv(**args) # pylint: disable=W0142
339

    
340
  def BuildHooksNodes(self):
341
    """Build hooks nodes.
342

343
    """
344
    mn = self.cfg.GetMasterNode()
345
    return ([mn], [mn])
346

    
347
  def Exec(self, feedback_fn):
348
    """Modifies the network.
349

350
    """
351
    #TODO: reserve/release via temporary reservation manager
352
    #      extend cfg.ReserveIp/ReleaseIp with the external flag
353
    if self.op.gateway:
354
      if self.gateway == self.network.gateway:
355
        self.LogWarning("Gateway is already %s", self.gateway)
356
      else:
357
        if self.gateway:
358
          self.pool.Reserve(self.gateway, external=True)
359
        if self.network.gateway:
360
          self.pool.Release(self.network.gateway, external=True)
361
        self.network.gateway = self.gateway
362

    
363
    if self.op.add_reserved_ips:
364
      for ip in self.op.add_reserved_ips:
365
        try:
366
          if self.pool.IsReserved(ip):
367
            self.LogWarning("IP address %s is already reserved", ip)
368
          else:
369
            self.pool.Reserve(ip, external=True)
370
        except errors.AddressPoolError, err:
371
          self.LogWarning("Cannot reserve IP address %s: %s", ip, err)
372

    
373
    if self.op.remove_reserved_ips:
374
      for ip in self.op.remove_reserved_ips:
375
        if ip == self.network.gateway:
376
          self.LogWarning("Cannot unreserve Gateway's IP")
377
          continue
378
        try:
379
          if not self.pool.IsReserved(ip):
380
            self.LogWarning("IP address %s is already unreserved", ip)
381
          else:
382
            self.pool.Release(ip, external=True)
383
        except errors.AddressPoolError, err:
384
          self.LogWarning("Cannot release IP address %s: %s", ip, err)
385

    
386
    if self.op.mac_prefix:
387
      self.network.mac_prefix = self.mac_prefix
388

    
389
    if self.op.network6:
390
      self.network.network6 = self.network6
391

    
392
    if self.op.gateway6:
393
      self.network.gateway6 = self.gateway6
394

    
395
    self.pool.Validate()
396

    
397
    self.cfg.Update(self.network, feedback_fn)
398

    
399

    
400
def _FmtNetworkConflict(details):
401
  """Utility for L{_NetworkConflictCheck}.
402

403
  """
404
  return utils.CommaJoin("nic%s/%s" % (idx, ipaddr)
405
                         for (idx, ipaddr) in details)
406

    
407

    
408
def _NetworkConflictCheck(lu, check_fn, action, instances):
409
  """Checks for network interface conflicts with a network.
410

411
  @type lu: L{LogicalUnit}
412
  @type check_fn: callable receiving one parameter (L{objects.NIC}) and
413
    returning boolean
414
  @param check_fn: Function checking for conflict
415
  @type action: string
416
  @param action: Part of error message (see code)
417
  @param instances: the instances to check
418
  @type instances: list of instance objects
419
  @raise errors.OpPrereqError: If conflicting IP addresses are found.
420

421
  """
422
  conflicts = []
423

    
424
  for instance in instances:
425
    instconflicts = [(idx, nic.ip)
426
                     for (idx, nic) in enumerate(instance.nics)
427
                     if check_fn(nic)]
428

    
429
    if instconflicts:
430
      conflicts.append((instance.name, instconflicts))
431

    
432
  if conflicts:
433
    lu.LogWarning("IP addresses from network '%s', which is about to %s"
434
                  " node group '%s', are in use: %s" %
435
                  (lu.network_name, action, lu.group.name,
436
                   utils.CommaJoin(("%s: %s" %
437
                                    (name, _FmtNetworkConflict(details)))
438
                                   for (name, details) in conflicts)))
439

    
440
    raise errors.OpPrereqError("Conflicting IP addresses found; "
441
                               " remove/modify the corresponding network"
442
                               " interfaces", errors.ECODE_STATE)
443

    
444

    
445
class LUNetworkConnect(LogicalUnit):
446
  """Connect a network to a nodegroup
447

448
  """
449
  HPATH = "network-connect"
450
  HTYPE = constants.HTYPE_NETWORK
451
  REQ_BGL = False
452

    
453
  def ExpandNames(self):
454
    self.network_name = self.op.network_name
455
    self.group_name = self.op.group_name
456
    self.network_mode = self.op.network_mode
457
    self.network_link = self.op.network_link
458

    
459
    self.network_uuid = self.cfg.LookupNetwork(self.network_name)
460
    self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
461

    
462
    self.needed_locks = {
463
      locking.LEVEL_INSTANCE: [],
464
      locking.LEVEL_NODEGROUP: [self.group_uuid],
465
      }
466
    self.share_locks[locking.LEVEL_INSTANCE] = 1
467

    
468
    if self.op.conflicts_check:
469
      self.needed_locks[locking.LEVEL_NETWORK] = [self.network_uuid]
470
      self.share_locks[locking.LEVEL_NETWORK] = 1
471

    
472
  def DeclareLocks(self, level):
473
    if level == locking.LEVEL_INSTANCE:
474
      assert not self.needed_locks[locking.LEVEL_INSTANCE]
475

    
476
      # Lock instances optimistically, needs verification once group lock has
477
      # been acquired
478
      if self.op.conflicts_check:
479
        self.needed_locks[locking.LEVEL_INSTANCE] = \
480
          self.cfg.GetInstanceNames(
481
            self.cfg.GetNodeGroupInstances(self.group_uuid))
482

    
483
  def BuildHooksEnv(self):
484
    ret = {
485
      "GROUP_NAME": self.group_name,
486
      "GROUP_NETWORK_MODE": self.network_mode,
487
      "GROUP_NETWORK_LINK": self.network_link,
488
      }
489
    return ret
490

    
491
  def BuildHooksNodes(self):
492
    node_uuids = self.cfg.GetNodeGroup(self.group_uuid).members
493
    return (node_uuids, node_uuids)
494

    
495
  def CheckPrereq(self):
496
    owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
497

    
498
    assert self.group_uuid in owned_groups
499

    
500
    # Check if locked instances are still correct
501
    owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
502
    if self.op.conflicts_check:
503
      CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instance_names)
504

    
505
    self.netparams = {
506
      constants.NIC_MODE: self.network_mode,
507
      constants.NIC_LINK: self.network_link,
508
      }
509
    objects.NIC.CheckParameterSyntax(self.netparams)
510

    
511
    self.group = self.cfg.GetNodeGroup(self.group_uuid)
512
    #if self.network_mode == constants.NIC_MODE_BRIDGED:
513
    #  _CheckNodeGroupBridgesExist(self, self.network_link, self.group_uuid)
514
    self.connected = False
515
    if self.network_uuid in self.group.networks:
516
      self.LogWarning("Network '%s' is already mapped to group '%s'" %
517
                      (self.network_name, self.group.name))
518
      self.connected = True
519

    
520
    # check only if not already connected
521
    elif self.op.conflicts_check:
522
      pool = network.AddressPool(self.cfg.GetNetwork(self.network_uuid))
523

    
524
      _NetworkConflictCheck(
525
        self, lambda nic: pool.Contains(nic.ip), "connect to",
526
        [instance_info for (_, instance_info) in
527
         self.cfg.GetMultiInstanceInfoByName(owned_instance_names)])
528

    
529
  def Exec(self, feedback_fn):
530
    # Connect the network and update the group only if not already connected
531
    if not self.connected:
532
      self.group.networks[self.network_uuid] = self.netparams
533
      self.cfg.Update(self.group, feedback_fn)
534

    
535

    
536
class LUNetworkDisconnect(LogicalUnit):
537
  """Disconnect a network to a nodegroup
538

539
  """
540
  HPATH = "network-disconnect"
541
  HTYPE = constants.HTYPE_NETWORK
542
  REQ_BGL = False
543

    
544
  def ExpandNames(self):
545
    self.network_name = self.op.network_name
546
    self.group_name = self.op.group_name
547

    
548
    self.network_uuid = self.cfg.LookupNetwork(self.network_name)
549
    self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
550

    
551
    self.needed_locks = {
552
      locking.LEVEL_INSTANCE: [],
553
      locking.LEVEL_NODEGROUP: [self.group_uuid],
554
      }
555
    self.share_locks[locking.LEVEL_INSTANCE] = 1
556

    
557
  def DeclareLocks(self, level):
558
    if level == locking.LEVEL_INSTANCE:
559
      assert not self.needed_locks[locking.LEVEL_INSTANCE]
560

    
561
      # Lock instances optimistically, needs verification once group lock has
562
      # been acquired
563
      self.needed_locks[locking.LEVEL_INSTANCE] = \
564
        self.cfg.GetInstanceNames(
565
          self.cfg.GetNodeGroupInstances(self.group_uuid))
566

    
567
  def BuildHooksEnv(self):
568
    ret = {
569
      "GROUP_NAME": self.group_name,
570
      }
571
    return ret
572

    
573
  def BuildHooksNodes(self):
574
    nodes = self.cfg.GetNodeGroup(self.group_uuid).members
575
    return (nodes, nodes)
576

    
577
  def CheckPrereq(self):
578
    owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
579

    
580
    assert self.group_uuid in owned_groups
581

    
582
    # Check if locked instances are still correct
583
    owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
584
    CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
585

    
586
    self.group = self.cfg.GetNodeGroup(self.group_uuid)
587
    self.connected = True
588
    if self.network_uuid not in self.group.networks:
589
      self.LogWarning("Network '%s' is not mapped to group '%s'",
590
                      self.network_name, self.group.name)
591
      self.connected = False
592

    
593
    # We need this check only if network is not already connected
594
    else:
595
      _NetworkConflictCheck(
596
        self, lambda nic: nic.network == self.network_uuid, "disconnect from",
597
        [instance_info for (_, instance_info) in
598
         self.cfg.GetMultiInstanceInfoByName(owned_instances)])
599

    
600
  def Exec(self, feedback_fn):
601
    # Disconnect the network and update the group only if network is connected
602
    if self.connected:
603
      del self.group.networks[self.network_uuid]
604
      self.cfg.Update(self.group, feedback_fn)