Skip to content

Commit 835e257

Browse files
committed
Add provisioning HTTP servers
This commit adds a HTTP server that enables a device which is being provisioned to get its configuration via HTTP. Devices can also report their status and thus transition out of the Provisioning Phase.
1 parent 41d7efa commit 835e257

File tree

4 files changed

+1591
-0
lines changed

4 files changed

+1591
-0
lines changed

cmd/main.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
1616
// to ensure that exec-entrypoint and run can make use of them.
1717
_ "k8s.io/client-go/plugin/pkg/client/auth"
18+
"k8s.io/klog/v2"
1819
"k8s.io/utils/ptr"
1920

2021
// Set runtime concurrency to match CPU limit imposed by Kubernetes
@@ -38,6 +39,7 @@ import (
3839
// Import all supported provider implementations.
3940
_ "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos"
4041
_ "github.com/ironcore-dev/network-operator/internal/provider/openconfig"
42+
"github.com/ironcore-dev/network-operator/internal/provisioning"
4143

4244
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
4345
corecontroller "github.com/ironcore-dev/network-operator/internal/controller/core"
@@ -72,6 +74,8 @@ func main() {
7274
var watchFilterValue string
7375
var providerName string
7476
var requeueInterval time.Duration
77+
var provisioningHTTPPort int
78+
var provisioningHTTPValidateSourceIP bool
7579
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
7680
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
7781
flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
@@ -86,6 +90,8 @@ func main() {
8690
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))
8791
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(), ", "))
8892
flag.DurationVar(&requeueInterval, "requeue-interval", 30*time.Second, "The interval after which Kubernetes resources should be reconciled again regardless of whether they have changed.")
93+
flag.IntVar(&provisioningHTTPPort, "provisioning-http-port", 8080, "The port on which the provisioning HTTP server listens.")
94+
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.")
8995
opts := zap.Options{
9096
Development: true,
9197
TimeEncoder: zapcore.ISO8601TimeEncoder,
@@ -365,6 +371,25 @@ func main() {
365371
os.Exit(1)
366372
}
367373
}
374+
provisioningProvider, ok := prov().(provider.ProvisioningProvider)
375+
if provisioningHTTPPort != 0 && ok {
376+
provisioningServer := provisioning.HTTPServer{
377+
Client: mgr.GetClient(),
378+
Port: provisioningHTTPPort,
379+
Logger: klog.NewKlogr().WithName("provisioning"),
380+
Recorder: mgr.GetEventRecorderFor("provisioning"),
381+
ValidateSourceIP: provisioningHTTPValidateSourceIP,
382+
Provider: provisioningProvider,
383+
}
384+
setupLog.Info("Starting provisioning HTTP server", "port", provisioningHTTPPort, "validateSourceIP", provisioningHTTPValidateSourceIP)
385+
go func() {
386+
if err := provisioningServer.Start(ctx); err != nil {
387+
setupLog.Error(err, "provisioning HTTP server failed")
388+
os.Exit(1)
389+
}
390+
}()
391+
}
392+
368393
// +kubebuilder:scaffold:builder
369394

370395
if metricsCertWatcher != nil {

internal/deviceutil/deviceutil.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"google.golang.org/grpc/credentials"
1515
"google.golang.org/grpc/credentials/insecure"
1616
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
"k8s.io/apimachinery/pkg/labels"
1718
"k8s.io/apimachinery/pkg/runtime/schema"
1819
ctrl "sigs.k8s.io/controller-runtime"
1920
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -62,6 +63,28 @@ func GetDeviceByName(ctx context.Context, r client.Reader, namespace, name strin
6263
return obj, nil
6364
}
6465

66+
func GetDeviceBySerial(ctx context.Context, r client.Reader, namespace, serial string) (*v1alpha1.Device, error) {
67+
deviceList := &v1alpha1.DeviceList{}
68+
listOpts := &client.ListOptions{
69+
LabelSelector: labels.SelectorFromSet(labels.Set{v1alpha1.DeviceSerialLabel: serial}),
70+
}
71+
72+
if namespace != "" {
73+
listOpts.Namespace = namespace
74+
}
75+
76+
if err := r.List(ctx, deviceList, listOpts); err != nil {
77+
return nil, fmt.Errorf("failed to list %s objects: %w", v1alpha1.GroupVersion.WithKind(v1alpha1.DeviceKind).String(), err)
78+
}
79+
if len(deviceList.Items) == 0 {
80+
return nil, fmt.Errorf("no %s object found with serial %q", v1alpha1.GroupVersion.WithKind(v1alpha1.DeviceKind).String(), serial)
81+
}
82+
if len(deviceList.Items) > 1 {
83+
return nil, fmt.Errorf("multiple %s objects found with serial %q", v1alpha1.GroupVersion.WithKind(v1alpha1.DeviceKind).String(), serial)
84+
}
85+
return &deviceList.Items[0], nil
86+
}
87+
6588
// Connection holds the necessary information to connect to a device's API.
6689
//
6790
// TODO(felix-kaestner): find a better place for this struct, maybe in a 'connection' package?

0 commit comments

Comments
 (0)