<commit_before>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"strings"
	"testing"

	"github.com/kisielk/gotool"
)

const testdata = "testdata"

var (
	warnsRe  = regexp.MustCompile(`^WARN (.*)\n?$`)
	singleRe = regexp.MustCompile(`([^ ]*) can be ([^ ]*)(,|$)`)
)

func goFiles(t *testing.T, p string) []string {
	if strings.HasSuffix(p, ".go") {
		return []string{p}
	}
	dirs := gotool.ImportPaths([]string{p})
	var paths []string
	for _, dir := range dirs {
		files, err := ioutil.ReadDir(dir)
		if err != nil {
			t.Fatal(err)
		}
		for _, file := range files {
			if file.IsDir() {
				continue
			}
			paths = append(paths, filepath.Join(dir, file.Name()))
		}
	}
	return paths
}

type identVisitor struct {
	fset   *token.FileSet
	idents map[string]token.Position
}

func identKey(pos token.Position, name string) string {
	return fmt.Sprintf("%d %s", pos.Line, name)
}

func (v *identVisitor) Visit(n ast.Node) ast.Visitor {
	switch x := n.(type) {
	case *ast.Ident:
		pos := v.fset.Position(x.Pos())
		v.idents[identKey(pos, x.Name)] = pos
	}
	return v
}

func identPositions(fset *token.FileSet, f *ast.File) map[string]token.Position {
	v := &identVisitor{
		fset:   fset,
		idents: make(map[string]token.Position),
	}
	ast.Walk(v, f)
	return v.idents
}

func wantedWarnings(t *testing.T, p string) []Warn {
	fset := token.NewFileSet()
	var warns []Warn
	for _, path := range goFiles(t, p) {
		src, err := os.Open(path)
		if err != nil {
			t.Fatal(err)
		}
		f, err := parser.ParseFile(fset, path, src, parser.ParseComments)
		src.Close()
		if err != nil {
			t.Fatal(err)
		}
		identPos := identPositions(fset, f)
		for _, group := range f.Comments {
			cm := warnsRe.FindStringSubmatch(group.Text())
			if cm == nil {
				continue
			}
			for _, m := range singleRe.FindAllStringSubmatch(cm[1], -1) {
				vname, tname := m[1], m[2]
				comPos := fset.Position(group.Pos())
				warns = append(warns, Warn{
					Pos:     identPos[identKey(comPos, vname)],
					Name:    vname,
					NewType: tname,
				})
			}
		}
	}
	return warns
}

func doTest(t *testing.T, p string) {
	t.Run(p, func(t *testing.T) {
		warns := wantedWarnings(t, p)
		doTestWarns(t, p, warns, p)
	})
}

func warnsJoin(warns []Warn) string {
	var b bytes.Buffer
	for _, warn := range warns {
		fmt.Fprintln(&b, warn.String())
	}
	return b.String()
}

func doTestWarns(t *testing.T, name string, exp []Warn, args ...string) {
	got, err := CheckArgsList(args)
	if err != nil {
		t.Fatalf("Did not want error in %s:\n%v", name, err)
	}
	if !reflect.DeepEqual(exp, got) {
		t.Fatalf("Output mismatch in %s:\nwant:\n%sgot:\n%s",
			name, warnsJoin(exp), warnsJoin(got))
	}
}

func endNewline(s string) string {
	if strings.HasSuffix(s, "\n") {
		return s
	}
	return s + "\n"
}

func doTestString(t *testing.T, name, exp string, args ...string) {
	var b bytes.Buffer
	switch len(args) {
	case 0:
		args = []string{name}
	case 1:
		if args[0] == "" {
			args = nil
		}
	}
	err := CheckArgsOutput(args, &b, true)
	if err != nil {
		t.Fatalf("Did not want error in %s:\n%v", name, err)
	}
	exp = endNewline(exp)
	got := b.String()
	if exp != got {
		t.Fatalf("Output mismatch in %s:\nExpected:\n%s\nGot:\n%s",
			name, exp, got)
	}
}

func inputPaths(t *testing.T, glob string) []string {
	all, err := filepath.Glob(glob)
	if err != nil {
		t.Fatal(err)
	}
	return all
}

func chdirUndo(t *testing.T, d string) func() {
	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Chdir(d); err != nil {
		t.Fatal(err)
	}
	return func() {
		if err := os.Chdir(wd); err != nil {
			t.Fatal(err)
		}
	}
}

func runFileTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "files")()
	if len(paths) == 0 {
		paths = inputPaths(t, "*")
	}
	for _, p := range paths {
		doTest(t, p)
	}
}

func runLocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "local")()
	if len(paths) > 0 {
		for _, p := range paths {
			doTest(t, p)
		}
		return
	}
	for _, p := range inputPaths(t, "*") {
		paths = append(paths, "./"+p+"/...")
	}
	for _, p := range paths {
		doTest(t, p)
	}
	// non-recursive
	doTest(t, "./single")
	doTestString(t, "no-args", ".", "")
}

func runNonlocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "src")()
	if len(paths) > 0 {
		for _, p := range paths {
			doTest(t, p)
		}
		return
	}
	paths = inputPaths(t, "*")
	for _, p := range paths {
		doTest(t, p+"/...")
	}
	// local recursive
	doTest(t, "./nested/...")
	// non-recursive
	doTest(t, "single")
	// make sure we don't miss a package's imports
	doTestString(t, "grab-import", "grab-import\ngrab-import/use.go:27:15: s can be def2.Fooer")
	defer chdirUndo(t, "nested/pkg")()
	// relative paths
	doTestString(t, "rel-path", "nested/pkg\nsimple.go:12:17: rc can be Closer", "./...")
}

func TestMain(m *testing.M) {
	flag.Parse()
	if err := os.Chdir(testdata); err != nil {
		panic(err)
	}
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	build.Default.GOPATH = wd
	gotool.DefaultContext.BuildContext.GOPATH = wd
	os.Exit(m.Run())
}

func TestWarns(t *testing.T) {
	runFileTests(t)
	runLocalTests(t)
	runNonlocalTests(t)
}

func doTestError(t *testing.T, name, exp string, args ...string) {
	switch len(args) {
	case 0:
		args = []string{name}
	case 1:
		if args[0] == "" {
			args = nil
		}
	}
	err := CheckArgsOutput(args, ioutil.Discard, false)
	if err == nil {
		t.Fatalf("Wanted error in %s, but none found.", name)
	}
	got := err.Error()
	if exp != got {
		t.Fatalf("Error mismatch in %s:\nExpected:\n%s\nGot:\n%s",
			name, exp, got)
	}
}

func TestErrors(t *testing.T) {
	// non-existent Go file
	doTestError(t, "missing.go", "open missing.go: no such file or directory")
	// local non-existent non-recursive
	doTestError(t, "./missing", "no initial packages were loaded")
	// non-local non-existent non-recursive
	doTestError(t, "missing", "no initial packages were loaded")
	// Mixing Go files and dirs
	doTestError(t, "wrong-args", "named files must be .go files: bar", "foo.go", "bar")
}

func TestExtraArg(t *testing.T) {
	err := CheckArgsOutput([]string{"single", "--", "foo", "bar"}, ioutil.Discard, false)
	got := err.Error()
	want := "unwanted extra args: [foo bar]"
	if got != want {
		t.Fatalf("Error mismatch:\nExpected:\n%s\nGot:\n%s", want, got)
	}
}
<commit_msg>test: remove unnecessary flag.Parse<commit_after>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"strings"
	"testing"

	"github.com/kisielk/gotool"
)

const testdata = "testdata"

var (
	warnsRe  = regexp.MustCompile(`^WARN (.*)\n?$`)
	singleRe = regexp.MustCompile(`([^ ]*) can be ([^ ]*)(,|$)`)
)

func goFiles(t *testing.T, p string) []string {
	if strings.HasSuffix(p, ".go") {
		return []string{p}
	}
	dirs := gotool.ImportPaths([]string{p})
	var paths []string
	for _, dir := range dirs {
		files, err := ioutil.ReadDir(dir)
		if err != nil {
			t.Fatal(err)
		}
		for _, file := range files {
			if file.IsDir() {
				continue
			}
			paths = append(paths, filepath.Join(dir, file.Name()))
		}
	}
	return paths
}

type identVisitor struct {
	fset   *token.FileSet
	idents map[string]token.Position
}

func identKey(pos token.Position, name string) string {
	return fmt.Sprintf("%d %s", pos.Line, name)
}

func (v *identVisitor) Visit(n ast.Node) ast.Visitor {
	switch x := n.(type) {
	case *ast.Ident:
		pos := v.fset.Position(x.Pos())
		v.idents[identKey(pos, x.Name)] = pos
	}
	return v
}

func identPositions(fset *token.FileSet, f *ast.File) map[string]token.Position {
	v := &identVisitor{
		fset:   fset,
		idents: make(map[string]token.Position),
	}
	ast.Walk(v, f)
	return v.idents
}

func wantedWarnings(t *testing.T, p string) []Warn {
	fset := token.NewFileSet()
	var warns []Warn
	for _, path := range goFiles(t, p) {
		src, err := os.Open(path)
		if err != nil {
			t.Fatal(err)
		}
		f, err := parser.ParseFile(fset, path, src, parser.ParseComments)
		src.Close()
		if err != nil {
			t.Fatal(err)
		}
		identPos := identPositions(fset, f)
		for _, group := range f.Comments {
			cm := warnsRe.FindStringSubmatch(group.Text())
			if cm == nil {
				continue
			}
			for _, m := range singleRe.FindAllStringSubmatch(cm[1], -1) {
				vname, tname := m[1], m[2]
				comPos := fset.Position(group.Pos())
				warns = append(warns, Warn{
					Pos:     identPos[identKey(comPos, vname)],
					Name:    vname,
					NewType: tname,
				})
			}
		}
	}
	return warns
}

func doTest(t *testing.T, p string) {
	t.Run(p, func(t *testing.T) {
		warns := wantedWarnings(t, p)
		doTestWarns(t, p, warns, p)
	})
}

func warnsJoin(warns []Warn) string {
	var b bytes.Buffer
	for _, warn := range warns {
		fmt.Fprintln(&b, warn.String())
	}
	return b.String()
}

func doTestWarns(t *testing.T, name string, exp []Warn, args ...string) {
	got, err := CheckArgsList(args)
	if err != nil {
		t.Fatalf("Did not want error in %s:\n%v", name, err)
	}
	if !reflect.DeepEqual(exp, got) {
		t.Fatalf("Output mismatch in %s:\nwant:\n%sgot:\n%s",
			name, warnsJoin(exp), warnsJoin(got))
	}
}

func endNewline(s string) string {
	if strings.HasSuffix(s, "\n") {
		return s
	}
	return s + "\n"
}

func doTestString(t *testing.T, name, exp string, args ...string) {
	var b bytes.Buffer
	switch len(args) {
	case 0:
		args = []string{name}
	case 1:
		if args[0] == "" {
			args = nil
		}
	}
	err := CheckArgsOutput(args, &b, true)
	if err != nil {
		t.Fatalf("Did not want error in %s:\n%v", name, err)
	}
	exp = endNewline(exp)
	got := b.String()
	if exp != got {
		t.Fatalf("Output mismatch in %s:\nExpected:\n%s\nGot:\n%s",
			name, exp, got)
	}
}

func inputPaths(t *testing.T, glob string) []string {
	all, err := filepath.Glob(glob)
	if err != nil {
		t.Fatal(err)
	}
	return all
}

func chdirUndo(t *testing.T, d string) func() {
	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Chdir(d); err != nil {
		t.Fatal(err)
	}
	return func() {
		if err := os.Chdir(wd); err != nil {
			t.Fatal(err)
		}
	}
}

func runFileTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "files")()
	if len(paths) == 0 {
		paths = inputPaths(t, "*")
	}
	for _, p := range paths {
		doTest(t, p)
	}
}

func runLocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "local")()
	if len(paths) > 0 {
		for _, p := range paths {
			doTest(t, p)
		}
		return
	}
	for _, p := range inputPaths(t, "*") {
		paths = append(paths, "./"+p+"/...")
	}
	for _, p := range paths {
		doTest(t, p)
	}
	// non-recursive
	doTest(t, "./single")
	doTestString(t, "no-args", ".", "")
}

func runNonlocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "src")()
	if len(paths) > 0 {
		for _, p := range paths {
			doTest(t, p)
		}
		return
	}
	paths = inputPaths(t, "*")
	for _, p := range paths {
		doTest(t, p+"/...")
	}
	// local recursive
	doTest(t, "./nested/...")
	// non-recursive
	doTest(t, "single")
	// make sure we don't miss a package's imports
	doTestString(t, "grab-import", "grab-import\ngrab-import/use.go:27:15: s can be def2.Fooer")
	defer chdirUndo(t, "nested/pkg")()
	// relative paths
	doTestString(t, "rel-path", "nested/pkg\nsimple.go:12:17: rc can be Closer", "./...")
}

func TestMain(m *testing.M) {
	if err := os.Chdir(testdata); err != nil {
		panic(err)
	}
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	build.Default.GOPATH = wd
	gotool.DefaultContext.BuildContext.GOPATH = wd
	os.Exit(m.Run())
}

func TestWarns(t *testing.T) {
	runFileTests(t)
	runLocalTests(t)
	runNonlocalTests(t)
}

func doTestError(t *testing.T, name, exp string, args ...string) {
	switch len(args) {
	case 0:
		args = []string{name}
	case 1:
		if args[0] == "" {
			args = nil
		}
	}
	err := CheckArgsOutput(args, ioutil.Discard, false)
	if err == nil {
		t.Fatalf("Wanted error in %s, but none found.", name)
	}
	got := err.Error()
	if exp != got {
		t.Fatalf("Error mismatch in %s:\nExpected:\n%s\nGot:\n%s",
			name, exp, got)
	}
}

func TestErrors(t *testing.T) {
	// non-existent Go file
	doTestError(t, "missing.go", "open missing.go: no such file or directory")
	// local non-existent non-recursive
	doTestError(t, "./missing", "no initial packages were loaded")
	// non-local non-existent non-recursive
	doTestError(t, "missing", "no initial packages were loaded")
	// Mixing Go files and dirs
	doTestError(t, "wrong-args", "named files must be .go files: bar", "foo.go", "bar")
}

func TestExtraArg(t *testing.T) {
	err := CheckArgsOutput([]string{"single", "--", "foo", "bar"}, ioutil.Discard, false)
	got := err.Error()
	want := "unwanted extra args: [foo bar]"
	if got != want {
		t.Fatalf("Error mismatch:\nExpected:\n%s\nGot:\n%s", want, got)
	}
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"
	neturl "net/url"
	"reflect"
	"regexp"
	"strings"
	"time"

	_ "github.com/lib/pq"

	"github.com/jmoiron/sqlx"
	"github.com/sosedoff/pgweb/pkg/command"
	"github.com/sosedoff/pgweb/pkg/connection"
	"github.com/sosedoff/pgweb/pkg/history"
	"github.com/sosedoff/pgweb/pkg/shared"
	"github.com/sosedoff/pgweb/pkg/statements"
)

var (
	postgresSignature = regexp.MustCompile(`(?i)postgresql ([\d\.]+)\s`)
	postgresType      = "PostgreSQL"

	cockroachSignature = regexp.MustCompile(`(?i)cockroachdb ccl v([\d\.]+)\s`)
	cockroachType      = "CockroachDB"
)

type Client struct {
	db               *sqlx.DB
	tunnel           *Tunnel
	serverVersion    string
	serverType       string
	lastQueryTime    time.Time
	External         bool
	History          []history.Record `json:"history"`
	ConnectionString string           `json:"connection_string"`
}

// Struct to hold table rows browsing options
type RowsOptions struct {
	Where      string // Custom filter
	Offset     int    // Number of rows to skip
	Limit      int    // Number of rows to fetch
	SortColumn string // Column to sort by
	SortOrder  string // Sort direction (ASC, DESC)
}

func getSchemaAndTable(str string) (string, string) {
	chunks := strings.Split(str, ".")
	if len(chunks) == 1 {
		return "public", chunks[0]
	}
	return chunks[0], chunks[1]
}

func New() (*Client, error) {
	str, err := connection.BuildString(command.Opts)

	if command.Opts.Debug && str != "" {
		fmt.Println("Creating a new client for:", str)
	}

	if err != nil {
		return nil, err
	}

	db, err := sqlx.Open("postgres", str)
	if err != nil {
		return nil, err
	}

	client := Client{
		db:               db,
		ConnectionString: str,
		History:          history.New(),
	}

	client.setServerVersion()
	return &client, nil
}

func NewFromUrl(url string, sshInfo *shared.SSHInfo) (*Client, error) {
	var tunnel *Tunnel

	if sshInfo != nil {
		if command.Opts.DisableSSH {
			return nil, fmt.Errorf("ssh connections are disabled")
		}
		if command.Opts.Debug {
			fmt.Println("Opening SSH tunnel for:", sshInfo)
		}

		tunnel, err := NewTunnel(sshInfo, url)
		if err != nil {
			tunnel.Close()
			return nil, err
		}

		err = tunnel.Configure()
		if err != nil {
			tunnel.Close()
			return nil, err
		}

		go tunnel.Start()

		uri, err := neturl.Parse(url)
		if err != nil {
			tunnel.Close()
			return nil, err
		}

		// Override remote postgres port with local proxy port
		url = strings.Replace(url, uri.Host, fmt.Sprintf("127.0.0.1:%v", tunnel.Port), 1)
	}

	if command.Opts.Debug {
		fmt.Println("Creating a new client for:", url)
	}

	uri, err := neturl.Parse(url)
	if err == nil && uri.Path == "" {
		return nil, fmt.Errorf("Database name is not provided")
	}

	db, err := sqlx.Open("postgres", url)
	if err != nil {
		return nil, err
	}

	client := Client{
		db:               db,
		tunnel:           tunnel,
		ConnectionString: url,
		History:          history.New(),
	}

	client.setServerVersion()
	return &client, nil
}

func (client *Client) setServerVersion() {
	res, err := client.query("SELECT version()")
	if err != nil || len(res.Rows) < 1 {
		return
	}

	version := res.Rows[0][0].(string)

	// Detect postgresql
	matches := postgresSignature.FindAllStringSubmatch(version, 1)
	if len(matches) > 0 {
		client.serverType = postgresType
		client.serverVersion = matches[0][1]
		return
	}

	// Detect cockroachdb
	matches = cockroachSignature.FindAllStringSubmatch(version, 1)
	if len(matches) > 0 {
		client.serverType = cockroachType
		client.serverVersion = matches[0][1]
		return
	}
}

func (client *Client) Test() error {
	return client.db.Ping()
}

func (client *Client) Info() (*Result, error) {
	return client.query(statements.Info)
}

func (client *Client) Databases() ([]string, error) {
	return client.fetchRows(statements.Databases)
}

func (client *Client) Schemas() ([]string, error) {
	return client.fetchRows(statements.Schemas)
}

func (client *Client) Objects() (*Result, error) {
	return client.query(statements.Objects)
}

func (client *Client) Table(table string) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	return client.query(statements.TableSchema, schema, table)
}

func (client *Client) MaterializedView(name string) (*Result, error) {
	return client.query(statements.MaterializedView, name)
}

func (client *Client) TableRows(table string, opts RowsOptions) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	sql := fmt.Sprintf(`SELECT * FROM "%s"."%s"`, schema, table)

	if opts.Where != "" {
		sql += fmt.Sprintf(" WHERE %s", opts.Where)
	}

	if opts.SortColumn != "" {
		if opts.SortOrder == "" {
			opts.SortOrder = "ASC"
		}

		sql += fmt.Sprintf(` ORDER BY "%s" %s`, opts.SortColumn, opts.SortOrder)
	}

	if opts.Limit > 0 {
		sql += fmt.Sprintf(" LIMIT %d", opts.Limit)
	}

	if opts.Offset > 0 {
		sql += fmt.Sprintf(" OFFSET %d", opts.Offset)
	}

	return client.query(sql)
}

func (client *Client) EstimatedTableRowsCount(table string, opts RowsOptions) (*Result, error) {
	sql := fmt.Sprintf(`SELECT reltuples FROM pg_class WHERE relname = '%s'`, table)

	result, err := client.query(sql)
	if err != nil {
		return nil, err
	}
	// float64 to int64 conversion
	estimatedRowsCount := result.Rows[0][0].(float64)
	result.Rows[0] = Row{int64(estimatedRowsCount)}

	return result, nil
}

func (client *Client) TableRowsCount(table string, opts RowsOptions) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	sql := fmt.Sprintf(`SELECT COUNT(1) FROM "%s"."%s"`, schema, table)

	if opts.Where != "" {
		sql += fmt.Sprintf(" WHERE %s", opts.Where)
	} else {
		tableInfo, err := client.TableInfo(table)
		if err != nil {
			return nil, err
		}
		estimatedRowsCount := tableInfo.Rows[0][3].(float64)
		if estimatedRowsCount > 100000 {
			return client.EstimatedTableRowsCount(table, opts)
		}
	}

	return client.query(sql)
}

func (client *Client) TableInfo(table string) (*Result, error) {
	if client.serverType == cockroachType {
		return client.query(statements.TableInfoCockroach)
	}
	return client.query(statements.TableInfo, table)
}

func (client *Client) TableIndexes(table string) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	res, err := client.query(statements.TableIndexes, schema, table)

	if err != nil {
		return nil, err
	}

	return res, err
}

func (client *Client) TableConstraints(table string) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	res, err := client.query(statements.TableConstraints, schema, table)

	if err != nil {
		return nil, err
	}

	return res, err
}

// Returns all active queriers on the server
func (client *Client) Activity() (*Result, error) {
	if client.serverType == cockroachType {
		return client.query("SHOW QUERIES")
	}

	version := getMajorMinorVersion(client.serverVersion)
	query := statements.Activity[version]
	if query == "" {
		query = statements.Activity["default"]
	}

	return client.query(query)
}

func (client *Client) Query(query string) (*Result, error) {
	res, err := client.query(query)

	// Save history records only if query did not fail
	if err == nil && !client.hasHistoryRecord(query) {
		client.History = append(client.History, history.NewRecord(query))
	}

	return res, err
}

func (client *Client) SetReadOnlyMode() error {
	var value string
	if err := client.db.Get(&value, "SHOW default_transaction_read_only;"); err != nil {
		return err
	}

	if value == "off" {
		_, err := client.db.Exec("SET default_transaction_read_only=on;")
		return err
	}

	return nil
}

func (client *Client) ServerVersion() string {
	return fmt.Sprintf("%s %s", client.serverType, client.serverVersion)
}

func (client *Client) query(query string, args ...interface{}) (*Result, error) {
	// Update the last usage time
	defer func() {
		client.lastQueryTime = time.Now().UTC()
	}()

	// We're going to force-set transaction mode on every query.
	// This is needed so that default mode could not be changed by user.
	if command.Opts.ReadOnly {
		if err := client.SetReadOnlyMode(); err != nil {
			return nil, err
		}
	}

	action := strings.ToLower(strings.Split(query, " ")[0])
	if action == "update" || action == "delete" {
		res, err := client.db.Exec(query, args...)
		if err != nil {
			return nil, err
		}

		affected, err := res.RowsAffected()
		if err != nil {
			return nil, err
		}

		result := Result{
			Columns: []string{"Rows Affected"},
			Rows: []Row{
				Row{affected},
			},
		}

		return &result, nil
	}

	rows, err := client.db.Queryx(query, args...)
	if err != nil {
		return nil, err
	}

	defer rows.Close()

	cols, err := rows.Columns()
	if err != nil {
		return nil, err
	}

	// Make sure to never return null colums
	if cols == nil {
		cols = []string{}
	}

	result := Result{
		Columns: cols,
		Rows:    []Row{},
	}

	for rows.Next() {
		obj, err := rows.SliceScan()

		for i, item := range obj {
			if item == nil {
				obj[i] = nil
			} else {
				t := reflect.TypeOf(item).Kind().String()

				if t == "slice" {
					obj[i] = string(item.([]byte))
				}
			}
		}

		if err == nil {
			result.Rows = append(result.Rows, obj)
		}
	}

	result.PrepareBigints()

	return &result, nil
}

// Close database connection
func (client *Client) Close() error {
	if client.tunnel != nil {
		client.tunnel.Close()
	}

	if client.db != nil {
		return client.db.Close()
	}

	return nil
}

func (client *Client) IsIdle() bool {
	mins := int(time.Since(client.lastQueryTime).Minutes())

	if command.Opts.ConnectionIdleTimeout > 0 {
		return mins >= command.Opts.ConnectionIdleTimeout
	}

	return false
}

// Fetch all rows as strings for a single column
func (client *Client) fetchRows(q string) ([]string, error) {
	res, err := client.query(q)

	if err != nil {
		return nil, err
	}

	// Init empty slice so json.Marshal will encode it to "[]" instead of "null"
	results := make([]string, 0)

	for _, row := range res.Rows {
		results = append(results, row[0].(string))
	}

	return results, nil
}

func (client *Client) hasHistoryRecord(query string) bool {
	result := false

	for _, record := range client.History {
		if record.Query == query {
			result = true
			break
		}
	}

	return result
}
<commit_msg>Use EstimatedTableRowsCount only for postgresql<commit_after>package client

import (
	"fmt"
	neturl "net/url"
	"reflect"
	"regexp"
	"strings"
	"time"

	_ "github.com/lib/pq"

	"github.com/jmoiron/sqlx"
	"github.com/sosedoff/pgweb/pkg/command"
	"github.com/sosedoff/pgweb/pkg/connection"
	"github.com/sosedoff/pgweb/pkg/history"
	"github.com/sosedoff/pgweb/pkg/shared"
	"github.com/sosedoff/pgweb/pkg/statements"
)

var (
	postgresSignature = regexp.MustCompile(`(?i)postgresql ([\d\.]+)\s`)
	postgresType      = "PostgreSQL"

	cockroachSignature = regexp.MustCompile(`(?i)cockroachdb ccl v([\d\.]+)\s`)
	cockroachType      = "CockroachDB"
)

type Client struct {
	db               *sqlx.DB
	tunnel           *Tunnel
	serverVersion    string
	serverType       string
	lastQueryTime    time.Time
	External         bool
	History          []history.Record `json:"history"`
	ConnectionString string           `json:"connection_string"`
}

// Struct to hold table rows browsing options
type RowsOptions struct {
	Where      string // Custom filter
	Offset     int    // Number of rows to skip
	Limit      int    // Number of rows to fetch
	SortColumn string // Column to sort by
	SortOrder  string // Sort direction (ASC, DESC)
}

func getSchemaAndTable(str string) (string, string) {
	chunks := strings.Split(str, ".")
	if len(chunks) == 1 {
		return "public", chunks[0]
	}
	return chunks[0], chunks[1]
}

func New() (*Client, error) {
	str, err := connection.BuildString(command.Opts)

	if command.Opts.Debug && str != "" {
		fmt.Println("Creating a new client for:", str)
	}

	if err != nil {
		return nil, err
	}

	db, err := sqlx.Open("postgres", str)
	if err != nil {
		return nil, err
	}

	client := Client{
		db:               db,
		ConnectionString: str,
		History:          history.New(),
	}

	client.setServerVersion()
	return &client, nil
}

func NewFromUrl(url string, sshInfo *shared.SSHInfo) (*Client, error) {
	var tunnel *Tunnel

	if sshInfo != nil {
		if command.Opts.DisableSSH {
			return nil, fmt.Errorf("ssh connections are disabled")
		}
		if command.Opts.Debug {
			fmt.Println("Opening SSH tunnel for:", sshInfo)
		}

		tunnel, err := NewTunnel(sshInfo, url)
		if err != nil {
			tunnel.Close()
			return nil, err
		}

		err = tunnel.Configure()
		if err != nil {
			tunnel.Close()
			return nil, err
		}

		go tunnel.Start()

		uri, err := neturl.Parse(url)
		if err != nil {
			tunnel.Close()
			return nil, err
		}

		// Override remote postgres port with local proxy port
		url = strings.Replace(url, uri.Host, fmt.Sprintf("127.0.0.1:%v", tunnel.Port), 1)
	}

	if command.Opts.Debug {
		fmt.Println("Creating a new client for:", url)
	}

	uri, err := neturl.Parse(url)
	if err == nil && uri.Path == "" {
		return nil, fmt.Errorf("Database name is not provided")
	}

	db, err := sqlx.Open("postgres", url)
	if err != nil {
		return nil, err
	}

	client := Client{
		db:               db,
		tunnel:           tunnel,
		ConnectionString: url,
		History:          history.New(),
	}

	client.setServerVersion()
	return &client, nil
}

func (client *Client) setServerVersion() {
	res, err := client.query("SELECT version()")
	if err != nil || len(res.Rows) < 1 {
		return
	}

	version := res.Rows[0][0].(string)

	// Detect postgresql
	matches := postgresSignature.FindAllStringSubmatch(version, 1)
	if len(matches) > 0 {
		client.serverType = postgresType
		client.serverVersion = matches[0][1]
		return
	}

	// Detect cockroachdb
	matches = cockroachSignature.FindAllStringSubmatch(version, 1)
	if len(matches) > 0 {
		client.serverType = cockroachType
		client.serverVersion = matches[0][1]
		return
	}
}

func (client *Client) Test() error {
	return client.db.Ping()
}

func (client *Client) Info() (*Result, error) {
	return client.query(statements.Info)
}

func (client *Client) Databases() ([]string, error) {
	return client.fetchRows(statements.Databases)
}

func (client *Client) Schemas() ([]string, error) {
	return client.fetchRows(statements.Schemas)
}

func (client *Client) Objects() (*Result, error) {
	return client.query(statements.Objects)
}

func (client *Client) Table(table string) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	return client.query(statements.TableSchema, schema, table)
}

func (client *Client) MaterializedView(name string) (*Result, error) {
	return client.query(statements.MaterializedView, name)
}

func (client *Client) TableRows(table string, opts RowsOptions) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	sql := fmt.Sprintf(`SELECT * FROM "%s"."%s"`, schema, table)

	if opts.Where != "" {
		sql += fmt.Sprintf(" WHERE %s", opts.Where)
	}

	if opts.SortColumn != "" {
		if opts.SortOrder == "" {
			opts.SortOrder = "ASC"
		}

		sql += fmt.Sprintf(` ORDER BY "%s" %s`, opts.SortColumn, opts.SortOrder)
	}

	if opts.Limit > 0 {
		sql += fmt.Sprintf(" LIMIT %d", opts.Limit)
	}

	if opts.Offset > 0 {
		sql += fmt.Sprintf(" OFFSET %d", opts.Offset)
	}

	return client.query(sql)
}

func (client *Client) EstimatedTableRowsCount(table string, opts RowsOptions) (*Result, error) {
	sql := fmt.Sprintf(`SELECT reltuples FROM pg_class WHERE relname = '%s'`, table)

	result, err := client.query(sql)
	if err != nil {
		return nil, err
	}
	// float64 to int64 conversion
	estimatedRowsCount := result.Rows[0][0].(float64)
	result.Rows[0] = Row{int64(estimatedRowsCount)}

	return result, nil
}

func (client *Client) TableRowsCount(table string, opts RowsOptions) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	sql := fmt.Sprintf(`SELECT COUNT(1) FROM "%s"."%s"`, schema, table)

	if opts.Where != "" {
		sql += fmt.Sprintf(" WHERE %s", opts.Where)
	} else if client.serverType == postgresType {
		tableInfo, err := client.TableInfo(table)
		if err != nil {
			return nil, err
		}
		estimatedRowsCount := tableInfo.Rows[0][3].(float64)
		if estimatedRowsCount > 100000 {
			return client.EstimatedTableRowsCount(table, opts)
		}
	}

	return client.query(sql)
}

func (client *Client) TableInfo(table string) (*Result, error) {
	if client.serverType == cockroachType {
		return client.query(statements.TableInfoCockroach)
	}
	return client.query(statements.TableInfo, table)
}

func (client *Client) TableIndexes(table string) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	res, err := client.query(statements.TableIndexes, schema, table)

	if err != nil {
		return nil, err
	}

	return res, err
}

func (client *Client) TableConstraints(table string) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	res, err := client.query(statements.TableConstraints, schema, table)

	if err != nil {
		return nil, err
	}

	return res, err
}

// Returns all active queriers on the server
func (client *Client) Activity() (*Result, error) {
	if client.serverType == cockroachType {
		return client.query("SHOW QUERIES")
	}

	version := getMajorMinorVersion(client.serverVersion)
	query := statements.Activity[version]
	if query == "" {
		query = statements.Activity["default"]
	}

	return client.query(query)
}

func (client *Client) Query(query string) (*Result, error) {
	res, err := client.query(query)

	// Save history records only if query did not fail
	if err == nil && !client.hasHistoryRecord(query) {
		client.History = append(client.History, history.NewRecord(query))
	}

	return res, err
}

func (client *Client) SetReadOnlyMode() error {
	var value string
	if err := client.db.Get(&value, "SHOW default_transaction_read_only;"); err != nil {
		return err
	}

	if value == "off" {
		_, err := client.db.Exec("SET default_transaction_read_only=on;")
		return err
	}

	return nil
}

func (client *Client) ServerVersion() string {
	return fmt.Sprintf("%s %s", client.serverType, client.serverVersion)
}

func (client *Client) query(query string, args ...interface{}) (*Result, error) {
	// Update the last usage time
	defer func() {
		client.lastQueryTime = time.Now().UTC()
	}()

	// We're going to force-set transaction mode on every query.
	// This is needed so that default mode could not be changed by user.
	if command.Opts.ReadOnly {
		if err := client.SetReadOnlyMode(); err != nil {
			return nil, err
		}
	}

	action := strings.ToLower(strings.Split(query, " ")[0])
	if action == "update" || action == "delete" {
		res, err := client.db.Exec(query, args...)
		if err != nil {
			return nil, err
		}

		affected, err := res.RowsAffected()
		if err != nil {
			return nil, err
		}

		result := Result{
			Columns: []string{"Rows Affected"},
			Rows: []Row{
				Row{affected},
			},
		}

		return &result, nil
	}

	rows, err := client.db.Queryx(query, args...)
	if err != nil {
		return nil, err
	}

	defer rows.Close()

	cols, err := rows.Columns()
	if err != nil {
		return nil, err
	}

	// Make sure to never return null colums
	if cols == nil {
		cols = []string{}
	}

	result := Result{
		Columns: cols,
		Rows:    []Row{},
	}

	for rows.Next() {
		obj, err := rows.SliceScan()

		for i, item := range obj {
			if item == nil {
				obj[i] = nil
			} else {
				t := reflect.TypeOf(item).Kind().String()

				if t == "slice" {
					obj[i] = string(item.([]byte))
				}
			}
		}

		if err == nil {
			result.Rows = append(result.Rows, obj)
		}
	}

	result.PrepareBigints()

	return &result, nil
}

// Close database connection
func (client *Client) Close() error {
	if client.tunnel != nil {
		client.tunnel.Close()
	}

	if client.db != nil {
		return client.db.Close()
	}

	return nil
}

func (client *Client) IsIdle() bool {
	mins := int(time.Since(client.lastQueryTime).Minutes())

	if command.Opts.ConnectionIdleTimeout > 0 {
		return mins >= command.Opts.ConnectionIdleTimeout
	}

	return false
}

// Fetch all rows as strings for a single column
func (client *Client) fetchRows(q string) ([]string, error) {
	res, err := client.query(q)

	if err != nil {
		return nil, err
	}

	// Init empty slice so json.Marshal will encode it to "[]" instead of "null"
	results := make([]string, 0)

	for _, row := range res.Rows {
		results = append(results, row[0].(string))
	}

	return results, nil
}

