diff --git a/doc/content/design/lldp.md b/doc/content/design/lldp.md index 615c992b091..369ecb035aa 100644 --- a/doc/content/design/lldp.md +++ b/doc/content/design/lldp.md @@ -83,9 +83,12 @@ Default after fresh install: `nearestbridge`. Type: `map(string, string)` -Stores the received LLDP TLVs from the corresponding PIF. +Stores the effective LLDP information for the physical NIC of the corresponding PIF: -Default: empty +- `state`: the effective LLDP state of the NIC, one of `enabled`, `disabled` or `blocked` (`blocked` means the NIC driver is in the blocking list). Always present for a managed physical NIC. +- `system_name`, `port_id`, `port_description`: the TLVs received from the neighbour, present only when a neighbour is seen. + +Default: empty (also empty for PIFs that are not managed physical NICs). ## XenAPI changes @@ -200,19 +203,26 @@ Some advertised values follow the default behavior of `lldpd`, while others are networkd periodically queries statistics for individual NICs and writes them to the in-memory file `/dev/shm/network_stats`. The file format is defined in `ocaml/xapi-idl/network/network_stats.ml` and is extended with a new field, `lldp_neighbor`. ```ocaml -type lldp_rx = { +type lldp_state = Enabled | Disabled | Blocked + +type lldp_neighbor = { system_name: string option; port_id: string option; port_description: string option; } + +type lldp_rx = { + state: lldp_state; + neighbor: lldp_neighbor option; +} [@@deriving rpcty] type iface_stats = { ... - lldp_neighbor: lldp_rx option; + lldp_rx: lldp_rx option; } ``` -networkd queries `lldpd` for the LLDP TLVs received on individual NICs and writes them into `/dev/shm/network_stats`. +networkd derives the `state` from `lldpcli show interfaces` (a NIC reporting `RX and TX` is `Enabled`; otherwise it is `Blocked` when its driver is in the blocking list, else `Disabled`) and queries `lldpd` for the LLDP TLVs received on individual NICs, writing both into `/dev/shm/network_stats`. Monitor_dbcalls.monitor_dbcall_thread in XAPI reads the in-memory file `/dev/shm/network_stats` periodically, and exposes the data through `PIF_metrics.lldp_neighbor` by storing them in XenAPI map form. ## Scenarios diff --git a/ocaml/idl/datamodel.ml b/ocaml/idl/datamodel.ml index 3d640cff7a1..57680a93cfd 100644 --- a/ocaml/idl/datamodel.ml +++ b/ocaml/idl/datamodel.ml @@ -3014,6 +3014,15 @@ module PIF_metrics = struct ~default_value:(Some (VMap [])) ~ty:(Map (String, String)) "other_config" "additional configuration" + ; field ~qualifier:DynamicRO ~lifecycle:[] + ~default_value:(Some (VMap [])) + ~ty:(Map (String, String)) + "lldp_neighbor" + "The LLDP information for the physical NIC of the corresponding \ + PIF: the effective LLDP state (key 'state', one of enabled, \ + disabled or blocked) and, when a neighbour is seen, the received \ + TLVs (keys 'system_name', 'port_id' and 'port_description'). \ + Empty for PIFs that are not managed physical NICs." ] () end diff --git a/ocaml/idl/datamodel_common.ml b/ocaml/idl/datamodel_common.ml index 211fc92daed..5f9db17ad6f 100644 --- a/ocaml/idl/datamodel_common.ml +++ b/ocaml/idl/datamodel_common.ml @@ -10,7 +10,7 @@ open Datamodel_roles to leave a gap for potential hotfixes needing to increment the schema version.*) let schema_major_vsn = 5 -let schema_minor_vsn = 909 +let schema_minor_vsn = 910 (* Historical schema versions just in case this is useful later *) let rio_schema_major_vsn = 5 diff --git a/ocaml/idl/schematest.ml b/ocaml/idl/schematest.ml index e960d144f90..58906afbe32 100644 --- a/ocaml/idl/schematest.ml +++ b/ocaml/idl/schematest.ml @@ -3,7 +3,7 @@ let hash x = Digest.string x |> Digest.to_hex (* BEWARE: if this changes, check that schema has been bumped accordingly in ocaml/idl/datamodel_common.ml, usually schema_minor_vsn *) -let last_known_schema_hash = "4f4145585a0be563e77b01220bf3f6af" +let last_known_schema_hash = "db58f982a09d1aded5413d5632c89560" let current_schema_hash : string = let open Datamodel_types in diff --git a/ocaml/networkd/bin/network_monitor_thread.ml b/ocaml/networkd/bin/network_monitor_thread.ml index 64052bd2b19..d37a047ad0e 100644 --- a/ocaml/networkd/bin/network_monitor_thread.ml +++ b/ocaml/networkd/bin/network_monitor_thread.ml @@ -132,10 +132,52 @@ let get_link_stats dbg () = in Cache.free cache ; Socket.close s ; Socket.free s ; links +(* Cache of the latest LLDP neighbour seen per interface. lldpd is queried on a + slower cadence than the rest of the stats (LLDPDUs arrive ~every 30s), to + avoid unnecessary lldpcli calls. *) +let lldp_neighbors : (string, Network_monitor.lldp_neighbor) Hashtbl.t = + Hashtbl.create 16 + +let lldp_last_query = ref neg_infinity + +let lldp_query_interval = 30.0 + +(* Interfaces on which lldpd currently has LLDP enabled (rx-and-tx), refreshed on + the same cadence as the neighbour query. *) +let lldp_enabled_interfaces = ref [] + +let refresh_lldp_neighbors () = + let now = Unix.gettimeofday () in + if now -. !lldp_last_query >= lldp_query_interval then ( + lldp_last_query := now ; + lldp_enabled_interfaces := Lldp.get_enabled_interfaces () ; + Hashtbl.reset lldp_neighbors ; + List.iter + (fun (dev, rx) -> + if Hashtbl.mem lldp_neighbors dev then + debug "Multiple LLDP neighbours on %s; keeping the first" dev + else + Hashtbl.replace lldp_neighbors dev rx + ) + (Lldp.get_neighbors ()) + ) + +(* The effective LLDP state of physical [dev], read from lldpd's reported status + (rx-and-tx) and, for non-enabled NICs, the driver blocklist. *) +let lldp_state_of dev = + Lldp.state_of dev ~enabled:(List.mem dev !lldp_enabled_interfaces) + +(* The LLDP information reported for physical [dev]: its effective state is + always populated; neighbour fields come from the last query, if any. *) +let lldp_rx_of dev = + Network_monitor. + {state= lldp_state_of dev; neighbor= Hashtbl.find_opt lldp_neighbors dev} + let rec monitor dbg () = let open Network_interface in let open Network_monitor in ( try + refresh_lldp_neighbors () ; let get_stats bonds devs = List.map (fun dev -> @@ -176,6 +218,7 @@ let rec monitor dbg () = ; nb_links ; links_up ; interfaces + ; lldp_rx= Some (lldp_rx_of dev) } else let carrier = List.exists (fun info -> info.up) bond_slaves in @@ -219,6 +262,7 @@ let rec monitor dbg () = ; nb_links ; links_up ; interfaces + ; lldp_rx= None } in check_for_changes ~dev ~stat ; diff --git a/ocaml/networkd/lib/lldp.ml b/ocaml/networkd/lib/lldp.ml index b85fe63cb29..a0558fc69a6 100644 --- a/ocaml/networkd/lib/lldp.ml +++ b/ocaml/networkd/lib/lldp.ml @@ -50,6 +50,67 @@ module Lldp_types = struct | Multicast_address of I.lldp_multicast_address list end +module Lldp_parse = struct + let ( let* ) = Option.bind + + (* Follow [keys] through a json0 doc. Object keys are consumed one at a time; + arrays are transparently entered at their head without consuming a key + (json0 wraps every repeatable node in an array). An exhausted path returns + the current node as-is. *) + let rec json0_get json0 keys = + match keys with + | [] -> + Some json0 + | k :: ks as keys -> ( + match json0 with + | `Assoc l -> + let* json' = List.assoc_opt k l in + json0_get json' ks + | `List (json' :: _) -> + json0_get json' keys + | _ -> + None + ) + + let json0_get_str json0 keys = + match json0_get json0 keys with Some (`String s) -> Some s | _ -> None + + let interfaces (output : string) : Yojson.Safe.t list = + match Yojson.Safe.from_string output with + | exception e -> + debug "%s: could not parse lldpcli JSON: %s" __FUNCTION__ + (Printexc.to_string e) ; + [] + | json0 -> ( + match json0_get json0 ["lldp"; "interface"] with + | Some (`List l) -> + l + | _ -> + [] + ) + + let parse_neighbors (output : string) : + (string * Network_stats.lldp_neighbor) list = + interfaces output + |> List.filter_map (fun iface -> + let* dev = json0_get_str iface ["name"] in + let system_name = json0_get_str iface ["chassis"; "name"; "value"] in + let port_id = json0_get_str iface ["port"; "id"; "value"] in + let port_description = json0_get_str iface ["port"; "descr"; "value"] in + Some (dev, Network_stats.{system_name; port_id; port_description}) + ) + + let parse_enabled_interfaces (output : string) : string list = + interfaces output + |> List.filter_map (fun iface -> + match json0_get_str iface ["status"; "value"] with + | Some "RX and TX" -> + json0_get_str iface ["name"] + | _ -> + None + ) +end + module type AGENT = sig type error = Lldp_types.error @@ -70,6 +131,12 @@ module type AGENT = sig val disable : string -> (unit, error) result (** Stop LLDP (rx-and-tx) on [dev]. *) + + val get_neighbors : unit -> (string * Network_stats.lldp_neighbor) list + (** Query the agent for the LLDP neighbour received on each interface. *) + + val get_enabled_interfaces : unit -> string list + (** The interfaces on which the agent currently has LLDP enabled (rx-and-tx). *) end let management_ip_address = @@ -104,6 +171,10 @@ module Lldpd : AGENT = struct let cli = "/usr/sbin/lldpcli" + let show_neighbors_args = ["-f"; "json0"; "show"; "neighbors"] + + let show_interfaces_args = ["-f"; "json0"; "show"; "interfaces"] + let systemctl = "/usr/bin/systemctl" let service = "lldpd" @@ -171,6 +242,28 @@ module Lldpd : AGENT = struct let disable dev = call_cli ["configure"; "ports"; dev; "lldp"; "status"; "disabled"] + let get_neighbors () = + match + try Ok (Network_utils.call_script cli show_neighbors_args) + with e -> Error (Printexc.to_string e) + with + | Ok output -> + Lldp_parse.parse_neighbors output + | Error msg -> + debug "%s: could not query LLDP neighbours: %s" __FUNCTION__ msg ; + [] + + let get_enabled_interfaces () = + match + try Ok (Network_utils.call_script cli show_interfaces_args) + with e -> Error (Printexc.to_string e) + with + | Ok output -> + Lldp_parse.parse_enabled_interfaces output + | Error msg -> + debug "%s: could not query LLDP interfaces: %s" __FUNCTION__ msg ; + [] + let string_of_multicast_address = function | I.Nearest_bridge -> "nearest-bridge" @@ -430,3 +523,19 @@ let stop = Lldp_agent.stop let set_tlv_management_address () = management_ip_address ~force:true () |> Lldp_agent.set_tlv_management_address + +let get_neighbors = Lldpd.get_neighbors + +let get_enabled_interfaces = Lldpd.get_enabled_interfaces + +let parse_neighbors = Lldp_parse.parse_neighbors + +let parse_enabled_interfaces = Lldp_parse.parse_enabled_interfaces + +let state_of dev ~enabled : Network_stats.lldp_state = + if enabled then + Network_stats.Enabled + else if Blocklist.mem dev then + Network_stats.Blocked + else + Network_stats.Disabled diff --git a/ocaml/networkd/lib/lldp.mli b/ocaml/networkd/lib/lldp.mli index 3c509e249ee..34f06effc1c 100644 --- a/ocaml/networkd/lib/lldp.mli +++ b/ocaml/networkd/lib/lldp.mli @@ -22,3 +22,25 @@ val stop : unit -> unit val set_tlv_management_address : unit -> unit (** [set_tlv_management_address ()] retrieves the management IP address(es) of the host and configure them in the LLDP management address TLV for advertising. *) + +val get_neighbors : unit -> (string * Network_stats.lldp_neighbor) list +(** [get_neighbors ()] queries the LLDP agent and returns, per interface, the + received neighbour information (system name, port id, port description). *) + +val get_enabled_interfaces : unit -> string list +(** [get_enabled_interfaces ()] queries the LLDP agent and returns the + interfaces on which LLDP is enabled (rx-and-tx). *) + +val parse_neighbors : string -> (string * Network_stats.lldp_neighbor) list +(** [parse_neighbors output] parses the JSON produced by + [lldpcli -f json0 show neighbors]. Exposed for testing. *) + +val parse_enabled_interfaces : string -> string list +(** [parse_enabled_interfaces output] parses the JSON produced by + [lldpcli -f json0 show interfaces], returning the rx-and-tx interfaces. + Exposed for testing. *) + +val state_of : string -> enabled:bool -> Network_stats.lldp_state +(** [state_of dev ~enabled] is the effective LLDP state of physical NIC [dev]: + [Enabled] when lldpd reports it as rx-and-tx, otherwise [Blocked] when its + driver is in the blocklist, else [Disabled]. *) diff --git a/ocaml/networkd/test/network_test.ml b/ocaml/networkd/test/network_test.ml index e3c8029c797..8453cd54c00 100644 --- a/ocaml/networkd/test/network_test.ml +++ b/ocaml/networkd/test/network_test.ml @@ -19,4 +19,5 @@ let () = @ Test_jsonrpc_client.tests @ Test_network_device_order_inherited.tests @ Test_network_device_order.tests + @ Test_lldp.tests ) diff --git a/ocaml/networkd/test/test_lldp.ml b/ocaml/networkd/test/test_lldp.ml new file mode 100644 index 00000000000..0ace0594610 --- /dev/null +++ b/ocaml/networkd/test/test_lldp.ml @@ -0,0 +1,159 @@ +(* + * Copyright (c) Cloud Software Group, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; version 2.1 only. with the special + * exception on linking described in file LICENSE. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + *) +(* Tests for Lldp.parse_neighbors: parsing the JSON emitted by + [lldpcli -f json0 show neighbors]. *) +let neighbor_testable = + Alcotest.testable + (fun ppf (n : Network_stats.lldp_neighbor) -> + Fmt.pf ppf "{system_name=%a; port_id=%a; port_description=%a}" + Fmt.(option string) + n.system_name + Fmt.(option string) + n.port_id + Fmt.(option string) + n.port_description + ) + ( = ) + +let result_testable = Alcotest.(list (pair string neighbor_testable)) + +let rx ?system_name ?port_id ?port_description () = + Network_stats.{system_name; port_id; port_description} + +(* One interface with one neighbour (a Cisco Nexus switch). *) +let single_json = + {| + { "lldp": [ { "interface": [ + { "name": "eno8303", + "chassis": [ { "name": [ { "value": "NKG-ESWA07-2.eng.citrite.net" } ] } ], + "port": [ { "id": [ { "value": "Ethernet1/28" } ], + "descr": [ { "value": "nkg-dt16/idrac" } ] } ] } + ] } ] } + |} + +(* One interface with two neighbours (wider multicast scope / repeater). *) +let multi_json = + {| + { "lldp": [ { "interface": [ + { "name": "eno8303", + "chassis": [ { "name": [ { "value": "TOR-A-01" } ] } ], + "port": [ { "id": [ { "value": "Eth1/8/3" } ], + "descr": [ { "value": "rack7-a" } ] } ] }, + { "name": "eno8303", + "chassis": [ { "name": [ { "value": "TOR-B-02" } ] } ], + "port": [ { "id": [ { "value": "Eth2/8/3" } ], + "descr": [ { "value": "rack7-b" } ] } ] } + ] } ] } + |} + +let empty_json = {| { "lldp": [ { "interface": [] } ] } |} + +let test_single () = + Alcotest.check result_testable "single neighbour" + [ + ( "eno8303" + , rx ~system_name:"NKG-ESWA07-2.eng.citrite.net" ~port_id:"Ethernet1/28" + ~port_description:"nkg-dt16/idrac" () + ) + ] + (Lldp.parse_neighbors single_json) + +let test_multi () = + (* The parser returns all neighbours; picking one is done by the caller. *) + Alcotest.check result_testable "two neighbours on one interface" + [ + ( "eno8303" + , rx ~system_name:"TOR-A-01" ~port_id:"Eth1/8/3" + ~port_description:"rack7-a" () + ) + ; ( "eno8303" + , rx ~system_name:"TOR-B-02" ~port_id:"Eth2/8/3" + ~port_description:"rack7-b" () + ) + ] + (Lldp.parse_neighbors multi_json) + +let test_empty () = + Alcotest.check result_testable "no neighbours" [] + (Lldp.parse_neighbors empty_json) + +let test_malformed () = + Alcotest.check result_testable "malformed JSON yields empty" [] + (Lldp.parse_neighbors "not json {") + +let state_testable = + Alcotest.testable (Fmt.of_to_string Network_stats.string_of_lldp_state) ( = ) + +(* [state_of] on a device with no real driver never matches the blocklist, so a + non-enabled interface is reported as [Disabled] rather than [Blocked]. *) +let test_state_disabled () = + Alcotest.check state_testable "not rx-and-tx is disabled" + Network_stats.Disabled + (Lldp.state_of "lldptest0" ~enabled:false) + +let test_state_enabled () = + Alcotest.check state_testable "rx-and-tx is enabled" Network_stats.Enabled + (Lldp.state_of "lldptest0" ~enabled:true) + +let interfaces_json = + {| + { "lldp": [ { "interface": [ + { "name": "eno0", "status": [ { "value": "RX and TX" } ] }, + { "name": "eno1", "status": [ { "value": "disabled" } ] }, + { "name": "ovs-system", "status": [ { "value": "disabled" } ] } + ] } ] } + |} + +let test_parse_enabled () = + Alcotest.check + Alcotest.(list string) + "only rx-and-tx interfaces" ["eno0"] + (Lldp.parse_enabled_interfaces interfaces_json) + +let test_parse_enabled_empty () = + Alcotest.check + Alcotest.(list string) + "no interfaces" [] + (Lldp.parse_enabled_interfaces {| { "lldp": { "interface": [] } } |}) + +let test_parse_enabled_malformed () = + Alcotest.check + Alcotest.(list string) + "malformed JSON yields empty" [] + (Lldp.parse_enabled_interfaces "not json {") + +let tests = + [ + ( "lldp_parse_neighbors" + , [ + ("single", `Quick, test_single) + ; ("multi", `Quick, test_multi) + ; ("empty", `Quick, test_empty) + ; ("malformed", `Quick, test_malformed) + ] + ) + ; ( "lldp_state_of" + , [ + ("disabled", `Quick, test_state_disabled) + ; ("enabled", `Quick, test_state_enabled) + ] + ) + ; ( "lldp_parse_enabled_interfaces" + , [ + ("enabled", `Quick, test_parse_enabled) + ; ("empty", `Quick, test_parse_enabled_empty) + ; ("malformed", `Quick, test_parse_enabled_malformed) + ] + ) + ] diff --git a/ocaml/networkd/test/test_lldp.mli b/ocaml/networkd/test/test_lldp.mli new file mode 100644 index 00000000000..c32d2a7e66b --- /dev/null +++ b/ocaml/networkd/test/test_lldp.mli @@ -0,0 +1,15 @@ +(* + * Copyright (c) Cloud Software Group, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; version 2.1 only. with the special + * exception on linking described in file LICENSE. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + *) + +val tests : unit Alcotest.test list diff --git a/ocaml/xapi-cli-server/records.ml b/ocaml/xapi-cli-server/records.ml index c24accd01f0..8f031dddc1b 100644 --- a/ocaml/xapi-cli-server/records.ml +++ b/ocaml/xapi-cli-server/records.ml @@ -741,6 +741,18 @@ let pif_record rpc session_id pif = Record_util.pif_lldp_mode_to_string (x ()).API.pIF_lldp_mode ) () + ; make_field ~name:"lldp-neighbor" + ~get:(fun () -> + Option.fold ~none:nid + ~some:(fun m -> get_from_map m.API.pIF_metrics_lldp_neighbor) + (xm ()) + ) + ~get_map:(fun () -> + Option.fold ~none:[] + ~some:(fun m -> m.API.pIF_metrics_lldp_neighbor) + (xm ()) + ) + () ] } diff --git a/ocaml/xapi-idl/network/network_stats.ml b/ocaml/xapi-idl/network/network_stats.ml index 45972855dc3..a5b6b537bbf 100644 --- a/ocaml/xapi-idl/network/network_stats.ml +++ b/ocaml/xapi-idl/network/network_stats.ml @@ -34,6 +34,27 @@ let checksum_bytes = 32 let length_bytes = 8 +type lldp_state = Enabled | Disabled | Blocked +[@@default Disabled] [@@deriving rpcty] + +let string_of_lldp_state = function + | Enabled -> + "enabled" + | Disabled -> + "disabled" + | Blocked -> + "blocked" + +type lldp_neighbor = { + system_name: string option + ; port_id: string option + ; port_description: string option +} +[@@deriving rpcty] + +type lldp_rx = {state: lldp_state; neighbor: lldp_neighbor option} +[@@deriving rpcty] + type iface_stats = { carrier: bool ; speed: int @@ -44,6 +65,7 @@ type iface_stats = { ; nb_links: int ; links_up: int ; interfaces: iface list + ; lldp_rx: lldp_rx option } [@@deriving rpcty] @@ -58,6 +80,7 @@ let default_stats = ; nb_links= 0 ; links_up= 0 ; interfaces= [] + ; lldp_rx= None } type stats_t = (iface * iface_stats) list [@@deriving rpcty] diff --git a/ocaml/xapi/monitor_dbcalls.ml b/ocaml/xapi/monitor_dbcalls.ml index 9561135fbc4..348d9cf7181 100644 --- a/ocaml/xapi/monitor_dbcalls.ml +++ b/ocaml/xapi/monitor_dbcalls.ml @@ -22,6 +22,22 @@ module D = Debug.Make (struct let name = "monitor_dbcalls" end) open D +let lldp_map_of_rx (rx : Network_stats.lldp_rx) : (string * string) list = + ("state", Network_stats.string_of_lldp_state rx.Network_stats.state) + :: + ( match rx.Network_stats.neighbor with + | None -> + [] + | Some n -> + List.filter_map + (fun (k, v) -> Option.map (fun x -> (k, x)) v) + [ + ("system_name", n.Network_stats.system_name) + ; ("port_id", n.Network_stats.port_id) + ; ("port_description", n.Network_stats.port_description) + ] + ) + let get_pif_and_bond_changes () = (* Read fresh PIF information from networkd. *) let open Network_stats in @@ -40,6 +56,13 @@ let get_pif_and_bond_changes () = ; pif_pci_bus_path= stat.pci_bus_path ; pif_vendor_id= stat.vendor_id ; pif_device_id= stat.device_id + ; pif_lldp_neighbor= + ( match stat.lldp_rx with + | Some rx -> + lldp_map_of_rx rx + | None -> + [] + ) } in Hashtbl.add pifs_tmp pif.pif_name pif diff --git a/ocaml/xapi/monitor_master.ml b/ocaml/xapi/monitor_master.ml index 18a2c9edf7e..46e84d64f24 100644 --- a/ocaml/xapi/monitor_master.ml +++ b/ocaml/xapi/monitor_master.ml @@ -50,7 +50,7 @@ let get_pciids vendor device = ) let set_pif_metrics ~__context ~self ~vendor ~device ~carrier ~speed ~duplex - ~pcibuspath pmr = + ~pcibuspath ~lldp_neighbor pmr = (* don't update & and reread pciids if db already contains same value *) if pmr.API.pIF_metrics_vendor_id <> vendor @@ -70,6 +70,12 @@ let set_pif_metrics ~__context ~self ~vendor ~device ~carrier ~speed ~duplex Db.PIF_metrics.set_duplex ~__context ~self ~value:duplex ; if pmr.API.pIF_metrics_pci_bus_path <> pcibuspath then Db.PIF_metrics.set_pci_bus_path ~__context ~self ~value:pcibuspath ; + ( match lldp_neighbor with + | Some v when pmr.API.pIF_metrics_lldp_neighbor <> v -> + Db.PIF_metrics.set_lldp_neighbor ~__context ~self ~value:v + | _ -> + () + ) ; Db.PIF_metrics.set_last_updated ~__context ~self ~value:(Date.now ()) (* Note that the following function is actually called on the slave most of the @@ -113,6 +119,14 @@ let update_pifs ~__context host pifs = let vendor = pif_stats.pif_vendor_id in let device = pif_stats.pif_device_id in let pcibuspath = pif_stats.pif_pci_bus_path in + (* LLDP is scoped to managed physical NICs; do not write it for + bond masters or non-managed PIFs. *) + let lldp_neighbor = + if pifrec.API.pIF_physical && pifrec.API.pIF_managed then + Some pif_stats.pif_lldp_neighbor + else + None + in (* 1. Update corresponding VIF carrier flags *) if !Xapi_globs.pass_through_pif_carrier then ( try @@ -189,13 +203,13 @@ let update_pifs ~__context host pifs = ~carrier:false ~device_name:"" ~vendor_name:"" ~device_id:"" ~vendor_id:"" ~speed:0L ~duplex:false ~pci_bus_path:"" ~io_read_kbs:0. ~io_write_kbs:0. ~last_updated:Date.epoch - ~other_config:[] ; + ~other_config:[] ~lldp_neighbor:[] ; Db.PIF.set_metrics ~__context ~self:pifdev ~value:ref ; ref in let pmr = Db.PIF_metrics.get_record ~__context ~self:metrics in set_pif_metrics ~__context ~self:metrics ~vendor ~device ~carrier - ~speed ~duplex ~pcibuspath pmr + ~speed ~duplex ~pcibuspath ~lldp_neighbor pmr with Not_found -> () ) db_pifs diff --git a/ocaml/xapi/monitor_types.ml b/ocaml/xapi/monitor_types.ml index 3e6ce7513d1..ac44bf3e545 100644 --- a/ocaml/xapi/monitor_types.ml +++ b/ocaml/xapi/monitor_types.ml @@ -23,6 +23,7 @@ type pif = { ; pif_pci_bus_path: string ; pif_vendor_id: string ; pif_device_id: string + ; pif_lldp_neighbor: (string * string) list } let vif_device_of_string x = diff --git a/ocaml/xapi/xapi_pif.ml b/ocaml/xapi/xapi_pif.ml index 737ad08f963..6f670a59da2 100644 --- a/ocaml/xapi/xapi_pif.ml +++ b/ocaml/xapi/xapi_pif.ml @@ -478,7 +478,7 @@ let make_pif_metrics ~__context = Db.PIF_metrics.create ~__context ~ref:metrics ~uuid:metrics_uuid ~carrier:false ~device_name:"" ~vendor_name:"" ~device_id:"" ~vendor_id:"" ~speed:0L ~duplex:false ~pci_bus_path:"" ~io_read_kbs:0. ~io_write_kbs:0. - ~last_updated:Date.epoch ~other_config:[] + ~last_updated:Date.epoch ~other_config:[] ~lldp_neighbor:[] in metrics