<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/rlister/let-me-in/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws"
	"github.com/rlister/let-me-in/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/rlister/let-me-in/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/ec2"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"text/tabwriter"
)

var VERSION = "dev"

type LmiInput struct {
	GroupId    *string
	IpProtocol *string
	FromPort   *int64
	ToPort     *int64
	CidrIp     *string
}

// error handler
func check(e error) {
	if e != nil {
		panic(e.Error())
	}
}

// return array of security group objects for given group names
func getGroups(client *ec2.EC2, names []string) ([]*ec2.SecurityGroup, error) {

	// get names as array of aws.String objects
	values := make([]*string, len(names))
	for i, name := range names {
		values[i] = aws.String(name)
	}

	// request params as filter for names
	params := &ec2.DescribeSecurityGroupsInput{
		Filters: []*ec2.Filter{
			{
				Name:   aws.String("group-name"),
				Values: values,
			},
		},
	}

	// send request
	resp, err := client.DescribeSecurityGroups(params)
	if err != nil {
		return nil, err
	}

	return resp.SecurityGroups, nil
}

func authorizeGroups(client *ec2.EC2, groups []*ec2.SecurityGroup, input LmiInput) {
	for _, group := range groups {
		authorizeGroup(client, group, input)
	}
}

// add given permission to security group
func authorizeGroup(client *ec2.EC2, group *ec2.SecurityGroup, input LmiInput) {
	_, err := client.AuthorizeSecurityGroupIngress(&ec2.AuthorizeSecurityGroupIngressInput{
		GroupId:    group.GroupId,
		IpProtocol: input.IpProtocol,
		FromPort:   input.FromPort,
		ToPort:     input.ToPort,
		CidrIp:     input.CidrIp,
	})

	// be idempotent, i.e. skip error if this permission already exists in group
	if err != nil {
		if err.(awserr.Error).Code() != "InvalidPermission.Duplicate" {
			panic(err)
		}
	}
}

func revokeGroups(client *ec2.EC2, groups []*ec2.SecurityGroup, input LmiInput) {
	for _, group := range groups {
		revokeGroup(client, group, input)
	}
}

// revoke given permission for security group
func revokeGroup(client *ec2.EC2, group *ec2.SecurityGroup, input LmiInput) {
	_, err := client.RevokeSecurityGroupIngress(&ec2.RevokeSecurityGroupIngressInput{
		GroupId:    group.GroupId,
		IpProtocol: input.IpProtocol,
		FromPort:   input.FromPort,
		ToPort:     input.ToPort,
		CidrIp:     input.CidrIp,
	})

	// be idempotent, i.e. skip error if this permission already exists in group
	if err != nil {
		if err.(awserr.Error).Code() != "InvalidPermission.NotFound" {
			panic(err)
		}
	}
}

func cleanGroups(client *ec2.EC2, groups []*ec2.SecurityGroup) {
	for _, group := range groups {
		cleanGroup(client, group)
	}
}

// revoke all existing permissions for security group
func cleanGroup(client *ec2.EC2, group *ec2.SecurityGroup) {
	for _, perm := range group.IpPermissions {
		for _, cidr := range perm.IpRanges {
			revokeGroup(client, group, LmiInput{
				IpProtocol: perm.IpProtocol,
				FromPort:   perm.FromPort,
				ToPort:     perm.ToPort,
				CidrIp:     cidr.CidrIp,
			})
		}
	}
}

// get my external-facing IP as a string
func getMyIp(ident string) string {
	resp, err := http.Get(ident)
	check(err)
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	check(err)

	return string(body)
}

// parse cmdline argv into args before '--' and cmd to exec afterwards
func parseArgs(argv []string) ([]string, []string) {

	// if -- found, return slices before and after
	for i, arg := range flag.Args() {
		if arg == "--" {
			return argv[0:i], argv[i+1:]
		}
	}

	// no -- found, return all of argv
	return argv, nil
}

// print out table of current IP permissions for given security groups
func printIpRanges(groups []*ec2.SecurityGroup) {
	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 0, '\t', 0)
	for _, group := range groups {
		for _, perm := range group.IpPermissions {
			for _, cidr := range perm.IpRanges {
				fmt.Fprintf(w, "%v\t%v\t%v\t%v\n", *group.GroupName, *perm.IpProtocol, *cidr.CidrIp, *perm.FromPort)
			}
		}
	}
	w.Flush()
}

// cmdline options
var versionFlag = flag.Bool("version", false, "show version and exit")
var listFlag = flag.Bool("list", false, "list current rules for security groups")
var cidrFlag = flag.String("cidr", "", "set a specific cidr block (default: current public ip)")
var protocolFlag = flag.String("protocol", "tcp", "protocol to allow: tcp, udp or icmp")
var portFlag = flag.Int("port", 22, "port number to allow")
var revokeFlag = flag.Bool("revoke", false, "revoke access from security groups")
var cleanFlag = flag.Bool("clean", false, "clean listed groups, i.e. revoke all access")

// short versions of some options
func init() {
	flag.BoolVar(versionFlag, "v", false, "")
	flag.BoolVar(listFlag, "l", false, "")
	flag.BoolVar(revokeFlag, "r", false, "")
}

func main() {
	flag.Parse()

	// show version and exit
	if *versionFlag {
		fmt.Printf("let-me-in %v\n", VERSION)
		return
	}

	// if cidr not given get ip from external service
	if *cidrFlag == "" {
		ident := os.Getenv("LMI_IDENT_URL")
		if ident == "" {
			ident = "http://v4.ident.me/"
		}
		ip := getMyIp(ident) + "/32"
		cidrFlag = &ip
	}

	input := LmiInput{
		IpProtocol: protocolFlag,
		FromPort:   aws.Int64(int64(*portFlag)),
		ToPort:     aws.Int64(int64(*portFlag)),
		CidrIp:     cidrFlag,
	}

	// configure aws-sdk from AWS_* env vars
	client := ec2.New(&aws.Config{})

	// get security group names and any command to exec after '--'
	groupNames, cmd := parseArgs(flag.Args())

	// get details for listed groups
	groups, err := getGroups(client, groupNames)
	if err != nil {
		fmt.Printf("%v\n", err) // if AWS creds not configured, report it here
		return
	}

	// print list of current IP permissions for groups
	if *listFlag {
		printIpRanges(groups)
		return
	}

	// remove all existing permissions for groups
	if *cleanFlag {
		cleanGroups(client, groups)
		return
	}

	// revoke given permission for groups
	if *revokeFlag {
		revokeGroups(client, groups, input)
		return
	}

	// default behaviour
	authorizeGroups(client, groups, input)

	// exec any command after '--', then revoke
	if cmd != nil {
		c := exec.Command(cmd[0], cmd[1:]...)
		c.Stdout = os.Stdout
		c.Stdin = os.Stdin
		c.Stderr = os.Stderr

		err := c.Run()
		if err != nil {
			fmt.Println(err) // show err and keep running so we hit revoke below
		}

		revokeGroups(client, groups, input)
	}
}
<commit_msg>rename Input struct<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/rlister/let-me-in/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws"
	"github.com/rlister/let-me-in/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/rlister/let-me-in/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/ec2"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"text/tabwriter"
)

var VERSION = "dev"

type Input struct {
	GroupId    *string
	IpProtocol *string
	FromPort   *int64
	ToPort     *int64
	CidrIp     *string
}

// error handler
func check(e error) {
	if e != nil {
		panic(e.Error())
	}
}

// return array of security group objects for given group names
func getGroups(client *ec2.EC2, names []string) ([]*ec2.SecurityGroup, error) {

	// get names as array of aws.String objects
	values := make([]*string, len(names))
	for i, name := range names {
		values[i] = aws.String(name)
	}

	// request params as filter for names
	params := &ec2.DescribeSecurityGroupsInput{
		Filters: []*ec2.Filter{
			{
				Name:   aws.String("group-name"),
				Values: values,
			},
		},
	}

	// send request
	resp, err := client.DescribeSecurityGroups(params)
	if err != nil {
		return nil, err
	}

	return resp.SecurityGroups, nil
}

func authorizeGroups(client *ec2.EC2, groups []*ec2.SecurityGroup, input Input) {
	for _, group := range groups {
		authorizeGroup(client, group, input)
	}
}

// add given permission to security group
func authorizeGroup(client *ec2.EC2, group *ec2.SecurityGroup, input Input) {
	_, err := client.AuthorizeSecurityGroupIngress(&ec2.AuthorizeSecurityGroupIngressInput{
		GroupId:    group.GroupId,
		IpProtocol: input.IpProtocol,
		FromPort:   input.FromPort,
		ToPort:     input.ToPort,
		CidrIp:     input.CidrIp,
	})

	// be idempotent, i.e. skip error if this permission already exists in group
	if err != nil {
		if err.(awserr.Error).Code() != "InvalidPermission.Duplicate" {
			panic(err)
		}
	}
}

func revokeGroups(client *ec2.EC2, groups []*ec2.SecurityGroup, input Input) {
	for _, group := range groups {
		revokeGroup(client, group, input)
	}
}

// revoke given permission for security group
func revokeGroup(client *ec2.EC2, group *ec2.SecurityGroup, input Input) {
	_, err := client.RevokeSecurityGroupIngress(&ec2.RevokeSecurityGroupIngressInput{
		GroupId:    group.GroupId,
		IpProtocol: input.IpProtocol,
		FromPort:   input.FromPort,
		ToPort:     input.ToPort,
		CidrIp:     input.CidrIp,
	})

	// be idempotent, i.e. skip error if this permission already exists in group
	if err != nil {
		if err.(awserr.Error).Code() != "InvalidPermission.NotFound" {
			panic(err)
		}
	}
}

func cleanGroups(client *ec2.EC2, groups []*ec2.SecurityGroup) {
	for _, group := range groups {
		cleanGroup(client, group)
	}
}

// revoke all existing permissions for security group
func cleanGroup(client *ec2.EC2, group *ec2.SecurityGroup) {
	for _, perm := range group.IpPermissions {
		for _, cidr := range perm.IpRanges {
			revokeGroup(client, group, Input{
				IpProtocol: perm.IpProtocol,
				FromPort:   perm.FromPort,
				ToPort:     perm.ToPort,
				CidrIp:     cidr.CidrIp,
			})
		}
	}
}

// get my external-facing IP as a string
func getMyIp(ident string) string {
	resp, err := http.Get(ident)
	check(err)
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	check(err)

	return string(body)
}

// parse cmdline argv into args before '--' and cmd to exec afterwards
func parseArgs(argv []string) ([]string, []string) {

	// if -- found, return slices before and after
	for i, arg := range flag.Args() {
		if arg == "--" {
			return argv[0:i], argv[i+1:]
		}
	}

	// no -- found, return all of argv
	return argv, nil
}

// print out table of current IP permissions for given security groups
func printIpRanges(groups []*ec2.SecurityGroup) {
	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 0, '\t', 0)
	for _, group := range groups {
		for _, perm := range group.IpPermissions {
			for _, cidr := range perm.IpRanges {
				fmt.Fprintf(w, "%v\t%v\t%v\t%v\n", *group.GroupName, *perm.IpProtocol, *cidr.CidrIp, *perm.FromPort)
			}
		}
	}
	w.Flush()
}

// cmdline options
var versionFlag = flag.Bool("version", false, "show version and exit")
var listFlag = flag.Bool("list", false, "list current rules for security groups")
var cidrFlag = flag.String("cidr", "", "set a specific cidr block (default: current public ip)")
var protocolFlag = flag.String("protocol", "tcp", "protocol to allow: tcp, udp or icmp")
var portFlag = flag.Int("port", 22, "port number to allow")
var revokeFlag = flag.Bool("revoke", false, "revoke access from security groups")
var cleanFlag = flag.Bool("clean", false, "clean listed groups, i.e. revoke all access")

// short versions of some options
func init() {
	flag.BoolVar(versionFlag, "v", false, "")
	flag.BoolVar(listFlag, "l", false, "")
	flag.BoolVar(revokeFlag, "r", false, "")
}

func main() {
	flag.Parse()

	// show version and exit
	if *versionFlag {
		fmt.Printf("let-me-in %v\n", VERSION)
		return
	}

	// if cidr not given get ip from external service
	if *cidrFlag == "" {
		ident := os.Getenv("LMI_IDENT_URL")
		if ident == "" {
			ident = "http://v4.ident.me/"
		}
		ip := getMyIp(ident) + "/32"
		cidrFlag = &ip
	}

	input := Input{
		IpProtocol: protocolFlag,
		FromPort:   aws.Int64(int64(*portFlag)),
		ToPort:     aws.Int64(int64(*portFlag)),
		CidrIp:     cidrFlag,
	}

	// configure aws-sdk from AWS_* env vars
	client := ec2.New(&aws.Config{})

	// get security group names and any command to exec after '--'
	groupNames, cmd := parseArgs(flag.Args())

	// get details for listed groups
	groups, err := getGroups(client, groupNames)
	if err != nil {
		fmt.Printf("%v\n", err) // if AWS creds not configured, report it here
		return
	}

	// print list of current IP permissions for groups
	if *listFlag {
		printIpRanges(groups)
		return
	}

	// remove all existing permissions for groups
	if *cleanFlag {
		cleanGroups(client, groups)
		return
	}

	// revoke given permission for groups
	if *revokeFlag {
		revokeGroups(client, groups, input)
		return
	}

	// default behaviour
	authorizeGroups(client, groups, input)

	// exec any command after '--', then revoke
	if cmd != nil {
		c := exec.Command(cmd[0], cmd[1:]...)
		c.Stdout = os.Stdout
		c.Stdin = os.Stdin
		c.Stderr = os.Stderr

		err := c.Run()
		if err != nil {
			fmt.Println(err) // show err and keep running so we hit revoke below
		}

		revokeGroups(client, groups, input)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"math"
)

type Body struct {
	Xs, Vs []Vector
	M      float64
}

// Shifts all the values in xs by one and puts x at the beginning.
func Shift(xs []Vector, x Vector) {

	for i := len(xs) - 1; i > 0; i-- {

		xs[i] = xs[i-1]
	}

	xs[0] = x
}

type Integrator func(xs, vs []Vector, a Vector, dt float64)

// Performs a step of an Euler integration
func Euler(xs, vs []Vector, a Vector, dt float64) {

	v := vs[0].Plus(a.Scale(dt))
	x := xs[0].Plus(v.Scale(dt))

	Shift(vs, v)
	Shift(xs, x)
}

// Performs a step of a Verlet integrator
//
// Note that v[0] will not be calculated until the next step
func Verlet(xs, vs []Vector, a Vector, dt float64) {

	xNext := xs[0].Scale(2).Minus(xs[1]).Plus(a.Scale(dt * dt))
	vs[0] = xNext.Minus(xs[1]).Scale(1 / (2 * dt))

	Shift(vs, NewZeroVector())
	Shift(xs, xNext)
}

type Spring struct {
	K, L0 float64
}

type Hooke struct {
	Springs [][]Spring
}

func (h Hooke) Accel(bs []Body, i int) (a Vector) {

	f := NewZeroVector()

	b := bs[i]

	for j, b2 := range bs {

		spring := h.Springs[i][j]

		dir, l := b2.Xs[0].Minus(b.Xs[0]).UnitAndNorm()

		f = f.Plus(dir.Scale(spring.K * (l - spring.L0)))
	}

	return f.Scale(1 / b.M)
}

type SingleHooke struct {
	K float64
}

func (sh SingleHooke) Accel(bs []Body, i int) (a Vector) {

	b := bs[i]

	return b.Xs[0].Scale(-sh.K / b.M)
}

type Force interface {
	Accel(bs []Body, i int) (a Vector)
}

type AnalyticSHM struct {
	K, M float64
	A    Vector
}

func (ashm AnalyticSHM) XVAt(t float64) (x, v Vector) {

	omega := math.Sqrt(ashm.K / ashm.M)

	x = ashm.A.Scale(math.Cos(omega * t))
	v = ashm.A.Negate().Scale(omega * math.Sin(omega*t))

	return
}

func (ashm AnalyticSHM) Force() Force {

	return Force(SingleHooke{K: ashm.K})
}

func (ashm AnalyticSHM) ForEuler() []Body {

	x0, v0 := ashm.XVAt(0)

	return []Body{
		Body{
			Xs: []Vector{x0},
			Vs: []Vector{v0},
			M:  ashm.M,
		},
	}
}

func main() {
}
<commit_msg>Add AnalyticSHM.ForVerlet<commit_after>package main

import (
	"math"
)

type Body struct {
	Xs, Vs []Vector
	M      float64
}

// Shifts all the values in xs by one and puts x at the beginning.
func Shift(xs []Vector, x Vector) {

	for i := len(xs) - 1; i > 0; i-- {

		xs[i] = xs[i-1]
	}

	xs[0] = x
}

type Integrator func(xs, vs []Vector, a Vector, dt float64)

// Performs a step of an Euler integration
func Euler(xs, vs []Vector, a Vector, dt float64) {

	v := vs[0].Plus(a.Scale(dt))
	x := xs[0].Plus(v.Scale(dt))

	Shift(vs, v)
	Shift(xs, x)
}

// Performs a step of a Verlet integrator
//
// Note that v[0] will not be calculated until the next step
func Verlet(xs, vs []Vector, a Vector, dt float64) {

	xNext := xs[0].Scale(2).Minus(xs[1]).Plus(a.Scale(dt * dt))
	vs[0] = xNext.Minus(xs[1]).Scale(1 / (2 * dt))

	Shift(vs, NewZeroVector())
	Shift(xs, xNext)
}

type Spring struct {
	K, L0 float64
}

type Hooke struct {
	Springs [][]Spring
}

func (h Hooke) Accel(bs []Body, i int) (a Vector) {

	f := NewZeroVector()

	b := bs[i]

	for j, b2 := range bs {

		spring := h.Springs[i][j]

		dir, l := b2.Xs[0].Minus(b.Xs[0]).UnitAndNorm()

		f = f.Plus(dir.Scale(spring.K * (l - spring.L0)))
	}

	return f.Scale(1 / b.M)
}

type SingleHooke struct {
	K float64
}

func (sh SingleHooke) Accel(bs []Body, i int) (a Vector) {

	b := bs[i]

	return b.Xs[0].Scale(-sh.K / b.M)
}

type Force interface {
	Accel(bs []Body, i int) (a Vector)
}

type AnalyticSHM struct {
	K, M float64
	A    Vector
}

func (ashm AnalyticSHM) XVAt(t float64) (x, v Vector) {

	omega := math.Sqrt(ashm.K / ashm.M)

	x = ashm.A.Scale(math.Cos(omega * t))
	v = ashm.A.Negate().Scale(omega * math.Sin(omega*t))

	return
}

func (ashm AnalyticSHM) Force() Force {

	return Force(SingleHooke{K: ashm.K})
}

func (ashm AnalyticSHM) ForEuler() []Body {

	x0, v0 := ashm.XVAt(0)

	return []Body{
		Body{
			Xs: []Vector{x0},
			Vs: []Vector{v0},
			M:  ashm.M,
		},
	}
}

func (ashm AnalyticSHM) ForVerlet(dt float64) []Body {

	x0, v0 := ashm.XVAt(0)
	xPrev, vPrev := ashm.XVAt(-dt)

	return []Body{
		Body{
			Xs: []Vector{x0, xPrev},
			Vs: []Vector{v0, vPrev},
			M:  ashm.M,
		},
	}
}

func main() {
}
<|endoftext|>


<commit_before>// Copyright 2016 The Linux Foundation
//
// 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 schema

// Generates an embbedded http.FileSystem for all schema files
// using esc (https://github.com/mjibson/esc).

// This should generally be invoked with `make schema-fs`
//go:generate esc -private -pkg=schema -ignore=.*go -ignore=.*swp .
<commit_msg>schema/gen: Include .json instead of excluding .go<commit_after>// Copyright 2016 The Linux Foundation
//
// 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 schema

// Generates an embbedded http.FileSystem for all schema files
// using esc (https://github.com/mjibson/esc).

// This should generally be invoked with `make schema-fs`
//go:generate esc -private -pkg=schema -include=.*\.json$ .
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/jackmanlabs/bucket/jlog"
	"github.com/jackmanlabs/errors"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"log"
	"regexp"
	"strings"
	"unicode"
)

func findDefinition(referringPackage, typeName string) (*DefinitionIntermediate, error) {

	pkgInfo := pkgInfos[referringPackage]
	typeName = strings.TrimPrefix(typeName, "*")
	importPaths := possibleImportPaths(pkgInfo, typeName)

	if len(importPaths) == 0 {
		log.Print("ERROR: Import paths not available for this type: ", typeName)
		jlog.Log(pkgInfo)
	}

	if len(importPaths) > 1 {
		log.Printf("WARNING: Multiple package candidates found for type (%s):", typeName)
		jlog.Log(importPaths)
	}

	if strings.Contains(typeName, ".") {
		index := strings.Index(typeName, ".") + 1
		typeName = typeName[index:]
	}

	for _, importPath := range importPaths {

		if importPath == "" {
			log.Print("Import path is blank!")
		}

		bpkg, err := build.Import(importPath, srcPath, 0)
		if err != nil {
			return nil, errors.Stack(err)
		}

		fset := token.NewFileSet()
		pkgs, err := parser.ParseDir(fset, bpkg.Dir, nil, parser.AllErrors|parser.ParseComments)
		if err != nil {
			return nil, errors.Stack(err)
		}

		for _, pkg := range pkgs {
			definitionVisitor := &DefinitionVisitor{
				Fset:     fset,
				TypeName: typeName,
			}

			ast.Walk(definitionVisitor, pkg)

			if definitionVisitor.Definition != nil {
				definition := definitionVisitor.Definition
				definition.PackageName = pkg.Name
				definition.PackagePath = importPath

				// If this definition is an enum (underlying type is primitive), then we assume it's an enum type that needs enum values.
				if isPrimitive, _, _ := IsPrimitive(definition.UnderlyingType); isPrimitive {
					values, err := findEnumValues(definition.PackagePath, definition.Name)
					if err != nil {
						return nil, errors.Stack(err)
					}
					definition.Enumerations = values
				}

				return definition, nil
			}
		}
	}

	return nil, nil
}

type DefinitionVisitor struct {
	Fset       *token.FileSet
	TypeName   string
	Definition *DefinitionIntermediate
}

func (this *DefinitionVisitor) Visit(node ast.Node) (w ast.Visitor) {
	/*
		Name          string
		PackageName   string
		PackagePath   string
		Members       []MemberIntermediate
		IsStruct      bool
		EmbeddedTypes []string
	*/

	if this.Fset == nil {
		log.Println("fset is nil.")
		return nil
	}

	switch t := node.(type) {

	case *ast.TypeSpec:
		if t.Name.String() == this.TypeName {
			this.Definition = &DefinitionIntermediate{
				Name:           t.Name.String(),
				Comment:        t.Comment.Text(),
				Documentation:  t.Doc.Text(),
				UnderlyingType: resolveTypeExpression(t.Type),
				Members:        make(map[string]SchemerDefiner),
			}
		} else {
			return nil
		}
	case *ast.Field:

		if this.Definition == nil {
			// Ignore fields that don't belong to our definition.
			return nil
		}

		if len(t.Names) > 1 {
			// On our test code base, this was never printed.
			log.Print("WARNING: Multiple names discovered.")
		}

		// Handle embedded structs.
		if len(t.Names) == 0 {
			//ast.Fprint(os.Stdout, this.Fset, t, nil)

			if this.Definition.EmbeddedTypes == nil {
				this.Definition.EmbeddedTypes = make([]string, 0)
			}
			embedded := resolveTypeExpression(t.Type)
			this.Definition.EmbeddedTypes = append(this.Definition.EmbeddedTypes, embedded)
			return nil
		}

		name := t.Names[0].String()

		// Ignore fields that are not exported.
		name_ := []rune(name)
		if unicode.IsLower(name_[0]) {
			return nil
		}

		var (
			jsonName      string
			jsonOmitEmpty bool
			isRequired    bool
		)
		if t.Tag != nil {
			jsonName, jsonOmitEmpty = parseJsonInfo(t.Tag.Value)
			if jsonName == "-" {
				return nil
			}

			isRequired = parseValidateTag(t.Tag.Value)
		}

		var desc string = parseMemberDescription(t.Doc.Text())
		if desc == "" {
			desc = parseMemberDescription(t.Comment.Text())
		}

		goType := resolveTypeExpression(t.Type)

		var member SchemerDefiner

		if isMap, k, v := IsMap(goType); isMap {
			keyType := &MemberIntermediate{
				Type: k,
				Name: name,
			}

			valueType := &MemberIntermediate{
				Type: v,
				Name: name,
			}

			member = &MapIntermediate{
				Name:          name,
				Type:          goType,
				JsonName:      jsonName,
				JsonOmitEmpty: jsonOmitEmpty,
				ValueType:     valueType,
				KeyType:       keyType,
				Description:   desc,
				Required:      isRequired,
			}

		} else if isSlice, v := IsSlice(goType); isSlice {
			valueType := &MemberIntermediate{
				Type: v,
				Name: name,
			}

			member = &SliceIntermediate{
				Name:          name,
				Type:          goType,
				JsonName:      jsonName,
				JsonOmitEmpty: jsonOmitEmpty,
				ValueType:     valueType,
				Description:   desc,
				Required:      isRequired,
			}
		} else {
			member = &MemberIntermediate{
				Type:          goType,
				Name:          name,
				JsonName:      jsonName,
				JsonOmitEmpty: jsonOmitEmpty,
				Description:   desc,
				Required:      isRequired,
			}
		}

		this.Definition.Members[name] = member

		return nil

	case *ast.FuncDecl:
		// Ignore function declarations.
		return nil
	case *ast.ImportSpec:
		// Ignore import declarations.
		return nil
	case nil:
	default:
		//log.Printf("unexpected type %T\n", t) // %T prints whatever type t has
	}

	return this
}

func resolveTypeExpression(expr ast.Expr) string {

	switch t := expr.(type) {
	case *ast.StarExpr:
		return "*" + resolveTypeExpression(t.X)
	case *ast.ArrayType:
		return "[]" + resolveTypeExpression(t.Elt)
	case *ast.Ident:
		return t.Name
	case *ast.SelectorExpr:
		return resolveTypeExpression(t.X) + "." + t.Sel.Name
	case *ast.MapType:
		return fmt.Sprintf("map[%s]%s", resolveTypeExpression(t.Key), resolveTypeExpression(t.Value))
	case *ast.InterfaceType:
		return "interface{}"
	case *ast.StructType:
		return "struct"
	default:
		return fmt.Sprintf("Unknown<%T>", t)
	}

}

// Returns true if primitive.
// If the type is primitive, the string parameters indicate the type and format per swagger spec.
// http://swagger.io/specification/ (Data Types)
func IsPrimitive(goType string) (bool, string, string) {

	// TODO: Figure out how complex types should be supported.

	goType = strings.Trim(goType, "*")

	switch goType {
	case "bool":
		return true, "boolean", ""
	case "byte":
		return true, "string", "byte"
	case "complex64":
		return true, "string", ""
	case "complex128":
		return true, "string", ""
	case "float32":
		return true, "number", "float"
	case "float64":
		return true, "number", "double"
	case "int":
		return true, "integer", ""
	case "int8":
		return true, "integer", ""
	case "int16":
		return true, "integer", ""
	case "int32":
		return true, "integer", "int32"
	case "int64":
		return true, "integer", "int64"
	case "rune":
		return true, "integer", ""
	case "string":
		return true, "string", ""
	case "uint":
		return true, "integer", ""
	case "uint8":
		return true, "integer", ""
	case "uint16":
		return true, "integer", ""
	case "uint32":
		return true, "integer", ""
	case "uint64":
		return true, "integer", ""
	case "uintptr":
		return true, "integer", ""
	case "[]byte":
		return true, "string", "binary"
	case "interface{}":
		return true, "object", ""

	case "time.Time":
		// This is a special case. While not strictly a primitive type, it's
		// something we can all agree is as simple as it needs to be.
		return true, "string", "date-time"
	}

	return false, "", ""
}

func IsMap(goType string) (bool, string, string) {

	rxMap := regexp.MustCompile(`map\[(.+)\]\**(.+)`)
	if rxMap.MatchString(goType) {
		matches := rxMap.FindStringSubmatch(goType)
		return true, matches[1], matches[2]
	}

	return false, "", ""
}

func IsSlice(goType string) (bool, string) {

	if strings.HasPrefix(goType, "[]") {
		return true, goType[2:]
	}

	return false, ""
}

func parseJsonInfo(s string) (string, bool) {
	rxJson := regexp.MustCompile(`json:"([^"]+)"`)

	if !rxJson.MatchString(s) {
		return "", false
	}

	matches := rxJson.FindStringSubmatch(s)
	words := strings.Split(matches[1], ",")

	name := words[0]
	if len(words) == 1 {
		return name, false
	}

	for _, word := range words[1:] {
		if word == "omitempty" {
			return name, true
		}
	}

	return name, false
}

func parseMemberDescription(s string) string {

	if s == "" {
		return ""
	}

	rxDesc := regexp.MustCompile(`@(?i:desc)\s+"([^"]+)"`)

	if !rxDesc.MatchString(s) {
		return ""
	}

	matches := rxDesc.FindStringSubmatch(s)

	return matches[1]
}

func parseValidateTag(s string) (isRequired bool) {
	rxValidate := regexp.MustCompile(`validate:"([^"]+)"`)

	if !rxValidate.MatchString(s) {
		return false
	}

	matches := rxValidate.FindStringSubmatch(s)

	words := strings.Split(matches[1], ",")
	for _, word := range words {
		if word == "required" {
			return true
		}
	}

	return false

}
<commit_msg>quotations around struct member descriptions are now optional.<commit_after>package main

import (
	"fmt"
	"github.com/jackmanlabs/bucket/jlog"
	"github.com/jackmanlabs/errors"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"log"
	"regexp"
	"strings"
	"unicode"
)

func findDefinition(referringPackage, typeName string) (*DefinitionIntermediate, error) {

	pkgInfo := pkgInfos[referringPackage]
	typeName = strings.TrimPrefix(typeName, "*")
	importPaths := possibleImportPaths(pkgInfo, typeName)

	if len(importPaths) == 0 {
		log.Print("ERROR: Import paths not available for this type: ", typeName)
		jlog.Log(pkgInfo)
	}

	if len(importPaths) > 1 {
		log.Printf("WARNING: Multiple package candidates found for type (%s):", typeName)
		jlog.Log(importPaths)
	}

	if strings.Contains(typeName, ".") {
		index := strings.Index(typeName, ".") + 1
		typeName = typeName[index:]
	}

	for _, importPath := range importPaths {

		if importPath == "" {
			log.Print("Import path is blank!")
		}

		bpkg, err := build.Import(importPath, srcPath, 0)
		if err != nil {
			return nil, errors.Stack(err)
		}

		fset := token.NewFileSet()
		pkgs, err := parser.ParseDir(fset, bpkg.Dir, nil, parser.AllErrors|parser.ParseComments)
		if err != nil {
			return nil, errors.Stack(err)
		}

		for _, pkg := range pkgs {
			definitionVisitor := &DefinitionVisitor{
				Fset:     fset,
				TypeName: typeName,
			}

			ast.Walk(definitionVisitor, pkg)

			if definitionVisitor.Definition != nil {
				definition := definitionVisitor.Definition
				definition.PackageName = pkg.Name
				definition.PackagePath = importPath

				// If this definition is an enum (underlying type is primitive), then we assume it's an enum type that needs enum values.
				if isPrimitive, _, _ := IsPrimitive(definition.UnderlyingType); isPrimitive {
					values, err := findEnumValues(definition.PackagePath, definition.Name)
					if err != nil {
						return nil, errors.Stack(err)
					}
					definition.Enumerations = values
				}

				return definition, nil
			}
		}
	}

	return nil, nil
}

type DefinitionVisitor struct {
	Fset       *token.FileSet
	TypeName   string
	Definition *DefinitionIntermediate
}

func (this *DefinitionVisitor) Visit(node ast.Node) (w ast.Visitor) {
	/*
		Name          string
		PackageName   string
		PackagePath   string
		Members       []MemberIntermediate
		IsStruct      bool
		EmbeddedTypes []string
	*/

	if this.Fset == nil {
		log.Println("fset is nil.")
		return nil
	}

	switch t := node.(type) {

	case *ast.TypeSpec:
		if t.Name.String() == this.TypeName {
			this.Definition = &DefinitionIntermediate{
				Name:           t.Name.String(),
				Comment:        t.Comment.Text(),
				Documentation:  t.Doc.Text(),
				UnderlyingType: resolveTypeExpression(t.Type),
				Members:        make(map[string]SchemerDefiner),
			}
		} else {
			return nil
		}
	case *ast.Field:

		if this.Definition == nil {
			// Ignore fields that don't belong to our definition.
			return nil
		}

		if len(t.Names) > 1 {
			// On our test code base, this was never printed.
			log.Print("WARNING: Multiple names discovered.")
		}

		// Handle embedded structs.
		if len(t.Names) == 0 {
			//ast.Fprint(os.Stdout, this.Fset, t, nil)

			if this.Definition.EmbeddedTypes == nil {
				this.Definition.EmbeddedTypes = make([]string, 0)
			}
			embedded := resolveTypeExpression(t.Type)
			this.Definition.EmbeddedTypes = append(this.Definition.EmbeddedTypes, embedded)
			return nil
		}

		name := t.Names[0].String()

		// Ignore fields that are not exported.
		name_ := []rune(name)
		if unicode.IsLower(name_[0]) {
			return nil
		}

		var (
			jsonName      string
			jsonOmitEmpty bool
			isRequired    bool
		)
		if t.Tag != nil {
			jsonName, jsonOmitEmpty = parseJsonInfo(t.Tag.Value)
			if jsonName == "-" {
				return nil
			}

			isRequired = parseValidateTag(t.Tag.Value)
		}

		var desc string = parseMemberDescription(t.Doc.Text())
		if desc == "" {
			desc = parseMemberDescription(t.Comment.Text())
		}

		goType := resolveTypeExpression(t.Type)

		var member SchemerDefiner

		if isMap, k, v := IsMap(goType); isMap {
			keyType := &MemberIntermediate{
				Type: k,
				Name: name,
			}

			valueType := &MemberIntermediate{
				Type: v,
				Name: name,
			}

			member = &MapIntermediate{
				Name:          name,
				Type:          goType,
				JsonName:      jsonName,
				JsonOmitEmpty: jsonOmitEmpty,
				ValueType:     valueType,
				KeyType:       keyType,
				Description:   desc,
				Required:      isRequired,
			}

		} else if isSlice, v := IsSlice(goType); isSlice {
			valueType := &MemberIntermediate{
				Type: v,
				Name: name,
			}

			member = &SliceIntermediate{
				Name:          name,
				Type:          goType,
				JsonName:      jsonName,
				JsonOmitEmpty: jsonOmitEmpty,
				ValueType:     valueType,
				Description:   desc,
				Required:      isRequired,
			}
		} else {
			member = &MemberIntermediate{
				Type:          goType,
				Name:          name,
				JsonName:      jsonName,
				JsonOmitEmpty: jsonOmitEmpty,
				Description:   desc,
				Required:      isRequired,
			}
		}

		this.Definition.Members[name] = member

		return nil

	case *ast.FuncDecl:
		// Ignore function declarations.
		return nil
	case *ast.ImportSpec:
		// Ignore import declarations.
		return nil
	case nil:
	default:
		//log.Printf("unexpected type %T\n", t) // %T prints whatever type t has
	}

	return this
}

func resolveTypeExpression(expr ast.Expr) string {

	switch t := expr.(type) {
	case *ast.StarExpr:
		return "*" + resolveTypeExpression(t.X)
	case *ast.ArrayType:
		return "[]" + resolveTypeExpression(t.Elt)
	case *ast.Ident:
		return t.Name
	case *ast.SelectorExpr:
		return resolveTypeExpression(t.X) + "." + t.Sel.Name
	case *ast.MapType:
		return fmt.Sprintf("map[%s]%s", resolveTypeExpression(t.Key), resolveTypeExpression(t.Value))
	case *ast.InterfaceType:
		return "interface{}"
	case *ast.StructType:
		return "struct"
	default:
		return fmt.Sprintf("Unknown<%T>", t)
	}

}

// Returns true if primitive.
// If the type is primitive, the string parameters indicate the type and format per swagger spec.
// http://swagger.io/specification/ (Data Types)
func IsPrimitive(goType string) (bool, string, string) {

	// TODO: Figure out how complex types should be supported.

	goType = strings.Trim(goType, "*")

	switch goType {
	case "bool":
		return true, "boolean", ""
	case "byte":
		return true, "string", "byte"
	case "complex64":
		return true, "string", ""
	case "complex128":
		return true, "string", ""
	case "float32":
		return true, "number", "float"
	case "float64":
		return true, "number", "double"
	case "int":
		return true, "integer", ""
	case "int8":
		return true, "integer", ""
	case "int16":
		return true, "integer", ""
	case "int32":
		return true, "integer", "int32"
	case "int64":
		return true, "integer", "int64"
	case "rune":
		return true, "integer", ""
	case "string":
		return true, "string", ""
	case "uint":
		return true, "integer", ""
	case "uint8":
		return true, "integer", ""
	case "uint16":
		return true, "integer", ""
	case "uint32":
		return true, "integer", ""
	case "uint64":
		return true, "integer", ""
	case "uintptr":
		return true, "integer", ""
	case "[]byte":
		return true, "string", "binary"
	case "interface{}":
		return true, "object", ""

	case "time.Time":
		// This is a special case. While not strictly a primitive type, it's
		// something we can all agree is as simple as it needs to be.
		return true, "string", "date-time"
	}

	return false, "", ""
}

func IsMap(goType string) (bool, string, string) {

	rxMap := regexp.MustCompile(`map\[(.+)\]\**(.+)`)
	if rxMap.MatchString(goType) {
		matches := rxMap.FindStringSubmatch(goType)
		return true, matches[1], matches[2]
	}

	return false, "", ""
}

func IsSlice(goType string) (bool, string) {

	if strings.HasPrefix(goType, "[]") {
		return true, goType[2:]
	}

	return false, ""
}

func parseJsonInfo(s string) (string, bool) {
	rxJson := regexp.MustCompile(`json:"([^"]+)"`)

	if !rxJson.MatchString(s) {
		return "", false
	}

	matches := rxJson.FindStringSubmatch(s)
	words := strings.Split(matches[1], ",")

	name := words[0]
	if len(words) == 1 {
		return name, false
	}

	for _, word := range words[1:] {
		if word == "omitempty" {
			return name, true
		}
	}

	return name, false
}

func parseMemberDescription(s string) string {

	if s == "" {
		return ""
	}

	rxDesc := regexp.MustCompile(`@(?i:desc)\s+"?([^"]+)"?`)

	if !rxDesc.MatchString(s) {
		return ""
	}

	matches := rxDesc.FindStringSubmatch(s)

	return matches[1]
}

func parseValidateTag(s string) (isRequired bool) {
	rxValidate := regexp.MustCompile(`validate:"([^"]+)"`)

	if !rxValidate.MatchString(s) {
		return false
	}

	matches := rxValidate.FindStringSubmatch(s)

	words := strings.Split(matches[1], ",")
	for _, word := range words {
		if word == "required" {
			return true
		}
	}

	return false

}
<|endoftext|>


<commit_before>// Copyright Istio 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 telemetry

import (
	"fmt"
	"testing"

	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/serviceregistry/provider"
)

func TestBuildStatPrefix(t *testing.T) {
	tests := []struct {
		name        string
		statPattern string
		host        string
		subsetName  string
		port        *model.Port
		attributes  *model.ServiceAttributes
		want        string
	}{
		{
			"Service only pattern",
			"%SERVICE%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default",
		},
		{
			"Service only pattern from different namespace",
			"%SERVICE%",
			"reviews.namespace1.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "namespace1",
			},
			"reviews.namespace1",
		},
		{
			"Service with port pattern from different namespace",
			"%SERVICE%.%SERVICE_PORT%",
			"reviews.namespace1.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "namespace1",
			},
			"reviews.namespace1.7443",
		},
		{
			"Service FQDN only pattern",
			"%SERVICE_FQDN%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local",
		},
		{
			"Service With Port pattern",
			"%SERVICE%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default_7443",
		},
		{
			"Service With Port Name pattern",
			"%SERVICE%_%SERVICE_PORT_NAME%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default_grpc-svc",
		},
		{
			"Service With Port and Port Name pattern",
			"%SERVICE%_%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default_grpc-svc_7443",
		},
		{
			"Service FQDN With Port pattern",
			"%SERVICE_FQDN%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local_7443",
		},
		{
			"Service FQDN With Port Name pattern",
			"%SERVICE_FQDN%_%SERVICE_PORT_NAME%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local_grpc-svc",
		},
		{
			"Service FQDN With Port and Port Name pattern",
			"%SERVICE_FQDN%_%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local_grpc-svc_7443",
		},
		{
			"Service FQDN With Empty Subset, Port and Port Name pattern",
			"%SERVICE_FQDN%%SUBSET_NAME%_%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local_grpc-svc_7443",
		},
		{
			"Service FQDN With Subset, Port and Port Name pattern",
			"%SERVICE_FQDN%.%SUBSET_NAME%.%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"v1",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local.v1.grpc-svc_7443",
		},
		{
			"Service FQDN With Unknown Pattern",
			"%SERVICE_FQDN%.%DUMMY%",
			"reviews.default.svc.cluster.local",
			"v1",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local.%DUMMY%",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := BuildStatPrefix(tt.statPattern, tt.host, tt.subsetName, tt.port, tt.attributes)
			if got != tt.want {
				t.Errorf("Expected alt statname %s, but got %s", tt.want, got)
			}
		})
	}
}

func TestTraceOperation(t *testing.T) {
	tests := []struct {
		host  string
		port  int
		match string
	}{
		{"localhost", 3000, "localhost:3000/*"},
		{"127.0.0.1", 3000, "127.0.0.1:3000/*"},
		{"::1", 3000, "[::1]:3000/*"},
		{"2001:4860:0:2001::68", 3000, "[2001:4860:0:2001::68]:3000/*"},
	}
	for _, tt := range tests {
		t.Run(fmt.Sprint(tt.host), func(t *testing.T) {
			if got := TraceOperation(tt.host, tt.port); got != tt.match {
				t.Fatalf("got %v wanted %v", got, tt.match)
			}
		})
	}
}
<commit_msg>fix: increase 100% test coverage for telemetry_test.go (#38900)<commit_after>// Copyright Istio 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 telemetry

import (
	"fmt"
	"testing"

	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/serviceregistry/provider"
)

func TestBuildStatPrefix(t *testing.T) {
	tests := []struct {
		name        string
		statPattern string
		host        string
		subsetName  string
		port        *model.Port
		attributes  *model.ServiceAttributes
		want        string
	}{
		{
			"Service only pattern",
			"%SERVICE%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default",
		},
		{
			"Service only pattern from different namespace",
			"%SERVICE%",
			"reviews.namespace1.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "namespace1",
			},
			"reviews.namespace1",
		},
		{
			"Service with port pattern from different namespace",
			"%SERVICE%.%SERVICE_PORT%",
			"reviews.namespace1.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "namespace1",
			},
			"reviews.namespace1.7443",
		},
		{
			"Service FQDN only pattern",
			"%SERVICE_FQDN%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local",
		},
		{
			"Service With Port pattern",
			"%SERVICE%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default_7443",
		},
		{
			"Service With Port Name pattern",
			"%SERVICE%_%SERVICE_PORT_NAME%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default_grpc-svc",
		},
		{
			"Service With Port and Port Name pattern",
			"%SERVICE%_%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default_grpc-svc_7443",
		},
		{
			"Service FQDN With Port pattern",
			"%SERVICE_FQDN%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local_7443",
		},
		{
			"Service FQDN With Port Name pattern",
			"%SERVICE_FQDN%_%SERVICE_PORT_NAME%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local_grpc-svc",
		},
		{
			"Service FQDN With Port and Port Name pattern",
			"%SERVICE_FQDN%_%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local_grpc-svc_7443",
		},
		{
			"Service FQDN With Empty Subset, Port and Port Name pattern",
			"%SERVICE_FQDN%%SUBSET_NAME%_%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local_grpc-svc_7443",
		},
		{
			"Service FQDN With Subset, Port and Port Name pattern",
			"%SERVICE_FQDN%.%SUBSET_NAME%.%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			"reviews.default.svc.cluster.local",
			"v1",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local.v1.grpc-svc_7443",
		},
		{
			"Service FQDN With Unknown Pattern",
			"%SERVICE_FQDN%.%DUMMY%",
			"reviews.default.svc.cluster.local",
			"v1",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				ServiceRegistry: provider.Kubernetes,
				Name:            "reviews",
				Namespace:       "default",
			},
			"reviews.default.svc.cluster.local.%DUMMY%",
		},
		{
			"Service FQDN without ServiceRegistry property",
			"%SERVICE_FQDN%",
			"reviews.default.svc.cluster.local",
			"v1",
			&model.Port{Name: "grpc-svc", Port: 7443, Protocol: "GRPC"},
			&model.ServiceAttributes{
				Name:      "reviews",
				Namespace: "default",
			},
			"reviews.default.svc.cluster.local",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := BuildStatPrefix(tt.statPattern, tt.host, tt.subsetName, tt.port, tt.attributes)
			if got != tt.want {
				t.Errorf("BuildStatPrefix:: Expected alt statname %s, but got %s", tt.want, got)
			}
		})
	}
}

func TestTraceOperation(t *testing.T) {
	tests := []struct {
		host  string
		port  int
		match string
	}{
		{"localhost", 3000, "localhost:3000/*"},
		{"127.0.0.1", 3000, "127.0.0.1:3000/*"},
		{"::1", 3000, "[::1]:3000/*"},
		{"2001:4860:0:2001::68", 3000, "[2001:4860:0:2001::68]:3000/*"},
	}
	for _, tt := range tests {
		t.Run(fmt.Sprint(tt.host), func(t *testing.T) {
			if got := TraceOperation(tt.host, tt.port); got != tt.match {
				t.Fatalf("got %v wanted %v", got, tt.match)
			}
		})
	}
}

func TestBuildInboundStatPrefix(t *testing.T) {
	tests := []struct {
		name        string
		statPattern string
		tm          FilterChainMetadata
		subset      string
		port        uint32
		portName    string
		want        string
	}{
		{
			"Service only pattern",
			"%SERVICE%",
			FilterChainMetadata{InstanceHostname: "svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "default"},
			"",
			7443,
			"grpc-svc",
			"reviews.default",
		},
		{
			"Service only pattern with empty service name",
			"%SERVICE%",
			FilterChainMetadata{InstanceHostname: "svc.cluster.local"},
			"",
			7443,
			"grpc-svc",
			"svc.cluster.local",
		},
		{
			"Service only pattern from different namespace",
			"%SERVICE%",
			FilterChainMetadata{InstanceHostname: "reviews.namespace1.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "namespace1"},
			"",
			7443,
			"grpc-svc",
			"reviews.namespace1",
		},
		{
			"Service with port pattern from different namespace",
			"%SERVICE%.%SERVICE_PORT%",
			FilterChainMetadata{InstanceHostname: "reviews.namespace1.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "namespace1"},
			"",
			7443,
			"grpc-svc",
			"reviews.namespace1.7443",
		},
		{
			"Service FQDN only pattern",
			"%SERVICE_FQDN%",
			FilterChainMetadata{InstanceHostname: "reviews.default.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "default"},
			"",
			7443,
			"grpc-svc",
			"reviews.default.svc.cluster.local",
		},
		{
			"Service With Port pattern",
			"%SERVICE%_%SERVICE_PORT%",
			FilterChainMetadata{InstanceHostname: "reviews.default.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "default"},
			"",
			7443,
			"grpc-svc",
			"reviews.default_7443",
		},
		{
			"Service With Port Name pattern",
			"%SERVICE%_%SERVICE_PORT_NAME%",
			FilterChainMetadata{InstanceHostname: "reviews.default.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "default"},
			"",
			7443,
			"grpc-svc",
			"reviews.default_grpc-svc",
		},
		{
			"Service With Port and Port Name pattern",
			"%SERVICE%_%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			FilterChainMetadata{InstanceHostname: "reviews.default.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "default"},
			"",
			7443,
			"grpc-svc",
			"reviews.default_grpc-svc_7443",
		},
		{
			"Service FQDN With Port pattern",
			"%SERVICE_FQDN%_%SERVICE_PORT%",
			FilterChainMetadata{InstanceHostname: "reviews.default.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "default"},
			"",
			7443,
			"grpc-svc",
			"reviews.default.svc.cluster.local_7443",
		},
		{
			"Service FQDN With Port Name pattern",
			"%SERVICE_FQDN%_%SERVICE_PORT_NAME%",
			FilterChainMetadata{InstanceHostname: "reviews.default.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "default"},
			"",
			7443,
			"grpc-svc",
			"reviews.default.svc.cluster.local_grpc-svc",
		},
		{
			"Service FQDN With Port and Port Name pattern",
			"%SERVICE_FQDN%_%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			FilterChainMetadata{InstanceHostname: "reviews.default.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "default"},
			"",
			7443,
			"grpc-svc",
			"reviews.default.svc.cluster.local_grpc-svc_7443",
		},
		{
			"Service FQDN With Empty Subset, Port and Port Name pattern",
			"%SERVICE_FQDN%%SUBSET_NAME%_%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			FilterChainMetadata{InstanceHostname: "reviews.default.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "default"},
			"",
			7443,
			"grpc-svc",
			"reviews.default.svc.cluster.local_grpc-svc_7443",
		},
		{
			"Service FQDN With Subset, Port and Port Name pattern",
			"%SERVICE_FQDN%.%SUBSET_NAME%.%SERVICE_PORT_NAME%_%SERVICE_PORT%",
			FilterChainMetadata{InstanceHostname: "reviews.default.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "default"},
			"v1",
			7443,
			"grpc-svc",
			"reviews.default.svc.cluster.local.v1.grpc-svc_7443",
		},
		{
			"Service FQDN With Unknown Pattern",
			"%SERVICE_FQDN%.%DUMMY%",
			FilterChainMetadata{InstanceHostname: "reviews.default.svc.cluster.local", KubernetesServiceName: "reviews", KubernetesServiceNamespace: "default"},
			"",
			7443,
			"grpc-svc",
			"reviews.default.svc.cluster.local.%DUMMY%",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := BuildInboundStatPrefix(tt.statPattern, tt.tm, tt.subset, tt.port, tt.portName)
			if got != tt.want {
				t.Errorf("BuildInboundStatPrefix:: Expected alt statname %s, but got %s", tt.want, got)
			}
		})
	}
}
<|endoftext|>


<commit_before>package sysregistriesv2

import (
	"github.com/containers/image/types"
	"github.com/stretchr/testify/assert"
	"testing"
)

var testConfig = []byte("")

func init() {
	readConf = func(_ string) ([]byte, error) {
		return testConfig, nil
	}
}

func TestParseURL(t *testing.T) {
	var err error
	var url string

	// invalid URLs
	_, err = parseURL("https://example.com")
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "invalid URL 'https://example.com': URI schemes are not supported")

	_, err = parseURL("john.doe@example.com")
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "invalid URL 'john.doe@example.com': user/password are not supported")

	// valid URLs
	url, err = parseURL("example.com")
	assert.Nil(t, err)
	assert.Equal(t, "example.com", url)

	url, err = parseURL("example.com/") // trailing slashes are stripped
	assert.Nil(t, err)
	assert.Equal(t, "example.com", url)

	url, err = parseURL("example.com//////") // trailing slahes are stripped
	assert.Nil(t, err)
	assert.Equal(t, "example.com", url)

	url, err = parseURL("example.com:5000/with/path")
	assert.Nil(t, err)
	assert.Equal(t, "example.com:5000/with/path", url)
}

func TestEmptyConfig(t *testing.T) {
	testConfig = []byte(``)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 0, len(registries))
}

func TestMirrors(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com"

[[registry.mirror]]
url = "mirror-1.registry.com"

[[registry.mirror]]
url = "mirror-2.registry.com"
insecure = true

[[registry]]
url = "blocked.registry.com"
blocked = true`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 2, len(registries))

	var reg *Registry
	reg = FindRegistry("registry.com/image:tag", registries)
	assert.NotNil(t, reg)
	assert.Equal(t, 2, len(reg.Mirrors))
	assert.Equal(t, "mirror-1.registry.com", reg.Mirrors[0].URL)
	assert.False(t, reg.Mirrors[0].Insecure)
	assert.Equal(t, "mirror-2.registry.com", reg.Mirrors[1].URL)
	assert.True(t, reg.Mirrors[1].Insecure)
}

func TestMissingRegistryURL(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry-a.com"
unqualified-search = true

[[registry]]
url = "registry-b.com"

[[registry]]
unqualified-search = true`)
	configCache = make(map[string][]Registry)
	_, err := GetRegistries(nil)
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "invalid URL")
}

func TestMissingMirrorURL(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry-a.com"
unqualified-search = true

[[registry]]
url = "registry-b.com"
[[registry.mirror]]
url = "mirror-b.com"
[[registry.mirror]]
`)
	configCache = make(map[string][]Registry)
	_, err := GetRegistries(nil)
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "invalid URL")
}

func TestFindRegistry(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com:5000"
prefix = "simple-prefix.com"

[[registry]]
url = "another-registry.com:5000"
prefix = "complex-prefix.com:4000/with/path"

[[registry]]
url = "registry.com:5000"
prefix = "another-registry.com"

[[registry]]
url = "no-prefix.com"

[[registry]]
url = "empty-prefix.com"
prefix = ""`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 5, len(registries))

	var reg *Registry
	reg = FindRegistry("simple-prefix.com/foo/bar:latest", registries)
	assert.NotNil(t, reg)
	assert.Equal(t, "simple-prefix.com", reg.Prefix)
	assert.Equal(t, reg.URL, "registry.com:5000")

	reg = FindRegistry("complex-prefix.com:4000/with/path/and/beyond:tag", registries)
	assert.NotNil(t, reg)
	assert.Equal(t, "complex-prefix.com:4000/with/path", reg.Prefix)
	assert.Equal(t, "another-registry.com:5000", reg.URL)

	reg = FindRegistry("no-prefix.com/foo:tag", registries)
	assert.NotNil(t, reg)
	assert.Equal(t, "no-prefix.com", reg.Prefix)
	assert.Equal(t, "no-prefix.com", reg.URL)

	reg = FindRegistry("empty-prefix.com/foo:tag", registries)
	assert.NotNil(t, reg)
	assert.Equal(t, "empty-prefix.com", reg.Prefix)
	assert.Equal(t, "empty-prefix.com", reg.URL)
}

func assertSearchRegistryURLsEqual(t *testing.T, expected []string, regs []Registry) {
	// verify the expected registries and their order
	names := []string{}
	for _, r := range regs {
		names = append(names, r.URL)
	}
	assert.Equal(t, expected, names)
}

func TestFindUnqualifiedSearchRegistries(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry-a.com"
unqualified-search = true

[[registry]]
url = "registry-b.com"

[[registry]]
url = "registry-c.com"
unqualified-search = true`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 3, len(registries))

	unqRegs := FindUnqualifiedSearchRegistries(registries)
	assert.Equal(t, 2, len(unqRegs))

	// check if the expected images are actually in the array
	var reg *Registry
	reg = FindRegistry("registry-a.com/foo:bar", unqRegs)
	assert.NotNil(t, reg)
	reg = FindRegistry("registry-c.com/foo:bar", unqRegs)
	assert.NotNil(t, reg)
}

func TestInsecureConfligs(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com"

[[registry.mirror]]
url = "mirror-1.registry.com"

[[registry.mirror]]
url = "mirror-2.registry.com"

[[registry]]
url = "registry.com"
insecure = true
`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.NotNil(t, err)
	assert.Nil(t, registries)
	assert.Contains(t, err.Error(), "registry 'registry.com' is defined multiple times with conflicting 'insecure' setting")
}

func TestBlockConfligs(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com"

[[registry.mirror]]
url = "mirror-1.registry.com"

[[registry.mirror]]
url = "mirror-2.registry.com"

[[registry]]
url = "registry.com"
blocked = true
`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.NotNil(t, err)
	assert.Nil(t, registries)
	assert.Contains(t, err.Error(), "registry 'registry.com' is defined multiple times with conflicting 'blocked' setting")
}

func TestUnmarshalConfig(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com"

[[registry.mirror]]
url = "mirror-1.registry.com"

[[registry.mirror]]
url = "mirror-2.registry.com"

[[registry]]
url = "blocked.registry.com"
blocked = true

[[registry]]
url = "insecure.registry.com"
insecure = true

[[registry]]
url = "untrusted.registry.com"
insecure = true`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 4, len(registries))
}

func TestV1BackwardsCompatibility(t *testing.T) {
	testConfig = []byte(`
[registries.search]
registries = ["registry-a.com////", "registry-c.com"]

[registries.block]
registries = ["registry-b.com"]

[registries.insecure]
registries = ["registry-d.com", "registry-e.com", "registry-a.com"]`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 5, len(registries))

	unqRegs := FindUnqualifiedSearchRegistries(registries)
	assert.Equal(t, 2, len(unqRegs))

	// check if the expected images are actually in the array
	var reg *Registry
	reg = FindRegistry("registry-a.com/foo:bar", unqRegs)
	assert.Equal(t, "registry-a.com", reg.URL)
	assert.NotNil(t, reg)
	reg = FindRegistry("registry-c.com/foo:bar", unqRegs)
	assert.NotNil(t, reg)

	// check if merging works
	reg = FindRegistry("registry-a.com/bar/foo/barfoo:latest", registries)
	assert.NotNil(t, reg)
	assert.True(t, reg.Search)
	assert.True(t, reg.Insecure)
	assert.False(t, reg.Blocked)
}

func TestMixingV1andV2(t *testing.T) {
	testConfig = []byte(`
[registries.search]
registries = ["registry-a.com", "registry-c.com"]

[registries.block]
registries = ["registry-b.com"]

[registries.insecure]
registries = ["registry-d.com", "registry-e.com", "registry-a.com"]

[[registry]]
url = "registry-a.com"
unqualified-search = true

[[registry]]
url = "registry-b.com"

[[registry]]
url = "registry-c.com"
unqualified-search = true `)

	configCache = make(map[string][]Registry)
	_, err := GetRegistries(nil)
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "mixing sysregistry v1/v2 is not supported")
}

func TestConfigCache(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com"

[[registry.mirror]]
url = "mirror-1.registry.com"

[[registry.mirror]]
url = "mirror-2.registry.com"

[[registry]]
url = "blocked.registry.com"
blocked = true

[[registry]]
url = "insecure.registry.com"
insecure = true

[[registry]]
url = "untrusted.registry.com"
insecure = true`)

	ctx := &types.SystemContext{SystemRegistriesConfPath: "foo"}

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(ctx)
	assert.Nil(t, err)
	assert.Equal(t, 4, len(registries))

	// empty the config, but use the same SystemContext to show that the
	// previously specified registries are in the cache
	testConfig = []byte("")
	registries, err = GetRegistries(ctx)
	assert.Nil(t, err)
	assert.Equal(t, 4, len(registries))
}
<commit_msg>Test that reading v2 config preserves the registry search order<commit_after>package sysregistriesv2

import (
	"testing"

	"github.com/containers/image/types"
	"github.com/stretchr/testify/assert"
)

var testConfig = []byte("")

func init() {
	readConf = func(_ string) ([]byte, error) {
		return testConfig, nil
	}
}

func TestParseURL(t *testing.T) {
	var err error
	var url string

	// invalid URLs
	_, err = parseURL("https://example.com")
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "invalid URL 'https://example.com': URI schemes are not supported")

	_, err = parseURL("john.doe@example.com")
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "invalid URL 'john.doe@example.com': user/password are not supported")

	// valid URLs
	url, err = parseURL("example.com")
	assert.Nil(t, err)
	assert.Equal(t, "example.com", url)

	url, err = parseURL("example.com/") // trailing slashes are stripped
	assert.Nil(t, err)
	assert.Equal(t, "example.com", url)

	url, err = parseURL("example.com//////") // trailing slahes are stripped
	assert.Nil(t, err)
	assert.Equal(t, "example.com", url)

	url, err = parseURL("example.com:5000/with/path")
	assert.Nil(t, err)
	assert.Equal(t, "example.com:5000/with/path", url)
}

func TestEmptyConfig(t *testing.T) {
	testConfig = []byte(``)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 0, len(registries))
}

func TestMirrors(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com"

[[registry.mirror]]
url = "mirror-1.registry.com"

[[registry.mirror]]
url = "mirror-2.registry.com"
insecure = true

[[registry]]
url = "blocked.registry.com"
blocked = true`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 2, len(registries))

	var reg *Registry
	reg = FindRegistry("registry.com/image:tag", registries)
	assert.NotNil(t, reg)
	assert.Equal(t, 2, len(reg.Mirrors))
	assert.Equal(t, "mirror-1.registry.com", reg.Mirrors[0].URL)
	assert.False(t, reg.Mirrors[0].Insecure)
	assert.Equal(t, "mirror-2.registry.com", reg.Mirrors[1].URL)
	assert.True(t, reg.Mirrors[1].Insecure)
}

func TestMissingRegistryURL(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry-a.com"
unqualified-search = true

[[registry]]
url = "registry-b.com"

[[registry]]
unqualified-search = true`)
	configCache = make(map[string][]Registry)
	_, err := GetRegistries(nil)
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "invalid URL")
}

func TestMissingMirrorURL(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry-a.com"
unqualified-search = true

[[registry]]
url = "registry-b.com"
[[registry.mirror]]
url = "mirror-b.com"
[[registry.mirror]]
`)
	configCache = make(map[string][]Registry)
	_, err := GetRegistries(nil)
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "invalid URL")
}

func TestFindRegistry(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com:5000"
prefix = "simple-prefix.com"

[[registry]]
url = "another-registry.com:5000"
prefix = "complex-prefix.com:4000/with/path"

[[registry]]
url = "registry.com:5000"
prefix = "another-registry.com"

[[registry]]
url = "no-prefix.com"

[[registry]]
url = "empty-prefix.com"
prefix = ""`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 5, len(registries))

	var reg *Registry
	reg = FindRegistry("simple-prefix.com/foo/bar:latest", registries)
	assert.NotNil(t, reg)
	assert.Equal(t, "simple-prefix.com", reg.Prefix)
	assert.Equal(t, reg.URL, "registry.com:5000")

	reg = FindRegistry("complex-prefix.com:4000/with/path/and/beyond:tag", registries)
	assert.NotNil(t, reg)
	assert.Equal(t, "complex-prefix.com:4000/with/path", reg.Prefix)
	assert.Equal(t, "another-registry.com:5000", reg.URL)

	reg = FindRegistry("no-prefix.com/foo:tag", registries)
	assert.NotNil(t, reg)
	assert.Equal(t, "no-prefix.com", reg.Prefix)
	assert.Equal(t, "no-prefix.com", reg.URL)

	reg = FindRegistry("empty-prefix.com/foo:tag", registries)
	assert.NotNil(t, reg)
	assert.Equal(t, "empty-prefix.com", reg.Prefix)
	assert.Equal(t, "empty-prefix.com", reg.URL)
}

func assertSearchRegistryURLsEqual(t *testing.T, expected []string, regs []Registry) {
	// verify the expected registries and their order
	names := []string{}
	for _, r := range regs {
		names = append(names, r.URL)
	}
	assert.Equal(t, expected, names)
}

func TestFindUnqualifiedSearchRegistries(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry-a.com"
unqualified-search = true

[[registry]]
url = "registry-b.com"

[[registry]]
url = "registry-c.com"
unqualified-search = true

[[registry]]
url = "registry-d.com"
unqualified-search = true
`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 4, len(registries))

	unqRegs := FindUnqualifiedSearchRegistries(registries)
	assertSearchRegistryURLsEqual(t, []string{"registry-a.com", "registry-c.com", "registry-d.com"}, unqRegs)
}

func TestInsecureConfligs(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com"

[[registry.mirror]]
url = "mirror-1.registry.com"

[[registry.mirror]]
url = "mirror-2.registry.com"

[[registry]]
url = "registry.com"
insecure = true
`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.NotNil(t, err)
	assert.Nil(t, registries)
	assert.Contains(t, err.Error(), "registry 'registry.com' is defined multiple times with conflicting 'insecure' setting")
}

func TestBlockConfligs(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com"

[[registry.mirror]]
url = "mirror-1.registry.com"

[[registry.mirror]]
url = "mirror-2.registry.com"

[[registry]]
url = "registry.com"
blocked = true
`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.NotNil(t, err)
	assert.Nil(t, registries)
	assert.Contains(t, err.Error(), "registry 'registry.com' is defined multiple times with conflicting 'blocked' setting")
}

func TestUnmarshalConfig(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com"

[[registry.mirror]]
url = "mirror-1.registry.com"

[[registry.mirror]]
url = "mirror-2.registry.com"

[[registry]]
url = "blocked.registry.com"
blocked = true

[[registry]]
url = "insecure.registry.com"
insecure = true

[[registry]]
url = "untrusted.registry.com"
insecure = true`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 4, len(registries))
}

func TestV1BackwardsCompatibility(t *testing.T) {
	testConfig = []byte(`
[registries.search]
registries = ["registry-a.com////", "registry-c.com"]

[registries.block]
registries = ["registry-b.com"]

[registries.insecure]
registries = ["registry-d.com", "registry-e.com", "registry-a.com"]`)

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(nil)
	assert.Nil(t, err)
	assert.Equal(t, 5, len(registries))

	unqRegs := FindUnqualifiedSearchRegistries(registries)
	assert.Equal(t, 2, len(unqRegs))

	// check if the expected images are actually in the array
	var reg *Registry
	reg = FindRegistry("registry-a.com/foo:bar", unqRegs)
	assert.Equal(t, "registry-a.com", reg.URL)
	assert.NotNil(t, reg)
	reg = FindRegistry("registry-c.com/foo:bar", unqRegs)
	assert.NotNil(t, reg)

	// check if merging works
	reg = FindRegistry("registry-a.com/bar/foo/barfoo:latest", registries)
	assert.NotNil(t, reg)
	assert.True(t, reg.Search)
	assert.True(t, reg.Insecure)
	assert.False(t, reg.Blocked)
}

func TestMixingV1andV2(t *testing.T) {
	testConfig = []byte(`
[registries.search]
registries = ["registry-a.com", "registry-c.com"]

[registries.block]
registries = ["registry-b.com"]

[registries.insecure]
registries = ["registry-d.com", "registry-e.com", "registry-a.com"]

[[registry]]
url = "registry-a.com"
unqualified-search = true

[[registry]]
url = "registry-b.com"

[[registry]]
url = "registry-c.com"
unqualified-search = true `)

	configCache = make(map[string][]Registry)
	_, err := GetRegistries(nil)
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "mixing sysregistry v1/v2 is not supported")
}

func TestConfigCache(t *testing.T) {
	testConfig = []byte(`
[[registry]]
url = "registry.com"

[[registry.mirror]]
url = "mirror-1.registry.com"

[[registry.mirror]]
url = "mirror-2.registry.com"

[[registry]]
url = "blocked.registry.com"
blocked = true

[[registry]]
url = "insecure.registry.com"
insecure = true

[[registry]]
url = "untrusted.registry.com"
insecure = true`)

	ctx := &types.SystemContext{SystemRegistriesConfPath: "foo"}

	configCache = make(map[string][]Registry)
	registries, err := GetRegistries(ctx)
	assert.Nil(t, err)
	assert.Equal(t, 4, len(registries))

	// empty the config, but use the same SystemContext to show that the
	// previously specified registries are in the cache
	testConfig = []byte("")
	registries, err = GetRegistries(ctx)
	assert.Nil(t, err)
	assert.Equal(t, 4, len(registries))
}
<|endoftext|>


<commit_before>package serf

import (
	"fmt"
	"math"
	"math/rand"
	"net"
	"regexp"
	"sync"
	"time"
)

// QueryParam is provided to Query() to configure the parameters of the
// query. If not provided, sane defaults will be used.
type QueryParam struct {
	// If provided, we restrict the nodes that should respond to those
	// with names in this list
	FilterNodes []string

	// FilterTags maps a tag name to a regular expression that is applied
	// to restrict the nodes that should respond
	FilterTags map[string]string

	// If true, we are requesting an delivery acknowledgement from
	// every node that meets the filter requirement. This means nodes
	// the receive the message but do not pass the filters, will not
	// send an ack.
	RequestAck bool

	// RelayFactor controls the number of duplicate responses to relay
	// back to the sender through other nodes for redundancy.
	RelayFactor uint8

	// The timeout limits how long the query is left open. If not provided,
	// then a default timeout is used based on the configuration of Serf
	Timeout time.Duration
}

// DefaultQueryTimeout returns the default timeout value for a query
// Computed as GossipInterval * QueryTimeoutMult * log(N+1)
func (s *Serf) DefaultQueryTimeout() time.Duration {
	n := s.memberlist.NumMembers()
	timeout := s.config.MemberlistConfig.GossipInterval
	timeout *= time.Duration(s.config.QueryTimeoutMult)
	timeout *= time.Duration(math.Ceil(math.Log10(float64(n + 1))))
	return timeout
}

// DefaultQueryParam is used to return the default query parameters
func (s *Serf) DefaultQueryParams() *QueryParam {
	return &QueryParam{
		FilterNodes: nil,
		FilterTags:  nil,
		RequestAck:  false,
		Timeout:     s.DefaultQueryTimeout(),
	}
}

// encodeFilters is used to convert the filters into the wire format
func (q *QueryParam) encodeFilters() ([][]byte, error) {
	var filters [][]byte

	// Add the node filter
	if len(q.FilterNodes) > 0 {
		if buf, err := encodeFilter(filterNodeType, q.FilterNodes); err != nil {
			return nil, err
		} else {
			filters = append(filters, buf)
		}
	}

	// Add the tag filters
	for tag, expr := range q.FilterTags {
		filt := filterTag{tag, expr}
		if buf, err := encodeFilter(filterTagType, &filt); err != nil {
			return nil, err
		} else {
			filters = append(filters, buf)
		}
	}

	return filters, nil
}

// QueryResponse is returned for each new Query. It is used to collect
// Ack's as well as responses and to provide those back to a client.
type QueryResponse struct {
	// ackCh is used to send the name of a node for which we've received an ack
	ackCh chan string

	// deadline is the query end time (start + query timeout)
	deadline time.Time

	// Query ID
	id uint32

	// Stores the LTime of the query
	lTime LamportTime

	// respCh is used to send a response from a node
	respCh chan NodeResponse

	// acks/responses are used to track the nodes that have sent an ack/response
	acks      map[string]struct{}
	responses map[string]struct{}

	closed    bool
	closeLock sync.Mutex
}

// newQueryResponse is used to construct a new query response
func newQueryResponse(n int, q *messageQuery) *QueryResponse {
	resp := &QueryResponse{
		deadline:  time.Now().Add(q.Timeout),
		id:        q.ID,
		lTime:     q.LTime,
		respCh:    make(chan NodeResponse, n),
		responses: make(map[string]struct{}),
	}
	if q.Ack() {
		resp.ackCh = make(chan string, n)
		resp.acks = make(map[string]struct{})
	}
	return resp
}

// Close is used to close the query, which will close the underlying
// channels and prevent further deliveries
func (r *QueryResponse) Close() {
	r.closeLock.Lock()
	defer r.closeLock.Unlock()
	if r.closed {
		return
	}
	r.closed = true
	if r.ackCh != nil {
		close(r.ackCh)
	}
	if r.respCh != nil {
		close(r.respCh)
	}
}

// Deadline returns the ending deadline of the query
func (r *QueryResponse) Deadline() time.Time {
	return r.deadline
}

// Finished returns if the query is finished running
func (r *QueryResponse) Finished() bool {
	return r.closed || time.Now().After(r.deadline)
}

// AckCh returns a channel that can be used to listen for acks
// Channel will be closed when the query is finished. This is nil,
// if the query did not specify RequestAck.
func (r *QueryResponse) AckCh() <-chan string {
	return r.ackCh
}

// ResponseCh returns a channel that can be used to listen for responses.
// Channel will be closed when the query is finished.
func (r *QueryResponse) ResponseCh() <-chan NodeResponse {
	return r.respCh
}

// NodeResponse is used to represent a single response from a node
type NodeResponse struct {
	From    string
	Payload []byte
}

// shouldProcessQuery checks if a query should be proceeded given
// a set of filers.
func (s *Serf) shouldProcessQuery(filters [][]byte) bool {
	for _, filter := range filters {
		switch filterType(filter[0]) {
		case filterNodeType:
			// Decode the filter
			var nodes filterNode
			if err := decodeMessage(filter[1:], &nodes); err != nil {
				s.logger.Printf("[WARN] serf: failed to decode filterNodeType: %v", err)
				return false
			}

			// Check if we are being targeted
			found := false
			for _, n := range nodes {
				if n == s.config.NodeName {
					found = true
					break
				}
			}
			if !found {
				return false
			}

		case filterTagType:
			// Decode the filter
			var filt filterTag
			if err := decodeMessage(filter[1:], &filt); err != nil {
				s.logger.Printf("[WARN] serf: failed to decode filterTagType: %v", err)
				return false
			}

			// Check if we match this regex
			tags := s.config.Tags
			matched, err := regexp.MatchString(filt.Expr, tags[filt.Tag])
			if err != nil {
				s.logger.Printf("[WARN] serf: failed to compile filter regex (%s): %v", filt.Expr, err)
				return false
			}
			if !matched {
				return false
			}

		default:
			s.logger.Printf("[WARN] serf: query has unrecognized filter type: %d", filter[0])
			return false
		}
	}
	return true
}

// relayResponse will relay a copy of the given response to up to relayFactor
// other members.
func (s *Serf) relayResponse(relayFactor uint8, addr net.UDPAddr, resp *messageQueryResponse) error {
	if relayFactor == 0 {
		return nil
	}

	// Needs to be worth it; we need to have at least relayFactor *other*
	// nodes. If you have a tiny cluster then the relayFactor shouldn't
	// be needed.
	members := s.Members()
	if uint8(len(members)) < relayFactor+1 {
		return nil
	}

	// Prep the relay message, which is a wrapped version of the original.
	raw, err := encodeRelayMessage(messageQueryResponseType, addr, &resp)
	if err != nil {
		return fmt.Errorf("failed to format relayed response: %v", err)
	}
	if len(raw) > s.config.QueryResponseSizeLimit {
		return fmt.Errorf("relayed response exceeds limit of %d bytes", s.config.QueryResponseSizeLimit)
	}

	// Relay to a random set of peers.
	localName := s.LocalMember().Name
	relayMembers := kRandomMembers(int(relayFactor), members, func(m Member) bool {
		return m.Status != StatusAlive || m.ProtocolMax < 5 || m.Name == localName
	})
	for _, m := range relayMembers {
		relayAddr := net.UDPAddr{IP: m.Addr, Port: int(m.Port)}
		if err := s.memberlist.SendTo(&relayAddr, raw); err != nil {
			return fmt.Errorf("failed to send relay response: %v", err)
		}
	}
	return nil
}

// kRandomMembers selects up to k members from a given list, optionally
// filtering by the given filterFunc
func kRandomMembers(k int, members []Member, filterFunc func(Member) bool) []Member {
	n := len(members)
	kMembers := make([]Member, 0, k)
OUTER:
	// Probe up to 3*n times, with large n this is not necessary
	// since k << n, but with small n we want search to be
	// exhaustive
	for i := 0; i < 3*n && len(kMembers) < k; i++ {
		// Get random member
		idx := rand.Intn(n)
		member := members[idx]

		// Give the filter a shot at it.
		if filterFunc != nil && filterFunc(member) {
			continue OUTER
		}

		// Check if we have this member already
		for j := 0; j < len(kMembers); j++ {
			if member.Name == kMembers[j].Name {
				continue OUTER
			}
		}

		// Append the member
		kMembers = append(kMembers, member)
	}

	return kMembers
}
<commit_msg>Does a safer compare since the relay factor is unsigned.<commit_after>package serf

import (
	"fmt"
	"math"
	"math/rand"
	"net"
	"regexp"
	"sync"
	"time"
)

// QueryParam is provided to Query() to configure the parameters of the
// query. If not provided, sane defaults will be used.
type QueryParam struct {
	// If provided, we restrict the nodes that should respond to those
	// with names in this list
	FilterNodes []string

	// FilterTags maps a tag name to a regular expression that is applied
	// to restrict the nodes that should respond
	FilterTags map[string]string

	// If true, we are requesting an delivery acknowledgement from
	// every node that meets the filter requirement. This means nodes
	// the receive the message but do not pass the filters, will not
	// send an ack.
	RequestAck bool

	// RelayFactor controls the number of duplicate responses to relay
	// back to the sender through other nodes for redundancy.
	RelayFactor uint8

	// The timeout limits how long the query is left open. If not provided,
	// then a default timeout is used based on the configuration of Serf
	Timeout time.Duration
}

// DefaultQueryTimeout returns the default timeout value for a query
// Computed as GossipInterval * QueryTimeoutMult * log(N+1)
func (s *Serf) DefaultQueryTimeout() time.Duration {
	n := s.memberlist.NumMembers()
	timeout := s.config.MemberlistConfig.GossipInterval
	timeout *= time.Duration(s.config.QueryTimeoutMult)
	timeout *= time.Duration(math.Ceil(math.Log10(float64(n + 1))))
	return timeout
}

// DefaultQueryParam is used to return the default query parameters
func (s *Serf) DefaultQueryParams() *QueryParam {
	return &QueryParam{
		FilterNodes: nil,
		FilterTags:  nil,
		RequestAck:  false,
		Timeout:     s.DefaultQueryTimeout(),
	}
}

// encodeFilters is used to convert the filters into the wire format
func (q *QueryParam) encodeFilters() ([][]byte, error) {
	var filters [][]byte

	// Add the node filter
	if len(q.FilterNodes) > 0 {
		if buf, err := encodeFilter(filterNodeType, q.FilterNodes); err != nil {
			return nil, err
		} else {
			filters = append(filters, buf)
		}
	}

	// Add the tag filters
	for tag, expr := range q.FilterTags {
		filt := filterTag{tag, expr}
		if buf, err := encodeFilter(filterTagType, &filt); err != nil {
			return nil, err
		} else {
			filters = append(filters, buf)
		}
	}

	return filters, nil
}

// QueryResponse is returned for each new Query. It is used to collect
// Ack's as well as responses and to provide those back to a client.
type QueryResponse struct {
	// ackCh is used to send the name of a node for which we've received an ack
	ackCh chan string

	// deadline is the query end time (start + query timeout)
	deadline time.Time

	// Query ID
	id uint32

	// Stores the LTime of the query
	lTime LamportTime

	// respCh is used to send a response from a node
	respCh chan NodeResponse

	// acks/responses are used to track the nodes that have sent an ack/response
	acks      map[string]struct{}
	responses map[string]struct{}

	closed    bool
	closeLock sync.Mutex
}

// newQueryResponse is used to construct a new query response
func newQueryResponse(n int, q *messageQuery) *QueryResponse {
	resp := &QueryResponse{
		deadline:  time.Now().Add(q.Timeout),
		id:        q.ID,
		lTime:     q.LTime,
		respCh:    make(chan NodeResponse, n),
		responses: make(map[string]struct{}),
	}
	if q.Ack() {
		resp.ackCh = make(chan string, n)
		resp.acks = make(map[string]struct{})
	}
	return resp
}

// Close is used to close the query, which will close the underlying
// channels and prevent further deliveries
func (r *QueryResponse) Close() {
	r.closeLock.Lock()
	defer r.closeLock.Unlock()
	if r.closed {
		return
	}
	r.closed = true
	if r.ackCh != nil {
		close(r.ackCh)
	}
	if r.respCh != nil {
		close(r.respCh)
	}
}

// Deadline returns the ending deadline of the query
func (r *QueryResponse) Deadline() time.Time {
	return r.deadline
}

// Finished returns if the query is finished running
func (r *QueryResponse) Finished() bool {
	return r.closed || time.Now().After(r.deadline)
}

// AckCh returns a channel that can be used to listen for acks
// Channel will be closed when the query is finished. This is nil,
// if the query did not specify RequestAck.
func (r *QueryResponse) AckCh() <-chan string {
	return r.ackCh
}

// ResponseCh returns a channel that can be used to listen for responses.
// Channel will be closed when the query is finished.
func (r *QueryResponse) ResponseCh() <-chan NodeResponse {
	return r.respCh
}

// NodeResponse is used to represent a single response from a node
type NodeResponse struct {
	From    string
	Payload []byte
}

// shouldProcessQuery checks if a query should be proceeded given
// a set of filers.
func (s *Serf) shouldProcessQuery(filters [][]byte) bool {
	for _, filter := range filters {
		switch filterType(filter[0]) {
		case filterNodeType:
			// Decode the filter
			var nodes filterNode
			if err := decodeMessage(filter[1:], &nodes); err != nil {
				s.logger.Printf("[WARN] serf: failed to decode filterNodeType: %v", err)
				return false
			}

			// Check if we are being targeted
			found := false
			for _, n := range nodes {
				if n == s.config.NodeName {
					found = true
					break
				}
			}
			if !found {
				return false
			}

		case filterTagType:
			// Decode the filter
			var filt filterTag
			if err := decodeMessage(filter[1:], &filt); err != nil {
				s.logger.Printf("[WARN] serf: failed to decode filterTagType: %v", err)
				return false
			}

			// Check if we match this regex
			tags := s.config.Tags
			matched, err := regexp.MatchString(filt.Expr, tags[filt.Tag])
			if err != nil {
				s.logger.Printf("[WARN] serf: failed to compile filter regex (%s): %v", filt.Expr, err)
				return false
			}
			if !matched {
				return false
			}

		default:
			s.logger.Printf("[WARN] serf: query has unrecognized filter type: %d", filter[0])
			return false
		}
	}
	return true
}

// relayResponse will relay a copy of the given response to up to relayFactor
// other members.
func (s *Serf) relayResponse(relayFactor uint8, addr net.UDPAddr, resp *messageQueryResponse) error {
	if relayFactor == 0 {
		return nil
	}

	// Needs to be worth it; we need to have at least relayFactor *other*
	// nodes. If you have a tiny cluster then the relayFactor shouldn't
	// be needed.
	members := s.Members()
	if len(members) < int(relayFactor)+1 {
		return nil
	}

	// Prep the relay message, which is a wrapped version of the original.
	raw, err := encodeRelayMessage(messageQueryResponseType, addr, &resp)
	if err != nil {
		return fmt.Errorf("failed to format relayed response: %v", err)
	}
	if len(raw) > s.config.QueryResponseSizeLimit {
		return fmt.Errorf("relayed response exceeds limit of %d bytes", s.config.QueryResponseSizeLimit)
	}

	// Relay to a random set of peers.
	localName := s.LocalMember().Name
	relayMembers := kRandomMembers(int(relayFactor), members, func(m Member) bool {
		return m.Status != StatusAlive || m.ProtocolMax < 5 || m.Name == localName
	})
	for _, m := range relayMembers {
		relayAddr := net.UDPAddr{IP: m.Addr, Port: int(m.Port)}
		if err := s.memberlist.SendTo(&relayAddr, raw); err != nil {
			return fmt.Errorf("failed to send relay response: %v", err)
		}
	}
	return nil
}

// kRandomMembers selects up to k members from a given list, optionally
// filtering by the given filterFunc
func kRandomMembers(k int, members []Member, filterFunc func(Member) bool) []Member {
	n := len(members)
	kMembers := make([]Member, 0, k)
OUTER:
	// Probe up to 3*n times, with large n this is not necessary
	// since k << n, but with small n we want search to be
	// exhaustive
	for i := 0; i < 3*n && len(kMembers) < k; i++ {
		// Get random member
		idx := rand.Intn(n)
		member := members[idx]

		// Give the filter a shot at it.
		if filterFunc != nil && filterFunc(member) {
			continue OUTER
		}

		// Check if we have this member already
		for j := 0; j < len(kMembers); j++ {
			if member.Name == kMembers[j].Name {
				continue OUTER
			}
		}

		// Append the member
		kMembers = append(kMembers, member)
	}

	return kMembers
}
<|endoftext|>


<commit_before>package flyrpc

import (
	"encoding/json"
	"reflect"
)

type Message interface{}

var (
	typeBytes  = reflect.TypeOf([]byte{})
	typeString = reflect.TypeOf("")
)

func MessageToBytes(message Message, serializer Serializer) ([]byte, error) {
	messageType := reflect.TypeOf(message)
	if messageType == typeBytes {
		return message.([]byte), nil
	}
	if messageType == typeString {
		return []byte(message.(string)), nil
	}
	return serializer.Marshal(message)
}

type Serializer interface {
	Marshal(interface{}) ([]byte, error)
	Unmarshal([]byte, interface{}) error
}

type serializer struct {
	marshal   func(interface{}) ([]byte, error)
	unmarshal func([]byte, interface{}) error
}

func NewSerializer(marshal func(interface{}) ([]byte, error), unmarshal func([]byte, interface{}) error) Serializer {
	return &serializer{
		marshal:   marshal,
		unmarshal: unmarshal,
	}
}

func (s *serializer) Marshal(msg interface{}) ([]byte, error) {
	return s.marshal(msg)
}

func (s *serializer) Unmarshal(bytes []byte, msg interface{}) error {
	return s.unmarshal(bytes, msg)
}

var (
	JSON Serializer = &serializer{marshal: json.Marshal, unmarshal: json.Unmarshal}
	/*
		Protobuf Serializer = &_proto{}
		Msgpack  Serializer = &_msgpack{}
	*/
)

/*
type _proto struct {
}

func (p *_proto) Marshal(v interface{}) ([]byte, error) {
	m, ok := v.(proto.Message)
	if !ok {
		return nil, newError(ErrNotProtoMessage)
	}
	return proto.Marshal(m)
}

func (p *_proto) Unmarshal(bytes []byte, v interface{}) error {
	m, ok := v.(proto.Message)
	if !ok {
		return newError(ErrNotProtoMessage)
	}
	return proto.Unmarshal(bytes, m)
}

type _msgpack struct {
}

func (m *_msgpack) Marshal(v interface{}) ([]byte, error) {
	return msgpack.Marshal(v)
}

func (m *_msgpack) Unmarshal(bytes []byte, v interface{}) error {
	return msgpack.Unmarshal(bytes, v)
}
*/
<commit_msg>clean serializer<commit_after>package flyrpc

import (
	"encoding/json"
	"reflect"
)

type Message interface{}

var (
	typeBytes  = reflect.TypeOf([]byte{})
	typeString = reflect.TypeOf("")
)

func MessageToBytes(message Message, serializer Serializer) ([]byte, error) {
	messageType := reflect.TypeOf(message)
	if messageType == typeBytes {
		return message.([]byte), nil
	}
	if messageType == typeString {
		return []byte(message.(string)), nil
	}
	return serializer.Marshal(message)
}

type Serializer interface {
	Marshal(interface{}) ([]byte, error)
	Unmarshal([]byte, interface{}) error
}

type serializer struct {
	marshal   func(interface{}) ([]byte, error)
	unmarshal func([]byte, interface{}) error
}

func NewSerializer(marshal func(interface{}) ([]byte, error), unmarshal func([]byte, interface{}) error) Serializer {
	return &serializer{
		marshal:   marshal,
		unmarshal: unmarshal,
	}
}

func (s *serializer) Marshal(msg interface{}) ([]byte, error) {
	return s.marshal(msg)
}

func (s *serializer) Unmarshal(bytes []byte, msg interface{}) error {
	return s.unmarshal(bytes, msg)
}

var (
	JSON Serializer = NewSerializer(json.Marshal, json.Unmarshal)
)
<|endoftext|>