~hudson-openstack/nova/trunk

« back to all changes in this revision

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

  • Committer: Tarmac
  • Author(s): Dan Wendlandt, Brad Hall, danwent at gmail
  • Date: 2011-09-08 07:43:11 UTC
  • mfrom: (1476.2.39 nova-trunk)
  • Revision ID: tarmac-20110908074311-j8grm3qj79bx04ra
This code contains contains a new NetworkManager class that can leverage Quantum + Melange.  

Thierry suggested that I merge prop this to get feedback, even though it is too late to get a branch of this size in for D-4.  Feedback will help determine whether we can get this code in during the Diablo time frame or not.  This branch was developed stacked on top of the melange branch, which is still not merged, but I am merge-propping it without that branch so that the diff is correct.  

The vast majority of the code is in nova/network/quantum and is only invoked if the network manager is set to nova.network.quantum.QuantumManager

In addition to implementing networks with Quantum instead of the linux bridge, the QuantumManager also provides a new, more flexible model of associating vNICs with networks.  It supports the creation of networks that are specific to a project and networks that are "global".  When a VM is created, it gets a vNIC for each project network, as well as each global network.  For example, this could support giving a VM a "public" vNIC on a shared network and a "private" vNIC on a tenant-specific network.  

The branch also implements Tushar's 'os-create-server-ext' extension, so that the server create API call can indicate the set of networks the VM should be attached to (if this extension is used, it replaces the above mechanism for determining vNICs).  

QuantumManager can use either the traditional IPAM functionality in nova DB (i.e., the networks, fixed_ips tables) or melange.  Similar to FlatManager, QuantumManager does not currently support DHCP, gateway/NAT, or floating IPs.  

This branch requires additional testing before it is really ready for merge, but we thought it would be best to merge prop as soon as possible to identify any significant concerns people had. 

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
from nova import db
 
19
from nova import exception
 
20
from nova import flags
 
21
from nova import log as logging
 
22
from nova import manager
 
23
from nova.network import manager
 
24
from nova.network.quantum import quantum_connection
 
25
from nova import utils
 
26
 
 
27
LOG = logging.getLogger("nova.network.quantum.manager")
 
28
 
 
29
FLAGS = flags.FLAGS
 
30
 
 
31
flags.DEFINE_string('quantum_ipam_lib',
 
32
                    'nova.network.quantum.nova_ipam_lib',
 
33
                    "Indicates underlying IP address management library")
 
34
 
 
35
 
 
36
class QuantumManager(manager.FlatManager):
 
37
    """NetworkManager class that communicates with a Quantum service
 
38
       via a web services API to provision VM network connectivity.
 
39
 
 
40
       For IP Address management, QuantumManager can be configured to
 
41
       use either Nova's local DB or the Melange IPAM service.
 
42
 
 
43
       Currently, the QuantumManager does NOT support any of the 'gateway'
 
44
       functionality implemented by the Nova VlanManager, including:
 
45
            * floating IPs
 
46
            * DHCP
 
47
            * NAT gateway
 
48
 
 
49
       Support for these capabilities are targted for future releases.
 
50
    """
 
51
 
 
52
    def __init__(self, q_conn=None, ipam_lib=None, *args, **kwargs):
 
53
        """Initialize two key libraries, the connection to a
 
54
           Quantum service, and the library for implementing IPAM.
 
55
 
 
56
           Calls inherited FlatManager constructor.
 
57
        """
 
58
 
 
59
        if not q_conn:
 
60
            q_conn = quantum_connection.QuantumClientConnection()
 
61
        self.q_conn = q_conn
 
62
 
 
63
        if not ipam_lib:
 
64
            ipam_lib = FLAGS.quantum_ipam_lib
 
65
        self.ipam = utils.import_object(ipam_lib).get_ipam_lib(self)
 
66
 
 
67
        super(QuantumManager, self).__init__(*args, **kwargs)
 
68
 
 
69
    def create_networks(self, context, label, cidr, multi_host, num_networks,
 
70
                        network_size, cidr_v6, gateway_v6, bridge,
 
71
                        bridge_interface, dns1=None, dns2=None, uuid=None,
 
72
                        **kwargs):
 
