Merge lp:~vishvananda/nova/network-refactor into lp:~hudson-openstack/nova/trunk

Proposed by Vish Ishaya
Status: Merged
Approved by: Jesse Andrews
Approved revision: 229
Merged at revision: 230
Proposed branch: lp:~vishvananda/nova/network-refactor
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 1437 lines (+411/-260)
8 files modified
bin/nova-dhcpbridge (+4/-3)
nova/endpoint/cloud.py (+5/-5)
nova/network/exception.py (+9/-1)
nova/network/linux_net.py (+69/-41)
nova/network/model.py (+135/-76)
nova/network/service.py (+25/-12)
nova/network/vpn.py (+26/-15)
nova/tests/network_unittest.py (+138/-107)
To merge this branch: bzr merge lp:~vishvananda/nova/network-refactor
Reviewer Review Type Date Requested Status
Jesse Andrews (community) Approve
Jay Pipes (community) Approve
Review via email: mp+32274@code.launchpad.net

Commit message

Improves pep8 compliance and pylint score in network code.

Description of the change

Mostly a whole bunch of pep and pylint cleanup, but also fixes the ordering of commands on ip release so that ips are reused.

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote :

This is excellent work, Vish. In particular, I really like how you cleaned up (and made more explicit and descriptive) the way that the BaseNetwork and its subclasses provide information on reserved IP addresses. The way I had done it before was sloppy :)

Nice job.

review: Approve
Revision history for this message
Jesse Andrews (anotherjesse) wrote :

