<commit_before>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os/exec"
	"strings"

	"golang.org/x/crypto/ssh"
)

// A project is made of project fields which has a program on it.
type program struct {
	setup              []string
	postUpdateFilename string
}

type projectField struct {
	name, label, inputQuestion, errorMsg, validationMsg string
	program                                             program
}

type project struct {
	projectname, hostname, pwd, port, typ projectField
}

var postUpdateContent string

func main() {

	// Initialization
	project := new(project)

	// Let's build our project!
	project.assemblyLine()

	// SSH connection config
	config := &ssh.ClientConfig{
		User: project.projectname.name,
		Auth: []ssh.AuthMethod{
			ssh.Password(project.pwd.name),
		},
	}

	// Now we need to know which instalation we going to make.
	// And once we get to know it, let's load the setup with
	// the aproppriate set of files and commands.
	if project.typ.name == "Yii" {

		// Loading common steps into the selected setup
		project.typ.program.setup = []string{}
		project.typ.program.postUpdateFilename = "post-update-yii"

	} else {

		// Loading common steps into the selected setup
		project.typ.program.setup = []string{
			"echo -e '[User]\nname = Pipi, server girl' > .gitconfig",
			"cd ~/www/www/ && git init",
			"cd ~/www/www/ && git add . ",
			"cd ~/www/www/ && git commit -m 'on the beginning was the commit'",
			"cd ~/private/ && mkdir repos && cd repos && mkdir " + project.projectname.name + "_hub.git && cd " + project.projectname.name + "_hub.git && git --bare init",
			"cd ~/www/www && git remote add hub ~/private/repos/" + project.projectname.name + "_hub.git && git push hub master",
			"post-update configuration",
			"cd ~/www/www && git remote add hub ~/private/repos/" + project.projectname.name + "_hub.git/hooks && chmod 755 post-update",
			"mkdir ~/sites/" + project.projectname.name + ".dev/",
		}
		project.typ.program.postUpdateFilename = "post-update-wp"
	}

	project.connect(config)
}

func (p *project) assemblyLine() {
	// project name
	p.projectname.inputQuestion = "project name: "
	p.projectname.label = "projectname"
	p.projectname.errorMsg = "error getting the project's name: "
	p.projectname.validationMsg = "make sure you type a valid name for your project (3 to 20 characters)."
	ask4Input(&p.projectname)

	// Hostname
	p.hostname.inputQuestion = "hostname: "
	p.hostname.label = "hostname"
	p.hostname.errorMsg = "error getting the project's hostname: "
	p.hostname.validationMsg = "make sure you type a valid hostname for your project. it must contain '.com', '.pt' or '.org', for example.)."
	ask4Input(&p.hostname)

	// Password
	p.pwd.inputQuestion = "password: "
	p.pwd.label = "pwd"
	p.pwd.errorMsg = "error getting the project's password: "
	p.pwd.validationMsg = "type a valid password. It must contain at least 6 digits"
	ask4Input(&p.pwd)

	// Port
	p.port.inputQuestion = "port (default 22): "
	p.port.label = "port"
	p.port.errorMsg = "error getting the project's port"
	p.port.validationMsg = "only digits allowed. min 0, max 999."
	ask4Input(&p.port)

	// Type
	p.typ.inputQuestion = "project type [1]yii [2]wp or gohugo: "
	p.typ.label = "type"
	p.typ.errorMsg = "error getting the project's type"
	p.typ.validationMsg = "pay attention to the options"
	ask4Input(&p.typ)
}

// Takes the assemblyLine's data and mount the prompt for the user.
func ask4Input(field *projectField) {
	fmt.Print(field.inputQuestion)

	var input string
	_, err := fmt.Scanln(&input)

	// The port admits empty string as user input. Setting the default value of "22".
	if err != nil && err.Error() == "unexpected newline" && field.label != "port" {
		ask4Input(field)
	} else if err != nil && err.Error() == "unexpected newline" {
		input = "22"
		checkInput(field, input)
	} else if err != nil {
		log.Fatal(field.errorMsg, err)
	}

	// After we've got the input we must check if it's valid.
	checkInput(field, input)
}

// A simple error checker.
func checkError(msg string, err error) {
	if err != nil {
		log.Fatal(msg, err.Error())
	}
}

// Check invalid parameters on the user input.
func checkInput(field *projectField, input string) {

	switch inputLength := len(input); field.label {
	case "projectname":
		if inputLength > 20 {
			fmt.Println(field.validationMsg)
			ask4Input(field)
		}
	case "hostname":
		if inputLength <= 5 {
			fmt.Println(field.validationMsg)
			ask4Input(field)
		}
	case "pwd":
		if inputLength <= 6 {
			fmt.Println(field.validationMsg)
			ask4Input(field)
		}
	case "port":
		if inputLength == 0 {
			input = "22"
		} else if inputLength > 3 {
			fmt.Println(field.validationMsg)
			ask4Input(field)
		}
	case "type":
		if input != "1" && input != "2" {
			fmt.Println(field.validationMsg)
			ask4Input(field)
		} else if input == "1" {
			input = "Yii"
		} else if input == "2" {
			input = "WP"
		}
	}
	field.name = input
}

// Creates a ssh connection between the local machine and the remote server.
func (p *project) connect(config *ssh.ClientConfig) {

	log.Printf("Trying connection...\n")

	conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", p.hostname.name, p.port.name), config)
	checkError("Failed to dial: ", err)
	log.Printf("Connection established.\n")

	session, err := conn.NewSession()
	checkError("Failed to build session: ", err)
	defer session.Close()

	// Loops over the slice of commands to be executed on the remote.
	for step := range p.typ.program.setup {

		if p.typ.program.setup[step] == "post-update configuration" {
			p.secureCopy(conn)
		} else if p.typ.program.setup[step] == "mkdir ~/sites/"+p.projectname.name+".dev/" {
			p.installOnLocal(step)

		} else {
			p.installOnRemote(step, conn)
		}
	}
}

func (p *project) installOnLocal(step int) {
	// cmd := exec.Command(p.typ.program.setup[step])
	cmd := exec.Command("pwd")
	err := cmd.Run()
	checkError("Failed to run "+p.typ.program.setup[step], err)
}

func (p *project) installOnRemote(step int, conn *ssh.Client) {

	// Git and some other programs can send us an unsuccessful exit (< 0)
	// even if the command was successfully executed on the remote shell.
	// On these cases, we want to ignore those errors and move onto the next step.
	ignoredError := "Reason was:  ()"

	// Creates a session over the ssh connection to execute the commands
	session, err := conn.NewSession()
	checkError("Failed to build session: ", err)
	defer session.Close()

	var stdoutBuf bytes.Buffer
	session.Stdout = &stdoutBuf

	log.Printf("Executing command: %s", p.typ.program.setup[step])

	err = session.Run(p.typ.program.setup[step])

	if err != nil && !strings.Contains(err.Error(), ignoredError) {
		log.Printf("Command '%s' failed on execution", p.typ.program.setup[step])
		log.Fatal("Error on command execution: ", err.Error())
	}
}

func readFile(file string) string {
	data, err := ioutil.ReadFile(file)
	checkError("Error on reading file.", err)
	return string(data[:len(data)])
}

// Secure Copy a file from local machine to remote host.
func (p *project) secureCopy(conn *ssh.Client) {
	session, err := conn.NewSession()
	checkError("Failed to build session: ", err)
	defer session.Close()

	var stdoutBuf bytes.Buffer
	session.Stdout = &stdoutBuf

	go func() {
		w, _ := session.StdinPipe()
		defer w.Close()
		content := readFile(p.typ.program.postUpdateFilename)
		fmt.Fprintln(w, "C0644", len(content), "post-update")
		fmt.Fprint(w, content)
		fmt.Fprint(w, "\x00")
	}()

	if err := session.Run("scp -qrt ~/private/repos/" + p.projectname.name + "_hub.git/hooks"); err != nil {
		log.Fatal("Failed to run SCP: " + err.Error())
	}
}
<commit_msg>ssh wp almost done<commit_after>package main

import (
	"bytes"
	"fmt"
	"github.com/alesr/errorUtil"
	"github.com/alesr/fileUtil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"strings"

	"golang.org/x/crypto/ssh"
)

// A project is made of project fields which has a program on it.
type program struct {
	setup              []string
	postUpdateFilename string
}

type projectField struct {
	name, label, inputQuestion, errorMsg, validationMsg string
	program                                             program
}

type project struct {
	projectname, hostname, pwd, port, typ projectField
}

var postUpdateContent string

func main() {

	// Initialization
	project := new(project)

	// Let's build our project!
	project.assemblyLine()

	// SSH connection config
	config := &ssh.ClientConfig{
		User: project.projectname.name,
		Auth: []ssh.AuthMethod{
			ssh.Password(project.pwd.name),
		},
	}

	// Now we need to know which instalation we going to make.
	// And once we get to know it, let's load the setup with
	// the aproppriate set of files and commands.
	if project.typ.name == "Yii" {

		// Loading common steps into the selected setup
		project.typ.program.setup = []string{}
		project.typ.program.postUpdateFilename = "post-update-yii"

	} else {

		// Loading common steps into the selected setup
		project.typ.program.setup = []string{
			"echo -e '[User]\nname = Pipi, server girl' > .gitconfig",
			"cd ~/www/www/ && git init",
			"cd ~/www/www/ && touch readme.txt && git add . ",
			"cd ~/www/www/ && git commit -m 'on the beginning was the commit'",
			"cd ~/private/ && mkdir repos && cd repos && mkdir " + project.projectname.name + "_hub.git && cd " + project.projectname.name + "_hub.git && git --bare init",
			"cd ~/www/www && git remote add hub ~/private/repos/" + project.projectname.name + "_hub.git && git push hub master",
			"post-update configuration",
			"cd ~/www/www && git remote add hub ~/private/repos/" + project.projectname.name + "_hub.git/hooks && chmod 755 post-update",
			project.projectname.name + ".dev",
			"git clone",
		}
		project.typ.program.postUpdateFilename = "post-update-wp"
	}
	project.connect(config)

	log.Println("Environment configuration done.")
}

func (p *project) assemblyLine() {
	// project name
	p.projectname.inputQuestion = "project name: "
	p.projectname.label = "projectname"
	p.projectname.errorMsg = "error getting the project's name: "
	p.projectname.validationMsg = "make sure you type a valid name for your project (3 to 20 characters)."
	ask4Input(&p.projectname)

	// Hostname
	p.hostname.inputQuestion = "hostname: "
	p.hostname.label = "hostname"
	p.hostname.errorMsg = "error getting the project's hostname: "
	p.hostname.validationMsg = "make sure you type a valid hostname for your project. it must contain '.com', '.pt' or '.org', for example.)."
	ask4Input(&p.hostname)

	// Password
	p.pwd.inputQuestion = "password: "
	p.pwd.label = "pwd"
	p.pwd.errorMsg = "error getting the project's password: "
	p.pwd.validationMsg = "type a valid password. It must contain at least 6 digits"
	ask4Input(&p.pwd)

	// Port
	p.port.inputQuestion = "port (default 22): "
	p.port.label = "port"
	p.port.errorMsg = "error getting the project's port"
	p.port.validationMsg = "only digits allowed. min 0, max 999."
	ask4Input(&p.port)

	// Type
	p.typ.inputQuestion = "project type [1]yii [2]wp or gohugo: "
	p.typ.label = "type"
	p.typ.errorMsg = "error getting the project's type"
	p.typ.validationMsg = "pay attention to the options"
	ask4Input(&p.typ)
}

// Takes the assemblyLine's data and mount the prompt for the user.
func ask4Input(field *projectField) {
	fmt.Print(field.inputQuestion)

	var input string
	_, err := fmt.Scanln(&input)

	// The port admits empty string as user input. Setting the default value of "22".
	if err != nil && err.Error() == "unexpected newline" && field.label != "port" {
		ask4Input(field)
	} else if err != nil && err.Error() == "unexpected newline" {
		input = "22"
		checkInput(field, input)
	} else if err != nil {
		log.Fatal(field.errorMsg, err)
	}

	// After we've got the input we must check if it's valid.
	checkInput(field, input)
}

// Check invalid parameters on the user input.
func checkInput(field *projectField, input string) {

	switch inputLength := len(input); field.label {
	case "projectname":
		if inputLength > 20 {
			fmt.Println(field.validationMsg)
			ask4Input(field)
		}
	case "hostname":
		if inputLength <= 5 {
			fmt.Println(field.validationMsg)
			ask4Input(field)
		}
	case "pwd":
		if inputLength <= 6 {
			fmt.Println(field.validationMsg)
			ask4Input(field)
		}
	case "port":
		if inputLength == 0 {
			input = "22"
		} else if inputLength > 3 {
			fmt.Println(field.validationMsg)
			ask4Input(field)
		}
	case "type":
		if input != "1" && input != "2" {
			fmt.Println(field.validationMsg)
			ask4Input(field)
		} else if input == "1" {
			input = "Yii"
		} else if input == "2" {
			input = "WP"
		}
	}
	field.name = input
}

// Creates a ssh connection between the local machine and the remote server.
func (p *project) connect(config *ssh.ClientConfig) {

	log.Printf("Trying connection...\n")

	conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", p.hostname.name, p.port.name), config)
	errorUtil.CheckError("Failed to dial: ", err)
	log.Printf("Connection established.\n")

	session, err := conn.NewSession()
	errorUtil.CheckError("Failed to build session: ", err)
	defer session.Close()

	// Loops over the slice of commands to be executed on the remote.
	for step := range p.typ.program.setup {

		if p.typ.program.setup[step] == "post-update configuration" {
			p.secureCopy(conn)
		} else if p.typ.program.setup[step] == p.projectname.name+".dev" {
			p.makeDirOnLocal(step)
		} else if p.typ.program.setup[step] == "git clone" {
			p.gitOnLocal(step)
		} else {
			p.installOnRemote(step, conn)
		}
	}
}

func (p *project) gitOnLocal(step int) {
	switch p.typ.program.setup[step] {
	case "git clone":
		p.installOnLocal(step)
	}
}

func (p *project) installOnLocal(step int) {
	homeDir := getUserHomeDir()

	if err := os.Chdir(homeDir + string(filepath.Separator) + "sites" + string(filepath.Separator) + p.projectname.name + ".dev/"); err != nil {
		log.Fatal("Failed to change directory.")
	}

	repo := "ssh://" + p.projectname.name + "@" + p.hostname.name + "/home/" + p.projectname.name + "/private/repos/" + p.projectname.name + "_hub.git"

	cmd := exec.Command("git", "clone", repo, ".")
	if err := cmd.Run(); err != nil {
		log.Fatal("Failed to execute git clone: ", err)
	}

}

// Creates a directory on the local machine. Case the directory already exists
// remove the old one and runs the function again.
func (p *project) makeDirOnLocal(step int) {

	log.Println("Creating directory...")

	// Get the user home directory path.
	homeDir := getUserHomeDir()

	// The dir we want to create.
	dir := homeDir + string(filepath.Separator) + "sites" + string(filepath.Separator) + p.typ.program.setup[step]

	// Check if the directory already exists.
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		err := os.Mkdir(dir, 0755)
		errorUtil.CheckError("Failed to create directory.", err)
		log.Println(dir + " successfully created.")
	} else {
		log.Println(dir + " already exist.\nRemoving old and creating new...")

		// Remove the old one.
		if err := os.RemoveAll(dir); err != nil {
			log.Fatalf("Error removing %s\n%s", dir, err)
		}
		p.makeDirOnLocal(step)
	}
}

func (p *project) installOnRemote(step int, conn *ssh.Client) {

	// Git and some other programs can send us an unsuccessful exit (< 0)
	// even if the command was successfully executed on the remote shell.
	// On these cases, we want to ignore those errors and move onto the next step.
	ignoredError := "Reason was:  ()"

	// Creates a session over the ssh connection to execute the commands
	session, err := conn.NewSession()
	errorUtil.CheckError("Failed to build session: ", err)
	defer session.Close()

	var stdoutBuf bytes.Buffer
	session.Stdout = &stdoutBuf

	log.Printf("Executing command: %s", p.typ.program.setup[step])

	err = session.Run(p.typ.program.setup[step])

	if err != nil && !strings.Contains(err.Error(), ignoredError) {
		log.Printf("Command '%s' failed on execution", p.typ.program.setup[step])
		log.Fatal("Error on command execution: ", err.Error())
	}
}

// Secure Copy a file from local machine to remote host.
func (p *project) secureCopy(conn *ssh.Client) {
	session, err := conn.NewSession()
	errorUtil.CheckError("Failed to build session: ", err)
	defer session.Close()

	var stdoutBuf bytes.Buffer
	session.Stdout = &stdoutBuf

	go func() {
		w, _ := session.StdinPipe()
		defer w.Close()
		content := fileUtil.ReadFile(p.typ.program.postUpdateFilename)
		fmt.Fprintln(w, "C0644", len(content), "post-update")
		fmt.Fprint(w, content)
		fmt.Fprint(w, "\x00")
	}()

	if err := session.Run("scp -qrt ~/private/repos/" + p.projectname.name + "_hub.git/hooks"); err != nil {
		log.Fatal("Failed to run SCP: " + err.Error())
	}
}

func getUserHomeDir() string {
	usr, err := user.Current()
	errorUtil.CheckError("Failed to locate user home directory ", err)

	return usr.HomeDir
}
<|endoftext|>


<commit_before>// Package dataloader source: https://github.com/nicksrandall/dataloader
//
// dataloader is an implimentation of facebook's dataloader in go.
// See https://github.com/facebook/dataloader for more information
package dataloader

import (
	"context"
	"fmt"
	"log"
	"runtime"
	"sync"
	"time"
)

// Loader implements the dataloader.Interface.
type Loader struct {
	// the batch function to be used by this loader
	batchFn Batch

	// the maximum batch size. Set to 0 if you want it to be unbounded.
	batchCap int

	// the internal cache. This packages contains a basic cache implementation but any custom cache
	// implementation could be used as long as it implements the `Cache` interface.
	cacheLock sync.Mutex
	cache     Cache
	// should we clear the cache on each batch?
	// this would allow batching but no long term caching
	clearCacheOnBatch bool

	// count of queued up items
	count int

	// the maximum input queue size. Set to 0 if you want it to be unbounded.
	inputCap int

	// the amount of time to wait before triggering a batch
	wait time.Duration

	// lock to protect the batching operations
	batchLock sync.Mutex

	// current batcher
	curBatcher *batcher

	// used to close the sleeper of the current batcher
	endSleeper chan bool

	// used by tests to prevent logs
	silent bool
}

// Thunk is a function that will block until the value (*Result) it contins is resolved.
// After the value it contains is resolved, this function will return the result.
// This function can be called many times, much like a Promise is other languages.
// The value will only need to be resolved once so subsequent calls will return immediately.
type Thunk func() (interface{}, error)

// ThunkMany is much like the Thunk func type but it contains a list of results.
type ThunkMany func() ([]interface{}, []error)

// type used to on input channel
type batchRequest struct {
	key     interface{}
	channel chan Result
}

// NewBatchedLoader constructs a new Loader with given options.
func newBatchedLoader(batchFn Batch, opts Options) DataLoader {
	return &Loader{
		batchFn:  batchFn,
		cache:    newCache(),
		batchCap: opts.BatchCapacity,
		inputCap: opts.InputCapacity,
		wait:     opts.Wait,
		silent:   opts.Silent,
	}
}

func (l *Loader) async(fn func() (interface{}, error)) (interface{}, error) {
	type result struct {
		data interface{}
		err  error
	}
	ch := make(chan *result, 1)
	go func() {
		defer close(ch)
		data, err := fn()
		ch <- &result{data: data, err: err}
	}()
	return func() (interface{}, error) {
		r := <-ch
		return r.data, r.err
	}, nil
}

// Load load/resolves the given key, returning a channel that will contain the value and error
func (l *Loader) Load(ctx context.Context, key interface{}) (interface{}, error) {
	return l.async(func() (interface{}, error) {
		c := make(chan Result, 1)
		var result struct {
			mu    sync.RWMutex
			value Result
		}

		// lock to prevent duplicate keys coming in before item has been added to cache.
		l.cacheLock.Lock()
		if v, ok := l.cache.Get(key); ok {
			defer l.cacheLock.Unlock()
			return v()
		}

		thunk := func() (interface{}, error) {
			result.mu.RLock()
			resultNotSet := result.value == nil
			result.mu.RUnlock()

			if resultNotSet {
				result.mu.Lock()
				if v, ok := <-c; ok {
					result.value = v
				}
				result.mu.Unlock()
			}
			result.mu.RLock()
			defer result.mu.RUnlock()
			return result.value.Data(), result.value.Error()
		}

		l.cache.Set(key, thunk)
		l.cacheLock.Unlock()

		// this is sent to batch fn. It contains the key and the channel to return the
		// the result on
		req := &batchRequest{key, c}

		l.batchLock.Lock()
		// start the batch window if it hasn't already started.
		if l.curBatcher == nil {
			l.curBatcher = l.newBatcher(l.silent)
			// start the current batcher batch function
			go l.curBatcher.batch(ctx)
			// start a sleeper for the current batcher
			l.endSleeper = make(chan bool)
			go l.sleeper(l.curBatcher, l.endSleeper)
		}

		l.curBatcher.input <- req

		// if we need to keep track of the count (max batch), then do so.
		if l.batchCap > 0 {
			l.count++
			// if we hit our limit, force the batch to start
			if l.count == l.batchCap {
				// end the batcher synchronously here because another call to Load
				// may concurrently happen and needs to go to a new batcher.
				l.curBatcher.end()
				// end the sleeper for the current batcher.
				// this is to stop the goroutine without waiting for the
				// sleeper timeout.
				close(l.endSleeper)
				l.reset()
			}
		}
		l.batchLock.Unlock()

		return thunk()
	})
}

// LoadMany loads mulitiple keys, returning a thunk (type: ThunkMany) that will resolve the keys passed in.
func (l *Loader) LoadMany(ctx context.Context, keys []interface{}) ([]interface{}, []error) {
	length := len(keys)
	data := make([]interface{}, length)
	errors := make([]error, length)
	c := make(chan ResultMany, 1)
	wg := sync.WaitGroup{}

	wg.Add(length)
	for i := range keys {
		go func(i int) {
			defer wg.Done()
			result, err := l.Load(ctx, keys[i])
			data[i] = result
			errors[i] = err
		}(i)
	}

	go func() {
		wg.Wait()
		c <- &Returns{data, errors}
		close(c)
	}()

	var result struct {
		mu    sync.RWMutex
		value ResultMany
	}

	thunkMany := func() ([]interface{}, []error) {
		result.mu.RLock()
		resultNotSet := result.value == nil
		result.mu.RUnlock()

		if resultNotSet {
			result.mu.Lock()
			if v, ok := <-c; ok {
				result.value = v
			}
			result.mu.Unlock()
		}
		result.mu.RLock()
		defer result.mu.RUnlock()
		return result.value.Data(), result.value.Errors()
	}

	return thunkMany()
}

// Clear clears the value at `key` from the cache, it it exsits. Returs self for method chaining
func (l *Loader) Clear(key interface{}) {
	l.cacheLock.Lock()
	l.cache.Delete(key)
	l.cacheLock.Unlock()
}

// ClearAll clears the entire cache. To be used when some event results in unknown invalidations.
// Returns self for method chaining.
func (l *Loader) ClearAll() {
	l.cacheLock.Lock()
	l.cache.Clear()
	l.cacheLock.Unlock()
}

// Prime adds the provided key and value to the cache. If the key already exists, no change is made.
// Returns self for method chaining
func (l *Loader) Prime(key interface{}, value interface{}) {
	if _, ok := l.cache.Get(key); !ok {
		thunk := func() (interface{}, error) {
			return value, nil
		}
		l.cache.Set(key, thunk)
	}
}

func (l *Loader) reset() {
	l.count = 0
	l.curBatcher = nil

	if l.clearCacheOnBatch {
		l.cache.Clear()
	}
}

type batcher struct {
	input    chan *batchRequest
	batchFn  Batch
	finished bool
	silent   bool
}

// newBatcher returns a batcher for the current requests
// all the batcher methods must be protected by a global batchLock
func (l *Loader) newBatcher(silent bool) *batcher {
	return &batcher{
		input:   make(chan *batchRequest, l.inputCap),
		batchFn: l.batchFn,
		silent:  silent,
	}
}

// stop receiving input and process batch function
func (b *batcher) end() {
	if !b.finished {
		close(b.input)
		b.finished = true
	}
}

// execute the batch of all items in queue
func (b *batcher) batch(ctx context.Context) {
	var keys []interface{}
	var reqs []*batchRequest

	for item := range b.input {
		keys = append(keys, item.key)
		reqs = append(reqs, item)
	}

	var items []Result
	var panicErr interface{}
	func() {
		defer func() {
			if r := recover(); r != nil {
				panicErr = r
				if b.silent {
					return
				}
				const size = 64 << 10
				buf := make([]byte, size)
				buf = buf[:runtime.Stack(buf, false)]
				log.Printf("Dataloader: Panic received in batch function:: %v\n%s", panicErr, buf)
			}
		}()
		items = b.batchFn.Handle(ctx, keys)
	}()

	if panicErr != nil {
		for _, req := range reqs {
			req.channel <- &Return{err: fmt.Errorf("Panic received in batch function: %v", panicErr)}
			close(req.channel)
		}
		return
	}

	if len(items) != len(keys) {
		err := &Return{err: fmt.Errorf(`
			The batch function supplied did not return an array of responses
			the same length as the array of keys.

			Keys:
			%v

			Values:
			%v
		`, keys, items)}

		for _, req := range reqs {
			req.channel <- err
			close(req.channel)
		}

		return
	}

	for i, req := range reqs {
		req.channel <- items[i]
		close(req.channel)
	}
}

// wait the appropriate amount of time for the provided batcher
func (l *Loader) sleeper(b *batcher, close chan bool) {
	select {
	// used by batch to close early. usually triggered by max batch size
	case <-close:
		return
	// this will move this goroutine to the back of the callstack?
	case <-time.After(l.wait):
	}

	// reset
	// this is protected by the batchLock to avoid closing the batcher input
	// channel while Load is inserting a request
	l.batchLock.Lock()
	b.end()

	// We can end here also if the batcher has already been closed and a
	// new one has been created. So reset the loader state only if the batcher
	// is the current one
	if l.curBatcher == b {
		l.reset()
	}
	l.batchLock.Unlock()
}
<commit_msg>Fixes leaking goroutines<commit_after>// Package dataloader source: https://github.com/nicksrandall/dataloader
//
// dataloader is an implimentation of facebook's dataloader in go.
// See https://github.com/facebook/dataloader for more information
package dataloader

import (
	"context"
	"fmt"
	"log"
	"runtime"
	"sync"
	"time"
)

// Loader implements the dataloader.Interface.
type Loader struct {
	// the batch function to be used by this loader
	batchFn Batch

	// the maximum batch size. Set to 0 if you want it to be unbounded.
	batchCap int

	// the internal cache. This packages contains a basic cache implementation but any custom cache
	// implementation could be used as long as it implements the `Cache` interface.
	cacheLock sync.Mutex
	cache     Cache
	// should we clear the cache on each batch?
	// this would allow batching but no long term caching
	clearCacheOnBatch bool

	// count of queued up items
	count int

	// the maximum input queue size. Set to 0 if you want it to be unbounded.
	inputCap int

	// the amount of time to wait before triggering a batch
	wait time.Duration

	// lock to protect the batching operations
	batchLock sync.Mutex

	// current batcher
	curBatcher *batcher

	// used to close the sleeper of the current batcher
	endSleeper chan bool

	// used by tests to prevent logs
	silent bool
}

// Thunk is a function that will block until the value (*Result) it contins is resolved.
// After the value it contains is resolved, this function will return the result.
// This function can be called many times, much like a Promise is other languages.
// The value will only need to be resolved once so subsequent calls will return immediately.
type Thunk func() (interface{}, error)

// ThunkMany is much like the Thunk func type but it contains a list of results.
type ThunkMany func() ([]interface{}, []error)

// type used to on input channel
type batchRequest struct {
	key     interface{}
	channel chan Result
}

// NewBatchedLoader constructs a new Loader with given options.
func newBatchedLoader(batchFn Batch, opts Options) DataLoader {
	return &Loader{
		batchFn:  batchFn,
		cache:    newCache(),
		batchCap: opts.BatchCapacity,
		inputCap: opts.InputCapacity,
		wait:     opts.Wait,
		silent:   opts.Silent,
	}
}

func (l *Loader) async(ctx context.Context, fn func() (interface{}, error)) (interface{}, error) {
	type result struct {
		data interface{}
		err  error
	}
	ch := make(chan *result, 1)
	go func() {
		defer close(ch)
		data, err := fn()
		// graphql.Do will finish immediately in the case concurrentFieldFoo returns an error. Therefore,
		// when using goroutines make sure to utilize a done channel to avoid leaking goroutines.
		select {
		case ch <- &result{data: data, err: err}:
		case <-ctx.Done():
		}
	}()
	return func() (interface{}, error) {
		select {
		case r := <-ch:
			return r.data, r.err
		case <-ctx.Done():
			return nil, nil
		}
	}, nil
}

// Load load/resolves the given key, returning a channel that will contain the value and error
func (l *Loader) Load(ctx context.Context, key interface{}) (interface{}, error) {
	return l.async(ctx, func() (interface{}, error) {
		c := make(chan Result, 1)
		var result struct {
			mu    sync.RWMutex
			value Result
		}

		// lock to prevent duplicate keys coming in before item has been added to cache.
		l.cacheLock.Lock()
		if v, ok := l.cache.Get(key); ok {
			defer l.cacheLock.Unlock()
			return v()
		}

		thunk := func() (interface{}, error) {
			result.mu.RLock()
			resultNotSet := result.value == nil
			result.mu.RUnlock()

			if resultNotSet {
				result.mu.Lock()
				if v, ok := <-c; ok {
					result.value = v
				}
				result.mu.Unlock()
			}
			result.mu.RLock()
			defer result.mu.RUnlock()
			return result.value.Data(), result.value.Error()
		}

		l.cache.Set(key, thunk)
		l.cacheLock.Unlock()

		// this is sent to batch fn. It contains the key and the channel to return the
		// the result on
		req := &batchRequest{key, c}

		l.batchLock.Lock()
		// start the batch window if it hasn't already started.
		if l.curBatcher == nil {
			l.curBatcher = l.newBatcher(l.silent)
			// start the current batcher batch function
			go l.curBatcher.batch(ctx)
			// start a sleeper for the current batcher
			l.endSleeper = make(chan bool)
			go l.sleeper(l.curBatcher, l.endSleeper)
		}

		l.curBatcher.input <- req

		// if we need to keep track of the count (max batch), then do so.
		if l.batchCap > 0 {
			l.count++
			// if we hit our limit, force the batch to start
			if l.count == l.batchCap {
				// end the batcher synchronously here because another call to Load
				// may concurrently happen and needs to go to a new batcher.
				l.curBatcher.end()
				// end the sleeper for the current batcher.
				// this is to stop the goroutine without waiting for the
				// sleeper timeout.
				close(l.endSleeper)
				l.reset()
			}
		}
		l.batchLock.Unlock()

		return thunk()
	})
}

// LoadMany loads mulitiple keys, returning a thunk (type: ThunkMany) that will resolve the keys passed in.
func (l *Loader) LoadMany(ctx context.Context, keys []interface{}) ([]interface{}, []error) {
	length := len(keys)
	data := make([]interface{}, length)
	errors := make([]error, length)
	c := make(chan ResultMany, 1)
	wg := sync.WaitGroup{}

	wg.Add(length)
	for i := range keys {
		go func(i int) {
			defer wg.Done()
			result, err := l.Load(ctx, keys[i])
			data[i] = result
			errors[i] = err
		}(i)
	}

	go func() {
		wg.Wait()
		c <- &Returns{data, errors}
		close(c)
	}()

	var result struct {
		mu    sync.RWMutex
		value ResultMany
	}

	thunkMany := func() ([]interface{}, []error) {
		result.mu.RLock()
		resultNotSet := result.value == nil
		result.mu.RUnlock()

		if resultNotSet {
			result.mu.Lock()
			if v, ok := <-c; ok {
				result.value = v
			}
			result.mu.Unlock()
		}
		result.mu.RLock()
		defer result.mu.RUnlock()
		return result.value.Data(), result.value.Errors()
	}

	return thunkMany()
}

// Clear clears the value at `key` from the cache, it it exsits. Returs self for method chaining
func (l *Loader) Clear(key interface{}) {
	l.cacheLock.Lock()
	l.cache.Delete(key)
	l.cacheLock.Unlock()
}

// ClearAll clears the entire cache. To be used when some event results in unknown invalidations.
// Returns self for method chaining.
func (l *Loader) ClearAll() {
	l.cacheLock.Lock()
	l.cache.Clear()
	l.cacheLock.Unlock()
}

// Prime adds the provided key and value to the cache. If the key already exists, no change is made.
// Returns self for method chaining
func (l *Loader) Prime(key interface{}, value interface{}) {
	if _, ok := l.cache.Get(key); !ok {
		thunk := func() (interface{}, error) {
			return value, nil
		}
		l.cache.Set(key, thunk)
	}
}

func (l *Loader) reset() {
	l.count = 0
	l.curBatcher = nil

	if l.clearCacheOnBatch {
		l.cache.Clear()
	}
}

type batcher struct {
	input    chan *batchRequest
	batchFn  Batch
	finished bool
	silent   bool
}

// newBatcher returns a batcher for the current requests
// all the batcher methods must be protected by a global batchLock
func (l *Loader) newBatcher(silent bool) *batcher {
	return &batcher{
		input:   make(chan *batchRequest, l.inputCap),
		batchFn: l.batchFn,
		silent:  silent,
	}
}

// stop receiving input and process batch function
func (b *batcher) end() {
	if !b.finished {
		close(b.input)
		b.finished = true
	}
}

// execute the batch of all items in queue
func (b *batcher) batch(ctx context.Context) {
	var keys []interface{}
	var reqs []*batchRequest

	for item := range b.input {
		keys = append(keys, item.key)
		reqs = append(reqs, item)
	}

	var items []Result
	var panicErr interface{}
	func() {
		defer func() {
			if r := recover(); r != nil {
				panicErr = r
				if b.silent {
					return
				}
				const size = 64 << 10
				buf := make([]byte, size)
				buf = buf[:runtime.Stack(buf, false)]
				log.Printf("Dataloader: Panic received in batch function:: %v\n%s", panicErr, buf)
			}
		}()
		items = b.batchFn.Handle(ctx, keys)
	}()

	if panicErr != nil {
		for _, req := range reqs {
			req.channel <- &Return{err: fmt.Errorf("Panic received in batch function: %v", panicErr)}
			close(req.channel)
		}
		return
	}

	if len(items) != len(keys) {
		err := &Return{err: fmt.Errorf(`
			The batch function supplied did not return an array of responses
			the same length as the array of keys.

			Keys:
			%v

			Values:
			%v
		`, keys, items)}

		for _, req := range reqs {
			req.channel <- err
			close(req.channel)
		}

		return
	}

	for i, req := range reqs {
		req.channel <- items[i]
		close(req.channel)
	}
}

// wait the appropriate amount of time for the provided batcher
func (l *Loader) sleeper(b *batcher, close chan bool) {
	select {
	// used by batch to close early. usually triggered by max batch size
	case <-close:
		return
	// this will move this goroutine to the back of the callstack?
	case <-time.After(l.wait):
	}

	// reset
	// this is protected by the batchLock to avoid closing the batcher input
	// channel while Load is inserting a request
	l.batchLock.Lock()
	b.end()

	// We can end here also if the batcher has already been closed and a
	// new one has been created. So reset the loader state only if the batcher
	// is the current one
	if l.curBatcher == b {
		l.reset()
	}
	l.batchLock.Unlock()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"runtime"
	"testing"
	"time"
)

//  To run these tests, you need to build a system that is set up as follows:
//      GPIO5  -> <1k Resistor  -> LED    -> GND
//      +3.3v  -> 4.7k Resistor -> GPIO19 -> 10uF capacitor -> GND
//      +3.3v  ->  10k Resistor -> GPIO6  -> 10uF capacitor -> GND
//      GPIO13 -> 4.7k Resistor -> Relay Input
//      GPIO26 -> Open Relay Terminals -> GND, relay will push the button...
//
//      SUPER IMPORTANT: Create an empty file in the current directory named TestRig
//                       to tell the test system it's ok to run these tests.

const (
	LED      = 5  // Pin 29
	RELAY    = 13 // Pin 33
	CAP4700  = 19 // Pin 35: Also PCM capable
	CAP10000 = 6  // Pin 31
	SWITCH   = 26 // Pin 37
)

var TestRig bool = false
var Led PiPin
var TestRelay *Relay
var Cap4700 PiPin
var Cap10000 PiPin
var Switch PiPin

var r10000 float64 = 9930.0
var r4700 float64 = 4600.0

func GpioStr(g PiPin) string {
	switch g.Pin() {
	case LED:
		return "LED"
	case RELAY:
		return "RELAY"
	case CAP4700:
		return "CAP4700"
	case CAP10000:
		return "CAP10000"
	case SWITCH:
		return "SWITCH"
	default:
		return "UNKNOWN"
	}
	return ""
}

func SkipTestIfNotTestRig(t *testing.T) {
	if TestRig {
		return
	}

	if runtime.GOOS != "linux" {
		t.Skipf("This system is not appropriate for the test: %s", runtime.GOOS)
		return
	}
	if runtime.GOARCH != "arm" {
		t.Skipf("This system is not appropriate for the test: %s", runtime.GOARCH)
		return
	}

	if _, err := os.Stat("TestRig"); err == nil {
		TestRig = true
		return
	}
	t.SkipNow()
}

func ExpectedState(t *testing.T, gpio PiPin, exp GpioState) {
	if val := gpio.Read(); val != exp {
		t.Errorf("%s: Expected %s but found %s", GpioStr(gpio), exp, val)
	}
}

func TestInitilization(t *testing.T) {
	SkipTestIfNotTestRig(t)
	err := GpioInit()
	t.Run("Init Host", func(t *testing.T) {
		if err != nil {
			t.Errorf("Problem initializing gpio: %s", err.Error())
		}
	})

	// Initialized GPIOs
	Led = NewGpio(LED)
	Led.Output(Low)
	ExpectedState(t, Led, Low)
}

func TestBlinkLed(t *testing.T) {
	SkipTestIfNotTestRig(t)
	Info("Running %s", t.Name())
	for i := 0; i < 6; i++ {
		time.Sleep(time.Second / 5)
		Led.Output(High)
		ExpectedState(t, Led, High)
		time.Sleep(time.Second / 5)
		Led.Output(Low)
		ExpectedState(t, Led, Low)
	}
}

func doStop(button *Button, b *bool, t time.Time) {
	*b = false
	button.Stop()
	*b = true
	Info("doStop - Stopped after %d ms", time.Now().Sub(t)/time.Millisecond)
}

func runRelayTestOn(t *testing.T, relay *Relay) {
	relay.TurnOn()
	Info("Testing Relay On: %s is %s", relay.Name(), relay.Status())
	if !relay.isOn() {
		t.Errorf("Relay(%s) is %s", relay.Name(), relay.Status())
	}
}

func runRelayTestOff(t *testing.T, relay *Relay) {
	relay.TurnOff()
	Info("Testing Relay Off: %s is %s", relay.Name(), relay.Status())
	if relay.isOn() {
		t.Errorf("Relay(%s) is %s", relay.Name(), relay.Status())
	}
}

func runRelayTest(t *testing.T, r *Relay, sleep time.Duration) {
	t.Run(fmt.Sprintf("%s.Test", r.Name()), func(t *testing.T) {
		Info("Running %s", t.Name())
		runRelayTestOn(t, r)
		time.Sleep(sleep)
		runRelayTestOff(t, r)
	})
}

func TestRelays(t *testing.T) {
	Info("Running %s", t.Name())
	TestRelay = NewRelay(RELAY, "Relay", "Testing")
	runRelayTest(t, TestRelay, time.Second)
}

func discharge_us(t *GpioThermometer, e Edge, p Pull) time.Duration {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	//Discharge the capacitor (low temps could make this really long)
	t.pin.Output(Low)
	time.Sleep(300 * time.Millisecond)

	// Start polling
	start := time.Now()
	t.pin.InputEdge(p, e)
	if !t.pin.WaitForEdge(time.Second / 2) {
		Trace("Thermometer %s, Rising read timed out", t.Name())
		return 0.0
	}
	stop := time.Now()
	t.pin.Output(Low)
	return stop.Sub(start)
}

func TestDischargeStrategies(t *testing.T) {
	SkipTestIfNotTestRig(t)
	t.Skip("Only for experimentation, not a real test")
	Info("Running %s", t.Name())
	therm := NewGpioThermometer("Fixed 4.7kOhm ResistorTest", "TestManufacturer", CAP4700)
	pulls := []Pull{PullDown, PullUp, Float}
	edges := []Edge{RisingEdge, FallingEdge, BothEdges}
	expected := r4700 * therm.microfarads / 2
	Info("Strategy: Pull, Edge, Expected, Average, Stddev, PctVar")
	for _, p := range pulls {
		for _, e := range edges {
			h := NewHistory(10)
			for i := 0; i < 10; i++ {
				dt := discharge_us(therm, e, p)
				h.Push(us(dt))
			}
			Info("Strategy: %s, %s, %0.3f, %0.3f, %0.4f, %0.2f",
				p, e, expected, h.Average(), h.Stddev(), 100.0*h.Stddev()/h.Average())
		}
	}
}

func TestThermometer(t *testing.T) {
	SkipTestIfNotTestRig(t)
	Info("Running %s", t.Name())
	therm := NewGpioThermometer("Fixed 4.7kOhm ResistorTest", "TestManufacturer", CAP4700)

	t.Run("Calibrate Cap4700", func(t *testing.T) {
		Info("Running %s", t.Name())
		err := therm.Calibrate(r4700)
		if err != nil {
			t.Errorf("Failure to Calibrate successfully: %s", err.Error())
		}
	})
	t.Run("Temperature Cap4700", func(t *testing.T) {
		Info("Running %s", t.Name())
		err := therm.Update()
		if err != nil {
			t.Errorf("Thermometer update failed: %s", err.Error())
		}
		if therm.Temperature() > 44.1 || therm.Temperature() < 43.1 {
			t.Errorf("Thermometer value off: %0.1f, expected 43.6",
				therm.Temperature())
		}
	})

	therm = NewGpioThermometer("Fixed 10kOhm ResistorTest", "TestManufacturer", CAP10000)
	t.Run("Calibrate Cap10000", func(t *testing.T) {
		Info("Running %s", t.Name())
		err := therm.Calibrate(r10000)
		if err != nil {
			t.Errorf("Failure to Calibrate successfully: %s", err.Error())
		}
	})
	t.Run("Temperature Cap10000", func(t *testing.T) {
		Info("Running %s", t.Name())
		err := therm.Update()
		if err != nil {
			t.Errorf("Thermometer update failed: %s", err.Error())
		}
		if therm.Temperature() > 25.4 || therm.Temperature() < 24.4 {
			t.Errorf("Thermometer value off: %0.1f, expected 24.9",
				therm.Temperature())
		}
	})
}

func TestPushButton(t *testing.T) {
	SkipTestIfNotTestRig(t)
	Info("Running %s", t.Name())
	wasRun := 0
	button := NewGpioButton(SWITCH, func() {
		wasRun++
		Led.Output(High)
		Info("Button Pushed %d!!!", wasRun)
	})

	Info("Starting button test, push it 3 times!")
	button.Start()
	time.Sleep(time.Second / 2) // let it start TODO Channel?
	for i := 0; i < 3; i++ {
		TestRelay.TurnOn()
		time.Sleep(time.Second / 3)
		TestRelay.TurnOff()
		Led.Output(Low)
		time.Sleep(2 * time.Second)
	}
	if wasRun < 3 {
		t.Errorf("Expected 3 button pushes")
	}
	Info("Stopping button job")
	exited := false
	go doStop(button, &exited, time.Now())
	time.Sleep(time.Second)
	if !exited {
		t.Errorf("Button loop should have stopped within time allotted")
	}
	Info("Button job stopped")
}
<commit_msg>test sync<commit_after>package main

import (
	"fmt"
	"os"
	"runtime"
	"testing"
	"time"
)

//  To run these tests, you need to build a system that is set up as follows:
//      GPIO5  -> <1k Resistor  -> LED    -> GND
//      +3.3v  -> 4.7k Resistor -> GPIO19 -> 10uF capacitor -> GND
//      +3.3v  ->  10k Resistor -> GPIO6  -> 10uF capacitor -> GND
//      GPIO13 -> 4.7k Resistor -> Relay Input
//      GPIO26 -> Open Relay Terminals -> GND, relay will push the button...
//
//      SUPER IMPORTANT: Create an empty file in the current directory named TestRig
//                       to tell the test system it's ok to run these tests.

const (
	LED      = 5  // Pin 29
	RELAY    = 13 // Pin 33
	CAP4700  = 19 // Pin 35: Also PCM capable
	CAP10000 = 6  // Pin 31
	SWITCH   = 26 // Pin 37
)

var TestRig bool = false
var Led PiPin
var TestRelay *Relay
var Cap4700 PiPin
var Cap10000 PiPin
var Switch PiPin

var r10000 float64 = 9930.0
var r4700 float64 = 4600.0

func TestInitilization(t *testing.T) {
	SkipTestIfNotTestRig(t)
	err := GpioInit()
	t.Run("Init Host", func(t *testing.T) {
		if err != nil {
			t.Errorf("Problem initializing gpio: %s", err.Error())
		}
	})

	// Initialized GPIOs
	Led = NewGpio(LED)
	Led.Output(Low)
	ExpectedState(t, Led, Low)
}

func TestBlinkLed(t *testing.T) {
	SkipTestIfNotTestRig(t)
	Info("Running %s", t.Name())
	for i := 0; i < 6; i++ {
		time.Sleep(time.Second / 5)
		Led.Output(High)
		ExpectedState(t, Led, High)
		time.Sleep(time.Second / 5)
		Led.Output(Low)
		ExpectedState(t, Led, Low)
	}
}

func TestRelays(t *testing.T) {
	SkipTestIfNotTestRig(t)
	Info("Running %s", t.Name())
	TestRelay = NewRelay(RELAY, "Relay", "Testing")
	runRelayTest(t, TestRelay, time.Second)
}

func TestDischargeStrategies(t *testing.T) {
	SkipTestIfNotTestRig(t)
	t.Skip("Only for experimentation, not a real test")
	Info("Running %s", t.Name())
	therm := NewGpioThermometer("Fixed 4.7kOhm ResistorTest", "TestManufacturer", CAP4700)
	pulls := []Pull{PullDown, PullUp, Float}
	edges := []Edge{RisingEdge, FallingEdge, BothEdges}
	expected := r4700 * therm.microfarads / 2
	Info("Strategy: Pull, Edge, Expected, Average, Stddev, PctVar")
	for _, p := range pulls {
		for _, e := range edges {
			h := NewHistory(10)
			for i := 0; i < 10; i++ {
				dt := discharge_us(therm, e, p)
				h.Push(us(dt))
			}
			Info("Strategy: %s, %s, %0.3f, %0.3f, %0.4f, %0.2f",
				p, e, expected, h.Average(), h.Stddev(), 100.0*h.Stddev()/h.Average())
		}
	}
}

func TestThermometer(t *testing.T) {
	SkipTestIfNotTestRig(t)
	Info("Running %s", t.Name())
	therm := NewGpioThermometer("Fixed 4.7kOhm ResistorTest", "TestManufacturer", CAP4700)

	t.Run("Calibrate Cap4700", func(t *testing.T) {
		Info("Running %s", t.Name())
		err := therm.Calibrate(r4700)
		if err != nil {
			t.Errorf("Failure to Calibrate successfully: %s", err.Error())
		}
	})
	t.Run("Temperature Cap4700", func(t *testing.T) {
		Info("Running %s", t.Name())
		err := therm.Update()
		if err != nil {
			t.Errorf("Thermometer update failed: %s", err.Error())
		}
		if therm.Temperature() > 44.1 || therm.Temperature() < 43.1 {
			t.Errorf("Thermometer value off: %0.1f, expected 43.6",
				therm.Temperature())
		}
	})

	therm = NewGpioThermometer("Fixed 10kOhm ResistorTest", "TestManufacturer", CAP10000)
	t.Run("Calibrate Cap10000", func(t *testing.T) {
		Info("Running %s", t.Name())
		err := therm.Calibrate(r10000)
		if err != nil {
			t.Errorf("Failure to Calibrate successfully: %s", err.Error())
		}
	})
	t.Run("Temperature Cap10000", func(t *testing.T) {
		Info("Running %s", t.Name())
		err := therm.Update()
		if err != nil {
			t.Errorf("Thermometer update failed: %s", err.Error())
		}
		if therm.Temperature() > 25.4 || therm.Temperature() < 24.4 {
			t.Errorf("Thermometer value off: %0.1f, expected 24.9",
				therm.Temperature())
		}
	})
}

func TestPushButton(t *testing.T) {
	SkipTestIfNotTestRig(t)
	Info("Running %s", t.Name())
	wasRun := 0
	button := NewGpioButton(SWITCH, func() {
		wasRun++
		Led.Output(High)
		Info("Button Pushed %d!!!", wasRun)
	})

	Info("Starting button test, push it 3 times!")
	button.Start()
	time.Sleep(time.Second / 2) // let it start TODO Channel?
	for i := 0; i < 3; i++ {
		TestRelay.TurnOn()
		time.Sleep(time.Second / 3)
		TestRelay.TurnOff()
		Led.Output(Low)
		time.Sleep(2 * time.Second)
	}
	if wasRun < 3 {
		t.Errorf("Expected 3 button pushes")
	}
	Info("Stopping button job")
	exited := false
	go doStop(button, &exited, time.Now())
	time.Sleep(time.Second)
	if !exited {
		t.Errorf("Button loop should have stopped within time allotted")
	}
	Info("Button job stopped")
}

func discharge_us(t *GpioThermometer, e Edge, p Pull) time.Duration {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	//Discharge the capacitor (low temps could make this really long)
	t.pin.Output(Low)
	time.Sleep(300 * time.Millisecond)

	// Start polling
	start := time.Now()
	t.pin.InputEdge(p, e)
	if !t.pin.WaitForEdge(time.Second / 2) {
		Trace("Thermometer %s, Rising read timed out", t.Name())
		return 0.0
	}
	stop := time.Now()
	t.pin.Output(Low)
	return stop.Sub(start)
}

func doStop(button *Button, b *bool, t time.Time) {
	*b = false
	button.Stop()
	*b = true
	Info("doStop - Stopped after %d ms", time.Now().Sub(t)/time.Millisecond)
}

func runRelayTestOn(t *testing.T, relay *Relay) {
	relay.TurnOn()
	Info("Testing Relay On: %s is %s", relay.Name(), relay.Status())
	if !relay.isOn() {
		t.Errorf("Relay(%s) is %s", relay.Name(), relay.Status())
	}
}

func runRelayTestOff(t *testing.T, relay *Relay) {
	relay.TurnOff()
	Info("Testing Relay Off: %s is %s", relay.Name(), relay.Status())
	if relay.isOn() {
		t.Errorf("Relay(%s) is %s", relay.Name(), relay.Status())
	}
}

func runRelayTest(t *testing.T, r *Relay, sleep time.Duration) {
	t.Run(fmt.Sprintf("%s.Test", r.Name()), func(t *testing.T) {
		Info("Running %s", t.Name())
		runRelayTestOn(t, r)
		time.Sleep(sleep)
		runRelayTestOff(t, r)
	})
}

func GpioStr(g PiPin) string {
	switch g.Pin() {
	case LED:
		return "LED"
	case RELAY:
		return "RELAY"
	case CAP4700:
		return "CAP4700"
	case CAP10000:
		return "CAP10000"
	case SWITCH:
		return "SWITCH"
	default:
		return "UNKNOWN"
	}
	return ""
}

func SkipTestIfNotTestRig(t *testing.T) {
	if TestRig {
		return
	}

	if runtime.GOOS != "linux" {
		t.SkipNow()
	}
	if runtime.GOARCH != "arm" {
		t.SkipNow()
	}

	if _, err := os.Stat("TestRig"); err == nil {
		TestRig = true
		return
	}
	t.SkipNow()
}

func ExpectedState(t *testing.T, gpio PiPin, exp GpioState) {
	if val := gpio.Read(); val != exp {
		t.Errorf("%s: Expected %s but found %s", GpioStr(gpio), exp, val)
	}
}
<|endoftext|>


<commit_before>/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

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

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

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/karlseguin/rcache"
	"github.com/nickvanw/ircx"
	"github.com/sorcix/irc"
)

type PlaceInfo struct {
	PlaceName string  `json:"place name"`
	State     string  `json:"state"`
	StateAbbr string  `json:"state abbreviation"`
	Latitude  float64 `json:"latitude,string"`
	Longitude float64 `json:"longitude,string"`
}

type ZipInfo struct {
	PostCode    string      `json:"post code"`
	Country     string      `json:"country"`
	CountryAbbr string      `json:"country abbreviation"`
	Places      []PlaceInfo `json:"places"`
}

type Current struct {
	Time                 int64   `json:"time"`
	Summary              string  `json:"summary"`
	Icon                 string  `json:"icon"`
	NearestStormDistance float64 `json:"nearestStormDistance"`
	NearestStormBearing  float64 `json:"nearestStormBearing"`
	PrecipIntensity      float64 `json:"precipIntensity"`
	PrecipProbability    float64 `json:"precipProbability"`
	Temperature          float64 `json:"temperature"`
	ApparentTemperature  float64 `json:"apparentTemperature"`
	DewPoint             float64 `json:"dewPoint"`
	Humidity             float64 `json:"humidity"`
	WindSpeed            float64 `json:"windSpeed"`
	WindBearing          float64 `json:"windBearing"`
	Visibility           float64 `json:"visibility"`
	CloudCover           float64 `json:"cloudCover"`
	Pressure             float64 `json:"pressure"`
	Ozone                float64 `json:"ozone"`
}

type Minutely struct {
	Summary string `json:"summary"`
}

type Hourly struct {
	Summary string `json:"summary"`
}

type Daily struct {
	Summary string `json:"summary"`
}

type WeatherReport struct {
	Latitude  float64  `json:"latitude"`
	Longitude float64  `json:"longitude"`
	Timezone  string   `json:"timezone"`
	Offset    float64  `json:"offset"`
	Currently Current  `json:"currently"`
	Minutely  Minutely `json:"minutely"`
	Hourly    Hourly   `json:"hourly"`
	Daily     Daily    `json:"daily"`
}

var cache *rcache.Cache

func fetcher(key string) interface{} {
	var z ZipInfo

	log.Println("Looking up coordinates for zip:", key)
	resp, err := http.Get(fmt.Sprintf("http://api.zippopotam.us/us/%s", key))

	if err != nil {
		return nil
	}

	defer resp.Body.Close()
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&z)

	if err != nil {
		return nil
	}

	return &z
}

func init() {
	cache = rcache.New(fetcher, time.Hour*24*7)
}

func GetWeather(s ircx.Sender, message *irc.Message) {
	if len(message.Trailing) == 5 {
		if _, err := strconv.Atoi(message.Trailing); err == nil {
			p := message.Params
			if p[0] == config.General.Name {
				p = []string{message.Prefix.Name}
			}

			m := &irc.Message{
				Command: irc.PRIVMSG,
				Params:  p,
			}

			z := cache.Get(message.Trailing).(*ZipInfo)

			if z != nil && z.Places != nil {
				resp, err := http.Get(fmt.Sprint("https://api.forecast.io/forecast/", config.Forecast.Key, "/",
					z.Places[0].Latitude, ",", z.Places[0].Longitude, "?exclude=flags"))
				if err != nil {
					// handle error
					return
				}
				defer resp.Body.Close()

				dec := json.NewDecoder(resp.Body)

				var w WeatherReport
				err = dec.Decode(&w)

				l, _ := time.LoadLocation(w.Timezone)

				t := time.Unix(w.Currently.Time, 0).In(l)

				log.Println("Sending weather for", message.Trailing)

				m.Trailing = fmt.Sprint(message.Prefix.Name, ": ", z.Places[0].PlaceName, ", ", z.Places[0].StateAbbr,
					" (", z.Places[0].Latitude, ", ", z.Places[0].Longitude, ") ", t, " - ",
					w.Currently.Temperature, "F (feels like ", w.Currently.ApparentTemperature, "F) - ",
					w.Currently.Summary)
				s.Send(m)

				m.Trailing = fmt.Sprint(message.Prefix.Name, ": ",
					w.Currently.Humidity*100, "% Humidity - ",
					"Wind from ", w.Currently.WindBearing, "° at ", w.Currently.WindSpeed, "MPH - ",
					"Visibility ", w.Currently.Visibility, " Miles - ",
					"Cloud Cover ", w.Currently.CloudCover*100, "% - ",
					"Precipitation Probability ", w.Currently.PrecipProbability*100, "%")
				s.Send(m)

				m.Trailing = fmt.Sprint(message.Prefix.Name, ": Next Hour - ", w.Minutely.Summary)
				s.Send(m)

				m.Trailing = fmt.Sprint(message.Prefix.Name, ": Next Day - ", w.Hourly.Summary)
				s.Send(m)

				m.Trailing = fmt.Sprint(message.Prefix.Name, ": Next Week - ", w.Daily.Summary)
				s.Send(m)
			}
		}
	}
}
<commit_msg>Move forecasts to single line<commit_after>/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

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

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

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/karlseguin/rcache"
	"github.com/nickvanw/ircx"
	"github.com/sorcix/irc"
)

type PlaceInfo struct {
	PlaceName string  `json:"place name"`
	State     string  `json:"state"`
	StateAbbr string  `json:"state abbreviation"`
	Latitude  float64 `json:"latitude,string"`
	Longitude float64 `json:"longitude,string"`
}

type ZipInfo struct {
	PostCode    string      `json:"post code"`
	Country     string      `json:"country"`
	CountryAbbr string      `json:"country abbreviation"`
	Places      []PlaceInfo `json:"places"`
}

type Current struct {
	Time                 int64   `json:"time"`
	Summary              string  `json:"summary"`
	Icon                 string  `json:"icon"`
	NearestStormDistance float64 `json:"nearestStormDistance"`
	NearestStormBearing  float64 `json:"nearestStormBearing"`
	PrecipIntensity      float64 `json:"precipIntensity"`
	PrecipProbability    float64 `json:"precipProbability"`
	Temperature          float64 `json:"temperature"`
	ApparentTemperature  float64 `json:"apparentTemperature"`
	DewPoint             float64 `json:"dewPoint"`
	Humidity             float64 `json:"humidity"`
	WindSpeed            float64 `json:"windSpeed"`
	WindBearing          float64 `json:"windBearing"`
	Visibility           float64 `json:"visibility"`
	CloudCover           float64 `json:"cloudCover"`
	Pressure             float64 `json:"pressure"`
	Ozone                float64 `json:"ozone"`
}

type Minutely struct {
	Summary string `json:"summary"`
}

type Hourly struct {
	Summary string `json:"summary"`
}

type Daily struct {
	Summary string `json:"summary"`
}

type WeatherReport struct {
	Latitude  float64  `json:"latitude"`
	Longitude float64  `json:"longitude"`
	Timezone  string   `json:"timezone"`
	Offset    float64  `json:"offset"`
	Currently Current  `json:"currently"`
	Minutely  Minutely `json:"minutely"`
	Hourly    Hourly   `json:"hourly"`
	Daily     Daily    `json:"daily"`
}

var cache *rcache.Cache

func fetcher(key string) interface{} {
	var z ZipInfo

	log.Println("Looking up coordinates for zip:", key)
	resp, err := http.Get(fmt.Sprintf("http://api.zippopotam.us/us/%s", key))

	if err != nil {
		return nil
	}

	defer resp.Body.Close()
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&z)

	if err != nil {
		return nil
	}

	return &z
}

func init() {
	cache = rcache.New(fetcher, time.Hour*24*7)
}

func GetWeather(s ircx.Sender, message *irc.Message) {
	if len(message.Trailing) == 5 {
		if _, err := strconv.Atoi(message.Trailing); err == nil {
			p := message.Params
			if p[0] == config.General.Name {
				p = []string{message.Prefix.Name}
			}

			m := &irc.Message{
				Command: irc.PRIVMSG,
				Params:  p,
			}

			z := cache.Get(message.Trailing).(*ZipInfo)

			if z != nil && z.Places != nil {
				resp, err := http.Get(fmt.Sprint("https://api.forecast.io/forecast/", config.Forecast.Key, "/",
					z.Places[0].Latitude, ",", z.Places[0].Longitude, "?exclude=flags"))
				if err != nil {
					// handle error
					return
				}
				defer resp.Body.Close()

				dec := json.NewDecoder(resp.Body)

				var w WeatherReport
				err = dec.Decode(&w)

				l, _ := time.LoadLocation(w.Timezone)

				t := time.Unix(w.Currently.Time, 0).In(l)

				log.Println("Sending weather for", message.Trailing)

				m.Trailing = fmt.Sprint(message.Prefix.Name, ": ", z.Places[0].PlaceName, ", ", z.Places[0].StateAbbr,
					" (", z.Places[0].Latitude, ", ", z.Places[0].Longitude, ") ", t, " - ",
					w.Currently.Temperature, "F (feels like ", w.Currently.ApparentTemperature, "F) - ",
					w.Currently.Summary)
				s.Send(m)

				m.Trailing = fmt.Sprint(message.Prefix.Name, ": ",
					w.Currently.Humidity*100, "% Humidity - ",
					"Wind from ", w.Currently.WindBearing, "° at ", w.Currently.WindSpeed, "MPH - ",
					"Visibility ", w.Currently.Visibility, " Miles - ",
					"Cloud Cover ", w.Currently.CloudCover*100, "% - ",
					"Precipitation Probability ", w.Currently.PrecipProbability*100, "%")
				s.Send(m)

				m.Trailing = fmt.Sprint(message.Prefix.Name, ": ", w.Minutely.Summary, " ", w.Hourly.Summary, " ", w.Daily.Summary)
				s.Send(m)
			}
		}
	}
}
<|endoftext|>


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

package virtcontainers

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"syscall"

	vcAnnotations "github.com/containers/virtcontainers/pkg/annotations"

	kataclient "github.com/kata-containers/agent/protocols/client"
	"github.com/kata-containers/agent/protocols/grpc"

	"github.com/opencontainers/runtime-spec/specs-go"
)

var (
	defaultKataSockPathTemplate = "%s/%s/kata.sock"
	defaultKataChannel          = "io.katacontainers.channel"
	defaultKataDeviceID         = "channel0"
	defaultKataID               = "charch0"
	errorMissingGRPClient       = errors.New("Missing gRPC client")
	errorMissingOCISpec         = errors.New("Missing OCI specification")
	kataHostSharedDir           = "/tmp/kata-containers/shared/pods/"
	kataGuestSharedDir          = "/tmp/kata-containers/shared/pods/"
	mountGuest9pTag             = "kataShared"
	type9pFs                    = "9p"
	devPath                     = "/dev"
)

// KataAgentConfig is a structure storing information needed
// to reach the Kata Containers agent.
type KataAgentConfig struct {
	GRPCSocket string
	Volumes    []Volume
	SocketPath string
	VMSocket   Socket
}

func (c *KataAgentConfig) validate(pod *Pod) bool {
	if c.SocketPath == "" {
		c.SocketPath = fmt.Sprintf(defaultKataSockPathTemplate, runStoragePath, pod.id)

		c.VMSocket = Socket{
			DeviceID: defaultKataDeviceID,
			ID:       defaultKataID,
			HostPath: c.SocketPath,
			Name:     defaultKataChannel,
		}
	}

	return true
}

type kataAgent struct {
	config KataAgentConfig

	client *kataclient.AgentClient
}

func (k *kataAgent) init(pod *Pod, config interface{}) error {
	switch c := config.(type) {
	case KataAgentConfig:
		if c.validate(pod) == false {
			return fmt.Errorf("Invalid Kata agent configuration: %v", c)
		}
		k.config = c
	default:
		return fmt.Errorf("Invalid config type")
	}

	// Override pod agent configuration
	pod.config.AgentConfig = k.config

	client, err := kataclient.NewAgentClient(k.config.GRPCSocket)
	if err != nil {
		return err
	}

	k.client = client

	return nil
}

func (k *kataAgent) vmURL() (string, error) {
	return "", nil
}

func (k *kataAgent) setProxyURL(url string) error {
	return nil
}

func (k *kataAgent) capabilities() capabilities {
	return capabilities{}
}

func (k *kataAgent) createPod(pod *Pod) error {
	for _, volume := range k.config.Volumes {
		err := pod.hypervisor.addDevice(volume, fsDev)
		if err != nil {
			return err
		}
	}

	// TODO Look at the grpc scheme to understand if we want
	// a serial or a vsock socket.
	err := pod.hypervisor.addDevice(k.config.VMSocket, serialPortDev)
	if err != nil {
		return err
	}

	// Adding the shared volume.
	// This volume contains all bind mounted container bundles.
	sharedVolume := Volume{
		MountTag: mountGuest9pTag,
		HostPath: filepath.Join(kataHostSharedDir, pod.id),
	}

	if err := os.MkdirAll(sharedVolume.HostPath, dirMode); err != nil {
		return err
	}

	return pod.hypervisor.addDevice(sharedVolume, fsDev)
}

func (k *kataAgent) exec(pod *Pod, c Container, process Process, cmd Cmd) error {
	return nil
}

func (k *kataAgent) startPod(pod Pod) error {
	if k.client == nil {
		return errorMissingGRPClient
	}

	hostname := pod.config.Hostname
	if len(hostname) > maxHostnameLen {
		hostname = hostname[:maxHostnameLen]
	}

	// We mount the shared directory in a predefined location
	// in the guest.
	// This is where at least some of the host config files
	// (resolv.conf, etc...) and potentially all container
	// rootfs will reside.
	sharedVolume := &grpc.Storage{
		Source:     mountGuest9pTag,
		MountPoint: kataGuestSharedDir,
		Fstype:     type9pFs,
		Options:    []string{"trans=virtio", "nodev"},
	}

	req := &grpc.CreateSandboxRequest{
		Hostname:     hostname,
		Storages:     []*grpc.Storage{sharedVolume},
		SandboxPidns: true,
	}
	_, err := k.client.CreateSandbox(context.Background(), req)
	return err
}

func (k *kataAgent) stopPod(pod Pod) error {
	if k.client == nil {
		return errorMissingGRPClient
	}

	req := &grpc.DestroySandboxRequest{}
	_, err := k.client.DestroySandbox(context.Background(), req)
	return err
}

func appendStorageFromMounts(storage []*grpc.Storage, mounts []*Mount) []*grpc.Storage {
	for _, m := range mounts {
		s := &grpc.Storage{
			Source:     m.Source,
			MountPoint: m.Destination,
		}

		storage = append(storage, s)
	}

	return storage
}

func (k *kataAgent) createContainer(pod *Pod, c *Container) error {
	if k.client == nil {
		return errorMissingGRPClient
	}

	ociSpecJSON, ok := c.config.Annotations[vcAnnotations.ConfigJSONKey]
	if !ok {
		return errorMissingOCISpec
	}

	var ociSpec specs.Spec
	if err := json.Unmarshal([]byte(ociSpecJSON), &ociSpec); err != nil {
		return err
	}

	grpcSpec, err := grpc.OCItoGRPC(&ociSpec)
	if err != nil {
		return err
	}

	var containerStorage []*grpc.Storage

	// The rootfs storage volume represents the container rootfs
	// mount point inside the guest.
	// It can be a block based device (when using block based container
	// overlay on the host) mount or a 9pfs one (for all other overlay
	// implementations).
	rootfs := &grpc.Storage{}

	// First we need to give the OCI spec our absolute path in the guest.
	grpcSpec.Root.Path = filepath.Join(kataGuestSharedDir, pod.id, c.id, rootfsDir)

	if c.state.Fstype != "" {
		// This is a block based device rootfs.
		// driveName is the predicted virtio-block guest name (the vd* in /dev/vd*).
		driveName, err := getVirtDriveName(c.state.BlockIndex)
		if err != nil {
			return err
		}

		rootfs.Source = filepath.Join(devPath, driveName)
		rootfs.MountPoint = grpcSpec.Root.Path // Should we remove the "rootfs" suffix?
		rootfs.Fstype = c.state.Fstype

		// Add rootfs to the list of container storage.
		// We only need to do this for block based rootfs, as we
		// want the agent to mount it into the right location
		// (/tmp/kata-containers/shared/pods/podID/ctrID/
		containerStorage = append(containerStorage, rootfs)

	} else {
		// This is not a block based device rootfs.
		// We are going to bind mount it into the 9pfs
		// shared drive between the host and the guest.
		// With 9pfs we don't need to ask the agent to
		// mount the rootfs as the shared directory
		// (/tmp/kata-containers/shared/pods/) is already
		// mounted in the guest. We only need to mount the
		// rootfs from the host and it will show up in the guest.
		if err := bindMountContainerRootfs(kataHostSharedDir, pod.id, c.id, c.rootFs, false); err != nil {
			bindUnmountAllRootfs(kataHostSharedDir, *pod)
			return err
		}
	}

	// Handle container mounts
	newMounts, err := bindMountContainerMounts(kataHostSharedDir, pod.id, c.id, c.mounts)
	if err != nil {
		bindUnmountAllRootfs(kataHostSharedDir, *pod)
		return err
	}
	containerStorage = appendStorageFromMounts(containerStorage, newMounts)

	// Append container mounts for block devices passed with --device.
	for _, device := range c.devices {
		d, ok := device.(*BlockDevice)

		if !ok {
			continue
		}

		deviceStorage := &grpc.Storage{
			Source:     d.VirtPath,
			MountPoint: d.DeviceInfo.ContainerPath,
		}

		containerStorage = append(containerStorage, deviceStorage)
	}

	req := &grpc.CreateContainerRequest{
		ContainerId: c.id,
		Storages:    containerStorage,
		OCI:         grpcSpec,
	}

	_, err = k.client.CreateContainer(context.Background(), req)
	return err
}

func (k *kataAgent) startContainer(pod Pod, c Container) error {
	if k.client == nil {
		return errorMissingGRPClient
	}

	req := &grpc.StartContainerRequest{
		ContainerId: c.id,
	}

	_, err := k.client.StartContainer(context.Background(), req)
	return err
}

func (k *kataAgent) stopContainer(pod Pod, c Container) error {
	return nil
}

func (k *kataAgent) killContainer(pod Pod, c Container, signal syscall.Signal, all bool) error {
	return nil
}

func (k *kataAgent) processListContainer(pod Pod, c Container, options ProcessListOptions) (ProcessList, error) {
	return nil, nil
}
<commit_msg>kata_agent: Initial VSOCK support<commit_after>//
// Copyright (c) 2017 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package virtcontainers

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"

	vcAnnotations "github.com/containers/virtcontainers/pkg/annotations"

	kataclient "github.com/kata-containers/agent/protocols/client"
	"github.com/kata-containers/agent/protocols/grpc"

	"github.com/opencontainers/runtime-spec/specs-go"
)

var (
	defaultKataSockPathTemplate = "%s/%s/kata.sock"
	defaultKataChannel          = "io.katacontainers.channel"
	defaultKataDeviceID         = "channel0"
	defaultKataID               = "charch0"
	errorMissingGRPClient       = errors.New("Missing gRPC client")
	errorMissingOCISpec         = errors.New("Missing OCI specification")
	kataHostSharedDir           = "/tmp/kata-containers/shared/pods/"
	kataGuestSharedDir          = "/tmp/kata-containers/shared/pods/"
	mountGuest9pTag             = "kataShared"
	type9pFs                    = "9p"
	devPath                     = "/dev"
	vsockSocketScheme           = "vsock"
)

// KataAgentConfig is a structure storing information needed
// to reach the Kata Containers agent.
type KataAgentConfig struct {
	GRPCSocket string
	Volumes    []Volume
}

type kataVSOCK struct {
	contextID uint32
	port      uint32
}

type kataAgent struct {
	config KataAgentConfig

	client   *kataclient.AgentClient
	vmSocket interface{}
}

func parseVSOCKAddr(sock string) (uint32, uint32, error) {
	sp := strings.Split(sock, ":")
	if len(sp) != 3 {
		return 0, 0, fmt.Errorf("Invalid vsock address: %s", sock)
	}
	if sp[0] != vsockSocketScheme {
		return 0, 0, fmt.Errorf("Invalid vsock URL scheme: %s", sp[0])
	}

	cid, err := strconv.ParseUint(sp[1], 10, 32)
	if err != nil {
		return 0, 0, fmt.Errorf("Invalid vsock cid: %s", sp[1])
	}
	port, err := strconv.ParseUint(sp[2], 10, 32)
	if err != nil {
		return 0, 0, fmt.Errorf("Invalid vsock port: %s", sp[2])
	}

	return uint32(cid), uint32(port), nil
}

func (k *kataAgent) validateConfig(pod *Pod, c *KataAgentConfig) error {
	if c.GRPCSocket == "" {
		return fmt.Errorf("Empty gRPC socket path")
	}

	cid, port, err := parseVSOCKAddr(c.GRPCSocket)
	if err != nil {
		// We need to generate a host UNIX socket path for the emulated serial port.
		k.vmSocket = Socket{
			DeviceID: defaultKataDeviceID,
			ID:       defaultKataID,
			HostPath: fmt.Sprintf(defaultKataSockPathTemplate, runStoragePath, pod.id),
			Name:     defaultKataChannel,
		}
	} else {
		// We want to go through VSOCK. The VM VSOCK endpoint will be our gRPC.
		k.vmSocket = kataVSOCK{
			contextID: cid,
			port:      port,
		}
	}

	return nil
}

func (k *kataAgent) init(pod *Pod, config interface{}) error {
	switch c := config.(type) {
	case KataAgentConfig:
		if err := k.validateConfig(pod, &c); err != nil {
			return err
		}
		k.config = c
	default:
		return fmt.Errorf("Invalid config type")
	}

	// Override pod agent configuration
	pod.config.AgentConfig = k.config

	client, err := kataclient.NewAgentClient(k.config.GRPCSocket)
	if err != nil {
		return err
	}

	k.client = client

	return nil
}

func (k *kataAgent) vmURL() (string, error) {
	return "", nil
}

func (k *kataAgent) setProxyURL(url string) error {
	return nil
}

func (k *kataAgent) capabilities() capabilities {
	return capabilities{}
}

func (k *kataAgent) createPod(pod *Pod) error {
	for _, volume := range k.config.Volumes {
		err := pod.hypervisor.addDevice(volume, fsDev)
		if err != nil {
			return err
		}
	}

	switch s := k.vmSocket.(type) {
	case Socket:
		err := pod.hypervisor.addDevice(s, serialPortDev)
		if err != nil {
			return err
		}
	case kataVSOCK:
		// TODO Add an hypervisor vsock
	default:
		return fmt.Errorf("Invalid config type")
	}

	// Adding the shared volume.
	// This volume contains all bind mounted container bundles.
	sharedVolume := Volume{
		MountTag: mountGuest9pTag,
		HostPath: filepath.Join(kataHostSharedDir, pod.id),
	}

	if err := os.MkdirAll(sharedVolume.HostPath, dirMode); err != nil {
		return err
	}

	return pod.hypervisor.addDevice(sharedVolume, fsDev)
}

func (k *kataAgent) exec(pod *Pod, c Container, process Process, cmd Cmd) error {
	return nil
}

func (k *kataAgent) startPod(pod Pod) error {
	if k.client == nil {
		return errorMissingGRPClient
	}

	hostname := pod.config.Hostname
	if len(hostname) > maxHostnameLen {
		hostname = hostname[:maxHostnameLen]
	}

	// We mount the shared directory in a predefined location
	// in the guest.
	// This is where at least some of the host config files
	// (resolv.conf, etc...) and potentially all container
	// rootfs will reside.
	sharedVolume := &grpc.Storage{
		Source:     mountGuest9pTag,
		MountPoint: kataGuestSharedDir,
		Fstype:     type9pFs,
		Options:    []string{"trans=virtio", "nodev"},
	}

	req := &grpc.CreateSandboxRequest{
		Hostname:     hostname,
		Storages:     []*grpc.Storage{sharedVolume},
		SandboxPidns: true,
	}
	_, err := k.client.CreateSandbox(context.Background(), req)
	return err
}

func (k *kataAgent) stopPod(pod Pod) error {
	if k.client == nil {
		return errorMissingGRPClient
	}

	req := &grpc.DestroySandboxRequest{}
	_, err := k.client.DestroySandbox(context.Background(), req)
	return err
}

func appendStorageFromMounts(storage []*grpc.Storage, mounts []*Mount) []*grpc.Storage {
	for _, m := range mounts {
		s := &grpc.Storage{
			Source:     m.Source,
			MountPoint: m.Destination,
		}

		storage = append(storage, s)
	}

	return storage
}

func (k *kataAgent) createContainer(pod *Pod, c *Container) error {
	if k.client == nil {
		return errorMissingGRPClient
	}

	ociSpecJSON, ok := c.config.Annotations[vcAnnotations.ConfigJSONKey]
	if !ok {
		return errorMissingOCISpec
	}

	var ociSpec specs.Spec
	if err := json.Unmarshal([]byte(ociSpecJSON), &ociSpec); err != nil {
		return err
	}

	grpcSpec, err := grpc.OCItoGRPC(&ociSpec)
	if err != nil {
		return err
	}

	var containerStorage []*grpc.Storage

	// The rootfs storage volume represents the container rootfs
	// mount point inside the guest.
	// It can be a block based device (when using block based container
	// overlay on the host) mount or a 9pfs one (for all other overlay
	// implementations).
	rootfs := &grpc.Storage{}

	// First we need to give the OCI spec our absolute path in the guest.
	grpcSpec.Root.Path = filepath.Join(kataGuestSharedDir, pod.id, c.id, rootfsDir)

	if c.state.Fstype != "" {
		// This is a block based device rootfs.
		// driveName is the predicted virtio-block guest name (the vd* in /dev/vd*).
		driveName, err := getVirtDriveName(c.state.BlockIndex)
		if err != nil {
			return err
		}

		rootfs.Source = filepath.Join(devPath, driveName)
		rootfs.MountPoint = grpcSpec.Root.Path // Should we remove the "rootfs" suffix?
		rootfs.Fstype = c.state.Fstype

		// Add rootfs to the list of container storage.
		// We only need to do this for block based rootfs, as we
		// want the agent to mount it into the right location
		// (/tmp/kata-containers/shared/pods/podID/ctrID/
		containerStorage = append(containerStorage, rootfs)

	} else {
		// This is not a block based device rootfs.
		// We are going to bind mount it into the 9pfs
		// shared drive between the host and the guest.
		// With 9pfs we don't need to ask the agent to
		// mount the rootfs as the shared directory
		// (/tmp/kata-containers/shared/pods/) is already
		// mounted in the guest. We only need to mount the
		// rootfs from the host and it will show up in the guest.
		if err := bindMountContainerRootfs(kataHostSharedDir, pod.id, c.id, c.rootFs, false); err != nil {
			bindUnmountAllRootfs(kataHostSharedDir, *pod)
			return err
		}
	}

	// Handle container mounts
	newMounts, err := bindMountContainerMounts(kataHostSharedDir, pod.id, c.id, c.mounts)
	if err != nil {
		bindUnmountAllRootfs(kataHostSharedDir, *pod)
		return err
	}
	containerStorage = appendStorageFromMounts(containerStorage, newMounts)

	// Append container mounts for block devices passed with --device.
	for _, device := range c.devices {
		d, ok := device.(*BlockDevice)

		if !ok {
			continue
		}

		deviceStorage := &grpc.Storage{
			Source:     d.VirtPath,
			MountPoint: d.DeviceInfo.ContainerPath,
		}

		containerStorage = append(containerStorage, deviceStorage)
	}

	req := &grpc.CreateContainerRequest{
		ContainerId: c.id,
		Storages:    containerStorage,
		OCI:         grpcSpec,
	}

	_, err = k.client.CreateContainer(context.Background(), req)
	return err
}

func (k *kataAgent) startContainer(pod Pod, c Container) error {
	if k.client == nil {
		return errorMissingGRPClient
	}

	req := &grpc.StartContainerRequest{
		ContainerId: c.id,
	}

	_, err := k.client.StartContainer(context.Background(), req)
	return err
}

func (k *kataAgent) stopContainer(pod Pod, c Container) error {
	return nil
}

func (k *kataAgent) killContainer(pod Pod, c Container, signal syscall.Signal, all bool) error {
	return nil
}

func (k *kataAgent) processListContainer(pod Pod, c Container, options ProcessListOptions) (ProcessList, error) {
	return nil, nil
}
<|endoftext|>


<commit_before>// Package pinned provides a dial function that checks TLS server certificates against local pins.
package pinned

import (
	"bytes"
	"crypto/sha256"
	"crypto/tls"
	"errors"
	"hash"
	"net"
)

type Config struct {
	// Hash specifies the hash function to use to check the Pin, it defaults to
	// sha256.New.
	Hash func() hash.Hash

	// Pin defines the expected digest of the peer's leaf certificate.
	Pin []byte

	// Config is used as the base TLS configuration, if set.
	Config *tls.Config
}

var ErrPinFailure = errors.New("pinned: the peer leaf certificate did not match the provided pin")

// Dial establishes a TLS connection to addr and checks the peer leaf
// certificate against the configured pin. The underlying type of the returned
// net.Conn is guaranteed to be *tls.Conn.
func (c *Config) Dial(network, addr string) (net.Conn, error) {
	var conf tls.Config
	if c.Config != nil {
		conf = *c.Config
	}
	conf.InsecureSkipVerify = true

	conn, err := tls.Dial(network, addr, &conf)
	if err != nil {
		return nil, err
	}

	if err := conn.Handshake(); err != nil {
		conn.Close()
		return nil, err
	}

	state := conn.ConnectionState()
	hashFunc := c.Hash
	if hashFunc == nil {
		hashFunc = sha256.New
	}
	h := hashFunc()
	h.Write(state.PeerCertificates[0].Raw)
	if !bytes.Equal(h.Sum(nil), c.Pin) {
		conn.Close()
		return nil, ErrPinFailure
	}
	return conn, nil
}
<commit_msg>pkg/pinned: Add CloseWrite support<commit_after>// Package pinned provides a dial function that checks TLS server certificates against local pins.
package pinned

import (
	"bytes"
	"crypto/sha256"
	"crypto/tls"
	"errors"
	"hash"
	"net"
)

type Config struct {
	// Hash specifies the hash function to use to check the Pin, it defaults to
	// sha256.New.
	Hash func() hash.Hash

	// Pin defines the expected digest of the peer's leaf certificate.
	Pin []byte

	// Config is used as the base TLS configuration, if set.
	Config *tls.Config
}

var ErrPinFailure = errors.New("pinned: the peer leaf certificate did not match the provided pin")

// Dial establishes a TLS connection to addr and checks the peer leaf
// certificate against the configured pin. The underlying type of the returned
// net.Conn is a Conn.
func (c *Config) Dial(network, addr string) (net.Conn, error) {
	var conf tls.Config
	if c.Config != nil {
		conf = *c.Config
	}
	conf.InsecureSkipVerify = true

	cn, err := net.Dial(network, addr)
	if err != nil {
		return nil, err
	}

	conn := Conn{
		Conn: tls.Client(cn, &conf),
		Wire: cn,
	}

	host, _, _ := net.SplitHostPort(addr)
	conf.ServerName = host

	if err = conn.Handshake(); err != nil {
		conn.Close()
		return nil, err
	}
	return conn, nil

	state := conn.ConnectionState()
	hashFunc := c.Hash
	if hashFunc == nil {
		hashFunc = sha256.New
	}
	h := hashFunc()
	h.Write(state.PeerCertificates[0].Raw)
	if !bytes.Equal(h.Sum(nil), c.Pin) {
		conn.Close()
		return nil, ErrPinFailure
	}
	return conn, nil
}

type Conn struct {
	*tls.Conn
	Wire net.Conn
}

func (c Conn) CloseWrite() error {
	if cw, ok := c.Wire.(interface {
		CloseWrite() error
	}); ok {
		return cw.CloseWrite()
	}
	return errors.New("pinned: underlying connection does not support CloseWrite")
}
<|endoftext|>


<commit_before>package imjasonh

import (
	"appengine"
	"appengine/datastore"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"strconv"
	"time"
)

const (
	kind       = "JsonObject"
	idKey      = "_id"
	createdKey = "_created"
)

// TODO: Support PUT to update entities
func init() {
	http.HandleFunc("/jsonstore", insert)
	http.HandleFunc("/jsonstore/", getOrDelete)
}

func getOrDelete(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		get(w, r)
	case "DELETE":
		delete(w, r)
	default:
		http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
	}
}

func getID(path string) (int64, error) {
	sid := path[len("/jsonstore/"):]
	if path == "" {
		return 0, errors.New("Must specify ID")
	}
	id, err := strconv.ParseInt(sid, 10, 64)
	if err != nil {
		return 0, err
	}
	return id, nil
}

func delete(w http.ResponseWriter, r *http.Request) {
	id, err := getID(r.URL.Path)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	c := appengine.NewContext(r)
	k := datastore.NewKey(c, kind, "", id, nil)
	if err = datastore.Delete(c, k); err != nil {
		if err == datastore.ErrNoSuchEntity {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func get(w http.ResponseWriter, r *http.Request) {
	id, err := getID(r.URL.Path)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	c := appengine.NewContext(r)
	k := datastore.NewKey(c, kind, "", id, nil)
	var plist datastore.PropertyList
	if err := datastore.Get(c, k, &plist); err != nil {
		if err == datastore.ErrNoSuchEntity {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
	m := make(map[string]interface{})
	for _, p := range plist {
		if _, exists := m[p.Name]; exists {
			if _, isArr := m[p.Name].([]interface{}); isArr {
				m[p.Name] = append(m[p.Name].([]interface{}), p.Value)
			} else {
				m[p.Name] = []interface{}{m[p.Name], p.Value}
			}
		} else {
			m[p.Name] = p.Value
		}
	}
	m[idKey] = id
	json.NewEncoder(w).Encode(m)
}

func insert(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
		return
	}
	m, err := parse(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	m[createdKey] = time.Now()

	plist := make(datastore.PropertyList, 0)
	for k, v := range m {
		if _, mult := v.([]interface{}); mult {
			for _, mv := range v.([]interface{}) {
				plist = append(plist, datastore.Property{
					Name:     k,
					Value:    mv,
					Multiple: true,
				})
			}
		} else {
			plist = append(plist, datastore.Property{
				Name:  k,
				Value: v,
			})
		}
	}

	c := appengine.NewContext(r)

	k := datastore.NewIncompleteKey(c, kind, nil)
	k, err = datastore.Put(c, k, &plist)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	m[idKey] = k.IntID()
	json.NewEncoder(w).Encode(m)
}

func parse(r io.Reader) (map[string]interface{}, error) {
	var m map[string]interface{}
	if err := json.NewDecoder(r).Decode(&m); err != nil {
		return nil, err
	}
	return m, nil
}
<commit_msg>General request plumbing<commit_after>package imjasonh

import (
	"appengine"
	"appengine/datastore"
	"encoding/json"
	"net/http"
	"strconv"
	"time"
)

const (
	kind       = "JsonObject"
	idKey      = "_id"
	createdKey = "_created"
)

func init() {
	http.HandleFunc("/jsonstore", jsonstore)
	http.HandleFunc("/jsonstore/", jsonstore)
}

func jsonstore(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	if r.URL.Path == "/jsonstore" {
		switch r.Method {
		case "POST":
			insert(w, r)
			return
		case "GET":
			list(w, c)
			return
		}
	} else {
		sid := r.URL.Path[len("/jsonstore/"):]
		if path == "" {
			http.Error(w, "Must specify ID", http.StatusBadRequest)
			return
		}
		id, err := strconv.ParseInt(sid, 10, 64)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		switch r.Method {
		case "GET":
			get(w, id, c)
			return
		case "DELETE":
			delete(w, id, c)
			return
		case "PUT":
			update(w, id, c)
			return
		}
	}
	http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
}

func delete(w http.ResponseWriter, id int64, c appengine.Context) {
	k := datastore.NewKey(c, kind, "", id, nil)
	if err := datastore.Delete(c, k); err != nil {
		if err == datastore.ErrNoSuchEntity {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func get(w http.ResponseWriter, id int64, c appengine.Context) {
	k := datastore.NewKey(c, kind, "", id, nil)
	var plist datastore.PropertyList
	if err := datastore.Get(c, k, &plist); err != nil {
		if err == datastore.ErrNoSuchEntity {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
	m := make(map[string]interface{})
	for _, p := range plist {
		if _, exists := m[p.Name]; exists {
			if _, isArr := m[p.Name].([]interface{}); isArr {
				m[p.Name] = append(m[p.Name].([]interface{}), p.Value)
			} else {
				m[p.Name] = []interface{}{m[p.Name], p.Value}
			}
		} else {
			m[p.Name] = p.Value
		}
	}
	m[idKey] = id
	json.NewEncoder(w).Encode(m)
}

func insert(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
		return
	}
	var m map[string]interface{}
	if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	m[createdKey] = time.Now()

	plist := make(datastore.PropertyList, 0, len(m))
	for k, v := range m {
		if _, mult := v.([]interface{}); mult {
			for _, mv := range v.([]interface{}) {
				plist = append(plist, datastore.Property{
					Name:     k,
					Value:    mv,
					Multiple: true,
				})
			}
		} else {
			plist = append(plist, datastore.Property{
				Name:  k,
				Value: v,
			})
		}
	}

	c := appengine.NewContext(r)

	k := datastore.NewIncompleteKey(c, kind, nil)
	k, err := datastore.Put(c, k, &plist)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	m[idKey] = k.IntID()
	json.NewEncoder(w).Encode(m)
}

func list(w http.ResponseWriter, c appengine.Context) {
	// TODO: Implement this, with rudimentary queries.
}

func update(w http.ResponseWriter, id int64, c appengine.Context) {
	// TODO: Implement this.
}
<|endoftext|>