summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/network/model.py609
-rw-r--r--nova/network/vpn.py127
2 files changed, 0 insertions, 736 deletions
diff --git a/nova/network/model.py b/nova/network/model.py
deleted file mode 100644
index 24e5d6afb..000000000
--- a/nova/network/model.py
+++ /dev/null
@@ -1,609 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""
-Model Classes for network control, including VLANs, DHCP, and IP allocation.
-"""
-
-import IPy
-import logging
-import os
-import time
-
-from nova import datastore
-from nova import exception as nova_exception
-from nova import flags
-from nova.auth import manager
-from nova.network import exception
-from nova.network import linux_net
-
-
-FLAGS = flags.FLAGS
-
-logging.getLogger().setLevel(logging.DEBUG)
-
-
-class Vlan():
- """Tracks vlans assigned to project it the datastore"""
- def __init__(self, project, vlan): # pylint: disable=W0231
- """
- Since we don't want to try and find a vlan by its identifier,
- but by a project id, we don't call super-init.
- """
- self.project_id = project
- self.vlan_id = vlan
-
- @property
- def identifier(self):
- """Datastore identifier"""
- return "%s:%s" % (self.project_id, self.vlan_id)
-
- @classmethod
- def create(cls, project, vlan):
- """Create a Vlan object"""
- instance = cls(project, vlan)
- instance.save()
- return instance
-
- @classmethod
- def lookup(cls, project):
- """Returns object by project if it exists in datastore or None"""
- set_name = cls._redis_set_name(cls.__name__)
- vlan = datastore.Redis.instance().hget(set_name, project)
- if vlan:
- return cls(project, vlan)
- else:
- return None
-
- @classmethod
- def dict_by_project(cls):
- """A hash of project:vlan"""
- set_name = cls._redis_set_name(cls.__name__)
- return datastore.Redis.instance().hgetall(set_name) or {}
-
- @classmethod
- def dict_by_vlan(cls):
- """A hash of vlan:project"""
- set_name = cls._redis_set_name(cls.__name__)
- retvals = {}
- hashset = datastore.Redis.instance().hgetall(set_name) or {}
- for (key, val) in hashset.iteritems():
- retvals[val] = key
- return retvals
-
- @classmethod
- def all(cls):
- set_name = cls._redis_set_name(cls.__name__)
- elements = datastore.Redis.instance().hgetall(set_name)
- for project in elements:
- yield cls(project, elements[project])
-
- def save(self):
- """
- Vlan saves state into a giant hash named "vlans", with keys of
- project_id and value of vlan number. Therefore, we skip the
- default way of saving into "vlan:ID" and adding to a set of "vlans".
- """
- set_name = self._redis_set_name(self.__class__.__name__)
- datastore.Redis.instance().hset(set_name,
- self.project_id,
- self.vlan_id)
-
- def destroy(self):
- """Removes the object from the datastore"""
- set_name = self._redis_set_name(self.__class__.__name__)
- datastore.Redis.instance().hdel(set_name, self.project_id)
-
- def subnet(self):
- """Returns a string containing the subnet"""
- vlan = int(self.vlan_id)
- network = IPy.IP(FLAGS.private_range)
- start = (vlan - FLAGS.vlan_start) * FLAGS.network_size
- # minus one for the gateway.
- return "%s-%s" % (network[start],
- network[start + FLAGS.network_size - 1])
-
-
-class FixedIp():
- """Represents a fixed ip in the datastore"""
-
- def __init__(self, address):
- self.address = address
- super(FixedIp, self).__init__()
-
- @property
- def identifier(self):
- return self.address
-
- # NOTE(vish): address states allocated, leased, deallocated
- def default_state(self):
- return {'address': self.address,
- 'state': 'none'}
-
- @classmethod
- # pylint: disable=R0913
- def create(cls, user_id, project_id, address, mac, hostname, network_id):
- """Creates an FixedIp object"""
- addr = cls(address)
- addr['user_id'] = user_id
- addr['project_id'] = project_id
- addr['mac'] = mac
- if hostname is None:
- hostname = "ip-%s" % address.replace('.', '-')
- addr['hostname'] = hostname
- addr['network_id'] = network_id
- addr['state'] = 'allocated'
- addr.save()
- return addr
-
- def save(self):
- is_new = self.is_new_record()
- success = super(FixedIp, self).save()
- if success and is_new:
- self.associate_with("network", self['network_id'])
-
- def destroy(self):
- self.unassociate_with("network", self['network_id'])
- super(FixedIp, self).destroy()
-
-
-class ElasticIp(FixedIp):
- """Represents an elastic ip in the datastore"""
- override_type = "address"
-
- def default_state(self):
- return {'address': self.address,
- 'instance_id': 'available',
- 'private_ip': 'available'}
-
-
-# CLEANUP:
-# TODO(ja): does vlanpool "keeper" need to know the min/max -
-# shouldn't FLAGS always win?
-class BaseNetwork():
- """Implements basic logic for allocating ips in a network"""
- override_type = 'network'
- address_class = FixedIp
-
- @property
- def identifier(self):
- """Datastore identifier"""
- return self.network_id
-
- def default_state(self):
- """Default values for new objects"""
- return {'network_id': self.network_id, 'network_str': self.network_str}
-
- @classmethod
- # pylint: disable=R0913
- def create(cls, user_id, project_id, security_group, vlan, network_str):
- """Create a BaseNetwork object"""
- network_id = "%s:%s" % (project_id, security_group)
- net = cls(network_id, network_str)
- net['user_id'] = user_id
- net['project_id'] = project_id
- net["vlan"] = vlan
- net["bridge_name"] = "br%s" % vlan
- net.save()
- return net
-
- def __init__(self, network_id, network_str=None):
- self.network_id = network_id
- self.network_str = network_str
- super(BaseNetwork, self).__init__()
- self.save()
-
- @property
- def network(self):
- """Returns a string representing the network"""
- return IPy.IP(self['network_str'])
-
- @property
- def netmask(self):
- """Returns the netmask of this network"""
- return self.network.netmask()
-
- @property
- def gateway(self):
- """Returns the network gateway address"""
- return self.network[1]
-
- @property
- def broadcast(self):
- """Returns the network broadcast address"""
- return self.network.broadcast()
-
- @property
- def bridge_name(self):
- """Returns the bridge associated with this network"""
- return "br%s" % (self["vlan"])
-
- @property
- def user(self):
- """Returns the user associated with this network"""
- return manager.AuthManager().get_user(self['user_id'])
-
- @property
- def project(self):
- """Returns the project associated with this network"""
- return manager.AuthManager().get_project(self['project_id'])
-
- # pylint: disable=R0913
- def _add_host(self, user_id, project_id, ip_address, mac, hostname):
- """Add a host to the datastore"""
- self.address_class.create(user_id, project_id, ip_address,
- mac, hostname, self.identifier)
-
- def _rem_host(self, ip_address):
- """Remove a host from the datastore"""
- self.address_class(ip_address).destroy()
-
- @property
- def assigned(self):
- """Returns a list of all assigned addresses"""
- return self.address_class.associated_keys('network', self.identifier)
-
- @property
- def assigned_objs(self):
- """Returns a list of all assigned addresses as objects"""
- return self.address_class.associated_to('network', self.identifier)
-
- def get_address(self, ip_address):
- """Returns a specific ip as an object"""
- if ip_address in self.assigned:
- return self.address_class(ip_address)
- return None
-
- @property
- def available(self):
- """Returns a list of all available addresses in the network"""
- for idx in range(self.num_bottom_reserved_ips,
- len(self.network) - self.num_top_reserved_ips):
- address = str(self.network[idx])
- if not address in self.assigned:
- yield address
-
- @property
- def num_bottom_reserved_ips(self):
- """Returns number of ips reserved at the bottom of the range"""
- return 2 # Network, Gateway
-
- @property
- def num_top_reserved_ips(self):
- """Returns number of ips reserved at the top of the range"""
- return 1 # Broadcast
-
- def allocate_ip(self, user_id, project_id, mac, hostname=None):
- """Allocates an ip to a mac address"""
- for address in self.available:
- logging.debug("Allocating IP %s to %s", address, project_id)
- self._add_host(user_id, project_id, address, mac, hostname)
- self.express(address=address)
- return address
- raise exception.NoMoreAddresses("Project %s with network %s" %
- (project_id, str(self.network)))
-
- def lease_ip(self, ip_str):
- """Called when DHCP lease is activated"""
- if not ip_str in self.assigned:
- raise exception.AddressNotAllocated()
- address = self.get_address(ip_str)
- if address:
- logging.debug("Leasing allocated IP %s", ip_str)
- address['state'] = 'leased'
- address.save()
-
- def release_ip(self, ip_str):
- """Called when DHCP lease expires
-
- Removes the ip from the assigned list"""
- if not ip_str in self.assigned:
- raise exception.AddressNotAllocated()
- logging.debug("Releasing IP %s", ip_str)
- self._rem_host(ip_str)
- self.deexpress(address=ip_str)
-
- def deallocate_ip(self, ip_str):
- """Deallocates an allocated ip"""
- if not ip_str in self.assigned:
- raise exception.AddressNotAllocated()
- address = self.get_address(ip_str)
- if address:
- if address['state'] != 'leased':
- # NOTE(vish): address hasn't been leased, so release it
- self.release_ip(ip_str)
- else:
- logging.debug("Deallocating allocated IP %s", ip_str)
- address['state'] == 'deallocated'
- address.save()
-
- def express(self, address=None):
- """Set up network. Implemented in subclasses"""
- pass
-
- def deexpress(self, address=None):
- """Tear down network. Implemented in subclasses"""
- pass
-
-
-class BridgedNetwork(BaseNetwork):
- """
- Virtual Network that can express itself to create a vlan and
- a bridge (with or without an IP address/netmask/gateway)
-
- properties:
- bridge_name - string (example value: br42)
- vlan - integer (example value: 42)
- bridge_dev - string (example: eth0)
- bridge_gets_ip - boolean used during bridge creation
-
- if bridge_gets_ip then network address for bridge uses the properties:
- gateway
- broadcast
- netmask
- """
-
- bridge_gets_ip = False
- override_type = 'network'
-
- @classmethod
- def get_network_for_project(cls,
- user_id,
- project_id,
- security_group='default'):
- """Returns network for a given project"""
- vlan = get_vlan_for_project(project_id)
- network_str = vlan.subnet()
- return cls.create(user_id, project_id, security_group, vlan.vlan_id,
- network_str)
-
- def __init__(self, *args, **kwargs):
- super(BridgedNetwork, self).__init__(*args, **kwargs)
- self['bridge_dev'] = FLAGS.bridge_dev
- self.save()
-
- def express(self, address=None):
- super(BridgedNetwork, self).express(address=address)
- linux_net.vlan_create(self)
- linux_net.bridge_create(self)
-
-
-class DHCPNetwork(BridgedNetwork):
- """Network supporting DHCP"""
- bridge_gets_ip = True
- override_type = 'network'
-
- def __init__(self, *args, **kwargs):
- super(DHCPNetwork, self).__init__(*args, **kwargs)
- if not(os.path.exists(FLAGS.networks_path)):
- os.makedirs(FLAGS.networks_path)
-
- @property
- def num_bottom_reserved_ips(self):
- # For cloudpipe
- return super(DHCPNetwork, self).num_bottom_reserved_ips + 1
-
- @property
- def num_top_reserved_ips(self):
- return super(DHCPNetwork, self).num_top_reserved_ips + \
- FLAGS.cnt_vpn_clients
-
- @property
- def dhcp_listen_address(self):
- """Address where dhcp server should listen"""
- return self.gateway
-
- @property
- def dhcp_range_start(self):
- """Starting address dhcp server should use"""
- return self.network[self.num_bottom_reserved_ips]
-
- def express(self, address=None):
- super(DHCPNetwork, self).express(address=address)
- if len(self.assigned) > 0:
- logging.debug("Starting dnsmasq server for network with vlan %s",
- self['vlan'])
- linux_net.start_dnsmasq(self)
- else:
- logging.debug("Not launching dnsmasq: no hosts.")
- self.express_vpn()
-
- def allocate_vpn_ip(self, user_id, project_id, mac, hostname=None):
- """Allocates the reserved ip to a vpn instance"""
- address = str(self.network[2])
- self._add_host(user_id, project_id, address, mac, hostname)
- self.express(address=address)
- return address
-
- def express_vpn(self):
- """Sets up routing rules for vpn"""
- private_ip = str(self.network[2])
- linux_net.confirm_rule("FORWARD -d %s -p udp --dport 1194 -j ACCEPT"
- % (private_ip, ))
- linux_net.confirm_rule(
- "PREROUTING -t nat -d %s -p udp --dport %s -j DNAT --to %s:1194"
- % (self.project.vpn_ip, self.project.vpn_port, private_ip))
-
- def deexpress(self, address=None):
- # if this is the last address, stop dns
- super(DHCPNetwork, self).deexpress(address=address)
- if len(self.assigned) == 0:
- linux_net.stop_dnsmasq(self)
- else:
- linux_net.start_dnsmasq(self)
-
-DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)]
-
-
-class PublicNetworkController(BaseNetwork):
- """Handles elastic ips"""
- override_type = 'network'
- address_class = ElasticIp
-
- def __init__(self, *args, **kwargs):
- network_id = "public:default"
- super(PublicNetworkController, self).__init__(network_id,
- FLAGS.public_range, *args, **kwargs)
- self['user_id'] = "public"
- self['project_id'] = "public"
- self["create_time"] = time.strftime('%Y-%m-%dT%H:%M:%SZ',
- time.gmtime())
- self["vlan"] = FLAGS.public_vlan
- self.save()
- self.express()
-
- def deallocate_ip(self, ip_str):
- # NOTE(vish): cleanup is now done on release by the parent class
- self.release_ip(ip_str)
-
- def associate_address(self, public_ip, private_ip, instance_id):
- """Associates a public ip to a private ip and instance id"""
- if not public_ip in self.assigned:
- raise exception.AddressNotAllocated()
- # TODO(josh): Keep an index going both ways
- for addr in self.assigned_objs:
- if addr.get('private_ip', None) == private_ip:
- raise exception.AddressAlreadyAssociated()
- addr = self.get_address(public_ip)
- if addr.get('private_ip', 'available') != 'available':
- raise exception.AddressAlreadyAssociated()
- addr['private_ip'] = private_ip
- addr['instance_id'] = instance_id
- addr.save()
- self.express(address=public_ip)
-
- def disassociate_address(self, public_ip):
- """Disassociates a public ip with its private ip"""
- if not public_ip in self.assigned:
- raise exception.AddressNotAllocated()
- addr = self.get_address(public_ip)
- if addr.get('private_ip', 'available') == 'available':
- raise exception.AddressNotAssociated()
- self.deexpress(address=public_ip)
- addr['private_ip'] = 'available'
- addr['instance_id'] = 'available'
- addr.save()
-
- def express(self, address=None):
- if address:
- if not address in self.assigned:
- raise exception.AddressNotAllocated()
- addresses = [self.get_address(address)]
- else:
- addresses = self.assigned_objs
- for addr in addresses:
- if addr.get('private_ip', 'available') == 'available':
- continue
- public_ip = addr['address']
- private_ip = addr['private_ip']
- linux_net.bind_public_ip(public_ip, FLAGS.public_interface)
- linux_net.confirm_rule("PREROUTING -t nat -d %s -j DNAT --to %s"
- % (public_ip, private_ip))
- linux_net.confirm_rule("POSTROUTING -t nat -s %s -j SNAT --to %s"
- % (private_ip, public_ip))
- # TODO(joshua): Get these from the secgroup datastore entries
- linux_net.confirm_rule("FORWARD -d %s -p icmp -j ACCEPT"
- % (private_ip))
- for (protocol, port) in DEFAULT_PORTS:
- linux_net.confirm_rule(
- "FORWARD -d %s -p %s --dport %s -j ACCEPT"
- % (private_ip, protocol, port))
-
- def deexpress(self, address=None):
- addr = self.get_address(address)
- private_ip = addr['private_ip']
- linux_net.unbind_public_ip(address, FLAGS.public_interface)
- linux_net.remove_rule("PREROUTING -t nat -d %s -j DNAT --to %s"
- % (address, private_ip))
- linux_net.remove_rule("POSTROUTING -t nat -s %s -j SNAT --to %s"
- % (private_ip, address))
- linux_net.remove_rule("FORWARD -d %s -p icmp -j ACCEPT"
- % (private_ip))
- for (protocol, port) in DEFAULT_PORTS:
- linux_net.remove_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT"
- % (private_ip, protocol, port))
-
-
-# FIXME(todd): does this present a race condition, or is there some
-# piece of architecture that mitigates it (only one queue
-# listener per net)?
-def get_vlan_for_project(project_id):
- """Allocate vlan IDs to individual users"""
- vlan = Vlan.lookup(project_id)
- if vlan:
- return vlan
- known_vlans = Vlan.dict_by_vlan()
- for vnum in range(FLAGS.vlan_start, FLAGS.vlan_end):
- vstr = str(vnum)
- if not vstr in known_vlans:
- return Vlan.create(project_id, vnum)
- old_project_id = known_vlans[vstr]
- if not manager.AuthManager().get_project(old_project_id):
- vlan = Vlan.lookup(old_project_id)
- if vlan:
- # NOTE(todd): This doesn't check for vlan id match, because
- # it seems to be assumed that vlan<=>project is
- # always a 1:1 mapping. It could be made way
- # sexier if it didn't fight against the way
- # BasicModel worked and used associate_with
- # to build connections to projects.
- # NOTE(josh): This is here because we want to make sure we
- # don't orphan any VLANs. It is basically
- # garbage collection for after projects abandoned
- # their reference.
- vlan.destroy()
- vlan.project_id = project_id
- vlan.save()
- return vlan
- else:
- return Vlan.create(project_id, vnum)
- raise exception.AddressNotAllocated("Out of VLANs")
-
-
-def get_project_network(project_id, security_group='default'):
- """Gets a project's private network, allocating one if needed"""
- project = manager.AuthManager().get_project(project_id)
- if not project:
- raise nova_exception.NotFound("Project %s doesn't exist." % project_id)
- manager_id = project.project_manager_id
- return DHCPNetwork.get_network_for_project(manager_id,
- project.id,
- security_group)
-
-
-def get_network_by_address(address):
- """Gets the network for a given private ip"""
- address_record = FixedIp.lookup(address)
- if not address_record:
- raise exception.AddressNotAllocated()
- return get_project_network(address_record['project_id'])
-
-
-def get_network_by_interface(iface, security_group='default'):
- """Gets the network for a given interface"""
- vlan = iface.rpartition("br")[2]
- project_id = Vlan.dict_by_vlan().get(vlan)
- return get_project_network(project_id, security_group)
-
-
-def get_public_ip_for_instance(instance_id):
- """Gets the public ip for a given instance"""
- # FIXME(josh): this should be a lookup - iteration won't scale
- for address_record in ElasticIp.all():
- if address_record.get('instance_id', 'available') == instance_id:
- return address_record['address']
diff --git a/nova/network/vpn.py b/nova/network/vpn.py
deleted file mode 100644
index 5eb1c2b20..000000000
--- a/nova/network/vpn.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""Network Data for projects"""
-
-from nova import datastore
-from nova import exception
-from nova import flags
-from nova import utils
-
-FLAGS = flags.FLAGS
-
-
-flags.DEFINE_string('vpn_ip', utils.get_my_ip(),
- 'Public IP for the cloudpipe VPN servers')
-flags.DEFINE_integer('vpn_start_port', 1000,
- 'Start port for the cloudpipe VPN servers')
-flags.DEFINE_integer('vpn_end_port', 2000,
- 'End port for the cloudpipe VPN servers')
-
-
-class NoMorePorts(exception.Error):
- """No ports available to allocate for the given ip"""
- pass
-
-
-class NetworkData():
- """Manages network host, and vpn ip and port for projects"""
- def __init__(self, project_id):
- self.project_id = project_id
- super(NetworkData, self).__init__()
-
- @property
- def identifier(self):
- """Identifier used for key in redis"""
- return self.project_id
-
- @classmethod
- def create(cls, project_id):
- """Creates a vpn for project
-
- This method finds a free ip and port and stores the associated
- values in the datastore.
- """
- # TODO(vish): will we ever need multiiple ips per host?
- port = cls.find_free_port_for_ip(FLAGS.vpn_ip)
- network_data = cls(project_id)
- # save ip for project
- network_data['host'] = FLAGS.node_name
- network_data['project'] = project_id
- network_data['ip'] = FLAGS.vpn_ip
- network_data['port'] = port
- network_data.save()
- return network_data
-
- @classmethod
- def find_free_port_for_ip(cls, vpn_ip):
- """Finds a free port for a given ip from the redis set"""
- # TODO(vish): these redis commands should be generalized and
- # placed into a base class. Conceptually, it is
- # similar to an association, but we are just
- # storing a set of values instead of keys that
- # should be turned into objects.
- cls._ensure_set_exists(vpn_ip)
-
- port = datastore.Redis.instance().spop(cls._redis_ports_key(vpn_ip))
- if not port:
- raise NoMorePorts()
- return port
-
- @classmethod
- def _redis_ports_key(cls, vpn_ip):
- """Key that ports are stored under in redis"""
- return 'ip:%s:ports' % vpn_ip
-
- @classmethod
- def _ensure_set_exists(cls, vpn_ip):
- """Creates the set of ports for the ip if it doesn't already exist"""
- # TODO(vish): these ports should be allocated through an admin
- # command instead of a flag
- redis = datastore.Redis.instance()
- if (not redis.exists(cls._redis_ports_key(vpn_ip)) and
- not redis.exists(cls._redis_association_name('ip', vpn_ip))):
- for i in range(FLAGS.vpn_start_port, FLAGS.vpn_end_port + 1):
- redis.sadd(cls._redis_ports_key(vpn_ip), i)
-
- @classmethod
- def num_ports_for_ip(cls, vpn_ip):
- """Calculates the number of free ports for a given ip"""
- cls._ensure_set_exists(vpn_ip)
- return datastore.Redis.instance().scard('ip:%s:ports' % vpn_ip)
-
- @property
- def ip(self): # pylint: disable=C0103
- """The ip assigned to the project"""
- return self['ip']
-
- @property
- def port(self):
- """The port assigned to the project"""
- return int(self['port'])
-
- def save(self):
- """Saves the association to the given ip"""
- self.associate_with('ip', self.ip)
- super(NetworkData, self).save()
-
- def destroy(self):
- """Cleans up datastore and adds port back to pool"""
- self.unassociate_with('ip', self.ip)
- datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port)
- super(NetworkData, self).destroy()