Skip to content
Merged
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
9 changes: 8 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import (
computewebhook "go.datum.net/compute/internal/webhook"
computev1alphawebhooks "go.datum.net/compute/internal/webhook/v1alpha"
networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha"
locationsv1alpha1 "go.miloapis.com/locations/api/v1alpha1"
infrastructurev1alpha1 "go.miloapis.com/milo/pkg/apis/infrastructure/v1alpha1"
quotav1alpha1 "go.miloapis.com/milo/pkg/apis/quota/v1alpha1"
resourcemanagerv1alpha1 "go.miloapis.com/milo/pkg/apis/resourcemanager/v1alpha1"
Expand Down Expand Up @@ -91,6 +92,7 @@ func init() {
utilruntime.Must(config.RegisterDefaults(scheme))
utilruntime.Must(computev1alpha.AddToScheme(scheme))
utilruntime.Must(networkingv1alpha.AddToScheme(scheme))
utilruntime.Must(locationsv1alpha1.AddToScheme(scheme))
utilruntime.Must(quotav1alpha1.AddToScheme(scheme))
utilruntime.Must(karmadapolicyv1alpha1.Install(scheme))
utilruntime.Must(karmadaclusterv1alpha1.Install(scheme))
Expand Down Expand Up @@ -350,6 +352,7 @@ func main() {
if enableManagementControllers {
if err = (&controller.WorkloadReconciler{
NetworkingEnabled: features.FeatureGate.Enabled(features.NetworkingIntegration),
LocationSource: serverConfig.LocationSource,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Workload")
os.Exit(1)
Expand All @@ -374,6 +377,7 @@ func main() {
}
if err = (&controller.WorkloadDeploymentReconciler{
NetworkingEnabled: features.FeatureGate.Enabled(features.NetworkingIntegration),
LocationSource: serverConfig.LocationSource,
}).SetupWithManager(mgr, wdOpts); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "WorkloadDeployment")
os.Exit(1)
Expand Down Expand Up @@ -457,7 +461,7 @@ func main() {
}

if serverConfig.WebhookServer != nil {
if err = computev1alphawebhooks.SetupWorkloadWebhookWithManager(mgr); err != nil {
if err = computev1alphawebhooks.SetupWorkloadWebhookWithManager(mgr, serverConfig.LocationSource); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "Workload")
os.Exit(1)
}
Expand Down Expand Up @@ -803,6 +807,9 @@ func loadServerConfig(path string) (config.WorkloadOperator, error) {
if err := runtime.DecodeInto(codecs.UniversalDecoder(), configData, &serverConfig); err != nil {
return serverConfig, fmt.Errorf("unable to decode server config: %w", err)
}
if _, err := serverConfig.LocationSource.Resolve(); err != nil {
return serverConfig, fmt.Errorf("invalid server config: %w", err)
}
return serverConfig, nil
}

Expand Down
69 changes: 69 additions & 0 deletions cmd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@
package main

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"

networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha"
locationsv1alpha1 "go.miloapis.com/locations/api/v1alpha1"
multiclusterproviders "go.miloapis.com/milo/pkg/multicluster-runtime"

"go.datum.net/compute/internal/locations"
)

// TestComputeWatchProviderClaims is the #171 guard: quota enforcement (and thus
Expand All @@ -21,3 +31,62 @@ func TestComputeWatchProviderClaims(t *testing.T) {
assert.False(t, computeWatchProviderClaims(multiclusterproviders.ProviderKind),
"non-milo modes disable quota")
}

// TestLoadServerConfig_LocationSource covers the startup guard: a config that
// names an unknown location source fails to load rather than surfacing later
// on every reconcile.
func TestLoadServerConfig_LocationSource(t *testing.T) {
write := func(t *testing.T, body string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "config.yaml")
require.NoError(t, os.WriteFile(path, []byte(body), 0o600))
return path
}

const header = `apiVersion: apiserver.config.datumapis.com/v1alpha1
kind: WorkloadOperator
metricsServer:
bindAddress: "0"
`

cfg, err := loadServerConfig(write(t, header))
require.NoError(t, err)
assert.Equal(t, locations.SourceNetworkServices, cfg.LocationSource)

cfg, err = loadServerConfig(write(t, header+"locationSource: Locations\n"))
require.NoError(t, err)
assert.Equal(t, locations.SourceLocations, cfg.LocationSource)

_, err = loadServerConfig(write(t, header+"locationSource: Nonsense\n"))
require.Error(t, err)
}

// TestSchemeResolvesLocationKinds is the runtime half of the typed locations
// dependency: compiling against the types says nothing about whether a client
// can resolve them. A kind missing here fails every list at runtime with "no
// kind is registered", not at build time.
func TestSchemeResolvesLocationKinds(t *testing.T) {
for _, object := range []client.Object{
&locationsv1alpha1.Location{},
&locationsv1alpha1.ServingLocation{},
&networkingv1alpha.LocationBinding{},
&networkingv1alpha.ServingLocation{},
} {
gvk, err := apiutil.GVKForObject(object, scheme)
require.NoErrorf(t, err, "%T must be registered on the manager scheme", object)
assert.NotEmpty(t, gvk.Kind)
}
}

// TestServingLocationObjectIsRegistered pins the watch the deployment
// reconciler installs: whichever source is selected, the object it watches has
// to be resolvable on the manager scheme.
func TestServingLocationObjectIsRegistered(t *testing.T) {
for _, source := range []locations.Source{"", locations.SourceNetworkServices, locations.SourceLocations} {
object, err := locations.ServingLocationObject(source)
require.NoError(t, err)

_, err = apiutil.GVKForObject(object, scheme)
require.NoErrorf(t, err, "the watch object for source %q must be registered", source)
}
}
9 changes: 9 additions & 0 deletions config/components/controller_rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ rules:
verbs:
- create
- patch
- apiGroups:
- locations.miloapis.com
resources:
- locations
- servinglocations
verbs:
- get
- list
- watch
- apiGroups:
- networking.datumapis.com
resources:
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ require (
// predates the Prepared condition this gate reads. Re-pin to a tagged
// release once one carries it.
go.datum.net/network-services-operator v0.26.1-0.20260821014231-aceb24b1b569
// Pinned by pseudo-version to the commit deployed to staging, which is the
// same one network-services-operator pins. The module publishes no tag yet.
go.miloapis.com/locations v0.0.0-20260825185141-507ac2cbd48c
go.miloapis.com/milo v0.32.0
golang.org/x/crypto v0.54.0
golang.org/x/sync v0.22.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ go.datum.net/datumctl v0.17.1-0.20260710003126-296c2fcbbd67 h1:Mhgt688CeTh2hX7ql
go.datum.net/datumctl v0.17.1-0.20260710003126-296c2fcbbd67/go.mod h1:6skEjcE7aT8VPf/HVamA/BB6Dc9IISA6c/DdYKhqWNc=
go.datum.net/network-services-operator v0.26.1-0.20260821014231-aceb24b1b569 h1:14vajo15fGGEAmdystQEE261rFH4AqER9s1Vg7POUKo=
go.datum.net/network-services-operator v0.26.1-0.20260821014231-aceb24b1b569/go.mod h1:A7JNOuc+e6j/KkUVCcZ7Z2odvf6JFMQlX0Zo1Awj2TY=
go.miloapis.com/locations v0.0.0-20260825185141-507ac2cbd48c h1:+BQirT3wYCgv7H2lEZtAH+dpMiWu9j/Wa+c8s4jJUIA=
go.miloapis.com/locations v0.0.0-20260825185141-507ac2cbd48c/go.mod h1:gzfAfHhSMwl/N68k/uSNYXOKK3IOBJCdXYaBgCE3gdE=
go.miloapis.com/milo v0.32.0 h1:TkNIQu/37d+SEquLJ5+GmdisSl+K2RT7eEC4idg6RIs=
go.miloapis.com/milo v0.32.0/go.mod h1:GKK3afjCwshfZfvhjNe1wp/H45z4m7x5oG/8xbSgU1M=
go.miloapis.com/service-catalog v0.4.0 h1:LvO1WCHMCoFokpS5igWMP8kyqly9gUFQmQj5IGhwuKs=
Expand Down
7 changes: 7 additions & 0 deletions internal/cmd/compute/util/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
computev1alpha "go.datum.net/compute/api/v1alpha"
"go.datum.net/datumctl/plugin"
networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha"
locationsv1alpha1 "go.miloapis.com/locations/api/v1alpha1"
quotav1alpha1 "go.miloapis.com/milo/pkg/apis/quota/v1alpha1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -52,6 +53,9 @@ func NewClient(project string) (client.Client, error) {
if err := networkingv1alpha.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("registering networking scheme: %w", err)
}
if err := locationsv1alpha1.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("registering locations scheme: %w", err)
}
if err := quotav1alpha1.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("registering quota scheme: %w", err)
}
Expand All @@ -78,6 +82,9 @@ func NewPlatformClient() (client.Client, error) {
}

scheme := runtime.NewScheme()
if err := locationsv1alpha1.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("registering locations scheme: %w", err)
}
if err := quotav1alpha1.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("registering quota scheme: %w", err)
}
Expand Down
14 changes: 14 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"

"go.datum.net/compute/internal/locations"
multiclusterproviders "go.miloapis.com/milo/pkg/multicluster-runtime"
)

Expand All @@ -42,6 +43,19 @@ type WorkloadOperator struct {

// ReferencedData configures the ReferencedDataController.
ReferencedData ReferencedDataConfig `json:"referencedData,omitempty"`

// LocationSource names the API group locations are read from. Use
// "NetworkServices" for networking.datumapis.com LocationBindings and
// ServingLocations, or "Locations" for the dedicated locations.miloapis.com
// service. It governs reads only; nothing about what compute writes changes
// with it. Defaults to "NetworkServices".
LocationSource locations.Source `json:"locationSource,omitempty"`
}

func SetDefaults_WorkloadOperator(obj *WorkloadOperator) {
if obj.LocationSource == "" {
obj.LocationSource = locations.SourceNetworkServices
}
}

// +k8s:deepcopy-gen=true
Expand Down
29 changes: 29 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"

"go.datum.net/compute/internal/locations"
)

func decode(t *testing.T, data string) *WorkloadOperator {
Expand Down Expand Up @@ -122,3 +124,30 @@ users:
t.Error("QuotaRestConfig() = nil, want non-nil when file exists")
}
}

// TestLocationSource_DefaultsToNetworkServices is the safety property behind
// the flag: a config that does not set it reads what it reads today.
func TestLocationSource_DefaultsToNetworkServices(t *testing.T) {
cfg := decode(t, `
apiVersion: apiserver.config.datumapis.com/v1alpha1
kind: WorkloadOperator
metricsServer:
bindAddress: "0"
`)
if cfg.LocationSource != locations.SourceNetworkServices {
t.Errorf("LocationSource = %q, want %q", cfg.LocationSource, locations.SourceNetworkServices)
}
}

func TestLocationSource_Explicit(t *testing.T) {
cfg := decode(t, `
apiVersion: apiserver.config.datumapis.com/v1alpha1
kind: WorkloadOperator
metricsServer:
bindAddress: "0"
locationSource: Locations
`)
if cfg.LocationSource != locations.SourceLocations {
t.Errorf("LocationSource = %q, want %q", cfg.LocationSource, locations.SourceLocations)
}
}
1 change: 1 addition & 0 deletions internal/config/zz_generated.defaults.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading