<commit_before>// #ignore

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Run runs tests in the test directory.
//
// TODO(bradfitz): docs of some sort, once we figure out how we're changing
// headers of files
package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"
)

var (
	verbose     = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.")
	numParallel = flag.Int("n", 2*runtime.NumCPU(), "number of parallel tests to run")
	summary     = flag.Bool("summary", false, "show summary of results")
	showSkips   = flag.Bool("show_skips", false, "show skipped tests")
)

var (
	// gc and ld are [568][gl].
	gc, ld string

	// letter is the build.ArchChar
	letter string

	// dirs are the directories to look for *.go files in.
	// TODO(bradfitz): just use all directories?
	dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "bugs"}

	// ratec controls the max number of tests running at a time.
	ratec chan bool

	// toRun is the channel of tests to run.
	// It is nil until the first test is started.
	toRun chan *test
)

// maxTests is an upper bound on the total number of tests.
// It is used as a channel buffer size to make sure sends don't block.
const maxTests = 5000

func main() {
	flag.Parse()

	// Disable parallelism if printing, or if running on
	// (presumably underpowered) arm systems.
	if *verbose || runtime.GOARCH == "arm" {
		*numParallel = 1
	}

	ratec = make(chan bool, *numParallel)
	var err error
	letter, err = build.ArchChar(build.Default.GOARCH)
	check(err)
	gc = letter + "g"
	ld = letter + "l"

	var tests []*test
	if flag.NArg() > 0 {
		for _, arg := range flag.Args() {
			if arg == "-" || arg == "--" {
				// Permit running either:
				// $ go run run.go - env.go
				// $ go run run.go -- env.go
				continue
			}
			if !strings.HasSuffix(arg, ".go") {
				log.Fatalf("can't yet deal with non-go file %q", arg)
			}
			dir, file := filepath.Split(arg)
			tests = append(tests, startTest(dir, file))
		}
	} else {
		for _, dir := range dirs {
			for _, baseGoFile := range goFiles(dir) {
				tests = append(tests, startTest(dir, baseGoFile))
			}
		}
	}

	failed := false
	resCount := map[string]int{}
	for _, test := range tests {
		<-test.donec
		_, isSkip := test.err.(skipError)
		errStr := "pass"
		if isSkip {
			errStr = "skip"
		}
		if test.err != nil {
			errStr = test.err.Error()
			if !isSkip {
				failed = true
			}
		}
		resCount[errStr]++
		if isSkip && !*verbose && !*showSkips {
			continue
		}
		if !*verbose && test.err == nil {
			continue
		}
		fmt.Printf("%-10s %-20s: %s\n", test.action, test.goFileName(), errStr)
	}

	if *summary {
		for k, v := range resCount {
			fmt.Printf("%5d %s\n", v, k)
		}
	}

	if failed {
		os.Exit(1)
	}
}

func toolPath(name string) string {
	p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name)
	if _, err := os.Stat(p); err != nil {
		log.Fatalf("didn't find binary at %s", p)
	}
	return p
}

func goFiles(dir string) []string {
	f, err := os.Open(dir)
	check(err)
	dirnames, err := f.Readdirnames(-1)
	check(err)
	names := []string{}
	for _, name := range dirnames {
		if strings.HasSuffix(name, ".go") {
			names = append(names, name)
		}
	}
	sort.Strings(names)
	return names
}

// skipError describes why a test was skipped.
type skipError string

func (s skipError) Error() string { return string(s) }

func check(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

// test holds the state of a test.
type test struct {
	dir, gofile string
	donec       chan bool // closed when done

	src    string
	action string // "compile", "build", "run", "errorcheck"

	tempDir string
	err     error
}

// startTest
func startTest(dir, gofile string) *test {
	t := &test{
		dir:    dir,
		gofile: gofile,
		donec:  make(chan bool, 1),
	}
	if toRun == nil {
		toRun = make(chan *test, maxTests)
		go runTests()
	}
	select {
	case toRun <- t:
	default:
		panic("toRun buffer size (maxTests) is too small")
	}
	return t
}

// runTests runs tests in parallel, but respecting the order they
// were enqueued on the toRun channel.
func runTests() {
	for {
		ratec <- true
		t := <-toRun
		go func() {
			t.run()
			<-ratec
		}()
	}
}

func (t *test) goFileName() string {
	return filepath.Join(t.dir, t.gofile)
}

// run runs a test.
func (t *test) run() {
	defer close(t.donec)

	srcBytes, err := ioutil.ReadFile(t.goFileName())
	if err != nil {
		t.err = err
		return
	}
	t.src = string(srcBytes)
	if t.src[0] == '\n' {
		t.err = skipError("starts with newline")
		return
	}
	pos := strings.Index(t.src, "\n\n")
	if pos == -1 {
		t.err = errors.New("double newline not found")
		return
	}
	action := t.src[:pos]
	if strings.HasPrefix(action, "//") {
		action = action[2:]
	}
	action = strings.TrimSpace(action)

	switch action {
	case "cmpout":
		action = "run" // the run case already looks for <dir>/<test>.out files
		fallthrough
	case "compile", "build", "run", "errorcheck":
		t.action = action
	default:
		t.err = skipError("skipped; unknown pattern: " + action)
		t.action = "??"
		return
	}

	t.makeTempDir()
	defer os.RemoveAll(t.tempDir)

	err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644)
	check(err)

	cmd := exec.Command("go", "tool", gc, "-e", "-o", "a."+letter, t.gofile)
	var buf bytes.Buffer
	cmd.Stdout = &buf
	cmd.Stderr = &buf
	cmd.Dir = t.tempDir
	err = cmd.Run()
	out := buf.String()

	if action == "errorcheck" {
		t.err = t.errorCheck(out)
		return
	}

	if err != nil {
		t.err = fmt.Errorf("build = %v (%q)", err, out)
		return
	}

	if action == "compile" {
		return
	}

	if action == "build" || action == "run" {
		buf.Reset()
		cmd = exec.Command("go", "tool", ld, "-o", "a.out", "a."+letter)
		cmd.Stdout = &buf
		cmd.Stderr = &buf
		cmd.Dir = t.tempDir
		err = cmd.Run()
		out = buf.String()
		if err != nil {
			t.err = fmt.Errorf("link = %v (%q)", err, out)
			return
		}
		if action == "build" {
			return
		}
	}

	if action == "run" {
		buf.Reset()
		cmd = exec.Command(filepath.Join(t.tempDir, "a.out"))
		cmd.Stdout = &buf
		cmd.Stderr = &buf
		cmd.Dir = t.tempDir
		cmd.Env = append(cmd.Env, "GOARCH="+runtime.GOARCH)
		err = cmd.Run()
		out = buf.String()
		if err != nil {
			t.err = fmt.Errorf("run = %v (%q)", err, out)
			return
		}

		if out != t.expectedOutput() {
			t.err = fmt.Errorf("output differs; got:\n%s", out)
		}
		return
	}

	t.err = fmt.Errorf("unimplemented action %q", action)
}

func (t *test) String() string {
	return filepath.Join(t.dir, t.gofile)
}

func (t *test) makeTempDir() {
	var err error
	t.tempDir, err = ioutil.TempDir("", "")
	check(err)
}

func (t *test) expectedOutput() string {
	filename := filepath.Join(t.dir, t.gofile)
	filename = filename[:len(filename)-len(".go")]
	filename += ".out"
	b, _ := ioutil.ReadFile(filename)
	return string(b)
}

func (t *test) errorCheck(outStr string) (err error) {
	defer func() {
		if *verbose && err != nil {
			log.Printf("%s gc output:\n%s", t, outStr)
		}
	}()
	var errs []error

	var out []string
	// 6g error messages continue onto additional lines with leading tabs.
	// Split the output at the beginning of each line that doesn't begin with a tab.
	for _, line := range strings.Split(outStr, "\n") {
		if strings.HasPrefix(line, "\t") {
			out[len(out)-1] += "\n" + line
		} else {
			out = append(out, line)
		}
	}

	for _, we := range t.wantedErrors() {
		var errmsgs []string
		errmsgs, out = partitionStrings(we.filterRe, out)
		if len(errmsgs) == 0 {
			errs = append(errs, fmt.Errorf("errchk: %s:%d: missing expected error: %s", we.file, we.lineNum, we.reStr))
			continue
		}
		matched := false
		for _, errmsg := range errmsgs {
			if we.re.MatchString(errmsg) {
				matched = true
			} else {
				out = append(out, errmsg)
			}
		}
		if !matched {
			errs = append(errs, fmt.Errorf("errchk: %s:%d: error(s) on line didn't match pattern: %s", we.file, we.lineNum, we.reStr))
			continue
		}
	}

	if len(errs) == 0 {
		return nil
	}
	if len(errs) == 1 {
		return errs[0]
	}
	var buf bytes.Buffer
	buf.WriteString("Multiple errors:\n")
	for _, err := range errs {
		fmt.Fprintf(&buf, "%s\n", err.Error())
	}
	return errors.New(buf.String())

}

func partitionStrings(rx *regexp.Regexp, strs []string) (matched, unmatched []string) {
	for _, s := range strs {
		if rx.MatchString(s) {
			matched = append(matched, s)
		} else {
			unmatched = append(unmatched, s)
		}
	}
	return
}

type wantedError struct {
	reStr    string
	re       *regexp.Regexp
	lineNum  int
	file     string
	filterRe *regexp.Regexp // /^file:linenum\b/m
}

var (
	errRx       = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
	errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
	lineRx      = regexp.MustCompile(`LINE(([+-])([0-9]+))?`)
)

func (t *test) wantedErrors() (errs []wantedError) {
	for i, line := range strings.Split(t.src, "\n") {
		lineNum := i + 1
		if strings.Contains(line, "////") {
			// double comment disables ERROR
			continue
		}
		m := errRx.FindStringSubmatch(line)
		if m == nil {
			continue
		}
		all := m[1]
		mm := errQuotesRx.FindAllStringSubmatch(all, -1)
		if mm == nil {
			log.Fatalf("invalid errchk line in %s: %s", t.goFileName(), line)
		}
		for _, m := range mm {
			rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
				n := lineNum
				if strings.HasPrefix(m, "LINE+") {
					delta, _ := strconv.Atoi(m[5:])
					n += delta
				} else if strings.HasPrefix(m, "LINE-") {
					delta, _ := strconv.Atoi(m[5:])
					n -= delta
				}
				return fmt.Sprintf("%s:%d", t.gofile, n)
			})
			filterPattern := fmt.Sprintf(`^(\w+/)?%s:%d[:[]`, t.gofile, lineNum)
			errs = append(errs, wantedError{
				reStr:    rx,
				re:       regexp.MustCompile(rx),
				filterRe: regexp.MustCompile(filterPattern),
				lineNum:  lineNum,
				file:     t.gofile,
			})
		}
	}

	return
}
<commit_msg>test/run: use all available cores on ARM system<commit_after>// #ignore

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Run runs tests in the test directory.
//
// TODO(bradfitz): docs of some sort, once we figure out how we're changing
// headers of files
package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"
)

var (
	verbose     = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.")
	numParallel = flag.Int("n", runtime.NumCPU(), "number of parallel tests to run")
	summary     = flag.Bool("summary", false, "show summary of results")
	showSkips   = flag.Bool("show_skips", false, "show skipped tests")
)

var (
	// gc and ld are [568][gl].
	gc, ld string

	// letter is the build.ArchChar
	letter string

	// dirs are the directories to look for *.go files in.
	// TODO(bradfitz): just use all directories?
	dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "bugs"}

	// ratec controls the max number of tests running at a time.
	ratec chan bool

	// toRun is the channel of tests to run.
	// It is nil until the first test is started.
	toRun chan *test
)

// maxTests is an upper bound on the total number of tests.
// It is used as a channel buffer size to make sure sends don't block.
const maxTests = 5000

func main() {
	flag.Parse()

	// Disable parallelism if printing
	if *verbose {
		*numParallel = 1
	}

	ratec = make(chan bool, *numParallel)
	var err error
	letter, err = build.ArchChar(build.Default.GOARCH)
	check(err)
	gc = letter + "g"
	ld = letter + "l"

	var tests []*test
	if flag.NArg() > 0 {
		for _, arg := range flag.Args() {
			if arg == "-" || arg == "--" {
				// Permit running either:
				// $ go run run.go - env.go
				// $ go run run.go -- env.go
				continue
			}
			if !strings.HasSuffix(arg, ".go") {
				log.Fatalf("can't yet deal with non-go file %q", arg)
			}
			dir, file := filepath.Split(arg)
			tests = append(tests, startTest(dir, file))
		}
	} else {
		for _, dir := range dirs {
			for _, baseGoFile := range goFiles(dir) {
				tests = append(tests, startTest(dir, baseGoFile))
			}
		}
	}

	failed := false
	resCount := map[string]int{}
	for _, test := range tests {
		<-test.donec
		_, isSkip := test.err.(skipError)
		errStr := "pass"
		if isSkip {
			errStr = "skip"
		}
		if test.err != nil {
			errStr = test.err.Error()
			if !isSkip {
				failed = true
			}
		}
		resCount[errStr]++
		if isSkip && !*verbose && !*showSkips {
			continue
		}
		if !*verbose && test.err == nil {
			continue
		}
		fmt.Printf("%-10s %-20s: %s\n", test.action, test.goFileName(), errStr)
	}

	if *summary {
		for k, v := range resCount {
			fmt.Printf("%5d %s\n", v, k)
		}
	}

	if failed {
		os.Exit(1)
	}
}

func toolPath(name string) string {
	p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name)
	if _, err := os.Stat(p); err != nil {
		log.Fatalf("didn't find binary at %s", p)
	}
	return p
}

func goFiles(dir string) []string {
	f, err := os.Open(dir)
	check(err)
	dirnames, err := f.Readdirnames(-1)
	check(err)
	names := []string{}
	for _, name := range dirnames {
		if strings.HasSuffix(name, ".go") {
			names = append(names, name)
		}
	}
	sort.Strings(names)
	return names
}

// skipError describes why a test was skipped.
type skipError string

func (s skipError) Error() string { return string(s) }

func check(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

// test holds the state of a test.
type test struct {
	dir, gofile string
	donec       chan bool // closed when done

	src    string
	action string // "compile", "build", "run", "errorcheck"

	tempDir string
	err     error
}

// startTest
func startTest(dir, gofile string) *test {
	t := &test{
		dir:    dir,
		gofile: gofile,
		donec:  make(chan bool, 1),
	}
	if toRun == nil {
		toRun = make(chan *test, maxTests)
		go runTests()
	}
	select {
	case toRun <- t:
	default:
		panic("toRun buffer size (maxTests) is too small")
	}
	return t
}

// runTests runs tests in parallel, but respecting the order they
// were enqueued on the toRun channel.
func runTests() {
	for {
		ratec <- true
		t := <-toRun
		go func() {
			t.run()
			<-ratec
		}()
	}
}

func (t *test) goFileName() string {
	return filepath.Join(t.dir, t.gofile)
}

// run runs a test.
func (t *test) run() {
	defer close(t.donec)

	srcBytes, err := ioutil.ReadFile(t.goFileName())
	if err != nil {
		t.err = err
		return
	}
	t.src = string(srcBytes)
	if t.src[0] == '\n' {
		t.err = skipError("starts with newline")
		return
	}
	pos := strings.Index(t.src, "\n\n")
	if pos == -1 {
		t.err = errors.New("double newline not found")
		return
	}
	action := t.src[:pos]
	if strings.HasPrefix(action, "//") {
		action = action[2:]
	}
	action = strings.TrimSpace(action)

	switch action {
	case "cmpout":
		action = "run" // the run case already looks for <dir>/<test>.out files
		fallthrough
	case "compile", "build", "run", "errorcheck":
		t.action = action
	default:
		t.err = skipError("skipped; unknown pattern: " + action)
		t.action = "??"
		return
	}

	t.makeTempDir()
	defer os.RemoveAll(t.tempDir)

	err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644)
	check(err)

	cmd := exec.Command("go", "tool", gc, "-e", "-o", "a."+letter, t.gofile)
	var buf bytes.Buffer
	cmd.Stdout = &buf
	cmd.Stderr = &buf
	cmd.Dir = t.tempDir
	err = cmd.Run()
	out := buf.String()

	if action == "errorcheck" {
		t.err = t.errorCheck(out)
		return
	}

	if err != nil {
		t.err = fmt.Errorf("build = %v (%q)", err, out)
		return
	}

	if action == "compile" {
		return
	}

	if action == "build" || action == "run" {
		buf.Reset()
		cmd = exec.Command("go", "tool", ld, "-o", "a.out", "a."+letter)
		cmd.Stdout = &buf
		cmd.Stderr = &buf
		cmd.Dir = t.tempDir
		err = cmd.Run()
		out = buf.String()
		if err != nil {
			t.err = fmt.Errorf("link = %v (%q)", err, out)
			return
		}
		if action == "build" {
			return
		}
	}

	if action == "run" {
		buf.Reset()
		cmd = exec.Command(filepath.Join(t.tempDir, "a.out"))
		cmd.Stdout = &buf
		cmd.Stderr = &buf
		cmd.Dir = t.tempDir
		cmd.Env = append(cmd.Env, "GOARCH="+runtime.GOARCH)
		err = cmd.Run()
		out = buf.String()
		if err != nil {
			t.err = fmt.Errorf("run = %v (%q)", err, out)
			return
		}

		if out != t.expectedOutput() {
			t.err = fmt.Errorf("output differs; got:\n%s", out)
		}
		return
	}

	t.err = fmt.Errorf("unimplemented action %q", action)
}

func (t *test) String() string {
	return filepath.Join(t.dir, t.gofile)
}

func (t *test) makeTempDir() {
	var err error
	t.tempDir, err = ioutil.TempDir("", "")
	check(err)
}

func (t *test) expectedOutput() string {
	filename := filepath.Join(t.dir, t.gofile)
	filename = filename[:len(filename)-len(".go")]
	filename += ".out"
	b, _ := ioutil.ReadFile(filename)
	return string(b)
}

func (t *test) errorCheck(outStr string) (err error) {
	defer func() {
		if *verbose && err != nil {
			log.Printf("%s gc output:\n%s", t, outStr)
		}
	}()
	var errs []error

	var out []string
	// 6g error messages continue onto additional lines with leading tabs.
	// Split the output at the beginning of each line that doesn't begin with a tab.
	for _, line := range strings.Split(outStr, "\n") {
		if strings.HasPrefix(line, "\t") {
			out[len(out)-1] += "\n" + line
		} else {
			out = append(out, line)
		}
	}

	for _, we := range t.wantedErrors() {
		var errmsgs []string
		errmsgs, out = partitionStrings(we.filterRe, out)
		if len(errmsgs) == 0 {
			errs = append(errs, fmt.Errorf("errchk: %s:%d: missing expected error: %s", we.file, we.lineNum, we.reStr))
			continue
		}
		matched := false
		for _, errmsg := range errmsgs {
			if we.re.MatchString(errmsg) {
				matched = true
			} else {
				out = append(out, errmsg)
			}
		}
		if !matched {
			errs = append(errs, fmt.Errorf("errchk: %s:%d: error(s) on line didn't match pattern: %s", we.file, we.lineNum, we.reStr))
			continue
		}
	}

	if len(errs) == 0 {
		return nil
	}
	if len(errs) == 1 {
		return errs[0]
	}
	var buf bytes.Buffer
	buf.WriteString("Multiple errors:\n")
	for _, err := range errs {
		fmt.Fprintf(&buf, "%s\n", err.Error())
	}
	return errors.New(buf.String())

}

func partitionStrings(rx *regexp.Regexp, strs []string) (matched, unmatched []string) {
	for _, s := range strs {
		if rx.MatchString(s) {
			matched = append(matched, s)
		} else {
			unmatched = append(unmatched, s)
		}
	}
	return
}

type wantedError struct {
	reStr    string
	re       *regexp.Regexp
	lineNum  int
	file     string
	filterRe *regexp.Regexp // /^file:linenum\b/m
}

var (
	errRx       = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
	errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
	lineRx      = regexp.MustCompile(`LINE(([+-])([0-9]+))?`)
)

func (t *test) wantedErrors() (errs []wantedError) {
	for i, line := range strings.Split(t.src, "\n") {
		lineNum := i + 1
		if strings.Contains(line, "////") {
			// double comment disables ERROR
			continue
		}
		m := errRx.FindStringSubmatch(line)
		if m == nil {
			continue
		}
		all := m[1]
		mm := errQuotesRx.FindAllStringSubmatch(all, -1)
		if mm == nil {
			log.Fatalf("invalid errchk line in %s: %s", t.goFileName(), line)
		}
		for _, m := range mm {
			rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
				n := lineNum
				if strings.HasPrefix(m, "LINE+") {
					delta, _ := strconv.Atoi(m[5:])
					n += delta
				} else if strings.HasPrefix(m, "LINE-") {
					delta, _ := strconv.Atoi(m[5:])
					n -= delta
				}
				return fmt.Sprintf("%s:%d", t.gofile, n)
			})
			filterPattern := fmt.Sprintf(`^(\w+/)?%s:%d[:[]`, t.gofile, lineNum)
			errs = append(errs, wantedError{
				reStr:    rx,
				re:       regexp.MustCompile(rx),
				filterRe: regexp.MustCompile(filterPattern),
				lineNum:  lineNum,
				file:     t.gofile,
			})
		}
	}

	return
}
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// 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 controllers

import (
	"bytes"
	"fmt"
	"time"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/remotecommand"
	"k8s.io/client-go/util/retry"
	log "k8s.io/klog/v2"
)

// ExecCmdParams stores parameters for invoking pod/exec.
type ExecCmdParams struct {
	Pod        string
	Ns         string
	Con        *corev1.Container
	Sch        *runtime.Scheme
	RestConfig *rest.Config
	Client     kubernetes.Interface
}

