mirror of https://github.com/k3s-io/k3s
128 lines
3.9 KiB
Go
128 lines
3.9 KiB
Go
/*
|
|
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)
|
|
}
|
|
}
|