<commit_before>package responses

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"
)

// IonResponse represents the response structure expected back from the Ion
// Channel API calls
type IonResponse struct {
	Data   json.RawMessage `json:"data"`
	Meta   Meta            `json:"meta"`
	status int
}

// Meta represents the metadata section of an IonResponse
type Meta struct {
	TotalCount int        `json:"total_count"`
	Limit      int        `json:"limit,omitempty"`
	Offset     int        `json:"offset"`
	LastUpdate *time.Time `json:"last_update,omitempty"`
}

// NewResponse takes a data object, meta object, and desired status code to
// build a new response.  It returns a newly formed Ion Response object with
// default values applied to the response.
func NewResponse(data interface{}, meta Meta, status int) (*IonResponse, error) {
	b, err := json.Marshal(data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %v", err.Error())
	}

	return &IonResponse{Data: b, Meta: meta, status: status}, nil
}

// WriteResponse takes an http ResponseWriter and writes the Ion Response object
// to the writer. It encodes the object as marshalled JSON. If it encounters any
// errors while attempt to formulate the response, it will write an Ion Error
// Response to the writer instead.
func (ir *IonResponse) WriteResponse(w http.ResponseWriter) {
	b, err := json.Marshal(ir)
	if err != nil {
		errMsg := fmt.Sprintf("failed to marshal response to json: %v", err.Error())
		er := NewErrorResponse(errMsg, nil, http.StatusInternalServerError)
		er.WriteResponse(w)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(ir.status)
	fmt.Fprintln(w, string(b))
}

// IonErrorResponse represents an error response from the Ion Channel API
type IonErrorResponse struct {
	Message string      `json:"message"`
	Fields  ErrorFields `json:"fields,omitempty"`
	Code    int         `json:"code"`
}

// ErrorFields is a representation of field name and error message
type ErrorFields map[string]string

func (ef ErrorFields) String() string {
	var fields []string

	for k, v := range ef {
		fields = append(fields, fmt.Sprintf("%v: %v", k, v))
	}

	return fmt.Sprintf("[%v]", strings.Join(fields, ", "))
}

// NewErrorResponse takes a message, related fields, and desired status code to
// build a new error response.  It returns an error message encoded as a byte
// slice and a corresponding status code.  The status code returned will be the
// same as passed into the status parameter unless an error is encountered when
// marshalling the error response into JSON, which will then return an Internal
// Server Error status code.
func NewErrorResponse(message string, fields map[string]string, status int) *IonErrorResponse {
	return &IonErrorResponse{
		Message: message,
		Fields:  fields,
		Code:    status,
	}
}

// WriteResponse takes an http ResponseWriter and writes the Ion Error Response
// object to the writer. It encodes the object as marshalled JSON. If it
// encounters any errors while attempt to formulate the response, it will write
// a standard message and internal server error status to the writer.
func (er *IonErrorResponse) WriteResponse(w http.ResponseWriter) {
	b, err := json.Marshal(er)
	if err != nil {
		w.Header().Set("Content-Type", "text/plain")
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, "failed to marshal error response")
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(er.Code)
	fmt.Fprintln(w, string(b))
}
<commit_msg>adds duration to responses.meta<commit_after>package responses

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"
)

// IonResponse represents the response structure expected back from the Ion
// Channel API calls
type IonResponse struct {
	Data   json.RawMessage `json:"data"`
	Meta   Meta            `json:"meta"`
	status int
}

// Meta represents the metadata section of an IonResponse
type Meta struct {
	TotalCount int        `json:"total_count"`
	Limit      int        `json:"limit,omitempty"`
	Offset     int        `json:"offset"`
	LastUpdate *time.Time `json:"last_update,omitempty"`
	Duration   int64      `json:"duration_ms,omitempty"`
}

// NewResponse takes a data object, meta object, and desired status code to
// build a new response.  It returns a newly formed Ion Response object with
// default values applied to the response.
func NewResponse(data interface{}, meta Meta, status int) (*IonResponse, error) {
	b, err := json.Marshal(data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %v", err.Error())
	}

	return &IonResponse{Data: b, Meta: meta, status: status}, nil
}

// WriteResponse takes an http ResponseWriter and writes the Ion Response object
// to the writer. It encodes the object as marshalled JSON. If it encounters any
// errors while attempt to formulate the response, it will write an Ion Error
// Response to the writer instead.
func (ir *IonResponse) WriteResponse(w http.ResponseWriter) {
	b, err := json.Marshal(ir)
	if err != nil {
		errMsg := fmt.Sprintf("failed to marshal response to json: %v", err.Error())
		er := NewErrorResponse(errMsg, nil, http.StatusInternalServerError)
		er.WriteResponse(w)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(ir.status)
	fmt.Fprintln(w, string(b))
}

// IonErrorResponse represents an error response from the Ion Channel API
type IonErrorResponse struct {
	Message string      `json:"message"`
	Fields  ErrorFields `json:"fields,omitempty"`
	Code    int         `json:"code"`
}

// ErrorFields is a representation of field name and error message
type ErrorFields map[string]string

func (ef ErrorFields) String() string {
	var fields []string

	for k, v := range ef {
		fields = append(fields, fmt.Sprintf("%v: %v", k, v))
	}

	return fmt.Sprintf("[%v]", strings.Join(fields, ", "))
}

// NewErrorResponse takes a message, related fields, and desired status code to
// build a new error response.  It returns an error message encoded as a byte
// slice and a corresponding status code.  The status code returned will be the
// same as passed into the status parameter unless an error is encountered when
// marshalling the error response into JSON, which will then return an Internal
// Server Error status code.
func NewErrorResponse(message string, fields map[string]string, status int) *IonErrorResponse {
	return &IonErrorResponse{
		Message: message,
		Fields:  fields,
		Code:    status,
	}
}

// WriteResponse takes an http ResponseWriter and writes the Ion Error Response
// object to the writer. It encodes the object as marshalled JSON. If it
// encounters any errors while attempt to formulate the response, it will write
// a standard message and internal server error status to the writer.
func (er *IonErrorResponse) WriteResponse(w http.ResponseWriter) {
	b, err := json.Marshal(er)
	if err != nil {
		w.Header().Set("Content-Type", "text/plain")
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, "failed to marshal error response")
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(er.Code)
	fmt.Fprintln(w, string(b))
}
<|endoftext|>


<commit_before>//
// Copyright (c) 2016 Intel Corporation
//
// 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 main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"regexp"
	"strings"
	"text/tabwriter"
	"text/template"
)

type PackageInfo struct {
	Name   string   `json:"name"`
	Path   string   `json:"path"`
	Files  []string `json:"files"`
	XFiles []string `json:"xfiles"`
}

type TestInfo struct {
	Name           string
	Summary        string
	Description    string
	ExpectedResult string
	Pass           bool
	Result         string
	TimeTaken      string
}

type PackageTests struct {
	Name     string
	Coverage string
	Tests    []*TestInfo
}

type testResults struct {
	result    string
	timeTaken string
}

type colouredRow struct {
	ansiSeq string
	columns []string
}

const goListTemplate = `{
"name" : "{{.ImportPath}}",
"path" : "{{.Dir}}",
"files" : [ {{range $index, $elem := .TestGoFiles }}{{if $index}}, "{{$elem}}"{{else}}"{{$elem}}"{{end}}{{end}} ],
"xfiles" : [ {{range $index, $elem := .XTestGoFiles }}{{if $index}}, "{{$elem}}"{{else}}"{{$elem}}"{{end}}{{end}} ]
},
`

const htmlTemplate = `

<title>Test Cases</title>

{{range .Tests}}

{{.Name}}

Coverage: {{.Coverage}}

Name	Summary	Description	ExpectedResult	Result	Time Taken

{{range .Tests}}

{{.Name}}	{{.Summary}}	{{.Description}}	{{.ExpectedResult}}	{{.Result}}	{{.TimeTaken}}

{{end}}

{{end}}

`

var resultRegexp *regexp.Regexp
var coverageRegexp *regexp.Regexp

var cssPath string
var textOutput bool
var short bool
var tags string
var colour bool

func init() {
	flag.StringVar(&cssPath, "css", "", "Full path to CSS file")
	flag.BoolVar(&textOutput, "text", false, "Output text instead of HTML")
	flag.BoolVar(&short, "short", false, "If true -short is passed to go test")
	flag.StringVar(&tags, "tags", "", "Build tags to pass to go test")
	flag.BoolVar(&colour, "colour", true, "If true failed tests are coloured red in text mode")
	resultRegexp = regexp.MustCompile(`--- (FAIL|PASS): ([^\s]+) \(([^\)]+)\)`)
	coverageRegexp = regexp.MustCompile(`^coverage: ([^\s]+)`)
}

func parseCommentGroup(ti *TestInfo, comment string) {
	groups := regexp.MustCompile("\n\n").Split(comment, 4)
	fields := []*string{&ti.Summary, &ti.Description, &ti.ExpectedResult}
	for i, c := range groups {
		*fields[i] = c
	}
}

func isTestingFunc(decl *ast.FuncDecl) bool {
	if !strings.HasPrefix(decl.Name.String(), "Test") {
		return false
	}

	paramList := decl.Type.Params.List
	if len(paramList) != 1 {
		return false
	}

	recType, ok := paramList[0].Type.(*ast.StarExpr)
	if !ok {
		return false
	}

	pt, ok := recType.X.(*ast.SelectorExpr)
	if !ok {
		return false
	}

	id, ok := pt.X.(*ast.Ident)
	if !ok {
		return false
	}

	return id.Name == "testing" && pt.Sel.Name == "T"
}

func parseTestFile(filePath string) ([]*TestInfo, error) {
	tests := make([]*TestInfo, 0, 32)
	fs := token.NewFileSet()
	tr, err := parser.ParseFile(fs, filePath, nil, parser.ParseComments)
	if err != nil {
		return nil, err
	}

	for _, decl := range tr.Decls {
		if decl, ok := decl.(*ast.FuncDecl); ok {
			if !isTestingFunc(decl) {
				continue
			}

			ti := &TestInfo{Name: decl.Name.String()}
			tests = append(tests, ti)

			if decl.Doc == nil {
				continue
			}

			parseCommentGroup(ti, decl.Doc.Text())
		}
	}

	return tests, nil
}

func extractTests(packages []PackageInfo) []*PackageTests {
	pts := make([]*PackageTests, 0, len(packages))
	for _, p := range packages {
		if len(p.Files) == 0 || strings.Contains(p.Name, "/vendor/") {
			continue
		}
		packageTest := &PackageTests{
			Name: p.Name,
		}

		files := make([]string, 0, len(p.Files)+len(p.XFiles))
		files = append(files, p.Files...)
		files = append(files, p.XFiles...)
		for _, f := range files {
			filePath := path.Join(p.Path, f)
			ti, err := parseTestFile(filePath)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Failed to parse %s: %s\n",
					filePath, err)
				continue
			}
			packageTest.Tests = append(packageTest.Tests, ti...)
		}
		pts = append(pts, packageTest)
	}
	return pts
}

func findTestFiles(packs []string) ([]PackageInfo, error) {
	var output bytes.Buffer
	fmt.Fprintln(&output, "[")
	listArgs := []string{"list", "-f", goListTemplate}
	listArgs = append(listArgs, packs...)
	cmd := exec.Command("go", listArgs...)
	cmd.Stdout = &output
	err := cmd.Run()
	if err != nil {
		return nil, err
	}
	lastComma := bytes.LastIndex(output.Bytes(), []byte{','})
	if lastComma != -1 {
		output.Truncate(lastComma)
	}
	fmt.Fprintln(&output, "]")
	var testPackages []PackageInfo
	err = json.Unmarshal(output.Bytes(), &testPackages)
	if err != nil {
		return nil, err
	}
	return testPackages, nil
}

func runPackageTests(p *PackageTests) int {
	var output bytes.Buffer
	var coverage string

	exitCode := 0
	results := make(map[string]*testResults)
	args := []string{"test", p.Name, "-v", "-cover"}
	if short {
		args = append(args, "-short")
	}
	if tags != "" {
		args = append(args, "-tags", tags)
	}

	cmd := exec.Command("go", args...)
	cmd.Stdout = &output
	_ = cmd.Run()

	scanner := bufio.NewScanner(&output)
	for scanner.Scan() {
		line := scanner.Text()
		matches := resultRegexp.FindStringSubmatch(line)
		if matches != nil && len(matches) == 4 {
			results[matches[2]] = &testResults{matches[1], matches[3]}
			continue
		}

		if coverage == "" {
			matches := coverageRegexp.FindStringSubmatch(line)
			if matches == nil || len(matches) != 2 {
				continue
			}
			coverage = matches[1]
		}
	}

	for _, t := range p.Tests {
		res := results[t.Name]
		if res == nil {
			t.Result = "NOT RUN"
			t.TimeTaken = "N/A"
			exitCode = 1
		} else {
			t.Result = res.result
			t.Pass = res.result == "PASS"
			if !t.Pass {
				exitCode = 1
			}
			t.TimeTaken = res.timeTaken
		}
	}

	if coverage != "" {
		p.Coverage = coverage
	} else {
		p.Coverage = "Unknown"
	}

	return exitCode
}

func identifyPackages(packs []string) []string {
	if len(packs) == 0 {
		packs = []string{"."}
	} else if len(packs) > 1 {
		for _, p := range packs {
			if p == "./..." {
				packs = []string{p}
				break
			}
		}
	}
	return packs
}

func generateHTMLReport(tests []*PackageTests) error {
	var css string
	if cssPath != "" {
		cssBytes, err := ioutil.ReadFile(cssPath)
		if err != nil {
			log.Printf("Unable to read css file %s : %v",
				cssPath, err)
		} else {
			css = string(cssBytes)
		}
	}

	tmpl, err := template.New("tests").Parse(htmlTemplate)
	if err != nil {
		log.Fatalf("Unable to parse html template: %s\n", err)
	}

	return tmpl.Execute(os.Stdout, &struct {
		Tests []*PackageTests
		CSS   string
	}{
		tests,
		css,
	})
}

func findCommonPrefix(tests []*PackageTests) string {
	if len(tests) == 0 {
		return ""
	}

	pkgName := tests[0].Name
OUTER:
	for {
		index := strings.LastIndex(pkgName, "/")
		if index == -1 {
			return ""
		}
		pkgName := pkgName[:index+1]

		var i int
		for i = 1; i < len(tests); i++ {
			if !strings.HasPrefix(tests[i].Name, pkgName) {
				continue OUTER
			}
		}
		return pkgName
	}
}

func generateColourTextReport(tests []*PackageTests) {
	prefix := findCommonPrefix(tests)
	table := make([]colouredRow, 0, 128)
	table = append(table, colouredRow{
		"",
		[]string{"Package", "Test Case", "Time Taken", "Result"},
	})
	colWidth := []int{0, 0, 0, 0}
	for i := range colWidth {
		colWidth[i] = len(table[0].columns[i])
	}

	coloured := false
	for _, p := range tests {
		pkgName := p.Name[len(prefix):]
		for _, t := range p.Tests {
			row := colouredRow{}
			if !t.Pass {
				row.ansiSeq = fmt.Sprintf("%c[%dm", 0x1b, 31)
				coloured = true
			} else if t.Pass && coloured {
				coloured = false
				row.ansiSeq = fmt.Sprintf("%c[%dm", 0x1b, 0)
			}
			row.columns = []string{pkgName, t.Name, t.TimeTaken, t.Result}
			for i := range colWidth {
				if colWidth[i] < len(row.columns[i]) {
					colWidth[i] = len(row.columns[i])
				}
			}
			table = append(table, row)
		}
	}

	for _, row := range table {
		fmt.Printf("%s", row.ansiSeq)
		for i, col := range row.columns {
			fmt.Printf(col)
			fmt.Printf("%s", strings.Repeat(" ", colWidth[i]-len(col)))
			fmt.Printf(" ")
		}
		fmt.Println("")
	}

	if coloured {
		fmt.Printf("%c[%dm\n", 0x1b, 0)
	}
}

func generateTextReport(tests []*PackageTests) {
	prefix := findCommonPrefix(tests)
	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 1, ' ', 0)
	fmt.Fprintln(w, "Package\tTest Case\tTime Taken\tResult\t")
	for _, p := range tests {
		pkgName := p.Name[len(prefix):]
		for _, t := range p.Tests {
			fmt.Fprintf(w, "%s\t%s\t%s\t%s\t\n", pkgName,
				t.Name, t.TimeTaken, t.Result)
		}
	}
	_ = w.Flush()
	fmt.Println()
}

func main() {

	flag.Parse()

	packs := identifyPackages(flag.Args())

	packages, err := findTestFiles(packs)
	if err != nil {
		log.Fatalf("Unable to discover test files: %s", err)
	}

	tests := extractTests(packages)
	exitCode := 0
	for _, p := range tests {
		exitCode = exitCode | runPackageTests(p)
	}

	if textOutput {
		if colour {
			generateColourTextReport(tests)
		} else {
			generateTextReport(tests)
		}
	} else {
		err = generateHTMLReport(tests)
	}

	if err != nil {
		log.Fatalf("Unable to generate report: %s\n", err)
	}

	os.Exit(exitCode)
}
<commit_msg>test-cases: Fix golint errors<commit_after>//
// Copyright (c) 2016 Intel Corporation
//
// 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 main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"regexp"
	"strings"
	"text/tabwriter"
	"text/template"
)

// PackageInfo contains information about a package under test.
type PackageInfo struct {
	// Name is the name of the package.
	Name string `json:"name"`

	// Path is the import path of the package.
	Path string `json:"path"`

	// File contains a list of all the test files associated with a package.
	Files []string `json:"files"`

	// XFiles contains a list of all the external test files associated with
	// a package.
	XFiles []string `json:"xfiles"`
}

// TestInfo contains information about an individual executed test case.
type TestInfo struct {
	// Name is the name of the test case.
	Name string

	// Summary provides a brief one line description of the test case.
	Summary string

	// Description contains a detailed description of the test case.
	Description string

	// ExpectedResult outlines the success criteria for the test case.
	ExpectedResult string

	// Pass indicates whether the test case passed or not.
	Pass bool

	// Result provides the result of the test case.
	Result string

	// TimeTaken is a description of the time taken to run the test case.
	TimeTaken string
}

// PackageTests contains information about the tests that have been executed for
// an individual package
type PackageTests struct {
	// Name is the name of the package.
	Name string

	// Coverage is the amount of package code covered by the test cases.
	Coverage string

	// Tests is an array containing information about the specific test cases.
	Tests []*TestInfo
}

type testResults struct {
	result    string
	timeTaken string
}

type colouredRow struct {
	ansiSeq string
	columns []string
}

const goListTemplate = `{
"name" : "{{.ImportPath}}",
"path" : "{{.Dir}}",
"files" : [ {{range $index, $elem := .TestGoFiles }}{{if $index}}, "{{$elem}}"{{else}}"{{$elem}}"{{end}}{{end}} ],
"xfiles" : [ {{range $index, $elem := .XTestGoFiles }}{{if $index}}, "{{$elem}}"{{else}}"{{$elem}}"{{end}}{{end}} ]
},
`

const htmlTemplate = `

<title>Test Cases</title>

{{range .Tests}}

{{.Name}}

Coverage: {{.Coverage}}

Name	Summary	Description	ExpectedResult	Result	Time Taken

{{range .Tests}}

{{.Name}}	{{.Summary}}	{{.Description}}	{{.ExpectedResult}}	{{.Result}}	{{.TimeTaken}}

{{end}}

{{end}}

`

var resultRegexp *regexp.Regexp
var coverageRegexp *regexp.Regexp

var cssPath string
var textOutput bool
var short bool
var tags string
var colour bool

func init() {
	flag.StringVar(&cssPath, "css", "", "Full path to CSS file")
	flag.BoolVar(&textOutput, "text", false, "Output text instead of HTML")
	flag.BoolVar(&short, "short", false, "If true -short is passed to go test")
	flag.StringVar(&tags, "tags", "", "Build tags to pass to go test")
	flag.BoolVar(&colour, "colour", true, "If true failed tests are coloured red in text mode")
	resultRegexp = regexp.MustCompile(`--- (FAIL|PASS): ([^\s]+) \(([^\)]+)\)`)
	coverageRegexp = regexp.MustCompile(`^coverage: ([^\s]+)`)
}

func parseCommentGroup(ti *TestInfo, comment string) {
	groups := regexp.MustCompile("\n\n").Split(comment, 4)
	fields := []*string{&ti.Summary, &ti.Description, &ti.ExpectedResult}
	for i, c := range groups {
		*fields[i] = c
	}
}

func isTestingFunc(decl *ast.FuncDecl) bool {
	if !strings.HasPrefix(decl.Name.String(), "Test") {
		return false
	}

	paramList := decl.Type.Params.List
	if len(paramList) != 1 {
		return false
	}

	recType, ok := paramList[0].Type.(*ast.StarExpr)
	if !ok {
		return false
	}

	pt, ok := recType.X.(*ast.SelectorExpr)
	if !ok {
		return false
	}

	id, ok := pt.X.(*ast.Ident)
	if !ok {
		return false
	}

	return id.Name == "testing" && pt.Sel.Name == "T"
}

func parseTestFile(filePath string) ([]*TestInfo, error) {
	tests := make([]*TestInfo, 0, 32)
	fs := token.NewFileSet()
	tr, err := parser.ParseFile(fs, filePath, nil, parser.ParseComments)
	if err != nil {
		return nil, err
	}

	for _, decl := range tr.Decls {
		if decl, ok := decl.(*ast.FuncDecl); ok {
			if !isTestingFunc(decl) {
				continue
			}

			ti := &TestInfo{Name: decl.Name.String()}
			tests = append(tests, ti)

			if decl.Doc == nil {
				continue
			}

			parseCommentGroup(ti, decl.Doc.Text())
		}
	}

	return tests, nil
}

func extractTests(packages []PackageInfo) []*PackageTests {
	pts := make([]*PackageTests, 0, len(packages))
	for _, p := range packages {
		if len(p.Files) == 0 || strings.Contains(p.Name, "/vendor/") {
			continue
		}
		packageTest := &PackageTests{
			Name: p.Name,
		}

		files := make([]string, 0, len(p.Files)+len(p.XFiles))
		files = append(files, p.Files...)
		files = append(files, p.XFiles...)
		for _, f := range files {
			filePath := path.Join(p.Path, f)
			ti, err := parseTestFile(filePath)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Failed to parse %s: %s\n",
					filePath, err)
				continue
			}
			packageTest.Tests = append(packageTest.Tests, ti...)
		}
		pts = append(pts, packageTest)
	}
	return pts
}

func findTestFiles(packs []string) ([]PackageInfo, error) {
	var output bytes.Buffer
	fmt.Fprintln(&output, "[")
	listArgs := []string{"list", "-f", goListTemplate}
	listArgs = append(listArgs, packs...)
	cmd := exec.Command("go", listArgs...)
	cmd.Stdout = &output
	err := cmd.Run()
	if err != nil {
		return nil, err
	}
	lastComma := bytes.LastIndex(output.Bytes(), []byte{','})
	if lastComma != -1 {
		output.Truncate(lastComma)
	}
	fmt.Fprintln(&output, "]")
	var testPackages []PackageInfo
	err = json.Unmarshal(output.Bytes(), &testPackages)
	if err != nil {
		return nil, err
	}
	return testPackages, nil
}

func runPackageTests(p *PackageTests) int {
	var output bytes.Buffer
	var coverage string

	exitCode := 0
	results := make(map[string]*testResults)
	args := []string{"test", p.Name, "-v", "-cover"}
	if short {
		args = append(args, "-short")
	}
	if tags != "" {
		args = append(args, "-tags", tags)
	}

	cmd := exec.Command("go", args...)
	cmd.Stdout = &output
	_ = cmd.Run()

	scanner := bufio.NewScanner(&output)
	for scanner.Scan() {
		line := scanner.Text()
		matches := resultRegexp.FindStringSubmatch(line)
		if matches != nil && len(matches) == 4 {
			results[matches[2]] = &testResults{matches[1], matches[3]}
			continue
		}

		if coverage == "" {
			matches := coverageRegexp.FindStringSubmatch(line)
			if matches == nil || len(matches) != 2 {
				continue
			}
			coverage = matches[1]
		}
	}

	for _, t := range p.Tests {
		res := results[t.Name]
		if res == nil {
			t.Result = "NOT RUN"
			t.TimeTaken = "N/A"
			exitCode = 1
		} else {
			t.Result = res.result
			t.Pass = res.result == "PASS"
			if !t.Pass {
				exitCode = 1
			}
			t.TimeTaken = res.timeTaken
		}
	}

	if coverage != "" {
		p.Coverage = coverage
	} else {
		p.Coverage = "Unknown"
	}

	return exitCode
}

func identifyPackages(packs []string) []string {
	if len(packs) == 0 {
		packs = []string{"."}
	} else if len(packs) > 1 {
		for _, p := range packs {
			if p == "./..." {
				packs = []string{p}
				break
			}
		}
	}
	return packs
}

func generateHTMLReport(tests []*PackageTests) error {
	var css string
	if cssPath != "" {
		cssBytes, err := ioutil.ReadFile(cssPath)
		if err != nil {
			log.Printf("Unable to read css file %s : %v",
				cssPath, err)
		} else {
			css = string(cssBytes)
		}
	}

	tmpl, err := template.New("tests").Parse(htmlTemplate)
	if err != nil {
		log.Fatalf("Unable to parse html template: %s\n", err)
	}

	return tmpl.Execute(os.Stdout, &struct {
		Tests []*PackageTests
		CSS   string
	}{
		tests,
		css,
	})
}

func findCommonPrefix(tests []*PackageTests) string {
	if len(tests) == 0 {
		return ""
	}

	pkgName := tests[0].Name
OUTER:
	for {
		index := strings.LastIndex(pkgName, "/")
		if index == -1 {
			return ""
		}
		pkgName := pkgName[:index+1]

		var i int
		for i = 1; i < len(tests); i++ {
			if !strings.HasPrefix(tests[i].Name, pkgName) {
				continue OUTER
			}
		}
		return pkgName
	}
}

func generateColourTextReport(tests []*PackageTests) {
	prefix := findCommonPrefix(tests)
	table := make([]colouredRow, 0, 128)
	table = append(table, colouredRow{
		"",
		[]string{"Package", "Test Case", "Time Taken", "Result"},
	})
	colWidth := []int{0, 0, 0, 0}
	for i := range colWidth {
		colWidth[i] = len(table[0].columns[i])
	}

	coloured := false
	for _, p := range tests {
		pkgName := p.Name[len(prefix):]
		for _, t := range p.Tests {
			row := colouredRow{}
			if !t.Pass {
				row.ansiSeq = fmt.Sprintf("%c[%dm", 0x1b, 31)
				coloured = true
			} else if t.Pass && coloured {
				coloured = false
				row.ansiSeq = fmt.Sprintf("%c[%dm", 0x1b, 0)
			}
			row.columns = []string{pkgName, t.Name, t.TimeTaken, t.Result}
			for i := range colWidth {
				if colWidth[i] < len(row.columns[i]) {
					colWidth[i] = len(row.columns[i])
				}
			}
			table = append(table, row)
		}
	}

	for _, row := range table {
		fmt.Printf("%s", row.ansiSeq)
		for i, col := range row.columns {
			fmt.Printf(col)
			fmt.Printf("%s", strings.Repeat(" ", colWidth[i]-len(col)))
			fmt.Printf(" ")
		}
		fmt.Println("")
	}

	if coloured {
		fmt.Printf("%c[%dm\n", 0x1b, 0)
	}
}

func generateTextReport(tests []*PackageTests) {
	prefix := findCommonPrefix(tests)
	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 1, ' ', 0)
	fmt.Fprintln(w, "Package\tTest Case\tTime Taken\tResult\t")
	for _, p := range tests {
		pkgName := p.Name[len(prefix):]
		for _, t := range p.Tests {
			fmt.Fprintf(w, "%s\t%s\t%s\t%s\t\n", pkgName,
				t.Name, t.TimeTaken, t.Result)
		}
	}
	_ = w.Flush()
	fmt.Println()
}

func main() {

	flag.Parse()

	packs := identifyPackages(flag.Args())

	packages, err := findTestFiles(packs)
	if err != nil {
		log.Fatalf("Unable to discover test files: %s", err)
	}

	tests := extractTests(packages)
	exitCode := 0
	for _, p := range tests {
		exitCode = exitCode | runPackageTests(p)
	}

	if textOutput {
		if colour {
			generateColourTextReport(tests)
		} else {
			generateTextReport(tests)
		}
	} else {
		err = generateHTMLReport(tests)
	}

	if err != nil {
		log.Fatalf("Unable to generate report: %s\n", err)
	}

	os.Exit(exitCode)
}
<|endoftext|>


<commit_before>package api

import (
	"gopkg.in/macaron.v1"
)

type PlansResp struct {
	Resp
	Plans []*Plan
}

type Plan struct {
	ID string
	Type int
	Cycle int
	Name string
	Currency string
	Amount int
	MaxDomains int
	MaxAddresses int
	MaxSpace int
	MaxMembers int
	TwoFactor int
}

func (api *Api) GetPlans(ctx *macaron.Context) {
	ctx.JSON(200, &PlansResp{
		Resp: Resp{Ok},
		Plans: []*Plan{},
	})
}

func (api *Api) GetSubscription(ctx *macaron.Context) {
	ctx.JSON(200, &ErrorResp{
		Resp: Resp{22110},
		Error: "You do not have an active subscription",
	})
}

type PaymentMethodsResp struct {
	Resp
	PaymentMethods []interface{} // TODO
}

func (api *Api) GetPaymentMethods(ctx *macaron.Context) {
	ctx.JSON(200, &PaymentMethodsResp{
		Resp: Resp{Ok},
		PaymentMethods: []interface{}{},
	})
}<commit_msg>Upgrades /payments/subscription route<commit_after>package api

import (
	"gopkg.in/macaron.v1"
)

type PlansResp struct {
	Resp
	Plans []*Plan
}

type Plan struct {
	ID string
	Type int
	Cycle int
	Name string
	Currency string
	Amount int
	MaxDomains int
	MaxAddresses int
	MaxSpace int
	MaxMembers int
	TwoFactor int
}

type SubscriptionResp struct {
	Resp
	Subscription *Subscription
}

type Subscription struct {
	ID string
	InvoiceID string
	Cycle int
	PeriodStart int64
	PeriodEnd int64
	CouponCode string
	Currency string
	Amount int
	Plans []*Plan
}

func (api *Api) GetPlans(ctx *macaron.Context) {
	ctx.JSON(200, &PlansResp{
		Resp: Resp{Ok},
		Plans: []*Plan{},
	})
}

func (api *Api) GetSubscription(ctx *macaron.Context) {
	ctx.JSON(200, &SubscriptionResp{
		Resp: Resp{Ok},
		Subscription: &Subscription{
			ID: "0",
			InvoiceID: "0",
			Plans: []*Plan{},
		},
	})
}

type PaymentMethodsResp struct {
	Resp
	PaymentMethods []interface{} // TODO
}

func (api *Api) GetPaymentMethods(ctx *macaron.Context) {
	ctx.JSON(200, &PaymentMethodsResp{
		Resp: Resp{Ok},
		PaymentMethods: []interface{}{},
	})
}
<|endoftext|>


<commit_before>// See LICENSE.txt for licensing information.

// Package piratebay implements a robust and comprehensive interface to PirateBay.
package piratebay

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"time"
)

const (
	VERSION = "0.0.1" // current version of the library
)

// Category represents a fully qualified PirateBay's category,
// e.g. "HD - TV shows". Categories are scraped.
type Category struct {
	Group string
	Title string
	ID    string
}

// Ordering represents a PirateBay's ordering. Orderings are scraped.
type Ordering struct {
	Title string
	ID    string
}

// Torrent represents a torrent and all its data that were possible to scrape.
type Torrent struct {
	Site
	Category
	ID       string
	Title    string
	Magnet   string
	Uploaded time.Time
	User     string
	VIPUser  bool
	SizeStr  string
	SizeInt  int64
	Seeders  int
	Leechers int
	Files    []*File

	detailed bool
}

// File represents a torrent's file. For convenience size is kept as both
// a int64 and as the scraped string.
type File struct {
	Path    string
	SizeStr string
	SizeInt int64
}

// Site gathers together all the information needed to interact with PirateBay.
// You may have several of this with different settings, each can then be used
// in parallel.
type Site struct {
	RootURI        string
	InfraURI       string
	SearchURI      string
	InfoURI        string
	FilesURI       string
	CategoryREGEXP *regexp.Regexp
	OrderingREGEXP *regexp.Regexp
	SearchREGEXP   *regexp.Regexp
	InfoREGEXP     *regexp.Regexp
	FilesREGEXP    *regexp.Regexp
	Categories     map[string]map[string]string
	Orderings      map[string]string
	Client         *http.Client
	Logger         *log.Logger

	infraData string
}

// String returns a pretty string representation of a Category.
func (c *Category) String() string {
	return fmt.Sprintf("%s/%s", c.Group, c.Title)
}

// String returns a pretty string representing an Ordering.
func (o *Ordering) String() string {
	return fmt.Sprintf("%s", o.Title)
}

// String returns a pretty string representation of a Torrent.
func (t *Torrent) String() string {
	return fmt.Sprintf("%s (%s)", t.Title, t.ID)
}

// String returns a pretty string representation of a File.
func (f *File) String() string {
	return fmt.Sprintf("%s", f.Path)
}

// String returns a pretty string representation of a Site.
func (s *Site) String() string {
	return fmt.Sprintf("%s", s.RootURI)
}

// InfoURI returns a string containing a URI to PirateBay's page with
// the details of the given Torrent.
func (t *Torrent) InfoURI() string {
	return t.Site.RootURI + fmt.Sprintf(t.Site.InfoURI, t.ID)
}

// GetDetails updates the Torrent data with additional information
// available only by scraping of the Torrent's details page.
func (t *Torrent) GetDetails() error {
	if t.detailed {
		t.Site.Logger.Println("Torrent already had details")
		return nil
	}
	data, err := t.Site.makeRequest(t.Site.RootURI + fmt.Sprintf(t.Site.InfoURI, t.ID))
	if err != nil {
		return err
	}
	t.parseDetails(data)
	return nil
}

// GetFiles updates the given Torrent slice of Files, by scraping the file list
// page.
func (t *Torrent) GetFiles() error {
	if len(t.Files) > 0 {
		t.Site.Logger.Println("Torrent already had files")
		return nil
	}
	data, err := t.Site.makeRequest(t.Site.RootURI + fmt.Sprintf(t.Site.FilesURI, t.ID))
	if err != nil {
		return err
	}
	return t.parseFiles(data)
}

// NewSite returns a Site with default settings.
func NewSite() *Site {
	return &Site{
		RootURI:        ROOTURI,
		InfraURI:       INFRAURI,
		SearchURI:      SEARCHURI,
		InfoURI:        INFOURI,
		FilesURI:       FILESURI,
		CategoryREGEXP: regexp.MustCompile(CATEGORYREGEXP),
		OrderingREGEXP: regexp.MustCompile(ORDERINGREGEXP),
		SearchREGEXP:   regexp.MustCompile(SEARCHREGEXP),
		InfoREGEXP:     regexp.MustCompile(INFOREGEXP),
		FilesREGEXP:    regexp.MustCompile(FILESREGEXP),
		Categories:     nil,
		Orderings:      nil,
		Client:         &http.Client{},
		Logger:         log.New(os.Stderr, "DEBUG: ", log.Lshortfile),
	}
}

// makeRequest makes a HTTP request using Site configuration,
// and returns response body on success.
func (s *Site) makeRequest(uri string) (string, error) {
	s.Logger.Printf("Making request for %s", uri)
	res, err := s.Client.Get(uri)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		return "", fmt.Errorf("Unsuccessful request for '%s': %d", uri, res.StatusCode)
	}
	data, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

// getInfraData fetches 'infrastructure' data, such as possible orderings and
// available categories.
func (s *Site) getInfraData() (string, error) {
	if s.infraData != "" {
		s.Logger.Println("Using cached infraData")
		return s.infraData, nil
	}
	data, err := s.makeRequest(s.RootURI + s.InfraURI)
	if err != nil {
		return "", nil
	}
	s.infraData = data
	return data, nil
}

// UpdateCategories updates available Categories.
func (s *Site) UpdateCategories() error {
	data, err := s.getInfraData()
	if err != nil {
		return err
	}
	s.parseCategories(data)
	return nil
}

// UpdateOrderings updates available Orderings.
func (s *Site) UpdateOrderings() error {
	data, err := s.getInfraData()
	if err != nil {
		return err
	}
	s.parseOrderings(data)
	return nil
}

// FindCategory returns the best matching PirateBay's Category for given group
// and category strings.
func (s *Site) FindCategory(group string, category string) (*Category, error) {
	if s.Categories == nil {
		return nil, fmt.Errorf("Categories not loaded")
	}
	if category == "" {
		return nil, fmt.Errorf("Category not specified")
	}
	if group != "" {
		categories, present := s.Categories[group]
		if !present {
			return nil, fmt.Errorf("Category group '%s' not found", group)
		}
		value, present := categories[category]
		if !present {
			return nil, fmt.Errorf("Category '%s/%s' not found", group, category)
		}
		return &Category{
			Group: group,
			Title: category,
			ID:    value,
		}, nil
	}
	var foundCat *Category
	for group, categories := range s.Categories {
		for cat, value := range categories {
			if cat == category {
				if foundCat != nil {
					return nil, fmt.Errorf("Category '%s' is ambiguous, please specify group", category)
				}
				foundCat = &Category{
					Group: group,
					Title: category,
					ID:    value,
				}
			}
		}
	}
	if foundCat == nil {
		return nil, fmt.Errorf("Category '%s' not found", category)
	}
	return foundCat, nil
}

// FindOrderings returns the best matching PirateBay's Ordering for given
// ordering string.
func (s *Site) FindOrdering(ordering string) (*Ordering, error) {
	if s.Orderings == nil {
		return nil, fmt.Errorf("Orderings not loaded")
	}
	if ordering == "" {
		return nil, fmt.Errorf("Ordering not specified")
	}
	value, present := s.Orderings[ordering]
	if !present {
		return nil, fmt.Errorf("Ordering '%s' not found", ordering)
	}
	return &Ordering{
		Title: ordering,
		ID:    value,
	}, nil
}

// Search executes a search query.
func (s *Site) Search(query string, c *Category, o *Ordering) ([]*Torrent, error) {
	var torrents []*Torrent
	data, err := s.makeRequest(s.RootURI + fmt.Sprintf(s.SearchURI, query, o.ID, c.ID))
	if err != nil {
		return torrents, err
	}
	return s.parseSearch(data), nil
}
<commit_msg>Version bump<commit_after>// See LICENSE.txt for licensing information.

// Package piratebay implements a robust and comprehensive interface to PirateBay.
package piratebay

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"time"
)

const (
	VERSION = "0.0.2" // current version of the library
)

// Category represents a fully qualified PirateBay's category,
// e.g. "HD - TV shows". Categories are scraped.
type Category struct {
	Group string
	Title string
	ID    string
}

// Ordering represents a PirateBay's ordering. Orderings are scraped.
type Ordering struct {
	Title string
	ID    string
}

// Torrent represents a torrent and all its data that were possible to scrape.
type Torrent struct {
	Site
	Category
	ID       string
	Title    string
	Magnet   string
	Uploaded time.Time
	User     string
	VIPUser  bool
	SizeStr  string
	SizeInt  int64
	Seeders  int
	Leechers int
	Files    []*File

	detailed bool
}

// File represents a torrent's file. For convenience size is kept as both
// a int64 and as the scraped string.
type File struct {
	Path    string
	SizeStr string
	SizeInt int64
}

// Site gathers together all the information needed to interact with PirateBay.
// You may have several of this with different settings, each can then be used
// in parallel.
type Site struct {
	RootURI        string
	InfraURI       string
	SearchURI      string
	InfoURI        string
	FilesURI       string
	CategoryREGEXP *regexp.Regexp
	OrderingREGEXP *regexp.Regexp
	SearchREGEXP   *regexp.Regexp
	InfoREGEXP     *regexp.Regexp
	FilesREGEXP    *regexp.Regexp
	Categories     map[string]map[string]string
	Orderings      map[string]string
	Client         *http.Client
	Logger         *log.Logger

	infraData string
}

// String returns a pretty string representation of a Category.
func (c *Category) String() string {
	return fmt.Sprintf("%s/%s", c.Group, c.Title)
}

// String returns a pretty string representing an Ordering.
func (o *Ordering) String() string {
	return fmt.Sprintf("%s", o.Title)
}

// String returns a pretty string representation of a Torrent.
func (t *Torrent) String() string {
	return fmt.Sprintf("%s (%s)", t.Title, t.ID)
}

// String returns a pretty string representation of a File.
func (f *File) String() string {
	return fmt.Sprintf("%s", f.Path)
}

// String returns a pretty string representation of a Site.
func (s *Site) String() string {
	return fmt.Sprintf("%s", s.RootURI)
}

// InfoURI returns a string containing a URI to PirateBay's page with
// the details of the given Torrent.
func (t *Torrent) InfoURI() string {
	return t.Site.RootURI + fmt.Sprintf(t.Site.InfoURI, t.ID)
}

// GetDetails updates the Torrent data with additional information
// available only by scraping of the Torrent's details page.
func (t *Torrent) GetDetails() error {
	if t.detailed {
		t.Site.Logger.Println("Torrent already had details")
		return nil
	}
	data, err := t.Site.makeRequest(t.Site.RootURI + fmt.Sprintf(t.Site.InfoURI, t.ID))
	if err != nil {
		return err
	}
	t.parseDetails(data)
	return nil
}

// GetFiles updates the given Torrent slice of Files, by scraping the file list
// page.
func (t *Torrent) GetFiles() error {
	if len(t.Files) > 0 {
		t.Site.Logger.Println("Torrent already had files")
		return nil
	}
	data, err := t.Site.makeRequest(t.Site.RootURI + fmt.Sprintf(t.Site.FilesURI, t.ID))
	if err != nil {
		return err
	}
	return t.parseFiles(data)
}

// NewSite returns a Site with default settings.
func NewSite() *Site {
	return &Site{
		RootURI:        ROOTURI,
		InfraURI:       INFRAURI,
		SearchURI:      SEARCHURI,
		InfoURI:        INFOURI,
		FilesURI:       FILESURI,
		CategoryREGEXP: regexp.MustCompile(CATEGORYREGEXP),
		OrderingREGEXP: regexp.MustCompile(ORDERINGREGEXP),
		SearchREGEXP:   regexp.MustCompile(SEARCHREGEXP),
		InfoREGEXP:     regexp.MustCompile(INFOREGEXP),
		FilesREGEXP:    regexp.MustCompile(FILESREGEXP),
		Categories:     nil,
		Orderings:      nil,
		Client:         &http.Client{},
		Logger:         log.New(os.Stderr, "DEBUG: ", log.Lshortfile),
	}
}

// makeRequest makes a HTTP request using Site configuration,
// and returns response body on success.
func (s *Site) makeRequest(uri string) (string, error) {
	s.Logger.Printf("Making request for %s", uri)
	res, err := s.Client.Get(uri)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		return "", fmt.Errorf("Unsuccessful request for '%s': %d", uri, res.StatusCode)
	}
	data, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

// getInfraData fetches 'infrastructure' data, such as possible orderings and
// available categories.
func (s *Site) getInfraData() (string, error) {
	if s.infraData != "" {
		s.Logger.Println("Using cached infraData")
		return s.infraData, nil
	}
	data, err := s.makeRequest(s.RootURI + s.InfraURI)
	if err != nil {
		return "", nil
	}
	s.infraData = data
	return data, nil
}

// UpdateCategories updates available Categories.
func (s *Site) UpdateCategories() error {
	data, err := s.getInfraData()
	if err != nil {
		return err
	}
	s.parseCategories(data)
	return nil
}

// UpdateOrderings updates available Orderings.
func (s *Site) UpdateOrderings() error {
	data, err := s.getInfraData()
	if err != nil {
		return err
	}
	s.parseOrderings(data)
	return nil
}

// FindCategory returns the best matching PirateBay's Category for given group
// and category strings.
func (s *Site) FindCategory(group string, category string) (*Category, error) {
	if s.Categories == nil {
		return nil, fmt.Errorf("Categories not loaded")
	}
	if category == "" {
		return nil, fmt.Errorf("Category not specified")
	}
	if group != "" {
		categories, present := s.Categories[group]
		if !present {
			return nil, fmt.Errorf("Category group '%s' not found", group)
		}
		value, present := categories[category]
		if !present {
			return nil, fmt.Errorf("Category '%s/%s' not found", group, category)
		}
		return &Category{
			Group: group,
			Title: category,
			ID:    value,
		}, nil
	}
	var foundCat *Category
	for group, categories := range s.Categories {
		for cat, value := range categories {
			if cat == category {
				if foundCat != nil {
					return nil, fmt.Errorf("Category '%s' is ambiguous, please specify group", category)
				}
				foundCat = &Category{
					Group: group,
					Title: category,
					ID:    value,
				}
			}
		}
	}
	if foundCat == nil {
		return nil, fmt.Errorf("Category '%s' not found", category)
	}
	return foundCat, nil
}

// FindOrderings returns the best matching PirateBay's Ordering for given
// ordering string.
func (s *Site) FindOrdering(ordering string) (*Ordering, error) {
	if s.Orderings == nil {
		return nil, fmt.Errorf("Orderings not loaded")
	}
	if ordering == "" {
		return nil, fmt.Errorf("Ordering not specified")
	}
	value, present := s.Orderings[ordering]
	if !present {
		return nil, fmt.Errorf("Ordering '%s' not found", ordering)
	}
	return &Ordering{
		Title: ordering,
		ID:    value,
	}, nil
}

// Search executes a search query.
func (s *Site) Search(query string, c *Category, o *Ordering) ([]*Torrent, error) {
	var torrents []*Torrent
	data, err := s.makeRequest(s.RootURI + fmt.Sprintf(s.SearchURI, query, o.ID, c.ID))
	if err != nil {
		return torrents, err
	}
	return s.parseSearch(data), nil
}
<|endoftext|>


<commit_before>package route

import (
	"encoding/binary"
	"fmt"

	"github.com/btcsuite/btcd/btcec"
	sphinx "github.com/lightningnetwork/lightning-onion"
	"github.com/lightningnetwork/lnd/lnwire"
)

// ErrNoRouteHopsProvided is returned when a caller attempts to construct a new
// sphinx packet, but provides an empty set of hops for each route.
var ErrNoRouteHopsProvided = fmt.Errorf("empty route hops provided")

// Vertex is a simple alias for the serialization of a compressed Bitcoin
// public key.
type Vertex [33]byte

// NewVertex returns a new Vertex given a public key.
func NewVertex(pub *btcec.PublicKey) Vertex {
	var v Vertex
	copy(v[:], pub.SerializeCompressed())
	return v
}

// String returns a human readable version of the Vertex which is the
// hex-encoding of the serialized compressed public key.
func (v Vertex) String() string {
	return fmt.Sprintf("%x", v[:])
}