// ExecCmdFunc  invokes pod/exec.
var ExecCmdFunc = func(p ExecCmdParams, cmd string) (string, error) {
	var cmdOut, cmdErr bytes.Buffer

	cmdShell := []string{"sh", "-c", cmd}

	req := p.Client.CoreV1().RESTClient().Post().Resource("pods").Name(p.Pod).
		Namespace(p.Ns).SubResource("exec")

	req.VersionedParams(&corev1.PodExecOptions{
		Container: p.Con.Name,
		Command:   cmdShell,
		Stdout:    true,
		Stderr:    true,
	}, scheme.ParameterCodec)

	exec, err := remotecommand.NewSPDYExecutor(p.RestConfig, "POST", req.URL())
	if err != nil {
		return "", fmt.Errorf("failed to init executor: %v", err)
	}

	// exec.Stream might return timout error, use a backoff with 4 retries
	// 100ms, 500ms, 2.5s, 12.5s
	var backoff = wait.Backoff{
		Steps:    4,
		Duration: 100 * time.Millisecond,
		Factor:   5.0,
		Jitter:   0.1,
	}
	if err := retry.OnError(backoff, func(error) bool { return true }, func() error {
		e := exec.Stream(remotecommand.StreamOptions{
			Stdout: &cmdOut,
			Stderr: &cmdErr,
			Tty:    false,
		})
		if e != nil {
			log.Error(fmt.Sprintf("exec.Stream failed, retrying, err: %v, stderr: %v, stdout: %v",
				err, cmdErr.String(), cmdOut.String()))
		}
		return e
	}); err != nil {
		return "", fmt.Errorf("failed to run a command [%v], err: %v, stderr: %v, stdout: %v",
			cmd, err, cmdErr.String(), cmdOut.String())
	}

	if cmdErr.Len() > 0 {
		return "", fmt.Errorf("stderr: %v", cmdErr.String())
	}

	return cmdOut.String(), nil
}
<commit_msg>[PE] Minor fix in controllers.exec() error reporting<commit_after>// Copyright 2021 Google LLC
//
// 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 controllers

import (
	"bytes"
	"fmt"
	"time"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/remotecommand"
	"k8s.io/client-go/util/retry"
	log "k8s.io/klog/v2"
)

// ExecCmdParams stores parameters for invoking pod/exec.
type ExecCmdParams struct {
	Pod        string
	Ns         string
	Con        *corev1.Container
	Sch        *runtime.Scheme
	RestConfig *rest.Config
	Client     kubernetes.Interface
}

// ExecCmdFunc  invokes pod/exec.
var ExecCmdFunc = func(p ExecCmdParams, cmd string) (string, error) {
	var cmdOut, cmdErr bytes.Buffer

	cmdShell := []string{"sh", "-c", cmd}

	req := p.Client.CoreV1().RESTClient().Post().Resource("pods").Name(p.Pod).
		Namespace(p.Ns).SubResource("exec")

	req.VersionedParams(&corev1.PodExecOptions{
		Container: p.Con.Name,
		Command:   cmdShell,
		Stdout:    true,
		Stderr:    true,
	}, scheme.ParameterCodec)

	exec, err := remotecommand.NewSPDYExecutor(p.RestConfig, "POST", req.URL())
	if err != nil {
		return "", fmt.Errorf("failed to init executor: %v", err)
	}

	// exec.Stream might return timout error, use a backoff with 4 retries
	// 100ms, 500ms, 2.5s, 12.5s
	var backoff = wait.Backoff{
		Steps:    4,
		Duration: 100 * time.Millisecond,
		Factor:   5.0,
		Jitter:   0.1,
	}
	if err := retry.OnError(backoff, func(error) bool { return true }, func() error {
		e := exec.Stream(remotecommand.StreamOptions{
			Stdout: &cmdOut,
			Stderr: &cmdErr,
			Tty:    false,
		})
		if e != nil {
			log.Error(fmt.Sprintf("exec.Stream failed, retrying, err: %v, stderr: %v, stdout: %v",
				e, cmdErr.String(), cmdOut.String()))
		}
		return e
	}); err != nil {
		return "", fmt.Errorf("failed to run a command [%v], err: %v, stderr: %v, stdout: %v",
			cmd, err, cmdErr.String(), cmdOut.String())
	}

	if cmdErr.Len() > 0 {
		return "", fmt.Errorf("stderr: %v", cmdErr.String())
	}

	return cmdOut.String(), nil
}
<|endoftext|>


<commit_before>package b2

import (
	"errors"
	"net/url"

	"github.com/graymeta/stow"
	"gopkg.in/kothar/go-backblaze.v0"
)

// Config key constants.
const (
	ConfigAccountID      = "account_id"
	ConfigApplicationKey = "application_key"
	HashType             = "sha1"
)

// Kind is the kind of Location this package provides.
const Kind = "b2"

func init() {
	validatefn := func(config stow.Config) error {
		_, ok := config.Config(ConfigAccountID)
		if !ok {
			return errors.New("missing account id")
		}
		_, ok = config.Config(ConfigApplicationKey)
		if !ok {
			return errors.New("missing application key")
		}
		return nil
	}
	makefn := func(config stow.Config) (stow.Location, error) {
		_, ok := config.Config(ConfigAccountID)
		if !ok {
			return nil, errors.New("missing account ID")
		}
		_, ok = config.Config(ConfigApplicationKey)
		if !ok {
			return nil, errors.New("missing application key")
		}
		l := &location{
			config: config,
		}
		var err error
		l.client, err = newB2Client(l.config)
		if err != nil {
			return nil, err
		}
		return l, nil
	}
	kindfn := func(u *url.URL) bool {
		return u.Scheme == Kind
	}
	stow.Register(Kind, makefn, kindfn, validatefn)
}

func newB2Client(cfg stow.Config) (*backblaze.B2, error) {
	accountID, _ := cfg.Config(ConfigAccountID)
	applicationKey, _ := cfg.Config(ConfigApplicationKey)

	client, err := backblaze.NewB2(backblaze.Credentials{
		AccountID:      accountID,
		ApplicationKey: applicationKey,
	})

	if err != nil {
		return nil, errors.New("Unable to create client")
	}

	err = client.AuthorizeAccount()
	if err != nil {
		return nil, errors.New("Unable to authenticate")
	}

	return client, nil
}
<commit_msg>b2: support use of non-master application keys<commit_after>package b2

import (
	"errors"
	"net/url"

	"github.com/graymeta/stow"
	"gopkg.in/kothar/go-backblaze.v0"
)

// Config key constants.
const (
	ConfigAccountID      = "account_id"
	ConfigApplicationKey = "application_key"
	ConfigKeyID          = "application_key_id"
)

// Kind is the kind of Location this package provides.
const Kind = "b2"

func init() {
	validatefn := func(config stow.Config) error {
		_, ok := config.Config(ConfigApplicationKey)
		if !ok {
			return errors.New("missing application key")
		}
		accountID, _ := config.Config(ConfigAccountID)
		keyID, _ := config.Config(ConfigKeyID)
		if accountID == "" && keyID == "" {
			return errors.New("account ID or applicaton key ID needs to be set")
		}
		return nil
	}
	makefn := func(config stow.Config) (stow.Location, error) {
		if err := validatefn(config); err != nil {
			return nil, err
		}
		l := &location{
			config: config,
		}
		var err error
		l.client, err = newB2Client(l.config)
		if err != nil {
			return nil, err
		}
		return l, nil
	}
	kindfn := func(u *url.URL) bool {
		return u.Scheme == Kind
	}
	stow.Register(Kind, makefn, kindfn, validatefn)
}

func newB2Client(cfg stow.Config) (*backblaze.B2, error) {
	accountID, _ := cfg.Config(ConfigAccountID)
	applicationKey, _ := cfg.Config(ConfigApplicationKey)
	keyID, _ := cfg.Config(ConfigKeyID)

	client, err := backblaze.NewB2(backblaze.Credentials{
		AccountID:      accountID,
		ApplicationKey: applicationKey,
		KeyID:          keyID,
	})

	if err != nil {
		return nil, errors.New("Unable to create client")
	}

	err = client.AuthorizeAccount()
	if err != nil {
		return nil, errors.New("Unable to authenticate")
	}

	return client, nil
}
<|endoftext|>


<commit_before>package bwhatsapp

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"errors"
	"fmt"
	"mime"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/Rhymen/go-whatsapp"
)

const (
	// Account config parameters
	cfgNumber         = "Number"
	qrOnWhiteTerminal = "QrOnWhiteTerminal"
	sessionFile       = "SessionFile"
)

// Bwhatsapp Bridge structure keeping all the information needed for relying
type Bwhatsapp struct {
	*bridge.Config

	session   *whatsapp.Session
	conn      *whatsapp.Conn
	startedAt uint64

	users       map[string]whatsapp.Contact
	userAvatars map[string]string
}

// New Create a new WhatsApp bridge. This will be called for each [whatsapp.<server>] entry you have in the config file
func New(cfg *bridge.Config) bridge.Bridger {
	number := cfg.GetString(cfgNumber)

	if number == "" {
		cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number")
	}

	b := &Bwhatsapp{
		Config: cfg,

		users:       make(map[string]whatsapp.Contact),
		userAvatars: make(map[string]string),
	}

	return b
}

// Connect to WhatsApp. Required implementation of the Bridger interface
func (b *Bwhatsapp) Connect() error {
	number := b.GetString(cfgNumber)
	if number == "" {
		return errors.New("whatsapp's telephone number need to be configured")
	}

	b.Log.Debugln("Connecting to WhatsApp..")
	conn, err := whatsapp.NewConn(20 * time.Second)
	if err != nil {
		return errors.New("failed to connect to WhatsApp: " + err.Error())
	}

	b.conn = conn

	b.conn.AddHandler(b)
	b.Log.Debugln("WhatsApp connection successful")

	// load existing session in order to keep it between restarts
	b.session, err = b.restoreSession()
	if err != nil {
		b.Log.Warn(err.Error())
	}

	// login to a new session
	if b.session == nil {
		if err = b.Login(); err != nil {
			return err
		}
	}

	b.startedAt = uint64(time.Now().Unix())

	_, err = b.conn.Contacts()
	if err != nil {
		return fmt.Errorf("error on update of contacts: %v", err)
	}

	// see https://github.com/Rhymen/go-whatsapp/issues/137#issuecomment-480316013
	for len(b.conn.Store.Contacts) == 0 {
		b.conn.Contacts() // nolint:errcheck

		<-time.After(1 * time.Second)
	}

	// map all the users
	for id, contact := range b.conn.Store.Contacts {
		if !isGroupJid(id) && id != "status@broadcast" {
			// it is user
			b.users[id] = contact
		}
	}

	// get user avatar asynchronously
	go func() {
		b.Log.Debug("Getting user avatars..")

		for jid := range b.users {
			info, err := b.GetProfilePicThumb(jid)
			if err != nil {
				b.Log.Warnf("Could not get profile photo of %s: %v", jid, err)
			} else {
				b.Lock()
				b.userAvatars[jid] = info.URL
				b.Unlock()
			}
		}

		b.Log.Debug("Finished getting avatars..")
	}()

	return nil
}

// Login to WhatsApp creating a new session. This will require to scan a QR code on your mobile device
func (b *Bwhatsapp) Login() error {
	b.Log.Debugln("Logging in..")

	invert := b.GetBool(qrOnWhiteTerminal) // false is the default
	qrChan := qrFromTerminal(invert)

	session, err := b.conn.Login(qrChan)
	if err != nil {
		b.Log.Warnln("Failed to log in:", err)

		return err
	}

	b.session = &session

	b.Log.Infof("Logged into session: %#v", session)
	b.Log.Infof("Connection: %#v", b.conn)

	err = b.writeSession(session)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error saving session: %v\n", err)
	}

	return nil
}

// Disconnect is called while reconnecting to the bridge
// Required implementation of the Bridger interface
func (b *Bwhatsapp) Disconnect() error {
	// We could Logout, but that would close the session completely and would require a new QR code scan
	// https://github.com/Rhymen/go-whatsapp/blob/c31092027237441cffba1b9cb148eadf7c83c3d2/session.go#L377-L381
	return nil
}

// JoinChannel Join a WhatsApp group specified in gateway config as channel='number-id@g.us' or channel='Channel name'
// Required implementation of the Bridger interface
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
func (b *Bwhatsapp) JoinChannel(channel config.ChannelInfo) error {
	byJid := isGroupJid(channel.Name)

	// see https://github.com/Rhymen/go-whatsapp/issues/137#issuecomment-480316013
	for len(b.conn.Store.Contacts) == 0 {
		b.conn.Contacts() // nolint:errcheck
		<-time.After(1 * time.Second)
	}

	// verify if we are member of the given group
	if byJid {
		// channel.Name specifies static group jID, not the name
		if _, exists := b.conn.Store.Contacts[channel.Name]; !exists {
			return fmt.Errorf("account doesn't belong to group with jid %s", channel.Name)
		}

		return nil
	}

	// channel.Name specifies group name that might change, warn about it
	var jids []string
	for id, contact := range b.conn.Store.Contacts {
		if isGroupJid(id) && contact.Name == channel.Name {
			jids = append(jids, id)
		}
	}

	switch len(jids) {
	case 0:
		// didn't match any group - print out possibilites
		for id, contact := range b.conn.Store.Contacts {
			if isGroupJid(id) {
				b.Log.Infof("%s %s", contact.Jid, contact.Name)
			}
		}

		return fmt.Errorf("please specify group's JID from the list above instead of the name '%s'", channel.Name)
	case 1:
		return fmt.Errorf("group name might change. Please configure gateway with channel=\"%v\" instead of channel=\"%v\"", jids[0], channel.Name)
	default:
		return fmt.Errorf("there is more than one group with name '%s'. Please specify one of JIDs as channel name: %v", channel.Name, jids)
	}
}

// Post a document message from the bridge to WhatsApp
func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (string, error) {
	fi := msg.Extra["file"][0].(config.FileInfo)

	// Post document message
	message := whatsapp.DocumentMessage{
		Info: whatsapp.MessageInfo{
			RemoteJid: msg.Channel,
		},
		Title:    fi.Name,
		FileName: fi.Name,
		Type:     filetype,
		Content:  bytes.NewReader(*fi.Data),
	}

	b.Log.Debugf("=> Sending %#v", msg)

	// create message ID
	// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
	idBytes := make([]byte, 10)
	if _, err := rand.Read(idBytes); err != nil {
		b.Log.Warn(err.Error())
	}

	message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
	_, err := b.conn.Send(message)

	return message.Info.Id, err
}

// Post an image message from the bridge to WhatsApp
// Handle, for sure image/jpeg, image/png and image/gif MIME types
func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (string, error) {
	fi := msg.Extra["file"][0].(config.FileInfo)

	// Post image message
	message := whatsapp.ImageMessage{
		Info: whatsapp.MessageInfo{
			RemoteJid: msg.Channel,
		},
		Type:    filetype,
		Caption: msg.Username + fi.Comment,
		Content: bytes.NewReader(*fi.Data),
	}

	b.Log.Debugf("=> Sending %#v", msg)

	// create message ID
	// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
	idBytes := make([]byte, 10)
	if _, err := rand.Read(idBytes); err != nil {
		b.Log.Warn(err.Error())
	}

	message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
	_, err := b.conn.Send(message)

	return message.Info.Id, err
}

// Send a message from the bridge to WhatsApp
// Required implementation of the Bridger interface
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
	b.Log.Debugf("=> Receiving %#v", msg)

	// Delete message
	if msg.Event == config.EventMsgDelete {
		if msg.ID == "" {
			// No message ID in case action is executed on a message sent before the bridge was started
			// and then the bridge cache doesn't have this message ID mapped
			return "", nil
		}

		_, err := b.conn.RevokeMessage(msg.Channel, msg.ID, true)

		return "", err
	}

	// Edit message
	if msg.ID != "" {
		b.Log.Debugf("updating message with id %s", msg.ID)

		if b.GetString("editsuffix") != "" {
			msg.Text += b.GetString("EditSuffix")
		} else {
			msg.Text += " (edited)"
		}
	}

	// Handle Upload a file
	if msg.Extra["file"] != nil {
		fi := msg.Extra["file"][0].(config.FileInfo)
		filetype := mime.TypeByExtension(filepath.Ext(fi.Name))

		b.Log.Debugf("Extra file is %#v", filetype)

		// TODO: add different types
		// TODO: add webp conversion
		switch filetype {
		case "image/jpeg", "image/png", "image/gif":
			return b.PostImageMessage(msg, filetype)
		default:
			return b.PostDocumentMessage(msg, filetype)
		}
	}

	// Post text message
	message := whatsapp.TextMessage{
		Info: whatsapp.MessageInfo{
			RemoteJid: msg.Channel, // which equals to group id
		},
		Text: msg.Username + msg.Text,
	}

	b.Log.Debugf("=> Sending %#v", msg)

	return b.conn.Send(message)
}

// TODO do we want that? to allow login with QR code from a bridged channel? https://github.com/tulir/mautrix-whatsapp/blob/513eb18e2d59bada0dd515ee1abaaf38a3bfe3d5/commands.go#L76
//func (b *Bwhatsapp) Command(cmd string) string {
//	return ""
//}
<commit_msg>Add whatsapp deprecation warning (#1792)<commit_after>package bwhatsapp

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"errors"
	"fmt"
	"mime"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/Rhymen/go-whatsapp"
)

const (
	// Account config parameters
	cfgNumber         = "Number"
	qrOnWhiteTerminal = "QrOnWhiteTerminal"
	sessionFile       = "SessionFile"
)

// Bwhatsapp Bridge structure keeping all the information needed for relying
type Bwhatsapp struct {
	*bridge.Config

	session   *whatsapp.Session
	conn      *whatsapp.Conn
	startedAt uint64

	users       map[string]whatsapp.Contact
	userAvatars map[string]string
}

// New Create a new WhatsApp bridge. This will be called for each [whatsapp.<server>] entry you have in the config file
func New(cfg *bridge.Config) bridge.Bridger {
	number := cfg.GetString(cfgNumber)

	cfg.Log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
	cfg.Log.Warn("This bridge is deprecated and not supported anymore. Use the new multidevice whatsapp bridge")
	cfg.Log.Warn("See https://github.com/42wim/matterbridge#building-with-whatsapp-beta-multidevice-support for more info")
	cfg.Log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")

	if number == "" {
		cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number")
	}

	b := &Bwhatsapp{
		Config: cfg,

		users:       make(map[string]whatsapp.Contact),
		userAvatars: make(map[string]string),
	}

	return b
}

// Connect to WhatsApp. Required implementation of the Bridger interface
func (b *Bwhatsapp) Connect() error {
	number := b.GetString(cfgNumber)
	if number == "" {
		return errors.New("whatsapp's telephone number need to be configured")
	}

	b.Log.Debugln("Connecting to WhatsApp..")
	conn, err := whatsapp.NewConn(20 * time.Second)
	if err != nil {
		return errors.New("failed to connect to WhatsApp: " + err.Error())
	}

	b.conn = conn

	b.conn.AddHandler(b)
	b.Log.Debugln("WhatsApp connection successful")

	// load existing session in order to keep it between restarts
	b.session, err = b.restoreSession()
	if err != nil {
		b.Log.Warn(err.Error())
	}

	// login to a new session
	if b.session == nil {
		if err = b.Login(); err != nil {
			return err
		}
	}

	b.startedAt = uint64(time.Now().Unix())

	_, err = b.conn.Contacts()
	if err != nil {
		return fmt.Errorf("error on update of contacts: %v", err)
	}

	// see https://github.com/Rhymen/go-whatsapp/issues/137#issuecomment-480316013
	for len(b.conn.Store.Contacts) == 0 {
		b.conn.Contacts() // nolint:errcheck

		<-time.After(1 * time.Second)
	}

	// map all the users
	for id, contact := range b.conn.Store.Contacts {
		if !isGroupJid(id) && id != "status@broadcast" {
			// it is user
			b.users[id] = contact
		}
	}

	// get user avatar asynchronously
	go func() {
		b.Log.Debug("Getting user avatars..")

		for jid := range b.users {
			info, err := b.GetProfilePicThumb(jid)
			if err != nil {
				b.Log.Warnf("Could not get profile photo of %s: %v", jid, err)
			} else {
				b.Lock()
				b.userAvatars[jid] = info.URL
				b.Unlock()
			}
		}

		b.Log.Debug("Finished getting avatars..")
	}()

	return nil
}

// Login to WhatsApp creating a new session. This will require to scan a QR code on your mobile device
func (b *Bwhatsapp) Login() error {
	b.Log.Debugln("Logging in..")

	invert := b.GetBool(qrOnWhiteTerminal) // false is the default
	qrChan := qrFromTerminal(invert)

	session, err := b.conn.Login(qrChan)
	if err != nil {
		b.Log.Warnln("Failed to log in:", err)

		return err
	}

	b.session = &session

	b.Log.Infof("Logged into session: %#v", session)
	b.Log.Infof("Connection: %#v", b.conn)

	err = b.writeSession(session)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error saving session: %v\n", err)
	}

	return nil
}

// Disconnect is called while reconnecting to the bridge
// Required implementation of the Bridger interface
func (b *Bwhatsapp) Disconnect() error {
	// We could Logout, but that would close the session completely and would require a new QR code scan
	// https://github.com/Rhymen/go-whatsapp/blob/c31092027237441cffba1b9cb148eadf7c83c3d2/session.go#L377-L381
	return nil
}

// JoinChannel Join a WhatsApp group specified in gateway config as channel='number-id@g.us' or channel='Channel name'
// Required implementation of the Bridger interface
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
func (b *Bwhatsapp) JoinChannel(channel config.ChannelInfo) error {
	byJid := isGroupJid(channel.Name)

	// see https://github.com/Rhymen/go-whatsapp/issues/137#issuecomment-480316013
	for len(b.conn.Store.Contacts) == 0 {
		b.conn.Contacts() // nolint:errcheck
		<-time.After(1 * time.Second)
	}

	// verify if we are member of the given group
	if byJid {
		// channel.Name specifies static group jID, not the name
		if _, exists := b.conn.Store.Contacts[channel.Name]; !exists {
			return fmt.Errorf("account doesn't belong to group with jid %s", channel.Name)
		}

		return nil
	}

	// channel.Name specifies group name that might change, warn about it
	var jids []string
	for id, contact := range b.conn.Store.Contacts {
		if isGroupJid(id) && contact.Name == channel.Name {
			jids = append(jids, id)
		}
	}

	switch len(jids) {
	case 0:
		// didn't match any group - print out possibilites
		for id, contact := range b.conn.Store.Contacts {
			if isGroupJid(id) {
				b.Log.Infof("%s %s", contact.Jid, contact.Name)
			}
		}

		return fmt.Errorf("please specify group's JID from the list above instead of the name '%s'", channel.Name)
	case 1:
		return fmt.Errorf("group name might change. Please configure gateway with channel=\"%v\" instead of channel=\"%v\"", jids[0], channel.Name)
	default:
		return fmt.Errorf("there is more than one group with name '%s'. Please specify one of JIDs as channel name: %v", channel.Name, jids)
	}
}

// Post a document message from the bridge to WhatsApp
func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (string, error) {
	fi := msg.Extra["file"][0].(config.FileInfo)

	// Post document message
	message := whatsapp.DocumentMessage{
		Info: whatsapp.MessageInfo{
			RemoteJid: msg.Channel,
		},
		Title:    fi.Name,
		FileName: fi.Name,
		Type:     filetype,
		Content:  bytes.NewReader(*fi.Data),
	}

	b.Log.Debugf("=> Sending %#v", msg)

	// create message ID
	// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
	idBytes := make([]byte, 10)
	if _, err := rand.Read(idBytes); err != nil {
		b.Log.Warn(err.Error())
	}

	message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
	_, err := b.conn.Send(message)

	return message.Info.Id, err
}

