mirror of https://github.com/k3s-io/k3s
Add services/status path, and let the service controller uses Services.UpdateStatus()
parent
870220e0f1
commit
90b4662d8d
|
@ -13558,6 +13558,66 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "/api/v1/namespaces/{namespace}/services/{name}/status",
|
||||||
|
"description": "API at /api/v1",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"type": "v1.Service",
|
||||||
|
"method": "PUT",
|
||||||
|
"summary": "replace status of the specified Service",
|
||||||
|
"nickname": "replaceNamespacedServiceStatus",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "query",
|
||||||
|
"name": "pretty",
|
||||||
|
"description": "If 'true', then the output is pretty printed.",
|
||||||
|
"required": false,
|
||||||
|
"allowMultiple": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "v1.Service",
|
||||||
|
"paramType": "body",
|
||||||
|
"name": "body",
|
||||||
|
"description": "",
|
||||||
|
"required": true,
|
||||||
|
"allowMultiple": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path",
|
||||||
|
"name": "namespace",
|
||||||
|
"description": "object name and auth scope, such as for teams and projects",
|
||||||
|
"required": true,
|
||||||
|
"allowMultiple": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path",
|
||||||
|
"name": "name",
|
||||||
|
"description": "name of the Service",
|
||||||
|
"required": true,
|
||||||
|
"allowMultiple": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responseMessages": [
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "OK",
|
||||||
|
"responseModel": "v1.Service"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json",
|
||||||
|
"application/yaml"
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"*/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "/api/v1",
|
"path": "/api/v1",
|
||||||
"description": "API at /api/v1",
|
"description": "API at /api/v1",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1603,6 +1603,13 @@ func ValidateServiceUpdate(service, oldService *api.Service) field.ErrorList {
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateServiceStatusUpdate tests if required fields in the Service are set when updating status.
|
||||||
|
func ValidateServiceStatusUpdate(service, oldService *api.Service) field.ErrorList {
|
||||||
|
allErrs := ValidateObjectMetaUpdate(&service.ObjectMeta, &oldService.ObjectMeta, field.NewPath("metadata"))
|
||||||
|
allErrs = append(allErrs, ValidateLoadBalancerStatus(&service.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateReplicationController tests if required fields in the replication controller are set.
|
// ValidateReplicationController tests if required fields in the replication controller are set.
|
||||||
func ValidateReplicationController(controller *api.ReplicationController) field.ErrorList {
|
func ValidateReplicationController(controller *api.ReplicationController) field.ErrorList {
|
||||||
allErrs := ValidateObjectMeta(&controller.ObjectMeta, true, ValidateReplicationControllerName, field.NewPath("metadata"))
|
allErrs := ValidateObjectMeta(&controller.ObjectMeta, true, ValidateReplicationControllerName, field.NewPath("metadata"))
|
||||||
|
|
|
@ -33,6 +33,7 @@ type ServiceInterface interface {
|
||||||
Get(name string) (*api.Service, error)
|
Get(name string) (*api.Service, error)
|
||||||
Create(srv *api.Service) (*api.Service, error)
|
Create(srv *api.Service) (*api.Service, error)
|
||||||
Update(srv *api.Service) (*api.Service, error)
|
Update(srv *api.Service) (*api.Service, error)
|
||||||
|
UpdateStatus(srv *api.Service) (*api.Service, error)
|
||||||
Delete(name string) error
|
Delete(name string) error
|
||||||
Watch(opts api.ListOptions) (watch.Interface, error)
|
Watch(opts api.ListOptions) (watch.Interface, error)
|
||||||
ProxyGet(scheme, name, port, path string, params map[string]string) ResponseWrapper
|
ProxyGet(scheme, name, port, path string, params map[string]string) ResponseWrapper
|
||||||
|
@ -82,6 +83,13 @@ func (c *services) Update(svc *api.Service) (result *api.Service, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateStatus takes a Service object with the new status and applies it as an update to the existing Service.
|
||||||
|
func (c *services) UpdateStatus(service *api.Service) (result *api.Service, err error) {
|
||||||
|
result = &api.Service{}
|
||||||
|
err = c.r.Put().Namespace(c.ns).Resource("services").Name(service.Name).SubResource("status").Body(service).Do().Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Delete deletes an existing service.
|
// Delete deletes an existing service.
|
||||||
func (c *services) Delete(name string) error {
|
func (c *services) Delete(name string) error {
|
||||||
return c.r.Delete().Namespace(c.ns).Resource("services").Name(name).Do().Error()
|
return c.r.Delete().Namespace(c.ns).Resource("services").Name(name).Do().Error()
|
||||||
|
|
|
@ -167,6 +167,51 @@ func TestDeleteService(t *testing.T) {
|
||||||
c.Validate(t, nil, err)
|
c.Validate(t, nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdateServiceStatus(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
|
lbStatus := api.LoadBalancerStatus{
|
||||||
|
Ingress: []api.LoadBalancerIngress{
|
||||||
|
{IP: "127.0.0.1"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
requestService := &api.Service{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: ns,
|
||||||
|
ResourceVersion: "1",
|
||||||
|
},
|
||||||
|
Status: api.ServiceStatus{
|
||||||
|
LoadBalancer: lbStatus,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c := &simple.Client{
|
||||||
|
Request: simple.Request{
|
||||||
|
Method: "PUT",
|
||||||
|
Path: testapi.Default.ResourcePath("services", ns, "foo") + "/status",
|
||||||
|
Query: simple.BuildQueryValues(nil),
|
||||||
|
},
|
||||||
|
Response: simple.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
Body: &api.Service{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"name": "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.ServiceSpec{},
|
||||||
|
Status: api.ServiceStatus{
|
||||||
|
LoadBalancer: lbStatus,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
receivedService, err := c.Setup(t).Services(ns).UpdateStatus(requestService)
|
||||||
|
defer c.Close()
|
||||||
|
c.Validate(t, receivedService, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestServiceProxyGet(t *testing.T) {
|
func TestServiceProxyGet(t *testing.T) {
|
||||||
body := "OK"
|
body := "OK"
|
||||||
ns := api.NamespaceDefault
|
ns := api.NamespaceDefault
|
||||||
|
|
|
@ -65,6 +65,15 @@ func (c *FakeServices) Update(service *api.Service) (*api.Service, error) {
|
||||||
return obj.(*api.Service), err
|
return obj.(*api.Service), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *FakeServices) UpdateStatus(service *api.Service) (result *api.Service, err error) {
|
||||||
|
obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("services", "status", c.Namespace, service), service)
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj.(*api.Service), err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *FakeServices) Delete(name string) error {
|
func (c *FakeServices) Delete(name string) error {
|
||||||
_, err := c.Fake.Invokes(NewDeleteAction("services", c.Namespace, name), &api.Service{})
|
_, err := c.Fake.Invokes(NewDeleteAction("services", c.Namespace, name), &api.Service{})
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -343,7 +343,7 @@ func (s *ServiceController) createLoadBalancerIfNeeded(namespacedName types.Name
|
||||||
func (s *ServiceController) persistUpdate(service *api.Service) error {
|
func (s *ServiceController) persistUpdate(service *api.Service) error {
|
||||||
var err error
|
var err error
|
||||||
for i := 0; i < clientRetryCount; i++ {
|
for i := 0; i < clientRetryCount; i++ {
|
||||||
_, err = s.kubeClient.Services(service.Namespace).Update(service)
|
_, err = s.kubeClient.Services(service.Namespace).UpdateStatus(service)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,7 +292,7 @@ func (m *Master) initV1ResourcesStorage(c *Config) {
|
||||||
m.ProxyTransport,
|
m.ProxyTransport,
|
||||||
)
|
)
|
||||||
|
|
||||||
serviceStorage := serviceetcd.NewREST(dbClient("services"), storageDecorator)
|
serviceStorage, serviceStatusStorage := serviceetcd.NewREST(dbClient("services"), storageDecorator)
|
||||||
m.serviceRegistry = service.NewRegistry(serviceStorage)
|
m.serviceRegistry = service.NewRegistry(serviceStorage)
|
||||||
|
|
||||||
var serviceClusterIPRegistry service.RangeRegistry
|
var serviceClusterIPRegistry service.RangeRegistry
|
||||||
|
@ -336,6 +336,7 @@ func (m *Master) initV1ResourcesStorage(c *Config) {
|
||||||
"replicationControllers": controllerStorage,
|
"replicationControllers": controllerStorage,
|
||||||
"replicationControllers/status": controllerStatusStorage,
|
"replicationControllers/status": controllerStatusStorage,
|
||||||
"services": service.NewStorage(m.serviceRegistry, m.endpointRegistry, serviceClusterIPAllocator, serviceNodePortAllocator, m.ProxyTransport),
|
"services": service.NewStorage(m.serviceRegistry, m.endpointRegistry, serviceClusterIPAllocator, serviceNodePortAllocator, m.ProxyTransport),
|
||||||
|
"services/status": serviceStatusStorage,
|
||||||
"endpoints": endpointsStorage,
|
"endpoints": endpointsStorage,
|
||||||
"nodes": nodeStorage,
|
"nodes": nodeStorage,
|
||||||
"nodes/status": nodeStatusStorage,
|
"nodes/status": nodeStatusStorage,
|
||||||
|
|
|
@ -32,7 +32,7 @@ type REST struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewREST returns a RESTStorage object that will work against services.
|
// NewREST returns a RESTStorage object that will work against services.
|
||||||
func NewREST(s storage.Interface, storageDecorator generic.StorageDecorator) *REST {
|
func NewREST(s storage.Interface, storageDecorator generic.StorageDecorator) (*REST, *StatusREST) {
|
||||||
prefix := "/services/specs"
|
prefix := "/services/specs"
|
||||||
|
|
||||||
newListFunc := func() runtime.Object { return &api.ServiceList{} }
|
newListFunc := func() runtime.Object { return &api.ServiceList{} }
|
||||||
|
@ -61,5 +61,21 @@ func NewREST(s storage.Interface, storageDecorator generic.StorageDecorator) *RE
|
||||||
|
|
||||||
Storage: storageInterface,
|
Storage: storageInterface,
|
||||||
}
|
}
|
||||||
return &REST{store}
|
statusStore := *store
|
||||||
|
statusStore.UpdateStrategy = service.StatusStrategy
|
||||||
|
return &REST{store}, &StatusREST{store: &statusStore}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusREST implements the REST endpoint for changing the status of a service.
|
||||||
|
type StatusREST struct {
|
||||||
|
store *etcdgeneric.Etcd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StatusREST) New() runtime.Object {
|
||||||
|
return &api.Service{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update alters the status subset of an object.
|
||||||
|
func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
||||||
|
return r.store.Update(ctx, obj)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,9 +29,10 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/util/intstr"
|
"k8s.io/kubernetes/pkg/util/intstr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
|
func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) {
|
||||||
etcdStorage, server := registrytest.NewEtcdStorage(t, "")
|
etcdStorage, server := registrytest.NewEtcdStorage(t, "")
|
||||||
return NewREST(etcdStorage, generic.UndecoratedStorage), server
|
serviceStorage, statusStorage := NewREST(etcdStorage, generic.UndecoratedStorage)
|
||||||
|
return serviceStorage, statusStorage, server
|
||||||
}
|
}
|
||||||
|
|
||||||
func validService() *api.Service {
|
func validService() *api.Service {
|
||||||
|
@ -55,7 +56,7 @@ func validService() *api.Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
func TestCreate(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, _, server := newStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
test := registrytest.New(t, storage.Etcd)
|
test := registrytest.New(t, storage.Etcd)
|
||||||
validService := validService()
|
validService := validService()
|
||||||
|
@ -85,7 +86,7 @@ func TestCreate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
func TestUpdate(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, _, server := newStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate()
|
test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate()
|
||||||
test.TestUpdate(
|
test.TestUpdate(
|
||||||
|
@ -110,28 +111,28 @@ func TestUpdate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelete(t *testing.T) {
|
func TestDelete(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, _, server := newStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate()
|
test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate()
|
||||||
test.TestDelete(validService())
|
test.TestDelete(validService())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, _, server := newStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate()
|
test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate()
|
||||||
test.TestGet(validService())
|
test.TestGet(validService())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
func TestList(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, _, server := newStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate()
|
test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate()
|
||||||
test.TestList(validService())
|
test.TestList(validService())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWatch(t *testing.T) {
|
func TestWatch(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, _, server := newStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
test := registrytest.New(t, storage.Etcd)
|
test := registrytest.New(t, storage.Etcd)
|
||||||
test.TestWatch(
|
test.TestWatch(
|
||||||
|
|
|
@ -51,10 +51,9 @@ func (svcStrategy) PrepareForCreate(obj runtime.Object) {
|
||||||
|
|
||||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||||
func (svcStrategy) PrepareForUpdate(obj, old runtime.Object) {
|
func (svcStrategy) PrepareForUpdate(obj, old runtime.Object) {
|
||||||
// TODO: once service has a status sub-resource we can enable this.
|
newService := obj.(*api.Service)
|
||||||
//newService := obj.(*api.Service)
|
oldService := old.(*api.Service)
|
||||||
//oldService := old.(*api.Service)
|
newService.Status = oldService.Status
|
||||||
//newService.Status = oldService.Status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates a new service.
|
// Validate validates a new service.
|
||||||
|
@ -118,3 +117,23 @@ func MatchServices(label labels.Selector, field fields.Selector) generic.Matcher
|
||||||
func ServiceToSelectableFields(service *api.Service) fields.Set {
|
func ServiceToSelectableFields(service *api.Service) fields.Set {
|
||||||
return generic.ObjectMetaFieldsSet(service.ObjectMeta, true)
|
return generic.ObjectMetaFieldsSet(service.ObjectMeta, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type serviceStatusStrategy struct {
|
||||||
|
svcStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusStrategy is the default logic invoked when updating service status.
|
||||||
|
var StatusStrategy = serviceStatusStrategy{Strategy}
|
||||||
|
|
||||||
|
// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
|
||||||
|
func (serviceStatusStrategy) PrepareForUpdate(obj, old runtime.Object) {
|
||||||
|
newService := obj.(*api.Service)
|
||||||
|
oldService := old.(*api.Service)
|
||||||
|
// status changes are not allowed to update spec
|
||||||
|
newService.Spec = oldService.Spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateUpdate is the default update validation for an end user updating status
|
||||||
|
func (serviceStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
|
return validation.ValidateServiceStatusUpdate(obj.(*api.Service), old.(*api.Service))
|
||||||
|
}
|
||||||
|
|
|
@ -217,3 +217,33 @@ func TestSelectableFieldLabelConversions(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServiceStatusStrategy(t *testing.T) {
|
||||||
|
ctx := api.NewDefaultContext()
|
||||||
|
if !StatusStrategy.NamespaceScoped() {
|
||||||
|
t.Errorf("Service must be namespace scoped")
|
||||||
|
}
|
||||||
|
oldService := makeValidService()
|
||||||
|
newService := makeValidService()
|
||||||
|
oldService.ResourceVersion = "4"
|
||||||
|
newService.ResourceVersion = "4"
|
||||||
|
newService.Spec.SessionAffinity = "ClientIP"
|
||||||
|
newService.Status = api.ServiceStatus{
|
||||||
|
LoadBalancer: api.LoadBalancerStatus{
|
||||||
|
Ingress: []api.LoadBalancerIngress{
|
||||||
|
{IP: "127.0.0.2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
StatusStrategy.PrepareForUpdate(&newService, &oldService)
|
||||||
|
if newService.Status.LoadBalancer.Ingress[0].IP != "127.0.0.2" {
|
||||||
|
t.Errorf("Service status updates should allow change of status fields")
|
||||||
|
}
|
||||||
|
if newService.Spec.SessionAffinity != "None" {
|
||||||
|
t.Errorf("PrepareForUpdate should have preserved old spec")
|
||||||
|
}
|
||||||
|
errs := StatusStrategy.ValidateUpdate(ctx, &newService, &oldService)
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Errorf("Unexpected error %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -603,7 +603,9 @@ var _ = Describe("Services", func() {
|
||||||
})
|
})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
if len(service.Status.LoadBalancer.Ingress) != 0 {
|
// Updating the service type shouldn't change the Status immediately. The status should be
|
||||||
|
// updated after waitForLoadBalancerDestroy.
|
||||||
|
if len(service.Status.LoadBalancer.Ingress) == 0 {
|
||||||
Failf("got unexpected len(Status.LoadBalancer.Ingress) for NodePort service: %v", service)
|
Failf("got unexpected len(Status.LoadBalancer.Ingress) for NodePort service: %v", service)
|
||||||
}
|
}
|
||||||
if service.Spec.Type != api.ServiceTypeClusterIP {
|
if service.Spec.Type != api.ServiceTypeClusterIP {
|
||||||
|
|
Loading…
Reference in New Issue