Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autostart trial #2160

Merged
merged 21 commits into from
Nov 29, 2019
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
12 changes: 6 additions & 6 deletions pkg/controller/common/license/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import (

const (
// LicenseLabelName is a label pointing to the name of the source enterprise license.
LicenseLabelName = "license.k8s.elastic.co/name"
LicenseLabelType = "license.k8s.elastic.co/type"
Type = "license"
EULAAnnotation = "elastic.co/eula"
EULAAcceptedValue = "accepted"
LicenseInvalidAnnotation = "license.k8s.elastic.co/invalid"
LicenseLabelName = "license.k8s.elastic.co/name"
LicenseLabelType = "license.k8s.elastic.co/type"
Type = "license"
EULAAnnotation = "elastic.co/eula"
EULAAcceptedValue = "accepted"
TrialLicenseStartedAnnotation = "license.k8s.elastic.co/trial-started"
thbkrkr marked this conversation as resolved.
Show resolved Hide resolved
)

// LicenseType is the type of license a resource is describing.
Expand Down
25 changes: 25 additions & 0 deletions pkg/controller/common/license/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ func bestMatchAt(
return best.license, best.parentUID, true, nil
}

func toTrial(el EnterpriseLicense) client.License {
return client.License{
Type: string(el.License.Type),
IssueDate: el.License.IssueDate,
IssueDateInMillis: el.License.IssueDateInMillis,
ExpiryDate: el.License.ExpiryDate,
ExpiryDateInMillis: el.License.ExpiryDateInMillis,
MaxNodes: el.License.MaxInstances,
IssuedTo: el.License.IssuedTo,
Issuer: el.License.Issuer,
StartDateInMillis: el.License.StartDateInMillis,
Signature: el.License.Signature,
}
}

func filterValid(now time.Time, licenses []EnterpriseLicense, filter func(EnterpriseLicense) (bool, error)) []licenseWithTimeLeft {
filtered := make([]licenseWithTimeLeft, 0)
for _, el := range licenses {
Expand All @@ -68,6 +83,16 @@ func filterValid(now time.Time, licenses []EnterpriseLicense, filter func(Enterp
if !ok {
continue
}

// Shortcut if it's a trial license
if el.IsTrial() {
return []licenseWithTimeLeft{{
license: toTrial(el),
parentUID: el.License.UID,
remaining: el.ExpiryTime().Sub(now),
}}
}

for _, l := range el.License.ClusterLicenses {
if l.License.IsValid(now) {
filtered = append(filtered, licenseWithTimeLeft{
Expand Down
11 changes: 9 additions & 2 deletions pkg/controller/common/license/trial.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ import (
const (
TrialStatusSecretKey = "trial-status"
TrialPubkeyKey = "pubkey"

TrialLicenseSecretName = "trial.k8s.elastic.co/secret-name" // nolint
TrialLicenseSecretNamespace = "trial.k8s.elastic.co/secret-namespace" // nolint
thbkrkr marked this conversation as resolved.
Show resolved Hide resolved
)

func InitTrial(c k8s.Client, secret corev1.Secret, l *EnterpriseLicense) (*rsa.PublicKey, error) {
func InitTrial(c k8s.Client, operatorNamespace string, secret corev1.Secret, l *EnterpriseLicense) (*rsa.PublicKey, error) {
if l == nil {
return nil, errors.New("license is nil")
}
Expand All @@ -51,11 +54,15 @@ func InitTrial(c k8s.Client, secret corev1.Secret, l *EnterpriseLicense) (*rsa.P
}
trialStatus := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: secret.Namespace,
Namespace: operatorNamespace,
Name: TrialStatusSecretKey,
Labels: map[string]string{
LicenseLabelName: l.License.UID,
},
Annotations: map[string]string{
TrialLicenseSecretName: secret.Name,
TrialLicenseSecretNamespace: secret.Namespace,
},
},
Data: map[string][]byte{
TrialPubkeyKey: pubkeyBytes,
Expand Down
1 change: 1 addition & 0 deletions pkg/controller/common/license/trial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func TestInitTrial(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
got, err := InitTrial(
tt.args.c,
"elastic-system",
corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Namespace: "elastic-system",
Expand Down
2 changes: 2 additions & 0 deletions pkg/controller/elasticsearch/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ type Client interface {
GetNodesStats(ctx context.Context) (NodesStats, error)
// GetLicense returns the currently applied license. Can be empty.
GetLicense(ctx context.Context) (License, error)
// StartTrial starts a 30-day trial license.
thbkrkr marked this conversation as resolved.
Show resolved Hide resolved
StartTrial(ctx context.Context) (StartTrialResponse, error)
// UpdateLicense attempts to update cluster license with the given licenses.
UpdateLicense(ctx context.Context, licenses LicenseUpdateRequest) (LicenseUpdateResponse, error)
// AddVotingConfigExclusions sets the transient and persistent setting of the same name in cluster settings.
Expand Down
11 changes: 11 additions & 0 deletions pkg/controller/elasticsearch/client/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,17 @@ func (lr LicenseUpdateResponse) IsSuccess() bool {
return lr.LicenseStatus == "valid"
}

// StartTrialResponse is the response to the start trial API call.
type StartTrialResponse struct {
Acknowledged bool `json:"acknowledged"`
TrialWasStarted bool `json:"trial_was_started"`
ErrorMessage string `json:"error_message"`
}

func (sr StartTrialResponse) IsSuccess() bool {
return sr.Acknowledged && sr.TrialWasStarted
}

// LicenseResponse is the response to GET _xpack/license. Licenses won't contain signature.
type LicenseResponse struct {
License License `json:"license"`
Expand Down
5 changes: 5 additions & 0 deletions pkg/controller/elasticsearch/client/v6.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ func (c *clientV6) UpdateLicense(ctx context.Context, licenses LicenseUpdateRequ
return response, c.post(ctx, "/_xpack/license", licenses, &response)
}

func (c *clientV6) StartTrial(ctx context.Context) (StartTrialResponse, error) {
var response StartTrialResponse
return response, c.post(ctx, "/_xpack/license/start_trial?acknowledge=true", nil, &response)
}

func (c *clientV6) AddVotingConfigExclusions(ctx context.Context, nodeNames []string, timeout string) error {
return errors.New("Not supported in Elasticsearch 6.x")
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/controller/elasticsearch/client/v7.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ func (c *clientV7) UpdateLicense(ctx context.Context, licenses LicenseUpdateRequ
return response, c.post(ctx, "/_license", licenses, &response)
}

func (c *clientV7) StartTrial(ctx context.Context) (StartTrialResponse, error) {
var response StartTrialResponse
return response, c.post(ctx, "/_license/start_trial?acknowledge=true", nil, &response)
}

func (c *clientV7) AddVotingConfigExclusions(ctx context.Context, nodeNames []string, timeout string) error {
if timeout == "" {
timeout = DefaultVotingConfigExclusionsTimeout
Expand Down
6 changes: 5 additions & 1 deletion pkg/controller/elasticsearch/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,17 @@ func (d *defaultDriver) Reconcile() *reconciler.Results {
results.Apply(
"reconcile-cluster-license",
func() (controller.Result, error) {
if !esReachable {
return defaultRequeue, nil
}

err := license.Reconcile(
d.Client,
d.ES,
esClient,
observedState.ClusterLicense,
)
if err != nil && esReachable {
if err != nil {
d.ReconcileState.AddEvent(
corev1.EventTypeWarning,
events.EventReasonUnexpected,
Expand Down
45 changes: 44 additions & 1 deletion pkg/controller/elasticsearch/license/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import (
"k8s.io/apimachinery/pkg/types"
)

// isTrial returns true if an Elasticsearch license is of the trial type
func isTrial(l *esclient.License) bool {
return l != nil && l.Type == string(common_license.LicenseTypeEnterpriseTrial)
}

func applyLinkedLicense(
c k8s.Client,
esCluster types.NamespacedName,
Expand All @@ -44,6 +49,12 @@ func applyLinkedLicense(
return err
}

// Shortcut if a trial license has already been started
if _, ok := license.Annotations[common_license.TrialLicenseStartedAnnotation]; ok {
log.V(1).Info("Trial license already started")
return nil
}

bytes, err := common_license.FetchLicenseData(license.Data)
if err != nil {
return err
Expand All @@ -54,7 +65,27 @@ func applyLinkedLicense(
if err != nil {
return pkgerrors.Wrap(err, "no valid license found in license secret")
}
return updater(lic)

err = updater(lic)
if err != nil {
return err
}

// Store that the trial license has been started in an annotation
if isTrial(&lic) {
if license.Annotations == nil {
license.Annotations = map[string]string{}
}
license.Annotations[common_license.TrialLicenseStartedAnnotation] = "true"

err = c.Update(&license)
thbkrkr marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
log.Info("Error when updating the license secret", "err", err.Error())
return err
}
}

return nil
}

// updateLicense make the call to Elasticsearch to set the license. This function exists mainly to facilitate testing.
Expand All @@ -73,6 +104,18 @@ func updateLicense(
}
ctx, cancel := context.WithTimeout(context.Background(), esclient.DefaultReqTimeout)
defer cancel()

if isTrial(&desired) {
response, err := c.StartTrial(ctx)
if err != nil {
return err
}
if !response.IsSuccess() {
return fmt.Errorf("failed to start trial license: %s", response.ErrorMessage)
}
return nil
}

response, err := c.UpdateLicense(ctx, request)
if err != nil {
return err
Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/elasticsearch/license/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import (
"github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1beta1"
esclient "github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch/client"
"github.com/elastic/cloud-on-k8s/pkg/utils/k8s"
logf "sigs.k8s.io/controller-runtime/pkg/log"
)

var log = logf.Log.WithName("elasticsearch-license")

// Reconcile reconciles the current Elasticsearch license with the desired one.
func Reconcile(
c k8s.Client,
Expand Down
22 changes: 12 additions & 10 deletions pkg/controller/license/trial/trial_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ type ReconcileTrials struct {
scheme *runtime.Scheme
recorder record.EventRecorder
// iteration is the number of times this controller has run its Reconcile method.
iteration int64
trialPubKey *rsa.PublicKey
iteration int64
trialPubKey *rsa.PublicKey
operatorNamespace string
}

// Reconcile watches a trial status secret. If it finds a trial license it checks whether a trial has been started.
Expand Down Expand Up @@ -78,7 +79,7 @@ func (r *ReconcileTrials) Reconcile(request reconcile.Request) (reconcile.Result

// 1. fetch trial status secret
var trialStatus corev1.Secret
err = r.Get(types.NamespacedName{Namespace: request.Namespace, Name: licensing.TrialStatusSecretKey}, &trialStatus)
err = r.Get(types.NamespacedName{Namespace: r.operatorNamespace, Name: licensing.TrialStatusSecretKey}, &trialStatus)
if errors.IsNotFound(err) {
// 2. if not present create one + finalizer
err := r.initTrial(secret, license)
Expand Down Expand Up @@ -107,7 +108,7 @@ func (r *ReconcileTrials) initTrial(secret corev1.Secret, l licensing.Enterprise
return nil
}

trialPubKey, err := licensing.InitTrial(r, secret, &l)
trialPubKey, err := licensing.InitTrial(r, r.operatorNamespace, secret, &l)
if err != nil {
return err
}
Expand All @@ -132,11 +133,12 @@ func (r *ReconcileTrials) reconcileTrialStatus(trialStatus corev1.Secret) error

}

func newReconciler(mgr manager.Manager, _ operator.Parameters) *ReconcileTrials {
func newReconciler(mgr manager.Manager, params operator.Parameters) *ReconcileTrials {
return &ReconcileTrials{
Client: k8s.WrapClient(mgr.GetClient()),
scheme: mgr.GetScheme(),
recorder: mgr.GetEventRecorderFor(name),
Client: k8s.WrapClient(mgr.GetClient()),
scheme: mgr.GetScheme(),
recorder: mgr.GetEventRecorderFor(name),
operatorNamespace: params.OperatorNamespace,
}
}

Expand Down Expand Up @@ -171,8 +173,8 @@ func add(mgr manager.Manager, r *ReconcileTrials) error {
return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
Namespace: obj.Meta.GetNamespace(),
Name: string(licensing.LicenseTypeEnterpriseTrial),
Namespace: secret.Annotations[licensing.TrialLicenseSecretNamespace],
Name: secret.Annotations[licensing.TrialLicenseSecretName],
},
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestMain(m *testing.M) {
}

func TestReconcile(t *testing.T) {
c, stop := test.StartManager(t, Add, operator.Parameters{})
c, stop := test.StartManager(t, Add, operator.Parameters{OperatorNamespace: operatorNs})
defer stop()

now := time.Now()
Expand Down
29 changes: 29 additions & 0 deletions test/e2e/es/license_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,32 @@ func TestEnterpriseLicenseSingle(t *testing.T) {
WithSteps(mutatedEsBuilder.DeletionTestSteps(k)).
RunSequential(t)
}

func TestEnterpriseTrialLicense(t *testing.T) {
esBuilder := elasticsearch.NewBuilder("test-es-trial-license").
WithESMasterDataNodes(1, elasticsearch.DefaultResources)

var licenseTestContext elasticsearch.LicenseTestContext

initStepsFn := func(k *test.K8sClient) test.StepList {
return test.StepList{
{
Name: "Create license test context",
Test: func(t *testing.T) {
licenseTestContext = elasticsearch.NewLicenseTestContext(k, esBuilder.Elasticsearch)
},
},
licenseTestContext.DeleteEnterpriseTrialLicenseSecret(),
thbkrkr marked this conversation as resolved.
Show resolved Hide resolved
licenseTestContext.CreateEnterpriseTrialLicenseSecret(),
}
}

stepsFn := func(k *test.K8sClient) test.StepList {
return test.StepList{
licenseTestContext.Init(),
licenseTestContext.CheckElasticsearchLicense(license.ElasticsearchLicenseTypeTrial),
}
}

test.Sequence(initStepsFn, stepsFn, esBuilder).RunSequential(t)
}
Loading