@@ -86,14 +86,33 @@ func (r *DeviceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c
8686 return ctrl.Result {}, err
8787 }
8888
89+ prov , ok := r .Provider ().(provider.DeviceProvider )
90+ if ! ok {
91+ err := errors .New ("provider does not implement DeviceProvider interface" )
92+ log .Error (err , "failed to reconcile resource" )
93+ return ctrl.Result {}, err
94+ }
95+ conn , err := deviceutil .GetDeviceConnection (ctx , r , obj )
96+ if err != nil {
97+ return ctrl.Result {}, fmt .Errorf ("failed to obtain device connection: %w" , err )
98+ }
99+
89100 orig := obj .DeepCopy ()
101+
90102 if conditions .InitializeConditions (obj , v1alpha1 .ReadyCondition ) {
91103 log .Info ("Initializing status conditions" )
92104 return ctrl.Result {}, r .Status ().Update (ctx , obj )
93105 }
94106
95- // Always attempt to update the status after reconciliation
107+ // Always attempt to update the metadata/ status after reconciliation
96108 defer func () {
109+ if ! equality .Semantic .DeepEqual (orig .ObjectMeta , obj .ObjectMeta ) {
110+ // pass obj.DeepCopy() to avoid Patch() modifying obj and interfering with status update below
111+ if err := r .Patch (ctx , obj .DeepCopy (), client .MergeFrom (orig )); err != nil {
112+ log .Error (err , "Failed to update resource metadata" )
113+ reterr = kerrors .NewAggregate ([]error {reterr , err })
114+ }
115+ }
97116 if ! equality .Semantic .DeepEqual (orig .Status , obj .Status ) {
98117 if err := r .Status ().Patch (ctx , obj , client .MergeFrom (orig )); err != nil {
99118 log .Error (err , "Failed to update status" )
@@ -130,6 +149,9 @@ func (r *DeviceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c
130149 return ctrl.Result {}, nil
131150
132151 case v1alpha1 .DevicePhaseProvisioning :
152+ annotations := obj .GetAnnotations ()
153+ delete (annotations , v1alpha1 .DeviceMaintenanceAnnotation )
154+ obj .SetAnnotations (annotations )
133155 activeProv := obj .GetActiveProvisioning ()
134156 if activeProv == nil {
135157 log .Info ("Device has not made a provisioning request yet" )
@@ -157,10 +179,6 @@ func (r *DeviceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c
157179 }
158180 log .Info ("Device provisioning completed, running post provisioning checks" )
159181 prov , _ := r .Provider ().(provider.ProvisioningProvider )
160- conn , err := deviceutil .GetDeviceConnection (ctx , r , obj )
161- if err != nil {
162- return ctrl.Result {}, fmt .Errorf ("failed to obtain device connection: %w" , err )
163- }
164182 if ok := prov .VerifyProvisioned (ctx , conn , obj ); ! ok {
165183 return ctrl.Result {RequeueAfter : r .RequeueInterval }, nil
166184 }
@@ -169,7 +187,7 @@ func (r *DeviceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c
169187 obj .Status .Phase = v1alpha1 .DevicePhaseRunning
170188
171189 case v1alpha1 .DevicePhaseRunning :
172- if err := r .reconcile (ctx , obj ); err != nil {
190+ if err := r .reconcile (ctx , obj , prov , conn ); err != nil {
173191 log .Error (err , "Failed to reconcile resource" )
174192 return ctrl.Result {}, err
175193 }
@@ -187,6 +205,10 @@ func (r *DeviceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c
187205 obj .Status .Phase = v1alpha1 .DevicePhaseRunning
188206 }
189207
208+ if err := r .reconcileMaintenance (ctx , obj , prov , conn ); err != nil {
209+ return ctrl.Result {}, err
210+ }
211+
190212 return ctrl.Result {}, nil
191213}
192214
@@ -231,73 +253,67 @@ func (r *DeviceReconciler) SetupWithManager(mgr ctrl.Manager) error {
231253 Complete (r )
232254}
233255
234- func (r * DeviceReconciler ) reconcile (ctx context.Context , device * v1alpha1.Device ) (reterr error ) {
235- if prov , ok := r .Provider ().(provider.DeviceProvider ); ok {
236- conn , err := deviceutil .GetDeviceConnection (ctx , r , device )
237- if err != nil {
238- return err
256+ func (r * DeviceReconciler ) reconcile (ctx context.Context , device * v1alpha1.Device , prov provider.DeviceProvider , conn * deviceutil.Connection ) (reterr error ) {
257+ if err := prov .Connect (ctx , conn ); err != nil {
258+ conditions .Set (device , metav1.Condition {
259+ Type : v1alpha1 .ReadyCondition ,
260+ Status : metav1 .ConditionFalse ,
261+ Reason : v1alpha1 .UnreachableReason ,
262+ Message : fmt .Sprintf ("Failed to connect to provider: %v" , err ),
263+ })
264+ return fmt .Errorf ("failed to connect to provider: %w" , err )
265+ }
266+ defer func () {
267+ if err := prov .Disconnect (ctx , conn ); err != nil {
268+ reterr = kerrors .NewAggregate ([]error {reterr , err })
239269 }
270+ }()
240271
241- if err := prov .Connect (ctx , conn ); err != nil {
242- conditions .Set (device , metav1.Condition {
243- Type : v1alpha1 .ReadyCondition ,
244- Status : metav1 .ConditionFalse ,
245- Reason : v1alpha1 .UnreachableReason ,
246- Message : fmt .Sprintf ("Failed to connect to provider: %v" , err ),
247- })
248- return fmt .Errorf ("failed to connect to provider: %w" , err )
249- }
250- defer func () {
251- if err := prov .Disconnect (ctx , conn ); err != nil {
252- reterr = kerrors .NewAggregate ([]error {reterr , err })
253- }
254- }()
272+ ports , err := prov .ListPorts (ctx )
273+ if err != nil {
274+ return fmt .Errorf ("failed to list device ports: %w" , err )
275+ }
255276
256- ports , err := prov . ListPorts ( ctx )
257- if err != nil {
258- return fmt .Errorf ("failed to list device ports : %w" , err )
259- }
277+ interfaces := new (v1alpha1. InterfaceList )
278+ if err := r . List ( ctx , interfaces , client . InNamespace ( device . Namespace ), client. MatchingLabels { v1alpha1 . DeviceLabel : device . Name }); err != nil {
279+ return fmt .Errorf ("failed to list interface resources for device : %w" , err )
280+ }
260281
261- interfaces := new (v1alpha1. InterfaceList )
262- if err := r . List ( ctx , interfaces , client . InNamespace ( device . Namespace ), client. MatchingLabels { v1alpha1 . DeviceLabel : device . Name }); err != nil {
263- return fmt . Errorf ( "failed to list interface resources for device: %w" , err )
264- }
282+ m := make ( map [ string ] string ) // ID => Resource Name
283+ for _ , intf := range interfaces . Items {
284+ m [ intf . Spec . Name ] = intf . Name
285+ }
265286
266- m := make (map [string ]string ) // ID => Resource Name
267- for _ , intf := range interfaces .Items {
268- m [intf .Spec .Name ] = intf .Name
287+ device .Status .Ports = make ([]v1alpha1.DevicePort , len (ports ))
288+ n := int32 (0 )
289+ for i , p := range ports {
290+ var ref * v1alpha1.LocalObjectReference
291+ if name , ok := m [p .ID ]; ok {
292+ ref = & v1alpha1.LocalObjectReference {Name : name }
293+ n ++
269294 }
270-
271- device .Status .Ports = make ([]v1alpha1.DevicePort , len (ports ))
272- n := int32 (0 )
273- for i , p := range ports {
274- var ref * v1alpha1.LocalObjectReference
275- if name , ok := m [p .ID ]; ok {
276- ref = & v1alpha1.LocalObjectReference {Name : name }
277- n ++
278- }
279- device .Status .Ports [i ] = v1alpha1.DevicePort {
280- Name : p .ID ,
281- Type : p .Type ,
282- SupportedSpeedsGbps : p .SupportedSpeedsGbps ,
283- Transceiver : p .Transceiver ,
284- InterfaceRef : ref ,
285- }
286- slices .Sort (device .Status .Ports [i ].SupportedSpeedsGbps )
295+ device .Status .Ports [i ] = v1alpha1.DevicePort {
296+ Name : p .ID ,
297+ Type : p .Type ,
298+ SupportedSpeedsGbps : p .SupportedSpeedsGbps ,
299+ Transceiver : p .Transceiver ,
300+ InterfaceRef : ref ,
287301 }
302+ slices .Sort (device .Status .Ports [i ].SupportedSpeedsGbps )
303+ }
288304
289- device .Status .PostSummary = PortSummary (device .Status .Ports )
290-
291- info , err := prov .GetDeviceInfo (ctx )
292- if err != nil {
293- return fmt .Errorf ("failed to get device details: %w" , err )
294- }
305+ device .Status .PostSummary = PortSummary (device .Status .Ports )
295306
296- device .Status .Manufacturer = info .Manufacturer
297- device .Status .Model = info .Model
298- device .Status .SerialNumber = info .SerialNumber
299- device .Status .FirmwareVersion = info .FirmwareVersion
307+ info , err := prov .GetDeviceInfo (ctx )
308+ if err != nil {
309+ return fmt .Errorf ("failed to get device details: %w" , err )
300310 }
311+
312+ device .Status .Manufacturer = info .Manufacturer
313+ device .Status .Model = info .Model
314+ device .Status .SerialNumber = info .SerialNumber
315+ device .Status .FirmwareVersion = info .FirmwareVersion
316+
301317 conditions .Set (device , metav1.Condition {
302318 Type : v1alpha1 .ReadyCondition ,
303319 Status : metav1 .ConditionTrue ,
@@ -308,6 +324,40 @@ func (r *DeviceReconciler) reconcile(ctx context.Context, device *v1alpha1.Devic
308324 return nil
309325}
310326
327+ func (r * DeviceReconciler ) reconcileMaintenance (ctx context.Context , obj * v1alpha1.Device , prov provider.DeviceProvider , conn * deviceutil.Connection ) error {
328+ action , ok := obj .Annotations [v1alpha1 .DeviceMaintenanceAnnotation ]
329+ if ! ok {
330+ return nil
331+ }
332+ delete (obj .Annotations , v1alpha1 .DeviceMaintenanceAnnotation )
333+ switch action {
334+ case v1alpha1 .DeviceMaintenanceReboot :
335+ r .Recorder .Event (obj , "Normal" , "RebootRequested" , "Device reboot has been requested" )
336+ if err := prov .Reboot (ctx , conn ); err != nil {
337+ return fmt .Errorf ("failed to reboot device: %w" , err )
338+ }
339+
340+ case v1alpha1 .DeviceMaintenanceFactoryReset :
341+ r .Recorder .Event (obj , "Normal" , "FactoryResetRequested" , "Device factory reset has been requested" )
342+ if err := prov .FactoryReset (ctx , conn ); err != nil {
343+ return fmt .Errorf ("failed to reset device to factory defaults: %w" , err )
344+ }
345+
346+ case v1alpha1 .DeviceMaintenanceReprovision :
347+ r .Recorder .Event (obj , "Normal" , "ReprovisioningRequested" , "Device reprovisioning has been requested. Preparing the device." )
348+ if err := prov .Reprovision (ctx , conn ); err != nil {
349+ return fmt .Errorf ("failed to reset device to factory defaults: %w" , err )
350+ }
351+ obj .Status .Phase = v1alpha1 .DevicePhasePending
352+ case v1alpha1 .DeviceMaintenanceResetPhaseToProvisioning :
353+ r .Recorder .Event (obj , "Normal" , "ResetPhaseToProvisioningRequested" , "Device phase reset to Pending has been requested." )
354+ obj .Status .Phase = v1alpha1 .DevicePhasePending
355+ default :
356+ return fmt .Errorf ("unknown device action: %s" , action )
357+ }
358+ return nil
359+ }
360+
311361// secretToDevices is a [handler.MapFunc] to be used to enqueue requests for reconciliation
312362// for a Device to update when one of its referenced Secrets gets updated.
313363func (r * DeviceReconciler ) secretToDevices (ctx context.Context , obj client.Object ) []ctrl.Request {
0 commit comments