diff --git a/redfish/ethernetinterface.go b/redfish/ethernetinterface.go index 800a7f24..e147c173 100644 --- a/redfish/ethernetinterface.go +++ b/redfish/ethernetinterface.go @@ -243,13 +243,14 @@ type EthernetInterface struct { // UefiDevicePath shall be the UEFI device path to the device which // implements this interface (port). UefiDevicePath string - // VLAN shall be the VLAN for this interface. If this interface supports - // more than one VLAN, the VLAN property shall not be present and the VLANS - // collection link shall be present instead. - VLAN VLAN // VLAN shall contain the VLAN for this interface. If this interface supports more than one VLAN, the VLAN property - // shall be absent and, instead, the VLAN collection link shall be present. - vlan string + // shall be absent and, instead, the VLANs call should be used instead. + VLAN VLAN + // VLANs is a collection of VLANs and is only used if the interface supports + // more than one VLANs, applies only if the interface supports more than one VLAN. If this property + // is present, the VLANEnabled and VLANId properties shall not be present. + // This property has been deprecated in favor of newer methods indicating multiple VLANs. + vlans string affiliatedInterfaces []string // AffiliatedInterfacesCount is the number of affiliated interfaces. @@ -314,7 +315,7 @@ func (ethernetinterface *EthernetInterface) UnmarshalJSON(b []byte) error { var t struct { temp Links links - VLAN common.Link + VLANs common.Link } err := json.Unmarshal(b, &t) @@ -338,7 +339,7 @@ func (ethernetinterface *EthernetInterface) UnmarshalJSON(b []byte) error { ethernetinterface.relatedInterfaces = t.Links.RelatedInterfaces.ToStrings() ethernetinterface.RelatedInterfacesCount = t.Links.RelatedInterfacesCount - ethernetinterface.vlan = t.VLAN.String() + ethernetinterface.vlans = t.VLANs.String() // This is a read/write object, so we need to save the raw object data for later ethernetinterface.rawData = b @@ -451,7 +452,11 @@ type StatelessAddressAutoConfiguration struct { IPv6AutoConfigEnabled bool } -// TODO: Add vlans +// VLAN gets the VLAN for this interface. If this interface supports more than one VLAN, the VLAN call +// will return nil and the VLANs call should be used instead. +func (ethernetinterface *EthernetInterface) VLANs() ([]*VLanNetworkInterface, error) { + return ListReferencedVLanNetworkInterfaces(ethernetinterface.GetClient(), ethernetinterface.vlans) +} // AffiliatedInterfaces gets any ethernet interfaces that are affiliated with this interface. func (ethernetinterface *EthernetInterface) AffiliatedInterfaces() ([]*EthernetInterface, error) { diff --git a/redfish/ethernetinterface_test.go b/redfish/ethernetinterface_test.go index 8f6b2ebe..0cb42f3b 100644 --- a/redfish/ethernetinterface_test.go +++ b/redfish/ethernetinterface_test.go @@ -136,6 +136,10 @@ func TestEthernetInterface(t *testing.T) { if result.SpeedMbps != 10000 { t.Errorf("Expected 10000 speed, got %d", result.SpeedMbps) } + + if result.VLAN.VLANID != 0 { + t.Errorf("Expected VLAN ID 0, got %d", result.VLAN.VLANID) + } } // TestEthernetInterfaceUpdate tests the Update call. diff --git a/redfish/resourceblock_test.go b/redfish/resourceblock_test.go new file mode 100644 index 00000000..6337a62d --- /dev/null +++ b/redfish/resourceblock_test.go @@ -0,0 +1,329 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +//nolint:dupl +package redfish + +import ( + "encoding/json" + "strings" + "testing" +) + +var resourceBlockComputeBody = strings.NewReader( + `{ + "@odata.context": "/redfish/v1/$metadata#ResourceBlock.ResourceBlock", + "@odata.etag": "\"1712866586\"", + "@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/ComputeBlock", + "@odata.type": "#ResourceBlock.v1_3_2.ResourceBlock", + "CompositionStatus": { + "CompositionState": "Unused", + "MaxCompositions": 1, + "Reserved": false + }, + "Description": "ComputeBlock", + "Id": "ComputeBlock", + "Links": { + "Chassis": [ + { + "@odata.id": "/redfish/v1/Chassis/BMC_0" + } + ], + "Zones": [ + { + "@odata.id": "/redfish/v1/CompositionService/ResourceZones/1" + } + ] + }, + "Memory": [ + { + "@odata.id": "/redfish/v1/Systems/System_0/Memory/DevType2_DIMM0" + } + ], + "Memory@odata.count": 1, + "Name": "ComputeBlock", + "Processors": [ + { + "@odata.id": "/redfish/v1/Systems/System_0/Processors/DevType1_CPU0" + } + ], + "Processors@odata.count": 1, + "ResourceBlockType": [ + "Compute" + ], + "Status": { + "Health": "OK", + "HealthRollup": "OK", + "State": "Enabled" + } + }`) + +var resourceBlockDriveBody = strings.NewReader( + `{ + "@odata.context": "/redfish/v1/$metadata#ResourceBlock.ResourceBlock", + "@odata.etag": "\"1712866586\"", + "@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/DrivesBlock", + "@odata.type": "#ResourceBlock.v1_3_2.ResourceBlock", + "CompositionStatus": { + "CompositionState": "Unused", + "MaxCompositions": 1, + "Reserved": false + }, + "Description": "DrivesBlock", + "Drives": [ + { + "@odata.id": "/redfish/v1/Systems/System_0/Storage/StorageUnit_0/Drives/NVMe_Device0_NSID1" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/Storage/StorageUnit_0/Drives/USB_Device1_Port4" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/Storage/StorageUnit_0/Drives/USB_Device2_Port4" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/Storage/StorageUnit_0/Drives/USB_Device3_Port2" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/Storage/StorageUnit_0/Drives/USB_Device4_Port4" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/Storage/StorageUnit_0/Drives/USB_Device5_Port4" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/Storage/StorageUnit_0/Drives/USB_Device6_Port4" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/Storage/StorageUnit_0/Drives/USB_Device7_Port4" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/Storage/StorageUnit_0/Drives/USB_Device8_Port4" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/Storage/StorageUnit_0/Drives/USB_Device9_Port4" + } + ], + "Drives@odata.count": 10, + "Id": "DrivesBlock", + "Links": { + "Chassis": [ + { + "@odata.id": "/redfish/v1/Chassis/BMC_0" + } + ], + "Zones": [ + { + "@odata.id": "/redfish/v1/CompositionService/ResourceZones/1" + } + ] + }, + "Name": "DrivesBlock", + "ResourceBlockType": [ + "Storage" + ], + "SimpleStorage": [ + { + "@odata.id": "/redfish/v1/Systems/System_0/SimpleStorage/0" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/SimpleStorage/1" + } + ], + "SimpleStorage@odata.count": 2, + "Status": { + "Health": "OK", + "HealthRollup": "OK", + "State": "Enabled" + }, + "Storage": [ + { + "@odata.id": "/redfish/v1/Systems/System_0/Storage/StorageUnit_0" + } + ], + "Storage@odata.count": 1 + }`) + +var resourceBlockNetworkBody = strings.NewReader( + `{ + "@odata.context": "/redfish/v1/$metadata#ResourceBlock.ResourceBlock", + "@odata.etag": "\"1712866586\"", + "@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/NetworkBlock", + "@odata.type": "#ResourceBlock.v1_3_2.ResourceBlock", + "CompositionStatus": { + "CompositionState": "Unused", + "MaxCompositions": 1, + "Reserved": false + }, + "Description": "NetworkBlock", + "EthernetInterfaces": [ + { + "@odata.id": "/redfish/v1/Systems/System_0/EthernetInterfaces/EthernetInterface0" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/EthernetInterfaces/EthernetInterface1" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/EthernetInterfaces/EthernetInterface2" + }, + { + "@odata.id": "/redfish/v1/Systems/System_0/EthernetInterfaces/EthernetInterface3" + } + ], + "EthernetInterfaces@odata.count": 4, + "Id": "NetworkBlock", + "Links": { + "Chassis": [ + { + "@odata.id": "/redfish/v1/Chassis/BMC_0" + } + ], + "Zones": [ + { + "@odata.id": "/redfish/v1/CompositionService/ResourceZones/1" + } + ] + }, + "Name": "NetworkBlock", + "ResourceBlockType": [ + "Network" + ], + "Status": { + "Health": "OK", + "HealthRollup": "OK", + "State": "Enabled" + } + }`) + +// TestComputeResourceBlock tests the parsing of Compute ResourceBlock objects. +func TestComputeResourceBlock(t *testing.T) { + var result ResourceBlock + err := json.NewDecoder(resourceBlockComputeBody).Decode(&result) + + if err != nil { + t.Errorf("Error decoding JSON: %s", err) + } + + assertEquals(t, "ComputeBlock", result.ID) + assertEquals(t, "ComputeBlock", result.Description) + assertEquals(t, "ComputeBlock", result.Name) + + if len(result.ResourceBlockType) != 1 { + t.Errorf("Expected 1 ResourceBlockType, got: %#v", result.ResourceBlockType) + } + + assertEquals(t, "Compute", string(result.ResourceBlockType[0])) + + if len(result.processors) != 1 { + t.Errorf("Expected 1 processor, got: %#v", result.processors) + } + + if len(result.memory) != 1 { + t.Errorf("Expected 1 memory, got: %#v", result.memory) + } + + if len(result.drives) != 0 { + t.Errorf("Expected 0 drives, got: %#v", result.drives) + } + + if len(result.storage) != 0 { + t.Errorf("Expected 0 storage links, got: %#v", result.storage) + } + + if len(result.simpleStorage) != 0 { + t.Errorf("Expected 0 simple storage links, got: %#v", result.simpleStorage) + } + + if len(result.ethernetInterfaces) != 0 { + t.Errorf("Expected 0 ethernet interfaces, got: %#v", result.ethernetInterfaces) + } + + assertEquals(t, "Unused", string(result.CompositionStatus.CompositionState)) +} + +// TestDrivesResourceBlock tests the parsing of Storage ResourceBlock objects. +func TestDrivesResourceBlock(t *testing.T) { + var result ResourceBlock + err := json.NewDecoder(resourceBlockDriveBody).Decode(&result) + + if err != nil { + t.Errorf("Error decoding JSON: %s", err) + } + + assertEquals(t, "DrivesBlock", result.ID) + assertEquals(t, "DrivesBlock", result.Description) + assertEquals(t, "DrivesBlock", result.Name) + + if len(result.ResourceBlockType) != 1 { + t.Errorf("Expected 1 ResourceBlockType, got: %#v", result.ResourceBlockType) + } + + assertEquals(t, "Storage", string(result.ResourceBlockType[0])) + + if len(result.processors) != 0 { + t.Errorf("Expected 0 processor, got: %#v", result.processors) + } + + if len(result.memory) != 0 { + t.Errorf("Expected 0 memory, got: %#v", result.memory) + } + + if len(result.drives) != 10 { + t.Errorf("Expected 10 drives, got: %#v", result.drives) + } + + if len(result.storage) != 1 { + t.Errorf("Expected 1 storage links, got: %#v", result.storage) + } + + if len(result.simpleStorage) != 2 { + t.Errorf("Expected 2 simple storage links, got: %#v", result.simpleStorage) + } + + if len(result.ethernetInterfaces) != 0 { + t.Errorf("Expected 0 ethernet interfaces, got: %#v", result.ethernetInterfaces) + } +} + +// TestNetworkResourceBlock tests the parsing of Network ResourceBlock objects. +func TestNetworkResourceBlock(t *testing.T) { + var result ResourceBlock + err := json.NewDecoder(resourceBlockNetworkBody).Decode(&result) + + if err != nil { + t.Errorf("Error decoding JSON: %s", err) + } + + assertEquals(t, "NetworkBlock", result.ID) + assertEquals(t, "NetworkBlock", result.Description) + assertEquals(t, "NetworkBlock", result.Name) + + if len(result.ResourceBlockType) != 1 { + t.Errorf("Expected 1 ResourceBlockType, got: %#v", result.ResourceBlockType) + } + + assertEquals(t, "Network", string(result.ResourceBlockType[0])) + + if len(result.processors) != 0 { + t.Errorf("Expected 0 processor, got: %#v", result.processors) + } + + if len(result.memory) != 0 { + t.Errorf("Expected 0 memory, got: %#v", result.memory) + } + + if len(result.drives) != 0 { + t.Errorf("Expected 0 drives, got: %#v", result.drives) + } + + if len(result.storage) != 0 { + t.Errorf("Expected 0 storage links, got: %#v", result.storage) + } + + if len(result.simpleStorage) != 0 { + t.Errorf("Expected 0 simple storage links, got: %#v", result.simpleStorage) + } + + if len(result.ethernetInterfaces) != 4 { + t.Errorf("Expected 4 ethernet interfaces, got: %#v", result.ethernetInterfaces) + } +} diff --git a/redfish/zone_test.go b/redfish/zone_test.go new file mode 100644 index 00000000..0a4ff29d --- /dev/null +++ b/redfish/zone_test.go @@ -0,0 +1,74 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package redfish + +import ( + "encoding/json" + "strings" + "testing" +) + +var zoneBody = `{ + "@Redfish.CollectionCapabilities": { + "@odata.type": "#CollectionCapabilities.v1_2_0.CollectionCapabilities", + "Capabilities": [ + { + "CapabilitiesObject": { + "@odata.id": "/redfish/v1/Systems/Capabilities" + }, + "Links": { + "TargetCollection": { + "@odata.id": "/redfish/v1/Systems" + } + }, + "UseCase": "ComputerSystemComposition" + } + ], + "MaxMembers": 1 + }, + "@odata.context": "/redfish/v1/$metadata#Zone.Zone", + "@odata.etag": "\"1712866586\"", + "@odata.id": "/redfish/v1/CompositionService/ResourceZones/1", + "@odata.type": "#Zone.v1_3_1.Zone", + "Description": "Resource Zone 1", + "Id": "1", + "Links": { + "ResourceBlocks": [ + { + "@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/ComputeBlock" + }, + { + "@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/DrivesBlock" + }, + { + "@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/NetworkBlock" + } + ] + }, + "Name": "Resource Zone 1", + "Status": { + "Health": "OK", + "HealthRollup": "OK", + "State": "Enabled" + } + }` + +// TestZone tests the parsing of Zone objects. +func TestZone(t *testing.T) { + var result Zone + err := json.NewDecoder(strings.NewReader(zoneBody)).Decode(&result) + + if err != nil { + t.Errorf("Error decoding JSON: %s", err) + } + + assertEquals(t, "1", result.ID) + assertEquals(t, "Resource Zone 1", result.Name) + assertEquals(t, "Resource Zone 1", result.Description) + + if len(result.resourceBlocks) != 3 { + t.Errorf("Expected 3 resource blocks, got %#v", result.resourceBlocks) + } +}