~bgh/nova/qmanager-dhcp

« back to all changes in this revision

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

  • Committer: Brad Hall
  • Date: 2011-10-02 00:34:49 UTC
  • Revision ID: brad@nicira.com-20111002003449-c93594f62o8zpt2l
Add DHCP support to the QuantumManager

This introduces a new flag "quantum_use_dhcp=<boolean>" which indicates
whether or not to enable dhcp for all of the networks.  If it is set then we
start dnsmasq (and provide it with the IP/MACs from Melange) similar to how
this was done in linux_net before.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
#    License for the specific language governing permissions and limitations
16
16
#    under the License.
17
17
 
 
18
from netaddr import IPNetwork, IPAddress
 
19
 
18
20
from nova import db
19
21
from nova import exception
20
22
from nova import flags
33
35
                    "Indicates underlying IP address management library")
34
36
 
35
37
 
 
38
flags.DEFINE_string('quantum_use_dhcp', 'False',
 
39
                    'Whether or not to enable DHCP for networks')
 
40
 
 
41
 
36
42
class QuantumManager(manager.FlatManager):
37
43
    """NetworkManager class that communicates with a Quantum service
38
44
       via a web services API to provision VM network connectivity.
65
71
        self.ipam = utils.import_object(ipam_lib).get_ipam_lib(self)
66
72
 
67
73
        super(QuantumManager, self).__init__(*args, **kwargs)
 
74
        self.driver.init_host()
 
75
        self.driver.ensure_metadata_ip()
 
76
        self.driver.metadata_forward()
68
77
 
69
78
    def create_networks(self, context, label, cidr, multi_host, num_networks,
70
 
                        network_size, cidr_v6, gateway_v6, bridge,
 
79
                        network_size, cidr_v6, gateway, gateway_v6, bridge,
71
80
                        bridge_interface, dns1=None, dns2=None, uuid=None,
72
81
                        **kwargs):
73
82
        """Unlike other NetworkManagers, with QuantumManager, each
98
107
        ipam_tenant_id = kwargs.get("project_id", None)
99
108
        priority = kwargs.get("priority", 0)
100
109
        self.ipam.create_subnet(context, label, ipam_tenant_id, quantum_net_id,
101
 
                            priority, cidr, gateway_v6, cidr_v6, dns1, dns2)
 
110
            priority, cidr, gateway, gateway_v6,
 
111
            cidr_v6, dns1, dns2)
102
112
 
103
113
    def delete_network(self, context, fixed_range):
104
114
        """Lookup network by IPv4 cidr, delete both the IPAM
105
115
           subnet and the corresponding Quantum network.
106
116
        """
 
117
        quantum_net_id = None
107
118
        project_id = context.project_id
108
 
        quantum_net_id = self.ipam.get_network_id_by_cidr(
109
 
                                    context, fixed_range, project_id)
 
119
        # TODO(bgh): The project_id isn't getting populated here for some
 
120
        # reason.. I'm not sure if it's an invalid assumption or just a bug.
 
121
        # In order to get the right quantum_net_id we'll have to query all the
 
122
        # project_ids for now.
 
123
        if project_id is None:
 
124
            projects = db.project_get_all(context)
 
125
            for p in projects:
 
126
                try:
 
127
                    quantum_net_id = self.ipam.get_network_id_by_cidr(
 
128
                        context, fixed_range, p['id'])
 
129
                except exception.NotFound:
 
130
                    continue  # Guess it wasn't that one!
 
131
                project_id = p['id']
 
132
        if project_id is None:
 
133
            raise Exception("Unable to find network for cidr: %s" %
 
134
                (fixed_range))
 
135
        LOG.debug("Deleting network for tenant: %s" % project_id)
110
136
        self.ipam.delete_subnets_by_net_id(context, quantum_net_id,
111
137
                                                            project_id)
112
138
        q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
113
139
        self.q_conn.delete_network(q_tenant_id, quantum_net_id)
114
140
 
 
141
    @utils.synchronized('quantum_allocate')
115
142
    def allocate_for_instance(self, context, **kwargs):
