mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
fix(vmware): send VI JSON _typeName on data-object arguments
The vSphere VI JSON API documents _typeName on request data objects. Calls whose only argument is a bare ManagedObjectReference (QueryPerfProviderSummary, QueryAvailablePerfMetric) succeed against vCenter 8.0.3, but every call that passes a composite data object (PerfQuerySpec/PerfMetricId for QueryPerf, EventFilterSpec/EventFilterSpecByEntity for QueryEvents) is rejected with HTTP 500. Issue #2070 shows exactly that split, so add the discriminators the documented schema carries to those request bodies. Morefs now carry _typeName too, matching the same schema. Focused tests pin the serialized request bodies. Change-source: pulse-maintainer
This commit is contained in:
@@ -38,6 +38,7 @@ type viJSONPerfProviderSummary struct {
|
||||
}
|
||||
|
||||
type viJSONPerfMetricID struct {
|
||||
TypeName string `json:"_typeName,omitempty"`
|
||||
CounterID int `json:"counterId"`
|
||||
Instance string `json:"instance"`
|
||||
}
|
||||
@@ -232,9 +233,10 @@ func (c *Client) queryPerfProviderSummary(
|
||||
var summary viJSONPerfProviderSummary
|
||||
path := fmt.Sprintf("/sdk/vim25/%s/PerformanceManager/%s/QueryPerfProviderSummary", release, perfManagerMoID)
|
||||
body := map[string]any{
|
||||
"entity": map[string]string{
|
||||
"type": strings.TrimSpace(entityType),
|
||||
"value": strings.TrimSpace(entityMoID),
|
||||
"entity": viJSONReference{
|
||||
TypeName: "ManagedObjectReference",
|
||||
Type: strings.TrimSpace(entityType),
|
||||
Value: strings.TrimSpace(entityMoID),
|
||||
},
|
||||
}
|
||||
if err := c.postVIJSONJSON(ctx, sessionID, path, "vmware performance provider summary", body, &summary); err != nil {
|
||||
@@ -254,9 +256,10 @@ func (c *Client) queryAvailablePerfMetrics(
|
||||
) ([]viJSONPerfMetricID, error) {
|
||||
path := fmt.Sprintf("/sdk/vim25/%s/PerformanceManager/%s/QueryAvailablePerfMetric", release, perfManagerMoID)
|
||||
body := map[string]any{
|
||||
"entity": map[string]string{
|
||||
"type": strings.TrimSpace(entityType),
|
||||
"value": strings.TrimSpace(entityMoID),
|
||||
"entity": viJSONReference{
|
||||
TypeName: "ManagedObjectReference",
|
||||
Type: strings.TrimSpace(entityType),
|
||||
Value: strings.TrimSpace(entityMoID),
|
||||
},
|
||||
"intervalId": intervalID,
|
||||
}
|
||||
@@ -278,15 +281,22 @@ func (c *Client) queryPerfMetrics(
|
||||
metricIDs []viJSONPerfMetricID,
|
||||
) ([]viJSONPerfEntityMetric, error) {
|
||||
path := fmt.Sprintf("/sdk/vim25/%s/PerformanceManager/%s/QueryPerf", release, perfManagerMoID)
|
||||
metricRequests := make([]viJSONPerfMetricID, len(metricIDs))
|
||||
for i, metricID := range metricIDs {
|
||||
metricID.TypeName = "PerfMetricId"
|
||||
metricRequests[i] = metricID
|
||||
}
|
||||
body := map[string]any{
|
||||
"querySpec": []map[string]any{{
|
||||
"entity": map[string]string{
|
||||
"type": strings.TrimSpace(entityType),
|
||||
"value": strings.TrimSpace(entityMoID),
|
||||
"_typeName": "PerfQuerySpec",
|
||||
"entity": viJSONReference{
|
||||
TypeName: "ManagedObjectReference",
|
||||
Type: strings.TrimSpace(entityType),
|
||||
Value: strings.TrimSpace(entityMoID),
|
||||
},
|
||||
"intervalId": intervalID,
|
||||
"maxSample": 1,
|
||||
"metricId": metricIDs,
|
||||
"metricId": metricRequests,
|
||||
}},
|
||||
}
|
||||
var result []viJSONPerfEntityMetric
|
||||
|
||||
@@ -11,9 +11,14 @@ import (
|
||||
|
||||
const vmwareSignalEnrichmentConcurrency = 8
|
||||
|
||||
// viJSONReference is a ManagedObjectReference. TypeName carries the VI JSON
|
||||
// API _typeName discriminator; the documented request schema includes it, and
|
||||
// the API rejects a composite data object that omits its discriminator while
|
||||
// accepting a bare moref without one.
|
||||
type viJSONReference struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
TypeName string `json:"_typeName,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type viJSONLocalizedMessage struct {
|
||||
@@ -39,11 +44,13 @@ type viJSONTaskInfo struct {
|
||||
}
|
||||
|
||||
type viJSONEventFilterSpec struct {
|
||||
TypeName string `json:"_typeName,omitempty"`
|
||||
Entity *viJSONEventFilterEntity `json:"entity,omitempty"`
|
||||
MaxCount int `json:"maxCount,omitempty"`
|
||||
}
|
||||
|
||||
type viJSONEventFilterEntity struct {
|
||||
TypeName string `json:"_typeName,omitempty"`
|
||||
Entity viJSONReference `json:"entity"`
|
||||
Recursion string `json:"recursion,omitempty"`
|
||||
}
|
||||
@@ -530,8 +537,10 @@ func (c *Client) collectRecentEvents(ctx context.Context, release, sessionID, ev
|
||||
path := fmt.Sprintf("/sdk/vim25/%s/EventManager/%s/QueryEvents", release, eventManagerMoID)
|
||||
body := map[string]any{
|
||||
"filter": viJSONEventFilterSpec{
|
||||
TypeName: "EventFilterSpec",
|
||||
Entity: &viJSONEventFilterEntity{
|
||||
Entity: viJSONReference{Type: strings.TrimSpace(managedType), Value: strings.TrimSpace(managedObjectID)},
|
||||
TypeName: "EventFilterSpecByEntity",
|
||||
Entity: viJSONReference{TypeName: "ManagedObjectReference", Type: strings.TrimSpace(managedType), Value: strings.TrimSpace(managedObjectID)},
|
||||
Recursion: "self",
|
||||
},
|
||||
MaxCount: 3,
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The vSphere VI JSON API documents a `_typeName` discriminator on request data
|
||||
// objects. A bare ManagedObjectReference is accepted without one, but the
|
||||
// composite argument objects (PerfQuerySpec, PerfMetricId, EventFilterSpec and
|
||||
// its nested EventFilterSpecByEntity) are rejected with HTTP 500 when the
|
||||
// discriminator is absent. These tests pin the exact request bodies that
|
||||
// vCenter 8.0.3 rejected for issue #2070.
|
||||
|
||||
func newVIJSONRequestClient(t *testing.T, handler http.Handler) (*Client, func()) {
|
||||
t.Helper()
|
||||
server := httptest.NewTLSServer(handler)
|
||||
client, err := NewClient(ClientConfig{
|
||||
Host: server.URL,
|
||||
Username: "admin",
|
||||
Password: "secret",
|
||||
InsecureSkipVerify: true,
|
||||
Timeout: 5 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
server.Close()
|
||||
t.Fatalf("NewClient: %v", err)
|
||||
}
|
||||
return client, server.Close
|
||||
}
|
||||
|
||||
func TestQueryPerfRequestCarriesTypeNameDiscriminators(t *testing.T) {
|
||||
var raw []byte
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/sdk/vim25/9.0.0.0/PerformanceManager/PerformanceManager/QueryPerf", func(w http.ResponseWriter, r *http.Request) {
|
||||
raw, _ = io.ReadAll(r.Body)
|
||||
writeJSON(w, []map[string]any{})
|
||||
})
|
||||
client, closeServer := newVIJSONRequestClient(t, mux)
|
||||
defer closeServer()
|
||||
|
||||
_, err := client.queryPerfMetrics(
|
||||
context.Background(),
|
||||
"9.0.0.0",
|
||||
"vi-session",
|
||||
"PerformanceManager",
|
||||
"HostSystem",
|
||||
"host-101",
|
||||
20,
|
||||
[]viJSONPerfMetricID{{CounterID: 1, Instance: ""}},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("queryPerfMetrics: %v", err)
|
||||
}
|
||||
|
||||
body := string(raw)
|
||||
for _, want := range []string{
|
||||
`"_typeName":"PerfQuerySpec"`,
|
||||
`"_typeName":"ManagedObjectReference"`,
|
||||
`"_typeName":"PerfMetricId"`,
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Fatalf("QueryPerf request %s missing %s", body, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryEventsRequestCarriesTypeNameDiscriminators(t *testing.T) {
|
||||
var raw []byte
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/sdk/vim25/9.0.0.0/EventManager/EventManager/QueryEvents", func(w http.ResponseWriter, r *http.Request) {
|
||||
raw, _ = io.ReadAll(r.Body)
|
||||
writeJSON(w, []map[string]any{})
|
||||
})
|
||||
client, closeServer := newVIJSONRequestClient(t, mux)
|
||||
defer closeServer()
|
||||
|
||||
_, err := client.collectRecentEvents(context.Background(), "9.0.0.0", "vi-session", "EventManager", "Network", "network-14")
|
||||
if err != nil {
|
||||
t.Fatalf("collectRecentEvents: %v", err)
|
||||
}
|
||||
|
||||
body := string(raw)
|
||||
for _, want := range []string{
|
||||
`"_typeName":"EventFilterSpec"`,
|
||||
`"_typeName":"EventFilterSpecByEntity"`,
|
||||
`"_typeName":"ManagedObjectReference"`,
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Fatalf("QueryEvents request %s missing %s", body, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user