mirror of
https://github.com/kubernetes-sigs/prometheus-adapter.git
synced 2026-04-07 02:07:58 +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
106
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver/apiserver.go
generated
vendored
Normal file
106
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver/apiserver.go
generated
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"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"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
|
||||
"github.com/directxman12/custom-metrics-boilerplate/pkg/provider"
|
||||
"k8s.io/metrics/pkg/apis/custom_metrics/install"
|
||||
)
|
||||
|
||||
var (
|
||||
groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
|
||||
registry = registered.NewOrDie("")
|
||||
Scheme = runtime.NewScheme()
|
||||
Codecs = serializer.NewCodecFactory(Scheme)
|
||||
)
|
||||
|
||||
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{},
|
||||
)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
GenericConfig *genericapiserver.Config
|
||||
}
|
||||
|
||||
// CustomMetricsAdapterServer contains state for a Kubernetes cluster master/api server.
|
||||
type CustomMetricsAdapterServer struct {
|
||||
GenericAPIServer *genericapiserver.GenericAPIServer
|
||||
Provider provider.CustomMetricsProvider
|
||||
}
|
||||
|
||||
type completedConfig struct {
|
||||
*Config
|
||||
}
|
||||
|
||||
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
|
||||
func (c *Config) Complete() completedConfig {
|
||||
c.GenericConfig.Complete()
|
||||
|
||||
c.GenericConfig.Version = &version.Info{
|
||||
Major: "1",
|
||||
Minor: "0",
|
||||
}
|
||||
|
||||
return completedConfig{c}
|
||||
}
|
||||
|
||||
// SkipComplete provides a way to construct a server instance without config completion.
|
||||
func (c *Config) SkipComplete() completedConfig {
|
||||
return completedConfig{c}
|
||||
}
|
||||
|
||||
// New returns a new instance of CustomMetricsAdapterServer from the given config.
|
||||
func (c completedConfig) New(cmProvider provider.CustomMetricsProvider) (*CustomMetricsAdapterServer, error) {
|
||||
genericServer, err := c.Config.GenericConfig.SkipComplete().New(genericapiserver.EmptyDelegate) // completion is done in Complete, no need for a second time
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &CustomMetricsAdapterServer{
|
||||
GenericAPIServer: genericServer,
|
||||
Provider: cmProvider,
|
||||
}
|
||||
|
||||
if err := s.InstallCustomMetricsAPI(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
89
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver/cmapis.go
generated
vendored
Normal file
89
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver/cmapis.go
generated
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apimachinery"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
genericapi "k8s.io/apiserver/pkg/endpoints"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||
|
||||
specificapi "github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver/installer"
|
||||
"github.com/directxman12/custom-metrics-boilerplate/pkg/provider"
|
||||
metricstorage "github.com/directxman12/custom-metrics-boilerplate/pkg/registry/custom_metrics"
|
||||
"k8s.io/metrics/pkg/apis/custom_metrics"
|
||||
)
|
||||
|
||||
func (s *CustomMetricsAdapterServer) InstallCustomMetricsAPI() error {
|
||||
|
||||
groupMeta := registry.GroupOrDie(custom_metrics.GroupName)
|
||||
|
||||
preferredVersionForDiscovery := metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: groupMeta.GroupVersion.String(),
|
||||
Version: groupMeta.GroupVersion.Version,
|
||||
}
|
||||
groupVersion := metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: groupMeta.GroupVersion.String(),
|
||||
Version: groupMeta.GroupVersion.Version,
|
||||
}
|
||||
apiGroup := metav1.APIGroup{
|
||||
Name: groupMeta.GroupVersion.Group,
|
||||
Versions: []metav1.GroupVersionForDiscovery{groupVersion},
|
||||
PreferredVersion: preferredVersionForDiscovery,
|
||||
}
|
||||
|
||||
cmAPI := s.cmAPI(groupMeta, &groupMeta.GroupVersion)
|
||||
|
||||
if err := cmAPI.InstallREST(s.GenericAPIServer.Handler.GoRestfulContainer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.GenericAPIServer.DiscoveryGroupManager.AddGroup(apiGroup)
|
||||
s.GenericAPIServer.Handler.GoRestfulContainer.Add(discovery.NewAPIGroupHandler(s.GenericAPIServer.Serializer, apiGroup).WebService())
|
||||
|
||||
return nil
|
||||
}
|
||||
func (s *CustomMetricsAdapterServer) cmAPI(groupMeta *apimachinery.GroupMeta, groupVersion *schema.GroupVersion) *specificapi.MetricsAPIGroupVersion {
|
||||
resourceStorage := metricstorage.NewREST(s.Provider)
|
||||
|
||||
return &specificapi.MetricsAPIGroupVersion{
|
||||
DynamicStorage: resourceStorage,
|
||||
APIGroupVersion: &genericapi.APIGroupVersion{
|
||||
Root: genericapiserver.APIGroupPrefix,
|
||||
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: s.GenericAPIServer.RequestContextMapper(),
|
||||
MinRequestTimeout: s.GenericAPIServer.MinRequestTimeout(),
|
||||
OptionsExternalVersion: &schema.GroupVersion{Version: "v1"},
|
||||
|
||||
ResourceLister: provider.NewResourceLister(s.Provider),
|
||||
},
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
87
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/cmd/server/start.go
generated
vendored
Normal file
87
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/cmd/server/start.go
generated
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
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 server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||
"github.com/directxman12/custom-metrics-boilerplate/pkg/apiserver"
|
||||
)
|
||||
|
||||
type CustomMetricsAdapterServerOptions struct {
|
||||
// genericoptions.ReccomendedOptions - EtcdOptions
|
||||
SecureServing *genericoptions.SecureServingOptions
|
||||
Authentication *genericoptions.DelegatingAuthenticationOptions
|
||||
Authorization *genericoptions.DelegatingAuthorizationOptions
|
||||
Features *genericoptions.FeatureOptions
|
||||
|
||||
StdOut io.Writer
|
||||
StdErr io.Writer
|
||||
}
|
||||
|
||||
func NewCustomMetricsAdapterServerOptions(out, errOut io.Writer) *CustomMetricsAdapterServerOptions {
|
||||
o := &CustomMetricsAdapterServerOptions{
|
||||
SecureServing: genericoptions.NewSecureServingOptions(),
|
||||
Authentication: genericoptions.NewDelegatingAuthenticationOptions(),
|
||||
Authorization: genericoptions.NewDelegatingAuthorizationOptions(),
|
||||
Features: genericoptions.NewFeatureOptions(),
|
||||
|
||||
StdOut: out,
|
||||
StdErr: errOut,
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func (o CustomMetricsAdapterServerOptions) Validate(args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CustomMetricsAdapterServerOptions) Complete() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o CustomMetricsAdapterServerOptions) Config() (*apiserver.Config, error) {
|
||||
// TODO have a "real" external address (have an AdvertiseAddress?)
|
||||
if err := o.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", nil, []net.IP{net.ParseIP("127.0.0.1")}); err != nil {
|
||||
return nil, fmt.Errorf("error creating self-signed certificates: %v", err)
|
||||
}
|
||||
|
||||
serverConfig := genericapiserver.NewConfig(apiserver.Codecs)
|
||||
if err := o.SecureServing.ApplyTo(serverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := o.Authentication.ApplyTo(serverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := o.Authorization.ApplyTo(serverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: we can't currently serve swagger because we don't have a good way to dynamically update it
|
||||
// serverConfig.SwaggerConfig = genericapiserver.DefaultSwaggerConfig()
|
||||
|
||||
config := &apiserver.Config{
|
||||
GenericConfig: serverConfig,
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
77
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/provider/interfaces.go
generated
vendored
Normal file
77
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/provider/interfaces.go
generated
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
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 provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/metrics/pkg/apis/custom_metrics"
|
||||
)
|
||||
|
||||
// MetricInfo describes a metric for a particular
|
||||
// fully-qualified group resource.
|
||||
type MetricInfo struct {
|
||||
GroupResource schema.GroupResource
|
||||
Namespaced bool
|
||||
Metric string
|
||||
}
|
||||
|
||||
func (i MetricInfo) String() string {
|
||||
if i.Namespaced {
|
||||
return fmt.Sprintf("%s/%s(namespaced)", i.GroupResource.String(), i.Metric)
|
||||
} else {
|
||||
return fmt.Sprintf("%s/%s", i.GroupResource.String(), i.Metric)
|
||||
}
|
||||
}
|
||||
|
||||
// CustomMetricsProvider is a soruce of custom metrics
|
||||
// which is able to supply a list of available metrics,
|
||||
// as well as metric values themselves on demand.
|
||||
//
|
||||
// Note that group-resources are provided as GroupResources,
|
||||
// not GroupKinds. This is to allow flexibility on the part
|
||||
// of the implementor: implementors do not necessarily need
|
||||
// to be aware of all existing kinds and their corresponding
|
||||
// REST mappings in order to perform queries.
|
||||
//
|
||||
// For queries that use label selectors, it is up to the
|
||||
// implementor to decide how to make use of the label selector --
|
||||
// they may wish to query the main Kubernetes API server, or may
|
||||
// wish to simply make use of stored information in their TSDB.
|
||||
type CustomMetricsProvider interface {
|
||||
// GetRootScopedMetricByName fetches a particular metric for a particular root-scoped object.
|
||||
GetRootScopedMetricByName(groupResource schema.GroupResource, name string, metricName string) (*custom_metrics.MetricValue, error)
|
||||
|
||||
// GetRootScopedMetricByName fetches a particular metric for a set of root-scoped objects
|
||||
// matching the given label selector.
|
||||
GetRootScopedMetricBySelector(groupResource schema.GroupResource, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error)
|
||||
|
||||
// GetNamespacedMetricByName fetches a particular metric for a particular namespaced object.
|
||||
GetNamespacedMetricByName(groupResource schema.GroupResource, namespace string, name string, metricName string) (*custom_metrics.MetricValue, error)
|
||||
|
||||
// GetNamespacedMetricByName fetches a particular metric for a set of namespaced objects
|
||||
// matching the given label selector.
|
||||
GetNamespacedMetricBySelector(groupResource schema.GroupResource, namespace string, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error)
|
||||
|
||||
// ListAllMetrics provides a list of all available metrics at
|
||||
// the current time. Note that this is not allowed to return
|
||||
// an error, so it is reccomended that implementors cache and
|
||||
// periodically update this list, instead of querying every time.
|
||||
ListAllMetrics() []MetricInfo
|
||||
}
|
||||
48
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/provider/resource_lister.go
generated
vendored
Normal file
48
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/provider/resource_lister.go
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
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 provider
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type customMetricsResourceLister struct {
|
||||
provider CustomMetricsProvider
|
||||
}
|
||||
|
||||
func NewResourceLister(provider CustomMetricsProvider) discovery.APIResourceLister {
|
||||
return &customMetricsResourceLister{
|
||||
provider: provider,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *customMetricsResourceLister) ListAPIResources() []metav1.APIResource {
|
||||
metrics := l.provider.ListAllMetrics()
|
||||
resources := make([]metav1.APIResource, len(metrics))
|
||||
|
||||
for i, metric := range metrics {
|
||||
resources[i] = metav1.APIResource{
|
||||
Name: metric.GroupResource.String()+"/"+metric.Metric,
|
||||
Namespaced: metric.Namespaced,
|
||||
Kind: "MetricValueList",
|
||||
Verbs: metav1.Verbs{"get"}, // TODO: support "watch"
|
||||
}
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
128
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/registry/custom_metrics/reststorage.go
generated
vendored
Normal file
128
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/registry/custom_metrics/reststorage.go
generated
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"github.com/directxman12/custom-metrics-boilerplate/pkg/provider"
|
||||
"k8s.io/metrics/pkg/apis/custom_metrics"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
)
|
||||
|
||||
type REST struct {
|
||||
cmProvider provider.CustomMetricsProvider
|
||||
}
|
||||
|
||||
var _ rest.Storage = &REST{}
|
||||
var _ rest.Lister = &REST{}
|
||||
|
||||
func NewREST(cmProvider provider.CustomMetricsProvider) *REST {
|
||||
return &REST{
|
||||
cmProvider: cmProvider,
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Storage
|
||||
|
||||
func (r *REST) New() runtime.Object {
|
||||
return &custom_metrics.MetricValue{}
|
||||
}
|
||||
|
||||
// Implement Lister
|
||||
|
||||
func (r *REST) NewList() runtime.Object {
|
||||
return &custom_metrics.MetricValueList{}
|
||||
}
|
||||
|
||||
func (r *REST) List(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
|
||||
// populate the label selector, defaulting to all
|
||||
selector := labels.Everything()
|
||||
if options != nil && options.LabelSelector != nil {
|
||||
selector = options.LabelSelector
|
||||
}
|
||||
|
||||
// grab the name, if present, from the field selector list options
|
||||
// (this is how the list handler logic injects it)
|
||||
// (otherwise we'd have to write a custom list handler)
|
||||
name := "*"
|
||||
if options != nil && options.FieldSelector != nil {
|
||||
if nameMatch, required := options.FieldSelector.RequiresExactMatch("metadata.name"); required {
|
||||
name = nameMatch
|
||||
}
|
||||
}
|
||||
|
||||
namespace := genericapirequest.NamespaceValue(ctx)
|
||||
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to get resource and metric name from request")
|
||||
}
|
||||
|
||||
resourceRaw := requestInfo.Resource
|
||||
metricName := requestInfo.Subresource
|
||||
|
||||
groupResource := schema.ParseGroupResource(resourceRaw)
|
||||
|
||||
// handle metrics describing namespaces
|
||||
if namespace != "" && resourceRaw == "metrics" {
|
||||
// namespace-describing metrics have a path of /namespaces/$NS/metrics/$metric,
|
||||
groupResource = schema.GroupResource{Resource: "namespaces"}
|
||||
metricName = name
|
||||
name = namespace
|
||||
namespace = ""
|
||||
}
|
||||
|
||||
// handle namespaced and root metrics
|
||||
if name == "*" {
|
||||
return r.handleWildcardOp(namespace, groupResource, selector, metricName)
|
||||
} else {
|
||||
return r.handleIndividualOp(namespace, groupResource, name, metricName)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *REST) handleIndividualOp(namespace string, groupResource schema.GroupResource, name string, metricName string) (*custom_metrics.MetricValueList, error) {
|
||||
var err error
|
||||
var singleRes *custom_metrics.MetricValue
|
||||
if namespace == "" {
|
||||
singleRes, err = r.cmProvider.GetRootScopedMetricByName(groupResource, name, metricName)
|
||||
} else {
|
||||
singleRes, err = r.cmProvider.GetNamespacedMetricByName(groupResource, namespace, name, metricName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &custom_metrics.MetricValueList{
|
||||
Items: []custom_metrics.MetricValue{*singleRes},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *REST) handleWildcardOp(namespace string, groupResource schema.GroupResource, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) {
|
||||
if namespace == "" {
|
||||
return r.cmProvider.GetRootScopedMetricBySelector(groupResource, selector, metricName)
|
||||
} else {
|
||||
return r.cmProvider.GetNamespacedMetricBySelector(groupResource, namespace, selector, metricName)
|
||||
}
|
||||
}
|
||||
180
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/sample-cmd/provider/provider.go
generated
vendored
Normal file
180
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/sample-cmd/provider/provider.go
generated
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
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 provider
|
||||
|
||||
import (
|
||||
"time"
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/metrics/pkg/apis/custom_metrics"
|
||||
"k8s.io/client-go/pkg/api"
|
||||
_ "k8s.io/client-go/pkg/api/install"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
|
||||
"github.com/directxman12/custom-metrics-boilerplate/pkg/provider"
|
||||
)
|
||||
|
||||
type incrementalTestingProvider struct {
|
||||
client coreclient.CoreV1Interface
|
||||
|
||||
values map[provider.MetricInfo]int64
|
||||
}
|
||||
|
||||
func NewFakeProvider(client coreclient.CoreV1Interface) provider.CustomMetricsProvider {
|
||||
return &incrementalTestingProvider{
|
||||
client: client,
|
||||
values: make(map[provider.MetricInfo]int64),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *incrementalTestingProvider) valueFor(groupResource schema.GroupResource, metricName string, namespaced bool) int64 {
|
||||
info := provider.MetricInfo{
|
||||
GroupResource: groupResource,
|
||||
Metric: metricName,
|
||||
Namespaced: namespaced,
|
||||
}
|
||||
|
||||
value := p.values[info]
|
||||
value += 1
|
||||
p.values[info] = value
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func (p *incrementalTestingProvider) metricFor(value int64, groupResource schema.GroupResource, namespace string, name string, metricName string) (*custom_metrics.MetricValue, error) {
|
||||
group, err := api.Registry.Group(groupResource.Group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kind, err := api.Registry.RESTMapper().KindFor(groupResource.WithVersion(group.GroupVersion.Version))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &custom_metrics.MetricValue{
|
||||
DescribedObject: api.ObjectReference{
|
||||
APIVersion: groupResource.Group+"/"+runtime.APIVersionInternal,
|
||||
Kind: kind.Kind,
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
MetricName: metricName,
|
||||
Timestamp: metav1.Time{time.Now()},
|
||||
Value: *resource.NewMilliQuantity(value * 100, resource.DecimalSI),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *incrementalTestingProvider) metricsFor(totalValue int64, groupResource schema.GroupResource, metricName string, list runtime.Object) (*custom_metrics.MetricValueList, error) {
|
||||
if !apimeta.IsListType(list) {
|
||||
return nil, fmt.Errorf("returned object was not a list")
|
||||
}
|
||||
|
||||
res := make([]custom_metrics.MetricValue, 0)
|
||||
|
||||
err := apimeta.EachListItem(list, func(item runtime.Object) error {
|
||||
objMeta := item.(metav1.ObjectMetaAccessor).GetObjectMeta()
|
||||
value, err := p.metricFor(0, groupResource, objMeta.GetNamespace(), objMeta.GetName(), metricName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res = append(res, *value)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range res {
|
||||
res[i].Value = *resource.NewMilliQuantity(100 * totalValue / int64(len(res)), resource.DecimalSI)
|
||||
}
|
||||
|
||||
//return p.metricFor(value, groupResource, "", name, metricName)
|
||||
return &custom_metrics.MetricValueList{
|
||||
Items: res,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *incrementalTestingProvider) GetRootScopedMetricByName(groupResource schema.GroupResource, name string, metricName string) (*custom_metrics.MetricValue, error) {
|
||||
value := p.valueFor(groupResource, metricName, false)
|
||||
return p.metricFor(value, groupResource, "", name, metricName)
|
||||
}
|
||||
|
||||
|
||||
func (p *incrementalTestingProvider) GetRootScopedMetricBySelector(groupResource schema.GroupResource, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) {
|
||||
totalValue := p.valueFor(groupResource, metricName, false)
|
||||
|
||||
// TODO: work for objects not in core v1
|
||||
matchingObjectsRaw, err := p.client.RESTClient().Get().
|
||||
Resource(groupResource.Resource).
|
||||
VersionedParams(&metav1.ListOptions{LabelSelector: selector.String()}, scheme.ParameterCodec).
|
||||
Do().
|
||||
Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.metricsFor(totalValue, groupResource, metricName, matchingObjectsRaw)
|
||||
}
|
||||
|
||||
func (p *incrementalTestingProvider) GetNamespacedMetricByName(groupResource schema.GroupResource, namespace string, name string, metricName string) (*custom_metrics.MetricValue, error) {
|
||||
value := p.valueFor(groupResource, metricName, true)
|
||||
return p.metricFor(value, groupResource, namespace, name, metricName)
|
||||
}
|
||||
|
||||
func (p *incrementalTestingProvider) GetNamespacedMetricBySelector(groupResource schema.GroupResource, namespace string, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) {
|
||||
totalValue := p.valueFor(groupResource, metricName, true)
|
||||
|
||||
// TODO: work for objects not in core v1
|
||||
matchingObjectsRaw, err := p.client.RESTClient().Get().
|
||||
Namespace(namespace).
|
||||
Resource(groupResource.Resource).
|
||||
VersionedParams(&metav1.ListOptions{LabelSelector: selector.String()}, scheme.ParameterCodec).
|
||||
Do().
|
||||
Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.metricsFor(totalValue, groupResource, metricName, matchingObjectsRaw)
|
||||
}
|
||||
|
||||
func (p *incrementalTestingProvider) ListAllMetrics() []provider.MetricInfo {
|
||||
// TODO: maybe dynamically generate this?
|
||||
return []provider.MetricInfo{
|
||||
{
|
||||
GroupResource: schema.GroupResource{Group: "", Resource: "pods"},
|
||||
Metric: "packets-per-second",
|
||||
Namespaced: true,
|
||||
},
|
||||
{
|
||||
GroupResource: schema.GroupResource{Group: "", Resource: "services"},
|
||||
Metric: "connections-per-second",
|
||||
Namespaced: true,
|
||||
},
|
||||
{
|
||||
GroupResource: schema.GroupResource{Group: "", Resource: "namespaces"},
|
||||
Metric: "queue-length",
|
||||
Namespaced: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
108
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/sample-cmd/server/start.go
generated
vendored
Normal file
108
vendor/github.com/directxman12/custom-metrics-boilerplate/pkg/sample-cmd/server/start.go
generated
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
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 server
|
||||
|
||||
import (
|
||||
"io"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
|
||||
"github.com/directxman12/custom-metrics-boilerplate/pkg/cmd/server"
|
||||
"github.com/directxman12/custom-metrics-boilerplate/pkg/sample-cmd/provider"
|
||||
)
|
||||
|
||||
// NewCommandStartMaster provides a CLI handler for 'start master' command
|
||||
func NewCommandStartSampleAdapterServer(out, errOut io.Writer, stopCh <-chan struct{}) *cobra.Command {
|
||||
baseOpts := server.NewCustomMetricsAdapterServerOptions(out, errOut)
|
||||
o := SampleAdapterServerOptions{
|
||||
CustomMetricsAdapterServerOptions: baseOpts,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Short: "Launch the custom metrics API adapter server",
|
||||
Long: "Launch the custom metrics API adapter server",
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
if err := o.Complete(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.Validate(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.RunCustomMetricsAdapterServer(stopCh); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
o.SecureServing.AddFlags(flags)
|
||||
o.Authentication.AddFlags(flags)
|
||||
o.Authorization.AddFlags(flags)
|
||||
o.Features.AddFlags(flags)
|
||||
|
||||
flags.StringVar(&o.RemoteKubeConfigFile, "lister-kubeconfig", o.RemoteKubeConfigFile, ""+
|
||||
"kubeconfig file pointing at the 'core' kubernetes server with enough rights to list "+
|
||||
"any described objets")
|
||||
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o SampleAdapterServerOptions) RunCustomMetricsAdapterServer(stopCh <-chan struct{}) error {
|
||||
config, err := o.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var clientConfig *rest.Config
|
||||
if len(o.RemoteKubeConfigFile) > 0 {
|
||||
loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: o.RemoteKubeConfigFile}
|
||||
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
|
||||
|
||||
clientConfig, err = loader.ClientConfig()
|
||||
} else {
|
||||
clientConfig, err = rest.InClusterConfig()
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to construct lister client config to initialize provider: %v", err)
|
||||
}
|
||||
|
||||
client, err := coreclient.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to construct lister client to initialize provider: %v", err)
|
||||
}
|
||||
|
||||
cmProvider := provider.NewFakeProvider(client)
|
||||
|
||||
server, err := config.Complete().New(cmProvider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return server.GenericAPIServer.PrepareRun().Run(stopCh)
|
||||
}
|
||||
|
||||
type SampleAdapterServerOptions struct {
|
||||
*server.CustomMetricsAdapterServerOptions
|
||||
|
||||
// RemoteKubeConfigFile is the config used to list pods from the master API server
|
||||
RemoteKubeConfigFile string
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue