mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 01:06:27 +00:00
Add more extensive tests to apiserver for variations in version
Formalize v1beta1 and v1beta3 style APIs in our test cases.
This commit is contained in:
parent
eb0eff69fe
commit
870da687d0
@ -670,6 +670,12 @@ func addParams(route *restful.RouteBuilder, params []*restful.Parameter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addObjectParams converts a runtime.Object into a set of go-restful Param() definitions on the route.
|
||||||
|
// The object must be a pointer to a struct; only fields at the top level of the struct that are not
|
||||||
|
// themselves interfaces or structs are used; only fields with a json tag that is non empty (the standard
|
||||||
|
// Go JSON behavior for omitting a field) become query parameters. The name of the query parameter is
|
||||||
|
// the JSON field name. If a description struct tag is set on the field, that description is used on the
|
||||||
|
// query parameter. In essence, it converts a standard JSON top level object into a query param schema.
|
||||||
func addObjectParams(ws *restful.WebService, route *restful.RouteBuilder, obj runtime.Object) error {
|
func addObjectParams(ws *restful.WebService, route *restful.RouteBuilder, obj runtime.Object) error {
|
||||||
sv, err := conversion.EnforcePtr(obj)
|
sv, err := conversion.EnforcePtr(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -204,7 +204,11 @@ func APIVersionHandler(versions ...string) restful.RouteFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// write renders a returned runtime.Object to the response as a stream or an encoded object.
|
// write renders a returned runtime.Object to the response as a stream or an encoded object. If the object
|
||||||
|
// returned by the response implements rest.ResourceStreamer that interface will be used to render the
|
||||||
|
// response. The Accept header and current API version will be passed in, and the output will be copied
|
||||||
|
// directly to the response body. If content type is returned it is used, otherwise the content type will
|
||||||
|
// be "application/octet-stream". All other objects are sent to standard JSON serialization.
|
||||||
func write(statusCode int, apiVersion string, codec runtime.Codec, object runtime.Object, w http.ResponseWriter, req *http.Request) {
|
func write(statusCode int, apiVersion string, codec runtime.Codec, object runtime.Object, w http.ResponseWriter, req *http.Request) {
|
||||||
if stream, ok := object.(rest.ResourceStreamer); ok {
|
if stream, ok := object.(rest.ResourceStreamer); ok {
|
||||||
out, contentType, err := stream.InputStream(apiVersion, req.Header.Get("Accept"))
|
out, contentType, err := stream.InputStream(apiVersion, req.Header.Get("Accept"))
|
||||||
|
@ -38,6 +38,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
@ -54,11 +55,21 @@ func convert(obj runtime.Object) (runtime.Object, error) {
|
|||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This creates a fake API version, similar to api/latest.go
|
// This creates a fake API version, similar to api/latest.go for a v1beta1 equivalent api. It is distinct
|
||||||
|
// from the Kubernetes API versions to allow clients to properly distinguish the two.
|
||||||
const testVersion = "version"
|
const testVersion = "version"
|
||||||
|
|
||||||
var versions = []string{testVersion}
|
// The equivalent of the Kubernetes v1beta3 API.
|
||||||
var codec = runtime.CodecFor(api.Scheme, testVersion)
|
const testVersion2 = "version2"
|
||||||
|
|
||||||
|
var versions = []string{testVersion, testVersion2}
|
||||||
|
var legacyCodec = runtime.CodecFor(api.Scheme, testVersion)
|
||||||
|
var codec = runtime.CodecFor(api.Scheme, testVersion2)
|
||||||
|
|
||||||
|
// these codecs reflect ListOptions/DeleteOptions coming from the serverAPIversion
|
||||||
|
var versionServerCodec = runtime.CodecFor(api.Scheme, "v1beta1")
|
||||||
|
var version2ServerCodec = runtime.CodecFor(api.Scheme, "v1beta3")
|
||||||
|
|
||||||
var accessor = meta.NewAccessor()
|
var accessor = meta.NewAccessor()
|
||||||
var versioner runtime.ResourceVersioner = accessor
|
var versioner runtime.ResourceVersioner = accessor
|
||||||
var selfLinker runtime.SelfLinker = accessor
|
var selfLinker runtime.SelfLinker = accessor
|
||||||
@ -69,6 +80,12 @@ var requestContextMapper api.RequestContextMapper
|
|||||||
func interfacesFor(version string) (*meta.VersionInterfaces, error) {
|
func interfacesFor(version string) (*meta.VersionInterfaces, error) {
|
||||||
switch version {
|
switch version {
|
||||||
case testVersion:
|
case testVersion:
|
||||||
|
return &meta.VersionInterfaces{
|
||||||
|
Codec: legacyCodec,
|
||||||
|
ObjectConvertor: api.Scheme,
|
||||||
|
MetadataAccessor: accessor,
|
||||||
|
}, nil
|
||||||
|
case testVersion2:
|
||||||
return &meta.VersionInterfaces{
|
return &meta.VersionInterfaces{
|
||||||
Codec: codec,
|
Codec: codec,
|
||||||
ObjectConvertor: api.Scheme,
|
ObjectConvertor: api.Scheme,
|
||||||
@ -100,7 +117,10 @@ func init() {
|
|||||||
api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{}, &api.Status{}, &api.ListOptions{})
|
api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{}, &api.Status{}, &api.ListOptions{})
|
||||||
// "version" version
|
// "version" version
|
||||||
// TODO: Use versioned api objects?
|
// TODO: Use versioned api objects?
|
||||||
api.Scheme.AddKnownTypes(testVersion, &Simple{}, &SimpleList{}, &v1beta1.DeleteOptions{}, &v1beta1.Status{}, &v1beta1.ListOptions{})
|
api.Scheme.AddKnownTypes(testVersion, &Simple{}, &SimpleList{}, &v1beta1.Status{})
|
||||||
|
// "version2" version
|
||||||
|
// TODO: Use versioned api objects?
|
||||||
|
api.Scheme.AddKnownTypes(testVersion2, &Simple{}, &SimpleList{}, &v1beta3.Status{})
|
||||||
|
|
||||||
nsMapper := newMapper()
|
nsMapper := newMapper()
|
||||||
legacyNsMapper := newMapper()
|
legacyNsMapper := newMapper()
|
||||||
@ -118,6 +138,18 @@ func init() {
|
|||||||
namespaceMapper = nsMapper
|
namespaceMapper = nsMapper
|
||||||
admissionControl = admit.NewAlwaysAdmit()
|
admissionControl = admit.NewAlwaysAdmit()
|
||||||
requestContextMapper = api.NewRequestContextMapper()
|
requestContextMapper = api.NewRequestContextMapper()
|
||||||
|
|
||||||
|
//mapper.(*meta.DefaultRESTMapper).Add(meta.RESTScopeNamespaceLegacy, "Simple", testVersion, false)
|
||||||
|
api.Scheme.AddFieldLabelConversionFunc(testVersion, "Simple",
|
||||||
|
func(label, value string) (string, string, error) {
|
||||||
|
return label, value, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
api.Scheme.AddFieldLabelConversionFunc(testVersion2, "Simple",
|
||||||
|
func(label, value string) (string, string, error) {
|
||||||
|
return label, value, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultAPIServer exposes nested objects for testability.
|
// defaultAPIServer exposes nested objects for testability.
|
||||||
@ -129,46 +161,61 @@ type defaultAPIServer struct {
|
|||||||
|
|
||||||
// uses the default settings
|
// uses the default settings
|
||||||
func handle(storage map[string]rest.Storage) http.Handler {
|
func handle(storage map[string]rest.Storage) http.Handler {
|
||||||
return handleInternal(storage, admissionControl, mapper, selfLinker)
|
return handleInternal(true, storage, admissionControl, selfLinker)
|
||||||
|
}
|
||||||
|
|
||||||
|
// uses the default settings for a v1beta3 compatible api
|
||||||
|
func handleNew(storage map[string]rest.Storage) http.Handler {
|
||||||
|
return handleInternal(false, storage, admissionControl, selfLinker)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tests with a deny admission controller
|
// tests with a deny admission controller
|
||||||
func handleDeny(storage map[string]rest.Storage) http.Handler {
|
func handleDeny(storage map[string]rest.Storage) http.Handler {
|
||||||
return handleInternal(storage, deny.NewAlwaysDeny(), mapper, selfLinker)
|
return handleInternal(true, storage, deny.NewAlwaysDeny(), selfLinker)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tests using the new namespace scope mechanism
|
// tests using the new namespace scope mechanism
|
||||||
func handleNamespaced(storage map[string]rest.Storage) http.Handler {
|
func handleNamespaced(storage map[string]rest.Storage) http.Handler {
|
||||||
return handleInternal(storage, admissionControl, namespaceMapper, selfLinker)
|
return handleInternal(false, storage, admissionControl, selfLinker)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tests using a custom self linker
|
// tests using a custom self linker
|
||||||
func handleLinker(storage map[string]rest.Storage, selfLinker runtime.SelfLinker) http.Handler {
|
func handleLinker(storage map[string]rest.Storage, selfLinker runtime.SelfLinker) http.Handler {
|
||||||
return handleInternal(storage, admissionControl, mapper, selfLinker)
|
return handleInternal(true, storage, admissionControl, selfLinker)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleInternal(storage map[string]rest.Storage, admissionControl admission.Interface, mapper meta.RESTMapper, selfLinker runtime.SelfLinker) http.Handler {
|
func handleInternal(legacy bool, storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker) http.Handler {
|
||||||
group := &APIGroupVersion{
|
group := &APIGroupVersion{
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
|
|
||||||
Mapper: mapper,
|
Root: "/api",
|
||||||
|
|
||||||
Root: "/api",
|
|
||||||
Version: testVersion,
|
|
||||||
|
|
||||||
Creater: api.Scheme,
|
Creater: api.Scheme,
|
||||||
Convertor: api.Scheme,
|
Convertor: api.Scheme,
|
||||||
Typer: api.Scheme,
|
Typer: api.Scheme,
|
||||||
Codec: codec,
|
|
||||||
Linker: selfLinker,
|
Linker: selfLinker,
|
||||||
|
|
||||||
Admit: admissionControl,
|
Admit: admissionControl,
|
||||||
Context: requestContextMapper,
|
Context: requestContextMapper,
|
||||||
}
|
}
|
||||||
|
if legacy {
|
||||||
|
group.Version = testVersion
|
||||||
|
group.ServerVersion = "v1beta1"
|
||||||
|
group.Codec = legacyCodec
|
||||||
|
group.Mapper = legacyNamespaceMapper
|
||||||
|
} else {
|
||||||
|
group.Version = testVersion2
|
||||||
|
group.ServerVersion = "v1beta3"
|
||||||
|
group.Codec = codec
|
||||||
|
group.Mapper = namespaceMapper
|
||||||
|
}
|
||||||
|
|
||||||
container := restful.NewContainer()
|
container := restful.NewContainer()
|
||||||
container.Router(restful.CurlyRouter{})
|
container.Router(restful.CurlyRouter{})
|
||||||
mux := container.ServeMux
|
mux := container.ServeMux
|
||||||
group.InstallREST(container)
|
if err := group.InstallREST(container); err != nil {
|
||||||
|
panic(fmt.Sprintf("unable to install container %s: %v", group.Version, err))
|
||||||
|
}
|
||||||
ws := new(restful.WebService)
|
ws := new(restful.WebService)
|
||||||
InstallSupport(mux, ws)
|
InstallSupport(mux, ws)
|
||||||
container.Add(ws)
|
container.Add(ws)
|
||||||
@ -557,27 +604,27 @@ func TestList(t *testing.T) {
|
|||||||
},
|
},
|
||||||
// list items in a namespace, v1beta3+
|
// list items in a namespace, v1beta3+
|
||||||
{
|
{
|
||||||
url: "/api/version/namespaces/default/simple",
|
url: "/api/version2/namespaces/default/simple",
|
||||||
namespace: "default",
|
namespace: "default",
|
||||||
selfLink: "/api/version/namespaces/default/simple",
|
selfLink: "/api/version2/namespaces/default/simple",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: "/api/version/namespaces/other/simple",
|
url: "/api/version2/namespaces/other/simple",
|
||||||
namespace: "other",
|
namespace: "other",
|
||||||
selfLink: "/api/version/namespaces/other/simple",
|
selfLink: "/api/version2/namespaces/other/simple",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: "/api/version/namespaces/other/simple?labels=a%3Db&fields=c%3Dd",
|
url: "/api/version2/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
|
||||||
namespace: "other",
|
namespace: "other",
|
||||||
selfLink: "/api/version/namespaces/other/simple",
|
selfLink: "/api/version2/namespaces/other/simple",
|
||||||
label: "a=b",
|
label: "a=b",
|
||||||
field: "c=d",
|
field: "c=d",
|
||||||
},
|
},
|
||||||
// list items across all namespaces
|
// list items across all namespaces
|
||||||
{
|
{
|
||||||
url: "/api/version/simple",
|
url: "/api/version2/simple",
|
||||||
namespace: "",
|
namespace: "",
|
||||||
selfLink: "/api/version/simple",
|
selfLink: "/api/version2/simple",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
@ -593,7 +640,7 @@ func TestList(t *testing.T) {
|
|||||||
if testCase.legacy {
|
if testCase.legacy {
|
||||||
handler = handleLinker(storage, selfLinker)
|
handler = handleLinker(storage, selfLinker)
|
||||||
} else {
|
} else {
|
||||||
handler = handleInternal(storage, admissionControl, namespaceMapper, selfLinker)
|
handler = handleInternal(false, storage, admissionControl, selfLinker)
|
||||||
}
|
}
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
@ -605,6 +652,9 @@ func TestList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
t.Errorf("%d: unexpected status: %d, Expected: %d, %#v", i, resp.StatusCode, http.StatusOK, resp)
|
t.Errorf("%d: unexpected status: %d, Expected: %d, %#v", i, resp.StatusCode, http.StatusOK, resp)
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
t.Logf("%d: body: %s", string(body))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
// TODO: future, restore get links
|
// TODO: future, restore get links
|
||||||
if !selfLinker.called {
|
if !selfLinker.called {
|
||||||
@ -875,16 +925,16 @@ func TestGetNamespaceSelfLink(t *testing.T) {
|
|||||||
}
|
}
|
||||||
selfLinker := &setTestSelfLinker{
|
selfLinker := &setTestSelfLinker{
|
||||||
t: t,
|
t: t,
|
||||||
expectedSet: "/api/version/namespaces/foo/simple/id",
|
expectedSet: "/api/version2/namespaces/foo/simple/id",
|
||||||
name: "id",
|
name: "id",
|
||||||
namespace: "foo",
|
namespace: "foo",
|
||||||
}
|
}
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
handler := handleInternal(storage, admissionControl, namespaceMapper, selfLinker)
|
handler := handleInternal(false, storage, admissionControl, selfLinker)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
resp, err := http.Get(server.URL + "/api/version/namespaces/foo/simple/id")
|
resp, err := http.Get(server.URL + "/api/version2/namespaces/foo/simple/id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -959,7 +1009,7 @@ func TestDeleteWithOptions(t *testing.T) {
|
|||||||
item := &api.DeleteOptions{
|
item := &api.DeleteOptions{
|
||||||
GracePeriodSeconds: &grace,
|
GracePeriodSeconds: &grace,
|
||||||
}
|
}
|
||||||
body, err := codec.Encode(item)
|
body, err := versionServerCodec.Encode(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -1020,7 +1070,7 @@ func TestLegacyDeleteIgnoresOptions(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
item := api.NewDeleteOptions(300)
|
item := api.NewDeleteOptions(300)
|
||||||
body, err := codec.Encode(item)
|
body, err := versionServerCodec.Encode(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -1629,7 +1679,7 @@ func TestCreateInvokesAdmissionControl(t *testing.T) {
|
|||||||
namespace: "other",
|
namespace: "other",
|
||||||
expectedSet: "/api/version/foo/bar?namespace=other",
|
expectedSet: "/api/version/foo/bar?namespace=other",
|
||||||
}
|
}
|
||||||
handler := handleInternal(map[string]rest.Storage{"foo": &storage}, deny.NewAlwaysDeny(), mapper, selfLinker)
|
handler := handleInternal(true, map[string]rest.Storage{"foo": &storage}, deny.NewAlwaysDeny(), selfLinker)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
|
@ -294,7 +294,7 @@ func TestProxy(t *testing.T) {
|
|||||||
server *httptest.Server
|
server *httptest.Server
|
||||||
proxyTestPattern string
|
proxyTestPattern string
|
||||||
}{
|
}{
|
||||||
{namespaceServer, "/api/version/proxy/namespaces/" + item.reqNamespace + "/foo/id" + item.path},
|
{namespaceServer, "/api/version2/proxy/namespaces/" + item.reqNamespace + "/foo/id" + item.path},
|
||||||
{legacyNamespaceServer, "/api/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace},
|
{legacyNamespaceServer, "/api/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +348,7 @@ func TestProxyUpgrade(t *testing.T) {
|
|||||||
server := httptest.NewServer(namespaceHandler)
|
server := httptest.NewServer(namespaceHandler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/api/version/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/")
|
ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/api/version2/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("websocket dial err: %s", err)
|
t.Fatalf("websocket dial err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ func TestRedirectWithNamespaces(t *testing.T) {
|
|||||||
for _, item := range table {
|
for _, item := range table {
|
||||||
simpleStorage.errors["resourceLocation"] = item.err
|
simpleStorage.errors["resourceLocation"] = item.err
|
||||||
simpleStorage.resourceLocation = &url.URL{Host: item.id}
|
simpleStorage.resourceLocation = &url.URL{Host: item.id}
|
||||||
resp, err := client.Get(server.URL + "/api/version/redirect/namespaces/other/foo/" + item.id)
|
resp, err := client.Get(server.URL + "/api/version2/redirect/namespaces/other/foo/" + item.id)
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
t.Fatalf("Unexpected nil resp")
|
t.Fatalf("Unexpected nil resp")
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
@ -49,14 +48,6 @@ var watchTestTable = []struct {
|
|||||||
{watch.Deleted, &Simple{ObjectMeta: api.ObjectMeta{Name: "bar"}}},
|
{watch.Deleted, &Simple{ObjectMeta: api.ObjectMeta{Name: "bar"}}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
mapper.(*meta.DefaultRESTMapper).Add(meta.RESTScopeNamespaceLegacy, "Simple", testVersion, false)
|
|
||||||
api.Scheme.AddFieldLabelConversionFunc(testVersion, "Simple",
|
|
||||||
func(label, value string) (string, string, error) {
|
|
||||||
return label, value, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWatchWebsocket(t *testing.T) {
|
func TestWatchWebsocket(t *testing.T) {
|
||||||
simpleStorage := &SimpleRESTStorage{}
|
simpleStorage := &SimpleRESTStorage{}
|
||||||
_ = rest.Watcher(simpleStorage) // Give compile error if this doesn't work.
|
_ = rest.Watcher(simpleStorage) // Give compile error if this doesn't work.
|
||||||
|
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Defines conversions between generic types and structs to map query strings
|
||||||
|
// to struct objects.
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
Loading…
Reference in New Issue
Block a user