@@ -15,9 +15,9 @@ import (
1515func (c * Controller ) ReconcileAll () {
1616 log .Println ("Starting reconciliation loop" )
1717
18- // Reconcile Applications
18+ // Reconcile Applications (ignore those marked Removing)
1919 var apps []v1.Application
20- if err := db .DB .Where ("deleted_at IS NULL" ).Find (& apps ).Error ; err != nil {
20+ if err := db .DB .Where ("deleted_at IS NULL AND observed_state <> ?" , "Removing " ).Find (& apps ).Error ; err != nil {
2121 log .Printf ("Failed to list applications: %v" , err )
2222 return
2323 }
@@ -27,9 +27,9 @@ func (c *Controller) ReconcileAll() {
2727 log .Printf ("Failed to reconcile application %s: %v" , app .Metadata .Name , err )
2828 }
2929 }
30- // Reconcile VMs
30+ // Reconcile VMs (ignore those marked Removing)
3131 var vms []v1.VirtualMachine
32- if err := db .DB .Where ("deleted_at IS NULL" ).Find (& vms ).Error ; err != nil {
32+ if err := db .DB .Where ("deleted_at IS NULL AND observed_state <> ?" , "Removing " ).Find (& vms ).Error ; err != nil {
3333 log .Printf ("Failed to list VMs: %v" , err )
3434 return
3535 }
@@ -39,16 +39,107 @@ func (c *Controller) ReconcileAll() {
3939 log .Printf ("Failed to reconcile VM %s: %v" , vm .Metadata .Name , err )
4040 }
4141 }
42+
43+ // After normal reconciliation, process removals in order: VMs -> Components -> Applications
44+ // 1) VMs marked Removing
45+ var removingVMs []v1.VirtualMachine
46+ if err := db .DB .Where ("observed_state = ?" , "Removing" ).Find (& removingVMs ).Error ; err != nil {
47+ log .Printf ("Failed to list removing VMs: %v" , err )
48+ } else {
49+ for _ , vm := range removingVMs {
50+ log .Printf ("Removing VM: %s" , vm .Metadata .Name )
51+ if vm .CloudStackID != "" {
52+ dp := c .csClient .VirtualMachine .NewDestroyVirtualMachineParams (vm .CloudStackID )
53+ dp .SetExpunge (true )
54+ c .csClient .VirtualMachine .DestroyVirtualMachine (dp )
55+ }
56+ db .DB .Delete (& vm )
57+ }
58+ }
59+
60+ // 2) Components marked Removing
61+ var removingComps []v1.Component
62+ if err := db .DB .Where ("observed_state = ?" , "Removing" ).Find (& removingComps ).Error ; err != nil {
63+ log .Printf ("Failed to list removing components: %v" , err )
64+ } else {
65+ for _ , comp := range removingComps {
66+ // Check if any VMs still reference this component
67+ var vmCount int64
68+ if err := db .DB .Model (& v1.VirtualMachine {}).Where ("component_id = ?" , comp .Metadata .Name ).Count (& vmCount ).Error ; err != nil {
69+ log .Printf ("Failed to count VMs for component %s: %v" , comp .Metadata .Name , err )
70+ continue
71+ }
72+ if vmCount > 0 {
73+ log .Printf ("Skipping deletion of Component %s: %d VMs still exist" , comp .Metadata .Name , vmCount )
74+ continue
75+ }
76+ log .Printf ("Removing Component: %s" , comp .Metadata .Name )
77+ db .DB .Delete (& comp )
78+ }
79+ }
80+
81+ // Process applications marked for removal: ensure VMs -> Components -> Applications
82+ var removingApps []v1.Application
83+ if err := db .DB .Where ("observed_state = ?" , "Removing" ).Find (& removingApps ).Error ; err != nil {
84+ log .Printf ("Failed to list removing applications: %v" , err )
85+ return
86+ }
87+ for _ , app := range removingApps {
88+ log .Printf ("Processing removal for application: %s" , app .Metadata .Name )
89+
90+ // Delete VMs that reference this application
91+ var appVMs []v1.VirtualMachine
92+ if err := db .DB .Where ("application_id = ?" , app .Metadata .Name ).Find (& appVMs ).Error ; err == nil {
93+ for _ , vm := range appVMs {
94+ if vm .CloudStackID != "" {
95+ dp := c .csClient .VirtualMachine .NewDestroyVirtualMachineParams (vm .CloudStackID )
96+ dp .SetExpunge (true )
97+ c .csClient .VirtualMachine .DestroyVirtualMachine (dp )
98+ }
99+ db .DB .Delete (& vm )
100+ }
101+ }
102+
103+ // Delete component records referenced by application only if no components remain
104+ var compNames []string
105+ for _ , cref := range app .Spec .Components {
106+ compNames = append (compNames , cref .Name )
107+ }
108+ var remaining int64
109+ if len (compNames ) > 0 {
110+ if err := db .DB .Model (& v1.Component {}).Where ("name IN ?" , compNames ).Count (& remaining ).Error ; err != nil {
111+ log .Printf ("Failed to count components for application %s: %v" , app .Metadata .Name , err )
112+ continue
113+ }
114+ }
115+ if remaining > 0 {
116+ log .Printf ("Skipping deletion of Application %s: %d components still exist" , app .Metadata .Name , remaining )
117+ continue
118+ }
119+ // Safe to delete application record
120+ db .DB .Delete (& app )
121+ log .Printf ("Application %s removed" , app .Metadata .Name )
122+ }
42123}
43124
44125// ReconcileApplication ensures application state matches desired state
45126func (c * Controller ) ReconcileApplication (app * v1.Application ) error {
127+ // Skip applications that are marked for removal
128+ if app .Status .ObservedState == "Removing" {
129+ return nil
130+ }
131+
46132 // Resolve component dependencies (health enforcement)
47133 return c .ResolveComponentDependencies (app )
48134}
49135
50136// ReconcileComponent ensures component state matches desired state
51137func (c * Controller ) ReconcileComponent (comp * v1.Component ) error {
138+ // Skip components that are marked for removal
139+ if comp .Status .ObservedState == "Removing" {
140+ return nil
141+ }
142+
52143 // Check component health
53144 healthy , err := c .CheckComponentHealth (comp )
54145 if err != nil {
@@ -76,6 +167,10 @@ func (c *Controller) ReconcileComponent(comp *v1.Component) error {
76167
77168// ReconcileVM ensures VM state matches desired state
78169func (c * Controller ) ReconcileVM (vm * v1.VirtualMachine ) error {
170+ // Skip VMs marked for removal
171+ if vm .Status .ObservedState == "Removing" {
172+ return nil
173+ }
79174 // Populate observed spec from CloudStack (if possible)
80175 if err := c .populateObservedSpec (vm ); err != nil {
81176 return err
@@ -238,7 +333,7 @@ func (c *Controller) populateObservedSpec(vm *v1.VirtualMachine) error {
238333}
239334
240335// createComponentVMs creates VM replicas for a component
241- func (c * Controller ) createComponentVMs (comp * v1.Component , compRef v1.ComponentRef ) error {
336+ func (c * Controller ) createComponentVMs (appName string , comp * v1.Component , compRef v1.ComponentRef ) error {
242337 // Load the referenced reusable VM spec
243338 var vsr v1.VirtualMachineSpecResource
244339 if err := db .DB .Where ("name = ?" , compRef .VirtualMachineSpec ).First (& vsr ).Error ; err != nil {
@@ -271,6 +366,14 @@ func (c *Controller) createComponentVMs(comp *v1.Component, compRef v1.Component
271366 Spec : effective ,
272367 }
273368
369+ // Link VM to owning application if provided
370+ if appName != "" {
371+ vm .ApplicationID = appName
372+ }
373+
374+ // Link VM to owning component
375+ vm .ComponentID = comp .Metadata .Name
376+
274377 // Persist desired VM record and create in CloudStack
275378 if err := db .DB .Save (vm ).Error ; err != nil {
276379 return err
@@ -306,7 +409,7 @@ func (c *Controller) recreateComponentVMs(comp *v1.Component) error {
306409 VirtualMachineSpec : comp .Spec .VirtualMachineSpec ,
307410 Replicas : comp .Spec .Replicas ,
308411 }
309- return c .createComponentVMs (comp , compRef )
412+ return c .createComponentVMs ("" , comp , compRef )
310413}
311414
312415// mergeVMSpec merges allowed overrides into a base VirtualMachineSpec
0 commit comments