~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to nova/compute/network.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
# Copyright [2010] [Anso Labs, LLC]
 
3
#
 
4
#    Licensed under the Apache License, Version 2.0 (the "License");
 
5
#    you may not use this file except in compliance with the License.
 
6
#    You may obtain a copy of the License at
 
7
#
 
8
#        http://www.apache.org/licenses/LICENSE-2.0
 
9
#
 
10
#    Unless required by applicable law or agreed to in writing, software
 
11
#    distributed under the License is distributed on an "AS IS" BASIS,
 
12
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
13
#    See the License for the specific language governing permissions and
 
14
#    limitations under the License.
 
15
 
 
16
"""
 
17
Classes for network control, including VLANs, DHCP, and IP allocation.
 
18
"""
 
19
 
 
20
import json
 
21
import logging
 
22
import os
 
23
 
 
24
# TODO(termie): clean up these imports
 
25
from nova import vendor
 
26
import IPy
 
27
 
 
28
from nova import datastore
 
29
import nova.exception
 
30
from nova.compute import exception
 
31
from nova import flags
 
32
from nova import utils
 
33
from nova.auth import users
 
34
 
 
35
import linux_net
 
36
 
 
37
FLAGS = flags.FLAGS
 
38
flags.DEFINE_string('net_libvirt_xml_template',
 
39
                    utils.abspath('compute/net.libvirt.xml.template'),
 
40
                    'Template file for libvirt networks')
 
41
flags.DEFINE_string('networks_path', utils.abspath('../networks'),
 
42
                    'Location to keep network config files')
 
43
flags.DEFINE_integer('public_vlan', 1, 'VLAN for public IP addresses')
 
44
flags.DEFINE_string('public_interface', 'vlan1', 'Interface for public IP addresses')
 
45
flags.DEFINE_string('bridge_dev', 'eth1',
 
46
                        'network device for bridges')
 
47
flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks')
 
48
flags.DEFINE_integer('vlan_end', 4093, 'Last VLAN for private networks')
 
49
flags.DEFINE_integer('network_size', 256, 'Number of addresses in each private subnet')
 
50
flags.DEFINE_string('public_range', '4.4.4.0/24', 'Public IP address block')
 
51
flags.DEFINE_string('private_range', '10.0.0.0/8', 'Private IP address block')
 
52
 
 
53
 
 
54
# HACK(vish): to delay _get_keeper() loading
 
55
def _get_keeper():
 
56
    if _get_keeper.keeper == None:
 
57
        _get_keeper.keeper = datastore.Keeper(prefix="net")
 
58
    return _get_keeper.keeper
 
59
_get_keeper.keeper = None
 
60
 
 
61
logging.getLogger().setLevel(logging.DEBUG)
 
62
 
 
63
# CLEANUP:
 
64
# TODO(ja): use singleton for usermanager instead of self.manager in vlanpool et al
 
65
# TODO(ja): does vlanpool "keeper" need to know the min/max - shouldn't FLAGS always win?
 
66
 
 
67
class Network(object):
 
68
    def __init__(self, *args, **kwargs):
 
69
        self.bridge_gets_ip = False
 
70
        try:
 
71
            os.makedirs(FLAGS.networks_path)
 
72
        except Exception, err:
 
73
            pass
 
74
        self.load(**kwargs)
 
75
 
 
76
    def to_dict(self):
 
77
        return {'vlan': self.vlan,
 
78
                'network': self.network_str,
 
79
                'hosts': self.hosts}
 
80
 
 
81
    def load(self, **kwargs):
 
82
        self.network_str = kwargs.get('network', "192.168.100.0/24")
 
83
        self.hosts = kwargs.get('hosts', {})
 
84
        self.vlan = kwargs.get('vlan', 100)
 
85
        self.name = "nova-%s" % (self.vlan)
 
86
        self.network = IPy.IP(self.network_str)
 
87
        self.gateway = self.network[1]
 
