Skip to content

Commit 03336ed

Browse files
committed
State
1 parent d81d60e commit 03336ed

File tree

7 files changed

+349
-50
lines changed

7 files changed

+349
-50
lines changed

cmd/manager/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import (
4545

4646
"github.com/sapcc/argora/internal/config"
4747
"github.com/sapcc/argora/internal/controller"
48+
"github.com/sapcc/argora/internal/netbox"
4849
// +kubebuilder:scaffold:imports
4950
)
5051

@@ -168,7 +169,7 @@ func main() {
168169
os.Exit(1)
169170
}
170171

171-
if err = controller.NewUpdateReconciler(mgr, cfg, flagVar.reconcileInterval).SetupWithManager(mgr, rateLimiter); err != nil {
172+
if err = controller.NewUpdateReconciler(mgr, cfg, netbox.NewNetbox(), flagVar.reconcileInterval).SetupWithManager(mgr, rateLimiter); err != nil {
172173
setupLog.Error(err, "unable to create controller", "controller", "update")
173174
os.Exit(1)
174175
}

internal/controller/metal3_controller.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ func NewMetal3Reconciler(client client.Client, scheme *runtime.Scheme, cfg *conf
8484
func (r *Metal3Reconciler) SetupWithManager(mgr manager.Manager, rateLimiter RateLimiter) error {
8585
return ctrl.NewControllerManagedBy(mgr).
8686
For(&clusterv1.Cluster{}).
87-
WithEventFilter(predicate.Or[client.Object](predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{})).
87+
WithEventFilter(predicate.Or(predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{})).
8888
WithOptions(controller.Options{
89-
RateLimiter: workqueue.NewTypedMaxOfRateLimiter[ctrl.Request](
89+
RateLimiter: workqueue.NewTypedMaxOfRateLimiter(
9090
workqueue.NewTypedItemExponentialFailureRateLimiter[ctrl.Request](rateLimiter.BaseDelay,
9191
rateLimiter.FailureMaxDelay),
9292
&workqueue.TypedBucketRateLimiter[ctrl.Request]{
@@ -154,7 +154,7 @@ func (r *Metal3Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
154154
}
155155
}
156156

157-
return ctrl.Result{RequeueAfter: r.reconcileInterval}, nil
157+
return ctrl.Result{RequeueAfter: r.reconcileInterval}, nil // TODO: should we reconcile in a fixed interval?
158158
}
159159

160160
func (r *Metal3Reconciler) reconcileDevice(ctx context.Context, netBox netbox.Netbox, cluster *clusterv1.Cluster, device *models.Device) error {
@@ -207,7 +207,11 @@ func (r *Metal3Reconciler) reconcileDevice(ctx context.Context, netBox netbox.Ne
207207
return fmt.Errorf("unable to upload bmc secret: %w", err)
208208
}
209209

210-
role := getRoleFromTags(device)
210+
role, err := getRoleFromTags(device)
211+
if err != nil {
212+
return fmt.Errorf("unable to get role from tags: %w", err)
213+
}
214+
211215
if role == device.DeviceRole.Slug {
212216
logger.Info("no role found in tags, using device role")
213217
} else {
@@ -393,9 +397,9 @@ func createLinkHint(device *models.Device, role string) (string, error) {
393397
return "", fmt.Errorf("unknown device model for link hint: %s", device.DeviceType.Model)
394398
}
395399

396-
func getRoleFromTags(device *models.Device) string {
400+
func getRoleFromTags(device *models.Device) (string, error) {
397401
tagsCount := 0
398-
deviceRole := ""
402+
deviceRole := device.DeviceRole.Slug
399403

400404
for _, tag := range device.Tags {
401405
switch tag.Name {
@@ -414,10 +418,11 @@ func getRoleFromTags(device *models.Device) string {
414418
}
415419
}
416420

417-
if tagsCount != 1 { // TODO: what about having multiple tags? - if more then Error
418-
return device.DeviceRole.Slug
421+
if tagsCount > 1 {
422+
return "", errors.New("device has multiple tags")
419423
}
420-
return deviceRole
424+
425+
return deviceRole, nil
421426
}
422427

423428
func getMacForIP(netBox netbox.Netbox, ipAddress string) (string, error) {

internal/controller/mock/mock.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package mock
2+
3+
import (
4+
"errors"
5+
6+
"github.com/sapcc/argora/internal/netbox/dcim"
7+
"github.com/sapcc/argora/internal/netbox/extras"
8+
"github.com/sapcc/argora/internal/netbox/ipam"
9+
"github.com/sapcc/argora/internal/netbox/virtualization"
10+
"github.com/sapcc/go-netbox-go/models"
11+
)
12+
13+
type FileReaderMock struct {
14+
FileContent map[string]string
15+
ReturnError bool
16+
}
17+
18+
func (f *FileReaderMock) ReadFile(fileName string) ([]byte, error) {
19+
if f.ReturnError {
20+
return nil, errors.New("error")
21+
}
22+
return []byte(f.FileContent[fileName]), nil
23+
}
24+
25+
type NetBoxMock struct {
26+
ReturnError bool
27+
VirtualizationMock virtualization.Virtualization
28+
DCIMMock dcim.DCIM
29+
IPAMMock ipam.IPAM
30+
ExtrasMock extras.Extras
31+
}
32+
33+
func (n *NetBoxMock) Reload(_, _ string) error {
34+
if n.ReturnError {
35+
return errors.New("error")
36+
}
37+
return nil
38+
}
39+
40+
func (n *NetBoxMock) Virtualization() virtualization.Virtualization {
41+
return n.VirtualizationMock
42+
}
43+
44+
func (n *NetBoxMock) SetVirtualization(virtualization virtualization.Virtualization) {
45+
}
46+
47+
func (n *NetBoxMock) DCIM() dcim.DCIM {
48+
return n.DCIMMock
49+
}
50+
51+
func (n *NetBoxMock) SetDCIM(dcim dcim.DCIM) {
52+
}
53+
54+
func (n *NetBoxMock) IPAM() ipam.IPAM {
55+
return n.IPAMMock
56+
}
57+
58+
func (n *NetBoxMock) SetIPAM(ipam ipam.IPAM) {
59+
}
60+
61+
func (n *NetBoxMock) Extras() extras.Extras {
62+
return n.ExtrasMock
63+
}
64+
65+
func (n *NetBoxMock) SetExtras(extras extras.Extras) {
66+
}
67+
68+
type VirtualizationMock struct {
69+
GetClusterByNameFunc func(clusterName string) (*models.Cluster, error)
70+
GetClusterByNameRegionTypeFunc func(name, region, clusterType string) (*models.Cluster, error)
71+
}
72+
73+
func (v *VirtualizationMock) GetClusterByName(clusterName string) (*models.Cluster, error) {
74+
return v.GetClusterByNameFunc(clusterName)
75+
}
76+
77+
func (v *VirtualizationMock) GetClusterByNameRegionType(name, region, clusterType string) (*models.Cluster, error) {
78+
return v.GetClusterByNameRegionTypeFunc(name, region, clusterType)
79+
}
80+
81+
type DCIMMock struct {
82+
GetDeviceByNameFunc func(deviceName string) (*models.Device, error)
83+
GetDeviceByIDFunc func(id int) (*models.Device, error)
84+
GetDevicesByClusterIDFunc func(clusterID int) ([]models.Device, error)
85+
GetRoleByNameFunc func(roleName string) (*models.DeviceRole, error)
86+
GetRegionForDeviceFunc func(device *models.Device) (string, error)
87+
GetInterfaceByIDFunc func(id int) (*models.Interface, error)
88+
GetInterfacesForDeviceFunc func(device *models.Device) ([]models.Interface, error)
89+
GetInterfaceForDeviceFunc func(device *models.Device, ifaceName string) (*models.Interface, error)
90+
GetInterfacesByLagIDFunc func(lagID int) ([]models.Interface, error)
91+
GetPlatformByNameFunc func(platformName string) (*models.Platform, error)
92+
93+
UpdateDeviceFunc func(device models.WritableDeviceWithConfigContext) (*models.Device, error)
94+
UpdateInterfaceFunc func(iface models.WritableInterface, id int) (*models.Interface, error)
95+
96+
DeleteInterfaceFunc func(id int) error
97+
}
98+
99+
func (d *DCIMMock) GetDeviceByName(deviceName string) (*models.Device, error) {
100+
return d.GetDeviceByNameFunc(deviceName)
101+
}
102+
103+
func (d *DCIMMock) GetDeviceByID(id int) (*models.Device, error) {
104+
return d.GetDeviceByIDFunc(id)
105+
}
106+
107+
func (d *DCIMMock) GetDevicesByClusterID(clusterID int) ([]models.Device, error) {
108+
return d.GetDevicesByClusterIDFunc(clusterID)
109+
}
110+
111+
func (d *DCIMMock) GetRoleByName(roleName string) (*models.DeviceRole, error) {
112+
return d.GetRoleByNameFunc(roleName)
113+
}
114+
115+
func (d *DCIMMock) GetRegionForDevice(device *models.Device) (string, error) {
116+
return d.GetRegionForDeviceFunc(device)
117+
}
118+
119+
func (d *DCIMMock) GetInterfaceByID(id int) (*models.Interface, error) {
120+
return d.GetInterfaceByIDFunc(id)
121+
}
122+
123+
func (d *DCIMMock) GetInterfacesForDevice(device *models.Device) ([]models.Interface, error) {
124+
return d.GetInterfacesForDeviceFunc(device)
125+
}
126+
127+
func (d *DCIMMock) GetInterfaceForDevice(device *models.Device, ifaceName string) (*models.Interface, error) {
128+
return d.GetInterfaceForDeviceFunc(device, ifaceName)
129+
}
130+
131+
func (d *DCIMMock) GetInterfacesByLagID(lagID int) ([]models.Interface, error) {
132+
return d.GetInterfacesByLagIDFunc(lagID)
133+
}
134+
135+
func (d *DCIMMock) GetPlatformByName(platformName string) (*models.Platform, error) {
136+
return d.GetPlatformByNameFunc(platformName)
137+
}
138+
139+
func (d *DCIMMock) UpdateDevice(device models.WritableDeviceWithConfigContext) (*models.Device, error) {
140+
return d.UpdateDeviceFunc(device)
141+
}
142+
143+
func (d *DCIMMock) UpdateInterface(iface models.WritableInterface, id int) (*models.Interface, error) {
144+
return d.UpdateInterfaceFunc(iface, id)
145+
}
146+
147+
func (d *DCIMMock) DeleteInterface(id int) error {
148+
return d.DeleteInterfaceFunc(id)
149+
}
150+
151+
type IPAMMock struct {
152+
GetVlanByNameFunc func(vlanName string) (*models.Vlan, error)
153+
GetIPAddressByAddressFunc func(address string) (*models.IPAddress, error)
154+
GetIPAddressesForInterfaceFunc func(interfaceID int) ([]models.IPAddress, error)
155+
GetIPAddressForInterfaceFunc func(interfaceID int) (*models.IPAddress, error)
156+
GetPrefixesContainingFunc func(contains string) ([]models.Prefix, error)
157+
158+
DeleteIPAddressFunc func(id int) error
159+
}
160+
161+
func (i *IPAMMock) GetVlanByName(vlanName string) (*models.Vlan, error) {
162+
return i.GetVlanByNameFunc(vlanName)
163+
}
164+
165+
func (i *IPAMMock) GetIPAddressByAddress(address string) (*models.IPAddress, error) {
166+
return i.GetIPAddressByAddressFunc(address)
167+
}
168+
169+
func (i *IPAMMock) GetIPAddressesForInterface(interfaceID int) ([]models.IPAddress, error) {
170+
return i.GetIPAddressesForInterfaceFunc(interfaceID)
171+
}
172+
173+
func (i *IPAMMock) GetIPAddressForInterface(interfaceID int) (*models.IPAddress, error) {
174+
return i.GetIPAddressForInterfaceFunc(interfaceID)
175+
}
176+
177+
func (i *IPAMMock) GetPrefixesContaining(contains string) ([]models.Prefix, error) {
178+
return i.GetPrefixesContainingFunc(contains)
179+
}
180+
181+
func (i *IPAMMock) DeleteIPAddress(id int) error {
182+
return i.DeleteIPAddressFunc(id)
183+
}
184+
185+
type ExtrasMock struct {
186+
GetTagByNameFunc func(tagName string) (*models.Tag, error)
187+
}
188+
189+
func (e *ExtrasMock) GetTagByName(tagName string) (*models.Tag, error) {
190+
return e.GetTagByNameFunc(tagName)
191+
}

internal/controller/update_controller.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,16 @@ type UpdateReconciler struct {
5252
k8sClient client.Client
5353
scheme *runtime.Scheme
5454
cfg *config.Config
55+
netBox netbox.Netbox
5556
reconcileInterval time.Duration
5657
}
5758

58-
func NewUpdateReconciler(mgr ctrl.Manager, cfg *config.Config, reconcileInterval time.Duration) *UpdateReconciler {
59+
func NewUpdateReconciler(mgr ctrl.Manager, cfg *config.Config, netBox netbox.Netbox, reconcileInterval time.Duration) *UpdateReconciler {
5960
return &UpdateReconciler{
6061
k8sClient: mgr.GetClient(),
6162
scheme: mgr.GetScheme(),
6263
cfg: cfg,
64+
netBox: netBox,
6365
reconcileInterval: reconcileInterval,
6466
}
6567
}
@@ -68,9 +70,9 @@ func NewUpdateReconciler(mgr ctrl.Manager, cfg *config.Config, reconcileInterval
6870
func (r *UpdateReconciler) SetupWithManager(mgr ctrl.Manager, rateLimiter RateLimiter) error {
6971
return ctrl.NewControllerManagedBy(mgr).
7072
For(&argorav1alpha1.Update{}).
71-
WithEventFilter(predicate.Or[client.Object](predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{})).
73+
WithEventFilter(predicate.Or(predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{})).
7274
WithOptions(controller.Options{
73-
RateLimiter: workqueue.NewTypedMaxOfRateLimiter[ctrl.Request](
75+
RateLimiter: workqueue.NewTypedMaxOfRateLimiter(
7476
workqueue.NewTypedItemExponentialFailureRateLimiter[ctrl.Request](rateLimiter.BaseDelay,
7577
rateLimiter.FailureMaxDelay),
7678
&workqueue.TypedBucketRateLimiter[ctrl.Request]{
@@ -98,9 +100,9 @@ func (r *UpdateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
98100

99101
logger.Info("configuration reloaded", "config", r.cfg)
100102

101-
netBox, err := netbox.NewDefaultNetbox(r.cfg.NetboxUrl, r.cfg.NetboxToken)
103+
err = r.netBox.Reload(r.cfg.NetboxUrl, r.cfg.NetboxToken)
102104
if err != nil {
103-
logger.Error(err, "unable to create netbox client")
105+
logger.Error(err, "unable to reload netbox")
104106
return ctrl.Result{}, err
105107
}
106108

@@ -115,7 +117,7 @@ func (r *UpdateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
115117

116118
logger.Info("clusters", "count", len(updateCR.Spec.Clusters))
117119
for _, clusterSelector := range updateCR.Spec.Clusters {
118-
cluster, err := netBox.Virtualization().GetClusterByNameRegionType(clusterSelector.Name, clusterSelector.Region, clusterSelector.Type)
120+
cluster, err := r.netBox.Virtualization().GetClusterByNameRegionType(clusterSelector.Name, clusterSelector.Region, clusterSelector.Type)
119121
if err != nil {
120122
logger.Error(err, "unable to find clusters", "name", clusterSelector.Name, "region", clusterSelector.Region, "type", clusterSelector.Type)
121123

@@ -125,7 +127,7 @@ func (r *UpdateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
125127
return ctrl.Result{}, err
126128
}
127129

128-
devices, err := netBox.DCIM().GetDevicesByClusterID(cluster.ID)
130+
devices, err := r.netBox.DCIM().GetDevicesByClusterID(cluster.ID)
129131
if err != nil {
130132
logger.Error(err, "unable to find devices for cluster", "name", cluster.Name, "ID", cluster.ID)
131133

@@ -136,7 +138,7 @@ func (r *UpdateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
136138
}
137139

138140
for _, device := range devices {
139-
err = r.reconcileDevice(ctx, netBox, &device)
141+
err = r.reconcileDevice(ctx, r.netBox, &device)
140142
if err != nil {
141143
logger.Error(err, "unable to reconcile device", "cluster", cluster.Name, "clusterID", cluster.ID, "device", device.Name, "deviceID", device.ID)
142144

0 commit comments

Comments
 (0)