mirror of
https://github.com/arangodb/kube-arangodb.git
synced 2024-12-14 11:57:37 +00:00
[Feature] Container runtime image update (#787)
This commit is contained in:
parent
c65c8d973d
commit
13f3e2a09b
34 changed files with 1153 additions and 62 deletions
|
@ -3,6 +3,7 @@
|
||||||
## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
|
## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
|
||||||
- Update UBI Image to 8.4
|
- Update UBI Image to 8.4
|
||||||
- Fix ArangoSync Liveness Prove
|
- Fix ArangoSync Liveness Prove
|
||||||
|
- Allow runtime update of Sidecar images
|
||||||
|
|
||||||
## [1.2.2](https://github.com/arangodb/kube-arangodb/tree/1.2.2) (2021-09-09)
|
## [1.2.2](https://github.com/arangodb/kube-arangodb/tree/1.2.2) (2021-09-09)
|
||||||
- Update 'github.com/arangodb/arangosync-client' dependency to v0.7.0
|
- Update 'github.com/arangodb/arangosync-client' dependency to v0.7.0
|
||||||
|
|
|
@ -36,10 +36,16 @@ func GetArangoMemberPodTemplate(pod *core.PodTemplateSpec, podSpecChecksum strin
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checksum := fmt.Sprintf("%0x", sha256.Sum256(data))
|
||||||
|
|
||||||
|
if podSpecChecksum == "" {
|
||||||
|
podSpecChecksum = checksum
|
||||||
|
}
|
||||||
|
|
||||||
return &ArangoMemberPodTemplate{
|
return &ArangoMemberPodTemplate{
|
||||||
PodSpec: pod,
|
PodSpec: pod,
|
||||||
PodSpecChecksum: podSpecChecksum,
|
PodSpecChecksum: podSpecChecksum,
|
||||||
Checksum: fmt.Sprintf("%0x", sha256.Sum256(data)),
|
Checksum: checksum,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +55,13 @@ type ArangoMemberPodTemplate struct {
|
||||||
Checksum string `json:"checksum,omitempty"`
|
Checksum string `json:"checksum,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ArangoMemberPodTemplate) GetChecksum() string {
|
||||||
|
if a == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return a.Checksum
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ArangoMemberPodTemplate) Equals(b *ArangoMemberPodTemplate) bool {
|
func (a *ArangoMemberPodTemplate) Equals(b *ArangoMemberPodTemplate) bool {
|
||||||
if a == nil && b == nil {
|
if a == nil && b == nil {
|
||||||
return true
|
return true
|
||||||
|
@ -58,7 +71,7 @@ func (a *ArangoMemberPodTemplate) Equals(b *ArangoMemberPodTemplate) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.Checksum == b.Checksum && a.PodSpecChecksum == b.PodSpecChecksum
|
return a.Checksum == b.Checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArangoMemberPodTemplate) RotationNeeded(b *ArangoMemberPodTemplate) bool {
|
func (a *ArangoMemberPodTemplate) RotationNeeded(b *ArangoMemberPodTemplate) bool {
|
||||||
|
@ -70,7 +83,7 @@ func (a *ArangoMemberPodTemplate) RotationNeeded(b *ArangoMemberPodTemplate) boo
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.PodSpecChecksum != b.PodSpecChecksum
|
return a.Checksum != b.Checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArangoMemberPodTemplate) EqualPodSpecChecksum(checksum string) bool {
|
func (a *ArangoMemberPodTemplate) EqualPodSpecChecksum(checksum string) bool {
|
||||||
|
|
|
@ -22,16 +22,8 @@
|
||||||
|
|
||||||
package v1
|
package v1
|
||||||
|
|
||||||
const (
|
|
||||||
ArangoMemberConditionPendingRestart ConditionType = "pending-restart"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ArangoMemberStatus struct {
|
type ArangoMemberStatus struct {
|
||||||
Conditions ConditionList `json:"conditions,omitempty"`
|
Conditions ConditionList `json:"conditions,omitempty"`
|
||||||
|
|
||||||
Template *ArangoMemberPodTemplate `json:"template,omitempty"`
|
Template *ArangoMemberPodTemplate `json:"template,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ArangoMemberStatus) IsPendingRestart() bool {
|
|
||||||
return a.Conditions.IsTrue(ArangoMemberConditionPendingRestart)
|
|
||||||
}
|
|
||||||
|
|
|
@ -77,6 +77,12 @@ const (
|
||||||
ConditionTypeRestart ConditionType = "Restart"
|
ConditionTypeRestart ConditionType = "Restart"
|
||||||
// ConditionTypePendingTLSRotation indicates that TLS rotation is pending
|
// ConditionTypePendingTLSRotation indicates that TLS rotation is pending
|
||||||
ConditionTypePendingTLSRotation ConditionType = "PendingTLSRotation"
|
ConditionTypePendingTLSRotation ConditionType = "PendingTLSRotation"
|
||||||
|
// ConditionTypePendingUpdate indicates that runtime update is pending
|
||||||
|
ConditionTypePendingUpdate ConditionType = "PendingUpdate"
|
||||||
|
// ConditionTypeUpdating indicates that runtime update is in progress
|
||||||
|
ConditionTypeUpdating ConditionType = "Updating"
|
||||||
|
// ConditionTypeUpdateFailed indicates that runtime update failed
|
||||||
|
ConditionTypeUpdateFailed ConditionType = "UpdateFailed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Condition represents one current condition of a deployment or deployment member.
|
// Condition represents one current condition of a deployment or deployment member.
|
||||||
|
|
|
@ -165,6 +165,10 @@ const (
|
||||||
ActionTypeArangoMemberUpdatePodSpec ActionType = "ArangoMemberUpdatePodSpec"
|
ActionTypeArangoMemberUpdatePodSpec ActionType = "ArangoMemberUpdatePodSpec"
|
||||||
// ActionTypeArangoMemberUpdatePodStatus updates pod spec
|
// ActionTypeArangoMemberUpdatePodStatus updates pod spec
|
||||||
ActionTypeArangoMemberUpdatePodStatus ActionType = "ArangoMemberUpdatePodStatus"
|
ActionTypeArangoMemberUpdatePodStatus ActionType = "ArangoMemberUpdatePodStatus"
|
||||||
|
|
||||||
|
// Runtime Updates
|
||||||
|
// ActionTypeRuntimeContainerImageUpdate updates container image in runtime
|
||||||
|
ActionTypeRuntimeContainerImageUpdate ActionType = "RuntimeContainerImageUpdate"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -245,6 +249,29 @@ func NewAction(actionType ActionType, group ServerGroup, memberID string, reason
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActionBuilder allows to generate actions based on predefined group and member id
|
||||||
|
type ActionBuilder interface {
|
||||||
|
// NewAction instantiates a new Action.
|
||||||
|
NewAction(actionType ActionType, reason ...string) Action
|
||||||
|
}
|
||||||
|
|
||||||
|
type actionBuilder struct {
|
||||||
|
group ServerGroup
|
||||||
|
memberID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a actionBuilder) NewAction(actionType ActionType, reason ...string) Action {
|
||||||
|
return NewAction(actionType, a.group, a.memberID, reason...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewActionBuilder create new action builder with provided group and id
|
||||||
|
func NewActionBuilder(group ServerGroup, memberID string) ActionBuilder {
|
||||||
|
return actionBuilder{
|
||||||
|
group: group,
|
||||||
|
memberID: memberID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetImage sets the Image field to the given value and returns the modified
|
// SetImage sets the Image field to the given value and returns the modified
|
||||||
// action.
|
// action.
|
||||||
func (a Action) SetImage(image string) Action {
|
func (a Action) SetImage(image string) Action {
|
||||||
|
|
|
@ -33,9 +33,14 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Timeouts struct {
|
type Timeouts struct {
|
||||||
|
// AddMember action timeout
|
||||||
AddMember *Timeout `json:"addMember,omitempty"`
|
AddMember *Timeout `json:"addMember,omitempty"`
|
||||||
|
|
||||||
|
// MaintenanceGracePeriod action timeout
|
||||||
MaintenanceGracePeriod *Timeout `json:"maintenanceGracePeriod,omitempty"`
|
MaintenanceGracePeriod *Timeout `json:"maintenanceGracePeriod,omitempty"`
|
||||||
|
|
||||||
|
// RuntimeContainerImageUpdate action timeout
|
||||||
|
RuntimeContainerImageUpdate *Timeout `json:"runtimeContainerImageUpdate,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Timeouts) GetMaintenanceGracePeriod() time.Duration {
|
func (t *Timeouts) GetMaintenanceGracePeriod() time.Duration {
|
||||||
|
|
5
pkg/apis/deployment/v1/zz_generated.deepcopy.go
generated
5
pkg/apis/deployment/v1/zz_generated.deepcopy.go
generated
|
@ -2136,6 +2136,11 @@ func (in *Timeouts) DeepCopyInto(out *Timeouts) {
|
||||||
*out = new(Timeout)
|
*out = new(Timeout)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.RuntimeContainerImageUpdate != nil {
|
||||||
|
in, out := &in.RuntimeContainerImageUpdate, &out.RuntimeContainerImageUpdate
|
||||||
|
*out = new(Timeout)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,10 +36,16 @@ func GetArangoMemberPodTemplate(pod *core.PodTemplateSpec, podSpecChecksum strin
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checksum := fmt.Sprintf("%0x", sha256.Sum256(data))
|
||||||
|
|
||||||
|
if podSpecChecksum == "" {
|
||||||
|
podSpecChecksum = checksum
|
||||||
|
}
|
||||||
|
|
||||||
return &ArangoMemberPodTemplate{
|
return &ArangoMemberPodTemplate{
|
||||||
PodSpec: pod,
|
PodSpec: pod,
|
||||||
PodSpecChecksum: podSpecChecksum,
|
PodSpecChecksum: podSpecChecksum,
|
||||||
Checksum: fmt.Sprintf("%0x", sha256.Sum256(data)),
|
Checksum: checksum,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +55,13 @@ type ArangoMemberPodTemplate struct {
|
||||||
Checksum string `json:"checksum,omitempty"`
|
Checksum string `json:"checksum,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ArangoMemberPodTemplate) GetChecksum() string {
|
||||||
|
if a == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return a.Checksum
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ArangoMemberPodTemplate) Equals(b *ArangoMemberPodTemplate) bool {
|
func (a *ArangoMemberPodTemplate) Equals(b *ArangoMemberPodTemplate) bool {
|
||||||
if a == nil && b == nil {
|
if a == nil && b == nil {
|
||||||
return true
|
return true
|
||||||
|
@ -58,7 +71,7 @@ func (a *ArangoMemberPodTemplate) Equals(b *ArangoMemberPodTemplate) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.Checksum == b.Checksum && a.PodSpecChecksum == b.PodSpecChecksum
|
return a.Checksum == b.Checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArangoMemberPodTemplate) RotationNeeded(b *ArangoMemberPodTemplate) bool {
|
func (a *ArangoMemberPodTemplate) RotationNeeded(b *ArangoMemberPodTemplate) bool {
|
||||||
|
@ -70,7 +83,7 @@ func (a *ArangoMemberPodTemplate) RotationNeeded(b *ArangoMemberPodTemplate) boo
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.PodSpecChecksum != b.PodSpecChecksum
|
return a.Checksum != b.Checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArangoMemberPodTemplate) EqualPodSpecChecksum(checksum string) bool {
|
func (a *ArangoMemberPodTemplate) EqualPodSpecChecksum(checksum string) bool {
|
||||||
|
|
|
@ -22,16 +22,8 @@
|
||||||
|
|
||||||
package v2alpha1
|
package v2alpha1
|
||||||
|
|
||||||
const (
|
|
||||||
ArangoMemberConditionPendingRestart ConditionType = "pending-restart"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ArangoMemberStatus struct {
|
type ArangoMemberStatus struct {
|
||||||
Conditions ConditionList `json:"conditions,omitempty"`
|
Conditions ConditionList `json:"conditions,omitempty"`
|
||||||
|
|
||||||
Template *ArangoMemberPodTemplate `json:"template,omitempty"`
|
Template *ArangoMemberPodTemplate `json:"template,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ArangoMemberStatus) IsPendingRestart() bool {
|
|
||||||
return a.Conditions.IsTrue(ArangoMemberConditionPendingRestart)
|
|
||||||
}
|
|
||||||
|
|
|
@ -77,6 +77,12 @@ const (
|
||||||
ConditionTypeRestart ConditionType = "Restart"
|
ConditionTypeRestart ConditionType = "Restart"
|
||||||
// ConditionTypePendingTLSRotation indicates that TLS rotation is pending
|
// ConditionTypePendingTLSRotation indicates that TLS rotation is pending
|
||||||
ConditionTypePendingTLSRotation ConditionType = "PendingTLSRotation"
|
ConditionTypePendingTLSRotation ConditionType = "PendingTLSRotation"
|
||||||
|
// ConditionTypePendingUpdate indicates that runtime update is pending
|
||||||
|
ConditionTypePendingUpdate ConditionType = "PendingUpdate"
|
||||||
|
// ConditionTypeUpdating indicates that runtime update is in progress
|
||||||
|
ConditionTypeUpdating ConditionType = "Updating"
|
||||||
|
// ConditionTypeUpdateFailed indicates that runtime update failed
|
||||||
|
ConditionTypeUpdateFailed ConditionType = "UpdateFailed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Condition represents one current condition of a deployment or deployment member.
|
// Condition represents one current condition of a deployment or deployment member.
|
||||||
|
|
|
@ -165,6 +165,10 @@ const (
|
||||||
ActionTypeArangoMemberUpdatePodSpec ActionType = "ArangoMemberUpdatePodSpec"
|
ActionTypeArangoMemberUpdatePodSpec ActionType = "ArangoMemberUpdatePodSpec"
|
||||||
// ActionTypeArangoMemberUpdatePodStatus updates pod spec
|
// ActionTypeArangoMemberUpdatePodStatus updates pod spec
|
||||||
ActionTypeArangoMemberUpdatePodStatus ActionType = "ArangoMemberUpdatePodStatus"
|
ActionTypeArangoMemberUpdatePodStatus ActionType = "ArangoMemberUpdatePodStatus"
|
||||||
|
|
||||||
|
// Runtime Updates
|
||||||
|
// ActionTypeRuntimeContainerImageUpdate updates container image in runtime
|
||||||
|
ActionTypeRuntimeContainerImageUpdate ActionType = "RuntimeContainerImageUpdate"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -245,6 +249,29 @@ func NewAction(actionType ActionType, group ServerGroup, memberID string, reason
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActionBuilder allows to generate actions based on predefined group and member id
|
||||||
|
type ActionBuilder interface {
|
||||||
|
// NewAction instantiates a new Action.
|
||||||
|
NewAction(actionType ActionType, reason ...string) Action
|
||||||
|
}
|
||||||
|
|
||||||
|
type actionBuilder struct {
|
||||||
|
group ServerGroup
|
||||||
|
memberID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a actionBuilder) NewAction(actionType ActionType, reason ...string) Action {
|
||||||
|
return NewAction(actionType, a.group, a.memberID, reason...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewActionBuilder create new action builder with provided group and id
|
||||||
|
func NewActionBuilder(group ServerGroup, memberID string) ActionBuilder {
|
||||||
|
return actionBuilder{
|
||||||
|
group: group,
|
||||||
|
memberID: memberID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetImage sets the Image field to the given value and returns the modified
|
// SetImage sets the Image field to the given value and returns the modified
|
||||||
// action.
|
// action.
|
||||||
func (a Action) SetImage(image string) Action {
|
func (a Action) SetImage(image string) Action {
|
||||||
|
|
|
@ -33,9 +33,14 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Timeouts struct {
|
type Timeouts struct {
|
||||||
|
// AddMember action timeout
|
||||||
AddMember *Timeout `json:"addMember,omitempty"`
|
AddMember *Timeout `json:"addMember,omitempty"`
|
||||||
|
|
||||||
|
// MaintenanceGracePeriod action timeout
|
||||||
MaintenanceGracePeriod *Timeout `json:"maintenanceGracePeriod,omitempty"`
|
MaintenanceGracePeriod *Timeout `json:"maintenanceGracePeriod,omitempty"`
|
||||||
|
|
||||||
|
// RuntimeContainerImageUpdate action timeout
|
||||||
|
RuntimeContainerImageUpdate *Timeout `json:"runtimeContainerImageUpdate,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Timeouts) GetMaintenanceGracePeriod() time.Duration {
|
func (t *Timeouts) GetMaintenanceGracePeriod() time.Duration {
|
||||||
|
|
|
@ -2136,6 +2136,11 @@ func (in *Timeouts) DeepCopyInto(out *Timeouts) {
|
||||||
*out = new(Timeout)
|
*out = new(Timeout)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.RuntimeContainerImageUpdate != nil {
|
||||||
|
in, out := &in.RuntimeContainerImageUpdate, &out.RuntimeContainerImageUpdate
|
||||||
|
*out = new(Timeout)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,22 @@ type Action interface {
|
||||||
MemberID() string
|
MemberID() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActionPost keep interface which is executed after action is completed.
|
||||||
|
type ActionPost interface {
|
||||||
|
Action
|
||||||
|
|
||||||
|
// Post execute after action is completed
|
||||||
|
Post(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActionPost(a Action, ctx context.Context) error {
|
||||||
|
if c, ok := a.(ActionPost); !ok {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return c.Post(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ActionReloadCachedStatus keeps information about CachedStatus reloading (executed after action has been executed)
|
// ActionReloadCachedStatus keeps information about CachedStatus reloading (executed after action has been executed)
|
||||||
type ActionReloadCachedStatus interface {
|
type ActionReloadCachedStatus interface {
|
||||||
Action
|
Action
|
||||||
|
|
|
@ -36,6 +36,10 @@ func init() {
|
||||||
registerAction(api.ActionTypeArangoMemberUpdatePodStatus, newArangoMemberUpdatePodStatusAction)
|
registerAction(api.ActionTypeArangoMemberUpdatePodStatus, newArangoMemberUpdatePodStatusAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ActionTypeArangoMemberUpdatePodStatusChecksum = "checksum"
|
||||||
|
)
|
||||||
|
|
||||||
// newArangoMemberUpdatePodStatusAction creates a new Action that implements the given
|
// newArangoMemberUpdatePodStatusAction creates a new Action that implements the given
|
||||||
// planned ArangoMemberUpdatePodStatus action.
|
// planned ArangoMemberUpdatePodStatus action.
|
||||||
func newArangoMemberUpdatePodStatusAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action {
|
func newArangoMemberUpdatePodStatusAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action {
|
||||||
|
@ -74,6 +78,17 @@ func (a *actionArangoMemberUpdatePodStatus) Start(ctx context.Context) (bool, er
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c, ok := a.action.GetParam(ActionTypeArangoMemberUpdatePodStatusChecksum); ok {
|
||||||
|
if member.Spec.Template == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if member.Spec.Template.Checksum != c {
|
||||||
|
// Checksum is invalid
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if member.Status.Template == nil || !member.Status.Template.Equals(member.Spec.Template) {
|
if member.Status.Template == nil || !member.Status.Template.Equals(member.Spec.Template) {
|
||||||
if err := a.actionCtx.WithArangoMemberStatusUpdate(context.Background(), member.GetNamespace(), member.GetName(), func(obj *api.ArangoMember, status *api.ArangoMemberStatus) bool {
|
if err := a.actionCtx.WithArangoMemberStatusUpdate(context.Background(), member.GetNamespace(), member.GetName(), func(obj *api.ArangoMember, status *api.ArangoMemberStatus) bool {
|
||||||
if status.Template == nil || !status.Template.Equals(member.Spec.Template) {
|
if status.Template == nil || !status.Template.Equals(member.Spec.Template) {
|
||||||
|
|
|
@ -26,6 +26,10 @@ package reconcile
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned"
|
||||||
|
monitoringClient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
|
||||||
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
|
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
|
||||||
|
|
||||||
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
|
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
|
||||||
|
@ -53,6 +57,7 @@ type ActionContext interface {
|
||||||
resources.DeploymentAgencyMaintenance
|
resources.DeploymentAgencyMaintenance
|
||||||
resources.ArangoMemberContext
|
resources.ArangoMemberContext
|
||||||
resources.DeploymentPodRenderer
|
resources.DeploymentPodRenderer
|
||||||
|
resources.DeploymentCLIGetter
|
||||||
|
|
||||||
// GetAPIObject returns the deployment as k8s object.
|
// GetAPIObject returns the deployment as k8s object.
|
||||||
GetAPIObject() k8sutil.APIObject
|
GetAPIObject() k8sutil.APIObject
|
||||||
|
@ -163,6 +168,18 @@ type actionContext struct {
|
||||||
cachedStatus inspectorInterface.Inspector
|
cachedStatus inspectorInterface.Inspector
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ac *actionContext) GetKubeCli() kubernetes.Interface {
|
||||||
|
return ac.context.GetKubeCli()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *actionContext) GetMonitoringV1Cli() monitoringClient.MonitoringV1Interface {
|
||||||
|
return ac.context.GetMonitoringV1Cli()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *actionContext) GetArangoCli() versioned.Interface {
|
||||||
|
return ac.context.GetArangoCli()
|
||||||
|
}
|
||||||
|
|
||||||
func (ac *actionContext) RenderPodForMemberFromCurrent(ctx context.Context, cachedStatus inspectorInterface.Inspector, memberID string) (*core.Pod, error) {
|
func (ac *actionContext) RenderPodForMemberFromCurrent(ctx context.Context, cachedStatus inspectorInterface.Inspector, memberID string) (*core.Pod, error) {
|
||||||
return ac.context.RenderPodForMemberFromCurrent(ctx, cachedStatus, memberID)
|
return ac.context.RenderPodForMemberFromCurrent(ctx, cachedStatus, memberID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,10 @@ func newActionImpl(log zerolog.Logger, action api.Action, actionCtx ActionContex
|
||||||
return newBaseActionImpl(log, action, actionCtx, NewTimeoutFetcher(timeout), memberIDRef)
|
return newBaseActionImpl(log, action, actionCtx, NewTimeoutFetcher(timeout), memberIDRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newBaseActionImplDefRef(log zerolog.Logger, action api.Action, actionCtx ActionContext, timeout TimeoutFetcher) actionImpl {
|
||||||
|
return newBaseActionImpl(log, action, actionCtx, timeout, &action.MemberID)
|
||||||
|
}
|
||||||
|
|
||||||
func newBaseActionImpl(log zerolog.Logger, action api.Action, actionCtx ActionContext, timeout TimeoutFetcher, memberIDRef *string) actionImpl {
|
func newBaseActionImpl(log zerolog.Logger, action api.Action, actionCtx ActionContext, timeout TimeoutFetcher, memberIDRef *string) actionImpl {
|
||||||
if memberIDRef == nil {
|
if memberIDRef == nil {
|
||||||
panic("Action cannot have nil reference to member!")
|
panic("Action cannot have nil reference to member!")
|
||||||
|
|
|
@ -0,0 +1,281 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 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"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/deployment/rotation"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerAction(api.ActionTypeRuntimeContainerImageUpdate, runtimeContainerImageUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runtimeContainerImageUpdate(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action {
|
||||||
|
a := &actionRuntimeContainerImageUpdate{}
|
||||||
|
|
||||||
|
a.actionImpl = newBaseActionImplDefRef(log, action, actionCtx, func(deploymentSpec api.DeploymentSpec) time.Duration {
|
||||||
|
return deploymentSpec.Timeouts.Get().AddMember.Get(defaultTimeout)
|
||||||
|
})
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ActionReloadCachedStatus = &actionRuntimeContainerImageUpdate{}
|
||||||
|
var _ ActionPost = &actionRuntimeContainerImageUpdate{}
|
||||||
|
|
||||||
|
type actionRuntimeContainerImageUpdate struct {
|
||||||
|
// actionImpl implement timeout and member id functions
|
||||||
|
actionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a actionRuntimeContainerImageUpdate) Post(ctx context.Context) error {
|
||||||
|
a.log.Info().Msgf("Updating container image")
|
||||||
|
m, ok := a.actionCtx.GetMemberStatusByID(a.action.MemberID)
|
||||||
|
if !ok {
|
||||||
|
a.log.Info().Msg("member is gone already")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name, image, ok := a.getContainerDetails()
|
||||||
|
if !ok {
|
||||||
|
a.log.Info().Msg("Unable to find container details")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
member, ok := a.actionCtx.GetCachedStatus().ArangoMember(m.ArangoMemberName(a.actionCtx.GetName(), a.action.Group))
|
||||||
|
if !ok {
|
||||||
|
err := errors.Newf("ArangoMember not found")
|
||||||
|
a.log.Error().Err(err).Msg("ArangoMember not found")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.actionCtx.WithArangoMemberStatusUpdate(ctx, member.GetNamespace(), member.GetName(), func(obj *api.ArangoMember, s *api.ArangoMemberStatus) bool {
|
||||||
|
if obj.Spec.Template == nil || s.Template == nil ||
|
||||||
|
obj.Spec.Template.PodSpec == nil || s.Template.PodSpec == nil {
|
||||||
|
a.log.Info().Msgf("Nil Member definition")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(obj.Spec.Template.PodSpec.Spec.Containers) != len(s.Template.PodSpec.Spec.Containers) {
|
||||||
|
a.log.Info().Msgf("Invalid size of containers")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for id := range obj.Spec.Template.PodSpec.Spec.Containers {
|
||||||
|
if obj.Spec.Template.PodSpec.Spec.Containers[id].Name == name {
|
||||||
|
if s.Template.PodSpec.Spec.Containers[id].Name != name {
|
||||||
|
a.log.Info().Msgf("Invalid order of containers")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Spec.Template.PodSpec.Spec.Containers[id].Image != image {
|
||||||
|
a.log.Info().Str("got", obj.Spec.Template.PodSpec.Spec.Containers[id].Image).Str("expected", image).Msgf("Invalid spec image of container")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Template.PodSpec.Spec.Containers[id].Image != image {
|
||||||
|
s.Template.PodSpec.Spec.Containers[id].Image = image
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a actionRuntimeContainerImageUpdate) ReloadCachedStatus() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a actionRuntimeContainerImageUpdate) getContainerDetails() (string, string, bool) {
|
||||||
|
container, ok := a.action.GetParam(rotation.ContainerName)
|
||||||
|
if !ok {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
image, ok := a.action.GetParam(rotation.ContainerImage)
|
||||||
|
if !ok {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return container, image, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the action for changing conditions on the provided member.
|
||||||
|
func (a actionRuntimeContainerImageUpdate) Start(ctx context.Context) (bool, error) {
|
||||||
|
m, ok := a.actionCtx.GetMemberStatusByID(a.action.MemberID)
|
||||||
|
if !ok {
|
||||||
|
a.log.Info().Msg("member is gone already")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name, image, ok := a.getContainerDetails()
|
||||||
|
if !ok {
|
||||||
|
a.log.Info().Msg("Unable to find container details")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.Phase.IsReady() {
|
||||||
|
a.log.Info().Msg("Member is not ready, unable to run update operation")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
member, ok := a.actionCtx.GetCachedStatus().ArangoMember(m.ArangoMemberName(a.actionCtx.GetName(), a.action.Group))
|
||||||
|
if !ok {
|
||||||
|
err := errors.Newf("ArangoMember not found")
|
||||||
|
a.log.Error().Err(err).Msg("ArangoMember not found")
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pod, ok := a.actionCtx.GetCachedStatus().Pod(m.PodName)
|
||||||
|
if !ok {
|
||||||
|
a.log.Info().Msg("pod is not present")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if member.Spec.Template == nil || member.Spec.Template.PodSpec == nil {
|
||||||
|
a.log.Info().Msg("pod spec is not present")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if member.Status.Template == nil || member.Status.Template.PodSpec == nil {
|
||||||
|
a.log.Info().Msg("pod status is not present")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pod.Spec.Containers) != len(member.Spec.Template.PodSpec.Spec.Containers) {
|
||||||
|
a.log.Info().Msg("spec container count is not equal")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pod.Spec.Containers) != len(member.Status.Template.PodSpec.Spec.Containers) {
|
||||||
|
a.log.Info().Msg("status container count is not equal")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := member.Spec.Template.PodSpec
|
||||||
|
status := member.Status.Template.PodSpec
|
||||||
|
|
||||||
|
for id := range pod.Spec.Containers {
|
||||||
|
if pod.Spec.Containers[id].Name != spec.Spec.Containers[id].Name ||
|
||||||
|
pod.Spec.Containers[id].Name != status.Spec.Containers[id].Name ||
|
||||||
|
pod.Spec.Containers[id].Name != name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if pod.Spec.Containers[id].Image != image {
|
||||||
|
// Update pod image
|
||||||
|
pod.Spec.Containers[id].Image = image
|
||||||
|
|
||||||
|
if _, err := a.actionCtx.GetKubeCli().CoreV1().Pods(pod.GetNamespace()).Update(ctx, pod, v1.UpdateOptions{}); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start wait action
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a actionRuntimeContainerImageUpdate) CheckProgress(ctx context.Context) (bool, bool, error) {
|
||||||
|
|
||||||
|
a.log.Info().Msgf("Update Progress")
|
||||||
|
m, ok := a.actionCtx.GetMemberStatusByID(a.action.MemberID)
|
||||||
|
if !ok {
|
||||||
|
a.log.Info().Msg("member is gone already")
|
||||||
|
return true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pod, ok := a.actionCtx.GetCachedStatus().Pod(m.PodName)
|
||||||
|
if !ok {
|
||||||
|
a.log.Info().Msg("pod is not present")
|
||||||
|
return true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name, image, ok := a.getContainerDetails()
|
||||||
|
if !ok {
|
||||||
|
a.log.Info().Msg("Unable to find container details")
|
||||||
|
return true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cspec, ok := k8sutil.GetContainerByName(pod, name)
|
||||||
|
if !ok {
|
||||||
|
a.log.Info().Msg("Unable to find container spec")
|
||||||
|
return true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cstatus, ok := k8sutil.GetContainerStatusByName(pod, name)
|
||||||
|
if !ok {
|
||||||
|
a.log.Info().Msg("Unable to find container status")
|
||||||
|
return true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if cspec.Image != image {
|
||||||
|
a.log.Info().Msg("Image changed")
|
||||||
|
return true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if s := cstatus.State.Terminated; s != nil {
|
||||||
|
// We are in terminated state
|
||||||
|
// Image is changed after start
|
||||||
|
if cspec.Image != cstatus.Image {
|
||||||
|
// Image not yet updated, retry soon
|
||||||
|
return false, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pod wont get up and running
|
||||||
|
return true, false, errors.Newf("Container %s failed during image replacement: (%d) %s: %s", name, s.ExitCode, s.Reason, s.Message)
|
||||||
|
} else if s := cstatus.State.Waiting; s != nil {
|
||||||
|
// Pod is still pulling image or pending for pod start
|
||||||
|
return false, false, nil
|
||||||
|
} else if s := cstatus.State.Running; s != nil {
|
||||||
|
// Image is changed after restart
|
||||||
|
if cspec.Image != cstatus.Image {
|
||||||
|
// Image not yet updated, retry soon
|
||||||
|
return false, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if k8sutil.IsPodReady(pod) {
|
||||||
|
// Pod is alive again
|
||||||
|
return true, false, nil
|
||||||
|
}
|
||||||
|
return false, false, nil
|
||||||
|
} else {
|
||||||
|
// Unknown state
|
||||||
|
return false, false, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,15 +65,21 @@ func (a actionSetMemberCondition) Start(ctx context.Context) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for condition, value := range a.action.Params {
|
for condition, value := range a.action.Params {
|
||||||
set, err := strconv.ParseBool(value)
|
if value == "" {
|
||||||
if err != nil {
|
a.log.Debug().Msg("remove the condition")
|
||||||
a.log.Error().Err(err).Str("value", value).Msg("can not parse string to boolean")
|
|
||||||
continue
|
m.Conditions.Remove(api.ConditionType(condition))
|
||||||
|
} else {
|
||||||
|
set, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
a.log.Error().Err(err).Str("value", value).Msg("can not parse string to boolean")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
a.log.Debug().Msg("set the condition")
|
||||||
|
|
||||||
|
m.Conditions.Update(api.ConditionType(condition), set, a.action.Reason, "action set the member condition")
|
||||||
}
|
}
|
||||||
|
|
||||||
a.log.Debug().Msg("set the condition")
|
|
||||||
|
|
||||||
m.Conditions.Update(api.ConditionType(condition), set, a.action.Reason, "action set the member condition")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.actionCtx.UpdateMember(ctx, m); err != nil {
|
if err := a.actionCtx.UpdateMember(ctx, m); err != nil {
|
||||||
|
|
|
@ -46,6 +46,7 @@ type Context interface {
|
||||||
resources.ArangoMemberContext
|
resources.ArangoMemberContext
|
||||||
resources.DeploymentPodRenderer
|
resources.DeploymentPodRenderer
|
||||||
resources.DeploymentImageManager
|
resources.DeploymentImageManager
|
||||||
|
resources.DeploymentCLIGetter
|
||||||
|
|
||||||
// GetAPIObject returns the deployment as k8s object.
|
// GetAPIObject returns the deployment as k8s object.
|
||||||
GetAPIObject() k8sutil.APIObject
|
GetAPIObject() k8sutil.APIObject
|
||||||
|
|
|
@ -87,6 +87,7 @@ func createHighPlan(ctx context.Context, log zerolog.Logger, apiObject k8sutil.A
|
||||||
ApplyIfEmpty(updateMemberPodTemplateSpec).
|
ApplyIfEmpty(updateMemberPodTemplateSpec).
|
||||||
ApplyIfEmpty(updateMemberPhasePlan).
|
ApplyIfEmpty(updateMemberPhasePlan).
|
||||||
ApplyIfEmpty(createCleanOutPlan).
|
ApplyIfEmpty(createCleanOutPlan).
|
||||||
|
ApplyIfEmpty(updateMemberUpdateConditionsPlan).
|
||||||
ApplyIfEmpty(updateMemberRotationConditionsPlan).
|
ApplyIfEmpty(updateMemberRotationConditionsPlan).
|
||||||
Plan(), true
|
Plan(), true
|
||||||
}
|
}
|
||||||
|
@ -157,6 +158,38 @@ func tlsRotateConditionAction(group api.ServerGroup, memberID string, reason str
|
||||||
return api.NewAction(api.ActionTypeSetMemberCondition, group, memberID, reason).AddParam(api.ConditionTypePendingTLSRotation.String(), "T")
|
return api.NewAction(api.ActionTypeSetMemberCondition, group, memberID, reason).AddParam(api.ConditionTypePendingTLSRotation.String(), "T")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateMemberUpdateConditionsPlan(ctx context.Context,
|
||||||
|
log zerolog.Logger, apiObject k8sutil.APIObject,
|
||||||
|
spec api.DeploymentSpec, status api.DeploymentStatus,
|
||||||
|
cachedStatus inspectorInterface.Inspector, context PlanBuilderContext) api.Plan {
|
||||||
|
var plan api.Plan
|
||||||
|
|
||||||
|
if err := status.Members.ForeachServerGroup(func(group api.ServerGroup, list api.MemberStatusList) error {
|
||||||
|
for _, m := range list {
|
||||||
|
if m.Conditions.IsTrue(api.ConditionTypeUpdating) {
|
||||||
|
// We are in updating phase
|
||||||
|
if status.Plan.IsEmpty() {
|
||||||
|
// If plan is empty then something went wrong
|
||||||
|
plan = append(plan,
|
||||||
|
api.NewAction(api.ActionTypeSetMemberCondition, group, m.ID, "Clean update actions after failure").
|
||||||
|
AddParam(api.ConditionTypePendingUpdate.String(), "").
|
||||||
|
AddParam(api.ConditionTypeUpdating.String(), "").
|
||||||
|
AddParam(api.ConditionTypeUpdateFailed.String(), "T").
|
||||||
|
AddParam(api.ConditionTypePendingRestart.String(), "T"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
log.Err(err).Msgf("Error while generating update plan")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return plan
|
||||||
|
}
|
||||||
|
|
||||||
func updateMemberRotationConditionsPlan(ctx context.Context,
|
func updateMemberRotationConditionsPlan(ctx context.Context,
|
||||||
log zerolog.Logger, apiObject k8sutil.APIObject,
|
log zerolog.Logger, apiObject k8sutil.APIObject,
|
||||||
spec api.DeploymentSpec, status api.DeploymentStatus,
|
spec api.DeploymentSpec, status api.DeploymentStatus,
|
||||||
|
@ -196,7 +229,7 @@ func updateMemberRotationConditions(log zerolog.Logger, apiObject k8sutil.APIObj
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if m, _, reason, err := rotation.IsRotationRequired(log, cachedStatus, spec, member, p, arangoMember.Spec.Template, arangoMember.Status.Template); err != nil {
|
if m, _, reason, err := rotation.IsRotationRequired(log, cachedStatus, spec, member, group, p, arangoMember.Spec.Template, arangoMember.Status.Template); err != nil {
|
||||||
log.Error().Err(err).Msgf("Error while getting rotation details")
|
log.Error().Err(err).Msgf("Error while getting rotation details")
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
|
@ -209,7 +242,20 @@ func updateMemberRotationConditions(log zerolog.Logger, apiObject k8sutil.APIObj
|
||||||
}
|
}
|
||||||
// We need to do enforced rotation
|
// We need to do enforced rotation
|
||||||
return api.Plan{restartMemberConditionAction(group, member.ID, reason)}, nil
|
return api.Plan{restartMemberConditionAction(group, member.ID, reason)}, nil
|
||||||
case rotation.GracefulRotation, rotation.InPlaceRotation, rotation.SilentRotation: // TODO: Add support for InPlace and Silent rotation
|
case rotation.InPlaceRotation:
|
||||||
|
if member.Conditions.IsTrue(api.ConditionTypeUpdateFailed) {
|
||||||
|
if !(member.Conditions.IsTrue(api.ConditionTypePendingRestart) || member.Conditions.IsTrue(api.ConditionTypeRestart)) {
|
||||||
|
return api.Plan{pendingRestartMemberConditionAction(group, member.ID, reason)}, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
} else if member.Conditions.IsTrue(api.ConditionTypeUpdating) || member.Conditions.IsTrue(api.ConditionTypePendingUpdate) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return api.Plan{api.NewAction(api.ActionTypeSetMemberCondition, group, member.ID, reason).AddParam(api.ConditionTypePendingUpdate.String(), "T")}, nil
|
||||||
|
case rotation.SilentRotation:
|
||||||
|
// Propagate changes without restart
|
||||||
|
return api.Plan{api.NewAction(api.ActionTypeArangoMemberUpdatePodStatus, group, member.ID, "Propagating status of pod").AddParam(ActionTypeArangoMemberUpdatePodStatusChecksum, arangoMember.Spec.Template.GetChecksum())}, nil
|
||||||
|
case rotation.GracefulRotation:
|
||||||
if reason != "" {
|
if reason != "" {
|
||||||
log.Info().Bool("enforced", false).Msgf(reason)
|
log.Info().Bool("enforced", false).Msgf(reason)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -124,8 +124,41 @@ func createRotateOrUpgradePlanInternal(log zerolog.Logger, apiObject k8sutil.API
|
||||||
newPlan = createUpgradeMemberPlan(log, m, group, "Version upgrade", spec, status,
|
newPlan = createUpgradeMemberPlan(log, m, group, "Version upgrade", spec, status,
|
||||||
!decision.AutoUpgradeNeeded)
|
!decision.AutoUpgradeNeeded)
|
||||||
} else {
|
} else {
|
||||||
if rotation.CheckPossible(m) && m.Conditions.IsTrue(api.ConditionTypeRestart) {
|
if rotation.CheckPossible(m) {
|
||||||
newPlan = createRotateMemberPlan(log, m, group, "Restart flag present")
|
if m.Conditions.IsTrue(api.ConditionTypeRestart) {
|
||||||
|
newPlan = createRotateMemberPlan(log, m, group, "Restart flag present")
|
||||||
|
} else if m.Conditions.IsTrue(api.ConditionTypeUpdating) || m.Conditions.IsTrue(api.ConditionTypeUpdateFailed) {
|
||||||
|
continue
|
||||||
|
} else if m.Conditions.IsTrue(api.ConditionTypePendingUpdate) {
|
||||||
|
arangoMember, ok := cachedStatus.ArangoMember(m.ArangoMemberName(apiObject.GetName(), group))
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p, ok := cachedStatus.Pod(m.PodName)
|
||||||
|
if !ok {
|
||||||
|
p = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode, p, reason, err := rotation.IsRotationRequired(log, cachedStatus, spec, m, group, p, arangoMember.Spec.Template, arangoMember.Status.Template); err != nil {
|
||||||
|
log.Err(err).Msgf("Error while generating update plan")
|
||||||
|
continue
|
||||||
|
} else if mode != rotation.InPlaceRotation {
|
||||||
|
newPlan = api.Plan{api.NewAction(api.ActionTypeSetMemberCondition, group, m.ID, "Cleaning update").
|
||||||
|
AddParam(api.ConditionTypePendingUpdate.String(), "").AddParam(api.ConditionTypeUpdating.String(), "T")}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
p = p.After(
|
||||||
|
api.NewAction(api.ActionTypeWaitForMemberUp, group, m.ID),
|
||||||
|
api.NewAction(api.ActionTypeWaitForMemberInSync, group, m.ID))
|
||||||
|
|
||||||
|
p = p.Wrap(api.NewAction(api.ActionTypeSetMemberCondition, group, m.ID, reason).
|
||||||
|
AddParam(api.ConditionTypePendingUpdate.String(), "").AddParam(api.ConditionTypeUpdating.String(), "T"),
|
||||||
|
api.NewAction(api.ActionTypeSetMemberCondition, group, m.ID, reason).
|
||||||
|
AddParam(api.ConditionTypeUpdating.String(), ""))
|
||||||
|
|
||||||
|
newPlan = p
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,10 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned"
|
||||||
|
monitoringClient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
|
||||||
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
|
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
|
||||||
|
|
||||||
apiErrors "k8s.io/apimachinery/pkg/api/errors"
|
apiErrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
@ -75,6 +79,18 @@ type testContext struct {
|
||||||
RecordedEvent *k8sutil.Event
|
RecordedEvent *k8sutil.Event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *testContext) GetKubeCli() kubernetes.Interface {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testContext) GetMonitoringV1Cli() monitoringClient.MonitoringV1Interface {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testContext) GetArangoCli() versioned.Interface {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
func (c *testContext) RenderPodForMemberFromCurrent(ctx context.Context, cachedStatus inspectorInterface.Inspector, memberID string) (*core.Pod, error) {
|
func (c *testContext) RenderPodForMemberFromCurrent(ctx context.Context, cachedStatus inspectorInterface.Inspector, memberID string) (*core.Pod, error) {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,11 @@ func (d *Reconciler) executePlan(ctx context.Context, cachedStatus inspectorInte
|
||||||
log.Info().Msgf("Appending new plan items")
|
log.Info().Msgf("Appending new plan items")
|
||||||
return newPlan, true, nil
|
return newPlan, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := getActionPost(action, ctx); err != nil {
|
||||||
|
log.Err(err).Msgf("Post action failed")
|
||||||
|
return nil, true, errors.WithStack(err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if plan[0].StartTime.IsZero() {
|
if plan[0].StartTime.IsZero() {
|
||||||
now := metav1.Now()
|
now := metav1.Now()
|
||||||
|
|
|
@ -27,11 +27,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned"
|
"github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned"
|
||||||
|
monitoringClient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
|
||||||
"github.com/arangodb/kube-arangodb/pkg/operator/scope"
|
"github.com/arangodb/kube-arangodb/pkg/operator/scope"
|
||||||
|
|
||||||
monitoringClient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1"
|
|
||||||
|
|
||||||
backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1"
|
backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1"
|
||||||
|
|
||||||
driver "github.com/arangodb/go-driver"
|
driver "github.com/arangodb/go-driver"
|
||||||
|
@ -40,7 +40,6 @@ import (
|
||||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||||
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
|
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
|
||||||
core "k8s.io/api/core/v1"
|
core "k8s.io/api/core/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServerGroupIterator provides a helper to callback on every server
|
// ServerGroupIterator provides a helper to callback on every server
|
||||||
|
@ -85,6 +84,15 @@ type DeploymentImageManager interface {
|
||||||
SelectImageForMember(spec api.DeploymentSpec, status api.DeploymentStatus, member api.MemberStatus) (api.ImageInfo, bool)
|
SelectImageForMember(spec api.DeploymentSpec, status api.DeploymentStatus, member api.MemberStatus) (api.ImageInfo, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeploymentCLIGetter interface {
|
||||||
|
// GetKubeCli returns the kubernetes client
|
||||||
|
GetKubeCli() kubernetes.Interface
|
||||||
|
// GetMonitoringV1Cli returns monitoring client
|
||||||
|
GetMonitoringV1Cli() monitoringClient.MonitoringV1Interface
|
||||||
|
// GetArangoCli returns the Arango CRD client
|
||||||
|
GetArangoCli() versioned.Interface
|
||||||
|
}
|
||||||
|
|
||||||
type ArangoMemberUpdateFunc func(obj *api.ArangoMember) bool
|
type ArangoMemberUpdateFunc func(obj *api.ArangoMember) bool
|
||||||
type ArangoMemberStatusUpdateFunc func(obj *api.ArangoMember, s *api.ArangoMemberStatus) bool
|
type ArangoMemberStatusUpdateFunc func(obj *api.ArangoMember, s *api.ArangoMemberStatus) bool
|
||||||
|
|
||||||
|
@ -102,6 +110,7 @@ type Context interface {
|
||||||
DeploymentAgencyMaintenance
|
DeploymentAgencyMaintenance
|
||||||
ArangoMemberContext
|
ArangoMemberContext
|
||||||
DeploymentImageManager
|
DeploymentImageManager
|
||||||
|
DeploymentCLIGetter
|
||||||
|
|
||||||
// GetAPIObject returns the deployment as k8s object.
|
// GetAPIObject returns the deployment as k8s object.
|
||||||
GetAPIObject() k8sutil.APIObject
|
GetAPIObject() k8sutil.APIObject
|
||||||
|
@ -114,12 +123,6 @@ type Context interface {
|
||||||
// UpdateStatus replaces the status of the deployment with the given status and
|
// UpdateStatus replaces the status of the deployment with the given status and
|
||||||
// updates the resources in k8s.
|
// updates the resources in k8s.
|
||||||
UpdateStatus(ctx context.Context, status api.DeploymentStatus, lastVersion int32, force ...bool) error
|
UpdateStatus(ctx context.Context, status api.DeploymentStatus, lastVersion int32, force ...bool) error
|
||||||
// GetKubeCli returns the kubernetes client
|
|
||||||
GetKubeCli() kubernetes.Interface
|
|
||||||
// GetMonitoringV1Cli returns monitoring client
|
|
||||||
GetMonitoringV1Cli() monitoringClient.MonitoringV1Interface
|
|
||||||
// GetArangoCli returns the Arango CRD client
|
|
||||||
GetArangoCli() versioned.Interface
|
|
||||||
// GetLifecycleImage returns the image name containing the lifecycle helper (== name of operator image)
|
// GetLifecycleImage returns the image name containing the lifecycle helper (== name of operator image)
|
||||||
GetLifecycleImage() string
|
GetLifecycleImage() string
|
||||||
// GetOperatorUUIDImage returns the image name containing the uuid helper (== name of operator image)
|
// GetOperatorUUIDImage returns the image name containing the uuid helper (== name of operator image)
|
||||||
|
|
|
@ -26,7 +26,6 @@ package resources
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
@ -632,6 +631,9 @@ func (r *Resources) createPodForMember(ctx context.Context, spec api.DeploymentS
|
||||||
m.Conditions.Remove(api.ConditionTypePendingTLSRotation)
|
m.Conditions.Remove(api.ConditionTypePendingTLSRotation)
|
||||||
m.Conditions.Remove(api.ConditionTypePendingRestart)
|
m.Conditions.Remove(api.ConditionTypePendingRestart)
|
||||||
m.Conditions.Remove(api.ConditionTypeRestart)
|
m.Conditions.Remove(api.ConditionTypeRestart)
|
||||||
|
m.Conditions.Remove(api.ConditionTypePendingUpdate)
|
||||||
|
m.Conditions.Remove(api.ConditionTypeUpdating)
|
||||||
|
m.Conditions.Remove(api.ConditionTypeUpdateFailed)
|
||||||
m.Conditions.Remove(api.ConditionTypeCleanedOut)
|
m.Conditions.Remove(api.ConditionTypeCleanedOut)
|
||||||
|
|
||||||
m.Upgrade = false
|
m.Upgrade = false
|
||||||
|
@ -737,7 +739,7 @@ func ChecksumArangoPod(groupSpec api.ServerGroupSpec, pod *core.Pod) (string, er
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%0x", sha256.Sum256(data)), nil
|
return util.SHA256(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsurePods creates all Pods listed in member status
|
// EnsurePods creates all Pods listed in member status
|
||||||
|
|
|
@ -185,7 +185,7 @@ func (r *Resources) InspectPods(ctx context.Context, cachedStatus inspectorInter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if k8sutil.IsPodReady(pod) {
|
if k8sutil.IsContainerReady(pod, k8sutil.ServerContainerName) {
|
||||||
// Pod is now ready
|
// Pod is now ready
|
||||||
if memberStatus.Conditions.Update(api.ConditionTypeReady, true, "Pod Ready", "") {
|
if memberStatus.Conditions.Update(api.ConditionTypeReady, true, "Pod Ready", "") {
|
||||||
log.Debug().Str("pod-name", pod.GetName()).Msg("Updating member condition Ready to true")
|
log.Debug().Str("pod-name", pod.GetName()).Msg("Updating member condition Ready to true")
|
||||||
|
|
102
pkg/deployment/rotation/arangod_containers.go
Normal file
102
pkg/deployment/rotation/arangod_containers.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2020 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 rotation
|
||||||
|
|
||||||
|
import (
|
||||||
|
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||||
|
core "k8s.io/api/core/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ContainerName = "name"
|
||||||
|
ContainerImage = "image"
|
||||||
|
)
|
||||||
|
|
||||||
|
func containersCompare(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) compareFunc {
|
||||||
|
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) {
|
||||||
|
a, b := spec.Containers, status.Containers
|
||||||
|
|
||||||
|
if len(a) == 0 || len(b) == 0 {
|
||||||
|
return SkippedRotation, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for id := range a {
|
||||||
|
if ac, bc := &a[id], &b[id]; ac.Name == k8sutil.ServerContainerName && ac.Name == bc.Name {
|
||||||
|
// Nothing to do
|
||||||
|
} else if ac.Name == bc.Name {
|
||||||
|
if ac.Image != bc.Image {
|
||||||
|
// Image changed
|
||||||
|
plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerImageUpdate).AddParam(ContainerName, ac.Name).AddParam(ContainerImage, ac.Image))
|
||||||
|
|
||||||
|
bc.Image = ac.Image
|
||||||
|
mode = mode.And(InPlaceRotation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func initContainersCompare(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) compareFunc {
|
||||||
|
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) {
|
||||||
|
gs := deploymentSpec.GetServerGroupSpec(group)
|
||||||
|
|
||||||
|
switch gs.InitContainers.GetMode().Get() {
|
||||||
|
case api.ServerGroupInitContainerIgnoreMode:
|
||||||
|
// Just copy spec to status if different
|
||||||
|
if equal, err := util.CompareJSON(spec.InitContainers, status.InitContainers); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
} else if !equal {
|
||||||
|
status.InitContainers = spec.InitContainers
|
||||||
|
mode = mode.And(SilentRotation)
|
||||||
|
} else {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if len(status.InitContainers) != len(spec.InitContainers) {
|
||||||
|
// Nothing to do, count is different
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for id := range status.InitContainers {
|
||||||
|
if status.InitContainers[id].Name != spec.InitContainers[id].Name {
|
||||||
|
// Nothing to do, order is different
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id := range status.InitContainers {
|
||||||
|
if api.IsReservedServerGroupInitContainerName(status.InitContainers[id].Name) {
|
||||||
|
if equal, err := util.CompareJSON(spec.InitContainers[id], status.InitContainers[id]); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
} else if !equal {
|
||||||
|
status.InitContainers[id] = spec.InitContainers[id]
|
||||||
|
mode = mode.And(SilentRotation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
146
pkg/deployment/rotation/arangod_containers_test.go
Normal file
146
pkg/deployment/rotation/arangod_containers_test.go
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2020 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 rotation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ArangoDContainers_SidecarImages(t *testing.T) {
|
||||||
|
testCases := []TestCase{
|
||||||
|
{
|
||||||
|
name: "Sidecar Image Update",
|
||||||
|
spec: buildPodSpec(addContainer(k8sutil.ServerContainerName, nil), addSidecarWithImage("sidecar", "local:1.0")),
|
||||||
|
status: buildPodSpec(addContainer(k8sutil.ServerContainerName, nil), addSidecarWithImage("sidecar", "local:2.0")),
|
||||||
|
|
||||||
|
expectedMode: InPlaceRotation,
|
||||||
|
expectedPlan: api.Plan{
|
||||||
|
api.NewAction(api.ActionTypeRuntimeContainerImageUpdate, 0, ""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sidecar Image Update with more than one sidecar",
|
||||||
|
spec: buildPodSpec(addSidecarWithImage("sidecar1", "local:1.0"), addSidecarWithImage("sidecar", "local:1.0")),
|
||||||
|
status: buildPodSpec(addSidecarWithImage("sidecar1", "local:1.0"), addSidecarWithImage("sidecar", "local:2.0")),
|
||||||
|
|
||||||
|
expectedMode: InPlaceRotation,
|
||||||
|
expectedPlan: api.Plan{
|
||||||
|
api.NewAction(api.ActionTypeRuntimeContainerImageUpdate, 0, ""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTestCases(t)(testCases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_InitContainers(t *testing.T) {
|
||||||
|
t.Run("Ignore", func(t *testing.T) {
|
||||||
|
testCases := []TestCase{
|
||||||
|
{
|
||||||
|
name: "Same containers",
|
||||||
|
spec: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *v1.Container) {
|
||||||
|
c.Image = "local:1.0"
|
||||||
|
})),
|
||||||
|
status: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *v1.Container) {
|
||||||
|
c.Image = "local:1.0"
|
||||||
|
})),
|
||||||
|
|
||||||
|
expectedMode: SkippedRotation,
|
||||||
|
|
||||||
|
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
||||||
|
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
||||||
|
Mode: api.ServerGroupInitContainerIgnoreMode.New(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Containers with different image",
|
||||||
|
spec: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *v1.Container) {
|
||||||
|
c.Image = "local:1.0"
|
||||||
|
})),
|
||||||
|
status: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *v1.Container) {
|
||||||
|
c.Image = "local:2.0"
|
||||||
|
})),
|
||||||
|
|
||||||
|
expectedMode: SilentRotation,
|
||||||
|
|
||||||
|
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
||||||
|
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
||||||
|
Mode: api.ServerGroupInitContainerIgnoreMode.New(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTestCases(t)(testCases...)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("update", func(t *testing.T) {
|
||||||
|
testCases := []TestCase{
|
||||||
|
{
|
||||||
|
name: "Containers with different image but init rotation enforced",
|
||||||
|
spec: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *v1.Container) {
|
||||||
|
c.Image = "local:1.0"
|
||||||
|
})),
|
||||||
|
status: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *v1.Container) {
|
||||||
|
c.Image = "local:2.0"
|
||||||
|
})),
|
||||||
|
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
|
||||||
|
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
||||||
|
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
||||||
|
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Core Containers with different image but init rotation enforced",
|
||||||
|
spec: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, func(c *v1.Container) {
|
||||||
|
c.Image = "local:1.0"
|
||||||
|
}), addInitContainer("sidecar", func(c *v1.Container) {
|
||||||
|
c.Image = "local:1.0"
|
||||||
|
})),
|
||||||
|
status: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, func(c *v1.Container) {
|
||||||
|
c.Image = "local:2.0"
|
||||||
|
}), addInitContainer("sidecar", func(c *v1.Container) {
|
||||||
|
c.Image = "local:1.0"
|
||||||
|
})),
|
||||||
|
|
||||||
|
expectedMode: SilentRotation,
|
||||||
|
|
||||||
|
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
||||||
|
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
||||||
|
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTestCases(t)(testCases...)
|
||||||
|
})
|
||||||
|
}
|
|
@ -34,15 +34,15 @@ import (
|
||||||
type Mode int
|
type Mode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EnforcedRotation Mode = iota
|
SkippedRotation Mode = iota
|
||||||
GracefulRotation
|
|
||||||
InPlaceRotation
|
|
||||||
SilentRotation
|
SilentRotation
|
||||||
SkippedRotation
|
InPlaceRotation
|
||||||
|
GracefulRotation
|
||||||
|
EnforcedRotation
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m Mode) And(b Mode) Mode {
|
func (m Mode) And(b Mode) Mode {
|
||||||
if m < b {
|
if m > b {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ func CheckPossible(member api.MemberStatus) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsRotationRequired(log zerolog.Logger, cachedStatus inspectorInterface.Inspector, spec api.DeploymentSpec, member api.MemberStatus, pod *core.Pod, specTemplate, statusTemplate *api.ArangoMemberPodTemplate) (mode Mode, plan api.Plan, reason string, err error) {
|
func IsRotationRequired(log zerolog.Logger, cachedStatus inspectorInterface.Inspector, spec api.DeploymentSpec, member api.MemberStatus, group api.ServerGroup, pod *core.Pod, specTemplate, statusTemplate *api.ArangoMemberPodTemplate) (mode Mode, plan api.Plan, reason string, err error) {
|
||||||
// Determine if rotation is required based on plan and actions
|
// Determine if rotation is required based on plan and actions
|
||||||
|
|
||||||
// Set default mode for return value
|
// Set default mode for return value
|
||||||
|
@ -123,12 +123,9 @@ func IsRotationRequired(log zerolog.Logger, cachedStatus inspectorInterface.Insp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if statusTemplate.RotationNeeded(specTemplate) {
|
if mode, plan, err := compare(log, spec, member, group, specTemplate, statusTemplate); err != nil {
|
||||||
reason = "Pod needs rotation - templates does not match"
|
return SkippedRotation, nil, "", err
|
||||||
mode = GracefulRotation
|
} else {
|
||||||
log.Info().Str("id", member.ID).Str("Before", member.PodSpecVersion).Msgf(reason)
|
return mode, plan, "Pod needs rotation", nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
101
pkg/deployment/rotation/compare.go
Normal file
101
pkg/deployment/rotation/compare.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2020 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 rotation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
core "k8s.io/api/core/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type compareFuncGen func(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) compareFunc
|
||||||
|
type compareFunc func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error)
|
||||||
|
|
||||||
|
func generator(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) func(c compareFuncGen) compareFunc {
|
||||||
|
return func(c compareFuncGen) compareFunc {
|
||||||
|
return c(deploymentSpec, group, spec, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareFuncs(builder api.ActionBuilder, f ...compareFunc) (mode Mode, plan api.Plan, err error) {
|
||||||
|
for _, q := range f {
|
||||||
|
if m, p, err := q(builder); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
} else {
|
||||||
|
mode = mode.And(m)
|
||||||
|
plan = append(plan, p...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func compare(log zerolog.Logger, deploymentSpec api.DeploymentSpec, member api.MemberStatus, group api.ServerGroup, spec, status *api.ArangoMemberPodTemplate) (mode Mode, plan api.Plan, err error) {
|
||||||
|
if spec.Checksum == status.Checksum {
|
||||||
|
return SkippedRotation, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mode = SkippedRotation
|
||||||
|
|
||||||
|
podStatus := status.PodSpec.DeepCopy()
|
||||||
|
|
||||||
|
// Try to fill fields
|
||||||
|
b := api.NewActionBuilder(group, member.ID)
|
||||||
|
|
||||||
|
g := generator(deploymentSpec, group, &spec.PodSpec.Spec, &podStatus.Spec)
|
||||||
|
|
||||||
|
if m, p, err := compareFuncs(b, g(containersCompare), g(initContainersCompare)); err != nil {
|
||||||
|
log.Err(err).Msg("Error while getting pod diff")
|
||||||
|
return SkippedRotation, nil, err
|
||||||
|
} else {
|
||||||
|
mode = mode.And(m)
|
||||||
|
plan = append(plan, p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
checksum, err := resources.ChecksumArangoPod(deploymentSpec.GetServerGroupSpec(group), resources.CreatePodFromTemplate(podStatus))
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("Error while getting pod checksum")
|
||||||
|
return SkippedRotation, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newSpec, err := api.GetArangoMemberPodTemplate(podStatus, checksum)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("Error while getting template")
|
||||||
|
return SkippedRotation, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec.RotationNeeded(newSpec) {
|
||||||
|
l := log.Info().Str("id", member.ID).Str("Before", spec.PodSpecChecksum)
|
||||||
|
if d, err := json.Marshal(status); err == nil {
|
||||||
|
l = l.Str("status", string(d))
|
||||||
|
}
|
||||||
|
if d, err := json.Marshal(newSpec); err == nil {
|
||||||
|
l = l.Str("spec", string(d))
|
||||||
|
}
|
||||||
|
l.Msgf("Pod needs rotation - templates does not match")
|
||||||
|
return GracefulRotation, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
140
pkg/deployment/rotation/utils_test.go
Normal file
140
pkg/deployment/rotation/utils_test.go
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2020 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 rotation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
core "k8s.io/api/core/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestCase struct {
|
||||||
|
name string
|
||||||
|
spec, status *core.PodTemplateSpec
|
||||||
|
|
||||||
|
deploymentSpec api.DeploymentSpec
|
||||||
|
expectedMode Mode
|
||||||
|
expectedPlan api.Plan
|
||||||
|
expectedErr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTestCases(t *testing.T) func(tcs ...TestCase) {
|
||||||
|
return func(tcs ...TestCase) {
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
pspec := newTemplateFromSpec(t, tc.spec, api.ServerGroupAgents, tc.deploymentSpec)
|
||||||
|
pstatus := newTemplateFromSpec(t, tc.status, api.ServerGroupAgents, tc.deploymentSpec)
|
||||||
|
|
||||||
|
mode, plan, err := compare(log.Logger, tc.deploymentSpec, api.MemberStatus{ID: "id"}, api.ServerGroupAgents, pspec, pstatus)
|
||||||
|
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.EqualError(t, err, tc.expectedErr)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, tc.expectedMode, mode)
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case InPlaceRotation:
|
||||||
|
require.Len(t, plan, len(tc.expectedPlan))
|
||||||
|
|
||||||
|
for i := range plan {
|
||||||
|
require.Equal(t, tc.expectedPlan[i].Type, plan[i].Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTemplateFromSpec(t *testing.T, podSpec *core.PodTemplateSpec, group api.ServerGroup, deploymentSpec api.DeploymentSpec) *api.ArangoMemberPodTemplate {
|
||||||
|
checksum, err := resources.ChecksumArangoPod(deploymentSpec.GetServerGroupSpec(group), resources.CreatePodFromTemplate(podSpec))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
newSpec, err := api.GetArangoMemberPodTemplate(podSpec, checksum)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return newSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
type podSpecBuilder func(pod *core.PodTemplateSpec)
|
||||||
|
|
||||||
|
func buildPodSpec(b ...podSpecBuilder) *core.PodTemplateSpec {
|
||||||
|
p := &core.PodTemplateSpec{}
|
||||||
|
|
||||||
|
for _, i := range b {
|
||||||
|
i(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func addContainer(name string, f func(c *core.Container)) podSpecBuilder {
|
||||||
|
return func(pod *core.PodTemplateSpec) {
|
||||||
|
var c core.Container
|
||||||
|
|
||||||
|
c.Name = name
|
||||||
|
|
||||||
|
if f != nil {
|
||||||
|
f(&c)
|
||||||
|
}
|
||||||
|
|
||||||
|
pod.Spec.Containers = append(pod.Spec.Containers, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addInitContainer(name string, f func(c *core.Container)) podSpecBuilder {
|
||||||
|
return func(pod *core.PodTemplateSpec) {
|
||||||
|
var c core.Container
|
||||||
|
|
||||||
|
c.Name = name
|
||||||
|
|
||||||
|
if f != nil {
|
||||||
|
f(&c)
|
||||||
|
}
|
||||||
|
|
||||||
|
pod.Spec.InitContainers = append(pod.Spec.InitContainers, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSidecarWithImage(name, image string) podSpecBuilder {
|
||||||
|
return addContainer(name, func(c *core.Container) {
|
||||||
|
c.Image = image
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type deploymentBuilder func(depl *api.DeploymentSpec)
|
||||||
|
|
||||||
|
func buildDeployment(b ...deploymentBuilder) api.DeploymentSpec {
|
||||||
|
p := api.DeploymentSpec{}
|
||||||
|
|
||||||
|
for _, i := range b {
|
||||||
|
i(&p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
|
@ -25,6 +25,8 @@ package util
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SHA256FromString(data string) string {
|
func SHA256FromString(data string) string {
|
||||||
|
@ -34,3 +36,25 @@ func SHA256FromString(data string) string {
|
||||||
func SHA256(data []byte) string {
|
func SHA256(data []byte) string {
|
||||||
return fmt.Sprintf("%0x", sha256.Sum256(data))
|
return fmt.Sprintf("%0x", sha256.Sum256(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SHA256FromJSON(a interface{}) (string, error) {
|
||||||
|
d, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return SHA256(d), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompareJSON(a, b interface{}) (bool, error) {
|
||||||
|
ad, err := SHA256FromJSON(a)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
bd, err := SHA256FromJSON(b)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ad == bd, nil
|
||||||
|
}
|
||||||
|
|
|
@ -25,11 +25,13 @@ package k8sutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||||
|
|
||||||
"github.com/arangodb/kube-arangodb/pkg/util/errors"
|
"github.com/arangodb/kube-arangodb/pkg/util/errors"
|
||||||
|
|
||||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/interfaces"
|
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/interfaces"
|
||||||
|
@ -65,6 +67,9 @@ const (
|
||||||
ClusterJWTSecretVolumeMountDir = "/secrets/cluster/jwt"
|
ClusterJWTSecretVolumeMountDir = "/secrets/cluster/jwt"
|
||||||
ExporterJWTVolumeMountDir = "/secrets/exporter/jwt"
|
ExporterJWTVolumeMountDir = "/secrets/exporter/jwt"
|
||||||
MasterJWTSecretVolumeMountDir = "/secrets/master/jwt"
|
MasterJWTSecretVolumeMountDir = "/secrets/master/jwt"
|
||||||
|
|
||||||
|
ServerContainerConditionContainersNotReady = "ContainersNotReady"
|
||||||
|
ServerContainerConditionPrefix = "containers with unready status: "
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsPodReady returns true if the PodReady condition on
|
// IsPodReady returns true if the PodReady condition on
|
||||||
|
@ -74,6 +79,35 @@ func IsPodReady(pod *core.Pod) bool {
|
||||||
return condition != nil && condition.Status == core.ConditionTrue
|
return condition != nil && condition.Status == core.ConditionTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsContainerReady returns true if the PodReady condition on
|
||||||
|
// the given pod is set to true.
|
||||||
|
func IsContainerReady(pod *core.Pod, container string) bool {
|
||||||
|
condition := getPodCondition(&pod.Status, core.PodReady)
|
||||||
|
if condition == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if condition.Status == core.ConditionTrue {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsContainerRunning(pod, container) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch condition.Reason {
|
||||||
|
case ServerContainerConditionContainersNotReady:
|
||||||
|
if strings.HasPrefix(condition.Message, ServerContainerConditionPrefix) {
|
||||||
|
n := strings.TrimPrefix(condition.Message, ServerContainerConditionPrefix)
|
||||||
|
|
||||||
|
return !strings.Contains(n, container)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetPodByName returns pod if it exists among the pods' list
|
// GetPodByName returns pod if it exists among the pods' list
|
||||||
// Returns false if not found.
|
// Returns false if not found.
|
||||||
func GetPodByName(pods []core.Pod, podName string) (core.Pod, bool) {
|
func GetPodByName(pods []core.Pod, podName string) (core.Pod, bool) {
|
||||||
|
@ -87,8 +121,13 @@ func GetPodByName(pods []core.Pod, podName string) (core.Pod, bool) {
|
||||||
|
|
||||||
// IsPodServerContainerRunning returns true if the arangodb container of the pod is still running
|
// IsPodServerContainerRunning returns true if the arangodb container of the pod is still running
|
||||||
func IsPodServerContainerRunning(pod *core.Pod) bool {
|
func IsPodServerContainerRunning(pod *core.Pod) bool {
|
||||||
|
return IsContainerRunning(pod, ServerContainerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsContainerRunning returns true if the container of the pod is still running
|
||||||
|
func IsContainerRunning(pod *core.Pod, name string) bool {
|
||||||
for _, c := range pod.Status.ContainerStatuses {
|
for _, c := range pod.Status.ContainerStatuses {
|
||||||
if c.Name != ServerContainerName {
|
if c.Name != name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,7 +459,7 @@ func GetPodSpecChecksum(podSpec core.PodSpec) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%0x", sha256.Sum256(data)), nil
|
return util.SHA256(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePod adds an owner to the given pod and calls the k8s api-server to created it.
|
// CreatePod adds an owner to the given pod and calls the k8s api-server to created it.
|
||||||
|
|
Loading…
Reference in a new issue