@@ -19,12 +19,10 @@ package controller
1919import  (
2020	"context" 
2121	"encoding/json" 
22- 	"errors" 
2322	"fmt" 
2423	"maps" 
2524	"strconv" 
2625	"strings" 
27- 	"time" 
2826
2927	netboxv1 "github.com/netbox-community/netbox-operator/api/v1" 
3028	"github.com/netbox-community/netbox-operator/pkg/config" 
@@ -77,33 +75,13 @@ func (r *IpRangeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
7775
7876	// if being deleted 
7977	if  ! o .ObjectMeta .DeletionTimestamp .IsZero () {
80- 		if  controllerutil .ContainsFinalizer (o , IpRangeFinalizerName ) {
81- 			if  ! o .Spec .PreserveInNetbox  {
82- 				err  :=  r .NetboxClient .DeleteIpRange (o .Status .IpRangeId )
83- 				if  err  !=  nil  {
84- 					setConditionErr  :=  r .SetConditionAndCreateEvent (ctx , o , netboxv1 .ConditionIpRangeReadyFalseDeletionFailed , corev1 .EventTypeWarning , err .Error ())
85- 					if  setConditionErr  !=  nil  {
86- 						return  ctrl.Result {}, fmt .Errorf ("error updating status: %w, when deletion of IpRange failed: %w" , setConditionErr , err )
87- 					}
88- 
89- 					return  ctrl.Result {Requeue : true }, nil 
90- 				}
91- 			}
92- 
93- 			debugLogger .Info ("removing the finalizer" )
94- 			removed  :=  controllerutil .RemoveFinalizer (o , IpRangeFinalizerName )
95- 			if  ! removed  {
96- 				return  ctrl.Result {}, errors .New ("failed to remove the finalizer" )
97- 			}
98- 
99- 			err  =  r .Update (ctx , o )
100- 			if  err  !=  nil  {
101- 				return  ctrl.Result {}, err 
102- 			}
78+ 		if  o .Spec .PreserveInNetbox  {
79+ 			// if there's a finalizer remove it and return 
80+ 			// this can be the case if a CR used to have spec.preserveInNetbox set to false 
81+ 			return  r .removeFinalizer (ctx , o )
10382		}
10483
105- 		// end loop if deletion timestamp is not zero 
106- 		return  ctrl.Result {}, nil 
84+ 		return  r .deleteFromNetboxAndRemoveFinalizer (ctx , o )
10785	}
10886
10987	// if PreserveIpInNetbox flag is false then register finalizer if not yet registered 
@@ -118,43 +96,16 @@ func (r *IpRangeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
11896	// 1. try to lock lease of parent prefix if IpRange status condition is not true 
11997	// and IpRange is owned by an IpRangeClaim 
12098	or  :=  o .ObjectMeta .OwnerReferences 
121- 	var  ll  * leaselocker.LeaseLocker 
12299	if  len (or ) >  0  /* len(nil array) = 0 */  &&  ! apismeta .IsStatusConditionTrue (o .Status .Conditions , "Ready" ) {
123- 		// get ip range claim 
124- 		orLookupKey  :=  types.NamespacedName {
125- 			Name :      or [0 ].Name ,
126- 			Namespace : req .Namespace ,
127- 		}
128- 		ipRangeClaim  :=  & netboxv1.IpRangeClaim {}
129- 		err  =  r .Client .Get (ctx , orLookupKey , ipRangeClaim )
130- 		if  err  !=  nil  {
131- 			return  ctrl.Result {}, err 
132- 		}
133- 
134- 		// get name of parent prefix 
135- 		leaseLockerNSN  :=  types.NamespacedName {
136- 			Name :      convertCIDRToLeaseLockName (ipRangeClaim .Spec .ParentPrefix ),
137- 			Namespace : r .OperatorNamespace ,
138- 		}
139- 		ll , err  =  leaselocker .NewLeaseLocker (r .RestConfig , leaseLockerNSN , req .NamespacedName .String ())
100+ 		ll , res , err  :=  r .tryLockOnParentPrefix (ctx , o )
101+ 		defer  func () {
102+ 			if  ll  !=  nil  {
103+ 				ll .Unlock ()
104+ 			}
105+ 		}()
140106		if  err  !=  nil  {
141- 			return  ctrl.Result {}, err 
142- 		}
143- 
144- 		lockCtx , cancel  :=  context .WithCancel (ctx )
145- 		defer  cancel ()
146- 
147- 		// create lock 
148- 		locked  :=  ll .TryLock (lockCtx )
149- 		if  ! locked  {
150- 			logger .Info (fmt .Sprintf ("failed to lock parent prefix %s" , ipRangeClaim .Spec .ParentPrefix ))
151- 			r .Recorder .Eventf (o , corev1 .EventTypeWarning , "FailedToLockParentPrefix" , "failed to lock parent prefix %s" ,
152- 				ipRangeClaim .Spec .ParentPrefix )
153- 			return  ctrl.Result {
154- 				RequeueAfter : 2  *  time .Second ,
155- 			}, nil 
107+ 			return  res , err 
156108		}
157- 		debugLogger .Info (fmt .Sprintf ("successfully locked parent prefix %s" , ipRangeClaim .Spec .ParentPrefix ))
158109	}
159110
160111	// 2. reserve or update ip range in netbox 
@@ -164,67 +115,34 @@ func (r *IpRangeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
164115		return  ctrl.Result {}, err 
165116	}
166117
167- 	ipRangeModel , err  :=  generateNetboxIpRangeModelFromIpRangeSpec (& o . Spec , req , annotations [LastIpRangeMetadataAnnotationName ])
118+ 	ipRangeModel , err  :=  r . generateNetboxIpRangeModelFromIpRangeSpec (ctx ,  o , req , annotations [LastIpRangeMetadataAnnotationName ])
168119	if  err  !=  nil  {
169120		return  ctrl.Result {}, err 
170121	}
171122
172123	netboxIpRangeModel , err  :=  r .NetboxClient .ReserveOrUpdateIpRange (ipRangeModel )
173124	if  err  !=  nil  {
174- 		updateStatusErr  :=  r .SetConditionAndCreateEvent (ctx , o , netboxv1 .ConditionIpRangeReadyFalse ,
175- 			corev1 .EventTypeWarning , fmt .Sprintf ("%s-%s" , o .Spec .StartAddress , o .Spec .EndAddress ))
176- 		return  ctrl.Result {}, fmt .Errorf ("failed to update ip range status: %w, " + 
177- 			"after reservation of ip in netbox failed: %w" , updateStatusErr , err )
178- 	}
179- 
180- 	// 3. unlock lease of parent prefix 
181- 	if  ll  !=  nil  {
182- 		ll .Unlock ()
125+ 		err  :=  r .SetConditionAndCreateEvent (ctx , o , netboxv1 .ConditionIpRangeReadyFalse ,
126+ 			corev1 .EventTypeWarning , fmt .Sprintf ("%s-%s" , o .Spec .StartAddress , o .Spec .EndAddress ), err )
127+ 		if  err  !=  nil  {
128+ 			return  ctrl.Result {}, nil 
129+ 		}
183130	}
184131
185132	// 4. update status fields 
186133	o .Status .IpRangeId  =  netboxIpRangeModel .ID 
187- 	o .Status .IpRangeUrl  =  config .GetBaseUrl () +  "/ipam/ip-rages /"  +  strconv .FormatInt (netboxIpRangeModel .ID , 10 )
134+ 	o .Status .IpRangeUrl  =  config .GetBaseUrl () +  "/ipam/ip-ranges /"  +  strconv .FormatInt (netboxIpRangeModel .ID , 10 )
188135	err  =  r .Client .Status ().Update (ctx , o )
189136	if  err  !=  nil  {
190137		return  ctrl.Result {}, err 
191138	}
192139
193- 	// update lastIpRangeMetadata annotation 
194- 	if  annotations  ==  nil  {
195- 		annotations  =  make (map [string ]string )
196- 	}
197- 
198- 	if  len (o .Spec .CustomFields ) >  0  {
199- 		lastIpRangeMetadata , err  :=  json .Marshal (o .Spec .CustomFields )
200- 		if  err  !=  nil  {
201- 			return  ctrl.Result {}, fmt .Errorf ("failed to marshal lastIpRangeMetadata annotation: %w" , err )
202- 		}
203- 
204- 		annotations [LastIpRangeMetadataAnnotationName ] =  string (lastIpRangeMetadata )
205- 	} else  {
206- 		annotations [LastIpRangeMetadataAnnotationName ] =  "{}" 
207- 	}
208- 
209- 	err  =  accessor .SetAnnotations (o , annotations )
140+ 	err  =  r .updateLastMetadataAnnotation (ctx , annotations , o , accessor )
210141	if  err  !=  nil  {
211142		return  ctrl.Result {}, err 
212143	}
213144
214- 	// update object to store lastIpRangeMetadata annotation 
215- 	if  err  :=  r .Update (ctx , o ); err  !=  nil  {
216- 		return  ctrl.Result {}, err 
217- 	}
218- 
219- 	// check if created ip range contains entire description from spec 
220- 	_ , found  :=  strings .CutPrefix (netboxIpRangeModel .Description , req .NamespacedName .String ()+ " // " + o .Spec .Description )
221- 	if  ! found  {
222- 		r .Recorder .Event (o , corev1 .EventTypeWarning , "IpRangeDescriptionTruncated" , "ip range was created with truncated description" )
223- 	}
224- 
225- 	debugLogger .Info (fmt .Sprintf ("reserved ip range in netbox, start address: %s, end address: %s" , o .Spec .StartAddress , o .Spec .EndAddress ))
226- 
227- 	err  =  r .SetConditionAndCreateEvent (ctx , o , netboxv1 .ConditionIpRangeReadyTrue , corev1 .EventTypeNormal , "" )
145+ 	err  =  r .SetConditionAndCreateEvent (ctx , o , netboxv1 .ConditionIpRangeReadyTrue , corev1 .EventTypeNormal , "" , nil )
228146	if  err  !=  nil  {
229147		return  ctrl.Result {}, err 
230148	}
@@ -241,22 +159,33 @@ func (r *IpRangeReconciler) SetupWithManager(mgr ctrl.Manager) error {
241159		Complete (r )
242160}
243161
244- func  (r  * IpRangeReconciler ) SetConditionAndCreateEvent (ctx  context.Context , o  * netboxv1.IpRange , condition  metav1.Condition , eventType  string , conditionMessageAppend  string ) error  {
162+ func  (r  * IpRangeReconciler ) SetConditionAndCreateEvent (ctx  context.Context , o  * netboxv1.IpRange , condition  metav1.Condition , eventType  string , conditionMessageAppend  string ,  errIn   error ) error  {
245163	if  len (conditionMessageAppend ) >  0  {
246164		condition .Message  =  condition .Message  +  ". "  +  conditionMessageAppend 
247165	}
166+ 
167+ 	if  errIn  !=  nil  {
168+ 		// log errors here, so we do not need to aggregate them in the reconcile loop with the error of updating the status 
169+ 		logger  :=  log .FromContext (ctx )
170+ 		logger .Error (errIn , condition .Message + errIn .Error ())
171+ 		condition .Message  =  condition .Message  +  ". Check the logs for more information." 
172+ 	}
173+ 
248174	conditionChanged  :=  apismeta .SetStatusCondition (& o .Status .Conditions , condition )
249175	if  conditionChanged  {
250176		r .Recorder .Event (o , eventType , condition .Reason , condition .Message )
251177	}
178+ 
252179	err  :=  r .Client .Status ().Update (ctx , o )
253180	if  err  !=  nil  {
254181		return  err 
255182	}
183+ 
256184	return  nil 
257185}
258186
259- func  generateNetboxIpRangeModelFromIpRangeSpec (spec  * netboxv1.IpRangeSpec , req  ctrl.Request , lastIpRangeMetadata  string ) (* models.IpRange , error ) {
187+ func  (r  * IpRangeReconciler ) generateNetboxIpRangeModelFromIpRangeSpec (ctx  context.Context , o  * netboxv1.IpRange , req  ctrl.Request , lastIpRangeMetadata  string ) (* models.IpRange , error ) {
188+ 	debugLogger  :=  log .FromContext (context .Background ()).V (4 )
260189	// unmarshal lastIpRangeMetadata json string to map[string]string 
261190	lastAppliedCustomFields  :=  make (map [string ]string )
262191	if  lastIpRangeMetadata  !=  ""  {
@@ -266,8 +195,8 @@ func generateNetboxIpRangeModelFromIpRangeSpec(spec *netboxv1.IpRangeSpec, req c
266195	}
267196
268197	netboxCustomFields  :=  make (map [string ]string )
269- 	if  len (spec .CustomFields ) >  0  {
270- 		netboxCustomFields  =  maps .Clone (spec .CustomFields )
198+ 	if  len (o . Spec .CustomFields ) >  0  {
199+ 		netboxCustomFields  =  maps .Clone (o . Spec .CustomFields )
271200	}
272201
273202	// if a custom field was removed from the spec, add it with an empty value 
@@ -279,14 +208,134 @@ func generateNetboxIpRangeModelFromIpRangeSpec(spec *netboxv1.IpRangeSpec, req c
279208		}
280209	}
281210
211+ 	description  :=  api .TruncateDescription (req .NamespacedName .String () +  " // "  +  o .Spec .Description )
212+ 
213+ 	// check if created ip range contains entire description from spec 
214+ 	_ , found  :=  strings .CutPrefix (description , req .NamespacedName .String ()+ " // " + o .Spec .Description )
215+ 	if  ! found  {
216+ 		r .Recorder .Event (o , corev1 .EventTypeWarning , "IpRangeDescriptionTruncated" , "ip range was created with truncated description" )
217+ 	}
218+ 
219+ 	debugLogger .Info (fmt .Sprintf ("reserved ip range in netbox, start address: %s, end address: %s" , o .Spec .StartAddress , o .Spec .EndAddress ))
220+ 
282221	return  & models.IpRange {
283- 		StartAddress : spec .StartAddress ,
284- 		EndAddress :   spec .EndAddress ,
222+ 		StartAddress : o . Spec .StartAddress ,
223+ 		EndAddress :   o . Spec .EndAddress ,
285224		Metadata : & models.NetboxMetadata {
286- 			Comments :    spec .Comments ,
225+ 			Comments :    o . Spec .Comments ,
287226			Custom :      netboxCustomFields ,
288- 			Description : req . NamespacedName . String ()  +   " // "   +   spec . Description ,
289- 			Tenant :      spec .Tenant ,
227+ 			Description : description ,
228+ 			Tenant :      o . Spec .Tenant ,
290229		},
291230	}, nil 
292231}
232+ 
233+ func  (r  * IpRangeReconciler ) deleteFromNetboxAndRemoveFinalizer (ctx  context.Context , o  * netboxv1.IpRange ) (ctrl.Result , error ) {
234+ 	err  :=  r .NetboxClient .DeleteIpRange (o .Status .IpRangeId )
235+ 	if  err  !=  nil  {
236+ 		err  =  r .SetConditionAndCreateEvent (ctx , o , netboxv1 .ConditionIpRangeReadyFalseDeletionFailed ,
237+ 			corev1 .EventTypeWarning , "" , err )
238+ 		if  err  !=  nil  {
239+ 			return  ctrl.Result {}, err 
240+ 		}
241+ 
242+ 		return  ctrl.Result {Requeue : true }, nil 
243+ 	}
244+ 
245+ 	res , err  :=  r .removeFinalizer (ctx , o )
246+ 	if  err  !=  nil  {
247+ 		return  res , err 
248+ 	}
249+ 
250+ 	err  =  r .Update (ctx , o )
251+ 	if  err  !=  nil  {
252+ 		return  ctrl.Result {}, err 
253+ 	}
254+ 
255+ 	return  ctrl.Result {}, nil 
256+ }
257+ 
258+ func  (r  * IpRangeReconciler ) tryLockOnParentPrefix (ctx  context.Context , o  * netboxv1.IpRange ) (* leaselocker.LeaseLocker , ctrl.Result , error ) {
259+ 	// determine NamespacedName of IpRangeClaim owning the IpRange CR 
260+ 	orLookupKey  :=  types.NamespacedName {
261+ 		Name :      o .ObjectMeta .OwnerReferences [0 ].Name ,
262+ 		Namespace : o .Namespace ,
263+ 	}
264+ 
265+ 	logger  :=  log .FromContext (ctx )
266+ 	debugLogger  :=  log .FromContext (ctx ).V (4 )
267+ 
268+ 	ipRangeClaim  :=  & netboxv1.IpRangeClaim {}
269+ 	err  :=  r .Client .Get (ctx , orLookupKey , ipRangeClaim )
270+ 	if  err  !=  nil  {
271+ 		return  nil , ctrl.Result {}, err 
272+ 	}
273+ 
274+ 	// get name of parent prefix 
275+ 	leaseLockerNSN  :=  types.NamespacedName {
276+ 		Name :      convertCIDRToLeaseLockName (ipRangeClaim .Spec .ParentPrefix ),
277+ 		Namespace : r .OperatorNamespace ,
278+ 	}
279+ 	ll , err  :=  leaselocker .NewLeaseLocker (r .RestConfig , leaseLockerNSN , orLookupKey .String ())
280+ 	if  err  !=  nil  {
281+ 		return  nil , ctrl.Result {}, err 
282+ 	}
283+ 
284+ 	lockCtx , cancel  :=  context .WithCancel (ctx )
285+ 	defer  cancel ()
286+ 
287+ 	// create lock 
288+ 	locked  :=  ll .TryLock (lockCtx )
289+ 	if  ! locked  {
290+ 		logger .Info (fmt .Sprintf ("failed to lock parent prefix %s" , ipRangeClaim .Spec .ParentPrefix ))
291+ 		r .Recorder .Eventf (o , corev1 .EventTypeWarning , "FailedToLockParentPrefix" , "failed to lock parent prefix %s" ,
292+ 			ipRangeClaim .Spec .ParentPrefix )
293+ 		return  ll , ctrl.Result {}, nil 
294+ 	}
295+ 	debugLogger .Info (fmt .Sprintf ("successfully locked parent prefix %s" , ipRangeClaim .Spec .ParentPrefix ))
296+ 
297+ 	return  ll , ctrl.Result {}, nil 
298+ }
299+ 
300+ func  (r  * IpRangeReconciler ) updateLastMetadataAnnotation (ctx  context.Context , annotations  map [string ]string , o  * netboxv1.IpRange , accessor  apismeta.MetadataAccessor ) error  {
301+ 	// update lastIpRangeMetadata annotation 
302+ 	if  annotations  ==  nil  {
303+ 		annotations  =  make (map [string ]string )
304+ 	}
305+ 
306+ 	if  len (o .Spec .CustomFields ) >  0  {
307+ 		lastIpRangeMetadata , err  :=  json .Marshal (o .Spec .CustomFields )
308+ 		if  err  !=  nil  {
309+ 			return  fmt .Errorf ("failed to marshal lastIpRangeMetadata annotation: %w" , err )
310+ 		}
311+ 
312+ 		annotations [LastIpRangeMetadataAnnotationName ] =  string (lastIpRangeMetadata )
313+ 	} else  {
314+ 		annotations [LastIpRangeMetadataAnnotationName ] =  "{}" 
315+ 	}
316+ 
317+ 	err  :=  accessor .SetAnnotations (o , annotations )
318+ 	if  err  !=  nil  {
319+ 		return  err 
320+ 	}
321+ 
322+ 	// update object to store lastIpRangeMetadata annotation 
323+ 	if  err  :=  r .Update (ctx , o ); err  !=  nil  {
324+ 		return  err 
325+ 	}
326+ 
327+ 	return  nil 
328+ }
329+ 
330+ func  (r  * IpRangeReconciler ) removeFinalizer (ctx  context.Context , o  * netboxv1.IpRange ) (ctrl.Result , error ) {
331+ 	debugLogger  :=  log .FromContext (ctx ).V (4 )
332+ 	if  controllerutil .ContainsFinalizer (o , IpRangeFinalizerName ) {
333+ 		debugLogger .Info ("removing the finalizer" )
334+ 		controllerutil .RemoveFinalizer (o , IpRangeFinalizerName )
335+ 		if  err  :=  r .Update (ctx , o ); err  !=  nil  {
336+ 			return  ctrl.Result {}, err 
337+ 		}
338+ 	}
339+ 
340+ 	return  ctrl.Result {}, nil 
341+ }
0 commit comments