internal/sqlservermetrics/sqlservermetrics_windows.go (227 lines of code) (raw):

/* 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 sqlservermetrics import ( "context" "fmt" "path/filepath" "regexp" "strings" "time" "github.com/GoogleCloudPlatform/workloadagent/internal/sqlservermetrics/guestoscollector" "github.com/GoogleCloudPlatform/workloadagent/internal/sqlservermetrics/guestoscollector/remote" "github.com/GoogleCloudPlatform/workloadagent/internal/sqlservermetrics/sqlcollector" "github.com/GoogleCloudPlatform/workloadagent/internal/sqlservermetrics/sqlserverutils" "github.com/GoogleCloudPlatform/workloadagent/internal/usagemetrics" "github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/log" ) const ( commandFind = `sudo find %s -type f -iname "%s" -print` commandDf = "sudo df --output=target %s | tail -n 1" commandMount = "mount | grep sd" ) // osCollection is the windows implementation of OSCollection. func (s *SQLServerMetrics) osCollection(ctx context.Context) error { if !s.Config.GetCollectionConfiguration().GetCollectGuestOsMetrics() { return nil } wlm, err := s.initCollection(ctx, true) if err != nil { usagemetrics.Error(usagemetrics.WorkloadManagerConnectionError) return err } for _, credentialCfg := range s.Config.GetCredentialConfigurations() { guestCfg := guestConfigFromCredential(credentialCfg) if err := validateCredCfgGuest(s.Config.GetRemoteCollection(), !guestCfg.LinuxRemote, guestCfg, credentialCfg.GetVmProperties().GetInstanceId(), credentialCfg.GetVmProperties().GetInstanceName()); err != nil { usagemetrics.Error(usagemetrics.SQLServerInvalidConfigurationsError) log.Logger.Errorw("Invalid credential configuration", "error", err) if !s.Config.GetRemoteCollection() { break } continue } targetInstanceProps := sip var c guestoscollector.GuestCollector if s.Config.GetRemoteCollection() { // remote collection targetInstanceProps = instanceProperties{ InstanceID: credentialCfg.GetVmProperties().GetInstanceId(), Instance: credentialCfg.GetVmProperties().GetInstanceName(), } host := guestCfg.ServerName username := guestCfg.GuestUserName if !guestCfg.LinuxRemote { log.Logger.Debug("Starting remote win guest collection for ip " + host) projectID := guestCfg.ProjectID if projectID == "" { projectID = sip.ProjectID } pswd, err := secretValue(ctx, projectID, guestCfg.GuestSecretName) if err != nil { usagemetrics.Error(usagemetrics.SecretManagerValueError) log.Logger.Errorw("Collection failed", "target", guestCfg.ServerName, "error", fmt.Errorf("failed to get secret value: %v", err)) if !s.Config.GetRemoteCollection() { break } continue } c = guestoscollector.NewWindowsCollector(host, username, pswd) } else { // on local windows vm collecting on remote linux vm's, we use ssh, otherwise we use wmi log.Logger.Debug("Starting remote linux guest collection for ip " + host) // disks only used for local linux collection c = guestoscollector.NewLinuxCollector(nil, host, username, guestCfg.LinuxSSHPrivateKeyPath, true, guestCfg.GuestPortNumber) } } else { // local win collection log.Logger.Debug("Starting local win guest collection") c = guestoscollector.NewWindowsCollector(nil, nil, nil) } details := []sqlserverutils.MetricDetails{} log.Logger.Debug("Collecting guest rules") details = append(details, c.CollectGuestRules(ctx, s.Config.GetCollectionTimeout().AsDuration())) if err := guestoscollector.UnknownOsFields(&details); err != nil { log.Logger.Warnf("RunOSCollection: Failed to mark unknown collected fields. error: %v", err) } log.Logger.Debug("Collecting guest rules completes") log.Logger.Debugf("Source vm %s is sending os collected data on target machine, %s, to workload manager.", sip.Instance, targetInstanceProps.Instance) updateCollectedData(wlm, sip, targetInstanceProps, details) sendRequestToWLM(ctx, wlm, sip.Name, s.Config.GetMaxRetries(), s.Config.GetRetryFrequency().AsDuration()) // Local collection. // Exit the loop. Only take the first credential in the credentialconfiguration array. if !s.Config.GetRemoteCollection() { break } } return nil } // sqlCollection is the windows implementation of SQLCollection. func (s *SQLServerMetrics) sqlCollection(ctx context.Context) error { if !s.Config.GetCollectionConfiguration().GetCollectSqlMetrics() { return nil } wlm, err := s.initCollection(ctx, true) if err != nil { usagemetrics.Error(usagemetrics.WorkloadManagerConnectionError) return err } for _, credentialCfg := range s.Config.GetCredentialConfigurations() { validationDetails := []sqlserverutils.MetricDetails{} guestCfg := guestConfigFromCredential(credentialCfg) for _, sqlCfg := range sqlConfigFromCredential(credentialCfg) { if err := validateCredCfgSQL(s.Config.GetRemoteCollection(), !guestCfg.LinuxRemote, sqlCfg, guestCfg, credentialCfg.GetVmProperties().GetInstanceId(), credentialCfg.GetVmProperties().GetInstanceName()); err != nil { usagemetrics.Error(usagemetrics.SQLServerInvalidConfigurationsError) log.Logger.Errorw("Invalid credential configuration", "error", err) continue } projectID := sqlCfg.ProjectID if projectID == "" { projectID = sip.ProjectID } pswd, err := secretValue(ctx, projectID, sqlCfg.SecretName) if err != nil { usagemetrics.Error(usagemetrics.SecretManagerValueError) log.Logger.Errorw("Failed to get secret value", "error", err) continue } conn := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%d;", sqlCfg.Host, sqlCfg.Username, pswd, sqlCfg.PortNumber) details, err := func(ctx context.Context, conn string, timeout time.Duration, windows bool) ([]sqlserverutils.MetricDetails, error) { c, err := sqlcollector.NewV1("sqlserver", conn, windows) if err != nil { return nil, err } defer c.Close() // Start db collection. log.Logger.Debug("Collecting SQL Server rules.") details := c.CollectSQLMetrics(ctx, timeout) log.Logger.Debug("Collecting SQL Server rules completes.") return details, nil }(ctx, conn, s.Config.GetCollectionTimeout().AsDuration(), !guestCfg.LinuxRemote) if err != nil { log.Logger.Errorw("Failed to run sql collection", "error", err) continue } for _, detail := range details { for _, field := range detail.Fields { field["host_name"] = sqlCfg.Host field["port_number"] = fmt.Sprintf("%d", sqlCfg.PortNumber) } } // getting physical drive if on local windows collecting sql on linux remote if s.Config.GetRemoteCollection() && guestCfg.LinuxRemote { addPhysicalDriveRemoteLinux(details, guestCfg) } else { addPhysicalDriveLocal(ctx, details, true) } for i, detail := range details { for _, vd := range validationDetails { if detail.Name == vd.Name { detail.Fields = append(vd.Fields, detail.Fields...) details[i] = detail break } } } validationDetails = details } targetInstanceProps := sip // update targetInstanceProps value for remote collections. if s.Config.GetRemoteCollection() { // remote collection targetInstanceProps = instanceProperties{ InstanceID: credentialCfg.GetVmProperties().GetInstanceId(), Instance: credentialCfg.GetVmProperties().GetInstanceName(), } } updateCollectedData(wlm, sip, targetInstanceProps, validationDetails) log.Logger.Debugf("Source vm %s is sending collected sql data on target machine, %s, to workload manager.", sip.Instance, targetInstanceProps.Instance) sendRequestToWLM(ctx, wlm, sip.Name, s.Config.GetMaxRetries(), s.Config.GetRetryFrequency().AsDuration()) } return nil } // addPhysicalDriveRemoteLinux adds physical drive to sql collection based off details for windows to remote linux instances func addPhysicalDriveRemoteLinux(details []sqlserverutils.MetricDetails, cred *sqlserverutils.GuestConfig) { user := cred.GuestUserName port := cred.GuestPortNumber ip := cred.ServerName // We need to call NewRemote, SetupKeys and CreateClient respectively to set up the remote correctly. r := remote.NewRemote(ip, user, port) if err := r.SetupKeys(cred.LinuxSSHPrivateKeyPath); err != nil { log.Logger.Errorw("Failed to setup keys.", "error", err) return } if err := r.CreateClient(); err != nil { log.Logger.Errorw("Failed to create client.", "error", err) return } defer r.Close() for _, detail := range details { if detail.Name != "DB_LOG_DISK_SEPARATION" { continue } for _, field := range detail.Fields { physicalPath, pathExists := field["physical_name"] if !pathExists { log.Logger.Warn("physical_name field for DB_LOG_DISK_SEPERATION does not exist") continue } dir, filePath := filepath.Split(physicalPath) findCommand := fmt.Sprintf(commandFind, dir, filePath) filePath, filePathErr := remote.RunCommandWithPipes(findCommand, r) if filePathErr != nil { log.Logger.Warnf("Failed to run cmd %v. error: %v", findCommand, filePathErr) continue } filePath = strings.TrimSuffix(filePath, "\n") command := fmt.Sprintf(commandDf, filePath) physicalPathMount, physicalPathErr := remote.RunCommandWithPipes(command, r) if physicalPathErr != nil { log.Logger.Warnf("Failed to run cmd %v. error: %v", command, physicalPathErr) continue } physicalPathMount = strings.TrimSuffix(physicalPathMount, "\n") resultMount, mountErr := remote.RunCommandWithPipes(commandMount, r) if mountErr != nil { log.Logger.Warnf("Failed to run cmd %v. error: %v", commandMount, mountErr) continue } allMounts := strings.TrimSuffix(resultMount, "\n") physicalDriveHelper := regexp.MustCompile(` `+physicalPathMount+` `).Split(allMounts, -1) physicalDrives := []string{} for i := 0; i < len(physicalDriveHelper)-1; i++ { splitStr := regexp.MustCompile("\n| |/").Split(physicalDriveHelper[i], -1) if len(splitStr) < 2 { log.Logger.Warn("regex for linux error. Unable to find physical drive associated with mount.") continue } physicalDrives = append(physicalDrives, splitStr[len(splitStr)-2]) } physicalDrive := strings.Join(physicalDrives, ", ") field["physical_drive"] = physicalDrive } } }