<commit_before>/*
Copyright 2018 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 vsphere

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"runtime"

	v1 "k8s.io/api/core/v1"
	klog "k8s.io/klog/v2"

	cloudprovider "k8s.io/cloud-provider"

	"github.com/vmware/vsphere-automation-sdk-go/runtime/log"

	ccfg "k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/config"
	"k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/loadbalancer"
	lcfg "k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/loadbalancer/config"
	"k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/route"
	rcfg "k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/route/config"
	"k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/server"
	cm "k8s.io/cloud-provider-vsphere/pkg/common/connectionmanager"
	k8s "k8s.io/cloud-provider-vsphere/pkg/common/kubernetes"
	"k8s.io/cloud-provider-vsphere/pkg/nsxt"
	ncfg "k8s.io/cloud-provider-vsphere/pkg/nsxt/config"
)

const (
	// ProviderName is the name of the cloud provider registered with
	// Kubernetes.
	ProviderName string = "vsphere"
	// ClientName is the user agent passed into the controller client builder.
	ClientName string = "vsphere-cloud-controller-manager"

	// dualStackFeatureGateEnv is a required environment variable when enabling dual-stack nodes
	dualStackFeatureGateEnv string = "ENABLE_ALPHA_DUAL_STACK"
)

func init() {
	cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
		byConfig, err := ioutil.ReadAll(config)
		if err != nil {
			klog.Errorf("ReadAll failed: %s", err)
			return nil, err
		}

		cfg, err := ccfg.ReadCPIConfig(byConfig)
		if err != nil {
			return nil, err
		}
		nsxtcfg, err := ncfg.ReadNsxtConfig(byConfig)
		if err != nil {
			klog.Errorf("ReadNsxtConfig failed: %s", err)
			nsxtcfg = nil
		}
		lbcfg, err := lcfg.ReadLBConfig(byConfig)
		if err != nil {
			lbcfg = nil //Error reading LBConfig, explicitly set to nil
		}
		routecfg, err := rcfg.ReadRouteConfig(byConfig)
		if err != nil {
			klog.Errorf("ReadRouteConfig failed: %s", err)
			routecfg = nil
		}

		return newVSphere(cfg, nsxtcfg, lbcfg, routecfg, true)
	})
}

var _ cloudprovider.Interface = &VSphere{}

// Creates new Controller node interface and returns
func newVSphere(cfg *ccfg.CPIConfig, nsxtcfg *ncfg.Config, lbcfg *lcfg.LBConfig, routecfg *rcfg.Config, finalize ...bool) (*VSphere, error) {
	vs, err := buildVSphereFromConfig(cfg, nsxtcfg, lbcfg, routecfg)
	if err != nil {
		return nil, err
	}
	if len(finalize) == 1 && finalize[0] {
		// optional for use in tests
		runtime.SetFinalizer(vs, logout)
	}
	return vs, nil
}

// Initialize initializes the cloud provider.
func (vs *VSphere) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) {
	client, err := clientBuilder.Client(ClientName)
	if err == nil {
		klog.V(1).Info("Kubernetes Client Init Succeeded")

		vs.informMgr = k8s.NewInformer(client, true)

		connMgr := cm.NewConnectionManager(&vs.cfg.Config, vs.informMgr, client)
		vs.connectionManager = connMgr
		vs.nodeManager.connectionManager = connMgr

		vs.informMgr.AddNodeListener(vs.nodeAdded, vs.nodeDeleted, nil)

		vs.informMgr.Listen()

		// if running secrets, init them
		connMgr.InitializeSecretLister()

		if !vs.cfg.Global.APIDisable {
			klog.V(1).Info("Starting the API Server")
			vs.server.Start()
		} else {
			klog.V(1).Info("API Server is disabled")
		}
	} else {
		klog.Errorf("Kubernetes Client Init Failed: %v", err)
	}
	if vs.isLoadBalancerSupportEnabled() {
		klog.Info("initializing load balancer support")
		if loadbalancer.ClusterName == "" {
			klog.Warning("Missing cluster id, no periodical cleanup possible")
		}
		vs.loadbalancer.Initialize(loadbalancer.ClusterName, client, stop)
	}
	err = vs.nsxtConnectorMgr.AddSecretListener(vs.informMgr.GetSecretInformer())
	if err != nil {
		klog.Warning("Adding NSXT secret listener failed: %v", err)
	}
}

func (vs *VSphere) isLoadBalancerSupportEnabled() bool {
	return vs.loadbalancer != nil
}

// LoadBalancer returns a balancer interface. Also returns true if the
// interface is supported, false otherwise.
func (vs *VSphere) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
	if vs.isLoadBalancerSupportEnabled() {
		return vs.loadbalancer, true
	}
	klog.Warning("The vSphere cloud provider does not support load balancers")
	return nil, false
}

// Instances returns an instances interface. Also returns true if the
// interface is supported, false otherwise.
func (vs *VSphere) Instances() (cloudprovider.Instances, bool) {
	klog.V(6).Info("Calling the Instances interface on vSphere cloud provider")
	return vs.instances, true
}

// InstancesV2 returns an implementation of cloudprovider.InstancesV2.
//  TODO: implement this for v1.20
func (vs *VSphere) InstancesV2() (cloudprovider.InstancesV2, bool) {
	klog.Warning("The vSphere cloud provider does not support InstancesV2")
	return nil, false
}

// Zones returns a zones interface. Also returns true if the interface
// is supported, false otherwise.
func (vs *VSphere) Zones() (cloudprovider.Zones, bool) {
	klog.V(6).Info("Calling the Zones interface on vSphere cloud provider")
	return vs.zones, true
}

// Clusters returns a clusters interface.  Also returns true if the interface
// is supported, false otherwise.
func (vs *VSphere) Clusters() (cloudprovider.Clusters, bool) {
	klog.Warning("The vSphere cloud provider does not support clusters")
	return nil, false
}

// Routes returns a routes interface along with whether the interface
// is supported.
func (vs *VSphere) Routes() (cloudprovider.Routes, bool) {
	if vs.routes != nil {
		return vs.routes, true
	}
	klog.Warning("Routes interface was not configured")
	return nil, false
}

// ProviderName returns the cloud provider ID.
func (vs *VSphere) ProviderName() string {
	return ProviderName
}

// ScrubDNS is not implemented.
// TODO(akutz) Add better documentation for this function.
func (vs *VSphere) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) {
	return nil, nil
}

// HasClusterID returns true if a ClusterID is required and set/
func (vs *VSphere) HasClusterID() bool {
	return true
}

// Initializes vSphere from vSphere CloudProvider Configuration
func buildVSphereFromConfig(cfg *ccfg.CPIConfig, nsxtcfg *ncfg.Config, lbcfg *lcfg.LBConfig, routecfg *rcfg.Config) (*VSphere, error) {
	nm := newNodeManager(cfg, nil)

	ncm, err := nsxt.NewConnectorManager(nsxtcfg)
	if err != nil {
		return nil, err
	}

	lb, err := loadbalancer.NewLBProvider(lbcfg, ncm.GetConnector())
	if err != nil {
		return nil, err
	}

	routes, err := route.NewRouteProvider(routecfg, ncm.GetConnector())
	if err != nil {
		return nil, err
	}

	// redirect vapi logging from the NSX-T GO SDK to klog
	log.SetLogger(NewKlogBridge())

	err = validateDualStack(cfg)
	if err != nil {
		return nil, err
	}

	vs := VSphere{
		cfg:              cfg,
		cfgLB:            lbcfg,
		nodeManager:      nm,
		nsxtConnectorMgr: ncm,
		loadbalancer:     lb,
		routes:           routes,
		instances:        newInstances(nm),
		zones:            newZones(nm, cfg.Labels.Zone, cfg.Labels.Region),
		server:           server.NewServer(cfg.Global.APIBinding, nm),
	}
	return &vs, nil
}

// validateDualStack returns an error if dual-stack was configured but not enabled
// using the alpha environment variable feature gate ENABLE_ALPHA_DUAL_STACK
func validateDualStack(cfg *ccfg.CPIConfig) error {
	_, dualStackEnabled := os.LookupEnv(dualStackFeatureGateEnv)
	if dualStackEnabled {
		return nil
	}

	for vcName, vcConfig := range cfg.VirtualCenter {
		if len(vcConfig.IPFamilyPriority) > 1 {
			return fmt.Errorf("mulitple IP families specified for virtual center %q but ENABLE_ALPHA_DUAL_STACK env var is not set", vcName)
		}
	}

	return nil
}

func logout(vs *VSphere) {
	vs.connectionManager.Logout()
}

// Notification handler when node is added into k8s cluster.
func (vs *VSphere) nodeAdded(obj interface{}) {
	node, ok := obj.(*v1.Node)
	if node == nil || !ok {
		klog.Warningf("nodeAdded: unrecognized object %+v", obj)
		return
	}

	vs.nodeManager.RegisterNode(node)
	if vs.routes != nil {
		vs.routes.AddNode(node)
	}
}

// Notification handler when node is removed from k8s cluster.
func (vs *VSphere) nodeDeleted(obj interface{}) {
	node, ok := obj.(*v1.Node)
	if node == nil || !ok {
		klog.Warningf("nodeDeleted: unrecognized object %+v", obj)
		return
	}

	vs.nodeManager.UnregisterNode(node)
	if vs.routes != nil {
		vs.routes.DeleteNode(node)
	}
}
<commit_msg>remove InstancesV2 warning since it is very noisy<commit_after>/*
Copyright 2018 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 vsphere

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"runtime"

	v1 "k8s.io/api/core/v1"
	klog "k8s.io/klog/v2"

	cloudprovider "k8s.io/cloud-provider"

	"github.com/vmware/vsphere-automation-sdk-go/runtime/log"

	ccfg "k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/config"
	"k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/loadbalancer"
	lcfg "k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/loadbalancer/config"
	"k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/route"
	rcfg "k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/route/config"
	"k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/server"
	cm "k8s.io/cloud-provider-vsphere/pkg/common/connectionmanager"
	k8s "k8s.io/cloud-provider-vsphere/pkg/common/kubernetes"
	"k8s.io/cloud-provider-vsphere/pkg/nsxt"
	ncfg "k8s.io/cloud-provider-vsphere/pkg/nsxt/config"
)

const (
	// ProviderName is the name of the cloud provider registered with
	// Kubernetes.
	ProviderName string = "vsphere"
	// ClientName is the user agent passed into the controller client builder.
	ClientName string = "vsphere-cloud-controller-manager"

	// dualStackFeatureGateEnv is a required environment variable when enabling dual-stack nodes
	dualStackFeatureGateEnv string = "ENABLE_ALPHA_DUAL_STACK"
)

func init() {
	cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
		byConfig, err := ioutil.ReadAll(config)
		if err != nil {
			klog.Errorf("ReadAll failed: %s", err)
			return nil, err
		}

		cfg, err := ccfg.ReadCPIConfig(byConfig)
		if err != nil {
			return nil, err
		}
		nsxtcfg, err := ncfg.ReadNsxtConfig(byConfig)
		if err != nil {
			klog.Errorf("ReadNsxtConfig failed: %s", err)
			nsxtcfg = nil
		}
		lbcfg, err := lcfg.ReadLBConfig(byConfig)
		if err != nil {
			lbcfg = nil //Error reading LBConfig, explicitly set to nil
		}
		routecfg, err := rcfg.ReadRouteConfig(byConfig)
		if err != nil {
			klog.Errorf("ReadRouteConfig failed: %s", err)
			routecfg = nil
		}

		return newVSphere(cfg, nsxtcfg, lbcfg, routecfg, true)
	})
}

var _ cloudprovider.Interface = &VSphere{}

// Creates new Controller node interface and returns
func newVSphere(cfg *ccfg.CPIConfig, nsxtcfg *ncfg.Config, lbcfg *lcfg.LBConfig, routecfg *rcfg.Config, finalize ...bool) (*VSphere, error) {
	vs, err := buildVSphereFromConfig(cfg, nsxtcfg, lbcfg, routecfg)
	if err != nil {
		return nil, err
	}
	if len(finalize) == 1 && finalize[0] {
		// optional for use in tests
		runtime.SetFinalizer(vs, logout)
	}
	return vs, nil
}

// Initialize initializes the cloud provider.
func (vs *VSphere) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) {
	client, err := clientBuilder.Client(ClientName)
	if err == nil {
		klog.V(1).Info("Kubernetes Client Init Succeeded")

		vs.informMgr = k8s.NewInformer(client, true)

		connMgr := cm.NewConnectionManager(&vs.cfg.Config, vs.informMgr, client)
		vs.connectionManager = connMgr
		vs.nodeManager.connectionManager = connMgr

		vs.informMgr.AddNodeListener(vs.nodeAdded, vs.nodeDeleted, nil)

		vs.informMgr.Listen()

		// if running secrets, init them
		connMgr.InitializeSecretLister()

		if !vs.cfg.Global.APIDisable {
			klog.V(1).Info("Starting the API Server")
			vs.server.Start()
		} else {
			klog.V(1).Info("API Server is disabled")
		}
	} else {
		klog.Errorf("Kubernetes Client Init Failed: %v", err)
	}
	if vs.isLoadBalancerSupportEnabled() {
		klog.Info("initializing load balancer support")
		if loadbalancer.ClusterName == "" {
			klog.Warning("Missing cluster id, no periodical cleanup possible")
		}
		vs.loadbalancer.Initialize(loadbalancer.ClusterName, client, stop)
	}
	err = vs.nsxtConnectorMgr.AddSecretListener(vs.informMgr.GetSecretInformer())
	if err != nil {
		klog.Warning("Adding NSXT secret listener failed: %v", err)
	}
}

func (vs *VSphere) isLoadBalancerSupportEnabled() bool {
	return vs.loadbalancer != nil
}

// LoadBalancer returns a balancer interface. Also returns true if the
// interface is supported, false otherwise.
func (vs *VSphere) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
	if vs.isLoadBalancerSupportEnabled() {
		return vs.loadbalancer, true
	}
	klog.Warning("The vSphere cloud provider does not support load balancers")
	return nil, false
}

// Instances returns an instances interface. Also returns true if the
// interface is supported, false otherwise.
func (vs *VSphere) Instances() (cloudprovider.Instances, bool) {
	klog.V(6).Info("Calling the Instances interface on vSphere cloud provider")
	return vs.instances, true
}

// InstancesV2 returns an implementation of cloudprovider.InstancesV2.
//  TODO: implement this for v1.20
func (vs *VSphere) InstancesV2() (cloudprovider.InstancesV2, bool) {
	return nil, false
}

// Zones returns a zones interface. Also returns true if the interface
// is supported, false otherwise.
func (vs *VSphere) Zones() (cloudprovider.Zones, bool) {
	klog.V(6).Info("Calling the Zones interface on vSphere cloud provider")
	return vs.zones, true
}

// Clusters returns a clusters interface.  Also returns true if the interface
// is supported, false otherwise.
func (vs *VSphere) Clusters() (cloudprovider.Clusters, bool) {
	klog.Warning("The vSphere cloud provider does not support clusters")
	return nil, false
}

// Routes returns a routes interface along with whether the interface
// is supported.
func (vs *VSphere) Routes() (cloudprovider.Routes, bool) {
	if vs.routes != nil {
		return vs.routes, true
	}
	klog.Warning("Routes interface was not configured")
	return nil, false
}

// ProviderName returns the cloud provider ID.
func (vs *VSphere) ProviderName() string {
	return ProviderName
}

// ScrubDNS is not implemented.
// TODO(akutz) Add better documentation for this function.
func (vs *VSphere) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) {
	return nil, nil
}

// HasClusterID returns true if a ClusterID is required and set/
func (vs *VSphere) HasClusterID() bool {
	return true
}

// Initializes vSphere from vSphere CloudProvider Configuration
func buildVSphereFromConfig(cfg *ccfg.CPIConfig, nsxtcfg *ncfg.Config, lbcfg *lcfg.LBConfig, routecfg *rcfg.Config) (*VSphere, error) {
	nm := newNodeManager(cfg, nil)

	ncm, err := nsxt.NewConnectorManager(nsxtcfg)
	if err != nil {
		return nil, err
	}

	lb, err := loadbalancer.NewLBProvider(lbcfg, ncm.GetConnector())
	if err != nil {
		return nil, err
	}

	routes, err := route.NewRouteProvider(routecfg, ncm.GetConnector())
	if err != nil {
		return nil, err
	}

	// redirect vapi logging from the NSX-T GO SDK to klog
	log.SetLogger(NewKlogBridge())

	err = validateDualStack(cfg)
	if err != nil {
		return nil, err
	}

	vs := VSphere{
		cfg:              cfg,
		cfgLB:            lbcfg,
		nodeManager:      nm,
		nsxtConnectorMgr: ncm,
		loadbalancer:     lb,
		routes:           routes,
		instances:        newInstances(nm),
		zones:            newZones(nm, cfg.Labels.Zone, cfg.Labels.Region),
		server:           server.NewServer(cfg.Global.APIBinding, nm),
	}
	return &vs, nil
}

// validateDualStack returns an error if dual-stack was configured but not enabled
// using the alpha environment variable feature gate ENABLE_ALPHA_DUAL_STACK
func validateDualStack(cfg *ccfg.CPIConfig) error {
	_, dualStackEnabled := os.LookupEnv(dualStackFeatureGateEnv)
	if dualStackEnabled {
		return nil
	}

	for vcName, vcConfig := range cfg.VirtualCenter {
		if len(vcConfig.IPFamilyPriority) > 1 {
			return fmt.Errorf("mulitple IP families specified for virtual center %q but ENABLE_ALPHA_DUAL_STACK env var is not set", vcName)
		}
	}

	return nil
}

func logout(vs *VSphere) {
	vs.connectionManager.Logout()
}

// Notification handler when node is added into k8s cluster.
func (vs *VSphere) nodeAdded(obj interface{}) {
	node, ok := obj.(*v1.Node)
	if node == nil || !ok {
		klog.Warningf("nodeAdded: unrecognized object %+v", obj)
		return
	}

	vs.nodeManager.RegisterNode(node)
	if vs.routes != nil {
		vs.routes.AddNode(node)
	}
}

// Notification handler when node is removed from k8s cluster.
func (vs *VSphere) nodeDeleted(obj interface{}) {
	node, ok := obj.(*v1.Node)
	if node == nil || !ok {
		klog.Warningf("nodeDeleted: unrecognized object %+v", obj)
		return
	}

	vs.nodeManager.UnregisterNode(node)
	if vs.routes != nil {
		vs.routes.DeleteNode(node)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Knative 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 receiver

import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"sync/atomic"
	"time"

	cloudeventsbindings "github.com/cloudevents/sdk-go/v2/binding"
	cloudeventshttp "github.com/cloudevents/sdk-go/v2/protocol/http"
	"github.com/kelseyhightower/envconfig"
	"go.uber.org/zap"
	"knative.dev/pkg/logging"

	"knative.dev/reconciler-test/pkg/eventshub"
	"knative.dev/reconciler-test/pkg/eventshub/dropevents"
)

// Receiver is the entry point for sinking events into the event log.
type Receiver struct {
	// Name is the name of this Receiver, used to filter if multiple observers.
	Name string
	// EventLogs is the list of EventLogger implementors to vent observed events.
	EventLogs *eventshub.EventLogs

	ctx                 context.Context
	seq                 uint64
	dropSeq             uint64
	replyFunc           func(context.Context, http.ResponseWriter, eventshub.EventInfo)
	counter             *dropevents.CounterHandler
	responseWaitTime    time.Duration
	skipResponseCode    int
	skipResponseHeaders map[string]string
}

type envConfig struct {
	// ReceiverName is used to identify this instance of the receiver.
	ReceiverName string `envconfig:"POD_NAME" default:"receiver-default" required:"true"`

	// ResponseWaitTime is the seconds to wait for the eventshub to write any response
	ResponseWaitTime int `envconfig:"RESPONSE_WAIT_TIME" default:"0" required:"false"`

	// Reply is used to define if the observer should reply back
	Reply bool `envconfig:"REPLY" default:"false" required:"false"`

	// The event type to use in the reply, if enabled
	ReplyEventType string `envconfig:"REPLY_EVENT_TYPE" default:"" required:"false"`

	// The event source to use in the reply, if enabled
	ReplyEventSource string `envconfig:"REPLY_EVENT_SOURCE" default:"" required:"false"`

	// The event data to use in the reply, if enabled
	ReplyEventData string `envconfig:"REPLY_EVENT_DATA" default:"" required:"false"`

	// This string to append in the data field in the reply, if enabled.
	// This will threat the data as text/plain field
	ReplyAppendData string `envconfig:"REPLY_APPEND_DATA" default:"" required:"false"`

	// If events should be dropped, specify the strategy here.
	SkipStrategy string `envconfig:"SKIP_ALGORITHM" default:"" required:"false"`

	// If events should be dropped according to Linear policy, this controls
	// how many events are dropped.
	SkipCounter uint64 `envconfig:"SKIP_COUNTER" default:"0" required:"false"`

	// If events should be dropped, specify the HTTP response code here.
	SkipResponseCode int `envconfig:"SKIP_RESPONSE_CODE" default:"409" required:"false"`

	// If events should be dropped, specify additional HTTP Headers to return in response.
	SkipResponseHeaders map[string]string `envconfig:"SKIP_RESPONSE_HEADERS" default:"" required:"false"`
}

func NewFromEnv(ctx context.Context, eventLogs *eventshub.EventLogs) *Receiver {
	var env envConfig
	if err := envconfig.Process("", &env); err != nil {
		logging.FromContext(ctx).Fatal("Failed to process env var", err)
	}

	logging.FromContext(ctx).Infof("Receiver environment configuration: %+v", env)

	var replyFunc func(context.Context, http.ResponseWriter, eventshub.EventInfo)
	if env.Reply {
		logging.FromContext(ctx).Info("Receiver will reply with an event")
		replyFunc = ReplyTransformerFunc(env.ReplyEventType, env.ReplyEventSource, env.ReplyEventData, env.ReplyAppendData)
	} else {
		logging.FromContext(ctx).Info("Receiver won't reply with an event")
		replyFunc = NoOpReply
	}
	var counter *dropevents.CounterHandler

	if env.SkipStrategy != "" {
		counter = &dropevents.CounterHandler{
			Skipper: dropevents.SkipperAlgorithmWithCount(env.SkipStrategy, env.SkipCounter),
		}
	} else {
		counter = &dropevents.CounterHandler{
			// Don't skip anything, since count is 0. nop skipper.
			Skipper: dropevents.NoopSkipper,
		}
	}

	var responseWaitTime time.Duration
	if env.ResponseWaitTime != 0 {
		responseWaitTime = time.Duration(env.ResponseWaitTime) * time.Second
	}

	return &Receiver{
		Name:                env.ReceiverName,
		EventLogs:           eventLogs,
		ctx:                 ctx,
		replyFunc:           replyFunc,
		counter:             counter,
		responseWaitTime:    responseWaitTime,
		skipResponseCode:    env.SkipResponseCode,
		skipResponseHeaders: env.SkipResponseHeaders,
	}
}

// Start will create the CloudEvents client and start listening for inbound
// HTTP requests. This is a is a blocking call.
func (o *Receiver) Start(ctx context.Context, handlerFuncs ...func(handler http.Handler) http.Handler) error {
	var handler http.Handler = o

	for _, dec := range handlerFuncs {
		handler = dec(handler)
	}

	server := &http.Server{Addr: ":8080", Handler: handler}

	var err error
	go func() {
		err = server.ListenAndServe()
	}()

	<-ctx.Done()

	if err != nil {
		return fmt.Errorf("error while starting the HTTP server: %w", err)
	}

	logging.FromContext(ctx).Info("Closing the HTTP server")

	return server.Close()
}

func (o *Receiver) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	// Special case probe events && readiness probe.
	if request.Method == http.MethodHead || request.URL.Path == "/health/ready" {
		code := http.StatusOK
		writer.WriteHeader(code)
		_, _ = writer.Write([]byte(http.StatusText(code)))
		return
	}

	m := cloudeventshttp.NewMessageFromHttpRequest(request)
	defer m.Finish(nil)

	event, eventErr := cloudeventsbindings.ToEvent(context.TODO(), m)
	headers := make(http.Header)
	for k, v := range request.Header {
		if !strings.HasPrefix(k, "Ce-") {
			headers[k] = v
		}
	}
	// Host header is removed from the request.Header map by net/http
	if request.Host != "" {
		headers.Set("Host", request.Host)
	}

	eventErrStr := ""
	if eventErr != nil {
		eventErrStr = eventErr.Error()
	}

	shouldSkip := o.counter.Skip()
	var s uint64
	var kind eventshub.EventKind
	if shouldSkip {
		kind = eventshub.EventRejected
		s = atomic.AddUint64(&o.dropSeq, 1)
	} else {
		kind = eventshub.EventReceived
		s = atomic.AddUint64(&o.seq, 1)
	}

	eventInfo := eventshub.EventInfo{
		Error:       eventErrStr,
		Event:       event,
		HTTPHeaders: headers,
		Origin:      request.RemoteAddr,
		Observer:    o.Name,
		Time:        time.Now(),
		Sequence:    s,
		Kind:        kind,
	}

	if err := o.EventLogs.Vent(eventInfo); err != nil {
		logging.FromContext(o.ctx).Fatalw("Error while venting the recorded event", zap.Error(err))
	}

	if o.responseWaitTime != 0 {
		logging.FromContext(o.ctx).Debugf("Waiting for %v before replying", o.responseWaitTime.String())
		time.Sleep(o.responseWaitTime)
	}

	if shouldSkip {
		// Trigger a redelivery
		for headerKey, headerValue := range o.skipResponseHeaders {
			writer.Header().Set(headerKey, headerValue)
		}
		writer.WriteHeader(o.skipResponseCode)
	} else {
		o.replyFunc(o.ctx, writer, eventInfo)
	}
}
<commit_msg>[eventshub] Allow defining a custom response body for skipped events (#358)<commit_after>/*
Copyright 2020 The Knative 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 receiver

import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"sync/atomic"
	"time"

	cloudeventsbindings "github.com/cloudevents/sdk-go/v2/binding"
	cloudeventshttp "github.com/cloudevents/sdk-go/v2/protocol/http"
	"github.com/kelseyhightower/envconfig"
	"go.uber.org/zap"
	"knative.dev/pkg/logging"

	"knative.dev/reconciler-test/pkg/eventshub"
	"knative.dev/reconciler-test/pkg/eventshub/dropevents"
)

// Receiver is the entry point for sinking events into the event log.
type Receiver struct {
	// Name is the name of this Receiver, used to filter if multiple observers.
	Name string
	// EventLogs is the list of EventLogger implementors to vent observed events.
	EventLogs *eventshub.EventLogs

	ctx                 context.Context
	seq                 uint64
	dropSeq             uint64
	replyFunc           func(context.Context, http.ResponseWriter, eventshub.EventInfo)
	counter             *dropevents.CounterHandler
	responseWaitTime    time.Duration
	skipResponseCode    int
	skipResponseHeaders map[string]string
	skipResponseBody    string
}

type envConfig struct {
	// ReceiverName is used to identify this instance of the receiver.
	ReceiverName string `envconfig:"POD_NAME" default:"receiver-default" required:"true"`

	// ResponseWaitTime is the seconds to wait for the eventshub to write any response
	ResponseWaitTime int `envconfig:"RESPONSE_WAIT_TIME" default:"0" required:"false"`

	// Reply is used to define if the observer should reply back
	Reply bool `envconfig:"REPLY" default:"false" required:"false"`

	// The event type to use in the reply, if enabled
	ReplyEventType string `envconfig:"REPLY_EVENT_TYPE" default:"" required:"false"`

	// The event source to use in the reply, if enabled
	ReplyEventSource string `envconfig:"REPLY_EVENT_SOURCE" default:"" required:"false"`

	// The event data to use in the reply, if enabled
	ReplyEventData string `envconfig:"REPLY_EVENT_DATA" default:"" required:"false"`

	// This string to append in the data field in the reply, if enabled.
	// This will threat the data as text/plain field
	ReplyAppendData string `envconfig:"REPLY_APPEND_DATA" default:"" required:"false"`

	// If events should be dropped, specify the strategy here.
	SkipStrategy string `envconfig:"SKIP_ALGORITHM" default:"" required:"false"`

	// If events should be dropped according to Linear policy, this controls
	// how many events are dropped.
	SkipCounter uint64 `envconfig:"SKIP_COUNTER" default:"0" required:"false"`

	// If events should be dropped, specify the HTTP response code here.
	SkipResponseCode int `envconfig:"SKIP_RESPONSE_CODE" default:"409" required:"false"`

	// If events should be dropped, specify the HTTP response body here.
	SkipResponseBody string `envconfig:"SKIP_RESPONSE_BODY" default:"" required:"false"`

	// If events should be dropped, specify additional HTTP Headers to return in response.
	SkipResponseHeaders map[string]string `envconfig:"SKIP_RESPONSE_HEADERS" default:"" required:"false"`
}

func NewFromEnv(ctx context.Context, eventLogs *eventshub.EventLogs) *Receiver {
	var env envConfig
	if err := envconfig.Process("", &env); err != nil {
		logging.FromContext(ctx).Fatal("Failed to process env var", err)
	}

	logging.FromContext(ctx).Infof("Receiver environment configuration: %+v", env)

	var replyFunc func(context.Context, http.ResponseWriter, eventshub.EventInfo)
	if env.Reply {
		logging.FromContext(ctx).Info("Receiver will reply with an event")
		replyFunc = ReplyTransformerFunc(env.ReplyEventType, env.ReplyEventSource, env.ReplyEventData, env.ReplyAppendData)
	} else {
		logging.FromContext(ctx).Info("Receiver won't reply with an event")
		replyFunc = NoOpReply
	}
	var counter *dropevents.CounterHandler

	if env.SkipStrategy != "" {
		counter = &dropevents.CounterHandler{
			Skipper: dropevents.SkipperAlgorithmWithCount(env.SkipStrategy, env.SkipCounter),
		}
	} else {
		counter = &dropevents.CounterHandler{
			// Don't skip anything, since count is 0. nop skipper.
			Skipper: dropevents.NoopSkipper,
		}
	}

	var responseWaitTime time.Duration
	if env.ResponseWaitTime != 0 {
		responseWaitTime = time.Duration(env.ResponseWaitTime) * time.Second
	}

	return &Receiver{
		Name:                env.ReceiverName,
		EventLogs:           eventLogs,
		ctx:                 ctx,
		replyFunc:           replyFunc,
		counter:             counter,
		responseWaitTime:    responseWaitTime,
		skipResponseCode:    env.SkipResponseCode,
		skipResponseBody:    env.SkipResponseBody,
		skipResponseHeaders: env.SkipResponseHeaders,
	}
}

// Start will create the CloudEvents client and start listening for inbound
// HTTP requests. This is a is a blocking call.
func (o *Receiver) Start(ctx context.Context, handlerFuncs ...func(handler http.Handler) http.Handler) error {
	var handler http.Handler = o

	for _, dec := range handlerFuncs {
		handler = dec(handler)
	}

	server := &http.Server{Addr: ":8080", Handler: handler}

	var err error
	go func() {
		err = server.ListenAndServe()
	}()

	<-ctx.Done()

	if err != nil {
		return fmt.Errorf("error while starting the HTTP server: %w", err)
	}

	logging.FromContext(ctx).Info("Closing the HTTP server")

	return server.Close()
}

func (o *Receiver) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	// Special case probe events && readiness probe.
	if request.Method == http.MethodHead || request.URL.Path == "/health/ready" {
		code := http.StatusOK
		writer.WriteHeader(code)
		_, _ = writer.Write([]byte(http.StatusText(code)))
		return
	}

	m := cloudeventshttp.NewMessageFromHttpRequest(request)
	defer m.Finish(nil)

	event, eventErr := cloudeventsbindings.ToEvent(context.TODO(), m)
	headers := make(http.Header)
	for k, v := range request.Header {
		if !strings.HasPrefix(k, "Ce-") {
			headers[k] = v
		}
	}
	// Host header is removed from the request.Header map by net/http
	if request.Host != "" {
		headers.Set("Host", request.Host)
	}

	eventErrStr := ""
	if eventErr != nil {
		eventErrStr = eventErr.Error()
	}

	shouldSkip := o.counter.Skip()
	var s uint64
	var kind eventshub.EventKind
	if shouldSkip {
		kind = eventshub.EventRejected
		s = atomic.AddUint64(&o.dropSeq, 1)
	} else {
		kind = eventshub.EventReceived
		s = atomic.AddUint64(&o.seq, 1)
	}

	eventInfo := eventshub.EventInfo{
		Error:       eventErrStr,
		Event:       event,
		HTTPHeaders: headers,
		Origin:      request.RemoteAddr,
		Observer:    o.Name,
		Time:        time.Now(),
		Sequence:    s,
		Kind:        kind,
	}

	if err := o.EventLogs.Vent(eventInfo); err != nil {
		logging.FromContext(o.ctx).Fatalw("Error while venting the recorded event", zap.Error(err))
	}

	if o.responseWaitTime != 0 {
		logging.FromContext(o.ctx).Debugf("Waiting for %v before replying", o.responseWaitTime.String())
		time.Sleep(o.responseWaitTime)
	}

	if shouldSkip {
		// Trigger a redelivery
		for headerKey, headerValue := range o.skipResponseHeaders {
			writer.Header().Set(headerKey, headerValue)
		}
		writer.WriteHeader(o.skipResponseCode)
		_, _ = writer.Write([]byte(o.skipResponseBody))
	} else {
		o.replyFunc(o.ctx, writer, eventInfo)
	}
}
<|endoftext|>


<commit_before>package kubecli

import (
	"errors"

	"kubevirt.io/kubevirt/pkg/api/v1"
	k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/clientcmd"
)

// GetMockKubevirtClientFromClientConfig, MockKubevirtClientInstance and InvalidMockClient are used to create a mechanism
// for overriding the actual kubevirt client access. This is useful when the unit tested code invokes GetKubevirtClientFromClientConfig()
// and therefore the unit test code cannot generate the mock client directly. In such a case following steps are needed:
// (1) set InvalidMockClient to true/false to control the behavior of GetKubevirtClientFromClientConfig()
// (2) if set to "false", you should override the GetKubevirtClientFromClientConfig() closure with GetMockKubevirtClientFromClientConfig()
// (3) then create the instance of the client, and assign into MockKubevirtClientInstance before the tests start:
//     ctrl := gomock.NewController(GinkgoT())
//     kubecli.MockKubevirtClientInstance = kubecli.NewMockKubevirtClient(ctrl)
// (4) rest of the kubevirt mocking is automatically generated in generated_mock_kubevirt.go

// InvalidMockClient is a parameter to control behavior of mock objects
var InvalidMockClient bool

// MockKubevirtClientInstance is a reference to the kubevirt client that could be manipulated by the test code
var MockKubevirtClientInstance *MockKubevirtClient

// GetMockKubevirtClientFromClientConfig is an entry point for testing, could be used to override GetKubevirtClientFromClientConfig
func GetMockKubevirtClientFromClientConfig(cmdConfig clientcmd.ClientConfig) (KubevirtClient, error) {
	if InvalidMockClient {
		return nil, errors.New("invalid fake client")
	}
	return MockKubevirtClientInstance, nil
}

func NewMinimalOVM(name string) *v1.OfflineVirtualMachine {
	return &v1.OfflineVirtualMachine{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1.GroupVersion.String(), Kind: "OfflineVirtualMachine"}, ObjectMeta: k8smetav1.ObjectMeta{Name: name}}
}

func NewOVMList(ovms ...v1.OfflineVirtualMachine) *v1.OfflineVirtualMachineList {
	return &v1.OfflineVirtualMachineList{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1.GroupVersion.String(), Kind: "OfflineVirtualMachineList"}, Items: ovms}
}

func NewVMReplicaSetList(rss ...v1.VirtualMachineReplicaSet) *v1.VirtualMachineReplicaSetList {
	return &v1.VirtualMachineReplicaSetList{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1.GroupVersion.String(), Kind: "VirtualMachineReplicaSetList"}, Items: rss}
}

func NewMinimalVMReplicaSet(name string) *v1.VirtualMachineReplicaSet {
	return &v1.VirtualMachineReplicaSet{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1.GroupVersion.String(), Kind: "VirtualMachineReplicaSet"}, ObjectMeta: k8smetav1.ObjectMeta{Name: name}}
}

func NewVMList(vms ...v1.VirtualMachine) *v1.VirtualMachineList {
	return &v1.VirtualMachineList{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1.GroupVersion.String(), Kind: "VirtualMachineList"}, Items: vms}
}
<commit_msg>missing 'make generate'<commit_after>package kubecli

import (
	"errors"

	k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/clientcmd"

	"kubevirt.io/kubevirt/pkg/api/v1"
)

// GetMockKubevirtClientFromClientConfig, MockKubevirtClientInstance and InvalidMockClient are used to create a mechanism
// for overriding the actual kubevirt client access. This is useful when the unit tested code invokes GetKubevirtClientFromClientConfig()
// and therefore the unit test code cannot generate the mock client directly. In such a case following steps are needed:
// (1) set InvalidMockClient to true/false to control the behavior of GetKubevirtClientFromClientConfig()
// (2) if set to "false", you should override the GetKubevirtClientFromClientConfig() closure with GetMockKubevirtClientFromClientConfig()
// (3) then create the instance of the client, and assign into MockKubevirtClientInstance before the tests start:
//     ctrl := gomock.NewController(GinkgoT())
//     kubecli.MockKubevirtClientInstance = kubecli.NewMockKubevirtClient(ctrl)
// (4) rest of the kubevirt mocking is automatically generated in generated_mock_kubevirt.go

// InvalidMockClient is a parameter to control behavior of mock objects
var InvalidMockClient bool

// MockKubevirtClientInstance is a reference to the kubevirt client that could be manipulated by the test code
var MockKubevirtClientInstance *MockKubevirtClient

// GetMockKubevirtClientFromClientConfig is an entry point for testing, could be used to override GetKubevirtClientFromClientConfig
func GetMockKubevirtClientFromClientConfig(cmdConfig clientcmd.ClientConfig) (KubevirtClient, error) {
	if InvalidMockClient {
		return nil, errors.New("invalid fake client")
	}
	return MockKubevirtClientInstance, nil
}

func NewMinimalOVM(name string) *v1.OfflineVirtualMachine {
	return &v1.OfflineVirtualMachine{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1.GroupVersion.String(), Kind: "OfflineVirtualMachine"}, ObjectMeta: k8smetav1.ObjectMeta{Name: name}}
}

func NewOVMList(ovms ...v1.OfflineVirtualMachine) *v1.OfflineVirtualMachineList {
	return &v1.OfflineVirtualMachineList{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1.GroupVersion.String(), Kind: "OfflineVirtualMachineList"}, Items: ovms}
}

func NewVMReplicaSetList(rss ...v1.VirtualMachineReplicaSet) *v1.VirtualMachineReplicaSetList {
	return &v1.VirtualMachineReplicaSetList{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1.GroupVersion.String(), Kind: "VirtualMachineReplicaSetList"}, Items: rss}
}

func NewMinimalVMReplicaSet(name string) *v1.VirtualMachineReplicaSet {
	return &v1.VirtualMachineReplicaSet{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1.GroupVersion.String(), Kind: "VirtualMachineReplicaSet"}, ObjectMeta: k8smetav1.ObjectMeta{Name: name}}
}

func NewVMList(vms ...v1.VirtualMachine) *v1.VirtualMachineList {
	return &v1.VirtualMachineList{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1.GroupVersion.String(), Kind: "VirtualMachineList"}, Items: vms}
}
<|endoftext|>


<commit_before>package diagnostics

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"

	"k8s.io/client-go/tools/clientcmd"
	kclientcmd "k8s.io/client-go/tools/clientcmd"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"

	"github.com/openshift/origin/pkg/oc/admin/diagnostics/diagnostics/util"
)

// use the base factory to return a raw config (not specific to a context)
func (o DiagnosticsOptions) buildRawConfig() (*clientcmdapi.Config, error) {
	kubeConfig, configErr := o.Factory.RawConfig()
	if configErr != nil {
		return nil, configErr
	}
	if len(kubeConfig.Contexts) == 0 {
		return nil, errors.New("No contexts found in config file.")
	}
	return &kubeConfig, nil
}

// determine if we even have a client config
func (o DiagnosticsOptions) detectClientConfig() (expected bool, detected bool) {
	if o.ClientFlags == nil {
		// options for client not provided, so it must not be expected.
		return false, false
	}
	o.Logger().Notice("CED2011", "Determining if client configuration exists for client/cluster diagnostics")
	confFlagName := kclientcmd.OpenShiftKubeConfigFlagName
	confFlagValue := o.ClientFlags.Lookup(confFlagName).Value.String()
	successfulLoad := false

	var foundPath string
	rules := kclientcmd.NewDefaultClientConfigLoadingRules()
	paths := append([]string{confFlagValue}, rules.Precedence...)
	for index, path := range paths {
		errmsg := ""
		switch index {
		case 0:
			errmsg = fmt.Sprintf("--%s specified that client config should be at %s\n", confFlagName, path)
		case len(paths) - 1: // config in ~/.kube
		// no error message indicated if it is not there... user didn't say it would be
		default: // can be multiple paths from the env var in theory; all cases should go here
			if len(os.Getenv(kclientcmd.RecommendedConfigPathEnvVar)) != 0 {
				errmsg = fmt.Sprintf("Env var %s specified that client config could be at %s\n", kclientcmd.RecommendedConfigPathEnvVar, path)
			}
		}

		if o.canOpenConfigFile(path, errmsg) && len(foundPath) == 0 {
			successfulLoad = true
			foundPath = path
		}
	}
	if len(foundPath) > 0 {
		if len(confFlagValue) > 0 && confFlagValue != foundPath {
			// found config but not where --config said
			o.Logger().Error("DCli1001", fmt.Sprintf(`
The client configuration file was not found where the --%s flag indicated:
  %s
A config file was found at the following location:
  %s
If you wish to use this file for client configuration, you can specify it
with the --%[1]s flag, or just not specify the flag.
			`, confFlagName, confFlagValue, foundPath))
		}
	} else { // not found, check for master-generated ones to recommend
		if len(confFlagValue) > 0 {
			o.Logger().Error("DCli1002", fmt.Sprintf("Did not find config file where --%s=%s indicated", confFlagName, confFlagValue))
		}
		adminWarningF := `
No client config file was available; however, one exists at
    %[2]s
which may have been generated automatically by the master.
If you want to use this config, you should copy it to the
standard location (%[3]s),
or you can set the environment variable %[1]s:
    export %[1]s=%[2]s
If not, obtain a config file and place it in the standard
location for use by the client and diagnostics.
`
		// look for it in auto-generated locations when not found properly
		for _, path := range util.AdminKubeConfigPaths {
			msg := fmt.Sprintf("Looking for a possible client config at %s\n", path)
			if o.canOpenConfigFile(path, msg) {
				o.Logger().Warn("DCli1003", fmt.Sprintf(adminWarningF, kclientcmd.RecommendedConfigPathEnvVar, path, kclientcmd.RecommendedHomeFile))
				break
			}
		}
	}
	return true, successfulLoad
}

// ----------------------------------------------------------
// Attempt to open file at path as client config
// If there is a problem and errmsg is set, log an error
func (o DiagnosticsOptions) canOpenConfigFile(path string, errmsg string) bool {
	var (
		file *os.File
		err  error
	)
	if len(path) == 0 { // empty param/envvar
		return false
	} else if file, err = os.Open(path); err == nil {
		o.Logger().Debug("DCli1004", fmt.Sprintf("Reading client config at %s", path))
	} else if len(errmsg) == 0 {
		o.Logger().Debug("DCli1005", fmt.Sprintf("Could not read client config at %s:\n%#v", path, err))
	} else if os.IsNotExist(err) {
		o.Logger().Debug("DCli1006", errmsg+"but that file does not exist.")
	} else if os.IsPermission(err) {
		o.Logger().Error("DCli1007", errmsg+"but lack permission to read that file.")
	} else {
		o.Logger().Error("DCli1008", fmt.Sprintf("%sbut there was an error opening it:\n%#v", errmsg, err))
	}
	if file == nil {
		return false
	}

	// file is open for reading
	defer file.Close()
	if buffer, err := ioutil.ReadAll(file); err != nil {
		o.Logger().Error("DCli1009", fmt.Sprintf("Unexpected error while reading client config file (%s): %v", path, err))
	} else if _, err := clientcmd.Load(buffer); err != nil {
		o.Logger().Error("DCli1010", fmt.Sprintf(`
Error reading YAML from client config file (%s):
  %v
This file may have been truncated or mis-edited.
Please fix, remove, or obtain a new client config`, file.Name(), err))
	} else {
		o.Logger().Info("DCli1011", fmt.Sprintf("Successfully read a client config file at '%s'", path))
		/* Note, we're not going to use this config file directly.
		 * Instead, we'll defer to the openshift client code to assimilate
		 * flags, env vars, and the potential hierarchy of config files
		 * into an actual configuration that the client uses.
		 * However, for diagnostic purposes, record the files we find.
		 */
		return true
	}
	return false
}
<commit_msg>Break a loop when config file found<commit_after>package diagnostics

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"

	"k8s.io/client-go/tools/clientcmd"
	kclientcmd "k8s.io/client-go/tools/clientcmd"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"

	"github.com/openshift/origin/pkg/oc/admin/diagnostics/diagnostics/util"
)

// use the base factory to return a raw config (not specific to a context)
func (o DiagnosticsOptions) buildRawConfig() (*clientcmdapi.Config, error) {
	kubeConfig, configErr := o.Factory.RawConfig()
	if configErr != nil {
		return nil, configErr
	}
	if len(kubeConfig.Contexts) == 0 {
		return nil, errors.New("No contexts found in config file.")
	}
	return &kubeConfig, nil
}

// determine if we even have a client config
func (o DiagnosticsOptions) detectClientConfig() (expected bool, detected bool) {
	if o.ClientFlags == nil {
		// options for client not provided, so it must not be expected.
		return false, false
	}
	o.Logger().Notice("CED2011", "Determining if client configuration exists for client/cluster diagnostics")
	confFlagName := kclientcmd.OpenShiftKubeConfigFlagName
	confFlagValue := o.ClientFlags.Lookup(confFlagName).Value.String()
	successfulLoad := false

	var foundPath string
	rules := kclientcmd.NewDefaultClientConfigLoadingRules()
	paths := append([]string{confFlagValue}, rules.Precedence...)
	for index, path := range paths {
		errmsg := ""
		switch index {
		case 0:
			errmsg = fmt.Sprintf("--%s specified that client config should be at %s\n", confFlagName, path)
		case len(paths) - 1: // config in ~/.kube
		// no error message indicated if it is not there... user didn't say it would be
		default: // can be multiple paths from the env var in theory; all cases should go here
			if len(os.Getenv(kclientcmd.RecommendedConfigPathEnvVar)) != 0 {
				errmsg = fmt.Sprintf("Env var %s specified that client config could be at %s\n", kclientcmd.RecommendedConfigPathEnvVar, path)
			}
		}

		if o.canOpenConfigFile(path, errmsg) {
			successfulLoad = true
			foundPath = path
			break
		}
	}
	if len(foundPath) > 0 {
		if len(confFlagValue) > 0 && confFlagValue != foundPath {
			// found config but not where --config said
			o.Logger().Error("DCli1001", fmt.Sprintf(`
The client configuration file was not found where the --%s flag indicated:
  %s
A config file was found at the following location:
  %s
If you wish to use this file for client configuration, you can specify it
with the --%[1]s flag, or just not specify the flag.
			`, confFlagName, confFlagValue, foundPath))
		}
	} else { // not found, check for master-generated ones to recommend
		if len(confFlagValue) > 0 {
			o.Logger().Error("DCli1002", fmt.Sprintf("Did not find config file where --%s=%s indicated", confFlagName, confFlagValue))
		}
		adminWarningF := `
No client config file was available; however, one exists at
    %[2]s
which may have been generated automatically by the master.
If you want to use this config, you should copy it to the
standard location (%[3]s),
or you can set the environment variable %[1]s:
    export %[1]s=%[2]s
If not, obtain a config file and place it in the standard
location for use by the client and diagnostics.
`
		// look for it in auto-generated locations when not found properly
		for _, path := range util.AdminKubeConfigPaths {
			msg := fmt.Sprintf("Looking for a possible client config at %s\n", path)
			if o.canOpenConfigFile(path, msg) {
				o.Logger().Warn("DCli1003", fmt.Sprintf(adminWarningF, kclientcmd.RecommendedConfigPathEnvVar, path, kclientcmd.RecommendedHomeFile))
				break
			}
		}
	}
	return true, successfulLoad
}

// ----------------------------------------------------------
// Attempt to open file at path as client config
// If there is a problem and errmsg is set, log an error
func (o DiagnosticsOptions) canOpenConfigFile(path string, errmsg string) bool {
	var (
		file *os.File
		err  error
	)
	if len(path) == 0 { // empty param/envvar
		return false
	} else if file, err = os.Open(path); err == nil {
		o.Logger().Debug("DCli1004", fmt.Sprintf("Reading client config at %s", path))
	} else if len(errmsg) == 0 {
		o.Logger().Debug("DCli1005", fmt.Sprintf("Could not read client config at %s:\n%#v", path, err))
	} else if os.IsNotExist(err) {
		o.Logger().Debug("DCli1006", errmsg+"but that file does not exist.")
	} else if os.IsPermission(err) {
		o.Logger().Error("DCli1007", errmsg+"but lack permission to read that file.")
	} else {
		o.Logger().Error("DCli1008", fmt.Sprintf("%sbut there was an error opening it:\n%#v", errmsg, err))
	}
	if file == nil {
		return false
	}

	// file is open for reading
	defer file.Close()
	if buffer, err := ioutil.ReadAll(file); err != nil {
		o.Logger().Error("DCli1009", fmt.Sprintf("Unexpected error while reading client config file (%s): %v", path, err))
	} else if _, err := clientcmd.Load(buffer); err != nil {
		o.Logger().Error("DCli1010", fmt.Sprintf(`
Error reading YAML from client config file (%s):
  %v
This file may have been truncated or mis-edited.
Please fix, remove, or obtain a new client config`, file.Name(), err))
	} else {
		o.Logger().Info("DCli1011", fmt.Sprintf("Successfully read a client config file at '%s'", path))
		/* Note, we're not going to use this config file directly.
		 * Instead, we'll defer to the openshift client code to assimilate
		 * flags, env vars, and the potential hierarchy of config files
		 * into an actual configuration that the client uses.
		 * However, for diagnostic purposes, record the files we find.
		 */
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package alerting

import (
	"context"
	"fmt"

	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/models"
)

// AlertTest makes a test alert.
func (e *AlertEngine) AlertTest(orgID int64, dashboard *simplejson.Json, panelID int64, user *models.SignedInUser) (*EvalContext, error) {
	dash := models.NewDashboardFromJson(dashboard)
	dashInfo := DashAlertInfo{
		User:  user,
		Dash:  dash,
		OrgID: orgID,
	}
	alerts, err := e.dashAlertExtractor.GetAlerts(context.Background(), dashInfo)
	if err != nil {
		return nil, err
	}

	for _, alert := range alerts {
		if alert.PanelId != panelID {
			continue
		}
		rule, err := NewRuleFromDBAlert(context.Background(), alert, true)
		if err != nil {
			return nil, err
		}

		handler := NewEvalHandler(e.DataService)

		context := NewEvalContext(context.Background(), rule, fakeRequestValidator{}, nil)
		context.IsTestRun = true
		context.IsDebug = true

		handler.Eval(context)
		context.Rule.State = context.GetNewState()
		return context, nil
	}

	return nil, fmt.Errorf("could not find alert with panel ID %d", panelID)
}
<commit_msg>Fix: Add missing store in test rule (#46151)<commit_after>package alerting

import (
	"context"
	"fmt"

	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/models"
)

// AlertTest makes a test alert.
func (e *AlertEngine) AlertTest(orgID int64, dashboard *simplejson.Json, panelID int64, user *models.SignedInUser) (*EvalContext, error) {
	dash := models.NewDashboardFromJson(dashboard)
	dashInfo := DashAlertInfo{
		User:  user,
		Dash:  dash,
		OrgID: orgID,
	}
	alerts, err := e.dashAlertExtractor.GetAlerts(context.Background(), dashInfo)
	if err != nil {
		return nil, err
	}

	for _, alert := range alerts {
		if alert.PanelId != panelID {
			continue
		}
		rule, err := NewRuleFromDBAlert(context.Background(), alert, true)
		if err != nil {
			return nil, err
		}

		handler := NewEvalHandler(e.DataService)

		context := NewEvalContext(context.Background(), rule, fakeRequestValidator{}, e.sqlStore)
		context.IsTestRun = true
		context.IsDebug = true

		handler.Eval(context)
		context.Rule.State = context.GetNewState()
		return context, nil
	}

	return nil, fmt.Errorf("could not find alert with panel ID %d", panelID)
}
<|endoftext|>


<commit_before>package webircgateway

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
	"sync"
)

type TransportTcp struct {
	gateway *Gateway
}

func (t *TransportTcp) Init(g *Gateway) {
	t.gateway = g
}

func (t *TransportTcp) Start(lAddr string) {
	l, err := net.Listen("tcp", lAddr)
	if err != nil {
		fmt.Println("Error listening:", err.Error())
		os.Exit(1)
	}
	// Close the listener when the application closes.
	defer l.Close()
	fmt.Println("TCP listening on " + lAddr)
	for {
		// Listen for an incoming connection.
		conn, err := l.Accept()
		if err != nil {
			fmt.Println("Error accepting: ", err.Error())
			os.Exit(1)
		}
		// Handle connections in a new goroutine.
		go t.handleConn(conn)
	}
}

func (t *TransportTcp) handleConn(conn net.Conn) {
	client := t.gateway.NewClient()

	client.RemoteAddr = conn.RemoteAddr().String()

	clientHostnames, err := net.LookupAddr(client.RemoteAddr)
	if err != nil {
		client.RemoteHostname = client.RemoteAddr
	} else {
		// FQDNs include a . at the end. Strip it out
		potentialHostname := strings.Trim(clientHostnames[0], ".")

		// Must check that the resolved hostname also resolves back to the users IP
		addr, err := net.LookupIP(potentialHostname)
		if err == nil && len(addr) == 1 && addr[0].String() == client.RemoteAddr {
			client.RemoteHostname = potentialHostname
		} else {
			client.RemoteHostname = client.RemoteAddr
		}
	}

	_, remoteAddrPort, _ := net.SplitHostPort(conn.RemoteAddr().String())
	client.Tags["remote-port"] = remoteAddrPort

	client.Log(2, "New client from %s %s", client.RemoteAddr, client.RemoteHostname)

	// We wait until the client send queue has been drained
	var sendDrained sync.WaitGroup
	sendDrained.Add(1)

	// Read from TCP
	go func() {
		reader := bufio.NewReader(conn)
		for {
			data, err := reader.ReadString('\n')
			if err == nil {
				message := strings.TrimRight(data, "\r\n")
				client.Log(1, "client->: %s", message)
				select {
				case client.Recv <- message:
				default:
					client.Log(3, "Recv queue full. Dropping data")
					// TODO: Should this really just drop the data or close the connection?
				}

			} else {
				client.Log(1, "TCP connection closed (%s)", err.Error())
				break

			}
		}

		close(client.Recv)
		client.StartShutdown("client_closed")
	}()

	// Process signals for the client
	for {
		signal, ok := <-client.Signals
		if !ok {
			sendDrained.Done()
			break
		}

		if signal[0] == "data" {
			//line := strings.Trim(signal[1], "\r\n")
			line := signal[1] + "\n"
			client.Log(1, "->tcp: %s", signal[1])
			conn.Write([]byte(line))
		}
	}

	sendDrained.Wait()
	conn.Close()
}
<commit_msg>TCP transport consistent logging<commit_after>package webircgateway

import (
	"bufio"
	"net"
	"strings"
	"sync"
)

type TransportTcp struct {
	gateway *Gateway
}

func (t *TransportTcp) Init(g *Gateway) {
	t.gateway = g
}

func (t *TransportTcp) Start(lAddr string) {
	l, err := net.Listen("tcp", lAddr)
	if err != nil {
		t.gateway.Log(3, "TCP error listening: "+err.Error())
		return
	}
	// Close the listener when the application closes.
	defer l.Close()
	t.gateway.Log(2, "TCP listening on "+lAddr)
	for {
		// Listen for an incoming connection.
		conn, err := l.Accept()
		if err != nil {
			t.gateway.Log(3, "TCP error accepting: "+err.Error())
			break
		}
		// Handle connections in a new goroutine.
		go t.handleConn(conn)
	}
}

func (t *TransportTcp) handleConn(conn net.Conn) {
	client := t.gateway.NewClient()

	client.RemoteAddr = conn.RemoteAddr().String()

	clientHostnames, err := net.LookupAddr(client.RemoteAddr)
	if err != nil {
		client.RemoteHostname = client.RemoteAddr
	} else {
		// FQDNs include a . at the end. Strip it out
		potentialHostname := strings.Trim(clientHostnames[0], ".")

		// Must check that the resolved hostname also resolves back to the users IP
		addr, err := net.LookupIP(potentialHostname)
		if err == nil && len(addr) == 1 && addr[0].String() == client.RemoteAddr {
			client.RemoteHostname = potentialHostname
		} else {
			client.RemoteHostname = client.RemoteAddr
		}
	}

	_, remoteAddrPort, _ := net.SplitHostPort(conn.RemoteAddr().String())
	client.Tags["remote-port"] = remoteAddrPort

	client.Log(2, "New client from %s %s", client.RemoteAddr, client.RemoteHostname)

	// We wait until the client send queue has been drained
	var sendDrained sync.WaitGroup
	sendDrained.Add(1)

	// Read from TCP
	go func() {
		reader := bufio.NewReader(conn)
		for {
			data, err := reader.ReadString('\n')
			if err == nil {
				message := strings.TrimRight(data, "\r\n")
				client.Log(1, "client->: %s", message)
				select {
				case client.Recv <- message:
				default:
					client.Log(3, "Recv queue full. Dropping data")
					// TODO: Should this really just drop the data or close the connection?
				}

			} else {
				client.Log(1, "TCP connection closed (%s)", err.Error())
				break

			}
		}

		close(client.Recv)
		client.StartShutdown("client_closed")
	}()

	// Process signals for the client
	for {
		signal, ok := <-client.Signals
		if !ok {
			sendDrained.Done()
			break
		}

		if signal[0] == "data" {
			//line := strings.Trim(signal[1], "\r\n")
			line := signal[1] + "\n"
			client.Log(1, "->tcp: %s", signal[1])
			conn.Write([]byte(line))
		}
	}

	sendDrained.Wait()
	conn.Close()
}
<|endoftext|>


<commit_before>//  Copyright (c) 2017 Rackspace
//
//  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 middleware

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"

	"github.com/troubling/hummingbird/common"
	"github.com/troubling/hummingbird/common/conf"
	"github.com/troubling/hummingbird/common/srv"
	"go.uber.org/zap"
)

type CopyWriter struct {
	http.ResponseWriter
	Logger        srv.LowLevelLogger
	accountName   string
	containerName string
	objectName    string
	origReqMethod string
	postAsCopy    bool
}

func (cw *CopyWriter) WriteHeader(status int) {
	if cw.postAsCopy && status == http.StatusCreated {
		cw.ResponseWriter.WriteHeader(http.StatusAccepted)
	} else {
		cw.ResponseWriter.WriteHeader(status)
	}
}

type copyMiddleware struct {
	next http.Handler
}

func (cw *CopyWriter) getSrcAccountName(request *http.Request) string {
	copyFromAccount := request.Header.Get("X-Copy-From-Account")
	name, err := common.CheckNameFormat(request, copyFromAccount, "Account")
	if err == nil {
		return name
	}
	return cw.accountName
}

func (cw *CopyWriter) getDestAccountName(request *http.Request) string {
	destAccount := request.Header.Get("Destination-Account")
	name, err := common.CheckNameFormat(request, destAccount, "Account")
	if err == nil {
		request.Header.Set("X-Copy-From-Account", cw.accountName)
		cw.accountName = name
		request.Header.Del("Destination-Account")
	}
	return cw.accountName
}

func getHeaderContainerObjectName(request *http.Request, header string) (string, string, error) {
	path, err := url.QueryUnescape(request.Header.Get(header))
	if err != nil {
		return "", "", errors.New(fmt.Sprintf("Invalid %s", header))
	}
	if !strings.HasPrefix(path, "/") {
		path = "/" + path
	}

	parts := strings.SplitN(path, "/", 3)
	if len(parts) != 3 {
		return "", "", errors.New(fmt.Sprintf("Invalid %s", header))
	}
	name, name_err := common.CheckNameFormat(request, parts[1], "Account")
	if name_err != nil {
		return "", "", errors.New(fmt.Sprintf("Invalid %s", header))
	}
	return name, parts[2], nil
}

type PipeResponseWriter struct {
	w      *io.PipeWriter
	status int
	header http.Header
	done   chan bool
	Logger srv.LowLevelLogger
}

func (w *PipeResponseWriter) Write(stuff []byte) (int, error) {
	written, err := w.w.Write(stuff)
	if err != nil {
		w.Logger.Error("PipeResponseWriter Write() error", zap.Error(err))
	}
	return written, err
}

func (w *PipeResponseWriter) Header() http.Header {
	return w.header
}

func (w *PipeResponseWriter) WriteHeader(status int) {
	w.status = status
	w.done <- true
}

func (w *PipeResponseWriter) Close() {
	w.w.Close()
}

func NewPipeResponseWriter(writer *io.PipeWriter, done chan bool, logger srv.LowLevelLogger) *PipeResponseWriter {
	header := make(map[string][]string)
	return &PipeResponseWriter{
		w:      writer,
		header: header,
		done:   done,
		Logger: logger,
	}
}

func (c *copyMiddleware) getSourceObject(object string, request *http.Request) (io.ReadCloser, http.Header, int) {
	ctx := GetProxyContext(request)
	subRequest, err := http.NewRequest("GET", object, nil)
	if err != nil {
		ctx.Logger.Error("getSourceObject GET error", zap.Error(err))
		return nil, nil, 400
	}
	if request.FormValue("multipart-manifest") == "get" {
		subRequest.URL.RawQuery = "multipart-manifest=get&format=raw"
	}
	copyItems(subRequest.Header, request.Header)
	// FIXME. Are we going to do X-Newest?
	subRequest.Header.Set("X-Newest", "true")
	subRequest.Header.Del("X-Backend-Storage-Policy-Index")

	pipeReader, pipeWriter := io.Pipe()
	done := make(chan bool)
	writer := NewPipeResponseWriter(pipeWriter, done, ctx.Logger)
	go func() {
		ctx.Subrequest(writer, subRequest, "copy", false)
		writer.Close()
	}()
	<-done

	return pipeReader, writer.Header(), writer.status
}

func (c *copyMiddleware) handlePostAsCopy(writer *CopyWriter, request *http.Request) {
	request.Method = "PUT"
	writer.postAsCopy = true

	request.URL.Path = fmt.Sprintf("/v1/%s/%s/%s", writer.accountName, writer.containerName, writer.objectName)
	request.ContentLength = 0
	request.Header.Del("Range")
	objectPath := fmt.Sprintf("/%s/%s", writer.containerName, writer.objectName)
	request.Header.Set("X-Copy-From", common.Urlencode(objectPath))

	values, err := url.ParseQuery(request.URL.RawQuery)
	if err != nil {
		srv.StandardResponse(writer, 400)
	}
	values.Set("multipart-manifest", "get")
	values.Set("format", "raw")
	request.URL.RawQuery = values.Encode()

	c.handlePut(writer, request)
}

func (c *copyMiddleware) handleCopy(writer *CopyWriter, request *http.Request) {
	if request.Header.Get("Destination") == "" {
		// FIXME.
		// swift has: body='Destination header required'
		srv.StandardResponse(writer, 412)
	}
	destAccount := writer.getDestAccountName(request)
	destContainer, destObject, err := getHeaderContainerObjectName(request, "Destination")
	if err != nil {
		srv.StandardResponse(writer, 412)
		return
	}
	source := fmt.Sprintf("%s/%s", writer.containerName, writer.objectName)
	writer.containerName = destContainer
	writer.objectName = destObject

	request.Method = "PUT"
	request.URL.Path = fmt.Sprintf("/v1/%s/%s/%s", destAccount, destContainer, destObject)
	request.ContentLength = 0
	request.Header.Set("X-Copy-From", common.Urlencode(source))
	request.Header.Del("Destination")

	c.handlePut(writer, request)
}

func RemoveItemsWithPrefix(header http.Header, prefix string) {
	for k := range header {
		if strings.HasPrefix(k, prefix) {
			header.Del(k)
		}
	}
}

func copyItemsWithPrefix(dest, src http.Header, prefix string) {
	for k, v := range src {
		if strings.HasPrefix(k, prefix) {
			dest.Del(k)
			for _, v1 := range v {
				dest.Add(k, v1)
			}
		}
	}
}

func copyItems(dest, src http.Header) {
	for k, v := range src {
		dest.Del(k)
		for _, v1 := range v {
			dest.Add(k, v1)
		}
	}
}

func copyMetaItems(dest, src http.Header) {
	copyItemsWithPrefix(dest, src, "X-Object-Meta-")
	copyItemsWithPrefix(dest, src, "X-Object-Sysmeta-")
	copyItemsWithPrefix(dest, src, "X-Object-Transient-Sysmeta-")
	dest.Set("X-Delete-At", src.Get("X-Delete-At"))
}

func excludeContains(exclude []string, k string) bool {
	for _, ex := range exclude {
		if k == ex {
			return true
		}
	}
	return false
}

func copyItemsExclude(dest, src http.Header, exclude []string) {
	for k, v := range src {
		if !excludeContains(exclude, k) {
			dest.Del(k)
			for _, v1 := range v {
				dest.Add(k, v1)
			}
		}
	}
}

func (c *copyMiddleware) handlePut(writer *CopyWriter, request *http.Request) {
	if request.ContentLength != 0 {
		srv.StandardResponse(writer, 400)
		return
	}

	srcAccountName := writer.getSrcAccountName(request)
	srcContainer, srcObject, err := getHeaderContainerObjectName(request, "X-Copy-From")
	if err != nil {
		srv.StandardResponse(writer, 412)
		return
	}

	srcPath := fmt.Sprintf("/v1/%s/%s/%s", srcAccountName, srcContainer, srcObject)

	if writer.origReqMethod != "POST" {
		writer.Logger.Info(fmt.Sprintf("Copying object from %s to %s", srcPath, request.URL.Path))
	}

	srcBody, srcHeader, srcStatus := c.getSourceObject(common.Urlencode(srcPath), request)
	if srcBody != nil {
		defer srcBody.Close()
	}
	if srcStatus >= 300 {
		// Return the result of the GET
		for k := range srcHeader {
			writer.Header().Set(k, srcHeader.Get(k))
		}
		writer.WriteHeader(srcStatus)
		if srcBody != nil {
			common.Copy(srcBody, writer)
		}
		return
	}

	origHeader := make(map[string][]string)
	copyItems(origHeader, request.Header)
	if common.LooksTrue(request.Header.Get("X-Fresh-Metadata")) {
		// # x-fresh-metadata only applies to copy, not post-as-copy: ignore
		// existing user metadata, update existing sysmeta with new
		copyItemsWithPrefix(request.Header, srcHeader, "X-Object-Sysmeta-")
		copyItemsWithPrefix(request.Header, origHeader, "X-Object-Sysmeta-")
	} else {
		// First copy existing sysmeta, user meta and other headers from the
		// source to the request, apart from headers that are conditionally
		// copied below and timestamps.
		exclude := []string{"X-Static-Large-Object", "X-Object-Manifest",
			"Etag", "Content-Type", "X-Timestamp", "X-Backend-Timestamp"}
		copyItemsExclude(request.Header, srcHeader, exclude)
		// now update with original req headers
		copyItems(request.Header, origHeader)
	}

	values, err := url.ParseQuery(request.URL.RawQuery)
	if err != nil {
		srv.StandardResponse(writer, 400)
	}
	if values.Get("multipart-manifest") == "get" {
		if srcHeader.Get("X-Static-Large-Object") != "" {
			values.Set("multipart-manifest", "put")
		}
		if srcHeader.Get("X-Object-Manifest") != "" {
			values.Del("multipart-manifest")
		}
	}

	request.URL.RawQuery = values.Encode()
	request.ContentLength = 0
	request.Body = srcBody

	if srcStatus == http.StatusOK &&
		srcHeader.Get("X-Static-Large-Object") == "" &&
		(srcHeader.Get("X-Object-Manifest") == "" || request.FormValue("multipart-manifest") == "get") {
		// copy source etag so that copied content is verified, unless:
		//  - not a 200 OK response: source etag may not match the actual
		//    content, for example with a 206 Partial Content response to a
		//    ranged request
		//  - SLO manifest: etag cannot be specified in manifest PUT; SLO
		//    generates its own etag value which may differ from source
		//  - SLO: etag in SLO response is not hash of actual content
		//  - DLO: etag in DLO response is not hash of actual content
		request.Header.Set("Etag", srcHeader.Get("Etag"))
	} else {
		// since we're not copying the source etag, make sure that any
		// container update override values are not copied.
		RemoveItemsWithPrefix(request.Header, "X-Object-Sysmeta-Container-Update-Override-")
	}

	request.Header.Del("X-Copy-From")
	request.Header.Del("X-Copy-From-Account")

	// If the copy request does not explicitly override content-type,
	// use the one present in the source object.
	if request.Header.Get("Content-Type") == "" {
		request.Header.Set("Content-Type", srcHeader.Get("Content-Type"))
	}

	var respHeader http.Header = make(map[string][]string)

	respHeader.Set("X-Copied-From-Account", common.Urlencode(srcAccountName))
	copiedFrom := fmt.Sprintf("%s/%s", srcContainer, srcObject)
	respHeader.Set("X-Copied-From", common.Urlencode(copiedFrom))
	if srcHeader.Get("Last-Modified") != "" {
		respHeader.Set("Last-Modified", srcHeader.Get("Last-Modified"))
	}

	copyMetaItems(respHeader, request.Header)

	for k, v := range respHeader {
		for _, v1 := range v {
			writer.Header().Add(k, v1)
		}
	}

	c.next.ServeHTTP(writer, request)
}

func (c *copyMiddleware) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	apiReq, account, container, object := getPathParts(request)
	if !apiReq || account == "" || container == "" || object == "" {
		c.next.ServeHTTP(writer, request)
		return
	}
	ctx := GetProxyContext(request)
	if ctx == nil {
		srv.StandardResponse(writer, 500)
		return
	}

	cw := &CopyWriter{
		ResponseWriter: writer,
		Logger:         ctx.Logger,
		accountName:    account,
		containerName:  container,
		objectName:     object,
	}

	if request.Method == "PUT" && request.Header.Get("X-Copy-From") != "" {
		c.handlePut(cw, request)
		return
	} else if request.Method == "COPY" {
		cw.origReqMethod = "COPY"
		c.handleCopy(cw, request)
		return
	} else if request.Method == "POST" && request.Header.Get("Content-Type") != "" {
		cw.origReqMethod = "POST"
		c.handlePostAsCopy(cw, request)
		return
	}
	c.next.ServeHTTP(writer, request)
}

func NewCopyMiddleware(config conf.Section) (func(http.Handler) http.Handler, error) {
	return func(next http.Handler) http.Handler { return &copyMiddleware{next: next} }, nil
}
<commit_msg>Reverse that logic<commit_after>//  Copyright (c) 2017 Rackspace
//
//  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 middleware

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"

	"github.com/troubling/hummingbird/common"
	"github.com/troubling/hummingbird/common/conf"
	"github.com/troubling/hummingbird/common/srv"
	"go.uber.org/zap"
)

type CopyWriter struct {
	http.ResponseWriter
	Logger        srv.LowLevelLogger
	accountName   string
	containerName string
	objectName    string
	origReqMethod string
	postAsCopy    bool
}

func (cw *CopyWriter) WriteHeader(status int) {
	if cw.postAsCopy && status == http.StatusCreated {
		cw.ResponseWriter.WriteHeader(http.StatusAccepted)
	} else {
		cw.ResponseWriter.WriteHeader(status)
	}
}

type copyMiddleware struct {
	next http.Handler
}

func (cw *CopyWriter) getSrcAccountName(request *http.Request) string {
	copyFromAccount := request.Header.Get("X-Copy-From-Account")
	name, err := common.CheckNameFormat(request, copyFromAccount, "Account")
	if err == nil {
		return name
	}
	return cw.accountName
}

