mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 10:09:05 +02:00
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
172 lines
5.1 KiB
Go
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))
|
|
}
|