diff --git a/.gitignore b/.gitignore index 0412b5d07..ddd33d1f2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ tools/vendor VERSION helm .vscode -hack/tools/bin/* \ No newline at end of file +hack/tools/bin/* +gardener-extension-provider-metal diff --git a/Makefile b/Makefile index 9ca3b646e..4f44b3caa 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,10 @@ start-admission-metal: # Rules related to binary build, Docker image build and release # ################################################################# +.PHONY: build +build: + go build -ldflags $(LD_FLAGS) -tags netgo ./cmd/gardener-extension-provider-metal + .PHONY: install install: revendor $(HELM) @LD_FLAGS="-w -X github.com/gardener/$(EXTENSION_PREFIX)-$(NAME)/pkg/version.Version=$(VERSION)" \ diff --git a/example/controller-registration.yaml b/example/controller-registration.yaml index 02eb11b96..75a799062 100644 --- a/example/controller-registration.yaml +++ b/example/controller-registration.yaml @@ -5,10 +5,10 @@ metadata: name: provider-metal type: helm providerConfig: - chart: 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 + chart: H4sIAAAAAAAAA+0ca2/bRjKf+SsWSg9oDyfqaacgkMO5idv6LrEF25ficDgEK3IlMaK47JK0rab57zf7IpcUKYp2arcpBwZMLnceuzszO7MPLTHzSEhYn9wlJIx9GvYjRm98D4o2JMHB4NmDYQjw4uhI/Aco/xfPo8l0ND4aHx/z8tHxZDh9ho4ezroZ0jjBDKFnjNJkX72m739QWDaO/6sVZom9xZvgvjz4AB9Pp7XjP56Wxn88HI+PnqHh52xoHfzJxx9H/jvC+Lg76GZk4SjKX4f2eGhPLI/ELvOjRBSeoB9JsEEuVwq0oAwlK4J+UEqE3nKNQTOlQCjTKSvEG+KgRmWzbsrMn7p/vnRotn+PuvaSPoRHk/0fTV6U/P+L6bCz/0eBwWBJnSXXAJwQFK9Q30U92x7A3w0JPcoGSz9ZpXPbpZuBVpb8YYXd9UCj910aJowGAegOI0s/TqAUNMoGskWlQjb66msXJ0hyend6eXV2cf6NeiV3eBMFZFBHjs9F6JX8OAtwSBxJ9CxcMAy1UjdJmS78ibI1YfKlZ1mDAZqBzHhJlA8jIZ4HJEaFbkijiCr/pgr9cClcnUsZI26CctlQQTYrMqn//t1Xs/0nBAYDeiW+dyTYOv4bDyfDcRf/PQa0Gf/3KxJEMEHbSdQqFmzw/6Px+Lg0/uPjLv57HPj4sY88svBDgno8SOuh/qdPVnOgxvFgfhC1LZNIgOckiG0IJO012Upy4iWdExYS0CPbpwPOqkCjhsQNDlIl08ePyA/dIPUySW2kEPcIsotbFpBTcVBNDcVfcNpthR+C8oQuEej2JQkIjol9DsJVSpaJ5m9ghpCSIcS/+Au0wvGMwfc71ItXeHx07ADbd5w9sOL17QQvUYYRMT9MFqj3l/gff4nLNRmJaOwnlG33kYA2kiqCzr0JQmONdpcHxCNRQLcbEiYqys+UIx5A6mF211NbxZ8H2vh/iHkW/nKDo74Y/BuIgyjrUxjNW+YnpHaNoCn+nx5PSv7/BS/q/P8jgPI+Bat+Jwb2Qo+r9H2FZYK1H3oOD8FBH97iyOKa4uEEO+AJZKpf7a2rFUchxRA6V7hSUSydjHTMToU75+R/hULQ5QRNeW0tjuAYvy9qqYN+5UT2trpIznBqTz1knxXuZf8tVwMb7P94MhqV7H8ymow7+38M+FyGnenGb2rMkktmwgig3++L/2ZDhOLaWo/tTLVjWxHQWm+7AU09iD5wEK3wSBDKukDl97IzUpnfWyV/qei5gQ+yQs0QnAhUky0EeUvljigFYV2XRLwcBEuutxGJRVcx8nPqM+KhXgN9e5cA8uMMv9ckXxW+Ell0si5tKZWB2U4cEzGT4+eoba8ARju+HCHjN09ZnLTkKHDa8ZQoxSmlWqs22F1B5HwmZjAtZ6FQWE9C/8MXxPYi105m1ZzJkpE4fk3ixA+F3mfsd7/skaGCTK0ggnjietpEYpiMQXT9ym0Jx/G59kFlToBpKxQ7q5mPLaBjcD1+sm3GVhUNxcDuOo1ySWJ3Rbw0qBdEIti6nimGR4IEX4U4ilc0mRHmU6+JTAVK7l9g+iLsJPX8RMsnFzQrqZrVbVVPk8K88JpeRUEarg+gVahvEqvWqNrqktGKuNd0TcJmTrpmjssV6q4ZUVQrcPyRKntvYsgrFjBnlB2GySvmmEkQnx7ao3ndAudXJ9/7XPMK8Wsdd1k5t7lvd00OJgKahnx5+/SOr3kTdsjY7yAdoAB7cSTLkCS3lK2vGV4sfDc3uHaiFKnscuEaA/VCHJzLmvtcWDP9MrG8u0dDY1LV0wAQzRu2AsUKa1xaBWeTiK1xzYZFdXrZQCsqqCl3ljUa1kDHxdWtB4qES3YvmoC5j+q/yPb+hNdkW6ZdMpDSPOSljMYHaaaeTwRGs33UVs/GFrPE55OoVqJanS2SKuEZzR0fFAuI5HiWBsEVcRnh84ZLPeK9pu5aB8f/vLo416KWqufepBKrquNaMPxSs/LHgzb5f76A224BoCn/nx5Py/n/5HjY5f+PAWbarFfiZQb8Ohvtg1cBfpPcP46ICAUYufG5nD/63L9t3/gbiHnRUHyJAt/FccGZqMJXfAqQTGOQhS/xOSqPStzVm8PkOJYEtCUoAkaniEgqDGmiUyLttA9cXs3mMwja1nG6MZbahRFWr5sWhuFrsYGDvrKvlZT2d9DxM5ysUO+glfveN6LJcvMJZDDlKs2JNaLuXRq6h7ANYh2oRN9mM7lSJCE9DRMM6THLxqrfpNkqdOT956BeefXY3CL79MnZ+Sw3ynpFOnySm1FQ02JWKjGi7GOh2+lmg0Mv17A+GlT4cKk1q21EmFGz6NnN4zpAE3iadftq3PoLSCFeDiAhHVR3jRqvgbE2VybDuUT8oA7wueMFbsoYjE2fEf4CDOKXxThAyZU9C2w7x7zahm5s9gvn5BfO/7TnVcRv5LYMKXChEZFLg/3cAdQyECgXGuMkQyjTvhWHltq3QOI1SX5L5itK13qAM5f9co/HrsWOCQM30ucBtimVqqVCbVlpVsoyODXPj3mka+hioXHqc74ay9OrD9QPUe9vvTpaincVoZ/UpxoqJLwxLUt6hDenJ69PL9+fvjl9dX12cf7+/OTt6dXs5NVpVhMhcU7ge0Y3jlGI0MIngXdJFsVSVc7dnZNNI3Y2DPedPLS8Z29Pfjh9B8JeXL6/eHd6+dPl2fWOrA4aiBNqxs7YoHKrbN8cwAc93u0wrSFy1A3OmdfdWT85RF0Q918JdWngoOtXs3K6wkhMU+aSgrpnhVXpUo7xKwr35YCi12iQbshbHk5UNFkagyHqhleUI9zsOx864nW7qlXC7Iy6UY8R7F2EAcxG4ARJ/cjz8fFdciIT7PPmefM54mslIV9+zUq4+ngnkJ+fVHxC2YL66xRCheWVXEqFpzPhQ1Xx6R1xU3Nj5bnqFxEHXBUCPv1RdAgP/k7vIr48bQZseY0+WpNt7bGg7ODQDh5CckIArugsrPgsrK+CIWd5wCGkIlpCIxrQ5VasgfSKh5P0+pCOOaQC78Q7JQ109daf6bIO3vnT4JEFToPkLeTtDpqOh+pTK/U+TLnby9tkLHtk/wKXGtrk/2CvMI2yVFwFmafekhy2ENB4/n9aOv8PDy+6/f9HAWWSywR9zTOyquz5GzQqHwGKRF4yuBnNQUX0gsGMeq8z9fhOqMfvY+UAwuh/h/gG+wEPAQX5OJ03NvjBKwZ/BJfRxv7ZHLv3uQjYYP/To9HO+e/RsDv/9yjAj8+Yli3GGKfJijL/F3mlZf2tiCfy00FyF/mSBqSNfbexXJYGPFLp81M9PzCaRiJs6SPjJE/xCI9VCPV5VbXXHcsXYxWhomQAOpCk8kNxEaCyzKwuM+7Cc/4Zoou5Eoc7QxGY+rF8uOXeRDxF2VMaQV+S3WZnTWtstVzH8bLSohC9v/Z2ibuUMk+dz1BjvUtX+OISNRfShSpp2xHU5cLPy2+lZSqoyl+EL+ZHsCr79baxE3s98Y8vcouHeYZRq/MSwdv4Ikso3PMyK0S+oZfGB7VRDHlKbTdkk15ceh1AToQD/xetWxCxQuIpHmOxLZapsQy+Va3Qi6ivK+b7NRpRpG2FF7VJGps2A3ZBdgrmYJ/QElme19j59IHO5QNEi/nDALIUqaBpIi7TqYTfNQ/XyeqQ7fheQx0XxKIb3ZfigoGff1X9LoIU/yAbUIel7BhHwrAqx4pjNpMCPaKxi4N9ow40Egg2AugZXV2Oc4n4c3mSsc8DGX/hu6jOL6pqCXbXlQwXkEnf4iAojOqtz11J267SpOx2PLM3rbm6oKikTdz11nYTc7H1fQA974CZBOqAdMxXGp+/tnH0D5nKgCNf+mVeXHptJcCDJvrvpIn/ZvM9sFDLpLqb9khoZYd0jUikQR4I+D/A/CGMRyJfFRaxPk9+8tTRXHtoE/+rKaN1CtCY/w/Hu/c/u/j/UaDy/L8yjd9H9p6Ic+jK1M9mhbT8sH37bJuirw7ITacTkdEX9xPEgipm4D9bbU889Qg+DO5h/ypkPNwNNNn/eFq+/zMdD7vzP48C++xfT41P6gaeuoO+cGi2f7lh9ZAfAGuw/8noqLz+N5y+mHb2/xggzzOJnEOfX3LQcuUyngkYWVbVIaPST0Lw2XPpIDFX8Hg6Mk43nQS3eBtblrnM7qCRlWc66OMnyzJOgajrftkinVyDL51scdCRhUrLc3sqygW6fZTqDug4aIEhd7Ws3QMpDvrv/6zS8RJRBul71Q4nP6zO9299dYPrubHXGeE0lmdhxDEJS+7zyi66NAco/02mfNPXfJwHdD7YYB4yDeapH3gDQXogD1HzI12W3mY3qMpRX1K6DMj7/IicxO3jjXc8VWhipHsTe9hTBdkPt43s0ci++2O3arTTqt7fX/KWjeUH27YtqxATOpY8FaGPtvAI03KzT9X3QKtugeJIGAivNPgQ01Dran4js7KGuCs5GsrNanWRcTThr8X7glwvKy/xqQ/ZBbzStYf88p0u0NfpJMfCDbnsfpyld9R377xZ9VePsjsV0uTEhn7lxaCKihV3e2TLqm/gZPdv1LtID7IWZk/EKJWXRuSgli/f7QhUcamuQujs8pslG8Cvs+kP4o6afpll4hk3yfRHdTPMKo1d4cJKZY+VL5ZwT1xzj6PuFoeV/WKQNAQtlp4h1JXsLpjroIMOOuiggw466KCDDjrooIMOOuiggw466KCDDjrooIMOOuiggw46+Ozwf7uTf68AeAAA values: image: - tag: v0.20.2 + tag: v0.20.3 --- apiVersion: core.gardener.cloud/v1beta1 kind: ControllerRegistration diff --git a/pkg/controller/worker/actuator.go b/pkg/controller/worker/actuator.go index 56cb6613e..83ba859b6 100644 --- a/pkg/controller/worker/actuator.go +++ b/pkg/controller/worker/actuator.go @@ -2,6 +2,8 @@ package worker import ( "context" + "fmt" + "time" "github.com/gardener/gardener/extensions/pkg/util" @@ -12,10 +14,14 @@ import ( apismetal "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal" "github.com/metal-stack/gardener-extension-provider-metal/pkg/imagevector" "github.com/metal-stack/gardener-extension-provider-metal/pkg/metal" + metalclient "github.com/metal-stack/gardener-extension-provider-metal/pkg/metal/client" metalv1alpha1 "github.com/metal-stack/machine-controller-manager-provider-metal/pkg/provider/migration/legacy-api/machine/v1alpha1" + metalgo "github.com/metal-stack/metal-go" + "github.com/metal-stack/metal-go/api/models" extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" gardener "github.com/gardener/gardener/pkg/client/kubernetes" + "github.com/metal-stack/metal-lib/pkg/cache" "github.com/go-logr/logr" @@ -28,28 +34,103 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) -type delegateFactory struct { - logger logr.Logger +type ( + // actuator reconciles the cluster's worker nodes and the firewalls. + // + // why is the firewall reconciliation here and not in the controlplane controller? + // the controlplane controller deploys the firewall-controller-manager including validating and mutating webhooks + // this has to be running before we can create a firewall deployment because the mutating webhook is creating the userdata + // the worker controller acts after the controlplane controller, also the terms and responsibilities are pretty similar between machine-controller-manager and firewall-controller-manager, + // so this place seems to be a valid fit. + actuator struct { + workerActuator worker.Actuator + + logger logr.Logger + controllerConfig config.ControllerConfiguration + networkCache *cache.Cache[*cacheKey, *models.V1NetworkResponse] + + client client.Client + scheme *runtime.Scheme + decoder runtime.Decoder + } + + delegateFactory struct { + logger logr.Logger + + restConfig *rest.Config + + client client.Client + scheme *runtime.Scheme + decoder runtime.Decoder + + dataGetter func(ctx context.Context, worker *extensionsv1alpha1.Worker, cluster *extensionscontroller.Cluster) (*additionalData, error) + + machineImageMapping []config.MachineImage + } + + workerDelegate struct { + logger logr.Logger - restConfig *rest.Config + client client.Client + scheme *runtime.Scheme + decoder runtime.Decoder - client client.Client - scheme *runtime.Scheme - decoder runtime.Decoder + machineImageMapping []config.MachineImage + seedChartApplier gardener.ChartApplier + serverVersion string + + cluster *extensionscontroller.Cluster + worker *extensionsv1alpha1.Worker + + machineClasses []map[string]interface{} + machineDeployments worker.MachineDeployments + machineImages []apismetal.MachineImage + + additionalData *additionalData + } +) + +func (a *actuator) InjectScheme(scheme *runtime.Scheme) error { + a.scheme = scheme + a.decoder = serializer.NewCodecFactory(a.scheme).UniversalDecoder() + return nil +} - machineImageMapping []config.MachineImage - controllerConfig config.ControllerConfiguration +func (a *actuator) InjectClient(client client.Client) error { + a.client = client + return nil } // NewActuator creates a new Actuator that updates the status of the handled WorkerPoolConfigs. func NewActuator(machineImages []config.MachineImage, controllerConfig config.ControllerConfiguration) worker.Actuator { + logger := log.Log.WithName("metal-worker-actuator") + + a := &actuator{ + logger: logger, + controllerConfig: controllerConfig, + networkCache: cache.New(15*time.Minute, func(ctx context.Context, accessor *cacheKey) (*models.V1NetworkResponse, error) { + mclient, ok := ctx.Value("client").(metalgo.Client) + if !ok { + return nil, fmt.Errorf("no client passed in context") + } + + privateNetwork, err := metalclient.GetPrivateNetworkFromNodeNetwork(ctx, mclient, accessor.projectID, accessor.nodeCIDR) + if err != nil { + return nil, err + } + + return privateNetwork, nil + }), + } + delegateFactory := &delegateFactory{ logger: log.Log.WithName("worker-actuator"), machineImageMapping: machineImages, - controllerConfig: controllerConfig, + dataGetter: a.getAdditionalData, } - return genericactuator.NewActuator( - log.Log.WithName("metal-worker-actuator"), + + a.workerActuator = genericactuator.NewActuator( + logger, delegateFactory, metal.MachineControllerManagerName, mcmChart, @@ -57,6 +138,44 @@ func NewActuator(machineImages []config.MachineImage, controllerConfig config.Co imagevector.ImageVector(), extensionscontroller.ChartRendererFactoryFunc(util.NewChartRendererForShoot), ) + + return a +} + +func (a *actuator) Reconcile(ctx context.Context, worker *extensionsv1alpha1.Worker, cluster *extensionscontroller.Cluster) error { + err := a.firewallReconcile(ctx, worker, cluster) + if err != nil { + return err + } + + return a.workerActuator.Reconcile(ctx, worker, cluster) +} + +func (a *actuator) Delete(ctx context.Context, worker *extensionsv1alpha1.Worker, cluster *extensionscontroller.Cluster) error { + err := a.workerActuator.Delete(ctx, worker, cluster) + if err != nil { + return err + } + + return a.firewallDelete(ctx, cluster) +} + +func (a *actuator) Migrate(ctx context.Context, worker *extensionsv1alpha1.Worker, cluster *extensionscontroller.Cluster) error { + err := a.workerActuator.Migrate(ctx, worker, cluster) + if err != nil { + return err + } + + return a.firewallMigrate(ctx, cluster) +} + +func (a *actuator) Restore(ctx context.Context, worker *extensionsv1alpha1.Worker, cluster *extensionscontroller.Cluster) error { + err := a.firewallRestore(ctx, worker, cluster) + if err != nil { + return err + } + + return a.workerActuator.Restore(ctx, worker, cluster) } func (d *delegateFactory) InjectScheme(scheme *runtime.Scheme) error { @@ -95,70 +214,24 @@ func (d *delegateFactory) WorkerDelegate(ctx context.Context, worker *extensions return nil, err } - return NewWorkerDelegate( - d.logger, - d.client, - d.scheme, - d.decoder, - - d.machineImageMapping, - seedChartApplier, - serverVersion.GitVersion, - - worker, - cluster, - d.controllerConfig, - ), nil -} - -type workerDelegate struct { - logger logr.Logger - - client client.Client - scheme *runtime.Scheme - decoder runtime.Decoder - - machineImageMapping []config.MachineImage - seedChartApplier gardener.ChartApplier - serverVersion string - - cluster *extensionscontroller.Cluster - worker *extensionsv1alpha1.Worker - - machineClasses []map[string]interface{} - machineDeployments worker.MachineDeployments - machineImages []apismetal.MachineImage - - controllerConfig config.ControllerConfiguration -} - -// NewWorkerDelegate creates a new context for a worker reconciliation. -func NewWorkerDelegate( - logger logr.Logger, - client client.Client, - scheme *runtime.Scheme, - decoder runtime.Decoder, - - machineImageMapping []config.MachineImage, - seedChartApplier gardener.ChartApplier, - serverVersion string, - - worker *extensionsv1alpha1.Worker, - cluster *extensionscontroller.Cluster, - controllerConfig config.ControllerConfiguration, + additionalData, err := d.dataGetter(ctx, worker, cluster) + if err != nil { + return nil, err + } -) genericactuator.WorkerDelegate { return &workerDelegate{ - logger: logger, - client: client, - scheme: scheme, - decoder: decoder, + logger: d.logger, + client: d.client, + scheme: d.scheme, + decoder: d.decoder, - machineImageMapping: machineImageMapping, + machineImageMapping: d.machineImageMapping, seedChartApplier: seedChartApplier, - serverVersion: serverVersion, + serverVersion: serverVersion.GitVersion, cluster: cluster, worker: worker, - } + + additionalData: additionalData, + }, nil } diff --git a/pkg/controller/worker/firewall_delete.go b/pkg/controller/worker/firewall_delete.go new file mode 100644 index 000000000..7467cb24e --- /dev/null +++ b/pkg/controller/worker/firewall_delete.go @@ -0,0 +1,50 @@ +package worker + +import ( + "context" + "errors" + "fmt" + "time" + + extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" + retryutils "github.com/gardener/gardener/pkg/utils/retry" + fcmv2 "github.com/metal-stack/firewall-controller-manager/api/v2" + "github.com/metal-stack/gardener-extension-provider-metal/pkg/metal" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func (a *actuator) firewallDelete(ctx context.Context, cluster *extensionscontroller.Cluster) error { + a.logger.Info("ensuring firewall deployment gets deleted") + + return retryutils.UntilTimeout(ctx, 5*time.Second, 2*time.Minute, func(ctx context.Context) (bool, error) { + deploy := &fcmv2.FirewallDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: metal.FirewallDeploymentName, + Namespace: cluster.ObjectMeta.Name, + }, + } + + err := a.client.Get(ctx, client.ObjectKeyFromObject(deploy), deploy) + if err != nil { + if apierrors.IsNotFound(err) { + a.logger.Info("firewall deployment deletion succeeded") + return retryutils.Ok() + } + return retryutils.SevereError(fmt.Errorf("error getting firewall deployment: %w", err)) + } + + if deploy.DeletionTimestamp == nil { + a.logger.Info("deleting firewall deployment") + err = a.client.Delete(ctx, deploy) + if err != nil { + return retryutils.SevereError(fmt.Errorf("error deleting firewall deployment: %w", err)) + } + + return retryutils.MinorError(errors.New("firewall deployment is still ongoing")) + } + + return retryutils.MinorError(errors.New("firewall deployment is still ongoing")) + }) +} diff --git a/pkg/controller/worker/firewall_migrate.go b/pkg/controller/worker/firewall_migrate.go new file mode 100644 index 000000000..7637a4412 --- /dev/null +++ b/pkg/controller/worker/firewall_migrate.go @@ -0,0 +1,110 @@ +package worker + +import ( + "context" + "fmt" + + extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" + "github.com/gardener/gardener/extensions/pkg/util" + "github.com/gardener/gardener/pkg/controllerutils" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" + + fcmv2 "github.com/metal-stack/firewall-controller-manager/api/v2" +) + +func (a *actuator) firewallMigrate(ctx context.Context, cluster *extensionscontroller.Cluster) error { + // approach is to restore firewalls from the firewall monitors in the shoot cluster + + var ( + namespace = cluster.ObjectMeta.Name + + fwdeploys = &fcmv2.FirewallDeploymentList{} + fwsets = &fcmv2.FirewallSetList{} + firewalls = &fcmv2.FirewallList{} + mons = &fcmv2.FirewallMonitorList{} + ) + + err := a.client.List(ctx, fwdeploys, client.InNamespace(namespace)) + if err != nil { + return fmt.Errorf("error listing firewall deployments: %w", err) + } + + err = a.client.List(ctx, fwsets, client.InNamespace(namespace)) + if err != nil { + return fmt.Errorf("error listing firewall sets: %w", err) + } + + err = a.client.List(ctx, firewalls, client.InNamespace(namespace)) + if err != nil { + return fmt.Errorf("error listing firewalls: %w", err) + } + + _, shootClient, err := util.NewClientForShoot(ctx, a.client, namespace, client.Options{}) + if err != nil { + return fmt.Errorf("unable to create shoot client: %w", err) + } + + err = shootClient.List(ctx, mons, &client.ListOptions{Namespace: fcmv2.FirewallShootNamespace}) + if err != nil { + return fmt.Errorf("error listing firewall resources: %w", err) + } + + if !everyFirewallHasAMonitor(firewalls, mons) { + return fmt.Errorf("every firewall needs to have a corresponding firewall monitor before migration, because firewalls are restored from the monitors") + } + + a.logger.Info("shallow deleting firewall entities for shoot migration") + + if err := shallowDeleteAllObjects(ctx, a.client, fwdeploys); err != nil { + return fmt.Errorf("error shallow deleting firewall deployments: %w", err) + } + if err := shallowDeleteAllObjects(ctx, a.client, fwsets); err != nil { + return fmt.Errorf("error shallow deleting firewall sets: %w", err) + } + if err := shallowDeleteAllObjects(ctx, a.client, firewalls); err != nil { + return fmt.Errorf("error shallow deleting firewalls: %w", err) + } + + return nil +} + +func shallowDeleteAllObjects(ctx context.Context, c client.Client, objectList client.ObjectList) error { + return meta.EachListItem(objectList, func(obj runtime.Object) error { + object := obj.(client.Object) + return shallowDeleteObject(ctx, c, object) + }) +} + +func shallowDeleteObject(ctx context.Context, c client.Client, object client.Object) error { + if err := controllerutils.RemoveAllFinalizers(ctx, c, c, object); err != nil { + return err + } + if err := c.Delete(ctx, object); client.IgnoreNotFound(err) != nil { + return err + } + + return nil +} + +func everyFirewallHasAMonitor(firewalls *fcmv2.FirewallList, mons *fcmv2.FirewallMonitorList) bool { + if len(firewalls.Items) != len(mons.Items) { + return false + } + + var ( + fwNames []string + monNames []string + ) + + for _, fw := range firewalls.Items { + fwNames = append(fwNames, fw.Name) + } + for _, mon := range mons.Items { + monNames = append(monNames, mon.Name) + } + + return sets.NewString(fwNames...).Equal(sets.NewString(monNames...)) +} diff --git a/pkg/controller/worker/firewall_reconcile.go b/pkg/controller/worker/firewall_reconcile.go new file mode 100644 index 000000000..f10b51eca --- /dev/null +++ b/pkg/controller/worker/firewall_reconcile.go @@ -0,0 +1,151 @@ +package worker + +import ( + "context" + "fmt" + + extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" + extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" + fcmv2 "github.com/metal-stack/firewall-controller-manager/api/v2" + apismetal "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal" + "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal/validation" + "github.com/metal-stack/gardener-extension-provider-metal/pkg/metal" + "github.com/metal-stack/metal-lib/pkg/tag" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func (a *actuator) firewallReconcile(ctx context.Context, worker *extensionsv1alpha1.Worker, cluster *extensionscontroller.Cluster) error { + if worker.DeletionTimestamp != nil { + return nil + } + if extensionscontroller.IsHibernated(cluster) { + return nil + } + + name := "firewall-controller-manager-" + cluster.ObjectMeta.Name + mwc := &admissionregistrationv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + err := a.client.Get(ctx, client.ObjectKeyFromObject(mwc), mwc) + if err != nil { + if apierrors.IsNotFound(err) { + return fmt.Errorf("mutating webhook configuration %q of firewall-controller-manager is not yet present, requeuing", name) + } + + return err + } + + err = a.ensureFirewallDeployment(ctx, worker, cluster) + if err != nil { + return err + } + + return nil +} + +func (a *actuator) ensureFirewallDeployment(ctx context.Context, worker *extensionsv1alpha1.Worker, cluster *extensionscontroller.Cluster) error { + var ( + clusterID = string(cluster.Shoot.GetUID()) + namespace = cluster.ObjectMeta.Name + ) + + d, err := a.getAdditionalData(ctx, worker, cluster) + if err != nil { + return fmt.Errorf("error getting additional data: %w", err) + } + + internalPrefixes := []string{} + if a.controllerConfig.AccountingExporter.Enabled && a.controllerConfig.AccountingExporter.NetworkTraffic.Enabled { + internalPrefixes = a.controllerConfig.AccountingExporter.NetworkTraffic.InternalNetworks + } + + fwcv, err := validation.ValidateFirewallControllerVersion(d.mcp.FirewallControllerVersions, d.infrastructureConfig.Firewall.ControllerVersion) + if err != nil { + return err + } + + deploy := &fcmv2.FirewallDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: metal.FirewallDeploymentName, + Namespace: namespace, + }, + Spec: fcmv2.FirewallDeploymentSpec{ + Template: fcmv2.FirewallTemplateSpec{ + Spec: fcmv2.FirewallSpec{ + Partition: d.infrastructureConfig.PartitionID, + Project: d.infrastructureConfig.ProjectID, + }, + }, + }, + } + + _, err = controllerutil.CreateOrUpdate(ctx, a.client, deploy, func() error { + if deploy.Labels == nil { + deploy.Labels = map[string]string{} + } + // this is the selector for the mutating webhook, without it the mutation will not happen + deploy.Labels[MutatingWebhookObjectSelectorLabel] = cluster.ObjectMeta.Name + + deploy.Spec.Replicas = 1 + + // we explicitly set the selector as otherwise firewall migration does not match, which should be prevented + deploy.Spec.Selector = map[string]string{ + tag.ClusterID: clusterID, + } + + if deploy.Spec.Template.Labels == nil { + deploy.Spec.Template.Labels = map[string]string{} + } + deploy.Spec.Template.Labels[tag.ClusterID] = clusterID + + deploy.Spec.Template.Spec.Size = d.infrastructureConfig.Firewall.Size + deploy.Spec.Template.Spec.Image = d.infrastructureConfig.Firewall.Image + deploy.Spec.Template.Spec.Networks = append(d.infrastructureConfig.Firewall.Networks, d.privateNetworkID) + deploy.Spec.Template.Spec.RateLimits = mapRateLimits(d.infrastructureConfig.Firewall.RateLimits) + deploy.Spec.Template.Spec.InternalPrefixes = internalPrefixes + deploy.Spec.Template.Spec.EgressRules = mapEgressRules(d.infrastructureConfig.Firewall.EgressRules) + deploy.Spec.Template.Spec.ControllerVersion = fwcv.Version + deploy.Spec.Template.Spec.ControllerURL = fwcv.URL + deploy.Spec.Template.Spec.NftablesExporterVersion = d.mcp.NftablesExporter.Version + deploy.Spec.Template.Spec.NftablesExporterURL = d.mcp.NftablesExporter.URL + deploy.Spec.Template.Spec.LogAcceptedConnections = d.infrastructureConfig.Firewall.LogAcceptedConnections + + return nil + }) + if err != nil { + return fmt.Errorf("error creating firewall deployment: %w", err) + } + + a.logger.Info("reconciled firewall deployment", "name", deploy.Name, "cluster-id", clusterID) + + return nil +} + +func mapRateLimits(limits []apismetal.RateLimit) []fcmv2.RateLimit { + var result []fcmv2.RateLimit + for _, l := range limits { + result = append(result, fcmv2.RateLimit{ + NetworkID: l.NetworkID, + Rate: l.RateLimit, + }) + } + return result +} + +func mapEgressRules(egress []apismetal.EgressRule) []fcmv2.EgressRuleSNAT { + var result []fcmv2.EgressRuleSNAT + for _, rule := range egress { + rule := rule + result = append(result, fcmv2.EgressRuleSNAT{ + NetworkID: rule.NetworkID, + IPs: rule.IPs, + }) + } + return result +} diff --git a/pkg/controller/worker/firewall_restore.go b/pkg/controller/worker/firewall_restore.go new file mode 100644 index 000000000..7b5fc8a30 --- /dev/null +++ b/pkg/controller/worker/firewall_restore.go @@ -0,0 +1,125 @@ +package worker + +import ( + "context" + "fmt" + + extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" + "github.com/gardener/gardener/extensions/pkg/util" + extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" + fcmv2 "github.com/metal-stack/firewall-controller-manager/api/v2" + "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal/validation" + "github.com/metal-stack/metal-go/api/client/firewall" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/metal-stack/metal-lib/pkg/tag" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func (a *actuator) firewallRestore(ctx context.Context, worker *extensionsv1alpha1.Worker, cluster *extensionscontroller.Cluster) error { + var ( + namespace = cluster.ObjectMeta.Name + + firewalls = &fcmv2.FirewallList{} + mons = &fcmv2.FirewallMonitorList{} + ) + + _, shootClient, err := util.NewClientForShoot(ctx, a.client, namespace, client.Options{}) + if err != nil { + return fmt.Errorf("unable to create shoot client: %w", err) + } + + err = shootClient.List(ctx, mons, &client.ListOptions{Namespace: fcmv2.FirewallShootNamespace}) + if err != nil { + return fmt.Errorf("error listing firewall resources: %w", err) + } + + err = a.client.List(ctx, firewalls, client.InNamespace(namespace)) + if err != nil { + return fmt.Errorf("error listing firewalls: %w", err) + } + + err = a.restoreFirewalls(ctx, worker, cluster, mons) + if err != nil { + return fmt.Errorf("error restoring firewall: %w", err) + } + + return nil +} + +func (a *actuator) restoreFirewalls(ctx context.Context, worker *extensionsv1alpha1.Worker, cluster *extensionscontroller.Cluster, mons *fcmv2.FirewallMonitorList) error { + d, err := a.getAdditionalData(ctx, worker, cluster) + if err != nil { + return fmt.Errorf("error getting additional data: %w", err) + } + + fwcv, err := validation.ValidateFirewallControllerVersion(d.mcp.FirewallControllerVersions, d.infrastructureConfig.Firewall.ControllerVersion) + if err != nil { + return err + } + + var ( + namespace = cluster.ObjectMeta.Name + clusterID = string(cluster.Shoot.GetUID()) + internalPrefixes = []string{} + ) + + if a.controllerConfig.AccountingExporter.Enabled && a.controllerConfig.AccountingExporter.NetworkTraffic.Enabled { + internalPrefixes = a.controllerConfig.AccountingExporter.NetworkTraffic.InternalNetworks + } + + for _, mon := range mons.Items { + resp, err := d.mclient.Firewall().FindFirewall(firewall.NewFindFirewallParams().WithID(mon.MachineStatus.MachineID).WithContext(ctx), nil) + if err != nil { + return fmt.Errorf("error finding firewall: %w", err) + } + + if pointer.SafeDeref(resp.Payload.Allocation.Project) != d.infrastructureConfig.ProjectID { + continue + } + + f := &fcmv2.Firewall{ + ObjectMeta: metav1.ObjectMeta{ + Name: mon.Name, + Namespace: namespace, + }, + } + + _, err = controllerutil.CreateOrUpdate(ctx, a.client, f, func() error { + if f.Labels == nil { + f.Labels = map[string]string{} + } + f.Labels[tag.ClusterID] = clusterID + + f.Spec = fcmv2.FirewallSpec{ + Size: d.infrastructureConfig.Firewall.Size, + Image: d.infrastructureConfig.Firewall.Image, + Partition: d.infrastructureConfig.PartitionID, + Project: d.infrastructureConfig.ProjectID, + Networks: append(d.infrastructureConfig.Firewall.Networks, d.privateNetworkID), + Userdata: resp.Payload.Allocation.UserData, + SSHPublicKeys: resp.Payload.Allocation.SSHPubKeys, + RateLimits: mapRateLimits(d.infrastructureConfig.Firewall.RateLimits), + InternalPrefixes: internalPrefixes, + EgressRules: mapEgressRules(d.infrastructureConfig.Firewall.EgressRules), + DryRun: false, + ControllerVersion: fwcv.Version, + ControllerURL: fwcv.URL, + NftablesExporterVersion: d.mcp.NftablesExporter.Version, + NftablesExporterURL: d.mcp.NftablesExporter.URL, + LogAcceptedConnections: d.infrastructureConfig.Firewall.LogAcceptedConnections, + DNSServerAddress: "", + DNSPort: nil, + } + return nil + }) + if err != nil { + return fmt.Errorf("error restoring firewall resource: %w", err) + } + + a.logger.Info("restored firewall", "name", f.Name, "cluster-id", clusterID) + } + + return nil +} diff --git a/pkg/controller/worker/helper.go b/pkg/controller/worker/helper.go index 64ec57cd4..1b80b3932 100644 --- a/pkg/controller/worker/helper.go +++ b/pkg/controller/worker/helper.go @@ -5,14 +5,107 @@ import ( "fmt" api "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal" + apismetal "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal" + "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal/helper" "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal/v1alpha1" + "github.com/metal-stack/gardener-extension-provider-metal/pkg/metal" + metalclient "github.com/metal-stack/gardener-extension-provider-metal/pkg/metal/client" + metalgo "github.com/metal-stack/metal-go" + extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" + extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" kutil "github.com/gardener/gardener/pkg/utils/kubernetes" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" cclient "sigs.k8s.io/controller-runtime/pkg/client" ) +type ( + additionalData struct { + privateNetworkID string + infrastructureConfig *apismetal.InfrastructureConfig + mcp *apismetal.MetalControlPlane + credentials *metal.Credentials + mclient metalgo.Client + } + + key int + + cacheKey struct { + nodeCIDR string + projectID string + } +) + +const ( + ClientKey key = iota +) + +func (a *actuator) getAdditionalData(ctx context.Context, worker *extensionsv1alpha1.Worker, cluster *extensionscontroller.Cluster) (*additionalData, error) { + cloudProfileConfig, err := helper.CloudProfileConfigFromCluster(cluster) + if err != nil { + return nil, err + } + + infrastructureConfig := &apismetal.InfrastructureConfig{} + if _, _, err := a.decoder.Decode(cluster.Shoot.Spec.Provider.InfrastructureConfig.Raw, nil, infrastructureConfig); err != nil { + return nil, err + } + + metalControlPlane, _, err := helper.FindMetalControlPlane(cloudProfileConfig, infrastructureConfig.PartitionID) + if err != nil { + return nil, err + } + + credentials, err := metalclient.ReadCredentialsFromSecretRef(ctx, a.client, &worker.Spec.SecretRef) + if err != nil { + return nil, err + } + + mclient, err := metalclient.NewClientFromCredentials(metalControlPlane.Endpoint, credentials) + if err != nil { + return nil, err + } + + // TODO: this is a workaround to speed things for the time being... + // the infrastructure controller writes the nodes cidr back into the infrastructure status, but the cluster resource does not contain it immediately + // it would need the start of another reconcilation until the node cidr can be picked up from the cluster resource + // therefore, we read it directly from the infrastructure status + infrastructure := &extensionsv1alpha1.Infrastructure{} + if err := a.client.Get(ctx, kutil.Key(worker.Namespace, cluster.Shoot.Name), infrastructure); err != nil { + return nil, err + } + + projectID := infrastructureConfig.ProjectID + nodeCIDR := infrastructure.Status.NodesCIDR + + if nodeCIDR == nil { + if cluster.Shoot.Spec.Networking.Nodes == nil { + return nil, fmt.Errorf("nodeCIDR was not yet set by infrastructure controller") + } + nodeCIDR = cluster.Shoot.Spec.Networking.Nodes + } + + nw, err := a.networkCache.Get(context.WithValue(ctx, ClientKey, mclient), &cacheKey{ + projectID: projectID, + nodeCIDR: *nodeCIDR, + }) + if err != nil { + return nil, err + } + + if nw.ID == nil { + return nil, fmt.Errorf("private network id is nil") + } + + return &additionalData{ + mcp: metalControlPlane, + infrastructureConfig: infrastructureConfig, + privateNetworkID: *nw.ID, + credentials: credentials, + }, nil +} + func (w *workerDelegate) decodeWorkerProviderStatus() (*api.WorkerStatus, error) { workerStatus := &api.WorkerStatus{} diff --git a/pkg/controller/worker/machine_dependencies.go b/pkg/controller/worker/machine_dependencies.go index eed38c4db..be0e6c686 100644 --- a/pkg/controller/worker/machine_dependencies.go +++ b/pkg/controller/worker/machine_dependencies.go @@ -2,16 +2,6 @@ package worker import ( "context" - "errors" - "fmt" - "time" - - retryutils "github.com/gardener/gardener/pkg/utils/retry" - fcmv2 "github.com/metal-stack/firewall-controller-manager/api/v2" - "github.com/metal-stack/gardener-extension-provider-metal/pkg/metal" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" ) // DeployMachineDependencies implements genericactuator.WorkerDelegate. @@ -21,43 +11,5 @@ func (w *workerDelegate) DeployMachineDependencies(_ context.Context) error { // CleanupMachineDependencies implements genericactuator.WorkerDelegate. func (w *workerDelegate) CleanupMachineDependencies(ctx context.Context) error { - if w.worker.DeletionTimestamp == nil { - return nil - } - - return w.ensureFirewallDeploymentDeleted(ctx) -} - -func (w *workerDelegate) ensureFirewallDeploymentDeleted(ctx context.Context) error { - w.logger.Info("ensuring firewall deployment gets deleted") - - return retryutils.UntilTimeout(ctx, 5*time.Second, 2*time.Minute, func(ctx context.Context) (bool, error) { - deploy := &fcmv2.FirewallDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: metal.FirewallDeploymentName, - Namespace: w.cluster.ObjectMeta.Name, - }, - } - - err := w.client.Get(ctx, client.ObjectKeyFromObject(deploy), deploy) - if err != nil { - if apierrors.IsNotFound(err) { - w.logger.Info("firewall deployment deletion succeeded") - return retryutils.Ok() - } - return retryutils.SevereError(fmt.Errorf("error getting firewall deployment: %w", err)) - } - - if deploy.DeletionTimestamp == nil { - w.logger.Info("deleting firewall deployment") - err = w.client.Delete(ctx, deploy) - if err != nil { - return retryutils.SevereError(fmt.Errorf("error deleting firewall deployment: %w", err)) - } - - return retryutils.MinorError(errors.New("firewall deployment is still ongoing")) - } - - return retryutils.MinorError(errors.New("firewall deployment is still ongoing")) - }) + return nil } diff --git a/pkg/controller/worker/machine_images.go b/pkg/controller/worker/machine_images.go index 619771652..32766d30f 100644 --- a/pkg/controller/worker/machine_images.go +++ b/pkg/controller/worker/machine_images.go @@ -14,7 +14,7 @@ import ( // UpdateMachineImagesStatus implements genericactuator.WorkerDelegate. func (w *workerDelegate) UpdateMachineImagesStatus(ctx context.Context) error { if w.machineImages == nil { - if err := w.generateMachineConfig(ctx); err != nil { + if err := w.generateMachineConfig(); err != nil { return fmt.Errorf("unable to generate the machine config %w", err) } } diff --git a/pkg/controller/worker/machines.go b/pkg/controller/worker/machines.go index f6ea932ac..15d4135d1 100644 --- a/pkg/controller/worker/machines.go +++ b/pkg/controller/worker/machines.go @@ -4,38 +4,18 @@ import ( "context" "fmt" "path/filepath" - "strconv" - "github.com/Masterminds/semver/v3" - metalgo "github.com/metal-stack/metal-go" - "github.com/metal-stack/metal-go/api/client/firewall" - "github.com/metal-stack/metal-go/api/models" - "github.com/metal-stack/metal-lib/pkg/tag" metaltag "github.com/metal-stack/metal-lib/pkg/tag" - apierrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" "github.com/gardener/gardener/extensions/pkg/controller/worker" - "github.com/gardener/gardener/extensions/pkg/util" - extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" apismetal "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal" - "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal/helper" - "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal/validation" - admissionregistrationv1 "k8s.io/api/admissionregistration/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/metal-stack/gardener-extension-provider-metal/pkg/metal" - metalclient "github.com/metal-stack/gardener-extension-provider-metal/pkg/metal/client" - - fcmv2 "github.com/metal-stack/firewall-controller-manager/api/v2" - v2 "github.com/metal-stack/firewall-controller-manager/api/v2" genericworkeractuator "github.com/gardener/gardener/extensions/pkg/controller/worker/genericactuator" v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" "github.com/gardener/gardener/pkg/client/kubernetes" - kutil "github.com/gardener/gardener/pkg/utils/kubernetes" machinev1alpha1 "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" ) @@ -62,7 +42,7 @@ func (w *workerDelegate) MachineClassList() client.ObjectList { // DeployMachineClasses generates and creates the metal specific machine classes. func (w *workerDelegate) DeployMachineClasses(ctx context.Context) error { if w.machineClasses == nil { - if err := w.generateMachineConfig(ctx); err != nil { + if err := w.generateMachineConfig(); err != nil { return err } } @@ -73,98 +53,22 @@ func (w *workerDelegate) DeployMachineClasses(ctx context.Context) error { } // GenerateMachineDeployments generates the configuration for the desired machine deployments. -func (w *workerDelegate) GenerateMachineDeployments(ctx context.Context) (worker.MachineDeployments, error) { +func (w *workerDelegate) GenerateMachineDeployments(_ context.Context) (worker.MachineDeployments, error) { if w.machineDeployments == nil { - if err := w.generateMachineConfig(ctx); err != nil { + if err := w.generateMachineConfig(); err != nil { return nil, err } } return w.machineDeployments, nil } -func (w *workerDelegate) generateMachineConfig(ctx context.Context) error { +func (w *workerDelegate) generateMachineConfig() error { var ( machineDeployments = worker.MachineDeployments{} machineClasses []map[string]interface{} machineImages []apismetal.MachineImage ) - cloudProfileConfig, err := helper.CloudProfileConfigFromCluster(w.cluster) - if err != nil { - return err - } - - infrastructureConfig := &apismetal.InfrastructureConfig{} - if _, _, err := w.decoder.Decode(w.cluster.Shoot.Spec.Provider.InfrastructureConfig.Raw, nil, infrastructureConfig); err != nil { - return err - } - - metalControlPlane, _, err := helper.FindMetalControlPlane(cloudProfileConfig, infrastructureConfig.PartitionID) - if err != nil { - return err - } - - credentials, err := metalclient.ReadCredentialsFromSecretRef(ctx, w.client, &w.worker.Spec.SecretRef) - if err != nil { - return err - } - - mclient, err := metalclient.NewClientFromCredentials(metalControlPlane.Endpoint, credentials) - if err != nil { - return err - } - - // TODO: this is a workaround to speed things for the time being... - // the infrastructure controller writes the nodes cidr back into the infrastructure status, but the cluster resource does not contain it immediately - // it would need the start of another reconcilation until the node cidr can be picked up from the cluster resource - // therefore, we read it directly from the infrastructure status - infrastructure := &extensionsv1alpha1.Infrastructure{} - if err := w.client.Get(ctx, kutil.Key(w.worker.Namespace, w.cluster.Shoot.Name), infrastructure); err != nil { - return err - } - - projectID := infrastructureConfig.ProjectID - nodeCIDR := infrastructure.Status.NodesCIDR - - if nodeCIDR == nil { - if w.cluster.Shoot.Spec.Networking.Nodes == nil { - return fmt.Errorf("nodeCIDR was not yet set by infrastructure controller") - } - nodeCIDR = w.cluster.Shoot.Spec.Networking.Nodes - } - - privateNetwork, err := metalclient.GetPrivateNetworkFromNodeNetwork(ctx, mclient, projectID, *nodeCIDR) - if err != nil { - return err - } - - if w.worker.DeletionTimestamp == nil && !extensionscontroller.IsHibernated(w.cluster) { - name := "firewall-controller-manager-" + w.cluster.ObjectMeta.Name - mwc := &admissionregistrationv1.MutatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - } - err := w.client.Get(ctx, client.ObjectKeyFromObject(mwc), mwc) - if err != nil { - if apierrors.IsNotFound(err) { - return fmt.Errorf("mutating webhook configuration %q of firewall-controller-manager is not yet present, requeuing", name) - } - - return err - } - - err = w.migrateFirewall(ctx, metalControlPlane, infrastructureConfig, w.cluster, mclient, *privateNetwork.ID) - if err != nil { - return err - } - - err = w.ensureFirewallDeployment(ctx, metalControlPlane, infrastructureConfig, w.cluster, *privateNetwork.ID) - if err != nil { - return err - } - } - for _, pool := range w.worker.Spec.Pools { workerPoolHash, err := worker.WorkerPoolHash(pool, w.cluster) if err != nil { @@ -184,13 +88,13 @@ func (w *workerDelegate) generateMachineConfig(ctx context.Context) error { var ( metalClusterIDTag = fmt.Sprintf("%s=%s", metaltag.ClusterID, w.cluster.Shoot.GetUID()) metalClusterNameTag = fmt.Sprintf("%s=%s", metaltag.ClusterName, w.worker.Namespace) - metalClusterProjectTag = fmt.Sprintf("%s=%s", metaltag.ClusterProject, infrastructureConfig.ProjectID) + metalClusterProjectTag = fmt.Sprintf("%s=%s", metaltag.ClusterProject, w.additionalData.infrastructureConfig.ProjectID) kubernetesClusterTag = fmt.Sprintf("kubernetes.io/cluster=%s", w.worker.Namespace) kubernetesRoleTag = "kubernetes.io/role=node" kubernetesInstanceTypeTag = fmt.Sprintf("node.kubernetes.io/instance-type=%s", pool.MachineType) kubernetesTopologyRegionTag = fmt.Sprintf("topology.kubernetes.io/region=%s", w.worker.Spec.Region) - kubernetesTopologyZoneTag = fmt.Sprintf("topology.kubernetes.io/zone=%s", infrastructureConfig.PartitionID) + kubernetesTopologyZoneTag = fmt.Sprintf("topology.kubernetes.io/zone=%s", w.additionalData.infrastructureConfig.PartitionID) ) tags := []string{ @@ -210,10 +114,10 @@ func (w *workerDelegate) generateMachineConfig(ctx context.Context) error { } machineClassSpec := map[string]interface{}{ - "partition": infrastructureConfig.PartitionID, + "partition": w.additionalData.infrastructureConfig.PartitionID, "size": pool.MachineType, - "project": projectID, - "network": privateNetwork.ID, + "project": w.additionalData.infrastructureConfig.ProjectID, + "network": w.additionalData.privateNetworkID, "image": machineImage, "tags": tags, "sshkeys": []string{string(w.worker.Spec.SSHPublicKey)}, @@ -252,9 +156,9 @@ func (w *workerDelegate) generateMachineConfig(ctx context.Context) error { // if we'd move the endpoint out of this secret into the deployment spec (which would be the way to go) // it would roll all worker nodes... - machineClassSpec["secret"].(map[string]interface{})["metalAPIURL"] = metalControlPlane.Endpoint - machineClassSpec["secret"].(map[string]interface{})[metal.APIKey] = credentials.MetalAPIKey - machineClassSpec["secret"].(map[string]interface{})[metal.APIHMac] = credentials.MetalAPIHMac + machineClassSpec["secret"].(map[string]interface{})["metalAPIURL"] = w.additionalData.mcp.Endpoint + machineClassSpec["secret"].(map[string]interface{})[metal.APIKey] = w.additionalData.credentials.MetalAPIKey + machineClassSpec["secret"].(map[string]interface{})[metal.APIHMac] = w.additionalData.credentials.MetalAPIHMac machineClasses = append(machineClasses, machineClassSpec) } @@ -264,335 +168,3 @@ func (w *workerDelegate) generateMachineConfig(ctx context.Context) error { return nil } - -// migrateFirewall can be removed along with the deployment of the old firewall resource after all firewalls are running firewall-controller >= v0.2.0 -func (w *workerDelegate) migrateFirewall(ctx context.Context, metalControlPlane *apismetal.MetalControlPlane, infrastructureConfig *apismetal.InfrastructureConfig, cluster *extensionscontroller.Cluster, mclient metalgo.Client, privateNetworkID string) error { - err := w.ensureMigrationFirewall(ctx, metalControlPlane, infrastructureConfig, cluster, mclient, privateNetworkID) - if err != nil { - return err - } - - return w.toggleAnnotation(ctx, cluster) -} - -func (w *workerDelegate) ensureMigrationFirewall(ctx context.Context, metalControlPlane *apismetal.MetalControlPlane, infrastructureConfig *apismetal.InfrastructureConfig, cluster *extensionscontroller.Cluster, mclient metalgo.Client, privateNetworkID string) error { - var ( - clusterID = string(cluster.Shoot.GetUID()) - projectID = infrastructureConfig.ProjectID - namespace = cluster.ObjectMeta.Name - ) - - resp, err := mclient.Firewall().FindFirewalls(firewall.NewFindFirewallsParams().WithBody(&models.V1FirewallFindRequest{ - AllocationProject: projectID, - Tags: []string{clusterTag(clusterID)}, - }).WithContext(ctx), nil) - if err != nil { - return fmt.Errorf("error finding firewall: %w", err) - } - - var toMigrate []*models.V1FirewallResponse - - for _, fw := range resp.Payload { - tm := tag.NewTagMap(fw.Tags) - - if value, _ := tm.Value(fcmv2.FirewallControllerManagedByAnnotation); value == v2.FirewallControllerManager { - // firewall is already owned by the firewall-cotroller-manager, does not need migration - continue - } - - toMigrate = append(toMigrate, fw) - } - - if len(toMigrate) == 0 { - w.logger.Info("no firewalls to be migrated to firewall-controller-manager") - return nil - } - - rateLimit := func(limits []apismetal.RateLimit) []fcmv2.RateLimit { - var result []fcmv2.RateLimit - for _, l := range limits { - result = append(result, fcmv2.RateLimit{ - NetworkID: l.NetworkID, - Rate: l.RateLimit, - }) - } - return result - } - - egressRules := func(egress []apismetal.EgressRule) []fcmv2.EgressRuleSNAT { - var result []fcmv2.EgressRuleSNAT - for _, rule := range egress { - rule := rule - result = append(result, fcmv2.EgressRuleSNAT{ - NetworkID: rule.NetworkID, - IPs: rule.IPs, - }) - } - return result - } - - fwcv, err := validation.ValidateFirewallControllerVersion(metalControlPlane.FirewallControllerVersions, infrastructureConfig.Firewall.ControllerVersion) - if err != nil { - return err - } - - internalPrefixes := []string{} - if w.controllerConfig.AccountingExporter.Enabled && w.controllerConfig.AccountingExporter.NetworkTraffic.Enabled { - internalPrefixes = w.controllerConfig.AccountingExporter.NetworkTraffic.InternalNetworks - } - - for _, fw := range toMigrate { - fw := fw - - f := &fcmv2.Firewall{ - ObjectMeta: metav1.ObjectMeta{ - Name: *fw.Allocation.Name, - Namespace: namespace, - }, - } - - _, err = controllerutil.CreateOrUpdate(ctx, w.client, f, func() error { - if f.Labels == nil { - f.Labels = map[string]string{} - } - f.Labels[tag.ClusterID] = clusterID - - if v, err := semver.NewVersion(fwcv.Version); err == nil && v.LessThan(semver.MustParse("v2.0.0")) { - f.Annotations = map[string]string{ - fcmv2.FirewallNoControllerConnectionAnnotation: "true", - } - } - f.Spec = fcmv2.FirewallSpec{ - Size: *fw.Size.ID, - Image: *fw.Allocation.Image.ID, - Partition: *fw.Partition.ID, - Project: *fw.Allocation.Project, - Networks: append(infrastructureConfig.Firewall.Networks, privateNetworkID), - Userdata: fw.Allocation.UserData, - SSHPublicKeys: fw.Allocation.SSHPubKeys, - RateLimits: rateLimit(infrastructureConfig.Firewall.RateLimits), - InternalPrefixes: internalPrefixes, - EgressRules: egressRules(infrastructureConfig.Firewall.EgressRules), - Interval: "10s", - DryRun: false, - Ipv4RuleFile: "", - ControllerVersion: fwcv.Version, - ControllerURL: fwcv.URL, - NftablesExporterVersion: metalControlPlane.NftablesExporter.Version, - NftablesExporterURL: metalControlPlane.NftablesExporter.URL, - LogAcceptedConnections: infrastructureConfig.Firewall.LogAcceptedConnections, - DNSServerAddress: "", - DNSPort: nil, - } - return nil - }) - if err != nil { - return fmt.Errorf("error creating firewall resource for firewall migration: %w", err) - } - - w.logger.Info("created firewall migration", "id", fw.ID, "cluster-id", clusterID) - - } - - return nil -} - -// toggleAnnotation removes the no-controller-connection annotation on the firewall -// when the firewall-controller has connected to the firewall which will happen as soon as -// the firewall-controller was updated to version >= 2.x. -// -// It adds the annotation when it's missing on a firewall-controller specified for a -// version < 2.x. -func (w *workerDelegate) toggleAnnotation(ctx context.Context, cluster *extensionscontroller.Cluster) error { - var ( - namespace = cluster.ObjectMeta.Name - ) - - firewalls := &v2.FirewallList{} - err := w.client.List(ctx, firewalls, &client.ListOptions{Namespace: namespace}) - if err != nil { - return fmt.Errorf("error listing firewall resources: %w", err) - } - - for _, fw := range firewalls.Items { - fw := fw - value, ok := fw.Annotations[v2.FirewallNoControllerConnectionAnnotation] - - if v, err := semver.NewVersion(fw.Spec.ControllerVersion); err == nil && v.LessThan(semver.MustParse("v2.0.0")) { - if ok { - continue - } - - _, err = controllerutil.CreateOrUpdate(ctx, w.client, &fw, func() error { - if fw.Annotations == nil { - fw.Annotations = map[string]string{} - } - fw.Annotations[fcmv2.FirewallNoControllerConnectionAnnotation] = "true" - - return nil - }) - - if err != nil { - return fmt.Errorf("unable to add no-controller-connection annotation on firewall %q: %w", fw.Name, err) - } - - continue - } - - if !ok { - continue - } - - active, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("unable to parse no-controller-connection annotation on firewall %q: %w", fw.Name, err) - } - - if !active { - continue - } - - shootConfig, _, err := util.NewClientForShoot(ctx, w.client, cluster.ObjectMeta.Name, client.Options{ - Scheme: w.scheme, - }) - if err != nil { - return fmt.Errorf("could not create shoot client config: %w", err) - } - - shootClient, err := client.New(shootConfig, client.Options{Scheme: w.scheme}) - if err != nil { - return fmt.Errorf("could not create shoot client: %w", err) - } - - mon := &v2.FirewallMonitor{ - ObjectMeta: metav1.ObjectMeta{ - Name: fw.Name, - Namespace: v2.FirewallShootNamespace, - }, - } - - err = shootClient.Get(ctx, client.ObjectKeyFromObject(mon), mon) - if err != nil { - return fmt.Errorf("could not get firewall monitor for firewall %q: %w", fw.Name, err) - } - - if mon.ControllerStatus == nil { - continue - } - - delete(fw.Annotations, v2.FirewallNoControllerConnectionAnnotation) - - err = w.client.Update(ctx, &fw) - if err != nil { - return fmt.Errorf("unable to toggle no-controller-connection annotation on firewall %q: %w", fw.Name, err) - } - - w.logger.Info("toggled no-controller-connection annotation on firewall because controller has connected", "firewall-name", fw.Name) - } - - return nil -} - -func (w *workerDelegate) ensureFirewallDeployment(ctx context.Context, metalControlPlane *apismetal.MetalControlPlane, infrastructureConfig *apismetal.InfrastructureConfig, cluster *extensionscontroller.Cluster, privateNetworkID string) error { - // why is this code here and not in the controlplane controller? - // the controlplane controller deploys the firewall-controller-manager including validating and mutating webhooks - // this has to be running before we can create a firewall deployment because the mutating webhook is creating the userdata - // the worker controller acts after the controlplane controller, also the terms and responsibilities are pretty similar between machine-controller-manager and firewall-controller-manager - var ( - clusterID = string(cluster.Shoot.GetUID()) - namespace = cluster.ObjectMeta.Name - - rateLimit = func(limits []apismetal.RateLimit) []fcmv2.RateLimit { - var result []fcmv2.RateLimit - for _, l := range limits { - result = append(result, fcmv2.RateLimit{ - NetworkID: l.NetworkID, - Rate: l.RateLimit, - }) - } - return result - } - - egressRules = func(egress []apismetal.EgressRule) []fcmv2.EgressRuleSNAT { - var result []fcmv2.EgressRuleSNAT - for _, rule := range egress { - rule := rule - result = append(result, fcmv2.EgressRuleSNAT{ - NetworkID: rule.NetworkID, - IPs: rule.IPs, - }) - } - return result - } - ) - - internalPrefixes := []string{} - if w.controllerConfig.AccountingExporter.Enabled && w.controllerConfig.AccountingExporter.NetworkTraffic.Enabled { - internalPrefixes = w.controllerConfig.AccountingExporter.NetworkTraffic.InternalNetworks - } - - fwcv, err := validation.ValidateFirewallControllerVersion(metalControlPlane.FirewallControllerVersions, infrastructureConfig.Firewall.ControllerVersion) - if err != nil { - return err - } - - deploy := &fcmv2.FirewallDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: metal.FirewallDeploymentName, - Namespace: namespace, - }, - Spec: fcmv2.FirewallDeploymentSpec{ - Template: fcmv2.FirewallTemplateSpec{ - Spec: fcmv2.FirewallSpec{ - Partition: infrastructureConfig.PartitionID, - Project: infrastructureConfig.ProjectID, - }, - }, - }, - } - - _, err = controllerutil.CreateOrUpdate(ctx, w.client, deploy, func() error { - if deploy.Labels == nil { - deploy.Labels = map[string]string{} - } - // this is the selector for the mutating webhook, without it the mutation will not happen - deploy.Labels[MutatingWebhookObjectSelectorLabel] = cluster.ObjectMeta.Name - - deploy.Spec.Replicas = 1 - - // we explicitly set the selector as otherwise firewall migration does not match, which should be prevented - deploy.Spec.Selector = map[string]string{ - tag.ClusterID: clusterID, - } - - if deploy.Spec.Template.Labels == nil { - deploy.Spec.Template.Labels = map[string]string{} - } - deploy.Spec.Template.Labels[tag.ClusterID] = clusterID - - deploy.Spec.Template.Spec.Size = infrastructureConfig.Firewall.Size - deploy.Spec.Template.Spec.Image = infrastructureConfig.Firewall.Image - deploy.Spec.Template.Spec.Networks = append(infrastructureConfig.Firewall.Networks, privateNetworkID) - deploy.Spec.Template.Spec.RateLimits = rateLimit(infrastructureConfig.Firewall.RateLimits) - deploy.Spec.Template.Spec.InternalPrefixes = internalPrefixes - deploy.Spec.Template.Spec.EgressRules = egressRules(infrastructureConfig.Firewall.EgressRules) - deploy.Spec.Template.Spec.ControllerVersion = fwcv.Version - deploy.Spec.Template.Spec.ControllerURL = fwcv.URL - deploy.Spec.Template.Spec.NftablesExporterVersion = metalControlPlane.NftablesExporter.Version - deploy.Spec.Template.Spec.NftablesExporterURL = metalControlPlane.NftablesExporter.URL - deploy.Spec.Template.Spec.LogAcceptedConnections = infrastructureConfig.Firewall.LogAcceptedConnections - - return nil - }) - if err != nil { - return fmt.Errorf("error creating firewall deployment: %w", err) - } - - w.logger.Info("created firewall deployment", "name", deploy.Name, "cluster-id", clusterID) - - return nil -} - -func clusterTag(clusterID string) string { - return fmt.Sprintf("%s=%s", tag.ClusterID, clusterID) -}