From da1110a3d845933f6497a7a393bba3654a12d95b Mon Sep 17 00:00:00 2001 From: Steven Webster Date: Tue, 18 Sep 2018 14:40:20 -0400 Subject: [PATCH] LLDP OVS enablement: puppet configuration This commit introduces puppet configuration enabling LLDP to operate over OVS. Specifically, separate ports flows are configured to handle LLDP traffic. In addition, we restrict the lldpd daemon from operating over bridge, tap, and ovs-netdev devices. Story: 2002946 Task: 22940 Change-Id: Ibadc9c082425412b5b68b02a55e8c02692de0e17 Signed-off-by: Steven Webster --- .../src/modules/platform/manifests/vswitch.pp | 19 ++++ .../modules/platform/templates/lldp.conf.erb | 1 + .../platform/templates/ovs.add-bridge.erb | 6 +- .../platform/templates/ovs.add-flow.erb | 9 ++ .../platform/templates/ovs.add-port.erb | 5 + .../src/sysinv/manifests/agent.pp | 7 +- .../sysinv/sysinv/sysinv/common/constants.py | 4 + .../sysinv/sysinv/sysinv/conductor/manager.py | 31 +++--- sysinv/sysinv/sysinv/sysinv/puppet/ovs.py | 104 ++++++++++++++++++ .../sysinv/sysinv/sysinv/puppet/platform.py | 13 +++ 10 files changed, 180 insertions(+), 19 deletions(-) create mode 100644 puppet-manifests/src/modules/platform/templates/ovs.add-flow.erb diff --git a/puppet-manifests/src/modules/platform/manifests/vswitch.pp b/puppet-manifests/src/modules/platform/manifests/vswitch.pp index 1ec5e08000..1e73d74205 100644 --- a/puppet-manifests/src/modules/platform/manifests/vswitch.pp +++ b/puppet-manifests/src/modules/platform/manifests/vswitch.pp @@ -35,6 +35,7 @@ define platform::vswitch::ovs::device( define platform::vswitch::ovs::bridge( $datapath_type = 'netdev', + $attributes = [], ) { exec { "ovs-add-br: ${title}": command => template("platform/ovs.add-bridge.erb") @@ -69,11 +70,24 @@ define platform::vswitch::ovs::address( } +define platform::vswitch::ovs::flow( + $bridge, + $attributes = [], + $actions, +) { + exec { "ovs-add-flow: ${title}": + command => template("platform/ovs.add-flow.erb"), + logoutput => true + } +} + + class platform::vswitch::ovs( $devices = {}, $bridges = {}, $ports = {}, $addresses = {}, + $flows = {}, ) inherits ::platform::vswitch::params { if $::platform::params::vswitch_type == 'ovs' { @@ -116,6 +130,7 @@ class platform::vswitch::ovs( Platform::Vswitch::Ovs::Bridge<||> -> Platform::Vswitch::Ovs::Port<||> Platform::Vswitch::Ovs::Bridge<||> -> Platform::Vswitch::Ovs::Address<||> + Platform::Vswitch::Ovs::Port<||> -> Platform::Vswitch::Ovs::Flow<||> } create_resources('platform::vswitch::ovs::bridge', $bridges, { @@ -129,4 +144,8 @@ class platform::vswitch::ovs( create_resources('platform::vswitch::ovs::address', $addresses, { require => Service['openvswitch'] }) + + create_resources('platform::vswitch::ovs::flow', $flows, { + require => Service['openvswitch'] + }) } diff --git a/puppet-manifests/src/modules/platform/templates/lldp.conf.erb b/puppet-manifests/src/modules/platform/templates/lldp.conf.erb index 0df6469d41..6e6ceae1a1 100644 --- a/puppet-manifests/src/modules/platform/templates/lldp.conf.erb +++ b/puppet-manifests/src/modules/platform/templates/lldp.conf.erb @@ -2,3 +2,4 @@ configure system hostname '<%= @hostname %>:<%= @system %>' configure system description 'Titanium Cloud version <%= @version %>' configure lldp tx-interval <%= @tx_interval %> configure lldp tx-hold <%= @tx_hold %> +configure system interface pattern *,!br*,!ovs*,!tap* diff --git a/puppet-manifests/src/modules/platform/templates/ovs.add-bridge.erb b/puppet-manifests/src/modules/platform/templates/ovs.add-bridge.erb index 59fdc07af0..82d311ba7b 100644 --- a/puppet-manifests/src/modules/platform/templates/ovs.add-bridge.erb +++ b/puppet-manifests/src/modules/platform/templates/ovs.add-bridge.erb @@ -1,2 +1,6 @@ ovs-vsctl --timeout 10 -- --may-exist add-br <%= @name -%> - -- set bridge <%= @name -%> datapath_type=<%= @datapath_type -%> + -- set bridge <%= @name -%> +<%- @attributes.each do |attribute| -%> + <%= attribute -%> +<%- end -%> + datapath_type=<%= @datapath_type -%> diff --git a/puppet-manifests/src/modules/platform/templates/ovs.add-flow.erb b/puppet-manifests/src/modules/platform/templates/ovs.add-flow.erb new file mode 100644 index 0000000000..62f5bfb659 --- /dev/null +++ b/puppet-manifests/src/modules/platform/templates/ovs.add-flow.erb @@ -0,0 +1,9 @@ +ovs-ofctl add-flow <%= @bridge -%> + <%- @attributes.each_with_index do |attrib, idx| -%> +<% if idx == 0 %> <% else -%>,<% end -%> +<%= attrib[0] -%>=<%= attrib[1] -%> +<%- end -%> +,actions=<%- @actions.each_with_index do |action, idx| -%> +<%- if idx > 0 -%>,<%- end -%> +<%= action['type'] -%>:<%= action['value'] -%> +<%- end -%> \ No newline at end of file diff --git a/puppet-manifests/src/modules/platform/templates/ovs.add-port.erb b/puppet-manifests/src/modules/platform/templates/ovs.add-port.erb index 80d66de163..d7f779d4f1 100644 --- a/puppet-manifests/src/modules/platform/templates/ovs.add-port.erb +++ b/puppet-manifests/src/modules/platform/templates/ovs.add-port.erb @@ -13,4 +13,9 @@ ovs-vsctl --timeout 10 -- --may-exist add-<%= @type -%> <%= @bridge -%> <%= @nam <%- interface['attributes'].each do |attribute| -%> <%= attribute -%> <%- end -%> +<%- end %> +<%- @interfaces.each do |interface| -%> + <%- if interface['type'] == 'internal' -%> + ip link set <%= interface['name'] -%> up + <%- end -%> <%- end -%> diff --git a/puppet-modules-wrs/puppet-sysinv/src/sysinv/manifests/agent.pp b/puppet-modules-wrs/puppet-sysinv/src/sysinv/manifests/agent.pp index 741e44e59e..90ce97cb61 100644 --- a/puppet-modules-wrs/puppet-sysinv/src/sysinv/manifests/agent.pp +++ b/puppet-modules-wrs/puppet-sysinv/src/sysinv/manifests/agent.pp @@ -17,7 +17,8 @@ class sysinv::agent ( $agent_driver = false, $package_ensure = 'latest', - $enabled = true + $enabled = true, + $lldp_drivers = [] ) { include sysinv::params @@ -32,6 +33,10 @@ class sysinv::agent ( } } + sysinv_config { + 'lldp/drivers': value => join($lldp_drivers,","); + } + if $::sysinv::params::agent_package { Package['sysinv-agent'] -> Sysinv_config<||> Package['sysinv-agent'] -> Sysinv_api_paste_ini<||> diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index d2fca746d7..59be2e8dcb 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -1041,6 +1041,10 @@ UPGRADE_ABORT_COMPLETING = 'abort-completing' UPGRADE_ABORTING_ROLLBACK = 'aborting-reinstall' # LLDP +LLDP_OVS_PORT_PREFIX = 'lldp' +LLDP_OVS_PORT_NAME_LEN = 15 +LLDP_MULTICAST_ADDRESS = '01:80:c2:00:00:0e' +LLDP_ETHER_TYPE = '0x88cc' LLDP_TLV_TYPE_CHASSIS_ID = 'chassis_id' LLDP_TLV_TYPE_PORT_ID = 'port_identifier' LLDP_TLV_TYPE_TTL = 'ttl' diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index 432c03ec11..a1a56084fd 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -2039,6 +2039,15 @@ class ConductorManager(service.PeriodicService): LOG.info("Updating port name: %s to %s" % (port_name, updated_name)) self.dbapi.ethernet_port_update(port['uuid'], {'name': updated_name}) + def lldp_id_to_port(self, id, ports): + ovs_id = re.sub(r'^{}'.format(constants.LLDP_OVS_PORT_PREFIX), '', id) + for port in ports: + if (port['name'] == id or + port['uuid'] == id or + port['uuid'].find(ovs_id) == 0): + return port + return None + def lldp_tlv_dict(self, agent_neighbour_dict): tlv_dict = {} for k, v in agent_neighbour_dict.iteritems(): @@ -2156,14 +2165,8 @@ class ConductorManager(service.PeriodicService): "Error getting LLDP agents for host %s") % host_uuid) for agent in agent_dict_array: - port_found = None - for db_port in db_ports: - if (db_port['name'] == agent['name_or_uuid'] or - db_port['uuid'] == agent['name_or_uuid']): - port_found = db_port - break - - if not port_found: + db_port = self.lldp_id_to_port(agent['name_or_uuid'], db_ports) + if not db_port: LOG.debug("Could not find port for agent %s", agent['name_or_uuid']) return @@ -2269,16 +2272,10 @@ class ConductorManager(service.PeriodicService): neighbour['uuid']) for neighbour in neighbour_dict_array: - port_found = None - for db_port in db_ports: - if (db_port['name'] == neighbour['name_or_uuid'] or - db_port['uuid'] == neighbour['name_or_uuid']): - port_found = db_port - break - - if not port_found: + db_port = self.lldp_id_to_port(neighbour['name_or_uuid'], db_ports) + if not db_port: LOG.debug("Could not find port for neighbour %s", - neighbour['name']) + neighbour['name_or_uuid']) return LOG.debug("Processing lldp neighbour %s" % neighbour) diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/ovs.py b/sysinv/sysinv/sysinv/sysinv/puppet/ovs.py index ff6c49f153..f94166d578 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/ovs.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/ovs.py @@ -26,6 +26,7 @@ class OVSPuppet(base.BasePuppet): config.update(self._get_port_config(host)) config.update(self._get_virtual_config(host)) config.update(self._get_neutron_config(host)) + config.update(self._get_lldp_config(host)) return config def _get_port_config(self, host): @@ -33,6 +34,7 @@ class OVSPuppet(base.BasePuppet): ovs_bridges = {} ovs_ports = {} ovs_addresses = {} + ovs_flows = {} index = 0 for iface in sorted(self.context['interfaces'].values(), @@ -61,6 +63,31 @@ class OVSPuppet(base.BasePuppet): ovs_ports.update({port['name']: port}) ovs_devices.update({d['pci_addr']: d for d in devices}) + if iface['iftype'] == constants.INTERFACE_TYPE_ETHERNET: + ovs_ifname = port['interfaces'][0]['name'] + lldp_port = self._get_lldp_port( + iface, brname, ovs_ifname=ovs_ifname) + ovs_ports.update({lldp_port['name']: lldp_port}) + flow = self._get_lldp_flow( + brname, ovs_ifname, lldp_port['name']) + ovs_flows.update({port['name']: flow}) + + if iface['iftype'] == constants.INTERFACE_TYPE_AE: + slaves = interface.get_bond_interface_slaves( + self.context, iface) + for member, slave in enumerate(slaves): + ovs_ifname = port['interfaces'][member]['name'] + + lldp_port = self._get_lldp_port( + slave, brname, ovs_ifname=ovs_ifname) + ovs_ports.update({lldp_port['name']: lldp_port}) + flow = self._get_lldp_flow( + brname, ovs_ifname, lldp_port['name']) + ovs_flows.update({flow['name']: flow}) + flow = self._get_lldp_flow( + brname, lldp_port['name'], ovs_ifname) + ovs_flows.update({flow['name']: flow}) + index += 1 # currently only one provider network is supported per @@ -83,6 +110,7 @@ class OVSPuppet(base.BasePuppet): 'platform::vswitch::ovs::bridges': ovs_bridges, 'platform::vswitch::ovs::ports': ovs_ports, 'platform::vswitch::ovs::addresses': ovs_addresses, + 'platform::vswitch::ovs::flows': ovs_flows, } def _get_ethernet_device(self, iface): @@ -147,6 +175,74 @@ class OVSPuppet(base.BasePuppet): return port, devices + def _get_lldp_interface(self, ifname, peer_ifname): + attributes = [] + + iftype = 'internal' + + attributes.append("other_config:lldp_phy_peer=%s" % peer_ifname) + + return { + 'name': ifname, + 'type': iftype, + 'attributes': attributes, + } + + def _get_lldp_port(self, iface, lldp_brname, ovs_ifname=None): + interfaces = [] + + port = interface.get_interface_port(self.context, iface) + + # Limit port name length to the maximum supported by ovs-ofctl to + # reference a port with a name rather than ofport number + # when creating flows. + + port_name_len = constants.LLDP_OVS_PORT_NAME_LEN + uuid_len = port_name_len - len(constants.LLDP_OVS_PORT_PREFIX) + + port_name = '{}{}'.format(constants.LLDP_OVS_PORT_PREFIX, + port.uuid[:uuid_len]) + + if ovs_ifname: + interfaces.append(self._get_lldp_interface(port_name, ovs_ifname)) + else: + interfaces.append(self._get_lldp_interface(port_name, iface['name'])) + + port = { + 'name': port_name, + 'bridge': lldp_brname, + 'interfaces': interfaces, + } + + return port + + def _get_lldp_flow(self, bridge, in_port, out_port): + actions = [] + + attributes = { + 'idle_timeout': 0, + 'hard_timeout': 0, + 'in_port': in_port, + 'dl_dst': constants.LLDP_MULTICAST_ADDRESS, + 'dl_type': constants.LLDP_ETHER_TYPE + } + + action = { + 'type': 'output', + 'value': out_port + } + + actions.append(action) + + flow = { + 'name': '{}-{}-{}'.format(bridge, in_port, out_port), + 'bridge': bridge, + 'attributes': attributes, + 'actions': actions + } + + return flow + def _get_bond_port(self, host, iface, bridge, index): devices = [] interfaces = [] @@ -294,3 +390,11 @@ class OVSPuppet(base.BasePuppet): def _is_vxlan_providernet(self, name): providernet_type = self._get_providernet_type(name) return bool(providernet_type == constants.NEUTRON_PROVIDERNET_VXLAN) + + def _get_lldp_config(self, host): + driver_list = self.context['_lldp_drivers'] + driver_list.append('ovs') + + return { + 'sysinv::agent::lldp_drivers': driver_list + } diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/platform.py b/sysinv/sysinv/sysinv/sysinv/puppet/platform.py index 9de4828053..e3d53eb0c4 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/platform.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/platform.py @@ -70,6 +70,7 @@ class PlatformPuppet(base.BasePuppet): config.update(self._get_host_tpm_config(host)) config.update(self._get_host_cpu_config(host)) config.update(self._get_host_memory_config(host)) + config.update(self._get_host_lldp_config(host)) return config def _get_static_software_config(self): @@ -818,3 +819,15 @@ class PlatformPuppet(base.BasePuppet): def _get_platform_cpu_count(self, host): cpus = self._get_host_cpu_list(host, constants.PLATFORM_FUNCTION, True) return len(cpus) + + def _get_host_lldp_config(self, host): + driver_list = [] + + # Default is lldpd + driver_list.append('lldpd') + + self.context['_lldp_drivers'] = driver_list + + return { + 'sysinv::agent::lldp_drivers': driver_list + }