<commit_before>package torrentfs

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	_ "net/http/pprof"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"golang.org/x/net/context"

	"bitbucket.org/anacrolix/go.torrent"
	"bitbucket.org/anacrolix/go.torrent/testutil"
	"bitbucket.org/anacrolix/go.torrent/util"
	"github.com/anacrolix/libtorgo/metainfo"

	"bazil.org/fuse"
	fusefs "bazil.org/fuse/fs"
)

func init() {
	go http.ListenAndServe(":6061", nil)
}

func TestTCPAddrString(t *testing.T) {
	l, err := net.Listen("tcp4", "localhost:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()
	c, err := net.Dial("tcp", l.Addr().String())
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()
	ras := c.RemoteAddr().String()
	ta := &net.TCPAddr{
		IP:   net.IPv4(127, 0, 0, 1),
		Port: util.AddrPort(l.Addr()),
	}
	s := ta.String()
	if ras != s {
		t.FailNow()
	}
}

type testLayout struct {
	BaseDir   string
	MountDir  string
	Completed string
	Metainfo  *metainfo.MetaInfo
}

func (me *testLayout) Destroy() error {
	return os.RemoveAll(me.BaseDir)
}

func newGreetingLayout() (tl testLayout, err error) {
	tl.BaseDir, err = ioutil.TempDir("", "torrentfs")
	if err != nil {
		return
	}
	tl.Completed = filepath.Join(tl.BaseDir, "completed")
	os.Mkdir(tl.Completed, 0777)
	tl.MountDir = filepath.Join(tl.BaseDir, "mnt")
	os.Mkdir(tl.MountDir, 0777)
	name := testutil.CreateDummyTorrentData(tl.Completed)
	metaInfoBuf := &bytes.Buffer{}
	testutil.CreateMetaInfo(name, metaInfoBuf)
	tl.Metainfo, err = metainfo.Load(metaInfoBuf)
	return
}

// Unmount without first killing the FUSE connection while there are FUSE
// operations blocked inside the filesystem code.
func TestUnmountWedged(t *testing.T) {
	layout, err := newGreetingLayout()
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		err := layout.Destroy()
		if err != nil {
			t.Log(err)
		}
	}()
	client, err := torrent.NewClient(&torrent.Config{
		DataDir:         filepath.Join(layout.BaseDir, "incomplete"),
		DisableTrackers: true,
		NoDHT:           true,

		NoDefaultBlocklist: true,
	})
	defer client.Stop()
	client.AddTorrent(layout.Metainfo)
	fs := New(client)
	fuseConn, err := fuse.Mount(layout.MountDir)
	if err != nil {
		if strings.Contains(err.Error(), "fuse") {
			t.Skip(err)
		}
		t.Fatal(err)
	}
	go func() {
		server := fusefs.Server{
			FS: fs,
			Debug: func(msg interface{}) {
				t.Log(msg)
			},
		}
		server.Serve(fuseConn)
	}()
	<-fuseConn.Ready
	if err := fuseConn.MountError; err != nil {
		t.Fatal(err)
	}
	// Read the greeting file, though it will never be available. This should
	// "wedge" FUSE, requiring the fs object to be forcibly destroyed. The
	// read call will return with a FS error.
	go func() {
		_, err := ioutil.ReadFile(filepath.Join(layout.MountDir, layout.Metainfo.Info.Name))
		if err == nil {
			t.Fatal("expected error reading greeting")
		}
	}()

	// Wait until the read has blocked inside the filesystem code.
	fs.mu.Lock()
	for fs.blockedReads != 1 {
		fs.event.Wait()
	}
	fs.mu.Unlock()

	fs.Destroy()

	for {
		err = fuse.Unmount(layout.MountDir)
		if err != nil {
			t.Logf("error unmounting: %s", err)
			time.Sleep(time.Millisecond)
		} else {
			break
		}
	}

	err = fuseConn.Close()
	if err != nil {
		t.Fatalf("error closing fuse conn: %s", err)
	}
}

func TestDownloadOnDemand(t *testing.T) {
	layout, err := newGreetingLayout()
	if err != nil {
		t.Fatal(err)
	}
	defer layout.Destroy()
	seeder, err := torrent.NewClient(&torrent.Config{
		DataDir:         layout.Completed,
		DisableTrackers: true,
		NoDHT:           true,
		ListenAddr:      ":0",

		NoDefaultBlocklist: true,
		// Ensure that the metainfo is obtained over the wire, since we added
		// the torrent to the seeder by magnet.
		DisableMetainfoCache: true,
	})
	if err != nil {
		t.Fatalf("error creating seeder client: %s", err)
	}
	seeder.SetIPBlockList(nil)
	defer seeder.Stop()
	http.HandleFunc("/seeder", func(w http.ResponseWriter, req *http.Request) {
		seeder.WriteStatus(w)
	})
	_, err = seeder.AddMagnet(fmt.Sprintf("magnet:?xt=urn:btih:%x", layout.Metainfo.Info.Hash))
	if err != nil {
		t.Fatal(err)
	}
	leecher, err := torrent.NewClient(&torrent.Config{
		DisableTrackers: true,
		NoDHT:           true,
		ListenAddr:      ":0",
		DisableTCP:      true,

		NoDefaultBlocklist: true,

		TorrentDataOpener: func(info *metainfo.Info) (torrent.TorrentData, error) {
			return mmap.TorrentData(info, filepath.Join(layout.BaseDir, "download"))
		},

		// This can be used to check if clients can connect to other clients
		// with the same ID.

		// PeerID: seeder.PeerID(),
	})
	leecher.SetIPBlockList(nil)
	http.HandleFunc("/leecher", func(w http.ResponseWriter, req *http.Request) {
		leecher.WriteStatus(w)
	})
	defer leecher.Stop()
	leecher.AddTorrent(layout.Metainfo)
	var ih torrent.InfoHash
	util.CopyExact(ih[:], layout.Metainfo.Info.Hash)
	leecher.AddPeers(ih, []torrent.Peer{func() torrent.Peer {
		_, port, err := net.SplitHostPort(seeder.ListenAddr().String())
		if err != nil {
			panic(err)
		}
		portInt64, err := strconv.ParseInt(port, 0, 0)
		if err != nil {
			panic(err)
		}
		return torrent.Peer{
			IP:   net.IPv6loopback,
			Port: int(portInt64),
		}
	}()})
	fs := New(leecher)
	defer fs.Destroy()
	root, _ := fs.Root()
	node, _ := root.(fusefs.NodeStringLookuper).Lookup(context.Background(), "greeting")
	size := int(node.Attr().Size)
	resp := &fuse.ReadResponse{
		Data: make([]byte, size),
	}
	node.(fusefs.HandleReader).Read(context.Background(), &fuse.ReadRequest{
		Size: size,
	}, resp)
	content := resp.Data
	if string(content) != testutil.GreetingFileContents {
		t.FailNow()
	}
}
<commit_msg>Fix imports<commit_after>package torrentfs

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	_ "net/http/pprof"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"golang.org/x/net/context"

	"bitbucket.org/anacrolix/go.torrent"
	"bitbucket.org/anacrolix/go.torrent/data/mmap"
	"bitbucket.org/anacrolix/go.torrent/testutil"
	"bitbucket.org/anacrolix/go.torrent/util"

	"github.com/anacrolix/libtorgo/metainfo"

	"bazil.org/fuse"
	fusefs "bazil.org/fuse/fs"
)

func init() {
	go http.ListenAndServe(":6061", nil)
}

func TestTCPAddrString(t *testing.T) {
	l, err := net.Listen("tcp4", "localhost:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()
	c, err := net.Dial("tcp", l.Addr().String())
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()
	ras := c.RemoteAddr().String()
	ta := &net.TCPAddr{
		IP:   net.IPv4(127, 0, 0, 1),
		Port: util.AddrPort(l.Addr()),
	}
	s := ta.String()
	if ras != s {
		t.FailNow()
	}
}

type testLayout struct {
	BaseDir   string
	MountDir  string
	Completed string
	Metainfo  *metainfo.MetaInfo
}

func (me *testLayout) Destroy() error {
	return os.RemoveAll(me.BaseDir)
}

func newGreetingLayout() (tl testLayout, err error) {
	tl.BaseDir, err = ioutil.TempDir("", "torrentfs")
	if err != nil {
		return
	}
	tl.Completed = filepath.Join(tl.BaseDir, "completed")
	os.Mkdir(tl.Completed, 0777)
	tl.MountDir = filepath.Join(tl.BaseDir, "mnt")
	os.Mkdir(tl.MountDir, 0777)
	name := testutil.CreateDummyTorrentData(tl.Completed)
	metaInfoBuf := &bytes.Buffer{}
	testutil.CreateMetaInfo(name, metaInfoBuf)
	tl.Metainfo, err = metainfo.Load(metaInfoBuf)
	return
}

// Unmount without first killing the FUSE connection while there are FUSE
// operations blocked inside the filesystem code.
func TestUnmountWedged(t *testing.T) {
	layout, err := newGreetingLayout()
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		err := layout.Destroy()
		if err != nil {
			t.Log(err)
		}
	}()
	client, err := torrent.NewClient(&torrent.Config{
		DataDir:         filepath.Join(layout.BaseDir, "incomplete"),
		DisableTrackers: true,
		NoDHT:           true,

		NoDefaultBlocklist: true,
	})
	defer client.Stop()
	client.AddTorrent(layout.Metainfo)
	fs := New(client)
	fuseConn, err := fuse.Mount(layout.MountDir)
	if err != nil {
		if strings.Contains(err.Error(), "fuse") {
			t.Skip(err)
		}
		t.Fatal(err)
	}
	go func() {
		server := fusefs.Server{
			FS: fs,
			Debug: func(msg interface{}) {
				t.Log(msg)
			},
		}
		server.Serve(fuseConn)
	}()
	<-fuseConn.Ready
	if err := fuseConn.MountError; err != nil {
		t.Fatal(err)
	}
	// Read the greeting file, though it will never be available. This should
	// "wedge" FUSE, requiring the fs object to be forcibly destroyed. The
	// read call will return with a FS error.
	go func() {
		_, err := ioutil.ReadFile(filepath.Join(layout.MountDir, layout.Metainfo.Info.Name))
		if err == nil {
			t.Fatal("expected error reading greeting")
		}
	}()

	// Wait until the read has blocked inside the filesystem code.
	fs.mu.Lock()
	for fs.blockedReads != 1 {
		fs.event.Wait()
	}
	fs.mu.Unlock()

	fs.Destroy()

	for {
		err = fuse.Unmount(layout.MountDir)
		if err != nil {
			t.Logf("error unmounting: %s", err)
			time.Sleep(time.Millisecond)
		} else {
			break
		}
	}

	err = fuseConn.Close()
	if err != nil {
		t.Fatalf("error closing fuse conn: %s", err)
	}
}

func TestDownloadOnDemand(t *testing.T) {
	layout, err := newGreetingLayout()
	if err != nil {
		t.Fatal(err)
	}
	defer layout.Destroy()
	seeder, err := torrent.NewClient(&torrent.Config{
		DataDir:         layout.Completed,
		DisableTrackers: true,
		NoDHT:           true,
		ListenAddr:      ":0",

		NoDefaultBlocklist: true,
		// Ensure that the metainfo is obtained over the wire, since we added
		// the torrent to the seeder by magnet.
		DisableMetainfoCache: true,
	})
	if err != nil {
		t.Fatalf("error creating seeder client: %s", err)
	}
	seeder.SetIPBlockList(nil)
	defer seeder.Stop()
	http.HandleFunc("/seeder", func(w http.ResponseWriter, req *http.Request) {
		seeder.WriteStatus(w)
	})
	_, err = seeder.AddMagnet(fmt.Sprintf("magnet:?xt=urn:btih:%x", layout.Metainfo.Info.Hash))
	if err != nil {
		t.Fatal(err)
	}
	leecher, err := torrent.NewClient(&torrent.Config{
		DisableTrackers: true,
		NoDHT:           true,
		ListenAddr:      ":0",
		DisableTCP:      true,

		NoDefaultBlocklist: true,

		TorrentDataOpener: func(info *metainfo.Info) (torrent.TorrentData, error) {
			return mmap.TorrentData(info, filepath.Join(layout.BaseDir, "download"))
		},

		// This can be used to check if clients can connect to other clients
		// with the same ID.

		// PeerID: seeder.PeerID(),
	})
	leecher.SetIPBlockList(nil)
	http.HandleFunc("/leecher", func(w http.ResponseWriter, req *http.Request) {
		leecher.WriteStatus(w)
	})
	defer leecher.Stop()
	leecher.AddTorrent(layout.Metainfo)
	var ih torrent.InfoHash
	util.CopyExact(ih[:], layout.Metainfo.Info.Hash)
	leecher.AddPeers(ih, []torrent.Peer{func() torrent.Peer {
		_, port, err := net.SplitHostPort(seeder.ListenAddr().String())
		if err != nil {
			panic(err)
		}
		portInt64, err := strconv.ParseInt(port, 0, 0)
		if err != nil {
			panic(err)
		}
		return torrent.Peer{
			IP:   net.IPv6loopback,
			Port: int(portInt64),
		}
	}()})
	fs := New(leecher)
	defer fs.Destroy()
	root, _ := fs.Root()
	node, _ := root.(fusefs.NodeStringLookuper).Lookup(context.Background(), "greeting")
	size := int(node.Attr().Size)
	resp := &fuse.ReadResponse{
		Data: make([]byte, size),
	}
	node.(fusefs.HandleReader).Read(context.Background(), &fuse.ReadRequest{
		Size: size,
	}, resp)
	content := resp.Data
	if string(content) != testutil.GreetingFileContents {
		t.FailNow()
	}
}
<|endoftext|>


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