// Hop represents an intermediate or final node of the route. This naming
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
// The struct houses the channel along which this hop can be reached and
// the values necessary to create the HTLC that needs to be sent to the
// next hop. It is also used to encode the per-hop payload included within
// the Sphinx packet.
type Hop struct {
	// PubKeyBytes is the raw bytes of the public key of the target node.
	PubKeyBytes Vertex

	// ChannelID is the unique channel ID for the channel. The first 3
	// bytes are the block height, the next 3 the index within the block,
	// and the last 2 bytes are the output index for the channel.
	ChannelID uint64

	// OutgoingTimeLock is the timelock value that should be used when
	// crafting the _outgoing_ HTLC from this hop.
	OutgoingTimeLock uint32

	// AmtToForward is the amount that this hop will forward to the next
	// hop. This value is less than the value that the incoming HTLC
	// carries as a fee will be subtracted by the hop.
	AmtToForward lnwire.MilliSatoshi
}

// Route represents a path through the channel graph which runs over one or
// more channels in succession. This struct carries all the information
// required to craft the Sphinx onion packet, and send the payment along the
// first hop in the path. A route is only selected as valid if all the channels
// have sufficient capacity to carry the initial payment amount after fees are
// accounted for.
type Route struct {
	// TotalTimeLock is the cumulative (final) time lock across the entire
	// route. This is the CLTV value that should be extended to the first
	// hop in the route. All other hops will decrement the time-lock as
	// advertised, leaving enough time for all hops to wait for or present
	// the payment preimage to complete the payment.
	TotalTimeLock uint32

	// TotalAmount is the total amount of funds required to complete a
	// payment over this route. This value includes the cumulative fees at
	// each hop. As a result, the HTLC extended to the first-hop in the
	// route will need to have at least this many satoshis, otherwise the
	// route will fail at an intermediate node due to an insufficient
	// amount of fees.
	TotalAmount lnwire.MilliSatoshi

	// SourcePubKey is the pubkey of the node where this route originates
	// from.
	SourcePubKey Vertex

	// Hops contains details concerning the specific forwarding details at
	// each hop.
	Hops []*Hop
}

// HopFee returns the fee charged by the route hop indicated by hopIndex.
func (r *Route) HopFee(hopIndex int) lnwire.MilliSatoshi {
	var incomingAmt lnwire.MilliSatoshi
	if hopIndex == 0 {
		incomingAmt = r.TotalAmount
	} else {
		incomingAmt = r.Hops[hopIndex-1].AmtToForward
	}

	// Fee is calculated as difference between incoming and outgoing amount.
	return incomingAmt - r.Hops[hopIndex].AmtToForward
}

// TotalFees is the sum of the fees paid at each hop within the final route. In
// the case of a one-hop payment, this value will be zero as we don't need to
// pay a fee to ourself.
func (r *Route) TotalFees() lnwire.MilliSatoshi {
	if len(r.Hops) == 0 {
		return 0
	}

	return r.TotalAmount - r.Hops[len(r.Hops)-1].AmtToForward
}

// NewRouteFromHops creates a new Route structure from the minimally required
// information to perform the payment. It infers fee amounts and populates the
// node, chan and prev/next hop maps.
func NewRouteFromHops(amtToSend lnwire.MilliSatoshi, timeLock uint32,
	sourceVertex Vertex, hops []*Hop) (*Route, error) {

	if len(hops) == 0 {
		return nil, ErrNoRouteHopsProvided
	}

	// First, we'll create a route struct and populate it with the fields
	// for which the values are provided as arguments of this function.
	// TotalFees is determined based on the difference between the amount
	// that is send from the source and the final amount that is received
	// by the destination.
	route := &Route{
		SourcePubKey:  sourceVertex,
		Hops:          hops,
		TotalTimeLock: timeLock,
		TotalAmount:   amtToSend,
	}

	return route, nil
}

// ToSphinxPath converts a complete route into a sphinx PaymentPath that
// contains the per-hop paylods used to encoding the HTLC routing data for each
// hop in the route.
func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) {
	var path sphinx.PaymentPath

	// For each hop encoded within the route, we'll convert the hop struct
	// to an OnionHop with matching per-hop payload within the path as used
	// by the sphinx package.
	for i, hop := range r.Hops {
		pub, err := btcec.ParsePubKey(
			hop.PubKeyBytes[:], btcec.S256(),
		)
		if err != nil {
			return nil, err
		}

		path[i] = sphinx.OnionHop{
			NodePub: *pub,
			HopData: sphinx.HopData{
				// TODO(roasbeef): properly set realm, make
				// sphinx type an enum actually?
				Realm:         [1]byte{0},
				ForwardAmount: uint64(hop.AmtToForward),
				OutgoingCltv:  hop.OutgoingTimeLock,
			},
		}

		// As a base case, the next hop is set to all zeroes in order
		// to indicate that the "last hop" as no further hops after it.
		nextHop := uint64(0)

		// If we aren't on the last hop, then we set the "next address"
		// field to be the channel that directly follows it.
		if i != len(r.Hops)-1 {
			nextHop = r.Hops[i+1].ChannelID
		}

		binary.BigEndian.PutUint64(
			path[i].HopData.NextAddress[:], nextHop,
		)
	}

	return &path, nil
}
<commit_msg>routing: Route String() method<commit_after>package route

import (
	"encoding/binary"
	"fmt"
	"strconv"
	"strings"

	"github.com/btcsuite/btcd/btcec"
	sphinx "github.com/lightningnetwork/lightning-onion"
	"github.com/lightningnetwork/lnd/lnwire"
)

// ErrNoRouteHopsProvided is returned when a caller attempts to construct a new
// sphinx packet, but provides an empty set of hops for each route.
var ErrNoRouteHopsProvided = fmt.Errorf("empty route hops provided")

// Vertex is a simple alias for the serialization of a compressed Bitcoin
// public key.
type Vertex [33]byte

// NewVertex returns a new Vertex given a public key.
func NewVertex(pub *btcec.PublicKey) Vertex {
	var v Vertex
	copy(v[:], pub.SerializeCompressed())
	return v
}

// String returns a human readable version of the Vertex which is the
// hex-encoding of the serialized compressed public key.
func (v Vertex) String() string {
	return fmt.Sprintf("%x", v[:])
}

// Hop represents an intermediate or final node of the route. This naming
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
// The struct houses the channel along which this hop can be reached and
// the values necessary to create the HTLC that needs to be sent to the
// next hop. It is also used to encode the per-hop payload included within
// the Sphinx packet.
type Hop struct {
	// PubKeyBytes is the raw bytes of the public key of the target node.
	PubKeyBytes Vertex

	// ChannelID is the unique channel ID for the channel. The first 3
	// bytes are the block height, the next 3 the index within the block,
	// and the last 2 bytes are the output index for the channel.
	ChannelID uint64

	// OutgoingTimeLock is the timelock value that should be used when
	// crafting the _outgoing_ HTLC from this hop.
	OutgoingTimeLock uint32

	// AmtToForward is the amount that this hop will forward to the next
	// hop. This value is less than the value that the incoming HTLC
	// carries as a fee will be subtracted by the hop.
	AmtToForward lnwire.MilliSatoshi
}

// Route represents a path through the channel graph which runs over one or
// more channels in succession. This struct carries all the information
// required to craft the Sphinx onion packet, and send the payment along the
// first hop in the path. A route is only selected as valid if all the channels
// have sufficient capacity to carry the initial payment amount after fees are
// accounted for.
type Route struct {
	// TotalTimeLock is the cumulative (final) time lock across the entire
	// route. This is the CLTV value that should be extended to the first
	// hop in the route. All other hops will decrement the time-lock as
	// advertised, leaving enough time for all hops to wait for or present
	// the payment preimage to complete the payment.
	TotalTimeLock uint32

	// TotalAmount is the total amount of funds required to complete a
	// payment over this route. This value includes the cumulative fees at
	// each hop. As a result, the HTLC extended to the first-hop in the
	// route will need to have at least this many satoshis, otherwise the
	// route will fail at an intermediate node due to an insufficient
	// amount of fees.
	TotalAmount lnwire.MilliSatoshi

	// SourcePubKey is the pubkey of the node where this route originates
	// from.
	SourcePubKey Vertex

	// Hops contains details concerning the specific forwarding details at
	// each hop.
	Hops []*Hop
}

// HopFee returns the fee charged by the route hop indicated by hopIndex.
func (r *Route) HopFee(hopIndex int) lnwire.MilliSatoshi {
	var incomingAmt lnwire.MilliSatoshi
	if hopIndex == 0 {
		incomingAmt = r.TotalAmount
	} else {
		incomingAmt = r.Hops[hopIndex-1].AmtToForward
	}

	// Fee is calculated as difference between incoming and outgoing amount.
	return incomingAmt - r.Hops[hopIndex].AmtToForward
}

// TotalFees is the sum of the fees paid at each hop within the final route. In
// the case of a one-hop payment, this value will be zero as we don't need to
// pay a fee to ourself.
func (r *Route) TotalFees() lnwire.MilliSatoshi {
	if len(r.Hops) == 0 {
		return 0
	}

	return r.TotalAmount - r.Hops[len(r.Hops)-1].AmtToForward
}

// NewRouteFromHops creates a new Route structure from the minimally required
// information to perform the payment. It infers fee amounts and populates the
// node, chan and prev/next hop maps.
func NewRouteFromHops(amtToSend lnwire.MilliSatoshi, timeLock uint32,
	sourceVertex Vertex, hops []*Hop) (*Route, error) {

	if len(hops) == 0 {
		return nil, ErrNoRouteHopsProvided
	}

	// First, we'll create a route struct and populate it with the fields
	// for which the values are provided as arguments of this function.
	// TotalFees is determined based on the difference between the amount
	// that is send from the source and the final amount that is received
	// by the destination.
	route := &Route{
		SourcePubKey:  sourceVertex,
		Hops:          hops,
		TotalTimeLock: timeLock,
		TotalAmount:   amtToSend,
	}

	return route, nil
}

// ToSphinxPath converts a complete route into a sphinx PaymentPath that
// contains the per-hop paylods used to encoding the HTLC routing data for each
// hop in the route.
func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) {
	var path sphinx.PaymentPath

	// For each hop encoded within the route, we'll convert the hop struct
	// to an OnionHop with matching per-hop payload within the path as used
	// by the sphinx package.
	for i, hop := range r.Hops {
		pub, err := btcec.ParsePubKey(
			hop.PubKeyBytes[:], btcec.S256(),
		)
		if err != nil {
			return nil, err
		}

		path[i] = sphinx.OnionHop{
			NodePub: *pub,
			HopData: sphinx.HopData{
				// TODO(roasbeef): properly set realm, make
				// sphinx type an enum actually?
				Realm:         [1]byte{0},
				ForwardAmount: uint64(hop.AmtToForward),
				OutgoingCltv:  hop.OutgoingTimeLock,
			},
		}

		// As a base case, the next hop is set to all zeroes in order
		// to indicate that the "last hop" as no further hops after it.
		nextHop := uint64(0)

		// If we aren't on the last hop, then we set the "next address"
		// field to be the channel that directly follows it.
		if i != len(r.Hops)-1 {
			nextHop = r.Hops[i+1].ChannelID
		}

		binary.BigEndian.PutUint64(
			path[i].HopData.NextAddress[:], nextHop,
		)
	}

	return &path, nil
}

// String returns a human readable representation of the route.
func (r *Route) String() string {
	var b strings.Builder

	for i, hop := range r.Hops {
		if i > 0 {
			b.WriteString(",")
		}
		b.WriteString(strconv.FormatUint(hop.ChannelID, 10))
	}

	return fmt.Sprintf("amt=%v, fees=%v, tl=%v, chans=%v",
		r.TotalAmount-r.TotalFees(), r.TotalFees(), r.TotalTimeLock,
		b.String(),
	)
}
<|endoftext|>


<commit_before>package autonat

import (
	"context"
	"net"
	"testing"
	"time"

	libp2p "github.com/libp2p/go-libp2p"
	host "github.com/libp2p/go-libp2p-host"
)

func makeAutoNAT(ctx context.Context, t *testing.T) (host.Host, AutoNAT) {
	h, err := libp2p.New(ctx, libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0"))
	if err != nil {
		t.Fatal(err)
	}

	a := NewAutoNAT(ctx, h)

	return h, a
}

// Note: these tests assume the host has only private inet addresses!
func TestAutoNATPrivate(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	save1 := AutoNATBootDelay
	AutoNATBootDelay = 1 * time.Second
	save2 := AutoNATRefreshInterval
	AutoNATRefreshInterval = 1 * time.Second

	hs, _ := makeAutoNATService(ctx, t)
	hc, an := makeAutoNAT(ctx, t)

	status := an.Status()
	if status != NATStatusUnknown {
		t.Fatalf("unexpected NAT status: %d", status)
	}

	connect(t, hs, hc)
	time.Sleep(2 * time.Second)

	status = an.Status()
	if status != NATStatusPrivate {
		t.Fatalf("unexpected NAT status: %d", status)
	}

	AutoNATBootDelay = save1
	AutoNATRefreshInterval = save2
}

func TestAutoNATPublic(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	save1 := AutoNATBootDelay
	AutoNATBootDelay = 1 * time.Second
	save2 := AutoNATRefreshInterval
	AutoNATRefreshInterval = 1 * time.Second
	save3 := private4
	private4 = []*net.IPNet{}

	hs, _ := makeAutoNATService(ctx, t)
	hc, an := makeAutoNAT(ctx, t)

	status := an.Status()
	if status != NATStatusUnknown {
		t.Fatalf("unexpected NAT status: %d", status)
	}

	connect(t, hs, hc)
	time.Sleep(2 * time.Second)

	status = an.Status()
	if status != NATStatusPublic {
		t.Fatalf("unexpected NAT status: %d", status)
	}

	AutoNATBootDelay = save1
	AutoNATRefreshInterval = save2
	private4 = save3
}
<commit_msg>adjust AutoNATRetryInterval in autonat tests<commit_after>package autonat

import (
	"context"
	"net"
	"testing"
	"time"

	libp2p "github.com/libp2p/go-libp2p"
	host "github.com/libp2p/go-libp2p-host"
)

func makeAutoNAT(ctx context.Context, t *testing.T) (host.Host, AutoNAT) {
	h, err := libp2p.New(ctx, libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0"))
	if err != nil {
		t.Fatal(err)
	}

	a := NewAutoNAT(ctx, h)

	return h, a
}

// Note: these tests assume the host has only private inet addresses!
func TestAutoNATPrivate(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	save1 := AutoNATBootDelay
	AutoNATBootDelay = 1 * time.Second
	save2 := AutoNATRefreshInterval
	AutoNATRefreshInterval = 1 * time.Second
	save3 := AutoNATRetryInterval
	AutoNATRetryInterval = 1 * time.Second

	hs, _ := makeAutoNATService(ctx, t)
	hc, an := makeAutoNAT(ctx, t)

	status := an.Status()
	if status != NATStatusUnknown {
		t.Fatalf("unexpected NAT status: %d", status)
	}

	connect(t, hs, hc)
	time.Sleep(2 * time.Second)

	status = an.Status()
	if status != NATStatusPrivate {
		t.Fatalf("unexpected NAT status: %d", status)
	}

	AutoNATBootDelay = save1
	AutoNATRefreshInterval = save2
	AutoNATRetryInterval = save3
}

func TestAutoNATPublic(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	save1 := AutoNATBootDelay
	AutoNATBootDelay = 1 * time.Second
	save2 := AutoNATRefreshInterval
	AutoNATRefreshInterval = 1 * time.Second
	save3 := AutoNATRetryInterval
	AutoNATRetryInterval = 1 * time.Second
	save4 := private4
	private4 = []*net.IPNet{}

	hs, _ := makeAutoNATService(ctx, t)
	hc, an := makeAutoNAT(ctx, t)

	status := an.Status()
	if status != NATStatusUnknown {
		t.Fatalf("unexpected NAT status: %d", status)
	}

	connect(t, hs, hc)
	time.Sleep(2 * time.Second)

	status = an.Status()
	if status != NATStatusPublic {
		t.Fatalf("unexpected NAT status: %d", status)
	}

	AutoNATBootDelay = save1
	AutoNATRefreshInterval = save2
	AutoNATRetryInterval = save3
	private4 = save4
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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

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

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

package e2e

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"golang.org/x/crypto/ssh"

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

// TODO: it would probably be slightly better to build up the objects
// in the code and then serialize to yaml.
var addon_controller_v1 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: addon-test-v1
  namespace: default
  labels:
    k8s-app: addon-test
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 2
  selector:
    k8s-app: addon-test
    version: v1
  template:
    metadata:
      labels:
        k8s-app: addon-test
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var addon_controller_v2 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: addon-test-v2
  namespace: default
  labels:
    k8s-app: addon-test
    version: v2
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 2
  selector:
    k8s-app: addon-test
    version: v2
  template:
    metadata:
      labels:
        k8s-app: addon-test
        version: v2
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var addon_service_v1 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: addon-test
  namespace: default
  labels:
    k8s-app: addon-test
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: addon-test
spec:
  ports:
  - port: 9376
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: addon-test
`

var addon_service_v2 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: addon-test-updated
  namespace: default
  labels:
    k8s-app: addon-test
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: addon-test
    newLabel: newValue
spec:
  ports:
  - port: 9376
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: addon-test
`

var invalid_addon_controller_v1 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: invalid-addon-test-v1
  namespace: default
  labels:
    k8s-app: invalid-addon-test
    version: v1
spec:
  replicas: 2
  selector:
    k8s-app: invalid-addon-test
    version: v1
  template:
    metadata:
      labels:
        k8s-app: invalid-addon-test
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: invalid-addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var invalid_addon_service_v1 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: ivalid-addon-test
  namespace: default
  labels:
    k8s-app: invalid-addon-test
    kubernetes.io/name: invalid-addon-test
spec:
  ports:
  - port: 9377
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: invalid-addon-test
`

var addonTestPollInterval = 3 * time.Second
var addonTestPollTimeout = 3 * time.Minute
var addonNamespace = api.NamespaceDefault // addons are in the default namespace

type stringPair struct {
	data, fileName string
}

var _ = Describe("Addon update", func() {

	var dir string
	var sshClient *ssh.Client
	var c *client.Client
	var namespace *api.Namespace

	BeforeEach(func() {
		var err error
		c, err = loadClient()
		Expect(err).NotTo(HaveOccurred())

		sshClient, err = getSSHClient()
		Expect(err).NotTo(HaveOccurred())

		namespace, err = createTestingNS("addon-update-test", c)
		Expect(err).NotTo(HaveOccurred())

		// do not use "service" command because it clears the environment variables
		sshExecAndVerify(sshClient, "sudo TEST_ADDON_CHECK_INTERVAL_SEC=1 /etc/init.d/kube-addons restart")
	})

	AfterEach(func() {
		if sshClient != nil {
			// restart addon_update with the default options
			sshExec(sshClient, "sudo /etc/init.d/kube-addons restart")
			sshClient.Close()
		}
		if err := c.Namespaces().Delete(namespace.Name); err != nil {
			Failf("Couldn't delete ns %q: %s", namespace, err)
		}
		// Paranoia-- prevent reuse!
		namespace = nil
		c = nil
	})

	// WARNING: the test is not parallel-friendly!
	It("should propagate add-on file changes", func() {
		//these tests are long, so I squeezed several cases in one scenario
		Expect(sshClient).NotTo(BeNil())
		dir = namespace.Name // we use it only to give a unique string for each test execution

		// This test requires SSH, so the provider check should be identical to
		// those tests.
		if !providerIs("gce") {
			Logf(fmt.Sprintf("Skipping test, which is not implemented for %s", testContext.Provider))
			return
		}

		temporaryRemotePathPrefix := "addon-test-dir"
		temporaryRemotePath := temporaryRemotePathPrefix + "/" + dir                  // in home directory on kubernetes-master
		defer sshExec(sshClient, fmt.Sprintf("rm -rf %s", temporaryRemotePathPrefix)) // ignore the result in cleanup
		sshExecAndVerify(sshClient, fmt.Sprintf("mkdir -p %s", temporaryRemotePath))

		rcv1 := "addon-controller-v1.yaml"
		rcv2 := "addon-controller-v2.yaml"
		rcInvalid := "invalid-addon-controller-v1.yaml"

		svcv1 := "addon-service-v1.yaml"
		svcv2 := "addon-service-v2.yaml"
		svcInvalid := "invalid-addon-service-v1.yaml"

		var remoteFiles []stringPair = []stringPair{
			{addon_controller_v1, rcv1},
			{addon_controller_v2, rcv2},
			{addon_service_v1, svcv1},
			{addon_service_v2, svcv2},
			{invalid_addon_controller_v1, rcInvalid},
			{invalid_addon_service_v1, svcInvalid},
		}

		for _, p := range remoteFiles {
			err := writeRemoteFile(sshClient, p.data, temporaryRemotePath, p.fileName, 0644)
			Expect(err).NotTo(HaveOccurred())
		}

		// directory on kubernetes-master
		destinationDirPrefix := "/etc/kubernetes/addons/addon-test-dir"
		destinationDir := destinationDirPrefix + "/" + dir

		// cleanup from previous tests
		_, _, _, err := sshExec(sshClient, fmt.Sprintf("sudo rm -rf %s", destinationDirPrefix))
		Expect(err).NotTo(HaveOccurred())

		defer sshExec(sshClient, fmt.Sprintf("sudo rm -rf %s", destinationDirPrefix)) // ignore result in cleanup
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo mkdir -p %s", destinationDir))

		By("copy invalid manifests to the destination dir (without kubernetes.io/cluster-service label)")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcInvalid, destinationDir, rcInvalid))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcInvalid, destinationDir, svcInvalid))
		// we will verify at the end of the test that the objects weren't created from the invalid manifests

		By("copy new manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcv1, destinationDir, rcv1))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcv1, destinationDir, svcv1))

		waitForServiceInAddonTest(c, "addon-test", true)
		waitForReplicationControllerInAddonTest(c, "addon-test-v1", true)

		By("update manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcv2, destinationDir, rcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcv2, destinationDir, svcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, rcv1))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, svcv1))
		/**
		 * Note that we have a small race condition here - the kube-addon-updater
		 * May notice that a new rc/service file appeared, while the old one will still be there.
		 * But it is ok - as long as we don't have rolling update, the result will be the same
		 */

		waitForServiceInAddonTest(c, "addon-test-updated", true)
		waitForReplicationControllerInAddonTest(c, "addon-test-v2", true)

		waitForServiceInAddonTest(c, "addon-test", false)
		waitForReplicationControllerInAddonTest(c, "addon-test-v1", false)

		By("remove manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, rcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, svcv2))

		waitForServiceInAddonTest(c, "addon-test-updated", false)
		waitForReplicationControllerInAddonTest(c, "invalid-addon-test-v1", false)

		By("verify invalid API addons weren't created")
		_, err = c.ReplicationControllers(addonNamespace).Get("invalid-addon-test-v1")
		Expect(err).To(HaveOccurred())
		_, err = c.Services(addonNamespace).Get("ivalid-addon-test")
		Expect(err).To(HaveOccurred())

		// invalid addons will be deleted by the deferred function
	})
})

func waitForServiceInAddonTest(c *client.Client, name string, exist bool) {
	expectNoError(waitForService(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout))
}

func waitForReplicationControllerInAddonTest(c *client.Client, name string, exist bool) {
	expectNoError(waitForReplicationController(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout))
}

// TODO marekbiskup 2015-06-11: merge the ssh code into pkg/util/ssh.go after
// kubernetes v1.0 is released. In particular the code of sshExec.
func getSSHClient() (*ssh.Client, error) {
	// Get a signer for the provider.
	signer, err := getSigner(testContext.Provider)
	if err != nil {
		return nil, fmt.Errorf("error getting signer for provider %s: '%v'", testContext.Provider, err)
	}

	config := &ssh.ClientConfig{
		User: os.Getenv("USER"),
		Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
	}

	host := getMasterHost() + ":22"
	client, err := ssh.Dial("tcp", host, config)
	if err != nil {
		return nil, fmt.Errorf("error getting SSH client to host %s: '%v'", host, err)
	}
	return client, err
}

func sshExecAndVerify(client *ssh.Client, cmd string) {
	_, _, rc, err := sshExec(client, cmd)
	Expect(err).NotTo(HaveOccurred())
	Expect(rc).To(Equal(0))
}

func sshExec(client *ssh.Client, cmd string) (string, string, int, error) {
	Logf(fmt.Sprintf("Executing '%s' on %v", cmd, client.RemoteAddr()))
	session, err := client.NewSession()
	if err != nil {
		return "", "", 0, fmt.Errorf("error creating session to host %s: '%v'", client.RemoteAddr(), err)
	}
	defer session.Close()

	// Run the command.
	code := 0
	var bout, berr bytes.Buffer

	session.Stdout, session.Stderr = &bout, &berr
	err = session.Run(cmd)
	if err != nil {
		// Check whether the command failed to run or didn't complete.
		if exiterr, ok := err.(*ssh.ExitError); ok {
			// If we got an ExitError and the exit code is nonzero, we'll
			// consider the SSH itself successful (just that the command run
			// errored on the host).
			if code = exiterr.ExitStatus(); code != 0 {
				err = nil
			}
		} else {
			// Some other kind of error happened (e.g. an IOError); consider the
			// SSH unsuccessful.
			err = fmt.Errorf("failed running `%s` on %s: '%v'", cmd, client.RemoteAddr(), err)
		}
	}
	return bout.String(), berr.String(), code, err
}

func writeRemoteFile(sshClient *ssh.Client, data, dir, fileName string, mode os.FileMode) error {
	Logf(fmt.Sprintf("Writing remote file '%s/%s' on %v", dir, fileName, sshClient.RemoteAddr()))
	session, err := sshClient.NewSession()
	if err != nil {
		return fmt.Errorf("error creating session to host %s: '%v'", sshClient.RemoteAddr(), err)
	}
	defer session.Close()

	fileSize := len(data)
	go func() {
		// ignore errors here. scp whould return errors if something goes wrong.
		pipe, _ := session.StdinPipe()
		defer pipe.Close()
		fmt.Fprintf(pipe, "C%#o %d %s\n", mode, fileSize, fileName)
		io.Copy(pipe, strings.NewReader(data))
		fmt.Fprint(pipe, "\x00")
	}()
	if err := session.Run(fmt.Sprintf("scp -t %s", dir)); err != nil {
		return err
	}
	return nil
}
<commit_msg>Update the addons test so it doesn't try to create clients on providers that aren't gce.<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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

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

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

package e2e

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"golang.org/x/crypto/ssh"

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

// TODO: it would probably be slightly better to build up the objects
// in the code and then serialize to yaml.
var addon_controller_v1 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: addon-test-v1
  namespace: default
  labels:
    k8s-app: addon-test
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 2
  selector:
    k8s-app: addon-test
    version: v1
  template:
    metadata:
      labels:
        k8s-app: addon-test
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var addon_controller_v2 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: addon-test-v2
  namespace: default
  labels:
    k8s-app: addon-test
    version: v2
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 2
  selector:
    k8s-app: addon-test
    version: v2
  template:
    metadata:
      labels:
        k8s-app: addon-test
        version: v2
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var addon_service_v1 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: addon-test
  namespace: default
  labels:
    k8s-app: addon-test
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: addon-test
spec:
  ports:
  - port: 9376
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: addon-test
`

var addon_service_v2 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: addon-test-updated
  namespace: default
  labels:
    k8s-app: addon-test
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: addon-test
    newLabel: newValue
spec:
  ports:
  - port: 9376
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: addon-test
`

var invalid_addon_controller_v1 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: invalid-addon-test-v1
  namespace: default
  labels:
    k8s-app: invalid-addon-test
    version: v1
spec:
  replicas: 2
  selector:
    k8s-app: invalid-addon-test
    version: v1
  template:
    metadata:
      labels:
        k8s-app: invalid-addon-test
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: invalid-addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var invalid_addon_service_v1 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: ivalid-addon-test
  namespace: default
  labels:
    k8s-app: invalid-addon-test
    kubernetes.io/name: invalid-addon-test
spec:
  ports:
  - port: 9377
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: invalid-addon-test
`

var addonTestPollInterval = 3 * time.Second
var addonTestPollTimeout = 3 * time.Minute
var addonNamespace = api.NamespaceDefault // addons are in the default namespace

type stringPair struct {
	data, fileName string
}

var _ = Describe("Addon update", func() {

	var dir string
	var sshClient *ssh.Client
	var c *client.Client
	var namespace *api.Namespace

	BeforeEach(func() {
		// This test requires SSH, so the provider check should be identical to
		// those tests.
		if !providerIs("gce") {
			return
		}
		var err error
		c, err = loadClient()
		Expect(err).NotTo(HaveOccurred())

		sshClient, err = getSSHClient()
		Expect(err).NotTo(HaveOccurred())

		namespace, err = createTestingNS("addon-update-test", c)
		Expect(err).NotTo(HaveOccurred())

		// do not use "service" command because it clears the environment variables
		sshExecAndVerify(sshClient, "sudo TEST_ADDON_CHECK_INTERVAL_SEC=1 /etc/init.d/kube-addons restart")
	})

	AfterEach(func() {
		// This test requires SSH, so the provider check should be identical to
		// those tests.
		if !providerIs("gce") {
			return
		}
		if sshClient != nil {
			// restart addon_update with the default options
			sshExec(sshClient, "sudo /etc/init.d/kube-addons restart")
			sshClient.Close()
		}
		if err := c.Namespaces().Delete(namespace.Name); err != nil {
			Failf("Couldn't delete ns %q: %s", namespace, err)
		}
		// Paranoia-- prevent reuse!
		namespace = nil
		c = nil
	})

	// WARNING: the test is not parallel-friendly!
	It("should propagate add-on file changes", func() {
		// This test requires SSH, so the provider check should be identical to
		// those tests.
		if !providerIs("gce") {
			Logf(fmt.Sprintf("Skipping test, which is not implemented for %s", testContext.Provider))
			return
		}

		//these tests are long, so I squeezed several cases in one scenario
		Expect(sshClient).NotTo(BeNil())
		dir = namespace.Name // we use it only to give a unique string for each test execution

		temporaryRemotePathPrefix := "addon-test-dir"
		temporaryRemotePath := temporaryRemotePathPrefix + "/" + dir                  // in home directory on kubernetes-master
		defer sshExec(sshClient, fmt.Sprintf("rm -rf %s", temporaryRemotePathPrefix)) // ignore the result in cleanup
		sshExecAndVerify(sshClient, fmt.Sprintf("mkdir -p %s", temporaryRemotePath))

		rcv1 := "addon-controller-v1.yaml"
		rcv2 := "addon-controller-v2.yaml"
		rcInvalid := "invalid-addon-controller-v1.yaml"

		svcv1 := "addon-service-v1.yaml"
		svcv2 := "addon-service-v2.yaml"
		svcInvalid := "invalid-addon-service-v1.yaml"

		var remoteFiles []stringPair = []stringPair{
			{addon_controller_v1, rcv1},
			{addon_controller_v2, rcv2},
			{addon_service_v1, svcv1},
			{addon_service_v2, svcv2},
			{invalid_addon_controller_v1, rcInvalid},
			{invalid_addon_service_v1, svcInvalid},
		}

		for _, p := range remoteFiles {
			err := writeRemoteFile(sshClient, p.data, temporaryRemotePath, p.fileName, 0644)
			Expect(err).NotTo(HaveOccurred())
		}

		// directory on kubernetes-master
		destinationDirPrefix := "/etc/kubernetes/addons/addon-test-dir"
		destinationDir := destinationDirPrefix + "/" + dir

		// cleanup from previous tests
		_, _, _, err := sshExec(sshClient, fmt.Sprintf("sudo rm -rf %s", destinationDirPrefix))
		Expect(err).NotTo(HaveOccurred())

		defer sshExec(sshClient, fmt.Sprintf("sudo rm -rf %s", destinationDirPrefix)) // ignore result in cleanup
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo mkdir -p %s", destinationDir))

		By("copy invalid manifests to the destination dir (without kubernetes.io/cluster-service label)")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcInvalid, destinationDir, rcInvalid))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcInvalid, destinationDir, svcInvalid))
		// we will verify at the end of the test that the objects weren't created from the invalid manifests

		By("copy new manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcv1, destinationDir, rcv1))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcv1, destinationDir, svcv1))

		waitForServiceInAddonTest(c, "addon-test", true)
		waitForReplicationControllerInAddonTest(c, "addon-test-v1", true)

		By("update manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcv2, destinationDir, rcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcv2, destinationDir, svcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, rcv1))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, svcv1))
		/**
		 * Note that we have a small race condition here - the kube-addon-updater
		 * May notice that a new rc/service file appeared, while the old one will still be there.
		 * But it is ok - as long as we don't have rolling update, the result will be the same
		 */

		waitForServiceInAddonTest(c, "addon-test-updated", true)
		waitForReplicationControllerInAddonTest(c, "addon-test-v2", true)

		waitForServiceInAddonTest(c, "addon-test", false)
		waitForReplicationControllerInAddonTest(c, "addon-test-v1", false)

		By("remove manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, rcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, svcv2))

		waitForServiceInAddonTest(c, "addon-test-updated", false)
		waitForReplicationControllerInAddonTest(c, "invalid-addon-test-v1", false)

		By("verify invalid API addons weren't created")
		_, err = c.ReplicationControllers(addonNamespace).Get("invalid-addon-test-v1")
		Expect(err).To(HaveOccurred())
		_, err = c.Services(addonNamespace).Get("ivalid-addon-test")
		Expect(err).To(HaveOccurred())

		// invalid addons will be deleted by the deferred function
	})
})

func waitForServiceInAddonTest(c *client.Client, name string, exist bool) {
	expectNoError(waitForService(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout))
}

func waitForReplicationControllerInAddonTest(c *client.Client, name string, exist bool) {
	expectNoError(waitForReplicationController(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout))
}

// TODO marekbiskup 2015-06-11: merge the ssh code into pkg/util/ssh.go after
// kubernetes v1.0 is released. In particular the code of sshExec.
func getSSHClient() (*ssh.Client, error) {
	// Get a signer for the provider.
	signer, err := getSigner(testContext.Provider)
	if err != nil {
		return nil, fmt.Errorf("error getting signer for provider %s: '%v'", testContext.Provider, err)
	}

	config := &ssh.ClientConfig{
		User: os.Getenv("USER"),
		Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
	}

	host := getMasterHost() + ":22"
	client, err := ssh.Dial("tcp", host, config)
	if err != nil {
		return nil, fmt.Errorf("error getting SSH client to host %s: '%v'", host, err)
	}
	return client, err
}

func sshExecAndVerify(client *ssh.Client, cmd string) {
	_, _, rc, err := sshExec(client, cmd)
	Expect(err).NotTo(HaveOccurred())
	Expect(rc).To(Equal(0))
}

func sshExec(client *ssh.Client, cmd string) (string, string, int, error) {
	Logf(fmt.Sprintf("Executing '%s' on %v", cmd, client.RemoteAddr()))
	session, err := client.NewSession()
	if err != nil {
		return "", "", 0, fmt.Errorf("error creating session to host %s: '%v'", client.RemoteAddr(), err)
	}
	defer session.Close()

	// Run the command.
	code := 0
	var bout, berr bytes.Buffer

	session.Stdout, session.Stderr = &bout, &berr
	err = session.Run(cmd)
	if err != nil {
		// Check whether the command failed to run or didn't complete.
		if exiterr, ok := err.(*ssh.ExitError); ok {
			// If we got an ExitError and the exit code is nonzero, we'll
			// consider the SSH itself successful (just that the command run
			// errored on the host).
			if code = exiterr.ExitStatus(); code != 0 {
				err = nil
			}
		} else {
			// Some other kind of error happened (e.g. an IOError); consider the
			// SSH unsuccessful.
			err = fmt.Errorf("failed running `%s` on %s: '%v'", cmd, client.RemoteAddr(), err)
		}
	}
	return bout.String(), berr.String(), code, err
}

func writeRemoteFile(sshClient *ssh.Client, data, dir, fileName string, mode os.FileMode) error {
	Logf(fmt.Sprintf("Writing remote file '%s/%s' on %v", dir, fileName, sshClient.RemoteAddr()))
	session, err := sshClient.NewSession()
	if err != nil {
		return fmt.Errorf("error creating session to host %s: '%v'", sshClient.RemoteAddr(), err)
	}
	defer session.Close()

	fileSize := len(data)
	go func() {
		// ignore errors here. scp whould return errors if something goes wrong.
		pipe, _ := session.StdinPipe()
		defer pipe.Close()
		fmt.Fprintf(pipe, "C%#o %d %s\n", mode, fileSize, fileName)
		io.Copy(pipe, strings.NewReader(data))
		fmt.Fprint(pipe, "\x00")
	}()
	if err := session.Run(fmt.Sprintf("scp -t %s", dir)); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"testing"
	"time"

	blockstorage_quota "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets"
	compute_quota "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets"
	"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/sapcc/kubernikus/pkg/api/client/operations"
	"github.com/sapcc/kubernikus/pkg/api/models"
	"github.com/sapcc/kubernikus/test/e2e/framework"
)

const (
	KlusterPhaseBecomesTerminatingTimeout = 1 * time.Minute
	WaitForKlusterToBeDeletedTimeout      = 5 * time.Minute
)

type CleanupTests struct {
	Kubernikus  *framework.Kubernikus
	OpenStack   *framework.OpenStack
	KlusterName string
	Reuse       bool
}

func (s *CleanupTests) Run(t *testing.T) {
	if t.Run("Cluster/Terminate", s.TerminateCluster) {
		t.Run("Cluster/BecomesTerminating", s.KlusterPhaseBecomesTerminating)
		t.Run("Cluster/IsDeleted", s.WaitForKlusterToBeDeleted)

		if s.Reuse == false {
			t.Run("QuotaPostFlightCheck", s.QuotaPostFlightCheck)
		}
	}
}

func (s *CleanupTests) TerminateCluster(t *testing.T) {
	_, err := s.Kubernikus.Client.Operations.TerminateCluster(
		operations.NewTerminateClusterParams().WithName(s.KlusterName),
		s.Kubernikus.AuthInfo,
	)
	assert.NoError(t, err, "There should be no error")
}

func (s *CleanupTests) WaitForKlusterToBeDeleted(t *testing.T) {
	err := s.Kubernikus.WaitForKlusterToBeDeleted(s.KlusterName, WaitForKlusterToBeDeletedTimeout)
	require.NoError(t, err, "There should be no error while waiting %v for the kluster to be deleted", WaitForKlusterToBeDeletedTimeout)
}

func (s *CleanupTests) KlusterPhaseBecomesTerminating(t *testing.T) {
	phase, err := s.Kubernikus.WaitForKlusterPhase(s.KlusterName, models.KlusterPhaseTerminating, KlusterPhaseBecomesTerminatingTimeout)

	if assert.NoError(t, err, "There should be no error") {
		assert.Equal(t, phase, models.KlusterPhaseTerminating, "Kluster should become Terminating")
	}
}

func (s *CleanupTests) QuotaPostFlightCheck(t *testing.T) {
	project, err := tokens.Get(s.OpenStack.Identity, s.OpenStack.Provider.Token()).ExtractProject()
	require.NoError(t, err, "There should be no error while getting project from token")

	quota, err := compute_quota.GetDetail(s.OpenStack.Compute, project.ID).Extract()
	require.NoError(t, err, "There should be no error while getting compute quota details")

	storage, err := blockstorage_quota.GetUsage(s.OpenStack.BlockStorage, project.ID).Extract()
	require.NoError(t, err, "There should be no error while getting storage quota details")

	assert.True(t, quota.Cores.InUse == 0, "There should be no cores left in use")
	assert.True(t, quota.Instances.InUse == 0, "There should be no instances left in use")
	assert.True(t, quota.RAM.InUse == 0, "There should be no RAM left in use")
	assert.True(t, storage.Volumes.InUse == 0, "There should be no Volume left in use")
	assert.True(t, storage.Gigabytes.InUse == 0, "There should be no Storage left in use")
}
<commit_msg>increases termination timeout. needs more time now for volume deletion<commit_after>package main

import (
	"testing"
	"time"

	blockstorage_quota "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets"
	compute_quota "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets"
	"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/sapcc/kubernikus/pkg/api/client/operations"
	"github.com/sapcc/kubernikus/pkg/api/models"
	"github.com/sapcc/kubernikus/test/e2e/framework"
)

const (
	KlusterPhaseBecomesTerminatingTimeout = 1 * time.Minute
	WaitForKlusterToBeDeletedTimeout      = 10 * time.Minute
)

type CleanupTests struct {
	Kubernikus  *framework.Kubernikus
	OpenStack   *framework.OpenStack
	KlusterName string
	Reuse       bool
}

func (s *CleanupTests) Run(t *testing.T) {
	if t.Run("Cluster/Terminate", s.TerminateCluster) {
		t.Run("Cluster/BecomesTerminating", s.KlusterPhaseBecomesTerminating)
		t.Run("Cluster/IsDeleted", s.WaitForKlusterToBeDeleted)

		if s.Reuse == false {
			t.Run("QuotaPostFlightCheck", s.QuotaPostFlightCheck)
		}
	}
}

func (s *CleanupTests) TerminateCluster(t *testing.T) {
	_, err := s.Kubernikus.Client.Operations.TerminateCluster(
		operations.NewTerminateClusterParams().WithName(s.KlusterName),
		s.Kubernikus.AuthInfo,
	)
	assert.NoError(t, err, "There should be no error")
}

func (s *CleanupTests) WaitForKlusterToBeDeleted(t *testing.T) {
	err := s.Kubernikus.WaitForKlusterToBeDeleted(s.KlusterName, WaitForKlusterToBeDeletedTimeout)
	require.NoError(t, err, "There should be no error while waiting %v for the kluster to be deleted", WaitForKlusterToBeDeletedTimeout)
}

func (s *CleanupTests) KlusterPhaseBecomesTerminating(t *testing.T) {
	phase, err := s.Kubernikus.WaitForKlusterPhase(s.KlusterName, models.KlusterPhaseTerminating, KlusterPhaseBecomesTerminatingTimeout)

	if assert.NoError(t, err, "There should be no error") {
		assert.Equal(t, phase, models.KlusterPhaseTerminating, "Kluster should become Terminating")
	}
}

func (s *CleanupTests) QuotaPostFlightCheck(t *testing.T) {
	project, err := tokens.Get(s.OpenStack.Identity, s.OpenStack.Provider.Token()).ExtractProject()
	require.NoError(t, err, "There should be no error while getting project from token")

	quota, err := compute_quota.GetDetail(s.OpenStack.Compute, project.ID).Extract()
	require.NoError(t, err, "There should be no error while getting compute quota details")

	storage, err := blockstorage_quota.GetUsage(s.OpenStack.BlockStorage, project.ID).Extract()
	require.NoError(t, err, "There should be no error while getting storage quota details")

	assert.True(t, quota.Cores.InUse == 0, "There should be no cores left in use")
	assert.True(t, quota.Instances.InUse == 0, "There should be no instances left in use")
	assert.True(t, quota.RAM.InUse == 0, "There should be no RAM left in use")
	assert.True(t, storage.Volumes.InUse == 0, "There should be no Volume left in use")
	assert.True(t, storage.Gigabytes.InUse == 0, "There should be no Storage left in use")
}
<|endoftext|>


<commit_before>// $G $D/$F.go && $L $F.$A && ./$A.out

// Copyright 2010 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.

// http://code.google.com/p/go/issues/detail?id=589

package main

import "unsafe"

var bug = false

var minus1 = -1
var big int64 = 10 | 1<<32

func shouldfail(f func(), desc string) {
	defer func() { recover() }()
	f()
	if !bug {
		println("BUG")
		bug = true
	}
	println("didn't crash: ", desc)
}

func badlen() {
	_ = make([]int, minus1)
}

func biglen() {
	_ = make([]int, big)
}

func badcap() {
	_ = make([]int, 10, minus1)
}

func badcap1() {
	_ = make([]int, 10, 5)
}

func bigcap() {
	_ = make([]int, 10, big)
}

const (
	addrBits = 8*uint(unsafe.Sizeof((*byte)(nil)))
	sh = addrBits/2 - 2
)
func overflow() {
	_ = make([][1<<sh][1<<sh]byte, 64)
}

func badmapcap() {
	_ = make(map[int]int, minus1)
}

func bigmapcap() {
	_ = make(map[int]int, big)
}

func badchancap() {
	_ = make(chan int, minus1)
}

func bigchancap() {
	_ = make(chan int, big)
}

func overflowchan() {
	if addrBits == 32 {
		_ = make(chan [1<<15]byte, 1<<20)
	} else {
		// cannot overflow on 64-bit, because
		// int is 32 bits and max chan value size
		// in the implementation is 64 kB.
		panic(1)
	}
}

func main() {
	shouldfail(badlen, "badlen")
	shouldfail(biglen, "biglen")
	shouldfail(badcap, "badcap")
	shouldfail(badcap1, "badcap1")
	shouldfail(bigcap, "bigcap")
	shouldfail(overflow, "overflow")
	shouldfail(badmapcap, "badmapcap")
	shouldfail(bigmapcap, "bigmapcap")
	shouldfail(badchancap, "badchancap")
	shouldfail(bigchancap, "bigchancap")
	shouldfail(overflowchan, "overflowchan")
}
<commit_msg>test: Use global variables to defeat gccgo optimizer.<commit_after>// $G $D/$F.go && $L $F.$A && ./$A.out

// Copyright 2010 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.

// http://code.google.com/p/go/issues/detail?id=589

package main

import "unsafe"

var bug = false

var minus1 = -1
var big int64 = 10 | 1<<32

var g1 []int

func shouldfail(f func(), desc string) {
	defer func() { recover() }()
	f()
	if !bug {
		println("BUG")
		bug = true
	}
	println("didn't crash: ", desc)
}

func badlen() {
	g1 = make([]int, minus1)
}

func biglen() {
	g1 = make([]int, big)
}

func badcap() {
	g1 = make([]int, 10, minus1)
}

func badcap1() {
	g1 = make([]int, 10, 5)
}

func bigcap() {
	g1 = make([]int, 10, big)
}

const (
	addrBits = 8*uint(unsafe.Sizeof((*byte)(nil)))
	sh = addrBits/2 - 2
)
var g2 [][1<<sh][1<<sh]byte
func overflow() {
	g2 = make([][1<<sh][1<<sh]byte, 64)
}

var g3 map[int]int
func badmapcap() {
	g3 = make(map[int]int, minus1)
}

func bigmapcap() {
	g3 = make(map[int]int, big)
}

var g4 chan int
func badchancap() {
	g4 = make(chan int, minus1)
}

func bigchancap() {
	g4 = make(chan int, big)
}

var g5 chan [1<<15]byte
func overflowchan() {
	if addrBits == 32 {
		g5 = make(chan [1<<15]byte, 1<<20)
	} else {
		// cannot overflow on 64-bit, because
		// int is 32 bits and max chan value size
		// in the implementation is 64 kB.
		panic(1)
	}
}

func main() {
	shouldfail(badlen, "badlen")
	shouldfail(biglen, "biglen")
	shouldfail(badcap, "badcap")
	shouldfail(badcap1, "badcap1")
	shouldfail(bigcap, "bigcap")
	shouldfail(overflow, "overflow")
	shouldfail(badmapcap, "badmapcap")
	shouldfail(bigmapcap, "bigmapcap")
	shouldfail(badchancap, "badchancap")
	shouldfail(bigchancap, "bigchancap")
	shouldfail(overflowchan, "overflowchan")
}
<|endoftext|>


<commit_before><commit_msg>regenerate fakes<commit_after><|endoftext|>