Skip to content

Commit

Permalink
feat: support ClusterIP service (#201)
Browse files Browse the repository at this point in the history
* generate clusterIP

* generate manifest

* add test

* fix linting
  • Loading branch information
winston0410 authored Jul 17, 2024
1 parent 5e67a62 commit a82d2bb
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 5 deletions.
4 changes: 2 additions & 2 deletions config/crd/bases/vpn.wireguard-operator.io_wireguards.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ spec:
type: object
serviceType:
description: A field that specifies the type of Kubernetes service
that should be used for the Wireguard VPN. This could be NodePort
or LoadBalancer, depending on the needs of the deployment.
that should be used for the Wireguard VPN. This could be ClusterIP,
NodePort or LoadBalancer, depending on the needs of the deployment.
type: string
useWgUserspaceImplementation:
description: A boolean field that specifies whether to use the userspace
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/v1alpha1/wireguard_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type WireguardSpec struct {
Address string `json:"address,omitempty"`
// A string field that specifies the DNS server(s) to be used by the peers.
Dns string `json:"dns,omitempty"`
// A field that specifies the type of Kubernetes service that should be used for the Wireguard VPN. This could be NodePort or LoadBalancer, depending on the needs of the deployment.
// A field that specifies the type of Kubernetes service that should be used for the Wireguard VPN. This could be ClusterIP, NodePort or LoadBalancer, depending on the needs of the deployment.
ServiceType corev1.ServiceType `json:"serviceType,omitempty"`
// A field that specifies the value to use for a nodePort ServiceType
NodePort int32 `json:"port,omitempty"`
Expand Down
11 changes: 11 additions & 0 deletions pkg/controllers/wireguard_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,17 @@ func (r *WireguardReconciler) Reconcile(ctx context.Context, req ctrl.Request) (

}

if serviceType == corev1.ServiceTypeClusterIP {
if len(svcFound.Spec.Ports) == 0 {
err = r.updateStatus(ctx, req, wireguard, v1alpha1.WgStatusReport{Status: v1alpha1.Pending, Message: "Waiting for service with type ClusterIP to be ready"})
if err != nil {
return ctrl.Result{}, err
}

return ctrl.Result{}, nil
}
}

if wireguard.Status.Address != address || port != wireguard.Status.Port || dnsAddress != wireguard.Status.Dns {
updateWireguard := wireguard.DeepCopy()
updateWireguard.Status.Address = address
Expand Down
159 changes: 157 additions & 2 deletions pkg/controllers/wireguard_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import (
"time"

"github.com/jodevsa/wireguard-operator/pkg/api/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/client"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// test helpers
Expand Down Expand Up @@ -65,6 +65,21 @@ func reconcileServiceWithTypeLoadBalancer(svcKey client.ObjectKey, hostname stri
return k8sClient.Status().Update(context.Background(), svc)
}

func reconcileServiceWithClusterIP(svcKey client.ObjectKey, port int32) error {
svc := &corev1.Service{}
Expect(k8sClient.Get(context.Background(), svcKey, svc)).Should(Succeed())

if svc.Spec.Type != corev1.ServiceTypeClusterIP {
return fmt.Errorf("ReconcileServiceWithClusterIP only reconsiles ClusterIP services")
}

svc.Spec.Ports = []corev1.ServicePort{{
Port: port,
TargetPort: intstr.FromInt32(port),
}}
return k8sClient.Status().Update(context.Background(), svc)
}

var _ = Describe("wireguard controller", func() {

// Define utility constants for object names and testing timeouts/durations and intervals.
Expand Down Expand Up @@ -515,6 +530,146 @@ Endpoint = %s:%s"`, peerKey.Name, peer.Spec.AllowedIPs, peer.Spec.Address, dnsSe

})

It("Should create a WG with ServiceType ClusterIP and WG peer successfully", func() {
expectedAddress := "test-address"

wgKey := types.NamespacedName{
Name: wgName,
Namespace: wgNamespace,
}
created := &v1alpha1.Wireguard{
ObjectMeta: metav1.ObjectMeta{
Name: wgKey.Name,
Namespace: wgKey.Namespace,
},
Spec: v1alpha1.WireguardSpec{
ServiceType: corev1.ServiceTypeClusterIP,
Address: expectedAddress,
},
}
expectedLabels := map[string]string{"app": "wireguard", "instance": wgKey.Name}

Expect(k8sClient.Create(context.Background(), created)).Should(Succeed())

// service created
serviceName := wgKey.Name + "-svc"
serviceKey := types.NamespacedName{
Namespace: wgKey.Namespace,
Name: serviceName,
}

// match labels
Eventually(func() map[string]string {
svc := &corev1.Service{}
//nolint:errcheck
k8sClient.Get(context.Background(), serviceKey, svc)
return svc.Spec.Selector
}, Timeout, Interval).Should(BeEquivalentTo(expectedLabels))

// match service type
Eventually(func() corev1.ServiceType {
svc := &corev1.Service{}
//nolint:errcheck
k8sClient.Get(context.Background(), serviceKey, svc)
return svc.Spec.Type
}, Timeout, Interval).Should(Equal(corev1.ServiceTypeClusterIP))

Eventually(func() v1alpha1.WireguardStatus {
wg := &v1alpha1.Wireguard{}
//nolint:errcheck
k8sClient.Get(context.Background(), wgKey, wg)
return wg.Status
}, Timeout, Interval).Should(Equal(v1alpha1.WireguardStatus{
Address: "",
Status: "pending",
Message: "Waiting for service to be ready",
}))

Expect(reconcileServiceWithClusterIP(serviceKey, 51820)).Should(Succeed())

// check that wireguard resource got the right status after the service is ready
wg := &v1alpha1.Wireguard{}
Eventually(func() v1alpha1.WireguardStatus {
Expect(k8sClient.Get(context.Background(), wgKey, wg)).Should(Succeed())
return wg.Status
}, Timeout, Interval).Should(Equal(v1alpha1.WireguardStatus{
Address: expectedAddress,
Port: "51820",
Status: "ready",
Dns: dnsServiceIp,
Message: "VPN is active!",
}))

Eventually(func() string {
deploymentKey := types.NamespacedName{
Name: wgName + "-dep",
Namespace: wgNamespace,
}
deployment := &appsv1.Deployment{}
Expect(k8sClient.Get(context.Background(), deploymentKey, deployment)).Should(Succeed())
Expect(len(deployment.Spec.Template.Spec.Containers)).Should(Equal(2))
Expect(deployment.Spec.Template.Spec.Containers[0].Image).Should(Equal(deployment.Spec.Template.Spec.Containers[1].Image))
return deployment.Spec.Template.Spec.Containers[0].Image
}, Timeout, Interval).Should(Equal(wgTestImage))

// create peer
peerKey := types.NamespacedName{
Name: wgKey.Name + "peer",
Namespace: wgKey.Namespace,
}
peer := &v1alpha1.WireguardPeer{
ObjectMeta: metav1.ObjectMeta{
Name: peerKey.Name,
Namespace: peerKey.Namespace,
},
Spec: v1alpha1.WireguardPeerSpec{
WireguardRef: wgKey.Name,
},
}
Expect(k8sClient.Create(context.Background(), peer)).Should(Succeed())

//get peer secret
wgSecretKeyName := types.NamespacedName{
Name: wgKey.Name,
Namespace: wgKey.Namespace,
}
wgSecret := &corev1.Secret{}
Eventually(func() error {
return k8sClient.Get(context.Background(), wgSecretKeyName, wgSecret)
}, Timeout, Interval).Should(Succeed())
wgPublicKey := string(wgSecret.Data["publicKey"])

Eventually(func() string {
Expect(k8sClient.Get(context.Background(), peerKey, peer)).Should(Succeed())
print(peer.Status.Message)
return peer.Spec.Address
}, Timeout, Interval).Should(Equal("10.8.0.2"))

Eventually(func() v1alpha1.WireguardPeerStatus {
Expect(k8sClient.Get(context.Background(), peerKey, peer)).Should(Succeed())
return peer.Status
}, Timeout, Interval).Should(Equal(v1alpha1.WireguardPeerStatus{
Config: fmt.Sprintf(`
echo "
[Interface]
PrivateKey = $(kubectl get secret %s-peer --template={{.data.privateKey}} -n default | base64 -d)
Address = %s
DNS = %s, %s.svc.cluster.local
[Peer]
PublicKey = %s
AllowedIPs = 0.0.0.0/0
Endpoint = %s:%s"`, peerKey.Name, peer.Spec.Address, dnsServiceIp, peer.Namespace, wgPublicKey, expectedAddress, wg.Status.Port),
Status: "ready",
Message: "Peer configured",
}))

Eventually(func() error {
return k8sClient.Get(context.Background(), wgSecretKeyName, wgSecret)
}, Timeout, Interval).Should(Succeed())

})

for _, useWgUserspace := range []bool{true, false} {
testTextPrefix := "uses"
if !useWgUserspace {
Expand Down

0 comments on commit a82d2bb

Please sign in to comment.