From 2dddab39bf2f6ffe0aea6c22cda62dfadd9522d9 Mon Sep 17 00:00:00 2001 From: Jingxiang Zhang Date: Wed, 16 Sep 2026 12:24:35 -0700 Subject: [PATCH 1/3] feat(node): expose OOB node kind (GPUHEALTH-3158) Signed-off-by: Jingxiang Zhang --- api/openapi/openapi.yaml | 123 ++++++++++ docs/cli.md | 4 +- docs/sdk.md | 14 +- internal/cmd/node/node.go | 4 +- internal/cmd/node/node_test.go | 41 +++- internal/generated/fleetapi/client.gen.go | 265 +++++++++++++++------- nvfleetint/node.go | 3 + nvfleetint/node_test.go | 9 +- nvfleetint/oob_inventory.go | 222 ++++++++++++------ nvfleetint/oob_inventory_test.go | 41 +++- 10 files changed, 568 insertions(+), 158 deletions(-) diff --git a/api/openapi/openapi.yaml b/api/openapi/openapi.yaml index 490baf2..f38f1ce 100644 --- a/api/openapi/openapi.yaml +++ b/api/openapi/openapi.yaml @@ -6783,11 +6783,15 @@ components: example: module type: string health: + description: 'Deprecated: use status.health.' example: OK type: string + deprecated: true healthRollup: + description: 'Deprecated: use status.healthRollup.' example: OK type: string + deprecated: true id: example: HGX_GPU_SXM_1 type: string @@ -6819,9 +6823,13 @@ components: sku: example: '0x37040020' type: string + status: + $ref: '#/components/schemas/models.OobInventoryStatus' statusState: + description: 'Deprecated: use status.state.' example: Enabled type: string + deprecated: true required: - id type: object @@ -6861,11 +6869,15 @@ components: models.OobFirmware: properties: health: + description: 'Deprecated: use status.health.' example: OK type: string + deprecated: true healthRollup: + description: 'Deprecated: use status.healthRollup.' example: OK type: string + deprecated: true id: example: HGX_FW_GPU_SXM_1 type: string @@ -6881,9 +6893,13 @@ components: serviceId: example: FirmwareInventory type: string + status: + $ref: '#/components/schemas/models.OobInventoryStatus' statusState: + description: 'Deprecated: use status.state.' example: Enabled type: string + deprecated: true version: example: 96.00.DA.00.0C type: string @@ -6917,6 +6933,8 @@ components: $ref: '#/components/schemas/models.OobManager' type: array uniqueItems: false + nodeKind: + $ref: '#/components/schemas/models.OobNodeKind' primarySystemId: description: 'Optional fields — omitted from the response when absent:' example: DGX @@ -6946,6 +6964,36 @@ components: - schemaVersion - source type: object + models.OobInventoryCondition: + properties: + conditionType: + example: Alert + type: string + message: + example: One or more errors were detected. + type: string + messageArgs: + items: + type: string + type: array + uniqueItems: false + messageId: + example: ResourceEvent.1.0.ResourceErrorsDetected + type: string + originOfCondition: + $ref: '#/components/schemas/models.OobInventoryReference' + resolution: + example: Review the related resource and replace the failed component. + type: string + severity: + example: Critical + type: string + timestamp: + example: '2026-07-23T17:20:43Z' + type: string + required: + - messageId + type: object models.OobInventoryNode: properties: agentVersion: @@ -7010,6 +7058,14 @@ components: example: 'Attestation verification unavailable: SDK error' type: string type: object + models.OobInventoryReference: + properties: + '@odata.id': + example: /redfish/v1/Chassis/HGX_GPU_SXM_1 + type: string + required: + - '@odata.id' + type: object models.OobInventoryReportResponse: properties: hasMore: @@ -7030,17 +7086,38 @@ components: example: 100 type: integer type: object + models.OobInventoryStatus: + properties: + conditions: + items: + $ref: '#/components/schemas/models.OobInventoryCondition' + type: array + uniqueItems: false + health: + example: OK + type: string + healthRollup: + example: Warning + type: string + state: + example: Enabled + type: string + type: object models.OobManager: properties: firmwareVersion: example: 26.03.03 type: string health: + description: 'Deprecated: use status.health.' example: OK type: string + deprecated: true healthRollup: + description: 'Deprecated: use status.healthRollup.' example: OK type: string + deprecated: true id: example: BMC type: string @@ -7053,9 +7130,13 @@ components: odataId: example: /redfish/v1/Managers/BMC type: string + status: + $ref: '#/components/schemas/models.OobInventoryStatus' statusState: + description: 'Deprecated: use status.state.' example: Enabled type: string + deprecated: true uuid: example: 6b00139a-bfde-1000-02b9-f2e9fb35ff5c type: string @@ -7110,6 +7191,8 @@ components: nodeGroup: example: node-group-1 type: string + nodeKind: + $ref: '#/components/schemas/models.OobNodeKind' nodeName: description: NodeName is the optional machine name configured on the OOB collector. @@ -7197,6 +7280,8 @@ components: nodeGroupId: example: 550e8400-e29b-41d4-a716-446655440001 type: string + nodeKind: + $ref: '#/components/schemas/models.OobNodeKind' nodeName: description: NodeName is the optional machine name configured on the OOB collector. @@ -7228,6 +7313,20 @@ components: required: - nodeUUID type: object + models.OobNodeKind: + default: unknown + enum: + - unknown + - compute + - nvswitch + - power_shelf + example: compute + type: string + x-enum-varnames: + - OobNodeKindUnknown + - OobNodeKindCompute + - OobNodeKindNvSwitch + - OobNodeKindPowerShelf models.OobNodesResponse: properties: hasMore: @@ -7257,11 +7356,15 @@ components: example: 96.00.DA.00.0C type: string health: + description: 'Deprecated: use status.health.' example: OK type: string + deprecated: true healthRollup: + description: 'Deprecated: use status.healthRollup.' example: OK type: string + deprecated: true id: example: GPU_SXM_1 type: string @@ -7283,9 +7386,13 @@ components: sku: example: '0x37040020' type: string + status: + $ref: '#/components/schemas/models.OobInventoryStatus' statusState: + description: 'Deprecated: use status.state.' example: Enabled type: string + deprecated: true uuid: example: beea8cdf-7b7d-035c-ed07-360e311fcbe1 type: string @@ -7295,11 +7402,15 @@ components: models.OobProcessor: properties: health: + description: 'Deprecated: use status.health.' example: OK type: string + deprecated: true healthRollup: + description: 'Deprecated: use status.healthRollup.' example: OK type: string + deprecated: true id: example: DevType1_CPU0 type: string @@ -7327,9 +7438,13 @@ components: socket: example: CPU0 type: string + status: + $ref: '#/components/schemas/models.OobInventoryStatus' statusState: + description: 'Deprecated: use status.state.' example: Enabled type: string + deprecated: true totalCores: example: 56 type: integer @@ -7385,11 +7500,15 @@ components: example: Intel(R) Xeon(R) Platinum 8480CL type: string health: + description: 'Deprecated: use status.health.' example: OK type: string + deprecated: true healthRollup: + description: 'Deprecated: use status.healthRollup.' example: OK type: string + deprecated: true hostName: example: dgx-01 type: string @@ -7425,9 +7544,13 @@ components: sku: example: 920-24387-2540-000 type: string + status: + $ref: '#/components/schemas/models.OobInventoryStatus' statusState: + description: 'Deprecated: use status.state.' example: Enabled type: string + deprecated: true uuid: example: 936672d4-bfde-1000-02bc-5cff35fbe9f2 type: string diff --git a/docs/cli.md b/docs/cli.md index 7961b8e..e7e721c 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -154,7 +154,9 @@ top-level `inband` and `oob` objects. Use `--agent-type inband` or `--agent-type oob` to request one view. OOB lists accept `--bmc-hostname` and the `bmcHostname` sort key. OOB describe tables can be narrowed with `--section managers,systems,chassis,firmware`; `--section all` includes every -inventory section. JSON describe output always includes the full inventory. +inventory section. Both the out-of-band node list and summary table include +`NODE KIND`. JSON describe output always includes +the full inventory, including nested Redfish status and condition details. The investigative alert workflow is `summary → node → describe`: start with impacted-node counts, inspect one node's alerts, then retrieve one alert's diff --git a/docs/sdk.md b/docs/sdk.md index c00104d..c11384b 100644 --- a/docs/sdk.md +++ b/docs/sdk.md @@ -58,10 +58,22 @@ node, err := client.DescribeNodeWithOptions(ctx, nodeUUID, nvfleetint.DescribeNo AgentType: nvfleetint.NodeAgentTypeOOB, }) if err == nil && node.OOBInventory != nil { - fmt.Println(node.OOBInventory.SchemaVersion) + fmt.Println(node.OOBInventory.SchemaVersion, node.OOBInventory.NodeKind) + for _, system := range node.OOBInventory.Systems { + if system.Status != nil { + fmt.Println(system.Status.State, system.Status.Health, system.Status.Conditions) + } + } } ``` +OOB node list and detail results expose the inventory classification as +`Node.NodeKind`. + +OOB inventory exposes node kinds (`unknown`, `compute`, `nvswitch`, and +`power_shelf`) and nested Redfish status conditions on systems, processors, +managers, chassis, PCIe devices, and firmware. + `NewClient` requires an HTTPS API URL and an API key. Plain HTTP is accepted only for loopback addresses used during local development. diff --git a/internal/cmd/node/node.go b/internal/cmd/node/node.go index 5f330ac..71c0af9 100644 --- a/internal/cmd/node/node.go +++ b/internal/cmd/node/node.go @@ -695,7 +695,7 @@ func writeNodeTable( if agentType == nvfleetint.NodeAgentTypeOOB || nodeListIsOOB(nodes) { return clioutput.WriteTable( w, - []string{"UUID", "NODE NAME", "BMC HOSTNAME", "BMC IP", "COMPUTE ZONE", "NODE GROUP", "HEALTH", "VERIFICATION CHECK", "AGENT STATUS"}, + []string{"UUID", "NODE NAME", "NODE KIND", "BMC HOSTNAME", "BMC IP", "COMPUTE ZONE", "NODE GROUP", "HEALTH", "VERIFICATION CHECK", "AGENT STATUS"}, oobDetailNodeRows(nodes), ) } @@ -754,6 +754,7 @@ func oobDetailNodeRows(nodes []nvfleetint.Node) [][]string { rows = append(rows, []string{ clioutput.DisplayString(node.UUID), clioutput.DisplayString(node.NodeName), + clioutput.DisplayString(string(node.NodeKind)), clioutput.DisplayString(node.BMCHostname), clioutput.DisplayString(node.BMCIP), clioutput.DisplayString(node.ComputeZone), @@ -1014,6 +1015,7 @@ func oobInventorySummaryRows(inventory *nvfleetint.OOBInventory) [][]string { return [][]string{ {"INVENTORY SCHEMA VERSION", clioutput.DisplayString(inventory.SchemaVersion)}, {"INVENTORY COLLECTED AT", clioutput.DisplayString(inventory.CollectedAt)}, + {"NODE KIND", clioutput.DisplayString(string(inventory.NodeKind))}, {"INVENTORY PRIMARY SYSTEM", clioutput.DisplayString(inventory.PrimarySystemID)}, {"INVENTORY MANAGERS", strconv.Itoa(len(inventory.Managers))}, {"INVENTORY SYSTEMS", strconv.Itoa(len(inventory.Systems))}, diff --git a/internal/cmd/node/node_test.go b/internal/cmd/node/node_test.go index aef233f..a95e227 100644 --- a/internal/cmd/node/node_test.go +++ b/internal/cmd/node/node_test.go @@ -276,7 +276,7 @@ func TestNodeListCombinedDetailViews(t *testing.T) { }`)) case "oob": _, _ = w.Write([]byte(`{ - "nodes":[{"nodeUUID":"node-oob","nodeName":"machine-001","agentType":"oob","bmcHostname":"bmc-001","bmcIP":"192.0.2.10:443","healthStatus":"Degraded"}], + "nodes":[{"nodeUUID":"node-oob","nodeName":"machine-001","nodeKind":"nvswitch","agentType":"oob","bmcHostname":"bmc-001","bmcIP":"192.0.2.10:443","healthStatus":"Degraded"}], "hasMore":false,"page":0,"pageSize":20,"total":1 }`)) default: @@ -299,7 +299,7 @@ func TestNodeListCombinedDetailViews(t *testing.T) { got := out.String() for _, want := range []string{ "In-band", "node-inband", "gpu-001", "GPU TYPE", - "Out-of-band", "node-oob", "machine-001", "NODE NAME", "bmc-001", "BMC HOSTNAME", + "Out-of-band", "node-oob", "machine-001", "NODE NAME", "nvswitch", "NODE KIND", "bmc-001", "BMC HOSTNAME", } { if !strings.Contains(got, want) { t.Fatalf("combined table missing %q: %q", want, got) @@ -330,7 +330,8 @@ func TestNodeListCombinedDetailViews(t *testing.T) { if len(combined.Inband.Nodes) != 1 || combined.Inband.Nodes[0]["nodeUUID"] != "node-inband" { t.Fatalf("unexpected in-band JSON: %#v", combined.Inband) } - if len(combined.OOB.Nodes) != 1 || combined.OOB.Nodes[0]["nodeUUID"] != "node-oob" { + if len(combined.OOB.Nodes) != 1 || combined.OOB.Nodes[0]["nodeUUID"] != "node-oob" || + combined.OOB.Nodes[0]["nodeKind"] != "nvswitch" { t.Fatalf("unexpected OOB JSON: %#v", combined.OOB) } if combined.Inband.Page != 1 || combined.OOB.Page != 1 { @@ -656,9 +657,10 @@ func TestNodeDescribeOOBTable(t *testing.T) { "bmcIP":"192.0.2.10", "oobInventory":{ "collectedAt":"2026-07-30T20:00:00Z", + "nodeKind":"nvswitch", "schemaVersion":"inventory.v1alpha1", "source":{"sourceType":"redfish","vendor":"Dell","address":"192.0.2.10:443","mac":"00:11:22:33:44:55","redfishVersion":"1.17.0"}, - "systems":[{"id":"System.Embedded.1","uuid":"system-uuid","manufacturer":"Dell","model":"PowerEdge XE9680","sku":"sku-1","serialNumber":"serial-1","biosVersion":"2.1.0","hostName":"host-001","assetTag":"asset-1","powerState":"on","statusState":"Enabled","health":"OK","healthRollup":"Warning","cpuCount":2,"memoryGib":2048,"secureBootEnabled":true,"processors":[{"id":"CPU.Socket.1","socket":"CPU 1","processorType":"cpu","processorArchitecture":"x86","manufacturer":"Intel","model":"Xeon","maxSpeedMhz":3800,"totalCores":56,"totalThreads":112,"statusState":"Enabled","health":"OK","healthRollup":"OK"},{"id":"GPU.Slot.1","processorType":"gpu","manufacturer":"NVIDIA","model":"NVIDIA H100","statusState":"Enabled","health":"OK","healthRollup":"OK"}]}], + "systems":[{"id":"System.Embedded.1","uuid":"system-uuid","manufacturer":"Dell","model":"PowerEdge XE9680","sku":"sku-1","serialNumber":"serial-1","biosVersion":"2.1.0","hostName":"host-001","assetTag":"asset-1","powerState":"on","status":{"state":"Enabled","health":"Critical","healthRollup":"Warning","conditions":[{"messageId":"ResourceEvent.1.0.ResourceErrorsDetected","severity":"Critical","message":"nested status condition"}]},"statusState":"Enabled","health":"OK","healthRollup":"Warning","cpuCount":2,"memoryGib":2048,"secureBootEnabled":true,"processors":[{"id":"CPU.Socket.1","socket":"CPU 1","processorType":"cpu","processorArchitecture":"x86","manufacturer":"Intel","model":"Xeon","maxSpeedMhz":3800,"totalCores":56,"totalThreads":112,"statusState":"Enabled","health":"OK","healthRollup":"OK"},{"id":"GPU.Slot.1","processorType":"gpu","manufacturer":"NVIDIA","model":"NVIDIA H100","statusState":"Enabled","health":"OK","healthRollup":"OK"}]}], "managers":[{"id":"iDRAC.Embedded.1","uuid":"manager-uuid","model":"iDRAC","managerType":"bmc","firmwareVersion":"7.10.00.00","statusState":"Enabled","health":"OK","healthRollup":"OK"}], "chassis":[{"id":"System.Embedded.1","chassisType":"rack_mount","manufacturer":"Dell","model":"XE9680","sku":"sku-1","serialNumber":"serial-1","partNumber":"part-1","assetTag":"asset-1","powerState":"on","statusState":"Enabled","health":"OK","healthRollup":"Warning","pcieDevices":[{"id":"GPU.Slot.1","uuid":"gpu-uuid","deviceType":"single_function","manufacturer":"NVIDIA","model":"NVIDIA H100","sku":"gpu-sku","serialNumber":"gpu-serial","partNumber":"gpu-part","firmwareVersion":"96.00.00","statusState":"Enabled","health":"OK","healthRollup":"OK"}]}], "firmware":[{"id":"BIOS","name":"System BIOS","serviceId":"fw-service","version":"1.2.3","releaseDate":"2026-01-01","statusState":"Enabled","health":"OK","healthRollup":"Warning"}], @@ -684,6 +686,7 @@ func TestNodeDescribeOOBTable(t *testing.T) { "FIELD", "VALUE", "BMC HOSTNAME", "bmc-001", "INVENTORY SCHEMA VERSION", "inventory.v1alpha1", + "NODE KIND", "nvswitch", "SOURCE ADDRESS", "192.0.2.10:443", "SOURCE MAC", "00:11:22:33:44:55", "SOURCE VENDOR", "Dell", "INVENTORY DOMAIN ERROR 1", "storage: Disk.1: collection failed", } { @@ -694,6 +697,7 @@ func TestNodeDescribeOOBTable(t *testing.T) { for _, unwanted := range []string{ "\nHOSTNAME ", "\nAGENT TYPE ", "\nGPU TYPE ", "\nGPU COUNT ", "\nFIRMWARE CHECK ", "\nPUBLIC IP ", "\nPRIVATE IP ", + "nested status condition", "ResourceEvent.1.0.ResourceErrorsDetected", } { if strings.Contains(got, unwanted) { t.Fatalf("OOB summary unexpectedly contains %q: %q", unwanted, got) @@ -756,6 +760,7 @@ func TestNodeDescribeOOBTable(t *testing.T) { got = out.String() for _, want := range []string{ "FIELD", "INVENTORY SCHEMA VERSION", "SOURCE ADDRESS", + "NODE KIND", "nvswitch", "\nManagers\n", "iDRAC.Embedded.1", "manager-uuid", "7.10.00.00", "\nSystems\n", "\nCPUs\n", "\nGPUs\n", "\nChassis\n", "\nPCIe Devices\n", @@ -765,6 +770,29 @@ func TestNodeDescribeOOBTable(t *testing.T) { t.Fatalf("all sections output missing %q: %q", want, got) } } + if strings.Contains(got, "nested status condition") || + strings.Contains(got, "ResourceEvent.1.0.ResourceErrorsDetected") { + t.Fatalf("table output unexpectedly contains nested status details: %q", got) + } + + out.Reset() + cmd = newRootCmd() + cmd.SetOut(&out) + cmd.SetArgs([]string{"node", "describe", "node-oob-1", "--agent-type", "oob", "--output", "json"}) + if err := cmd.Execute(); err != nil { + t.Fatalf("JSON command failed: %v", err) + } + jsonOutput := out.String() + for _, want := range []string{ + `"nodeKind":"nvswitch"`, + `"status":{`, + `"messageId":"ResourceEvent.1.0.ResourceErrorsDetected"`, + `"message":"nested status condition"`, + } { + if !strings.Contains(jsonOutput, want) { + t.Fatalf("JSON output missing %q: %q", want, jsonOutput) + } + } } // Verifies describe drill-down flags are validated before making an API request @@ -848,17 +876,18 @@ func TestOOBDetailNodeRowsOmitInbandHostname(t *testing.T) { UUID: "node-1", Hostname: "inband-hostname", NodeName: "machine-001", + NodeKind: nvfleetint.OOBNodeKindNVSwitch, BMCHostname: "bmc-001", BMCIP: "192.0.2.10:443", }}) - if len(rows) != 1 || len(rows[0]) != 9 { + if len(rows) != 1 || len(rows[0]) != 10 { t.Fatalf("unexpected OOB row shape: %#v", rows) } if slices.Contains(rows[0], "inband-hostname") { t.Fatalf("OOB row contains in-band hostname: %#v", rows[0]) } - if rows[0][1] != "machine-001" || rows[0][2] != "bmc-001" || rows[0][3] != "192.0.2.10:443" { + if rows[0][1] != "machine-001" || rows[0][2] != "nvswitch" || rows[0][3] != "bmc-001" || rows[0][4] != "192.0.2.10:443" { t.Fatalf("unexpected OOB identity columns: %#v", rows[0]) } } diff --git a/internal/generated/fleetapi/client.gen.go b/internal/generated/fleetapi/client.gen.go index 3d67a5d..8fe663b 100644 --- a/internal/generated/fleetapi/client.gen.go +++ b/internal/generated/fleetapi/client.gen.go @@ -395,6 +395,30 @@ func (e ModelsNotificationScopeType) Valid() bool { } } +// Defines values for ModelsOobNodeKind. +const ( + OobNodeKindCompute ModelsOobNodeKind = "compute" + OobNodeKindNvSwitch ModelsOobNodeKind = "nvswitch" + OobNodeKindPowerShelf ModelsOobNodeKind = "power_shelf" + OobNodeKindUnknown ModelsOobNodeKind = "unknown" +) + +// Valid indicates whether the value is a known member of the ModelsOobNodeKind enum. +func (e ModelsOobNodeKind) Valid() bool { + switch e { + case OobNodeKindCompute: + return true + case OobNodeKindNvSwitch: + return true + case OobNodeKindPowerShelf: + return true + case OobNodeKindUnknown: + return true + default: + return false + } +} + // Defines values for ModelsVerificationCheck. const ( VerificationCheckDegraded ModelsVerificationCheck = "Degraded" @@ -2563,9 +2587,15 @@ type ModelsNotificationTarget struct { // ModelsOobChassis defines model for models.OobChassis. type ModelsOobChassis struct { - AssetTag *string `json:"assetTag,omitempty"` - ChassisType *string `json:"chassisType,omitempty"` - Health *string `json:"health,omitempty"` + AssetTag *string `json:"assetTag,omitempty"` + ChassisType *string `json:"chassisType,omitempty"` + + // Health Deprecated: use status.health. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + Health *string `json:"health,omitempty"` + + // HealthRollup Deprecated: use status.healthRollup. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set HealthRollup *string `json:"healthRollup,omitempty"` Id string `json:"id"` Location *ModelsOobChassisLocation `json:"location,omitempty"` @@ -2577,7 +2607,11 @@ type ModelsOobChassis struct { PowerState *string `json:"powerState,omitempty"` SerialNumber *string `json:"serialNumber,omitempty"` Sku *string `json:"sku,omitempty"` - StatusState *string `json:"statusState,omitempty"` + Status *ModelsOobInventoryStatus `json:"status,omitempty"` + + // StatusState Deprecated: use status.state. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + StatusState *string `json:"statusState,omitempty"` } // ModelsOobChassisLocation defines model for models.OobChassisLocation. @@ -2598,15 +2632,24 @@ type ModelsOobDomainError struct { // ModelsOobFirmware defines model for models.OobFirmware. type ModelsOobFirmware struct { - Health *string `json:"health,omitempty"` - HealthRollup *string `json:"healthRollup,omitempty"` - Id string `json:"id"` - Name string `json:"name"` - OdataId *string `json:"odataId,omitempty"` - ReleaseDate *string `json:"releaseDate,omitempty"` - ServiceId string `json:"serviceId"` - StatusState *string `json:"statusState,omitempty"` - Version *string `json:"version,omitempty"` + // Health Deprecated: use status.health. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + Health *string `json:"health,omitempty"` + + // HealthRollup Deprecated: use status.healthRollup. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + HealthRollup *string `json:"healthRollup,omitempty"` + Id string `json:"id"` + Name string `json:"name"` + OdataId *string `json:"odataId,omitempty"` + ReleaseDate *string `json:"releaseDate,omitempty"` + ServiceId string `json:"serviceId"` + Status *ModelsOobInventoryStatus `json:"status,omitempty"` + + // StatusState Deprecated: use status.state. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + StatusState *string `json:"statusState,omitempty"` + Version *string `json:"version,omitempty"` } // ModelsOobInventory defines model for models.OobInventory. @@ -2616,6 +2659,7 @@ type ModelsOobInventory struct { DomainErrors *[]ModelsOobDomainError `json:"domainErrors,omitempty"` Firmware *[]ModelsOobFirmware `json:"firmware,omitempty"` Managers *[]ModelsOobManager `json:"managers,omitempty"` + NodeKind *ModelsOobNodeKind `json:"nodeKind,omitempty"` // PrimarySystemId Optional fields — omitted from the response when absent: PrimarySystemId *string `json:"primarySystemId,omitempty"` @@ -2632,6 +2676,18 @@ type ModelsOobInventory struct { TargetError *string `json:"targetError,omitempty"` } +// ModelsOobInventoryCondition defines model for models.OobInventoryCondition. +type ModelsOobInventoryCondition struct { + ConditionType *string `json:"conditionType,omitempty"` + Message *string `json:"message,omitempty"` + MessageArgs *[]string `json:"messageArgs,omitempty"` + MessageId string `json:"messageId"` + OriginOfCondition *ModelsOobInventoryReference `json:"originOfCondition,omitempty"` + Resolution *string `json:"resolution,omitempty"` + Severity *string `json:"severity,omitempty"` + Timestamp *string `json:"timestamp,omitempty"` +} + // ModelsOobInventoryNode defines model for models.OobInventoryNode. type ModelsOobInventoryNode struct { AgentVersion *string `json:"agentVersion,omitempty"` @@ -2670,6 +2726,11 @@ type ModelsOobInventoryNode struct { VerificationCheckReason *string `json:"verificationCheckReason,omitempty"` } +// ModelsOobInventoryReference defines model for models.OobInventoryReference. +type ModelsOobInventoryReference struct { + OdataId string `json:"@odata.id"` +} + // ModelsOobInventoryReportResponse defines model for models.OobInventoryReportResponse. type ModelsOobInventoryReportResponse struct { HasMore *bool `json:"hasMore,omitempty"` @@ -2679,17 +2740,35 @@ type ModelsOobInventoryReportResponse struct { Total *int `json:"total,omitempty"` } +// ModelsOobInventoryStatus defines model for models.OobInventoryStatus. +type ModelsOobInventoryStatus struct { + Conditions *[]ModelsOobInventoryCondition `json:"conditions,omitempty"` + Health *string `json:"health,omitempty"` + HealthRollup *string `json:"healthRollup,omitempty"` + State *string `json:"state,omitempty"` +} + // ModelsOobManager defines model for models.OobManager. type ModelsOobManager struct { FirmwareVersion *string `json:"firmwareVersion,omitempty"` - Health *string `json:"health,omitempty"` - HealthRollup *string `json:"healthRollup,omitempty"` - Id string `json:"id"` - ManagerType *string `json:"managerType,omitempty"` - Model *string `json:"model,omitempty"` - OdataId *string `json:"odataId,omitempty"` - StatusState *string `json:"statusState,omitempty"` - Uuid *string `json:"uuid,omitempty"` + + // Health Deprecated: use status.health. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + Health *string `json:"health,omitempty"` + + // HealthRollup Deprecated: use status.healthRollup. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + HealthRollup *string `json:"healthRollup,omitempty"` + Id string `json:"id"` + ManagerType *string `json:"managerType,omitempty"` + Model *string `json:"model,omitempty"` + OdataId *string `json:"odataId,omitempty"` + Status *ModelsOobInventoryStatus `json:"status,omitempty"` + + // StatusState Deprecated: use status.state. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + StatusState *string `json:"statusState,omitempty"` + Uuid *string `json:"uuid,omitempty"` } // ModelsOobNode defines model for models.OobNode. @@ -2716,10 +2795,11 @@ type ModelsOobNode struct { // LastIntegrityCheckTS Deprecated: use lastVerificationCheckTS. // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set - LastIntegrityCheckTS *string `json:"lastIntegrityCheckTS,omitempty"` - LastUpdatedTS *string `json:"lastUpdatedTS,omitempty"` - LastVerificationCheckTS *string `json:"lastVerificationCheckTS,omitempty"` - NodeGroup *string `json:"nodeGroup,omitempty"` + LastIntegrityCheckTS *string `json:"lastIntegrityCheckTS,omitempty"` + LastUpdatedTS *string `json:"lastUpdatedTS,omitempty"` + LastVerificationCheckTS *string `json:"lastVerificationCheckTS,omitempty"` + NodeGroup *string `json:"nodeGroup,omitempty"` + NodeKind *ModelsOobNodeKind `json:"nodeKind,omitempty"` // NodeName NodeName is the optional machine name configured on the OOB collector. NodeName *string `json:"nodeName,omitempty"` @@ -2761,12 +2841,13 @@ type ModelsOobNodeDetailsResponse struct { // LastIntegrityCheckTS Deprecated: use lastVerificationCheckTS. // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set - LastIntegrityCheckTS *string `json:"lastIntegrityCheckTS,omitempty"` - LastUpdatedTS *string `json:"lastUpdatedTS,omitempty"` - LastVerificationCheckTS *string `json:"lastVerificationCheckTS,omitempty"` - Location *ModelsLocation `json:"location,omitempty"` - NodeGroup *string `json:"nodeGroup,omitempty"` - NodeGroupId *string `json:"nodeGroupId,omitempty"` + LastIntegrityCheckTS *string `json:"lastIntegrityCheckTS,omitempty"` + LastUpdatedTS *string `json:"lastUpdatedTS,omitempty"` + LastVerificationCheckTS *string `json:"lastVerificationCheckTS,omitempty"` + Location *ModelsLocation `json:"location,omitempty"` + NodeGroup *string `json:"nodeGroup,omitempty"` + NodeGroupId *string `json:"nodeGroupId,omitempty"` + NodeKind *ModelsOobNodeKind `json:"nodeKind,omitempty"` // NodeName NodeName is the optional machine name configured on the OOB collector. NodeName *string `json:"nodeName,omitempty"` @@ -2779,6 +2860,9 @@ type ModelsOobNodeDetailsResponse struct { VerificationCheckReason *string `json:"verificationCheckReason,omitempty"` } +// ModelsOobNodeKind defines model for models.OobNodeKind. +type ModelsOobNodeKind string + // ModelsOobNodesResponse defines model for models.OobNodesResponse. type ModelsOobNodesResponse struct { HasMore *bool `json:"hasMore,omitempty"` @@ -2792,35 +2876,54 @@ type ModelsOobNodesResponse struct { type ModelsOobPcieDevice struct { DeviceType *string `json:"deviceType,omitempty"` FirmwareVersion *string `json:"firmwareVersion,omitempty"` - Health *string `json:"health,omitempty"` - HealthRollup *string `json:"healthRollup,omitempty"` - Id string `json:"id"` - Manufacturer *string `json:"manufacturer,omitempty"` - Model *string `json:"model,omitempty"` - OdataId *string `json:"odataId,omitempty"` - PartNumber *string `json:"partNumber,omitempty"` - SerialNumber *string `json:"serialNumber,omitempty"` - Sku *string `json:"sku,omitempty"` - StatusState *string `json:"statusState,omitempty"` - Uuid *string `json:"uuid,omitempty"` + + // Health Deprecated: use status.health. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + Health *string `json:"health,omitempty"` + + // HealthRollup Deprecated: use status.healthRollup. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + HealthRollup *string `json:"healthRollup,omitempty"` + Id string `json:"id"` + Manufacturer *string `json:"manufacturer,omitempty"` + Model *string `json:"model,omitempty"` + OdataId *string `json:"odataId,omitempty"` + PartNumber *string `json:"partNumber,omitempty"` + SerialNumber *string `json:"serialNumber,omitempty"` + Sku *string `json:"sku,omitempty"` + Status *ModelsOobInventoryStatus `json:"status,omitempty"` + + // StatusState Deprecated: use status.state. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + StatusState *string `json:"statusState,omitempty"` + Uuid *string `json:"uuid,omitempty"` } // ModelsOobProcessor defines model for models.OobProcessor. type ModelsOobProcessor struct { - Health *string `json:"health,omitempty"` - HealthRollup *string `json:"healthRollup,omitempty"` - Id string `json:"id"` - InstructionSet *string `json:"instructionSet,omitempty"` - Manufacturer *string `json:"manufacturer,omitempty"` - MaxSpeedMhz *int `json:"maxSpeedMhz,omitempty"` - Model *string `json:"model,omitempty"` - OdataId *string `json:"odataId,omitempty"` - ProcessorArchitecture *string `json:"processorArchitecture,omitempty"` - ProcessorType *string `json:"processorType,omitempty"` - Socket *string `json:"socket,omitempty"` - StatusState *string `json:"statusState,omitempty"` - TotalCores *int `json:"totalCores,omitempty"` - TotalThreads *int `json:"totalThreads,omitempty"` + // Health Deprecated: use status.health. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + Health *string `json:"health,omitempty"` + + // HealthRollup Deprecated: use status.healthRollup. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + HealthRollup *string `json:"healthRollup,omitempty"` + Id string `json:"id"` + InstructionSet *string `json:"instructionSet,omitempty"` + Manufacturer *string `json:"manufacturer,omitempty"` + MaxSpeedMhz *int `json:"maxSpeedMhz,omitempty"` + Model *string `json:"model,omitempty"` + OdataId *string `json:"odataId,omitempty"` + ProcessorArchitecture *string `json:"processorArchitecture,omitempty"` + ProcessorType *string `json:"processorType,omitempty"` + Socket *string `json:"socket,omitempty"` + Status *ModelsOobInventoryStatus `json:"status,omitempty"` + + // StatusState Deprecated: use status.state. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + StatusState *string `json:"statusState,omitempty"` + TotalCores *int `json:"totalCores,omitempty"` + TotalThreads *int `json:"totalThreads,omitempty"` } // ModelsOobSource defines model for models.OobSource. @@ -2836,26 +2939,36 @@ type ModelsOobSource struct { // ModelsOobSystem defines model for models.OobSystem. type ModelsOobSystem struct { - AssetTag *string `json:"assetTag,omitempty"` - BiosVersion *string `json:"biosVersion,omitempty"` - CpuCoreCount *int `json:"cpuCoreCount,omitempty"` - CpuCount *int `json:"cpuCount,omitempty"` - CpuModel *string `json:"cpuModel,omitempty"` - Health *string `json:"health,omitempty"` - HealthRollup *string `json:"healthRollup,omitempty"` - HostName *string `json:"hostName,omitempty"` - Id string `json:"id"` - Manufacturer *string `json:"manufacturer,omitempty"` - MemoryGib *float32 `json:"memoryGib,omitempty"` - Model *string `json:"model,omitempty"` - OdataId *string `json:"odataId,omitempty"` - PowerState *string `json:"powerState,omitempty"` - Processors *[]ModelsOobProcessor `json:"processors,omitempty"` - SecureBootEnabled *bool `json:"secureBootEnabled,omitempty"` - SerialNumber *string `json:"serialNumber,omitempty"` - Sku *string `json:"sku,omitempty"` - StatusState *string `json:"statusState,omitempty"` - Uuid *string `json:"uuid,omitempty"` + AssetTag *string `json:"assetTag,omitempty"` + BiosVersion *string `json:"biosVersion,omitempty"` + CpuCoreCount *int `json:"cpuCoreCount,omitempty"` + CpuCount *int `json:"cpuCount,omitempty"` + CpuModel *string `json:"cpuModel,omitempty"` + + // Health Deprecated: use status.health. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + Health *string `json:"health,omitempty"` + + // HealthRollup Deprecated: use status.healthRollup. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + HealthRollup *string `json:"healthRollup,omitempty"` + HostName *string `json:"hostName,omitempty"` + Id string `json:"id"` + Manufacturer *string `json:"manufacturer,omitempty"` + MemoryGib *float32 `json:"memoryGib,omitempty"` + Model *string `json:"model,omitempty"` + OdataId *string `json:"odataId,omitempty"` + PowerState *string `json:"powerState,omitempty"` + Processors *[]ModelsOobProcessor `json:"processors,omitempty"` + SecureBootEnabled *bool `json:"secureBootEnabled,omitempty"` + SerialNumber *string `json:"serialNumber,omitempty"` + Sku *string `json:"sku,omitempty"` + Status *ModelsOobInventoryStatus `json:"status,omitempty"` + + // StatusState Deprecated: use status.state. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + StatusState *string `json:"statusState,omitempty"` + Uuid *string `json:"uuid,omitempty"` } // ModelsOptions defines model for models.Options. diff --git a/nvfleetint/node.go b/nvfleetint/node.go index 6370c41..72d3c4a 100644 --- a/nvfleetint/node.go +++ b/nvfleetint/node.go @@ -182,6 +182,7 @@ type Node struct { UUID string `json:"nodeUUID"` Hostname string `json:"hostname,omitempty"` NodeName string `json:"nodeName,omitempty"` + NodeKind OOBNodeKind `json:"nodeKind,omitempty"` AgentType string `json:"agentType,omitempty"` AgentVersion string `json:"agentVersion,omitempty"` BMCHostname string `json:"bmcHostname,omitempty"` @@ -685,6 +686,7 @@ func oobNodeFromGenerated(node fleetapi.ModelsOobNode) Node { return Node{ UUID: node.NodeUUID, NodeName: stringValue(node.NodeName), + NodeKind: oobNodeKindFromGenerated(node.NodeKind), AgentType: stringValue(node.AgentType), AgentVersion: stringValue(node.AgentVersion), BMCHostname: stringValue(node.BmcHostname), @@ -757,6 +759,7 @@ func oobNodeDetailsFromGenerated(node fleetapi.ModelsOobNodeDetailsResponse) Nod Node: Node{ UUID: node.NodeUUID, NodeName: stringValue(node.NodeName), + NodeKind: oobNodeKindFromGenerated(node.NodeKind), AgentType: stringValue(node.AgentType), AgentVersion: stringValue(node.AgentVersion), BMCHostname: stringValue(node.BmcHostname), diff --git a/nvfleetint/node_test.go b/nvfleetint/node_test.go index 785efa8..6505e3b 100644 --- a/nvfleetint/node_test.go +++ b/nvfleetint/node_test.go @@ -248,6 +248,7 @@ func TestDescribeNodeOOBDecodesInventory(t *testing.T) { "nodeUUID":"node-oob-1", "hostname":"host-001", "nodeName":"machine-001", + "nodeKind":"nvswitch", "agentType":"oob", "bmcHostname":"bmc-001", "bmcIP":"192.0.2.10", @@ -276,7 +277,8 @@ func TestDescribeNodeOOBDecodesInventory(t *testing.T) { if err != nil { t.Fatalf("describe failed: %v", err) } - if got.AgentType != "oob" || got.NodeName != "machine-001" || got.BMCHostname != "bmc-001" || got.BMCIP != "192.0.2.10" { + if got.AgentType != "oob" || got.NodeName != "machine-001" || got.NodeKind != OOBNodeKindNVSwitch || + got.BMCHostname != "bmc-001" || got.BMCIP != "192.0.2.10" { t.Fatalf("unexpected OOB node fields: %#v", got.Node) } if got.OOBInventory == nil || got.OOBInventory.SchemaVersion != "inventory.v1alpha1" { @@ -307,7 +309,7 @@ func TestListNodesOOB(t *testing.T) { t.Fatalf("unexpected query: %q", r.URL.RawQuery) } w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"nodes":[{"nodeUUID":"node-oob-1","hostname":"host-001","nodeName":"machine-001","agentType":"oob","bmcHostname":"bmc-001","bmcIP":"192.0.2.10"}],"hasMore":false,"page":0,"pageSize":20,"total":1}`)) + _, _ = w.Write([]byte(`{"nodes":[{"nodeUUID":"node-oob-1","hostname":"host-001","nodeName":"machine-001","nodeKind":"nvswitch","agentType":"oob","bmcHostname":"bmc-001","bmcIP":"192.0.2.10"}],"hasMore":false,"page":0,"pageSize":20,"total":1}`)) })) defer server.Close() @@ -324,7 +326,8 @@ func TestListNodesOOB(t *testing.T) { if err != nil { t.Fatalf("list failed: %v", err) } - if len(got.Nodes) != 1 || got.Nodes[0].NodeName != "machine-001" || got.Nodes[0].BMCHostname != "bmc-001" || got.Nodes[0].BMCIP != "192.0.2.10" { + if len(got.Nodes) != 1 || got.Nodes[0].NodeName != "machine-001" || got.Nodes[0].NodeKind != OOBNodeKindNVSwitch || + got.Nodes[0].BMCHostname != "bmc-001" || got.Nodes[0].BMCIP != "192.0.2.10" { t.Fatalf("unexpected OOB nodes: %#v", got.Nodes) } } diff --git a/nvfleetint/oob_inventory.go b/nvfleetint/oob_inventory.go index 7dc7fe2..f8a1915 100644 --- a/nvfleetint/oob_inventory.go +++ b/nvfleetint/oob_inventory.go @@ -5,12 +5,23 @@ package nvfleetint import "github.com/NVIDIA/fleet-intelligence-client/internal/generated/fleetapi" +// OOBNodeKind classifies the independently addressable node that produced an OOB inventory document. +type OOBNodeKind string + +const ( + OOBNodeKindUnknown OOBNodeKind = "unknown" + OOBNodeKindCompute OOBNodeKind = "compute" + OOBNodeKindNVSwitch OOBNodeKind = "nvswitch" + OOBNodeKindPowerShelf OOBNodeKind = "power_shelf" +) + // Represents inventory collected out of band through a node's BMC type OOBInventory struct { Chassis []OOBChassis `json:"chassis,omitempty"` CollectedAt string `json:"collectedAt"` DomainErrors []OOBDomainError `json:"domainErrors,omitempty"` Firmware []OOBFirmware `json:"firmware,omitempty"` + NodeKind OOBNodeKind `json:"nodeKind,omitempty"` Managers []OOBManager `json:"managers,omitempty"` PrimarySystemID string `json:"primarySystemId,omitempty"` SchemaVersion string `json:"schemaVersion"` @@ -19,6 +30,31 @@ type OOBInventory struct { TargetError string `json:"targetError,omitempty"` } +// OOBInventoryStatus represents the Redfish status reported for an inventory resource. +type OOBInventoryStatus struct { + Conditions []OOBInventoryCondition `json:"conditions,omitempty"` + Health string `json:"health,omitempty"` + HealthRollup string `json:"healthRollup,omitempty"` + State string `json:"state,omitempty"` +} + +// OOBInventoryCondition contains the details of one Redfish status condition. +type OOBInventoryCondition struct { + ConditionType string `json:"conditionType,omitempty"` + Message string `json:"message,omitempty"` + MessageArgs []string `json:"messageArgs,omitempty"` + MessageID string `json:"messageId"` + OriginOfCondition *OOBInventoryReference `json:"originOfCondition,omitempty"` + Resolution string `json:"resolution,omitempty"` + Severity string `json:"severity,omitempty"` + Timestamp string `json:"timestamp,omitempty"` +} + +// OOBInventoryReference identifies the Redfish resource associated with a condition. +type OOBInventoryReference struct { + ODataID string `json:"@odata.id"` +} + // Identifies the source used to collect OOB inventory type OOBSource struct { Address string `json:"address,omitempty"` @@ -32,57 +68,60 @@ type OOBSource struct { // Represents a computer system reported by the BMC type OOBSystem struct { - AssetTag string `json:"assetTag,omitempty"` - BIOSVersion string `json:"biosVersion,omitempty"` - CPUCoreCount *int `json:"cpuCoreCount,omitempty"` - CPUCount *int `json:"cpuCount,omitempty"` - CPUModel string `json:"cpuModel,omitempty"` - Health string `json:"health,omitempty"` - HealthRollup string `json:"healthRollup,omitempty"` - Hostname string `json:"hostName,omitempty"` - ID string `json:"id,omitempty"` - Manufacturer string `json:"manufacturer,omitempty"` - MemoryGiB *float32 `json:"memoryGib,omitempty"` - Model string `json:"model,omitempty"` - ODataID string `json:"odataId,omitempty"` - PowerState string `json:"powerState,omitempty"` - Processors []OOBProcessor `json:"processors,omitempty"` - SecureBootEnabled *bool `json:"secureBootEnabled,omitempty"` - SerialNumber string `json:"serialNumber,omitempty"` - SKU string `json:"sku,omitempty"` - StatusState string `json:"statusState,omitempty"` - UUID string `json:"uuid,omitempty"` + AssetTag string `json:"assetTag,omitempty"` + BIOSVersion string `json:"biosVersion,omitempty"` + CPUCoreCount *int `json:"cpuCoreCount,omitempty"` + CPUCount *int `json:"cpuCount,omitempty"` + CPUModel string `json:"cpuModel,omitempty"` + Health string `json:"health,omitempty"` + HealthRollup string `json:"healthRollup,omitempty"` + Hostname string `json:"hostName,omitempty"` + ID string `json:"id,omitempty"` + Manufacturer string `json:"manufacturer,omitempty"` + MemoryGiB *float32 `json:"memoryGib,omitempty"` + Model string `json:"model,omitempty"` + ODataID string `json:"odataId,omitempty"` + PowerState string `json:"powerState,omitempty"` + Processors []OOBProcessor `json:"processors,omitempty"` + SecureBootEnabled *bool `json:"secureBootEnabled,omitempty"` + SerialNumber string `json:"serialNumber,omitempty"` + SKU string `json:"sku,omitempty"` + Status *OOBInventoryStatus `json:"status,omitempty"` + StatusState string `json:"statusState,omitempty"` + UUID string `json:"uuid,omitempty"` } // Represents a processor reported by the BMC type OOBProcessor struct { - Health string `json:"health,omitempty"` - HealthRollup string `json:"healthRollup,omitempty"` - ID string `json:"id,omitempty"` - InstructionSet string `json:"instructionSet,omitempty"` - Manufacturer string `json:"manufacturer,omitempty"` - MaxSpeedMHz *int `json:"maxSpeedMhz,omitempty"` - Model string `json:"model,omitempty"` - ODataID string `json:"odataId,omitempty"` - ProcessorArchitecture string `json:"processorArchitecture,omitempty"` - ProcessorType string `json:"processorType,omitempty"` - Socket string `json:"socket,omitempty"` - StatusState string `json:"statusState,omitempty"` - TotalCores *int `json:"totalCores,omitempty"` - TotalThreads *int `json:"totalThreads,omitempty"` + Health string `json:"health,omitempty"` + HealthRollup string `json:"healthRollup,omitempty"` + ID string `json:"id,omitempty"` + InstructionSet string `json:"instructionSet,omitempty"` + Manufacturer string `json:"manufacturer,omitempty"` + MaxSpeedMHz *int `json:"maxSpeedMhz,omitempty"` + Model string `json:"model,omitempty"` + ODataID string `json:"odataId,omitempty"` + ProcessorArchitecture string `json:"processorArchitecture,omitempty"` + ProcessorType string `json:"processorType,omitempty"` + Socket string `json:"socket,omitempty"` + Status *OOBInventoryStatus `json:"status,omitempty"` + StatusState string `json:"statusState,omitempty"` + TotalCores *int `json:"totalCores,omitempty"` + TotalThreads *int `json:"totalThreads,omitempty"` } // Represents a BMC manager type OOBManager struct { - FirmwareVersion string `json:"firmwareVersion,omitempty"` - Health string `json:"health,omitempty"` - HealthRollup string `json:"healthRollup,omitempty"` - ID string `json:"id,omitempty"` - ManagerType string `json:"managerType,omitempty"` - Model string `json:"model,omitempty"` - ODataID string `json:"odataId,omitempty"` - StatusState string `json:"statusState,omitempty"` - UUID string `json:"uuid,omitempty"` + FirmwareVersion string `json:"firmwareVersion,omitempty"` + Health string `json:"health,omitempty"` + HealthRollup string `json:"healthRollup,omitempty"` + ID string `json:"id,omitempty"` + ManagerType string `json:"managerType,omitempty"` + Model string `json:"model,omitempty"` + ODataID string `json:"odataId,omitempty"` + Status *OOBInventoryStatus `json:"status,omitempty"` + StatusState string `json:"statusState,omitempty"` + UUID string `json:"uuid,omitempty"` } // Represents a chassis reported by the BMC @@ -101,6 +140,7 @@ type OOBChassis struct { PowerState string `json:"powerState,omitempty"` SerialNumber string `json:"serialNumber,omitempty"` SKU string `json:"sku,omitempty"` + Status *OOBInventoryStatus `json:"status,omitempty"` StatusState string `json:"statusState,omitempty"` } @@ -115,32 +155,34 @@ type OOBChassisLocation struct { // Represents a PCIe device reported by the BMC type OOBPCIeDevice struct { - DeviceType string `json:"deviceType,omitempty"` - FirmwareVersion string `json:"firmwareVersion,omitempty"` - Health string `json:"health,omitempty"` - HealthRollup string `json:"healthRollup,omitempty"` - ID string `json:"id,omitempty"` - Manufacturer string `json:"manufacturer,omitempty"` - Model string `json:"model,omitempty"` - ODataID string `json:"odataId,omitempty"` - PartNumber string `json:"partNumber,omitempty"` - SerialNumber string `json:"serialNumber,omitempty"` - SKU string `json:"sku,omitempty"` - StatusState string `json:"statusState,omitempty"` - UUID string `json:"uuid,omitempty"` + DeviceType string `json:"deviceType,omitempty"` + FirmwareVersion string `json:"firmwareVersion,omitempty"` + Health string `json:"health,omitempty"` + HealthRollup string `json:"healthRollup,omitempty"` + ID string `json:"id,omitempty"` + Manufacturer string `json:"manufacturer,omitempty"` + Model string `json:"model,omitempty"` + ODataID string `json:"odataId,omitempty"` + PartNumber string `json:"partNumber,omitempty"` + SerialNumber string `json:"serialNumber,omitempty"` + SKU string `json:"sku,omitempty"` + Status *OOBInventoryStatus `json:"status,omitempty"` + StatusState string `json:"statusState,omitempty"` + UUID string `json:"uuid,omitempty"` } // Represents a firmware inventory entry reported by the BMC type OOBFirmware struct { - Health string `json:"health,omitempty"` - HealthRollup string `json:"healthRollup,omitempty"` - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - ODataID string `json:"odataId,omitempty"` - ReleaseDate string `json:"releaseDate,omitempty"` - ServiceID string `json:"serviceId,omitempty"` - StatusState string `json:"statusState,omitempty"` - Version string `json:"version,omitempty"` + Health string `json:"health,omitempty"` + HealthRollup string `json:"healthRollup,omitempty"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + ODataID string `json:"odataId,omitempty"` + ReleaseDate string `json:"releaseDate,omitempty"` + ServiceID string `json:"serviceId,omitempty"` + Status *OOBInventoryStatus `json:"status,omitempty"` + StatusState string `json:"statusState,omitempty"` + Version string `json:"version,omitempty"` } // Represents a collection error scoped to one OOB inventory domain @@ -157,6 +199,7 @@ func oobInventoryFromGenerated(inventory *fleetapi.ModelsOobInventory) *OOBInven out := &OOBInventory{ CollectedAt: inventory.CollectedAt, + NodeKind: oobNodeKindFromGenerated(inventory.NodeKind), PrimarySystemID: stringValue(inventory.PrimarySystemId), SchemaVersion: inventory.SchemaVersion, Source: oobSourceFromGenerated(inventory.Source), @@ -226,6 +269,7 @@ func oobSystemFromGenerated(system fleetapi.ModelsOobSystem) OOBSystem { SecureBootEnabled: cloneBool(system.SecureBootEnabled), SerialNumber: stringValue(system.SerialNumber), SKU: stringValue(system.Sku), + Status: oobInventoryStatusFromGenerated(system.Status), StatusState: stringValue(system.StatusState), UUID: stringValue(system.Uuid), } @@ -251,6 +295,7 @@ func oobProcessorFromGenerated(processor fleetapi.ModelsOobProcessor) OOBProcess ProcessorArchitecture: stringValue(processor.ProcessorArchitecture), ProcessorType: stringValue(processor.ProcessorType), Socket: stringValue(processor.Socket), + Status: oobInventoryStatusFromGenerated(processor.Status), StatusState: stringValue(processor.StatusState), TotalCores: cloneInt(processor.TotalCores), TotalThreads: cloneInt(processor.TotalThreads), @@ -266,6 +311,7 @@ func oobManagerFromGenerated(manager fleetapi.ModelsOobManager) OOBManager { ManagerType: stringValue(manager.ManagerType), Model: stringValue(manager.Model), ODataID: stringValue(manager.OdataId), + Status: oobInventoryStatusFromGenerated(manager.Status), StatusState: stringValue(manager.StatusState), UUID: stringValue(manager.Uuid), } @@ -286,6 +332,7 @@ func oobChassisFromGenerated(chassis fleetapi.ModelsOobChassis) OOBChassis { PowerState: stringValue(chassis.PowerState), SerialNumber: stringValue(chassis.SerialNumber), SKU: stringValue(chassis.Sku), + Status: oobInventoryStatusFromGenerated(chassis.Status), StatusState: stringValue(chassis.StatusState), } if chassis.PcieDevices != nil { @@ -323,6 +370,7 @@ func oobPCIeDeviceFromGenerated(device fleetapi.ModelsOobPcieDevice) OOBPCIeDevi PartNumber: stringValue(device.PartNumber), SerialNumber: stringValue(device.SerialNumber), SKU: stringValue(device.Sku), + Status: oobInventoryStatusFromGenerated(device.Status), StatusState: stringValue(device.StatusState), UUID: stringValue(device.Uuid), } @@ -337,11 +385,55 @@ func oobFirmwareFromGenerated(firmware fleetapi.ModelsOobFirmware) OOBFirmware { ODataID: stringValue(firmware.OdataId), ReleaseDate: stringValue(firmware.ReleaseDate), ServiceID: firmware.ServiceId, + Status: oobInventoryStatusFromGenerated(firmware.Status), StatusState: stringValue(firmware.StatusState), Version: stringValue(firmware.Version), } } +func oobNodeKindFromGenerated(kind *fleetapi.ModelsOobNodeKind) OOBNodeKind { + if kind == nil { + return "" + } + return OOBNodeKind(*kind) +} + +func oobInventoryStatusFromGenerated(status *fleetapi.ModelsOobInventoryStatus) *OOBInventoryStatus { + if status == nil { + return nil + } + out := &OOBInventoryStatus{ + Health: stringValue(status.Health), + HealthRollup: stringValue(status.HealthRollup), + State: stringValue(status.State), + } + if status.Conditions != nil { + out.Conditions = make([]OOBInventoryCondition, 0, len(*status.Conditions)) + for _, condition := range *status.Conditions { + out.Conditions = append(out.Conditions, oobInventoryConditionFromGenerated(condition)) + } + } + return out +} + +func oobInventoryConditionFromGenerated(condition fleetapi.ModelsOobInventoryCondition) OOBInventoryCondition { + out := OOBInventoryCondition{ + ConditionType: stringValue(condition.ConditionType), + Message: stringValue(condition.Message), + MessageID: condition.MessageId, + Resolution: stringValue(condition.Resolution), + Severity: stringValue(condition.Severity), + Timestamp: stringValue(condition.Timestamp), + } + if condition.MessageArgs != nil { + out.MessageArgs = append([]string(nil), (*condition.MessageArgs)...) + } + if condition.OriginOfCondition != nil { + out.OriginOfCondition = &OOBInventoryReference{ODataID: condition.OriginOfCondition.OdataId} + } + return out +} + func oobDomainErrorFromGenerated(domainError fleetapi.ModelsOobDomainError) OOBDomainError { return OOBDomainError{ Domain: domainError.Domain, diff --git a/nvfleetint/oob_inventory_test.go b/nvfleetint/oob_inventory_test.go index d2d3530..8a7b6a7 100644 --- a/nvfleetint/oob_inventory_test.go +++ b/nvfleetint/oob_inventory_test.go @@ -11,6 +11,23 @@ import ( // Verifies generated OOB inventory models are mapped into the public SDK models. func TestOOBInventoryFromGenerated(t *testing.T) { + status := &fleetapi.ModelsOobInventoryStatus{ + Health: testPointer("Critical"), + HealthRollup: testPointer("Warning"), + State: testPointer("Enabled"), + Conditions: &[]fleetapi.ModelsOobInventoryCondition{{ + ConditionType: testPointer("Alert"), + Message: testPointer("One or more errors were detected."), + MessageArgs: &[]string{"GPU_SXM_1"}, + MessageId: "ResourceEvent.1.0.ResourceErrorsDetected", + OriginOfCondition: &fleetapi.ModelsOobInventoryReference{ + OdataId: "/redfish/v1/Chassis/HGX_GPU_SXM_1", + }, + Resolution: testPointer("Replace the failed component."), + Severity: testPointer("Critical"), + Timestamp: testPointer("2026-07-23T17:20:43Z"), + }}, + } processor := fleetapi.ModelsOobProcessor{ Id: "cpu-1", Model: testPointer("Grace"), @@ -23,6 +40,7 @@ func TestOOBInventoryFromGenerated(t *testing.T) { ProcessorType: testPointer("CPU"), Socket: testPointer("CPU0"), InstructionSet: testPointer("ARM-A64"), + Status: status, } pcieDevice := fleetapi.ModelsOobPcieDevice{ Id: "gpu-1", @@ -32,9 +50,11 @@ func TestOOBInventoryFromGenerated(t *testing.T) { FirmwareVersion: testPointer("96.00.5E.00.01"), StatusState: testPointer("Enabled"), Health: testPointer("OK"), + Status: status, } inventory := &fleetapi.ModelsOobInventory{ CollectedAt: "2026-08-17T12:00:00Z", + NodeKind: testPointer(fleetapi.OobNodeKindNvSwitch), SchemaVersion: "inventory.v1alpha1", PrimarySystemId: testPointer("system-1"), Source: fleetapi.ModelsOobSource{ @@ -54,11 +74,13 @@ func TestOOBInventoryFromGenerated(t *testing.T) { MemoryGib: testPointer(float32(2048)), SecureBootEnabled: testPointer(true), Processors: &[]fleetapi.ModelsOobProcessor{processor}, + Status: status, }}, Managers: &[]fleetapi.ModelsOobManager{{ Id: "manager-1", FirmwareVersion: testPointer("7.10.00.00"), ManagerType: testPointer("BMC"), + Status: status, StatusState: testPointer("Enabled"), }}, Chassis: &[]fleetapi.ModelsOobChassis{{ @@ -69,6 +91,7 @@ func TestOOBInventoryFromGenerated(t *testing.T) { RackOffset: testPointer(12), }, PcieDevices: &[]fleetapi.ModelsOobPcieDevice{pcieDevice}, + Status: status, }}, Firmware: &[]fleetapi.ModelsOobFirmware{{ Id: "bios-1", @@ -78,6 +101,7 @@ func TestOOBInventoryFromGenerated(t *testing.T) { StatusState: testPointer("Enabled"), Health: testPointer("OK"), HealthRollup: testPointer("Warning"), + Status: status, }}, DomainErrors: &[]fleetapi.ModelsOobDomainError{{ Domain: "firmware", @@ -89,7 +113,8 @@ func TestOOBInventoryFromGenerated(t *testing.T) { got := oobInventoryFromGenerated(inventory) if got == nil || got.CollectedAt != inventory.CollectedAt || got.SchemaVersion != inventory.SchemaVersion || - got.PrimarySystemID != "system-1" || got.TargetError != "one target was unavailable" { + got.NodeKind != OOBNodeKindNVSwitch || got.PrimarySystemID != "system-1" || + got.TargetError != "one target was unavailable" { t.Fatalf("unexpected inventory metadata: %#v", got) } if got.Source == nil || got.Source.Address != "192.0.2.10" || got.Source.Hostname != "bmc-1" || @@ -98,19 +123,25 @@ func TestOOBInventoryFromGenerated(t *testing.T) { } if len(got.Systems) != 1 || got.Systems[0].CPUCount == nil || *got.Systems[0].CPUCount != 2 || len(got.Systems[0].Processors) != 1 || got.Systems[0].Processors[0].TotalCores == nil || - *got.Systems[0].Processors[0].TotalCores != 72 { + *got.Systems[0].Processors[0].TotalCores != 72 || got.Systems[0].Status == nil || + got.Systems[0].Status.Health != "Critical" || len(got.Systems[0].Status.Conditions) != 1 || + got.Systems[0].Status.Conditions[0].MessageID != "ResourceEvent.1.0.ResourceErrorsDetected" || + got.Systems[0].Status.Conditions[0].OriginOfCondition == nil || + got.Systems[0].Status.Conditions[0].OriginOfCondition.ODataID != "/redfish/v1/Chassis/HGX_GPU_SXM_1" { t.Fatalf("unexpected systems: %#v", got.Systems) } - if len(got.Managers) != 1 || got.Managers[0].FirmwareVersion != "7.10.00.00" { + if len(got.Managers) != 1 || got.Managers[0].FirmwareVersion != "7.10.00.00" || + got.Managers[0].Status == nil { t.Fatalf("unexpected managers: %#v", got.Managers) } if len(got.Chassis) != 1 || got.Chassis[0].Location == nil || got.Chassis[0].Location.RackOffset == nil || *got.Chassis[0].Location.RackOffset != 12 || len(got.Chassis[0].PCIeDevices) != 1 || - got.Chassis[0].PCIeDevices[0].Model != "H100" { + got.Chassis[0].PCIeDevices[0].Model != "H100" || got.Chassis[0].Status == nil || + got.Chassis[0].PCIeDevices[0].Status == nil { t.Fatalf("unexpected chassis: %#v", got.Chassis) } if len(got.Firmware) != 1 || got.Firmware[0].ServiceID != "firmware-service-1" || - got.Firmware[0].HealthRollup != "Warning" { + got.Firmware[0].HealthRollup != "Warning" || got.Firmware[0].Status == nil { t.Fatalf("unexpected firmware: %#v", got.Firmware) } if len(got.DomainErrors) != 1 || got.DomainErrors[0].Domain != "firmware" || From ac2ad720532a50cc84c6d2d32530b5fb7cb33c96 Mon Sep 17 00:00:00 2001 From: Jingxiang Zhang Date: Wed, 16 Sep 2026 12:33:58 -0700 Subject: [PATCH 2/3] refactor(node): simplify node kind naming Signed-off-by: Jingxiang Zhang --- api/openapi/openapi.yaml | 16 ++--- internal/cmd/node/node_test.go | 2 +- internal/generated/fleetapi/client.gen.go | 80 +++++++++++------------ nvfleetint/node.go | 6 +- nvfleetint/node_test.go | 4 +- nvfleetint/oob_inventory.go | 20 +++--- nvfleetint/oob_inventory_test.go | 4 +- 7 files changed, 66 insertions(+), 66 deletions(-) diff --git a/api/openapi/openapi.yaml b/api/openapi/openapi.yaml index f38f1ce..7a6c6a9 100644 --- a/api/openapi/openapi.yaml +++ b/api/openapi/openapi.yaml @@ -6934,7 +6934,7 @@ components: type: array uniqueItems: false nodeKind: - $ref: '#/components/schemas/models.OobNodeKind' + $ref: '#/components/schemas/models.NodeKind' primarySystemId: description: 'Optional fields — omitted from the response when absent:' example: DGX @@ -7192,7 +7192,7 @@ components: example: node-group-1 type: string nodeKind: - $ref: '#/components/schemas/models.OobNodeKind' + $ref: '#/components/schemas/models.NodeKind' nodeName: description: NodeName is the optional machine name configured on the OOB collector. @@ -7281,7 +7281,7 @@ components: example: 550e8400-e29b-41d4-a716-446655440001 type: string nodeKind: - $ref: '#/components/schemas/models.OobNodeKind' + $ref: '#/components/schemas/models.NodeKind' nodeName: description: NodeName is the optional machine name configured on the OOB collector. @@ -7313,7 +7313,7 @@ components: required: - nodeUUID type: object - models.OobNodeKind: + models.NodeKind: default: unknown enum: - unknown @@ -7323,10 +7323,10 @@ components: example: compute type: string x-enum-varnames: - - OobNodeKindUnknown - - OobNodeKindCompute - - OobNodeKindNvSwitch - - OobNodeKindPowerShelf + - NodeKindUnknown + - NodeKindCompute + - NodeKindNvSwitch + - NodeKindPowerShelf models.OobNodesResponse: properties: hasMore: diff --git a/internal/cmd/node/node_test.go b/internal/cmd/node/node_test.go index a95e227..1999d8e 100644 --- a/internal/cmd/node/node_test.go +++ b/internal/cmd/node/node_test.go @@ -876,7 +876,7 @@ func TestOOBDetailNodeRowsOmitInbandHostname(t *testing.T) { UUID: "node-1", Hostname: "inband-hostname", NodeName: "machine-001", - NodeKind: nvfleetint.OOBNodeKindNVSwitch, + NodeKind: nvfleetint.NodeKindNVSwitch, BMCHostname: "bmc-001", BMCIP: "192.0.2.10:443", }}) diff --git a/internal/generated/fleetapi/client.gen.go b/internal/generated/fleetapi/client.gen.go index 8fe663b..73dfa6e 100644 --- a/internal/generated/fleetapi/client.gen.go +++ b/internal/generated/fleetapi/client.gen.go @@ -347,6 +347,30 @@ func (e ModelsIntegrityCheck) Valid() bool { } } +// Defines values for ModelsNodeKind. +const ( + NodeKindCompute ModelsNodeKind = "compute" + NodeKindNvSwitch ModelsNodeKind = "nvswitch" + NodeKindPowerShelf ModelsNodeKind = "power_shelf" + NodeKindUnknown ModelsNodeKind = "unknown" +) + +// Valid indicates whether the value is a known member of the ModelsNodeKind enum. +func (e ModelsNodeKind) Valid() bool { + switch e { + case NodeKindCompute: + return true + case NodeKindNvSwitch: + return true + case NodeKindPowerShelf: + return true + case NodeKindUnknown: + return true + default: + return false + } +} + // Defines values for ModelsNotificationChannel. const ( NotificationChannelEmail ModelsNotificationChannel = "email" @@ -395,30 +419,6 @@ func (e ModelsNotificationScopeType) Valid() bool { } } -// Defines values for ModelsOobNodeKind. -const ( - OobNodeKindCompute ModelsOobNodeKind = "compute" - OobNodeKindNvSwitch ModelsOobNodeKind = "nvswitch" - OobNodeKindPowerShelf ModelsOobNodeKind = "power_shelf" - OobNodeKindUnknown ModelsOobNodeKind = "unknown" -) - -// Valid indicates whether the value is a known member of the ModelsOobNodeKind enum. -func (e ModelsOobNodeKind) Valid() bool { - switch e { - case OobNodeKindCompute: - return true - case OobNodeKindNvSwitch: - return true - case OobNodeKindPowerShelf: - return true - case OobNodeKindUnknown: - return true - default: - return false - } -} - // Defines values for ModelsVerificationCheck. const ( VerificationCheckDegraded ModelsVerificationCheck = "Degraded" @@ -2491,6 +2491,9 @@ type ModelsNodeHistoryResponse struct { Total *int `json:"total,omitempty"` } +// ModelsNodeKind defines model for models.NodeKind. +type ModelsNodeKind string + // ModelsNodeRemovalPolicy defines model for models.NodeRemovalPolicy. type ModelsNodeRemovalPolicy struct { CreatedAt *string `json:"createdAt,omitempty"` @@ -2659,7 +2662,7 @@ type ModelsOobInventory struct { DomainErrors *[]ModelsOobDomainError `json:"domainErrors,omitempty"` Firmware *[]ModelsOobFirmware `json:"firmware,omitempty"` Managers *[]ModelsOobManager `json:"managers,omitempty"` - NodeKind *ModelsOobNodeKind `json:"nodeKind,omitempty"` + NodeKind *ModelsNodeKind `json:"nodeKind,omitempty"` // PrimarySystemId Optional fields — omitted from the response when absent: PrimarySystemId *string `json:"primarySystemId,omitempty"` @@ -2795,11 +2798,11 @@ type ModelsOobNode struct { // LastIntegrityCheckTS Deprecated: use lastVerificationCheckTS. // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set - LastIntegrityCheckTS *string `json:"lastIntegrityCheckTS,omitempty"` - LastUpdatedTS *string `json:"lastUpdatedTS,omitempty"` - LastVerificationCheckTS *string `json:"lastVerificationCheckTS,omitempty"` - NodeGroup *string `json:"nodeGroup,omitempty"` - NodeKind *ModelsOobNodeKind `json:"nodeKind,omitempty"` + LastIntegrityCheckTS *string `json:"lastIntegrityCheckTS,omitempty"` + LastUpdatedTS *string `json:"lastUpdatedTS,omitempty"` + LastVerificationCheckTS *string `json:"lastVerificationCheckTS,omitempty"` + NodeGroup *string `json:"nodeGroup,omitempty"` + NodeKind *ModelsNodeKind `json:"nodeKind,omitempty"` // NodeName NodeName is the optional machine name configured on the OOB collector. NodeName *string `json:"nodeName,omitempty"` @@ -2841,13 +2844,13 @@ type ModelsOobNodeDetailsResponse struct { // LastIntegrityCheckTS Deprecated: use lastVerificationCheckTS. // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set - LastIntegrityCheckTS *string `json:"lastIntegrityCheckTS,omitempty"` - LastUpdatedTS *string `json:"lastUpdatedTS,omitempty"` - LastVerificationCheckTS *string `json:"lastVerificationCheckTS,omitempty"` - Location *ModelsLocation `json:"location,omitempty"` - NodeGroup *string `json:"nodeGroup,omitempty"` - NodeGroupId *string `json:"nodeGroupId,omitempty"` - NodeKind *ModelsOobNodeKind `json:"nodeKind,omitempty"` + LastIntegrityCheckTS *string `json:"lastIntegrityCheckTS,omitempty"` + LastUpdatedTS *string `json:"lastUpdatedTS,omitempty"` + LastVerificationCheckTS *string `json:"lastVerificationCheckTS,omitempty"` + Location *ModelsLocation `json:"location,omitempty"` + NodeGroup *string `json:"nodeGroup,omitempty"` + NodeGroupId *string `json:"nodeGroupId,omitempty"` + NodeKind *ModelsNodeKind `json:"nodeKind,omitempty"` // NodeName NodeName is the optional machine name configured on the OOB collector. NodeName *string `json:"nodeName,omitempty"` @@ -2860,9 +2863,6 @@ type ModelsOobNodeDetailsResponse struct { VerificationCheckReason *string `json:"verificationCheckReason,omitempty"` } -// ModelsOobNodeKind defines model for models.OobNodeKind. -type ModelsOobNodeKind string - // ModelsOobNodesResponse defines model for models.OobNodesResponse. type ModelsOobNodesResponse struct { HasMore *bool `json:"hasMore,omitempty"` diff --git a/nvfleetint/node.go b/nvfleetint/node.go index 72d3c4a..5149280 100644 --- a/nvfleetint/node.go +++ b/nvfleetint/node.go @@ -182,7 +182,7 @@ type Node struct { UUID string `json:"nodeUUID"` Hostname string `json:"hostname,omitempty"` NodeName string `json:"nodeName,omitempty"` - NodeKind OOBNodeKind `json:"nodeKind,omitempty"` + NodeKind NodeKind `json:"nodeKind,omitempty"` AgentType string `json:"agentType,omitempty"` AgentVersion string `json:"agentVersion,omitempty"` BMCHostname string `json:"bmcHostname,omitempty"` @@ -686,7 +686,7 @@ func oobNodeFromGenerated(node fleetapi.ModelsOobNode) Node { return Node{ UUID: node.NodeUUID, NodeName: stringValue(node.NodeName), - NodeKind: oobNodeKindFromGenerated(node.NodeKind), + NodeKind: nodeKindFromGenerated(node.NodeKind), AgentType: stringValue(node.AgentType), AgentVersion: stringValue(node.AgentVersion), BMCHostname: stringValue(node.BmcHostname), @@ -759,7 +759,7 @@ func oobNodeDetailsFromGenerated(node fleetapi.ModelsOobNodeDetailsResponse) Nod Node: Node{ UUID: node.NodeUUID, NodeName: stringValue(node.NodeName), - NodeKind: oobNodeKindFromGenerated(node.NodeKind), + NodeKind: nodeKindFromGenerated(node.NodeKind), AgentType: stringValue(node.AgentType), AgentVersion: stringValue(node.AgentVersion), BMCHostname: stringValue(node.BmcHostname), diff --git a/nvfleetint/node_test.go b/nvfleetint/node_test.go index 6505e3b..4315d87 100644 --- a/nvfleetint/node_test.go +++ b/nvfleetint/node_test.go @@ -277,7 +277,7 @@ func TestDescribeNodeOOBDecodesInventory(t *testing.T) { if err != nil { t.Fatalf("describe failed: %v", err) } - if got.AgentType != "oob" || got.NodeName != "machine-001" || got.NodeKind != OOBNodeKindNVSwitch || + if got.AgentType != "oob" || got.NodeName != "machine-001" || got.NodeKind != NodeKindNVSwitch || got.BMCHostname != "bmc-001" || got.BMCIP != "192.0.2.10" { t.Fatalf("unexpected OOB node fields: %#v", got.Node) } @@ -326,7 +326,7 @@ func TestListNodesOOB(t *testing.T) { if err != nil { t.Fatalf("list failed: %v", err) } - if len(got.Nodes) != 1 || got.Nodes[0].NodeName != "machine-001" || got.Nodes[0].NodeKind != OOBNodeKindNVSwitch || + if len(got.Nodes) != 1 || got.Nodes[0].NodeName != "machine-001" || got.Nodes[0].NodeKind != NodeKindNVSwitch || got.Nodes[0].BMCHostname != "bmc-001" || got.Nodes[0].BMCIP != "192.0.2.10" { t.Fatalf("unexpected OOB nodes: %#v", got.Nodes) } diff --git a/nvfleetint/oob_inventory.go b/nvfleetint/oob_inventory.go index f8a1915..35656e2 100644 --- a/nvfleetint/oob_inventory.go +++ b/nvfleetint/oob_inventory.go @@ -5,14 +5,14 @@ package nvfleetint import "github.com/NVIDIA/fleet-intelligence-client/internal/generated/fleetapi" -// OOBNodeKind classifies the independently addressable node that produced an OOB inventory document. -type OOBNodeKind string +// NodeKind classifies the independently addressable node that produced an OOB inventory document. +type NodeKind string const ( - OOBNodeKindUnknown OOBNodeKind = "unknown" - OOBNodeKindCompute OOBNodeKind = "compute" - OOBNodeKindNVSwitch OOBNodeKind = "nvswitch" - OOBNodeKindPowerShelf OOBNodeKind = "power_shelf" + NodeKindUnknown NodeKind = "unknown" + NodeKindCompute NodeKind = "compute" + NodeKindNVSwitch NodeKind = "nvswitch" + NodeKindPowerShelf NodeKind = "power_shelf" ) // Represents inventory collected out of band through a node's BMC @@ -21,7 +21,7 @@ type OOBInventory struct { CollectedAt string `json:"collectedAt"` DomainErrors []OOBDomainError `json:"domainErrors,omitempty"` Firmware []OOBFirmware `json:"firmware,omitempty"` - NodeKind OOBNodeKind `json:"nodeKind,omitempty"` + NodeKind NodeKind `json:"nodeKind,omitempty"` Managers []OOBManager `json:"managers,omitempty"` PrimarySystemID string `json:"primarySystemId,omitempty"` SchemaVersion string `json:"schemaVersion"` @@ -199,7 +199,7 @@ func oobInventoryFromGenerated(inventory *fleetapi.ModelsOobInventory) *OOBInven out := &OOBInventory{ CollectedAt: inventory.CollectedAt, - NodeKind: oobNodeKindFromGenerated(inventory.NodeKind), + NodeKind: nodeKindFromGenerated(inventory.NodeKind), PrimarySystemID: stringValue(inventory.PrimarySystemId), SchemaVersion: inventory.SchemaVersion, Source: oobSourceFromGenerated(inventory.Source), @@ -391,11 +391,11 @@ func oobFirmwareFromGenerated(firmware fleetapi.ModelsOobFirmware) OOBFirmware { } } -func oobNodeKindFromGenerated(kind *fleetapi.ModelsOobNodeKind) OOBNodeKind { +func nodeKindFromGenerated(kind *fleetapi.ModelsNodeKind) NodeKind { if kind == nil { return "" } - return OOBNodeKind(*kind) + return NodeKind(*kind) } func oobInventoryStatusFromGenerated(status *fleetapi.ModelsOobInventoryStatus) *OOBInventoryStatus { diff --git a/nvfleetint/oob_inventory_test.go b/nvfleetint/oob_inventory_test.go index 8a7b6a7..b088905 100644 --- a/nvfleetint/oob_inventory_test.go +++ b/nvfleetint/oob_inventory_test.go @@ -54,7 +54,7 @@ func TestOOBInventoryFromGenerated(t *testing.T) { } inventory := &fleetapi.ModelsOobInventory{ CollectedAt: "2026-08-17T12:00:00Z", - NodeKind: testPointer(fleetapi.OobNodeKindNvSwitch), + NodeKind: testPointer(fleetapi.NodeKindNvSwitch), SchemaVersion: "inventory.v1alpha1", PrimarySystemId: testPointer("system-1"), Source: fleetapi.ModelsOobSource{ @@ -113,7 +113,7 @@ func TestOOBInventoryFromGenerated(t *testing.T) { got := oobInventoryFromGenerated(inventory) if got == nil || got.CollectedAt != inventory.CollectedAt || got.SchemaVersion != inventory.SchemaVersion || - got.NodeKind != OOBNodeKindNVSwitch || got.PrimarySystemID != "system-1" || + got.NodeKind != NodeKindNVSwitch || got.PrimarySystemID != "system-1" || got.TargetError != "one target was unavailable" { t.Fatalf("unexpected inventory metadata: %#v", got) } From cf81106dd090195459578fa154e8b0ef7bfb5caf Mon Sep 17 00:00:00 2001 From: Jingxiang Zhang Date: Wed, 16 Sep 2026 13:37:18 -0700 Subject: [PATCH 3/3] fix(node): read kind from node response Signed-off-by: Jingxiang Zhang --- api/openapi/openapi.yaml | 2 -- docs/sdk.md | 2 +- internal/cmd/node/node.go | 2 +- internal/cmd/node/node_test.go | 2 +- internal/generated/fleetapi/client.gen.go | 1 - nvfleetint/oob_inventory.go | 2 -- nvfleetint/oob_inventory_test.go | 3 +-- 7 files changed, 4 insertions(+), 10 deletions(-) diff --git a/api/openapi/openapi.yaml b/api/openapi/openapi.yaml index 7a6c6a9..e16ad03 100644 --- a/api/openapi/openapi.yaml +++ b/api/openapi/openapi.yaml @@ -6933,8 +6933,6 @@ components: $ref: '#/components/schemas/models.OobManager' type: array uniqueItems: false - nodeKind: - $ref: '#/components/schemas/models.NodeKind' primarySystemId: description: 'Optional fields — omitted from the response when absent:' example: DGX diff --git a/docs/sdk.md b/docs/sdk.md index c11384b..64cb880 100644 --- a/docs/sdk.md +++ b/docs/sdk.md @@ -58,7 +58,7 @@ node, err := client.DescribeNodeWithOptions(ctx, nodeUUID, nvfleetint.DescribeNo AgentType: nvfleetint.NodeAgentTypeOOB, }) if err == nil && node.OOBInventory != nil { - fmt.Println(node.OOBInventory.SchemaVersion, node.OOBInventory.NodeKind) + fmt.Println(node.OOBInventory.SchemaVersion, node.NodeKind) for _, system := range node.OOBInventory.Systems { if system.Status != nil { fmt.Println(system.Status.State, system.Status.Health, system.Status.Conditions) diff --git a/internal/cmd/node/node.go b/internal/cmd/node/node.go index 71c0af9..2fec440 100644 --- a/internal/cmd/node/node.go +++ b/internal/cmd/node/node.go @@ -882,6 +882,7 @@ func oobNodeDescribeRows(node nvfleetint.NodeDetails) [][]string { {"UNHEALTHY COMPONENTS", clioutput.FormatOptionalInt(node.UnhealthyComponentCount)}, {"LOCATION", cmdutil.FormatLocation(node.Location)}, {"NODE NAME", clioutput.DisplayString(node.NodeName)}, + {"NODE KIND", clioutput.DisplayString(string(node.NodeKind))}, {"BMC HOSTNAME", clioutput.DisplayString(node.BMCHostname)}, {"BMC IP", clioutput.DisplayString(node.BMCIP)}, } @@ -1015,7 +1016,6 @@ func oobInventorySummaryRows(inventory *nvfleetint.OOBInventory) [][]string { return [][]string{ {"INVENTORY SCHEMA VERSION", clioutput.DisplayString(inventory.SchemaVersion)}, {"INVENTORY COLLECTED AT", clioutput.DisplayString(inventory.CollectedAt)}, - {"NODE KIND", clioutput.DisplayString(string(inventory.NodeKind))}, {"INVENTORY PRIMARY SYSTEM", clioutput.DisplayString(inventory.PrimarySystemID)}, {"INVENTORY MANAGERS", strconv.Itoa(len(inventory.Managers))}, {"INVENTORY SYSTEMS", strconv.Itoa(len(inventory.Systems))}, diff --git a/internal/cmd/node/node_test.go b/internal/cmd/node/node_test.go index 1999d8e..0fd20eb 100644 --- a/internal/cmd/node/node_test.go +++ b/internal/cmd/node/node_test.go @@ -651,13 +651,13 @@ func TestNodeDescribeOOBTable(t *testing.T) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{ "nodeUUID":"node-oob-1", + "nodeKind":"nvswitch", "hostname":"host-001", "agentType":"oob", "bmcHostname":"bmc-001", "bmcIP":"192.0.2.10", "oobInventory":{ "collectedAt":"2026-07-30T20:00:00Z", - "nodeKind":"nvswitch", "schemaVersion":"inventory.v1alpha1", "source":{"sourceType":"redfish","vendor":"Dell","address":"192.0.2.10:443","mac":"00:11:22:33:44:55","redfishVersion":"1.17.0"}, "systems":[{"id":"System.Embedded.1","uuid":"system-uuid","manufacturer":"Dell","model":"PowerEdge XE9680","sku":"sku-1","serialNumber":"serial-1","biosVersion":"2.1.0","hostName":"host-001","assetTag":"asset-1","powerState":"on","status":{"state":"Enabled","health":"Critical","healthRollup":"Warning","conditions":[{"messageId":"ResourceEvent.1.0.ResourceErrorsDetected","severity":"Critical","message":"nested status condition"}]},"statusState":"Enabled","health":"OK","healthRollup":"Warning","cpuCount":2,"memoryGib":2048,"secureBootEnabled":true,"processors":[{"id":"CPU.Socket.1","socket":"CPU 1","processorType":"cpu","processorArchitecture":"x86","manufacturer":"Intel","model":"Xeon","maxSpeedMhz":3800,"totalCores":56,"totalThreads":112,"statusState":"Enabled","health":"OK","healthRollup":"OK"},{"id":"GPU.Slot.1","processorType":"gpu","manufacturer":"NVIDIA","model":"NVIDIA H100","statusState":"Enabled","health":"OK","healthRollup":"OK"}]}], diff --git a/internal/generated/fleetapi/client.gen.go b/internal/generated/fleetapi/client.gen.go index 73dfa6e..6f598ad 100644 --- a/internal/generated/fleetapi/client.gen.go +++ b/internal/generated/fleetapi/client.gen.go @@ -2662,7 +2662,6 @@ type ModelsOobInventory struct { DomainErrors *[]ModelsOobDomainError `json:"domainErrors,omitempty"` Firmware *[]ModelsOobFirmware `json:"firmware,omitempty"` Managers *[]ModelsOobManager `json:"managers,omitempty"` - NodeKind *ModelsNodeKind `json:"nodeKind,omitempty"` // PrimarySystemId Optional fields — omitted from the response when absent: PrimarySystemId *string `json:"primarySystemId,omitempty"` diff --git a/nvfleetint/oob_inventory.go b/nvfleetint/oob_inventory.go index 35656e2..db8ca44 100644 --- a/nvfleetint/oob_inventory.go +++ b/nvfleetint/oob_inventory.go @@ -21,7 +21,6 @@ type OOBInventory struct { CollectedAt string `json:"collectedAt"` DomainErrors []OOBDomainError `json:"domainErrors,omitempty"` Firmware []OOBFirmware `json:"firmware,omitempty"` - NodeKind NodeKind `json:"nodeKind,omitempty"` Managers []OOBManager `json:"managers,omitempty"` PrimarySystemID string `json:"primarySystemId,omitempty"` SchemaVersion string `json:"schemaVersion"` @@ -199,7 +198,6 @@ func oobInventoryFromGenerated(inventory *fleetapi.ModelsOobInventory) *OOBInven out := &OOBInventory{ CollectedAt: inventory.CollectedAt, - NodeKind: nodeKindFromGenerated(inventory.NodeKind), PrimarySystemID: stringValue(inventory.PrimarySystemId), SchemaVersion: inventory.SchemaVersion, Source: oobSourceFromGenerated(inventory.Source), diff --git a/nvfleetint/oob_inventory_test.go b/nvfleetint/oob_inventory_test.go index b088905..87fd5f3 100644 --- a/nvfleetint/oob_inventory_test.go +++ b/nvfleetint/oob_inventory_test.go @@ -54,7 +54,6 @@ func TestOOBInventoryFromGenerated(t *testing.T) { } inventory := &fleetapi.ModelsOobInventory{ CollectedAt: "2026-08-17T12:00:00Z", - NodeKind: testPointer(fleetapi.NodeKindNvSwitch), SchemaVersion: "inventory.v1alpha1", PrimarySystemId: testPointer("system-1"), Source: fleetapi.ModelsOobSource{ @@ -113,7 +112,7 @@ func TestOOBInventoryFromGenerated(t *testing.T) { got := oobInventoryFromGenerated(inventory) if got == nil || got.CollectedAt != inventory.CollectedAt || got.SchemaVersion != inventory.SchemaVersion || - got.NodeKind != NodeKindNVSwitch || got.PrimarySystemID != "system-1" || + got.PrimarySystemID != "system-1" || got.TargetError != "one target was unavailable" { t.Fatalf("unexpected inventory metadata: %#v", got) }