<commit_before>package kubernetes

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"os"
	"reflect"
	"strconv"
	"strings"
	"text/template"
	"time"

	"github.com/cenk/backoff"
	"github.com/containous/traefik/job"
	"github.com/containous/traefik/log"
	"github.com/containous/traefik/provider"
	"github.com/containous/traefik/safe"
	"github.com/containous/traefik/types"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"
	"k8s.io/client-go/pkg/util/intstr"
)

var _ provider.Provider = (*Provider)(nil)

const (
	annotationFrontendRuleType = "traefik.frontend.rule.type"
	ruleTypePathPrefixStrip    = "PathPrefixStrip"
	ruleTypePathStrip          = "PathStrip"
	ruleTypePath               = "Path"
	ruleTypePathPrefix         = "PathPrefix"
)

const traefikDefaultRealm = "traefik"

// Provider holds configurations of the provider.
type Provider struct {
	provider.BaseProvider  `mapstructure:",squash"`
	Endpoint               string     `description:"Kubernetes server endpoint (required for external cluster client)"`
	Token                  string     `description:"Kubernetes bearer token (not needed for in-cluster client)"`
	CertAuthFilePath       string     `description:"Kubernetes certificate authority file path (not needed for in-cluster client)"`
	DisablePassHostHeaders bool       `description:"Kubernetes disable PassHost Headers"`
	Namespaces             Namespaces `description:"Kubernetes namespaces"`
	LabelSelector          string     `description:"Kubernetes api label selector to use"`
	lastConfiguration      safe.Safe
}

func (p *Provider) newK8sClient() (Client, error) {
	withEndpoint := ""
	if p.Endpoint != "" {
		withEndpoint = fmt.Sprintf(" with endpoint %v", p.Endpoint)
	}

	if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" {
		log.Infof("Creating in-cluster Provider client%s\n", withEndpoint)
		return NewInClusterClient(p.Endpoint)
	}

	log.Infof("Creating cluster-external Provider client%s\n", withEndpoint)
	return NewExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath)
}

// Provide allows the k8s provider to provide configurations to traefik
// using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
	k8sClient, err := p.newK8sClient()
	if err != nil {
		return err
	}
	p.Constraints = append(p.Constraints, constraints...)

	pool.Go(func(stop chan bool) {
		operation := func() error {
			for {
				stopWatch := make(chan struct{}, 1)
				defer close(stopWatch)
				log.Debugf("Using label selector: '%s'", p.LabelSelector)
				eventsChan, err := k8sClient.WatchAll(p.LabelSelector, stopWatch)
				if err != nil {
					log.Errorf("Error watching kubernetes events: %v", err)
					timer := time.NewTimer(1 * time.Second)
					select {
					case <-timer.C:
						return err
					case <-stop:
						return nil
					}
				}
				for {
					select {
					case <-stop:
						return nil
					case event := <-eventsChan:
						log.Debugf("Received event from kubernetes %+v", event)
						templateObjects, err := p.loadIngresses(k8sClient)
						if err != nil {
							return err
						}
						if reflect.DeepEqual(p.lastConfiguration.Get(), templateObjects) {
							log.Debugf("Skipping event from kubernetes %+v", event)
						} else {
							p.lastConfiguration.Set(templateObjects)
							configurationChan <- types.ConfigMessage{
								ProviderName:  "kubernetes",
								Configuration: p.loadConfig(*templateObjects),
							}
						}
					}
				}
			}
		}

		notify := func(err error, time time.Duration) {
			log.Errorf("Provider connection error %+v, retrying in %s", err, time)
		}
		err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
		if err != nil {
			log.Errorf("Cannot connect to Provider server %+v", err)
		}
	})

	return nil
}

func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) {
	ingresses := k8sClient.GetIngresses(p.Namespaces)

	templateObjects := types.Configuration{
		map[string]*types.Backend{},
		map[string]*types.Frontend{},
	}
	for _, i := range ingresses {
		ingressClass := i.Annotations["kubernetes.io/ingress.class"]

		if !shouldProcessIngress(ingressClass) {
			continue
		}

		for _, r := range i.Spec.Rules {
			if r.HTTP == nil {
				log.Warnf("Error in ingress: HTTP is nil")
				continue
			}
			for _, pa := range r.HTTP.Paths {
				if _, exists := templateObjects.Backends[r.Host+pa.Path]; !exists {
					templateObjects.Backends[r.Host+pa.Path] = &types.Backend{
						Servers: make(map[string]types.Server),
						LoadBalancer: &types.LoadBalancer{
							Sticky: false,
							Method: "wrr",
						},
					}
				}

				PassHostHeader := p.getPassHostHeader()

				passHostHeaderAnnotation := i.Annotations["traefik.frontend.passHostHeader"]
				switch passHostHeaderAnnotation {
				case "true":
					PassHostHeader = true
				case "false":
					PassHostHeader = false
				default:
					log.Warnf("Unknown value of %s for traefik.frontend.passHostHeader, falling back to %s", passHostHeaderAnnotation, PassHostHeader)
				}
				if realm := i.Annotations["ingress.kubernetes.io/auth-realm"]; realm != "" && realm != traefikDefaultRealm {
					return nil, errors.New("no realm customization supported")
				}

				if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
					basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient)
					if err != nil {
						return nil, err
					}
					templateObjects.Frontends[r.Host+pa.Path] = &types.Frontend{
						Backend:        r.Host + pa.Path,
						PassHostHeader: PassHostHeader,
						Routes:         make(map[string]types.Route),
						Priority:       len(pa.Path),
						BasicAuth:      basicAuthCreds,
					}
				}
				if len(r.Host) > 0 {
					rule := "Host:" + r.Host

					if strings.Contains(r.Host, "*") {
						rule = "HostRegexp:" + strings.Replace(r.Host, "*", "{subdomain:[A-Za-z0-9-_]+}", 1)
					}

					if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
						templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
							Rule: rule,
						}
					}
				}

				if len(pa.Path) > 0 {
					ruleType, unknown := getRuleTypeFromAnnotation(i.Annotations)
					switch {
					case unknown:
						log.Warnf("Unknown RuleType '%s' for Ingress %s/%s, falling back to PathPrefix", ruleType, i.ObjectMeta.Namespace, i.ObjectMeta.Name)
						fallthrough
					case ruleType == "":
						ruleType = ruleTypePathPrefix
					}

					templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
						Rule: ruleType + ":" + pa.Path,
					}
				}

				service, exists, err := k8sClient.GetService(i.ObjectMeta.Namespace, pa.Backend.ServiceName)
				if err != nil {
					log.Errorf("Error while retrieving service information from k8s API %s/%s: %v", i.ObjectMeta.Namespace, pa.Backend.ServiceName, err)
					return nil, err
				}

				if !exists {
					log.Errorf("Service not found for %s/%s", i.ObjectMeta.Namespace, pa.Backend.ServiceName)
					delete(templateObjects.Frontends, r.Host+pa.Path)
					continue
				}

				if expression := service.Annotations["traefik.backend.circuitbreaker"]; expression != "" {
					templateObjects.Backends[r.Host+pa.Path].CircuitBreaker = &types.CircuitBreaker{
						Expression: expression,
					}
				}
				if service.Annotations["traefik.backend.loadbalancer.method"] == "drr" {
					templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Method = "drr"
				}
				if service.Annotations["traefik.backend.loadbalancer.sticky"] == "true" {
					templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Sticky = true
				}

				protocol := "http"
				for _, port := range service.Spec.Ports {
					if equalPorts(port, pa.Backend.ServicePort) {
						if port.Port == 443 {
							protocol = "https"
						}
						if service.Spec.Type == "ExternalName" {
							url := protocol + "://" + service.Spec.ExternalName
							name := url

							templateObjects.Backends[r.Host+pa.Path].Servers[name] = types.Server{
								URL:    url,
								Weight: 1,
							}
						} else {
							endpoints, exists, err := k8sClient.GetEndpoints(service.ObjectMeta.Namespace, service.ObjectMeta.Name)
							if err != nil {
								log.Errorf("Error retrieving endpoints %s/%s: %v", service.ObjectMeta.Namespace, service.ObjectMeta.Name, err)
								return nil, err
							}

							if !exists {
								log.Errorf("Endpoints not found for %s/%s", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
								continue
							}

							if len(endpoints.Subsets) == 0 {
								log.Warnf("Service endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
								templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
									URL:    protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(int(port.Port)),
									Weight: 1,
								}
							} else {
								for _, subset := range endpoints.Subsets {
									for _, address := range subset.Addresses {
										url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports))
										name := url
										if address.TargetRef != nil && address.TargetRef.Name != "" {
											name = address.TargetRef.Name
										}
										templateObjects.Backends[r.Host+pa.Path].Servers[name] = types.Server{
											URL:    url,
											Weight: 1,
										}
									}
								}
							}
						}
						break
					}
				}
			}
		}
	}
	return &templateObjects, nil
}

func handleBasicAuthConfig(i *v1beta1.Ingress, k8sClient Client) ([]string, error) {
	authType, exists := i.Annotations["ingress.kubernetes.io/auth-type"]
	if !exists {
		return nil, nil
	}
	if strings.ToLower(authType) != "basic" {
		return nil, fmt.Errorf("unsupported auth-type: %q", authType)
	}
	authSecret := i.Annotations["ingress.kubernetes.io/auth-secret"]
	if authSecret == "" {
		return nil, errors.New("auth-secret annotation must be set")
	}
	basicAuthCreds, err := loadAuthCredentials(i.Namespace, authSecret, k8sClient)
	if err != nil {
		return nil, err
	}
	if len(basicAuthCreds) == 0 {
		return nil, errors.New("secret file without credentials")
	}
	return basicAuthCreds, nil
}

func loadAuthCredentials(namespace, secretName string, k8sClient Client) ([]string, error) {
	secret, ok, err := k8sClient.GetSecret(namespace, secretName)
	switch { // keep order of case conditions
	case err != nil:
		return nil, fmt.Errorf("failed to fetch secret %q/%q: %s", namespace, secretName, err)
	case !ok:
		return nil, fmt.Errorf("secret %q/%q not found", namespace, secretName)
	case secret == nil:
		return nil, errors.New("secret data must not be nil")
	case len(secret.Data) != 1:
		return nil, errors.New("secret must contain single element only")
	default:
	}
	var firstSecret []byte
	for _, v := range secret.Data {
		firstSecret = v
		break
	}
	creds := make([]string, 0)
	scanner := bufio.NewScanner(bytes.NewReader(firstSecret))
	for scanner.Scan() {
		if cred := scanner.Text(); cred != "" {
			creds = append(creds, cred)
		}
	}
	return creds, nil
}

func endpointPortNumber(servicePort v1.ServicePort, endpointPorts []v1.EndpointPort) int {
	if len(endpointPorts) > 0 {
		//name is optional if there is only one port
		port := endpointPorts[0]
		for _, endpointPort := range endpointPorts {
			if servicePort.Name == endpointPort.Name {
				port = endpointPort
			}
		}
		return int(port.Port)
	}
	return int(servicePort.Port)
}

func equalPorts(servicePort v1.ServicePort, ingressPort intstr.IntOrString) bool {
	if int(servicePort.Port) == ingressPort.IntValue() {
		return true
	}
	if servicePort.Name != "" && servicePort.Name == ingressPort.String() {
		return true
	}
	return false
}

func shouldProcessIngress(ingressClass string) bool {
	switch ingressClass {
	case "", "traefik":
		return true
	default:
		return false
	}
}

func (p *Provider) getPassHostHeader() bool {
	if p.DisablePassHostHeaders {
		return false
	}
	return true
}

func (p *Provider) loadConfig(templateObjects types.Configuration) *types.Configuration {
	var FuncMap = template.FuncMap{}
	configuration, err := p.GetConfiguration("templates/kubernetes.tmpl", FuncMap, templateObjects)
	if err != nil {
		log.Error(err)
	}
	return configuration
}

func getRuleTypeFromAnnotation(annotations map[string]string) (ruleType string, unknown bool) {
	ruleType = annotations[annotationFrontendRuleType]
	for _, knownRuleType := range []string{
		ruleTypePathPrefixStrip,
		ruleTypePathStrip,
		ruleTypePath,
		ruleTypePathPrefix,
	} {
		if strings.ToLower(ruleType) == strings.ToLower(knownRuleType) {
			return knownRuleType, false
		}
	}

	if ruleType != "" {
		// Annotation is set but does not match anything we know.
		unknown = true
	}

	return ruleType, unknown
}
<commit_msg>[Kubernetes] Ignore missing pass host header annotation.<commit_after>package kubernetes

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"os"
	"reflect"
	"strconv"
	"strings"
	"text/template"
	"time"

	"github.com/cenk/backoff"
	"github.com/containous/traefik/job"
	"github.com/containous/traefik/log"
	"github.com/containous/traefik/provider"
	"github.com/containous/traefik/safe"
	"github.com/containous/traefik/types"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"
	"k8s.io/client-go/pkg/util/intstr"
)

var _ provider.Provider = (*Provider)(nil)

const (
	annotationFrontendRuleType = "traefik.frontend.rule.type"
	ruleTypePathPrefixStrip    = "PathPrefixStrip"
	ruleTypePathStrip          = "PathStrip"
	ruleTypePath               = "Path"
	ruleTypePathPrefix         = "PathPrefix"
)

const traefikDefaultRealm = "traefik"

// Provider holds configurations of the provider.
type Provider struct {
	provider.BaseProvider  `mapstructure:",squash"`
	Endpoint               string     `description:"Kubernetes server endpoint (required for external cluster client)"`
	Token                  string     `description:"Kubernetes bearer token (not needed for in-cluster client)"`
	CertAuthFilePath       string     `description:"Kubernetes certificate authority file path (not needed for in-cluster client)"`
	DisablePassHostHeaders bool       `description:"Kubernetes disable PassHost Headers"`
	Namespaces             Namespaces `description:"Kubernetes namespaces"`
	LabelSelector          string     `description:"Kubernetes api label selector to use"`
	lastConfiguration      safe.Safe
}

func (p *Provider) newK8sClient() (Client, error) {
	withEndpoint := ""
	if p.Endpoint != "" {
		withEndpoint = fmt.Sprintf(" with endpoint %v", p.Endpoint)
	}

	if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" {
		log.Infof("Creating in-cluster Provider client%s\n", withEndpoint)
		return NewInClusterClient(p.Endpoint)
	}

	log.Infof("Creating cluster-external Provider client%s\n", withEndpoint)
	return NewExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath)
}

// Provide allows the k8s provider to provide configurations to traefik
// using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
	k8sClient, err := p.newK8sClient()
	if err != nil {
		return err
	}
	p.Constraints = append(p.Constraints, constraints...)

	pool.Go(func(stop chan bool) {
		operation := func() error {
			for {
				stopWatch := make(chan struct{}, 1)
				defer close(stopWatch)
				log.Debugf("Using label selector: '%s'", p.LabelSelector)
				eventsChan, err := k8sClient.WatchAll(p.LabelSelector, stopWatch)
				if err != nil {
					log.Errorf("Error watching kubernetes events: %v", err)
					timer := time.NewTimer(1 * time.Second)
					select {
					case <-timer.C:
						return err
					case <-stop:
						return nil
					}
				}
				for {
					select {
					case <-stop:
						return nil
					case event := <-eventsChan:
						log.Debugf("Received event from kubernetes %+v", event)
						templateObjects, err := p.loadIngresses(k8sClient)
						if err != nil {
							return err
						}
						if reflect.DeepEqual(p.lastConfiguration.Get(), templateObjects) {
							log.Debugf("Skipping event from kubernetes %+v", event)
						} else {
							p.lastConfiguration.Set(templateObjects)
							configurationChan <- types.ConfigMessage{
								ProviderName:  "kubernetes",
								Configuration: p.loadConfig(*templateObjects),
							}
						}
					}
				}
			}
		}

		notify := func(err error, time time.Duration) {
			log.Errorf("Provider connection error %+v, retrying in %s", err, time)
		}
		err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
		if err != nil {
			log.Errorf("Cannot connect to Provider server %+v", err)
		}
	})

	return nil
}

func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) {
	ingresses := k8sClient.GetIngresses(p.Namespaces)

	templateObjects := types.Configuration{
		map[string]*types.Backend{},
		map[string]*types.Frontend{},
	}
	for _, i := range ingresses {
		ingressClass := i.Annotations["kubernetes.io/ingress.class"]

		if !shouldProcessIngress(ingressClass) {
			continue
		}

		for _, r := range i.Spec.Rules {
			if r.HTTP == nil {
				log.Warnf("Error in ingress: HTTP is nil")
				continue
			}
			for _, pa := range r.HTTP.Paths {
				if _, exists := templateObjects.Backends[r.Host+pa.Path]; !exists {
					templateObjects.Backends[r.Host+pa.Path] = &types.Backend{
						Servers: make(map[string]types.Server),
						LoadBalancer: &types.LoadBalancer{
							Sticky: false,
							Method: "wrr",
						},
					}
				}

				PassHostHeader := p.getPassHostHeader()

				passHostHeaderAnnotation, ok := i.Annotations["traefik.frontend.passHostHeader"]
				switch {
				case !ok:
					// No op.
				case passHostHeaderAnnotation == "false":
					PassHostHeader = false
				case passHostHeaderAnnotation == "true":
					PassHostHeader = true
				default:
					log.Warnf("Unknown value '%s' for traefik.frontend.passHostHeader, falling back to %s", passHostHeaderAnnotation, PassHostHeader)
				}
				if realm := i.Annotations["ingress.kubernetes.io/auth-realm"]; realm != "" && realm != traefikDefaultRealm {
					return nil, errors.New("no realm customization supported")
				}

				if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
					basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient)
					if err != nil {
						return nil, err
					}
					templateObjects.Frontends[r.Host+pa.Path] = &types.Frontend{
						Backend:        r.Host + pa.Path,
						PassHostHeader: PassHostHeader,
						Routes:         make(map[string]types.Route),
						Priority:       len(pa.Path),
						BasicAuth:      basicAuthCreds,
					}
				}
				if len(r.Host) > 0 {
					rule := "Host:" + r.Host

					if strings.Contains(r.Host, "*") {
						rule = "HostRegexp:" + strings.Replace(r.Host, "*", "{subdomain:[A-Za-z0-9-_]+}", 1)
					}

					if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
						templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
							Rule: rule,
						}
					}
				}

				if len(pa.Path) > 0 {
					ruleType, unknown := getRuleTypeFromAnnotation(i.Annotations)
					switch {
					case unknown:
						log.Warnf("Unknown RuleType '%s' for Ingress %s/%s, falling back to PathPrefix", ruleType, i.ObjectMeta.Namespace, i.ObjectMeta.Name)
						fallthrough
					case ruleType == "":
						ruleType = ruleTypePathPrefix
					}

					templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
						Rule: ruleType + ":" + pa.Path,
					}
				}

				service, exists, err := k8sClient.GetService(i.ObjectMeta.Namespace, pa.Backend.ServiceName)
				if err != nil {
					log.Errorf("Error while retrieving service information from k8s API %s/%s: %v", i.ObjectMeta.Namespace, pa.Backend.ServiceName, err)
					return nil, err
				}

				if !exists {
					log.Errorf("Service not found for %s/%s", i.ObjectMeta.Namespace, pa.Backend.ServiceName)
					delete(templateObjects.Frontends, r.Host+pa.Path)
					continue
				}

				if expression := service.Annotations["traefik.backend.circuitbreaker"]; expression != "" {
					templateObjects.Backends[r.Host+pa.Path].CircuitBreaker = &types.CircuitBreaker{
						Expression: expression,
					}
				}
				if service.Annotations["traefik.backend.loadbalancer.method"] == "drr" {
					templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Method = "drr"
				}
				if service.Annotations["traefik.backend.loadbalancer.sticky"] == "true" {
					templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Sticky = true
				}

				protocol := "http"
				for _, port := range service.Spec.Ports {
					if equalPorts(port, pa.Backend.ServicePort) {
						if port.Port == 443 {
							protocol = "https"
						}
						if service.Spec.Type == "ExternalName" {
							url := protocol + "://" + service.Spec.ExternalName
							name := url

							templateObjects.Backends[r.Host+pa.Path].Servers[name] = types.Server{
								URL:    url,
								Weight: 1,
							}
						} else {
							endpoints, exists, err := k8sClient.GetEndpoints(service.ObjectMeta.Namespace, service.ObjectMeta.Name)
							if err != nil {
								log.Errorf("Error retrieving endpoints %s/%s: %v", service.ObjectMeta.Namespace, service.ObjectMeta.Name, err)
								return nil, err
							}

							if !exists {
								log.Errorf("Endpoints not found for %s/%s", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
								continue
							}

							if len(endpoints.Subsets) == 0 {
								log.Warnf("Service endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
								templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
									URL:    protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(int(port.Port)),
									Weight: 1,
								}
							} else {
								for _, subset := range endpoints.Subsets {
									for _, address := range subset.Addresses {
										url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports))
										name := url
										if address.TargetRef != nil && address.TargetRef.Name != "" {
											name = address.TargetRef.Name
										}
										templateObjects.Backends[r.Host+pa.Path].Servers[name] = types.Server{
											URL:    url,
											Weight: 1,
										}
									}
								}
							}
						}
						break
					}
				}
			}
		}
	}
	return &templateObjects, nil
}

func handleBasicAuthConfig(i *v1beta1.Ingress, k8sClient Client) ([]string, error) {
	authType, exists := i.Annotations["ingress.kubernetes.io/auth-type"]
	if !exists {
		return nil, nil
	}
	if strings.ToLower(authType) != "basic" {
		return nil, fmt.Errorf("unsupported auth-type: %q", authType)
	}
	authSecret := i.Annotations["ingress.kubernetes.io/auth-secret"]
	if authSecret == "" {
		return nil, errors.New("auth-secret annotation must be set")
	}
	basicAuthCreds, err := loadAuthCredentials(i.Namespace, authSecret, k8sClient)
	if err != nil {
		return nil, err
	}
	if len(basicAuthCreds) == 0 {
		return nil, errors.New("secret file without credentials")
	}
	return basicAuthCreds, nil
}

func loadAuthCredentials(namespace, secretName string, k8sClient Client) ([]string, error) {
	secret, ok, err := k8sClient.GetSecret(namespace, secretName)
	switch { // keep order of case conditions
	case err != nil:
		return nil, fmt.Errorf("failed to fetch secret %q/%q: %s", namespace, secretName, err)
	case !ok:
		return nil, fmt.Errorf("secret %q/%q not found", namespace, secretName)
	case secret == nil:
		return nil, errors.New("secret data must not be nil")
	case len(secret.Data) != 1:
		return nil, errors.New("secret must contain single element only")
	default:
	}
	var firstSecret []byte
	for _, v := range secret.Data {
		firstSecret = v
		break
	}
	creds := make([]string, 0)
	scanner := bufio.NewScanner(bytes.NewReader(firstSecret))
	for scanner.Scan() {
		if cred := scanner.Text(); cred != "" {
			creds = append(creds, cred)
		}
	}
	return creds, nil
}

func endpointPortNumber(servicePort v1.ServicePort, endpointPorts []v1.EndpointPort) int {
	if len(endpointPorts) > 0 {
		//name is optional if there is only one port
		port := endpointPorts[0]
		for _, endpointPort := range endpointPorts {
			if servicePort.Name == endpointPort.Name {
				port = endpointPort
			}
		}
		return int(port.Port)
	}
	return int(servicePort.Port)
}

func equalPorts(servicePort v1.ServicePort, ingressPort intstr.IntOrString) bool {
	if int(servicePort.Port) == ingressPort.IntValue() {
		return true
	}
	if servicePort.Name != "" && servicePort.Name == ingressPort.String() {
		return true
	}
	return false
}

func shouldProcessIngress(ingressClass string) bool {
	switch ingressClass {
	case "", "traefik":
		return true
	default:
		return false
	}
}

func (p *Provider) getPassHostHeader() bool {
	if p.DisablePassHostHeaders {
		return false
	}
	return true
}

func (p *Provider) loadConfig(templateObjects types.Configuration) *types.Configuration {
	var FuncMap = template.FuncMap{}
	configuration, err := p.GetConfiguration("templates/kubernetes.tmpl", FuncMap, templateObjects)
	if err != nil {
		log.Error(err)
	}
	return configuration
}