func (cw *CopyWriter) getDestAccountName(request *http.Request) string {
	destAccount := request.Header.Get("Destination-Account")
	name, err := common.CheckNameFormat(request, destAccount, "Account")
	if err == nil {
		request.Header.Set("X-Copy-From-Account", cw.accountName)
		cw.accountName = name
		request.Header.Del("Destination-Account")
	}
	return cw.accountName
}

func getHeaderContainerObjectName(request *http.Request, header string) (string, string, error) {
	path, err := url.QueryUnescape(request.Header.Get(header))
	if err != nil {
		return "", "", errors.New(fmt.Sprintf("Invalid %s", header))
	}
	if !strings.HasPrefix(path, "/") {
		path = "/" + path
	}

	parts := strings.SplitN(path, "/", 3)
	if len(parts) != 3 {
		return "", "", errors.New(fmt.Sprintf("Invalid %s", header))
	}
	name, name_err := common.CheckNameFormat(request, parts[1], "Account")
	if name_err != nil {
		return "", "", errors.New(fmt.Sprintf("Invalid %s", header))
	}
	return name, parts[2], nil
}

type PipeResponseWriter struct {
	w      *io.PipeWriter
	status int
	header http.Header
	done   chan bool
	Logger srv.LowLevelLogger
}

func (w *PipeResponseWriter) Write(stuff []byte) (int, error) {
	written, err := w.w.Write(stuff)
	if err != nil {
		w.Logger.Error("PipeResponseWriter Write() error", zap.Error(err))
	}
	return written, err
}