88
        self.netmask = self.network.netmask()
 
89
        self.broadcast = self.network.broadcast()
 
90
        self.bridge_name =  "br%s" % (self.vlan)
 
91
 
 
92
    def __str__(self):
 
93
        return json.dumps(self.to_dict())
 
94
 
 
95
    def __unicode__(self):
 
96
        return json.dumps(self.to_dict())
 
97
 
 
98
    @classmethod
 
99
    def from_dict(cls, args):
 
100
        for arg in args.keys():
 
101
            value = args[arg]
 
102
            del args[arg]
 
103
            args[str(arg)] = value
 
104
        self = cls(**args)
 
105
        return self
 
106
 
 
107
    @classmethod
 
108
    def from_json(cls, json_string):
 
109
        parsed = json.loads(json_string)
 
110
        return cls.from_dict(parsed)
 
111
 
 
112
    def range(self):
 
113
        for idx in range(3, len(self.network)-2):
 
114
            yield self.network[idx]
 
115
 
 
116
    def allocate_ip(self, user_id, mac):
 
117
        for ip in self.range():
 
118
            address = str(ip)
 
119
            if not address in self.hosts.keys():
 
120
                logging.debug("Allocating IP %s to %s" % (address, user_id))
 
121
                self.hosts[address] = {
 
122
                    "address" : address, "user_id" : user_id, 'mac' : mac
 
123
                }
 
124
                self.express(address=address)
 
125
                return address
 
126
        raise exception.NoMoreAddresses()
 
127
 
 
128
    def deallocate_ip(self, ip_str):
 
129
        if not ip_str in self.hosts.keys():
 
130
            raise exception.AddressNotAllocated()
 
131
        del self.hosts[ip_str]
 
132
        # TODO(joshua) SCRUB from the leases file somehow
 
133
        self.deexpress(address=ip_str)
 
134
 
 
135
    def list_addresses(self):
 
136
        for address in self.hosts.values():
 
137
            yield address
 
138
 
 
139
    def express(self, address=None):
 
140
        pass
 
141
 
 
142
    def deexpress(self, address=None):
 
143
        pass
 
144
 
 
145
 
 
146
class Vlan(Network):
 
147
    """
 
148
    VLAN configuration, that when expressed creates the vlan
 
149
 
 
150
    properties:
 
151
 
 
152
        vlan - integer (example: 42)
 
153
        bridge_dev - string (example: eth0)
 
154
    """
 
155
 
 
156
    def __init__(self, *args, **kwargs):
 
157
        super(Vlan, self).__init__(*args, **kwargs)
 
158
        self.bridge_dev = FLAGS.bridge_dev
 
159
 
 
160
    def express(self, address=None):
 
161
        super(Vlan, self).express(address=address)
 
162
        try:
 
163
            logging.debug("Starting VLAN inteface for %s network" % (self.vlan))
 
164
            linux_net.vlan_create(self)
 
165
        except:
 
166
            pass
 
167
 
 
168
 
 
169
class VirtNetwork(Vlan):
 
170
    """
 
171
    Virtual Network that can export libvirt configuration or express itself to
 
172
    create a bridge (with or without an IP address/netmask/gateway)
 
173
 
 
174
    properties:
 
175
        bridge_name - string (example value: br42)
 
176
        vlan - integer (example value: 42)
 
177
        bridge_gets_ip - boolean used during bridge creation
 
178
 
 
179
        if bridge_gets_ip then network address for bridge uses the properties:
 
180
            gateway
 
181
            broadcast
 
182
            netmask
 
183
    """
 
184
 
 
185
    def __init__(self, *args, **kwargs):
 
186
        super(VirtNetwork, self).__init__(*args, **kwargs)
 
187
 
 
188
    def virtXML(self):
 
189
        """ generate XML for libvirt network """
 
190
 
 
191
        libvirt_xml = open(FLAGS.net_libvirt_xml_template).read()
 
