Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "network-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
control-plane: controller-manager
name: {{ include "network-operator.resourceName" (dict "suffix" "controller-manager-tftp-service" "context" $) }}
namespace: {{ .Release.Namespace }}
spec:
ports:
- name: tftp
port: 1069
protocol: UDP
targetPort: 1069
selector:
app.kubernetes.io/name: {{ include "network-operator.name" . }}
control-plane: controller-manager
type: ClusterIP
6 changes: 6 additions & 0 deletions charts/network-operator/templates/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ spec:
{{- with .Values.manager.nodeSelector }}
nodeSelector: {{ toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.manager.imagePullSecrets }}
imagePullSecrets: {{ toYaml . | nindent 8 }}
{{- end }}
containers:
- args:
{{- if .Values.metrics.enable }}
Expand All @@ -54,6 +57,9 @@ spec:
- /manager
image: "{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}"
imagePullPolicy: {{ .Values.manager.image.pullPolicy }}
{{- with .Values.manager.env }}
env: {{ toYaml . | nindent 10 }}
{{- end }}
livenessProbe:
httpGet:
path: /healthz
Expand Down
12 changes: 6 additions & 6 deletions charts/network-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,24 @@ manager:
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
type: RuntimeDefault

## Container-level security settings
##
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
drop:
- ALL

## Resource limits and requests
##
resources:
limits:
memory: 512Mi
memory: 512Mi
requests:
cpu: 150m
memory: 256Mi
cpu: 150m
memory: 256Mi

## Manager pod's affinity
##
Expand Down
36 changes: 28 additions & 8 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth"

// Set runtime concurrency to match CPU limit imposed by Kubernetes
_ "go.uber.org/automaxprocs"

"github.com/sapcc/go-api-declarations/bininfo"
_ "go.uber.org/automaxprocs"
"go.uber.org/zap/zapcore"
coordinationv1 "k8s.io/api/coordination/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -36,20 +35,21 @@ import (
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"

// Import all supported provider implementations.
_ "github.com/ironcore-dev/network-operator/internal/provider/cisco/iosxr"
_ "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos"
_ "github.com/ironcore-dev/network-operator/internal/provider/openconfig"

nxv1alpha1 "github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1"
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
nxcontroller "github.com/ironcore-dev/network-operator/internal/controller/cisco/nx"
corecontroller "github.com/ironcore-dev/network-operator/internal/controller/core"
"github.com/ironcore-dev/network-operator/internal/provider"
"github.com/ironcore-dev/network-operator/internal/provisioning"
"github.com/ironcore-dev/network-operator/internal/resourcelock"
tftpserver "github.com/ironcore-dev/network-operator/internal/tftp"
webhooknxv1alpha1 "github.com/ironcore-dev/network-operator/internal/webhook/cisco/nx/v1alpha1"
webhookv1alpha1 "github.com/ironcore-dev/network-operator/internal/webhook/core/v1alpha1"

// Import all supported provider implementations.
_ "github.com/ironcore-dev/network-operator/internal/provider/cisco/iosxr"
_ "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos"
_ "github.com/ironcore-dev/network-operator/internal/provider/openconfig"
// +kubebuilder:scaffold:imports
)

