Skip to content

Commit

Permalink
Initial functionality commit
Browse files Browse the repository at this point in the history
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
DirectXMan12 committed Mar 9, 2017
1 parent 5063232 commit 4616b24
Show file tree
Hide file tree
Showing 11 changed files with 856 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.swp
*~
sample-main
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@ It includes the necessary boilerplate for setting up an implementation
(generic API server setup, registration of resources, etc), plus a sample
implementation backed by fake data.

## How to use this repository

In order to use this repository, you should vendor this repository at
`k8s.io/custom-metrics-boilerplate`, and implement the
`"k8s.io/custom-metrics-boilerplate/pkg/provider".CustomMetricsProvider`
interface. You can then pass this to the main setup functions.

The `pkg/cmd` package contains the building blocks of the actual API
server setup. You'll most likely want to wrap the existing options and
flags setup to add your own flags for configuring your provider.

A sample implementation of this can be found in the file `sample-main.go`
and `pkg/sample-cmd` directory. You'll want to have the equivalent files
in your project.

### A note on Godeps

The vendored directory currently contains a copy of `k8s.io/apiserver`
with a special patch applied to support the dynamic nature of the custom
metrics API server
([kubernetes/kubernetes#42845](https://github.com/kubernetes/kubernetes/pull/42845)).
Until this functionality lands in the main `k8s.io/apiserver` repository,
you should use the vendored copy here.

## Compatibility

The APIs in this repository follow the standard guarantees for Kubernetes
Expand Down
106 changes: 106 additions & 0 deletions pkg/apiserver/apiserver.go
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
}
94 changes: 94 additions & 0 deletions pkg/apiserver/cmapis.go
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),
}
}
87 changes: 87 additions & 0 deletions pkg/cmd/server/start.go
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
}
67 changes: 67 additions & 0 deletions pkg/provider/interfaces.go
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
}
Loading

0 comments on commit 4616b24

Please sign in to comment.