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 <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
230 lines
7.5 KiB
Go
230 lines
7.5 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"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// CLUSPROP_FORMAT values that the collectors do not decode.
|
|
const (
|
|
formatBinary = 1
|
|
formatMultiString = 5
|
|
formatULargeInteger = 6
|
|
formatFileTime = 12
|
|
)
|
|
|
|
// Property lists in testdata were captured with TestDumpPropertyLists from the
|
|
// CI cluster (Windows Server 2022). Unlike lists built from the documentation,
|
|
// they end with the extra CLUSPROP_SYNTAX_ENDMARK of the cluster service.
|
|
func readPropertyFixture(tb testing.TB, name string) []byte {
|
|
tb.Helper()
|
|
|
|
text, err := os.ReadFile(filepath.Join("testdata", name))
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
data, err := hex.DecodeString(strings.Join(strings.Fields(string(text)), ""))
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
type fixtureValue struct {
|
|
format uint16
|
|
length int
|
|
dword *uint32
|
|
text *string
|
|
}
|
|
|
|
func dwordValue(value uint32) fixtureValue {
|
|
return fixtureValue{format: formatDWORD, length: 4, dword: &value}
|
|
}
|
|
|
|
func stringValue(value string, length int) fixtureValue {
|
|
return fixtureValue{format: formatString, length: length, text: &value}
|
|
}
|
|
|
|
func checkFixtureProperties(t *testing.T, properties map[string][]Property, count int, want map[string]fixtureValue) {
|
|
t.Helper()
|
|
|
|
if len(properties) != count {
|
|
t.Errorf("properties = %d, want %d", len(properties), count)
|
|
}
|
|
|
|
for name, value := range want {
|
|
values := properties[name]
|
|
if len(values) != 1 || values[0].Format != value.format || len(values[0].Data) != value.length {
|
|
t.Errorf("%s = %+v, want format %d with %d bytes", name, values, value.format, value.length)
|
|
|
|
continue
|
|
}
|
|
|
|
if value.dword != nil {
|
|
if got, err := values[0].DWORD(); err != nil || got != *value.dword {
|
|
t.Errorf("%s = %d, %v, want %d", name, got, err, *value.dword)
|
|
}
|
|
}
|
|
|
|
if value.text != nil {
|
|
if got, err := values[0].String(); err != nil || got != *value.text {
|
|
t.Errorf("%s = %q, %v, want %q", name, got, err, *value.text)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParsePropertiesCapturedResourceLists(t *testing.T) {
|
|
// Read-only common properties: an SZ whose 38 bytes need 2 bytes of
|
|
// padding, ULARGE_INTEGER values and a DWORD.
|
|
properties, err := ParseProperties(readPropertyFixture(t, "resource_ro_common_generic_service.hex"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
checkFixtureProperties(t, properties, 6, map[string]fixtureValue{
|
|
"Name": stringValue("CI Generic Service", 38),
|
|
"MonitorProcessId": dwordValue(9092),
|
|
"StatusInformation": {format: formatULargeInteger, length: 8},
|
|
"LastOperationStatusCode": {format: formatULargeInteger, length: 8},
|
|
"ResourceSpecificData1": {format: formatULargeInteger, length: 8},
|
|
"ResourceSpecificData2": {format: formatULargeInteger, length: 8},
|
|
})
|
|
|
|
// Common properties: the resource type name the collector publishes, empty
|
|
// strings (2 bytes, padded) and the DWORD values.
|
|
properties, err = ParseProperties(readPropertyFixture(t, "resource_common_generic_service.hex"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
checkFixtureProperties(t, properties, 15, map[string]fixtureValue{
|
|
"Type": stringValue("Generic Service", 32),
|
|
"Description": stringValue("", 2),
|
|
"ResourceSpecificStatus": stringValue("", 2),
|
|
"DeadlockTimeout": dwordValue(300000),
|
|
"EmbeddedFailureAction": dwordValue(2),
|
|
"IsAlivePollInterval": dwordValue(^uint32(0)),
|
|
"LooksAlivePollInterval": dwordValue(^uint32(0)),
|
|
"PendingTimeout": dwordValue(180000),
|
|
"PersistentState": dwordValue(1),
|
|
"RestartAction": dwordValue(2),
|
|
"RestartDelay": dwordValue(500),
|
|
"RestartPeriod": dwordValue(600000),
|
|
"RestartThreshold": dwordValue(1),
|
|
"RetryPeriodOnFailure": dwordValue(600000),
|
|
"SeparateMonitor": dwordValue(0),
|
|
})
|
|
|
|
// Read-only private properties of an IP Address: FILETIME values.
|
|
properties, err = ParseProperties(readPropertyFixture(t, "resource_ro_private_ip_address.hex"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
checkFixtureProperties(t, properties, 5, map[string]fixtureValue{
|
|
"LeaseObtainedTime": {format: formatFileTime, length: 8},
|
|
"LeaseExpiresTime": {format: formatFileTime, length: 8},
|
|
"DhcpServer": stringValue("255.255.255.255", 32),
|
|
"DhcpAddress": stringValue("0.0.0.0", 16),
|
|
"DhcpSubnetMask": stringValue("255.0.0.0", 20),
|
|
})
|
|
}
|
|
|
|
func TestParsePropertiesCapturedClusterList(t *testing.T) {
|
|
// The cluster common list is larger than the 4 KiB first buffer and holds
|
|
// BINARY security descriptors, MULTI_SZ lists, ULARGE_INTEGER and FILETIME
|
|
// values next to 64 DWORDs.
|
|
data := readPropertyFixture(t, "cluster_common.hex")
|
|
if len(data) <= propertyListBufferSize {
|
|
t.Fatalf("fixture has %d bytes, want more than the first buffer", len(data))
|
|
}
|
|
|
|
properties, err := ParseProperties(data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
checkFixtureProperties(t, properties, 74, map[string]fixtureValue{
|
|
"Description": stringValue("", 2),
|
|
"PreferredSite": stringValue("", 2),
|
|
"Security Descriptor": {format: formatBinary, length: 396},
|
|
"SharedVolumeSecurityDescriptor": {format: formatBinary, length: 280},
|
|
"SharedVolumeCompatibleFilters": {format: formatMultiString, length: 2},
|
|
"SharedVolumeIncompatibleFilters": {format: formatMultiString, length: 2},
|
|
"EnabledEventLogs": {format: formatMultiString, length: 354},
|
|
"DumpPolicy": {format: formatULargeInteger, length: 8},
|
|
"S2DCacheBehavior": {format: formatULargeInteger, length: 8},
|
|
"RecentEventsResetTime": {format: formatFileTime, length: 8},
|
|
"ClusterEnforcedAntiaffinity": {format: formatDWORD, length: 4},
|
|
})
|
|
|
|
dwords := 0
|
|
|
|
for _, values := range properties {
|
|
if _, err := values[0].Value32(); err == nil {
|
|
dwords++
|
|
}
|
|
}
|
|
|
|
if dwords != 64 {
|
|
t.Errorf("32-bit values = %d, want 64", dwords)
|
|
}
|
|
|
|
// Every truncation of a real list must be rejected without a panic.
|
|
for _, name := range []string{"resource_ro_common_generic_service.hex", "resource_common_generic_service.hex"} {
|
|
data := readPropertyFixture(t, name)
|
|
for index := range len(data) - 4 {
|
|
if _, err := ParseProperties(data[:index]); err == nil {
|
|
t.Errorf("%s: accepted truncation at %d", name, index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func FuzzParsePropertiesCaptured(f *testing.F) {
|
|
for _, name := range []string{"resource_ro_common_generic_service.hex", "resource_common_generic_service.hex", "resource_ro_private_ip_address.hex", "cluster_common.hex"} {
|
|
f.Add(readPropertyFixture(f, name))
|
|
}
|
|
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
properties, err := ParseProperties(data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, values := range properties {
|
|
for _, value := range values {
|
|
_, _ = value.String()
|
|
_, _ = value.Value32()
|
|
|
|
if (value.Format == formatDWORD || value.Format == formatLong) && len(value.Data) != 4 {
|
|
t.Fatalf("32-bit value with %d bytes", len(value.Data))
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|