From 0239363cc9c347d063d5df9d71620ac3bd23b440 Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Wed, 19 Nov 2014 16:40:17 -0800 Subject: [PATCH 01/19] Network OS (netos) extension. Code cleanups. --- neutron/extensions/netos.py | 49 +++++++++++++++++++ neutron/extensions/topology.py | 6 +-- neutron/plugins/opencloud/common/constants.py | 2 +- neutron/plugins/ovx/plugin.py | 35 +++++++------ 4 files changed, 73 insertions(+), 19 deletions(-) create mode 100644 neutron/extensions/netos.py diff --git a/neutron/extensions/netos.py b/neutron/extensions/netos.py new file mode 100644 index 00000000000..969f93d123e --- /dev/null +++ b/neutron/extensions/netos.py @@ -0,0 +1,49 @@ +from neutron.api.v2 import attributes +from neutron.plugins.common import constants + +IMAGE = 'netos:image' +URL = 'netos:url' + +EXTENDED_ATTRIBUTES_2_0 = { + 'networks': { + IMAGE: {'allow_post': True, 'allow_put': False, + 'default': attr.ATTR_NOT_SPECIFIED, + 'validate': {'type:string': None}, + 'is_visible': True + }, + URL: {'allow_post': True, 'allow_put': False, + 'default': attr.ATTR_NOT_SPECIFIED, + 'validate': {'type:string': None}, + 'is_visible': True + } + } +} + +class NetOS(object): + @classmethod + def get_name(cls): + return "Network OS Extension" + + @classmethod + def get_alias(cls): + return "netos" + + @classmethod + def get_description(cls): + return "Configure network operating system" + + @classmethod + def get_namespace(cls): + # return "http://docs.openstack.org/ext/provider/api/v1.0" + # Nothing there right now + return "http://www.vicci.org/ext/opencloud/topology/api/v0.1" + + @classmethod + def get_updated(cls): + return "2014-11-19T10:00:00-00:00" + + def get_extended_resources(self, version): + if version == "2.0": + return EXTENDED_ATTRIBUTES_2_0 + else: + return {} diff --git a/neutron/extensions/topology.py b/neutron/extensions/topology.py index ece2994911e..1d7166f189b 100644 --- a/neutron/extensions/topology.py +++ b/neutron/extensions/topology.py @@ -6,9 +6,9 @@ EXTENDED_ATTRIBUTES_2_0 = { 'networks': { TYPE: {'allow_post': True, 'allow_put': False, - 'default': constants.BIGSWITCH, - 'validate': {'type:values': [constants.BIGSWITCH, constants.PHYSICAL, constants.CUSTOM]}, - 'is_visible': True + 'default': constants.BIGSWITCH, + 'validate': {'type:values': [constants.BIGSWITCH, constants.PHYSICAL, constants.CUSTOM]}, + 'is_visible': True } } } diff --git a/neutron/plugins/opencloud/common/constants.py b/neutron/plugins/opencloud/common/constants.py index 7582e8e4872..bd121bdd677 100644 --- a/neutron/plugins/opencloud/common/constants.py +++ b/neutron/plugins/opencloud/common/constants.py @@ -37,7 +37,7 @@ 'ip_version': 4, 'cidr': '171.66.164.0/24', 'gateway_ip': '171.66.164.1', - 'dns_nameservers': [], + 'dns_nameservers': ['8.8.8.8'], 'allocation_pools': [{"start": "171.66.164.3", "end": "171.66.164.254"}], 'host_routes': [], 'enable_dhcp': True diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 59a3d2d512d..0a92dec3b83 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -36,6 +36,7 @@ from neutron.db import portbindings_base from neutron.db import portbindings_db from neutron.db import quota_db # noqa +from neutron.extensions import netos from neutron.extensions import portbindings from neutron.extensions import topology from neutron.openstack.common import log as logging @@ -125,7 +126,6 @@ def __init__(self, ctrl_network): service_type="compute") # Check if Nova config is correct try: - self._image = self._nova.images.find(name=cfg.CONF.NOVA.image_name) self._flavor = self._nova.flavors.find(name=cfg.CONF.NOVA.flavor) # Check if the key name is found, don't save the ref (novaclient wants the name) if cfg.CONF.NOVA.key_name: @@ -134,15 +134,18 @@ def __init__(self, ctrl_network): LOG.error("Could not initialize Nova bindings. Check your config. (%s)" % e) sys.exit(1) - def spawn(self, name): + def spawn(self, name, image_name): """Spawns SDN controller inside the control network. Returns the Nova server ID and IP address.""" + # Raise an exception if any of the parameters is incorrect + image = self._nova.images.find(name=image_name) + # Connect controller to control network nic_config = {'net-id': self.ctrl_network_id} # Can also set 'fixed_ip' if needed server = self._nova.servers.create(name='OVX_%s' % name, - image=self._image, + image=image, flavor=self._flavor, key_name=cfg.CONF.NOVA.key_name, nics=[nic_config]) @@ -160,7 +163,7 @@ def spawn(self, name): # Fetch IP address of controller instance controller_ip = server.addresses[self.ctrl_network_name][0]['addr'] - LOG.info("Spawned SDN controller image %s: ID %s, IP %s" % (cfg.CONF.NOVA.image_name, controller_id, controller_ip)) + LOG.info("Spawned SDN controller image %s: ID %s, IP %s" % (image_name, controller_id, controller_ip)) return (controller_id, controller_ip) @@ -174,7 +177,7 @@ class OVXNeutronPlugin(db_base_plugin_v2.NeutronDbPluginV2, agents_db.AgentDbMixin, portbindings_db.PortBindingMixin): - supported_extension_aliases = ['quotas', 'binding', 'agent', 'topology'] + supported_extension_aliases = ['quotas', 'binding', 'agent', 'topology', 'netos'] def __init__(self): super(OVXNeutronPlugin, self).__init__() @@ -225,21 +228,21 @@ def create_network(self, context, network): # Save in db net_db = super(OVXNeutronPlugin, self).create_network(context, network) - # Spawn controller - (controller_id, controller_ip) = self.ctrl_manager.spawn(net_db['id']) + # Spawn controller + netos_image = network['network'].get(netos.IMAGE) + if not attributes.is_attr_set(netos_image): + netos_image = cfg.CONF.NOVA.image_name + + (controller_id, controller_ip) = self.ctrl_manager.spawn(net_db['id'], netos_image) try: + # Set OVX vnet config ctrl = 'tcp:%s:%s' % (controller_ip, cfg.CONF.NOVA.image_port) # Subnet value is irrelevant to OVX subnet = '10.0.0.0/24' # Create virtual network with requested topology topology_type = network['network'].get(topology.TYPE) - topology_type_set = attributes.is_attr_set(topology_type) - - # Default topology type is bigswitch - if not topology_type_set: - topology_type = svc_constants.BIGSWITCH if topology_type == svc_constants.BIGSWITCH: ovx_tenant_id = self._do_big_switch_network(ctrl, subnet) @@ -252,7 +255,9 @@ def create_network(self, context, network): if net_db['admin_state_up']: self.ovx_client.startNetwork(ovx_tenant_id) except Exception: - self.ctrl_manager.delete(controller_id) + # Don't try to delete a controller we haven't spawned + if not attributes.is_attr_set(netos_url): + self.ctrl_manager.delete(controller_id) raise # Save mapping between Neutron network ID and OVX tenant ID @@ -501,7 +506,7 @@ def _do_physical_network(self, ctrl, subnet, routing='spf', num_backup=1, copy_d # Create virtual network tenant_id = self.ovx_client.createNetwork(ctrls, net_address, int(net_mask)) - # Create big switch, remove virtual network if something went wrong + # Create duplicate of physical network, remove virtual network if something went wrong try: # Create virtual switch for each physical dpid for dpid in switches: @@ -513,7 +518,7 @@ def _do_physical_network(self, ctrl, subnet, routing='spf', num_backup=1, copy_d # Create virtual ports and connect virtual links connected = [] for link in phy_topo['links']: - # OVX creates reverse link automatically, so be careful no to create a link twice + # OVX creates reverse link automatically, so be careful not to create a link twice if (link['src']['dpid'], link['src']['port']) not in connected: # Create virtual source port # Type conversions needed because OVX JSON output is stringified From cd961a38ee1f2c5cb7c96e1cb0004d18fcecfdd0 Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Wed, 19 Nov 2014 16:40:17 -0800 Subject: [PATCH 02/19] Network OS (netos) extension. Code cleanups. --- neutron/extensions/netos.py | 49 +++++++++++++++++++ neutron/extensions/topology.py | 6 +-- neutron/plugins/opencloud/common/constants.py | 2 +- neutron/plugins/ovx/plugin.py | 35 +++++++------ 4 files changed, 73 insertions(+), 19 deletions(-) create mode 100644 neutron/extensions/netos.py diff --git a/neutron/extensions/netos.py b/neutron/extensions/netos.py new file mode 100644 index 00000000000..969f93d123e --- /dev/null +++ b/neutron/extensions/netos.py @@ -0,0 +1,49 @@ +from neutron.api.v2 import attributes +from neutron.plugins.common import constants + +IMAGE = 'netos:image' +URL = 'netos:url' + +EXTENDED_ATTRIBUTES_2_0 = { + 'networks': { + IMAGE: {'allow_post': True, 'allow_put': False, + 'default': attr.ATTR_NOT_SPECIFIED, + 'validate': {'type:string': None}, + 'is_visible': True + }, + URL: {'allow_post': True, 'allow_put': False, + 'default': attr.ATTR_NOT_SPECIFIED, + 'validate': {'type:string': None}, + 'is_visible': True + } + } +} + +class NetOS(object): + @classmethod + def get_name(cls): + return "Network OS Extension" + + @classmethod + def get_alias(cls): + return "netos" + + @classmethod + def get_description(cls): + return "Configure network operating system" + + @classmethod + def get_namespace(cls): + # return "http://docs.openstack.org/ext/provider/api/v1.0" + # Nothing there right now + return "http://www.vicci.org/ext/opencloud/topology/api/v0.1" + + @classmethod + def get_updated(cls): + return "2014-11-19T10:00:00-00:00" + + def get_extended_resources(self, version): + if version == "2.0": + return EXTENDED_ATTRIBUTES_2_0 + else: + return {} diff --git a/neutron/extensions/topology.py b/neutron/extensions/topology.py index ece2994911e..1d7166f189b 100644 --- a/neutron/extensions/topology.py +++ b/neutron/extensions/topology.py @@ -6,9 +6,9 @@ EXTENDED_ATTRIBUTES_2_0 = { 'networks': { TYPE: {'allow_post': True, 'allow_put': False, - 'default': constants.BIGSWITCH, - 'validate': {'type:values': [constants.BIGSWITCH, constants.PHYSICAL, constants.CUSTOM]}, - 'is_visible': True + 'default': constants.BIGSWITCH, + 'validate': {'type:values': [constants.BIGSWITCH, constants.PHYSICAL, constants.CUSTOM]}, + 'is_visible': True } } } diff --git a/neutron/plugins/opencloud/common/constants.py b/neutron/plugins/opencloud/common/constants.py index 7582e8e4872..bd121bdd677 100644 --- a/neutron/plugins/opencloud/common/constants.py +++ b/neutron/plugins/opencloud/common/constants.py @@ -37,7 +37,7 @@ 'ip_version': 4, 'cidr': '171.66.164.0/24', 'gateway_ip': '171.66.164.1', - 'dns_nameservers': [], + 'dns_nameservers': ['8.8.8.8'], 'allocation_pools': [{"start": "171.66.164.3", "end": "171.66.164.254"}], 'host_routes': [], 'enable_dhcp': True diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 59a3d2d512d..0a92dec3b83 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -36,6 +36,7 @@ from neutron.db import portbindings_base from neutron.db import portbindings_db from neutron.db import quota_db # noqa +from neutron.extensions import netos from neutron.extensions import portbindings from neutron.extensions import topology from neutron.openstack.common import log as logging @@ -125,7 +126,6 @@ def __init__(self, ctrl_network): service_type="compute") # Check if Nova config is correct try: - self._image = self._nova.images.find(name=cfg.CONF.NOVA.image_name) self._flavor = self._nova.flavors.find(name=cfg.CONF.NOVA.flavor) # Check if the key name is found, don't save the ref (novaclient wants the name) if cfg.CONF.NOVA.key_name: @@ -134,15 +134,18 @@ def __init__(self, ctrl_network): LOG.error("Could not initialize Nova bindings. Check your config. (%s)" % e) sys.exit(1) - def spawn(self, name): + def spawn(self, name, image_name): """Spawns SDN controller inside the control network. Returns the Nova server ID and IP address.""" + # Raise an exception if any of the parameters is incorrect + image = self._nova.images.find(name=image_name) + # Connect controller to control network nic_config = {'net-id': self.ctrl_network_id} # Can also set 'fixed_ip' if needed server = self._nova.servers.create(name='OVX_%s' % name, - image=self._image, + image=image, flavor=self._flavor, key_name=cfg.CONF.NOVA.key_name, nics=[nic_config]) @@ -160,7 +163,7 @@ def spawn(self, name): # Fetch IP address of controller instance controller_ip = server.addresses[self.ctrl_network_name][0]['addr'] - LOG.info("Spawned SDN controller image %s: ID %s, IP %s" % (cfg.CONF.NOVA.image_name, controller_id, controller_ip)) + LOG.info("Spawned SDN controller image %s: ID %s, IP %s" % (image_name, controller_id, controller_ip)) return (controller_id, controller_ip) @@ -174,7 +177,7 @@ class OVXNeutronPlugin(db_base_plugin_v2.NeutronDbPluginV2, agents_db.AgentDbMixin, portbindings_db.PortBindingMixin): - supported_extension_aliases = ['quotas', 'binding', 'agent', 'topology'] + supported_extension_aliases = ['quotas', 'binding', 'agent', 'topology', 'netos'] def __init__(self): super(OVXNeutronPlugin, self).__init__() @@ -225,21 +228,21 @@ def create_network(self, context, network): # Save in db net_db = super(OVXNeutronPlugin, self).create_network(context, network) - # Spawn controller - (controller_id, controller_ip) = self.ctrl_manager.spawn(net_db['id']) + # Spawn controller + netos_image = network['network'].get(netos.IMAGE) + if not attributes.is_attr_set(netos_image): + netos_image = cfg.CONF.NOVA.image_name + + (controller_id, controller_ip) = self.ctrl_manager.spawn(net_db['id'], netos_image) try: + # Set OVX vnet config ctrl = 'tcp:%s:%s' % (controller_ip, cfg.CONF.NOVA.image_port) # Subnet value is irrelevant to OVX subnet = '10.0.0.0/24' # Create virtual network with requested topology topology_type = network['network'].get(topology.TYPE) - topology_type_set = attributes.is_attr_set(topology_type) - - # Default topology type is bigswitch - if not topology_type_set: - topology_type = svc_constants.BIGSWITCH if topology_type == svc_constants.BIGSWITCH: ovx_tenant_id = self._do_big_switch_network(ctrl, subnet) @@ -252,7 +255,9 @@ def create_network(self, context, network): if net_db['admin_state_up']: self.ovx_client.startNetwork(ovx_tenant_id) except Exception: - self.ctrl_manager.delete(controller_id) + # Don't try to delete a controller we haven't spawned + if not attributes.is_attr_set(netos_url): + self.ctrl_manager.delete(controller_id) raise # Save mapping between Neutron network ID and OVX tenant ID @@ -501,7 +506,7 @@ def _do_physical_network(self, ctrl, subnet, routing='spf', num_backup=1, copy_d # Create virtual network tenant_id = self.ovx_client.createNetwork(ctrls, net_address, int(net_mask)) - # Create big switch, remove virtual network if something went wrong + # Create duplicate of physical network, remove virtual network if something went wrong try: # Create virtual switch for each physical dpid for dpid in switches: @@ -513,7 +518,7 @@ def _do_physical_network(self, ctrl, subnet, routing='spf', num_backup=1, copy_d # Create virtual ports and connect virtual links connected = [] for link in phy_topo['links']: - # OVX creates reverse link automatically, so be careful no to create a link twice + # OVX creates reverse link automatically, so be careful not to create a link twice if (link['src']['dpid'], link['src']['port']) not in connected: # Create virtual source port # Type conversions needed because OVX JSON output is stringified From 71914f307943c5c1a4c766aadd08f073bd3c97aa Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Wed, 19 Nov 2014 16:53:03 -0800 Subject: [PATCH 03/19] Naming fix --- neutron/extensions/netos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neutron/extensions/netos.py b/neutron/extensions/netos.py index 969f93d123e..76eedeef8be 100644 --- a/neutron/extensions/netos.py +++ b/neutron/extensions/netos.py @@ -1,4 +1,4 @@ -from neutron.api.v2 import attributes +from neutron.api.v2 import attributes as attr from neutron.plugins.common import constants IMAGE = 'netos:image' From faa9f6e8f187021b3f5436bd15e62a759045c8ec Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Wed, 19 Nov 2014 17:11:40 -0800 Subject: [PATCH 04/19] Follow OpenStack class naming convention --- neutron/extensions/netos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neutron/extensions/netos.py b/neutron/extensions/netos.py index 76eedeef8be..73294c98551 100644 --- a/neutron/extensions/netos.py +++ b/neutron/extensions/netos.py @@ -19,7 +19,7 @@ } } -class NetOS(object): +class Netos(object): @classmethod def get_name(cls): return "Network OS Extension" From 44d676abd3ddd35284cb42e5fa4cce03711ff33f Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Wed, 19 Nov 2014 18:49:48 -0800 Subject: [PATCH 05/19] Added more network OS fields --- neutron/extensions/netos.py | 19 +++++++ neutron/plugins/ovx/plugin.py | 93 ++++++++++++++++++++++------------- 2 files changed, 78 insertions(+), 34 deletions(-) diff --git a/neutron/extensions/netos.py b/neutron/extensions/netos.py index 73294c98551..ed462ff78eb 100644 --- a/neutron/extensions/netos.py +++ b/neutron/extensions/netos.py @@ -1,16 +1,35 @@ from neutron.api.v2 import attributes as attr from neutron.plugins.common import constants +FLAVOR = 'netos:flavor' IMAGE = 'netos:image' +KEY = 'netos:key' +PORT = 'netos:port' URL = 'netos:url' EXTENDED_ATTRIBUTES_2_0 = { 'networks': { + FLAVOR: {'allow_post': True, 'allow_put': False, + 'default': attr.ATTR_NOT_SPECIFIED, + 'validate': {'type:string': None}, + 'is_visible': True + }, IMAGE: {'allow_post': True, 'allow_put': False, 'default': attr.ATTR_NOT_SPECIFIED, 'validate': {'type:string': None}, 'is_visible': True }, + KEY: {'allow_post': True, 'allow_put': False, + 'default': attr.ATTR_NOT_SPECIFIED, + 'validate': {'type:string': None}, + 'is_visible': True + }, + PORT: {'allow_post': True, 'allow_put': False, + 'validate': {'type:range': [0, 65535]}, + 'default': attr.ATTR_NOT_SPECIFIED, + 'convert_to': attr.convert_to_int, + 'is_visible': True + }, URL: {'allow_post': True, 'allow_put': False, 'default': attr.ATTR_NOT_SPECIFIED, 'validate': {'type:string': None}, diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 0a92dec3b83..3f3163c4ff9 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -25,7 +25,7 @@ from oslo.config import cfg from neutron import context as ctx -from neutron.api.v2 import attributes +from neutron.api.v2 import attributes as attr from neutron.common import constants as q_const from neutron.common import exceptions as q_exc from neutron.common import rpc as q_rpc @@ -120,34 +120,29 @@ class ControllerManager(): def __init__(self, ctrl_network): self.ctrl_network_id = ctrl_network['id'] self.ctrl_network_name = ctrl_network['name'] + # Nova config for default controllers self._nova = nova_client(username=cfg.CONF.NOVA.username, api_key=cfg.CONF.NOVA.password, project_id=cfg.CONF.NOVA.project_id, auth_url=cfg.CONF.NOVA.auth_url, service_type="compute") - # Check if Nova config is correct - try: - self._flavor = self._nova.flavors.find(name=cfg.CONF.NOVA.flavor) - # Check if the key name is found, don't save the ref (novaclient wants the name) - if cfg.CONF.NOVA.key_name: - self._nova.keypairs.find(name=cfg.CONF.NOVA.key_name) - except Exception as e: - LOG.error("Could not initialize Nova bindings. Check your config. (%s)" % e) - sys.exit(1) - - def spawn(self, name, image_name): + + def spawn(self, name, nos): """Spawns SDN controller inside the control network. Returns the Nova server ID and IP address.""" # Raise an exception if any of the parameters is incorrect - image = self._nova.images.find(name=image_name) + image = self._nova.images.find(name=nos['image']) + flavor = self._nova.flavors.find(name=nos['flavor']) + # Check if the key name is found, don't save the ref (novaclient wants the name) + self._nova.keypairs.find(name=nos['key']) # Connect controller to control network nic_config = {'net-id': self.ctrl_network_id} # Can also set 'fixed_ip' if needed server = self._nova.servers.create(name='OVX_%s' % name, image=image, - flavor=self._flavor, - key_name=cfg.CONF.NOVA.key_name, + flavor=flavor, + key_name=key, nics=[nic_config]) controller_id = server.id # TODO: need a good way to obtain IP address @@ -225,19 +220,21 @@ def create_network(self, context, network): """ LOG.debug("Neutron OVX") with context.session.begin(subtransactions=True): + # Parse network OS parameters that were passed in or use default values + nos = self._parse_netos_params(network) + # Save in db net_db = super(OVXNeutronPlugin, self).create_network(context, network) - # Spawn controller - netos_image = network['network'].get(netos.IMAGE) - if not attributes.is_attr_set(netos_image): - netos_image = cfg.CONF.NOVA.image_name - - (controller_id, controller_ip) = self.ctrl_manager.spawn(net_db['id'], netos_image) - + # Spawn network OS + if not attr.is_attr_set(nos['url']): + (controller_id, controller_ip) = self.ctrl_manager.spawn(net_db['id'], nos) + + # Create and start virtual network in OVX, + # delete the network OS if something goes wrong try: - # Set OVX vnet config - ctrl = 'tcp:%s:%s' % (controller_ip, cfg.CONF.NOVA.image_port) + # OVX vnet config + ctrl = 'tcp:%s:%s' % (controller_ip, nos['port']) # Subnet value is irrelevant to OVX subnet = '10.0.0.0/24' @@ -250,19 +247,17 @@ def create_network(self, context, network): ovx_tenant_id = self._do_physical_network(ctrl, subnet) else: raise Exception("Topology type %s not supported" % topology_type) - - # Start network if requested - if net_db['admin_state_up']: - self.ovx_client.startNetwork(ovx_tenant_id) + except Exception: # Don't try to delete a controller we haven't spawned - if not attributes.is_attr_set(netos_url): + if not attr.is_attr_set(netos_url): self.ctrl_manager.delete(controller_id) - raise - + raise + # Save mapping between Neutron network ID and OVX tenant ID - ovxdb.add_ovx_network(context.session, net_db['id'], ovx_tenant_id, controller_id) - + if not attr.is_attr_set(netos_url): + ovxdb.add_ovx_network(context.session, net_db['id'], ovx_tenant_id, controller_id) + # Return created network return net_db @@ -444,8 +439,34 @@ def delete_port(self, context, id): # Remove network from db super(OVXNeutronPlugin, self).delete_port(context, id) + def _parse_netos_params(self, network): + """Returns dict with network OS parameters from network request and default values.""" + netos_image = network['network'].get(netos.IMAGE) + if not attr.is_attr_set(netos_image): + netos_image = cfg.CONF.NOVA.image_name + + netos_flavor = network['network'].get(netos.FLAVOR) + if not attr.is_attr_set(netos_flavor): + netos_flavor = cfg.CONF.NOVA.flavor + + netos_key = network['network'].get(netos.KEY) + if not attr.is_attr_set(netos_key): + netos_key = cfg.CONF.NOVA.key_name + + netos_port = network['network'].get(netos.PORT) + if not attr.is_attr_set(netos_port): + netos_port = cfg.CONF.NOVA.image_port + + netos_url = network['network'].get(netos.URL) + + return {'image': netos_image, + 'flavor': netos_flavor, + 'key': netos_key, + 'port': netos_port, + 'url': netos_url} + def _do_big_switch_network(self, ctrl, subnet, routing='spf', num_backup=1): - """Create virtual network in OVX that is a single big switch. + """Create and start virtual network in OVX that is a single big switch. If any step fails during network creation, no virtual network will be created.""" @@ -476,6 +497,8 @@ def _do_big_switch_network(self, ctrl, subnet, routing='spf', num_backup=1): # Set routing algorithm and number of backups if (len(dpids) > 1): self.ovx_client.setInternalRouting(tenant_id, vdpid, routing, num_backup) + # Start network + self.ovx_client.startNetwork(tenant_id) except Exception: self.ovx_client.removeNetwork(tenant_id) raise @@ -536,6 +559,8 @@ def _do_physical_network(self, ctrl, subnet, routing='spf', num_backup=1, copy_d # Store reverse link so we don't try to create it again connected.append((link['dst']['dpid'], link['dst']['port'])) + # Start network + self.ovx_client.startNetwork(tenant_id) except Exception: self.ovx_client.removeNetwork(tenant_id) raise From eeee3ef2480f89a8486628714dadf34bce84492c Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Wed, 19 Nov 2014 22:11:38 -0800 Subject: [PATCH 06/19] Fix key passing issue --- neutron/plugins/ovx/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 3f3163c4ff9..8e927d18abc 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -142,7 +142,7 @@ def spawn(self, name, nos): server = self._nova.servers.create(name='OVX_%s' % name, image=image, flavor=flavor, - key_name=key, + key_name=nos['key'], nics=[nic_config]) controller_id = server.id # TODO: need a good way to obtain IP address From 06e88d4096091d4865ec0762213d5128fdb00083 Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Wed, 19 Nov 2014 23:07:00 -0800 Subject: [PATCH 07/19] Key naming troubles --- neutron/plugins/ovx/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 8e927d18abc..7db8fcf3663 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -134,7 +134,8 @@ def spawn(self, name, nos): image = self._nova.images.find(name=nos['image']) flavor = self._nova.flavors.find(name=nos['flavor']) # Check if the key name is found, don't save the ref (novaclient wants the name) - self._nova.keypairs.find(name=nos['key']) + if nos['key']: + self._nova.keypairs.find(name=nos['key']) # Connect controller to control network nic_config = {'net-id': self.ctrl_network_id} From 13f57f2b6bd73ec482040e46e66d842d001a4316 Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Wed, 19 Nov 2014 23:31:07 -0800 Subject: [PATCH 08/19] Log fix --- neutron/plugins/ovx/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 7db8fcf3663..0d1bf1673bf 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -159,7 +159,7 @@ def spawn(self, name, nos): # Fetch IP address of controller instance controller_ip = server.addresses[self.ctrl_network_name][0]['addr'] - LOG.info("Spawned SDN controller image %s: ID %s, IP %s" % (image_name, controller_id, controller_ip)) + LOG.info("Spawned SDN controller image %s: ID %s, IP %s" % (nos['image'], controller_id, controller_ip)) return (controller_id, controller_ip) From c6ba1f179ce3ee0e5717a3d72325b7939d8ad282 Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Wed, 19 Nov 2014 23:39:44 -0800 Subject: [PATCH 09/19] Fixed net OS URL --- neutron/plugins/ovx/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 0d1bf1673bf..231919f01a8 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -251,12 +251,12 @@ def create_network(self, context, network): except Exception: # Don't try to delete a controller we haven't spawned - if not attr.is_attr_set(netos_url): + if not attr.is_attr_set(nos['url']): self.ctrl_manager.delete(controller_id) raise # Save mapping between Neutron network ID and OVX tenant ID - if not attr.is_attr_set(netos_url): + if not attr.is_attr_set(nos['url']): ovxdb.add_ovx_network(context.session, net_db['id'], ovx_tenant_id, controller_id) # Return created network From 76a5970b56c1d768fb03c46c0ea7eeb3101dcfb9 Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Thu, 20 Nov 2014 15:47:16 -0800 Subject: [PATCH 10/19] Don't delete net OS managed by user. Fix OVX vnet create bug. --- neutron/plugins/ovx/plugin.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 231919f01a8..e7518252b54 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -227,18 +227,18 @@ def create_network(self, context, network): # Save in db net_db = super(OVXNeutronPlugin, self).create_network(context, network) - # Spawn network OS + # Spawn network OS and create OVX vnet config if not attr.is_attr_set(nos['url']): (controller_id, controller_ip) = self.ctrl_manager.spawn(net_db['id'], nos) + ctrl = 'tcp:%s:%s' % (controller_ip, nos['port']) + else: + ctrl = nos['url'] + subnet = '10.0.0.0/24' + # Create and start virtual network in OVX, # delete the network OS if something goes wrong try: - # OVX vnet config - ctrl = 'tcp:%s:%s' % (controller_ip, nos['port']) - # Subnet value is irrelevant to OVX - subnet = '10.0.0.0/24' - # Create virtual network with requested topology topology_type = network['network'].get(topology.TYPE) @@ -315,9 +315,14 @@ def delete_network(self, context, id): # then you get into an error state # Need to remove the controller before the network, - # as Nova will also delete the port in Neutron - ovx_controller = ovxdb.get_ovx_network(context.session, id).ovx_controller - self.ctrl_manager.delete(ovx_controller) + # as Nova will also delete the port in Neutron. + # Ignore if controller doesn't exist + # TODO: check if netos:url was set instead + try: + ovx_controller = ovxdb.get_ovx_network(context.session, id).ovx_controller + self.ctrl_manager.delete(ovx_controller) + except NoResultFound: + pass # Remove network from OVX ovx_tenant_id = ovxdb.get_ovx_network(context.session, id).ovx_tenant_id From 43a5123e3af0fc4e2d6763b60775b65a6724607d Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Thu, 20 Nov 2014 15:56:00 -0800 Subject: [PATCH 11/19] Fix delete_network for user-managed controller --- neutron/plugins/ovx/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index e7518252b54..ba281f23ef5 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -321,7 +321,8 @@ def delete_network(self, context, id): try: ovx_controller = ovxdb.get_ovx_network(context.session, id).ovx_controller self.ctrl_manager.delete(ovx_controller) - except NoResultFound: + except: + log.warn("Could not remove controller, most likely because it is managed by user." pass # Remove network from OVX From b7b69c88e835ad323d73ad422eff59c7a569fdfa Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Thu, 20 Nov 2014 15:58:41 -0800 Subject: [PATCH 12/19] Typos --- neutron/plugins/ovx/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index ba281f23ef5..79a7075835e 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -322,8 +322,7 @@ def delete_network(self, context, id): ovx_controller = ovxdb.get_ovx_network(context.session, id).ovx_controller self.ctrl_manager.delete(ovx_controller) except: - log.warn("Could not remove controller, most likely because it is managed by user." - pass + LOG.warn("Could not remove controller, most likely because it is managed by user.") # Remove network from OVX ovx_tenant_id = ovxdb.get_ovx_network(context.session, id).ovx_tenant_id From c04a1d980756c8a683f0d50ff28a182ad9520a57 Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Thu, 20 Nov 2014 16:09:25 -0800 Subject: [PATCH 13/19] Fix for network delete without controller --- neutron/plugins/ovx/plugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 79a7075835e..f1850778398 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -232,6 +232,8 @@ def create_network(self, context, network): (controller_id, controller_ip) = self.ctrl_manager.spawn(net_db['id'], nos) ctrl = 'tcp:%s:%s' % (controller_ip, nos['port']) else: + # Need a fake one because it's stored with the OVX tenant ID + controller_id = None ctrl = nos['url'] subnet = '10.0.0.0/24' @@ -256,8 +258,7 @@ def create_network(self, context, network): raise # Save mapping between Neutron network ID and OVX tenant ID - if not attr.is_attr_set(nos['url']): - ovxdb.add_ovx_network(context.session, net_db['id'], ovx_tenant_id, controller_id) + ovxdb.add_ovx_network(context.session, net_db['id'], ovx_tenant_id, controller_id) # Return created network return net_db From af59bb183035f18b48b9b02e5a9128010db5f9b7 Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Thu, 20 Nov 2014 16:14:08 -0800 Subject: [PATCH 14/19] Allow null controller IDs in db --- neutron/plugins/ovx/ovx_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neutron/plugins/ovx/ovx_models.py b/neutron/plugins/ovx/ovx_models.py index c97adafb7bf..4d7d6bb6451 100644 --- a/neutron/plugins/ovx/ovx_models.py +++ b/neutron/plugins/ovx/ovx_models.py @@ -25,7 +25,7 @@ class NetworkMapping(model_base.BASEV2): sa.ForeignKey('networks.id', ondelete="CASCADE"), primary_key=True) ovx_tenant_id = sa.Column(sa.Integer, nullable=False) - ovx_controller = sa.Column(sa.String(36), nullable=False) + ovx_controller = sa.Column(sa.String(36), nullable=True) def __repr__(self): return "" % (self.neutron_network_id, From 9a30124aa9ae1e469bbe406a6113de72c1e95a51 Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Thu, 20 Nov 2014 16:27:40 -0800 Subject: [PATCH 15/19] Simpler handling of controller removal --- neutron/plugins/ovx/plugin.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index f1850778398..840b7d1c171 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -235,6 +235,7 @@ def create_network(self, context, network): # Need a fake one because it's stored with the OVX tenant ID controller_id = None ctrl = nos['url'] + # Subnet value is ignored in OVX for now subnet = '10.0.0.0/24' @@ -317,13 +318,9 @@ def delete_network(self, context, id): # Need to remove the controller before the network, # as Nova will also delete the port in Neutron. - # Ignore if controller doesn't exist - # TODO: check if netos:url was set instead - try: - ovx_controller = ovxdb.get_ovx_network(context.session, id).ovx_controller + ovx_controller = ovxdb.get_ovx_network(context.session, id).ovx_controller + if ovx_controller: self.ctrl_manager.delete(ovx_controller) - except: - LOG.warn("Could not remove controller, most likely because it is managed by user.") # Remove network from OVX ovx_tenant_id = ovxdb.get_ovx_network(context.session, id).ovx_tenant_id From 7e993b206517cb77ac03a12561188dc9c59c7c6e Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Fri, 21 Nov 2014 14:02:13 -0800 Subject: [PATCH 16/19] RPC for ports with updated openflow port --- neutron/plugins/ovx/agent.py | 24 ++++++++++++++---------- neutron/plugins/ovx/ovxdb.py | 9 +++++++++ neutron/plugins/ovx/plugin.py | 30 +++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/neutron/plugins/ovx/agent.py b/neutron/plugins/ovx/agent.py index 18970f9e418..90e40c76980 100644 --- a/neutron/plugins/ovx/agent.py +++ b/neutron/plugins/ovx/agent.py @@ -33,17 +33,18 @@ LOG = log.getLogger(__name__) class OVXPluginApi(agent_rpc.PluginApi): - def update_ports(self, context, agent_id, dpid, ports_added, ports_removed): + def update_ports(self, context, agent_id, dpid, ports_added, ports_removed, ports_updated): """RPC to update information of ports on Neutron plugin.""" - LOG.info(_("Update ports: added=%(added)s, removed=%(removed)s"), - {'added': ports_added, 'removed': ports_removed}) + LOG.info(_("Update ports: added=%(added)s, removed=%(removed)s, updated=%(updated)s"), + {'added': ports_added, 'removed': ports_removed, 'updated': ports_updated}) self.cast(context, self.make_msg('update_ports', topic=topics.AGENT, agent_id=agent_id, dpid=dpid, ports_added=ports_added, - ports_removed=ports_removed)) + ports_removed=ports_removed, + ports_updated=ports_updated)) class OVXNeutronAgent(): def __init__(self, data_bridge, root_helper, polling_interval): @@ -107,19 +108,22 @@ def daemon_loop(self): while True: start = time.time() try: - # List of port IDs - cur_ports = [] if self.need_sync else self.cur_ports - new_ports = [] + # Dicts with (key,value): (port ID, port info) + cur_ports = {} if self.need_sync else self.cur_ports + new_ports = {} # List of port dicts ports_added = [] + ports_updated = [] for vif_port in self.data_bridge.get_vif_ports(): port_info = self._vif_port_to_port_info(vif_port) port_id = port_info['id'] - new_ports.append(port_id) + new_ports[port_id] = port_info if port_id not in cur_ports: ports_added.append(port_info) + elif cur_ports[port_id]['port_no'] != port_info['port_no']: + ports_updated.append(port_info) # List of port IDs ports_removed = [] @@ -127,10 +131,10 @@ def daemon_loop(self): if port_id not in new_ports: ports_removed.append(port_id) - if ports_added or ports_removed: + if ports_added or ports_removed or ports_updated: self.plugin_rpc.update_ports(self.context, self.agent_id, self.dpid, - ports_added, ports_removed) + ports_added, ports_removed, ports_updated) else: LOG.debug(_("No ports changed.")) diff --git a/neutron/plugins/ovx/ovxdb.py b/neutron/plugins/ovx/ovxdb.py index 39d31b6ee38..bcbd395fd80 100644 --- a/neutron/plugins/ovx/ovxdb.py +++ b/neutron/plugins/ovx/ovxdb.py @@ -46,6 +46,15 @@ def get_ovx_port(session, neutron_port_id): query = session.query(ovx_models.PortMapping) return query.filter_by(neutron_port_id=neutron_port_id).one() +def set_ovx_port(session, neutron_port_id, ovx_vdpid, ovx_vport, ovx_host_id): + query = session.query(ovx_models.PortMapping) + result = query.filter_by(neutron_port_id=neutron_port_id).one() + result['ovx_vdpid'] = ovx_vdpid + result['ovx_vport'] = ovx_vport + result['ovx_host_id'] = ovx_vhost_id + session.merge(result) + session.flush() + def del_ovx_port(session, neutron_port_id): query = session.query(ovx_models.PortMapping) result = query.filter_by(neutron_port_id=neutron_port_id).one() diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 840b7d1c171..eaa16a35548 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -112,7 +112,35 @@ def update_ports(self, rpc_context, **kwargs): # Set port status to DOWN if port_db['status'] != q_const.PORT_STATUS_DOWN: ovxdb.set_port_status(rpc_context.session, port_id, q_const.PORT_STATUS_DOWN) - + + # Ports updated on the compute node will be recreated in OVX and updated in the db. + for p in kwargs.get('ports_updated', []): + port_id = p['id'] + port_no = p['port_no'] + + with rpc_context.session.begin(subtransactions=True): + # Lookup port + port_db = self.plugin.get_port(rpc_context, port_id) + + # Lookup OVX tenant ID + neutron_network_id = port_db['network_id'] + ovx_tenant_id = ovxdb.get_ovx_network(rpc_context.session, neutron_network_id).ovx_tenant_id + + # Lookup OVX tenant ID, virtual dpid and virtual port number + ovx_port = ovxdb.get_ovx_port(rpc_context.session, port_db['id']) + (ovx_vdpid, ovx_vport, ovx_host_id) = ovx_port.ovx_vdpid, ovx_port.ovx_vport, ovx_port.ovx_host_id + + # Recreate port in OVX + self.plugin.ovx_client.removePort(ovx_tenant_id, ovx_vdpid, ovx_vport) + (ovx_vdpid, ovx_vport) = self.plugin.ovx_client.createPort(ovx_tenant_id, ovxlib.hexToLong(dpid), int(port_no)) + + # Register host in OVX + ovx_host_id = self.plugin.ovx_client.connectHost(ovx_tenant_id, ovx_vdpid, ovx_vport, port_db['mac_address']) + + # Updated mapping between Neutron port ID and OVX dpid, port number, and host ID + ovxdb.set_ovx_port(rpc_context.session, port_db['id'], ovx_vdpid, ovx_vport, ovx_host_id) + + class ControllerManager(): """Simple manager for SDN controllers. Spawns a VM running a controller for each request inside the control network.""" From e8ef0fad32bc54a42998129e176047c24fffee65 Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Fri, 21 Nov 2014 15:41:59 -0800 Subject: [PATCH 17/19] Removed updated ports RPC. Improved handling of delete ports in RPC. --- neutron/plugins/ovx/agent.py | 31 ++++++++++++++----------------- neutron/plugins/ovx/ovxdb.py | 9 --------- neutron/plugins/ovx/plugin.py | 34 +++++++++------------------------- 3 files changed, 23 insertions(+), 51 deletions(-) diff --git a/neutron/plugins/ovx/agent.py b/neutron/plugins/ovx/agent.py index 90e40c76980..8626b5a59eb 100644 --- a/neutron/plugins/ovx/agent.py +++ b/neutron/plugins/ovx/agent.py @@ -33,18 +33,17 @@ LOG = log.getLogger(__name__) class OVXPluginApi(agent_rpc.PluginApi): - def update_ports(self, context, agent_id, dpid, ports_added, ports_removed, ports_updated): + def update_ports(self, context, agent_id, dpid, ports_added, ports_removed): """RPC to update information of ports on Neutron plugin.""" - LOG.info(_("Update ports: added=%(added)s, removed=%(removed)s, updated=%(updated)s"), - {'added': ports_added, 'removed': ports_removed, 'updated': ports_updated}) + LOG.info(_("Update ports: added=%(added)s, removed=%(removed)s"), + {'added': ports_added, 'removed': ports_removed}) self.cast(context, self.make_msg('update_ports', topic=topics.AGENT, agent_id=agent_id, dpid=dpid, ports_added=ports_added, - ports_removed=ports_removed, - ports_updated=ports_updated)) + ports_removed=ports_removed)) class OVXNeutronAgent(): def __init__(self, data_bridge, root_helper, polling_interval): @@ -108,33 +107,31 @@ def daemon_loop(self): while True: start = time.time() try: - # Dicts with (key,value): (port ID, port info) - cur_ports = {} if self.need_sync else self.cur_ports - new_ports = {} + # List of port IDs + cur_ports = [] if self.need_sync else self.cur_ports + new_ports = [] - # List of port dicts + # Build list of all port IDs that are present now, and list + # of ports that were added since the previous iteration. + # List of full port info ports_added = [] - ports_updated = [] for vif_port in self.data_bridge.get_vif_ports(): port_info = self._vif_port_to_port_info(vif_port) port_id = port_info['id'] - new_ports[port_id] = port_info - + new_ports.append(port_id) if port_id not in cur_ports: ports_added.append(port_info) - elif cur_ports[port_id]['port_no'] != port_info['port_no']: - ports_updated.append(port_info) - # List of port IDs + # Build list port IDs ports_removed = [] for port_id in cur_ports: if port_id not in new_ports: ports_removed.append(port_id) - if ports_added or ports_removed or ports_updated: + if ports_added or ports_removed: self.plugin_rpc.update_ports(self.context, self.agent_id, self.dpid, - ports_added, ports_removed, ports_updated) + ports_added, ports_removed) else: LOG.debug(_("No ports changed.")) diff --git a/neutron/plugins/ovx/ovxdb.py b/neutron/plugins/ovx/ovxdb.py index bcbd395fd80..39d31b6ee38 100644 --- a/neutron/plugins/ovx/ovxdb.py +++ b/neutron/plugins/ovx/ovxdb.py @@ -46,15 +46,6 @@ def get_ovx_port(session, neutron_port_id): query = session.query(ovx_models.PortMapping) return query.filter_by(neutron_port_id=neutron_port_id).one() -def set_ovx_port(session, neutron_port_id, ovx_vdpid, ovx_vport, ovx_host_id): - query = session.query(ovx_models.PortMapping) - result = query.filter_by(neutron_port_id=neutron_port_id).one() - result['ovx_vdpid'] = ovx_vdpid - result['ovx_vport'] = ovx_vport - result['ovx_host_id'] = ovx_vhost_id - session.merge(result) - session.flush() - def del_ovx_port(session, neutron_port_id): query = session.query(ovx_models.PortMapping) result = query.filter_by(neutron_port_id=neutron_port_id).one() diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index eaa16a35548..36c6bbe1597 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -98,30 +98,17 @@ def update_ports(self, rpc_context, **kwargs): # Set port in active state in db ovxdb.set_port_status(rpc_context.session, port_db['id'], q_const.PORT_STATUS_ACTIVE) - # Ports removed on the compute node will be marked as down in the database. - # Use Neutron API to explicitly remove port from OVX & Neutron. + # Ports removed on the compute node will be removed from OVX and the OVX-Neutron mapping. + # Use Neutron API to explicitly remove port from Neutron db. for port_id in kwargs.get('ports_removed', []): with rpc_context.session.begin(subtransactions=True): - # Lookup port + # Lookup port, move on if it was already delete in delete_port try: port_db = self.plugin.get_port(rpc_context, port_id) except q_exc.PortNotFound: continue - # Set port status to DOWN - if port_db['status'] != q_const.PORT_STATUS_DOWN: - ovxdb.set_port_status(rpc_context.session, port_id, q_const.PORT_STATUS_DOWN) - - # Ports updated on the compute node will be recreated in OVX and updated in the db. - for p in kwargs.get('ports_updated', []): - port_id = p['id'] - port_no = p['port_no'] - - with rpc_context.session.begin(subtransactions=True): - # Lookup port - port_db = self.plugin.get_port(rpc_context, port_id) - # Lookup OVX tenant ID neutron_network_id = port_db['network_id'] ovx_tenant_id = ovxdb.get_ovx_network(rpc_context.session, neutron_network_id).ovx_tenant_id @@ -130,16 +117,13 @@ def update_ports(self, rpc_context, **kwargs): ovx_port = ovxdb.get_ovx_port(rpc_context.session, port_db['id']) (ovx_vdpid, ovx_vport, ovx_host_id) = ovx_port.ovx_vdpid, ovx_port.ovx_vport, ovx_port.ovx_host_id - # Recreate port in OVX + # Remove port from OVX and db self.plugin.ovx_client.removePort(ovx_tenant_id, ovx_vdpid, ovx_vport) - (ovx_vdpid, ovx_vport) = self.plugin.ovx_client.createPort(ovx_tenant_id, ovxlib.hexToLong(dpid), int(port_no)) - - # Register host in OVX - ovx_host_id = self.plugin.ovx_client.connectHost(ovx_tenant_id, ovx_vdpid, ovx_vport, port_db['mac_address']) - - # Updated mapping between Neutron port ID and OVX dpid, port number, and host ID - ovxdb.set_ovx_port(rpc_context.session, port_db['id'], ovx_vdpid, ovx_vport, ovx_host_id) - + ovxdb.del_ovx_port(rpc_context.session, port_db['id']) + + # Set port status to DOWN + if port_db['status'] != q_const.PORT_STATUS_DOWN: + ovxdb.set_port_status(rpc_context.session, port_id, q_const.PORT_STATUS_DOWN) class ControllerManager(): """Simple manager for SDN controllers. Spawns a VM running a controller for each request From a2b0e8109f6d227bcbc61f41ed628fbe3d1b985b Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Fri, 21 Nov 2014 15:56:21 -0800 Subject: [PATCH 18/19] Catch exception when removing OVX port --- neutron/plugins/ovx/plugin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 36c6bbe1597..7c704d8814d 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -118,7 +118,11 @@ def update_ports(self, rpc_context, **kwargs): (ovx_vdpid, ovx_vport, ovx_host_id) = ovx_port.ovx_vdpid, ovx_port.ovx_vport, ovx_port.ovx_host_id # Remove port from OVX and db - self.plugin.ovx_client.removePort(ovx_tenant_id, ovx_vdpid, ovx_vport) + try: + self.plugin.ovx_client.removePort(ovx_tenant_id, ovx_vdpid, ovx_vport) + except ovxlib.OVXException: + LOG.warn("Could not remove OVX port, most likely because physical port was already removed.") + ovxdb.del_ovx_port(rpc_context.session, port_db['id']) # Set port status to DOWN From b6168db0cf8aa8b134080944c49094cc52e1b2a8 Mon Sep 17 00:00:00 2001 From: Marc De Leenheer Date: Tue, 16 Dec 2014 16:57:20 -0800 Subject: [PATCH 19/19] Reintroduced port updates --- neutron/plugins/ovx/agent.py | 37 ++++++++++++++++++++++------------- neutron/plugins/ovx/ovxdb.py | 11 ++++++++++- neutron/plugins/ovx/plugin.py | 36 +++++++++++++++++++++++++++++----- 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/neutron/plugins/ovx/agent.py b/neutron/plugins/ovx/agent.py index 8626b5a59eb..af50362cc5e 100644 --- a/neutron/plugins/ovx/agent.py +++ b/neutron/plugins/ovx/agent.py @@ -33,17 +33,19 @@ LOG = log.getLogger(__name__) class OVXPluginApi(agent_rpc.PluginApi): - def update_ports(self, context, agent_id, dpid, ports_added, ports_removed): + def update_ports(self, context, agent_id, dpid, + ports_added, ports_removed, ports_updated): """RPC to update information of ports on Neutron plugin.""" - LOG.info(_("Update ports: added=%(added)s, removed=%(removed)s"), - {'added': ports_added, 'removed': ports_removed}) + LOG.info(_("Update ports: added=%(added)s, removed=%(removed)s, updated=%(updated)s"), + {'added': ports_added, 'removed': ports_removed, 'updated': ports_updated}) self.cast(context, self.make_msg('update_ports', topic=topics.AGENT, agent_id=agent_id, dpid=dpid, ports_added=ports_added, - ports_removed=ports_removed)) + ports_removed=ports_removed, + ports_updated=ports_updated)) class OVXNeutronAgent(): def __init__(self, data_bridge, root_helper, polling_interval): @@ -100,27 +102,34 @@ def _vif_port_to_port_info(self, vif_port): def daemon_loop(self): """Runs an infinite loop where each iteration: - (a) checks if ports were added or removed, + (a) checks if ports were added, removed, or updated (b) if port changes are observed, an RPC call on the plugin is triggered, and - (c) sleeps for the polling interval.""" + (c) sleeps for the polling interval. + + When rebooting an instance, the port may be deleted and added in the same iteration. + The only change will be the OpenFlow port number, so we need to check for updated ports. + """ while True: start = time.time() try: - # List of port IDs - cur_ports = [] if self.need_sync else self.cur_ports - new_ports = [] + # Dict with (key,value): (port ID, port info) + cur_ports = {} if self.need_sync else self.cur_ports + new_ports = {} - # Build list of all port IDs that are present now, and list - # of ports that were added since the previous iteration. + # Build list of all port IDs that are present now, and lists + # of ports that were added or updated since the previous iteration. # List of full port info ports_added = [] + ports_updated = [] for vif_port in self.data_bridge.get_vif_ports(): port_info = self._vif_port_to_port_info(vif_port) port_id = port_info['id'] - new_ports.append(port_id) + new_ports[port_id] = port_info if port_id not in cur_ports: ports_added.append(port_info) + elif cur_ports[port_id]['port_no'] != port_info['port_no']: + ports_updated.append(port_info) # Build list port IDs ports_removed = [] @@ -128,10 +137,10 @@ def daemon_loop(self): if port_id not in new_ports: ports_removed.append(port_id) - if ports_added or ports_removed: + if ports_added or ports_removed or ports_updated: self.plugin_rpc.update_ports(self.context, self.agent_id, self.dpid, - ports_added, ports_removed) + ports_added, ports_removed, ports_updated) else: LOG.debug(_("No ports changed.")) diff --git a/neutron/plugins/ovx/ovxdb.py b/neutron/plugins/ovx/ovxdb.py index 39d31b6ee38..b141ff0ea99 100644 --- a/neutron/plugins/ovx/ovxdb.py +++ b/neutron/plugins/ovx/ovxdb.py @@ -51,7 +51,16 @@ def del_ovx_port(session, neutron_port_id): result = query.filter_by(neutron_port_id=neutron_port_id).one() if result: session.delete(result) - + +def set_ovx_port(session, neutron_port_id, ovx_vdpid, ovx_vport, ovx_host_id): + query = session.query(ovx_models.PortMapping) + result = query.filter_by(neutron_port_id=neutron_port_id).one() + result['ovx_vdpid'] = ovx_vdpid + result['ovx_vport'] = ovx_vport + result['ovx_host_id'] = ovx_vhost_id + session.merge(result) + session.flush() + def set_port_status(session, port_id, status): """Set the port status.""" query = session.query(models_v2.Port) diff --git a/neutron/plugins/ovx/plugin.py b/neutron/plugins/ovx/plugin.py index 7c704d8814d..01ea0c3aa2f 100644 --- a/neutron/plugins/ovx/plugin.py +++ b/neutron/plugins/ovx/plugin.py @@ -82,16 +82,16 @@ def update_ports(self, rpc_context, **kwargs): neutron_network_id = port_db['network_id'] ovx_tenant_id = ovxdb.get_ovx_network(rpc_context.session, neutron_network_id).ovx_tenant_id - # Stop port if requested (port is started by default in OVX) - if not port_db['admin_state_up']: - self.plugin.ovx_client.stopPort(ovx_tenant_id, ovx_vdpid, ovx_vport) - # Create port in OVX (ovx_vdpid, ovx_vport) = self.plugin.ovx_client.createPort(ovx_tenant_id, ovxlib.hexToLong(dpid), int(port_no)) # Register host in OVX ovx_host_id = self.plugin.ovx_client.connectHost(ovx_tenant_id, ovx_vdpid, ovx_vport, port_db['mac_address']) + # Stop port if requested (port is started by default in OVX) + if not port_db['admin_state_up']: + self.plugin.ovx_client.stopPort(ovx_tenant_id, ovx_vdpid, ovx_vport) + # Save mapping between Neutron port ID and OVX dpid, port number, and host ID ovxdb.add_ovx_port(rpc_context.session, port_db['id'], ovx_vdpid, ovx_vport, ovx_host_id) @@ -106,7 +106,7 @@ def update_ports(self, rpc_context, **kwargs): # Lookup port, move on if it was already delete in delete_port try: port_db = self.plugin.get_port(rpc_context, port_id) - except q_exc.PortNotFound: + except: continue # Lookup OVX tenant ID @@ -129,6 +129,32 @@ def update_ports(self, rpc_context, **kwargs): if port_db['status'] != q_const.PORT_STATUS_DOWN: ovxdb.set_port_status(rpc_context.session, port_id, q_const.PORT_STATUS_DOWN) + for p in kwargs.get('ports_updated', []): + port_id = p['id'] + port_no = p['port_no'] + + with rpc_context.session.begin(subtransactions=True): + # Lookup port + port_db = self.plugin.get_port(rpc_context, port_id) + + # Lookup OVX tenant ID + neutron_network_id = port_db['network_id'] + ovx_tenant_id = ovxdb.get_ovx_network(rpc_context.session, neutron_network_id).ovx_tenant_id + + # Lookup OVX tenant ID, virtual dpid and virtual port number + ovx_port = ovxdb.get_ovx_port(rpc_context.session, port_id) + (ovx_vdpid, ovx_vport, ovx_host_id) = ovx_port.ovx_vdpid, ovx_port.ovx_vport, ovx_port.ovx_host_id + + # Recreate port in OVX + self.plugin.ovx_client.removePort(ovx_tenant_id, ovx_vdpid, ovx_vport) + (ovx_vdpid, ovx_vport) = self.plugin.ovx_client.createPort(ovx_tenant_id, ovxlib.hexToLong(dpid), int(port_no)) + + # Register host in OVX + ovx_host_id = self.plugin.ovx_client.connectHost(ovx_tenant_id, ovx_vdpid, ovx_vport, port_db['mac_address']) + + # Update mapping between Neutron port ID and OVX dpid, port number, and host ID + ovxdb.set_ovx_port(rpc_context.session, port_id, ovx_vdpid, ovx_vport, ovx_host_id) + class ControllerManager(): """Simple manager for SDN controllers. Spawns a VM running a controller for each request inside the control network."""