func (w *PipeResponseWriter) Header() http.Header {
	return w.header
}

func (w *PipeResponseWriter) WriteHeader(status int) {
	w.status = status
	w.done <- true
}

func (w *PipeResponseWriter) Close() {
	w.w.Close()
}

func NewPipeResponseWriter(writer *io.PipeWriter, done chan bool, logger srv.LowLevelLogger) *PipeResponseWriter {
	header := make(map[string][]string)
	return &PipeResponseWriter{
		w:      writer,
		header: header,
		done:   done,
		Logger: logger,
	}
}

func (c *copyMiddleware) getSourceObject(object string, request *http.Request) (io.ReadCloser, http.Header, int) {
	ctx := GetProxyContext(request)
	subRequest, err := http.NewRequest("GET", object, nil)
	if err != nil {
		ctx.Logger.Error("getSourceObject GET error", zap.Error(err))
		return nil, nil, 400
	}
	if request.FormValue("multipart-manifest") == "get" {
		subRequest.URL.RawQuery = "multipart-manifest=get&format=raw"
	}
	copyItems(subRequest.Header, request.Header)
	// FIXME. Are we going to do X-Newest?
	subRequest.Header.Set("X-Newest", "true")
	subRequest.Header.Del("X-Backend-Storage-Policy-Index")

	pipeReader, pipeWriter := io.Pipe()
	done := make(chan bool)
	writer := NewPipeResponseWriter(pipeWriter, done, ctx.Logger)
	go func() {
		ctx.Subrequest(writer, subRequest, "copy", false)
		writer.Close()
	}()
	<-done

	return pipeReader, writer.Header(), writer.status
}