Expand Down Expand Up @@ -81,6 +81,8 @@ func main() {
var watchFilterValue string
var providerName string
var requeueInterval time.Duration
var tftpPort int
var tftpValidateSourceIP bool
var maxConcurrentReconciles int
var lockerNamespace string
var lockerDuration time.Duration
Expand All @@ -102,12 +104,14 @@ func main() {
flag.StringVar(&watchFilterValue, "watch-filter", "", fmt.Sprintf("Label value that the controller watches to reconcile api objects. Label key is always %q. If unspecified, the controller watches for all api objects.", v1alpha1.WatchLabel))
flag.StringVar(&providerName, "provider", "openconfig", "The provider to use for the controller. If not specified, the default provider is used. Available providers: "+strings.Join(provider.Providers(), ", "))
flag.DurationVar(&requeueInterval, "requeue-interval", time.Hour, "The interval after which Kubernetes resources should be reconciled again regardless of whether they have changed.")
flag.IntVar(&tftpPort, "tftp-port", 1069, "The port on which the inline TFTP server listens. Set to 0 to disable the TFTP server.")
flag.BoolVar(&tftpValidateSourceIP, "tftp-validate-source-ip", false, "If set, the TFTP server validates the source IP and requested serial-based filename against the same Device.")
flag.IntVar(&maxConcurrentReconciles, "max-concurrent-reconciles", 1, "The maximum number of concurrent reconciles per controller. Defaults to 1.")
flag.StringVar(&lockerNamespace, "locker-namespace", "", "The namespace to use for resource locker coordination. If not specified, uses the namespace the manager is deployed in, or 'default' if undetectable.")
flag.DurationVar(&lockerDuration, "locker-duration", 5*time.Second, "The duration of the resource locker lease.")
flag.DurationVar(&lockerRenewInterval, "locker-renew-interval", time.Second, "The interval at which the resource locker lease is renewed.")
flag.IntVar(&provisioningHTTPPort, "provisioning-http-port", 8080, "The port on which the provisioning HTTP server listens.")
flag.BoolVar(&provisioningHTTPValidateSourceIP, "provisioning-http-validate-source-ip", false, "If set, the provisioning HTTP server will validate the source IP of incoming requests against the DeviceIPLabel of Device resources.")
flag.BoolVar(&provisioningHTTPValidateSourceIP, "provisioning-http-validate-source-ip", false, "If set, the provisioning HTTP server will validate the source IP of incoming requests against Device.spec.endpoint.address.")
opts := zap.Options{
Development: true,
TimeEncoder: zapcore.ISO8601TimeEncoder,
Expand Down Expand Up @@ -672,6 +676,22 @@ func main() {
}
}

// Start inline TFTP server when the configured port is non-zero.
if tftpPort != 0 {
tftpAddr := fmt.Sprintf(":%d", tftpPort)
srv, err := tftpserver.New(ctx, tftpAddr, tftpValidateSourceIP, mgr, klog.NewKlogr().WithName("tftp"))
if err != nil {
setupLog.Error(err, "unable to initialize TFTP server")
os.Exit(1)
}

setupLog.Info("Adding inline TFTP server to manager", "address", tftpAddr, "validateSourceIP", tftpValidateSourceIP)
if err := mgr.Add(srv); err != nil {
setupLog.Error(err, "unable to add TFTP server to manager")
os.Exit(1)
}
}

// +kubebuilder:scaffold:builder

if metricsCertWatcher != nil {
Expand Down
7 changes: 7 additions & 0 deletions config/default/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ resources:
- metrics_service.yaml
# [PROVISIONING] Expose the controller manager provisioning service.
- provisioning_service.yaml
# [TFTP] Expose the controller manager TFTP service.
- tftp_service.yaml
# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.
# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.
# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will
Expand Down Expand Up @@ -61,6 +63,11 @@ patches:
target:
kind: Deployment

# [TFTP] The following patch will add the TFTP port to the manager container.
- path: manager_tftp_patch.yaml
target:
kind: Deployment

# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
# Uncomment the following replacements to add the cert-manager CA injection annotations
replacements:
Expand Down
7 changes: 7 additions & 0 deletions config/default/manager_tftp_patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This patch adds the container port for the TFTP service.
- op: add
path: /spec/template/spec/containers/0/ports/-
value:
containerPort: 1069
name: tftp
protocol: UDP
19 changes: 19 additions & 0 deletions config/default/tftp_service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
labels:
control-plane: controller-manager
app.kubernetes.io/name: network-operator
app.kubernetes.io/managed-by: kustomize
name: controller-manager-tftp-service
namespace: system
spec:
ports:
- name: tftp
port: 1069
protocol: UDP
targetPort: 1069
selector:
control-plane: controller-manager
app.kubernetes.io/name: network-operator
type: ClusterIP
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/openconfig/goyang v1.6.3
github.com/openconfig/ygnmi v0.14.0
github.com/openconfig/ygot v0.34.0
github.com/pin/tftp/v3 v3.1.0
github.com/sapcc/go-api-declarations v1.21.0
github.com/stretchr/testify v1.11.1
github.com/tidwall/gjson v1.18.0
Expand Down Expand Up @@ -70,7 +71,7 @@ require (
github.com/google/gnostic-models v0.7.1 // indirect
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
Expand Down Expand Up @@ -113,8 +114,8 @@ require (
golang.org/x/text v0.35.0 // indirect
golang.org/x/time v0.14.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 h1:kEISI/Gx67NzH3nJxAmY/dGac80kKZgZt134u7Y/k1s=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4/go.mod h1:6Nz966r3vQYCqIzWsuEl9d7cf7mRhtDmm++sOxlnfxI=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
Expand Down Expand Up @@ -153,6 +155,8 @@ github.com/openconfig/ygnmi v0.14.0 h1:WXkZU8NcMVJzZBn1mDm7GMdJ+WyU2ab/4ls1kMKPN
github.com/openconfig/ygnmi v0.14.0/go.mod h1:c5ThNBAhrg5o3ZP5V9xagjlJZaKLPFMcJ2Mc3mcNJ8g=
github.com/openconfig/ygot v0.34.0 h1:9OkVjy3SGi4mbvAZc4HTQBU9u4MT6k4j5DdX+hgRiC4=
github.com/openconfig/ygot v0.34.0/go.mod h1:eMNQHrJpanet+pQoBw/P3ua4sLY/tRTXyJ7ALkWCvl4=
github.com/pin/tftp/v3 v3.1.0 h1:rQaxd4pGwcAJnpId8zC+O2NX3B2/NscjDZQaqEjuE7c=
github.com/pin/tftp/v3 v3.1.0/go.mod h1:xwQaN4viYL019tM4i8iecm++5cGxSqen6AJEOEyEI0w=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down Expand Up @@ -210,6 +214,8 @@ go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
Expand All @@ -234,22 +240,26 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
Expand All @@ -262,8 +272,12 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
Expand Down
12 changes: 12 additions & 0 deletions internal/controller/core/device_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,18 @@ func (r *DeviceReconciler) reconcile(ctx context.Context, device *v1alpha1.Devic
Message: "Device is healthy",
})

log := ctrl.LoggerFrom(ctx)
if device.Labels == nil {
device.Labels = map[string]string{}
}
if serial := strings.ToLower(device.Status.SerialNumber); serial != "" {
if device.Labels[v1alpha1.DeviceSerialLabel] == "" {
device.Labels[v1alpha1.DeviceSerialLabel] = serial
} else if device.Labels[v1alpha1.DeviceSerialLabel] != serial {
log.Info("Device serial label does not match observed device serial number", "labelSerial", device.Labels[v1alpha1.DeviceSerialLabel], "observedSerial", serial)
}
}

return nil
}

Expand Down
32 changes: 32 additions & 0 deletions internal/controller/core/device_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ var _ = Describe("Device Controller", func() {
g.Expect(resource.Status.Manufacturer).To(Equal("Manufacturer"))
g.Expect(resource.Status.Model).To(Equal("Model"))
g.Expect(resource.Status.SerialNumber).To(Equal("123456789"))
g.Expect(resource.Labels).To(HaveKeyWithValue(v1alpha1.DeviceSerialLabel, "123456789"))
g.Expect(resource.Status.FirmwareVersion).To(Equal("1.0.0"))
g.Expect(resource.Status.LastRebootTime.Time).To(BeTemporally("==", lastRebootTime))

Expand Down Expand Up @@ -182,6 +183,37 @@ var _ = Describe("Device Controller", func() {
}).Should(Succeed())
})

It("Should keep an existing mismatched serial label", func() {
By("Creating the custom resource for the Kind Device with a pre-set serial label")
device := &v1alpha1.Device{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
Labels: map[string]string{
v1alpha1.DeviceSerialLabel: "manual-serial",
},
},
Spec: v1alpha1.DeviceSpec{
Endpoint: v1alpha1.Endpoint{
Address: "192.168.10.2:9339",
SecretRef: &v1alpha1.SecretReference{
Name: name,
},
},
},
}
Expect(k8sClient.Create(ctx, device)).To(Succeed())

By("Verifying the observed serial number is recorded without overwriting the existing label")
Eventually(func(g Gomega) {
resource := &v1alpha1.Device{}
g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed())
g.Expect(resource.Status.Phase).To(Equal(v1alpha1.DevicePhaseRunning))
g.Expect(resource.Status.SerialNumber).To(Equal("123456789"))
g.Expect(resource.Labels).To(HaveKeyWithValue(v1alpha1.DeviceSerialLabel, "manual-serial"))
}).Should(Succeed())
})

It("Should transition from ProvisioningCompleted to Running", func() {
By("Creating a Device")
device := &v1alpha1.Device{
Expand Down
Loading
Loading