Skip to content

Commit 669f3e4

Browse files
authored
feat(controller): migrate service reconciliation to kustomize manifest (#105)
Migrate service creation from programmatic to manifest-based Kustomizer pipeline, with added service comparison utilities to detect unexpected changes and enforce field mutability boundaries between operator-managed and cluster-managed state. Approved-by: rhdedgar Approved-by: VaishnaviHire
1 parent 1710fa6 commit 669f3e4

File tree

6 files changed

+110
-69
lines changed

6 files changed

+110
-69
lines changed

controllers/llamastackdistribution_controller.go

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -225,28 +225,21 @@ func (r *LlamaStackDistributionReconciler) reconcileResources(ctx context.Contex
225225
}
226226
}
227227

228-
// Reconcile the NetworkPolicy
229-
if err := r.reconcileNetworkPolicy(ctx, instance); err != nil {
230-
return fmt.Errorf("failed to reconcile NetworkPolicy: %w", err)
231-
}
232-
233228
// Reconcile manifest-based resources
234229
if err := r.reconcileManifestResources(ctx, instance); err != nil {
235230
return err
236231
}
237232

233+
// Reconcile the NetworkPolicy
234+
if err := r.reconcileNetworkPolicy(ctx, instance); err != nil {
235+
return fmt.Errorf("failed to reconcile NetworkPolicy: %w", err)
236+
}
237+
238238
// Reconcile the Deployment
239239
if err := r.reconcileDeployment(ctx, instance); err != nil {
240240
return fmt.Errorf("failed to reconcile Deployment: %w", err)
241241
}
242242

243-
// Reconcile the Service if ports are defined, else use default port
244-
if instance.HasPorts() {
245-
if err := r.reconcileService(ctx, instance); err != nil {
246-
return fmt.Errorf("failed to reconcile service: %w", err)
247-
}
248-
}
249-
250243
return nil
251244
}
252245

@@ -656,36 +649,6 @@ func (r *LlamaStackDistributionReconciler) reconcileDeployment(ctx context.Conte
656649
return deploy.ApplyDeployment(ctx, r.Client, r.Scheme, instance, deployment, logger)
657650
}
658651

659-
// reconcileService manages the Service if ports are defined.
660-
func (r *LlamaStackDistributionReconciler) reconcileService(ctx context.Context, instance *llamav1alpha1.LlamaStackDistribution) error {
661-
logger := log.FromContext(ctx)
662-
// Use the container's port (defaulted to 8321 if unset)
663-
port := deploy.GetServicePort(instance)
664-
665-
service := &corev1.Service{
666-
ObjectMeta: metav1.ObjectMeta{
667-
Name: deploy.GetServiceName(instance),
668-
Namespace: instance.Namespace,
669-
},
670-
Spec: corev1.ServiceSpec{
671-
Selector: map[string]string{
672-
llamav1alpha1.DefaultLabelKey: llamav1alpha1.DefaultLabelValue,
673-
"app.kubernetes.io/instance": instance.Name,
674-
},
675-
Ports: []corev1.ServicePort{{
676-
Name: llamav1alpha1.DefaultServicePortName,
677-
Port: port,
678-
TargetPort: intstr.IntOrString{
679-
IntVal: port,
680-
},
681-
}},
682-
Type: corev1.ServiceTypeClusterIP,
683-
},
684-
}
685-
686-
return deploy.ApplyService(ctx, r.Client, r.Scheme, instance, service, logger)
687-
}
688-
689652
// getServerURL returns the URL for the LlamaStack server.
690653
func (r *LlamaStackDistributionReconciler) getServerURL(instance *llamav1alpha1.LlamaStackDistribution, path string) *url.URL {
691654
serviceName := deploy.GetServiceName(instance)

controllers/manifests/base/kustomization.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ resources:
55
- pvc.yaml
66
- serviceaccount.yaml
77
- scc-binding.yaml
8+
- service.yaml
89

910
labels:
10-
- includeSelectors: true
11+
- includeSelectors: false
1112
pairs:
1213
app.kubernetes.io/managed-by: llama-stack-operator
1314
app.kubernetes.io/part-of: llama-stack
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: service
5+
spec:
6+
type: ClusterIP
7+
selector: {}
8+
ports:
9+
- name: http
10+
protocol: TCP

pkg/deploy/deploy.go

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/go-logr/logr"
99
llamav1alpha1 "github.com/llamastack/llama-stack-k8s-operator/api/v1alpha1"
1010
appsv1 "k8s.io/api/apps/v1"
11-
corev1 "k8s.io/api/core/v1"
1211
"k8s.io/apimachinery/pkg/api/errors"
1312
"k8s.io/apimachinery/pkg/runtime"
1413
ctrl "sigs.k8s.io/controller-runtime"
@@ -42,28 +41,3 @@ func ApplyDeployment(ctx context.Context, cli client.Client, scheme *runtime.Sch
4241
}
4342
return nil
4443
}
45-
46-
// ApplyService creates or updates the Service.
47-
func ApplyService(ctx context.Context, cli client.Client, scheme *runtime.Scheme,
48-
instance *llamav1alpha1.LlamaStackDistribution, service *corev1.Service, logger logr.Logger) error {
49-
if err := ctrl.SetControllerReference(instance, service, scheme); err != nil {
50-
return fmt.Errorf("failed to set controller reference: %w", err)
51-
}
52-
53-
found := &corev1.Service{}
54-
err := cli.Get(ctx, client.ObjectKeyFromObject(service), found)
55-
if err != nil && errors.IsNotFound(err) {
56-
logger.Info("Creating Service", "service", service.Name)
57-
return cli.Create(ctx, service)
58-
} else if err != nil {
59-
return fmt.Errorf("failed to fetch Service: %w", err)
60-
}
61-
62-
if !reflect.DeepEqual(found.Spec.Selector, service.Spec.Selector) || !reflect.DeepEqual(found.Spec.Ports, service.Spec.Ports) {
63-
found.Spec.Selector = service.Spec.Selector
64-
found.Spec.Ports = service.Spec.Ports
65-
logger.Info("Updating Service", "service", service.Name)
66-
return cli.Update(ctx, found)
67-
}
68-
return nil
69-
}

pkg/deploy/kustomizer.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"slices"
99

1010
llamav1alpha1 "github.com/llamastack/llama-stack-k8s-operator/api/v1alpha1"
11+
"github.com/llamastack/llama-stack-k8s-operator/pkg/compare"
1112
"github.com/llamastack/llama-stack-k8s-operator/pkg/deploy/plugins"
1213
k8serr "k8s.io/apimachinery/pkg/api/errors"
1314
"k8s.io/apimachinery/pkg/api/meta"
@@ -173,6 +174,10 @@ func patchResource(ctx context.Context, cli client.Client, desired, existing *un
173174
"name", existing.GetName(),
174175
"namespace", existing.GetNamespace())
175176
return nil
177+
} else if existing.GetKind() == "Service" {
178+
if err := compare.CheckAndLogServiceChanges(ctx, cli, desired); err != nil {
179+
return fmt.Errorf("failed to validate resource mutations while patching: %w", err)
180+
}
176181
}
177182

