Merge pull request #4964 from lavalamp/fix6

Put #4575 back in and fix e2e failure
This commit is contained in:
Zach Loafman 2015-03-02 16:50:37 -08:00
commit fe1edcfeda
13 changed files with 224 additions and 57 deletions

View File

@ -1291,4 +1291,26 @@ func init() {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
// Add field conversion funcs.
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "pods",
func(label, value string) (string, string, error) {
switch label {
case "name":
return "name", value, nil
case "DesiredState.Host":
return "Status.Host", value, nil
case "DesiredState.Status":
podStatus := PodStatus(value)
var internalValue newer.PodPhase
newer.Scheme.Convert(&podStatus, &internalValue)
return "Status.Phase", string(internalValue), nil
default:
return "", "", fmt.Errorf("field label not supported: %s", label)
}
})
if err != nil {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
}

View File

@ -1206,4 +1206,26 @@ func init() {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
// Add field conversion funcs.
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "pods",
func(label, value string) (string, string, error) {
switch label {
case "name":
return "name", value, nil
case "DesiredState.Host":
return "Status.Host", value, nil
case "DesiredState.Status":
podStatus := PodStatus(value)
var internalValue newer.PodPhase
newer.Scheme.Convert(&podStatus, &internalValue)
return "Status.Phase", string(internalValue), nil
default:
return "", "", fmt.Errorf("field label not supported: %s", label)
}
})
if err != nil {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
}

View File

@ -0,0 +1,44 @@
/*
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.
*/
package v1beta3
import (
"fmt"
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
func init() {
// Add field conversion funcs.
err := newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "pods",
func(label, value string) (string, string, error) {
switch label {
case "name":
fallthrough
case "Status.Phase":
fallthrough
case "Status.Host":
return label, value, nil
default:
return "", "", fmt.Errorf("field label not supported: %s", label)
}
})
if err != nil {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
}

View File

@ -242,7 +242,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
addParams(route, action.Params)
ws.Route(route)
case "LIST": // List all resources of a kind.
route := ws.GET(action.Path).To(ListResource(lister, ctxFn, action.Namer, codec)).
route := ws.GET(action.Path).To(ListResource(lister, ctxFn, action.Namer, codec, a.group.info)).
Filter(m).
Doc("list objects of kind " + kind).
Operation("list" + kind).

View File

@ -18,6 +18,7 @@ package apiserver
import (
"net/http"
"net/url"
gpath "path"
"time"
@ -79,8 +80,24 @@ func GetResource(r RESTGetter, ctxFn ContextFunc, namer ScopeNamer, codec runtim
}
}
func parseSelectorQueryParams(query url.Values, version, apiResource string) (label, field labels.Selector, err error) {
label, err = labels.ParseSelector(query.Get("labels"))
if err != nil {
return nil, nil, err
}
convertToInternalVersionFunc := func(label, value string) (newLabel, newValue string, err error) {
return api.Scheme.ConvertFieldLabel(version, apiResource, label, value)
}
field, err = labels.ParseAndTransformSelector(query.Get("fields"), convertToInternalVersionFunc)
if err != nil {
return nil, nil, err
}
return label, field, nil
}
// ListResource returns a function that handles retrieving a list of resources from a RESTStorage object.
func ListResource(r RESTLister, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec) restful.RouteFunction {
func ListResource(r RESTLister, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, requestInfoResolver *APIRequestInfoResolver) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) {
w := res.ResponseWriter
@ -92,12 +109,13 @@ func ListResource(r RESTLister, ctxFn ContextFunc, namer ScopeNamer, codec runti
ctx := ctxFn(req)
ctx = api.WithNamespace(ctx, namespace)
label, err := labels.ParseSelector(req.Request.URL.Query().Get("labels"))
requestInfo, err := requestInfoResolver.GetAPIRequestInfo(req.Request)
if err != nil {
errorJSON(err, codec, w)
return
}
field, err := labels.ParseSelector(req.Request.URL.Query().Get("fields"))
label, field, err := parseSelectorQueryParams(req.Request.URL.Query(), requestInfo.APIVersion, requestInfo.Resource)
if err != nil {
errorJSON(err, codec, w)
return

View File

@ -18,7 +18,6 @@ package apiserver
import (
"net/http"
"net/url"
"path"
"regexp"
"strings"
@ -27,7 +26,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
@ -56,25 +54,6 @@ func (h *WatchHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request)
return h.linker.SetSelfLink(obj, newURL.String())
}
func getWatchParams(query url.Values) (label, field labels.Selector, resourceVersion string, err error) {
s, perr := labels.ParseSelector(query.Get("labels"))
if perr != nil {
err = perr
return
}
label = s
s, perr = labels.ParseSelector(query.Get("fields"))
if perr != nil {
err = perr
return
}
field = s
resourceVersion = query.Get("resourceVersion")
return
}
var connectionUpgradeRegex = regexp.MustCompile("(^|.*,\\s*)upgrade($|\\s*,)")
func isWebsocketRequest(req *http.Request) bool {
@ -117,11 +96,13 @@ func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
label, field, resourceVersion, err := getWatchParams(req.URL.Query())
label, field, err := parseSelectorQueryParams(req.URL.Query(), requestInfo.APIVersion, apiResource)
if err != nil {
httpCode = errorJSON(err, h.codec, w)
return
}
resourceVersion := req.URL.Query().Get("resourceVersion")
watching, err := watcher.Watch(ctx, label, field, resourceVersion)
if err != nil {
httpCode = errorJSON(err, h.codec, w)

View File

@ -25,6 +25,7 @@ import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"golang.org/x/net/websocket"
@ -164,15 +165,19 @@ func TestWatchHTTP(t *testing.T) {
}
func TestWatchParamParsing(t *testing.T) {
api.Scheme.AddFieldLabelConversionFunc(testVersion, "foo",
func(label, value string) (string, string, error) {
return label, value, nil
})
simpleStorage := &SimpleRESTStorage{}
handler := Handle(map[string]RESTStorage{
"foo": simpleStorage,
}, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper)
}, codec, "/api", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler)
defer server.Close()
dest, _ := url.Parse(server.URL)
dest.Path = "/api/version/watch/foo"
dest.Path = "/api/" + testVersion + "/watch/foo"
table := []struct {
rawQuery string
@ -194,10 +199,10 @@ func TestWatchParamParsing(t *testing.T) {
fieldSelector: "Host=",
namespace: api.NamespaceDefault,
}, {
rawQuery: "namespace=watchother&fields=ID%3dfoo&resourceVersion=1492",
rawQuery: "namespace=watchother&fields=id%3dfoo&resourceVersion=1492",
resourceVersion: "1492",
labelSelector: "",
fieldSelector: "ID=foo",
fieldSelector: "id=foo",
namespace: "watchother",
}, {
rawQuery: "",
@ -209,8 +214,8 @@ func TestWatchParamParsing(t *testing.T) {
}
for _, item := range table {
simpleStorage.requestedLabelSelector = nil
simpleStorage.requestedFieldSelector = nil
simpleStorage.requestedLabelSelector = labels.Everything()
simpleStorage.requestedFieldSelector = labels.Everything()
simpleStorage.requestedResourceVersion = "5" // Prove this is set in all cases
simpleStorage.requestedResourceNamespace = ""
dest.RawQuery = item.rawQuery

View File

@ -48,6 +48,15 @@ func buildResourcePath(prefix, namespace, resource string) string {
return path.Join(base, resource)
}
func getHostFieldLabel() string {
switch testapi.Version() {
case "v1beta1", "v1beta2":
return "DesiredState.Host"
default:
return "Status.Host"
}
}
// buildQueryValues is a convenience function for knowing if a namespace should be in a query param or not
func buildQueryValues(namespace string, query url.Values) url.Values {
v := url.Values{}
@ -84,17 +93,17 @@ func TestListWatchesCanList(t *testing.T) {
},
// pod with "assigned" field selector.
{
location: buildLocation(buildResourcePath("", api.NamespaceAll, "pods"), buildQueryValues(api.NamespaceAll, url.Values{"fields": []string{"DesiredState.Host="}})),
location: buildLocation(buildResourcePath("", api.NamespaceAll, "pods"), buildQueryValues(api.NamespaceAll, url.Values{"fields": []string{getHostFieldLabel() + "="}})),
resource: "pods",
namespace: api.NamespaceAll,
fieldSelector: labels.Set{"DesiredState.Host": ""}.AsSelector(),
fieldSelector: labels.Set{getHostFieldLabel(): ""}.AsSelector(),
},
// pod in namespace "foo"
{
location: buildLocation(buildResourcePath("", "foo", "pods"), buildQueryValues("foo", url.Values{"fields": []string{"DesiredState.Host="}})),
location: buildLocation(buildResourcePath("", "foo", "pods"), buildQueryValues("foo", url.Values{"fields": []string{getHostFieldLabel() + "="}})),
resource: "pods",
namespace: "foo",
fieldSelector: labels.Set{"DesiredState.Host": ""}.AsSelector(),
fieldSelector: labels.Set{getHostFieldLabel(): ""}.AsSelector(),
},
}
for _, item := range table {
@ -138,19 +147,19 @@ func TestListWatchesCanWatch(t *testing.T) {
},
// pod with "assigned" field selector.
{
location: buildLocation(buildResourcePath("watch", api.NamespaceAll, "pods"), buildQueryValues(api.NamespaceAll, url.Values{"fields": []string{"DesiredState.Host="}, "resourceVersion": []string{"0"}})),
location: buildLocation(buildResourcePath("watch", api.NamespaceAll, "pods"), buildQueryValues(api.NamespaceAll, url.Values{"fields": []string{getHostFieldLabel() + "="}, "resourceVersion": []string{"0"}})),
rv: "0",
resource: "pods",
namespace: api.NamespaceAll,
fieldSelector: labels.Set{"DesiredState.Host": ""}.AsSelector(),
fieldSelector: labels.Set{getHostFieldLabel(): ""}.AsSelector(),
},
// pod with namespace foo and assigned field selector
{
location: buildLocation(buildResourcePath("watch", "foo", "pods"), buildQueryValues("foo", url.Values{"fields": []string{"DesiredState.Host="}, "resourceVersion": []string{"0"}})),
location: buildLocation(buildResourcePath("watch", "foo", "pods"), buildQueryValues("foo", url.Values{"fields": []string{getHostFieldLabel() + "="}, "resourceVersion": []string{"0"}})),
rv: "0",
resource: "pods",
namespace: "foo",
fieldSelector: labels.Set{"DesiredState.Host": ""}.AsSelector(),
fieldSelector: labels.Set{getHostFieldLabel(): ""}.AsSelector(),
},
}

View File

@ -28,7 +28,7 @@ import (
// NewSourceApiserver creates a config source that watches and pulls from the apiserver.
func NewSourceApiserver(client *client.Client, hostname string, updates chan<- interface{}) {
lw := cache.NewListWatchFromClient(client, "pods", api.NamespaceAll, labels.OneTermEqualSelector("Status.Host", hostname))
lw := cache.NewListWatchFromClient(client, "pods", api.NamespaceAll, labels.OneTermEqualSelector(getHostFieldLabel(client.APIVersion()), hostname))
newSourceApiserverFromLW(lw, updates)
}
@ -51,3 +51,12 @@ func newSourceApiserverFromLW(lw cache.ListerWatcher, updates chan<- interface{}
}
cache.NewReflector(lw, &api.Pod{}, cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc)).Run()
}
func getHostFieldLabel(apiVersion string) string {
switch apiVersion {
case "v1beta1", "v1beta2":
return "DesiredState.Host"
default:
return "Status.Host"
}
}

View File

@ -800,6 +800,21 @@ func SelectorFromSetParse(ls Set) (Selector, error) {
// ParseSelector takes a string representing a selector and returns an
// object suitable for matching, or an error.
func ParseSelector(selector string) (Selector, error) {
return parseSelector(selector,
func(lhs, rhs string) (newLhs, newRhs string, err error) {
return lhs, rhs, nil
})
}
// Parses the selector and runs them through the given TransformFunc.
func ParseAndTransformSelector(selector string, fn TransformFunc) (Selector, error) {
return parseSelector(selector, fn)
}
// Function to transform selectors.
type TransformFunc func(label, value string) (newLabel, newValue string, err error)
func parseSelector(selector string, fn TransformFunc) (Selector, error) {
parts := strings.Split(selector, ",")
sort.StringSlice(parts).Sort()
var items []Selector
@ -808,10 +823,22 @@ func ParseSelector(selector string) (Selector, error) {
continue
}
if lhs, rhs, ok := try(part, "!="); ok {
lhs, rhs, err := fn(lhs, rhs)
if err != nil {
return nil, err
}
items = append(items, &notHasTerm{label: lhs, value: rhs})
} else if lhs, rhs, ok := try(part, "=="); ok {
lhs, rhs, err := fn(lhs, rhs)
if err != nil {
return nil, err
}
items = append(items, &hasTerm{label: lhs, value: rhs})
} else if lhs, rhs, ok := try(part, "="); ok {
lhs, rhs, err := fn(lhs, rhs)
if err != nil {
return nil, err
}
items = append(items, &hasTerm{label: lhs, value: rhs})
} else {
return nil, fmt.Errorf("invalid selector: '%s'; can't understand '%s'", selector, part)

View File

@ -23,7 +23,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
@ -120,18 +119,10 @@ func MatchPod(label, field labels.Selector) generic.Matcher {
// PodToSelectableFields returns a label set that represents the object
// TODO: fields are not labels, and the validation rules for them do not apply.
func PodToSelectableFields(pod *api.Pod) labels.Set {
// TODO we are populating both Status and DesiredState because selectors are not aware of API versions
// see https://github.com/GoogleCloudPlatform/kubernetes/pull/2503
var olderPodStatus v1beta1.PodStatus
api.Scheme.Convert(pod.Status.Phase, &olderPodStatus)
return labels.Set{
"name": pod.Name,
"Status.Phase": string(pod.Status.Phase),
"Status.Host": pod.Status.Host,
"DesiredState.Status": string(olderPodStatus),
"DesiredState.Host": pod.Status.Host,
"name": pod.Name,
"Status.Phase": string(pod.Status.Phase),
"Status.Host": pod.Status.Host,
}
}

View File

@ -27,8 +27,14 @@ import (
// is an adaptation of conversion's Scheme for our API objects.
type Scheme struct {
raw *conversion.Scheme
// Map from version and resource to the corresponding func to convert
// resource field labels in that version to internal version.
fieldLabelConversionFuncs map[string]map[string]FieldLabelConversionFunc
}
// Function to convert a field selector to internal representation.
type FieldLabelConversionFunc func(label, value string) (internalLabel, internalValue string, err error)
// fromScope gets the input version, desired output version, and desired Scheme
// from a conversion.Scope.
func (self *Scheme) fromScope(s conversion.Scope) (inVersion, outVersion string, scheme *Scheme) {
@ -200,7 +206,7 @@ func (self *Scheme) rawExtensionToRuntimeObjectArray(in *[]RawExtension, out *[]
// NewScheme creates a new Scheme. This scheme is pluggable by default.
func NewScheme() *Scheme {
s := &Scheme{conversion.NewScheme()}
s := &Scheme{conversion.NewScheme(), map[string]map[string]FieldLabelConversionFunc{}}
s.raw.InternalVersion = ""
s.raw.MetaFactory = conversion.SimpleMetaFactory{BaseFields: []string{"TypeMeta"}, VersionField: "APIVersion", KindField: "Kind"}
if err := s.raw.AddConversionFuncs(
@ -280,6 +286,17 @@ func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
return s.raw.AddConversionFuncs(conversionFuncs...)
}
// AddFieldLabelConversionFunc adds a conversion function to convert field selectors
// of the given api resource from the given version to internal version representation.
func (s *Scheme) AddFieldLabelConversionFunc(version, apiResource string, conversionFunc FieldLabelConversionFunc) error {
if s.fieldLabelConversionFuncs[version] == nil {
s.fieldLabelConversionFuncs[version] = map[string]FieldLabelConversionFunc{}
}
s.fieldLabelConversionFuncs[version][apiResource] = conversionFunc
return nil
}
// AddStructFieldConversion allows you to specify a mechanical copy for a moved
// or renamed struct field without writing an entire conversion function. See
// the comment in conversion.Converter.SetStructFieldCopy for parameter details.
@ -302,6 +319,18 @@ func (s *Scheme) Convert(in, out interface{}) error {
return s.raw.Convert(in, out)
}
// Converts the given field label and value for an apiResource field selector from
// versioned representation to an unversioned one.
func (s *Scheme) ConvertFieldLabel(version, apiResource, label, value string) (string, string, error) {
if typeFuncMap, ok := s.fieldLabelConversionFuncs[version]; ok {
if conversionFunc, ok := typeFuncMap[apiResource]; ok {
return conversionFunc(label, value)
}
}
// Don't fail on types we haven't added conversion funcs for yet.
return label, value, nil
}
// ConvertToVersion attempts to convert an input object to its matching Kind in another
// version within this scheme. Will return an error if the provided version does not
// contain the inKind (or a mapping by name defined with AddKnownTypeWithName). Will also

View File

@ -163,7 +163,7 @@ func (f *ConfigFactory) CreateFromKeys(predicateKeys, priorityKeys util.StringSe
// Returns a cache.ListWatch that finds all pods that need to be
// scheduled.
func (factory *ConfigFactory) createUnassignedPodLW() *cache.ListWatch {
return cache.NewListWatchFromClient(factory.Client, "pods", api.NamespaceAll, labels.Set{"DesiredState.Host": ""}.AsSelector())
return cache.NewListWatchFromClient(factory.Client, "pods", api.NamespaceAll, labels.Set{getHostFieldLabel(factory.Client.APIVersion()): ""}.AsSelector())
}
func parseSelectorOrDie(s string) labels.Selector {
@ -178,7 +178,8 @@ func parseSelectorOrDie(s string) labels.Selector {
// already scheduled.
// TODO: return a ListerWatcher interface instead?
func (factory *ConfigFactory) createAssignedPodLW() *cache.ListWatch {
return cache.NewListWatchFromClient(factory.Client, "pods", api.NamespaceAll, parseSelectorOrDie("DesiredState.Host!="))
return cache.NewListWatchFromClient(factory.Client, "pods", api.NamespaceAll,
parseSelectorOrDie(getHostFieldLabel(factory.Client.APIVersion())+"!="))
}
// createMinionLW returns a cache.ListWatch that gets all changes to minions.
@ -251,7 +252,16 @@ func (factory *ConfigFactory) makeDefaultErrorFunc(backoff *podBackoff, podQueue
}
}
// Allows a cache.Poller to enumerate items in an api.NodeList
func getHostFieldLabel(apiVersion string) string {
switch apiVersion {
case "v1beta1", "v1beta2":
return "DesiredState.Host"
default:
return "Status.Host"
}
}
// nodeEnumerator allows a cache.Poller to enumerate items in an api.NodeList
type nodeEnumerator struct {
*api.NodeList
}