From c9d2b8bcb365f326a47df93920c11be2ca054b18 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 30 Sep 2010 23:04:53 -0700 Subject: Fixed flat network manager with network index gone. Both managers use ips created through nova manage. Use of project_get_network is minimized to make way for managers that would prefer to use cluste or host based ips instead of project based ips. --- bin/nova-manage | 20 ++++--- nova/api/ec2/cloud.py | 11 ++-- nova/api/rackspace/servers.py | 37 ++++++------ nova/db/api.py | 7 ++- nova/db/sqlalchemy/api.py | 14 ++++- nova/network/linux_net.py | 6 +- nova/network/manager.py | 121 +++++++++++++++++++++------------------ nova/test.py | 4 +- nova/tests/compute_unittest.py | 3 +- nova/tests/network_unittest.py | 13 +++-- nova/tests/scheduler_unittest.py | 1 + nova/virt/libvirt_conn.py | 4 +- 12 files changed, 140 insertions(+), 101 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index d421b997a..6da2efe95 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -375,11 +375,14 @@ class FloatingIpCommands(object): class NetworkCommands(object): """Class for managing networks.""" - def create(self, num_networks=None, network_size=None, - vlan_start=None, vpn_start=None): - """Creates floating ips for host by range - arguments: [num_networks=FLAG], [network_size=FLAG], - [vlan_start=FLAG, vpn_start=FLAG]""" + def create(self, fixed_range=None, num_networks=None, + network_size=None, vlan_start=None, vpn_start=None): + """Creates fixed ips for host by range + arguments: [fixed_range=FLAG], [num_networks=FLAG], + [network_size=FLAG], [vlan_start=FLAG], + [vpn_start=FLAG]""" + if not fixed_range: + fixed_range = FLAGS.fixed_range if not num_networks: num_networks = FLAGS.num_networks if not network_size: @@ -388,9 +391,10 @@ class NetworkCommands(object): vlan_start = FLAGS.vlan_start if not vpn_start: vpn_start = FLAGS.vpn_start - net_manager = network_manager.VlanManager() - net_manager.create_networks(None, int(num_networks), int(network_size), - int(vlan_start), int(vpn_start)) + net_manager = utils.import_object(FLAGS.network_manager) + net_manager.create_networks(None, fixed_range, int(num_networks), + int(network_size), int(vlan_start), + int(vpn_start)) CATEGORIES = [ ('user', UserCommands), diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 79c95788b..d8462f7a0 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -522,13 +522,13 @@ class CloudController(object): def _get_network_topic(self, context): """Retrieves the network host for a project""" - network_ref = db.project_get_network(context, context.project.id) + network_ref = self.network_manager.get_network(context) host = network_ref['host'] if not host: host = rpc.call(FLAGS.network_topic, {"method": "set_network_host", "args": {"context": None, - "project_id": context.project.id}}) + "network_id": network_ref['id']}}) return db.queue_get_for(context, FLAGS.network_topic, host) def run_instances(self, context, **kwargs): @@ -612,12 +612,13 @@ class CloudController(object): inst['launch_index'] = num inst['hostname'] = instance_ref['ec2_id'] db.instance_update(context, inst_id, inst) + # TODO(vish): This probably should be done in the scheduler + # or in compute as a call. The network should be + # allocated after the host is assigned and setup + # can happen at the same time. address = self.network_manager.allocate_fixed_ip(context, inst_id, vpn) - - # TODO(vish): This probably should be done in the scheduler - # network is setup when host is assigned network_topic = self._get_network_topic(context) rpc.call(network_topic, {"method": "setup_fixed_ip", diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 11efd8aef..0606d14bb 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -64,8 +64,8 @@ def _entity_list(entities): def _entity_detail(inst): """ Maps everything to Rackspace-like attributes for return""" - power_mapping = { - power_state.NOSTATE: 'build', + power_mapping = { + power_state.NOSTATE: 'build', power_state.RUNNING: 'active', power_state.BLOCKED: 'active', power_state.PAUSED: 'suspended', @@ -75,7 +75,7 @@ def _entity_detail(inst): } inst_dict = {} - mapped_keys = dict(status='state', imageId='image_id', + mapped_keys = dict(status='state', imageId='image_id', flavorId='instance_type', name='server_name', id='id') for k, v in mapped_keys.iteritems(): @@ -98,7 +98,7 @@ class Controller(wsgi.Controller): _serialization_metadata = { 'application/xml': { "attributes": { - "server": [ "id", "imageId", "name", "flavorId", "hostId", + "server": [ "id", "imageId", "name", "flavorId", "hostId", "status", "progress", "progress" ] } } @@ -178,7 +178,7 @@ class Controller(wsgi.Controller): user_id = req.environ['nova.context']['user']['id'] inst_dict = self._deserialize(req.body, req) - + if not inst_dict: return faults.Fault(exc.HTTPUnprocessableEntity()) @@ -186,12 +186,12 @@ class Controller(wsgi.Controller): if not instance or instance.user_id != user_id: return faults.Fault(exc.HTTPNotFound()) - self.db_driver.instance_update(None, id, + self.db_driver.instance_update(None, id, _filter_params(inst_dict['server'])) return faults.Fault(exc.HTTPNoContent()) def action(self, req, id): - """ multi-purpose method used to reboot, rebuild, and + """ multi-purpose method used to reboot, rebuild, and resize a server """ input_dict = self._deserialize(req.body, req) try: @@ -217,13 +217,13 @@ class Controller(wsgi.Controller): if v['flavorid'] == flavor_id][0] image_id = env['server']['imageId'] - + img_service, image_id_trans = _image_service() - opaque_image_id = image_id_trans.to_rs_id(image_id) + opaque_image_id = image_id_trans.to_rs_id(image_id) image = img_service.show(opaque_image_id) - if not image: + if not image: raise Exception, "Image not found" inst['server_name'] = env['server']['name'] @@ -259,15 +259,15 @@ class Controller(wsgi.Controller): ref = self.db_driver.instance_create(None, inst) inst['id'] = inst_id_trans.to_rs_id(ref.ec2_id) - + # TODO(dietz): this isn't explicitly necessary, but the networking # calls depend on an object with a project_id property, and therefore # should be cleaned up later api_context = context.APIRequestContext(user_id) - + inst['mac_address'] = utils.generate_mac() - - #TODO(dietz) is this necessary? + + #TODO(dietz) is this necessary? inst['launch_index'] = 0 inst['hostname'] = ref.ec2_id @@ -279,21 +279,20 @@ class Controller(wsgi.Controller): # TODO(vish): This probably should be done in the scheduler # network is setup when host is assigned - network_topic = self._get_network_topic(user_id) + network_topic = self._get_network_topic(None) rpc.call(network_topic, {"method": "setup_fixed_ip", "args": {"context": None, "address": address}}) return inst - def _get_network_topic(self, user_id): + def _get_network_topic(self, context): """Retrieves the network host for a project""" - network_ref = self.db_driver.project_get_network(None, - user_id) + network_ref = self.network_manager.get_network(context) host = network_ref['host'] if not host: host = rpc.call(FLAGS.network_topic, {"method": "set_network_host", "args": {"context": None, - "project_id": user_id}}) + "network_id": network_ref['id']}}) return self.db_driver.queue_get_for(None, FLAGS.network_topic, host) diff --git a/nova/db/api.py b/nova/db/api.py index 9ba994909..1eeee524b 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -400,10 +400,15 @@ def network_get_associated_fixed_ips(context, network_id): def network_get_by_bridge(context, bridge): - """Get an network or raise if it does not exist.""" + """Get a network by bridge or raise if it does not exist.""" return IMPL.network_get_by_bridge(context, bridge) +def network_get_by_instance(context, instance_id): + """Get a network by instance id or raise if it does not exist.""" + return IMPL.network_get_by_instance(context, instance_id) + + def network_get_index(context, network_id): """Get non-conflicting index for network""" return IMPL.network_get_index(context, network_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9d17ccec4..c8099c83e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -28,7 +28,6 @@ from nova.db.sqlalchemy.session import get_session from sqlalchemy import or_ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload_all -from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.sql import exists, func FLAGS = flags.FLAGS @@ -664,6 +663,19 @@ def network_get_by_bridge(_context, bridge): return rv +def network_get_by_instance(_context, instance_id): + session = get_session() + rv = session.query(models.Network + ).filter_by(deleted=False + ).join(models.Network.fixed_ips + ).filter_by(instance_id=instance_id + ).filter_by(deleted=False + ).first() + if not rv: + raise exception.NotFound('No network for instance %s' % instance_id) + return rv + + def network_set_host(_context, network_id, host_id): session = get_session() with session.begin(): diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 709195ba4..a3a0d9a37 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -58,12 +58,12 @@ def init_host(): # SNAT rule for outbound traffic. _confirm_rule("POSTROUTING", "-t nat -s %s " "-j SNAT --to-source %s" - % (FLAGS.private_range, FLAGS.routing_source_ip)) + % (FLAGS.fixed_range, FLAGS.routing_source_ip)) _confirm_rule("POSTROUTING", "-t nat -s %s -j MASQUERADE" % - FLAGS.private_range) + FLAGS.fixed_range) _confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" % - {'range': FLAGS.private_range}) + {'range': FLAGS.fixed_range}) def bind_floating_ip(floating_ip): """Bind ip to public interface""" diff --git a/nova/network/manager.py b/nova/network/manager.py index 0a5acf17a..71f915707 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -37,17 +37,6 @@ from nova import utils FLAGS = flags.FLAGS flags.DEFINE_string('flat_network_bridge', 'br100', 'Bridge for simple network instances') -flags.DEFINE_list('flat_network_ips', - ['192.168.0.2', '192.168.0.3', '192.168.0.4'], - 'Available ips for simple network') -flags.DEFINE_string('flat_network_network', '192.168.0.0', - 'Network for simple network') -flags.DEFINE_string('flat_network_netmask', '255.255.255.0', - 'Netmask for simple network') -flags.DEFINE_string('flat_network_gateway', '192.168.0.1', - 'Broadcast for simple network') -flags.DEFINE_string('flat_network_broadcast', '192.168.0.255', - 'Broadcast for simple network') flags.DEFINE_string('flat_network_dns', '8.8.4.4', 'Dns for simple network') flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks') @@ -57,8 +46,8 @@ flags.DEFINE_string('vpn_ip', utils.get_my_ip(), flags.DEFINE_integer('vpn_start', 1000, 'First Vpn port for private networks') flags.DEFINE_integer('network_size', 256, 'Number of addresses in each private subnet') -flags.DEFINE_string('public_range', '4.4.4.0/24', 'Public IP address block') -flags.DEFINE_string('private_range', '10.0.0.0/8', 'Private IP address block') +flags.DEFINE_string('floating_range', '4.4.4.0/24', 'Floating IP address block') +flags.DEFINE_string('fixed_range', '10.0.0.0/8', 'Fixed IP address block') flags.DEFINE_integer('cnt_vpn_clients', 5, 'Number of addresses reserved for vpn clients') flags.DEFINE_string('network_driver', 'nova.network.linux_net', @@ -85,13 +74,9 @@ class NetworkManager(manager.Manager): self.driver = utils.import_object(network_driver) super(NetworkManager, self).__init__(*args, **kwargs) - def set_network_host(self, context, project_id): - """Safely sets the host of the projects network""" + def set_network_host(self, context, network_id): + """Safely sets the host of the network""" logging.debug("setting network host") - network_ref = self.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 = self.db.network_set_host(context, network_id, self.host) @@ -111,10 +96,10 @@ class NetworkManager(manager.Manager): raise NotImplementedError() def _on_set_network_host(self, context, network_id): - """Called when this host becomes the host for a project""" + """Called when this host becomes the host for a network""" raise NotImplementedError() - def setup_compute_network(self, context, project_id): + def setup_compute_network(self, context, instance_id): """Sets up matching network for compute hosts""" raise NotImplementedError() @@ -144,6 +129,16 @@ class NetworkManager(manager.Manager): """Returns an floating ip to the pool""" self.db.floating_ip_deallocate(context, floating_address) + def get_network(self, context): + """Get the network for the current context""" + raise NotImplementedError() + + def create_networks(self, context, num_networks, network_size, + *args, **kwargs): + """Create networks based on parameters""" + raise NotImplementedError() + + @property def _bottom_reserved_ips(self): # pylint: disable-msg=R0201 """Number of reserved ips at the bottom of the range""" @@ -179,7 +174,12 @@ class FlatManager(NetworkManager): def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): """Gets a fixed ip from the pool""" - network_ref = self.db.project_get_network(context, context.project.id) + # TODO(vish): when this is called by compute, we can associate compute + # with a network, or a cluster of computes with a network + # and use that network here with a method like + # network_get_by_compute_host + network_ref = self.db.network_get_by_bridge(context, + FLAGS.flat_network_bridge) address = self.db.fixed_ip_associate_pool(context, network_ref['id'], instance_id) @@ -191,7 +191,7 @@ class FlatManager(NetworkManager): self.db.fixed_ip_update(context, address, {'allocated': False}) self.db.fixed_ip_disassociate(context, address) - def setup_compute_network(self, context, project_id): + def setup_compute_network(self, context, instance_id): """Network is created manually""" pass @@ -199,25 +199,42 @@ class FlatManager(NetworkManager): """Currently no setup""" pass + def create_networks(self, context, cidr, num_networks, network_size, + *args, **kwargs): + """Create networks based on parameters""" + fixed_net = IPy.IP(cidr) + for index in range(num_networks): + start = index * network_size + significant_bits = 32 - int(math.log(network_size, 2)) + cidr = "%s/%s" % (fixed_net[start], significant_bits) + project_net = IPy.IP(cidr) + net = {} + net['cidr'] = cidr + net['netmask'] = str(project_net.netmask()) + net['gateway'] = str(project_net[1]) + net['broadcast'] = str(project_net.broadcast()) + net['dhcp_start'] = str(project_net[2]) + network_ref = self.db.network_create_safe(context, net) + if network_ref: + self._create_fixed_ips(context, network_ref['id']) + + def get_network(self, context): + """Get the network for the current context""" + # NOTE(vish): To support mutilple network hosts, This could randomly + # select from multiple networks instead of just + # returning the one. It could also potentially be done + # in the scheduler. + return self.db.network_get_by_bridge(context, + FLAGS.flat_network_bridge) + def _on_set_network_host(self, context, network_id): - """Called when this host becomes the host for a project""" - # NOTE(vish): should there be two types of network objects - # in the datastore? + """Called when this host becomes the host for a network""" net = {} net['injected'] = True - 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 self.db.network_update(context, network_id, net) - # NOTE(vish): Rignt now we are putting all of the fixed ips in - # one large pool, but ultimately it may be better to - # have each network manager have its own network that - # it is responsible for and its own pool of ips. - for address in FLAGS.flat_network_ips: - self.db.fixed_ip_create(context, {'address': address}) + class VlanManager(NetworkManager): @@ -244,6 +261,9 @@ class VlanManager(NetworkManager): def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): """Gets a fixed ip from the pool""" + # TODO(vish): This should probably be getting project_id from + # the instance, but it is another trip to the db. + # Perhaps this method should take an instance_ref. network_ref = self.db.project_get_network(context, context.project.id) if kwargs.get('vpn', None): address = network_ref['vpn_private_address'] @@ -313,22 +333,9 @@ class VlanManager(NetworkManager): network_ref = self.db.fixed_ip_get_network(context, address) self.driver.update_dhcp(context, network_ref['id']) - - def associate_network(self, context, project_id): - """Associates a network to a project""" - network_ref = db.network_associate(context, project_id) - network_id = network_ref['id'] - return network_id - - - def disassociate_network(self, context, network_id): - """Disassociates a newtwork from a project""" - db.network_disassocate(context, network_id) - - - def setup_compute_network(self, context, project_id): + def setup_compute_network(self, context, instance_id): """Sets up matching network for compute hosts""" - network_ref = self.db.project_get_network(context, project_id) + network_ref = db.network_get_by_instance(context, instance_id) self.driver.ensure_vlan_bridge(network_ref['vlan'], network_ref['bridge']) @@ -337,15 +344,15 @@ class VlanManager(NetworkManager): # TODO(vish): Implement this pass - def create_networks(self, context, num_networks, network_size, + def create_networks(self, context, cidr, num_networks, network_size, vlan_start, vpn_start): """Create networks based on parameters""" + fixed_net = IPy.IP(cidr) for index in range(num_networks): - private_net = IPy.IP(FLAGS.private_range) vlan = vlan_start + index start = index * network_size significant_bits = 32 - int(math.log(network_size, 2)) - cidr = "%s/%s" % (private_net[start], significant_bits) + cidr = "%s/%s" % (fixed_net[start], significant_bits) project_net = IPy.IP(cidr) net = {} net['cidr'] = cidr @@ -363,8 +370,12 @@ class VlanManager(NetworkManager): if network_ref: self._create_fixed_ips(context, network_ref['id']) + def get_network(self, context): + """Get the network for the current context""" + return self.db.project_get_network(context, context.project.id) + def _on_set_network_host(self, context, network_id): - """Called when this host becomes the host for a project""" + """Called when this host becomes the host for a network""" network_ref = self.db.network_get(context, network_id) net = {} net['vpn_public_address'] = FLAGS.vpn_ip diff --git a/nova/test.py b/nova/test.py index 91668f9b0..493754e83 100644 --- a/nova/test.py +++ b/nova/test.py @@ -63,7 +63,9 @@ class TrialTestCase(unittest.TestCase): # now that we have some required db setup for the system # to work properly. if db.network_count(None) != 5: - network_manager.VlanManager().create_networks(None, 5, 16, + network_manager.VlanManager().create_networks(None, + FLAGS.fixed_range, + 5, 16, FLAGS.vlan_start, FLAGS.vpn_start) diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index f5c0f1c09..e695d8fba 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -40,7 +40,8 @@ class ComputeTestCase(test.TrialTestCase): def setUp(self): # pylint: disable-msg=C0103 logging.getLogger().setLevel(logging.DEBUG) super(ComputeTestCase, self).setUp() - self.flags(connection_type='fake') + self.flags(connection_type='fake', + network_manager='nova.network.manager.FlatManager') self.compute = utils.import_object(FLAGS.compute_manager) self.manager = manager.AuthManager() self.user = self.manager.create_user('fake', 'fake', 'fake') diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index 5ceb336ec..8bd2bb2fd 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -52,11 +52,14 @@ class NetworkTestCase(test.TrialTestCase): self.context = context.APIRequestContext(project=None, user=self.user) for i in range(5): name = 'project%s' % i - self.projects.append(self.manager.create_project(name, - 'netuser', - name)) + project = self.manager.create_project(name, 'netuser', name) + self.projects.append(project) # create the necessary network data for the project - self.network.set_network_host(self.context, self.projects[i].id) + self.context.project = project + network_ref = self.network.get_network(self.context) + if not network_ref['host']: + self.network.set_network_host(self.context, network_ref['id']) + self.context.project = None instance_ref = db.instance_create(None, {'mac_address': utils.generate_mac()}) self.instance_id = instance_ref['id'] @@ -84,7 +87,7 @@ class NetworkTestCase(test.TrialTestCase): def test_public_network_association(self): """Makes sure that we can allocaate a public ip""" # TODO(vish): better way of adding floating ips - pubnet = IPy.IP(flags.FLAGS.public_range) + pubnet = IPy.IP(flags.FLAGS.floating_range) address = str(pubnet[0]) try: db.floating_ip_get_by_address(None, address) diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index fde30f81e..f6ee19756 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -75,6 +75,7 @@ class SimpleDriverTestCase(test.TrialTestCase): self.flags(connection_type='fake', max_cores=4, max_gigabytes=4, + network_manager='nova.network.manager.FlatManager', volume_driver='nova.volume.driver.FakeAOEDriver', scheduler_driver='nova.scheduler.simple.SimpleScheduler') self.scheduler = manager.SchedulerManager() diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d868e083c..f6d8aace6 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -280,7 +280,7 @@ class LibvirtConnection(object): key = str(inst['key_data']) net = None - network_ref = db.project_get_network(None, project.id) + network_ref = db.network_get_by_instance(None, inst['id']) if network_ref['injected']: address = db.instance_get_fixed_address(None, inst['id']) with open(FLAGS.injected_network_template) as f: @@ -314,7 +314,7 @@ class LibvirtConnection(object): def to_xml(self, instance): # TODO(termie): cache? logging.debug('instance %s: starting toXML method', instance['name']) - network = db.project_get_network(None, instance['project_id']) + network = db.instance_get_fixed_by_instance(None, inst['id']) # FIXME(vish): stick this in db instance_type = instance_types.INSTANCE_TYPES[instance['instance_type']] xml_info = {'type': FLAGS.libvirt_type, -- cgit