~ubuntu-branches/ubuntu/quantal/nova/quantal-proposed

« back to all changes in this revision

Viewing changes to nova/network/quantum/manager.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2012-08-16 14:04:11 UTC
  • mto: This revision was merged to the branch mainline in revision 84.
  • Revision ID: package-import@ubuntu.com-20120816140411-0mr4n241wmk30t9l
Tags: upstream-2012.2~f3
ImportĀ upstreamĀ versionĀ 2012.2~f3

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
 
 
3
 
# Copyright 2011 Nicira Networks, Inc
4
 
# All Rights Reserved.
5
 
#
6
 
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
7
 
#    not use this file except in compliance with the License. You may obtain
8
 
#    a copy of the License at
9
 
#
10
 
#         http://www.apache.org/licenses/LICENSE-2.0
11
 
#
12
 
#    Unless required by applicable law or agreed to in writing, software
13
 
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
 
#    License for the specific language governing permissions and limitations
16
 
#    under the License.
17
 
 
18
 
import time
19
 
 
20
 
import netaddr
21
 
 
22
 
from nova import context
23
 
from nova import db
24
 
from nova import exception
25
 
from nova import flags
26
 
from nova.network import manager
27
 
from nova.network.quantum import melange_ipam_lib
28
 
from nova.network.quantum import quantum_connection
29
 
from nova.openstack.common import cfg
30
 
from nova.openstack.common import log as logging
31
 
from nova.openstack.common import rpc
32
 
from nova import utils
33
 
 
34
 
LOG = logging.getLogger(__name__)
35
 
 
36
 
quantum_opts = [
37
 
    cfg.StrOpt('quantum_ipam_lib',
38
 
               default='nova.network.quantum.nova_ipam_lib',
39
 
               help="Indicates underlying IP address management library"),
40
 
    # TODO(Vek): Eventually, this needs to mean more than just using
41
 
    #            Melange for assignment of MAC addresses (with an
42
 
    #            appropriate flag name change, of course), but this is all
43
 
    #            it does right now
44
 
    cfg.BoolOpt('use_melange_mac_generation',
45
 
                default=False,
46
 
                help="Use Melange for assignment of MAC addresses"),
47
 
    cfg.BoolOpt('quantum_use_dhcp',
48
 
                default=False,
49
 
                help='Whether or not to enable DHCP for networks'),
50
 
    cfg.BoolOpt('quantum_use_port_security',
51
 
                default=False,
52
 
                help='Whether or not to enable port security'),
53
 
    cfg.BoolOpt('quantum_port_security_include_link_local',
54
 
                default=False,
55
 
                help='Add the link local address to the port security list'),
56
 
    ]
57
 
 
58
 
FLAGS = flags.FLAGS
59
 
FLAGS.register_opts(quantum_opts)
60
 
 
61
 
 
62
 
class QuantumManager(manager.FloatingIP, manager.FlatManager):
63
 
    """NetworkManager class that communicates with a Quantum service
64
 
       via a web services API to provision VM network connectivity.
65
 
 
66
 
       For IP Address management, QuantumManager can be configured to
67
 
       use either Nova's local DB or the Melange IPAM service.
68
 
    """
69
 
 
70
 
    DHCP = FLAGS.quantum_use_dhcp
71
 
 
72
 
    def __init__(self, q_conn=None, ipam_lib=None, *args, **kwargs):
73
 
        """Initialize two key libraries, the connection to a
74
 
           Quantum service, and the library for implementing IPAM.
75
 
 
76
 
           Calls inherited FlatManager constructor.
77
 
        """
78
 
 
79
 
        if not q_conn:
80
 
            q_conn = quantum_connection.QuantumClientConnection()
81
 
        self.q_conn = q_conn
82
 
 
83
 
        if not ipam_lib:
84
 
            ipam_lib = FLAGS.quantum_ipam_lib
85
 
        self._import_ipam_lib(ipam_lib)
86
 
 
87
 
        super(QuantumManager, self).__init__(*args, **kwargs)
88
 
 
89
 
    def init_host(self):
90
 
        # Initialize general L3 networking
91
 
        self.l3driver.initialize()
92
 
        super(QuantumManager, self).init_host()
93
 
        # Initialize floating ip support (only works for nova ipam currently)
94
 
        if FLAGS.quantum_ipam_lib == 'nova.network.quantum.nova_ipam_lib':
95
 
            LOG.debug("Initializing FloatingIP support")
96
 
            self.init_host_floating_ips()
97
 
        # Set up all the forwarding rules for any network that has a
98
 
        # gateway set.
99
 
        networks = self.get_all_networks(context.get_admin_context())
100
 
        cidrs = []
101
 
        for net in networks:
102
 
            # Don't update host information for network that does not
103
 
            # belong to you
104
 
            if net['host'] != self.host:
105
 
                continue