// Post an image message from the bridge to WhatsApp
// Handle, for sure image/jpeg, image/png and image/gif MIME types
func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (string, error) {
	fi := msg.Extra["file"][0].(config.FileInfo)

	// Post image message
	message := whatsapp.ImageMessage{
		Info: whatsapp.MessageInfo{
			RemoteJid: msg.Channel,
		},
		Type:    filetype,
		Caption: msg.Username + fi.Comment,
		Content: bytes.NewReader(*fi.Data),
	}

	b.Log.Debugf("=> Sending %#v", msg)

	// create message ID
	// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
	idBytes := make([]byte, 10)
	if _, err := rand.Read(idBytes); err != nil {
		b.Log.Warn(err.Error())
	}

	message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
	_, err := b.conn.Send(message)

	return message.Info.Id, err
}

// Send a message from the bridge to WhatsApp
// Required implementation of the Bridger interface
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
	b.Log.Debugf("=> Receiving %#v", msg)

	// Delete message
	if msg.Event == config.EventMsgDelete {
		if msg.ID == "" {
			// No message ID in case action is executed on a message sent before the bridge was started
			// and then the bridge cache doesn't have this message ID mapped
			return "", nil
		}

		_, err := b.conn.RevokeMessage(msg.Channel, msg.ID, true)

		return "", err
	}

	// Edit message
	if msg.ID != "" {
		b.Log.Debugf("updating message with id %s", msg.ID)

		if b.GetString("editsuffix") != "" {
			msg.Text += b.GetString("EditSuffix")
		} else {
			msg.Text += " (edited)"
		}
	}

	// Handle Upload a file
	if msg.Extra["file"] != nil {
		fi := msg.Extra["file"][0].(config.FileInfo)
		filetype := mime.TypeByExtension(filepath.Ext(fi.Name))

		b.Log.Debugf("Extra file is %#v", filetype)

		// TODO: add different types
		// TODO: add webp conversion
		switch filetype {
		case "image/jpeg", "image/png", "image/gif":
			return b.PostImageMessage(msg, filetype)
		default:
			return b.PostDocumentMessage(msg, filetype)
		}
	}

	// Post text message
	message := whatsapp.TextMessage{
		Info: whatsapp.MessageInfo{
			RemoteJid: msg.Channel, // which equals to group id
		},
		Text: msg.Username + msg.Text,
	}

	b.Log.Debugf("=> Sending %#v", msg)

	return b.conn.Send(message)
}

// TODO do we want that? to allow login with QR code from a bridged channel? https://github.com/tulir/mautrix-whatsapp/blob/513eb18e2d59bada0dd515ee1abaaf38a3bfe3d5/commands.go#L76
//func (b *Bwhatsapp) Command(cmd string) string {
//	return ""
//}
<|endoftext|>


