mirror of
https://github.com/kubernetes-sigs/prometheus-adapter.git
synced 2026-04-07 10:17:51 +00:00
Add vendor folder to git
This commit is contained in:
parent
66cf5eaafb
commit
183585f56f
6916 changed files with 2629581 additions and 1 deletions
329
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver/installer/apiserver_test.go
generated
vendored
Normal file
329
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver/installer/apiserver_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 installer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
genericapi "k8s.io/apiserver/pkg/endpoints"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apimachinery/pkg/apimachinery"
|
||||
"k8s.io/metrics/pkg/apis/custom_metrics/install"
|
||||
"k8s.io/metrics/pkg/apis/custom_metrics"
|
||||
cmv1alpha1 "k8s.io/metrics/pkg/apis/custom_metrics/v1alpha1"
|
||||
|
||||
"github.com/directxman12/custom-metrics-boilerplate/pkg/provider"
|
||||
metricstorage "github.com/directxman12/custom-metrics-boilerplate/pkg/registry/custom_metrics"
|
||||
|
||||
)
|
||||
|
||||
// defaultAPIServer exposes nested objects for testability.
|
||||
type defaultAPIServer struct {
|
||||
http.Handler
|
||||
container *restful.Container
|
||||
}
|
||||
|
||||
var (
|
||||
groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
|
||||
registry = registered.NewOrDie("")
|
||||
Scheme = runtime.NewScheme()
|
||||
Codecs = serializer.NewCodecFactory(Scheme)
|
||||
prefix = genericapiserver.APIGroupPrefix
|
||||
groupVersion schema.GroupVersion
|
||||
groupMeta *apimachinery.GroupMeta
|
||||
codec = Codecs.LegacyCodec()
|
||||
emptySet = labels.Set{}
|
||||
matchingSet = labels.Set{"foo": "bar"}
|
||||
)
|
||||
|
||||
func init() {
|
||||
install.Install(groupFactoryRegistry, registry, Scheme)
|
||||
|
||||
// we need to add the options to empty v1
|
||||
// TODO fix the server code to avoid this
|
||||
metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
|
||||
|
||||
// TODO: keep the generic API server from wanting this
|
||||
unversioned := schema.GroupVersion{Group: "", Version: "v1"}
|
||||
Scheme.AddUnversionedTypes(unversioned,
|
||||
&metav1.Status{},
|
||||
&metav1.APIVersions{},
|
||||
&metav1.APIGroupList{},
|
||||
&metav1.APIGroup{},
|
||||
&metav1.APIResourceList{},
|
||||
)
|
||||
|
||||
groupMeta = registry.GroupOrDie(custom_metrics.GroupName)
|
||||
groupVersion = groupMeta.GroupVersion
|
||||
}
|
||||
|
||||
func extractBody(response *http.Response, object runtime.Object) error {
|
||||
defer response.Body.Close()
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return runtime.DecodeInto(codec, body, object)
|
||||
}
|
||||
|
||||
func extractBodyString(response *http.Response) (string, error) {
|
||||
defer response.Body.Close()
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(body), err
|
||||
}
|
||||
|
||||
func handle(prov provider.CustomMetricsProvider) http.Handler {
|
||||
container := restful.NewContainer()
|
||||
container.Router(restful.CurlyRouter{})
|
||||
mux := container.ServeMux
|
||||
resourceStorage := metricstorage.NewREST(prov)
|
||||
group := &MetricsAPIGroupVersion{
|
||||
DynamicStorage: resourceStorage,
|
||||
APIGroupVersion: &genericapi.APIGroupVersion{
|
||||
Root: prefix,
|
||||
GroupVersion: groupVersion,
|
||||
|
||||
ParameterCodec: metav1.ParameterCodec,
|
||||
Serializer: Codecs,
|
||||
Creater: Scheme,
|
||||
Convertor: Scheme,
|
||||
UnsafeConvertor: runtime.UnsafeObjectConvertor(Scheme),
|
||||
Copier: Scheme,
|
||||
Typer: Scheme,
|
||||
Linker: groupMeta.SelfLinker,
|
||||
Mapper: groupMeta.RESTMapper,
|
||||
|
||||
Context: request.NewRequestContextMapper(),
|
||||
OptionsExternalVersion: &schema.GroupVersion{Version: "v1"},
|
||||
|
||||
ResourceLister: provider.NewResourceLister(prov),
|
||||
},
|
||||
}
|
||||
|
||||
if err := group.InstallREST(container); err != nil {
|
||||
panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err))
|
||||
}
|
||||
|
||||
return &defaultAPIServer{mux, container}
|
||||
}
|
||||
|
||||
type fakeProvider struct {
|
||||
rootValues map[string][]custom_metrics.MetricValue
|
||||
namespacedValues map[string][]custom_metrics.MetricValue
|
||||
rootSubsetCounts map[string]int
|
||||
namespacedSubsetCounts map[string]int
|
||||
metrics []provider.MetricInfo
|
||||
}
|
||||
|
||||
func (p *fakeProvider) GetRootScopedMetricByName(groupResource schema.GroupResource, name string, metricName string) (*custom_metrics.MetricValue, error) {
|
||||
metricId := groupResource.String()+"/"+name+"/"+metricName
|
||||
values, ok := p.rootValues[metricId]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("non-existant metric requested (id: %s)", metricId)
|
||||
}
|
||||
|
||||
return &values[0], nil
|
||||
}
|
||||
|
||||
func (p *fakeProvider) GetRootScopedMetricBySelector(groupResource schema.GroupResource, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) {
|
||||
metricId := groupResource.String()+"/*/"+metricName
|
||||
values, ok := p.rootValues[metricId]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("non-existant metric requested (id: %s)", metricId)
|
||||
}
|
||||
|
||||
var trimmedValues custom_metrics.MetricValueList
|
||||
|
||||
if trimmedCount, ok := p.rootSubsetCounts[metricId]; ok {
|
||||
trimmedValues = custom_metrics.MetricValueList{
|
||||
Items: make([]custom_metrics.MetricValue, 0, trimmedCount),
|
||||
}
|
||||
for i := range values {
|
||||
var lbls labels.Labels
|
||||
if i < trimmedCount {
|
||||
lbls = matchingSet
|
||||
} else {
|
||||
lbls = emptySet
|
||||
}
|
||||
if selector.Matches(lbls) {
|
||||
trimmedValues.Items = append(trimmedValues.Items, custom_metrics.MetricValue{})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
trimmedValues = custom_metrics.MetricValueList{
|
||||
Items: values,
|
||||
}
|
||||
}
|
||||
|
||||
return &trimmedValues, nil
|
||||
}
|
||||
|
||||
func (p *fakeProvider) GetNamespacedMetricByName(groupResource schema.GroupResource, namespace string, name string, metricName string) (*custom_metrics.MetricValue, error) {
|
||||
metricId := namespace+"/"+groupResource.String()+"/"+name+"/"+metricName
|
||||
values, ok := p.namespacedValues[metricId]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("non-existant metric requested (id: %s)", metricId)
|
||||
}
|
||||
|
||||
return &values[0], nil
|
||||
}
|
||||
|
||||
func (p *fakeProvider) GetNamespacedMetricBySelector(groupResource schema.GroupResource, namespace string, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) {
|
||||
metricId := namespace+"/"+groupResource.String()+"/*/"+metricName
|
||||
values, ok := p.namespacedValues[metricId]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("non-existant metric requested (id: %s)", metricId)
|
||||
}
|
||||
|
||||
var trimmedValues custom_metrics.MetricValueList
|
||||
|
||||
if trimmedCount, ok := p.namespacedSubsetCounts[metricId]; ok {
|
||||
trimmedValues = custom_metrics.MetricValueList{
|
||||
Items: make([]custom_metrics.MetricValue, 0, trimmedCount),
|
||||
}
|
||||
for i := range values {
|
||||
var lbls labels.Labels
|
||||
if i < trimmedCount {
|
||||
lbls = matchingSet
|
||||
} else {
|
||||
lbls = emptySet
|
||||
}
|
||||
if selector.Matches(lbls) {
|
||||
trimmedValues.Items = append(trimmedValues.Items, custom_metrics.MetricValue{})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
trimmedValues = custom_metrics.MetricValueList{
|
||||
Items: values,
|
||||
}
|
||||
}
|
||||
|
||||
return &trimmedValues, nil
|
||||
}
|
||||
|
||||
func (p *fakeProvider) ListAllMetrics() []provider.MetricInfo {
|
||||
return p.metrics
|
||||
}
|
||||
|
||||
func TestCustomMetricsAPI(t *testing.T) {
|
||||
totalNodesCount := 4
|
||||
totalPodsCount := 16
|
||||
matchingNodesCount := 2
|
||||
matchingPodsCount := 8
|
||||
|
||||
type T struct {
|
||||
Method string
|
||||
Path string
|
||||
Status int
|
||||
ExpectedCount int
|
||||
}
|
||||
cases := map[string]T{
|
||||
// checks which should fail
|
||||
"GET long prefix": {"GET", "/" + prefix + "/", http.StatusNotFound, 0},
|
||||
|
||||
"root GET missing storage": {"GET", "/" + prefix + "/" + groupVersion.Group + "/" + groupVersion.Version + "/blah", http.StatusNotFound, 0},
|
||||
|
||||
"namespaced GET long prefix": {"GET", "/" + prefix + "/", http.StatusNotFound, 0},
|
||||
"namespaced GET missing storage": {"GET", "/" + prefix + "/" + groupVersion.Group + "/" + groupVersion.Version + "/blah", http.StatusNotFound, 0},
|
||||
|
||||
"GET at root resource leaf": {"GET", "/" + prefix + "/" + groupVersion.Group + "/" + groupVersion.Version + "/nodes/foo", http.StatusNotFound, 0},
|
||||
"GET at namespaced resource leaft": {"GET", "/" + prefix + "/" + groupVersion.Group + "/" + groupVersion.Version + "/namespaces/ns/pods/bar", http.StatusNotFound, 0},
|
||||
|
||||
// Positive checks to make sure everything is wired correctly
|
||||
"GET for all nodes (root)": {"GET", "/" + prefix + "/" + groupVersion.Group + "/" + groupVersion.Version + "/nodes/*/some-metric", http.StatusOK, totalNodesCount},
|
||||
"GET for all pods (namespaced)": {"GET", "/" + prefix + "/" + groupVersion.Group + "/" + groupVersion.Version + "/namespaces/ns/pods/*/some-metric", http.StatusOK, totalPodsCount},
|
||||
"GET for namespace": {"GET", "/" + prefix + "/" + groupVersion.Group + "/" + groupVersion.Version + "/namespaces/ns/metrics/some-metric", http.StatusOK, 1},
|
||||
"GET for label selected nodes (root)": {"GET", "/" + prefix + "/" + groupVersion.Group + "/" + groupVersion.Version + "/nodes/*/some-metric?labelSelector=foo%3Dbar", http.StatusOK, matchingNodesCount},
|
||||
"GET for label selected pods (namespaced)": {"GET", "/" + prefix + "/" + groupVersion.Group + "/" + groupVersion.Version + "/namespaces/ns/pods/*/some-metric?labelSelector=foo%3Dbar", http.StatusOK, matchingPodsCount},
|
||||
"GET for single node (root)": {"GET", "/" + prefix + "/" + groupVersion.Group + "/" + groupVersion.Version + "/nodes/foo/some-metric", http.StatusOK, 1},
|
||||
"GET for single pod (namespaced)": {"GET", "/" + prefix + "/" + groupVersion.Group + "/" + groupVersion.Version + "/namespaces/ns/pods/foo/some-metric", http.StatusOK, 1},
|
||||
}
|
||||
|
||||
prov := &fakeProvider{
|
||||
rootValues: map[string][]custom_metrics.MetricValue{
|
||||
"nodes/*/some-metric": make([]custom_metrics.MetricValue, totalNodesCount),
|
||||
"nodes/foo/some-metric": make([]custom_metrics.MetricValue, 1),
|
||||
"namespaces/ns/some-metric": make([]custom_metrics.MetricValue, 1),
|
||||
},
|
||||
namespacedValues: map[string][]custom_metrics.MetricValue{
|
||||
"ns/pods/*/some-metric": make([]custom_metrics.MetricValue, totalPodsCount),
|
||||
"ns/pods/foo/some-metric": make([]custom_metrics.MetricValue, 1),
|
||||
},
|
||||
|
||||
rootSubsetCounts: map[string]int{
|
||||
"nodes/*/some-metric": matchingNodesCount,
|
||||
},
|
||||
namespacedSubsetCounts: map[string]int{
|
||||
"ns/pods/*/some-metric": matchingPodsCount,
|
||||
},
|
||||
}
|
||||
|
||||
server := httptest.NewServer(handle(prov))
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
for k, v := range cases {
|
||||
request, err := http.NewRequest(v.Method, server.URL+v.Path, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error (%s): %v", k, err)
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error (%s): %v", k, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if response.StatusCode != v.Status {
|
||||
body, err := extractBodyString(response)
|
||||
bodyPart := body
|
||||
if err != nil {
|
||||
bodyPart = fmt.Sprintf("[error extracting body: %v]", err)
|
||||
}
|
||||
t.Errorf("Expected %d for %s (%s), Got %#v -- %s", v.Status, v.Method, k, response, bodyPart)
|
||||
continue
|
||||
}
|
||||
|
||||
if v.ExpectedCount > 0 {
|
||||
lst := &cmv1alpha1.MetricValueList{}
|
||||
if err := extractBody(response, lst); err != nil {
|
||||
t.Errorf("unexpected error (%s): %v", k, err)
|
||||
continue
|
||||
}
|
||||
if len(lst.Items) != v.ExpectedCount {
|
||||
t.Errorf("Expected %d items, got %d (%s): %#v", v.ExpectedCount, len(lst.Items), k, lst.Items)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
476
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver/installer/installer.go
generated
vendored
Normal file
476
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver/installer/installer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 installer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
gpath "path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
)
|
||||
|
||||
// NB: the contents of this file should mostly be a subset of the functionality
|
||||
// in "k8s.io/apiserver/pkg/endpoints". It would be nice to eventual figure out
|
||||
// a way to not have to recreate/copy a bunch of the structure from the normal API
|
||||
// installer, so that this trivially tracks changes to the main installer.
|
||||
|
||||
// MetricsAPIGroupVersion is similar to "k8s.io/apiserver/pkg/endpoints".APIGroupVersion,
|
||||
// except that it installs the metrics REST handlers, which use wildcard resources
|
||||
// and subresources.
|
||||
//
|
||||
// This basically only serves the limitted use case required by the metrics API server --
|
||||
// the only verb accepted is GET (and perhaps WATCH in the future).
|
||||
type MetricsAPIGroupVersion struct {
|
||||
DynamicStorage rest.Storage
|
||||
|
||||
*endpoints.APIGroupVersion
|
||||
}
|
||||
|
||||
// InstallDynamicREST registers the dynamic REST handlers into a restful Container.
|
||||
// It is expected that the provided path root prefix will serve all operations. Root MUST
|
||||
// NOT end in a slash. It should mirror InstallREST in the plain APIGroupVersion.
|
||||
func (g *MetricsAPIGroupVersion) InstallREST(container *restful.Container) error {
|
||||
installer := g.newDynamicInstaller()
|
||||
ws := installer.NewWebService()
|
||||
|
||||
registrationErrors := installer.Install(ws)
|
||||
lister := g.ResourceLister
|
||||
if lister == nil {
|
||||
return fmt.Errorf("must provide a dynamic lister for dynamic API groups")
|
||||
}
|
||||
versionDiscoveryHandler := discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, lister)
|
||||
versionDiscoveryHandler.AddToWebService(ws)
|
||||
container.Add(ws)
|
||||
return utilerrors.NewAggregate(registrationErrors)
|
||||
}
|
||||
|
||||
// newDynamicInstaller is a helper to create the installer. It mirrors
|
||||
// newInstaller in APIGroupVersion.
|
||||
func (g *MetricsAPIGroupVersion) newDynamicInstaller() *MetricsAPIInstaller {
|
||||
prefix := gpath.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
|
||||
installer := &MetricsAPIInstaller{
|
||||
group: g,
|
||||
prefix: prefix,
|
||||
minRequestTimeout: g.MinRequestTimeout,
|
||||
}
|
||||
|
||||
return installer
|
||||
}
|
||||
|
||||
// MetricsAPIInstaller is a specialized API installer for the metrics API.
|
||||
// It is intended to be fully compliant with the Kubernetes API server conventions,
|
||||
// but serves wildcard resource/subresource routes instead of hard-coded resources
|
||||
// and subresources.
|
||||
type MetricsAPIInstaller struct {
|
||||
group *MetricsAPIGroupVersion
|
||||
prefix string // Path prefix where API resources are to be registered.
|
||||
minRequestTimeout time.Duration
|
||||
|
||||
// TODO: do we want to embed a normal API installer here so we can serve normal
|
||||
// endpoints side by side with dynamic ones (from the same API group)?
|
||||
}
|
||||
|
||||
// Install installs handlers for API resources.
|
||||
func (a *MetricsAPIInstaller) Install(ws *restful.WebService) (errors []error) {
|
||||
errors = make([]error, 0)
|
||||
|
||||
err := a.registerResourceHandlers(a.group.DynamicStorage, ws)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Errorf("error in registering custom metrics resource: %v", err))
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
// NewWebService creates a new restful webservice with the api installer's prefix and version.
|
||||
func (a *MetricsAPIInstaller) NewWebService() *restful.WebService {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(a.prefix)
|
||||
// a.prefix contains "prefix/group/version"
|
||||
ws.Doc("API at " + a.prefix)
|
||||
// Backwards compatibility, we accepted objects with empty content-type at V1.
|
||||
// If we stop using go-restful, we can default empty content-type to application/json on an
|
||||
// endpoint by endpoint basis
|
||||
ws.Consumes("*/*")
|
||||
mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
|
||||
ws.Produces(append(mediaTypes, streamMediaTypes...)...)
|
||||
ws.ApiVersion(a.group.GroupVersion.String())
|
||||
|
||||
return ws
|
||||
}
|
||||
|
||||
// registerResourceHandlers registers the resource handlers for custom metrics.
|
||||
// Compared to the normal installer, this plays fast and loose a bit, but should still
|
||||
// follow the API conventions.
|
||||
func (a *MetricsAPIInstaller) registerResourceHandlers(storage rest.Storage, ws *restful.WebService) error {
|
||||
context := a.group.Context
|
||||
|
||||
optionsExternalVersion := a.group.GroupVersion
|
||||
if a.group.OptionsExternalVersion != nil {
|
||||
optionsExternalVersion = *a.group.OptionsExternalVersion
|
||||
}
|
||||
|
||||
mapping, err := a.restMapping()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fqKindToRegister, err := a.getResourceKind(storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kind := fqKindToRegister.Kind
|
||||
|
||||
lister := storage.(rest.Lister)
|
||||
list := lister.NewList()
|
||||
listGVKs, _, err := a.group.Typer.ObjectKinds(list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
versionedListPtr, err := a.group.Creater.New(a.group.GroupVersion.WithKind(listGVKs[0].Kind))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
versionedList := indirectArbitraryPointer(versionedListPtr)
|
||||
|
||||
versionedListOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("ListOptions"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctxFn := func(req *http.Request) request.Context {
|
||||
if ctx, ok := context.Get(req); ok {
|
||||
return request.WithUserAgent(ctx, req.Header.Get("User-Agent"))
|
||||
}
|
||||
return request.WithUserAgent(request.NewContext(), req.Header.Get("User-Agent"))
|
||||
}
|
||||
|
||||
scope := mapping.Scope
|
||||
nameParam := ws.PathParameter("name", "name of the described resource").DataType("string")
|
||||
resourceParam := ws.PathParameter("resource", "the name of the resource").DataType("string")
|
||||
subresourceParam := ws.PathParameter("subresource", "the name of the subresource").DataType("string")
|
||||
|
||||
// metrics describing non-namespaced objects (e.g. nodes)
|
||||
rootScopedParams := []*restful.Parameter{
|
||||
resourceParam,
|
||||
nameParam,
|
||||
subresourceParam,
|
||||
}
|
||||
rootScopedPath := "{resource}/{name}/{subresource}"
|
||||
|
||||
// metrics describing namespaced objects (e.g. pods)
|
||||
namespaceParam := ws.PathParameter(scope.ArgumentName(), scope.ParamDescription()).DataType("string")
|
||||
namespacedParams := []*restful.Parameter{
|
||||
namespaceParam,
|
||||
resourceParam,
|
||||
nameParam,
|
||||
subresourceParam,
|
||||
}
|
||||
namespacedPath := scope.ParamName() + "/{" + scope.ArgumentName() + "}/{resource}/{name}/{subresource}"
|
||||
|
||||
namespaceSpecificPath := scope.ParamName() + "/{" + scope.ArgumentName() + "}/metrics/{name}"
|
||||
namespaceSpecificParams := []*restful.Parameter{
|
||||
namespaceParam,
|
||||
nameParam,
|
||||
}
|
||||
|
||||
mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
|
||||
allMediaTypes := append(mediaTypes, streamMediaTypes...)
|
||||
ws.Produces(allMediaTypes...)
|
||||
|
||||
reqScope := handlers.RequestScope{
|
||||
ContextFunc: ctxFn,
|
||||
Serializer: a.group.Serializer,
|
||||
ParameterCodec: a.group.ParameterCodec,
|
||||
Creater: a.group.Creater,
|
||||
Convertor: a.group.Convertor,
|
||||
Copier: a.group.Copier,
|
||||
Typer: a.group.Typer,
|
||||
UnsafeConvertor: a.group.UnsafeConvertor,
|
||||
|
||||
// TODO: This seems wrong for cross-group subresources. It makes an assumption that a subresource and its parent are in the same group version. Revisit this.
|
||||
Resource: a.group.GroupVersion.WithResource("*"),
|
||||
Subresource: "*",
|
||||
Kind: fqKindToRegister,
|
||||
|
||||
MetaGroupVersion: metav1.SchemeGroupVersion,
|
||||
}
|
||||
if a.group.MetaGroupVersion != nil {
|
||||
reqScope.MetaGroupVersion = *a.group.MetaGroupVersion
|
||||
}
|
||||
|
||||
// we need one path for namespaced resources, one for non-namespaced resources
|
||||
doc := "list custom metrics describing an object or objects"
|
||||
reqScope.Namer = MetricsNaming{
|
||||
handlers.ContextBasedNaming{
|
||||
GetContext: ctxFn,
|
||||
SelfLinker: a.group.Linker,
|
||||
ClusterScoped: true,
|
||||
SelfLinkPathPrefix: a.prefix + "/",
|
||||
},
|
||||
}
|
||||
|
||||
rootScopedHandler := metrics.InstrumentRouteFunc("LIST", "custom-metrics", restfulListResource(lister, nil, reqScope, false, a.minRequestTimeout))
|
||||
|
||||
// install the root-scoped route
|
||||
rootScopedRoute := ws.GET(rootScopedPath).To(rootScopedHandler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
Operation("list"+kind).
|
||||
Produces(allMediaTypes...).
|
||||
Returns(http.StatusOK, "OK", versionedList).
|
||||
Writes(versionedList)
|
||||
if err := addObjectParams(ws, rootScopedRoute, versionedListOptions); err != nil {
|
||||
return err
|
||||
}
|
||||
addParams(rootScopedRoute, rootScopedParams)
|
||||
ws.Route(rootScopedRoute)
|
||||
|
||||
// install the namespace-scoped route
|
||||
reqScope.Namer = MetricsNaming{
|
||||
handlers.ContextBasedNaming{
|
||||
GetContext: ctxFn,
|
||||
SelfLinker: a.group.Linker,
|
||||
ClusterScoped: false,
|
||||
SelfLinkPathPrefix: gpath.Join(a.prefix, scope.ParamName()) + "/",
|
||||
},
|
||||
}
|
||||
namespacedHandler := metrics.InstrumentRouteFunc("LIST", "custom-metrics-namespaced", restfulListResource(lister, nil, reqScope, false, a.minRequestTimeout))
|
||||
namespacedRoute := ws.GET(namespacedPath).To(namespacedHandler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
Operation("listNamespaced"+kind).
|
||||
Produces(allMediaTypes...).
|
||||
Returns(http.StatusOK, "OK", versionedList).
|
||||
Writes(versionedList)
|
||||
if err := addObjectParams(ws, namespacedRoute, versionedListOptions); err != nil {
|
||||
return err
|
||||
}
|
||||
addParams(namespacedRoute, namespacedParams)
|
||||
ws.Route(namespacedRoute)
|
||||
|
||||
// install the special route for metrics describing namespaces (last b/c we modify the context func)
|
||||
reqScope.ContextFunc = ctxFn
|
||||
reqScope.Namer = MetricsNaming{
|
||||
handlers.ContextBasedNaming{
|
||||
GetContext: ctxFn,
|
||||
SelfLinker: a.group.Linker,
|
||||
ClusterScoped: false,
|
||||
SelfLinkPathPrefix: gpath.Join(a.prefix, scope.ParamName()) + "/",
|
||||
},
|
||||
}
|
||||
namespaceSpecificHandler := metrics.InstrumentRouteFunc("LIST", "custom-metrics-for-namespace", restfulListResource(lister, nil, reqScope, false, a.minRequestTimeout))
|
||||
namespaceSpecificRoute := ws.GET(namespaceSpecificPath).To(namespaceSpecificHandler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
Operation("read"+kind+"ForNamespace").
|
||||
Produces(allMediaTypes...).
|
||||
Returns(http.StatusOK, "OK", versionedList).
|
||||
Writes(versionedList)
|
||||
if err := addObjectParams(ws, namespaceSpecificRoute, versionedListOptions); err != nil {
|
||||
return err
|
||||
}
|
||||
addParams(namespaceSpecificRoute, namespaceSpecificParams)
|
||||
ws.Route(namespaceSpecificRoute)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This magic incantation returns *ptrToObject for an arbitrary pointer
|
||||
func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
|
||||
return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()
|
||||
}
|
||||
|
||||
// getResourceKind returns the external group version kind registered for the given storage object.
|
||||
func (a *MetricsAPIInstaller) getResourceKind(storage rest.Storage) (schema.GroupVersionKind, error) {
|
||||
object := storage.New()
|
||||
fqKinds, _, err := a.group.Typer.ObjectKinds(object)
|
||||
if err != nil {
|
||||
return schema.GroupVersionKind{}, err
|
||||
}
|
||||
|
||||
// a given go type can have multiple potential fully qualified kinds. Find the one that corresponds with the group
|
||||
// we're trying to register here
|
||||
fqKindToRegister := schema.GroupVersionKind{}
|
||||
for _, fqKind := range fqKinds {
|
||||
if fqKind.Group == a.group.GroupVersion.Group {
|
||||
fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind)
|
||||
break
|
||||
}
|
||||
|
||||
// TODO: keep rid of extensions api group dependency here
|
||||
// This keeps it doing what it was doing before, but it doesn't feel right.
|
||||
if fqKind.Group == "extensions" && fqKind.Kind == "ThirdPartyResourceData" {
|
||||
fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind)
|
||||
}
|
||||
}
|
||||
if fqKindToRegister.Empty() {
|
||||
return schema.GroupVersionKind{}, fmt.Errorf("unable to locate fully qualified kind for %v: found %v when registering for %v", reflect.TypeOf(object), fqKinds, a.group.GroupVersion)
|
||||
}
|
||||
return fqKindToRegister, nil
|
||||
}
|
||||
|
||||
// restMapping returns rest mapper for the resource provided by DynamicStorage.
|
||||
func (a *MetricsAPIInstaller) restMapping() (*meta.RESTMapping, error) {
|
||||
// subresources must have parent resources, and follow the namespacing rules of their parent.
|
||||
// So get the storage of the resource (which is the parent resource in case of subresources)
|
||||
fqKindToRegister, err := a.getResourceKind(a.group.DynamicStorage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to locate fully qualified kind for mapper resource for dynamic storage: %v", err)
|
||||
}
|
||||
return a.group.Mapper.RESTMapping(fqKindToRegister.GroupKind(), fqKindToRegister.Version)
|
||||
}
|
||||
|
||||
func addParams(route *restful.RouteBuilder, params []*restful.Parameter) {
|
||||
for _, param := range params {
|
||||
route.Param(param)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 interface{}) error {
|
||||
sv, err := conversion.EnforcePtr(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st := sv.Type()
|
||||
switch st.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < st.NumField(); i++ {
|
||||
name := st.Field(i).Name
|
||||
sf, ok := st.FieldByName(name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch sf.Type.Kind() {
|
||||
case reflect.Interface, reflect.Struct:
|
||||
case reflect.Ptr:
|
||||
// TODO: This is a hack to let metav1.Time through. This needs to be fixed in a more generic way eventually. bug #36191
|
||||
if (sf.Type.Elem().Kind() == reflect.Interface || sf.Type.Elem().Kind() == reflect.Struct) && strings.TrimPrefix(sf.Type.String(), "*") != "metav1.Time" {
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
jsonTag := sf.Tag.Get("json")
|
||||
if len(jsonTag) == 0 {
|
||||
continue
|
||||
}
|
||||
jsonName := strings.SplitN(jsonTag, ",", 2)[0]
|
||||
if len(jsonName) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var desc string
|
||||
if docable, ok := obj.(documentable); ok {
|
||||
desc = docable.SwaggerDoc()[jsonName]
|
||||
}
|
||||
route.Param(ws.QueryParameter(jsonName, desc).DataType(typeToJSON(sf.Type.String())))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: this is incomplete, expand as needed.
|
||||
// Convert the name of a golang type to the name of a JSON type
|
||||
func typeToJSON(typeName string) string {
|
||||
switch typeName {
|
||||
case "bool", "*bool":
|
||||
return "boolean"
|
||||
case "uint8", "*uint8", "int", "*int", "int32", "*int32", "int64", "*int64", "uint32", "*uint32", "uint64", "*uint64":
|
||||
return "integer"
|
||||
case "float64", "*float64", "float32", "*float32":
|
||||
return "number"
|
||||
case "metav1.Time", "*metav1.Time":
|
||||
return "string"
|
||||
case "byte", "*byte":
|
||||
return "string"
|
||||
case "v1.DeletionPropagation", "*v1.DeletionPropagation":
|
||||
return "string"
|
||||
|
||||
// TODO: Fix these when go-restful supports a way to specify an array query param:
|
||||
// https://github.com/emicklei/go-restful/issues/225
|
||||
case "[]string", "[]*string":
|
||||
return "string"
|
||||
case "[]int32", "[]*int32":
|
||||
return "integer"
|
||||
|
||||
default:
|
||||
return typeName
|
||||
}
|
||||
}
|
||||
|
||||
// An interface to see if an object supports swagger documentation as a method
|
||||
type documentable interface {
|
||||
SwaggerDoc() map[string]string
|
||||
}
|
||||
|
||||
// MetricsNaming is similar to handlers.ContextBasedNaming, except that it handles
|
||||
// polymorphism over subresources.
|
||||
type MetricsNaming struct {
|
||||
handlers.ContextBasedNaming
|
||||
}
|
||||
|
||||
func (n MetricsNaming) GenerateLink(req *http.Request, obj runtime.Object) (uri string, err error) {
|
||||
requestInfo, ok := request.RequestInfoFrom(n.GetContext(req))
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing requestInfo")
|
||||
}
|
||||
|
||||
if requestInfo.Resource != "metrics" {
|
||||
n.SelfLinkPathSuffix += "/" + requestInfo.Subresource
|
||||
}
|
||||
|
||||
// since this is not a pointer receiver, it's ok to modify it here
|
||||
// (since we copy around every method call)
|
||||
if n.ClusterScoped {
|
||||
n.SelfLinkPathPrefix += requestInfo.Resource + "/"
|
||||
return n.ContextBasedNaming.GenerateLink(req, obj)
|
||||
}
|
||||
|
||||
return n.ContextBasedNaming.GenerateLink(req, obj)
|
||||
}
|
||||
|
||||
func restfulListResource(r rest.Lister, rw rest.Watcher, scope handlers.RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.ListResource(r, rw, scope, forceWatch, minRequestTimeout)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
79
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver/installer/installer_test.go
generated
vendored
Normal file
79
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver/installer/installer_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 installer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/pkg/api"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
)
|
||||
|
||||
type setTestSelfLinker struct {
|
||||
t *testing.T
|
||||
expectedSet string
|
||||
name string
|
||||
namespace string
|
||||
called bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *setTestSelfLinker) Namespace(runtime.Object) (string, error) { return s.namespace, s.err }
|
||||
func (s *setTestSelfLinker) Name(runtime.Object) (string, error) { return s.name, s.err }
|
||||
func (s *setTestSelfLinker) SelfLink(runtime.Object) (string, error) { return "", s.err }
|
||||
func (s *setTestSelfLinker) SetSelfLink(obj runtime.Object, selfLink string) error {
|
||||
if e, a := s.expectedSet, selfLink; e != a {
|
||||
s.t.Errorf("expected '%v', got '%v'", e, a)
|
||||
}
|
||||
s.called = true
|
||||
return s.err
|
||||
}
|
||||
|
||||
func TestScopeNamingGenerateLink(t *testing.T) {
|
||||
selfLinker := &setTestSelfLinker{
|
||||
t: t,
|
||||
expectedSet: "/api/v1/namespaces/other/services/foo",
|
||||
name: "foo",
|
||||
namespace: "other",
|
||||
}
|
||||
s := scopeNaming{
|
||||
meta.RESTScopeNamespace,
|
||||
selfLinker,
|
||||
func(name, namespace, resource, subresource string) bytes.Buffer {
|
||||
return *bytes.NewBufferString("/api/v1/namespaces/" + namespace + "/services/" + name)
|
||||
},
|
||||
true,
|
||||
}
|
||||
service := &api.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: "other",
|
||||
},
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Service",
|
||||
},
|
||||
}
|
||||
_, err := s.GenerateLink(&restful.Request{}, service)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue