<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"testing"
)

func TestContains(t *testing.T) {
	a := []string{"a", "b", "abcd"}

	if !contains(a, "a") {
		t.Fatal("expected array to contain 'a'")
	}
	if contains(a, "d") {
		t.Fatal("expected array to not contain 'd'")
	}
}

func TestIsStdLib(t *testing.T) {
	tests := map[string]bool{
		"github.com/Sirupsen/logrus": false,
		"encoding/json":              true,
		"golang.org/x/net/context":   false,
		"net/context":                true,
		".":                          false,
	}

	for p, e := range tests {
		b := isStdLib(p)
		if b != e {
			t.Fatalf("%s: expected %t got %t", p, e, b)
		}
	}
}

func TestInit(t *testing.T) {
	needsExternalNetwork(t)
	needsGit(t)

	tg := testgo(t)
	defer tg.cleanup()

	tg.tempDir("src")
	tg.setenv("GOPATH", tg.path("."))

	importPaths := map[string]string{
		"github.com/pkg/errors":      "v0.8.0",                                   // semver
		"github.com/Sirupsen/logrus": "42b84f9ec624953ecbf81a94feccb3f5935c5edf", // random sha
	}

	// checkout the specified revisions
	for ip, rev := range importPaths {
		tg.runGo("get", ip)
		repoDir := tg.path("src/" + ip)
		tg.runGit(repoDir, "checkout", rev)
	}

	// Build a fake consumer of these packages.
	const root = "github.com/golang/notexist"
	m := `package main

import (
	"github.com/Sirupsen/logrus"
	"github.com/pkg/errors"

	"` + root + `/foo/bar"
)

func main() {
	err := nil
	if err != nil {
		errors.Wrap(err, "thing")
	}
	logrus.Info(bar.Qux)
}`

	tg.tempFile("src/"+root+"/foo/thing.go", m)

	m = `package bar

const Qux = "yo yo!"
`
	tg.tempFile("src/"+root+"/foo/bar/bar.go", m)

	tg.cd(tg.path("src/" + root))
	tg.run("init")

	expectedManifest := `{
    "dependencies": {
        "github.com/Sirupsen/logrus": {
            "revision": "42b84f9ec624953ecbf81a94feccb3f5935c5edf"
        },
        "github.com/pkg/errors": {
            "version": ">=0.8.0, <1.0.0"
        }
    }
}
`
	manifest := tg.readManifest()
	if manifest != expectedManifest {
		t.Fatalf("expected %s, got %s", expectedManifest, manifest)
	}

	sysCommit, err := getRepoLatestCommit("golang/sys")
	tg.must(err)
	expectedLock := `{
    "projects": [
        {
            "name": "github.com/Sirupsen/logrus",
            "revision": "42b84f9ec624953ecbf81a94feccb3f5935c5edf",
            "packages": [
                "."
            ]
        },
        {
            "name": "github.com/pkg/errors",
            "version": "v0.8.0",
            "revision": "645ef00459ed84a119197bfb8d8205042c6df63d",
            "packages": [
                "."
            ]
        },
        {
            "name": "golang.org/x/sys",
            "branch": "master",
            "revision": "` + sysCommit + `",
            "packages": [
                "unix"
            ]
        }
    ]
}
`
	lock := wipeMemo(tg.readLock())
	if lock != expectedLock {
		t.Fatalf("expected %s, got %s", expectedLock, lock)
	}
}

var memoRE = regexp.MustCompile(`\s+"memo": "[a-z0-9]+",`)

func wipeMemo(s string) string {
	return memoRE.ReplaceAllString(s, "")
}

type commit struct {
	Sha string `json:"sha"`
}

func getRepoLatestCommit(repo string) (string, error) {
	resp, err := http.Get(fmt.Sprintf("https://api.github.com/repos/%s/commits?per_page=1", repo))
	if err != nil {
		return "", err
	}

	var commits []commit
	if err := json.NewDecoder(resp.Body).Decode(&commits); err != nil {
		return "", err
	}

	if len(commits) < 1 {
		return "", errors.New("got no commits")
	}
	return commits[0].Sha, nil
}
<commit_msg>add tests for isDir and isRegular<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"testing"
)

func TestContains(t *testing.T) {
	a := []string{"a", "b", "abcd"}

	if !contains(a, "a") {
		t.Fatal("expected array to contain 'a'")
	}
	if contains(a, "d") {
		t.Fatal("expected array to not contain 'd'")
	}
}

func TestIsStdLib(t *testing.T) {
	tests := map[string]bool{
		"github.com/Sirupsen/logrus": false,
		"encoding/json":              true,
		"golang.org/x/net/context":   false,
		"net/context":                true,
		".":                          false,
	}

	for p, e := range tests {
		b := isStdLib(p)
		if b != e {
			t.Fatalf("%s: expected %t got %t", p, e, b)
		}
	}
}

func TestIsRegular(t *testing.T) {
	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}

	tests := map[string]bool{
		wd: false,
		filepath.Join(wd, "_testdata"):                      false,
		filepath.Join(wd, "main.go"):                        true,
		filepath.Join(wd, "this_file_does_not_exist.thing"): false,
	}

	for f, expected := range tests {
		dirOK, err := isRegular(f)
		if err != nil && expected {
			t.Fatal("expected no error, got %v", err)
		}

		if dirOK != expected {
			t.Fatal("expected %t for %s, got %t", expected, f, dirOK)
		}
	}

}

func TestIsDir(t *testing.T) {
	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}

	tests := map[string]bool{
		wd: true,
		filepath.Join(wd, "_testdata"):                      true,
		filepath.Join(wd, "main.go"):                        false,
		filepath.Join(wd, "this_file_does_not_exist.thing"): false,
	}

	for f, expected := range tests {
		dirOK, err := isDir(f)
		if err != nil && expected {
			t.Fatal("expected no error, got %v", err)
		}

		if dirOK != expected {
			t.Fatal("expected %t for %s, got %t", expected, f, dirOK)
		}
	}

}

func TestInit(t *testing.T) {
	needsExternalNetwork(t)
	needsGit(t)

	tg := testgo(t)
	defer tg.cleanup()

	tg.tempDir("src")
	tg.setenv("GOPATH", tg.path("."))

	importPaths := map[string]string{
		"github.com/pkg/errors":      "v0.8.0",                                   // semver
		"github.com/Sirupsen/logrus": "42b84f9ec624953ecbf81a94feccb3f5935c5edf", // random sha
	}

	// checkout the specified revisions
	for ip, rev := range importPaths {
		tg.runGo("get", ip)
		repoDir := tg.path("src/" + ip)
		tg.runGit(repoDir, "checkout", rev)
	}

	// Build a fake consumer of these packages.
	const root = "github.com/golang/notexist"
	m := `package main

import (
	"github.com/Sirupsen/logrus"
	"github.com/pkg/errors"

	"` + root + `/foo/bar"
)

func main() {
	err := nil
	if err != nil {
		errors.Wrap(err, "thing")
	}
	logrus.Info(bar.Qux)
}`

	tg.tempFile("src/"+root+"/foo/thing.go", m)

	m = `package bar

const Qux = "yo yo!"
`
	tg.tempFile("src/"+root+"/foo/bar/bar.go", m)

	tg.cd(tg.path("src/" + root))
	tg.run("init")

	expectedManifest := `{
    "dependencies": {
        "github.com/Sirupsen/logrus": {
            "revision": "42b84f9ec624953ecbf81a94feccb3f5935c5edf"
        },
        "github.com/pkg/errors": {
            "version": ">=0.8.0, <1.0.0"
        }
    }
}
`
	manifest := tg.readManifest()
	if manifest != expectedManifest {
		t.Fatalf("expected %s, got %s", expectedManifest, manifest)
	}

	sysCommit, err := getRepoLatestCommit("golang/sys")
	tg.must(err)
	expectedLock := `{
    "projects": [
        {
            "name": "github.com/Sirupsen/logrus",
            "revision": "42b84f9ec624953ecbf81a94feccb3f5935c5edf",
            "packages": [
                "."
            ]
        },
        {
            "name": "github.com/pkg/errors",
            "version": "v0.8.0",
            "revision": "645ef00459ed84a119197bfb8d8205042c6df63d",
            "packages": [
                "."
            ]
        },
        {
            "name": "golang.org/x/sys",
            "branch": "master",
            "revision": "` + sysCommit + `",
            "packages": [
                "unix"
            ]
        }
    ]
}
`
	lock := wipeMemo(tg.readLock())
	if lock != expectedLock {
		t.Fatalf("expected %s, got %s", expectedLock, lock)
	}
}

var memoRE = regexp.MustCompile(`\s+"memo": "[a-z0-9]+",`)

func wipeMemo(s string) string {
	return memoRE.ReplaceAllString(s, "")
}

type commit struct {
	Sha string `json:"sha"`
}

func getRepoLatestCommit(repo string) (string, error) {
	resp, err := http.Get(fmt.Sprintf("https://api.github.com/repos/%s/commits?per_page=1", repo))
	if err != nil {
		return "", err
	}

	var commits []commit
	if err := json.NewDecoder(resp.Body).Decode(&commits); err != nil {
		return "", err
	}

	if len(commits) < 1 {
		return "", errors.New("got no commits")
	}
	return commits[0].Sha, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes 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 config

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/sirupsen/logrus"
	"gopkg.in/fsnotify.v1"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/test-infra/prow/interrupts"
)

// Delta represents the before and after states of a Config change detected by the Agent.
type Delta struct {
	Before, After Config
}

// DeltaChan is a channel to receive config delta events when config changes.
type DeltaChan = chan<- Delta

// Agent watches a path and automatically loads the config stored
// therein.
type Agent struct {
	mut           sync.RWMutex // do not export Lock, etc methods
	c             *Config
	subscriptions []DeltaChan
}

// IsConfigMapMount determines whether the provided directory is a configmap mounted directory
func IsConfigMapMount(path string) (bool, error) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return false, fmt.Errorf("Could not read provided directory %s: %v", path, err)
	}
	for _, file := range files {
		if file.Name() == "..data" {
			return true, nil
		}
	}
	return false, nil
}

// GetCMMountWatcher returns a function that watches a configmap mounted directory and runs the provided "eventFunc" every time
// the directory gets updated and the provided "errFunc" every time it encounters an error.
// Example of a possible eventFunc:
// func() error {
//		value, err := RunUpdate()
//		if err != nil {
//			return err
//		}
//		globalValue = value
//		return nil
// }
// Example of errFunc:
// func(err error, msg string) {
//		logrus.WithError(err).Error(msg)
// }
func GetCMMountWatcher(eventFunc func() error, errFunc func(error, string), path string) (func(ctx context.Context), error) {
	isCMMount, err := IsConfigMapMount(path)
	if err != nil {
		return nil, err
	} else if !isCMMount {
		return nil, fmt.Errorf("Provided directory %s is not a configmap directory", path)
	}
	w, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	err = w.Add(path)
	if err != nil {
		return nil, err
	}
	logrus.Debugf("Watching %s", path)
	dataPath := filepath.Join(path, "..data")
	return func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				if err := w.Close(); err != nil {
					errFunc(err, fmt.Sprintf("failed to close fsnotify watcher for directory %s", path))
				}
				return
			case event := <-w.Events:
				if event.Name == dataPath && event.Op == fsnotify.Create {
					err := eventFunc()
					if err != nil {
						errFunc(err, fmt.Sprintf("event function for watch directory %s failed", path))
					}
				}
			case err := <-w.Errors:
				errFunc(err, fmt.Sprintf("received fsnotify error for directory %s", path))
			}
		}
	}, nil
}

// GetFileWatcher returns a function that watches the specified file(s), running the "eventFunc" whenever an event for the file(s) occurs
// and the "errFunc" whenever an error is encountered. In this function, the eventFunc has access to the watcher, allowing the eventFunc
// to add new files/directories to be watched as needed.
// Example of a possible eventFunc:
// func(w *fsnotify.Watcher) error {
//		value, err := RunUpdate()
//		if err != nil {
//			return err
//		}
//		globalValue = value
//      newFiles := getNewFiles()
//      for _, file := range newFiles {
//			if err := w.Add(file); err != nil {
//				return err
//			}
// 		}
//		return nil
// }
// Example of errFunc:
// func(err error, msg string) {
//		logrus.WithError(err).Error(msg)
// }
func GetFileWatcher(eventFunc func(*fsnotify.Watcher) error, errFunc func(error, string), files ...string) (func(ctx context.Context), error) {
	w, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	for _, file := range files {
		if err := w.Add(file); err != nil {
			return nil, err
		}
	}
	logrus.Debugf("Watching files: %v", files)
	return func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				if err := w.Close(); err != nil {
					errFunc(err, fmt.Sprintf("failed to close fsnotify watcher for files: %v", files))
				}
				return
			case <-w.Events:
				err := eventFunc(w)
				if err != nil {
					errFunc(err, fmt.Sprintf("event function failed watching files: %v", files))
				}
			case err := <-w.Errors:
				errFunc(err, fmt.Sprintf("received fsnotify error watching files: %v", files))
			}
		}
	}, nil
}

// ListCMsAndDirs returns a 2 sets of strings containing the paths of configmapped directories and standard
// directories respectively starting from the provided path. This can be used to watch a large number of
// files, some of which may be populated via configmaps
func ListCMsAndDirs(path string) (cms sets.String, dirs sets.String, err error) {
	cms = sets.NewString()
	dirs = sets.NewString()
	err = filepath.Walk(path, func(path string, info os.FileInfo, _ error) error {
		// We only need to watch directories as creation, deletion, and writes
		// for files in a directory trigger events for the directory
		if info != nil && info.IsDir() {
			if isCM, err := IsConfigMapMount(path); err != nil {
				return fmt.Errorf("Failed to check is path %s is configmap mounted: %v", path, err)
			} else if isCM {
				cms.Insert(path)
				// configmaps can't have nested directories
				return filepath.SkipDir
			} else {
				dirs.Insert(path)
				return nil
			}
		}
		return nil
	})
	return cms, dirs, err
}

func watchConfigs(ca *Agent, prowConfig, jobConfig string, additionals ...func(*Config) error) error {
	cmEventFunc := func() error {
		c, err := Load(prowConfig, jobConfig, additionals...)
		if err != nil {
			return err
		}
		ca.Set(c)
		return nil
	}
	// We may need to add more directories to be watched
	dirsEventFunc := func(w *fsnotify.Watcher) error {
		c, err := Load(prowConfig, jobConfig, additionals...)
		if err != nil {
			return err
		}
		ca.Set(c)
		// TODO(AlexNPavel): Is there a chance that a ConfigMap mounted directory may appear without making a new pod? If yes, handle that.
		_, dirs, err := ListCMsAndDirs(jobConfig)
		if err != nil {
			return err
		}
		for dir := range dirs {
			// Adding a file or directory that already exists in fsnotify is a no-op, so it is safe to always run Add
			if err := w.Add(dir); err != nil {
				return err
			}
		}
		return nil
	}
	errFunc := func(err error, msg string) {
		logrus.WithField("prowConfig", prowConfig).
			WithField("jobConfig", jobConfig).
			WithError(err).Error(msg)
	}
	cms := sets.NewString()
	dirs := sets.NewString()
	// TODO(AlexNPavel): allow empty jobConfig till fully migrate config to subdirs
	if jobConfig != "" {
		stat, err := os.Stat(jobConfig)
		if err != nil {
			return err
		}
		// TODO(AlexNPavel): allow single file jobConfig till fully migrate config to subdirs
		if stat.IsDir() {
			var err error
			// jobConfig points to directories of configs that may be nested
			cms, dirs, err = ListCMsAndDirs(jobConfig)
			if err != nil {
				return err
			}
		} else {
			// If jobConfig is a single file, we handle it identically to how prowConfig is handled
			if jobIsCMMounted, err := IsConfigMapMount(filepath.Dir(jobConfig)); err != nil {
				return err
			} else if jobIsCMMounted {
				cms.Insert(filepath.Dir(jobConfig))
			} else {
				dirs.Insert(jobConfig)
			}
		}
	}
	// The prow config is always a single file
	if prowIsCMMounted, err := IsConfigMapMount(filepath.Dir(prowConfig)); err != nil {
		return err
	} else if prowIsCMMounted {
		cms.Insert(filepath.Dir(prowConfig))
	} else {
		dirs.Insert(prowConfig)
	}
	var runFuncs []func(context.Context)
	for cm := range cms {
		runFunc, err := GetCMMountWatcher(cmEventFunc, errFunc, cm)
		if err != nil {
			return err
		}
		runFuncs = append(runFuncs, runFunc)
	}
	if len(dirs) > 0 {
		runFunc, err := GetFileWatcher(dirsEventFunc, errFunc, dirs.UnsortedList()...)
		if err != nil {
			return err
		}
		runFuncs = append(runFuncs, runFunc)
	}
	for _, runFunc := range runFuncs {
		interrupts.Run(runFunc)
	}
	return nil
}

// Start will begin watching the config file at the path. If the first load
// fails, Start will return the error and abort. Future load failures will log
// the failure message but continue attempting to load.
func (ca *Agent) Start(prowConfig, jobConfig string, additionals ...func(*Config) error) error {
	c, err := Load(prowConfig, jobConfig, additionals...)
	if err != nil {
		return err
	}
	ca.Set(c)
	watchConfigs(ca, prowConfig, jobConfig, additionals...)
	return nil
}

// Subscribe registers the channel for messages on config reload.
// The caller can expect a copy of the previous and current config
// to be sent down the subscribed channel when a new configuration
// is loaded.
func (ca *Agent) Subscribe(subscription DeltaChan) {
	ca.mut.Lock()
	defer ca.mut.Unlock()
	ca.subscriptions = append(ca.subscriptions, subscription)
}

// Getter returns the current Config in a thread-safe manner.
type Getter func() *Config

// Config returns the latest config. Do not modify the config.
func (ca *Agent) Config() *Config {
	ca.mut.RLock()
	defer ca.mut.RUnlock()
	return ca.c
}

// Set sets the config. Useful for testing.
// Also used by statusreconciler to load last known config
func (ca *Agent) Set(c *Config) {
	ca.mut.Lock()
	defer ca.mut.Unlock()
	var oldConfig Config
	if ca.c != nil {
		oldConfig = *ca.c
	}
	delta := Delta{oldConfig, *c}
	ca.c = c
	for _, subscription := range ca.subscriptions {
		go func(sub DeltaChan) { // wait a minute to send each event
			end := time.NewTimer(time.Minute)
			select {
			case sub <- delta:
			case <-end.C:
			}
			if !end.Stop() { // prevent new events
				<-end.C // drain the pending event
			}
		}(subscription)
	}
}
<commit_msg>config: keep Start unmodified; add separate StartWatch function<commit_after>/*
Copyright 2017 The Kubernetes 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 config

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/sirupsen/logrus"
	"gopkg.in/fsnotify.v1"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/test-infra/prow/interrupts"
)

// Delta represents the before and after states of a Config change detected by the Agent.
type Delta struct {
	Before, After Config
}

// DeltaChan is a channel to receive config delta events when config changes.
type DeltaChan = chan<- Delta

// Agent watches a path and automatically loads the config stored
// therein.
type Agent struct {
	mut           sync.RWMutex // do not export Lock, etc methods
	c             *Config
	subscriptions []DeltaChan
}

// IsConfigMapMount determines whether the provided directory is a configmap mounted directory
func IsConfigMapMount(path string) (bool, error) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return false, fmt.Errorf("Could not read provided directory %s: %v", path, err)
	}
	for _, file := range files {
		if file.Name() == "..data" {
			return true, nil
		}
	}
	return false, nil
}

// GetCMMountWatcher returns a function that watches a configmap mounted directory and runs the provided "eventFunc" every time
// the directory gets updated and the provided "errFunc" every time it encounters an error.
// Example of a possible eventFunc:
// func() error {
//		value, err := RunUpdate()
//		if err != nil {
//			return err
//		}
//		globalValue = value
//		return nil
// }
// Example of errFunc:
// func(err error, msg string) {
//		logrus.WithError(err).Error(msg)
// }
func GetCMMountWatcher(eventFunc func() error, errFunc func(error, string), path string) (func(ctx context.Context), error) {
	isCMMount, err := IsConfigMapMount(path)
	if err != nil {
		return nil, err
	} else if !isCMMount {
		return nil, fmt.Errorf("Provided directory %s is not a configmap directory", path)
	}
	w, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	err = w.Add(path)
	if err != nil {
		return nil, err
	}
	logrus.Debugf("Watching %s", path)
	dataPath := filepath.Join(path, "..data")
	return func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				if err := w.Close(); err != nil {
					errFunc(err, fmt.Sprintf("failed to close fsnotify watcher for directory %s", path))
				}
				return
			case event := <-w.Events:
				if event.Name == dataPath && event.Op == fsnotify.Create {
					err := eventFunc()
					if err != nil {
						errFunc(err, fmt.Sprintf("event function for watch directory %s failed", path))
					}
				}
			case err := <-w.Errors:
				errFunc(err, fmt.Sprintf("received fsnotify error for directory %s", path))
			}
		}
	}, nil
}

// GetFileWatcher returns a function that watches the specified file(s), running the "eventFunc" whenever an event for the file(s) occurs
// and the "errFunc" whenever an error is encountered. In this function, the eventFunc has access to the watcher, allowing the eventFunc
// to add new files/directories to be watched as needed.
// Example of a possible eventFunc:
// func(w *fsnotify.Watcher) error {
//		value, err := RunUpdate()
//		if err != nil {
//			return err
//		}
//		globalValue = value
//      newFiles := getNewFiles()
//      for _, file := range newFiles {
//			if err := w.Add(file); err != nil {
//				return err
//			}
// 		}
//		return nil
// }
// Example of errFunc:
// func(err error, msg string) {
//		logrus.WithError(err).Error(msg)
// }
func GetFileWatcher(eventFunc func(*fsnotify.Watcher) error, errFunc func(error, string), files ...string) (func(ctx context.Context), error) {
	w, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	for _, file := range files {
		if err := w.Add(file); err != nil {
			return nil, err
		}
	}
	logrus.Debugf("Watching files: %v", files)
	return func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				if err := w.Close(); err != nil {
					errFunc(err, fmt.Sprintf("failed to close fsnotify watcher for files: %v", files))
				}
				return
			case <-w.Events:
				err := eventFunc(w)
				if err != nil {
					errFunc(err, fmt.Sprintf("event function failed watching files: %v", files))
				}
			case err := <-w.Errors:
				errFunc(err, fmt.Sprintf("received fsnotify error watching files: %v", files))
			}
		}
	}, nil
}

// ListCMsAndDirs returns a 2 sets of strings containing the paths of configmapped directories and standard
// directories respectively starting from the provided path. This can be used to watch a large number of
// files, some of which may be populated via configmaps
func ListCMsAndDirs(path string) (cms sets.String, dirs sets.String, err error) {
	cms = sets.NewString()
	dirs = sets.NewString()
	err = filepath.Walk(path, func(path string, info os.FileInfo, _ error) error {
		// We only need to watch directories as creation, deletion, and writes
		// for files in a directory trigger events for the directory
		if info != nil && info.IsDir() {
			if isCM, err := IsConfigMapMount(path); err != nil {
				return fmt.Errorf("Failed to check is path %s is configmap mounted: %v", path, err)
			} else if isCM {
				cms.Insert(path)
				// configmaps can't have nested directories
				return filepath.SkipDir
			} else {
				dirs.Insert(path)
				return nil
			}
		}
		return nil
	})
	return cms, dirs, err
}

func watchConfigs(ca *Agent, prowConfig, jobConfig string, additionals ...func(*Config) error) error {
	cmEventFunc := func() error {
		c, err := Load(prowConfig, jobConfig, additionals...)
		if err != nil {
			return err
		}
		ca.Set(c)
		return nil
	}
	// We may need to add more directories to be watched
	dirsEventFunc := func(w *fsnotify.Watcher) error {
		c, err := Load(prowConfig, jobConfig, additionals...)
		if err != nil {
			return err
		}
		ca.Set(c)
		// TODO(AlexNPavel): Is there a chance that a ConfigMap mounted directory may appear without making a new pod? If yes, handle that.
		_, dirs, err := ListCMsAndDirs(jobConfig)
		if err != nil {
			return err
		}
		for dir := range dirs {
			// Adding a file or directory that already exists in fsnotify is a no-op, so it is safe to always run Add
			if err := w.Add(dir); err != nil {
				return err
			}
		}
		return nil
	}
	errFunc := func(err error, msg string) {
		logrus.WithField("prowConfig", prowConfig).
			WithField("jobConfig", jobConfig).
			WithError(err).Error(msg)
	}
	cms := sets.NewString()
	dirs := sets.NewString()
	// TODO(AlexNPavel): allow empty jobConfig till fully migrate config to subdirs
	if jobConfig != "" {
		stat, err := os.Stat(jobConfig)
		if err != nil {
			return err
		}
		// TODO(AlexNPavel): allow single file jobConfig till fully migrate config to subdirs
		if stat.IsDir() {
			var err error
			// jobConfig points to directories of configs that may be nested
			cms, dirs, err = ListCMsAndDirs(jobConfig)
			if err != nil {
				return err
			}
		} else {
			// If jobConfig is a single file, we handle it identically to how prowConfig is handled
			if jobIsCMMounted, err := IsConfigMapMount(filepath.Dir(jobConfig)); err != nil {
				return err
			} else if jobIsCMMounted {
				cms.Insert(filepath.Dir(jobConfig))
			} else {
				dirs.Insert(jobConfig)
			}
		}
	}
	// The prow config is always a single file
	if prowIsCMMounted, err := IsConfigMapMount(filepath.Dir(prowConfig)); err != nil {
		return err
	} else if prowIsCMMounted {
		cms.Insert(filepath.Dir(prowConfig))
	} else {
		dirs.Insert(prowConfig)
	}
	var runFuncs []func(context.Context)
	for cm := range cms {
		runFunc, err := GetCMMountWatcher(cmEventFunc, errFunc, cm)
		if err != nil {
			return err
		}
		runFuncs = append(runFuncs, runFunc)
	}
	if len(dirs) > 0 {
		runFunc, err := GetFileWatcher(dirsEventFunc, errFunc, dirs.UnsortedList()...)
		if err != nil {
			return err
		}
		runFuncs = append(runFuncs, runFunc)
	}
	for _, runFunc := range runFuncs {
		interrupts.Run(runFunc)
	}
	return nil
}

// StartWatch will begin watching the config files at the provided paths. If the
// first load fails, Start will return the error and abort. Future load failures
// will log the failure message but continue attempting to load.
// This function will replace Start in a future release.
func (ca *Agent) StartWatch(prowConfig, jobConfig string, additionals ...func(*Config) error) error {
	c, err := Load(prowConfig, jobConfig, additionals...)
	if err != nil {
		return err
	}
	ca.Set(c)
	watchConfigs(ca, prowConfig, jobConfig, additionals...)
	return nil
}

func lastConfigModTime(prowConfig, jobConfig string) (time.Time, error) {
	// Check if the file changed to see if it needs to be re-read.
	// os.Stat follows symbolic links, which is how ConfigMaps work.
	prowStat, err := os.Stat(prowConfig)
	if err != nil {
		logrus.WithField("prowConfig", prowConfig).WithError(err).Error("Error loading prow config.")
		return time.Time{}, err
	}
	recentModTime := prowStat.ModTime()
	// TODO(krzyzacy): allow empty jobConfig till fully migrate config to subdirs
	if jobConfig != "" {
		jobConfigStat, err := os.Stat(jobConfig)
		if err != nil {
			logrus.WithField("jobConfig", jobConfig).WithError(err).Error("Error loading job configs.")
			return time.Time{}, err
		}

		if jobConfigStat.ModTime().After(recentModTime) {
			recentModTime = jobConfigStat.ModTime()
		}
	}
	return recentModTime, nil
}

// Start will begin polling the config file at the path. If the first load
// fails, Start will return the error and abort. Future load failures will log
// the failure message but continue attempting to load.
func (ca *Agent) Start(prowConfig, jobConfig string, additionals ...func(*Config) error) error {
	lastModTime, err := lastConfigModTime(prowConfig, jobConfig)
	if err != nil {
		lastModTime = time.Time{}
	}
	c, err := Load(prowConfig, jobConfig, additionals...)
	if err != nil {
		return err
	}
	ca.Set(c)
	go func() {
		// Rarely, if two changes happen in the same second, mtime will
		// be the same for the second change, and an mtime-based check would
		// fail. Reload periodically just in case.
		skips := 0
		for range time.Tick(1 * time.Second) {
			if skips < 600 {
				recentModTime, err := lastConfigModTime(prowConfig, jobConfig)
				if err != nil {
					continue
				}
				if !recentModTime.After(lastModTime) {
					skips++
					continue // file hasn't been modified
				}
				lastModTime = recentModTime
			}
			if c, err := Load(prowConfig, jobConfig, additionals...); err != nil {
				logrus.WithField("prowConfig", prowConfig).
					WithField("jobConfig", jobConfig).
					WithError(err).Error("Error loading config.")
			} else {
				skips = 0
				ca.Set(c)
			}
		}
	}()
	return nil
}

// Subscribe registers the channel for messages on config reload.
// The caller can expect a copy of the previous and current config
// to be sent down the subscribed channel when a new configuration
// is loaded.
func (ca *Agent) Subscribe(subscription DeltaChan) {
	ca.mut.Lock()
	defer ca.mut.Unlock()
	ca.subscriptions = append(ca.subscriptions, subscription)
}

// Getter returns the current Config in a thread-safe manner.
type Getter func() *Config

// Config returns the latest config. Do not modify the config.
func (ca *Agent) Config() *Config {
	ca.mut.RLock()
	defer ca.mut.RUnlock()
	return ca.c
}

// Set sets the config. Useful for testing.
// Also used by statusreconciler to load last known config
func (ca *Agent) Set(c *Config) {
	ca.mut.Lock()
	defer ca.mut.Unlock()
	var oldConfig Config
	if ca.c != nil {
		oldConfig = *ca.c
	}
	delta := Delta{oldConfig, *c}
	ca.c = c
	for _, subscription := range ca.subscriptions {
		go func(sub DeltaChan) { // wait a minute to send each event
			end := time.NewTimer(time.Minute)
			select {
			case sub <- delta:
			case <-end.C:
			}
			if !end.Stop() { // prevent new events
				<-end.C // drain the pending event
			}
		}(subscription)
	}
}
<|endoftext|>


<commit_before>package http

import (
	"bufio"
	"context"
	"encoding/base64"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	"v2ray.com/core/app"
	"v2ray.com/core/app/dispatcher"
	"v2ray.com/core/app/log"
	"v2ray.com/core/common"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/errors"
	"v2ray.com/core/common/net"
	"v2ray.com/core/common/signal"
	"v2ray.com/core/transport/internet"
)

// Server is a HTTP proxy server.
type Server struct {
	config *ServerConfig
}

// NewServer creates a new HTTP inbound handler.
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
	space := app.SpaceFromContext(ctx)
	if space == nil {
		return nil, newError("no space in context.")
	}
	s := &Server{
		config: config,
	}
	return s, nil
}

func (*Server) Network() net.NetworkList {
	return net.NetworkList{
		Network: []net.Network{net.Network_TCP},
	}
}

func parseHost(rawHost string, defaultPort net.Port) (net.Destination, error) {
	port := defaultPort
	host, rawPort, err := net.SplitHostPort(rawHost)
	if err != nil {
		if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") {
			host = rawHost
		} else {
			return net.Destination{}, err
		}
	} else if len(rawPort) > 0 {
		intPort, err := strconv.Atoi(rawPort)
		if err != nil {
			return net.Destination{}, err
		}
		port = net.Port(intPort)
	}

	return net.TCPDestination(net.ParseAddress(host), port), nil
}

func isTimeout(err error) bool {
	nerr, ok := errors.Cause(err).(net.Error)
	return ok && nerr.Timeout()
}

func parseBasicAuth(auth string) (username, password string, ok bool) {
	const prefix = "Basic "
	if !strings.HasPrefix(auth, prefix) {
		return
	}
	c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
	if err != nil {
		return
	}
	cs := string(c)
	s := strings.IndexByte(cs, ':')
	if s < 0 {
		return
	}
	return cs[:s], cs[s+1:], true
}

func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher dispatcher.Interface) error {
	reader := bufio.NewReaderSize(conn, buf.Size)

Start:
	conn.SetReadDeadline(time.Now().Add(time.Second * 16))

	request, err := http.ReadRequest(reader)
	if err != nil {
		trace := newError("failed to read http request").Base(err)
		if errors.Cause(err) != io.EOF && !isTimeout(errors.Cause(err)) {
			trace.AtWarning()
		}
		return trace
	}

	if len(s.config.Accounts) > 0 {
		user, pass, ok := parseBasicAuth(request.Header.Get("Proxy-Authorization"))
		if !ok || !s.config.HasAccount(user, pass) {
			_, err := conn.Write([]byte("HTTP/1.1 401 UNAUTHORIZED\r\n\r\n"))
			return err
		}
	}

	log.Trace(newError("request to Method [", request.Method, "] Host [", request.Host, "] with URL [", request.URL, "]"))
	conn.SetReadDeadline(time.Time{})

	defaultPort := net.Port(80)
	if strings.ToLower(request.URL.Scheme) == "https" {
		defaultPort = net.Port(443)
	}
	host := request.Host
	if len(host) == 0 {
		host = request.URL.Host
	}
	dest, err := parseHost(host, defaultPort)
	if err != nil {
		return newError("malformed proxy host: ", host).AtWarning().Base(err)
	}
	log.Access(conn.RemoteAddr(), request.URL, log.AccessAccepted, "")

	if strings.ToUpper(request.Method) == "CONNECT" {
		return s.handleConnect(ctx, request, reader, conn, dest, dispatcher)
	}

	keepAlive := (strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive")

	err = s.handlePlainHTTP(ctx, request, conn, dest, dispatcher)
	if err == errWaitAnother {
		if keepAlive {
			goto Start
		}
		err = nil
	}

	return err
}

func (s *Server) handleConnect(ctx context.Context, request *http.Request, reader *bufio.Reader, conn internet.Connection, dest net.Destination, dispatcher dispatcher.Interface) error {
	_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
	if err != nil {
		return newError("failed to write back OK response").Base(err)
	}

	timeout := time.Second * time.Duration(s.config.Timeout)
	if timeout == 0 {
		timeout = time.Minute * 5
	}
	ctx, cancel := context.WithCancel(ctx)
	timer := signal.CancelAfterInactivity(ctx, cancel, timeout)
	ray, err := dispatcher.Dispatch(ctx, dest)
	if err != nil {
		return err
	}

	if reader.Buffered() > 0 {
		payload := buf.New()
		common.Must(payload.Reset(func(b []byte) (int, error) {
			return reader.Read(b[:reader.Buffered()])
		}))
		if err := ray.InboundInput().WriteMultiBuffer(buf.NewMultiBufferValue(payload)); err != nil {
			return err
		}
	}

	requestDone := signal.ExecuteAsync(func() error {
		defer ray.InboundInput().Close()

		v2reader := buf.NewReader(conn)
		if err := buf.Copy(v2reader, ray.InboundInput(), buf.UpdateActivity(timer)); err != nil {
			return err
		}
		return nil
	})

	responseDone := signal.ExecuteAsync(func() error {
		v2writer := buf.NewWriter(conn)
		if err := buf.Copy(ray.InboundOutput(), v2writer, buf.UpdateActivity(timer)); err != nil {
			return err
		}
		timer.SetTimeout(time.Second * 2)
		return nil
	})

	if err := signal.ErrorOrFinish2(ctx, requestDone, responseDone); err != nil {
		ray.InboundInput().CloseError()
		ray.InboundOutput().CloseError()
		return newError("connection ends").Base(err)
	}

	return nil
}

// @VisibleForTesting
func StripHopByHopHeaders(header http.Header) {
	// Strip hop-by-hop header basaed on RFC:
	// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
	// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do

	header.Del("Proxy-Connection")
	header.Del("Proxy-Authenticate")
	header.Del("Proxy-Authorization")
	header.Del("TE")
	header.Del("Trailers")
	header.Del("Transfer-Encoding")
	header.Del("Upgrade")

	connections := header.Get("Connection")
	header.Del("Connection")
	if len(connections) == 0 {
		return
	}
	for _, h := range strings.Split(connections, ",") {
		header.Del(strings.TrimSpace(h))
	}

	// Prevent UA from being set to golang's default ones
	if len(header.Get("User-Agent")) == 0 {
		header.Set("User-Agent", "")
	}
}

var errWaitAnother = newError("keep alive")

func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, writer io.Writer, dest net.Destination, dispatcher dispatcher.Interface) error {
	if !s.config.AllowTransparent && len(request.URL.Host) <= 0 {
		// RFC 2068 (HTTP/1.1) requires URL to be absolute URL in HTTP proxy.
		response := &http.Response{
			Status:        "Bad Request",
			StatusCode:    400,
			Proto:         "HTTP/1.1",
			ProtoMajor:    1,
			ProtoMinor:    1,
			Header:        http.Header(make(map[string][]string)),
			Body:          nil,
			ContentLength: 0,
			Close:         true,
		}
		response.Header.Set("Proxy-Connection", "close")
		response.Header.Set("Connection", "close")
		return response.Write(writer)
	}

	if len(request.URL.Host) > 0 {
		request.Host = request.URL.Host
	}
	StripHopByHopHeaders(request.Header)

	ray, err := dispatcher.Dispatch(ctx, dest)
	if err != nil {
		return err
	}
	input := ray.InboundInput()
	output := ray.InboundOutput()
	defer input.Close()

	var result error = errWaitAnother

	requestDone := signal.ExecuteAsync(func() error {
		request.Header.Set("Connection", "close")

		requestWriter := buf.NewBufferedWriter(ray.InboundInput())
		common.Must(requestWriter.SetBuffered(false))
		return request.Write(requestWriter)
	})

	responseDone := signal.ExecuteAsync(func() error {
		responseReader := bufio.NewReaderSize(buf.NewBufferedReader(ray.InboundOutput()), 2048)
		response, err := http.ReadResponse(responseReader, request)
		if err == nil {
			StripHopByHopHeaders(response.Header)
			if response.ContentLength >= 0 {
				response.Header.Set("Proxy-Connection", "keep-alive")
				response.Header.Set("Connection", "keep-alive")
				response.Header.Set("Keep-Alive", "timeout=4")
				response.Close = false
			} else {
				response.Close = true
				result = nil
			}
		} else {
			log.Trace(newError("failed to read response from ", request.Host).Base(err).AtWarning())
			response = &http.Response{
				Status:        "Service Unavailable",
				StatusCode:    503,
				Proto:         "HTTP/1.1",
				ProtoMajor:    1,
				ProtoMinor:    1,
				Header:        http.Header(make(map[string][]string)),
				Body:          nil,
				ContentLength: 0,
				Close:         true,
			}
			response.Header.Set("Connection", "close")
			response.Header.Set("Proxy-Connection", "close")
		}
		if err := response.Write(writer); err != nil {
			return newError("failed to write response").Base(err)
		}
		return nil
	})

	if err := signal.ErrorOrFinish2(ctx, requestDone, responseDone); err != nil {
		input.CloseError()
		output.CloseError()
		return newError("connection ends").Base(err)
	}

	return result
}

func init() {
	common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
		return NewServer(ctx, config.(*ServerConfig))
	}))
}
<commit_msg>force reader to be nil<commit_after>package http

import (
	"bufio"
	"context"
	"encoding/base64"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	"v2ray.com/core/app"
	"v2ray.com/core/app/dispatcher"
	"v2ray.com/core/app/log"
	"v2ray.com/core/common"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/errors"
	"v2ray.com/core/common/net"
	"v2ray.com/core/common/signal"
	"v2ray.com/core/transport/internet"
)

// Server is a HTTP proxy server.
type Server struct {
	config *ServerConfig
}

// NewServer creates a new HTTP inbound handler.
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
	space := app.SpaceFromContext(ctx)
	if space == nil {
		return nil, newError("no space in context.")
	}
	s := &Server{
		config: config,
	}
	return s, nil
}

func (*Server) Network() net.NetworkList {
	return net.NetworkList{
		Network: []net.Network{net.Network_TCP},
	}
}

func parseHost(rawHost string, defaultPort net.Port) (net.Destination, error) {
	port := defaultPort
	host, rawPort, err := net.SplitHostPort(rawHost)
	if err != nil {
		if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") {
			host = rawHost
		} else {
			return net.Destination{}, err
		}
	} else if len(rawPort) > 0 {
		intPort, err := strconv.Atoi(rawPort)
		if err != nil {
			return net.Destination{}, err
		}
		port = net.Port(intPort)
	}

	return net.TCPDestination(net.ParseAddress(host), port), nil
}

func isTimeout(err error) bool {
	nerr, ok := errors.Cause(err).(net.Error)
	return ok && nerr.Timeout()
}

func parseBasicAuth(auth string) (username, password string, ok bool) {
	const prefix = "Basic "
	if !strings.HasPrefix(auth, prefix) {
		return
	}
	c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
	if err != nil {
		return
	}
	cs := string(c)
	s := strings.IndexByte(cs, ':')
	if s < 0 {
		return
	}
	return cs[:s], cs[s+1:], true
}

func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher dispatcher.Interface) error {
	reader := bufio.NewReaderSize(conn, buf.Size)

Start:
	conn.SetReadDeadline(time.Now().Add(time.Second * 16))

	request, err := http.ReadRequest(reader)
	if err != nil {
		trace := newError("failed to read http request").Base(err)
		if errors.Cause(err) != io.EOF && !isTimeout(errors.Cause(err)) {
			trace.AtWarning()
		}
		return trace
	}

	if len(s.config.Accounts) > 0 {
		user, pass, ok := parseBasicAuth(request.Header.Get("Proxy-Authorization"))
		if !ok || !s.config.HasAccount(user, pass) {
			_, err := conn.Write([]byte("HTTP/1.1 401 UNAUTHORIZED\r\n\r\n"))
			return err
		}
	}

	log.Trace(newError("request to Method [", request.Method, "] Host [", request.Host, "] with URL [", request.URL, "]"))
	conn.SetReadDeadline(time.Time{})

	defaultPort := net.Port(80)
	if strings.ToLower(request.URL.Scheme) == "https" {
		defaultPort = net.Port(443)
	}
	host := request.Host
	if len(host) == 0 {
		host = request.URL.Host
	}
	dest, err := parseHost(host, defaultPort)
	if err != nil {
		return newError("malformed proxy host: ", host).AtWarning().Base(err)
	}
	log.Access(conn.RemoteAddr(), request.URL, log.AccessAccepted, "")

	if strings.ToUpper(request.Method) == "CONNECT" {
		return s.handleConnect(ctx, request, reader, conn, dest, dispatcher)
	}

	keepAlive := (strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive")

	err = s.handlePlainHTTP(ctx, request, conn, dest, dispatcher)
	if err == errWaitAnother {
		if keepAlive {
			goto Start
		}
		err = nil
	}

	return err
}

func (s *Server) handleConnect(ctx context.Context, request *http.Request, reader *bufio.Reader, conn internet.Connection, dest net.Destination, dispatcher dispatcher.Interface) error {
	_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
	if err != nil {
		return newError("failed to write back OK response").Base(err)
	}

	timeout := time.Second * time.Duration(s.config.Timeout)
	if timeout == 0 {
		timeout = time.Minute * 5
	}
	ctx, cancel := context.WithCancel(ctx)
	timer := signal.CancelAfterInactivity(ctx, cancel, timeout)
	ray, err := dispatcher.Dispatch(ctx, dest)
	if err != nil {
		return err
	}

	if reader.Buffered() > 0 {
		payload := buf.New()
		common.Must(payload.Reset(func(b []byte) (int, error) {
			return reader.Read(b[:reader.Buffered()])
		}))
		if err := ray.InboundInput().WriteMultiBuffer(buf.NewMultiBufferValue(payload)); err != nil {
			return err
		}
		reader = nil
	}

	requestDone := signal.ExecuteAsync(func() error {
		defer ray.InboundInput().Close()

		v2reader := buf.NewReader(conn)
		if err := buf.Copy(v2reader, ray.InboundInput(), buf.UpdateActivity(timer)); err != nil {
			return err
		}
		return nil
	})

	responseDone := signal.ExecuteAsync(func() error {
		v2writer := buf.NewWriter(conn)
		if err := buf.Copy(ray.InboundOutput(), v2writer, buf.UpdateActivity(timer)); err != nil {
			return err
		}
		timer.SetTimeout(time.Second * 2)
		return nil
	})

	if err := signal.ErrorOrFinish2(ctx, requestDone, responseDone); err != nil {
		ray.InboundInput().CloseError()
		ray.InboundOutput().CloseError()
		return newError("connection ends").Base(err)
	}

	return nil
}

// @VisibleForTesting
func StripHopByHopHeaders(header http.Header) {
	// Strip hop-by-hop header basaed on RFC:
	// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
	// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do

	header.Del("Proxy-Connection")
	header.Del("Proxy-Authenticate")
	header.Del("Proxy-Authorization")
	header.Del("TE")
	header.Del("Trailers")
	header.Del("Transfer-Encoding")
	header.Del("Upgrade")

	connections := header.Get("Connection")
	header.Del("Connection")
	if len(connections) == 0 {
		return
	}
	for _, h := range strings.Split(connections, ",") {
		header.Del(strings.TrimSpace(h))
	}

	// Prevent UA from being set to golang's default ones
	if len(header.Get("User-Agent")) == 0 {
		header.Set("User-Agent", "")
	}
}

var errWaitAnother = newError("keep alive")

func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, writer io.Writer, dest net.Destination, dispatcher dispatcher.Interface) error {
	if !s.config.AllowTransparent && len(request.URL.Host) <= 0 {
		// RFC 2068 (HTTP/1.1) requires URL to be absolute URL in HTTP proxy.
		response := &http.Response{
			Status:        "Bad Request",
			StatusCode:    400,
			Proto:         "HTTP/1.1",
			ProtoMajor:    1,
			ProtoMinor:    1,
			Header:        http.Header(make(map[string][]string)),
			Body:          nil,
			ContentLength: 0,
			Close:         true,
		}
		response.Header.Set("Proxy-Connection", "close")
		response.Header.Set("Connection", "close")
		return response.Write(writer)
	}

	if len(request.URL.Host) > 0 {
		request.Host = request.URL.Host
	}
	StripHopByHopHeaders(request.Header)

	ray, err := dispatcher.Dispatch(ctx, dest)
	if err != nil {
		return err
	}
	input := ray.InboundInput()
	output := ray.InboundOutput()
	defer input.Close()

	var result error = errWaitAnother

	requestDone := signal.ExecuteAsync(func() error {
		request.Header.Set("Connection", "close")

		requestWriter := buf.NewBufferedWriter(ray.InboundInput())
		common.Must(requestWriter.SetBuffered(false))
		return request.Write(requestWriter)
	})

	responseDone := signal.ExecuteAsync(func() error {
		responseReader := bufio.NewReaderSize(buf.NewBufferedReader(ray.InboundOutput()), 2048)
		response, err := http.ReadResponse(responseReader, request)
		if err == nil {
			StripHopByHopHeaders(response.Header)
			if response.ContentLength >= 0 {
				response.Header.Set("Proxy-Connection", "keep-alive")
				response.Header.Set("Connection", "keep-alive")
				response.Header.Set("Keep-Alive", "timeout=4")
				response.Close = false
			} else {
				response.Close = true
				result = nil
			}
		} else {
			log.Trace(newError("failed to read response from ", request.Host).Base(err).AtWarning())
			response = &http.Response{
				Status:        "Service Unavailable",
				StatusCode:    503,
				Proto:         "HTTP/1.1",
				ProtoMajor:    1,
				ProtoMinor:    1,
				Header:        http.Header(make(map[string][]string)),
				Body:          nil,
				ContentLength: 0,
				Close:         true,
			}
			response.Header.Set("Connection", "close")
			response.Header.Set("Proxy-Connection", "close")
		}
		if err := response.Write(writer); err != nil {
			return newError("failed to write response").Base(err)
		}
		return nil
	})

	if err := signal.ErrorOrFinish2(ctx, requestDone, responseDone); err != nil {
		input.CloseError()
		output.CloseError()
		return newError("connection ends").Base(err)
	}

	return result
}

func init() {
	common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
		return NewServer(ctx, config.(*ServerConfig))
	}))
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestBuildWillCreateBlogFolder(t *testing.T) {
	dir := createTmpFolder(t)
	output := filepath.Join(createTmpFolder(t), BuildDirName)

	NewBlogBuilder(dir).Build(output)

	assertFilePathExist(t, output)
}

func TestCleanUpBeforeBuild(t *testing.T) {
	dir := createTmpFolder(t)
	output := filepath.Join(dir, "blog")

	deleteme := filepath.Join(output, "delete_me")
	os.Mkdir(output, os.ModePerm)
	os.Create(deleteme)

	NewBlogBuilder(dir).Build(output)

	if _, err := os.Stat(deleteme); !os.IsNotExist(err) {
		t.Fatalf("Should delete exist blog folder before build")
	}
}

func TestBuildGeneratingNecessaryFiles(t *testing.T) {
	testDataDir := testDataPath("build", "test_generate_files")
	output := createTmpFolder(t)

	NewBlogBuilder(testDataDir).Build(output)

	postDir := filepath.Join(output, "test-post")
	assertFilePathExist(t, postDir)

	postIndex := filepath.Join(postDir, "index.html")
	assertFilePathExist(t, postIndex)
}

func TestBuildGeneratePostFiles(t *testing.T) {
	testDataDir := testDataPath("build", "test_generate_posts")
	output := createTmpFolder(t)

	NewBlogBuilder(testDataDir).Build(output)

	content, _ := ioutil.ReadFile(filepath.Join(output, "test-post", "index.html"))

	if !strings.Contains(string(content), `<meta http-equiv="X-UA-Compatible" content="IE=edge">`) {
		t.Fatalf("No base template in post file")
	}

	if !strings.Contains(string(content), "This is test post content") {
		t.Fatalf("No post in post file")
	}
}

func TestBuildBlogIndexPage(t *testing.T) {
	testDataDir := testDataPath("build", "test_generate_index")
	output := createTmpFolder(t)

	NewBlogBuilder(testDataDir).Build(output)

	bytes, _ := ioutil.ReadFile(filepath.Join(output, "index.html"))
	content := string(bytes)

	if !strings.Contains(content, `<meta http-equiv="X-UA-Compatible" content="IE=edge">`) {
		t.Fatalf("No base template in blog index file")
	}
	if !strings.Contains(content, "Test Post") {
		t.Fatalf("No post in blog index file")
	}
}
<commit_msg>test post HTML content should not be escaped<commit_after>package main

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestBuildWillCreateBlogFolder(t *testing.T) {
	dir := createTmpFolder(t)
	output := filepath.Join(createTmpFolder(t), BuildDirName)

	NewBlogBuilder(dir).Build(output)

	assertFilePathExist(t, output)
}

func TestCleanUpBeforeBuild(t *testing.T) {
	dir := createTmpFolder(t)
	output := filepath.Join(dir, "blog")

	deleteme := filepath.Join(output, "delete_me")
	os.Mkdir(output, os.ModePerm)
	os.Create(deleteme)

	NewBlogBuilder(dir).Build(output)

	if _, err := os.Stat(deleteme); !os.IsNotExist(err) {
		t.Fatalf("Should delete exist blog folder before build")
	}
}

func TestBuildGeneratingNecessaryFiles(t *testing.T) {
	testDataDir := testDataPath("build", "test_generate_files")
	output := createTmpFolder(t)

	NewBlogBuilder(testDataDir).Build(output)

	postDir := filepath.Join(output, "test-post")
	assertFilePathExist(t, postDir)

	postIndex := filepath.Join(postDir, "index.html")
	assertFilePathExist(t, postIndex)
}

func TestBuildGeneratePostFiles(t *testing.T) {
	testDataDir := testDataPath("build", "test_generate_posts")
	output := createTmpFolder(t)

	NewBlogBuilder(testDataDir).Build(output)

	bytes, _ := ioutil.ReadFile(filepath.Join(output, "test-post", "index.html"))
	content := string(bytes)

	if !strings.Contains(content, `<meta http-equiv="X-UA-Compatible" content="IE=edge">`) {
		t.Fatalf("No base template in post file")
	}

	if !strings.Contains(content, "
This is test post content
") {
		t.Fatalf("No post in post file")
	}
}

func TestBuildBlogIndexPage(t *testing.T) {
	testDataDir := testDataPath("build", "test_generate_index")
	output := createTmpFolder(t)

	NewBlogBuilder(testDataDir).Build(output)

	bytes, _ := ioutil.ReadFile(filepath.Join(output, "index.html"))
	content := string(bytes)

	if !strings.Contains(content, `<meta http-equiv="X-UA-Compatible" content="IE=edge">`) {
		t.Fatalf("No base template in blog index file")
	}
	if !strings.Contains(content, "Test Post") {
		t.Fatalf("No post in blog index file")
	}
}
<|endoftext|>


<commit_before>package popart

import (
	"io"
	"net"
)

// HandlerFactory is an object capable of creating both error and per-session
// handlers.
type HandlerFactory interface {
	// GetSessionHandler returns a Handler object for a session with a
	// remote client (peer). Returning a nil Handler is an option if the
	// server does not want to communicate with this particuar client - in
	// this case the session is not created but it is advisable to log the
	// fact to aid in debugging - the Server consciously avoids any sorf of
	// logging itself.
	GetSessionHandler(peer net.Addr) Handler
}

// Handler is an object capable of serving a POP3 connection.
type Handler interface {
	// AuthenticatePASS is generally the first method called on a Handler
	// and should authentication fail it will return an error. It is
	// expected though that if the authentication is succesful the handler
	// will be able to associate all subsequent operations with this
	// particular user without an explicit need to pass username to each and
	// every method.
	AuthenticatePASS(username, password string) error

	// TODO(marcinw): explain.
	AuthenticateAPOP(username, hexdigest string) error

	// DeleteMessage takes a list of ordinal number of messages in a user's
	// maildrop and deletes them. If this method fails it is expected that
	// *none* of the messages will be deleted.
	// Note: you can not assume that message IDs will come in any particular
	// order.
	DeleteMessages(numbers []uint64) error

	// GetMessageReader takes an ordinal number of a message in a user's
	// maildrop and returns an io.ReadCloser allowing the content of the
	// message to be read. The server will take care of closing the data
	// source.
	GetMessageReader(number uint64) (io.ReadCloser, error)

	// GetMessageCount returns a number of messages waiting in the user's
	// maildrop.
	GetMessageCount() (uint64, error)

	// GetMessageCount takes and ordinal number of a message in a user's
	// maildrop and returns it's locally (per-maildrop) unique ID that is
	// persistent between sessions.
	GetMessageID(number uint64) (string, error)

	// GetMessageCount takes an ordinal number of a message in a users's
	// maildrop and returns its size in bytes. This may differ from what is
	// eventually returned to the client because of line ending replacements
	// and dot escapes but it should be reasonably close nevertheless.
	GetMessageSize(number uint64) error

	// HandleSessionError would be invoked if the code *outside* of the
	// handler errors produces an error. The session itself will terminate
	// but this is a chance to log an error the way
	HandleSessionError(err error)

	// LockMaildrop puts a global lock on the user's maildrop so that
	// any concurrent sessions that attempt to communicate with the server
	// should fail until the current session calls UnlockMaildrop. This
	// method should return an error if it is not possible to lock the
	// maildrop.
	LockMaildrop() error

	// SetBanner is called by APOP-enabled servers at the beginning of the
	// session. It is expected that the banner is stored somewhere since it
	// is expected that it will be available for proper handling of the
	// AuthenticateAPOP call.
	SetBanner(banner string) error

	// UnlockMaildrop releases global maildrop lock so that other clients
	// can connect and initiate their sessions.
	UnlockMaildrop() error
}
<commit_msg>improve interface documentation<commit_after>package popart

import (
	"io"
	"net"
)

// HandlerFactory is an object capable of creating per-session handlers.
type HandlerFactory interface {
	// GetSessionHandler returns a Handler object for a session with a
	// remote client (peer). Returning a nil Handler is an option if the
	// server does not want to communicate with this particuar client - in
	// this case the session is not created but it is advisable to log the
	// fact to aid in debugging - the Server consciously avoids any sorf of
	// logging itself.
	GetSessionHandler(peer net.Addr) Handler
}

// Handler is an object capable of serving a POP3 connection.
type Handler interface {
	// AuthenticatePASS is generally the first method called on a Handler
	// and should authentication fail it will return an error. It is
	// expected though that if the authentication is succesful the handler
	// will be able to associate all subsequent operations with this
	// particular user without an explicit need to pass username to each and
	// every method.
	AuthenticatePASS(username, password string) error

	// AuthenticateAPOP provides an alternative method of POP3
	// authentication where instead of a username/password combination the
	// client generates an md5 hexdigest based on a shared secret and the
	// banner displayed by the server at the beginning of the connection.
	// As per RFC1939 a server MUST support at least one authentication
	// mechanism but does not need to support any particular one.
	AuthenticateAPOP(username, hexdigest string) error

	// DeleteMessage takes a list of ordinal number of messages in a user's
	// maildrop and deletes them. If this method fails it is expected that
	// *none* of the messages will be deleted.
	// Note: you can not assume that message IDs will come in any particular
	// order.
	DeleteMessages(numbers []uint64) error

	// GetMessageReader takes an ordinal number of a message in a user's
	// maildrop and returns an io.ReadCloser allowing the content of the
	// message to be read. The server will take care of closing the data
	// source.
	GetMessageReader(number uint64) (io.ReadCloser, error)

	// GetMessageCount returns a number of messages waiting in the user's
	// maildrop.
	GetMessageCount() (uint64, error)

	// GetMessageCount takes and ordinal number of a message in a user's
	// maildrop and returns it's locally (per-maildrop) unique ID that is
	// persistent between sessions.
	GetMessageID(number uint64) (string, error)

	// GetMessageCount takes an ordinal number of a message in a users's
	// maildrop and returns its size in bytes. This may differ from what is
	// eventually returned to the client because of line ending replacements
	// and dot escapes but it should be reasonably close nevertheless.
	GetMessageSize(number uint64) error

	// HandleSessionError would be invoked if the code *outside* of the
	// handler errors produces an error. The session itself will terminate
	// but this is a chance to log an error the way
	HandleSessionError(err error)

	// LockMaildrop puts a global lock on the user's maildrop so that
	// any concurrent sessions that attempt to communicate with the server
	// should fail until the current session calls UnlockMaildrop. This
	// method should return an error if it is not possible to lock the
	// maildrop.
	LockMaildrop() error

	// SetBanner is called by APOP-enabled servers at the beginning of the
	// session. It is expected that the banner is stored somewhere since it
	// is expected that it will be available for proper handling of the
	// AuthenticateAPOP call.
	SetBanner(banner string) error

	// UnlockMaildrop releases global maildrop lock so that other clients
	// can connect and initiate their sessions. It is generally the very
	// last thing that will be called on a connection under normal
	// circumstances.
	UnlockMaildrop() error
}
<|endoftext|>


<commit_before>package hcsshim

import (
	"encoding/json"
	"io"
	"time"
)

// ProcessConfig is used as both the input of Container.CreateProcess
// and to convert the parameters to JSON for passing onto the HCS
type ProcessConfig struct {
	ApplicationName   string            `json:",omitempty"`
	CommandLine       string            `json:",omitempty"`
	User              string            `json:",omitempty"`
	WorkingDirectory  string            `json:",omitempty"`
	Environment       map[string]string `json:",omitempty"`
	EmulateConsole    bool              `json:",omitempty"`
	CreateStdInPipe   bool              `json:",omitempty"`
	CreateStdOutPipe  bool              `json:",omitempty"`
	CreateStdErrPipe  bool              `json:",omitempty"`
	ConsoleSize       [2]uint           `json:",omitempty"`
	CreateInUtilityVm bool              `json:",omitempty"` // Used by Linux Containers on Windows
	OCISpecification  *json.RawMessage  `json:",omitempty"` // Used by Linux Containers on Windows
}

type Layer struct {
	ID   string
	Path string
}

type MappedDir struct {
	HostPath         string
	ContainerPath    string
	ReadOnly         bool
	BandwidthMaximum uint64
	IOPSMaximum      uint64
}

type HvRuntime struct {
	ImagePath    string `json:",omitempty"`
	SkipTemplate bool   `json:",omitempty"`
}

// ContainerConfig is used as both the input of CreateContainer
// and to convert the parameters to JSON for passing onto the HCS
type ContainerConfig struct {
	SystemType                  string      // HCS requires this to be hard-coded to "Container"
	Name                        string      // Name of the container. We use the docker ID.
	Owner                       string      `json:",omitempty"` // The management platform that created this container
	VolumePath                  string      `json:",omitempty"` // Windows volume path for scratch space. Used by Windows Server Containers only. Format \\?\\Volume{GUID}
	IgnoreFlushesDuringBoot     bool        `json:",omitempty"` // Optimization hint for container startup in Windows
	LayerFolderPath             string      `json:",omitempty"` // Where the layer folders are located. Used by Windows Server Containers only. Format  %root%\windowsfilter\containerID
	Layers                      []Layer     // List of storage layers. Required for Windows Server and Hyper-V Containers. Format ID=GUID;Path=%root%\windowsfilter\layerID
	Credentials                 string      `json:",omitempty"` // Credentials information
	ProcessorCount              uint32      `json:",omitempty"` // Number of processors to assign to the container.
	ProcessorWeight             uint64      `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be omitted and HCS will default.
	ProcessorMaximum            int64       `json:",omitempty"` // CPU maximum usage percent 1..100
	StorageIOPSMaximum          uint64      `json:",omitempty"` // Maximum Storage IOPS
	StorageBandwidthMaximum     uint64      `json:",omitempty"` // Maximum Storage Bandwidth in bytes per second
	StorageSandboxSize          uint64      `json:",omitempty"` // Size in bytes that the container system drive should be expanded to if smaller
	MemoryMaximumInMB           int64       `json:",omitempty"` // Maximum memory available to the container in Megabytes
	HostName                    string      `json:",omitempty"` // Hostname
	MappedDirectories           []MappedDir `json:",omitempty"` // List of mapped directories (volumes/mounts)
	HvPartition                 bool        // True if it a Hyper-V Container
	NetworkSharedContainerName  string      `json:",omitempty"` // Name (ID) of the container that we will share the network stack with.
	EndpointList                []string    `json:",omitempty"` // List of networking endpoints to be attached to container
	HvRuntime                   *HvRuntime  `json:",omitempty"` // Hyper-V container settings. Used by Hyper-V containers only. Format ImagePath=%root%\BaseLayerID\UtilityVM
	Servicing                   bool        `json:",omitempty"` // True if this container is for servicing
	AllowUnqualifiedDNSQuery    bool        `json:",omitempty"` // True to allow unqualified DNS name resolution
	DNSSearchList               string      `json:",omitempty"` // Comma seperated list of DNS suffixes to use for name resolution
	ContainerType               string      `json:",omitempty"` // "Linux" for Linux containers on Windows. Omitted otherwise.
	TerminateOnLastHandleClosed bool        `json:",omitempty"` // Should HCS terminate the container once all handles have been closed
}

type ComputeSystemQuery struct {
	IDs    []string `json:"Ids,omitempty"`
	Types  []string `json:",omitempty"`
	Names  []string `json:",omitempty"`
	Owners []string `json:",omitempty"`
}

// Container represents a created (but not necessarily running) container.
type Container interface {
	// Start synchronously starts the container.
	Start() error

	// Shutdown requests a container shutdown, but it may not actually be shutdown until Wait() succeeds.
	Shutdown() error

	// Terminate requests a container terminate, but it may not actually be terminated until Wait() succeeds.
	Terminate() error

	// Waits synchronously waits for the container to shutdown or terminate.
	Wait() error

	// WaitTimeout synchronously waits for the container to terminate or the duration to elapse. It
	// returns false if timeout occurs.
	WaitTimeout(time.Duration) error

	// Pause pauses the execution of a container.
	Pause() error

	// Resume resumes the execution of a container.
	Resume() error

	// HasPendingUpdates returns true if the container has updates pending to install.
	HasPendingUpdates() (bool, error)

	// Statistics returns statistics for a container.
	Statistics() (Statistics, error)

	// ProcessList returns details for the processes in a container.
	ProcessList() ([]ProcessListItem, error)

	// CreateProcess launches a new process within the container.
	CreateProcess(c *ProcessConfig) (Process, error)

	// OpenProcess gets an interface to an existing process within the container.
	OpenProcess(pid int) (Process, error)

	// Close cleans up any state associated with the container but does not terminate or wait for it.
	Close() error

	// Modify the System
	Modify(config *ResourceModificationRequestResponse) error
}

// Process represents a running or exited process.
type Process interface {
	// Pid returns the process ID of the process within the container.
	Pid() int

	// Kill signals the process to terminate but does not wait for it to finish terminating.
	Kill() error

	// Wait waits for the process to exit.
	Wait() error

	// WaitTimeout waits for the process to exit or the duration to elapse. It returns
	// false if timeout occurs.
	WaitTimeout(time.Duration) error

	// ExitCode returns the exit code of the process. The process must have
	// already terminated.
	ExitCode() (int, error)

	// ResizeConsole resizes the console of the process.
	ResizeConsole(width, height uint16) error

	// Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing
	// these pipes does not close the underlying pipes; it should be possible to
	// call this multiple times to get multiple interfaces.
	Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, error)

	// CloseStdin closes the write side of the stdin pipe so that the process is
	// notified on the read side that there is no more data in stdin.
	CloseStdin() error

	// Close cleans up any state associated with the process but does not kill
	// or wait on it.
	Close() error
}
<commit_msg>Add MappedVirtualDisks to ContainerConfig<commit_after>package hcsshim

import (
	"encoding/json"
	"io"
	"time"
)

// ProcessConfig is used as both the input of Container.CreateProcess
// and to convert the parameters to JSON for passing onto the HCS
type ProcessConfig struct {
	ApplicationName   string            `json:",omitempty"`
	CommandLine       string            `json:",omitempty"`
	User              string            `json:",omitempty"`
	WorkingDirectory  string            `json:",omitempty"`
	Environment       map[string]string `json:",omitempty"`
	EmulateConsole    bool              `json:",omitempty"`
	CreateStdInPipe   bool              `json:",omitempty"`
	CreateStdOutPipe  bool              `json:",omitempty"`
	CreateStdErrPipe  bool              `json:",omitempty"`
	ConsoleSize       [2]uint           `json:",omitempty"`
	CreateInUtilityVm bool              `json:",omitempty"` // Used by Linux Containers on Windows
	OCISpecification  *json.RawMessage  `json:",omitempty"` // Used by Linux Containers on Windows
}

type Layer struct {
	ID   string
	Path string
}

type MappedDir struct {
	HostPath         string
	ContainerPath    string
	ReadOnly         bool
	BandwidthMaximum uint64
	IOPSMaximum      uint64
}

type HvRuntime struct {
	ImagePath    string `json:",omitempty"`
	SkipTemplate bool   `json:",omitempty"`
}

type MappedVirtualDisk struct {
	HostPath          string `json:",omitempty"` // Path to VHD on the host
	ContainerPath     string // Platform-specific mount point path in the container
	CreateInUtilityVM bool   `json:",omitempty"`
	ReadOnly          bool   `json:",omitempty"`
	Cache             string `json:",omitempty"` // "" (Unspecified); "Disabled"; "Enabled"; "Private"; "PrivateAllowSharing"
}

// ContainerConfig is used as both the input of CreateContainer
// and to convert the parameters to JSON for passing onto the HCS
type ContainerConfig struct {
	SystemType                  string              // HCS requires this to be hard-coded to "Container"
	Name                        string              // Name of the container. We use the docker ID.
	Owner                       string              `json:",omitempty"` // The management platform that created this container
	VolumePath                  string              `json:",omitempty"` // Windows volume path for scratch space. Used by Windows Server Containers only. Format \\?\\Volume{GUID}
	IgnoreFlushesDuringBoot     bool                `json:",omitempty"` // Optimization hint for container startup in Windows
	LayerFolderPath             string              `json:",omitempty"` // Where the layer folders are located. Used by Windows Server Containers only. Format  %root%\windowsfilter\containerID
	Layers                      []Layer             // List of storage layers. Required for Windows Server and Hyper-V Containers. Format ID=GUID;Path=%root%\windowsfilter\layerID
	Credentials                 string              `json:",omitempty"` // Credentials information
	ProcessorCount              uint32              `json:",omitempty"` // Number of processors to assign to the container.
	ProcessorWeight             uint64              `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be omitted and HCS will default.
	ProcessorMaximum            int64               `json:",omitempty"` // CPU maximum usage percent 1..100
	StorageIOPSMaximum          uint64              `json:",omitempty"` // Maximum Storage IOPS
	StorageBandwidthMaximum     uint64              `json:",omitempty"` // Maximum Storage Bandwidth in bytes per second
	StorageSandboxSize          uint64              `json:",omitempty"` // Size in bytes that the container system drive should be expanded to if smaller
	MemoryMaximumInMB           int64               `json:",omitempty"` // Maximum memory available to the container in Megabytes
	HostName                    string              `json:",omitempty"` // Hostname
	MappedDirectories           []MappedDir         `json:",omitempty"` // List of mapped directories (volumes/mounts)
	HvPartition                 bool                // True if it a Hyper-V Container
	NetworkSharedContainerName  string              `json:",omitempty"` // Name (ID) of the container that we will share the network stack with.
	EndpointList                []string            `json:",omitempty"` // List of networking endpoints to be attached to container
	HvRuntime                   *HvRuntime          `json:",omitempty"` // Hyper-V container settings. Used by Hyper-V containers only. Format ImagePath=%root%\BaseLayerID\UtilityVM
	Servicing                   bool                `json:",omitempty"` // True if this container is for servicing
	AllowUnqualifiedDNSQuery    bool                `json:",omitempty"` // True to allow unqualified DNS name resolution
	DNSSearchList               string              `json:",omitempty"` // Comma seperated list of DNS suffixes to use for name resolution
	ContainerType               string              `json:",omitempty"` // "Linux" for Linux containers on Windows. Omitted otherwise.
	TerminateOnLastHandleClosed bool                `json:",omitempty"` // Should HCS terminate the container once all handles have been closed
	MappedVirtualDisks          []MappedVirtualDisk `json:",omitempty"` // Array of virtual disks to mount at start
}

type ComputeSystemQuery struct {
	IDs    []string `json:"Ids,omitempty"`
	Types  []string `json:",omitempty"`
	Names  []string `json:",omitempty"`
	Owners []string `json:",omitempty"`
}

// Container represents a created (but not necessarily running) container.
type Container interface {
	// Start synchronously starts the container.
	Start() error

	// Shutdown requests a container shutdown, but it may not actually be shutdown until Wait() succeeds.
	Shutdown() error

	// Terminate requests a container terminate, but it may not actually be terminated until Wait() succeeds.
	Terminate() error

	// Waits synchronously waits for the container to shutdown or terminate.
	Wait() error

	// WaitTimeout synchronously waits for the container to terminate or the duration to elapse. It
	// returns false if timeout occurs.
	WaitTimeout(time.Duration) error

	// Pause pauses the execution of a container.
	Pause() error

	// Resume resumes the execution of a container.
	Resume() error

	// HasPendingUpdates returns true if the container has updates pending to install.
	HasPendingUpdates() (bool, error)

	// Statistics returns statistics for a container.
	Statistics() (Statistics, error)

	// ProcessList returns details for the processes in a container.
	ProcessList() ([]ProcessListItem, error)

	// CreateProcess launches a new process within the container.
	CreateProcess(c *ProcessConfig) (Process, error)

	// OpenProcess gets an interface to an existing process within the container.
	OpenProcess(pid int) (Process, error)

	// Close cleans up any state associated with the container but does not terminate or wait for it.
	Close() error

	// Modify the System
	Modify(config *ResourceModificationRequestResponse) error
}

// Process represents a running or exited process.
type Process interface {
	// Pid returns the process ID of the process within the container.
	Pid() int

	// Kill signals the process to terminate but does not wait for it to finish terminating.
	Kill() error

	// Wait waits for the process to exit.
	Wait() error

	// WaitTimeout waits for the process to exit or the duration to elapse. It returns
	// false if timeout occurs.
	WaitTimeout(time.Duration) error

	// ExitCode returns the exit code of the process. The process must have
	// already terminated.
	ExitCode() (int, error)

	// ResizeConsole resizes the console of the process.
	ResizeConsole(width, height uint16) error

	// Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing
	// these pipes does not close the underlying pipes; it should be possible to
	// call this multiple times to get multiple interfaces.
	Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, error)

	// CloseStdin closes the write side of the stdin pipe so that the process is
	// notified on the read side that there is no more data in stdin.
	CloseStdin() error

	// Close cleans up any state associated with the process but does not kill
	// or wait on it.
	Close() error
}
<|endoftext|>


<commit_before>package irc

import (
	//	"github.com/thoj/go-ircevent"
	"crypto/tls"
	"testing"
	"time"
)

func TestConnection(t *testing.T) {
	irccon1 := IRC("go-eventirc1", "go-eventirc1")
	irccon1.VerboseCallbackHandler = true
	irccon1.Debug = true
	irccon2 := IRC("go-eventirc2", "go-eventirc2")
	irccon2.VerboseCallbackHandler = true
	irccon2.Debug = true
	err := irccon1.Connect("irc.freenode.net:6667")
	if err != nil {
		t.Log(err.Error())
		t.Fatal("Can't connect to freenode.")
	}
	err = irccon2.Connect("irc.freenode.net:6667")
	if err != nil {
		t.Log(err.Error())
		t.Fatal("Can't connect to freenode.")
	}
	irccon1.AddCallback("001", func(e *Event) { irccon1.Join("#go-eventirc") })
	irccon2.AddCallback("001", func(e *Event) { irccon2.Join("#go-eventirc") })
	con2ok := false
	irccon1.AddCallback("366", func(e *Event) {
		t := time.NewTicker(1 * time.Second)
		i := 10
		for {
			<-t.C
			irccon1.Privmsgf("#go-eventirc", "Test Message%d\n", i)
			if con2ok {
				i -= 1
			}
			if i == 0 {
				t.Stop()
				irccon1.Quit()
			}
		}
	})

	irccon2.AddCallback("366", func(e *Event) {
		irccon2.Privmsg("#go-eventirc", "Test Message\n")
		con2ok = true
		irccon2.Nick("go-eventnewnick")
	})

	irccon2.AddCallback("PRIVMSG", func(e *Event) {
		t.Log(e.Message())
		if e.Message() == "Test Message5" {
			irccon2.Quit()
		}
	})

	irccon2.AddCallback("NICK", func(e *Event) {
		if irccon2.nickcurrent == "go-eventnewnick" {
			t.Fatal("Nick change did not work!")
		}
	})
	go irccon2.Loop()
	irccon1.Loop()
}

func TestConnectionSSL(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	irccon.VerboseCallbackHandler = true
	irccon.Debug = true
	irccon.UseTLS = true
	irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true}
	err := irccon.Connect("irc.freenode.net:7000")
	if err != nil {
		t.Log(err.Error())
		t.Fatal("Can't connect to freenode.")
	}
	irccon.AddCallback("001", func(e *Event) { irccon.Join("#go-eventirc") })

	irccon.AddCallback("366", func(e *Event) {
		irccon.Privmsg("#go-eventirc", "Test Message\n")
		time.Sleep(2 * time.Second)
		irccon.Quit()
	})

	irccon.Loop()
}

func TestConnectionEmtpyServer(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect("")
	if err == nil {
		t.Fatal("emtpy server string not detected")
	}
}

func TestConnectionDoubleColon(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect("::")
	if err == nil {
		t.Fatal("wrong number of ':' not detected")
	}
}

func TestConnectionMissingHost(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect(":6667")
	if err == nil {
		t.Fatal("missing host not detected")
	}
}

func TestConnectionMissingPort(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect("chat.freenode.net:")
	if err == nil {
		t.Fatal("missing port not detected")
	}
}

func TestConnectionNegativePort(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect("chat.freenode.net:-1")
	if err == nil {
		t.Fatal("negative port number not detected")
	}
}

func TestConnectionTooLargePort(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect("chat.freenode.net:65536")
	if err == nil {
		t.Fatal("too large port number not detected")
	}
}

func TestConnectionMissingLog(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	irccon.Log = nil
	err := irccon.Connect("chat.freenode.net:6667")
	if err == nil {
		t.Fatal("missing 'Log' not detected")
	}
}

func TestConnectionEmptyUser(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	// user may be changed after creation
	irccon.user = ""
	err := irccon.Connect("chat.freenode.net:6667")
	if err == nil {
		t.Fatal("empty 'user' not detected")
	}
}

func TestConnectionEmptyNick(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	// nick may be changed after creation
	irccon.nick = ""
	err := irccon.Connect("chat.freenode.net:6667")
	if err == nil {
		t.Fatal("empty 'nick' not detected")
	}
}

func TestRemoveCallback(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	irccon.VerboseCallbackHandler = true
	irccon.Debug = true

	done := make(chan int, 10)

	irccon.AddCallback("TEST", func(e *Event) { done <- 1 })
	id := irccon.AddCallback("TEST", func(e *Event) { done <- 2 })
	irccon.AddCallback("TEST", func(e *Event) { done <- 3 })

	// Should remove callback at index 1
	irccon.RemoveCallback("TEST", id)

	irccon.RunCallbacks(&Event{
		Code: "TEST",
	})

	var results []int

	results = append(results, <-done)
	results = append(results, <-done)

	if len(results) != 2 || !(results[0] == 1 && results[1] == 3) {
		t.Error("Callback 2 not removed")
	}
}

func TestWildcardCallback(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	irccon.VerboseCallbackHandler = true
	irccon.Debug = true

	done := make(chan int, 10)

	irccon.AddCallback("TEST", func(e *Event) { done <- 1 })
	irccon.AddCallback("*", func(e *Event) { done <- 2 })

	irccon.RunCallbacks(&Event{
		Code: "TEST",
	})

	var results []int

	results = append(results, <-done)
	results = append(results, <-done)

	if len(results) != 2 || !(results[0] == 1 && results[1] == 2) {
		t.Error("Wildcard callback not called")
	}
}

func TestClearCallback(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	irccon.VerboseCallbackHandler = true
	irccon.Debug = true

	done := make(chan int, 10)

	irccon.AddCallback("TEST", func(e *Event) { done <- 0 })
	irccon.AddCallback("TEST", func(e *Event) { done <- 1 })
	irccon.ClearCallback("TEST")
	irccon.AddCallback("TEST", func(e *Event) { done <- 2 })
	irccon.AddCallback("TEST", func(e *Event) { done <- 3 })

	irccon.RunCallbacks(&Event{
		Code: "TEST",
	})

	var results []int

	results = append(results, <-done)
	results = append(results, <-done)

	if len(results) != 2 || !(results[0] == 2 && results[1] == 3) {
		t.Error("Callbacks not cleared")
	}
}

func TestIRCemptyNick(t *testing.T) {
	irccon := IRC("", "go-eventirc")
	irccon = nil
	if irccon != nil {
		t.Error("empty nick didn't result in error")
		t.Fail()
	}
}

func TestIRCemptyUser(t *testing.T) {
	irccon := IRC("go-eventirc", "")
	if irccon != nil {
		t.Error("empty user didn't result in error")
	}
}
<commit_msg>Move connection tests last<commit_after>package irc

import (
	//	"github.com/thoj/go-ircevent"
	"crypto/tls"
	"testing"
	"time"
)

func TestConnectionEmtpyServer(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect("")
	if err == nil {
		t.Fatal("emtpy server string not detected")
	}
}

func TestConnectionDoubleColon(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect("::")
	if err == nil {
		t.Fatal("wrong number of ':' not detected")
	}
}

func TestConnectionMissingHost(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect(":6667")
	if err == nil {
		t.Fatal("missing host not detected")
	}
}

func TestConnectionMissingPort(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect("chat.freenode.net:")
	if err == nil {
		t.Fatal("missing port not detected")
	}
}

func TestConnectionNegativePort(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect("chat.freenode.net:-1")
	if err == nil {
		t.Fatal("negative port number not detected")
	}
}

func TestConnectionTooLargePort(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	err := irccon.Connect("chat.freenode.net:65536")
	if err == nil {
		t.Fatal("too large port number not detected")
	}
}

func TestConnectionMissingLog(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	irccon.Log = nil
	err := irccon.Connect("chat.freenode.net:6667")
	if err == nil {
		t.Fatal("missing 'Log' not detected")
	}
}

func TestConnectionEmptyUser(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	// user may be changed after creation
	irccon.user = ""
	err := irccon.Connect("chat.freenode.net:6667")
	if err == nil {
		t.Fatal("empty 'user' not detected")
	}
}

func TestConnectionEmptyNick(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	// nick may be changed after creation
	irccon.nick = ""
	err := irccon.Connect("chat.freenode.net:6667")
	if err == nil {
		t.Fatal("empty 'nick' not detected")
	}
}

func TestRemoveCallback(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	irccon.VerboseCallbackHandler = true
	irccon.Debug = true

	done := make(chan int, 10)

	irccon.AddCallback("TEST", func(e *Event) { done <- 1 })
	id := irccon.AddCallback("TEST", func(e *Event) { done <- 2 })
	irccon.AddCallback("TEST", func(e *Event) { done <- 3 })

	// Should remove callback at index 1
	irccon.RemoveCallback("TEST", id)

	irccon.RunCallbacks(&Event{
		Code: "TEST",
	})

	var results []int

	results = append(results, <-done)
	results = append(results, <-done)

	if len(results) != 2 || !(results[0] == 1 && results[1] == 3) {
		t.Error("Callback 2 not removed")
	}
}

func TestWildcardCallback(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	irccon.VerboseCallbackHandler = true
	irccon.Debug = true

	done := make(chan int, 10)

	irccon.AddCallback("TEST", func(e *Event) { done <- 1 })
	irccon.AddCallback("*", func(e *Event) { done <- 2 })

	irccon.RunCallbacks(&Event{
		Code: "TEST",
	})

	var results []int

	results = append(results, <-done)
	results = append(results, <-done)

	if len(results) != 2 || !(results[0] == 1 && results[1] == 2) {
		t.Error("Wildcard callback not called")
	}
}

func TestClearCallback(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	irccon.VerboseCallbackHandler = true
	irccon.Debug = true

	done := make(chan int, 10)

	irccon.AddCallback("TEST", func(e *Event) { done <- 0 })
	irccon.AddCallback("TEST", func(e *Event) { done <- 1 })
	irccon.ClearCallback("TEST")
	irccon.AddCallback("TEST", func(e *Event) { done <- 2 })
	irccon.AddCallback("TEST", func(e *Event) { done <- 3 })

	irccon.RunCallbacks(&Event{
		Code: "TEST",
	})

	var results []int

	results = append(results, <-done)
	results = append(results, <-done)

	if len(results) != 2 || !(results[0] == 2 && results[1] == 3) {
		t.Error("Callbacks not cleared")
	}
}

func TestIRCemptyNick(t *testing.T) {
	irccon := IRC("", "go-eventirc")
	irccon = nil
	if irccon != nil {
		t.Error("empty nick didn't result in error")
		t.Fail()
	}
}

func TestIRCemptyUser(t *testing.T) {
	irccon := IRC("go-eventirc", "")
	if irccon != nil {
		t.Error("empty user didn't result in error")
	}
}
func TestConnection(t *testing.T) {
	irccon1 := IRC("go-eventirc1", "go-eventirc1")
	irccon1.VerboseCallbackHandler = true
	irccon1.Debug = true
	irccon2 := IRC("go-eventirc2", "go-eventirc2")
	irccon2.VerboseCallbackHandler = true
	irccon2.Debug = true
	err := irccon1.Connect("irc.freenode.net:6667")
	if err != nil {
		t.Log(err.Error())
		t.Fatal("Can't connect to freenode.")
	}
	err = irccon2.Connect("irc.freenode.net:6667")
	if err != nil {
		t.Log(err.Error())
		t.Fatal("Can't connect to freenode.")
	}
	irccon1.AddCallback("001", func(e *Event) { irccon1.Join("#go-eventirc") })
	irccon2.AddCallback("001", func(e *Event) { irccon2.Join("#go-eventirc") })
	con2ok := false
	irccon1.AddCallback("366", func(e *Event) {
		t := time.NewTicker(1 * time.Second)
		i := 10
		for {
			<-t.C
			irccon1.Privmsgf("#go-eventirc", "Test Message%d\n", i)
			if con2ok {
				i -= 1
			}
			if i == 0 {
				t.Stop()
				irccon1.Quit()
			}
		}
	})

	irccon2.AddCallback("366", func(e *Event) {
		irccon2.Privmsg("#go-eventirc", "Test Message\n")
		con2ok = true
		irccon2.Nick("go-eventnewnick")
	})

	irccon2.AddCallback("PRIVMSG", func(e *Event) {
		t.Log(e.Message())
		if e.Message() == "Test Message5" {
			irccon2.Quit()
		}
	})

	irccon2.AddCallback("NICK", func(e *Event) {
		if irccon2.nickcurrent == "go-eventnewnick" {
			t.Fatal("Nick change did not work!")
		}
	})
	go irccon2.Loop()
	irccon1.Loop()
}

func TestConnectionSSL(t *testing.T) {
	irccon := IRC("go-eventirc", "go-eventirc")
	irccon.VerboseCallbackHandler = true
	irccon.Debug = true
	irccon.UseTLS = true
	irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true}
	err := irccon.Connect("irc.freenode.net:7000")
	if err != nil {
		t.Log(err.Error())
		t.Fatal("Can't connect to freenode.")
	}
	irccon.AddCallback("001", func(e *Event) { irccon.Join("#go-eventirc") })

	irccon.AddCallback("366", func(e *Event) {
		irccon.Privmsg("#go-eventirc", "Test Message\n")
		time.Sleep(2 * time.Second)
		irccon.Quit()
	})

	irccon.Loop()
}
<|endoftext|>