<commit_before>package cli

import (
	"fmt"
	"github.com/jkomoros/boardgame"
	"github.com/jroimartin/gocui"
	"reflect"
	"strings"
	"unicode"
)

type inputMode interface {
	//enterMode enters the specified mode, doing any init necessary. Do not
	//register keybindings (unless you're doing mouse)
	enterMode()

	//handleInput is where all input is routed.
	handleInput(key gocui.Key, ch rune, mode gocui.Modifier)
	//statusLine returns the text that should be displayed in the status line.
	statusLine() string
	//Whether or not the overlay should be visible
	showOverlay() bool
	//Returns overlay content
	overlayContent() []string
	//Returns the title for overlay
	overlayTitle() string
	//Which line in the overlay to highlight. -1 is "none"
	overlayHighlightedLine() int
}

type modeBase struct {
	c *Controller
}

type modeNormal struct {
	modeBase
}

type modePickMove struct {
	modeBase
	numLines    int
	currentLine int
}

type modeEditMove struct {
	modeBase
	move boardgame.Move
}

func (m *modeBase) enterMode() {

	m.c.gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return gocui.ErrQuit
	})

}

func (m *modeBase) handleInput(key gocui.Key, ch rune, mode gocui.Modifier) {
	//Currently there arent any base handlers. Quitting via Ctrl-C is handled
	//via gocui key bindings so we can exit cleanly.
}

func (m *modeBase) statusLine() string {
	return "Type 't' to toggle json or render output, 'm' to propose a move, Ctrl-C to quit"
}

func (m *modeBase) showOverlay() bool {
	return false
}

func (m *modeBase) overlayContent() []string {
	return nil
}

func (m *modeBase) overlayTitle() string {
	return ""
}

func (m *modeBase) overlayHighlightedLine() int {
	return -1
}

func (m *modeNormal) handleInput(key gocui.Key, ch rune, mode gocui.Modifier) {
	handled := false
	switch key {
	case gocui.KeyArrowUp:
		m.c.ScrollUp()
		handled = true
	case gocui.KeyArrowDown:
		m.c.ScrollDown()
		handled = true
	}
	if handled {
		return
	}
	switch ch {
	case 't':
		m.c.ToggleRender()
		handled = true
	case 'm':
		m.c.StartProposingMove()
		handled = true
	}
	if handled {
		return
	}
	m.modeBase.handleInput(key, ch, mode)
}

func (m *modeNormal) enterMode() {

	c := m.c

	m.modeBase.enterMode()

	g := c.gui

	//TODO: can we skip setting key bindings for mouse?

	if err := g.SetKeybinding("", gocui.MouseWheelUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		c.ScrollUp()
		return nil
	}); err != nil {
		panic(err)
	}

	if err := g.SetKeybinding("", gocui.MouseWheelDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		c.ScrollDown()
		return nil
	}); err != nil {
		panic(err)
	}

}

func (m *modePickMove) handleInput(key gocui.Key, ch rune, mode gocui.Modifier) {
	handled := false
	switch key {
	case gocui.KeyArrowUp:
		m.MoveSelectionUp()
		handled = true
	case gocui.KeyArrowDown:
		m.MoveSelectionDown()
		handled = true
	case gocui.KeyEsc:
		m.c.CancelMode()
		handled = true
	case gocui.KeyEnter:
		m.PickCurrentlySelectedMoveToEdit()
	}
	if handled {
		return
	}
	m.modeBase.handleInput(key, ch, mode)
}

func (m *modePickMove) enterMode() {

	m.modeBase.enterMode()

}

func (m *modePickMove) statusLine() string {
	return "'Enter' to pick a move to edit. 'Esc' to cancel"
}

func (m *modePickMove) showOverlay() bool {
	return true
}

func (m *modePickMove) overlayContent() []string {
	//TODO: memoize this
	moves := m.c.renderMoves()

	//TODO: this is VERY weird that we're using a side-effect to set this
	//piece of state in controller.
	m.numLines = len(moves)

	return moves
}

func (m *modePickMove) overlayTitle() string {
	return "Pick Move To Propose"
}

func (m *modePickMove) overlayHighlightedLine() int {
	return m.currentLine
}

func (m *modePickMove) MoveSelectionUp() {

	if m.currentLine == 0 {
		return
	}
	m.currentLine--
}

func (m *modePickMove) MoveSelectionDown() {

	if m.currentLine+1 >= m.numLines {
		return
	}
	m.currentLine++
}

func (m *modePickMove) PickCurrentlySelectedMoveToEdit() {

	move := m.c.game.Moves()[m.currentLine]

	m.c.PickMoveToEdit(move)
}

func (m *modeEditMove) handleInput(key gocui.Key, ch rune, mode gocui.Modifier) {
	handled := false
	switch key {
	case gocui.KeyEsc:
		//TODO: should this esc handler just be in baseMode?
		m.c.CancelMode()
		handled = true
	}
	if handled {
		return
	}
	m.modeBase.handleInput(key, ch, mode)
}

func (m *modeEditMove) enterMode() {
	m.modeBase.enterMode()

	//TODO:should the esc handler just be in baseMode?

}

func (m *modeEditMove) statusLine() string {
	return "this is where you edit the move, I guess. Or 'Esc' to cancel. I don't care."
}

func (m *modeEditMove) showOverlay() bool {
	return true
}

func moveFieldNameShouldBeIncluded(name string) bool {
	if len(name) < 1 {
		return false
	}

	firstChar := []rune(name)[0]

	if firstChar != unicode.ToUpper(firstChar) {
		//It was not upper case, thus private, thus should not be included.
		return false
	}

	return true
}

func (m *modeEditMove) overlayContent() []string {
	//TODO; return real content

	var lines []string

	s := reflect.ValueOf(m.move).Elem()
	typeOfT := s.Type()
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		fieldName := typeOfT.Field(i).Name
		if !moveFieldNameShouldBeIncluded(fieldName) {
			continue
		}
		lines = append(lines, fmt.Sprintf("%s (%s): %v", fieldName, f.Type(), f.Interface()))
	}

	result := make([]string, len(lines))

	//Make sure all of the field types for the size are set the same size
	maxLineLength := 0

	for _, line := range lines {
		parts := strings.Split(line, ":")
		if len(parts[0]) > maxLineLength {
			maxLineLength = len(parts[0])
		}
	}

	for i, line := range lines {
		parts := strings.Split(line, ":")
		result[i] = strings.Repeat(" ", maxLineLength-len(parts[0])) + line
	}

	return result

}

func (m *modeEditMove) overlayTitle() string {
	return "Editing Move"
}
<commit_msg>If there are no fields to modify in a move, print that. Part of #26.<commit_after>package cli

import (
	"fmt"
	"github.com/jkomoros/boardgame"
	"github.com/jroimartin/gocui"
	"reflect"
	"strings"
	"unicode"
)

type inputMode interface {
	//enterMode enters the specified mode, doing any init necessary. Do not
	//register keybindings (unless you're doing mouse)
	enterMode()

	//handleInput is where all input is routed.
	handleInput(key gocui.Key, ch rune, mode gocui.Modifier)
	//statusLine returns the text that should be displayed in the status line.
	statusLine() string
	//Whether or not the overlay should be visible
	showOverlay() bool
	//Returns overlay content
	overlayContent() []string
	//Returns the title for overlay
	overlayTitle() string
	//Which line in the overlay to highlight. -1 is "none"
	overlayHighlightedLine() int
}

type modeBase struct {
	c *Controller
}

type modeNormal struct {
	modeBase
}

type modePickMove struct {
	modeBase
	numLines    int
	currentLine int
}

type modeEditMove struct {
	modeBase
	move boardgame.Move
}

func (m *modeBase) enterMode() {

	m.c.gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return gocui.ErrQuit
	})

}

func (m *modeBase) handleInput(key gocui.Key, ch rune, mode gocui.Modifier) {
	//Currently there arent any base handlers. Quitting via Ctrl-C is handled
	//via gocui key bindings so we can exit cleanly.
}

func (m *modeBase) statusLine() string {
	return "Type 't' to toggle json or render output, 'm' to propose a move, Ctrl-C to quit"
}

func (m *modeBase) showOverlay() bool {
	return false
}

func (m *modeBase) overlayContent() []string {
	return nil
}

func (m *modeBase) overlayTitle() string {
	return ""
}

func (m *modeBase) overlayHighlightedLine() int {
	return -1
}

func (m *modeNormal) handleInput(key gocui.Key, ch rune, mode gocui.Modifier) {
	handled := false
	switch key {
	case gocui.KeyArrowUp:
		m.c.ScrollUp()
		handled = true
	case gocui.KeyArrowDown:
		m.c.ScrollDown()
		handled = true
	}
	if handled {
		return
	}
	switch ch {
	case 't':
		m.c.ToggleRender()
		handled = true
	case 'm':
		m.c.StartProposingMove()
		handled = true
	}
	if handled {
		return
	}
	m.modeBase.handleInput(key, ch, mode)
}

func (m *modeNormal) enterMode() {

	c := m.c

	m.modeBase.enterMode()

	g := c.gui

	//TODO: can we skip setting key bindings for mouse?

	if err := g.SetKeybinding("", gocui.MouseWheelUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		c.ScrollUp()
		return nil
	}); err != nil {
		panic(err)
	}

	if err := g.SetKeybinding("", gocui.MouseWheelDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		c.ScrollDown()
		return nil
	}); err != nil {
		panic(err)
	}

}

func (m *modePickMove) handleInput(key gocui.Key, ch rune, mode gocui.Modifier) {
	handled := false
	switch key {
	case gocui.KeyArrowUp:
		m.MoveSelectionUp()
		handled = true
	case gocui.KeyArrowDown:
		m.MoveSelectionDown()
		handled = true
	case gocui.KeyEsc:
		m.c.CancelMode()
		handled = true
	case gocui.KeyEnter:
		m.PickCurrentlySelectedMoveToEdit()
	}
	if handled {
		return
	}
	m.modeBase.handleInput(key, ch, mode)
}

func (m *modePickMove) enterMode() {

	m.modeBase.enterMode()

}

func (m *modePickMove) statusLine() string {
	return "'Enter' to pick a move to edit. 'Esc' to cancel"
}

func (m *modePickMove) showOverlay() bool {
	return true
}

func (m *modePickMove) overlayContent() []string {
	//TODO: memoize this
	moves := m.c.renderMoves()

	//TODO: this is VERY weird that we're using a side-effect to set this
	//piece of state in controller.
	m.numLines = len(moves)

	return moves
}

func (m *modePickMove) overlayTitle() string {
	return "Pick Move To Propose"
}

func (m *modePickMove) overlayHighlightedLine() int {
	return m.currentLine
}

func (m *modePickMove) MoveSelectionUp() {

	if m.currentLine == 0 {
		return
	}
	m.currentLine--
}

func (m *modePickMove) MoveSelectionDown() {

	if m.currentLine+1 >= m.numLines {
		return
	}
	m.currentLine++
}

func (m *modePickMove) PickCurrentlySelectedMoveToEdit() {

	move := m.c.game.Moves()[m.currentLine]

	m.c.PickMoveToEdit(move)
}

func (m *modeEditMove) handleInput(key gocui.Key, ch rune, mode gocui.Modifier) {
	handled := false
	switch key {
	case gocui.KeyEsc:
		//TODO: should this esc handler just be in baseMode?
		m.c.CancelMode()
		handled = true
	}
	if handled {
		return
	}
	m.modeBase.handleInput(key, ch, mode)
}

func (m *modeEditMove) enterMode() {
	m.modeBase.enterMode()

	//TODO:should the esc handler just be in baseMode?

}

func (m *modeEditMove) statusLine() string {
	return "this is where you edit the move, I guess. Or 'Esc' to cancel. I don't care."
}

func (m *modeEditMove) showOverlay() bool {
	return true
}

func moveFieldNameShouldBeIncluded(name string) bool {
	if len(name) < 1 {
		return false
	}

	firstChar := []rune(name)[0]

	if firstChar != unicode.ToUpper(firstChar) {
		//It was not upper case, thus private, thus should not be included.
		return false
	}

	return true
}

func (m *modeEditMove) overlayContent() []string {
	//TODO; return real content

	var lines []string

	s := reflect.ValueOf(m.move).Elem()
	typeOfT := s.Type()
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		fieldName := typeOfT.Field(i).Name
		if !moveFieldNameShouldBeIncluded(fieldName) {
			continue
		}
		lines = append(lines, fmt.Sprintf("%s (%s): %v", fieldName, f.Type(), f.Interface()))
	}

	result := make([]string, len(lines))

	//Make sure all of the field types for the size are set the same size
	maxLineLength := 0

	for _, line := range lines {
		parts := strings.Split(line, ":")
		if len(parts[0]) > maxLineLength {
			maxLineLength = len(parts[0])
		}
	}

	for i, line := range lines {
		parts := strings.Split(line, ":")
		result[i] = strings.Repeat(" ", maxLineLength-len(parts[0])) + line
	}

	if len(result) == 0 {
		//No fields!
		return []string{"No fields to modify"}
	}

	return result

}

func (m *modeEditMove) overlayTitle() string {
	return "Editing Move"
}
<|endoftext|>


<commit_before>// Copyright 2014 The Serviced 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 agent implements a service that runs on a serviced node. It is
// responsible for ensuring that a particular node is running the correct services
// and reporting the state and health of those services back to the master
// serviced.

package isvcs

import (
	"time"

	"github.com/control-center/serviced/commons"
	"github.com/control-center/serviced/commons/docker"
	"github.com/control-center/serviced/dao"
	"github.com/control-center/serviced/domain"
	"github.com/zenoss/glog"
	dockerclient "github.com/zenoss/go-dockerclient"

	"errors"
	"fmt"
	"os"
	"path"
	"sort"
	"sync"
)

// managerOp is a type of manager operation (stop, start, notify)
type managerOp int

// constants for the manager operations
const (
	managerOpStart             managerOp = iota // Start the subservices
	managerOpStop                               // stop the subservices
	managerOpNotify                             // notify config in subservices
	managerOpExit                               // exit the loop of the manager
	managerOpRegisterContainer                  // register a given container
	managerOpInit                               // make sure manager is ready to run containers
	managerOpWipe                               // wipe all data associated with volumes
)

var (
	ErrManagerUnknownOp  = errors.New("manager: unknown operation")
	ErrManagerUnknownArg = errors.New("manager: unknown arg type")
	ErrImageNotExists    = errors.New("manager: image does not exist")
	ErrNotifyFailed      = errors.New("manager: notification failure")
)

type StartError int

func (err StartError) Error() string {
	return fmt.Sprintf("manager: could not start %d isvcs", int(err))
}

type StopError int

func (err StopError) Error() string {
	return fmt.Sprintf("manager: coulf not stop %d isvcs", int(err))
}

// A managerRequest describes an operation for the manager loop() to perform and a response channel
type managerRequest struct {
	op       managerOp // the operation to perform
	val      interface{}
	response chan error // the response channel
}

// A manager of docker services run in ephemeral containers
type Manager struct {
	imagesDir  string              // local directory where images could be loaded from
	volumesDir string              // local directory where volumes are stored
	requests   chan managerRequest // the main loops request channel
	services   map[string]*IService
}

// Returns a new Manager struct and starts the Manager's main loop()
func NewManager(imagesDir, volumesDir string) *Manager {
	loadvolumes()

	manager := &Manager{
		imagesDir:  imagesDir,
		volumesDir: volumesDir,
		requests:   make(chan managerRequest),
		services:   make(map[string]*IService),
	}
	go manager.loop()
	return manager
}

// checks to see if the given repo:tag exists in docker
func (m *Manager) imageExists(repo, tag string) (bool, error) {
	if _, err := docker.FindImage(commons.JoinRepoTag(repo, tag), false); err == docker.ErrNoSuchImage {
		return false, nil
	} else if err != nil {
		return false, err
	}
	return true, nil
}

// SetVolumesDir sets the volumes dir for *Manager
func (m *Manager) SetVolumesDir(dir string) {
	m.volumesDir = dir
}

func (m *Manager) SetConfigurationOption(name, key string, value interface{}) error {
	svc, found := m.services[name]
	if !found {
		return errors.New("could not find isvc")
	}
	glog.Infof("setting %s, %s: %s", name, key, value)
	svc.Configuration[key] = value
	return nil
}

// Returns a list of iservice names in sorted order
func (m *Manager) GetServiceNames() []string {
	names := make([]string, 0, len(m.services))
	for name := range m.services {
		names = append(names, name)
	}
	sort.Strings(names)
	return names
}

func (m *Manager) GetHealthStatus(name string) (dao.IServiceHealthResult, error) {
	result := dao.IServiceHealthResult{
		ServiceName:    name,
		ContainerName:  "",
		ContainerID:    "",
		HealthStatuses: make([]domain.HealthCheckStatus, 0),
	}

	svc, found := m.services[name]
	if !found {
		glog.Errorf("Internal service %q not found", name)
		return dao.IServiceHealthResult{}, fmt.Errorf("could not find isvc %q", name)
	}

	// FIXME: Does it make sense to exit with a failure when the container is
	//        not found, or should we return an instance of IServiceHealthResult
	//        with HealthStatuses["running"] == a-failed-instance?
	ctr, err := docker.FindContainer(svc.name())
	if err != nil {
		glog.Errorf("Could not find container for isvc %s: %s", svc.Name, err)
		return dao.IServiceHealthResult{}, err
	}

	svc.lock.RLock()
	defer svc.lock.RUnlock()

	result.ContainerName = svc.name()
	result.ContainerID = ctr.ID
	for _, value := range svc.healthStatuses {
		result.HealthStatuses = append(result.HealthStatuses, *value)
	}
	return result, nil
}

// checks for the existence of all the container images
func (m *Manager) allImagesExist() error {
	for _, c := range m.services {
		if exists, err := m.imageExists(c.Repo, c.Tag); err != nil {
			return err
		} else {
			if !exists {
				return ErrImageNotExists
			}
		}
	}
	return nil
}

// wipe() removes the data directory associate with the manager
func (m *Manager) wipe() error {

	if err := os.RemoveAll(m.volumesDir); err != nil {
		glog.V(2).Infof("could not remove %s: %v", m.volumesDir, err)
	}
	//nothing to wipe if the volumesDir doesn't exist
	if _, err := os.Stat(m.volumesDir); os.IsNotExist(err) {
		glog.V(2).Infof("Not using docker to remove directories as %s doesn't exist", m.volumesDir)
		return nil
	}
	glog.Infof("Using docker to remove directories in %s", m.volumesDir)

	// remove volumeDir by running a container as root
	// FIXME: detect if already root and avoid running docker
	var config dockerclient.Config
	cd := &docker.ContainerDefinition{
		dockerclient.CreateContainerOptions{Config: &config},
		dockerclient.HostConfig{},
	}

	config.Image = "ubuntu"
	config.Cmd = []string{"/bin/sh", "-c", "rm -Rf /mnt/volumes/*"}
	config.Volumes = map[string]struct{}{
		"/mnt/volumes": struct{}{},
	}

	cd.Binds = []string{m.volumesDir + ":/mnt/volumes"}
	ctr, err := docker.NewContainer(cd, false, 5*time.Second, nil, nil)
	if err != nil {
		return err
	}

	ctr.OnEvent(docker.Die, func(cid string) {
		ctr.Delete(true)
	})

	return ctr.Start(10 * time.Second)
}

// loadImages() loads all the images defined in the registered services
func (m *Manager) loadImages() error {
	loadedImages := make(map[string]bool)
	for _, c := range m.services {
		glog.V(2).Infof("Checking isvcs container %+v", c)
		if exists, err := m.imageExists(c.Repo, c.Tag); err != nil {
			return err
		} else {
			if exists {
				continue
			}
			localTar := path.Join(m.imagesDir, c.Repo, c.Tag+".tar.gz")
			imageRepoTag := c.Repo + ":" + c.Tag
			glog.Infof("Looking for image %s in tar %s", imageRepoTag, localTar)
			if _, exists := loadedImages[imageRepoTag]; exists {
				continue
			}
			if _, err := os.Stat(localTar); err == nil {
				if err := docker.ImportImage(imageRepoTag, localTar); err != nil {
					return err
				}
				glog.Infof("Loaded %s from %s", imageRepoTag, localTar)
				loadedImages[imageRepoTag] = true
			} else {
				glog.Infof("Pulling image %s", imageRepoTag)
				if err := docker.PullImage(imageRepoTag); err != nil {
					return fmt.Errorf("Failed to pull image %s: %s", imageRepoTag, err)
				}
				glog.Infof("Pulled %s", imageRepoTag)
				loadedImages[imageRepoTag] = true
			}
		}
	}
	return nil
}

type containerStartResponse struct {
	name string
	err  error
}

// loop() maitainers the Manager's state
func (m *Manager) loop() {

	var once sync.Once

	for {
		select {
		case request := <-m.requests:
			switch request.op {
			case managerOpWipe:
				// stop all iservices
				var wg sync.WaitGroup
				for name, svc := range m.services {
					if svc.IsRunning() {
						wg.Add(1)
						go func(svc *IService) {
							defer wg.Done()
							if err := svc.Stop(); err != nil {
								glog.Errorf("Error stopping isvc %s: %s", svc.Name, err)
							}
						}(m.services[name])
					}
				}
				wg.Wait()
				request.response <- m.wipe()
			case managerOpNotify:
				var failed bool
				for _, svc := range m.services {
					if svc.Notify != nil && svc.IsRunning() {
						if err := svc.Notify(svc, request.val); err != nil {
							glog.Errorf("Could not notify isvc %s: %s", svc.Name, err)
							failed = true
						}
					}
				}
				if failed {
					request.response <- ErrNotifyFailed
				} else {
					request.response <- nil
				}
			case managerOpExit:
				request.response <- nil
				return // this will exit the loop()
			case managerOpStart:
				var err error
				once.Do(func() {
					if err = m.loadImages(); err != nil {
						return
					} else if err = m.allImagesExist(); err != nil {
						return
					}
				})
				if err != nil {
					request.response <- err
					continue
				}

				// track the number of services that haven't started
				var noStart = make([]int, len(m.services))

				// start services in parallel
				var wg sync.WaitGroup
				index := 0
				for name, svc := range m.services {
					if !svc.IsRunning() {
						wg.Add(1)
						go func(svc *IService, i int) {
							defer wg.Done()
							if err := svc.Start(); err != nil {
								glog.Errorf("Error starting isvc %s: %s", svc.Name, err)
								noStart[i] = 1
								return
							}
						}(m.services[name], index)
					}
					index++
				}
				wg.Wait()

				count := 0
				for _, i := range noStart {
					count += i
				}
				if count > 0 {
					request.response <- StartError(count)
				} else {
					request.response <- nil
				}
			case managerOpStop:
				// track the number of services that haven't stopped
				var noStop = make([]int, len(m.services))

				// stop services in parallel
				var wg sync.WaitGroup
				index := 0
				for name, svc := range m.services {
					if svc.IsRunning() {
						wg.Add(1)
						go func(svc *IService, i int) {
							defer wg.Done()
							if err := svc.Stop(); err != nil {
								glog.Errorf("Error stopping isvc %s: %s", svc.Name, err)
								noStop[i] = 1
								return
							}
						}(m.services[name], index)
					}
					index++
				}
				wg.Wait()
				count := 0
				for _, i := range noStop {
					count += i
				}
				if count > 0 {
					request.response <- StopError(count)
				} else {
					request.response <- nil
				}
			case managerOpRegisterContainer:
				svc, ok := request.val.(*IService)
				if !ok {
					request.response <- ErrManagerUnknownArg
				} else {
					m.services[svc.Name] = svc
					request.response <- nil
				}
			case managerOpInit:
				request.response <- nil
			default:
				request.response <- ErrManagerUnknownOp
			}
		}
	}
}

// makeRequest sends a manager operation request to the *Manager's loop()
func (m *Manager) makeRequest(op managerOp) error {
	request := managerRequest{
		op:       op,
		response: make(chan error),
	}
	m.requests <- request
	return <-request.response
}

// Register() registers a container to be managed by the *Manager
func (m *Manager) Register(svc *IService) error {
	request := managerRequest{
		op:       managerOpRegisterContainer,
		val:      svc,
		response: make(chan error),
	}
	m.requests <- request
	return <-request.response
}

// Wipe() removes the data directory associated with the Manager
func (m *Manager) Wipe() error {
	glog.V(2).Infof("manager sending wipe request")
	defer glog.V(2).Infof("received wipe response")
	return m.makeRequest(managerOpWipe)
}

// Stop() stops all the containers currently registered to the *Manager
func (m *Manager) Stop() error {
	glog.V(2).Infof("manager sending stop request")
	defer glog.V(2).Infof("received stop response")
	return m.makeRequest(managerOpStop)
}

// Start() starts all the containers managed by the *Manager
func (m *Manager) Start() error {
	glog.V(2).Infof("manager sending start request")
	defer glog.V(2).Infof("received start response")
	return m.makeRequest(managerOpStart)
}

// Notify() sends a notify() message to all the containers with the given data val
func (m *Manager) Notify(val interface{}) error {
	glog.V(2).Infof("manager sending notify request")
	defer glog.V(2).Infof("received notify response")
	request := managerRequest{
		op:       managerOpNotify,
		val:      val,
		response: make(chan error),
	}
	m.requests <- request
	return <-request.response
}

// TearDown() causes the *Manager's loop() to exit
func (m *Manager) TearDown() error {
	glog.V(2).Infof("manager sending exit request")
	defer glog.V(2).Infof("received exit response")
	return m.makeRequest(managerOpExit)
}
<commit_msg>Return health check results even if we don't find the container<commit_after>// Copyright 2014 The Serviced 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 agent implements a service that runs on a serviced node. It is
// responsible for ensuring that a particular node is running the correct services
// and reporting the state and health of those services back to the master
// serviced.

package isvcs

import (
	"time"

	"github.com/control-center/serviced/commons"
	"github.com/control-center/serviced/commons/docker"
	"github.com/control-center/serviced/dao"
	"github.com/control-center/serviced/domain"
	"github.com/zenoss/glog"
	dockerclient "github.com/zenoss/go-dockerclient"

	"errors"
	"fmt"
	"os"
	"path"
	"sort"
	"sync"
)

// managerOp is a type of manager operation (stop, start, notify)
type managerOp int

// constants for the manager operations
const (
	managerOpStart             managerOp = iota // Start the subservices
	managerOpStop                               // stop the subservices
	managerOpNotify                             // notify config in subservices
	managerOpExit                               // exit the loop of the manager
	managerOpRegisterContainer                  // register a given container
	managerOpInit                               // make sure manager is ready to run containers
	managerOpWipe                               // wipe all data associated with volumes
)

var (
	ErrManagerUnknownOp  = errors.New("manager: unknown operation")
	ErrManagerUnknownArg = errors.New("manager: unknown arg type")
	ErrImageNotExists    = errors.New("manager: image does not exist")
	ErrNotifyFailed      = errors.New("manager: notification failure")
)

type StartError int

func (err StartError) Error() string {
	return fmt.Sprintf("manager: could not start %d isvcs", int(err))
}

type StopError int

func (err StopError) Error() string {
	return fmt.Sprintf("manager: coulf not stop %d isvcs", int(err))
}

// A managerRequest describes an operation for the manager loop() to perform and a response channel
type managerRequest struct {
	op       managerOp // the operation to perform
	val      interface{}
	response chan error // the response channel
}

// A manager of docker services run in ephemeral containers
type Manager struct {
	imagesDir  string              // local directory where images could be loaded from
	volumesDir string              // local directory where volumes are stored
	requests   chan managerRequest // the main loops request channel
	services   map[string]*IService
}

// Returns a new Manager struct and starts the Manager's main loop()
func NewManager(imagesDir, volumesDir string) *Manager {
	loadvolumes()

	manager := &Manager{
		imagesDir:  imagesDir,
		volumesDir: volumesDir,
		requests:   make(chan managerRequest),
		services:   make(map[string]*IService),
	}
	go manager.loop()
	return manager
}

// checks to see if the given repo:tag exists in docker
func (m *Manager) imageExists(repo, tag string) (bool, error) {
	if _, err := docker.FindImage(commons.JoinRepoTag(repo, tag), false); err == docker.ErrNoSuchImage {
		return false, nil
	} else if err != nil {
		return false, err
	}
	return true, nil
}

// SetVolumesDir sets the volumes dir for *Manager
func (m *Manager) SetVolumesDir(dir string) {
	m.volumesDir = dir
}

func (m *Manager) SetConfigurationOption(name, key string, value interface{}) error {
	svc, found := m.services[name]
	if !found {
		return errors.New("could not find isvc")
	}
	glog.Infof("setting %s, %s: %s", name, key, value)
	svc.Configuration[key] = value
	return nil
}

// Returns a list of iservice names in sorted order
func (m *Manager) GetServiceNames() []string {
	names := make([]string, 0, len(m.services))
	for name := range m.services {
		names = append(names, name)
	}
	sort.Strings(names)
	return names
}

func (m *Manager) GetHealthStatus(name string) (dao.IServiceHealthResult, error) {
	result := dao.IServiceHealthResult{
		ServiceName:    name,
		ContainerName:  "",
		ContainerID:    "",
		HealthStatuses: make([]domain.HealthCheckStatus, 0),
	}

	svc, found := m.services[name]
	if !found {
		glog.Errorf("Internal service %q not found", name)
		return dao.IServiceHealthResult{}, fmt.Errorf("could not find isvc %q", name)
	}

	if ctr, err := docker.FindContainer(svc.name()); err == nil {
		result.ContainerID = ctr.ID
	} else {
		result.ContainerID = "<none>"
	}

	svc.lock.RLock()
	defer svc.lock.RUnlock()

	result.ContainerName = svc.name()
	for _, value := range svc.healthStatuses {
		result.HealthStatuses = append(result.HealthStatuses, *value)
	}
	return result, nil
}

// checks for the existence of all the container images
func (m *Manager) allImagesExist() error {
	for _, c := range m.services {
		if exists, err := m.imageExists(c.Repo, c.Tag); err != nil {
			return err
		} else {
			if !exists {
				return ErrImageNotExists
			}
		}
	}
	return nil
}

// wipe() removes the data directory associate with the manager
func (m *Manager) wipe() error {

	if err := os.RemoveAll(m.volumesDir); err != nil {
		glog.V(2).Infof("could not remove %s: %v", m.volumesDir, err)
	}
	//nothing to wipe if the volumesDir doesn't exist
	if _, err := os.Stat(m.volumesDir); os.IsNotExist(err) {
		glog.V(2).Infof("Not using docker to remove directories as %s doesn't exist", m.volumesDir)
		return nil
	}
	glog.Infof("Using docker to remove directories in %s", m.volumesDir)

	// remove volumeDir by running a container as root
	// FIXME: detect if already root and avoid running docker
	var config dockerclient.Config
	cd := &docker.ContainerDefinition{
		dockerclient.CreateContainerOptions{Config: &config},
		dockerclient.HostConfig{},
	}

	config.Image = "ubuntu"
	config.Cmd = []string{"/bin/sh", "-c", "rm -Rf /mnt/volumes/*"}
	config.Volumes = map[string]struct{}{
		"/mnt/volumes": struct{}{},
	}

	cd.Binds = []string{m.volumesDir + ":/mnt/volumes"}
	ctr, err := docker.NewContainer(cd, false, 5*time.Second, nil, nil)
	if err != nil {
		return err
	}

	ctr.OnEvent(docker.Die, func(cid string) {
		ctr.Delete(true)
	})

	return ctr.Start(10 * time.Second)
}

// loadImages() loads all the images defined in the registered services
func (m *Manager) loadImages() error {
	loadedImages := make(map[string]bool)
	for _, c := range m.services {
		glog.V(2).Infof("Checking isvcs container %+v", c)
		if exists, err := m.imageExists(c.Repo, c.Tag); err != nil {
			return err
		} else {
			if exists {
				continue
			}
			localTar := path.Join(m.imagesDir, c.Repo, c.Tag+".tar.gz")
			imageRepoTag := c.Repo + ":" + c.Tag
			glog.Infof("Looking for image %s in tar %s", imageRepoTag, localTar)
			if _, exists := loadedImages[imageRepoTag]; exists {
				continue
			}
			if _, err := os.Stat(localTar); err == nil {
				if err := docker.ImportImage(imageRepoTag, localTar); err != nil {
					return err
				}
				glog.Infof("Loaded %s from %s", imageRepoTag, localTar)
				loadedImages[imageRepoTag] = true
			} else {
				glog.Infof("Pulling image %s", imageRepoTag)
				if err := docker.PullImage(imageRepoTag); err != nil {
					return fmt.Errorf("Failed to pull image %s: %s", imageRepoTag, err)
				}
				glog.Infof("Pulled %s", imageRepoTag)
				loadedImages[imageRepoTag] = true
			}
		}
	}
	return nil
}

type containerStartResponse struct {
	name string
	err  error
}

// loop() maitainers the Manager's state
func (m *Manager) loop() {

	var once sync.Once

	for {
		select {
		case request := <-m.requests:
			switch request.op {
			case managerOpWipe:
				// stop all iservices
				var wg sync.WaitGroup
				for name, svc := range m.services {
					if svc.IsRunning() {
						wg.Add(1)
						go func(svc *IService) {
							defer wg.Done()
							if err := svc.Stop(); err != nil {
								glog.Errorf("Error stopping isvc %s: %s", svc.Name, err)
							}
						}(m.services[name])
					}
				}
				wg.Wait()
				request.response <- m.wipe()
			case managerOpNotify:
				var failed bool
				for _, svc := range m.services {
					if svc.Notify != nil && svc.IsRunning() {
						if err := svc.Notify(svc, request.val); err != nil {
							glog.Errorf("Could not notify isvc %s: %s", svc.Name, err)
							failed = true
						}
					}
				}
				if failed {
					request.response <- ErrNotifyFailed
				} else {
					request.response <- nil
				}
			case managerOpExit:
				request.response <- nil
				return // this will exit the loop()
			case managerOpStart:
				var err error
				once.Do(func() {
					if err = m.loadImages(); err != nil {
						return
					} else if err = m.allImagesExist(); err != nil {
						return
					}
				})
				if err != nil {
					request.response <- err
					continue
				}

				// track the number of services that haven't started
				var noStart = make([]int, len(m.services))

				// start services in parallel
				var wg sync.WaitGroup
				index := 0
				for name, svc := range m.services {
					if !svc.IsRunning() {
						wg.Add(1)
						go func(svc *IService, i int) {
							defer wg.Done()
							if err := svc.Start(); err != nil {
								glog.Errorf("Error starting isvc %s: %s", svc.Name, err)
								noStart[i] = 1
								return
							}
						}(m.services[name], index)
					}
					index++
				}
				wg.Wait()

				count := 0
				for _, i := range noStart {
					count += i
				}
				if count > 0 {
					request.response <- StartError(count)
				} else {
					request.response <- nil
				}
			case managerOpStop:
				// track the number of services that haven't stopped
				var noStop = make([]int, len(m.services))

				// stop services in parallel
				var wg sync.WaitGroup
				index := 0
				for name, svc := range m.services {
					if svc.IsRunning() {
						wg.Add(1)
						go func(svc *IService, i int) {
							defer wg.Done()
							if err := svc.Stop(); err != nil {
								glog.Errorf("Error stopping isvc %s: %s", svc.Name, err)
								noStop[i] = 1
								return
							}
						}(m.services[name], index)
					}
					index++
				}
				wg.Wait()
				count := 0
				for _, i := range noStop {
					count += i
				}
				if count > 0 {
					request.response <- StopError(count)
				} else {
					request.response <- nil
				}
			case managerOpRegisterContainer:
				svc, ok := request.val.(*IService)
				if !ok {
					request.response <- ErrManagerUnknownArg
				} else {
					m.services[svc.Name] = svc
					request.response <- nil
				}
			case managerOpInit:
				request.response <- nil
			default:
				request.response <- ErrManagerUnknownOp
			}
		}
	}
}

// makeRequest sends a manager operation request to the *Manager's loop()
func (m *Manager) makeRequest(op managerOp) error {
	request := managerRequest{
		op:       op,
		response: make(chan error),
	}
	m.requests <- request
	return <-request.response
}

// Register() registers a container to be managed by the *Manager
func (m *Manager) Register(svc *IService) error {
	request := managerRequest{
		op:       managerOpRegisterContainer,
		val:      svc,
		response: make(chan error),
	}
	m.requests <- request
	return <-request.response
}

// Wipe() removes the data directory associated with the Manager
func (m *Manager) Wipe() error {
	glog.V(2).Infof("manager sending wipe request")
	defer glog.V(2).Infof("received wipe response")
	return m.makeRequest(managerOpWipe)
}

// Stop() stops all the containers currently registered to the *Manager
func (m *Manager) Stop() error {
	glog.V(2).Infof("manager sending stop request")
	defer glog.V(2).Infof("received stop response")
	return m.makeRequest(managerOpStop)
}

// Start() starts all the containers managed by the *Manager
func (m *Manager) Start() error {
	glog.V(2).Infof("manager sending start request")
	defer glog.V(2).Infof("received start response")
	return m.makeRequest(managerOpStart)
}

// Notify() sends a notify() message to all the containers with the given data val
func (m *Manager) Notify(val interface{}) error {
	glog.V(2).Infof("manager sending notify request")
	defer glog.V(2).Infof("received notify response")
	request := managerRequest{
		op:       managerOpNotify,
		val:      val,
		response: make(chan error),
	}
	m.requests <- request
	return <-request.response
}

// TearDown() causes the *Manager's loop() to exit
func (m *Manager) TearDown() error {
	glog.V(2).Infof("manager sending exit request")
	defer glog.V(2).Infof("received exit response")
	return m.makeRequest(managerOpExit)
}
<|endoftext|>


<commit_before>package goenc

import (
	"bytes"
	"crypto/rand"
	"io/ioutil"
	"os"
	"testing"

	"fmt"

	"github.com/alistanis/goenc/encerrors"
	"github.com/alistanis/goenc/generate"
	"github.com/kisom/testio"
	. "github.com/smartystreets/goconvey/convey"
	"golang.org/x/crypto/nacl/box"
)

func TestFileIO(t *testing.T) {

	Convey("We can successfully perform file writing and reading using the block cipher interface functions", t, func() {
		bc, err := NewCipher(Mock, testComplexity)
		So(err, ShouldBeNil)
		d, err := ioutil.TempDir("/tmp", "")
		So(err, ShouldBeNil)
		defer os.RemoveAll(d)
		tf, err := ioutil.TempFile(d, "")
		So(err, ShouldBeNil)

		data := []byte("test data we'd like to 'encrypt' and save to file")
		key := []byte("test key which is meaningless")

		err = EncryptAndSave(bc, key, data, tf.Name())
		So(err, ShouldBeNil)

		err = tf.Close()
		So(err, ShouldBeNil)

		nd, err := ReadEncryptedFile(bc, key, tf.Name())
		So(err, ShouldBeNil)
		So(bytes.Equal(data, nd), ShouldBeTrue)
	})

}

var (
	alicePub, alicePriv *[32]byte
	bobPub, bobPriv     *[32]byte
)

func TestGenerateKeys(t *testing.T) {
	var err error

	alicePub, alicePriv, err = box.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatalf("%v", err)
	}

	bobPub, bobPriv, err = box.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatalf("%v", err)
	}
}

var (
	testMessage = []byte("do not go gentle into that good night")
	testSecured []byte

	aliceSession, bobSession *Session
	ciphers                  []*Cipher
)

func init() {
	cbc, err := NewCipher(CBC, testComplexity)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}

	cfb, err := NewCipher(CFB, testComplexity)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}
	ctr, err := NewCipher(CTR, testComplexity)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}

	gcm, err := NewCipher(GCM, testComplexity)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}

	nacl, err := NewCipher(NaCL, testComplexity, []byte("this is a pad to use for our key mwahahaha 123456789"))
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}
	ciphers = []*Cipher{cbc, cfb, ctr, gcm, nacl}
}

func TestSessionSetup(t *testing.T) {

	Convey("We can test all ciphers with a session", t, func() {
		for _, c := range ciphers {

			pub, priv, err := GenerateKeyPair()
			So(err, ShouldBeNil)

			conn := testio.NewBufferConn()
			conn.WritePeer(pub[:])

			aliceSession, err = Dial(conn, c)
			So(err, ShouldBeNil)

			var peer [64]byte
			_, err = conn.ReadClient(peer[:])
			So(err, ShouldBeNil)

			bobSession = &Session{
				recvKey: new([32]byte),
				sendKey: new([32]byte),
				Channel: testio.NewBufCloser(nil),
				Cipher:  c,
			}

			bobSession.KeyExchange(priv, &peer, false)
			aliceSession.Channel = bobSession.Channel
			err = aliceSession.Send(testMessage)
			So(err, ShouldBeNil)

			out, err := bobSession.Receive()
			So(err, ShouldBeNil)

			if !bytes.Equal(out, testMessage) {
				t.Fatal("recovered message doesn't match original")
			}

			if err = aliceSession.Send(nil); err == nil {
				t.Fatal("empty message should trigger an error")
			}

			aliceSession.Close()
			bobSession.Close()
		}
	})

}

var oldMessage []byte

func TestSessionListen(t *testing.T) {

	Convey("We can test session listening with all ciphers", t, func() {

		for _, c := range ciphers {
			pub, priv, err := GenerateKeyPair()
			So(err, ShouldBeNil)

			conn := testio.NewBufferConn()
			conn.WritePeer(pub[:])

			aliceSession, err = Listen(conn, c)
			So(err, ShouldBeNil)

			var peer [64]byte
			_, err = conn.ReadClient(peer[:])
			So(err, ShouldBeNil)

			bobSession = &Session{
				recvKey: new([32]byte),
				sendKey: new([32]byte),
				Channel: testio.NewBufCloser(nil),
				Cipher:  c,
			}

			bobSession.KeyExchange(priv, &peer, true)

			aliceSession.Channel = bobSession.Channel
			err = aliceSession.Send(testMessage)
			So(err, ShouldBeNil)

			out, err := bobSession.Receive()
			So(err, ShouldBeNil)

			So(bobSession.LastRecv(), ShouldBeGreaterThan, 0)
			So(aliceSession.LastSent(), ShouldBeGreaterThan, 0)
			// The NBA is always listening, on and off the court.
			oldMessage = out

			if !bytes.Equal(out, testMessage) {
				t.Fatal("recovered message doesn't match original")
			}

			for i := 0; i < 4; i++ {
				randMessage, err := generate.RandBytes(128)
				So(err, ShouldBeNil)

				err = aliceSession.Send(randMessage)
				So(err, ShouldBeNil)

				out, err = bobSession.Receive()
				So(err, ShouldBeNil)

				if !bytes.Equal(out, randMessage) {
					t.Fatal("recovered message doesn't match original")
				}
			}

			// NBA injects an old message into the channel. Damn those hoops!
			bobSession.Channel.Write(oldMessage)
			_, err = bobSession.Receive()
			So(err, ShouldNotBeNil)
		}
	})

}

func TestErrors(t *testing.T) {
	Convey("We can get appropriate errors", t, func() {
		_, err := NewCipher(NaCL, testComplexity)
		So(err, ShouldEqual, encerrors.ErrNoPadProvided)

		_, err = NewCipher(50, testComplexity)
		So(err, ShouldEqual, encerrors.ErrInvalidCipherKind)

		_, err = UnmarshalMessage([]byte{})
		So(err, ShouldEqual, encerrors.ErrInvalidMessageLength)

		c, err := NewCipher(GCM, testComplexity)
		So(err, ShouldBeNil)
		s := NewSession(testio.NewBufCloser(nil), c)

		_, err = s.Decrypt([]byte{})
		So(err, ShouldNotBeNil)

		msg := []byte("this is a message")
		k, err := generate.Key()
		So(err, ShouldBeNil)
		s.sendKey = k
		s.recvKey = k
		data, err := s.Encrypt(msg)
		So(err, ShouldBeNil)
		s.lastRecv = 40
		_, err = s.Decrypt(data)
		So(err, ShouldNotBeNil)

	})
}
<commit_msg>bolster test coverage a little bit<commit_after>package goenc

import (
	"bytes"
	"crypto/rand"
	"io/ioutil"
	"os"
	"testing"

	"fmt"

	"github.com/alistanis/goenc/encerrors"
	"github.com/alistanis/goenc/generate"
	"github.com/kisom/testio"
	. "github.com/smartystreets/goconvey/convey"
	"golang.org/x/crypto/nacl/box"
)

var (
	testMessage = []byte("do not go gentle into that good night")
	testSecured []byte

	aliceSession, bobSession *Session
	ciphers                  []*Cipher
)

func init() {
	cbc, err := NewCipher(CBC, testComplexity)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}

	cfb, err := NewCipher(CFB, testComplexity)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}
	ctr, err := NewCipher(CTR, testComplexity)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}

	gcm, err := NewCipher(GCM, testComplexity)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}

	nacl, err := NewCipher(NaCL, testComplexity, []byte("this is a pad to use for our key mwahahaha 123456789"))
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}
	ciphers = []*Cipher{cbc, cfb, ctr, gcm, nacl}
}

func TestFileIO(t *testing.T) {

	Convey("We can successfully perform file writing and reading using the block cipher interface functions", t, func() {
		bc, err := NewCipher(Mock, testComplexity)
		So(err, ShouldBeNil)
		d, err := ioutil.TempDir("/tmp", "")
		So(err, ShouldBeNil)
		defer os.RemoveAll(d)
		tf, err := ioutil.TempFile(d, "")
		So(err, ShouldBeNil)

		data := []byte("test data we'd like to 'encrypt' and save to file")
		key := []byte("test key which is meaningless")

		err = EncryptAndSave(bc, key, data, tf.Name())
		So(err, ShouldBeNil)

		err = tf.Close()
		So(err, ShouldBeNil)

		nd, err := ReadEncryptedFile(bc, key, tf.Name())
		So(err, ShouldBeNil)
		So(bytes.Equal(data, nd), ShouldBeTrue)
	})

}

func TestFileIOErrors(t *testing.T) {
	Convey("We can get errors on file io when we should", t, func() {
		c, err := NewCipher(GCM, testComplexity)
		So(err, ShouldBeNil)

		err = EncryptAndSave(c, []byte{}, []byte{}, "")
		So(err, ShouldNotBeNil)

		_, err = ReadEncryptedFile(c, []byte{}, "")
		So(err, ShouldNotBeNil)
	})
}

var (
	alicePub, alicePriv *[32]byte
	bobPub, bobPriv     *[32]byte
)

func TestGenerateKeys(t *testing.T) {
	var err error

	alicePub, alicePriv, err = box.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatalf("%v", err)
	}

	bobPub, bobPriv, err = box.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatalf("%v", err)
	}
}

func TestSessionSetup(t *testing.T) {

	Convey("We can test all ciphers with a session", t, func() {
		for _, c := range ciphers {

			pub, priv, err := GenerateKeyPair()
			So(err, ShouldBeNil)

			conn := testio.NewBufferConn()
			conn.WritePeer(pub[:])

			aliceSession, err = Dial(conn, c)
			So(err, ShouldBeNil)

			var peer [64]byte
			_, err = conn.ReadClient(peer[:])
			So(err, ShouldBeNil)

			bobSession = &Session{
				recvKey: new([32]byte),
				sendKey: new([32]byte),
				Channel: testio.NewBufCloser(nil),
				Cipher:  c,
			}

			bobSession.KeyExchange(priv, &peer, false)
			aliceSession.Channel = bobSession.Channel
			err = aliceSession.Send(testMessage)
			So(err, ShouldBeNil)

			out, err := bobSession.Receive()
			So(err, ShouldBeNil)

			if !bytes.Equal(out, testMessage) {
				t.Fatal("recovered message doesn't match original")
			}

			if err = aliceSession.Send(nil); err == nil {
				t.Fatal("empty message should trigger an error")
			}

			aliceSession.Close()
			bobSession.Close()
		}
	})

}

var oldMessage []byte

func TestSessionListen(t *testing.T) {

	Convey("We can test session listening with all ciphers", t, func() {

		for _, c := range ciphers {
			pub, priv, err := GenerateKeyPair()
			So(err, ShouldBeNil)

			conn := testio.NewBufferConn()
			conn.WritePeer(pub[:])

			aliceSession, err = Listen(conn, c)
			So(err, ShouldBeNil)

			var peer [64]byte
			_, err = conn.ReadClient(peer[:])
			So(err, ShouldBeNil)

			bobSession = &Session{
				recvKey: new([32]byte),
				sendKey: new([32]byte),
				Channel: testio.NewBufCloser(nil),
				Cipher:  c,
			}

			bobSession.KeyExchange(priv, &peer, true)

			aliceSession.Channel = bobSession.Channel
			err = aliceSession.Send(testMessage)
			So(err, ShouldBeNil)

			out, err := bobSession.Receive()
			So(err, ShouldBeNil)

			So(bobSession.LastRecv(), ShouldBeGreaterThan, 0)
			So(aliceSession.LastSent(), ShouldBeGreaterThan, 0)
			// The NBA is always listening, on and off the court.
			oldMessage = out

			if !bytes.Equal(out, testMessage) {
				t.Fatal("recovered message doesn't match original")
			}

			for i := 0; i < 4; i++ {
				randMessage, err := generate.RandBytes(128)
				So(err, ShouldBeNil)

				err = aliceSession.Send(randMessage)
				So(err, ShouldBeNil)

				out, err = bobSession.Receive()
				So(err, ShouldBeNil)

				if !bytes.Equal(out, randMessage) {
					t.Fatal("recovered message doesn't match original")
				}
			}

			// NBA injects an old message into the channel. Damn those hoops!
			bobSession.Channel.Write(oldMessage)
			_, err = bobSession.Receive()
			So(err, ShouldNotBeNil)
		}
	})

}

func TestErrors(t *testing.T) {
	Convey("We can get appropriate errors", t, func() {
		_, err := NewCipher(NaCL, testComplexity)
		So(err, ShouldEqual, encerrors.ErrNoPadProvided)

		_, err = NewCipher(50, testComplexity)
		So(err, ShouldEqual, encerrors.ErrInvalidCipherKind)

		_, err = UnmarshalMessage([]byte{})
		So(err, ShouldEqual, encerrors.ErrInvalidMessageLength)

		c, err := NewCipher(GCM, testComplexity)
		So(err, ShouldBeNil)
		s := NewSession(testio.NewBufCloser(nil), c)

		_, err = s.Decrypt([]byte{})
		So(err, ShouldNotBeNil)

		msg := []byte("this is a message")
		k, err := generate.Key()
		So(err, ShouldBeNil)
		s.sendKey = k
		s.recvKey = k
		data, err := s.Encrypt(msg)
		So(err, ShouldBeNil)
		s.lastRecv = 40
		_, err = s.Decrypt(data)
		So(err, ShouldNotBeNil)

		_, err = DeriveKey([]byte{}, []byte{}, 0, 1)
		So(err, ShouldNotBeNil)
	})
}
<|endoftext|>


<commit_before>package aqua

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/carbocation/interpose"
	"github.com/gorilla/mux"
	"github.com/tolexo/aero/activity"
	"github.com/tolexo/aero/auth"
	"github.com/tolexo/aero/cache"
	"github.com/tolexo/aero/conf"
	monit "github.com/tolexo/aero/monit"
	"github.com/tolexo/aero/panik"
)

type endPoint struct {
	caller     MethodInvoker
	info       Fixture
	httpMethod string

	isStdHttpHandler bool
	needsJarInput    bool

	muxUrl    string
	muxVars   []string
	modules   []func(http.Handler) http.Handler
	stash     cache.Cacher
	serviceId string
}

func NewEndPoint(inv MethodInvoker, f Fixture, matchUrl string, httpMethod string, mods map[string]func(http.Handler) http.Handler,
	caches map[string]cache.Cacher, serviceId string) endPoint {

	out := endPoint{
		caller:           inv,
		info:             f,
		isStdHttpHandler: false,
		needsJarInput:    false,
		muxUrl:           matchUrl,
		muxVars:          extractRouteVars(matchUrl),
		httpMethod:       httpMethod,
		modules:          make([]func(http.Handler) http.Handler, 0),
		stash:            nil,
		serviceId:        serviceId,
	}

	if f.Stub == "" {
		out.isStdHttpHandler = out.signatureMatchesDefaultHttpHandler()
		out.needsJarInput = out.needsVariableJar()

		out.validateMuxVarsMatchFuncInputs()
		out.validateFuncInputsAreOfRightType()
		out.validateFuncOutputsAreCorrect()
	}

	// Tag modules used by this endpoint
	if mods != nil && f.Modules != "" {
		names := strings.Split(f.Modules, ",")
		out.modules = make([]func(http.Handler) http.Handler, 0)
		for _, name := range names {
			name = strings.TrimSpace(name)
			fn, found := mods[name]
			if !found {
				panic(fmt.Sprintf("Module:%s not found", name))
			}
			out.modules = append(out.modules, fn)
		}
	}

	// Tag the cache
	if c, ok := caches[f.Cache]; ok {
		out.stash = c
	} else if f.Cache != "" {
		panic("Cache not found: " + f.Cache + " for " + matchUrl)
	}

	return out
}

func (me *endPoint) signatureMatchesDefaultHttpHandler() bool {
	return me.caller.outCount == 0 &&
		me.caller.inpCount == 2 &&
		me.caller.inpParams[0] == "i:net/http.ResponseWriter" &&
		me.caller.inpParams[1] == "*st:net/http.Request"
}

func (me *endPoint) needsVariableJar() bool {
	// needs jar input as the last parameter
	for i := 0; i < len(me.caller.inpParams)-1; i++ {
		if me.caller.inpParams[i] == "st:github.com/tolexo/aqua.Jar" {
			panic("Jar parameter should be the last one: " + me.caller.name)
		}
	}
	return me.caller.inpCount > 0 && me.caller.inpParams[me.caller.inpCount-1] == "st:github.com/tolexo/aqua.Jar"
}

func (me *endPoint) validateMuxVarsMatchFuncInputs() {
	// for non-standard http handlers, the mux vars count should match
	// the count of inputs to the user's method
	if !me.isStdHttpHandler {
		inputs := me.caller.inpCount
		if me.needsJarInput {
			inputs += -1
		}
		if len(me.muxVars) != inputs {
			panic(fmt.Sprintf("%s has %d inputs, but the func (%s) has %d",
				me.muxUrl, len(me.muxVars), me.caller.name, inputs))
		}
	}
}

func (me *endPoint) validateFuncInputsAreOfRightType() {
	if !me.isStdHttpHandler {
		for _, s := range me.caller.inpParams {
			switch s {
			case "st:github.com/tolexo/aqua.Jar":
			case "int":
			case "string":
			default:
				panic("Func input params should be 'int' or 'string'. Observed: " + s + " in: " + me.caller.name)
			}
		}
	}
}

func (me *endPoint) validateFuncOutputsAreCorrect() {

	var accepts = make(map[string]bool)
	accepts["string"] = true
	accepts["map"] = true
	accepts["st:github.com/tolexo/aqua.Sac"] = true
	accepts["*st:github.com/tolexo/aqua.Sac"] = true

	if !me.isStdHttpHandler {
		switch me.caller.outCount {
		case 1:
			_, found := accepts[me.caller.outParams[0]]
			if !found && !strings.HasPrefix(me.caller.outParams[0], "st:") {
				fmt.Println(me.caller.outParams[0])
				panic("Incorrect return type found in: " + me.caller.name)
			}
		case 2:
			if me.caller.outParams[0] != "int" {
				panic("When a func returns two params, the first must be an int (http status code) : " + me.caller.name)
			}
			_, found := accepts[me.caller.outParams[1]]
			if !found && !strings.HasPrefix(me.caller.outParams[1], "st:") {
				panic("Incorrect return type for second return param found in: " + me.caller.name)
			}
		default:
			panic("Incorrect number of returns for Func: " + me.caller.name)
		}
	}
}

// func middleman(next http.Handler) http.Handler {
// 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 		fmt.Println("In the middle >>>>")
// 		next.ServeHTTP(w, r)
// 		fmt.Println("And leaving middle <<<<")
// 	})
// }

func (me *endPoint) setupMuxHandlers(mux *mux.Router) {

	fn := handleIncoming(me)

	m := interpose.New()
	for i, _ := range me.modules {
		m.Use(me.modules[i])
		//fmt.Println("using module:", me.modules[i], reflect.TypeOf(me.modules[i]))
	}
	m.UseHandler(http.HandlerFunc(fn))

	if me.info.Version == "*" {
		mux.Handle(me.muxUrl, m).Methods(me.httpMethod)
	} else {
		urlWithVersion := cleanUrl(me.info.Prefix, "v"+me.info.Version, me.muxUrl)
		urlWithoutVersion := cleanUrl(me.info.Prefix, me.muxUrl)

		// versioned url
		mux.Handle(urlWithVersion, m).Methods(me.httpMethod)

		// content type (style1)
		header1 := fmt.Sprintf("application/%s-v%s+json", me.info.Vendor, me.info.Version)
		mux.Handle(urlWithoutVersion, m).Methods(me.httpMethod).Headers("Accept", header1)

		// content type (style2)
		header2 := fmt.Sprintf("application/%s+json;version=%s", me.info.Vendor, me.info.Version)
		mux.Handle(urlWithoutVersion, m).Methods(me.httpMethod).Headers("Accept", header2)
	}
}

//Copy request body
func copyReqBody(reqBody io.ReadCloser) (originalBody io.ReadCloser, copyBody interface{}) {
	bodyByte, _ := ioutil.ReadAll(reqBody)
	if err := json.Unmarshal(bodyByte, &copyBody); err != nil {
		copyBody = string(bodyByte)
	}
	originalBody = ioutil.NopCloser(bytes.NewBuffer(bodyByte))
	return
}

func handleIncoming(e *endPoint) func(http.ResponseWriter, *http.Request) {

	// return stub
	if e.info.Stub != "" {
		return func(w http.ResponseWriter, r *http.Request) {
			d, err := getContent(e.info.Stub)
			if err == nil {
				fmt.Fprintf(w, "%s", d)
			} else {
				w.WriteHeader(400)
				fmt.Fprintf(w, "{ message: \"%s\"}", "Stub path not found")
			}
		}
	}

	return func(w http.ResponseWriter, r *http.Request) {

		// cacheHit := false

		// TODO: create less local variables
		// TODO: move vars to closure level

		var out []reflect.Value
		//TODO: capture this using instrumentation handler

		var body interface{}
		logActivity := conf.Bool("log_activity", false) && e.info.Log
		if logActivity == true {
			r.Body, body = copyReqBody(r.Body)
		}
		defer func(reqStartTime time.Time) {
			var (
				response     interface{}
				responseCode int64 = 200
			)
			respTime := time.Since(reqStartTime).Seconds() * 1000
			if out != nil && len(out) == 2 && e.caller.outParams[0] == "int" {
				responseCode = out[0].Int()
			}
			/*
				go func() {
					if e.serviceId != "" {
						monitorParams := monit.MonitorParams{
							ServiceId:    e.serviceId,
							RespTime:     respTime,
							ResponseCode: responseCode,
							CacheHit:     cacheHit,
						}
						monit.MonitorMe(monitorParams)
					}
				}()
			*/

			//User Activity logger start
			if logActivity == true {
				if out != nil {
					outLen := len(out)
					if outLen > 1 {
						response = out[1].Interface()
					} else if outLen > 0 {
						response = out[0].Interface()
					}
				}
				activity.LogActivity(r.RequestURI+" "+e.serviceId, body, response,
					int(responseCode), respTime)
			}
			//User Activity logger end

			if reqR := recover(); reqR != nil {
				monit.PanicLogger(reqR, e.serviceId, r.RequestURI, time.Now())
			}
		}(time.Now())

		//check authentication
		if e.info.Auth != "" {
			ok, errMsg := auth.AuthenticateRequest(r, e.info.Auth)
			if !ok { //print authentication error
				w.WriteHeader(401)
				w.Header().Set("Content-Type", "application/json")
				w.Header().Set("Content-Length", strconv.Itoa(len(errMsg)))
				fmt.Fprintf(w, "%s", errMsg)
				return
			}
		}

		var useCache bool = false
		var ttl time.Duration = 0 * time.Second
		var val []byte
		var err error

		if e.info.Ttl != "" {
			ttl, err = time.ParseDuration(e.info.Ttl)
			panik.On(err)
		}
		useCache = r.Method == "GET" && ttl > 0 && e.stash != nil

		muxVals := mux.Vars(r)
		params := make([]string, len(e.muxVars))
		for i, v := range e.muxVars {
			params[i] = muxVals[v]
		}

		if e.isStdHttpHandler {
			//TODO: caching of standard handler
			e.caller.Do([]reflect.Value{reflect.ValueOf(w), reflect.ValueOf(r)})
		} else {
			ref := convertToType(params, e.caller.inpParams)
			if e.needsJarInput {
				ref = append(ref, reflect.ValueOf(NewJar(r)))
			}

			if useCache {
				val, err = e.stash.Get(r.RequestURI)
				if err == nil {
					// cacheHit = true
					// fmt.Print(".")
					out = decomposeCachedValues(val, e.caller.outParams)
				} else {
					out = e.caller.Do(ref)
					if len(out) == 2 && e.caller.outParams[0] == "int" {
						code := out[0].Int()
						if code < 200 || code > 299 {
							useCache = false
						}
					}
					if useCache {
						bytes := prepareForCaching(out, e.caller.outParams)
						e.stash.Set(r.RequestURI, bytes, ttl)
						// fmt.Print(":", len(bytes), r.RequestURI)
					}

				}
			} else {
				out = e.caller.Do(ref)
				// fmt.Print("!")
			}
			writeOutput(w, e.caller.outParams, out, e.info.Pretty)
		}
	}
}

func prepareForCaching(r []reflect.Value, outputParams []string) []byte {

	var err error
	buf := new(bytes.Buffer)
	encd := json.NewEncoder(buf)

	for i, _ := range r {
		switch outputParams[i] {
		case "int":
			err = encd.Encode(r[i].Int())
			panik.On(err)
		case "map":
			err = encd.Encode(r[i].Interface().(map[string]interface{}))
			panik.On(err)
		case "string":
			err = encd.Encode(r[i].String())
			panik.On(err)
		case "*st:github.com/tolexo/aqua.Sac":
			err = encd.Encode(r[i].Elem().Interface().(Sac).Data)
		default:
			panic("Unknown type of output to be sent to endpoint cache: " + outputParams[i])
		}
	}

	return buf.Bytes()
}

func decomposeCachedValues(data []byte, outputParams []string) []reflect.Value {

	var err error
	buf := bytes.NewBuffer(data)
	decd := json.NewDecoder(buf)
	out := make([]reflect.Value, len(outputParams))

	for i, o := range outputParams {
		switch o {
		case "int":
			var j int
			err = decd.Decode(&j)
			panik.On(err)
			out[i] = reflect.ValueOf(j)
		case "map":
			var m map[string]interface{}
			err = decd.Decode(&m)
			panik.On(err)
			out[i] = reflect.ValueOf(m)
		case "string":
			var s string
			err = decd.Decode(&s)
			panik.On(err)
			out[i] = reflect.ValueOf(s)
		case "*st:github.com/tolexo/aqua.Sac":
			var m map[string]interface{}
			err = decd.Decode(&m)
			panik.On(err)
			s := NewSac()
			s.Data = m
			out[i] = reflect.ValueOf(s)
		default:
			panic("Unknown type of output to be decoded from endpoint cache:" + o)
		}
	}

	return out

}
<commit_msg>PRA-410A: service id added<commit_after>package aqua

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/carbocation/interpose"
	"github.com/gorilla/mux"
	"github.com/tolexo/aero/activity"
	"github.com/tolexo/aero/auth"
	"github.com/tolexo/aero/cache"
	"github.com/tolexo/aero/conf"
	monit "github.com/tolexo/aero/monit"
	"github.com/tolexo/aero/panik"
)

type endPoint struct {
	caller     MethodInvoker
	info       Fixture
	httpMethod string

	isStdHttpHandler bool
	needsJarInput    bool

	muxUrl    string
	muxVars   []string
	modules   []func(http.Handler) http.Handler
	stash     cache.Cacher
	serviceId string
}

func NewEndPoint(inv MethodInvoker, f Fixture, matchUrl string, httpMethod string, mods map[string]func(http.Handler) http.Handler,
	caches map[string]cache.Cacher, serviceId string) endPoint {

	out := endPoint{
		caller:           inv,
		info:             f,
		isStdHttpHandler: false,
		needsJarInput:    false,
		muxUrl:           matchUrl,
		muxVars:          extractRouteVars(matchUrl),
		httpMethod:       httpMethod,
		modules:          make([]func(http.Handler) http.Handler, 0),
		stash:            nil,
		serviceId:        serviceId,
	}

	if f.Stub == "" {
		out.isStdHttpHandler = out.signatureMatchesDefaultHttpHandler()
		out.needsJarInput = out.needsVariableJar()

		out.validateMuxVarsMatchFuncInputs()
		out.validateFuncInputsAreOfRightType()
		out.validateFuncOutputsAreCorrect()
	}

	// Tag modules used by this endpoint
	if mods != nil && f.Modules != "" {
		names := strings.Split(f.Modules, ",")
		out.modules = make([]func(http.Handler) http.Handler, 0)
		for _, name := range names {
			name = strings.TrimSpace(name)
			fn, found := mods[name]
			if !found {
				panic(fmt.Sprintf("Module:%s not found", name))
			}
			out.modules = append(out.modules, fn)
		}
	}

	// Tag the cache
	if c, ok := caches[f.Cache]; ok {
		out.stash = c
	} else if f.Cache != "" {
		panic("Cache not found: " + f.Cache + " for " + matchUrl)
	}

	return out
}

func (me *endPoint) signatureMatchesDefaultHttpHandler() bool {
	return me.caller.outCount == 0 &&
		me.caller.inpCount == 2 &&
		me.caller.inpParams[0] == "i:net/http.ResponseWriter" &&
		me.caller.inpParams[1] == "*st:net/http.Request"
}

func (me *endPoint) needsVariableJar() bool {
	// needs jar input as the last parameter
	for i := 0; i < len(me.caller.inpParams)-1; i++ {
		if me.caller.inpParams[i] == "st:github.com/tolexo/aqua.Jar" {
			panic("Jar parameter should be the last one: " + me.caller.name)
		}
	}
	return me.caller.inpCount > 0 && me.caller.inpParams[me.caller.inpCount-1] == "st:github.com/tolexo/aqua.Jar"
}

func (me *endPoint) validateMuxVarsMatchFuncInputs() {
	// for non-standard http handlers, the mux vars count should match
	// the count of inputs to the user's method
	if !me.isStdHttpHandler {
		inputs := me.caller.inpCount
		if me.needsJarInput {
			inputs += -1
		}
		if len(me.muxVars) != inputs {
			panic(fmt.Sprintf("%s has %d inputs, but the func (%s) has %d",
				me.muxUrl, len(me.muxVars), me.caller.name, inputs))
		}
	}
}

func (me *endPoint) validateFuncInputsAreOfRightType() {
	if !me.isStdHttpHandler {
		for _, s := range me.caller.inpParams {
			switch s {
			case "st:github.com/tolexo/aqua.Jar":
			case "int":
			case "string":
			default:
				panic("Func input params should be 'int' or 'string'. Observed: " + s + " in: " + me.caller.name)
			}
		}
	}
}

func (me *endPoint) validateFuncOutputsAreCorrect() {

	var accepts = make(map[string]bool)
	accepts["string"] = true
	accepts["map"] = true
	accepts["st:github.com/tolexo/aqua.Sac"] = true
	accepts["*st:github.com/tolexo/aqua.Sac"] = true

	if !me.isStdHttpHandler {
		switch me.caller.outCount {
		case 1:
			_, found := accepts[me.caller.outParams[0]]
			if !found && !strings.HasPrefix(me.caller.outParams[0], "st:") {
				fmt.Println(me.caller.outParams[0])
				panic("Incorrect return type found in: " + me.caller.name)
			}
		case 2:
			if me.caller.outParams[0] != "int" {
				panic("When a func returns two params, the first must be an int (http status code) : " + me.caller.name)
			}
			_, found := accepts[me.caller.outParams[1]]
			if !found && !strings.HasPrefix(me.caller.outParams[1], "st:") {
				panic("Incorrect return type for second return param found in: " + me.caller.name)
			}
		default:
			panic("Incorrect number of returns for Func: " + me.caller.name)
		}
	}
}

// func middleman(next http.Handler) http.Handler {
// 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 		fmt.Println("In the middle >>>>")
// 		next.ServeHTTP(w, r)
// 		fmt.Println("And leaving middle <<<<")
// 	})
// }

func (me *endPoint) setupMuxHandlers(mux *mux.Router) {

	fn := handleIncoming(me)

	m := interpose.New()
	for i, _ := range me.modules {
		m.Use(me.modules[i])
		//fmt.Println("using module:", me.modules[i], reflect.TypeOf(me.modules[i]))
	}
	m.UseHandler(http.HandlerFunc(fn))

	if me.info.Version == "*" {
		mux.Handle(me.muxUrl, m).Methods(me.httpMethod)
	} else {
		urlWithVersion := cleanUrl(me.info.Prefix, "v"+me.info.Version, me.muxUrl)
		urlWithoutVersion := cleanUrl(me.info.Prefix, me.muxUrl)

		// versioned url
		mux.Handle(urlWithVersion, m).Methods(me.httpMethod)

		// content type (style1)
		header1 := fmt.Sprintf("application/%s-v%s+json", me.info.Vendor, me.info.Version)
		mux.Handle(urlWithoutVersion, m).Methods(me.httpMethod).Headers("Accept", header1)

		// content type (style2)
		header2 := fmt.Sprintf("application/%s+json;version=%s", me.info.Vendor, me.info.Version)
		mux.Handle(urlWithoutVersion, m).Methods(me.httpMethod).Headers("Accept", header2)
	}
}

//Copy request body
func copyReqBody(reqBody io.ReadCloser) (originalBody io.ReadCloser, copyBody interface{}) {
	bodyByte, _ := ioutil.ReadAll(reqBody)
	if err := json.Unmarshal(bodyByte, &copyBody); err != nil {
		copyBody = string(bodyByte)
	}
	originalBody = ioutil.NopCloser(bytes.NewBuffer(bodyByte))
	return
}

func handleIncoming(e *endPoint) func(http.ResponseWriter, *http.Request) {

	// return stub
	if e.info.Stub != "" {
		return func(w http.ResponseWriter, r *http.Request) {
			d, err := getContent(e.info.Stub)
			if err == nil {
				fmt.Fprintf(w, "%s", d)
			} else {
				w.WriteHeader(400)
				fmt.Fprintf(w, "{ message: \"%s\"}", "Stub path not found")
			}
		}
	}

	return func(w http.ResponseWriter, r *http.Request) {

		// cacheHit := false

		// TODO: create less local variables
		// TODO: move vars to closure level

		var out []reflect.Value
		//TODO: capture this using instrumentation handler

		var body interface{}
		logActivity := conf.Bool("log_activity", false) && e.info.Log
		if logActivity == true {
			r.Body, body = copyReqBody(r.Body)
		}
		defer func(reqStartTime time.Time) {
			var (
				response     interface{}
				responseCode int64 = 200
			)
			respTime := time.Since(reqStartTime).Seconds() * 1000
			if out != nil && len(out) == 2 && e.caller.outParams[0] == "int" {
				responseCode = out[0].Int()
			}
			/*
				go func() {
					if e.serviceId != "" {
						monitorParams := monit.MonitorParams{
							ServiceId:    e.serviceId,
							RespTime:     respTime,
							ResponseCode: responseCode,
							CacheHit:     cacheHit,
						}
						monit.MonitorMe(monitorParams)
					}
				}()
			*/

			//User Activity logger start
			if logActivity == true {
				if out != nil {
					outLen := len(out)
					if outLen > 1 {
						response = out[1].Interface()
					} else if outLen > 0 {
						response = out[0].Interface()
					}
				}
				activity.LogActivity(r.RequestURI, e.serviceId, body, response,
					int(responseCode), respTime)
			}
			//User Activity logger end

			if reqR := recover(); reqR != nil {
				monit.PanicLogger(reqR, e.serviceId, r.RequestURI, time.Now())
			}
		}(time.Now())

		//check authentication
		if e.info.Auth != "" {
			ok, errMsg := auth.AuthenticateRequest(r, e.info.Auth)
			if !ok { //print authentication error
				w.WriteHeader(401)
				w.Header().Set("Content-Type", "application/json")
				w.Header().Set("Content-Length", strconv.Itoa(len(errMsg)))
				fmt.Fprintf(w, "%s", errMsg)
				return
			}
		}

		var useCache bool = false
		var ttl time.Duration = 0 * time.Second
		var val []byte
		var err error

		if e.info.Ttl != "" {
			ttl, err = time.ParseDuration(e.info.Ttl)
			panik.On(err)
		}
		useCache = r.Method == "GET" && ttl > 0 && e.stash != nil

		muxVals := mux.Vars(r)
		params := make([]string, len(e.muxVars))
		for i, v := range e.muxVars {
			params[i] = muxVals[v]
		}

		if e.isStdHttpHandler {
			//TODO: caching of standard handler
			e.caller.Do([]reflect.Value{reflect.ValueOf(w), reflect.ValueOf(r)})
		} else {
			ref := convertToType(params, e.caller.inpParams)
			if e.needsJarInput {
				ref = append(ref, reflect.ValueOf(NewJar(r)))
			}

			if useCache {
				val, err = e.stash.Get(r.RequestURI)
				if err == nil {
					// cacheHit = true
					// fmt.Print(".")
					out = decomposeCachedValues(val, e.caller.outParams)
				} else {
					out = e.caller.Do(ref)
					if len(out) == 2 && e.caller.outParams[0] == "int" {
						code := out[0].Int()
						if code < 200 || code > 299 {
							useCache = false
						}
					}
					if useCache {
						bytes := prepareForCaching(out, e.caller.outParams)
						e.stash.Set(r.RequestURI, bytes, ttl)
						// fmt.Print(":", len(bytes), r.RequestURI)
					}

				}
			} else {
				out = e.caller.Do(ref)
				// fmt.Print("!")
			}
			writeOutput(w, e.caller.outParams, out, e.info.Pretty)
		}
	}
}

func prepareForCaching(r []reflect.Value, outputParams []string) []byte {

	var err error
	buf := new(bytes.Buffer)
	encd := json.NewEncoder(buf)

	for i, _ := range r {
		switch outputParams[i] {
		case "int":
			err = encd.Encode(r[i].Int())
			panik.On(err)
		case "map":
			err = encd.Encode(r[i].Interface().(map[string]interface{}))
			panik.On(err)
		case "string":
			err = encd.Encode(r[i].String())
			panik.On(err)
		case "*st:github.com/tolexo/aqua.Sac":
			err = encd.Encode(r[i].Elem().Interface().(Sac).Data)
		default:
			panic("Unknown type of output to be sent to endpoint cache: " + outputParams[i])
		}
	}

	return buf.Bytes()
}

func decomposeCachedValues(data []byte, outputParams []string) []reflect.Value {

	var err error
	buf := bytes.NewBuffer(data)
	decd := json.NewDecoder(buf)
	out := make([]reflect.Value, len(outputParams))

	for i, o := range outputParams {
		switch o {
		case "int":
			var j int
			err = decd.Decode(&j)
			panik.On(err)
			out[i] = reflect.ValueOf(j)
		case "map":
			var m map[string]interface{}
			err = decd.Decode(&m)
			panik.On(err)
			out[i] = reflect.ValueOf(m)
		case "string":
			var s string
			err = decd.Decode(&s)
			panik.On(err)
			out[i] = reflect.ValueOf(s)
		case "*st:github.com/tolexo/aqua.Sac":
			var m map[string]interface{}
			err = decd.Decode(&m)
			panik.On(err)
			s := NewSac()
			s.Data = m
			out[i] = reflect.ValueOf(s)
		default:
			panic("Unknown type of output to be decoded from endpoint cache:" + o)
		}
	}

	return out

}
<|endoftext|>


<commit_before>package httpie

import (
    "net/url"
    "net/http"
    "io/ioutil"
    "bytes"
)

type Endpoint interface {
    ApplyTo(*http.Request)
}

type Get struct {
    *url.URL
}

func (g Get) ApplyTo(req *http.Request) {
    req.Method = "GET"
    req.URL    = g.URL
}

type Post struct {
    *url.URL
    Body []byte
    ContentType string
}

func (p Post) ApplyTo(req *http.Request) {
    req.Method        = "POST"
    req.URL           = p.URL
    req.Body          = ioutil.NopCloser(bytes.NewBuffer(p.Body))
    req.ContentLength = int64(len(p.Body))
    req.Header.Set("Content-Type", p.ContentType)
}

type Put struct {
    *url.URL
    Body []byte
    ContentType string
}

func (p Put) ApplyTo(req *http.Request) {
    req.Method        = "PUT"
    req.URL           = p.URL
    req.Body          = ioutil.NopCloser(bytes.NewBuffer(p.Body))
    req.ContentLength = int64(len(p.Body))
    req.Header.Set("Content-Type", p.ContentType)
}

type Delete struct {
    *url.URL
}

func (d Delete) ApplyTo(req *http.Request) {
    req.Method = "DELETE"
    req.URL    = d.URL
}
<commit_msg>Endpoint docs<commit_after>package httpie

import (
    "net/url"
    "net/http"
    "io/ioutil"
    "bytes"
)

// Endpoint is implemented to provide delayed
// attachment of the URL/Method/Body of a request
type Endpoint interface {
    ApplyTo(*http.Request)
}

// Represents an HTTP GET
type Get struct {
    *url.URL
}

// ApplyTo sets the requests Method to GET and URL
func (g Get) ApplyTo(req *http.Request) {
    req.Method = "GET"
    req.URL    = g.URL
}

// Represents an HTTP POST
type Post struct {
    *url.URL
    Body []byte
    ContentType string
}

// ApplyTo sets the requests Method to POST, URL and Body
func (p Post) ApplyTo(req *http.Request) {
    req.Method        = "POST"
    req.URL           = p.URL
    req.Body          = ioutil.NopCloser(bytes.NewBuffer(p.Body))
    req.ContentLength = int64(len(p.Body))
    req.Header.Set("Content-Type", p.ContentType)
}

// Represents an HTTP PUT
type Put struct {
    *url.URL
    Body []byte
    ContentType string
}

// ApplyTo sets the requests Method to PUT, URL and Body
func (p Put) ApplyTo(req *http.Request) {
    req.Method        = "PUT"
    req.URL           = p.URL
    req.Body          = ioutil.NopCloser(bytes.NewBuffer(p.Body))
    req.ContentLength = int64(len(p.Body))
    req.Header.Set("Content-Type", p.ContentType)
}

// Represents an HTTP DELETE
type Delete struct {
    *url.URL
}

// ApplyTo sets the requests Method to DELETE and URL
func (d Delete) ApplyTo(req *http.Request) {
    req.Method = "DELETE"
    req.URL    = d.URL
}
<|endoftext|>


<commit_before>package main

import (
    "flag"
    "fmt"
    "log"
    "net/http"
    "strings"
)

func handler(w http.ResponseWriter, r *http.Request) {
    msg := "Request " + r.URL.Path
    for k, v := range r.Header {
        msg += ";\n\t" + k + ": " + strings.Join(v, ",")
    }

    // Copy the request body
    body_part := make([]byte, 20)
    var body string
    n, err := r.Body.Read(body_part)
    if err != nil {
        body = "<Empty>"
    } else {
        body = string(body_part[:n])
    }

    msg += "\nBody: '" + body + "'"

    fmt.Fprintf(w, msg)
}

func main() {
    var port = flag.String("port", ":8090", "Port to listen on")
    flag.Parse()

    log.Println("Listening on", *port)

    http.HandleFunc("/", handler)
    http.ListenAndServe(*port, nil)
}

<commit_msg>Improve logging and reponse in endpoint.<commit_after>package main

import (
    "flag"
    "fmt"
    "log"
    "net/http"
    "strings"
)

func handler(w http.ResponseWriter, r *http.Request) {
    log.Println("Incoming request for", r.URL.Path)

    msg := "Request:" + r.URL.Path
    msg += "\nHeaders:"
    for k, v := range r.Header {
        msg += k + ": " + strings.Join(v, ",") + ";"
    }

    // Copy the request body
    body_part := make([]byte, 20)
    var body string
    n, err := r.Body.Read(body_part)
    if err != nil {
        body = "<Empty>"
    } else {
        body = string(body_part[:n])
    }

    msg += "\nBody:" + body

    fmt.Fprintf(w, msg)
}

func main() {
    var port = flag.String("port", ":8090", "Port to listen on")
    flag.Parse()

    log.Println("Listening on", *port)

    http.HandleFunc("/", handler)
    http.ListenAndServe(*port, nil)
}

<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/codegangsta/cli"
	"github.com/gongo/go-airplay"
)

// Define commands
var Commands = []cli.Command{
	{
		Name:  "play",
		Usage: "Play media file(Movie, Music)",
		Action: func(c *cli.Context) {
			client, err := airplay.NewClient()
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			ch := client.Play(source(c.Args().First()))
			<-ch
		},
	},
	{
		Name:  "devices",
		Usage: "Show AirPlay devices",
		Action: func(c *cli.Context) {
			for _, d := range Devices() {
				fmt.Println(d.Name)
			}
		},
	},
}
<commit_msg>Use Playlist<commit_after>package main

import (
	"fmt"
	"log"
	"os"

	"github.com/codegangsta/cli"
	"github.com/gongo/go-airplay"
)

// Define commands
var Commands = []cli.Command{
	{
		Name:  "play",
		Usage: "Play media file(Movie, Music)",
		Action: func(c *cli.Context) {
			target := c.Args().First()
			playlist := NewPlaylist()
			err := playlist.Add(target)
			if err != nil {
				log.Fatal(err)
				os.Exit(1)
			}

			client, err := airplay.NewClient()
			if err != nil {
				log.Fatal(err)
				os.Exit(1)
			}
			for _, media := range playlist.Entries {
				fmt.Println(media.Path)
				ch := client.Play(source(media.Path))
				<-ch
			}
		},
	},
	{
		Name:  "devices",
		Usage: "Show AirPlay devices",
		Action: func(c *cli.Context) {
			devices, err := Devices()
			if err != nil {
				log.Fatal(err)
				os.Exit(1)
			}
			for _, d := range devices {
				fmt.Println(d.Name)
			}
		},
	},
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Stuart Glenn
// All rights reserved
// Use of this source code is goverened by a BSD 3-clause license,
// see included LICENSE file for details
// Datastore for perstiance of access rules, clients & keys
package atm

import (
	"database/sql"
	"errors"
	"fmt"
)

type Datastore struct {
	pool        *sql.DB
	signingKeys Cache
}

type Account struct {
	Id   string `json:id`
	Name string `json:name`
}

func NewDatastore(driver, dsn string) (*Datastore, error) {
	ds := &Datastore{}
	var err error
	ds.pool, err = sql.Open(driver, dsn)
	if nil != err {
		return nil, err
	}
	err = ds.Ping()
	if nil != err {
		return nil, err
	}
	ds.signingKeys = NewCache()
	return ds, nil
}

func (d *Datastore) Ping() error {
	return d.pool.Ping()
}

func (d *Datastore) Close() error {
	return d.pool.Close()
}

func (d *Datastore) AddSigningKeyForAccount(key, account string) {
	d.signingKeys.Set(account, key)
}

func (d *Datastore) signingKeyFor(account string) string {
	return d.signingKeys.Get(account)
}

func (d *Datastore) Account(name string) (*Account, error) {
	a := &Account{}
	stmt, err := d.pool.Prepare("SELECT id, name from accounts where name = ?")
	if nil != err {
		return a, err
	}
	defer stmt.Close()
	rows, err := stmt.Query(name)
	if nil != err {
		return a, err
	}
	defer rows.Close()
	numRows := 0
	for rows.Next() {
		if numRows > 1 {
			return a, errors.New("Too many results")
		}
		err := rows.Scan(&a.Id, &a.Name)
		if nil != err {
			return a, err
		}
		err = rows.Err()
		if nil != err {
			return a, err
		}
		numRows++
	}
	return a, nil
}

func (d *Datastore) KeyForRequest(u *UrlRequest, appId string) (string, int64, error) {
	var signing_key string
	var duration int64
	stmt, err := d.pool.Prepare("SELECT a.id, r.duration as duration from accounts a, rules r " +
		"WHERE r.account_id=a.id AND requestor_id = ? AND a.name = ? AND " +
		"? REGEXP r.container AND ? REGEXP r.object AND r.method = ?")
	if nil != err {
		return signing_key, duration, err
	}
	defer stmt.Close()
	rows, err := stmt.Query(appId, u.Account, u.Container, u.Object, u.Method)
	if nil != err {
		return signing_key, duration, err
	}
	defer rows.Close()
	numRows := 0
	var grantingAccountId string
	for rows.Next() {
		if numRows > 1 {
			return signing_key, duration, errors.New("Too many results")
		}
		err := rows.Scan(&grantingAccountId, &duration)
		if nil != err {
			return signing_key, duration, err
		}
		err = rows.Err()
		if nil != err {
			return signing_key, duration, err
		}
		numRows++
	}
	if 0 == numRows {
		return "", 0, nil
	}

	signing_key = d.signingKeyFor(grantingAccountId)
	if "" == signing_key {
		return signing_key, 0, errors.New(fmt.Sprintf("Key not set for %s", u.Account))
	}

	return signing_key, duration, nil
}
<commit_msg>Add method to get API secret key from store<commit_after>// Copyright (c) 2015 Stuart Glenn
// All rights reserved
// Use of this source code is goverened by a BSD 3-clause license,
// see included LICENSE file for details
// Datastore for perstiance of access rules, clients & keys
package atm

import (
	"database/sql"
	"errors"
	"fmt"
)

type Datastore struct {
	pool        *sql.DB
	signingKeys Cache
}

type Account struct {
	Id   string `json:id`
	Name string `json:name`
}

func NewDatastore(driver, dsn string) (*Datastore, error) {
	ds := &Datastore{}
	var err error
	ds.pool, err = sql.Open(driver, dsn)
	if nil != err {
		return nil, err
	}
	err = ds.Ping()
	if nil != err {
		return nil, err
	}
	ds.signingKeys = NewCache()
	return ds, nil
}

func (d *Datastore) Ping() error {
	return d.pool.Ping()
}

func (d *Datastore) Close() error {
	return d.pool.Close()
}

func (d *Datastore) AddSigningKeyForAccount(key, account string) {
	d.signingKeys.Set(account, key)
}

func (d *Datastore) signingKeyFor(account string) string {
	return d.signingKeys.Get(account)
}

func (d *Datastore) Account(name string) (*Account, error) {
	a := &Account{}
	stmt, err := d.pool.Prepare("SELECT id, name from accounts where name = ?")
	if nil != err {
		return a, err
	}
	defer stmt.Close()
	rows, err := stmt.Query(name)
	if nil != err {
		return a, err
	}
	defer rows.Close()
	numRows := 0
	for rows.Next() {
		if numRows > 1 {
			return a, errors.New("Too many results")
		}
		err := rows.Scan(&a.Id, &a.Name)
		if nil != err {
			return a, err
		}
		err = rows.Err()
		if nil != err {
			return a, err
		}
		numRows++
	}
	return a, nil
}

func (d *Datastore) KeyForRequest(u *UrlRequest, appId string) (string, int64, error) {
	var signing_key string
	var duration int64
	stmt, err := d.pool.Prepare("SELECT a.id, r.duration as duration from accounts a, rules r " +
		"WHERE r.account_id=a.id AND requestor_id = ? AND a.name = ? AND " +
		"? REGEXP r.container AND ? REGEXP r.object AND r.method = ?")
	if nil != err {
		return signing_key, duration, err
	}
	defer stmt.Close()
	rows, err := stmt.Query(appId, u.Account, u.Container, u.Object, u.Method)
	if nil != err {
		return signing_key, duration, err
	}
	defer rows.Close()
	numRows := 0
	var grantingAccountId string
	for rows.Next() {
		if numRows > 1 {
			return signing_key, duration, errors.New("Too many results")
		}
		err := rows.Scan(&grantingAccountId, &duration)
		if nil != err {
			return signing_key, duration, err
		}
		err = rows.Err()
		if nil != err {
			return signing_key, duration, err
		}
		numRows++
	}
	if 0 == numRows {
		return "", 0, nil
	}

	signing_key = d.signingKeyFor(grantingAccountId)
	if "" == signing_key {
		return signing_key, 0, errors.New(fmt.Sprintf("Key not set for %s", u.Account))
	}

	return signing_key, duration, nil
}

func (d *Datastore) ApiKeySecret(apiKey string) (string, error) {
	var secret string
	stmt, err := d.pool.Prepare("SELECT secret from accounts where id = ?")
	if nil != err {
		return secret, err
	}
	defer stmt.Close()
	rows, err := stmt.Query(apiKey)
	if nil != err {
		return secret, err
	}
	defer rows.Close()
	numRows := 0
	for rows.Next() {
		if numRows > 1 {
			return secret, errors.New("Too many results")
		}
		err := rows.Scan(&secret)
		if nil != err {
			return "", err
		}
		err = rows.Err()
		if nil != err {
			return "", err
		}
		numRows++
	}
	if 0 == numRows || "" == secret {
		return "", errors.New("No secret")
	}

	return secret, nil
}
<|endoftext|>