1 package pxe
2
3 import (
4 "context"
5
6 corev1 "k8s.io/api/core/v1"
7 kerrors "k8s.io/apimachinery/pkg/api/errors"
8 "k8s.io/client-go/dynamic"
9 "sigs.k8s.io/cli-utils/pkg/kstatus/watcher"
10 ctrl "sigs.k8s.io/controller-runtime"
11 "sigs.k8s.io/controller-runtime/pkg/builder"
12 "sigs.k8s.io/controller-runtime/pkg/client"
13 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
14 "sigs.k8s.io/controller-runtime/pkg/event"
15 "sigs.k8s.io/controller-runtime/pkg/handler"
16 "sigs.k8s.io/controller-runtime/pkg/predicate"
17 "sigs.k8s.io/controller-runtime/pkg/reconcile"
18
19 "edge-infra.dev/pkg/k8s/runtime/patch"
20 "edge-infra.dev/pkg/k8s/runtime/sap"
21 "edge-infra.dev/pkg/lib/fog"
22 "edge-infra.dev/pkg/sds/ien/bootoptions"
23 v1ienode "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
24 v1pxe "edge-infra.dev/pkg/sds/ien/k8s/controllers/pxe/apis/v1"
25 "edge-infra.dev/pkg/sds/ien/k8s/controllers/pxe/dnsmasq"
26 "edge-infra.dev/pkg/sds/ien/k8s/controllers/pxe/staticfileserver"
27 )
28
29 const scalerPXEName = "scaler"
30
31
32
33
34
35 type Scaler struct {
36 Reconciler
37 }
38
39
40 func NewScaler(mgr ctrl.Manager, cli client.Client) (*Scaler, error) {
41 name := scalerPXEName
42
43 d, err := dynamic.NewForConfig(mgr.GetConfig())
44 if err != nil {
45 return nil, err
46 }
47
48 manager := sap.NewResourceManager(
49 cli,
50 watcher.NewDefaultStatusWatcher(d, mgr.GetRESTMapper()),
51 sap.Owner{Field: name},
52 )
53
54 return &Scaler{
55 Reconciler: Reconciler{
56 name,
57 cli,
58 manager,
59 },
60 }, nil
61 }
62
63
64 func (s *Scaler) SetupWithManager(mgr ctrl.Manager) error {
65 return ctrl.NewControllerManagedBy(mgr).
66 For(
67 &v1pxe.PXE{},
68 builder.WithPredicates(predicate.GenerationChangedPredicate{}, s.isScaler(), s.ignoreDelete(), predicate.Or(s.isDeleteRequested(), s.isSuspendChanged())),
69 ).
70 Watches(
71 &corev1.ConfigMap{},
72 handler.EnqueueRequestsFromMapFunc(s.createReconcileRequest),
73 builder.WithPredicates(
74 predicate.ResourceVersionChangedPredicate{},
75 s.isBootOptionsConfigMap(),
76 s.ignoreDelete(),
77 ),
78 ).
79 Watches(
80 &corev1.Node{},
81 handler.EnqueueRequestsFromMapFunc(s.createReconcileRequest),
82 builder.WithPredicates(s.ignoreUpdate()),
83 ).
84 Watches(
85 &v1ienode.IENode{},
86 handler.EnqueueRequestsFromMapFunc(s.createReconcileRequest),
87 builder.WithPredicates(s.ignoreUpdate()),
88 ).Complete(s)
89 }
90
91
92
93 func (s *Scaler) isScaler() predicate.Funcs {
94 return predicate.NewPredicateFuncs(func(obj client.Object) bool {
95 return obj.GetName() == scalerPXEName
96 })
97 }
98
99
100
101 func (s *Scaler) isDeleteRequested() predicate.Funcs {
102 return predicate.NewPredicateFuncs(func(obj client.Object) bool {
103 return !obj.GetDeletionTimestamp().IsZero()
104 })
105 }
106
107
108
109
110 func (s *Scaler) isSuspendChanged() predicate.Funcs {
111 return predicate.Funcs{
112 CreateFunc: func(_ event.CreateEvent) bool {
113 return false
114 },
115 UpdateFunc: func(e event.UpdateEvent) bool {
116 pxeOld := e.ObjectOld.(*v1pxe.PXE)
117 pxeNew := e.ObjectNew.(*v1pxe.PXE)
118
119 return pxeOld.Spec.Suspend != pxeNew.Spec.Suspend
120 },
121 DeleteFunc: func(_ event.DeleteEvent) bool {
122 return false
123 },
124 }
125 }
126
127
128
129 func (s *Scaler) isBootOptionsConfigMap() predicate.Funcs {
130 return predicate.NewPredicateFuncs(func(obj client.Object) bool {
131 return bootoptions.IsBootOptionsConfigMap(obj)
132 })
133 }
134
135
136 func (s *Scaler) ignoreDelete() predicate.Funcs {
137 return predicate.Funcs{
138 DeleteFunc: func(_ event.DeleteEvent) bool {
139 return false
140 },
141 }
142 }
143
144
145 func (s *Scaler) ignoreUpdate() predicate.Funcs {
146 return predicate.Funcs{
147 UpdateFunc: func(_ event.UpdateEvent) bool {
148 return false
149 },
150 }
151 }
152
153
154
155 func (s *Scaler) createReconcileRequest(_ context.Context, _ client.Object) []reconcile.Request {
156 return []reconcile.Request{
157 {
158 NamespacedName: client.ObjectKey{
159 Name: scalerPXEName,
160 },
161 },
162 }
163 }
164
165
166
167
168
169
170
171
172 func (s *Scaler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, recErr error) {
173 log := fog.FromContext(ctx).WithValues("reconciler", s.name)
174 ctx = fog.IntoContext(ctx, log)
175
176 log.Info("reconciling")
177
178 pxe := &v1pxe.PXE{}
179 if err := s.client.Get(ctx, req.NamespacedName, pxe); err != nil {
180 return ctrl.Result{}, client.IgnoreNotFound(err)
181 }
182
183
184 if pxe.Spec.Suspend {
185 log.Info("reconciliation suspended")
186
187 return ctrl.Result{}, nil
188 }
189
190 pxeMgr := newPXEManager(s.manager, pxe)
191
192 options, err := bootoptions.FromClient(ctx, s.client)
193 if err != nil {
194 return ctrl.Result{}, err
195 }
196
197 patcher := patch.NewSerialPatcher(pxe, s.client)
198 defer func() {
199 res, recErr = s.summarize(ctx, patcher, pxe, recErr)
200 }()
201
202
203 if !options.PXEEnabled {
204 log.Info("PXE booting is disabled - scaling down PXE resources")
205
206 return ctrl.Result{}, s.disablePXEBooting(ctx, pxeMgr)
207 }
208
209
210 if !pxe.GetDeletionTimestamp().IsZero() {
211 log.Info("PXE resource scheduled for deletion - scaling down PXE resources")
212
213 controllerutil.RemoveFinalizer(pxe, v1pxe.Finalizer)
214 return ctrl.Result{}, s.disablePXEBooting(ctx, pxeMgr)
215 }
216
217 required, err := nodesNeedInstallation(ctx, s.client)
218 if err != nil {
219 return ctrl.Result{}, err
220 }
221
222 if !required {
223 log.Info("no nodes currently require installation - scaling down PXE resources")
224
225 return ctrl.Result{}, s.disablePXEBooting(ctx, pxeMgr)
226 }
227
228 log.Info("scaling up PXE resources")
229 return ctrl.Result{}, s.enablePXEBooting(ctx, pxeMgr)
230 }
231
232
233
234
235
236 func nodesNeedInstallation(ctx context.Context, cli client.Client) (bool, error) {
237 ienodeList := &v1ienode.IENodeList{}
238 if err := cli.List(ctx, ienodeList); err != nil {
239 return false, err
240 }
241
242 for i := range ienodeList.Items {
243 err := cli.Get(ctx, client.ObjectKeyFromObject(&ienodeList.Items[i]), &corev1.Node{})
244 if client.IgnoreNotFound(err) != nil {
245 return false, err
246 }
247
248 if kerrors.IsNotFound(err) {
249 return true, nil
250 }
251 }
252 return false, nil
253 }
254
255
256
257
258 func (s *Scaler) enablePXEBooting(ctx context.Context, pxeMgr pxeManager) error {
259 dnsmasqDeployment, err := dnsmasq.ScaledUpDeployment()
260 if err != nil {
261 return err
262 }
263
264 sfsConfigMap, err := staticfileserver.ConfigMap()
265 if err != nil {
266 return err
267 }
268
269 sfsDeployment, err := staticfileserver.ScaledUpDeployment()
270 if err != nil {
271 return err
272 }
273
274 ienodeList := &v1ienode.IENodeList{}
275 if err := s.client.List(ctx, ienodeList); err != nil {
276 return err
277 }
278 globalDNSMasq, err := dnsmasq.GlobalDNSMasqManifest(ienodeList)
279 if err != nil {
280 return err
281 }
282
283 return pxeMgr.apply(ctx, dnsmasqDeployment, sfsConfigMap, sfsDeployment, globalDNSMasq)
284 }
285
286
287
288
289 func (s *Scaler) disablePXEBooting(ctx context.Context, pxeMgr pxeManager) error {
290 dnsmasqDeployment, err := dnsmasq.ScaledDownDeployment()
291 if err != nil {
292 return err
293 }
294
295 sfsDeployment, err := staticfileserver.ScaledDownDeployment()
296 if err != nil {
297 return err
298 }
299
300 return pxeMgr.apply(ctx, dnsmasqDeployment, sfsDeployment)
301 }
302
View as plain text