<commit_before>package file

import (
	"github.com/watermint/toolbox/domain/dropbox/api/dbx_conn"
	"github.com/watermint/toolbox/infra/control/app_control"
	"github.com/watermint/toolbox/infra/recipe/rc_recipe"
	"github.com/watermint/toolbox/quality/infra/qt_errors"
)

type Scan struct {
	rc_recipe.RemarkSecret
	Peer dbx_conn.ConnScopedTeam
}

func (z *Scan) Preset() {
	//TODO implement me
	panic("implement me")
}

func (z *Scan) Exec(c app_control.Control) error {

	//TODO implement me
	panic("implement me")
}

func (z *Scan) Test(c app_control.Control) error {
	return qt_errors.ErrorNoTestRequired
}
<commit_msg>fix tests<commit_after>package file

import (
	"github.com/watermint/toolbox/domain/dropbox/api/dbx_conn"
	"github.com/watermint/toolbox/infra/control/app_control"
	"github.com/watermint/toolbox/infra/recipe/rc_recipe"
	"github.com/watermint/toolbox/quality/infra/qt_errors"
)

type Scan struct {
	rc_recipe.RemarkSecret
	Peer dbx_conn.ConnScopedTeam
}

func (z *Scan) Preset() {
}

func (z *Scan) Exec(c app_control.Control) error {
	return nil
}

func (z *Scan) Test(c app_control.Control) error {
	return qt_errors.ErrorNoTestRequired
}
<|endoftext|>


<commit_before>// 24 march 2015
package main

import (
	"fmt"
	"os"
	"bufio"
	"strings"
)

type method struct {
	Ret		string
	Name	string
	Args		[]string
}

type ifaceentry struct {
	Raw		string
	Name	string
	Method	method
}

type iface []ifaceentry

func die(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintf(os.Stderr, "\n")
	os.Exit(1)
}

func readiface(filename string) (i iface) {
	f, err := os.Open(filename)
	if err != nil {
		die("error opening %s: %v", filename, err)
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		s := scanner.Text()
		if s == "" {
			continue
		}
		if s[0] == 'r' {		// raw line
			i = append(i, ifaceentry{
				Raw:		s[1:],
			})
			continue
		}
		p := strings.Split(strings.TrimSpace(s), " ")
		if p[0][0] == '#' {	// comment
			continue
		}
		if p[0] == "i" {		// new interface
			i = append(i, ifaceentry{
				Name:	p[1],
			})
			continue
		}
		if p[0] == "m" {		// method
			i = append(i, ifaceentry{
				Method:	method{
					Ret:		p[1],
					Name:	p[2],
					Args:	p[3:],
				},
			})
			continue
		}
		die("unknown line %q\n", s)
	}
	if err := scanner.Err(); err != nil {
		die("error reading %s: %v", filename, err)
	}

	return i
}

const banner = "// generated by tools/iface2cdecl.go; do not edit"

// TODO break apart T* into T *
// TODO arguments
// TODO merge functions

func (m method) decl(curiface string, name string) string {
	s := m.Ret
	s += " "
	s += name
	s += "(" + curiface + " *this"
	i := 1
	for _, a := range m.Args {
		s += ", " + a + " " + fmt.Sprintf("arg%d", i)
		i++
	}
	s += ")"
	return s
}

func (m method) vtabledecl(curiface string) string {
	return m.decl(curiface, "(*" + m.Name + ")")
}

func (m method) globaldecl(curiface string) string {
	return m.decl(curiface, curiface + m.Name)
}

func genhfile(i iface, filename string) {
	f, err := os.Create(filename)
	if err != nil {
		die("error creating %s: %v", filename, err)
	}
	defer f.Close()

	// TODO error checking

	curiface := ""
	curmethods := []method(nil)
	startiface := func() {
		fmt.Fprintf(f, "#define %sType (%s_get_type())\n", curiface, curiface)
		fmt.Fprintf(f, "#define %s(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), %sType, %s))\n", curiface, curiface, curiface)
		fmt.Fprintf(f, "#define Is%s(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), %sType))\n", curiface, curiface)
		fmt.Fprintf(f, "#define Get%sInterface(inst) (G_TYPE_INSTANCE_GET_INTERFACE((inst), %sType, %sInterface))\n", curiface, curiface, curiface)
		fmt.Fprintf(f, "typedef struct %s %s;\n", curiface, curiface)
		fmt.Fprintf(f, "typedef struct %sInterface %sInterface;\n", curiface, curiface);
		fmt.Fprintf(f, "struct %sInterface {\n", curiface);
		fmt.Fprintf(f, "\tGTypeInterface parent_iface;\n");
		// TODO add a newline here?
	}
	endiface := func() {
		if curiface == "" {
			return
		}
		fmt.Fprintf(f, "};\n")
		fmt.Fprintf(f, "extern GType %s_get_type(void);\n", curiface)
		fmt.Fprintf(f, "extern void verify%sImpl(char *, %sInterface *);\n", curiface, curiface)
		for _, m := range curmethods {
			fmt.Fprintf(f, "extern %s;\n", m.globaldecl(curiface))
		}
		fmt.Fprintf(f, "\n")
		curiface = ""
		curmethods = nil
	}

	// TODO error checking
	fmt.Fprintf(f, "%s\n", banner)
	for _, e := range i {
		if e.Raw != "" {		// raw line
			continue
		}
		if e.Name != "" {	// new interface
			endiface()
			curiface = e.Name
			startiface()
			continue
		}
		// must be a method
		curmethods = append(curmethods, e.Method)
		fmt.Fprintf(f, "\t%s;\n", e.Method.vtabledecl(curiface))
	}
	endiface()
}

func gencfile(i iface, filename string) {
	f, err := os.Create(filename)
	if err != nil {
		die("error creating %s: %v", filename, err)
	}
	defer f.Close()

	// TODO error checking

	curiface := ""
	curmethods := []method(nil)
	startiface := func() {
		// TODO really G_TYPE_INTERFACE?
		fmt.Fprintf(f, "G_DEFINE_INTERFACE(%s, %s, G_TYPE_INTERFACE)\n", curiface, curiface)
	}
	endiface := func() {
		if curiface == "" {
			return
		}
		fmt.Fprintf(f, "static void %s_default_init(%sInterface *iface)\n", curiface, curiface)
		fmt.Fprintf(f, "{\n")
		fmt.Fprintf(f, "}\n")
		fmt.Fprintf(f, "void verify%sImpl(char *typename, %sInterface *iface)\n", curiface, curiface)
		fmt.Fprintf(f, "{\n")
		for _, m := range curmethods {
			fmt.Fprintf(f, "\tif (iface->%s == NULL)\n", m.Name)
			fmt.Fprintf(f, "\t\tg_error(\"BUG: type %%s missing implementation of %s method %s\", typename);\n", curiface, m.Name)
		}
		fmt.Fprintf(f, "}\n")
		fmt.Fprintf(f, "\n")
		curiface = ""
		curmethods = nil
	}

	// TODO error checking
	fmt.Fprintf(f, "%s\n", banner)
	for _, e := range i {
		if e.Raw != "" {		// raw line
			continue
		}
		if e.Name != "" {	// new interface
			endiface()
			curiface = e.Name
			startiface()
			continue
		}
		// must be a method
		curmethods = append(curmethods, e.Method)
		fmt.Fprintf(f, "%s\n", e.Method.globaldecl(curiface))
		fmt.Fprintf(f, "{\n")
		fmt.Fprintf(f, "\t")
		if e.Method.Ret != "void" {
			fmt.Fprintf(f, "return ")
		}
		k := "(*(Get" + curiface + "Interface(this)->" + e.Method.Name + "))"
		fmt.Fprintf(f, "%s(this", k)
		n := 1
		for _, _ = range e.Method.Args {
			fmt.Fprintf(f, ", arg%d", n)
			n++
		}
		fmt.Fprintf(f, ");\n")
		fmt.Fprintf(f, "}\n")
	}
	endiface()
}

func main() {
	if len(os.Args) != 4 {
		die("usage: %s ifacefile type outfile", os.Args[0])
	}

	iface := readiface(os.Args[1])
	outfile := os.Args[3]

	switch os.Args[2] {
	case "hfile":
		genhfile(iface, outfile)
	case "cfile":
		gencfile(iface, outfile)
	default:
		die("unknown output type %q", os.Args[2])
	}
}
<commit_msg>Resolved some TODOs.<commit_after>// 24 march 2015
package main

import (
	"fmt"
	"os"
	"bufio"
	"strings"
)

type method struct {
	Ret		string
	Name	string
	Args		[]string
}

type ifaceentry struct {
	Raw		string
	Name	string
	Method	method
}

type iface []ifaceentry

func die(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintf(os.Stderr, "\n")
	os.Exit(1)
}

func readiface(filename string) (i iface) {
	f, err := os.Open(filename)
	if err != nil {
		die("error opening %s: %v", filename, err)
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		s := scanner.Text()
		if s == "" {
			continue
		}
		if s[0] == 'r' {		// raw line
			i = append(i, ifaceentry{
				Raw:		s[1:],
			})
			continue
		}
		p := strings.Split(strings.TrimSpace(s), " ")
		if p[0][0] == '#' {	// comment
			continue
		}
		if p[0] == "i" {		// new interface
			i = append(i, ifaceentry{
				Name:	p[1],
			})
			continue
		}
		if p[0] == "m" {		// method
			i = append(i, ifaceentry{
				Method:	method{
					Ret:		p[1],
					Name:	p[2],
					Args:	p[3:],
				},
			})
			continue
		}
		die("unknown line %q\n", s)
	}
	if err := scanner.Err(); err != nil {
		die("error reading %s: %v", filename, err)
	}

	return i
}

const banner = "// generated by tools/iface2cdecl.go; do not edit"

// TODO break apart T* into T *
// TODO arguments
// TODO merge functions

func (m method) decl(curiface string, name string) string {
	s := m.Ret
	s += " "
	s += name
	s += "(" + curiface + " *this"
	i := 1
	for _, a := range m.Args {
		s += ", " + a + " " + fmt.Sprintf("arg%d", i)
		i++
	}
	s += ")"
	return s
}

func (m method) vtabledecl(curiface string) string {
	return m.decl(curiface, "(*" + m.Name + ")")
}

func (m method) globaldecl(curiface string) string {
	return m.decl(curiface, curiface + m.Name)
}

func genhfile(i iface, filename string) {
	f, err := os.Create(filename)
	if err != nil {
		die("error creating %s: %v", filename, err)
	}
	defer f.Close()

	// TODO error checking

	curiface := ""
	curmethods := []method(nil)
	startiface := func() {
		fmt.Fprintf(f, "#define %sType (%s_get_type())\n", curiface, curiface)
		fmt.Fprintf(f, "#define %s(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), %sType, %s))\n", curiface, curiface, curiface)
		fmt.Fprintf(f, "#define Is%s(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), %sType))\n", curiface, curiface)
		fmt.Fprintf(f, "#define Get%sInterface(inst) (G_TYPE_INSTANCE_GET_INTERFACE((inst), %sType, %sInterface))\n", curiface, curiface, curiface)
		fmt.Fprintf(f, "typedef struct %s %s;\n", curiface, curiface)
		fmt.Fprintf(f, "typedef struct %sInterface %sInterface;\n", curiface, curiface);
		fmt.Fprintf(f, "struct %sInterface {\n", curiface);
		fmt.Fprintf(f, "\tGTypeInterface parent_iface;\n");
		// TODO add a newline here?
	}
	endiface := func() {
		if curiface == "" {
			return
		}
		fmt.Fprintf(f, "};\n")
		fmt.Fprintf(f, "extern GType %s_get_type(void);\n", curiface)
		fmt.Fprintf(f, "extern void verify%sImpl(char *, %sInterface *);\n", curiface, curiface)
		for _, m := range curmethods {
			fmt.Fprintf(f, "extern %s;\n", m.globaldecl(curiface))
		}
		fmt.Fprintf(f, "\n")
		curiface = ""
		curmethods = nil
	}

	// TODO error checking
	fmt.Fprintf(f, "%s\n", banner)
	for _, e := range i {
		if e.Raw != "" {		// raw line
			continue
		}
		if e.Name != "" {	// new interface
			endiface()
			curiface = e.Name
			startiface()
			continue
		}
		// must be a method
		curmethods = append(curmethods, e.Method)
		fmt.Fprintf(f, "\t%s;\n", e.Method.vtabledecl(curiface))
	}
	endiface()
}

func gencfile(i iface, filename string) {
	f, err := os.Create(filename)
	if err != nil {
		die("error creating %s: %v", filename, err)
	}
	defer f.Close()

	// TODO error checking

	curiface := ""
	curmethods := []method(nil)
	startiface := func() {
		fmt.Fprintf(f, "G_DEFINE_INTERFACE(%s, %s, G_TYPE_OBJECT)\n", curiface, curiface)
	}
	endiface := func() {
		if curiface == "" {
			return
		}
		fmt.Fprintf(f, "static void %s_default_init(%sInterface *iface)\n", curiface, curiface)
		fmt.Fprintf(f, "{\n")
		fmt.Fprintf(f, "}\n")
		fmt.Fprintf(f, "void verify%sImpl(char *typename, %sInterface *iface)\n", curiface, curiface)
		fmt.Fprintf(f, "{\n")
		for _, m := range curmethods {
			fmt.Fprintf(f, "\tif (iface->%s == NULL)\n", m.Name)
			fmt.Fprintf(f, "\t\tg_error(\"BUG: type %%s missing implementation of %s method %s\", typename);\n", curiface, m.Name)
		}
		fmt.Fprintf(f, "}\n")
		fmt.Fprintf(f, "\n")
		curiface = ""
		curmethods = nil
	}

	// TODO error checking
	fmt.Fprintf(f, "%s\n", banner)
	for _, e := range i {
		if e.Raw != "" {		// raw line
			continue
		}
		if e.Name != "" {	// new interface
			endiface()
			curiface = e.Name
			startiface()
			continue
		}
		// must be a method
		curmethods = append(curmethods, e.Method)
		fmt.Fprintf(f, "%s\n", e.Method.globaldecl(curiface))
		fmt.Fprintf(f, "{\n")
		fmt.Fprintf(f, "\t")
		if e.Method.Ret != "void" {
			fmt.Fprintf(f, "return ")
		}
		k := "(*(Get" + curiface + "Interface(this)->" + e.Method.Name + "))"
		fmt.Fprintf(f, "%s(this", k)
		n := 1
		for _, _ = range e.Method.Args {
			fmt.Fprintf(f, ", arg%d", n)
			n++
		}
		fmt.Fprintf(f, ");\n")
		fmt.Fprintf(f, "}\n")
	}
	endiface()
}

func main() {
	if len(os.Args) != 4 {
		die("usage: %s ifacefile type outfile", os.Args[0])
	}

	iface := readiface(os.Args[1])
	outfile := os.Args[3]

	switch os.Args[2] {
	case "hfile":
		genhfile(iface, outfile)
	case "cfile":
		gencfile(iface, outfile)
	default:
		die("unknown output type %q", os.Args[2])
	}
}
<|endoftext|>


<commit_before>package secstring

import "testing"

func TestNew(t *testing.T) {
	b := []byte("test")
	c := []byte("test")
	s, err := NewSecString(b)
	defer s.Destroy()
	if s == nil {
		t.Error("New: Expected non-nil SecString. Got nil")
	}

	if err != nil {
		t.Errorf("New: Expected nil err. Got %v", err)
	}

	for i := 0; i < len(b); i++ {
		if b[i] != 0 {
			t.Error("New: input should have been cleared")
			t.Errorf("offending byte at offset %v: %v", i, b[i])
		}
	}

	count := 0
	for i := 0; i < len(c); i++ {
		if c[i] == s.String[i] {
			count++
		}
	}

	if s.Length != 4 {
		t.Errorf("New: Expected length 4. Got %v", s.Length)
	}

	if count == s.Length {
		t.Error("FromString: Expected encryption. Got nothing")
	}
}

func TestDestroy(t *testing.T) {
	s, err := NewSecString([]byte("test"))
	err = s.Destroy()

	if err != nil {
		t.Errorf("Destroy: Expected nil err. Got %v", err)
	}

	if s.String != nil {
		t.Errorf("Destroy: Expected nil s.String. got %v", s.String)
	}
}

func TestFromString(t *testing.T) {
	b := "test"
	c := []byte("test")
	s, err := FromString(&b)
	defer s.Destroy()
	if s == nil {
		t.Error("FromString: Expected non-nil SecString. Got nil")
	}

	if err != nil {
		t.Errorf("FromString: Expected nil err. Got %v", err)
	}

	for i := 0; i < s.Length; i++ {
		if b[i] != uint8('x') {
			t.Error("FromString: input should have been cleared")
			t.Errorf("offending byte at offset %v: %v", i, b[i])
		}
	}

	if s.Length != 4 {
		t.Errorf("FromString: Expected length 4. Got %v", s.Length)
	}

	count := 0
	for i := 0; i < len(c); i++ {
		if c[i] == s.String[i] {
			count++
		}
	}

	if count == s.Length {
		t.Error("FromString: Expected encryption. Got nothing")
	}
}
<commit_msg>Added test for decrypt<commit_after>package secstring

import "testing"

func TestNew(t *testing.T) {
	b := []byte("test")
	c := []byte("test")
	s, err := NewSecString(b)
	if s == nil {
		t.Error("New: Expected non-nil SecString. Got nil")
	}

	if err != nil {
		t.Errorf("New: Expected nil err. Got %v", err)
	}
	defer s.Destroy()

	for i := 0; i < len(b); i++ {
		if b[i] != 0 {
			t.Error("New: input should have been cleared")
			t.Errorf("offending byte %v at position %v", b[i], i)
		}
	}

	count := 0
	for i := 0; i < len(c); i++ {
		if c[i] == s.String[i] {
			count++
		}
	}

	if s.Length != 4 {
		t.Errorf("New: Expected length 4. Got %v", s.Length)
	}

	if count == s.Length {
		t.Error("FromString: Expected encryption. Got nothing")
	}
}

func TestDestroy(t *testing.T) {
	s, err := NewSecString([]byte("test"))
	err = s.Destroy()

	if err != nil {
		t.Errorf("Destroy: Expected nil err. Got %v", err)
	}

	if s.String != nil {
		t.Errorf("Destroy: Expected nil s.String. got %v", s.String)
	}
}

func TestFromString(t *testing.T) {
	b := "test"
	c := []byte("test")
	s, err := FromString(&b)
	if s == nil {
		t.Error("FromString: Expected non-nil SecString. Got nil")
	}

	if err != nil {
		t.Errorf("FromString: Expected nil err. Got %v", err)
	}
	defer s.Destroy()

	for i := 0; i < s.Length; i++ {
		if b[i] != uint8('x') {
			t.Error("FromString: input should have been cleared")
			t.Errorf("offending byte %v at position %v", b[i], i)
		}
	}

	if s.Length != 4 {
		t.Errorf("FromString: Expected length 4. Got %v", s.Length)
	}

	count := 0
	for i := 0; i < len(c); i++ {
		if c[i] == s.String[i] {
			count++
		}
	}

	if count == s.Length {
		t.Error("FromString: Expected encryption. Got nothing")
	}
}

func TestDecrypt(t *testing.T) {
	b := "test"
	c := []byte("test")
	s, _ := FromString(&b)
	defer s.Destroy()

	if err := s.Decrypt(); err != nil {
		t.Errorf("Decrypt: Expected nil err. Got %v", err)
	}

	for i := 0; i < len(c); i++ {
		if c[i] != s.String[i] {
			t.Error("Decrypt: Expected matching []bytes.")
			t.Errorf("offending byte %v at position %v", s.String[i], i)
		}
	}
}
<|endoftext|>


<commit_before>// Package send provides an interface for defining "senders" for
// different logging backends, as well as basic implementations for
// common logging approaches to use with the Grip logging
// interface. Backends currently include: syslog, systemd's journal,
// standard output, and file baased methods.
package send

import (
	"github.com/mongodb/grip/level"
	"github.com/mongodb/grip/message"
)

// The Sender interface describes how the Journaler type's method in
// primary "grip" package's methods interact with a logging output
// method. The Journaler type provides Sender() and SetSender()
// methods that allow client code to swap logging backend
// implementations dependency-injection style.
type Sender interface {
	// returns the name of the logging system. Typically this corresponds directly with
	Name() string
	SetName(string)

	// Method that actually sends messages (the string) to the
	// logging capture system. The Send() method filters out
	// logged messages based on priority, typically using the
	// generic MessageInfo.ShouldLog() function.
	Send(message.Composer)

	// SetLevel allows you to modify the level
	// configuration. Returns an error if you specify impossible
	// values.
	SetLevel(LevelInfo) error

	// Level returns the level configuration document.
	Level() LevelInfo

	// SetErrorHandler provides a method to inject error handling
	// behavior to a sender. Not all sender implementations use
	// the error handler, although some, use a default handler to
	// write logging errors to standard output.
	SetErrorHandler(ErrorHandler) error

	// SetFormatter allows users to inject formatting functions to
	// modify the output of the log sender by providing a function
	// that takes a message and returns string and error.
	SetFormatter(MessageFormatter) error

	// If the logging sender holds any resources that require
	// desecration, they should be cleaned up tin the Close()
	// method. Close() is called by the SetSender() method before
	// changing loggers.
	Close() error
}

// LevelInfo provides a sender-independent structure for storing
// information about a sender's configured log levels.
type LevelInfo struct {
	Default   level.Priority
	Threshold level.Priority
}

// Valid checks that the priorities stored in the LevelInfo document are valid.
func (l LevelInfo) Valid() bool {
	return level.IsValidPriority(l.Default) && level.IsValidPriority(l.Threshold)
}

// ShouldLog checks to see if the log message should be logged, and
// returns false if there is no message or if the message's priority
// is below the logging threshold.
func (l LevelInfo) ShouldLog(m message.Composer) bool {
	// priorities are 0 = Emergency; 7 = debug
	return m.Loggable() && (m.Priority() >= l.Threshold)
}

