pkg/clustermanager/eksa_mover.go (98 lines of code) (raw):
package clustermanager
import (
"context"
"math"
"time"
"github.com/go-logr/logr"
"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/aws/eks-anywhere/pkg/api/v1alpha1"
"github.com/aws/eks-anywhere/pkg/clients/kubernetes"
"github.com/aws/eks-anywhere/pkg/cluster"
"github.com/aws/eks-anywhere/pkg/retrier"
)
// MoverOpt allows to customize a Mover on construction.
type MoverOpt func(*Mover)
// Mover applies the cluster spec to the management cluster and waits
// until the changes are fully reconciled.
type Mover struct {
log logr.Logger
clientFactory ClientFactory
moveClusterTimeout time.Duration
retryBackOff time.Duration
}
// NewMover builds an Mover.
func NewMover(log logr.Logger, clientFactory ClientFactory, opts ...MoverOpt) *Mover {
m := &Mover{
log: log,
clientFactory: clientFactory,
moveClusterTimeout: applyClusterSpecTimeout,
retryBackOff: retryBackOff,
}
for _, opt := range opts {
opt(m)
}
return m
}
// WithMoverNoTimeouts disables the timeout for all the waits and retries in management upgrader.
func WithMoverNoTimeouts() MoverOpt {
return func(a *Mover) {
maxTime := time.Duration(math.MaxInt64)
a.moveClusterTimeout = maxTime
}
}
// WithMoverApplyClusterTimeout allows to configure how long the mover retries
// to apply the objects in case of failure.
// Generally only used in tests.
func WithMoverApplyClusterTimeout(timeout time.Duration) MoverOpt {
return func(m *Mover) {
m.moveClusterTimeout = timeout
}
}
// WithMoverRetryBackOff allows to configure how long the mover waits between requests
// to update the cluster spec objects and check the status of the Cluster.
// Generally only used in tests.
func WithMoverRetryBackOff(backOff time.Duration) MoverOpt {
return func(m *Mover) {
m.retryBackOff = backOff
}
}
// Move applies the cluster's namespace and spec without checking for reconcile conditions.
func (m *Mover) Move(ctx context.Context, spec *cluster.Spec, fromClient, toClient kubernetes.Client) error {
m.log.V(3).Info("Moving the cluster object")
err := retrier.New(
m.moveClusterTimeout,
retrier.WithRetryPolicy(retrier.BackOffPolicy(m.retryBackOff)),
).Retry(func() error {
// read the cluster from bootstrap
cluster := &v1alpha1.Cluster{}
if err := fromClient.Get(ctx, spec.Cluster.Name, spec.Cluster.Namespace, cluster); err != nil {
return errors.Wrapf(err, "reading cluster from source")
}
// pause cluster on bootstrap
cluster.PauseReconcile()
// For baremetal provider we need to clear this annotation once we move to management cluster
// at this point all the hardware is provisioned and tink stack is up on the management cluster.
// We don't need the Bootstrap IP anymore.
// ideally this logic should be outside of move but the current code structure needs refactor
// to be able to do that.
cluster.ClearTinkerbellIPAnnotation()
if err := fromClient.Update(ctx, cluster); err != nil {
return errors.Wrapf(err, "updating cluster on source")
}
if err := moveClusterResource(ctx, cluster, toClient); err != nil {
return err
}
if err := moveChildObjects(ctx, spec, fromClient, toClient); err != nil {
return err
}
return nil
})
return err
}
func moveClusterResource(ctx context.Context, cluster *v1alpha1.Cluster, client kubernetes.Client) error {
cluster.ResourceVersion = ""
cluster.UID = ""
// move eksa cluster
if err := client.Create(ctx, cluster); err != nil && !apierrors.IsAlreadyExists(err) {
return errors.Wrapf(err, "moving cluster %s", cluster.Name)
}
return nil
}
func moveChildObjects(ctx context.Context, spec *cluster.Spec, fromClient, toClient kubernetes.Client) error {
// read and move child objects
for _, child := range spec.ChildObjects() {
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(child.GetObjectKind().GroupVersionKind())
if err := fromClient.Get(ctx, child.GetName(), child.GetNamespace(), obj); err != nil {
return errors.Wrapf(err, "reading child object %s %s", child.GetObjectKind().GroupVersionKind().Kind, child.GetName())
}
obj.SetResourceVersion("")
obj.SetUID("")
obj.SetOwnerReferences(nil)
if err := toClient.Create(ctx, obj); err != nil && !apierrors.IsAlreadyExists(err) {
return errors.Wrapf(err, "moving child object %s %s", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName())
}
}
return nil
}