178183
data, err := json.Marshal(desired)
@@ -227,6 +232,34 @@ func applyPlugins(resMap *resmap.ResMap, ownerInstance *llamav1alpha1.LlamaStack
227232
TargetKind: "ClusterRoleBinding",
228233
CreateIfNotExists: true,
229234
},
235+
{
236+
SourceValue: getServicePort(ownerInstance),
237+
DefaultValue: llamav1alpha1.DefaultServerPort,
238+
TargetField: "/spec/ports/0/port",
239+
TargetKind: "Service",
240+
CreateIfNotExists: true,
241+
},
242+
{
243+
SourceValue: getServicePort(ownerInstance),
244+
DefaultValue: llamav1alpha1.DefaultServerPort,
245+
TargetField: "/spec/ports/0/targetPort",
246+
TargetKind: "Service",
247+
CreateIfNotExists: true,
248+
},
249+
{
250+
SourceValue: nil,
251+
DefaultValue: llamav1alpha1.DefaultLabelValue,
252+
TargetField: "/spec/selector/" + llamav1alpha1.DefaultLabelKey,
253+
TargetKind: "Service",
254+
CreateIfNotExists: true,
255+
},
256+
{
257+
SourceValue: nil,
258+
DefaultValue: ownerInstance.GetName(),
259+
TargetField: "/spec/selector/app.kubernetes.io~1instance",
260+
TargetKind: "Service",
261+
CreateIfNotExists: true,
262+
},
230263
},
231264
})
232265
if err := fieldTransformerPlugin.Transform(*resMap); err != nil {
@@ -245,6 +278,15 @@ func getStorageSize(instance *llamav1alpha1.LlamaStackDistribution) string {
245278
return ""
246279
}
247280

281+
// getServicePort returns the service port or nil if not specified.
282+
func getServicePort(instance *llamav1alpha1.LlamaStackDistribution) any {
283+
if instance.Spec.Server.ContainerSpec.Port != 0 {
284+
return instance.Spec.Server.ContainerSpec.Port
285+
}
286+
// Returning nil signals the field transformer to use the default value.
287+
return nil
288+
}
289+
248290
func FilterExcludeKinds(resMap *resmap.ResMap, kindsToExclude []string) (*resmap.ResMap, error) {
249291
filteredResMap := resmap.New()
250292
for _, res := range (*resMap).Resources() {

pkg/deploy/kustomizer_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77

88
llamav1alpha1 "github.com/llamastack/llama-stack-k8s-operator/api/v1alpha1"
9+
"github.com/llamastack/llama-stack-k8s-operator/pkg/deploy/plugins"
910
"github.com/stretchr/testify/assert"
1011
"github.com/stretchr/testify/require"
1112
appsv1 "k8s.io/api/apps/v1"
@@ -530,3 +531,53 @@ func TestFilterExcludeKinds(t *testing.T) {
530531
require.Equal(t, "Deployment", (*filtered).Resources()[0].GetKind())
531532
})
532533
}
534+
535+
func TestSetDefaultPort(t *testing.T) {
536+
// arrange
537+
// instance with no custom port and service with empty port values
538+
instance := &llamav1alpha1.LlamaStackDistribution{
539+
Spec: llamav1alpha1.LlamaStackDistributionSpec{
540+
Server: llamav1alpha1.ServerSpec{
541+
ContainerSpec: llamav1alpha1.ContainerSpec{
542+
Port: 0, // no port configured
543+
},
544+
},
545+
},
546+
}
547+
548+
service := newTestResource(t, "v1", "Service", "test-service", "test-ns", map[string]any{
549+
"ports": []any{
550+
map[string]any{"port": nil}, // empty port to trigger default
551+
},
552+
})
553+
554+
fieldMutator := plugins.CreateFieldMutator(plugins.FieldMutatorConfig{
555+
Mappings: []plugins.FieldMapping{
556+
{
557+
SourceValue: getServicePort(instance), // tests getServicePort() integration with kustomizer
558+
DefaultValue: llamav1alpha1.DefaultServerPort,
559+
TargetField: "/spec/ports/0/port",
560+
TargetKind: "Service",
561+
CreateIfNotExists: true,
562+
},
563+
},
564+
})
565+
566+
resMap := resmap.New()
567+
require.NoError(t, resMap.Append(service))
568+
569+
// act
570+
// apply field transformation
571+
require.NoError(t, fieldMutator.Transform(resMap))
572+
573+
// assert
574+
// port should be set to default value
575+
transformedService := resMap.Resources()[0]
576+
serviceMap, err := transformedService.Map()
577+
require.NoError(t, err)
578+
ports, ok := serviceMap["spec"].(map[string]any)["ports"].([]any)
579+
require.True(t, ok)
580+
actualPort, ok := ports[0].(map[string]any)["port"]
581+
require.True(t, ok)
582+
require.Equal(t, int(llamav1alpha1.DefaultServerPort), actualPort)
583+
}

0 commit comments

Comments
 (0)