Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / network.py @ 44ffd981

History | View | Annotate | Download (19.5 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 query
30
from ganeti import utils
31
from ganeti.cmdlib.base import LogicalUnit, NoHooksLU, QueryBase
32
from ganeti.cmdlib.common import CheckNodeGroupInstances
33

    
34

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

39
  This builds the hook environment from individual variables.
40

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

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

    
73
  return env
74

    
75

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
202

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

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

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

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

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

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

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

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

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

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

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

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

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

    
262

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
396
    self.pool.Validate()
397

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

    
400

    
401
class NetworkQuery(QueryBase):
402
  FIELDS = query.NETWORK_FIELDS
403

    
404
  def ExpandNames(self, lu):
405
    raise NotImplementedError
406

    
407
  def DeclareLocks(self, lu, level):
408
    raise NotImplementedError
409

    
410
  def _GetQueryData(self, lu):
411
    raise NotImplementedError
412

    
413
  @staticmethod
414
  def _GetStats(pool):
415
    raise NotImplementedError
416

    
417

    
418
class LUNetworkQuery(NoHooksLU):
419
  """Logical unit for querying networks.
420

421
  """
422
  REQ_BGL = False
423

    
424
  def CheckArguments(self):
425
    raise NotImplementedError
426

    
427
  def ExpandNames(self):
428
    raise NotImplementedError
429

    
430
  def Exec(self, feedback_fn):
431
    raise NotImplementedError
432

    
433

    
434
def _FmtNetworkConflict(details):
435
  """Utility for L{_NetworkConflictCheck}.
436

437
  """
438
  return utils.CommaJoin("nic%s/%s" % (idx, ipaddr)
439
                         for (idx, ipaddr) in details)
440

    
441

    
442
def _NetworkConflictCheck(lu, check_fn, action, instances):
443
  """Checks for network interface conflicts with a network.
444

445
  @type lu: L{LogicalUnit}
446
  @type check_fn: callable receiving one parameter (L{objects.NIC}) and
447
    returning boolean
448
  @param check_fn: Function checking for conflict
449
  @type action: string
450
  @param action: Part of error message (see code)
451
  @param instances: the instances to check
452
  @type instances: list of instance objects
453
  @raise errors.OpPrereqError: If conflicting IP addresses are found.
454

455
  """
456
  conflicts = []
457

    
458
  for instance in instances:
459
    instconflicts = [(idx, nic.ip)
460
                     for (idx, nic) in enumerate(instance.nics)
461
                     if check_fn(nic)]
462

    
463
    if instconflicts:
464
      conflicts.append((instance.name, instconflicts))
465

    
466
  if conflicts:
467
    lu.LogWarning("IP addresses from network '%s', which is about to %s"
468
                  " node group '%s', are in use: %s" %
469
                  (lu.network_name, action, lu.group.name,
470
                   utils.CommaJoin(("%s: %s" %
471
                                    (name, _FmtNetworkConflict(details)))
472
                                   for (name, details) in conflicts)))
473

    
474
    raise errors.OpPrereqError("Conflicting IP addresses found; "
475
                               " remove/modify the corresponding network"
476
                               " interfaces", errors.ECODE_STATE)
477

    
478

    
479
class LUNetworkConnect(LogicalUnit):
480
  """Connect a network to a nodegroup
481

482
  """
483
  HPATH = "network-connect"
484
  HTYPE = constants.HTYPE_NETWORK
485
  REQ_BGL = False
486

    
487
  def ExpandNames(self):
488
    self.network_name = self.op.network_name
489
    self.group_name = self.op.group_name
490
    self.network_mode = self.op.network_mode
491
    self.network_link = self.op.network_link
492

    
493
    self.network_uuid = self.cfg.LookupNetwork(self.network_name)
494
    self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
495

    
496
    self.needed_locks = {
497
      locking.LEVEL_INSTANCE: [],
498
      locking.LEVEL_NODEGROUP: [self.group_uuid],
499
      }
500
    self.share_locks[locking.LEVEL_INSTANCE] = 1
501

    
502
    if self.op.conflicts_check:
503
      self.needed_locks[locking.LEVEL_NETWORK] = [self.network_uuid]
504
      self.share_locks[locking.LEVEL_NETWORK] = 1
505

    
506
  def DeclareLocks(self, level):
507
    if level == locking.LEVEL_INSTANCE:
508
      assert not self.needed_locks[locking.LEVEL_INSTANCE]
509

    
510
      # Lock instances optimistically, needs verification once group lock has
511
      # been acquired
512
      if self.op.conflicts_check:
513
        self.needed_locks[locking.LEVEL_INSTANCE] = \
514
          self.cfg.GetInstanceNames(
515
            self.cfg.GetNodeGroupInstances(self.group_uuid))
516

    
517
  def BuildHooksEnv(self):
518
    ret = {
519
      "GROUP_NAME": self.group_name,
520
      "GROUP_NETWORK_MODE": self.network_mode,
521
      "GROUP_NETWORK_LINK": self.network_link,
522
      }
523
    return ret
524

    
525
  def BuildHooksNodes(self):
526
    node_uuids = self.cfg.GetNodeGroup(self.group_uuid).members
527
    return (node_uuids, node_uuids)
528

    
529
  def CheckPrereq(self):
530
    owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
531

    
532
    assert self.group_uuid in owned_groups
533

    
534
    # Check if locked instances are still correct
535
    owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
536
    if self.op.conflicts_check:
537
      CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instance_names)
