From fa4852c7d9b59e0babab7e0e85f7d5ee8360fa4f Mon Sep 17 00:00:00 2001 From: Matt Spilchen Date: Fri, 13 Oct 2023 12:31:16 -0400 Subject: [PATCH] Sync from server repo (acba5f67332) --- commands/cmd_add_node.go | 2 +- commands/cmd_create_db.go | 20 +- commands/cmd_init.go | 17 +- commands/cmd_remove_node.go | 2 +- commands/cmd_remove_subcluster.go | 2 +- commands/cmd_revive_db.go | 16 +- commands/cmd_start_db.go | 24 +-- vclusterops/add_node.go | 11 +- vclusterops/add_subcluster.go | 12 +- vclusterops/cluster_config.go | 28 ++- vclusterops/cluster_op.go | 1 - vclusterops/coordinator_database.go | 89 ++++++--- vclusterops/create_db.go | 32 +++- vclusterops/create_db_test.go | 7 +- vclusterops/drop_db.go | 21 ++- vclusterops/helpers.go | 50 +---- vclusterops/nma_download_file_op.go | 8 +- vclusterops/nma_load_remote_catalog_op.go | 18 +- vclusterops/nma_spread_security_op.go | 203 +++++++++++++++++++++ vclusterops/nma_spread_security_op_test.go | 52 ++++++ vclusterops/remove_node.go | 11 +- vclusterops/restart_node.go | 6 +- vclusterops/revive_db.go | 4 +- vclusterops/start_db.go | 30 ++- vclusterops/stop_db.go | 18 +- vclusterops/test_data/vertica_cluster.yaml | 33 ++-- vclusterops/vcluster_database_options.go | 98 +++++++--- vclusterops/vlog/vLog.go | 3 +- 28 files changed, 609 insertions(+), 209 deletions(-) create mode 100644 vclusterops/nma_spread_security_op.go create mode 100644 vclusterops/nma_spread_security_op_test.go diff --git a/commands/cmd_add_node.go b/commands/cmd_add_node.go index f6d6f97..0505b92 100644 --- a/commands/cmd_add_node.go +++ b/commands/cmd_add_node.go @@ -174,7 +174,7 @@ func (c *CmdAddNode) Run(log vlog.Printer) error { return addNodeError } // write cluster information to the YAML config file - err = vclusterops.WriteClusterConfig(&vdb, options.ConfigDirectory) + err = vdb.WriteClusterConfig(options.ConfigDirectory) if err != nil { vlog.LogPrintWarning("fail to write config file, details: %s", err) } diff --git a/commands/cmd_create_db.go b/commands/cmd_create_db.go index c6ea527..e64cd02 100644 --- a/commands/cmd_create_db.go +++ b/commands/cmd_create_db.go @@ -33,9 +33,8 @@ import ( */ type CmdCreateDB struct { - createDBOptions *vclusterops.VCreateDatabaseOptions - configParamListStr *string // raw input from user, need further processing - communalStorageParams *string // raw input from user, need further processing + createDBOptions *vclusterops.VCreateDatabaseOptions + configParamListStr *string // raw input from user, need further processing CmdBase } @@ -69,8 +68,6 @@ func makeCmdCreateDB() *CmdCreateDB { createDBOptions.CommunalStorageLocation = newCmd.parser.String("communal-storage-location", "", util.GetEonFlagMsg("Location of communal storage")) createDBOptions.ShardCount = newCmd.parser.Int("shard-count", 0, util.GetEonFlagMsg("Number of shards in the database")) - newCmd.communalStorageParams = newCmd.parser.String("communal-storage-params", "", util.GetOptionalFlagMsg( - "Comma-separated list of NAME=VALUE pairs for communal storage parameters")) createDBOptions.DepotPrefix = newCmd.parser.String("depot-path", "", util.GetEonFlagMsg("Path to depot directory")) createDBOptions.DepotSize = newCmd.parser.String("depot-size", "", util.GetEonFlagMsg("Size of depot")) createDBOptions.GetAwsCredentialsFromEnv = newCmd.parser.Bool("get-aws-credentials-from-env-vars", false, @@ -149,17 +146,8 @@ func (c *CmdCreateDB) Parse(inputArgv []string) error { func (c *CmdCreateDB) validateParse() error { vlog.LogInfoln("Called validateParse()") - // check the format of communal storage params string, and parse it into configParams - communalStorageParams, err := util.ParseConfigParams(*c.communalStorageParams) - if err != nil { - return err - } - if communalStorageParams != nil { - c.createDBOptions.CommunalStorageParameters = communalStorageParams - } - // parse raw host str input into a []string of createDBOptions - err = c.createDBOptions.ParseHostList(*c.hostListStr) + err := c.createDBOptions.ParseHostList(*c.hostListStr) if err != nil { return err } @@ -193,7 +181,7 @@ func (c *CmdCreateDB) Run(log vlog.Printer) error { return createError } // write cluster information to the YAML config file - err := vclusterops.WriteClusterConfig(&vdb, c.createDBOptions.ConfigDirectory) + err := vdb.WriteClusterConfig(c.createDBOptions.ConfigDirectory) if err != nil { vlog.LogPrintWarning("fail to write config file, details: %s", err) } diff --git a/commands/cmd_init.go b/commands/cmd_init.go index 2bd81b5..79412bb 100644 --- a/commands/cmd_init.go +++ b/commands/cmd_init.go @@ -35,7 +35,8 @@ import ( * Implements ClusterCommand interface */ type CmdInit struct { - Hosts *string + DBName *string + Hosts *string ConfigHandler } @@ -48,6 +49,7 @@ func makeCmdInit() *CmdInit { "The directory under which the config file will be created. "+ "By default the current directory will be used.", ) + newCmd.DBName = newCmd.parser.String("db-name", "", "Database name") newCmd.Hosts = newCmd.parser.String("hosts", "", "Comma-separated list of hosts to participate in database") return newCmd } @@ -106,12 +108,21 @@ func (c *CmdInit) Run(_ vlog.Printer) error { // TODO: this will be improved later with more cluster info // build cluster config information - clusterConfig := vclusterops.MakeClusterConfig() + dbConfig := vclusterops.MakeDatabaseConfig() + hosts, err := util.SplitHosts(*c.Hosts) if err != nil { return err } - clusterConfig.Hosts = hosts + + for _, h := range hosts { + nodeConfig := vclusterops.NodeConfig{} + nodeConfig.Address = h + dbConfig.Nodes = append(dbConfig.Nodes, nodeConfig) + } + + clusterConfig := vclusterops.MakeClusterConfig() + clusterConfig[*c.DBName] = dbConfig // write information to the YAML file err = clusterConfig.WriteConfig(configFilePath) diff --git a/commands/cmd_remove_node.go b/commands/cmd_remove_node.go index 8570375..820ac4b 100644 --- a/commands/cmd_remove_node.go +++ b/commands/cmd_remove_node.go @@ -128,7 +128,7 @@ func (c *CmdRemoveNode) Run(log vlog.Printer) error { vlog.LogPrintInfo("Successfully removed nodes %s from database %s", *c.hostToRemoveListStr, *options.DBName) // write cluster information to the YAML config file. - err = vclusterops.WriteClusterConfig(&vdb, options.ConfigDirectory) + err = vdb.WriteClusterConfig(options.ConfigDirectory) if err != nil { vlog.LogPrintWarning("failed to write config file, details: %s", err) } diff --git a/commands/cmd_remove_subcluster.go b/commands/cmd_remove_subcluster.go index 3f139fb..eb4d3ef 100644 --- a/commands/cmd_remove_subcluster.go +++ b/commands/cmd_remove_subcluster.go @@ -111,7 +111,7 @@ func (c *CmdRemoveSubcluster) Run(log vlog.Printer) error { *c.removeScOptions.SubclusterToRemove, *c.removeScOptions.DBName) // write cluster information to the YAML config file. - err = vclusterops.WriteClusterConfig(&vdb, c.removeScOptions.ConfigDirectory) + err = vdb.WriteClusterConfig(c.removeScOptions.ConfigDirectory) if err != nil { vcc.Log.PrintWarning("failed to write config file, details: %s", err) } diff --git a/commands/cmd_revive_db.go b/commands/cmd_revive_db.go index 431a98c..c78baeb 100644 --- a/commands/cmd_revive_db.go +++ b/commands/cmd_revive_db.go @@ -15,8 +15,8 @@ import ( */ type CmdReviveDB struct { CmdBase - reviveDBOptions *vclusterops.VReviveDatabaseOptions - communalStorageParams *string // raw input from user, need further processing + reviveDBOptions *vclusterops.VReviveDatabaseOptions + configurationParams *string // raw input from user, need further processing } func makeCmdReviveDB() *CmdReviveDB { @@ -35,8 +35,8 @@ func makeCmdReviveDB() *CmdReviveDB { // optional flags newCmd.ipv6 = newCmd.parser.Bool("ipv6", false, util.GetOptionalFlagMsg("Revive database with IPv6 hosts")) - newCmd.communalStorageParams = newCmd.parser.String("communal-storage-params", "", util.GetOptionalFlagMsg( - "Comma-separated list of NAME=VALUE pairs for communal storage parameters")) + newCmd.configurationParams = newCmd.parser.String("config-param", "", util.GetOptionalFlagMsg( + "Comma-separated list of NAME=VALUE pairs for configuration parameters")) reviveDBOptions.ForceRemoval = newCmd.parser.Bool("force-removal", false, util.GetOptionalFlagMsg("Force removal of existing database directories(exclude user storage directories) before reviving the database")) reviveDBOptions.LoadCatalogTimeout = newCmd.parser.Uint("load-catalog-timeout", util.DefaultLoadCatalogTimeoutSeconds, @@ -80,13 +80,13 @@ func (c *CmdReviveDB) Parse(inputArgv []string) error { func (c *CmdReviveDB) validateParse() error { vlog.LogInfo("[%s] Called validateParse()", c.CommandType()) - // check the format of communal storage params string, and parse it into configParams - communalStorageParams, err := util.ParseConfigParams(*c.communalStorageParams) + // check the format of configuration params string, and parse it into configParams + configurationParams, err := util.ParseConfigParams(*c.configurationParams) if err != nil { return err } - if communalStorageParams != nil { - c.reviveDBOptions.CommunalStorageParameters = communalStorageParams + if configurationParams != nil { + c.reviveDBOptions.ConfigurationParameters = configurationParams } // when --display-only is provided, we do not need to parse some base options like hostListStr diff --git a/commands/cmd_start_db.go b/commands/cmd_start_db.go index 1842ab5..421b5fe 100644 --- a/commands/cmd_start_db.go +++ b/commands/cmd_start_db.go @@ -18,12 +18,12 @@ type CmdStartDB struct { CmdBase startDBOptions *vclusterops.VStartDatabaseOptions - Force *bool // force cleanup to start the database - AllowFallbackKeygen *bool // Generate spread encryption key from Vertica. Use under support guidance only - IgnoreClusterLease *bool // ignore the cluster lease in communal storage - Unsafe *bool // Start database unsafely, skipping recovery. - Fast *bool // Attempt fast startup database - communalStorageParams *string // raw input from user, need further processing + Force *bool // force cleanup to start the database + AllowFallbackKeygen *bool // Generate spread encryption key from Vertica. Use under support guidance only + IgnoreClusterLease *bool // ignore the cluster lease in communal storage + Unsafe *bool // Start database unsafely, skipping recovery. + Fast *bool // Attempt fast startup database + configurationParams *string // raw input from user, need further processing } func makeCmdStartDB() *CmdStartDB { @@ -57,8 +57,8 @@ func makeCmdStartDB() *CmdStartDB { " Use it when you do not trust "+vclusterops.ConfigFileName)) startDBOptions.CommunalStorageLocation = newCmd.parser.String("communal-storage-location", "", util.GetEonFlagMsg("Location of communal storage")) - newCmd.communalStorageParams = newCmd.parser.String("communal-storage-params", "", util.GetOptionalFlagMsg( - "Comma-separated list of NAME=VALUE pairs for communal storage parameters")) + newCmd.configurationParams = newCmd.parser.String("config-param", "", util.GetOptionalFlagMsg( + "Comma-separated list of NAME=VALUE pairs for configuration parameters")) // hidden options // TODO: the following options will be processed later @@ -111,13 +111,13 @@ func (c *CmdStartDB) Parse(inputArgv []string) error { func (c *CmdStartDB) validateParse() error { vlog.LogInfo("[%s] Called validateParse()", c.CommandType()) - // check the format of communal storage params string, and parse it into configParams - communalStorageParams, err := util.ParseConfigParams(*c.communalStorageParams) + // check the format of configuration params string, and parse it into configParams + configurationParams, err := util.ParseConfigParams(*c.configurationParams) if err != nil { return err } - if communalStorageParams != nil { - c.startDBOptions.CommunalStorageParameters = communalStorageParams + if configurationParams != nil { + c.startDBOptions.ConfigurationParameters = configurationParams } return c.ValidateParseBaseOptions(&c.startDBOptions.DatabaseOptions) diff --git a/vclusterops/add_node.go b/vclusterops/add_node.go index 4d8402e..215d4fc 100644 --- a/vclusterops/add_node.go +++ b/vclusterops/add_node.go @@ -129,11 +129,18 @@ func (vcc *VClusterCommands) VAddNode(options *VAddNodeOptions) (VCoordinationDa } // get hosts from config file and options. - hosts := options.GetHosts(options.Config) + hosts, err := options.GetHosts(options.Config) + if err != nil { + return vdb, err + } + options.Hosts = hosts // get depot and data prefix from config file or options. // after VER-88122, we will able to get them from an https endpoint. - *options.DepotPrefix, *options.DataPrefix = options.getDepotAndDataPrefix(options.Config) + *options.DepotPrefix, *options.DataPrefix, err = options.getDepotAndDataPrefix(options.Config) + if err != nil { + return vdb, err + } err = getVDBFromRunningDB(&vdb, &options.DatabaseOptions) if err != nil { diff --git a/vclusterops/add_subcluster.go b/vclusterops/add_subcluster.go index 05bdde5..05e0bfc 100644 --- a/vclusterops/add_subcluster.go +++ b/vclusterops/add_subcluster.go @@ -83,7 +83,12 @@ func (options *VAddSubclusterOptions) validateRequiredOptions() error { } func (options *VAddSubclusterOptions) validateEonOptions(config *ClusterConfig) error { - if !options.IsEonMode(config) { + isEon, err := options.IsEonMode(config) + if err != nil { + return err + } + + if !isEon { return fmt.Errorf("add subcluster is only supported in Eon mode") } return nil @@ -194,7 +199,10 @@ func (vcc *VClusterCommands) VAddSubcluster(options *VAddSubclusterOptions) erro ControlSetSize: *options.ControlSetSize, CloneSC: *options.CloneSC, } - addSubclusterInfo.DBName, addSubclusterInfo.Hosts = options.GetNameAndHosts(options.Config) + addSubclusterInfo.DBName, addSubclusterInfo.Hosts, err = options.GetNameAndHosts(options.Config) + if err != nil { + return err + } instructions, err := produceAddSubclusterInstructions(&addSubclusterInfo, options) if err != nil { diff --git a/vclusterops/cluster_config.go b/vclusterops/cluster_config.go index c348977..d484ba4 100644 --- a/vclusterops/cluster_config.go +++ b/vclusterops/cluster_config.go @@ -33,9 +33,10 @@ const ( const ConfigFileName = "vertica_cluster.yaml" const ConfigBackupName = "vertica_cluster.yaml.backup" -type ClusterConfig struct { - DBName string `yaml:"db_name"` - Hosts []string `yaml:"hosts"` +// VER-89599: add config file version +type ClusterConfig map[string]DatabaseConfig + +type DatabaseConfig struct { Nodes []NodeConfig `yaml:"nodes"` CatalogPath string `yaml:"catalog_path"` DataPath string `yaml:"data_path"` @@ -45,12 +46,17 @@ type ClusterConfig struct { } type NodeConfig struct { - Name string `yaml:"name"` - Address string `yaml:"address"` + Name string `yaml:"name"` + Address string `yaml:"address"` + Subcluster string `yaml:"subcluster"` } func MakeClusterConfig() ClusterConfig { - return ClusterConfig{} + return make(ClusterConfig) +} + +func MakeDatabaseConfig() DatabaseConfig { + return DatabaseConfig{} } // read config information from the YAML file @@ -86,6 +92,16 @@ func (c *ClusterConfig) WriteConfig(configFilePath string) error { return nil } +func (c *DatabaseConfig) GetHosts() []string { + var hostList []string + + for _, vnode := range c.Nodes { + hostList = append(hostList, vnode.Address) + } + + return hostList +} + func GetConfigFilePath(dbName string, inputConfigDir *string) (string, error) { var configParentPath string diff --git a/vclusterops/cluster_op.go b/vclusterops/cluster_op.go index 129c046..057bd31 100644 --- a/vclusterops/cluster_op.go +++ b/vclusterops/cluster_op.go @@ -170,7 +170,6 @@ func (status ResultStatus) getStatusString() string { // log* implemented by embedding OpBase, but overrideable type ClusterOp interface { getName() string - setupClusterHTTPRequest(hosts []string) error prepare(execContext *OpEngineExecContext) error execute(execContext *OpEngineExecContext) error finalize(execContext *OpEngineExecContext) error diff --git a/vclusterops/coordinator_database.go b/vclusterops/coordinator_database.go index 34ecf9b..5dde814 100644 --- a/vclusterops/coordinator_database.go +++ b/vclusterops/coordinator_database.go @@ -146,26 +146,34 @@ func (vdb *VCoordinationDatabase) addHosts(hosts []string) error { return nil } -func (vdb *VCoordinationDatabase) SetFromClusterConfig(clusterConfig *ClusterConfig) { +func (vdb *VCoordinationDatabase) SetFromClusterConfig(dbName string, + clusterConfig *ClusterConfig) error { // we trust the information in the config file // so we do not perform validation here - vdb.Name = clusterConfig.DBName - vdb.CatalogPrefix = clusterConfig.CatalogPath - vdb.DataPrefix = clusterConfig.DataPath - vdb.DepotPrefix = clusterConfig.DepotPath - vdb.HostList = clusterConfig.Hosts - vdb.IsEon = clusterConfig.IsEon - vdb.Ipv6 = clusterConfig.Ipv6 + dbConfig, ok := (*clusterConfig)[dbName] + if !ok { + return cannotFindDBFromConfigErr(dbName) + } + + vdb.Name = dbName + vdb.CatalogPrefix = dbConfig.CatalogPath + vdb.DataPrefix = dbConfig.DataPath + vdb.DepotPrefix = dbConfig.DepotPath + vdb.IsEon = dbConfig.IsEon + vdb.Ipv6 = dbConfig.Ipv6 if vdb.DepotPrefix != "" { vdb.UseDepot = true } vdb.HostNodeMap = makeVHostNodeMap() - for _, nodeConfig := range clusterConfig.Nodes { + for _, nodeConfig := range dbConfig.Nodes { vnode := VCoordinationNode{} vnode.SetFromNodeConfig(nodeConfig, vdb) vdb.HostNodeMap[vnode.Address] = &vnode + vdb.HostList = append(vdb.HostList, vnode.Address) } + + return nil } // Copy copies the receiver's fields into a new VCoordinationDatabase struct and @@ -221,21 +229,6 @@ func (vdb *VCoordinationDatabase) genNodeNameToHostMap() map[string]string { return vnodes } -// SetDBInfoFromClusterConfig will set various fields with values -// from the config file -func (vdb *VCoordinationDatabase) SetDBInfoFromClusterConfig(clusterConfig *ClusterConfig) { - vdb.Name = clusterConfig.DBName - vdb.CatalogPrefix = clusterConfig.CatalogPath - vdb.DataPrefix = clusterConfig.DataPath - vdb.DepotPrefix = clusterConfig.DepotPath - vdb.HostList = clusterConfig.Hosts - vdb.IsEon = clusterConfig.IsEon - vdb.Ipv6 = clusterConfig.Ipv6 - if vdb.DepotPrefix != "" { - vdb.UseDepot = true - } -} - // getSCNames returns a slice of subcluster names which the nodes // in the current VCoordinationDatabase instance belong to. func (vdb *VCoordinationDatabase) getSCNames() []string { @@ -407,3 +400,51 @@ func (vnode *VCoordinationNode) SetFromNodeConfig(nodeConfig NodeConfig, vdb *VC vnode.ControlAddressFamily = util.DefaultControlAddressFamily } } + +// WriteClusterConfig writes config information to a yaml file. +func (vdb *VCoordinationDatabase) WriteClusterConfig(configDir *string) error { + /* build config information + */ + dbConfig := MakeDatabaseConfig() + dbConfig.CatalogPath = vdb.CatalogPrefix + dbConfig.DataPath = vdb.DataPrefix + dbConfig.DepotPath = vdb.DepotPrefix + // loop over HostList is needed as we want to preserve the order + for _, host := range vdb.HostList { + vnode, ok := vdb.HostNodeMap[host] + if !ok { + return fmt.Errorf("cannot find host %s from HostNodeMap", host) + } + nodeConfig := NodeConfig{} + nodeConfig.Name = vnode.Name + nodeConfig.Address = vnode.Address + nodeConfig.Subcluster = vnode.Subcluster + dbConfig.Nodes = append(dbConfig.Nodes, nodeConfig) + } + dbConfig.IsEon = vdb.IsEon + dbConfig.Ipv6 = vdb.Ipv6 + + clusterConfig := MakeClusterConfig() + clusterConfig[vdb.Name] = dbConfig + + /* write config to a YAML file + */ + configFilePath, err := GetConfigFilePath(vdb.Name, configDir) + if err != nil { + return err + } + + // if the config file exists already + // create its backup before overwriting it + err = BackupConfigFile(configFilePath) + if err != nil { + return err + } + + err = clusterConfig.WriteConfig(configFilePath) + if err != nil { + return err + } + + return nil +} diff --git a/vclusterops/create_db.go b/vclusterops/create_db.go index 8bde4d0..7cf7f27 100644 --- a/vclusterops/create_db.go +++ b/vclusterops/create_db.go @@ -38,7 +38,6 @@ type VCreateDatabaseOptions struct { DepotSize *string // like 10G GetAwsCredentialsFromEnv *bool // part 3: optional info - ConfigurationParameters map[string]string ForceCleanupOnFailure *bool ForceRemovalAtCreation *bool SkipPackageInstall *bool @@ -80,7 +79,6 @@ func (opt *VCreateDatabaseOptions) SetDefaultValues() { opt.GetAwsCredentialsFromEnv = new(bool) // optional info - opt.ConfigurationParameters = make(map[string]string) opt.ForceCleanupOnFailure = new(bool) opt.ForceRemovalAtCreation = new(bool) opt.SkipPackageInstall = new(bool) @@ -504,13 +502,6 @@ func (vcc *VClusterCommands) produceCreateDBBootstrapInstructions( return instructions, err } - nmaStartNodeOp := makeNMAStartNodeOp(bootstrapHost) - - httpsPollBootstrapNodeStateOp, err := makeHTTPSPollNodeStateOp(bootstrapHost, true, *options.UserName, options.Password) - if err != nil { - return instructions, err - } - instructions = append(instructions, &nmaHealthOp, &nmaVerticaVersionOp, @@ -519,6 +510,23 @@ func (vcc *VClusterCommands) produceCreateDBBootstrapInstructions( &nmaNetworkProfileOp, &nmaBootstrapCatalogOp, &nmaReadCatalogEditorOp, + ) + + if enabled, keyType := options.isSpreadEncryptionEnabled(); enabled { + instructions = append(instructions, + vcc.addEnableSpreadEncryptionOp(keyType), + ) + } + + nmaStartNodeOp := makeNMAStartNodeOp(bootstrapHost) + + httpsPollBootstrapNodeStateOp, err := makeHTTPSPollNodeStateOp(bootstrapHost, true, /* useHTTPPassword */ + *options.UserName, options.Password) + if err != nil { + return instructions, err + } + + instructions = append(instructions, &nmaStartNodeOp, &httpsPollBootstrapNodeStateOp, ) @@ -634,3 +642,9 @@ func (vcc *VClusterCommands) produceAdditionalCreateDBInstructions(vdb *VCoordin } return instructions, nil } + +func (vcc *VClusterCommands) addEnableSpreadEncryptionOp(keyType string) ClusterOp { + vcc.Log.Info("adding instruction to set key for spread encryption") + op := makeNMASpreadSecurityOp(vcc.Log, keyType) + return &op +} diff --git a/vclusterops/create_db_test.go b/vclusterops/create_db_test.go index 1299af6..e55f746 100644 --- a/vclusterops/create_db_test.go +++ b/vclusterops/create_db_test.go @@ -59,6 +59,7 @@ func TestValidateDepotSize(t *testing.T) { func TestWriteClusterConfig(t *testing.T) { const dbName = "practice_db" + const scName = "default_subcluster" // generate a YAML file based on a stub vdb vdb := VCoordinationDatabase{} @@ -71,11 +72,13 @@ func TestWriteClusterConfig(t *testing.T) { for i, h := range vdb.HostList { n := VCoordinationNode{} n.Name = fmt.Sprintf("node_name_%d", i+1) + n.Address = h + n.Subcluster = scName vdb.HostNodeMap[h] = &n } vdb.IsEon = true - err := WriteClusterConfig(&vdb, nil) + err := vdb.WriteClusterConfig(nil) assert.NoError(t, err) // compare the generated file with expected output @@ -85,7 +88,7 @@ func TestWriteClusterConfig(t *testing.T) { // now write the config file again // a backup file should be generated - err = WriteClusterConfig(&vdb, nil) + err = vdb.WriteClusterConfig(nil) assert.NoError(t, err) err = util.CanReadAccessDir(dbName + "/" + ConfigBackupName) assert.NoError(t, err) diff --git a/vclusterops/drop_db.go b/vclusterops/drop_db.go index d0a039d..9688f51 100644 --- a/vclusterops/drop_db.go +++ b/vclusterops/drop_db.go @@ -48,6 +48,13 @@ func (options *VDropDatabaseOptions) AnalyzeOptions() error { return nil } +func (options *VDropDatabaseOptions) ValidateAnalyzeOptions() error { + if *options.DBName == "" { + return fmt.Errorf("database name must be provided") + } + return nil +} + func (vcc *VClusterCommands) VDropDatabase(options *VDropDatabaseOptions) error { /* * - Produce Instructions @@ -55,6 +62,11 @@ func (vcc *VClusterCommands) VDropDatabase(options *VDropDatabaseOptions) error * - Give the instructions to the VClusterOpEngine to run */ + err := options.ValidateAnalyzeOptions() + if err != nil { + return err + } + // Analyze to produce vdb info for drop db use vdb := MakeVCoordinationDatabase() @@ -66,8 +78,8 @@ func (vcc *VClusterCommands) VDropDatabase(options *VDropDatabaseOptions) error if options.ConfigDirectory != nil { configDir = *options.ConfigDirectory } else { - currentDir, err := os.Getwd() - if err != nil { + currentDir, e := os.Getwd() + if e != nil { return fmt.Errorf("fail to get current directory") } configDir = currentDir @@ -77,7 +89,10 @@ func (vcc *VClusterCommands) VDropDatabase(options *VDropDatabaseOptions) error if err != nil { return err } - vdb.SetFromClusterConfig(&clusterConfig) + err = vdb.SetFromClusterConfig(*options.DBName, &clusterConfig) + if err != nil { + return err + } // produce drop_db instructions instructions, err := vcc.produceDropDBInstructions(&vdb, options) diff --git a/vclusterops/helpers.go b/vclusterops/helpers.go index 69272ea..fa6cdcc 100644 --- a/vclusterops/helpers.go +++ b/vclusterops/helpers.go @@ -72,52 +72,6 @@ func updateCatalogPathMapFromCatalogEditor(hosts []string, nmaVDB *NmaVDatabase, return nil } -// WriteClusterConfig writes config information to a yaml file. -func WriteClusterConfig(vdb *VCoordinationDatabase, configDir *string) error { - /* build config information - */ - clusterConfig := MakeClusterConfig() - clusterConfig.DBName = vdb.Name - clusterConfig.Hosts = vdb.HostList - clusterConfig.CatalogPath = vdb.CatalogPrefix - clusterConfig.DataPath = vdb.DataPrefix - clusterConfig.DepotPath = vdb.DepotPrefix - for _, host := range vdb.HostList { - nodeConfig := NodeConfig{} - node, ok := vdb.HostNodeMap[host] - if !ok { - errMsg := fmt.Sprintf("cannot find node info from host %s", host) - return errors.New(vlog.ErrorLog + errMsg) - } - nodeConfig.Address = host - nodeConfig.Name = node.Name - clusterConfig.Nodes = append(clusterConfig.Nodes, nodeConfig) - } - clusterConfig.IsEon = vdb.IsEon - clusterConfig.Ipv6 = vdb.Ipv6 - - /* write config to a YAML file - */ - configFilePath, err := GetConfigFilePath(vdb.Name, configDir) - if err != nil { - return err - } - - // if the config file exists already - // create its backup before overwriting it - err = BackupConfigFile(configFilePath) - if err != nil { - return err - } - - err = clusterConfig.WriteConfig(configFilePath) - if err != nil { - return err - } - - return nil -} - // The following structs will store hosts' necessary information for https_get_up_nodes_op, // https_get_nodes_information_from_running_db, and incoming operations. type NodeStateInfo struct { @@ -204,3 +158,7 @@ func getInitiator(hosts []string) string { // simply use the first one in user input return hosts[0] } + +func cannotFindDBFromConfigErr(dbName string) error { + return fmt.Errorf("database %s cannot be found in the config file", dbName) +} diff --git a/vclusterops/nma_download_file_op.go b/vclusterops/nma_download_file_op.go index 6740c64..614c324 100644 --- a/vclusterops/nma_download_file_op.go +++ b/vclusterops/nma_download_file_op.go @@ -84,7 +84,7 @@ func (e *ReviveDBNodeCountMismatchError) Error() string { } func makeNMADownloadFileOp(newNodes []string, sourceFilePath, destinationFilePath, catalogPath string, - communalStorageParameters map[string]string, vdb *VCoordinationDatabase) (NMADownloadFileOp, error) { + configurationParameters map[string]string, vdb *VCoordinationDatabase) (NMADownloadFileOp, error) { op := NMADownloadFileOp{} op.name = "NMADownloadFileOp" initiator := getInitiator(newNodes) @@ -99,7 +99,7 @@ func makeNMADownloadFileOp(newNodes []string, sourceFilePath, destinationFilePat requestData.SourceFilePath = sourceFilePath requestData.DestinationFilePath = destinationFilePath requestData.CatalogPath = catalogPath - requestData.Parameters = communalStorageParameters + requestData.Parameters = configurationParameters dataBytes, err := json.Marshal(requestData) if err != nil { @@ -113,9 +113,9 @@ func makeNMADownloadFileOp(newNodes []string, sourceFilePath, destinationFilePat } func makeNMADownloadFileOpForRevive(newNodes []string, sourceFilePath, destinationFilePath, catalogPath string, - communalStorageParameters map[string]string, vdb *VCoordinationDatabase, displayOnly, ignoreClusterLease bool) (NMADownloadFileOp, error) { + configurationParameters map[string]string, vdb *VCoordinationDatabase, displayOnly, ignoreClusterLease bool) (NMADownloadFileOp, error) { op, err := makeNMADownloadFileOp(newNodes, sourceFilePath, destinationFilePath, - catalogPath, communalStorageParameters, vdb) + catalogPath, configurationParameters, vdb) if err != nil { return op, err } diff --git a/vclusterops/nma_load_remote_catalog_op.go b/vclusterops/nma_load_remote_catalog_op.go index b11813a..ea24499 100644 --- a/vclusterops/nma_load_remote_catalog_op.go +++ b/vclusterops/nma_load_remote_catalog_op.go @@ -25,12 +25,12 @@ import ( type nmaLoadRemoteCatalogOp struct { OpBase - hostRequestBodyMap map[string]string - communalStorageParameters map[string]string - oldHosts []string - vdb *VCoordinationDatabase - timeout uint - primaryNodeCount uint + hostRequestBodyMap map[string]string + configurationParameters map[string]string + oldHosts []string + vdb *VCoordinationDatabase + timeout uint + primaryNodeCount uint } type loadRemoteCatalogRequestData struct { @@ -46,13 +46,13 @@ type loadRemoteCatalogRequestData struct { Parameters map[string]string `json:"parameters,omitempty"` } -func makeNMALoadRemoteCatalogOp(oldHosts []string, communalStorageParameters map[string]string, +func makeNMALoadRemoteCatalogOp(oldHosts []string, configurationParameters map[string]string, vdb *VCoordinationDatabase, timeout uint) nmaLoadRemoteCatalogOp { op := nmaLoadRemoteCatalogOp{} op.name = "NMALoadRemoteCatalogOp" op.hosts = vdb.HostList op.oldHosts = oldHosts - op.communalStorageParameters = communalStorageParameters + op.configurationParameters = configurationParameters op.vdb = vdb op.timeout = timeout @@ -96,7 +96,7 @@ func (op *nmaLoadRemoteCatalogOp) setupRequestBody(execContext *OpEngineExecCont requestData.CatalogPath = vNode.CatalogPath requestData.StorageLocations = vNode.StorageLocations requestData.NodeAddresses = nodeAddresses - requestData.Parameters = op.communalStorageParameters + requestData.Parameters = op.configurationParameters dataBytes, err := json.Marshal(requestData) if err != nil { diff --git a/vclusterops/nma_spread_security_op.go b/vclusterops/nma_spread_security_op.go new file mode 100644 index 0000000..7e96da4 --- /dev/null +++ b/vclusterops/nma_spread_security_op.go @@ -0,0 +1,203 @@ +/* + (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 ( + crand "crypto/rand" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + + "github.com/vertica/vcluster/vclusterops/vlog" +) + +type nmaSpreadSecurityOp struct { + OpBase + catalogPathMap map[string]string + keyType string +} + +type nmaSpreadSecurityPayload struct { + CatalogPath string `json:"catalog_path"` + SpreadSecurityDetails string `json:"spread_security_details"` +} + +const spreadKeyTypeVertica = "vertica" + +// makeNMASpreadSecurityOp will create the op to set or rotate the key for +// spread encryption. +func makeNMASpreadSecurityOp( + log vlog.Printer, + keyType string, +) nmaSpreadSecurityOp { + return nmaSpreadSecurityOp{ + OpBase: OpBase{ + log: log, + name: "NMASpreadSecurityOp", + hosts: nil, // We always set this at runtime from read catalog editor + }, + catalogPathMap: nil, // Set at runtime after reading the catalog editor + keyType: keyType, + } +} + +func (op *nmaSpreadSecurityOp) setupRequestBody() (map[string]string, error) { + if len(op.hosts) == 0 { + return nil, fmt.Errorf("[%s] no hosts specified", op.name) + } + + // Get the spread encryption key. Never write the contents of securityDetails + // to a log or error message. Otherwise, we risk leaking the key. + securityDetails, err := op.generateSecurityDetails() + if err != nil { + return nil, err + } + + hostRequestBodyMap := make(map[string]string, len(op.hosts)) + for _, host := range op.hosts { + fullCatalogPath, ok := op.catalogPathMap[host] + if !ok { + return nil, fmt.Errorf("could not find host %s in catalogPathMap %v", host, op.catalogPathMap) + } + payload := nmaSpreadSecurityPayload{ + CatalogPath: getCatalogPath(fullCatalogPath), + SpreadSecurityDetails: securityDetails, + } + + dataBytes, err := json.Marshal(payload) + if err != nil { + return nil, fmt.Errorf("[%s] fail to marshal payload data into JSON string, detail %w", op.name, err) + } + + hostRequestBodyMap[host] = string(dataBytes) + } + return hostRequestBodyMap, nil +} + +func (op *nmaSpreadSecurityOp) setupClusterHTTPRequest(hostRequestBodyMap map[string]string) error { + op.clusterHTTPRequest = ClusterHTTPRequest{} + op.clusterHTTPRequest.RequestCollection = make(map[string]HostHTTPRequest, len(hostRequestBodyMap)) + op.setVersionToSemVar() + + for host, requestBody := range hostRequestBodyMap { + httpRequest := HostHTTPRequest{} + httpRequest.Method = PostMethod + httpRequest.BuildNMAEndpoint("catalog/spread-security") + httpRequest.RequestData = requestBody + op.clusterHTTPRequest.RequestCollection[host] = httpRequest + } + + return nil +} + +func (op *nmaSpreadSecurityOp) prepare(execContext *OpEngineExecContext) error { + if err := op.setRuntimeParms(execContext); err != nil { + return err + } + hostRequestBodyMap, err := op.setupRequestBody() + if err != nil { + return err + } + execContext.dispatcher.Setup(op.hosts) + + return op.setupClusterHTTPRequest(hostRequestBodyMap) +} + +func (op *nmaSpreadSecurityOp) execute(execContext *OpEngineExecContext) error { + if err := op.runExecute(execContext); err != nil { + return err + } + + return op.processResult(execContext) +} + +func (op *nmaSpreadSecurityOp) finalize(_ *OpEngineExecContext) error { + return nil +} + +func (op *nmaSpreadSecurityOp) processResult(_ *OpEngineExecContext) error { + var allErrs error + for host, result := range op.clusterHTTPRequest.ResultCollection { + op.logResponse(host, result) + // For a passing result, the response that comes back isn't JSON. So, + // don't parse and validate it. Just check the status code. The non-JSON + // response we get is: 'Written to spread.conf'. VER-89658 is opened + // to change the endpoint to return JSON. + if !result.isPassing() { + allErrs = errors.Join(allErrs, result.err) + } + } + return allErrs +} + +// setRuntimeParms will set options based on runtime context. +func (op *nmaSpreadSecurityOp) setRuntimeParms(execContext *OpEngineExecContext) error { + // Always pull the hosts at runtime using the node with the latest catalog. + // Need to use the ones with the latest catalog because those are the hosts + // that we copy the spread.conf from during start db. + op.hosts = execContext.hostsWithLatestCatalog + + op.catalogPathMap = make(map[string]string, len(op.hosts)) + err := updateCatalogPathMapFromCatalogEditor(op.hosts, &execContext.nmaVDatabase, op.catalogPathMap) + if err != nil { + return fmt.Errorf("failed to get catalog paths from catalog editor: %w", err) + } + return nil +} + +func (op *nmaSpreadSecurityOp) generateSecurityDetails() (string, error) { + keyID, err := op.generateKeyID() + if err != nil { + return "", err + } + + var spreadKey string + switch op.keyType { + case spreadKeyTypeVertica: + spreadKey, err = op.generateVerticaSpreadKey() + if err != nil { + return "", err + } + default: + // Note, there is another key type that we support in the server + // (aws-kms). But we haven't yet added support for that here. + // VER-89659 is opened to address that. + return "", fmt.Errorf("unsupported spread key type %s", op.keyType) + } + // Note, we log the key ID for info purposes and is safe because it isn't + // sensitive. NEVER log the spreadKey. + op.log.Info("generating spread key", "keyID", keyID) + return fmt.Sprintf(`{\"%s\":\"%s\"}`, keyID, spreadKey), nil +} + +func (op *nmaSpreadSecurityOp) generateVerticaSpreadKey() (string, error) { + const spreadKeySize = 32 + bytes := make([]byte, spreadKeySize) + if _, err := crand.Read(bytes); err != nil { + return "", fmt.Errorf("failed to generate random bytes for spread: %w", err) + } + return hex.EncodeToString(bytes), nil +} + +func (op *nmaSpreadSecurityOp) generateKeyID() (string, error) { + const keyLength = 2 + bytes := make([]byte, keyLength) + if _, err := crand.Read(bytes); err != nil { + return "", fmt.Errorf("failed to generate random bytes for key ID: %w", err) + } + return hex.EncodeToString(bytes), nil +} diff --git a/vclusterops/nma_spread_security_op_test.go b/vclusterops/nma_spread_security_op_test.go new file mode 100644 index 0000000..1dd8f08 --- /dev/null +++ b/vclusterops/nma_spread_security_op_test.go @@ -0,0 +1,52 @@ +/* + (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 ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vertica/vcluster/vclusterops/vlog" +) + +func TestSpreadOpRequestBody(t *testing.T) { + op := makeNMASpreadSecurityOp(vlog.Printer{}, spreadKeyTypeVertica) + const hostname = "host1" + op.hosts = []string{hostname} + op.catalogPathMap = map[string]string{ + hostname: "/catalog/v_db_node0001_catalog/Catalog", + } + requestBody, err := op.setupRequestBody() + assert.NoError(t, err) + assert.Len(t, requestBody, 1) + assert.Contains(t, requestBody, hostname) + hostReq := requestBody[hostname] + assert.Contains(t, hostReq, `"/catalog/v_db_node0001_catalog"`) + assert.Regexp(t, regexp.MustCompile(`"spread_security_details":.*\{.*\w+.*:.*\w+.*\}`), hostReq) +} + +func TestSpreadKeyGeneration(t *testing.T) { + op := makeNMASpreadSecurityOp(vlog.Printer{}, spreadKeyTypeVertica) + keyID, err := op.generateKeyID() + assert.NoError(t, err) + const expectedKeyIDSize = 4 + assert.Equal(t, expectedKeyIDSize, len(keyID), "keyID '%s' is not %d in length", keyID, expectedKeyIDSize) + spreadKey, err := op.generateVerticaSpreadKey() + assert.NoError(t, err) + const expectedSpreadKeySize = 32 * 2 // 32-bytes at 2 chars each + assert.Equal(t, expectedSpreadKeySize, len(spreadKey), "spreadKey '%s' is not %d in length", spreadKey, expectedSpreadKeySize) +} diff --git a/vclusterops/remove_node.go b/vclusterops/remove_node.go index 56e2f49..cd2671b 100644 --- a/vclusterops/remove_node.go +++ b/vclusterops/remove_node.go @@ -129,11 +129,18 @@ func (vcc *VClusterCommands) VRemoveNode(options *VRemoveNodeOptions) (VCoordina } // get db name and hosts from config file and options. - dbName, hosts := options.GetNameAndHosts(options.Config) + dbName, hosts, err := options.GetNameAndHosts(options.Config) + if err != nil { + return vdb, err + } + options.DBName = &dbName options.Hosts = hosts // get depot and data prefix from config file or options - *options.DepotPrefix, *options.DataPrefix = options.getDepotAndDataPrefix(options.Config) + *options.DepotPrefix, *options.DataPrefix, err = options.getDepotAndDataPrefix(options.Config) + if err != nil { + return vdb, err + } err = getVDBFromRunningDB(&vdb, &options.DatabaseOptions) if err != nil { diff --git a/vclusterops/restart_node.go b/vclusterops/restart_node.go index 4f2b923..46e1eb4 100644 --- a/vclusterops/restart_node.go +++ b/vclusterops/restart_node.go @@ -126,7 +126,11 @@ func (vcc *VClusterCommands) VRestartNodes(options *VRestartNodesOptions) error } // get db name and hosts from config file and options - dbName, hosts := options.GetNameAndHosts(options.Config) + dbName, hosts, err := options.GetNameAndHosts(options.Config) + if err != nil { + return err + } + options.DBName = &dbName options.Hosts = hosts diff --git a/vclusterops/revive_db.go b/vclusterops/revive_db.go index 4d6af68..faae1bb 100644 --- a/vclusterops/revive_db.go +++ b/vclusterops/revive_db.go @@ -183,7 +183,7 @@ func (vcc *VClusterCommands) producePreReviveDBInstructions(options *VReviveData // use description file path as source file path sourceFilePath := options.getDescriptionFilePath() nmaDownLoadFileOp, err := makeNMADownloadFileOpForRevive(options.Hosts, sourceFilePath, destinationFilePath, catalogPath, - options.CommunalStorageParameters, vdb, *options.DisplayOnly, *options.IgnoreClusterLease) + options.ConfigurationParameters, vdb, *options.DisplayOnly, *options.IgnoreClusterLease) if err != nil { return instructions, err } @@ -239,7 +239,7 @@ func produceReviveDBInstructions(options *VReviveDatabaseOptions, vdb *VCoordina nmaNetworkProfileOp := makeNMANetworkProfileOp(options.Hosts) - nmaLoadRemoteCatalogOp := makeNMALoadRemoteCatalogOp(oldHosts, options.CommunalStorageParameters, &newVDB, *options.LoadCatalogTimeout) + nmaLoadRemoteCatalogOp := makeNMALoadRemoteCatalogOp(oldHosts, options.ConfigurationParameters, &newVDB, *options.LoadCatalogTimeout) instructions = append(instructions, &nmaPrepareDirectoriesOp, diff --git a/vclusterops/start_db.go b/vclusterops/start_db.go index eb4f937..76c7130 100644 --- a/vclusterops/start_db.go +++ b/vclusterops/start_db.go @@ -108,10 +108,17 @@ func (vcc *VClusterCommands) VStartDatabase(options *VStartDatabaseOptions) erro } // get db name and hosts from config file and options - dbName, hosts := options.GetNameAndHosts(options.Config) + dbName, hosts, err := options.GetNameAndHosts(options.Config) + if err != nil { + return err + } + options.DBName = &dbName options.Hosts = hosts - options.CatalogPrefix = options.GetCatalogPrefix(options.Config) + options.CatalogPrefix, err = options.GetCatalogPrefix(options.Config) + if err != nil { + return err + } // set default value to StatePollingTimeout if options.StatePollingTimeout == 0 { @@ -120,7 +127,12 @@ func (vcc *VClusterCommands) VStartDatabase(options *VStartDatabaseOptions) erro var pVDB *VCoordinationDatabase // retrieve database information from cluster_config.json for EON databases - if options.IsEonMode(options.Config) { + isEon, err := options.IsEonMode(options.Config) + if err != nil { + return err + } + + if isEon { if *options.CommunalStorageLocation != "" { vdb, e := options.getVDBWhenDBIsDown() if e != nil { @@ -208,6 +220,12 @@ func (vcc *VClusterCommands) produceStartDBInstructions(options *VStartDatabaseO } instructions = append(instructions, &nmaReadCatalogEditorOp) + if enabled, keyType := options.isSpreadEncryptionEnabled(); enabled { + instructions = append(instructions, + vcc.setOrRotateEncryptionKey(keyType), + ) + } + // sourceConfHost is set to nil value in upload and download step // we use information from catalog editor operation to update the sourceConfHost value // after we find host with the highest catalog and hosts that need to synchronize the catalog @@ -239,3 +257,9 @@ func (vcc *VClusterCommands) produceStartDBInstructions(options *VStartDatabaseO return instructions, nil } + +func (vcc *VClusterCommands) setOrRotateEncryptionKey(keyType string) ClusterOp { + vcc.Log.Info("adding instruction to set or rotate the key for spread encryption") + op := makeNMASpreadSecurityOp(vcc.Log, keyType) + return &op +} diff --git a/vclusterops/stop_db.go b/vclusterops/stop_db.go index 1683f3b..406955d 100644 --- a/vclusterops/stop_db.go +++ b/vclusterops/stop_db.go @@ -65,7 +65,12 @@ func (options *VStopDatabaseOptions) validateRequiredOptions() error { func (options *VStopDatabaseOptions) validateEonOptions(config *ClusterConfig) error { // if db is enterprise db and we see --drain-seconds, we will ignore it - if !options.IsEonMode(config) { + isEon, err := options.IsEonMode(config) + if err != nil { + return err + } + + if !isEon { if options.DrainSeconds != nil { vlog.LogPrintInfoln("Notice: --drain-seconds option will be ignored because database is in enterprise mode." + " Connection draining is only available in eon mode.") @@ -140,8 +145,15 @@ func (vcc *VClusterCommands) VStopDatabase(options *VStopDatabaseOptions) error stopDBInfo.UserName = *options.UserName stopDBInfo.Password = options.Password stopDBInfo.DrainSeconds = options.DrainSeconds - stopDBInfo.DBName, stopDBInfo.Hosts = options.GetNameAndHosts(options.Config) - stopDBInfo.IsEon = options.IsEonMode(options.Config) + stopDBInfo.DBName, stopDBInfo.Hosts, err = options.GetNameAndHosts(options.Config) + if err != nil { + return err + } + + stopDBInfo.IsEon, err = options.IsEonMode(options.Config) + if err != nil { + return err + } instructions, err := vcc.produceStopDBInstructions(stopDBInfo, options) if err != nil { diff --git a/vclusterops/test_data/vertica_cluster.yaml b/vclusterops/test_data/vertica_cluster.yaml index 99f8e65..f0453c9 100644 --- a/vclusterops/test_data/vertica_cluster.yaml +++ b/vclusterops/test_data/vertica_cluster.yaml @@ -1,17 +1,16 @@ -db_name: practice_db -hosts: - - ip_1 - - ip_2 - - ip_3 -nodes: - - name: node_name_1 - address: ip_1 - - name: node_name_2 - address: ip_2 - - name: node_name_3 - address: ip_3 -catalog_path: /data -data_path: /data -depot_path: /data -eon_mode: true -ipv6: false +practice_db: + nodes: + - name: node_name_1 + address: ip_1 + subcluster: default_subcluster + - name: node_name_2 + address: ip_2 + subcluster: default_subcluster + - name: node_name_3 + address: ip_3 + subcluster: default_subcluster + catalog_path: /data + data_path: /data + depot_path: /data + eon_mode: true + ipv6: false diff --git a/vclusterops/vcluster_database_options.go b/vclusterops/vcluster_database_options.go index ae1c637..26596e7 100644 --- a/vclusterops/vcluster_database_options.go +++ b/vclusterops/vcluster_database_options.go @@ -38,10 +38,10 @@ type DatabaseOptions struct { ConfigDirectory *string // part 2: Eon database info - DepotPrefix *string - IsEon vstruct.NullableBool - CommunalStorageLocation *string - CommunalStorageParameters map[string]string + DepotPrefix *string + IsEon vstruct.NullableBool + CommunalStorageLocation *string + ConfigurationParameters map[string]string // part 3: authentication info UserName *string @@ -82,7 +82,7 @@ func (opt *DatabaseOptions) SetDefaultValues() { opt.Ipv6 = vstruct.NotSet opt.IsEon = vstruct.NotSet opt.CommunalStorageLocation = new(string) - opt.CommunalStorageParameters = make(map[string]string) + opt.ConfigurationParameters = make(map[string]string) } func (opt *DatabaseOptions) CheckNilPointerParams() error { @@ -264,31 +264,42 @@ func (opt *DatabaseOptions) SetUsePassword() error { } // IsEonMode can choose the right eon mode from user input and config file -func (opt *DatabaseOptions) IsEonMode(config *ClusterConfig) bool { +func (opt *DatabaseOptions) IsEonMode(config *ClusterConfig) (bool, error) { // when config file is not available, we use user input // HonorUserInput must be true at this time, otherwise vcluster has stopped when it cannot find the config file if config == nil { - return opt.IsEon.ToBool() + return opt.IsEon.ToBool(), nil } - isEon := config.IsEon + dbConfig, ok := (*config)[*opt.DBName] + if !ok { + return false, cannotFindDBFromConfigErr(*opt.DBName) + } + + isEon := dbConfig.IsEon // if HonorUserInput is set, we choose the user input if opt.IsEon != vstruct.NotSet && *opt.HonorUserInput { isEon = opt.IsEon.ToBool() } - return isEon + return isEon, nil } // GetNameAndHosts can choose the right dbName and hosts from user input and config file -func (opt *DatabaseOptions) GetNameAndHosts(config *ClusterConfig) (dbName string, hosts []string) { +func (opt *DatabaseOptions) GetNameAndHosts(config *ClusterConfig) (dbName string, hosts []string, err error) { // when config file is not available, we use user input // HonorUserInput must be true at this time, otherwise vcluster has stopped when it cannot find the config file + dbName = *opt.DBName + if config == nil { - return *opt.DBName, opt.Hosts + return *opt.DBName, opt.Hosts, nil + } + + dbConfig, ok := (*config)[dbName] + if !ok { + return dbName, hosts, cannotFindDBFromConfigErr(dbName) } - dbName = config.DBName - hosts = config.Hosts + hosts = dbConfig.GetHosts() // if HonorUserInput is set, we choose the user input if *opt.DBName != "" && *opt.HonorUserInput { dbName = *opt.DBName @@ -296,50 +307,68 @@ func (opt *DatabaseOptions) GetNameAndHosts(config *ClusterConfig) (dbName strin if len(opt.Hosts) > 0 && *opt.HonorUserInput { hosts = opt.Hosts } - return dbName, hosts + return dbName, hosts, nil } // GetHosts chooses the right hosts from user input and config file -func (opt *DatabaseOptions) GetHosts(config *ClusterConfig) (hosts []string) { +func (opt *DatabaseOptions) GetHosts(config *ClusterConfig) (hosts []string, err error) { // when config file is not available, we use user input // HonorUserInput must be true at this time, otherwise vcluster has stopped when it cannot find the config file if config == nil { - return opt.Hosts + return opt.Hosts, nil } - hosts = config.Hosts + dbConfig, ok := (*config)[*opt.DBName] + if !ok { + return hosts, cannotFindDBFromConfigErr(*opt.DBName) + } + + hosts = dbConfig.GetHosts() // if HonorUserInput is set, we choose the user input if len(opt.Hosts) > 0 && *opt.HonorUserInput { hosts = opt.Hosts } - return hosts + return hosts, nil } // GetCatalogPrefix can choose the right catalog prefix from user input and config file -func (opt *DatabaseOptions) GetCatalogPrefix(config *ClusterConfig) (catalogPrefix *string) { +func (opt *DatabaseOptions) GetCatalogPrefix(config *ClusterConfig) (catalogPrefix *string, err error) { // when config file is not available, we use user input // HonorUserInput must be true at this time, otherwise vcluster has stopped when it cannot find the config file if config == nil { - return opt.CatalogPrefix + return opt.CatalogPrefix, nil + } + + dbConfig, ok := (*config)[*opt.DBName] + if !ok { + return nil, cannotFindDBFromConfigErr(*opt.DBName) } - catalogPrefix = &config.CatalogPath + + catalogPrefix = &dbConfig.CatalogPath // if HonorUserInput is set, we choose the user input if *opt.CatalogPrefix != "" && *opt.HonorUserInput { catalogPrefix = opt.CatalogPrefix } - return catalogPrefix + return catalogPrefix, nil } // getDepotAndDataPrefix chooses the right depot/data prefix from user input and config file. -func (opt *DatabaseOptions) getDepotAndDataPrefix(config *ClusterConfig) (depotPrefix, dataPrefix string) { +func (opt *DatabaseOptions) getDepotAndDataPrefix( + config *ClusterConfig) (depotPrefix, dataPrefix string, err error) { if config == nil { - return *opt.DepotPrefix, *opt.DataPrefix + return *opt.DepotPrefix, *opt.DataPrefix, nil + } + + dbConfig, ok := (*config)[*opt.DBName] + if !ok { + return depotPrefix, dataPrefix, cannotFindDBFromConfigErr(*opt.DBName) } - depotPrefix = config.DepotPath - dataPrefix = config.DataPath + + depotPrefix = dbConfig.DepotPath + dataPrefix = dbConfig.DataPath // if HonorUserInput is set, we choose the user input if !*opt.HonorUserInput { - return depotPrefix, dataPrefix + return depotPrefix, dataPrefix, nil } if *opt.DepotPrefix != "" { depotPrefix = *opt.DepotPrefix @@ -347,7 +376,7 @@ func (opt *DatabaseOptions) getDepotAndDataPrefix(config *ClusterConfig) (depotP if *opt.DataPrefix != "" { dataPrefix = *opt.DataPrefix } - return depotPrefix, dataPrefix + return depotPrefix, dataPrefix, nil } // GetDBConfig can read database configurations from vertica_cluster.yaml to the struct ClusterConfig @@ -433,7 +462,7 @@ func (opt *DatabaseOptions) getVDBWhenDBIsDown() (vdb VCoordinationDatabase, err var instructions2 []ClusterOp sourceFilePath := opt.getDescriptionFilePath() nmaDownLoadFileOp, err := makeNMADownloadFileOp(opt.Hosts, sourceFilePath, destinationFilePath, catalogPath, - opt.CommunalStorageParameters, &vdb2) + opt.ConfigurationParameters, &vdb2) if err != nil { return vdb, err } @@ -486,3 +515,14 @@ func (opt *DatabaseOptions) getDescriptionFilePath() string { return descriptionFilePath } + +func (opt *DatabaseOptions) isSpreadEncryptionEnabled() (enabled bool, encryptionType string) { + const EncryptSpreadCommConfigName = "encryptspreadcomm" + // We cannot use the map lookup because the key name is case insensitive. + for key, val := range opt.ConfigurationParameters { + if strings.EqualFold(key, EncryptSpreadCommConfigName) { + return true, val + } + } + return false, "" +} diff --git a/vclusterops/vlog/vLog.go b/vclusterops/vlog/vLog.go index c865adf..1872369 100644 --- a/vclusterops/vlog/vLog.go +++ b/vclusterops/vlog/vLog.go @@ -331,8 +331,7 @@ func (logger *Vlogger) logMaskedArgParse(inputArgv []string) { ) // We need to mask any parameters containing sensitive information targetMaskedArg := map[string]bool{ - "--config-param": true, - "--communal-storage-params": true, + "--config-param": true, } for i := 0; i < len(inputArgv); i++ { if targetMaskedArg[inputArgv[i]] && i+1 < len(inputArgv) {