Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Server from registry once reached Available #31

Merged
merged 1 commit into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"flag"
"fmt"
"os"
"time"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
Expand Down Expand Up @@ -67,7 +68,9 @@ func main() {
var registryPort int
var registryProtocol string
var registryURL string
var requeueInterval time.Duration

flag.DurationVar(&requeueInterval, "requeue-interval", 10*time.Second, "Reconciler requeue interval.")
flag.StringVar(&registryURL, "registry-url", "", "The URL of the registry.")
flag.StringVar(&registryProtocol, "registry-protocol", "http", "The protocol to use for the registry.")
flag.IntVar(&registryPort, "registry-port", 10000, "The port to use for the registry.")
Expand Down Expand Up @@ -203,6 +206,7 @@ func main() {
ProbeImage: probeImage,
ProbeOSImage: probeOSImage,
RegistryURL: registryURL,
RequeueInterval: requeueInterval,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Server")
os.Exit(1)
Expand Down
3 changes: 0 additions & 3 deletions config/crd/bases/metal.ironcore.dev_bmcs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ spec:
- jsonPath: .status.ip
name: MACAddress
type: string
- jsonPath: .status.manufacturer
name: Manufacturer
type: string
- jsonPath: .status.model
name: Model
type: string
Expand Down
3 changes: 1 addition & 2 deletions internal/controller/bmc_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,7 @@ func (r *BMCReconciler) discoverServers(ctx context.Context, bmcObj *metalv1alph
func (r *BMCReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&metalv1alpha1.BMC{}).
// TODO: don't recreate Server if deleted manually
//Owns(&metalv1alpha1.Server{}).
Owns(&metalv1alpha1.Server{}).
// TODO: add watches for Endpoints and BMCSecrets
Complete(r)
}
37 changes: 34 additions & 3 deletions internal/controller/server_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"

Expand Down Expand Up @@ -61,6 +62,7 @@ type ServerReconciler struct {
ProbeImage string
RegistryURL string
ProbeOSImage string
RequeueInterval time.Duration
}

//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=bmcs,verbs=get;list;watch
Expand Down Expand Up @@ -149,7 +151,7 @@ func (r *ServerReconciler) reconcile(ctx context.Context, log logr.Logger, serve

requeue, err := r.ensureServerStateTransition(ctx, log, server)
if requeue && err == nil {
return ctrl.Result{Requeue: requeue, RequeueAfter: 10 * time.Second}, nil
return ctrl.Result{Requeue: requeue, RequeueAfter: r.RequeueInterval}, nil
}
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to ensure server state transition: %w", err)
Expand Down Expand Up @@ -216,7 +218,6 @@ func (r *ServerReconciler) ensureServerStateTransition(ctx context.Context, log
}
if err != nil {
log.V(1).Info("Could not get server details from registry.")
// TODO: instead of requeue subscribe to registry events and requeue Server objects in SetupWithManager
return false, err
}
log.V(1).Info("Extracted Server details")
Expand All @@ -229,10 +230,15 @@ func (r *ServerReconciler) ensureServerStateTransition(ctx context.Context, log
// TODO: fix that by providing the power state to the ensure method
server.Spec.Power = metalv1alpha1.PowerOff
if err := r.ensureServerPowerState(ctx, log, server); err != nil {
return false, fmt.Errorf("failed to shutdown server: %w", err)
return false, fmt.Errorf("failed to ensure server power state: %w", err)
}
log.V(1).Info("Server state set to power off")

if err := r.invalidateRegistryEntryForServer(log, server); err != nil {
return false, fmt.Errorf("failed to invalidate registry entry for server: %w", err)
}
log.V(1).Info("Removed Server from Registry")

log.V(1).Info("Setting Server state set to available")
if modified, err := r.patchServerState(ctx, server, metalv1alpha1.ServerStateAvailable); err != nil || modified {
return false, err
Expand Down Expand Up @@ -518,6 +524,31 @@ func (r *ServerReconciler) ensureInitialBootConfigurationIsDeleted(ctx context.C
return nil
}

func (r *ServerReconciler) invalidateRegistryEntryForServer(log logr.Logger, server *metalv1alpha1.Server) error {
url := fmt.Sprintf("%s/delete/%s", r.RegistryURL, server.Spec.UUID)

c := &http.Client{}

// Create the DELETE request
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return err
}

// Send the request
resp, err := c.Do(req)
if err != nil {
return err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
log.Error(err, "Failed to close response body")
}
}(resp.Body)
return nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *ServerReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Expand Down
7 changes: 7 additions & 0 deletions internal/controller/server_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package controller

import (
"fmt"
"net/http"

apierrors "k8s.io/apimachinery/pkg/api/errors"

Expand Down Expand Up @@ -74,6 +75,7 @@ var _ = Describe("Server Controller", func() {
},
}
Eventually(Object(bootConfig)).Should(SatisfyAll(
HaveField("Finalizers", ContainElement(ServerBootConfigurationFinalizer)),
HaveField("Spec.ServerRef", v1.LocalObjectReference{Name: server.Name}),
HaveField("Spec.Image", "fooOS:latest"),
HaveField("Spec.IgnitionSecretRef", &v1.LocalObjectReference{Name: server.Name}),
Expand Down Expand Up @@ -157,6 +159,11 @@ var _ = Describe("Server Controller", func() {
},
}
Consistently(Get(config)).Should(Satisfy(apierrors.IsNotFound))

By("Ensuring that the server is removed from the registry")
response, err := http.Get(registryURL + "/systems/" + server.Spec.UUID)
Expect(err).NotTo(HaveOccurred())
Expect(response.StatusCode).To(Equal(http.StatusNotFound))
})

// TODO: test server with manual BMC registration
Expand Down
20 changes: 19 additions & 1 deletion internal/controller/serverbootconfiguration_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import (
"context"
"fmt"

"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/handler"

"github.com/ironcore-dev/controller-utils/clientutils"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -149,7 +152,7 @@ func (r *ServerBootConfigurationReconciler) removeServerBootConfigRef(ctx contex

serverBase := server.DeepCopy()
server.Spec.BootConfigurationRef = nil
if err := r.Patch(ctx, server, client.MergeFrom(serverBase)); !apierrors.IsNotFound(err) {
if err := r.Patch(ctx, server, client.MergeFrom(serverBase)); err != nil {
return err
}

Expand All @@ -160,5 +163,20 @@ func (r *ServerBootConfigurationReconciler) removeServerBootConfigRef(ctx contex
func (r *ServerBootConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&metalv1alpha1.ServerBootConfiguration{}).
Watches(&metalv1alpha1.Server{}, r.enqueueServerBootConfigByServerRef()).
Complete(r)
}

func (r *ServerBootConfigurationReconciler) enqueueServerBootConfigByServerRef() handler.EventHandler {
return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []ctrl.Request {
server := obj.(*metalv1alpha1.Server)
if server.Spec.BootConfigurationRef != nil {
return []ctrl.Request{
{
NamespacedName: types.NamespacedName{Namespace: server.Spec.BootConfigurationRef.Namespace, Name: server.Spec.BootConfigurationRef.Name},
},
}
}
return nil
})
}
5 changes: 3 additions & 2 deletions internal/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ import (
)

const (
pollingInterval = 50 * time.Millisecond
eventuallyTimeout = 5 * time.Second
pollingInterval = 100 * time.Millisecond
eventuallyTimeout = 3 * time.Second
consistentlyDuration = 1 * time.Second
)

Expand Down Expand Up @@ -182,6 +182,7 @@ func SetupTest() *corev1.Namespace {
ProbeImage: "foo:latest",
ProbeOSImage: "fooOS:latest",
RegistryURL: registryURL,
RequeueInterval: 100 * time.Millisecond,
}).SetupWithManager(k8sManager)).To(Succeed())

Expect((&ServerClaimReconciler{
Expand Down
26 changes: 26 additions & 0 deletions internal/registry/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func NewServer(addr string) *Server {
// routes registers the server's routes.
func (s *Server) routes() {
s.mux.HandleFunc("/register", s.registerHandler)
s.mux.HandleFunc("/delete/", s.deleteHandler)
s.mux.HandleFunc("/systems/", s.systemsHandler)
}

Expand Down Expand Up @@ -107,6 +108,31 @@ func (s *Server) systemsHandler(w http.ResponseWriter, r *http.Request) {
}
}

// deleteHandler handles the DELETE requests to remove a system by UUID.
func (s *Server) deleteHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Received method: %s", r.Method) // This will log the method of the request
log.Printf("Requested URI: %s", r.RequestURI) // This logs the full request URI

if r.Method != http.MethodDelete {
http.Error(w, "Only DELETE method is allowed", http.StatusMethodNotAllowed)
return
}

uuid := r.URL.Path[len("/delete/"):] // Assuming the URL is like /delete/{uuid}

// Attempt to delete the entry from the store
if _, ok := s.systemsStore.Load(uuid); !ok {
http.NotFound(w, r)
return
}

s.systemsStore.Delete(uuid) // Perform the deletion

// Respond with success message
w.WriteHeader(http.StatusOK)
log.Printf("System with UUID %s deleted successfully", uuid)
}

// Start starts the server on the specified address and adds logging for key events.
func (s *Server) Start(ctx context.Context) error {
log.Printf("Starting registry server on port %s\n", s.addr)
Expand Down
13 changes: 13 additions & 0 deletions internal/registry/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,18 @@ var _ = Describe("RegistryServer", func() {
},
},
}))

By("Ensuring that the server is removed from the registry")
request, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/delete/%s", testServerURL, systemRegistrationPayload.SystemUUID), nil)
Expect(err).NotTo(HaveOccurred())
c := &http.Client{}
response, err = c.Do(request)
Expect(err).NotTo(HaveOccurred())
Expect(response.StatusCode).To(Equal(http.StatusOK))

By("Ensuring that the server is removed from the registry")
response, err = http.Get(fmt.Sprintf("%s/systems/%s", testServerURL, systemRegistrationPayload.SystemUUID))
Expect(err).NotTo(HaveOccurred())
Expect(response.StatusCode).To(Equal(http.StatusNotFound))
})
})
Loading