106
 
            if net['gateway']:
107
 
                LOG.debug("Initializing NAT: %s (cidr: %s, gw: %s)" % (
108
 
                    net['label'], net['cidr'], net['gateway']))
109
 
                cidrs.append(net['cidr'])
110
 
            self._update_network_host(context.get_admin_context(),
111
 
                                      net['uuid'])
112
 
        # .. and for each network
113
 
        for c in cidrs:
114
 
            self.l3driver.initialize_network(c)
115
 
 
116
 
    # Similar to FlatDHCPMananger, except we check for quantum_use_dhcp flag
117
 
    # before we try to update_dhcp
118
 
    def _setup_network_on_host(self, context, network):
119
 
        """Sets up network on this host."""
120
 
        network['dhcp_server'] = self._get_dhcp_ip(context, network)
121
 
        self.l3driver.initialize_gateway(network)
122
 
 
123
 
        if FLAGS.quantum_use_dhcp and not FLAGS.fake_network:
124
 
            dev = self.driver.get_dev(network)
125
 
            self.driver.update_dhcp(context, dev, network)
126
 
            if FLAGS.use_ipv6:
127
 
                self.driver.update_ra(context, dev, network)
128
 
                gateway = utils.get_my_linklocal(dev)
129
 
                self.db.network_update(context, network['id'],
130
 
                                       {'gateway_v6': gateway})
131
 
 
132
 
    def _update_network_host(self, context, net_uuid):
133
 
        """Set the host column in the networks table: note that this won't
134
 
           work with multi-host but QuantumManager doesn't support that
135
 
           anyways.  The floating IPs mixin required network['host'] to be
136
 
           set."""
137
 
        entry = db.network_get_by_uuid(context.elevated(), net_uuid)
138
 
        entry['host'] = self.host
139
 
        db.network_update(context.elevated(), entry['id'], entry)
140
 
 
141
 
    def _get_nova_id(self, instance=None):
142
 
        # When creating the network we need to pass in an identifier for
143
 
        # this zone.  Some Quantum plugins need this information in order
144
 
        # to set up appropriate networking.
145
 
        if instance and instance['availability_zone']:
146
 
            return instance['availability_zone']
147
 
        else:
148
 
            return FLAGS.node_availability_zone
149
 
 
150
 
    def get_all_networks(self, context):
151
 
        networks = []
152
 
        networks.extend(self.ipam.get_global_networks(context))
153
 
        networks.extend(self.ipam.get_project_networks(context))
154
 
        return networks
155
 
 
156
 
    def create_networks(self, context, label, cidr, multi_host, num_networks,
157
 
                        network_size, cidr_v6, gateway, gateway_v6, bridge,
158
 
                        bridge_interface, dns1=None, dns2=None, uuid=None,
159
 
                        **kwargs):
160
 
        """Unlike other NetworkManagers, with QuantumManager, each
161
 
           create_networks calls should create only a single network.
162
 
 
163
 
           Two scenarios exist:
164
 
                - no 'uuid' is specified, in which case we contact
165
 
                  Quantum and create a new network.
166
 
                - an existing 'uuid' is specified, corresponding to
167
 
                  a Quantum network created out of band.
168
 
 
169
 
           In both cases, we initialize a subnet using the IPAM lib.
170
 
        """
171
 
        # Enforce Configuration sanity.
172
 
        #
173
 
        # These flags are passed in from bin/nova-manage. The script
174
 
        # collects the arguments and then passes them in through this
175
 
        # function call. Note that in some cases, the script pre-processes
176
 
        # the arguments, and sets them to a default value if a parameter's
177
 
        # value was not specified on the command line. For pre-processed
178
 
        # parameters, the most effective check to see if the user passed it
179
 
        # in is to see if is different from the default value. (This
180
 
        # does miss the use case where the user passes in the default value
181
 
        # on the command line -- but it is unavoidable.)
182
 
        if multi_host != FLAGS.multi_host:
183
 
            # User specified it on the command line.
184
 
            raise Exception(_("QuantumManager does not use 'multi_host'"
185
 
                              " parameter."))
186
 
 
187
 
        if num_networks != 1:
188
 
            raise Exception(_("QuantumManager requires that only one"
189
 
                              " network is created per call"))
190
 
 
191
 
        if network_size != int(FLAGS.network_size):
192
 
            # User specified it on the command line.
193
 
            LOG.warning("Ignoring unnecessary parameter 'network_size'")
194
 
 
195
 
        if kwargs.get('vlan_start', None):
196
 
            if kwargs['vlan_start'] != int(FLAGS.vlan_start):
197
 
                # User specified it on the command line.
198
 
                LOG.warning(_("QuantumManager does not use 'vlan_start'"
199
 
                              " parameter."))
200
 
 
201
 
        if kwargs.get('vpn_start', None):
