diff options
| author | Vishvananda Ishaya <vishvananda@yahoo.com> | 2010-08-23 13:55:16 -0700 |
|---|---|---|
| committer | Vishvananda Ishaya <vishvananda@yahoo.com> | 2010-08-23 13:55:16 -0700 |
| commit | 78c2175898a468ae734e27dfbc8f5b70f90fd477 (patch) | |
| tree | 33e7bb0f56c1b44d1ba46c36f7c82be3997dfecb | |
| parent | 152baf34247c5a4b76f643cac0d33c0158de0bfa (diff) | |
Refactored network model access into data abstraction layer.
Also changed the name to floating_ip.
| -rwxr-xr-x | bin/nova-dhcpbridge | 23 | ||||
| -rw-r--r-- | nova/db/api.py | 112 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/api.py | 187 | ||||
| -rw-r--r-- | nova/endpoint/cloud.py | 28 | ||||
| -rw-r--r-- | nova/models.py | 8 | ||||
| -rw-r--r-- | nova/network/linux_net.py | 24 | ||||
| -rw-r--r-- | nova/network/service.py | 328 | ||||
| -rw-r--r-- | nova/tests/network_unittest.py | 70 |
8 files changed, 497 insertions, 283 deletions
diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge index b17a56e6e..8008100f6 100755 --- a/bin/nova-dhcpbridge +++ b/bin/nova-dhcpbridge @@ -25,9 +25,9 @@ import logging import os import sys -#TODO(joshua): there is concern that the user dnsmasq runs under will not -# have nova in the path. This should be verified and if it is -# not true the ugly line below can be removed +# TODO(joshua): there is concern that the user dnsmasq runs under will not +# have nova in the path. This should be verified and if it is +# not true the ugly line below can be removed sys.path.append(os.path.abspath(os.path.join(__file__, "../../"))) from nova import flags @@ -36,6 +36,7 @@ from nova import utils from nova.network import linux_net from nova.network import service from nova import datastore # for redis_db flag +from nova.auth import manager # for auth flags FLAGS = flags.FLAGS @@ -43,16 +44,16 @@ FLAGS = flags.FLAGS def add_lease(_mac, ip, _hostname, _interface): """Set the IP that was assigned by the DHCP server.""" if FLAGS.fake_rabbit: - logging.debug("leasing_ip") + logging.debug("leasing ip") from nova import models print models.FixedIp.count() print models.Network.count() print FLAGS.sql_connection - service.VlanNetworkService().lease_ip(ip) + service.VlanNetworkService().lease_fixed_ip(ip) else: rpc.cast("%s.%s" % (FLAGS.network_topic, FLAGS.node_name), - {"method": "lease_ip", - "args": {"fixed_ip_str": ip}}) + {"method": "lease_fixed_ip", + "args": {"address": ip}}) def old_lease(_mac, _ip, _hostname, _interface): @@ -63,12 +64,12 @@ def old_lease(_mac, _ip, _hostname, _interface): def del_lease(_mac, ip, _hostname, _interface): """Called when a lease expires.""" if FLAGS.fake_rabbit: - logging.debug("releasing_ip") - service.VlanNetworkService().release_ip(ip) + logging.debug("releasing ip") + service.VlanNetworkService().release_fixed_ip(ip) else: rpc.cast("%s.%s" % (FLAGS.network_topic, FLAGS.node_name), - {"method": "release_ip", - "args": {"fixed_ip_str": ip}}) + {"method": "release_fixed_ip", + "args": {"address": ip}}) def init_leases(interface): diff --git a/nova/db/api.py b/nova/db/api.py index bbd69ec65..a0e2b3715 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -30,16 +30,24 @@ _impl = utils.LazyPluggable(FLAGS['db_backend'], sqlalchemy='nova.db.sqlalchemy.api') +class NoMoreAddresses(exception.Error): + pass + + class NoMoreBlades(exception.Error): pass +class NoMoreNetworks(exception.Error): + pass + + ################### def daemon_get(context, node_name, binary): return _impl.daemon_get(context, node_name, binary) - + def daemon_create(context, values): return _impl.daemon_create(context, values) @@ -52,6 +60,78 @@ def daemon_update(context, values): ################### +def floating_ip_allocate_address(context, node_name, project_id): + """Allocate free floating ip and return the address. + + Raises if one is not available. + """ + return _impl.floating_ip_allocate_address(context, node_name, project_id) + + +def floating_ip_fixed_ip_associate(context, floating_address, fixed_address): + """Associate an floating ip to a fixed_ip by address.""" + return _impl.floating_ip_fixed_ip_associate(context, + floating_address, + fixed_address) + + +def floating_ip_disassociate(context, address): + """Disassociate an floating ip from a fixed ip by address. + + Returns the address of the existing fixed ip. + """ + return _impl.floating_ip_disassociate(context, address) + + +def floating_ip_deallocate(context, address): + """Deallocate an floating ip by address""" + return _impl.floating_ip_deallocate(context, address) + + +#################### + + +def fixed_ip_allocate_address(context, network_id): + """Allocate free fixed ip and return the address. + + Raises if one is not available. + """ + return _impl.fixed_ip_allocate_address(context, network_id) + + +def fixed_ip_get_by_address(context, address): + """Get a fixed ip by address.""" + return _impl.fixed_ip_get_by_address(context, address) + + +def fixed_ip_lease(context, address): + """Lease a fixed ip by address.""" + return _impl.fixed_ip_lease(context, address) + + +def fixed_ip_release(context, address): + """Un-Lease a fixed ip by address.""" + return _impl.fixed_ip_release(context, address) + + +def fixed_ip_deallocate(context, address): + """Deallocate a fixed ip by address.""" + return _impl.fixed_ip_deallocate(context, address) + + +def fixed_ip_instance_associate(context, address, instance_id): + """Associate a fixed ip to an instance by address.""" + return _impl.fixed_ip_instance_associate(context, address, instance_id) + + +def fixed_ip_instance_disassociate(context, address): + """Disassociate a fixed ip from an instance by address.""" + return _impl.fixed_ip_instance_disassociate(context, address) + + +#################### + + def instance_create(context, values): """Create an instance from the values dictionary.""" return _impl.instance_create(context, values) @@ -89,16 +169,46 @@ def network_create(context, values): return _impl.network_create(context, values) +def network_create_fixed_ips(context, network_id, num_vpn_clients): + """Create the ips for the network, reserving sepecified ips.""" + return _impl.network_create_fixed_ips(context, network_id, num_vpn_clients) + + def network_destroy(context, network_id): """Destroy the network or raise if it does not exist.""" return _impl.network_destroy(context, network_id) +def network_ensure_indexes(context, num_networks): + """Ensure that network indexes exist, creating them if necessary.""" + return _impl.network_ensure_indexes(context, num_networks) + + def network_get(context, network_id): """Get an network or raise if it does not exist.""" return _impl.network_get(context, network_id) +def network_get_host(context, network_id): + """Get host assigned to network or raise""" + return _impl.network_get_host(context, network_id) + + +def network_get_index(context, network_id): + """Gets non-conflicting index for network""" + return _impl.network_get_index(context, network_id) + + +def network_set_cidr(context, network_id, cidr): + """Set the Classless Inner Domain Routing for the network""" + return _impl.network_set_cidr(context, network_id, cidr) + + +def network_set_host(context, network_id, host_id): + """Safely set the host for network""" + return _impl.network_set_host(context, network_id, host_id) + + def network_update(context, network_id, values): """Set the given properties on an network and update it. diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e883e14cb..a3a5ff8de 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -16,6 +16,8 @@ # License for the specific language governing permissions and limitations # under the License. +import IPy + from nova import db from nova import exception from nova import models @@ -27,7 +29,7 @@ from nova import models def daemon_get(context, node_name, binary): return None return models.Daemon.find_by_args(node_name, binary) - + def daemon_create(context, values): daemon_ref = models.Daemon(**values) @@ -45,6 +47,99 @@ def daemon_update(context, node_name, binary, values): ################### +def floating_ip_allocate_address(context, node_name, project_id): + session = models.NovaBase.get_session() + query = session.query(models.FloatingIp).filter_by(node_name=node_name) + query = query.filter_by(fixed_ip_id=None).with_lockmode("update") + floating_ip_ref = query.first() + # NOTE(vish): if with_lockmode isn't supported, as in sqlite, + # then this has concurrency issues + if not floating_ip_ref: + raise db.NoMoreAddresses() + floating_ip_ref['project_id'] = project_id + session.add(floating_ip_ref) + session.commit() + return floating_ip_ref['ip_str'] + + +def floating_ip_fixed_ip_associate(context, floating_address, fixed_address): + floating_ip_ref = models.FloatingIp.find_by_ip_str(floating_address) + fixed_ip_ref = models.FixedIp.find_by_ip_str(fixed_address) + floating_ip_ref.fixed_ip = fixed_ip_ref + floating_ip_ref.save() + + +def floating_ip_disassociate(context, address): + floating_ip_ref = models.FloatingIp.find_by_ip_str(address) + fixed_ip_address = floating_ip_ref.fixed_ip['ip_str'] + floating_ip_ref['fixed_ip'] = None + floating_ip_ref.save() + return fixed_ip_address + +def floating_ip_deallocate(context, address): + floating_ip_ref = models.FloatingIp.find_by_ip_str(address) + floating_ip_ref['project_id'] = None + floating_ip_ref.save() + +################### + + +def fixed_ip_allocate_address(context, network_id): + session = models.NovaBase.get_session() + query = session.query(models.FixedIp).filter_by(network_id=network_id) + query = query.filter_by(reserved=False).filter_by(allocated=False) + query = query.filter_by(leased=False).with_lockmode("update") + fixed_ip_ref = query.first() + # NOTE(vish): if with_lockmode isn't supported, as in sqlite, + # then this has concurrency issues + if not fixed_ip_ref: + raise db.NoMoreAddresses() + fixed_ip_ref['allocated'] = True + session.add(fixed_ip_ref) + session.commit() + return fixed_ip_ref['ip_str'] + + +def fixed_ip_get_by_address(context, address): + return models.FixedIp.find_by_ip_str(address) + + +def fixed_ip_lease(context, address): + fixed_ip_ref = fixed_ip_get_by_address(context, address) + if not fixed_ip_ref['allocated']: + raise db.AddressNotAllocated(address) + fixed_ip_ref['leased'] = True + fixed_ip_ref.save() + + +def fixed_ip_release(context, address): + fixed_ip_ref = fixed_ip_get_by_address(context, address) + fixed_ip_ref['allocated'] = False + fixed_ip_ref['leased'] = False + fixed_ip_ref.save() + + +def fixed_ip_deallocate(context, address): + fixed_ip_ref = fixed_ip_get_by_address(context, address) + fixed_ip_ref['allocated'] = False + fixed_ip_ref.save() + + +def fixed_ip_instance_associate(context, address, instance_id): + fixed_ip_ref = fixed_ip_get_by_address(context, address) + fixed_ip_ref.instance = instance_get(context, instance_id) + fixed_ip_ref.save() + + +def fixed_ip_instance_disassociate(context, address): + fixed_ip_ref = fixed_ip_get_by_address(context, address) + fixed_ip_ref.instance = None + fixed_ip_ref.save() + + +################### + + def instance_create(context, values): instance_ref = models.Instance() for (key, value) in values.iteritems(): @@ -85,13 +180,99 @@ def network_create(context, values): return network_ref +def network_create_fixed_ips(context, network_id, num_vpn_clients): + network_ref = network_get(context, network_id) + # NOTE(vish): should these be properties of the network as opposed + # to constants? + BOTTOM_RESERVED = 3 + TOP_RESERVED = 1 + num_vpn_clients + project_net = IPy.IP(network_ref['cidr']) + num_ips = len(project_net) + session = models.NovaBase.get_session() + for i in range(num_ips): + fixed_ip = models.FixedIp() + fixed_ip.ip_str = str(project_net[i]) + if i < BOTTOM_RESERVED or num_ips - i < TOP_RESERVED: + fixed_ip['reserved'] = True + fixed_ip['network'] = network_get(context, network_id) + session.add(fixed_ip) + session.commit() + + +def network_ensure_indexes(context, num_networks): + if models.NetworkIndex.count() == 0: + session = models.NovaBase.get_session() + for i in range(num_networks): + network_index = models.NetworkIndex() + network_index.index = i + session.add(network_index) + session.commit() + + def network_destroy(context, network_id): network_ref = network_get(context, network_id) network_ref.delete() def network_get(context, network_id): - return models.Instance.find(network_id) + return models.Network.find(network_id) + + +def network_get_vpn_ip(context, network_id): + # TODO(vish): possible concurrency issue here + network = network_get(context, network_id) + address = network['vpn_private_ip_str'] + fixed_ip = fixed_ip_get_by_address(context, address) + if fixed_ip['allocated']: + raise db.AddressAlreadyAllocated() + db.fixed_ip_allocate(context, {'allocated': True}) + + +def network_get_host(context, network_id): + network_ref = network_get(context, network_id) + return network_ref['node_name'] + + +def network_get_index(context, network_id): + session = models.NovaBase.get_session() + query = session.query(models.NetworkIndex).filter_by(network_id=None) + network_index = query.with_lockmode("update").first() + if not network_index: + raise db.NoMoreNetworks() + network_index['network'] = network_get(context, network_id) + session.add(network_index) + session.commit() + return network_index['index'] + + +def network_set_cidr(context, network_id, cidr): + network_ref = network_get(context, network_id) + project_net = IPy.IP(cidr) + network_ref['cidr'] = cidr + # FIXME we can turn these into properties + network_ref['netmask'] = str(project_net.netmask()) + network_ref['gateway'] = str(project_net[1]) + network_ref['broadcast'] = str(project_net.broadcast()) + network_ref['vpn_private_ip_str'] = str(project_net[2]) + + +def network_set_host(context, network_id, host_id): + session = models.NovaBase.get_session() + # FIXME will a second request fail or wait for first to finish? + query = session.query(models.Network).filter_by(id=network_id) + network = query.with_lockmode("update").first() + if not network: + raise exception.NotFound("Couldn't find network with %s" % + network_id) + # NOTE(vish): if with_lockmode isn't supported, as in sqlite, + # then this has concurrency issues + if network.node_name: + session.commit() + return network['node_name'] + network['node_name'] = host_id + session.add(network) + session.commit() + return network['node_name'] def network_update(context, network_id, values): @@ -110,7 +291,7 @@ def project_get_network(context, project_id): if not rv: raise exception.NotFound('No network for project: %s' % project_id) return rv - + ################### diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index e5d4661df..e64005c2e 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -311,7 +311,7 @@ class CloudController(object): def _get_address(self, context, public_ip): # FIXME(vish) this should move into network.py - address = network_model.ElasticIp.lookup(public_ip) + address = network_model.FloatingIp.lookup(public_ip) if address and (context.user.is_admin() or address['project_id'] == context.project.id): return address raise exception.NotFound("Address at ip %s not found" % public_ip) @@ -459,7 +459,7 @@ class CloudController(object): def format_addresses(self, context): addresses = [] - for address in network_model.ElasticIp.all(): + for address in network_model.FloatingIp.all(): # TODO(vish): implement a by_project iterator for addresses if (context.user.is_admin() or address['project_id'] == context.project.id): @@ -481,7 +481,7 @@ class CloudController(object): def allocate_address(self, context, **kwargs): network_topic = yield self._get_network_topic(context) public_ip = yield rpc.call(network_topic, - {"method": "allocate_elastic_ip", + {"method": "allocate_floating_ip", "args": {"user_id": context.user.id, "project_id": context.project.id}}) defer.returnValue({'addressSet': [{'publicIp': public_ip}]}) @@ -492,8 +492,8 @@ class CloudController(object): # NOTE(vish): Should we make sure this works? network_topic = yield self._get_network_topic(context) rpc.cast(network_topic, - {"method": "deallocate_elastic_ip", - "args": {"elastic_ip": public_ip}}) + {"method": "deallocate_floating_ip", + "args": {"floating_ip": public_ip}}) defer.returnValue({'releaseResponse': ["Address released."]}) @rbac.allow('netadmin') @@ -503,8 +503,8 @@ class CloudController(object): address = self._get_address(context, public_ip) network_topic = yield self._get_network_topic(context) rpc.cast(network_topic, - {"method": "associate_elastic_ip", - "args": {"elastic_ip": address['address'], + {"method": "associate_floating_ip", + "args": {"floating_ip": address['address'], "fixed_ip": instance['private_dns_name'], "instance_id": instance['instance_id']}}) defer.returnValue({'associateResponse': ["Address associated."]}) @@ -515,8 +515,8 @@ class CloudController(object): address = self._get_address(context, public_ip) network_topic = yield self._get_network_topic(context) rpc.cast(network_topic, - {"method": "disassociate_elastic_ip", - "args": {"elastic_ip": address['address']}}) + {"method": "disassociate_floating_ip", + "args": {"floating_ip": address['address']}}) defer.returnValue({'disassociateResponse': ["Address disassociated."]}) @defer.inlineCallbacks @@ -617,15 +617,15 @@ class CloudController(object): logging.warning("Instance %s was not found during terminate" % i) continue - elastic_ip = network_model.get_public_ip_for_instance(i) - if elastic_ip: - logging.debug("Disassociating address %s" % elastic_ip) + floating_ip = network_model.get_public_ip_for_instance(i) + if floating_ip: + logging.debug("Disassociating address %s" % floating_ip) # NOTE(vish): Right now we don't really care if the ip is # disassociated. We may need to worry about # checking this later. Perhaps in the scheduler? rpc.cast(network_topic, - {"method": "disassociate_elastic_ip", - "args": {"elastic_ip": elastic_ip}}) + {"method": "disassociate_floating_ip", + "args": {"floating_ip": floating_ip}}) fixed_ip = instance.get('private_dns_name', None) if fixed_ip: diff --git a/nova/models.py b/nova/models.py index e4cd37336..70caeff76 100644 --- a/nova/models.py +++ b/nova/models.py @@ -278,12 +278,12 @@ class FixedIp(Base, NovaBase): raise exception.NotFound("No model for ip str %s" % ip_str) -class ElasticIp(Base, NovaBase): - __tablename__ = 'elastic_ips' +class FloatingIp(Base, NovaBase): + __tablename__ = 'floating_ips' id = Column(Integer, primary_key=True) ip_str = Column(String(255), unique=True) fixed_ip_id = Column(Integer, ForeignKey('fixed_ips.id'), nullable=True) - fixed_ip = relationship(FixedIp, backref=backref('elastic_ips')) + fixed_ip = relationship(FixedIp, backref=backref('floating_ips')) project_id = Column(String(255)) #, ForeignKey('projects.id'), nullable=False) node_name = Column(String(255)) #, ForeignKey('physical_node.id')) @@ -305,7 +305,7 @@ class Network(Base, NovaBase): kind = Column(String(255)) injected = Column(Boolean, default=False) - network_str = Column(String(255)) + cidr = Column(String(255)) netmask = Column(String(255)) bridge = Column(String(255)) gateway = Column(String(255)) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 6fa3bae73..4a57a8393 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -40,15 +40,15 @@ flags.DEFINE_string('public_interface', 'vlan1', flags.DEFINE_string('bridge_dev', 'eth0', 'network device for bridges') -def bind_elastic_ip(elastic_ip): +def bind_floating_ip(floating_ip): """Bind ip to public interface""" - _execute("sudo ip addr add %s dev %s" % (elastic_ip, + _execute("sudo ip addr add %s dev %s" % (floating_ip, FLAGS.public_interface)) -def unbind_elastic_ip(elastic_ip): +def unbind_floating_ip(floating_ip): """Unbind a public ip from public interface""" - _execute("sudo ip addr del %s dev %s" % (elastic_ip, + _execute("sudo ip addr del %s dev %s" % (floating_ip, FLAGS.public_interface)) @@ -61,12 +61,12 @@ def ensure_vlan_forward(public_ip, port, private_ip): DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)] -def ensure_elastic_forward(elastic_ip, fixed_ip): - """Ensure elastic ip forwarding rule""" +def ensure_floating_forward(floating_ip, fixed_ip): + """Ensure floating ip forwarding rule""" _confirm_rule("PREROUTING -t nat -d %s -j DNAT --to %s" - % (elastic_ip, fixed_ip)) + % (floating_ip, fixed_ip)) _confirm_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" - % (fixed_ip, elastic_ip)) + % (fixed_ip, floating_ip)) # TODO(joshua): Get these from the secgroup datastore entries _confirm_rule("FORWARD -d %s -p icmp -j ACCEPT" % (fixed_ip)) @@ -75,12 +75,12 @@ def ensure_elastic_forward(elastic_ip, fixed_ip): "FORWARD -d %s -p %s --dport %s -j ACCEPT" % (fixed_ip, protocol, port)) -def remove_elastic_forward(elastic_ip, fixed_ip): - """Remove forwarding for elastic ip""" +def remove_floating_forward(floating_ip, fixed_ip): + """Remove forwarding for floating ip""" _remove_rule("PREROUTING -t nat -d %s -j DNAT --to %s" - % (elastic_ip, fixed_ip)) + % (floating_ip, fixed_ip)) _remove_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" - % (fixed_ip, elastic_ip)) + % (fixed_ip, floating_ip)) _remove_rule("FORWARD -d %s -p icmp -j ACCEPT" % (fixed_ip)) for (protocol, port) in DEFAULT_PORTS: diff --git a/nova/network/service.py b/nova/network/service.py index e47f07ef0..bb2e4ae8a 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -21,17 +21,15 @@ Network Hosts are responsible for allocating ips and setting up network """ import logging +import math import IPy from nova import db from nova import exception from nova import flags -from nova import models from nova import service from nova import utils -from nova.auth import manager -from nova.network import exception as network_exception from nova.network import linux_net @@ -67,9 +65,19 @@ flags.DEFINE_string('private_range', '10.0.0.0/8', 'Private IP address block') flags.DEFINE_integer('cnt_vpn_clients', 5, 'Number of addresses reserved for vpn clients') + +class AddressAlreadyAllocated(exception.Error): + pass + + +class AddressNotAllocated(exception.Error): + pass + + # TODO(vish): some better type of dependency injection? _driver = linux_net + def type_to_class(network_type): """Convert a network_type string into an actual Python class""" if not network_type: @@ -85,22 +93,14 @@ def type_to_class(network_type): def setup_compute_network(project_id): """Sets up the network on a compute host""" - network = get_network_for_project(project_id) + network = db.project_get_network(None, project_id) srv = type_to_class(network.kind) srv.setup_compute_network(network) -def get_network_for_project(project_id, context=None): - """Get network allocated to project from datastore""" - project = manager.AuthManager().get_project(project_id) - if not project: - raise exception.NotFound("Couldn't find project %s" % project_id) - return db.project_get_network(context, project_id) - - def get_host_for_project(project_id): """Get host allocated to project from datastore""" - return get_network_for_project(project_id).node_name + return db.project_get_network(None, project_id).node_name class BaseNetworkService(service.Service): @@ -109,57 +109,35 @@ class BaseNetworkService(service.Service): This class must be subclassed. """ - def set_network_host(self, project_id): + def set_network_host(self, project_id, context=None): """Safely sets the host of the projects network""" - # FIXME abstract this - session = models.NovaBase.get_session() - # FIXME will a second request fail or wait for first to finish? - query = session.query(models.Network).filter_by(project_id=project_id) - network = query.with_lockmode("update").first() - if not network: - raise exception.NotFound("Couldn't find network for %s" % - project_id) - # NOTE(vish): if with_lockmode isn't supported, as in sqlite, - # then this has concurrency issues - if network.node_name: - session.commit() - return network.node_name - network.node_name = FLAGS.node_name - network.kind = FLAGS.network_type - session.add(network) - session.commit() - self._on_set_network_host(network) - return network.node_name - - def allocate_fixed_ip(self, project_id, instance_id, *args, **kwargs): + network_ref = db.project_get_network(context, project_id) + # TODO(vish): can we minimize db access by just getting the + # id here instead of the ref? + network_id = network_ref['id'] + host = db.network_set_host(context, + network_id, + FLAGS.node_name) + self._on_set_network_host(context, network_id) + return host + + def allocate_fixed_ip(self, project_id, instance_id, context=None, + *args, **kwargs): """Gets fixed ip from the pool""" - # FIXME abstract this - network = get_network_for_project(project_id) - session = models.NovaBase.get_session() - query = session.query(models.FixedIp).filter_by(network_id=network.id) - query = query.filter_by(reserved=False).filter_by(allocated=False) - query = query.filter_by(leased=False).with_lockmode("update") - fixed_ip = query.first() - # NOTE(vish): if with_lockmode isn't supported, as in sqlite, - # then this has concurrency issues - if not fixed_ip: - raise network_exception.NoMoreAddresses() - # FIXME will this set backreference? - fixed_ip.instance_id = instance_id - fixed_ip.allocated = True - session.add(fixed_ip) - session.commit() - return fixed_ip.ip_str - - def deallocate_fixed_ip(self, fixed_ip_str, *args, **kwargs): + network_ref = db.project_get_network(context, project_id) + address = db.fixed_ip_allocate_address(context, network_ref['id']) + db.fixed_ip_instance_associate(context, + address, + instance_id) + return address + + def deallocate_fixed_ip(self, address, context=None): """Returns a fixed ip to the pool""" - fixed_ip = models.FixedIp.find_by_ip_str(fixed_ip_str) - fixed_ip.instance = None - fixed_ip.allocated = False - fixed_ip.save() + db.fixed_ip_deallocate(context, address) + db.fixed_ip_instance_disassociate(context, address) - def _on_set_network_host(self, network, *args, **kwargs): + def _on_set_network_host(self, context, network_id): """Called when this host becomes the host for a project""" pass @@ -168,45 +146,32 @@ class BaseNetworkService(service.Service): """Sets up matching network for compute hosts""" raise NotImplementedError() - def allocate_elastic_ip(self, project_id): - """Gets an elastic ip from the pool""" - # FIXME: add elastic ips through manage command - # FIXME: abstract this - session = models.NovaBase.get_session() - node_name = FLAGS.node_name - query = session.query(models.ElasticIp).filter_by(node_name=node_name) - query = query.filter_by(fixed_ip_id=None).with_lockmode("update") - elastic_ip = query.first() - if not elastic_ip: - raise network_exception.NoMoreAddresses() - elastic_ip.project_id = project_id - session.add(elastic_ip) - session.commit() - return elastic_ip.ip_str - - def associate_elastic_ip(self, elastic_ip_str, fixed_ip_str): - """Associates an elastic ip to a fixed ip""" - elastic_ip = models.ElasticIp.find_by_ip_str(elastic_ip_str) - fixed_ip = models.FixedIp.find_by_ip_str(fixed_ip_str) - elastic_ip.fixed_ip = fixed_ip - _driver.bind_elastic_ip(elastic_ip_str) - _driver.ensure_elastic_forward(elastic_ip_str, fixed_ip_str) - elastic_ip.save() - - def disassociate_elastic_ip(self, elastic_ip_str): - """Disassociates a elastic ip""" - elastic_ip = models.ElasticIp.find_by_ip_str(elastic_ip_str) - fixed_ip_str = elastic_ip.fixed_ip.ip_str - elastic_ip.fixed_ip = None - _driver.unbind_elastic_ip(elastic_ip_str) - _driver.remove_elastic_forward(elastic_ip_str, fixed_ip_str) - elastic_ip.save() - - def deallocate_elastic_ip(self, elastic_ip_str): - """Returns an elastic ip to the pool""" - elastic_ip = models.ElasticIp.find_by_ip_str(elastic_ip_str) - elastic_ip.project_id = None - elastic_ip.save() + def allocate_floating_ip(self, project_id, context=None): + """Gets an floating ip from the pool""" + # TODO(vish): add floating ips through manage command + return db.floating_ip_allocate_address(context, + FLAGS.node_name, + project_id) + + def associate_floating_ip(self, floating_address, fixed_address, + context=None): + """Associates an floating ip to a fixed ip""" + db.floating_ip_fixed_ip_associate(context, + floating_address, + fixed_address) + _driver.bind_floating_ip(floating_address) + _driver.ensure_floating_forward(floating_address, fixed_address) + + def disassociate_floating_ip(self, floating_address, context=None): + """Disassociates a floating ip""" + fixed_address = db.floating_ip_disassociate(context, + floating_address) + _driver.unbind_floating_ip(floating_address) + _driver.remove_floating_forward(floating_address, fixed_address) + + def deallocate_floating_ip(self, floating_address, context=None): + """Returns an floating ip to the pool""" + db.floating_ip_deallocate(context, floating_address) class FlatNetworkService(BaseNetworkService): @@ -217,141 +182,96 @@ class FlatNetworkService(BaseNetworkService): """Network is created manually""" pass - def _on_set_network_host(self, network, *args, **kwargs): + def _on_set_network_host(self, context, network_id): """Called when this host becomes the host for a project""" - # FIXME should there be two types of network objects in the database? - network.injected = True - network.network_str=FLAGS.flat_network_network - network.netmask=FLAGS.flat_network_netmask - network.bridge=FLAGS.flat_network_bridge - network.gateway=FLAGS.flat_network_gateway - network.broadcast=FLAGS.flat_network_broadcast - network.dns=FLAGS.flat_network_dns - network.save() - # FIXME add public ips from flags to the datastore + # NOTE(vish): should there be two types of network objects + # in the database? + net = {} + net['injected'] = True + net['kind'] = FLAGS.network_type + net['network_str']=FLAGS.flat_network_network + net['netmask']=FLAGS.flat_network_netmask + net['bridge']=FLAGS.flat_network_bridge + net['gateway']=FLAGS.flat_network_gateway + net['broadcast']=FLAGS.flat_network_broadcast + net['dns']=FLAGS.flat_network_dns + db.network_update(context, network_id, net) + # TODO(vish): add public ips from flags to the datastore class VlanNetworkService(BaseNetworkService): """Vlan network with dhcp""" def __init__(self, *args, **kwargs): super(VlanNetworkService, self).__init__(*args, **kwargs) - self._ensure_network_indexes() - - def _ensure_network_indexes(self): # NOTE(vish): this should probably be removed and added via # admin command or fixtures - if models.NetworkIndex.count() == 0: - session = models.NovaBase.get_session() - for i in range(FLAGS.num_networks): - network_index = models.NetworkIndex() - network_index.index = i - session.add(network_index) - session.commit() + db.network_ensure_indexes(None, FLAGS.num_networks) def allocate_fixed_ip(self, project_id, instance_id, is_vpn=False, - *args, **kwargs): + context=None, *args, **kwargs): """Gets a fixed ip from the pool""" - network = get_network_for_project(project_id) + network_ref = db.project_get_network(context, project_id) if is_vpn: - # FIXME concurrency issue? - fixed_ip = models.FixedIp.find_by_ip_str(network.vpn_private_ip_str) - if fixed_ip.allocated: - raise network_exception.AddressAlreadyAllocated() - # FIXME will this set backreference? - fixed_ip.instance_id = instance_id - fixed_ip.allocated = True - fixed_ip.save() - _driver.ensure_vlan_forward(network.vpn_public_ip_str, - network.vpn_public_port, - network.vpn_private_ip_str) - ip_str = fixed_ip.ip_str - logging.debug("Allocating vpn IP %s", ip_str) + address = db.network_get_vpn_ip_address(context, + network_ref['id']) + logging.debug("Allocating vpn IP %s", address) + db.fixed_ip_instance_associate(context, + address, + instance_id) + _driver.ensure_vlan_forward(network_ref['vpn_public_ip_str'], + network_ref['vpn_public_port'], + network_ref['vpn_private_ip_str']) else: parent = super(VlanNetworkService, self) - ip_str = parent.allocate_fixed_ip(project_id, instance_id) - _driver.ensure_vlan_bridge(network.vlan, network.bridge) - return ip_str - - def deallocate_fixed_ip(self, fixed_ip_str): + address = parent.allocate_fixed_ip(project_id, + instance_id, + context) + _driver.ensure_vlan_bridge(network_ref['vlan'], + network_ref['bridge']) + return address + + def deallocate_fixed_ip(self, address, context=None): """Returns an ip to the pool""" - fixed_ip = models.FixedIp.find_by_ip_str(fixed_ip_str) - if fixed_ip.leased: - logging.debug("Deallocating IP %s", fixed_ip_str) - fixed_ip.allocated = False - # keep instance id until release occurs - fixed_ip.save() + fixed_ip_ref = db.fixed_ip_get_by_address(context, address) + if fixed_ip_ref['leased']: + logging.debug("Deallocating IP %s", address) + db.fixed_ip_deallocate(context, address) + # NOTE(vish): we keep instance id until release occurs else: - self.release_ip(fixed_ip_str) + self.release_fixed_ip(address, context) - def lease_ip(self, fixed_ip_str): + def lease_fixed_ip(self, address, context=None): """Called by bridge when ip is leased""" - fixed_ip = models.FixedIp.find_by_ip_str(fixed_ip_str) - if not fixed_ip.allocated: - raise network_exception.AddressNotAllocated(fixed_ip_str) - logging.debug("Leasing IP %s", fixed_ip_str) - fixed_ip.leased = True - fixed_ip.save() - - def release_ip(self, fixed_ip_str): - """Called by bridge when ip is released""" - fixed_ip = models.FixedIp.find_by_ip_str(fixed_ip_str) - logging.debug("Releasing IP %s", fixed_ip_str) - fixed_ip.leased = False - fixed_ip.allocated = False - fixed_ip.instance = None - fixed_ip.save() + logging.debug("Leasing IP %s", address) + db.fixed_ip_lease(context, address) + def release_fixed_ip(self, address, context=None): + """Called by bridge when ip is released""" + logging.debug("Releasing IP %s", address) + db.fixed_ip_release(context, address) + db.fixed_ip_instance_disassociate(context, address) def restart_nets(self): """Ensure the network for each user is enabled""" # FIXME pass - def _on_set_network_host(self, network): + def _on_set_network_host(self, context, network_id): """Called when this host becomes the host for a project""" - index = self._get_network_index(network) + index = db.network_get_index(context, network_id) private_net = IPy.IP(FLAGS.private_range) start = index * FLAGS.network_size - # minus one for the gateway. - network_str = "%s-%s" % (private_net[start], - private_net[start + FLAGS.network_size - 1]) + significant_bits = 32 - int(math.log(FLAGS.network_size, 2)) + cidr = "%s/%s" % (private_net[start], significant_bits) + db.network_set_cidr(context, network_id, cidr) vlan = FLAGS.vlan_start + index - project_net = IPy.IP(network_str) - network.network_str = network_str - network.netmask = str(project_net.netmask()) - network.vlan = vlan - network.bridge = 'br%s' % vlan - network.gateway = str(project_net[1]) - network.broadcast = str(project_net.broadcast()) - network.vpn_private_ip_str = str(project_net[2]) - network.vpn_public_ip_str = FLAGS.vpn_ip - network.vpn_public_port = FLAGS.vpn_start + index - # create network fixed ips - BOTTOM_RESERVED = 3 - TOP_RESERVED = 1 + FLAGS.cnt_vpn_clients - num_ips = len(project_net) - session = models.NovaBase.get_session() - for i in range(num_ips): - fixed_ip = models.FixedIp() - fixed_ip.ip_str = str(project_net[i]) - if i < BOTTOM_RESERVED or num_ips - i < TOP_RESERVED: - fixed_ip.reserved = True - fixed_ip.network = network - session.add(fixed_ip) - session.commit() - - - def _get_network_index(self, network): - """Get non-conflicting index for network""" - session = models.NovaBase.get_session() - node_name = FLAGS.node_name - query = session.query(models.NetworkIndex).filter_by(network_id=None) - network_index = query.with_lockmode("update").first() - if not network_index: - raise network_exception.NoMoreNetworks() - network_index.network = network - session.add(network_index) - session.commit() - return network_index.index + net = {} + net['kind'] = FLAGS.network_type + net['vlan'] = vlan + net['bridge'] = 'br%s' % vlan + net['vpn_public_ip_str'] = FLAGS.vpn_ip + net['vpn_public_port'] = FLAGS.vpn_start + index + db.network_update(context, network_id, net) + db.network_create_fixed_ips(context, network_id, FLAGS.cnt_vpn_clients) @classmethod diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index 76c76edbf..c4c496219 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -21,8 +21,8 @@ Unit Tests for network code import IPy import os import logging -import tempfile +from nova import db from nova import exception from nova import flags from nova import models @@ -30,7 +30,6 @@ from nova import test from nova import utils from nova.auth import manager from nova.network import service -from nova.network.exception import NoMoreAddresses, NoMoreNetworks FLAGS = flags.FLAGS @@ -59,49 +58,52 @@ class NetworkTestCase(test.TrialTestCase): name)) # create the necessary network data for the project self.service.set_network_host(self.projects[i].id) - instance = models.Instance() - instance.mac_address = utils.generate_mac() - instance.hostname = 'fake' - instance.save() - self.instance_id = instance.id + instance_id = db.instance_create(None, + {'mac_address': utils.generate_mac()}) + self.instance_id = instance_id + instance_id = db.instance_create(None, + {'mac_address': utils.generate_mac()}) + self.instance2_id = instance_id def tearDown(self): # pylint: disable=C0103 super(NetworkTestCase, self).tearDown() # TODO(termie): this should really be instantiating clean datastores # in between runs, one failure kills all the tests + db.instance_destroy(None, self.instance_id) + db.instance_destroy(None, self.instance2_id) for project in self.projects: self.manager.delete_project(project) self.manager.delete_user(self.user) def test_public_network_association(self): """Makes sure that we can allocaate a public ip""" - # FIXME better way of adding elastic ips + # TODO(vish): better way of adding floating ips pubnet = IPy.IP(flags.FLAGS.public_range) ip_str = str(pubnet[0]) try: - elastic_ip = models.ElasticIp.find_by_ip_str(ip_str) + floating_ip = models.FloatingIp.find_by_ip_str(ip_str) except exception.NotFound: - elastic_ip = models.ElasticIp() - elastic_ip.ip_str = ip_str - elastic_ip.node_name = FLAGS.node_name - elastic_ip.save() - eaddress = self.service.allocate_elastic_ip(self.projects[0].id) + floating_ip = models.FloatingIp() + floating_ip.ip_str = ip_str + floating_ip.node_name = FLAGS.node_name + floating_ip.save() + eaddress = self.service.allocate_floating_ip(self.projects[0].id) faddress = self.service.allocate_fixed_ip(self.projects[0].id, self.instance_id) self.assertEqual(eaddress, str(pubnet[0])) - self.service.associate_elastic_ip(eaddress, faddress) + self.service.associate_floating_ip(eaddress, faddress) # FIXME datamodel abstraction - self.assertEqual(elastic_ip.fixed_ip.ip_str, faddress) - self.service.disassociate_elastic_ip(eaddress) - self.assertEqual(elastic_ip.fixed_ip, None) - self.service.deallocate_elastic_ip(eaddress) + self.assertEqual(floating_ip.fixed_ip.ip_str, faddress) + self.service.disassociate_floating_ip(eaddress) + self.assertEqual(floating_ip.fixed_ip, None) + self.service.deallocate_floating_ip(eaddress) self.service.deallocate_fixed_ip(faddress) def test_allocate_deallocate_fixed_ip(self): """Makes sure that we can allocate and deallocate a fixed ip""" address = self.service.allocate_fixed_ip(self.projects[0].id, self.instance_id) - net = service.get_network_for_project(self.projects[0].id) + net = db.project_get_network(None, self.projects[0].id) self.assertTrue(is_allocated_in_project(address, self.projects[0].id)) issue_ip(address, net.bridge) self.service.deallocate_fixed_ip(address) @@ -117,10 +119,10 @@ class NetworkTestCase(test.TrialTestCase): address = self.service.allocate_fixed_ip(self.projects[0].id, self.instance_id) address2 = self.service.allocate_fixed_ip(self.projects[1].id, - self.instance_id) + self.instance2_id) - net = service.get_network_for_project(self.projects[0].id) - net2 = service.get_network_for_project(self.projects[1].id) + net = db.project_get_network(None, self.projects[0].id) + net2 = db.project_get_network(None, self.projects[1].id) self.assertTrue(is_allocated_in_project(address, self.projects[0].id)) self.assertTrue(is_allocated_in_project(address2, self.projects[1].id)) @@ -151,7 +153,7 @@ class NetworkTestCase(test.TrialTestCase): address = self.service.allocate_fixed_ip(project_id, self.instance_id) address2 = self.service.allocate_fixed_ip(project_id, self.instance_id) address3 = self.service.allocate_fixed_ip(project_id, self.instance_id) - net = service.get_network_for_project(project_id) + net = db.project_get_network(None, project_id) issue_ip(address, net.bridge) issue_ip(address2, net.bridge) issue_ip(address3, net.bridge) @@ -167,7 +169,7 @@ class NetworkTestCase(test.TrialTestCase): release_ip(address, net.bridge) release_ip(address2, net.bridge) release_ip(address3, net.bridge) - net = service.get_network_for_project(self.projects[0].id) + net = db.project_get_network(None, self.projects[0].id) self.service.deallocate_fixed_ip(first) def test_vpn_ip_and_port_looks_valid(self): @@ -186,7 +188,7 @@ class NetworkTestCase(test.TrialTestCase): self.service.set_network_host(project.id) projects.append(project) project = self.manager.create_project('boom' , self.user) - self.assertRaises(NoMoreNetworks, + self.assertRaises(db.NoMoreNetworks, self.service.set_network_host, project.id) self.manager.delete_project(project) @@ -198,7 +200,7 @@ class NetworkTestCase(test.TrialTestCase): """Makes sure that ip addresses that are deallocated get reused""" address = self.service.allocate_fixed_ip(self.projects[0].id, self.instance_id) - net = service.get_network_for_project(self.projects[0].id) + net = db.project_get_network(None, self.projects[0].id) issue_ip(address, net.bridge) self.service.deallocate_fixed_ip(address) release_ip(address, net.bridge) @@ -219,7 +221,7 @@ class NetworkTestCase(test.TrialTestCase): There are ips reserved at the bottom and top of the range. services (network, gateway, CloudPipe, broadcast) """ - network = service.get_network_for_project(self.projects[0].id) + network = db.project_get_network(None, self.projects[0].id) net_size = flags.FLAGS.network_size total_ips = (available_ips(network) + reserved_ips(network) + @@ -229,7 +231,7 @@ class NetworkTestCase(test.TrialTestCase): def test_too_many_addresses(self): """Test for a NoMoreAddresses exception when all fixed ips are used. """ - network = service.get_network_for_project(self.projects[0].id) + network = db.project_get_network(None, self.projects[0].id) # Number of availaible ips is len of the available list @@ -242,7 +244,7 @@ class NetworkTestCase(test.TrialTestCase): issue_ip(addresses[i],network.bridge) self.assertEqual(available_ips(network), 0) - self.assertRaises(NoMoreAddresses, + self.assertRaises(db.NoMoreAddresses, self.service.allocate_fixed_ip, self.projects[0].id, self.instance_id) @@ -274,11 +276,11 @@ def reserved_ips(network): def is_allocated_in_project(address, project_id): """Returns true if address is in specified project""" - fixed_ip = models.FixedIp.find_by_ip_str(address) - project_net = service.get_network_for_project(project_id) + fixed_ip = db.fixed_ip_get_by_address(None, address) + project_net = db.project_get_network(None, project_id) # instance exists until release - logging.error('fixed_ip.instance: %s', fixed_ip.instance) - logging.error('project_net: %s', project_net) + logging.debug('fixed_ip.instance: %s', fixed_ip.instance) + logging.debug('project_net: %s', project_net) return fixed_ip.instance is not None and fixed_ip.network == project_net |
