Files
windows_exporter/internal/headers/clusapi/dump_test.go
2026-10-11 13:52:55 +02:00

172 lines
5.1 KiB
Go

// SPDX-License-Identifier: Apache-2.0
//
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package clusapi
import (
"encoding/hex"
"errors"
"fmt"
"os"
"slices"
"strings"
"testing"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
const (
resourceGetROPrivateProperties = 0x0100007d
resourceGetPrivateProperties = 0x01000081
resourceTypeGetROCommonProperties = 0x02000055
resourceTypeGetCommonProperties = 0x02000059
resourceTypeGetPrivateProperties = 0x02000081
)
//nolint:gochecknoglobals
var resourceTypeControl = dll.NewProc("ClusterResourceTypeControl")
// TestDumpPropertyLists logs raw property lists from a live cluster as hex, so
// parser fixtures can be built from real cluster service output. Set
// WINDOWS_EXPORTER_TEST_CLUSAPI_DUMP=1 on a cluster node to enable it.
func TestDumpPropertyLists(t *testing.T) {
if os.Getenv("WINDOWS_EXPORTER_TEST_CLUSAPI_DUMP") == "" {
t.Skip("WINDOWS_EXPORTER_TEST_CLUSAPI_DUMP is not set")
}
cluster, _, err := openCluster.Call(0, windows.GENERIC_READ, 0)
if cluster == 0 {
t.Fatalf("OpenClusterEx: %v", err)
}
defer closeCluster.Call(cluster) //nolint:errcheck
deadline := time.Now().Add(time.Minute)
for _, code := range []uint32{objectCluster<<24 | ctlGetROCommonProperties, objectCluster<<24 | ctlGetCommonProperties} {
data, err := controlBufferIn(deadline, newControlBuffer(0), func(buffer []byte) (uint32, error) {
var (
size uint32
pointer *byte
)
if len(buffer) != 0 {
pointer = &buffer[0]
}
status, _, _ := clusterControl.Call(cluster, 0, uintptr(code), 0, 0, uintptr(unsafe.Pointer(pointer)), uintptr(len(buffer)), uintptr(unsafe.Pointer(&size)))
if status != 0 {
return size, windows.Errno(status)
}
return size, nil
})
logPropertyList(t, fmt.Sprintf("cluster %#x", code), data, err)
}
// Resource type lists carry EXPAND_SZ values such as DllName, which the
// cluster service returns as an EXPAND_SZ and EXPANDED_SZ value pair.
for _, typeName := range []string{"Generic Service", "IP Address"} {
typePtr, err := windows.UTF16PtrFromString(typeName)
if err != nil {
t.Fatal(err)
}
for _, code := range []uint32{resourceTypeGetROCommonProperties, resourceTypeGetCommonProperties, resourceTypeGetPrivateProperties} {
data, err := controlBufferIn(deadline, newControlBuffer(0), func(buffer []byte) (uint32, error) {
var (
size uint32
pointer *byte
)
if len(buffer) != 0 {
pointer = &buffer[0]
}
status, _, _ := resourceTypeControl.Call(cluster, uintptr(unsafe.Pointer(typePtr)), 0, uintptr(code), 0, 0, uintptr(unsafe.Pointer(pointer)), uintptr(len(buffer)), uintptr(unsafe.Pointer(&size)))
if status != 0 {
return size, windows.Errno(status)
}
return size, nil
})
logPropertyList(t, fmt.Sprintf("resource type %q %#x", typeName, code), data, err)
}
}
enum, _, err := openEnum.Call(cluster, 4)
if enum == 0 {
t.Fatalf("ClusterOpenEnum: %v", err)
}
defer closeEnum.Call(enum) //nolint:errcheck
for index := uint32(0); ; index++ {
name, err := enumName(enum, index, enumResource, deadline)
if errors.Is(err, windows.ERROR_NO_MORE_ITEMS) {
return
}
if err != nil {
t.Fatalf("ClusterEnum: %v", err)
}
handle, _, err := openResource.Call(cluster, uintptr(unsafe.Pointer(&name.units[0])), windows.GENERIC_READ, 0)
if handle == 0 {
t.Errorf("OpenClusterResourceEx %q: %v", name.name, err)
continue
}
for _, code := range []uint32{resourceGetROCommonProperties, resourceGetCommonProperties, resourceGetROPrivateProperties, resourceGetPrivateProperties} {
data, err := resourceBufferIn(handle, code, nil, deadline)
logPropertyList(t, fmt.Sprintf("resource %q %#x", name.name, code), data, err)
}
data, err := resourceBufferIn(handle, resourceGetType, nil, deadline)
t.Logf("resource %q GET_RESOURCE_TYPE: %s err=%v", name.name, hex.EncodeToString(data), err)
closeResource.Call(handle) //nolint:errcheck
}
}
func logPropertyList(t *testing.T, label string, data []byte, err error) {
t.Helper()
if err != nil {
t.Logf("%s: %v", label, err)
return
}
properties, parseErr := ParseProperties(data)
formats := make([]string, 0, len(properties))
for name, values := range properties {
valueFormats := make([]string, 0, len(values))
for _, value := range values {
valueFormats = append(valueFormats, fmt.Sprintf("%d/%d", value.Format, len(value.Data)))
}
formats = append(formats, name+"="+strings.Join(valueFormats, "+"))
}
slices.Sort(formats)
t.Logf("%s: parse error=%v formats=[%s]\n%s", label, parseErr, strings.Join(formats, " "), hex.EncodeToString(data))
}