<commit_before>/*
Copyright 2019 Google LLC.
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 connection

import (
	"context"
	"errors"
	"net"
	"sync"
	"testing"
	"time"

	"google.golang.org/grpc"

	pb "github.com/openconfig/gnmi/proto/gnmi"
)

func newDevice(t *testing.T) (string, func()) {
	t.Helper()

	srv := grpc.NewServer()
	s := &pb.UnimplementedGNMIServer{}
	pb.RegisterGNMIServer(srv, s)
	lis, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		t.Fatalf("Failed to listen: %v", err)
	}
	go srv.Serve(lis)

	return lis.Addr().String(), srv.Stop
}

func TestCtxCanceled(t *testing.T) {
	addr, stop := newDevice(t)
	defer stop()
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	m, err := NewManager(grpc.WithBlock(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("Failed to initialize Manager: %v", err)
	}
	if _, _, err := m.Connection(ctx, addr); err == nil {
		t.Errorf("Connection returned no error, want error")
	}
}

func assertConns(t *testing.T, m *Manager, want int) {
	if l := len(m.conns); l != want {
		t.Fatalf("got %v connections, want %v", l, want)
	}
}

func assertRefs(t *testing.T, m *Manager, addr string, want int) {
	c, ok := m.conns[addr]
	if !ok {
		t.Fatalf("connection %q missing", addr)
	}
	if r := c.ref; r != want {
		t.Fatalf("got %v references, want %v", r, want)
	}
}

func TestConcurrentConnection(t *testing.T) {
	addr, stop := newDevice(t)
	defer stop()
	ctx := context.Background()
	m, err := NewManager(grpc.WithBlock(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("Failed to initialize Manager: %v", err)
	}
	var wg sync.WaitGroup
	lim := 300
	wg.Add(lim)

	for i := 0; i < lim; i++ {
		go func() {
			conn, _, err := m.Connection(ctx, addr)
			if err != nil {
				t.Fatalf("got error creating connection: %v, want no error", err)
			}
			if conn == nil {
				t.Fatalf("got nil connection, expected not nil")
			}
			wg.Done()
		}()
	}
	wg.Wait()

	assertConns(t, m, 1)
	assertRefs(t, m, addr, lim)
}

func TestDone(t *testing.T) {
	addr, stop := newDevice(t)
	defer stop()
	ctx := context.Background()
	m, err := NewManager(grpc.WithBlock(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("Failed to initialize Manager: %v", err)
	}

	_, done1, err := m.Connection(ctx, addr)
	if err != nil {
		t.Fatalf("got error creating connection: %v, want no error", err)
	}
	assertConns(t, m, 1)
	assertRefs(t, m, addr, 1)

	_, done2, err := m.Connection(ctx, addr)
	if err != nil {
		t.Fatalf("got error creating connection: %v, want no error", err)
	}
	assertConns(t, m, 1)
	assertRefs(t, m, addr, 2)

	done1()
	assertConns(t, m, 1)
	assertRefs(t, m, addr, 1)

	done2()
	assertConns(t, m, 0)

	done1() // No panic.
	done2() // No panic.
}

// TestConnectionDone simulates concurrently creating, reusing, and
// destroying multiple connections to different addresses.
func TestConnectionDone(t *testing.T) {
	type connTest struct {
		desc         string
		connections  int
		dones        int
		connectionID string // Populated by test.
	}

	tests := []*connTest{
		// NOTE: done counts are relatively low to avoid connection
		// thrashing that causes gRPC attempts to hang and affect this test.
		{
			desc:        "connection1",
			connections: 200,
			dones:       20,
		},
		{
			desc:        "connection2",
			connections: 200,
			dones:       25,
		},
		{
			desc:        "connection3",
			connections: 200,
			dones:       30,
		},
	}

	process := func(ctx context.Context, t *testing.T, c *connTest, m *Manager, wg *sync.WaitGroup) {
		for i := 0; i < c.connections; i++ {
			d := i
			go func() {
				conn, done, err := m.Connection(ctx, c.connectionID)
				wg.Done()
				if conn == nil {
					t.Fatalf("got nil connection")
				}
				if err != nil {
					t.Fatalf("got error creating connection: %v, want no error", err)
				}
				if d < c.dones {
					go func() {
						done()
						wg.Done()
					}()
				}
			}()
		}
	}

	var wg sync.WaitGroup
	m, err := NewManager(grpc.WithBlock(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("Failed to initialize Manager: %v", err)
	}
	for _, tt := range tests {
		wg.Add(tt.connections + tt.dones)
		addr, stop := newDevice(t)
		defer stop()
		tt.connectionID = addr
	}
	for _, tt := range tests {
		go process(context.Background(), t, tt, m, &wg)
	}
	wg.Wait()

	if len(m.conns) != len(tests) {
		t.Fatalf("got %v connections, want %v", len(m.conns), len(tests))
	}
	for _, tt := range tests {
		_, ok := m.conns[tt.connectionID]
		if !ok {
			t.Fatalf("%s: missing connection", tt.desc)
		}
		assertRefs(t, m, tt.connectionID, tt.connections-tt.dones)
	}
}

func errDialWait(d time.Duration) func(_ context.Context, _ string, _ ...grpc.DialOption) (*grpc.ClientConn, error) {
	return func(_ context.Context, _ string, _ ...grpc.DialOption) (*grpc.ClientConn, error) {
		time.Sleep(d)
		return nil, errors.New("error occurred")
	}
}

func TestConcurrentDialErr(t *testing.T) {
	ctx := context.Background()
	m, err := NewManagerCustom(errDialWait(time.Second), grpc.WithBlock(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("Failed to initialize Manager: %v", err)
	}
	var wg sync.WaitGroup
	lim := 2
	wg.Add(lim)
	errs := make(chan error, lim)
	start := make(chan struct{})

	for i := 0; i < lim; i++ {
		go func() {
			<-start
			_, _, err := m.Connection(ctx, "")
			if err == nil {
				t.Fatal("got no error, want error")
			}
			errs <- err
			wg.Done()
		}()
	}
	close(start)
	wg.Wait()

	close(errs)
	var prevErr error
	for err := range errs {
		if prevErr == nil {
			prevErr = err
		}
		if err != prevErr {
			t.Fatal("got different error instance, want same")
		}
	}
}

func TestNewManagerCustom(t *testing.T) {
	tests := []struct {
		desc    string
		d       Dial
		opts    []grpc.DialOption
		wantErr bool
	}{
		{
			desc:    "missing dial",
			opts:    []grpc.DialOption{grpc.WithBlock()},
			wantErr: true,
		}, {
			desc: "missing opts",
			d:    grpc.DialContext,
		}, {
			desc: "valid dial and opts",
			opts: []grpc.DialOption{grpc.WithBlock()},
			d:    grpc.DialContext,
		},
	}

	for _, tt := range tests {
		_, err := NewManagerCustom(tt.d, tt.opts...)
		switch {
		case err == nil && !tt.wantErr:
		case err == nil && tt.wantErr:
			t.Errorf("%v: got no error, want error.", tt.desc)
		case err != nil && !tt.wantErr:
			t.Errorf("%v: got error, want no error.", tt.desc)
		}
	}
}
<commit_msg>Remove t.Fatalf from connection_test.go goroutines<commit_after>/*
Copyright 2019 Google LLC.
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 connection

import (
	"context"
	"errors"
	"net"
	"sync"
	"testing"
	"time"

	"google.golang.org/grpc"

	pb "github.com/openconfig/gnmi/proto/gnmi"
)

func newDevice(t *testing.T) (string, func()) {
	t.Helper()

	srv := grpc.NewServer()
	s := &pb.UnimplementedGNMIServer{}
	pb.RegisterGNMIServer(srv, s)
	lis, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		t.Fatalf("Failed to listen: %v", err)
	}
	go srv.Serve(lis)

	return lis.Addr().String(), srv.Stop
}

func TestCtxCanceled(t *testing.T) {
	addr, stop := newDevice(t)
	defer stop()
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	m, err := NewManager(grpc.WithBlock(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("Failed to initialize Manager: %v", err)
	}
	if _, _, err := m.Connection(ctx, addr); err == nil {
		t.Error("Connection returned no error, want error")
	}
}

func assertConns(t *testing.T, m *Manager, want int) {
	t.Helper()
	if l := len(m.conns); l != want {
		t.Fatalf("got %v connections, want %v", l, want)
	}
}

func assertRefs(t *testing.T, m *Manager, addr string, want int) {
	t.Helper()
	c, ok := m.conns[addr]
	if !ok {
		t.Fatalf("connection %q missing", addr)
	}
	if r := c.ref; r != want {
		t.Fatalf("got %v references, want %v", r, want)
	}
}

func TestConcurrentConnection(t *testing.T) {
	addr, stop := newDevice(t)
	defer stop()
	ctx := context.Background()
	m, err := NewManager(grpc.WithBlock(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("Failed to initialize Manager: %v", err)
	}
	var wg sync.WaitGroup
	lim := 300
	wg.Add(lim)

	for i := 0; i < lim; i++ {
		go func() {
			conn, _, err := m.Connection(ctx, addr)
			switch {
			case err != nil:
				t.Errorf("got error creating connection: %v, want no error", err)
			case conn == nil:
				t.Error("got nil connection, expected not nil")
			}
			wg.Done()
		}()
	}
	wg.Wait()

	assertConns(t, m, 1)
	assertRefs(t, m, addr, lim)
}

func TestDone(t *testing.T) {
	addr, stop := newDevice(t)
	defer stop()
	ctx := context.Background()
	m, err := NewManager(grpc.WithBlock(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("Failed to initialize Manager: %v", err)
	}

	_, done1, err := m.Connection(ctx, addr)
	if err != nil {
		t.Fatalf("got error creating connection: %v, want no error", err)
	}
	assertConns(t, m, 1)
	assertRefs(t, m, addr, 1)

	_, done2, err := m.Connection(ctx, addr)
	if err != nil {
		t.Fatalf("got error creating connection: %v, want no error", err)
	}
	assertConns(t, m, 1)
	assertRefs(t, m, addr, 2)

	done1()
	assertConns(t, m, 1)
	assertRefs(t, m, addr, 1)

	done2()
	assertConns(t, m, 0)

	done1() // No panic.
	done2() // No panic.
}

// TestConnectionDone simulates concurrently creating, reusing, and
// destroying multiple connections to different addresses.
func TestConnectionDone(t *testing.T) {
	type connTest struct {
		desc         string
		connections  int
		dones        int
		connectionID string // Populated by test.
	}

	tests := []*connTest{
		// NOTE: done counts are relatively low to avoid connection
		// thrashing that causes gRPC attempts to hang and affect this test.
		{
			desc:        "connection1",
			connections: 200,
			dones:       20,
		},
		{
			desc:        "connection2",
			connections: 200,
			dones:       25,
		},
		{
			desc:        "connection3",
			connections: 200,
			dones:       30,
		},
	}

	process := func(ctx context.Context, t *testing.T, c *connTest, m *Manager, wg *sync.WaitGroup) {
		for i := 0; i < c.connections; i++ {
			d := i
			go func() {
				conn, done, err := m.Connection(ctx, c.connectionID)
				wg.Done()
				if conn == nil {
					t.Error("got nil connection")
				}
				if err != nil {
					t.Errorf("got error creating connection: %v, want no error", err)
				}
				if d < c.dones {
					go func() {
						done()
						wg.Done()
					}()
				}
			}()
		}
	}

	var wg sync.WaitGroup
	m, err := NewManager(grpc.WithBlock(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("Failed to initialize Manager: %v", err)
	}
	for _, tt := range tests {
		wg.Add(tt.connections + tt.dones)
		addr, stop := newDevice(t)
		defer stop()
		tt.connectionID = addr
	}
	for _, tt := range tests {
		go process(context.Background(), t, tt, m, &wg)
	}
	wg.Wait()

	if len(m.conns) != len(tests) {
		t.Fatalf("got %v connections, want %v", len(m.conns), len(tests))
	}
	for _, tt := range tests {
		_, ok := m.conns[tt.connectionID]
		if !ok {
			t.Fatalf("%s: missing connection", tt.desc)
		}
		assertRefs(t, m, tt.connectionID, tt.connections-tt.dones)
	}
}

func errDialWait(d time.Duration) func(_ context.Context, _ string, _ ...grpc.DialOption) (*grpc.ClientConn, error) {
	return func(_ context.Context, _ string, _ ...grpc.DialOption) (*grpc.ClientConn, error) {
		time.Sleep(d)
		return nil, errors.New("error occurred")
	}
}

func TestConcurrentDialErr(t *testing.T) {
	ctx := context.Background()
	m, err := NewManagerCustom(errDialWait(time.Second), grpc.WithBlock(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("Failed to initialize Manager: %v", err)
	}
	var wg sync.WaitGroup
	lim := 2
	wg.Add(lim)
	errs := make(chan error, lim)
	start := make(chan struct{})

	for i := 0; i < lim; i++ {
		go func() {
			<-start
			_, _, err := m.Connection(ctx, "")
			if err == nil {
				t.Fatal("got no error, want error")
			}
			errs <- err
			wg.Done()
		}()
	}
	close(start)
	wg.Wait()

	close(errs)
	var prevErr error
	for err := range errs {
		if prevErr == nil {
			prevErr = err
		}
		if err != prevErr {
			t.Fatal("got different error instance, want same")
		}
	}
}

func TestNewManagerCustom(t *testing.T) {
	tests := []struct {
		desc    string
		d       Dial
		opts    []grpc.DialOption
		wantErr bool
	}{
		{
			desc:    "missing dial",
			opts:    []grpc.DialOption{grpc.WithBlock()},
			wantErr: true,
		}, {
			desc: "missing opts",
			d:    grpc.DialContext,
		}, {
			desc: "valid dial and opts",
			opts: []grpc.DialOption{grpc.WithBlock()},
			d:    grpc.DialContext,
		},
	}

	for _, tt := range tests {
		_, err := NewManagerCustom(tt.d, tt.opts...)
		switch {
		case err == nil && !tt.wantErr:
		case err == nil && tt.wantErr:
			t.Errorf("%v: got no error, want error.", tt.desc)
		case err != nil && !tt.wantErr:
			t.Errorf("%v: got error, want no error.", tt.desc)
		}
	}
}
<|endoftext|>


<commit_before>package test

import (
	"sync"
	"testing"

	"github.com/neptulon/jsonrpc"
	"github.com/neptulon/jsonrpc/middleware"
)

type echoMsg struct {
	Message string `json:"message"`
}

func TestEcho(t *testing.T) {
	// todo: streamline these like test.NewServerHelper(t).GetRouter().GetClientHelper() // these could wrap other helpers or directly objects?
	sh := NewServerHelper(t).Start()
	defer sh.Close()

	ch := sh.nepSH.GetTCPClientHelper().Connect()
	defer ch.Close()

	rout := sh.GetRouter()
	rout.Request("echo", middleware.Echo)

	// -----------------

	var wg sync.WaitGroup

	// todo: separate echo middleware into /middleware package
	// todo2: use sender.go rather than this manual handling
	// todo3: Helper.Middleware function should do the wg.Add(1)/wg.Done() and Close should wait for it. Also in neptulon

	jc := jsonrpc.UseClient(ch.Client)
	jc.ResMiddleware(func(ctx *jsonrpc.ResCtx) error {
		defer wg.Done()
		var msg echoMsg
		if err := ctx.Result(&msg); err != nil {
			t.Fatal(err)
		}
		if msg.Message != "Hello!" {
			t.Fatalf("expected: %v got: %v", "Hello!", msg.Message)
		}
		return ctx.Next()
	})

	wg.Add(1)
	// todo4 (do after todo3): SendRequest should use Sender automatically
	//  and accept response callback which would be registered as last middleware
	//  same goes for Server.SendTo() also, which would use the same Sender.go middleware
	jc.SendRequest("echo", echoMsg{Message: "Hello!"})
	wg.Wait()
}

func TestOrderedDuplex(t *testing.T) {
	// client requests, server answers, server requests, client answers, client closes gracefully
}

func TestSimultaneousDuplex(t *testing.T) {
	// mash of requests, notifications, responses flying in both directions in a random loop with predefined answers
	// along with long running background request-response with large data to test interleaving and to experiment with future streaming semantics
}
<commit_msg>add ClientHelper wrapping note<commit_after>package test

import (
	"sync"
	"testing"

	"github.com/neptulon/jsonrpc"
	"github.com/neptulon/jsonrpc/middleware"
)

type echoMsg struct {
	Message string `json:"message"`
}

func TestEcho(t *testing.T) {
	sh := NewServerHelper(t).Start()
	defer sh.Close()

	// todo5: implement sh.GetClientHelper() instead after deciding what and how to wrap Neptulon Client/ClientHelper
	ch := sh.nepSH.GetTCPClientHelper().Connect()
	defer ch.Close()

	rout := sh.GetRouter()
	rout.Request("echo", middleware.Echo)

	// -----------------

	var wg sync.WaitGroup

	// todo1: separate echo middleware into /middleware package
	// todo2: use sender.go rather than this manual handling
	// todo3: Helper.Middleware function should do the wg.Add(1)/wg.Done() and Close should wait for it. Also in neptulon

	jc := jsonrpc.UseClient(ch.Client)
	jc.ResMiddleware(func(ctx *jsonrpc.ResCtx) error {
		defer wg.Done()
		var msg echoMsg
		if err := ctx.Result(&msg); err != nil {
			t.Fatal(err)
		}
		if msg.Message != "Hello!" {
			t.Fatalf("expected: %v got: %v", "Hello!", msg.Message)
		}
		return ctx.Next()
	})

	wg.Add(1)
	// todo4 (do after todo3): SendRequest should use Sender automatically
	//  and accept response callback which would be registered as last middleware
	//  same goes for Server.SendTo() also, which would use the same Sender.go middleware
	jc.SendRequest("echo", echoMsg{Message: "Hello!"})
	wg.Wait()
}

func TestOrderedDuplex(t *testing.T) {
	// client requests, server answers, server requests, client answers, client closes gracefully
}

func TestSimultaneousDuplex(t *testing.T) {
	// mash of requests, notifications, responses flying in both directions in a random loop with predefined answers
	// along with long running background request-response with large data to test interleaving and to experiment with future streaming semantics
}
<|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2020 Red Hat, Inc.
 *
 */

package network

import (
	"net"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	v1 "kubevirt.io/client-go/api/v1"
	"kubevirt.io/client-go/kubecli"

	"kubevirt.io/kubevirt/tests"
	"kubevirt.io/kubevirt/tests/console"
	cd "kubevirt.io/kubevirt/tests/containerdisk"
	"kubevirt.io/kubevirt/tests/libnet"
	"kubevirt.io/kubevirt/tests/libvmi"
)

