Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / network.py @ 1c4910f7

History | View | Annotate | Download (18.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
"""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, external=True)
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, external=True)
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
          self.pool.Reserve(ip, external=True)
367
        except errors.AddressPoolError, err:
368
          self.LogWarning("Cannot reserve IP address %s: %s", ip, err)
369

    
370
    if self.op.remove_reserved_ips:
371
      for ip in self.op.remove_reserved_ips:
372
        if ip == self.network.gateway:
373
          self.LogWarning("Cannot unreserve Gateway's IP")
374
          continue
375
        try:
376
          self.pool.Release(ip, external=True)
377
        except errors.AddressPoolError, err:
378
          self.LogWarning("Cannot release IP address %s: %s", ip, err)
379

    
380
    if self.op.mac_prefix:
381
      self.network.mac_prefix = self.mac_prefix
382

    
383
    if self.op.network6:
384
      self.network.network6 = self.network6
385

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

    
389
    self.pool.Validate()
390

    
391
    self.cfg.Update(self.network, feedback_fn)
392

    
393

    
394
def _FmtNetworkConflict(details):
395
  """Utility for L{_NetworkConflictCheck}.
396

397
  """
398
  return utils.CommaJoin("nic%s/%s" % (idx, ipaddr)
399
                         for (idx, ipaddr) in details)
400

    
401

    
402
def _NetworkConflictCheck(lu, check_fn, action, instances):
403
  """Checks for network interface conflicts with a network.
404

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

415
  """
416
  conflicts = []
417

    
418
  for instance in instances:
419
    instconflicts = [(idx, nic.ip)
420
                     for (idx, nic) in enumerate(instance.nics)
421
                     if check_fn(nic)]
422

    
423
    if instconflicts:
424
      conflicts.append((instance.name, instconflicts))
425

    
426
  if conflicts:
427
    lu.LogWarning("IP addresses from network '%s', which is about to %s"
428
                  " node group '%s', are in use: %s" %
429
                  (lu.network_name, action, lu.group.name,
430
                   utils.CommaJoin(("%s: %s" %
431
                                    (name, _FmtNetworkConflict(details)))
432
                                   for (name, details) in conflicts)))
433

    
434
    raise errors.OpPrereqError("Conflicting IP addresses found; "
435
                               " remove/modify the corresponding network"
436
                               " interfaces", errors.ECODE_STATE)
437

    
438

    
439
class LUNetworkConnect(LogicalUnit):
440
  """Connect a network to a nodegroup
441

442
  """
443
  HPATH = "network-connect"
444
  HTYPE = constants.HTYPE_NETWORK
445
  REQ_BGL = False
446

    
447
  def ExpandNames(self):
448
    self.network_name = self.op.network_name
449
    self.group_name = self.op.group_name
450
    self.network_mode = self.op.network_mode
451
    self.network_link = self.op.network_link
452

    
453
    self.network_uuid = self.cfg.LookupNetwork(self.network_name)
454
    self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
455

    
456
    self.needed_locks = {
457
      locking.LEVEL_INSTANCE: [],
458
      locking.LEVEL_NODEGROUP: [self.group_uuid],
459
      }
460
    self.share_locks[locking.LEVEL_INSTANCE] = 1
461

    
462
    if self.op.conflicts_check:
463
      self.needed_locks[locking.LEVEL_NETWORK] = [self.network_uuid]
464
      self.share_locks[locking.LEVEL_NETWORK] = 1
465

    
466
  def DeclareLocks(self, level):
467
    if level == locking.LEVEL_INSTANCE:
468
      assert not self.needed_locks[locking.LEVEL_INSTANCE]
469

    
470
      # Lock instances optimistically, needs verification once group lock has
471
      # been acquired
472
      if self.op.conflicts_check:
473
        self.needed_locks[locking.LEVEL_INSTANCE] = \
474
          self.cfg.GetInstanceNames(
475
            self.cfg.GetNodeGroupInstances(self.group_uuid))
476

    
477
  def BuildHooksEnv(self):
478
    ret = {
479
      "GROUP_NAME": self.group_name,
480
      "GROUP_NETWORK_MODE": self.network_mode,
481
      "GROUP_NETWORK_LINK": self.network_link,
482
      }
483
    return ret
484

    
485
  def BuildHooksNodes(self):
486
    node_uuids = self.cfg.GetNodeGroup(self.group_uuid).members
487
    return (node_uuids, node_uuids)
488

    
489
  def CheckPrereq(self):
490
    owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
491

    
492
    assert self.group_uuid in owned_groups
493

    
494
    # Check if locked instances are still correct
495
    owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
496
    if self.op.conflicts_check:
497
      CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instance_names)
498

    
499
    self.netparams = {
500
      constants.NIC_MODE: self.network_mode,
501
      constants.NIC_LINK: self.network_link,
502
      }
503
    objects.NIC.CheckParameterSyntax(self.netparams)
504

    
505
    self.group = self.cfg.GetNodeGroup(self.group_uuid)
506
    #if self.network_mode == constants.NIC_MODE_BRIDGED:
507
    #  _CheckNodeGroupBridgesExist(self, self.network_link, self.group_uuid)
508
    self.connected = False
509
    if self.network_uuid in self.group.networks:
510
      self.LogWarning("Network '%s' is already mapped to group '%s'" %
511
                      (self.network_name, self.group.name))
512
      self.connected = True
513

    
514
    # check only if not already connected
515
    elif self.op.conflicts_check:
516
      pool = network.AddressPool(self.cfg.GetNetwork(self.network_uuid))
517

    
518
      _NetworkConflictCheck(
519
        self, lambda nic: pool.Contains(nic.ip), "connect to",
520
        [instance_info for (_, instance_info) in
521
         self.cfg.GetMultiInstanceInfoByName(owned_instance_names)])
522

    
523
  def Exec(self, feedback_fn):
524
    # Connect the network and update the group only if not already connected
525
    if not self.connected:
526
      self.group.networks[self.network_uuid] = self.netparams
527
      self.cfg.Update(self.group, feedback_fn)
528

    
529

    
530
class LUNetworkDisconnect(LogicalUnit):
531
  """Disconnect a network to a nodegroup
532

533
  """
534
  HPATH = "network-disconnect"
535
  HTYPE = constants.HTYPE_NETWORK
536
  REQ_BGL = False
537

    
538
  def ExpandNames(self):
539
    self.network_name = self.op.network_name
540
    self.group_name = self.op.group_name
541

    
542
    self.network_uuid = self.cfg.LookupNetwork(self.network_name)
543
    self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
544

    
545
    self.needed_locks = {
546
      locking.LEVEL_INSTANCE: [],
547
      locking.LEVEL_NODEGROUP: [self.group_uuid],
548
      }
549
    self.share_locks[locking.LEVEL_INSTANCE] = 1
550

    
551
  def DeclareLocks(self, level):
552
    if level == locking.LEVEL_INSTANCE:
553
      assert not self.needed_locks[locking.LEVEL_INSTANCE]
554

    
555
      # Lock instances optimistically, needs verification once group lock has
556
      # been acquired
557
      self.needed_locks[locking.LEVEL_INSTANCE] = \
558
        self.cfg.GetInstanceNames(
559
          self.cfg.GetNodeGroupInstances(self.group_uuid))
560

    
561
  def BuildHooksEnv(self):
562
    ret = {
563
      "GROUP_NAME": self.group_name,
564
      }
565
    return ret
566

    
567
  def BuildHooksNodes(self):
568
    nodes = self.cfg.GetNodeGroup(self.group_uuid).members
569
    return (nodes, nodes)
570

    
571
  def CheckPrereq(self):
572
    owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
573

    
574
    assert self.group_uuid in owned_groups
575

    
576
    # Check if locked instances are still correct
577
    owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
578
    CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
579

    
580
    self.group = self.cfg.GetNodeGroup(self.group_uuid)
581
    self.connected = True
582
    if self.network_uuid not in self.group.networks:
583
      self.LogWarning("Network '%s' is not mapped to group '%s'",
584
                      self.network_name, self.group.name)
585
      self.connected = False
586

    
587
    # We need this check only if network is not already connected
588
    else:
589
      _NetworkConflictCheck(
590
        self, lambda nic: nic.network == self.network_uuid, "disconnect from",
591
        [instance_info for (_, instance_info) in
592
         self.cfg.GetMultiInstanceInfoByName(owned_instances)])
593

    
594
  def Exec(self, feedback_fn):
595
    # Disconnect the network and update the group only if network is connected
596
    if self.connected:
597
      del self.group.networks[self.network_uuid]
598
      self.cfg.Update(self.group, feedback_fn)