<commit_before>/*
Copyright 2014 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 ports

// In this file, we can see all default port of cluster.
// It's also a important documentation for us. So don't remove them easily.
const (
	// ProxyStatusPort is the default port for the proxy metrics server.
	// May be overridden by a flag at startup.
	ProxyStatusPort = 10249
	// KubeletPort is the default port for the kubelet server on each host machine.
	// May be overridden by a flag at startup.
	KubeletPort = 10250
	// InsecureKubeControllerManagerPort is the default port for the controller manager status server.
	// May be overridden by a flag at startup.
	// Deprecated: use the secure KubeControllerManagerPort instead.
	InsecureKubeControllerManagerPort = 10252
	// KubeletReadOnlyPort exposes basic read-only services from the kubelet.
	// May be overridden by a flag at startup.
	// This is necessary for heapster to collect monitoring stats from the kubelet
	// until heapster can transition to using the SSL endpoint.
	// TODO(roberthbailey): Remove this once we have a better solution for heapster.
	KubeletReadOnlyPort = 10255
	// ProxyHealthzPort is the default port for the proxy healthz server.
	// May be overridden by a flag at startup.
	ProxyHealthzPort = 10256
	// KubeControllerManagerPort is the default port for the controller manager status server.
	// May be overridden by a flag at startup.
	KubeControllerManagerPort = 10257
	// CloudControllerManagerPort is the default port for the cloud controller manager server.
	// This value may be overridden by a flag at startup.
	CloudControllerManagerPort = 10258
)
<commit_msg>[pkg/cluster/ports/ports]: fix minor syntax<commit_after>/*
Copyright 2014 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 ports

// In this file, we can see all default port of cluster.
// It's also an important documentation for us. So don't remove them easily.
const (
	// ProxyStatusPort is the default port for the proxy metrics server.
	// May be overridden by a flag at startup.
	ProxyStatusPort = 10249
	// KubeletPort is the default port for the kubelet server on each host machine.
	// May be overridden by a flag at startup.
	KubeletPort = 10250
	// InsecureKubeControllerManagerPort is the default port for the controller manager status server.
	// May be overridden by a flag at startup.
	// Deprecated: use the secure KubeControllerManagerPort instead.
	InsecureKubeControllerManagerPort = 10252
	// KubeletReadOnlyPort exposes basic read-only services from the kubelet.
	// May be overridden by a flag at startup.
	// This is necessary for heapster to collect monitoring stats from the kubelet
	// until heapster can transition to using the SSL endpoint.
	// TODO(roberthbailey): Remove this once we have a better solution for heapster.
	KubeletReadOnlyPort = 10255
	// ProxyHealthzPort is the default port for the proxy healthz server.
	// May be overridden by a flag at startup.
	ProxyHealthzPort = 10256
	// KubeControllerManagerPort is the default port for the controller manager status server.
	// May be overridden by a flag at startup.
	KubeControllerManagerPort = 10257
	// CloudControllerManagerPort is the default port for the cloud controller manager server.
	// This value may be overridden by a flag at startup.
	CloudControllerManagerPort = 10258
)
<|endoftext|>


<commit_before>/*
Copyright 2016 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 dnsprovider

import (
	"fmt"
	"io"
	"os"
	"sync"

	"github.com/golang/glog"
)

// Factory is a function that returns a dnsprovider.Interface.
// The config parameter provides an io.Reader handler to the factory in
// order to load specific configurations. If no configuration is provided
// the parameter is nil.
type Factory func(config io.Reader) (Interface, error)

// All registered dns providers.
var providersMutex sync.Mutex
var providers = make(map[string]Factory)

// RegisterDnsProvider registers a dnsprovider.Factory by name.  This
// is expected to happen during startup.
func RegisterDnsProvider(name string, cloud Factory) {
	providersMutex.Lock()
	defer providersMutex.Unlock()
	if _, found := providers[name]; found {
		glog.Fatalf("DNS provider %q was registered twice", name)
	}
	glog.V(1).Infof("Registered DNS provider %q", name)
	providers[name] = cloud
}

// GetDnsProvider creates an instance of the named DNS provider, or nil if
// the name is not known.  The error return is only used if the named provider
// was known but failed to initialize. The config parameter specifies the
// io.Reader handler of the configuration file for the DNS provider, or nil
// for no configuation.
func GetDnsProvider(name string, config io.Reader) (Interface, error) {
	providersMutex.Lock()
	defer providersMutex.Unlock()
	f, found := providers[name]
	if !found {
		return nil, nil
	}
	return f(config)
}

// Returns a list of registered dns providers.
func RegisteredDnsProviders() []string {
	registeredProviders := make([]string, len(providers))
	i := 0
	for provider := range providers {
		registeredProviders[i] = provider
		i = i + 1
	}
	return registeredProviders
}

// InitDnsProvider creates an instance of the named DNS provider.
func InitDnsProvider(name string, configFilePath string) (Interface, error) {
	var dns Interface
	var err error

	if name == "" {
		glog.Info("No DNS provider specified.")
		return nil, nil
	}

	if configFilePath != "" {
		var config *os.File
		config, err = os.Open(configFilePath)
		if err != nil {
			return nil, fmt.Errorf("Couldn't open DNS provider configuration %s: %#v", configFilePath, err)
		}

		defer config.Close()
		dns, err = GetDnsProvider(name, config)
	} else {
		// Pass explicit nil so plugins can actually check for nil. See
		// "Why is my nil error value not equal to nil?" in golang.org/doc/faq.
		dns, err = GetDnsProvider(name, nil)
	}

	if err != nil {
		return nil, fmt.Errorf("could not init DNS provider %q: %v", name, err)
	}
	if dns == nil {
		return nil, fmt.Errorf("unknown DNS provider %q", name)
	}

	return dns, nil
}
<commit_msg>fix typo on federation/pkg/dnsprovider/plugins.go<commit_after>/*
Copyright 2016 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 dnsprovider

import (
	"fmt"
	"io"
	"os"
	"sync"

	"github.com/golang/glog"
)

// Factory is a function that returns a dnsprovider.Interface.
// The config parameter provides an io.Reader handler to the factory in
// order to load specific configurations. If no configuration is provided
// the parameter is nil.
type Factory func(config io.Reader) (Interface, error)

// All registered dns providers.
var providersMutex sync.Mutex
var providers = make(map[string]Factory)

// RegisterDnsProvider registers a dnsprovider.Factory by name.  This
// is expected to happen during startup.
func RegisterDnsProvider(name string, cloud Factory) {
	providersMutex.Lock()
	defer providersMutex.Unlock()
	if _, found := providers[name]; found {
		glog.Fatalf("DNS provider %q was registered twice", name)
	}
	glog.V(1).Infof("Registered DNS provider %q", name)
	providers[name] = cloud
}

// GetDnsProvider creates an instance of the named DNS provider, or nil if
// the name is not known.  The error return is only used if the named provider
// was known but failed to initialize. The config parameter specifies the
// io.Reader handler of the configuration file for the DNS provider, or nil
// for no configuration.
func GetDnsProvider(name string, config io.Reader) (Interface, error) {
	providersMutex.Lock()
	defer providersMutex.Unlock()
	f, found := providers[name]
	if !found {
		return nil, nil
	}
	return f(config)
}

// Returns a list of registered dns providers.
func RegisteredDnsProviders() []string {
	registeredProviders := make([]string, len(providers))
	i := 0
	for provider := range providers {
		registeredProviders[i] = provider
		i = i + 1
	}
	return registeredProviders
}

// InitDnsProvider creates an instance of the named DNS provider.
func InitDnsProvider(name string, configFilePath string) (Interface, error) {
	var dns Interface
	var err error

	if name == "" {
		glog.Info("No DNS provider specified.")
		return nil, nil
	}

	if configFilePath != "" {
		var config *os.File
		config, err = os.Open(configFilePath)
		if err != nil {
			return nil, fmt.Errorf("Couldn't open DNS provider configuration %s: %#v", configFilePath, err)
		}

		defer config.Close()
		dns, err = GetDnsProvider(name, config)
	} else {
		// Pass explicit nil so plugins can actually check for nil. See
		// "Why is my nil error value not equal to nil?" in golang.org/doc/faq.
		dns, err = GetDnsProvider(name, nil)
	}

	if err != nil {
		return nil, fmt.Errorf("could not init DNS provider %q: %v", name, err)
	}
	if dns == nil {
		return nil, fmt.Errorf("unknown DNS provider %q", name)
	}

	return dns, nil
}
<|endoftext|>


<commit_before>package middleware

import (
	"context"
	"fmt"
	"sync"
	"testing"

	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/tracing"
	"github.com/go-kit/kit/endpoint"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func testFanoutNilSpanner(t *testing.T) {
	var (
		assert = assert.New(t)
		dummy  = func(context.Context, interface{}) (interface{}, error) {
			assert.Fail("The endpoint should not have been called")
			return nil, nil
		}
	)

	assert.Panics(func() {
		Fanout(nil, map[string]endpoint.Endpoint{"test": dummy})
	})
}

func testFanoutNoConfiguredEndpoints(t *testing.T) {
	assert := assert.New(t)
	for _, empty := range []map[string]endpoint.Endpoint{nil, {}} {
		assert.Panics(func() {
			Fanout(tracing.NewSpanner(), empty)
		})
	}
}

func testFanoutSuccessFirst(t *testing.T, serviceCount int) {
	var (
		require             = require.New(t)
		assert              = assert.New(t)
		expectedCtx, cancel = context.WithCancel(
			logging.WithLogger(context.Background(), logging.NewTestLogger(nil, t)),
		)

		expectedRequest  = "expectedRequest"
		expectedResponse = new(tracing.NopMergeable)

		endpoints   = make(map[string]endpoint.Endpoint, serviceCount)
		success     = make(chan string, 1)
		failureGate = make(chan struct{})
	)

	for i := 0; i < serviceCount; i++ {
		if i == 0 {
			endpoints["success"] = func(ctx context.Context, request interface{}) (interface{}, error) {
				assert.Equal(expectedCtx, ctx)
				assert.Equal(expectedRequest, request)
				success <- "success"
				return expectedResponse, nil
			}
		} else {
			endpoints[fmt.Sprintf("failure#%d", i)] = func(ctx context.Context, request interface{}) (interface{}, error) {
				assert.Equal(expectedCtx, ctx)
				assert.Equal(expectedRequest, request)
				<-failureGate
				return nil, fmt.Errorf("expected failure #%d", i)
			}
		}
	}

	defer cancel()
	fanout := Fanout(tracing.NewSpanner(), endpoints)
	require.NotNil(fanout)

	response, err := fanout(expectedCtx, expectedRequest)
	assert.NoError(err)
	require.NotNil(response)
	assert.Equal("success", <-success)

	close(failureGate)
	spans := response.(tracing.Spanned).Spans()
	assert.Len(spans, 1)
	assert.Equal("success", spans[0].Name())
	assert.NoError(spans[0].Error())
}

func testFanoutSuccessLast(t *testing.T, serviceCount int) {
	var (
		require             = require.New(t)
		assert              = assert.New(t)
		expectedCtx, cancel = context.WithCancel(
			logging.WithLogger(context.Background(), logging.NewTestLogger(nil, t)),
		)

		expectedRequest  = "expectedRequest"
		expectedResponse = new(tracing.NopMergeable)

		endpoints    = make(map[string]endpoint.Endpoint, serviceCount)
		success      = make(chan string, 1)
		successGate  = make(chan struct{})
		failuresDone = new(sync.WaitGroup)
	)

	failuresDone.Add(serviceCount - 1)
	for i := 0; i < serviceCount; i++ {
		if i == 0 {
			endpoints["success"] = func(ctx context.Context, request interface{}) (interface{}, error) {
				assert.Equal(expectedCtx, ctx)
				assert.Equal(expectedRequest, request)
				<-successGate
				success <- "success"
				return expectedResponse, nil
			}
		} else {
			endpoints[fmt.Sprintf("failure#%d", i)] = func(ctx context.Context, request interface{}) (interface{}, error) {
				defer failuresDone.Done()
				assert.Equal(expectedCtx, ctx)
				assert.Equal(expectedRequest, request)
				return nil, fmt.Errorf("expected failure #%d", i)
			}
		}
	}

	defer cancel()
	fanout := Fanout(tracing.NewSpanner(), endpoints)
	require.NotNil(fanout)

	// to force the success to be last, we spawn a goroutine to wait until
	// all failures are done followed by closing the success gate.
	go func() {
		failuresDone.Wait()
		close(successGate)
	}()

	response, err := fanout(expectedCtx, expectedRequest)
	assert.NoError(err)
	require.NotNil(response)
	assert.Equal("success", <-success)

	// because race detection and coverage can mess with the timings of select statements,
	// we have to allow a margin of error
	spans := response.(tracing.Spanned).Spans()
	assert.True(0 < len(spans) && len(spans) <= serviceCount)

	successSpanFound := false
	for _, s := range spans {
		if s.Name() == "success" {
			assert.NoError(s.Error())
			successSpanFound = true
		} else {
			assert.Error(s.Error())
		}
	}

	assert.True(successSpanFound)
}

func testFanoutTimeout(t *testing.T, serviceCount int) {
	var (
		require             = require.New(t)
		assert              = assert.New(t)
		expectedCtx, cancel = context.WithCancel(
			logging.WithLogger(context.Background(), logging.NewTestLogger(nil, t)),
		)

		expectedRequest  = "expectedRequest"
		expectedResponse = new(tracing.NopMergeable)

		endpoints        = make(map[string]endpoint.Endpoint, serviceCount)
		endpointGate     = make(chan struct{})
		endpointsWaiting = new(sync.WaitGroup)
	)

	endpointsWaiting.Add(serviceCount)
	for i := 0; i < serviceCount; i++ {
		endpoints[fmt.Sprintf("slow#%d", i)] = func(ctx context.Context, request interface{}) (interface{}, error) {
			assert.Equal(expectedCtx, ctx)
			assert.Equal(expectedRequest, request)
			endpointsWaiting.Done()
			<-endpointGate
			return expectedResponse, nil
		}
	}

	// release the endpoint goroutines when this test exits, to clean things up
	defer close(endpointGate)

	fanout := Fanout(tracing.NewSpanner(), endpoints)
	require.NotNil(fanout)

	// in order to force a timeout in the select, we spawn a goroutine that waits until
	// all endpoints are blocked, then we cancel the context.
	go func() {
		endpointsWaiting.Wait()
		cancel()
	}()

	response, err := fanout(expectedCtx, expectedRequest)
	assert.Error(err)
	assert.Nil(response)

	spanError := err.(tracing.SpanError)
	assert.Equal(context.Canceled, spanError.Err())
	assert.Equal(context.Canceled.Error(), spanError.Error())
	assert.Empty(spanError.Spans())
}

func testFanoutAllEndpointsFail(t *testing.T, serviceCount int) {
	var (
		require             = require.New(t)
		assert              = assert.New(t)
		expectedCtx, cancel = context.WithCancel(
			logging.WithLogger(context.Background(), logging.NewTestLogger(nil, t)),
		)

		expectedRequest   = "expectedRequest"
		expectedLastError = fmt.Errorf("last error")

		endpoints          = make(map[string]endpoint.Endpoint, serviceCount)
		lastEndpointGate   = make(chan struct{})
		otherEndpointsDone = new(sync.WaitGroup)
	)

	otherEndpointsDone.Add(serviceCount - 1)
	for i := 0; i < serviceCount; i++ {
		if i == 0 {
			endpoints[fmt.Sprintf("failure#%d", i)] = func(ctx context.Context, request interface{}) (interface{}, error) {
				assert.Equal(expectedCtx, ctx)
				assert.Equal(expectedRequest, request)
				<-lastEndpointGate
				return nil, expectedLastError
			}
		} else {
			endpoints[fmt.Sprintf("failure#%d", i)] = func(ctx context.Context, request interface{}) (interface{}, error) {
				defer otherEndpointsDone.Done()
				assert.Equal(expectedCtx, ctx)
				assert.Equal(expectedRequest, request)
				return nil, fmt.Errorf("failure#%d", i)
			}
		}
	}

	defer cancel()
	fanout := Fanout(tracing.NewSpanner(), endpoints)
	require.NotNil(fanout)

	// in order to force a known endpoint to be last, we spawn a goroutine and wait
	// for the other, non-last endpoints to finish.  Then, we close the last endpoint gate.
	go func() {
		otherEndpointsDone.Wait()
		close(lastEndpointGate)
	}()

	response, err := fanout(expectedCtx, expectedRequest)
	assert.Error(err)
	assert.Nil(response)

	spanError := err.(tracing.SpanError)
	assert.Equal(expectedLastError, spanError.Err())
	assert.Equal(expectedLastError.Error(), spanError.Error())
	assert.Len(spanError.Spans(), serviceCount)
	for _, s := range spanError.Spans() {
		assert.Error(s.Error())
	}
}

func TestFanout(t *testing.T) {
	t.Run("NoConfiguredEndpoints", testFanoutNoConfiguredEndpoints)
	t.Run("NilSpanner", testFanoutNilSpanner)

	t.Run("SuccessFirst", func(t *testing.T) {
		for c := 1; c <= 5; c++ {
			t.Run(fmt.Sprintf("EndpointCount=%d", c), func(t *testing.T) {
				testFanoutSuccessFirst(t, c)
			})
		}
	})

	t.Run("SuccessLast", func(t *testing.T) {
		for c := 1; c <= 5; c++ {
			t.Run(fmt.Sprintf("EndpointCount=%d", c), func(t *testing.T) {
				testFanoutSuccessLast(t, c)
			})
		}
	})

	t.Run("Timeout", func(t *testing.T) {
		for c := 1; c <= 5; c++ {
			t.Run(fmt.Sprintf("EndpointCount=%d", c), func(t *testing.T) {
				testFanoutTimeout(t, c)
			})
		}
	})

	t.Run("AllEndpointsFail", func(t *testing.T) {
		for c := 1; c <= 5; c++ {
			t.Run(fmt.Sprintf("EndpointCount=%d", c), func(t *testing.T) {
				testFanoutAllEndpointsFail(t, c)
			})
		}
	})
}
<commit_msg>Attempt to fix unit test.<commit_after>package middleware

import (
	"context"
	"fmt"
	"sync"
	"testing"

	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/tracing"
	"github.com/go-kit/kit/endpoint"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func testFanoutNilSpanner(t *testing.T) {
	var (
		assert = assert.New(t)
		dummy  = func(context.Context, interface{}) (interface{}, error) {
			assert.Fail("The endpoint should not have been called")
			return nil, nil
		}
	)

	assert.Panics(func() {
		Fanout(nil, map[string]endpoint.Endpoint{"test": dummy})
	})
}

func testFanoutNoConfiguredEndpoints(t *testing.T) {
	assert := assert.New(t)
	for _, empty := range []map[string]endpoint.Endpoint{nil, {}} {
		assert.Panics(func() {
			Fanout(tracing.NewSpanner(), empty)
		})
	}
}

func testFanoutSuccessFirst(t *testing.T, serviceCount int) {
	var (
		require             = require.New(t)
		assert              = assert.New(t)
		expectedCtx, cancel = context.WithCancel(
			logging.WithLogger(context.Background(), logging.NewTestLogger(nil, t)),
		)

		expectedRequest  = "expectedRequest"
		expectedResponse = new(tracing.NopMergeable)

		endpoints   = make(map[string]endpoint.Endpoint, serviceCount)
		success     = make(chan string, 1)
		failureGate = make(chan struct{})
	)

	for i := 0; i < serviceCount; i++ {
		if i == 0 {
			endpoints["success"] = func(ctx context.Context, request interface{}) (interface{}, error) {
				assert.Equal(expectedCtx, ctx)
				assert.Equal(expectedRequest, request)
				success <- "success"
				return expectedResponse, nil
			}
		} else {
			endpoints[fmt.Sprintf("failure#%d", i)] = func(ctx context.Context, request interface{}) (interface{}, error) {
				assert.Equal(expectedCtx, ctx)
				assert.Equal(expectedRequest, request)
				<-failureGate
				return nil, fmt.Errorf("expected failure #%d", i)
			}
		}
	}

	defer cancel()
	fanout := Fanout(tracing.NewSpanner(), endpoints)
	require.NotNil(fanout)

	response, err := fanout(expectedCtx, expectedRequest)
	assert.NoError(err)
	require.NotNil(response)
	assert.Equal("success", <-success)

	close(failureGate)
	spans := response.(tracing.Spanned).Spans()
	assert.Len(spans, 1)
	assert.Equal("success", spans[0].Name())
	assert.NoError(spans[0].Error())
}

func testFanoutSuccessLast(t *testing.T, serviceCount int) {
	var (
		require             = require.New(t)
		assert              = assert.New(t)
		expectedCtx, cancel = context.WithCancel(
			logging.WithLogger(context.Background(), logging.NewTestLogger(nil, t)),
		)

		expectedRequest  = "expectedRequest"
		expectedResponse = new(tracing.NopMergeable)

		endpoints    = make(map[string]endpoint.Endpoint, serviceCount)
		success      = make(chan string, 1)
		successGate  = make(chan struct{})
		failuresDone = new(sync.WaitGroup)
	)

	failuresDone.Add(serviceCount - 1)
	for i := 0; i < serviceCount; i++ {
		if i == 0 {
			endpoints["success"] = func(ctx context.Context, request interface{}) (interface{}, error) {
				assert.Equal(expectedCtx, ctx)
				assert.Equal(expectedRequest, request)
				<-successGate
				success <- "success"
				return expectedResponse, nil
			}
		} else {
			endpoints[fmt.Sprintf("failure#%d", i)] = func(ctx context.Context, request interface{}) (interface{}, error) {
				defer failuresDone.Done()
				assert.Equal(expectedCtx, ctx)
				assert.Equal(expectedRequest, request)
				return nil, fmt.Errorf("expected failure #%d", i)
			}
		}
	}

	defer cancel()
	fanout := Fanout(tracing.NewSpanner(), endpoints)
	require.NotNil(fanout)

	// to force the success to be last, we spawn a goroutine to wait until
	// all failures are done followed by closing the success gate.
	go func() {
		failuresDone.Wait()
		close(successGate)
	}()

	response, err := fanout(expectedCtx, expectedRequest)
	assert.NoError(err)
	require.NotNil(response)
	assert.Equal("success", <-success)

	// because race detection and coverage can mess with the timings of select statements,
	// we have to allow a margin of error
	spans := response.(tracing.Spanned).Spans()
	assert.True(0 < len(spans) && len(spans) <= serviceCount)

	successSpanFound := false
	for _, s := range spans {
		if s.Name() == "success" {
			assert.NoError(s.Error())
			successSpanFound = true
		} else {
			assert.Error(s.Error())
		}
	}

	assert.True(successSpanFound)
}

func testFanoutTimeout(t *testing.T, serviceCount int) {
	var (
		require             = require.New(t)
		assert              = assert.New(t)
		expectedCtx, cancel = context.WithCancel(
			logging.WithLogger(context.Background(), logging.NewTestLogger(nil, t)),
		)

		expectedRequest  = "expectedRequest"
		expectedResponse = new(tracing.NopMergeable)

		endpoints        = make(map[string]endpoint.Endpoint, serviceCount)
		endpointGate     = make(chan struct{})
		endpointsWaiting = new(sync.WaitGroup)
	)

	endpointsWaiting.Add(serviceCount)
	for i := 0; i < serviceCount; i++ {
		endpoints[fmt.Sprintf("slow#%d", i)] = func(ctx context.Context, request interface{}) (interface{}, error) {
			assert.Equal(expectedCtx, ctx)
			assert.Equal(expectedRequest, request)
			endpointsWaiting.Done()
			<-endpointGate
			return expectedResponse, nil
		}
	}

	// release the endpoint goroutines when this test exits, to clean things up
	defer close(endpointGate)

	fanout := Fanout(tracing.NewSpanner(), endpoints)
	require.NotNil(fanout)

	// in order to force a timeout in the select, we spawn a goroutine that waits until
	// all endpoints are blocked, then we cancel the context.
	go func() {
		endpointsWaiting.Wait()
		cancel()
	}()

	response, err := fanout(expectedCtx, expectedRequest)
	assert.Error(err)
	assert.Nil(response)

	spanError := err.(tracing.SpanError)
	assert.Equal(context.Canceled, spanError.Err())
	assert.Equal(context.Canceled.Error(), spanError.Error())
	assert.Empty(spanError.Spans())
}

func testFanoutAllEndpointsFail(t *testing.T, serviceCount int) {
	var (
		require             = require.New(t)
		assert              = assert.New(t)
		expectedCtx, cancel = context.WithCancel(
			logging.WithLogger(context.Background(), logging.NewTestLogger(nil, t)),
		)

		expectedRequest   = "expectedRequest"
		expectedLastError = fmt.Errorf("last error")

		endpoints          = make(map[string]endpoint.Endpoint, serviceCount)
		lastEndpointGate   = make(chan struct{})
		otherEndpointsDone = new(sync.WaitGroup)
	)

	otherEndpointsDone.Add(serviceCount - 1)
	for i := 0; i < serviceCount; i++ {
		if i == 0 {
			endpoints[fmt.Sprintf("failure#%d", i)] = func(ctx context.Context, request interface{}) (interface{}, error) {
				assert.Equal(expectedCtx, ctx)
				assert.Equal(expectedRequest, request)
				<-lastEndpointGate
				return nil, expectedLastError
			}
		} else {
			endpoints[fmt.Sprintf("failure#%d", i)] = func(index int) endpoint.Endpoint {
				return func(ctx context.Context, request interface{}) (interface{}, error) {
					defer otherEndpointsDone.Done()
					assert.Equal(expectedCtx, ctx)
					assert.Equal(expectedRequest, request)
					return nil, fmt.Errorf("failure#%d", index)
				}
			}(i)
		}
	}

	defer cancel()
	fanout := Fanout(tracing.NewSpanner(), endpoints)
	require.NotNil(fanout)

	// in order to force a known endpoint to be last, we spawn a goroutine and wait
	// for the other, non-last endpoints to finish.  Then, we close the last endpoint gate.
	go func() {
		otherEndpointsDone.Wait()
		close(lastEndpointGate)
	}()

	response, err := fanout(expectedCtx, expectedRequest)
	assert.Error(err)
	assert.Nil(response)

	spanError := err.(tracing.SpanError)
	assert.Equal(expectedLastError, spanError.Err())
	assert.Equal(expectedLastError.Error(), spanError.Error())
	assert.Len(spanError.Spans(), serviceCount)
	for _, s := range spanError.Spans() {
		assert.Error(s.Error())
	}
}

func TestFanout(t *testing.T) {
	t.Run("NoConfiguredEndpoints", testFanoutNoConfiguredEndpoints)
	t.Run("NilSpanner", testFanoutNilSpanner)

	t.Run("SuccessFirst", func(t *testing.T) {
		for c := 1; c <= 5; c++ {
			t.Run(fmt.Sprintf("EndpointCount=%d", c), func(t *testing.T) {
				testFanoutSuccessFirst(t, c)
			})
		}
	})

	t.Run("SuccessLast", func(t *testing.T) {
		for c := 1; c <= 5; c++ {
			t.Run(fmt.Sprintf("EndpointCount=%d", c), func(t *testing.T) {
				testFanoutSuccessLast(t, c)
			})
		}
	})

	t.Run("Timeout", func(t *testing.T) {
		for c := 1; c <= 5; c++ {
			t.Run(fmt.Sprintf("EndpointCount=%d", c), func(t *testing.T) {
				testFanoutTimeout(t, c)
			})
		}
	})

	t.Run("AllEndpointsFail", func(t *testing.T) {
		for c := 1; c <= 5; c++ {
			t.Run(fmt.Sprintf("EndpointCount=%d", c), func(t *testing.T) {
				testFanoutAllEndpointsFail(t, c)
			})
		}
	})
}
<|endoftext|>


<commit_before>package reader

import (
	"debug/dwarf"
	"errors"
	"fmt"

	"github.com/derekparker/delve/pkg/dwarf/op"
)

type Reader struct {
	*dwarf.Reader
	depth int
}

// New returns a reader for the specified dwarf data.
func New(data *dwarf.Data) *Reader {
	return &Reader{data.Reader(), 0}
}

// Seek moves the reader to an arbitrary offset.
func (reader *Reader) Seek(off dwarf.Offset) {
	reader.depth = 0
	reader.Reader.Seek(off)
}

// SeekToEntry moves the reader to an arbitrary entry.
func (reader *Reader) SeekToEntry(entry *dwarf.Entry) error {
	reader.Seek(entry.Offset)
	// Consume the current entry so .Next works as intended
	_, err := reader.Next()
	return err
}

// SeekToFunctionEntry moves the reader to the function that includes the
// specified program counter.
func (reader *Reader) SeekToFunction(pc RelAddr) (*dwarf.Entry, error) {
	reader.Seek(0)
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		if entry.Tag != dwarf.TagSubprogram {
			continue
		}

		lowpc, ok := entry.Val(dwarf.AttrLowpc).(uint64)
		if !ok {
			continue
		}

		highpc, ok := entry.Val(dwarf.AttrHighpc).(uint64)
		if !ok {
			continue
		}

		if lowpc <= uint64(pc) && highpc > uint64(pc) {
			return entry, nil
		}
	}

	return nil, fmt.Errorf("unable to find function context")
}

// Returns the address for the named entry.
func (reader *Reader) AddrFor(name string, staticBase uint64) (uint64, error) {
	entry, err := reader.FindEntryNamed(name, false)
	if err != nil {
		return 0, err
	}
	instructions, ok := entry.Val(dwarf.AttrLocation).([]byte)
	if !ok {
		return 0, fmt.Errorf("type assertion failed")
	}
	addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{StaticBase: staticBase}, instructions)
	if err != nil {
		return 0, err
	}
	return uint64(addr), nil
}

// Returns the address for the named struct member. Expects the reader to be at the parent entry
// or one of the parents children, thus does not seek to parent by itself.
func (reader *Reader) AddrForMember(member string, initialInstructions []byte) (uint64, error) {
	for {
		entry, err := reader.NextMemberVariable()
		if err != nil {
			return 0, err
		}
		if entry == nil {
			return 0, fmt.Errorf("nil entry for member named %s", member)
		}
		name, ok := entry.Val(dwarf.AttrName).(string)
		if !ok || name != member {
			continue
		}
		instructions, ok := entry.Val(dwarf.AttrDataMemberLoc).([]byte)
		if !ok {
			continue
		}
		addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{}, append(initialInstructions, instructions...))
		return uint64(addr), err
	}
}

var TypeNotFoundErr = errors.New("no type entry found, use 'types' for a list of valid types")

// SeekToType moves the reader to the type specified by the entry,
// optionally resolving typedefs and pointer types. If the reader is set
// to a struct type the NextMemberVariable call can be used to walk all member data.
func (reader *Reader) SeekToType(entry *dwarf.Entry, resolveTypedefs bool, resolvePointerTypes bool) (*dwarf.Entry, error) {
	offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset)
	if !ok {
		return nil, fmt.Errorf("entry does not have a type attribute")
	}

	// Seek to the first type offset
	reader.Seek(offset)

	// Walk the types to the base
	for typeEntry, err := reader.Next(); typeEntry != nil; typeEntry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		if typeEntry.Tag == dwarf.TagTypedef && !resolveTypedefs {
			return typeEntry, nil
		}

		if typeEntry.Tag == dwarf.TagPointerType && !resolvePointerTypes {
			return typeEntry, nil
		}

		offset, ok = typeEntry.Val(dwarf.AttrType).(dwarf.Offset)
		if !ok {
			return typeEntry, nil
		}

		reader.Seek(offset)
	}

	return nil, TypeNotFoundErr
}

func (reader *Reader) NextType() (*dwarf.Entry, error) {
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		switch entry.Tag {
		case dwarf.TagArrayType, dwarf.TagBaseType, dwarf.TagClassType, dwarf.TagStructType, dwarf.TagUnionType, dwarf.TagConstType, dwarf.TagVolatileType, dwarf.TagRestrictType, dwarf.TagEnumerationType, dwarf.TagPointerType, dwarf.TagSubroutineType, dwarf.TagTypedef, dwarf.TagUnspecifiedType:
			return entry, nil
		}
	}

	return nil, nil
}

// SeekToTypeNamed moves the reader to the type specified by the name.
// If the reader is set to a struct type the NextMemberVariable call
// can be used to walk all member data.
func (reader *Reader) SeekToTypeNamed(name string) (*dwarf.Entry, error) {
	// Walk the types to the base
	for entry, err := reader.NextType(); entry != nil; entry, err = reader.NextType() {
		if err != nil {
			return nil, err
		}

		n, ok := entry.Val(dwarf.AttrName).(string)
		if !ok {
			continue
		}

		if n == name {
			return entry, nil
		}
	}

	return nil, TypeNotFoundErr
}

// Finds the entry for 'name'.
func (reader *Reader) FindEntryNamed(name string, member bool) (*dwarf.Entry, error) {
	depth := 1
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		if entry.Children {
			depth++
		}

		if entry.Tag == 0 {
			depth--
			if depth <= 0 {
				return nil, fmt.Errorf("could not find symbol value for %s", name)
			}
		}

		if member {
			if entry.Tag != dwarf.TagMember {
				continue
			}
		} else {
			if entry.Tag != dwarf.TagVariable && entry.Tag != dwarf.TagFormalParameter && entry.Tag != dwarf.TagStructType {
				continue
			}
		}

		n, ok := entry.Val(dwarf.AttrName).(string)
		if !ok || n != name {
			continue
		}
		return entry, nil
	}
	return nil, fmt.Errorf("could not find symbol value for %s", name)
}

func (reader *Reader) InstructionsForEntryNamed(name string, member bool) ([]byte, error) {
	entry, err := reader.FindEntryNamed(name, member)
	if err != nil {
		return nil, err
	}
	var attr dwarf.Attr
	if member {
		attr = dwarf.AttrDataMemberLoc
	} else {
		attr = dwarf.AttrLocation
	}
	instr, ok := entry.Val(attr).([]byte)
	if !ok {
		return nil, errors.New("invalid typecast for Dwarf instructions")
	}
	return instr, nil
}

func (reader *Reader) InstructionsForEntry(entry *dwarf.Entry) ([]byte, error) {
	if entry.Tag == dwarf.TagMember {
		instructions, ok := entry.Val(dwarf.AttrDataMemberLoc).([]byte)
		if !ok {
			return nil, fmt.Errorf("member data has no data member location attribute")
		}
		// clone slice to prevent stomping on the dwarf data
		return append([]byte{}, instructions...), nil
	}

	// non-member
	instructions, ok := entry.Val(dwarf.AttrLocation).([]byte)
	if !ok {
		return nil, fmt.Errorf("entry has no location attribute")
	}

	// clone slice to prevent stomping on the dwarf data
	return append([]byte{}, instructions...), nil
}

// NextMemberVariable moves the reader to the next debug entry that describes a member variable and returns the entry.
func (reader *Reader) NextMemberVariable() (*dwarf.Entry, error) {
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		// All member variables will be at the same depth
		reader.SkipChildren()

		// End of the current depth
		if entry.Tag == 0 {
			break
		}

		if entry.Tag == dwarf.TagMember {
			return entry, nil
		}
	}

	// No more items
	return nil, nil
}

// NextPackageVariable moves the reader to the next debug entry that describes a package variable.
// Any TagVariable entry that is not inside a sub prgram entry and is marked external is considered a package variable.
func (reader *Reader) NextPackageVariable() (*dwarf.Entry, error) {
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		if entry.Tag == dwarf.TagVariable {
			ext, ok := entry.Val(dwarf.AttrExternal).(bool)
			if ok && ext {
				return entry, nil
			}
		}

		// Ignore everything inside sub programs
		if entry.Tag == dwarf.TagSubprogram {
			reader.SkipChildren()
		}
	}

	// No more items
	return nil, nil
}

func (reader *Reader) NextCompileUnit() (*dwarf.Entry, error) {
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		if entry.Tag == dwarf.TagCompileUnit {
			return entry, nil
		}
	}

	return nil, nil
}

// Entry represents a debug_info entry.
// When calling Val, if the entry does not have the specified attribute, the
// entry specified by DW_AT_abstract_origin will be searched recursively.
type Entry interface {
	Val(dwarf.Attr) interface{}
}

type compositeEntry []*dwarf.Entry

func (ce compositeEntry) Val(attr dwarf.Attr) interface{} {
	for _, e := range ce {
		if r := e.Val(attr); r != nil {
			return r
		}
	}
	return nil
}

// LoadAbstractOrigin loads the entry corresponding to the
// DW_AT_abstract_origin of entry and returns a combination of entry and its
// abstract origin.
func LoadAbstractOrigin(entry *dwarf.Entry, aordr *dwarf.Reader) (Entry, dwarf.Offset) {
	ao, ok := entry.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
	if !ok {
		return entry, entry.Offset
	}

	r := []*dwarf.Entry{entry}

	for {
		aordr.Seek(ao)
		e, _ := aordr.Next()
		if e == nil {
			break
		}
		r = append(r, e)

		ao, ok = e.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
		if !ok {
			break
		}
	}

	return compositeEntry(r), entry.Offset
}

// InlineStackReader provides a way to read the stack of inlined calls at a
// specified PC address.
type InlineStackReader struct {
	dwarf  *dwarf.Data
	reader *dwarf.Reader
	entry  *dwarf.Entry
	depth  int
	pc     uint64
	err    error
}

// InlineStack returns an InlineStackReader for the specified function and
// PC address.
// If pc is 0 then all inlined calls will be returned.
func InlineStack(dwarf *dwarf.Data, fnoff dwarf.Offset, pc RelAddr) *InlineStackReader {
	reader := dwarf.Reader()
	reader.Seek(fnoff)
	return &InlineStackReader{dwarf: dwarf, reader: reader, entry: nil, depth: 0, pc: uint64(pc)}
}

// Next reads next inlined call in the stack, returns false if there aren't any.
func (irdr *InlineStackReader) Next() bool {
	if irdr.err != nil {
		return false
	}

	for {
		irdr.entry, irdr.err = irdr.reader.Next()
		if irdr.entry == nil || irdr.err != nil {
			return false
		}

		switch irdr.entry.Tag {
		case 0:
			irdr.depth--
			if irdr.depth == 0 {
				return false
			}

		case dwarf.TagLexDwarfBlock, dwarf.TagSubprogram, dwarf.TagInlinedSubroutine:
			var recur bool
			if irdr.pc != 0 {
				recur, irdr.err = entryRangesContains(irdr.dwarf, irdr.entry, irdr.pc)
			} else {
				recur = true
			}
			if recur {
				irdr.depth++
				if irdr.entry.Tag == dwarf.TagInlinedSubroutine {
					return true
				}
			} else {
				if irdr.depth == 0 {
					return false
				}
				irdr.reader.SkipChildren()
			}

		default:
			irdr.reader.SkipChildren()
		}
	}
}

// Entry returns the DIE for the current inlined call.
func (irdr *InlineStackReader) Entry() *dwarf.Entry {
	return irdr.entry
}

// Err returns an error, if any was encountered.
func (irdr *InlineStackReader) Err() error {
	return irdr.err
}

// SkipChildren skips all children of the current inlined call.
func (irdr *InlineStackReader) SkipChildren() {
	irdr.reader.SkipChildren()
}
<commit_msg>pkg/dwarf: Remove exec bit from reader.go<commit_after>package reader

import (
	"debug/dwarf"
	"errors"
	"fmt"

	"github.com/derekparker/delve/pkg/dwarf/op"
)

type Reader struct {
	*dwarf.Reader
	depth int
}

// New returns a reader for the specified dwarf data.
func New(data *dwarf.Data) *Reader {
	return &Reader{data.Reader(), 0}
}

// Seek moves the reader to an arbitrary offset.
func (reader *Reader) Seek(off dwarf.Offset) {
	reader.depth = 0
	reader.Reader.Seek(off)
}

// SeekToEntry moves the reader to an arbitrary entry.
func (reader *Reader) SeekToEntry(entry *dwarf.Entry) error {
	reader.Seek(entry.Offset)
	// Consume the current entry so .Next works as intended
	_, err := reader.Next()
	return err
}

// SeekToFunctionEntry moves the reader to the function that includes the
// specified program counter.
func (reader *Reader) SeekToFunction(pc RelAddr) (*dwarf.Entry, error) {
	reader.Seek(0)
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		if entry.Tag != dwarf.TagSubprogram {
			continue
		}

		lowpc, ok := entry.Val(dwarf.AttrLowpc).(uint64)
		if !ok {
			continue
		}

		highpc, ok := entry.Val(dwarf.AttrHighpc).(uint64)
		if !ok {
			continue
		}

		if lowpc <= uint64(pc) && highpc > uint64(pc) {
			return entry, nil
		}
	}

	return nil, fmt.Errorf("unable to find function context")
}

// Returns the address for the named entry.
func (reader *Reader) AddrFor(name string, staticBase uint64) (uint64, error) {
	entry, err := reader.FindEntryNamed(name, false)
	if err != nil {
		return 0, err
	}
	instructions, ok := entry.Val(dwarf.AttrLocation).([]byte)
	if !ok {
		return 0, fmt.Errorf("type assertion failed")
	}
	addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{StaticBase: staticBase}, instructions)
	if err != nil {
		return 0, err
	}
	return uint64(addr), nil
}

// Returns the address for the named struct member. Expects the reader to be at the parent entry
// or one of the parents children, thus does not seek to parent by itself.
func (reader *Reader) AddrForMember(member string, initialInstructions []byte) (uint64, error) {
	for {
		entry, err := reader.NextMemberVariable()
		if err != nil {
			return 0, err
		}
		if entry == nil {
			return 0, fmt.Errorf("nil entry for member named %s", member)
		}
		name, ok := entry.Val(dwarf.AttrName).(string)
		if !ok || name != member {
			continue
		}
		instructions, ok := entry.Val(dwarf.AttrDataMemberLoc).([]byte)
		if !ok {
			continue
		}
		addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{}, append(initialInstructions, instructions...))
		return uint64(addr), err
	}
}

var TypeNotFoundErr = errors.New("no type entry found, use 'types' for a list of valid types")

// SeekToType moves the reader to the type specified by the entry,
// optionally resolving typedefs and pointer types. If the reader is set
// to a struct type the NextMemberVariable call can be used to walk all member data.
func (reader *Reader) SeekToType(entry *dwarf.Entry, resolveTypedefs bool, resolvePointerTypes bool) (*dwarf.Entry, error) {
	offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset)
	if !ok {
		return nil, fmt.Errorf("entry does not have a type attribute")
	}

	// Seek to the first type offset
	reader.Seek(offset)

	// Walk the types to the base
	for typeEntry, err := reader.Next(); typeEntry != nil; typeEntry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		if typeEntry.Tag == dwarf.TagTypedef && !resolveTypedefs {
			return typeEntry, nil
		}

		if typeEntry.Tag == dwarf.TagPointerType && !resolvePointerTypes {
			return typeEntry, nil
		}

		offset, ok = typeEntry.Val(dwarf.AttrType).(dwarf.Offset)
		if !ok {
			return typeEntry, nil
		}

		reader.Seek(offset)
	}

	return nil, TypeNotFoundErr
}

func (reader *Reader) NextType() (*dwarf.Entry, error) {
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		switch entry.Tag {
		case dwarf.TagArrayType, dwarf.TagBaseType, dwarf.TagClassType, dwarf.TagStructType, dwarf.TagUnionType, dwarf.TagConstType, dwarf.TagVolatileType, dwarf.TagRestrictType, dwarf.TagEnumerationType, dwarf.TagPointerType, dwarf.TagSubroutineType, dwarf.TagTypedef, dwarf.TagUnspecifiedType:
			return entry, nil
		}
	}

	return nil, nil
}

// SeekToTypeNamed moves the reader to the type specified by the name.
// If the reader is set to a struct type the NextMemberVariable call
// can be used to walk all member data.
func (reader *Reader) SeekToTypeNamed(name string) (*dwarf.Entry, error) {
	// Walk the types to the base
	for entry, err := reader.NextType(); entry != nil; entry, err = reader.NextType() {
		if err != nil {
			return nil, err
		}

		n, ok := entry.Val(dwarf.AttrName).(string)
		if !ok {
			continue
		}

		if n == name {
			return entry, nil
		}
	}

	return nil, TypeNotFoundErr
}

// Finds the entry for 'name'.
func (reader *Reader) FindEntryNamed(name string, member bool) (*dwarf.Entry, error) {
	depth := 1
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		if entry.Children {
			depth++
		}

		if entry.Tag == 0 {
			depth--
			if depth <= 0 {
				return nil, fmt.Errorf("could not find symbol value for %s", name)
			}
		}

		if member {
			if entry.Tag != dwarf.TagMember {
				continue
			}
		} else {
			if entry.Tag != dwarf.TagVariable && entry.Tag != dwarf.TagFormalParameter && entry.Tag != dwarf.TagStructType {
				continue
			}
		}

		n, ok := entry.Val(dwarf.AttrName).(string)
		if !ok || n != name {
			continue
		}
		return entry, nil
	}
	return nil, fmt.Errorf("could not find symbol value for %s", name)
}

func (reader *Reader) InstructionsForEntryNamed(name string, member bool) ([]byte, error) {
	entry, err := reader.FindEntryNamed(name, member)
	if err != nil {
		return nil, err
	}
	var attr dwarf.Attr
	if member {
		attr = dwarf.AttrDataMemberLoc
	} else {
		attr = dwarf.AttrLocation
	}
	instr, ok := entry.Val(attr).([]byte)
	if !ok {
		return nil, errors.New("invalid typecast for Dwarf instructions")
	}
	return instr, nil
}

func (reader *Reader) InstructionsForEntry(entry *dwarf.Entry) ([]byte, error) {
	if entry.Tag == dwarf.TagMember {
		instructions, ok := entry.Val(dwarf.AttrDataMemberLoc).([]byte)
		if !ok {
			return nil, fmt.Errorf("member data has no data member location attribute")
		}
		// clone slice to prevent stomping on the dwarf data
		return append([]byte{}, instructions...), nil
	}

	// non-member
	instructions, ok := entry.Val(dwarf.AttrLocation).([]byte)
	if !ok {
		return nil, fmt.Errorf("entry has no location attribute")
	}

	// clone slice to prevent stomping on the dwarf data
	return append([]byte{}, instructions...), nil
}

// NextMemberVariable moves the reader to the next debug entry that describes a member variable and returns the entry.
func (reader *Reader) NextMemberVariable() (*dwarf.Entry, error) {
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		// All member variables will be at the same depth
		reader.SkipChildren()

		// End of the current depth
		if entry.Tag == 0 {
			break
		}

		if entry.Tag == dwarf.TagMember {
			return entry, nil
		}
	}

	// No more items
	return nil, nil
}

// NextPackageVariable moves the reader to the next debug entry that describes a package variable.
// Any TagVariable entry that is not inside a sub prgram entry and is marked external is considered a package variable.
func (reader *Reader) NextPackageVariable() (*dwarf.Entry, error) {
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		if entry.Tag == dwarf.TagVariable {
			ext, ok := entry.Val(dwarf.AttrExternal).(bool)
			if ok && ext {
				return entry, nil
			}
		}

		// Ignore everything inside sub programs
		if entry.Tag == dwarf.TagSubprogram {
			reader.SkipChildren()
		}
	}

	// No more items
	return nil, nil
}

func (reader *Reader) NextCompileUnit() (*dwarf.Entry, error) {
	for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
		if err != nil {
			return nil, err
		}

		if entry.Tag == dwarf.TagCompileUnit {
			return entry, nil
		}
	}

	return nil, nil
}

// Entry represents a debug_info entry.
// When calling Val, if the entry does not have the specified attribute, the
// entry specified by DW_AT_abstract_origin will be searched recursively.
type Entry interface {
	Val(dwarf.Attr) interface{}
}

type compositeEntry []*dwarf.Entry

func (ce compositeEntry) Val(attr dwarf.Attr) interface{} {
	for _, e := range ce {
		if r := e.Val(attr); r != nil {
			return r
		}
	}
	return nil
}

// LoadAbstractOrigin loads the entry corresponding to the
// DW_AT_abstract_origin of entry and returns a combination of entry and its
// abstract origin.
func LoadAbstractOrigin(entry *dwarf.Entry, aordr *dwarf.Reader) (Entry, dwarf.Offset) {
	ao, ok := entry.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
	if !ok {
		return entry, entry.Offset
	}

	r := []*dwarf.Entry{entry}

	for {
		aordr.Seek(ao)
		e, _ := aordr.Next()
		if e == nil {
			break
		}
		r = append(r, e)

		ao, ok = e.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
		if !ok {
			break
		}
	}

	return compositeEntry(r), entry.Offset
}

// InlineStackReader provides a way to read the stack of inlined calls at a
// specified PC address.
type InlineStackReader struct {
	dwarf  *dwarf.Data
	reader *dwarf.Reader
	entry  *dwarf.Entry
	depth  int
	pc     uint64
	err    error
}

// InlineStack returns an InlineStackReader for the specified function and
// PC address.
// If pc is 0 then all inlined calls will be returned.
func InlineStack(dwarf *dwarf.Data, fnoff dwarf.Offset, pc RelAddr) *InlineStackReader {
	reader := dwarf.Reader()
	reader.Seek(fnoff)
	return &InlineStackReader{dwarf: dwarf, reader: reader, entry: nil, depth: 0, pc: uint64(pc)}
}

// Next reads next inlined call in the stack, returns false if there aren't any.
func (irdr *InlineStackReader) Next() bool {
	if irdr.err != nil {
		return false
	}

	for {
		irdr.entry, irdr.err = irdr.reader.Next()
		if irdr.entry == nil || irdr.err != nil {
			return false
		}

		switch irdr.entry.Tag {
		case 0:
			irdr.depth--
			if irdr.depth == 0 {
				return false
			}

		case dwarf.TagLexDwarfBlock, dwarf.TagSubprogram, dwarf.TagInlinedSubroutine:
			var recur bool
			if irdr.pc != 0 {
				recur, irdr.err = entryRangesContains(irdr.dwarf, irdr.entry, irdr.pc)
			} else {
				recur = true
			}
			if recur {
				irdr.depth++
				if irdr.entry.Tag == dwarf.TagInlinedSubroutine {
					return true
				}
			} else {
				if irdr.depth == 0 {
					return false
				}
				irdr.reader.SkipChildren()
			}

		default:
			irdr.reader.SkipChildren()
		}
	}
}

// Entry returns the DIE for the current inlined call.
func (irdr *InlineStackReader) Entry() *dwarf.Entry {
	return irdr.entry
}

// Err returns an error, if any was encountered.
func (irdr *InlineStackReader) Err() error {
	return irdr.err
}

// SkipChildren skips all children of the current inlined call.
func (irdr *InlineStackReader) SkipChildren() {
	irdr.reader.SkipChildren()
}
<|endoftext|>


<commit_before>package etwlogrus

import (
	"github.com/Microsoft/go-winio/internal/etw"
	"testing"
)

func fireEvent(t *testing.T, p *etw.Provider, name string, value interface{}) {
	if err := p.WriteEvent(
		name,
		nil,
		etw.WithFields(getFieldOpt("Field", value))); err != nil {

		t.Fatal(err)
	}
}

// The purpose of this test is to log lots of different field types, to test the
// logic that converts them to ETW. Because we don't have a way to
// programatically validate the ETW events, this test has two main purposes: (1)
// validate nothing causes a panic while logging (2) allow manual validation that
// the data is logged correctly (through a tool like WPA).
func TestFieldLogging(t *testing.T) {
	// Sample WPRP to collect this provider:
	//
	// <?xml version="1.0"?>
	// <WindowsPerformanceRecorder Version="1">
	//   <Profiles>
	//     <EventCollector Id="Collector" Name="MyCollector">
	//       <BufferSize Value="256"/>
	//       <Buffers Value="100"/>
	//     </EventCollector>
	//     <EventProvider Id="HookTest" Name="5e50de03-107c-5a83-74c6-998c4491e7e9"/>
	//     <Profile Id="Test.Verbose.File" Name="Test" Description="Test" LoggingMode="File" DetailLevel="Verbose">
	//       <Collectors>
	//         <EventCollectorId Value="Collector">
	//           <EventProviders>
	//             <EventProviderId Value="HookTest"/>
	//           </EventProviders>
	//         </EventCollectorId>
	//       </Collectors>
	//     </Profile>
	//   </Profiles>
	// </WindowsPerformanceRecorder>
	//
	// Start collection:
	// wpr -start HookTest.wprp -filemode
	//
	// Stop collection:
	// wpr -stop HookTest.etl
	p, err := etw.NewProvider("HookTest", nil)
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		if err := p.Close(); err != nil {
			t.Fatal(err)
		}
	}()

	fireEvent(t, p, "Bool", true)
	fireEvent(t, p, "BoolSlice", []bool{true, false, true})
	fireEvent(t, p, "String", "teststring")
	fireEvent(t, p, "StringSlice", []string{"sstr1", "sstr2", "sstr3"})
	fireEvent(t, p, "Int", int(1))
	fireEvent(t, p, "IntSlice", []int{2, 3, 4})
	fireEvent(t, p, "Int8", int8(5))
	fireEvent(t, p, "Int8Slice", []int8{6, 7, 8})
	fireEvent(t, p, "Int16", int16(9))
	fireEvent(t, p, "Int16Slice", []int16{10, 11, 12})
	fireEvent(t, p, "Int32", int32(13))
	fireEvent(t, p, "Int32Slice", []int32{14, 15, 16})
	fireEvent(t, p, "Int64", int64(17))
	fireEvent(t, p, "Int64Slice", []int64{18, 19, 20})
	fireEvent(t, p, "Uint", uint(21))
	fireEvent(t, p, "UintSlice", []uint{22, 23, 24})
	fireEvent(t, p, "Uint8", uint8(25))
	fireEvent(t, p, "Uint8Slice", []uint8{26, 27, 28})
	fireEvent(t, p, "Uint16", uint16(29))
	fireEvent(t, p, "Uint16Slice", []uint16{30, 31, 32})
	fireEvent(t, p, "Uint32", uint32(33))
	fireEvent(t, p, "Uint32Slice", []uint32{34, 35, 36})
	fireEvent(t, p, "Uint64", uint64(37))
	fireEvent(t, p, "Uint64Slice", []uint64{38, 39, 40})
	fireEvent(t, p, "Uintptr", uintptr(41))
	fireEvent(t, p, "UintptrSlice", []uintptr{42, 43, 44})
	fireEvent(t, p, "Float32", float32(45.46))
	fireEvent(t, p, "Float32Slice", []float32{47.48, 49.50, 51.52})
	fireEvent(t, p, "Float64", float64(53.54))
	fireEvent(t, p, "Float64Slice", []float64{55.56, 57.58, 59.60})

	type struct1 struct {
		A    float32
		priv int
		B    []uint
	}
	type struct2 struct {
		A int
		B int
	}
	type struct3 struct {
		struct2
		A    int
		B    string
		priv string
		C    struct1
		D    uint16
	}
	// Unexported fields, and fields in embedded structs, should not log.
	fireEvent(t, p, "Struct1", struct3{struct2{-1, -2}, 1, "2s", "-3s", struct1{3.4, -4, []uint{5, 6, 7}}, 8})
}
<commit_msg>Add test cases for logging empty slices<commit_after>package etwlogrus

import (
	"github.com/Microsoft/go-winio/internal/etw"
	"testing"
)

func fireEvent(t *testing.T, p *etw.Provider, name string, value interface{}) {
	if err := p.WriteEvent(
		name,
		nil,
		etw.WithFields(getFieldOpt("Field", value))); err != nil {

		t.Fatal(err)
	}
}

// The purpose of this test is to log lots of different field types, to test the
// logic that converts them to ETW. Because we don't have a way to
// programatically validate the ETW events, this test has two main purposes: (1)
// validate nothing causes a panic while logging (2) allow manual validation that
// the data is logged correctly (through a tool like WPA).
func TestFieldLogging(t *testing.T) {
	// Sample WPRP to collect this provider:
	//
	// <?xml version="1.0"?>
	// <WindowsPerformanceRecorder Version="1">
	//   <Profiles>
	//     <EventCollector Id="Collector" Name="MyCollector">
	//       <BufferSize Value="256"/>
	//       <Buffers Value="100"/>
	//     </EventCollector>
	//     <EventProvider Id="HookTest" Name="5e50de03-107c-5a83-74c6-998c4491e7e9"/>
	//     <Profile Id="Test.Verbose.File" Name="Test" Description="Test" LoggingMode="File" DetailLevel="Verbose">
	//       <Collectors>
	//         <EventCollectorId Value="Collector">
	//           <EventProviders>
	//             <EventProviderId Value="HookTest"/>
	//           </EventProviders>
	//         </EventCollectorId>
	//       </Collectors>
	//     </Profile>
	//   </Profiles>
	// </WindowsPerformanceRecorder>
	//
	// Start collection:
	// wpr -start HookTest.wprp -filemode
	//
	// Stop collection:
	// wpr -stop HookTest.etl
	p, err := etw.NewProvider("HookTest", nil)
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		if err := p.Close(); err != nil {
			t.Fatal(err)
		}
	}()

	fireEvent(t, p, "Bool", true)
	fireEvent(t, p, "BoolSlice", []bool{true, false, true})
	fireEvent(t, p, "EmptyBoolSlice", []bool{})
	fireEvent(t, p, "String", "teststring")
	fireEvent(t, p, "StringSlice", []string{"sstr1", "sstr2", "sstr3"})
	fireEvent(t, p, "EmptyStringSlice", []string{})
	fireEvent(t, p, "Int", int(1))
	fireEvent(t, p, "IntSlice", []int{2, 3, 4})
	fireEvent(t, p, "EmptyIntSlice", []int{})
	fireEvent(t, p, "Int8", int8(5))
	fireEvent(t, p, "Int8Slice", []int8{6, 7, 8})
	fireEvent(t, p, "EmptyInt8Slice", []int8{})
	fireEvent(t, p, "Int16", int16(9))
	fireEvent(t, p, "Int16Slice", []int16{10, 11, 12})
	fireEvent(t, p, "EmptyInt16Slice", []int16{})
	fireEvent(t, p, "Int32", int32(13))
	fireEvent(t, p, "Int32Slice", []int32{14, 15, 16})
	fireEvent(t, p, "EmptyInt32Slice", []int32{})
	fireEvent(t, p, "Int64", int64(17))
	fireEvent(t, p, "Int64Slice", []int64{18, 19, 20})
	fireEvent(t, p, "EmptyInt64Slice", []int64{})
	fireEvent(t, p, "Uint", uint(21))
	fireEvent(t, p, "UintSlice", []uint{22, 23, 24})
	fireEvent(t, p, "EmptyUintSlice", []uint{})
	fireEvent(t, p, "Uint8", uint8(25))
	fireEvent(t, p, "Uint8Slice", []uint8{26, 27, 28})
	fireEvent(t, p, "EmptyUint8Slice", []uint8{})
	fireEvent(t, p, "Uint16", uint16(29))
	fireEvent(t, p, "Uint16Slice", []uint16{30, 31, 32})
	fireEvent(t, p, "EmptyUint16Slice", []uint16{})
	fireEvent(t, p, "Uint32", uint32(33))
	fireEvent(t, p, "Uint32Slice", []uint32{34, 35, 36})
	fireEvent(t, p, "EmptyUint32Slice", []uint32{})
	fireEvent(t, p, "Uint64", uint64(37))
	fireEvent(t, p, "Uint64Slice", []uint64{38, 39, 40})
	fireEvent(t, p, "EmptyUint64Slice", []uint64{})
	fireEvent(t, p, "Uintptr", uintptr(41))
	fireEvent(t, p, "UintptrSlice", []uintptr{42, 43, 44})
	fireEvent(t, p, "EmptyUintptrSlice", []uintptr{})
	fireEvent(t, p, "Float32", float32(45.46))
	fireEvent(t, p, "Float32Slice", []float32{47.48, 49.50, 51.52})
	fireEvent(t, p, "EmptyFloat32Slice", []float32{})
	fireEvent(t, p, "Float64", float64(53.54))
	fireEvent(t, p, "Float64Slice", []float64{55.56, 57.58, 59.60})
	fireEvent(t, p, "EmptyFloat64Slice", []float64{})

	type struct1 struct {
		A    float32
		priv int
		B    []uint
	}
	type struct2 struct {
		A int
		B int
	}
	type struct3 struct {
		struct2
		A    int
		B    string
		priv string
		C    struct1
		D    uint16
	}
	// Unexported fields, and fields in embedded structs, should not log.
	fireEvent(t, p, "Struct", struct3{struct2{-1, -2}, 1, "2s", "-3s", struct1{3.4, -4, []uint{5, 6, 7}}, 8})
}
<|endoftext|>


<commit_before>package handlers

import (
	"fmt"
	"net/http"
	"net/http/httputil"
)

// PrintRequest wraps the call to httputil.DumpRequest...
func PrintRequest(req *http.Request) error {

	// func DumpRequest(req *http.Request, body bool) ([]byte, error)
	requestDump, err := httputil.DumpRequest(req, true)
	if err != nil {
		return HTTPStatusError{http.StatusBadRequest, err}
	}
	fmt.Println(string(requestDump))
	return nil
}

//func logRequest(req *http.Request) {
//
//	// type Request struct {
//	//            Method string
//	//            URL *url.URL
//	//            Proto      string // "HTTP/1.0"
//	//            ProtoMajor int    // 1
//	//            ProtoMinor int    // 0
//	//            Header Header
//	//            Body io.ReadCloser
//	//            ContentLength int64
//	//            TransferEncoding []string
//	//            Close bool
//	//            Host string
//	//            Form url.Values
//	//            PostForm url.Values
//	//            MultipartForm *multipart.Form
//	//            Trailer Header
//	//            RemoteAddr string
//	//            RequestURI string
//	//            TLS *tls.ConnectionState
//	//    }
//
//	logger := environment.Logger
//	logger.Info("Request received",
//		zap.String("URL Path", req.URL.Path[1:]),
//		zap.String("HTTP method", req.Method),
//		zap.String("URL", req.URL.String()),
//		zap.String("Protocol", req.Proto),
//		zap.Int("ProtoMajor", req.ProtoMajor),
//		zap.Int("ProtoMinor", req.ProtoMinor),
//
//		//TODO - finish logging the rest of the request
//		//fmt.Fprintf(w, "Header = %s\n", req.Header)
//		//fmt.Fprintf(w, "Body = %s\n", req.Body)
//		//fmt.Fprintf(w, "Content Length = %d\n", req.ContentLength)
//		//fmt.Fprintf(w, "Transfer Encoding = %s\n", req.TransferEncoding)
//		//fmt.Fprintf(w, "Close boolean = %t\n", req.Close)
//		//fmt.Fprintf(w, "Host = %s\n", req.Host)
//		//fmt.Fprintf(w, "Post Form Values = %s\n", req.Form)
//	)
//}
<commit_msg>Removed trailing ...<commit_after>package handlers

import (
	"fmt"
	"net/http"
	"net/http/httputil"
)

// PrintRequest wraps the call to httputil.DumpRequest
func PrintRequest(req *http.Request) error {

	// func DumpRequest(req *http.Request, body bool) ([]byte, error)
	requestDump, err := httputil.DumpRequest(req, true)
	if err != nil {
		return HTTPStatusError{http.StatusBadRequest, err}
	}
	fmt.Println(string(requestDump))
	return nil
}

//func logRequest(req *http.Request) {
//
//	// type Request struct {
//	//            Method string
//	//            URL *url.URL
//	//            Proto      string // "HTTP/1.0"
//	//            ProtoMajor int    // 1
//	//            ProtoMinor int    // 0
//	//            Header Header
//	//            Body io.ReadCloser
//	//            ContentLength int64
//	//            TransferEncoding []string
//	//            Close bool
//	//            Host string
//	//            Form url.Values
//	//            PostForm url.Values
//	//            MultipartForm *multipart.Form
//	//            Trailer Header
//	//            RemoteAddr string
//	//            RequestURI string
//	//            TLS *tls.ConnectionState
//	//    }
//
//	logger := environment.Logger
//	logger.Info("Request received",
//		zap.String("URL Path", req.URL.Path[1:]),
//		zap.String("HTTP method", req.Method),
//		zap.String("URL", req.URL.String()),
//		zap.String("Protocol", req.Proto),
//		zap.Int("ProtoMajor", req.ProtoMajor),
//		zap.Int("ProtoMinor", req.ProtoMinor),
//
//		//TODO - finish logging the rest of the request
//		//fmt.Fprintf(w, "Header = %s\n", req.Header)
//		//fmt.Fprintf(w, "Body = %s\n", req.Body)
//		//fmt.Fprintf(w, "Content Length = %d\n", req.ContentLength)
//		//fmt.Fprintf(w, "Transfer Encoding = %s\n", req.TransferEncoding)
//		//fmt.Fprintf(w, "Close boolean = %t\n", req.Close)
//		//fmt.Fprintf(w, "Host = %s\n", req.Host)
//		//fmt.Fprintf(w, "Post Form Values = %s\n", req.Form)
//	)
//}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 runtime

import (
	"fmt"
	"reflect"

	"k8s.io/apimachinery/pkg/runtime/schema"
)

// CheckCodec makes sure that the codec can encode objects like internalType,
// decode all of the external types listed, and also decode them into the given
// object. (Will modify internalObject.) (Assumes JSON serialization.)
// TODO: verify that the correct external version is chosen on encode...
func CheckCodec(c Codec, internalType Object, externalTypes ...schema.GroupVersionKind) error {
	_, err := Encode(c, internalType)
	if err != nil {
		return fmt.Errorf("Internal type not encodable: %v", err)
	}
	for _, et := range externalTypes {
		exBytes := []byte(fmt.Sprintf(`{"kind":"%v","apiVersion":"%v"}`, et.Kind, et.GroupVersion().String()))
		obj, err := Decode(c, exBytes)
		if err != nil {
			return fmt.Errorf("external type %s not interpretable: %v", et, err)
		}
		if reflect.TypeOf(obj) != reflect.TypeOf(internalType) {
			return fmt.Errorf("decode of external type %s produced: %#v", et, obj)
		}
		err = DecodeInto(c, exBytes, internalType)
		if err != nil {
			return fmt.Errorf("external type %s not convertable to internal type: %v", et, err)
		}
	}
	return nil
}
<commit_msg>fix err message and small change in UX<commit_after>/*
Copyright 2016 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 runtime

import (
	"fmt"
	"reflect"

	"k8s.io/apimachinery/pkg/runtime/schema"
)

// CheckCodec makes sure that the codec can encode objects like internalType,
// decode all of the external types listed, and also decode them into the given
// object. (Will modify internalObject.) (Assumes JSON serialization.)
// TODO: verify that the correct external version is chosen on encode...
func CheckCodec(c Codec, internalType Object, externalTypes ...schema.GroupVersionKind) error {
	if _, err := Encode(c, internalType); err != nil {
		return fmt.Errorf("Internal type not encodable: %v", err)
	}
	for _, et := range externalTypes {
		exBytes := []byte(fmt.Sprintf(`{"kind":"%v","apiVersion":"%v"}`, et.Kind, et.GroupVersion().String()))
		obj, err := Decode(c, exBytes)
		if err != nil {
			return fmt.Errorf("external type %s not interpretable: %v", et, err)
		}
		if reflect.TypeOf(obj) != reflect.TypeOf(internalType) {
			return fmt.Errorf("decode of external type %s produced: %#v", et, obj)
		}
		if err = DecodeInto(c, exBytes, internalType); err != nil {
			return fmt.Errorf("external type %s not convertible to internal type: %v", et, err)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 options

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/spf13/pflag"

	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/registry/generic"
	genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
	"k8s.io/apiserver/pkg/server"
	"k8s.io/apiserver/pkg/server/healthz"
	"k8s.io/apiserver/pkg/server/options/encryptionconfig"
	serverstorage "k8s.io/apiserver/pkg/server/storage"
	"k8s.io/apiserver/pkg/storage/storagebackend"
	storagefactory "k8s.io/apiserver/pkg/storage/storagebackend/factory"
	"k8s.io/klog/v2"
)

type EtcdOptions struct {
	// The value of Paging on StorageConfig will be overridden by the
	// calculated feature gate value.
	StorageConfig                    storagebackend.Config
	EncryptionProviderConfigFilepath string

	EtcdServersOverrides []string

	// To enable protobuf as storage format, it is enough
	// to set it to "application/vnd.kubernetes.protobuf".
	DefaultStorageMediaType string
	DeleteCollectionWorkers int
	EnableGarbageCollection bool

	// Set EnableWatchCache to false to disable all watch caches
	EnableWatchCache bool
	// Set DefaultWatchCacheSize to zero to disable watch caches for those resources that have no explicit cache size set
	DefaultWatchCacheSize int
	// WatchCacheSizes represents override to a given resource
	WatchCacheSizes []string
}

var storageTypes = sets.NewString(
	storagebackend.StorageTypeETCD3,
)

func NewEtcdOptions(backendConfig *storagebackend.Config) *EtcdOptions {
	options := &EtcdOptions{
		StorageConfig:           *backendConfig,
		DefaultStorageMediaType: "application/json",
		DeleteCollectionWorkers: 1,
		EnableGarbageCollection: true,
		EnableWatchCache:        true,
		DefaultWatchCacheSize:   100,
	}
	options.StorageConfig.CountMetricPollPeriod = time.Minute
	return options
}

func (s *EtcdOptions) Validate() []error {
	if s == nil {
		return nil
	}

	allErrors := []error{}
	if len(s.StorageConfig.Transport.ServerList) == 0 {
		allErrors = append(allErrors, fmt.Errorf("--etcd-servers must be specified"))
	}

	if s.StorageConfig.Type != storagebackend.StorageTypeUnset && !storageTypes.Has(s.StorageConfig.Type) {
		allErrors = append(allErrors, fmt.Errorf("--storage-backend invalid, allowed values: %s. If not specified, it will default to 'etcd3'", strings.Join(storageTypes.List(), ", ")))
	}

	for _, override := range s.EtcdServersOverrides {
		tokens := strings.Split(override, "#")
		if len(tokens) != 2 {
			allErrors = append(allErrors, fmt.Errorf("--etcd-servers-overrides invalid, must be of format: group/resource#servers, where servers are URLs, semicolon separated"))
			continue
		}

		apiresource := strings.Split(tokens[0], "/")
		if len(apiresource) != 2 {
			allErrors = append(allErrors, fmt.Errorf("--etcd-servers-overrides invalid, must be of format: group/resource#servers, where servers are URLs, semicolon separated"))
			continue
		}

	}

	return allErrors
}

// AddEtcdFlags adds flags related to etcd storage for a specific APIServer to the specified FlagSet
func (s *EtcdOptions) AddFlags(fs *pflag.FlagSet) {
	if s == nil {
		return
	}

	fs.StringSliceVar(&s.EtcdServersOverrides, "etcd-servers-overrides", s.EtcdServersOverrides, ""+
		"Per-resource etcd servers overrides, comma separated. The individual override "+
		"format: group/resource#servers, where servers are URLs, semicolon separated. "+
		"Note that this applies only to resources compiled into this server binary. ")

	fs.StringVar(&s.DefaultStorageMediaType, "storage-media-type", s.DefaultStorageMediaType, ""+
		"The media type to use to store objects in storage. "+
		"Some resources or storage backends may only support a specific media type and will ignore this setting.")
	fs.IntVar(&s.DeleteCollectionWorkers, "delete-collection-workers", s.DeleteCollectionWorkers,
		"Number of workers spawned for DeleteCollection call. These are used to speed up namespace cleanup.")

	fs.BoolVar(&s.EnableGarbageCollection, "enable-garbage-collector", s.EnableGarbageCollection, ""+
		"Enables the generic garbage collector. MUST be synced with the corresponding flag "+
		"of the kube-controller-manager.")

	fs.BoolVar(&s.EnableWatchCache, "watch-cache", s.EnableWatchCache,
		"Enable watch caching in the apiserver")

	fs.IntVar(&s.DefaultWatchCacheSize, "default-watch-cache-size", s.DefaultWatchCacheSize,
		"Default watch cache size. If zero, watch cache will be disabled for resources that do not have a default watch size set.")

	fs.StringSliceVar(&s.WatchCacheSizes, "watch-cache-sizes", s.WatchCacheSizes, ""+
		"Watch cache size settings for some resources (pods, nodes, etc.), comma separated. "+
		"The individual setting format: resource[.group]#size, where resource is lowercase plural (no version), "+
		"group is omitted for resources of apiVersion v1 (the legacy core API) and included for others, "+
		"and size is a number. It takes effect when watch-cache is enabled. "+
		"Some resources (replicationcontrollers, endpoints, nodes, pods, services, apiservices.apiregistration.k8s.io) "+
		"have system defaults set by heuristics, others default to default-watch-cache-size")

	fs.StringVar(&s.StorageConfig.Type, "storage-backend", s.StorageConfig.Type,
		"The storage backend for persistence. Options: 'etcd3' (default).")

	dummyCacheSize := 0
	fs.IntVar(&dummyCacheSize, "deserialization-cache-size", 0, "Number of deserialized json objects to cache in memory.")
	fs.MarkDeprecated("deserialization-cache-size", "the deserialization cache was dropped in 1.13 with support for etcd2")

	fs.StringSliceVar(&s.StorageConfig.Transport.ServerList, "etcd-servers", s.StorageConfig.Transport.ServerList,
		"List of etcd servers to connect with (scheme://ip:port), comma separated.")

	fs.StringVar(&s.StorageConfig.Prefix, "etcd-prefix", s.StorageConfig.Prefix,
		"The prefix to prepend to all resource paths in etcd.")

	fs.StringVar(&s.StorageConfig.Transport.KeyFile, "etcd-keyfile", s.StorageConfig.Transport.KeyFile,
		"SSL key file used to secure etcd communication.")

	fs.StringVar(&s.StorageConfig.Transport.CertFile, "etcd-certfile", s.StorageConfig.Transport.CertFile,
		"SSL certification file used to secure etcd communication.")

	fs.StringVar(&s.StorageConfig.Transport.TrustedCAFile, "etcd-cafile", s.StorageConfig.Transport.TrustedCAFile,
		"SSL Certificate Authority file used to secure etcd communication.")

	fs.StringVar(&s.EncryptionProviderConfigFilepath, "experimental-encryption-provider-config", s.EncryptionProviderConfigFilepath,
		"The file containing configuration for encryption providers to be used for storing secrets in etcd")
	fs.MarkDeprecated("experimental-encryption-provider-config", "use --encryption-provider-config.")

	fs.StringVar(&s.EncryptionProviderConfigFilepath, "encryption-provider-config", s.EncryptionProviderConfigFilepath,
		"The file containing configuration for encryption providers to be used for storing secrets in etcd")

	fs.DurationVar(&s.StorageConfig.CompactionInterval, "etcd-compaction-interval", s.StorageConfig.CompactionInterval,
		"The interval of compaction requests. If 0, the compaction request from apiserver is disabled.")

	fs.DurationVar(&s.StorageConfig.CountMetricPollPeriod, "etcd-count-metric-poll-period", s.StorageConfig.CountMetricPollPeriod, ""+
		"Frequency of polling etcd for number of resources per type. 0 disables the metric collection.")

	fs.DurationVar(&s.StorageConfig.DBMetricPollInterval, "etcd-db-metric-poll-interval", s.StorageConfig.DBMetricPollInterval,
		"The interval of requests to poll etcd and update metric. 0 disables the metric collection")

	fs.DurationVar(&s.StorageConfig.HealthcheckTimeout, "etcd-healthcheck-timeout", s.StorageConfig.HealthcheckTimeout,
		"The timeout to use when checking etcd health.")

	fs.Int64Var(&s.StorageConfig.LeaseManagerConfig.ReuseDurationSeconds, "lease-reuse-duration-seconds", s.StorageConfig.LeaseManagerConfig.ReuseDurationSeconds,
		"The time in seconds that each lease is reused. A lower value could avoid large number of objects reusing the same lease. Notice that a too small value may cause performance problems at storage layer.")
}

func (s *EtcdOptions) ApplyTo(c *server.Config) error {
	if s == nil {
		return nil
	}
	if err := s.addEtcdHealthEndpoint(c); err != nil {
		return err
	}
	c.RESTOptionsGetter = &SimpleRestOptionsFactory{Options: *s}
	return nil
}

func (s *EtcdOptions) ApplyWithStorageFactoryTo(factory serverstorage.StorageFactory, c *server.Config) error {
	if err := s.addEtcdHealthEndpoint(c); err != nil {
		return err
	}
	c.RESTOptionsGetter = &StorageFactoryRestOptionsFactory{Options: *s, StorageFactory: factory}
	return nil
}

func (s *EtcdOptions) addEtcdHealthEndpoint(c *server.Config) error {
	healthCheck, err := storagefactory.CreateHealthCheck(s.StorageConfig)
	if err != nil {
		return err
	}
	c.AddHealthChecks(healthz.NamedCheck("etcd", func(r *http.Request) error {
		return healthCheck()
	}))

	if s.EncryptionProviderConfigFilepath != "" {
		kmsPluginHealthzChecks, err := encryptionconfig.GetKMSPluginHealthzCheckers(s.EncryptionProviderConfigFilepath)
		if err != nil {
			return err
		}
		c.AddHealthChecks(kmsPluginHealthzChecks...)
	}

	return nil
}

type SimpleRestOptionsFactory struct {
	Options EtcdOptions
}

func (f *SimpleRestOptionsFactory) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
	ret := generic.RESTOptions{
		StorageConfig:           &f.Options.StorageConfig,
		Decorator:               generic.UndecoratedStorage,
		EnableGarbageCollection: f.Options.EnableGarbageCollection,
		DeleteCollectionWorkers: f.Options.DeleteCollectionWorkers,
		ResourcePrefix:          resource.Group + "/" + resource.Resource,
		CountMetricPollPeriod:   f.Options.StorageConfig.CountMetricPollPeriod,
	}
	if f.Options.EnableWatchCache {
		sizes, err := ParseWatchCacheSizes(f.Options.WatchCacheSizes)
		if err != nil {
			return generic.RESTOptions{}, err
		}
		size, ok := sizes[resource]
		if ok && size > 0 {
			klog.Warningf("Dropping watch-cache-size for %v - watchCache size is now dynamic", resource)
		}
		if ok && size <= 0 {
			ret.Decorator = generic.UndecoratedStorage
		} else {
			ret.Decorator = genericregistry.StorageWithCacher()
		}
	}
	return ret, nil
}

type StorageFactoryRestOptionsFactory struct {
	Options        EtcdOptions
	StorageFactory serverstorage.StorageFactory
}

func (f *StorageFactoryRestOptionsFactory) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
	storageConfig, err := f.StorageFactory.NewConfig(resource)
	if err != nil {
		return generic.RESTOptions{}, fmt.Errorf("unable to find storage destination for %v, due to %v", resource, err.Error())
	}

	ret := generic.RESTOptions{
		StorageConfig:           storageConfig,
		Decorator:               generic.UndecoratedStorage,
		DeleteCollectionWorkers: f.Options.DeleteCollectionWorkers,
		EnableGarbageCollection: f.Options.EnableGarbageCollection,
		ResourcePrefix:          f.StorageFactory.ResourcePrefix(resource),
		CountMetricPollPeriod:   f.Options.StorageConfig.CountMetricPollPeriod,
	}
	if f.Options.EnableWatchCache {
		sizes, err := ParseWatchCacheSizes(f.Options.WatchCacheSizes)
		if err != nil {
			return generic.RESTOptions{}, err
		}
		size, ok := sizes[resource]
		if ok && size > 0 {
			klog.Warningf("Dropping watch-cache-size for %v - watchCache size is now dynamic", resource)
		}
		if ok && size <= 0 {
			ret.Decorator = generic.UndecoratedStorage
		} else {
			ret.Decorator = genericregistry.StorageWithCacher()
		}
	}

	return ret, nil
}

// ParseWatchCacheSizes turns a list of cache size values into a map of group resources
// to requested sizes.
func ParseWatchCacheSizes(cacheSizes []string) (map[schema.GroupResource]int, error) {
	watchCacheSizes := make(map[schema.GroupResource]int)
	for _, c := range cacheSizes {
		tokens := strings.Split(c, "#")
		if len(tokens) != 2 {
			return nil, fmt.Errorf("invalid value of watch cache size: %s", c)
		}

		size, err := strconv.Atoi(tokens[1])
		if err != nil {
			return nil, fmt.Errorf("invalid size of watch cache size: %s", c)
		}
		if size < 0 {
			return nil, fmt.Errorf("watch cache size cannot be negative: %s", c)
		}
		watchCacheSizes[schema.ParseGroupResource(tokens[0])] = size
	}
	return watchCacheSizes, nil
}

// WriteWatchCacheSizes turns a map of cache size values into a list of string specifications.
func WriteWatchCacheSizes(watchCacheSizes map[schema.GroupResource]int) ([]string, error) {
	var cacheSizes []string

	for resource, size := range watchCacheSizes {
		if size < 0 {
			return nil, fmt.Errorf("watch cache size cannot be negative for resource %s", resource)
		}
		cacheSizes = append(cacheSizes, fmt.Sprintf("%s#%d", resource.String(), size))
	}
	return cacheSizes, nil
}
<commit_msg>support storage encryption for aa server<commit_after>/*
Copyright 2016 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 options

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/spf13/pflag"

	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/registry/generic"
	genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
	"k8s.io/apiserver/pkg/server"
	"k8s.io/apiserver/pkg/server/healthz"
	"k8s.io/apiserver/pkg/server/options/encryptionconfig"
	serverstorage "k8s.io/apiserver/pkg/server/storage"
	"k8s.io/apiserver/pkg/storage/storagebackend"
	storagefactory "k8s.io/apiserver/pkg/storage/storagebackend/factory"
	"k8s.io/apiserver/pkg/storage/value"
	"k8s.io/klog/v2"
)

type EtcdOptions struct {
	// The value of Paging on StorageConfig will be overridden by the
	// calculated feature gate value.
	StorageConfig                    storagebackend.Config
	EncryptionProviderConfigFilepath string

	EtcdServersOverrides []string

	// To enable protobuf as storage format, it is enough
	// to set it to "application/vnd.kubernetes.protobuf".
	DefaultStorageMediaType string
	DeleteCollectionWorkers int
	EnableGarbageCollection bool

	// Set EnableWatchCache to false to disable all watch caches
	EnableWatchCache bool
	// Set DefaultWatchCacheSize to zero to disable watch caches for those resources that have no explicit cache size set
	DefaultWatchCacheSize int
	// WatchCacheSizes represents override to a given resource
	WatchCacheSizes []string
}

var storageTypes = sets.NewString(
	storagebackend.StorageTypeETCD3,
)

func NewEtcdOptions(backendConfig *storagebackend.Config) *EtcdOptions {
	options := &EtcdOptions{
		StorageConfig:           *backendConfig,
		DefaultStorageMediaType: "application/json",
		DeleteCollectionWorkers: 1,
		EnableGarbageCollection: true,
		EnableWatchCache:        true,
		DefaultWatchCacheSize:   100,
	}
	options.StorageConfig.CountMetricPollPeriod = time.Minute
	return options
}

func (s *EtcdOptions) Validate() []error {
	if s == nil {
		return nil
	}

	allErrors := []error{}
	if len(s.StorageConfig.Transport.ServerList) == 0 {
		allErrors = append(allErrors, fmt.Errorf("--etcd-servers must be specified"))
	}

	if s.StorageConfig.Type != storagebackend.StorageTypeUnset && !storageTypes.Has(s.StorageConfig.Type) {
		allErrors = append(allErrors, fmt.Errorf("--storage-backend invalid, allowed values: %s. If not specified, it will default to 'etcd3'", strings.Join(storageTypes.List(), ", ")))
	}

	for _, override := range s.EtcdServersOverrides {
		tokens := strings.Split(override, "#")
		if len(tokens) != 2 {
			allErrors = append(allErrors, fmt.Errorf("--etcd-servers-overrides invalid, must be of format: group/resource#servers, where servers are URLs, semicolon separated"))
			continue
		}

		apiresource := strings.Split(tokens[0], "/")
		if len(apiresource) != 2 {
			allErrors = append(allErrors, fmt.Errorf("--etcd-servers-overrides invalid, must be of format: group/resource#servers, where servers are URLs, semicolon separated"))
			continue
		}

	}

	return allErrors
}

// AddEtcdFlags adds flags related to etcd storage for a specific APIServer to the specified FlagSet
func (s *EtcdOptions) AddFlags(fs *pflag.FlagSet) {
	if s == nil {
		return
	}

	fs.StringSliceVar(&s.EtcdServersOverrides, "etcd-servers-overrides", s.EtcdServersOverrides, ""+
		"Per-resource etcd servers overrides, comma separated. The individual override "+
		"format: group/resource#servers, where servers are URLs, semicolon separated. "+
		"Note that this applies only to resources compiled into this server binary. ")

	fs.StringVar(&s.DefaultStorageMediaType, "storage-media-type", s.DefaultStorageMediaType, ""+
		"The media type to use to store objects in storage. "+
		"Some resources or storage backends may only support a specific media type and will ignore this setting.")
	fs.IntVar(&s.DeleteCollectionWorkers, "delete-collection-workers", s.DeleteCollectionWorkers,
		"Number of workers spawned for DeleteCollection call. These are used to speed up namespace cleanup.")

	fs.BoolVar(&s.EnableGarbageCollection, "enable-garbage-collector", s.EnableGarbageCollection, ""+
		"Enables the generic garbage collector. MUST be synced with the corresponding flag "+
		"of the kube-controller-manager.")

	fs.BoolVar(&s.EnableWatchCache, "watch-cache", s.EnableWatchCache,
		"Enable watch caching in the apiserver")

	fs.IntVar(&s.DefaultWatchCacheSize, "default-watch-cache-size", s.DefaultWatchCacheSize,
		"Default watch cache size. If zero, watch cache will be disabled for resources that do not have a default watch size set.")

	fs.StringSliceVar(&s.WatchCacheSizes, "watch-cache-sizes", s.WatchCacheSizes, ""+
		"Watch cache size settings for some resources (pods, nodes, etc.), comma separated. "+
		"The individual setting format: resource[.group]#size, where resource is lowercase plural (no version), "+
		"group is omitted for resources of apiVersion v1 (the legacy core API) and included for others, "+
		"and size is a number. It takes effect when watch-cache is enabled. "+
		"Some resources (replicationcontrollers, endpoints, nodes, pods, services, apiservices.apiregistration.k8s.io) "+
		"have system defaults set by heuristics, others default to default-watch-cache-size")

	fs.StringVar(&s.StorageConfig.Type, "storage-backend", s.StorageConfig.Type,
		"The storage backend for persistence. Options: 'etcd3' (default).")

	dummyCacheSize := 0
	fs.IntVar(&dummyCacheSize, "deserialization-cache-size", 0, "Number of deserialized json objects to cache in memory.")
	fs.MarkDeprecated("deserialization-cache-size", "the deserialization cache was dropped in 1.13 with support for etcd2")

	fs.StringSliceVar(&s.StorageConfig.Transport.ServerList, "etcd-servers", s.StorageConfig.Transport.ServerList,
		"List of etcd servers to connect with (scheme://ip:port), comma separated.")

	fs.StringVar(&s.StorageConfig.Prefix, "etcd-prefix", s.StorageConfig.Prefix,
		"The prefix to prepend to all resource paths in etcd.")

	fs.StringVar(&s.StorageConfig.Transport.KeyFile, "etcd-keyfile", s.StorageConfig.Transport.KeyFile,
		"SSL key file used to secure etcd communication.")

	fs.StringVar(&s.StorageConfig.Transport.CertFile, "etcd-certfile", s.StorageConfig.Transport.CertFile,
		"SSL certification file used to secure etcd communication.")

	fs.StringVar(&s.StorageConfig.Transport.TrustedCAFile, "etcd-cafile", s.StorageConfig.Transport.TrustedCAFile,
		"SSL Certificate Authority file used to secure etcd communication.")

	fs.StringVar(&s.EncryptionProviderConfigFilepath, "experimental-encryption-provider-config", s.EncryptionProviderConfigFilepath,
		"The file containing configuration for encryption providers to be used for storing secrets in etcd")
	fs.MarkDeprecated("experimental-encryption-provider-config", "use --encryption-provider-config.")

	fs.StringVar(&s.EncryptionProviderConfigFilepath, "encryption-provider-config", s.EncryptionProviderConfigFilepath,
		"The file containing configuration for encryption providers to be used for storing secrets in etcd")

	fs.DurationVar(&s.StorageConfig.CompactionInterval, "etcd-compaction-interval", s.StorageConfig.CompactionInterval,
		"The interval of compaction requests. If 0, the compaction request from apiserver is disabled.")

	fs.DurationVar(&s.StorageConfig.CountMetricPollPeriod, "etcd-count-metric-poll-period", s.StorageConfig.CountMetricPollPeriod, ""+
		"Frequency of polling etcd for number of resources per type. 0 disables the metric collection.")

	fs.DurationVar(&s.StorageConfig.DBMetricPollInterval, "etcd-db-metric-poll-interval", s.StorageConfig.DBMetricPollInterval,
		"The interval of requests to poll etcd and update metric. 0 disables the metric collection")

	fs.DurationVar(&s.StorageConfig.HealthcheckTimeout, "etcd-healthcheck-timeout", s.StorageConfig.HealthcheckTimeout,
		"The timeout to use when checking etcd health.")

	fs.Int64Var(&s.StorageConfig.LeaseManagerConfig.ReuseDurationSeconds, "lease-reuse-duration-seconds", s.StorageConfig.LeaseManagerConfig.ReuseDurationSeconds,
		"The time in seconds that each lease is reused. A lower value could avoid large number of objects reusing the same lease. Notice that a too small value may cause performance problems at storage layer.")
}

func (s *EtcdOptions) ApplyTo(c *server.Config) error {
	if s == nil {
		return nil
	}
	if err := s.addEtcdHealthEndpoint(c); err != nil {
		return err
	}
	transformerOverrides := make(map[schema.GroupResource]value.Transformer)
	if len(s.EncryptionProviderConfigFilepath) > 0 {
		var err error
		transformerOverrides, err = encryptionconfig.GetTransformerOverrides(s.EncryptionProviderConfigFilepath)
		if err != nil {
			return err
		}
	}

	c.RESTOptionsGetter = &SimpleRestOptionsFactory{
		Options:              *s,
		TransformerOverrides: transformerOverrides,
	}
	return nil
}

func (s *EtcdOptions) ApplyWithStorageFactoryTo(factory serverstorage.StorageFactory, c *server.Config) error {
	if err := s.addEtcdHealthEndpoint(c); err != nil {
		return err
	}
	c.RESTOptionsGetter = &StorageFactoryRestOptionsFactory{Options: *s, StorageFactory: factory}
	return nil
}

func (s *EtcdOptions) addEtcdHealthEndpoint(c *server.Config) error {
	healthCheck, err := storagefactory.CreateHealthCheck(s.StorageConfig)
	if err != nil {
		return err
	}
	c.AddHealthChecks(healthz.NamedCheck("etcd", func(r *http.Request) error {
		return healthCheck()
	}))

	if s.EncryptionProviderConfigFilepath != "" {
		kmsPluginHealthzChecks, err := encryptionconfig.GetKMSPluginHealthzCheckers(s.EncryptionProviderConfigFilepath)
		if err != nil {
			return err
		}
		c.AddHealthChecks(kmsPluginHealthzChecks...)
	}

	return nil
}

type SimpleRestOptionsFactory struct {
	Options              EtcdOptions
	TransformerOverrides map[schema.GroupResource]value.Transformer
}

func (f *SimpleRestOptionsFactory) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
	ret := generic.RESTOptions{
		StorageConfig:           &f.Options.StorageConfig,
		Decorator:               generic.UndecoratedStorage,
		EnableGarbageCollection: f.Options.EnableGarbageCollection,
		DeleteCollectionWorkers: f.Options.DeleteCollectionWorkers,
		ResourcePrefix:          resource.Group + "/" + resource.Resource,
		CountMetricPollPeriod:   f.Options.StorageConfig.CountMetricPollPeriod,
	}
	if f.TransformerOverrides != nil {
		if transformer, ok := f.TransformerOverrides[resource]; ok {
			ret.StorageConfig.Transformer = transformer
		}
	}
	if f.Options.EnableWatchCache {
		sizes, err := ParseWatchCacheSizes(f.Options.WatchCacheSizes)
		if err != nil {
			return generic.RESTOptions{}, err
		}
		size, ok := sizes[resource]
		if ok && size > 0 {
			klog.Warningf("Dropping watch-cache-size for %v - watchCache size is now dynamic", resource)
		}
		if ok && size <= 0 {
			ret.Decorator = generic.UndecoratedStorage
		} else {
			ret.Decorator = genericregistry.StorageWithCacher()
		}
	}
	return ret, nil
}

type StorageFactoryRestOptionsFactory struct {
	Options        EtcdOptions
	StorageFactory serverstorage.StorageFactory
}

func (f *StorageFactoryRestOptionsFactory) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
	storageConfig, err := f.StorageFactory.NewConfig(resource)
	if err != nil {
		return generic.RESTOptions{}, fmt.Errorf("unable to find storage destination for %v, due to %v", resource, err.Error())
	}

	ret := generic.RESTOptions{
		StorageConfig:           storageConfig,
		Decorator:               generic.UndecoratedStorage,
		DeleteCollectionWorkers: f.Options.DeleteCollectionWorkers,
		EnableGarbageCollection: f.Options.EnableGarbageCollection,
		ResourcePrefix:          f.StorageFactory.ResourcePrefix(resource),
		CountMetricPollPeriod:   f.Options.StorageConfig.CountMetricPollPeriod,
	}
	if f.Options.EnableWatchCache {
		sizes, err := ParseWatchCacheSizes(f.Options.WatchCacheSizes)
		if err != nil {
			return generic.RESTOptions{}, err
		}
		size, ok := sizes[resource]
		if ok && size > 0 {
			klog.Warningf("Dropping watch-cache-size for %v - watchCache size is now dynamic", resource)
		}
		if ok && size <= 0 {
			ret.Decorator = generic.UndecoratedStorage
		} else {
			ret.Decorator = genericregistry.StorageWithCacher()
		}
	}

	return ret, nil
}

// ParseWatchCacheSizes turns a list of cache size values into a map of group resources
// to requested sizes.
func ParseWatchCacheSizes(cacheSizes []string) (map[schema.GroupResource]int, error) {
	watchCacheSizes := make(map[schema.GroupResource]int)
	for _, c := range cacheSizes {
		tokens := strings.Split(c, "#")
		if len(tokens) != 2 {
			return nil, fmt.Errorf("invalid value of watch cache size: %s", c)
		}

		size, err := strconv.Atoi(tokens[1])
		if err != nil {
			return nil, fmt.Errorf("invalid size of watch cache size: %s", c)
		}
		if size < 0 {
			return nil, fmt.Errorf("watch cache size cannot be negative: %s", c)
		}
		watchCacheSizes[schema.ParseGroupResource(tokens[0])] = size
	}
	return watchCacheSizes, nil
}

// WriteWatchCacheSizes turns a map of cache size values into a list of string specifications.
func WriteWatchCacheSizes(watchCacheSizes map[schema.GroupResource]int) ([]string, error) {
	var cacheSizes []string

	for resource, size := range watchCacheSizes {
		if size < 0 {
			return nil, fmt.Errorf("watch cache size cannot be negative for resource %s", resource)
		}
		cacheSizes = append(cacheSizes, fmt.Sprintf("%s#%d", resource.String(), size))
	}
	return cacheSizes, nil
}
<|endoftext|>