func getRuleTypeFromAnnotation(annotations map[string]string) (ruleType string, unknown bool) {
	ruleType = annotations[annotationFrontendRuleType]
	for _, knownRuleType := range []string{
		ruleTypePathPrefixStrip,
		ruleTypePathStrip,
		ruleTypePath,
		ruleTypePathPrefix,
	} {
		if strings.ToLower(ruleType) == strings.ToLower(knownRuleType) {
			return knownRuleType, false
		}
	}

	if ruleType != "" {
		// Annotation is set but does not match anything we know.
		unknown = true
	}

	return ruleType, unknown
}
<|endoftext|>


<commit_before>package spotify

import (
	"testing"

	"github.com/markbates/goth"
	"github.com/stretchr/testify/assert"
)

func Test_ImplementsSession(t *testing.T) {
	t.Parallel()
	a := assert.New(t)
	s := Session{}
	a.Implements((*goth.Session)(nil), s)
}

func Test_GetAuthURL(t *testing.T) {
	t.Parallel()
	a := assert.New(t)
	s := &Session{}

	_, err := s.GetAuthURL()
	a.Error(err)

	s.AuthURL = "/foo"
	url, _ := s.GetAuthURL()
	a.Equal(url, "/foo")
}

func Test_ToJSON(t *testing.T) {
	t.Parallel()
	a := assert.New(t)
	s := &Session{}

	data := s.Marshal()
	a.Equal(data, `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresAt":"0001-01-01T00:00:00Z"}`)
}
<commit_msg>updated package name from spotify to spotify_test (stay consistent with other provider packages) added import spotify package. fixed unit test Test_ImplementsSessions to reflect pointer change.<commit_after>package spotify_test

import (
	"testing"

	"github.com/markbates/goth"
	"github.com/markbates/goth/providers/spotify"
	"github.com/stretchr/testify/assert"
)

func Test_ImplementsSession(t *testing.T) {
	t.Parallel()
	a := assert.New(t)
	s := &spotify.Session{}
	a.Implements((*goth.Session)(nil), s)
}

func Test_GetAuthURL(t *testing.T) {
	t.Parallel()
	a := assert.New(t)
	s := &spotify.Session{}

	_, err := s.GetAuthURL()
	a.Error(err)

	s.AuthURL = "/foo"
	url, _ := s.GetAuthURL()
	a.Equal(url, "/foo")
}

func Test_ToJSON(t *testing.T) {
	t.Parallel()
	a := assert.New(t)
	s := &spotify.Session{}

	data := s.Marshal()
	a.Equal(data, `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresAt":"0001-01-01T00:00:00Z"}`)
}
<|endoftext|>


<commit_before>//
// data.go
//
// Created by Frederic DELBOS - fred@hyperboloide.com on Nov  9 2014.
//

package rw

import (
	"errors"
	"sync"
)

type Data struct {
	sync.RWMutex
	data map[string]interface{}
}

func NewData() *Data {
	return &Data{
		data: make(map[string]interface{}),
	}
}

func (d *Data) Get(key string) (interface{}, error) {
	d.RLock()
	defer d.RUnlock()
	v, exists := d.data[key]
	if exists == false {
		return nil, errors.New("Data '" + key + "' not found")
	}
	return v, nil
}

func (d *Data) Set(key string, value interface{}) {
	d.Lock()
	defer d.Unlock()
	d.data[key] = value
}
<commit_msg>convenience functions<commit_after>//
// data.go
//
// Created by Frederic DELBOS - fred@hyperboloide.com on Nov  9 2014.
//

package rw

import (
	"encoding/json"
	"errors"
	"sync"
)

type Data struct {
	sync.RWMutex
	data map[string]interface{}
}

func NewData() *Data {
	return &Data{
		data: make(map[string]interface{}),
	}
}

func NewDataFrom(o map[string]interface{}) *Data {
	return &Data{
		data: o,
	}
}

func (d *Data) Get(key string) (interface{}, error) {
	d.RLock()
	defer d.RUnlock()
	v, exists := d.data[key]
	if exists == false {
		return nil, errors.New("Data '" + key + "' not found")
	}
	return v, nil
}

func (d *Data) Export() map[string]interface{} {
	d.RLock()
	defer d.RUnlock()

	copy := make(map[string]interface{})
	for k, v := range d.data {
		copy[k] = v
	}
	return copy
}

func (d *Data) Set(key string, value interface{}) {
	d.Lock()
	defer d.Unlock()
	d.data[key] = value
}

func (d *Data) Filter() ([]byte, error) {
	d.RLock()
	defer d.RUnlock()

	copy := map[string]interface{}{
		"size":       d.data["size"],
		"identifier": d.data["identifier"],
		"filename":   d.data["filename"],
	}
	return json.Marshal(copy)
}

func (d *Data) ToJson() ([]byte, error) {
	d.RLock()
	defer d.RUnlock()
	return json.Marshal(d.data)
}
<|endoftext|>


<commit_before>package checks

import (
	"testing"

	"github.com/mackerelio/mackerel-agent/config"
)

func TestChecker_Check(t *testing.T) {
	checkerOK := Checker{
		Config: config.PluginConfig{
			Command: "go run testdata/exit.go -code 0 -message OK",
		},
	}

	checkerWarning := Checker{
		Config: config.PluginConfig{
			Command: "go run testdata/exit.go -code 1 -message 'something is going wrong'",
		},
	}

	{
		status, msg, err := checkerOK.Check()
		if err != nil {
			t.Errorf("err should be nil: %v", err)
		}
		if status != StatusOK {
			t.Errorf("status should be OK: %v", status)
		}
		if msg != "OK\n" {
			t.Errorf("wrong message: %q", msg)
		}
	}

	{
		status, msg, err := checkerWarning.Check()
		if err != nil {
			t.Errorf("err should be nil: %v", err)
		}
		if status != StatusWarning {
			t.Errorf("status should be WARNING: %v", status)
		}
		if msg != "something is going wrong\n" {
			t.Errorf("wrong message: %q", msg)
		}
	}
}
<commit_msg>fix test<commit_after>package checks

import (
	"testing"

	"github.com/mackerelio/mackerel-agent/config"
)

func TestChecker_Check(t *testing.T) {
	checkerOK := Checker{
		Config: config.PluginConfig{
			Command: "go run testdata/exit.go -code 0 -message OK",
		},
	}

	checkerWarning := Checker{
		Config: config.PluginConfig{
			Command: "go run testdata/exit.go -code 1 -message 'something is going wrong'",
		},
	}

	{
		report, err := checkerOK.Check()
		if err != nil {
			t.Errorf("err should be nil: %v", err)
		}
		if report.Status != StatusOK {
			t.Errorf("status should be OK: %v", report.Status)
		}
		if report.Message != "OK\n" {
			t.Errorf("wrong message: %q", report.Message)
		}
	}

	{
		report, err := checkerWarning.Check()
		if err != nil {
			t.Errorf("err should be nil: %v", err)
		}
		if report.Status != StatusWarning {
			t.Errorf("status should be WARNING: %v", report.Status)
		}
		if report.Message != "something is going wrong\n" {
			t.Errorf("wrong message: %q", report.Message)
		}
	}
}
<|endoftext|>


<commit_before>package checks

import (
	"bytes"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/yhat/go-docker"
)

var dockerSock = "unix:///var/run/docker.sock"

type Check func() error

type namedCheck struct {
	Name  string
	Check Check
}

type Checker struct {
	checks []namedCheck
}

func (c *Checker) Register(check Check, name string) {
	c.checks = append(c.checks, namedCheck{
		Name:  name,
		Check: check,
	})
}

func (c *Checker) Run() (exitCode int) {
	maxNameLen := 0
	for _, check := range c.checks {
		if n := len(check.Name); maxNameLen < n {
			maxNameLen = n
		}
	}
	s := strconv.Itoa(maxNameLen)
	pad := func(name string) string {
		return fmt.Sprintf("%-"+s+"s", name)
	}
	var err error
	for _, check := range c.checks {
		name := pad(check.Name)
		fmt.Print(name + " ... ")
		start := time.Now()
		err = check.Check()
		delta := time.Now().Sub(start)
		if err != nil {
			exitCode = 2
			fmt.Println("ERROR")
			fmt.Printf("\t%v\n", err)
		} else {
			fmt.Printf("OK (%s)\n", delta)
		}
	}
	return 0
}

func newClient() (*docker.Client, error) {
	return docker.NewClient(dockerSock, nil, 3*time.Second)
}

// CheckDockerSocket confirms that docker can be reached
func CheckDockerSocket() error {
	cli, err := newClient()
	if err != nil {
		return fmt.Errorf("creating client: %v", err)
	}
	_, err = cli.Info()
	if err != nil {
		return fmt.Errorf("simple ping: %v", err)
	}
	return nil
}

// CheckVersion confirms docker is at least the version passed.
func CheckVersion(minVersion []int) Check {
	return func() error {
		cli, err := newClient()
		if err != nil {
			return fmt.Errorf("creating client: %v", err)
		}
		v, err := cli.Version()
		if err != nil {
			return fmt.Errorf("getting version: %v", err)
		}
		return versionAtLeast(v.Version, minVersion)
	}
}

func versionAtLeast(vStr string, minVersion []int) error {
	vParts := strings.Split(vStr, ".")
	n := len(vParts)
	if n == 0 {
		return fmt.Errorf("parsing version '%s'", vStr)
	}

	var err error
	version := make([]int, len(vParts))
	for i, p := range vParts {
		version[i], err = strconv.Atoi(p)
		if err != nil {
			return fmt.Errorf("parsing version '%s' %v", vStr, err)
		}
	}

	if n > len(minVersion) {
		version = version[:len(minVersion)]
	}
	for i, vp := range version {
		minVP := minVersion[i]
		if minVP > vp {
			return fmt.Errorf("'%s' is not at least '%d'", vStr, minVersion)
		} else if minVP < vp {
			return nil
		}
	}
	if n < len(minVersion) {
		return fmt.Errorf("'%s' is not at least '%d'", vStr, minVersion)
	}
	return nil
}

// CheckDriver confirms that docker is using the provided driver.
func CheckDriver(driver string) Check {
	return func() error {
		cli, err := newClient()
		if err != nil {
			return fmt.Errorf("creating client: %v", err)
		}
		info, err := cli.Info()
		if err != nil {
			return fmt.Errorf("getting info: %v", err)
		}
		if info.Driver != driver {
			return fmt.Errorf("driver is '%s' not 'driver'", info.Driver, driver)
		}
		return nil
	}
}

// CheckSimpleCommand executes a simple echo command to ensure docker can
// create and run a container.
func CheckSimpleCommand() (err error) {
	cli, err := newClient()
	if err != nil {
		return fmt.Errorf("creating client: %v", err)
	}
	echoStr := "hello"
	config := &docker.ContainerConfig{
		Image: "ubuntu:14.04",
		Cmd:   []string{"echo", "-n", echoStr},
	}
	cid, err := cli.CreateContainer(config, "hellotest")
	if err != nil {
		return fmt.Errorf("creating container: %v", err)
	}
	defer func() {
		rcErr := cli.RemoveContainer(cid, true, false)
		if err == nil {
			err = rcErr
		} else if rcErr != nil {
			fmt.Fprintf(os.Stderr, "removing container %v\n", rcErr)
		}
	}()

	// attach to the container
	streamOpts := &docker.AttachOptions{Stream: true, Stdout: true, Stderr: true}
	stream, err := cli.Attach(cid, streamOpts)
	if err != nil {
		return err
	}
	defer stream.Close()

	// start the container
	err = cli.StartContainer(cid, &docker.HostConfig{})
	if err != nil {
		return err
	}

	var stderr bytes.Buffer
	var stdout bytes.Buffer
	if err = docker.SplitStream(stream, &stdout, &stderr); err != nil {
		return fmt.Errorf("reading stdout: %v", err)
	}

	// wait for the container to exit
	exitCode, err := cli.Wait(cid)
	if err != nil {
		return err
	}

	if exitCode != 0 {
		return fmt.Errorf("bad exit code %d %s", exitCode, stderr)
	}
	if out := stdout.String(); out != echoStr {
		return fmt.Errorf("expected '%s' from echo, got '%s'", echoStr, out)
	}
	if sErr := stderr.String(); sErr != "" {
		return fmt.Errorf("expected no response from stderr, got '%s'", sErr)
	}

	return nil
}

// CheckExposedPort confirms that docker can expose a port to the host machine
// when creating a container.
func CheckExposedPort() (err error) {
	cli, err := newClient()
	if err != nil {
		return fmt.Errorf("creating client: %v", err)
	}
	port := "8000/tcp"

	hostConfig := docker.HostConfig{
		PortBindings: map[string][]docker.PortBinding{
			port: []docker.PortBinding{
				// Docker will auto assign the port if left blank
				{HostIp: "0.0.0.0", HostPort: ""},
			},
		},
	}

	config := &docker.ContainerConfig{
		Image: "ubuntu:14.04",
		Cmd:   []string{"python3", "-m", "http.server"},
		ExposedPorts: map[string]struct{}{
			port: struct{}{},
		},
		HostConfig: hostConfig,
	}
	cid, err := cli.CreateContainer(config, "exposedporttest")
	if err != nil {
		return fmt.Errorf("creating container: %v", err)
	}
	defer func() {
		rcErr := cli.RemoveContainer(cid, true, false)
		if err == nil {
			err = rcErr
		} else if rcErr != nil {
			fmt.Fprintf(os.Stderr, "removing container %v\n", rcErr)
		}
	}()
	// start the container and get the port Docker assigned to it
	if err = cli.StartContainer(cid, &hostConfig); err != nil {
		return fmt.Errorf("start container: %v", err)
	}
	info, err := cli.InspectContainer(cid)
	if err != nil {
		return fmt.Errorf("inspect container: %v", err)
	}
	exposed, ok := info.NetworkSettings.Ports[port]
	if !ok {
		return fmt.Errorf("no ports exposed")
	}
	if len(exposed) != 1 {
		return fmt.Errorf("expected one exposed port, got %s", exposed)
	}
	hostIp, hostPort := exposed[0].HostIp, exposed[0].HostPort

	u := (&url.URL{
		Scheme: "http",
		Host:   hostIp + ":" + hostPort,
		Path:   "/",
	}).String()

	// give the simple http server a second to start up
	time.Sleep(500 * time.Millisecond)
	resp, err = http.Get(u)
	if resp != nil {
		defer resp.Body.Close()
	}
	if err != nil {
		return fmt.Errorf("connecting to docker port %v", err)
	}
	return nil
}

// CheckInternetAccess confirms that processes within docker containers can
// access the outside internet by making a request to a PyPi JSON page and
// parsing the response.
func CheckInternetAccess() (err error) {
	cli, err := newClient()
	if err != nil {
		return fmt.Errorf("creating client: %v", err)
	}

	u := "https://pypi.python.org/pypi/yhat/json"
	// attempt to access a remote address
	cmd := `import urllib.request as r; print(r.urlopen("` + u + `").read().decode("utf-8"))`
	config := &docker.ContainerConfig{
		Image: "ubuntu:14.04",
		Cmd:   []string{"python3", "-c", cmd},
	}

	cid, err := cli.CreateContainer(config, "internetaccesstest")
	if err != nil {
		return fmt.Errorf("creating container: %v", err)
	}
	defer func() {
		rcErr := cli.RemoveContainer(cid, true, false)
		if err == nil {
			err = rcErr
		} else if rcErr != nil {
			fmt.Fprintf(os.Stderr, "removing container %v\n", rcErr)
		}
	}()

	// attach to the container
	streamOpts := &docker.AttachOptions{Stream: true, Stdout: true, Stderr: true}
	stream, err := cli.Attach(cid, streamOpts)
	if err != nil {
		return err
	}
	defer stream.Close()

	// start the container
	err = cli.StartContainer(cid, &docker.HostConfig{})
	if err != nil {
		return err
	}

	var stderr bytes.Buffer
	var stdout bytes.Buffer
	if err = docker.SplitStream(stream, &stdout, &stderr); err != nil {
		return fmt.Errorf("reading stdout: %v", err)
	}

	// wait for the container to exit
	exitCode, err := cli.Wait(cid)
	if err != nil {
		return err
	}

	if exitCode != 0 {
		return fmt.Errorf("bad exit code %d %s", exitCode, stderr)
	}

	var data struct {
		Info struct {
			HomePage string `json:"home_page"`
		} `json:"info"`
	}
	if err = json.Unmarshal(stdout.Bytes(), &data); err != nil {
		return fmt.Errorf("decoding JSON response from pypi: %v", err)
	}
	if data.Info.HomePage != "https://github.com/yhat/yhat-client" {
		return fmt.Errorf("bad JSON returned from %s", u)
	}
	if sErr := stderr.String(); sErr != "" {
		return fmt.Errorf("expected no response from stderr, got '%s'", sErr)
	}

	return nil
}

// CheckFileMounting confirms that docker can mount a file on the host machine
// into a container.
func CheckFileMounting() (err error) {
	cli, err := newClient()
	if err != nil {
		return fmt.Errorf("creating client: %v", err)
	}

	tempDir, err := ioutil.TempDir("", "")
	if err != nil {
		return fmt.Errorf("creating temp dir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	fileName := "foo"

	file, err := os.Create(filepath.Join(tempDir, fileName))
	if err != nil {
		return fmt.Errorf("creating file: %v", err)
	}

	// write 1 KiB of random data to the file
	randData := make([]byte, 1<<10)
	if _, err = io.ReadFull(rand.Reader, randData); err != nil {
		return fmt.Errorf("reading random data: %v", err)
	}
	if _, err = file.Write(randData); err != nil {
		return fmt.Errorf("writing random data: %v", err)
	}

	targetDir := "/tmp/bar"
	targetFile := "/tmp/bar/" + fileName

	volumes := map[string]struct{}{targetDir: struct{}{}}
	bindings := []string{tempDir + ":" + targetDir}

	hostConfig := docker.HostConfig{Binds: bindings}

	config := &docker.ContainerConfig{
		Image:      "ubuntu:14.04",
		Cmd:        []string{"cat", targetFile},
		Volumes:    volumes,
		HostConfig: hostConfig,
	}
	cid, err := cli.CreateContainer(config, "filemounttest")
	if err != nil {
		return fmt.Errorf("creating container: %v", err)
	}
	defer func() {
		rcErr := cli.RemoveContainer(cid, true, false)
		if err == nil {
			err = rcErr
		} else if rcErr != nil {
			fmt.Fprintf(os.Stderr, "removing container %v\n", rcErr)
		}
	}()

	// attach to the container
	streamOpts := &docker.AttachOptions{Stream: true, Stdout: true, Stderr: true}
	stream, err := cli.Attach(cid, streamOpts)
	if err != nil {
		return err
	}
	defer stream.Close()

	// start the container
	err = cli.StartContainer(cid, &docker.HostConfig{})
	if err != nil {
		return err
	}

	var stderr bytes.Buffer
	var stdout bytes.Buffer
	if err = docker.SplitStream(stream, &stdout, &stderr); err != nil {
		return fmt.Errorf("reading stdout: %v", err)
	}

	// wait for the container to exit
	exitCode, err := cli.Wait(cid)
	if err != nil {
		return err
	}

	if exitCode != 0 {
		return fmt.Errorf("bad exit code %d %s", exitCode, stderr)
	}
	if bytes.Compare(stdout.Bytes(), randData) != 0 {
		return fmt.Errorf("mounted file did not match")
	}
	if sErr := stderr.String(); sErr != "" {
		return fmt.Errorf("expected no response from stderr, got '%s'", sErr)
	}

	return nil
}
<commit_msg>: is helpful<commit_after>package checks

import (
	"bytes"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/yhat/go-docker"
)

var dockerSock = "unix:///var/run/docker.sock"

type Check func() error

type namedCheck struct {
	Name  string
	Check Check
}

type Checker struct {
	checks []namedCheck
}

func (c *Checker) Register(check Check, name string) {
	c.checks = append(c.checks, namedCheck{
		Name:  name,
		Check: check,
	})
}

func (c *Checker) Run() (exitCode int) {
	maxNameLen := 0
	for _, check := range c.checks {
		if n := len(check.Name); maxNameLen < n {
			maxNameLen = n
		}
	}
	s := strconv.Itoa(maxNameLen)
	pad := func(name string) string {
		return fmt.Sprintf("%-"+s+"s", name)
	}
	var err error
	for _, check := range c.checks {
		name := pad(check.Name)
		fmt.Print(name + " ... ")
		start := time.Now()
		err = check.Check()
		delta := time.Now().Sub(start)
		if err != nil {
			exitCode = 2
			fmt.Println("ERROR")
			fmt.Printf("\t%v\n", err)
		} else {
			fmt.Printf("OK (%s)\n", delta)
		}
	}
	return 0
}

func newClient() (*docker.Client, error) {
	return docker.NewClient(dockerSock, nil, 3*time.Second)
}

// CheckDockerSocket confirms that docker can be reached
func CheckDockerSocket() error {
	cli, err := newClient()
	if err != nil {
		return fmt.Errorf("creating client: %v", err)
	}
	_, err = cli.Info()
	if err != nil {
		return fmt.Errorf("simple ping: %v", err)
	}
	return nil
}

// CheckVersion confirms docker is at least the version passed.
func CheckVersion(minVersion []int) Check {
	return func() error {
		cli, err := newClient()
		if err != nil {
			return fmt.Errorf("creating client: %v", err)
		}
		v, err := cli.Version()
		if err != nil {
			return fmt.Errorf("getting version: %v", err)
		}
		return versionAtLeast(v.Version, minVersion)
	}
}

func versionAtLeast(vStr string, minVersion []int) error {
	vParts := strings.Split(vStr, ".")
	n := len(vParts)
	if n == 0 {
		return fmt.Errorf("parsing version '%s'", vStr)
	}

	var err error
	version := make([]int, len(vParts))
	for i, p := range vParts {
		version[i], err = strconv.Atoi(p)
		if err != nil {
			return fmt.Errorf("parsing version '%s' %v", vStr, err)
		}
	}

	if n > len(minVersion) {
		version = version[:len(minVersion)]
	}
	for i, vp := range version {
		minVP := minVersion[i]
		if minVP > vp {
			return fmt.Errorf("'%s' is not at least '%d'", vStr, minVersion)
		} else if minVP < vp {
			return nil
		}
	}
	if n < len(minVersion) {
		return fmt.Errorf("'%s' is not at least '%d'", vStr, minVersion)
	}
	return nil
}

// CheckDriver confirms that docker is using the provided driver.
func CheckDriver(driver string) Check {
	return func() error {
		cli, err := newClient()
		if err != nil {
			return fmt.Errorf("creating client: %v", err)
		}
		info, err := cli.Info()
		if err != nil {
			return fmt.Errorf("getting info: %v", err)
		}
		if info.Driver != driver {
			return fmt.Errorf("driver is '%s' not 'driver'", info.Driver, driver)
		}
		return nil
	}
}

// CheckSimpleCommand executes a simple echo command to ensure docker can
// create and run a container.
func CheckSimpleCommand() (err error) {
	cli, err := newClient()
	if err != nil {
		return fmt.Errorf("creating client: %v", err)
	}
	echoStr := "hello"
	config := &docker.ContainerConfig{
		Image: "ubuntu:14.04",
		Cmd:   []string{"echo", "-n", echoStr},
	}
	cid, err := cli.CreateContainer(config, "hellotest")
	if err != nil {
		return fmt.Errorf("creating container: %v", err)
	}
	defer func() {
		rcErr := cli.RemoveContainer(cid, true, false)
		if err == nil {
			err = rcErr
		} else if rcErr != nil {
			fmt.Fprintf(os.Stderr, "removing container %v\n", rcErr)
		}
	}()

	// attach to the container
	streamOpts := &docker.AttachOptions{Stream: true, Stdout: true, Stderr: true}
	stream, err := cli.Attach(cid, streamOpts)
	if err != nil {
		return err
	}
	defer stream.Close()

	// start the container
	err = cli.StartContainer(cid, &docker.HostConfig{})
	if err != nil {
		return err
	}

	var stderr bytes.Buffer
	var stdout bytes.Buffer
	if err = docker.SplitStream(stream, &stdout, &stderr); err != nil {
		return fmt.Errorf("reading stdout: %v", err)
	}

	// wait for the container to exit
	exitCode, err := cli.Wait(cid)
	if err != nil {
		return err
	}

	if exitCode != 0 {
		return fmt.Errorf("bad exit code %d %s", exitCode, stderr)
	}
	if out := stdout.String(); out != echoStr {
		return fmt.Errorf("expected '%s' from echo, got '%s'", echoStr, out)
	}
	if sErr := stderr.String(); sErr != "" {
		return fmt.Errorf("expected no response from stderr, got '%s'", sErr)
	}

	return nil
}

// CheckExposedPort confirms that docker can expose a port to the host machine
// when creating a container.
func CheckExposedPort() (err error) {
	cli, err := newClient()
	if err != nil {
		return fmt.Errorf("creating client: %v", err)
	}
	port := "8000/tcp"

	hostConfig := docker.HostConfig{
		PortBindings: map[string][]docker.PortBinding{
			port: []docker.PortBinding{
				// Docker will auto assign the port if left blank
				{HostIp: "0.0.0.0", HostPort: ""},
			},
		},
	}

	config := &docker.ContainerConfig{
		Image: "ubuntu:14.04",
		Cmd:   []string{"python3", "-m", "http.server"},
		ExposedPorts: map[string]struct{}{
			port: struct{}{},
		},
		HostConfig: hostConfig,
	}
	cid, err := cli.CreateContainer(config, "exposedporttest")
	if err != nil {
		return fmt.Errorf("creating container: %v", err)
	}
	defer func() {
		rcErr := cli.RemoveContainer(cid, true, false)
		if err == nil {
			err = rcErr
		} else if rcErr != nil {
			fmt.Fprintf(os.Stderr, "removing container %v\n", rcErr)
		}
	}()
	// start the container and get the port Docker assigned to it
	if err = cli.StartContainer(cid, &hostConfig); err != nil {
		return fmt.Errorf("start container: %v", err)
	}
	info, err := cli.InspectContainer(cid)
	if err != nil {
		return fmt.Errorf("inspect container: %v", err)
	}
	exposed, ok := info.NetworkSettings.Ports[port]
	if !ok {
		return fmt.Errorf("no ports exposed")
	}
	if len(exposed) != 1 {
		return fmt.Errorf("expected one exposed port, got %s", exposed)
	}
	hostIp, hostPort := exposed[0].HostIp, exposed[0].HostPort

	u := (&url.URL{
		Scheme: "http",
		Host:   hostIp + ":" + hostPort,
		Path:   "/",
	}).String()

	// give the simple http server a second to start up
	time.Sleep(500 * time.Millisecond)
	resp, err := http.Get(u)
	if resp != nil {
		defer resp.Body.Close()
	}
	if err != nil {
		return fmt.Errorf("connecting to docker port %v", err)
	}
	return nil
}

// CheckInternetAccess confirms that processes within docker containers can
// access the outside internet by making a request to a PyPi JSON page and
// parsing the response.
func CheckInternetAccess() (err error) {
	cli, err := newClient()
	if err != nil {
		return fmt.Errorf("creating client: %v", err)
	}

	u := "https://pypi.python.org/pypi/yhat/json"
	// attempt to access a remote address
	cmd := `import urllib.request as r; print(r.urlopen("` + u + `").read().decode("utf-8"))`
	config := &docker.ContainerConfig{
		Image: "ubuntu:14.04",
		Cmd:   []string{"python3", "-c", cmd},
	}

	cid, err := cli.CreateContainer(config, "internetaccesstest")
	if err != nil {
		return fmt.Errorf("creating container: %v", err)
	}
	defer func() {
		rcErr := cli.RemoveContainer(cid, true, false)
		if err == nil {
			err = rcErr
		} else if rcErr != nil {
			fmt.Fprintf(os.Stderr, "removing container %v\n", rcErr)
		}
	}()

	// attach to the container
	streamOpts := &docker.AttachOptions{Stream: true, Stdout: true, Stderr: true}
	stream, err := cli.Attach(cid, streamOpts)
	if err != nil {
		return err
	}
	defer stream.Close()

	// start the container
	err = cli.StartContainer(cid, &docker.HostConfig{})
	if err != nil {
		return err
	}

	var stderr bytes.Buffer
	var stdout bytes.Buffer
	if err = docker.SplitStream(stream, &stdout, &stderr); err != nil {
		return fmt.Errorf("reading stdout: %v", err)
	}

	// wait for the container to exit
	exitCode, err := cli.Wait(cid)
	if err != nil {
		return err
	}

	if exitCode != 0 {
		return fmt.Errorf("bad exit code %d %s", exitCode, stderr)
	}

	var data struct {
		Info struct {
			HomePage string `json:"home_page"`
		} `json:"info"`
	}
	if err = json.Unmarshal(stdout.Bytes(), &data); err != nil {
		return fmt.Errorf("decoding JSON response from pypi: %v", err)
	}
	if data.Info.HomePage != "https://github.com/yhat/yhat-client" {
		return fmt.Errorf("bad JSON returned from %s", u)
	}
	if sErr := stderr.String(); sErr != "" {
		return fmt.Errorf("expected no response from stderr, got '%s'", sErr)
	}

	return nil
}

// CheckFileMounting confirms that docker can mount a file on the host machine
// into a container.
func CheckFileMounting() (err error) {
	cli, err := newClient()
	if err != nil {
		return fmt.Errorf("creating client: %v", err)
	}

	tempDir, err := ioutil.TempDir("", "")
	if err != nil {
		return fmt.Errorf("creating temp dir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	fileName := "foo"

	file, err := os.Create(filepath.Join(tempDir, fileName))
	if err != nil {
		return fmt.Errorf("creating file: %v", err)
	}

	// write 1 KiB of random data to the file
	randData := make([]byte, 1<<10)
	if _, err = io.ReadFull(rand.Reader, randData); err != nil {
		return fmt.Errorf("reading random data: %v", err)
	}
	if _, err = file.Write(randData); err != nil {
		return fmt.Errorf("writing random data: %v", err)
	}

	targetDir := "/tmp/bar"
	targetFile := "/tmp/bar/" + fileName

	volumes := map[string]struct{}{targetDir: struct{}{}}
	bindings := []string{tempDir + ":" + targetDir}

	hostConfig := docker.HostConfig{Binds: bindings}

	config := &docker.ContainerConfig{
		Image:      "ubuntu:14.04",
		Cmd:        []string{"cat", targetFile},
		Volumes:    volumes,
		HostConfig: hostConfig,
	}
	cid, err := cli.CreateContainer(config, "filemounttest")
	if err != nil {
		return fmt.Errorf("creating container: %v", err)
	}
	defer func() {
		rcErr := cli.RemoveContainer(cid, true, false)
		if err == nil {
			err = rcErr
		} else if rcErr != nil {
			fmt.Fprintf(os.Stderr, "removing container %v\n", rcErr)
		}
	}()

	// attach to the container
	streamOpts := &docker.AttachOptions{Stream: true, Stdout: true, Stderr: true}
	stream, err := cli.Attach(cid, streamOpts)
	if err != nil {
		return err
	}
	defer stream.Close()

	// start the container
	err = cli.StartContainer(cid, &docker.HostConfig{})
	if err != nil {
		return err
	}

	var stderr bytes.Buffer
	var stdout bytes.Buffer
	if err = docker.SplitStream(stream, &stdout, &stderr); err != nil {
		return fmt.Errorf("reading stdout: %v", err)
	}

	// wait for the container to exit
	exitCode, err := cli.Wait(cid)
	if err != nil {
		return err
	}

	if exitCode != 0 {
		return fmt.Errorf("bad exit code %d %s", exitCode, stderr)
	}
	if bytes.Compare(stdout.Bytes(), randData) != 0 {
		return fmt.Errorf("mounted file did not match")
	}
	if sErr := stderr.String(); sErr != "" {
		return fmt.Errorf("expected no response from stderr, got '%s'", sErr)
	}

	return nil
}
<|endoftext|>


<commit_before>// microcache is an HTTP cache implemented as Go middleware.
package microcache

import (
	"net/http"
	"strings"
	"time"
)

type microcache struct {
	Nocache              bool
	Timeout              time.Duration
	TTL                  time.Duration
	StaleIfError         time.Duration
	StaleRecache         bool
	StaleWhileRevalidate time.Duration
	TTLSync              bool
	HashQuery            bool
	CollapsedForwarding  bool
	Vary                 []string
	Driver               Driver
	Monitor              Monitor
	Exposed              bool

	stopMonitor chan bool
}

type Config struct {
	// Nocache prevents responses from being cached by default
	// Can be overridden by the microcache-cache and microcache-nocache response headers
	Nocache bool

	// Timeout specifies the maximum execution time for backend responses
	// Example: If the underlying handler takes more than 10s to respond,
	// the request is cancelled and the response is treated as 503
	// Recommended: 10s
	// Default: 0
	Timeout time.Duration

	// TTL specifies a default ttl for cached responses
	// Can be overridden by the microcache-ttl response header
	// Recommended: 10s
	// Default: 0
	TTL time.Duration

	// StaleWhileRevalidate specifies a period during which a stale response may be
	// served immediately while the resource is fetched in the background. This can be
	// useful for ensuring consistent response times at the cost of content freshness.
	// More Info: https://tools.ietf.org/html/rfc5861
	// Recommended: 20s
	// Default: 0
	StaleWhileRevalidate time.Duration

	// StaleIfError specifies a default stale grace period
	// If a request fails and StaleIfError is set, the object will be served as stale
	// and the response will be re-cached for the duration of this grace period
	// Can be overridden by the microcache-ttl-stale response header
	// More Info: https://tools.ietf.org/html/rfc5861
	// Recommended: 20s
	// Default: 0
	StaleIfError time.Duration

	// StaleRecache specifies whether to re-cache the response object for ttl while serving
	// stale response on backend error
	// Recommended: true
	// Default: false
	StaleRecache bool

	// StaleWhileRevalidate specifies a period during which a stale response may be
	// served immediately while the resource is fetched in the background. This can be
	// useful for ensuring consistent response times at the cost of content freshness.
	// Recommended: 20s
	// Default: 0
	CollapsedFowarding bool

	// TTLSync will lock TTLs to the system clock to improve consistency between caches.
	// This can help prevent parallel caches from expiring response objects at different times.
	// This assumes that the parallel caches in question have synchronised clocks (see ntpd)
	// This will cause response expiration to vary between now and now + ttl due to rounding.
	// A 10s TTL applied at 09:12:43 will set expires to 09:12:50 rather than 09:12:53 (7s).
	// A 10s TTL applied at 09:12:49 will set expires to 09:12:50 rather than 09:12:59 (1s).
	// This should only be set where consistency is more important than efficiency.
	// Can be overridden by the microcache-ttl-sync response header
	// Default: false
	TTLSync bool

	// HashQuery determines whether all query parameters in the request URI
	// should be hashed to differentiate requests
	// Default: false
	HashQuery bool

	// Vary specifies a list of http request headers by which all requests
	// should be differentiated. When making use of this option, it may be a good idea
	// to normalize these headers first using a separate piece of middleware.
	//
	//   []string{"accept-language", "accept-encoding", "xml-http-request"}
	//
	// Default: []string{}
	Vary []string

	// Driver specifies a cache storage driver.
	// Default: gcache with 10,000 item capacity
	Driver Driver

	// Monitor is an optional parameter which will periodically report statistics about
	// the cache to enable monitoring of cache size, cache efficiency and error rate
	// Default: nil
	Monitor Monitor

	// Exposed determines whether to add a header to the response indicating the response state
	// Microcache: ( HIT | MISS | STALE )
	// Default: 0
	Exposed bool
}

// New creates and returns a configured microcache instance
func New(o Config) microcache {
	// Defaults
	m := microcache{
		Nocache:              o.Nocache,
		TTL:                  o.TTL,
		StaleIfError:         o.StaleIfError,
		StaleRecache:         o.StaleRecache,
		StaleWhileRevalidate: o.StaleWhileRevalidate,
		TTLSync:              o.TTLSync,
		Timeout:              o.Timeout,
		HashQuery:            o.HashQuery,
		CollapsedForwarding:  true,
		Vary:                 o.Vary,
		Driver:               o.Driver,
		Monitor:              o.Monitor,
		Exposed:              o.Exposed,
	}
	if o.Driver == nil {
		m.Driver = NewGcacheDriver(1e4) // default 10k cache items
	}
	m.Start()
	return m
}

// Middleware can be used to wrap an HTTP handler with microcache functionality.
// It can also be passed to http middleware providers like alice as a constructor.
//
//     mx := microcache.New(microcache.Config{TTL: 10 * time.Second})
//     newHandler := mx.Middleware(yourHandler)
//
// Or with alice
//
//    chain.Append(mx.Middleware)
//
func (m *microcache) Middleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Websocket passthrough
		upgrade := strings.ToLower(r.Header.Get("connection")) == "upgrade"
		if upgrade || m.Driver == nil {
			h.ServeHTTP(w, r)
			return
		}

		// Fetch request options
		reqHash := getRequestHash(m, r)
		req := m.Driver.GetRequestOpts(reqHash)

		// Hard passthrough on non cacheable requests
		if req.nocache {
			h.ServeHTTP(w, r)
			return
		}

		// Fetch cached response object
		var objHash string
		var obj Response
		if req.found {
			objHash = req.getObjectHash(reqHash, r)
			obj = m.Driver.Get(objHash)
		}

		// Non-cacheable request method passthrough and purge
		if r.Method != "GET" && r.Method != "HEAD" {
			if obj.found {
				// HTTP spec requires caches to purge cached responses following
				//  successful unsafe request.
				ptw := passthroughWriter{w, 0}
				h.ServeHTTP(ptw, r)
				if ptw.status >= 200 && ptw.status < 400 {
					m.Driver.Remove(objHash)
				}
			} else {
				h.ServeHTTP(w, r)
			}
			return
		}

		// Fresh response object found
		if obj.found && obj.expires.After(time.Now()) {
			if m.Monitor != nil {
				m.Monitor.Hit()
			}
			if m.Exposed {
				w.Header().Set("microcache", "HIT")
			}
			obj.sendResponse(w)
			return
		}

		// Stale While Revalidate
		if obj.found && req.staleWhileRevalidate > 0 &&
			obj.expires.Add(req.staleWhileRevalidate).After(time.Now()) {
			obj.sendResponse(w)
			if m.Exposed {
				w.Header().Set("microcache", "STALE")
			}
			go m.handleBackendResponse(h, w, r, reqHash, req, objHash, obj, true)
			return
		} else {
			m.handleBackendResponse(h, w, r, reqHash, req, objHash, obj, false)
			return
		}
	})
}

func (m *microcache) handleBackendResponse(
	h http.Handler,
	w http.ResponseWriter,
	r *http.Request,
	reqHash string,
	req RequestOpts,
	objHash string,
	obj Response,
	revalidating bool,
) {
	// Backend Response
	beres := Response{header: http.Header{}}

	if req.collapsedForwarding && req.found && req.ttl > 0 {
		// collapsedForwarding not yet implemented
	}

	// Execute request
	if m.Timeout > 0 {
		th := http.TimeoutHandler(h, m.Timeout, "Timed out")
		th.ServeHTTP(&beres, r)
	} else {
		h.ServeHTTP(&beres, r)
	}

	// Serve Stale
	if beres.status >= 500 && obj.found {
		// Extend stale response expiration by staleIfError grace period
		if req.found && req.staleRecache {
			obj.setExpires(req.ttl, req.ttlSync)
			m.Driver.Set(objHash, obj)
		}
		if m.Monitor != nil {
			m.Monitor.Error()
		}
		if !revalidating && obj.expires.Add(req.staleIfError).After(time.Now()) {
			if m.Exposed {
				w.Header().Set("microcache", "STALE")
			}
			obj.sendResponse(w)
			return
		}
	}

	if beres.status >= 200 && beres.status < 400 {
		if !req.found {
			// Store request options
			req = buildRequestOpts(m, beres, r)
			m.Driver.SetRequestOpts(reqHash, req)
			objHash = req.getObjectHash(reqHash, r)
		}
		// Cache response
		if !req.nocache {
			beres.found = true
			beres.setExpires(req.ttl, req.ttlSync)
			m.Driver.Set(objHash, beres)
		}
	}

	if !revalidating {
		if m.Exposed {
			w.Header().Set("microcache", "MISS")
		}
		beres.sendResponse(w)
		return
	}
}

// Start starts the monitor and any other required background processes
func (m *microcache) Start() {
	m.stopMonitor = make(chan bool)
	if m.Monitor != nil {
		go func() {
			for {
				select {
				case <-time.After(m.Monitor.GetInterval()):
					m.Monitor.Log(Stats{
						Size: m.Driver.GetSize(),
					})
				case <-m.stopMonitor:
					return
				}
			}
		}()
	}
}

// Stop stops the monitor and any other required background processes
func (m *microcache) Stop() {
	m.stopMonitor <- true
}
<commit_msg>Fixing serveStale logic<commit_after>// microcache is an HTTP cache implemented as Go middleware.
package microcache

import (
	"net/http"
	"strings"
	"time"
)

type microcache struct {
	Nocache              bool
	Timeout              time.Duration
	TTL                  time.Duration
	StaleIfError         time.Duration
	StaleRecache         bool
	StaleWhileRevalidate time.Duration
	TTLSync              bool
	HashQuery            bool
	CollapsedForwarding  bool
	Vary                 []string
	Driver               Driver
	Monitor              Monitor
	Exposed              bool

	stopMonitor chan bool
}

type Config struct {
	// Nocache prevents responses from being cached by default
	// Can be overridden by the microcache-cache and microcache-nocache response headers
	Nocache bool

	// Timeout specifies the maximum execution time for backend responses
	// Example: If the underlying handler takes more than 10s to respond,
	// the request is cancelled and the response is treated as 503
	// Recommended: 10s
	// Default: 0
	Timeout time.Duration

	// TTL specifies a default ttl for cached responses
	// Can be overridden by the microcache-ttl response header
	// Recommended: 10s
	// Default: 0
	TTL time.Duration

	// StaleWhileRevalidate specifies a period during which a stale response may be
	// served immediately while the resource is fetched in the background. This can be
	// useful for ensuring consistent response times at the cost of content freshness.
	// More Info: https://tools.ietf.org/html/rfc5861
	// Recommended: 20s
	// Default: 0
	StaleWhileRevalidate time.Duration

	// StaleIfError specifies a default stale grace period
	// If a request fails and StaleIfError is set, the object will be served as stale
	// and the response will be re-cached for the duration of this grace period
	// Can be overridden by the microcache-ttl-stale response header
	// More Info: https://tools.ietf.org/html/rfc5861
	// Recommended: 20s
	// Default: 0
	StaleIfError time.Duration

	// StaleRecache specifies whether to re-cache the response object for ttl while serving
	// stale response on backend error
	// Recommended: true
	// Default: false
	StaleRecache bool

	// StaleWhileRevalidate specifies a period during which a stale response may be
	// served immediately while the resource is fetched in the background. This can be
	// useful for ensuring consistent response times at the cost of content freshness.
	// Recommended: 20s
	// Default: 0
	CollapsedFowarding bool

	// TTLSync will lock TTLs to the system clock to improve consistency between caches.
	// This can help prevent parallel caches from expiring response objects at different times.
	// This assumes that the parallel caches in question have synchronised clocks (see ntpd)
	// This will cause response expiration to vary between now and now + ttl due to rounding.
	// A 10s TTL applied at 09:12:43 will set expires to 09:12:50 rather than 09:12:53 (7s).
	// A 10s TTL applied at 09:12:49 will set expires to 09:12:50 rather than 09:12:59 (1s).
	// This should only be set where consistency is more important than efficiency.
	// Can be overridden by the microcache-ttl-sync response header
	// Default: false
	TTLSync bool

	// HashQuery determines whether all query parameters in the request URI
	// should be hashed to differentiate requests
	// Default: false
	HashQuery bool

	// Vary specifies a list of http request headers by which all requests
	// should be differentiated. When making use of this option, it may be a good idea
	// to normalize these headers first using a separate piece of middleware.
	//
	//   []string{"accept-language", "accept-encoding", "xml-http-request"}
	//
	// Default: []string{}
	Vary []string

	// Driver specifies a cache storage driver.
	// Default: gcache with 10,000 item capacity
	Driver Driver

	// Monitor is an optional parameter which will periodically report statistics about
	// the cache to enable monitoring of cache size, cache efficiency and error rate
	// Default: nil
	Monitor Monitor

	// Exposed determines whether to add a header to the response indicating the response state
	// Microcache: ( HIT | MISS | STALE )
	// Default: 0
	Exposed bool
}

// New creates and returns a configured microcache instance
func New(o Config) microcache {
	// Defaults
	m := microcache{
		Nocache:              o.Nocache,
		TTL:                  o.TTL,
		StaleIfError:         o.StaleIfError,
		StaleRecache:         o.StaleRecache,
		StaleWhileRevalidate: o.StaleWhileRevalidate,
		TTLSync:              o.TTLSync,
		Timeout:              o.Timeout,
		HashQuery:            o.HashQuery,
		CollapsedForwarding:  true,
		Vary:                 o.Vary,
		Driver:               o.Driver,
		Monitor:              o.Monitor,
		Exposed:              o.Exposed,
	}
	if o.Driver == nil {
		m.Driver = NewGcacheDriver(1e4) // default 10k cache items
	}
	m.Start()
	return m
}

// Middleware can be used to wrap an HTTP handler with microcache functionality.
// It can also be passed to http middleware providers like alice as a constructor.
//
//     mx := microcache.New(microcache.Config{TTL: 10 * time.Second})
//     newHandler := mx.Middleware(yourHandler)
//
// Or with alice
//
//    chain.Append(mx.Middleware)
//
func (m *microcache) Middleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Websocket passthrough
		upgrade := strings.ToLower(r.Header.Get("connection")) == "upgrade"
		if upgrade || m.Driver == nil {
			h.ServeHTTP(w, r)
			return
		}

		// Fetch request options
		reqHash := getRequestHash(m, r)
		req := m.Driver.GetRequestOpts(reqHash)

		// Hard passthrough on non cacheable requests
		if req.nocache {
			h.ServeHTTP(w, r)
			return
		}

		// Fetch cached response object
		var objHash string
		var obj Response
		if req.found {
			objHash = req.getObjectHash(reqHash, r)
			obj = m.Driver.Get(objHash)
		}

		// Non-cacheable request method passthrough and purge
		if r.Method != "GET" && r.Method != "HEAD" {
			if obj.found {
				// HTTP spec requires caches to purge cached responses following
				//  successful unsafe request.
				ptw := passthroughWriter{w, 0}
				h.ServeHTTP(ptw, r)
				if ptw.status >= 200 && ptw.status < 400 {
					m.Driver.Remove(objHash)
				}
			} else {
				h.ServeHTTP(w, r)
			}
			return
		}

		// Fresh response object found
		if obj.found && obj.expires.After(time.Now()) {
			if m.Monitor != nil {
				m.Monitor.Hit()
			}
			if m.Exposed {
				w.Header().Set("microcache", "HIT")
			}
			obj.sendResponse(w)
			return
		}

		// Stale While Revalidate
		if obj.found && req.staleWhileRevalidate > 0 &&
			obj.expires.Add(req.staleWhileRevalidate).After(time.Now()) {
			obj.sendResponse(w)
			if m.Exposed {
				w.Header().Set("microcache", "STALE")
			}
			go m.handleBackendResponse(h, w, r, reqHash, req, objHash, obj, true)
			return
		} else {
			m.handleBackendResponse(h, w, r, reqHash, req, objHash, obj, false)
			return
		}
	})
}

func (m *microcache) handleBackendResponse(
	h http.Handler,
	w http.ResponseWriter,
	r *http.Request,
	reqHash string,
	req RequestOpts,
	objHash string,
	obj Response,
	revalidating bool,
) {
	// Backend Response
	beres := Response{header: http.Header{}}

	if req.collapsedForwarding && req.found && req.ttl > 0 {
		// collapsedForwarding not yet implemented
	}

	// Execute request
	if m.Timeout > 0 {
		th := http.TimeoutHandler(h, m.Timeout, "Timed out")
		th.ServeHTTP(&beres, r)
	} else {
		h.ServeHTTP(&beres, r)
	}

	// Serve Stale
	if beres.status >= 500 && obj.found {
		serveStale := obj.expires.Add(req.staleIfError).After(time.Now())
		// Extend stale response expiration by staleIfError grace period
		if req.found && serveStale && req.staleRecache {
			obj.setExpires(req.ttl, req.ttlSync)
			m.Driver.Set(objHash, obj)
		}
		if m.Monitor != nil {
			m.Monitor.Error()
		}
		if !revalidating && serveStale {
			if m.Exposed {
				w.Header().Set("microcache", "STALE")
			}
			obj.sendResponse(w)
			return
		}
	}

	if beres.status >= 200 && beres.status < 400 {
		if !req.found {
			// Store request options
			req = buildRequestOpts(m, beres, r)
			m.Driver.SetRequestOpts(reqHash, req)
			objHash = req.getObjectHash(reqHash, r)
		}
		// Cache response
		if !req.nocache {
			beres.found = true
			beres.setExpires(req.ttl, req.ttlSync)
			m.Driver.Set(objHash, beres)
		}
	}

	if !revalidating {
		if m.Exposed {
			w.Header().Set("microcache", "MISS")
		}
		beres.sendResponse(w)
		return
	}
}

