1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

[Feature] License V2 for ArangoDB 3.9.0+ (#870)

This commit is contained in:
Adam Janikowski 2021-12-29 13:06:34 +01:00 committed by GitHub
parent 591acec8aa
commit b5e707e2af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 779 additions and 71 deletions

View file

@ -7,6 +7,7 @@
- Keep only recent terminations
- Add endpoint into member status
- Add debug mode (Golang DLV)
- License V2 for ArangoDB 3.9.0+
## [1.2.6](https://github.com/arangodb/kube-arangodb/tree/1.2.6) (2021-12-15)
- Add ArangoBackup backoff functionality

View file

@ -84,6 +84,9 @@ const (
// ConditionTypeTopologyAware indicates that the member is deployed with TopologyAwareness.
ConditionTypeTopologyAware ConditionType = "TopologyAware"
// ConditionTypeLicenseSet indicates that license V2 is set on cluster.
ConditionTypeLicenseSet ConditionType = "LicenseSet"
)
// Condition represents one current condition of a deployment or deployment member.
@ -102,6 +105,8 @@ type Condition struct {
Reason string `json:"reason,omitempty"`
// A human readable message indicating details about the transition.
Message string `json:"message,omitempty"`
// Hash keep propagation hash id, for example checksum of secret
Hash string `json:"hash,omitempty"`
}
func (c Condition) IsTrue() bool {
@ -139,7 +144,8 @@ func (c Condition) Equal(other Condition) bool {
util.TimeCompareEqual(c.LastUpdateTime, other.LastUpdateTime) &&
util.TimeCompareEqual(c.LastTransitionTime, other.LastTransitionTime) &&
c.Reason == other.Reason &&
c.Message == other.Message
c.Message == other.Message &&
c.Hash == other.Hash
}
// IsTrue return true when a condition with given type exists and its status is `True`.
@ -178,47 +184,72 @@ func (list *ConditionList) Touch(conditionType ConditionType) bool {
return false
}
// Update the condition, replacing an old condition with same type (if any)
// Returns true when changes were made, false otherwise.
func (list *ConditionList) Update(conditionType ConditionType, status bool, reason, message string) bool {
func (list ConditionList) Index(conditionType ConditionType) int {
for i, x := range list {
if x.Type == conditionType {
return i
}
}
return -1
}
func (list *ConditionList) update(conditionType ConditionType, status bool, reason, message, hash string) bool {
src := *list
statusX := v1.ConditionFalse
if status {
statusX = v1.ConditionTrue
}
for i, x := range src {
if x.Type == conditionType {
if x.Status != statusX {
// Transition to another status
src[i].Status = statusX
now := metav1.Now()
src[i].LastTransitionTime = now
src[i].LastUpdateTime = now
src[i].Reason = reason
src[i].Message = message
} else if x.Reason != reason || x.Message != message {
src[i].LastUpdateTime = metav1.Now()
src[i].Reason = reason
src[i].Message = message
} else {
return false
}
return true
}
index := list.Index(conditionType)
if index == -1 {
// Not found
now := metav1.Now()
*list = append(src, Condition{
Type: conditionType,
LastUpdateTime: now,
LastTransitionTime: now,
Status: statusX,
Reason: reason,
Message: message,
Hash: hash,
})
return true
}
if src[index].Status != statusX {
// Transition to another status
src[index].Status = statusX
now := metav1.Now()
src[index].LastTransitionTime = now
src[index].LastUpdateTime = now
src[index].Reason = reason
src[index].Message = message
src[index].Hash = hash
} else if src[index].Reason != reason || src[index].Message != message || src[index].Hash != hash {
src[index].LastUpdateTime = metav1.Now()
src[index].Reason = reason
src[index].Message = message
src[index].Hash = hash
} else {
return false
}
// Not found
now := metav1.Now()
*list = append(src, Condition{
Type: conditionType,
LastUpdateTime: now,
LastTransitionTime: now,
Status: statusX,
Reason: reason,
Message: message,
})
return true
}
// Update the condition, replacing an old condition with same type (if any)
// Returns true when changes were made, false otherwise.
func (list *ConditionList) Update(conditionType ConditionType, status bool, reason, message string) bool {
return list.update(conditionType, status, reason, message, "")
}
// UpdateWithHash updates the condition, replacing an old condition with same type (if any)
// Returns true when changes were made, false otherwise.
func (list *ConditionList) UpdateWithHash(conditionType ConditionType, status bool, reason, message, hash string) bool {
return list.update(conditionType, status, reason, message, hash)
}
// Remove the condition with given type.
// Returns true if removed, or false if not found.
func (list *ConditionList) Remove(conditionType ConditionType) bool {

View file

@ -49,7 +49,7 @@ func (a ActionType) String() string {
// Priority returns plan priority
func (a ActionType) Priority() ActionPriority {
switch a {
case ActionTypeMemberPhaseUpdate, ActionTypeMemberRIDUpdate, ActionTypeSetMemberCondition, ActionTypeSetCondition:
case ActionTypeMemberPhaseUpdate, ActionTypeMemberRIDUpdate, ActionTypeSetMemberCondition, ActionTypeSetCondition, ActionTypeSetMemberConditionV2:
return ActionPriorityHigh
default:
return ActionPriorityNormal
@ -161,8 +161,12 @@ const (
ActionTypeMemberPhaseUpdate ActionType = "MemberPhaseUpdate"
// ActionTypeSetMemberCondition sets member condition. It is high priority action.
ActionTypeSetMemberCondition ActionType = "SetMemberCondition"
// ActionTypeSetMemberConditionV2 sets member condition. It is high priority action.
ActionTypeSetMemberConditionV2 ActionType = "SetMemberConditionV2"
// ActionTypeSetCondition sets condition. It is high priority action.
ActionTypeSetCondition ActionType = "SetCondition"
// ActionTypeSetConditionV2 sets condition. It is high priority action.
ActionTypeSetConditionV2 ActionType = "SetConditionV2"
// ActionTypeMemberRIDUpdate updated member Run ID (UID). High priority
ActionTypeMemberRIDUpdate ActionType = "MemberRIDUpdate"
// ActionTypeArangoMemberUpdatePodSpec updates pod spec

View file

@ -84,6 +84,9 @@ const (
// ConditionTypeTopologyAware indicates that the member is deployed with TopologyAwareness.
ConditionTypeTopologyAware ConditionType = "TopologyAware"
// ConditionTypeLicenseSet indicates that license V2 is set on cluster.
ConditionTypeLicenseSet ConditionType = "LicenseSet"
)
// Condition represents one current condition of a deployment or deployment member.
@ -102,6 +105,8 @@ type Condition struct {
Reason string `json:"reason,omitempty"`
// A human readable message indicating details about the transition.
Message string `json:"message,omitempty"`
// Hash keep propagation hash id, for example checksum of secret
Hash string `json:"hash,omitempty"`
}
func (c Condition) IsTrue() bool {
@ -139,7 +144,8 @@ func (c Condition) Equal(other Condition) bool {
util.TimeCompareEqual(c.LastUpdateTime, other.LastUpdateTime) &&
util.TimeCompareEqual(c.LastTransitionTime, other.LastTransitionTime) &&
c.Reason == other.Reason &&
c.Message == other.Message
c.Message == other.Message &&
c.Hash == other.Hash
}
// IsTrue return true when a condition with given type exists and its status is `True`.
@ -178,47 +184,72 @@ func (list *ConditionList) Touch(conditionType ConditionType) bool {
return false
}
// Update the condition, replacing an old condition with same type (if any)
// Returns true when changes were made, false otherwise.
func (list *ConditionList) Update(conditionType ConditionType, status bool, reason, message string) bool {
func (list ConditionList) Index(conditionType ConditionType) int {
for i, x := range list {
if x.Type == conditionType {
return i
}
}
return -1
}
func (list *ConditionList) update(conditionType ConditionType, status bool, reason, message, hash string) bool {
src := *list
statusX := v1.ConditionFalse
if status {
statusX = v1.ConditionTrue
}
for i, x := range src {
if x.Type == conditionType {
if x.Status != statusX {
// Transition to another status
src[i].Status = statusX
now := metav1.Now()
src[i].LastTransitionTime = now
src[i].LastUpdateTime = now
src[i].Reason = reason
src[i].Message = message
} else if x.Reason != reason || x.Message != message {
src[i].LastUpdateTime = metav1.Now()
src[i].Reason = reason
src[i].Message = message
} else {
return false
}
return true
}
index := list.Index(conditionType)
if index == -1 {
// Not found
now := metav1.Now()
*list = append(src, Condition{
Type: conditionType,
LastUpdateTime: now,
LastTransitionTime: now,
Status: statusX,
Reason: reason,
Message: message,
Hash: hash,
})
return true
}
if src[index].Status != statusX {
// Transition to another status
src[index].Status = statusX
now := metav1.Now()
src[index].LastTransitionTime = now
src[index].LastUpdateTime = now
src[index].Reason = reason
src[index].Message = message
src[index].Hash = hash
} else if src[index].Reason != reason || src[index].Message != message || src[index].Hash != hash {
src[index].LastUpdateTime = metav1.Now()
src[index].Reason = reason
src[index].Message = message
src[index].Hash = hash
} else {
return false
}
// Not found
now := metav1.Now()
*list = append(src, Condition{
Type: conditionType,
LastUpdateTime: now,
LastTransitionTime: now,
Status: statusX,
Reason: reason,
Message: message,
})
return true
}
// Update the condition, replacing an old condition with same type (if any)
// Returns true when changes were made, false otherwise.
func (list *ConditionList) Update(conditionType ConditionType, status bool, reason, message string) bool {
return list.update(conditionType, status, reason, message, "")
}
// UpdateWithHash updates the condition, replacing an old condition with same type (if any)
// Returns true when changes were made, false otherwise.
func (list *ConditionList) UpdateWithHash(conditionType ConditionType, status bool, reason, message, hash string) bool {
return list.update(conditionType, status, reason, message, hash)
}
// Remove the condition with given type.
// Returns true if removed, or false if not found.
func (list *ConditionList) Remove(conditionType ConditionType) bool {

View file

@ -49,7 +49,7 @@ func (a ActionType) String() string {
// Priority returns plan priority
func (a ActionType) Priority() ActionPriority {
switch a {
case ActionTypeMemberPhaseUpdate, ActionTypeMemberRIDUpdate, ActionTypeSetMemberCondition, ActionTypeSetCondition:
case ActionTypeMemberPhaseUpdate, ActionTypeMemberRIDUpdate, ActionTypeSetMemberCondition, ActionTypeSetCondition, ActionTypeSetMemberConditionV2:
return ActionPriorityHigh
default:
return ActionPriorityNormal
@ -161,8 +161,12 @@ const (
ActionTypeMemberPhaseUpdate ActionType = "MemberPhaseUpdate"
// ActionTypeSetMemberCondition sets member condition. It is high priority action.
ActionTypeSetMemberCondition ActionType = "SetMemberCondition"
// ActionTypeSetMemberConditionV2 sets member condition. It is high priority action.
ActionTypeSetMemberConditionV2 ActionType = "SetMemberConditionV2"
// ActionTypeSetCondition sets condition. It is high priority action.
ActionTypeSetCondition ActionType = "SetCondition"
// ActionTypeSetConditionV2 sets condition. It is high priority action.
ActionTypeSetConditionV2 ActionType = "SetConditionV2"
// ActionTypeMemberRIDUpdate updated member Run ID (UID). High priority
ActionTypeMemberRIDUpdate ActionType = "MemberRIDUpdate"
// ActionTypeArangoMemberUpdatePodSpec updates pod spec

View file

@ -36,6 +36,8 @@ func NewClient(c driver.Connection) Client {
}
type Client interface {
LicenseClient
GetTLS(ctx context.Context) (TLSDetails, error)
RefreshTLS(ctx context.Context) (TLSDetails, error)

View file

@ -0,0 +1,89 @@
//
// DISCLAIMER
//
// Copyright 2016-2021 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package client
import (
"context"
"net/http"
)
const AdminLicenseUrl = "/_admin/license"
type LicenseClient interface {
GetLicense(ctx context.Context) (License, error)
SetLicense(ctx context.Context, license string, force bool) error
}
type License struct {
Hash string `json:"hash,omitempty"`
}
func (c *client) GetLicense(ctx context.Context) (License, error) {
req, err := c.c.NewRequest(http.MethodGet, AdminLicenseUrl)
if err != nil {
return License{}, err
}
resp, err := c.c.Do(ctx, req)
if err != nil {
return License{}, err
}
if err := resp.CheckStatus(http.StatusOK); err != nil {
return License{}, err
}
var l License
if err := resp.ParseBody("", &l); err != nil {
return License{}, err
}
return l, nil
}
func (c *client) SetLicense(ctx context.Context, license string, force bool) error {
req, err := c.c.NewRequest(http.MethodPut, AdminLicenseUrl)
if err != nil {
return err
}
if r, err := req.SetBody(license); err != nil {
return err
} else {
req = r
}
if force {
req = req.SetQuery("force", "true")
}
resp, err := c.c.Do(ctx, req)
if err != nil {
return err
}
if err := resp.CheckStatus(http.StatusCreated); err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,104 @@
//
// DISCLAIMER
//
// Copyright 2016-2021 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Tomasz Mielech
//
package reconcile
import (
"context"
"github.com/rs/zerolog"
core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
)
func init() {
registerAction(api.ActionTypeSetConditionV2, setConditionV2)
}
const (
setConditionActionV2KeyTypeAdd string = "add"
setConditionActionV2KeyTypeRemove string = "remove"
setConditionActionV2KeyType string = "type"
setConditionActionV2KeyAction string = "action"
setConditionActionV2KeyStatus string = "status"
setConditionActionV2KeyReason string = "reason"
setConditionActionV2KeyMessage string = "message"
setConditionActionV2KeyHash string = "hash"
)
func setConditionV2(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action {
a := &actionSetConditionV2{}
a.actionImpl = newActionImplDefRef(log, action, actionCtx, defaultTimeout)
return a
}
type actionSetConditionV2 struct {
// actionImpl implement timeout and member id functions
actionImpl
actionEmptyCheckProgress
}
// Start starts the action for changing conditions on the provided member.
func (a actionSetConditionV2) Start(ctx context.Context) (bool, error) {
at, ok := a.action.Params[setConditionActionV2KeyType]
if !ok {
a.log.Info().Msgf("key %s is missing in action definition", setConditionActionV2KeyType)
return true, nil
}
aa, ok := a.action.Params[setConditionActionV2KeyAction]
if !ok {
a.log.Info().Msgf("key %s is missing in action definition", setConditionActionV2KeyAction)
return true, nil
}
switch at {
case setConditionActionV2KeyTypeAdd:
ah := a.action.Params[setConditionActionV2KeyHash]
am := a.action.Params[setConditionActionV2KeyMessage]
ar := a.action.Params[setConditionActionV2KeyReason]
as := a.action.Params[setConditionActionV2KeyStatus] == string(core.ConditionTrue)
if err := a.actionCtx.WithStatusUpdateErr(ctx, func(s *api.DeploymentStatus) (bool, error) {
return s.Conditions.UpdateWithHash(api.ConditionType(aa), as, ar, am, ah), nil
}); err != nil {
a.log.Warn().Err(err).Msgf("unable to update status")
return true, nil
}
case setConditionActionV2KeyTypeRemove:
if err := a.actionCtx.WithStatusUpdateErr(ctx, func(s *api.DeploymentStatus) (bool, error) {
return s.Conditions.Remove(api.ConditionType(aa)), nil
}); err != nil {
a.log.Warn().Err(err).Msgf("unable to update status")
return true, nil
}
default:
a.log.Info().Msgf("unknown type %s", at)
return true, nil
}
return true, nil
}

View file

@ -0,0 +1,120 @@
//
// DISCLAIMER
//
// Copyright 2020-2021 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Adam Janikowski
//
package reconcile
import (
"context"
"github.com/arangodb/kube-arangodb/pkg/util/globals"
"github.com/arangodb/kube-arangodb/pkg/deployment/client"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/rs/zerolog"
)
func init() {
registerAction(api.ActionTypeLicenseSet, newLicenseSet)
}
func newLicenseSet(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action {
a := &licenseSetAction{}
a.actionImpl = newActionImplDefRef(log, action, actionCtx, defaultTimeout)
return a
}
type licenseSetAction struct {
actionImpl
actionEmptyCheckProgress
}
func (a *licenseSetAction) Start(ctx context.Context) (bool, error) {
log := a.log
spec := a.actionCtx.GetSpec()
if !spec.License.HasSecretName() {
log.Error().Msg("License is not set")
return true, nil
}
l, ok := k8sutil.GetLicenseFromSecret(a.actionCtx.GetCachedStatus(), spec.License.GetSecretName())
if !ok {
return true, nil
}
if !l.V2.IsV2Set() {
return true, nil
}
group := a.action.Group
m, ok := a.actionCtx.GetMemberStatusByID(a.action.MemberID)
if !ok {
log.Error().Msg("No such member")
return true, nil
}
ctxChild, cancel := globals.GetGlobals().Timeouts().ArangoD().WithTimeout(ctx)
defer cancel()
c, err := a.actionCtx.GetServerClient(ctxChild, group, m.ID)
if !ok {
log.Error().Err(err).Msg("Unable to get client")
return true, nil
}
client := client.NewClient(c.Connection())
if ok, err := licenseV2Compare(ctx, client, l.V2); err != nil {
log.Error().Err(err).Msg("Unable to verify license")
return true, nil
} else if ok {
// Already latest license
return true, nil
}
if err := client.SetLicense(ctx, string(l.V2), true); err != nil {
log.Error().Err(err).Msg("Unable to set license")
return true, nil
}
return true, nil
}
func licenseV2Compare(ctx context.Context, client client.Client, license k8sutil.License) (bool, error) {
currentLicense, err := client.GetLicense(ctx)
if err != nil {
return false, err
}
if currentLicense.Hash == license.V2Hash() {
// Already latest license
return true, nil
}
return false, nil
}

View file

@ -0,0 +1,102 @@
//
// DISCLAIMER
//
// Copyright 2016-2021 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package reconcile
import (
"context"
"github.com/rs/zerolog"
core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
)
func init() {
registerAction(api.ActionTypeSetMemberConditionV2, setMemberConditionV2)
}
func setMemberConditionV2(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action {
a := &actionSetMemberConditionV2{}
a.actionImpl = newActionImplDefRef(log, action, actionCtx, defaultTimeout)
return a
}
type actionSetMemberConditionV2 struct {
// actionImpl implement timeout and member id functions
actionImpl
actionEmptyCheckProgress
}
// Start starts the action for changing conditions on the provided member.
func (a actionSetMemberConditionV2) Start(ctx context.Context) (bool, error) {
at, ok := a.action.Params[setConditionActionV2KeyType]
if !ok {
a.log.Info().Msgf("key %s is missing in action definition", setConditionActionV2KeyType)
return true, nil
}
aa, ok := a.action.Params[setConditionActionV2KeyAction]
if !ok {
a.log.Info().Msgf("key %s is missing in action definition", setConditionActionV2KeyAction)
return true, nil
}
switch at {
case setConditionActionV2KeyTypeAdd:
ah := a.action.Params[setConditionActionV2KeyHash]
am := a.action.Params[setConditionActionV2KeyMessage]
ar := a.action.Params[setConditionActionV2KeyReason]
as := a.action.Params[setConditionActionV2KeyStatus] == string(core.ConditionTrue)
if err := a.actionCtx.WithStatusUpdateErr(ctx, func(s *api.DeploymentStatus) (bool, error) {
m, _, ok := s.Members.ElementByID(a.action.MemberID)
if !ok {
a.log.Info().Msg("can not set the condition because the member is gone already")
return false, nil
}
return m.Conditions.UpdateWithHash(api.ConditionType(aa), as, ar, am, ah), nil
}); err != nil {
a.log.Warn().Err(err).Msgf("unable to update status")
return true, nil
}
case setConditionActionV2KeyTypeRemove:
if err := a.actionCtx.WithStatusUpdateErr(ctx, func(s *api.DeploymentStatus) (bool, error) {
m, _, ok := s.Members.ElementByID(a.action.MemberID)
if !ok {
a.log.Info().Msg("can not set the condition because the member is gone already")
return false, nil
}
return m.Conditions.Remove(api.ConditionType(aa)), nil
}); err != nil {
a.log.Warn().Err(err).Msgf("unable to update status")
return true, nil
}
default:
a.log.Info().Msgf("unknown type %s", at)
return true, nil
}
return true, nil
}

View file

@ -37,6 +37,7 @@ const (
const (
BackOffCheck api.BackOffKey = "check"
LicenseCheck api.BackOffKey = "license"
)
// CreatePlan considers the current specification & status of the deployment creates a plan to

View file

@ -54,6 +54,7 @@ type PlanAppender interface {
ApplySubPlanIfEmpty(pb planBuilderSubPlan, plans ...planBuilder) PlanAppender
ApplyWithBackOff(key api.BackOffKey, delay time.Duration, pb planBuilder) PlanAppender
ApplyIfEmptyWithBackOff(key api.BackOffKey, delay time.Duration, pb planBuilder) PlanAppender
BackOff() api.BackOff
@ -75,6 +76,12 @@ func (p planAppenderRecovery) ApplyWithBackOff(key api.BackOffKey, delay time.Du
})
}
func (p planAppenderRecovery) ApplyIfEmptyWithBackOff(key api.BackOffKey, delay time.Duration, pb planBuilder) PlanAppender {
return p.create(func(in PlanAppender) PlanAppender {
return in.ApplyIfEmptyWithBackOff(key, delay, pb)
})
}
func (p planAppenderRecovery) create(ret func(in PlanAppender) PlanAppender) (r PlanAppender) {
defer func() {
if e := recover(); e != nil {
@ -150,6 +157,13 @@ func (p *planAppenderType) ApplyWithBackOff(key api.BackOffKey, delay time.Durat
return p.Apply(pb)
}
func (p *planAppenderType) ApplyIfEmptyWithBackOff(key api.BackOffKey, delay time.Duration, pb planBuilder) PlanAppender {
if p.current.IsEmpty() {
return p.ApplyWithBackOff(key, delay, pb)
}
return p
}
func (p *planAppenderType) Plan() api.Plan {
return p.current
}

View file

@ -54,6 +54,7 @@ func createHighPlan(ctx context.Context, log zerolog.Logger, apiObject k8sutil.A
ApplyIfEmpty(updateMemberUpdateConditionsPlan).
ApplyIfEmpty(updateMemberRotationConditionsPlan).
ApplyIfEmpty(createTopologyMemberUpdatePlan).
ApplyIfEmptyWithBackOff(LicenseCheck, 30*time.Second, updateClusterLicense).
ApplyIfEmpty(createTopologyMemberConditionPlan).
ApplyIfEmpty(createRebalancerCheckPlan).
ApplyWithBackOff(BackOffCheck, time.Minute, emptyPlanBuilder))

View file

@ -0,0 +1,89 @@
//
// DISCLAIMER
//
// Copyright 2016-2021 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package reconcile
import (
"context"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/client"
"github.com/arangodb/kube-arangodb/pkg/util/arangod"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
"github.com/rs/zerolog"
)
func updateClusterLicense(ctx context.Context,
log zerolog.Logger, apiObject k8sutil.APIObject,
spec api.DeploymentSpec, status api.DeploymentStatus,
cachedStatus inspectorInterface.Inspector, context PlanBuilderContext) api.Plan {
if !spec.License.HasSecretName() {
return nil
}
l, ok := k8sutil.GetLicenseFromSecret(context.GetCachedStatus(), spec.License.GetSecretName())
if !ok {
log.Trace().Str("secret", spec.Authentication.GetJWTSecretName()).Msgf("Unable to find license secret key")
return nil
}
if !l.V2.IsV2Set() {
log.Trace().Str("secret", spec.Authentication.GetJWTSecretName()).Msgf("V2 License key is not set")
return nil
}
members := status.Members.AsListInGroups(arangod.GroupsWithLicenseV2()...).Filter(func(a api.DeploymentStatusMemberElement) bool {
i := a.Member.Image
if i == nil {
return false
}
return i.ArangoDBVersion.CompareTo("3.9.0") >= 0
})
if len(members) == 0 {
// No member found to take this action
log.Trace().Msgf("No member in version 3.9.0 or above")
return nil
}
member := members[0]
c, err := context.GetServerClient(ctx, member.Group, member.Member.ID)
if err != nil {
log.Err(err).Msgf("Unable to get client")
return nil
}
internalClient := client.NewClient(c.Connection())
if ok, err := licenseV2Compare(ctx, internalClient, l.V2); err != nil {
log.Error().Err(err).Msg("Unable to verify license")
return nil
} else if ok {
if c, _ := status.Conditions.Get(api.ConditionTypeLicenseSet); !c.IsTrue() || c.Hash != l.V2.V2Hash() {
return api.Plan{updateConditionActionV2("License is set", api.ConditionTypeLicenseSet, true, "License UpToDate", "", l.V2.V2Hash())}
}
return nil
}
return api.Plan{removeConditionActionV2("License is not set", api.ConditionTypeLicenseSet), api.NewAction(api.ActionTypeLicenseSet, member.Group, member.Member.ID, "Setting license")}
}

View file

@ -25,6 +25,8 @@ package reconcile
import (
"context"
core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
@ -57,3 +59,24 @@ func emptyPlanBuilder(ctx context.Context,
cachedStatus inspectorInterface.Inspector, context PlanBuilderContext) api.Plan {
return nil
}
func removeConditionActionV2(actionReason string, conditionType api.ConditionType) api.Action {
return api.NewAction(api.ActionTypeSetConditionV2, api.ServerGroupUnknown, "", actionReason).
AddParam(setConditionActionV2KeyAction, setConditionActionV2KeyTypeRemove).
AddParam(setConditionActionV2KeyType, string(conditionType))
}
func updateConditionActionV2(actionReason string, conditionType api.ConditionType, status bool, reason, message, hash string) api.Action {
statusBool := core.ConditionTrue
if !status {
statusBool = core.ConditionFalse
}
return api.NewAction(api.ActionTypeSetConditionV2, api.ServerGroupUnknown, "", actionReason).
AddParam(setConditionActionV2KeyAction, string(conditionType)).
AddParam(setConditionActionV2KeyType, setConditionActionV2KeyTypeAdd).
AddParam(setConditionActionV2KeyStatus, string(statusBool)).
AddParam(setConditionActionV2KeyReason, reason).
AddParam(setConditionActionV2KeyMessage, message).
AddParam(setConditionActionV2KeyHash, hash)
}

View file

@ -182,7 +182,7 @@ func (a *ArangoDContainer) GetImage() string {
func (a *ArangoDContainer) GetEnvs() []core.EnvVar {
envs := NewEnvBuilder()
if a.spec.License.HasSecretName() {
if a.spec.License.HasSecretName() && a.imageInfo.ArangoDBVersion.CompareTo("3.9.0") < 0 {
env := k8sutil.CreateEnvSecretKeySelector(constants.EnvArangoLicenseKey, a.spec.License.GetSecretName(),
constants.SecretKeyToken)

View file

@ -0,0 +1,27 @@
//
// DISCLAIMER
//
// Copyright 2016-2021 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package arangod
import api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
func GroupsWithLicenseV2() api.ServerGroups {
return api.ServerGroups{api.ServerGroupSingle, api.ServerGroupDBServers, api.ServerGroupCoordinators}
}

View file

@ -33,8 +33,10 @@ const (
EnvArangoLicenseKey = "ARANGO_LICENSE_KEY" // Contains the License Key for the Docker Image
EnvArangoSyncMonitoringToken = "ARANGOSYNC_MONITORING_TOKEN" // Constains monitoring token for ArangoSync servers
SecretEncryptionKey = "key" // Key in a Secret.Data used to store an 32-byte encryption key
SecretKeyToken = "token" // Key inside a Secret used to hold a JWT or monitoring token
SecretEncryptionKey = "key" // Key in a Secret.Data used to store an 32-byte encryption key
SecretKeyToken = "token" // Key inside a Secret used to hold a JWT or monitoring token
SecretKeyV2Token = "token-v2" // Key inside a Secret used to hold a License in V2 Format
SecretKeyV2License = "license-v2" // Key inside a Secret used to hold a License in V2 Format
SecretCACertificate = "ca.crt" // Key in Secret.data used to store a PEM encoded CA certificate (public key)
SecretCAKey = "ca.key" // Key in Secret.data used to store a PEM encoded CA private key

View file

@ -0,0 +1,63 @@
//
// DISCLAIMER
//
// Copyright 2016-2021 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package k8sutil
import (
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/secret"
)
type License string
func (l License) IsV2Set() bool {
return l != ""
}
func (l License) V2Hash() string {
return util.SHA256FromString(string(l))
}
type LicenseSecret struct {
V1 string
V2 License
}
func GetLicenseFromSecret(secret secret.Inspector, name string) (LicenseSecret, bool) {
s, ok := secret.Secret(name)
if !ok {
return LicenseSecret{}, false
}
var l LicenseSecret
if v, ok := s.Data[constants.SecretKeyToken]; ok {
l.V1 = string(v)
}
if v1, ok1 := s.Data[constants.SecretKeyV2License]; ok1 {
l.V2 = License(v1)
} else if v2, ok2 := s.Data[constants.SecretKeyV2Token]; ok2 {
l.V2 = License(v2)
}
return l, true
}