<commit_before>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package apply

import (
	"encoding/json"
	"fmt"
	"io"
	"time"

	"github.com/pkg/errors"

	"github.com/jonboulle/clockwork"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/jsonmergepatch"
	"k8s.io/apimachinery/pkg/util/mergepatch"
	"k8s.io/apimachinery/pkg/util/strategicpatch"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/cli-runtime/pkg/resource"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/scheme"
	"k8s.io/kubectl/pkg/util"
	"k8s.io/kubectl/pkg/util/openapi"
)

const (
	// maxPatchRetry is the maximum number of conflicts retry for during a patch operation before returning failure
	maxPatchRetry = 5
	// backOffPeriod is the period to back off when apply patch results in error.
	backOffPeriod = 1 * time.Second
	// how many times we can retry before back off
	triesBeforeBackOff = 1
)

var createPatchErrFormat = "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"

// Patcher defines options to patch OpenAPI objects.
type Patcher struct {
	Mapping *meta.RESTMapping
	Helper  *resource.Helper

	Overwrite bool
	BackOff   clockwork.Clock

	Force             bool
	CascadingStrategy metav1.DeletionPropagation
	Timeout           time.Duration
	GracePeriod       int

	// If set, forces the patch against a specific resourceVersion
	ResourceVersion *string

	// Number of retries to make if the patch fails with conflict
	Retries int

	OpenapiSchema openapi.Resources
}

func newPatcher(o *ApplyOptions, info *resource.Info, helper *resource.Helper) (*Patcher, error) {
	var openapiSchema openapi.Resources
	if o.OpenAPIPatch {
		openapiSchema = o.OpenAPISchema
	}

	return &Patcher{
		Mapping:           info.Mapping,
		Helper:            helper,
		Overwrite:         o.Overwrite,
		BackOff:           clockwork.NewRealClock(),
		Force:             o.DeleteOptions.ForceDeletion,
		CascadingStrategy: o.DeleteOptions.CascadingStrategy,
		Timeout:           o.DeleteOptions.Timeout,
		GracePeriod:       o.DeleteOptions.GracePeriod,
		OpenapiSchema:     openapiSchema,
		Retries:           maxPatchRetry,
	}, nil
}

func (p *Patcher) delete(namespace, name string) error {
	options := asDeleteOptions(p.CascadingStrategy, p.GracePeriod)
	_, err := p.Helper.DeleteWithOptions(namespace, name, &options)
	return err
}

func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
	// Serialize the current configuration of the object from the server.
	current, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
	if err != nil {
		return nil, nil, errors.Wrapf(err, "serializing current configuration from:\n%v\nfor:", obj)
	}

	// Retrieve the original configuration of the object from the annotation.
	original, err := util.GetOriginalConfiguration(obj)
	if err != nil {
		return nil, nil, errors.Wrapf(err, "retrieving original configuration from:\n%v\nfor:", obj)
	}

	var patch []byte

	patchType := types.StrategicMergePatchType
	_, err = scheme.Scheme.New(p.Mapping.GroupVersionKind)
	if err != nil {
		if !runtime.IsNotRegisteredError(err) {
			return nil, nil, errors.Wrapf(err, "getting instance of versioned object for %v:", p.Mapping.GroupVersionKind)
		}
		patchType = types.MergePatchType
	}

	switch patchType {
	case types.MergePatchType:
		patch, err = p.buildMergePatch(original, modified, current)
		if err != nil {
			return nil, nil, errors.Wrapf(err, createPatchErrFormat, original, modified, current)
		}
	case types.StrategicMergePatchType:
		// Try to use openapi first if the openapi spec is available and can successfully calculate the patch.
		// Otherwise, fall back to baked-in types.
		if p.OpenapiSchema != nil {
			patch, err = p.buildStrategicMergeFromOpenAPI(p.Mapping.GroupVersionKind, original, modified, current)
			if err != nil {
				// Warn user about problem and continue strategic merge patching using builtin types.
				fmt.Fprintf(errOut, "warning: error calculating patch from openapi spec: %v\n", err)
			}
		}

		if patch == nil {
			patch, err = p.buildStrategicMergeFromBuiltins(p.Mapping.GroupVersionKind, original, modified, current)
			if err != nil {
				return nil, nil, errors.Wrapf(err, createPatchErrFormat, original, modified, current)
			}
		}
	}

	if string(patch) == "{}" {
		return patch, obj, nil
	}

	if p.ResourceVersion != nil {
		patch, err = addResourceVersion(patch, *p.ResourceVersion)
		if err != nil {
			return nil, nil, errors.Wrap(err, "Failed to insert resourceVersion in patch")
		}
	}

	patchedObj, err := p.Helper.Patch(namespace, name, patchType, patch, nil)
	return patch, patchedObj, err
}

// buildMergePatch builds patch according to the JSONMergePatch which is used for
// custom resource definitions.
func (p *Patcher) buildMergePatch(original, modified, current []byte) ([]byte, error) {
	preconditions := []mergepatch.PreconditionFunc{mergepatch.RequireKeyUnchanged("apiVersion"),
		mergepatch.RequireKeyUnchanged("kind"), mergepatch.RequireMetadataKeyUnchanged("name")}
	patch, err := jsonmergepatch.CreateThreeWayJSONMergePatch(original, modified, current, preconditions...)
	if err != nil {
		if mergepatch.IsPreconditionFailed(err) {
			return nil, fmt.Errorf("%s", "At least one of apiVersion, kind and name was changed")
		}
		return nil, err
	}

	return patch, nil
}

// buildStrategicMergeFromOpenAPI builds patch from OpenAPI if it is enabled.
// This is used for core types which is published in openapi.
func (p *Patcher) buildStrategicMergeFromOpenAPI(gvk schema.GroupVersionKind, original, modified, current []byte) ([]byte, error) {
	if s := p.OpenapiSchema.LookupResource(gvk); s != nil {
		lookupPatchMeta := strategicpatch.PatchMetaFromOpenAPI{Schema: s}
		if openapiPatch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.Overwrite); err != nil {
			return nil, err
		} else {
			return openapiPatch, nil
		}
	}

	return nil, nil
}

// buildStrategicMergeFromStruct builds patch from struct. This is used when
// openapi endpoint is not working or user disables it by setting openapi-patch flag
// to false.
func (p *Patcher) buildStrategicMergeFromBuiltins(gvk schema.GroupVersionKind, original, modified, current []byte) ([]byte, error) {
	versionedObject, err := scheme.Scheme.New(gvk)
	if err != nil {
		return nil, err
	}
	lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject)
	if err != nil {
		return nil, err
	}
	patch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.Overwrite)
	if err != nil {
		return nil, err
	}

	return patch, nil
}

// Patch tries to patch an OpenAPI resource. On success, returns the merge patch as well
// the final patched object. On failure, returns an error.
func (p *Patcher) Patch(current runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
	var getErr error
	patchBytes, patchObject, err := p.patchSimple(current, modified, namespace, name, errOut)
	if p.Retries == 0 {
		p.Retries = maxPatchRetry
	}
	for i := 1; i <= p.Retries && apierrors.IsConflict(err); i++ {
		if i > triesBeforeBackOff {
			p.BackOff.Sleep(backOffPeriod)
		}
		current, getErr = p.Helper.Get(namespace, name)
		if getErr != nil {
			return nil, nil, getErr
		}
		patchBytes, patchObject, err = p.patchSimple(current, modified, namespace, name, errOut)
	}
	if err != nil {
		if (apierrors.IsConflict(err) || apierrors.IsInvalid(err)) && p.Force {
			patchBytes, patchObject, err = p.deleteAndCreate(current, modified, namespace, name)
		} else {
			err = cmdutil.AddSourceToErr("patching", source, err)
		}
	}
	return patchBytes, patchObject, err
}

func (p *Patcher) deleteAndCreate(original runtime.Object, modified []byte, namespace, name string) ([]byte, runtime.Object, error) {
	if err := p.delete(namespace, name); err != nil {
		return modified, nil, err
	}
	// TODO: use wait
	if err := wait.PollImmediate(1*time.Second, p.Timeout, func() (bool, error) {
		if _, err := p.Helper.Get(namespace, name); !apierrors.IsNotFound(err) {
			return false, err
		}
		return true, nil
	}); err != nil {
		return modified, nil, err
	}
	versionedObject, _, err := unstructured.UnstructuredJSONScheme.Decode(modified, nil, nil)
	if err != nil {
		return modified, nil, err
	}
	createdObject, err := p.Helper.Create(namespace, true, versionedObject)
	if err != nil {
		// restore the original object if we fail to create the new one
		// but still propagate and advertise error to user
		recreated, recreateErr := p.Helper.Create(namespace, true, original)
		if recreateErr != nil {
			err = fmt.Errorf("An error occurred force-replacing the existing object with the newly provided one:\n\n%v.\n\nAdditionally, an error occurred attempting to restore the original object:\n\n%v", err, recreateErr)
		} else {
			createdObject = recreated
		}
	}
	return modified, createdObject, err
}

func addResourceVersion(patch []byte, rv string) ([]byte, error) {
	var patchMap map[string]interface{}
	err := json.Unmarshal(patch, &patchMap)
	if err != nil {
		return nil, err
	}
	u := unstructured.Unstructured{Object: patchMap}
	a, err := meta.Accessor(&u)
	if err != nil {
		return nil, err
	}
	a.SetResourceVersion(rv)

	return json.Marshal(patchMap)
}
<commit_msg>Move resource lookup into its own function<commit_after>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package apply

import (
	"encoding/json"
	"fmt"
	"io"
	"time"

	"github.com/pkg/errors"

	"github.com/jonboulle/clockwork"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/jsonmergepatch"
	"k8s.io/apimachinery/pkg/util/mergepatch"
	"k8s.io/apimachinery/pkg/util/strategicpatch"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/cli-runtime/pkg/resource"
	"k8s.io/kube-openapi/pkg/util/proto"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/scheme"
	"k8s.io/kubectl/pkg/util"
	"k8s.io/kubectl/pkg/util/openapi"
)

const (
	// maxPatchRetry is the maximum number of conflicts retry for during a patch operation before returning failure
	maxPatchRetry = 5
	// backOffPeriod is the period to back off when apply patch results in error.
	backOffPeriod = 1 * time.Second
	// how many times we can retry before back off
	triesBeforeBackOff = 1
)

var createPatchErrFormat = "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"

// Patcher defines options to patch OpenAPI objects.
type Patcher struct {
	Mapping *meta.RESTMapping
	Helper  *resource.Helper

	Overwrite bool
	BackOff   clockwork.Clock

	Force             bool
	CascadingStrategy metav1.DeletionPropagation
	Timeout           time.Duration
	GracePeriod       int

	// If set, forces the patch against a specific resourceVersion
	ResourceVersion *string

	// Number of retries to make if the patch fails with conflict
	Retries int

	OpenapiSchema openapi.Resources
}

func newPatcher(o *ApplyOptions, info *resource.Info, helper *resource.Helper) (*Patcher, error) {
	var openapiSchema openapi.Resources
	if o.OpenAPIPatch {
		openapiSchema = o.OpenAPISchema
	}

	return &Patcher{
		Mapping:           info.Mapping,
		Helper:            helper,
		Overwrite:         o.Overwrite,
		BackOff:           clockwork.NewRealClock(),
		Force:             o.DeleteOptions.ForceDeletion,
		CascadingStrategy: o.DeleteOptions.CascadingStrategy,
		Timeout:           o.DeleteOptions.Timeout,
		GracePeriod:       o.DeleteOptions.GracePeriod,
		OpenapiSchema:     openapiSchema,
		Retries:           maxPatchRetry,
	}, nil
}

func (p *Patcher) delete(namespace, name string) error {
	options := asDeleteOptions(p.CascadingStrategy, p.GracePeriod)
	_, err := p.Helper.DeleteWithOptions(namespace, name, &options)
	return err
}

func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
	// Serialize the current configuration of the object from the server.
	current, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
	if err != nil {
		return nil, nil, errors.Wrapf(err, "serializing current configuration from:\n%v\nfor:", obj)
	}

	// Retrieve the original configuration of the object from the annotation.
	original, err := util.GetOriginalConfiguration(obj)
	if err != nil {
		return nil, nil, errors.Wrapf(err, "retrieving original configuration from:\n%v\nfor:", obj)
	}

	var patch []byte

	patchType := types.StrategicMergePatchType
	_, err = scheme.Scheme.New(p.Mapping.GroupVersionKind)
	if err != nil {
		if !runtime.IsNotRegisteredError(err) {
			return nil, nil, errors.Wrapf(err, "getting instance of versioned object for %v:", p.Mapping.GroupVersionKind)
		}
		patchType = types.MergePatchType
	}

	switch patchType {
	case types.MergePatchType:
		patch, err = p.buildMergePatch(original, modified, current)
		if err != nil {
			return nil, nil, errors.Wrapf(err, createPatchErrFormat, original, modified, current)
		}
	case types.StrategicMergePatchType:
		// Try to use openapi first if the openapi spec is available and can successfully calculate the patch.
		// Otherwise, fall back to baked-in types.
		if s := p.findOpenAPIResource(p.Mapping.GroupVersionKind); s != nil {
			patch, err = p.buildStrategicMergeFromOpenAPI(s, original, modified, current)
			if err != nil {
				// Warn user about problem and continue strategic merge patching using builtin types.
				fmt.Fprintf(errOut, "warning: error calculating patch from openapi spec: %v\n", err)
			}
		}

		if patch == nil {
			patch, err = p.buildStrategicMergeFromBuiltins(p.Mapping.GroupVersionKind, original, modified, current)
			if err != nil {
				return nil, nil, errors.Wrapf(err, createPatchErrFormat, original, modified, current)
			}
		}
	}

	if string(patch) == "{}" {
		return patch, obj, nil
	}

	if p.ResourceVersion != nil {
		patch, err = addResourceVersion(patch, *p.ResourceVersion)
		if err != nil {
			return nil, nil, errors.Wrap(err, "Failed to insert resourceVersion in patch")
		}
	}

	patchedObj, err := p.Helper.Patch(namespace, name, patchType, patch, nil)
	return patch, patchedObj, err
}

// buildMergePatch builds patch according to the JSONMergePatch which is used for
// custom resource definitions.
func (p *Patcher) buildMergePatch(original, modified, current []byte) ([]byte, error) {
	preconditions := []mergepatch.PreconditionFunc{mergepatch.RequireKeyUnchanged("apiVersion"),
		mergepatch.RequireKeyUnchanged("kind"), mergepatch.RequireMetadataKeyUnchanged("name")}
	patch, err := jsonmergepatch.CreateThreeWayJSONMergePatch(original, modified, current, preconditions...)
	if err != nil {
		if mergepatch.IsPreconditionFailed(err) {
			return nil, fmt.Errorf("%s", "At least one of apiVersion, kind and name was changed")
		}
		return nil, err
	}

	return patch, nil
}

// buildStrategicMergeFromOpenAPI builds patch from OpenAPI if it is enabled.
// This is used for core types which is published in openapi.
func (p *Patcher) buildStrategicMergeFromOpenAPI(schema proto.Schema, original, modified, current []byte) ([]byte, error) {
	lookupPatchMeta := strategicpatch.PatchMetaFromOpenAPI{Schema: schema}
	if openapiPatch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.Overwrite); err != nil {
		return nil, err
	} else {
		return openapiPatch, nil
	}
}

// findOpenAPIResource finds schema of GVK in OpenAPI endpoint.
func (p *Patcher) findOpenAPIResource(gvk schema.GroupVersionKind) proto.Schema {
	if p.OpenapiSchema == nil {
		return nil
	}
	return p.OpenapiSchema.LookupResource(gvk)
}

// buildStrategicMergeFromStruct builds patch from struct. This is used when
// openapi endpoint is not working or user disables it by setting openapi-patch flag
// to false.
func (p *Patcher) buildStrategicMergeFromBuiltins(gvk schema.GroupVersionKind, original, modified, current []byte) ([]byte, error) {
	versionedObject, err := scheme.Scheme.New(gvk)
	if err != nil {
		return nil, err
	}
	lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject)
	if err != nil {
		return nil, err
	}
	patch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.Overwrite)
	if err != nil {
		return nil, err
	}

	return patch, nil
}

// Patch tries to patch an OpenAPI resource. On success, returns the merge patch as well
// the final patched object. On failure, returns an error.
func (p *Patcher) Patch(current runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
	var getErr error
	patchBytes, patchObject, err := p.patchSimple(current, modified, namespace, name, errOut)
	if p.Retries == 0 {
		p.Retries = maxPatchRetry
	}
	for i := 1; i <= p.Retries && apierrors.IsConflict(err); i++ {
		if i > triesBeforeBackOff {
			p.BackOff.Sleep(backOffPeriod)
		}
		current, getErr = p.Helper.Get(namespace, name)
		if getErr != nil {
			return nil, nil, getErr
		}
		patchBytes, patchObject, err = p.patchSimple(current, modified, namespace, name, errOut)
	}
	if err != nil {
		if (apierrors.IsConflict(err) || apierrors.IsInvalid(err)) && p.Force {
			patchBytes, patchObject, err = p.deleteAndCreate(current, modified, namespace, name)
		} else {
			err = cmdutil.AddSourceToErr("patching", source, err)
		}
	}
	return patchBytes, patchObject, err
}

func (p *Patcher) deleteAndCreate(original runtime.Object, modified []byte, namespace, name string) ([]byte, runtime.Object, error) {
	if err := p.delete(namespace, name); err != nil {
		return modified, nil, err
	}
	// TODO: use wait
	if err := wait.PollImmediate(1*time.Second, p.Timeout, func() (bool, error) {
		if _, err := p.Helper.Get(namespace, name); !apierrors.IsNotFound(err) {
			return false, err
		}
		return true, nil
	}); err != nil {
		return modified, nil, err
	}
	versionedObject, _, err := unstructured.UnstructuredJSONScheme.Decode(modified, nil, nil)
	if err != nil {
		return modified, nil, err
	}
	createdObject, err := p.Helper.Create(namespace, true, versionedObject)
	if err != nil {
		// restore the original object if we fail to create the new one
		// but still propagate and advertise error to user
		recreated, recreateErr := p.Helper.Create(namespace, true, original)
		if recreateErr != nil {
			err = fmt.Errorf("An error occurred force-replacing the existing object with the newly provided one:\n\n%v.\n\nAdditionally, an error occurred attempting to restore the original object:\n\n%v", err, recreateErr)
		} else {
			createdObject = recreated
		}
	}
	return modified, createdObject, err
}

func addResourceVersion(patch []byte, rv string) ([]byte, error) {
	var patchMap map[string]interface{}
	err := json.Unmarshal(patch, &patchMap)
	if err != nil {
		return nil, err
	}
	u := unstructured.Unstructured{Object: patchMap}
	a, err := meta.Accessor(&u)
	if err != nil {
		return nil, err
	}
	a.SetResourceVersion(rv)

	return json.Marshal(patchMap)
}
<|endoftext|>


<commit_before>// Package bamstats provides functions for computing several mapping statistics on a BAM file.
package bamstats

import (
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/guigolab/bamstats/annotation"
	"github.com/guigolab/bamstats/config"
	"github.com/guigolab/bamstats/sam"
	"github.com/guigolab/bamstats/stats"
	"github.com/guigolab/bamstats/utils"
)

func init() {
	log.SetLevel(log.WarnLevel)
}

// var wg sync.WaitGroup

func worker(id int, in interface{}, out chan stats.Map, index *annotation.RtreeMap, cfg *config.Config, wg *sync.WaitGroup) {
	defer wg.Done()
	logger := log.WithFields(log.Fields{
		"worker": id,
	})
	logger.Debug("Starting")

	sm := makeStatsMap(index, cfg)

	collectStats(in, sm)

	logger.Debug("Done")

	out <- sm
}

func collectStats(in interface{}, sm stats.Map) {
	switch in.(type) {
	case chan *sam.Record:
		c := in.(chan *sam.Record)
		for record := range c {
			for _, s := range sm {
				s.Collect(record)
			}
		}
	case chan *sam.Iterator:
		iterators := in.(chan *sam.Iterator)
		for it := range iterators {
			for it.Next() {
				for _, s := range sm {
					s.Collect(it.Record())
				}
			}
		}
	}
}

func process(bamFile string, index *annotation.RtreeMap, cpu int, maxBuf int, reads int, uniq bool) (stats.Map, error) {

	var wg sync.WaitGroup

	conf := config.NewConfig(cpu, maxBuf, reads, uniq)

	br, err := sam.NewReader(bamFile, conf)
	defer br.Close()
	if err != nil {
		return nil, err
	}
	statChan := make(chan stats.Map, cpu)
	for i := 0; i < br.Workers; i++ {
		id := i + 1
		wg.Add(1)
		go worker(id, br.Channels[i], statChan, index, conf, &wg)
	}

	br.Read()

	go waitProcess(statChan, &wg)
	stat := <-statChan
	stat.Merge(statChan)
	for k, v := range stat {
		v.Finalize()
		if k == "general" {
			s := v.(*stats.GeneralStats)
			s.Reads.Total += br.Unmapped()
		}
	}

	return stat, nil
}

func waitProcess(st chan stats.Map, wg *sync.WaitGroup) {
	wg.Wait()
	close(st)
}

// Process process the input BAM file and collect different mapping stats.
func Process(bamFile string, anno string, cpu int, maxBuf int, reads int, uniq bool) (stats.Map, error) {
	var index *annotation.RtreeMap
	if anno != "" {
		log.Infof("Creating index for %s", anno)
		start := time.Now()
		index = annotation.CreateIndex(anno, bamFile, cpu)
		log.Infof("Index done in %v", time.Since(start))
	}
	start := time.Now()
	log.Infof("Collecting stats for %s", bamFile)
	allStats, err := process(bamFile, index, cpu, maxBuf, reads, uniq)
	if err != nil {
		return nil, err
	}
	log.Infof("Stats done in %v", time.Since(start))
	return allStats, nil
}

func WriteOutput(output string, st stats.Map) {
	out := utils.NewOutput(output)
	utils.OutputJSON(out, st)
}

func makeStatsMap(index *annotation.RtreeMap, cfg *config.Config) stats.Map {
	m := make(stats.Map)
	m.Add("general", stats.NewGeneralStats())
	if index != nil {
		m.Add("coverage", stats.NewCoverageStats(index, false))
		if cfg.Uniq {
			m.Add("coverageUniq", stats.NewCoverageStats(index, true))
		}
	}
	return m
}
<commit_msg>[ci skip] Read bam in goroutine<commit_after>// Package bamstats provides functions for computing several mapping statistics on a BAM file.
package bamstats

import (
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/guigolab/bamstats/annotation"
	"github.com/guigolab/bamstats/config"
	"github.com/guigolab/bamstats/sam"
	"github.com/guigolab/bamstats/stats"
	"github.com/guigolab/bamstats/utils"
)

func init() {
	log.SetLevel(log.WarnLevel)
}

// var wg sync.WaitGroup

func worker(id int, in interface{}, out chan stats.Map, index *annotation.RtreeMap, cfg *config.Config, wg *sync.WaitGroup) {
	defer wg.Done()
	logger := log.WithFields(log.Fields{
		"worker": id,
	})
	logger.Debug("Starting")

	sm := makeStatsMap(index, cfg)

	collectStats(in, sm)

	logger.Debug("Done")

	out <- sm
}

func collectStats(in interface{}, sm stats.Map) {
	switch in.(type) {
	case chan *sam.Record:
		c := in.(chan *sam.Record)
		for record := range c {
			for _, s := range sm {
				s.Collect(record)
			}
		}
	case chan *sam.Iterator:
		iterators := in.(chan *sam.Iterator)
		for it := range iterators {
			for it.Next() {
				for _, s := range sm {
					s.Collect(it.Record())
				}
			}
		}
	}
}

func process(bamFile string, index *annotation.RtreeMap, cpu int, maxBuf int, reads int, uniq bool) (stats.Map, error) {

	var wg sync.WaitGroup

	conf := config.NewConfig(cpu, maxBuf, reads, uniq)

	br, err := sam.NewReader(bamFile, conf)
	defer br.Close()
	if err != nil {
		return nil, err
	}
	statChan := make(chan stats.Map, cpu)
	for i := 0; i < br.Workers; i++ {
		id := i + 1
		wg.Add(1)
		go worker(id, br.Channels[i], statChan, index, conf, &wg)
	}

	go br.Read()

	go waitProcess(statChan, &wg)
	stat := <-statChan
	stat.Merge(statChan)
	for k, v := range stat {
		v.Finalize()
		if k == "general" {
			s := v.(*stats.GeneralStats)
			s.Reads.Total += br.Unmapped()
		}
	}

	return stat, nil
}

