Merge lp:~vishvananda/nova/network-refactor into lp:~hudson-openstack/nova/trunk
- network-refactor
- Merge into 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 |
Related bugs: | |
Related blueprints: |
Refactor networking to use its own binary
(Undefined)
|
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
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) |
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.