Skip to content
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
40 changes: 25 additions & 15 deletions cmd/byohctl/client/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ const (
DefaultFilePerms = 0644
// DefaultDirPerms is the default directory permissions
DefaultDirPerms = 0755
// machineRefPollInterval is how long to wait between checks for a
// ByoHost's machineRef being cleared.
machineRefPollInterval = 5 * time.Second
)

// K8sClient handles Kubernetes API operations
Expand Down Expand Up @@ -309,7 +312,7 @@ func (client *Client) DeleteByoHostObject(namespace string) error {
}

// AnnotateMachineObject annotates the machine object with the given annotation
func (client *Client) AnnotateMachineObject(machineObj *unstructured.Unstructured, namespace, annotationKey, annotationValue string) error {
func (client *Client) AnnotateMachineObject(ctx context.Context, machineObj *unstructured.Unstructured, namespace, annotationKey, annotationValue string) error {
machineGVR := schema.GroupVersionResource{
Group: "cluster.x-k8s.io",
Version: "v1beta1",
Expand All @@ -325,7 +328,7 @@ func (client *Client) AnnotateMachineObject(machineObj *unstructured.Unstructure
machineObj.SetAnnotations(annotations)

// Update the machine object
_, err := client.DynamicClient.Resource(machineGVR).Namespace(namespace).Update(context.TODO(), machineObj, metav1.UpdateOptions{})
_, err := client.DynamicClient.Resource(machineGVR).Namespace(namespace).Update(ctx, machineObj, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("error updating machine object: %v", err)
}
Expand All @@ -334,8 +337,7 @@ func (client *Client) AnnotateMachineObject(machineObj *unstructured.Unstructure
}

// ScaleDownMachineDeployment scales down the machine deployment by 1
func (client *Client) ScaleDownMachineDeployment(machineObj *unstructured.Unstructured, namespace string) error {

func (client *Client) ScaleDownMachineDeployment(ctx context.Context, machineObj *unstructured.Unstructured, namespace string) error {
// Get machine deployment name from machine object
machineDeploymentName := machineObj.GetLabels()["cluster.x-k8s.io/deployment-name"]

Expand All @@ -349,7 +351,7 @@ func (client *Client) ScaleDownMachineDeployment(machineObj *unstructured.Unstru
}

// Get the machine deployment object
unstructuredDeploymentObj, err := client.DynamicClient.Resource(deploymentGVR).Namespace(namespace).Get(context.TODO(), machineDeploymentName, metav1.GetOptions{})
unstructuredDeploymentObj, err := client.DynamicClient.Resource(deploymentGVR).Namespace(namespace).Get(ctx, machineDeploymentName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("error getting machine deployment object: %v", err)
}
Expand All @@ -371,7 +373,7 @@ func (client *Client) ScaleDownMachineDeployment(machineObj *unstructured.Unstru
}

// Update the machine deployment object
_, err = client.DynamicClient.Resource(deploymentGVR).Namespace(namespace).Update(context.TODO(), updatedUnstructured, metav1.UpdateOptions{})
_, err = client.DynamicClient.Resource(deploymentGVR).Namespace(namespace).Update(ctx, updatedUnstructured, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("error updating machine deployment object: %v", err)
}
Expand All @@ -380,15 +382,15 @@ func (client *Client) ScaleDownMachineDeployment(machineObj *unstructured.Unstru
}

// GetMachineObject returns the machine object
func (client *Client) GetUnstructuredMachineObject(namespace, machineName string) (*unstructured.Unstructured, error) {
func (client *Client) GetUnstructuredMachineObject(ctx context.Context, namespace, machineName string) (*unstructured.Unstructured, error) {
machineGVR := schema.GroupVersionResource{
Group: "cluster.x-k8s.io",
Version: "v1beta1",
Resource: "machines",
}

// Get the machine object
unstructuredMachineObj, err := client.DynamicClient.Resource(machineGVR).Namespace(namespace).Get(context.TODO(), machineName, metav1.GetOptions{})
unstructuredMachineObj, err := client.DynamicClient.Resource(machineGVR).Namespace(namespace).Get(ctx, machineName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting machine object: %v", err)
}
Expand All @@ -397,8 +399,7 @@ func (client *Client) GetUnstructuredMachineObject(namespace, machineName string
}

// GetMachineDeploymentReplicaCount returns the replica count of the machine deployment
func (client *Client) GetMachineDeploymentReplicaCount(machineObj *unstructured.Unstructured, namespace string) (int32, error) {

func (client *Client) GetMachineDeploymentReplicaCount(ctx context.Context, machineObj *unstructured.Unstructured, namespace string) (int32, error) {
// Get machine deployment name from machine object
machineDeploymentName := machineObj.GetLabels()["cluster.x-k8s.io/deployment-name"]

Expand All @@ -412,7 +413,7 @@ func (client *Client) GetMachineDeploymentReplicaCount(machineObj *unstructured.
}

// Get the machine deployment object
unstructuredDeploymentObj, err := client.DynamicClient.Resource(deploymentGVR).Namespace(namespace).Get(context.TODO(), machineDeploymentName, metav1.GetOptions{})
unstructuredDeploymentObj, err := client.DynamicClient.Resource(deploymentGVR).Namespace(namespace).Get(ctx, machineDeploymentName, metav1.GetOptions{})
if err != nil {
return 0, fmt.Errorf("error getting machine deployment object: %v", err)
}
Expand All @@ -426,10 +427,15 @@ func (client *Client) GetMachineDeploymentReplicaCount(machineObj *unstructured.
}

// WaitForMachineRefToBeUnset waits for the machineRef to be unset from the byohost object status field
func (client *Client) WaitForMachineRefToBeUnset(byoHost *infrastructurev1beta1.ByoHost, namespace string) error {
func (client *Client) WaitForMachineRefToBeUnset(ctx context.Context, byoHost *infrastructurev1beta1.ByoHost, namespace string) error {
startTime := time.Now()

for {
// Stop polling once the caller gives up.
if err := ctx.Err(); err != nil {
return fmt.Errorf("waiting for machineRef to be unset: %w", err)
}

// Check if we've exceeded the timeout
if time.Since(startTime) > service.WaitForMachineRefToBeUnsetTimeout {
return fmt.Errorf("timeout waiting for machineRef to be unset")
Expand All @@ -449,20 +455,24 @@ func (client *Client) WaitForMachineRefToBeUnset(byoHost *infrastructurev1beta1.

// Wait a bit before checking again
utils.LogInfo("Waiting for machineRef to be unset...")
time.Sleep(5 * time.Second)
select {
case <-ctx.Done():
return fmt.Errorf("waiting for machineRef to be unset: %w", ctx.Err())
case <-time.After(machineRefPollInterval):
}
}
}

// CheckRegionAvailability checks if the region is available for the tenant
func (c *K8sClient) CheckRegionAvailability(regionName string) (bool, []string, error) {
func (c *K8sClient) CheckRegionAvailability(ctx context.Context, regionName string) (bool, []string, error) {
// Create a client from the kubeconfig
client, err := GetK8sClient(service.KubeconfigFilePath)
if err != nil {
return false, nil, fmt.Errorf("error creating Kubernetes client: %v", err)
}

// Get the region configmap from the management cluster from the tenant namespace
regionConfigMap, err := client.Clientset.CoreV1().ConfigMaps(c.getNamespace()).Get(context.TODO(), "region-config", metav1.GetOptions{})
regionConfigMap, err := client.Clientset.CoreV1().ConfigMaps(c.getNamespace()).Get(ctx, "region-config", metav1.GetOptions{})
if err != nil {
return false, nil, fmt.Errorf("error getting region configmap: %v", err)
}
Expand Down
22 changes: 11 additions & 11 deletions cmd/byohctl/client/k8s_unstructured_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func TestGetUnstructuredMachineObject(t *testing.T) {
dynamicClient := dynamicfake.NewSimpleDynamicClient(scheme, objs...)
c := &Client{DynamicClient: dynamicClient}

obj, err := c.GetUnstructuredMachineObject("ns1", "m1")
obj, err := c.GetUnstructuredMachineObject(t.Context(), "ns1", "m1")
if tt.wantErr {
assert.Error(t, err)
return
Expand All @@ -103,10 +103,10 @@ func TestAnnotateMachineObject(t *testing.T) {
dynamicClient := dynamicfake.NewSimpleDynamicClient(scheme, machine)
c := &Client{DynamicClient: dynamicClient}

obj, err := c.GetUnstructuredMachineObject("ns1", "m1")
obj, err := c.GetUnstructuredMachineObject(t.Context(), "ns1", "m1")
require.NoError(t, err)

err = c.AnnotateMachineObject(obj, "ns1", "byoh.platform9.io/decommissioned", "true")
err = c.AnnotateMachineObject(t.Context(), obj, "ns1", "byoh.platform9.io/decommissioned", "true")
require.NoError(t, err)

updated, err := dynamicClient.Resource(machineGVR).Namespace("ns1").Get(t.Context(), "m1", metav1.GetOptions{})
Expand All @@ -121,10 +121,10 @@ func TestGetMachineDeploymentReplicaCount(t *testing.T) {
dynamicClient := dynamicfake.NewSimpleDynamicClient(scheme, machine, deployment)
c := &Client{DynamicClient: dynamicClient}

obj, err := c.GetUnstructuredMachineObject("ns1", "m1")
obj, err := c.GetUnstructuredMachineObject(t.Context(), "ns1", "m1")
require.NoError(t, err)

count, err := c.GetMachineDeploymentReplicaCount(obj, "ns1")
count, err := c.GetMachineDeploymentReplicaCount(t.Context(), obj, "ns1")
require.NoError(t, err)
assert.Equal(t, int32(3), count)
}
Expand All @@ -135,10 +135,10 @@ func TestGetMachineDeploymentReplicaCount_MissingLabel(t *testing.T) {
dynamicClient := dynamicfake.NewSimpleDynamicClient(scheme, machine)
c := &Client{DynamicClient: dynamicClient}

obj, err := c.GetUnstructuredMachineObject("ns1", "m1")
obj, err := c.GetUnstructuredMachineObject(t.Context(), "ns1", "m1")
require.NoError(t, err)

_, err = c.GetMachineDeploymentReplicaCount(obj, "ns1")
_, err = c.GetMachineDeploymentReplicaCount(t.Context(), obj, "ns1")
assert.Error(t, err)
assert.Contains(t, err.Error(), "does not have a machine deployment name")
}
Expand All @@ -150,10 +150,10 @@ func TestScaleDownMachineDeployment(t *testing.T) {
dynamicClient := dynamicfake.NewSimpleDynamicClient(scheme, machine, deployment)
c := &Client{DynamicClient: dynamicClient}

obj, err := c.GetUnstructuredMachineObject("ns1", "m1")
obj, err := c.GetUnstructuredMachineObject(t.Context(), "ns1", "m1")
require.NoError(t, err)

err = c.ScaleDownMachineDeployment(obj, "ns1")
err = c.ScaleDownMachineDeployment(t.Context(), obj, "ns1")
require.NoError(t, err)

updatedUnstructured, err := dynamicClient.Resource(machineDeploymentGVR).Namespace("ns1").Get(t.Context(), "md1", metav1.GetOptions{})
Expand All @@ -170,10 +170,10 @@ func TestScaleDownMachineDeployment_MissingLabel(t *testing.T) {
dynamicClient := dynamicfake.NewSimpleDynamicClient(scheme, machine)
c := &Client{DynamicClient: dynamicClient}

obj, err := c.GetUnstructuredMachineObject("ns1", "m1")
obj, err := c.GetUnstructuredMachineObject(t.Context(), "ns1", "m1")
require.NoError(t, err)

err = c.ScaleDownMachineDeployment(obj, "ns1")
err = c.ScaleDownMachineDeployment(t.Context(), obj, "ns1")
assert.Error(t, err)
assert.Contains(t, err.Error(), "does not have a machine deployment name")
}
5 changes: 4 additions & 1 deletion cmd/byohctl/cmd/deauthorise.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright 2026 Platform9, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
Expand Down Expand Up @@ -38,7 +41,7 @@ func runDeauthorise(cmd *cobra.Command, args []string) {
os.Exit(1)
}

err = pkg.PerformHostOperation(pkg.OperationDeauthorise, namespace)
err = pkg.PerformHostOperation(cmd.Context(), pkg.OperationDeauthorise, namespace)
if err != nil {
fmt.Println("Failed to deauthorise host. " + err.Error())
os.Exit(1)
Expand Down
5 changes: 4 additions & 1 deletion cmd/byohctl/cmd/decommission.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright 2026 Platform9, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
Expand Down Expand Up @@ -38,7 +41,7 @@ func runDecommission(cmd *cobra.Command, args []string) {
os.Exit(1)
}

err = pkg.PerformHostOperation(pkg.OperationDecommission, namespace)
err = pkg.PerformHostOperation(cmd.Context(), pkg.OperationDecommission, namespace)
if err != nil {
fmt.Println("Failed to decommission host. " + err.Error())
os.Exit(1)
Expand Down
2 changes: 1 addition & 1 deletion cmd/byohctl/cmd/onboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ func runOnboard(cmd *cobra.Command, args []string) {

// Check if region where user wants to onboard to is available for this tenant or not
// If not available, roll back the onboarding process
available, regions, err := k8sClient.CheckRegionAvailability(regionName)
available, regions, err := k8sClient.CheckRegionAvailability(cmd.Context(), regionName)
if err != nil {
utils.LogError("Failed to check region availability, rolling back onboarding process: %v", err)
if err := k8sClient.DeleteSavedKubeconfig(); err != nil {
Expand Down
20 changes: 12 additions & 8 deletions cmd/byohctl/pkg/host_operations.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Copyright 2026 Platform9, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package pkg

import (
"context"
"fmt"
"os"

Expand All @@ -17,7 +21,7 @@ const (
)

// PerformHostOperation performs the common steps for host deauthorisation or decommissioning
func PerformHostOperation(operationType HostOperationType, namespace string) error {
func PerformHostOperation(ctx context.Context, operationType HostOperationType, namespace string) error {

// Deauthorise and decommission host steps -
// 1. Authenticate with Platform9 with the kubeconfig present in the agent directory ( kubeconfig )
Expand Down Expand Up @@ -93,7 +97,7 @@ func PerformHostOperation(operationType HostOperationType, namespace string) err
machineName := byoHost.Status.MachineRef.Name

// Get the machine object ( unstructured )
unstructuredMachineObj, err := client.GetUnstructuredMachineObject(namespace, machineName)
unstructuredMachineObj, err := client.GetUnstructuredMachineObject(ctx, namespace, machineName)
if err != nil {
return fmt.Errorf("failed to get machine object: %v", err)
}
Expand All @@ -106,7 +110,7 @@ func PerformHostOperation(operationType HostOperationType, namespace string) err
// So when doing de-auth, check if the node count in the workload cluster and stop the de-auth if that is last node.

// Check machine deployment replica count. If it is 1, then warn and ask the user to continue de-uth or not.
replicaCount, err := client.GetMachineDeploymentReplicaCount(unstructuredMachineObj, namespace)
replicaCount, err := client.GetMachineDeploymentReplicaCount(ctx, unstructuredMachineObj, namespace)
if err != nil {
return fmt.Errorf("failed to get machine deployment replica count: %v", err)
}
Expand All @@ -124,36 +128,36 @@ func PerformHostOperation(operationType HostOperationType, namespace string) err
}

// Since this is the last machine in the cluster, annotate machine objects to exclude the node drain
err = client.AnnotateMachineObject(unstructuredMachineObj, namespace, "machine.cluster.x-k8s.io/exclude-node-draining", "")
err = client.AnnotateMachineObject(ctx, unstructuredMachineObj, namespace, "machine.cluster.x-k8s.io/exclude-node-draining", "")
if err != nil {
return fmt.Errorf("failed to annotate the last machine object to be deauth: %v", err)
}
}

// Get the fresh machine object from the server to get the updated machine object
unstructuredMachineObj, err = client.GetUnstructuredMachineObject(namespace, machineName)
unstructuredMachineObj, err = client.GetUnstructuredMachineObject(ctx, namespace, machineName)
if err != nil {
return fmt.Errorf("failed to get machine object: %v", err)
}

// 5. Annonate the respective machine object with "cluster.x-k8s.io/delete-machine"="yes"
err = client.AnnotateMachineObject(unstructuredMachineObj, namespace, "cluster.x-k8s.io/delete-machine", "yes")
err = client.AnnotateMachineObject(ctx, unstructuredMachineObj, namespace, "cluster.x-k8s.io/delete-machine", "yes")
if err != nil {
return fmt.Errorf("failed to annotate machine object: %v", err)
}

utils.LogSuccess("Successfully annotated machine object that needs to be removed from the cluster")

// 6. Scale down the machine deployment by 1
err = client.ScaleDownMachineDeployment(unstructuredMachineObj, namespace)
err = client.ScaleDownMachineDeployment(ctx, unstructuredMachineObj, namespace)
if err != nil {
return fmt.Errorf("failed to scale down machine deployment: %v", err)
}

utils.LogSuccess("Successfully scaled down machine deployment by 1")

// 7. Wait for machineRef to be unset from the byohost object status field
err = client.WaitForMachineRefToBeUnset(byoHost, namespace)
err = client.WaitForMachineRefToBeUnset(ctx, byoHost, namespace)
if err != nil {
return fmt.Errorf("failed to wait for machineRef to be unset: %v", err)
}
Expand Down
Loading