internal/onetime/gcbdr/discovery/discovery.go (233 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 discovery is the module containing one time execution for HANA discovery.
package discovery
import (
"context"
"encoding/xml"
"fmt"
"flag"
"github.com/google/subcommands"
"github.com/GoogleCloudPlatform/sapagent/internal/onetime"
"github.com/GoogleCloudPlatform/sapagent/internal/usagemetrics"
"github.com/GoogleCloudPlatform/sapagent/internal/utils/filesystem"
hdpb "github.com/GoogleCloudPlatform/sapagent/protos/gcbdrhanadiscovery"
"github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/commandlineexecutor"
"github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/log"
gpb "github.com/GoogleCloudPlatform/workloadagentplatform/sharedprotos/gcbdractions"
)
const (
discoveryScriptPath = "/etc/google-cloud-sap-agent/gcbdr/discoverySAP.sh"
discoverySAPHANAXMLPath = "/etc/google-cloud-sap-agent/gcbdr/SAPHANA.xml"
)
// Applications struct for GCBDR CoreApp discovery script. It contains the list of applications
// struct.
type Applications struct {
XMLName xml.Name `xml:"applications"`
Application []Application `xml:"application"`
}
// Application struct for GCBDR CoreApp discovery script.
type Application struct {
Name string `xml:"name,attr"`
Friendlytype string `xml:"friendlytype,attr"`
Instance string `xml:"instance,attr"`
DBSID string `xml:"DBSID,attr"`
PORT string `xml:"PORT,attr"`
DBPORT string `xml:"DBPORT,attr"`
Version string `xml:"version,attr"`
Datavolowner string `xml:"datavolowner,attr"`
Hananodes string `xml:"hananodes,attr"`
Masternode string `xml:"masternode,attr"`
Standbynode string `xml:"standbynode,attr"`
Extendedworker string `xml:"extendedworker,attr"`
Keyname string `xml:"keyname,attr"`
Dbnames string `xml:"dbnames,attr"`
UUID string `xml:"uuid,attr"`
Hardwarekey string `xml:"hardwarekey,attr"`
Sitename string `xml:"sitename,attr"`
Configtype string `xml:"configtype,attr"`
Clustertype string `xml:"clustertype,attr"`
ReplicationNodes string `xml:"replication_nodes,attr"`
Files Files `xml:"files"`
Logbackuppath Logbackuppath `xml:"logbackuppath"`
Globalinipath Globalinipath `xml:"globalinipath"`
Catalogbackuppath Catalogbackuppath `xml:"catalogbackuppath"`
Logmode string `xml:"logmode,attr"`
Scripts Scripts `xml:"scripts"`
Volumes Volumes `xml:"volumes"`
}
// Files struct for GCBDR CoreApp discovery script.
type Files struct {
File []File `xml:"file"`
}
// File struct for GCBDR CoreApp discovery script.
type File struct {
Path string `xml:"path,attr"`
Datavol string `xml:"datavol,attr"`
}
// Logbackuppath struct for GCBDR CoreApp discovery script.
type Logbackuppath struct {
File File `xml:"file"`
}
// Globalinipath struct for GCBDR CoreApp discovery script.
type Globalinipath struct {
File File `xml:"file"`
}
// Catalogbackuppath struct for GCBDR CoreApp discovery script.
type Catalogbackuppath struct {
File File `xml:"file"`
}
// Scripts struct for GCBDR CoreApp discovery script.
type Scripts struct {
Script []Script `xml:"script"`
}
// Script struct for GCBDR CoreApp discovery script.
type Script struct {
Phase string `xml:"phase,attr"`
Path string `xml:"path,attr"`
}
// Volumes struct for GCBDR CoreApp discovery script.
type Volumes struct {
Volume []Volume `xml:"volume"`
}
// Volume struct for GCBDR CoreApp discovery script.
type Volume struct {
Name string `xml:"name,attr"`
Mountpoint string `xml:"mountpoint,attr"`
Vgname string `xml:"vgname,attr"`
Lvname string `xml:"lvname,attr"`
Pddisks Pddisks `xml:"pddisks"`
}
// Pddisks struct for GCBDR CoreApp discovery script.
type Pddisks struct {
Pd []Pd `xml:"pd"`
}
// Pd struct for GCBDR CoreApp discovery script.
type Pd struct {
Disk string `xml:"disk,attr"`
Devicename string `xml:"devicename,attr"`
}
// Discovery struct has arguments for discovery subcommand.
type Discovery struct {
FSH filesystem.FileSystem
help bool
logLevel, logPath string
oteLogger *onetime.OTELogger
}
// Name implements the subcommand interface for Discovery.
func (*Discovery) Name() string { return "gcbdr-discovery" }
// Synopsis implements the subcommand interface for Discovery.
func (*Discovery) Synopsis() string {
return "deep discovery on the HANA DB to discover HANA DB attributes, PD disks supporting Data and log volumes and PD properties."
}
// Usage implements the subcommand interface for Discovery.
func (*Discovery) Usage() string {
return "Usage: gcbdr-discovery [-h] [-loglevel=<debug|info|warn|error>] [-log-path=<log-path>]\n"
}
// SetFlags implements the subcommand interface for Discovery.
func (d *Discovery) SetFlags(fs *flag.FlagSet) {
fs.BoolVar(&d.help, "h", false, "Display help")
fs.StringVar(&d.logLevel, "loglevel", "info", "Sets the logging level for a log file")
fs.StringVar(&d.logPath, "log-path", "", "The log path to write the log file (optional), default value is /var/log/google-cloud-sap-agent/gcbdr-discovery.log")
}
// Execute implements the subcommand interface for Discovery.
// This is not in use, but is required for the subcommands.Command interface.
func (d *Discovery) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
_, _, exitStatus, completed := onetime.Init(ctx, onetime.InitOptions{
Name: d.Name(),
Help: d.help,
LogLevel: d.logLevel,
LogPath: d.logPath,
Fs: f,
}, args...)
if !completed {
return exitStatus
}
d.oteLogger = onetime.CreateOTELogger(false)
_, result := d.discoveryHandler(ctx, commandlineexecutor.ExecuteCommand, d.FSH)
exitStatus = subcommands.ExitSuccess
if result.GetExitCode() != 0 {
exitStatus = subcommands.ExitFailure
}
if exitStatus == subcommands.ExitFailure {
d.oteLogger.LogUsageError(usagemetrics.GCBDRDiscoveryFailure)
}
return exitStatus
}
func (d *Discovery) discoveryHandler(ctx context.Context, exec commandlineexecutor.Execute, fsh filesystem.FileSystem) (*Applications, *gpb.CommandResult) {
log.CtxLogger(ctx).Info("Starting HANA DB discovery using GCBDR CoreAPP script")
d.oteLogger.LogUsageAction(usagemetrics.GCBDRDiscoveryStarted)
args := commandlineexecutor.Params{
Executable: "/bin/bash",
ArgsToSplit: discoveryScriptPath,
}
res := exec(ctx, args)
result := &gpb.CommandResult{
Stdout: res.StdOut,
Stderr: res.StdErr,
ExitCode: int32(res.ExitCode),
}
if res.ExitCode != 0 {
log.CtxLogger(ctx).Errorf("Failed to execute GCBDR CoreAPP script %v", res.StdErr)
return nil, result
}
xmlContent, err := fsh.ReadFile(discoverySAPHANAXMLPath)
if err != nil {
errMsg := fmt.Sprintf("Could not read the file for HANA discovery. file: %v , error: %v", discoverySAPHANAXMLPath, err)
log.CtxLogger(ctx).Errorw(errMsg)
result.Stderr = errMsg
return nil, result
}
apps := &Applications{}
err = xml.Unmarshal(xmlContent, apps)
if err != nil {
errMsg := fmt.Sprintf("Failed to unmarshal GCBDR CoreAPP script: %v", err)
log.CtxLogger(ctx).Errorf(errMsg)
result.Stderr = errMsg
return nil, result
}
log.CtxLogger(ctx).Info("HANA Applications discovered %v", apps.Application)
d.oteLogger.LogUsageAction(usagemetrics.GCBDRDiscoveryFinished)
return apps, result
}
// Run provides the Daemon mode invocation for gcbdr discovery, returning the
// list of HANA discovery applications.
func (d *Discovery) Run(ctx context.Context, opts *onetime.RunOptions, exec commandlineexecutor.Execute, fsh filesystem.FileSystem) (*hdpb.ApplicationsList, *gpb.CommandResult) {
d.oteLogger = onetime.CreateOTELogger(opts.DaemonMode)
apps, cmdResult := d.discoveryHandler(ctx, exec, fsh)
if cmdResult.ExitCode != 0 {
log.CtxLogger(ctx).Errorf("Failed to get HANA discovery applications. Exit code: %v", cmdResult.ExitCode)
d.oteLogger.LogUsageError(usagemetrics.GCBDRDiscoveryFailure)
return nil, cmdResult
}
result := constructApplicationsProto(apps)
return result, cmdResult
}
func constructApplicationsProto(apps *Applications) *hdpb.ApplicationsList {
if len(apps.Application) == 0 {
return nil
}
result := &hdpb.ApplicationsList{}
for _, app := range apps.Application {
protoApp := hdpb.Application{
Name: app.Name,
Dbsid: app.DBSID,
Type: app.Friendlytype,
HanaVersion: app.Version,
ConfigType: app.Configtype,
HardwareKey: app.Hardwarekey,
Port: app.PORT,
HanaNodes: app.Hananodes,
MasterNode: app.Masternode,
ReplicationNodes: app.ReplicationNodes,
Instance: app.Instance,
CatalogBackupPath: app.Catalogbackuppath.File.Path,
GlobalInitPath: app.Globalinipath.File.Path,
DataVolumeOwner: app.Datavolowner,
DbNames: app.Dbnames,
}
setPDVolumes(&protoApp, app.Volumes)
result.Apps = append(result.Apps, &protoApp)
}
return result
}
func setPDVolumes(protoApp *hdpb.Application, volumes Volumes) {
if len(volumes.Volume) == 0 {
return
}
vols := []*hdpb.VolumePD{}
for _, vol := range volumes.Volume {
protoVol := &hdpb.VolumePD{
MountPoint: vol.Mountpoint,
VolumeType: vol.Name,
VolumeGroup: vol.Vgname,
}
switch vol.Name {
case "datavol":
protoVol.VolumeName = fmt.Sprintf("%s/DB%s", vol.Mountpoint, protoApp.GetDbsid())
protoVol.LogicalName = "data"
case "logvol":
protoVol.VolumeName = fmt.Sprintf("%s/DB%s", vol.Mountpoint, protoApp.GetDbsid())
protoVol.LogicalName = "log"
case "logbackupvol":
protoVol.VolumeName = fmt.Sprintf("%s/log", vol.Mountpoint)
protoVol.LogicalName = "logbackup"
}
vols = append(vols, protoVol)
}
protoApp.VolumeDetails = vols
}