package kubernetes import ( "testing" . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function "github.com/docker/compose-on-kubernetes/api/labels" "github.com/docker/docker/api/types/swarm" "gotest.tools/v3/assert" appsv1beta2 "k8s.io/api/apps/v1beta2" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apimachineryTypes "k8s.io/apimachinery/pkg/types" apimachineryUtil "k8s.io/apimachinery/pkg/util/intstr" ) func TestReplicasConversionNeedsAService(t *testing.T) { replicas := appsv1beta2.ReplicaSetList{ Items: []appsv1beta2.ReplicaSet{makeReplicaSet("unknown", 0, 0)}, } services := apiv1.ServiceList{} _, err := convertToServices(&replicas, &appsv1beta2.DaemonSetList{}, &services) assert.ErrorContains(t, err, "could not find service") } func TestKubernetesServiceToSwarmServiceConversion(t *testing.T) { testCases := []struct { doc string replicas *appsv1beta2.ReplicaSetList services *apiv1.ServiceList expectedServices []swarm.Service }{ { doc: "Match replicas with headless stack services", replicas: &appsv1beta2.ReplicaSetList{ Items: []appsv1beta2.ReplicaSet{ makeReplicaSet("service1", 2, 5), makeReplicaSet("service2", 3, 3), }, }, services: &apiv1.ServiceList{ Items: []apiv1.Service{ makeKubeService("service1", "stack", "uid1", apiv1.ServiceTypeClusterIP, nil), makeKubeService("service2", "stack", "uid2", apiv1.ServiceTypeClusterIP, nil), makeKubeService("service3", "other-stack", "uid2", apiv1.ServiceTypeClusterIP, nil), }, }, expectedServices: []swarm.Service{ makeSwarmService(t, "stack_service1", "uid1", ReplicatedService(5), ServiceStatus(5, 2)), makeSwarmService(t, "stack_service2", "uid2", ReplicatedService(3), ServiceStatus(3, 3)), }, }, { doc: "Headless service and LoadBalancer Service are tied to the same Swarm service", replicas: &appsv1beta2.ReplicaSetList{ Items: []appsv1beta2.ReplicaSet{ makeReplicaSet("service", 1, 1), }, }, services: &apiv1.ServiceList{ Items: []apiv1.Service{ makeKubeService("service", "stack", "uid1", apiv1.ServiceTypeClusterIP, nil), makeKubeService("service-published", "stack", "uid2", apiv1.ServiceTypeLoadBalancer, []apiv1.ServicePort{ { Port: 80, TargetPort: apimachineryUtil.FromInt(80), Protocol: apiv1.ProtocolTCP, }, }), }, }, expectedServices: []swarm.Service{ makeSwarmService(t, "stack_service", "uid1", ReplicatedService(1), ServiceStatus(1, 1), withPort(swarm.PortConfig{ PublishMode: swarm.PortConfigPublishModeIngress, PublishedPort: 80, TargetPort: 80, Protocol: swarm.PortConfigProtocolTCP, }), ), }, }, { doc: "Headless service and NodePort Service are tied to the same Swarm service", replicas: &appsv1beta2.ReplicaSetList{ Items: []appsv1beta2.ReplicaSet{ makeReplicaSet("service", 1, 1), }, }, services: &apiv1.ServiceList{ Items: []apiv1.Service{ makeKubeService("service", "stack", "uid1", apiv1.ServiceTypeClusterIP, nil), makeKubeService("service-random-ports", "stack", "uid2", apiv1.ServiceTypeNodePort, []apiv1.ServicePort{ { Port: 35666, TargetPort: apimachineryUtil.FromInt(80), Protocol: apiv1.ProtocolTCP, }, }), }, }, expectedServices: []swarm.Service{ makeSwarmService(t, "stack_service", "uid1", ReplicatedService(1), ServiceStatus(1, 1), withPort(swarm.PortConfig{ PublishMode: swarm.PortConfigPublishModeHost, PublishedPort: 35666, TargetPort: 80, Protocol: swarm.PortConfigProtocolTCP, }), ), }, }, } for _, tc := range testCases { tc := tc t.Run(tc.doc, func(t *testing.T) { swarmServices, err := convertToServices(tc.replicas, &appsv1beta2.DaemonSetList{}, tc.services) assert.NilError(t, err) assert.DeepEqual(t, tc.expectedServices, swarmServices) }) } } func makeReplicaSet(service string, available, replicas int32) appsv1beta2.ReplicaSet { return appsv1beta2.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ labels.ForServiceName: service, }, }, Spec: appsv1beta2.ReplicaSetSpec{ Template: apiv1.PodTemplateSpec{ Spec: apiv1.PodSpec{ Containers: []apiv1.Container{ { Image: "image", }, }, }, }, }, Status: appsv1beta2.ReplicaSetStatus{ AvailableReplicas: available, Replicas: replicas, }, } } func makeKubeService(service, stack, uid string, serviceType apiv1.ServiceType, ports []apiv1.ServicePort) apiv1.Service { return apiv1.Service{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ labels.ForStackName: stack, }, Name: service, UID: apimachineryTypes.UID(uid), }, Spec: apiv1.ServiceSpec{ Type: serviceType, Ports: ports, }, } } // TODO convertToServices currently doesn't set swarm.EndpointSpec.Ports func withPort(port swarm.PortConfig) func(*swarm.Service) { return func(service *swarm.Service) { if service.Endpoint.Ports == nil { service.Endpoint.Ports = make([]swarm.PortConfig, 0) } service.Endpoint.Ports = append(service.Endpoint.Ports, port) } } func makeSwarmService(t *testing.T, name, id string, opts ...func(*swarm.Service)) swarm.Service { t.Helper() options := []func(*swarm.Service){ServiceID(id), ServiceName(name), ServiceImage("image")} options = append(options, opts...) return *Service(options...) }