<commit_before>// Copyright 2016 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package frankenstein

import (
	"fmt"
	"hash/fnv"
	"sync"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
	"github.com/prometheus/common/model"
	"golang.org/x/net/context"

	"github.com/prometheus/prometheus/storage/metric"
)

var (
	numClientsDesc = prometheus.NewDesc(
		"prometheus_distributor_ingester_clients",
		"The current number of ingester clients.",
		nil, nil,
	)
)

// Distributor is a storage.SampleAppender and a frankenstein.Querier which
// forwards appends and queries to individual ingesters.
type Distributor struct {
	ring       *Ring
	cfg        DistributorConfig
	clientsMtx sync.RWMutex
	clients    map[string]*IngesterClient

	quit chan struct{}
	done chan struct{}

	queryDuration   *prometheus.HistogramVec
	consulUpdates   prometheus.Counter
	receivedSamples prometheus.Counter
	sendDuration    *prometheus.HistogramVec
}

// DistributorConfig contains the configuration require to
// create a Distributor
type DistributorConfig struct {
	ConsulClient  ConsulClient
	ConsulPrefix  string
	ClientFactory func(string) (*IngesterClient, error)
}

// IngesterDesc is the serialised state in Consul representing
// an individual ingester.
type IngesterDesc struct {
	ID       string   `json:"ID"`
	Hostname string   `json:"hostname"`
	Tokens   []uint32 `json:"tokens"`
}

// NewDistributor constructs a new Distributor
func NewDistributor(cfg DistributorConfig) (*Distributor, error) {
	d := &Distributor{
		ring:    NewRing(),
		cfg:     cfg,
		clients: map[string]*IngesterClient{},
		quit:    make(chan struct{}),
		done:    make(chan struct{}),
		queryDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: "prometheus",
			Name:      "distributor_query_duration_seconds",
			Help:      "Time spent executing expression queries.",
			Buckets:   prometheus.DefBuckets,
		}, []string{"status_code"}),
		consulUpdates: prometheus.NewCounter(prometheus.CounterOpts{
			Namespace: "prometheus",
			Name:      "distributor_consul_updates_total",
			Help:      "The total number of received Consul updates.",
		}),
		receivedSamples: prometheus.NewCounter(prometheus.CounterOpts{
			Namespace: "prometheus",
			Name:      "distributor_received_samples_total",
			Help:      "The total number of received samples.",
		}),
		sendDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: "prometheus",
			Name:      "distributor_send_duration_seconds",
			Help:      "Time spent sending sample batches to ingesters.",
			Buckets:   prometheus.DefBuckets,
		}, []string{"status_code"}),
	}
	go d.loop()
	return d, nil
}

// Stop the distributor.
func (d *Distributor) Stop() {
	close(d.quit)
	<-d.done
}

func (d *Distributor) loop() {
	defer close(d.done)
	factory := func() interface{} { return &IngesterDesc{} }
	d.cfg.ConsulClient.WatchPrefix(d.cfg.ConsulPrefix, factory, d.quit, func(key string, value interface{}) bool {
		c := *value.(*IngesterDesc)
		log.Infof("Got update to ingester %d", c.ID)
		d.consulUpdates.Inc()
		d.ring.Update(c)
		return true
	})
}

func (d *Distributor) getClientFor(hostname string) (*IngesterClient, error) {
	d.clientsMtx.RLock()
	client, ok := d.clients[hostname]
	d.clientsMtx.RUnlock()
	if ok {
		return client, nil
	}

	d.clientsMtx.Lock()
	defer d.clientsMtx.Unlock()
	client, ok = d.clients[hostname]
	if ok {
		return client, nil
	}

	client, err := d.cfg.ClientFactory(hostname)
	if err != nil {
		return nil, err
	}
	d.clients[hostname] = client
	return client, nil
}

func tokenForMetric(metric model.Metric) uint32 {
	name := metric[model.MetricNameLabel]
	return tokenFor(name)
}

func tokenFor(name model.LabelValue) uint32 {
	h := fnv.New32()
	h.Write([]byte(name))
	return h.Sum32()
}

// Append implements SampleAppender.
func (d *Distributor) Append(ctx context.Context, samples []*model.Sample) error {
	d.receivedSamples.Add(float64(len(samples)))

	samplesByIngester := map[string][]*model.Sample{}
	for _, sample := range samples {
		key := tokenForMetric(sample.Metric)
		collector, err := d.ring.Get(key)
		if err != nil {
			return err
		}
		otherSamples := samplesByIngester[collector.Hostname]
		samplesByIngester[collector.Hostname] = append(otherSamples, sample)
	}

	errs := make(chan error)
	for hostname, samples := range samplesByIngester {
		go func(hostname string, samples []*model.Sample) {
			errs <- d.sendSamples(ctx, hostname, samples)
		}(hostname, samples)
	}
	var lastErr error
	for i := 0; i < len(samplesByIngester); i++ {
		if err := <-errs; err != nil {
			lastErr = err
		}
	}
	return lastErr
}

func (d *Distributor) sendSamples(ctx context.Context, hostname string, samples []*model.Sample) error {
	client, err := d.getClientFor(hostname)
	if err != nil {
		return err
	}
	return timeRequestHistogramStatus(d.sendDuration, nil, func() error {
		return client.Append(ctx, samples)
	})
}

func metricNameFromLabelMatchers(matchers ...*metric.LabelMatcher) (model.LabelValue, error) {
	for _, m := range matchers {
		if m.Name == model.MetricNameLabel {
			if m.Type != metric.Equal {
				return "", fmt.Errorf("non-equality matchers are not supported on the metric name")
			}
			return m.Value, nil
		}
	}
	return "", fmt.Errorf("no metric name matcher found")
}

// Query implements Querier.
func (d *Distributor) Query(ctx context.Context, from, to model.Time, matchers ...*metric.LabelMatcher) (model.Matrix, error) {
	var result model.Matrix
	err := timeRequestHistogramStatus(d.queryDuration, nil, func() error {
		metricName, err := metricNameFromLabelMatchers(matchers...)
		if err != nil {
			return err
		}

		collector, err := d.ring.Get(tokenFor(metricName))
		if err != nil {
			return err
		}

		client, err := d.getClientFor(collector.Hostname)
		if err != nil {
			return err
		}

		result, err = client.Query(ctx, from, to, matchers...)
		if err != nil {
			return err
		}
		return nil
	})
	return result, err
}

// LabelValuesForLabelName returns all of the label values that are associated with a given label name.
func (d *Distributor) LabelValuesForLabelName(ctx context.Context, labelName model.LabelName) (model.LabelValues, error) {
	valueSet := map[model.LabelValue]struct{}{}
	for _, c := range d.ring.GetAll() {
		client, err := d.getClientFor(c.Hostname)
		if err != nil {
			return nil, err
		}
		vals, err := client.LabelValuesForLabelName(ctx, labelName)
		if err != nil {
			return nil, err
		}
		for _, v := range vals {
			valueSet[v] = struct{}{}
		}
	}

	values := make(model.LabelValues, 0, len(valueSet))
	for v := range valueSet {
		values = append(values, v)
	}
	return values, nil
}

// NeedsThrottling implements SampleAppender.
func (*Distributor) NeedsThrottling(_ context.Context) bool {
	return false
}

// Describe implements prometheus.Collector.
func (d *Distributor) Describe(ch chan<- *prometheus.Desc) {
	d.queryDuration.Describe(ch)
	ch <- d.consulUpdates.Desc()
	ch <- d.receivedSamples.Desc()
	d.sendDuration.Describe(ch)
	d.ring.Describe(ch)
	ch <- numClientsDesc
}

// Collect implements prometheus.Collector.
func (d *Distributor) Collect(ch chan<- prometheus.Metric) {
	d.queryDuration.Collect(ch)
	ch <- d.consulUpdates
	ch <- d.receivedSamples
	d.sendDuration.Collect(ch)
	d.ring.Collect(ch)

	d.clientsMtx.RLock()
	defer d.clientsMtx.RUnlock()
	ch <- prometheus.MustNewConstMetric(
		numClientsDesc,
		prometheus.GaugeValue,
		float64(len(d.clients)),
	)
}
<commit_msg>Include the userid in the ring hash, hopefully to get better load balancing (#61)<commit_after>// Copyright 2016 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package frankenstein

import (
	"fmt"
	"hash/fnv"
	"sync"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
	"github.com/prometheus/common/model"
	"golang.org/x/net/context"

	"github.com/prometheus/prometheus/storage/metric"
)

var (
	numClientsDesc = prometheus.NewDesc(
		"prometheus_distributor_ingester_clients",
		"The current number of ingester clients.",
		nil, nil,
	)
)

// Distributor is a storage.SampleAppender and a frankenstein.Querier which
// forwards appends and queries to individual ingesters.
type Distributor struct {
	ring       *Ring
	cfg        DistributorConfig
	clientsMtx sync.RWMutex
	clients    map[string]*IngesterClient

	quit chan struct{}
	done chan struct{}

	queryDuration   *prometheus.HistogramVec
	consulUpdates   prometheus.Counter
	receivedSamples prometheus.Counter
	sendDuration    *prometheus.HistogramVec
}

// DistributorConfig contains the configuration require to
// create a Distributor
type DistributorConfig struct {
	ConsulClient  ConsulClient
	ConsulPrefix  string
	ClientFactory func(string) (*IngesterClient, error)
}

// IngesterDesc is the serialised state in Consul representing
// an individual ingester.
type IngesterDesc struct {
	ID       string   `json:"ID"`
	Hostname string   `json:"hostname"`
	Tokens   []uint32 `json:"tokens"`
}

// NewDistributor constructs a new Distributor
func NewDistributor(cfg DistributorConfig) (*Distributor, error) {
	d := &Distributor{
		ring:    NewRing(),
		cfg:     cfg,
		clients: map[string]*IngesterClient{},
		quit:    make(chan struct{}),
		done:    make(chan struct{}),
		queryDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: "prometheus",
			Name:      "distributor_query_duration_seconds",
			Help:      "Time spent executing expression queries.",
			Buckets:   prometheus.DefBuckets,
		}, []string{"status_code"}),
		consulUpdates: prometheus.NewCounter(prometheus.CounterOpts{
			Namespace: "prometheus",
			Name:      "distributor_consul_updates_total",
			Help:      "The total number of received Consul updates.",
		}),
		receivedSamples: prometheus.NewCounter(prometheus.CounterOpts{
			Namespace: "prometheus",
			Name:      "distributor_received_samples_total",
			Help:      "The total number of received samples.",
		}),
		sendDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: "prometheus",
			Name:      "distributor_send_duration_seconds",
			Help:      "Time spent sending sample batches to ingesters.",
			Buckets:   prometheus.DefBuckets,
		}, []string{"status_code"}),
	}
	go d.loop()
	return d, nil
}

// Stop the distributor.
func (d *Distributor) Stop() {
	close(d.quit)
	<-d.done
}

func (d *Distributor) loop() {
	defer close(d.done)
	factory := func() interface{} { return &IngesterDesc{} }
	d.cfg.ConsulClient.WatchPrefix(d.cfg.ConsulPrefix, factory, d.quit, func(key string, value interface{}) bool {
		c := *value.(*IngesterDesc)
		log.Infof("Got update to ingester %d", c.ID)
		d.consulUpdates.Inc()
		d.ring.Update(c)
		return true
	})
}

func (d *Distributor) getClientFor(hostname string) (*IngesterClient, error) {
	d.clientsMtx.RLock()
	client, ok := d.clients[hostname]
	d.clientsMtx.RUnlock()
	if ok {
		return client, nil
	}

	d.clientsMtx.Lock()
	defer d.clientsMtx.Unlock()
	client, ok = d.clients[hostname]
	if ok {
		return client, nil
	}

	client, err := d.cfg.ClientFactory(hostname)
	if err != nil {
		return nil, err
	}
	d.clients[hostname] = client
	return client, nil
}

func tokenForMetric(userID string, metric model.Metric) uint32 {
	name := metric[model.MetricNameLabel]
	return tokenFor(userID, name)
}

func tokenFor(userID string, name model.LabelValue) uint32 {
	h := fnv.New32()
	h.Write([]byte(userID))
	h.Write([]byte(name))
	return h.Sum32()
}

// Append implements SampleAppender.
func (d *Distributor) Append(ctx context.Context, samples []*model.Sample) error {
	userID, err := userID(ctx)
	if err != nil {
		return err
	}

	d.receivedSamples.Add(float64(len(samples)))

	samplesByIngester := map[string][]*model.Sample{}
	for _, sample := range samples {
		key := tokenForMetric(userID, sample.Metric)
		collector, err := d.ring.Get(key)
		if err != nil {
			return err
		}
		otherSamples := samplesByIngester[collector.Hostname]
		samplesByIngester[collector.Hostname] = append(otherSamples, sample)
	}

	errs := make(chan error)
	for hostname, samples := range samplesByIngester {
		go func(hostname string, samples []*model.Sample) {
			errs <- d.sendSamples(ctx, hostname, samples)
		}(hostname, samples)
	}
	var lastErr error
	for i := 0; i < len(samplesByIngester); i++ {
		if err := <-errs; err != nil {
			lastErr = err
		}
	}
	return lastErr
}

func (d *Distributor) sendSamples(ctx context.Context, hostname string, samples []*model.Sample) error {
	client, err := d.getClientFor(hostname)
	if err != nil {
		return err
	}
	return timeRequestHistogramStatus(d.sendDuration, nil, func() error {
		return client.Append(ctx, samples)
	})
}

func metricNameFromLabelMatchers(matchers ...*metric.LabelMatcher) (model.LabelValue, error) {
	for _, m := range matchers {
		if m.Name == model.MetricNameLabel {
			if m.Type != metric.Equal {
				return "", fmt.Errorf("non-equality matchers are not supported on the metric name")
			}
			return m.Value, nil
		}
	}
	return "", fmt.Errorf("no metric name matcher found")
}

// Query implements Querier.
func (d *Distributor) Query(ctx context.Context, from, to model.Time, matchers ...*metric.LabelMatcher) (model.Matrix, error) {
	var result model.Matrix
	err := timeRequestHistogramStatus(d.queryDuration, nil, func() error {
		metricName, err := metricNameFromLabelMatchers(matchers...)
		if err != nil {
			return err
		}

		userID, err := userID(ctx)
		if err != nil {
			return err
		}

		collector, err := d.ring.Get(tokenFor(userID, metricName))
		if err != nil {
			return err
		}

		client, err := d.getClientFor(collector.Hostname)
		if err != nil {
			return err
		}

		result, err = client.Query(ctx, from, to, matchers...)
		if err != nil {
			return err
		}
		return nil
	})
	return result, err
}

// LabelValuesForLabelName returns all of the label values that are associated with a given label name.
func (d *Distributor) LabelValuesForLabelName(ctx context.Context, labelName model.LabelName) (model.LabelValues, error) {
	valueSet := map[model.LabelValue]struct{}{}
	for _, c := range d.ring.GetAll() {
		client, err := d.getClientFor(c.Hostname)
		if err != nil {
			return nil, err
		}
		vals, err := client.LabelValuesForLabelName(ctx, labelName)
		if err != nil {
			return nil, err
		}
		for _, v := range vals {
			valueSet[v] = struct{}{}
		}
	}

	values := make(model.LabelValues, 0, len(valueSet))
	for v := range valueSet {
		values = append(values, v)
	}
	return values, nil
}

// NeedsThrottling implements SampleAppender.
func (*Distributor) NeedsThrottling(_ context.Context) bool {
	return false
}

// Describe implements prometheus.Collector.
func (d *Distributor) Describe(ch chan<- *prometheus.Desc) {
	d.queryDuration.Describe(ch)
	ch <- d.consulUpdates.Desc()
	ch <- d.receivedSamples.Desc()
	d.sendDuration.Describe(ch)
	d.ring.Describe(ch)
	ch <- numClientsDesc
}

// Collect implements prometheus.Collector.
func (d *Distributor) Collect(ch chan<- prometheus.Metric) {
	d.queryDuration.Collect(ch)
	ch <- d.consulUpdates
	ch <- d.receivedSamples
	d.sendDuration.Collect(ch)
	d.ring.Collect(ch)

	d.clientsMtx.RLock()
	defer d.clientsMtx.RUnlock()
	ch <- prometheus.MustNewConstMetric(
		numClientsDesc,
		prometheus.GaugeValue,
		float64(len(d.clients)),
	)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/jessevdk/go-flags"
	"github.com/jessevdk/go-operators/types"
	"go/ast"
	"go/build"
	"go/format"
	"go/parser"
	"go/token"
	"os"
	"path"
	"strings"
)

type TypeCheck struct {
	Path         string
	ImportPath   string
	Package      *types.Package
	FileSet      *token.FileSet
	ParseFiles   []string
	ProcessFiles []string
	FilesToAst   map[string]*ast.File
	Ast          []*ast.File
}

var typechecks = make(map[string]*TypeCheck)

func packageLocations(args []string) []string {
	if len(args) == 0 {
		dirname, err := os.Getwd()

		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to obtain current working directory\n")
			os.Exit(1)
		}

		return []string{dirname}
	}

	return args
}

func buildFiles(packageDir string) (parse []string, process []string, pkgname string) {
	ctx := build.Default

	ctx.BuildTags = []string{"operators"}

	p, err := ctx.ImportDir(packageDir, 0)

	if err != nil {
		fmt.Fprintf(os.Stderr, "Error while importing build: %s\n", err)
		os.Exit(1)
	}

	for _, f := range p.GoFiles {
		parse = append(parse, path.Join(packageDir, f))

		if strings.HasSuffix(f, ".op.go") {
			process = append(process, f)
		}
	}

	return parse, process, p.Name
}

func parseAST(files []string) (fs *token.FileSet, afs []*ast.File, afsmap map[string]*ast.File) {
	fs = token.NewFileSet()
	afs = make([]*ast.File, 0, len(files))
	afsmap = make(map[string]*ast.File)

	for _, f := range files {
		af, err := parser.ParseFile(fs, f, nil, 0)

		if err != nil {
			fmt.Fprintf(os.Stderr, "Error while parsing AST: %s\n", err)
			os.Exit(1)
		}

		afsmap[f] = af
		afs = append(afs, af)
	}

	return fs, afs, afsmap
}

func checkTypes(pkgpath string, importpath string) *TypeCheck {
	if ret, ok := typechecks[importpath]; ok {
		return ret
	}

	parse, process, pkgname := buildFiles(pkgpath)
	fs, afs, afsmap := parseAST(parse)

	var conf types.Config

	conf.Import = importSources

	pp, err := conf.Check(pkgname, fs, afs, nil)

	if err != nil {
		fmt.Fprintf(os.Stderr, "Error while type checking: %s\n", err)
		os.Exit(1)
	}

	ret := &TypeCheck{
		Path:         pkgpath,
		Package:      pp,
		ImportPath:   importpath,
		ParseFiles:   parse,
		ProcessFiles: process,
		FileSet:      fs,
		Ast:          afs,
		FilesToAst:   afsmap,
	}

	typechecks[importpath] = ret
	return ret
}

func resolvePackage(importpath string, tryLocal bool) *TypeCheck {
	if ret, ok := typechecks[importpath]; ok {
		return ret
	}

	// Try local first
	if tryLocal {
		if _, err := os.Stat(importpath); err == nil {
			return checkTypes(importpath, importpath)
		}
	}

	paths := strings.Split(os.Getenv("GOPATH"), ":")

	for _, p := range paths {
		src := path.Join(p, "src", importpath)

		if _, err := os.Stat(src); err != nil {
			continue
		}

		return checkTypes(src, importpath)
	}

	fmt.Fprintf(os.Stderr, "Could not find package %s.\n", importpath)
	os.Exit(1)

	return nil
}

func importSource(imports map[string]*types.Package, path string) (pkg *types.Package, err error) {
	// Try from source
	ct := resolvePackage(path, false)

	if ct == nil {
		return nil, fmt.Errorf("Could not locate import path %s", path)
	}

	imports[ct.ImportPath] = ct.Package
	return ct.Package, nil

}

func importSources(imports map[string]*types.Package, path string) (pkg *types.Package, err error) {
	if operatorPackages[path] {
		if pkg, ok := imports[path]; ok {
			return pkg, nil
		}

		return importSource(imports, path)
	}

	pkg, err = types.GcImport(imports, path)

	if err != nil {
		if path == "C" {
			return nil, fmt.Errorf("go-operators does not have support for packages that use cgo at the moment")
		}

		return importSource(imports, path)
	}

	return pkg, err
}

func replacer(overloads map[ast.Expr]types.OverloadInfo, node ast.Node) ast.Node {
	expr, ok := node.(ast.Expr)

	if !ok {
		return node
	}

	info, ok := overloads[expr]

	if !ok {
		return node
	}

	sel := &ast.SelectorExpr{
		X:   info.Recv,
		Sel: ast.NewIdent(info.Func.Name()),
	}

	args := []ast.Expr{}

	if info.Oper != nil {
		args = append(args, info.Oper)
	}

	// Create function call expression
	call := &ast.CallExpr{
		Fun:  sel,
		Args: args,
	}

	return call
}

func replaceOperators(ct *TypeCheck) {
	overloads := ct.Package.Overloads()

	for _, f := range ct.ProcessFiles {
		af := ct.FilesToAst[path.Join(ct.Path, f)]

		af = replace(func(node ast.Node) ast.Node {
			return replacer(overloads, node)
		}, af).(*ast.File)

		suffix := ".op.go"
		outname := f[:len(f)-len(suffix)] + ".go"

		fn := path.Join(ct.Path, outname)

		of, err := os.Create(fn)

		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to create output file: %s\n", err)
			os.Exit(1)
		}

		defer of.Close()

		if opts.Verbose {
			fmt.Println(fn)
		}

		// Write build constraint
		fmt.Fprintln(of, "// +build !operators\n")

		if err := format.Node(of, ct.FileSet, af); err != nil {
			fmt.Fprintf(os.Stderr, "Failed to write code: %s\n", err)
			os.Exit(1)
		}
	}
}

var opts struct {
	Verbose bool `short:"v" long:"verbose" description:"Enable verbose mode"`
}

var operatorPackages = make(map[string]bool)

func main() {
	fp := flags.NewParser(&opts, flags.Default)

	args, err := fp.Parse()

	if err != nil {
		os.Exit(1)
	}

	packageDirs := packageLocations(args)

	for _, p := range packageDirs {
		operatorPackages[p] = true
	}

	for _, p := range packageDirs {
		ct := resolvePackage(p, true)
		replaceOperators(ct)
	}
}
<commit_msg>Do not import soure twice<commit_after>package main

import (
	"fmt"
	"github.com/jessevdk/go-flags"
	"github.com/jessevdk/go-operators/types"
	"go/ast"
	"go/build"
	"go/format"
	"go/parser"
	"go/token"
	"os"
	"path"
	"strings"
)

type TypeCheck struct {
	Path         string
	ImportPath   string
	Package      *types.Package
	FileSet      *token.FileSet
	ParseFiles   []string
	ProcessFiles []string
	FilesToAst   map[string]*ast.File
	Ast          []*ast.File
}

var typechecks = make(map[string]*TypeCheck)

func packageLocations(args []string) []string {
	if len(args) == 0 {
		dirname, err := os.Getwd()

		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to obtain current working directory\n")
			os.Exit(1)
		}

		return []string{dirname}
	}

	return args
}

func buildFiles(packageDir string) (parse []string, process []string, pkgname string) {
	ctx := build.Default

	ctx.BuildTags = []string{"operators"}

	p, err := ctx.ImportDir(packageDir, 0)

	if err != nil {
		fmt.Fprintf(os.Stderr, "Error while importing build: %s\n", err)
		os.Exit(1)
	}

	for _, f := range p.GoFiles {
		parse = append(parse, path.Join(packageDir, f))

		if strings.HasSuffix(f, ".op.go") {
			process = append(process, f)
		}
	}

	return parse, process, p.Name
}

func parseAST(files []string) (fs *token.FileSet, afs []*ast.File, afsmap map[string]*ast.File) {
	fs = token.NewFileSet()
	afs = make([]*ast.File, 0, len(files))
	afsmap = make(map[string]*ast.File)

	for _, f := range files {
		af, err := parser.ParseFile(fs, f, nil, 0)

		if err != nil {
			fmt.Fprintf(os.Stderr, "Error while parsing AST: %s\n", err)
			os.Exit(1)
		}

		afsmap[f] = af
		afs = append(afs, af)
	}

	return fs, afs, afsmap
}

func checkTypes(pkgpath string, importpath string) *TypeCheck {
	if ret, ok := typechecks[importpath]; ok {
		return ret
	}

	parse, process, pkgname := buildFiles(pkgpath)
	fs, afs, afsmap := parseAST(parse)

	var conf types.Config

	conf.Import = importSources

	pp, err := conf.Check(pkgname, fs, afs, nil)

	if err != nil {
		fmt.Fprintf(os.Stderr, "Error while type checking: %s\n", err)
		os.Exit(1)
	}

	ret := &TypeCheck{
		Path:         pkgpath,
		Package:      pp,
		ImportPath:   importpath,
		ParseFiles:   parse,
		ProcessFiles: process,
		FileSet:      fs,
		Ast:          afs,
		FilesToAst:   afsmap,
	}

	typechecks[importpath] = ret
	return ret
}

func resolvePackage(importpath string, tryLocal bool) *TypeCheck {
	if ret, ok := typechecks[importpath]; ok {
		return ret
	}

	// Try local first
	if tryLocal {
		if _, err := os.Stat(importpath); err == nil {
			return checkTypes(importpath, importpath)
		}
	}

	paths := strings.Split(os.Getenv("GOPATH"), ":")

	for _, p := range paths {
		src := path.Join(p, "src", importpath)

		if _, err := os.Stat(src); err != nil {
			continue
		}

		return checkTypes(src, importpath)
	}

	fmt.Fprintf(os.Stderr, "Could not find package %s.\n", importpath)
	os.Exit(1)

	return nil
}

func importSource(imports map[string]*types.Package, path string) (pkg *types.Package, err error) {
	if pkg, ok := imports[path]; ok {
		return pkg, nil
	}

	// Try from source
	ct := resolvePackage(path, false)

	if ct == nil {
		return nil, fmt.Errorf("Could not locate import path %s", path)
	}

	imports[ct.ImportPath] = ct.Package
	return ct.Package, nil

}

func importSources(imports map[string]*types.Package, path string) (pkg *types.Package, err error) {
	if operatorPackages[path] {
		return importSource(imports, path)
	}

	pkg, err = types.GcImport(imports, path)

	if err != nil {
		if path == "C" {
			return nil, fmt.Errorf("go-operators does not have support for packages that use cgo at the moment")
		}

		return importSource(imports, path)
	}

	return pkg, err
}

func replacer(overloads map[ast.Expr]types.OverloadInfo, node ast.Node) ast.Node {
	expr, ok := node.(ast.Expr)

	if !ok {
		return node
	}

	info, ok := overloads[expr]

	if !ok {
		return node
	}

	sel := &ast.SelectorExpr{
		X:   info.Recv,
		Sel: ast.NewIdent(info.Func.Name()),
	}

	args := []ast.Expr{}

	if info.Oper != nil {
		args = append(args, info.Oper)
	}

	// Create function call expression
	call := &ast.CallExpr{
		Fun:  sel,
		Args: args,
	}

	return call
}

func replaceOperators(ct *TypeCheck) {
	overloads := ct.Package.Overloads()

	for _, f := range ct.ProcessFiles {
		af := ct.FilesToAst[path.Join(ct.Path, f)]

		af = replace(func(node ast.Node) ast.Node {
			return replacer(overloads, node)
		}, af).(*ast.File)

		suffix := ".op.go"
		outname := f[:len(f)-len(suffix)] + ".go"

		fn := path.Join(ct.Path, outname)

		of, err := os.Create(fn)

		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to create output file: %s\n", err)
			os.Exit(1)
		}

		defer of.Close()

		if opts.Verbose {
			fmt.Println(fn)
		}

		// Write build constraint
		fmt.Fprintln(of, "// +build !operators\n")

		if err := format.Node(of, ct.FileSet, af); err != nil {
			fmt.Fprintf(os.Stderr, "Failed to write code: %s\n", err)
			os.Exit(1)
		}
	}
}

var opts struct {
	Verbose bool `short:"v" long:"verbose" description:"Enable verbose mode"`
}

var operatorPackages = make(map[string]bool)

func main() {
	fp := flags.NewParser(&opts, flags.Default)

	args, err := fp.Parse()

	if err != nil {
		os.Exit(1)
	}

	packageDirs := packageLocations(args)

	for _, p := range packageDirs {
		operatorPackages[p] = true
	}

	for _, p := range packageDirs {
		ct := resolvePackage(p, true)
		replaceOperators(ct)
	}
}
<|endoftext|>


<commit_before>package config

import (
	"fmt"

	"github.com/hashicorp/hcl"
	"github.com/hashicorp/hcl/hcl/ast"
)

type ProxyEndpoint struct {
	XMLName             string               `xml:"ProxyEndpoint" hcl:",-"`
	Name                string               `xml:",attr" hcl:",-"`
	PreFlow             *PreFlow             `hcl:"pre_flow"`
	Flows               []*Flow              `xml:"Flows", hcl:"flows"`
	PostFlow            *PostFlow            `hcl:"post_flow"`
	HTTPProxyConnection *HTTPProxyConnection `hcl:"http_proxy_connection"`
	RouteRules          []*RouteRule         `xml:"RouteRule" hcl:"route_rule"`
}

type HTTPProxyConnection struct {
	XMLName     string   `xml:"HTTPProxyConnection", hcl:",-"`
	BasePath    string   `hcl:"base_path"`
	VirtualHost []string `xml:",innerxml" hcl:"virtual_host"`
}

type RouteRule struct {
	XMLName        string `xml:"RouteRule"`
	Name           string `xml:",attr", hcl:",-"`
	Condition      string `xml:",omitempty" hcl:"condition"`
	TargetEndpoint string `xml:",omitempty" hcl:"target_endpoint"`
	URL            string `xml:",omitempty" hcl:"url"`
}

func loadProxyEndpointsHCL(list *ast.ObjectList) ([]*ProxyEndpoint, error) {
	var result []*ProxyEndpoint
	for _, item := range list.Items {
		n := item.Keys[0].Token.Value().(string)

		var listVal *ast.ObjectList
		if ot, ok := item.Val.(*ast.ObjectType); ok {
			listVal = ot.List
		} else {
			return nil, fmt.Errorf("proxy endpoint not an object")
		}

		var proxyEndpoint ProxyEndpoint

		if err := hcl.DecodeObject(&proxyEndpoint, item.Val); err != nil {
			return nil, err
		}

		proxyEndpoint.Name = n

		if preFlow := listVal.Filter("pre_flow"); len(preFlow.Items) > 0 {
			preFlow, err := loadPreFlowHCL(preFlow)
			if err != nil {
				return nil, err
			}

			proxyEndpoint.PreFlow = preFlow
		}

		if flows := listVal.Filter("flow"); len(flows.Items) > 0 {
			flows, err := loadFlowsHCL(flows)
			if err != nil {
				return nil, err
			}

			proxyEndpoint.Flows = flows
		}

		if postFlow := listVal.Filter("post_flow"); len(postFlow.Items) > 0 {
			postFlow, err := loadPostFlowHCL(postFlow)
			if err != nil {
				return nil, err
			}

			proxyEndpoint.PostFlow = postFlow
		}

		if routeRules := listVal.Filter("route_rule"); len(routeRules.Items) > 0 {
			routeRules, err := loadProxyEndpointRouteRulesHCL(routeRules)
			if err != nil {
				return nil, err
			}

			proxyEndpoint.RouteRules = routeRules
		}

		result = append(result, &proxyEndpoint)
	}

	return result, nil
}

func loadProxyEndpointRouteRulesHCL(list *ast.ObjectList) ([]*RouteRule, error) {
	var result []*RouteRule

	for _, item := range list.Items {
		var rule RouteRule
		if err := hcl.DecodeObject(&rule, item.Val); err != nil {
			return nil, fmt.Errorf("error decoding route rule object")
		}
		rule.Name = item.Keys[0].Token.Value().(string)

		result = append(result, &rule)
	}

	return result, nil
}
<commit_msg>Nesting Flow inside Flows.<commit_after>package config

import (
	"fmt"

	"github.com/hashicorp/hcl"
	"github.com/hashicorp/hcl/hcl/ast"
)

type ProxyEndpoint struct {
	XMLName             string               `xml:"ProxyEndpoint" hcl:",-"`
	Name                string               `xml:",attr" hcl:",-"`
	PreFlow             *PreFlow             `hcl:"pre_flow"`
	Flows               []*Flow              `xml:"Flows>Flow", hcl:"flows"`
	PostFlow            *PostFlow            `hcl:"post_flow"`
	HTTPProxyConnection *HTTPProxyConnection `hcl:"http_proxy_connection"`
	RouteRules          []*RouteRule         `xml:"RouteRule" hcl:"route_rule"`
}

type HTTPProxyConnection struct {
	XMLName     string   `xml:"HTTPProxyConnection", hcl:",-"`
	BasePath    string   `hcl:"base_path"`
	VirtualHost []string `xml:",innerxml" hcl:"virtual_host"`
}

type RouteRule struct {
	XMLName        string `xml:"RouteRule"`
	Name           string `xml:",attr", hcl:",-"`
	Condition      string `xml:",omitempty" hcl:"condition"`
	TargetEndpoint string `xml:",omitempty" hcl:"target_endpoint"`
	URL            string `xml:",omitempty" hcl:"url"`
}