116
143
        """Called by compute when it is creating a new VM.
117
144
 
150
177
            net_proj_pairs = self.ipam.get_project_and_global_net_ids(context,
151
178
                                                                project_id)
152
179
 
 
180
        # Quantum may also know about networks that aren't in the networks
 
181
        # table so we need to query Quanutm for any tenant networks and add
 
182
        # them to net_proj_pairs.
 
183
        qnets = self.q_conn.get_networks(project_id)
 
184
        for qn in qnets['networks']:
 
185
            net_proj_pairs.append((qn['id'], project_id))
 
186
 
153
187
        # Create a port via quantum and attach the vif
154
188
        for (quantum_net_id, project_id) in net_proj_pairs:
155
 
 
156
189
            # FIXME(danwent): We'd like to have the manager be
157
190
            # completely decoupled from the nova networks table.
158
191
            # However, other parts of nova sometimes go behind our
163
196
            # solution, but this would require significant work
164
197
            # elsewhere.
165
198
            admin_context = context.elevated()
 
199
 
 
200
            # We may not be able to get a network_ref here if this network
 
201
            # isn't in the database (i.e. it came from Quantum).  In that case
 
202
            # we need to create it in the db (for now) since the
 
203
            # virtual_interfaces table has a fkey pointing to networks.
166
204
            network_ref = db.network_get_by_uuid(admin_context,
167
205
                                                 quantum_net_id)
 
206
            if network_ref is None:
 
207
                network_ref = {}
 
208
                network_ref = {"uuid": quantum_net_id,
 
209
                    "project_id": project_id,
 
210
                    # NOTE(bgh): We need to document this somewhere but since
 
211
                    # we don't know the priority of any networks we get from
 
212
                    # quantum we just give them a priority of 0.  If its
 
213
                    # necessary to specify the order of the vifs and what
 
214
                    # network they map to then the user will have to use the
 
215
                    # OSCreateServer extension and specify them explicitly.
 
216
                    "priority": 0,
 
217
                    "label": "quantum-net-%s" % quantum_net_id}
 
218
                network_ref = self.db.network_create_safe(admin_context,
 
219
                    network_ref)
 
220
                LOG.debug("Created new network: %s" % network_ref)
168
221
 
169
222
            vif_rec = manager.FlatManager.add_virtual_interface(self,
170
223
                                  context, instance_id, network_ref['id'])
173
226
            q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
174
227
            self.q_conn.create_and_attach_port(q_tenant_id, quantum_net_id,
175
228
                                               vif_rec['uuid'])
176
 
            self.ipam.allocate_fixed_ip(context, project_id, quantum_net_id,
177
 
                                        vif_rec)
 
229
 
 
230
            ip = self.ipam.allocate_fixed_ip(context, project_id,
 
231
                    quantum_net_id, vif_rec)
 
232
 
 
233
            if FLAGS.quantum_use_dhcp:
 
234
                LOG.info("Using DHCP for network: %s" % network_ref['label'])
 
235
                # Figure out the ipam tenant id for this subnet:  We need to
 
236
                # query for the tenant_id since the network could be created
 
237
                # with the project_id as the tenant or the default tenant.
 
238
                ipam_tenant_id = self.ipam.get_tenant_id_by_net_id(context,
 
239
                    quantum_net_id, vif_rec['uuid'], project_id)
 
240
                # Figure out what subnets correspond to this network
 
241
                v4_subnet, v6_subnet = self.ipam.get_subnets_by_net_id(context,
 
242
                            ipam_tenant_id, quantum_net_id, vif_rec['uuid'])
 
243
                # Set up (or find) the dhcp server for each of the subnets
 
244
                # returned above (both v4 and v6).
 
245
                for subnet in [v4_subnet, v6_subnet]:
 
246
                    if subnet is None:
 
247
                        continue
 
248
                    # Fill in some of the network fields that we would have
 
249
                    # previously gotten from the network table (they'll be
 
250
                    # passed to the linux_net functions).
 
251
                    network_ref['cidr'] = subnet['cidr']
 
252
                    n = IPNetwork(subnet['cidr'])
 
253
                    network_ref['dhcp_server'] = IPAddress(n.first + 1)
 
254
                    network_ref['dhcp_start'] = IPAddress(n.first + 2)
 
255
                    network_ref['broadcast'] = IPAddress(n.broadcast)
 
256
                    # TODO(bgh): We need to account for the case where the
 
257
                    # user doesn't want a gateway and tell linux_net to add
 
258
                    # the appropriate flows to make the dhcp server not act as
 
259
                    # a gateway.  The call to initialize_gateway_device will
 
260
                    # need to be conditional and that call will probably need
 
261
                    # to be broken up.
 
262
                    if network_ref['gateway'] is None:
 
263
                        network_ref['gateway'] = IPAddress(n.first + 1)
 
264
                    # Construct the interface id that we'll use for the bridge
 
265
                    interface_id = "gw-%x" % n.network
 
266
                    network_ref['bridge'] = interface_id
 
267
                    # Query quantum to see if we've already created a port for
 
268
                    # the gateway device and attached the device to the port.
 
269
                    # If we haven't then we need to intiialize it and create
 
270
                    # it.  This device will be the one serving dhcp via
 
271
                    # dnsmasq.
 
272
                    port = self.q_conn.get_port_by_attachment(project_id,
 
273
                            quantum_net_id, interface_id)
 
274
                    if not port:  # No dhcp server has been started
 
275
                        mac_address = self.generate_mac_address()
 
276
                        dev = self.driver.plug(network_ref, mac_address)
 
277
                        self.driver.initialize_gateway_device(dev, network_ref)
 
278
                        LOG.debug("Intializing DHCP for network: %s" %
 
279
                            network_ref)
 
280
                        self.q_conn.create_and_attach_port(project_id,
 
281
                                quantum_net_id, interface_id)
 
282
                    else:  # We've already got one and its plugged in
 
283
                        dev = interface_id
 
284
 
 
285
                    hosts = self.ipam.get_dhcp_hosts_text(context,
 
286
                        subnet['network_id'], project_id)
 
287
                    self.driver.update_dhcp_hostfile_with_text(dev, hosts)
 
288
                    self.driver.restart_dhcp(dev, network_ref)
178
289
 
179
290
        return self.get_instance_nw_info(context, instance_id,
180
291
                                         instance_type_id, host)
181
292
 
 
293
    def get_dhcp_leases_text(self, context, network_ref):
 
294
        return self.ipam.get_dhcp_leases_text(context, network_ref)
 
295
 
182
296
    def get_instance_nw_info(self, context, instance_id,
183
297
                                instance_type_id, host):
184
298
        """This method is used by compute to fetch all network data
