Check dup NodePort with protocols when update services

This commit is contained in:
xiangpengzhao 2017-07-20 10:54:37 +08:00
parent b47e0f8399
commit 17515a9f87

View File

@ -22,6 +22,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"reflect"
"strconv" "strconv"
"github.com/golang/glog" "github.com/golang/glog"
@ -61,6 +62,16 @@ type REST struct {
proxyTransport http.RoundTripper proxyTransport http.RoundTripper
} }
// ServiceNodePort includes protocol and port number of a service NodePort.
type ServiceNodePort struct {
// The IP protocol for this port. Supports "TCP" and "UDP".
Protocol api.Protocol
// The port on each node on which this service is exposed.
// Default is to auto-allocate a port if the ServiceType of this Service requires one.
NodePort int32
}
// NewStorage returns a new REST. // NewStorage returns a new REST.
func NewStorage(registry Registry, endpoints endpoint.Registry, serviceIPs ipallocator.Interface, func NewStorage(registry Registry, endpoints endpoint.Registry, serviceIPs ipallocator.Interface,
serviceNodePorts portallocator.Interface, proxyTransport http.RoundTripper) *ServiceRest { serviceNodePorts portallocator.Interface, proxyTransport http.RoundTripper) *ServiceRest {
@ -437,7 +448,7 @@ func (rs *REST) ResourceLocation(ctx genericapirequest.Context, id string) (*url
// This is O(N), but we expect haystack to be small; // This is O(N), but we expect haystack to be small;
// so small that we expect a linear search to be faster // so small that we expect a linear search to be faster
func contains(haystack []int, needle int) bool { func containsNumber(haystack []int, needle int) bool {
for _, v := range haystack { for _, v := range haystack {
if v == needle { if v == needle {
return true return true
@ -446,6 +457,17 @@ func contains(haystack []int, needle int) bool {
return false return false
} }
// This is O(N), but we expect serviceNodePorts to be small;
// so small that we expect a linear search to be faster
func containsNodePort(serviceNodePorts []ServiceNodePort, serviceNodePort ServiceNodePort) bool {
for _, snp := range serviceNodePorts {
if reflect.DeepEqual(snp, serviceNodePort) {
return true
}
}
return false
}
func CollectServiceNodePorts(service *api.Service) []int { func CollectServiceNodePorts(service *api.Service) []int {
servicePorts := []int{} servicePorts := []int{}
for i := range service.Spec.Ports { for i := range service.Spec.Ports {
@ -569,44 +591,49 @@ func (rs *REST) initNodePorts(service *api.Service, nodePortOp *portallocator.Po
} }
func (rs *REST) updateNodePorts(oldService, newService *api.Service, nodePortOp *portallocator.PortAllocationOperation) error { func (rs *REST) updateNodePorts(oldService, newService *api.Service, nodePortOp *portallocator.PortAllocationOperation) error {
oldNodePorts := CollectServiceNodePorts(oldService) oldNodePortsNumbers := CollectServiceNodePorts(oldService)
newNodePorts := []ServiceNodePort{}
portAllocated := map[int]bool{}
newNodePorts := []int{}
for i := range newService.Spec.Ports { for i := range newService.Spec.Ports {
servicePort := &newService.Spec.Ports[i] servicePort := &newService.Spec.Ports[i]
nodePort := int(servicePort.NodePort) nodePort := ServiceNodePort{Protocol: servicePort.Protocol, NodePort: servicePort.NodePort}
if nodePort != 0 { if nodePort.NodePort != 0 {
if !contains(oldNodePorts, nodePort) { if !containsNumber(oldNodePortsNumbers, int(nodePort.NodePort)) && !portAllocated[int(nodePort.NodePort)] {
err := nodePortOp.Allocate(nodePort) err := nodePortOp.Allocate(int(nodePort.NodePort))
if err != nil { if err != nil {
el := field.ErrorList{field.Invalid(field.NewPath("spec", "ports").Index(i).Child("nodePort"), nodePort, err.Error())} el := field.ErrorList{field.Invalid(field.NewPath("spec", "ports").Index(i).Child("nodePort"), nodePort.NodePort, err.Error())}
return errors.NewInvalid(api.Kind("Service"), newService.Name, el) return errors.NewInvalid(api.Kind("Service"), newService.Name, el)
} }
portAllocated[int(nodePort.NodePort)] = true
} }
} else { } else {
nodePort, err := nodePortOp.AllocateNext() nodePortNumber, err := nodePortOp.AllocateNext()
if err != nil { if err != nil {
// TODO: what error should be returned here? It's not a // TODO: what error should be returned here? It's not a
// field-level validation failure (the field is valid), and it's // field-level validation failure (the field is valid), and it's
// not really an internal error. // not really an internal error.
return errors.NewInternalError(fmt.Errorf("failed to allocate a nodePort: %v", err)) return errors.NewInternalError(fmt.Errorf("failed to allocate a nodePort: %v", err))
} }
servicePort.NodePort = int32(nodePort) servicePort.NodePort = int32(nodePortNumber)
nodePort.NodePort = servicePort.NodePort
} }
// Detect duplicate node ports; this should have been caught by validation, so we panic // Detect duplicate node ports; this should have been caught by validation, so we panic
if contains(newNodePorts, nodePort) { if containsNodePort(newNodePorts, nodePort) {
panic("duplicate node port") panic("duplicate node port")
} }
newNodePorts = append(newNodePorts, nodePort) newNodePorts = append(newNodePorts, nodePort)
} }
newNodePortsNumbers := CollectServiceNodePorts(newService)
// The comparison loops are O(N^2), but we don't expect N to be huge // The comparison loops are O(N^2), but we don't expect N to be huge
// (there's a hard-limit at 2^16, because they're ports; and even 4 ports would be a lot) // (there's a hard-limit at 2^16, because they're ports; and even 4 ports would be a lot)
for _, oldNodePort := range oldNodePorts { for _, oldNodePortNumber := range oldNodePortsNumbers {
if contains(newNodePorts, oldNodePort) { if containsNumber(newNodePortsNumbers, oldNodePortNumber) {
continue continue
} }
nodePortOp.ReleaseDeferred(oldNodePort) nodePortOp.ReleaseDeferred(int(oldNodePortNumber))
} }
return nil return nil