func (c *copyMiddleware) handlePostAsCopy(writer *CopyWriter, request *http.Request) {
	request.Method = "PUT"
	writer.postAsCopy = true

	request.URL.Path = fmt.Sprintf("/v1/%s/%s/%s", writer.accountName, writer.containerName, writer.objectName)
	request.ContentLength = 0
	request.Header.Del("Range")
	objectPath := fmt.Sprintf("/%s/%s", writer.containerName, writer.objectName)
	request.Header.Set("X-Copy-From", common.Urlencode(objectPath))

	values, err := url.ParseQuery(request.URL.RawQuery)
	if err != nil {
		srv.StandardResponse(writer, 400)
	}
	values.Set("multipart-manifest", "get")
	values.Set("format", "raw")
	request.URL.RawQuery = values.Encode()

	c.handlePut(writer, request)
}

func (c *copyMiddleware) handleCopy(writer *CopyWriter, request *http.Request) {
	if request.Header.Get("Destination") == "" {
		// FIXME.
		// swift has: body='Destination header required'
		srv.StandardResponse(writer, 412)
	}
	destAccount := writer.getDestAccountName(request)
	destContainer, destObject, err := getHeaderContainerObjectName(request, "Destination")
	if err != nil {
		srv.StandardResponse(writer, 412)
		return
	}
	source := fmt.Sprintf("%s/%s", writer.containerName, writer.objectName)
	writer.containerName = destContainer
	writer.objectName = destObject

	request.Method = "PUT"
	request.URL.Path = fmt.Sprintf("/v1/%s/%s/%s", destAccount, destContainer, destObject)
	request.ContentLength = 0
	request.Header.Set("X-Copy-From", common.Urlencode(source))
	request.Header.Del("Destination")

	c.handlePut(writer, request)
}

func RemoveItemsWithPrefix(header http.Header, prefix string) {
	for k := range header {
		if strings.HasPrefix(k, prefix) {
			header.Del(k)
		}
	}
}

func copyItemsWithPrefix(dest, src http.Header, prefix string) {
	for k, v := range src {
		if strings.HasPrefix(k, prefix) {
			dest.Del(k)
			for _, v1 := range v {
				dest.Add(k, v1)
			}
		}
	}
}

func copyItems(dest, src http.Header) {
	for k, v := range src {
		dest.Del(k)
		for _, v1 := range v {
			dest.Add(k, v1)
		}
	}
}

func copyMetaItems(dest, src http.Header) {
	copyItemsWithPrefix(dest, src, "X-Object-Meta-")
	copyItemsWithPrefix(dest, src, "X-Object-Sysmeta-")
	copyItemsWithPrefix(dest, src, "X-Object-Transient-Sysmeta-")
	dest.Set("X-Delete-At", src.Get("X-Delete-At"))
}

func excludeContains(exclude []string, k string) bool {
	for _, ex := range exclude {
		if k == ex {
			return true
		}
	}
	return false
}

func copyItemsExclude(dest, src http.Header, exclude []string) {
	for k, v := range src {
		if !excludeContains(exclude, k) {
			dest.Del(k)
			for _, v1 := range v {
				dest.Add(k, v1)
			}
		}
	}
}

func (c *copyMiddleware) handlePut(writer *CopyWriter, request *http.Request) {
	if request.ContentLength != 0 {
		srv.StandardResponse(writer, 400)
		return
	}

	srcAccountName := writer.getSrcAccountName(request)
	srcContainer, srcObject, err := getHeaderContainerObjectName(request, "X-Copy-From")
	if err != nil {
		srv.StandardResponse(writer, 412)
		return
	}

	srcPath := fmt.Sprintf("/v1/%s/%s/%s", srcAccountName, srcContainer, srcObject)

	if writer.origReqMethod != "POST" {
		writer.Logger.Info(fmt.Sprintf("Copying object from %s to %s", srcPath, request.URL.Path))
	}

	srcBody, srcHeader, srcStatus := c.getSourceObject(common.Urlencode(srcPath), request)
	if srcBody != nil {
		defer srcBody.Close()
	}
	if srcStatus >= 300 {
		// Return the result of the GET
		for k := range srcHeader {
			writer.Header().Set(k, srcHeader.Get(k))
		}
		writer.WriteHeader(srcStatus)
		if srcBody != nil {
			common.Copy(srcBody, writer)
		}
		return
	}

	origHeader := make(map[string][]string)
	copyItems(origHeader, request.Header)
	if common.LooksTrue(request.Header.Get("X-Fresh-Metadata")) {
		// # x-fresh-metadata only applies to copy, not post-as-copy: ignore
		// existing user metadata, update existing sysmeta with new
		copyItemsWithPrefix(request.Header, srcHeader, "X-Object-Sysmeta-")
		copyItemsWithPrefix(request.Header, origHeader, "X-Object-Sysmeta-")
	} else {
		// First copy existing sysmeta, user meta and other headers from the
		// source to the request, apart from headers that are conditionally
		// copied below and timestamps.
		exclude := []string{"X-Static-Large-Object", "X-Object-Manifest",
			"Etag", "Content-Type", "X-Timestamp", "X-Backend-Timestamp"}
		copyItemsExclude(request.Header, srcHeader, exclude)
		// now update with original req headers
		copyItems(request.Header, origHeader)
	}

	values, err := url.ParseQuery(request.URL.RawQuery)
	if err != nil {
		srv.StandardResponse(writer, 400)
	}
	if values.Get("multipart-manifest") == "get" {
		if srcHeader.Get("X-Static-Large-Object") != "" {
			values.Set("multipart-manifest", "put")
		}
		if srcHeader.Get("X-Object-Manifest") != "" {
			values.Del("multipart-manifest")
			request.Header.Set("X-Object-Manifest", srcHeader.Get("X-Object-Manifest"))
		}
	}

	request.URL.RawQuery = values.Encode()
	request.ContentLength = 0
	request.Body = srcBody

	if srcStatus == http.StatusOK &&
		srcHeader.Get("X-Static-Large-Object") == "" &&
		(srcHeader.Get("X-Object-Manifest") == "" || request.FormValue("multipart-manifest") == "get") {
		// copy source etag so that copied content is verified, unless:
		//  - not a 200 OK response: source etag may not match the actual
		//    content, for example with a 206 Partial Content response to a
		//    ranged request
		//  - SLO manifest: etag cannot be specified in manifest PUT; SLO
		//    generates its own etag value which may differ from source
		//  - SLO: etag in SLO response is not hash of actual content
		//  - DLO: etag in DLO response is not hash of actual content
		request.Header.Set("Etag", srcHeader.Get("Etag"))
	} else {
		// since we're not copying the source etag, make sure that any
		// container update override values are not copied.
		RemoveItemsWithPrefix(request.Header, "X-Object-Sysmeta-Container-Update-Override-")
	}

	request.Header.Del("X-Copy-From")
	request.Header.Del("X-Copy-From-Account")

	// If the copy request does not explicitly override content-type,
	// use the one present in the source object.
	if request.Header.Get("Content-Type") == "" {
		request.Header.Set("Content-Type", srcHeader.Get("Content-Type"))
	}

	var respHeader http.Header = make(map[string][]string)

	respHeader.Set("X-Copied-From-Account", common.Urlencode(srcAccountName))
	copiedFrom := fmt.Sprintf("%s/%s", srcContainer, srcObject)
	respHeader.Set("X-Copied-From", common.Urlencode(copiedFrom))
	if srcHeader.Get("Last-Modified") != "" {
		respHeader.Set("Last-Modified", srcHeader.Get("Last-Modified"))
	}

	copyMetaItems(respHeader, request.Header)

	for k, v := range respHeader {
		for _, v1 := range v {
			writer.Header().Add(k, v1)
		}
	}

	c.next.ServeHTTP(writer, request)
}

