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