Skip to content

Commit

Permalink
refactor(build): refactor service build support listenerclass (#186)
Browse files Browse the repository at this point in the history
  • Loading branch information
whg517 authored Sep 20, 2024
1 parent f9ca8db commit 1b64749
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 61 deletions.
1 change: 0 additions & 1 deletion pkg/builder/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ type ServiceBuilder interface {
GetObject() *corev1.Service
AddPort(port *corev1.ServicePort)
GetPorts() []corev1.ServicePort
GetServiceType() corev1.ServiceType
}

type ServiceAccountBuilder interface {
Expand Down
8 changes: 8 additions & 0 deletions pkg/builder/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ import (
commonsv1alpha1 "github.com/zncdatadev/operator-go/pkg/apis/commons/v1alpha1"
)

type Option struct {
ClusterName string
RoleName string
RoleGroupName string
Labels map[string]string
Annotations map[string]string
}

type Options struct {
ClusterName string
RoleName string
Expand Down
98 changes: 65 additions & 33 deletions pkg/builder/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package builder
import (
"context"

"github.com/zncdatadev/operator-go/pkg/client"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/zncdatadev/operator-go/pkg/client"
"github.com/zncdatadev/operator-go/pkg/constants"
)

func ContainerPorts2ServicePorts(port []corev1.ContainerPort) []corev1.ServicePort {
Expand All @@ -29,33 +31,56 @@ func ContainerPorts2ServicePorts(port []corev1.ContainerPort) []corev1.ServicePo
return ports
}

// ListenerClass2ServiceType converts listener class to k8s service type
//
// ClusterInternal --> ClusterIP
// ExternalUnstable --> NodePort
// ExternalStable --> LoadBalancer
// Default --> ClusterIP
func ListenerClass2ServiceType(listenerClass constants.ListenerClass) corev1.ServiceType {
switch listenerClass {
case constants.ClusterInternal:
return corev1.ServiceTypeClusterIP
case constants.ExternalUnstable:
return corev1.ServiceTypeNodePort
case constants.ExternalStable:
return corev1.ServiceTypeLoadBalancer
default:
return corev1.ServiceTypeClusterIP
}
}

var _ ServiceBuilder = &BaseServiceBuilder{}

type BaseServiceBuilder struct {
BaseResourceBuilder

// if you want to get ports, please use GetPorts() method
ports []corev1.ServicePort

serviceType *corev1.ServiceType

headless bool
ports []corev1.ServicePort
listenerClass constants.ListenerClass
headless bool
// Setting this parameter will override the default matching labels, generally not needed
matchingLabels map[string]string
}

func (b *BaseServiceBuilder) GetObject() *corev1.Service {
clusterIp := ""
if b.headless {
clusterIp = corev1.ClusterIPNone
matchingLabels := b.GetMatchingLabels()
if b.matchingLabels != nil {
matchingLabels = b.matchingLabels
}
return &corev1.Service{
obj := &corev1.Service{
ObjectMeta: b.GetObjectMeta(),
Spec: corev1.ServiceSpec{
Ports: b.GetPorts(),
Selector: b.GetMatchingLabels(),
Type: b.GetServiceType(),
ClusterIP: clusterIp,
Ports: b.GetPorts(),
Selector: matchingLabels,
Type: ListenerClass2ServiceType(b.listenerClass),
},
}

if b.headless {
obj.Spec.ClusterIP = corev1.ClusterIPNone
}

return obj
}

func (b *BaseServiceBuilder) AddPort(port *corev1.ServicePort) {
Expand All @@ -66,39 +91,46 @@ func (b *BaseServiceBuilder) GetPorts() []corev1.ServicePort {
return b.ports
}

func (b *BaseServiceBuilder) GetServiceType() corev1.ServiceType {
if b.serviceType == nil {
return corev1.ServiceTypeClusterIP
}
return *b.serviceType
}

func (b *BaseServiceBuilder) Build(_ context.Context) (ctrlclient.Object, error) {
obj := b.GetObject()
return obj, nil
}

type ServiceBuilderOption struct {
Option

// If not set, ClusterIP will be used
ListenerClass constants.ListenerClass
Headless bool
MatchingLabels map[string]string
}

type ServiceBuilderOptions func(*ServiceBuilderOption)

func NewServiceBuilder(
client *client.Client,
name string,
labels map[string]string,
annotations map[string]string,
ports []corev1.ContainerPort,
serviceType *corev1.ServiceType,
headless bool,
options ...ServiceBuilderOptions,
) *BaseServiceBuilder {

servicePorts := ContainerPorts2ServicePorts(ports)
opt := &ServiceBuilderOption{}

for _, o := range options {
o(opt)
}

return &BaseServiceBuilder{
BaseResourceBuilder: BaseResourceBuilder{
Client: client,
Name: name,
labels: labels,
Client: client,
Name: name,
labels: opt.Labels,
annotations: opt.Annotations,
},
ports: servicePorts,
ports: ContainerPorts2ServicePorts(ports),

serviceType: serviceType,
headless: headless,
headless: opt.Headless,
matchingLabels: opt.MatchingLabels,
listenerClass: opt.ListenerClass,
}
}
72 changes: 72 additions & 0 deletions pkg/builder/service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package builder_test

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

"github.com/zncdatadev/operator-go/pkg/builder"
"github.com/zncdatadev/operator-go/pkg/client"
"github.com/zncdatadev/operator-go/pkg/constants"
)

func TestNewServiceBuilder(t *testing.T) {
fakeOwner := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "fake-owner",
Namespace: "default",
UID: types.UID("fake-uid"),
},
}

mockClient := client.NewClient(k8sClient, fakeOwner)
name := "test-service"
ports := []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 80,
Protocol: corev1.ProtocolTCP,
},
}

t.Run("default options", func(t *testing.T) {
builder := builder.NewServiceBuilder(mockClient, name, ports)
service := builder.GetObject()

assert.Equal(t, name, service.Name)
assert.Equal(t, corev1.ServiceTypeClusterIP, service.Spec.Type)
assert.Equal(t, 1, len(service.Spec.Ports))
assert.Equal(t, "http", service.Spec.Ports[0].Name)
assert.Equal(t, int32(80), service.Spec.Ports[0].Port)
assert.Equal(t, corev1.ProtocolTCP, service.Spec.Ports[0].Protocol)

obj, err := builder.Build(context.Background())
assert.Nil(t, err)
assert.NotNil(t, obj)
})

t.Run("with options", func(t *testing.T) {
options := []builder.ServiceBuilderOptions{
func(opt *builder.ServiceBuilderOption) {
opt.ListenerClass = constants.ExternalStable
opt.Headless = true
opt.MatchingLabels = map[string]string{"app": "test"}
},
}
builder := builder.NewServiceBuilder(mockClient, name, ports, options...)
service := builder.GetObject()

assert.Equal(t, name, service.Name)
assert.Equal(t, corev1.ServiceTypeLoadBalancer, service.Spec.Type)
assert.Equal(t, corev1.ClusterIPNone, service.Spec.ClusterIP)
assert.Equal(t, map[string]string{"app": "test"}, service.Spec.Selector)

obj, err := builder.Build(context.Background())
assert.Nil(t, err)
assert.NotNil(t, obj)
})
}
13 changes: 13 additions & 0 deletions pkg/constants/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,16 @@ const (
// If not set, the listener name will be the same as the pod name.
AnnotationListenerName string = listenerAPIGroupPrefix + "listenerName"
)

type ListenerClass string

const (
// ClusterInternal is the default listener class.
// cluster-internal --> k8s service with ClusterIP
ClusterInternal ListenerClass = "cluster-internal"
// external-unstable --> k8s service with NodePort
ExternalUnstable ListenerClass = "external-unstable"
// ExternalStable requires a k8s LoadBalancer
// external-stable --> k8s service with LoadBalancer
ExternalStable ListenerClass = "external-stable"
)
18 changes: 10 additions & 8 deletions pkg/reconciler/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import (

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
commonsv1alpha1 "github.com/zncdatadev/operator-go/pkg/apis/commons/v1alpha1"
"github.com/zncdatadev/operator-go/pkg/client"
"github.com/zncdatadev/operator-go/pkg/constants"
"github.com/zncdatadev/operator-go/pkg/reconciler"
appv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

commonsv1alpha1 "github.com/zncdatadev/operator-go/pkg/apis/commons/v1alpha1"
"github.com/zncdatadev/operator-go/pkg/builder"
"github.com/zncdatadev/operator-go/pkg/client"
"github.com/zncdatadev/operator-go/pkg/constants"
"github.com/zncdatadev/operator-go/pkg/reconciler"
)

// ClusterReconciler reconciles a TrinoCluster object
Expand Down Expand Up @@ -52,16 +54,16 @@ func (r *ClusterReconciler) RegisterResources(ctx context.Context) error {
serviceReconciler := reconciler.NewServiceReconciler(
r.GetClient(),
r.GetName(),
r.ClusterInfo.GetLabels(),
r.ClusterInfo.GetAnnotations(),
[]corev1.ContainerPort{
{
Name: "http",
ContainerPort: 3000,
},
},
nil,
false,
func(sbo *builder.ServiceBuilderOption) {
sbo.Annotations = r.ClusterInfo.GetAnnotations()
sbo.Labels = r.ClusterInfo.GetLabels()
},
)
// Register resources
r.AddResource(serviceReconciler)
Expand Down
8 changes: 4 additions & 4 deletions pkg/reconciler/role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,16 @@ func (r *RoleReconciler) getServiceReconciler(info reconciler.RoleGroupInfo) rec
return reconciler.NewServiceReconciler(
r.GetClient(),
info.GetFullName(),
info.GetLabels(),
info.GetAnnotations(),
[]corev1.ContainerPort{
{
Name: "http",
ContainerPort: 3000,
},
},
nil,
false,
func(sbo *builder.ServiceBuilderOption) {
sbo.Annotations = info.GetAnnotations()
sbo.Labels = info.GetLabels()
},
)
}

Expand Down
13 changes: 4 additions & 9 deletions pkg/reconciler/service.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package reconciler

import (
corev1 "k8s.io/api/core/v1"

"github.com/zncdatadev/operator-go/pkg/builder"
"github.com/zncdatadev/operator-go/pkg/client"
corev1 "k8s.io/api/core/v1"
)

var _ ResourceReconciler[builder.ServiceBuilder] = &Service{}
Expand All @@ -15,20 +16,14 @@ type Service struct {
func NewServiceReconciler(
client *client.Client,
name string,
labels map[string]string,
annotations map[string]string,
ports []corev1.ContainerPort,
serviceType *corev1.ServiceType,
headless bool,
options ...builder.ServiceBuilderOptions,
) *Service {
svcBuilder := builder.NewServiceBuilder(
client,
name,
labels,
annotations,
ports,
serviceType,
headless,
options...,
)
return &Service{
GenericResourceReconciler: *NewGenericResourceReconciler[builder.ServiceBuilder](
Expand Down
15 changes: 9 additions & 6 deletions pkg/reconciler/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/zncdatadev/operator-go/pkg/client"
"github.com/zncdatadev/operator-go/pkg/reconciler"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

"github.com/zncdatadev/operator-go/pkg/builder"
"github.com/zncdatadev/operator-go/pkg/client"
"github.com/zncdatadev/operator-go/pkg/reconciler"
)

var _ = Describe("Service reconciler", func() {
Expand Down Expand Up @@ -53,17 +55,18 @@ var _ = Describe("Service reconciler", func() {
serviceReconciler := reconciler.NewServiceReconciler(
resourceClient,
name,
map[string]string{"app.kubernetes.io/name": name},
map[string]string{"app.kubernetes.io/name": name},
[]corev1.ContainerPort{
{
Name: "http",
ContainerPort: 80,
Protocol: corev1.ProtocolTCP,
},
},
nil,
false,
func(sbo *builder.ServiceBuilderOption) {
sbo.Annotations = map[string]string{"app.kubernetes.io/name": name}
sbo.Labels = map[string]string{"app.kubernetes.io/name": name}

},
)
Expect(serviceReconciler).ShouldNot(BeNil())

Expand Down

0 comments on commit 1b64749

Please sign in to comment.