192
        xml_info = {'name' : self.name,
 
193
                    'bridge_name' : self.bridge_name,
 
194
                    'device' : "vlan%s" % (self.vlan),
 
195
                    'gateway' : self.gateway,
 
196
                    'netmask' : self.netmask,
 
197
                   }
 
198
        libvirt_xml = libvirt_xml % xml_info
 
199
        return libvirt_xml
 
200
 
 
201
    def express(self, address=None):
 
202
        """ creates a bridge device on top of the Vlan """
 
203
        super(VirtNetwork, self).express(address=address)
 
204
        try:
 
205
            logging.debug("Starting Bridge inteface for %s network" % (self.vlan))
 
206
            linux_net.bridge_create(self)
 
207
        except:
 
208
            pass
 
209
 
 
210
class DHCPNetwork(VirtNetwork):
 
211
    """
 
212
    properties:
 
213
        dhcp_listen_address: the ip of the gateway / dhcp host
 
214
        dhcp_range_start: the first ip to give out
 
215
        dhcp_range_end: the last ip to give out
 
216
    """
 
217
    def __init__(self, *args, **kwargs):
 
218
        super(DHCPNetwork, self).__init__(*args, **kwargs)
 
219
        logging.debug("Initing DHCPNetwork object...")
 
220
        self.bridge_gets_ip = True
 
221
        self.dhcp_listen_address = self.network[1]
 
222
        self.dhcp_range_start = self.network[3]
 
223
        self.dhcp_range_end = self.network[-2]
 
224
 
 
225
    def express(self, address=None):
 
226
        super(DHCPNetwork, self).express(address=address)
 
227
        if len(self.hosts.values()) > 0:
 
228
            logging.debug("Starting dnsmasq server for network with vlan %s" % self.vlan)
 
229
            linux_net.start_dnsmasq(self)
 
230
        else:
 
231
            logging.debug("Not launching dnsmasq cause I don't think we have any hosts.")
 
232
 
 
233
    def deexpress(self, address=None):
 
234
        # if this is the last address, stop dns
 
235
        super(DHCPNetwork, self).deexpress(address=address)
 
236
        if len(self.hosts.values()) == 0:
 
237
            linux_net.stop_dnsmasq(self)
 
238
        else:
 
239
            linux_net.start_dnsmasq(self)
 
240
 
 
241
 
 
242
class PrivateNetwork(DHCPNetwork):
 
243
    def __init__(self, **kwargs):
 
244
        super(PrivateNetwork, self).__init__(**kwargs)
 
245
        # self.express()
 
246
 
 
247
    def to_dict(self):
 
248
        return {'vlan': self.vlan,
 
249
                'network': self.network_str,
 
250
                'hosts': self.hosts}
 
251
 
 
252
    def express(self, *args, **kwargs):
 
253
        super(PrivateNetwork, self).express(*args, **kwargs)
 
254
 
 
255
 
 
256
 
 
257
class PublicNetwork(Network):
 
258
    def __init__(self, network="192.168.216.0/24", **kwargs):
 
259
        super(PublicNetwork, self).__init__(network=network, **kwargs)
 
260
        self.express()
 
261
 
 
262
    def allocate_ip(self, user_id, mac):
 
263
        for ip in self.range():
 
264
            address = str(ip)
 
265
            if not address in self.hosts.keys():
 
266
                logging.debug("Allocating IP %s to %s" % (address, user_id))
 
267
                self.hosts[address] = {
 
268
                    "address" : address, "user_id" : user_id, 'mac' : mac
 
269
                }
 
270
                self.express(address=address)
 
271
                return address
 
272
        raise exception.NoMoreAddresses()
 
273
 
 
274
    def deallocate_ip(self, ip_str):
 
275
        if not ip_str in self.hosts:
 
276
            raise exception.AddressNotAllocated()
 
277
        del self.hosts[ip_str]
 
