2014-06-06 23:40:48 +00:00
|
|
|
/*
|
|
|
|
Copyright 2014 Google Inc. All rights reserved.
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2014-06-18 21:18:53 +00:00
|
|
|
|
2014-08-11 07:34:59 +00:00
|
|
|
package service
|
2014-06-06 23:40:48 +00:00
|
|
|
|
|
|
|
import (
|
2014-06-17 17:50:42 +00:00
|
|
|
"fmt"
|
2014-08-25 21:36:15 +00:00
|
|
|
"math/rand"
|
2014-09-18 23:03:34 +00:00
|
|
|
"net"
|
2015-03-23 18:42:39 +00:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2015-02-19 03:54:15 +00:00
|
|
|
"strconv"
|
2015-03-20 21:24:43 +00:00
|
|
|
"strings"
|
2014-06-06 23:40:48 +00:00
|
|
|
|
2014-06-12 20:17:34 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
2014-09-03 21:16:00 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
2015-01-29 02:36:36 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
2014-09-01 05:10:49 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
2015-03-06 23:29:03 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
2014-06-17 02:10:43 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
2015-03-15 06:03:46 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
|
2014-08-11 07:34:59 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
|
2014-09-06 02:22:03 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
2015-03-22 21:40:47 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
|
2014-08-14 19:48:34 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
2014-09-18 23:03:34 +00:00
|
|
|
"github.com/golang/glog"
|
2014-06-06 23:40:48 +00:00
|
|
|
)
|
|
|
|
|
2014-09-08 21:40:56 +00:00
|
|
|
// REST adapts a service registry into apiserver's RESTStorage model.
|
|
|
|
type REST struct {
|
2015-04-01 06:39:00 +00:00
|
|
|
registry Registry
|
2015-02-13 22:58:42 +00:00
|
|
|
machines minion.Registry
|
2015-03-15 06:03:46 +00:00
|
|
|
endpoints endpoint.Registry
|
2015-02-13 22:58:42 +00:00
|
|
|
portalMgr *ipAllocator
|
|
|
|
clusterName string
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
2015-03-21 16:32:31 +00:00
|
|
|
// NewStorage returns a new REST.
|
2015-04-01 06:39:00 +00:00
|
|
|
func NewStorage(registry Registry, machines minion.Registry, endpoints endpoint.Registry, portalNet *net.IPNet,
|
2015-02-13 22:58:42 +00:00
|
|
|
clusterName string) *REST {
|
2014-09-18 23:03:34 +00:00
|
|
|
// TODO: Before we can replicate masters, this has to be synced (e.g. lives in etcd)
|
|
|
|
ipa := newIPAllocator(portalNet)
|
2014-10-28 00:56:33 +00:00
|
|
|
if ipa == nil {
|
|
|
|
glog.Fatalf("Failed to create an IP allocator. Is subnet '%v' valid?", portalNet)
|
|
|
|
}
|
2014-09-18 23:03:34 +00:00
|
|
|
reloadIPsFromStorage(ipa, registry)
|
|
|
|
|
2014-09-08 21:40:56 +00:00
|
|
|
return &REST{
|
2015-02-13 22:58:42 +00:00
|
|
|
registry: registry,
|
|
|
|
machines: machines,
|
2015-03-15 06:03:46 +00:00
|
|
|
endpoints: endpoints,
|
2015-02-13 22:58:42 +00:00
|
|
|
portalMgr: ipa,
|
|
|
|
clusterName: clusterName,
|
2014-09-18 23:03:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper: mark all previously allocated IPs in the allocator.
|
|
|
|
func reloadIPsFromStorage(ipa *ipAllocator, registry Registry) {
|
|
|
|
services, err := registry.ListServices(api.NewContext())
|
|
|
|
if err != nil {
|
|
|
|
// This is really bad.
|
2014-11-20 10:00:36 +00:00
|
|
|
glog.Errorf("can't list services to init service REST: %v", err)
|
2014-09-18 23:03:34 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
for i := range services.Items {
|
2014-10-22 17:02:02 +00:00
|
|
|
service := &services.Items[i]
|
2015-03-16 21:36:30 +00:00
|
|
|
if !api.IsServiceIPSet(service) {
|
2014-09-18 23:03:34 +00:00
|
|
|
continue
|
|
|
|
}
|
2014-10-30 13:29:11 +00:00
|
|
|
if err := ipa.Allocate(net.ParseIP(service.Spec.PortalIP)); err != nil {
|
2014-09-18 23:03:34 +00:00
|
|
|
// This is really bad.
|
2014-11-20 10:00:36 +00:00
|
|
|
glog.Errorf("service %q PortalIP %s could not be allocated: %v", service.Name, service.Spec.PortalIP, err)
|
2014-09-18 23:03:34 +00:00
|
|
|
}
|
2014-06-17 17:50:42 +00:00
|
|
|
}
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
2015-02-10 17:49:56 +00:00
|
|
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
2014-10-22 17:02:02 +00:00
|
|
|
service := obj.(*api.Service)
|
2014-08-23 18:33:24 +00:00
|
|
|
|
2015-01-29 02:36:36 +00:00
|
|
|
if err := rest.BeforeCreate(rest.Services, ctx, obj); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-23 18:33:24 +00:00
|
|
|
|
2015-04-09 23:10:21 +00:00
|
|
|
releaseServiceIP := false
|
|
|
|
defer func() {
|
|
|
|
if releaseServiceIP {
|
|
|
|
if api.IsServiceIPSet(service) {
|
|
|
|
rs.portalMgr.Release(net.ParseIP(service.Spec.PortalIP))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2015-03-16 21:36:30 +00:00
|
|
|
if api.IsServiceIPRequested(service) {
|
2014-10-24 17:28:39 +00:00
|
|
|
// Allocate next available.
|
2015-01-29 02:36:36 +00:00
|
|
|
ip, err := rs.portalMgr.AllocateNext()
|
|
|
|
if err != nil {
|
2014-10-24 17:28:39 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-01-29 02:36:36 +00:00
|
|
|
service.Spec.PortalIP = ip.String()
|
2015-04-09 23:10:21 +00:00
|
|
|
releaseServiceIP = true
|
2015-03-16 21:36:30 +00:00
|
|
|
} else if api.IsServiceIPSet(service) {
|
2014-10-24 17:28:39 +00:00
|
|
|
// Try to respect the requested IP.
|
2014-10-30 13:29:11 +00:00
|
|
|
if err := rs.portalMgr.Allocate(net.ParseIP(service.Spec.PortalIP)); err != nil {
|
2015-03-22 21:40:47 +00:00
|
|
|
el := fielderrors.ValidationErrorList{fielderrors.NewFieldInvalid("spec.portalIP", service.Spec.PortalIP, err.Error())}
|
2015-01-29 02:36:36 +00:00
|
|
|
return nil, errors.NewInvalid("Service", service.Name, el)
|
2014-10-24 17:28:39 +00:00
|
|
|
}
|
2015-04-09 23:10:21 +00:00
|
|
|
releaseServiceIP = true
|
2014-09-18 23:03:34 +00:00
|
|
|
}
|
|
|
|
|
2015-02-28 00:37:04 +00:00
|
|
|
out, err := rs.registry.CreateService(ctx, service)
|
|
|
|
if err != nil {
|
2015-02-10 17:49:56 +00:00
|
|
|
err = rest.CheckGeneratedNameError(rest.Services, err, service)
|
|
|
|
}
|
2015-04-09 23:10:21 +00:00
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
releaseServiceIP = false
|
|
|
|
}
|
|
|
|
|
2015-02-28 00:37:04 +00:00
|
|
|
return out, err
|
2014-07-13 04:46:01 +00:00
|
|
|
}
|
|
|
|
|
2015-02-10 17:49:56 +00:00
|
|
|
func (rs *REST) Delete(ctx api.Context, id string) (runtime.Object, error) {
|
2014-09-26 19:18:42 +00:00
|
|
|
service, err := rs.registry.GetService(ctx, id)
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
2014-08-11 07:34:59 +00:00
|
|
|
return nil, err
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
2015-03-16 21:36:30 +00:00
|
|
|
if api.IsServiceIPSet(service) {
|
|
|
|
rs.portalMgr.Release(net.ParseIP(service.Spec.PortalIP))
|
|
|
|
}
|
2015-02-10 17:49:56 +00:00
|
|
|
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteService(ctx, id)
|
2014-08-11 07:34:59 +00:00
|
|
|
}
|
|
|
|
|
2014-09-26 15:46:04 +00:00
|
|
|
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
|
2014-10-22 17:02:02 +00:00
|
|
|
service, err := rs.registry.GetService(ctx, id)
|
2014-08-11 07:34:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
2014-10-22 17:02:02 +00:00
|
|
|
return service, err
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
2015-03-06 23:29:03 +00:00
|
|
|
func (rs *REST) List(ctx api.Context, label labels.Selector, field fields.Selector) (runtime.Object, error) {
|
2014-09-26 19:18:42 +00:00
|
|
|
list, err := rs.registry.ListServices(ctx)
|
2014-06-13 21:58:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-06-17 02:10:43 +00:00
|
|
|
var filtered []api.Service
|
|
|
|
for _, service := range list.Items {
|
2014-09-16 23:15:40 +00:00
|
|
|
if label.Matches(labels.Set(service.Labels)) {
|
2014-06-17 02:10:43 +00:00
|
|
|
filtered = append(filtered, service)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
list.Items = filtered
|
2014-06-08 04:07:20 +00:00
|
|
|
return list, err
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
2014-08-14 19:48:34 +00:00
|
|
|
// Watch returns Services events via a watch.Interface.
|
2015-03-21 16:24:16 +00:00
|
|
|
// It implements rest.Watcher.
|
2015-03-06 23:29:03 +00:00
|
|
|
func (rs *REST) Watch(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
|
2014-09-26 19:18:42 +00:00
|
|
|
return rs.registry.WatchServices(ctx, label, field, resourceVersion)
|
2014-08-14 19:48:34 +00:00
|
|
|
}
|
|
|
|
|
2014-09-08 21:40:56 +00:00
|
|
|
func (*REST) New() runtime.Object {
|
2014-08-11 07:34:59 +00:00
|
|
|
return &api.Service{}
|
|
|
|
}
|
|
|
|
|
2015-01-12 05:33:25 +00:00
|
|
|
func (*REST) NewList() runtime.Object {
|
2015-02-17 21:30:29 +00:00
|
|
|
return &api.ServiceList{}
|
2015-01-12 05:33:25 +00:00
|
|
|
}
|
|
|
|
|
2015-02-10 17:49:56 +00:00
|
|
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
2014-10-22 17:02:02 +00:00
|
|
|
service := obj.(*api.Service)
|
2014-10-23 20:53:32 +00:00
|
|
|
if !api.ValidNamespace(ctx, &service.ObjectMeta) {
|
2015-02-10 17:49:56 +00:00
|
|
|
return nil, false, errors.NewConflict("service", service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context"))
|
2014-09-29 21:17:42 +00:00
|
|
|
}
|
2015-01-27 23:55:54 +00:00
|
|
|
|
|
|
|
oldService, err := rs.registry.GetService(ctx, service.Name)
|
|
|
|
if err != nil {
|
2015-02-10 17:49:56 +00:00
|
|
|
return nil, false, err
|
2015-01-27 23:55:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Copy over non-user fields
|
|
|
|
// TODO: make this a merge function
|
|
|
|
if errs := validation.ValidateServiceUpdate(oldService, service); len(errs) > 0 {
|
2015-02-10 17:49:56 +00:00
|
|
|
return nil, false, errors.NewInvalid("service", service.Name, errs)
|
2014-08-11 07:34:59 +00:00
|
|
|
}
|
2015-02-28 00:37:04 +00:00
|
|
|
out, err := rs.registry.UpdateService(ctx, service)
|
2015-02-10 17:49:56 +00:00
|
|
|
return out, false, err
|
2014-08-11 07:34:59 +00:00
|
|
|
}
|
2014-08-04 20:42:01 +00:00
|
|
|
|
2015-03-23 18:42:39 +00:00
|
|
|
// Implement Redirector.
|
|
|
|
var _ = rest.Redirector(&REST{})
|
|
|
|
|
2014-08-25 21:36:15 +00:00
|
|
|
// ResourceLocation returns a URL to which one can send traffic for the specified service.
|
2015-03-23 18:42:39 +00:00
|
|
|
func (rs *REST) ResourceLocation(ctx api.Context, id string) (*url.URL, http.RoundTripper, error) {
|
2015-03-20 21:24:43 +00:00
|
|
|
// Allow ID as "svcname" or "svcname:port".
|
|
|
|
parts := strings.Split(id, ":")
|
|
|
|
if len(parts) > 2 {
|
|
|
|
return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid service request %q", id))
|
|
|
|
}
|
|
|
|
svcName := parts[0]
|
|
|
|
portStr := ""
|
|
|
|
if len(parts) == 2 {
|
|
|
|
portStr = parts[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
eps, err := rs.endpoints.GetEndpoints(ctx, svcName)
|
2014-08-25 21:36:15 +00:00
|
|
|
if err != nil {
|
2015-03-23 18:42:39 +00:00
|
|
|
return nil, nil, err
|
2014-08-25 21:36:15 +00:00
|
|
|
}
|
2015-03-20 21:24:43 +00:00
|
|
|
if len(eps.Subsets) == 0 {
|
|
|
|
return nil, nil, fmt.Errorf("no endpoints available for %q", svcName)
|
|
|
|
}
|
|
|
|
// 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)]
|
|
|
|
for i := range ss.Ports {
|
|
|
|
if ss.Ports[i].Name == portStr {
|
|
|
|
// Pick a random address.
|
|
|
|
ip := ss.Addresses[rand.Intn(len(ss.Addresses))].IP
|
|
|
|
port := ss.Ports[i].Port
|
|
|
|
// We leave off the scheme ('http://') because we have no idea what sort of server
|
|
|
|
// is listening at this endpoint.
|
|
|
|
return &url.URL{
|
|
|
|
Host: net.JoinHostPort(ip, strconv.Itoa(port)),
|
|
|
|
}, nil, nil
|
|
|
|
}
|
|
|
|
}
|
2014-08-25 21:36:15 +00:00
|
|
|
}
|
2015-03-20 21:24:43 +00:00
|
|
|
return nil, nil, fmt.Errorf("no endpoints available for %q", id)
|
2014-08-25 21:36:15 +00:00
|
|
|
}
|