<commit_before>// Copyright 2010  The "go-linoise" Authors
//
// Use of this source code is governed by the Simplified BSD License
// that can be found in the LICENSE file.
//
// This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, either express or implied. See the License
// for more details.

/* Important: linoise sets tty in 'raw mode' so there is to use CR+LF (\r\n) at
writing.
*/

package linoise

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

	"github.com/kless/go-term/term"
)

// Values by default for prompts.
var (
	PS1 = "linoise$ "
	PS2 = "> "
)

// Input / Output
var (
	input  *os.File = os.Stdin
	output *os.File = os.Stdout
)

// === Init
// ===

func init() {
	if err := term.CheckIsatty(input.Fd()); err != nil {
		panic(err)
	}
}

// === Type
// ===

// Represents a line.
type Line struct {
	useHistory bool
	ps1Len     int      // Primary prompt size
	ps1        string   // Primary prompt
	ps2        string   // Command continuations
	buf        *buffer  // Text buffer
	hist       *history // History file
}

// Gets a line type using the primary prompt by default. Sets the TTY raw mode.
func NewLine(hist *history) *Line {
	term.MakeRaw()

	buf := newBuffer(len(PS1))
	buf.insertRunes([]int(PS1))

	return &Line{
		hasHistory(hist),
		len(PS1),
		PS1,
		PS2,
		buf,
		hist,
	}
}

// Gets a line type using the given prompt as primary. Sets the TTY raw mode.
// 'ansiLen' is the length of ANSI codes that the prompt could have.
func NewLinePrompt(prompt string, ansiLen int, hist *history) *Line {
	term.MakeRaw()

	buf := newBuffer(len(prompt) - ansiLen)
	buf.insertRunes([]int(prompt))

	return &Line{
		hasHistory(hist),
		len(prompt) - ansiLen,
		prompt,
		PS2,
		buf,
		hist,
	}
}

// Restores terminal settings so it is disabled the raw mode.
func (ln *Line) RestoreTerm() {
	term.RestoreTerm()
}

// Tests if it has an history file.
func hasHistory(h *history) bool {
	if h == nil {
		return false
	}
	return true
}

// === Output
// ===

// Prints the primary prompt.
func (ln *Line) prompt() (err os.Error) {
	if _, err = output.Write(delLine_CR); err != nil {
		return outputError(err.String())
	}
	if _, err = fmt.Fprint(output, ln.ps1); err != nil {
		return outputError(err.String())
	}

	ln.buf.pos, ln.buf.size = ln.ps1Len, ln.ps1Len
	return
}

// === Get
// ===

// Reads charactes from input to write them to output, allowing line editing.
// The errors that could return are to indicate if Ctrl-D was pressed, and for
// both input / output errors.
func (ln *Line) Read() (line string, err os.Error) {
	var anotherLine []int  // For lines got from history.
	var isHistoryUsed bool // If the history has been accessed.

	in := bufio.NewReader(input) // Read input.
	seq := make([]byte, 2)       // For escape sequences.
	seq2 := make([]byte, 1)      // Extended escape sequences.

	// Print the primary prompt.
	if err = ln.prompt(); err != nil {
		return "", err
	}

	// === Detect change of window size.
	go term.TrapWinsize()

	go func() {
		for {
			<-term.WinsizeChan // Wait for.

			_, ln.buf.winColumns = term.GetWinsizeInChar()
			ln.buf.refresh()
		}
	}()

	for {
		rune, _, err := in.ReadRune()
		if err != nil {
			return "", inputError(err.String())
		}

		switch rune {
		default:
			if err = ln.buf.insertRune(rune); err != nil {
				return "", err
			}
			continue

		case 13: // enter
			line = ln.buf.toString()

			if ln.useHistory {
				ln.hist.Add(line)
			}
			if _, err = output.Write(_CR_LF); err != nil {
				return "", outputError(err.String())
			}

			return strings.TrimSpace(line), nil

		case 127, 8: // backspace, Ctrl-h
			if err = ln.buf.deletePrev(); err != nil {
				return "", err
			}
			continue

		case 9: // horizontal tab
			// TODO: disabled by now
			continue

		case 3: // Ctrl-c
			if err = ln.buf.insertRunes(ctrlC); err != nil {
				return "", err
			}
			if _, err = output.Write(_CR_LF); err != nil {
				return "", outputError(err.String())
			}
			if err = ln.prompt(); err != nil {
				return "", err
			}

			continue

		case 4: // Ctrl-d
			if err = ln.buf.insertRunes(ctrlD); err != nil {
				return "", err
			}
			if _, err = output.Write(_CR_LF); err != nil {
				return "", outputError(err.String())
			}

			return "", ErrCtrlD

		// Escape sequence
		case 27: // Escape: Ctrl-[ ("033" in octal, "\x1b" in hexadecimal)
			if _, err = in.Read(seq); err != nil {
				return "", inputError(err.String())
			}

			if seq[0] == 79 { // 'O'
				switch seq[1] {
				case 72: // Home: "\x1bOH"
					goto _start
				case 70: // End: "\x1bOF"
					goto _end
				}
			}

			if seq[0] == 91 { // Left square bracket: "["
				switch seq[1] {
				case 68: // "\x1b[D"
					goto _leftArrow
				case 67: // "\x1b[C"
					goto _rightArrow
				case 65, 66: // Up: "\x1b[A"; Down: "\x1b[B"
					goto _upDownArrow
				}

				// Extended escape.
				if seq[1] > 48 && seq[1] < 55 {
					if _, err = in.Read(seq2); err != nil {
						return "", inputError(err.String())
					}

					if seq2[0] == 126 { // '~'
						switch seq[1] {
						//case 50: // Insert: "\x1b[2~"

						case 51: // Delete: "\x1b[3~"
							if err = ln.buf.delete(); err != nil {
								return "", err
							}
						//case 53: // RePag: "\x1b[5~"

						//case 54: // AvPag: "\x1b[6~"

						}
					}
				}
			}
			continue

		case 20: // Ctrl-t, swap actual character by the previous one.
			if err = ln.buf.swap(); err != nil {
				return "", err
			}
			continue

		case 21: // Ctrl+u, delete the whole line.
			if err = ln.buf.deleteLine(); err != nil {
				return "", err
			}
			if err = ln.prompt(); err != nil {
				return "", err
			}
			continue

		case 11: // Ctrl+k, delete from current to end of line.
			if err = ln.buf.deleteRight(); err != nil {
				return "", err
			}
			continue

		case 1: // Ctrl+a, go to the start of the line.
			goto _start

		case 5: // Ctrl+e, go to the end of the line.
			goto _end

		case 2: // Ctrl-b
			goto _leftArrow

		case 6: // Ctrl-f
			goto _rightArrow

		case 16: // Ctrl-p
			seq[1] = 65
			goto _upDownArrow

		case 14: // Ctrl-n
			seq[1] = 66
			goto _upDownArrow
		}

	_upDownArrow: // Up and down arrow: history
		if !ln.useHistory {
			continue
		}

		// Up
		if seq[1] == 65 {
			anotherLine, err = ln.hist.Prev()
			// Down
		} else {
			anotherLine, err = ln.hist.Next()
		}
		if err != nil {
			continue
		}

		// Update the current history entry before to overwrite it with
		// the next one.
		// TODO: it has to be removed before of to be saved the history
		if !isHistoryUsed {
			ln.hist.Add(ln.buf.toString())
		}
		isHistoryUsed = true

		ln.buf.grow(len(anotherLine))
		ln.buf.size = len(anotherLine)
		copy(ln.buf.data[ln.ps1Len:], anotherLine)

		if err = ln.buf.refresh(); err != nil {
			return "", err
		}
		continue

	_leftArrow:
		if err = ln.buf.backward(); err != nil {
			return "", err
		}
		continue

	_rightArrow:
		if err = ln.buf.forward(); err != nil {
			return "", err
		}
		continue

	_start:
		if err = ln.buf.start(); err != nil {
			return "", err
		}
		continue

	_end:
		if _, err = ln.buf.end(); err != nil {
			return "", err
		}
		continue
	}
	return
}

<commit_msg>fix crash in history handling<commit_after>// Copyright 2010  The "go-linoise" Authors
//
// Use of this source code is governed by the Simplified BSD License
// that can be found in the LICENSE file.
//
// This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, either express or implied. See the License
// for more details.

/* Important: linoise sets tty in 'raw mode' so there is to use CR+LF (\r\n) at
writing.
*/

package linoise

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

	"github.com/kless/go-term/term"
)

// Values by default for prompts.
var (
	PS1 = "linoise$ "
	PS2 = "> "
)

// Input / Output
var (
	input  *os.File = os.Stdin
	output *os.File = os.Stdout
)

// === Init
// ===

func init() {
	if err := term.CheckIsatty(input.Fd()); err != nil {
		panic(err)
	}
}

// === Type
// ===

// Represents a line.
type Line struct {
	useHistory bool
	ps1Len     int      // Primary prompt size
	ps1        string   // Primary prompt
	ps2        string   // Command continuations
	buf        *buffer  // Text buffer
	hist       *history // History file
}

// Gets a line type using the primary prompt by default. Sets the TTY raw mode.
func NewLine(hist *history) *Line {
	term.MakeRaw()

	buf := newBuffer(len(PS1))
	buf.insertRunes([]int(PS1))

	return &Line{
		hasHistory(hist),
		len(PS1),
		PS1,
		PS2,
		buf,
		hist,
	}
}

// Gets a line type using the given prompt as primary. Sets the TTY raw mode.
// 'ansiLen' is the length of ANSI codes that the prompt could have.
func NewLinePrompt(prompt string, ansiLen int, hist *history) *Line {
	term.MakeRaw()

	buf := newBuffer(len(prompt) - ansiLen)
	buf.insertRunes([]int(prompt))

	return &Line{
		hasHistory(hist),
		len(prompt) - ansiLen,
		prompt,
		PS2,
		buf,
		hist,
	}
}

// Restores terminal settings so it is disabled the raw mode.
func (ln *Line) RestoreTerm() {
	term.RestoreTerm()
}

// Tests if it has an history file.
func hasHistory(h *history) bool {
	if h == nil {
		return false
	}
	return true
}

// === Output
// ===

// Prints the primary prompt.
func (ln *Line) prompt() (err os.Error) {
	if _, err = output.Write(delLine_CR); err != nil {
		return outputError(err.String())
	}
	if _, err = fmt.Fprint(output, ln.ps1); err != nil {
		return outputError(err.String())
	}

	ln.buf.pos, ln.buf.size = ln.ps1Len, ln.ps1Len
	return
}

// === Get
// ===

// Reads charactes from input to write them to output, allowing line editing.
// The errors that could return are to indicate if Ctrl-D was pressed, and for
// both input / output errors.
func (ln *Line) Read() (line string, err os.Error) {
	var anotherLine []int  // For lines got from history.
	var isHistoryUsed bool // If the history has been accessed.

	in := bufio.NewReader(input) // Read input.
	seq := make([]byte, 2)       // For escape sequences.
	seq2 := make([]byte, 1)      // Extended escape sequences.

	// Print the primary prompt.
	if err = ln.prompt(); err != nil {
		return "", err
	}

	// === Detect change of window size.
	go term.TrapWinsize()

	go func() {
		for {
			<-term.WinsizeChan // Wait for.

			_, ln.buf.winColumns = term.GetWinsizeInChar()
			ln.buf.refresh()
		}
	}()

	for {
		rune, _, err := in.ReadRune()
		if err != nil {
			return "", inputError(err.String())
		}

		switch rune {
		default:
			if err = ln.buf.insertRune(rune); err != nil {
				return "", err
			}
			continue

		case 13: // enter
			line = ln.buf.toString()

			if ln.useHistory {
				ln.hist.Add(line)
			}
			if _, err = output.Write(_CR_LF); err != nil {
				return "", outputError(err.String())
			}

			return strings.TrimSpace(line), nil

		case 127, 8: // backspace, Ctrl-h
			if err = ln.buf.deletePrev(); err != nil {
				return "", err
			}
			continue

		case 9: // horizontal tab
			// TODO: disabled by now
			continue

		case 3: // Ctrl-c
			if err = ln.buf.insertRunes(ctrlC); err != nil {
				return "", err
			}
			if _, err = output.Write(_CR_LF); err != nil {
				return "", outputError(err.String())
			}
			if err = ln.prompt(); err != nil {
				return "", err
			}

			continue

		case 4: // Ctrl-d
			if err = ln.buf.insertRunes(ctrlD); err != nil {
				return "", err
			}
			if _, err = output.Write(_CR_LF); err != nil {
				return "", outputError(err.String())
			}

			return "", ErrCtrlD

		// Escape sequence
		case 27: // Escape: Ctrl-[ ("033" in octal, "\x1b" in hexadecimal)
			if _, err = in.Read(seq); err != nil {
				return "", inputError(err.String())
			}

			if seq[0] == 79 { // 'O'
				switch seq[1] {
				case 72: // Home: "\x1bOH"
					goto _start
				case 70: // End: "\x1bOF"
					goto _end
				}
			}

			if seq[0] == 91 { // Left square bracket: "["
				switch seq[1] {
				case 68: // "\x1b[D"
					goto _leftArrow
				case 67: // "\x1b[C"
					goto _rightArrow
				case 65, 66: // Up: "\x1b[A"; Down: "\x1b[B"
					goto _upDownArrow
				}

				// Extended escape.
				if seq[1] > 48 && seq[1] < 55 {
					if _, err = in.Read(seq2); err != nil {
						return "", inputError(err.String())
					}

					if seq2[0] == 126 { // '~'
						switch seq[1] {
						//case 50: // Insert: "\x1b[2~"

						case 51: // Delete: "\x1b[3~"
							if err = ln.buf.delete(); err != nil {
								return "", err
							}
						//case 53: // RePag: "\x1b[5~"

						//case 54: // AvPag: "\x1b[6~"

						}
					}
				}
			}
			continue

		case 20: // Ctrl-t, swap actual character by the previous one.
			if err = ln.buf.swap(); err != nil {
				return "", err
			}
			continue

		case 21: // Ctrl+u, delete the whole line.
			if err = ln.buf.deleteLine(); err != nil {
				return "", err
			}
			if err = ln.prompt(); err != nil {
				return "", err
			}
			continue

		case 11: // Ctrl+k, delete from current to end of line.
			if err = ln.buf.deleteRight(); err != nil {
				return "", err
			}
			continue

		case 1: // Ctrl+a, go to the start of the line.
			goto _start

		case 5: // Ctrl+e, go to the end of the line.
			goto _end

		case 2: // Ctrl-b
			goto _leftArrow

		case 6: // Ctrl-f
			goto _rightArrow

		case 16: // Ctrl-p
			seq[1] = 65
			goto _upDownArrow

		case 14: // Ctrl-n
			seq[1] = 66
			goto _upDownArrow
		}

	_upDownArrow: // Up and down arrow: history
		if !ln.useHistory {
			continue
		}

		// Up
		if seq[1] == 65 {
			anotherLine, err = ln.hist.Prev()
			// Down
		} else {
			anotherLine, err = ln.hist.Next()
		}
		if err != nil {
			continue
		}

		// Update the current history entry before to overwrite it with
		// the next one.
		// TODO: it has to be removed before of to be saved the history
		if !isHistoryUsed {
			ln.hist.Add(ln.buf.toString())
		}
		isHistoryUsed = true

		ln.buf.grow(len(anotherLine))
		ln.buf.size = len(anotherLine) + ln.buf.promptLen
		copy(ln.buf.data[ln.ps1Len:], anotherLine)

		if err = ln.buf.refresh(); err != nil {
			return "", err
		}
		continue

	_leftArrow:
		if err = ln.buf.backward(); err != nil {
			return "", err
		}
		continue

	_rightArrow:
		if err = ln.buf.forward(); err != nil {
			return "", err
		}
		continue

	_start:
		if err = ln.buf.start(); err != nil {
			return "", err
		}
		continue

	_end:
		if _, err = ln.buf.end(); err != nil {
			return "", err
		}
		continue
	}
	return
}

<|endoftext|>


<commit_before>package mesos_test

import (
	"testing"

	"github.com/mesos/mesos-go"
)

func TestOpCreate(t *testing.T) {
	var (
		total = resources(
			resource(name("cpus"), valueScalar(1)),
			resource(name("mem"), valueScalar(512)),
			resource(name("disk"), valueScalar(1000), role("role")),
		)
		volume1 = resource(name("disk"), valueScalar(200), role("role"), disk("1", "path"))
		volume2 = resource(name("disk"), valueScalar(2000), role("role"), disk("1", "path"))
	)
	op := create(resources(volume1))
	rs, err := op.Apply(total)
	if err != nil {
		t.Fatalf("unexpected error: %+v", err)
	}
	expected := resources(
		resource(name("cpus"), valueScalar(1)),
		resource(name("mem"), valueScalar(512)),
		resource(name("disk"), valueScalar(800), role("role")),
		volume1,
	)
	if !expected.Equivalent(rs) {
		t.Fatalf("expected %v instead of %v", expected, rs)
	}

	// check the case of insufficient disk resources
	op = create(resources(volume2))
	_, err = op.Apply(total)
	if err == nil {
		t.Fatalf("expected an error due to insufficient disk resources")
	}
}

func TestOpUnreserve(t *testing.T) {
	var (
		reservedCPU = resources(
			resource(name("cpus"),
				valueScalar(1),
				role("role"),
				reservation(reservedBy("principal"))))
		reservedMem = resources(
			resource(name("mem"),
				valueScalar(512),
				role("role"),
				reservation(reservedBy("principal"))))
		reserved = reservedCPU.Plus(reservedMem...)
	)

	// test case 1: unreserve some amount of CPU that's already been reserved
	unreservedCPU := reservedCPU.Flatten()
	t.Log("unreservedCPU=" + unreservedCPU.String())

	wantsUnreserved := reservedMem.Plus(unreservedCPU...)
	actualUnreserved, err := unreserve(reservedCPU).Apply(reserved)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !wantsUnreserved.Equivalent(actualUnreserved) {
		t.Errorf("expected resources %+v instead of %+v", wantsUnreserved, actualUnreserved)
	}

	// test case 2: unreserve some amount of CPU greater than that which already been reserved
	reservedCPU2 := resources(
		resource(name("cpus"),
			valueScalar(2),
			role("role"),
			reservation(reservedBy("principal"))))
	_, err = unreserve(reservedCPU2).Apply(reserved)
	if err == nil {
		t.Fatalf("expected reservation error")
	}
}

func TestOpReserve(t *testing.T) {
	// func opReserve(operation mesos.Offer_Operation, resources mesos.Resources) (mesos.Resources, error)
	var (
		unreservedCPU = resources(resource(name("cpus"), valueScalar(1)))
		unreservedMem = resources(resource(name("mem"), valueScalar(512)))
		unreserved    = unreservedCPU.Plus(unreservedMem...)
		reservedCPU1  = unreservedCPU.Flatten(mesos.Role("role").Assign(), reservedBy("principal").Assign())
	)

	// test case 1: reserve an amount of CPU that's available
	wantsReserved := unreservedMem.Plus(reservedCPU1...)
	actualReserved, err := reserve(reservedCPU1).Apply(unreserved)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !wantsReserved.Equivalent(actualReserved) {
		t.Errorf("expected resources %+v instead of %+v", wantsReserved, actualReserved)
	}

	// test case 2: reserve an amount of CPU that's NOT available
	reservedCPU2 := resources(
		resource(name("cpus"),
			valueScalar(2),
			role("role"),
			reservation(reservedBy("principal"))))
	_, err = reserve(reservedCPU2).Apply(unreserved)
	if err == nil {
		t.Fatalf("expected reservation error")
	}
}

func reservedBy(principal string) *mesos.Resource_ReservationInfo {
	result := &mesos.Resource_ReservationInfo{}
	if principal != "" {
		result.Principal = &principal
	}
	return result
}

func reserve(r mesos.Resources) *mesos.Offer_Operation {
	return &mesos.Offer_Operation{
		Type: mesos.RESERVE.Enum(),
		Reserve: &mesos.Offer_Operation_Reserve{
			Resources: r,
		},
	}
}

func unreserve(r mesos.Resources) *mesos.Offer_Operation {
	return &mesos.Offer_Operation{
		Type: mesos.UNRESERVE.Enum(),
		Unreserve: &mesos.Offer_Operation_Unreserve{
			Resources: r,
		},
	}
}

func create(r mesos.Resources) *mesos.Offer_Operation {
	return &mesos.Offer_Operation{
		Type: mesos.CREATE.Enum(),
		Create: &mesos.Offer_Operation_Create{
			Volumes: r,
		},
	}
}

func reservation(ri *mesos.Resource_ReservationInfo) resourceOpt {
	return func(r *mesos.Resource) {
		r.Reservation = ri
	}
}

func disk(persistenceID, containerPath string) resourceOpt {
	return func(r *mesos.Resource) {
		r.Disk = &mesos.Resource_DiskInfo{}
		if containerPath != "" {
			r.Disk.Volume = &mesos.Volume{ContainerPath: containerPath}
		}
		if persistenceID != "" {
			r.Disk.Persistence = &mesos.Resource_DiskInfo_Persistence{ID: persistenceID}
		}
	}
}
<commit_msg>fix compilation error, s/Role/RoleName<commit_after>package mesos_test

import (
	"testing"

	"github.com/mesos/mesos-go"
)

func TestOpCreate(t *testing.T) {
	var (
		total = resources(
			resource(name("cpus"), valueScalar(1)),
			resource(name("mem"), valueScalar(512)),
			resource(name("disk"), valueScalar(1000), role("role")),
		)
		volume1 = resource(name("disk"), valueScalar(200), role("role"), disk("1", "path"))
		volume2 = resource(name("disk"), valueScalar(2000), role("role"), disk("1", "path"))
	)
	op := create(resources(volume1))
	rs, err := op.Apply(total)
	if err != nil {
		t.Fatalf("unexpected error: %+v", err)
	}
	expected := resources(
		resource(name("cpus"), valueScalar(1)),
		resource(name("mem"), valueScalar(512)),
		resource(name("disk"), valueScalar(800), role("role")),
		volume1,
	)
	if !expected.Equivalent(rs) {
		t.Fatalf("expected %v instead of %v", expected, rs)
	}

	// check the case of insufficient disk resources
	op = create(resources(volume2))
	_, err = op.Apply(total)
	if err == nil {
		t.Fatalf("expected an error due to insufficient disk resources")
	}
}

func TestOpUnreserve(t *testing.T) {
	var (
		reservedCPU = resources(
			resource(name("cpus"),
				valueScalar(1),
				role("role"),
				reservation(reservedBy("principal"))))
		reservedMem = resources(
			resource(name("mem"),
				valueScalar(512),
				role("role"),
				reservation(reservedBy("principal"))))
		reserved = reservedCPU.Plus(reservedMem...)
	)

	// test case 1: unreserve some amount of CPU that's already been reserved
	unreservedCPU := reservedCPU.Flatten()
	t.Log("unreservedCPU=" + unreservedCPU.String())

	wantsUnreserved := reservedMem.Plus(unreservedCPU...)
	actualUnreserved, err := unreserve(reservedCPU).Apply(reserved)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !wantsUnreserved.Equivalent(actualUnreserved) {
		t.Errorf("expected resources %+v instead of %+v", wantsUnreserved, actualUnreserved)
	}

	// test case 2: unreserve some amount of CPU greater than that which already been reserved
	reservedCPU2 := resources(
		resource(name("cpus"),
			valueScalar(2),
			role("role"),
			reservation(reservedBy("principal"))))
	_, err = unreserve(reservedCPU2).Apply(reserved)
	if err == nil {
		t.Fatalf("expected reservation error")
	}
}

func TestOpReserve(t *testing.T) {
	// func opReserve(operation mesos.Offer_Operation, resources mesos.Resources) (mesos.Resources, error)
	var (
		unreservedCPU = resources(resource(name("cpus"), valueScalar(1)))
		unreservedMem = resources(resource(name("mem"), valueScalar(512)))
		unreserved    = unreservedCPU.Plus(unreservedMem...)
		reservedCPU1  = unreservedCPU.Flatten(mesos.RoleName("role").Assign(), reservedBy("principal").Assign())
	)

	// test case 1: reserve an amount of CPU that's available
	wantsReserved := unreservedMem.Plus(reservedCPU1...)
	actualReserved, err := reserve(reservedCPU1).Apply(unreserved)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !wantsReserved.Equivalent(actualReserved) {
		t.Errorf("expected resources %+v instead of %+v", wantsReserved, actualReserved)
	}

	// test case 2: reserve an amount of CPU that's NOT available
	reservedCPU2 := resources(
		resource(name("cpus"),
			valueScalar(2),
			role("role"),
			reservation(reservedBy("principal"))))
	_, err = reserve(reservedCPU2).Apply(unreserved)
	if err == nil {
		t.Fatalf("expected reservation error")
	}
}

func reservedBy(principal string) *mesos.Resource_ReservationInfo {
	result := &mesos.Resource_ReservationInfo{}
	if principal != "" {
		result.Principal = &principal
	}
	return result
}

func reserve(r mesos.Resources) *mesos.Offer_Operation {
	return &mesos.Offer_Operation{
		Type: mesos.RESERVE.Enum(),
		Reserve: &mesos.Offer_Operation_Reserve{
			Resources: r,
		},
	}
}

func unreserve(r mesos.Resources) *mesos.Offer_Operation {
	return &mesos.Offer_Operation{
		Type: mesos.UNRESERVE.Enum(),
		Unreserve: &mesos.Offer_Operation_Unreserve{
			Resources: r,
		},
	}
}

func create(r mesos.Resources) *mesos.Offer_Operation {
	return &mesos.Offer_Operation{
		Type: mesos.CREATE.Enum(),
		Create: &mesos.Offer_Operation_Create{
			Volumes: r,
		},
	}
}

func reservation(ri *mesos.Resource_ReservationInfo) resourceOpt {
	return func(r *mesos.Resource) {
		r.Reservation = ri
	}
}

func disk(persistenceID, containerPath string) resourceOpt {
	return func(r *mesos.Resource) {
		r.Disk = &mesos.Resource_DiskInfo{}
		if containerPath != "" {
			r.Disk.Volume = &mesos.Volume{ContainerPath: containerPath}
		}
		if persistenceID != "" {
			r.Disk.Persistence = &mesos.Resource_DiskInfo_Persistence{ID: persistenceID}
		}
	}
}
<|endoftext|>


<commit_before>package models

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"time"

	"github.com/8legd/hugocms/config"
	"github.com/jinzhu/gorm"
)

var slugger = regexp.MustCompile("[^a-z0-9]+")

type Page struct {
	gorm.Model

	Path       string
	prevPath   string
	MenuWeight uint
	Links      []PageLink

	Name     string
	prevName string

	SEO PageMeta

	ContentColumns []PageContentColumn
}

type PageLink struct {
	gorm.Model

	PageID uint

	LinkText string `sql:"size:2000"`
	Link     string
}

type PageMeta struct {
	gorm.Model

	PageID uint

	PageTitle   string
	Description string
}

type PageContentColumn struct {
	gorm.Model

	PageID uint

	ColumnWidth   string
	ColumnHeading string
	ColumnText    string                   `sql:"size:2000"`
	ColumnImage   []PageContentColumnImage // We only use 1 image but a slice allows image removal
	VideoID       uint
	Video         Video
	SlideshowID   uint
	Slideshow     Slideshow
	ColumnLink    string
}

type PageContentColumnImage struct {
	gorm.Model

	PageContentColumnID uint

	Image     ContentImageStorage `sql:"type:varchar(4096)"`
	Alt       string
	Alignment string
}

func slug(s string) string {
	if s == "" {
		return ""
	}
	return strings.Trim(slugger.ReplaceAllString(strings.ToLower(s), "-"), "-")
}

func (p *Page) Slug() string {
	return slug(p.Name)
}

func (p *Page) PrevSlug() string {
	return slug(p.prevName)
}

func (p *Page) AfterFind() error {
	// handle renames
	p.prevPath = p.Path
	p.prevName = p.Name
	return nil
}

func (p *Page) AfterSave() error {
	// handle renames
	if p.prevPath != "" && (p.prevPath != p.Path || p.prevName != p.Name) {
		// Remove content file from Hugo but rename data files in case we ever need to restore :)
		// TODO use hugo config to get content dir
		filename := "content" + p.prevPath + p.PrevSlug() + ".json"
		if err := os.Remove(filename); err != nil {
			return err
		}
		// TODO use hugo config to get data dir
		filename = "data" + p.prevPath + p.PrevSlug() + ".json"
		if err := os.Rename(filename, filename+".deleted_at_"+time.Now().Format("20060102150405")); err != nil {
			return err
		}
	}

	// If we have any, fetch the associated Slideshow and its Slides
	// (We need to do this because of the way the relationships are for PageContentColumn > Slideshow > Slides)
	for i, col := range p.ContentColumns {
		if col.SlideshowID > 0 {
			config.DB.First(&col.Slideshow, col.SlideshowID)
			config.DB.Where("slideshow_id = ?", col.SlideshowID).Find(&col.Slideshow.Slides)
			p.ContentColumns[i] = col
		}
	}

	return p.syncWrite()
}

func (p *Page) AfterDelete() error {
	// Remove content file from Hugo but rename data files in case we ever need to restore :)
	// TODO use hugo config to get content dir
	filename := "content" + p.Path + p.Slug() + ".json"
	if err := os.Remove(filename); err != nil {
		return err
	}
	// TODO use hugo config to get data dir
	filename = "data" + p.Path + p.Slug() + ".json"
	if err := os.Rename(filename, filename+".deleted_at_"+time.Now().Format("20060102150405")); err != nil {
		return err
	}
	return nil
}

// Syncs creation and update events for a page with Hugo
func (p *Page) syncWrite() error {

	var path = p.Path + p.Slug()
	output, err := json.MarshalIndent(p, "", "  ")
	if err != nil {
		return err
	}
	// Write the data file for Hugo
	// TODO use hugo config to get data dir
	dataFile := "data" + path + ".json"
	// If required, create data dir first
	if _, err := os.Stat("./data"); os.IsNotExist(err) {
		err = os.MkdirAll("./data", os.ModePerm)
		if err != nil {
			return err
		}
	}
	if _, err := os.Stat("./data" + p.Path); os.IsNotExist(err) {
		err = os.MkdirAll("./data"+p.Path, os.ModePerm)
		if err != nil {
			return err
		}
	}
	err = ioutil.WriteFile(dataFile, output, 0644)
	if err != nil {
		return err
	}
	// Write the content file for Hugo
	menu := make(map[string]map[string]uint)
	if p.MenuWeight > 0 {
		menuWeight := make(map[string]uint)
		menuWeight["weight"] = p.MenuWeight
		if p.Path != "" && p.Path != "/" {
			menuName := slug(p.Path)
			menu[menuName] = menuWeight
		}
	}
	content, err := json.MarshalIndent(
		struct {
			Title       string                     `json:"Title"`
			Description string                     `json:"Description"`
			Date        string                     `json:"Date"`
			Menu        map[string]map[string]uint `json:"Menu"`
		}{
			p.SEO.PageTitle,
			p.SEO.Description,
			p.CreatedAt.Format("2006-01-02T15:04:05Z"),
			menu,
		},
		"",
		"  ",
	)
	// TODO use hugo config to get content dir
	contentFile := "content" + path + ".json"
	// If required, create content dir first
	if _, err := os.Stat("./content"); os.IsNotExist(err) {
		err = os.MkdirAll("./content", os.ModePerm)
		if err != nil {
			return err
		}
	}
	if _, err := os.Stat("./content" + p.Path); os.IsNotExist(err) {
		err = os.MkdirAll("./content"+p.Path, os.ModePerm)
		if err != nil {
			return err
		}
	}
	err = ioutil.WriteFile(contentFile, content, 0644)
	if err != nil {
		return err
	}

	cmdOutput, err := exec.Command("hugo").Output()
	if err != nil {
		return err
	}
	fmt.Printf("\n\nRunning hugo...\n%s\n\n", cmdOutput)

	return nil
}
<commit_msg>typo - content should be saved as .html<commit_after>package models

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"time"

	"github.com/8legd/hugocms/config"
	"github.com/jinzhu/gorm"
)

var slugger = regexp.MustCompile("[^a-z0-9]+")

type Page struct {
	gorm.Model

	Path       string
	prevPath   string
	MenuWeight uint
	Links      []PageLink

	Name     string
	prevName string

	SEO PageMeta

	ContentColumns []PageContentColumn
}

type PageLink struct {
	gorm.Model

	PageID uint

	LinkText string `sql:"size:2000"`
	Link     string
}

type PageMeta struct {
	gorm.Model

	PageID uint

	PageTitle   string
	Description string
}

type PageContentColumn struct {
	gorm.Model

	PageID uint

	ColumnWidth   string
	ColumnHeading string
	ColumnText    string                   `sql:"size:2000"`
	ColumnImage   []PageContentColumnImage // We only use 1 image but a slice allows image removal
	VideoID       uint
	Video         Video
	SlideshowID   uint
	Slideshow     Slideshow
	ColumnLink    string
}

type PageContentColumnImage struct {
	gorm.Model

	PageContentColumnID uint

	Image     ContentImageStorage `sql:"type:varchar(4096)"`
	Alt       string
	Alignment string
}

func slug(s string) string {
	if s == "" {
		return ""
	}
	return strings.Trim(slugger.ReplaceAllString(strings.ToLower(s), "-"), "-")
}

func (p *Page) Slug() string {
	return slug(p.Name)
}

func (p *Page) PrevSlug() string {
	return slug(p.prevName)
}

func (p *Page) AfterFind() error {
	// handle renames
	p.prevPath = p.Path
	p.prevName = p.Name
	return nil
}

func (p *Page) AfterSave() error {
	// handle renames
	if p.prevPath != "" && (p.prevPath != p.Path || p.prevName != p.Name) {
		// Remove content file from Hugo but rename data files in case we ever need to restore :)
		// TODO use hugo config to get content dir
		filename := "content" + p.prevPath + p.PrevSlug() + ".html"
		if err := os.Remove(filename); err != nil {
			return err
		}
		// TODO use hugo config to get data dir
		filename = "data" + p.prevPath + p.PrevSlug() + ".json"
		if err := os.Rename(filename, filename+".deleted_at_"+time.Now().Format("20060102150405")); err != nil {
			return err
		}
	}

	// If we have any, fetch the associated Slideshow and its Slides
	// (We need to do this because of the way the relationships are for PageContentColumn > Slideshow > Slides)
	for i, col := range p.ContentColumns {
		if col.SlideshowID > 0 {
			config.DB.First(&col.Slideshow, col.SlideshowID)
			config.DB.Where("slideshow_id = ?", col.SlideshowID).Find(&col.Slideshow.Slides)
			p.ContentColumns[i] = col
		}
	}

	return p.syncWrite()
}

func (p *Page) AfterDelete() error {
	// Remove content file from Hugo but rename data files in case we ever need to restore :)
	// TODO use hugo config to get content dir
	filename := "content" + p.Path + p.Slug() + ".json"
	if err := os.Remove(filename); err != nil {
		return err
	}
	// TODO use hugo config to get data dir
	filename = "data" + p.Path + p.Slug() + ".json"
	if err := os.Rename(filename, filename+".deleted_at_"+time.Now().Format("20060102150405")); err != nil {
		return err
	}
	return nil
}

// Syncs creation and update events for a page with Hugo
func (p *Page) syncWrite() error {

	var path = p.Path + p.Slug()
	output, err := json.MarshalIndent(p, "", "  ")
	if err != nil {
		return err
	}
	// Write the data file for Hugo
	// TODO use hugo config to get data dir
	dataFile := "data" + path + ".json"
	// If required, create data dir first
	if _, err := os.Stat("./data"); os.IsNotExist(err) {
		err = os.MkdirAll("./data", os.ModePerm)
		if err != nil {
			return err
		}
	}
	if _, err := os.Stat("./data" + p.Path); os.IsNotExist(err) {
		err = os.MkdirAll("./data"+p.Path, os.ModePerm)
		if err != nil {
			return err
		}
	}
	err = ioutil.WriteFile(dataFile, output, 0644)
	if err != nil {
		return err
	}
	// Write the content file for Hugo
	menu := make(map[string]map[string]uint)
	if p.MenuWeight > 0 {
		menuWeight := make(map[string]uint)
		menuWeight["weight"] = p.MenuWeight
		if p.Path != "" && p.Path != "/" {
			menuName := slug(p.Path)
			menu[menuName] = menuWeight
		}
	}
	content, err := json.MarshalIndent(
		struct {
			Title       string                     `json:"Title"`
			Description string                     `json:"Description"`
			Date        string                     `json:"Date"`
			Menu        map[string]map[string]uint `json:"Menu"`
		}{
			p.SEO.PageTitle,
			p.SEO.Description,
			p.CreatedAt.Format("2006-01-02T15:04:05Z"),
			menu,
		},
		"",
		"  ",
	)
	// TODO use hugo config to get content dir
	contentFile := "content" + path + ".html"
	// If required, create content dir first
	if _, err := os.Stat("./content"); os.IsNotExist(err) {
		err = os.MkdirAll("./content", os.ModePerm)
		if err != nil {
			return err
		}
	}
	if _, err := os.Stat("./content" + p.Path); os.IsNotExist(err) {
		err = os.MkdirAll("./content"+p.Path, os.ModePerm)
		if err != nil {
			return err
		}
	}
	err = ioutil.WriteFile(contentFile, content, 0644)
	if err != nil {
		return err
	}

	cmdOutput, err := exec.Command("hugo").Output()
	if err != nil {
		return err
	}
	fmt.Printf("\n\nRunning hugo...\n%s\n\n", cmdOutput)

	return nil
}
<|endoftext|>


<commit_before>package query

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/kshvmdn/fsql/tokenizer"
	"github.com/kshvmdn/fsql/transform"
)

// ConditionNode represents a single node of a query's WHERE clause tree.
type ConditionNode struct {
	Type      *tokenizer.TokenType
	Left      *ConditionNode
	Right     *ConditionNode
	Condition *Condition
}

func (root *ConditionNode) String() string {
	if root == nil {
		return "<nil>"
	}

	return fmt.Sprintf("{%v (%v %v) %v}", root.Type, root.Left, root.Right,
		root.Condition)
}

// evaluateTree runs pre-order traversal on the ConditionNode tree rooted at
// root and evaluates each conditional along the path with the provided compare
// method.
func (root *ConditionNode) evaluateTree(path string, file os.FileInfo) bool {
	if root == nil {
		return true
	}

	if root.Condition != nil {
		if root.Condition.IsSubquery {
			// Unevaluated subquery.
			// TODO: Handle this case.
			return false
		}

		if !root.Condition.Parsed {
			if err := root.Condition.applyModifiers(); err != nil {
				log.Fatal(err.Error())
			}
		}

		return root.Condition.evaluate(path, file)
	}

	if *root.Type == tokenizer.And {
		return root.Left.evaluateTree(path, file) &&
			root.Right.evaluateTree(path, file)
	}

	if *root.Type == tokenizer.Or {
		if root.Left.evaluateTree(path, file) {
			return true
		}
		return root.Right.evaluateTree(path, file)
	}

	return false
}

// Condition represents a WHERE condition.
type Condition struct {
	Attribute          string
	AttributeModifiers []Modifier
	Parsed             bool

	Operator tokenizer.TokenType
	Value    interface{}
	Negate   bool

	Subquery   *Query
	IsSubquery bool
}

// ApplyModifiers applies each modifier to the value of this Condition.
func (c *Condition) applyModifiers() error {
	value := c.Value
	for _, m := range c.AttributeModifiers {
		var err error
		value, err = transform.Parse(&transform.ParseParams{
			Attribute: c.Attribute,
			Value:     c.Value,
			Name:      m.Name,
			Args:      m.Arguments,
		})
		if err != nil {
			return err
		}
	}

	c.Value = value
	c.Parsed = true
	return nil
}

// evaluate runs the respective evaluate function for this Condition.
func (c *Condition) evaluate(path string, file os.FileInfo) bool {
	var retval bool

	switch c.Attribute {
	case "name":
		retval = c.evaluateName(path, file)
	case "size":
		retval = c.evaluateSize(path, file)
	case "time":
		retval = c.evaluateTime(path, file)
	case "mode":
		retval = c.evaluateMode(path, file)
	}

	if c.Negate {
		return !retval
	}

	return retval
}

// evaluateName evaluates a Condition with attribute `name`.
func (c *Condition) evaluateName(path string, file os.FileInfo) bool {
	switch c.Value.(type) {
	case string:
		return cmpAlpha(c.Operator, file.Name(), c.Value.(string))

	case []string:
		return cmpAlpha(c.Operator, file.Name(), c.Value.([]string))

	case map[interface{}]bool:
		return cmpAlpha(c.Operator, file.Name(), c.Value.(map[interface{}]bool))
	}

	return false
}

// evaluateSize evaluates a Condition with attribute `size`.
func (c *Condition) evaluateSize(path string, file os.FileInfo) bool {
	switch c.Value.(type) {
	case float64:
		return cmpNumeric(c.Operator, file.Size(), int64(c.Value.(float64)))

	case string:
		size, err := strconv.ParseFloat(c.Value.(string), 10)
		if err != nil {
			log.Fatalln(err)
		}

		return cmpNumeric(c.Operator, file.Size(), int64(size))

	case map[interface{}]bool:
		return cmpNumeric(c.Operator, file.Size(), c.Value.(map[interface{}]bool))
	}

	return false
}

// evaluateTime evaluates a Condition with attribute `time`.
func (c *Condition) evaluateTime(path string, file os.FileInfo) bool {
	switch c.Value.(type) {
	case string:
		t, err := time.Parse("Jan 02 2006 15 04", c.Value.(string))
		if err != nil {
			log.Fatalln(err)
		}
		return cmpTime(c.Operator, file.ModTime(), t)

	case time.Time:
		return cmpTime(c.Operator, file.ModTime(), c.Value.(time.Time))

	case map[interface{}]bool:
		return cmpTime(c.Operator, file.ModTime(), c.Value.(map[interface{}]bool))
	}

	return false
}

// evaluateMode evaluates a Condition with attribute `mode`.
func (c *Condition) evaluateMode(path string, file os.FileInfo) bool {
	return cmpMode(c.Operator, file, c.Value)
}
<commit_msg>Fix bug with chained functions in WHERE clause<commit_after>package query

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/kshvmdn/fsql/tokenizer"
	"github.com/kshvmdn/fsql/transform"
)

// ConditionNode represents a single node of a query's WHERE clause tree.
type ConditionNode struct {
	Type      *tokenizer.TokenType
	Left      *ConditionNode
	Right     *ConditionNode
	Condition *Condition
}

func (root *ConditionNode) String() string {
	if root == nil {
		return "<nil>"
	}

	return fmt.Sprintf("{%v (%v %v) %v}", root.Type, root.Left, root.Right,
		root.Condition)
}

// evaluateTree runs pre-order traversal on the ConditionNode tree rooted at
// root and evaluates each conditional along the path with the provided compare
// method.
func (root *ConditionNode) evaluateTree(path string, info os.FileInfo) bool {
	if root == nil {
		return true
	}

	if root.Condition != nil {
		if root.Condition.IsSubquery {
			// Unevaluated subquery.
			// TODO: Handle this case.
			return false
		}

		if !root.Condition.Parsed {
			if err := root.Condition.applyModifiers(); err != nil {
				log.Fatal(err.Error())
			}
		}

		return root.Condition.evaluate(path, info)
	}

	if *root.Type == tokenizer.And {
		return root.Left.evaluateTree(path, info) &&
			root.Right.evaluateTree(path, info)
	}

	if *root.Type == tokenizer.Or {
		if root.Left.evaluateTree(path, info) {
			return true
		}
		return root.Right.evaluateTree(path, info)
	}

	return false
}

// Condition represents a WHERE condition.
type Condition struct {
	Attribute          string
	AttributeModifiers []Modifier
	Parsed             bool

	Operator tokenizer.TokenType
	Value    interface{}
	Negate   bool

	Subquery   *Query
	IsSubquery bool
}

// ApplyModifiers applies each modifier to the value of this Condition.
func (c *Condition) applyModifiers() error {
	value := c.Value

	for _, m := range c.AttributeModifiers {
		var err error
		value, err = transform.Parse(&transform.ParseParams{
			Attribute: c.Attribute,
			Value:     value,
			Name:      m.Name,
			Args:      m.Arguments,
		})
		if err != nil {
			return err
		}
	}

	c.Value = value
	c.Parsed = true
	return nil
}

// evaluate runs the respective evaluate function for this Condition.
func (c *Condition) evaluate(path string, file os.FileInfo) bool {
	var retval bool

	switch c.Attribute {
	case "name":
		retval = c.evaluateName(path, file)
	case "size":
		retval = c.evaluateSize(path, file)
	case "time":
		retval = c.evaluateTime(path, file)
	case "mode":
		retval = c.evaluateMode(path, file)
	}

	if c.Negate {
		return !retval
	}

	return retval
}

// evaluateName evaluates a Condition with attribute `name`.
func (c *Condition) evaluateName(path string, file os.FileInfo) bool {
	switch c.Value.(type) {
	case string:
		return cmpAlpha(c.Operator, file.Name(), c.Value.(string))

	case []string:
		return cmpAlpha(c.Operator, file.Name(), c.Value.([]string))

	case map[interface{}]bool:
		return cmpAlpha(c.Operator, file.Name(), c.Value.(map[interface{}]bool))
	}

	return false
}

// evaluateSize evaluates a Condition with attribute `size`.
func (c *Condition) evaluateSize(path string, file os.FileInfo) bool {
	switch c.Value.(type) {
	case float64:
		return cmpNumeric(c.Operator, file.Size(), int64(c.Value.(float64)))

	case string:
		size, err := strconv.ParseFloat(c.Value.(string), 10)
		if err != nil {
			log.Fatalln(err)
		}

		return cmpNumeric(c.Operator, file.Size(), int64(size))

	case map[interface{}]bool:
		return cmpNumeric(c.Operator, file.Size(), c.Value.(map[interface{}]bool))
	}

	return false
}

// evaluateTime evaluates a Condition with attribute `time`.
func (c *Condition) evaluateTime(path string, file os.FileInfo) bool {
	switch c.Value.(type) {
	case string:
		t, err := time.Parse("Jan 02 2006 15 04", c.Value.(string))
		if err != nil {
			log.Fatalln(err)
		}
		return cmpTime(c.Operator, file.ModTime(), t)

	case time.Time:
		return cmpTime(c.Operator, file.ModTime(), c.Value.(time.Time))

	case map[interface{}]bool:
		return cmpTime(c.Operator, file.ModTime(), c.Value.(map[interface{}]bool))
	}

	return false
}

// evaluateMode evaluates a Condition with attribute `mode`.
func (c *Condition) evaluateMode(path string, file os.FileInfo) bool {
	return cmpMode(c.Operator, file, c.Value)
}
<|endoftext|>


<commit_before>package queues
/*
 *  Filename:    priority.go
 *  Package:     queues
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     Wed Jul  6 22:18:57 PDT 2011
 *  Description:
 */
import (
    "sort"
    "fmt"
    "container/heap"
    "container/vector"
)

type PrioritizedTask interface {
    Task
    Key() float64
    SetKey(float64)
}
type PTask struct {
    F func(int64)
    P float64
}
func (pt *PTask) Type() string {
    return "PTask"
}
func (pt *PTask) SetFunc(f func(int64)) {
    pt.F = f
}
func (pt *PTask) Func() func(int64) {
    return pt.F
}
func (pt *PTask) Key() float64 {
    return pt.P
}
func (pt *PTask) SetKey(k float64) {
    pt.P = k
}

type pQueue struct {
    elements []RegisteredTask
}
func newPQueue() *pQueue {
    var h = new(pQueue)
    h.elements = make([]RegisteredTask, 0, 5)
    return h
}
func (h *pQueue) GetPTask(i int) PrioritizedTask {
    if n := len(h.elements) ; i < 0 || i >= n {
        panic("badindex")
    }
    return h.elements[i].Task().(PrioritizedTask)
}
func (h *pQueue) Len() int {
    return len(h.elements)
}
func (h *pQueue) Less(i, j int) bool {
    return h.GetPTask(i).Key() < h.GetPTask(j).Key()
}
func (h *pQueue) Swap(i, j int) {
    if n := len(h.elements) ; i < 0 || i >=n || j < 0 || j >= n {
        panic("badindex")
    }
    var tmp = h.elements[i]
    h.elements[i] = h.elements[j]
    h.elements[j] = tmp
}
func (h *pQueue) Push(x interface{}) {
    switch x.(RegisteredTask).Task().(type) {
    case PrioritizedTask:
        h.elements = append(h.elements, x.(RegisteredTask))
    default:
        panic("badtype")
    }
}
func (h *pQueue) Pop() interface{} {
    if len(h.elements) <= 0 {
        panic("empty")
    }
    var head = h.elements[0]
    h.elements = h.elements[1:]
    return head
}
func (h *pQueue) FindId(id int64) (int, RegisteredTask) {
    for i, elm := range h.elements {
        if elm.Id() == id {
            return i, elm
        }
    }
    return -1, nil
}

type PriorityQueue struct {
    h  *pQueue
}

func NewPriorityQueue() *PriorityQueue {
    var pq = new(PriorityQueue)
    pq.h = newPQueue()
    // No need to call heap.Init(pq.h) on an empty heap.
    return pq
}

func (pq *PriorityQueue) Len() int {
    return pq.h.Len()
}
func (pq *PriorityQueue) Dequeue() RegisteredTask {
    if pq.Len() <= 0 {
        panic("empty")
    }
    return heap.Pop(pq.h).(RegisteredTask)
}
func (pq *PriorityQueue) Enqueue(task RegisteredTask) {
    switch task.Task().(type) {
    case PrioritizedTask:
        heap.Push(pq.h, task)
    default:
        panic(fmt.Sprintf("nokey %s", task.Task().Type()))
    }
}
func (pq *PriorityQueue) SetKey(id int64, k float64) {
    var i, task = pq.h.FindId(id)
    if i < 0 {
        return
    }
    heap.Remove(pq.h, i)
    task.Task().(PrioritizedTask).SetKey(k)
    heap.Push(pq.h, task)
}

//  A priority queue based on the "container/vector" package.
//  Ideally, an array-based priority queue implementation should have
//  fast dequeues and slow enqueues. I fear the vector.Vector class
//  gives slow equeues and slow dequeues.
type VectorPriorityQueue struct {
    v *vector.Vector
}

func NewVectorPriorityQueue() *VectorPriorityQueue {
    var vpq = new(VectorPriorityQueue)
    vpq.v = new(vector.Vector)
    return vpq
}

func (vpq *VectorPriorityQueue) Len() int {
    return vpq.v.Len()
}
type etypeStopIter struct {
}
func (e etypeStopIter) String() string {
    return "STOPITER"
}
func (vpq *VectorPriorityQueue) Enqueue(task RegisteredTask) {
    switch task.Task().(type) {
    case PrioritizedTask:
        break
    default:
        panic(fmt.Sprintf("nokey %s", task.Task().Type()))
    }
    var i int
    defer func() {
        if r := recover(); r != nil {
            switch r.(type) {
            case etypeStopIter:
                break
            default:
                panic(r)
            }
        }
        vpq.v.Insert(i, task)
    } ()
    vpq.v.Do(func (telm interface{}) {
        if task.Task().(PrioritizedTask).Key() > telm.(RegisteredTask).Task().(PrioritizedTask).Key() {
            i++
        } else {
            panic(etypeStopIter{})
        }
    })
}
func (vpq *VectorPriorityQueue) Dequeue() RegisteredTask {
    var head = vpq.v.At(0).(RegisteredTask)
    vpq.v.Delete(0)
    return head
}
func (vpq *VectorPriorityQueue) SetKey(id int64, k float64) {
    var i int
    defer func() {
        if r := recover(); r != nil {
            switch r.(type) {
            case etypeStopIter:
                var rtask = vpq.v.At(i).(RegisteredTask)
                vpq.v.Delete(i)
                rtask.Task().(PrioritizedTask).SetKey(k)
                vpq.Enqueue(rtask)
            default:
                panic(r)
            }
        }
    } ()
    vpq.v.Do(func (telm interface{}) {
        if telm.(RegisteredTask).Id() != id {
            i++
        } else {
            panic(etypeStopIter{})
        }
    })
}

type ArrayPriorityQueue struct {
    v          []RegisteredTask
    head, tail int
}

func NewArrayPriorityQueue() *ArrayPriorityQueue {
    var apq = new(ArrayPriorityQueue)
    apq.v = make([]RegisteredTask, 10)
    return apq
}

func (apq *ArrayPriorityQueue) Len() int {
    return apq.tail - apq.head
}

func (apq *ArrayPriorityQueue) Enqueue(task RegisteredTask) {
    var key = task.Task().(PrioritizedTask).Key()
    var n = apq.Len()
    var insertoffset = sort.Search(
            n,
            func(i int)bool{
                if n == 0 || i >= n {
                    return true
                }
                return apq.v[apq.head+i].Task().(PrioritizedTask).Key() >= key } )
    if apq.tail != len(apq.v) {
        for j := apq.tail ; j > apq.head+insertoffset ; j-- {
            apq.v[j] = apq.v[j-1]
        }
        apq.v[apq.head+insertoffset] = task
        apq.tail++
        return
    }
    var newv = apq.v
    if apq.head <= len(apq.v)/2 {
        newv = make([]RegisteredTask, 2* len(apq.v))
    }
    copy(newv, apq.v[apq.head:apq.head+insertoffset])
    newv[insertoffset] = task
    copy(newv[insertoffset+1:], apq.v[apq.head+insertoffset:apq.tail])
    for i := apq.head ; i < apq.tail ; i++ {
        apq.v[i] = nil
    }
    apq.v = newv
    apq.head = 0
    apq.tail = n+1
}

func (apq *ArrayPriorityQueue) Dequeue() RegisteredTask {
    if apq.Len() == 0 {
        panic("empty")
    }
    var task = apq.v[apq.head]
    apq.v[apq.head] = nil
    apq.head++
    return task
}

func (apq *ArrayPriorityQueue) SetKey(id int64, k float64) {
}
<commit_msg>Remove paranoid check.<commit_after>package queues
/*
 *  Filename:    priority.go
 *  Package:     queues
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     Wed Jul  6 22:18:57 PDT 2011
 *  Description:
 */
import (
    "sort"
    "fmt"
    "container/heap"
    "container/vector"
)

type PrioritizedTask interface {
    Task
    Key() float64
    SetKey(float64)
}
type PTask struct {
    F func(int64)
    P float64
}
func (pt *PTask) Type() string {
    return "PTask"
}
func (pt *PTask) SetFunc(f func(int64)) {
    pt.F = f
}
func (pt *PTask) Func() func(int64) {
    return pt.F
}
func (pt *PTask) Key() float64 {
    return pt.P
}
func (pt *PTask) SetKey(k float64) {
    pt.P = k
}

type pQueue struct {
    elements []RegisteredTask
}
func newPQueue() *pQueue {
    var h = new(pQueue)
    h.elements = make([]RegisteredTask, 0, 5)
    return h
}
func (h *pQueue) GetPTask(i int) PrioritizedTask {
    if n := len(h.elements) ; i < 0 || i >= n {
        panic("badindex")
    }
    return h.elements[i].Task().(PrioritizedTask)
}
func (h *pQueue) Len() int {
    return len(h.elements)
}
func (h *pQueue) Less(i, j int) bool {
    return h.GetPTask(i).Key() < h.GetPTask(j).Key()
}
func (h *pQueue) Swap(i, j int) {
    if n := len(h.elements) ; i < 0 || i >=n || j < 0 || j >= n {
        panic("badindex")
    }
    var tmp = h.elements[i]
    h.elements[i] = h.elements[j]
    h.elements[j] = tmp
}
func (h *pQueue) Push(x interface{}) {
    switch x.(RegisteredTask).Task().(type) {
    case PrioritizedTask:
        h.elements = append(h.elements, x.(RegisteredTask))
    default:
        panic("badtype")
    }
}
func (h *pQueue) Pop() interface{} {
    if len(h.elements) <= 0 {
        panic("empty")
    }
    var head = h.elements[0]
    h.elements = h.elements[1:]
    return head
}
func (h *pQueue) FindId(id int64) (int, RegisteredTask) {
    for i, elm := range h.elements {
        if elm.Id() == id {
            return i, elm
        }
    }
    return -1, nil
}

type PriorityQueue struct {
    h  *pQueue
}

func NewPriorityQueue() *PriorityQueue {
    var pq = new(PriorityQueue)
    pq.h = newPQueue()
    // No need to call heap.Init(pq.h) on an empty heap.
    return pq
}

func (pq *PriorityQueue) Len() int {
    return pq.h.Len()
}
func (pq *PriorityQueue) Dequeue() RegisteredTask {
    if pq.Len() <= 0 {
        panic("empty")
    }
    return heap.Pop(pq.h).(RegisteredTask)
}
func (pq *PriorityQueue) Enqueue(task RegisteredTask) {
    switch task.Task().(type) {
    case PrioritizedTask:
        heap.Push(pq.h, task)
    default:
        panic(fmt.Sprintf("nokey %s", task.Task().Type()))
    }
}
func (pq *PriorityQueue) SetKey(id int64, k float64) {
    var i, task = pq.h.FindId(id)
    if i < 0 {
        return
    }
    heap.Remove(pq.h, i)
    task.Task().(PrioritizedTask).SetKey(k)
    heap.Push(pq.h, task)
}

//  A priority queue based on the "container/vector" package.
//  Ideally, an array-based priority queue implementation should have
//  fast dequeues and slow enqueues. I fear the vector.Vector class
//  gives slow equeues and slow dequeues.
type VectorPriorityQueue struct {
    v *vector.Vector
}

func NewVectorPriorityQueue() *VectorPriorityQueue {
    var vpq = new(VectorPriorityQueue)
    vpq.v = new(vector.Vector)
    return vpq
}

func (vpq *VectorPriorityQueue) Len() int {
    return vpq.v.Len()
}
type etypeStopIter struct {
}
func (e etypeStopIter) String() string {
    return "STOPITER"
}
func (vpq *VectorPriorityQueue) Enqueue(task RegisteredTask) {
    switch task.Task().(type) {
    case PrioritizedTask:
        break
    default:
        panic(fmt.Sprintf("nokey %s", task.Task().Type()))
    }
    var i int
    defer func() {
        if r := recover(); r != nil {
            switch r.(type) {
            case etypeStopIter:
                break
            default:
                panic(r)
            }
        }
        vpq.v.Insert(i, task)
    } ()
    vpq.v.Do(func (telm interface{}) {
        if task.Task().(PrioritizedTask).Key() > telm.(RegisteredTask).Task().(PrioritizedTask).Key() {
            i++
        } else {
            panic(etypeStopIter{})
        }
    })
}
func (vpq *VectorPriorityQueue) Dequeue() RegisteredTask {
    var head = vpq.v.At(0).(RegisteredTask)
    vpq.v.Delete(0)
    return head
}
func (vpq *VectorPriorityQueue) SetKey(id int64, k float64) {
    var i int
    defer func() {
        if r := recover(); r != nil {
            switch r.(type) {
            case etypeStopIter:
                var rtask = vpq.v.At(i).(RegisteredTask)
                vpq.v.Delete(i)
                rtask.Task().(PrioritizedTask).SetKey(k)
                vpq.Enqueue(rtask)
            default:
                panic(r)
            }
        }
    } ()
    vpq.v.Do(func (telm interface{}) {
        if telm.(RegisteredTask).Id() != id {
            i++
        } else {
            panic(etypeStopIter{})
        }
    })
}

type ArrayPriorityQueue struct {
    v          []RegisteredTask
    head, tail int
}

func NewArrayPriorityQueue() *ArrayPriorityQueue {
    var apq = new(ArrayPriorityQueue)
    apq.v = make([]RegisteredTask, 10)
    return apq
}

func (apq *ArrayPriorityQueue) Len() int {
    return apq.tail - apq.head
}

func (apq *ArrayPriorityQueue) Enqueue(task RegisteredTask) {
    var key = task.Task().(PrioritizedTask).Key()
    var n = apq.Len()
    var insertoffset = sort.Search(
            n,
            func(i int)bool{
                return apq.v[apq.head+i].Task().(PrioritizedTask).Key() >= key } )
    if apq.tail != len(apq.v) {
        for j := apq.tail ; j > apq.head+insertoffset ; j-- {
            apq.v[j] = apq.v[j-1]
        }
        apq.v[apq.head+insertoffset] = task
        apq.tail++
        return
    }
    var newv = apq.v
    if apq.head <= len(apq.v)/2 {
        newv = make([]RegisteredTask, 2* len(apq.v))
    }
    copy(newv, apq.v[apq.head:apq.head+insertoffset])
    newv[insertoffset] = task
    copy(newv[insertoffset+1:], apq.v[apq.head+insertoffset:apq.tail])
    for i := apq.head ; i < apq.tail ; i++ {
        apq.v[i] = nil
    }
    apq.v = newv
    apq.head = 0
    apq.tail = n+1
}

func (apq *ArrayPriorityQueue) Dequeue() RegisteredTask {
    if apq.Len() == 0 {
        panic("empty")
    }
    var task = apq.v[apq.head]
    apq.v[apq.head] = nil
    apq.head++
    return task
}

func (apq *ArrayPriorityQueue) SetKey(id int64, k float64) {
}
<|endoftext|>


<commit_before>// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package configcron

import (
	"context"

	"google.golang.org/protobuf/proto"

	"go.chromium.org/luci/common/data/rand/mathrand"
	"go.chromium.org/luci/common/data/stringset"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/sync/parallel"
	"go.chromium.org/luci/server/tq"

	"go.chromium.org/luci/cv/internal/common"
	"go.chromium.org/luci/cv/internal/config"
	"go.chromium.org/luci/cv/internal/prjmanager"
)

// SubmitRefreshTasks submits tasks that update config for LUCI projects
// or disable projects that do not have CV config in LUCI Config.
//
// It's expected to be called by a cron.
//
// If isDev is true, only some projects will be considered,
// regardless of which projects are registered.
// TODO(crbug/1158505): switch to -dev configs and remove isDev parameter.
func SubmitRefreshTasks(ctx context.Context, isDev bool) error {
	projects, err := config.ProjectsWithConfig(ctx)
	if err != nil {
		return err
	}
	if isDev {
		projects = []string{"infra", "chromium", "chromium-m86", "v8"}
	}
	tasks := make([]*tq.Task, len(projects))
	for i, p := range projects {
		tasks[i] = &tq.Task{
			Title: "update/" + p,
			Payload: &RefreshProjectConfigTask{
				Project: p,
			},
		}
	}

	curEnabledProjects, err := config.GetAllProjectIDs(ctx, true)
	if err != nil {
		return err
	}
	projectsInLUCIConfig := stringset.NewFromSlice(projects...)
	for _, p := range curEnabledProjects {
		if !projectsInLUCIConfig.Has(p) {
			tasks = append(tasks, &tq.Task{
				Title: "disable/" + p,
				Payload: &RefreshProjectConfigTask{
					Project: p,
					Disable: true,
				},
			})
		}
	}

	err = parallel.WorkPool(32, func(workCh chan<- func() error) {
		for _, task := range tasks {
			task := task
			workCh <- func() (err error) {
				if err = tq.AddTask(ctx, task); err != nil {
					logging.Errorf(ctx, "Failed to submit task for %q: %s", task.Title, err)
				}
				return
			}
		}
	})

	if err != nil {
		return err.(errors.MultiError).First()
	}
	return nil
}

func init() {
	tq.RegisterTaskClass(tq.TaskClass{
		ID:        "refresh-project-config",
		Prototype: &RefreshProjectConfigTask{},
		Queue:     "refresh-project-config",
		Quiet:     true,
		Handler: func(ctx context.Context, payload proto.Message) error {
			task := payload.(*RefreshProjectConfigTask)
			if err := refreshProject(ctx, task.GetProject(), task.GetDisable()); err != nil {
				// Never retry tasks because the refresh task is submitted every minute
				// by AppEngine Cron.
				err = tq.Fatal.Apply(err)
				return common.TQifyError(ctx, err)
			}
			return nil
		},
	})
}

func refreshProject(ctx context.Context, project string, disable bool) error {
	action, actionFn := "update", config.UpdateProject
	if disable {
		action, actionFn = "disable", config.DisableProject
	}
	err := actionFn(ctx, project, func(ctx context.Context) error {
		return prjmanager.UpdateConfig(ctx, project)
	})
	if err != nil {
		return errors.Annotate(err, "failed to %s project %q", action, project).Err()
	}
	// TODO(crbug/1158500): replace with time-based decision s.t. we can guarantee
	// that PM will be invoked and hence can do alerting if it's not the case.
	if mathrand.Float32(ctx) >= 0.9 { // ~10% chance.
		if err := prjmanager.Poke(ctx, project); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>cv: best-effort poke PM every 10 minutes.<commit_after>// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package configcron

import (
	"context"
	"time"

	"google.golang.org/protobuf/proto"

	"go.chromium.org/luci/common/clock"
	"go.chromium.org/luci/common/data/stringset"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/sync/parallel"
	"go.chromium.org/luci/server/tq"

	"go.chromium.org/luci/cv/internal/common"
	"go.chromium.org/luci/cv/internal/config"
	"go.chromium.org/luci/cv/internal/prjmanager"
)

// SubmitRefreshTasks submits tasks that update config for LUCI projects
// or disable projects that do not have CV config in LUCI Config.
//
// It's expected to be called by a cron.
//
// If isDev is true, only some projects will be considered,
// regardless of which projects are registered.
// TODO(crbug/1158505): switch to -dev configs and remove isDev parameter.
func SubmitRefreshTasks(ctx context.Context, isDev bool) error {
	projects, err := config.ProjectsWithConfig(ctx)
	if err != nil {
		return err
	}
	if isDev {
		projects = []string{"infra", "chromium", "chromium-m86", "v8"}
	}
	tasks := make([]*tq.Task, len(projects))
	for i, p := range projects {
		tasks[i] = &tq.Task{
			Title: "update/" + p,
			Payload: &RefreshProjectConfigTask{
				Project: p,
			},
		}
	}

	curEnabledProjects, err := config.GetAllProjectIDs(ctx, true)
	if err != nil {
		return err
	}
	projectsInLUCIConfig := stringset.NewFromSlice(projects...)
	for _, p := range curEnabledProjects {
		if !projectsInLUCIConfig.Has(p) {
			tasks = append(tasks, &tq.Task{
				Title: "disable/" + p,
				Payload: &RefreshProjectConfigTask{
					Project: p,
					Disable: true,
				},
			})
		}
	}

	err = parallel.WorkPool(32, func(workCh chan<- func() error) {
		for _, task := range tasks {
			task := task
			workCh <- func() (err error) {
				if err = tq.AddTask(ctx, task); err != nil {
					logging.Errorf(ctx, "Failed to submit task for %q: %s", task.Title, err)
				}
				return
			}
		}
	})

	if err != nil {
		return err.(errors.MultiError).First()
	}
	return nil
}

func init() {
	tq.RegisterTaskClass(tq.TaskClass{
		ID:        "refresh-project-config",
		Prototype: &RefreshProjectConfigTask{},
		Queue:     "refresh-project-config",
		Quiet:     true,
		Handler: func(ctx context.Context, payload proto.Message) error {
			task := payload.(*RefreshProjectConfigTask)
			if err := refreshProject(ctx, task.GetProject(), task.GetDisable()); err != nil {
				// Never retry tasks because the refresh task is submitted every minute
				// by AppEngine Cron.
				err = tq.Fatal.Apply(err)
				return common.TQifyError(ctx, err)
			}
			return nil
		},
	})
}

func refreshProject(ctx context.Context, project string, disable bool) error {
	action, actionFn := "update", config.UpdateProject
	if disable {
		action, actionFn = "disable", config.DisableProject
	}
	err := actionFn(ctx, project, func(ctx context.Context) error {
		return prjmanager.UpdateConfig(ctx, project)
	})
	if err != nil {
		return errors.Annotate(err, "failed to %s project %q", action, project).Err()
	}
	if !disable {
		return maybePokePM(ctx, project)
	}
	return nil
}

const pokePMInterval = 10 * time.Minute

func maybePokePM(ctx context.Context, project string) error {
	now := clock.Now(ctx).UTC()
	offset := common.ProjectOffset("cron-poke", pokePMInterval, project)
	nextPokeETA := now.Truncate(pokePMInterval).Add(offset)
	if nextPokeETA.Before(now) {
		nextPokeETA = nextPokeETA.Add(pokePMInterval)
	}

	// Cron runs every minute on average and triggers RefreshProjectConfigTask,
	// which may be delayed, so send iff it's less than 1.5 minutes before next
	// poke. This will sometimes result in 2 pokes sent instead of 1, but pokes
	// are less likely to not be sent at all.
	if nextPokeETA.Sub(now) < 90*time.Second {
		return prjmanager.Poke(ctx, project)
	}
	return nil
}
<|endoftext|>


<commit_before>package services_bbs_test

import (
	"os"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pivotal-golang/lager/lagertest"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"

	. "github.com/cloudfoundry-incubator/runtime-schema/bbs/services_bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/bbs/shared"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	"github.com/cloudfoundry/gunk/timeprovider/faketimeprovider"
	"github.com/cloudfoundry/storeadapter"
)

var _ = Describe("Cell Service Registry", func() {
	const interval = time.Second
	var (
		timeProvider *faketimeprovider.FakeTimeProvider

		bbs                *ServicesBBS
		heartbeat1         ifrit.Process
		heartbeat2         ifrit.Process
		firstCellPresence  models.CellPresence
		secondCellPresence models.CellPresence
	)

	BeforeEach(func() {
		timeProvider = faketimeprovider.New(time.Now())
		bbs = New(etcdClient, timeProvider, lagertest.NewTestLogger("test"))

		firstCellPresence = models.NewCellPresence("first-rep", "lucid64", "1.2.3.4", "the-zone")
		secondCellPresence = models.NewCellPresence("second-rep", ".Net", "4.5.6.7", "the-zone")

		heartbeat1 = ifrit.Invoke(bbs.NewCellHeartbeat(firstCellPresence, interval))
		heartbeat2 = ifrit.Invoke(bbs.NewCellHeartbeat(secondCellPresence, interval))
	})

	AfterEach(func() {
		heartbeat1.Signal(os.Interrupt)
		heartbeat2.Signal(os.Interrupt)
		Eventually(heartbeat1.Wait()).Should(Receive(BeNil()))
		Eventually(heartbeat2.Wait()).Should(Receive(BeNil()))
	})

	Describe("MaintainCellPresence", func() {
		It("should put /cell/CELL_ID in the store with a TTL", func() {
			node, err := etcdClient.Get("/v1/cell/" + firstCellPresence.CellID)
			Ω(err).ShouldNot(HaveOccurred())
			Ω(node.TTL).ShouldNot(BeZero())

			expectedJSON, err := models.ToJSON(firstCellPresence)
			Ω(err).ShouldNot(HaveOccurred())

			Ω(node.Value).Should(MatchJSON(expectedJSON))
		})
	})

	Describe("CellById", func() {
		Context("when the cell exists", func() {
			It("returns the correct CellPresence", func() {
				cellPresence, err := bbs.CellById(firstCellPresence.CellID)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(cellPresence).Should(Equal(firstCellPresence))
			})
		})

		Context("when the cell does not exist", func() {})
	})

	Describe("Cells", func() {
		Context("when there are available Cells", func() {
			It("should get from /v1/cell/", func() {
				cellPresences, err := bbs.Cells()
				Ω(err).ShouldNot(HaveOccurred())
				Ω(cellPresences).Should(HaveLen(2))
				Ω(cellPresences).Should(ContainElement(firstCellPresence))
				Ω(cellPresences).Should(ContainElement(secondCellPresence))
			})

			Context("when there is unparsable JSON in there...", func() {
				BeforeEach(func() {
					etcdClient.Create(storeadapter.StoreNode{
						Key:   shared.CellSchemaPath("blah"),
						Value: []byte("ß"),
					})
				})

				It("should ignore the unparsable JSON and move on", func() {
					cellPresences, err := bbs.Cells()
					Ω(err).ShouldNot(HaveOccurred())
					Ω(cellPresences).Should(HaveLen(2))
					Ω(cellPresences).Should(ContainElement(firstCellPresence))
					Ω(cellPresences).Should(ContainElement(secondCellPresence))
				})
			})
		})

		Context("when there are none", func() {
			BeforeEach(func() {
				heartbeat1.Signal(os.Interrupt)
				heartbeat2.Signal(os.Interrupt)
				Eventually(heartbeat1.Wait()).Should(Receive(BeNil()))
				Eventually(heartbeat2.Wait()).Should(Receive(BeNil()))
			})

			It("should return empty", func() {
				reps, err := bbs.Cells()
				Ω(err).ShouldNot(HaveOccurred())
				Ω(reps).Should(BeEmpty())
			})
		})
	})

	Describe("WaitForCellEvent", func() {
		Context("when the store is around", func() {
			var receivedEvents <-chan CellEvent

			BeforeEach(func() {
				eventChan := make(chan CellEvent, 1)
				receivedEvents = eventChan

				go func() {
					defer GinkgoRecover()

					event, err := bbs.WaitForCellEvent()
					Ω(err).ShouldNot(HaveOccurred())

					eventChan <- event
				}()

				time.Sleep(100 * time.Millisecond) //give the watcher a chance to connect
			})

			Context("when a cell presence appears", func() {
				cellPresence := models.CellPresence{
					CellID:     "some-cell",
					Stack:      "some-stack",
					RepAddress: "some-rep-address",
					Zone:       "some-zone",
				}

				var (
					process ifrit.Process
				)

				BeforeEach(func() {
					process = ifrit.Invoke(bbs.NewCellHeartbeat(cellPresence, time.Second))
				})

				AfterEach(func() {
					ginkgomon.Interrupt(process)
				})

				It("receives a CellAppeared event", func() {
					Eventually(receivedEvents).Should(Receive(Equal(CellAppearedEvent{
						Presence: cellPresence,
					})))
				})

				Describe("watching again", func() {
					var receivedEvents <-chan CellEvent

					BeforeEach(func() {
						eventChan := make(chan CellEvent, 1)
						receivedEvents = eventChan

						go func() {
							defer GinkgoRecover()

							event, err := bbs.WaitForCellEvent()
							Ω(err).ShouldNot(HaveOccurred())

							eventChan <- event
						}()

						time.Sleep(100 * time.Millisecond) //give the watcher a chance to connect
					})

					Context("when the cell then disappears", func() {
						BeforeEach(func() {
							ginkgomon.Interrupt(process)
						})

						It("receives a CellDisappeared event", func() {
							Eventually(receivedEvents).Should(Receive(Equal(CellDisappearedEvent{
								Presence: cellPresence,
							})))
						})
					})
				})
			})
		})

		Context("when the store is down", func() {
			BeforeEach(func() {
				etcdRunner.Stop()
			})

			It("returns an error", func() {
				_, err := bbs.WaitForCellEvent()
				Ω(err).Should(HaveOccurred())
			})
		})
	})
})
<commit_msg>refactor/fix flaky bbs cell services tests<commit_after>package services_bbs_test

import (
	"os"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pivotal-golang/lager/lagertest"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"

	"github.com/cloudfoundry-incubator/runtime-schema/bbs/bbserrors"
	. "github.com/cloudfoundry-incubator/runtime-schema/bbs/services_bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/bbs/shared"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	"github.com/cloudfoundry/gunk/timeprovider/faketimeprovider"
	"github.com/cloudfoundry/storeadapter"
)

var _ = Describe("Cell Service Registry", func() {
	const interval = time.Second
	var (
		timeProvider *faketimeprovider.FakeTimeProvider

		bbs                *ServicesBBS
		heartbeat1         ifrit.Process
		heartbeat2         ifrit.Process
		firstCellPresence  models.CellPresence
		secondCellPresence models.CellPresence
	)

	BeforeEach(func() {
		timeProvider = faketimeprovider.New(time.Now())
		bbs = New(etcdClient, timeProvider, lagertest.NewTestLogger("test"))

		firstCellPresence = models.NewCellPresence("first-rep", "lucid64", "1.2.3.4", "the-zone")
		secondCellPresence = models.NewCellPresence("second-rep", ".Net", "4.5.6.7", "the-zone")

	})

	AfterEach(func() {
		if heartbeat1 != nil {
			heartbeat1.Signal(os.Interrupt)
			Eventually(heartbeat1.Wait()).Should(Receive(BeNil()))
		}

		if heartbeat2 != nil {
			heartbeat2.Signal(os.Interrupt)
			Eventually(heartbeat2.Wait()).Should(Receive(BeNil()))
		}
	})

	Describe("MaintainCellPresence", func() {
		BeforeEach(func() {
			heartbeat1 = ifrit.Invoke(bbs.NewCellHeartbeat(firstCellPresence, interval))
		})

		It("should put /cell/CELL_ID in the store with a TTL", func() {
			node, err := etcdClient.Get("/v1/cell/" + firstCellPresence.CellID)
			Ω(err).ShouldNot(HaveOccurred())
			Ω(node.TTL).ShouldNot(BeZero())

			expectedJSON, err := models.ToJSON(firstCellPresence)
			Ω(err).ShouldNot(HaveOccurred())

			Ω(node.Value).Should(MatchJSON(expectedJSON))
		})
	})

	Describe("CellById", func() {
		Context("when the cell exists", func() {
			BeforeEach(func() {
				heartbeat1 = ifrit.Invoke(bbs.NewCellHeartbeat(firstCellPresence, interval))
			})

			It("returns the correct CellPresence", func() {
				cellPresence, err := bbs.CellById(firstCellPresence.CellID)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(cellPresence).Should(Equal(firstCellPresence))
			})
		})

		Context("when the cell does not exist", func() {
			It("returns ErrStoreResourceNotFound", func() {
				_, err := bbs.CellById(firstCellPresence.CellID)
				Ω(err).Should(Equal(bbserrors.ErrStoreResourceNotFound))
			})
		})
	})

	Describe("Cells", func() {
		Context("when there are available Cells", func() {
			BeforeEach(func() {
				heartbeat1 = ifrit.Invoke(bbs.NewCellHeartbeat(firstCellPresence, interval))
				heartbeat2 = ifrit.Invoke(bbs.NewCellHeartbeat(secondCellPresence, interval))
			})

			It("should get from /v1/cell/", func() {
				cellPresences, err := bbs.Cells()
				Ω(err).ShouldNot(HaveOccurred())
				Ω(cellPresences).Should(HaveLen(2))
				Ω(cellPresences).Should(ContainElement(firstCellPresence))
				Ω(cellPresences).Should(ContainElement(secondCellPresence))
			})

			Context("when there is unparsable JSON in there...", func() {
				BeforeEach(func() {
					etcdClient.Create(storeadapter.StoreNode{
						Key:   shared.CellSchemaPath("blah"),
						Value: []byte("ß"),
					})
				})

				It("should ignore the unparsable JSON and move on", func() {
					cellPresences, err := bbs.Cells()
					Ω(err).ShouldNot(HaveOccurred())
					Ω(cellPresences).Should(HaveLen(2))
					Ω(cellPresences).Should(ContainElement(firstCellPresence))
					Ω(cellPresences).Should(ContainElement(secondCellPresence))
				})
			})
		})

		Context("when there are none", func() {
			BeforeEach(func() {
				heartbeat1.Signal(os.Interrupt)
				heartbeat2.Signal(os.Interrupt)
				Eventually(heartbeat1.Wait()).Should(Receive(BeNil()))
				Eventually(heartbeat2.Wait()).Should(Receive(BeNil()))
			})

			It("should return empty", func() {
				reps, err := bbs.Cells()
				Ω(err).ShouldNot(HaveOccurred())
				Ω(reps).Should(BeEmpty())
			})
		})
	})

	Describe("WaitForCellEvent", func() {
		Context("when the store is around", func() {
			var receivedEvents <-chan CellEvent

			BeforeEach(func() {
				eventChan := make(chan CellEvent, 1)
				receivedEvents = eventChan

				go func() {
					defer GinkgoRecover()

					event, err := bbs.WaitForCellEvent()
					Ω(err).ShouldNot(HaveOccurred())

					eventChan <- event
				}()

				time.Sleep(100 * time.Millisecond) //give the watcher a chance to connect
			})

			Context("when a cell presence appears", func() {
				BeforeEach(func() {
					heartbeat1 = ifrit.Invoke(bbs.NewCellHeartbeat(firstCellPresence, interval))
				})

				It("receives a CellAppeared event", func() {
					Eventually(receivedEvents).Should(Receive(Equal(CellAppearedEvent{
						Presence: firstCellPresence,
					})))
				})

				Describe("watching again", func() {
					var receivedEvents <-chan CellEvent

					BeforeEach(func() {
						eventChan := make(chan CellEvent, 1)
						receivedEvents = eventChan

						go func() {
							defer GinkgoRecover()

							event, err := bbs.WaitForCellEvent()
							Ω(err).ShouldNot(HaveOccurred())

							eventChan <- event
						}()

						time.Sleep(100 * time.Millisecond) //give the watcher a chance to connect
					})

					Context("when the cell then disappears", func() {
						BeforeEach(func() {
							ginkgomon.Interrupt(heartbeat1)
						})

						It("receives a CellDisappeared event", func() {
							Eventually(receivedEvents).Should(Receive(Equal(CellDisappearedEvent{
								Presence: firstCellPresence,
							})))
						})
					})
				})
			})
		})

		Context("when the store is down", func() {
			BeforeEach(func() {
				etcdRunner.Stop()
			})

			It("returns an error", func() {
				_, err := bbs.WaitForCellEvent()
				Ω(err).Should(HaveOccurred())
			})
		})
	})
})
<|endoftext|>


<commit_before>/*
 *    Copyright (C) 2014-2017 Christian Muehlhaeuser
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as published
 *    by the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    Authors:
 *      Matthias Krauser <matthias@krauser.eu>
 */

package emailserverbee

import (
	"github.com/flashmob/go-guerrilla"
	"github.com/flashmob/go-guerrilla/backends"
	"github.com/flashmob/go-guerrilla/mail"

	//"github.com/flashmob/go-guerrilla/response"

	"github.com/muesli/beehive/bees"
)

// EmailServerBee is a Bee that starts an SMTP server and fires events for incoming
// Emails.
type EmailServerBee struct {
	bees.Bee

	address        string
	allowedHosts   []string
	hostname       string
	maxSize        int64
	timeout        int
	maxClients     int
	startTLSOn     bool
	tlsAlwaysOn    bool
	privateKeyFile string
	publicKeyFile  string

	eventChan chan bees.Event
}

func (mod *EmailServerBee) mailProcessor() func() backends.Decorator {
	return func() backends.Decorator {
		return func(p backends.Processor) backends.Processor {
			return backends.ProcessWith(
				func(e *mail.Envelope, task backends.SelectTask) (backends.Result, error) {
					if task == backends.TaskSaveMail {

						recipients := make([]string, len(e.RcptTo))

						for index, rcpt := range e.RcptTo {
							recipients[index] = rcpt.String()
						}

						/*headers := make([]string, len(e.Header))

						i := 0
						for name, value := range e.Header {
							headers[i] = name + ": " + value
							i += 1
						}*/

						// create events and send it to cin
						ev := bees.Event{
							Bee: mod.Name(),
							Options: []bees.Placeholder{
								{
									Name:  "sender",
									Type:  "string",
									Value: e.MailFrom.String(),
								},
								{
									Name:  "remote_ip",
									Type:  "string",
									Value: e.RemoteIP,
								},
								{
									Name:  "recipients",
									Type:  "[]string",
									Value: recipients,
								},
								{
									Name:  "subject",
									Type:  "string",
									Value: e.Subject,
								},
								{
									Name:  "tls",
									Type:  "boolean",
									Value: e.TLS,
								},
								{
									Name:  "headers",
									Type:  "[]string",
									Value: e.Header,
								},
								{
									Name:  "body",
									Type:  "string",
									Value: e.Data.String(),
								},
							},
						}

						mod.eventChan <- ev
					}

					return p.Process(e, task)
				},
			)
		}
	}
}

// Run executes the Bee's event loop.
func (mod *EmailServerBee) Run(cin chan bees.Event) {
	mod.eventChan = cin

	// see https://github.com/flashmob/go-guerrilla/wiki/Using-as-a-package

	cfg := &guerrilla.AppConfig{
		AllowedHosts: mod.allowedHosts,
		// LogLevel controls the lowest level we log.
		// "info", "debug", "error", "panic". Default "info"
		LogLevel: "info",
	}

	tc := guerrilla.ServerTLSConfig{
		StartTLSOn:     mod.startTLSOn,
		AlwaysOn:       mod.tlsAlwaysOn,
		PrivateKeyFile: mod.privateKeyFile,
		PublicKeyFile:  mod.publicKeyFile,
	}
	sc := guerrilla.ServerConfig{
		IsEnabled:       true,
		Hostname:        mod.hostname,
		ListenInterface: mod.address,
		MaxSize:         mod.maxSize,
		Timeout:         mod.timeout,
		MaxClients:      mod.maxClients,
		TLS:             tc,
	}
	cfg.Servers = append(cfg.Servers, sc)

	bcfg := backends.BackendConfig{
		"save_process":       "HeadersParser|Header|Hasher|Beehive",
		"log_received_mails": true,
	}
	cfg.BackendConfig = bcfg

	d := guerrilla.Daemon{Config: cfg}
	d.AddProcessor("Beehive", mod.mailProcessor())

	err := d.Start()
	if err != nil {
		mod.LogFatal("Error starting SMTP-Server", err)
	}

	select {
	case <-mod.SigChan:
		d.Shutdown()
		return
	}
}

// ReloadOptions parses the config options and initializes the Bee.
func (mod *EmailServerBee) ReloadOptions(options bees.BeeOptions) {
	mod.SetOptions(options)

	options.Bind("address", &mod.address)
	options.Bind("allowedHosts", &mod.allowedHosts)
	options.Bind("startTLSOn", &mod.startTLSOn)
	options.Bind("tlsAlwaysOn", &mod.tlsAlwaysOn)
	options.Bind("privateKeyFile", &mod.privateKeyFile)
	options.Bind("publicKeyFile", &mod.publicKeyFile)
	options.Bind("hostname", &mod.hostname)
	options.Bind("maxSize", &mod.maxSize)
	options.Bind("timeout", &mod.timeout)
	options.Bind("maxClient", &mod.maxClients)
}
<commit_msg>Added missing event name<commit_after>/*
 *    Copyright (C) 2014-2017 Christian Muehlhaeuser
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as published
 *    by the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    Authors:
 *      Matthias Krauser <matthias@krauser.eu>
 */

package emailserverbee

import (
	"github.com/flashmob/go-guerrilla"
	"github.com/flashmob/go-guerrilla/backends"
	"github.com/flashmob/go-guerrilla/mail"

	//"github.com/flashmob/go-guerrilla/response"

	"github.com/muesli/beehive/bees"
)

// EmailServerBee is a Bee that starts an SMTP server and fires events for incoming
// Emails.
type EmailServerBee struct {
	bees.Bee

	address        string
	allowedHosts   []string
	hostname       string
	maxSize        int64
	timeout        int
	maxClients     int
	startTLSOn     bool
	tlsAlwaysOn    bool
	privateKeyFile string
	publicKeyFile  string

	eventChan chan bees.Event
}

func (mod *EmailServerBee) mailProcessor() func() backends.Decorator {
	return func() backends.Decorator {
		return func(p backends.Processor) backends.Processor {
			return backends.ProcessWith(
				func(e *mail.Envelope, task backends.SelectTask) (backends.Result, error) {
					if task == backends.TaskSaveMail {

						recipients := make([]string, len(e.RcptTo))

						for index, rcpt := range e.RcptTo {
							recipients[index] = rcpt.String()
						}

						/*headers := make([]string, len(e.Header))

						i := 0
						for name, value := range e.Header {
							headers[i] = name + ": " + value
							i += 1
						}*/

						// create events and send it to cin
						ev := bees.Event{
							Bee:  mod.Name(),
							Name: "received",
							Options: []bees.Placeholder{
								{
									Name:  "sender",
									Type:  "string",
									Value: e.MailFrom.String(),
								},
								{
									Name:  "remote_ip",
									Type:  "string",
									Value: e.RemoteIP,
								},
								{
									Name:  "recipients",
									Type:  "[]string",
									Value: recipients,
								},
								{
									Name:  "subject",
									Type:  "string",
									Value: e.Subject,
								},
								{
									Name:  "tls",
									Type:  "boolean",
									Value: e.TLS,
								},
								{
									Name:  "headers",
									Type:  "[]string",
									Value: e.Header,
								},
								{
									Name:  "body",
									Type:  "string",
									Value: e.Data.String(),
								},
							},
						}

						mod.eventChan <- ev
					}

					return p.Process(e, task)
				},
			)
		}
	}
}

// Run executes the Bee's event loop.
func (mod *EmailServerBee) Run(cin chan bees.Event) {
	mod.eventChan = cin

	// see https://github.com/flashmob/go-guerrilla/wiki/Using-as-a-package

	cfg := &guerrilla.AppConfig{
		AllowedHosts: mod.allowedHosts,
		// LogLevel controls the lowest level we log.
		// "info", "debug", "error", "panic". Default "info"
		LogLevel: "info",
	}

	tc := guerrilla.ServerTLSConfig{
		StartTLSOn:     mod.startTLSOn,
		AlwaysOn:       mod.tlsAlwaysOn,
		PrivateKeyFile: mod.privateKeyFile,
		PublicKeyFile:  mod.publicKeyFile,
	}
	sc := guerrilla.ServerConfig{
		IsEnabled:       true,
		Hostname:        mod.hostname,
		ListenInterface: mod.address,
		MaxSize:         mod.maxSize,
		Timeout:         mod.timeout,
		MaxClients:      mod.maxClients,
		TLS:             tc,
	}
	cfg.Servers = append(cfg.Servers, sc)

	bcfg := backends.BackendConfig{
		"save_process":       "HeadersParser|Header|Hasher|Beehive",
		"log_received_mails": true,
	}
	cfg.BackendConfig = bcfg

	d := guerrilla.Daemon{Config: cfg}
	d.AddProcessor("Beehive", mod.mailProcessor())

	err := d.Start()
	if err != nil {
		mod.LogFatal("Error starting SMTP-Server", err)
	}

	select {
	case <-mod.SigChan:
		d.Shutdown()
		return
	}
}

// ReloadOptions parses the config options and initializes the Bee.
func (mod *EmailServerBee) ReloadOptions(options bees.BeeOptions) {
	mod.SetOptions(options)

	options.Bind("address", &mod.address)
	options.Bind("allowedHosts", &mod.allowedHosts)
	options.Bind("startTLSOn", &mod.startTLSOn)
	options.Bind("tlsAlwaysOn", &mod.tlsAlwaysOn)
	options.Bind("privateKeyFile", &mod.privateKeyFile)
	options.Bind("publicKeyFile", &mod.publicKeyFile)
	options.Bind("hostname", &mod.hostname)
	options.Bind("maxSize", &mod.maxSize)
	options.Bind("timeout", &mod.timeout)
	options.Bind("maxClient", &mod.maxClients)
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

import (
	"fmt"

	"github.com/google/trillian"
	"github.com/google/trillian/merkle/hashers"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func validateGetInclusionProofRequest(req *trillian.GetInclusionProofRequest) error {
	if req.TreeSize <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetInclusionProofRequest.TreeSize: %v, want > 0", req.TreeSize)
	}
	if req.LeafIndex < 0 {
		return status.Errorf(codes.InvalidArgument, "GetInclusionProofRequest.LeafIndex: %v, want >= 0", req.LeafIndex)
	}
	if req.LeafIndex >= req.TreeSize {
		return status.Errorf(codes.InvalidArgument, "GetInclusionProofRequest.LeafIndex: %v >= TreeSize: %v, want < ", req.LeafIndex, req.TreeSize)
	}
	return nil
}

func validateGetInclusionProofByHashRequest(req *trillian.GetInclusionProofByHashRequest, hasher hashers.LogHasher) error {
	if req.TreeSize <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetInclusionProofByHashRequest.TreeSize: %v, want > 0", req.TreeSize)
	}
	if err := validateLeafHash(req.LeafHash, hasher); err != nil {
		return status.Errorf(codes.InvalidArgument, "GetInclusionProofByHashRequest.LeafHash: %v", err)
	}
	return nil
}

func validateGetLeavesByHashRequest(req *trillian.GetLeavesByHashRequest, hasher hashers.LogHasher) error {
	if len(req.LeafHash) == 0 {
		return status.Error(codes.InvalidArgument, "GetLeavesByHashRequest.LeafHash empty")
	}
	for i, hash := range req.LeafHash {
		if err := validateLeafHash(hash, hasher); err != nil {
			return status.Errorf(codes.InvalidArgument, "GetLeavesByHashRequest.LeafHash[%v]: %v", i, err)
		}
	}
	return nil
}

func validateGetLeavesByIndexRequest(req *trillian.GetLeavesByIndexRequest) error {
	if len(req.LeafIndex) == 0 {
		return status.Error(codes.InvalidArgument, "GetLeavesByIndexRequest.LeafIndex empty")
	}
	for i, leafIndex := range req.LeafIndex {
		if leafIndex < 0 {
			return status.Errorf(codes.InvalidArgument, "GetLeavesByIndexRequest.LeafIndex[%v]: %v, want >= 0", i, leafIndex)
		}
	}
	return nil
}

func validateGetLeavesByRangeRequest(req *trillian.GetLeavesByRangeRequest) error {
	if req.StartIndex < 0 {
		return status.Errorf(codes.InvalidArgument, "GetLeavesByRangeRequest.StartIndex: %v, want >= 0", req.StartIndex)
	}
	if req.Count <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetLeavesByRangeRequest.Count: %v, want > 0", req.Count)
	}
	return nil
}

func validateGetConsistencyProofRequest(req *trillian.GetConsistencyProofRequest) error {
	if req.FirstTreeSize <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetConsistencyProofRequest.FirstTreeSize: %v, want > 0", req.FirstTreeSize)
	}
	if req.SecondTreeSize <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetConsistencyProofRequest.SecondTreeSize: %v, want > 0", req.SecondTreeSize)
	}
	if req.SecondTreeSize < req.FirstTreeSize {
		return status.Errorf(codes.InvalidArgument, "GetConsistencyProofRequest.FirstTreeSize: %v < GetConsistencyProofRequest.SecondTreeSize: %v, want >= ", req.FirstTreeSize, req.SecondTreeSize)
	}
	return nil
}

func validateGetEntryAndProofRequest(req *trillian.GetEntryAndProofRequest) error {
	if req.TreeSize <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetEntryAndProofRequest.TreeSize: %v, want > 0", req.TreeSize)
	}
	if req.LeafIndex < 0 {
		return status.Errorf(codes.InvalidArgument, "GetEntryAndProofRequest.LeafIndex: %v, want >= 0", req.LeafIndex)
	}
	if req.LeafIndex >= req.TreeSize {
		return status.Errorf(codes.InvalidArgument, "GetEntryAndProofRequest.LeafIndex: %v >= TreeSize: %v, want < ", req.LeafIndex, req.TreeSize)
	}
	return nil
}

func validateAddSequencedLeavesRequest(req *trillian.AddSequencedLeavesRequest) error {
	prefix := "AddSequencedLeavesRequest"
	if err := validateLogLeaves(req.Leaves, prefix); err != nil {
		return err
	}

	// Note: Not empty, as verified by validateLogLeaves.
	nextIndex := req.Leaves[0].LeafIndex
	for i, leaf := range req.Leaves {
		if leaf.LeafIndex != nextIndex {
			return status.Errorf(codes.FailedPrecondition, "%v.Leaves[%v].LeafIndex=%v, want %v", prefix, i, leaf.LeafIndex, nextIndex)
		}
		nextIndex++
	}
	return nil
}

func validateLogLeaves(leaves []*trillian.LogLeaf, errPrefix string) error {
	if len(leaves) == 0 {
		return status.Errorf(codes.InvalidArgument, "%v.Leaves empty", errPrefix)
	}
	for i, leaf := range leaves {
		if err := validateLogLeaf(leaf, ""); err != nil {
			return status.Errorf(codes.InvalidArgument, "%v.Leaves[%v]%v", errPrefix, i, err)
		}
	}
	return nil
}

func validateLogLeaf(leaf *trillian.LogLeaf, errPrefix string) error {
	if leaf == nil {
		return status.Errorf(codes.InvalidArgument, "%v empty", errPrefix)
	}
	switch {
	case len(leaf.LeafValue) == 0:
		return status.Errorf(codes.InvalidArgument, "%v.LeafValue: empty", errPrefix)
	case leaf.LeafIndex < 0:
		return status.Errorf(codes.InvalidArgument, "%v.LeafIndex: %v, want >= 0", errPrefix, leaf.LeafIndex)
	}
	return nil
}

func validateLeafHash(hash []byte, hasher hashers.LogHasher) error {
	if got, want := len(hash), hasher.Size(); got != want {
		return fmt.Errorf("%d bytes, want %d", got, want)
	}
	return nil
}
<commit_msg>Fix a typo in error message (#1517)<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

import (
	"fmt"

	"github.com/google/trillian"
	"github.com/google/trillian/merkle/hashers"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func validateGetInclusionProofRequest(req *trillian.GetInclusionProofRequest) error {
	if req.TreeSize <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetInclusionProofRequest.TreeSize: %v, want > 0", req.TreeSize)
	}
	if req.LeafIndex < 0 {
		return status.Errorf(codes.InvalidArgument, "GetInclusionProofRequest.LeafIndex: %v, want >= 0", req.LeafIndex)
	}
	if req.LeafIndex >= req.TreeSize {
		return status.Errorf(codes.InvalidArgument, "GetInclusionProofRequest.LeafIndex: %v >= TreeSize: %v, want < ", req.LeafIndex, req.TreeSize)
	}
	return nil
}

func validateGetInclusionProofByHashRequest(req *trillian.GetInclusionProofByHashRequest, hasher hashers.LogHasher) error {
	if req.TreeSize <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetInclusionProofByHashRequest.TreeSize: %v, want > 0", req.TreeSize)
	}
	if err := validateLeafHash(req.LeafHash, hasher); err != nil {
		return status.Errorf(codes.InvalidArgument, "GetInclusionProofByHashRequest.LeafHash: %v", err)
	}
	return nil
}

func validateGetLeavesByHashRequest(req *trillian.GetLeavesByHashRequest, hasher hashers.LogHasher) error {
	if len(req.LeafHash) == 0 {
		return status.Error(codes.InvalidArgument, "GetLeavesByHashRequest.LeafHash empty")
	}
	for i, hash := range req.LeafHash {
		if err := validateLeafHash(hash, hasher); err != nil {
			return status.Errorf(codes.InvalidArgument, "GetLeavesByHashRequest.LeafHash[%v]: %v", i, err)
		}
	}
	return nil
}

func validateGetLeavesByIndexRequest(req *trillian.GetLeavesByIndexRequest) error {
	if len(req.LeafIndex) == 0 {
		return status.Error(codes.InvalidArgument, "GetLeavesByIndexRequest.LeafIndex empty")
	}
	for i, leafIndex := range req.LeafIndex {
		if leafIndex < 0 {
			return status.Errorf(codes.InvalidArgument, "GetLeavesByIndexRequest.LeafIndex[%v]: %v, want >= 0", i, leafIndex)
		}
	}
	return nil
}

func validateGetLeavesByRangeRequest(req *trillian.GetLeavesByRangeRequest) error {
	if req.StartIndex < 0 {
		return status.Errorf(codes.InvalidArgument, "GetLeavesByRangeRequest.StartIndex: %v, want >= 0", req.StartIndex)
	}
	if req.Count <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetLeavesByRangeRequest.Count: %v, want > 0", req.Count)
	}
	return nil
}

func validateGetConsistencyProofRequest(req *trillian.GetConsistencyProofRequest) error {
	if req.FirstTreeSize <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetConsistencyProofRequest.FirstTreeSize: %v, want > 0", req.FirstTreeSize)
	}
	if req.SecondTreeSize <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetConsistencyProofRequest.SecondTreeSize: %v, want > 0", req.SecondTreeSize)
	}
	if req.SecondTreeSize < req.FirstTreeSize {
		return status.Errorf(codes.InvalidArgument, "GetConsistencyProofRequest.SecondTreeSize: %v < GetConsistencyProofRequest.FirstTreeSize: %v, want >= ", req.SecondTreeSize, req.FirstTreeSize)
	}
	return nil
}

func validateGetEntryAndProofRequest(req *trillian.GetEntryAndProofRequest) error {
	if req.TreeSize <= 0 {
		return status.Errorf(codes.InvalidArgument, "GetEntryAndProofRequest.TreeSize: %v, want > 0", req.TreeSize)
	}
	if req.LeafIndex < 0 {
		return status.Errorf(codes.InvalidArgument, "GetEntryAndProofRequest.LeafIndex: %v, want >= 0", req.LeafIndex)
	}
	if req.LeafIndex >= req.TreeSize {
		return status.Errorf(codes.InvalidArgument, "GetEntryAndProofRequest.LeafIndex: %v >= TreeSize: %v, want < ", req.LeafIndex, req.TreeSize)
	}
	return nil
}

func validateAddSequencedLeavesRequest(req *trillian.AddSequencedLeavesRequest) error {
	prefix := "AddSequencedLeavesRequest"
	if err := validateLogLeaves(req.Leaves, prefix); err != nil {
		return err
	}

	// Note: Not empty, as verified by validateLogLeaves.
	nextIndex := req.Leaves[0].LeafIndex
	for i, leaf := range req.Leaves {
		if leaf.LeafIndex != nextIndex {
			return status.Errorf(codes.FailedPrecondition, "%v.Leaves[%v].LeafIndex=%v, want %v", prefix, i, leaf.LeafIndex, nextIndex)
		}
		nextIndex++
	}
	return nil
}

func validateLogLeaves(leaves []*trillian.LogLeaf, errPrefix string) error {
	if len(leaves) == 0 {
		return status.Errorf(codes.InvalidArgument, "%v.Leaves empty", errPrefix)
	}
	for i, leaf := range leaves {
		if err := validateLogLeaf(leaf, ""); err != nil {
			return status.Errorf(codes.InvalidArgument, "%v.Leaves[%v]%v", errPrefix, i, err)
		}
	}
	return nil
}

func validateLogLeaf(leaf *trillian.LogLeaf, errPrefix string) error {
	if leaf == nil {
		return status.Errorf(codes.InvalidArgument, "%v empty", errPrefix)
	}
	switch {
	case len(leaf.LeafValue) == 0:
		return status.Errorf(codes.InvalidArgument, "%v.LeafValue: empty", errPrefix)
	case leaf.LeafIndex < 0:
		return status.Errorf(codes.InvalidArgument, "%v.LeafIndex: %v, want >= 0", errPrefix, leaf.LeafIndex)
	}
	return nil
}

func validateLeafHash(hash []byte, hasher hashers.LogHasher) error {
	if got, want := len(hash), hasher.Size(); got != want {
		return fmt.Errorf("%d bytes, want %d", got, want)
	}
	return nil
}
<|endoftext|>