func (c *copyMiddleware) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	apiReq, account, container, object := getPathParts(request)
	if !apiReq || account == "" || container == "" || object == "" {
		c.next.ServeHTTP(writer, request)
		return
	}
	ctx := GetProxyContext(request)
	if ctx == nil {
		srv.StandardResponse(writer, 500)
		return
	}

	cw := &CopyWriter{
		ResponseWriter: writer,
		Logger:         ctx.Logger,
		accountName:    account,
		containerName:  container,
		objectName:     object,
	}

	if request.Method == "PUT" && request.Header.Get("X-Copy-From") != "" {
		c.handlePut(cw, request)
		return
	} else if request.Method == "COPY" {
		cw.origReqMethod = "COPY"
		c.handleCopy(cw, request)
		return
	} else if request.Method == "POST" && request.Header.Get("Content-Type") != "" {
		cw.origReqMethod = "POST"
		c.handlePostAsCopy(cw, request)
		return
	}
	c.next.ServeHTTP(writer, request)
}

func NewCopyMiddleware(config conf.Section) (func(http.Handler) http.Handler, error) {
	return func(next http.Handler) http.Handler { return &copyMiddleware{next: next} }, nil
}
<|endoftext|>


<commit_before>// +build integration

/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 integration

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"k8s.io/apimachinery/pkg/labels"
	pkgutil "k8s.io/minikube/pkg/util"
	"k8s.io/minikube/test/integration/util"
)

func testAddons(t *testing.T) {
	t.Parallel()
	client, err := pkgutil.GetClient()
	if err != nil {
		t.Fatalf("Could not get kubernetes client: %v", err)
	}
	selector := labels.SelectorFromSet(labels.Set(map[string]string{"component": "kube-addon-manager"}))
	if err := pkgutil.WaitForPodsWithLabelRunning(client, "kube-system", selector); err != nil {
		t.Errorf("Error waiting for addon manager to be up")
	}
}

func testDashboard(t *testing.T) {
	t.Parallel()
	minikubeRunner := NewMinikubeRunner(t)

	cmd, out := minikubeRunner.RunDaemon("dashboard --url")
	defer func() {
		err := cmd.Process.Kill()
		if err != nil {
			t.Logf("Failed to kill mount command: %v", err)
		}
	}()

	s, err := out.ReadString('\n')
	if err != nil {
		t.Fatalf("failed to read url: %v", err)
	}

	u, err := url.Parse(strings.TrimSpace(s))
	if err != nil {
		t.Fatalf("failed to parse %q: %v", s, err)
	}

	if u.Scheme != "http" {
		t.Errorf("got Scheme %s, expected http", u.Scheme)
	}
	host, _, err := net.SplitHostPort(u.Host)
	if err != nil {
		t.Fatalf("failed SplitHostPort: %v", err)
	}
	if host != "127.0.0.1" {
		t.Errorf("got host %s, expected 127.0.0.1", host)
	}

	resp, err := http.Get(u.String())
	if err != nil {
		t.Fatalf("failed get: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatalf("Unable to read http response body: %v", err)
		}
		t.Errorf("%s returned status code %d, expected %d.\nbody:\n%s", u, resp.StatusCode, http.StatusOK, body)
	}
}

func testIngressController(t *testing.T) {
	t.Parallel()
	minikubeRunner := NewMinikubeRunner(t)
	kubectlRunner := util.NewKubectlRunner(t)

	minikubeRunner.RunCommand("addons enable ingress", true)
	if err := util.WaitForIngressControllerRunning(t); err != nil {
		t.Fatalf("waiting for ingress-controller to be up: %v", err)
	}

	if err := util.WaitForIngressDefaultBackendRunning(t); err != nil {
		t.Fatalf("waiting for default-http-backend to be up: %v", err)
	}

	ingressPath, _ := filepath.Abs("testdata/nginx-ing.yaml")
	if _, err := kubectlRunner.RunCommand([]string{"create", "-f", ingressPath}); err != nil {
		t.Fatalf("creating nginx ingress resource: %v", err)
	}

	podPath, _ := filepath.Abs("testdata/nginx-pod-svc.yaml")
	if _, err := kubectlRunner.RunCommand([]string{"create", "-f", podPath}); err != nil {
		t.Fatalf("creating nginx ingress resource: %v", err)
	}

	if err := util.WaitForNginxRunning(t); err != nil {
		t.Fatalf("waiting for nginx to be up: %v", err)
	}

	checkIngress := func() error {
		expectedStr := "Welcome to nginx!"
		runCmd := fmt.Sprintf("curl http://127.0.0.1:80 -H 'Host: nginx.example.com'")
		sshCmdOutput, _ := minikubeRunner.SSH(runCmd)
		if !strings.Contains(sshCmdOutput, expectedStr) {
			return fmt.Errorf("ExpectedStr sshCmdOutput to be: %s. Output was: %s", expectedStr, sshCmdOutput)
		}
		return nil
	}

	if err := util.Retry(t, checkIngress, 3*time.Second, 5); err != nil {
		t.Fatalf(err.Error())
	}

	defer func() {
		for _, p := range []string{podPath, ingressPath} {
			if out, err := kubectlRunner.RunCommand([]string{"delete", "-f", p}); err != nil {
				t.Logf("delete -f %s failed: %v\noutput: %s\n", p, err, out)
			}
		}
	}()
	minikubeRunner.RunCommand("addons disable ingress", true)
}

func testServicesList(t *testing.T) {
	t.Parallel()
	minikubeRunner := NewMinikubeRunner(t)

	checkServices := func() error {
		output := minikubeRunner.RunCommand("service list", false)
		if !strings.Contains(output, "kubernetes") {
			return fmt.Errorf("Error, kubernetes service missing from output %s", output)
		}
		return nil
	}
	if err := util.Retry(t, checkServices, 2*time.Second, 5); err != nil {
		t.Fatalf(err.Error())
	}
}
<commit_msg>testDashboard: add timeout around reading URL from stdout.<commit_after>// +build integration

/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 integration

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"k8s.io/apimachinery/pkg/labels"
	pkgutil "k8s.io/minikube/pkg/util"
	"k8s.io/minikube/test/integration/util"
)

func testAddons(t *testing.T) {
	t.Parallel()
	client, err := pkgutil.GetClient()
	if err != nil {
		t.Fatalf("Could not get kubernetes client: %v", err)
	}
	selector := labels.SelectorFromSet(labels.Set(map[string]string{"component": "kube-addon-manager"}))
	if err := pkgutil.WaitForPodsWithLabelRunning(client, "kube-system", selector); err != nil {
		t.Errorf("Error waiting for addon manager to be up")
	}
}

func readLineWithTimeout(b *bufio.Reader, timeout time.Duration) (string, error) {
	s := make(chan string)
	e := make(chan error)
	go func() {
		read, err := b.ReadString('\n')
		if err != nil {
			e <- err
		} else {
			s <- read
		}
		close(s)
		close(e)
	}()

	select {
	case line := <-s:
		return line, nil
	case err := <-e:
		return "", err
	case <-time.After(timeout):
		return "", fmt.Errorf("timeout after %s", timeout)
	}
}

func testDashboard(t *testing.T) {
	t.Parallel()
	minikubeRunner := NewMinikubeRunner(t)
	t.Logf("Launching dashboard ...")
	cmd, out := minikubeRunner.RunDaemon("dashboard --url")
	defer func() {
		t.Logf("Killing dashboard ...")
		err := cmd.Process.Kill()
		if err != nil {
			t.Logf("Failed to kill dashboard command: %v", err)
		}
	}()

	t.Logf("Waiting for URL to be output by minikube dashboard...")
	s, err := readLineWithTimeout(out, 180*time.Second)
	if err != nil {
		t.Fatalf("failed to read url: %v", err)
	}

	u, err := url.Parse(strings.TrimSpace(s))
	if err != nil {
		t.Fatalf("failed to parse %q: %v", s, err)
	}

	if u.Scheme != "http" {
		t.Errorf("got Scheme %s, expected http", u.Scheme)
	}
	host, _, err := net.SplitHostPort(u.Host)
	if err != nil {
		t.Fatalf("failed SplitHostPort: %v", err)
	}
	if host != "127.0.0.1" {
		t.Errorf("got host %s, expected 127.0.0.1", host)
	}

	resp, err := http.Get(u.String())
	if err != nil {
		t.Fatalf("failed get: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatalf("Unable to read http response body: %v", err)
		}
		t.Errorf("%s returned status code %d, expected %d.\nbody:\n%s", u, resp.StatusCode, http.StatusOK, body)
	}
}

func testIngressController(t *testing.T) {
	t.Parallel()
	minikubeRunner := NewMinikubeRunner(t)
	kubectlRunner := util.NewKubectlRunner(t)

	minikubeRunner.RunCommand("addons enable ingress", true)
	if err := util.WaitForIngressControllerRunning(t); err != nil {
		t.Fatalf("waiting for ingress-controller to be up: %v", err)
	}

	if err := util.WaitForIngressDefaultBackendRunning(t); err != nil {
		t.Fatalf("waiting for default-http-backend to be up: %v", err)
	}

	ingressPath, _ := filepath.Abs("testdata/nginx-ing.yaml")
	if _, err := kubectlRunner.RunCommand([]string{"create", "-f", ingressPath}); err != nil {
		t.Fatalf("creating nginx ingress resource: %v", err)
	}

	podPath, _ := filepath.Abs("testdata/nginx-pod-svc.yaml")
	if _, err := kubectlRunner.RunCommand([]string{"create", "-f", podPath}); err != nil {
		t.Fatalf("creating nginx ingress resource: %v", err)
	}

	if err := util.WaitForNginxRunning(t); err != nil {
		t.Fatalf("waiting for nginx to be up: %v", err)
	}

	checkIngress := func() error {
		expectedStr := "Welcome to nginx!"
		runCmd := fmt.Sprintf("curl http://127.0.0.1:80 -H 'Host: nginx.example.com'")
		sshCmdOutput, _ := minikubeRunner.SSH(runCmd)
		if !strings.Contains(sshCmdOutput, expectedStr) {
			return fmt.Errorf("ExpectedStr sshCmdOutput to be: %s. Output was: %s", expectedStr, sshCmdOutput)
		}
		return nil
	}

	if err := util.Retry(t, checkIngress, 3*time.Second, 5); err != nil {
		t.Fatalf(err.Error())
	}

	defer func() {
		for _, p := range []string{podPath, ingressPath} {
			if out, err := kubectlRunner.RunCommand([]string{"delete", "-f", p}); err != nil {
				t.Logf("delete -f %s failed: %v\noutput: %s\n", p, err, out)
			}
		}
	}()
	minikubeRunner.RunCommand("addons disable ingress", true)
}

func testServicesList(t *testing.T) {
	t.Parallel()
	minikubeRunner := NewMinikubeRunner(t)

	checkServices := func() error {
		output := minikubeRunner.RunCommand("service list", false)
		if !strings.Contains(output, "kubernetes") {
			return fmt.Errorf("Error, kubernetes service missing from output %s", output)
		}
		return nil
	}
	if err := util.Retry(t, checkServices, 2*time.Second, 5); err != nil {
		t.Fatalf(err.Error())
	}
}
<|endoftext|>