202
 
            if kwargs['vpn_start'] != int(FLAGS.vpn_start):
203
 
                # User specified it on the command line.
204
 
                LOG.warning(_("QuantumManager does not use 'vpn_start'"
205
 
                              " parameter."))
206
 
 
207
 
        if bridge is not None and len(bridge) > 0:
208
 
            LOG.warning(_("QuantumManager does not use 'bridge'"
209
 
                          " parameter."))
210
 
 
211
 
        if bridge_interface is not None and len(bridge_interface) > 0:
212
 
            LOG.warning(_("QuantumManager does not use 'bridge_interface'"
213
 
                          " parameter."))
214
 
 
215
 
        if gateway is not None and len(gateway) > 0:
216
 
            if gateway.split('.')[3] != '1':
217
 
                raise Exception(_("QuantumManager requires a valid (.1)"
218
 
                              " gateway address."))
219
 
 
220
 
        q_tenant_id = kwargs["project_id"] or FLAGS.quantum_default_tenant_id
221
 
        quantum_net_id = uuid
222
 
        # If a uuid was specified with the network it should have already been
223
 
        # created in Quantum, so make sure.
224
 
        if quantum_net_id:
225
 
            if not self.q_conn.network_exists(q_tenant_id, quantum_net_id):
226
 
                    raise Exception(_("Unable to find existing quantum "
227
 
                                      "network for tenant '%(q_tenant_id)s' "
228
 
                                      "with net-id '%(quantum_net_id)s'") %
229
 
                                    locals())
230
 
        else:
231
 
            nova_id = self._get_nova_id()
232
 
            quantum_net_id = self.q_conn.create_network(q_tenant_id, label,
233
 
                                                        nova_id=nova_id)
234
 
 
235
 
        ipam_tenant_id = kwargs.get("project_id", None)
236
 
        priority = kwargs.get("priority", 0)
237
 
        # NOTE(tr3buchet): this call creates a nova network in the nova db
238
 
        self.ipam.create_subnet(context, label, ipam_tenant_id,
239
 
                                quantum_net_id, priority, cidr,
240
 
                                gateway, gateway_v6, cidr_v6, dns1, dns2)
241
 
 
242
 
        self._update_network_host(context, quantum_net_id)
243
 
 
244
 
        # Initialize forwarding
245
 
        self.l3driver.initialize_network(cidr)
246
 
 
247
 
        return [{'uuid': quantum_net_id}]
248
 
 
249
 
    def delete_network(self, context, fixed_range, uuid):
250
 
        """Lookup network by uuid, delete both the IPAM
251
 
           subnet and the corresponding Quantum network.
252
 
 
253
 
           The fixed_range parameter is kept here for interface compatibility
254
 
           but is not used.
255
 
        """
256
 
        net_ref = db.network_get_by_uuid(context.elevated(), uuid)
257
 
        project_id = net_ref['project_id']
258
 
        q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
259
 
        net_uuid = net_ref['uuid']
260
 
 
261
 
        # Check for any attached ports on the network and fail the deletion if
262
 
        # there is anything but the gateway port attached.  If it is only the
263
 
        # gateway port, unattach and delete it.
264
 
        ports = self.q_conn.get_attached_ports(q_tenant_id, net_uuid)
265
 
        num_ports = len(ports)
266
 
        gw_interface_id = self.driver.get_dev(net_ref)
267
 
        gw_port_uuid = None
268
 
        if gw_interface_id is not None:
269
 
            gw_port_uuid = self.q_conn.get_port_by_attachment(q_tenant_id,
270
 
                                        net_uuid, gw_interface_id)
271
 
 
272
 
        if gw_port_uuid:
273
 
            num_ports -= 1
274
 
 
275
 
        if num_ports > 0:
276
 
            raise exception.NetworkBusy(network=net_uuid)
277
 
 
278
 
        # only delete gw ports if we are going to finish deleting network
279
 
        if gw_port_uuid:
280
 
            self.q_conn.detach_and_delete_port(q_tenant_id,
281
 
                                                   net_uuid,
282
 
                                                   gw_port_uuid)
283
 
            self.l3driver.remove_gateway(net_ref)
284
 
 
285
 
        # Now we can delete the network
286
 
        self.q_conn.delete_network(q_tenant_id, net_uuid)
287
 
        LOG.debug("Deleting network %s for tenant: %s" %
288
 
                  (net_uuid, q_tenant_id))
289
 
        self.ipam.delete_subnets_by_net_id(context, net_uuid, project_id)
290
 
        # Get rid of dnsmasq
291
 
        if FLAGS.quantum_use_dhcp:
292
 
            if net_ref['host'] == self.host:
293
 
                self.kill_dhcp(net_ref)
294
 
            else:
295
 
                topic = rpc.queue_get_for(context,
296
 
                        FLAGS.network_topic,
297
 
                        net_ref['host'])