var _ = SIGDescribe("[Serial]Primary Pod Network", func() {
	var virtClient kubecli.KubevirtClient
	BeforeEach(func() {
		var err error
		virtClient, err = kubecli.GetKubevirtClient()
		Expect(err).NotTo(HaveOccurred(), "Should successfully initialize an API client")
	})

	Describe("Status", func() {
		AssertReportedIP := func(vmi *v1.VirtualMachineInstance) {
			By("Getting pod of the VMI")
			vmiPod := tests.GetRunningPodByVirtualMachineInstance(vmi, tests.NamespaceTestDefault)

			By("Making sure IP/s reported on the VMI matches the ones on the pod")
			Expect(libnet.ValidateVMIandPodIPMatch(vmi, vmiPod)).To(Succeed(), "Should have matching IP/s between pod and vmi")
		}

		Context("VMI connected to the pod network using the default (implicit) binding", func() {
			var vmi *v1.VirtualMachineInstance

			BeforeEach(func() {
				vmi = setupVMI(virtClient, vmiWithDefaultBinding())
			})

			AfterEach(func() {
				cleanupVMI(virtClient, vmi)
			})

			It("should report PodIP as its own on interface status", func() { AssertReportedIP(vmi) })
		})

		Context("VMI connected to the pod network using bridge binding", func() {
			When("Guest Agent exists", func() {
				var (
					vmi   *v1.VirtualMachineInstance
					vmiIP = func() string {
						var err error
						vmi, err = virtClient.VirtualMachineInstance(vmi.Namespace).Get(vmi.Name, &metav1.GetOptions{})
						ExpectWithOffset(1, err).ToNot(HaveOccurred(), "should success retrieving VMI to get IP")
						return vmi.Status.Interfaces[0].IP
					}

					vmiIPs = func() []string {
						var err error
						vmi, err = virtClient.VirtualMachineInstance(vmi.Namespace).Get(vmi.Name, &metav1.GetOptions{})
						ExpectWithOffset(1, err).ToNot(HaveOccurred(), "should success retrieving VMI to get IPs")
						return vmi.Status.Interfaces[0].IPs
					}
				)
				BeforeEach(func() {
					var err error

					vmi, err = newFedoraWithGuestAgentAndDefaultInterface(libvmi.InterfaceDeviceWithBridgeBinding())
					Expect(err).NotTo(HaveOccurred())

					vmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
					Expect(err).NotTo(HaveOccurred())
					tests.WaitForSuccessfulVMIStart(vmi)
					tests.WaitAgentConnected(virtClient, vmi)
				})

				AfterEach(func() {
					cleanupVMI(virtClient, vmi)
				})

				It("should report PodIP/s as its own on interface status", func() {
					vmiPod := tests.GetRunningPodByVirtualMachineInstance(vmi, vmi.Namespace)
					Eventually(vmiIP).Should(WithTransform(removeCIDR, Equal(vmiPod.Status.PodIP)), "should contain VMI Status IP as Pod status ip")
					Eventually(vmiIPs).Should(ContainElements(vmi.Status.Interfaces[0].IP, libnet.DefaultIPv6Address), "should contain at IPs the IP and the cloud-init configured ipv6")
				})
			})
			When("no Guest Agent exists", func() {
				var vmi *v1.VirtualMachineInstance

				BeforeEach(func() {
					vmi = setupVMI(virtClient, vmiWithBridgeBinding())
				})

				AfterEach(func() {
					cleanupVMI(virtClient, vmi)
				})

				It("should report PodIP as its own on interface status", func() { AssertReportedIP(vmi) })
			})
		})

		Context("VMI connected to the pod network using masquerade binding", func() {
			When("Guest Agent exists", func() {
				var vmi *v1.VirtualMachineInstance

				BeforeEach(func() {
					tmpVmi, err := newFedoraWithGuestAgentAndDefaultInterface(libvmi.InterfaceDeviceWithMasqueradeBinding())
					Expect(err).NotTo(HaveOccurred())

					tmpVmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(tmpVmi)
					Expect(err).NotTo(HaveOccurred())
					vmi = tests.WaitUntilVMIReady(tmpVmi, console.LoginToFedora)

					tests.WaitAgentConnected(virtClient, vmi)
				})

				AfterEach(func() {
					cleanupVMI(virtClient, vmi)
				})

				It("[test_id:4153]should report PodIP/s as its own on interface status", func() {
					vmiPod := tests.GetRunningPodByVirtualMachineInstance(vmi, vmi.Namespace)
					Consistently(func() error {
						vmi, err := virtClient.VirtualMachineInstance(vmi.Namespace).Get(vmi.Name, &metav1.GetOptions{})
						if err != nil {
							return err
						}
						return libnet.ValidateVMIandPodIPMatch(vmi, vmiPod)
					}, 5*time.Second, time.Second).Should(Succeed())
				})

			})

			When("no Guest Agent exists", func() {
				var vmi *v1.VirtualMachineInstance

				BeforeEach(func() {
					vmi = setupVMI(virtClient, vmiWithMasqueradeBinding())
				})

				AfterEach(func() {
					cleanupVMI(virtClient, vmi)
				})

				It("[Conformance] should report PodIP as its own on interface status", func() { AssertReportedIP(vmi) })
			})
		})
	})
})

func setupVMI(virtClient kubecli.KubevirtClient, vmi *v1.VirtualMachineInstance) *v1.VirtualMachineInstance {
	By("Creating the VMI")
	var err error
	vmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
	Expect(err).NotTo(HaveOccurred(), "VMI should be successfully created")

	By("Waiting until the VMI gets ready")
	vmi = tests.WaitUntilVMIReady(vmi, console.LoginToAlpine)

	return vmi
}

func cleanupVMI(virtClient kubecli.KubevirtClient, vmi *v1.VirtualMachineInstance) {
	if vmi != nil {
		By("Deleting the VMI")
		Expect(virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Delete(vmi.GetName(), &metav1.DeleteOptions{})).To(Succeed())

		By("Waiting for the VMI to be gone")
		Eventually(func() error {
			_, err := virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Get(vmi.GetName(), &metav1.GetOptions{})
			return err
		}, 2*time.Minute, time.Second).Should(SatisfyAll(HaveOccurred(), WithTransform(errors.IsNotFound, BeTrue())), "The VMI should be gone within the given timeout")
	}
}

func vmiWithDefaultBinding() *v1.VirtualMachineInstance {
	vmi := tests.NewRandomVMIWithEphemeralDisk(cd.ContainerDiskFor(cd.ContainerDiskAlpine))
	vmi.Spec.Domain.Devices.Interfaces = nil
	vmi.Spec.Networks = nil
	return vmi
}

func vmiWithBridgeBinding() *v1.VirtualMachineInstance {
	vmi := tests.NewRandomVMIWithEphemeralDisk(cd.ContainerDiskFor(cd.ContainerDiskAlpine))
	vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{*v1.DefaultBridgeNetworkInterface()}
	vmi.Spec.Networks = []v1.Network{*v1.DefaultPodNetwork()}
	return vmi
}

func vmiWithMasqueradeBinding() *v1.VirtualMachineInstance {
	vmi := tests.NewRandomVMIWithEphemeralDisk(cd.ContainerDiskFor(cd.ContainerDiskAlpine))
	vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{*v1.DefaultMasqueradeNetworkInterface()}
	vmi.Spec.Networks = []v1.Network{*v1.DefaultPodNetwork()}
	return vmi
}

func newFedoraWithGuestAgentAndDefaultInterface(iface v1.Interface) (*v1.VirtualMachineInstance, error) {
	networkData, err := libnet.CreateDefaultCloudInitNetworkData()
	if err != nil {
		return nil, err
	}

	vmi := libvmi.NewFedora(
		libvmi.WithInterface(iface),
		libvmi.WithNetwork(v1.DefaultPodNetwork()),
		libvmi.WithCloudInitNoCloudUserData(tests.GetGuestAgentUserData(), false),
		libvmi.WithCloudInitNoCloudNetworkData(networkData, false),
	)
	return vmi, nil
}

func removeCIDR(ip string) string {
	ipAddr, _, err := net.ParseCIDR(ip)
	if err != nil {
		return ip
	}
	return ipAddr.String()
}
<commit_msg>tests, network: Fix race condition GA test<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2020 Red Hat, Inc.
 *
 */

package network

import (
	"net"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	v1 "kubevirt.io/client-go/api/v1"
	"kubevirt.io/client-go/kubecli"

	"kubevirt.io/kubevirt/tests"
	"kubevirt.io/kubevirt/tests/console"
	cd "kubevirt.io/kubevirt/tests/containerdisk"
	"kubevirt.io/kubevirt/tests/libnet"
	"kubevirt.io/kubevirt/tests/libvmi"
)

var _ = SIGDescribe("[Serial]Primary Pod Network", func() {
	var virtClient kubecli.KubevirtClient
	BeforeEach(func() {
		var err error
		virtClient, err = kubecli.GetKubevirtClient()
		Expect(err).NotTo(HaveOccurred(), "Should successfully initialize an API client")
	})

	Describe("Status", func() {
		AssertReportedIP := func(vmi *v1.VirtualMachineInstance) {
			By("Getting pod of the VMI")
			vmiPod := tests.GetRunningPodByVirtualMachineInstance(vmi, tests.NamespaceTestDefault)

			By("Making sure IP/s reported on the VMI matches the ones on the pod")
			Expect(libnet.ValidateVMIandPodIPMatch(vmi, vmiPod)).To(Succeed(), "Should have matching IP/s between pod and vmi")
		}

		Context("VMI connected to the pod network using the default (implicit) binding", func() {
			var vmi *v1.VirtualMachineInstance

			BeforeEach(func() {
				vmi = setupVMI(virtClient, vmiWithDefaultBinding())
			})

			AfterEach(func() {
				cleanupVMI(virtClient, vmi)
			})

			It("should report PodIP as its own on interface status", func() { AssertReportedIP(vmi) })
		})

		Context("VMI connected to the pod network using bridge binding", func() {
			When("Guest Agent exists", func() {
				var (
					vmi   *v1.VirtualMachineInstance
					vmiIP = func() string {
						var err error
						vmi, err = virtClient.VirtualMachineInstance(vmi.Namespace).Get(vmi.Name, &metav1.GetOptions{})
						ExpectWithOffset(1, err).ToNot(HaveOccurred(), "should success retrieving VMI to get IP")
						return vmi.Status.Interfaces[0].IP
					}

					vmiIPs = func() []string {
						var err error
						vmi, err = virtClient.VirtualMachineInstance(vmi.Namespace).Get(vmi.Name, &metav1.GetOptions{})
						ExpectWithOffset(1, err).ToNot(HaveOccurred(), "should success retrieving VMI to get IPs")
						return vmi.Status.Interfaces[0].IPs
					}
				)
				BeforeEach(func() {
					var err error

					vmi, err = newFedoraWithGuestAgentAndDefaultInterface(libvmi.InterfaceDeviceWithBridgeBinding())
					Expect(err).NotTo(HaveOccurred())

					vmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
					Expect(err).NotTo(HaveOccurred())
					tests.WaitForSuccessfulVMIStart(vmi)
					tests.WaitAgentConnected(virtClient, vmi)
				})

				AfterEach(func() {
					cleanupVMI(virtClient, vmi)
				})

				It("should report PodIP/s IPv4 as its own on interface status", func() {
					vmiPod := tests.GetRunningPodByVirtualMachineInstance(vmi, vmi.Namespace)
					Eventually(vmiIP).Should(WithTransform(removeCIDR, Equal(vmiPod.Status.PodIP)), "should contain VMI Status IP as Pod status ip")
					Eventually(vmiIPs).Should(WithTransform(removeCIDRs, ContainElement(vmiPod.Status.PodIP)), "should contain IPv4 reported by guest agent")
				})

				It("should report VMIs static IPv6 at interface status", func() {
					Eventually(vmiIPs).Should(ContainElement(libnet.DefaultIPv6Address), "should contain IPv6 address set by cloud-init and reported by guest agent")
				})
			})
			When("no Guest Agent exists", func() {
				var vmi *v1.VirtualMachineInstance

				BeforeEach(func() {
					vmi = setupVMI(virtClient, vmiWithBridgeBinding())
				})

				AfterEach(func() {
					cleanupVMI(virtClient, vmi)
				})

				It("should report PodIP as its own on interface status", func() { AssertReportedIP(vmi) })
			})
		})

		Context("VMI connected to the pod network using masquerade binding", func() {
			When("Guest Agent exists", func() {
				var vmi *v1.VirtualMachineInstance

				BeforeEach(func() {
					tmpVmi, err := newFedoraWithGuestAgentAndDefaultInterface(libvmi.InterfaceDeviceWithMasqueradeBinding())
					Expect(err).NotTo(HaveOccurred())

					tmpVmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(tmpVmi)
					Expect(err).NotTo(HaveOccurred())
					vmi = tests.WaitUntilVMIReady(tmpVmi, console.LoginToFedora)

					tests.WaitAgentConnected(virtClient, vmi)
				})

				AfterEach(func() {
					cleanupVMI(virtClient, vmi)
				})

				It("[test_id:4153]should report PodIP/s as its own on interface status", func() {
					vmiPod := tests.GetRunningPodByVirtualMachineInstance(vmi, vmi.Namespace)
					Consistently(func() error {
						vmi, err := virtClient.VirtualMachineInstance(vmi.Namespace).Get(vmi.Name, &metav1.GetOptions{})
						if err != nil {
							return err
						}
						return libnet.ValidateVMIandPodIPMatch(vmi, vmiPod)
					}, 5*time.Second, time.Second).Should(Succeed())
				})

			})

			When("no Guest Agent exists", func() {
				var vmi *v1.VirtualMachineInstance

				BeforeEach(func() {
					vmi = setupVMI(virtClient, vmiWithMasqueradeBinding())
				})

				AfterEach(func() {
					cleanupVMI(virtClient, vmi)
				})

				It("[Conformance] should report PodIP as its own on interface status", func() { AssertReportedIP(vmi) })
			})
		})
	})
})

