forked from kubernetes-sigs/custom-metrics-apiserver
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This includes the initial functionality for building custom metrics API server, with a sample implementation that can be used for testing purposes.
- Loading branch information
1 parent
5063232
commit 4616b24
Showing
11 changed files
with
856 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
*.swp | ||
*~ | ||
sample-main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
|
||
"k8s.io/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() // 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/* | ||
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 ( | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/apimachinery" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
"k8s.io/apiserver/pkg/registry/rest" | ||
genericapiserver "k8s.io/apiserver/pkg/server" | ||
genericapi "k8s.io/apiserver/pkg/endpoints" | ||
|
||
"k8s.io/metrics/pkg/apis/custom_metrics" | ||
metricstorage "k8s.io/custom-metrics-boilerplate/pkg/registry/custom_metrics" | ||
"k8s.io/custom-metrics-boilerplate/pkg/provider" | ||
) | ||
|
||
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.String(), | ||
Versions: []metav1.GroupVersionForDiscovery{groupVersion}, | ||
PreferredVersion: preferredVersionForDiscovery, | ||
} | ||
|
||
cmAPI := s.cmAPI(groupMeta, &groupMeta.GroupVersion) | ||
|
||
if err := cmAPI.InstallREST(s.GenericAPIServer.HandlerContainer.Container); err != nil { | ||
return err | ||
} | ||
|
||
path := genericapiserver.APIGroupPrefix+"/"+groupMeta.GroupVersion.Group | ||
s.GenericAPIServer.HandlerContainer.Add(genericapi.NewGroupWebService(s.GenericAPIServer.Serializer, path, apiGroup)) | ||
|
||
return nil | ||
} | ||
func (s *CustomMetricsAdapterServer) cmAPI(groupMeta *apimachinery.GroupMeta, groupVersion *schema.GroupVersion) *genericapi.APIGroupVersion { | ||
resourceStorage := metricstorage.NewREST(s.Provider) | ||
|
||
storage := map[string]rest.Storage{ | ||
// TODO: make this non-returning storage | ||
"*": resourceStorage, | ||
"*/*": resourceStorage, | ||
} | ||
|
||
return &genericapi.APIGroupVersion{ | ||
Root: genericapiserver.APIGroupPrefix, | ||
GroupVersion: *groupVersion, | ||
|
||
ParameterCodec: metav1.ParameterCodec, | ||
Serializer: Codecs, | ||
Creater: Scheme, | ||
Convertor: Scheme, | ||
UnsafeConvertor: runtime.UnsafeObjectConvertor(Scheme), | ||
Copier: Scheme, | ||
Typer: Scheme, | ||
SubresourceGroupVersionKind: nil, // TODO: do we need this? | ||
Linker: groupMeta.SelfLinker, | ||
Mapper: groupMeta.RESTMapper, | ||
Storage: storage, | ||
|
||
// TODO: Admit? | ||
Context: s.GenericAPIServer.RequestContextMapper(), | ||
MinRequestTimeout: s.GenericAPIServer.MinRequestTimeout(), | ||
OptionsExternalVersion: &schema.GroupVersion{Version: "v1"}, | ||
|
||
ResourceLister: provider.NewResourceLister(s.Provider), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
"k8s.io/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 | ||
if err := o.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", net.ParseIP("127.0.0.1")); err != nil { | ||
return nil, fmt.Errorf("error creating self-signed certificates: %v", err) | ||
} | ||
|
||
serverConfig := genericapiserver.NewConfig().WithSerializer(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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
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/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 | ||
} | ||
|
||
// 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 | ||
} |
Oops, something went wrong.