Skip to content

Commit

Permalink
Merge pull request #2781 from dtantsur/inventory-api
Browse files Browse the repository at this point in the history
baremetal: support ironic native PluginData
  • Loading branch information
EmilienM committed Oct 2, 2023
2 parents 502a173 + 72156e2 commit 1fcb806
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 4 deletions.
41 changes: 41 additions & 0 deletions openstack/baremetal/inventory/plugindata.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,44 @@ func (r *LLDPTLVType) UnmarshalJSON(data []byte) error {
r.Value = value
return nil
}

type HardwareManager struct {
Name string `json:"name"`
Version string `json:"version"`
}

type ConfigurationType struct {
// Collectors is a list of enabled collectors - ramdisk-side inspection
// plugins that populated the plugin data.
Collectors []string `json:"collectors"`
// Managers is a list of hardware managers - ramdisk-side plugins that
// implement all actions, such as writing images or collecting
// inventory.
Managers []HardwareManager `json:"managers"`
}

type ProcessedInterfaceType struct {
InterfaceType
// Whether PXE was enabled on this interface during inspection
PXEEnabled bool `json:"pxe_enabled"`
// TODO(dtantsur): add LLDPProcessed once it's actually implemented
}

// StandardPluginData represents the plugin data as collected and processes
// by a standard ramdisk and a standard Ironic deployment.
// The format and contents of the stored data depends on the ramdisk used
// and plugins enabled both in the ramdisk and in inspector itself.
// This structure has been provided for basic compatibility but it
// will need extensions.
type StandardPluginData struct {
AllInterfaces map[string]ProcessedInterfaceType `json:"all_interfaces"`
BootInterface string `json:"boot_interface"`
Configuration ConfigurationType `json:"configuration"`
Error string `json:"error"`
Extra ExtraDataType `json:"extra"`
MACs []string `json:"macs"`
NUMATopology NUMATopology `json:"numa_topology"`
RawLLDP map[string][]LLDPTLVType `json:"lldp_raw"`
RootDisk RootDiskType `json:"root_disk"`
ValidInterfaces map[string]ProcessedInterfaceType `json:"valid_interfaces"`
}
156 changes: 155 additions & 1 deletion openstack/baremetal/inventory/testing/fixtures.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package testing

import "github.com/gophercloud/gophercloud/openstack/baremetal/inventory"
import (
"fmt"

"github.com/gophercloud/gophercloud/openstack/baremetal/inventory"
)

const InventorySample = `{
"bmc_address": "192.167.2.134",
Expand Down Expand Up @@ -190,6 +194,84 @@ const NUMADataJSONSample = `
}
`

var StandardPluginDataSample = fmt.Sprintf(`
{
"all_interfaces": {
"eth0": {
"client_id": null,
"has_carrier": true,
"ipv4_address": "172.24.42.101",
"mac_address": "52:54:00:47:20:4d",
"name": "eth1",
"product": "0x0001",
"vendor": "0x1af4",
"pxe_enabled": true
},
"eth1": {
"client_id": null,
"has_carrier": true,
"ipv4_address": "172.24.42.100",
"mac_address": "52:54:00:4e:3d:30",
"name": "eth0",
"product": "0x0001",
"vendor": "0x1af4",
"speed_mbps": 1000,
"pxe_enabled": false
}
},
"boot_interface": "52:54:00:4e:3d:30",
"configuration": {
"collectors": ["default", "logs"],
"managers": [
{
"name": "generic_hardware_manager",
"version": "1.1"
}
]
},
"error": null,
"extra": %s,
"valid_interfaces": {
"eth0": {
"client_id": null,
"has_carrier": true,
"ipv4_address": "172.24.42.101",
"mac_address": "52:54:00:47:20:4d",
"name": "eth1",
"product": "0x0001",
"vendor": "0x1af4",
"pxe_enabled": true
}
},
"lldp_raw": {
"eth0": [
[
1,
"04112233aabbcc"
],
[
5,
"737730312d646973742d31622d623132"
]
]
},
"macs": [
"52:54:00:4e:3d:30"
],
"root_disk": {
"hctl": null,
"model": "",
"name": "/dev/vda",
"rotational": true,
"serial": null,
"size": 13958643712,
"vendor": "0x1af4",
"wwn": null,
"wwn_vendor_extension": null,
"wwn_with_extension": null
}
}`, ExtraDataJSONSample)

var Inventory = inventory.InventoryType{
SystemVendor: inventory.SystemVendorType{
Manufacturer: "Bochs",
Expand Down Expand Up @@ -350,3 +432,75 @@ var NUMATopology = inventory.NUMATopology{
},
},
}

var StandardPluginData = inventory.StandardPluginData{
AllInterfaces: map[string]inventory.ProcessedInterfaceType{
"eth0": {
InterfaceType: inventory.InterfaceType{
Vendor: "0x1af4",
HasCarrier: true,
MACAddress: "52:54:00:47:20:4d",
Name: "eth1",
Product: "0x0001",
IPV4Address: "172.24.42.101",
},
PXEEnabled: true,
},
"eth1": {
InterfaceType: inventory.InterfaceType{
IPV4Address: "172.24.42.100",
MACAddress: "52:54:00:4e:3d:30",
Name: "eth0",
Product: "0x0001",
HasCarrier: true,
Vendor: "0x1af4",
SpeedMbps: 1000,
},
},
},
BootInterface: "52:54:00:4e:3d:30",
Configuration: inventory.ConfigurationType{
Collectors: []string{"default", "logs"},
Managers: []inventory.HardwareManager{
{
Name: "generic_hardware_manager",
Version: "1.1",
},
},
},
Error: "",
Extra: ExtraData,
MACs: []string{"52:54:00:4e:3d:30"},
RawLLDP: map[string][]inventory.LLDPTLVType{
"eth0": {
{
Type: 1,
Value: "04112233aabbcc",
},
{
Type: 5,
Value: "737730312d646973742d31622d623132",
},
},
},
RootDisk: inventory.RootDiskType{
Rotational: true,
Model: "",
Name: "/dev/vda",
Size: 13958643712,
Vendor: "0x1af4",
},
ValidInterfaces: map[string]inventory.ProcessedInterfaceType{
"eth0": {
InterfaceType: inventory.InterfaceType{
Vendor: "0x1af4",
HasCarrier: true,
MACAddress: "52:54:00:47:20:4d",
Name: "eth1",
Product: "0x0001",
IPV4Address: "172.24.42.101",
},
PXEEnabled: true,
},
},
}
14 changes: 12 additions & 2 deletions openstack/baremetal/inventory/testing/plugindata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"testing"

"github.com/gophercloud/gophercloud/openstack/baremetal/inventory"
"github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection"
th "github.com/gophercloud/gophercloud/testhelper"
)

Expand All @@ -20,11 +19,22 @@ func TestExtraHardware(t *testing.T) {
}

func TestIntrospectionNUMA(t *testing.T) {
var output introspection.Data
var output inventory.StandardPluginData
err := json.Unmarshal([]byte(NUMADataJSONSample), &output)
if err != nil {
t.Fatalf("Failed to unmarshal NUMA Data: %s", err)
}

th.CheckDeepEquals(t, NUMATopology, output.NUMATopology)
}

func TestStandardPluginData(t *testing.T) {
var output inventory.StandardPluginData

err := json.Unmarshal([]byte(StandardPluginDataSample), &output)
if err != nil {
t.Fatalf("Failed to unmarshal plugin data: %s", err)
}

th.CheckDeepEquals(t, StandardPluginData, output)
}
37 changes: 36 additions & 1 deletion openstack/baremetal/v1/nodes/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nodes

import (
"encoding/json"
"fmt"
"time"

"github.com/gophercloud/gophercloud"
Expand Down Expand Up @@ -546,12 +547,46 @@ func (pd PluginData) AsMap() (result map[string]interface{}, err error) {
return
}

// Interpret plugin data as coming from ironic-inspector.
// AsStandardData interprets plugin data as coming from ironic native inspection.
func (pd PluginData) AsStandardData() (result inventory.StandardPluginData, err error) {
err = json.Unmarshal(pd.RawMessage, &result)
return
}

// AsInspectorData interprets plugin data as coming from ironic-inspector.
func (pd PluginData) AsInspectorData() (result introspection.Data, err error) {
err = json.Unmarshal(pd.RawMessage, &result)
return
}

// GuessFormat tries to guess which format the data is in. Unless there is
// an error while parsing, one result will be valid, the other - nil.
// Unknown (but still parseable) format defaults to standard.
func (pd PluginData) GuessFormat() (*inventory.StandardPluginData, *introspection.Data, error) {
// Ironic and Inspector formats are compatible, don't expect an error in either case
ironic, err := pd.AsStandardData()
if err != nil {
return nil, nil, err
}

// The valid_interfaces field only exists in the Ironic data (it's called just interfaces in Inspector)
if len(ironic.ValidInterfaces) > 0 {
return &ironic, nil, nil
}

inspector, err := pd.AsInspectorData()
if err != nil {
return nil, nil, fmt.Errorf("cannot interpret PluginData as coming from inspector on conversion: %w", err)
}

// If the format does not match anything (but still parses), assume a heavily customized deployment
if len(inspector.Interfaces) == 0 {
return &ironic, nil, nil
}

return nil, &inspector, nil
}

// InventoryData is the full node inventory.
type InventoryData struct {
// Formally specified bare metal node inventory.
Expand Down
75 changes: 75 additions & 0 deletions openstack/baremetal/v1/nodes/testing/results_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package testing

import (
"testing"

"github.com/gophercloud/gophercloud/openstack/baremetal/inventory"
invtest "github.com/gophercloud/gophercloud/openstack/baremetal/inventory/testing"
"github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes"
"github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection"
insptest "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing"
"github.com/gophercloud/gophercloud/testhelper"
)

func TestStandardPluginData(t *testing.T) {
var pluginData nodes.PluginData
err := pluginData.RawMessage.UnmarshalJSON([]byte(invtest.StandardPluginDataSample))
testhelper.AssertNoErr(t, err)

parsedData, err := pluginData.AsStandardData()
testhelper.AssertNoErr(t, err)
testhelper.CheckDeepEquals(t, invtest.StandardPluginData, parsedData)

irData, inspData, err := pluginData.GuessFormat()
testhelper.AssertNoErr(t, err)
testhelper.CheckDeepEquals(t, invtest.StandardPluginData, *irData)
testhelper.CheckEquals(t, (*introspection.Data)(nil), inspData)
}

func TestInspectorPluginData(t *testing.T) {
var pluginData nodes.PluginData
err := pluginData.RawMessage.UnmarshalJSON([]byte(insptest.IntrospectionDataJSONSample))
testhelper.AssertNoErr(t, err)

parsedData, err := pluginData.AsInspectorData()
testhelper.AssertNoErr(t, err)
testhelper.CheckDeepEquals(t, insptest.IntrospectionDataRes, parsedData)

irData, inspData, err := pluginData.GuessFormat()
testhelper.AssertNoErr(t, err)
testhelper.CheckEquals(t, (*inventory.StandardPluginData)(nil), irData)
testhelper.CheckDeepEquals(t, insptest.IntrospectionDataRes, *inspData)
}

func TestGuessFormatUnknownDefaultsToIronic(t *testing.T) {
var pluginData nodes.PluginData
err := pluginData.RawMessage.UnmarshalJSON([]byte("{}"))
testhelper.AssertNoErr(t, err)

irData, inspData, err := pluginData.GuessFormat()
testhelper.CheckDeepEquals(t, inventory.StandardPluginData{}, *irData)
testhelper.CheckEquals(t, (*introspection.Data)(nil), inspData)
testhelper.AssertNoErr(t, err)
}

func TestGuessFormatErrors(t *testing.T) {
var pluginData nodes.PluginData
err := pluginData.RawMessage.UnmarshalJSON([]byte("\"banana\""))
testhelper.AssertNoErr(t, err)

irData, inspData, err := pluginData.GuessFormat()
testhelper.CheckEquals(t, (*inventory.StandardPluginData)(nil), irData)
testhelper.CheckEquals(t, (*introspection.Data)(nil), inspData)
testhelper.AssertErr(t, err)

failsInspectorConversion := `{
"interfaces": "banana"
}`
err = pluginData.RawMessage.UnmarshalJSON([]byte(failsInspectorConversion))
testhelper.AssertNoErr(t, err)

irData, inspData, err = pluginData.GuessFormat()
testhelper.CheckEquals(t, (*inventory.StandardPluginData)(nil), irData)
testhelper.CheckEquals(t, (*introspection.Data)(nil), inspData)
testhelper.AssertErr(t, err)
}

0 comments on commit 1fcb806

Please sign in to comment.