From fa5d829dbfc0cbab499bd06daae4cde1b6ecb82d Mon Sep 17 00:00:00 2001 From: Ming Lu Date: Tue, 8 Apr 2025 17:31:24 +0800 Subject: [PATCH 1/3] CP-44103: Ordering network devices - IDL changes Signed-off-by: Ming Lu --- ocaml/networkd/lib/network_config.ml | 1 + ocaml/xapi-idl/network/dune | 1 + ocaml/xapi-idl/network/network_interface.ml | 41 +++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/ocaml/networkd/lib/network_config.ml b/ocaml/networkd/lib/network_config.ml index b306b580b32..5225691ed2a 100644 --- a/ocaml/networkd/lib/network_config.ml +++ b/ocaml/networkd/lib/network_config.ml @@ -187,6 +187,7 @@ let read_management_conf () = ; bridge_config ; gateway_interface= Some bridge_name ; dns_interface= Some bridge_name + ; interface_order= None } with e -> error "Error while trying to read firstboot data: %s\n%s" diff --git a/ocaml/xapi-idl/network/dune b/ocaml/xapi-idl/network/dune index a9a4869945d..ad507be230e 100644 --- a/ocaml/xapi-idl/network/dune +++ b/ocaml/xapi-idl/network/dune @@ -11,6 +11,7 @@ xapi-idl xapi-log ipaddr + macaddr ) (wrapped false) (preprocess (pps ppx_deriving_rpc))) diff --git a/ocaml/xapi-idl/network/network_interface.ml b/ocaml/xapi-idl/network/network_interface.ml index 6b27e31f5bf..caf6a98fdb8 100644 --- a/ocaml/xapi-idl/network/network_interface.ml +++ b/ocaml/xapi-idl/network/network_interface.ml @@ -81,6 +81,34 @@ module Unix = struct } end +module Macaddr = struct + include Macaddr + + let typ_of = + Rpc.Types.Abstract + { + aname= "macaddr" + ; test_data= [Macaddr.of_string_exn "ca:fe:ba:be:ee:ee"] + ; rpc_of= (fun t -> Rpc.String (Macaddr.to_octets t)) + ; of_rpc= + (function + | Rpc.String s -> + Macaddr.of_octets s + |> Result.map_error (fun (`Msg e) -> + `Msg (Printf.sprintf "typ_of_macaddr: %s" e) + ) + | r -> + Error + (`Msg + (Printf.sprintf + "typ_of_macaddr: expectd rpc string but got %s" + (Rpc.to_string r) + ) + ) + ) + } +end + (** {2 Types} *) type debug_info = string [@@deriving rpcty] @@ -91,6 +119,8 @@ type port = string [@@deriving rpcty] type bridge = string [@@deriving rpcty] +type mac_address = Macaddr.t [@@deriving rpcty] + (* rpcty cannot handle polymorphic variant, so change the definition to variant *) type dhcp_options = Set_gateway | Set_dns [@@deriving rpcty] @@ -184,11 +214,21 @@ type bridge_config_t = { } [@@deriving rpcty] +type ordered_iface = { + name: iface + ; position: int + ; mac: mac_address + ; pci: Xcp_pci.address + ; present: bool +} +[@@deriving rpcty] + type config_t = { interface_config: (iface * interface_config_t) list [@default []] ; bridge_config: (bridge * bridge_config_t) list [@default []] ; gateway_interface: iface option [@default None] ; dns_interface: iface option [@default None] + ; interface_order: ordered_iface list option [@default None] } [@@deriving rpcty] @@ -226,6 +266,7 @@ let default_config = ; bridge_config= [] ; gateway_interface= None ; dns_interface= None + ; interface_order= None } (** {2 Configuration manipulation} *) From 1a782e7baa2400f91ef79b708f57ec51dadbb2fb Mon Sep 17 00:00:00 2001 From: Ming Lu Date: Tue, 8 Apr 2025 17:31:39 +0800 Subject: [PATCH 2/3] CP-44103: Ordering network devices - ordering logic This part sorts host network devices in xcp-networkd. Previously, the ordering was handled by the interface-rename functionality. This will now be replaced by an equivalent function in xcp-networkd, but without renaming the network devices. The renmaing performed by the interface-rename was used to record the sorting result as the name of the NICs like "eth". Now the sorting result will be saved in xcp-networkd database. Signed-off-by: Ming Lu --- ocaml/networkd/lib/dune | 2 + ocaml/networkd/lib/network_device_order.ml | 540 ++++++++++++++++++++ ocaml/networkd/lib/network_device_order.mli | 140 +++++ 3 files changed, 682 insertions(+) create mode 100644 ocaml/networkd/lib/network_device_order.ml create mode 100644 ocaml/networkd/lib/network_device_order.mli diff --git a/ocaml/networkd/lib/dune b/ocaml/networkd/lib/dune index 548d326a4b2..4a1dd03fbff 100644 --- a/ocaml/networkd/lib/dune +++ b/ocaml/networkd/lib/dune @@ -4,6 +4,7 @@ (libraries astring forkexec + macaddr mtime mtime.clock.os re @@ -19,6 +20,7 @@ xapi-stdext-threads xapi-stdext-unix xapi-inventory + xapi-idl xapi-idl.network xapi-log xapi-open-uri diff --git a/ocaml/networkd/lib/network_device_order.ml b/ocaml/networkd/lib/network_device_order.ml new file mode 100644 index 00000000000..79d1d284595 --- /dev/null +++ b/ocaml/networkd/lib/network_device_order.ml @@ -0,0 +1,540 @@ +(* + * 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. + *) + +(** Generate an order for host network devices and keep the order as stable as possible. + *) + +module D = Debug.Make (struct let name = __MODULE__ end) + +open D +open Network_interface + +let initial_rules_file_path = + "/etc/firstboot.d/data/initial_network_device_rules.conf" + +let ( let* ) = Result.bind + +let cmd_biosdevname = "/usr/sbin/biosdevname" + +type error = + | Pci_addr_parse_error of string + | Mac_addr_parse_error of string + | Rule_parse_error of string + | Missing_biosdevname_key of string + | Duplicate_mac_address + | Duplicate_position + | Invalid_biosdevname_key_value of (string * string) + +module Pciaddr = struct + type t = Xcp_pci.address + + let default = Xcp_pci.{domain= 0; bus= 0; dev= 0; fn= 0} + + let to_string = Xcp_pci.string_of_address + + let of_string s = + try Ok (Xcp_pci.address_of_string s) + with _ -> Error (Pci_addr_parse_error s) + + let compare t1 t2 = + let open Xcp_pci in + let ( ) a b = if a = 0 then b else a in + compare t1.domain t2.domain + compare t1.bus t2.bus + compare t1.dev t2.dev + compare t1.fn t2.fn +end + +module Macaddr = struct + include Macaddr + + let of_string s = + of_string s |> Result.map_error (fun _ -> Mac_addr_parse_error s) +end + +module PciaddrMap = Map.Make (Pciaddr) +module MacaddrSet = Set.Make (Macaddr) +module MacaddrMap = Map.Make (Macaddr) +module IntMap = Map.Make (Int) +module IntSet = Set.Make (Int) + +module UniqueMap (M : Map.S) : sig + exception Duplicate_key + + val of_unique_list : ('a -> M.key) -> 'a list -> 'a M.t + (** [of_unique_list map lst] creates a map with the values in [lst]. Their + keys are created by calling [map value]. Raises [Duplicate_key] whenever + more than one value in [lst] produces the same key when calling + [map value]. *) +end = struct + exception Duplicate_key + + let fail _ = raise Duplicate_key + + let of_unique_list map l = + List.fold_left + (fun acc v -> + let f x = Some (Option.fold ~none:v ~some:fail x) in + M.update (map v) f acc + ) + M.empty l +end + +module MultiMap (M : Map.S) : sig + val of_list : ('a -> M.key) -> 'a list -> 'a list M.t + (** [of_list map lst] creates a map with the values in [lst]. Their keys are + created by calling [map value]. Whenever more than a value generates the + key when calling [map value], the values are concatenated as a list. *) +end = struct + let of_list map l = + List.fold_left + (fun acc v -> + let f x = Some (Option.fold ~none:[v] ~some:(List.cons v) x) in + M.update (map v) f acc + ) + M.empty l +end + +module IntUniqueMap = UniqueMap (IntMap) +module MacaddrUniqueMap = UniqueMap (MacaddrMap) +module PciaddrMultiMap = MultiMap (PciaddrMap) + +let fold_results (l : ('a, 'e) result list) : ('a list, 'e) result = + List.fold_left + (fun acc r -> + match (acc, r) with + | Ok acc, Ok r -> + Ok (r :: acc) + | Error error, _ -> + Error error + | Ok _, Error error -> + Error error + ) + (Ok []) l + +module Rule = struct + type index = Mac_addr of Macaddr.t | Pci_addr of Pciaddr.t | Label of string + + type t = {position: int; index: index} + + let matches ~(mac : Macaddr.t) ~(pci : Pciaddr.t) ~(label : string) t : bool = + match t.index with + | Mac_addr mac' -> + mac' = mac + | Pci_addr pci' -> + pci' = pci + | Label label' -> + label' = label + + let parse line = + debug "%s: line: %s" __FUNCTION__ line ; + try + Scanf.sscanf line {|%d:%s@="%s@"|} (fun position ty value -> + let to_rule index = Ok {position; index} in + match ty with + | "pci" -> + let* pci = Pciaddr.of_string value in + to_rule (Pci_addr pci) + | "mac" -> + let* mac = Macaddr.of_string value in + to_rule (Mac_addr mac) + | "label" -> + to_rule (Label value) + | _ -> + Error (Rule_parse_error line) + ) + with _ -> Error (Rule_parse_error line) + + let validate (l : (t, error) result list) = + let* rules = fold_results l in + try + IntUniqueMap.of_unique_list (fun dev -> dev.position) rules |> ignore ; + Ok rules + with IntUniqueMap.Duplicate_key -> Error Duplicate_position + + let read ~(path : string) : (t list, error) result = + if not (Sys.file_exists path) then + Ok [] + else + Xapi_stdext_unix.Unixext.read_lines ~path |> List.map parse |> validate +end + +module Dev = struct + type t = { + name: Network_interface.iface + ; mac: Network_interface.mac_address + ; pci: Xcp_pci.address + ; bios_eth_order: int + ; multi_nic: bool + } + + let default = + { + name= "" + ; mac= Macaddr.of_string_exn "00:00:00:00:00:00" + ; pci= Pciaddr.default + ; bios_eth_order= -1 + ; multi_nic= false + } + + let compare_on_mac t1 t2 = Macaddr.compare t1.mac t2.mac + + let compare_on_bios_eth_order t1 t2 = + compare t1.bios_eth_order t2.bios_eth_order + + let to_string t = + Printf.sprintf "Name=%s; MAC=%s; PCI=%s; bios_eth_order=%d; multi_nic=%s" + t.name (Macaddr.to_string t.mac) (Pciaddr.to_string t.pci) + t.bios_eth_order + (string_of_bool t.multi_nic) + + let n_of_ethn ethn = + try Ok (Scanf.sscanf ethn "eth%d" (fun n -> n)) + with _ -> Error (Invalid_biosdevname_key_value ("BIOS device", ethn)) + + let parse output_of_one_dev = + debug "%s: line: %s" __FUNCTION__ output_of_one_dev ; + let kvs = + let open Astring.String in + cuts ~sep:"\n" output_of_one_dev + |> List.filter_map (fun line -> + cut ~sep:":" line |> Option.map (fun (k, v) -> (trim k, trim v)) + ) + in + List.iter (fun (k, v) -> debug "%s: [%s]=[%s]" __FUNCTION__ k v) kvs ; + [ + ( "BIOS device" + , fun r v -> + let* bios_eth_order = n_of_ethn v in + Ok {r with bios_eth_order} + ) + ; ("Kernel name", fun r v -> Ok {r with name= v}) + ; ( "Assigned MAC" + , fun r v -> + let* mac = Macaddr.of_string v in + Ok {r with mac} + ) + ; ( "Bus Info" + , fun r v -> + let* pci = Pciaddr.of_string v in + Ok {r with pci} + ) + ] + |> List.fold_left + (fun acc (k, f) -> + let* r = acc in + match List.assoc_opt k kvs with + | Some v -> + Result.map_error + (fun _ -> Invalid_biosdevname_key_value (k, v)) + (f r v) + | None -> + Error (Missing_biosdevname_key k) + ) + (Ok default) + + let update_multi_nic devs = + let pci_cnt = + let f o = Some (Option.fold ~none:1 ~some:(fun c -> c + 1) o) in + List.fold_left + (fun acc dev -> PciaddrMap.update dev.pci f acc) + PciaddrMap.empty devs + in + List.map + (fun dev : t -> + let multi_nic = + (* Will never raise exception or be < 1 *) + let c = PciaddrMap.find dev.pci pci_cnt in + if c > 1 then true else false + in + {dev with multi_nic} + ) + devs + + let get_all () : (t list, error) result = + let* devs = + Network_utils.call_script cmd_biosdevname + ["--policy"; "all_ethN"; "-d"; "-x"] + |> Astring.String.cuts ~sep:"\n\n" + |> List.filter (fun line -> line <> "") + |> List.map parse + |> fold_results + in + try + MacaddrUniqueMap.of_unique_list (fun v -> v.mac) devs |> ignore ; + Ok (update_multi_nic devs) + with MacaddrUniqueMap.Duplicate_key -> Error Duplicate_mac_address +end + +module OrderedDev = struct + type t = Network_interface.ordered_iface + + let compare_on_mac t1 t2 = Macaddr.compare t1.mac t2.mac + + let to_string t = + Printf.sprintf "position=%d; name=%s; MAC=%s; PCI=%s; present=%s" t.position + t.name (Macaddr.to_string t.mac) (Pciaddr.to_string t.pci) + (string_of_bool t.present) + + let map_by_pci (l : t list) : t list PciaddrMap.t = + PciaddrMultiMap.of_list (fun v -> v.pci) l + + let map_by_position (l : t list) : (t IntMap.t, error) result = + try Ok (IntUniqueMap.of_unique_list (fun v -> v.position) l) + with _ -> Error Duplicate_position + + let validate_no_duplicate_position (l : t list) : (t list, error) result = + try + IntUniqueMap.of_unique_list (fun dev -> dev.position) l |> ignore ; + Ok l + with _ -> Error Duplicate_position + + let validate_no_duplicate_mac (l : t list) : (t list, error) result = + try + MacaddrUniqueMap.of_unique_list (fun dev -> dev.mac) l |> ignore ; + Ok l + with _ -> Error Duplicate_mac_address + + let validate_order (l : t list) : (t list, error) result = + let* l = validate_no_duplicate_position l in + validate_no_duplicate_mac l + + let assign_position (dev : Dev.t) position = + Network_interface. + {name= dev.name; mac= dev.mac; pci= dev.pci; position; present= true} +end + +type ordering = OrderedDev.t list * Dev.t list + +let assign_position_by_rules ~(rules : Rule.t list) + ((ordered, unordered) : ordering) : ordering = + List.fold_left + (fun (acc_ordered, acc_unordered) (dev : Dev.t) -> + match + List.find_opt + (Rule.matches ~mac:dev.mac ~pci:dev.pci ~label:dev.name) + rules + with + | Some {position; _} -> + debug "%s: assign position: %d <- %s" __FUNCTION__ position + (Dev.to_string dev) ; + let dev' = OrderedDev.assign_position dev position in + (dev' :: acc_ordered, acc_unordered) + | None -> + (acc_ordered, dev :: acc_unordered) + ) + (ordered, []) unordered + +let assign_position_by_mac ~(last_order : OrderedDev.t list) + ((ordered, unordered) : ordering) : ordering = + List.fold_left + (fun (acc_ordered, acc_unordered) (dev : Dev.t) -> + match List.find_opt (fun dev' -> dev.mac = dev'.mac) last_order with + | Some {position; _} -> + (* Found a MAC matched network device in [last_order]: assign the position as last. *) + debug "%s: assign position: %d <- %s" __FUNCTION__ position + (Dev.to_string dev) ; + let dev' = OrderedDev.assign_position dev position in + (dev' :: acc_ordered, acc_unordered) + | None -> + debug "%s: skip %s" __FUNCTION__ (Dev.to_string dev) ; + (* a new network device: leave it unassigned at the moment *) + (acc_ordered, dev :: acc_unordered) + ) + (ordered, []) unordered + +let assign_position_by_pci ~(last_pcis : OrderedDev.t list PciaddrMap.t) + ~(curr_macs : MacaddrSet.t) ((ordered, unordered) : ordering) : ordering = + List.fold_left + (fun (acc_ordered, acc_unordered) (dev : Dev.t) -> + match (dev, PciaddrMap.find_opt dev.pci last_pcis) with + | Dev.{multi_nic= false; _}, Some [{position; mac; _}] -> ( + (* Not a multi-nic function. + And found a ever-seen device which had located at the same PCI address. *) + match MacaddrSet.find_opt mac curr_macs with + | None -> + (* The ever-seen device has been removed - not in current MAC addresses. + This is a replacement: assign the position as before. *) + debug "%s: assign position: %d <- %s" __FUNCTION__ position + (Dev.to_string dev) ; + let dev' = OrderedDev.assign_position dev position in + (dev' :: acc_ordered, acc_unordered) + | Some _ -> + (* The ever-seen device is still presenting this time. + It must have been positioned via the MAC address already. But its PCI address changes. *) + debug "%s: skip (seen) %s" __FUNCTION__ (Dev.to_string dev) ; + (acc_ordered, dev :: acc_unordered) + ) + | _ -> + debug "%s: skip %s" __FUNCTION__ (Dev.to_string dev) ; + (acc_ordered, dev :: acc_unordered) + ) + (ordered, []) unordered + +let assign_position_for_multinic ~(last_pcis : OrderedDev.t list PciaddrMap.t) + ~(assigned_positions : IntSet.t) (multinics : Dev.t list) : ordering = + PciaddrMap.fold + (fun pci devs (acc_ordered, acc_unordered) -> + (* The [last_devs] are the devices which were previously occupying the PCI address. + The positions of these devices are called the "last positions". *) + let last_devs = + PciaddrMap.find_opt pci last_pcis |> Option.value ~default:[] + in + match + ( List.exists + (fun {position; _} -> IntSet.mem position assigned_positions) + last_devs + , List.length devs = List.length last_devs + ) + with + | false, true -> + (* All the "last positions" have not been assigned yet. + And no change on the number of devices sharing the PCI address. + Re-assign the "last positions" by sorting with MAC addresses. *) + let devs' = List.sort Dev.compare_on_mac devs in + let lasts' = List.sort OrderedDev.compare_on_mac last_devs in + let ordered_devs = + List.rev_map2 + (fun dev last -> + let position = last.position in + debug "%s: assign position: %d <- %s" __FUNCTION__ position + (Dev.to_string dev) ; + OrderedDev.assign_position dev position + ) + devs' lasts' + in + (List.rev_append ordered_devs acc_ordered, acc_unordered) + | true, _ + (* Some of the "last positions" have been assigned by MAC address. + But there are some new ones reported this time. *) + | false, false -> + (* This means at this PCI address, the devices have completely + different MAC addresses and the number of devices changes as well. + Consider them being new devices. *) + + (* Collect all BIOS eth order numbers *) + let bios_eth_orders = + devs + |> List.map (fun dev -> dev.Dev.bios_eth_order) + |> List.sort compare + in + (* Re-assgin the BIOS eth order by zipping the BIOS eth order and MAC order. *) + let unordered_devs = + devs + |> List.stable_sort Dev.compare_on_mac + |> List.rev_map2 + (fun bios_eth_order dev -> Dev.{dev with bios_eth_order}) + bios_eth_orders + in + (acc_ordered, List.rev_append unordered_devs acc_unordered) + ) + (PciaddrMultiMap.of_list (fun v -> v.Dev.pci) multinics) + ([], []) + +let assign_position_for_remaining ~(max_position : int) (devs : Dev.t list) : + OrderedDev.t list = + List.fold_left + (fun (acc_pos, acc) (dev : Dev.t) -> + let pos = acc_pos + 1 in + debug "%s: assign position: %d <- %s" __FUNCTION__ pos (Dev.to_string dev) ; + let dev' = OrderedDev.assign_position dev pos in + (pos, dev' :: acc) + ) + (max_position, []) devs + |> snd + +let sort' ~(currents : Dev.t list) ~(rules : Rule.t list) + ~(last_order : OrderedDev.t list) : (OrderedDev.t list, error) result = + let open Dev in + let curr_macs = + currents |> List.map (fun dev -> dev.mac) |> MacaddrSet.of_list + in + let last_pcis = OrderedDev.map_by_pci last_order in + let ordered, unordered = + ([], currents) + |> assign_position_by_rules ~rules + |> assign_position_by_mac ~last_order + |> assign_position_by_pci ~last_pcis ~curr_macs + in + let ordered, remaining = + (* Split the unordered list into two: + multinics - the devices each share a PCI BUS ID with others (multinic function). + remaining - the deivces each occupy a PCI BUS ID exclusively. *) + let multinics, remaining = + unordered |> List.partition (fun dev -> dev.multi_nic) + in + let assigned_positions = + ordered |> List.map (fun dev -> dev.position) |> IntSet.of_list + in + let ordered', unordered' = + assign_position_for_multinic ~last_pcis ~assigned_positions multinics + in + (List.rev_append ordered ordered', List.rev_append remaining unordered') + in + let* m = OrderedDev.map_by_position ordered in + let removed = + last_order + |> List.filter_map (fun (dev : OrderedDev.t) -> + if MacaddrSet.mem dev.mac curr_macs then + None + else + Some {dev with present= false} + ) + |> List.filter (fun dev -> not (IntMap.mem dev.position m)) + in + let ordered = List.rev_append ordered removed in + let max_position = + List.fold_left + (fun max dev -> if max < dev.position then dev.position else max) + (-1) ordered + in + let new_order = + remaining + |> List.stable_sort compare_on_bios_eth_order + |> assign_position_for_remaining ~max_position + |> List.rev_append ordered + in + OrderedDev.validate_order new_order + +let sort last_order = + let* rules = Rule.read ~path:initial_rules_file_path in + let rules, last_order = + if last_order = [] then + (rules, []) + else + ([], last_order) + in + let* currents = Dev.get_all () in + currents + |> List.iter (fun x -> debug "%s current: %s" __FUNCTION__ (Dev.to_string x)) ; + let* new_order = sort' ~currents ~rules ~last_order in + new_order + |> List.iter (fun x -> + debug "%s new order: %s" __FUNCTION__ (OrderedDev.to_string x) + ) ; + + (* Find the NICs whose name changes *) + let* m = OrderedDev.map_by_position last_order in + let changes = + List.fold_left + (fun acc {position; name= curr; _} -> + match IntMap.find_opt position m with + | Some {name= last; _} when last <> curr -> + (last, curr) :: acc + | _ -> + acc + ) + [] new_order + in + Ok (new_order, changes) diff --git a/ocaml/networkd/lib/network_device_order.mli b/ocaml/networkd/lib/network_device_order.mli new file mode 100644 index 00000000000..81d0d23f96b --- /dev/null +++ b/ocaml/networkd/lib/network_device_order.mli @@ -0,0 +1,140 @@ +(* + * 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. + *) + +(** Generate an order for host network devices and keep the order as stable as + possible. *) + +type error = + | Pci_addr_parse_error of string + | Mac_addr_parse_error of string + | Rule_parse_error of string + | Missing_biosdevname_key of string + | Duplicate_mac_address + | Duplicate_position + | Invalid_biosdevname_key_value of (string * string) + +(** PCI address in format SBDF: domain:bus:device:function *) +module Pciaddr : sig + (** Type of the PCI address *) + type t = Xcp_pci.address + + val of_string : string -> (t, error) result + (** [of_string s] returns [Ok pci] where [pci] is the PCI address converted + from [s]. Otherwise, it returns [Error error] whenever [s] can't be + parsed. [error] is for the parsing failure. *) + + val compare : t -> t -> int + (** [compare x y] return 0 if [x] is equal to [y]; a negative integer if [x] + is less than [y], and a positive integer if [x] is greater than [y]. *) +end + +module Macaddr : sig + type t = Macaddr.t + + val of_string : string -> (t, error) result + (** [of_string s] returns [Ok pci] where [pci] is the PCI address converted + from [s]. Otherwise, it returns [Error error] whenever [s] can't be + parsed. [error] is for the parsing failure. *) +end + +(** A rule specifies a position for a network device which can be identified by + MAC address, PCI address, or name label. *) +module Rule : sig + type index = + | Mac_addr of Macaddr.t + | Pci_addr of Pciaddr.t + | Label of string (** Type of mapping *) + + (** Type of one mapping configuration. *) + type t = {position: int; index: index} + + val read : path:string -> (t list, error) result + (** [read ~path] returns either [Ok rules], where [rules] are parsed from the + content of the file at [path], or [Error error], where [error] is the + reason for the parsing failure. The file at [path] contains lines in the + following format: + :="", where + label: means the is the name label of the device, + mac: means the is the MAC address of the device like + 00:02:C9:ED:FD:F0, + pci: means the is the PCI address (in SBDF format) of the device + locates at, like 0000:05:00.0. *) + + val matches : mac:Macaddr.t -> pci:Pciaddr.t -> label:string -> t -> bool + (** [true] if any of the [mac], [pci], or [label] meets the rule [t]. *) +end + +(** A network device recognized by biosdevname *) +module Dev : sig + (** Type of an network deivce parsed from the output of biosdevname. *) + type t = { + name: Network_interface.iface + ; mac: Network_interface.mac_address + ; pci: Xcp_pci.address + ; bios_eth_order: int + (** The in eth which is the value of "BIOS device" from output + of [biosdevname --policy all_ethN], is greater than or equal to 0. + *) + ; multi_nic: bool + (** [true] if there are other devices locate at the same PCI address. + Otherwise [false]. *) + } + + val get_all : unit -> (t list, error) result + (** [get_all ()] returns [Ok l], where l is a list of network devices parsed + from the output of biosdevname. Otherwise, it returns [Error error], where + [error] is the reason for the parsing failure. *) +end + +module IntMap : Map.S with type key = int + +(** A network device which has been assigned a postion in the order by sorting *) +module OrderedDev : sig + (** Type of an ordered network device. *) + type t = Network_interface.ordered_iface + + val map_by_position : t list -> (t IntMap.t, error) result + (** [map_by_position lst] returns [Ok map], where [map] is a map with values + from [lst] and their keys are positions. It returns + [Error Duplicate_position] if more than one value in [lst] has the same + position. *) + + val validate_order : t list -> (t list, error) result + (** [validate_order devs] returns [Ok lst], where [lst] is a list of devices + without duplicate MAC addresses or duplicate positions. Otherwise, + [Error error] is returned, where [error] is either Duplicate_position or + Duplicate_mac_address. *) + + val assign_position : Dev.t -> int -> Network_interface.ordered_iface + (** [assign_position dev pos] returns a device with [pos] assigned. *) +end + +val sort : + OrderedDev.t list + -> (OrderedDev.t list * (string * string) list, error) result +(** [sort last_order] sorts and generates an order based on [last_order]. It + returns [Ok (order, changes)], where [order] is a list of devices each + assigned unique positions, and [changes] is a list of pairs like + [(old, new)]. In these pairs, [old] is the name of the device from the + previous call to [sort], and [new] is the current name of the device. + It returns [Error error] when it fails to generate an order.[error] is the + reason for the failure. *) + +(* Below is exposed only for unit tests *) + +val sort' : + currents:Dev.t list + -> rules:Rule.t list + -> last_order:OrderedDev.t list + -> (OrderedDev.t list, error) result From bdb023ab377efd1e0dcaec0afc3524aa21e47b8d Mon Sep 17 00:00:00 2001 From: Ming Lu Date: Fri, 21 Mar 2025 09:22:35 +0000 Subject: [PATCH 3/3] CP-44103: Unit tests for ordering network devices The "test_network_device_order_inherited.ml" is inherited from the interface-rename functionality. Signed-off-by: Ming Lu --- ocaml/networkd/test/dune | 3 + ocaml/networkd/test/network_test.ml | 6 +- .../test/test_network_device_order.ml | 478 ++++++++++++ .../test/test_network_device_order.mli | 15 + .../test_network_device_order_inherited.ml | 702 ++++++++++++++++++ .../test_network_device_order_inherited.mli | 15 + 6 files changed, 1218 insertions(+), 1 deletion(-) create mode 100644 ocaml/networkd/test/test_network_device_order.ml create mode 100644 ocaml/networkd/test/test_network_device_order.mli create mode 100644 ocaml/networkd/test/test_network_device_order_inherited.ml create mode 100644 ocaml/networkd/test/test_network_device_order_inherited.mli diff --git a/ocaml/networkd/test/dune b/ocaml/networkd/test/dune index b3519ce2ec5..0141d108c69 100644 --- a/ocaml/networkd/test/dune +++ b/ocaml/networkd/test/dune @@ -6,9 +6,12 @@ astring fmt + macaddr networklibs rpclib.core rpclib.json + xapi-idl + xapi-idl.network xapi-log xapi-test-utils) ) diff --git a/ocaml/networkd/test/network_test.ml b/ocaml/networkd/test/network_test.ml index 601fe8055b8..e3c8029c797 100644 --- a/ocaml/networkd/test/network_test.ml +++ b/ocaml/networkd/test/network_test.ml @@ -15,4 +15,8 @@ let () = Debug.log_to_stdout () ; Alcotest.run "base_suite" - (Network_test_lacp_properties.suite @ Test_jsonrpc_client.tests) + (Network_test_lacp_properties.suite + @ Test_jsonrpc_client.tests + @ Test_network_device_order_inherited.tests + @ Test_network_device_order.tests + ) diff --git a/ocaml/networkd/test/test_network_device_order.ml b/ocaml/networkd/test/test_network_device_order.ml new file mode 100644 index 00000000000..b3fd21b89c3 --- /dev/null +++ b/ocaml/networkd/test/test_network_device_order.ml @@ -0,0 +1,478 @@ +(* + * 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. + *) + +open Network_device_order +open Network_interface + +let pci_addr0 = Pciaddr.of_string "0000:01:0f.0" |> Result.get_ok + +let pci_addr1 = Pciaddr.of_string "0000:01:0f.1" |> Result.get_ok + +let pci_addr2 = Pciaddr.of_string "0000:01:0f.2" |> Result.get_ok + +let pci_addr3 = Pciaddr.of_string "0000:01:0f.3" |> Result.get_ok + +let pci_addr4 = Pciaddr.of_string "0000:05:0f.0" |> Result.get_ok + +let mac_addr0 = Macaddr.of_string "ec:f4:bb:e6:d7:b8" |> Result.get_ok + +let mac_addr1 = Macaddr.of_string "ec:f4:bb:e6:d7:b9" |> Result.get_ok + +let mac_addr2 = Macaddr.of_string "ec:f4:bb:e6:d7:ba" |> Result.get_ok + +let mac_addr3 = Macaddr.of_string "ec:f4:bb:e6:d7:bb" |> Result.get_ok + +let mac_addr4 = Macaddr.of_string "00:02:c9:ed:fd:f0" |> Result.get_ok + +let mac_addr5 = Macaddr.of_string "00:02:c9:ed:fd:f1" |> Result.get_ok + +let name0 = "eno1" + +let name1 = "eno2" + +let name2 = "eno3" + +let name3 = "eno4" + +let name4 = "enp5s0" + +let name5 = "enp5s0d1" + +let seen_dev0 = + {name= name0; pci= pci_addr0; mac= mac_addr0; position= 0; present= true} + +let seen_dev1 = + {name= name1; pci= pci_addr1; mac= mac_addr1; position= 1; present= true} + +let seen_dev2 = + {name= name2; pci= pci_addr2; mac= mac_addr2; position= 2; present= true} + +let seen_dev3 = + {name= name3; pci= pci_addr3; mac= mac_addr3; position= 3; present= true} + +let seen_dev4 = + {name= name4; pci= pci_addr4; mac= mac_addr4; position= 4; present= true} + +let seen_dev5 = + {name= name5; pci= pci_addr4; mac= mac_addr5; position= 5; present= true} + +let dev0 = + { + Dev.name= name0 + ; pci= pci_addr0 + ; mac= mac_addr0 + ; bios_eth_order= 0 + ; multi_nic= false + } + +let dev1 = + { + Dev.name= name1 + ; pci= pci_addr1 + ; mac= mac_addr1 + ; bios_eth_order= 1 + ; multi_nic= false + } + +let dev2 = + { + Dev.name= name2 + ; pci= pci_addr2 + ; mac= mac_addr2 + ; bios_eth_order= 2 + ; multi_nic= false + } + +let dev3 = + { + Dev.name= name3 + ; pci= pci_addr3 + ; mac= mac_addr3 + ; bios_eth_order= 3 + ; multi_nic= false + } + +let dev4 = + { + Dev.name= name4 + ; pci= pci_addr4 + ; mac= mac_addr4 + ; bios_eth_order= 4 + ; multi_nic= true (* multinic: share PCI address with dev4 *) + } + +let dev5 = + { + Dev.name= name5 + ; pci= pci_addr4 + ; mac= mac_addr5 + ; bios_eth_order= 5 + ; multi_nic= true (* multinic: share PCI address with dev4 *) + } + +let plug dev devices = List.cons dev devices + +let unplug d devices = List.filter (fun dev -> dev.Dev.mac <> d.Dev.mac) devices + +let pos_of_mac mac order = + match List.find_opt (fun dev -> dev.mac = mac) order with + | Some {position; _} -> + position + | _ -> + -1 + +let present_of_mac mac order = + match List.find_opt (fun dev -> dev.mac = mac) order with + | Some {present; _} -> + present + | _ -> + failwith "Can't find the device!" + +let test_postion_and_present expected_position expected_present dev order = + let mac = dev.Dev.mac in + let name = Format.asprintf "Position assigned for %a" Macaddr.pp mac in + Alcotest.(check int) name expected_position (pos_of_mac mac order) ; + Alcotest.(check bool) name expected_present (present_of_mac mac order) + +let test_default () = + let currents = [dev0; dev1; dev2; dev3; dev4; dev5] in + let order = sort' ~currents ~rules:[] ~last_order:[] in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "6 devices in the order" 6 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 3 true dev3 order ; + (* The dev4 and dev5 are multinic functions. To assign initial positions, + they are sorted by MAC addresses. *) + test_postion_and_present 4 true dev4 order ; + test_postion_and_present 5 true dev5 order + +let test_unstable_bios_eth_order () = + let dev4 = {dev4 with mac= mac_addr5; bios_eth_order= 9} in + let dev5 = {dev5 with mac= mac_addr4; bios_eth_order= 10} in + let currents = [dev0; dev1; dev2; dev3; dev4; dev5] in + let order = sort' ~currents ~rules:[] ~last_order:[] in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "6 devices in the order" 6 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 3 true dev3 order ; + (* The dev4 and dev5 are multinic functions. To assign initial positions, + they are sorted by MAC addresses. *) + test_postion_and_present 5 true dev4 order ; + test_postion_and_present 4 true dev5 order + +let test_initial_rules_via_mac () = + let currents = [dev0; dev1; dev2; dev3; dev4; dev5] in + let rules = + Rule. + [ + {position= 0; index= Mac_addr mac_addr5} + ; {position= 1; index= Mac_addr mac_addr4} + ; {position= 2; index= Mac_addr mac_addr3} + ; {position= 3; index= Mac_addr mac_addr2} + ; {position= 4; index= Mac_addr mac_addr1} + ; {position= 5; index= Mac_addr mac_addr0} + ] + in + let order = sort' ~currents ~rules ~last_order:[] in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "6 devices in the order" 6 (List.length order) ; + test_postion_and_present 5 true dev0 order ; + test_postion_and_present 4 true dev1 order ; + test_postion_and_present 3 true dev2 order ; + test_postion_and_present 2 true dev3 order ; + test_postion_and_present 1 true dev4 order ; + test_postion_and_present 0 true dev5 order + +let test_initial_rules_via_label () = + let currents = [dev0; dev1; dev2; dev3; dev4; dev5] in + let rules = + Rule. + [ + {position= 0; index= Label name5} + ; {position= 1; index= Label name4} + ; {position= 2; index= Label name3} + ; {position= 3; index= Label name2} + ; {position= 4; index= Label name1} + ; {position= 5; index= Label name0} + ] + in + let order = sort' ~currents ~rules ~last_order:[] in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "6 devices in the order" 6 (List.length order) ; + test_postion_and_present 5 true dev0 order ; + test_postion_and_present 4 true dev1 order ; + test_postion_and_present 3 true dev2 order ; + test_postion_and_present 2 true dev3 order ; + test_postion_and_present 1 true dev4 order ; + test_postion_and_present 0 true dev5 order + +let test_replacement () = + let mac_addr0' = Macaddr.of_string "fc:f4:bb:e6:d7:b8" |> Result.get_ok in + let mac_addr1' = Macaddr.of_string "fc:f4:bb:e6:d7:b9" |> Result.get_ok in + let dev0' = + { + Dev.name= "eno10" + ; pci= pci_addr0 + ; mac= mac_addr0' + ; bios_eth_order= + 1 (* this order is not expected to take effect in this case *) + ; multi_nic= false + } + in + let dev1' = + { + Dev.name= "eno11" + ; pci= pci_addr1 + ; mac= mac_addr1' + ; bios_eth_order= + 0 (* this order is not expected to take effect in this case *) + ; multi_nic= false + } + in + let last_order = + [seen_dev0; seen_dev1; seen_dev2; seen_dev3; seen_dev4; seen_dev5] + in + let currents = + [dev0; dev1; dev2; dev3; dev4; dev5] + |> unplug dev0 + |> plug dev0' + |> unplug dev1 + |> plug dev1' + in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + + Alcotest.(check int) "6 devices in the order" 6 (List.length order) ; + + test_postion_and_present 0 true dev0' order ; + test_postion_and_present 1 true dev1' order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 3 true dev3 order ; + test_postion_and_present 4 true dev4 order ; + test_postion_and_present 5 true dev5 order + +let test_adding () = + let pci_addr6 = Pciaddr.of_string "0000:06:0f.0" |> Result.get_ok in + let mac_addr6 = Macaddr.of_string "fc:f4:bb:e6:d7:b8" |> Result.get_ok in + let pci_addr7 = Pciaddr.of_string "0000:06:0f.1" |> Result.get_ok in + let mac_addr7 = Macaddr.of_string "fc:f4:bb:e6:d7:b9" |> Result.get_ok in + let dev6 = + { + Dev.name= "eno6" + ; pci= pci_addr6 + ; mac= mac_addr6 + ; bios_eth_order= 1 (* This impacts the initial position *) + ; multi_nic= false + } + in + let dev7 = + { + Dev.name= "eno7" + ; pci= pci_addr7 + ; mac= mac_addr7 + ; bios_eth_order= 0 (* This impacts the initial position *) + ; multi_nic= false + } + in + let last_order = + [seen_dev0; seen_dev1; seen_dev2; seen_dev3; seen_dev4; seen_dev5] + in + (* Add two devices *) + let currents = + [dev0; dev1; dev2; dev3; dev4; dev5] |> plug dev6 |> plug dev7 + in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "8 devices in the order" 8 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 3 true dev3 order ; + test_postion_and_present 4 true dev4 order ; + test_postion_and_present 5 true dev5 order ; + (* The positions of newly added devices are impacted by the bios_eth_order *) + test_postion_and_present 6 true dev7 order ; + test_postion_and_present 7 true dev6 order + +let test_removing () = + let last_order = + [seen_dev0; seen_dev1; seen_dev2; seen_dev3; seen_dev4; seen_dev5] + in + (* Remove two devices *) + let currents = + [dev0; dev1; dev2; dev3; dev4; dev5] |> unplug dev0 |> unplug dev1 + in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + + Alcotest.(check int) "6 devices in the order" 6 (List.length order) ; + test_postion_and_present 0 false dev0 order ; + test_postion_and_present 1 false dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 3 true dev3 order ; + test_postion_and_present 4 true dev4 order ; + test_postion_and_present 5 true dev5 order + +let test_replug_removed () = + (* Mark the devices as removed. *) + let seen_dev0 = {seen_dev0 with present= false} in + let seen_dev1 = {seen_dev1 with present= false} in + let last_order = + [seen_dev0; seen_dev1; seen_dev2; seen_dev3; seen_dev4; seen_dev5] + in + let currents = [dev2; dev3; dev4; dev5] |> plug dev0 |> plug dev1 in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "6 devices in the order" 6 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 3 true dev3 order ; + test_postion_and_present 4 true dev4 order ; + test_postion_and_present 5 true dev5 order + +let test_multi_nic_inplace_reorder () = + (* The MAC addresses of multi_nic functions change *) + let mac_addr4' = Macaddr.of_string "01:02:c9:ed:fd:f0" |> Result.get_ok in + let mac_addr5' = Macaddr.of_string "01:02:c9:ed:fd:f1" |> Result.get_ok in + let dev4' = Dev.{dev4 with mac= mac_addr4'; bios_eth_order= 5} in + let dev5' = Dev.{dev5 with mac= mac_addr5'; bios_eth_order= 4} in + let last_order = + [seen_dev0; seen_dev1; seen_dev2; seen_dev3; seen_dev4; seen_dev5] + in + let currents = [dev0; dev1; dev2; dev3; dev4'; dev5'] in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "6 devices in the order" 6 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 3 true dev3 order ; + test_postion_and_present 4 true dev4' order ; + test_postion_and_present 5 true dev5' order + +let test_multi_nic_new_devices () = + let mac_addr6 = Macaddr.of_string "01:02:c9:ed:fd:f0" |> Result.get_ok in + let mac_addr7 = Macaddr.of_string "01:02:c9:ed:fd:f1" |> Result.get_ok in + let dev6 = + Dev. + { + name= "enp5s0d2" + ; pci= pci_addr4 + ; mac= mac_addr6 + ; bios_eth_order= 1 + ; multi_nic= true (* multinic: share PCI address with dev4 *) + } + in + let dev7 = + Dev. + { + name= "enp5s0d3" + ; pci= pci_addr4 + ; mac= mac_addr7 + ; bios_eth_order= 0 + ; multi_nic= true (* multinic: share PCI address with dev4*) + } + in + (* New devices are reported on the same PCI address. + It's equivalent to plugging new devices but locate at the same PCI address. *) + let last_order = + [seen_dev0; seen_dev1; seen_dev2; seen_dev3; seen_dev4; seen_dev5] + in + let currents = + [dev0; dev1; dev2; dev3; dev4; dev5] |> plug dev6 |> plug dev7 + in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "8 devices in the order" 8 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 3 true dev3 order ; + test_postion_and_present 4 true dev4 order ; + test_postion_and_present 5 true dev5 order ; + test_postion_and_present 6 true dev6 order ; + test_postion_and_present 7 true dev7 order + +let test_pci_changes () = + let move_bus_by_1 pci_addr = Xcp_pci.{pci_addr with bus= pci_addr.bus + 1} in + let last_order = + [seen_dev0; seen_dev1; seen_dev2; seen_dev3; seen_dev4; seen_dev5] + in + let currents = + [dev0; dev1; dev2; dev3; dev4; dev5] + |> List.map (fun dev -> Dev.{dev with pci= move_bus_by_1 dev.pci}) + in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "6 devices in the order" 6 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 3 true dev3 order ; + test_postion_and_present 4 true dev4 order ; + test_postion_and_present 5 true dev5 order + +let test_pci_addr_compare () = + let addr0 = Pciaddr.of_string "0000:01:0e.0" |> Result.get_ok in + let addr1 = Pciaddr.of_string "0000:01:0e.0" |> Result.get_ok in + let addr2 = Pciaddr.of_string "0000:01:0e.2" |> Result.get_ok in + let addr3 = Pciaddr.of_string "0000:01:0e.3" |> Result.get_ok in + let addr4 = Pciaddr.of_string "0000:01:0f.0" |> Result.get_ok in + let addr5 = Pciaddr.of_string "0000:02:0f.0" |> Result.get_ok in + let addr6 = Pciaddr.of_string "0001:02:0f.0" |> Result.get_ok in + Alcotest.(check bool) "equal" true (Pciaddr.compare addr0 addr1 = 0) ; + Alcotest.(check bool) "less than" true (Pciaddr.compare addr0 addr2 < 0) ; + Alcotest.(check bool) "greater than" true (Pciaddr.compare addr3 addr2 > 0) ; + Alcotest.(check bool) "greater than" true (Pciaddr.compare addr4 addr3 > 0) ; + Alcotest.(check bool) "greater than" true (Pciaddr.compare addr5 addr4 > 0) ; + Alcotest.(check bool) "less than" true (Pciaddr.compare addr6 addr5 > 0) + +let tests = + [ + ( "test_known_cases" + , [ + ("test_default", `Quick, test_default) + ; ("test_unstable_bios_eth_order", `Quick, test_unstable_bios_eth_order) + ; ("test_initial_mapping_via_mac", `Quick, test_initial_rules_via_mac) + ; ("test_initial_mapping_via_name", `Quick, test_initial_rules_via_label) + ; ("test_replacement", `Quick, test_replacement) + ; ("test_adding", `Quick, test_adding) + ; ("test_removing", `Quick, test_removing) + ; ("test_replug_removed", `Quick, test_replug_removed) + ; ( "test_multi_nic_inplace_reorder" + , `Quick + , test_multi_nic_inplace_reorder + ) + ; ("test_multi_nic_new_devices", `Quick, test_multi_nic_new_devices) + ; ("test_pci_changes", `Quick, test_pci_changes) + ; ("test_pci_addr_compare", `Quick, test_pci_addr_compare) + ] + ) + ] diff --git a/ocaml/networkd/test/test_network_device_order.mli b/ocaml/networkd/test/test_network_device_order.mli new file mode 100644 index 00000000000..c32d2a7e66b --- /dev/null +++ b/ocaml/networkd/test/test_network_device_order.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/networkd/test/test_network_device_order_inherited.ml b/ocaml/networkd/test/test_network_device_order_inherited.ml new file mode 100644 index 00000000000..1024c4c87f1 --- /dev/null +++ b/ocaml/networkd/test/test_network_device_order_inherited.ml @@ -0,0 +1,702 @@ +(* + * 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. + *) + +open Network_device_order +open Network_interface + +let pos_of_mac mac_addr order = + match List.find_opt (fun d -> d.mac = mac_addr) order with + | Some {position; _} -> + position + | _ -> + -1 + +let present_of_mac mac order = + match List.find_opt (fun dev -> dev.mac = mac) order with + | Some {present; _} -> + present + | _ -> + failwith "Can't find the device!" + +let test_postion_and_present expected_position expected_present dev order = + let mac = dev.Dev.mac in + let name = Format.asprintf "Position assigned for %a" Macaddr.pp mac in + Alcotest.(check int) name expected_position (pos_of_mac mac order) ; + Alcotest.(check bool) name expected_present (present_of_mac mac order) + +let test_newhw_norules_1eth () = + let mac_addr = Macaddr.of_string "ab:cd:ef:12:34:56" |> Result.get_ok in + let dev0 = + { + Dev.name= "side-12-eth1" + ; pci= Pciaddr.of_string "0000:00:0f.0" |> Result.get_ok + ; mac= mac_addr + ; bios_eth_order= 0 + ; multi_nic= false + } + in + let currents = [dev0] in + let order = sort' ~currents ~rules:[] ~last_order:[] in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "1 device in the order" 1 (List.length order) ; + test_postion_and_present 0 true dev0 order + +let test_newhw_norules_2eth () = + let dev0 = + { + Dev.name= "side-12-eth1" + ; pci= Pciaddr.of_string "0000:00:0f.0" |> Result.get_ok + ; mac= Macaddr.of_string "ab:cd:ef:12:34:56" |> Result.get_ok + ; bios_eth_order= 0 + ; multi_nic= false + } + in + let dev1 = + { + Dev.name= "side-33-eth0" + ; pci= Pciaddr.of_string "0000:00:01.0" |> Result.get_ok + ; mac= Macaddr.of_string "ab:cd:ef:12:34:57" |> Result.get_ok + ; bios_eth_order= 1 + ; multi_nic= false + } + in + let currents = [dev0; dev1] in + let order = sort' ~currents ~rules:[] ~last_order:[] in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "2 devices in the order" 2 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order + +let test_newhw_2srule_2eth () = + let mac_addr0 = Macaddr.of_string "12:34:56:78:90:12" |> Result.get_ok in + let mac_addr1 = Macaddr.of_string "ab:cd:ef:12:34:56" |> Result.get_ok in + let rules = + Rule. + [ + {position= 0; index= Mac_addr mac_addr1} + ; {position= 1; index= Mac_addr mac_addr0} + ] + in + let dev0 = + { + Dev.name= "eth0" + ; pci= Pciaddr.of_string "0000:00:01.0" |> Result.get_ok + ; mac= mac_addr0 + ; bios_eth_order= 1 + ; multi_nic= false + } + in + let dev1 = + { + Dev.name= "side-12-eth1" + ; pci= Pciaddr.of_string "0000:00:0f.0" |> Result.get_ok + ; mac= mac_addr1 + ; bios_eth_order= 0 + ; multi_nic= false + } + in + let currents = [dev0; dev1] in + let order = sort' ~currents ~rules ~last_order:[] in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + test_postion_and_present 1 true dev0 order ; + test_postion_and_present 0 true dev1 order + +let test_nosrules_1eth_incorrect_udev () = + let mac_addr = Macaddr.of_string "ab:cd:ef:12:34:56" |> Result.get_ok in + let pci_addr = Pciaddr.of_string "0000:00:0f.0" |> Result.get_ok in + let dev0 = + { + Dev.name= "side-12-eth0" + ; pci= pci_addr + ; mac= mac_addr + ; bios_eth_order= 0 + ; multi_nic= false + } + in + let currents = [dev0] in + let seen_dev0 = + {name= "eth2"; pci= pci_addr; mac= mac_addr; position= 3; present= true} + in + let last_order = [seen_dev0] in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + test_postion_and_present 3 true dev0 order + +let test_1srule_1eth_1last_correct_udev () = + let mac_addr = Macaddr.of_string "ab:cd:ef:12:34:56" |> Result.get_ok in + let pci_addr = Pciaddr.of_string "0000:00:0f.0" |> Result.get_ok in + let dev0 = + { + Dev.name= "eth1" + ; pci= pci_addr + ; mac= mac_addr + ; bios_eth_order= 1 + ; multi_nic= false + } + in + let currents = [dev0] in + let rules = Rule.[{position= 0; index= Mac_addr mac_addr}] in + let seen_dev0 = + {name= "eth1"; pci= pci_addr; mac= mac_addr; position= 1; present= true} + in + let last_order = [seen_dev0] in + let order = sort' ~currents ~rules ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "1 device in the order" 1 (List.length order) ; + test_postion_and_present 0 true dev0 order + +let test_1srule_1eth_already_complete () = + let mac_addr = Macaddr.of_string "00:13:72:2d:2a:ec" |> Result.get_ok in + let pci_addr = Pciaddr.of_string "0000:04:00.0" |> Result.get_ok in + let dev0 = + { + Dev.name= "eth0" + ; pci= pci_addr + ; mac= mac_addr + ; bios_eth_order= 0 + ; multi_nic= false + } + in + let currents = [dev0] in + let rules = Rule.[{position= 0; index= Mac_addr mac_addr}] in + let order = sort' ~currents ~rules ~last_order:[] in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "1 device in the order" 1 (List.length order) ; + test_postion_and_present 0 true dev0 order + +let test_1drule_1eth_already_complete () = + let mac_addr = Macaddr.of_string "00:13:72:2d:2a:ec" |> Result.get_ok in + let pci_addr = Pciaddr.of_string "0000:04:00.0" |> Result.get_ok in + let dev0 = + { + Dev.name= "eth0" + ; pci= pci_addr + ; mac= mac_addr + ; bios_eth_order= 0 + ; multi_nic= false + } + in + let currents = [dev0] in + let seen_dev0 = + {name= "eth0"; pci= pci_addr; mac= mac_addr; position= 0; present= true} + in + let last_order = [seen_dev0] in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "1 device in the order" 1 (List.length order) ; + test_postion_and_present 0 true dev0 order + +let test_usecase1 () = + let mac_addr0 = Macaddr.of_string "01:23:45:67:89:01" |> Result.get_ok in + let mac_addr1 = Macaddr.of_string "11:23:45:67:89:01" |> Result.get_ok in + let mac_addr2 = Macaddr.of_string "21:23:45:67:89:01" |> Result.get_ok in + let mac_addr3 = Macaddr.of_string "31:23:45:67:89:01" |> Result.get_ok in + let mac_addr4 = Macaddr.of_string "41:23:45:67:89:01" |> Result.get_ok in + let pci_addr0 = Pciaddr.of_string "0000:01:00.0" |> Result.get_ok in + let pci_addr1 = Pciaddr.of_string "0000:02:00.0" |> Result.get_ok in + let pci_addr2 = Pciaddr.of_string "0000:03:00.0" |> Result.get_ok in + let pci_addr3 = Pciaddr.of_string "0000:04:00.0" |> Result.get_ok in + let pci_addr4 = Pciaddr.of_string "0000:05:00.0" |> Result.get_ok in + + let dev0 = + { + Dev.name= "eth0" + ; pci= pci_addr0 + ; mac= mac_addr0 + ; bios_eth_order= 0 + ; multi_nic= false + } + in + let dev1 = + { + Dev.name= "eth1" + ; pci= pci_addr1 + ; mac= mac_addr1 + ; bios_eth_order= 1 + ; multi_nic= false + } + in + let dev2 = + { + Dev.name= "eth2" + ; pci= pci_addr2 + ; mac= mac_addr2 + ; bios_eth_order= 2 + ; multi_nic= false + } + in + let dev3 = + { + Dev.name= "eth3" + ; pci= pci_addr3 + ; mac= mac_addr3 + ; bios_eth_order= 3 + ; multi_nic= false + } + in + let dev4 = + { + Dev.name= "eth4" + ; pci= pci_addr4 + ; mac= mac_addr4 + ; bios_eth_order= 4 + ; multi_nic= false + } + in + let currents = [dev0; dev1; dev2; dev3; dev4] in + let seen_dev0 = + {name= "eth0"; pci= pci_addr0; mac= mac_addr0; position= 0; present= true} + in + let seen_dev1 = + {name= "eth1"; pci= pci_addr1; mac= mac_addr1; position= 1; present= true} + in + let seen_dev2 = + {name= "eth2"; pci= pci_addr2; mac= mac_addr2; position= 2; present= true} + in + let seen_dev3 = + {name= "eth3"; pci= pci_addr3; mac= mac_addr3; position= 3; present= true} + in + let seen_dev4 = + {name= "eth4"; pci= pci_addr4; mac= mac_addr4; position= 4; present= true} + in + let last_order = [seen_dev0; seen_dev1; seen_dev2; seen_dev3; seen_dev4] in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "5 devices in the order" 5 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 3 true dev3 order ; + test_postion_and_present 4 true dev4 order + +let test_usecase5 () = + let mac_addr0' = Macaddr.of_string "02:23:45:67:89:01" |> Result.get_ok in + let mac_addr1' = Macaddr.of_string "12:23:45:67:89:01" |> Result.get_ok in + let mac_addr2' = Macaddr.of_string "22:23:45:67:89:01" |> Result.get_ok in + let mac_addr3' = Macaddr.of_string "32:23:45:67:89:01" |> Result.get_ok in + let mac_addr4' = Macaddr.of_string "42:23:45:67:89:01" |> Result.get_ok in + + let mac_addr0 = Macaddr.of_string "01:23:45:67:89:01" |> Result.get_ok in + let mac_addr1 = Macaddr.of_string "11:23:45:67:89:01" |> Result.get_ok in + let mac_addr2 = Macaddr.of_string "21:23:45:67:89:01" |> Result.get_ok in + let mac_addr3 = Macaddr.of_string "31:23:45:67:89:01" |> Result.get_ok in + let mac_addr4 = Macaddr.of_string "41:23:45:67:89:01" |> Result.get_ok in + + let pci_addr0 = Pciaddr.of_string "0000:01:00.0" |> Result.get_ok in + let pci_addr1 = Pciaddr.of_string "0000:02:00.0" |> Result.get_ok in + let pci_addr2 = Pciaddr.of_string "0000:03:00.0" |> Result.get_ok in + let pci_addr3 = Pciaddr.of_string "0000:04:00.0" |> Result.get_ok in + let pci_addr4 = Pciaddr.of_string "0000:05:00.0" |> Result.get_ok in + + let dev0 = + { + Dev.name= "side-1-eth0" + ; pci= pci_addr0 + ; mac= mac_addr0' + ; bios_eth_order= 0 + ; multi_nic= false + } + in + let dev1 = + { + Dev.name= "side-34-eth1" + ; pci= pci_addr1 + ; mac= mac_addr1' + ; bios_eth_order= 1 + ; multi_nic= false + } + in + let dev2 = + { + Dev.name= "side-71-eth2" + ; pci= pci_addr2 + ; mac= mac_addr2' + ; bios_eth_order= 2 + ; multi_nic= false + } + in + let dev3 = + { + Dev.name= "side-3012-eth3" + ; pci= pci_addr3 + ; mac= mac_addr3' + ; bios_eth_order= 3 + ; multi_nic= false + } + in + let dev4 = + { + Dev.name= "side-4332-eth4" + ; pci= pci_addr4 + ; mac= mac_addr4' + ; bios_eth_order= 4 + ; multi_nic= false + } + in + let currents = [dev0; dev1; dev2; dev3; dev4] in + let seen_dev0 = + {name= "eth0"; pci= pci_addr0; mac= mac_addr0; position= 0; present= true} + in + let seen_dev1 = + {name= "eth1"; pci= pci_addr1; mac= mac_addr1; position= 1; present= true} + in + let seen_dev2 = + {name= "eth2"; pci= pci_addr2; mac= mac_addr2; position= 2; present= true} + in + let seen_dev3 = + {name= "eth3"; pci= pci_addr3; mac= mac_addr3; position= 3; present= true} + in + let seen_dev4 = + {name= "eth4"; pci= pci_addr4; mac= mac_addr4; position= 4; present= true} + in + let last_order = [seen_dev0; seen_dev1; seen_dev2; seen_dev3; seen_dev4] in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "5 devices in the order" 5 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 3 true dev3 order ; + test_postion_and_present 4 true dev4 order + +let test_CA_94279 () = + let mac_addr0 = Macaddr.of_string "00:1b:21:aa:ef:f0" |> Result.get_ok in + let mac_addr1 = Macaddr.of_string "00:1b:21:aa:ef:f1" |> Result.get_ok in + let mac_addr2 = Macaddr.of_string "00:1b:21:aa:ef:f4" |> Result.get_ok in + let mac_addr3 = Macaddr.of_string "00:1b:21:aa:ef:f5" |> Result.get_ok in + let mac_addr4 = Macaddr.of_string "60:eb:69:ed:9a:16" |> Result.get_ok in + let mac_addr5 = Macaddr.of_string "60:eb:69:ed:9a:17" |> Result.get_ok in + + let pci_addr0 = Pciaddr.of_string "0000:03:00.0" |> Result.get_ok in + let pci_addr1 = Pciaddr.of_string "0000:03:00.1" |> Result.get_ok in + let pci_addr2 = Pciaddr.of_string "0000:04:00.0" |> Result.get_ok in + let pci_addr3 = Pciaddr.of_string "0000:04:00.1" |> Result.get_ok in + let pci_addr4 = Pciaddr.of_string "0000:06:00.0" |> Result.get_ok in + let pci_addr5 = Pciaddr.of_string "0000:06:00.1" |> Result.get_ok in + + let dev0 = + { + Dev.name= "side-1-eth0" + ; pci= pci_addr0 + ; mac= mac_addr0 + ; bios_eth_order= 2 + ; multi_nic= false + } + in + let dev1 = + { + Dev.name= "side-2-eth1" + ; pci= pci_addr1 + ; mac= mac_addr1 + ; bios_eth_order= 3 + ; multi_nic= false + } + in + let dev2 = + { + Dev.name= "side-3-eth2" + ; pci= pci_addr2 + ; mac= mac_addr2 + ; bios_eth_order= 4 + ; multi_nic= false + } + in + let dev3 = + { + Dev.name= "side-4-eth3" + ; pci= pci_addr3 + ; mac= mac_addr3 + ; bios_eth_order= 5 + ; multi_nic= false + } + in + let dev4 = + { + Dev.name= "side-5-eth4" + ; pci= pci_addr4 + ; mac= mac_addr4 + ; bios_eth_order= 0 + ; multi_nic= false + } + in + let dev5 = + { + Dev.name= "side-6-eth5" + ; pci= pci_addr5 + ; mac= mac_addr5 + ; bios_eth_order= 1 + ; multi_nic= false + } + in + let currents = [dev0; dev1; dev2; dev3; dev4; dev5] in + let order = sort' ~currents ~rules:[] ~last_order:[] in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "6 devices in the order" 6 (List.length order) ; + test_postion_and_present 2 true dev0 order ; + test_postion_and_present 3 true dev1 order ; + test_postion_and_present 4 true dev2 order ; + test_postion_and_present 5 true dev3 order ; + test_postion_and_present 0 true dev4 order ; + test_postion_and_present 1 true dev5 order + +let test_rshp_new_hardware () = + let mac_addr0' = Macaddr.of_string "02:23:45:67:89:01" |> Result.get_ok in + let mac_addr1' = Macaddr.of_string "12:23:45:67:89:01" |> Result.get_ok in + let mac_addr2' = Macaddr.of_string "22:23:45:67:89:01" |> Result.get_ok in + let mac_addr3' = Macaddr.of_string "32:23:45:67:89:01" |> Result.get_ok in + let mac_addr4' = Macaddr.of_string "32:23:45:67:89:02" |> Result.get_ok in + + let pci_addr0 = Pciaddr.of_string "0000:01:00.0" |> Result.get_ok in + let pci_addr1 = Pciaddr.of_string "0000:02:00.0" |> Result.get_ok in + let pci_addr2 = Pciaddr.of_string "0000:03:00.0" |> Result.get_ok in + let pci_addr3 = Pciaddr.of_string "0000:04:00.0" |> Result.get_ok in + + let mac_addr0 = Macaddr.of_string "01:23:45:67:89:01" |> Result.get_ok in + let mac_addr1 = Macaddr.of_string "11:23:45:67:89:01" |> Result.get_ok in + let mac_addr2 = Macaddr.of_string "21:23:45:67:89:01" |> Result.get_ok in + let mac_addr3 = Macaddr.of_string "31:23:45:67:89:02" |> Result.get_ok in + let mac_addr4 = Macaddr.of_string "31:23:45:67:89:01" |> Result.get_ok in + + let dev0 = + { + Dev.name= "side-1-eth0" + ; pci= pci_addr0 + ; mac= mac_addr0' + ; bios_eth_order= 0 + ; multi_nic= false + } + in + + let dev1 = + { + Dev.name= "side-34-eth1" + ; pci= pci_addr1 + ; mac= mac_addr1' + ; bios_eth_order= 1 + ; multi_nic= false + } + in + let dev2 = + { + Dev.name= "side-71-eth2" + ; pci= pci_addr2 + ; mac= mac_addr2' + ; bios_eth_order= 2 + ; multi_nic= false + } + in + + let dev3 = + { + Dev.name= "side-3012-eth3" + ; pci= pci_addr3 + ; mac= mac_addr3' + ; bios_eth_order= 3 + ; multi_nic= true + } + in + let dev4 = + { + Dev.name= "side-4332-eth4" + ; pci= pci_addr3 + ; mac= mac_addr4' + ; bios_eth_order= 4 + ; multi_nic= true + } + in + let seen_dev0 = + {name= "eth0"; pci= pci_addr0; mac= mac_addr0; position= 0; present= true} + in + let seen_dev1 = + {name= "eth1"; pci= pci_addr1; mac= mac_addr1; position= 1; present= true} + in + let seen_dev2 = + {name= "eth2"; pci= pci_addr2; mac= mac_addr2; position= 2; present= true} + in + let seen_dev3 = + {name= "eth3"; pci= pci_addr3; mac= mac_addr3; position= 3; present= true} + in + let seen_dev4 = + {name= "eth4"; pci= pci_addr3; mac= mac_addr4; position= 4; present= true} + in + let currents = [dev0; dev1; dev2; dev3; dev4] in + let last_order = [seen_dev0; seen_dev1; seen_dev2; seen_dev3; seen_dev4] in + let order = sort' ~currents ~rules:[] ~last_order in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "5 devices in the order" 5 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 4 true dev3 order ; + test_postion_and_present 3 true dev4 order + +let test_bad_biosdevname_order () = + let pci_addr0 = Pciaddr.of_string "0000:01:00.0" |> Result.get_ok in + let pci_addr1 = Pciaddr.of_string "0000:02:00.0" |> Result.get_ok in + let pci_addr4 = Pciaddr.of_string "0000:03:00.0" |> Result.get_ok in + let pci_addr5 = Pciaddr.of_string "0000:04:00.0" |> Result.get_ok in + let pci_addr8 = Pciaddr.of_string "0000:05:00.0" |> Result.get_ok in + + let mac_addr0 = Macaddr.of_string "00:00:00:00:00:01" |> Result.get_ok in + let mac_addr1 = Macaddr.of_string "00:00:44:00:01:01" |> Result.get_ok in + let mac_addr2 = Macaddr.of_string "00:00:44:00:01:02" |> Result.get_ok in + let mac_addr3 = Macaddr.of_string "00:00:44:00:01:03" |> Result.get_ok in + let mac_addr4 = Macaddr.of_string "00:00:00:00:02:01" |> Result.get_ok in + let mac_addr5 = Macaddr.of_string "00:00:22:00:03:01" |> Result.get_ok in + let mac_addr6 = Macaddr.of_string "00:00:22:00:03:02" |> Result.get_ok in + let mac_addr7 = Macaddr.of_string "00:00:22:00:03:03" |> Result.get_ok in + let mac_addr8 = Macaddr.of_string "00:00:00:00:04:01" |> Result.get_ok in + + let dev0 = + { + Dev.name= "side-0-eth0" + ; pci= pci_addr0 + ; mac= mac_addr0 + ; bios_eth_order= 0 + ; multi_nic= false + } + in + let dev1 = + { + Dev.name= "side-0-eth2" + ; pci= pci_addr1 + ; mac= mac_addr1 + ; bios_eth_order= 2 + ; multi_nic= true + } + in + let dev2 = + { + Dev.name= "side-0-eth6" + ; pci= pci_addr1 + ; mac= mac_addr2 + ; bios_eth_order= 6 + ; multi_nic= true + } + in + let dev3 = + { + Dev.name= "side-0-eth1" + ; pci= pci_addr1 + ; mac= mac_addr3 + ; bios_eth_order= 1 + ; multi_nic= true + } + in + let dev4 = + { + Dev.name= "side-0-eth4" + ; pci= pci_addr4 + ; mac= mac_addr4 + ; bios_eth_order= 4 + ; multi_nic= true + } + in + let dev5 = + { + Dev.name= "side-0-eth5" + ; pci= pci_addr5 + ; mac= mac_addr5 + ; bios_eth_order= 7 + ; multi_nic= true + } + in + let dev6 = + { + Dev.name= "side-0-eth3" + ; pci= pci_addr5 + ; mac= mac_addr6 + ; bios_eth_order= 3 + ; multi_nic= true + } + in + let dev7 = + { + Dev.name= "side-0-eth7" + ; pci= pci_addr5 + ; mac= mac_addr7 + ; bios_eth_order= 5 + ; multi_nic= true + } + in + let dev8 = + { + Dev.name= "side-0-eth8" + ; pci= pci_addr8 + ; mac= mac_addr8 + ; bios_eth_order= 8 + ; multi_nic= false + } + in + let currents = [dev0; dev1; dev2; dev3; dev4; dev5; dev6; dev7; dev8] in + let order = sort' ~currents ~rules:[] ~last_order:[] in + Alcotest.(check bool) "is Ok" true (Result.is_ok order) ; + let order = Result.get_ok order in + Alcotest.(check int) "9 devices in the order" 9 (List.length order) ; + test_postion_and_present 0 true dev0 order ; + test_postion_and_present 1 true dev1 order ; + test_postion_and_present 2 true dev2 order ; + test_postion_and_present 6 true dev3 order ; + test_postion_and_present 4 true dev4 order ; + test_postion_and_present 3 true dev5 order ; + test_postion_and_present 5 true dev6 order ; + test_postion_and_present 7 true dev7 order ; + test_postion_and_present 8 true dev8 order + +let tests = + [ + ( "test_simple_logic" + , [ + ("test_newhw_norules_1eth", `Quick, test_newhw_norules_1eth) + ; ("test_newhw_norules_2eth", `Quick, test_newhw_norules_2eth) + ; ("test_newhw_2srule_2eth", `Quick, test_newhw_2srule_2eth) + ; ( "test_nosrules_1eth_incorrect_udev" + , `Quick + , test_nosrules_1eth_incorrect_udev + ) + ; ( "test_1srule_1eth_1last_correct_udev" + , `Quick + , test_1srule_1eth_1last_correct_udev + ) + ; ( "test_1srule_1eth_already_complete" + , `Quick + , test_1srule_1eth_already_complete + ) + ; ( "test_1drule_1eth_already_complete" + , `Quick + , test_1drule_1eth_already_complete + ) + ] + ) + ; ( "test_use_cases" + , [ + ("test_usecase1", `Quick, test_usecase1) + ; ("test_usecase5", `Quick, test_usecase5) + ; ("test_CA_94279", `Quick, test_CA_94279) + ; ("test_rshp_new_hardware", `Quick, test_rshp_new_hardware) + ; ("test_bad_biosdevname_order", `Quick, test_bad_biosdevname_order) + ] + ) + ] diff --git a/ocaml/networkd/test/test_network_device_order_inherited.mli b/ocaml/networkd/test/test_network_device_order_inherited.mli new file mode 100644 index 00000000000..c32d2a7e66b --- /dev/null +++ b/ocaml/networkd/test/test_network_device_order_inherited.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