diff --git a/commands/cluster_command_launcher.go b/commands/cluster_command_launcher.go index 196a8fe..6978ef7 100644 --- a/commands/cluster_command_launcher.go +++ b/commands/cluster_command_launcher.go @@ -100,6 +100,7 @@ func constructCmds(_ vlog.Printer) []ClusterCommand { makeCmdReIP(), makeCmdReviveDB(), makeCmdShowRestorePoints(), + makeCmdInstallPackages(), // sc-scope cmds makeCmdAddSubcluster(), makeCmdRemoveSubcluster(), diff --git a/commands/cmd_install_packages.go b/commands/cmd_install_packages.go new file mode 100644 index 0000000..44b88e4 --- /dev/null +++ b/commands/cmd_install_packages.go @@ -0,0 +1,124 @@ +/* + (c) Copyright [2023] Open Text. + 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. +*/ + +package commands + +import ( + "flag" + + "github.com/vertica/vcluster/vclusterops" + "github.com/vertica/vcluster/vclusterops/util" + "github.com/vertica/vcluster/vclusterops/vlog" +) + +/* CmdInstallPackages + * + * Parses arguments for VInstallPackagesOptions to pass down to + * VInstallPackages. + * + * Implements ClusterCommand interface + */ + +type CmdInstallPackages struct { + CmdBase + installPkgOpts *vclusterops.VInstallPackagesOptions +} + +func makeCmdInstallPackages() *CmdInstallPackages { + newCmd := &CmdInstallPackages{} + + // parser, used to parse command-line flags + newCmd.parser = flag.NewFlagSet("install_packages", flag.ExitOnError) + installPkgOpts := vclusterops.VInstallPackagesOptionsFactory() + + // required flags + installPkgOpts.DBName = newCmd.parser.String("db-name", "", "The name of the database to install packages in") + + // optional flags + installPkgOpts.Password = newCmd.parser.String("password", "", util.GetOptionalFlagMsg("Database password in single quotes")) + newCmd.hostListStr = newCmd.parser.String("hosts", "", util.GetOptionalFlagMsg("Comma-separated list of hosts in database.")) + newCmd.ipv6 = newCmd.parser.Bool("ipv6", false, util.GetOptionalFlagMsg("Used to specify the hosts are IPv6 hosts")) + installPkgOpts.HonorUserInput = newCmd.parser.Bool("honor-user-input", false, + util.GetOptionalFlagMsg("Forcefully use the user's input instead of reading the options from "+vclusterops.ConfigFileName)) + installPkgOpts.ConfigDirectory = newCmd.parser.String("config-directory", "", + util.GetOptionalFlagMsg("Directory where "+vclusterops.ConfigFileName+" is located")) + installPkgOpts.ForceReinstall = newCmd.parser.Bool("force-reinstall", false, + util.GetOptionalFlagMsg("Install the packages, even if they are already installed.")) + + newCmd.installPkgOpts = &installPkgOpts + + newCmd.parser.Usage = func() { + util.SetParserUsage(newCmd.parser, "install_packages") + } + + return newCmd +} + +func (c *CmdInstallPackages) CommandType() string { + return "install_packages" +} + +func (c *CmdInstallPackages) Parse(inputArgv []string, logger vlog.Printer) error { + c.argv = inputArgv + err := c.ValidateParseArgv(c.CommandType(), logger) + if err != nil { + return err + } + + // for some options, we do not want to use their default values, + // if they are not provided in cli, + // reset the value of those options to nil + if !util.IsOptionSet(c.parser, "password") { + c.installPkgOpts.Password = nil + } + if !util.IsOptionSet(c.parser, "ipv6") { + c.CmdBase.ipv6 = nil + } + if !util.IsOptionSet(c.parser, "config-directory") { + c.installPkgOpts.ConfigDirectory = nil + } + + return c.validateParse() +} + +// all validations of the arguments should go in here +func (c *CmdInstallPackages) validateParse() error { + return c.ValidateParseBaseOptions(&c.installPkgOpts.DatabaseOptions) +} + +func (c *CmdInstallPackages) Analyze(_ vlog.Printer) error { + return nil +} + +func (c *CmdInstallPackages) Run(vcc vclusterops.VClusterCommands) error { + options := c.installPkgOpts + + // get config from vertica_cluster.yaml + config, err := options.GetDBConfig(vcc) + if err != nil { + return err + } + options.Config = config + + var status *vclusterops.InstallPackageStatus + status, err = vcc.VInstallPackages(options) + if err != nil { + vcc.Log.Error(err, "failed to install the packages") + return err + } + + vcc.Log.PrintInfo("Installed the packages:\n%v", status.Packages) + return nil +} diff --git a/commands/cmd_revive_db.go b/commands/cmd_revive_db.go index 5586b63..ce431bc 100644 --- a/commands/cmd_revive_db.go +++ b/commands/cmd_revive_db.go @@ -53,6 +53,8 @@ func makeCmdReviveDB() *CmdReviveDB { "The (1-based) index of the restore point in the restore archive to restore from")) reviveDBOptions.RestorePoint.ID = newCmd.parser.String("restore-point-id", "", util.GetOptionalFlagMsg( "The identifier of the restore point in the restore archive to restore from")) + reviveDBOptions.ConfigDirectory = newCmd.parser.String("config-directory", "", + util.GetOptionalFlagMsg("Directory where "+vclusterops.ConfigFileName+" is located")) newCmd.reviveDBOptions = &reviveDBOptions @@ -79,6 +81,9 @@ func (c *CmdReviveDB) Parse(inputArgv []string, logger vlog.Printer) error { if !util.IsOptionSet(c.parser, "ipv6") { c.CmdBase.ipv6 = nil } + if !util.IsOptionSet(c.parser, "config-directory") { + c.reviveDBOptions.ConfigDirectory = nil + } return c.validateParse(logger) } @@ -113,7 +118,7 @@ func (c *CmdReviveDB) Analyze(logger vlog.Printer) error { func (c *CmdReviveDB) Run(vcc vclusterops.VClusterCommands) error { vcc.Log.V(1).Info("Called method Run()") - dbInfo, err := vcc.VReviveDatabase(c.reviveDBOptions) + dbInfo, vdb, err := vcc.VReviveDatabase(c.reviveDBOptions) if err != nil { vcc.Log.Error(err, "fail to revive database", "DBName", *c.reviveDBOptions.DBName) return err @@ -124,6 +129,11 @@ func (c *CmdReviveDB) Run(vcc vclusterops.VClusterCommands) error { return nil } + err = vdb.WriteClusterConfig(c.reviveDBOptions.ConfigDirectory, vcc.Log) + if err != nil { + vcc.Log.PrintWarning("fail to write config file, details: %s", err) + } + vcc.Log.PrintInfo("Successfully revived database %s", *c.reviveDBOptions.DBName) return nil diff --git a/vclusterops/cluster_config.go b/vclusterops/cluster_config.go index 055b3de..7db65e7 100644 --- a/vclusterops/cluster_config.go +++ b/vclusterops/cluster_config.go @@ -149,6 +149,22 @@ func (c *ClusterConfig) getCommunalStorageLocation(dbName string) (communalStora return dbConfig.CommunalStorageLocation, nil } +func (c *ClusterConfig) removeDatabaseFromConfigFile(dbName, configDirectory string, logger vlog.Printer) error { + configFilePath := filepath.Join(configDirectory, ConfigFileName) + + // back up the old config file + err := backupConfigFile(configFilePath, logger) + if err != nil { + return err + } + + // remove the target database from the cluster config + // and overwrite the config file + delete(*c, dbName) + + return c.WriteConfig(configFilePath) +} + func (c *DatabaseConfig) getHosts() []string { var hostList []string @@ -207,7 +223,7 @@ func backupConfigFile(configFilePath string, logger vlog.Printer) error { return nil } -func removeConfigFile(configDirectory string, logger vlog.Printer) error { +func RemoveConfigFile(configDirectory string, logger vlog.Printer) error { configFilePath := filepath.Join(configDirectory, ConfigFileName) configBackupPath := filepath.Join(configDirectory, ConfigBackupName) diff --git a/vclusterops/coordinator_database.go b/vclusterops/coordinator_database.go index 96de41f..cf0ea6b 100644 --- a/vclusterops/coordinator_database.go +++ b/vclusterops/coordinator_database.go @@ -439,9 +439,24 @@ func (vdb *VCoordinationDatabase) WriteClusterConfig(configDir *string, logger v nodeConfig.Name = vnode.Name nodeConfig.Address = vnode.Address nodeConfig.Subcluster = vnode.Subcluster - nodeConfig.CatalogPath = vdb.CatalogPrefix - nodeConfig.DataPath = vdb.DataPrefix - nodeConfig.DepotPath = vdb.DepotPrefix + + // VER-91869 will replace the path prefixes with full paths + if vdb.CatalogPrefix == "" { + nodeConfig.CatalogPath = util.GetPathPrefix(vnode.CatalogPath) + } else { + nodeConfig.CatalogPath = vdb.CatalogPrefix + } + if vdb.DataPrefix == "" && len(vnode.StorageLocations) > 0 { + nodeConfig.DataPath = util.GetPathPrefix(vnode.StorageLocations[0]) + } else { + nodeConfig.DataPath = vdb.DataPrefix + } + if vdb.IsEon && vdb.DepotPrefix == "" { + nodeConfig.DepotPath = util.GetPathPrefix(vnode.DepotPath) + } else { + nodeConfig.DepotPath = vdb.DepotPrefix + } + dbConfig.Nodes = append(dbConfig.Nodes, &nodeConfig) } dbConfig.IsEon = vdb.IsEon diff --git a/vclusterops/create_db.go b/vclusterops/create_db.go index 20c2716..006265b 100644 --- a/vclusterops/create_db.go +++ b/vclusterops/create_db.go @@ -647,7 +647,8 @@ func (vcc *VClusterCommands) produceAdditionalCreateDBInstructions(vdb *VCoordin } if !*options.SkipPackageInstall { - httpsInstallPackagesOp, err := makeHTTPSInstallPackagesOp(vcc.Log, bootstrapHost, true, username, options.Password) + httpsInstallPackagesOp, err := makeHTTPSInstallPackagesOp(vcc.Log, bootstrapHost, true, username, options.Password, + false /* forceReinstall */, true /* verbose */) if err != nil { return instructions, err } diff --git a/vclusterops/drop_db.go b/vclusterops/drop_db.go index e1d71c3..aecea6d 100644 --- a/vclusterops/drop_db.go +++ b/vclusterops/drop_db.go @@ -110,11 +110,12 @@ func (vcc *VClusterCommands) VDropDatabase(options *VDropDatabaseOptions) error return fmt.Errorf("fail to drop database: %w", runError) } - // if the database is successfully dropped, the config file will be removed + // if the database is successfully dropped, the database will be removed from the config file // if failed to remove it, we will ask users to manually do it - err = removeConfigFile(configDir, vcc.Log) + err = clusterConfig.removeDatabaseFromConfigFile(vdb.Name, configDir, vcc.Log) if err != nil { - vcc.Log.PrintWarning("Fail to remove the config file(s), please manually clean up under directory %s", configDir) + vcc.Log.PrintWarning("Fail to remove the database information from config file, "+ + "please manually clean up under directory %s. Details: %v", configDir, err) } return nil diff --git a/vclusterops/https_get_up_nodes_op.go b/vclusterops/https_get_up_nodes_op.go index c59629d..ed90b96 100644 --- a/vclusterops/https_get_up_nodes_op.go +++ b/vclusterops/https_get_up_nodes_op.go @@ -30,6 +30,7 @@ const ( StopDBCmd ScrutinizeCmd DBAddSubclusterCmd + InstallPackageCmd ) type CommandType int diff --git a/vclusterops/https_install_packages_op.go b/vclusterops/https_install_packages_op.go index 45f18be..116d247 100644 --- a/vclusterops/https_install_packages_op.go +++ b/vclusterops/https_install_packages_op.go @@ -18,6 +18,7 @@ package vclusterops import ( "errors" "fmt" + "strconv" "github.com/vertica/vcluster/vclusterops/util" "github.com/vertica/vcluster/vclusterops/vlog" @@ -26,15 +27,20 @@ import ( type httpsInstallPackagesOp struct { opBase opHTTPSBase + verbose bool // Include verbose output about package install status + forceReinstall bool + status InstallPackageStatus // Filled in once the op completes } func makeHTTPSInstallPackagesOp(logger vlog.Printer, hosts []string, useHTTPPassword bool, - userName string, httpsPassword *string, + userName string, httpsPassword *string, forceReinstall bool, verbose bool, ) (httpsInstallPackagesOp, error) { op := httpsInstallPackagesOp{} op.name = "HTTPSInstallPackagesOp" op.logger = logger.WithName(op.name) op.hosts = hosts + op.verbose = verbose + op.forceReinstall = forceReinstall err := util.ValidateUsernameAndPassword(op.name, useHTTPPassword, userName) if err != nil { @@ -55,6 +61,9 @@ func (op *httpsInstallPackagesOp) setupClusterHTTPRequest(hosts []string) error httpRequest.Password = op.httpsPassword httpRequest.Username = op.userName } + httpRequest.QueryParams = map[string]string{ + "force-install": strconv.FormatBool(op.forceReinstall), + } op.clusterHTTPRequest.RequestCollection[host] = httpRequest } @@ -62,6 +71,14 @@ func (op *httpsInstallPackagesOp) setupClusterHTTPRequest(hosts []string) error } func (op *httpsInstallPackagesOp) prepare(execContext *opEngineExecContext) error { + // If no hosts passed in, we will find the hosts from execute-context + if len(op.hosts) == 0 { + if len(execContext.upHosts) == 0 { + return fmt.Errorf(`[%s] Cannot find any up hosts in OpEngineExecContext`, op.name) + } + // use first up host to execute https post request + op.hosts = []string{execContext.upHosts[0]} + } execContext.dispatcher.setup(op.hosts) return op.setupClusterHTTPRequest(op.hosts) @@ -80,7 +97,8 @@ func (op *httpsInstallPackagesOp) finalize(_ *opEngineExecContext) error { } /* - httpsInstallPackagesResponse example: +The response from the package endpoint, which are encoded in the next two +structs, will look like this: {'packages': [ @@ -94,9 +112,22 @@ func (op *httpsInstallPackagesOp) finalize(_ *opEngineExecContext) error { }, ... ] - } +} */ -type httpsInstallPackagesResponse map[string][]map[string]string + +// InstallPackageStatus provides status for each package install attempted. +type InstallPackageStatus struct { + Packages []PackageStatus `json:"packages"` +} + +// PackageStatus has install status for a single package. +type PackageStatus struct { + // Name of the package this status is for + PackageName string `json:"package_name"` + // One word outcome of the install status: + // Skipped, Success or Failure + InstallStatus string `json:"install_status"` +} func (op *httpsInstallPackagesOp) processResult(_ *opEngineExecContext) error { var allErrs error @@ -109,21 +140,25 @@ func (op *httpsInstallPackagesOp) processResult(_ *opEngineExecContext) error { continue } - var responseObj httpsInstallPackagesResponse - err := op.parseAndCheckResponse(host, result.content, &responseObj) - + err := op.parseAndCheckResponse(host, result.content, &op.status) if err != nil { allErrs = errors.Join(allErrs, err) continue } - installedPackages, ok := responseObj["packages"] - if !ok { - err = fmt.Errorf(`[%s] response does not contain field "packages"`, op.name) + if len(op.status.Packages) == 0 { + err = fmt.Errorf(`[%s] response does not have status for any packages`, op.name) allErrs = errors.Join(allErrs, err) } - op.logger.PrintInfo("[%s] installed packages: %v", op.name, installedPackages) + // Only print out status if verbose output was requested. Otherwise, + // just write status to the log. + msg := fmt.Sprintf("[%s] installation status of packages: %v", op.name, op.status.Packages) + if op.verbose { + op.logger.PrintInfo(msg) + } else { + op.logger.V(1).Info(msg) + } } return allErrs } diff --git a/vclusterops/install_packages.go b/vclusterops/install_packages.go new file mode 100644 index 0000000..675187e --- /dev/null +++ b/vclusterops/install_packages.go @@ -0,0 +1,151 @@ +/* + (c) Copyright [2023] Open Text. + 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. +*/ + +package vclusterops + +import ( + "fmt" + + "github.com/vertica/vcluster/vclusterops/util" + "github.com/vertica/vcluster/vclusterops/vlog" +) + +type VInstallPackagesOptions struct { + /* part 1: basic db info */ + DatabaseOptions + + // If true, the packages will be reinstalled even if they are already installed. + ForceReinstall *bool +} + +type vInstallPackagesInfo struct { + dbName string + hosts []string + userName string + password *string +} + +func VInstallPackagesOptionsFactory() VInstallPackagesOptions { + opt := VInstallPackagesOptions{ + ForceReinstall: new(bool), + } + opt.DatabaseOptions.setDefaultValues() + return opt +} + +// resolve hostnames to be IPs +func (options *VInstallPackagesOptions) analyzeOptions() (err error) { + // we analyze hostnames when HonorUserInput is set, otherwise we use hosts in yaml config + if *options.HonorUserInput { + // resolve RawHosts to be IP addresses + options.Hosts, err = util.ResolveRawHostsToAddresses(options.RawHosts, options.Ipv6.ToBool()) + if err != nil { + return err + } + } + + return nil +} + +func (options *VInstallPackagesOptions) validateAnalyzeOptions(log vlog.Printer) error { + if err := options.validateBaseOptions("install_packages", log); err != nil { + return err + } + return options.analyzeOptions() +} + +func (vcc *VClusterCommands) VInstallPackages(options *VInstallPackagesOptions) (*InstallPackageStatus, error) { + /* + * - Produce Instructions + * - Create a VClusterOpEngine + * - Give the instructions to the VClusterOpEngine to run + */ + + err := options.validateAnalyzeOptions(vcc.Log) + if err != nil { + return nil, err + } + + installPkgInfo := new(vInstallPackagesInfo) + installPkgInfo.userName = *options.UserName + installPkgInfo.password = options.Password + installPkgInfo.dbName, installPkgInfo.hosts, err = options.getNameAndHosts(options.Config) + if err != nil { + return nil, err + } + + // Generate the instructions and a pointer to the status object that will + // get filled in when we run the instructions. + instructions, status, err := vcc.produceInstallPackagesInstructions(installPkgInfo, options) + if err != nil { + return nil, fmt.Errorf("fail to production instructions: %w", err) + } + + // Create a VClusterOpEngine. No need for certs since this operation doesn't + // talk to the NMA. + clusterOpEngine := makeClusterOpEngine(instructions, &httpsCerts{}) + + // Give the instructions to the VClusterOpEngine to run + runError := clusterOpEngine.run(vcc.Log) + if runError != nil { + return nil, fmt.Errorf("fail to install packages: %w", runError) + } + if len(status.Packages) == 0 { + return nil, fmt.Errorf("did not flow back the install package status") + } + + return status, nil +} + +// produceInstallPackagesInstructions will build a list of instructions to execute for +// the install packages operation. It will return a status object that gets +// filled in when the instructions are run. +// +// The generated instructions are as follows: +// - Get up nodes through https call +// - Install packages using one of the up nodes +func (vcc *VClusterCommands) produceInstallPackagesInstructions(info *vInstallPackagesInfo, + opts *VInstallPackagesOptions, +) ([]clusterOp, *InstallPackageStatus, error) { + // when password is specified, we will use username/password to call https endpoints + usePassword := false + if info.password != nil { + usePassword = true + err := opts.validateUserName(vcc.Log) + if err != nil { + return nil, nil, err + } + } + + httpsGetUpNodesOp, err := makeHTTPSGetUpNodesOp(vcc.Log, info.dbName, info.hosts, + usePassword, *opts.UserName, info.password, InstallPackageCmd) + if err != nil { + return nil, nil, err + } + + var noHosts = []string{} // We pass in no hosts so that this op picks an up node from the previous call. + verbose := false // Silence verbose output as we will print package status at the end + installOp, err := makeHTTPSInstallPackagesOp(vcc.Log, noHosts, usePassword, *opts.UserName, info.password, *opts.ForceReinstall, verbose) + if err != nil { + return nil, nil, err + } + + instructions := []clusterOp{ + &httpsGetUpNodesOp, + &installOp, + } + + return instructions, &installOp.status, nil +} diff --git a/vclusterops/nma_download_file_op.go b/vclusterops/nma_download_file_op.go index b177b2e..696ce9a 100644 --- a/vclusterops/nma_download_file_op.go +++ b/vclusterops/nma_download_file_op.go @@ -330,7 +330,7 @@ func (op *nmaDownloadFileOp) buildVDBFromClusterConfig(descFileContent fileConte func (op *nmaDownloadFileOp) clusterLeaseCheck(clusterLeaseExpiration string) error { if op.ignoreClusterLease { - op.logger.PrintWarning("Skipping cluster lease check\n") + op.logger.PrintWarning("Skipping cluster lease check") return nil } diff --git a/vclusterops/nma_stage_error_report_op.go b/vclusterops/nma_stage_files_op.go similarity index 59% rename from vclusterops/nma_stage_error_report_op.go rename to vclusterops/nma_stage_files_op.go index 3bc3181..7c9a878 100644 --- a/vclusterops/nma_stage_error_report_op.go +++ b/vclusterops/nma_stage_files_op.go @@ -1,5 +1,5 @@ /* - (c) Copyright [2023] Open Text. + (c) Copyright [2024] Open Text. 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 @@ -22,51 +22,58 @@ import ( "github.com/vertica/vcluster/vclusterops/vlog" ) -type nmaStageErrorReportOp struct { +type nmaStageFilesOp struct { scrutinizeOpBase + logSizeLimitBytes int64 // maximum file size in bytes for any individual file } -type stageErrorReportRequestData struct { - CatalogPath string `json:"catalog_path"` +type stageFilesRequestData struct { + CatalogPath string `json:"catalog_path"` + LogSizeLimitBytes int64 `json:"log_size_limit_bytes"` } -type stageErrorReportResponseData struct { +type stageFilesResponseData struct { Name string `json:"name"` SizeBytes int64 `json:"size_bytes"` ModTime string `json:"mod_time"` } -func makeNMAStageErrorReportOp(logger vlog.Printer, - id string, +func makeNMAStageFilesOp(logger vlog.Printer, + id, batch string, hosts []string, hostNodeNameMap map[string]string, - hostCatPathMap map[string]string) (nmaStageErrorReportOp, error) { + hostCatPathMap map[string]string, + logSizeLimitBytes int64) (nmaStageFilesOp, error) { // base members - op := nmaStageErrorReportOp{} - op.name = "NMAStageErrorReportOp" + op := nmaStageFilesOp{} + op.name = "NMAStageFilesOp" op.logger = logger.WithName(op.name) op.hosts = hosts // scrutinize members op.id = id - op.batch = scrutinizeBatchContext + op.batch = batch op.hostNodeNameMap = hostNodeNameMap op.hostCatPathMap = hostCatPathMap op.httpMethod = PostMethod - op.urlSuffix = "/ErrorReport.txt" + op.urlSuffix = "/files" + + // custom members + op.logSizeLimitBytes = logSizeLimitBytes // the caller is responsible for making sure hosts and maps match up exactly err := validateHostMaps(hosts, hostNodeNameMap, hostCatPathMap) return op, err } -func (op *nmaStageErrorReportOp) setupRequestBody(hosts []string) error { +func (op *nmaStageFilesOp) setupRequestBody(hosts []string) error { op.hostRequestBodyMap = make(map[string]string, len(hosts)) for _, host := range hosts { - stageErrorReportData := stageErrorReportRequestData{} - stageErrorReportData.CatalogPath = op.hostCatPathMap[host] + stageFilesData := stageFilesRequestData{} + stageFilesData.CatalogPath = op.hostCatPathMap[host] + stageFilesData.LogSizeLimitBytes = op.logSizeLimitBytes - dataBytes, err := json.Marshal(stageErrorReportData) + dataBytes, err := json.Marshal(stageFilesData) if err != nil { return fmt.Errorf("[%s] fail to marshal request data to JSON string, detail %w", op.name, err) } @@ -77,7 +84,7 @@ func (op *nmaStageErrorReportOp) setupRequestBody(hosts []string) error { return nil } -func (op *nmaStageErrorReportOp) prepare(execContext *opEngineExecContext) error { +func (op *nmaStageFilesOp) prepare(execContext *opEngineExecContext) error { err := op.setupRequestBody(op.hosts) if err != nil { return err @@ -87,7 +94,7 @@ func (op *nmaStageErrorReportOp) prepare(execContext *opEngineExecContext) error return op.setupClusterHTTPRequest(op.hosts) } -func (op *nmaStageErrorReportOp) execute(execContext *opEngineExecContext) error { +func (op *nmaStageFilesOp) execute(execContext *opEngineExecContext) error { if err := op.runExecute(execContext); err != nil { return err } @@ -95,11 +102,11 @@ func (op *nmaStageErrorReportOp) execute(execContext *opEngineExecContext) error return op.processResult(execContext) } -func (op *nmaStageErrorReportOp) finalize(_ *opEngineExecContext) error { +func (op *nmaStageFilesOp) finalize(_ *opEngineExecContext) error { return nil } -func (op *nmaStageErrorReportOp) processResult(_ *opEngineExecContext) error { - fileList := make([]stageErrorReportResponseData, 0) +func (op *nmaStageFilesOp) processResult(_ *opEngineExecContext) error { + fileList := make([]stageFilesResponseData, 0) return processStagedItemsResult(&op.scrutinizeOpBase, fileList) } diff --git a/vclusterops/revive_db.go b/vclusterops/revive_db.go index ff3827d..137645e 100644 --- a/vclusterops/revive_db.go +++ b/vclusterops/revive_db.go @@ -195,7 +195,7 @@ func (options *VReviveDatabaseOptions) validateAnalyzeOptions() error { // VReviveDatabase revives a database that was terminated but whose communal storage data still exists. // It returns the database information retrieved from communal storage and any error encountered. -func (vcc *VClusterCommands) VReviveDatabase(options *VReviveDatabaseOptions) (dbInfo string, err error) { +func (vcc *VClusterCommands) VReviveDatabase(options *VReviveDatabaseOptions) (dbInfo string, vdbPtr *VCoordinationDatabase, err error) { /* * - Validate options * - Run VClusterOpEngine to get terminated database info @@ -205,7 +205,7 @@ func (vcc *VClusterCommands) VReviveDatabase(options *VReviveDatabaseOptions) (d // validate and analyze options err = options.validateAnalyzeOptions() if err != nil { - return dbInfo, err + return dbInfo, nil, err } vdb := makeVCoordinationDatabase() @@ -213,7 +213,7 @@ func (vcc *VClusterCommands) VReviveDatabase(options *VReviveDatabaseOptions) (d // part 1: produce instructions for getting terminated database info, and save the info to vdb preReviveDBInstructions, err := vcc.producePreReviveDBInstructions(options, &vdb) if err != nil { - return dbInfo, fmt.Errorf("fail to produce pre-revive database instructions %w", err) + return dbInfo, nil, fmt.Errorf("fail to produce pre-revive database instructions %w", err) } // generate clusterOpEngine certs @@ -222,46 +222,53 @@ func (vcc *VClusterCommands) VReviveDatabase(options *VReviveDatabaseOptions) (d clusterOpEngine := makeClusterOpEngine(preReviveDBInstructions, &certs) err = clusterOpEngine.run(vcc.Log) if err != nil { - return dbInfo, fmt.Errorf("fail to collect the information of database in revive_db %w", err) + return dbInfo, nil, fmt.Errorf("fail to collect the information of database in revive_db %w", err) } if options.isRestoreEnabled() { validatedRestorePointID, findErr := options.findSpecifiedRestorePoint(clusterOpEngine.execContext.restorePoints) if findErr != nil { - return dbInfo, fmt.Errorf("fail to find a restore point as specified %w", findErr) + return dbInfo, &vdb, fmt.Errorf("fail to find a restore point as specified %w", findErr) } restoreDBSpecificInstructions, produceErr := vcc.produceRestoreDBSpecificInstructions(options, &vdb, validatedRestorePointID) if produceErr != nil { - return dbInfo, fmt.Errorf("fail to produce restore-specific instructions %w", produceErr) + return dbInfo, &vdb, fmt.Errorf("fail to produce restore-specific instructions %w", produceErr) } // feed the restore db specific instructions to the VClusterOpEngine clusterOpEngine = makeClusterOpEngine(restoreDBSpecificInstructions, &certs) runErr := clusterOpEngine.run(vcc.Log) if runErr != nil { - return dbInfo, fmt.Errorf("fail to collect the restore-specific information of database in revive_db %w", runErr) + return dbInfo, &vdb, fmt.Errorf("fail to collect the restore-specific information of database in revive_db %w", runErr) } } if *options.DisplayOnly { dbInfo = clusterOpEngine.execContext.dbInfo - return dbInfo, nil + return dbInfo, &vdb, nil } // part 2: produce instructions for reviving database using terminated database info reviveDBInstructions, err := vcc.produceReviveDBInstructions(options, &vdb) if err != nil { - return dbInfo, fmt.Errorf("fail to produce revive database instructions %w", err) + return dbInfo, &vdb, fmt.Errorf("fail to produce revive database instructions %w", err) } // feed revive db instructions to the VClusterOpEngine clusterOpEngine = makeClusterOpEngine(reviveDBInstructions, &certs) err = clusterOpEngine.run(vcc.Log) if err != nil { - return dbInfo, fmt.Errorf("fail to revive database %w", err) + return dbInfo, &vdb, fmt.Errorf("fail to revive database %w", err) } - return dbInfo, nil + + // fill vdb with VReviveDatabaseOptions information + vdb.Name = *options.DBName + vdb.IsEon = true + vdb.CommunalStorageLocation = *options.CommunalStorageLocation + vdb.Ipv6 = options.Ipv6.ToBool() + + return dbInfo, &vdb, nil } // revive db instructions are split into two parts: diff --git a/vclusterops/scrutinize.go b/vclusterops/scrutinize.go index bee0112..7c823a9 100644 --- a/vclusterops/scrutinize.go +++ b/vclusterops/scrutinize.go @@ -35,8 +35,9 @@ const scrutinizeRemoteOutputPath = scrutinizeOutputBasePath + "/remote" const scrutinizeLogFileName = "vcluster.log" // these could be replaced with options later -const scrutinizeLogAgeHours = 24 // copy archived logs produced in recent 24 hours -const scrutinizeLogLimitBytes = 10737418240 // 10GB in bytes +const scrutinizeLogAgeHours = 24 // copy archived logs produced in recent 24 hours +const scrutinizeLogLimitBytes = 10737418240 // 10GB in bytes +const scrutinizeFileLimitBytes = 100 * 1024 * 1024 // 100 MB in bytes // batches are fixed, top level folders for each node's data const scrutinizeBatchNormal = "normal" @@ -263,7 +264,7 @@ func (options *VScrutinizeOptions) getVDBForScrutinize(logger vlog.Printer, // - Get up nodes through https call // - Initiate system table staging on the first up node, if available // - Stage vertica logs on all nodes -// - Stage ErrorReport.txt on all nodes +// - Stage files on all nodes // - Stage DC tables on all nodes // - Tar and retrieve vertica logs and DC tables from all nodes (batch normal) // - Tar and retrieve error report from all nodes (batch context) @@ -312,15 +313,23 @@ func (vcc *VClusterCommands) produceScrutinizeInstructions(options *VScrutinizeO } instructions = append(instructions, &stageDCTablesOp) - // stage ErrorReport.txt - stageVerticaErrorReportOp, err := makeNMAStageErrorReportOp(vcc.Log, options.ID, options.Hosts, - hostNodeNameMap, hostCatPathMap) + // stage 'normal' batch files -- see NMA for what files are collected + stageVerticaNormalFilesOp, err := makeNMAStageFilesOp(vcc.Log, options.ID, scrutinizeBatchNormal, + options.Hosts, hostNodeNameMap, hostCatPathMap, scrutinizeFileLimitBytes) + if err != nil { + return nil, err + } + instructions = append(instructions, &stageVerticaNormalFilesOp) + + // stage 'context' batch files -- see NMA for what files are collected + stageVerticaContextFilesOp, err := makeNMAStageFilesOp(vcc.Log, options.ID, scrutinizeBatchContext, + options.Hosts, hostNodeNameMap, hostCatPathMap, scrutinizeFileLimitBytes) if err != nil { return nil, err } - instructions = append(instructions, &stageVerticaErrorReportOp) + instructions = append(instructions, &stageVerticaContextFilesOp) - // get 'normal' batch tarball (inc. Vertica logs) + // get 'normal' batch tarball (inc. Vertica logs and 'normal' batch files) getNormalTarballOp, err := makeNMAGetScrutinizeTarOp(vcc.Log, options.ID, scrutinizeBatchNormal, options.Hosts, hostNodeNameMap) if err != nil { @@ -328,7 +337,7 @@ func (vcc *VClusterCommands) produceScrutinizeInstructions(options *VScrutinizeO } instructions = append(instructions, &getNormalTarballOp) - // get 'context' batch tarball (inc. ErrorReport.txt) + // get 'context' batch tarball (inc. 'context' batch files) getContextTarballOp, err := makeNMAGetScrutinizeTarOp(vcc.Log, options.ID, scrutinizeBatchContext, options.Hosts, hostNodeNameMap) if err != nil { diff --git a/vclusterops/util/util.go b/vclusterops/util/util.go index 0e768ac..bb7aec2 100644 --- a/vclusterops/util/util.go +++ b/vclusterops/util/util.go @@ -593,3 +593,8 @@ func Max[T constraints.Ordered](a, b T) T { } return b } + +// GetPathPrefix returns a path prefix for a (catalog/data/depot) path of a node +func GetPathPrefix(path string) string { + return filepath.Dir(filepath.Dir(path)) +}