Skip to content

Commit

Permalink
Merge pull request #37 from alperencelik/external-watcher-implentation
Browse files Browse the repository at this point in the history
feat: Add external watcher for Proxmox resources
  • Loading branch information
alperencelik authored Jul 5, 2024
2 parents 5396478 + cb88b7c commit ea2b9bd
Show file tree
Hide file tree
Showing 13 changed files with 501 additions and 119 deletions.
11 changes: 10 additions & 1 deletion api/proxmox/v1alpha1/container_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,16 @@ type ContainerSpec struct {
NodeName string `json:"nodeName"`
// TemplateSpec of the source Container
Template ContainerTemplate `json:"template,omitempty"`
// This field should be modified further
// DeletionProtection is a flag that indicates whether the resource should be protected from deletion.
// If true, the resource will not be deleted when the Kubernetes resource is deleted.
// If not set, it defaults to false.
// +kubebuilder:default:=false
DeletionProtection bool `json:"deletionProtection,omitempty"`
// EnableAutoStart is a flag that indicates whether the resource should automatically start when it's powered off.
// If true, the resource will start automatically when it's powered off.
// If not set, it defaults to true.
// +kubebuilder:default:=true
EnableAutoStart bool `json:"enableAutoStart,omitempty"`
}

type ContainerTemplate struct {
Expand Down
18 changes: 15 additions & 3 deletions charts/kubemox/templates/crds/containers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ kind: CustomResourceDefinition
metadata:
name: containers.proxmox.alperen.cloud
spec:
conversion:
strategy: None
group: proxmox.alperen.cloud
names:
kind: Container
Expand Down Expand Up @@ -63,6 +61,20 @@ spec:
spec:
description: ContainerSpec defines the desired state of Container
properties:
deletionProtection:
default: false
description: DeletionProtection is a flag that indicates whether the
resource should be protected from deletion. If true, the resource
will not be deleted when the Kubernetes resource is deleted. If
not set, it defaults to false.
type: boolean
enableAutoStart:
default: true
description: EnableAutoStart is a flag that indicates whether the
resource should automatically start when it's powered off. If true,
the resource will start automatically when it's powered off. If
not set, it defaults to true.
type: boolean
name:
description: Name is the name of the Container
type: string
Expand Down Expand Up @@ -127,7 +139,7 @@ spec:
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions ]metav1.Condition
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties:
Expand Down
17 changes: 10 additions & 7 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
proxmoxv1alpha1 "github.com/alperencelik/kubemox/api/proxmox/v1alpha1"
proxmoxcontroller "github.com/alperencelik/kubemox/internal/controller/proxmox"
_ "github.com/alperencelik/kubemox/pkg/kubernetes"
_ "github.com/alperencelik/kubemox/pkg/proxmox"
"github.com/alperencelik/kubemox/pkg/proxmox"
"github.com/alperencelik/kubemox/pkg/utils"
//+kubebuilder:scaffold:imports
)
Expand Down Expand Up @@ -96,15 +96,17 @@ func main() {
setupLog.Info("Pod namespace has been found as:", "POD_NAMESPACE", PodNamespace)

if err = (&proxmoxcontroller.VirtualMachineReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Watchers: proxmox.NewExternalWatchers(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "VirtualMachine")
os.Exit(1)
}
if err = (&proxmoxcontroller.ManagedVirtualMachineReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Watchers: proxmox.NewExternalWatchers(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ManagedVirtualMachine")
os.Exit(1)
Expand All @@ -131,8 +133,9 @@ func main() {
os.Exit(1)
}
if err = (&proxmoxcontroller.ContainerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Watchers: proxmox.NewExternalWatchers(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Container")
os.Exit(1)
Expand Down
14 changes: 14 additions & 0 deletions config/crd/bases/proxmox.alperen.cloud_containers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ spec:
spec:
description: ContainerSpec defines the desired state of Container
properties:
deletionProtection:
default: false
description: DeletionProtection is a flag that indicates whether the
resource should be protected from deletion. If true, the resource
will not be deleted when the Kubernetes resource is deleted. If
not set, it defaults to false.
type: boolean
enableAutoStart:
default: true
description: EnableAutoStart is a flag that indicates whether the
resource should automatically start when it's powered off. If true,
the resource will start automatically when it's powered off. If
not set, it defaults to true.
type: boolean
name:
description: Name is the name of the Container
type: string
Expand Down
4 changes: 4 additions & 0 deletions docs/crds/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ metadata:
spec:
name: container-new
nodeName: lowtower
# Deletion protection is whether to delete VM from Proxmox or not
deleteProtection: false
# VM should be started any time found in stopped state
enableAutostart: true
template:
# Name of the template to be cloned
name: test-container
Expand Down
2 changes: 2 additions & 0 deletions examples/container.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ metadata:
spec:
name: container-new
nodeName: lowtower
deletionProtection: false
enableAutoStart: true
template:
# Name of the template to be cloned
name: test-container
Expand Down
6 changes: 3 additions & 3 deletions examples/virtualmachineclone.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ spec:
memory: 4096 # As MB
# Disk used by the VM
disk:
- storage: nvme
- storage: local-lvm
size: 50 # As GB
device: scsi0
- storage: nvme
- storage: local-lvm
size: 20 # As GB
device: scsi1
# Network interfaces used by the VM
network:
- model: virtio
bridge: vmbr0
bridge: vmbr0
87 changes: 74 additions & 13 deletions internal/controller/proxmox/container_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@ import (
// ContainerReconciler reconciles a Container object
type ContainerReconciler struct {
client.Client
Scheme *runtime.Scheme
Scheme *runtime.Scheme
Watchers *proxmox.ExternalWatchers
}

const (
containerFinalizerName = "container.proxmox.alperen.cloud/finalizer"

typeDeletingContainer = "Deleting"
typeAvailableContainer = "Available"
typeStoppedContainer = "stopped"
typeCreatingContainer = "Creating"
typeErrorContainer = "Error"
)
Expand Down Expand Up @@ -76,6 +78,9 @@ func (r *ContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
}
logger.Info(fmt.Sprintf("Reconciling Container %s", container.Name))

// Handle the external watcher for the Container
r.handleWatcher(ctx, req, container)

containerName := container.Spec.Name
nodeName := container.Spec.NodeName

Expand All @@ -91,7 +96,7 @@ func (r *ContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
// The object is being deleted
if controllerutil.ContainsFinalizer(container, containerFinalizerName) {
// Delete the Container
logger.Info("Deleting Container", "name", container.Name)
logger.Info("Deleting Container", "name", containerName)

if !meta.IsStatusConditionPresentAndEqual(container.Status.Conditions, typeDeletingContainer, metav1.ConditionTrue) {
meta.SetStatusCondition(&container.Status.Conditions, metav1.Condition{
Expand All @@ -106,7 +111,13 @@ func (r *ContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
}
return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err)
}
proxmox.DeleteContainer(containerName, nodeName)
// Stop the watcher if resource is being deleted
if stopChan, exists := r.Watchers.Watchers[req.Name]; exists {
close(stopChan)
delete(r.Watchers.Watchers, req.Name)
}
// Handle deletion of the Container
r.handleContainerDeletion(ctx, container)
// Remove finalizer
controllerutil.RemoveFinalizer(container, containerFinalizerName)
if err = r.Update(ctx, container); err != nil {
Expand All @@ -131,12 +142,6 @@ func (r *ContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
}
}

// Update Container Status
err = r.UpdateContainerStatus(ctx, container)
if err != nil {
logger.Error(err, "Failed to update Container status")
return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err)
}
return ctrl.Result{}, client.IgnoreNotFound(err)
}

Expand Down Expand Up @@ -190,7 +195,7 @@ func (r *ContainerReconciler) UpdateContainerStatus(ctx context.Context, contain
ID: containerStatus.ID,
}
container.Status.Status = qemuStatus
// // Update Container
// Update Container
err := r.Status().Update(ctx, container)
if err != nil {
return err
Expand Down Expand Up @@ -222,13 +227,12 @@ func (r *ContainerReconciler) handleFinalizer(ctx context.Context, container *pr

func (r *ContainerReconciler) StartOrUpdateContainer(ctx context.Context,
container *proxmoxv1alpha1.Container) error {
//
logger := log.FromContext(ctx)
containerName := container.Spec.Name
nodeName := container.Spec.NodeName
// Update Container
containerState := proxmox.GetContainerState(containerName, nodeName)
if containerState == "stopped" {
if containerState == typeStoppedContainer {
err := proxmox.StartContainer(containerName, nodeName)
if err != nil {
logger.Error(err, "Failed to start Container")
Expand All @@ -247,7 +251,6 @@ func (r *ContainerReconciler) StartOrUpdateContainer(ctx context.Context,
}

func (r *ContainerReconciler) handleCloneContainer(ctx context.Context, container *proxmoxv1alpha1.Container) error {
//
logger := log.FromContext(ctx)
// Create Container
err := r.CloneContainer(container)
Expand All @@ -262,3 +265,61 @@ func (r *ContainerReconciler) handleCloneContainer(ctx context.Context, containe
}
return nil
}

func (r *ContainerReconciler) handleAutoStart(ctx context.Context,
container *proxmoxv1alpha1.Container) (ctrl.Result, error) {
logger := log.FromContext(ctx)
containerName := container.Spec.Name
nodeName := container.Spec.NodeName
if container.Spec.EnableAutoStart {
containerState := proxmox.GetContainerState(containerName, nodeName)
if containerState == typeStoppedContainer {
err := proxmox.StartContainer(containerName, nodeName)
if err != nil {
logger.Error(err, "Failed to start Container")
return ctrl.Result{Requeue: true}, err
}
return ctrl.Result{Requeue: true}, nil
}
}
return ctrl.Result{}, nil
}

func (r *ContainerReconciler) handleWatcher(ctx context.Context, req ctrl.Request, container *proxmoxv1alpha1.Container) {
r.Watchers.HandleWatcher(ctx, req, func(ctx context.Context, stopChan chan struct{}) (ctrl.Result, error) {
return proxmox.StartWatcher(ctx, container, stopChan, r.fetchResource, r.updateStatus,
r.checkDelta, r.handleAutoStartFunc, r.handleReconcileFunc, r.Watchers.DeleteWatcher)
})
}

func (r *ContainerReconciler) fetchResource(ctx context.Context, key client.ObjectKey, obj proxmox.Resource) error {
return r.Get(ctx, key, obj.(*proxmoxv1alpha1.Container))
}

func (r *ContainerReconciler) updateStatus(ctx context.Context, obj proxmox.Resource) error {
return r.UpdateContainerStatus(ctx, obj.(*proxmoxv1alpha1.Container))
}

func (r *ContainerReconciler) checkDelta(obj proxmox.Resource) (bool, error) {
return proxmox.CheckContainerDelta(obj.(*proxmoxv1alpha1.Container))
}

func (r *ContainerReconciler) handleAutoStartFunc(ctx context.Context, obj proxmox.Resource) (ctrl.Result, error) {
return r.handleAutoStart(ctx, obj.(*proxmoxv1alpha1.Container))
}

func (r *ContainerReconciler) handleReconcileFunc(ctx context.Context, obj proxmox.Resource) (ctrl.Result, error) {
return r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(obj)})
}

func (r *ContainerReconciler) handleContainerDeletion(ctx context.Context, container *proxmoxv1alpha1.Container) {
logger := log.FromContext(ctx)
containerName := container.Spec.Name
nodeName := container.Spec.NodeName
if container.Spec.DeletionProtection {
logger.Info(fmt.Sprintf("Container %s is protected from deletion", containerName))
return
} else {
proxmox.DeleteContainer(containerName, nodeName)
}
}
Loading

0 comments on commit ea2b9bd

Please sign in to comment.