Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/v4/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,11 @@ type PhaseInfo struct {
FailCount uint32 `json:"failCount,omitempty"`
}

type UpgradeStrategy struct {
WaitForHistSearchDomain bool `json:"waitForHistSearchDrain,omitempty"`
EnableSearchRestartOnRemoteFailure bool `json:"enableSearchRestartOnRemoteFailure,omitempty"`
}

const (
// AppPkgDownloadPending indicates pending
AppPkgDownloadPending AppPhaseStatusType = 101
Expand Down
5 changes: 5 additions & 0 deletions api/v4/searchheadcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ type SearchHeadClusterSpec struct {

// Splunk Deployer Node Affinity
DeployerNodeAffinity *corev1.NodeAffinity `json:"deployerNodeAffinity,omitempty"`

// upgrade strategy for the search head cluster
UpgradeStrategy UpgradeStrategy `json:"upgradeStrategy,omitempty"`
}

// SearchHeadClusterMemberStatus is used to track the status of each search head cluster member
Expand Down Expand Up @@ -128,6 +131,8 @@ type SearchHeadClusterStatus struct {

// Auxillary message describing CR status
Message string `json:"message"`

UpgradePhase string `json:"upgradePhase"`
}

// SearchHeadCluster is the Schema for a Splunk Enterprise search head cluster
Expand Down
16 changes: 16 additions & 0 deletions api/v4/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 66 additions & 5 deletions config/crd/bases/enterprise.splunk.com_clustermanagers.yaml

Large diffs are not rendered by default.

71 changes: 66 additions & 5 deletions config/crd/bases/enterprise.splunk.com_clustermasters.yaml

Large diffs are not rendered by default.

140 changes: 131 additions & 9 deletions config/crd/bases/enterprise.splunk.com_indexerclusters.yaml

Large diffs are not rendered by default.

71 changes: 66 additions & 5 deletions config/crd/bases/enterprise.splunk.com_licensemanagers.yaml

Large diffs are not rendered by default.

71 changes: 66 additions & 5 deletions config/crd/bases/enterprise.splunk.com_licensemasters.yaml

Large diffs are not rendered by default.

140 changes: 131 additions & 9 deletions config/crd/bases/enterprise.splunk.com_monitoringconsoles.yaml

Large diffs are not rendered by default.

152 changes: 143 additions & 9 deletions config/crd/bases/enterprise.splunk.com_searchheadclusters.yaml

Large diffs are not rendered by default.

140 changes: 131 additions & 9 deletions config/crd/bases/enterprise.splunk.com_standalones.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
github.com/aws/aws-sdk-go v1.47.11
github.com/go-logr/logr v1.4.2
github.com/go-resty/resty/v2 v2.16.5
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXym
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
Expand Down
132 changes: 125 additions & 7 deletions pkg/splunk/client/enterprise.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"regexp"
"strconv"
"strings"
"time"

"github.com/go-resty/resty/v2"
splcommon "github.com/splunk/splunk-operator/pkg/splunk/common"
)

Expand All @@ -35,6 +35,35 @@
Do(*http.Request) (*http.Response, error)
}

// RestyClientWrapper is a wrapper around resty.Client to implement SplunkHTTPClient interface
type RestyClientWrapper struct {
*resty.Client
}

// Do sends an HTTP request and returns an HTTP response, implementing the SplunkHTTPClient interface
func (r *RestyClientWrapper) Do(req *http.Request) (*http.Response, error) {
restyReq := r.R()
restyReq.Method = req.Method
restyReq.URL = req.URL.String()
restyReq.Body = req.Body
restyReq.Header = req.Header

resp, err := restyReq.Send()
if err != nil {
return nil, err
}

httpResp := &http.Response{
Status: resp.Status(),
StatusCode: resp.StatusCode(),
Body: io.NopCloser(strings.NewReader(resp.String())),
Header: resp.Header(),
ContentLength: resp.Size(),
}

return httpResp, nil
}

// SplunkClient is a simple object used to send HTTP REST API requests
type SplunkClient struct {
// https endpoint for management interface (e.g. "https://server:8089")
Expand All @@ -50,18 +79,35 @@
Client SplunkHTTPClient
}

// UpgradeMetrics holds search metrics for a search head during upgrade.
type UpgradeMetrics struct {
ShortSearchSuccess int `json:"short_search_success"`
ShortSearchFailure int `json:"short_search_failure"`
TotalSearchSuccess int `json:"total_search_success"`
TotalSearchFailure int `json:"total_search_failure"`
}

// NewSplunkClient returns a new SplunkClient object initialized with a username and password.
func NewSplunkClient(managementURI, username, password string) *SplunkClient {
client := &RestyClientWrapper{
Client: resty.New().
SetBaseURL(managementURI).
SetBasicAuth(username, password).
SetRetryCount(3).
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}),

Check failure

Code scanning / CodeQL

Disabled TLS certificate check High

InsecureSkipVerify should not be used in production code.

Copilot Autofix

AI 7 months ago

To fix the problem, we need to ensure that TLS certificate verification is enabled. This involves setting InsecureSkipVerify to false and properly configuring the client to use valid certificates. If the certificates are self-signed or from a private CA, we should add the CA certificate to the client's trusted pool.

  1. Change the InsecureSkipVerify setting to false.
  2. If necessary, load the CA certificate and add it to the client's trusted pool.