278
        # TODO(joshua) SCRUB from the leases file somehow
 
279
        self.deexpress(address=ip_str)
 
280
 
 
281
    def associate_address(self, public_ip, private_ip, instance_id):
 
282
        if not public_ip in self.hosts:
 
283
            raise exception.AddressNotAllocated()
 
284
        for addr in self.hosts.values():
 
285
            if addr.has_key('private_ip') and addr['private_ip'] == private_ip:
 
286
                raise exception.AddressAlreadyAssociated()
 
287
        if self.hosts[public_ip].has_key('private_ip'):
 
288
            raise exception.AddressAlreadyAssociated()
 
289
        self.hosts[public_ip]['private_ip'] = private_ip
 
290
        self.hosts[public_ip]['instance_id'] = instance_id
 
291
        self.express(address=public_ip)
 
292
 
 
293
    def disassociate_address(self, public_ip):
 
294
        if not public_ip in self.hosts:
 
295
            raise exception.AddressNotAllocated()
 
296
        if not self.hosts[public_ip].has_key('private_ip'):
 
297
            raise exception.AddressNotAssociated()
 
298
        self.deexpress(public_ip)
 
299
        del self.hosts[public_ip]['private_ip']
 
300
        del self.hosts[public_ip]['instance_id']
 
301
        # TODO Express the removal
 
302
 
 
303
    def deexpress(self, address):
 
304
        addr = self.hosts[address]
 
305
        public_ip = addr['address']
 
306
        private_ip = addr['private_ip']
 
307
        linux_net.remove_rule("PREROUTING -t nat -d %s -j DNAT --to %s" % (public_ip, private_ip))
 
308
        linux_net.remove_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" % (private_ip, public_ip))
 
309
        linux_net.remove_rule("FORWARD -d %s -p icmp -j ACCEPT" % (private_ip))
 
310
        for (protocol, port) in [("tcp",80), ("tcp",22), ("udp",1194), ("tcp",443)]:
 
311
            linux_net.remove_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT" % (private_ip, protocol, port))
 
312
 
 
313
    def express(self, address=None):
 
314
        logging.debug("Todo - need to create IPTables natting entries for this net.")
 
315
        addresses = self.hosts.values()
 
316
        if address:
 
317
            addresses = [self.hosts[address]]
 
318
        for addr in addresses:
 
319
            if not addr.has_key('private_ip'):
 
320
                continue
 
321
            public_ip = addr['address']
 
322
            private_ip = addr['private_ip']
 
323
            linux_net.bind_public_ip(public_ip, FLAGS.public_interface)
 
324
            linux_net.confirm_rule("PREROUTING -t nat -d %s -j DNAT --to %s" % (public_ip, private_ip))
 
325
            linux_net.confirm_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" % (private_ip, public_ip))
 
326
            # TODO: Get these from the secgroup datastore entries
 
327
            linux_net.confirm_rule("FORWARD -d %s -p icmp -j ACCEPT" % (private_ip))
 
328
            for (protocol, port) in [("tcp",80), ("tcp",22), ("udp",1194), ("tcp",443)]:
 
329
                linux_net.confirm_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT" % (private_ip, protocol, port))
 
330
 
 
331
 
 
332
class NetworkPool(object):
 
333
    # TODO - Allocations need to be system global
 
334
 
 
335
    def __init__(self):
 
336
        self.network = IPy.IP(FLAGS.private_range)
 
337
        netsize = FLAGS.network_size
 
338
        if not netsize in [4,8,16,32,64,128,256,512,1024]:
 
339
            raise exception.NotValidNetworkSize()
 
340
        self.netsize = netsize
 
341
        self.startvlan = FLAGS.vlan_start
 
342
 
 
343
    def get_from_vlan(self, vlan):
 
344
        start = (vlan-self.startvlan) * self.netsize
 
345
        net_str = "%s-%s" % (self.network[start], self.network[start + self.netsize - 1])
 
346
        logging.debug("Allocating %s" % net_str)
 
347
        return net_str
 
348
 
 
349
 
 
350
class VlanPool(object):
 
351
    def __init__(self, **kwargs):
 
352
        self.start = FLAGS.vlan_start
 
353
        self.end = FLAGS.vlan_end
 
354
        self.vlans = kwargs.get('vlans', {})
 
355
        self.vlanpool = {}
 
356
        self.manager = users.UserManager.instance()
 
357
        for user_id, vlan in self.vlans.iteritems():
 
358
            self.vlanpool[vlan] = user_id
 
359
 
 
360
    def to_dict(self):
 
361
        return {'vlans': self.vlans}
 
362
 
 
363
    def __str__(self):
 
364
        return json.dumps(self.to_dict())
 
365
 
 
366
    def __unicode__(self):
 
367
        return json.dumps(self.to_dict())
 
368
 
 
369
    @classmethod
 
370
    def from_dict(cls, args):
 
371
        for arg in args.keys():
 
372
            value = args[arg]
 
373
            del args[arg]
 
374
            args[str(arg)] = value
 
375
        self = cls(**args)
 
376
        return self
 
377
 
 
378
    @classmethod
 
379
    def from_json(cls, json_string):
 
380
        parsed = json.loads(json_string)
 
381
        return cls.from_dict(parsed)
 
382
 
 
383
    def assign_vlan(self, user_id, vlan):
 
384
        logging.debug("Assigning vlan %s to user %s" % (vlan, user_id))
 
385
        self.vlans[user_id] = vlan
 
386
        self.vlanpool[vlan] = user_id
 
387
        return self.vlans[user_id]
 
388
 
 
389
    def next(self, user_id):
 
390
        for old_user_id, vlan in self.vlans.iteritems():
 
391
            if not self.manager.get_user(old_user_id):
 
392
                _get_keeper()["%s-default" % old_user_id] = {}
 
393
                del _get_keeper()["%s-default" % old_user_id]
 
394
                del self.vlans[old_user_id]
 
395
                return self.assign_vlan(user_id, vlan)
 
396
        vlans = self.vlanpool.keys()
 
397
        vlans.append(self.start)
 
398
        nextvlan = max(vlans) + 1
 
399
        if nextvlan == self.end:
 
400
            raise exception.AddressNotAllocated("Out of VLANs")
 
401
        return self.assign_vlan(user_id, nextvlan)
 
402
 
 
403
 
 
404
class NetworkController(object):
 
405
    """ The network controller is in charge of network connections  """
 
406
 
 
407
    def __init__(self, **kwargs):
 
408
        logging.debug("Starting up the network controller.")
 
409
        self.manager = users.UserManager.instance()
 
410
        self._pubnet = None
 
411
        if not _get_keeper()['vlans']:
 
412
            _get_keeper()['vlans'] = {}
 
413
        if not _get_keeper()['public']:
 
414
            _get_keeper()['public'] = {'vlan': FLAGS.public_vlan, 'network' : FLAGS.public_range}
 
415
        self.express()
 
416
 
 
417
    def reset(self):
 
418
        _get_keeper()['public'] = {'vlan': FLAGS.public_vlan, 'network': FLAGS.public_range }
 
419
        _get_keeper()['vlans'] = {}
 
420
        # TODO : Get rid of old interfaces, bridges, and IPTables rules.
 
421
 
 
422
    @property
 
423
    def public_net(self):
 
424
        if not self._pubnet:
 
425
            self._pubnet = PublicNetwork.from_dict(_get_keeper()['public'])
 
426
        self._pubnet.load(**_get_keeper()['public'])
 
427
        return self._pubnet
 
428
 
 
429
    @property
 
430
    def vlan_pool(self):
 
431
        return VlanPool.from_dict(_get_keeper()['vlans'])
 
432
 
 
433
    def get_network_from_name(self, network_name):
 