298
 
 
299
 
                rpc.call(context, topic, {'method': 'kill_dhcp',
300
 
                        'args': {'net_ref': net_ref}})
301
 
 
302
 
    def kill_dhcp(self, net_ref):
303
 
        dev = self.driver.get_dev(net_ref)
304
 
        if self.driver._device_exists(dev):
305
 
            self.driver.kill_dhcp(dev)
306
 
 
307
 
    def allocate_for_instance(self, context, **kwargs):
308
 
        """Called by compute when it is creating a new VM.
309
 
 
310
 
           There are three key tasks:
311
 
                - Determine the number and order of vNICs to create
312
 
                - Allocate IP addresses
313
 
                - Create ports on a Quantum network and attach vNICs.
314
 
 
315
 
           We support two approaches to determining vNICs:
316
 
                - By default, a VM gets a vNIC for any network belonging
317
 
                  to the VM's project, and a vNIC for any "global" network
318
 
                  that has a NULL project_id.  vNIC order is determined
319
 
                  by the network's 'priority' field.
320
 
                - If the 'os-create-server-ext' was used to create the VM,
321
 
                  only the networks in 'requested_networks' are used to
322
 
                  create vNICs, and the vNIC order is determiend by the
323
 
                  order in the requested_networks array.
324
 
 
325
 
           For each vNIC, use the FlatManager to create the entries
326
 
           in the virtual_interfaces table, contact Quantum to
327
 
           create a port and attachment the vNIC, and use the IPAM
328
 
           lib to allocate IP addresses.
329
 
        """
330
 
        instance_id = kwargs['instance_id']
331
 
        rxtx_factor = kwargs['rxtx_factor']
332
 
        host = kwargs['host']
333
 
        project_id = kwargs['project_id']
334
 
        LOG.debug(_("network allocations for instance %s"), project_id)
335
 
        requested_networks = kwargs.get('requested_networks')
336
 
        instance = db.instance_get(context, instance_id)
337
 
 
338
 
        net_proj_pairs = self.ipam.get_project_and_global_net_ids(context,
339
 
                                                                project_id)
340
 
        if requested_networks:
341
 
            # need to figure out if a requested network is owned
342
 
            # by the tenant, or by the provider
343
 
            # Note: these are the only possible options, as the compute
344
 
            # API already validated networks using validate_network()
345
 
            proj_net_ids = set([p[0] for p in net_proj_pairs if p[1]])
346
 
            net_proj_pairs = []
347
 
            for net_id, _i in requested_networks:
348
 
                if net_id in proj_net_ids:
349
 
                    net_proj_pairs.append((net_id, project_id))
350
 
                else:
351
 
                    net_proj_pairs.append((net_id, None))
352
 
 
353
 
        # Create a port via quantum and attach the vif
354
 
        for proj_pair in net_proj_pairs:
355
 
            network = self.get_network(context, proj_pair)
356
 
 
357
 
            # TODO(tr3buchet): broken. Virtual interfaces require an integer
358
 
            #                  network ID and it is not nullable
359
 
            vif_rec = self.add_virtual_interface(context,
360
 
                                                 instance_id,
361
 
                                                 network['id'],
362
 
                                                 project_id)
363
 
 
364
 
            # talk to Quantum API to create and attach port.
365
 
            nova_id = self._get_nova_id(instance)
366
 
            # Tell the ipam library to allocate an IP
367
 
            ips = self.ipam.allocate_fixed_ips(context, project_id,
368
 
                    network['quantum_net_id'], network['net_tenant_id'],
369
 
                    vif_rec)
370
 
            pairs = []
371
 
            # Set up port security if enabled
372
 
            if FLAGS.quantum_use_port_security:
373
 
                if FLAGS.quantum_port_security_include_link_local:
374
 
                    mac = netaddr.EUI(vif_rec['address'])
375
 
                    ips.append(str(mac.ipv6_link_local()))
376
 
 
377
 
                pairs = [{'mac_address': vif_rec['address'],
378
 
                          'ip_address': ip} for ip in ips]
379
 
 
380
 
            self.q_conn.create_and_attach_port(network['net_tenant_id'],
381
 
                                               network['quantum_net_id'],
382
 
                                               vif_rec['uuid'],
383
 
                                               vm_id=instance['uuid'],
384
 
                                               rxtx_factor=rxtx_factor,
385
 
                                               nova_id=nova_id,
386
 
                                               allowed_address_pairs=pairs)
387
 
            # Set up/start the dhcp server for this network if necessary
388
 
            if FLAGS.quantum_use_dhcp:
389
 
                if network['host'] == self.host:
390
 
                    self.enable_dhcp(context, network['quantum_net_id'],
391
 
                            network, vif_rec, network['net_tenant_id'])
392
 
                else:
393
 
                    topic = rpc.queue_get_for(context,
394
 
                                FLAGS.network_topic, network['host'])
395
 
                    rpc.call(context, topic, {'method': 'enable_dhcp',
396
 
                        'args': {'quantum_net_id': network['quantum_net_id'],
397
 
                        'network_ref': network,
398
 
                        'vif_rec': vif_rec,
399
 
                        'project_id': network['net_tenant_id']}})
400
 
 
401
 
        return self.get_instance_nw_info(context, instance_id,
402
 
                                         instance['uuid'],
403
 
                                         rxtx_factor, host,
404
 
                                         project_id=project_id)
405
 
 
406
 
    def get_network(self, context, proj_pair):
407
 
        (quantum_net_id, net_tenant_id) = proj_pair
408
 
 
409
 
        net_tenant_id = net_tenant_id or FLAGS.quantum_default_tenant_id
410
 
        # FIXME(danwent): We'd like to have the manager be
411
 
        # completely decoupled from the nova networks table.
412
 
        # However, other parts of nova sometimes go behind our
413
 
        # back and access network data directly from the DB.  So
414
 
        # for now, the quantum manager knows that there is a nova
415
 
        # networks DB table and accesses it here.  updating the
416
 
        # virtual_interfaces table to use UUIDs would be one
417
 
        # solution, but this would require significant work
418
 
        # elsewhere.
419
 
        admin_context = context.elevated()
420
 
 
421
 
        # We may not be able to get a network_ref here if this network
422
 
        # isn't in the database (i.e. it came from Quantum).
423
 
        network_ref = db.network_get_by_uuid(admin_context,
424
 
                                             quantum_net_id)
425
 
 
426
 
        if network_ref is None:
427
 
            network_ref = {}
428
 
            network_ref = {"uuid": quantum_net_id,
429
 
                           "project_id": net_tenant_id,
430
 
            # NOTE(bgh): We need to document this somewhere but since
431
 
            # we don't know the priority of any networks we get from
432
 
            # quantum we just give them a priority of 0.  If its
433
 
            # necessary to specify the order of the vifs and what
434
 
            # network they map to then the user will have to use the
435
 
            # OSCreateServer extension and specify them explicitly.
436
 
            #
437
 
            # In the future users will be able to tag quantum networks
438
 
            # with a priority .. and at that point we can update the
439
 
            # code here to reflect that.
440
 
                           "priority": 0,
441
 
                           "id": 'NULL',
442
 
                           "label": "quantum-net-%s" % quantum_net_id}
443
 
        network_ref['net_tenant_id'] = net_tenant_id
444
 
        network_ref['quantum_net_id'] = quantum_net_id
445
 
        return network_ref
446
 
 
447
 
    @manager.wrap_check_policy
448
 
    def get_instance_uuids_by_ip_filter(self, context, filters):
449
 
        # This is not returning the instance IDs like the method name
450
 
        # would make you think; it is matching the return format of
451
 
        # the method it's overriding.
452
 
        instance_ids = self.ipam.get_instance_ids_by_ip_address(
453
 
                                    context, filters.get('ip'))
454
 
        instances = [db.instance_get(context, id) for id in instance_ids]
455
 
        return [{'instance_uuid':instance.uuid} for instance in instances]
456
 
 
457
 
    @utils.synchronized('quantum-enable-dhcp')
458
 
    def enable_dhcp(self, context, quantum_net_id, network_ref, vif_rec,
459
 
            project_id):
460
 
        LOG.info("Using DHCP for network: %s" % network_ref['label'])
461
 
        # Figure out the ipam tenant id for this subnet:  We need to
462
 
        # query for the tenant_id since the network could be created
463
 
        # with the project_id as the tenant or the default tenant.
464
 
        ipam_tenant_id = self.ipam.get_tenant_id_by_net_id(context,
465
 
            quantum_net_id, vif_rec['uuid'], project_id)
466
 
        # Figure out what subnets correspond to this network
467
 
        subnets = self.ipam.get_subnets_by_net_id(context,
468
 
                            ipam_tenant_id, quantum_net_id, vif_rec['uuid'])
469
 
 
470
 
        # Set up (or find) the dhcp server for each of the subnets
471
 
        # returned above (both v4 and v6).
472
 
        for subnet in subnets:
473
 
            if subnet is None or subnet['cidr'] is None:
474
 
                continue
475
 
            # Fill in some of the network fields that we would have
476
 
            # previously gotten from the network table (they'll be
477
 
            # passed to the linux_net functions).
478
 
            network_ref['cidr'] = subnet['cidr']
479
 
            n = netaddr.IPNetwork(subnet['cidr'])
480
 
            # NOTE(tr3buchet): should probably not always assume first+1
481
 
            network_ref['dhcp_server'] = netaddr.IPAddress(n.first + 1)