73
        """Unlike other NetworkManagers, with QuantumManager, each
 
74
           create_networks calls should create only a single network.
 
75
 
 
76
           Two scenarios exist:
 
77
                - no 'uuid' is specified, in which case we contact
 
78
                  Quantum and create a new network.
 
79
                - an existing 'uuid' is specified, corresponding to
 
80
                  a Quantum network created out of band.
 
81
 
 
82
           In both cases, we initialize a subnet using the IPAM lib.
 
83
        """
 
84
        if num_networks != 1:
 
85
            raise Exception(_("QuantumManager requires that only one"
 
86
                              " network is created per call"))
 
87
        q_tenant_id = kwargs["project_id"] or FLAGS.quantum_default_tenant_id
 
88
        quantum_net_id = uuid
 
89
        if quantum_net_id:
 
90
            if not self.q_conn.network_exists(q_tenant_id, quantum_net_id):
 
91
                    raise Exception(_("Unable to find existing quantum " \
 
92
                        " network for tenant '%(q_tenant_id)s' with "
 
93
                        "net-id '%(quantum_net_id)s'" % locals()))
 
94
        else:
 
95
            # otherwise, create network from default quantum pool
 
96
            quantum_net_id = self.q_conn.create_network(q_tenant_id, label)
 
97
 
 
98
        ipam_tenant_id = kwargs.get("project_id", None)
 
99
        priority = kwargs.get("priority", 0)
 
100
        self.ipam.create_subnet(context, label, ipam_tenant_id, quantum_net_id,
 
101
                            priority, cidr, gateway_v6, cidr_v6, dns1, dns2)
 
102
 
 
103
    def delete_network(self, context, fixed_range):
 
104
        """Lookup network by IPv4 cidr, delete both the IPAM
 
105
           subnet and the corresponding Quantum network.
 
106
        """
 
107
        project_id = context.project_id
 
108
        quantum_net_id = self.ipam.get_network_id_by_cidr(
 
109
                                    context, fixed_range, project_id)
 
110
        self.ipam.delete_subnets_by_net_id(context, quantum_net_id,
 
111
                                                            project_id)
 
112
        q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
 
113
        self.q_conn.delete_network(q_tenant_id, quantum_net_id)
 
114
 
 
115
    def allocate_for_instance(self, context, **kwargs):
 
116
        """Called by compute when it is creating a new VM.
 
117
 
 
118
           There are three key tasks:
 
119
                - Determine the number and order of vNICs to create
 
120
                - Allocate IP addresses
 
121
                - Create ports on a Quantum network and attach vNICs.
 
122
 
 
123
           We support two approaches to determining vNICs:
 
124
                - By default, a VM gets a vNIC for any network belonging
 
125
                  to the VM's project, and a vNIC for any "global" network
 
126
                  that has a NULL project_id.  vNIC order is determined
 
127
                  by the network's 'priority' field.
 
128
                - If the 'os-create-server-ext' was used to create the VM,
 
129
                  only the networks in 'requested_networks' are used to
 
130
                  create vNICs, and the vNIC order is determiend by the
 
131
                  order in the requested_networks array.
 
132
 
 
133
           For each vNIC, use the FlatManager to create the entries
 
134
           in the virtual_interfaces table, contact Quantum to
 
135
           create a port and attachment the vNIC, and use the IPAM
 
136
           lib to allocate IP addresses.
 
137
        """
 
138
        instance_id = kwargs.pop('instance_id')
 
139
        instance_type_id = kwargs['instance_type_id']
 
140
        host = kwargs.pop('host')
 
141
        project_id = kwargs.pop('project_id')
 
142
        LOG.debug(_("network allocations for instance %s"), instance_id)
 
143
 
 
144
        requested_networks = kwargs.get('requested_networks')
 
145
 
 
146
        if requested_networks:
 
147
            net_proj_pairs = [(net_id, project_id) \
 
148
                for (net_id, _i) in requested_networks]
 
149
        else:
 
150
            net_proj_pairs = self.ipam.get_project_and_global_net_ids(context,
 
151
                                                                project_id)
 
152
 
 
153
        # Create a port via quantum and attach the vif
 
154
        for (quantum_net_id, project_id) in net_proj_pairs:
 
155
 
 
156
            # FIXME(danwent): We'd like to have the manager be
 
157
            # completely decoupled from the nova networks table.
 
158
            # However, other parts of nova sometimes go behind our
 
159
            # back and access network data directly from the DB.  So
 
160
            # for now, the quantum manager knows that there is a nova
 
161
            # networks DB table and accesses it here.  updating the
 
162
            # virtual_interfaces table to use UUIDs would be one
 
163
            # solution, but this would require significant work
 
164
            # elsewhere.
 
165
            admin_context = context.elevated()
 
166
            network_ref = db.network_get_by_uuid(admin_context,
 
167
                                                 quantum_net_id)
 
168
 
 
169
            vif_rec = manager.FlatManager.add_virtual_interface(self,
 
170
                                  context, instance_id, network_ref['id'])
 
171
 
 
172
            # talk to Quantum API to create and attach port.
 
173
            q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
 
174
            self.q_conn.create_and_attach_port(q_tenant_id, quantum_net_id,
 
175
                                               vif_rec['uuid'])
 
176
            self.ipam.allocate_fixed_ip(context, project_id, quantum_net_id,
 
177
                                        vif_rec)
 
178
 
 
179
        return self.get_instance_nw_info(context, instance_id,
 
180
                                         instance_type_id, host)
 
181
 
 
182
    def get_instance_nw_info(self, context, instance_id,
 
183
                                instance_type_id, host):
 
184
        """This method is used by compute to fetch all network data
 
185
           that should be used when creating the VM.
 
186
 
 
187
           The method simply loops through all virtual interfaces
 
188
           stored in the nova DB and queries the IPAM lib to get
 
189
           the associated IP data.
 
190
 
 
191
           The format of returned data is 'defined' by the initial
 
192
           set of NetworkManagers found in nova/network/manager.py .
 
193
           Ideally this 'interface' will be more formally defined
 
194
           in the future.
 
195
        """
 
196
        network_info = []
 
197
        instance = db.instance_get(context, instance_id)
 
198
        project_id = instance.project_id
 
199
 
 
200
        admin_context = context.elevated()
 
201
        vifs = db.virtual_interface_get_by_instance(admin_context,
 
202
                                                    instance_id)
 
203
        for vif in vifs:
 
204
            q_tenant_id = project_id
 
205
            ipam_tenant_id = project_id
 
206
            net_id, port_id = self.q_conn.get_port_by_attachment(q_tenant_id,
 
207
                                                                 vif['uuid'])
 
208
            if not net_id:
 
209
                q_tenant_id = FLAGS.quantum_default_tenant_id
 
210
                ipam_tenant_id = None
 
211
                net_id, port_id = self.q_conn.get_port_by_attachment(
 
212
                                             q_tenant_id, vif['uuid'])
 
213
            if not net_id:
 
214
                # TODO(bgh): We need to figure out a way to tell if we
 
215
                # should actually be raising this exception or not.
 
216
                # In the case that a VM spawn failed it may not have
 
217
                # attached the vif and raising the exception here
 
218
                # prevents deletion of the VM.  In that case we should
 
219
                # probably just log, continue, and move on.
 
220
                raise Exception(_("No network for for virtual interface %s") %
 
221
                                vif['uuid'])
 
222
            (v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context,
 
223
                                        ipam_tenant_id, net_id)
 
224
            v4_ips = self.ipam.get_v4_ips_by_interface(context,
 
225
                                        net_id, vif['uuid'],
 
226
                                        project_id=ipam_tenant_id)
 
227
            v6_ips = self.ipam.get_v6_ips_by_interface(context,
 
228
                                        net_id, vif['uuid'],
 
229
                                        project_id=ipam_tenant_id)
 
230
 
 
231
            quantum_net_id = v4_subnet['network_id'] or v6_subnet['network_id']
 
232
 
 
233
            def ip_dict(ip, subnet):
 
234
                return {
 
235
                    "ip": ip,
 
236
                    "netmask": subnet["netmask"],
 
237
                    "enabled": "1"}
 
238
 
 
239
            network_dict = {
 
240
                'cidr': v4_subnet['cidr'],
 
241
                'injected': True,
 
242
                'multi_host': False}
 
243
 
 
244
            info = {
 
245
                'gateway': v4_subnet['gateway'],
 
246
                'dhcp_server': v4_subnet['gateway'],
 
247
                'broadcast': v4_subnet['broadcast'],
 
248
                'mac': vif['address'],
 
249
                'vif_uuid': vif['uuid'],
 
250
                'dns': [],
 
251
                'ips': [ip_dict(ip, v4_subnet) for ip in v4_ips]}
 
252
 
 
253
            if v6_subnet:
 
254
                if v6_subnet['cidr']:
 
255
                    network_dict['cidr_v6'] = v6_subnet['cidr']
 
256
                    info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips]
 
257
 
 
258
                if v6_subnet['gateway']:
 
259
                    info['gateway6'] = v6_subnet['gateway']
 
260
 
 
261
            dns_dict = {}
 
262
            for s in [v4_subnet, v6_subnet]:
 
263
                for k in ['dns1', 'dns2']:
 
264
                    if s and s[k]:
 
265
                        dns_dict[s[k]] = None
 
266
            info['dns'] = [d for d in dns_dict.keys()]
 
267
 
 
268
            network_info.append((network_dict, info))
 
269
        return network_info
 
270
 
 
271
    def deallocate_for_instance(self, context, **kwargs):
 
272
        """Called when a VM is terminated.  Loop through each virtual
 
273
           interface in the Nova DB and remove the Quantum port and
 
274
           clear the IP allocation using the IPAM.  Finally, remove
 
275
           the virtual interfaces from the Nova DB.
 
276
        """
 
277
        instance_id = kwargs.get('instance_id')
 
278
        project_id = kwargs.pop('project_id', None)
 
279
 
 
280
        admin_context = context.elevated()
 
281
        vifs = db.virtual_interface_get_by_instance(admin_context,
 
282
                                                    instance_id)
 
283
        for vif_ref in vifs:
 
284
            interface_id = vif_ref['uuid']
 
285
            q_tenant_id = project_id
 
286
            ipam_tenant_id = project_id
 
287
            (net_id, port_id) = self.q_conn.get_port_by_attachment(q_tenant_id,
 
288
                                            interface_id)
 
289
            if not net_id:
 
290
                q_tenant_id = FLAGS.quantum_default_tenant_id
 
291
                ipam_tenant_id = None
 
292
                (net_id, port_id) = self.q_conn.get_port_by_attachment(
 
293
                                        q_tenant_id, interface_id)
 
294
            if not net_id:
 
295
                LOG.error("Unable to find port with attachment: %s" %
 
296
                          (interface_id))
 
297
                continue
 
298
            self.q_conn.detach_and_delete_port(q_tenant_id,
 
299
                                               net_id, port_id)
 
300
 
 
301
            self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id,
 
302
                                            net_id, vif_ref)
 
303
 
 
304
        try:
 
305
            db.virtual_interface_delete_by_instance(admin_context,
 
306
                                                    instance_id)
 
307
        except exception.InstanceNotFound:
 
308
            LOG.error(_("Attempted to deallocate non-existent instance: %s" %
 
309
                        (instance_id)))
 
310
 
 
311
    def validate_networks(self, context, networks):
 
312
        """Validates that this tenant has quantum networks with the associated
 
313
           UUIDs.  This is called by the 'os-create-server-ext' API extension
 
314
           code so that we can return an API error code to the caller if they
 
315
           request an invalid network.
 
316
        """
 
317
        if networks is None:
 
318
            return
 
319
 
 
320
        project_id = context.project_id
 
321
        for (net_id, _i) in networks:
 
322
            self.ipam.verify_subnet_exists(context, project_id, net_id)
 
323
            if not self.q_conn.network_exists(project_id, net_id):
 
324
                raise exception.NetworkNotFound(network_id=net_id)