| /* |
| Copyright 2017 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 proxy |
| |
| import ( |
| "fmt" |
| "math/rand" |
| "net" |
| "net/url" |
| "strconv" |
| |
| "k8s.io/api/core/v1" |
| "k8s.io/apimachinery/pkg/api/errors" |
| listersv1 "k8s.io/client-go/listers/core/v1" |
| |
| "k8s.io/apimachinery/pkg/util/intstr" |
| ) |
| |
| // findServicePort finds the service port by name or numerically. |
| func findServicePort(svc *v1.Service, port intstr.IntOrString) (*v1.ServicePort, error) { |
| for _, svcPort := range svc.Spec.Ports { |
| if (port.Type == intstr.Int && int32(svcPort.Port) == port.IntVal) || (port.Type == intstr.String && svcPort.Name == port.StrVal) { |
| return &svcPort, nil |
| } |
| } |
| return nil, errors.NewServiceUnavailable(fmt.Sprintf("no service port %q found for service %q", port.String(), svc.Name)) |
| } |
| |
| // ResourceLocation returns a URL to which one can send traffic for the specified service. |
| func ResolveEndpoint(services listersv1.ServiceLister, endpoints listersv1.EndpointsLister, namespace, id string) (*url.URL, error) { |
| svc, err := services.Services(namespace).Get(id) |
| if err != nil { |
| return nil, err |
| } |
| |
| port := intstr.FromInt(443) |
| svcPort, err := findServicePort(svc, port) |
| if err != nil { |
| return nil, err |
| } |
| |
| switch { |
| case svc.Spec.Type == v1.ServiceTypeClusterIP, svc.Spec.Type == v1.ServiceTypeLoadBalancer, svc.Spec.Type == v1.ServiceTypeNodePort: |
| // these are fine |
| default: |
| return nil, fmt.Errorf("unsupported service type %q", svc.Spec.Type) |
| } |
| |
| eps, err := endpoints.Endpoints(namespace).Get(svc.Name) |
| if err != nil { |
| return nil, err |
| } |
| if len(eps.Subsets) == 0 { |
| return nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", svc.Name)) |
| } |
| |
| // Pick a random Subset to start searching from. |
| ssSeed := rand.Intn(len(eps.Subsets)) |
| |
| // Find a Subset that has the port. |
| for ssi := 0; ssi < len(eps.Subsets); ssi++ { |
| ss := &eps.Subsets[(ssSeed+ssi)%len(eps.Subsets)] |
| if len(ss.Addresses) == 0 { |
| continue |
| } |
| for i := range ss.Ports { |
| if ss.Ports[i].Name == svcPort.Name { |
| // Pick a random address. |
| ip := ss.Addresses[rand.Intn(len(ss.Addresses))].IP |
| port := int(ss.Ports[i].Port) |
| return &url.URL{ |
| Scheme: "https", |
| Host: net.JoinHostPort(ip, strconv.Itoa(port)), |
| }, nil |
| } |
| } |
| } |
| return nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", id)) |
| } |
| |
| func ResolveCluster(services listersv1.ServiceLister, namespace, id string) (*url.URL, error) { |
| svc, err := services.Services(namespace).Get(id) |
| if err != nil { |
| return nil, err |
| } |
| |
| port := intstr.FromInt(443) |
| |
| switch { |
| case svc.Spec.Type == v1.ServiceTypeClusterIP && svc.Spec.ClusterIP == v1.ClusterIPNone: |
| return nil, fmt.Errorf(`cannot route to service with ClusterIP "None"`) |
| // use IP from a clusterIP for these service types |
| case svc.Spec.Type == v1.ServiceTypeClusterIP, svc.Spec.Type == v1.ServiceTypeLoadBalancer, svc.Spec.Type == v1.ServiceTypeNodePort: |
| svcPort, err := findServicePort(svc, port) |
| if err != nil { |
| return nil, err |
| } |
| return &url.URL{ |
| Scheme: "https", |
| Host: net.JoinHostPort(svc.Spec.ClusterIP, fmt.Sprintf("%d", svcPort.Port)), |
| }, nil |
| case svc.Spec.Type == v1.ServiceTypeExternalName: |
| if port.Type != intstr.Int { |
| return nil, fmt.Errorf("named ports not supported") |
| } |
| return &url.URL{ |
| Scheme: "https", |
| Host: net.JoinHostPort(svc.Spec.ExternalName, port.String()), |
| }, nil |
| default: |
| return nil, fmt.Errorf("unsupported service type %q", svc.Spec.Type) |
| } |
| } |