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:
parent
591acec8aa
commit
b5e707e2af
19 changed files with 779 additions and 71 deletions
|
@ -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
|
||||
|
|
|
@ -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,34 +184,26 @@ 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{
|
||||
|
@ -215,10 +213,43 @@ func (list *ConditionList) Update(conditionType ConditionType, status bool, reas
|
|||
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
|
||||
}
|
||||
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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,34 +184,26 @@ 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{
|
||||
|
@ -215,10 +213,43 @@ func (list *ConditionList) Update(conditionType ConditionType, status bool, reas
|
|||
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
|
||||
}
|
||||
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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
89
pkg/deployment/client/license.go
Normal file
89
pkg/deployment/client/license.go
Normal 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
|
||||
}
|
104
pkg/deployment/reconcile/action_set_condition_v2.go
Normal file
104
pkg/deployment/reconcile/action_set_condition_v2.go
Normal 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
|
||||
}
|
120
pkg/deployment/reconcile/action_set_license.go
Normal file
120
pkg/deployment/reconcile/action_set_license.go
Normal 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
|
||||
}
|
102
pkg/deployment/reconcile/action_set_member_condition_v2.go
Normal file
102
pkg/deployment/reconcile/action_set_member_condition_v2.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
89
pkg/deployment/reconcile/plan_builder_license.go
Normal file
89
pkg/deployment/reconcile/plan_builder_license.go
Normal 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")}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
27
pkg/util/arangod/license.go
Normal file
27
pkg/util/arangod/license.go
Normal 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}
|
||||
}
|
|
@ -35,6 +35,8 @@ const (
|
|||
|
||||
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
|
||||
|
|
63
pkg/util/k8sutil/license.go
Normal file
63
pkg/util/k8sutil/license.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue