<commit_before>package docker

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"code.google.com/p/go.net/context"
	"github.com/Sirupsen/logrus"
	"github.com/facebookgo/stackerr"
	dockerclient "github.com/fsouza/go-dockerclient"
)

type ContainerConfig dockerclient.Config

func (c *ContainerConfig) String() string {
	return fmt.Sprintf("%s: %s", c.Image, strings.Join(c.Cmd, " "))
}

type ContainerResponse struct {
	Err error
	Log []byte
}

type Docker struct {
	Client *dockerclient.Client
}

func NewDocker() (*Docker, error) {
	client, err := GetDockerClient()
	if err != nil {
		return nil, err
	}
	return &Docker{
		Client: client,
	}, nil
}

// Ping the docker server
//
// See http://goo.gl/stJENm for more details.
func (d *Docker) Ping() error {
	return d.Client.Ping()
}

//func (d *Docker) PullImages(images []string) error {
//	for _, image := range images {
//		logrus.Debugf("Pull image %s", image)
//		if err := d.Client.PullImage(image, nil); err != nil {
//			return err
//		}
//		d.log.Print("successful \n")
//	}
//	return nil
//}

func (d *Docker) RunImage(ctx context.Context, config *ContainerConfig) <-chan ContainerResponse {
	ch := make(chan ContainerResponse, 1)
	go func(ch chan<- ContainerResponse, docker *dockerclient.Client, config *ContainerConfig) {
		resp := ContainerResponse{}
		// Create a container
		logrus.Infof("Create container %s", config)
		cfg := dockerclient.Config(*config)
		opts := dockerclient.CreateContainerOptions{
			Config: &cfg,
		}
		container, err := docker.CreateContainer(opts)
		if err != nil {
			logrus.Errorf("Failed: %v", err)
			resp.Err = err
			ch <- resp
			return
		}
		cprint := func(format string, opt ...interface{}) {
			logrus.Infof("[%s] %s", container.ID[:6], fmt.Sprintf(format, opt...))
		}
		cprint("Created with config: %s", config)

		defer func() {
			docker.RemoveContainer(dockerclient.RemoveContainerOptions{
				ID: container.ID,
			})
			cprint("Removed")
		}()

		// Start the container
		err = docker.StartContainer(container.ID, opts.HostConfig)
		if err != nil {
			resp.Err = stackerr.Wrap(err)
			ch <- resp
			return
		}
		cprint("Started")
		defer func() {
			docker.StopContainer(container.ID, 5)
			cprint("Stopped")
		}()
		tmpCh := make(chan ContainerResponse, 1)
		go func(tmpCh chan<- ContainerResponse) {
			resp := ContainerResponse{}
			cprint("Waiting for log")
			stdout := bytes.NewBuffer(nil)
			stderr := bytes.NewBuffer(nil)
			err := docker.Logs(dockerclient.LogsOptions{
				OutputStream: stdout,
				ErrorStream:  stderr,
				Container:    container.ID,
				Follow:       true,
				Stdout:       true,
				Stderr:       true,
				RawTerminal:  true,
			})
			if err != nil {
				resp.Err = stackerr.Wrap(err)
				tmpCh <- resp
				return
			}
			serr := stderr.Bytes()
			sout := stdout.Bytes()
			if serr != nil {
				cprint("STDERR: %s", string(serr))
			}
			if sout != nil {
				cprint("STDOUT: %s", string(sout))
			}
			resp.Log = sout
			tmpCh <- resp
		}(tmpCh)
		select {
		case <-ctx.Done():
			cprint("Context done")
			resp.Err = ctx.Err()
		case tmpResp := <-tmpCh:
			resp.Err = tmpResp.Err
			resp.Log = tmpResp.Log
		}
		ch <- resp
		return

	}(ch, d.Client, config)
	return ch
}

func GetDockerClient() (*dockerclient.Client, error) {
	var (
		client *dockerclient.Client
		err    error
	)
	endpoint := os.Getenv("DOCKER_HOST")
	if os.Getenv("DOCKER_TLS_VERIFY") == "1" {
		certPath := os.Getenv("DOCKER_CERT_PATH")
		cert := filepath.Join(certPath, "cert.pem")
		key := filepath.Join(certPath, "key.pem")
		ca := filepath.Join(certPath, "ca.pem")
		client, err = dockerclient.NewTLSClient(endpoint, cert, key, ca)
	} else {
		client, err = dockerclient.NewClient(endpoint)
	}

	if err != nil {
		return nil, err
	}

	return client, nil
}
<commit_msg>pull images before running container fix #10<commit_after>package docker

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"code.google.com/p/go.net/context"
	"github.com/Sirupsen/logrus"
	"github.com/facebookgo/stackerr"
	dockerclient "github.com/fsouza/go-dockerclient"
)

type ContainerConfig dockerclient.Config

func (c *ContainerConfig) String() string {
	return fmt.Sprintf("%s: %s", c.Image, strings.Join(c.Cmd, " "))
}

type ContainerResponse struct {
	Err error
	Log []byte
}

type Docker struct {
	Client *dockerclient.Client
}

func NewDocker() (*Docker, error) {
	client, err := GetDockerClient()
	if err != nil {
		return nil, err
	}
	return &Docker{
		Client: client,
	}, nil
}

// Ping the docker server
//
// See http://goo.gl/stJENm for more details.
func (d *Docker) Ping() error {
	return d.Client.Ping()
}

//func (d *Docker) PullImages(images []string) error {
//	for _, image := range images {
//		logrus.Debugf("Pull image %s", image)
//		if err := d.Client.PullImage(image, nil); err != nil {
//			return err
//		}
//		d.log.Print("successful \n")
//	}
//	return nil
//}

func (d *Docker) PullImage(name string) error {
	_, err := d.Client.InspectImage(name)
	if err == dockerclient.ErrNoSuchImage {
		logrus.Infof("pull image: %s", name)
		err = d.Client.PullImage(dockerclient.PullImageOptions{
			Repository: name,
			OutputStream: os.Stdout,
		}, dockerclient.AuthConfiguration{})
	}
	return err
}

func (d *Docker) RunImage(ctx context.Context, config *ContainerConfig) <-chan ContainerResponse {
	ch := make(chan ContainerResponse, 1)
	go func(ch chan<- ContainerResponse, docker *dockerclient.Client, config *ContainerConfig) {
		resp := ContainerResponse{}
		// pull container
		if err := d.PullImage(config.Image); err != nil {
			logrus.Errorf("Failed: %v", err)
			resp.Err = err
			ch <- resp
			return
		}
		// Create a container
		logrus.Infof("Create container %s", config)
		cfg := dockerclient.Config(*config)
		opts := dockerclient.CreateContainerOptions{
			Config: &cfg,
		}
		container, err := docker.CreateContainer(opts)
		if err != nil {
			logrus.Errorf("Failed: %v", err)
			resp.Err = err
			ch <- resp
			return
		}
		cprint := func(format string, opt ...interface{}) {
			logrus.Infof("[%s] %s", container.ID[:6], fmt.Sprintf(format, opt...))
		}
		cprint("Created with config: %s", config)

		defer func() {
			docker.RemoveContainer(dockerclient.RemoveContainerOptions{
				ID: container.ID,
			})
			cprint("Removed")
		}()

		// Start the container
		err = docker.StartContainer(container.ID, opts.HostConfig)
		if err != nil {
			resp.Err = stackerr.Wrap(err)
			ch <- resp
			return
		}
		cprint("Started")
		defer func() {
			docker.StopContainer(container.ID, 5)
			cprint("Stopped")
		}()
		tmpCh := make(chan ContainerResponse, 1)
		go func(tmpCh chan<- ContainerResponse) {
			resp := ContainerResponse{}
			cprint("Waiting for log")
			stdout := bytes.NewBuffer(nil)
			stderr := bytes.NewBuffer(nil)
			err := docker.Logs(dockerclient.LogsOptions{
				OutputStream: stdout,
				ErrorStream:  stderr,
				Container:    container.ID,
				Follow:       true,
				Stdout:       true,
				Stderr:       true,
				RawTerminal:  true,
			})
			if err != nil {
				resp.Err = stackerr.Wrap(err)
				tmpCh <- resp
				return
			}
			serr := stderr.Bytes()
			sout := stdout.Bytes()
			if serr != nil {
				cprint("STDERR: %s", string(serr))
			}
			if sout != nil {
				cprint("STDOUT: %s", string(sout))
			}
			resp.Log = sout
			tmpCh <- resp
		}(tmpCh)
		select {
		case <-ctx.Done():
			cprint("Context done")
			resp.Err = ctx.Err()
		case tmpResp := <-tmpCh:
			resp.Err = tmpResp.Err
			resp.Log = tmpResp.Log
		}
		ch <- resp
		return

	}(ch, d.Client, config)
	return ch
}

func GetDockerClient() (*dockerclient.Client, error) {
	var (
		client *dockerclient.Client
		err    error
	)
	endpoint := os.Getenv("DOCKER_HOST")
	if os.Getenv("DOCKER_TLS_VERIFY") == "1" {
		certPath := os.Getenv("DOCKER_CERT_PATH")
		cert := filepath.Join(certPath, "cert.pem")
		key := filepath.Join(certPath, "key.pem")
		ca := filepath.Join(certPath, "ca.pem")
		client, err = dockerclient.NewTLSClient(endpoint, cert, key, ca)
	} else {
		client, err = dockerclient.NewClient(endpoint)
	}

	if err != nil {
		return nil, err
	}

	return client, nil
}
<|endoftext|>


<commit_before>// Copyright 2016-2019 Authors of Cilium
//
// 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 policy

import (
	"fmt"
	"io"
	"strconv"
	"strings"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/policy/api"

	"github.com/op/go-logging"
)

type Tracing int

const (
	TRACE_DISABLED Tracing = iota
	TRACE_ENABLED
	TRACE_VERBOSE
)

// PolicyTrace logs the given message into the SearchContext logger only if
// TRACE_ENABLED or TRACE_VERBOSE is enabled in the receiver's SearchContext.
func (s *SearchContext) PolicyTrace(format string, a ...interface{}) {
	switch s.Trace {
	case TRACE_ENABLED, TRACE_VERBOSE:
		log.Debugf(format, a...)
		if s.Logging != nil {
			format = "%-" + s.CallDepth() + "s" + format
			a = append([]interface{}{""}, a...)
			s.Logging.Logger.Printf(format, a...)
		}
	}
}

// PolicyTraceVerbose logs the given message into the SearchContext logger only
// if TRACE_VERBOSE is enabled in the receiver's SearchContext.
func (s *SearchContext) PolicyTraceVerbose(format string, a ...interface{}) {
	switch s.Trace {
	case TRACE_VERBOSE:
		log.Debugf(format, a...)
		if s.Logging != nil {
			s.Logging.Logger.Printf(format, a...)
		}
	}
}

// SearchContext defines the context while evaluating policy
type SearchContext struct {
	Trace   Tracing
	Depth   int
	Logging *logging.LogBackend
	From    labels.LabelArray
	To      labels.LabelArray
	DPorts  []*models.Port
	// rulesSelect specifies whether or not to check whether a rule which is
	// being analyzed using this SearchContext matches either From or To.
	// This is used to avoid using EndpointSelector.Matches() if possible,
	// since it is costly in terms of performance.
	rulesSelect bool
	// skipL4RequirementsAggregation allows for skipping of aggregation of
	// requirements in L4 policy parsing, as it is expensive. This is used
	// when the policy is being calculated for an endpoint (vs. a trace),
	// and the set of denied identities can be consulted for when the PolicyMap
	// state is computed for an endpoint.
	skipL4RequirementsAggregation bool
}

func (s *SearchContext) String() string {
	from := []string{}
	to := []string{}
	dports := []string{}
	for _, fromLabel := range s.From {
		from = append(from, fromLabel.String())
	}
	for _, toLabel := range s.To {
		to = append(to, toLabel.String())
	}
	for _, dport := range s.DPorts {
		dports = append(dports, fmt.Sprintf("%d/%s", dport.Port, dport.Protocol))
	}
	ret := fmt.Sprintf("From: [%s]", strings.Join(from, ", "))
	ret += fmt.Sprintf(" => To: [%s]", strings.Join(to, ", "))
	if len(dports) != 0 {
		ret += fmt.Sprintf(" Ports: [%s]", strings.Join(dports, ", "))
	}
	return ret
}

func (s *SearchContext) CallDepth() string {
	return strconv.Itoa(s.Depth * 2)
}

// WithLogger returns a shallow copy of the received SearchContext with the
// logging set to write to 'log'.
func (s *SearchContext) WithLogger(log io.Writer) *SearchContext {
	result := *s
	result.Logging = logging.NewLogBackend(log, "", 0)
	if result.Trace == TRACE_DISABLED {
		result.Trace = TRACE_ENABLED
	}
	return &result
}

// Translator is an interface for altering policy rules
type Translator interface {
	Translate(*api.Rule, *TranslationResult) error
}
<commit_msg>policy: Add SearchContext.TraceEnabled()<commit_after>// Copyright 2016-2019 Authors of Cilium
//
// 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 policy

import (
	"fmt"
	"io"
	"strconv"
	"strings"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/policy/api"

	"github.com/op/go-logging"
)

type Tracing int

const (
	TRACE_DISABLED Tracing = iota
	TRACE_ENABLED
	TRACE_VERBOSE
)

// TraceEnabled returns true if the SearchContext requests tracing.
func (s *SearchContext) TraceEnabled() bool {
	return s.Trace != TRACE_DISABLED
}

// PolicyTrace logs the given message into the SearchContext logger only if
// TRACE_ENABLED or TRACE_VERBOSE is enabled in the receiver's SearchContext.
func (s *SearchContext) PolicyTrace(format string, a ...interface{}) {
	if s.TraceEnabled() {
		log.Debugf(format, a...)
		if s.Logging != nil {
			format = "%-" + s.CallDepth() + "s" + format
			a = append([]interface{}{""}, a...)
			s.Logging.Logger.Printf(format, a...)
		}
	}
}

// PolicyTraceVerbose logs the given message into the SearchContext logger only
// if TRACE_VERBOSE is enabled in the receiver's SearchContext.
func (s *SearchContext) PolicyTraceVerbose(format string, a ...interface{}) {
	switch s.Trace {
	case TRACE_VERBOSE:
		log.Debugf(format, a...)
		if s.Logging != nil {
			s.Logging.Logger.Printf(format, a...)
		}
	}
}

// SearchContext defines the context while evaluating policy
type SearchContext struct {
	Trace   Tracing
	Depth   int
	Logging *logging.LogBackend
	From    labels.LabelArray
	To      labels.LabelArray
	DPorts  []*models.Port
	// rulesSelect specifies whether or not to check whether a rule which is
	// being analyzed using this SearchContext matches either From or To.
	// This is used to avoid using EndpointSelector.Matches() if possible,
	// since it is costly in terms of performance.
	rulesSelect bool
	// skipL4RequirementsAggregation allows for skipping of aggregation of
	// requirements in L4 policy parsing, as it is expensive. This is used
	// when the policy is being calculated for an endpoint (vs. a trace),
	// and the set of denied identities can be consulted for when the PolicyMap
	// state is computed for an endpoint.
	skipL4RequirementsAggregation bool
}

func (s *SearchContext) String() string {
	from := []string{}
	to := []string{}
	dports := []string{}
	for _, fromLabel := range s.From {
		from = append(from, fromLabel.String())
	}
	for _, toLabel := range s.To {
		to = append(to, toLabel.String())
	}
	for _, dport := range s.DPorts {
		dports = append(dports, fmt.Sprintf("%d/%s", dport.Port, dport.Protocol))
	}
	ret := fmt.Sprintf("From: [%s]", strings.Join(from, ", "))
	ret += fmt.Sprintf(" => To: [%s]", strings.Join(to, ", "))
	if len(dports) != 0 {
		ret += fmt.Sprintf(" Ports: [%s]", strings.Join(dports, ", "))
	}
	return ret
}

func (s *SearchContext) CallDepth() string {
	return strconv.Itoa(s.Depth * 2)
}

// WithLogger returns a shallow copy of the received SearchContext with the
// logging set to write to 'log'.
func (s *SearchContext) WithLogger(log io.Writer) *SearchContext {
	result := *s
	result.Logging = logging.NewLogBackend(log, "", 0)
	if result.Trace == TRACE_DISABLED {
		result.Trace = TRACE_ENABLED
	}
	return &result
}

// Translator is an interface for altering policy rules
type Translator interface {
	Translate(*api.Rule, *TranslationResult) error
}
<|endoftext|>


<commit_before>package fanouthttp

import (
	"context"
	"net/http"
	"time"

	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/middleware"
	"github.com/Comcast/webpa-common/xhttp"
	"github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/log"
)

const (
	DefaultMaxIdleConnsPerHost               = 20
	DefaultFanoutTimeout       time.Duration = 45 * time.Second
	DefaultClientTimeout       time.Duration = 30 * time.Second
	DefaultMaxClients          int64         = 10000
	DefaultConcurrency                       = 1000
)

// Options defines the common options useful for creating HTTP fanouts.
type Options struct {
	// Logger is the go-kit logger to use when creating the service fanout.  If not set, logging.DefaultLogger is used.
	Logger log.Logger `json:"-"`

	// Endpoints are the URLs for each endpoint to fan out to
	Endpoints []string `json:"endpoints,omitempty"`

	// Authorization is the Basic Auth token.  There is no default for this field.
	Authorization string `json:"authorization"`

	// Transport is the http.Client transport
	Transport http.Transport `json:"transport"`

	// FanoutTimeout is the timeout for the entire fanout operation.  If not supplied, DefaultFanoutTimeout is used.
	FanoutTimeout time.Duration `json:"timeout"`

	// ClientTimeout is the http.Client Timeout.  If not set, DefaultClientTimeout is used.
	ClientTimeout time.Duration `json:"clientTimeout"`

	// MaxClients is the maximum number of concurrent clients that can be using the fanout.  This should be set to
	// something larger than the Concurrency field.
	MaxClients int64 `json:"maxClients"`

	// Concurrency is the maximum number of concurrent fanouts allowed.  This is enforced via a Concurrent middleware.
	// If this is not set, DefaultConcurrency is used.
	Concurrency int `json:"concurrency"`
}

func (o *Options) logger() log.Logger {
	if o != nil && o.Logger != nil {
		return o.Logger
	}

	return logging.DefaultLogger()
}

func (o *Options) endpoints() []string {
	if o != nil {
		return o.Endpoints
	}

	return nil
}

func (o *Options) authorization() string {
	if o != nil && len(o.Authorization) > 0 {
		return o.Authorization
	}

	return ""
}

func (o *Options) fanoutTimeout() time.Duration {
	if o != nil && o.FanoutTimeout > 0 {
		return o.FanoutTimeout
	}

	return DefaultFanoutTimeout
}

func (o *Options) clientTimeout() time.Duration {
	if o != nil && o.ClientTimeout > 0 {
		return o.ClientTimeout
	}

	return DefaultClientTimeout
}

func (o *Options) transport() *http.Transport {
	transport := new(http.Transport)

	if o != nil {
		*transport = o.Transport
	}

	if transport.MaxIdleConnsPerHost < 1 {
		transport.MaxIdleConnsPerHost = DefaultMaxIdleConnsPerHost
	}

	return transport
}

func (o *Options) maxClients() int64 {
	if o != nil && o.MaxClients > 0 {
		return o.MaxClients
	}

	return DefaultMaxClients
}

func (o *Options) concurrency() int {
	if o != nil && o.Concurrency > 0 {
		return o.Concurrency
	}

	return DefaultConcurrency
}

// NewClient returns a distinct HTTP client synthesized from these options
func (o *Options) NewClient() *http.Client {
	return &http.Client{
		Transport: o.transport(),
		Timeout:   o.clientTimeout(),
	}
}

func (o *Options) loggerMiddleware(next endpoint.Endpoint) endpoint.Endpoint {
	logger := o.logger()
	return func(ctx context.Context, request interface{}) (interface{}, error) {
		ctx = logging.WithLogger(ctx, logger)
		return next(ctx, request)
	}
}

// FanoutMiddleware uses these options to produce a go-kit middleware decorator for the
// fanout endpoint.
func (o *Options) FanoutMiddleware() endpoint.Middleware {
	return endpoint.Chain(
		// logging is the outermost middleware, so everything downstream can log consistently
		o.loggerMiddleware,
		middleware.Busy(o.maxClients(), &xhttp.Error{Code: http.StatusTooManyRequests, Text: "Server Busy"}),
		middleware.Timeout(o.fanoutTimeout()),
		middleware.Concurrent(o.concurrency(), &xhttp.Error{Code: http.StatusServiceUnavailable, Text: "Server Busy"}),
	)
}
<commit_msg>Extract a redirect policy from the fanout configuration<commit_after>package fanouthttp

import (
	"context"
	"net/http"
	"time"

	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/middleware"
	"github.com/Comcast/webpa-common/xhttp"
	"github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/log"
)

const (
	DefaultMaxIdleConnsPerHost               = 20
	DefaultFanoutTimeout       time.Duration = 45 * time.Second
	DefaultClientTimeout       time.Duration = 30 * time.Second
	DefaultMaxClients          int64         = 10000
	DefaultConcurrency                       = 1000
)

// Options defines the common options useful for creating HTTP fanouts.
type Options struct {
	// Logger is the go-kit logger to use when creating the service fanout.  If not set, logging.DefaultLogger is used.
	Logger log.Logger `json:"-"`

	// Endpoints are the URLs for each endpoint to fan out to
	Endpoints []string `json:"endpoints,omitempty"`

	// Authorization is the Basic Auth token.  There is no default for this field.
	Authorization string `json:"authorization"`

	// Transport is the http.Client transport
	Transport http.Transport `json:"transport"`

	// FanoutTimeout is the timeout for the entire fanout operation.  If not supplied, DefaultFanoutTimeout is used.
	FanoutTimeout time.Duration `json:"timeout"`

	// ClientTimeout is the http.Client Timeout.  If not set, DefaultClientTimeout is used.
	ClientTimeout time.Duration `json:"clientTimeout"`

	// MaxClients is the maximum number of concurrent clients that can be using the fanout.  This should be set to
	// something larger than the Concurrency field.
	MaxClients int64 `json:"maxClients"`

	// Concurrency is the maximum number of concurrent fanouts allowed.  This is enforced via a Concurrent middleware.
	// If this is not set, DefaultConcurrency is used.
	Concurrency int `json:"concurrency"`

	// MaxRedirects defines the maximum number of redirects each fanout will allow
	MaxRedirects int `json:"maxRedirects"`

	// RedirectExcludeHeaders are the headers that will *not* be copied on a redirect
	RedirectExcludeHeaders []string `json:"redirectExcludeHeaders,omitempty"`
}

func (o *Options) logger() log.Logger {
	if o != nil && o.Logger != nil {
		return o.Logger
	}

	return logging.DefaultLogger()
}

func (o *Options) endpoints() []string {
	if o != nil {
		return o.Endpoints
	}

	return nil
}

func (o *Options) authorization() string {
	if o != nil && len(o.Authorization) > 0 {
		return o.Authorization
	}

	return ""
}

func (o *Options) fanoutTimeout() time.Duration {
	if o != nil && o.FanoutTimeout > 0 {
		return o.FanoutTimeout
	}

	return DefaultFanoutTimeout
}

func (o *Options) clientTimeout() time.Duration {
	if o != nil && o.ClientTimeout > 0 {
		return o.ClientTimeout
	}

	return DefaultClientTimeout
}

func (o *Options) transport() *http.Transport {
	transport := new(http.Transport)

	if o != nil {
		*transport = o.Transport
	}

	if transport.MaxIdleConnsPerHost < 1 {
		transport.MaxIdleConnsPerHost = DefaultMaxIdleConnsPerHost
	}

	return transport
}

func (o *Options) maxClients() int64 {
	if o != nil && o.MaxClients > 0 {
		return o.MaxClients
	}

	return DefaultMaxClients
}

func (o *Options) concurrency() int {
	if o != nil && o.Concurrency > 0 {
		return o.Concurrency
	}

	return DefaultConcurrency
}

func (o *Options) maxRedirects() int {
	if o != nil {
		return o.MaxRedirects
	}

	return 0
}

func (o *Options) redirectExcludeHeaders() []string {
	if o != nil {
		return o.RedirectExcludeHeaders
	}

	return nil
}

func (o *Options) checkRedirect() func(*http.Request, []*http.Request) error {
	return xhttp.CheckRedirect(xhttp.RedirectPolicy{
		Logger:         o.logger(),
		MaxRedirects:   o.maxRedirects(),
		ExcludeHeaders: o.redirectExcludeHeaders(),
	})
}

// NewClient returns a distinct HTTP client synthesized from these options
func (o *Options) NewClient() *http.Client {
	return &http.Client{
		CheckRedirect: o.checkRedirect(),
		Transport:     o.transport(),
		Timeout:       o.clientTimeout(),
	}
}

func (o *Options) loggerMiddleware(next endpoint.Endpoint) endpoint.Endpoint {
	logger := o.logger()
	return func(ctx context.Context, request interface{}) (interface{}, error) {
		ctx = logging.WithLogger(ctx, logger)
		return next(ctx, request)
	}
}

// FanoutMiddleware uses these options to produce a go-kit middleware decorator for the
// fanout endpoint.
func (o *Options) FanoutMiddleware() endpoint.Middleware {
	return endpoint.Chain(
		// logging is the outermost middleware, so everything downstream can log consistently
		o.loggerMiddleware,
		middleware.Busy(o.maxClients(), &xhttp.Error{Code: http.StatusTooManyRequests, Text: "Server Busy"}),
		middleware.Timeout(o.fanoutTimeout()),
		middleware.Concurrent(o.concurrency(), &xhttp.Error{Code: http.StatusServiceUnavailable, Text: "Server Busy"}),
	)
}
<|endoftext|>


<commit_before>// Package server implements the serving of the backend and the web UI.
package server

import (
	"fmt"
	"log"
	"time"

	"github.com/facette/facette/pkg/catalog"
	"github.com/facette/facette/pkg/config"
	"github.com/facette/facette/pkg/connector"
	"github.com/facette/facette/pkg/provider"
	"github.com/facette/facette/pkg/worker"
)

const (
	_ = iota
	eventInit
	eventRun
	eventCatalogRefresh
	eventShutdown

	_ = iota
	jobSignalRefresh
	jobSignalShutdown
)

func (server *Server) startProviderWorkers() error {
	server.providerWorkers = worker.NewWorkerPool()

	log.Println("DEBUG: declaring providers")

	for _, prov := range server.providers {
		connectorType, err := config.GetString(prov.Config.Connector, "type", true)
		if err != nil {
			return fmt.Errorf("provider `%s' connector: %s", prov.Name, err)
		} else if _, ok := connector.Connectors[connectorType]; !ok {
			return fmt.Errorf("provider `%s' uses unknown connector type `%s'", prov.Name, connectorType)
		}

		providerWorker := worker.NewWorker()
		providerWorker.RegisterEvent(eventInit, workerProviderInit)
		providerWorker.RegisterEvent(eventShutdown, workerProviderShutdown)
		providerWorker.RegisterEvent(eventRun, workerProviderRun)
		providerWorker.RegisterEvent(eventCatalogRefresh, workerProviderRefresh)

		server.providerWorkers.Add(providerWorker)

		if err := providerWorker.SendEvent(eventInit, false, prov, connectorType); err != nil {
			log.Printf("ERROR: in provider `%s', %s", prov.Name, err.Error())
			log.Printf("WARNING: discarding provider `%s'", prov.Name)
			continue
		}

		providerWorker.SendEvent(eventRun, true, nil)

		log.Printf("DEBUG: declared provider `%s'", prov.Name)
	}

	return nil
}

func (server *Server) stopProviderWorkers() {
	server.providerWorkers.Broadcast(eventShutdown, nil)

	// Wait for all workers to shut down
	server.providerWorkers.Wg.Wait()
}

func workerProviderInit(w *worker.Worker, args ...interface{}) {
	var (
		prov          = args[0].(*provider.Provider)
		connectorType = args[1].(string)
	)

	log.Printf("DEBUG: providerWorker[%s]: init", prov.Name)

	// Instanciate the connector according to its type
	conn, err := connector.Connectors[connectorType](prov.Config.Connector)
	if err != nil {
		w.ReturnErr(err)
	}

	prov.Connector = conn.(connector.Connector)

	// Worker properties:
	// 0: provider instance (*provider.Provider)
	w.Props = append(w.Props, prov)

	w.ReturnErr(nil)
}

func workerProviderShutdown(w *worker.Worker, args ...interface{}) {
	var prov = w.Props[0].(*provider.Provider)

	log.Printf("DEBUG: providerWorker[%s]: shutdown", prov.Name)

	w.SendJobSignal(jobSignalShutdown)
}

func workerProviderRun(w *worker.Worker, args ...interface{}) {
	var (
		prov       = w.Props[0].(*provider.Provider)
		timeTicker *time.Ticker
		timeChan   <-chan time.Time
	)

	defer func() { w.State = worker.JobStopped }()
	defer w.Shutdown()

	log.Printf("DEBUG: providerWorker[%s]: starting", prov.Name)

	// If provider `refresh_interval` has been configured, set up a time ticker
	if prov.Config.RefreshInterval > 0 {
		timeTicker = time.NewTicker(time.Duration(prov.Config.RefreshInterval) * time.Second)
		timeChan = timeTicker.C
	}

	for {
		select {
		case _ = <-timeChan:
			if err := prov.Connector.Refresh(prov.Name, prov.Filters.Input); err != nil {
				log.Printf("ERROR: unable to refresh provider `%s': %s", prov.Name, err)
				continue
			}

			prov.LastRefresh = time.Now()

		case cmd := <-w.ReceiveJobSignals():
			switch cmd {
			case jobSignalRefresh:
				log.Printf("INFO: providerWorker[%s]: received refresh command", prov.Name)

				if err := prov.Connector.Refresh(prov.Name, prov.Filters.Input); err != nil {
					log.Printf("ERROR: unable to refresh provider `%s': %s", prov.Name, err)
					continue
				}

				prov.LastRefresh = time.Now()

			case jobSignalShutdown:
				log.Printf("INFO: providerWorker[%s]: received shutdown command, stopping job", prov.Name)

				w.State = worker.JobStopped

				if timeTicker != nil {
					// Stop refresh time ticker
					timeTicker.Stop()
				}

				return

			default:
				log.Println("NOTICE: providerWorker[%s]: received unknown command, ignoring", prov.Name)
			}
		}
	}
}

func workerProviderRefresh(w *worker.Worker, args ...interface{}) {
	var prov = w.Props[0].(*provider.Provider)

	log.Printf("DEBUG: providerWorker[%s]: refresh", prov.Name)

	w.SendJobSignal(jobSignalRefresh)
}

func workerCatalogInit(w *worker.Worker, args ...interface{}) {
	var catalog = args[0].(*catalog.Catalog)

	log.Println("DEBUG: catalogWorker: init")

	// Worker properties:
	// 0: catalog instance (*catalog.Catalog)
	w.Props = append(w.Props, catalog)

	w.ReturnErr(nil)
}

func workerCatalogShutdown(w *worker.Worker, args ...interface{}) {
	log.Println("DEBUG: catalogWorker: shutdown")

	w.SendJobSignal(jobSignalShutdown)

	w.ReturnErr(nil)
}

func workerCatalogRun(w *worker.Worker, args ...interface{}) {
	var serverCatalog = w.Props[0].(*catalog.Catalog)

	defer w.Shutdown()

	log.Println("DEBUG: catalogWorker: starting")

	w.State = worker.JobStarted

	for {
		select {
		case cmd := <-w.ReceiveJobSignals():
			switch cmd {
			case jobSignalShutdown:
				log.Println("INFO: catalogWorker: received shutdown command, stopping job")

				w.State = worker.JobStopped

				w.ReturnErr(nil)

				return

			default:
				log.Println("NOTICE: catalogWorker: received unknown command, ignoring")
			}

		case record := <-serverCatalog.RecordChan:
			serverCatalog.Insert(record)
		}
	}
}
<commit_msg>Fix crash with providers initialization<commit_after>// Package server implements the serving of the backend and the web UI.
package server

import (
	"fmt"
	"log"
	"time"

	"github.com/facette/facette/pkg/catalog"
	"github.com/facette/facette/pkg/config"
	"github.com/facette/facette/pkg/connector"
	"github.com/facette/facette/pkg/provider"
	"github.com/facette/facette/pkg/worker"
)

const (
	_ = iota
	eventInit
	eventRun
	eventCatalogRefresh
	eventShutdown

	_ = iota
	jobSignalRefresh
	jobSignalShutdown
)

func (server *Server) startProviderWorkers() error {
	server.providerWorkers = worker.NewWorkerPool()

	log.Println("DEBUG: declaring providers")

	for _, prov := range server.providers {
		connectorType, err := config.GetString(prov.Config.Connector, "type", true)
		if err != nil {
			return fmt.Errorf("provider `%s' connector: %s", prov.Name, err)
		} else if _, ok := connector.Connectors[connectorType]; !ok {
			return fmt.Errorf("provider `%s' uses unknown connector type `%s'", prov.Name, connectorType)
		}

		providerWorker := worker.NewWorker()
		providerWorker.RegisterEvent(eventInit, workerProviderInit)
		providerWorker.RegisterEvent(eventShutdown, workerProviderShutdown)
		providerWorker.RegisterEvent(eventRun, workerProviderRun)
		providerWorker.RegisterEvent(eventCatalogRefresh, workerProviderRefresh)

		if err := providerWorker.SendEvent(eventInit, false, prov, connectorType); err != nil {
			log.Printf("ERROR: in provider `%s', %s", prov.Name, err.Error())
			log.Printf("WARNING: discarding provider `%s'", prov.Name)
			continue
		}

		// Add worker into pool if initialization went fine
		server.providerWorkers.Add(providerWorker)

		providerWorker.SendEvent(eventRun, true, nil)

		log.Printf("DEBUG: declared provider `%s'", prov.Name)
	}

	return nil
}

func (server *Server) stopProviderWorkers() {
	server.providerWorkers.Broadcast(eventShutdown, nil)

	// Wait for all workers to shut down
	server.providerWorkers.Wg.Wait()
}

func workerProviderInit(w *worker.Worker, args ...interface{}) {
	var (
		prov          = args[0].(*provider.Provider)
		connectorType = args[1].(string)
	)

	log.Printf("DEBUG: providerWorker[%s]: init", prov.Name)

	// Instanciate the connector according to its type
	conn, err := connector.Connectors[connectorType](prov.Config.Connector)
	if err != nil {
		w.ReturnErr(err)
		return
	}

	prov.Connector = conn.(connector.Connector)

	// Worker properties:
	// 0: provider instance (*provider.Provider)
	w.Props = append(w.Props, prov)

	w.ReturnErr(nil)
}

func workerProviderShutdown(w *worker.Worker, args ...interface{}) {
	var prov = w.Props[0].(*provider.Provider)

	log.Printf("DEBUG: providerWorker[%s]: shutdown", prov.Name)

	w.SendJobSignal(jobSignalShutdown)
}

func workerProviderRun(w *worker.Worker, args ...interface{}) {
	var (
		prov       = w.Props[0].(*provider.Provider)
		timeTicker *time.Ticker
		timeChan   <-chan time.Time
	)

	defer func() { w.State = worker.JobStopped }()
	defer w.Shutdown()

	log.Printf("DEBUG: providerWorker[%s]: starting", prov.Name)

	// If provider `refresh_interval` has been configured, set up a time ticker
	if prov.Config.RefreshInterval > 0 {
		timeTicker = time.NewTicker(time.Duration(prov.Config.RefreshInterval) * time.Second)
		timeChan = timeTicker.C
	}

	for {
		select {
		case _ = <-timeChan:
			if err := prov.Connector.Refresh(prov.Name, prov.Filters.Input); err != nil {
				log.Printf("ERROR: unable to refresh provider `%s': %s", prov.Name, err)
				continue
			}

			prov.LastRefresh = time.Now()

		case cmd := <-w.ReceiveJobSignals():
			switch cmd {
			case jobSignalRefresh:
				log.Printf("INFO: providerWorker[%s]: received refresh command", prov.Name)

				if err := prov.Connector.Refresh(prov.Name, prov.Filters.Input); err != nil {
					log.Printf("ERROR: unable to refresh provider `%s': %s", prov.Name, err)
					continue
				}

				prov.LastRefresh = time.Now()

			case jobSignalShutdown:
				log.Printf("INFO: providerWorker[%s]: received shutdown command, stopping job", prov.Name)

				w.State = worker.JobStopped

				if timeTicker != nil {
					// Stop refresh time ticker
					timeTicker.Stop()
				}

				return

			default:
				log.Println("NOTICE: providerWorker[%s]: received unknown command, ignoring", prov.Name)
			}
		}
	}
}

func workerProviderRefresh(w *worker.Worker, args ...interface{}) {
	var prov = w.Props[0].(*provider.Provider)

	log.Printf("DEBUG: providerWorker[%s]: refresh", prov.Name)

	w.SendJobSignal(jobSignalRefresh)
}

func workerCatalogInit(w *worker.Worker, args ...interface{}) {
	var catalog = args[0].(*catalog.Catalog)

	log.Println("DEBUG: catalogWorker: init")

	// Worker properties:
	// 0: catalog instance (*catalog.Catalog)
	w.Props = append(w.Props, catalog)

	w.ReturnErr(nil)
}

func workerCatalogShutdown(w *worker.Worker, args ...interface{}) {
	log.Println("DEBUG: catalogWorker: shutdown")

	w.SendJobSignal(jobSignalShutdown)

	w.ReturnErr(nil)
}

func workerCatalogRun(w *worker.Worker, args ...interface{}) {
	var serverCatalog = w.Props[0].(*catalog.Catalog)

	defer w.Shutdown()

	log.Println("DEBUG: catalogWorker: starting")

	w.State = worker.JobStarted

	for {
		select {
		case cmd := <-w.ReceiveJobSignals():
			switch cmd {
			case jobSignalShutdown:
				log.Println("INFO: catalogWorker: received shutdown command, stopping job")

				w.State = worker.JobStopped

				w.ReturnErr(nil)

				return

			default:
				log.Println("NOTICE: catalogWorker: received unknown command, ignoring")
			}

		case record := <-serverCatalog.RecordChan:
			serverCatalog.Insert(record)
		}
	}
}
<|endoftext|>


<commit_before>// Package catalog handles interacting with the OSB catalog endpoint
// (i.e. informers/helpers for ClusterServiceClass and ClusterServicePlan)
package store

import (
	"fmt"
	"sync"

	sc_v1b1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1"
	"github.com/pkg/errors"
	"github.com/xeipuuv/gojsonschema"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/tools/cache"
)

type planSchemaAction string
type planSchemaKey string

const (
	serviceClassExternalNameIndex        = "ServiceClassExternalNameIndex"
	serviceClassExternalIDIndex          = "ServiceClassExternalIDIndex"
	serviceClassAndPlanExternalNameIndex = "ServiceClassAndPlanExternalNameIndex"
	servicePlanExternalIDIndex           = "ServicePlanExternalIDIndex"
	instanceCreateAction                 = planSchemaAction("instanceCreate")
	instanceUpdateAction                 = planSchemaAction("instanceUpdate")
	bindingCreateAction                  = planSchemaAction("bindingCreate")
)

type schemaWithResourceVersion struct {
	resourceVersion string
	schema          *gojsonschema.Schema
}

type ValidationResult struct {
	Errors []error
}

// Catalog is a convenience interface to access OSB catalog information
type Catalog struct {
	serviceClassInfIndexer cache.Indexer
	servicePlanInfIndexer  cache.Indexer

	// schemas is a cache of schemas by plan/action, but NOT resourceVersion.
	// However, we check ResourceVersion in the accessor to see if something is cached,
	// which means we don't hold old ResourceVersions around (but instead
	// replace them with an up-to-date version ASAP).
	// Unlike other parts of smith, this is an on-demand cache, and processing is
	// NOT currently triggered by addition/updates of plans.
	schemas        map[planSchemaKey]schemaWithResourceVersion
	schemasRWMutex sync.RWMutex
}

func NewCatalog(serviceClassInf cache.SharedIndexInformer, servicePlanInf cache.SharedIndexInformer) (*Catalog, error) {
	err := serviceClassInf.AddIndexers(cache.Indexers{
		serviceClassExternalNameIndex: func(obj interface{}) ([]string, error) {
			serviceClass := obj.(*sc_v1b1.ClusterServiceClass)
			return []string{serviceClass.Spec.ExternalName}, nil
		},
		serviceClassExternalIDIndex: func(obj interface{}) ([]string, error) {
			serviceClass := obj.(*sc_v1b1.ClusterServiceClass)
			return []string{serviceClass.Spec.ExternalID}, nil
		},
	})
	if err != nil {
		return nil, errors.WithStack(err)
	}
	err = servicePlanInf.AddIndexers(cache.Indexers{
		serviceClassAndPlanExternalNameIndex: func(obj interface{}) ([]string, error) {
			servicePlan := obj.(*sc_v1b1.ClusterServicePlan)
			return []string{serviceClassAndPlanExternalNameIndexKey(servicePlan.Spec.ClusterServiceClassRef.Name, servicePlan.Spec.ExternalName)}, nil
		},
		servicePlanExternalIDIndex: func(obj interface{}) ([]string, error) {
			servicePlan := obj.(*sc_v1b1.ClusterServicePlan)
			return []string{servicePlan.Spec.ExternalID}, nil
		},
	})
	if err != nil {
		return nil, errors.WithStack(err)
	}

	return &Catalog{
		serviceClassInfIndexer: serviceClassInf.GetIndexer(),
		servicePlanInfIndexer:  servicePlanInf.GetIndexer(),
		schemas:                make(map[planSchemaKey]schemaWithResourceVersion),
	}, nil
}

func serviceClassAndPlanExternalNameIndexKey(serviceClassName string, servicePlanExternalName string) string {
	return serviceClassName + "/" + servicePlanExternalName
}

func (c *Catalog) GetClassOf(serviceInstanceSpec *sc_v1b1.ServiceInstanceSpec) (*sc_v1b1.ClusterServiceClass, error) {
	switch {
	case serviceInstanceSpec.ClusterServiceClassName != "" && serviceInstanceSpec.ClusterServiceClassExternalName != "":
		fallthrough
	case serviceInstanceSpec.ClusterServiceClassName != "" && serviceInstanceSpec.ClusterServiceClassExternalID != "":
		fallthrough
	case serviceInstanceSpec.ClusterServiceClassExternalName != "" && serviceInstanceSpec.ClusterServiceClassExternalID != "":
		return nil, errors.Errorf("ServiceInstance must have only one of ClusterServiceClassName or ClusterServiceClassExternalName or ClusterServiceClassExternalID")
	}
	switch {
	case serviceInstanceSpec.ClusterServiceClassName != "":
		item, exists, err := c.serviceClassInfIndexer.GetByKey(serviceInstanceSpec.ClusterServiceClassName)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		if !exists {
			return nil, errors.Errorf("ServiceInstance refers to non-existant ClusterServiceClass Name=%q", serviceInstanceSpec.ClusterServiceClassName)
		}
		return item.(*sc_v1b1.ClusterServiceClass), nil
	case serviceInstanceSpec.ClusterServiceClassExternalID != "":
		items, err := c.serviceClassInfIndexer.ByIndex(serviceClassExternalIDIndex, serviceInstanceSpec.ClusterServiceClassExternalID)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		switch len(items) {
		case 0:
			return nil, errors.Errorf("ServiceInstance refers to non-existant ClusterServiceClass ExternalID=%q", serviceInstanceSpec.ClusterServiceClassExternalID)
		case 1:
			return items[0].(*sc_v1b1.ClusterServiceClass), nil
		default:
			return nil, errors.Errorf("informer reported multiple instances for ClusterServiceClass ExternalID=%q", serviceInstanceSpec.ClusterServiceClassExternalID)
		}
	case serviceInstanceSpec.ClusterServiceClassExternalName != "":
		items, err := c.serviceClassInfIndexer.ByIndex(serviceClassExternalNameIndex, serviceInstanceSpec.ClusterServiceClassExternalName)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		switch len(items) {
		case 0:
			return nil, errors.Errorf("ServiceInstance refers to non-existent ClusterServiceClass ExternalName=%q", serviceInstanceSpec.ClusterServiceClassExternalName)
		case 1:
			return items[0].(*sc_v1b1.ClusterServiceClass), nil
		default:
			return nil, errors.Errorf("informer reported multiple instances for ClusterServiceClass ExternalName=%q", serviceInstanceSpec.ClusterServiceClassExternalName)
		}
	default:
		return nil, errors.Errorf("ServiceInstance must have at least ClusterServiceClassName or ClusterServiceExternalName or ClusterServiceClassExternalID")
	}
}

func (c *Catalog) GetPlanOf(serviceInstanceSpec *sc_v1b1.ServiceInstanceSpec) (*sc_v1b1.ClusterServicePlan, error) {
	switch {
	case serviceInstanceSpec.ClusterServicePlanName != "" && serviceInstanceSpec.ClusterServicePlanExternalName != "":
		fallthrough
	case serviceInstanceSpec.ClusterServicePlanName != "" && serviceInstanceSpec.ClusterServicePlanExternalID != "":
		fallthrough
	case serviceInstanceSpec.ClusterServicePlanExternalName != "" && serviceInstanceSpec.ClusterServicePlanExternalID != "":
		return nil, errors.Errorf("ServiceInstance must have only one of ClusterServicePlanName or ClusterServicePlanExternalName or ClusterServicePlanExternalID")
	}
	switch {
	case serviceInstanceSpec.ClusterServicePlanName != "":
		item, exists, err := c.servicePlanInfIndexer.GetByKey(serviceInstanceSpec.ClusterServicePlanName)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		if !exists {
			return nil, errors.Errorf("ServiceInstance refers to non-existent ClusterServicePlan Name=%q", serviceInstanceSpec.ClusterServicePlanName)
		}
		return item.(*sc_v1b1.ClusterServicePlan), nil
	case serviceInstanceSpec.ClusterServicePlanExternalID != "":
		items, err := c.servicePlanInfIndexer.ByIndex(servicePlanExternalIDIndex, serviceInstanceSpec.ClusterServicePlanExternalID)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		switch len(items) {
		case 0:
			return nil, errors.Errorf("ServiceInstance refers to non-existant ClusterServicePlan ExternalID=%q", serviceInstanceSpec.ClusterServicePlanExternalID)
		case 1:
			return items[0].(*sc_v1b1.ClusterServicePlan), nil
		default:
			return nil, errors.Errorf("informer reported multiple instances for ClusterServicePlan ExternalID=%q", serviceInstanceSpec.ClusterServicePlanExternalID)
		}
	case serviceInstanceSpec.ClusterServicePlanExternalName != "":
		// If we don't have the plan UUID, we need to look up the class to find its UUID
		serviceClass, err := c.GetClassOf(serviceInstanceSpec)
		if err != nil {
			return nil, err
		}

		planKey := serviceClassAndPlanExternalNameIndexKey(serviceClass.Name, serviceInstanceSpec.ClusterServicePlanExternalName)
		items, err := c.servicePlanInfIndexer.ByIndex(serviceClassAndPlanExternalNameIndex, planKey)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		switch len(items) {
		case 0:
			return nil, errors.Errorf("ServiceInstance refers to non-existant ClusterServicePlan %q", planKey)
		case 1:
			return items[0].(*sc_v1b1.ClusterServicePlan), nil
		default:
			return nil, errors.Errorf("informer reported multiple instances for ClusterServicePlan %q", planKey)
		}
	default:
		return nil, errors.Errorf("ServiceInstance must have at least ClusterServiceClassName or ClusterServiceExternalName or ClusterServiceClassExternalID")
	}
}

func makePlanSchemaKey(plan *sc_v1b1.ClusterServicePlan, action planSchemaAction) planSchemaKey {
	return planSchemaKey(fmt.Sprintf("%s/%s", plan.Name, action))
}

func (c *Catalog) getSchemaCache(plan *sc_v1b1.ClusterServicePlan, action planSchemaAction) (*gojsonschema.Schema, bool) {
	key := makePlanSchemaKey(plan, action)

	c.schemasRWMutex.RLock()
	defer c.schemasRWMutex.RUnlock()
	if schemaWithRv, ok := c.schemas[key]; ok && schemaWithRv.resourceVersion == plan.ResourceVersion {
		return schemaWithRv.schema, true
	}
	// nil is a valid entry in the cache
	return nil, false
}

func (c *Catalog) setSchemaCache(plan *sc_v1b1.ClusterServicePlan, action planSchemaAction, schema *gojsonschema.Schema) {
	key := makePlanSchemaKey(plan, action)

	c.schemasRWMutex.Lock()
	defer c.schemasRWMutex.Unlock()
	c.schemas[key] = schemaWithResourceVersion{plan.ResourceVersion, schema}
}

func (c *Catalog) getParsedSchema(plan *sc_v1b1.ClusterServicePlan, action planSchemaAction) (*gojsonschema.Schema, error) {
	if schema, ok := c.getSchemaCache(plan, action); ok {
		return schema, nil
	}

	var rawSchema *runtime.RawExtension
	switch action {
	case instanceCreateAction:
		rawSchema = plan.Spec.InstanceCreateParameterSchema
	case instanceUpdateAction:
		rawSchema = plan.Spec.InstanceUpdateParameterSchema
	case bindingCreateAction:
		rawSchema = plan.Spec.ServiceBindingCreateParameterSchema
	default:
		return nil, errors.Errorf("plan action %q not understood", action)
	}

	var schema *gojsonschema.Schema
	if rawSchema == nil {
		schema = nil
	} else {
		var err error
		schema, err = gojsonschema.NewSchema(gojsonschema.NewBytesLoader(rawSchema.Raw))
		if err != nil {
			return nil, errors.Wrapf(err,
				"cannot parse json schema for plan %q on broker %q",
				plan.Spec.ExternalName, plan.Spec.ClusterServiceBrokerName)
		}
	}

	c.setSchemaCache(plan, action, schema)
	return schema, nil
}

func (c *Catalog) ValidateServiceInstanceSpec(serviceInstanceSpec *sc_v1b1.ServiceInstanceSpec) (ValidationResult, error) {
	if len(serviceInstanceSpec.ParametersFrom) > 0 {
		return ValidationResult{}, errors.New("cannot validate ServiceInstanceSpec which has a ParametersFrom block (insufficient information)")
	}

	servicePlan, err := c.GetPlanOf(serviceInstanceSpec)
	if err != nil {
		return ValidationResult{}, err
	}

	// We ignore the update schema here and assume it's equivalent to
	// create (since kubernetes/service catalog can't properly distinguish
	// them anyway as there are currently no true PATCH updates).
	schema, err := c.getParsedSchema(servicePlan, instanceCreateAction)
	if err != nil {
		return ValidationResult{
			Errors: []error{err},
		}, nil
	}
	if schema == nil {
		// no schema to validate against anyway
		return ValidationResult{}, nil
	}
	var parameters []byte
	if serviceInstanceSpec.Parameters != nil {
		parameters = serviceInstanceSpec.Parameters.Raw
	} else {
		// I'm not entirely sure what request ServiceCatalog ends up making when
		// no parameters at all are provided, but pretending it's an empty object
		// here makes testing more straight-forward and means that leaving out
		// parameters will give sane looking early validation failures...
		parameters = []byte("{}")
	}
	result, err := schema.Validate(gojsonschema.NewBytesLoader(parameters))
	if err != nil {
		return ValidationResult{}, errors.Wrapf(err, "error validating osb resource parameters for %q", servicePlan.Spec.ExternalName)
	}

	if !result.Valid() {
		validationErrors := result.Errors()
		errs := make([]error, 0, len(validationErrors))

		for _, validationErr := range validationErrors {
			errs = append(errs, errors.New(validationErr.String()))
		}

		return ValidationResult{errs}, nil
	}

	return ValidationResult{}, nil
}
<commit_msg>Satisfy linter<commit_after>// Package catalog handles interacting with the OSB catalog endpoint
// (i.e. informers/helpers for ClusterServiceClass and ClusterServicePlan)
package store

import (
	"fmt"
	"sync"

	sc_v1b1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1"
	"github.com/pkg/errors"
	"github.com/xeipuuv/gojsonschema"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/tools/cache"
)

type planSchemaAction string
type planSchemaKey string

const (
	serviceClassExternalNameIndex        = "ServiceClassExternalNameIndex"
	serviceClassExternalIDIndex          = "ServiceClassExternalIDIndex"
	serviceClassAndPlanExternalNameIndex = "ServiceClassAndPlanExternalNameIndex"
	servicePlanExternalIDIndex           = "ServicePlanExternalIDIndex"
	instanceCreateAction                 = planSchemaAction("instanceCreate")
	instanceUpdateAction                 = planSchemaAction("instanceUpdate")
	bindingCreateAction                  = planSchemaAction("bindingCreate")
)

type schemaWithResourceVersion struct {
	resourceVersion string
	schema          *gojsonschema.Schema
}

type ValidationResult struct {
	Errors []error
}

// Catalog is a convenience interface to access OSB catalog information
type Catalog struct {
	serviceClassInfIndexer cache.Indexer
	servicePlanInfIndexer  cache.Indexer

	// schemas is a cache of schemas by plan/action, but NOT resourceVersion.
	// However, we check ResourceVersion in the accessor to see if something is cached,
	// which means we don't hold old ResourceVersions around (but instead
	// replace them with an up-to-date version ASAP).
	// Unlike other parts of smith, this is an on-demand cache, and processing is
	// NOT currently triggered by addition/updates of plans.
	schemas        map[planSchemaKey]schemaWithResourceVersion
	schemasRWMutex sync.RWMutex
}

func NewCatalog(serviceClassInf cache.SharedIndexInformer, servicePlanInf cache.SharedIndexInformer) (*Catalog, error) {
	err := serviceClassInf.AddIndexers(cache.Indexers{
		serviceClassExternalNameIndex: func(obj interface{}) ([]string, error) {
			serviceClass := obj.(*sc_v1b1.ClusterServiceClass)
			return []string{serviceClass.Spec.ExternalName}, nil
		},
		serviceClassExternalIDIndex: func(obj interface{}) ([]string, error) {
			serviceClass := obj.(*sc_v1b1.ClusterServiceClass)
			return []string{serviceClass.Spec.ExternalID}, nil
		},
	})
	if err != nil {
		return nil, errors.WithStack(err)
	}
	err = servicePlanInf.AddIndexers(cache.Indexers{
		serviceClassAndPlanExternalNameIndex: func(obj interface{}) ([]string, error) {
			servicePlan := obj.(*sc_v1b1.ClusterServicePlan)
			return []string{serviceClassAndPlanExternalNameIndexKey(servicePlan.Spec.ClusterServiceClassRef.Name, servicePlan.Spec.ExternalName)}, nil
		},
		servicePlanExternalIDIndex: func(obj interface{}) ([]string, error) {
			servicePlan := obj.(*sc_v1b1.ClusterServicePlan)
			return []string{servicePlan.Spec.ExternalID}, nil
		},
	})
	if err != nil {
		return nil, errors.WithStack(err)
	}

	return &Catalog{
		serviceClassInfIndexer: serviceClassInf.GetIndexer(),
		servicePlanInfIndexer:  servicePlanInf.GetIndexer(),
		schemas:                make(map[planSchemaKey]schemaWithResourceVersion),
	}, nil
}

func serviceClassAndPlanExternalNameIndexKey(serviceClassName string, servicePlanExternalName string) string {
	return serviceClassName + "/" + servicePlanExternalName
}

func (c *Catalog) GetClassOf(serviceInstanceSpec *sc_v1b1.ServiceInstanceSpec) (*sc_v1b1.ClusterServiceClass, error) {
	switch {
	case serviceInstanceSpec.ClusterServiceClassName != "" && serviceInstanceSpec.ClusterServiceClassExternalName != "":
		fallthrough
	case serviceInstanceSpec.ClusterServiceClassName != "" && serviceInstanceSpec.ClusterServiceClassExternalID != "":
		fallthrough
	case serviceInstanceSpec.ClusterServiceClassExternalName != "" && serviceInstanceSpec.ClusterServiceClassExternalID != "":
		return nil, errors.Errorf("ServiceInstance must have only one of ClusterServiceClassName or ClusterServiceClassExternalName or ClusterServiceClassExternalID")
	}
	switch {
	case serviceInstanceSpec.ClusterServiceClassName != "":
		item, exists, err := c.serviceClassInfIndexer.GetByKey(serviceInstanceSpec.ClusterServiceClassName)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		if !exists {
			return nil, errors.Errorf("ServiceInstance refers to nonexistent ClusterServiceClass Name=%q", serviceInstanceSpec.ClusterServiceClassName)
		}
		return item.(*sc_v1b1.ClusterServiceClass), nil
	case serviceInstanceSpec.ClusterServiceClassExternalID != "":
		items, err := c.serviceClassInfIndexer.ByIndex(serviceClassExternalIDIndex, serviceInstanceSpec.ClusterServiceClassExternalID)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		switch len(items) {
		case 0:
			return nil, errors.Errorf("ServiceInstance refers to nonexistent ClusterServiceClass ExternalID=%q", serviceInstanceSpec.ClusterServiceClassExternalID)
		case 1:
			return items[0].(*sc_v1b1.ClusterServiceClass), nil
		default:
			return nil, errors.Errorf("informer reported multiple instances for ClusterServiceClass ExternalID=%q", serviceInstanceSpec.ClusterServiceClassExternalID)
		}
	case serviceInstanceSpec.ClusterServiceClassExternalName != "":
		items, err := c.serviceClassInfIndexer.ByIndex(serviceClassExternalNameIndex, serviceInstanceSpec.ClusterServiceClassExternalName)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		switch len(items) {
		case 0:
			return nil, errors.Errorf("ServiceInstance refers to nonexistent ClusterServiceClass ExternalName=%q", serviceInstanceSpec.ClusterServiceClassExternalName)
		case 1:
			return items[0].(*sc_v1b1.ClusterServiceClass), nil
		default:
			return nil, errors.Errorf("informer reported multiple instances for ClusterServiceClass ExternalName=%q", serviceInstanceSpec.ClusterServiceClassExternalName)
		}
	default:
		return nil, errors.Errorf("ServiceInstance must have at least ClusterServiceClassName or ClusterServiceExternalName or ClusterServiceClassExternalID")
	}
}

func (c *Catalog) GetPlanOf(serviceInstanceSpec *sc_v1b1.ServiceInstanceSpec) (*sc_v1b1.ClusterServicePlan, error) {
	switch {
	case serviceInstanceSpec.ClusterServicePlanName != "" && serviceInstanceSpec.ClusterServicePlanExternalName != "":
		fallthrough
	case serviceInstanceSpec.ClusterServicePlanName != "" && serviceInstanceSpec.ClusterServicePlanExternalID != "":
		fallthrough
	case serviceInstanceSpec.ClusterServicePlanExternalName != "" && serviceInstanceSpec.ClusterServicePlanExternalID != "":
		return nil, errors.Errorf("ServiceInstance must have only one of ClusterServicePlanName or ClusterServicePlanExternalName or ClusterServicePlanExternalID")
	}
	switch {
	case serviceInstanceSpec.ClusterServicePlanName != "":
		item, exists, err := c.servicePlanInfIndexer.GetByKey(serviceInstanceSpec.ClusterServicePlanName)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		if !exists {
			return nil, errors.Errorf("ServiceInstance refers to nonexistent ClusterServicePlan Name=%q", serviceInstanceSpec.ClusterServicePlanName)
		}
		return item.(*sc_v1b1.ClusterServicePlan), nil
	case serviceInstanceSpec.ClusterServicePlanExternalID != "":
		items, err := c.servicePlanInfIndexer.ByIndex(servicePlanExternalIDIndex, serviceInstanceSpec.ClusterServicePlanExternalID)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		switch len(items) {
		case 0:
			return nil, errors.Errorf("ServiceInstance refers to nonexistent ClusterServicePlan ExternalID=%q", serviceInstanceSpec.ClusterServicePlanExternalID)
		case 1:
			return items[0].(*sc_v1b1.ClusterServicePlan), nil
		default:
			return nil, errors.Errorf("informer reported multiple instances for ClusterServicePlan ExternalID=%q", serviceInstanceSpec.ClusterServicePlanExternalID)
		}
	case serviceInstanceSpec.ClusterServicePlanExternalName != "":
		// If we don't have the plan UUID, we need to look up the class to find its UUID
		serviceClass, err := c.GetClassOf(serviceInstanceSpec)
		if err != nil {
			return nil, err
		}

		planKey := serviceClassAndPlanExternalNameIndexKey(serviceClass.Name, serviceInstanceSpec.ClusterServicePlanExternalName)
		items, err := c.servicePlanInfIndexer.ByIndex(serviceClassAndPlanExternalNameIndex, planKey)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		switch len(items) {
		case 0:
			return nil, errors.Errorf("ServiceInstance refers to nonexistent ClusterServicePlan %q", planKey)
		case 1:
			return items[0].(*sc_v1b1.ClusterServicePlan), nil
		default:
			return nil, errors.Errorf("informer reported multiple instances for ClusterServicePlan %q", planKey)
		}
	default:
		return nil, errors.Errorf("ServiceInstance must have at least ClusterServiceClassName or ClusterServiceExternalName or ClusterServiceClassExternalID")
	}
}

func makePlanSchemaKey(plan *sc_v1b1.ClusterServicePlan, action planSchemaAction) planSchemaKey {
	return planSchemaKey(fmt.Sprintf("%s/%s", plan.Name, action))
}

func (c *Catalog) getSchemaCache(plan *sc_v1b1.ClusterServicePlan, action planSchemaAction) (*gojsonschema.Schema, bool) {
	key := makePlanSchemaKey(plan, action)

	c.schemasRWMutex.RLock()
	defer c.schemasRWMutex.RUnlock()
	if schemaWithRv, ok := c.schemas[key]; ok && schemaWithRv.resourceVersion == plan.ResourceVersion {
		return schemaWithRv.schema, true
	}
	// nil is a valid entry in the cache
	return nil, false
}

func (c *Catalog) setSchemaCache(plan *sc_v1b1.ClusterServicePlan, action planSchemaAction, schema *gojsonschema.Schema) {
	key := makePlanSchemaKey(plan, action)

	c.schemasRWMutex.Lock()
	defer c.schemasRWMutex.Unlock()
	c.schemas[key] = schemaWithResourceVersion{plan.ResourceVersion, schema}
}

func (c *Catalog) getParsedSchema(plan *sc_v1b1.ClusterServicePlan, action planSchemaAction) (*gojsonschema.Schema, error) {
	if schema, ok := c.getSchemaCache(plan, action); ok {
		return schema, nil
	}

	var rawSchema *runtime.RawExtension
	switch action {
	case instanceCreateAction:
		rawSchema = plan.Spec.InstanceCreateParameterSchema
	case instanceUpdateAction:
		rawSchema = plan.Spec.InstanceUpdateParameterSchema
	case bindingCreateAction:
		rawSchema = plan.Spec.ServiceBindingCreateParameterSchema
	default:
		return nil, errors.Errorf("plan action %q not understood", action)
	}

	var schema *gojsonschema.Schema
	if rawSchema == nil {
		schema = nil
	} else {
		var err error
		schema, err = gojsonschema.NewSchema(gojsonschema.NewBytesLoader(rawSchema.Raw))
		if err != nil {
			return nil, errors.Wrapf(err,
				"cannot parse json schema for plan %q on broker %q",
				plan.Spec.ExternalName, plan.Spec.ClusterServiceBrokerName)
		}
	}

	c.setSchemaCache(plan, action, schema)
	return schema, nil
}

func (c *Catalog) ValidateServiceInstanceSpec(serviceInstanceSpec *sc_v1b1.ServiceInstanceSpec) (ValidationResult, error) {
	if len(serviceInstanceSpec.ParametersFrom) > 0 {
		return ValidationResult{}, errors.New("cannot validate ServiceInstanceSpec which has a ParametersFrom block (insufficient information)")
	}

	servicePlan, err := c.GetPlanOf(serviceInstanceSpec)
	if err != nil {
		return ValidationResult{}, err
	}

	// We ignore the update schema here and assume it's equivalent to
	// create (since kubernetes/service catalog can't properly distinguish
	// them anyway as there are currently no true PATCH updates).
	schema, err := c.getParsedSchema(servicePlan, instanceCreateAction)
	if err != nil {
		return ValidationResult{
			Errors: []error{err},
		}, nil
	}
	if schema == nil {
		// no schema to validate against anyway
		return ValidationResult{}, nil
	}
	var parameters []byte
	if serviceInstanceSpec.Parameters != nil {
		parameters = serviceInstanceSpec.Parameters.Raw
	} else {
		// I'm not entirely sure what request ServiceCatalog ends up making when
		// no parameters at all are provided, but pretending it's an empty object
		// here makes testing more straight-forward and means that leaving out
		// parameters will give sane looking early validation failures...
		parameters = []byte("{}")
	}
	result, err := schema.Validate(gojsonschema.NewBytesLoader(parameters))
	if err != nil {
		return ValidationResult{}, errors.Wrapf(err, "error validating osb resource parameters for %q", servicePlan.Spec.ExternalName)
	}

	if !result.Valid() {
		validationErrors := result.Errors()
		errs := make([]error, 0, len(validationErrors))

		for _, validationErr := range validationErrors {
			errs = append(errs, errors.New(validationErr.String()))
		}

		return ValidationResult{errs}, nil
	}

	return ValidationResult{}, nil
}
<|endoftext|>


<commit_before>package svgxml

import (
	"encoding/xml"
	"fmt"
	"os"
	s "strings"
)

type PathDef struct {
	Id    string `xml:"id,attr"`
	D     string `xml:"d,attr"`
	Style string `xml:"style,attr"`
}

type GroupDef struct {
	Id    string    `xml:"id,attr"`
	Path  []PathDef `xml:"path"`
	Xform string    `xml:"transform,attr"`
	Style string    `xml:"style,attr"`
}

type DefsDef struct {
	Id string `xml:"id,attr"`
}

type TSpanDef struct {
	Id    string `xml:"id,attr"`
	X     string `xml:"x,attr"`
	Y     string `xml:"y,attr"`
	Label string `xml:",chardata"`
}

type TextDef struct {
	Style string   `xml:"style,attr"`
	X     string   `xml:"x,attr"`
	Y     string   `xml:"y,attr"`
	Id    string   `xml:"id,attr"`
	TSpan TSpanDef `xml:"tspan"`
}

type SVG struct {
	XMLName xml.Name   `xml:"svg"`
	XMLNS   string     `xml:"xmlns,attr"`
	Width   string     `xml:"width,attr"`
	Height  string     `xml:"height,attr"`
	Id      string     `xml:"id,attr"`
	G       []GroupDef `xml:"g"`
	Path    []PathDef  `xml:"path"`
	Defs    DefsDef    `xml:"defs"`
	Version string     `xml:"version,attr"`
	Text    []TextDef  `xml:"text"`
}

func XML2SVG(svg_xml []byte) *SVG {

	svg_obj := SVG{}
	err := xml.Unmarshal([]byte(svg_xml), &svg_obj)
	if err == nil {
		return &svg_obj
	} else {
		fmt.Fprintf(os.Stderr, "xml.Unmarshal error: %v\n", err)
		return nil
	}
}

func SVG2XML(imgxml *SVG, multi_line bool) []byte {

	var xml_txt []byte
	var err error

	if multi_line {
		xml_txt, err = xml.MarshalIndent(imgxml, "", "    ")
	} else {
		xml_txt, err = xml.Marshal(imgxml)
	}
	if err != nil {
		fmt.Fprintf(os.Stderr, "xml.Marshal error: %v\n", err)
		return nil
	}

	svgtag_end := s.Index(string(xml_txt), "<svg") + 4
	xmlout := []byte(
		xml.Header +
			string(xml_txt[:svgtag_end]) +
			` xmlns:svg="http://www.w3.org/2000/svg" xml:space="preserve"` +
			s.Replace(
				s.Replace(
					string(xml_txt[svgtag_end:]),
					"></path", " /",
					-1),
				"></defs", " /",
				-1))
	return xmlout
}

func FindPathById(mapsvg_obj *SVG, id string) *PathDef {
	for g_ind, group := range mapsvg_obj.G {
		for p_ind, path := range group.Path {
			if path.Id == id {
				return &(mapsvg_obj.G[g_ind].Path[p_ind])
			}
		}
	}
	return nil
}
<commit_msg>make 'Id' the first field of all structs that have it<commit_after>package svgxml

import (
	"encoding/xml"
	"fmt"
	"os"
	s "strings"
)

type PathDef struct {
	Id    string `xml:"id,attr"`
	D     string `xml:"d,attr"`
	Style string `xml:"style,attr"`
}

type GroupDef struct {
	Id    string    `xml:"id,attr"`
	Path  []PathDef `xml:"path"`
	Xform string    `xml:"transform,attr"`
	Style string    `xml:"style,attr"`
}

type DefsDef struct {
	Id string `xml:"id,attr"`
}

type TSpanDef struct {
	Id    string `xml:"id,attr"`
	X     string `xml:"x,attr"`
	Y     string `xml:"y,attr"`
	Label string `xml:",chardata"`
}

type TextDef struct {
	Id    string   `xml:"id,attr"`
	Style string   `xml:"style,attr"`
	X     string   `xml:"x,attr"`
	Y     string   `xml:"y,attr"`
	TSpan TSpanDef `xml:"tspan"`
}

type SVG struct {
	XMLName xml.Name   `xml:"svg"`
	XMLNS   string     `xml:"xmlns,attr"`
	Width   string     `xml:"width,attr"`
	Height  string     `xml:"height,attr"`
	Id      string     `xml:"id,attr"`
	G       []GroupDef `xml:"g"`
	Path    []PathDef  `xml:"path"`
	Defs    DefsDef    `xml:"defs"`
	Version string     `xml:"version,attr"`
	Text    []TextDef  `xml:"text"`
}

func XML2SVG(svg_xml []byte) *SVG {

	svg_obj := SVG{}
	err := xml.Unmarshal([]byte(svg_xml), &svg_obj)
	if err == nil {
		return &svg_obj
	} else {
		fmt.Fprintf(os.Stderr, "xml.Unmarshal error: %v\n", err)
		return nil
	}
}

func SVG2XML(imgxml *SVG, multi_line bool) []byte {

	var xml_txt []byte
	var err error

	if multi_line {
		xml_txt, err = xml.MarshalIndent(imgxml, "", "    ")
	} else {
		xml_txt, err = xml.Marshal(imgxml)
	}
	if err != nil {
		fmt.Fprintf(os.Stderr, "xml.Marshal error: %v\n", err)
		return nil
	}

	svgtag_end := s.Index(string(xml_txt), "<svg") + 4
	xmlout := []byte(
		xml.Header +
			string(xml_txt[:svgtag_end]) +
			` xmlns:svg="http://www.w3.org/2000/svg" xml:space="preserve"` +
			s.Replace(
				s.Replace(
					string(xml_txt[svgtag_end:]),
					"></path", " /",
					-1),
				"></defs", " /",
				-1))
	return xmlout
}

func FindPathById(mapsvg_obj *SVG, id string) *PathDef {
	for g_ind, group := range mapsvg_obj.G {
		for p_ind, path := range group.Path {
			if path.Id == id {
				return &(mapsvg_obj.G[g_ind].Path[p_ind])
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google LLC. 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 webhook

import (
	"errors"

	"github.com/elafros/elafros/pkg/apis/ela/v1alpha1"
	"github.com/golang/glog"
	"github.com/mattbaird/jsonpatch"
)

var (
	errInvalidRevisions        = errors.New("The route must has exactly one of revision or configuration in traffic field")
	errInvalidRouteInput       = errors.New("Failed to convert input into Route")
	errInvalidTargetPercentSum = errors.New("The route must has traffic percent sum equal to 100")
	errNegativeTargetPercent   = errors.New("The route cannot has negative traffic percent")
)

// ValidateRoute is Route resource specific validation and mutation handler
func ValidateRoute(patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error {
	var oldRoute *v1alpha1.Route
	if old != nil {
		var ok bool
		oldRoute, ok = old.(*v1alpha1.Route)
		if !ok {
			return errInvalidRouteInput
		}
	}
	glog.Infof("ValidateRoute: OLD Route is\n%+v", oldRoute)
	newRoute, ok := new.(*v1alpha1.Route)
	if !ok {
		return errInvalidRouteInput
	}
	glog.Infof("ValidateRoute: NEW Route is\n%+v", newRoute)

	if err := validateTrafficTarget(newRoute); err != nil {
		return err
	}

	return nil
}

func validateTrafficTarget(route *v1alpha1.Route) error {
	// A service as a placeholder that's not backed by anything is allowed.
	if route.Spec.Traffic == nil {
		return nil
	}

	percentSum := 0
	for _, trafficTarget := range route.Spec.Traffic {
		revisionLen := len(trafficTarget.RevisionName)
		configurationLen := len(trafficTarget.ConfigurationName)
		if (revisionLen == 0 && configurationLen == 0) ||
			(revisionLen != 0 && configurationLen != 0) {
			return errInvalidRevisions
		}

		if trafficTarget.Percent < 0 {
			return errNegativeTargetPercent
		}
		percentSum += trafficTarget.Percent
	}

	if percentSum != 100 {
		return errInvalidTargetPercentSum
	}
	return nil
}
<commit_msg>Minor typo fix (#307)<commit_after>/*
Copyright 2018 Google LLC. 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 webhook

import (
	"errors"

	"github.com/elafros/elafros/pkg/apis/ela/v1alpha1"
	"github.com/golang/glog"
	"github.com/mattbaird/jsonpatch"
)

var (
	errInvalidRevisions        = errors.New("The route must have exactly one of revision or configuration in traffic field.")
	errInvalidRouteInput       = errors.New("Failed to convert input into Route.")
	errInvalidTargetPercentSum = errors.New("The route must have traffic percent sum equal to 100.")
	errNegativeTargetPercent   = errors.New("The route cannot have a negative traffic percent.")
)

// ValidateRoute is Route resource specific validation and mutation handler
func ValidateRoute(patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error {
	var oldRoute *v1alpha1.Route
	if old != nil {
		var ok bool
		oldRoute, ok = old.(*v1alpha1.Route)
		if !ok {
			return errInvalidRouteInput
		}
	}
	glog.Infof("ValidateRoute: OLD Route is\n%+v", oldRoute)
	newRoute, ok := new.(*v1alpha1.Route)
	if !ok {
		return errInvalidRouteInput
	}
	glog.Infof("ValidateRoute: NEW Route is\n%+v", newRoute)

	if err := validateTrafficTarget(newRoute); err != nil {
		return err
	}

	return nil
}

func validateTrafficTarget(route *v1alpha1.Route) error {
	// A service as a placeholder that's not backed by anything is allowed.
	if route.Spec.Traffic == nil {
		return nil
	}

	percentSum := 0
	for _, trafficTarget := range route.Spec.Traffic {
		revisionLen := len(trafficTarget.RevisionName)
		configurationLen := len(trafficTarget.ConfigurationName)
		if (revisionLen == 0 && configurationLen == 0) ||
			(revisionLen != 0 && configurationLen != 0) {
			return errInvalidRevisions
		}

		if trafficTarget.Percent < 0 {
			return errNegativeTargetPercent
		}
		percentSum += trafficTarget.Percent
	}

	if percentSum != 100 {
		return errInvalidTargetPercentSum
	}
	return nil
}
<|endoftext|>


<commit_before>package coremain

const (
	coreName    = "CoreDNS"
	coreVersion = "1.0.3"

	serverType = "dns"
)
<commit_msg>Up version to 1.0.4 (#1404)<commit_after>package coremain

const (
	coreName    = "CoreDNS"
	coreVersion = "1.0.4"

	serverType = "dns"
)
<|endoftext|>


<commit_before>package concrete

import (
	"github.com/gonum/graph"
	"math"
)

// A dense graph is a graph such that all IDs are in a contiguous block from 0 to TheNumberOfNodes-1
// it uses an adjacency matrix and should be relatively fast for both access and writing.
//
// This graph implements the CrunchGraph, but since it's naturally dense this is superfluous
type DenseGraph struct {
	adjacencyMatrix []float64
	numNodes        int
}

// Creates a dense graph with the proper number of nodes. If passable is true all nodes will have
// an edge with cost 1.0, otherwise every node will start unconnected (cost of +Inf)
func NewDenseGraph(numNodes int, passable bool) *DenseGraph {
	dg := &DenseGraph{adjacencyMatrix: make([]float64, numNodes*numNodes), numNodes: numNodes}
	if passable {
		for i := range dg.adjacencyMatrix {
			dg.adjacencyMatrix[i] = 1.0
		}
	} else {
		for i := range dg.adjacencyMatrix {
			dg.adjacencyMatrix[i] = math.Inf(1)
		}
	}

	return dg
}

func (dg *DenseGraph) NodeExists(node graph.Node) bool {
	return node.ID() < dg.numNodes
}

func (dg *DenseGraph) Degree(node graph.Node) int {
	deg := 0
	for i := 0; i < dg.numNodes; i++ {
		if dg.adjacencyMatrix[i*dg.numNodes+node.ID()] != math.Inf(1) {
			deg++
		}

		if dg.adjacencyMatrix[node.ID()*dg.numNodes+i] != math.Inf(1) {
			deg++
		}
	}

	return deg
}

func (dg *DenseGraph) NodeList() []graph.Node {
	nodes := make([]graph.Node, dg.numNodes)
	for i := 0; i < dg.numNodes; i++ {
		nodes[i] = GonumNode(i)
	}

	return nodes
}

func (dg *DenseGraph) DirectedEdgeList() []graph.Edge {
	edges := make([]graph.Edge, 0, len(dg.adjacencyMatrix))
	for i := 0; i < dg.numNodes; i++ {
		for j := 0; j < dg.numNodes; j++ {
			if dg.adjacencyMatrix[i*dg.numNodes+j] != math.Inf(1) {
				edges = append(edges, GonumEdge{GonumNode(i), GonumNode(j)})
			}
		}
	}

	return edges
}

func (dg *DenseGraph) Neighbors(node graph.Node) []graph.Node {
	neighbors := make([]graph.Node, 0)
	for i := 0; i < dg.numNodes; i++ {
		if dg.adjacencyMatrix[i*dg.numNodes+node.ID()] != math.Inf(1) ||
			dg.adjacencyMatrix[node.ID()*dg.numNodes+i] != math.Inf(1) {
			neighbors = append(neighbors, GonumNode(i))
		}
	}

	return neighbors
}

func (dg *DenseGraph) IsNeighbor(node, neighbor graph.Node) bool {
	return dg.adjacencyMatrix[neighbor.ID()*dg.numNodes+node.ID()] != math.Inf(1) ||
		dg.adjacencyMatrix[node.ID()*dg.numNodes+neighbor.ID()] != math.Inf(1)
}

func (dg *DenseGraph) Successors(node graph.Node) []graph.Node {
	neighbors := make([]graph.Node, 0)
	for i := 0; i < dg.numNodes; i++ {
		if dg.adjacencyMatrix[node.ID()*dg.numNodes+i] != math.Inf(1) {
			neighbors = append(neighbors, GonumNode(i))
		}
	}

	return neighbors
}

func (dg *DenseGraph) IsSuccessor(node, succ graph.Node) bool {
	return dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()] != math.Inf(1)
}

func (dg *DenseGraph) Predecessors(node graph.Node) []graph.Node {
	neighbors := make([]graph.Node, 0)
	for i := 0; i < dg.numNodes; i++ {
		if dg.adjacencyMatrix[i*dg.numNodes+node.ID()] != math.Inf(1) {
			neighbors = append(neighbors, GonumNode(i))
		}
	}

	return neighbors
}

func (dg *DenseGraph) IsPredecessor(node, pred graph.Node) bool {
	return dg.adjacencyMatrix[pred.ID()*dg.numNodes+node.ID()] != math.Inf(1)
}

// DenseGraph is naturally dense, we don't need to do anything
func (dg *DenseGraph) Crunch() {
}

func (dg *DenseGraph) Cost(node, succ graph.Node) float64 {
	return dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()]
}

// Sets the cost of the edge between node and succ. If the cost is +Inf, it will remove the edge,
// if directed is true, it will only remove the edge one way. If it's false it will change the cost
// of the edge from succ to node as well.
func (dg *DenseGraph) SetEdgeCost(node, succ graph.Node, cost float64, directed bool) {
	dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()] = cost
	if !directed {
		dg.adjacencyMatrix[succ.ID()*dg.numNodes+node.ID()] = cost
	}
}

// More or less equivalent to SetEdgeCost(node, succ, math.Inf(1), directed)
func (dg *DenseGraph) RemoveEdge(node, succ graph.Node, directed bool) {
	dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()] = math.Inf(1)
	if !directed {
		dg.adjacencyMatrix[succ.ID()*dg.numNodes+node.ID()] = math.Inf(1)
	}
}
<commit_msg>Made a basic dense graph<commit_after>package concrete

import (
	"github.com/gonum/graph"
	"math"
)

// A dense graph is a graph such that all IDs are in a contiguous block from 0 to TheNumberOfNodes-1
// it uses an adjacency matrix and should be relatively fast for both access and writing.
//
// This graph implements the CrunchGraph, but since it's naturally dense this is superfluous
type DenseGraph struct {
	adjacencyMatrix []float64
	numNodes        int
}

// Creates a dense graph with the proper number of nodes. If passable is true all nodes will have
// an edge with cost 1.0, otherwise every node will start unconnected (cost of +Inf)
func NewDenseGraph(numNodes int, passable bool) *DenseGraph {
	dg := &DenseGraph{adjacencyMatrix: make([]float64, numNodes*numNodes), numNodes: numNodes}
	if passable {
		for i := range dg.adjacencyMatrix {
			dg.adjacencyMatrix[i] = 1.0
		}
	} else {
		for i := range dg.adjacencyMatrix {
			dg.adjacencyMatrix[i] = math.Inf(1)
		}
	}

	return dg
}

func (dg *DenseGraph) NodeExists(node graph.Node) bool {
	return node.ID() < dg.numNodes
}

func (dg *DenseGraph) Degree(node graph.Node) int {
	deg := 0
	for i := 0; i < dg.numNodes; i++ {
		if dg.adjacencyMatrix[i*dg.numNodes+node.ID()] != math.Inf(1) {
			deg++
		}

		if dg.adjacencyMatrix[node.ID()*dg.numNodes+i] != math.Inf(1) {
			deg++
		}
	}

	return deg
}

func (dg *DenseGraph) NodeList() []graph.Node {
	nodes := make([]graph.Node, dg.numNodes)
	for i := 0; i < dg.numNodes; i++ {
		nodes[i] = GonumNode(i)
	}

	return nodes
}

func (dg *DenseGraph) DirectedEdgeList() []graph.Edge {
	edges := make([]graph.Edge, 0, len(dg.adjacencyMatrix))
	for i := 0; i < dg.numNodes; i++ {
		for j := 0; j < dg.numNodes; j++ {
			if dg.adjacencyMatrix[i*dg.numNodes+j] != math.Inf(1) {
				edges = append(edges, GonumEdge{GonumNode(i), GonumNode(j)})
			}
		}
	}

	return edges
}

func (dg *DenseGraph) Neighbors(node graph.Node) []graph.Node {
	neighbors := make([]graph.Node, 0)
	for i := 0; i < dg.numNodes; i++ {
		if dg.adjacencyMatrix[i*dg.numNodes+node.ID()] != math.Inf(1) ||
			dg.adjacencyMatrix[node.ID()*dg.numNodes+i] != math.Inf(1) {
			neighbors = append(neighbors, GonumNode(i))
		}
	}

	return neighbors
}

func (dg *DenseGraph) IsNeighbor(node, neighbor graph.Node) bool {
	return dg.adjacencyMatrix[neighbor.ID()*dg.numNodes+node.ID()] != math.Inf(1) ||
		dg.adjacencyMatrix[node.ID()*dg.numNodes+neighbor.ID()] != math.Inf(1)
}

func (dg *DenseGraph) Successors(node graph.Node) []graph.Node {
	neighbors := make([]graph.Node, 0)
	for i := 0; i < dg.numNodes; i++ {
		if dg.adjacencyMatrix[node.ID()*dg.numNodes+i] != math.Inf(1) {
			neighbors = append(neighbors, GonumNode(i))
		}
	}

	return neighbors
}

func (dg *DenseGraph) IsSuccessor(node, succ graph.Node) bool {
	return dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()] != math.Inf(1)
}

func (dg *DenseGraph) Predecessors(node graph.Node) []graph.Node {
	neighbors := make([]graph.Node, 0)
	for i := 0; i < dg.numNodes; i++ {
		if dg.adjacencyMatrix[i*dg.numNodes+node.ID()] != math.Inf(1) {
			neighbors = append(neighbors, GonumNode(i))
		}
	}

	return neighbors
}

func (dg *DenseGraph) IsPredecessor(node, pred graph.Node) bool {
	return dg.adjacencyMatrix[pred.ID()*dg.numNodes+node.ID()] != math.Inf(1)
}

// DenseGraph is naturally dense, we don't need to do anything
func (dg *DenseGraph) Crunch() {
}

func (dg *DenseGraph) Cost(node, succ graph.Node) float64 {
	return dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()]
}

// Sets the cost of the edge between node and succ. If the cost is +Inf, it will remove the edge,
// if directed is true, it will only remove the edge one way. If it's false it will change the cost
// of the edge from succ to node as well.
func (dg *DenseGraph) SetEdgeCost(node, succ graph.Node, cost float64, directed bool) {
	dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()] = cost
	if !directed {
		dg.adjacencyMatrix[succ.ID()*dg.numNodes+node.ID()] = cost
	}
}

<<<<<<< HEAD
// More or less equivalent to SetEdgeCost(node, succ, math.Inf(1), directed)
=======
>>>>>>> Made a basic dense graph
func (dg *DenseGraph) RemoveEdge(node, succ graph.Node, directed bool) {
	dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()] = math.Inf(1)
	if !directed {
		dg.adjacencyMatrix[succ.ID()*dg.numNodes+node.ID()] = math.Inf(1)
	}
}
<|endoftext|>


<commit_before>// Copyright 2020 xgfone
//
// 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 namespace

import "testing"

func TestNameSpace(t *testing.T) {
	ns := NewNameSpace("_test_ns_")

	if exist, err := ns.IsExist(); err != nil {
		t.Error(err)
	} else if exist {
		t.Fail()
	}

	if err := ns.Create(); err != nil {
		t.Fatal(err)
	}

	if exist, err := ns.IsExist(); err != nil {
		t.Error(err)
	} else if !exist {
		t.Fail()
	}

	if _, err := ns.Exec("ls"); err != nil {
		t.Error(err)
	}

	if nss, err := GetAllNameSpace(); err != nil {
		t.Error(err)
	} else {
		var exist bool
		for _, _ns := range nss {
			if _ns.Name == ns.Name {
				exist = true
			}
		}

		if !exist {
			t.Errorf("NS '%s' does not exist", ns.Name)
		}
	}

	if err := ns.Delete(); err != nil {
		t.Fatal(err)
	}

	if nss, err := GetAllNameSpace(); err != nil {
		t.Error(err)
	} else {
		var exist bool
		for _, _ns := range nss {
			if _ns.Name == ns.Name {
				exist = true
			}
		}

		if exist {
			t.Errorf("NS '%s' exist", ns.Name)
		}
	}
}
<commit_msg>fix the linux namespace test<commit_after>// Copyright 2020 xgfone
//
// 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 namespace

import (
	"strings"
	"testing"
)

func TestNameSpace(t *testing.T) {
	ns := NewNameSpace("_test_ns_")

	if exist, err := ns.IsExist(); err != nil {
		t.Error(err)
	} else if exist {
		t.Fail()
	}

	if err := ns.Create(); err != nil {
		if strings.Contain(err.Error(), "Permission denied") {
			return
		}
		t.Fatal(err)
	}

	if exist, err := ns.IsExist(); err != nil {
		t.Error(err)
	} else if !exist {
		t.Fail()
	}

	if _, err := ns.Exec("ls"); err != nil {
		t.Error(err)
	}

	if nss, err := GetAllNameSpace(); err != nil {
		t.Error(err)
	} else {
		var exist bool
		for _, _ns := range nss {
			if _ns.Name == ns.Name {
				exist = true
			}
		}

		if !exist {
			t.Errorf("NS '%s' does not exist", ns.Name)
		}
	}

	if err := ns.Delete(); err != nil {
		t.Fatal(err)
	}

	if nss, err := GetAllNameSpace(); err != nil {
		t.Error(err)
	} else {
		var exist bool
		for _, _ns := range nss {
			if _ns.Name == ns.Name {
				exist = true
			}
		}

		if exist {
			t.Errorf("NS '%s' exist", ns.Name)
		}
	}
}
<|endoftext|>


<commit_before>// Package plumb provides routines for sending and receiving messages for the plumber.
package plumb

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
	"sync"

	"code.google.com/p/goplan9/plan9/client"
)

// Message represents a message to or from the plumber.
type Message struct {
	Src  string     // The source of the message ("acme").
	Dst  string     // The destination port of the message ("edit").
	Dir  string     // The working directory in which to interpret the message.
	Type string     // The type of the message ("text").
	Attr *Attribute // The attributes; may be nil.
	Data []byte     // The data; may be nil.
}

// Attribute represents a list of attributes for a single Message.
type Attribute struct {
	Name  string // The name of the attribute ("addr").
	Value string // The value of the attribute ("/long johns/")
	Next  *Attribute
}

var (
	ErrAttribute = errors.New("bad attribute syntax")
	ErrQuote     = errors.New("bad attribute quoting")
)

var fsys *client.Fsys
var fsysErr error
var fsysOnce sync.Once

func mountPlumb() {
	fsys, fsysErr = client.MountService("plumb")
}

// Open opens the plumbing file with the given name and open mode.
func Open(name string, mode int) (*client.Fid, error) {
	fsysOnce.Do(mountPlumb)
	if fsysErr != nil {
		return nil, fsysErr
	}
	fid, err := fsys.Open(name, uint8(mode))
	if err != nil {
		return nil, err
	}
	return fid, nil
}

// Send writes the message to the writer. The message may be sent with
// multiple calls to Write.
func (m *Message) Send(w io.Writer) error {
	fmt.Fprintf(w, "%s\n", m.Src)
	fmt.Fprintf(w, "%s\n", m.Dst)
	fmt.Fprintf(w, "%s\n", m.Dir)
	fmt.Fprintf(w, "%s\n", m.Type)
	m.Attr.send(w)
	fmt.Fprintf(w, "%d\n", len(m.Data))
	_, err := w.Write(m.Data) // We assume the last write will give us any error.
	return err
}

func (attr *Attribute) send(w io.Writer) {
	if attr == nil {
		return
	}
	for a := attr; a != nil; a = a.Next {
		if a != attr {
			fmt.Fprint(w, " ")
		}
		fmt.Fprintf(w, "%s=%s", a.Name, quoteAttribute(a.Value))
	}
	fmt.Fprintf(w, "\n")
}

const quote = '\''

// quoteAttribute quotes the attribute value, if necessary, and returns the result.
func quoteAttribute(s string) string {
	if !strings.ContainsAny(s, " '=\t") {
		return s
	}
	b := make([]byte, 0, 10+len(s)) // Room for a couple of quotes and a few backslashes.
	b = append(b, quote)
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c == quote {
			b = append(b, quote)
		}
		b = append(b, c)
	}
	b = append(b, quote)
	return string(b)
}

// Recv reads a message from the reader. Since the messages are properly
// delimited, Recv will not read any data beyond the message itself.
func (m *Message) Recv(r io.ByteReader) error {
	reader := newReader(r)
	m.Src = reader.readLine()
	m.Dst = reader.readLine()
	m.Dir = reader.readLine()
	m.Type = reader.readLine()
	m.Attr = reader.readAttr()
	if reader.err != nil {
		return reader.err
	}
	n, err := strconv.Atoi(reader.readLine())
	if err != nil {
		return err
	}
	m.Data = make([]byte, n)
	reader.read(m.Data)
	return reader.err
}

type reader struct {
	r    io.ByteReader
	buf  []byte
	attr *Attribute
	err  error
}

func newReader(r io.ByteReader) *reader {
	return &reader{
		r:   r,
		buf: make([]byte, 32),
	}
}

func (r *reader) readLine() string {
	r.buf = r.buf[:0]
	var c byte
	for r.err == nil {
		c, r.err = r.r.ReadByte()
		if c == '\n' {
			break
		}
		r.buf = append(r.buf, c)
	}
	return string(r.buf)
}

func (r *reader) read(p []byte) {
	rr, ok := r.r.(io.Reader)
	if r.err == nil && ok {
		_, r.err = rr.Read(p)
		return
	}
	for i := range p {
		if r.err != nil {
			break
		}
		p[i], r.err = r.r.ReadByte()
	}
}

func (r *reader) readAttr() *Attribute {
	r.buf = r.buf[:0]
	var c byte
	quoting := false
Loop:
	for r.err == nil {
		c, r.err = r.r.ReadByte()
		if quoting && c == quote {
			r.buf = append(r.buf, c)
			c, r.err = r.r.ReadByte()
			if c != quote {
				quoting = false
			}
		}
		if !quoting {
			switch c {
			case '\n':
				break Loop
			case quote:
				quoting = true
			case ' ':
				r.newAttr()
				r.buf = r.buf[:0]
				continue Loop // Don't add the space.
			}
		}
		r.buf = append(r.buf, c)
	}
	if len(r.buf) > 0 && r.err == nil {
		r.newAttr()
	}
	// Attributes are ordered so reverse the list.
	var next, rattr *Attribute
	for a := r.attr; a != nil; a = next {
		next = a.Next
		a.Next = rattr
		rattr = a
	}
	return rattr
}

func (r *reader) newAttr() {
	equals := bytes.IndexByte(r.buf, '=')
	if equals < 0 {
		r.err = ErrAttribute
		return
	}
	str := string(r.buf)
	r.attr = &Attribute{
		Name: str[:equals],
		Next: r.attr,
	}
	r.attr.Value, r.err = unquoteAttribute(str[equals+1:])
}

// unquoteAttribute unquotes the attribute value, if necessary, and returns the result.
func unquoteAttribute(s string) (string, error) {
	if !strings.Contains(s, "'") {
		return s, nil
	}
	if len(s) < 2 || s[0] != quote || s[len(s)-1] != quote {
		return s, ErrQuote
	}
	s = s[1 : len(s)-1]
	b := make([]byte, 0, len(s))
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c == quote { // Must be doubled.
			if i == len(s)-1 || s[i+1] != quote {
				return s, ErrQuote
			}
			i++
		}
		b = append(b, c)
	}
	return string(b), nil
}
<commit_msg>goplan9/plan9/plumb: make Send do a single write. Nicer semantics. Cleaner error handling.<commit_after>// Package plumb provides routines for sending and receiving messages for the plumber.
package plumb

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
	"sync"

	"code.google.com/p/goplan9/plan9/client"
)

// Message represents a message to or from the plumber.
type Message struct {
	Src  string     // The source of the message ("acme").
	Dst  string     // The destination port of the message ("edit").
	Dir  string     // The working directory in which to interpret the message.
	Type string     // The type of the message ("text").
	Attr *Attribute // The attributes; may be nil.
	Data []byte     // The data; may be nil.
}

// Attribute represents a list of attributes for a single Message.
type Attribute struct {
	Name  string // The name of the attribute ("addr").
	Value string // The value of the attribute ("/long johns/")
	Next  *Attribute
}

var (
	ErrAttribute = errors.New("bad attribute syntax")
	ErrQuote     = errors.New("bad attribute quoting")
)

var fsys *client.Fsys
var fsysErr error
var fsysOnce sync.Once

func mountPlumb() {
	fsys, fsysErr = client.MountService("plumb")
}

// Open opens the plumbing file with the given name and open mode.
func Open(name string, mode int) (*client.Fid, error) {
	fsysOnce.Do(mountPlumb)
	if fsysErr != nil {
		return nil, fsysErr
	}
	fid, err := fsys.Open(name, uint8(mode))
	if err != nil {
		return nil, err
	}
	return fid, nil
}

// Send writes the message to the writer. The message will be sent with
// a single call to Write.
func (m *Message) Send(w io.Writer) error {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "%s\n", m.Src)
	fmt.Fprintf(&buf, "%s\n", m.Dst)
	fmt.Fprintf(&buf, "%s\n", m.Dir)
	fmt.Fprintf(&buf, "%s\n", m.Type)
	m.Attr.send(&buf)
	fmt.Fprintf(&buf, "%d\n", len(m.Data))
	buf.Write(m.Data)
	_, err := w.Write(buf.Bytes())
	return err
}

func (attr *Attribute) send(w io.Writer) {
	if attr == nil {
		return
	}
	for a := attr; a != nil; a = a.Next {
		if a != attr {
			fmt.Fprint(w, " ")
		}
		fmt.Fprintf(w, "%s=%s", a.Name, quoteAttribute(a.Value))
	}
	fmt.Fprintf(w, "\n")
}

const quote = '\''

// quoteAttribute quotes the attribute value, if necessary, and returns the result.
func quoteAttribute(s string) string {
	if !strings.ContainsAny(s, " '=\t") {
		return s
	}
	b := make([]byte, 0, 10+len(s)) // Room for a couple of quotes and a few backslashes.
	b = append(b, quote)
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c == quote {
			b = append(b, quote)
		}
		b = append(b, c)
	}
	b = append(b, quote)
	return string(b)
}

// Recv reads a message from the reader and stores it in the Message.
// Since encoded messages are properly delimited, Recv will not read
// any data beyond the message itself.
func (m *Message) Recv(r io.ByteReader) error {
	reader := newReader(r)
	m.Src = reader.readLine()
	m.Dst = reader.readLine()
	m.Dir = reader.readLine()
	m.Type = reader.readLine()
	m.Attr = reader.readAttr()
	if reader.err != nil {
		return reader.err
	}
	n, err := strconv.Atoi(reader.readLine())
	if err != nil {
		return err
	}
	m.Data = make([]byte, n)
	reader.read(m.Data)
	return reader.err
}

type reader struct {
	r    io.ByteReader
	buf  []byte
	attr *Attribute
	err  error
}

func newReader(r io.ByteReader) *reader {
	return &reader{
		r:   r,
		buf: make([]byte, 32),
	}
}

func (r *reader) readLine() string {
	r.buf = r.buf[:0]
	var c byte
	for r.err == nil {
		c, r.err = r.r.ReadByte()
		if c == '\n' {
			break
		}
		r.buf = append(r.buf, c)
	}
	return string(r.buf)
}

func (r *reader) read(p []byte) {
	rr, ok := r.r.(io.Reader)
	if r.err == nil && ok {
		_, r.err = rr.Read(p)
		return
	}
	for i := range p {
		if r.err != nil {
			break
		}
		p[i], r.err = r.r.ReadByte()
	}
}

func (r *reader) readAttr() *Attribute {
	r.buf = r.buf[:0]
	var c byte
	quoting := false
Loop:
	for r.err == nil {
		c, r.err = r.r.ReadByte()
		if quoting && c == quote {
			r.buf = append(r.buf, c)
			c, r.err = r.r.ReadByte()
			if c != quote {
				quoting = false
			}
		}
		if !quoting {
			switch c {
			case '\n':
				break Loop
			case quote:
				quoting = true
			case ' ':
				r.newAttr()
				r.buf = r.buf[:0]
				continue Loop // Don't add the space.
			}
		}
		r.buf = append(r.buf, c)
	}
	if len(r.buf) > 0 && r.err == nil {
		r.newAttr()
	}
	// Attributes are ordered so reverse the list.
	var next, rattr *Attribute
	for a := r.attr; a != nil; a = next {
		next = a.Next
		a.Next = rattr
		rattr = a
	}
	return rattr
}

func (r *reader) newAttr() {
	equals := bytes.IndexByte(r.buf, '=')
	if equals < 0 {
		r.err = ErrAttribute
		return
	}
	str := string(r.buf)
	r.attr = &Attribute{
		Name: str[:equals],
		Next: r.attr,
	}
	r.attr.Value, r.err = unquoteAttribute(str[equals+1:])
}

// unquoteAttribute unquotes the attribute value, if necessary, and returns the result.
func unquoteAttribute(s string) (string, error) {
	if !strings.Contains(s, "'") {
		return s, nil
	}
	if len(s) < 2 || s[0] != quote || s[len(s)-1] != quote {
		return s, ErrQuote
	}
	s = s[1 : len(s)-1]
	b := make([]byte, 0, len(s))
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c == quote { // Must be doubled.
			if i == len(s)-1 || s[i+1] != quote {
				return s, ErrQuote
			}
			i++
		}
		b = append(b, c)
	}
	return string(b), nil
}
<|endoftext|>