func setupVMI(virtClient kubecli.KubevirtClient, vmi *v1.VirtualMachineInstance) *v1.VirtualMachineInstance {
	By("Creating the VMI")
	var err error
	vmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
	Expect(err).NotTo(HaveOccurred(), "VMI should be successfully created")

	By("Waiting until the VMI gets ready")
	vmi = tests.WaitUntilVMIReady(vmi, console.LoginToAlpine)

	return vmi
}

func cleanupVMI(virtClient kubecli.KubevirtClient, vmi *v1.VirtualMachineInstance) {
	if vmi != nil {
		By("Deleting the VMI")
		Expect(virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Delete(vmi.GetName(), &metav1.DeleteOptions{})).To(Succeed())

		By("Waiting for the VMI to be gone")
		Eventually(func() error {
			_, err := virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Get(vmi.GetName(), &metav1.GetOptions{})
			return err
		}, 2*time.Minute, time.Second).Should(SatisfyAll(HaveOccurred(), WithTransform(errors.IsNotFound, BeTrue())), "The VMI should be gone within the given timeout")
	}
}

func vmiWithDefaultBinding() *v1.VirtualMachineInstance {
	vmi := tests.NewRandomVMIWithEphemeralDisk(cd.ContainerDiskFor(cd.ContainerDiskAlpine))
	vmi.Spec.Domain.Devices.Interfaces = nil
	vmi.Spec.Networks = nil
	return vmi
}

func vmiWithBridgeBinding() *v1.VirtualMachineInstance {
	vmi := tests.NewRandomVMIWithEphemeralDisk(cd.ContainerDiskFor(cd.ContainerDiskAlpine))
	vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{*v1.DefaultBridgeNetworkInterface()}
	vmi.Spec.Networks = []v1.Network{*v1.DefaultPodNetwork()}
	return vmi
}

func vmiWithMasqueradeBinding() *v1.VirtualMachineInstance {
	vmi := tests.NewRandomVMIWithEphemeralDisk(cd.ContainerDiskFor(cd.ContainerDiskAlpine))
	vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{*v1.DefaultMasqueradeNetworkInterface()}
	vmi.Spec.Networks = []v1.Network{*v1.DefaultPodNetwork()}
	return vmi
}

func newFedoraWithGuestAgentAndDefaultInterface(iface v1.Interface) (*v1.VirtualMachineInstance, error) {
	networkData, err := libnet.CreateDefaultCloudInitNetworkData()
	if err != nil {
		return nil, err
	}

	vmi := libvmi.NewFedora(
		libvmi.WithInterface(iface),
		libvmi.WithNetwork(v1.DefaultPodNetwork()),
		libvmi.WithCloudInitNoCloudUserData(tests.GetGuestAgentUserData(), false),
		libvmi.WithCloudInitNoCloudNetworkData(networkData, false),
	)
	return vmi, nil
}

func removeCIDRs(ips []string) []string {
	ipAddresses := []string{}
	for _, ip := range ips {
		ipAddresses = append(ipAddresses, removeCIDR(ip))
	}
	return ipAddresses
}

func removeCIDR(ip string) string {
	ipAddr, _, err := net.ParseCIDR(ip)
	if err != nil {
		return ip
	}
	return ipAddr.String()
}
<|endoftext|>


<commit_before>package netlink

import (
	"fmt"
	"net"
	"strings"
)

// Scope is an enum representing a route scope.
type Scope uint8

type NextHopFlag int

type Destination interface {
	Family() int
	Decode([]byte) error
	Encode() ([]byte, error)
	String() string
}

type Encap interface {
	Type() int
	Decode([]byte) error
	Encode() ([]byte, error)
	String() string
}

// Route represents a netlink route.
type Route struct {
	LinkIndex  int
	ILinkIndex int
	Scope      Scope
	Dst        *net.IPNet
	Src        net.IP
	Gw         net.IP
	MultiPath  []*NexthopInfo
	Protocol   int
	Priority   int
	Table      int
	Type       int
	Tos        int
	Flags      int
	MPLSDst    *int
	NewDst     Destination
	Encap      Encap
}

func (r Route) String() string {
	elems := []string{}
	if len(r.MultiPath) == 0 {
		elems = append(elems, fmt.Sprintf("Ifindex: %d", r.LinkIndex))
	}
	if r.MPLSDst != nil {
		elems = append(elems, fmt.Sprintf("Dst: %d", r.MPLSDst))
	} else {
		elems = append(elems, fmt.Sprintf("Dst: %s", r.Dst))
	}
	if r.NewDst != nil {
		elems = append(elems, fmt.Sprintf("NewDst: %s", r.NewDst))
	}
	if r.Encap != nil {
		elems = append(elems, fmt.Sprintf("Encap: %s", r.Encap))
	}
	elems = append(elems, fmt.Sprintf("Src: %s", r.Src))
	if len(r.MultiPath) > 0 {
		elems = append(elems, fmt.Sprintf("Gw: %s", r.MultiPath))
	} else {
		elems = append(elems, fmt.Sprintf("Gw: %s", r.Gw))
	}
	elems = append(elems, fmt.Sprintf("Flags: %s", r.ListFlags()))
	elems = append(elems, fmt.Sprintf("Table: %d", r.Table))
	return fmt.Sprintf("{%s}", strings.Join(elems, " "))
}

func (r *Route) SetFlag(flag NextHopFlag) {
	r.Flags |= int(flag)
}

func (r *Route) ClearFlag(flag NextHopFlag) {
	r.Flags &^= int(flag)
}

type flagString struct {
	f NextHopFlag
	s string
}

// RouteUpdate is sent when a route changes - type is RTM_NEWROUTE or RTM_DELROUTE
type RouteUpdate struct {
	Type uint16
	Route
}

type NexthopInfo struct {
	LinkIndex int
	Hops      int
	Gw        net.IP
	Flags     int
	NewDst    Destination
	Encap     Encap
}

func (n *NexthopInfo) String() string {
	elems := []string{}
	elems = append(elems, fmt.Sprintf("Ifindex: %d", n.LinkIndex))
	if n.NewDst != nil {
		elems = append(elems, fmt.Sprintf("NewDst: %s", n.NewDst))
	}
	if n.Encap != nil {
		elems = append(elems, fmt.Sprintf("Encap: %s", n.Encap))
	}
	elems = append(elems, fmt.Sprintf("Weight: %d", n.Hops+1))
	elems = append(elems, fmt.Sprintf("Gw: %d", n.Gw))
	elems = append(elems, fmt.Sprintf("Flags: %s", n.ListFlags()))
	return fmt.Sprintf("{%s}", strings.Join(elems, " "))
}
<commit_msg>route: fix display of nexthop gateway for multipath routes<commit_after>package netlink

import (
	"fmt"
	"net"
	"strings"
)

// Scope is an enum representing a route scope.
type Scope uint8

type NextHopFlag int

type Destination interface {
	Family() int
	Decode([]byte) error
	Encode() ([]byte, error)
	String() string
}

type Encap interface {
	Type() int
	Decode([]byte) error
	Encode() ([]byte, error)
	String() string
}

// Route represents a netlink route.
type Route struct {
	LinkIndex  int
	ILinkIndex int
	Scope      Scope
	Dst        *net.IPNet
	Src        net.IP
	Gw         net.IP
	MultiPath  []*NexthopInfo
	Protocol   int
	Priority   int
	Table      int
	Type       int
	Tos        int
	Flags      int
	MPLSDst    *int
	NewDst     Destination
	Encap      Encap
}

func (r Route) String() string {
	elems := []string{}
	if len(r.MultiPath) == 0 {
		elems = append(elems, fmt.Sprintf("Ifindex: %d", r.LinkIndex))
	}
	if r.MPLSDst != nil {
		elems = append(elems, fmt.Sprintf("Dst: %d", r.MPLSDst))
	} else {
		elems = append(elems, fmt.Sprintf("Dst: %s", r.Dst))
	}
	if r.NewDst != nil {
		elems = append(elems, fmt.Sprintf("NewDst: %s", r.NewDst))
	}
	if r.Encap != nil {
		elems = append(elems, fmt.Sprintf("Encap: %s", r.Encap))
	}
	elems = append(elems, fmt.Sprintf("Src: %s", r.Src))
	if len(r.MultiPath) > 0 {
		elems = append(elems, fmt.Sprintf("Gw: %s", r.MultiPath))
	} else {
		elems = append(elems, fmt.Sprintf("Gw: %s", r.Gw))
	}
	elems = append(elems, fmt.Sprintf("Flags: %s", r.ListFlags()))
	elems = append(elems, fmt.Sprintf("Table: %d", r.Table))
	return fmt.Sprintf("{%s}", strings.Join(elems, " "))
}

func (r *Route) SetFlag(flag NextHopFlag) {
	r.Flags |= int(flag)
}

func (r *Route) ClearFlag(flag NextHopFlag) {
	r.Flags &^= int(flag)
}

type flagString struct {
	f NextHopFlag
	s string
}

// RouteUpdate is sent when a route changes - type is RTM_NEWROUTE or RTM_DELROUTE
type RouteUpdate struct {
	Type uint16
	Route
}

type NexthopInfo struct {
	LinkIndex int
	Hops      int
	Gw        net.IP
	Flags     int
	NewDst    Destination
	Encap     Encap
}

func (n *NexthopInfo) String() string {
	elems := []string{}
	elems = append(elems, fmt.Sprintf("Ifindex: %d", n.LinkIndex))
	if n.NewDst != nil {
		elems = append(elems, fmt.Sprintf("NewDst: %s", n.NewDst))
	}
	if n.Encap != nil {
		elems = append(elems, fmt.Sprintf("Encap: %s", n.Encap))
	}
	elems = append(elems, fmt.Sprintf("Weight: %d", n.Hops+1))
	elems = append(elems, fmt.Sprintf("Gw: %s", n.Gw))
	elems = append(elems, fmt.Sprintf("Flags: %s", n.ListFlags()))
	return fmt.Sprintf("{%s}", strings.Join(elems, " "))
}
<|endoftext|>