package host

import (
	"github.com/control-center/serviced/utils"
	"github.com/control-center/serviced/validation"
	"github.com/zenoss/glog"

	"strings"
	"testing"
)

func containsAll(ve *validation.ValidationError, errStrings ...string) bool {
	for _, err := range errStrings {
		if !contains(ve, err) {
			return false
		}
	}
	return true
}
func contains(ve *validation.ValidationError, errString string) bool {

	for _, err := range ve.Errors {
		if err.Error() == errString {
			return true
		}
	}
	return false
}

type validateCase struct {
	id             string
	rpcport        int
	poolid         string
	ip             string
	expectedErrors []string
}

var validatetable []validateCase

func init() {

	ip, err := utils.GetIPAddress()
	if err != nil {
		glog.Errorf("Could not determine ip %v", err)
	}

	validatetable = []validateCase{
		validateCase{"", 65535, "", "", []string{"empty string for Host.ID", "empty string for Host.PoolID", "invalid IP Address "}},
		validateCase{"hostid", 65535, "", "", []string{"empty string for Host.PoolID", "invalid IP Address "}},
		validateCase{"", 65535, "poolid", "", []string{"empty string for Host.ID", "invalid IP Address "}},
		validateCase{"hostid", 65535, "poolid", "", []string{"invalid IP Address "}},
		validateCase{"hostid", 65535, "poolid", "blam", []string{"invalid IP Address blam"}},
		validateCase{"hostid", 65535, "poolid", "127.0.0.1", []string{"host ip can not be a loopback address"}},
		validateCase{"hostid", -1, "poolid", ip, []string{"not in valid port range: -1"}},
		validateCase{"hostid", 65536, "poolid", ip, []string{"not in valid port range: 65536"}},
		validateCase{"deadb30f", 65535, "poolid", ip, []string{}},
	}

}

func Test_ValidateTable(t *testing.T) {
	for idx, test := range validatetable {
		h := New()
		h.ID = test.id
		h.RPCPort = test.rpcport
		h.PoolID = test.poolid
		h.IPAddr = test.ip

		err := h.ValidEntity()
		if len(test.expectedErrors) > 0 {
			if verr, isVErr := err.(*validation.ValidationError); !isVErr {
				t.Errorf("expected ValidationError for case %v, got %v", idx, err)
			} else if !containsAll(verr, test.expectedErrors...) {
				t.Errorf("Did not find expected errors for case %v, %v", idx, verr)
			}
		} else if err != nil {
			t.Errorf("Unexpected error testig case %v %v: %v", idx, test, err)
		}

	}

}

func Test_BuildInvalid(t *testing.T) {

	empty := make([]string, 0)
	_, err := Build("", "65535", "", "", empty...)
	if err == nil {
		t.Errorf("expected error")
	}

	_, err = Build("1234", "65535", "", "", empty...)
	if err == nil {
		t.Errorf("expected error")
	}

	_, err = Build("", "65535", "", "", empty...)
	if err == nil {
		t.Errorf("expected error")
	}

	_, err = Build("127.0.0.1", "65535", "poolid", "", empty...)
	if err == nil || err.Error() != "loopback address 127.0.0.1 cannot be used to register a host" {
		t.Errorf("Unexpected error %v", err)
	}

	_, err = Build("", "65535", "poolid", "", "127.0.0.1")
	if err == nil || err.Error() != "loopback address 127.0.0.1 cannot be used as an IP Resource" {
		t.Errorf("Unexpected error %v", err)
	}

	_, err = Build("", "65535", "poolid", "")
	if err == nil {
		t.Errorf("Expected error %v", err)
	}

}

func Test_Build(t *testing.T) {
	ip, err := utils.GetIPAddress()
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}

	empty := make([]string, 0)
	host, err := Build("", "65535", "test_pool", "", empty...)
	glog.Infof("build  error %v", err)
	if err != nil {
		t.Errorf("Unexpected error %v", err)
	}
	if err = host.ValidEntity(); err != nil {
		t.Errorf("Validation failed %v", err)
	}

	if len(host.IPs) != 1 {
		t.Errorf("Unexpected result %v", host.IPs)
	}

	if host.IPAddr != ip {
		t.Errorf("Expected ip %v, got %v", ip, host.IPs)
	}

	if host.IPs[0].IPAddress != ip {
		t.Errorf("Expected ip %v, got %v", ip, host.IPs)
	}

}

func Test_getIPResources(t *testing.T) {

	ips, err := getIPResources("dummy_hostId", "123")
	if err == nil || err.Error() != "IP address 123 not valid for this host" {
		t.Errorf("Unexpected error %v", err)
	}
	if len(ips) != 0 {
		t.Errorf("Unexpected result %v", ips)
	}

	ips, err = getIPResources("dummy_hostId", "127.0.0.1")
	if err == nil || err.Error() != "loopback address 127.0.0.1 cannot be used as an IP Resource" {
		t.Errorf("Unexpected error %v", err)
	}
	if len(ips) != 0 {
		t.Errorf("Unexpected result %v", ips)
	}

	ip, err := utils.GetIPAddress()
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}

	validIPs := []string{ip, strings.ToLower(ip), strings.ToUpper(ip)}
	for _, validIP := range validIPs {
		ips, err = getIPResources("dummy_hostId", validIP)
		if err != nil {
			if err != nil {
				t.Errorf("Unexpected error %v", err)
			}
			if len(ips) != 1 {
				t.Errorf("Unexpected result %v", ips)
			}
		}
	}
}

func Test_getOSKernelData(t *testing.T) {
	kernelVersion, kernelRelease, err := getOSKernelData()

	if err != nil {
		t.Errorf("Unexpected error %v", err)
	}

	if kernelVersion == "There was an error retrieving kernel data" || kernelRelease == "There was an error retrieving kernel data" {
		t.Errorf("Unexpected error %v", err)
	}

	glog.Infof("Kernel Version:  %v Kernel Release: %v", kernelVersion, kernelRelease)
}
<commit_msg>fixed another unit test<commit_after>// Copyright 2014 The Serviced Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package host

import (
	"github.com/control-center/serviced/utils"
	"github.com/control-center/serviced/validation"
	"github.com/zenoss/glog"

	"strings"
	"testing"
)

func containsAll(ve *validation.ValidationError, errStrings ...string) bool {
	for _, err := range errStrings {
		if !contains(ve, err) {
			return false
		}
	}
	return true
}
func contains(ve *validation.ValidationError, errString string) bool {

	for _, err := range ve.Errors {
		if err.Error() == errString {
			return true
		}
	}
	return false
}

type validateCase struct {
	id             string
	rpcport        int
	poolid         string
	ip             string
	expectedErrors []string
}

var validatetable []validateCase

func init() {

	ip, err := utils.GetIPAddress()
	if err != nil {
		glog.Errorf("Could not determine ip %v", err)
	}

	validatetable = []validateCase{
		validateCase{"", 65535, "", "", []string{"empty string for Host.ID", "empty string for Host.PoolID", "invalid IP Address "}},
		validateCase{"hostid", 65535, "", "", []string{"empty string for Host.PoolID", "invalid IP Address "}},
		validateCase{"", 65535, "poolid", "", []string{"empty string for Host.ID", "invalid IP Address "}},
		validateCase{"hostid", 65535, "poolid", "", []string{"invalid IP Address "}},
		validateCase{"hostid", 65535, "poolid", "blam", []string{"invalid IP Address blam"}},
		validateCase{"hostid", 65535, "poolid", "127.0.0.1", []string{"host ip can not be a loopback address"}},
		validateCase{"hostid", -1, "poolid", ip, []string{"not in valid port range: -1"}},
		validateCase{"hostid", 65536, "poolid", ip, []string{"not in valid port range: 65536"}},
		validateCase{"deadb30f", 65535, "poolid", ip, []string{}},
	}

}

func Test_ValidateTable(t *testing.T) {
	for idx, test := range validatetable {
		h := New()
		h.ID = test.id
		h.RPCPort = test.rpcport
		h.PoolID = test.poolid
		h.IPAddr = test.ip

		err := h.ValidEntity()
		if len(test.expectedErrors) > 0 {
			if verr, isVErr := err.(*validation.ValidationError); !isVErr {
				t.Errorf("expected ValidationError for case %v, got %v", idx, err)
			} else if !containsAll(verr, test.expectedErrors...) {
				t.Errorf("Did not find expected errors for case %v, %v", idx, verr)
			}
		} else if err != nil {
			t.Errorf("Unexpected error testig case %v %v: %v", idx, test, err)
		}

	}

}

func Test_BuildInvalid(t *testing.T) {

	empty := make([]string, 0)
	_, err := Build("", "65535", "", "", empty...)
	if err == nil {
		t.Errorf("expected error")
	}

	_, err = Build("1234", "65535", "", "", empty...)
	if err == nil {
		t.Errorf("expected error")
	}

	_, err = Build("", "65535", "", "", empty...)
	if err == nil {
		t.Errorf("expected error")
	}

	_, err = Build("127.0.0.1", "65535", "poolid", "", empty...)
	if err == nil || err.Error() != "loopback address 127.0.0.1 cannot be used to register a host" {
		t.Errorf("Unexpected error %v", err)
	}

	_, err = Build("", "65535", "poolid", "", "127.0.0.1")
	if err == nil || err.Error() != "loopback address 127.0.0.1 cannot be used as an IP Resource" {
		t.Errorf("Unexpected error %v", err)
	}

	_, err = Build("", "65535", "poolid", "", "")
	if err == nil {
		t.Errorf("Expected error %v", err)
	}

}

func Test_Build(t *testing.T) {
	ip, err := utils.GetIPAddress()
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}

	empty := make([]string, 0)
	host, err := Build("", "65535", "test_pool", "", empty...)
	glog.Infof("build  error %v", err)
	if err != nil {
		t.Errorf("Unexpected error %v", err)
	}
	if err = host.ValidEntity(); err != nil {
		t.Errorf("Validation failed %v", err)
	}

	if len(host.IPs) != 1 {
		t.Errorf("Unexpected result %v", host.IPs)
	}

	if host.IPAddr != ip {
		t.Errorf("Expected ip %v, got %v", ip, host.IPs)
	}

	if host.IPs[0].IPAddress != ip {
		t.Errorf("Expected ip %v, got %v", ip, host.IPs)
	}

}

func Test_getIPResources(t *testing.T) {

	ips, err := getIPResources("dummy_hostId", "123")
	if err == nil || err.Error() != "IP address 123 not valid for this host" {
		t.Errorf("Unexpected error %v", err)
	}
	if len(ips) != 0 {
		t.Errorf("Unexpected result %v", ips)
	}

	ips, err = getIPResources("dummy_hostId", "127.0.0.1")
	if err == nil || err.Error() != "loopback address 127.0.0.1 cannot be used as an IP Resource" {
		t.Errorf("Unexpected error %v", err)
	}
	if len(ips) != 0 {
		t.Errorf("Unexpected result %v", ips)
	}

	ip, err := utils.GetIPAddress()
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}

	validIPs := []string{ip, strings.ToLower(ip), strings.ToUpper(ip)}
	for _, validIP := range validIPs {
		ips, err = getIPResources("dummy_hostId", validIP)
		if err != nil {
			if err != nil {
				t.Errorf("Unexpected error %v", err)
			}
			if len(ips) != 1 {
				t.Errorf("Unexpected result %v", ips)
			}
		}
	}
}

func Test_getOSKernelData(t *testing.T) {
	kernelVersion, kernelRelease, err := getOSKernelData()

	if err != nil {
		t.Errorf("Unexpected error %v", err)
	}

	if kernelVersion == "There was an error retrieving kernel data" || kernelRelease == "There was an error retrieving kernel data" {
		t.Errorf("Unexpected error %v", err)
	}

	glog.Infof("Kernel Version:  %v Kernel Release: %v", kernelVersion, kernelRelease)
}
<|endoftext|>


<commit_before>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package downloader

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"

	"github.com/juju/errors"
	"github.com/juju/loggo"
	"github.com/juju/utils"
	"launchpad.net/tomb"
)

var logger = loggo.GetLogger("juju.downloader")

// Request holds a single download request.
type Request struct {
	// URL is the location from which the file will be downloaded.
	URL *url.URL

	// TargetDir is the directory into which the file will be downloaded.
	// It defaults to os.TempDir().
	TargetDir string
}

// Status represents the status of a completed download.
type Status struct {
	// File holds the downloaded data on success.
	File *os.File

	// Err describes any error encountered while downloading.
	Err error
}

// Download can download a file from the network.
type Download struct {
	tomb                 tomb.Tomb
	done                 chan Status
	hostnameVerification utils.SSLHostnameVerification
}

// New returns a new Download instance based on the provided request.
//
// hostnameVerification is that which should be used for the client.
// If it is disableSSLHostnameVerification then a non-validating
// client will be used.
func New(req Request, hostnameVerification utils.SSLHostnameVerification) *Download {
	d := &Download{
		done:                 make(chan Status),
		hostnameVerification: hostnameVerification,
	}
	go d.run(req)
	return d
}

// Stop stops any download that's in progress.
func (d *Download) Stop() {
	d.tomb.Kill(nil)
	d.tomb.Wait()
}

// Done returns a channel that receives a status when the download has
// completed.  It is the receiver's responsibility to close and remove
// the received file.
func (d *Download) Done() <-chan Status {
	return d.done
}

// Wait blocks until the download completes or the abort channel receives.
func (dl *Download) Wait(abort <-chan struct{}) (Status, error) {
	defer dl.Stop()

	select {
	case <-abort:
		logger.Infof("download aborted")
		return Status{}, errors.New("aborted")
	case status := <-dl.Done():
		if status.Err != nil {
			return Status{}, errors.Trace(status.Err)
		}
		return status, nil
	}
}

func (d *Download) run(req Request) {
	defer d.tomb.Done()

	// TODO(dimitern) 2013-10-03 bug #1234715
	// Add a testing HTTPS storage to verify the
	// disableSSLHostnameVerification behavior here.
	file, err := download(req, d.sendHTTPDownload)
	if err != nil {
		err = fmt.Errorf("cannot download %q: %v", req.URL, err)
	}

	status := Status{
		File: file,
		Err:  err,
	}
	select {
	case d.done <- status:
	case <-d.tomb.Dying():
		cleanTempFile(file)
	}
}

func (d *Download) sendHTTPDownload(url *url.URL) (io.ReadCloser, error) {
	// TODO(rog) make the download operation interruptible.
	client := utils.GetHTTPClient(d.hostnameVerification)
	resp, err := client.Get(url.String())
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		resp.Body.Close()
		return nil, fmt.Errorf("bad http response: %v", resp.Status)
	}
	return resp.Body, nil
}

func download(req Request, httpDownload func(url *url.URL) (io.ReadCloser, error)) (file *os.File, err error) {
	dir := req.TargetDir
	if dir == "" {
		dir = os.TempDir()
	}
	tempFile, err := ioutil.TempFile(dir, "inprogress-")
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			cleanTempFile(tempFile)
		}
	}()

	reader, err := httpDownload(req.URL)
	if err != nil {
		return nil, err
	}
	defer reader.Close()

	_, err = io.Copy(tempFile, reader)
	if err != nil {
		return nil, err
	}
	if _, err := tempFile.Seek(0, 0); err != nil {
		return nil, err
	}
	return tempFile, nil
}

func cleanTempFile(f *os.File) {
	if f != nil {
		f.Close()
		if err := os.Remove(f.Name()); err != nil {
			logger.Warningf("cannot remove temp file %q: %v", f.Name(), err)
		}
	}
}
<commit_msg>Deal with a blob opener in Download.<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package downloader

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"

	"github.com/juju/errors"
	"github.com/juju/loggo"
	"github.com/juju/utils"
	"launchpad.net/tomb"
)

var logger = loggo.GetLogger("juju.downloader")

// Request holds a single download request.
type Request struct {
	// URL is the location from which the file will be downloaded.
	URL *url.URL

	// TargetDir is the directory into which the file will be downloaded.
	// It defaults to os.TempDir().
	TargetDir string
}

// Status represents the status of a completed download.
type Status struct {
	// File holds the downloaded data on success.
	File *os.File

	// Err describes any error encountered while downloading.
	Err error
}

// Download can download a file from the network.
type Download struct {
	tomb     tomb.Tomb
	done     chan Status
	openBlob func(*url.URL) (io.ReadCloser, error)
}

// New returns a new Download instance based on the provided request.
//
// openBlob is used to gain access to the blob, whether through an HTTP
// request or some other means.
func New(req Request, hostnameVerification utils.SSLHostnameVerification) *Download {
	d := &Download{
		done:     make(chan Status),
		openBlob: httpBlobOpener(hostnameVerification),
	}
	go d.run(req)
	return d
}

// Stop stops any download that's in progress.
func (d *Download) Stop() {
	d.tomb.Kill(nil)
	d.tomb.Wait()
}

// Done returns a channel that receives a status when the download has
// completed.  It is the receiver's responsibility to close and remove
// the received file.
func (d *Download) Done() <-chan Status {
	return d.done
}

// Wait blocks until the download completes or the abort channel receives.
func (dl *Download) Wait(abort <-chan struct{}) (Status, error) {
	defer dl.Stop()

	select {
	case <-abort:
		logger.Infof("download aborted")
		return Status{}, errors.New("aborted")
	case status := <-dl.Done():
		if status.Err != nil {
			return Status{}, errors.Trace(status.Err)
		}
		return status, nil
	}
}

func (d *Download) run(req Request) {
	defer d.tomb.Done()

	// TODO(dimitern) 2013-10-03 bug #1234715
	// Add a testing HTTPS storage to verify the
	// disableSSLHostnameVerification behavior here.
	file, err := download(req, d.openBlob)
	if err != nil {
		err = fmt.Errorf("cannot download %q: %v", req.URL, err)
	}

	status := Status{
		File: file,
		Err:  err,
	}
	select {
	case d.done <- status:
	case <-d.tomb.Dying():
		cleanTempFile(file)
	}
}

func httpBlobOpener(hostnameVerification utils.SSLHostnameVerification) func(*url.URL) (io.ReadCloser, error) {
	return func(url *url.URL) (io.ReadCloser, error) {
		// TODO(rog) make the download operation interruptible.
		client := utils.GetHTTPClient(hostnameVerification)
		resp, err := client.Get(url.String())
		if err != nil {
			return nil, err
		}
		if resp.StatusCode != http.StatusOK {
			resp.Body.Close()
			return nil, fmt.Errorf("bad http response: %v", resp.Status)
		}
		return resp.Body, nil
	}
}

func download(req Request, openBlob func(*url.URL) (io.ReadCloser, error)) (file *os.File, err error) {
	dir := req.TargetDir
	if dir == "" {
		dir = os.TempDir()
	}
	tempFile, err := ioutil.TempFile(dir, "inprogress-")
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			cleanTempFile(tempFile)
		}
	}()

	reader, err := openBlob(req.URL)
	if err != nil {
		return nil, err
	}
	defer reader.Close()

	_, err = io.Copy(tempFile, reader)
	if err != nil {
		return nil, err
	}
	if _, err := tempFile.Seek(0, 0); err != nil {
		return nil, err
	}
	return tempFile, nil
}

func cleanTempFile(f *os.File) {
	if f != nil {
		f.Close()
		if err := os.Remove(f.Name()); err != nil {
			logger.Warningf("cannot remove temp file %q: %v", f.Name(), err)
		}
	}
}
<|endoftext|>


<commit_before>package docker

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/fsouza/go-dockerclient"
	"github.com/iron-io/titan/runner/drivers"
	"github.com/iron-io/titan/runner/drivers/common"
)

const (
	configFile  = ".task_config"
	logFile     = "proc.out"
	payloadFile = ".task_payload"
	runtimeDir  = "/mnt"
	taskDir     = "/task"
)

type dockerDriver struct {
	conf       common.Config
	docker     *docker.Client
	clock      common.Clock
	hostname   string
	runtimeDir string
}

func NewDocker(conf common.Config, clock common.Clock, hostname string) (*dockerDriver, error) {
	// docker, err := docker.NewClient(conf.Docker)
	docker, err := docker.NewClientFromEnv()
	if err != nil {
		return nil, err
	}

	return &dockerDriver{
		conf:       conf,
		docker:     docker,
		clock:      clock,
		hostname:   hostname,
		runtimeDir: runtimeDir,
	}, nil
}

func (drv *dockerDriver) Run(task drivers.ContainerTask, isCancelled chan bool) drivers.RunResult {
	defer os.RemoveAll(drv.taskDir(task))

	if err := drv.ensureTaskDir(task); err != nil {
		return drv.error(err)
	}

	container, err := drv.startTask(task)
	if container != "" {
		// It is possible that startTask created a container but could not start it. So always try to remove a valid container.
		defer drv.removeContainer(container)
	}

	if err != nil {
		return drv.error(err)
	}

	sentence := make(chan string, 1)

	done := make(chan struct{})
	defer close(done)
	go drv.nanny(container, task, sentence, done, isCancelled)

	log, err := drv.ensureLogFile(task)
	if err != nil {
		return drv.error(err)
	}

	w := &limitedWriter{W: log, N: 8 * 1024 * 1024 * 1024} // TODO get max log size from somewhere

	// Docker sometimes fails to close the attach response connection even after
	// the container stops, leaving the runner stuck. We use a non-blocking
	// attach so we can sleep a bit after WaitContainer returns and then forcibly
	// close the connection.
	closer, err := drv.docker.AttachToContainerNonBlocking(docker.AttachToContainerOptions{
		Container: container, OutputStream: w, ErrorStream: w,
		Stream: true, Logs: true, Stdout: true, Stderr: true})
	defer closer.Close()
	if err != nil {
		return drv.error(err)
	}

	exitCode, err := drv.docker.WaitContainer(container)
	time.Sleep(10 * time.Millisecond)

	done <- struct{}{}
	if err != nil {
		return drv.error(err)
	}
	status, err := drv.status(exitCode, sentence)
	return &runResult{
		StatusValue: status,
		LogData:     log,
		Err:         err,
	}
}

func (drv *dockerDriver) taskDir(task drivers.ContainerTask) string {
	return drv.conf.Root + taskDir + "/" + task.Id()
}

func (drv *dockerDriver) ensureTaskDir(task drivers.ContainerTask) error {
	return os.Mkdir(drv.taskDir(task), 0777)
}

func (drv *dockerDriver) ensureLogFile(task drivers.ContainerTask) (*os.File, error) {
	log, err := os.Create(drv.taskDir(task) + "/" + logFile)
	if err != nil {
		return nil, fmt.Errorf("%v %v", "couldn't open task log", err)
	}
	return log, nil
}

func (drv *dockerDriver) error(err error) *runResult {
	return &runResult{
		Err:         err,
		StatusValue: drivers.StatusError,
	}
}

func (drv *dockerDriver) startTask(task drivers.ContainerTask) (dockerId string, err error) {
	if task.Image() == "" {
		// TODO support for old
		return "", errors.New("no image specified, this runner cannot run this")
	}

	var cmd []string
	if task.Command() != "" {
		// TODO: maybe check for spaces or shell meta characters?
		// There's a possibility that the container doesn't have sh.
		cmd = []string{"sh", "-c", task.Command()}
	}

	hostTaskDir := drv.taskDir(task)

	config := hostTaskDir + "/" + configFile
	payload := hostTaskDir + "/" + payloadFile
	err = writeFile(config, task.Config())
	if err != nil {
		return "", err
	}
	err = writeFile(payload, task.Payload())
	if err != nil {
		return "", err
	}

	envvars := make([]string, 0, len(task.EnvVars())+4)
	for name, val := range task.EnvVars() {
		envvars = append(envvars, name+"="+val)
	}
	envvars = append(envvars, "TASK_ID="+task.Id())
	envvars = append(envvars, "PAYLOAD_FILE="+runtimePath(payloadFile))
	envvars = append(envvars, "TASK_DIR="+runtimePath(taskDir))
	envvars = append(envvars, "CONFIG_FILE="+runtimePath(configFile))
	absTaskDir, err := filepath.Abs(hostTaskDir)
	if err != nil {
		return "", err
	}

	cID, err := drv.createContainer(envvars, cmd, task.Image(), absTaskDir)
	if err != nil {
		return "", err
	}

	err = drv.docker.StartContainer(cID, nil)
	return cID, err
}

func runtimePath(s ...string) string {
	return path.Join(append([]string{runtimeDir}, s...)...)
}

func writeFile(name, body string) error {
	f, err := os.Create(name)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = io.Copy(f, strings.NewReader(body))
	return err
}

func (drv *dockerDriver) createContainer(envvars, cmd []string, image string, absTaskDir string) (string, error) {
	container := docker.CreateContainerOptions{
		Config: &docker.Config{
			Env:       envvars,
			Cmd:       cmd,
			Memory:    drv.conf.Memory,
			CPUShares: drv.conf.CPUShares,
			Hostname:  drv.hostname,
			Image:     image,
			Volumes: map[string]struct{}{
				drv.runtimeDir: {},
			},
		},
		HostConfig: &docker.HostConfig{
			Binds: []string{absTaskDir + ":" + drv.runtimeDir},
		},
	}

	c, err := drv.docker.CreateContainer(container)
	if err != nil {
		if err != docker.ErrNoSuchImage {
			return "", err
		}

		repo, tag := docker.ParseRepositoryTag(image)

		err = drv.docker.PullImage(docker.PullImageOptions{Repository: repo, Tag: tag}, docker.AuthConfiguration{}) // TODO AuthConfig from code
		if err != nil {
			return "", err
		}

		// should have it now
		c, err = drv.docker.CreateContainer(container)
		if err != nil {
			return "", err
		}
	}
	return c.ID, nil
}

func (drv *dockerDriver) removeContainer(container string) {
	// TODO: trap error
	drv.docker.RemoveContainer(docker.RemoveContainerOptions{
		ID: container, Force: true, RemoveVolumes: true})
}

// watch for cancel or timeout and kill process.
func (drv *dockerDriver) nanny(container string, task drivers.ContainerTask, sentence chan<- string, done chan struct{}, isCancelledSignal chan bool) {
	t := time.Duration(drv.conf.DefaultTimeout) * time.Second
	if task.Timeout() != 0 {
		t = time.Duration(task.Timeout()) * time.Second
	}
	// Just in case we make a calculation mistake.
	// TODO: trap this condition
	// if t > 24*time.Hour {
	// 	ctx.Warn("task has really long timeout of greater than a day")
	// }
	// Log task timeout values so we can get a good idea of what people generally set it to.
	timeout := drv.clock.After(t)

	select {
	case <-done:
		return
	case <-timeout:
		sentence <- drivers.StatusTimeout
		drv.cancel(container)
	case isCancelled := <-isCancelledSignal:
		if isCancelled {
			sentence <- drivers.StatusKilled
			drv.cancel(container)
		}
	}

	<-done
}