434
        net_dict = _get_keeper()[network_name]
 
435
        if net_dict:
 
436
            return PrivateNetwork.from_dict(net_dict)
 
437
        return None
 
438
 
 
439
    def get_public_ip_for_instance(self, instance_id):
 
440
        # FIXME: this should be a lookup - iteration won't scale
 
441
        for address_record in self.describe_addresses(type=PublicNetwork):
 
442
            if address_record.get(u'instance_id', 'free') == instance_id:
 
443
                return address_record[u'address']
 
444
 
 
445
    def get_users_network(self, user_id):
 
446
        """ get a user's private network, allocating one if needed """
 
447
 
 
448
        user = self.manager.get_user(user_id)
 
449
        if not user:
 
450
           raise Exception("User %s doesn't exist, uhoh." % user_id)
 
451
        usernet = self.get_network_from_name("%s-default" % user_id)
 
452
        if not usernet:
 
453
            pool = self.vlan_pool
 
454
            vlan = pool.next(user_id)
 
455
            private_pool = NetworkPool()
 
456
            network_str = private_pool.get_from_vlan(vlan)
 
457
            logging.debug("Constructing network %s and %s for %s" % (network_str, vlan, user_id))
 
458
            usernet = PrivateNetwork(
 
459
                network=network_str,
 
460
                vlan=vlan)
 
461
            _get_keeper()["%s-default" % user_id] = usernet.to_dict()
 
462
            _get_keeper()['vlans'] = pool.to_dict()
 
463
        return usernet
 
464
 
 
465
    def allocate_address(self, user_id, mac=None, type=PrivateNetwork):
 
466
        ip = None
 
467
        net_name = None
 
468
        if type == PrivateNetwork:
 
469
            net = self.get_users_network(user_id)
 
470
            ip = net.allocate_ip(user_id, mac)
 
471
            net_name = net.name
 
472
            _get_keeper()["%s-default" % user_id] = net.to_dict()
 
473
        else:
 
474
            net = self.public_net
 
475
            ip = net.allocate_ip(user_id, mac)
 
476
            net_name = net.name
 
477
            _get_keeper()['public'] = net.to_dict()
 
478
        return (ip, net_name)
 
479
 
 
480
    def deallocate_address(self, address):
 
481
        if address in self.public_net.network:
 
482
            net = self.public_net
 
483
            rv = net.deallocate_ip(str(address))
 
484
            _get_keeper()['public'] = net.to_dict()
 
485
            return rv
 
486
        for user in self.manager.get_users():
 
487
            if address in self.get_users_network(user.id).network:
 
488
                net = self.get_users_network(user.id)
 
489
                rv = net.deallocate_ip(str(address))
 
490
                _get_keeper()["%s-default" % user.id] = net.to_dict()
 
491
                return rv
 
492
        raise exception.AddressNotAllocated()
 
493
 
 
494
    def describe_addresses(self, type=PrivateNetwork):
 
495
        if type == PrivateNetwork:
 
496
            addresses = []
 
497
            for user in self.manager.get_users():
 
498
                addresses.extend(self.get_users_network(user.id).list_addresses())
 
499
            return addresses
 
500
        return self.public_net.list_addresses()
 
501
 
 
502
    def associate_address(self, address, private_ip, instance_id):
 
503
        net = self.public_net
 
504
        rv = net.associate_address(address, private_ip, instance_id)
 
505
        _get_keeper()['public'] = net.to_dict()
 
506
        return rv
 
507
 
 
508
    def disassociate_address(self, address):
 
509
        net = self.public_net
 
510
        rv = net.disassociate_address(address)
 
511
        _get_keeper()['public'] = net.to_dict()
 
512
        return rv
 
513
 
 
514
    def express(self,address=None):
 
515
        for user in self.manager.get_users():
 
516
            self.get_users_network(user.id).express()
 
517
 
 
518
    def report_state(self):
 
519
        pass
 
520