// Start starts the monitor and any other required background processes
func (m *microcache) Start() {
	m.stopMonitor = make(chan bool)
	if m.Monitor != nil {
		go func() {
			for {
				select {
				case <-time.After(m.Monitor.GetInterval()):
					m.Monitor.Log(Stats{
						Size: m.Driver.GetSize(),
					})
				case <-m.stopMonitor:
					return
				}
			}
		}()
	}
}

// Stop stops the monitor and any other required background processes
func (m *microcache) Stop() {
	m.stopMonitor <- true
}
<|endoftext|>


<commit_before>// Copyright 2018 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

// Package gvisor provides support for gVisor, user-space kernel, testing.
// See https://github.com/google/gvisor
package gvisor

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/google/syzkaller/pkg/config"
	"github.com/google/syzkaller/pkg/log"
	"github.com/google/syzkaller/pkg/osutil"
	"github.com/google/syzkaller/vm/vmimpl"
)

func init() {
	vmimpl.Register("gvisor", ctor, true)
}

type Config struct {
	Count     int    `json:"count"` // number of VMs to use
	RunscArgs string `json:"runsc_args"`
}

type Pool struct {
	env *vmimpl.Env
	cfg *Config
}

type instance struct {
	cfg      *Config
	image    string
	debug    bool
	rootDir  string
	imageDir string
	name     string
	port     int
	cmd      *exec.Cmd
	merger   *vmimpl.OutputMerger
}

func ctor(env *vmimpl.Env) (vmimpl.Pool, error) {
	cfg := &Config{
		Count: 1,
	}
	if err := config.LoadData(env.Config, cfg); err != nil {
		return nil, fmt.Errorf("failed to parse vm config: %v", err)
	}
	if cfg.Count < 1 || cfg.Count > 128 {
		return nil, fmt.Errorf("invalid config param count: %v, want [1, 128]", cfg.Count)
	}
	if env.Debug && cfg.Count > 1 {
		log.Logf(0, "limiting number of VMs from %v to 1 in debug mode", cfg.Count)
		cfg.Count = 1
	}
	if !osutil.IsExist(env.Image) {
		return nil, fmt.Errorf("image file %q does not exist", env.Image)
	}
	pool := &Pool{
		cfg: cfg,
		env: env,
	}
	return pool, nil
}

func (pool *Pool) Count() int {
	return pool.cfg.Count
}

func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
	rootDir := filepath.Clean(filepath.Join(workdir, "..", "gvisor_root"))
	imageDir := filepath.Join(workdir, "image")
	bundleDir := filepath.Join(workdir, "bundle")
	osutil.MkdirAll(rootDir)
	osutil.MkdirAll(bundleDir)
	osutil.MkdirAll(imageDir)

	caps := ""
	for _, c := range sandboxCaps {
		if caps != "" {
			caps += ", "
		}
		caps += "\"" + c + "\""
	}
	vmConfig := fmt.Sprintf(configTempl, imageDir, caps)
	if err := osutil.WriteFile(filepath.Join(bundleDir, "config.json"), []byte(vmConfig)); err != nil {
		return nil, err
	}
	bin, err := exec.LookPath(os.Args[0])
	if err != nil {
		return nil, fmt.Errorf("failed to lookup %v: %v", os.Args[0], err)
	}
	if err := osutil.CopyFile(bin, filepath.Join(imageDir, "init")); err != nil {
		return nil, err
	}

	rpipe, wpipe, err := osutil.LongPipe()
	if err != nil {
		return nil, err
	}
	var tee io.Writer
	if pool.env.Debug {
		tee = os.Stdout
	}
	merger := vmimpl.NewOutputMerger(tee)
	merger.Add("gvisor", rpipe)

	inst := &instance{
		cfg:      pool.cfg,
		image:    pool.env.Image,
		debug:    pool.env.Debug,
		rootDir:  rootDir,
		imageDir: imageDir,
		name:     fmt.Sprintf("%v-%v", pool.env.Name, index),
		merger:   merger,
	}

	// Kill the previous instance in case it's still running.
	osutil.Run(time.Minute, inst.runscCmd("delete", "-force", inst.name))
	time.Sleep(3 * time.Second)

	cmd := inst.runscCmd("run", "-bundle", bundleDir, inst.name)
	cmd.Stdout = wpipe
	cmd.Stderr = wpipe
	if err := cmd.Start(); err != nil {
		wpipe.Close()
		merger.Wait()
		return nil, err
	}
	inst.cmd = cmd
	wpipe.Close()

	if err := inst.waitBoot(); err != nil {
		inst.Close()
		return nil, err
	}
	return inst, nil
}

func (inst *instance) waitBoot() error {
	errorMsg := []byte("FATAL ERROR:")
	bootedMsg := []byte(initStartMsg)
	timeout := time.NewTimer(time.Minute)
	defer timeout.Stop()
	var output []byte
	for {
		select {
		case out := <-inst.merger.Output:
			output = append(output, out...)
			if pos := bytes.Index(output, errorMsg); pos != -1 {
				end := bytes.IndexByte(output[pos:], '\n')
				if end == -1 {
					end = len(output)
				} else {
					end += pos
				}
				return vmimpl.BootError{
					Title:  string(output[pos:end]),
					Output: output,
				}
			}
			if bytes.Contains(output, bootedMsg) {
				return nil
			}
		case err := <-inst.merger.Err:
			return vmimpl.BootError{
				Title:  fmt.Sprintf("runsc failed: %v", err),
				Output: output,
			}
		case <-timeout.C:
			return vmimpl.BootError{
				Title:  "init process did not start",
				Output: output,
			}
		}
	}
}

func (inst *instance) runscCmd(add ...string) *exec.Cmd {
	args := []string{
		"-root", inst.rootDir,
		"-watchdog-action=panic",
		"-network=none",
		"-debug",
		"-alsologtostderr",
	}
	if inst.cfg.RunscArgs != "" {
		args = append(args, strings.Split(inst.cfg.RunscArgs, " ")...)
	}
	args = append(args, add...)
	cmd := osutil.Command(inst.image, args...)
	cmd.Env = []string{
		"GOTRACEBACK=all",
		"GORACE=halt_on_error=1",
	}
	return cmd
}

func (inst *instance) Close() {
	time.Sleep(3 * time.Second)
	osutil.Run(time.Minute, inst.runscCmd("delete", "-force", inst.name))
	inst.cmd.Process.Kill()
	inst.merger.Wait()
	inst.cmd.Wait()
	osutil.Run(time.Minute, inst.runscCmd("delete", "-force", inst.name))
	time.Sleep(3 * time.Second)
}

func (inst *instance) Forward(port int) (string, error) {
	if inst.port != 0 {
		return "", fmt.Errorf("forward port is already setup")
	}
	inst.port = port
	return "stdin", nil
}

func (inst *instance) Copy(hostSrc string) (string, error) {
	fname := filepath.Base(hostSrc)
	if err := osutil.CopyFile(hostSrc, filepath.Join(inst.imageDir, fname)); err != nil {
		return "", err
	}
	if err := os.Chmod(inst.imageDir, 0777); err != nil {
		return "", err
	}
	return filepath.Join("/", fname), nil
}

func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) (
	<-chan []byte, <-chan error, error) {
	args := []string{"exec", "-user=0:0"}
	for _, c := range sandboxCaps {
		args = append(args, "-cap", c)
	}
	args = append(args, inst.name)
	args = append(args, strings.Split(command, " ")...)
	cmd := inst.runscCmd(args...)

	rpipe, wpipe, err := osutil.LongPipe()
	if err != nil {
		return nil, nil, err
	}
	defer wpipe.Close()
	inst.merger.Add("cmd", rpipe)
	cmd.Stdout = wpipe
	cmd.Stderr = wpipe

	guestSock, err := inst.guestProxy()
	if err != nil {
		return nil, nil, err
	}
	if guestSock != nil {
		defer guestSock.Close()
		cmd.Stdin = guestSock
	}

	if err := cmd.Start(); err != nil {
		return nil, nil, err
	}
	errc := make(chan error, 1)
	signal := func(err error) {
		select {
		case errc <- err:
		default:
		}
	}

	go func() {
		select {
		case <-time.After(timeout):
			signal(vmimpl.ErrTimeout)
		case <-stop:
			signal(vmimpl.ErrTimeout)
		case err := <-inst.merger.Err:
			cmd.Process.Kill()
			if cmdErr := cmd.Wait(); cmdErr == nil {
				// If the command exited successfully, we got EOF error from merger.
				// But in this case no error has happened and the EOF is expected.
				err = nil
			}
			signal(err)
			return
		}
		cmd.Process.Kill()
		cmd.Wait()
	}()
	return inst.merger.Output, errc, nil
}

func (inst *instance) guestProxy() (*os.File, error) {
	if inst.port == 0 {
		return nil, nil
	}
	// One does not simply let gvisor guest connect to host tcp port.
	// We create a unix socket, pass it to guest in stdin.
	// Guest will use it instead of dialing manager directly.
	// On host we connect to manager tcp port and proxy between the tcp and unix connections.
	socks, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
	if err != nil {
		return nil, err
	}
	hostSock := os.NewFile(uintptr(socks[0]), "host unix proxy")
	guestSock := os.NewFile(uintptr(socks[1]), "guest unix proxy")
	conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", inst.port))
	if err != nil {
		hostSock.Close()
		guestSock.Close()
		return nil, err
	}
	go func() {
		io.Copy(hostSock, conn)
		hostSock.Close()
	}()
	go func() {
		io.Copy(conn, hostSock)
		conn.Close()
	}()
	return guestSock, nil
}

func (inst *instance) Diagnose() ([]byte, bool) {
	b, err := osutil.Run(time.Minute, inst.runscCmd("debug", "-stacks", inst.name))
	if err != nil {
		b = append(b, []byte(fmt.Sprintf("\n\nError collecting stacks: %v", err))...)
	}
	return b, false
}

func init() {
	if os.Getenv("SYZ_GVISOR_PROXY") != "" {
		fmt.Fprint(os.Stderr, initStartMsg)
		select {}
	}
}

const initStartMsg = "SYZKALLER INIT STARTED\n"

const configTempl = `
{
	"root": {
		"path": "%[1]v",
		"readonly": true
	},
	"process":{
                "args": ["/init"],
                "cwd": "/tmp",
                "env": ["SYZ_GVISOR_PROXY=1"],
                "capabilities": {
                	"bounding": [%[2]v],
                	"effective": [%[2]v],
                	"inheritable": [%[2]v],
                	"permitted": [%[2]v],
                	"ambient": [%[2]v]
                }
	}
}
`