func (drv *dockerDriver) status(exitCode int, sentence <-chan string) (string, error) {
	var status string
	var err error
	select {
	case status = <-sentence: // use this if killed / timed out
	default:
		switch exitCode {
		case 0:
			status = drivers.StatusComplete
		case 137:
			// Probably an OOM kill

			// TODO: try harder to detect OOM kills. We can call
			// docker.InspectContainer and look at
			// container.State.OOMKilled, but this field isn't set
			// consistently.
			// See: https://github.com/docker/docker/issues/15621

			status = drivers.StatusKilled
			// TODO: better message; show memory limit
			err = drivers.ErrOutOfMemory
		default:
			status = drivers.StatusError
			err = drivers.ErrUnknown
		}
	}
	return status, err
}

// TODO we _sure_ it's dead?
func (drv *dockerDriver) cancel(container string) { drv.docker.StopContainer(container, 5) }
<commit_msg>driver/docker: remove dependency on clock<commit_after>package docker

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/fsouza/go-dockerclient"
	"github.com/iron-io/titan/runner/drivers"
	"github.com/iron-io/titan/runner/drivers/common"
)

const (
	configFile  = ".task_config"
	logFile     = "proc.out"
	payloadFile = ".task_payload"
	runtimeDir  = "/mnt"
	taskDir     = "/task"
)

type dockerDriver struct {
	conf       common.Config
	docker     *docker.Client
	hostname   string
	runtimeDir string
}

func NewDocker(conf common.Config, hostname string) (*dockerDriver, error) {
	// docker, err := docker.NewClient(conf.Docker)
	docker, err := docker.NewClientFromEnv()
	if err != nil {
		return nil, err
	}

	return &dockerDriver{
		conf:       conf,
		docker:     docker,
		hostname:   hostname,
		runtimeDir: runtimeDir,
	}, nil
}

func (drv *dockerDriver) Run(task drivers.ContainerTask, isCancelled chan bool) drivers.RunResult {
	defer os.RemoveAll(drv.taskDir(task))

	if err := drv.ensureTaskDir(task); err != nil {
		return drv.error(err)
	}

	container, err := drv.startTask(task)
	if container != "" {
		// It is possible that startTask created a container but could not start it. So always try to remove a valid container.
		defer drv.removeContainer(container)
	}

	if err != nil {
		return drv.error(err)
	}

	sentence := make(chan string, 1)

	done := make(chan struct{})
	defer close(done)
	go drv.nanny(container, task, sentence, done, isCancelled)

	log, err := drv.ensureLogFile(task)
	if err != nil {
		return drv.error(err)
	}

	w := &limitedWriter{W: log, N: 8 * 1024 * 1024 * 1024} // TODO get max log size from somewhere

	// Docker sometimes fails to close the attach response connection even after
	// the container stops, leaving the runner stuck. We use a non-blocking
	// attach so we can sleep a bit after WaitContainer returns and then forcibly
	// close the connection.
	closer, err := drv.docker.AttachToContainerNonBlocking(docker.AttachToContainerOptions{
		Container: container, OutputStream: w, ErrorStream: w,
		Stream: true, Logs: true, Stdout: true, Stderr: true})
	defer closer.Close()
	if err != nil {
		return drv.error(err)
	}

	exitCode, err := drv.docker.WaitContainer(container)
	time.Sleep(10 * time.Millisecond)

	done <- struct{}{}
	if err != nil {
		return drv.error(err)
	}
	status, err := drv.status(exitCode, sentence)
	return &runResult{
		StatusValue: status,
		LogData:     log,
		Err:         err,
	}
}

func (drv *dockerDriver) taskDir(task drivers.ContainerTask) string {
	return drv.conf.Root + taskDir + "/" + task.Id()
}

func (drv *dockerDriver) ensureTaskDir(task drivers.ContainerTask) error {
	return os.Mkdir(drv.taskDir(task), 0777)
}

func (drv *dockerDriver) ensureLogFile(task drivers.ContainerTask) (*os.File, error) {
	log, err := os.Create(drv.taskDir(task) + "/" + logFile)
	if err != nil {
		return nil, fmt.Errorf("%v %v", "couldn't open task log", err)
	}
	return log, nil
}

func (drv *dockerDriver) error(err error) *runResult {
	return &runResult{
		Err:         err,
		StatusValue: drivers.StatusError,
	}
}

func (drv *dockerDriver) startTask(task drivers.ContainerTask) (dockerId string, err error) {
	if task.Image() == "" {
		// TODO support for old
		return "", errors.New("no image specified, this runner cannot run this")
	}

	var cmd []string
	if task.Command() != "" {
		// TODO: maybe check for spaces or shell meta characters?
		// There's a possibility that the container doesn't have sh.
		cmd = []string{"sh", "-c", task.Command()}
	}

	hostTaskDir := drv.taskDir(task)

	config := hostTaskDir + "/" + configFile
	payload := hostTaskDir + "/" + payloadFile
	err = writeFile(config, task.Config())
	if err != nil {
		return "", err
	}
	err = writeFile(payload, task.Payload())
	if err != nil {
		return "", err
	}

	envvars := make([]string, 0, len(task.EnvVars())+4)
	for name, val := range task.EnvVars() {
		envvars = append(envvars, name+"="+val)
	}
	envvars = append(envvars, "TASK_ID="+task.Id())
	envvars = append(envvars, "PAYLOAD_FILE="+runtimePath(payloadFile))
	envvars = append(envvars, "TASK_DIR="+runtimePath(taskDir))
	envvars = append(envvars, "CONFIG_FILE="+runtimePath(configFile))
	absTaskDir, err := filepath.Abs(hostTaskDir)
	if err != nil {
		return "", err
	}

	cID, err := drv.createContainer(envvars, cmd, task.Image(), absTaskDir)
	if err != nil {
		return "", err
	}

	err = drv.docker.StartContainer(cID, nil)
	return cID, err
}

func runtimePath(s ...string) string {
	return path.Join(append([]string{runtimeDir}, s...)...)
}

func writeFile(name, body string) error {
	f, err := os.Create(name)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = io.Copy(f, strings.NewReader(body))
	return err
}

func (drv *dockerDriver) createContainer(envvars, cmd []string, image string, absTaskDir string) (string, error) {
	container := docker.CreateContainerOptions{
		Config: &docker.Config{
			Env:       envvars,
			Cmd:       cmd,
			Memory:    drv.conf.Memory,
			CPUShares: drv.conf.CPUShares,
			Hostname:  drv.hostname,
			Image:     image,
			Volumes: map[string]struct{}{
				drv.runtimeDir: {},
			},
		},
		HostConfig: &docker.HostConfig{
			Binds: []string{absTaskDir + ":" + drv.runtimeDir},
		},
	}

	c, err := drv.docker.CreateContainer(container)
	if err != nil {
		if err != docker.ErrNoSuchImage {
			return "", err
		}

		repo, tag := docker.ParseRepositoryTag(image)

		err = drv.docker.PullImage(docker.PullImageOptions{Repository: repo, Tag: tag}, docker.AuthConfiguration{}) // TODO AuthConfig from code
		if err != nil {
			return "", err
		}

		// should have it now
		c, err = drv.docker.CreateContainer(container)
		if err != nil {
			return "", err
		}
	}
	return c.ID, nil
}

func (drv *dockerDriver) removeContainer(container string) {
	// TODO: trap error
	drv.docker.RemoveContainer(docker.RemoveContainerOptions{
		ID: container, Force: true, RemoveVolumes: true})
}

// watch for cancel or timeout and kill process.
func (drv *dockerDriver) nanny(container string, task drivers.ContainerTask, sentence chan<- string, done chan struct{}, isCancelledSignal chan bool) {
	t := time.Duration(drv.conf.DefaultTimeout) * time.Second
	if task.Timeout() != 0 {
		t = time.Duration(task.Timeout()) * time.Second
	}
	// Just in case we make a calculation mistake.
	// TODO: trap this condition
	// if t > 24*time.Hour {
	// 	ctx.Warn("task has really long timeout of greater than a day")
	// }
	// Log task timeout values so we can get a good idea of what people generally set it to.
	timeout := time.After(t)

	select {
	case <-done:
		return
	case <-timeout:
		sentence <- drivers.StatusTimeout
		drv.cancel(container)
	case isCancelled := <-isCancelledSignal:
		if isCancelled {
			sentence <- drivers.StatusKilled
			drv.cancel(container)
		}
	}

	<-done
}

func (drv *dockerDriver) status(exitCode int, sentence <-chan string) (string, error) {
	var status string
	var err error
	select {
	case status = <-sentence: // use this if killed / timed out
	default:
		switch exitCode {
		case 0:
			status = drivers.StatusComplete
		case 137:
			// Probably an OOM kill

			// TODO: try harder to detect OOM kills. We can call
			// docker.InspectContainer and look at
			// container.State.OOMKilled, but this field isn't set
			// consistently.
			// See: https://github.com/docker/docker/issues/15621

			status = drivers.StatusKilled
			// TODO: better message; show memory limit
			err = drivers.ErrOutOfMemory
		default:
			status = drivers.StatusError
			err = drivers.ErrUnknown
		}
	}
	return status, err
}

// TODO we _sure_ it's dead?
func (drv *dockerDriver) cancel(container string) { drv.docker.StopContainer(container, 5) }
<|endoftext|>


<commit_before>// +build integration

// Copyright 2016 Google, Inc.
//
// 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 auth

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	"testing"

	"github.com/GoogleCloudPlatform/docker-credential-gcr/config"
	"golang.org/x/oauth2"
)

const (
	// The client ID corresponding to GCR's OAuth2 login page.
	expectedClientID  = "99426463878-o7n0bshgue20tdpm25q4at0vs2mr4utq.apps.googleusercontent.com"
	expectedScope     = "https://www.googleapis.com/auth/cloud-platform"
	expectedHost      = "localhost"
	expectedAuthPath  = "/auth"
	expectedTokenPath = "/token"
	expectedGrantType = "authorization_code"

	expectedCode         = "sUp3r@w3$om3c0d3"
	expectedAccessToken  = "@cce$$4dayz"
	expectedRefreshToken = "refreshplz"
	expectedTTL          = 3600
)

func initAuthServer() (net.Listener, error) {
	testLn, testPort, err := getListener()
	if err != nil {
		return nil, err
	}
	config.GCROAuth2Endpoint = oauth2.Endpoint{
		AuthURL:  fmt.Sprintf("http://%s:%d%s", expectedHost, testPort, expectedAuthPath),
		TokenURL: fmt.Sprintf("http://%s:%d%s", expectedHost, testPort, expectedTokenPath),
	}
	return testLn, nil
}

type testBrowser struct {
	shouldSucceed bool
	t             *testing.T
	RedirectURL   chan *url.URL
	State         chan string
}

// the testBrowser's Open method exists to verify the URL which is passed
// when attempting to open the default web browser during the login operation
func (b *testBrowser) Open(urlStr string) error {
	if !b.shouldSucceed {
		return errors.New("you asked for it")
	}

	URL, err := url.Parse(urlStr)

	if err != nil {
		b.t.Errorf("Could not parse URL: %s", urlStr)
		return nil
	}

	if URL.Path != expectedAuthPath {
		b.t.Errorf("Expected Path to be: %s, got: %s", expectedAuthPath, URL.Path)
	}
	if !strings.HasPrefix(URL.Host, expectedHost) {
		b.t.Errorf("Expected Host to begin with: %s, got: %s", expectedHost, URL.Host)
	}

	responseType := URL.Query().Get("response_type")
	if responseType != "code" {
		b.t.Errorf("Expected response_type: %s, got: %s", "code", responseType)
	}

	clientID := URL.Query().Get("client_id")
	if clientID != expectedClientID {
		b.t.Errorf("Expected client_id: %s, got: %s", expectedClientID, clientID)
	}

	redirectURI := URL.Query().Get("redirect_uri")
	redirURL, err := url.Parse(redirectURI)
	if err != nil {
		b.t.Errorf("Unable to parse redirect_uri: %s", redirectURI)
	} else {
		// pass the redirect URL to the browser
		b.RedirectURL <- redirURL

		if !strings.HasPrefix(redirURL.Host, expectedHost) {
			b.t.Errorf("RedirectURL should begin with %s: %s", expectedHost, redirURL.Host)
		}
	}

	scope := URL.Query().Get("scope")
	if scope != expectedScope {
		b.t.Errorf("Expected scope: %s, got: %s", expectedScope, scope)
	}

	// pass the 'state' variable to the browser thread
	b.State <- URL.Query().Get("state")

	return nil
}

func performBrowserActions(t *testing.T, browser *testBrowser) {
	// simulate the authorization code redirect after the user
	// performs the login flow
	redirURL := <-browser.RedirectURL
	state := <-browser.State
	args := redirURL.Query()
	args.Set("code", expectedCode)
	args.Set("state", state)
	redirURL.RawQuery = args.Encode()
	resp, err := http.Get(redirURL.String())
	defer resp.Body.Close()
	if err != nil {
		t.Fatalf("Could not send authorization code response: %v", err)
	}
	// the browser should recieve a response AFTER the entire auth flow has completed
	if resp.StatusCode >= 400 {
		t.Errorf("Unsuccessful response: %+v", *resp)
	}
}

func performAuthServerActions(t *testing.T, testLn net.Listener) {
	// perform the auth server-side actions...
	// receive the authorization_code exchange request
	conn, err := testLn.Accept()
	if err != nil {
		t.Errorf("Could not accept tcp connection: %v", err)
	}

	srvConn := httputil.NewServerConn(conn, nil)
	defer srvConn.Close()

	req, err := srvConn.Read()
	if err != nil {
		t.Fatalf("Could not read from connection: %v", err)
	}

	if req.URL.Path != expectedTokenPath {
		t.Errorf("Expected path: %s, got %s", expectedTokenPath, req.URL.Path)
	}

	grantType := req.PostFormValue("grant_type")
	if grantType != expectedGrantType {
		t.Errorf("Expected grant_type: %s, got: %s", expectedGrantType, grantType)
	}

	clientID := req.PostFormValue("client_id")
	if clientID == "" {
		// Newer google oauth libraries deliver client_id in the Authorization header.
		if username, _, headerExists := req.BasicAuth(); !headerExists || username != expectedClientID {
			t.Errorf("Expected username: %s, got: %s", expectedClientID, username)
		}
	} else if clientID != expectedClientID {
		// Older libraries use a client_id form value.
		t.Errorf("Expected client_id: %s, got: %s", expectedClientID, clientID)
	}
	redirectURI := req.PostFormValue("redirect_uri")
	if redirectURI == "" {
		t.Errorf("Expected redirect_uri to be present: %+v", *req)
	}
	code := req.PostFormValue("code")
	if code != expectedCode {
		t.Errorf("Expected authorization_code: %s, got: %s", expectedCode, code)
	}

	if t.Failed() {
		t.Errorf("Request: %+v", *req)
	}

	// Respond with the access_token, refresh_token
	var resp http.Response
	bodyString := fmt.Sprintf(`{
		"access_token":"%s",
		"expires_in":%d,
		"token_type":"%s",
		"refresh_token":"%s"
	}`, expectedAccessToken, expectedTTL, "Bearer", expectedRefreshToken)
	resp.Body = getReadCloserFromString(bodyString)
	resp.StatusCode = 200
	resp.Proto = "HTTP/1.1"
	resp.Close = true
	resp.ContentLength = -1   // unknown length
	srvConn.Write(req, &resp) // ignore errors; expected
}

// turn a string into a ReadCloser as required by http Responses
func getReadCloserFromString(body string) io.ReadCloser {
	reader := strings.NewReader(body)
	return ioutil.NopCloser(reader)
}

// TestBrowserFlow tests much of the flow required to authenticate a GCR user
// via an automatically-launched user agent. The browser is mocked for testing
// feasibility, and the OAuth2 endpoints are reditected to the localhost.
func TestBrowserAllowed(t *testing.T) {
	testLn, err := initAuthServer()
	if err != nil {
		t.Fatalf("Unable to initialize auth server: %v", err)
	}
	defer testLn.Close()
	mockBrowser := &testBrowser{
		shouldSucceed: true,
		t:             t,
		RedirectURL:   make(chan *url.URL),
		State:         make(chan string),
	}
	defer close(mockBrowser.RedirectURL)
	defer close(mockBrowser.State)

	// start a goroutine to act as the browser
	go performBrowserActions(t, mockBrowser)

	// start a goroutine to act as the auth server
	go performAuthServerActions(t, testLn)

	// test the client-side code
	tested := &GCRLoginAgent{
		AllowBrowser: true,
		OpenBrowser:  mockBrowser.Open,
	}
	tok, err := tested.PerformLogin()
	if err != nil {
		t.Fatalf("Login failed: %v", err)
	}
	if tok.AccessToken != expectedAccessToken {
		t.Errorf("Expected access_token: %s, got: %s", expectedAccessToken, tok.AccessToken)
	}
	if tok.RefreshToken != expectedRefreshToken {
		t.Errorf("Expected refresh_token: %s, got: %s", expectedRefreshToken, tok.RefreshToken)
	}
	if tok.TokenType != "Bearer" {
		t.Errorf("Expected token_type: %s, got: %s", "Bearer", tok.TokenType)
	}
}

func verifyCommandLineURL(t *testing.T, urlStr string) {
	URL, err := url.Parse(urlStr)

	if err != nil {
		t.Errorf("Could not parse URL: %s", urlStr)
	} else {
		if URL.Path != expectedAuthPath {
			t.Errorf("Expected Path to be: %s, got: %s", expectedAuthPath, URL.Path)
		}
		if !strings.HasPrefix(URL.Host, expectedHost) {
			t.Errorf("Expected Host to begin with: %s, got: %s", expectedHost, URL.Host)
		}

		responseType := URL.Query().Get("response_type")
		if responseType != "code" {
			t.Errorf("Expected response_type: %s, got: %s", "code", responseType)
		}

		clientID := URL.Query().Get("client_id")
		if clientID != expectedClientID {
			t.Errorf("Expected client_id: %s, got: %s", expectedClientID, clientID)
		}

		redirectURI := URL.Query().Get("redirect_uri")
		if redirectURI != "urn:ietf:wg:oauth:2.0:oob" {
			t.Errorf("Expected redirect_uri: %s, got %s", "urn:ietf:wg:oauth:2.0:oob", redirectURI)
		}

		scope := URL.Query().Get("scope")
		if scope != expectedScope {
			t.Errorf("Expected scope: %s, got: %s", expectedScope, scope)
		}
	}
}

func performCommandLineInteraction(t *testing.T, outRead io.Reader) {
	var line string
	for {
		fmt.Fscanln(outRead, &line)
		line = strings.TrimSpace(line)
		if line == "Please visit the following URL and complete the authorization dialog:" {
			// expected
		} else if strings.HasPrefix(line, "http") {
			verifyCommandLineURL(t, line)
		} else if line == "Authorization code:" {
			break
		} else {
			t.Errorf("Unexpected line: %s", line)
			break
		}
	}
}

// multiThreadReadWriter is a io.ReadWriter that allows for simpler
// communication than is afforded by io.Pipe
type multiThreadReadWriter struct {
	c chan []byte
}

func (m multiThreadReadWriter) Read(p []byte) (int, error) {
	bytes := <-m.c
	return copy(p, bytes), nil
}
func (m multiThreadReadWriter) Write(p []byte) (int, error) {
	m.c <- p
	return len(p), nil
}
func newMultiThreadReadWriter() io.ReadWriter {
	return multiThreadReadWriter{
		c: make(chan []byte),
	}
}

func TestBrowserForbidden(t *testing.T) {
	mockStdout := newMultiThreadReadWriter()
	mockStdin := strings.NewReader(expectedCode)

	testLn, err := initAuthServer()
	if err != nil {
		t.Fatalf("Unable to initialize auth server: %v", err)
	}
	defer testLn.Close()

	// start a goroutine to act as the auth server and verify interactions
	// with the client
	go performAuthServerActions(t, testLn)

	// start a goroutine to act as a command-line user and verify interactions
	// with the client
	go performCommandLineInteraction(t, mockStdout)

	tested := &GCRLoginAgent{
		AllowBrowser: false,
		In:           mockStdin,
		Out:          mockStdout,
	}
	tok, err := tested.PerformLogin()

	// verify the token
	if err != nil {
		t.Fatalf("Login failed: %v", err)
	}
	if tok.AccessToken != expectedAccessToken {
		t.Errorf("Expected access_token: %s, got: %s", expectedAccessToken, tok.AccessToken)
	}
	if tok.RefreshToken != expectedRefreshToken {
		t.Errorf("Expected refresh_token: %s, got: %s", expectedRefreshToken, tok.RefreshToken)
	}
	if tok.TokenType != "Bearer" {
		t.Errorf("Expected token_type: %s, got: %s", "Bearer", tok.TokenType)
	}
}

func TestBrowserAllowed_BrowserOpenFails(t *testing.T) {
	mockStdout := newMultiThreadReadWriter()
	mockStdin := strings.NewReader(expectedCode)

	testLn, err := initAuthServer()
	if err != nil {
		t.Fatalf("Unable to initialize auth server: %v", err)
	}
	defer testLn.Close()
	mockBrowser := &testBrowser{
		shouldSucceed: false,
		t:             t,
		RedirectURL:   nil,
		State:         nil,
	}

	// start a goroutine to act as the auth server and verify interactions
	// with the client
	go performAuthServerActions(t, testLn)

	// start a goroutine to act as a command-line user and verify interactions
	// with the client
	go performCommandLineInteraction(t, mockStdout)

	tested := &GCRLoginAgent{
		AllowBrowser: true,
		In:           mockStdin,
		Out:          mockStdout,
		OpenBrowser:  mockBrowser.Open,
	}
	tok, err := tested.PerformLogin()
	if err != nil {
		t.Fatalf("Login failed: %v", err)
	}
	if tok.AccessToken != expectedAccessToken {
		t.Errorf("Expected access_token: %s, got: %s", expectedAccessToken, tok.AccessToken)
	}
	if tok.RefreshToken != expectedRefreshToken {
		t.Errorf("Expected refresh_token: %s, got: %s", expectedRefreshToken, tok.RefreshToken)
	}
	if tok.TokenType != "Bearer" {
		t.Errorf("Expected token_type: %s, got: %s", "Bearer", tok.TokenType)
	}
}
<commit_msg>Update login_integration_test.go<commit_after>// +build integration

// Copyright 2016 Google, Inc.
//
// 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 auth

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	"testing"

	"github.com/GoogleCloudPlatform/docker-credential-gcr/config"
	"golang.org/x/oauth2"
)

const (
	// The client ID corresponding to GCR's OAuth2 login page.
	expectedClientID  = "99426463878-o7n0bshgue20tdpm25q4at0vs2mr4utq.apps.googleusercontent.com"
	expectedScope     = "https://www.googleapis.com/auth/cloud-platform"
	expectedHost      = "localhost"
	expectedAuthPath  = "/auth"
	expectedTokenPath = "/token"
	expectedGrantType = "authorization_code"

	expectedCode         = "sUp3r@w3$om3c0d3"
	expectedAccessToken  = "@cce$$4dayz"
	expectedRefreshToken = "refreshplz"
	expectedTTL          = 3600
)

func initAuthServer() (net.Listener, error) {
	testLn, testPort, err := getListener()
	if err != nil {
		return nil, err
	}
	config.GCROAuth2Endpoint = oauth2.Endpoint{
		AuthURL:  fmt.Sprintf("http://%s:%d%s", expectedHost, testPort, expectedAuthPath),
		TokenURL: fmt.Sprintf("http://%s:%d%s", expectedHost, testPort, expectedTokenPath),
	}
	return testLn, nil
}

type testBrowser struct {
	shouldSucceed bool
	t             *testing.T
	RedirectURL   chan *url.URL
	State         chan string
}

// the testBrowser's Open method exists to verify the URL which is passed
// when attempting to open the default web browser during the login operation
func (b *testBrowser) Open(urlStr string) error {
	if !b.shouldSucceed {
		return errors.New("you asked for it")
	}

	URL, err := url.Parse(urlStr)

	if err != nil {
		b.t.Errorf("Could not parse URL: %s", urlStr)
		return nil
	}

	if URL.Path != expectedAuthPath {
		b.t.Errorf("Expected Path to be: %s, got: %s", expectedAuthPath, URL.Path)
	}
	if !strings.HasPrefix(URL.Host, expectedHost) {
		b.t.Errorf("Expected Host to begin with: %s, got: %s", expectedHost, URL.Host)
	}

	responseType := URL.Query().Get("response_type")
	if responseType != "code" {
		b.t.Errorf("Expected response_type: %s, got: %s", "code", responseType)
	}

	clientID := URL.Query().Get("client_id")
	if clientID != expectedClientID {
		b.t.Errorf("Expected client_id: %s, got: %s", expectedClientID, clientID)
	}

	redirectURI := URL.Query().Get("redirect_uri")
	redirURL, err := url.Parse(redirectURI)
	if err != nil {
		b.t.Errorf("Unable to parse redirect_uri: %s", redirectURI)
	} else {
		// pass the redirect URL to the browser
		b.RedirectURL <- redirURL

		if !strings.HasPrefix(redirURL.Host, expectedHost) {
			b.t.Errorf("RedirectURL should begin with %s: %s", expectedHost, redirURL.Host)
		}
	}

	scope := URL.Query().Get("scope")
	if scope != expectedScope {
		b.t.Errorf("Expected scope: %s, got: %s", expectedScope, scope)
	}

	// pass the 'state' variable to the browser thread
	b.State <- URL.Query().Get("state")

	return nil
}

func performBrowserActions(t *testing.T, browser *testBrowser) {
	// simulate the authorization code redirect after the user
	// performs the login flow
	redirURL := <-browser.RedirectURL
	state := <-browser.State
	args := redirURL.Query()
	args.Set("code", expectedCode)
	args.Set("state", state)
	redirURL.RawQuery = args.Encode()
	resp, err := http.Get(redirURL.String())
	if err != nil {
		t.Fatalf("Could not send authorization code response: %v", err)
	}
	defer resp.Body.Close()
	// the browser should receive a response AFTER the entire auth flow has completed
	if resp.StatusCode >= 400 {
		t.Errorf("Unsuccessful response: %+v", *resp)
	}
}

