From 76fc0addc985195dc6fddd78c9030cc5d24cfe88 Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Tue, 23 Jul 2013 13:21:24 +0800 Subject: [PATCH] refactor port binding codes Bug #1195047 This patch: 1. removes the iteration in get_ports to call extend_dict_binding 2. uses a unified way for plugins to do this kind of stuff ml2 will enhance the binding so that it can have different binding for port according to port's host. mlnx and bigswitch are also exceptions due to the dynamic binding info. Change-Id: I5a77eb7395e14482a856e033f536f72a1bf82e06 --- neutron/db/portbindings_base.py | 43 +++++++++++++++ neutron/db/portbindings_db.py | 30 +++++------ neutron/plugins/brocade/NeutronPlugin.py | 52 ++++++++----------- .../plugins/hyperv/hyperv_neutron_plugin.py | 35 ++++++------- .../plugins/linuxbridge/lb_neutron_plugin.py | 2 +- neutron/plugins/ml2/plugin.py | 2 +- neutron/plugins/nec/nec_plugin.py | 47 ++++++++--------- neutron/plugins/nicira/NeutronPlugin.py | 2 +- .../plugins/openvswitch/ovs_neutron_plugin.py | 2 +- neutron/plugins/ryu/ryu_neutron_plugin.py | 33 +++++++----- 10 files changed, 139 insertions(+), 109 deletions(-) create mode 100644 neutron/db/portbindings_base.py diff --git a/neutron/db/portbindings_base.py b/neutron/db/portbindings_base.py new file mode 100644 index 0000000000..615b017b75 --- /dev/null +++ b/neutron/db/portbindings_base.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 UnitedStack Inc. +# 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. +# @author: Yong Sheng Gong, UnitedStack Inc. + +from neutron.api.v2 import attributes +from neutron.db import db_base_plugin_v2 + + +class PortBindingBaseMixin(object): + base_binding_dict = None + + def _process_portbindings_create_and_update(self, context, port_data, + port): + self.extend_port_dict_binding(port, None) + + def extend_port_dict_binding(self, port_res, port_db): + if self.base_binding_dict: + port_res.update(self.base_binding_dict) + + +def _extend_port_dict_binding(plugin, port_res, port_db): + if not isinstance(plugin, PortBindingBaseMixin): + return + plugin.extend_port_dict_binding(port_res, port_db) + + +def register_port_dict_function(): + db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs( + attributes.PORTS, [_extend_port_dict_binding]) diff --git a/neutron/db/portbindings_db.py b/neutron/db/portbindings_db.py index a551064648..8340e0f098 100644 --- a/neutron/db/portbindings_db.py +++ b/neutron/db/portbindings_db.py @@ -23,11 +23,8 @@ from neutron.api.v2 import attributes from neutron.db import db_base_plugin_v2 from neutron.db import model_base from neutron.db import models_v2 +from neutron.db import portbindings_base from neutron.extensions import portbindings -from neutron.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) class PortBindingPort(model_base.BASEV2): @@ -42,7 +39,7 @@ class PortBindingPort(model_base.BASEV2): cascade='delete')) -class PortBindingMixin(object): +class PortBindingMixin(portbindings_base.PortBindingBaseMixin): extra_binding_dict = None def _port_model_hook(self, context, original_model, query): @@ -77,7 +74,7 @@ class PortBindingMixin(object): host = port_data.get(portbindings.HOST_ID) host_set = attributes.is_attr_set(host) if not host_set: - _extend_port_dict_binding_host(self, port, None) + self._extend_port_dict_binding_host(port, None) return with context.session.begin(subtransactions=True): bind_port = context.session.query( @@ -87,7 +84,7 @@ class PortBindingMixin(object): host=host)) else: bind_port.host = host - _extend_port_dict_binding_host(self, port, host) + self._extend_port_dict_binding_host(port, host) def get_port_host(self, context, port_id): with context.session.begin(subtransactions=True): @@ -95,21 +92,22 @@ class PortBindingMixin(object): PortBindingPort).filter_by(port_id=port_id).first() return bind_port and bind_port.host or None + def _extend_port_dict_binding_host(self, port_res, host): + super(PortBindingMixin, self).extend_port_dict_binding( + port_res, None) + port_res[portbindings.HOST_ID] = host -def _extend_port_dict_binding_host(plugin, port_res, host): - port_res[portbindings.HOST_ID] = host - if plugin.extra_binding_dict: - port_res.update(plugin.extra_binding_dict) - return port_res + def extend_port_dict_binding(self, port_res, port_db): + host = (port_db.portbinding and port_db.portbinding.host or None) + self._extend_port_dict_binding_host(port_res, host) def _extend_port_dict_binding(plugin, port_res, port_db): if not isinstance(plugin, PortBindingMixin): return - host = (port_db.portbinding and port_db.portbinding.host or None) - return _extend_port_dict_binding_host( - plugin, port_res, host) + plugin.extend_port_dict_binding(port_res, port_db) - # Register dict extend functions for ports + +# Register dict extend functions for ports db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs( attributes.PORTS, [_extend_port_dict_binding]) diff --git a/neutron/plugins/brocade/NeutronPlugin.py b/neutron/plugins/brocade/NeutronPlugin.py index 2a676e918d..1534a312cf 100644 --- a/neutron/plugins/brocade/NeutronPlugin.py +++ b/neutron/plugins/brocade/NeutronPlugin.py @@ -41,6 +41,7 @@ from neutron.db import db_base_plugin_v2 from neutron.db import dhcp_rpc_base from neutron.db import extraroute_db from neutron.db import l3_rpc_base +from neutron.db import portbindings_base from neutron.db import securitygroups_rpc_base as sg_db_rpc from neutron.extensions import portbindings from neutron.extensions import securitygroup as ext_sg @@ -199,7 +200,8 @@ class BrocadePluginV2(db_base_plugin_v2.NeutronDbPluginV2, extraroute_db.ExtraRoute_db_mixin, sg_db_rpc.SecurityGroupServerRpcMixin, agentschedulers_db.L3AgentSchedulerDbMixin, - agentschedulers_db.DhcpAgentSchedulerDbMixin): + agentschedulers_db.DhcpAgentSchedulerDbMixin, + portbindings_base.PortBindingBaseMixin): """BrocadePluginV2 is a Neutron plugin. Provides L2 Virtual Network functionality using VDX. Upper @@ -220,6 +222,8 @@ class BrocadePluginV2(db_base_plugin_v2.NeutronDbPluginV2, self.physical_interface = (cfg.CONF.PHYSICAL_INTERFACE. physical_interface) + self.base_binding_dict = self._get_base_binding_dict() + portbindings_base.register_port_dict_function() db.configure_db() self.ctxt = context.get_admin_context() self.ctxt.session = db.get_session() @@ -359,6 +363,9 @@ class BrocadePluginV2(db_base_plugin_v2.NeutronDbPluginV2, neutron_port = super(BrocadePluginV2, self).create_port(context, port) + self._process_portbindings_create_and_update(context, + port['port'], + neutron_port) interface_mac = neutron_port['mac_address'] port_id = neutron_port['id'] @@ -384,7 +391,7 @@ class BrocadePluginV2(db_base_plugin_v2.NeutronDbPluginV2, vlan_id, tenant_id, admin_state_up) # apply any extensions - return self._extend_port_dict_binding(context, neutron_port) + return neutron_port def delete_port(self, context, port_id): with context.session.begin(subtransactions=True): @@ -408,10 +415,12 @@ class BrocadePluginV2(db_base_plugin_v2.NeutronDbPluginV2, port['port'], port['port'][ext_sg.SECURITYGROUPS]) port_updated = True - + port_data = port['port'] port = super(BrocadePluginV2, self).update_port( context, port_id, port) - + self._process_portbindings_create_and_update(context, + port_data, + port) if original_port['admin_state_up'] != port['admin_state_up']: port_updated = True @@ -425,27 +434,7 @@ class BrocadePluginV2(db_base_plugin_v2.NeutronDbPluginV2, if port_updated: self._notify_port_updated(context, port) - return self._extend_port_dict_binding(context, port) - - def get_port(self, context, port_id, fields=None): - with context.session.begin(subtransactions=True): - port = super(BrocadePluginV2, self).get_port( - context, port_id, fields) - self._extend_port_dict_binding(context, port) - - return self._fields(port, fields) - - def get_ports(self, context, filters=None, fields=None): - res_ports = [] - with context.session.begin(subtransactions=True): - ports = super(BrocadePluginV2, self).get_ports(context, - filters, - fields) - for port in ports: - self._extend_port_dict_binding(context, port) - res_ports.append(self._fields(port, fields)) - - return res_ports + return port def _notify_port_updated(self, context, port): port_id = port['id'] @@ -454,12 +443,13 @@ class BrocadePluginV2(db_base_plugin_v2.NeutronDbPluginV2, bport.physical_interface, bport.vlan_id) - def _extend_port_dict_binding(self, context, port): - port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_BRIDGE - port[portbindings.CAPABILITIES] = { - portbindings.CAP_PORT_FILTER: - 'security-group' in self.supported_extension_aliases} - return port + def _get_base_binding_dict(self): + binding = { + portbindings.VIF_TYPE: portbindings.VIF_TYPE_BRIDGE, + portbindings.CAPABILITIES: { + portbindings.CAP_PORT_FILTER: + 'security-group' in self.supported_extension_aliases}} + return binding def get_plugin_version(self): """Get version number of the plugin.""" diff --git a/neutron/plugins/hyperv/hyperv_neutron_plugin.py b/neutron/plugins/hyperv/hyperv_neutron_plugin.py index fcb77c98a1..5b8523704e 100644 --- a/neutron/plugins/hyperv/hyperv_neutron_plugin.py +++ b/neutron/plugins/hyperv/hyperv_neutron_plugin.py @@ -23,6 +23,7 @@ from neutron.common import exceptions as q_exc from neutron.common import topics from neutron.db import db_base_plugin_v2 from neutron.db import l3_gwmode_db +from neutron.db import portbindings_base from neutron.db import quota_db # noqa from neutron.extensions import portbindings from neutron.extensions import providernet as provider @@ -141,7 +142,8 @@ class VlanNetworkProvider(BaseNetworkProvider): class HyperVNeutronPlugin(db_base_plugin_v2.NeutronDbPluginV2, - l3_gwmode_db.L3_NAT_db_mixin): + l3_gwmode_db.L3_NAT_db_mixin, + portbindings_base.PortBindingBaseMixin): # This attribute specifies whether the plugin supports or not # bulk operations. Name mangling is used in order to ensure it @@ -153,7 +155,9 @@ class HyperVNeutronPlugin(db_base_plugin_v2.NeutronDbPluginV2, def __init__(self, configfile=None): self._db = hyperv_db.HyperVPluginDB() self._db.initialize() - + self.base_binding_dict = { + portbindings.VIF_TYPE: portbindings.VIF_TYPE_HYPERV} + portbindings_base.register_port_dict_function() self._set_tenant_network_type() self._parse_network_vlan_ranges() @@ -287,29 +291,22 @@ class HyperVNeutronPlugin(db_base_plugin_v2.NeutronDbPluginV2, return [self._fields(net, fields) for net in nets] - def _extend_port_dict_binding(self, context, port): - port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_HYPERV - return port - def create_port(self, context, port): + port_data = port['port'] port = super(HyperVNeutronPlugin, self).create_port(context, port) - return self._extend_port_dict_binding(context, port) - - def get_port(self, context, id, fields=None): - port = super(HyperVNeutronPlugin, self).get_port(context, id, fields) - return self._fields(self._extend_port_dict_binding(context, port), - fields) - - def get_ports(self, context, filters=None, fields=None): - ports = super(HyperVNeutronPlugin, self).get_ports( - context, filters, fields) - return [self._fields(self._extend_port_dict_binding(context, port), - fields) for port in ports] + self._process_portbindings_create_and_update(context, + port_data, + port) + return port def update_port(self, context, id, port): original_port = super(HyperVNeutronPlugin, self).get_port( context, id) + port_data = port['port'] port = super(HyperVNeutronPlugin, self).update_port(context, id, port) + self._process_portbindings_create_and_update(context, + port_data, + port) if original_port['admin_state_up'] != port['admin_state_up']: binding = self._db.get_network_binding( None, port['network_id']) @@ -317,7 +314,7 @@ class HyperVNeutronPlugin(db_base_plugin_v2.NeutronDbPluginV2, binding.network_type, binding.segmentation_id, binding.physical_network) - return self._extend_port_dict_binding(context, port) + return port def delete_port(self, context, id, l3_port_check=True): # if needed, check to see if this is a port owned by diff --git a/neutron/plugins/linuxbridge/lb_neutron_plugin.py b/neutron/plugins/linuxbridge/lb_neutron_plugin.py index 367d5d45d2..f161f256d2 100644 --- a/neutron/plugins/linuxbridge/lb_neutron_plugin.py +++ b/neutron/plugins/linuxbridge/lb_neutron_plugin.py @@ -232,7 +232,7 @@ class LinuxBridgePluginV2(db_base_plugin_v2.NeutronDbPluginV2, return self._aliases def __init__(self): - self.extra_binding_dict = { + self.base_binding_dict = { portbindings.VIF_TYPE: portbindings.VIF_TYPE_BRIDGE, portbindings.CAPABILITIES: { portbindings.CAP_PORT_FILTER: diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 891386481e..468b9fbd66 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -170,7 +170,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, def _extend_port_dict_binding(self, context, port): # TODO(rkukura): Implement based on host_id, agents, and # MechanismDrivers. Also set CAPABILITIES. Use - # extra_binding_dict if applicable, or maybe a new hook so + # base_binding_dict if applicable, or maybe a new hook so # base handles field processing and get_port and get_ports # don't need to be overridden. port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_UNBOUND diff --git a/neutron/plugins/nec/nec_plugin.py b/neutron/plugins/nec/nec_plugin.py index 17c0e62a87..25d22aaf8d 100644 --- a/neutron/plugins/nec/nec_plugin.py +++ b/neutron/plugins/nec/nec_plugin.py @@ -30,6 +30,7 @@ from neutron.db import dhcp_rpc_base from neutron.db import extraroute_db from neutron.db import l3_gwmode_db from neutron.db import l3_rpc_base +from neutron.db import portbindings_base from neutron.db import quota_db # noqa from neutron.db import securitygroups_rpc_base as sg_db_rpc from neutron.extensions import portbindings @@ -68,7 +69,8 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2, sg_db_rpc.SecurityGroupServerRpcMixin, agentschedulers_db.L3AgentSchedulerDbMixin, agentschedulers_db.DhcpAgentSchedulerDbMixin, - packet_filter.PacketFilterMixin): + packet_filter.PacketFilterMixin, + portbindings_base.PortBindingBaseMixin): """NECPluginV2 controls an OpenFlow Controller. The Neutron NECPluginV2 maps L2 logical networks to L2 virtualized networks @@ -98,9 +100,11 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2, return self._aliases def __init__(self): + ndb.initialize() self.ofc = ofc_manager.OFCManager() - + self.base_binding_dict = self._get_base_binding_dict() + portbindings_base.register_port_dict_function() # Set the plugin default extension path # if no api_extensions_path is specified. if not config.CONF.api_extensions_path: @@ -347,27 +351,32 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2, reason = _("delete_ofc_tenant() failed due to %s") % exc LOG.warn(reason) - def _extend_port_dict_binding(self, context, port): - port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_OVS - port[portbindings.CAPABILITIES] = { - portbindings.CAP_PORT_FILTER: - 'security-group' in self.supported_extension_aliases} - return port + def _get_base_binding_dict(self): + binding = { + portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS, + portbindings.CAPABILITIES: { + portbindings.CAP_PORT_FILTER: + 'security-group' in self.supported_extension_aliases}} + return binding def create_port(self, context, port): """Create a new port entry on DB, then try to activate it.""" LOG.debug(_("NECPluginV2.create_port() called, port=%s ."), port) + port_data = port['port'] with context.session.begin(subtransactions=True): self._ensure_default_security_group_on_port(context, port) sgids = self._get_security_groups_on_port(context, port) port = super(NECPluginV2, self).create_port(context, port) + self._process_portbindings_create_and_update(context, + port_data, + port) self._process_port_create_security_group( context, port, sgids) self.notify_security_groups_member_updated(context, port) self._update_resource_status(context, "port", port['id'], OperationalStatus.BUILD) self.activate_port_if_ready(context, port) - return self._extend_port_dict_binding(context, port) + return port def update_port(self, context, id, port): """Update port, and handle packetfilters associated with the port. @@ -382,6 +391,9 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2, with context.session.begin(subtransactions=True): old_port = super(NECPluginV2, self).get_port(context, id) new_port = super(NECPluginV2, self).update_port(context, id, port) + self._process_portbindings_create_and_update(context, + port['port'], + new_port) need_port_update_notify = self.update_security_group_on_port( context, id, port, old_port, new_port) @@ -397,7 +409,7 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2, else: self.deactivate_port(context, old_port) - return self._extend_port_dict_binding(context, new_port) + return new_port def delete_port(self, context, id, l3_port_check=True): """Delete port and packet_filters associated with the port.""" @@ -426,21 +438,6 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2, super(NECPluginV2, self).delete_port(context, id) self.notify_security_groups_member_updated(context, port) - def get_port(self, context, id, fields=None): - with context.session.begin(subtransactions=True): - port = super(NECPluginV2, self).get_port(context, id, fields) - self._extend_port_dict_binding(context, port) - return self._fields(port, fields) - - def get_ports(self, context, filters=None, fields=None): - with context.session.begin(subtransactions=True): - ports = super(NECPluginV2, self).get_ports(context, filters, - fields) - # TODO(amotoki) filter by security group - for port in ports: - self._extend_port_dict_binding(context, port) - return [self._fields(port, fields) for port in ports] - class NECPluginV2AgentNotifierApi(proxy.RpcProxy, sg_rpc.SecurityGroupAgentRpcApiMixin): diff --git a/neutron/plugins/nicira/NeutronPlugin.py b/neutron/plugins/nicira/NeutronPlugin.py index 597c5aa751..2763d1e30f 100644 --- a/neutron/plugins/nicira/NeutronPlugin.py +++ b/neutron/plugins/nicira/NeutronPlugin.py @@ -200,7 +200,7 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2, self.nvp_opts.concurrent_connections, self.nvp_opts.nvp_gen_timeout) - self.extra_binding_dict = { + self.base_binding_dict = { pbin.VIF_TYPE: pbin.VIF_TYPE_OVS, pbin.CAPABILITIES: { pbin.CAP_PORT_FILTER: diff --git a/neutron/plugins/openvswitch/ovs_neutron_plugin.py b/neutron/plugins/openvswitch/ovs_neutron_plugin.py index bb29c91149..baeef5ef54 100644 --- a/neutron/plugins/openvswitch/ovs_neutron_plugin.py +++ b/neutron/plugins/openvswitch/ovs_neutron_plugin.py @@ -263,7 +263,7 @@ class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, return self._aliases def __init__(self, configfile=None): - self.extra_binding_dict = { + self.base_binding_dict = { portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS, portbindings.CAPABILITIES: { portbindings.CAP_PORT_FILTER: diff --git a/neutron/plugins/ryu/ryu_neutron_plugin.py b/neutron/plugins/ryu/ryu_neutron_plugin.py index 1e10ca8b6f..6a7e59153d 100644 --- a/neutron/plugins/ryu/ryu_neutron_plugin.py +++ b/neutron/plugins/ryu/ryu_neutron_plugin.py @@ -32,7 +32,9 @@ from neutron.db import extraroute_db from neutron.db import l3_gwmode_db from neutron.db import l3_rpc_base from neutron.db import models_v2 +from neutron.db import portbindings_base from neutron.db import securitygroups_rpc_base as sg_db_rpc +from neutron.extensions import portbindings from neutron.openstack.common import log as logging from neutron.openstack.common import rpc from neutron.openstack.common.rpc import proxy @@ -88,10 +90,12 @@ class AgentNotifierApi(proxy.RpcProxy, class RyuNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, extraroute_db.ExtraRoute_db_mixin, l3_gwmode_db.L3_NAT_db_mixin, - sg_db_rpc.SecurityGroupServerRpcMixin): + sg_db_rpc.SecurityGroupServerRpcMixin, + portbindings_base.PortBindingBaseMixin): _supported_extension_aliases = ["router", "ext-gw-mode", - "extraroute", "security-group"] + "extraroute", "security-group", + "binding"] @property def supported_extension_aliases(self): @@ -102,6 +106,12 @@ class RyuNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, return self._aliases def __init__(self, configfile=None): + self.base_binding_dict = { + portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS, + portbindings.CAPABILITIES: { + portbindings.CAP_PORT_FILTER: + 'security-group' in self.supported_extension_aliases}} + portbindings_base.register_port_dict_function() db.configure_db() self.tunnel_key = db_api_v2.TunnelKey( cfg.CONF.OVS.tunnel_key_min, cfg.CONF.OVS.tunnel_key_max) @@ -185,10 +195,14 @@ class RyuNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, def create_port(self, context, port): session = context.session + port_data = port['port'] with session.begin(subtransactions=True): self._ensure_default_security_group_on_port(context, port) sgids = self._get_security_groups_on_port(context, port) port = super(RyuNeutronPluginV2, self).create_port(context, port) + self._process_portbindings_create_and_update(context, + port_data, + port) self._process_port_create_security_group( context, port, sgids) self.notify_security_groups_member_updated(context, port) @@ -219,6 +233,9 @@ class RyuNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, context, id) updated_port = super(RyuNeutronPluginV2, self).update_port( context, id, port) + self._process_portbindings_create_and_update(context, + port['port'], + updated_port) need_port_update_notify = self.update_security_group_on_port( context, id, port, original_port, updated_port) @@ -234,15 +251,3 @@ class RyuNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, if deleted: db_api_v2.set_port_status(session, id, q_const.PORT_STATUS_DOWN) return updated_port - - def get_port(self, context, id, fields=None): - with context.session.begin(subtransactions=True): - port = super(RyuNeutronPluginV2, self).get_port(context, id, - fields) - return self._fields(port, fields) - - def get_ports(self, context, filters=None, fields=None): - with context.session.begin(subtransactions=True): - ports = super(RyuNeutronPluginV2, self).get_ports( - context, filters, fields) - return [self._fields(port, fields) for port in ports]