diff --git a/Dockerfile b/Dockerfile index 404af421e7..4197437ec6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,9 @@ FROM registry.svc.ci.openshift.org/openshift/release:golang-1.10 AS builder WORKDIR /go/src/github.com/metal3-io/baremetal-operator COPY . . RUN make build +RUN make tools FROM registry.svc.ci.openshift.org/openshift/origin-v4.0:base COPY --from=builder /go/src/github.com/metal3-io/baremetal-operator/build/_output/bin/baremetal-operator / +COPY --from=builder /go/src/github.com/metal3-io/baremetal-operator/build/_output/bin/get-hardware-details / RUN if ! rpm -q genisoimage; then yum install -y genisoimage && yum clean all && rm -rf /var/cache/yum/*; fi diff --git a/Makefile b/Makefile index 69df3459cb..d02b7d762c 100644 --- a/Makefile +++ b/Makefile @@ -122,6 +122,11 @@ build: @echo LDFLAGS=$(LDFLAGS) go build -o build/_output/bin/baremetal-operator cmd/manager/main.go +.PHONY: tools +tools: + @echo LDFLAGS=$(LDFLAGS) + go build -o build/_output/bin/get-hardware-details cmd/get-hardware-details/main.go + .PHONY: deploy deploy: cd deploy && kustomize edit set namespace $(RUN_NAMESPACE) && cd .. diff --git a/cmd/get-hardware-details/main.go b/cmd/get-hardware-details/main.go new file mode 100644 index 0000000000..066adebb61 --- /dev/null +++ b/cmd/get-hardware-details/main.go @@ -0,0 +1,44 @@ +// get-hardware-details is a tool that can be used to convert raw Ironic introspection data into the HardwareDetails +// type used by Metal3. +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth" + "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection" + "github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/hardwaredetails" +) + +func main() { + if len(os.Args) != 3 { + fmt.Println("Usage: get-hardware-details ") + return + } + + inspector, err := noauth.NewBareMetalIntrospectionNoAuth( + noauth.EndpointOpts{ + IronicInspectorEndpoint: os.Args[1], + }) + if err != nil { + fmt.Printf("could not get inspector client: %s", err) + os.Exit(1) + } + + introData := introspection.GetIntrospectionData(inspector, os.Args[2]) + data, err := introData.Extract() + if err != nil { + fmt.Printf("could not get introspection data: %s", err) + os.Exit(1) + } + + json, err := json.MarshalIndent(hardwaredetails.GetHardwareDetails(data), "", "\t") + if err != nil { + fmt.Printf("could not convert introspection data: %s", err) + os.Exit(1) + } + + fmt.Println(string(json)) +} diff --git a/pkg/provisioner/ironic/hardwaredetails/hardwaredetails.go b/pkg/provisioner/ironic/hardwaredetails/hardwaredetails.go new file mode 100644 index 0000000000..795aeecd0b --- /dev/null +++ b/pkg/provisioner/ironic/hardwaredetails/hardwaredetails.go @@ -0,0 +1,143 @@ +package hardwaredetails + +import ( + "fmt" + "sort" + "strings" + + "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection" + metal3v1alpha1 "github.com/metal3-io/baremetal-operator/pkg/apis/metal3/v1alpha1" +) + +// GetHardwareDetails converts Ironic introspection data into BareMetalHost HardwareDetails. +func GetHardwareDetails(data *introspection.Data) *metal3v1alpha1.HardwareDetails { + details := new(metal3v1alpha1.HardwareDetails) + details.Firmware = getFirmwareDetails(data.Extra.Firmware) + details.SystemVendor = getSystemVendorDetails(data.Inventory.SystemVendor) + details.RAMMebibytes = data.MemoryMB + details.NIC = getNICDetails(data.Inventory.Interfaces, data.AllInterfaces, data.Extra.Network) + details.Storage = getStorageDetails(data.Inventory.Disks) + details.CPU = getCPUDetails(&data.Inventory.CPU) + details.Hostname = data.Inventory.Hostname + return details +} + +func getVLANs(intf introspection.BaseInterfaceType) (vlans []metal3v1alpha1.VLAN, vlanid metal3v1alpha1.VLANID) { + if intf.LLDPProcessed == nil { + return + } + if spvs, ok := intf.LLDPProcessed["switch_port_vlans"]; ok { + if data, ok := spvs.([]map[string]interface{}); ok { + vlans = make([]metal3v1alpha1.VLAN, len(data)) + for i, vlan := range data { + vid, _ := vlan["id"].(int) + name, _ := vlan["name"].(string) + vlans[i] = metal3v1alpha1.VLAN{ + ID: metal3v1alpha1.VLANID(vid), + Name: name, + } + } + } + } + if vid, ok := intf.LLDPProcessed["switch_port_untagged_vlan_id"].(int); ok { + vlanid = metal3v1alpha1.VLANID(vid) + } + return +} + +func getNICSpeedGbps(intfExtradata introspection.ExtraHardwareData) (speedGbps int) { + if speed, ok := intfExtradata["speed"].(string); ok { + if strings.HasSuffix(speed, "Gbps") { + fmt.Sscanf(speed, "%d", &speedGbps) + } + } + return +} + +func getNICDetails(ifdata []introspection.InterfaceType, + basedata map[string]introspection.BaseInterfaceType, + extradata introspection.ExtraHardwareDataSection) []metal3v1alpha1.NIC { + nics := make([]metal3v1alpha1.NIC, len(ifdata)) + for i, intf := range ifdata { + baseIntf := basedata[intf.Name] + vlans, vlanid := getVLANs(baseIntf) + ip := intf.IPV4Address + if ip == "" { + ip = intf.IPV6Address + } + nics[i] = metal3v1alpha1.NIC{ + Name: intf.Name, + Model: strings.TrimLeft(fmt.Sprintf("%s %s", + intf.Vendor, intf.Product), " "), + MAC: intf.MACAddress, + IP: ip, + VLANs: vlans, + VLANID: vlanid, + SpeedGbps: getNICSpeedGbps(extradata[intf.Name]), + PXE: baseIntf.PXE, + } + } + return nics +} + +func getStorageDetails(diskdata []introspection.RootDiskType) []metal3v1alpha1.Storage { + storage := make([]metal3v1alpha1.Storage, len(diskdata)) + for i, disk := range diskdata { + storage[i] = metal3v1alpha1.Storage{ + Name: disk.Name, + Rotational: disk.Rotational, + SizeBytes: metal3v1alpha1.Capacity(disk.Size), + Vendor: disk.Vendor, + Model: disk.Model, + SerialNumber: disk.Serial, + WWN: disk.Wwn, + WWNVendorExtension: disk.WwnVendorExtension, + WWNWithExtension: disk.WwnWithExtension, + HCTL: disk.Hctl, + } + } + return storage +} + +func getSystemVendorDetails(vendor introspection.SystemVendorType) metal3v1alpha1.HardwareSystemVendor { + return metal3v1alpha1.HardwareSystemVendor{ + Manufacturer: vendor.Manufacturer, + ProductName: vendor.ProductName, + SerialNumber: vendor.SerialNumber, + } +} + +func getCPUDetails(cpudata *introspection.CPUType) metal3v1alpha1.CPU { + var freq float64 + fmt.Sscanf(cpudata.Frequency, "%f", &freq) + sort.Strings(cpudata.Flags) + cpu := metal3v1alpha1.CPU{ + Arch: cpudata.Architecture, + Model: cpudata.ModelName, + ClockMegahertz: metal3v1alpha1.ClockSpeed(freq) * metal3v1alpha1.MegaHertz, + Count: cpudata.Count, + Flags: cpudata.Flags, + } + + return cpu +} + +func getFirmwareDetails(firmwaredata introspection.ExtraHardwareDataSection) metal3v1alpha1.Firmware { + + // handle bios optionally + var bios metal3v1alpha1.BIOS + + if biosdata, ok := firmwaredata["bios"]; ok { + // we do not know if all fields will be supplied + // as this is not a structured response + // so we must handle each field conditionally + bios.Vendor, _ = biosdata["vendor"].(string) + bios.Version, _ = biosdata["version"].(string) + bios.Date, _ = biosdata["date"].(string) + } + + return metal3v1alpha1.Firmware{ + BIOS: bios, + } + +} diff --git a/pkg/provisioner/ironic/hardwaredetails/hardwaredetails_test.go b/pkg/provisioner/ironic/hardwaredetails/hardwaredetails_test.go new file mode 100644 index 0000000000..fac70cbed8 --- /dev/null +++ b/pkg/provisioner/ironic/hardwaredetails/hardwaredetails_test.go @@ -0,0 +1,277 @@ +package hardwaredetails + +import ( + "reflect" + "testing" + + "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection" + + metal3v1alpha1 "github.com/metal3-io/baremetal-operator/pkg/apis/metal3/v1alpha1" +) + +func TestGetVLANs(t *testing.T) { + vlans, vid := getVLANs(introspection.BaseInterfaceType{ + LLDPProcessed: map[string]interface{}{ + "switch_port_vlans": []map[string]interface{}{ + { + "id": 1, + "name": "vlan1", + }, + { + "id": 4094, + "name": "vlan4094", + }, + }, + "switch_port_untagged_vlan_id": 1, + }, + }) + if vid != 1 { + t.Errorf("Unexpected untagged VLAN ID %d", vid) + } + if len(vlans) != 2 { + t.Errorf("Expected 2 VLANs, got %d", len(vlans)) + } + if (vlans[0] != metal3v1alpha1.VLAN{ID: 1, Name: "vlan1"}) { + t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) + } + if (vlans[1] != metal3v1alpha1.VLAN{ID: 4094, Name: "vlan4094"}) { + t.Errorf("Unexpected VLAN %d %s", vlans[1].ID, vlans[1].Name) + } +} + +func TestGetVLANsMalformed(t *testing.T) { + vlans, vid := getVLANs(introspection.BaseInterfaceType{ + LLDPProcessed: map[string]interface{}{ + "switch_port_vlans": []map[string]interface{}{ + { + "foo": "bar", + "name": "vlan1", + }, + { + "foo": "bar", + "id": 1, + }, + { + "name": "vlan2", + "id": "2", + }, + { + "name": 3, + "id": 3, + }, + { + "foo": "bar", + }, + }, + "switch_port_untagged_vlan_id": "1", + }, + }) + if vid != 0 { + t.Errorf("Unexpected untagged VLAN ID %d", vid) + } + if len(vlans) != 5 { + t.Errorf("Expected 5 VLANs, got %d", len(vlans)) + } + if (vlans[0] != metal3v1alpha1.VLAN{Name: "vlan1"}) { + t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) + } + if (vlans[1] != metal3v1alpha1.VLAN{ID: 1}) { + t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) + } + if (vlans[2] != metal3v1alpha1.VLAN{Name: "vlan2"}) { + t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) + } + if (vlans[3] != metal3v1alpha1.VLAN{ID: 3}) { + t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) + } + if (vlans[4] != metal3v1alpha1.VLAN{}) { + t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) + } + + vlans, vid = getVLANs(introspection.BaseInterfaceType{ + LLDPProcessed: map[string]interface{}{ + "switch_port_vlans": map[string]interface{}{ + "foo": "bar", + }, + "switch_port_untagged_vlan_id": "1", + }, + }) + if vid != 0 { + t.Errorf("Unexpected untagged VLAN ID %d", vid) + } + if len(vlans) != 0 { + t.Errorf("Expected 0 VLANs, got %d", len(vlans)) + } + + vlans, vid = getVLANs(introspection.BaseInterfaceType{ + LLDPProcessed: map[string]interface{}{ + "switch_port_vlans": []interface{}{ + "foo", + }, + }, + }) + if vid != 0 { + t.Errorf("Unexpected untagged VLAN ID %d", vid) + } + if len(vlans) != 0 { + t.Errorf("Expected 0 VLANs, got %d", len(vlans)) + } + + vlans, vid = getVLANs(introspection.BaseInterfaceType{ + LLDPProcessed: map[string]interface{}{ + "switch_port_vlans": "foo", + }, + }) + if vid != 0 { + t.Errorf("Unexpected untagged VLAN ID %d", vid) + } + if len(vlans) != 0 { + t.Errorf("Expected 0 VLANs, got %d", len(vlans)) + } + + vlans, vid = getVLANs(introspection.BaseInterfaceType{ + LLDPProcessed: map[string]interface{}{}, + }) + if vid != 0 { + t.Errorf("Unexpected untagged VLAN ID %d", vid) + } + if len(vlans) != 0 { + t.Errorf("Expected 0 VLANs, got %d", len(vlans)) + } + + vlans, vid = getVLANs(introspection.BaseInterfaceType{}) + if vid != 0 { + t.Errorf("Unexpected untagged VLAN ID %d", vid) + } + if len(vlans) != 0 { + t.Errorf("Expected 0 VLANs, got %d", len(vlans)) + } +} + +func TestGetNICDetails(t *testing.T) { + nics := getNICDetails( + []introspection.InterfaceType{ + { + Name: "eth0", + IPV4Address: "192.0.2.1", + MACAddress: "00:11:22:33:44:55"}, + { + Name: "eth1", + IPV6Address: "2001:db8::1", + MACAddress: "66:77:88:99:aa:bb"}, + }, + map[string]introspection.BaseInterfaceType{ + "eth0": { + PXE: true, + LLDPProcessed: map[string]interface{}{ + "switch_port_vlans": []map[string]interface{}{ + { + "id": 1, + }, + }, + "switch_port_untagged_vlan_id": 1, + }, + }, + }, + introspection.ExtraHardwareDataSection{ + "eth1": introspection.ExtraHardwareData{ + "speed": "1Gbps", + }, + }) + + if len(nics) != 2 { + t.Errorf("Expected 2 NICs, got %d", len(nics)) + } + if (!reflect.DeepEqual(nics[0], metal3v1alpha1.NIC{ + Name: "eth0", + MAC: "00:11:22:33:44:55", + IP: "192.0.2.1", + PXE: true, + VLANs: []metal3v1alpha1.VLAN{ + {ID: 1}, + }, + VLANID: 1, + })) { + t.Errorf("Unexpected NIC data") + } + if (!reflect.DeepEqual(nics[1], metal3v1alpha1.NIC{ + Name: "eth1", + MAC: "66:77:88:99:aa:bb", + IP: "2001:db8::1", + SpeedGbps: 1, + })) { + t.Errorf("Unexpected NIC data") + } +} + +func TestGetNICSpeedGbps(t *testing.T) { + s1 := getNICSpeedGbps(introspection.ExtraHardwareData{ + "speed": "25Gbps", + }) + if s1 != 25 { + t.Errorf("Expected speed 25, got %d", s1) + } + + s2 := getNICSpeedGbps(introspection.ExtraHardwareData{ + "speed": "100Mbps", + }) + if s2 != 0 { + t.Errorf("Expected speed 0, got %d", s2) + } + + s3 := getNICSpeedGbps(introspection.ExtraHardwareData{ + "speed": 10, + }) + if s3 != 0 { + t.Errorf("Expected speed 0, got %d", s3) + } + + s4 := getNICSpeedGbps(introspection.ExtraHardwareData{}) + if s4 != 0 { + t.Errorf("Expected speed 0, got %d", s4) + } +} + +func TestGetFirmwareDetails(t *testing.T) { + // Test full (known) firmware payload + firmware := getFirmwareDetails(introspection.ExtraHardwareDataSection{ + "bios": { + "vendor": "foobar", + "version": "1.2.3", + "date": "2019-07-10", + }, + }) + + if firmware.BIOS.Vendor != "foobar" { + t.Errorf("Expected firmware BIOS vendor to be foobar, but got: %s", firmware) + } + + // Ensure we can handle partial firmware/bios data + firmware = getFirmwareDetails(introspection.ExtraHardwareDataSection{ + "bios": { + "vendor": "foobar", + "version": "1.2.3", + }, + }) + + if firmware.BIOS.Date != "" { + t.Errorf("Expected firmware BIOS date to be empty but got: %s", firmware) + } + + // Ensure we can handle unexpected types + firmware = getFirmwareDetails(introspection.ExtraHardwareDataSection{ + "bios": { + "vendor": 3, + "version": []int{2, 1}, + "date": map[string]string{"year": "2019", "month": "07", "day": "10"}, + }, + }) + + // Finally, ensure we can handle completely empty firmware data + firmware = getFirmwareDetails(introspection.ExtraHardwareDataSection{}) + + if (firmware != metal3v1alpha1.Firmware{}) { + t.Errorf("Expected firmware data to be empty but got: %s", firmware) + } + +} diff --git a/pkg/provisioner/ironic/ironic.go b/pkg/provisioner/ironic/ironic.go index 60d6bc2a9f..36b2b7e238 100644 --- a/pkg/provisioner/ironic/ironic.go +++ b/pkg/provisioner/ironic/ironic.go @@ -3,7 +3,6 @@ package ironic import ( "fmt" "os" - "sort" "strings" "time" @@ -23,6 +22,7 @@ import ( "github.com/metal3-io/baremetal-operator/pkg/bmc" "github.com/metal3-io/baremetal-operator/pkg/hardware" "github.com/metal3-io/baremetal-operator/pkg/provisioner" + "github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/hardwaredetails" ) var log = logf.Log.WithName("baremetalhost_ironic") @@ -436,138 +436,6 @@ func (p *ironicProvisioner) changeNodeProvisionState(ironicNode *nodes.Node, opt return result, nil } -func getVLANs(intf introspection.BaseInterfaceType) (vlans []metal3v1alpha1.VLAN, vlanid metal3v1alpha1.VLANID) { - if intf.LLDPProcessed == nil { - return - } - if spvs, ok := intf.LLDPProcessed["switch_port_vlans"]; ok { - if data, ok := spvs.([]map[string]interface{}); ok { - vlans = make([]metal3v1alpha1.VLAN, len(data)) - for i, vlan := range data { - vid, _ := vlan["id"].(int) - name, _ := vlan["name"].(string) - vlans[i] = metal3v1alpha1.VLAN{ - ID: metal3v1alpha1.VLANID(vid), - Name: name, - } - } - } - } - if vid, ok := intf.LLDPProcessed["switch_port_untagged_vlan_id"].(int); ok { - vlanid = metal3v1alpha1.VLANID(vid) - } - return -} - -func getNICSpeedGbps(intfExtradata introspection.ExtraHardwareData) (speedGbps int) { - if speed, ok := intfExtradata["speed"].(string); ok { - if strings.HasSuffix(speed, "Gbps") { - fmt.Sscanf(speed, "%d", &speedGbps) - } - } - return -} - -func getNICDetails(ifdata []introspection.InterfaceType, - basedata map[string]introspection.BaseInterfaceType, - extradata introspection.ExtraHardwareDataSection) []metal3v1alpha1.NIC { - nics := make([]metal3v1alpha1.NIC, len(ifdata)) - for i, intf := range ifdata { - baseIntf := basedata[intf.Name] - vlans, vlanid := getVLANs(baseIntf) - ip := intf.IPV4Address - if ip == "" { - ip = intf.IPV6Address - } - nics[i] = metal3v1alpha1.NIC{ - Name: intf.Name, - Model: strings.TrimLeft(fmt.Sprintf("%s %s", - intf.Vendor, intf.Product), " "), - MAC: intf.MACAddress, - IP: ip, - VLANs: vlans, - VLANID: vlanid, - SpeedGbps: getNICSpeedGbps(extradata[intf.Name]), - PXE: baseIntf.PXE, - } - } - return nics -} - -func getStorageDetails(diskdata []introspection.RootDiskType) []metal3v1alpha1.Storage { - storage := make([]metal3v1alpha1.Storage, len(diskdata)) - for i, disk := range diskdata { - storage[i] = metal3v1alpha1.Storage{ - Name: disk.Name, - Rotational: disk.Rotational, - SizeBytes: metal3v1alpha1.Capacity(disk.Size), - Vendor: disk.Vendor, - Model: disk.Model, - SerialNumber: disk.Serial, - WWN: disk.Wwn, - WWNVendorExtension: disk.WwnVendorExtension, - WWNWithExtension: disk.WwnWithExtension, - HCTL: disk.Hctl, - } - } - return storage -} - -func getSystemVendorDetails(vendor introspection.SystemVendorType) metal3v1alpha1.HardwareSystemVendor { - return metal3v1alpha1.HardwareSystemVendor{ - Manufacturer: vendor.Manufacturer, - ProductName: vendor.ProductName, - SerialNumber: vendor.SerialNumber, - } -} - -func getCPUDetails(cpudata *introspection.CPUType) metal3v1alpha1.CPU { - var freq float64 - fmt.Sscanf(cpudata.Frequency, "%f", &freq) - sort.Strings(cpudata.Flags) - cpu := metal3v1alpha1.CPU{ - Arch: cpudata.Architecture, - Model: cpudata.ModelName, - ClockMegahertz: metal3v1alpha1.ClockSpeed(freq) * metal3v1alpha1.MegaHertz, - Count: cpudata.Count, - Flags: cpudata.Flags, - } - - return cpu -} - -func getFirmwareDetails(firmwaredata introspection.ExtraHardwareDataSection) metal3v1alpha1.Firmware { - - // handle bios optionally - var bios metal3v1alpha1.BIOS - - if biosdata, ok := firmwaredata["bios"]; ok { - // we do not know if all fields will be supplied - // as this is not a structured response - // so we must handle each field conditionally - bios.Vendor, _ = biosdata["vendor"].(string) - bios.Version, _ = biosdata["version"].(string) - bios.Date, _ = biosdata["date"].(string) - } - - return metal3v1alpha1.Firmware{ - BIOS: bios, - } - -} - -func getHardwareDetails(data *introspection.Data) *metal3v1alpha1.HardwareDetails { - details := new(metal3v1alpha1.HardwareDetails) - details.Firmware = getFirmwareDetails(data.Extra.Firmware) - details.SystemVendor = getSystemVendorDetails(data.Inventory.SystemVendor) - details.RAMMebibytes = data.MemoryMB - details.NIC = getNICDetails(data.Inventory.Interfaces, data.AllInterfaces, data.Extra.Network) - details.Storage = getStorageDetails(data.Inventory.Disks) - details.CPU = getCPUDetails(&data.Inventory.CPU) - details.Hostname = data.Inventory.Hostname - return details -} - // InspectHardware updates the HardwareDetails field of the host with // details of devices discovered on the hardware. It may be called // multiple times, and should return true for its dirty flag until the @@ -622,7 +490,7 @@ func (p *ironicProvisioner) InspectHardware() (result provisioner.Result, detail } p.log.Info("received introspection data", "data", introData.Body) - details = getHardwareDetails(data) + details = hardwaredetails.GetHardwareDetails(data) p.publisher("InspectionComplete", "Hardware inspection completed") return } diff --git a/pkg/provisioner/ironic/ironic_test.go b/pkg/provisioner/ironic/ironic_test.go index db905a7c76..8bb7905a55 100644 --- a/pkg/provisioner/ironic/ironic_test.go +++ b/pkg/provisioner/ironic/ironic_test.go @@ -1,14 +1,12 @@ package ironic import ( - "reflect" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes" - "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection" metal3v1alpha1 "github.com/metal3-io/baremetal-operator/pkg/apis/metal3/v1alpha1" "github.com/metal3-io/baremetal-operator/pkg/bmc" @@ -189,271 +187,3 @@ func TestGetUpdateOptsForNodeDell(t *testing.T) { } } } - -func TestGetVLANs(t *testing.T) { - vlans, vid := getVLANs(introspection.BaseInterfaceType{ - LLDPProcessed: map[string]interface{}{ - "switch_port_vlans": []map[string]interface{}{ - map[string]interface{}{ - "id": 1, - "name": "vlan1", - }, - map[string]interface{}{ - "id": 4094, - "name": "vlan4094", - }, - }, - "switch_port_untagged_vlan_id": 1, - }, - }) - if vid != 1 { - t.Errorf("Unexpected untagged VLAN ID %d", vid) - } - if len(vlans) != 2 { - t.Errorf("Expected 2 VLANs, got %d", len(vlans)) - } - if (vlans[0] != metal3v1alpha1.VLAN{ID: 1, Name: "vlan1"}) { - t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) - } - if (vlans[1] != metal3v1alpha1.VLAN{ID: 4094, Name: "vlan4094"}) { - t.Errorf("Unexpected VLAN %d %s", vlans[1].ID, vlans[1].Name) - } -} - -func TestGetVLANsMalformed(t *testing.T) { - vlans, vid := getVLANs(introspection.BaseInterfaceType{ - LLDPProcessed: map[string]interface{}{ - "switch_port_vlans": []map[string]interface{}{ - map[string]interface{}{ - "foo": "bar", - "name": "vlan1", - }, - map[string]interface{}{ - "foo": "bar", - "id": 1, - }, - map[string]interface{}{ - "name": "vlan2", - "id": "2", - }, - map[string]interface{}{ - "name": 3, - "id": 3, - }, - map[string]interface{}{ - "foo": "bar", - }, - }, - "switch_port_untagged_vlan_id": "1", - }, - }) - if vid != 0 { - t.Errorf("Unexpected untagged VLAN ID %d", vid) - } - if len(vlans) != 5 { - t.Errorf("Expected 5 VLANs, got %d", len(vlans)) - } - if (vlans[0] != metal3v1alpha1.VLAN{Name: "vlan1"}) { - t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) - } - if (vlans[1] != metal3v1alpha1.VLAN{ID: 1}) { - t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) - } - if (vlans[2] != metal3v1alpha1.VLAN{Name: "vlan2"}) { - t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) - } - if (vlans[3] != metal3v1alpha1.VLAN{ID: 3}) { - t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) - } - if (vlans[4] != metal3v1alpha1.VLAN{}) { - t.Errorf("Unexpected VLAN %d %s", vlans[0].ID, vlans[0].Name) - } - - vlans, vid = getVLANs(introspection.BaseInterfaceType{ - LLDPProcessed: map[string]interface{}{ - "switch_port_vlans": map[string]interface{}{ - "foo": "bar", - }, - "switch_port_untagged_vlan_id": "1", - }, - }) - if vid != 0 { - t.Errorf("Unexpected untagged VLAN ID %d", vid) - } - if len(vlans) != 0 { - t.Errorf("Expected 0 VLANs, got %d", len(vlans)) - } - - vlans, vid = getVLANs(introspection.BaseInterfaceType{ - LLDPProcessed: map[string]interface{}{ - "switch_port_vlans": []interface{}{ - "foo", - }, - }, - }) - if vid != 0 { - t.Errorf("Unexpected untagged VLAN ID %d", vid) - } - if len(vlans) != 0 { - t.Errorf("Expected 0 VLANs, got %d", len(vlans)) - } - - vlans, vid = getVLANs(introspection.BaseInterfaceType{ - LLDPProcessed: map[string]interface{}{ - "switch_port_vlans": "foo", - }, - }) - if vid != 0 { - t.Errorf("Unexpected untagged VLAN ID %d", vid) - } - if len(vlans) != 0 { - t.Errorf("Expected 0 VLANs, got %d", len(vlans)) - } - - vlans, vid = getVLANs(introspection.BaseInterfaceType{ - LLDPProcessed: map[string]interface{}{}, - }) - if vid != 0 { - t.Errorf("Unexpected untagged VLAN ID %d", vid) - } - if len(vlans) != 0 { - t.Errorf("Expected 0 VLANs, got %d", len(vlans)) - } - - vlans, vid = getVLANs(introspection.BaseInterfaceType{}) - if vid != 0 { - t.Errorf("Unexpected untagged VLAN ID %d", vid) - } - if len(vlans) != 0 { - t.Errorf("Expected 0 VLANs, got %d", len(vlans)) - } -} - -func TestGetNICDetails(t *testing.T) { - nics := getNICDetails( - []introspection.InterfaceType{ - introspection.InterfaceType{ - Name: "eth0", - IPV4Address: "192.0.2.1", - MACAddress: "00:11:22:33:44:55"}, - introspection.InterfaceType{ - Name: "eth1", - IPV6Address: "2001:db8::1", - MACAddress: "66:77:88:99:aa:bb"}, - }, - map[string]introspection.BaseInterfaceType{ - "eth0": introspection.BaseInterfaceType{ - PXE: true, - LLDPProcessed: map[string]interface{}{ - "switch_port_vlans": []map[string]interface{}{ - map[string]interface{}{ - "id": 1, - }, - }, - "switch_port_untagged_vlan_id": 1, - }, - }, - }, - introspection.ExtraHardwareDataSection{ - "eth1": introspection.ExtraHardwareData{ - "speed": "1Gbps", - }, - }) - - if len(nics) != 2 { - t.Errorf("Expected 2 NICs, got %d", len(nics)) - } - if (!reflect.DeepEqual(nics[0], metal3v1alpha1.NIC{ - Name: "eth0", - MAC: "00:11:22:33:44:55", - IP: "192.0.2.1", - PXE: true, - VLANs: []metal3v1alpha1.VLAN{ - metal3v1alpha1.VLAN{ID: 1}, - }, - VLANID: 1, - })) { - t.Errorf("Unexpected NIC data") - } - if (!reflect.DeepEqual(nics[1], metal3v1alpha1.NIC{ - Name: "eth1", - MAC: "66:77:88:99:aa:bb", - IP: "2001:db8::1", - SpeedGbps: 1, - })) { - t.Errorf("Unexpected NIC data") - } -} - -func TestGetNICSpeedGbps(t *testing.T) { - s1 := getNICSpeedGbps(introspection.ExtraHardwareData{ - "speed": "25Gbps", - }) - if s1 != 25 { - t.Errorf("Expected speed 25, got %d", s1) - } - - s2 := getNICSpeedGbps(introspection.ExtraHardwareData{ - "speed": "100Mbps", - }) - if s2 != 0 { - t.Errorf("Expected speed 0, got %d", s2) - } - - s3 := getNICSpeedGbps(introspection.ExtraHardwareData{ - "speed": 10, - }) - if s3 != 0 { - t.Errorf("Expected speed 0, got %d", s3) - } - - s4 := getNICSpeedGbps(introspection.ExtraHardwareData{}) - if s4 != 0 { - t.Errorf("Expected speed 0, got %d", s4) - } -} - -func TestGetFirmwareDetails(t *testing.T) { - - // Test full (known) firmware payload - firmware := getFirmwareDetails(introspection.ExtraHardwareDataSection{ - "bios": { - "vendor": "foobar", - "version": "1.2.3", - "date": "2019-07-10", - }, - }) - - if firmware.BIOS.Vendor != "foobar" { - t.Errorf("Expected firmware BIOS vendor to be foobar, but got: %s", firmware) - } - - // Ensure we can handle partial firmware/bios data - firmware = getFirmwareDetails(introspection.ExtraHardwareDataSection{ - "bios": { - "vendor": "foobar", - "version": "1.2.3", - }, - }) - - if firmware.BIOS.Date != "" { - t.Errorf("Expected firmware BIOS date to be empty but got: %s", firmware) - } - - // Ensure we can handle unexpected types - firmware = getFirmwareDetails(introspection.ExtraHardwareDataSection{ - "bios": { - "vendor": 3, - "version": []int{2, 1}, - "date": map[string]string{"year": "2019", "month": "07", "day": "10"}, - }, - }) - - // Finally, ensure we can handle completely empty firmware data - firmware = getFirmwareDetails(introspection.ExtraHardwareDataSection{}) - - if (firmware != metal3v1alpha1.Firmware{}) { - t.Errorf("Expected firmware data to be empty but got: %s", firmware) - } - -}