Suggested changeset 1
pkg/splunk/client/enterprise.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pkg/splunk/client/enterprise.go b/pkg/splunk/client/enterprise.go
--- a/pkg/splunk/client/enterprise.go
+++ b/pkg/splunk/client/enterprise.go
@@ -96,3 +96,5 @@
 			SetRetryCount(3).
-			SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}),
+			SetTLSClientConfig(&tls.Config{InsecureSkipVerify: false}),
+			// Add logic to load CA certificates if needed
+			// .SetRootCertificate("/path/to/ca-cert.pem"),
 	}
EOF
@@ -96,3 +96,5 @@
SetRetryCount(3).
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}),
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: false}),
// Add logic to load CA certificates if needed
// .SetRootCertificate("/path/to/ca-cert.pem"),
}
Copilot is powered by AI and may make mistakes. Always verify output.
}

return &SplunkClient{
ManagementURI: managementURI,
Username: username,
Password: password,
Client: &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // don't verify ssl certs
},
},
Client: client,
//Client: &http.Client{
// Timeout: 5 * time.Second,
// Transport: &http.Transport{
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // don't verify ssl certs
// },
//},
}
}

Expand Down Expand Up @@ -935,3 +981,75 @@
expectedStatus := []int{200}
return c.Do(request, expectedStatus, nil)
}

// GetUpgradeSearchMetrics uses Resty to query the search head for upgrade search metrics.
func (c *SplunkClient) GetUpgradeSearchMetrics() (*UpgradeMetrics, error) {
resp, err := c.Client.(*RestyClientWrapper).R().
SetResult(&UpgradeMetrics{}).
Get("/services/search/metrics")
if err != nil {
return nil, err
}
if resp.IsError() {
return nil, fmt.Errorf("error fetching upgrade metrics: %s", resp.Status())
}
metrics, ok := resp.Result().(*UpgradeMetrics)
if !ok {
return nil, fmt.Errorf("failed to parse upgrade metrics")
}
return metrics, nil
}

// InitShcUpgrade calls the endpoint to display the upgrade banner.
func (c *SplunkClient) InitShcUpgrade() error {
resp, err := c.Client.(*RestyClientWrapper).R().
Post("/services/shcluster/captain/control/control/upgrade-init")
if err != nil {
return err
}
if resp.IsError() {
return fmt.Errorf("error initiating SHC upgrade: %s", resp.Status())
}
return nil
}

// FinalizeShcUpgrade calls the endpoint to remove the upgrade banner.
func (c *SplunkClient) FinalizeShcUpgrade() error {
resp, err := c.Client.(*RestyClientWrapper).R().
Post("/services/shcluster/captain/control/control/upgrade-finalize")
if err != nil {
return err
}
if resp.IsError() {
return fmt.Errorf("error finalizing SHC upgrade: %s", resp.Status())
}
return nil
}

// SetManualDetentionMode puts the search head into manual detention mode.
func (c *SplunkClient) SetManualDetentionMode() error {
resp, err := c.Client.(*RestyClientWrapper).R().
SetBody(map[string]bool{"mode": true}).
Post("/servicesNS/admin/search/shcluster/member/control/control/set_manual_detention")
if err != nil {
return err
}
if resp.IsError() {
return fmt.Errorf("error setting manual detention mode: %s", resp.Status())
}
return nil
}

// UnsetManualDetentionMode removes the search head from manual detention mode.
func (c *SplunkClient) UnsetManualDetentionMode() error {
resp, err := c.Client.(*RestyClientWrapper).R().
SetBody(map[string]bool{"mode": false}).
Post("/servicesNS/admin/search/shcluster/member/control/control/set_manual_detention")
if err != nil {
return err
}
if resp.IsError() {
return fmt.Errorf("error unsetting manual detention mode: %s", resp.Status())
}
return nil
}
53 changes: 53 additions & 0 deletions pkg/splunk/enterprise/metrics/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package metrics

import (
"github.com/prometheus/client_golang/prometheus"
)

var (
UpgradeStartTime = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "splunk_upgrade_start_time",
Help: "Unix timestamp when the SHC upgrade started",
})
UpgradeEndTime = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "splunk_upgrade_end_time",
Help: "Unix timestamp when the SHC upgrade ended",
})
ShortSearchSuccessCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "splunk_short_search_success_total",
Help: "Total number of successful short searches per search head",
},
[]string{"sh_name"},
)
ShortSearchFailureCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "splunk_short_search_failure_total",
Help: "Total number of failed short searches per search head",
},
[]string{"sh_name"},
)
TotalSearchSuccessCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "splunk_total_search_success_total",
Help: "Total number of successful total searches per search head",
},
[]string{"sh_name"},
)
TotalSearchFailureCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "splunk_total_search_failure_total",
Help: "Total number of failed total searches per search head",
},
[]string{"sh_name"},
)
)

func init() {
prometheus.MustRegister(UpgradeStartTime)
prometheus.MustRegister(UpgradeEndTime)
prometheus.MustRegister(ShortSearchSuccessCounter)
prometheus.MustRegister(ShortSearchFailureCounter)
prometheus.MustRegister(TotalSearchSuccessCounter)
prometheus.MustRegister(TotalSearchFailureCounter)
}
Loading
Loading