482
 
            # TODO(bgh): Melange should probably track dhcp_start
483
 
            # TODO(tr3buchet): melange should store dhcp_server as well
484
 
            if (not 'dhcp_start' in network_ref or
485
 
                network_ref['dhcp_start'] is None):
486
 
                network_ref['dhcp_start'] = netaddr.IPAddress(n.first + 2)
487
 
            network_ref['broadcast'] = netaddr.IPAddress(n.broadcast)
488
 
            network_ref['gateway'] = subnet['gateway']
489
 
            # Construct the interface id that we'll use for the bridge
490
 
            interface_id = self.driver.get_dev(network_ref)
491
 
            network_ref['bridge'] = interface_id
492
 
            # Query quantum to see if we've already created a port for
493
 
            # the gateway device and attached the device to the port.
494
 
            # If we haven't then we need to intiialize it and create
495
 
            # it.  This device will be the one serving dhcp via
496
 
            # dnsmasq.
497
 
            q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
498
 
            port = self.q_conn.get_port_by_attachment(q_tenant_id,
499
 
                    quantum_net_id, interface_id)
500
 
 
501
 
            if not port:  # No dhcp server has been started
502
 
                self.l3driver.initialize_gateway(network_ref)
503
 
                LOG.debug("Intializing DHCP for network: %s" %
504
 
                    network_ref)
505
 
                self.q_conn.create_and_attach_port(q_tenant_id,
506
 
                        quantum_net_id, interface_id)
507
 
 
508
 
            hosts = self.get_dhcp_hosts_text(context,
509
 
                subnet['network_id'], project_id)
510
 
            self.driver.update_dhcp_hostfile_with_text(interface_id, hosts)
511
 
            self.driver.restart_dhcp(context, interface_id, network_ref)
512
 
 
513
 
    def add_virtual_interface(self, context, instance_id, network_id,
514
 
                              net_tenant_id):
515
 
        # If we're not using melange, use the default means...
516
 
        if FLAGS.use_melange_mac_generation:
517
 
            return self._add_virtual_interface(context, instance_id,
518
 
                                               network_id, net_tenant_id)
519
 
 
520
 
        return super(QuantumManager, self).add_virtual_interface(context,
521
 
                                                                 instance_id,
522
 
                                                                 network_id)
523
 
 
524
 
    def _add_virtual_interface(self, context, instance_id, network_id,
525
 
                               net_tenant_id):
526
 
        vif = {'instance_id': instance_id,
527
 
               'network_id': network_id,
528
 
               'uuid': str(utils.gen_uuid())}
529
 
 
530
 
        # TODO(Vek): Ideally, we would have a VirtualInterface class
531
 
        #            that would take care of delegating to whoever it
532
 
        #            needs to get information from.  We'll look at
533
 
        #            this after Trey's refactorings...
534
 
        m_ipam = melange_ipam_lib.get_ipam_lib(self)
535
 
        vif['address'] = m_ipam.create_vif(vif['uuid'],
536
 
                                           vif['instance_id'],
537
 
                                           net_tenant_id)
538
 
 
539
 
        return self.db.virtual_interface_create(context, vif)
540
 
 
541
 
    def get_instance_nw_info(self, context, instance_id, instance_uuid,
542
 
                                            rxtx_factor, host, **kwargs):
543
 
        """This method is used by compute to fetch all network data
544
 
           that should be used when creating the VM.
545
 
 
546
 
           The method simply loops through all virtual interfaces
547
 
           stored in the nova DB and queries the IPAM lib to get
548
 
           the associated IP data.
549
 
 
550
 
           The format of returned data is 'defined' by the initial
551
 
           set of NetworkManagers found in nova/network/manager.py .
552
 
           Ideally this 'interface' will be more formally defined
553
 
           in the future.
554
 
        """
555
 
        project_id = kwargs['project_id']
556
 
        vifs = db.virtual_interface_get_by_instance(context, instance_id)
557
 
 
558
 
        net_tenant_dict = dict((net_id, tenant_id)
559
 
                               for (net_id, tenant_id)
560
 
                               in self.ipam.get_project_and_global_net_ids(
561
 
                                                          context, project_id))
562
 
        networks = {}
563
 
        for vif in vifs:
564
 
            if vif.get('network_id') is not None:
565
 
                network = db.network_get(context.elevated(), vif['network_id'])
566
 
                net_tenant_id = net_tenant_dict[network['uuid']]
567
 
                if net_tenant_id is None:
568
 
                    net_tenant_id = FLAGS.quantum_default_tenant_id
569
 
                network = {'id': network['id'],
570
 
                           'uuid': network['uuid'],
571
 
                           'bridge': '',  # Quantum ignores this field
572
 
                           'label': network['label'],
573
 
                           'injected': FLAGS.flat_injected,
574
 
                           'project_id': net_tenant_id}
575
 
                networks[vif['uuid']] = network
576
 
 
577
 
        # update instance network cache and return network_info
578
 
        nw_info = self.build_network_info_model(context, vifs, networks,
579
 
                                                rxtx_factor, host)
580
 
        db.instance_info_cache_update(context, instance_uuid,
581
 
                                      {'network_info': nw_info.json()})
582
 
 
583
 
        return nw_info
584
 
 
585
 
    def deallocate_for_instance(self, context, **kwargs):
586
 
        """Called when a VM is terminated.  Loop through each virtual
587
 
           interface in the Nova DB and remove the Quantum port and
588
 
           clear the IP allocation using the IPAM.  Finally, remove
589
 
           the virtual interfaces from the Nova DB.
590
 
        """
591
 
        instance_id = kwargs.get('instance_id')
592
 
        project_id = kwargs.pop('project_id', None)
593
 
 
594
 
        admin_context = context.elevated()
595
 
        vifs = db.virtual_interface_get_by_instance(admin_context,
596
 
                                                    instance_id)
597
 
 
598
 
        for vif in vifs:
599
 
            network = db.network_get(admin_context, vif['network_id'])
600
 
 
601
 
            self.deallocate_port(vif['uuid'], network['uuid'], project_id,
602
 
                                 instance_id)
603
 
 
604
 
            ipam_tenant_id = self.deallocate_ip_address(context,
605
 
                                network['uuid'], project_id, vif, instance_id)
606
 
 
607
 
            if FLAGS.quantum_use_dhcp:
608
 
                if network['host'] == self.host:
609
 
                    self.update_dhcp(context, ipam_tenant_id, network,
610
 
                                 vif, project_id)
611
 
                else:
612
 
                    topic = rpc.queue_get_for(context,
613
 
                                FLAGS.network_topic, network['host'])
614
 
                    rpc.call(context, topic, {'method': 'update_dhcp',
615
 
                        'args': {'ipam_tenant_id': ipam_tenant_id,
616
 
                        'network_ref': network,
617
 
                        'vif_ref': vif,
618
 
                        'project_id': network['project_id']}})
619
 
 
620
 
            db.virtual_interface_delete(admin_context, vif['id'])
621
 
 
622
 
    def deallocate_port(self, interface_id, net_id, q_tenant_id, instance_id):
623
 
        port_id = None
624
 
        try:
625
 
            port_id = self.q_conn.get_port_by_attachment(q_tenant_id,
626
 
                                                         net_id, interface_id)
627
 
            if not port_id:
628
 
                q_tenant_id = FLAGS.quantum_default_tenant_id
629
 
                port_id = self.q_conn.get_port_by_attachment(
630
 
                    q_tenant_id, net_id, interface_id)
631
 
 
632
 
            if not port_id:
633
 
                LOG.error("Unable to find port with attachment: %s" %
634
 
                          (interface_id))
635
 
            else:
636
 
                self.q_conn.detach_and_delete_port(q_tenant_id,
637
 
                                                   net_id, port_id)
638
 
        except Exception:
639
 
            # except anything so the rest of deallocate can succeed
640
 
            LOG.exception(_('port deallocation failed for instance: '
641
 
                    '|%(instance_id)s|, port_id: |%(port_id)s|') % locals())
642
 
 
643
 
    def deallocate_ip_address(self, context, net_id,
644
 
                              project_id, vif_ref, instance_id):
645
 
        ipam_tenant_id = None
646
 
        try:
647
 
            ipam_tenant_id = self.ipam.get_tenant_id_by_net_id(context,
648
 
                                                               net_id,
649
 
                                                               vif_ref['uuid'],
650
 
                                                               project_id)
651
 
 
652
 
            self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id,
653
 
                                            net_id, vif_ref)
654
 
 
655
 
        except Exception:
656
 
            # except anything so the rest of deallocate can succeed
657
 
            vif_uuid = vif_ref['uuid']
658
 
            msg = _('ipam deallocation failed for instance: '
659
 
                    '|%(instance_id)s|, vif_uuid: |%(vif_uuid)s|') % locals()
660
 
            LOG.exception(msg)
661
 
        return ipam_tenant_id
662
 
 
663
 
    # enable_dhcp() could also use this
664
 
    #
665
 
    # Use semaphore to :
666
 
    # 1) avoid race between restart_dhcps
667
 
    # 2) avoid race between update_dhcp_hostfile_with_texts
668
 
    @utils.synchronized('quantum-restart-dhcp')
669
 
    def _atomic_restart_dhcp(self, context, dev, hosts, network_ref):
670
 
        self.driver.update_dhcp_hostfile_with_text(dev, hosts)
671
 
        # Since we only update hosts file, explict kill_dhcp() isn't needed.
672
 
        # restart_dhcp() will reload hostfile if dnsmasq is already running,