func setup(s Sender, name string, l LevelInfo) (Sender, error) {
	if err := s.SetLevel(l); err != nil {
		return nil, err
	}

	s.SetName(name)

	return s, nil
}
<commit_msg>MAKE-896: add JSON tags to send.LevelInfo (#58)<commit_after>// Package send provides an interface for defining "senders" for
// different logging backends, as well as basic implementations for
// common logging approaches to use with the Grip logging
// interface. Backends currently include: syslog, systemd's journal,
// standard output, and file baased methods.
package send

import (
	"github.com/mongodb/grip/level"
	"github.com/mongodb/grip/message"
)

// The Sender interface describes how the Journaler type's method in
// primary "grip" package's methods interact with a logging output
// method. The Journaler type provides Sender() and SetSender()
// methods that allow client code to swap logging backend
// implementations dependency-injection style.
type Sender interface {
	// returns the name of the logging system. Typically this corresponds directly with
	Name() string
	SetName(string)

	// Method that actually sends messages (the string) to the
	// logging capture system. The Send() method filters out
	// logged messages based on priority, typically using the
	// generic MessageInfo.ShouldLog() function.
	Send(message.Composer)

	// SetLevel allows you to modify the level
	// configuration. Returns an error if you specify impossible
	// values.
	SetLevel(LevelInfo) error

	// Level returns the level configuration document.
	Level() LevelInfo

	// SetErrorHandler provides a method to inject error handling
	// behavior to a sender. Not all sender implementations use
	// the error handler, although some, use a default handler to
	// write logging errors to standard output.
	SetErrorHandler(ErrorHandler) error

	// SetFormatter allows users to inject formatting functions to
	// modify the output of the log sender by providing a function
	// that takes a message and returns string and error.
	SetFormatter(MessageFormatter) error

	// If the logging sender holds any resources that require
	// desecration, they should be cleaned up tin the Close()
	// method. Close() is called by the SetSender() method before
	// changing loggers.
	Close() error
}

// LevelInfo provides a sender-independent structure for storing
// information about a sender's configured log levels.
type LevelInfo struct {
	Default   level.Priority `json:"default"`
	Threshold level.Priority `json:"threshold"`
}

// Valid checks that the priorities stored in the LevelInfo document are valid.
func (l LevelInfo) Valid() bool {
	return level.IsValidPriority(l.Default) && level.IsValidPriority(l.Threshold)
}

// ShouldLog checks to see if the log message should be logged, and
// returns false if there is no message or if the message's priority
// is below the logging threshold.
func (l LevelInfo) ShouldLog(m message.Composer) bool {
	// priorities are 0 = Emergency; 7 = debug
	return m.Loggable() && (m.Priority() >= l.Threshold)
}

func setup(s Sender, name string, l LevelInfo) (Sender, error) {
	if err := s.SetLevel(l); err != nil {
		return nil, err
	}

	s.SetName(name)

	return s, nil
}
<|endoftext|>


<commit_before>package inigo_test

import (
	"fmt"
	"syscall"
	"time"

	"github.com/cloudfoundry-incubator/inigo/fixtures"
	"github.com/cloudfoundry-incubator/inigo/helpers"
	"github.com/cloudfoundry-incubator/inigo/loggredile"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	"github.com/nu7hatch/gouuid"
	"github.com/tedsuo/ifrit"

	"github.com/cloudfoundry-incubator/inigo/inigo_server"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	archive_helper "github.com/pivotal-golang/archiver/extractor/test_helper"
)

var _ = Describe("Convergence to desired state", func() {
	var desiredAppRequest models.DesireAppRequestFromCC
	var appId string
	var processGuid string

	var tpsProcess ifrit.Process
	var tpsAddr string

	var logOutput *gbytes.Buffer
	var stop chan<- bool

	CONVERGE_REPEAT_INTERVAL := time.Duration(LONG_TIMEOUT) * time.Second
	LONGER_TIMEOUT := 2 * LONG_TIMEOUT

	BeforeEach(func() {
		guid, err := uuid.NewV4()
		if err != nil {
			panic("Failed to generate App ID")
		}
		appId = guid.String()

		guid, err = uuid.NewV4()
		if err != nil {
			panic("Failed to generate Process Guid")
		}
		processGuid = guid.String()

		suiteContext.FileServerRunner.Start()
		suiteContext.ExecutorRunner.Start()
		suiteContext.RepRunner.Start()
		suiteContext.AuctioneerRunner.Start()
		suiteContext.AppManagerRunner.Start()
		suiteContext.RouteEmitterRunner.Start()
		suiteContext.RouterRunner.Start()
		suiteContext.ConvergerRunner.Start(CONVERGE_REPEAT_INTERVAL, 30*time.Second, 5*time.Minute, 30*time.Second, 300*time.Second)

		tpsProcess = ifrit.Envoke(suiteContext.TPSRunner)
		tpsAddr = fmt.Sprintf("http://127.0.0.1:%d", suiteContext.TPSPort)

		archive_helper.CreateZipArchive("/tmp/simple-echo-droplet.zip", fixtures.HelloWorldIndexApp())
		inigo_server.UploadFile("simple-echo-droplet.zip", "/tmp/simple-echo-droplet.zip")

		suiteContext.FileServerRunner.ServeFile("some-lifecycle-bundle.tgz", suiteContext.SharedContext.CircusZipPath)

		logOutput, stop = loggredile.StreamIntoGBuffer(
			suiteContext.LoggregatorRunner.Config.OutgoingPort,
			fmt.Sprintf("/tail/?app=%s", appId),
			"App",
		)

		desiredAppRequest = models.DesireAppRequestFromCC{
			ProcessGuid:  processGuid,
			DropletUri:   inigo_server.DownloadUrl("simple-echo-droplet.zip"),
			Stack:        suiteContext.RepStack,
			Environment:  []models.EnvironmentVariable{{Key: "VCAP_APPLICATION", Value: "{}"}},
			NumInstances: 1,
			Routes:       []string{"route-to-simple"},
			StartCommand: "./run",
			LogGuid:      appId,
		}

		err = suiteContext.NatsRunner.MessageBus.Publish("diego.desire.app", desiredAppRequest.ToJSON())
		Ω(err).ShouldNot(HaveOccurred())

		Eventually(helpers.RunningLRPInstancesPoller(tpsAddr, processGuid), LONGER_TIMEOUT).Should(HaveLen(1))
		poller := helpers.HelloWorldInstancePoller(suiteContext.RouterRunner.Addr(), "route-to-simple")
		Eventually(poller, LONGER_TIMEOUT, 1).Should(Equal([]string{"0"}))
	})

	AfterEach(func() {
		tpsProcess.Signal(syscall.SIGKILL)
		Eventually(tpsProcess.Wait()).Should(Receive())
		close(stop)
	})

	Describe("Executor fault tolerance", func() {
		Context("When starting a long-running process and then bouncing the executor", func() {
			FIt("Eventually brings the long-running process up", func() {
				suiteContext.ExecutorRunner.Stop()

				Eventually(helpers.RunningLRPInstancesPoller(tpsAddr, processGuid), LONGER_TIMEOUT).Should(BeEmpty())
				poller := helpers.HelloWorldInstancePoller(suiteContext.RouterRunner.Addr(), "route-to-simple")
				Eventually(poller, LONGER_TIMEOUT, 1).Should(BeEmpty())

				suiteContext.ExecutorRunner.Start()

				Eventually(helpers.RunningLRPInstancesPoller(tpsAddr, processGuid), LONGER_TIMEOUT).Should(HaveLen(1))
				poller = helpers.HelloWorldInstancePoller(suiteContext.RouterRunner.Addr(), "route-to-simple")
				Eventually(poller, LONGER_TIMEOUT, 1).Should(Equal([]string{"0"}))
			})
		})
	})
})
<commit_msg>Add LRP convergence tests for late-born executors and reps missing stop requests<commit_after>package inigo_test

import (
	"fmt"
	"syscall"
	"time"

	"github.com/cloudfoundry-incubator/inigo/fixtures"
	"github.com/cloudfoundry-incubator/inigo/helpers"
	"github.com/cloudfoundry-incubator/inigo/loggredile"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	"github.com/nu7hatch/gouuid"
	"github.com/tedsuo/ifrit"

	"github.com/cloudfoundry-incubator/inigo/inigo_server"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	archive_helper "github.com/pivotal-golang/archiver/extractor/test_helper"
)

var _ = Describe("Convergence to desired state", func() {
	var desiredAppRequest models.DesireAppRequestFromCC
	var appId string
	var processGuid string

	var tpsProcess ifrit.Process
	var tpsAddr string

	var logOutput *gbytes.Buffer
	var stop chan<- bool

	CONVERGE_REPEAT_INTERVAL := time.Duration(LONG_TIMEOUT) * time.Second
	LONGER_TIMEOUT := 2 * LONG_TIMEOUT

	BeforeEach(func() {
		guid, err := uuid.NewV4()
		if err != nil {
			panic("Failed to generate App ID")
		}
		appId = guid.String()

		guid, err = uuid.NewV4()
		if err != nil {
			panic("Failed to generate Process Guid")
		}
		processGuid = guid.String()

		suiteContext.FileServerRunner.Start()
		suiteContext.AuctioneerRunner.Start()
		suiteContext.AppManagerRunner.Start()
		suiteContext.RouteEmitterRunner.Start()
		suiteContext.RouterRunner.Start()
		suiteContext.ConvergerRunner.Start(CONVERGE_REPEAT_INTERVAL, 30*time.Second, 5*time.Minute, 30*time.Second, 300*time.Second)

		tpsProcess = ifrit.Envoke(suiteContext.TPSRunner)
		tpsAddr = fmt.Sprintf("http://127.0.0.1:%d", suiteContext.TPSPort)

		archive_helper.CreateZipArchive("/tmp/simple-echo-droplet.zip", fixtures.HelloWorldIndexApp())
		inigo_server.UploadFile("simple-echo-droplet.zip", "/tmp/simple-echo-droplet.zip")

		suiteContext.FileServerRunner.ServeFile("some-lifecycle-bundle.tgz", suiteContext.SharedContext.CircusZipPath)

		logOutput, stop = loggredile.StreamIntoGBuffer(
			suiteContext.LoggregatorRunner.Config.OutgoingPort,
			fmt.Sprintf("/tail/?app=%s", appId),
			"App",
		)
	})

	AfterEach(func() {
		tpsProcess.Signal(syscall.SIGKILL)
		Eventually(tpsProcess.Wait()).Should(Receive())
		close(stop)
	})

	FDescribe("Executor fault tolerance", func() {
		Context("When starting a long-running process and then bouncing the executor", func() {
			BeforeEach(func() {
				suiteContext.ExecutorRunner.Start()
				suiteContext.RepRunner.Start()

				desiredAppRequest = models.DesireAppRequestFromCC{
					ProcessGuid:  processGuid,
					DropletUri:   inigo_server.DownloadUrl("simple-echo-droplet.zip"),
					Stack:        suiteContext.RepStack,
					Environment:  []models.EnvironmentVariable{{Key: "VCAP_APPLICATION", Value: "{}"}},
					NumInstances: 1,
					Routes:       []string{"route-to-simple"},
					StartCommand: "./run",
					LogGuid:      appId,
				}

				err := suiteContext.NatsRunner.MessageBus.Publish("diego.desire.app", desiredAppRequest.ToJSON())
				Ω(err).ShouldNot(HaveOccurred())

				running_lrps_poller := helpers.RunningLRPInstancesPoller(tpsAddr, processGuid)
				hello_world_instance_poller := helpers.HelloWorldInstancePoller(suiteContext.RouterRunner.Addr(), "route-to-simple")
				Eventually(running_lrps_poller, LONGER_TIMEOUT).Should(HaveLen(1))
				Eventually(hello_world_instance_poller, LONGER_TIMEOUT, 1).Should(Equal([]string{"0"}))
			})

			It("Eventually brings the long-running process up", func() {
				suiteContext.ExecutorRunner.Stop()

				running_lrps_poller := helpers.RunningLRPInstancesPoller(tpsAddr, processGuid)
				hello_world_instance_poller := helpers.HelloWorldInstancePoller(suiteContext.RouterRunner.Addr(), "route-to-simple")
				Eventually(running_lrps_poller, LONGER_TIMEOUT).Should(BeEmpty())
				Eventually(hello_world_instance_poller, LONGER_TIMEOUT, 1).Should(BeEmpty())

				suiteContext.ExecutorRunner.Start()

				running_lrps_poller = helpers.RunningLRPInstancesPoller(tpsAddr, processGuid)
				hello_world_instance_poller = helpers.HelloWorldInstancePoller(suiteContext.RouterRunner.Addr(), "route-to-simple")
				Eventually(running_lrps_poller, LONGER_TIMEOUT).Should(HaveLen(1))
				Eventually(hello_world_instance_poller, LONGER_TIMEOUT, 1).Should(Equal([]string{"0"}))
			})
		})

		Context("When trying to start a long-running process before the executor is up", func() {
			BeforeEach(func() {
				suiteContext.RepRunner.Start()

				desiredAppRequest = models.DesireAppRequestFromCC{
					ProcessGuid:  processGuid,
					DropletUri:   inigo_server.DownloadUrl("simple-echo-droplet.zip"),
					Stack:        suiteContext.RepStack,
					Environment:  []models.EnvironmentVariable{{Key: "VCAP_APPLICATION", Value: "{}"}},
					NumInstances: 1,
					Routes:       []string{"route-to-simple"},
					StartCommand: "./run",
					LogGuid:      appId,
				}

				err := suiteContext.NatsRunner.MessageBus.Publish("diego.desire.app", desiredAppRequest.ToJSON())
				Ω(err).ShouldNot(HaveOccurred())

				running_lrps_poller := helpers.RunningLRPInstancesPoller(tpsAddr, processGuid)
				hello_world_instance_poller := helpers.HelloWorldInstancePoller(suiteContext.RouterRunner.Addr(), "route-to-simple")
				Consistently(running_lrps_poller, LONGER_TIMEOUT).Should(BeEmpty())
				Consistently(hello_world_instance_poller, LONGER_TIMEOUT, 1).Should(BeEmpty())
			})

			It("Eventually brings the long-running process up", func() {
				suiteContext.ExecutorRunner.Start()

				running_lrps_poller := helpers.RunningLRPInstancesPoller(tpsAddr, processGuid)
				hello_world_instance_poller := helpers.HelloWorldInstancePoller(suiteContext.RouterRunner.Addr(), "route-to-simple")
				Eventually(running_lrps_poller, LONGER_TIMEOUT).Should(HaveLen(1))
				Eventually(hello_world_instance_poller, LONGER_TIMEOUT, 1).Should(Equal([]string{"0"}))
			})
		})

		Context("When the original request to stop a long-running process is lost", func() {
			BeforeEach(func() {
				suiteContext.RepRunner.Start()
				suiteContext.ExecutorRunner.Start()

				desiredAppRequest = models.DesireAppRequestFromCC{
					ProcessGuid:  processGuid,
					DropletUri:   inigo_server.DownloadUrl("simple-echo-droplet.zip"),
					Stack:        suiteContext.RepStack,
					Environment:  []models.EnvironmentVariable{{Key: "VCAP_APPLICATION", Value: "{}"}},
					NumInstances: 1,
					Routes:       []string{"route-to-simple"},
					StartCommand: "./run",
					LogGuid:      appId,
				}

				err := suiteContext.NatsRunner.MessageBus.Publish("diego.desire.app", desiredAppRequest.ToJSON())
				Ω(err).ShouldNot(HaveOccurred())

				running_lrps_poller := helpers.RunningLRPInstancesPoller(tpsAddr, processGuid)
				hello_world_instance_poller := helpers.HelloWorldInstancePoller(suiteContext.RouterRunner.Addr(), "route-to-simple")
				Eventually(running_lrps_poller, LONGER_TIMEOUT).Should(HaveLen(1))
				Eventually(hello_world_instance_poller, LONGER_TIMEOUT, 1).Should(Equal([]string{"0"}))
			})

			It("Eventually brings the long-running process down", func() {
				suiteContext.RepRunner.Stop()

				desiredAppStopRequest := models.DesireAppRequestFromCC{
					ProcessGuid:  processGuid,
					DropletUri:   inigo_server.DownloadUrl("simple-echo-droplet.zip"),
					Stack:        suiteContext.RepStack,
					Environment:  []models.EnvironmentVariable{{Key: "VCAP_APPLICATION", Value: "{}"}},
					NumInstances: 0,
					Routes:       []string{"route-to-simple"},
					StartCommand: "./run",
					LogGuid:      appId,
				}

				err := suiteContext.NatsRunner.MessageBus.Publish("diego.desire.app", desiredAppStopRequest.ToJSON())
				Ω(err).ShouldNot(HaveOccurred())

				running_lrps_poller := helpers.RunningLRPInstancesPoller(tpsAddr, processGuid)
				Eventually(running_lrps_poller, LONGER_TIMEOUT).Should(HaveLen(1))

				running_lrps_poller = helpers.RunningLRPInstancesPoller(tpsAddr, processGuid)
				Consistently(running_lrps_poller, LONGER_TIMEOUT).Should(HaveLen(1))

				suiteContext.RepRunner.Start()

				running_lrps_poller = helpers.RunningLRPInstancesPoller(tpsAddr, processGuid)
				hello_world_instance_poller := helpers.HelloWorldInstancePoller(suiteContext.RouterRunner.Addr(), "route-to-simple")
				Eventually(running_lrps_poller, LONGER_TIMEOUT).Should(BeEmpty())
				Eventually(hello_world_instance_poller, LONGER_TIMEOUT, 1).Should(BeEmpty())
			})
		})
	})
})
<|endoftext|>


<commit_before>package server

import (
	"github.com/smancke/guble/protocol"

	"fmt"
	"net/http"
	"reflect"
	"time"
)

// Startable interface for modules which provide a start mechanism
type Startable interface {
	Start() error
}

// Stopable interface for modules which provide a stop mechanism
type Stopable interface {
	Stop() error
}

// Endpoint adds a HTTP handler for the `GetPrefix()` to the webserver
type Endpoint interface {
	http.Handler
	GetPrefix() string
}

// Service is the main class for simple control of a server
type Service struct {
	webServer  *WebServer
	router     Router
	stopables  []Stopable
	startables []Startable
	// The time given to each Module on Stop()
	StopGracePeriod time.Duration
}

// NewService registers the Main Router, where other modules can subscribe for messages
func NewService(
	addr string,
	router Router) *Service {
	service := &Service{
		stopables:       make([]Stopable, 0, 5),
		webServer:       NewWebServer(addr),
		router:          router,
		StopGracePeriod: time.Second * 2,
	}
	service.Register(service.webServer)
	service.Register(service.router)

	return service
}

// Register the supplied module on this service.
// This method checks the module for the following interfaces and
// does the expected registrations:
//   Stopable: notify when the service stops
//   Endpoint: Register the handler function of the Endpoint in the http service at prefix
//
// If the module does not have a HandlerFunc, the prefix parameter is ignored
func (service *Service) Register(module interface{}) {
	name := reflect.TypeOf(module).String()

	if m, ok := module.(Startable); ok {
		protocol.Info("register %v as StartListener", name)
		service.AddStartListener(m)
	}

	if m, ok := module.(Endpoint); ok {
		protocol.Info("register %v as Endpoint to %v", name, m.GetPrefix())
		service.AddHandler(m.GetPrefix(), m)
	}

	if m, ok := module.(Stopable); ok {
		protocol.Info("register %v as StopListener", name)
		service.AddStopListener(m)
	}
}

func (service *Service) AddHandler(prefix string, handler http.Handler) {
	service.webServer.mux.Handle(prefix, handler)
}

func (service *Service) Start() error {
	el := protocol.NewErrorList("Errors occured while startup the service: ")

	for _, startable := range service.startables {
		name := reflect.TypeOf(startable).String()

		protocol.Debug("starting module %v", name)
		if err := startable.Start(); err != nil {
			protocol.Err("error on startup module %v", name)
			el.Add(err)
		}
	}
	return el.ErrorOrNil()
}

func (service *Service) AddStopListener(stopable Stopable) {
	service.stopables = append(service.stopables, stopable)
}

func (service *Service) AddStartListener(startable Startable) {
	service.startables = append(service.startables, startable)
}

func (service *Service) Stop() error {
	errors := make(map[string]error)
	for _, stopable := range service.stopables {
		name := reflect.TypeOf(stopable).String()
		stoppedChan := make(chan bool)
		errorChan := make(chan error)
		protocol.Info("stopping %v ...", name)
		go func() {
			err := stopable.Stop()
			if err != nil {
				errorChan <- err
				return
			}
			stoppedChan <- true
		}()
		select {
		case err := <-errorChan:
			protocol.Err("error while stopping %v: %v", name, err.Error)
			errors[name] = err
		case <-stoppedChan:
			protocol.Info("stopped %v", name)
		case <-time.After(service.StopGracePeriod):
			errors[name] = fmt.Errorf("error while stopping %v: not returned after %v seconds", name, service.StopGracePeriod)
			protocol.Err(errors[name].Error())
		}
	}
	if len(errors) > 0 {
		return fmt.Errorf("Errors while stopping modules %q", errors)
	}
	return nil
}

func (service *Service) GetWebServer() *WebServer {
	return service.webServer
}
<commit_msg>reordering<commit_after>package server

import (
	"github.com/smancke/guble/protocol"

	"fmt"
	"net/http"
	"reflect"
	"time"
)

// Startable interface for modules which provide a start mechanism
type Startable interface {
	Start() error
}

// Stopable interface for modules which provide a stop mechanism
type Stopable interface {
	Stop() error
}

// Endpoint adds a HTTP handler for the `GetPrefix()` to the webserver
type Endpoint interface {
	http.Handler
	GetPrefix() string
}

// Service is the main class for simple control of a server
type Service struct {
	webServer  *WebServer
	router     Router
	stopables  []Stopable
	startables []Startable
	// The time given to each Module on Stop()
	StopGracePeriod time.Duration
}

// NewService registers the Main Router, where other modules can subscribe for messages
func NewService(
	addr string,
	router Router) *Service {
	service := &Service{
		stopables:       make([]Stopable, 0, 5),
		webServer:       NewWebServer(addr),
		router:          router,
		StopGracePeriod: time.Second * 2,
	}
	service.Register(service.webServer)
	service.Register(service.router)

	return service
}

// Register the supplied module on this service.
// This method checks the module for the following interfaces and
// does the expected registrations:
//   Stopable: notify when the service stops
//   Endpoint: Register the handler function of the Endpoint in the http service at prefix
//
// If the module does not have a HandlerFunc, the prefix parameter is ignored
func (service *Service) Register(module interface{}) {
	name := reflect.TypeOf(module).String()

	if m, ok := module.(Startable); ok {
		protocol.Info("register %v as StartListener", name)
		service.AddStartListener(m)
	}

	if m, ok := module.(Endpoint); ok {
		protocol.Info("register %v as Endpoint to %v", name, m.GetPrefix())
		service.AddHandler(m.GetPrefix(), m)
	}

	if m, ok := module.(Stopable); ok {
		protocol.Info("register %v as StopListener", name)
		service.AddStopListener(m)
	}
}

func (service *Service) Start() error {
	el := protocol.NewErrorList("Errors occured while startup the service: ")

	for _, startable := range service.startables {
		name := reflect.TypeOf(startable).String()

		protocol.Debug("starting module %v", name)
		if err := startable.Start(); err != nil {
			protocol.Err("error on startup module %v", name)
			el.Add(err)
		}
	}
	return el.ErrorOrNil()
}

func (service *Service) Stop() error {
	errors := make(map[string]error)
	for _, stopable := range service.stopables {
		name := reflect.TypeOf(stopable).String()
		stoppedChan := make(chan bool)
		errorChan := make(chan error)
		protocol.Info("stopping %v ...", name)
		go func() {
			err := stopable.Stop()
			if err != nil {
				errorChan <- err
				return
			}
			stoppedChan <- true
		}()
		select {
		case err := <-errorChan:
			protocol.Err("error while stopping %v: %v", name, err.Error)
			errors[name] = err
		case <-stoppedChan:
			protocol.Info("stopped %v", name)
		case <-time.After(service.StopGracePeriod):
			errors[name] = fmt.Errorf("error while stopping %v: not returned after %v seconds", name, service.StopGracePeriod)
			protocol.Err(errors[name].Error())
		}
	}
	if len(errors) > 0 {
		return fmt.Errorf("Errors while stopping modules %q", errors)
	}
	return nil
}

func (service *Service) AddStopListener(stopable Stopable) {
	service.stopables = append(service.stopables, stopable)
}

func (service *Service) AddStartListener(startable Startable) {
	service.startables = append(service.startables, startable)
}

func (service *Service) AddHandler(prefix string, handler http.Handler) {
	service.webServer.mux.Handle(prefix, handler)
}

func (service *Service) GetWebServer() *WebServer {
	return service.webServer
}
<|endoftext|>


<commit_before>// Copyright (c) 2014 The SurgeMQ 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 service

import (
	"bufio"
	"fmt"
	"io"
	"sync/atomic"
	"runtime"
//"github.com/surgemq/message"
	"encoding/binary"
)

var (
	bufcnt int64
	DefaultBufferSize int64

	DeviceInBufferSize int64
	DeviceOutBufferSize int64

	MasterInBufferSize int64
	MasterOutBufferSize int64
)

const (
	smallReadBlockSize = 512
	defaultReadBlockSize = 8192
	defaultWriteBlockSize = 8192
)

/**
2016.03.03 修改
bingbuffer结构体
 */
type buffer struct {
	readIndex  int64        //读序号
	writeIndex int64        //写序号
	ringBuffer []*ByteArray //环形buffer指针数组
	bufferSize int64        //初始化环形buffer指针数组大小
	mask       int64        //掩码：bufferSize-1
	done       int64        //是否完成
}

type ByteArray struct {
	bArray []byte
}

func (this *buffer)ReadCommit(index int64) {
	this.ringBuffer[index] = nil
}

/**
2016.03.03 添加
初始化ringbuffer
参数bufferSize：初始化环形buffer指针数组大小
 */
func newBuffer(size int64) (*buffer, error) {
	if size < 0 {
		return nil, bufio.ErrNegativeCount
	}
	if size == 0 {
		size = DefaultBufferSize
	}
	if !powerOfTwo64(size) {
		fmt.Printf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
		return nil, fmt.Errorf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
	}

	return &buffer{
		readIndex: int64(0), //读序号
		writeIndex: int64(0), //写序号
		ringBuffer: make([]*ByteArray, size), //环形buffer指针数组
		bufferSize: size, //初始化环形buffer指针数组大小
		mask:size - 1,
	}, nil
}

/**
2016.03.03 添加
获取当前读序号
 */
func (this *buffer)GetCurrentReadIndex() (int64) {
	return atomic.LoadInt64(&this.readIndex)
}
/**
2016.03.03 添加
获取当前写序号
 */
func (this *buffer)GetCurrentWriteIndex() (int64) {
	return atomic.LoadInt64(&this.writeIndex)
}

/**
2016.03.03 添加
读取ringbuffer指定的buffer指针，返回该指针并清空ringbuffer该位置存在的指针内容，以及将读序号加1
 */
func (this *buffer)ReadBuffer() (p []byte, index int64, ok bool) {
	ok = true
	p = nil

	index = int64(-1)
	readIndex := atomic.LoadInt64(&this.readIndex)
	writeIndex := atomic.LoadInt64(&this.writeIndex)
	switch  {
	case readIndex >= writeIndex:
		ok = false
	case writeIndex - readIndex > this.bufferSize:
		ok = false
	default:
		//index := buffer.readIndex % buffer.bufferSize
		index = readIndex & this.mask

		p_ := this.ringBuffer[index]
		//this.ringBuffer[index] = nil
		atomic.AddInt64(&this.readIndex, 1)
		p = p_.bArray

		if p == nil {
			ok = false
		}
	}
	return p, index, ok
}

/**
2016.03.03 添加
写入ringbuffer指针，以及将写序号加1
 */
func (this *buffer)WriteBuffer(in *[]byte) (ok bool) {
	ok = true

	readIndex := atomic.LoadInt64(&this.readIndex)
	writeIndex := atomic.LoadInt64(&this.writeIndex)
	switch  {
	case writeIndex - readIndex < 0:
		ok = false
	default:
		//index := buffer.writeIndex % buffer.bufferSize
		index := writeIndex & this.mask
		if this.ringBuffer[index] == nil {
			this.ringBuffer[index] = &ByteArray{bArray:*in}
			atomic.AddInt64(&this.writeIndex, 1)
		}else {
			ok = false
		}
	}
	return ok
}

/**
2016.03.03 修改
关闭缓存
 */
func (this *buffer) Close() error {
	atomic.StoreInt64(&this.done, 1)
	return nil
}
/*

/**
2016.03.03 修改
向ringbuffer中写数据（从connection的中向ringbuffer中写）--生产者
*/
func (this *buffer) ReadFrom(r io.Reader) (int64, error) {
	defer this.Close()
	for {
		total := int64(0)
		if this.isDone() {
			return total, io.EOF
		}
		b := make([]byte, 5)
		n, err := r.Read(b[0:1])

		if n > 0 {
			total += int64(n)
			if err != nil {
				return total, err
			}
		}

		/**************************/
		cnt := 1

		// Let's read enough bytes to get the message header (msg type, remaining length)
		for {
			// If we have read 5 bytes and still not done, then there's a problem.
			if cnt > 4 {
				return 0, fmt.Errorf("sendrecv/peekMessageSize: 4th byte of remaining length has continuation bit set")
			}

			// Peek cnt bytes from the input buffer.
			_, err := r.Read(b[cnt:cnt + 1])
			if err != nil {
				return 0, err
			}

			// If we got enough bytes, then check the last byte to see if the continuation
			// bit is set. If so, increment cnt and continue peeking
			if b[cnt] >= 0x80 {
				cnt++
			} else {
				break
			}
		}

		// Get the remaining length of the message
		remlen, _ := binary.Uvarint(b[1:])

		// Total message length is remlen + 1 (msg type) + m (remlen bytes)
		len := int64(len(b))
		remlen_ := int64(remlen)
		total = remlen_ + int64(len)

		//mtype := message.MessageType(b[0] >> 4)
		/****************/
		//var msg message.Message
		//
		//msg, err = mtype.New()
		//if err != nil {
		//	return 0, err
		//}
		b_ := make([]byte, remlen_)
		_, err = r.Read(b_[0:])
		if err != nil {
			return 0, err
		}

		b = append(b, b_...)
		//n, err = msg.Decode(b)
		//if err != nil {
		//	return 0, err
		//}

		/*************************/

		for !this.WriteBuffer(&b) {
			runtime.Gosched()
		}

		return total, nil
	}
}

/**
2016.03.03 修改
 */
func (this *buffer) WriteTo(w io.Writer) (int64, error) {
	defer this.Close()
	total := int64(0)
	for {
		if this.isDone() {
			return total, io.EOF
		}
		p, index, ok := this.ReadBuffer()
		if !ok {
			runtime.Gosched()
		}
		defer this.ReadCommit(index)
		Log.Debugc(func() string {
			return fmt.Sprintf("defer this.ReadCommit(%d)", index)
		})
		Log.Debugc(func() string {
			return fmt.Sprintf("WriteTo函数》》读取*p：" + string(p))
		})

		Log.Debugc(func() string {
			return fmt.Sprintf(" WriteTo(w io.Writer)(7)")
		})
		//
		//Log.Errorc(func() string {
		//	return fmt.Sprintf("msg::" + msg.Name())
		//})
		//
		//p := make([]byte, msg.Len())
		//_, err := msg.Encode(p)
		//if err != nil {
		//	Log.Errorc(func() string {
		//		return fmt.Sprintf("msg.Encode(p)")
		//	})
		//	return total, io.EOF
		//}
		// There's some data, let's process it first
		if len(p) > 0 {
			n, err := w.Write(p)
			total += int64(n)
			Log.Debugc(func() string {
				return fmt.Sprintf("Wrote %d bytes, totaling %d bytes", n, total)
			})

			if err != nil {
				Log.Errorc(func() string {
					return fmt.Sprintf("w.Write(p) error")
				})
				return total, err
			}
		}

		return total, nil
	}
}

/**
2016.03.03 修改
*/
func (this *buffer) isDone() bool {
	if atomic.LoadInt64(&this.done) == 1 {
		return true
	}

	return false
}

func powerOfTwo64(n int64) bool {
	return n != 0 && (n & (n - 1)) == 0
}

func roundUpPowerOfTwo64(n int64) int64 {
	n--
	n |= n >> 1
	n |= n >> 2
	n |= n >> 4
	n |= n >> 8
	n |= n >> 16
	n |= n >> 32
	n++

	return n
}
<commit_msg>添加测试日志<commit_after>// Copyright (c) 2014 The SurgeMQ 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 service

import (
	"bufio"
	"fmt"
	"io"
	"sync/atomic"
	"runtime"
//"github.com/surgemq/message"
	"encoding/binary"
)

var (
	bufcnt int64
	DefaultBufferSize int64

	DeviceInBufferSize int64
	DeviceOutBufferSize int64

	MasterInBufferSize int64
	MasterOutBufferSize int64
)

const (
	smallReadBlockSize = 512
	defaultReadBlockSize = 8192
	defaultWriteBlockSize = 8192
)

/**
2016.03.03 修改
bingbuffer结构体
 */
type buffer struct {
	readIndex  int64        //读序号
	writeIndex int64        //写序号
	ringBuffer []*ByteArray //环形buffer指针数组
	bufferSize int64        //初始化环形buffer指针数组大小
	mask       int64        //掩码：bufferSize-1
	done       int64        //是否完成
}

type ByteArray struct {
	bArray []byte
}

func (this *buffer)ReadCommit(index int64) {
	this.ringBuffer[index] = nil
}

/**
2016.03.03 添加
初始化ringbuffer
参数bufferSize：初始化环形buffer指针数组大小
 */
func newBuffer(size int64) (*buffer, error) {
	if size < 0 {
		return nil, bufio.ErrNegativeCount
	}
	if size == 0 {
		size = DefaultBufferSize
	}
	if !powerOfTwo64(size) {
		fmt.Printf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
		return nil, fmt.Errorf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
	}

	return &buffer{
		readIndex: int64(0), //读序号
		writeIndex: int64(0), //写序号
		ringBuffer: make([]*ByteArray, size), //环形buffer指针数组
		bufferSize: size, //初始化环形buffer指针数组大小
		mask:size - 1,
	}, nil
}

/**
2016.03.03 添加
获取当前读序号
 */
func (this *buffer)GetCurrentReadIndex() (int64) {
	return atomic.LoadInt64(&this.readIndex)
}
/**
2016.03.03 添加
获取当前写序号
 */
func (this *buffer)GetCurrentWriteIndex() (int64) {
	return atomic.LoadInt64(&this.writeIndex)
}

/**
2016.03.03 添加
读取ringbuffer指定的buffer指针，返回该指针并清空ringbuffer该位置存在的指针内容，以及将读序号加1
 */
func (this *buffer)ReadBuffer() ([]byte, int64, bool) {

	readIndex := atomic.LoadInt64(&this.readIndex)
	writeIndex := atomic.LoadInt64(&this.writeIndex)
	switch  {
	case readIndex >= writeIndex:
		return nil, -1, false
	case writeIndex - readIndex > this.bufferSize:
		return nil, -1, false
	default:
		//index := buffer.readIndex % buffer.bufferSize
		index := readIndex & this.mask

		p_ := this.ringBuffer[index]
		//this.ringBuffer[index] = nil
		atomic.AddInt64(&this.readIndex, 1)
		p := p_.bArray

		if p == nil {
			return nil, -1, false
		}
		return p, index, true
	}
	return nil, -1, false
}

/**
2016.03.03 添加
写入ringbuffer指针，以及将写序号加1
 */
func (this *buffer)WriteBuffer(in *[]byte) (bool) {

	readIndex := atomic.LoadInt64(&this.readIndex)
	writeIndex := atomic.LoadInt64(&this.writeIndex)
	switch  {
	case writeIndex - readIndex < 0:
		return false
	default:
		//index := buffer.writeIndex % buffer.bufferSize
		index := writeIndex & this.mask
		if this.ringBuffer[index] == nil {
			this.ringBuffer[index] = &ByteArray{bArray:*in}
			atomic.AddInt64(&this.writeIndex, 1)
			return true
		}else {
			return false
		}
	}
}

/**
2016.03.03 修改
关闭缓存
 */
func (this *buffer) Close() error {
	atomic.StoreInt64(&this.done, 1)
	return nil
}
/*

/**
2016.03.03 修改
向ringbuffer中写数据（从connection的中向ringbuffer中写）--生产者
*/
func (this *buffer) ReadFrom(r io.Reader) (int64, error) {
	defer this.Close()
	for {
		total := int64(0)
		if this.isDone() {
			return total, io.EOF
		}
		b := make([]byte, 5)
		n, err := r.Read(b[0:1])

		if n > 0 {
			total += int64(n)
			if err != nil {
				return total, err
			}
		}

		/**************************/
		cnt := 1

		// Let's read enough bytes to get the message header (msg type, remaining length)
		for {
			// If we have read 5 bytes and still not done, then there's a problem.
			if cnt > 4 {
				return 0, fmt.Errorf("sendrecv/peekMessageSize: 4th byte of remaining length has continuation bit set")
			}

			// Peek cnt bytes from the input buffer.
			_, err := r.Read(b[cnt:cnt + 1])
			if err != nil {
				return 0, err
			}

			// If we got enough bytes, then check the last byte to see if the continuation
			// bit is set. If so, increment cnt and continue peeking
			if b[cnt] >= 0x80 {
				cnt++
			} else {
				break
			}
		}

		// Get the remaining length of the message
		remlen, _ := binary.Uvarint(b[1:])

		// Total message length is remlen + 1 (msg type) + m (remlen bytes)
		len := int64(len(b))
		remlen_ := int64(remlen)
		total = remlen_ + int64(len)

		//mtype := message.MessageType(b[0] >> 4)
		/****************/
		//var msg message.Message
		//
		//msg, err = mtype.New()
		//if err != nil {
		//	return 0, err
		//}
		b_ := make([]byte, remlen_)
		_, err = r.Read(b_[0:])
		if err != nil {
			return 0, err
		}

		b = append(b, b_...)
		//n, err = msg.Decode(b)
		//if err != nil {
		//	return 0, err
		//}

		/*************************/

		for !this.WriteBuffer(&b) {
			runtime.Gosched()
		}

		return total, nil
	}
}

/**
2016.03.03 修改
 */
func (this *buffer) WriteTo(w io.Writer) (int64, error) {
	defer this.Close()
	total := int64(0)
	for {
		if this.isDone() {
			return total, io.EOF
		}
		p, index, ok := this.ReadBuffer()
		if !ok {
			runtime.Gosched()
		}
		defer this.ReadCommit(index)
		Log.Debugc(func() string {
			return fmt.Sprintf("defer this.ReadCommit(%d)", index)
		})
		Log.Debugc(func() string {
			return fmt.Sprintf("WriteTo函数》》读取*p：" + string(p))
		})

		Log.Debugc(func() string {
			return fmt.Sprintf(" WriteTo(w io.Writer)(7)")
		})
		//
		//Log.Errorc(func() string {
		//	return fmt.Sprintf("msg::" + msg.Name())
		//})
		//
		//p := make([]byte, msg.Len())
		//_, err := msg.Encode(p)
		//if err != nil {
		//	Log.Errorc(func() string {
		//		return fmt.Sprintf("msg.Encode(p)")
		//	})
		//	return total, io.EOF
		//}
		// There's some data, let's process it first
		if len(p) > 0 {
			n, err := w.Write(p)
			total += int64(n)
			Log.Debugc(func() string {
				return fmt.Sprintf("Wrote %d bytes, totaling %d bytes", n, total)
			})

			if err != nil {
				Log.Errorc(func() string {
					return fmt.Sprintf("w.Write(p) error")
				})
				return total, err
			}
		}

		return total, nil
	}
}

/**
2016.03.03 修改
*/
func (this *buffer) isDone() bool {
	if atomic.LoadInt64(&this.done) == 1 {
		return true
	}

	return false
}

func powerOfTwo64(n int64) bool {
	return n != 0 && (n & (n - 1)) == 0
}

func roundUpPowerOfTwo64(n int64) int64 {
	n--
	n |= n >> 1
	n |= n >> 2
	n |= n >> 4
	n |= n >> 8
	n |= n >> 16
	n |= n >> 32
	n++

	return n
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/gorilla/websocket"

	"github.com/zenoss/glog"
	"github.com/zenoss/serviced"
	"github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/shell"

	"fmt"
	"net"
	"net/http"
	"os"
	"os/exec"
	"sort"
	"time"
)

type hub struct {
	// Registered connections.
	connections map[*shell.WebsocketShell]bool

	// Register requests from the connections
	register chan *shell.WebsocketShell

	// Unregister requests from the connections
	unregister chan *shell.WebsocketShell
}

func (h *hub) run() {
	for {
		select {
		case c := <-h.register:
			h.connections[c] = true
		case c := <-h.unregister:
			delete(h.connections, c)
			c.Close()
		}
	}
}

var h = hub{
	register:    make(chan *shell.WebsocketShell),
	unregister:  make(chan *shell.WebsocketShell),
	connections: make(map[*shell.WebsocketShell]bool),
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
	ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
	if _, ok := err.(websocket.HandshakeError); ok {
		http.Error(w, "Not a websocket handshake", 400)
		return
	} else if err != nil {
		return
	}
	c := shell.Connect(ws)
	h.register <- c
	defer func() { h.unregister <- c }()
	go c.Writer()
	c.Reader()
}

// Start a service proxy.
func (cli *ServicedCli) CmdProxy(args ...string) error {

	if err := proxyCmd.Parse(args); err != nil {
		return err
	}
	if len(proxyCmd.Args()) != 2 {
		proxyCmd.Usage()
		glog.Flush()
		os.Exit(2)
	}
	config := serviced.MuxConfig{}
	config.TCPMux.Port = proxyOptions.muxport
	config.TCPMux.Enabled = proxyOptions.mux
	config.TCPMux.UseTLS = proxyOptions.tls
	config.ServiceId = proxyCmd.Arg(0)
	config.Command = proxyCmd.Arg(1)

	if config.TCPMux.Enabled {
		go config.TCPMux.ListenAndMux()
	}

	go h.run()
	http.HandleFunc("/exec", wsHandler)
	http.ListenAndServe(":50000", nil)

	procexit := make(chan int)

	// continually execute subprocess
	go func(cmdString string) {
		defer func() { procexit <- 1 }()
		for {
			glog.V(0).Info("About to execute: ", cmdString)
			cmd := exec.Command("bash", "-c", cmdString)
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
			cmd.Stdin = os.Stdin
			err := cmd.Run()
			if err != nil {
				glog.Errorf("Problem running service: %v", err)
				glog.Flush()
			}
			if !proxyOptions.autorestart {
				break
			}
			glog.V(0).Info("service exited, sleeping...")
			time.Sleep(time.Minute)
		}
	}(config.Command)

	go func() {
		// *********************************************************************************************
		// ***** FIX ME the following 3 variables are defined in agent.go as well! *********************
		containerLogstashForwarderDir := "/usr/local/serviced/resources/logstash"
		containerLogstashForwarderBinaryPath := containerLogstashForwarderDir + "/logstash-forwarder"
		containerLogstashForwarderConfPath := containerLogstashForwarderDir + "/logstash-forwarder.conf"
		// *********************************************************************************************
		cmdString := containerLogstashForwarderBinaryPath + " -config " + containerLogstashForwarderConfPath
		glog.V(0).Info("About to execute: ", cmdString)
		myCmd := exec.Command("bash", "-c", cmdString)
		myErr := myCmd.Run()
		if myErr != nil {
			glog.Errorf("Problem running service: %v", myErr)
			glog.Flush()
		}
	}()

	go func() {
		for {
			func() {
				client, err := serviced.NewLBClient(proxyOptions.servicedEndpoint)
				if err != nil {
					glog.Errorf("Could not create a client to endpoint %s: %s", proxyOptions.servicedEndpoint, err)
					return
				}
				defer client.Close()

				var endpoints map[string][]*dao.ApplicationEndpoint
				err = client.GetServiceEndpoints(config.ServiceId, &endpoints)
				if err != nil {
					glog.Errorf("Error getting application endpoints for service %s: %s", config.ServiceId, err)
					return
				}

				for key, endpointList := range endpoints {
					if len(endpointList) <= 0 {
						glog.Warningf("No endpoints found for %s", key)
						if proxy, ok := proxies[key]; ok {
							emptyAddressList := make([]string, 0)
							proxy.SetNewAddresses(emptyAddressList)
						}
						continue
					}
					addresses := make([]string, len(endpointList))
					for i, endpoint := range endpointList {
						addresses[i] = fmt.Sprintf("%s:%d", endpoint.HostIp, endpoint.HostPort)
					}
					sort.Strings(addresses)

					var proxy *serviced.Proxy
					var ok bool
					if proxy, ok = proxies[key]; !ok {
						// setup a new proxy
						listener, err := net.Listen("tcp4", fmt.Sprintf(":%d", endpointList[0].ContainerPort))
						if err != nil {
							glog.Errorf("Could not bind to port: %s", err)
							continue
						}
						proxy, err = serviced.NewProxy(
							fmt.Sprintf("%v", endpointList[0]),
							uint16(config.TCPMux.Port),
							config.TCPMux.UseTLS,
							listener)
						if err != nil {
							glog.Errorf("Could not build proxy %s", err)
							continue
						}
						proxies[key] = proxy
					}
					proxy.SetNewAddresses(addresses)
				}
			}()

			time.Sleep(time.Second * 10)
		}
	}()

	<-procexit // Wait for proc goroutine to exit

	glog.Flush()
	os.Exit(0)
	return nil
}

var proxies map[string]*serviced.Proxy

func init() {
	proxies = make(map[string]*serviced.Proxy)
}
<commit_msg>listen and serve is blocking<commit_after>package main

import (
	"github.com/gorilla/websocket"

	"github.com/zenoss/glog"
	"github.com/zenoss/serviced"
	"github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/shell"

	"fmt"
	"net"
	"net/http"
	"os"
	"os/exec"
	"sort"
	"time"
)

type hub struct {
	// Registered connections.
	connections map[*shell.WebsocketShell]bool

	// Register requests from the connections
	register chan *shell.WebsocketShell

	// Unregister requests from the connections
	unregister chan *shell.WebsocketShell
}

func (h *hub) run() {
	for {
		select {
		case c := <-h.register:
			h.connections[c] = true
		case c := <-h.unregister:
			delete(h.connections, c)
			c.Close()
		}
	}
}

var h = hub{
	register:    make(chan *shell.WebsocketShell),
	unregister:  make(chan *shell.WebsocketShell),
	connections: make(map[*shell.WebsocketShell]bool),
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
	ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
	if _, ok := err.(websocket.HandshakeError); ok {
		http.Error(w, "Not a websocket handshake", 400)
		return
	} else if err != nil {
		return
	}
	c := shell.Connect(ws)
	h.register <- c
	defer func() { h.unregister <- c }()
	go c.Writer()
	c.Reader()
}

// Start a service proxy.
func (cli *ServicedCli) CmdProxy(args ...string) error {

	if err := proxyCmd.Parse(args); err != nil {
		return err
	}
	if len(proxyCmd.Args()) != 2 {
		proxyCmd.Usage()
		glog.Flush()
		os.Exit(2)
	}
	config := serviced.MuxConfig{}
	config.TCPMux.Port = proxyOptions.muxport
	config.TCPMux.Enabled = proxyOptions.mux
	config.TCPMux.UseTLS = proxyOptions.tls
	config.ServiceId = proxyCmd.Arg(0)
	config.Command = proxyCmd.Arg(1)

	if config.TCPMux.Enabled {
		go config.TCPMux.ListenAndMux()
	}

	go h.run()
	http.HandleFunc("/exec", wsHandler)
	go http.ListenAndServe(":50000", nil)

	procexit := make(chan int)

	// continually execute subprocess
	go func(cmdString string) {
		defer func() { procexit <- 1 }()
		for {
			glog.V(0).Info("About to execute: ", cmdString)
			cmd := exec.Command("bash", "-c", cmdString)
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
			cmd.Stdin = os.Stdin
			err := cmd.Run()
			if err != nil {
				glog.Errorf("Problem running service: %v", err)
				glog.Flush()
			}
			if !proxyOptions.autorestart {
				break
			}
			glog.V(0).Info("service exited, sleeping...")
			time.Sleep(time.Minute)
		}
	}(config.Command)

	go func() {
		// *********************************************************************************************
		// ***** FIX ME the following 3 variables are defined in agent.go as well! *********************
		containerLogstashForwarderDir := "/usr/local/serviced/resources/logstash"
		containerLogstashForwarderBinaryPath := containerLogstashForwarderDir + "/logstash-forwarder"
		containerLogstashForwarderConfPath := containerLogstashForwarderDir + "/logstash-forwarder.conf"
		// *********************************************************************************************
		cmdString := containerLogstashForwarderBinaryPath + " -config " + containerLogstashForwarderConfPath
		glog.V(0).Info("About to execute: ", cmdString)
		myCmd := exec.Command("bash", "-c", cmdString)
		myErr := myCmd.Run()
		if myErr != nil {
			glog.Errorf("Problem running service: %v", myErr)
			glog.Flush()
		}
	}()

	go func() {
		for {
			func() {
				client, err := serviced.NewLBClient(proxyOptions.servicedEndpoint)
				if err != nil {
					glog.Errorf("Could not create a client to endpoint %s: %s", proxyOptions.servicedEndpoint, err)
					return
				}
				defer client.Close()

				var endpoints map[string][]*dao.ApplicationEndpoint
				err = client.GetServiceEndpoints(config.ServiceId, &endpoints)
				if err != nil {
					glog.Errorf("Error getting application endpoints for service %s: %s", config.ServiceId, err)
					return
				}

				for key, endpointList := range endpoints {
					if len(endpointList) <= 0 {
						glog.Warningf("No endpoints found for %s", key)
						if proxy, ok := proxies[key]; ok {
							emptyAddressList := make([]string, 0)
							proxy.SetNewAddresses(emptyAddressList)
						}
						continue
					}
					addresses := make([]string, len(endpointList))
					for i, endpoint := range endpointList {
						addresses[i] = fmt.Sprintf("%s:%d", endpoint.HostIp, endpoint.HostPort)
					}
					sort.Strings(addresses)

					var proxy *serviced.Proxy
					var ok bool
					if proxy, ok = proxies[key]; !ok {
						// setup a new proxy
						listener, err := net.Listen("tcp4", fmt.Sprintf(":%d", endpointList[0].ContainerPort))
						if err != nil {
							glog.Errorf("Could not bind to port: %s", err)
							continue
						}
						proxy, err = serviced.NewProxy(
							fmt.Sprintf("%v", endpointList[0]),
							uint16(config.TCPMux.Port),
							config.TCPMux.UseTLS,
							listener)
						if err != nil {
							glog.Errorf("Could not build proxy %s", err)
							continue
						}
						proxies[key] = proxy
					}
					proxy.SetNewAddresses(addresses)
				}
			}()

			time.Sleep(time.Second * 10)
		}
	}()

	<-procexit // Wait for proc goroutine to exit

	glog.Flush()
	os.Exit(0)
	return nil
}

var proxies map[string]*serviced.Proxy

func init() {
	proxies = make(map[string]*serviced.Proxy)
}
<|endoftext|>


<commit_before>package core

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"runtime"
	"strconv"

	"github.com/mdlayher/wavepipe/api"
	"github.com/mdlayher/wavepipe/api/auth"
	"github.com/mdlayher/wavepipe/config"

	"github.com/go-martini/martini"
	"github.com/martini-contrib/gzip"
	"github.com/martini-contrib/render"
)

// apiRouter sets up the instance of martini
func apiRouter(apiKillChan chan struct{}) {
	log.Println("api: starting...")

	// Initialize martini
	m := martini.New()

	// Set up middleware
	// GZIP all requests to drastically reduce size
	m.Use(gzip.All())
	m.Use(render.Renderer(render.Options{
		// Output human-readable JSON. GZIP will essentially negate the size increase, and this
		// makes the API much more developer-friendly
		IndentJSON: true,
	}))

	// Enable graceful shutdown when triggered by manager
	stopAPI := false
	m.Use(func(req *http.Request, res http.ResponseWriter, r render.Render) {
		// On debug, log everything
		if os.Getenv("WAVEPIPE_DEBUG") == "1" {
			log.Println(req.Header)
			log.Println(req.URL)
		}

		// Send a Server header with all responses
		res.Header().Set("Server", fmt.Sprintf("%s/%s (%s_%s)", App, Version, runtime.GOOS, runtime.GOARCH))

		// If API is stopping, render a HTTP 503
		if stopAPI {
			r.JSON(503, api.Error{
				Code:    503,
				Message: "service is shutting down",
			})
			return
		}
	})

	// Serve static content from web directory
	m.Use(martini.Static("web", martini.StaticOptions{
		// Use the index in the web directory
		IndexFile: "index.html",
		// Skip logging output
		SkipLogging: true,
	}))

	// Authenticate all API calls
	m.Use(func(req *http.Request, res http.ResponseWriter, c martini.Context, r render.Render) {
		// Use factory to determine the proper authentication method for this path
		method := auth.Factory(req.URL.Path)
		if method == nil {
			// If no method returned, path is not authenticated
			return
		}

		// Attempt authentication
		user, session, clientErr, serverErr := method.Authenticate(req)

		// Check for client error
		if clientErr != nil {
			// If debug mode, and no username or password, send a WWW-Authenticate header to prompt request
			// This allows for manual exploration of the API if needed
			if os.Getenv("WAVEPIPE_DEBUG") == "1" && (clientErr == auth.ErrNoUsername || clientErr == auth.ErrNoPassword) {
				res.Header().Set("WWW-Authenticate", "Basic")
			}

			r.JSON(401, api.Error{
				Code:    401,
				Message: "authentication failed: " + clientErr.Error(),
			})
			return
		}

		// Check for server error
		if serverErr != nil {
			log.Println(serverErr)

			r.JSON(500, api.Error{
				Code:    500,
				Message: "server error",
			})
			return
		}

		// Successful login, map session user and session to martini context
		c.Map(user)
		c.Map(session)

		// Print information about this API call
		log.Printf("api: [%s] %s", req.RemoteAddr, req.URL.Path)
	})

	// Set up API routes
	r := martini.NewRouter()

	// Set up API information route
	r.Get("/api", api.APIInfo)

	// Set up API group routes, with API version parameter
	r.Group("/api/:version", func(r martini.Router) {
		// Root API, containing information and help
		r.Get("", api.APIInfo)

		// Albums API
		r.Get("/albums", api.GetAlbums)
		r.Get("/albums/:id", api.GetAlbums)

		// Art API
		r.Get("/art", api.GetArt)
		r.Get("/art/:id", api.GetArt)

		// Artists API
		r.Get("/artists", api.GetArtists)
		r.Get("/artists/:id", api.GetArtists)

		// Folders API
		r.Get("/folders", api.GetFolders)
		r.Get("/folders/:id", api.GetFolders)

		// LastFM API
		r.Get("/lastfm", api.GetLastFM)
		r.Get("/lastfm/:action", api.GetLastFM)
		r.Get("/lastfm/:action/:id", api.GetLastFM)

		// Login API
		r.Get("/login", api.GetLogin)

		// Logout API
		r.Get("/logout", api.GetLogout)

		// Search API
		r.Get("/search", api.GetSearch)
		r.Get("/search/:query", api.GetSearch)

		// Songs API
		r.Get("/songs", api.GetSongs)
		r.Get("/songs/:id", api.GetSongs)

		// Status API
		r.Get("/status", api.GetStatus)

		// Stream API
		r.Get("/stream", api.GetStream)
		r.Get("/stream/:id", api.GetStream)

		// Transcode API
		r.Get("/transcode", api.GetTranscode)
		r.Get("/transcode/:id", api.GetTranscode)
	})

	// Add router action, start server
	m.Action(r.Handle)
	go func() {
		// Load config
		conf, err := config.C.Load()
		if err != nil {
			log.Println(err)
			return
		}

		// Start server
		log.Println("api: listening on port", conf.Port)
		if err := http.ListenAndServe(":"+strconv.Itoa(conf.Port), m); err != nil {
			log.Println(err)
		}
	}()

	// Trigger events via channel
	for {
		select {
		// Stop API
		case <-apiKillChan:
			// Stop serving requests
			stopAPI = true

			// Inform manager that shutdown is complete
			log.Println("api: stopped!")
			apiKillChan <- struct{}{}
			return
		}
	}
}
<commit_msg>Add /debug/pprof endpoints in debug mode<commit_after>package core

import (
	"fmt"
	"log"
	"net/http"
	"net/http/pprof"
	"os"
	"runtime"
	"strconv"

	"github.com/mdlayher/wavepipe/api"
	"github.com/mdlayher/wavepipe/api/auth"
	"github.com/mdlayher/wavepipe/config"

	"github.com/go-martini/martini"
	"github.com/martini-contrib/gzip"
	"github.com/martini-contrib/render"
)

// apiRouter sets up the instance of martini
func apiRouter(apiKillChan chan struct{}) {
	log.Println("api: starting...")

	// Initialize martini
	m := martini.New()

	// Set up middleware
	// GZIP all requests to drastically reduce size
	m.Use(gzip.All())
	m.Use(render.Renderer(render.Options{
		// Output human-readable JSON. GZIP will essentially negate the size increase, and this
		// makes the API much more developer-friendly
		IndentJSON: true,
	}))

	// Enable graceful shutdown when triggered by manager
	stopAPI := false
	m.Use(func(req *http.Request, res http.ResponseWriter, r render.Render) {
		// On debug, log everything
		if os.Getenv("WAVEPIPE_DEBUG") == "1" {
			log.Println(req.Header)
			log.Println(req.URL)
		}

		// Send a Server header with all responses
		res.Header().Set("Server", fmt.Sprintf("%s/%s (%s_%s)", App, Version, runtime.GOOS, runtime.GOARCH))

		// If API is stopping, render a HTTP 503
		if stopAPI {
			r.JSON(503, api.Error{
				Code:    503,
				Message: "service is shutting down",
			})
			return
		}
	})

	// Serve static content from web directory
	m.Use(martini.Static("web", martini.StaticOptions{
		// Use the index in the web directory
		IndexFile: "index.html",
		// Skip logging output
		SkipLogging: true,
	}))

	// Authenticate all API calls
	m.Use(func(req *http.Request, res http.ResponseWriter, c martini.Context, r render.Render) {
		// Use factory to determine the proper authentication method for this path
		method := auth.Factory(req.URL.Path)
		if method == nil {
			// If no method returned, path is not authenticated
			return
		}

		// Attempt authentication
		user, session, clientErr, serverErr := method.Authenticate(req)

		// Check for client error
		if clientErr != nil {
			// If debug mode, and no username or password, send a WWW-Authenticate header to prompt request
			// This allows for manual exploration of the API if needed
			if os.Getenv("WAVEPIPE_DEBUG") == "1" && (clientErr == auth.ErrNoUsername || clientErr == auth.ErrNoPassword) {
				res.Header().Set("WWW-Authenticate", "Basic")
			}

			r.JSON(401, api.Error{
				Code:    401,
				Message: "authentication failed: " + clientErr.Error(),
			})
			return
		}

		// Check for server error
		if serverErr != nil {
			log.Println(serverErr)

			r.JSON(500, api.Error{
				Code:    500,
				Message: "server error",
			})
			return
		}

		// Successful login, map session user and session to martini context
		c.Map(user)
		c.Map(session)

		// Print information about this API call
		log.Printf("api: [%s] %s", req.RemoteAddr, req.URL.Path)
	})

	// Set up API routes
	r := martini.NewRouter()

	// Set up API information route
	r.Get("/api", api.APIInfo)

	// Set up API group routes, with API version parameter
	r.Group("/api/:version", func(r martini.Router) {
		// Root API, containing information and help
		r.Get("", api.APIInfo)

		// Albums API
		r.Get("/albums", api.GetAlbums)
		r.Get("/albums/:id", api.GetAlbums)

		// Art API
		r.Get("/art", api.GetArt)
		r.Get("/art/:id", api.GetArt)

		// Artists API
		r.Get("/artists", api.GetArtists)
		r.Get("/artists/:id", api.GetArtists)

		// Folders API
		r.Get("/folders", api.GetFolders)
		r.Get("/folders/:id", api.GetFolders)

		// LastFM API
		r.Get("/lastfm", api.GetLastFM)
		r.Get("/lastfm/:action", api.GetLastFM)
		r.Get("/lastfm/:action/:id", api.GetLastFM)

		// Login API
		r.Get("/login", api.GetLogin)

		// Logout API
		r.Get("/logout", api.GetLogout)

		// Search API
		r.Get("/search", api.GetSearch)
		r.Get("/search/:query", api.GetSearch)

		// Songs API
		r.Get("/songs", api.GetSongs)
		r.Get("/songs/:id", api.GetSongs)

		// Status API
		r.Get("/status", api.GetStatus)

		// Stream API
		r.Get("/stream", api.GetStream)
		r.Get("/stream/:id", api.GetStream)

		// Transcode API
		r.Get("/transcode", api.GetTranscode)
		r.Get("/transcode/:id", api.GetTranscode)
	})

	// On debug mode, enable pprof debug endpoints
	// Thanks: https://github.com/go-martini/martini/issues/228
	if os.Getenv("WAVEPIPE_DEBUG") == "1" {
		r.Group("/debug/pprof", func(r martini.Router) {
			r.Any("/", pprof.Index)
			r.Any("/cmdline", pprof.Cmdline)
			r.Any("/profile", pprof.Profile)
			r.Any("/symbol", pprof.Symbol)
			r.Any("/block", pprof.Handler("block").ServeHTTP)
			r.Any("/heap", pprof.Handler("heap").ServeHTTP)
			r.Any("/goroutine", pprof.Handler("goroutine").ServeHTTP)
			r.Any("/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
		})
	}

	// Add router action, start server
	m.Action(r.Handle)
	go func() {
		// Load config
		conf, err := config.C.Load()
		if err != nil {
			log.Println(err)
			return
		}

		// Start server
		log.Println("api: listening on port", conf.Port)
		if err := http.ListenAndServe(":"+strconv.Itoa(conf.Port), m); err != nil {
			log.Println(err)
		}
	}()

	// Trigger events via channel
	for {
		select {
		// Stop API
		case <-apiKillChan:
			// Stop serving requests
			stopAPI = true

			// Inform manager that shutdown is complete
			log.Println("api: stopped!")
			apiKillChan <- struct{}{}
			return
		}
	}
}
<|endoftext|>


<commit_before>package shared

type Device map[string]string
type Devices map[string]Device

func (list Devices) ContainsName(k string) bool {
	if list[k] != nil {
		return true
	}
	return false
}

func nicEqual(d1 Device, d2 Device) bool {
	if !nicSettingsEqual(d1, d2) {
		return false
	}
	if d1.get("hwaddr") != d2.get("hwaddr") {
		return false
	}
	return true
}

func nicSettingsEqual(d1 Device, d2 Device) bool {
	if d1.get("nictype") != d2.get("nictype") {
		return false
	}
	if d1.get("name") != d2.get("name") || d1.get("parent") != d2.get("parent") {
		return false
	}
	if d1.get("mtu") != d2.get("mtu") {
		return false
	}
	return true
}

func diskEqual(d1 Device, d2 Device) bool {
	if d1.get("path") != d2.get("path") || d1.get("source") != d2.get("source") {
		return false
	}
	if d1.get("optional") != d2.get("optional") || d1.get("readonly") != d2.get("readonly") {
		return false
	}
	return true
}

func (d Device) get(key string) string {
	return d[key]
}

func (list Devices) Contains(k string, d Device) bool {
	if list[k] == nil {
		return false
	}
	ld := list[k]
	if ld.get("type") != d.get("type") {
		return false
	}
	switch ld.get("type") {
	case "nic":
		if !nicEqual(ld, d) {
			return false
		}
	case "disk":
		if !diskEqual(ld, d) {
			return false
		}
	}
	return true
}

func liveUpdateable(devtype string) bool {
	switch devtype {
	case "nic":
		return true
	case "disk":
		return true
	case "unix-block":
		return true
	case "unix-char":
		return true
	default:
		return false
	}
}

func (old Devices) Update(newlist Devices) (map[string]Device, map[string]Device) {
	rmlist := map[string]Device{}
	addlist := map[string]Device{}
	for key, d := range old {
		switch {
		case !liveUpdateable(d["type"]):
			continue
		case !newlist.Contains(key, d):
			rmlist[key] = d
		}
	}
	for key, d := range newlist {
		switch {
		case !liveUpdateable(d["type"]):
			continue
		case !old.Contains(key, d):
			addlist[key] = d
		}
	}
	return rmlist, addlist
}

func (newBaseDevices Devices) ExtendFromProfile(currentFullDevices Devices, newDevicesFromProfile Devices) error {

	// Add devices from profile to a list of new devices. If a nic
	// is already in currentFullDevices and only differs by
	// hwaddr, keep the existing one instead of overwriting it
	// with the one from the profile
	for name, newDev := range newDevicesFromProfile {
		if newDev["type"] == "nic" {
			if curDev, ok := currentFullDevices[name]; ok {
				if nicSettingsEqual(newDev, curDev) {
					newBaseDevices[name] = curDev
				} else {
					newBaseDevices[name] = newDev
				}
			} else {
				newBaseDevices[name] = newDev
			}
		} else {
			newBaseDevices[name] = newDev
		}
	}

	return nil
}
<commit_msg>Simplify NIC compare<commit_after>package shared

type Device map[string]string
type Devices map[string]Device

func (list Devices) ContainsName(k string) bool {
	if list[k] != nil {
		return true
	}
	return false
}

func nicEqual(d1 Device, d2 Device) bool {
	if !nicSettingsEqual(d1, d2) {
		return false
	}
	if d1.get("hwaddr") != d2.get("hwaddr") {
		return false
	}
	return true
}

func nicSettingsEqual(d1 Device, d2 Device) bool {
	for _, prop := range([]string{"nictype", "name", "parent", "mtu", "hwaddr"}) {
		if d1.get(prop) != d2.get(prop) {
			return false
		}
	}
	return true
}

func diskEqual(d1 Device, d2 Device) bool {
	if d1.get("path") != d2.get("path") || d1.get("source") != d2.get("source") {
		return false
	}
	if d1.get("optional") != d2.get("optional") || d1.get("readonly") != d2.get("readonly") {
		return false
	}
	return true
}

func (d Device) get(key string) string {
	return d[key]
}

func (list Devices) Contains(k string, d Device) bool {
	if list[k] == nil {
		return false
	}
	ld := list[k]
	if ld.get("type") != d.get("type") {
		return false
	}
	switch ld.get("type") {
	case "nic":
		if !nicEqual(ld, d) {
			return false
		}
	case "disk":
		if !diskEqual(ld, d) {
			return false
		}
	}
	return true
}

func liveUpdateable(devtype string) bool {
	switch devtype {
	case "nic":
		return true
	case "disk":
		return true
	case "unix-block":
		return true
	case "unix-char":
		return true
	default:
		return false
	}
}

func (old Devices) Update(newlist Devices) (map[string]Device, map[string]Device) {
	rmlist := map[string]Device{}
	addlist := map[string]Device{}
	for key, d := range old {
		switch {
		case !liveUpdateable(d["type"]):
			continue
		case !newlist.Contains(key, d):
			rmlist[key] = d
		}
	}
	for key, d := range newlist {
		switch {
		case !liveUpdateable(d["type"]):
			continue
		case !old.Contains(key, d):
			addlist[key] = d
		}
	}
	return rmlist, addlist
}

func (newBaseDevices Devices) ExtendFromProfile(currentFullDevices Devices, newDevicesFromProfile Devices) error {

	// Add devices from profile to a list of new devices. If a nic
	// is already in currentFullDevices and only differs by
	// hwaddr, keep the existing one instead of overwriting it
	// with the one from the profile
	for name, newDev := range newDevicesFromProfile {
		if newDev["type"] == "nic" {
			if curDev, ok := currentFullDevices[name]; ok {
				if nicSettingsEqual(newDev, curDev) {
					newBaseDevices[name] = curDev
				} else {
					newBaseDevices[name] = newDev
				}
			} else {
				newBaseDevices[name] = newDev
			}
		} else {
			newBaseDevices[name] = newDev
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package core

import (
	"errors"
	"github.com/sclevine/agouti/core/internal/selection"
)

// Selection instances refer to a selection of elements.
// All Selection methods are valid MultiSelection methods.
//
// Examples:
//
//    selection.Find("table").All("tr").At(2).First("td input[type=checkbox]").Check()
// Will check the first checkbox in the third row of the only table.
//    selection.Find("table").All("tr").Find("td").All("input[type=checkbox]").Check()
// Will check all checkboxes in the first-and-only cell of each row in the only table.
type Selection interface {
	// Selections have Find*, All*, and First* methods to create new Selections
	Selectable

	// String returns a string representation of the selection, ex.
	//    CSS: .some-class | XPath: //table [3] | Link "click me" [single]
	String() string

	// Count returns the number of elements the selection refers to.
	Count() (int, error)

	// EqualsElement returns whether or not two selections of exactly
	// one element each refer to the same element.
	EqualsElement(comparable interface{}) (bool, error)

	// Click clicks on all of the elements the selection refers to.
	Click() error

	// DoubleClick double-clicks on all of the elements the selection refers to.
	DoubleClick() error

	// Fill fills all of the input fields the selection refers to.
	Fill(text string) error

	// Check checks all of the unchecked checkboxes that the selection refers to.
	Check() error

	// Uncheck unchecks all of the checked checkboxes that the selection refers to.
	Uncheck() error

	// Select, when called on any number of <select> elements, will select all
	// <options> under those elements that match the provided text.
	Select(text string) error

	// Submit submits a form. The selection may refer to the form itself, or any
	// input element contained within the form.
	Submit() error

	// Text returns text for exactly one element.
	Text() (string, error)

	// Attribute returns an attribute value for exactly one element.
	Attribute(attribute string) (string, error)

	// CSS returns a CSS style property value for exactly one element.
	CSS(property string) (string, error)

	// Selected returns true if all of the elements that the selection
	// refers to are selected.
	Selected() (bool, error)

	// Visible returns true if all of the elements that the selection
	// refers to are visible.
	Visible() (bool, error)

	// Enabled returns true if all of the elements that the selection
	// refers to are enabled.
	Enabled() (bool, error)
}

// A MultiSelection is a Selection with elements that may be indexed using
// the At(index) or Single() methods.
//
// A Selection returned by At(index) or Single() may still refer to multiple
// elements if any parent of the MultiSelection refers to multiple elements.
//
// Examples:
//    selection.All("section").All("form").At(1).Submit()
// Will submit the second form in each section.
//    selection.All("div").Find("h1").Click()
// Will click the only h1 in each div, failing if any div does not contain exactly one h1.
type MultiSelection interface {
	// All Selection methods are valid MultiSelection methods.
	Selection

	// At finds the element at the provided index.
	At(index int) Selection

	// Single specifies that the selection must refer to exactly one element.
	//    selection.Find("#selector")
	// is equivalent to
	//    selection.All("#selector").Single()
	Single() Selection
}

// Selectable methods apply their selectors to each element in the selection they
// are called on. If the selection they are called on refers to multiple
// elements, the resulting selection will refer to at least that many elements.
type Selectable interface {
	// For each element in the current selection:

	// Find finds exactly one element by CSS selector.
	Find(selector string) Selection

	// FindByXPath finds exactly one element by XPath selector.
	FindByXPath(selector string) Selection

	// FindByLink finds exactly one element by anchor link text.
	FindByLink(text string) Selection

	// FindByLabel finds exactly one element by associated label text.
	FindByLabel(text string) Selection

	// First finds the first element by CSS selector.
	First(selector string) Selection

	// FirstByXPath finds the first element by XPath selector.
	FirstByXPath(selector string) Selection

	// FirstByLink finds the first element by anchor link text.
	FirstByLink(text string) Selection

	// FirstByLabel finds the first element by associated label text.
	FirstByLabel(text string) Selection

	// All finds zero or more elements by CSS selector.
	All(selector string) MultiSelection

	// AllByXPath finds zero or more elements by XPath selector.
	AllByXPath(selector string) MultiSelection

	// AllByLink finds zero or more elements by anchor link text.
	AllByLink(text string) MultiSelection

	// AllByLabel finds zero or more elements by associated label text.
	AllByLabel(text string) MultiSelection
}

type baseSelection struct {
	*selection.Selection
}

func (s *baseSelection) EqualsElement(comparable interface{}) (bool, error) {
	var other *selection.Selection
	switch comparable := comparable.(type) {
	case *baseSelection:
		other = comparable.Selection
	case *multiSelection:
		other = comparable.baseSelection.Selection
	default:
		return false, errors.New("provided object is not a Selection")
	}
	return s.Selection.EqualsElement(other)
}

func (s *baseSelection) Find(selector string) Selection {
	return s.All(selector).Single()
}

func (s *baseSelection) FindByXPath(selector string) Selection {
	return s.AllByXPath(selector).Single()
}

func (s *baseSelection) FindByLink(text string) Selection {
	return s.AllByLink(text).Single()
}

func (s *baseSelection) FindByLabel(text string) Selection {
	return s.AllByLabel(text).Single()
}

func (s *baseSelection) First(selector string) Selection {
	return s.All(selector).At(0)
}

func (s *baseSelection) FirstByXPath(selector string) Selection {
	return s.AllByXPath(selector).At(0)
}

func (s *baseSelection) FirstByLink(text string) Selection {
	return s.AllByLink(text).At(0)
}

func (s *baseSelection) FirstByLabel(text string) Selection {
	return s.AllByLabel(text).At(0)
}

func (s *baseSelection) All(selector string) MultiSelection {
	return &multiSelection{&baseSelection{s.AppendCSS(selector)}}
}

func (s *baseSelection) AllByXPath(selector string) MultiSelection {
	return &multiSelection{&baseSelection{s.AppendXPath(selector)}}
}

func (s *baseSelection) AllByLink(text string) MultiSelection {
	return &multiSelection{&baseSelection{s.AppendLink(text)}}
}

func (s *baseSelection) AllByLabel(text string) MultiSelection {
	return &multiSelection{&baseSelection{s.AppendLabeled(text)}}
}

type multiSelection struct {
	*baseSelection
}

func (m *multiSelection) At(index int) Selection {
	return &baseSelection{m.AtIndex(index)}
}

func (m *multiSelection) Single() Selection {
	return &baseSelection{m.SingleOnly()}
}
<commit_msg>Remove multiSelection struct in favor of interface-only restrictions<commit_after>package core

import (
	"errors"
	"github.com/sclevine/agouti/core/internal/selection"
)

// Selection instances refer to a selection of elements.
// All Selection methods are valid MultiSelection methods.
//
// Examples:
//
//    selection.Find("table").All("tr").At(2).First("td input[type=checkbox]").Check()
// Will check the first checkbox in the third row of the only table.
//    selection.Find("table").All("tr").Find("td").All("input[type=checkbox]").Check()
// Will check all checkboxes in the first-and-only cell of each row in the only table.
type Selection interface {
	// Selections have Find*, All*, and First* methods to create new Selections
	Selectable

	// String returns a string representation of the selection, ex.
	//    CSS: .some-class | XPath: //table [3] | Link "click me" [single]
	String() string

	// Count returns the number of elements the selection refers to.
	Count() (int, error)

	// EqualsElement returns whether or not two selections of exactly
	// one element each refer to the same element.
	EqualsElement(comparable interface{}) (bool, error)

	// Click clicks on all of the elements the selection refers to.
	Click() error

	// DoubleClick double-clicks on all of the elements the selection refers to.
	DoubleClick() error

	// Fill fills all of the input fields the selection refers to.
	Fill(text string) error

	// Check checks all of the unchecked checkboxes that the selection refers to.
	Check() error

	// Uncheck unchecks all of the checked checkboxes that the selection refers to.
	Uncheck() error

	// Select, when called on any number of <select> elements, will select all
	// <options> under those elements that match the provided text.
	Select(text string) error

	// Submit submits a form. The selection may refer to the form itself, or any
	// input element contained within the form.
	Submit() error

	// Text returns text for exactly one element.
	Text() (string, error)

	// Attribute returns an attribute value for exactly one element.
	Attribute(attribute string) (string, error)

	// CSS returns a CSS style property value for exactly one element.
	CSS(property string) (string, error)

	// Selected returns true if all of the elements that the selection
	// refers to are selected.
	Selected() (bool, error)

	// Visible returns true if all of the elements that the selection
	// refers to are visible.
	Visible() (bool, error)

	// Enabled returns true if all of the elements that the selection
	// refers to are enabled.
	Enabled() (bool, error)
}

// A MultiSelection is a Selection with elements that may be indexed using
// the At(index) or Single() methods.
//
// A Selection returned by At(index) or Single() may still refer to multiple
// elements if any parent of the MultiSelection refers to multiple elements.
//
// Examples:
//    selection.All("section").All("form").At(1).Submit()
// Will submit the second form in each section.
//    selection.All("div").Find("h1").Click()
// Will click the only h1 in each div, failing if any div does not contain exactly one h1.
type MultiSelection interface {
	// All Selection methods are valid MultiSelection methods.
	Selection

	// At finds the element at the provided index.
	At(index int) Selection

	// Single specifies that the selection must refer to exactly one element.
	//    selection.Find("#selector")
	// is equivalent to
	//    selection.All("#selector").Single()
	Single() Selection
}

// Selectable methods apply their selectors to each element in the selection they
// are called on. If the selection they are called on refers to multiple
// elements, the resulting selection will refer to at least that many elements.
type Selectable interface {
	// For each element in the current selection:

	// Find finds exactly one element by CSS selector.
	Find(selector string) Selection

	// FindByXPath finds exactly one element by XPath selector.
	FindByXPath(selector string) Selection

	// FindByLink finds exactly one element by anchor link text.
	FindByLink(text string) Selection

	// FindByLabel finds exactly one element by associated label text.
	FindByLabel(text string) Selection

	// First finds the first element by CSS selector.
	First(selector string) Selection

	// FirstByXPath finds the first element by XPath selector.
	FirstByXPath(selector string) Selection

	// FirstByLink finds the first element by anchor link text.
	FirstByLink(text string) Selection

	// FirstByLabel finds the first element by associated label text.
	FirstByLabel(text string) Selection

	// All finds zero or more elements by CSS selector.
	All(selector string) MultiSelection

	// AllByXPath finds zero or more elements by XPath selector.
	AllByXPath(selector string) MultiSelection

	// AllByLink finds zero or more elements by anchor link text.
	AllByLink(text string) MultiSelection

	// AllByLabel finds zero or more elements by associated label text.
	AllByLabel(text string) MultiSelection
}

type baseSelection struct {
	*selection.Selection
}

func (s *baseSelection) EqualsElement(comparable interface{}) (bool, error) {
	other, ok := comparable.(*baseSelection)
	if !ok {
		return false, errors.New("provided object is not a Selection")
	}
	return s.Selection.EqualsElement(other.Selection)
}

func (s *baseSelection) Find(selector string) Selection {
	return s.All(selector).Single()
}

func (s *baseSelection) FindByXPath(selector string) Selection {
	return s.AllByXPath(selector).Single()
}

func (s *baseSelection) FindByLink(text string) Selection {
	return s.AllByLink(text).Single()
}

func (s *baseSelection) FindByLabel(text string) Selection {
	return s.AllByLabel(text).Single()
}

func (s *baseSelection) First(selector string) Selection {
	return s.All(selector).At(0)
}

func (s *baseSelection) FirstByXPath(selector string) Selection {
	return s.AllByXPath(selector).At(0)
}

func (s *baseSelection) FirstByLink(text string) Selection {
	return s.AllByLink(text).At(0)
}

func (s *baseSelection) FirstByLabel(text string) Selection {
	return s.AllByLabel(text).At(0)
}

func (s *baseSelection) All(selector string) MultiSelection {
	return &baseSelection{s.AppendCSS(selector)}
}

func (s *baseSelection) AllByXPath(selector string) MultiSelection {
	return &baseSelection{s.AppendXPath(selector)}
}

func (s *baseSelection) AllByLink(text string) MultiSelection {
	return &baseSelection{s.AppendLink(text)}
}

func (s *baseSelection) AllByLabel(text string) MultiSelection {
	return &baseSelection{s.AppendLabeled(text)}
}

func (m *baseSelection) At(index int) Selection {
	return &baseSelection{m.AtIndex(index)}
}

func (m *baseSelection) Single() Selection {
	return &baseSelection{m.SingleOnly()}
}
<|endoftext|>