mirror of https://github.com/docker/cli.git
Fix docker stack services command on Port output when kubernetes service is a LoadBalancer or a NodePort
* added tests on Kubernetes service conversion to swarm service Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
This commit is contained in:
parent
2731c71c99
commit
b816bde6cc
|
@ -116,25 +116,31 @@ func (t tasksBySlot) Less(i, j int) bool {
|
|||
return t[j].Meta.CreatedAt.Before(t[i].CreatedAt)
|
||||
}
|
||||
|
||||
const (
|
||||
publishedServiceSuffix = "-published"
|
||||
publishedOnRandomPortSuffix = "-random-ports"
|
||||
)
|
||||
|
||||
// Replicas conversion
|
||||
func replicasToServices(replicas *appsv1beta2.ReplicaSetList, services *apiv1.ServiceList) ([]swarm.Service, map[string]formatter.ServiceListInfo, error) {
|
||||
result := make([]swarm.Service, len(replicas.Items))
|
||||
infos := make(map[string]formatter.ServiceListInfo, len(replicas.Items))
|
||||
for i, r := range replicas.Items {
|
||||
service, ok := findService(services, r.Labels[labels.ForServiceName])
|
||||
serviceName := r.Labels[labels.ForServiceName]
|
||||
serviceHeadless, ok := findService(services, serviceName)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("could not find service '%s'", r.Labels[labels.ForServiceName])
|
||||
return nil, nil, fmt.Errorf("could not find service '%s'", serviceName)
|
||||
}
|
||||
stack, ok := service.Labels[labels.ForStackName]
|
||||
stack, ok := serviceHeadless.Labels[labels.ForStackName]
|
||||
if ok {
|
||||
stack += "_"
|
||||
}
|
||||
uid := string(service.UID)
|
||||
uid := string(serviceHeadless.UID)
|
||||
s := swarm.Service{
|
||||
ID: uid,
|
||||
Spec: swarm.ServiceSpec{
|
||||
Annotations: swarm.Annotations{
|
||||
Name: stack + service.Name,
|
||||
Name: stack + serviceHeadless.Name,
|
||||
},
|
||||
TaskTemplate: swarm.TaskSpec{
|
||||
ContainerSpec: &swarm.ContainerSpec{
|
||||
|
@ -143,17 +149,11 @@ func replicasToServices(replicas *appsv1beta2.ReplicaSetList, services *apiv1.Se
|
|||
},
|
||||
},
|
||||
}
|
||||
if service.Spec.Type == apiv1.ServiceTypeLoadBalancer {
|
||||
configs := make([]swarm.PortConfig, len(service.Spec.Ports))
|
||||
for i, p := range service.Spec.Ports {
|
||||
configs[i] = swarm.PortConfig{
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
PublishedPort: uint32(p.Port),
|
||||
TargetPort: uint32(p.TargetPort.IntValue()),
|
||||
Protocol: toSwarmProtocol(p.Protocol),
|
||||
}
|
||||
}
|
||||
s.Endpoint = swarm.Endpoint{Ports: configs}
|
||||
if serviceNodePort, ok := findService(services, serviceName+publishedOnRandomPortSuffix); ok && serviceNodePort.Spec.Type == apiv1.ServiceTypeNodePort {
|
||||
s.Endpoint = serviceEndpoint(serviceNodePort, swarm.PortConfigPublishModeHost)
|
||||
}
|
||||
if serviceLoadBalancer, ok := findService(services, serviceName+publishedServiceSuffix); ok && serviceLoadBalancer.Spec.Type == apiv1.ServiceTypeLoadBalancer {
|
||||
s.Endpoint = serviceEndpoint(serviceLoadBalancer, swarm.PortConfigPublishModeIngress)
|
||||
}
|
||||
result[i] = s
|
||||
infos[uid] = formatter.ServiceListInfo{
|
||||
|
@ -172,3 +172,16 @@ func findService(services *apiv1.ServiceList, name string) (apiv1.Service, bool)
|
|||
}
|
||||
return apiv1.Service{}, false
|
||||
}
|
||||
|
||||
func serviceEndpoint(service apiv1.Service, publishMode swarm.PortConfigPublishMode) swarm.Endpoint {
|
||||
configs := make([]swarm.PortConfig, len(service.Spec.Ports))
|
||||
for i, p := range service.Spec.Ports {
|
||||
configs[i] = swarm.PortConfig{
|
||||
PublishMode: publishMode,
|
||||
PublishedPort: uint32(p.Port),
|
||||
TargetPort: uint32(p.TargetPort.IntValue()),
|
||||
Protocol: toSwarmProtocol(p.Protocol),
|
||||
}
|
||||
}
|
||||
return swarm.Endpoint{Ports: configs}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/kubernetes/labels"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/gotestyourself/gotestyourself/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 := replicasToServices(&replicas, &services)
|
||||
assert.ErrorContains(t, err, "could not find service")
|
||||
}
|
||||
|
||||
func TestKubernetesServiceToSwarmServiceConversion(t *testing.T) {
|
||||
testCases := []struct {
|
||||
replicas *appsv1beta2.ReplicaSetList
|
||||
services *apiv1.ServiceList
|
||||
expectedServices []swarm.Service
|
||||
expectedListInfo map[string]formatter.ServiceListInfo
|
||||
}{
|
||||
// Match replicas with headless stack services
|
||||
{
|
||||
&appsv1beta2.ReplicaSetList{
|
||||
Items: []appsv1beta2.ReplicaSet{
|
||||
makeReplicaSet("service1", 2, 5),
|
||||
makeReplicaSet("service2", 3, 3),
|
||||
},
|
||||
},
|
||||
&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),
|
||||
},
|
||||
},
|
||||
[]swarm.Service{
|
||||
makeSwarmService("stack_service1", "uid1", nil),
|
||||
makeSwarmService("stack_service2", "uid2", nil),
|
||||
},
|
||||
map[string]formatter.ServiceListInfo{
|
||||
"uid1": {"replicated", "2/5"},
|
||||
"uid2": {"replicated", "3/3"},
|
||||
},
|
||||
},
|
||||
// Headless service and LoadBalancer Service are tied to the same Swarm service
|
||||
{
|
||||
&appsv1beta2.ReplicaSetList{
|
||||
Items: []appsv1beta2.ReplicaSet{
|
||||
makeReplicaSet("service", 1, 1),
|
||||
},
|
||||
},
|
||||
&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,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
[]swarm.Service{
|
||||
makeSwarmService("stack_service", "uid1", []swarm.PortConfig{
|
||||
{
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
PublishedPort: 80,
|
||||
TargetPort: 80,
|
||||
Protocol: swarm.PortConfigProtocolTCP,
|
||||
},
|
||||
}),
|
||||
},
|
||||
map[string]formatter.ServiceListInfo{
|
||||
"uid1": {"replicated", "1/1"},
|
||||
},
|
||||
},
|
||||
// Headless service and NodePort Service are tied to the same Swarm service
|
||||
|
||||
{
|
||||
&appsv1beta2.ReplicaSetList{
|
||||
Items: []appsv1beta2.ReplicaSet{
|
||||
makeReplicaSet("service", 1, 1),
|
||||
},
|
||||
},
|
||||
&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,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
[]swarm.Service{
|
||||
makeSwarmService("stack_service", "uid1", []swarm.PortConfig{
|
||||
{
|
||||
PublishMode: swarm.PortConfigPublishModeHost,
|
||||
PublishedPort: 35666,
|
||||
TargetPort: 80,
|
||||
Protocol: swarm.PortConfigProtocolTCP,
|
||||
},
|
||||
}),
|
||||
},
|
||||
map[string]formatter.ServiceListInfo{
|
||||
"uid1": {"replicated", "1/1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
swarmServices, listInfo, err := replicasToServices(tc.replicas, tc.services)
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, tc.expectedServices, swarmServices)
|
||||
assert.DeepEqual(t, tc.expectedListInfo, listInfo)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeSwarmService(service, id string, ports []swarm.PortConfig) swarm.Service {
|
||||
return swarm.Service{
|
||||
ID: id,
|
||||
Spec: swarm.ServiceSpec{
|
||||
Annotations: swarm.Annotations{
|
||||
Name: service,
|
||||
},
|
||||
TaskTemplate: swarm.TaskSpec{
|
||||
ContainerSpec: &swarm.ContainerSpec{
|
||||
Image: "image",
|
||||
},
|
||||
},
|
||||
},
|
||||
Endpoint: swarm.Endpoint{
|
||||
Ports: ports,
|
||||
},
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue