From 92233ff68ecbfe08ed2d69cf4abf947d785261f5 Mon Sep 17 00:00:00 2001 From: chenjie1 Date: Tue, 8 Oct 2019 04:58:24 +0800 Subject: [PATCH] OVS collectd interface/port state monitoring Use collectd to monitor OVS interface and port. Some host interfaces will be added to OVS port. Only these ports and interfaces will be monitored. Change-Id: Icbf3d4c47afd177392f023720c114783332b143b Story: #2002948 Task: #22944 Closes-Bug: #1834512 Signed-off-by: Chenjie Xu --- collectd-extensions/centos/build_srpm.data | 4 +- .../centos/collectd-extensions.spec | 4 + collectd-extensions/src/ovs_interface.conf | 12 + collectd-extensions/src/ovs_interface.py | 1081 +++++++++++++++++ collectd-extensions/src/python_plugins.conf | 1 + 5 files changed, 1101 insertions(+), 1 deletion(-) create mode 100644 collectd-extensions/src/ovs_interface.conf create mode 100755 collectd-extensions/src/ovs_interface.py diff --git a/collectd-extensions/centos/build_srpm.data b/collectd-extensions/centos/build_srpm.data index 9426f70..caf24a9 100644 --- a/collectd-extensions/centos/build_srpm.data +++ b/collectd-extensions/centos/build_srpm.data @@ -19,6 +19,8 @@ COPY_LIST="$PKG_BASE/src/LICENSE \ $PKG_BASE/src/remotels.conf \ $PKG_BASE/src/ptp.py \ $PKG_BASE/src/ptp.conf \ + $PKG_BASE/src/ovs_interface.py \ + $PKG_BASE/src/ovs_interface.conf \ $PKG_BASE/src/example.py \ $PKG_BASE/src/example.conf" -TIS_PATCH_VER=15 +TIS_PATCH_VER=16 diff --git a/collectd-extensions/centos/collectd-extensions.spec b/collectd-extensions/centos/collectd-extensions.spec index 8f3bd56..b3c2245 100644 --- a/collectd-extensions/centos/collectd-extensions.spec +++ b/collectd-extensions/centos/collectd-extensions.spec @@ -24,6 +24,7 @@ Source15: ntpq.py Source16: interface.py Source17: remotels.py Source18: ptp.py +Source19: ovs_interface.py # collectd plugin conf files into /etc/collectd.d Source100: python_plugins.conf @@ -35,6 +36,7 @@ Source105: ntpq.conf Source106: interface.conf Source107: remotels.conf Source108: ptp.conf +Source109: ovs_interface.conf BuildRequires: systemd-devel @@ -85,6 +87,7 @@ install -m 700 %{SOURCE15} %{buildroot}%{local_python_extensions_dir} install -m 700 %{SOURCE16} %{buildroot}%{local_python_extensions_dir} install -m 700 %{SOURCE17} %{buildroot}%{local_python_extensions_dir} install -m 700 %{SOURCE18} %{buildroot}%{local_python_extensions_dir} +install -m 700 %{SOURCE19} %{buildroot}%{local_python_extensions_dir} # collectd plugin conf files into /etc/collectd.d @@ -97,6 +100,7 @@ install -m 600 %{SOURCE105} %{buildroot}%{local_plugin_dir} install -m 600 %{SOURCE106} %{buildroot}%{local_plugin_dir} install -m 600 %{SOURCE107} %{buildroot}%{local_plugin_dir} install -m 600 %{SOURCE108} %{buildroot}%{local_plugin_dir} +install -m 600 %{SOURCE109} %{buildroot}%{local_plugin_dir} %clean rm -rf $RPM_BUILD_ROOT diff --git a/collectd-extensions/src/ovs_interface.conf b/collectd-extensions/src/ovs_interface.conf new file mode 100644 index 0000000..8be9a3f --- /dev/null +++ b/collectd-extensions/src/ovs_interface.conf @@ -0,0 +1,12 @@ + + + + Instance "used" + Persist true + PersistOK true + WarningMin 100 + FailureMin 1 + Invert false + + + diff --git a/collectd-extensions/src/ovs_interface.py b/collectd-extensions/src/ovs_interface.py new file mode 100755 index 0000000..8c7f53a --- /dev/null +++ b/collectd-extensions/src/ovs_interface.py @@ -0,0 +1,1081 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2019 Intel Corporation +# +############################################################################ +# +# This is the OVS Interface Monitor plugin for collectd. +# +# This plugin monitors the OVS port and interface status. Some host interfaces +# will be added to OVS port. Only these port and interfaces will be monitored. +# +# This plugin only runs on openstack worker node because OVS is running on +# this kind of node. +# +# This plugin queries interface states by using OVS commands as following: +# ovs-vsctl show +# ovs-vsctl list-br +# ovs-vsctl list-ifaces +# ovs-ofctl dump-ports-desc +# ovs-appctl --target=/var/run/openvswitch/ovs-vswitchd.43.ctl bond/list +# ovs-appctl --target=/var/run/openvswitch/ovs-vswitchd.43.ctl bond/show +# +# By parsing the result, interface and port states can be retrived. +# +# To be noticed, the port can be a bond and then the port will contain multiple +# interfaces. In this case, the port states is determined by the states of all +# interfaces. The rule is following: + +# Severity: Interface and Port levels +# +# Alarm Level Minor Major Critical +# ----------- ----- --------------------- ---------------------------- +# Interface N/A Physical Link is Down N/A +# Port N/A Some interfaces are down All Interface ports are Down +# +# Sample Data: represented as % of total interfaces Up for that port +# +# 100 or 100% percent used - all interfaces of that port are up. +# 0 1: + all_up = True + all_down = True + for interface in self.interfaces: + if interface.state == LINK_UP: + all_down = False + elif interface.state == LINK_DOWN: + all_up = False + else: + all_up = False + + if all_up: + current_severity = fm_constants.FM_ALARM_SEVERITY_CLEAR + elif all_down: + current_severity = fm_constants.FM_ALARM_SEVERITY_CRITICAL + else: + current_severity = fm_constants.FM_ALARM_SEVERITY_MAJOR + # bridge doesn't have physical interfaces + else: + collectd.info("%s port %s doesn't have physical interfaces" + % (PLUGIN, self.bridge_name)) + return 0 + + if current_severity != self.severity: + if current_severity == fm_constants.FM_ALARM_SEVERITY_CLEAR: + self._clear_alarm() + elif current_severity == fm_constants.FM_ALARM_SEVERITY_MAJOR: + self._raise_alarm(fm_constants.FM_ALARM_SEVERITY_MAJOR) + elif current_severity == fm_constants.FM_ALARM_SEVERITY_CRITICAL: + self._raise_alarm(fm_constants.FM_ALARM_SEVERITY_CRITICAL) + else: + collectd.debug("%s failed to manage_port_alarm" % PLUGIN) + return 0 + # self.severity will be updated in function _raise_alarm() and + # _clear_alarm() + + +######################################################################### +# +# Name : this_hosts_alarm +# +# Purpose : Determine if the supplied eid is for this host. +# +# Description: The eid formats for the alarms managed by this plugin are +# +# host=.interface= +# host=.port= +# host=.port=: +# +# Assumptions: There is no restriction preventing the system +# administrator from creating hostnames with period's ('.') +# in them. Because so the eid cannot simply be split +# around '='s and '.'s. Instead its split around this +# plugins level type '.port' or '.interface'. +# +# Returns : True if hostname is a match +# False otherwise +# +########################################################################## +def this_hosts_alarm(hostname, eid): + """Check if the specified eid is for this host""" + + if hostname: + if eid: + # 'host=controller-0.interface=eth0' + # 'host=controller-0.port=br-phy0' + # 'host=controller-0.port=br-phy0:bond0' + try: + eid_host = None + eid_disected = eid.split('=') + if len(eid_disected) == 3: + # ['host', 'controller-0.interface', 'eth0'] + # ['host', 'controller-0.port', 'br-phy0'] + # ['host', 'controller-0.port', 'br-phy0:bond0'] + if len(eid_disected[1].split('.interface')) == 2: + eid_host = eid_disected[1].split('.interface')[0] + if eid_host and eid_host == hostname: + return True + elif len(eid_disected[1].split('.port')) == 2: + eid_host = eid_disected[1].split('.port')[0] + if eid_host and eid_host == hostname: + return True + except Exception as ex: + collectd.error("%s failed to parse alarm eid (%s)" + " [eid:%s]" % (PLUGIN, str(ex), eid)) + + return False + + +########################################################################## +# +# Name : clear_alarms +# +# Purpose : Clear all interface alarms on process startup. +# +# Description: Loops over the provided alarm id list querying all alarms +# for each. Any that are raised are precisely cleared. +# +# Prevents stuck alarms over port and interface reconfig. +# +# If the original alarm case still exists the alarm will +# be re-raised. +# +# Parameters : A list of this plugin's alarm ids +# +# Returns : True on Success +# False on Failure +# +########################################################################## +def clear_alarms(alarm_id_list): + """Clear alarm state of all plugin alarms""" + found = False + for alarm_id in alarm_id_list: + + try: + alarms = api.get_faults_by_id(alarm_id) + except Exception as ex: + collectd.error("%s 'get_faults_by_id' exception ;" + " %s ; %s" % + (PLUGIN, alarm_id, ex)) + return False + + if alarms: + for alarm in alarms: + eid = alarm.entity_instance_id + if this_hosts_alarm(obj.hostname, eid) is False: + # ignore other host alarms + continue + + if alarm_id == OVS_IFACE_ALARMID or \ + alarm_id == OVS_PORT_ALARMID: + + try: + if api.clear_fault(alarm_id, eid) is False: + collectd.info("%s %s:%s:%s alarm already cleared" % + (PLUGIN, + alarm.severity, + alarm_id, + eid)) + else: + found = True + collectd.info("%s %s:%s:%s alarm cleared" % + (PLUGIN, + alarm.severity, + alarm_id, + eid)) + except Exception as ex: + collectd.error("%s 'clear_fault' exception ; " + "%s:%s ; %s" % + (PLUGIN, alarm_id, eid, ex)) + return False + if found is False: + collectd.info("%s found no startup alarms" % PLUGIN) + + return True + + +########################################################################## +# +# Name : manage_alarm +# +# Purpose : Raises or clears port and interface alarms based on +# calling parameters. +# +# Returns : True on success +# False on failure +# +########################################################################## +def manage_alarm(name, level, action, severity, alarm_id, timestamp): + """Manage raise and clear port and interface alarms""" + + ts = timestamp.strftime('%Y-%m-%d %H:%M:%S.%f') + + if action == ALARM_ACTION_CLEAR: + alarm_state = fm_constants.FM_ALARM_STATE_CLEAR + reason = '' + repair = '' + else: + # reason ad repair strings are only needed on alarm assertion + alarm_state = fm_constants.FM_ALARM_STATE_SET + reason = "'" + name + "' " + level + repair = 'Check cabling and far-end port configuration ' \ + 'and status on adjacent equipment.' + + # build the alarm eid and name string + if level == LEVEL_INTERFACE: + eid = 'host=' + obj.hostname + "." + level + '=' + name + reason += " failed" + else: + eid = 'host=' + obj.hostname + "." + level + '=' + name + if severity == fm_constants.FM_ALARM_SEVERITY_MAJOR: + reason += " degraded" + else: + reason += " failed" + + if alarm_state == fm_constants.FM_ALARM_STATE_CLEAR: + try: + if api.clear_fault(alarm_id, eid) is False: + collectd.info("%s %s:%s alarm already cleared" % + (PLUGIN, alarm_id, eid)) + else: + collectd.info("%s %s:%s alarm cleared" % + (PLUGIN, alarm_id, eid)) + return True + + except Exception as ex: + collectd.error("%s 'clear_fault' failed ; %s:%s ; %s" % + (PLUGIN, alarm_id, eid, ex)) + return False + + else: + fault = fm_api.Fault( + uuid="", + alarm_id=alarm_id, + alarm_state=alarm_state, + entity_type_id=fm_constants.FM_ENTITY_TYPE_HOST, + entity_instance_id=eid, + severity=severity, + reason_text=reason, + alarm_type=fm_constants.FM_ALARM_TYPE_7, + probable_cause=fm_constants.ALARM_PROBABLE_CAUSE_UNKNOWN, + proposed_repair_action=repair, + service_affecting=True, + timestamp=ts, + suppression=True) + + try: + alarm_uuid = api.set_fault(fault) + except Exception as ex: + collectd.error("%s 'set_fault' exception ; %s:%s ; %s" % + (PLUGIN, alarm_id, eid, ex)) + return False + + if pc.is_uuid_like(alarm_uuid) is False: + collectd.error("%s 'set_fault' failed ; %s:%s ; %s" % + (PLUGIN, alarm_id, eid, alarm_uuid)) + return False + else: + return True + + +def get_physical_interfaces(): + """get host physical interfaces""" + + if (os.path.exists(INTERFACE_PATH)): + try: + interfaces = os.listdir(INTERFACE_PATH) + except EnvironmentError as e: + collectd.error(str(e), UserWarning) + + if (os.path.exists(VIRTUAL_INTERFACE_PATH)): + try: + virtual_interfaces = os.listdir(VIRTUAL_INTERFACE_PATH) + except EnvironmentError as e: + collectd.error(str(e), UserWarning) + + if interfaces: + if virtual_interfaces: + physical_interfaces = [] + for interface in interfaces: + if interface not in virtual_interfaces: + physical_interfaces.append(interface) + return physical_interfaces + else: + return interfaces + else: + collectd.error("%s finds no interfaces in path %s" % + (PLUGIN, INTERFACE_PATH)) + + +def parse_ovs_vsctl_list_ports(buf): + """parse the result of command ovs-vsctl list-ports $BRIDGE""" + + buf = buf.strip().split("\n") + return buf + + +def parse_ovs_vsctl_list_ifaces(buf): + """parse the result of command ovs-vsctl list-ifaces $BRIDGE""" + + buf = buf.strip().split("\n") + return buf + + +def parse_ovs_ofctl_dump_ports_desc(buf, port): + """parse the result of command ovs-ofctl dump-ports-desc $BRIDGE""" + + #OFPST_PORT_DESC reply (xid=0x2): + # 1(lldpb111d58c-6b): addr:52:e4:c9:da:19:32 + # config: 0 + # state: 0 + # current: 10MB-FD COPPER + # speed: 10 Mbps now, 0 Mbps max + # 2(eth0): addr:3c:fd:fe:da:e8:84 + # config: 0 + # state: LINK_DOWN + # current: AUTO_NEG + # speed: 0 Mbps now, 0 Mbps max + # LOCAL(br-phy0): addr:3c:fd:fe:da:e8:84 + # config: 0 + # state: 0 + # current: 10MB-FD COPPER + # speed: 10 Mbps now, 0 Mbps max + + buf = buf.strip().split("\n") + result = {} + find = False + for idx, line in enumerate(buf): + line = line.strip() + for interface in port.interfaces: + if interface.name in line: + state = buf[idx + 2].split(":") + if "0" in state[1]: + result[interface.name] = LINK_UP + elif "LINK_DOWN" in state[1]: + result[interface.name] = LINK_DOWN + else: + result[interface.name] = UNKNOWN_STATE + + return result + + +def parse_ovs_vsctl_show(buf, bridge): + """parse the result of command ovs-vsctl show $PORT""" + + # Bridge "br-phy1" + # Port "eth1" + # Interface "eth1" + # type: dpdk + # options: {dpdk-devargs="0000:18:00.1", n_rxq="1"} + buf = buf.strip().split("\n") + result = [] + find = False + for line in buf: + line = line.strip() + if "Bridge" in line: + if bridge in line: + find = True + port = "" + else: + find = False + if find: + if line.startswith("Port"): + port = line[4:].strip() + if port.startswith("\""): + port = port[1:len(port) - 1] + if port: + if "dpdk" in line: + result.append(port) + + return result + + +def parse_ovs_appctl_bond_list(buf): + """parse the result of command ovs-appctl bond/list""" + + # bond type recircID slaves + # bond0 active-backup 0 enp134s0f1, enp134s0f0 + buf = buf.strip().split("\n") + result = {} + for idx, line in enumerate(buf): + if idx is 0: + continue + + line = line.strip() + items = line.split("\t") + interfaces = items[3].split(",") + for idx, interface in enumerate(interfaces): + interfaces[idx] = interface.strip() + + result[items[0]] = interfaces + + return result + + +def parse_ovs_appctl_bond_show(buf): + """parse the result of command ovs-appctl bond/show $PORT""" + + #---- bond0 ---- + #bond_mode: active-backup + #bond may use recirculation: no, Recirc-ID : -1 + #bond-hash-basis: 0 + #updelay: 0 ms + #downdelay: 0 ms + #lacp_status: configured + #lacp_fallback_ab: false + #active slave mac: 00:00:00:00:00:00(none) + # + #slave enp134s0f0: disabled + # may_enable: false + # + #slave enp134s0f1: disabled + # may_enable: false + buf = buf.strip().split("\n") + states = {} + for idx, line in enumerate(buf): + line = line.strip() + if line.startswith("slave"): + state = line.split(":") + interface = state[0][6:] + if "disabled" in state[1]: + states[interface] = LINK_DOWN + elif "enabled" in state[1]: + states[interface] = LINK_UP + else: + states[interface] = UNKNOWN_STATE + + return states + + +def is_physical_interface(interface, bridge): + """Check whether interface is physical interface or not + + If OVS-DPDK is used, the relative interface can't be found + in directory /sys/class/net + """ + + physical_interfaces = get_physical_interfaces() + if interface in physical_interfaces: + return True + + # The interface will have type dpdk + # Bridge "br-phy1" + # Port "eth1" + # Interface "eth1" + # type: dpdk + # options: {dpdk-devargs="0000:18:00.1", n_rxq="1"} + res, err = processutils.execute(OVS_VSCTL_SHOW, shell=True) + if err: + raise Exception("%s failed to run command ovs-vsctl show %s" + "retry next audit" % (PLUGIN, err)) + elif res: + dpdk_interfaces = parse_ovs_vsctl_show(res, bridge) + if interface in dpdk_interfaces: + return True + else: + return False + else: + return False + + +def is_interface_in_port(interface, port): + """Check interface is in port or not""" + + for iface in port.interfaces: + if interface == iface.name: + return True + + return False + + +def compare_interfaces(interfaces1, interfaces2): + set1 = set(interfaces1) + set2 = set(interfaces2) + len1 = len(set1 - set2) + len2 = len(set2 - set1) + + if len1 is 0 and len2 is 0: + return True + else: + return False + + +# The config function - called once on collectd process startup +def config_func(config): + + """Configure the plugin""" + + collectd.info('%s config function' % PLUGIN) + + +# The init function - called once on collectd process startup +def init_func(): + """Init the plugin""" + + if obj.init_done is False: + if obj.init_ready() is False: + return 0 + + obj.hostname = obj.gethostname() + + # Check whether this host is openstack worker node or not + # OVS and OVSDPDK will only run on openstack worker node + # For non openstack worker node, pid file won't exist + if os.path.exists(OVS_VSWITCHD_PID_FILE): + with open(OVS_VSWITCHD_PID_FILE, 'r') as infile: + count = 0 + for line in infile: + pid = line.strip() + count += 1 + + if count == 1 and pid: + # /var/run/openvswitch/ovs-vswitchd.43.ctl + global OVS_VSWITCHD_SOCKET + OVS_VSWITCHD_SOCKET = "".join([OVS_VSWITCHD_PATH, ".", pid, ".ctl"]) + obj.init_done = True + collectd.info("%s initialization complete" % PLUGIN) + else: + collectd.info("%s failed to retrieve pid for ovs-vswitchd in " + "file /var/run/openvswitch/ovs-vswitchd.pid" % PLUGIN) + else: + collectd.info("%s failed to initial because pid file " + "for ovs-vswitchd doesn't exist" % PLUGIN) + + return 0 + + +# The sample read function - called on every audit interval +def read_func(): + """collectd ovs interface/port monitor plugin read function""" + + if obj.init_done is False: + init_func() + return 0 + + if obj.phase < RUN_PHASE__ALARMS_CLEARED: + + # clear all alarms on first audit + # + # block on fm availability + # + # If the existing raised alarms are still valid then + # they will be re-raised + if clear_alarms(ALARM_ID_LIST) is False: + collectd.error("%s failed to clear existing alarms ; " + "retry next audit" % PLUGIN) + + # Don't proceed till we can communicate with FM and + # clear all existing interface and port alarms. + return 0 + else: + obj.phase = RUN_PHASE__ALARMS_CLEARED + + # construct monitoring objects + bridges_phy = [] + res, err = processutils.execute(OVS_VSCTL_LIST_BR, shell=True) + if err: + collectd.error("%s failed to list ovs bridges ; " + "retry next audit" % PLUGIN) + return 0 + elif res: + # remove the trailing '\n' with strip() + bridges = res.strip().split("\n") + for bridge in bridges: + # the bridges added by starlingx follows the format br-phy%num + # the bridges' name start with br-phy is used for datanetwork + if bridge.startswith("br-phy"): + bridges_phy.append(bridge) + else: + collectd.error("%s find no ovs bridges ; " + "retry next audit" % PLUGIN) + return 0 + + # generate port and interface objects for first time + physical_interfaces = get_physical_interfaces() + for bridge in bridges_phy: + if bridge not in ports: + port = PortObject(bridge) + ports[bridge] = port + + cmd = " ".join([OVS_VSCTL_LIST_IFACES, bridge]) + res, err = processutils.execute(cmd, shell=True) + if err: + collectd.error("%s failed to dump ports %s desc ; " + "retry next audit" % (PLUGIN, bridge)) + return 0 + elif res: + interfaces = parse_ovs_vsctl_list_ifaces(res) + port = ports[bridge] + # Remove the physical interfaces which has been removed from port + remove_list = [] + for interface in port.interfaces: + if interface.name not in interfaces: + remove_list.append(interface) + + for interface in remove_list: + # Remove the existing alarms before deleting interface + interface.manage_interface_alarm(LINK_UP) + interface.state = LINK_UP + port.interfaces.remove(interface) + + # Add physical interfaces to port.interfaces + for interface in interfaces: + try: + if is_physical_interface(interface, bridge): + if not is_interface_in_port(interface, port): + iface = InterfaceObject(interface) + port.interfaces.append(iface) + except Exception as ex: + collectd.error("%s exception %s" % (PLUGIN, ex)) + return 0 + + for port in ports.values(): + # Delete the port which has been removed from OVS + if port.bridge_name not in bridges_phy: + # Remove the existing alarms before delete ports + for interface in port.interfaces: + interface.manage_interface_alarm(LINK_UP) + interface.state = LINK_UP + port.manage_port_alarm() + ports.pop(port.bridge_name) + continue + + # Retrieve interface state + cmd = " ".join([OVS_OFCTL_DUMP_PORTS_DESC, port.bridge_name]) + res, err = processutils.execute(cmd, shell=True) + if err: + collectd.error("%s failed to dump ports %s desc ; " + "retry next audit" % (PLUGIN, bridge)) + return 0 + elif res: + states = parse_ovs_ofctl_dump_ports_desc(res, port) + # Not bond + if len(port.interfaces) == 1: + interface = port.interfaces[0] + interface.manage_interface_alarm(states[interface.name]) + interface.state = states[interface.name] + # Bond: do nothing here + # Need to use ovs-appctl commands to retrieve bond interfaces + # status. Because if LACP is enabled for bond, it can detect + # the remote status of the bond which can't retrived here. + + # handle bond + cmd = "".join([OVS_APPCTL, " --target=", OVS_VSWITCHD_SOCKET, " ", BOND_LIST]) + res, err = processutils.execute(cmd, shell=True) + if err: + collectd.error("%s failed to list bond ; " + "retry next audit" % PLUGIN) + return 0 + elif res: + bonds = parse_ovs_appctl_bond_list(res) + + # Mapping bond to port by checking interfaces same or not + # If port is a bond, the port name will be set + for bond, interfaces in bonds.iteritems(): + for port in ports.values(): + port_interfaces = [] + for iface in port.interfaces: + port_interfaces.append(iface.name) + if compare_interfaces(interfaces, port_interfaces): + port.name = bond + + for port in ports.values(): + if port.name: + cmd = "".join([OVS_APPCTL, " --target=", OVS_VSWITCHD_SOCKET, + " ", BOND_SHOW, " ", port.name]) + res, err = processutils.execute(cmd, shell=True) + if err: + collectd.error("%s failed to list bond ; " + "retry next audit" % PLUGIN) + return 0 + elif res: + states = parse_ovs_appctl_bond_show(res) + for interface in port.interfaces: + interface.manage_interface_alarm(states[interface.name]) + interface.state = states[interface.name] + + # Manage port alarms + for port in ports.values(): + port.manage_port_alarm() + + # Dispatch usage value to collectd + val = collectd.Values(host=obj.hostname) + val.plugin = 'ovs interface' + val.type = 'percent' + val.type_instance = 'used' + + # For each port + # calculate the percentage used sample + # sample = 100 % when all its links are up + # sample = 0 % when all its links are down + # sample = x % x > 0 and x < 100. Some interfaces of + # that bond are Down. For example, x will + # be 50 when one interface of the bond + # which contains two interfaces is down. + # x will be 66.7 when two of the bond + # which contains three interfaces is down. + for port in ports.values(): + if len(port.interfaces) > 0: + val.plugin_instance = port.bridge_name + port_up = 0.0 + for interface in port.interfaces: + if interface.state == LINK_UP: + port_up += 1.0 + + port.sample = (port_up / float(len(port.interfaces))) * 100.0 + val.dispatch(values=[port.sample]) + + else: + collectd.debug("%s %s bridge not provisioned" % + (PLUGIN, port.bridge_name)) + obj.audits += 1 + + return 0 + + +# register the config, init and read functions +collectd.register_config(config_func) +collectd.register_init(init_func) +collectd.register_read(read_func, interval=PLUGIN_AUDIT_INTERVAL) diff --git a/collectd-extensions/src/python_plugins.conf b/collectd-extensions/src/python_plugins.conf index 7def75d..49d626c 100644 --- a/collectd-extensions/src/python_plugins.conf +++ b/collectd-extensions/src/python_plugins.conf @@ -17,6 +17,7 @@ LoadPlugin python Port 2122 + Import "ovs_interface" Import "remotels" LogTraces = true Encoding "utf-8"