Skip to content

Commit

Permalink
Sync from server repo (4c6fff568dd)
Browse files Browse the repository at this point in the history
  • Loading branch information
cchen-vertica committed Aug 8, 2024
1 parent 2ad21f1 commit 530a0da
Show file tree
Hide file tree
Showing 11 changed files with 343 additions and 46 deletions.
2 changes: 2 additions & 0 deletions commands/cluster_command_launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,8 @@ func constructCmds() []*cobra.Command {
makeCmdCreateConnection(),
// hidden cmds (for internal testing only)
makeCmdPromoteSandbox(),

makeCmdCheckVClusterServerPid(),
}
}

Expand Down
2 changes: 1 addition & 1 deletion commands/cmd_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func (c *CmdBase) setConfigFlags(cmd *cobra.Command, flags []string) {
configFlag,
"c",
"",
"The path to the config file. If a configuration file is present in the default location (automatically generated by `create_db`),\n"+
"The path to the config file. If a configuration file is present in the default location (automatically generated by create_db),\n"+
"you do not need to specify this option.\n"+
"Default: /opt/vertica/config/vertica_cluster.yaml")
markFlagsFileName(cmd, map[string][]string{configFlag: {"yaml"}})
Expand Down
86 changes: 86 additions & 0 deletions commands/cmd_check_vcluster_server_pid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
(c) Copyright [2023-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
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 (
"fmt"

"github.com/spf13/cobra"
"github.com/vertica/vcluster/vclusterops"
"github.com/vertica/vcluster/vclusterops/vlog"
)

/* CmdCheckVClusterServerPid
*
* Implements ClusterCommand interface
*/
type CmdCheckVClusterServerPid struct {
checkPidOptions *vclusterops.VCheckVClusterServerPidOptions

CmdBase
}

func makeCmdCheckVClusterServerPid() *cobra.Command {
newCmd := &CmdCheckVClusterServerPid{}
opt := vclusterops.VCheckVClusterServerPidOptionsFactory()
newCmd.checkPidOptions = &opt

cmd := makeBasicCobraCmd(
newCmd,
"check_pid",
"Check VCluster server PID files",
`Check VCluster server PID files in nodes to make sure that
only one host is running the VCluster server`,
[]string{hostsFlag},
)

return cmd
}

func (c *CmdCheckVClusterServerPid) Parse(inputArgv []string, logger vlog.Printer) error {
c.argv = inputArgv
logger.LogArgParse(&c.argv)

return c.validateParse(logger)
}

func (c *CmdCheckVClusterServerPid) validateParse(logger vlog.Printer) error {
logger.Info("Called validateParse()")
if !c.usePassword() {
err := c.getCertFilesFromCertPaths(&c.checkPidOptions.DatabaseOptions)
if err != nil {
return err
}
}
return c.ValidateParseBaseOptions(&c.checkPidOptions.DatabaseOptions)
}

func (c *CmdCheckVClusterServerPid) Run(vcc vclusterops.ClusterCommands) error {
vcc.V(1).Info("Called method Run()")

hostsWithVclusterServerPid, err := vcc.VCheckVClusterServerPid(c.checkPidOptions)
if err != nil {
vcc.LogError(err, "failed to drop the database")
return err
}
fmt.Printf("Hosts with VCluster server PID files: %+v\n", hostsWithVclusterServerPid)

return nil
}

func (c *CmdCheckVClusterServerPid) SetDatabaseOptions(opt *vclusterops.DatabaseOptions) {
c.checkPidOptions.DatabaseOptions = *opt
}
5 changes: 5 additions & 0 deletions commands/vcluster_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@ func (c *DatabaseConfig) write(configFilePath string, forceOverwrite bool) error
return nil
}

// Exposing the write function for external packages
func (c *DatabaseConfig) Write(configFilePath string, forceOverwrite bool) error {
return c.write(configFilePath, forceOverwrite)
}