func loadProxyEndpointsHCL(list *ast.ObjectList) ([]*ProxyEndpoint, error) {
	var result []*ProxyEndpoint
	for _, item := range list.Items {
		n := item.Keys[0].Token.Value().(string)

		var listVal *ast.ObjectList
		if ot, ok := item.Val.(*ast.ObjectType); ok {
			listVal = ot.List
		} else {
			return nil, fmt.Errorf("proxy endpoint not an object")
		}

		var proxyEndpoint ProxyEndpoint

		if err := hcl.DecodeObject(&proxyEndpoint, item.Val); err != nil {
			return nil, err
		}

		proxyEndpoint.Name = n

		if preFlow := listVal.Filter("pre_flow"); len(preFlow.Items) > 0 {
			preFlow, err := loadPreFlowHCL(preFlow)
			if err != nil {
				return nil, err
			}

			proxyEndpoint.PreFlow = preFlow
		}

		if flows := listVal.Filter("flow"); len(flows.Items) > 0 {
			flows, err := loadFlowsHCL(flows)
			if err != nil {
				return nil, err
			}

			proxyEndpoint.Flows = flows
		}

		if postFlow := listVal.Filter("post_flow"); len(postFlow.Items) > 0 {
			postFlow, err := loadPostFlowHCL(postFlow)
			if err != nil {
				return nil, err
			}

			proxyEndpoint.PostFlow = postFlow
		}

		if routeRules := listVal.Filter("route_rule"); len(routeRules.Items) > 0 {
			routeRules, err := loadProxyEndpointRouteRulesHCL(routeRules)
			if err != nil {
				return nil, err
			}

			proxyEndpoint.RouteRules = routeRules
		}

		result = append(result, &proxyEndpoint)
	}

	return result, nil
}

func loadProxyEndpointRouteRulesHCL(list *ast.ObjectList) ([]*RouteRule, error) {
	var result []*RouteRule

	for _, item := range list.Items {
		var rule RouteRule
		if err := hcl.DecodeObject(&rule, item.Val); err != nil {
			return nil, fmt.Errorf("error decoding route rule object")
		}
		rule.Name = item.Keys[0].Token.Value().(string)

		result = append(result, &rule)
	}

	return result, nil
}
<|endoftext|>


<commit_before>//
// go-sonos
// ========
//
// Copyright (c) 2012, Ian T. Richards <ianr@panix.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in the
//     documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

// cscl := (c)ontrol (s)onos from the (c)ommand (l)ine

package main

import (
	"flag"
	"fmt"
	"github.com/ianr0bkny/go-sonos"
	"github.com/ianr0bkny/go-sonos/config"
	"log"
	"os"
	"path"
)

var CONFIG *config.Config

func initConfig(dir string) {
	if "" == dir {
		dir = path.Join(os.Getenv("HOME"), ".go-sonos")
	}
	CONFIG = config.MakeConfig(dir)
	CONFIG.Init()
}

func cleanup() {
	CONFIG.Save()
}

func discover(args *Args) {
	if mgr, err := sonos.Discover(*args.discoveryDevice, fmt.Sprintf("%d", *args.discoveryPort)); nil != err {
		panic(err)
	} else {
		log.Printf("%#v", mgr)
	}
}

type Args struct {
	alias           *string
	help, usage     *bool
	configDir       *string
	discoveryDevice *string
	discoveryPort   *int
}

func Usage() {
	fmt.Fprintf(os.Stderr, "Usage of %s [args] command:\n", os.Args[0])
	flag.PrintDefaults()
}

func main() {
	log.SetFlags(log.Ltime | log.Lshortfile)

	args := Args{}
	args.alias = flag.String("S", "", "device alias name")
	args.configDir = flag.String("C", "", "go-sonos configuration directory")
	args.discoveryDevice = flag.String("D", "eth0", "discovery device")
	args.discoveryPort = flag.Int("P", 13104, "discovery response port")
	args.help = flag.Bool("help", false, "show the usage message")
	args.usage = flag.Bool("usage", false, "show the usage message")
	flag.Usage = Usage
	flag.Parse()

	if 0 == len(flag.Args()) {
		flag.Usage()
		os.Exit(0)
	}

	initConfig(*args.configDir)

	for _, cmd := range flag.Args() {
		switch cmd {
		case "discover":
			discover(&args)
		default:
			flag.Usage()
		}
		break
	}

	cleanup()
}
<commit_msg>Usage message<commit_after>//
// go-sonos
// ========
//
// Copyright (c) 2012, Ian T. Richards <ianr@panix.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in the
//     documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

// cscl := (c)ontrol (s)onos from the (c)ommand (l)ine

package main

import (
	"flag"
	"fmt"
	"github.com/ianr0bkny/go-sonos"
	"github.com/ianr0bkny/go-sonos/config"
	"log"
	"os"
	"path"
)

var CONFIG *config.Config

func initConfig(dir string) {
	if "" == dir {
		dir = path.Join(os.Getenv("HOME"), ".go-sonos")
	}
	CONFIG = config.MakeConfig(dir)
	CONFIG.Init()
}

func cleanup() {
	CONFIG.Save()
}

func discover(args *Args) {
	if mgr, err := sonos.Discover(*args.discoveryDevice, fmt.Sprintf("%d", *args.discoveryPort)); nil != err {
		panic(err)
	} else {
		log.Printf("%#v", mgr)
	}
}

type Args struct {
	alias           *string
	help, usage     *bool
	configDir       *string
	discoveryDevice *string
	discoveryPort   *int
}

func Usage() {
	fmt.Fprintf(os.Stderr, "Usage: %s [args] command\n", os.Args[0])
	flag.PrintDefaults()
}

func main() {
	log.SetFlags(log.Ltime | log.Lshortfile)

	args := Args{}
	args.alias = flag.String("S", "", "device alias name")
	args.configDir = flag.String("C", "", "go-sonos configuration directory")
	args.discoveryDevice = flag.String("D", "eth0", "discovery device")
	args.discoveryPort = flag.Int("P", 13104, "discovery response port")
	args.help = flag.Bool("help", false, "show the usage message")
	args.usage = flag.Bool("usage", false, "show the usage message")
	flag.Usage = Usage
	flag.Parse()

	if 0 == len(flag.Args()) {
		flag.Usage()
		os.Exit(0)
	}

	initConfig(*args.configDir)

	for _, cmd := range flag.Args() {
		switch cmd {
		case "discover":
			discover(&args)
		default:
			flag.Usage()
		}
		break
	}

	cleanup()
}
<|endoftext|>


<commit_before>package console

import (
	"fmt"
	"io"
	"strconv"
	"strings"
	"time"
	"util/human"
)

type ProgressBarWriter struct {
	total  int64
	w      io.Writer
	done   int64
	start  time.Time
	prefix string
}

func (self *ProgressBarWriter) Write(b []byte) (n int, err error) {
	if self.done == 0 {
		self.start = time.Now()
	}
	switch {
	case self.total > 0:
		self.done = self.done + int64(len(b))
		percent := int((self.done * 100) / self.total)
		width := (80 - 9) - 32
		progress := (width * percent) / 100
		bar := strings.Repeat("#", int(progress))
		bps := float64(self.done) / time.Now().Sub(self.start).Seconds()
		fmt.Printf("\r%-20.20s [%-*s] %s/s %3.3s%%", self.prefix, width, bar, human.ByteSize(bps), strconv.Itoa(percent))
	default:
		fmt.Printf("\r%-20.20s", self.prefix)
	}
	return self.w.Write(b)
}

func (self *ProgressBarWriter) Close() error {
	fmt.Println()
	return nil
}
func NewProgressBarWriter(p string, t int64, w io.Writer) *ProgressBarWriter {
	return &ProgressBarWriter{prefix: p, total: t, w: w}
}
<commit_msg>make go get friendly<commit_after>package console

import (
	"fmt"
	"io"
	"strconv"
	"strings"
	"time"
	"github.com/str1ngs/util/human"
)

type ProgressBarWriter struct {
	total  int64
	w      io.Writer
	done   int64
	start  time.Time
	prefix string
}

func (self *ProgressBarWriter) Write(b []byte) (n int, err error) {
	if self.done == 0 {
		self.start = time.Now()
	}
	switch {
	case self.total > 0:
		self.done = self.done + int64(len(b))
		percent := int((self.done * 100) / self.total)
		width := (80 - 9) - 32
		progress := (width * percent) / 100
		bar := strings.Repeat("#", int(progress))
		bps := float64(self.done) / time.Now().Sub(self.start).Seconds()
		fmt.Printf("\r%-20.20s [%-*s] %s/s %3.3s%%", self.prefix, width, bar, human.ByteSize(bps), strconv.Itoa(percent))
	default:
		fmt.Printf("\r%-20.20s", self.prefix)
	}
	return self.w.Write(b)
}

func (self *ProgressBarWriter) Close() error {
	fmt.Println()
	return nil
}
func NewProgressBarWriter(p string, t int64, w io.Writer) *ProgressBarWriter {
	return &ProgressBarWriter{prefix: p, total: t, w: w}
}
<|endoftext|>


<commit_before>/*
 *
 * Copyright 2020 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package tests

import (
	"testing"

	"google.golang.org/grpc"
	"google.golang.org/grpc/internal/grpctest"
	xdsclient "google.golang.org/grpc/xds/internal/client"
	"google.golang.org/grpc/xds/internal/client/bootstrap"
	_ "google.golang.org/grpc/xds/internal/client/v2" // Register the v2 API client.
	"google.golang.org/grpc/xds/internal/testutils"
	"google.golang.org/grpc/xds/internal/version"
)

type s struct {
	grpctest.Tester
}

func Test(t *testing.T) {
	grpctest.RunSubTests(t, s{})
}

const (
	testXDSServer = "xds-server"
)

func clientOpts(balancerName string) xdsclient.Options {
	return xdsclient.Options{
		Config: bootstrap.Config{
			BalancerName: balancerName,
			Creds:        grpc.WithInsecure(),
			NodeProto:    testutils.EmptyNodeProtoV2,
		},
	}
}

func (s) TestNew(t *testing.T) {
	tests := []struct {
		name    string
		opts    xdsclient.Options
		wantErr bool
	}{
		{name: "empty-opts", opts: xdsclient.Options{}, wantErr: true},
		{
			name: "empty-balancer-name",
			opts: xdsclient.Options{
				Config: bootstrap.Config{
					Creds:     grpc.WithInsecure(),
					NodeProto: testutils.EmptyNodeProtoV2,
				},
			},
			wantErr: true,
		},
		{
			name: "empty-dial-creds",
			opts: xdsclient.Options{
				Config: bootstrap.Config{
					BalancerName: testXDSServer,
					NodeProto:    testutils.EmptyNodeProtoV2,
				},
			},
			wantErr: true,
		},
		{
			name: "empty-node-proto",
			opts: xdsclient.Options{
				Config: bootstrap.Config{
					BalancerName: testXDSServer,
					Creds:        grpc.WithInsecure(),
				},
			},
			wantErr: true,
		},
		{
			name: "node-proto-version-mismatch",
			opts: xdsclient.Options{
				Config: bootstrap.Config{
					BalancerName: testXDSServer,
					Creds:        grpc.WithInsecure(),
					NodeProto:    testutils.EmptyNodeProtoV3,
					TransportAPI: version.TransportV2,
				},
			},
			wantErr: true,
		},
		// TODO(easwars): Add cases for v3 API client.
		{
			name: "happy-case",
			opts: clientOpts(testXDSServer),
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			c, err := xdsclient.New(test.opts)
			if (err != nil) != test.wantErr {
				t.Fatalf("New(%+v) = %v, wantErr: %v", test.opts, err, test.wantErr)
			}
			if c != nil {
				c.Close()
			}
		})
	}
}
<commit_msg>internal: rename package for test alone file (#3855)<commit_after>/*
 *
 * Copyright 2020 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package tests_test

import (
	"testing"

	"google.golang.org/grpc"
	"google.golang.org/grpc/internal/grpctest"
	xdsclient "google.golang.org/grpc/xds/internal/client"
	"google.golang.org/grpc/xds/internal/client/bootstrap"
	_ "google.golang.org/grpc/xds/internal/client/v2" // Register the v2 API client.
	"google.golang.org/grpc/xds/internal/testutils"
	"google.golang.org/grpc/xds/internal/version"
)

type s struct {
	grpctest.Tester
}

func Test(t *testing.T) {
	grpctest.RunSubTests(t, s{})
}

const (
	testXDSServer = "xds-server"
)

func clientOpts(balancerName string) xdsclient.Options {
	return xdsclient.Options{
		Config: bootstrap.Config{
			BalancerName: balancerName,
			Creds:        grpc.WithInsecure(),
			NodeProto:    testutils.EmptyNodeProtoV2,
		},
	}
}

func (s) TestNew(t *testing.T) {
	tests := []struct {
		name    string
		opts    xdsclient.Options
		wantErr bool
	}{
		{name: "empty-opts", opts: xdsclient.Options{}, wantErr: true},
		{
			name: "empty-balancer-name",
			opts: xdsclient.Options{
				Config: bootstrap.Config{
					Creds:     grpc.WithInsecure(),
					NodeProto: testutils.EmptyNodeProtoV2,
				},
			},
			wantErr: true,
		},
		{
			name: "empty-dial-creds",
			opts: xdsclient.Options{
				Config: bootstrap.Config{
					BalancerName: testXDSServer,
					NodeProto:    testutils.EmptyNodeProtoV2,
				},
			},
			wantErr: true,
		},
		{
			name: "empty-node-proto",
			opts: xdsclient.Options{
				Config: bootstrap.Config{
					BalancerName: testXDSServer,
					Creds:        grpc.WithInsecure(),
				},
			},
			wantErr: true,
		},
		{
			name: "node-proto-version-mismatch",
			opts: xdsclient.Options{
				Config: bootstrap.Config{
					BalancerName: testXDSServer,
					Creds:        grpc.WithInsecure(),
					NodeProto:    testutils.EmptyNodeProtoV3,
					TransportAPI: version.TransportV2,
				},
			},
			wantErr: true,
		},
		// TODO(easwars): Add cases for v3 API client.
		{
			name: "happy-case",
			opts: clientOpts(testXDSServer),
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			c, err := xdsclient.New(test.opts)
			if (err != nil) != test.wantErr {
				t.Fatalf("New(%+v) = %v, wantErr: %v", test.opts, err, test.wantErr)
			}
			if c != nil {
				c.Close()
			}
		})
	}
}
<|endoftext|>


<commit_before>package control

import (
	"crypto/rsa"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/intelsdilabs/gomit"

	"github.com/intelsdilabs/pulse/control/plugin"
	"github.com/intelsdilabs/pulse/control/plugin/client"
	"github.com/intelsdilabs/pulse/control/plugin/cpolicy"
	"github.com/intelsdilabs/pulse/control/routing"
	"github.com/intelsdilabs/pulse/core"
	"github.com/intelsdilabs/pulse/core/cdata"
	"github.com/intelsdilabs/pulse/core/control_event"
	"github.com/intelsdilabs/pulse/pkg/logger"
)

// control private key (RSA private key)
// control public key (RSA public key)
// Plugin token = token generated by plugin and passed to control
// Session token = plugin seed encrypted by control private key, verified by plugin using control public key
//

type executablePlugins []plugin.ExecutablePlugin

type pluginControl struct {
	// TODO, going to need coordination on changing of these
	RunningPlugins executablePlugins
	Started        bool

	controlPrivKey *rsa.PrivateKey
	controlPubKey  *rsa.PublicKey
	eventManager   *gomit.EventController

	pluginManager managesPlugins
	metricCatalog catalogsMetrics
	pluginRunner  runsPlugins

	strategy RoutingStrategy
}

type runsPlugins interface {
	Start() error
	Stop() []error
	AvailablePlugins() *availablePlugins
	AddDelegates(delegates ...gomit.Delegator)
	SetMetricCatalog(c catalogsMetrics)
	SetPluginManager(m managesPlugins)
	Monitor() *monitor
}

type managesPlugins interface {
	LoadPlugin(string) (*loadedPlugin, error)
	UnloadPlugin(CatalogedPlugin) error
	LoadedPlugins() *loadedPlugins
	SetMetricCatalog(catalogsMetrics)
	GenerateArgs() plugin.Arg
}

type catalogsMetrics interface {
	Get([]string, int) (*metricType, error)
	Add(*metricType)
	AddLoadedMetricType(*loadedPlugin, core.MetricType)
	Item() (string, []*metricType)
	Next() bool
	Subscribe([]string, int) error
	Unsubscribe([]string, int) error
	Table() map[string][]*metricType
	GetPlugin([]string, int) (*loadedPlugin, error)
}

// Returns a new pluginControl instance
func New() *pluginControl {

	c := &pluginControl{}
	// Initialize components
	//
	// Event Manager
	c.eventManager = gomit.NewEventController()
	logger.Debug("control.init", "event controller created")

	// Metric Catalog
	c.metricCatalog = newMetricCatalog()
	logger.Debug("control.init", "metric catalog created")

	// Plugin Manager
	c.pluginManager = newPluginManager()
	logger.Debug("control.init", "plugin manager created")
	//    Plugin Manager needs a reference to the metric catalog
	c.pluginManager.SetMetricCatalog(c.metricCatalog)

	// Plugin Runner
	c.pluginRunner = newRunner()
	logger.Debug("control.init", "runner created")
	c.pluginRunner.AddDelegates(c.eventManager)
	c.pluginRunner.SetMetricCatalog(c.metricCatalog)
	c.pluginRunner.SetPluginManager(c.pluginManager)

	// Strategy
	c.strategy = &routing.RoundRobinStrategy{}

	// Wire event manager

	// Start stuff
	err := c.pluginRunner.Start()
	if err != nil {
		panic(err)
	}

	return c
}

// Begin handling load, unload, and inventory
func (p *pluginControl) Start() error {
	// Start pluginManager when pluginControl starts
	p.Started = true
	logger.Debug("control.start", "started")
	return nil
}

func (p *pluginControl) Stop() {
	p.Started = false
	logger.Debug("control.stop", "stopped")
}

// Load is the public method to load a plugin into
// the LoadedPlugins array and issue an event when
// successful.
func (p *pluginControl) Load(path string) error {
	// logger.Debug("control.load", fmt.Sprintf("load called on path: %s", path))
	if !p.Started {
		return errors.New("Must start Controller before calling Load()")
	}

	if _, err := p.pluginManager.LoadPlugin(path); err != nil {
		return err
	}

	// defer sending event
	event := new(control_event.LoadPluginEvent)
	defer p.eventManager.Emit(event)
	return nil
}

func (p *pluginControl) Unload(pl CatalogedPlugin) error {
	err := p.pluginManager.UnloadPlugin(pl)
	if err != nil {
		return err
	}

	event := new(control_event.UnloadPluginEvent)
	defer p.eventManager.Emit(event)
	return nil
}

func (p *pluginControl) SwapPlugins(inPath string, out CatalogedPlugin) error {

	lp, err := p.pluginManager.LoadPlugin(inPath)
	if err != nil {
		return err
	}

	err = p.pluginManager.UnloadPlugin(out)
	if err != nil {
		err2 := p.pluginManager.UnloadPlugin(lp)
		if err2 != nil {
			return errors.New("failed to rollback after error" + err2.Error() + " -- " + err.Error())
		}
		return err
	}

	event := new(control_event.SwapPluginsEvent)
	defer p.eventManager.Emit(event)

	return nil
}

func (p *pluginControl) generateArgs() plugin.Arg {
	a := plugin.Arg{
		ControlPubKey: p.controlPubKey,
		PluginLogPath: "/tmp/pulse-test-plugin.log",
	}
	return a
}

// SubscribeMetricType validates the given config data, and if valid
// returns a MetricType with a config.  On error a collection of errors is returned
// either from config data processing, or the inability to find the metric.
func (p *pluginControl) SubscribeMetricType(mt core.MetricType, cd *cdata.ConfigDataNode) (core.MetricType, []error) {
	logger.Info("control.subscribe", fmt.Sprintf("subscription called with: %s", mt.Namespace()))
	subErrs := make([]error, 0)

	m, err := p.metricCatalog.Get(mt.Namespace(), mt.Version())
	if err != nil {
		subErrs = append(subErrs, err)
		return nil, subErrs
	}

	// No metric found return error.
	if m == nil {
		subErrs = append(subErrs, errors.New(fmt.Sprintf("no metric found cannot subscribe: (%s) version(%d)", mt.Namespace(), mt.Version())))
		return nil, subErrs
	}

	if m.policy == nil {
		m.policy = cpolicy.NewPolicyNode()
	}
	ncdTable, errs := m.policy.Process(cd.Table())
	if errs != nil && errs.HasErrors() {
		return nil, errs.Errors()
	}
	m.config = cdata.FromTable(*ncdTable)

	m.Subscribe()
	e := &control_event.MetricSubscriptionEvent{
		MetricNamespace: m.Namespace(),
		Version:         m.Version(),
	}
	defer p.eventManager.Emit(e)

	return m, nil
}

// UnsubscribeMetricType unsubscribes a MetricType
// If subscriptions fall below zero we will panic.
func (p *pluginControl) UnsubscribeMetricType(mt core.MetricType) {
	logger.Info("control.subscribe", fmt.Sprintf("unsubscription called with: %s", mt.Namespace()))
	err := p.metricCatalog.Unsubscribe(mt.Namespace(), mt.Version())
	if err != nil {
		// panic because if a metric falls below 0, something bad has happened
		panic(err.Error())
	}
	e := &control_event.MetricUnsubscriptionEvent{
		MetricNamespace: mt.Namespace(),
	}
	p.eventManager.Emit(e)
}

// SetMonitorOptions exposes monitors options
func (p *pluginControl) SetMonitorOptions(options ...monitorOption) {
	p.pluginRunner.Monitor().Option(options...)
}

// the public interface for a plugin
// this should be the contract for
// how mgmt modules know a plugin
type CatalogedPlugin interface {
	Name() string
	Version() int
	TypeName() string
	Status() string
	LoadedTimestamp() int64
}

// the collection of cataloged plugins used
// by mgmt modules
type PluginCatalog []CatalogedPlugin

// returns a copy of the plugin catalog
func (p *pluginControl) PluginCatalog() PluginCatalog {
	table := p.pluginManager.LoadedPlugins().Table()
	pc := make([]CatalogedPlugin, len(table))
	for i, lp := range table {
		pc[i] = lp
	}
	return pc
}

func (p *pluginControl) MetricCatalog() []core.MetricType {
	var c []core.MetricType
	for p.metricCatalog.Next() {
		_, mts := p.metricCatalog.Item()
		for _, mt := range mts {
			c = append(c, mt)
		}
	}
	return c
}

func (p *pluginControl) MetricExists(mns []string, ver int) bool {
	_, err := p.metricCatalog.Get(mns, ver)
	if err == nil {
		return true
	}
	return false
}

// Calls collector plugins for the metric types and returns collection response containing metrics. Blocking method.
func (p *pluginControl) CollectMetrics(metricTypes []core.MetricType, config *cdata.ConfigDataNode, deadline time.Time) ([]core.Metric, error) {

	pluginToMetricMap, err := groupMetricTypesByPlugin(p.metricCatalog, metricTypes)
	if err != nil {
		return nil, err
	}

	metrics := []core.Metric{}

	// For each available plugin call available plugin using RPC client and wait for response (goroutines)
	for pluginKey, pmt := range pluginToMetricMap {
		// fmt.Printf("plugin: (%s) has (%d) metrics to gather\n", pluginKey, metrics.Count())

		pool, err := getPool(pluginKey, p.pluginRunner.AvailablePlugins())
		if err != nil {
			return nil, err
		}

		ap, err := getAvailablePlugin(pool, p.strategy)
		if err != nil {
			return nil, err
		}

		// Attempt collection on selected available plugin
		ap.hitCount++
		ap.lastHitTime = time.Now()

		cli, ok := ap.Client.(client.PluginCollectorClient)
		if !ok {
			return nil, errors.New("unable to cast client to PluginCollectorClient")
		}

		metrics, err = cli.CollectMetrics(pmt.metricTypes)
		if err != nil {
			return nil, err
		}
	}

	return metrics, nil
}

// ------------------- helper struct and function for grouping metrics types ------

// just a tuple of loadedPlugin and metricType slice
type pluginMetricTypes struct {
	plugin      *loadedPlugin
	metricTypes []core.MetricType
}

func (p *pluginMetricTypes) Count() int {
	return len(p.metricTypes)
}

// groupMetricTypesByPlugin groups metricTypes by a plugin.Key() and returns appropriate structure
func groupMetricTypesByPlugin(cat catalogsMetrics, metricTypes []core.MetricType) (map[string]pluginMetricTypes, error) {
	pmts := make(map[string]pluginMetricTypes)
	// For each plugin type select a matching available plugin to call
	for _, mt := range metricTypes {

		// This is set to choose the newest and not pin version. TODO, be sure version is set to -1 if not provided by user on Task creation.
		lp, err := cat.GetPlugin(mt.Namespace(), -1)
		if err != nil {
			return nil, err
		}
		// if loaded plugin is nil, we have failed.  return error
		if lp == nil {
			return nil, errors.New(fmt.Sprintf("Metric missing: %s", strings.Join(mt.Namespace(), "/")))
		}

		// fmt.Printf("Found plugin (%s v%d) for metric (%s)\n", lp.Name(), lp.Version(), strings.Join(m.Namespace(), "/"))

		key := lp.Key()

		//
		pmt, _ := pmts[key]
		pmt.plugin = lp
		pmt.metricTypes = append(pmt.metricTypes, mt)
		pmts[key] = pmt

	}
	return pmts, nil
}

// getPool finds a pool for a given pluginKey and checks is not empty
func getPool(pluginKey string, availablePlugins *availablePlugins) (*availablePluginPool, error) {

	pool := availablePlugins.Collectors.GetPluginPool(pluginKey)

	if pool == nil {
		// return error because this plugin has no pool
		return nil, errors.New(fmt.Sprintf("no available plugins for plugin type (%s)", pluginKey))
	}

	// TODO: Lock this apPool so we are the only one operating on it.
	if pool.Count() == 0 {
		// return error indicating we have no available plugins to call for Collect
		return nil, errors.New(fmt.Sprintf("there is no availablePlugins in pool (%s)", pluginKey))
	}
	return pool, nil
}

// getAvailablePlugin finds a "best" availablePlugin to be asked for metrics
func getAvailablePlugin(pool *availablePluginPool, strategy RoutingStrategy) (*availablePlugin, error) {

	// Use a router strategy to select an available plugin from the pool
	ap, err := pool.SelectUsingStrategy(strategy)
	if err != nil {
		return nil, err
	}

	if ap == nil {
		return nil, errors.New(fmt.Sprintf("no available plugin selected in pool %v", pool))
	}

	return ap, nil
}
<commit_msg>CollectMetrics returns slice of errors<commit_after>package control

import (
	"crypto/rsa"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/intelsdilabs/gomit"

	"github.com/intelsdilabs/pulse/control/plugin"
	"github.com/intelsdilabs/pulse/control/plugin/client"
	"github.com/intelsdilabs/pulse/control/plugin/cpolicy"
	"github.com/intelsdilabs/pulse/control/routing"
	"github.com/intelsdilabs/pulse/core"
	"github.com/intelsdilabs/pulse/core/cdata"
	"github.com/intelsdilabs/pulse/core/control_event"
	"github.com/intelsdilabs/pulse/pkg/logger"
)

// control private key (RSA private key)
// control public key (RSA public key)
// Plugin token = token generated by plugin and passed to control
// Session token = plugin seed encrypted by control private key, verified by plugin using control public key
//

type executablePlugins []plugin.ExecutablePlugin

type pluginControl struct {
	// TODO, going to need coordination on changing of these
	RunningPlugins executablePlugins
	Started        bool

	controlPrivKey *rsa.PrivateKey
	controlPubKey  *rsa.PublicKey
	eventManager   *gomit.EventController

	pluginManager managesPlugins
	metricCatalog catalogsMetrics
	pluginRunner  runsPlugins

	strategy RoutingStrategy
}

type runsPlugins interface {
	Start() error
	Stop() []error
	AvailablePlugins() *availablePlugins
	AddDelegates(delegates ...gomit.Delegator)
	SetMetricCatalog(c catalogsMetrics)
	SetPluginManager(m managesPlugins)
	Monitor() *monitor
}

type managesPlugins interface {
	LoadPlugin(string) (*loadedPlugin, error)
	UnloadPlugin(CatalogedPlugin) error
	LoadedPlugins() *loadedPlugins
	SetMetricCatalog(catalogsMetrics)
	GenerateArgs() plugin.Arg
}

type catalogsMetrics interface {
	Get([]string, int) (*metricType, error)
	Add(*metricType)
	AddLoadedMetricType(*loadedPlugin, core.MetricType)
	Item() (string, []*metricType)
	Next() bool
	Subscribe([]string, int) error
	Unsubscribe([]string, int) error
	Table() map[string][]*metricType
	GetPlugin([]string, int) (*loadedPlugin, error)
}

// Returns a new pluginControl instance
func New() *pluginControl {

	c := &pluginControl{}
	// Initialize components
	//
	// Event Manager
	c.eventManager = gomit.NewEventController()
	logger.Debug("control.init", "event controller created")

	// Metric Catalog
	c.metricCatalog = newMetricCatalog()
	logger.Debug("control.init", "metric catalog created")

	// Plugin Manager
	c.pluginManager = newPluginManager()
	logger.Debug("control.init", "plugin manager created")
	//    Plugin Manager needs a reference to the metric catalog
	c.pluginManager.SetMetricCatalog(c.metricCatalog)

	// Plugin Runner
	c.pluginRunner = newRunner()
	logger.Debug("control.init", "runner created")
	c.pluginRunner.AddDelegates(c.eventManager)
	c.pluginRunner.SetMetricCatalog(c.metricCatalog)
	c.pluginRunner.SetPluginManager(c.pluginManager)

	// Strategy
	c.strategy = &routing.RoundRobinStrategy{}

	// Wire event manager

	// Start stuff
	err := c.pluginRunner.Start()
	if err != nil {
		panic(err)
	}

	return c
}

// Begin handling load, unload, and inventory
func (p *pluginControl) Start() error {
	// Start pluginManager when pluginControl starts
	p.Started = true
	logger.Debug("control.start", "started")
	return nil
}

func (p *pluginControl) Stop() {
	p.Started = false
	logger.Debug("control.stop", "stopped")
}

// Load is the public method to load a plugin into
// the LoadedPlugins array and issue an event when
// successful.
func (p *pluginControl) Load(path string) error {
	// logger.Debug("control.load", fmt.Sprintf("load called on path: %s", path))
	if !p.Started {
		return errors.New("Must start Controller before calling Load()")
	}

	if _, err := p.pluginManager.LoadPlugin(path); err != nil {
		return err
	}

	// defer sending event
	event := new(control_event.LoadPluginEvent)
	defer p.eventManager.Emit(event)
	return nil
}

func (p *pluginControl) Unload(pl CatalogedPlugin) error {
	err := p.pluginManager.UnloadPlugin(pl)
	if err != nil {
		return err
	}

	event := new(control_event.UnloadPluginEvent)
	defer p.eventManager.Emit(event)
	return nil
}

func (p *pluginControl) SwapPlugins(inPath string, out CatalogedPlugin) error {

	lp, err := p.pluginManager.LoadPlugin(inPath)
	if err != nil {
		return err
	}

	err = p.pluginManager.UnloadPlugin(out)
	if err != nil {
		err2 := p.pluginManager.UnloadPlugin(lp)
		if err2 != nil {
			return errors.New("failed to rollback after error" + err2.Error() + " -- " + err.Error())
		}
		return err
	}

	event := new(control_event.SwapPluginsEvent)
	defer p.eventManager.Emit(event)

	return nil
}

func (p *pluginControl) generateArgs() plugin.Arg {
	a := plugin.Arg{
		ControlPubKey: p.controlPubKey,
		PluginLogPath: "/tmp/pulse-test-plugin.log",
	}
	return a
}

// SubscribeMetricType validates the given config data, and if valid
// returns a MetricType with a config.  On error a collection of errors is returned
// either from config data processing, or the inability to find the metric.
func (p *pluginControl) SubscribeMetricType(mt core.MetricType, cd *cdata.ConfigDataNode) (core.MetricType, []error) {
	logger.Info("control.subscribe", fmt.Sprintf("subscription called with: %s", mt.Namespace()))
	subErrs := make([]error, 0)

	m, err := p.metricCatalog.Get(mt.Namespace(), mt.Version())
	if err != nil {
		subErrs = append(subErrs, err)
		return nil, subErrs
	}

	// No metric found return error.
	if m == nil {
		subErrs = append(subErrs, errors.New(fmt.Sprintf("no metric found cannot subscribe: (%s) version(%d)", mt.Namespace(), mt.Version())))
		return nil, subErrs
	}

	if m.policy == nil {
		m.policy = cpolicy.NewPolicyNode()
	}
	ncdTable, errs := m.policy.Process(cd.Table())
	if errs != nil && errs.HasErrors() {
		return nil, errs.Errors()
	}
	m.config = cdata.FromTable(*ncdTable)

	m.Subscribe()
	e := &control_event.MetricSubscriptionEvent{
		MetricNamespace: m.Namespace(),
		Version:         m.Version(),
	}
	defer p.eventManager.Emit(e)

	return m, nil
}

// UnsubscribeMetricType unsubscribes a MetricType
// If subscriptions fall below zero we will panic.
func (p *pluginControl) UnsubscribeMetricType(mt core.MetricType) {
	logger.Info("control.subscribe", fmt.Sprintf("unsubscription called with: %s", mt.Namespace()))
	err := p.metricCatalog.Unsubscribe(mt.Namespace(), mt.Version())
	if err != nil {
		// panic because if a metric falls below 0, something bad has happened
		panic(err.Error())
	}
	e := &control_event.MetricUnsubscriptionEvent{
		MetricNamespace: mt.Namespace(),
	}
	p.eventManager.Emit(e)
}

// SetMonitorOptions exposes monitors options
func (p *pluginControl) SetMonitorOptions(options ...monitorOption) {
	p.pluginRunner.Monitor().Option(options...)
}

// the public interface for a plugin
// this should be the contract for
// how mgmt modules know a plugin
type CatalogedPlugin interface {
	Name() string
	Version() int
	TypeName() string
	Status() string
	LoadedTimestamp() int64
}

// the collection of cataloged plugins used
// by mgmt modules
type PluginCatalog []CatalogedPlugin

// returns a copy of the plugin catalog
func (p *pluginControl) PluginCatalog() PluginCatalog {
	table := p.pluginManager.LoadedPlugins().Table()
	pc := make([]CatalogedPlugin, len(table))
	for i, lp := range table {
		pc[i] = lp
	}
	return pc
}

func (p *pluginControl) MetricCatalog() []core.MetricType {
	var c []core.MetricType
	for p.metricCatalog.Next() {
		_, mts := p.metricCatalog.Item()
		for _, mt := range mts {
			c = append(c, mt)
		}
	}
	return c
}

func (p *pluginControl) MetricExists(mns []string, ver int) bool {
	_, err := p.metricCatalog.Get(mns, ver)
	if err == nil {
		return true
	}
	return false
}

// Calls collector plugins for the metric types and returns collection response containing metrics.
// Blocking method.
// returns both metrics and and all errors independtly
// is up to the client logic to ignore received metrics and check errors or partialy used received metrics
func (p *pluginControl) CollectMetrics(
	metricTypes []core.MetricType,
	config *cdata.ConfigDataNode,
	deadline time.Time,
) (metrics []core.Metric, errs []error) {

	pluginToMetricMap, err := groupMetricTypesByPlugin(p.metricCatalog, metricTypes)
	if err != nil {
		errs = append(errs, err)
		return
	}

	// For each available plugin call available plugin using RPC client and wait for response (goroutines)
	for pluginKey, pmt := range pluginToMetricMap {

		// resolve a pool (from catalog)
		pool, err := getPool(pluginKey, p.pluginRunner.AvailablePlugins())
		if err != nil {
			errs = append(errs, err)
			continue
		}

		// resolve a available plugin from pool
		ap, err := getAvailablePlugin(pool, p.strategy)
		if err != nil {
			errs = append(errs, err)
			continue
		}

		// cast client to PluginCollectorClient
		cli, ok := ap.Client.(client.PluginCollectorClient)
		if !ok {
			err := errors.New("unable to cast client to PluginCollectorClient")
			errs = append(errs, err)
			continue
		}

		// get a metrics
		metrics, err = cli.CollectMetrics(pmt.metricTypes)
		if err != nil {
			errs = append(errs, err)
			continue
		}

		// update statics about plugin usage - only when succesful
		ap.hitCount++
		ap.lastHitTime = time.Now()
	}

	// return both collected metrics and all errors
	return
}

// ------------------- helper struct and function for grouping metrics types ------

// just a tuple of loadedPlugin and metricType slice
type pluginMetricTypes struct {
	plugin      *loadedPlugin
	metricTypes []core.MetricType
}

func (p *pluginMetricTypes) Count() int {
	return len(p.metricTypes)
}

// groupMetricTypesByPlugin groups metricTypes by a plugin.Key() and returns appropriate structure
func groupMetricTypesByPlugin(cat catalogsMetrics, metricTypes []core.MetricType) (map[string]pluginMetricTypes, error) {
	pmts := make(map[string]pluginMetricTypes)
	// For each plugin type select a matching available plugin to call
	for _, mt := range metricTypes {

		// This is set to choose the newest and not pin version. TODO, be sure version is set to -1 if not provided by user on Task creation.
		lp, err := cat.GetPlugin(mt.Namespace(), -1)
		if err != nil {
			return nil, err
		}
		// if loaded plugin is nil, we have failed.  return error
		if lp == nil {
			return nil, errors.New(fmt.Sprintf("Metric missing: %s", strings.Join(mt.Namespace(), "/")))
		}

		// fmt.Printf("Found plugin (%s v%d) for metric (%s)\n", lp.Name(), lp.Version(), strings.Join(m.Namespace(), "/"))

		key := lp.Key()

		//
		pmt, _ := pmts[key]
		pmt.plugin = lp
		pmt.metricTypes = append(pmt.metricTypes, mt)
		pmts[key] = pmt

	}
	return pmts, nil
}

// getPool finds a pool for a given pluginKey and checks is not empty
func getPool(pluginKey string, availablePlugins *availablePlugins) (*availablePluginPool, error) {

	pool := availablePlugins.Collectors.GetPluginPool(pluginKey)

	if pool == nil {
		// return error because this plugin has no pool
		return nil, errors.New(fmt.Sprintf("no available plugins for plugin type (%s)", pluginKey))
	}

	// TODO: Lock this apPool so we are the only one operating on it.
	if pool.Count() == 0 {
		// return error indicating we have no available plugins to call for Collect
		return nil, errors.New(fmt.Sprintf("there is no availablePlugins in pool (%s)", pluginKey))
	}
	return pool, nil
}

// getAvailablePlugin finds a "best" availablePlugin to be asked for metrics
func getAvailablePlugin(pool *availablePluginPool, strategy RoutingStrategy) (*availablePlugin, error) {

	// Use a router strategy to select an available plugin from the pool
	ap, err := pool.SelectUsingStrategy(strategy)
	if err != nil {
		return nil, err
	}

	if ap == nil {
		return nil, errors.New(fmt.Sprintf("no available plugin selected in pool %v", pool))
	}

	return ap, nil
}
<|endoftext|>


<commit_before>// Copyright 2011 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 oglemock_test

import (
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/oglemock"
	. "github.com/jacobsa/ogletest"
)

////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////

type errorReport struct {
	fileName string
	lineNumber int
	err error
}

type fakeErrorReporter struct {
	errorsReported []errorReport
}

func (r *fakeErrorReporter) ReportError(fileName string, lineNumber int, err error) {
	report := errorReport{fileName, lineNumber, err}
	r.errorsReported = append(r.errorsReported, report)
}

type trivialMockObject struct {
	id uintptr
	desc string
}

func (o *trivialMockObject) Oglemock_Id() uintptr {
	return o.id
}

func (o *trivialMockObject) Oglemock_Description() string {
	return o.desc
}

// Method being mocked
func (o *trivialMockObject) StringToInt(s string) int {
	return 0
}

// Method being mocked
func (o *trivialMockObject) TwoIntsToString(i, j int) string {
	return ""
}

type ControllerTest struct {
	reporter fakeErrorReporter
	controller Controller

	mock1 MockObject
	mock2 MockObject
}

func (t *ControllerTest) SetUp() {
	t.reporter.errorsReported = make([]errorReport, 0)
	t.controller = NewController(&t.reporter)

	t.mock1 = &trivialMockObject{17, "taco"}
	t.mock2 = &trivialMockObject{19, "burrito"}
}

func init() { RegisterTestSuite(&ControllerTest{}) }

////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////

func (t *ControllerTest) FinishWithoutAnyEvents() {
	t.controller.Finish()
	ExpectThat(len(t.reporter.errorsReported), Equals(0))
}

func (t *ControllerTest) HandleCallForUnknownObject() {
	p := []byte{255}
	t.controller.HandleMethodCall(
		t.mock1,
		"StringToInt",
		"taco.go",
		112,
		[]interface{}{p})

	// The error should be reported immediately.
	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("taco.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(112))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unexpected")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Read")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("[255]")))

	// Finish should change nothing.
	t.controller.Finish()
	ExpectThat(len(t.reporter.errorsReported), Equals(1))
}

func (t *ControllerTest) ExpectCallForUnknownMethod() {
	ExpectThat(
		func() {
			t.controller.ExpectCall(t.mock1, "Frobnicate", "", 0)
		},
		Panics(HasSubstr("Unknown method: Frobnicate")))
}

func (t *ControllerTest) PartialExpectationGivenWrongNumberOfArgs() {
	ExpectThat(
		func() {
			t.controller.ExpectCall(t.mock1, "TwoIntsToString", "", 0)(17, 19, 23)
		},
		Panics(HasSubstr("arguments: expected 2, got 3")))
}

func (t *ControllerTest) PartialExpectationCalledTwice() {
	ExpectThat(
		func() {
			partial := t.controller.ExpectCall(t.mock1, "StringToInt", "", 0)
			partial("taco")
			partial("taco")
		},
		Panics(HasSubstr("called more than once")))
}

func (t *ControllerTest) ExpectThenNonMatchingCall() {
	// Expectation
	partial := t.controller.ExpectCall(
		t.mock1,
		"TwoIntsToString",
		"burrito.go",
		117)

	partial(LessThan(10), Equals(2))

	// Call
	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"taco.go",
		112,
		[]interface{}{8, 1})

	// The error should be reported immediately.
	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("taco.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(112))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unexpected")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Tried")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("burrito.go:117")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("arg 1")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Expected: 2")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Actual:   1")))

	// Finish should change nothing.
	t.controller.Finish()
	ExpectThat(len(t.reporter.errorsReported), Equals(1))
}

func (t *ControllerTest) ExplicitCardinalityNotSatisfied() {
	// Expectation -- set up an explicit cardinality of three.
	partial := t.controller.ExpectCall(
		t.mock1,
		"StringToInt",
		"burrito.go",
		117)

	exp := partial(HasSubstr(""))
	exp.Times(3)

	// Call twice.
	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	// The error should not yet be reported.
	ExpectThat(len(t.reporter.errorsReported), Equals(0))

	// Finish should cause the error to be reported.
	t.controller.Finish()

	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("burrito.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(117))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unsatisfied")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("StringToInt")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("has substring \"\"")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 3 times")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 2 times")))
}

func (t *ControllerTest) ImplicitOneTimeActionCountNotSatisfied() {
	// Expectation -- add three one-time actions.
	partial := t.controller.ExpectCall(
		t.mock1,
		"StringToInt",
		"burrito.go",
		117)

	exp := partial(HasSubstr(""))
	exp.WillOnce(Return(0))
	exp.WillOnce(Return(1))
	exp.WillOnce(Return(2))

	// Call twice.
	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	// The error should not yet be reported.
	ExpectThat(len(t.reporter.errorsReported), Equals(0))

	// Finish should cause the error to be reported.
	t.controller.Finish()

	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("burrito.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(117))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unsatisfied")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("StringToInt")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("has substring \"\"")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 3 times")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 2 times")))
}

func (t *ControllerTest) ImplicitOneTimeActionLowerBoundNotSatisfied() {
	// Expectation -- add three one-time actions and a fallback.
	partial := t.controller.ExpectCall(
		t.mock1,
		"StringToInt",
		"burrito.go",
		117)

	exp := partial(HasSubstr(""))
	exp.WillOnce(Return(0))
	exp.WillOnce(Return(1))
	exp.WillOnce(Return(2))
	exp.WillRepeatedly(Return(3))

	// Call twice.
	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	// The error should not yet be reported.
	ExpectThat(len(t.reporter.errorsReported), Equals(0))

	// Finish should cause the error to be reported.
	t.controller.Finish()

	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("burrito.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(117))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unsatisfied")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("StringToInt")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("has substring \"\"")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 3 times")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 2 times")))
}

func (t *ControllerTest) ImplicitCardinalityOfOneNotSatisfied() {
	// Expectation -- add no actions.
	partial := t.controller.ExpectCall(
		t.mock1,
		"StringToInt",
		"burrito.go",
		117)

	partial(HasSubstr(""))

	// Don't call.

	// The error should not yet be reported.
	ExpectThat(len(t.reporter.errorsReported), Equals(0))

	// Finish should cause the error to be reported.
	t.controller.Finish()

	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("burrito.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(117))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unsatisfied")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("StringToInt")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("has substring \"\"")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 1 time")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 0 times")))
}

func (t *ControllerTest) ExplicitCardinalityOverrun() {
}

func (t *ControllerTest) ImplicitOneTimeActionCountOverrun() {
}

func (t *ControllerTest) ImplicitCountOfOneOverrun() {
}

func (t *ControllerTest) ExplicitCardinalitySatisfied() {
}

func (t *ControllerTest) ImplicitOneTimeActionCountSatisfied() {
}

func (t *ControllerTest) ImplicitOneTimeActionLowerBoundJustSatisfied() {
}

func (t *ControllerTest) ImplicitOneTimeActionLowerBoundMoreThanSatisfied() {
}

func (t *ControllerTest) FallbackActionConfiguredWithZeroCalls() {
}

func (t *ControllerTest) FallbackActionConfiguredWithMultipleCalls() {
}

func (t *ControllerTest) ImplicitCardinalityOfOneSatisfied() {
}

func (t *ControllerTest) InvokesOneTimeActions() {
}

func (t *ControllerTest) InvokesFallbackActions() {
}

func (t *ControllerTest) InvokesImplicitActions() {
}

func (t *ControllerTest) ExpectationsAreMatchedLastToFirst() {
}

func (t *ControllerTest) ExpectationsAreSegregatedByMockObject() {
}

func (t *ControllerTest) ExpectationsAreSegregatedByMethodName() {
}
<commit_msg>ControllerTest.ExplicitCardinalityOverrun<commit_after>// Copyright 2011 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 oglemock_test

import (
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/oglemock"
	. "github.com/jacobsa/ogletest"
)

////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////

type errorReport struct {
	fileName string
	lineNumber int
	err error
}

type fakeErrorReporter struct {
	errorsReported []errorReport
}

func (r *fakeErrorReporter) ReportError(fileName string, lineNumber int, err error) {
	report := errorReport{fileName, lineNumber, err}
	r.errorsReported = append(r.errorsReported, report)
}

type trivialMockObject struct {
	id uintptr
	desc string
}

func (o *trivialMockObject) Oglemock_Id() uintptr {
	return o.id
}

func (o *trivialMockObject) Oglemock_Description() string {
	return o.desc
}

// Method being mocked
func (o *trivialMockObject) StringToInt(s string) int {
	return 0
}

// Method being mocked
func (o *trivialMockObject) TwoIntsToString(i, j int) string {
	return ""
}

type ControllerTest struct {
	reporter fakeErrorReporter
	controller Controller

	mock1 MockObject
	mock2 MockObject
}

func (t *ControllerTest) SetUp() {
	t.reporter.errorsReported = make([]errorReport, 0)
	t.controller = NewController(&t.reporter)

	t.mock1 = &trivialMockObject{17, "taco"}
	t.mock2 = &trivialMockObject{19, "burrito"}
}

func init() { RegisterTestSuite(&ControllerTest{}) }

////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////

func (t *ControllerTest) FinishWithoutAnyEvents() {
	t.controller.Finish()
	ExpectThat(len(t.reporter.errorsReported), Equals(0))
}

func (t *ControllerTest) HandleCallForUnknownObject() {
	p := []byte{255}
	t.controller.HandleMethodCall(
		t.mock1,
		"StringToInt",
		"taco.go",
		112,
		[]interface{}{p})

	// The error should be reported immediately.
	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("taco.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(112))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unexpected")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Read")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("[255]")))

	// Finish should change nothing.
	t.controller.Finish()
	ExpectThat(len(t.reporter.errorsReported), Equals(1))
}

func (t *ControllerTest) ExpectCallForUnknownMethod() {
	ExpectThat(
		func() {
			t.controller.ExpectCall(t.mock1, "Frobnicate", "", 0)
		},
		Panics(HasSubstr("Unknown method: Frobnicate")))
}

func (t *ControllerTest) PartialExpectationGivenWrongNumberOfArgs() {
	ExpectThat(
		func() {
			t.controller.ExpectCall(t.mock1, "TwoIntsToString", "", 0)(17, 19, 23)
		},
		Panics(HasSubstr("arguments: expected 2, got 3")))
}

func (t *ControllerTest) PartialExpectationCalledTwice() {
	ExpectThat(
		func() {
			partial := t.controller.ExpectCall(t.mock1, "StringToInt", "", 0)
			partial("taco")
			partial("taco")
		},
		Panics(HasSubstr("called more than once")))
}

func (t *ControllerTest) ExpectThenNonMatchingCall() {
	// Expectation
	partial := t.controller.ExpectCall(
		t.mock1,
		"TwoIntsToString",
		"burrito.go",
		117)

	partial(LessThan(10), Equals(2))

	// Call
	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"taco.go",
		112,
		[]interface{}{8, 1})

	// The error should be reported immediately.
	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("taco.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(112))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unexpected")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Tried")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("burrito.go:117")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("arg 1")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Expected: 2")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Actual:   1")))

	// Finish should change nothing.
	t.controller.Finish()
	ExpectThat(len(t.reporter.errorsReported), Equals(1))
}

func (t *ControllerTest) ExplicitCardinalityNotSatisfied() {
	// Expectation -- set up an explicit cardinality of three.
	partial := t.controller.ExpectCall(
		t.mock1,
		"StringToInt",
		"burrito.go",
		117)

	exp := partial(HasSubstr(""))
	exp.Times(3)

	// Call twice.
	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	// The error should not yet be reported.
	ExpectThat(len(t.reporter.errorsReported), Equals(0))

	// Finish should cause the error to be reported.
	t.controller.Finish()

	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("burrito.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(117))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unsatisfied")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("StringToInt")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("has substring \"\"")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 3 times")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 2 times")))
}

func (t *ControllerTest) ImplicitOneTimeActionCountNotSatisfied() {
	// Expectation -- add three one-time actions.
	partial := t.controller.ExpectCall(
		t.mock1,
		"StringToInt",
		"burrito.go",
		117)

	exp := partial(HasSubstr(""))
	exp.WillOnce(Return(0))
	exp.WillOnce(Return(1))
	exp.WillOnce(Return(2))

	// Call twice.
	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	// The error should not yet be reported.
	ExpectThat(len(t.reporter.errorsReported), Equals(0))

	// Finish should cause the error to be reported.
	t.controller.Finish()

	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("burrito.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(117))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unsatisfied")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("StringToInt")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("has substring \"\"")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 3 times")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 2 times")))
}

func (t *ControllerTest) ImplicitOneTimeActionLowerBoundNotSatisfied() {
	// Expectation -- add three one-time actions and a fallback.
	partial := t.controller.ExpectCall(
		t.mock1,
		"StringToInt",
		"burrito.go",
		117)

	exp := partial(HasSubstr(""))
	exp.WillOnce(Return(0))
	exp.WillOnce(Return(1))
	exp.WillOnce(Return(2))
	exp.WillRepeatedly(Return(3))

	// Call twice.
	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	// The error should not yet be reported.
	ExpectThat(len(t.reporter.errorsReported), Equals(0))

	// Finish should cause the error to be reported.
	t.controller.Finish()

	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("burrito.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(117))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unsatisfied")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("StringToInt")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("has substring \"\"")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 3 times")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 2 times")))
}

func (t *ControllerTest) ImplicitCardinalityOfOneNotSatisfied() {
	// Expectation -- add no actions.
	partial := t.controller.ExpectCall(
		t.mock1,
		"StringToInt",
		"burrito.go",
		117)

	partial(HasSubstr(""))

	// Don't call.

	// The error should not yet be reported.
	ExpectThat(len(t.reporter.errorsReported), Equals(0))

	// Finish should cause the error to be reported.
	t.controller.Finish()

	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("burrito.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(117))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unsatisfied")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("StringToInt")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("has substring \"\"")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 1 time")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 0 times")))
}

func (t *ControllerTest) ExplicitCardinalityOverrun() {
	// Expectation -- call times(2).
	partial := t.controller.ExpectCall(
		t.mock1,
		"StringToInt",
		"burrito.go",
		117)

	exp := partial(HasSubstr(""))
	exp.Times(2)

	// Call three times.
	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	t.controller.HandleMethodCall(
		t.mock1,
		"TwoIntsToString",
		"",
		0,
		[]interface{}{""})

	// The error should be reported immediately.
	ExpectThat(len(t.reporter.errorsReported), Equals(1))
	ExpectThat(t.reporter.errorsReported[0].fileName, Equals("burrito.go"))
	ExpectThat(t.reporter.errorsReported[0].lineNumber, Equals(117))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("Unexpected")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("StringToInt")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("has substring \"\"")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 2 times")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("called 3 times")))
	ExpectThat(t.reporter.errorsReported[0].err, Error(HasSubstr("oversatisfied")))

	// Finish should change nothing.
	t.controller.Finish()
	ExpectThat(len(t.reporter.errorsReported), Equals(1))
}

func (t *ControllerTest) ImplicitOneTimeActionCountOverrun() {
}

func (t *ControllerTest) ImplicitCardinalityOfOneOverrun() {
}

func (t *ControllerTest) ExplicitCardinalitySatisfied() {
}

func (t *ControllerTest) ImplicitOneTimeActionCountSatisfied() {
}

func (t *ControllerTest) ImplicitOneTimeActionLowerBoundJustSatisfied() {
}

func (t *ControllerTest) ImplicitOneTimeActionLowerBoundMoreThanSatisfied() {
}

func (t *ControllerTest) FallbackActionConfiguredWithZeroCalls() {
}

func (t *ControllerTest) FallbackActionConfiguredWithMultipleCalls() {
}

func (t *ControllerTest) ImplicitCardinalityOfOneSatisfied() {
}

func (t *ControllerTest) InvokesOneTimeActions() {
}

func (t *ControllerTest) InvokesFallbackActions() {
}

func (t *ControllerTest) InvokesImplicitActions() {
}

func (t *ControllerTest) ExpectationsAreMatchedLastToFirst() {
}

func (t *ControllerTest) ExpectationsAreSegregatedByMockObject() {
}

func (t *ControllerTest) ExpectationsAreSegregatedByMethodName() {
}
<|endoftext|>