fix: kubectl expose fails for apps with same-port, different-protocol
Fixed: https://github.com/kubernetes/kubernetes/issues/114402 Signed-off-by: aimuz <mr.imuz@gmail.com> Kubernetes-commit: e5116a39c96a78511fc8c0da4730d3262b2c121c
This commit is contained in:
parent
0a61782351
commit
2ec2847518
|
@ -38,7 +38,6 @@ import (
|
||||||
"k8s.io/cli-runtime/pkg/printers"
|
"k8s.io/cli-runtime/pkg/printers"
|
||||||
"k8s.io/cli-runtime/pkg/resource"
|
"k8s.io/cli-runtime/pkg/resource"
|
||||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||||
"k8s.io/kubectl/pkg/generate"
|
|
||||||
"k8s.io/kubectl/pkg/polymorphichelpers"
|
"k8s.io/kubectl/pkg/polymorphichelpers"
|
||||||
"k8s.io/kubectl/pkg/scheme"
|
"k8s.io/kubectl/pkg/scheme"
|
||||||
"k8s.io/kubectl/pkg/util"
|
"k8s.io/kubectl/pkg/util"
|
||||||
|
@ -123,7 +122,7 @@ type ExposeServiceOptions struct {
|
||||||
CanBeExposed polymorphichelpers.CanBeExposedFunc
|
CanBeExposed polymorphichelpers.CanBeExposedFunc
|
||||||
MapBasedSelectorForObject func(runtime.Object) (string, error)
|
MapBasedSelectorForObject func(runtime.Object) (string, error)
|
||||||
PortsForObject polymorphichelpers.PortsForObjectFunc
|
PortsForObject polymorphichelpers.PortsForObjectFunc
|
||||||
ProtocolsForObject func(runtime.Object) (map[string]string, error)
|
ProtocolsForObject polymorphichelpers.MultiProtocolsWithForObjectFunc
|
||||||
|
|
||||||
Namespace string
|
Namespace string
|
||||||
Mapper meta.RESTMapper
|
Mapper meta.RESTMapper
|
||||||
|
@ -276,7 +275,7 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory) error {
|
||||||
o.ClientForMapping = f.ClientForMapping
|
o.ClientForMapping = f.ClientForMapping
|
||||||
o.CanBeExposed = polymorphichelpers.CanBeExposedFn
|
o.CanBeExposed = polymorphichelpers.CanBeExposedFn
|
||||||
o.MapBasedSelectorForObject = polymorphichelpers.MapBasedSelectorForObjectFn
|
o.MapBasedSelectorForObject = polymorphichelpers.MapBasedSelectorForObjectFn
|
||||||
o.ProtocolsForObject = polymorphichelpers.ProtocolsForObjectFn
|
o.ProtocolsForObject = polymorphichelpers.MultiProtocolsForObjectFn
|
||||||
o.PortsForObject = polymorphichelpers.PortsForObjectFn
|
o.PortsForObject = polymorphichelpers.PortsForObjectFn
|
||||||
|
|
||||||
o.Mapper, err = f.ToRESTMapper()
|
o.Mapper, err = f.ToRESTMapper()
|
||||||
|
@ -361,7 +360,7 @@ func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("couldn't find protocol via introspection: %v", err)
|
return fmt.Errorf("couldn't find protocol via introspection: %v", err)
|
||||||
}
|
}
|
||||||
if protocols := generate.MakeProtocols(protocolsMap); !generate.IsZero(protocols) {
|
if protocols := makeProtocols(protocolsMap); len(protocols) > 0 {
|
||||||
o.Protocols = protocols
|
o.Protocols = protocols
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,13 +374,11 @@ func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) erro
|
||||||
|
|
||||||
// Generate new object
|
// Generate new object
|
||||||
service, err := o.createService()
|
service, err := o.createService()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
overrideService, err := o.NewOverrider(&corev1.Service{}).Apply(service)
|
overrideService, err := o.NewOverrider(&corev1.Service{}).Apply(service)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -455,7 +452,7 @@ func (o *ExposeServiceOptions) createService() (*corev1.Service, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var portProtocolMap map[string]string
|
var portProtocolMap map[string][]string
|
||||||
if o.Protocols != "" {
|
if o.Protocols != "" {
|
||||||
portProtocolMap, err = parseProtocols(o.Protocols)
|
portProtocolMap, err = parseProtocols(o.Protocols)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -499,8 +496,20 @@ func (o *ExposeServiceOptions) createService() (*corev1.Service, error) {
|
||||||
case len(protocol) == 0 && len(portProtocolMap) > 0:
|
case len(protocol) == 0 && len(portProtocolMap) > 0:
|
||||||
// no --protocol and we expose a multiprotocol resource
|
// no --protocol and we expose a multiprotocol resource
|
||||||
protocol = "TCP" // have the default so we can stay sane
|
protocol = "TCP" // have the default so we can stay sane
|
||||||
if exposeProtocol, found := portProtocolMap[stillPortString]; found {
|
if exposeProtocols, found := portProtocolMap[stillPortString]; found {
|
||||||
protocol = exposeProtocol
|
if len(exposeProtocols) == 1 {
|
||||||
|
protocol = exposeProtocols[0]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, exposeProtocol := range exposeProtocols {
|
||||||
|
name := fmt.Sprintf("port-%d-%s", i+1, strings.ToLower(exposeProtocol))
|
||||||
|
ports = append(ports, corev1.ServicePort{
|
||||||
|
Name: name,
|
||||||
|
Port: int32(port),
|
||||||
|
Protocol: corev1.Protocol(exposeProtocol),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ports = append(ports, corev1.ServicePort{
|
ports = append(ports, corev1.ServicePort{
|
||||||
|
@ -590,12 +599,22 @@ func parseLabels(labelSpec string) (map[string]string, error) {
|
||||||
return labels, nil
|
return labels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeProtocols(protocols map[string][]string) string {
|
||||||
|
var out []string
|
||||||
|
for key, value := range protocols {
|
||||||
|
for _, s := range value {
|
||||||
|
out = append(out, fmt.Sprintf("%s/%s", key, s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(out, ",")
|
||||||
|
}
|
||||||
|
|
||||||
// parseProtocols turns a string representation of a protocols set into a map[string]string
|
// parseProtocols turns a string representation of a protocols set into a map[string]string
|
||||||
func parseProtocols(protocols string) (map[string]string, error) {
|
func parseProtocols(protocols string) (map[string][]string, error) {
|
||||||
if len(protocols) == 0 {
|
if len(protocols) == 0 {
|
||||||
return nil, fmt.Errorf("no protocols passed")
|
return nil, fmt.Errorf("no protocols passed")
|
||||||
}
|
}
|
||||||
portProtocolMap := map[string]string{}
|
portProtocolMap := map[string][]string{}
|
||||||
protocolsSlice := strings.Split(protocols, ",")
|
protocolsSlice := strings.Split(protocols, ",")
|
||||||
for ix := range protocolsSlice {
|
for ix := range protocolsSlice {
|
||||||
portProtocol := strings.Split(protocolsSlice[ix], "/")
|
portProtocol := strings.Split(protocolsSlice[ix], "/")
|
||||||
|
@ -608,7 +627,8 @@ func parseProtocols(protocols string) (map[string]string, error) {
|
||||||
if len(portProtocol[1]) == 0 {
|
if len(portProtocol[1]) == 0 {
|
||||||
return nil, fmt.Errorf("unexpected empty protocol")
|
return nil, fmt.Errorf("unexpected empty protocol")
|
||||||
}
|
}
|
||||||
portProtocolMap[portProtocol[0]] = portProtocol[1]
|
port := portProtocol[0]
|
||||||
|
portProtocolMap[port] = append(portProtocolMap[port], portProtocol[1])
|
||||||
}
|
}
|
||||||
return portProtocolMap, nil
|
return portProtocolMap, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1688,6 +1688,40 @@ func TestGenerateService(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Fixed #114402 kubectl expose fails for apps with same-port, different-protocol
|
||||||
|
"test #114402": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
clusterIP: "None",
|
||||||
|
protocols: "53/TCP,53/UDP",
|
||||||
|
port: "53",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "port-1-tcp",
|
||||||
|
Port: 53,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
TargetPort: intstr.FromInt(53),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-1-udp",
|
||||||
|
Port: 53,
|
||||||
|
Protocol: corev1.ProtocolUDP,
|
||||||
|
TargetPort: intstr.FromInt(53),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClusterIP: corev1.ClusterIPNone,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"check selector": {
|
"check selector": {
|
||||||
name: "test",
|
name: "test",
|
||||||
protocol: "SCTP",
|
protocol: "SCTP",
|
||||||
|
|
|
@ -67,11 +67,21 @@ var MapBasedSelectorForObjectFn MapBasedSelectorForObjectFunc = mapBasedSelector
|
||||||
|
|
||||||
// ProtocolsForObjectFunc will call the provided function on the protocols for the object,
|
// ProtocolsForObjectFunc will call the provided function on the protocols for the object,
|
||||||
// return nil-map if no protocols for the object, or return an error.
|
// return nil-map if no protocols for the object, or return an error.
|
||||||
|
// Deprecated: use PortsProtocolsForObjectFunc instead.
|
||||||
|
// When the same port has different protocols, data will be lost
|
||||||
type ProtocolsForObjectFunc func(object runtime.Object) (map[string]string, error)
|
type ProtocolsForObjectFunc func(object runtime.Object) (map[string]string, error)
|
||||||
|
|
||||||
// ProtocolsForObjectFn gives a way to easily override the function for unit testing if needed
|
// ProtocolsForObjectFn gives a way to easily override the function for unit testing if needed
|
||||||
|
// Deprecated: use MultiProtocolsForObjectFn instead.
|
||||||
var ProtocolsForObjectFn ProtocolsForObjectFunc = protocolsForObject
|
var ProtocolsForObjectFn ProtocolsForObjectFunc = protocolsForObject
|
||||||
|
|
||||||
|
// MultiProtocolsWithForObjectFunc will call the provided function on the protocols for the object,
|
||||||
|
// return nil-map if no protocols for the object, or return an error.
|
||||||
|
type MultiProtocolsWithForObjectFunc func(object runtime.Object) (map[string][]string, error)
|
||||||
|
|
||||||
|
// MultiProtocolsForObjectFn gives a way to easily override the function for unit testing if needed
|
||||||
|
var MultiProtocolsForObjectFn MultiProtocolsWithForObjectFunc = multiProtocolsForObject
|
||||||
|
|
||||||
// PortsForObjectFunc returns the ports associated with the provided object
|
// PortsForObjectFunc returns the ports associated with the provided object
|
||||||
type PortsForObjectFunc func(object runtime.Object) ([]string, error)
|
type PortsForObjectFunc func(object runtime.Object) ([]string, error)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package polymorphichelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
appsv1beta1 "k8s.io/api/apps/v1beta1"
|
||||||
|
appsv1beta2 "k8s.io/api/apps/v1beta2"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func multiProtocolsForObject(object runtime.Object) (map[string][]string, error) {
|
||||||
|
// TODO: replace with a swagger schema based approach (identify pod selector via schema introspection)
|
||||||
|
switch t := object.(type) {
|
||||||
|
case *corev1.ReplicationController:
|
||||||
|
return getMultiProtocols(t.Spec.Template.Spec), nil
|
||||||
|
|
||||||
|
case *corev1.Pod:
|
||||||
|
return getMultiProtocols(t.Spec), nil
|
||||||
|
|
||||||
|
case *corev1.Service:
|
||||||
|
return getServiceMultiProtocols(t.Spec), nil
|
||||||
|
|
||||||
|
case *extensionsv1beta1.Deployment:
|
||||||
|
return getMultiProtocols(t.Spec.Template.Spec), nil
|
||||||
|
case *appsv1.Deployment:
|
||||||
|
return getMultiProtocols(t.Spec.Template.Spec), nil
|
||||||
|
case *appsv1beta2.Deployment:
|
||||||
|
return getMultiProtocols(t.Spec.Template.Spec), nil
|
||||||
|
case *appsv1beta1.Deployment:
|
||||||
|
return getMultiProtocols(t.Spec.Template.Spec), nil
|
||||||
|
|
||||||
|
case *extensionsv1beta1.ReplicaSet:
|
||||||
|
return getMultiProtocols(t.Spec.Template.Spec), nil
|
||||||
|
case *appsv1.ReplicaSet:
|
||||||
|
return getMultiProtocols(t.Spec.Template.Spec), nil
|
||||||
|
case *appsv1beta2.ReplicaSet:
|
||||||
|
return getMultiProtocols(t.Spec.Template.Spec), nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("cannot extract protocols from %T", object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMultiProtocols(spec corev1.PodSpec) map[string][]string {
|
||||||
|
result := make(map[string][]string)
|
||||||
|
var protocol corev1.Protocol
|
||||||
|
for _, container := range spec.Containers {
|
||||||
|
for _, port := range container.Ports {
|
||||||
|
// Empty protocol must be defaulted (TCP)
|
||||||
|
protocol = corev1.ProtocolTCP
|
||||||
|
if len(port.Protocol) > 0 {
|
||||||
|
protocol = port.Protocol
|
||||||
|
}
|
||||||
|
p := strconv.Itoa(int(port.ContainerPort))
|
||||||
|
result[p] = append(result[p], string(protocol))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracts the protocols exposed by a service from the given service spec.
|
||||||
|
func getServiceMultiProtocols(spec corev1.ServiceSpec) map[string][]string {
|
||||||
|
result := make(map[string][]string)
|
||||||
|
var protocol corev1.Protocol
|
||||||
|
for _, servicePort := range spec.Ports {
|
||||||
|
// Empty protocol must be defaulted (TCP)
|
||||||
|
protocol = corev1.ProtocolTCP
|
||||||
|
if len(servicePort.Protocol) > 0 {
|
||||||
|
protocol = servicePort.Protocol
|
||||||
|
}
|
||||||
|
p := strconv.Itoa(int(servicePort.Port))
|
||||||
|
result[p] = append(result[p], string(protocol))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package polymorphichelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMultiProtocolsForObject(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
object runtime.Object
|
||||||
|
expectErr bool
|
||||||
|
expected map[string][]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "pod with TCP protocol",
|
||||||
|
object: &corev1.Pod{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Ports: []corev1.ContainerPort{
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
Protocol: "TCP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string][]string{"101": {"TCP"}},
|
||||||
|
},
|
||||||
|
// No protocol--should default to TCP.
|
||||||
|
{
|
||||||
|
name: "pod with no protocol",
|
||||||
|
object: &corev1.Pod{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Ports: []corev1.ContainerPort{
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string][]string{"101": {"TCP"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod with same-port,different-protocol",
|
||||||
|
object: &corev1.Pod{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Ports: []corev1.ContainerPort{
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
Protocol: "TCP",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
Protocol: "UDP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string][]string{"101": {"TCP", "UDP"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service with TCP protocol",
|
||||||
|
object: &corev1.Service{
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 101,
|
||||||
|
Protocol: "TCP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string][]string{"101": {"TCP"}},
|
||||||
|
},
|
||||||
|
// No protocol for service port--default to TCP
|
||||||
|
{
|
||||||
|
name: "service with no protocol",
|
||||||
|
object: &corev1.Service{
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 101,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string][]string{"101": {"TCP"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replication with TCP protocol",
|
||||||
|
object: &corev1.ReplicationController{
|
||||||
|
Spec: corev1.ReplicationControllerSpec{
|
||||||
|
Template: &corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Ports: []corev1.ContainerPort{
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
Protocol: "TCP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string][]string{"101": {"TCP"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deployment with TCP protocol",
|
||||||
|
object: &extensionsv1beta1.Deployment{
|
||||||
|
Spec: extensionsv1beta1.DeploymentSpec{
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Ports: []corev1.ContainerPort{
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
Protocol: "TCP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string][]string{"101": {"TCP"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replicaset with TCP protocol",
|
||||||
|
object: &extensionsv1beta1.ReplicaSet{
|
||||||
|
Spec: extensionsv1beta1.ReplicaSetSpec{
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Ports: []corev1.ContainerPort{
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
Protocol: "TCP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string][]string{"101": {"TCP"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported object",
|
||||||
|
object: &corev1.Node{},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
actual, err := multiProtocolsForObject(test.object)
|
||||||
|
if test.expectErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Error("unexpected non-error")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !test.expectErr && err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual, test.expected) {
|
||||||
|
t.Errorf("expected ports %v, but got %v", test.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,19 +60,31 @@ func portsForObject(object runtime.Object) ([]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPorts(spec corev1.PodSpec) []string {
|
func getPorts(spec corev1.PodSpec) []string {
|
||||||
result := []string{}
|
var result []string
|
||||||
|
exists := map[string]struct{}{}
|
||||||
for _, container := range spec.Containers {
|
for _, container := range spec.Containers {
|
||||||
for _, port := range container.Ports {
|
for _, port := range container.Ports {
|
||||||
result = append(result, strconv.Itoa(int(port.ContainerPort)))
|
// remove duplicate ports
|
||||||
|
key := strconv.Itoa(int(port.ContainerPort))
|
||||||
|
if _, ok := exists[key]; !ok {
|
||||||
|
exists[key] = struct{}{}
|
||||||
|
result = append(result, key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServicePorts(spec corev1.ServiceSpec) []string {
|
func getServicePorts(spec corev1.ServiceSpec) []string {
|
||||||
result := []string{}
|
var result []string
|
||||||
|
exists := map[string]struct{}{}
|
||||||
for _, servicePort := range spec.Ports {
|
for _, servicePort := range spec.Ports {
|
||||||
result = append(result, strconv.Itoa(int(servicePort.Port)))
|
// remove duplicate ports
|
||||||
|
key := strconv.Itoa(int(servicePort.Port))
|
||||||
|
if _, ok := exists[key]; !ok {
|
||||||
|
exists[key] = struct{}{}
|
||||||
|
result = append(result, key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ func TestPortsForObject(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
object runtime.Object
|
object runtime.Object
|
||||||
expectErr bool
|
expectErr bool
|
||||||
|
expected []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
object: &corev1.Pod{
|
object: &corev1.Pod{
|
||||||
|
@ -45,6 +46,74 @@ func TestPortsForObject(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expected: []string{"101"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: &corev1.Pod{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Ports: []corev1.ContainerPort{
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ContainerPort: 102,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []string{"101", "102"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: &corev1.Pod{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Ports: []corev1.ContainerPort{
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
Protocol: corev1.ProtocolUDP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ContainerPort: 102,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []string{"101", "102"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: &corev1.Pod{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Ports: []corev1.ContainerPort{
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ContainerPort: 102,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ContainerPort: 101,
|
||||||
|
Protocol: corev1.ProtocolUDP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []string{"101", "102"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
object: &corev1.Service{
|
object: &corev1.Service{
|
||||||
|
@ -56,6 +125,7 @@ func TestPortsForObject(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expected: []string{"101"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
object: &corev1.ReplicationController{
|
object: &corev1.ReplicationController{
|
||||||
|
@ -75,6 +145,7 @@ func TestPortsForObject(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expected: []string{"101"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
object: &extensionsv1beta1.Deployment{
|
object: &extensionsv1beta1.Deployment{
|
||||||
|
@ -94,6 +165,7 @@ func TestPortsForObject(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expected: []string{"101"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
object: &extensionsv1beta1.ReplicaSet{
|
object: &extensionsv1beta1.ReplicaSet{
|
||||||
|
@ -113,13 +185,13 @@ func TestPortsForObject(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expected: []string{"101"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
object: &corev1.Node{},
|
object: &corev1.Node{},
|
||||||
expectErr: true,
|
expectErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedPorts := []string{"101"}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
actual, err := portsForObject(test.object)
|
actual, err := portsForObject(test.object)
|
||||||
|
@ -133,8 +205,8 @@ func TestPortsForObject(t *testing.T) {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(actual, expectedPorts) {
|
if !reflect.DeepEqual(actual, test.expected) {
|
||||||
t.Errorf("expected ports %v, but got %v", expectedPorts, actual)
|
t.Errorf("expected ports %v, but got %v", test.expected, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue