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"
|
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"
|
2014-09-01 05:10:49 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
2014-06-06 23:40:48 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
2014-06-17 17:50:42 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
2014-06-17 02:10:43 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
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"
|
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 {
|
2014-09-18 23:03:34 +00:00
|
|
|
registry Registry
|
|
|
|
cloud cloudprovider.Interface
|
|
|
|
machines minion.Registry
|
|
|
|
portalMgr *ipAllocator
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
2014-09-08 21:40:56 +00:00
|
|
|
// NewREST returns a new REST.
|
2014-09-18 23:03:34 +00:00
|
|
|
func NewREST(registry Registry, cloud cloudprovider.Interface, machines minion.Registry, portalNet *net.IPNet) *REST {
|
|
|
|
// 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{
|
2014-09-18 23:03:34 +00:00
|
|
|
registry: registry,
|
|
|
|
cloud: cloud,
|
|
|
|
machines: machines,
|
|
|
|
portalMgr: ipa,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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]
|
2014-10-30 13:29:11 +00:00
|
|
|
if service.Spec.PortalIP == "" {
|
2014-10-22 17:02:02 +00:00
|
|
|
glog.Warningf("service %q has no PortalIP", service.Name)
|
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
|
|
|
}
|
|
|
|
|
2014-10-24 17:16:02 +00:00
|
|
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, 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) {
|
2014-10-22 17:02:02 +00:00
|
|
|
return nil, errors.NewConflict("service", service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context"))
|
2014-09-29 21:17:42 +00:00
|
|
|
}
|
2014-10-31 06:03:52 +00:00
|
|
|
if errs := validation.ValidateService(service, rs.registry, ctx); len(errs) > 0 {
|
2014-10-22 17:02:02 +00:00
|
|
|
return nil, errors.NewInvalid("service", service.Name, errs)
|
2014-07-13 04:46:01 +00:00
|
|
|
}
|
2014-08-23 18:33:24 +00:00
|
|
|
|
2014-11-12 21:27:10 +00:00
|
|
|
api.FillObjectMetaSystemFields(ctx, &service.ObjectMeta)
|
2014-08-23 18:33:24 +00:00
|
|
|
|
2014-10-30 13:29:11 +00:00
|
|
|
if service.Spec.PortalIP == "" {
|
2014-10-24 17:28:39 +00:00
|
|
|
// Allocate next available.
|
|
|
|
if ip, err := rs.portalMgr.AllocateNext(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
2014-10-30 13:29:11 +00:00
|
|
|
service.Spec.PortalIP = ip.String()
|
2014-10-24 17:28:39 +00:00
|
|
|
}
|
2014-09-18 23:03:34 +00:00
|
|
|
} else {
|
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 {
|
2014-10-24 17:28:39 +00:00
|
|
|
// TODO: Differentiate "IP already allocated" from real errors.
|
2014-10-30 13:29:11 +00:00
|
|
|
el := errors.ValidationErrorList{errors.NewFieldInvalid("spec.portalIP", service.Spec.PortalIP)}
|
2014-10-24 17:28:39 +00:00
|
|
|
return nil, errors.NewInvalid("service", service.Name, el)
|
|
|
|
}
|
2014-09-18 23:03:34 +00:00
|
|
|
}
|
|
|
|
|
2014-09-06 02:22:03 +00:00
|
|
|
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
2014-08-11 07:34:59 +00:00
|
|
|
// TODO: Consider moving this to a rectification loop, so that we make/remove external load balancers
|
|
|
|
// correctly no matter what http operations happen.
|
2014-11-14 06:23:33 +00:00
|
|
|
// TODO: Get rid of ProxyPort.
|
2014-10-30 13:29:11 +00:00
|
|
|
service.Spec.ProxyPort = 0
|
|
|
|
if service.Spec.CreateExternalLoadBalancer {
|
2014-08-11 07:34:59 +00:00
|
|
|
if rs.cloud == nil {
|
|
|
|
return nil, fmt.Errorf("requested an external service, but no cloud provider supplied.")
|
|
|
|
}
|
|
|
|
balancer, ok := rs.cloud.TCPLoadBalancer()
|
|
|
|
if !ok {
|
2014-11-20 10:00:36 +00:00
|
|
|
return nil, fmt.Errorf("the cloud provider does not support external TCP load balancers.")
|
2014-08-11 07:34:59 +00:00
|
|
|
}
|
|
|
|
zones, ok := rs.cloud.Zones()
|
|
|
|
if !ok {
|
2014-11-20 10:00:36 +00:00
|
|
|
return nil, fmt.Errorf("the cloud provider does not support zone enumeration.")
|
2014-08-11 07:34:59 +00:00
|
|
|
}
|
2014-09-09 03:02:21 +00:00
|
|
|
hosts, err := rs.machines.ListMinions(ctx)
|
2014-08-11 07:34:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
zone, err := zones.GetZone()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-11-12 04:08:33 +00:00
|
|
|
var ip net.IP
|
|
|
|
if len(service.Spec.PublicIPs) > 0 {
|
|
|
|
for _, publicIP := range service.Spec.PublicIPs {
|
|
|
|
ip, err = balancer.CreateTCPLoadBalancer(service.Name, zone.Region, net.ParseIP(publicIP), service.Spec.Port, hostsFromMinionList(hosts))
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ip, err = balancer.CreateTCPLoadBalancer(service.Name, zone.Region, nil, service.Spec.Port, hostsFromMinionList(hosts))
|
|
|
|
}
|
2014-08-11 07:34:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-11-12 04:08:33 +00:00
|
|
|
service.Spec.PublicIPs = []string{ip.String()}
|
2014-08-11 07:34:59 +00:00
|
|
|
}
|
2014-10-22 17:02:02 +00:00
|
|
|
err := rs.registry.CreateService(ctx, service)
|
2014-08-11 07:34:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-10-22 17:02:02 +00:00
|
|
|
return rs.registry.GetService(ctx, service.Name)
|
2014-08-11 07:34:59 +00:00
|
|
|
}), nil
|
2014-07-13 04:46:01 +00:00
|
|
|
}
|
|
|
|
|
2014-09-26 23:28:30 +00:00
|
|
|
func hostsFromMinionList(list *api.MinionList) []string {
|
|
|
|
result := make([]string, len(list.Items))
|
|
|
|
for ix := range list.Items {
|
2014-10-22 17:02:02 +00:00
|
|
|
result[ix] = list.Items[ix].Name
|
2014-09-26 23:28:30 +00:00
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2014-10-24 17:16:02 +00:00
|
|
|
func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, 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
|
|
|
}
|
2014-10-30 13:29:11 +00:00
|
|
|
rs.portalMgr.Release(net.ParseIP(service.Spec.PortalIP))
|
2014-09-06 02:22:03 +00:00
|
|
|
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
2014-08-11 07:34:59 +00:00
|
|
|
rs.deleteExternalLoadBalancer(service)
|
2014-09-26 19:18:42 +00:00
|
|
|
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteService(ctx, id)
|
2014-08-11 07:34:59 +00:00
|
|
|
}), nil
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2014-09-16 23:15:40 +00:00
|
|
|
// TODO: implement field selector?
|
2014-09-26 15:46:04 +00:00
|
|
|
func (rs *REST) List(ctx api.Context, label, field labels.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.
|
|
|
|
// It implements apiserver.ResourceWatcher.
|
2014-10-07 20:51:28 +00:00
|
|
|
func (rs *REST) Watch(ctx api.Context, label, field labels.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{}
|
|
|
|
}
|
|
|
|
|
2014-10-24 17:16:02 +00:00
|
|
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, 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) {
|
2014-10-22 17:02:02 +00:00
|
|
|
return nil, errors.NewConflict("service", service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context"))
|
2014-09-29 21:17:42 +00:00
|
|
|
}
|
2014-10-31 06:03:52 +00:00
|
|
|
if errs := validation.ValidateService(service, rs.registry, ctx); len(errs) > 0 {
|
2014-10-22 17:02:02 +00:00
|
|
|
return nil, errors.NewInvalid("service", service.Name, errs)
|
2014-08-11 07:34:59 +00:00
|
|
|
}
|
2014-09-06 02:22:03 +00:00
|
|
|
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
2014-10-22 17:02:02 +00:00
|
|
|
cur, err := rs.registry.GetService(ctx, service.Name)
|
2014-09-18 23:03:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-10-30 13:29:11 +00:00
|
|
|
if service.Spec.PortalIP != cur.Spec.PortalIP {
|
2014-10-24 17:28:39 +00:00
|
|
|
// TODO: Would be nice to pass "field is immutable" to users.
|
2014-10-30 13:29:11 +00:00
|
|
|
el := errors.ValidationErrorList{errors.NewFieldInvalid("spec.portalIP", service.Spec.PortalIP)}
|
2014-10-24 17:28:39 +00:00
|
|
|
return nil, errors.NewInvalid("service", service.Name, el)
|
|
|
|
}
|
2014-09-18 23:03:34 +00:00
|
|
|
// Copy over non-user fields.
|
2014-10-30 13:29:11 +00:00
|
|
|
service.Spec.ProxyPort = cur.Spec.ProxyPort
|
2014-08-11 07:34:59 +00:00
|
|
|
// TODO: check to see if external load balancer status changed
|
2014-10-22 17:02:02 +00:00
|
|
|
err = rs.registry.UpdateService(ctx, service)
|
2014-08-11 07:34:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-10-22 17:02:02 +00:00
|
|
|
return rs.registry.GetService(ctx, service.Name)
|
2014-08-11 07:34:59 +00:00
|
|
|
}), nil
|
|
|
|
}
|
2014-08-04 20:42:01 +00:00
|
|
|
|
2014-08-25 21:36:15 +00:00
|
|
|
// ResourceLocation returns a URL to which one can send traffic for the specified service.
|
2014-09-26 15:46:04 +00:00
|
|
|
func (rs *REST) ResourceLocation(ctx api.Context, id string) (string, error) {
|
2014-09-26 19:18:42 +00:00
|
|
|
e, err := rs.registry.GetEndpoints(ctx, id)
|
2014-08-25 21:36:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if len(e.Endpoints) == 0 {
|
|
|
|
return "", fmt.Errorf("no endpoints available for %v", id)
|
|
|
|
}
|
2014-10-17 21:21:53 +00:00
|
|
|
// We leave off the scheme ('http://') because we have no idea what sort of server
|
|
|
|
// is listening at this endpoint.
|
|
|
|
return e.Endpoints[rand.Intn(len(e.Endpoints))], nil
|
2014-08-25 21:36:15 +00:00
|
|
|
}
|
|
|
|
|
2014-09-08 21:40:56 +00:00
|
|
|
func (rs *REST) deleteExternalLoadBalancer(service *api.Service) error {
|
2014-10-30 13:29:11 +00:00
|
|
|
if !service.Spec.CreateExternalLoadBalancer || rs.cloud == nil {
|
2014-08-11 07:34:59 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
zones, ok := rs.cloud.Zones()
|
2014-08-04 20:42:01 +00:00
|
|
|
if !ok {
|
|
|
|
// We failed to get zone enumerator.
|
|
|
|
// As this should have failed when we tried in "create" too,
|
|
|
|
// assume external load balancer was never created.
|
|
|
|
return nil
|
|
|
|
}
|
2014-08-11 07:34:59 +00:00
|
|
|
balancer, ok := rs.cloud.TCPLoadBalancer()
|
2014-08-04 20:42:01 +00:00
|
|
|
if !ok {
|
|
|
|
// See comment above.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
zone, err := zones.GetZone()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-10-22 17:02:02 +00:00
|
|
|
if err := balancer.DeleteTCPLoadBalancer(service.Name, zone.Region); err != nil {
|
2014-08-04 20:42:01 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|