internal/guestcollector/guestcollector_win.go (221 lines of code) (raw):
//go:build windows
// +build windows
/*
Copyright 2023 Google LLC
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
https://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.
*/
// Package guestcollector contains modules for collecting guest os information.
package guestcollector
import (
"context"
"encoding/json"
"regexp"
"time"
"github.com/StackExchange/wmi"
"github.com/GoogleCloudPlatform/sql-server-agent/internal/agentstatus"
"github.com/GoogleCloudPlatform/sql-server-agent/internal"
"github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/log"
)
// WindowsCollector is the collector for windows system.
type WindowsCollector struct {
host any
username any
password any
guestRuleWMIMap map[string]wmiExecutor
logicalToPhysicalDiskMap map[string]string
physicalDiskToTypeMap map[string]string
usageMetricLogger agentstatus.AgentStatus
}
type wmiExecutor struct {
namespace string
query string
isRule bool
runWMIQuery func(wmiConnectionArgs) (string, error)
}
// WMIConnectionArgs takes all required fields to run a WMI query.
type wmiConnectionArgs struct {
host any
username any
password any
namespace string
query string
}
// NewWindowsCollector initializes and returns new WindowsCollector object.
func NewWindowsCollector(host, username, password any, usageMetricLogger agentstatus.AgentStatus) *WindowsCollector {
c := WindowsCollector{
host: host,
username: username,
password: password,
guestRuleWMIMap: map[string]wmiExecutor{},
logicalToPhysicalDiskMap: map[string]string{},
physicalDiskToTypeMap: map[string]string{},
usageMetricLogger: usageMetricLogger,
}
c.guestRuleWMIMap[internal.PowerProfileSettingRule] = wmiExecutor{
namespace: `root\cimv2\power`,
query: `SELECT elementname FROM win32_powerplan WHERE isactive = true`,
isRule: true,
runWMIQuery: func(connArgs wmiConnectionArgs) (string, error) {
var result []struct {
ElementName string
}
// https://learn.microsoft.com/en-us/windows/win32/wmisdk/swbemlocator-connectserver
if err := wmi.Query(connArgs.query, &result, connArgs.host, connArgs.namespace, connArgs.username, connArgs.password); err != nil {
return "", err
}
return result[0].ElementName, nil
},
}
c.guestRuleWMIMap[internal.LogicalDiskToPartition] = wmiExecutor{
namespace: `root\cimv2`,
query: `SELECT antecedent, dependent FROM win32_logicaldisktopartition`,
runWMIQuery: func(connArgs wmiConnectionArgs) (string, error) {
var result []struct {
Antecedent string
Dependent string
}
if err := wmi.Query(connArgs.query, &result, connArgs.host, connArgs.namespace, connArgs.username, connArgs.password); err != nil {
return "", err
}
// example output:
// Antecedent: \\[HOSTNAME]\root\cimv2:Win32_DiskPartition.DeviceID="Disk #0, Partition #1"
// Dependent: \\[HOSTNAME]\root\cimv2:Win32_LogicalDisk.DeviceID="C:"
for _, v := range result {
if re := regexp.MustCompile(`\.*\\root\\cimv2:Win32_DiskPartition\.DeviceID=\"Disk #(.*), Partition #.*\"`); re.MatchString(v.Antecedent) {
disk := re.FindStringSubmatch(v.Antecedent)[1]
if re = regexp.MustCompile(`\.*\\root\\cimv2:Win32_LogicalDisk\.DeviceID=\"(.*)\"`); re.MatchString(v.Dependent) {
logicalDisk := re.FindStringSubmatch(v.Dependent)[1]
c.logicalToPhysicalDiskMap[logicalDisk] = disk
}
}
}
return "", nil
},
}
c.guestRuleWMIMap[internal.PhysicalDiskToType] = wmiExecutor{
namespace: `root\microsoft\windows\storage`,
query: `SELECT deviceid, friendlyname, size, mediatype FROM msft_physicaldisk`,
runWMIQuery: func(connArgs wmiConnectionArgs) (string, error) {
var result []struct {
DeviceID string
FriendlyName string
Size int64
MediaType int16
}
if err := wmi.Query(connArgs.query, &result, connArgs.host, connArgs.namespace, connArgs.username, connArgs.password); err != nil {
return "", err
}
for _, v := range result {
c.physicalDiskToTypeMap[v.DeviceID] = FriendlyNameToDiskType(v.FriendlyName, v.Size, v.MediaType)
}
return "", nil
},
}
c.guestRuleWMIMap[internal.DataDiskAllocationUnitsRule] = wmiExecutor{
namespace: `root\cimv2`,
isRule: true,
query: `SELECT caption, blocksize FROM win32_volume`,
runWMIQuery: func(connArgs wmiConnectionArgs) (string, error) {
var result []struct {
BlockSize int64
Caption string
}
if err := wmi.Query(connArgs.query, &result, connArgs.host, connArgs.namespace, connArgs.username, connArgs.password); err != nil {
return "", err
}
re := regexp.MustCompile(`.*Volume{.*}.*`)
var r []struct {
BlockSize int64
Caption string
}
for _, v := range result {
if !re.MatchString(v.Caption) {
r = append(r, v)
}
}
res, err := json.Marshal(r)
if err != nil {
return "", err
}
return string(res), nil
},
}
c.guestRuleWMIMap[internal.GCBDRAgentRunning] = wmiExecutor{
namespace: `root\cimv2`,
isRule: true,
query: `SELECT caption FROM Win32_Process WHERE Name="udsagent.exe"`,
runWMIQuery: func(connArgs wmiConnectionArgs) (string, error) {
var result []struct {
Caption string
}
if err := wmi.Query(connArgs.query, &result, connArgs.host, connArgs.namespace, connArgs.username, connArgs.password); err != nil {
return "", err
}
if len(result) == 0 {
return "false", nil
}
return "true", nil
},
}
return &c
}
// LogicalDiskMediaType generates the logicalDrive : mediaType mappings and add the result to details.
func (c *WindowsCollector) logicalDiskMediaType(details *internal.Details) {
logicalToTypeMap := map[string]string{}
for key, val := range c.logicalToPhysicalDiskMap {
v, ok := c.physicalDiskToTypeMap[val]
if ok {
logicalToTypeMap[key] = v
}
}
if len(logicalToTypeMap) == 0 {
details.Fields[0][internal.LocalSSDRule] = "unknown"
return
}
r, err := json.Marshal(logicalToTypeMap)
if err != nil {
log.Logger.Error(err)
c.usageMetricLogger.Error(agentstatus.InvalidJSONFormatError)
} else {
details.Fields[0][internal.LocalSSDRule] = string(r)
}
}
// CollectGuestRules collects all guest rules. The rules are defined in rules.go.
func (c *WindowsCollector) CollectGuestRules(ctx context.Context, timeout time.Duration) internal.Details {
details := internal.Details{
Name: "OS",
}
fields := map[string]string{}
for rule, exe := range c.guestRuleWMIMap {
func() {
ctxWithTimeout, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
ch := make(chan bool, 1)
go func() {
connArgs := wmiConnectionArgs{
host: c.host,
username: c.username,
password: c.password,
}
connArgs.namespace = exe.namespace
connArgs.query = exe.query
res, err := exe.runWMIQuery(connArgs)
if err != nil {
log.Logger.Error(err)
c.usageMetricLogger.Error(agentstatus.WMIQueryExecutionError)
if exe.isRule {
fields[rule] = "unknown"
}
ch <- false
return
}
if exe.isRule {
fields[rule] = res
}
ch <- true
}()
select {
case <-ctxWithTimeout.Done():
log.Logger.Errorf("Running windows guest rule %s timeout", rule)
c.usageMetricLogger.Error(agentstatus.WinGuestCollectionTimeout)
case <-ch:
}
}()
}
details.Fields = append(details.Fields, fields)
c.logicalDiskMediaType(&details)
return details
}
// FriendlyNameToDiskType determines disk type based on name, size, and media type.
func FriendlyNameToDiskType(friendlyName string, size int64, mediaType int16) string {
if (friendlyName == "nvme_card" || friendlyName == "Google EphemeralDisk") && size%402653184000 == 0 {
return internal.LocalSSD.String()
} else if friendlyName == "Google PersistentDisk" && mediaType == 4 {
return internal.PersistentSSD.String()
} else {
return internal.Other.String()
}
}