lgtm, we are using this already inside nebula

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/nova-dhcpbridge'
2--- bin/nova-dhcpbridge 2010-08-08 02:51:17 +0000
3+++ bin/nova-dhcpbridge 2010-08-10 22:55:54 +0000
4@@ -56,7 +56,7 @@
5
6
7 def del_lease(_mac, ip, _hostname, _interface):
8- """Remove the leased IP from the databases."""
9+ """Called when a lease expires."""
10 if FLAGS.fake_rabbit:
11 service.VlanNetworkService().release_ip(ip)
12 else:
13@@ -70,8 +70,9 @@
14 net = model.get_network_by_interface(interface)
15 res = ""
16 for host_name in net.hosts:
17- res += "%s\n" % linux_net.hostDHCP(net, host_name,
18- net.hosts[host_name])
19+ res += "%s\n" % linux_net.host_dhcp(net,
20+ host_name,
21+ net.hosts[host_name])
22 return res
23
24
25
26=== modified file 'nova/endpoint/cloud.py'
27--- nova/endpoint/cloud.py 2010-08-08 18:40:03 +0000
28+++ nova/endpoint/cloud.py 2010-08-10 22:55:54 +0000
29@@ -103,7 +103,7 @@
30 result = {}
31 for instance in self.instdir.all:
32 if instance['project_id'] == project_id:
33- line = '%s slots=%d' % (instance['private_dns_name'],
34+ line = '%s slots=%d' % (instance['private_dns_name'],
35 INSTANCE_TYPES[instance['instance_type']]['vcpus'])
36 if instance['key_name'] in result:
37 result[instance['key_name']].append(line)
38@@ -423,7 +423,7 @@
39 i['key_name'] = instance.get('key_name', None)
40 if context.user.is_admin():
41 i['key_name'] = '%s (%s, %s)' % (i['key_name'],
42- instance.get('project_id', None),
43+ instance.get('project_id', None),
44 instance.get('node_name', ''))
45 i['product_codes_set'] = self._convert_to_set(
46 instance.get('product_codes', None), 'product_code')
47@@ -560,15 +560,15 @@
48 # TODO: Get the real security group of launch in here
49 security_group = "default"
50 for num in range(int(kwargs['max_count'])):
51- vpn = False
52+ is_vpn = False
53 if image_id == FLAGS.vpn_image_id:
54- vpn = True
55+ is_vpn = True
56 allocate_result = yield rpc.call(network_topic,
57 {"method": "allocate_fixed_ip",
58 "args": {"user_id": context.user.id,
59 "project_id": context.project.id,
60 "security_group": security_group,
61- "vpn": vpn}})
62+ "is_vpn": is_vpn}})
63 allocate_data = allocate_result['result']
64 inst = self.instdir.new()
65 inst['image_id'] = image_id
66
67=== modified file 'nova/network/exception.py'
68--- nova/network/exception.py 2010-08-03 21:31:47 +0000
69+++ nova/network/exception.py 2010-08-10 22:55:54 +0000
70@@ -24,17 +24,25 @@
71
72
73 class NoMoreAddresses(Error):
74+ """No More Addresses are available in the network"""
75 pass
76
77+
78 class AddressNotAllocated(Error):
79+ """The specified address has not been allocated"""
80 pass
81
82+
83 class AddressAlreadyAssociated(Error):
84+ """The specified address has already been associated"""
85 pass
86
87+
88 class AddressNotAssociated(Error):
89+ """The specified address is not associated"""
90 pass
91
92+
93 class NotValidNetworkSize(Error):
94+ """The network size is not valid"""
95 pass
96-
97
98=== modified file 'nova/network/linux_net.py'
99--- nova/network/linux_net.py 2010-08-03 21:31:47 +0000
100+++ nova/network/linux_net.py 2010-08-10 22:55:54 +0000
101@@ -15,85 +15,102 @@
102 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
103 # License for the specific language governing permissions and limitations
104 # under the License.
105+"""
106+Implements vlans, bridges, and iptables rules using linux utilities.
107+"""
108
109 import logging
110 import signal
111 import os
112-import subprocess
113
114 # todo(ja): does the definition of network_path belong here?
115
116+from nova import flags
117 from nova import utils
118
119-from nova import flags
120-FLAGS=flags.FLAGS
121+FLAGS = flags.FLAGS
122
123 flags.DEFINE_string('dhcpbridge_flagfile',
124 '/etc/nova/nova-dhcpbridge.conf',
125 'location of flagfile for dhcpbridge')
126
127+
128 def execute(cmd, addl_env=None):
129+ """Wrapper around utils.execute for fake_network"""
130 if FLAGS.fake_network:
131- logging.debug("FAKE NET: %s" % cmd)
132+ logging.debug("FAKE NET: %s", cmd)
133 return "fake", 0
134 else:
135 return utils.execute(cmd, addl_env=addl_env)
136
137+
138 def runthis(desc, cmd):
139+ """Wrapper around utils.runthis for fake_network"""
140 if FLAGS.fake_network:
141 return execute(cmd)
142 else:
143- return utils.runthis(desc,cmd)
144-
145-def Popen(cmd):
146- if FLAGS.fake_network:
147- execute(' '.join(cmd))
148- else:
149- subprocess.Popen(cmd)
150+ return utils.runthis(desc, cmd)
151
152
153 def device_exists(device):
154- (out, err) = execute("ifconfig %s" % device)
155+ """Check if ethernet device exists"""
156+ (_out, err) = execute("ifconfig %s" % device)
157 return not err
158
159+
160 def confirm_rule(cmd):
161+ """Delete and re-add iptables rule"""
162 execute("sudo iptables --delete %s" % (cmd))
163 execute("sudo iptables -I %s" % (cmd))
164
165+
166 def remove_rule(cmd):
167+ """Remove iptables rule"""
168 execute("sudo iptables --delete %s" % (cmd))
169
170-def bind_public_ip(ip, interface):
171- runthis("Binding IP to interface: %s", "sudo ip addr add %s dev %s" % (ip, interface))
172-
173-def unbind_public_ip(ip, interface):
174- runthis("Binding IP to interface: %s", "sudo ip addr del %s dev %s" % (ip, interface))
175+
176+def bind_public_ip(public_ip, interface):
177+ """Bind ip to an interface"""
178+ runthis("Binding IP to interface: %s",
179+ "sudo ip addr add %s dev %s" % (public_ip, interface))
180+
181+
182+def unbind_public_ip(public_ip, interface):
183+ """Unbind a public ip from an interface"""
184+ runthis("Binding IP to interface: %s",
185+ "sudo ip addr del %s dev %s" % (public_ip, interface))
186+
187
188 def vlan_create(net):
189- """ create a vlan on on a bridge device unless vlan already exists """
190+ """Create a vlan on on a bridge device unless vlan already exists"""
191 if not device_exists("vlan%s" % net['vlan']):
192 logging.debug("Starting VLAN inteface for %s network", (net['vlan']))
193 execute("sudo vconfig set_name_type VLAN_PLUS_VID_NO_PAD")
194 execute("sudo vconfig add %s %s" % (FLAGS.bridge_dev, net['vlan']))
195 execute("sudo ifconfig vlan%s up" % (net['vlan']))
196
197+
198 def bridge_create(net):
199- """ create a bridge on a vlan unless it already exists """
200+ """Create a bridge on a vlan unless it already exists"""
201 if not device_exists(net['bridge_name']):
202 logging.debug("Starting Bridge inteface for %s network", (net['vlan']))
203 execute("sudo brctl addbr %s" % (net['bridge_name']))
204 execute("sudo brctl setfd %s 0" % (net.bridge_name))
205 # execute("sudo brctl setageing %s 10" % (net.bridge_name))
206 execute("sudo brctl stp %s off" % (net['bridge_name']))
207- execute("sudo brctl addif %s vlan%s" % (net['bridge_name'], net['vlan']))
208+ execute("sudo brctl addif %s vlan%s" % (net['bridge_name'],
209+ net['vlan']))
210 if net.bridge_gets_ip:
211 execute("sudo ifconfig %s %s broadcast %s netmask %s up" % \
212 (net['bridge_name'], net.gateway, net.broadcast, net.netmask))
213- confirm_rule("FORWARD --in-interface %s -j ACCEPT" % (net['bridge_name']))
214+ confirm_rule("FORWARD --in-interface %s -j ACCEPT" %
215+ (net['bridge_name']))
216 else:
217 execute("sudo ifconfig %s up" % net['bridge_name'])
218
219-def dnsmasq_cmd(net):
220+
221+def _dnsmasq_cmd(net):
222+ """Builds dnsmasq command"""
223 cmd = ['sudo -E dnsmasq',
224 ' --strict-order',
225 ' --bind-interfaces',
226@@ -101,42 +118,48 @@
227 ' --pid-file=%s' % dhcp_file(net['vlan'], 'pid'),
228 ' --listen-address=%s' % net.dhcp_listen_address,
229 ' --except-interface=lo',
230- ' --dhcp-range=%s,static,600s' % (net.dhcp_range_start),
231+ ' --dhcp-range=%s,static,600s' % net.dhcp_range_start,
232 ' --dhcp-hostsfile=%s' % dhcp_file(net['vlan'], 'conf'),
233 ' --dhcp-script=%s' % bin_file('nova-dhcpbridge'),
234 ' --leasefile-ro']
235 return ''.join(cmd)
236
237-def hostDHCP(network, host, mac):
238- idx = host.split(".")[-1] # Logically, the idx of instances they've launched in this net
239+
240+def host_dhcp(network, host, mac):
241+ """Return a host string for a network, host, and mac"""
242+ # Logically, the idx of instances they've launched in this net
243+ idx = host.split(".")[-1]
244 return "%s,%s-%s-%s.novalocal,%s" % \
245 (mac, network['user_id'], network['vlan'], idx, host)
246
247-# todo(ja): if the system has restarted or pid numbers have wrapped
248+
249+# TODO(ja): if the system has restarted or pid numbers have wrapped
250 # then you cannot be certain that the pid refers to the
251 # dnsmasq. As well, sending a HUP only reloads the hostfile,
252 # so any configuration options (like dchp-range, vlan, ...)
253 # aren't reloaded
254 def start_dnsmasq(network):
255- """ (re)starts a dnsmasq server for a given network
256+ """(Re)starts a dnsmasq server for a given network
257
258 if a dnsmasq instance is already running then send a HUP
259 signal causing it to reload, otherwise spawn a new instance
260 """
261 with open(dhcp_file(network['vlan'], 'conf'), 'w') as f:
262 for host_name in network.hosts:
263- f.write("%s\n" % hostDHCP(network, host_name, network.hosts[host_name]))
264+ f.write("%s\n" % host_dhcp(network,
265+ host_name,
266+ network.hosts[host_name]))
267
268 pid = dnsmasq_pid_for(network)
269
270 # if dnsmasq is already running, then tell it to reload
271 if pid:
272- # todo(ja): use "/proc/%d/cmdline" % (pid) to determine if pid refers
273+ # TODO(ja): use "/proc/%d/cmdline" % (pid) to determine if pid refers
274 # correct dnsmasq process
275 try:
276 os.kill(pid, signal.SIGHUP)
277- except Exception, e:
278- logging.debug("Hupping dnsmasq threw %s", e)
279+ except Exception as exc: # pylint: disable=W0703
280+ logging.debug("Hupping dnsmasq threw %s", exc)
281
282 # otherwise delete the existing leases file and start dnsmasq
283 lease_file = dhcp_file(network['vlan'], 'leases')
284@@ -146,31 +169,37 @@
285 # FLAGFILE and DNSMASQ_INTERFACE in env
286 env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile,
287 'DNSMASQ_INTERFACE': network['bridge_name']}
288- execute(dnsmasq_cmd(network), addl_env=env)
289+ execute(_dnsmasq_cmd(network), addl_env=env)
290+
291
292 def stop_dnsmasq(network):
293- """ stops the dnsmasq instance for a given network """
294+ """Stops the dnsmasq instance for a given network"""
295 pid = dnsmasq_pid_for(network)
296
297 if pid:
298 try:
299 os.kill(pid, signal.SIGTERM)
300- except Exception, e:
301- logging.debug("Killing dnsmasq threw %s", e)
302+ except Exception as exc: # pylint: disable=W0703
303+ logging.debug("Killing dnsmasq threw %s", exc)
304+
305
306 def dhcp_file(vlan, kind):
307- """ return path to a pid, leases or conf file for a vlan """
308+ """Return path to a pid, leases or conf file for a vlan"""
309
310 return os.path.abspath("%s/nova-%s.%s" % (FLAGS.networks_path, vlan, kind))
311
312+
313 def bin_file(script):
314+ """Return the absolute path to scipt in the bin directory"""
315 return os.path.abspath(os.path.join(__file__, "../../../bin", script))
316
317+
318 def dnsmasq_pid_for(network):
319- """ the pid for prior dnsmasq instance for a vlan,
320- returns None if no pid file exists
321-
322- if machine has rebooted pid might be incorrect (caller should check)
323+ """Returns he pid for prior dnsmasq instance for a vlan
324+
325+ Returns None if no pid file exists
326+
327+ If machine has rebooted pid might be incorrect (caller should check)
328 """
329
330 pid_file = dhcp_file(network['vlan'], 'pid')
331@@ -178,4 +207,3 @@
332 if os.path.exists(pid_file):
333 with open(pid_file, 'r') as f:
334 return int(f.read())
335-
336
337=== modified file 'nova/network/model.py'
338--- nova/network/model.py 2010-08-08 20:20:50 +0000
339+++ nova/network/model.py 2010-08-10 22:55:54 +0000
340@@ -57,7 +57,8 @@
341
342
343 class Vlan(datastore.BasicModel):
344- def __init__(self, project, vlan):
345+ """Tracks vlans assigned to project it the datastore"""
346+ def __init__(self, project, vlan): # pylint: disable=W0231
347 """
348 Since we don't want to try and find a vlan by its identifier,
349 but by a project id, we don't call super-init.
350@@ -67,10 +68,12 @@
351
352 @property
353 def identifier(self):
354+ """Datastore identifier"""
355 return "%s:%s" % (self.project_id, self.vlan_id)
356
357 @classmethod
358 def create(cls, project, vlan):
359+ """Create a Vlan object"""
360 instance = cls(project, vlan)
361 instance.save()
362 return instance
363@@ -78,6 +81,7 @@
364 @classmethod
365 @datastore.absorb_connection_error
366 def lookup(cls, project):
367+ """Returns object by project if it exists in datastore or None"""
368 set_name = cls._redis_set_name(cls.__name__)
369 vlan = datastore.Redis.instance().hget(set_name, project)
370 if vlan:
371@@ -88,19 +92,19 @@
372 @classmethod
373 @datastore.absorb_connection_error
374 def dict_by_project(cls):
375- """a hash of project:vlan"""
376+ """A hash of project:vlan"""
377 set_name = cls._redis_set_name(cls.__name__)
378- return datastore.Redis.instance().hgetall(set_name)
379+ return datastore.Redis.instance().hgetall(set_name) or {}
380
381 @classmethod
382 @datastore.absorb_connection_error
383 def dict_by_vlan(cls):
384- """a hash of vlan:project"""
385+ """A hash of vlan:project"""
386 set_name = cls._redis_set_name(cls.__name__)
387 retvals = {}
388- hashset = datastore.Redis.instance().hgetall(set_name)
389- for val in hashset.keys():
390- retvals[hashset[val]] = val
391+ hashset = datastore.Redis.instance().hgetall(set_name) or {}
392+ for (key, val) in hashset.iteritems():
393+ retvals[val] = key
394 return retvals
395
396 @classmethod
397@@ -119,40 +123,47 @@
398 default way of saving into "vlan:ID" and adding to a set of "vlans".
399 """
400 set_name = self._redis_set_name(self.__class__.__name__)
401- datastore.Redis.instance().hset(set_name, self.project_id, self.vlan_id)
402+ datastore.Redis.instance().hset(set_name,
403+ self.project_id,
404+ self.vlan_id)
405
406 @datastore.absorb_connection_error
407 def destroy(self):
408+ """Removes the object from the datastore"""
409 set_name = self._redis_set_name(self.__class__.__name__)
410 datastore.Redis.instance().hdel(set_name, self.project_id)
411
412 def subnet(self):
413+ """Returns a string containing the subnet"""
414 vlan = int(self.vlan_id)
415 network = IPy.IP(FLAGS.private_range)
416- start = (vlan-FLAGS.vlan_start) * FLAGS.network_size
417+ start = (vlan - FLAGS.vlan_start) * FLAGS.network_size
418 # minus one for the gateway.
419 return "%s-%s" % (network[start],
420 network[start + FLAGS.network_size - 1])
421
422+
423 # CLEANUP:
424 # TODO(ja): Save the IPs at the top of each subnet for cloudpipe vpn clients
425-# TODO(ja): does vlanpool "keeper" need to know the min/max -
426+# TODO(ja): does vlanpool "keeper" need to know the min/max -
427 # shouldn't FLAGS always win?
428-# TODO(joshua): Save the IPs at the top of each subnet for cloudpipe vpn clients
429-
430 class BaseNetwork(datastore.BasicModel):
431+ """Implements basic logic for allocating ips in a network"""
432 override_type = 'network'
433- NUM_STATIC_IPS = 3 # Network, Gateway, and CloudPipe
434
435 @property
436 def identifier(self):
437+ """Datastore identifier"""
438 return self.network_id
439
440 def default_state(self):
441+ """Default values for new objects"""
442 return {'network_id': self.network_id, 'network_str': self.network_str}
443
444 @classmethod
445+ # pylint: disable=R0913
446 def create(cls, user_id, project_id, security_group, vlan, network_str):
447+ """Create a BaseNetwork object"""
448 network_id = "%s:%s" % (project_id, security_group)
449 net = cls(network_id, network_str)
450 net['user_id'] = user_id
451@@ -170,93 +181,124 @@
452
453 @property
454 def network(self):
455+ """Returns a string representing the network"""
456 return IPy.IP(self['network_str'])
457
458 @property
459 def netmask(self):
460+ """Returns the netmask of this network"""
461 return self.network.netmask()
462
463 @property
464 def gateway(self):
465+ """Returns the network gateway address"""
466 return self.network[1]
467
468 @property
469 def broadcast(self):
470+ """Returns the network broadcast address"""
471 return self.network.broadcast()
472
473 @property
474 def bridge_name(self):
475+ """Returns the bridge associated with this network"""
476 return "br%s" % (self["vlan"])
477
478 @property
479 def user(self):
480+ """Returns the user associated with this network"""
481 return manager.AuthManager().get_user(self['user_id'])
482
483 @property
484 def project(self):
485+ """Returns the project associated with this network"""
486 return manager.AuthManager().get_project(self['project_id'])
487
488 @property
489 def _hosts_key(self):
490+ """Datastore key where hosts are stored"""
491 return "network:%s:hosts" % (self['network_str'])
492
493 @property
494 def hosts(self):
495+ """Returns a hash of all hosts allocated in this network"""
496 return datastore.Redis.instance().hgetall(self._hosts_key) or {}
497
498 def _add_host(self, _user_id, _project_id, host, target):
499+ """Add a host to the datastore"""
500 datastore.Redis.instance().hset(self._hosts_key, host, target)
501
502 def _rem_host(self, host):
503+ """Remove a host from the datastore"""
504 datastore.Redis.instance().hdel(self._hosts_key, host)
505
506 @property
507 def assigned(self):
508+ """Returns a list of all assigned keys"""
509 return datastore.Redis.instance().hkeys(self._hosts_key)
510
511 @property
512 def available(self):
513- # the .2 address is always CloudPipe
514- # and the top <n> are for vpn clients
515- num_ips = self.num_static_ips
516- num_clients = FLAGS.cnt_vpn_clients
517- for idx in range(num_ips, len(self.network)-(1 + num_clients)):
518+ """Returns a list of all available addresses in the network"""
519+ for idx in range(self.num_bottom_reserved_ips,
520+ len(self.network) - self.num_top_reserved_ips):
521 address = str(self.network[idx])
522 if not address in self.hosts.keys():
523 yield address
524
525 @property
526- def num_static_ips(self):
527- return BaseNetwork.NUM_STATIC_IPS
528+ def num_bottom_reserved_ips(self):
529+ """Returns number of ips reserved at the bottom of the range"""
530+ return 2 # Network, Gateway
531+
532+ @property
533+ def num_top_reserved_ips(self):
534+ """Returns number of ips reserved at the top of the range"""
535+ return 1 # Broadcast
536
537 def allocate_ip(self, user_id, project_id, mac):
538+ """Allocates an ip to a mac address"""
539 for address in self.available:
540- logging.debug("Allocating IP %s to %s" % (address, project_id))
541+ logging.debug("Allocating IP %s to %s", address, project_id)
542 self._add_host(user_id, project_id, address, mac)
543 self.express(address=address)
544 return address
545 raise exception.NoMoreAddresses("Project %s with network %s" %
546- (project_id, str(self.network)))
547+ (project_id, str(self.network)))
548
549 def lease_ip(self, ip_str):
550- logging.debug("Leasing allocated IP %s" % (ip_str))
551+ """Called when DHCP lease is activated"""
552+ logging.debug("Leasing allocated IP %s", ip_str)
553
554 def release_ip(self, ip_str):
555+ """Called when DHCP lease expires
556+
557+ Removes the ip from the assigned list"""
558 if not ip_str in self.assigned:
559 raise exception.AddressNotAllocated()
560+ self._rem_host(ip_str)
561 self.deexpress(address=ip_str)
562- self._rem_host(ip_str)
563+ logging.debug("Releasing IP %s", ip_str)
564
565 def deallocate_ip(self, ip_str):
566- # Do nothing for now, cleanup on ip release
567- pass
568+ """Deallocates an allocated ip"""
569+ # NOTE(vish): Perhaps we should put the ip into an intermediate
570+ # state, so we know that we are pending waiting for
571+ # dnsmasq to confirm that it has been released.
572+ logging.debug("Deallocating allocated IP %s", ip_str)
573
574 def list_addresses(self):
575+ """List all allocated addresses"""
576 for address in self.hosts:
577 yield address
578
579- def express(self, address=None): pass
580- def deexpress(self, address=None): pass
581+ def express(self, address=None):
582+ """Set up network. Implemented in subclasses"""
583+ pass
584+
585+ def deexpress(self, address=None):
586+ """Tear down network. Implemented in subclasses"""
587+ pass
588
589
590 class BridgedNetwork(BaseNetwork):
591@@ -280,7 +322,11 @@
592 override_type = 'network'
593
594 @classmethod
595- def get_network_for_project(cls, user_id, project_id, security_group):
596+ def get_network_for_project(cls,
597+ user_id,
598+ project_id,
599+ security_group='default'):
600+ """Returns network for a given project"""
601 vlan = get_vlan_for_project(project_id)
602 network_str = vlan.subnet()
603 return cls.create(user_id, project_id, security_group, vlan.vlan_id,
604@@ -296,30 +342,36 @@
605 linux_net.vlan_create(self)
606 linux_net.bridge_create(self)
607
608+
609 class DHCPNetwork(BridgedNetwork):
610- """
611- properties:
612- dhcp_listen_address: the ip of the gateway / dhcp host
613- dhcp_range_start: the first ip to give out
614- dhcp_range_end: the last ip to give out
615- """
616+ """Network supporting DHCP"""
617 bridge_gets_ip = True
618 override_type = 'network'
619
620 def __init__(self, *args, **kwargs):
621 super(DHCPNetwork, self).__init__(*args, **kwargs)
622- # logging.debug("Initing DHCPNetwork object...")
623- self.dhcp_listen_address = self.network[1]
624- self.dhcp_range_start = self.network[3]
625- self.dhcp_range_end = self.network[-(1 + FLAGS.cnt_vpn_clients)]
626- try:
627+ if not(os.path.exists(FLAGS.networks_path)):
628 os.makedirs(FLAGS.networks_path)
629- # NOTE(todd): I guess this is a lazy way to not have to check if the
630- # directory exists, but shouldn't we be smarter about
631- # telling the difference between existing directory and
632- # permission denied? (Errno 17 vs 13, OSError)
633- except Exception, err:
634- pass
635+
636+ @property
637+ def num_bottom_reserved_ips(self):
638+ # For cloudpipe
639+ return super(DHCPNetwork, self).num_bottom_reserved_ips + 1
640+
641+ @property
642+ def num_top_reserved_ips(self):
643+ return super(DHCPNetwork, self).num_top_reserved_ips + \
644+ FLAGS.cnt_vpn_clients
645+
646+ @property
647+ def dhcp_listen_address(self):
648+ """Address where dhcp server should listen"""
649+ return self.gateway
650+
651+ @property
652+ def dhcp_range_start(self):
653+ """Starting address dhcp server should use"""
654+ return self.network[self.num_bottom_reserved_ips]
655
656 def express(self, address=None):
657 super(DHCPNetwork, self).express(address=address)
658@@ -329,15 +381,17 @@
659 linux_net.start_dnsmasq(self)
660 else:
661 logging.debug("Not launching dnsmasq: no hosts.")
662- self.express_cloudpipe()
663+ self.express_vpn()
664
665 def allocate_vpn_ip(self, user_id, project_id, mac):
666+ """Allocates the reserved ip to a vpn instance"""
667 address = str(self.network[2])
668 self._add_host(user_id, project_id, address, mac)
669 self.express(address=address)
670 return address
671
672- def express_cloudpipe(self):
673+ def express_vpn(self):
674+ """Sets up routing rules for vpn"""
675 private_ip = str(self.network[2])
676 linux_net.confirm_rule("FORWARD -d %s -p udp --dport 1194 -j ACCEPT"
677 % (private_ip, ))
678@@ -353,7 +407,9 @@
679 else:
680 linux_net.start_dnsmasq(self)
681
682+
683 class PublicAddress(datastore.BasicModel):
684+ """Represents an elastic ip in the datastore"""
685 override_type = "address"
686
687 def __init__(self, address):
688@@ -369,6 +425,7 @@
689
690 @classmethod
691 def create(cls, user_id, project_id, address):
692+ """Creates a PublicAddress object"""
693 addr = cls(address)
694 addr['user_id'] = user_id
695 addr['project_id'] = project_id
696@@ -379,35 +436,34 @@
697
698
699 DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)]
700+
701+
702 class PublicNetworkController(BaseNetwork):
703+ """Handles elastic ips"""
704 override_type = 'network'
705
706 def __init__(self, *args, **kwargs):
707 network_id = "public:default"
708- super(PublicNetworkController, self).__init__(network_id,
709- FLAGS.public_range)
710+ super(PublicNetworkController, self).__init__(network_id,
711+ FLAGS.public_range, *args, **kwargs)
712 self['user_id'] = "public"
713 self['project_id'] = "public"
714- self["create_time"] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
715+ self["create_time"] = time.strftime('%Y-%m-%dT%H:%M:%SZ',
716+ time.gmtime())
717 self["vlan"] = FLAGS.public_vlan
718 self.save()
719 self.express()
720
721 @property
722- def available(self):
723- for idx in range(2, len(self.network)-1):
724- address = str(self.network[idx])
725- if not address in self.hosts.keys():
726- yield address
727-
728- @property
729 def host_objs(self):
730+ """Returns assigned addresses as PublicAddress objects"""
731 for address in self.assigned:
732 yield PublicAddress(address)
733
734- def get_host(self, host):
735- if host in self.assigned:
736- return PublicAddress(host)
737+ def get_host(self, public_ip):
738+ """Returns a specific public ip as PublicAddress object"""
739+ if public_ip in self.assigned:
740+ return PublicAddress(public_ip)
741 return None
742
743 def _add_host(self, user_id, project_id, host, _target):
744@@ -423,9 +479,10 @@
745 self.release_ip(ip_str)
746
747 def associate_address(self, public_ip, private_ip, instance_id):
748+ """Associates a public ip to a private ip and instance id"""
749 if not public_ip in self.assigned:
750 raise exception.AddressNotAllocated()
751- # TODO(joshua): Keep an index going both ways
752+ # TODO(josh): Keep an index going both ways
753 for addr in self.host_objs:
754 if addr.get('private_ip', None) == private_ip:
755 raise exception.AddressAlreadyAssociated()
756@@ -438,6 +495,7 @@
757 self.express(address=public_ip)
758
759 def disassociate_address(self, public_ip):
760+ """Disassociates a public ip with its private ip"""
761 if not public_ip in self.assigned:
762 raise exception.AddressNotAllocated()
763 addr = self.get_host(public_ip)
764@@ -453,7 +511,7 @@
765 if address:
766 addresses = [self.get_host(address)]
767 for addr in addresses:
768- if addr.get('private_ip','available') == 'available':
769+ if addr.get('private_ip', 'available') == 'available':
770 continue
771 public_ip = addr['address']
772 private_ip = addr['private_ip']
773@@ -462,7 +520,7 @@
774 % (public_ip, private_ip))
775 linux_net.confirm_rule("POSTROUTING -t nat -s %s -j SNAT --to %s"
776 % (private_ip, public_ip))
777- # TODO: Get these from the secgroup datastore entries
778+ # TODO(joshua): Get these from the secgroup datastore entries
779 linux_net.confirm_rule("FORWARD -d %s -p icmp -j ACCEPT"
780 % (private_ip))
781 for (protocol, port) in DEFAULT_PORTS:
782@@ -485,19 +543,18 @@
783 % (private_ip, protocol, port))
784
785
786-# FIXME(todd): does this present a race condition, or is there some piece of
787-# architecture that mitigates it (only one queue listener per net)?
788+# FIXME(todd): does this present a race condition, or is there some
789+# piece of architecture that mitigates it (only one queue
790+# listener per net)?
791 def get_vlan_for_project(project_id):
792- """
793- Allocate vlan IDs to individual users.
794- """
795+ """Allocate vlan IDs to individual users"""
796 vlan = Vlan.lookup(project_id)
797 if vlan:
798 return vlan
799 known_vlans = Vlan.dict_by_vlan()
800 for vnum in range(FLAGS.vlan_start, FLAGS.vlan_end):
801 vstr = str(vnum)
802- if not known_vlans.has_key(vstr):
803+ if not vstr in known_vlans:
804 return Vlan.create(project_id, vnum)
805 old_project_id = known_vlans[vstr]
806 if not manager.AuthManager().get_project(old_project_id):
807@@ -521,8 +578,9 @@
808 return Vlan.create(project_id, vnum)
809 raise exception.AddressNotAllocated("Out of VLANs")
810
811+
812 def get_project_network(project_id, security_group='default'):
813- """ get a project's private network, allocating one if needed """
814+ """Gets a project's private network, allocating one if needed"""
815 project = manager.AuthManager().get_project(project_id)
816 if not project:
817 raise nova_exception.NotFound("Project %s doesn't exist." % project_id)
818@@ -533,28 +591,29 @@
819
820
821 def get_network_by_address(address):
822+ """Gets the network for a given private ip"""
823 # TODO(vish): This is completely the wrong way to do this, but
824 # I'm getting the network binary working before I
825 # tackle doing this the right way.
826- logging.debug("Get Network By Address: %s" % address)
827+ logging.debug("Get Network By Address: %s", address)
828 for project in manager.AuthManager().get_projects():
829 net = get_project_network(project.id)
830 if address in net.assigned:
831- logging.debug("Found %s in %s" % (address, project.id))
832+ logging.debug("Found %s in %s", address, project.id)
833 return net
834 raise exception.AddressNotAllocated()
835
836
837 def get_network_by_interface(iface, security_group='default'):
838+ """Gets the network for a given interface"""
839 vlan = iface.rpartition("br")[2]
840 project_id = Vlan.dict_by_vlan().get(vlan)
841 return get_project_network(project_id, security_group)
842
843
844-
845 def get_public_ip_for_instance(instance_id):
846- # FIXME: this should be a lookup - iteration won't scale
847+ """Gets the public ip for a given instance"""
848+ # FIXME(josh): this should be a lookup - iteration won't scale
849 for address_record in PublicAddress.all():
850 if address_record.get('instance_id', 'available') == instance_id:
851 return address_record['address']
852-
853
854=== modified file 'nova/network/service.py'
855--- nova/network/service.py 2010-08-05 19:29:50 +0000
856+++ nova/network/service.py 2010-08-10 22:55:54 +0000
857@@ -17,7 +17,7 @@
858 # under the License.
859
860 """
861-Network Nodes are responsible for allocating ips and setting up network
862+Network Hosts are responsible for allocating ips and setting up network
863 """
864
865 from nova import datastore
866@@ -38,7 +38,7 @@
867 flags.DEFINE_string('flat_network_bridge', 'br100',
868 'Bridge for simple network instances')
869 flags.DEFINE_list('flat_network_ips',
870- ['192.168.0.2','192.168.0.3','192.168.0.4'],
871+ ['192.168.0.2', '192.168.0.3', '192.168.0.4'],
872 'Available ips for simple network')
873 flags.DEFINE_string('flat_network_network', '192.168.0.0',
874 'Network for simple network')
875@@ -51,26 +51,34 @@
876 flags.DEFINE_string('flat_network_dns', '8.8.4.4',
877 'Dns for simple network')
878
879+
880 def type_to_class(network_type):
881+ """Convert a network_type string into an actual Python class"""
882 if network_type == 'flat':
883 return FlatNetworkService
884- elif network_type == 'vlan':
885+ elif network_type == 'vlan':
886 return VlanNetworkService
887 raise NotFound("Couldn't find %s network type" % network_type)
888
889
890 def setup_compute_network(network_type, user_id, project_id, security_group):
891+ """Sets up the network on a compute host"""
892 srv = type_to_class(network_type)
893- srv.setup_compute_network(network_type, user_id, project_id, security_group)
894+ srv.setup_compute_network(network_type,
895+ user_id,
896+ project_id,
897+ security_group)
898
899
900 def get_host_for_project(project_id):
901+ """Get host allocated to project from datastore"""
902 redis = datastore.Redis.instance()
903 return redis.get(_host_key(project_id))
904
905
906 def _host_key(project_id):
907- return "network_host:%s" % project_id
908+ """Returns redis host key for network"""
909+ return "networkhost:%s" % project_id
910
911
912 class BaseNetworkService(service.Service):
913@@ -80,6 +88,7 @@
914 """
915 def __init__(self, *args, **kwargs):
916 self.network = model.PublicNetworkController()
917+ super(BaseNetworkService, self).__init__(*args, **kwargs)
918
919 def set_network_host(self, user_id, project_id, *args, **kwargs):
920 """Safely sets the host of the projects network"""
921@@ -109,7 +118,7 @@
922 pass
923
924 @classmethod
925- def setup_compute_network(self, user_id, project_id, security_group,
926+ def setup_compute_network(cls, user_id, project_id, security_group,
927 *args, **kwargs):
928 """Sets up matching network for compute hosts"""
929 raise NotImplementedError()
930@@ -138,7 +147,7 @@
931 """Basic network where no vlans are used"""
932
933 @classmethod
934- def setup_compute_network(self, user_id, project_id, security_group,
935+ def setup_compute_network(cls, user_id, project_id, security_group,
936 *args, **kwargs):
937 """Network is created manually"""
938 pass
939@@ -175,26 +184,28 @@
940 """Returns an ip to the pool"""
941 datastore.Redis.instance().sadd('ips', fixed_ip)
942
943+
944 class VlanNetworkService(BaseNetworkService):
945 """Vlan network with dhcp"""
946 # NOTE(vish): A lot of the interactions with network/model.py can be
947 # simplified and improved. Also there it may be useful
948 # to support vlans separately from dhcp, instead of having
949 # both of them together in this class.
950+ # pylint: disable=W0221
951 def allocate_fixed_ip(self, user_id, project_id,
952 security_group='default',
953- vpn=False, *args, **kwargs):
954- """Gets a fixed ip from the pool """
955+ is_vpn=False, *args, **kwargs):
956+ """Gets a fixed ip from the pool"""
957 mac = utils.generate_mac()
958 net = model.get_project_network(project_id)
959- if vpn:
960+ if is_vpn:
961 fixed_ip = net.allocate_vpn_ip(user_id, project_id, mac)
962 else:
963 fixed_ip = net.allocate_ip(user_id, project_id, mac)
964 return {'network_type': FLAGS.network_type,
965 'bridge_name': net['bridge_name'],
966 'mac_address': mac,
967- 'private_dns_name' : fixed_ip}
968+ 'private_dns_name': fixed_ip}
969
970 def deallocate_fixed_ip(self, fixed_ip,
971 *args, **kwargs):
972@@ -202,9 +213,11 @@
973 return model.get_network_by_address(fixed_ip).deallocate_ip(fixed_ip)
974
975 def lease_ip(self, address):
976+ """Called by bridge when ip is leased"""
977 return model.get_network_by_address(address).lease_ip(address)
978
979 def release_ip(self, address):
980+ """Called by bridge when ip is released"""
981 return model.get_network_by_address(address).release_ip(address)
982
983 def restart_nets(self):
984@@ -218,7 +231,7 @@
985 vpn.NetworkData.create(project_id)
986
987 @classmethod
988- def setup_compute_network(self, user_id, project_id, security_group,
989+ def setup_compute_network(cls, user_id, project_id, security_group,
990 *args, **kwargs):
991 """Sets up matching network for compute hosts"""
992 # NOTE(vish): Use BridgedNetwork instead of DHCPNetwork because
993
994=== modified file 'nova/network/vpn.py'
995--- nova/network/vpn.py 2010-08-05 19:29:50 +0000
996+++ nova/network/vpn.py 2010-08-10 22:55:54 +0000
997@@ -33,7 +33,9 @@
998 flags.DEFINE_integer('vpn_end_port', 2000,
999 'End port for the cloudpipe VPN servers')
1000
1001+
1002 class NoMorePorts(exception.Error):
1003+ """No ports available to allocate for the given ip"""
1004 pass
1005
1006
1007@@ -67,34 +69,44 @@
1008 return network_data
1009
1010 @classmethod
1011- def find_free_port_for_ip(cls, ip):
1012+ def find_free_port_for_ip(cls, vpn_ip):
1013 """Finds a free port for a given ip from the redis set"""
1014 # TODO(vish): these redis commands should be generalized and
1015 # placed into a base class. Conceptually, it is
1016 # similar to an association, but we are just
1017 # storing a set of values instead of keys that
1018 # should be turned into objects.
1019- redis = datastore.Redis.instance()
1020- key = 'ip:%s:ports' % ip
1021+ cls._ensure_set_exists(vpn_ip)
1022+
1023+ port = datastore.Redis.instance().spop(cls._redis_ports_key(vpn_ip))
1024+ if not port:
1025+ raise NoMorePorts()
1026+ return port
1027+
1028+ @classmethod
1029+ def _redis_ports_key(cls, vpn_ip):
1030+ """Key that ports are stored under in redis"""
1031+ return 'ip:%s:ports' % vpn_ip
1032+
1033+ @classmethod
1034+ def _ensure_set_exists(cls, vpn_ip):
1035+ """Creates the set of ports for the ip if it doesn't already exist"""
1036 # TODO(vish): these ports should be allocated through an admin
1037 # command instead of a flag
1038- if (not redis.exists(key) and
1039- not redis.exists(cls._redis_association_name('ip', ip))):
1040+ redis = datastore.Redis.instance()
1041+ if (not redis.exists(cls._redis_ports_key(vpn_ip)) and
1042+ not redis.exists(cls._redis_association_name('ip', vpn_ip))):
1043 for i in range(FLAGS.vpn_start_port, FLAGS.vpn_end_port + 1):
1044- redis.sadd(key, i)
1045-
1046- port = redis.spop(key)
1047- if not port:
1048- raise NoMorePorts()
1049- return port
1050+ redis.sadd(cls._redis_ports_key(vpn_ip), i)
1051
1052 @classmethod
1053- def num_ports_for_ip(cls, ip):
1054+ def num_ports_for_ip(cls, vpn_ip):
1055 """Calculates the number of free ports for a given ip"""
1056- return datastore.Redis.instance().scard('ip:%s:ports' % ip)
1057+ cls._ensure_set_exists(vpn_ip)
1058+ return datastore.Redis.instance().scard('ip:%s:ports' % vpn_ip)
1059
1060 @property
1061- def ip(self):
1062+ def ip(self): # pylint: disable=C0103
1063 """The ip assigned to the project"""
1064 return self['ip']
1065
1066@@ -113,4 +125,3 @@
1067 self.unassociate_with('ip', self.ip)
1068 datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port)
1069 super(NetworkData, self).destroy()
1070-
1071
1072=== modified file 'nova/tests/network_unittest.py'
1073--- nova/tests/network_unittest.py 2010-08-06 21:31:03 +0000
1074+++ nova/tests/network_unittest.py 2010-08-10 22:55:54 +0000
1075@@ -15,7 +15,9 @@
1076 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1077 # License for the specific language governing permissions and limitations
1078 # under the License.
1079-
1080+"""
1081+Unit Tests for network code
1082+"""
1083 import IPy
1084 import os
1085 import logging
1086@@ -31,8 +33,10 @@
1087
1088 FLAGS = flags.FLAGS
1089
1090+
1091 class NetworkTestCase(test.TrialTestCase):
1092- def setUp(self):
1093+ """Test cases for network code"""
1094+ def setUp(self): # pylint: disable=C0103
1095 super(NetworkTestCase, self).setUp()
1096 # NOTE(vish): if you change these flags, make sure to change the
1097 # flags in the corresponding section in nova-dhcpbridge
1098@@ -43,7 +47,6 @@
1099 network_size=32)
1100 logging.getLogger().setLevel(logging.DEBUG)
1101 self.manager = manager.AuthManager()
1102- self.dnsmasq = FakeDNSMasq()
1103 self.user = self.manager.create_user('netuser', 'netuser', 'netuser')
1104 self.projects = []
1105 self.projects.append(self.manager.create_project('netuser',
1106@@ -54,47 +57,49 @@
1107 self.projects.append(self.manager.create_project(name,
1108 'netuser',
1109 name))
1110- self.network = model.PublicNetworkController()
1111+ vpn.NetworkData.create(self.projects[i].id)
1112 self.service = service.VlanNetworkService()
1113
1114- def tearDown(self):
1115+ def tearDown(self): # pylint: disable=C0103
1116 super(NetworkTestCase, self).tearDown()
1117 for project in self.projects:
1118 self.manager.delete_project(project)
1119 self.manager.delete_user(self.user)
1120
1121 def test_public_network_allocation(self):
1122+ """Makes sure that we can allocaate a public ip"""
1123 pubnet = IPy.IP(flags.FLAGS.public_range)
1124- address = self.network.allocate_ip(self.user.id, self.projects[0].id, "public")
1125+ address = self.service.allocate_elastic_ip(self.user.id,
1126+ self.projects[0].id)
1127 self.assertTrue(IPy.IP(address) in pubnet)
1128- self.assertTrue(IPy.IP(address) in self.network.network)
1129
1130 def test_allocate_deallocate_fixed_ip(self):
1131- result = yield self.service.allocate_fixed_ip(
1132+ """Makes sure that we can allocate and deallocate a fixed ip"""
1133+ result = self.service.allocate_fixed_ip(
1134 self.user.id, self.projects[0].id)
1135 address = result['private_dns_name']
1136 mac = result['mac_address']
1137- logging.debug("Was allocated %s" % (address))
1138 net = model.get_project_network(self.projects[0].id, "default")
1139 self.assertEqual(True, is_in_project(address, self.projects[0].id))
1140 hostname = "test-host"
1141- self.dnsmasq.issue_ip(mac, address, hostname, net.bridge_name)
1142- rv = self.service.deallocate_fixed_ip(address)
1143+ issue_ip(mac, address, hostname, net.bridge_name)
1144+ self.service.deallocate_fixed_ip(address)
1145
1146 # Doesn't go away until it's dhcp released
1147 self.assertEqual(True, is_in_project(address, self.projects[0].id))
1148
1149- self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
1150+ release_ip(mac, address, hostname, net.bridge_name)
1151 self.assertEqual(False, is_in_project(address, self.projects[0].id))
1152
1153- def test_range_allocation(self):
1154- hostname = "test-host"
1155- result = yield self.service.allocate_fixed_ip(
1156- self.user.id, self.projects[0].id)
1157+ def test_side_effects(self):
1158+ """Ensures allocating and releasing has no side effects"""
1159+ hostname = "side-effect-host"
1160+ result = self.service.allocate_fixed_ip(self.user.id,
1161+ self.projects[0].id)
1162 mac = result['mac_address']
1163 address = result['private_dns_name']
1164- result = yield self.service.allocate_fixed_ip(
1165- self.user, self.projects[1].id)
1166+ result = self.service.allocate_fixed_ip(self.user,
1167+ self.projects[1].id)
1168 secondmac = result['mac_address']
1169 secondaddress = result['private_dns_name']
1170
1171@@ -102,66 +107,75 @@
1172 secondnet = model.get_project_network(self.projects[1].id, "default")
1173
1174 self.assertEqual(True, is_in_project(address, self.projects[0].id))
1175- self.assertEqual(True, is_in_project(secondaddress, self.projects[1].id))
1176+ self.assertEqual(True, is_in_project(secondaddress,
1177+ self.projects[1].id))
1178 self.assertEqual(False, is_in_project(address, self.projects[1].id))
1179
1180 # Addresses are allocated before they're issued
1181- self.dnsmasq.issue_ip(mac, address, hostname, net.bridge_name)
1182- self.dnsmasq.issue_ip(secondmac, secondaddress,
1183- hostname, secondnet.bridge_name)
1184+ issue_ip(mac, address, hostname, net.bridge_name)
1185+ issue_ip(secondmac, secondaddress, hostname, secondnet.bridge_name)
1186
1187- rv = self.service.deallocate_fixed_ip(address)
1188- self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
1189+ self.service.deallocate_fixed_ip(address)
1190+ release_ip(mac, address, hostname, net.bridge_name)
1191 self.assertEqual(False, is_in_project(address, self.projects[0].id))
1192
1193 # First address release shouldn't affect the second
1194- self.assertEqual(True, is_in_project(secondaddress, self.projects[1].id))
1195+ self.assertEqual(True, is_in_project(secondaddress,
1196+ self.projects[1].id))
1197
1198- rv = self.service.deallocate_fixed_ip(secondaddress)
1199- self.dnsmasq.release_ip(secondmac, secondaddress,
1200- hostname, secondnet.bridge_name)
1201- self.assertEqual(False, is_in_project(secondaddress, self.projects[1].id))
1202+ self.service.deallocate_fixed_ip(secondaddress)
1203+ release_ip(secondmac, secondaddress, hostname, secondnet.bridge_name)
1204+ self.assertEqual(False, is_in_project(secondaddress,
1205+ self.projects[1].id))
1206
1207 def test_subnet_edge(self):
1208- result = yield self.service.allocate_fixed_ip(self.user.id,
1209+ """Makes sure that private ips don't overlap"""
1210+ result = self.service.allocate_fixed_ip(self.user.id,
1211 self.projects[0].id)
1212 firstaddress = result['private_dns_name']
1213 hostname = "toomany-hosts"
1214- for i in range(1,5):
1215+ for i in range(1, 5):
1216 project_id = self.projects[i].id
1217- result = yield self.service.allocate_fixed_ip(
1218+ result = self.service.allocate_fixed_ip(
1219 self.user, project_id)
1220 mac = result['mac_address']
1221 address = result['private_dns_name']
1222- result = yield self.service.allocate_fixed_ip(
1223+ result = self.service.allocate_fixed_ip(
1224 self.user, project_id)
1225 mac2 = result['mac_address']
1226 address2 = result['private_dns_name']
1227- result = yield self.service.allocate_fixed_ip(
1228+ result = self.service.allocate_fixed_ip(
1229 self.user, project_id)
1230 mac3 = result['mac_address']
1231 address3 = result['private_dns_name']
1232- self.assertEqual(False, is_in_project(address, self.projects[0].id))
1233- self.assertEqual(False, is_in_project(address2, self.projects[0].id))
1234- self.assertEqual(False, is_in_project(address3, self.projects[0].id))
1235- rv = self.service.deallocate_fixed_ip(address)
1236- rv = self.service.deallocate_fixed_ip(address2)
1237- rv = self.service.deallocate_fixed_ip(address3)
1238 net = model.get_project_network(project_id, "default")
1239- self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
1240- self.dnsmasq.release_ip(mac2, address2, hostname, net.bridge_name)
1241- self.dnsmasq.release_ip(mac3, address3, hostname, net.bridge_name)
1242+ issue_ip(mac, address, hostname, net.bridge_name)
1243+ issue_ip(mac2, address2, hostname, net.bridge_name)
1244+ issue_ip(mac3, address3, hostname, net.bridge_name)
1245+ self.assertEqual(False, is_in_project(address,
1246+ self.projects[0].id))
1247+ self.assertEqual(False, is_in_project(address2,
1248+ self.projects[0].id))
1249+ self.assertEqual(False, is_in_project(address3,
1250+ self.projects[0].id))
1251+ self.service.deallocate_fixed_ip(address)
1252+ self.service.deallocate_fixed_ip(address2)
1253+ self.service.deallocate_fixed_ip(address3)
1254+ release_ip(mac, address, hostname, net.bridge_name)
1255+ release_ip(mac2, address2, hostname, net.bridge_name)
1256+ release_ip(mac3, address3, hostname, net.bridge_name)
1257 net = model.get_project_network(self.projects[0].id, "default")
1258- rv = self.service.deallocate_fixed_ip(firstaddress)
1259- self.dnsmasq.release_ip(mac, firstaddress, hostname, net.bridge_name)
1260+ self.service.deallocate_fixed_ip(firstaddress)
1261+ release_ip(mac, firstaddress, hostname, net.bridge_name)
1262
1263- def test_212_vpn_ip_and_port_looks_valid(self):
1264- vpn.NetworkData.create(self.projects[0].id)
1265+ def test_vpn_ip_and_port_looks_valid(self):
1266+ """Ensure the vpn ip and port are reasonable"""
1267 self.assert_(self.projects[0].vpn_ip)
1268 self.assert_(self.projects[0].vpn_port >= FLAGS.vpn_start_port)
1269 self.assert_(self.projects[0].vpn_port <= FLAGS.vpn_end_port)
1270
1271 def test_too_many_vpns(self):
1272+ """Ensure error is raised if we run out of vpn ports"""
1273 vpns = []
1274 for i in xrange(vpn.NetworkData.num_ports_for_ip(FLAGS.vpn_ip)):
1275 vpns.append(vpn.NetworkData.create("vpnuser%s" % i))
1276@@ -169,84 +183,101 @@
1277 for network_datum in vpns:
1278 network_datum.destroy()
1279
1280- def test_release_before_deallocate(self):
1281- pass
1282-
1283- def test_deallocate_before_issued(self):
1284- pass
1285-
1286- def test_too_many_addresses(self):
1287- """
1288- Here, we test that a proper NoMoreAddresses exception is raised.
1289-
1290- However, the number of available IP addresses depends on the test
1291+ def test_ips_are_reused(self):
1292+ """Makes sure that ip addresses that are deallocated get reused"""
1293+ result = self.service.allocate_fixed_ip(
1294+ self.user.id, self.projects[0].id)
1295+ mac = result['mac_address']
1296+ address = result['private_dns_name']
1297+
1298+ hostname = "reuse-host"
1299+ net = model.get_project_network(self.projects[0].id, "default")
1300+
1301+ issue_ip(mac, address, hostname, net.bridge_name)
1302+ self.service.deallocate_fixed_ip(address)
1303+ release_ip(mac, address, hostname, net.bridge_name)
1304+
1305+ result = self.service.allocate_fixed_ip(
1306+ self.user, self.projects[0].id)
1307+ secondmac = result['mac_address']
1308+ secondaddress = result['private_dns_name']
1309+ self.assertEqual(address, secondaddress)
1310+ self.service.deallocate_fixed_ip(secondaddress)
1311+ issue_ip(secondmac, secondaddress, hostname, net.bridge_name)
1312+ release_ip(secondmac, secondaddress, hostname, net.bridge_name)
1313+
1314+ def test_available_ips(self):
1315+ """Make sure the number of available ips for the network is correct
1316+
1317+ The number of available IP addresses depends on the test
1318 environment's setup.
1319
1320 Network size is set in test fixture's setUp method.
1321
1322- There are FLAGS.cnt_vpn_clients addresses reserved for VPN (NUM_RESERVED_VPN_IPS)
1323-
1324- And there are NUM_STATIC_IPS that are always reserved by Nova for the necessary
1325- services (gateway, CloudPipe, etc)
1326-
1327- So we should get flags.network_size - (NUM_STATIC_IPS +
1328- NUM_PREALLOCATED_IPS +
1329- NUM_RESERVED_VPN_IPS)
1330- usable addresses
1331+ There are ips reserved at the bottom and top of the range.
1332+ services (network, gateway, CloudPipe, broadcast)
1333 """
1334 net = model.get_project_network(self.projects[0].id, "default")
1335-
1336- # Determine expected number of available IP addresses
1337- num_static_ips = net.num_static_ips
1338 num_preallocated_ips = len(net.hosts.keys())
1339- num_reserved_vpn_ips = flags.FLAGS.cnt_vpn_clients
1340- num_available_ips = flags.FLAGS.network_size - (num_static_ips +
1341- num_preallocated_ips +
1342- num_reserved_vpn_ips)
1343+ net_size = flags.FLAGS.network_size
1344+ num_available_ips = net_size - (net.num_bottom_reserved_ips +
1345+ num_preallocated_ips +
1346+ net.num_top_reserved_ips)
1347+ self.assertEqual(num_available_ips, len(list(net.available)))
1348+
1349+ def test_too_many_addresses(self):
1350+ """Test for a NoMoreAddresses exception when all fixed ips are used.
1351+ """
1352+ net = model.get_project_network(self.projects[0].id, "default")
1353
1354 hostname = "toomany-hosts"
1355 macs = {}
1356 addresses = {}
1357- for i in range(0, (num_available_ips - 1)):
1358- result = yield self.service.allocate_fixed_ip(self.user.id, self.projects[0].id)
1359+ # Number of availaible ips is len of the available list
1360+ num_available_ips = len(list(net.available))
1361+ for i in range(num_available_ips):
1362+ result = self.service.allocate_fixed_ip(self.user.id,
1363+ self.projects[0].id)
1364 macs[i] = result['mac_address']
1365 addresses[i] = result['private_dns_name']
1366- self.dnsmasq.issue_ip(macs[i], addresses[i], hostname, net.bridge_name)
1367-
1368- self.assertFailure(self.service.allocate_fixed_ip(self.user.id, self.projects[0].id), NoMoreAddresses)
1369-
1370- for i in range(0, (num_available_ips - 1)):
1371- rv = self.service.deallocate_fixed_ip(addresses[i])
1372- self.dnsmasq.release_ip(macs[i], addresses[i], hostname, net.bridge_name)
1373+ issue_ip(macs[i], addresses[i], hostname, net.bridge_name)
1374+
1375+ self.assertEqual(len(list(net.available)), 0)
1376+ self.assertRaises(NoMoreAddresses, self.service.allocate_fixed_ip,
1377+ self.user.id, self.projects[0].id)
1378+
1379+ for i in range(len(addresses)):
1380+ self.service.deallocate_fixed_ip(addresses[i])
1381+ release_ip(macs[i], addresses[i], hostname, net.bridge_name)
1382+ self.assertEqual(len(list(net.available)), num_available_ips)
1383+
1384
1385 def is_in_project(address, project_id):
1386+ """Returns true if address is in specified project"""
1387 return address in model.get_project_network(project_id).list_addresses()
1388
1389-def _get_project_addresses(project_id):
1390- project_addresses = []
1391- for addr in model.get_project_network(project_id).list_addresses():
1392- project_addresses.append(addr)
1393- return project_addresses
1394
1395 def binpath(script):
1396+ """Returns the absolute path to a script in bin"""
1397 return os.path.abspath(os.path.join(__file__, "../../../bin", script))
1398
1399-class FakeDNSMasq(object):
1400- def issue_ip(self, mac, ip, hostname, interface):
1401- cmd = "%s add %s %s %s" % (binpath('nova-dhcpbridge'),
1402- mac, ip, hostname)
1403- env = {'DNSMASQ_INTERFACE': interface,
1404- 'TESTING' : '1',
1405- 'FLAGFILE' : FLAGS.dhcpbridge_flagfile}
1406- (out, err) = utils.execute(cmd, addl_env=env)
1407- logging.debug("ISSUE_IP: %s, %s " % (out, err))
1408-
1409- def release_ip(self, mac, ip, hostname, interface):
1410- cmd = "%s del %s %s %s" % (binpath('nova-dhcpbridge'),
1411- mac, ip, hostname)
1412- env = {'DNSMASQ_INTERFACE': interface,
1413- 'TESTING' : '1',
1414- 'FLAGFILE' : FLAGS.dhcpbridge_flagfile}
1415- (out, err) = utils.execute(cmd, addl_env=env)
1416- logging.debug("RELEASE_IP: %s, %s " % (out, err))
1417-
1418+
1419+def issue_ip(mac, private_ip, hostname, interface):
1420+ """Run add command on dhcpbridge"""
1421+ cmd = "%s add %s %s %s" % (binpath('nova-dhcpbridge'),
1422+ mac, private_ip, hostname)
1423+ env = {'DNSMASQ_INTERFACE': interface,
1424+ 'TESTING': '1',
1425+ 'FLAGFILE': FLAGS.dhcpbridge_flagfile}
1426+ (out, err) = utils.execute(cmd, addl_env=env)
1427+ logging.debug("ISSUE_IP: %s, %s ", out, err)
1428+
1429+def release_ip(mac, private_ip, hostname, interface):
1430+ """Run del command on dhcpbridge"""
1431+ cmd = "%s del %s %s %s" % (binpath('nova-dhcpbridge'),
1432+ mac, private_ip, hostname)
1433+ env = {'DNSMASQ_INTERFACE': interface,
1434+ 'TESTING': '1',
1435+ 'FLAGFILE': FLAGS.dhcpbridge_flagfile}
1436+ (out, err) = utils.execute(cmd, addl_env=env)
1437+ logging.debug("RELEASE_IP: %s, %s ", out, err)