538

    
539
    self.netparams = {
540
      constants.NIC_MODE: self.network_mode,
541
      constants.NIC_LINK: self.network_link,
542
      }
543
    objects.NIC.CheckParameterSyntax(self.netparams)
544

    
545
    self.group = self.cfg.GetNodeGroup(self.group_uuid)
546
    #if self.network_mode == constants.NIC_MODE_BRIDGED:
547
    #  _CheckNodeGroupBridgesExist(self, self.network_link, self.group_uuid)
548
    self.connected = False
549
    if self.network_uuid in self.group.networks:
550
      self.LogWarning("Network '%s' is already mapped to group '%s'" %
551
                      (self.network_name, self.group.name))
552
      self.connected = True
553

    
554
    # check only if not already connected
555
    elif self.op.conflicts_check:
556
      pool = network.AddressPool(self.cfg.GetNetwork(self.network_uuid))
557

    
558
      _NetworkConflictCheck(
559
        self, lambda nic: pool.Contains(nic.ip), "connect to",
560
        [instance_info for (_, instance_info) in
561
         self.cfg.GetMultiInstanceInfoByName(owned_instance_names)])
562

    
563
  def Exec(self, feedback_fn):
564
    # Connect the network and update the group only if not already connected
565
    if not self.connected:
566
      self.group.networks[self.network_uuid] = self.netparams
567
      self.cfg.Update(self.group, feedback_fn)
568

    
569

    
570
class LUNetworkDisconnect(LogicalUnit):
571
  """Disconnect a network to a nodegroup
572

573
  """
574
  HPATH = "network-disconnect"
575
  HTYPE = constants.HTYPE_NETWORK
576
  REQ_BGL = False
577

    
578
  def ExpandNames(self):
579
    self.network_name = self.op.network_name
580
    self.group_name = self.op.group_name
581

    
582
    self.network_uuid = self.cfg.LookupNetwork(self.network_name)
583
    self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
584

    
585
    self.needed_locks = {
586
      locking.LEVEL_INSTANCE: [],
587
      locking.LEVEL_NODEGROUP: [self.group_uuid],
588
      }
589
    self.share_locks[locking.LEVEL_INSTANCE] = 1
590

    
591
  def DeclareLocks(self, level):
592
    if level == locking.LEVEL_INSTANCE:
593
      assert not self.needed_locks[locking.LEVEL_INSTANCE]
594

    
595
      # Lock instances optimistically, needs verification once group lock has
596
      # been acquired
597
      self.needed_locks[locking.LEVEL_INSTANCE] = \
598
        self.cfg.GetInstanceNames(
599
          self.cfg.GetNodeGroupInstances(self.group_uuid))
600

    
601
  def BuildHooksEnv(self):
602
    ret = {
603
      "GROUP_NAME": self.group_name,
604
      }
605
    return ret
606

    
607
  def BuildHooksNodes(self):
608
    nodes = self.cfg.GetNodeGroup(self.group_uuid).members
609
    return (nodes, nodes)
610

    
611
  def CheckPrereq(self):
612
    owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
613

    
614
    assert self.group_uuid in owned_groups
615

    
616
    # Check if locked instances are still correct
617
    owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
618
    CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
619

    
620
    self.group = self.cfg.GetNodeGroup(self.group_uuid)
621
    self.connected = True
622
    if self.network_uuid not in self.group.networks:
623
      self.LogWarning("Network '%s' is not mapped to group '%s'",
624
                      self.network_name, self.group.name)
625
      self.connected = False
626

    
627
    # We need this check only if network is not already connected
628
    else:
629
      _NetworkConflictCheck(
630
        self, lambda nic: nic.network == self.network_uuid, "disconnect from",
631
        [instance_info for (_, instance_info) in
632
         self.cfg.GetMultiInstanceInfoByName(owned_instances)])
633

    
634
  def Exec(self, feedback_fn):
635
    # Disconnect the network and update the group only if network is connected
636
    if self.connected:
637
      del self.group.networks[self.network_uuid]
638
      self.cfg.Update(self.group, feedback_fn)