func waitProcess(st chan stats.Map, wg *sync.WaitGroup) {
	wg.Wait()
	close(st)
}

// Process process the input BAM file and collect different mapping stats.
func Process(bamFile string, anno string, cpu int, maxBuf int, reads int, uniq bool) (stats.Map, error) {
	var index *annotation.RtreeMap
	if anno != "" {
		log.Infof("Creating index for %s", anno)
		start := time.Now()
		index = annotation.CreateIndex(anno, bamFile, cpu)
		log.Infof("Index done in %v", time.Since(start))
	}
	start := time.Now()
	log.Infof("Collecting stats for %s", bamFile)
	allStats, err := process(bamFile, index, cpu, maxBuf, reads, uniq)
	if err != nil {
		return nil, err
	}
	log.Infof("Stats done in %v", time.Since(start))
	return allStats, nil
}

func WriteOutput(output string, st stats.Map) {
	out := utils.NewOutput(output)
	utils.OutputJSON(out, st)
}

func makeStatsMap(index *annotation.RtreeMap, cfg *config.Config) stats.Map {
	m := make(stats.Map)
	m.Add("general", stats.NewGeneralStats())
	if index != nil {
		m.Add("coverage", stats.NewCoverageStats(index, false))
		if cfg.Uniq {
			m.Add("coverageUniq", stats.NewCoverageStats(index, true))
		}
	}
	return m
}
<|endoftext|>


<commit_before>package cortexbot

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"

	valid "github.com/asaskevich/govalidator"
	"github.com/ilyaglow/go-cortex"
	"gopkg.in/telegram-bot-api.v4"
)

// ProcessCortex asks Cortex about data submitted by a user
func (c *Client) ProcessCortex(input *tgbotapi.Message) error {
	j, err := constructJob(input.Text)
	if err != nil {
		log.Println(err.Error())
		return err
	}

	// Run all analyzers over it with 1 minute timeout
	reports, err := c.Cortex.AnalyzeData(j, "1minute")
	if err != nil {
		msg := tgbotapi.NewMessage(input.Chat.ID, "")
		msg.ReplyToMessageID = input.MessageID
		msg.Text = fmt.Sprintf("Cortex failed with an error: %s", err.Error())
		c.Bot.Send(msg)
	}

	// Iterate over channel with reports and get taxonomies
	for m := range reports {
		if m.Status == "Failure" {
			continue
		}

		txs := m.Taxonomies()
		// Send every taxonomy as a message
		for _, t := range txs {
			msg := tgbotapi.NewMessage(input.Chat.ID, "")
			msg.ReplyToMessageID = input.MessageID
			msg.Text = fmt.Sprintf("%s:%s = %s", t.Namespace, t.Predicate, t.Value)
			c.Bot.Send(msg)
		}

		// Send JSON file with full report
		tr, _ := json.MarshalIndent(m, "", "  ")

		fb := tgbotapi.FileBytes{
			Name:  fmt.Sprintf("%s-%s.json", m.AnalyzerID, m.ID),
			Bytes: tr,
		}

		attachment := tgbotapi.NewDocumentUpload(input.Chat.ID, fb)
		attachment.ReplyToMessageID = input.MessageID
		c.Bot.Send(attachment)
	}

	return nil
}

// constructJob make a JobBody depends on its type
func constructJob(s string) (*gocortex.JobBody, error) {
	var dataType string

	if valid.IsIP(s) {
		dataType = "ip"
	} else if IsDNSName(s) {
		dataType = "domain"
	} else if IsHash(s) {
		dataType = "hash"
	} else if valid.IsURL(s) {
		dataType = "url"
	} else {
		dataType = "unknown"
		return nil, errors.New("Unknown data type")
	}

	j := &gocortex.JobBody{
		Data: s,
		Attributes: gocortex.ArtifactAttributes{
			DataType: dataType,
			TLP:      3,
		},
	}

	return j, nil
}
<commit_msg>Reduce number of messages from taxonomies<commit_after>package cortexbot

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"strings"

	valid "github.com/asaskevich/govalidator"
	"github.com/ilyaglow/go-cortex"
	"gopkg.in/telegram-bot-api.v4"
)

// ProcessCortex asks Cortex about data submitted by a user
func (c *Client) ProcessCortex(input *tgbotapi.Message) error {
	j, err := constructJob(input.Text)
	if err != nil {
		log.Println(err.Error())
		return err
	}

	// Run all analyzers over it with 1 minute timeout
	reports, err := c.Cortex.AnalyzeData(j, "1minute")
	if err != nil {
		msg := tgbotapi.NewMessage(input.Chat.ID, "")
		msg.ReplyToMessageID = input.MessageID
		msg.Text = fmt.Sprintf("Cortex failed with an error: %s", err.Error())
		c.Bot.Send(msg)
	}

	// Iterate over channel with reports and get taxonomies
	for m := range reports {
		if m.Status == "Failure" {
			continue
		}

		// Send taxonomies as a message
		msg := tgbotapi.NewMessage(input.Chat.ID, "")
		msg.ReplyToMessageID = input.MessageID
		msg.Text = buildTaxonomies(m.Taxonomies())
		c.Bot.Send(msg)

		// Send JSON file with full report
		tr, _ := json.MarshalIndent(m, "", "  ")

		fb := tgbotapi.FileBytes{
			Name:  fmt.Sprintf("%s-%s.json", m.AnalyzerID, m.ID),
			Bytes: tr,
		}

		attachment := tgbotapi.NewDocumentUpload(input.Chat.ID, fb)
		attachment.ReplyToMessageID = input.MessageID
		c.Bot.Send(attachment)
	}

	return nil
}

// buildTaxonomies joins taxonomies in one formatted string
// Every taxonomy is separated with two spaces from each other
func buildTaxonomies(txs []gocortex.Taxonomy) string {
	var stats []string

	for _, t := range txs {
		stats = append(stats, fmt.Sprintf("%s:%s = %s", t.Namespace, t.Predicate, t.Value))

	}
	return strings.Join(stats, ", ")
}

// constructJob make a JobBody depends on its type
func constructJob(s string) (*gocortex.JobBody, error) {
	var dataType string

	if valid.IsIP(s) {
		dataType = "ip"
	} else if IsDNSName(s) {
		dataType = "domain"
	} else if IsHash(s) {
		dataType = "hash"
	} else if valid.IsURL(s) {
		dataType = "url"
	} else {
		dataType = "unknown"
		return nil, errors.New("Unknown data type")
	}

	j := &gocortex.JobBody{
		Data: s,
		Attributes: gocortex.ArtifactAttributes{
			DataType: dataType,
			TLP:      3,
		},
	}

	return j, nil
}
<|endoftext|>


<commit_before>// Package bamstats provides functions for computing several mapping statistics on a BAM file.
package bamstats

import (
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/guigolab/bamstats/annotation"
	"github.com/guigolab/bamstats/config"
	"github.com/guigolab/bamstats/sam"
	"github.com/guigolab/bamstats/stats"
	"github.com/guigolab/bamstats/utils"
)

func init() {
	log.SetLevel(log.WarnLevel)
}

// var wg sync.WaitGroup

func worker(id int, in interface{}, out chan stats.StatsMap, index *annotation.RtreeMap, cfg *config.Config, wg *sync.WaitGroup) {
	defer wg.Done()
	logger := log.WithFields(log.Fields{
		"worker": id,
	})
	logger.Debug("Starting")

	sm := stats.NewStatsMap(true, (index != nil), cfg.Uniq)

	collectStats(in, sm, index)

	logger.Debug("Done")

	out <- sm
}

func collectStats(in interface{}, sm stats.StatsMap, index *annotation.RtreeMap) {
	switch in.(type) {
	case chan *sam.Record:
		c := in.(chan *sam.Record)
		for record := range c {
			for _, s := range sm {
				s.Collect(record, index)
			}
		}
	case chan *sam.Iterator:
		iterators := in.(chan *sam.Iterator)
		for it := range iterators {
			for it.Next() {
				for _, s := range sm {
					s.Collect(it.Record(), index)
				}
			}
		}
	}
}

func process(bamFile string, index *annotation.RtreeMap, cpu int, maxBuf int, reads int, uniq bool) (stats.StatsMap, error) {

	var wg sync.WaitGroup

	conf := config.NewConfig(cpu, maxBuf, reads, uniq)

	br, err := sam.NewReader(bamFile, conf)
	defer br.Close()
	if err != nil {
		return nil, err
	}
	statChan := make(chan stats.StatsMap, cpu)
	for i := 0; i < br.Workers; i++ {
		id := i + 1
		wg.Add(1)
		go worker(id, br.Channels[i], statChan, index, conf, &wg)
	}

	br.Read()

	go waitProcess(statChan, &wg)
	stat := <-statChan
	stat.Merge(statChan)
	if v, ok := stat["general"]; ok {
		s := v.(*stats.GeneralStats)
		s.Reads.Total += br.Unmapped()
		s.Finalize()
	}

	return stat, nil
}

func waitProcess(st chan stats.StatsMap, wg *sync.WaitGroup) {
	wg.Wait()
	close(st)
}

// Process process the input BAM file and collect different mapping stats.
func Process(bamFile string, anno string, cpu int, maxBuf int, reads int, uniq bool) (stats.StatsMap, error) {
	var index *annotation.RtreeMap
	if anno != "" {
		log.Infof("Creating index for %s", anno)
		start := time.Now()
		index = annotation.CreateIndex(anno, cpu)
		log.Infof("Index done in %v", time.Since(start))
	}
	start := time.Now()
	log.Infof("Collecting stats for %s", bamFile)
	stats, err := process(bamFile, index, cpu, maxBuf, reads, uniq)
	if err != nil {
		return nil, err
	}
	log.Infof("Stats done in %v", time.Since(start))
	return stats, nil
}

func WriteOutput(output string, st stats.StatsMap) {
	out := utils.NewOutput(output)
	utils.OutputJSON(out, st)
}
<commit_msg>Fix issue with CoverageStats non finalizing when running with 1 cpu<commit_after>// Package bamstats provides functions for computing several mapping statistics on a BAM file.
package bamstats

import (
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/guigolab/bamstats/annotation"
	"github.com/guigolab/bamstats/config"
	"github.com/guigolab/bamstats/sam"
	"github.com/guigolab/bamstats/stats"
	"github.com/guigolab/bamstats/utils"
)

func init() {
	log.SetLevel(log.WarnLevel)
}

// var wg sync.WaitGroup

func worker(id int, in interface{}, out chan stats.StatsMap, index *annotation.RtreeMap, cfg *config.Config, wg *sync.WaitGroup) {
	defer wg.Done()
	logger := log.WithFields(log.Fields{
		"worker": id,
	})
	logger.Debug("Starting")

	sm := stats.NewStatsMap(true, (index != nil), cfg.Uniq)

	collectStats(in, sm, index)

	logger.Debug("Done")

	out <- sm
}

func collectStats(in interface{}, sm stats.StatsMap, index *annotation.RtreeMap) {
	switch in.(type) {
	case chan *sam.Record:
		c := in.(chan *sam.Record)
		for record := range c {
			for _, s := range sm {
				s.Collect(record, index)
			}
		}
	case chan *sam.Iterator:
		iterators := in.(chan *sam.Iterator)
		for it := range iterators {
			for it.Next() {
				for _, s := range sm {
					s.Collect(it.Record(), index)
				}
			}
		}
	}
}

func process(bamFile string, index *annotation.RtreeMap, cpu int, maxBuf int, reads int, uniq bool) (stats.StatsMap, error) {

	var wg sync.WaitGroup

	conf := config.NewConfig(cpu, maxBuf, reads, uniq)

	br, err := sam.NewReader(bamFile, conf)
	defer br.Close()
	if err != nil {
		return nil, err
	}
	statChan := make(chan stats.StatsMap, cpu)
	for i := 0; i < br.Workers; i++ {
		id := i + 1
		wg.Add(1)
		go worker(id, br.Channels[i], statChan, index, conf, &wg)
	}

	br.Read()

	go waitProcess(statChan, &wg)
	stat := <-statChan
	stat.Merge(statChan)
	for k, v := range stat {
		v.Finalize()
		if k == "general" {
			s := v.(*stats.GeneralStats)
			s.Reads.Total += br.Unmapped()
		}
	}

	return stat, nil
}

func waitProcess(st chan stats.StatsMap, wg *sync.WaitGroup) {
	wg.Wait()
	close(st)
}

// Process process the input BAM file and collect different mapping stats.
func Process(bamFile string, anno string, cpu int, maxBuf int, reads int, uniq bool) (stats.StatsMap, error) {
	var index *annotation.RtreeMap
	if anno != "" {
		log.Infof("Creating index for %s", anno)
		start := time.Now()
		index = annotation.CreateIndex(anno, cpu)
		log.Infof("Index done in %v", time.Since(start))
	}
	start := time.Now()
	log.Infof("Collecting stats for %s", bamFile)
	stats, err := process(bamFile, index, cpu, maxBuf, reads, uniq)
	if err != nil {
		return nil, err
	}
	log.Infof("Stats done in %v", time.Since(start))
	return stats, nil
}

func WriteOutput(output string, st stats.StatsMap) {
	out := utils.NewOutput(output)
	utils.OutputJSON(out, st)
}
<|endoftext|>


<commit_before>// +build k3s

package k8s

import (
	"context"
	"fmt"
	"os"

	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/kubernetes/cmd/server"
)

func getEmbedded(ctx context.Context) (bool, context.Context, *rest.Config, error) {
	sc, ok := ctx.Value(serverConfig).(*server.ServerConfig)
	if !ok {
		return false, ctx, nil, fmt.Errorf("failed to find k3s config")
	}

	err := server.Server(ctx, sc)
	if err != nil {
		return false, ctx, nil, err
	}

	os.Setenv("KUBECONFIG", sc.KubeConfig)
	restConfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		&clientcmd.ClientConfigLoadingRules{ExplicitPath: sc.KubeConfig}, &clientcmd.ConfigOverrides{}).ClientConfig()

	return true, ctx, restConfig, err
}
<commit_msg>Switch to k3s 1.12<commit_after>// +build k3s

package k8s

import (
	"context"
	"fmt"
	"os"

	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/kubernetes/pkg/wrapper/server"
)

func getEmbedded(ctx context.Context) (bool, context.Context, *rest.Config, error) {
	sc, ok := ctx.Value(serverConfig).(*server.ServerConfig)
	if !ok {
		return false, ctx, nil, fmt.Errorf("failed to find k3s config")
	}

	err := server.Server(ctx, sc)
	if err != nil {
		return false, ctx, nil, err
	}

	os.Setenv("KUBECONFIG", sc.KubeConfig)
	restConfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		&clientcmd.ClientConfigLoadingRules{ExplicitPath: sc.KubeConfig}, &clientcmd.ConfigOverrides{}).ClientConfig()

	return true, ctx, restConfig, err
}
<|endoftext|>


<commit_before>package k8s

import (
	"fmt"
	"time"

	"github.com/jtblin/kube2iam"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	v1 "k8s.io/client-go/pkg/api/v1"
	selector "k8s.io/client-go/pkg/fields"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/cache"
)

const (
	podIPIndexName     = "byPodIP"
	namespaceIndexName = "byName"
)

// Client represents a kubernetes client.
type Client struct {
	*kubernetes.Clientset
	namespaceController *cache.Controller
	namespaceIndexer    cache.Indexer
	podController       *cache.Controller
	podIndexer          cache.Indexer
	nodeName            string
}

// Returns a cache.ListWatch that gets all changes to pods.
func (k8s *Client) createPodLW() *cache.ListWatch {
	fieldSelector := selector.Everything()
	if k8s.nodeName != "" {
		fieldSelector = selector.OneTermEqualSelector("spec.nodeName", k8s.nodeName)
	}
	return cache.NewListWatchFromClient(k8s.CoreV1().RESTClient(), "pods", v1.NamespaceAll, fieldSelector)
}

// WatchForPods watches for pod changes.
func (k8s *Client) WatchForPods(podEventLogger cache.ResourceEventHandler, resyncPeriod time.Duration) cache.InformerSynced {
	k8s.podIndexer, k8s.podController = cache.NewIndexerInformer(
		k8s.createPodLW(),
		&v1.Pod{},
		resyncPeriod,
		podEventLogger,
		cache.Indexers{podIPIndexName: kube2iam.PodIPIndexFunc},
	)
	go k8s.podController.Run(wait.NeverStop)
	return k8s.podController.HasSynced
}

// returns a cache.ListWatch of namespaces.
func (k8s *Client) createNamespaceLW() *cache.ListWatch {
	return cache.NewListWatchFromClient(k8s.CoreV1().RESTClient(), "namespaces", v1.NamespaceAll, selector.Everything())
}

// WatchForNamespaces watches for namespaces changes.
func (k8s *Client) WatchForNamespaces(nsEventLogger cache.ResourceEventHandler, resyncPeriod time.Duration) cache.InformerSynced {
	k8s.namespaceIndexer, k8s.namespaceController = cache.NewIndexerInformer(
		k8s.createNamespaceLW(),
		&v1.Namespace{},
		resyncPeriod,
		nsEventLogger,
		cache.Indexers{namespaceIndexName: kube2iam.NamespaceIndexFunc},
	)
	go k8s.namespaceController.Run(wait.NeverStop)
	return k8s.namespaceController.HasSynced
}

// ListPodIPs returns the underlying set of pods being managed/indexed
func (k8s *Client) ListPodIPs() []string {
	// Decided to simply dump this and leave it up to consumer
	// as k8s package currently doesn't need to be concerned about what's
	// a signficant annotation to process, that is left up to store/server
	return k8s.podIndexer.ListIndexFuncValues(podIPIndexName)
}

// ListNamespaces returns the underlying set of namespaces being managed/indexed
func (k8s *Client) ListNamespaces() []string {
	return k8s.namespaceIndexer.ListIndexFuncValues(namespaceIndexName)
}

// PodByIP provides the representation of the pod itself being cached keyed off of it's IP
// Returns an error if there are multiple pods attempting to be keyed off of the same IP
// (Which happens when they of type `hostNetwork: true`)
func (k8s *Client) PodByIP(IP string) (*v1.Pod, error) {
	pods, err := k8s.podIndexer.ByIndex(podIPIndexName, IP)
	if err != nil {
		return nil, err
	}

	if len(pods) == 0 {
		return nil, fmt.Errorf("pod with specificed IP not found")
	}

	if len(pods) == 1 {
		return pods[0].(*v1.Pod), nil
	}

	//This happens with `hostNetwork: true` pods
	podNames := make([]string, len(pods))
	for i, pod := range pods {
		podNames[i] = pod.(*v1.Pod).ObjectMeta.Name
	}
	return nil, fmt.Errorf("%d pods (%v) with the ip %s indexed", len(pods), podNames, IP)
}

// NamespaceByName retrieves a namespace by it's given name.
// Returns an error if there are no namespaces available
func (k8s *Client) NamespaceByName(namespaceName string) (*v1.Namespace, error) {
	namespace, err := k8s.namespaceIndexer.ByIndex(namespaceIndexName, namespaceName)
	if err != nil {
		return nil, err
	}

	if len(namespace) == 0 {
		return nil, fmt.Errorf("namespace was not found")
	}

	return namespace[0].(*v1.Namespace), nil
}

// NewClient returns a new kubernetes client.
func NewClient(host, token, nodeName string, insecure bool) (*Client, error) {
	var config *rest.Config
	var err error
	if host != "" && token != "" {
		config = &rest.Config{
			Host:        host,
			BearerToken: token,
			Insecure:    insecure,
		}
	} else {
		config, err = rest.InClusterConfig()
		if err != nil {
			return nil, err
		}
	}
	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}
	return &Client{Clientset: client, nodeName: nodeName}, nil
}
<commit_msg>Fix typo: specificed => specified (#214)<commit_after>package k8s

import (
	"fmt"
	"time"

	"github.com/jtblin/kube2iam"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	v1 "k8s.io/client-go/pkg/api/v1"
	selector "k8s.io/client-go/pkg/fields"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/cache"
)

const (
	podIPIndexName     = "byPodIP"
	namespaceIndexName = "byName"
)

// Client represents a kubernetes client.
type Client struct {
	*kubernetes.Clientset
	namespaceController *cache.Controller
	namespaceIndexer    cache.Indexer
	podController       *cache.Controller
	podIndexer          cache.Indexer
	nodeName            string
}

// Returns a cache.ListWatch that gets all changes to pods.
func (k8s *Client) createPodLW() *cache.ListWatch {
	fieldSelector := selector.Everything()
	if k8s.nodeName != "" {
		fieldSelector = selector.OneTermEqualSelector("spec.nodeName", k8s.nodeName)
	}
	return cache.NewListWatchFromClient(k8s.CoreV1().RESTClient(), "pods", v1.NamespaceAll, fieldSelector)
}

// WatchForPods watches for pod changes.
func (k8s *Client) WatchForPods(podEventLogger cache.ResourceEventHandler, resyncPeriod time.Duration) cache.InformerSynced {
	k8s.podIndexer, k8s.podController = cache.NewIndexerInformer(
		k8s.createPodLW(),
		&v1.Pod{},
		resyncPeriod,
		podEventLogger,
		cache.Indexers{podIPIndexName: kube2iam.PodIPIndexFunc},
	)
	go k8s.podController.Run(wait.NeverStop)
	return k8s.podController.HasSynced
}

// returns a cache.ListWatch of namespaces.
func (k8s *Client) createNamespaceLW() *cache.ListWatch {
	return cache.NewListWatchFromClient(k8s.CoreV1().RESTClient(), "namespaces", v1.NamespaceAll, selector.Everything())
}

// WatchForNamespaces watches for namespaces changes.
func (k8s *Client) WatchForNamespaces(nsEventLogger cache.ResourceEventHandler, resyncPeriod time.Duration) cache.InformerSynced {
	k8s.namespaceIndexer, k8s.namespaceController = cache.NewIndexerInformer(
		k8s.createNamespaceLW(),
		&v1.Namespace{},
		resyncPeriod,
		nsEventLogger,
		cache.Indexers{namespaceIndexName: kube2iam.NamespaceIndexFunc},
	)
	go k8s.namespaceController.Run(wait.NeverStop)
	return k8s.namespaceController.HasSynced
}

// ListPodIPs returns the underlying set of pods being managed/indexed
func (k8s *Client) ListPodIPs() []string {
	// Decided to simply dump this and leave it up to consumer
	// as k8s package currently doesn't need to be concerned about what's
	// a signficant annotation to process, that is left up to store/server
	return k8s.podIndexer.ListIndexFuncValues(podIPIndexName)
}

// ListNamespaces returns the underlying set of namespaces being managed/indexed
func (k8s *Client) ListNamespaces() []string {
	return k8s.namespaceIndexer.ListIndexFuncValues(namespaceIndexName)
}

// PodByIP provides the representation of the pod itself being cached keyed off of it's IP
// Returns an error if there are multiple pods attempting to be keyed off of the same IP
// (Which happens when they of type `hostNetwork: true`)
func (k8s *Client) PodByIP(IP string) (*v1.Pod, error) {
	pods, err := k8s.podIndexer.ByIndex(podIPIndexName, IP)
	if err != nil {
		return nil, err
	}

	if len(pods) == 0 {
		return nil, fmt.Errorf("pod with specified IP not found")
	}

	if len(pods) == 1 {
		return pods[0].(*v1.Pod), nil
	}

	//This happens with `hostNetwork: true` pods
	podNames := make([]string, len(pods))
	for i, pod := range pods {
		podNames[i] = pod.(*v1.Pod).ObjectMeta.Name
	}
	return nil, fmt.Errorf("%d pods (%v) with the ip %s indexed", len(pods), podNames, IP)
}

// NamespaceByName retrieves a namespace by it's given name.
// Returns an error if there are no namespaces available
func (k8s *Client) NamespaceByName(namespaceName string) (*v1.Namespace, error) {
	namespace, err := k8s.namespaceIndexer.ByIndex(namespaceIndexName, namespaceName)
	if err != nil {
		return nil, err
	}

	if len(namespace) == 0 {
		return nil, fmt.Errorf("namespace was not found")
	}

	return namespace[0].(*v1.Namespace), nil
}

// NewClient returns a new kubernetes client.
func NewClient(host, token, nodeName string, insecure bool) (*Client, error) {
	var config *rest.Config
	var err error
	if host != "" && token != "" {
		config = &rest.Config{
			Host:        host,
			BearerToken: token,
			Insecure:    insecure,
		}
	} else {
		config, err = rest.InClusterConfig()
		if err != nil {
			return nil, err
		}
	}
	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}
	return &Client{Clientset: client, nodeName: nodeName}, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 govppmux

import (
	"context"
	"sync"
	"time"

	"git.fd.io/govpp.git/adapter"
	govppapi "git.fd.io/govpp.git/api"
	govpp "git.fd.io/govpp.git/core"
	"github.com/ligato/cn-infra/datasync/resync"
	"github.com/ligato/cn-infra/health/statuscheck"
	"github.com/ligato/cn-infra/infra"
	"github.com/ligato/cn-infra/logging"
	"github.com/ligato/cn-infra/logging/logrus"
	"github.com/ligato/cn-infra/logging/measure"
	"github.com/ligato/cn-infra/logging/measure/model/apitrace"
	"github.com/pkg/errors"

	"github.com/ligato/vpp-agent/plugins/govppmux/vppcalls"
	_ "github.com/ligato/vpp-agent/plugins/govppmux/vppcalls/vpp1810"
)

// Default path to socket for VPP stats
const defaultStatsSocket = "/run/vpp/stats.sock"

// Plugin implements the govppmux plugin interface.
type Plugin struct {
	Deps

	vppConn      *govpp.Connection
	vppAdapter   adapter.VppAPI
	statsAdapter adapter.StatsAPI
	vppConChan   chan govpp.ConnectionEvent

	lastConnErr error

	config *Config

	// Cancel can be used to cancel all goroutines and their jobs inside of the plugin.
	cancel context.CancelFunc

	// Plugin-wide tracer instance used to trace and time-measure binary API calls. Can be nil if not set.
	tracer measure.Tracer

	// Wait group allows to wait until all goroutines of the plugin have finished.
	wg sync.WaitGroup
}

// Deps groups injected dependencies of plugin
// so that they do not mix with other plugin fields.
type Deps struct {
	infra.PluginDeps
	StatusCheck statuscheck.PluginStatusWriter
	Resync      *resync.Plugin
}

// Config groups the configurable parameter of GoVpp.
type Config struct {
	TraceEnabled             bool          `json:"trace-enabled"`
	ReconnectResync          bool          `json:"resync-after-reconnect"`
	HealthCheckProbeInterval time.Duration `json:"health-check-probe-interval"`
	HealthCheckReplyTimeout  time.Duration `json:"health-check-reply-timeout"`
	HealthCheckThreshold     int           `json:"health-check-threshold"`
	ReplyTimeout             time.Duration `json:"reply-timeout"`
	// The prefix prepended to the name used for shared memory (SHM) segments. If not set,
	// shared memory segments are created directly in the SHM directory /dev/shm.
	ShmPrefix       string `json:"shm-prefix"`
	StatsSocketName string `json:"stats-socket-name"`
	// How many times can be request resent in case vpp is suddenly disconnected.
	RetryRequestCount int `json:"retry-request-count"`
	// Time between request resend attempts. Default is 500ms.
	RetryRequestTimeout time.Duration `json:"retry-request-timeout"`
}

func defaultConfig() *Config {
	return &Config{
		HealthCheckProbeInterval: time.Second,
		HealthCheckReplyTimeout:  250 * time.Millisecond,
		HealthCheckThreshold:     1,
		ReplyTimeout:             time.Second,
		RetryRequestTimeout:      500 * time.Millisecond,
	}
}

func (p *Plugin) loadConfig() (*Config, error) {
	cfg := defaultConfig()

	found, err := p.Cfg.LoadValue(cfg)
	if err != nil {
		return nil, err
	} else if found {
		p.Log.Debugf("config loaded from file %q", p.Cfg.GetConfigName())
	} else {
		p.Log.Debugf("config file %q not found, using default config", p.Cfg.GetConfigName())
	}

	return cfg, nil
}

// Init is the entry point called by Agent Core. A single binary-API connection to VPP is established.
func (p *Plugin) Init() error {
	var err error

	govppLogger := p.Deps.Log.NewLogger("govpp")
	if govppLogger, ok := govppLogger.(*logrus.Logger); ok {
		govppLogger.SetLevel(logging.InfoLevel)
		govpp.SetLogger(govppLogger.StandardLogger())
	}

	if p.config, err = p.loadConfig(); err != nil {
		return err
	}

	p.Log.Debugf("config: %+v", p.config)
	govpp.HealthCheckProbeInterval = p.config.HealthCheckProbeInterval
	govpp.HealthCheckReplyTimeout = p.config.HealthCheckReplyTimeout
	govpp.HealthCheckThreshold = p.config.HealthCheckThreshold
	govpp.DefaultReplyTimeout = p.config.ReplyTimeout
	if p.config.TraceEnabled {
		p.tracer = measure.NewTracer("govpp-mux")
		p.Log.Info("VPP API trace enabled")
	}

	if p.vppAdapter == nil {
		p.vppAdapter = NewVppAdapter(p.config.ShmPrefix)
	} else {
		// this is used for testing purposes
		p.Log.Info("Reusing existing vppAdapter")
	}

	startTime := time.Now()
	p.vppConn, p.vppConChan, err = govpp.AsyncConnect(p.vppAdapter)
	if err != nil {
		return err
	}

	// TODO: Async connect & automatic reconnect support is not yet implemented in the agent,
	// so synchronously wait until connected to VPP.
	status := <-p.vppConChan
	if status.State != govpp.Connected {
		return errors.New("unable to connect to VPP")
	}
	vppConnectTime := time.Since(startTime)

	vppAPIChan, err := p.vppConn.NewAPIChannel()
	if err != nil {
		p.Log.Error("getting new api channel failed:", err)
		return err
	}
	defer vppAPIChan.Close()

	vpeHandler := vppcalls.CompatibleVpeHandler(vppAPIChan)

	ver, err := vpeHandler.GetVersionInfo()
	if err != nil {
		p.Log.Errorf("retrieving version info failed: %v", err)
		return err
	}
	info, err := vpeHandler.GetVpeInfo()
	if err != nil {
		p.Log.Errorf("retrieving vpe info failed: %v", err)
		return err
	}
	p.Log.Infof("Connected to VPP %v [PID:%d, ClientIdx:%d] (took %s)",
		ver.Version, info.PID, info.ClientIdx, vppConnectTime.Truncate(time.Millisecond))

	// Register providing status reports (push mode)
	p.StatusCheck.Register(p.PluginName, nil)
	p.StatusCheck.ReportStateChange(p.PluginName, statuscheck.OK, nil)

	var ctx context.Context
	ctx, p.cancel = context.WithCancel(context.Background())
	go p.handleVPPConnectionEvents(ctx)

	// Connect to VPP status socket
	if p.config.StatsSocketName != "" {
		p.statsAdapter = NewStatsAdapter(p.config.StatsSocketName)
	} else {
		p.statsAdapter = NewStatsAdapter(defaultStatsSocket)
	}
	if err := p.statsAdapter.Connect(); err != nil {
		p.Log.Warnf("Unable to connect to VPP statistics socket, %v", err)
		p.statsAdapter = nil
	}

	return nil
}

// Close cleans up the resources allocated by the govppmux plugin.
func (p *Plugin) Close() error {
	p.cancel()
	p.wg.Wait()

	defer func() {
		if p.vppConn != nil {
			p.vppConn.Disconnect()
		}
		if p.statsAdapter != nil {
			if err := p.statsAdapter.Disconnect(); err != nil {
				p.Log.Errorf("VPP statistics socket adapter disconnect error: %v", err)
			}
		}
	}()

	return nil
}

// NewAPIChannel returns a new API channel for communication with VPP via govpp core.
// It uses default buffer sizes for the request and reply Go channels.
//
// Example of binary API call from some plugin using GOVPP:
//      ch, _ := govpp_mux.NewAPIChannel()
//      ch.SendRequest(req).ReceiveReply
func (p *Plugin) NewAPIChannel() (govppapi.Channel, error) {
	ch, err := p.vppConn.NewAPIChannel()
	if err != nil {
		return nil, err
	}
	retryCfg := retryConfig{
		p.config.RetryRequestCount,
		p.config.RetryRequestTimeout,
	}
	return &goVppChan{ch, retryCfg, p.tracer}, nil
}

// NewAPIChannelBuffered returns a new API channel for communication with VPP via govpp core.
// It allows to specify custom buffer sizes for the request and reply Go channels.
//
// Example of binary API call from some plugin using GOVPP:
//      ch, _ := govpp_mux.NewAPIChannelBuffered(100, 100)
//      ch.SendRequest(req).ReceiveReply
func (p *Plugin) NewAPIChannelBuffered(reqChanBufSize, replyChanBufSize int) (govppapi.Channel, error) {
	ch, err := p.vppConn.NewAPIChannelBuffered(reqChanBufSize, replyChanBufSize)
	if err != nil {
		return nil, err
	}
	retryCfg := retryConfig{
		p.config.RetryRequestCount,
		p.config.RetryRequestTimeout,
	}
	return &goVppChan{ch, retryCfg, p.tracer}, nil
}

// GetTrace returns all trace entries measured so far
func (p *Plugin) GetTrace() *apitrace.Trace {
	if !p.config.TraceEnabled {
		p.Log.Warnf("VPP API trace is disabled")
		return nil
	}
	return p.tracer.Get()
}

// ListStats returns all stats names
func (p *Plugin) ListStats(prefixes ...string) ([]string, error) {
	if p.statsAdapter == nil {
		return nil, nil
	}
	return p.statsAdapter.ListStats(prefixes...)
}

// DumpStats returns all stats with name, type and value
func (p *Plugin) DumpStats(prefixes ...string) ([]*adapter.StatEntry, error) {
	if p.statsAdapter == nil {
		return nil, nil
	}
	return p.statsAdapter.DumpStats(prefixes...)
}

// handleVPPConnectionEvents handles VPP connection events.
func (p *Plugin) handleVPPConnectionEvents(ctx context.Context) {
	p.wg.Add(1)
	defer p.wg.Done()

	for {
		select {
		case status := <-p.vppConChan:
			if status.State == govpp.Connected {
				vppAPIChan, err := p.vppConn.NewAPIChannel()
				if err != nil {
					p.Log.Error("getting new api channel failed:", err)
					return
				}
				defer vppAPIChan.Close()

				vpeHandler := vppcalls.CompatibleVpeHandler(vppAPIChan)
				vpeHandler.GetVersionInfo()
				vpeHandler.GetVpeInfo()
				if p.config.ReconnectResync && p.lastConnErr != nil {
					p.Log.Info("Starting resync after VPP reconnect")
					if p.Resync != nil {
						p.Resync.DoResync()
						p.lastConnErr = nil
					} else {
						p.Log.Warn("Expected resync after VPP reconnect could not start because of missing Resync plugin")
					}
				}
				p.StatusCheck.ReportStateChange(p.PluginName, statuscheck.OK, nil)
			} else {
				p.lastConnErr = errors.Errorf("VPP disconnected (status: %v)", status)
				p.StatusCheck.ReportStateChange(p.PluginName, statuscheck.Error, p.lastConnErr)
			}

		case <-ctx.Done():
			return
		}
	}
}

/*func (p *Plugin) retrieveVpeInfo() (*vppcalls.VpeInfo, error) {
	vppAPIChan, err := p.vppConn.NewAPIChannel()
	if err != nil {
		p.Log.Error("getting new api channel failed:", err)
		return nil, err
	}
	defer vppAPIChan.Close()

	info, err := vppcalls.GetVpeInfo(vppAPIChan)
	if err != nil {
		p.Log.Warn("getting version info failed:", err)
		return nil, err
	}
	p.Log.Debugf("vpp module versions: %+v", info.ModuleVersions)

	return info, nil
}

func (p *Plugin) retrieveVersion() {
	vppAPIChan, err := p.vppConn.NewAPIChannel()
	if err != nil {
		p.Log.Error("getting new api channel failed:", err)
		return
	}
	defer vppAPIChan.Close()

	version, err := vppcalls.GetVersionInfo(vppAPIChan)
	if err != nil {
		p.Log.Warn("getting version info failed:", err)
		return
	}

	p.Log.Debugf("version info: %+v", version)
	p.Log.Infof("VPP version: %q (%v)", version.Version, version.BuildDate)

	// Get VPP ACL plugin version
	var aclVersion string
	if aclVersion, err = vppcalls.GetACLPluginVersion(vppAPIChan); err != nil {
		p.Log.Warn("getting acl version info failed:", err)
		return
	}
	p.Log.Infof("VPP ACL plugin version: %q", aclVersion)
}
*/
<commit_msg>Print modules versions<commit_after>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 govppmux

import (
	"context"
	"sync"
	"time"

	"git.fd.io/govpp.git/adapter"
	govppapi "git.fd.io/govpp.git/api"
	govpp "git.fd.io/govpp.git/core"
	"github.com/ligato/cn-infra/datasync/resync"
	"github.com/ligato/cn-infra/health/statuscheck"
	"github.com/ligato/cn-infra/infra"
	"github.com/ligato/cn-infra/logging"
	"github.com/ligato/cn-infra/logging/logrus"
	"github.com/ligato/cn-infra/logging/measure"
	"github.com/ligato/cn-infra/logging/measure/model/apitrace"
	"github.com/pkg/errors"

	"github.com/ligato/vpp-agent/plugins/govppmux/vppcalls"
	_ "github.com/ligato/vpp-agent/plugins/govppmux/vppcalls/vpp1810"
)

// Default path to socket for VPP stats
const defaultStatsSocket = "/run/vpp/stats.sock"

// Plugin implements the govppmux plugin interface.
type Plugin struct {
	Deps

	vppConn      *govpp.Connection
	vppAdapter   adapter.VppAPI
	statsAdapter adapter.StatsAPI
	vppConChan   chan govpp.ConnectionEvent

	lastConnErr error

	config *Config

	// Cancel can be used to cancel all goroutines and their jobs inside of the plugin.
	cancel context.CancelFunc

	// Plugin-wide tracer instance used to trace and time-measure binary API calls. Can be nil if not set.
	tracer measure.Tracer

	// Wait group allows to wait until all goroutines of the plugin have finished.
	wg sync.WaitGroup
}

// Deps groups injected dependencies of plugin
// so that they do not mix with other plugin fields.
type Deps struct {
	infra.PluginDeps
	StatusCheck statuscheck.PluginStatusWriter
	Resync      *resync.Plugin
}

// Config groups the configurable parameter of GoVpp.
type Config struct {
	TraceEnabled             bool          `json:"trace-enabled"`
	ReconnectResync          bool          `json:"resync-after-reconnect"`
	HealthCheckProbeInterval time.Duration `json:"health-check-probe-interval"`
	HealthCheckReplyTimeout  time.Duration `json:"health-check-reply-timeout"`
	HealthCheckThreshold     int           `json:"health-check-threshold"`
	ReplyTimeout             time.Duration `json:"reply-timeout"`
	// The prefix prepended to the name used for shared memory (SHM) segments. If not set,
	// shared memory segments are created directly in the SHM directory /dev/shm.
	ShmPrefix       string `json:"shm-prefix"`
	StatsSocketName string `json:"stats-socket-name"`
	// How many times can be request resent in case vpp is suddenly disconnected.
	RetryRequestCount int `json:"retry-request-count"`
	// Time between request resend attempts. Default is 500ms.
	RetryRequestTimeout time.Duration `json:"retry-request-timeout"`
}

func defaultConfig() *Config {
	return &Config{
		HealthCheckProbeInterval: time.Second,
		HealthCheckReplyTimeout:  250 * time.Millisecond,
		HealthCheckThreshold:     1,
		ReplyTimeout:             time.Second,
		RetryRequestTimeout:      500 * time.Millisecond,
	}
}

func (p *Plugin) loadConfig() (*Config, error) {
	cfg := defaultConfig()

	found, err := p.Cfg.LoadValue(cfg)
	if err != nil {
		return nil, err
	} else if found {
		p.Log.Debugf("config loaded from file %q", p.Cfg.GetConfigName())
	} else {
		p.Log.Debugf("config file %q not found, using default config", p.Cfg.GetConfigName())
	}

	return cfg, nil
}

// Init is the entry point called by Agent Core. A single binary-API connection to VPP is established.
func (p *Plugin) Init() error {
	var err error

	govppLogger := p.Deps.Log.NewLogger("govpp")
	if govppLogger, ok := govppLogger.(*logrus.Logger); ok {
		govppLogger.SetLevel(logging.InfoLevel)
		govpp.SetLogger(govppLogger.StandardLogger())
	}

	if p.config, err = p.loadConfig(); err != nil {
		return err
	}

	p.Log.Debugf("config: %+v", p.config)
	govpp.HealthCheckProbeInterval = p.config.HealthCheckProbeInterval
	govpp.HealthCheckReplyTimeout = p.config.HealthCheckReplyTimeout
	govpp.HealthCheckThreshold = p.config.HealthCheckThreshold
	govpp.DefaultReplyTimeout = p.config.ReplyTimeout
	if p.config.TraceEnabled {
		p.tracer = measure.NewTracer("govpp-mux")
		p.Log.Info("VPP API trace enabled")
	}

	if p.vppAdapter == nil {
		p.vppAdapter = NewVppAdapter(p.config.ShmPrefix)
	} else {
		// this is used for testing purposes
		p.Log.Info("Reusing existing vppAdapter")
	}

	startTime := time.Now()
	p.vppConn, p.vppConChan, err = govpp.AsyncConnect(p.vppAdapter)
	if err != nil {
		return err
	}

	// TODO: Async connect & automatic reconnect support is not yet implemented in the agent,
	// so synchronously wait until connected to VPP.
	status := <-p.vppConChan
	if status.State != govpp.Connected {
		return errors.New("unable to connect to VPP")
	}
	vppConnectTime := time.Since(startTime)

	vppAPIChan, err := p.vppConn.NewAPIChannel()
	if err != nil {
		p.Log.Error("getting new api channel failed:", err)
		return err
	}
	defer vppAPIChan.Close()

	vpeHandler := vppcalls.CompatibleVpeHandler(vppAPIChan)

	ver, err := vpeHandler.GetVersionInfo()
	if err != nil {
		p.Log.Errorf("retrieving version info failed: %v", err)
		return err
	}
	info, err := vpeHandler.GetVpeInfo()
	if err != nil {
		p.Log.Errorf("retrieving vpe info failed: %v", err)
		return err
	}
	p.Log.Infof("Connected to VPP %v [PID:%d, ClientIdx:%d] (took %s)",
		ver.Version, info.PID, info.ClientIdx, vppConnectTime.Truncate(time.Millisecond))
	p.Log.Debugf("loaded module versions: %v", info.ModuleVersions)

	// Register providing status reports (push mode)
	p.StatusCheck.Register(p.PluginName, nil)
	p.StatusCheck.ReportStateChange(p.PluginName, statuscheck.OK, nil)

	var ctx context.Context
	ctx, p.cancel = context.WithCancel(context.Background())
	go p.handleVPPConnectionEvents(ctx)

	// Connect to VPP status socket
	if p.config.StatsSocketName != "" {
		p.statsAdapter = NewStatsAdapter(p.config.StatsSocketName)
	} else {
		p.statsAdapter = NewStatsAdapter(defaultStatsSocket)
	}
	if err := p.statsAdapter.Connect(); err != nil {
		p.Log.Warnf("Unable to connect to VPP statistics socket, %v", err)
		p.statsAdapter = nil
	}

	return nil
}

// Close cleans up the resources allocated by the govppmux plugin.
func (p *Plugin) Close() error {
	p.cancel()
	p.wg.Wait()

	defer func() {
		if p.vppConn != nil {
			p.vppConn.Disconnect()
		}
		if p.statsAdapter != nil {
			if err := p.statsAdapter.Disconnect(); err != nil {
				p.Log.Errorf("VPP statistics socket adapter disconnect error: %v", err)
			}
		}
	}()

	return nil
}

// NewAPIChannel returns a new API channel for communication with VPP via govpp core.
// It uses default buffer sizes for the request and reply Go channels.
//
// Example of binary API call from some plugin using GOVPP:
//      ch, _ := govpp_mux.NewAPIChannel()
//      ch.SendRequest(req).ReceiveReply
func (p *Plugin) NewAPIChannel() (govppapi.Channel, error) {
	ch, err := p.vppConn.NewAPIChannel()
	if err != nil {
		return nil, err
	}
	retryCfg := retryConfig{
		p.config.RetryRequestCount,
		p.config.RetryRequestTimeout,
	}
	return &goVppChan{ch, retryCfg, p.tracer}, nil
}

// NewAPIChannelBuffered returns a new API channel for communication with VPP via govpp core.
// It allows to specify custom buffer sizes for the request and reply Go channels.
//
// Example of binary API call from some plugin using GOVPP:
//      ch, _ := govpp_mux.NewAPIChannelBuffered(100, 100)
//      ch.SendRequest(req).ReceiveReply
func (p *Plugin) NewAPIChannelBuffered(reqChanBufSize, replyChanBufSize int) (govppapi.Channel, error) {
	ch, err := p.vppConn.NewAPIChannelBuffered(reqChanBufSize, replyChanBufSize)
	if err != nil {
		return nil, err
	}
	retryCfg := retryConfig{
		p.config.RetryRequestCount,
		p.config.RetryRequestTimeout,
	}
	return &goVppChan{ch, retryCfg, p.tracer}, nil
}

// GetTrace returns all trace entries measured so far
func (p *Plugin) GetTrace() *apitrace.Trace {
	if !p.config.TraceEnabled {
		p.Log.Warnf("VPP API trace is disabled")
		return nil
	}
	return p.tracer.Get()
}

// ListStats returns all stats names
func (p *Plugin) ListStats(prefixes ...string) ([]string, error) {
	if p.statsAdapter == nil {
		return nil, nil
	}
	return p.statsAdapter.ListStats(prefixes...)
}

// DumpStats returns all stats with name, type and value
func (p *Plugin) DumpStats(prefixes ...string) ([]*adapter.StatEntry, error) {
	if p.statsAdapter == nil {
		return nil, nil
	}
	return p.statsAdapter.DumpStats(prefixes...)
}

// handleVPPConnectionEvents handles VPP connection events.
func (p *Plugin) handleVPPConnectionEvents(ctx context.Context) {
	p.wg.Add(1)
	defer p.wg.Done()

	for {
		select {
		case status := <-p.vppConChan:
			if status.State == govpp.Connected {
				vppAPIChan, err := p.vppConn.NewAPIChannel()
				if err != nil {
					p.Log.Error("getting new api channel failed:", err)
					return
				}
				defer vppAPIChan.Close()

				vpeHandler := vppcalls.CompatibleVpeHandler(vppAPIChan)
				vpeHandler.GetVersionInfo()
				vpeHandler.GetVpeInfo()
				if p.config.ReconnectResync && p.lastConnErr != nil {
					p.Log.Info("Starting resync after VPP reconnect")
					if p.Resync != nil {
						p.Resync.DoResync()
						p.lastConnErr = nil
					} else {
						p.Log.Warn("Expected resync after VPP reconnect could not start because of missing Resync plugin")
					}
				}
				p.StatusCheck.ReportStateChange(p.PluginName, statuscheck.OK, nil)
			} else {
				p.lastConnErr = errors.Errorf("VPP disconnected (status: %v)", status)
				p.StatusCheck.ReportStateChange(p.PluginName, statuscheck.Error, p.lastConnErr)
			}

		case <-ctx.Done():
			return
		}
	}
}

/*func (p *Plugin) retrieveVpeInfo() (*vppcalls.VpeInfo, error) {
	vppAPIChan, err := p.vppConn.NewAPIChannel()
	if err != nil {
		p.Log.Error("getting new api channel failed:", err)
		return nil, err
	}
	defer vppAPIChan.Close()

	info, err := vppcalls.GetVpeInfo(vppAPIChan)
	if err != nil {
		p.Log.Warn("getting version info failed:", err)
		return nil, err
	}
	p.Log.Debugf("vpp module versions: %+v", info.ModuleVersions)

	return info, nil
}

func (p *Plugin) retrieveVersion() {
	vppAPIChan, err := p.vppConn.NewAPIChannel()
	if err != nil {
		p.Log.Error("getting new api channel failed:", err)
		return
	}
	defer vppAPIChan.Close()

	version, err := vppcalls.GetVersionInfo(vppAPIChan)
	if err != nil {
		p.Log.Warn("getting version info failed:", err)
		return
	}

	p.Log.Debugf("version info: %+v", version)
	p.Log.Infof("VPP version: %q (%v)", version.Version, version.BuildDate)

	// Get VPP ACL plugin version
	var aclVersion string
	if aclVersion, err = vppcalls.GetACLPluginVersion(vppAPIChan); err != nil {
		p.Log.Warn("getting acl version info failed:", err)
		return
	}
	p.Log.Infof("VPP ACL plugin version: %q", aclVersion)
}
*/
<|endoftext|>


<commit_before>package main

import (

	"database/sql"
	"strconv"
	log "github.com/Sirupsen/logrus"
)

//Insert World Event for Hero, adds a world event notification for a specific hero
//Parameters: Hero ID, World Event Message and the SQL.DB connection.
//Return: None
func Insert_World_Event_for_Hero(heroID int64, worldEvent string, conn *sql.DB) {

	log.Info("[DAL] Insert_World_Event_for_Hero : HeroID: " + strconv.FormatInt(heroID, 10) + " | Event: " + worldEvent)

	var query = "INSERT INTO worldevent (event_text) VALUES ('" + worldEvent + "')"

	log.Info("[DAL] World Event Query: " + query)

	statement, err := conn.Exec(query)

	if err != nil {
		log.Errorln("[DAL] Insert_Item_for_Hero: Worldevent Insert failed: %s", err)
	}

	last_worldevent_Id, err := statement.LastInsertId()

	if last_worldevent_Id != 0 {

		Insert_Hero_World_Event(heroID, int(last_worldevent_Id), conn)
	}
}

//Insert Hero World Event, populates the table that maps Heros with their World events
//Parameters: Hero ID, World Event ID and the SQL.DB connection.
//Return: None
func Insert_Hero_World_Event(heroID int64, woldEvent_id int, conn *sql.DB) {

	var query = "INSERT INTO heroworldevent (hero_id, worldevent_id ) VALUES (" + strconv.FormatInt(heroID, 10) + ", " + strconv.Itoa(woldEvent_id) + ")"

	log.Info("[DAL] Heroworldevent Query: " + query)

	_, err := conn.Exec(query)

	if err != nil {
		log.Errorln("[DAL] Insert_Item_for_Hero: Heroworldevent Insert failed: %s", err)
	}
}

//Get Item by Hero ID, retries an item's level value for the specified Hero ID
//Parameters: Hero ID, Type of Item, and the SQL.DB connection.
//Return: The Item level value
func Get_Item_By_HeroID(heroID int64, item_type string, conn *sql.DB) int {

	log.Info("Get_item_level_for_user ")

	var current_item_level int

	//Check what is the level of the current Item, update value if needed, msg player
	var query = "SELECT " + item_type + " FROM hero WHERE hero_id = ?"

	log.Info("Select Query: " + query)

	stmt, err := conn.Prepare(query)
	if err != nil {
		log.Errorln("DB: Get_Item_By_HeroID: Prepare Query failed: %s", err)
	}

	err = stmt.QueryRow(heroID).Scan(&current_item_level)

	if err != nil {
		log.Errorln("DB: Get_Item_By_HeroID: QueryRow failed: %s", err)
	}

	log.Info("Item Value: " + strconv.Itoa(current_item_level))

	defer stmt.Close()

	return current_item_level
}

//Update Item for Hero, updates the Item level value for a specified item for a  Hero (using the Hero ID)
//Parameters: Hero ID, Type of Item, The new item level and the SQL.DB connection.
//Return: None
func Update_Item_for_Hero(heroID int64, item_type string, item_level int, conn *sql.DB) {

	log.Info("Updating Item: " + item_type + " with Level: " + strconv.Itoa(item_level) +" from Hero ID: " + strconv.FormatInt(heroID, 10))

	var query = "UPDATE hero SET " + item_type + "=" + strconv.Itoa(item_level) +" WHERE hero_id="+ strconv.FormatInt(heroID, 10)

	_, err := conn.Exec(query)

	if err != nil {
		log.Errorln("DB: Insert_Item_for_Hero: Item Insert failed: %s", err)
	}

	log.Info("Executed: " + query)
}<commit_msg>Cleaned up sql query for Get Item for HeroID<commit_after>package main

import (

	"database/sql"
	"strconv"
	log "github.com/Sirupsen/logrus"
)

//Insert World Event for Hero, adds a world event notification for a specific hero
//Parameters: Hero ID, World Event Message and the SQL.DB connection.
//Return: None
func Insert_World_Event_for_Hero(heroID int64, worldEvent string, conn *sql.DB) {

	log.Info("[DAL] Insert_World_Event_for_Hero : HeroID: " + strconv.FormatInt(heroID, 10) + " | Event: " + worldEvent)

	var query = "INSERT INTO worldevent (event_text) VALUES ('" + worldEvent + "')"

	log.Info("[DAL] World Event Query: " + query)

	statement, err := conn.Exec(query)

	if err != nil {
		log.Errorln("[DAL] Insert_Item_for_Hero: Worldevent Insert failed: %s", err)
	}

	last_worldevent_Id, err := statement.LastInsertId()

	if last_worldevent_Id != 0 {

		Insert_Hero_World_Event(heroID, int(last_worldevent_Id), conn)
	}
}

//Insert Hero World Event, populates the table that maps Heros with their World events
//Parameters: Hero ID, World Event ID and the SQL.DB connection.
//Return: None
func Insert_Hero_World_Event(heroID int64, woldEvent_id int, conn *sql.DB) {

	var query = "INSERT INTO heroworldevent (hero_id, worldevent_id ) VALUES (" + strconv.FormatInt(heroID, 10) + ", " + strconv.Itoa(woldEvent_id) + ")"

	log.Info("[DAL] Heroworldevent Query: " + query)

	_, err := conn.Exec(query)

	if err != nil {
		log.Errorln("[DAL] Insert_Item_for_Hero: Heroworldevent Insert failed: %s", err)
	}
}

//Get Item by Hero ID, retries an item's level value for the specified Hero ID
//Parameters: Hero ID, Type of Item, and the SQL.DB connection.
//Return: The Item level value
func Get_Item_By_HeroID(heroID int64, item_type string, conn *sql.DB) int {

	log.Info("Get_item_level_for_user ")

	var current_item_level int

	//Check what is the level of the current Item, update value if needed, msg player
	var query = "SELECT " + item_type + " FROM hero WHERE hero_id=?"

	log.Info("Select Query: " + query)

	stmt, err := conn.Prepare(query)
	if err != nil {
		log.Errorln("DB: Get_Item_By_HeroID: Prepare Query failed: %s", err)
	}

	err = stmt.QueryRow(heroID).Scan(&current_item_level)

	if err != nil {
		log.Errorln("DB: Get_Item_By_HeroID: QueryRow failed: %s", err)
	}

	log.Info("Item Value: " + strconv.Itoa(current_item_level))

	defer stmt.Close()

	return current_item_level
}

//Update Item for Hero, updates the Item level value for a specified item for a  Hero (using the Hero ID)
//Parameters: Hero ID, Type of Item, The new item level and the SQL.DB connection.
//Return: None
func Update_Item_for_Hero(heroID int64, item_type string, item_level int, conn *sql.DB) {

	log.Info("Updating Item: " + item_type + " with Level: " + strconv.Itoa(item_level) +" from Hero ID: " + strconv.FormatInt(heroID, 10))

	var query = "UPDATE hero SET " + item_type + "=" + strconv.Itoa(item_level) +" WHERE hero_id="+ strconv.FormatInt(heroID, 10)

	_, err := conn.Exec(query)

	if err != nil {
		log.Errorln("DB: Insert_Item_for_Hero: Item Insert failed: %s", err)
	}

	log.Info("Executed: " + query)
}<|endoftext|>


<commit_before>package slang

import (
	"fmt"
	"strings"
	"testing"
	"time"

	vcontext "v.io/v23/context"
)

func fn1(rt Runtime) error {
	return nil
}

func fn2(rt Runtime) (int, error) {
	return 3, nil
}

func fn3(rt Runtime, v int) (int, error) {
	return v, nil
}

type tt1 struct {
	msg string
}

func (t *tt1) String() string {
	return t.msg
}

func fn4(rt Runtime, s string) (*tt1, error) {
	return &tt1{msg: s}, nil
}

func fn5(rt Runtime, v *tt1) error {
	return fmt.Errorf("%v", v.msg)
}

func fn6(rt Runtime, duration time.Duration, flag bool) error {
	return nil
}

func fn7(rt Runtime, format string, args ...interface{}) (string, error) {
	return fmt.Sprintf(format, args...), nil
}

func fn8(rt Runtime, format string, second int, args ...interface{}) (string, error) {
	return fmt.Sprintf(format, args...), nil
}

func fn9(rt Runtime, args ...string) (string, error) {
	return strings.Join(args, ", "), nil
}

func fn10(rt Runtime, duration time.Duration, flag bool) (time.Duration, bool, error) {
	return duration, flag, nil
}

func stringv(rt Runtime, args ...string) ([]string, error) {
	return args, nil
}

func init() {
	RegisterFunction(fn1, "test", "")
	RegisterFunction(fn2, "test", "")
	RegisterFunction(fn3, "test", "", "v")
	RegisterFunction(fn4, "test", "", "s", "msg")
	RegisterFunction(fn5, "test", "", "v")
	RegisterFunction(fn6, "test", "", "duration", "flag")
	RegisterFunction(fn7, "test", "", "format", "args")
	RegisterFunction(fn8, "test", "", "format", "second", "args")
	RegisterFunction(fn9, "test", "", "args")
	RegisterFunction(fn10, "test", "", "duration", "flag")
	RegisterFunction(stringv, "test", "", "args")

}

func parseAndCompile(src string) (string, error) {
	stmts, err := parseBytes([]byte(src))
	if err != nil {
		return "", err
	}
	out := &strings.Builder{}
	scr := &Script{}
	if err := scr.compile(stmts); err != nil {
		return "", err
	}
	scr.formatCompiledState(out)
	return out.String(), nil
}

func TestCompile(t *testing.T) {
	for i, tc := range []struct {
		script   string
		compiled string
	}{
		{"fn1()", "1:1: fn1() :: fn1()\n"},
		{"a:=fn2()", "a: int\n1:1: a := fn2() :: fn2() int\n"},
		{"a:=fn2(); b:=fn3(a)", `a: int
b: int
1:1: a := fn2() :: fn2() int
1:11: b := fn3(a) :: fn3(v int) int
`},
		{"b:=fn3(33)", `b: int
1:1: b := fn3(33) :: fn3(v int) int
`},
		{`tt:=fn4("msg")`, `tt: *slang.tt1
1:1: tt := fn4("msg") :: fn4(s string) (msg *slang.tt1)
`},
		{`tt:=fn4("msg"); fn5(tt)`, `tt: *slang.tt1
1:1: tt := fn4("msg") :: fn4(s string) (msg *slang.tt1)
1:17: fn5(tt) :: fn5(v *slang.tt1)
`},
		{`fn6("1h", false)`, `1:1: fn6(1h0m0s, false) :: fn6(duration time.Duration, flag bool)
`},
		{`fn6("1h", true)`, `1:1: fn6(1h0m0s, true) :: fn6(duration time.Duration, flag bool)
`},
		{`s := fn7("format")`, `s: string
1:1: s := fn7("format") :: fn7(format string, args ...interface {}) string
`},
		{`s := fn7("format"); s = fn7("new format")`, `s: string
1:1: s := fn7("format") :: fn7(format string, args ...interface {}) string
1:21: s = fn7("new format") :: fn7(format string, args ...interface {}) string
`},
		{`s := fn7("%v", 3)`, `s: string
1:1: s := fn7("%v", 3) :: fn7(format string, args ...interface {}) string
`},
		{`s := fn7("%v %v", 3, 2)`, `s: string
1:1: s := fn7("%v %v", 3, 2) :: fn7(format string, args ...interface {}) string
`},
		{`s := fn7("%v %v", "msg", 2)`, `s: string
1:1: s := fn7("%v %v", "msg", 2) :: fn7(format string, args ...interface {}) string
`},
		{`d,f := fn10("1h30s", false); s := fn7("duration: %v, bool %v", d,f)`, `d: time.Duration
f: bool
s: string
1:1: d, f := fn10(1h0m30s, false) :: fn10(duration time.Duration, flag bool) (time.Duration, bool)
1:30: s := fn7("duration: %v, bool %v", d, f) :: fn7(format string, args ...interface {}) string
`},
		{`va := stringv("a", "b", "c"); vb := stringv(va...)`, `va: []string
vb: []string
1:1: va := stringv("a", "b", "c") :: stringv(args ...string) []string
1:31: vb := stringv(va...) :: stringv(args ...string) []string
`},
	} {
		out, err := parseAndCompile(tc.script)
		if err != nil {
			t.Errorf("%v: failed to compile: %v %v", i, tc.script, err)
			continue
		}
		if got, want := out, tc.compiled; got != want {
			t.Errorf("%v: got %v, want %v", i, got, want)
		}
	}
}

func TestCompileErrors(t *testing.T) {
	for i, tc := range []struct {
		script string
		msg    string
	}{
		{"fnx()", "1:1: unrecognised function name: fnx"},
		{"fn1(33)", "1:1: wrong # of arguments for fn1()"},
		{"fn6()", "1:1: wrong # of arguments for fn6(duration time.Duration, flag bool)"},
		{"x:=fn1()", "1:1: wrong # of results for fn1()"},
		{"fn2()", "1:1: wrong # of results for fn2() int"},
		{`a:=fn3("xxx")`, `1:1: 1:8: literal arg '"xxx"' of type string is not assignable to int`},
		{"a:=fn3(3); fn5(a)", `1:12: 1:16: arg 'a' is of the wrong type int, should be *slang.tt1`},
		{`fn6("1h", fals)`, `1:1: 1:11: arg 'fals' is an invalid literal: not a bool: must be one of 'true' or 'false'`},
		{`fn6("1d", fals)`, `1:1: 1:5: arg '"1d"' is an invalid literal: time: unknown unit "d" in duration "1d"`},
		{`a:=fn3(1); a := fn4("a")`, `1:12: result 'a' is redefined with a new type *slang.tt1, previous type int`},
		{`a:=fn3(1); a := fn3(2)`, `1:12: result 'a' is redefined`},
		{`a=fn3(1)`, `1:1: result 'a' is not defined`},
		{`s:=fn7()`, "1:1: need at least 1 arguments for variadic function fn7(format string, args ...interface {}) string"},
		{`s:=fn8("format")`, "1:1: need at least 2 arguments for variadic function fn8(format string, second int, args ...interface {}) string"},
		{`s:=fn9(13)`, `1:1: 1:8: literal arg '13' of type int is not assignable to string`},
		{`s:=fn9("a", 14)`, `1:1: 1:13: literal arg '14' of type int is not assignable to string`},
		{`va := stringv("a", "b", "c"); o := sprintf("%v", va...)`, `1:31: 1:50: arg 'va' is of the wrong type []string, should be []interface {}`},
	} {

		_, err := parseAndCompile(tc.script)
		if err == nil {
			t.Errorf("%v: should have failed to compile, no error returned", i)
			continue
		}
		if got, want := err.Error(), tc.msg; got != want {
			t.Errorf("%v: got %v, want %v", i, got, want)
		}
	}

}

func TestConsts(t *testing.T) {
	ctx, cancel := vcontext.RootContext()
	defer cancel()

	out := &strings.Builder{}
	scr := &Script{}
	scr.SetStdout(out)
	now := time.Now()
	scr.RegisterConst("startTime", now.Format(time.RFC1123))
	err := scr.ExecuteBytes(ctx, []byte(`printf("now: %s",startTime)`))
	if err != nil {
		t.Fatal(err)
	}
	if got, want := out.String(), "now: "+now.Format(time.RFC1123); got != want {
		t.Errorf("got %v, want %v", got, want)
	}

	err = scr.ExecuteBytes(ctx, []byte(`startTime = sprintf("a")`))
	if err == nil || err.Error() != `1:1: cannot assign or define result 'startTime' which is a constant` {
		t.Errorf("missing or unexpected error: %v", err)
	}
	err = scr.ExecuteBytes(ctx, []byte(`a := fn3(startTime)`))
	if err == nil || err.Error() != `1:1: 1:10: arg 'startTime' is of the wrong type string, should be int` {
		t.Errorf("missing or unexpected error: %v", err)
	}
}
<commit_msg>x/ref/lib/slang: fix test to compiler/library version independent (#205)<commit_after>package slang

import (
	"fmt"
	"strings"
	"testing"
	"time"

	vcontext "v.io/v23/context"
)

func fn1(rt Runtime) error {
	return nil
}

func fn2(rt Runtime) (int, error) {
	return 3, nil
}

func fn3(rt Runtime, v int) (int, error) {
	return v, nil
}

type tt1 struct {
	msg string
}

func (t *tt1) String() string {
	return t.msg
}

func fn4(rt Runtime, s string) (*tt1, error) {
	return &tt1{msg: s}, nil
}

func fn5(rt Runtime, v *tt1) error {
	return fmt.Errorf("%v", v.msg)
}

func fn6(rt Runtime, duration time.Duration, flag bool) error {
	return nil
}

func fn7(rt Runtime, format string, args ...interface{}) (string, error) {
	return fmt.Sprintf(format, args...), nil
}

func fn8(rt Runtime, format string, second int, args ...interface{}) (string, error) {
	return fmt.Sprintf(format, args...), nil
}

func fn9(rt Runtime, args ...string) (string, error) {
	return strings.Join(args, ", "), nil
}

func fn10(rt Runtime, duration time.Duration, flag bool) (time.Duration, bool, error) {
	return duration, flag, nil
}

func stringv(rt Runtime, args ...string) ([]string, error) {
	return args, nil
}

func init() {
	RegisterFunction(fn1, "test", "")
	RegisterFunction(fn2, "test", "")
	RegisterFunction(fn3, "test", "", "v")
	RegisterFunction(fn4, "test", "", "s", "msg")
	RegisterFunction(fn5, "test", "", "v")
	RegisterFunction(fn6, "test", "", "duration", "flag")
	RegisterFunction(fn7, "test", "", "format", "args")
	RegisterFunction(fn8, "test", "", "format", "second", "args")
	RegisterFunction(fn9, "test", "", "args")
	RegisterFunction(fn10, "test", "", "duration", "flag")
	RegisterFunction(stringv, "test", "", "args")

}

func parseAndCompile(src string) (string, error) {
	stmts, err := parseBytes([]byte(src))
	if err != nil {
		return "", err
	}
	out := &strings.Builder{}
	scr := &Script{}
	if err := scr.compile(stmts); err != nil {
		return "", err
	}
	scr.formatCompiledState(out)
	return out.String(), nil
}

func TestCompile(t *testing.T) {
	for i, tc := range []struct {
		script   string
		compiled string
	}{
		{"fn1()", "1:1: fn1() :: fn1()\n"},
		{"a:=fn2()", "a: int\n1:1: a := fn2() :: fn2() int\n"},
		{"a:=fn2(); b:=fn3(a)", `a: int
b: int
1:1: a := fn2() :: fn2() int
1:11: b := fn3(a) :: fn3(v int) int
`},
		{"b:=fn3(33)", `b: int
1:1: b := fn3(33) :: fn3(v int) int
`},
		{`tt:=fn4("msg")`, `tt: *slang.tt1
1:1: tt := fn4("msg") :: fn4(s string) (msg *slang.tt1)
`},
		{`tt:=fn4("msg"); fn5(tt)`, `tt: *slang.tt1
1:1: tt := fn4("msg") :: fn4(s string) (msg *slang.tt1)
1:17: fn5(tt) :: fn5(v *slang.tt1)
`},
		{`fn6("1h", false)`, `1:1: fn6(1h0m0s, false) :: fn6(duration time.Duration, flag bool)
`},
		{`fn6("1h", true)`, `1:1: fn6(1h0m0s, true) :: fn6(duration time.Duration, flag bool)
`},
		{`s := fn7("format")`, `s: string
1:1: s := fn7("format") :: fn7(format string, args ...interface {}) string
`},
		{`s := fn7("format"); s = fn7("new format")`, `s: string
1:1: s := fn7("format") :: fn7(format string, args ...interface {}) string
1:21: s = fn7("new format") :: fn7(format string, args ...interface {}) string
`},
		{`s := fn7("%v", 3)`, `s: string
1:1: s := fn7("%v", 3) :: fn7(format string, args ...interface {}) string
`},
		{`s := fn7("%v %v", 3, 2)`, `s: string
1:1: s := fn7("%v %v", 3, 2) :: fn7(format string, args ...interface {}) string
`},
		{`s := fn7("%v %v", "msg", 2)`, `s: string
1:1: s := fn7("%v %v", "msg", 2) :: fn7(format string, args ...interface {}) string
`},
		{`d,f := fn10("1h30s", false); s := fn7("duration: %v, bool %v", d,f)`, `d: time.Duration
f: bool
s: string
1:1: d, f := fn10(1h0m30s, false) :: fn10(duration time.Duration, flag bool) (time.Duration, bool)
1:30: s := fn7("duration: %v, bool %v", d, f) :: fn7(format string, args ...interface {}) string
`},
		{`va := stringv("a", "b", "c"); vb := stringv(va...)`, `va: []string
vb: []string
1:1: va := stringv("a", "b", "c") :: stringv(args ...string) []string
1:31: vb := stringv(va...) :: stringv(args ...string) []string
`},
	} {
		out, err := parseAndCompile(tc.script)
		if err != nil {
			t.Errorf("%v: failed to compile: %v %v", i, tc.script, err)
			continue
		}
		if got, want := out, tc.compiled; got != want {
			t.Errorf("%v: got %v, want %v", i, got, want)
		}
	}
}

func TestCompileErrors(t *testing.T) {
	_, durationErr := time.ParseDuration("1d")

	for i, tc := range []struct {
		script string
		msg    string
	}{
		{"fnx()", "1:1: unrecognised function name: fnx"},
		{"fn1(33)", "1:1: wrong # of arguments for fn1()"},
		{"fn6()", "1:1: wrong # of arguments for fn6(duration time.Duration, flag bool)"},
		{"x:=fn1()", "1:1: wrong # of results for fn1()"},
		{"fn2()", "1:1: wrong # of results for fn2() int"},
		{`a:=fn3("xxx")`, `1:1: 1:8: literal arg '"xxx"' of type string is not assignable to int`},
		{"a:=fn3(3); fn5(a)", `1:12: 1:16: arg 'a' is of the wrong type int, should be *slang.tt1`},
		{`fn6("1h", fals)`, `1:1: 1:11: arg 'fals' is an invalid literal: not a bool: must be one of 'true' or 'false'`},
		{`fn6("1d", fals)`, `1:1: 1:5: arg '"1d"' is an invalid literal: ` + durationErr.Error()},
		{`a:=fn3(1); a := fn4("a")`, `1:12: result 'a' is redefined with a new type *slang.tt1, previous type int`},
		{`a:=fn3(1); a := fn3(2)`, `1:12: result 'a' is redefined`},
		{`a=fn3(1)`, `1:1: result 'a' is not defined`},
		{`s:=fn7()`, "1:1: need at least 1 arguments for variadic function fn7(format string, args ...interface {}) string"},
		{`s:=fn8("format")`, "1:1: need at least 2 arguments for variadic function fn8(format string, second int, args ...interface {}) string"},
		{`s:=fn9(13)`, `1:1: 1:8: literal arg '13' of type int is not assignable to string`},
		{`s:=fn9("a", 14)`, `1:1: 1:13: literal arg '14' of type int is not assignable to string`},
		{`va := stringv("a", "b", "c"); o := sprintf("%v", va...)`, `1:31: 1:50: arg 'va' is of the wrong type []string, should be []interface {}`},
	} {

		_, err := parseAndCompile(tc.script)
		if err == nil {
			t.Errorf("%v: should have failed to compile, no error returned", i)
			continue
		}
		if got, want := err.Error(), tc.msg; got != want {
			t.Errorf("%v: got %v, want %v", i, got, want)
		}
	}

}

func TestConsts(t *testing.T) {
	ctx, cancel := vcontext.RootContext()
	defer cancel()

	out := &strings.Builder{}
	scr := &Script{}
	scr.SetStdout(out)
	now := time.Now()
	scr.RegisterConst("startTime", now.Format(time.RFC1123))
	err := scr.ExecuteBytes(ctx, []byte(`printf("now: %s",startTime)`))
	if err != nil {
		t.Fatal(err)
	}
	if got, want := out.String(), "now: "+now.Format(time.RFC1123); got != want {
		t.Errorf("got %v, want %v", got, want)
	}

	err = scr.ExecuteBytes(ctx, []byte(`startTime = sprintf("a")`))
	if err == nil || err.Error() != `1:1: cannot assign or define result 'startTime' which is a constant` {
		t.Errorf("missing or unexpected error: %v", err)
	}
	err = scr.ExecuteBytes(ctx, []byte(`a := fn3(startTime)`))
	if err == nil || err.Error() != `1:1: 1:10: arg 'startTime' is of the wrong type string, should be int` {
		t.Errorf("missing or unexpected error: %v", err)
	}
}
<|endoftext|>