673
 
        # or start new dnsmasq
674
 
        self.driver.restart_dhcp(context, dev, network_ref)
675
 
 
676
 
    # TODO(bgh): At some point we should consider merging enable_dhcp() and
677
 
    # update_dhcp()
678
 
    # TODO(tr3buchet): agree, i'm curious why they differ even now..
679
 
    def update_dhcp(self, context, ipam_tenant_id, network_ref, vif_ref,
680
 
            project_id):
681
 
        # Figure out what subnet corresponds to this network/vif
682
 
        subnets = self.ipam.get_subnets_by_net_id(context,
683
 
                        ipam_tenant_id, network_ref['uuid'], vif_ref['uuid'])
684
 
        for subnet in subnets:
685
 
            if subnet is None:
686
 
                continue
687
 
            # Fill in some of the network fields that we would have
688
 
            # previously gotten from the network table (they'll be
689
 
            # passed to the linux_net functions).
690
 
            if subnet['cidr']:
691
 
                network_ref['cidr'] = subnet['cidr']
692
 
            n = netaddr.IPNetwork(network_ref['cidr'])
693
 
            network_ref['dhcp_server'] = netaddr.IPAddress(n.first + 1)
694
 
            network_ref['dhcp_start'] = netaddr.IPAddress(n.first + 2)
695
 
            network_ref['broadcast'] = netaddr.IPAddress(n.broadcast)
696
 
            network_ref['gateway'] = netaddr.IPAddress(n.first + 1)
697
 
            dev = self.driver.get_dev(network_ref)
698
 
            # And remove the dhcp mappings for the subnet
699
 
            hosts = self.get_dhcp_hosts_text(context,
700
 
                subnet['network_id'], project_id)
701
 
 
702
 
            self._atomic_restart_dhcp(context, dev, hosts, network_ref)
703
 
 
704
 
    def validate_networks(self, context, networks):
705
 
        """Validates that this tenant has quantum networks with the associated
706
 
           UUIDs.  This is called by the 'os-create-server-ext' API extension
707
 
           code so that we can return an API error code to the caller if they
708
 
           request an invalid network.
709
 
        """
710
 
        if networks is None:
711
 
            return
712
 
 
713
 
        project_id = context.project_id
714
 
        for (net_id, _i) in networks:
715
 
            # TODO(bgh): At some point we should figure out whether or
716
 
            # not we want the verify_subnet_exists call to be optional.
717
 
            if not self.ipam.verify_subnet_exists(context, project_id,
718
 
                                                  net_id):
719
 
                raise exception.NetworkNotFound(network_id=net_id)
720
 
            is_tenant_net = self.q_conn.network_exists(project_id, net_id)
721
 
            is_provider_net = self.q_conn.network_exists(
722
 
                                    FLAGS.quantum_default_tenant_id,
723
 
                                    net_id)
724
 
            if not (is_tenant_net or is_provider_net):
725
 
                raise exception.NetworkNotFound(network_id=net_id)
726
 
 
727
 
    def get_dhcp_hosts_text(self, context, subnet_id, project_id=None):
728
 
        ips = self.ipam.get_allocated_ips(context, subnet_id, project_id)
729
 
        hosts_text = ""
730
 
        admin_context = context.elevated()
731
 
        for ip in ips:
732
 
            address, vif_id = ip
733
 
            vif = db.virtual_interface_get_by_uuid(admin_context, vif_id)
734
 
            mac_address = vif['address']
735
 
            text = "%s,%s.%s,%s\n" % (mac_address, "host-" + address,
736
 
                FLAGS.dhcp_domain, address)
737
 
            hosts_text += text
738
 
        LOG.debug("DHCP hosts: %s" % hosts_text)
739
 
        return hosts_text
740
 
 
741
 
    def get_dhcp_leases(self, context, network_ref):
742
 
        """Return a network's hosts config in dnsmasq leasefile format."""
743
 
        subnet_id = network_ref['uuid']
744
 
        project_id = network_ref['project_id']
745
 
        ips = self.ipam.get_allocated_ips(context, subnet_id, project_id)
746
 
        leases_text = ""
747
 
        admin_context = context.elevated()
748
 
        for ip in ips:
749
 
            address, vif_id = ip
750
 
            vif = db.virtual_interface_get_by_uuid(admin_context, vif_id)
751
 
            mac_address = vif['address']
752
 
            text = ("%s %s %s %s *\n" % (int(time.time()) -
753
 
                                         FLAGS.dhcp_lease_time,
754
 
                                         mac_address, address, '*'))
755
 
            leases_text += text
756
 
        LOG.debug("DHCP leases: %s" % leases_text)
757
 
        return leases_text
758
 
 
759
 
    def setup_networks_on_host(self, *args, **kwargs):
760
 
        # no host specific setup is needed in quantum manager
761
 
        pass