var sandboxCaps = []string{
	"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_DAC_READ_SEARCH", "CAP_FOWNER", "CAP_FSETID",
	"CAP_KILL", "CAP_SETGID", "CAP_SETUID", "CAP_SETPCAP", "CAP_LINUX_IMMUTABLE",
	"CAP_NET_BIND_SERVICE", "CAP_NET_BROADCAST", "CAP_NET_ADMIN", "CAP_NET_RAW",
	"CAP_IPC_LOCK", "CAP_IPC_OWNER", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_CHROOT",
	"CAP_SYS_PTRACE", "CAP_SYS_PACCT", "CAP_SYS_ADMIN", "CAP_SYS_BOOT", "CAP_SYS_NICE",
	"CAP_SYS_RESOURCE", "CAP_SYS_TIME", "CAP_SYS_TTY_CONFIG", "CAP_MKNOD", "CAP_LEASE",
	"CAP_AUDIT_WRITE", "CAP_AUDIT_CONTROL", "CAP_SETFCAP", "CAP_MAC_OVERRIDE", "CAP_MAC_ADMIN",
	"CAP_SYSLOG", "CAP_WAKE_ALARM", "CAP_BLOCK_SUSPEND", "CAP_AUDIT_READ",
}
<commit_msg>vm/gvisor: show a list of processes in case of any failures<commit_after>// Copyright 2018 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

// Package gvisor provides support for gVisor, user-space kernel, testing.
// See https://github.com/google/gvisor
package gvisor

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/google/syzkaller/pkg/config"
	"github.com/google/syzkaller/pkg/log"
	"github.com/google/syzkaller/pkg/osutil"
	"github.com/google/syzkaller/vm/vmimpl"
)

func init() {
	vmimpl.Register("gvisor", ctor, true)
}

type Config struct {
	Count     int    `json:"count"` // number of VMs to use
	RunscArgs string `json:"runsc_args"`
}

type Pool struct {
	env *vmimpl.Env
	cfg *Config
}

type instance struct {
	cfg      *Config
	image    string
	debug    bool
	rootDir  string
	imageDir string
	name     string
	port     int
	cmd      *exec.Cmd
	merger   *vmimpl.OutputMerger
}

func ctor(env *vmimpl.Env) (vmimpl.Pool, error) {
	cfg := &Config{
		Count: 1,
	}
	if err := config.LoadData(env.Config, cfg); err != nil {
		return nil, fmt.Errorf("failed to parse vm config: %v", err)
	}
	if cfg.Count < 1 || cfg.Count > 128 {
		return nil, fmt.Errorf("invalid config param count: %v, want [1, 128]", cfg.Count)
	}
	if env.Debug && cfg.Count > 1 {
		log.Logf(0, "limiting number of VMs from %v to 1 in debug mode", cfg.Count)
		cfg.Count = 1
	}
	if !osutil.IsExist(env.Image) {
		return nil, fmt.Errorf("image file %q does not exist", env.Image)
	}
	pool := &Pool{
		cfg: cfg,
		env: env,
	}
	return pool, nil
}

func (pool *Pool) Count() int {
	return pool.cfg.Count
}

func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
	rootDir := filepath.Clean(filepath.Join(workdir, "..", "gvisor_root"))
	imageDir := filepath.Join(workdir, "image")
	bundleDir := filepath.Join(workdir, "bundle")
	osutil.MkdirAll(rootDir)
	osutil.MkdirAll(bundleDir)
	osutil.MkdirAll(imageDir)

	caps := ""
	for _, c := range sandboxCaps {
		if caps != "" {
			caps += ", "
		}
		caps += "\"" + c + "\""
	}
	vmConfig := fmt.Sprintf(configTempl, imageDir, caps)
	if err := osutil.WriteFile(filepath.Join(bundleDir, "config.json"), []byte(vmConfig)); err != nil {
		return nil, err
	}
	bin, err := exec.LookPath(os.Args[0])
	if err != nil {
		return nil, fmt.Errorf("failed to lookup %v: %v", os.Args[0], err)
	}
	if err := osutil.CopyFile(bin, filepath.Join(imageDir, "init")); err != nil {
		return nil, err
	}

	rpipe, wpipe, err := osutil.LongPipe()
	if err != nil {
		return nil, err
	}
	var tee io.Writer
	if pool.env.Debug {
		tee = os.Stdout
	}
	merger := vmimpl.NewOutputMerger(tee)
	merger.Add("gvisor", rpipe)

	inst := &instance{
		cfg:      pool.cfg,
		image:    pool.env.Image,
		debug:    pool.env.Debug,
		rootDir:  rootDir,
		imageDir: imageDir,
		name:     fmt.Sprintf("%v-%v", pool.env.Name, index),
		merger:   merger,
	}

	// Kill the previous instance in case it's still running.
	osutil.Run(time.Minute, inst.runscCmd("delete", "-force", inst.name))
	time.Sleep(3 * time.Second)

	cmd := inst.runscCmd("run", "-bundle", bundleDir, inst.name)
	cmd.Stdout = wpipe
	cmd.Stderr = wpipe
	if err := cmd.Start(); err != nil {
		wpipe.Close()
		merger.Wait()
		return nil, err
	}
	inst.cmd = cmd
	wpipe.Close()

	if err := inst.waitBoot(); err != nil {
		inst.Close()
		return nil, err
	}
	return inst, nil
}

func (inst *instance) waitBoot() error {
	errorMsg := []byte("FATAL ERROR:")
	bootedMsg := []byte(initStartMsg)
	timeout := time.NewTimer(time.Minute)
	defer timeout.Stop()
	var output []byte
	for {
		select {
		case out := <-inst.merger.Output:
			output = append(output, out...)
			if pos := bytes.Index(output, errorMsg); pos != -1 {
				end := bytes.IndexByte(output[pos:], '\n')
				if end == -1 {
					end = len(output)
				} else {
					end += pos
				}
				return vmimpl.BootError{
					Title:  string(output[pos:end]),
					Output: output,
				}
			}
			if bytes.Contains(output, bootedMsg) {
				return nil
			}
		case err := <-inst.merger.Err:
			return vmimpl.BootError{
				Title:  fmt.Sprintf("runsc failed: %v", err),
				Output: output,
			}
		case <-timeout.C:
			return vmimpl.BootError{
				Title:  "init process did not start",
				Output: output,
			}
		}
	}
}

func (inst *instance) runscCmd(add ...string) *exec.Cmd {
	args := []string{
		"-root", inst.rootDir,
		"-watchdog-action=panic",
		"-network=none",
		"-debug",
		"-alsologtostderr",
	}
	if inst.cfg.RunscArgs != "" {
		args = append(args, strings.Split(inst.cfg.RunscArgs, " ")...)
	}
	args = append(args, add...)
	cmd := osutil.Command(inst.image, args...)
	cmd.Env = []string{
		"GOTRACEBACK=all",
		"GORACE=halt_on_error=1",
	}
	return cmd
}

func (inst *instance) Close() {
	time.Sleep(3 * time.Second)
	osutil.Run(time.Minute, inst.runscCmd("delete", "-force", inst.name))
	inst.cmd.Process.Kill()
	inst.merger.Wait()
	inst.cmd.Wait()
	osutil.Run(time.Minute, inst.runscCmd("delete", "-force", inst.name))
	time.Sleep(3 * time.Second)
}

func (inst *instance) Forward(port int) (string, error) {
	if inst.port != 0 {
		return "", fmt.Errorf("forward port is already setup")
	}
	inst.port = port
	return "stdin", nil
}

func (inst *instance) Copy(hostSrc string) (string, error) {
	fname := filepath.Base(hostSrc)
	if err := osutil.CopyFile(hostSrc, filepath.Join(inst.imageDir, fname)); err != nil {
		return "", err
	}
	if err := os.Chmod(inst.imageDir, 0777); err != nil {
		return "", err
	}
	return filepath.Join("/", fname), nil
}

func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) (
	<-chan []byte, <-chan error, error) {
	args := []string{"exec", "-user=0:0"}
	for _, c := range sandboxCaps {
		args = append(args, "-cap", c)
	}
	args = append(args, inst.name)
	args = append(args, strings.Split(command, " ")...)
	cmd := inst.runscCmd(args...)

	rpipe, wpipe, err := osutil.LongPipe()
	if err != nil {
		return nil, nil, err
	}
	defer wpipe.Close()
	inst.merger.Add("cmd", rpipe)
	cmd.Stdout = wpipe
	cmd.Stderr = wpipe

	guestSock, err := inst.guestProxy()
	if err != nil {
		return nil, nil, err
	}
	if guestSock != nil {
		defer guestSock.Close()
		cmd.Stdin = guestSock
	}

	if err := cmd.Start(); err != nil {
		return nil, nil, err
	}
	errc := make(chan error, 1)
	signal := func(err error) {
		select {
		case errc <- err:
		default:
		}
	}

	go func() {
		select {
		case <-time.After(timeout):
			signal(vmimpl.ErrTimeout)
		case <-stop:
			signal(vmimpl.ErrTimeout)
		case err := <-inst.merger.Err:
			cmd.Process.Kill()
			if cmdErr := cmd.Wait(); cmdErr == nil {
				// If the command exited successfully, we got EOF error from merger.
				// But in this case no error has happened and the EOF is expected.
				err = nil
			}
			signal(err)
			return
		}
		cmd.Process.Kill()
		cmd.Wait()
	}()
	return inst.merger.Output, errc, nil
}

func (inst *instance) guestProxy() (*os.File, error) {
	if inst.port == 0 {
		return nil, nil
	}
	// One does not simply let gvisor guest connect to host tcp port.
	// We create a unix socket, pass it to guest in stdin.
	// Guest will use it instead of dialing manager directly.
	// On host we connect to manager tcp port and proxy between the tcp and unix connections.
	socks, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
	if err != nil {
		return nil, err
	}
	hostSock := os.NewFile(uintptr(socks[0]), "host unix proxy")
	guestSock := os.NewFile(uintptr(socks[1]), "guest unix proxy")
	conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", inst.port))
	if err != nil {
		hostSock.Close()
		guestSock.Close()
		return nil, err
	}
	go func() {
		io.Copy(hostSock, conn)
		hostSock.Close()
	}()
	go func() {
		io.Copy(conn, hostSock)
		conn.Close()
	}()
	return guestSock, nil
}

func (inst *instance) Diagnose() ([]byte, bool) {
	b, err := osutil.Run(time.Minute, inst.runscCmd("debug", "-stacks", "--ps", inst.name))
	if err != nil {
		b = append(b, []byte(fmt.Sprintf("\n\nError collecting stacks: %v", err))...)
	}
	return b, false
}

func init() {
	if os.Getenv("SYZ_GVISOR_PROXY") != "" {
		fmt.Fprint(os.Stderr, initStartMsg)
		select {}
	}
}

const initStartMsg = "SYZKALLER INIT STARTED\n"

const configTempl = `
{
	"root": {
		"path": "%[1]v",
		"readonly": true
	},
	"process":{
                "args": ["/init"],
                "cwd": "/tmp",
                "env": ["SYZ_GVISOR_PROXY=1"],
                "capabilities": {
                	"bounding": [%[2]v],
                	"effective": [%[2]v],
                	"inheritable": [%[2]v],
                	"permitted": [%[2]v],
                	"ambient": [%[2]v]
                }
	}
}
`

var sandboxCaps = []string{
	"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_DAC_READ_SEARCH", "CAP_FOWNER", "CAP_FSETID",
	"CAP_KILL", "CAP_SETGID", "CAP_SETUID", "CAP_SETPCAP", "CAP_LINUX_IMMUTABLE",
	"CAP_NET_BIND_SERVICE", "CAP_NET_BROADCAST", "CAP_NET_ADMIN", "CAP_NET_RAW",
	"CAP_IPC_LOCK", "CAP_IPC_OWNER", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_CHROOT",
	"CAP_SYS_PTRACE", "CAP_SYS_PACCT", "CAP_SYS_ADMIN", "CAP_SYS_BOOT", "CAP_SYS_NICE",
	"CAP_SYS_RESOURCE", "CAP_SYS_TIME", "CAP_SYS_TTY_CONFIG", "CAP_MKNOD", "CAP_LEASE",
	"CAP_AUDIT_WRITE", "CAP_AUDIT_CONTROL", "CAP_SETFCAP", "CAP_MAC_OVERRIDE", "CAP_MAC_ADMIN",
	"CAP_SYSLOG", "CAP_WAKE_ALARM", "CAP_BLOCK_SUSPEND", "CAP_AUDIT_READ",
}
<|endoftext|>