214
328
                raise Exception(_("No network for for virtual interface %s") %
215
329
                                vif['uuid'])
216
330
 
217
 
            ipam_tenant_id = project_id
218
 
            (v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context,
219
 
                                        ipam_tenant_id, net_id, vif['uuid'])
220
 
            # There isn't a good way of figuring out what the ipam
221
 
            # tenant id should be before hand so we have to try both
222
 
            # (project_id and None).
223
 
            if not v4_subnet and not v6_subnet:
224
 
                ipam_tenant_id = None
225
 
                (v4_subnet, v6_subnet) = \
 
331
            ipam_tenant_id = self.ipam.get_tenant_id_by_net_id(context,
 
332
                net_id, vif['uuid'], project_id)
 
333
            v4_subnet, v6_subnet = \
226
334
                    self.ipam.get_subnets_by_net_id(context,
227
 
                                                    ipam_tenant_id,
228
 
                                                    net_id, vif['uuid'])
 
335
                            ipam_tenant_id, net_id, vif['uuid'])
 
336
 
229
337
            v4_ips = self.ipam.get_v4_ips_by_interface(context,
230
338
                                        net_id, vif['uuid'],
231
339
                                        project_id=ipam_tenant_id)
247
355
                'multi_host': False}
248
356
 
249
357
            info = {
 
358
                'label': net['label'],
250
359
                'gateway': v4_subnet['gateway'],
251
360
                'dhcp_server': v4_subnet['gateway'],
252
361
                'broadcast': v4_subnet['broadcast'],
306
415
                self.q_conn.detach_and_delete_port(q_tenant_id,
307
416
                                                   net_id, port_id)
308
417
 
309
 
            # Figure out which ipam tenant id we need here, either the project
310
 
            # id or None (which corresponds to the provider address space).
311
 
            if self.ipam.verify_subnet_exists(context, project_id, net_id):
312
 
                ipam_tenant_id = project_id
313
 
            else:
314
 
                ipam_tenant_id = None
 
418
            ipam_tenant_id = self.ipam.get_tenant_id_by_net_id(context,
 
419
                net_id, vif_ref['uuid'], project_id)
315
420
 
316
421
            self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id,
317
422
                                            net_id, vif_ref)
318
423
 
 
424
            # If DHCP is enabled on this network then we need to update the
 
425
            # leases and restart the server.
 
426
            if FLAGS.quantum_use_dhcp:
 
427
                # Figure out what subnet corresponds to this network/vif
 
428
                v4_subnet, v6_subnet = self.ipam.get_subnets_by_net_id(context,
 
429
                                ipam_tenant_id, net_id, vif_ref['uuid'])
 
430
                for subnet in [v4_subnet, v6_subnet]:
 
431
                    if subnet is None:
 
432
                        continue
 
433
                    # Fill in some of the network fields that we would have
 
434
                    # previously gotten from the network table (they'll be
 
435
                    # passed to the linux_net functions).
 
436
                    net['cidr'] = subnet['cidr']
 
437
                    n = IPNetwork(subnet['cidr'])
 
438
                    net['dhcp_server'] = IPAddress(n.first + 1)
 
439
                    net['dhcp_start'] = IPAddress(n.first + 2)
 
440
                    net['broadcast'] = IPAddress(n.broadcast)
 
441
                    net['gateway'] = IPAddress(n.first + 1)
 
442
                    dev = "gw-%x" % n.network
 
443
                    # And remove the dhcp mappings for the subnet
 
444
                    hosts = self.ipam.get_dhcp_hosts_text(context,
 
445
                        subnet['network_id'], project_id)
 
446
                    self.driver.update_dhcp_hostfile_with_text(dev, hosts)
 
447
                    # Restart dnsmasq
 
448
                    self.driver.restart_dhcp(dev, net)
 
449
 
319
450
        try:
320
451
            db.virtual_interface_delete_by_instance(admin_context,
321
452
                                                    instance_id)
341
472
                raise exception.NetworkNotFound(network_id=net_id)
342
473
            if not self.q_conn.network_exists(project_id, net_id):
343
474
                raise exception.NetworkNotFound(network_id=net_id)
 
475
 
 
476
    def release_fixed_ip(self, context, address):
 
477
        # NOTE(bgh): deallocate_for_instance will take care of this..
 
478
        pass