// getHosts returns host addresses of all nodes in database
func (c *DatabaseConfig) getHosts() []string {
var hostList []string
Expand Down
6 changes: 3 additions & 3 deletions rfc7807/rfc7807_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestHttpResponse(t *testing.T) {
p := New(CommunalAccessError).
WithDetail("communal endpoint is down").
WithHost("pod-2")
handler := func(w http.ResponseWriter, r *http.Request) {
handler := func(w http.ResponseWriter, _ *http.Request) {
p.SendError(w)
}

Expand All @@ -70,7 +70,7 @@ func TestProblemExtraction(t *testing.T) {
origProblem := New(CommunalRWAccessError).
WithDetail("could not read from communal storage").
WithHost("pod-3")
handler := func(w http.ResponseWriter, r *http.Request) {
handler := func(w http.ResponseWriter, _ *http.Request) {
origProblem.SendError(w)
}

Expand All @@ -94,7 +94,7 @@ func TestProblemExtraction(t *testing.T) {
}

func TestJSONExtractFailure(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
handler := func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, "not json")
}
req := httptest.NewRequest("GET", "http://vertica.com/bootstrapEndpoint", http.NoBody)
Expand Down
100 changes: 100 additions & 0 deletions vclusterops/check_vcluster_server_pid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
(c) Copyright [2023-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
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"
"golang.org/x/exp/slices"
)

type VCheckVClusterServerPidOptions struct {
DatabaseOptions
}

func VCheckVClusterServerPidOptionsFactory() VCheckVClusterServerPidOptions {
options := VCheckVClusterServerPidOptions{}
// set default values to the params
options.setDefaultValues()

return options
}

func (options *VCheckVClusterServerPidOptions) setDefaultValues() {
options.DatabaseOptions.setDefaultValues()
}

func (options *VCheckVClusterServerPidOptions) analyzeOptions() (err error) {
// resolve RawHosts to be IP addresses
if len(options.RawHosts) > 0 {
options.Hosts, err = util.ResolveRawHostsToAddresses(options.RawHosts, options.IPv6)
if err != nil {
return err
}
}

return nil
}

func (options *VCheckVClusterServerPidOptions) validateAnalyzeOptions(_ vlog.Printer) error {
err := options.analyzeOptions()
if err != nil {
return err
}
return nil
}

func (vcc VClusterCommands) VCheckVClusterServerPid(
options *VCheckVClusterServerPidOptions) (hostsWithVclusterServerPid []string, err error) {
// validate and analyze options
err = options.validateAnalyzeOptions(vcc.Log)
if err != nil {
return hostsWithVclusterServerPid, err
}

// produce instructions of checking VCluster server PID files
instructions, err := vcc.produceCheckVClusterServerPidInstructions(options)
if err != nil {
return hostsWithVclusterServerPid, fmt.Errorf("fail to produce instructions, %w", err)
}

// create a VClusterOpEngine, and add certs to the engine
clusterOpEngine := makeClusterOpEngine(instructions, options)

// give the instructions to the VClusterOpEngine to run
runError := clusterOpEngine.run(vcc.Log)
if runError != nil {
return hostsWithVclusterServerPid, fmt.Errorf("fail to check VCluster server PID files: %w", runError)
}

hostsWithVclusterServerPid = clusterOpEngine.execContext.HostsWithVclusterServerPid
slices.Sort(hostsWithVclusterServerPid)

return hostsWithVclusterServerPid, nil
}

func (vcc VClusterCommands) produceCheckVClusterServerPidInstructions(options *VCheckVClusterServerPidOptions) ([]clusterOp, error) {
var instructions []clusterOp

nmaHealthOp := makeNMAHealthOpSkipUnreachable(options.Hosts)

nmaCheckVClusterServerPidOp := makeNMACheckVClusterServerPidOp(options.Hosts)

instructions = append(instructions, &nmaHealthOp, &nmaCheckVClusterServerPidOp)
return instructions, nil
}
2 changes: 2 additions & 0 deletions vclusterops/cluster_op.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,8 @@ type ClusterCommands interface {
VPromoteSandboxToMain(options *VPromoteSandboxToMainOptions) error
VRenameSubcluster(options *VRenameSubclusterOptions) error
VFetchNodesDetails(options *VFetchNodesDetailsOptions) (NodesDetails, error)

VCheckVClusterServerPid(options *VCheckVClusterServerPidOptions) ([]string, error)
}

type VClusterCommandsLogger struct {
Expand Down
3 changes: 3 additions & 0 deletions vclusterops/cluster_op_engine_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ type opEngineExecContext struct {

// hosts that is not reachable through NMA
unreachableHosts []string

// hosts that have the VCluster server PID file
HostsWithVclusterServerPid []string
}

func makeOpEngineExecContext(logger vlog.Printer) opEngineExecContext {
Expand Down
43 changes: 1 addition & 42 deletions vclusterops/http_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ func (adapter *httpAdapter) setupHTTPClient(
// Note that hosts at this point are IP addresses, so verify-full may be impractical
// or impossible due to the complications of issuing certificates valid for IPs.
// Hence the custom validator skipping hostname validation.
config.VerifyPeerCertificate = adapter.generateTLSVerifyFunc(caCertPool)
config.VerifyPeerCertificate = util.GenerateTLSVerifyFunc(caCertPool)
}
}
}
Expand All @@ -399,47 +399,6 @@ func (adapter *httpAdapter) setupHTTPClient(
return client, nil
}

// generateTLSVerifyFunc returns a callback function suitable for use as the VerifyPeerCertificate
// field of a tls.Config struct. It is a slightly less performant but logically equivalent version of
// the validation logic which gets run when InsecureSkipVerify == false in go v1.20.11. The difference
// is that hostname validation is elided, which is not possible without custom verification.
//
// See crypto/x509/verify.go for hostname validation behavior and crypto/tls/handshake_client.go for
// the reference implementation of this function.
func (*httpAdapter) generateTLSVerifyFunc(rootCAs *x509.CertPool) func([][]byte, [][]*x509.Certificate) error {
return func(certificates [][]byte, _ [][]*x509.Certificate) error {
// Reparse certs. The crypto/tls package version does some extra checks, but they're already
// done by this point, so no need to repeat them. It also uses a cache to reduce parsing, which
// isn't included here, but could be if there is a perf issue.
certs := make([]*x509.Certificate, len(certificates))
for i, asn1Data := range certificates {
cert, err := x509.ParseCertificate(asn1Data)
if err != nil {
return err
}
certs[i] = cert
}

// construct verification options like reference implementation, minus hostname
opts := x509.VerifyOptions{
Roots: rootCAs,
CurrentTime: time.Now(),
DNSName: "",
Intermediates: x509.NewCertPool(),
}

for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
_, err := certs[0].Verify(opts)
if err != nil {
return &tls.CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
}

return nil
}
}

func buildQueryParamString(queryParams map[string]string) string {
var queryParamString string
if len(queryParams) == 0 {
Expand Down
Loading

0 comments on commit 530a0da

Please sign in to comment.