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

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

Commit message

Improves pep8 compliance and pylint score in network code.

Description of the change

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

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

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

Nice job.

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

lgtm, we are using this already inside nebula

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/nova-dhcpbridge'
--- bin/nova-dhcpbridge 2010-08-08 02:51:17 +0000
+++ bin/nova-dhcpbridge 2010-08-10 22:55:54 +0000
@@ -56,7 +56,7 @@
5656
5757
58def del_lease(_mac, ip, _hostname, _interface):58def del_lease(_mac, ip, _hostname, _interface):
59 """Remove the leased IP from the databases."""59 """Called when a lease expires."""
60 if FLAGS.fake_rabbit:60 if FLAGS.fake_rabbit:
61 service.VlanNetworkService().release_ip(ip)61 service.VlanNetworkService().release_ip(ip)
62 else:62 else:
@@ -70,8 +70,9 @@
70 net = model.get_network_by_interface(interface)70 net = model.get_network_by_interface(interface)
71 res = ""71 res = ""
72 for host_name in net.hosts:72 for host_name in net.hosts:
73 res += "%s\n" % linux_net.hostDHCP(net, host_name,73 res += "%s\n" % linux_net.host_dhcp(net,
74 net.hosts[host_name])74 host_name,
75 net.hosts[host_name])
75 return res76 return res
7677
7778
7879
=== modified file 'nova/endpoint/cloud.py'
--- nova/endpoint/cloud.py 2010-08-08 18:40:03 +0000
+++ nova/endpoint/cloud.py 2010-08-10 22:55:54 +0000
@@ -103,7 +103,7 @@
103 result = {}103 result = {}
104 for instance in self.instdir.all:104 for instance in self.instdir.all:
105 if instance['project_id'] == project_id:105 if instance['project_id'] == project_id:
106 line = '%s slots=%d' % (instance['private_dns_name'], 106 line = '%s slots=%d' % (instance['private_dns_name'],
107 INSTANCE_TYPES[instance['instance_type']]['vcpus'])107 INSTANCE_TYPES[instance['instance_type']]['vcpus'])
108 if instance['key_name'] in result:108 if instance['key_name'] in result:
109 result[instance['key_name']].append(line)109 result[instance['key_name']].append(line)
@@ -423,7 +423,7 @@
423 i['key_name'] = instance.get('key_name', None)423 i['key_name'] = instance.get('key_name', None)
424 if context.user.is_admin():424 if context.user.is_admin():
425 i['key_name'] = '%s (%s, %s)' % (i['key_name'],425 i['key_name'] = '%s (%s, %s)' % (i['key_name'],
426 instance.get('project_id', None), 426 instance.get('project_id', None),
427 instance.get('node_name', ''))427 instance.get('node_name', ''))
428 i['product_codes_set'] = self._convert_to_set(428 i['product_codes_set'] = self._convert_to_set(
429 instance.get('product_codes', None), 'product_code')429 instance.get('product_codes', None), 'product_code')
@@ -560,15 +560,15 @@
560 # TODO: Get the real security group of launch in here560 # TODO: Get the real security group of launch in here
561 security_group = "default"561 security_group = "default"
562 for num in range(int(kwargs['max_count'])):562 for num in range(int(kwargs['max_count'])):
563 vpn = False563 is_vpn = False
564 if image_id == FLAGS.vpn_image_id:564 if image_id == FLAGS.vpn_image_id:
565 vpn = True565 is_vpn = True
566 allocate_result = yield rpc.call(network_topic,566 allocate_result = yield rpc.call(network_topic,
567 {"method": "allocate_fixed_ip",567 {"method": "allocate_fixed_ip",
568 "args": {"user_id": context.user.id,568 "args": {"user_id": context.user.id,
569 "project_id": context.project.id,569 "project_id": context.project.id,
570 "security_group": security_group,570 "security_group": security_group,
571 "vpn": vpn}})571 "is_vpn": is_vpn}})
572 allocate_data = allocate_result['result']572 allocate_data = allocate_result['result']
573 inst = self.instdir.new()573 inst = self.instdir.new()
574 inst['image_id'] = image_id574 inst['image_id'] = image_id
575575
=== modified file 'nova/network/exception.py'
--- nova/network/exception.py 2010-08-03 21:31:47 +0000
+++ nova/network/exception.py 2010-08-10 22:55:54 +0000
@@ -24,17 +24,25 @@
2424
2525
26class NoMoreAddresses(Error):26class NoMoreAddresses(Error):
27 """No More Addresses are available in the network"""
27 pass28 pass
2829
30
29class AddressNotAllocated(Error):31class AddressNotAllocated(Error):
32 """The specified address has not been allocated"""
30 pass33 pass
3134
35
32class AddressAlreadyAssociated(Error):36class AddressAlreadyAssociated(Error):
37 """The specified address has already been associated"""
33 pass38 pass
3439
40
35class AddressNotAssociated(Error):41class AddressNotAssociated(Error):
42 """The specified address is not associated"""
36 pass43 pass
3744
45
38class NotValidNetworkSize(Error):46class NotValidNetworkSize(Error):
47 """The network size is not valid"""
39 pass48 pass
40
4149
=== modified file 'nova/network/linux_net.py'
--- nova/network/linux_net.py 2010-08-03 21:31:47 +0000
+++ nova/network/linux_net.py 2010-08-10 22:55:54 +0000
@@ -15,85 +15,102 @@
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations16# License for the specific language governing permissions and limitations
17# under the License.17# under the License.
18"""
19Implements vlans, bridges, and iptables rules using linux utilities.
20"""
1821
19import logging22import logging
20import signal23import signal
21import os24import os
22import subprocess
2325
24# todo(ja): does the definition of network_path belong here?26# todo(ja): does the definition of network_path belong here?
2527
28from nova import flags
26from nova import utils29from nova import utils
2730
28from nova import flags31FLAGS = flags.FLAGS
29FLAGS=flags.FLAGS
3032
31flags.DEFINE_string('dhcpbridge_flagfile',33flags.DEFINE_string('dhcpbridge_flagfile',
32 '/etc/nova/nova-dhcpbridge.conf',34 '/etc/nova/nova-dhcpbridge.conf',
33 'location of flagfile for dhcpbridge')35 'location of flagfile for dhcpbridge')
3436
37
35def execute(cmd, addl_env=None):38def execute(cmd, addl_env=None):
39 """Wrapper around utils.execute for fake_network"""
36 if FLAGS.fake_network:40 if FLAGS.fake_network:
37 logging.debug("FAKE NET: %s" % cmd)41 logging.debug("FAKE NET: %s", cmd)
38 return "fake", 042 return "fake", 0
39 else:43 else:
40 return utils.execute(cmd, addl_env=addl_env)44 return utils.execute(cmd, addl_env=addl_env)
4145
46
42def runthis(desc, cmd):47def runthis(desc, cmd):
48 """Wrapper around utils.runthis for fake_network"""
43 if FLAGS.fake_network:49 if FLAGS.fake_network:
44 return execute(cmd)50 return execute(cmd)
45 else:51 else:
46 return utils.runthis(desc,cmd)52 return utils.runthis(desc, cmd)
47
48def Popen(cmd):
49 if FLAGS.fake_network:
50 execute(' '.join(cmd))
51 else:
52 subprocess.Popen(cmd)
5353
5454
55def device_exists(device):55def device_exists(device):
56 (out, err) = execute("ifconfig %s" % device)56 """Check if ethernet device exists"""
57 (_out, err) = execute("ifconfig %s" % device)
57 return not err58 return not err
5859
60
59def confirm_rule(cmd):61def confirm_rule(cmd):
62 """Delete and re-add iptables rule"""
60 execute("sudo iptables --delete %s" % (cmd))63 execute("sudo iptables --delete %s" % (cmd))
61 execute("sudo iptables -I %s" % (cmd))64 execute("sudo iptables -I %s" % (cmd))
6265
66
63def remove_rule(cmd):67def remove_rule(cmd):
68 """Remove iptables rule"""
64 execute("sudo iptables --delete %s" % (cmd))69 execute("sudo iptables --delete %s" % (cmd))
6570
66def bind_public_ip(ip, interface):71
67 runthis("Binding IP to interface: %s", "sudo ip addr add %s dev %s" % (ip, interface))72def bind_public_ip(public_ip, interface):
6873 """Bind ip to an interface"""
69def unbind_public_ip(ip, interface):74 runthis("Binding IP to interface: %s",
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))
76
77
78def unbind_public_ip(public_ip, interface):
79 """Unbind a public ip from an interface"""
80 runthis("Binding IP to interface: %s",
81 "sudo ip addr del %s dev %s" % (public_ip, interface))
82
7183
72def vlan_create(net):84def vlan_create(net):
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"""
74 if not device_exists("vlan%s" % net['vlan']):86 if not device_exists("vlan%s" % net['vlan']):
75 logging.debug("Starting VLAN inteface for %s network", (net['vlan']))87 logging.debug("Starting VLAN inteface for %s network", (net['vlan']))
76 execute("sudo vconfig set_name_type VLAN_PLUS_VID_NO_PAD")88 execute("sudo vconfig set_name_type VLAN_PLUS_VID_NO_PAD")
77 execute("sudo vconfig add %s %s" % (FLAGS.bridge_dev, net['vlan']))89 execute("sudo vconfig add %s %s" % (FLAGS.bridge_dev, net['vlan']))
78 execute("sudo ifconfig vlan%s up" % (net['vlan']))90 execute("sudo ifconfig vlan%s up" % (net['vlan']))
7991
92
80def bridge_create(net):93def bridge_create(net):
81 """ create a bridge on a vlan unless it already exists """94 """Create a bridge on a vlan unless it already exists"""
82 if not device_exists(net['bridge_name']):95 if not device_exists(net['bridge_name']):
83 logging.debug("Starting Bridge inteface for %s network", (net['vlan']))96 logging.debug("Starting Bridge inteface for %s network", (net['vlan']))
84 execute("sudo brctl addbr %s" % (net['bridge_name']))97 execute("sudo brctl addbr %s" % (net['bridge_name']))
85 execute("sudo brctl setfd %s 0" % (net.bridge_name))98 execute("sudo brctl setfd %s 0" % (net.bridge_name))
86 # execute("sudo brctl setageing %s 10" % (net.bridge_name))99 # execute("sudo brctl setageing %s 10" % (net.bridge_name))
87 execute("sudo brctl stp %s off" % (net['bridge_name']))100 execute("sudo brctl stp %s off" % (net['bridge_name']))
88 execute("sudo brctl addif %s vlan%s" % (net['bridge_name'], net['vlan']))101 execute("sudo brctl addif %s vlan%s" % (net['bridge_name'],
102 net['vlan']))
89 if net.bridge_gets_ip:103 if net.bridge_gets_ip:
90 execute("sudo ifconfig %s %s broadcast %s netmask %s up" % \104 execute("sudo ifconfig %s %s broadcast %s netmask %s up" % \
91 (net['bridge_name'], net.gateway, net.broadcast, net.netmask))105 (net['bridge_name'], net.gateway, net.broadcast, net.netmask))
92 confirm_rule("FORWARD --in-interface %s -j ACCEPT" % (net['bridge_name']))106 confirm_rule("FORWARD --in-interface %s -j ACCEPT" %
107 (net['bridge_name']))
93 else:108 else:
94 execute("sudo ifconfig %s up" % net['bridge_name'])109 execute("sudo ifconfig %s up" % net['bridge_name'])
95110
96def dnsmasq_cmd(net):111
112def _dnsmasq_cmd(net):
113 """Builds dnsmasq command"""
97 cmd = ['sudo -E dnsmasq',114 cmd = ['sudo -E dnsmasq',
98 ' --strict-order',115 ' --strict-order',
99 ' --bind-interfaces',116 ' --bind-interfaces',
@@ -101,42 +118,48 @@
101 ' --pid-file=%s' % dhcp_file(net['vlan'], 'pid'),118 ' --pid-file=%s' % dhcp_file(net['vlan'], 'pid'),
102 ' --listen-address=%s' % net.dhcp_listen_address,119 ' --listen-address=%s' % net.dhcp_listen_address,
103 ' --except-interface=lo',120 ' --except-interface=lo',
104 ' --dhcp-range=%s,static,600s' % (net.dhcp_range_start),121 ' --dhcp-range=%s,static,600s' % net.dhcp_range_start,
105 ' --dhcp-hostsfile=%s' % dhcp_file(net['vlan'], 'conf'),122 ' --dhcp-hostsfile=%s' % dhcp_file(net['vlan'], 'conf'),
106 ' --dhcp-script=%s' % bin_file('nova-dhcpbridge'),123 ' --dhcp-script=%s' % bin_file('nova-dhcpbridge'),
107 ' --leasefile-ro']124 ' --leasefile-ro']
108 return ''.join(cmd)125 return ''.join(cmd)
109126
110def hostDHCP(network, host, mac):127
111 idx = host.split(".")[-1] # Logically, the idx of instances they've launched in this net128def host_dhcp(network, host, mac):
129 """Return a host string for a network, host, and mac"""
130 # Logically, the idx of instances they've launched in this net
131 idx = host.split(".")[-1]
112 return "%s,%s-%s-%s.novalocal,%s" % \132 return "%s,%s-%s-%s.novalocal,%s" % \
113 (mac, network['user_id'], network['vlan'], idx, host)133 (mac, network['user_id'], network['vlan'], idx, host)
114134
115# todo(ja): if the system has restarted or pid numbers have wrapped135
136# TODO(ja): if the system has restarted or pid numbers have wrapped
116# then you cannot be certain that the pid refers to the137# then you cannot be certain that the pid refers to the
117# dnsmasq. As well, sending a HUP only reloads the hostfile,138# dnsmasq. As well, sending a HUP only reloads the hostfile,
118# so any configuration options (like dchp-range, vlan, ...)139# so any configuration options (like dchp-range, vlan, ...)
119# aren't reloaded140# aren't reloaded
120def start_dnsmasq(network):141def start_dnsmasq(network):
121 """ (re)starts a dnsmasq server for a given network142 """(Re)starts a dnsmasq server for a given network
122143
123 if a dnsmasq instance is already running then send a HUP144 if a dnsmasq instance is already running then send a HUP
124 signal causing it to reload, otherwise spawn a new instance145 signal causing it to reload, otherwise spawn a new instance
125 """146 """
126 with open(dhcp_file(network['vlan'], 'conf'), 'w') as f:147 with open(dhcp_file(network['vlan'], 'conf'), 'w') as f:
127 for host_name in network.hosts:148 for host_name in network.hosts:
128 f.write("%s\n" % hostDHCP(network, host_name, network.hosts[host_name]))149 f.write("%s\n" % host_dhcp(network,
150 host_name,
151 network.hosts[host_name]))
129152
130 pid = dnsmasq_pid_for(network)153 pid = dnsmasq_pid_for(network)
131154
132 # if dnsmasq is already running, then tell it to reload155 # if dnsmasq is already running, then tell it to reload
133 if pid:156 if pid:
134 # todo(ja): use "/proc/%d/cmdline" % (pid) to determine if pid refers157 # TODO(ja): use "/proc/%d/cmdline" % (pid) to determine if pid refers
135 # correct dnsmasq process158 # correct dnsmasq process
136 try:159 try:
137 os.kill(pid, signal.SIGHUP)160 os.kill(pid, signal.SIGHUP)
138 except Exception, e:161 except Exception as exc: # pylint: disable=W0703
139 logging.debug("Hupping dnsmasq threw %s", e)162 logging.debug("Hupping dnsmasq threw %s", exc)
140163
141 # otherwise delete the existing leases file and start dnsmasq164 # otherwise delete the existing leases file and start dnsmasq
142 lease_file = dhcp_file(network['vlan'], 'leases')165 lease_file = dhcp_file(network['vlan'], 'leases')
@@ -146,31 +169,37 @@
146 # FLAGFILE and DNSMASQ_INTERFACE in env169 # FLAGFILE and DNSMASQ_INTERFACE in env
147 env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile,170 env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile,
148 'DNSMASQ_INTERFACE': network['bridge_name']}171 'DNSMASQ_INTERFACE': network['bridge_name']}
149 execute(dnsmasq_cmd(network), addl_env=env)172 execute(_dnsmasq_cmd(network), addl_env=env)
173
150174
151def stop_dnsmasq(network):175def stop_dnsmasq(network):
152 """ stops the dnsmasq instance for a given network """176 """Stops the dnsmasq instance for a given network"""
153 pid = dnsmasq_pid_for(network)177 pid = dnsmasq_pid_for(network)
154178
155 if pid:179 if pid:
156 try:180 try:
157 os.kill(pid, signal.SIGTERM)181 os.kill(pid, signal.SIGTERM)
158 except Exception, e:182 except Exception as exc: # pylint: disable=W0703
159 logging.debug("Killing dnsmasq threw %s", e)183 logging.debug("Killing dnsmasq threw %s", exc)
184
160185
161def dhcp_file(vlan, kind):186def dhcp_file(vlan, kind):
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"""
163188
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))
165190
191
166def bin_file(script):192def bin_file(script):
193 """Return the absolute path to scipt in the bin directory"""
167 return os.path.abspath(os.path.join(__file__, "../../../bin", script))194 return os.path.abspath(os.path.join(__file__, "../../../bin", script))
168195
196
169def dnsmasq_pid_for(network):197def dnsmasq_pid_for(network):
170 """ the pid for prior dnsmasq instance for a vlan,198 """Returns he pid for prior dnsmasq instance for a vlan
171 returns None if no pid file exists199
172200 Returns None if no pid file exists
173 if machine has rebooted pid might be incorrect (caller should check)201
202 If machine has rebooted pid might be incorrect (caller should check)
174 """203 """
175204
176 pid_file = dhcp_file(network['vlan'], 'pid')205 pid_file = dhcp_file(network['vlan'], 'pid')
@@ -178,4 +207,3 @@
178 if os.path.exists(pid_file):207 if os.path.exists(pid_file):
179 with open(pid_file, 'r') as f:208 with open(pid_file, 'r') as f:
180 return int(f.read())209 return int(f.read())
181
182210
=== modified file 'nova/network/model.py'
--- nova/network/model.py 2010-08-08 20:20:50 +0000
+++ nova/network/model.py 2010-08-10 22:55:54 +0000
@@ -57,7 +57,8 @@
5757
5858
59class Vlan(datastore.BasicModel):59class Vlan(datastore.BasicModel):
60 def __init__(self, project, vlan):60 """Tracks vlans assigned to project it the datastore"""
61 def __init__(self, project, vlan): # pylint: disable=W0231
61 """62 """
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,
63 but by a project id, we don't call super-init.64 but by a project id, we don't call super-init.
@@ -67,10 +68,12 @@
6768
68 @property69 @property
69 def identifier(self):70 def identifier(self):
71 """Datastore identifier"""
70 return "%s:%s" % (self.project_id, self.vlan_id)72 return "%s:%s" % (self.project_id, self.vlan_id)
7173
72 @classmethod74 @classmethod
73 def create(cls, project, vlan):75 def create(cls, project, vlan):
76 """Create a Vlan object"""
74 instance = cls(project, vlan)77 instance = cls(project, vlan)
75 instance.save()78 instance.save()
76 return instance79 return instance
@@ -78,6 +81,7 @@
78 @classmethod81 @classmethod
79 @datastore.absorb_connection_error82 @datastore.absorb_connection_error
80 def lookup(cls, project):83 def lookup(cls, project):
84 """Returns object by project if it exists in datastore or None"""
81 set_name = cls._redis_set_name(cls.__name__)85 set_name = cls._redis_set_name(cls.__name__)
82 vlan = datastore.Redis.instance().hget(set_name, project)86 vlan = datastore.Redis.instance().hget(set_name, project)
83 if vlan:87 if vlan:
@@ -88,19 +92,19 @@
88 @classmethod92 @classmethod
89 @datastore.absorb_connection_error93 @datastore.absorb_connection_error
90 def dict_by_project(cls):94 def dict_by_project(cls):
91 """a hash of project:vlan"""95 """A hash of project:vlan"""
92 set_name = cls._redis_set_name(cls.__name__)96 set_name = cls._redis_set_name(cls.__name__)
93 return datastore.Redis.instance().hgetall(set_name)97 return datastore.Redis.instance().hgetall(set_name) or {}
9498
95 @classmethod99 @classmethod
96 @datastore.absorb_connection_error100 @datastore.absorb_connection_error
97 def dict_by_vlan(cls):101 def dict_by_vlan(cls):
98 """a hash of vlan:project"""102 """A hash of vlan:project"""
99 set_name = cls._redis_set_name(cls.__name__)103 set_name = cls._redis_set_name(cls.__name__)
100 retvals = {}104 retvals = {}
101 hashset = datastore.Redis.instance().hgetall(set_name)105 hashset = datastore.Redis.instance().hgetall(set_name) or {}
102 for val in hashset.keys():106 for (key, val) in hashset.iteritems():
103 retvals[hashset[val]] = val107 retvals[val] = key
104 return retvals108 return retvals
105109
106 @classmethod110 @classmethod
@@ -119,40 +123,47 @@
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".
120 """124 """
121 set_name = self._redis_set_name(self.__class__.__name__)125 set_name = self._redis_set_name(self.__class__.__name__)
122 datastore.Redis.instance().hset(set_name, self.project_id, self.vlan_id)126 datastore.Redis.instance().hset(set_name,
127 self.project_id,
128 self.vlan_id)
123129
124 @datastore.absorb_connection_error130 @datastore.absorb_connection_error
125 def destroy(self):131 def destroy(self):
132 """Removes the object from the datastore"""
126 set_name = self._redis_set_name(self.__class__.__name__)133 set_name = self._redis_set_name(self.__class__.__name__)
127 datastore.Redis.instance().hdel(set_name, self.project_id)134 datastore.Redis.instance().hdel(set_name, self.project_id)
128135
129 def subnet(self):136 def subnet(self):
137 """Returns a string containing the subnet"""
130 vlan = int(self.vlan_id)138 vlan = int(self.vlan_id)
131 network = IPy.IP(FLAGS.private_range)139 network = IPy.IP(FLAGS.private_range)
132 start = (vlan-FLAGS.vlan_start) * FLAGS.network_size140 start = (vlan - FLAGS.vlan_start) * FLAGS.network_size
133 # minus one for the gateway.141 # minus one for the gateway.
134 return "%s-%s" % (network[start],142 return "%s-%s" % (network[start],
135 network[start + FLAGS.network_size - 1])143 network[start + FLAGS.network_size - 1])
136144
145
137# CLEANUP:146# CLEANUP:
138# TODO(ja): Save the IPs at the top of each subnet for cloudpipe vpn clients147# TODO(ja): Save the IPs at the top of each subnet for cloudpipe vpn clients
139# TODO(ja): does vlanpool "keeper" need to know the min/max - 148# TODO(ja): does vlanpool "keeper" need to know the min/max -
140# shouldn't FLAGS always win?149# shouldn't FLAGS always win?
141# TODO(joshua): Save the IPs at the top of each subnet for cloudpipe vpn clients
142
143class BaseNetwork(datastore.BasicModel):150class BaseNetwork(datastore.BasicModel):
151 """Implements basic logic for allocating ips in a network"""
144 override_type = 'network'152 override_type = 'network'
145 NUM_STATIC_IPS = 3 # Network, Gateway, and CloudPipe
146153
147 @property154 @property
148 def identifier(self):155 def identifier(self):
156 """Datastore identifier"""
149 return self.network_id157 return self.network_id
150158
151 def default_state(self):159 def default_state(self):
160 """Default values for new objects"""
152 return {'network_id': self.network_id, 'network_str': self.network_str}161 return {'network_id': self.network_id, 'network_str': self.network_str}
153162
154 @classmethod163 @classmethod
164 # pylint: disable=R0913
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):
166 """Create a BaseNetwork object"""
156 network_id = "%s:%s" % (project_id, security_group)167 network_id = "%s:%s" % (project_id, security_group)
157 net = cls(network_id, network_str)168 net = cls(network_id, network_str)
158 net['user_id'] = user_id169 net['user_id'] = user_id
@@ -170,93 +181,124 @@
170181
171 @property182 @property
172 def network(self):183 def network(self):
184 """Returns a string representing the network"""
173 return IPy.IP(self['network_str'])185 return IPy.IP(self['network_str'])
174186
175 @property187 @property
176 def netmask(self):188 def netmask(self):
189 """Returns the netmask of this network"""
177 return self.network.netmask()190 return self.network.netmask()
178191
179 @property192 @property
180 def gateway(self):193 def gateway(self):
194 """Returns the network gateway address"""
181 return self.network[1]195 return self.network[1]
182196
183 @property197 @property
184 def broadcast(self):198 def broadcast(self):
199 """Returns the network broadcast address"""
185 return self.network.broadcast()200 return self.network.broadcast()
186201
187 @property202 @property
188 def bridge_name(self):203 def bridge_name(self):
204 """Returns the bridge associated with this network"""
189 return "br%s" % (self["vlan"])205 return "br%s" % (self["vlan"])
190206
191 @property207 @property
192 def user(self):208 def user(self):
209 """Returns the user associated with this network"""
193 return manager.AuthManager().get_user(self['user_id'])210 return manager.AuthManager().get_user(self['user_id'])
194211
195 @property212 @property
196 def project(self):213 def project(self):
214 """Returns the project associated with this network"""
197 return manager.AuthManager().get_project(self['project_id'])215 return manager.AuthManager().get_project(self['project_id'])
198216
199 @property217 @property
200 def _hosts_key(self):218 def _hosts_key(self):
219 """Datastore key where hosts are stored"""
201 return "network:%s:hosts" % (self['network_str'])220 return "network:%s:hosts" % (self['network_str'])
202221
203 @property222 @property
204 def hosts(self):223 def hosts(self):
224 """Returns a hash of all hosts allocated in this network"""
205 return datastore.Redis.instance().hgetall(self._hosts_key) or {}225 return datastore.Redis.instance().hgetall(self._hosts_key) or {}
206226
207 def _add_host(self, _user_id, _project_id, host, target):227 def _add_host(self, _user_id, _project_id, host, target):
228 """Add a host to the datastore"""
208 datastore.Redis.instance().hset(self._hosts_key, host, target)229 datastore.Redis.instance().hset(self._hosts_key, host, target)
209230
210 def _rem_host(self, host):231 def _rem_host(self, host):
232 """Remove a host from the datastore"""
211 datastore.Redis.instance().hdel(self._hosts_key, host)233 datastore.Redis.instance().hdel(self._hosts_key, host)
212234
213 @property235 @property
214 def assigned(self):236 def assigned(self):
237 """Returns a list of all assigned keys"""
215 return datastore.Redis.instance().hkeys(self._hosts_key)238 return datastore.Redis.instance().hkeys(self._hosts_key)
216239
217 @property240 @property
218 def available(self):241 def available(self):
219 # the .2 address is always CloudPipe242 """Returns a list of all available addresses in the network"""
220 # and the top <n> are for vpn clients243 for idx in range(self.num_bottom_reserved_ips,
221 num_ips = self.num_static_ips244 len(self.network) - self.num_top_reserved_ips):
222 num_clients = FLAGS.cnt_vpn_clients
223 for idx in range(num_ips, len(self.network)-(1 + num_clients)):
224 address = str(self.network[idx])245 address = str(self.network[idx])
225 if not address in self.hosts.keys():246 if not address in self.hosts.keys():
226 yield address247 yield address
227248
228 @property249 @property
229 def num_static_ips(self):250 def num_bottom_reserved_ips(self):
230 return BaseNetwork.NUM_STATIC_IPS251 """Returns number of ips reserved at the bottom of the range"""
252 return 2 # Network, Gateway
253
254 @property
255 def num_top_reserved_ips(self):
256 """Returns number of ips reserved at the top of the range"""
257 return 1 # Broadcast
231258
232 def allocate_ip(self, user_id, project_id, mac):259 def allocate_ip(self, user_id, project_id, mac):
260 """Allocates an ip to a mac address"""
233 for address in self.available:261 for address in self.available:
234 logging.debug("Allocating IP %s to %s" % (address, project_id))262 logging.debug("Allocating IP %s to %s", address, project_id)
235 self._add_host(user_id, project_id, address, mac)263 self._add_host(user_id, project_id, address, mac)
236 self.express(address=address)264 self.express(address=address)
237 return address265 return address
238 raise exception.NoMoreAddresses("Project %s with network %s" %266 raise exception.NoMoreAddresses("Project %s with network %s" %
239 (project_id, str(self.network)))267 (project_id, str(self.network)))
240268
241 def lease_ip(self, ip_str):269 def lease_ip(self, ip_str):
242 logging.debug("Leasing allocated IP %s" % (ip_str))270 """Called when DHCP lease is activated"""
271 logging.debug("Leasing allocated IP %s", ip_str)
243272
244 def release_ip(self, ip_str):273 def release_ip(self, ip_str):
274 """Called when DHCP lease expires
275
276 Removes the ip from the assigned list"""
245 if not ip_str in self.assigned:277 if not ip_str in self.assigned:
246 raise exception.AddressNotAllocated()278 raise exception.AddressNotAllocated()
279 self._rem_host(ip_str)
247 self.deexpress(address=ip_str)280 self.deexpress(address=ip_str)
248 self._rem_host(ip_str)281 logging.debug("Releasing IP %s", ip_str)
249282
250 def deallocate_ip(self, ip_str):283 def deallocate_ip(self, ip_str):
251 # Do nothing for now, cleanup on ip release284 """Deallocates an allocated ip"""
252 pass285 # NOTE(vish): Perhaps we should put the ip into an intermediate
286 # state, so we know that we are pending waiting for
287 # dnsmasq to confirm that it has been released.
288 logging.debug("Deallocating allocated IP %s", ip_str)
253289
254 def list_addresses(self):290 def list_addresses(self):
291 """List all allocated addresses"""
255 for address in self.hosts:292 for address in self.hosts:
256 yield address293 yield address
257294
258 def express(self, address=None): pass295 def express(self, address=None):
259 def deexpress(self, address=None): pass296 """Set up network. Implemented in subclasses"""
297 pass
298
299 def deexpress(self, address=None):
300 """Tear down network. Implemented in subclasses"""
301 pass
260302
261303
262class BridgedNetwork(BaseNetwork):304class BridgedNetwork(BaseNetwork):
@@ -280,7 +322,11 @@
280 override_type = 'network'322 override_type = 'network'
281323
282 @classmethod324 @classmethod
283 def get_network_for_project(cls, user_id, project_id, security_group):325 def get_network_for_project(cls,
326 user_id,
327 project_id,
328 security_group='default'):
329 """Returns network for a given project"""
284 vlan = get_vlan_for_project(project_id)330 vlan = get_vlan_for_project(project_id)
285 network_str = vlan.subnet()331 network_str = vlan.subnet()
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,
@@ -296,30 +342,36 @@
296 linux_net.vlan_create(self)342 linux_net.vlan_create(self)
297 linux_net.bridge_create(self)343 linux_net.bridge_create(self)
298344
345
299class DHCPNetwork(BridgedNetwork):346class DHCPNetwork(BridgedNetwork):
300 """347 """Network supporting DHCP"""
301 properties:
302 dhcp_listen_address: the ip of the gateway / dhcp host
303 dhcp_range_start: the first ip to give out
304 dhcp_range_end: the last ip to give out
305 """
306 bridge_gets_ip = True348 bridge_gets_ip = True
307 override_type = 'network'349 override_type = 'network'
308350
309 def __init__(self, *args, **kwargs):351 def __init__(self, *args, **kwargs):
310 super(DHCPNetwork, self).__init__(*args, **kwargs)352 super(DHCPNetwork, self).__init__(*args, **kwargs)
311 # logging.debug("Initing DHCPNetwork object...")353 if not(os.path.exists(FLAGS.networks_path)):
312 self.dhcp_listen_address = self.network[1]
313 self.dhcp_range_start = self.network[3]
314 self.dhcp_range_end = self.network[-(1 + FLAGS.cnt_vpn_clients)]
315 try:
316 os.makedirs(FLAGS.networks_path)354 os.makedirs(FLAGS.networks_path)
317 # NOTE(todd): I guess this is a lazy way to not have to check if the355
318 # directory exists, but shouldn't we be smarter about356 @property
319 # telling the difference between existing directory and357 def num_bottom_reserved_ips(self):
320 # permission denied? (Errno 17 vs 13, OSError)358 # For cloudpipe
321 except Exception, err:359 return super(DHCPNetwork, self).num_bottom_reserved_ips + 1
322 pass360
361 @property
362 def num_top_reserved_ips(self):
363 return super(DHCPNetwork, self).num_top_reserved_ips + \
364 FLAGS.cnt_vpn_clients
365
366 @property
367 def dhcp_listen_address(self):
368 """Address where dhcp server should listen"""
369 return self.gateway
370
371 @property
372 def dhcp_range_start(self):
373 """Starting address dhcp server should use"""
374 return self.network[self.num_bottom_reserved_ips]
323375
324 def express(self, address=None):376 def express(self, address=None):
325 super(DHCPNetwork, self).express(address=address)377 super(DHCPNetwork, self).express(address=address)
@@ -329,15 +381,17 @@
329 linux_net.start_dnsmasq(self)381 linux_net.start_dnsmasq(self)
330 else:382 else:
331 logging.debug("Not launching dnsmasq: no hosts.")383 logging.debug("Not launching dnsmasq: no hosts.")
332 self.express_cloudpipe()384 self.express_vpn()
333385
334 def allocate_vpn_ip(self, user_id, project_id, mac):386 def allocate_vpn_ip(self, user_id, project_id, mac):
387 """Allocates the reserved ip to a vpn instance"""
335 address = str(self.network[2])388 address = str(self.network[2])
336 self._add_host(user_id, project_id, address, mac)389 self._add_host(user_id, project_id, address, mac)
337 self.express(address=address)390 self.express(address=address)
338 return address391 return address
339392
340 def express_cloudpipe(self):393 def express_vpn(self):
394 """Sets up routing rules for vpn"""
341 private_ip = str(self.network[2])395 private_ip = str(self.network[2])
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"
343 % (private_ip, ))397 % (private_ip, ))
@@ -353,7 +407,9 @@
353 else:407 else:
354 linux_net.start_dnsmasq(self)408 linux_net.start_dnsmasq(self)
355409
410
356class PublicAddress(datastore.BasicModel):411class PublicAddress(datastore.BasicModel):
412 """Represents an elastic ip in the datastore"""
357 override_type = "address"413 override_type = "address"
358414
359 def __init__(self, address):415 def __init__(self, address):
@@ -369,6 +425,7 @@
369425
370 @classmethod426 @classmethod
371 def create(cls, user_id, project_id, address):427 def create(cls, user_id, project_id, address):
428 """Creates a PublicAddress object"""
372 addr = cls(address)429 addr = cls(address)
373 addr['user_id'] = user_id430 addr['user_id'] = user_id
374 addr['project_id'] = project_id431 addr['project_id'] = project_id
@@ -379,35 +436,34 @@
379436
380437
381DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)]438DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)]
439
440
382class PublicNetworkController(BaseNetwork):441class PublicNetworkController(BaseNetwork):
442 """Handles elastic ips"""
383 override_type = 'network'443 override_type = 'network'
384444
385 def __init__(self, *args, **kwargs):445 def __init__(self, *args, **kwargs):
386 network_id = "public:default"446 network_id = "public:default"
387 super(PublicNetworkController, self).__init__(network_id, 447 super(PublicNetworkController, self).__init__(network_id,
388 FLAGS.public_range)448 FLAGS.public_range, *args, **kwargs)
389 self['user_id'] = "public"449 self['user_id'] = "public"
390 self['project_id'] = "public"450 self['project_id'] = "public"
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',
452 time.gmtime())
392 self["vlan"] = FLAGS.public_vlan453 self["vlan"] = FLAGS.public_vlan
393 self.save()454 self.save()
394 self.express()455 self.express()
395456
396 @property457 @property
397 def available(self):
398 for idx in range(2, len(self.network)-1):
399 address = str(self.network[idx])
400 if not address in self.hosts.keys():
401 yield address
402
403 @property
404 def host_objs(self):458 def host_objs(self):
459 """Returns assigned addresses as PublicAddress objects"""
405 for address in self.assigned:460 for address in self.assigned:
406 yield PublicAddress(address)461 yield PublicAddress(address)
407462
408 def get_host(self, host):463 def get_host(self, public_ip):
409 if host in self.assigned:464 """Returns a specific public ip as PublicAddress object"""
410 return PublicAddress(host)465 if public_ip in self.assigned:
466 return PublicAddress(public_ip)
411 return None467 return None
412468
413 def _add_host(self, user_id, project_id, host, _target):469 def _add_host(self, user_id, project_id, host, _target):
@@ -423,9 +479,10 @@
423 self.release_ip(ip_str)479 self.release_ip(ip_str)
424480
425 def associate_address(self, public_ip, private_ip, instance_id):481 def associate_address(self, public_ip, private_ip, instance_id):
482 """Associates a public ip to a private ip and instance id"""
426 if not public_ip in self.assigned:483 if not public_ip in self.assigned:
427 raise exception.AddressNotAllocated()484 raise exception.AddressNotAllocated()
428 # TODO(joshua): Keep an index going both ways485 # TODO(josh): Keep an index going both ways
429 for addr in self.host_objs:486 for addr in self.host_objs:
430 if addr.get('private_ip', None) == private_ip:487 if addr.get('private_ip', None) == private_ip:
431 raise exception.AddressAlreadyAssociated()488 raise exception.AddressAlreadyAssociated()
@@ -438,6 +495,7 @@
438 self.express(address=public_ip)495 self.express(address=public_ip)
439496
440 def disassociate_address(self, public_ip):497 def disassociate_address(self, public_ip):
498 """Disassociates a public ip with its private ip"""
441 if not public_ip in self.assigned:499 if not public_ip in self.assigned:
442 raise exception.AddressNotAllocated()500 raise exception.AddressNotAllocated()
443 addr = self.get_host(public_ip)501 addr = self.get_host(public_ip)
@@ -453,7 +511,7 @@
453 if address:511 if address:
454 addresses = [self.get_host(address)]512 addresses = [self.get_host(address)]
455 for addr in addresses:513 for addr in addresses:
456 if addr.get('private_ip','available') == 'available':514 if addr.get('private_ip', 'available') == 'available':
457 continue515 continue
458 public_ip = addr['address']516 public_ip = addr['address']
459 private_ip = addr['private_ip']517 private_ip = addr['private_ip']
@@ -462,7 +520,7 @@
462 % (public_ip, private_ip))520 % (public_ip, private_ip))
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"
464 % (private_ip, public_ip))522 % (private_ip, public_ip))
465 # TODO: Get these from the secgroup datastore entries523 # TODO(joshua): Get these from the secgroup datastore entries
466 linux_net.confirm_rule("FORWARD -d %s -p icmp -j ACCEPT"524 linux_net.confirm_rule("FORWARD -d %s -p icmp -j ACCEPT"
467 % (private_ip))525 % (private_ip))
468 for (protocol, port) in DEFAULT_PORTS:526 for (protocol, port) in DEFAULT_PORTS:
@@ -485,19 +543,18 @@
485 % (private_ip, protocol, port))543 % (private_ip, protocol, port))
486544
487545
488# FIXME(todd): does this present a race condition, or is there some piece of546# FIXME(todd): does this present a race condition, or is there some
489# architecture that mitigates it (only one queue listener per net)?547# piece of architecture that mitigates it (only one queue
548# listener per net)?
490def get_vlan_for_project(project_id):549def get_vlan_for_project(project_id):
491 """550 """Allocate vlan IDs to individual users"""
492 Allocate vlan IDs to individual users.
493 """
494 vlan = Vlan.lookup(project_id)551 vlan = Vlan.lookup(project_id)
495 if vlan:552 if vlan:
496 return vlan553 return vlan
497 known_vlans = Vlan.dict_by_vlan()554 known_vlans = Vlan.dict_by_vlan()
498 for vnum in range(FLAGS.vlan_start, FLAGS.vlan_end):555 for vnum in range(FLAGS.vlan_start, FLAGS.vlan_end):
499 vstr = str(vnum)556 vstr = str(vnum)
500 if not known_vlans.has_key(vstr):557 if not vstr in known_vlans:
501 return Vlan.create(project_id, vnum)558 return Vlan.create(project_id, vnum)
502 old_project_id = known_vlans[vstr]559 old_project_id = known_vlans[vstr]
503 if not manager.AuthManager().get_project(old_project_id):560 if not manager.AuthManager().get_project(old_project_id):
@@ -521,8 +578,9 @@
521 return Vlan.create(project_id, vnum)578 return Vlan.create(project_id, vnum)
522 raise exception.AddressNotAllocated("Out of VLANs")579 raise exception.AddressNotAllocated("Out of VLANs")
523580
581
524def get_project_network(project_id, security_group='default'):582def get_project_network(project_id, security_group='default'):
525 """ get a project's private network, allocating one if needed """583 """Gets a project's private network, allocating one if needed"""
526 project = manager.AuthManager().get_project(project_id)584 project = manager.AuthManager().get_project(project_id)
527 if not project:585 if not project:
528 raise nova_exception.NotFound("Project %s doesn't exist." % project_id)586 raise nova_exception.NotFound("Project %s doesn't exist." % project_id)
@@ -533,28 +591,29 @@
533591
534592
535def get_network_by_address(address):593def get_network_by_address(address):
594 """Gets the network for a given private ip"""
536 # TODO(vish): This is completely the wrong way to do this, but595 # TODO(vish): This is completely the wrong way to do this, but
537 # I'm getting the network binary working before I596 # I'm getting the network binary working before I
538 # tackle doing this the right way.597 # tackle doing this the right way.
539 logging.debug("Get Network By Address: %s" % address)598 logging.debug("Get Network By Address: %s", address)
540 for project in manager.AuthManager().get_projects():599 for project in manager.AuthManager().get_projects():
541 net = get_project_network(project.id)600 net = get_project_network(project.id)
542 if address in net.assigned:601 if address in net.assigned:
543 logging.debug("Found %s in %s" % (address, project.id))602 logging.debug("Found %s in %s", address, project.id)
544 return net603 return net
545 raise exception.AddressNotAllocated()604 raise exception.AddressNotAllocated()
546605
547606
548def get_network_by_interface(iface, security_group='default'):607def get_network_by_interface(iface, security_group='default'):
608 """Gets the network for a given interface"""
549 vlan = iface.rpartition("br")[2]609 vlan = iface.rpartition("br")[2]
550 project_id = Vlan.dict_by_vlan().get(vlan)610 project_id = Vlan.dict_by_vlan().get(vlan)
551 return get_project_network(project_id, security_group)611 return get_project_network(project_id, security_group)
552612
553613
554
555def get_public_ip_for_instance(instance_id):614def get_public_ip_for_instance(instance_id):
556 # FIXME: this should be a lookup - iteration won't scale615 """Gets the public ip for a given instance"""
616 # FIXME(josh): this should be a lookup - iteration won't scale
557 for address_record in PublicAddress.all():617 for address_record in PublicAddress.all():
558 if address_record.get('instance_id', 'available') == instance_id:618 if address_record.get('instance_id', 'available') == instance_id:
559 return address_record['address']619 return address_record['address']
560
561620
=== modified file 'nova/network/service.py'
--- nova/network/service.py 2010-08-05 19:29:50 +0000
+++ nova/network/service.py 2010-08-10 22:55:54 +0000
@@ -17,7 +17,7 @@
17# under the License.17# under the License.
1818
19"""19"""
20Network Nodes are responsible for allocating ips and setting up network20Network Hosts are responsible for allocating ips and setting up network
21"""21"""
2222
23from nova import datastore23from nova import datastore
@@ -38,7 +38,7 @@
38flags.DEFINE_string('flat_network_bridge', 'br100',38flags.DEFINE_string('flat_network_bridge', 'br100',
39 'Bridge for simple network instances')39 'Bridge for simple network instances')
40flags.DEFINE_list('flat_network_ips',40flags.DEFINE_list('flat_network_ips',
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'],
42 'Available ips for simple network')42 'Available ips for simple network')
43flags.DEFINE_string('flat_network_network', '192.168.0.0',43flags.DEFINE_string('flat_network_network', '192.168.0.0',
44 'Network for simple network')44 'Network for simple network')
@@ -51,26 +51,34 @@
51flags.DEFINE_string('flat_network_dns', '8.8.4.4',51flags.DEFINE_string('flat_network_dns', '8.8.4.4',
52 'Dns for simple network')52 'Dns for simple network')
5353
54
54def type_to_class(network_type):55def type_to_class(network_type):
56 """Convert a network_type string into an actual Python class"""
55 if network_type == 'flat':57 if network_type == 'flat':
56 return FlatNetworkService58 return FlatNetworkService
57 elif network_type == 'vlan':59 elif network_type == 'vlan':
58 return VlanNetworkService60 return VlanNetworkService
59 raise NotFound("Couldn't find %s network type" % network_type)61 raise NotFound("Couldn't find %s network type" % network_type)
6062
6163
62def setup_compute_network(network_type, user_id, project_id, security_group):64def setup_compute_network(network_type, user_id, project_id, security_group):
65 """Sets up the network on a compute host"""
63 srv = type_to_class(network_type)66 srv = type_to_class(network_type)
64 srv.setup_compute_network(network_type, user_id, project_id, security_group)67 srv.setup_compute_network(network_type,
68 user_id,
69 project_id,
70 security_group)
6571
6672
67def get_host_for_project(project_id):73def get_host_for_project(project_id):
74 """Get host allocated to project from datastore"""
68 redis = datastore.Redis.instance()75 redis = datastore.Redis.instance()
69 return redis.get(_host_key(project_id))76 return redis.get(_host_key(project_id))
7077
7178
72def _host_key(project_id):79def _host_key(project_id):
73 return "network_host:%s" % project_id80 """Returns redis host key for network"""
81 return "networkhost:%s" % project_id
7482
7583
76class BaseNetworkService(service.Service):84class BaseNetworkService(service.Service):
@@ -80,6 +88,7 @@
80 """88 """
81 def __init__(self, *args, **kwargs):89 def __init__(self, *args, **kwargs):
82 self.network = model.PublicNetworkController()90 self.network = model.PublicNetworkController()
91 super(BaseNetworkService, self).__init__(*args, **kwargs)
8392
84 def set_network_host(self, user_id, project_id, *args, **kwargs):93 def set_network_host(self, user_id, project_id, *args, **kwargs):
85 """Safely sets the host of the projects network"""94 """Safely sets the host of the projects network"""
@@ -109,7 +118,7 @@
109 pass118 pass
110119
111 @classmethod120 @classmethod
112 def setup_compute_network(self, user_id, project_id, security_group,121 def setup_compute_network(cls, user_id, project_id, security_group,
113 *args, **kwargs):122 *args, **kwargs):
114 """Sets up matching network for compute hosts"""123 """Sets up matching network for compute hosts"""
115 raise NotImplementedError()124 raise NotImplementedError()
@@ -138,7 +147,7 @@
138 """Basic network where no vlans are used"""147 """Basic network where no vlans are used"""
139148
140 @classmethod149 @classmethod
141 def setup_compute_network(self, user_id, project_id, security_group,150 def setup_compute_network(cls, user_id, project_id, security_group,
142 *args, **kwargs):151 *args, **kwargs):
143 """Network is created manually"""152 """Network is created manually"""
144 pass153 pass
@@ -175,26 +184,28 @@
175 """Returns an ip to the pool"""184 """Returns an ip to the pool"""
176 datastore.Redis.instance().sadd('ips', fixed_ip)185 datastore.Redis.instance().sadd('ips', fixed_ip)
177186
187
178class VlanNetworkService(BaseNetworkService):188class VlanNetworkService(BaseNetworkService):
179 """Vlan network with dhcp"""189 """Vlan network with dhcp"""
180 # NOTE(vish): A lot of the interactions with network/model.py can be190 # NOTE(vish): A lot of the interactions with network/model.py can be
181 # simplified and improved. Also there it may be useful191 # simplified and improved. Also there it may be useful
182 # to support vlans separately from dhcp, instead of having192 # to support vlans separately from dhcp, instead of having
183 # both of them together in this class.193 # both of them together in this class.
194 # pylint: disable=W0221
184 def allocate_fixed_ip(self, user_id, project_id,195 def allocate_fixed_ip(self, user_id, project_id,
185 security_group='default',196 security_group='default',
186 vpn=False, *args, **kwargs):197 is_vpn=False, *args, **kwargs):
187 """Gets a fixed ip from the pool """198 """Gets a fixed ip from the pool"""
188 mac = utils.generate_mac()199 mac = utils.generate_mac()
189 net = model.get_project_network(project_id)200 net = model.get_project_network(project_id)
190 if vpn:201 if is_vpn:
191 fixed_ip = net.allocate_vpn_ip(user_id, project_id, mac)202 fixed_ip = net.allocate_vpn_ip(user_id, project_id, mac)
192 else:203 else:
193 fixed_ip = net.allocate_ip(user_id, project_id, mac)204 fixed_ip = net.allocate_ip(user_id, project_id, mac)
194 return {'network_type': FLAGS.network_type,205 return {'network_type': FLAGS.network_type,
195 'bridge_name': net['bridge_name'],206 'bridge_name': net['bridge_name'],
196 'mac_address': mac,207 'mac_address': mac,
197 'private_dns_name' : fixed_ip}208 'private_dns_name': fixed_ip}
198209
199 def deallocate_fixed_ip(self, fixed_ip,210 def deallocate_fixed_ip(self, fixed_ip,
200 *args, **kwargs):211 *args, **kwargs):
@@ -202,9 +213,11 @@
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)
203214
204 def lease_ip(self, address):215 def lease_ip(self, address):
216 """Called by bridge when ip is leased"""
205 return model.get_network_by_address(address).lease_ip(address)217 return model.get_network_by_address(address).lease_ip(address)
206218
207 def release_ip(self, address):219 def release_ip(self, address):
220 """Called by bridge when ip is released"""
208 return model.get_network_by_address(address).release_ip(address)221 return model.get_network_by_address(address).release_ip(address)
209222
210 def restart_nets(self):223 def restart_nets(self):
@@ -218,7 +231,7 @@
218 vpn.NetworkData.create(project_id)231 vpn.NetworkData.create(project_id)
219232
220 @classmethod233 @classmethod
221 def setup_compute_network(self, user_id, project_id, security_group,234 def setup_compute_network(cls, user_id, project_id, security_group,
222 *args, **kwargs):235 *args, **kwargs):
223 """Sets up matching network for compute hosts"""236 """Sets up matching network for compute hosts"""
224 # NOTE(vish): Use BridgedNetwork instead of DHCPNetwork because237 # NOTE(vish): Use BridgedNetwork instead of DHCPNetwork because
225238
=== modified file 'nova/network/vpn.py'
--- nova/network/vpn.py 2010-08-05 19:29:50 +0000
+++ nova/network/vpn.py 2010-08-10 22:55:54 +0000
@@ -33,7 +33,9 @@
33flags.DEFINE_integer('vpn_end_port', 2000,33flags.DEFINE_integer('vpn_end_port', 2000,
34 'End port for the cloudpipe VPN servers')34 'End port for the cloudpipe VPN servers')
3535
36
36class NoMorePorts(exception.Error):37class NoMorePorts(exception.Error):
38 """No ports available to allocate for the given ip"""
37 pass39 pass
3840
3941
@@ -67,34 +69,44 @@
67 return network_data69 return network_data
6870
69 @classmethod71 @classmethod
70 def find_free_port_for_ip(cls, ip):72 def find_free_port_for_ip(cls, vpn_ip):
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"""
72 # TODO(vish): these redis commands should be generalized and74 # TODO(vish): these redis commands should be generalized and
73 # placed into a base class. Conceptually, it is75 # placed into a base class. Conceptually, it is
74 # similar to an association, but we are just76 # similar to an association, but we are just
75 # storing a set of values instead of keys that77 # storing a set of values instead of keys that
76 # should be turned into objects.78 # should be turned into objects.
77 redis = datastore.Redis.instance()79 cls._ensure_set_exists(vpn_ip)
78 key = 'ip:%s:ports' % ip80
81 port = datastore.Redis.instance().spop(cls._redis_ports_key(vpn_ip))
82 if not port:
83 raise NoMorePorts()
84 return port
85
86 @classmethod
87 def _redis_ports_key(cls, vpn_ip):
88 """Key that ports are stored under in redis"""
89 return 'ip:%s:ports' % vpn_ip
90
91 @classmethod
92 def _ensure_set_exists(cls, vpn_ip):
93 """Creates the set of ports for the ip if it doesn't already exist"""
79 # TODO(vish): these ports should be allocated through an admin94 # TODO(vish): these ports should be allocated through an admin
80 # command instead of a flag95 # command instead of a flag
81 if (not redis.exists(key) and96 redis = datastore.Redis.instance()
82 not redis.exists(cls._redis_association_name('ip', ip))):97 if (not redis.exists(cls._redis_ports_key(vpn_ip)) and
98 not redis.exists(cls._redis_association_name('ip', vpn_ip))):
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):
84 redis.sadd(key, i)100 redis.sadd(cls._redis_ports_key(vpn_ip), i)
85
86 port = redis.spop(key)
87 if not port:
88 raise NoMorePorts()
89 return port
90101
91 @classmethod102 @classmethod
92 def num_ports_for_ip(cls, ip):103 def num_ports_for_ip(cls, vpn_ip):
93 """Calculates the number of free ports for a given ip"""104 """Calculates the number of free ports for a given ip"""
94 return datastore.Redis.instance().scard('ip:%s:ports' % ip)105 cls._ensure_set_exists(vpn_ip)
106 return datastore.Redis.instance().scard('ip:%s:ports' % vpn_ip)
95107
96 @property108 @property
97 def ip(self):109 def ip(self): # pylint: disable=C0103
98 """The ip assigned to the project"""110 """The ip assigned to the project"""
99 return self['ip']111 return self['ip']
100112
@@ -113,4 +125,3 @@
113 self.unassociate_with('ip', self.ip)125 self.unassociate_with('ip', self.ip)
114 datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port)126 datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port)
115 super(NetworkData, self).destroy()127 super(NetworkData, self).destroy()
116
117128
=== modified file 'nova/tests/network_unittest.py'
--- nova/tests/network_unittest.py 2010-08-06 21:31:03 +0000
+++ nova/tests/network_unittest.py 2010-08-10 22:55:54 +0000
@@ -15,7 +15,9 @@
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations16# License for the specific language governing permissions and limitations
17# under the License.17# under the License.
1818"""
19Unit Tests for network code
20"""
19import IPy21import IPy
20import os22import os
21import logging23import logging
@@ -31,8 +33,10 @@
3133
32FLAGS = flags.FLAGS34FLAGS = flags.FLAGS
3335
36
34class NetworkTestCase(test.TrialTestCase):37class NetworkTestCase(test.TrialTestCase):
35 def setUp(self):38 """Test cases for network code"""
39 def setUp(self): # pylint: disable=C0103
36 super(NetworkTestCase, self).setUp()40 super(NetworkTestCase, self).setUp()
37 # NOTE(vish): if you change these flags, make sure to change the41 # NOTE(vish): if you change these flags, make sure to change the
38 # flags in the corresponding section in nova-dhcpbridge42 # flags in the corresponding section in nova-dhcpbridge
@@ -43,7 +47,6 @@
43 network_size=32)47 network_size=32)
44 logging.getLogger().setLevel(logging.DEBUG)48 logging.getLogger().setLevel(logging.DEBUG)
45 self.manager = manager.AuthManager()49 self.manager = manager.AuthManager()
46 self.dnsmasq = FakeDNSMasq()
47 self.user = self.manager.create_user('netuser', 'netuser', 'netuser')50 self.user = self.manager.create_user('netuser', 'netuser', 'netuser')
48 self.projects = []51 self.projects = []
49 self.projects.append(self.manager.create_project('netuser',52 self.projects.append(self.manager.create_project('netuser',
@@ -54,47 +57,49 @@
54 self.projects.append(self.manager.create_project(name,57 self.projects.append(self.manager.create_project(name,
55 'netuser',58 'netuser',
56 name))59 name))
57 self.network = model.PublicNetworkController()60 vpn.NetworkData.create(self.projects[i].id)
58 self.service = service.VlanNetworkService()61 self.service = service.VlanNetworkService()
5962
60 def tearDown(self):63 def tearDown(self): # pylint: disable=C0103
61 super(NetworkTestCase, self).tearDown()64 super(NetworkTestCase, self).tearDown()
62 for project in self.projects:65 for project in self.projects:
63 self.manager.delete_project(project)66 self.manager.delete_project(project)
64 self.manager.delete_user(self.user)67 self.manager.delete_user(self.user)
6568
66 def test_public_network_allocation(self):69 def test_public_network_allocation(self):
70 """Makes sure that we can allocaate a public ip"""
67 pubnet = IPy.IP(flags.FLAGS.public_range)71 pubnet = IPy.IP(flags.FLAGS.public_range)
68 address = self.network.allocate_ip(self.user.id, self.projects[0].id, "public")72 address = self.service.allocate_elastic_ip(self.user.id,
73 self.projects[0].id)
69 self.assertTrue(IPy.IP(address) in pubnet)74 self.assertTrue(IPy.IP(address) in pubnet)
70 self.assertTrue(IPy.IP(address) in self.network.network)
7175
72 def test_allocate_deallocate_fixed_ip(self):76 def test_allocate_deallocate_fixed_ip(self):
73 result = yield self.service.allocate_fixed_ip(77 """Makes sure that we can allocate and deallocate a fixed ip"""
78 result = self.service.allocate_fixed_ip(
74 self.user.id, self.projects[0].id)79 self.user.id, self.projects[0].id)
75 address = result['private_dns_name']80 address = result['private_dns_name']
76 mac = result['mac_address']81 mac = result['mac_address']
77 logging.debug("Was allocated %s" % (address))
78 net = model.get_project_network(self.projects[0].id, "default")82 net = model.get_project_network(self.projects[0].id, "default")
79 self.assertEqual(True, is_in_project(address, self.projects[0].id))83 self.assertEqual(True, is_in_project(address, self.projects[0].id))
80 hostname = "test-host"84 hostname = "test-host"
81 self.dnsmasq.issue_ip(mac, address, hostname, net.bridge_name)85 issue_ip(mac, address, hostname, net.bridge_name)
82 rv = self.service.deallocate_fixed_ip(address)86 self.service.deallocate_fixed_ip(address)
8387
84 # Doesn't go away until it's dhcp released88 # Doesn't go away until it's dhcp released
85 self.assertEqual(True, is_in_project(address, self.projects[0].id))89 self.assertEqual(True, is_in_project(address, self.projects[0].id))
8690
87 self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)91 release_ip(mac, address, hostname, net.bridge_name)
88 self.assertEqual(False, is_in_project(address, self.projects[0].id))92 self.assertEqual(False, is_in_project(address, self.projects[0].id))
8993
90 def test_range_allocation(self):94 def test_side_effects(self):
91 hostname = "test-host"95 """Ensures allocating and releasing has no side effects"""
92 result = yield self.service.allocate_fixed_ip(96 hostname = "side-effect-host"
93 self.user.id, self.projects[0].id)97 result = self.service.allocate_fixed_ip(self.user.id,
98 self.projects[0].id)
94 mac = result['mac_address']99 mac = result['mac_address']
95 address = result['private_dns_name']100 address = result['private_dns_name']
96 result = yield self.service.allocate_fixed_ip(101 result = self.service.allocate_fixed_ip(self.user,
97 self.user, self.projects[1].id)102 self.projects[1].id)
98 secondmac = result['mac_address']103 secondmac = result['mac_address']
99 secondaddress = result['private_dns_name']104 secondaddress = result['private_dns_name']
100105
@@ -102,66 +107,75 @@
102 secondnet = model.get_project_network(self.projects[1].id, "default")107 secondnet = model.get_project_network(self.projects[1].id, "default")
103108
104 self.assertEqual(True, is_in_project(address, self.projects[0].id))109 self.assertEqual(True, is_in_project(address, self.projects[0].id))
105 self.assertEqual(True, is_in_project(secondaddress, self.projects[1].id))110 self.assertEqual(True, is_in_project(secondaddress,
111 self.projects[1].id))
106 self.assertEqual(False, is_in_project(address, self.projects[1].id))112 self.assertEqual(False, is_in_project(address, self.projects[1].id))
107113
108 # Addresses are allocated before they're issued114 # Addresses are allocated before they're issued
109 self.dnsmasq.issue_ip(mac, address, hostname, net.bridge_name)115 issue_ip(mac, address, hostname, net.bridge_name)
110 self.dnsmasq.issue_ip(secondmac, secondaddress,116 issue_ip(secondmac, secondaddress, hostname, secondnet.bridge_name)
111 hostname, secondnet.bridge_name)
112117
113 rv = self.service.deallocate_fixed_ip(address)118 self.service.deallocate_fixed_ip(address)
114 self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)119 release_ip(mac, address, hostname, net.bridge_name)
115 self.assertEqual(False, is_in_project(address, self.projects[0].id))120 self.assertEqual(False, is_in_project(address, self.projects[0].id))
116121
117 # First address release shouldn't affect the second122 # First address release shouldn't affect the second
118 self.assertEqual(True, is_in_project(secondaddress, self.projects[1].id))123 self.assertEqual(True, is_in_project(secondaddress,
124 self.projects[1].id))
119125
120 rv = self.service.deallocate_fixed_ip(secondaddress)126 self.service.deallocate_fixed_ip(secondaddress)
121 self.dnsmasq.release_ip(secondmac, secondaddress,127 release_ip(secondmac, secondaddress, hostname, secondnet.bridge_name)
122 hostname, secondnet.bridge_name)128 self.assertEqual(False, is_in_project(secondaddress,
123 self.assertEqual(False, is_in_project(secondaddress, self.projects[1].id))129 self.projects[1].id))
124130
125 def test_subnet_edge(self):131 def test_subnet_edge(self):
126 result = yield self.service.allocate_fixed_ip(self.user.id,132 """Makes sure that private ips don't overlap"""
133 result = self.service.allocate_fixed_ip(self.user.id,
127 self.projects[0].id)134 self.projects[0].id)
128 firstaddress = result['private_dns_name']135 firstaddress = result['private_dns_name']
129 hostname = "toomany-hosts"136 hostname = "toomany-hosts"
130 for i in range(1,5):137 for i in range(1, 5):
131 project_id = self.projects[i].id138 project_id = self.projects[i].id
132 result = yield self.service.allocate_fixed_ip(139 result = self.service.allocate_fixed_ip(
133 self.user, project_id)140 self.user, project_id)
134 mac = result['mac_address']141 mac = result['mac_address']
135 address = result['private_dns_name']142 address = result['private_dns_name']
136 result = yield self.service.allocate_fixed_ip(143 result = self.service.allocate_fixed_ip(
137 self.user, project_id)144 self.user, project_id)
138 mac2 = result['mac_address']145 mac2 = result['mac_address']
139 address2 = result['private_dns_name']146 address2 = result['private_dns_name']
140 result = yield self.service.allocate_fixed_ip(147 result = self.service.allocate_fixed_ip(
141 self.user, project_id)148 self.user, project_id)
142 mac3 = result['mac_address']149 mac3 = result['mac_address']
143 address3 = result['private_dns_name']150 address3 = result['private_dns_name']
144 self.assertEqual(False, is_in_project(address, self.projects[0].id))
145 self.assertEqual(False, is_in_project(address2, self.projects[0].id))
146 self.assertEqual(False, is_in_project(address3, self.projects[0].id))
147 rv = self.service.deallocate_fixed_ip(address)
148 rv = self.service.deallocate_fixed_ip(address2)
149 rv = self.service.deallocate_fixed_ip(address3)
150 net = model.get_project_network(project_id, "default")151 net = model.get_project_network(project_id, "default")
151 self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)152 issue_ip(mac, address, hostname, net.bridge_name)
152 self.dnsmasq.release_ip(mac2, address2, hostname, net.bridge_name)153 issue_ip(mac2, address2, hostname, net.bridge_name)
153 self.dnsmasq.release_ip(mac3, address3, hostname, net.bridge_name)154 issue_ip(mac3, address3, hostname, net.bridge_name)
155 self.assertEqual(False, is_in_project(address,
156 self.projects[0].id))
157 self.assertEqual(False, is_in_project(address2,
158 self.projects[0].id))
159 self.assertEqual(False, is_in_project(address3,
160 self.projects[0].id))
161 self.service.deallocate_fixed_ip(address)
162 self.service.deallocate_fixed_ip(address2)
163 self.service.deallocate_fixed_ip(address3)
164 release_ip(mac, address, hostname, net.bridge_name)
165 release_ip(mac2, address2, hostname, net.bridge_name)
166 release_ip(mac3, address3, hostname, net.bridge_name)
154 net = model.get_project_network(self.projects[0].id, "default")167 net = model.get_project_network(self.projects[0].id, "default")
155 rv = self.service.deallocate_fixed_ip(firstaddress)168 self.service.deallocate_fixed_ip(firstaddress)
156 self.dnsmasq.release_ip(mac, firstaddress, hostname, net.bridge_name)169 release_ip(mac, firstaddress, hostname, net.bridge_name)
157170
158 def test_212_vpn_ip_and_port_looks_valid(self):171 def test_vpn_ip_and_port_looks_valid(self):
159 vpn.NetworkData.create(self.projects[0].id)172 """Ensure the vpn ip and port are reasonable"""
160 self.assert_(self.projects[0].vpn_ip)173 self.assert_(self.projects[0].vpn_ip)
161 self.assert_(self.projects[0].vpn_port >= FLAGS.vpn_start_port)174 self.assert_(self.projects[0].vpn_port >= FLAGS.vpn_start_port)
162 self.assert_(self.projects[0].vpn_port <= FLAGS.vpn_end_port)175 self.assert_(self.projects[0].vpn_port <= FLAGS.vpn_end_port)
163176
164 def test_too_many_vpns(self):177 def test_too_many_vpns(self):
178 """Ensure error is raised if we run out of vpn ports"""
165 vpns = []179 vpns = []
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)):
167 vpns.append(vpn.NetworkData.create("vpnuser%s" % i))181 vpns.append(vpn.NetworkData.create("vpnuser%s" % i))
@@ -169,84 +183,101 @@
169 for network_datum in vpns:183 for network_datum in vpns:
170 network_datum.destroy()184 network_datum.destroy()
171185
172 def test_release_before_deallocate(self):186 def test_ips_are_reused(self):
173 pass187 """Makes sure that ip addresses that are deallocated get reused"""
174188 result = self.service.allocate_fixed_ip(
175 def test_deallocate_before_issued(self):189 self.user.id, self.projects[0].id)
176 pass190 mac = result['mac_address']
177191 address = result['private_dns_name']
178 def test_too_many_addresses(self):192
179 """193 hostname = "reuse-host"
180 Here, we test that a proper NoMoreAddresses exception is raised.194 net = model.get_project_network(self.projects[0].id, "default")
181195
182 However, the number of available IP addresses depends on the test196 issue_ip(mac, address, hostname, net.bridge_name)
197 self.service.deallocate_fixed_ip(address)
198 release_ip(mac, address, hostname, net.bridge_name)
199
200 result = self.service.allocate_fixed_ip(
201 self.user, self.projects[0].id)
202 secondmac = result['mac_address']
203 secondaddress = result['private_dns_name']
204 self.assertEqual(address, secondaddress)
205 self.service.deallocate_fixed_ip(secondaddress)
206 issue_ip(secondmac, secondaddress, hostname, net.bridge_name)
207 release_ip(secondmac, secondaddress, hostname, net.bridge_name)
208
209 def test_available_ips(self):
210 """Make sure the number of available ips for the network is correct
211
212 The number of available IP addresses depends on the test
183 environment's setup.213 environment's setup.
184214
185 Network size is set in test fixture's setUp method.215 Network size is set in test fixture's setUp method.
186216
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.
188218 services (network, gateway, CloudPipe, broadcast)
189 And there are NUM_STATIC_IPS that are always reserved by Nova for the necessary
190 services (gateway, CloudPipe, etc)
191
192 So we should get flags.network_size - (NUM_STATIC_IPS +
193 NUM_PREALLOCATED_IPS +
194 NUM_RESERVED_VPN_IPS)
195 usable addresses
196 """219 """
197 net = model.get_project_network(self.projects[0].id, "default")220 net = model.get_project_network(self.projects[0].id, "default")
198
199 # Determine expected number of available IP addresses
200 num_static_ips = net.num_static_ips
201 num_preallocated_ips = len(net.hosts.keys())221 num_preallocated_ips = len(net.hosts.keys())
202 num_reserved_vpn_ips = flags.FLAGS.cnt_vpn_clients222 net_size = flags.FLAGS.network_size
203 num_available_ips = flags.FLAGS.network_size - (num_static_ips +223 num_available_ips = net_size - (net.num_bottom_reserved_ips +
204 num_preallocated_ips +224 num_preallocated_ips +
205 num_reserved_vpn_ips)225 net.num_top_reserved_ips)
226 self.assertEqual(num_available_ips, len(list(net.available)))
227
228 def test_too_many_addresses(self):
229 """Test for a NoMoreAddresses exception when all fixed ips are used.
230 """
231 net = model.get_project_network(self.projects[0].id, "default")
206232
207 hostname = "toomany-hosts"233 hostname = "toomany-hosts"
208 macs = {}234 macs = {}
209 addresses = {}235 addresses = {}
210 for i in range(0, (num_available_ips - 1)):236 # Number of availaible ips is len of the available list
211 result = yield self.service.allocate_fixed_ip(self.user.id, self.projects[0].id)237 num_available_ips = len(list(net.available))
238 for i in range(num_available_ips):
239 result = self.service.allocate_fixed_ip(self.user.id,
240 self.projects[0].id)
212 macs[i] = result['mac_address']241 macs[i] = result['mac_address']
213 addresses[i] = result['private_dns_name']242 addresses[i] = result['private_dns_name']
214 self.dnsmasq.issue_ip(macs[i], addresses[i], hostname, net.bridge_name)243 issue_ip(macs[i], addresses[i], hostname, net.bridge_name)
215244
216 self.assertFailure(self.service.allocate_fixed_ip(self.user.id, self.projects[0].id), NoMoreAddresses)245 self.assertEqual(len(list(net.available)), 0)
217246 self.assertRaises(NoMoreAddresses, self.service.allocate_fixed_ip,
218 for i in range(0, (num_available_ips - 1)):247 self.user.id, self.projects[0].id)
219 rv = self.service.deallocate_fixed_ip(addresses[i])248
220 self.dnsmasq.release_ip(macs[i], addresses[i], hostname, net.bridge_name)249 for i in range(len(addresses)):
250 self.service.deallocate_fixed_ip(addresses[i])
251 release_ip(macs[i], addresses[i], hostname, net.bridge_name)
252 self.assertEqual(len(list(net.available)), num_available_ips)
253
221254
222def is_in_project(address, project_id):255def is_in_project(address, project_id):
256 """Returns true if address is in specified project"""
223 return address in model.get_project_network(project_id).list_addresses()257 return address in model.get_project_network(project_id).list_addresses()
224258
225def _get_project_addresses(project_id):
226 project_addresses = []
227 for addr in model.get_project_network(project_id).list_addresses():
228 project_addresses.append(addr)
229 return project_addresses
230259
231def binpath(script):260def binpath(script):
261 """Returns the absolute path to a script in bin"""
232 return os.path.abspath(os.path.join(__file__, "../../../bin", script))262 return os.path.abspath(os.path.join(__file__, "../../../bin", script))
233263
234class FakeDNSMasq(object):264
235 def issue_ip(self, mac, ip, hostname, interface):265def issue_ip(mac, private_ip, hostname, interface):
236 cmd = "%s add %s %s %s" % (binpath('nova-dhcpbridge'),266 """Run add command on dhcpbridge"""
237 mac, ip, hostname)267 cmd = "%s add %s %s %s" % (binpath('nova-dhcpbridge'),
238 env = {'DNSMASQ_INTERFACE': interface,268 mac, private_ip, hostname)
239 'TESTING' : '1',269 env = {'DNSMASQ_INTERFACE': interface,
240 'FLAGFILE' : FLAGS.dhcpbridge_flagfile}270 'TESTING': '1',
241 (out, err) = utils.execute(cmd, addl_env=env)271 'FLAGFILE': FLAGS.dhcpbridge_flagfile}
242 logging.debug("ISSUE_IP: %s, %s " % (out, err))272 (out, err) = utils.execute(cmd, addl_env=env)
243273 logging.debug("ISSUE_IP: %s, %s ", out, err)
244 def release_ip(self, mac, ip, hostname, interface):274
245 cmd = "%s del %s %s %s" % (binpath('nova-dhcpbridge'),275def release_ip(mac, private_ip, hostname, interface):
246 mac, ip, hostname)276 """Run del command on dhcpbridge"""
247 env = {'DNSMASQ_INTERFACE': interface,277 cmd = "%s del %s %s %s" % (binpath('nova-dhcpbridge'),
248 'TESTING' : '1',278 mac, private_ip, hostname)
249 'FLAGFILE' : FLAGS.dhcpbridge_flagfile}279 env = {'DNSMASQ_INTERFACE': interface,
250 (out, err) = utils.execute(cmd, addl_env=env)280 'TESTING': '1',
251 logging.debug("RELEASE_IP: %s, %s " % (out, err))281 'FLAGFILE': FLAGS.dhcpbridge_flagfile}
252282 (out, err) = utils.execute(cmd, addl_env=env)
283 logging.debug("RELEASE_IP: %s, %s ", out, err)