func performAuthServerActions(t *testing.T, testLn net.Listener) {
	// perform the auth server-side actions...
	// receive the authorization_code exchange request
	conn, err := testLn.Accept()
	if err != nil {
		t.Errorf("Could not accept tcp connection: %v", err)
	}

	srvConn := httputil.NewServerConn(conn, nil)
	defer srvConn.Close()

	req, err := srvConn.Read()
	if err != nil {
		t.Fatalf("Could not read from connection: %v", err)
	}

	if req.URL.Path != expectedTokenPath {
		t.Errorf("Expected path: %s, got %s", expectedTokenPath, req.URL.Path)
	}

	grantType := req.PostFormValue("grant_type")
	if grantType != expectedGrantType {
		t.Errorf("Expected grant_type: %s, got: %s", expectedGrantType, grantType)
	}

	clientID := req.PostFormValue("client_id")
	if clientID == "" {
		// Newer google oauth libraries deliver client_id in the Authorization header.
		if username, _, headerExists := req.BasicAuth(); !headerExists || username != expectedClientID {
			t.Errorf("Expected username: %s, got: %s", expectedClientID, username)
		}
	} else if clientID != expectedClientID {
		// Older libraries use a client_id form value.
		t.Errorf("Expected client_id: %s, got: %s", expectedClientID, clientID)
	}
	redirectURI := req.PostFormValue("redirect_uri")
	if redirectURI == "" {
		t.Errorf("Expected redirect_uri to be present: %+v", *req)
	}
	code := req.PostFormValue("code")
	if code != expectedCode {
		t.Errorf("Expected authorization_code: %s, got: %s", expectedCode, code)
	}

	if t.Failed() {
		t.Errorf("Request: %+v", *req)
	}

	// Respond with the access_token, refresh_token
	var resp http.Response
	bodyString := fmt.Sprintf(`{
		"access_token":"%s",
		"expires_in":%d,
		"token_type":"%s",
		"refresh_token":"%s"
	}`, expectedAccessToken, expectedTTL, "Bearer", expectedRefreshToken)
	resp.Body = getReadCloserFromString(bodyString)
	resp.StatusCode = 200
	resp.Proto = "HTTP/1.1"
	resp.Close = true
	resp.ContentLength = -1   // unknown length
	srvConn.Write(req, &resp) // ignore errors; expected
}

// turn a string into a ReadCloser as required by http Responses
func getReadCloserFromString(body string) io.ReadCloser {
	reader := strings.NewReader(body)
	return ioutil.NopCloser(reader)
}

// TestBrowserFlow tests much of the flow required to authenticate a GCR user
// via an automatically-launched user agent. The browser is mocked for testing
// feasibility, and the OAuth2 endpoints are reditected to the localhost.
func TestBrowserAllowed(t *testing.T) {
	testLn, err := initAuthServer()
	if err != nil {
		t.Fatalf("Unable to initialize auth server: %v", err)
	}
	defer testLn.Close()
	mockBrowser := &testBrowser{
		shouldSucceed: true,
		t:             t,
		RedirectURL:   make(chan *url.URL),
		State:         make(chan string),
	}
	defer close(mockBrowser.RedirectURL)
	defer close(mockBrowser.State)

	// start a goroutine to act as the browser
	go performBrowserActions(t, mockBrowser)

	// start a goroutine to act as the auth server
	go performAuthServerActions(t, testLn)

	// test the client-side code
	tested := &GCRLoginAgent{
		AllowBrowser: true,
		OpenBrowser:  mockBrowser.Open,
	}
	tok, err := tested.PerformLogin()
	if err != nil {
		t.Fatalf("Login failed: %v", err)
	}
	if tok.AccessToken != expectedAccessToken {
		t.Errorf("Expected access_token: %s, got: %s", expectedAccessToken, tok.AccessToken)
	}
	if tok.RefreshToken != expectedRefreshToken {
		t.Errorf("Expected refresh_token: %s, got: %s", expectedRefreshToken, tok.RefreshToken)
	}
	if tok.TokenType != "Bearer" {
		t.Errorf("Expected token_type: %s, got: %s", "Bearer", tok.TokenType)
	}
}

func verifyCommandLineURL(t *testing.T, urlStr string) {
	URL, err := url.Parse(urlStr)

	if err != nil {
		t.Errorf("Could not parse URL: %s", urlStr)
	} else {
		if URL.Path != expectedAuthPath {
			t.Errorf("Expected Path to be: %s, got: %s", expectedAuthPath, URL.Path)
		}
		if !strings.HasPrefix(URL.Host, expectedHost) {
			t.Errorf("Expected Host to begin with: %s, got: %s", expectedHost, URL.Host)
		}

		responseType := URL.Query().Get("response_type")
		if responseType != "code" {
			t.Errorf("Expected response_type: %s, got: %s", "code", responseType)
		}

		clientID := URL.Query().Get("client_id")
		if clientID != expectedClientID {
			t.Errorf("Expected client_id: %s, got: %s", expectedClientID, clientID)
		}

		redirectURI := URL.Query().Get("redirect_uri")
		if redirectURI != "urn:ietf:wg:oauth:2.0:oob" {
			t.Errorf("Expected redirect_uri: %s, got %s", "urn:ietf:wg:oauth:2.0:oob", redirectURI)
		}

		scope := URL.Query().Get("scope")
		if scope != expectedScope {
			t.Errorf("Expected scope: %s, got: %s", expectedScope, scope)
		}
	}
}

func performCommandLineInteraction(t *testing.T, outRead io.Reader) {
	var line string
	for {
		fmt.Fscanln(outRead, &line)
		line = strings.TrimSpace(line)
		if line == "Please visit the following URL and complete the authorization dialog:" {
			// expected
		} else if strings.HasPrefix(line, "http") {
			verifyCommandLineURL(t, line)
		} else if line == "Authorization code:" {
			break
		} else {
			t.Errorf("Unexpected line: %s", line)
			break
		}
	}
}

// multiThreadReadWriter is a io.ReadWriter that allows for simpler
// communication than is afforded by io.Pipe
type multiThreadReadWriter struct {
	c chan []byte
}

func (m multiThreadReadWriter) Read(p []byte) (int, error) {
	bytes := <-m.c
	return copy(p, bytes), nil
}
func (m multiThreadReadWriter) Write(p []byte) (int, error) {
	m.c <- p
	return len(p), nil
}
func newMultiThreadReadWriter() io.ReadWriter {
	return multiThreadReadWriter{
		c: make(chan []byte),
	}
}

func TestBrowserForbidden(t *testing.T) {
	mockStdout := newMultiThreadReadWriter()
	mockStdin := strings.NewReader(expectedCode)

	testLn, err := initAuthServer()
	if err != nil {
		t.Fatalf("Unable to initialize auth server: %v", err)
	}
	defer testLn.Close()

	// start a goroutine to act as the auth server and verify interactions
	// with the client
	go performAuthServerActions(t, testLn)

	// start a goroutine to act as a command-line user and verify interactions
	// with the client
	go performCommandLineInteraction(t, mockStdout)

	tested := &GCRLoginAgent{
		AllowBrowser: false,
		In:           mockStdin,
		Out:          mockStdout,
	}
	tok, err := tested.PerformLogin()

	// verify the token
	if err != nil {
		t.Fatalf("Login failed: %v", err)
	}
	if tok.AccessToken != expectedAccessToken {
		t.Errorf("Expected access_token: %s, got: %s", expectedAccessToken, tok.AccessToken)
	}
	if tok.RefreshToken != expectedRefreshToken {
		t.Errorf("Expected refresh_token: %s, got: %s", expectedRefreshToken, tok.RefreshToken)
	}
	if tok.TokenType != "Bearer" {
		t.Errorf("Expected token_type: %s, got: %s", "Bearer", tok.TokenType)
	}
}

func TestBrowserAllowed_BrowserOpenFails(t *testing.T) {
	mockStdout := newMultiThreadReadWriter()
	mockStdin := strings.NewReader(expectedCode)

	testLn, err := initAuthServer()
	if err != nil {
		t.Fatalf("Unable to initialize auth server: %v", err)
	}
	defer testLn.Close()
	mockBrowser := &testBrowser{
		shouldSucceed: false,
		t:             t,
		RedirectURL:   nil,
		State:         nil,
	}

	// start a goroutine to act as the auth server and verify interactions
	// with the client
	go performAuthServerActions(t, testLn)

	// start a goroutine to act as a command-line user and verify interactions
	// with the client
	go performCommandLineInteraction(t, mockStdout)

	tested := &GCRLoginAgent{
		AllowBrowser: true,
		In:           mockStdin,
		Out:          mockStdout,
		OpenBrowser:  mockBrowser.Open,
	}
	tok, err := tested.PerformLogin()
	if err != nil {
		t.Fatalf("Login failed: %v", err)
	}
	if tok.AccessToken != expectedAccessToken {
		t.Errorf("Expected access_token: %s, got: %s", expectedAccessToken, tok.AccessToken)
	}
	if tok.RefreshToken != expectedRefreshToken {
		t.Errorf("Expected refresh_token: %s, got: %s", expectedRefreshToken, tok.RefreshToken)
	}
	if tok.TokenType != "Bearer" {
		t.Errorf("Expected token_type: %s, got: %s", "Bearer", tok.TokenType)
	}
}
<|endoftext|>


<commit_before>// Package tsz implement time-series compression
/*

http://www.vldb.org/pvldb/vol8/p1816-teller.pdf

*/
package tsz

import (
	"bytes"
	"encoding/binary"
	"io"
	"math"
	"math/bits"
	"sync"
)

// Series is the basic series primitive
// you can concurrently put values, finish the stream, and create iterators
type Series struct {
	sync.Mutex

	// TODO(dgryski): timestamps in the paper are uint64
	T0  uint32
	t   uint32
	val float64

	bw       bstream
	leading  uint8
	trailing uint8
	finished bool

	tDelta uint32
}

// New series
func New(t0 uint32) *Series {
	s := Series{
		T0:      t0,
		leading: ^uint8(0),
	}

	// block header
	s.bw.writeBits(uint64(t0), 32)

	return &s

}

// Bytes value of the series stream
func (s *Series) Bytes() []byte {
	s.Lock()
	defer s.Unlock()
	return s.bw.bytes()
}

func finish(w *bstream) {
	// write an end-of-stream record
	w.writeBits(0x0f, 4)
	w.writeBits(0xffffffff, 32)
	w.writeBit(zero)
}

// Finish the series by writing an end-of-stream record
func (s *Series) Finish() {
	s.Lock()
	if !s.finished {
		finish(&s.bw)
		s.finished = true
	}
	s.Unlock()
}

// Push a timestamp and value to the series
func (s *Series) Push(t uint32, v float64) {
	s.Lock()
	defer s.Unlock()

	if s.t == 0 {
		// first point
		s.t = t
		s.val = v
		s.tDelta = t - s.T0
		s.bw.writeBits(uint64(s.tDelta), 14)
		s.bw.writeBits(math.Float64bits(v), 64)
		return
	}

	tDelta := t - s.t
	dod := int32(tDelta - s.tDelta)

	switch {
	case dod == 0:
		s.bw.writeBit(zero)
	case -63 <= dod && dod <= 64:
		s.bw.writeBits(0x02, 2) // '10'
		s.bw.writeBits(uint64(dod), 7)
	case -255 <= dod && dod <= 256:
		s.bw.writeBits(0x06, 3) // '110'
		s.bw.writeBits(uint64(dod), 9)
	case -2047 <= dod && dod <= 2048:
		s.bw.writeBits(0x0e, 4) // '1110'
		s.bw.writeBits(uint64(dod), 12)
	default:
		s.bw.writeBits(0x0f, 4) // '1111'
		s.bw.writeBits(uint64(dod), 32)
	}

	vDelta := math.Float64bits(v) ^ math.Float64bits(s.val)

	if vDelta == 0 {
		s.bw.writeBit(zero)
	} else {
		s.bw.writeBit(one)

		leading := uint8(bits.LeadingZeros64(vDelta))
		trailing := uint8(bits.TrailingZeros64(vDelta))

		// clamp number of leading zeros to avoid overflow when encoding
		if leading >= 32 {
			leading = 31
		}

		// TODO(dgryski): check if it's 'cheaper' to reset the leading/trailing bits instead
		if s.leading != ^uint8(0) && leading >= s.leading && trailing >= s.trailing {
			s.bw.writeBit(zero)
			s.bw.writeBits(vDelta>>s.trailing, 64-int(s.leading)-int(s.trailing))
		} else {
			s.leading, s.trailing = leading, trailing

			s.bw.writeBit(one)
			s.bw.writeBits(uint64(leading), 5)

			// Note that if leading == trailing == 0, then sigbits == 64.  But that value doesn't actually fit into the 6 bits we have.
			// Luckily, we never need to encode 0 significant bits, since that would put us in the other case (vdelta == 0).
			// So instead we write out a 0 and adjust it back to 64 on unpacking.
			sigbits := 64 - leading - trailing
			s.bw.writeBits(uint64(sigbits), 6)
			s.bw.writeBits(vDelta>>trailing, int(sigbits))
		}
	}

	s.tDelta = tDelta
	s.t = t
	s.val = v

}

// Iter lets you iterate over a series.  It is not concurrency-safe.
func (s *Series) Iter() *Iter {
	s.Lock()
	w := s.bw.clone()
	s.Unlock()

	finish(w)
	iter, _ := bstreamIterator(w)
	return iter
}

// Iter lets you iterate over a series.  It is not concurrency-safe.
type Iter struct {
	T0 uint32

	t   uint32
	val float64

	br       bstream
	leading  uint8
	trailing uint8

	finished bool

	tDelta uint32
	err    error
}

func bstreamIterator(br *bstream) (*Iter, error) {

	br.count = 8

	t0, err := br.readBits(32)
	if err != nil {
		return nil, err
	}

	return &Iter{
		T0: uint32(t0),
		br: *br,
	}, nil
}

// NewIterator for the series
func NewIterator(b []byte) (*Iter, error) {
	return bstreamIterator(newBReader(b))
}

// Next iteration of the series iterator
func (it *Iter) Next() bool {

	if it.err != nil || it.finished {
		return false
	}

	if it.t == 0 {
		// read first t and v
		tDelta, err := it.br.readBits(14)
		if err != nil {
			it.err = err
			return false
		}
		it.tDelta = uint32(tDelta)
		it.t = it.T0 + it.tDelta
		v, err := it.br.readBits(64)
		if err != nil {
			it.err = err
			return false
		}

		it.val = math.Float64frombits(v)

		return true
	}

	// read delta-of-delta
	var d byte
	for i := 0; i < 4; i++ {
		d <<= 1
		bit, err := it.br.readBit()
		if err != nil {
			it.err = err
			return false
		}
		if bit == zero {
			break
		}
		d |= 1
	}

	var dod int32
	var sz uint
	switch d {
	case 0x00:
		// dod == 0
	case 0x02:
		sz = 7
	case 0x06:
		sz = 9
	case 0x0e:
		sz = 12
	case 0x0f:
		bits, err := it.br.readBits(32)
		if err != nil {
			it.err = err
			return false
		}

		// end of stream
		if bits == 0xffffffff {
			it.finished = true
			return false
		}

		dod = int32(bits)
	}

	if sz != 0 {
		bits, err := it.br.readBits(int(sz))
		if err != nil {
			it.err = err
			return false
		}
		if bits > (1 << (sz - 1)) {
			// or something
			bits = bits - (1 << sz)
		}
		dod = int32(bits)
	}

	tDelta := it.tDelta + uint32(dod)

	it.tDelta = tDelta
	it.t = it.t + it.tDelta

	// read compressed value
	bit, err := it.br.readBit()
	if err != nil {
		it.err = err
		return false
	}

	if bit == zero {
		// it.val = it.val
	} else {
		bit, itErr := it.br.readBit()
		if itErr != nil {
			it.err = err
			return false
		}
		if bit == zero {
			// reuse leading/trailing zero bits
			// it.leading, it.trailing = it.leading, it.trailing
		} else {
			bits, err := it.br.readBits(5)
			if err != nil {
				it.err = err
				return false
			}
			it.leading = uint8(bits)

			bits, err = it.br.readBits(6)
			if err != nil {
				it.err = err
				return false
			}
			mbits := uint8(bits)
			// 0 significant bits here means we overflowed and we actually need 64; see comment in encoder
			if mbits == 0 {
				mbits = 64
			}
			it.trailing = 64 - it.leading - mbits
		}

		mbits := int(64 - it.leading - it.trailing)
		bits, err := it.br.readBits(mbits)
		if err != nil {
			it.err = err
			return false
		}
		vbits := math.Float64bits(it.val)
		vbits ^= (bits << it.trailing)
		it.val = math.Float64frombits(vbits)
	}

	return true
}

// Values at the current iterator position
func (it *Iter) Values() (uint32, float64) {
	return it.t, it.val
}

// Err error at the current iterator position
func (it *Iter) Err() error {
	return it.err
}

type errMarshal struct {
	w   io.Writer
	r   io.Reader
	err error
}

func (em *errMarshal) write(t interface{}) {
	if em.err != nil {
		return
	}
	em.err = binary.Write(em.w, binary.BigEndian, t)
}

func (em *errMarshal) read(t interface{}) {
	if em.err != nil {
		return
	}
	em.err = binary.Read(em.r, binary.BigEndian, t)
}

// MarshalBinary implements the encoding.BinaryMarshaler interface
func (s *Series) MarshalBinary() ([]byte, error) {
	buf := new(bytes.Buffer)
	em := &errMarshal{w: buf}
	em.write(s.T0)
	em.write(s.leading)
	em.write(s.t)
	em.write(s.tDelta)
	em.write(s.trailing)
	em.write(s.val)
	bStream, err := s.bw.MarshalBinary()
	if err != nil {
		return nil, err
	}
	em.write(bStream)
	if em.err != nil {
		return nil, em.err
	}
	return buf.Bytes(), nil
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
func (s *Series) UnmarshalBinary(b []byte) error {
	buf := bytes.NewReader(b)
	em := &errMarshal{r: buf}
	em.read(&s.T0)
	em.read(&s.leading)
	em.read(&s.t)
	em.read(&s.tDelta)
	em.read(&s.trailing)
	em.read(&s.val)
	outBuf := make([]byte, buf.Len())
	em.read(outBuf)
	err := s.bw.UnmarshalBinary(outBuf)
	if err != nil {
		return err
	}
	if em.err != nil {
		return em.err
	}
	return nil
}
<commit_msg>single out reading the delta of delta<commit_after>// Package tsz implement time-series compression
/*

http://www.vldb.org/pvldb/vol8/p1816-teller.pdf

*/
package tsz

import (
	"bytes"
	"encoding/binary"
	"io"
	"math"
	"math/bits"
	"sync"
)

// Series is the basic series primitive
// you can concurrently put values, finish the stream, and create iterators
type Series struct {
	sync.Mutex

	// TODO(dgryski): timestamps in the paper are uint64
	T0  uint32
	t   uint32
	val float64

	bw       bstream
	leading  uint8
	trailing uint8
	finished bool

	tDelta uint32
}

// New series
func New(t0 uint32) *Series {
	s := Series{
		T0:      t0,
		leading: ^uint8(0),
	}

	// block header
	s.bw.writeBits(uint64(t0), 32)

	return &s

}

// Bytes value of the series stream
func (s *Series) Bytes() []byte {
	s.Lock()
	defer s.Unlock()
	return s.bw.bytes()
}

func finish(w *bstream) {
	// write an end-of-stream record
	w.writeBits(0x0f, 4)
	w.writeBits(0xffffffff, 32)
	w.writeBit(zero)
}

// Finish the series by writing an end-of-stream record
func (s *Series) Finish() {
	s.Lock()
	if !s.finished {
		finish(&s.bw)
		s.finished = true
	}
	s.Unlock()
}

// Push a timestamp and value to the series
func (s *Series) Push(t uint32, v float64) {
	s.Lock()
	defer s.Unlock()

	if s.t == 0 {
		// first point
		s.t = t
		s.val = v
		s.tDelta = t - s.T0
		s.bw.writeBits(uint64(s.tDelta), 14)
		s.bw.writeBits(math.Float64bits(v), 64)
		return
	}

	tDelta := t - s.t
	dod := int32(tDelta - s.tDelta)

	switch {
	case dod == 0:
		s.bw.writeBit(zero)
	case -63 <= dod && dod <= 64:
		s.bw.writeBits(0x02, 2) // '10'
		s.bw.writeBits(uint64(dod), 7)
	case -255 <= dod && dod <= 256:
		s.bw.writeBits(0x06, 3) // '110'
		s.bw.writeBits(uint64(dod), 9)
	case -2047 <= dod && dod <= 2048:
		s.bw.writeBits(0x0e, 4) // '1110'
		s.bw.writeBits(uint64(dod), 12)
	default:
		s.bw.writeBits(0x0f, 4) // '1111'
		s.bw.writeBits(uint64(dod), 32)
	}

	vDelta := math.Float64bits(v) ^ math.Float64bits(s.val)

	if vDelta == 0 {
		s.bw.writeBit(zero)
	} else {
		s.bw.writeBit(one)

		leading := uint8(bits.LeadingZeros64(vDelta))
		trailing := uint8(bits.TrailingZeros64(vDelta))

		// clamp number of leading zeros to avoid overflow when encoding
		if leading >= 32 {
			leading = 31
		}

		// TODO(dgryski): check if it's 'cheaper' to reset the leading/trailing bits instead
		if s.leading != ^uint8(0) && leading >= s.leading && trailing >= s.trailing {
			s.bw.writeBit(zero)
			s.bw.writeBits(vDelta>>s.trailing, 64-int(s.leading)-int(s.trailing))
		} else {
			s.leading, s.trailing = leading, trailing

			s.bw.writeBit(one)
			s.bw.writeBits(uint64(leading), 5)

			// Note that if leading == trailing == 0, then sigbits == 64.  But that value doesn't actually fit into the 6 bits we have.
			// Luckily, we never need to encode 0 significant bits, since that would put us in the other case (vdelta == 0).
			// So instead we write out a 0 and adjust it back to 64 on unpacking.
			sigbits := 64 - leading - trailing
			s.bw.writeBits(uint64(sigbits), 6)
			s.bw.writeBits(vDelta>>trailing, int(sigbits))
		}
	}

	s.tDelta = tDelta
	s.t = t
	s.val = v

}

// Iter lets you iterate over a series.  It is not concurrency-safe.
func (s *Series) Iter() *Iter {
	s.Lock()
	w := s.bw.clone()
	s.Unlock()

	finish(w)
	iter, _ := bstreamIterator(w)
	return iter
}

// Iter lets you iterate over a series.  It is not concurrency-safe.
type Iter struct {
	T0 uint32

	t   uint32
	val float64

	br       bstream
	leading  uint8
	trailing uint8

	finished bool

	tDelta uint32
	err    error
}

func bstreamIterator(br *bstream) (*Iter, error) {

	br.count = 8

	t0, err := br.readBits(32)
	if err != nil {
		return nil, err
	}

	return &Iter{
		T0: uint32(t0),
		br: *br,
	}, nil
}

// NewIterator for the series
func NewIterator(b []byte) (*Iter, error) {
	return bstreamIterator(newBReader(b))
}

func (it *Iter) dod() (int32, bool) {
	var d byte
	for i := 0; i < 4; i++ {
		d <<= 1
		bit, err := it.br.readBit()
		if err != nil {
			it.err = err
			return 0, false
		}
		if bit == zero {
			break
		}
		d |= 1
	}

	var dod int32
	var sz uint
	switch d {
	case 0x00:
		// dod == 0
	case 0x02:
		sz = 7
	case 0x06:
		sz = 9
	case 0x0e:
		sz = 12
	case 0x0f:
		bits, err := it.br.readBits(32)
		if err != nil {
			it.err = err
			return 0, false
		}

		// end of stream
		if bits == 0xffffffff {
			it.finished = true
			return 0, false
		}

		dod = int32(bits)
	}

	if sz != 0 {
		bits, err := it.br.readBits(int(sz))
		if err != nil {
			it.err = err
			return 0, false
		}
		if bits > (1 << (sz - 1)) {
			// or something
			bits = bits - (1 << sz)
		}
		dod = int32(bits)
	}

	return dod, true
}

// Next iteration of the series iterator
func (it *Iter) Next() bool {

	if it.err != nil || it.finished {
		return false
	}

	if it.t == 0 {
		// read first t and v
		tDelta, err := it.br.readBits(14)
		if err != nil {
			it.err = err
			return false
		}
		it.tDelta = uint32(tDelta)
		it.t = it.T0 + it.tDelta
		v, err := it.br.readBits(64)
		if err != nil {
			it.err = err
			return false
		}

		it.val = math.Float64frombits(v)

		return true
	}

	// read delta-of-delta
	dod, ok := it.dod()
	if !ok {
		return false
	}

	tDelta := it.tDelta + uint32(dod)

	it.tDelta = tDelta
	it.t = it.t + it.tDelta

	// read compressed value
	bit, err := it.br.readBit()
	if err != nil {
		it.err = err
		return false
	}

	if bit == zero {
		// it.val = it.val
	} else {
		bit, itErr := it.br.readBit()
		if itErr != nil {
			it.err = err
			return false
		}
		if bit == zero {
			// reuse leading/trailing zero bits
			// it.leading, it.trailing = it.leading, it.trailing
		} else {
			bits, err := it.br.readBits(5)
			if err != nil {
				it.err = err
				return false
			}
			it.leading = uint8(bits)

			bits, err = it.br.readBits(6)
			if err != nil {
				it.err = err
				return false
			}
			mbits := uint8(bits)
			// 0 significant bits here means we overflowed and we actually need 64; see comment in encoder
			if mbits == 0 {
				mbits = 64
			}
			it.trailing = 64 - it.leading - mbits
		}

		mbits := int(64 - it.leading - it.trailing)
		bits, err := it.br.readBits(mbits)
		if err != nil {
			it.err = err
			return false
		}
		vbits := math.Float64bits(it.val)
		vbits ^= (bits << it.trailing)
		it.val = math.Float64frombits(vbits)
	}

	return true
}

// Values at the current iterator position
func (it *Iter) Values() (uint32, float64) {
	return it.t, it.val
}

// Err error at the current iterator position
func (it *Iter) Err() error {
	return it.err
}

type errMarshal struct {
	w   io.Writer
	r   io.Reader
	err error
}

func (em *errMarshal) write(t interface{}) {
	if em.err != nil {
		return
	}
	em.err = binary.Write(em.w, binary.BigEndian, t)
}

func (em *errMarshal) read(t interface{}) {
	if em.err != nil {
		return
	}
	em.err = binary.Read(em.r, binary.BigEndian, t)
}

// MarshalBinary implements the encoding.BinaryMarshaler interface
func (s *Series) MarshalBinary() ([]byte, error) {
	buf := new(bytes.Buffer)
	em := &errMarshal{w: buf}
	em.write(s.T0)
	em.write(s.leading)
	em.write(s.t)
	em.write(s.tDelta)
	em.write(s.trailing)
	em.write(s.val)
	bStream, err := s.bw.MarshalBinary()
	if err != nil {
		return nil, err
	}
	em.write(bStream)
	if em.err != nil {
		return nil, em.err
	}
	return buf.Bytes(), nil
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
func (s *Series) UnmarshalBinary(b []byte) error {
	buf := bytes.NewReader(b)
	em := &errMarshal{r: buf}
	em.read(&s.T0)
	em.read(&s.leading)
	em.read(&s.t)
	em.read(&s.tDelta)
	em.read(&s.trailing)
	em.read(&s.val)
	outBuf := make([]byte, buf.Len())
	em.read(outBuf)
	err := s.bw.UnmarshalBinary(outBuf)
	if err != nil {
		return err
	}
	if em.err != nil {
		return em.err
	}
	return nil
}
<|endoftext|>


<commit_before>package index

import (
	"fmt"
	"html/template"
	"net/http"

	"github.com/pivotal-golang/lager"
	"github.com/tedsuo/rata"

	"github.com/concourse/atc/builds"
	"github.com/concourse/atc/config"
	"github.com/concourse/atc/db"
	"github.com/concourse/atc/radar"
	"github.com/concourse/atc/web/routes"
)

type handler struct {
	logger lager.Logger

	// used for providing resource state
	radar *radar.Radar

	resources config.Resources
	jobs      config.Jobs
	db        db.DB
	template  *template.Template
}

func NewHandler(logger lager.Logger, radar *radar.Radar, resources config.Resources, jobs config.Jobs, db db.DB, template *template.Template) http.Handler {
	return &handler{
		logger: logger,

		radar: radar,

		resources: resources,
		jobs:      jobs,
		db:        db,
		template:  template,
	}
}

type TemplateData struct {
	Jobs  []JobStatus
	Nodes []DotNode
	Edges []DotEdge
}

type DotNode struct {
	ID    string            `json:"id"`
	Value map[string]string `json:"value,omitempty"`
}

type DotEdge struct {
	Source      string            `json:"u"`
	Destination string            `json:"v"`
	Value       map[string]string `json:"value,omitempty"`
}

type JobStatus struct {
	Job          config.Job
	CurrentBuild builds.Build
}

func (handler *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	data := TemplateData{
		Nodes: []DotNode{},
		Edges: []DotEdge{},
	}

	log := handler.logger.Session("index")

	currentBuilds := map[string]builds.Build{}

	for _, job := range handler.jobs {
		currentBuild, err := handler.db.GetCurrentBuild(job.Name)
		if err != nil {
			currentBuild.Status = builds.StatusPending
		}

		currentBuilds[job.Name] = currentBuild

		data.Jobs = append(data.Jobs, JobStatus{
			Job:          job,
			CurrentBuild: currentBuild,
		})
	}

	for _, resource := range handler.resources {
		resourceID := resourceNode(resource.Name)

		resourceURI, _ := routes.Routes.CreatePathForRoute(routes.GetResource, rata.Params{
			"resource": resource.Name,
		})

		failing, checking := handler.radar.ResourceStatus(resource.Name)

		var status string
		if failing {
			status = "failing"
		} else {
			status = "ok"
		}

		if checking {
			status += " checking"
		}

		data.Nodes = append(data.Nodes, DotNode{
			ID: resourceID,
			Value: map[string]string{
				"label":  fmt.Sprintf(`<h1 class="resource"><a href="%s">%s</a></h1>`, resourceURI, resource.Name),
				"type":   "resource",
				"status": status,
			},
		})
	}

	for _, job := range handler.jobs {
		jobID := jobNode(job.Name)
		currentBuild := currentBuilds[job.Name]

		var buildURI string
		var err error

		if currentBuild.Name != "" {
			buildURI, err = routes.Routes.CreatePathForRoute(routes.GetBuild, rata.Params{
				"job":   job.Name,
				"build": currentBuild.Name,
			})
		} else {
			buildURI, err = routes.Routes.CreatePathForRoute(routes.GetJob, rata.Params{
				"job": job.Name,
			})
		}

		if err != nil {
			log.Error("failed-to-create-route", err)
		}

		data.Nodes = append(data.Nodes, DotNode{
			ID: jobID,
			Value: map[string]string{
				"label":  fmt.Sprintf(`<h1 class="job"><a href="%s">%s</a>`, buildURI, job.Name),
				"status": string(currentBuild.Status),
				"type":   "job",
			},
		})

		edges := map[string]DotEdge{}

		for _, input := range job.Inputs {
			if len(input.Passed) > 0 {
				var nodeID string

				if len(input.Passed) > 1 {
					nodeID = jobID + "-input-" + input.Resource

					data.Nodes = append(data.Nodes, DotNode{
						ID: nodeID,
						Value: map[string]string{
							"useDef": "gateway",
						},
					})

					data.Edges = append(data.Edges, DotEdge{
						Source:      nodeID,
						Destination: jobID,
					})
				} else {
					nodeID = jobID
				}

				for _, passed := range input.Passed {
					currentBuild := currentBuilds[passed]

					passedJob, found := handler.jobs.Lookup(passed)
					if !found {
						panic("unknown job: " + passed)
					}

					existingEdge, found := edges[passed]
					if found {
						if len(passedJob.Inputs) > 1 {
							existingEdge.Value["label"] += "\n" + input.Resource
						}
					} else {
						value := map[string]string{
							"status": string(currentBuild.Status),
						}

						if len(passedJob.Inputs) > 1 {
							value["label"] = input.Resource
						}

						edges[passed] = DotEdge{
							Source:      jobNode(passed),
							Destination: nodeID,
							Value:       value,
						}
					}
				}
			} else {
				data.Edges = append(data.Edges, DotEdge{
					Source:      resourceNode(input.Resource),
					Destination: jobID,
				})
			}
		}

		for _, edge := range edges {
			data.Edges = append(data.Edges, edge)
		}

		for _, output := range job.Outputs {
			data.Edges = append(data.Edges, DotEdge{
				Source:      jobID,
				Destination: resourceNode(output.Resource),
				Value: map[string]string{
					"status": string(currentBuild.Status),
				},
			})
		}
	}

	err := handler.template.Execute(w, data)
	if err != nil {
		log.Fatal("failed-to-execute-template", err, lager.Data{
			"template-data": data,
		})
	}
}

func resourceNode(resource string) string {
	return "resource-" + resource
}

func jobNode(job string) string {
	return "job-" + job
}
<commit_msg>hide resources from pipeline with ?hide-resources<commit_after>package index

import (
	"fmt"
	"html/template"
	"net/http"

	"github.com/pivotal-golang/lager"
	"github.com/tedsuo/rata"

	"github.com/concourse/atc/builds"
	"github.com/concourse/atc/config"
	"github.com/concourse/atc/db"
	"github.com/concourse/atc/radar"
	"github.com/concourse/atc/web/routes"
)

type handler struct {
	logger lager.Logger

	// used for providing resource state
	radar *radar.Radar

	resources config.Resources
	jobs      config.Jobs
	db        db.DB
	template  *template.Template
}

func NewHandler(logger lager.Logger, radar *radar.Radar, resources config.Resources, jobs config.Jobs, db db.DB, template *template.Template) http.Handler {
	return &handler{
		logger: logger,

		radar: radar,

		resources: resources,
		jobs:      jobs,
		db:        db,
		template:  template,
	}
}

type TemplateData struct {
	Jobs  []JobStatus
	Nodes []DotNode
	Edges []DotEdge
}

type DotNode struct {
	ID    string            `json:"id"`
	Value map[string]string `json:"value,omitempty"`
}

type DotEdge struct {
	Source      string            `json:"u"`
	Destination string            `json:"v"`
	Value       map[string]string `json:"value,omitempty"`
}

type JobStatus struct {
	Job          config.Job
	CurrentBuild builds.Build
}

func (handler *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	_, hideResources := r.URL.Query()["hide-resources"]
	showResources := !hideResources

	data := TemplateData{
		Nodes: []DotNode{},
		Edges: []DotEdge{},
	}

	log := handler.logger.Session("index")

	currentBuilds := map[string]builds.Build{}

	for _, job := range handler.jobs {
		currentBuild, err := handler.db.GetCurrentBuild(job.Name)
		if err != nil {
			currentBuild.Status = builds.StatusPending
		}

		currentBuilds[job.Name] = currentBuild

		data.Jobs = append(data.Jobs, JobStatus{
			Job:          job,
			CurrentBuild: currentBuild,
		})
	}

	if showResources {
		for _, resource := range handler.resources {
			resourceID := resourceNode(resource.Name)

			resourceURI, _ := routes.Routes.CreatePathForRoute(routes.GetResource, rata.Params{
				"resource": resource.Name,
			})

			failing, checking := handler.radar.ResourceStatus(resource.Name)

			var status string
			if failing {
				status = "failing"
			} else {
				status = "ok"
			}

			if checking {
				status += " checking"
			}

			data.Nodes = append(data.Nodes, DotNode{
				ID: resourceID,
				Value: map[string]string{
					"label":  fmt.Sprintf(`<h1 class="resource"><a href="%s">%s</a></h1>`, resourceURI, resource.Name),
					"type":   "resource",
					"status": status,
				},
			})
		}
	}

	for _, job := range handler.jobs {
		jobID := jobNode(job.Name)
		currentBuild := currentBuilds[job.Name]

		var buildURI string
		var err error

		if currentBuild.Name != "" {
			buildURI, err = routes.Routes.CreatePathForRoute(routes.GetBuild, rata.Params{
				"job":   job.Name,
				"build": currentBuild.Name,
			})
		} else {
			buildURI, err = routes.Routes.CreatePathForRoute(routes.GetJob, rata.Params{
				"job": job.Name,
			})
		}

		if err != nil {
			log.Error("failed-to-create-route", err)
		}

		data.Nodes = append(data.Nodes, DotNode{
			ID: jobID,
			Value: map[string]string{
				"label":  fmt.Sprintf(`<h1 class="job"><a href="%s">%s</a>`, buildURI, job.Name),
				"status": string(currentBuild.Status),
				"type":   "job",
			},
		})

		edges := map[string]DotEdge{}

		for _, input := range job.Inputs {
			if len(input.Passed) > 0 {
				var nodeID string

				if len(input.Passed) > 1 {
					nodeID = jobID + "-input-" + input.Resource

					data.Nodes = append(data.Nodes, DotNode{
						ID: nodeID,
						Value: map[string]string{
							"useDef": "gateway",
						},
					})

					data.Edges = append(data.Edges, DotEdge{
						Source:      nodeID,
						Destination: jobID,
					})
				} else {
					nodeID = jobID
				}

				for _, passed := range input.Passed {
					currentBuild := currentBuilds[passed]

					passedJob, found := handler.jobs.Lookup(passed)
					if !found {
						panic("unknown job: " + passed)
					}

					existingEdge, found := edges[passed]
					if found {
						if len(passedJob.Inputs) > 1 {
							existingEdge.Value["label"] += "\n" + input.Resource
						}
					} else {
						value := map[string]string{
							"status": string(currentBuild.Status),
						}

						if len(passedJob.Inputs) > 1 {
							value["label"] = input.Resource
						}

						edges[passed] = DotEdge{
							Source:      jobNode(passed),
							Destination: nodeID,
							Value:       value,
						}
					}
				}
			} else if showResources {
				data.Edges = append(data.Edges, DotEdge{
					Source:      resourceNode(input.Resource),
					Destination: jobID,
				})
			}
		}

		for _, edge := range edges {
			data.Edges = append(data.Edges, edge)
		}

		if showResources {
			for _, output := range job.Outputs {
				data.Edges = append(data.Edges, DotEdge{
					Source:      jobID,
					Destination: resourceNode(output.Resource),
					Value: map[string]string{
						"status": string(currentBuild.Status),
					},
				})
			}
		}
	}

	err := handler.template.Execute(w, data)
	if err != nil {
		log.Fatal("failed-to-execute-template", err, lager.Data{
			"template-data": data,
		})
	}
}

func resourceNode(resource string) string {
	return "resource-" + resource
}

func jobNode(job string) string {
	return "job-" + job
}
<|endoftext|>


<commit_before>// Package `renderer` makes more comfortable work
// with tempalte rendering
package render

import (
	"github.com/codegangsta/martini-contrib/render"
	"github.com/go-martini/martini"
	"log"
)

type Render struct {
	renderer render.Render
}

func (r *Render) HTML(name string, bindings map[string]interface{}) {
	r.renderer.HTML(200, name, bindings)
}

func (r *Render) Redirect(url string) {
	r.renderer.Redirect(url)
}

func RenderHandler(c martini.Context, mr render.Render) {
	r := &Render{mr}
	c.Map(r)
}
<commit_msg>Fixed error<commit_after>// Package `renderer` makes more comfortable work
// with tempalte rendering
package render

import (
	"github.com/codegangsta/martini-contrib/render"
	"github.com/go-martini/martini"
)

type Render struct {
	renderer render.Render
}

func (r *Render) HTML(name string, bindings map[string]interface{}) {
	r.renderer.HTML(200, name, bindings)
}

func (r *Render) Redirect(url string) {
	r.renderer.Redirect(url)
}

func RenderHandler(c martini.Context, mr render.Render) {
	r := &Render{mr}
	c.Map(r)
}
<|endoftext|>