From 02ff98722a66b11fcf9ab08d9d3b5911b197b7e5 Mon Sep 17 00:00:00 2001 From: Simon Murray Date: Tue, 26 Nov 2024 14:08:13 +0000 Subject: [PATCH] Improve Cascading Cleanup Servers need to be deleted before networks, networks before identities etc. Kubernetes provides this via cascading foregroud deletion and blocking owner deletion. This basically removes a bunch of code where we were doing this ourselves. While it works via the API, be damn sure you use `kubectl delete --cascade=foreground` via the CLI if ever needed. Implements #75 --- .../identity-controller/clusterrole.yaml | 12 -- .../clusterrole.yaml | 8 -- pkg/handler/handler.go | 42 ++++-- pkg/handler/server/client.go | 4 +- pkg/handler/server/conversion.go | 11 +- pkg/openapi/schema.go | 110 +++++++-------- pkg/openapi/server.spec.yaml | 2 + pkg/providers/openstack/provider.go | 5 + .../managers/identity/provisioner.go | 125 +----------------- .../managers/network/provisioner.go | 8 ++ .../managers/security-group/provisioner.go | 51 +------ 11 files changed, 123 insertions(+), 255 deletions(-) diff --git a/charts/region/templates/identity-controller/clusterrole.yaml b/charts/region/templates/identity-controller/clusterrole.yaml index efa0db3..ab41885 100644 --- a/charts/region/templates/identity-controller/clusterrole.yaml +++ b/charts/region/templates/identity-controller/clusterrole.yaml @@ -31,18 +31,6 @@ rules: - create - update - delete -# Cascading deletion. -- apiGroups: - - region.unikorn-cloud.org - resources: - - quotas - - networks - - securitygroups - - servers - verbs: - - list - - watch - - delete - apiGroups: - "" resources: diff --git a/charts/region/templates/security-group-controller/clusterrole.yaml b/charts/region/templates/security-group-controller/clusterrole.yaml index 29066b0..2930dd9 100644 --- a/charts/region/templates/security-group-controller/clusterrole.yaml +++ b/charts/region/templates/security-group-controller/clusterrole.yaml @@ -39,14 +39,6 @@ rules: - create - update - delete -- apiGroups: - - region.unikorn-cloud.org - resources: - - securitygrouprules - verbs: - - list - - watch - - delete - apiGroups: - "" resources: diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 56052fd..0279ccd 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -46,6 +46,7 @@ import ( "github.com/unikorn-cloud/region/pkg/providers" kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/utils/ptr" @@ -53,6 +54,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) +var ( + foregroundDeleteOptions = &client.DeleteOptions{ + PropagationPolicy: ptr.To(metav1.DeletePropagationForeground), + } +) + type Handler struct { // client gives cached access to Kubernetes. client client.Client @@ -109,7 +116,7 @@ func (h *Handler) getNetwork(ctx context.Context, id string) (*unikornv1.Network return nil, errors.HTTPNotFound().WithError(err) } - return nil, errors.OAuth2ServerError("unable to physical network identity").WithError(err) + return nil, errors.OAuth2ServerError("unable to network identity").WithError(err) } return resource, nil @@ -493,7 +500,7 @@ func (h *Handler) DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDIdentit return } - if err := h.client.Delete(r.Context(), identity); err != nil { + if err := h.client.Delete(r.Context(), identity, foregroundDeleteOptions); err != nil { if kerrors.IsNotFound(err) { errors.HandleError(w, r, errors.HTTPNotFound().WithError(err)) return @@ -579,7 +586,7 @@ func (h *Handler) GetApiV1OrganizationsOrganizationIDNetworks(w http.ResponseWri } if err := h.client.List(r.Context(), &result, options); err != nil { - errors.HandleError(w, r, errors.OAuth2ServerError("unable to list physical networks").WithError(err)) + errors.HandleError(w, r, errors.OAuth2ServerError("unable to list networks").WithError(err)) return } @@ -650,8 +657,14 @@ func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitie network.Spec.Tags = generateTagList(request.Spec.Tags) } + // The resource belongs to its identity, for cascading deletion. + if err := controllerutil.SetOwnerReference(identity, network, h.client.Scheme(), controllerutil.WithBlockOwnerDeletion(true)); err != nil { + errors.HandleError(w, r, errors.OAuth2ServerError("unable to set resource owner").WithError(err)) + return + } + if err := h.client.Create(r.Context(), network); err != nil { - errors.HandleError(w, r, errors.OAuth2ServerError("unable to create physical network").WithError(err)) + errors.HandleError(w, r, errors.OAuth2ServerError("unable to create network").WithError(err)) return } @@ -685,13 +698,13 @@ func (h *Handler) DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDIdentit return } - if err := h.client.Delete(r.Context(), resource); err != nil { + if err := h.client.Delete(r.Context(), resource, foregroundDeleteOptions); err != nil { if kerrors.IsNotFound(err) { errors.HandleError(w, r, errors.HTTPNotFound().WithError(err)) return } - errors.HandleError(w, r, errors.OAuth2ServerError("unable to delete physical network").WithError(err)) + errors.HandleError(w, r, errors.OAuth2ServerError("unable to delete network").WithError(err)) return } @@ -819,7 +832,7 @@ func (h *Handler) generateQuota(ctx context.Context, organizationID, projectID s // Ensure the quota is owned by the identity so it is automatically cleaned // up on identity deletion. - if err := controllerutil.SetOwnerReference(identity, resource, h.client.Scheme()); err != nil { + if err := controllerutil.SetOwnerReference(identity, resource, h.client.Scheme(), controllerutil.WithBlockOwnerDeletion(true)); err != nil { return nil, err } @@ -1006,7 +1019,7 @@ func (h *Handler) generateSecurityGroup(ctx context.Context, organizationID, pro // Ensure the security is owned by the identity so it is automatically cleaned // up on identity deletion. - if err := controllerutil.SetOwnerReference(identity, resource, h.client.Scheme()); err != nil { + if err := controllerutil.SetOwnerReference(identity, resource, h.client.Scheme(), controllerutil.WithBlockOwnerDeletion(true)); err != nil { return nil, err } @@ -1076,7 +1089,7 @@ func (h *Handler) DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDIdentit return } - if err := h.client.Delete(r.Context(), resource); err != nil { + if err := h.client.Delete(r.Context(), resource, foregroundDeleteOptions); err != nil { if kerrors.IsNotFound(err) { errors.HandleError(w, r, errors.HTTPNotFound().WithError(err)) return @@ -1313,7 +1326,7 @@ func (h *Handler) generateSecurityGroupRule(ctx context.Context, organizationID, // Ensure the security is owned by the security group so it is automatically cleaned // up on security group deletion. - if err := controllerutil.SetOwnerReference(securityGroup, resource, h.client.Scheme()); err != nil { + if err := controllerutil.SetOwnerReference(securityGroup, resource, h.client.Scheme(), controllerutil.WithBlockOwnerDeletion(true)); err != nil { return nil, err } @@ -1456,7 +1469,14 @@ func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitie return } - result, err := server.NewClient(h.client, h.namespace).Create(r.Context(), organizationID, projectID, identity, request) + // NOTE: exactly 1 is enforced at the API schema level. + network, err := h.getNetwork(r.Context(), request.Spec.Networks[0].Id) + if err != nil { + errors.HandleError(w, r, err) + return + } + + result, err := server.NewClient(h.client, h.namespace).Create(r.Context(), organizationID, projectID, identity, network, request) if err != nil { errors.HandleError(w, r, err) return diff --git a/pkg/handler/server/client.go b/pkg/handler/server/client.go index 61859a2..f715be9 100644 --- a/pkg/handler/server/client.go +++ b/pkg/handler/server/client.go @@ -68,9 +68,9 @@ func (c *Client) List(ctx context.Context, organizationID string) (openapi.Serve return convertList(result), nil } -func (c *Client) Create(ctx context.Context, organizationID, projectID string, identity *unikornv1.Identity, request *openapi.ServerWrite) (*openapi.ServerRead, error) { +func (c *Client) Create(ctx context.Context, organizationID, projectID string, identity *unikornv1.Identity, network *unikornv1.Network, request *openapi.ServerWrite) (*openapi.ServerRead, error) { - resource, err := newGenerator(c.client, c.namespace, organizationID, projectID, identity).generate(ctx, request) + resource, err := newGenerator(c.client, c.namespace, organizationID, projectID, identity, network).generate(ctx, request) if err != nil { return nil, err } diff --git a/pkg/handler/server/conversion.go b/pkg/handler/server/conversion.go index 4456926..c2592ba 100644 --- a/pkg/handler/server/conversion.go +++ b/pkg/handler/server/conversion.go @@ -178,15 +178,18 @@ type generator struct { projectID string // identity is the identity the resource is provisioned for. identity *unikornv1.Identity + // network is the network tha resource is attacked to. + network *unikornv1.Network } -func newGenerator(client client.Client, namespace, organizationID, projectID string, identity *unikornv1.Identity) *generator { +func newGenerator(client client.Client, namespace, organizationID, projectID string, identity *unikornv1.Identity, network *unikornv1.Network) *generator { return &generator{ client: client, namespace: namespace, organizationID: organizationID, projectID: projectID, identity: identity, + network: network, } } @@ -210,9 +213,9 @@ func (g *generator) generate(ctx context.Context, in *openapi.ServerWrite) (*uni }, } - // Ensure the server is owned by the identity so it is automatically cleaned - // up on identity deletion. - if err := controllerutil.SetOwnerReference(g.identity, resource, g.client.Scheme()); err != nil { + // Ensure the server is owned by the network so it is automatically cleaned + // up on cascading deletion. + if err := controllerutil.SetOwnerReference(g.network, resource, g.client.Scheme(), controllerutil.WithBlockOwnerDeletion(true)); err != nil { return nil, err } diff --git a/pkg/openapi/schema.go b/pkg/openapi/schema.go index f89b2f4..e6864b1 100644 --- a/pkg/openapi/schema.go +++ b/pkg/openapi/schema.go @@ -96,61 +96,61 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/openapi/server.spec.yaml b/pkg/openapi/server.spec.yaml index 4270cc6..4b3b8e6 100644 --- a/pkg/openapi/server.spec.yaml +++ b/pkg/openapi/server.spec.yaml @@ -1217,6 +1217,8 @@ components: description: A list of networks. type: array minItems: 1 + # NOTE: this is pinned due to cascading deletion semantics, for now. + maxItems: 1 items: $ref: '#/components/schemas/serverNetwork' serverNetwork: diff --git a/pkg/providers/openstack/provider.go b/pkg/providers/openstack/provider.go index ee34001..fc495f7 100644 --- a/pkg/providers/openstack/provider.go +++ b/pkg/providers/openstack/provider.go @@ -799,6 +799,11 @@ func (p *Provider) DeleteIdentity(ctx context.Context, identity *unikornv1.Ident defer record() + // User never even created, so nothing else will have been. + if openstackIdentity.Spec.UserID == nil { + return nil + } + // Rescope to the user/project... providerClient := NewPasswordProvider(p.region.Spec.Openstack.Endpoint, *openstackIdentity.Spec.UserID, *openstackIdentity.Spec.Password, *openstackIdentity.Spec.ProjectID) diff --git a/pkg/provisioners/managers/identity/provisioner.go b/pkg/provisioners/managers/identity/provisioner.go index 819eda7..afb5a92 100644 --- a/pkg/provisioners/managers/identity/provisioner.go +++ b/pkg/provisioners/managers/identity/provisioner.go @@ -27,11 +27,9 @@ import ( "github.com/unikorn-cloud/region/pkg/constants" "github.com/unikorn-cloud/region/pkg/handler/region" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) // Provisioner encapsulates control plane provisioning. @@ -77,34 +75,16 @@ func (p *Provisioner) Provision(ctx context.Context) error { // Deprovision implements the Provision interface. func (p *Provisioner) Deprovision(ctx context.Context) error { - - cli, err := coreclient.ProvisionerClientFromContext(ctx) - if err != nil { - return err + // Wait for any owned resources to be cleaned up first. + if controllerutil.ContainsFinalizer(p.identity, metav1.FinalizerDeleteDependents) { + return provisioners.ErrYield } - identityRequirement, err := labels.NewRequirement(constants.IdentityLabel, selection.Equals, []string{p.identity.Name}) + cli, err := coreclient.ProvisionerClientFromContext(ctx) if err != nil { return err } - selector := labels.NewSelector() - selector = selector.Add(*identityRequirement) - - // Block identity deletion until all owned resources are deleted, we cannot guarantee - // the underlying cloud implementation will not just orphan them and leak resources. - if err := p.triggerServerDeletion(ctx, cli, selector); err != nil { - return err - } - - if err := p.triggerSecurityGroupDeletion(ctx, cli, selector); err != nil { - return err - } - - if err := p.triggerNetworkDeletion(ctx, cli, selector); err != nil { - return err - } - provider, err := region.NewClient(cli, p.identity.Namespace).Provider(ctx, p.identity.Labels[constants.RegionLabel]) if err != nil { return err @@ -116,96 +96,3 @@ func (p *Provisioner) Deprovision(ctx context.Context) error { return nil } - -func (p *Provisioner) triggerNetworkDeletion(ctx context.Context, cli client.Client, selector labels.Selector) error { - log := log.FromContext(ctx) - - var networks unikornv1.NetworkList - - if err := cli.List(ctx, &networks, &client.ListOptions{Namespace: p.identity.Namespace, LabelSelector: selector}); err != nil { - return err - } - - if len(networks.Items) != 0 { - for i := range networks.Items { - resource := &networks.Items[i] - - if resource.DeletionTimestamp != nil { - log.Info("awaiting physical network deletion", "physical network", resource.Name) - continue - } - - log.Info("triggering network deletion", "physical network", resource.Name) - - if err := cli.Delete(ctx, resource); err != nil { - return err - } - } - - return provisioners.ErrYield - } - - return nil -} - -func (p *Provisioner) triggerSecurityGroupDeletion(ctx context.Context, cli client.Client, selector labels.Selector) error { - log := log.FromContext(ctx) - - var securityGroups unikornv1.SecurityGroupList - - if err := cli.List(ctx, &securityGroups, &client.ListOptions{Namespace: p.identity.Namespace, LabelSelector: selector}); err != nil { - return err - } - - if len(securityGroups.Items) != 0 { - for i := range securityGroups.Items { - resource := &securityGroups.Items[i] - - if resource.DeletionTimestamp != nil { - log.Info("awaiting security group deletion", "security group", resource.Name) - continue - } - - log.Info("triggering security group deletion", "security group", resource.Name) - - if err := cli.Delete(ctx, resource); err != nil { - return err - } - } - - return provisioners.ErrYield - } - - return nil -} - -func (p *Provisioner) triggerServerDeletion(ctx context.Context, cli client.Client, selector labels.Selector) error { - log := log.FromContext(ctx) - - var servers unikornv1.ServerList - - if err := cli.List(ctx, &servers, &client.ListOptions{Namespace: p.identity.Namespace, LabelSelector: selector}); err != nil { - return err - } - - if len(servers.Items) != 0 { - for i := range servers.Items { - resource := &servers.Items[i] - - if resource.DeletionTimestamp != nil { - log.Info("awaiting server deletion", "server", resource.Name) - continue - } - - log.Info("triggering server deletion", "server", resource.Name) - - if err := cli.Delete(ctx, resource); err != nil { - return err - } - } - - return provisioners.ErrYield - } - - return nil -} diff --git a/pkg/provisioners/managers/network/provisioner.go b/pkg/provisioners/managers/network/provisioner.go index a67be6d..e00c2ac 100644 --- a/pkg/provisioners/managers/network/provisioner.go +++ b/pkg/provisioners/managers/network/provisioner.go @@ -29,7 +29,10 @@ import ( "github.com/unikorn-cloud/region/pkg/constants" "github.com/unikorn-cloud/region/pkg/handler/region" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -117,6 +120,11 @@ func (p *Provisioner) Provision(ctx context.Context) error { // Deprovision implements the Provision interface. func (p *Provisioner) Deprovision(ctx context.Context) error { + // Wait for any owned resources to be cleaned up first. + if controllerutil.ContainsFinalizer(p.network, metav1.FinalizerDeleteDependents) { + return provisioners.ErrYield + } + cli, err := coreclient.ProvisionerClientFromContext(ctx) if err != nil { return err diff --git a/pkg/provisioners/managers/security-group/provisioner.go b/pkg/provisioners/managers/security-group/provisioner.go index 743ff4e..1179196 100644 --- a/pkg/provisioners/managers/security-group/provisioner.go +++ b/pkg/provisioners/managers/security-group/provisioner.go @@ -29,8 +29,10 @@ import ( "github.com/unikorn-cloud/region/pkg/constants" "github.com/unikorn-cloud/region/pkg/handler/region" - "k8s.io/apimachinery/pkg/labels" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -116,7 +118,10 @@ func (p *Provisioner) Provision(ctx context.Context) error { // Deprovision implements the Provision interface. func (p *Provisioner) Deprovision(ctx context.Context) error { - log := log.FromContext(ctx) + // Wait for any owned resources to be cleaned up first. + if controllerutil.ContainsFinalizer(p.securitygroup, metav1.FinalizerDeleteDependents) { + return provisioners.ErrYield + } cli, err := coreclient.ProvisionerClientFromContext(ctx) if err != nil { @@ -128,31 +133,6 @@ func (p *Provisioner) Deprovision(ctx context.Context) error { return err } - // Block security group deletion until all owned resources are deleted - rules, err := p.listSecurityGroupRules(ctx, cli, identity) - if err != nil { - return err - } - - if len(rules.Items) != 0 { - for i := range rules.Items { - resource := &rules.Items[i] - - if resource.DeletionTimestamp != nil { - log.Info("awaiting security group rule deletion", "security group rule", resource.Name) - continue - } - - log.Info("triggering security group rule deletion", "security group rule", resource.Name) - - if err := cli.Delete(ctx, resource); err != nil { - return err - } - } - - return provisioners.ErrYield - } - provider, err := region.NewClient(cli, p.securitygroup.Namespace).Provider(ctx, p.securitygroup.Labels[constants.RegionLabel]) if err != nil { return err @@ -164,20 +144,3 @@ func (p *Provisioner) Deprovision(ctx context.Context) error { return nil } - -func (p *Provisioner) listSecurityGroupRules(ctx context.Context, cli client.Client, identity *unikornv1.Identity) (*unikornv1.SecurityGroupRuleList, error) { - var result unikornv1.SecurityGroupRuleList - - options := &client.ListOptions{ - LabelSelector: labels.SelectorFromSet(map[string]string{ - constants.IdentityLabel: identity.Name, - constants.SecurityGroupLabel: p.securitygroup.Name, - }), - } - - if err := cli.List(ctx, &result, options); err != nil { - return nil, err - } - - return &result, nil -}