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
40 changes: 40 additions & 0 deletions pkg/splunk/client/enterprise.go
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,46 @@ func (c *SplunkClient) SetIdxcSecret(idxcSecret string) error {
return c.Do(request, expectedStatus, nil)
}

// LicenseInfo represents license information from Splunk
type LicenseInfo struct {
Title string `json:"title"`
Status string `json:"status"`
ExpirationTime int64 `json:"expiration_time"`
}

// LicenseResponse represents the API response from /services/licenser/licenses
type LicenseResponse struct {
Entry []struct {
Name string `json:"name"`
Content LicenseInfo `json:"content"`
} `json:"entry"`
}

// GetLicenseInfo retrieves license information from Splunk instance
// See https://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTlicense#licenser.2Flicenses
func (c *SplunkClient) GetLicenseInfo() (map[string]LicenseInfo, error) {
endpoint := fmt.Sprintf("%s/services/licenser/licenses?output_mode=json", c.ManagementURI)
request, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return nil, err
}

var response LicenseResponse
expectedStatus := []int{200}
err = c.Do(request, expectedStatus, &response)
if err != nil {
return nil, err
}

// Convert response to map
licenses := make(map[string]LicenseInfo)
for _, entry := range response.Entry {
licenses[entry.Name] = entry.Content
}

return licenses, nil
}

// RestartSplunk restarts specific Splunk instance
// Can be used for any Splunk Instance
// See https://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTsystem#server.2Fcontrol.2Frestart
Expand Down
62 changes: 61 additions & 1 deletion pkg/splunk/enterprise/licensemanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"time"

enterpriseApi "github.com/splunk/splunk-operator/api/v4"
splclient "github.com/splunk/splunk-operator/pkg/splunk/client"
splutil "github.com/splunk/splunk-operator/pkg/splunk/util"

appsv1 "k8s.io/api/apps/v1"
Expand Down Expand Up @@ -59,7 +60,7 @@ func ApplyLicenseManager(ctx context.Context, client splcommon.ControllerClient,
// validate and updates defaults for CR
err = validateLicenseManagerSpec(ctx, client, cr)
if err != nil {
eventPublisher.Warning(ctx, "validateLicenseManagerSpec", fmt.Sprintf("validate licensemanager spec failed %s", err.Error()))
eventPublisher.Warning(ctx, "validateLicenseManagerSpec", fmt.Sprintf("validate license manager spec failed %s", err.Error()))
scopedLog.Error(err, "Failed to validate license manager spec")
return result, err
}
Expand Down Expand Up @@ -141,6 +142,9 @@ func ApplyLicenseManager(ctx context.Context, client splcommon.ControllerClient,
return result, err
}

// Check for license-related pod failures before updating
checkLicenseRelatedPodFailures(ctx, client, cr, statefulSet, eventPublisher)

mgr := splctrl.DefaultStatefulSetPodManager{}
phase, err := mgr.Update(ctx, client, statefulSet, 1)
if err != nil {
Expand Down Expand Up @@ -220,6 +224,62 @@ func validateLicenseManagerSpec(ctx context.Context, c splcommon.ControllerClien
return validateCommonSplunkSpec(ctx, c, &cr.Spec.CommonSplunkSpec, cr)
}

// checkLicenseRelatedPodFailures checks license status via Splunk API
// and publishes warning event when expired license is detected
func checkLicenseRelatedPodFailures(ctx context.Context, client splcommon.ControllerClient, cr *enterpriseApi.LicenseManager, statefulSet *appsv1.StatefulSet, eventPublisher *K8EventPublisher) {
reqLogger := log.FromContext(ctx)
scopedLog := reqLogger.WithName("checkLicenseRelatedPodFailures")

// Check if pod is ready before attempting API call
podName := fmt.Sprintf("%s-0", statefulSet.GetName())
namespacedName := types.NamespacedName{Namespace: statefulSet.GetNamespace(), Name: podName}
var pod corev1.Pod
err := client.Get(ctx, namespacedName, &pod)
if err != nil {
// Pod might not exist yet, which is normal during initial creation
Copy link
Collaborator

@kubabuczak kubabuczak Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we log something on return?

return
}

// Only check license if pod is running
if pod.Status.Phase != corev1.PodRunning {
return
}

// Get admin password from namespace-scoped secret
defaultSecretObjName := splcommon.GetNamespaceScopedSecretName(cr.GetNamespace())
defaultSecret, err := splutil.GetSecretByName(ctx, client, cr.GetNamespace(), cr.GetName(), defaultSecretObjName)
if err != nil {
scopedLog.Error(err, "Failed to get namespace secret for license check")
return
}

adminPassword := string(defaultSecret.Data["password"])
if adminPassword == "" {
scopedLog.Info("Admin password not found in secret, skipping license check")
return
}

// Create Splunk client
fqdnName := GetSplunkStatefulsetURL(cr.GetNamespace(), SplunkLicenseManager, cr.GetName(), 0, false)
splunkClient := splclient.NewSplunkClient(fmt.Sprintf("https://%s:8089", fqdnName), "admin", adminPassword)

// Get license information from Splunk API
licenses, err := splunkClient.GetLicenseInfo()
if err != nil {
scopedLog.Error(err, "Failed to get license information from Splunk API")
return
}

// Check for expired licenses
for licenseName, licenseInfo := range licenses {
if licenseInfo.Status == "EXPIRED" {
eventPublisher.Warning(ctx, "LicenseExpired",
fmt.Sprintf("License '%s' has expired", licenseName))
scopedLog.Info("Detected expired license", "licenseName", licenseName, "title", licenseInfo.Title)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be Info?

}
}
}

// helper function to get the list of LicenseManager types in the current namespace
func getLicenseManagerList(ctx context.Context, c splcommon.ControllerClient, cr splcommon.MetaObject, listOpts []client.ListOption) (enterpriseApi.LicenseManagerList, error) {
reqLogger := log.FromContext(ctx)
Expand Down
Loading
Loading