func (client *Client) hasHistoryRecord(query string) bool {
	result := false

	for _, record := range client.History {
		if record.Query == query {
			result = true
			break
		}
	}

	return result
}
<|endoftext|>


<commit_before>// +build !appengine

package internal

import (
	"reflect"
	"unsafe"
)

func BytesToString(b []byte) string {
	bytesHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	strHeader := reflect.StringHeader{
		Data: bytesHeader.Data,
		Len:  bytesHeader.Len,
	}
	return *(*string)(unsafe.Pointer(&strHeader))
}

func StringToBytes(s string) []byte {
	sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
	bh := reflect.SliceHeader{
		Data: sh.Data,
		Len:  sh.Len,
		Cap:  sh.Len,
	}
	return *(*[]byte)(unsafe.Pointer(&bh))
}
<commit_msg>fix unsafe.go<commit_after>// +build !appengine

package internal

import (
	"unsafe"
)

// BytesToString converts byte slice to string.
func BytesToString(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}

// StringToBytes converts string to byte slice.
func StringToBytes(s string) []byte {
	return *(*[]byte)(unsafe.Pointer(
		&struct {
		string
		Cap int
		}{s,len(s)},
	))
}
<|endoftext|>


<commit_before>package config

import (
	"github.com/thetechnick/nginx-ingress/pkg/storage/pb"
	api_v1 "k8s.io/client-go/pkg/api/v1"
	extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1"
	"k8s.io/client-go/tools/cache"
)

// KeyFunc is used to get the k8s object key,
// which identifies an object
var KeyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc

// Upstream describes an NGINX upstream
type Upstream struct {
	Name            string
	UpstreamServers []UpstreamServer
}

// NewUpstreamWithDefaultServer creates an upstream with the default server.
// proxy_pass to an upstream with the default server returns 502.
// We use it for services that have no endpoints
func NewUpstreamWithDefaultServer(name string) Upstream {
	return Upstream{
		Name:            name,
		UpstreamServers: []UpstreamServer{UpstreamServer{Address: "127.0.0.1", Port: "8181"}},
	}
}

// UpstreamServer describes a server in an NGINX upstream
type UpstreamServer struct {
	Address string
	Port    string
}

// Server describes an NGINX server
type Server struct {
	ServerSnippets        []string
	Name                  string
	ServerTokens          bool
	Locations             []Location
	Upstreams             []Upstream
	SSL                   bool
	SSLCertificate        string
	SSLCertificateKey     string
	HTTP2                 bool
	RedirectToHTTPS       bool
	ProxyProtocol         bool
	HSTS                  bool
	HSTSMaxAge            int64
	HSTSIncludeSubdomains bool
	ProxyHideHeaders      []string
	ProxyPassHeaders      []string

	// http://nginx.org/en/docs/http/ngx_http_realip_module.html
	RealIPHeader    string
	SetRealIPFrom   []string
	RealIPRecursive bool

	TLSCertificateFile *pb.TLSCertificate
}

// Location describes an NGINX location
type Location struct {
	LocationSnippets     []string
	Path                 string
	Upstream             Upstream
	ProxyConnectTimeout  string
	ProxyReadTimeout     string
	ClientMaxBodySize    string
	Websocket            bool
	Rewrite              string
	SSL                  bool
	ProxyBuffering       bool
	ProxyBuffers         string
	ProxyBufferSize      string
	ProxyMaxTempFileSize string
}

// IngressEx holds an Ingress along with Endpoints of the services
// that are referenced in this Ingress
type IngressEx struct {
	Ingress   *extensions.Ingress
	Endpoints map[string][]string       // indexed by service Name + port
	Secrets   map[string]*api_v1.Secret // indexed by secret name
}

// CreateLocation creates a new location from the given params
func CreateLocation(path string, upstream Upstream, cfg *GlobalConfig, websocket bool, rewrite string, ssl bool) Location {
	loc := Location{
		Path:                 path,
		Upstream:             upstream,
		ProxyConnectTimeout:  cfg.ProxyConnectTimeout,
		ProxyReadTimeout:     cfg.ProxyReadTimeout,
		ClientMaxBodySize:    cfg.ClientMaxBodySize,
		Websocket:            websocket,
		Rewrite:              rewrite,
		SSL:                  ssl,
		ProxyBuffering:       cfg.ProxyBuffering,
		ProxyBuffers:         cfg.ProxyBuffers,
		ProxyBufferSize:      cfg.ProxyBufferSize,
		ProxyMaxTempFileSize: cfg.ProxyMaxTempFileSize,
		LocationSnippets:     cfg.LocationSnippets,
	}

	return loc
}

// CertificatePair describes a key and certificate pair
type CertificatePair struct {
	Key         []byte
	Certificate []byte
}
<commit_msg>Removed CertificatePair config<commit_after>package config

import (
	"github.com/thetechnick/nginx-ingress/pkg/storage/pb"
	api_v1 "k8s.io/client-go/pkg/api/v1"
	extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1"
	"k8s.io/client-go/tools/cache"
)

// KeyFunc is used to get the k8s object key,
// which identifies an object
var KeyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc

// Upstream describes an NGINX upstream
type Upstream struct {
	Name            string
	UpstreamServers []UpstreamServer
}

// NewUpstreamWithDefaultServer creates an upstream with the default server.
// proxy_pass to an upstream with the default server returns 502.
// We use it for services that have no endpoints
func NewUpstreamWithDefaultServer(name string) Upstream {
	return Upstream{
		Name:            name,
		UpstreamServers: []UpstreamServer{UpstreamServer{Address: "127.0.0.1", Port: "8181"}},
	}
}

// UpstreamServer describes a server in an NGINX upstream
type UpstreamServer struct {
	Address string
	Port    string
}

// Server describes an NGINX server
type Server struct {
	ServerSnippets        []string
	Name                  string
	ServerTokens          bool
	Locations             []Location
	Upstreams             []Upstream
	SSL                   bool
	SSLCertificate        string
	SSLCertificateKey     string
	HTTP2                 bool
	RedirectToHTTPS       bool
	ProxyProtocol         bool
	HSTS                  bool
	HSTSMaxAge            int64
	HSTSIncludeSubdomains bool
	ProxyHideHeaders      []string
	ProxyPassHeaders      []string

	// http://nginx.org/en/docs/http/ngx_http_realip_module.html
	RealIPHeader    string
	SetRealIPFrom   []string
	RealIPRecursive bool

	TLSCertificateFile *pb.TLSCertificate
}

// Location describes an NGINX location
type Location struct {
	LocationSnippets     []string
	Path                 string
	Upstream             Upstream
	ProxyConnectTimeout  string
	ProxyReadTimeout     string
	ClientMaxBodySize    string
	Websocket            bool
	Rewrite              string
	SSL                  bool
	ProxyBuffering       bool
	ProxyBuffers         string
	ProxyBufferSize      string
	ProxyMaxTempFileSize string
}

// IngressEx holds an Ingress along with Endpoints of the services
// that are referenced in this Ingress
type IngressEx struct {
	Ingress   *extensions.Ingress
	Endpoints map[string][]string       // indexed by service Name + port
	Secrets   map[string]*api_v1.Secret // indexed by secret name
}

// CreateLocation creates a new location from the given params
func CreateLocation(path string, upstream Upstream, cfg *GlobalConfig, websocket bool, rewrite string, ssl bool) Location {
	loc := Location{
		Path:                 path,
		Upstream:             upstream,
		ProxyConnectTimeout:  cfg.ProxyConnectTimeout,
		ProxyReadTimeout:     cfg.ProxyReadTimeout,
		ClientMaxBodySize:    cfg.ClientMaxBodySize,
		Websocket:            websocket,
		Rewrite:              rewrite,
		SSL:                  ssl,
		ProxyBuffering:       cfg.ProxyBuffering,
		ProxyBuffers:         cfg.ProxyBuffers,
		ProxyBufferSize:      cfg.ProxyBufferSize,
		ProxyMaxTempFileSize: cfg.ProxyMaxTempFileSize,
		LocationSnippets:     cfg.LocationSnippets,
	}

	return loc
}
<|endoftext|>


<commit_before>package config

type DaemonConfig struct {
	Providers Providers `yaml:"providers"`
	Version   string    `yaml:"version"`
}
type Providers struct {
	Aws        []Aws        `yaml:"aws"`
	Vsphere    []Vsphere    `yaml:"vsphere"`
	Virtualbox []Virtualbox `yaml:"virtualbox"`
}

type Aws struct {
	Name              string `yaml:"name"`
	Region            string `yaml:"region"`
	Zone              string `yaml:"zone"`
}

type Vsphere struct {
	Name            string `yaml:"name"`
	VsphereUser     string `yaml:"vsphere_user"`
	VspherePassword string `yaml:"vsphere_password"`
	VsphereURL      string `yaml:"vsphere_url"`
	Datastore       string `yaml:"datastore"`
	NetworkLabel    string `yaml:"network_label"`
}

type Virtualbox struct {
	Name                  string                `yaml:"name"`
	AdapterName           string                `yaml:"adapter_name"`
	VirtualboxAdapterType VirtualboxAdapterType `yaml:"adapter_type"`
}

type VirtualboxAdapterType string

const (
	BridgedAdapter  = VirtualboxAdapterType("bridged")
	HostOnlyAdapter = VirtualboxAdapterType("host_only")
)

type ClientConfig struct {
	Host string `yaml:"host"`
}
<commit_msg>fix typo<commit_after>package config

type DaemonConfig struct {
	Providers Providers `yaml:"providers"`
	Version   string    `yaml:"version"`
}
type Providers struct {
	Aws        []Aws        `yaml:"aws"`
	Vsphere    []Vsphere    `yaml:"vsphere"`
	Virtualbox []Virtualbox `yaml:"virtualbox"`
}

type Aws struct {
	Name              string `yaml:"name"`
	Region            string `yaml:"region"`
	Zone              string `yaml:"zone"`
}

type Vsphere struct {
	Name            string `yaml:"name"`
	VsphereUser     string `yaml:"vsphere_user"`
	VspherePassword string `yaml:"vsphere_password"`
	VsphereURL      string `yaml:"vsphere_url"`
	Datastore       string `yaml:"datastore"`
	NetworkLabel    string `yaml:"network"`
}

type Virtualbox struct {
	Name                  string                `yaml:"name"`
	AdapterName           string                `yaml:"adapter_name"`
	VirtualboxAdapterType VirtualboxAdapterType `yaml:"adapter_type"`
}

type VirtualboxAdapterType string

const (
	BridgedAdapter  = VirtualboxAdapterType("bridged")
	HostOnlyAdapter = VirtualboxAdapterType("host_only")
)

type ClientConfig struct {
	Host string `yaml:"host"`
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. 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 kubelet

import (
	"fmt"
	"strconv"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
	execprobe "github.com/GoogleCloudPlatform/kubernetes/pkg/probe/exec"
	httprobe "github.com/GoogleCloudPlatform/kubernetes/pkg/probe/http"
	tcprobe "github.com/GoogleCloudPlatform/kubernetes/pkg/probe/tcp"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec"

	"github.com/golang/glog"
)

const maxProbeRetries = 3

// probeContainer probes the liveness/readiness of the given container.
// If the container's liveness probe is unsuccessful, set readiness to false.
// If liveness is successful, do a readiness check and set readiness accordingly.
func (kl *Kubelet) probeContainer(pod *api.Pod, status api.PodStatus, container api.Container, containerID string, createdAt int64) (probe.Result, error) {
	// Probe liveness.
	live, err := kl.probeContainerLiveness(pod, status, container, createdAt)
	if err != nil {
		glog.V(1).Infof("Liveness probe errored: %v", err)
		kl.readinessManager.SetReadiness(containerID, false)
		return probe.Unknown, err
	}
	if live != probe.Success {
		glog.V(1).Infof("Liveness probe unsuccessful: %v", live)
		kl.readinessManager.SetReadiness(containerID, false)
		return live, nil
	}

	// Probe readiness.
	ready, err := kl.probeContainerReadiness(pod, status, container, createdAt)
	if err == nil && ready == probe.Success {
		glog.V(3).Infof("Readiness probe successful: %v", ready)
		kl.readinessManager.SetReadiness(containerID, true)
		return probe.Success, nil
	}

	glog.V(1).Infof("Readiness probe failed/errored: %v, %v", ready, err)
	kl.readinessManager.SetReadiness(containerID, false)

	ref, ok := kl.containerRefManager.GetRef(containerID)
	if !ok {
		glog.Warningf("No ref for pod '%v' - '%v'", containerID, container.Name)
	} else {
		kl.recorder.Eventf(ref, "unhealthy", "Liveness Probe Failed %v - %v", containerID, container.Name)
	}
	return ready, err
}

// probeContainerLiveness probes the liveness of a container.
// If the initalDelay since container creation on liveness probe has not passed the probe will return probe.Success.
func (kl *Kubelet) probeContainerLiveness(pod *api.Pod, status api.PodStatus, container api.Container, createdAt int64) (probe.Result, error) {
	p := container.LivenessProbe
	if p == nil {
		return probe.Success, nil
	}
	if time.Now().Unix()-createdAt < p.InitialDelaySeconds {
		return probe.Success, nil
	}
	return kl.runProbeWithRetries(p, pod, status, container, maxProbeRetries)
}

// probeContainerLiveness probes the readiness of a container.
// If the initial delay on the readiness probe has not passed the probe will return probe.Failure.
func (kl *Kubelet) probeContainerReadiness(pod *api.Pod, status api.PodStatus, container api.Container, createdAt int64) (probe.Result, error) {
	p := container.ReadinessProbe
	if p == nil {
		return probe.Success, nil
	}
	if time.Now().Unix()-createdAt < p.InitialDelaySeconds {
		return probe.Failure, nil
	}
	return kl.runProbeWithRetries(p, pod, status, container, maxProbeRetries)
}

// runProbeWithRetries tries to probe the container in a finite loop, it returns the last result
// if it never succeeds.
func (kl *Kubelet) runProbeWithRetries(p *api.Probe, pod *api.Pod, status api.PodStatus, container api.Container, retires int) (probe.Result, error) {
	var err error
	var result probe.Result
	for i := 0; i < retires; i++ {
		result, err = kl.runProbe(p, pod, status, container)
		if result == probe.Success {
			return probe.Success, nil
		}
	}
	return result, err
}

func (kl *Kubelet) runProbe(p *api.Probe, pod *api.Pod, status api.PodStatus, container api.Container) (probe.Result, error) {
	timeout := time.Duration(p.TimeoutSeconds) * time.Second
	if p.Exec != nil {
		return kl.prober.exec.Probe(kl.newExecInContainer(pod, container))
	}
	if p.HTTPGet != nil {
		port, err := extractPort(p.HTTPGet.Port, container)
		if err != nil {
			return probe.Unknown, err
		}
		host, port, path := extractGetParams(p.HTTPGet, status, port)
		return kl.prober.http.Probe(host, port, path, timeout)
	}
	if p.TCPSocket != nil {
		port, err := extractPort(p.TCPSocket.Port, container)
		if err != nil {
			return probe.Unknown, err
		}
		return kl.prober.tcp.Probe(status.PodIP, port, timeout)
	}
	glog.Warningf("Failed to find probe builder for %s %+v", container.Name, container.LivenessProbe)
	return probe.Unknown, nil
}

func extractGetParams(action *api.HTTPGetAction, status api.PodStatus, port int) (string, int, string) {
	host := action.Host
	if host == "" {
		host = status.PodIP
	}
	return host, port, action.Path
}

func extractPort(param util.IntOrString, container api.Container) (int, error) {
	port := -1
	var err error
	switch param.Kind {
	case util.IntstrInt:
		port := param.IntVal
		if port > 0 && port < 65536 {
			return port, nil
		}
		return port, fmt.Errorf("invalid port number: %v", port)
	case util.IntstrString:
		port = findPortByName(container, param.StrVal)
		if port == -1 {
			// Last ditch effort - maybe it was an int stored as string?
			if port, err = strconv.Atoi(param.StrVal); err != nil {
				return port, err
			}
		}
		if port > 0 && port < 65536 {
			return port, nil
		}
		return port, fmt.Errorf("invalid port number: %v", port)
	default:
		return port, fmt.Errorf("IntOrString had no kind: %+v", param)
	}
}

// findPortByName is a helper function to look up a port in a container by name.
// Returns the HostPort if found, -1 if not found.
func findPortByName(container api.Container, portName string) int {
	for _, port := range container.Ports {
		if port.Name == portName {
			return port.HostPort
		}
	}
	return -1
}

type execInContainer struct {
	run func() ([]byte, error)
}

func (kl *Kubelet) newExecInContainer(pod *api.Pod, container api.Container) exec.Cmd {
	uid := pod.UID
	podFullName := kubecontainer.GetPodFullName(pod)
	return execInContainer{func() ([]byte, error) {
		return kl.RunInContainer(podFullName, uid, container.Name, container.LivenessProbe.Exec.Command)
	}}
}

func (eic execInContainer) CombinedOutput() ([]byte, error) {
	return eic.run()
}

func (eic execInContainer) SetDir(dir string) {
	//unimplemented
}

func newProbeHolder() probeHolder {
	return probeHolder{
		exec: execprobe.New(),
		http: httprobe.New(),
		tcp:  tcprobe.New(),
	}
}

type probeHolder struct {
	exec execprobe.ExecProber
	http httprobe.HTTPProber
	tcp  tcprobe.TCPProber
}
<commit_msg>Fix comment typo probeContainerReadiness<commit_after>/*
Copyright 2014 Google Inc. 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 kubelet

import (
	"fmt"
	"strconv"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
	execprobe "github.com/GoogleCloudPlatform/kubernetes/pkg/probe/exec"
	httprobe "github.com/GoogleCloudPlatform/kubernetes/pkg/probe/http"
	tcprobe "github.com/GoogleCloudPlatform/kubernetes/pkg/probe/tcp"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec"

	"github.com/golang/glog"
)

const maxProbeRetries = 3

// probeContainer probes the liveness/readiness of the given container.
// If the container's liveness probe is unsuccessful, set readiness to false.
// If liveness is successful, do a readiness check and set readiness accordingly.
func (kl *Kubelet) probeContainer(pod *api.Pod, status api.PodStatus, container api.Container, containerID string, createdAt int64) (probe.Result, error) {
	// Probe liveness.
	live, err := kl.probeContainerLiveness(pod, status, container, createdAt)
	if err != nil {
		glog.V(1).Infof("Liveness probe errored: %v", err)
		kl.readinessManager.SetReadiness(containerID, false)
		return probe.Unknown, err
	}
	if live != probe.Success {
		glog.V(1).Infof("Liveness probe unsuccessful: %v", live)
		kl.readinessManager.SetReadiness(containerID, false)
		return live, nil
	}

	// Probe readiness.
	ready, err := kl.probeContainerReadiness(pod, status, container, createdAt)
	if err == nil && ready == probe.Success {
		glog.V(3).Infof("Readiness probe successful: %v", ready)
		kl.readinessManager.SetReadiness(containerID, true)
		return probe.Success, nil
	}

	glog.V(1).Infof("Readiness probe failed/errored: %v, %v", ready, err)
	kl.readinessManager.SetReadiness(containerID, false)

	ref, ok := kl.containerRefManager.GetRef(containerID)
	if !ok {
		glog.Warningf("No ref for pod '%v' - '%v'", containerID, container.Name)
	} else {
		kl.recorder.Eventf(ref, "unhealthy", "Liveness Probe Failed %v - %v", containerID, container.Name)
	}
	return ready, err
}

// probeContainerLiveness probes the liveness of a container.
// If the initalDelay since container creation on liveness probe has not passed the probe will return probe.Success.
func (kl *Kubelet) probeContainerLiveness(pod *api.Pod, status api.PodStatus, container api.Container, createdAt int64) (probe.Result, error) {
	p := container.LivenessProbe
	if p == nil {
		return probe.Success, nil
	}
	if time.Now().Unix()-createdAt < p.InitialDelaySeconds {
		return probe.Success, nil
	}
	return kl.runProbeWithRetries(p, pod, status, container, maxProbeRetries)
}

// probeContainerReadiness probes the readiness of a container.
// If the initial delay on the readiness probe has not passed the probe will return probe.Failure.
func (kl *Kubelet) probeContainerReadiness(pod *api.Pod, status api.PodStatus, container api.Container, createdAt int64) (probe.Result, error) {
	p := container.ReadinessProbe
	if p == nil {
		return probe.Success, nil
	}
	if time.Now().Unix()-createdAt < p.InitialDelaySeconds {
		return probe.Failure, nil
	}
	return kl.runProbeWithRetries(p, pod, status, container, maxProbeRetries)
}

// runProbeWithRetries tries to probe the container in a finite loop, it returns the last result
// if it never succeeds.
func (kl *Kubelet) runProbeWithRetries(p *api.Probe, pod *api.Pod, status api.PodStatus, container api.Container, retires int) (probe.Result, error) {
	var err error
	var result probe.Result
	for i := 0; i < retires; i++ {
		result, err = kl.runProbe(p, pod, status, container)
		if result == probe.Success {
			return probe.Success, nil
		}
	}
	return result, err
}

func (kl *Kubelet) runProbe(p *api.Probe, pod *api.Pod, status api.PodStatus, container api.Container) (probe.Result, error) {
	timeout := time.Duration(p.TimeoutSeconds) * time.Second
	if p.Exec != nil {
		return kl.prober.exec.Probe(kl.newExecInContainer(pod, container))
	}
	if p.HTTPGet != nil {
		port, err := extractPort(p.HTTPGet.Port, container)
		if err != nil {
			return probe.Unknown, err
		}
		host, port, path := extractGetParams(p.HTTPGet, status, port)
		return kl.prober.http.Probe(host, port, path, timeout)
	}
	if p.TCPSocket != nil {
		port, err := extractPort(p.TCPSocket.Port, container)
		if err != nil {
			return probe.Unknown, err
		}
		return kl.prober.tcp.Probe(status.PodIP, port, timeout)
	}
	glog.Warningf("Failed to find probe builder for %s %+v", container.Name, container.LivenessProbe)
	return probe.Unknown, nil
}

func extractGetParams(action *api.HTTPGetAction, status api.PodStatus, port int) (string, int, string) {
	host := action.Host
	if host == "" {
		host = status.PodIP
	}
	return host, port, action.Path
}

func extractPort(param util.IntOrString, container api.Container) (int, error) {
	port := -1
	var err error
	switch param.Kind {
	case util.IntstrInt:
		port := param.IntVal
		if port > 0 && port < 65536 {
			return port, nil
		}
		return port, fmt.Errorf("invalid port number: %v", port)
	case util.IntstrString:
		port = findPortByName(container, param.StrVal)
		if port == -1 {
			// Last ditch effort - maybe it was an int stored as string?
			if port, err = strconv.Atoi(param.StrVal); err != nil {
				return port, err
			}
		}
		if port > 0 && port < 65536 {
			return port, nil
		}
		return port, fmt.Errorf("invalid port number: %v", port)
	default:
		return port, fmt.Errorf("IntOrString had no kind: %+v", param)
	}
}

// findPortByName is a helper function to look up a port in a container by name.
// Returns the HostPort if found, -1 if not found.
func findPortByName(container api.Container, portName string) int {
	for _, port := range container.Ports {
		if port.Name == portName {
			return port.HostPort
		}
	}
	return -1
}

type execInContainer struct {
	run func() ([]byte, error)
}

func (kl *Kubelet) newExecInContainer(pod *api.Pod, container api.Container) exec.Cmd {
	uid := pod.UID
	podFullName := kubecontainer.GetPodFullName(pod)
	return execInContainer{func() ([]byte, error) {
		return kl.RunInContainer(podFullName, uid, container.Name, container.LivenessProbe.Exec.Command)
	}}
}

func (eic execInContainer) CombinedOutput() ([]byte, error) {
	return eic.run()
}

func (eic execInContainer) SetDir(dir string) {
	//unimplemented
}

func newProbeHolder() probeHolder {
	return probeHolder{
		exec: execprobe.New(),
		http: httprobe.New(),
		tcp:  tcprobe.New(),
	}
}

type probeHolder struct {
	exec execprobe.ExecProber
	http httprobe.HTTPProber
	tcp  tcprobe.TCPProber
}
<|endoftext|>


<commit_before>package listen

import (
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"time"

	"github.com/gorilla/websocket"
)

type ErrConnectionFail struct {
	resp *http.Response
	err  error
}

func (e *ErrConnectionFail) Error() string {
	var status *int
	if e.resp != nil {
		status = &e.resp.StatusCode
	}
	return fmt.Sprintf("connection failure (status %v): %v", status, e.err)
}

func Watch(
	target string, header http.Header, finish <-chan struct{},
) (<-chan []byte, <-chan error, error) {

	u, err := url.Parse(target)
	if err != nil {
		return nil, nil, err
	}

	if u.User != nil {
		userPassBytes := []byte(u.User.String() + ":")
		token := base64.StdEncoding.EncodeToString(userPassBytes)
		header.Add("Authorization", fmt.Sprintf("Basic %v", token))
		u.User = nil
	}

	conn, resp, err := websocket.DefaultDialer.Dial(u.String(), header)
	if err != nil {
		return nil, nil, &ErrConnectionFail{resp, err}
	}

	const (
		pongWait = 40 * time.Second
	)

	const MiB = 1 << 20
	conn.SetReadLimit(1 * MiB)

	conn.SetReadDeadline(time.Now().Add(pongWait))
	conn.SetPongHandler(func(string) error {
		log.Println("Pong")
		conn.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})

	messages := make(chan []byte, 1)
	errors := make(chan error, 1)
	readerDone := make(chan struct{})

	// Writer goroutine
	go func() {
		defer conn.Close()

		for {
			select {
			case <-time.After(20 * time.Second):
				log.Println("Ping")
				conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
				err := conn.WriteMessage(websocket.PingMessage, []byte{})
				if err != nil {
					log.Printf("Error in WriteMessage: %v", err)
					return
				}
			case <-finish:
				return
			case <-readerDone:
				return
			}
		}
	}()

	// Reader goroutine
	go func() {
		defer close(readerDone)
		defer close(errors)
		defer close(messages)

		for {
			_, r, err := conn.NextReader()
			if err != nil {
				select {
				case <-finish:
					// We've been requested to finish, ignore the error.
					return
				default:
				}

				errors <- err
				log.Printf("Error in NextReader(): %v", err)
				return
			}

			m, err := ioutil.ReadAll(r)
			if err != nil {
				select {
				case <-finish:
					// We've been requested to finish, ignore the error.
					return
				default:
				}

				errors <- err
				log.Printf("Error in ReadAll(): %v", err)
				return
			}

			select {
			case messages <- m:
			case <-finish:
				return
			}

		}
	}()

	return messages, errors, nil
}

// This function is like Watch() except if the transport fails, it is
// automatically retried.
func RetryingWatch(
	target string, header http.Header, finish <-chan struct{},
) (<-chan []byte, <-chan error) {

	outm := make(chan []byte)
	oute := make(chan error)

	go func() {
		defer close(outm)
		defer close(oute)

		for {
			ms, errs, err := Watch(target, header, finish)
			if err != nil {
				oute <- err
				goto retry
			}

			log.Printf("Connected to %q", target)

			go func() {
				for m := range ms {
					outm <- m
				}
			}()

			for err := range errs {
				oute <- err
			}

			select {
			case <-finish:
				return
			default:
			}

		retry:
			log.Printf("Connection failed. Retrying in 5 seconds.")
			time.Sleep(5*time.Second + Jitter())
		}
	}()

	return outm, oute
}

// Return a random duration from -1s to +1s
func Jitter() time.Duration {
	return time.Duration(rand.Intn(2*int(time.Second))) - 1*time.Second
}
<commit_msg>Fix send-after-close<commit_after>package listen

import (
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

type ErrConnectionFail struct {
	resp *http.Response
	err  error
}

func (e *ErrConnectionFail) Error() string {
	var status *int
	if e.resp != nil {
		status = &e.resp.StatusCode
	}
	return fmt.Sprintf("connection failure (status %v): %v", status, e.err)
}

func Watch(
	target string, header http.Header, finish <-chan struct{},
) (<-chan []byte, <-chan error, error) {

	u, err := url.Parse(target)
	if err != nil {
		return nil, nil, err
	}

	if u.User != nil {
		userPassBytes := []byte(u.User.String() + ":")
		token := base64.StdEncoding.EncodeToString(userPassBytes)
		header.Add("Authorization", fmt.Sprintf("Basic %v", token))
		u.User = nil
	}

	conn, resp, err := websocket.DefaultDialer.Dial(u.String(), header)
	if err != nil {
		return nil, nil, &ErrConnectionFail{resp, err}
	}

	const (
		pongWait = 40 * time.Second
	)

	const MiB = 1 << 20
	conn.SetReadLimit(1 * MiB)

	conn.SetReadDeadline(time.Now().Add(pongWait))
	conn.SetPongHandler(func(string) error {
		log.Println("Pong")
		conn.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})

	messages := make(chan []byte, 1)
	errors := make(chan error, 1)
	readerDone := make(chan struct{})

	// Writer goroutine
	go func() {
		defer conn.Close()

		for {
			select {
			case <-time.After(20 * time.Second):
				log.Println("Ping")
				conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
				err := conn.WriteMessage(websocket.PingMessage, []byte{})
				if err != nil {
					log.Printf("Error in WriteMessage: %v", err)
					return
				}
			case <-finish:
				return
			case <-readerDone:
				return
			}
		}
	}()

	// Reader goroutine
	go func() {
		defer close(readerDone)
		defer close(errors)
		defer close(messages)

		for {
			_, r, err := conn.NextReader()
			if err != nil {
				select {
				case <-finish:
					// We've been requested to finish, ignore the error.
					return
				default:
				}

				errors <- err
				log.Printf("Error in NextReader(): %v", err)
				return
			}

			m, err := ioutil.ReadAll(r)
			if err != nil {
				select {
				case <-finish:
					// We've been requested to finish, ignore the error.
					return
				default:
				}

				errors <- err
				log.Printf("Error in ReadAll(): %v", err)
				return
			}

			select {
			case messages <- m:
			case <-finish:
				return
			}

		}
	}()

	return messages, errors, nil
}

// This function is like Watch() except if the transport fails, it is
// automatically retried.
func RetryingWatch(
	target string, header http.Header, finish <-chan struct{},
) (<-chan []byte, <-chan error) {

	outm := make(chan []byte)
	oute := make(chan error)

	go func() {
		defer close(outm)
		defer close(oute)

		var wg sync.WaitGroup
		defer wg.Wait()

		for {
			ms, errs, err := Watch(target, header, finish)
			if err != nil {
				oute <- err
				goto retry
			}

			log.Printf("Connected to %q", target)

			wg.Add(1)
			go func() {
				defer wg.Done()
				for m := range ms {
					outm <- m
				}
			}()

			for err := range errs {
				oute <- err
			}

			select {
			case <-finish:
				return
			default:
			}

		retry:
			log.Printf("Connection failed. Retrying in 5 seconds.")
			time.Sleep(5*time.Second + Jitter())
		}
	}()

	return outm, oute
}

// Return a random duration from -1s to +1s
func Jitter() time.Duration {
	return time.Duration(rand.Intn(2*int(time.Second))) - 1*time.Second
}
<|endoftext|>


<commit_before>// includes code from
// https://raw.githubusercontent.com/rcrowley/go-metrics/master/sample.go
// Copyright 2012 Richard Crowley. All rights reserved.

package metrics

import (
	"sync"
	"time"
)

// Timers capture the duration and rate of events.
type Timer interface {
	Metric

	Count() int64
	Max() int64
	Mean() float64
	Min() int64
	Percentile(float64) float64
	Percentiles([]float64) []float64
	Rate1() float64
	Rate5() float64
	Rate15() float64
	RateMean() float64
	StdDev() float64
	Sum() int64
	Time(func())
	Update(time.Duration)
	UpdateSince(time.Time)
	Variance() float64
}

// NewCustomTimer constructs a new StandardTimer from a Histogram and a Meter.
func NewCustomTimer(meta *MetricMeta, h Histogram, m Meter) Timer {
	if UseNilMetrics {
		return NilTimer{}
	}
	return &StandardTimer{
		MetricMeta: meta,
		histogram:  h,
		meter:      m,
	}
}

// NewTimer constructs a new StandardTimer using an exponentially-decaying
// sample with the same reservoir size and alpha as UNIX load averages.
func NewTimer(meta *MetricMeta) Timer {
	if UseNilMetrics {
		return NilTimer{}
	}
	return &StandardTimer{
		MetricMeta: meta,
		histogram:  NewHistogram(meta, NewExpDecaySample(1028, 0.015)),
		meter:      NewMeter(meta),
	}
}

func RegTimer(name string, tagStrings ...string) Timer {
	tr := NewTimer(NewMetricMeta(name, tagStrings))
	MetricStats.Register(tr)
	return tr
}

// NilTimer is a no-op Timer.
type NilTimer struct {
	*MetricMeta
	h Histogram
	m Meter
}

// Count is a no-op.
func (NilTimer) Count() int64 { return 0 }

// Max is a no-op.
func (NilTimer) Max() int64 { return 0 }

// Mean is a no-op.
func (NilTimer) Mean() float64 { return 0.0 }

// Min is a no-op.
func (NilTimer) Min() int64 { return 0 }

// Percentile is a no-op.
func (NilTimer) Percentile(p float64) float64 { return 0.0 }

// Percentiles is a no-op.
func (NilTimer) Percentiles(ps []float64) []float64 {
	return make([]float64, len(ps))
}

// Rate1 is a no-op.
func (NilTimer) Rate1() float64 { return 0.0 }

// Rate5 is a no-op.
func (NilTimer) Rate5() float64 { return 0.0 }

// Rate15 is a no-op.
func (NilTimer) Rate15() float64 { return 0.0 }

// RateMean is a no-op.
func (NilTimer) RateMean() float64 { return 0.0 }

// Snapshot is a no-op.
func (n NilTimer) Snapshot() Metric { return n }

// StdDev is a no-op.
func (NilTimer) StdDev() float64 { return 0.0 }

// Sum is a no-op.
func (NilTimer) Sum() int64 { return 0 }

// Time is a no-op.
func (NilTimer) Time(func()) {}

// Update is a no-op.
func (NilTimer) Update(time.Duration) {}

// UpdateSince is a no-op.
func (NilTimer) UpdateSince(time.Time) {}

// Variance is a no-op.
func (NilTimer) Variance() float64 { return 0.0 }

// StandardTimer is the standard implementation of a Timer and uses a Histogram
// and Meter.
type StandardTimer struct {
	*MetricMeta
	histogram Histogram
	meter     Meter
	mutex     sync.Mutex
}

// Count returns the number of events recorded.
func (t *StandardTimer) Count() int64 {
	return t.histogram.Count()
}

// Max returns the maximum value in the sample.
func (t *StandardTimer) Max() int64 {
	return t.histogram.Max()
}

// Mean returns the mean of the values in the sample.
func (t *StandardTimer) Mean() float64 {
	return t.histogram.Mean()
}

// Min returns the minimum value in the sample.
func (t *StandardTimer) Min() int64 {
	return t.histogram.Min()
}

// Percentile returns an arbitrary percentile of the values in the sample.
func (t *StandardTimer) Percentile(p float64) float64 {
	return t.histogram.Percentile(p)
}

// Percentiles returns a slice of arbitrary percentiles of the values in the
// sample.
func (t *StandardTimer) Percentiles(ps []float64) []float64 {
	return t.histogram.Percentiles(ps)
}

// Rate1 returns the one-minute moving average rate of events per second.
func (t *StandardTimer) Rate1() float64 {
	return t.meter.Rate1()
}

// Rate5 returns the five-minute moving average rate of events per second.
func (t *StandardTimer) Rate5() float64 {
	return t.meter.Rate5()
}

// Rate15 returns the fifteen-minute moving average rate of events per second.
func (t *StandardTimer) Rate15() float64 {
	return t.meter.Rate15()
}

// RateMean returns the meter's mean rate of events per second.
func (t *StandardTimer) RateMean() float64 {
	return t.meter.RateMean()
}

// Snapshot returns a read-only copy of the timer.
func (t *StandardTimer) Snapshot() Metric {
	t.mutex.Lock()
	defer t.mutex.Unlock()
	return &TimerSnapshot{
		MetricMeta: t.MetricMeta,
		histogram:  t.histogram.Snapshot().(*HistogramSnapshot),
		meter:      t.meter.Snapshot().(*MeterSnapshot),
	}
}

// StdDev returns the standard deviation of the values in the sample.
func (t *StandardTimer) StdDev() float64 {
	return t.histogram.StdDev()
}

// Sum returns the sum in the sample.
func (t *StandardTimer) Sum() int64 {
	return t.histogram.Sum()
}

// Record the duration of the execution of the given function.
func (t *StandardTimer) Time(f func()) {
	ts := time.Now()
	f()
	t.Update(time.Since(ts))
}

// Record the duration of an event.
func (t *StandardTimer) Update(d time.Duration) {
	t.mutex.Lock()
	defer t.mutex.Unlock()
	t.histogram.Update(int64(d))
	t.meter.Mark(1)
}

// Record the duration of an event that started at a time and ends now.
func (t *StandardTimer) UpdateSince(ts time.Time) {
	t.mutex.Lock()
	defer t.mutex.Unlock()
	t.histogram.Update(int64(time.Since(ts)))
	t.meter.Mark(1)
}

// Variance returns the variance of the values in the sample.
func (t *StandardTimer) Variance() float64 {
	return t.histogram.Variance()
}

// TimerSnapshot is a read-only copy of another Timer.
type TimerSnapshot struct {
	*MetricMeta
	histogram *HistogramSnapshot
	meter     *MeterSnapshot
}

// Count returns the number of events recorded at the time the snapshot was
// taken.
func (t *TimerSnapshot) Count() int64 { return t.histogram.Count() }

// Max returns the maximum value at the time the snapshot was taken.
func (t *TimerSnapshot) Max() int64 { return t.histogram.Max() }

// Mean returns the mean value at the time the snapshot was taken.
func (t *TimerSnapshot) Mean() float64 { return t.histogram.Mean() }

// Min returns the minimum value at the time the snapshot was taken.
func (t *TimerSnapshot) Min() int64 { return t.histogram.Min() }

// Percentile returns an arbitrary percentile of sampled values at the time the
// snapshot was taken.
func (t *TimerSnapshot) Percentile(p float64) float64 {
	return t.histogram.Percentile(p)
}

// Percentiles returns a slice of arbitrary percentiles of sampled values at
// the time the snapshot was taken.
func (t *TimerSnapshot) Percentiles(ps []float64) []float64 {
	return t.histogram.Percentiles(ps)
}

// Rate1 returns the one-minute moving average rate of events per second at the
// time the snapshot was taken.
func (t *TimerSnapshot) Rate1() float64 { return t.meter.Rate1() }

// Rate5 returns the five-minute moving average rate of events per second at
// the time the snapshot was taken.
func (t *TimerSnapshot) Rate5() float64 { return t.meter.Rate5() }

// Rate15 returns the fifteen-minute moving average rate of events per second
// at the time the snapshot was taken.
func (t *TimerSnapshot) Rate15() float64 { return t.meter.Rate15() }

// RateMean returns the meter's mean rate of events per second at the time the
// snapshot was taken.
func (t *TimerSnapshot) RateMean() float64 { return t.meter.RateMean() }

// Snapshot returns the snapshot.
func (t *TimerSnapshot) Snapshot() Metric { return t }

// StdDev returns the standard deviation of the values at the time the snapshot
// was taken.
func (t *TimerSnapshot) StdDev() float64 { return t.histogram.StdDev() }

// Sum returns the sum at the time the snapshot was taken.
func (t *TimerSnapshot) Sum() int64 { return t.histogram.Sum() }

// Time panics.
func (*TimerSnapshot) Time(func()) {
	panic("Time called on a TimerSnapshot")
}

// Update panics.
func (*TimerSnapshot) Update(time.Duration) {
	panic("Update called on a TimerSnapshot")
}

// UpdateSince panics.
func (*TimerSnapshot) UpdateSince(time.Time) {
	panic("UpdateSince called on a TimerSnapshot")
}

// Variance returns the variance of the values at the time the snapshot was
// taken.
func (t *TimerSnapshot) Variance() float64 { return t.histogram.Variance() }
<commit_msg>fix(metrics): default updateSince to ms<commit_after>// includes code from
// https://raw.githubusercontent.com/rcrowley/go-metrics/master/sample.go
// Copyright 2012 Richard Crowley. All rights reserved.

package metrics

import (
	"sync"
	"time"
)

// Timers capture the duration and rate of events.
type Timer interface {
	Metric

	Count() int64
	Max() int64
	Mean() float64
	Min() int64
	Percentile(float64) float64
	Percentiles([]float64) []float64
	Rate1() float64
	Rate5() float64
	Rate15() float64
	RateMean() float64
	StdDev() float64
	Sum() int64
	Time(func())
	Update(time.Duration)
	UpdateSince(time.Time)
	Variance() float64
}

// NewCustomTimer constructs a new StandardTimer from a Histogram and a Meter.
func NewCustomTimer(meta *MetricMeta, h Histogram, m Meter) Timer {
	if UseNilMetrics {
		return NilTimer{}
	}
	return &StandardTimer{
		MetricMeta: meta,
		histogram:  h,
		meter:      m,
	}
}

// NewTimer constructs a new StandardTimer using an exponentially-decaying
// sample with the same reservoir size and alpha as UNIX load averages.
func NewTimer(meta *MetricMeta) Timer {
	if UseNilMetrics {
		return NilTimer{}
	}
	return &StandardTimer{
		MetricMeta: meta,
		histogram:  NewHistogram(meta, NewExpDecaySample(1028, 0.015)),
		meter:      NewMeter(meta),
	}
}

func RegTimer(name string, tagStrings ...string) Timer {
	tr := NewTimer(NewMetricMeta(name, tagStrings))
	MetricStats.Register(tr)
	return tr
}

// NilTimer is a no-op Timer.
type NilTimer struct {
	*MetricMeta
	h Histogram
	m Meter
}

// Count is a no-op.
func (NilTimer) Count() int64 { return 0 }

// Max is a no-op.
func (NilTimer) Max() int64 { return 0 }

// Mean is a no-op.
func (NilTimer) Mean() float64 { return 0.0 }

// Min is a no-op.
func (NilTimer) Min() int64 { return 0 }

// Percentile is a no-op.
func (NilTimer) Percentile(p float64) float64 { return 0.0 }

// Percentiles is a no-op.
func (NilTimer) Percentiles(ps []float64) []float64 {
	return make([]float64, len(ps))
}

// Rate1 is a no-op.
func (NilTimer) Rate1() float64 { return 0.0 }

// Rate5 is a no-op.
func (NilTimer) Rate5() float64 { return 0.0 }

// Rate15 is a no-op.
func (NilTimer) Rate15() float64 { return 0.0 }

// RateMean is a no-op.
func (NilTimer) RateMean() float64 { return 0.0 }

// Snapshot is a no-op.
func (n NilTimer) Snapshot() Metric { return n }

// StdDev is a no-op.
func (NilTimer) StdDev() float64 { return 0.0 }

// Sum is a no-op.
func (NilTimer) Sum() int64 { return 0 }

// Time is a no-op.
func (NilTimer) Time(func()) {}

// Update is a no-op.
func (NilTimer) Update(time.Duration) {}

// UpdateSince is a no-op.
func (NilTimer) UpdateSince(time.Time) {}

// Variance is a no-op.
func (NilTimer) Variance() float64 { return 0.0 }

// StandardTimer is the standard implementation of a Timer and uses a Histogram
// and Meter.
type StandardTimer struct {
	*MetricMeta
	histogram Histogram
	meter     Meter
	mutex     sync.Mutex
}

// Count returns the number of events recorded.
func (t *StandardTimer) Count() int64 {
	return t.histogram.Count()
}

// Max returns the maximum value in the sample.
func (t *StandardTimer) Max() int64 {
	return t.histogram.Max()
}

// Mean returns the mean of the values in the sample.
func (t *StandardTimer) Mean() float64 {
	return t.histogram.Mean()
}

// Min returns the minimum value in the sample.
func (t *StandardTimer) Min() int64 {
	return t.histogram.Min()
}

// Percentile returns an arbitrary percentile of the values in the sample.
func (t *StandardTimer) Percentile(p float64) float64 {
	return t.histogram.Percentile(p)
}

// Percentiles returns a slice of arbitrary percentiles of the values in the
// sample.
func (t *StandardTimer) Percentiles(ps []float64) []float64 {
	return t.histogram.Percentiles(ps)
}

// Rate1 returns the one-minute moving average rate of events per second.
func (t *StandardTimer) Rate1() float64 {
	return t.meter.Rate1()
}

// Rate5 returns the five-minute moving average rate of events per second.
func (t *StandardTimer) Rate5() float64 {
	return t.meter.Rate5()
}

// Rate15 returns the fifteen-minute moving average rate of events per second.
func (t *StandardTimer) Rate15() float64 {
	return t.meter.Rate15()
}

// RateMean returns the meter's mean rate of events per second.
func (t *StandardTimer) RateMean() float64 {
	return t.meter.RateMean()
}

// Snapshot returns a read-only copy of the timer.
func (t *StandardTimer) Snapshot() Metric {
	t.mutex.Lock()
	defer t.mutex.Unlock()
	return &TimerSnapshot{
		MetricMeta: t.MetricMeta,
		histogram:  t.histogram.Snapshot().(*HistogramSnapshot),
		meter:      t.meter.Snapshot().(*MeterSnapshot),
	}
}

// StdDev returns the standard deviation of the values in the sample.
func (t *StandardTimer) StdDev() float64 {
	return t.histogram.StdDev()
}

// Sum returns the sum in the sample.
func (t *StandardTimer) Sum() int64 {
	return t.histogram.Sum()
}

// Record the duration of the execution of the given function.
func (t *StandardTimer) Time(f func()) {
	ts := time.Now()
	f()
	t.Update(time.Since(ts))
}

// Record the duration of an event.
func (t *StandardTimer) Update(d time.Duration) {
	t.mutex.Lock()
	defer t.mutex.Unlock()
	t.histogram.Update(int64(d))
	t.meter.Mark(1)
}

// Record the duration of an event that started at a time and ends now.
func (t *StandardTimer) UpdateSince(ts time.Time) {
	t.mutex.Lock()
	defer t.mutex.Unlock()
	sinceMs := time.Since(ts) / time.Millisecond
	t.histogram.Update(int64(sinceMs))
	t.meter.Mark(1)
}

// Variance returns the variance of the values in the sample.
func (t *StandardTimer) Variance() float64 {
	return t.histogram.Variance()
}

// TimerSnapshot is a read-only copy of another Timer.
type TimerSnapshot struct {
	*MetricMeta
	histogram *HistogramSnapshot
	meter     *MeterSnapshot
}

// Count returns the number of events recorded at the time the snapshot was
// taken.
func (t *TimerSnapshot) Count() int64 { return t.histogram.Count() }

// Max returns the maximum value at the time the snapshot was taken.
func (t *TimerSnapshot) Max() int64 { return t.histogram.Max() }

// Mean returns the mean value at the time the snapshot was taken.
func (t *TimerSnapshot) Mean() float64 { return t.histogram.Mean() }

// Min returns the minimum value at the time the snapshot was taken.
func (t *TimerSnapshot) Min() int64 { return t.histogram.Min() }

// Percentile returns an arbitrary percentile of sampled values at the time the
// snapshot was taken.
func (t *TimerSnapshot) Percentile(p float64) float64 {
	return t.histogram.Percentile(p)
}

// Percentiles returns a slice of arbitrary percentiles of sampled values at
// the time the snapshot was taken.
func (t *TimerSnapshot) Percentiles(ps []float64) []float64 {
	return t.histogram.Percentiles(ps)
}

// Rate1 returns the one-minute moving average rate of events per second at the
// time the snapshot was taken.
func (t *TimerSnapshot) Rate1() float64 { return t.meter.Rate1() }

// Rate5 returns the five-minute moving average rate of events per second at
// the time the snapshot was taken.
func (t *TimerSnapshot) Rate5() float64 { return t.meter.Rate5() }

// Rate15 returns the fifteen-minute moving average rate of events per second
// at the time the snapshot was taken.
func (t *TimerSnapshot) Rate15() float64 { return t.meter.Rate15() }

// RateMean returns the meter's mean rate of events per second at the time the
// snapshot was taken.
func (t *TimerSnapshot) RateMean() float64 { return t.meter.RateMean() }

// Snapshot returns the snapshot.
func (t *TimerSnapshot) Snapshot() Metric { return t }

// StdDev returns the standard deviation of the values at the time the snapshot
// was taken.
func (t *TimerSnapshot) StdDev() float64 { return t.histogram.StdDev() }

// Sum returns the sum at the time the snapshot was taken.
func (t *TimerSnapshot) Sum() int64 { return t.histogram.Sum() }

// Time panics.
func (*TimerSnapshot) Time(func()) {
	panic("Time called on a TimerSnapshot")
}

// Update panics.
func (*TimerSnapshot) Update(time.Duration) {
	panic("Update called on a TimerSnapshot")
}

// UpdateSince panics.
func (*TimerSnapshot) UpdateSince(time.Time) {
	panic("UpdateSince called on a TimerSnapshot")
}

// Variance returns the variance of the values at the time the snapshot was
// taken.
func (t *TimerSnapshot) Variance() float64 { return t.histogram.Variance() }
<|endoftext|>


<commit_before>package snapshot

import (
	crdv1 "github.com/kubernetes-incubator/external-storage/snapshot/pkg/apis/crd/v1"
	"github.com/libopenstorage/stork/pkg/rule"
	k8sextops "github.com/portworx/sched-ops/k8s/externalstorage"
	storkops "github.com/portworx/sched-ops/k8s/stork"
	"github.com/sirupsen/logrus"
	v1 "k8s.io/api/core/v1"
)

const (
	storkRuleAnnotationPrefixDeprecated = "stork.rule"
	storkRuleAnnotationPrefix           = "stork.libopenstorage.org"
	preSnapRuleAnnotationKey            = storkRuleAnnotationPrefix + "/pre-snapshot-rule"
	postSnapRuleAnnotationKey           = storkRuleAnnotationPrefix + "/post-snapshot-rule"
	preSnapRuleAnnotationKeyDeprecated  = storkRuleAnnotationPrefixDeprecated + "/pre-snapshot"
	postSnapRuleAnnotationKeyDeprecated = storkRuleAnnotationPrefixDeprecated + "/post-snapshot"
)

var ruleAnnotationKeyTypes = map[string]rule.Type{
	preSnapRuleAnnotationKey:            rule.PreExecRule,
	postSnapRuleAnnotationKey:           rule.PostExecRule,
	preSnapRuleAnnotationKeyDeprecated:  rule.PreExecRule,
	postSnapRuleAnnotationKeyDeprecated: rule.PostExecRule,
}

// validateSnapRules validates the rules if they are present in the given snapshot's annotations
func validateSnapRules(snap *crdv1.VolumeSnapshot) error {
	if snap.Metadata.Annotations != nil {
		ruleAnnotations := []string{
			preSnapRuleAnnotationKey,
			postSnapRuleAnnotationKey,
			preSnapRuleAnnotationKeyDeprecated,
			preSnapRuleAnnotationKeyDeprecated,
		}
		for _, annotation := range ruleAnnotations {
			ruleName, present := snap.Metadata.Annotations[annotation]
			if present && len(ruleName) > 0 {
				r, err := storkops.Instance().GetRule(ruleName, snap.Metadata.Namespace)
				if err != nil {
					return err
				}
				err = rule.ValidateRule(r, ruleAnnotationKeyTypes[annotation])
				if err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func setKind(snap *crdv1.VolumeSnapshot) {
	snap.Kind = "VolumeSnapshot"
	snap.APIVersion = crdv1.SchemeGroupVersion.String()
}

// ExecutePreSnapRule executes the pre snapshot rule. pvcs is a list of PVCs that are associated
// with the snapshot. It returns a channel which the caller can trigger to delete the termination of background commands
func ExecutePreSnapRule(snap *crdv1.VolumeSnapshot, pvcs []v1.PersistentVolumeClaim) (chan bool, error) {
	setKind(snap)
	if err := validateSnapRules(snap); err != nil {
		return nil, err
	}
	if snap.Metadata.Annotations != nil {
		ruleName, present := snap.Metadata.Annotations[preSnapRuleAnnotationKey]
		if !present {
			ruleName, present = snap.Metadata.Annotations[preSnapRuleAnnotationKeyDeprecated]
			if !present {
				return nil, nil
			}
		}
		r, err := storkops.Instance().GetRule(ruleName, snap.Metadata.Namespace)
		if err != nil {
			return nil, err
		}
		return rule.ExecuteRule(r, rule.PreExecRule, snap, snap.Metadata.Namespace)
	}
	return nil, nil
}

// ExecutePostSnapRule executes the post snapshot rule for the given snapshot. pvcs is a list of PVCs
// that are associated with the snapshot
func ExecutePostSnapRule(pvcs []v1.PersistentVolumeClaim, snap *crdv1.VolumeSnapshot) error {
	setKind(snap)
	if err := validateSnapRules(snap); err != nil {
		return err
	}
	if snap.Metadata.Annotations != nil {
		ruleName, present := snap.Metadata.Annotations[postSnapRuleAnnotationKey]
		if !present {
			ruleName, present = snap.Metadata.Annotations[postSnapRuleAnnotationKeyDeprecated]
			if !present {
				return nil
			}
		}
		r, err := storkops.Instance().GetRule(ruleName, snap.Metadata.Namespace)
		if err != nil {
			return err
		}
		_, err = rule.ExecuteRule(r, rule.PostExecRule, snap, snap.Metadata.Namespace)
		return err
	}
	return nil
}

// performRuleRecovery terminates potential background commands running pods for
// the given snapshot
func performRuleRecovery() error {
	allSnaps, err := k8sextops.Instance().ListSnapshots(v1.NamespaceAll)
	if err != nil {
		logrus.Errorf("Failed to list all snapshots due to: %v. Will retry.", err)
		return err
	}

	if allSnaps == nil {
		return nil
	}

	var lastError error
	for _, snap := range allSnaps.Items {
		setKind(&snap)
		err := rule.PerformRuleRecovery(&snap)
		if err != nil {
			lastError = err
		}
	}
	return lastError
}
<commit_msg>Ignore empty pre and post exec rules<commit_after>package snapshot

import (
	crdv1 "github.com/kubernetes-incubator/external-storage/snapshot/pkg/apis/crd/v1"
	"github.com/libopenstorage/stork/pkg/rule"
	k8sextops "github.com/portworx/sched-ops/k8s/externalstorage"
	storkops "github.com/portworx/sched-ops/k8s/stork"
	"github.com/sirupsen/logrus"
	v1 "k8s.io/api/core/v1"
)

const (
	storkRuleAnnotationPrefixDeprecated = "stork.rule"
	storkRuleAnnotationPrefix           = "stork.libopenstorage.org"
	preSnapRuleAnnotationKey            = storkRuleAnnotationPrefix + "/pre-snapshot-rule"
	postSnapRuleAnnotationKey           = storkRuleAnnotationPrefix + "/post-snapshot-rule"
	preSnapRuleAnnotationKeyDeprecated  = storkRuleAnnotationPrefixDeprecated + "/pre-snapshot"
	postSnapRuleAnnotationKeyDeprecated = storkRuleAnnotationPrefixDeprecated + "/post-snapshot"
)

var ruleAnnotationKeyTypes = map[string]rule.Type{
	preSnapRuleAnnotationKey:            rule.PreExecRule,
	postSnapRuleAnnotationKey:           rule.PostExecRule,
	preSnapRuleAnnotationKeyDeprecated:  rule.PreExecRule,
	postSnapRuleAnnotationKeyDeprecated: rule.PostExecRule,
}

// validateSnapRules validates the rules if they are present in the given snapshot's annotations
func validateSnapRules(snap *crdv1.VolumeSnapshot) error {
	if snap.Metadata.Annotations != nil {
		ruleAnnotations := []string{
			preSnapRuleAnnotationKey,
			postSnapRuleAnnotationKey,
			preSnapRuleAnnotationKeyDeprecated,
			preSnapRuleAnnotationKeyDeprecated,
		}
		for _, annotation := range ruleAnnotations {
			ruleName, present := snap.Metadata.Annotations[annotation]
			if present && len(ruleName) > 0 {
				r, err := storkops.Instance().GetRule(ruleName, snap.Metadata.Namespace)
				if err != nil {
					return err
				}
				err = rule.ValidateRule(r, ruleAnnotationKeyTypes[annotation])
				if err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func setKind(snap *crdv1.VolumeSnapshot) {
	snap.Kind = "VolumeSnapshot"
	snap.APIVersion = crdv1.SchemeGroupVersion.String()
}

// ExecutePreSnapRule executes the pre snapshot rule. pvcs is a list of PVCs that are associated
// with the snapshot. It returns a channel which the caller can trigger to delete the termination of background commands
func ExecutePreSnapRule(snap *crdv1.VolumeSnapshot, pvcs []v1.PersistentVolumeClaim) (chan bool, error) {
	setKind(snap)
	if err := validateSnapRules(snap); err != nil {
		return nil, err
	}
	if snap.Metadata.Annotations != nil {
		ruleName, present := snap.Metadata.Annotations[preSnapRuleAnnotationKey]
		if !present {
			ruleName, present = snap.Metadata.Annotations[preSnapRuleAnnotationKeyDeprecated]
			if !present {
				return nil, nil
			}
		}
		if ruleName == "" {
			return nil, nil
		}
		r, err := storkops.Instance().GetRule(ruleName, snap.Metadata.Namespace)
		if err != nil {
			return nil, err
		}
		return rule.ExecuteRule(r, rule.PreExecRule, snap, snap.Metadata.Namespace)
	}
	return nil, nil
}

// ExecutePostSnapRule executes the post snapshot rule for the given snapshot. pvcs is a list of PVCs
// that are associated with the snapshot
func ExecutePostSnapRule(pvcs []v1.PersistentVolumeClaim, snap *crdv1.VolumeSnapshot) error {
	setKind(snap)
	if err := validateSnapRules(snap); err != nil {
		return err
	}
	if snap.Metadata.Annotations != nil {
		ruleName, present := snap.Metadata.Annotations[postSnapRuleAnnotationKey]
		if !present {
			ruleName, present = snap.Metadata.Annotations[postSnapRuleAnnotationKeyDeprecated]
			if !present {
				return nil
			}
		}
		if ruleName == "" {
			return nil
		}
		r, err := storkops.Instance().GetRule(ruleName, snap.Metadata.Namespace)
		if err != nil {
			return err
		}
		_, err = rule.ExecuteRule(r, rule.PostExecRule, snap, snap.Metadata.Namespace)
		return err
	}
	return nil
}

// performRuleRecovery terminates potential background commands running pods for
// the given snapshot
func performRuleRecovery() error {
	allSnaps, err := k8sextops.Instance().ListSnapshots(v1.NamespaceAll)
	if err != nil {
		logrus.Errorf("Failed to list all snapshots due to: %v. Will retry.", err)
		return err
	}

	if allSnaps == nil {
		return nil
	}

	var lastError error
	for _, snap := range allSnaps.Items {
		setKind(&snap)
		err := rule.PerformRuleRecovery(&snap)
		if err != nil {
			lastError = err
		}
	}
	return lastError
}
<|endoftext|>