<commit_before>package trackello

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

	"github.com/VojtechVitek/go-trello"
	"github.com/klauern/trackello/rest"
	"github.com/spf13/viper"
)

type trelloActivity struct {
	cardsWorkedOn map[string]time.Time
	oldestDate    time.Time
	boardActions  map[string][]trello.Action
}

// Trackello represents the connection to Trello for a specific user.
type Trackello struct {
	token  string
	appKey string
	client trello.Client
}

// Card is both the Trello Card + other stats on the actions in it.
type Card struct {
	card  trello.Card
	stats statistics
}

// List is both the Trello List + other stats on the actions in it.
type List struct {
	name  string
	cards []Card
	stats statistics
}

// NewTrackello will create a `Trackello` type using your preferences application token and appkey.
func NewTrackello() (*Trackello, error) {
	token := viper.GetString("token")
	appKey := viper.GetString("appkey")

	// New Trello Client
	tr, err := trello.NewAuthClient(appKey, &token)
	if err != nil {
		log.Fatal(err)
		return nil, err
	}

	return &Trackello{
		token:  token,
		appKey: appKey,
		client: *tr,
	}, nil
}

// PrimaryBoard will return a board based on your settings for a primary board.
func (t *Trackello) PrimaryBoard() (trello.Board, error) {
	board, err := t.client.Board(viper.GetString("board"))
	if err != nil {
		log.Fatal(err)
		return *board, err
	}
	return *board, nil
}

// BoardWithId will return the Trello Board given it's ID string.
func (t *Trackello) BoardWithId(id string) (trello.Board, error) {
	board, err := t.client.Board(id)
	if err != nil {
		log.Fatal(err)
		return *board, err
	}
	return *board, nil
}

// Boards will list all of the boards for the authenticated user (i.e. 'me').
func (t *Trackello) Boards() ([]trello.Board, error) {
	member, err := t.client.Member("me")
	if err != nil {
		log.Fatalf("Error getting 'me' Member: %v", err)
		return make([]trello.Board, 0), err
	}
	boards, err := member.Boards()
	return boards, err
}

func (t *Trackello) mapActionsAndDates(actions []trello.Action, activities *trelloActivity) {
	for _, action := range actions {
		switch activities.boardActions[action.Data.Card.Name] {
		case nil:
			activities.boardActions[action.Data.Card.Name] = []trello.Action{action}
		default:
			activities.boardActions[action.Data.Card.Name] = append(activities.boardActions[action.Data.Card.Name], action)
		}
		if action.Data.List.Name == "" {
			action.Data.List.Name = t.getListForAction(action)
		}
	}
}

func (t *Trackello) getListForAction(a trello.Action) string {
	if len(a.Data.List.Id) > 0 {
		if list, err := t.client.List(a.Data.List.Id); err == nil {
			return list.Name
		}
	}
	return ""
}

func (t *Trackello) getCardForAction(a trello.Action) (*trello.Card, error) {
	return t.client.Card(a.Data.Card.Id)
}

// MapBoardActions takes the slice of []trello.Action and maps it to a Card and it's associated List.
func (t *Trackello) MapBoardActions(actions []trello.Action) ([]List, error) {
	listCards := make(map[string]List)
	for _, v := range actions {
		if len(v.Data.Card.Id) > 0 {
			card, err := t.getCardForAction(v)
			if err != nil {
				fmt.Printf("error in getting Cards for Action")
				return nil, err
			}
			list, err := t.client.List(card.IdList)
			if err != nil {
				return nil, err
			}
			lc, ok := listCards[list.Name]
			if ok {
				fmt.Printf("Length of %s before append is %d \n", lc.name, len(lc.cards))
				lc.cards = append(lc.cards, *Card{card: card})
				fmt.Printf("Length of %s after append  is %d \n", lc.name, len(lc.cards))
			} else {
				cards := make([]Card, 1)
				cards = append(cards, &Card{card: card})
				listCards[list.Name] = List{
					name:  list.Name,
					cards: []Card{{card: card}},
				}
			}
		}
	}
	list := make([]List, len(listCards))
	for _, v := range listCards {
		list = append(list, v)
	}
	return list, nil
}

// Print will print out a list and all of the cards to the command-line.
func (l *List) Print() {
	if len(l.name) > 0 {
		fmt.Printf("%s\n", l.name)
		for _, card := range l.cards {
			fmt.Printf("  * %s\n", card.card.Name)
		}
	}
}

// Board will pull the Trello Board with an ID.  If id is "", it will pull it from the PrimaryBoard configuration setting.
func (t *Trackello) Board(id string) (trello.Board, error) {
	if id == "" {
		return t.PrimaryBoard()
	}
	return t.BoardWithId(id)
}

// BoardActions will retrieve a slice of trello.Action based on the Board ID.
func BoardActions(id string) ([]trello.Action, error) {
	t, err := NewTrackello()
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}

	board, err := t.Board(id)
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}

	args := rest.CreateArgsForBoardActions()
	actions, err := board.Actions(args...)
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
	return actions, err
}
<commit_msg>use some pointers for pete's sake<commit_after>package trackello

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

	"github.com/VojtechVitek/go-trello"
	"github.com/klauern/trackello/rest"
	"github.com/spf13/viper"
)

type trelloActivity struct {
	cardsWorkedOn map[string]time.Time
	oldestDate    time.Time
	boardActions  map[string][]trello.Action
}

// Trackello represents the connection to Trello for a specific user.
type Trackello struct {
	token  string
	appKey string
	client *trello.Client
}

// Card is both the Trello Card + other stats on the actions in it.
type Card struct {
	card  *trello.Card
	stats *statistics
}

// List is both the Trello List + other stats on the actions in it.
type List struct {
	name  string
	cards []Card
	stats *statistics
}

// NewTrackello will create a `Trackello` type using your preferences application token and appkey.
func NewTrackello() (*Trackello, error) {
	token := viper.GetString("token")
	appKey := viper.GetString("appkey")

	// New Trello Client
	tr, err := trello.NewAuthClient(appKey, &token)
	if err != nil {
		log.Fatal(err)
		return nil, err
	}

	return &Trackello{
		token:  token,
		appKey: appKey,
		client: tr,
	}, nil
}

// PrimaryBoard will return a board based on your settings for a primary board.
func (t *Trackello) PrimaryBoard() (trello.Board, error) {
	board, err := t.client.Board(viper.GetString("board"))
	if err != nil {
		log.Fatal(err)
		return *board, err
	}
	return *board, nil
}

// BoardWithId will return the Trello Board given it's ID string.
func (t *Trackello) BoardWithId(id string) (trello.Board, error) {
	board, err := t.client.Board(id)
	if err != nil {
		log.Fatal(err)
		return *board, err
	}
	return *board, nil
}

// Boards will list all of the boards for the authenticated user (i.e. 'me').
func (t *Trackello) Boards() ([]trello.Board, error) {
	member, err := t.client.Member("me")
	if err != nil {
		log.Fatalf("Error getting 'me' Member: %v", err)
		return make([]trello.Board, 0), err
	}
	boards, err := member.Boards()
	return boards, err
}

func (t *Trackello) mapActionsAndDates(actions []trello.Action, activities *trelloActivity) {
	for _, action := range actions {
		switch activities.boardActions[action.Data.Card.Name] {
		case nil:
			activities.boardActions[action.Data.Card.Name] = []trello.Action{action}
		default:
			activities.boardActions[action.Data.Card.Name] = append(activities.boardActions[action.Data.Card.Name], action)
		}
		if action.Data.List.Name == "" {
			action.Data.List.Name = t.getListForAction(action)
		}
	}
}

func (t *Trackello) getListForAction(a trello.Action) string {
	if len(a.Data.List.Id) > 0 {
		if list, err := t.client.List(a.Data.List.Id); err == nil {
			return list.Name
		}
	}
	return ""
}

func (t *Trackello) getCardForAction(a trello.Action) (*trello.Card, error) {
	return t.client.Card(a.Data.Card.Id)
}

// MapBoardActions takes the slice of []trello.Action and maps it to a Card and it's associated List.
func (t *Trackello) MapBoardActions(actions []trello.Action) ([]List, error) {
	listCards := make(map[string]List)
	for _, v := range actions {
		if len(v.Data.Card.Id) > 0 {
			card, err := t.getCardForAction(v)
			if err != nil {
				fmt.Printf("error in getting Cards for Action")
				return nil, err
			}
			list, err := t.client.List(card.IdList)
			if err != nil {
				return nil, err
			}
			lc, ok := listCards[list.Name]
			if ok {
				fmt.Printf("Length of %s before append is %d \n", lc.name, len(lc.cards))
				lc.cards = append(lc.cards, Card{card: card})
				fmt.Printf("Length of %s after append  is %d \n", lc.name, len(lc.cards))
			} else {
				cards := make([]Card, 1)
				cards = append(cards, Card{card: card})
				listCards[list.Name] = List{
					name:  list.Name,
					cards: []Card{{card: card}},
				}
			}
		}
	}
	list := make([]List, len(listCards))
	for _, v := range listCards {
		list = append(list, v)
	}
	return list, nil
}

// Print will print out a list and all of the cards to the command-line.
func (l *List) Print() {
	if len(l.name) > 0 {
		fmt.Printf("%s\n", l.name)
		for _, card := range l.cards {
			fmt.Printf("  * %s\n", card.card.Name)
		}
	}
}

// Board will pull the Trello Board with an ID.  If id is "", it will pull it from the PrimaryBoard configuration setting.
func (t *Trackello) Board(id string) (trello.Board, error) {
	if id == "" {
		return t.PrimaryBoard()
	}
	return t.BoardWithId(id)
}

// BoardActions will retrieve a slice of trello.Action based on the Board ID.
func BoardActions(id string) ([]trello.Action, error) {
	t, err := NewTrackello()
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}

	board, err := t.Board(id)
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}

	args := rest.CreateArgsForBoardActions()
	actions, err := board.Actions(args...)
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
	return actions, err
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/digitalocean/godo"
	"time"
)

const (
	actionUnknown = iota
	actionSnapshot
	actionShuttingDown
	actionDestroy
	actionCreate
	actionErrored
	actionRunning
)

func startDropletMonitor() {
	for {
		delay := runDropletCheck()
		time.Sleep(delay)
	}
}

func runDropletCheck() (delay time.Duration) {
	delay = time.Second * 30

	serverPrefixes := make([]string, len(allServers))

	for i, server := range allServers {
		server.StateLock.Lock()
		defer server.StateLock.Unlock()
		serverPrefixes[i] = server.Name
	}

	droplets, err := getDropletsList(serverPrefixes)
	if err != nil {
		Log("droplet monitor", "Failed to get droplet list:", err)
		return time.Second * 10
	}

	for i, droplet := range droplets {
		server := allServers[i]
		if !droplet.exists {
			server.SetState(stateOff)
			continue
		}

		server.IPAddress = droplet.Networks.V4[0].IPAddress
		server.DropletId = droplet.ID

		if droplet.Status == "off" && server.State == stateShutdown {
			go server.Snapshot()
			continue
		}

		if droplet.Status == "active" && server.State == stateSnapshot {
			go server.Destroy()
			continue
		}

		if server.IsMinecraftServerResponding() {
			server.SetState(stateStarted)
			continue
		}

		event, err := getRunningAction(server, droplet.Status)
		if err != nil {
			server.Log("droplet monitor", "Failed to get running event:", err)
			server.SetState(stateUnavailable)
			continue
		}

		switch event {
		case actionUnknown:
			server.SetState(stateUnavailable)
		case actionSnapshot:
			server.SetState(stateSnapshot)
			delay = time.Second * 10
		case actionShuttingDown:
			server.SetState(stateShutdown)
			delay = time.Second * 10
		case actionDestroy:
			server.SetState(stateDestroy)
			delay = time.Second * 10
		case actionCreate:
			server.SetState(stateStarting)
			delay = time.Second * 10
		case actionErrored:
			server.SetState(stateUnavailable)
		case actionRunning:
			server.SetState(stateUnavailable)
		}
	}

	return delay
}

type dropletState struct {
	godo.Droplet
	exists bool
}

func getDropletsList(dropletPrefixes []string) ([]dropletState, error) {
	opt := &godo.ListOptions{
		Page:    1,
		PerPage: 100,
	}

	var droplets []godo.Droplet

	droplets, _, err := doClient.Droplets.List(opt)
	if err != nil {
		return []dropletState{}, err
	}

	var automatedDroplets []dropletState

	for _, prefix := range dropletPrefixes {
		found := false

		for _, droplet := range droplets {
			if droplet.Name == prefix+"-automated" {
				dropletInfo := dropletState{Droplet: droplet, exists: true}
				automatedDroplets = append(automatedDroplets, dropletInfo)
				found = true
				break
			}
		}

		if !found {
			automatedDroplets = append(automatedDroplets,
				dropletState{exists: false})
		}
	}

	return automatedDroplets, nil
}

func getRunningAction(server *Server, dropletStatus string) (int, error) {
	opt := &godo.ListOptions{
		Page:    1,
		PerPage: 100,
	}
	actions, _, err := doClient.Droplets.Actions(server.DropletId, opt)
	if err != nil {
		return 0, err
	}

	// Issues with the most recent action takes priority over whether the
	// the droplet is active or not.
	if actions[0].Status == "errored" {
		return actionErrored, nil
	}

	if actions[0].Status == "completed" && dropletStatus == "active" {
		if server.State == stateShutdown {
			return actionShuttingDown, nil
		}

		return actionRunning, nil
	}

	switch actions[0].Type {
	case "create":
		return actionCreate, nil
	case "snapshot":
		if server.State != stateUnavailable {
			return actionSnapshot, nil
		} else {
			return actionRunning, nil
		}
	case "destroy":
		return actionDestroy, nil
	case "shutdown":
		return actionShuttingDown, nil
	}

	return actionUnknown, nil
}
<commit_msg>More attempts at improving state detection<commit_after>package main

import (
	"github.com/digitalocean/godo"
	"time"
)

const (
	actionUnknown = iota
	actionSnapshot
	actionShuttingDown
	actionDestroy
	actionCreate
	actionErrored
	actionRunning
)

func startDropletMonitor() {
	for {
		delay := runDropletCheck()
		time.Sleep(delay)
	}
}

func runDropletCheck() (delay time.Duration) {
	delay = time.Second * 30

	serverPrefixes := make([]string, len(allServers))

	for i, server := range allServers {
		server.StateLock.Lock()
		defer server.StateLock.Unlock()
		serverPrefixes[i] = server.Name
	}

	droplets, err := getDropletsList(serverPrefixes)
	if err != nil {
		Log("droplet monitor", "Failed to get droplet list:", err)
		return time.Second * 10
	}

	for i, droplet := range droplets {
		server := allServers[i]
		if !droplet.exists {
			server.SetState(stateOff)
			continue
		}

		server.IPAddress = droplet.Networks.V4[0].IPAddress
		server.DropletId = droplet.ID

		if droplet.Status == "off" && server.State == stateShutdown {
			go server.Snapshot()
			continue
		}

		if droplet.Status == "active" && server.State == stateSnapshot {
			go server.Destroy()
			continue
		}

		if server.IsMinecraftServerResponding() {
			server.SetState(stateStarted)
			continue
		}

		event, err := getRunningAction(server, droplet.Status)
		if err != nil {
			server.Log("droplet monitor", "Failed to get running event:", err)
			server.SetState(stateUnavailable)
			continue
		}

		switch event {
		case actionUnknown:
			server.SetState(stateUnavailable)
		case actionSnapshot:
			server.SetState(stateSnapshot)
			delay = time.Second * 10
		case actionShuttingDown:
			server.SetState(stateShutdown)
			delay = time.Second * 10
		case actionDestroy:
			server.SetState(stateDestroy)
			delay = time.Second * 10
		case actionCreate:
			server.SetState(stateStarting)
			delay = time.Second * 10
		case actionErrored:
			server.SetState(stateUnavailable)
		case actionRunning:
			if server.IsMinecraftServerRunning() {
				server.SetState(stateStarting)
			} else {
				if server.State != stateStarting {
					server.SetState(stateUnavailable)
				}
			}
		}
	}

	return delay
}

type dropletState struct {
	godo.Droplet
	exists bool
}

func getDropletsList(dropletPrefixes []string) ([]dropletState, error) {
	opt := &godo.ListOptions{
		Page:    1,
		PerPage: 100,
	}

	var droplets []godo.Droplet

	droplets, _, err := doClient.Droplets.List(opt)
	if err != nil {
		return []dropletState{}, err
	}

	var automatedDroplets []dropletState

	for _, prefix := range dropletPrefixes {
		found := false

		for _, droplet := range droplets {
			if droplet.Name == prefix+"-automated" {
				dropletInfo := dropletState{Droplet: droplet, exists: true}
				automatedDroplets = append(automatedDroplets, dropletInfo)
				found = true
				break
			}
		}

		if !found {
			automatedDroplets = append(automatedDroplets,
				dropletState{exists: false})
		}
	}

	return automatedDroplets, nil
}

func getRunningAction(server *Server, dropletStatus string) (int, error) {
	opt := &godo.ListOptions{
		Page:    1,
		PerPage: 100,
	}
	actions, _, err := doClient.Droplets.Actions(server.DropletId, opt)
	if err != nil {
		return 0, err
	}

	// Issues with the most recent action takes priority over whether the
	// the droplet is active or not.
	if actions[0].Status == "errored" {
		return actionErrored, nil
	}

	if actions[0].Status == "completed" && dropletStatus == "active" {
		if server.State == stateShutdown {
			return actionShuttingDown, nil
		}

		return actionRunning, nil
	}

	switch actions[0].Type {
	case "create":
		return actionCreate, nil
	case "snapshot":
		if server.State != stateUnavailable {
			return actionSnapshot, nil
		} else {
			return actionRunning, nil
		}
	case "destroy":
		return actionDestroy, nil
	case "shutdown":
		return actionShuttingDown, nil
	}

	return actionUnknown, nil
}
<|endoftext|>


<commit_before>package countfds

import (
	"fmt"
	"io/ioutil"
	"os"
)

func CountFds() int {
	es, err := os.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid()))
	if err != nil {
		return 0
	}
	return len(es)
}
<commit_msg>attempt to fix build<commit_after>package countfds

import (
	"fmt"
	"io/ioutil"
	"os"
)

func CountFds() int {
	es, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid()))
	if err != nil {
		return 0
	}
	return len(es)
}
<|endoftext|>


<commit_before>// This script is used to load data into Dgraph from an RDF file by performing
// mutations using the HTTP interface.
//
// You can run the script like
// go build . && ./mutations --rdf $GOPATH/src/github.com/dgraph-io/benchmarks/data/names.gz --concurrent 100
package main

import (
	"bufio"
	"compress/gzip"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/dgraph-io/dgraph/x"
)

var (
	file       = flag.String("rdf", "", "Location of rdf file to load")
	dgraph     = flag.String("dgraph", "http://127.0.0.1:8080/query", "Dgraph server address")
	concurrent = flag.Int("concurrent", 10, "Number of concurrent requests to make to Dgraph")
)

func body(rdf string) string {
	return `mutation {
  set {
	` + rdf + `
      }
  }`
}

type response struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

var hc http.Client
var r response

func makeRequest(ch chan string, c *uint64, wg *sync.WaitGroup) {
	var counter uint64
	for m := range ch {
		counter = atomic.AddUint64(c, 1)
		if counter%10000 == 0 {
			fmt.Printf("Request: %v\n", counter)
		}
		req, err := http.NewRequest("POST", *dgraph, strings.NewReader(m))
		x.Check(err)
		res, err := hc.Do(req)
		x.Check(err)

		body, err := ioutil.ReadAll(res.Body)
		x.Check(err)
		x.Check(json.Unmarshal(body, &r))
		if r.Code != "ErrorOk" {
			log.Fatalf("Error while performing mutation: %v, err: %v", m, r.Message)
		}
	}
	wg.Done()
}

func main() {
	flag.Parse()
	f, err := os.Open(*file)
	x.Check(err)
	defer f.Close()
	gr, err := gzip.NewReader(f)
	x.Check(err)

	hc = http.Client{Timeout: 5 * time.Minute}
	ch := make(chan string, 1000)
	var count uint64 = 0
	var wg sync.WaitGroup
	for i := 0; i < *concurrent; i++ {
		wg.Add(1)
		go makeRequest(ch, &count, &wg)
	}

	scanner := bufio.NewScanner(gr)
	for scanner.Scan() {
		m := body(scanner.Text())
		ch <- m
	}
	x.Check(scanner.Err())
	close(ch)
	wg.Wait()
	fmt.Println("Final count of mutations run: ", count)
}
<commit_msg>Batch mutations<commit_after>// This script is used to load data into Dgraph from an RDF file by performing
// mutations using the HTTP interface.
//
// You can run the script like
// go build . && ./mutations --rdf $GOPATH/src/github.com/dgraph-io/benchmarks/data/names.gz --concurrent 100
package main

import (
	"bufio"
	"compress/gzip"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/dgraph-io/dgraph/x"
)

var (
	file       = flag.String("rdf", "", "Location of rdf file to load")
	dgraph     = flag.String("dgraph", "http://127.0.0.1:8080/query", "Dgraph server address")
	concurrent = flag.Int("concurrent", 1000, "Number of concurrent requests to make to Dgraph")
	numRdf     = flag.Int("mutations", 100, "Number of RDF N-Quads to send as part of a mutation.")
)

func body(rdf string) string {
	return `mutation {
  set {
  	` + rdf + `
      }
  }`
}

type response struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

var hc http.Client
var r response

func makeRequest(mutation chan string, c *uint64, wg *sync.WaitGroup) {
	var counter uint64
	for m := range mutation {
		counter = atomic.AddUint64(c, 1)
		if counter%10000 == 0 {
			fmt.Printf("Request: %v\n", counter)
		}
		req, err := http.NewRequest("POST", *dgraph, strings.NewReader(body(m)))
		x.Check(err)
		res, err := hc.Do(req)
		x.Check(err)

		body, err := ioutil.ReadAll(res.Body)
		x.Check(err)
		x.Check(json.Unmarshal(body, &r))
		if r.Code != "ErrorOk" {
			log.Fatalf("Error while performing mutation: %v, err: %v", m, r.Message)
		}
	}
	wg.Done()
}

func buildMutation(rdf chan string, m chan string, rdfCount *uint64, wg *sync.WaitGroup) {
	set := ""
	count := 0
	for r := range rdf {
		atomic.AddUint64(rdfCount, 1)
		count++
		set += r + "\n"
		if count == *numRdf {
			m <- set
			set = ""
			count = 0
		}

	}
	if set != "" {
		m <- set
	}
	wg.Done()
}

func main() {
	flag.Parse()
	f, err := os.Open(*file)
	x.Check(err)
	defer f.Close()
	gr, err := gzip.NewReader(f)
	x.Check(err)

	hc = http.Client{Timeout: 5 * time.Minute}

	rdf := make(chan string, 1000)
	mutation := make(chan string, 100)

	var rdfCount uint64 = 0
	var wgm sync.WaitGroup
	for i := 0; i < 10; i++ {
		wgm.Add(1)
		go buildMutation(rdf, mutation, &rdfCount, &wgm)
	}

	var count uint64 = 0
	var wg sync.WaitGroup
	for i := 0; i < *concurrent; i++ {
		wg.Add(1)
		go makeRequest(mutation, &count, &wg)
	}

	scanner := bufio.NewScanner(gr)
	for scanner.Scan() {
		// Lets send the rdf's to the channel.
		rdf <- scanner.Text()
	}
	x.Check(scanner.Err())
	close(rdf)

	wgm.Wait()
	close(mutation)
	wg.Wait()
	fmt.Println("Number of RDF's parsed: ", rdfCount)
	fmt.Println("Number of mutations run: ", count)
}
<|endoftext|>


<commit_before>// Copyright 2014 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package va

import (
	"testing"
	"net"
	"net/http"
	"fmt"
	"strings"
	"math/big"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"crypto/sha256"
	"time"

	"github.com/letsencrypt/boulder/core"
	"github.com/letsencrypt/boulder/test"
	"github.com/letsencrypt/boulder/jose"
)

func bigIntFromB64(b64 string) *big.Int {
	bytes, _ := jose.B64dec(b64)
	x := big.NewInt(0)
	x.SetBytes(bytes)
	return x
}

func intFromB64(b64 string) int {
	return int(bigIntFromB64(b64).Int64())
}

var n *big.Int = bigIntFromB64("n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw")
var e int = intFromB64("AQAB")
var d *big.Int = bigIntFromB64("bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78eiZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRldY7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-bMwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDjd18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOcOpBrQzwQ")
var p *big.Int = bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc")
var q *big.Int = bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc")

var TheKey rsa.PrivateKey = rsa.PrivateKey{
	PublicKey: rsa.PublicKey{N: n, E: e},
	D:         d,
	Primes:    []*big.Int{p, q},
}

var ident core.AcmeIdentifier = core.AcmeIdentifier{Type: core.IdentifierType("dns"), Value: "localhost"}

func simpleSrv(t *testing.T, token string, stopChan, waitChan chan bool) {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if strings.HasSuffix(r.URL.Path, "404") {
			http.NotFound(w, r)
		} else if strings.HasSuffix(r.URL.Path, "wrongtoken") {
			fmt.Fprintf(w, "wrongtoken")
		}
		fmt.Fprintf(w, "%s", token)
	})

	httpsServer := &http.Server{Addr: "localhost:5001"}
	conn, err := net.Listen("tcp", httpsServer.Addr)
	if err != nil {
		waitChan <- true
		t.Fatalf("Couldn't listen on %s: %s", httpsServer.Addr, err)
	}

	go func() {
		<-stopChan
		conn.Close()
	}()

	waitChan <- true
	t.Fatalf("%s", httpsServer.Serve(conn))
}

func dvsniSrv(t *testing.T, R, S []byte, waitChan chan bool) {
	RS := append(R, S...)
	z := sha256.Sum256(RS)
	zName := fmt.Sprintf("%064x.acme.invalid", z)
	template := &x509.Certificate{
		SerialNumber: big.NewInt(1337),
		Subject: pkix.Name{
			Organization: []string{"tests"},
		},
		NotBefore: time.Now(),
		NotAfter: time.Now().AddDate(0, 0, 1),

		KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,

		DNSNames: []string{zName},
	}

	certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
	cert := &tls.Certificate{
		Certificate: [][]byte{certBytes},
		PrivateKey: &TheKey,
	}

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{*cert},
		ClientAuth: tls.NoClientCert,
		GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
			fmt.Println(clientHello)
			return cert, nil
		},
		NextProtos: []string{"http/1.1"},
	}

	httpsServer := &http.Server{Addr: "localhost:5001"}
	conn, err := net.Listen("tcp", httpsServer.Addr)
	if err != nil {
		waitChan <- true
		t.Fatalf("Couldn't listen on %s: %s", httpsServer.Addr, err)
	}
	tlsListener := tls.NewListener(conn, tlsConfig)
	waitChan <- true
	t.Fatalf("%s", httpsServer.Serve(tlsListener))
}

func TestSimpleHttps(t *testing.T) {
	va := NewValidationAuthorityImpl(true)

	chall := core.Challenge{Path: "test", Token: "THETOKEN"}

	invalidChall := va.validateSimpleHTTPS(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	stopChan := make(chan bool, 1)
	waitChan := make(chan bool, 1)
	go simpleSrv(t, "THETOKEN", stopChan, waitChan)

	finChall := va.validateSimpleHTTPS(ident, chall)
	test.AssertEquals(t, finChall.Status, core.StatusValid)

	chall.Path = "404"
	invalidChall = va.validateSimpleHTTPS(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	chall.Path = "wrongtoken"
	invalidChall = va.validateSimpleHTTPS(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	chall.Path = ""
	invalidChall = va.validateSimpleHTTPS(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	chall.Path = "validish"
	invalidChall = va.validateSimpleHTTPS(core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	stopChan <- true
}

func TestDvsni(t *testing.T) {
	va := NewValidationAuthorityImpl(true)

	a := []byte{1,2,3,4,5,6,7,8,9,0}
	ba := core.B64enc(a)
	chall := core.Challenge{R: ba, S: ba}

	invalidChall := va.validateDvsni(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	waitChan := make(chan bool, 1)
	go dvsniSrv(t, a, a, waitChan)
	<-waitChan

	finChall := va.validateDvsni(ident, chall)
	test.AssertEquals(t, finChall.Status, core.StatusValid)

	chall.R = ba[5:]
	invalidChall = va.validateDvsni(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	invalidChall = va.validateSimpleHTTPS(core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	chall.R = ba
	chall.S = "!@#"
	invalidChall = va.validateDvsni(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
}

type MockRegistrationAuthority struct{}

func (ra *MockRegistrationAuthority) NewRegistration(reg core.Registration, jwk jose.JsonWebKey) (core.Registration, error) {
	return reg, nil
}

func (ra *MockRegistrationAuthority) NewAuthorization(authz core.Authorization, jwk jose.JsonWebKey) (core.Authorization, error) {
	return authz, nil
}

func (ra *MockRegistrationAuthority) NewCertificate(req core.CertificateRequest, jwk jose.JsonWebKey) (core.Certificate, error) {
	return core.Certificate{}, nil
}

func (ra *MockRegistrationAuthority) UpdateRegistration(reg core.Registration, updated core.Registration) (core.Registration, error) {
	return reg, nil
}

func (ra *MockRegistrationAuthority) UpdateAuthorization(authz core.Authorization, foo int, challenge core.Challenge) (core.Authorization, error) {
	return authz, nil
}

func (ra *MockRegistrationAuthority) RevokeCertificate(cert x509.Certificate) error {
	return nil
}

func (ra *MockRegistrationAuthority) OnValidationUpdate(authz core.Authorization) {
}

func TestValidate(t *testing.T) {
	va := NewValidationAuthorityImpl(false)
	va.RA = &MockRegistrationAuthority{}

	a := []byte{1,2,3,4,5,6,7,8,9,0}
	ba := core.B64enc(a)
	Dchall := core.Challenge{Path: "test", R: ba, S: ba}
	Schall := core.Challenge{Path: "test", Token: "THETOKEN"}

	authz := core.Authorization{Identifier: ident, Challenges: []core.Challenge{Dchall, Schall}}

	va.validate(authz)
}
<commit_msg>remove useless test stub<commit_after>// Copyright 2014 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package va

import (
	"testing"
	"net"
	"net/http"
	"fmt"
	"strings"
	"math/big"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"crypto/sha256"
	"time"

	"github.com/letsencrypt/boulder/core"
	"github.com/letsencrypt/boulder/test"
	"github.com/letsencrypt/boulder/jose"
)

func bigIntFromB64(b64 string) *big.Int {
	bytes, _ := jose.B64dec(b64)
	x := big.NewInt(0)
	x.SetBytes(bytes)
	return x
}

func intFromB64(b64 string) int {
	return int(bigIntFromB64(b64).Int64())
}

var n *big.Int = bigIntFromB64("n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw")
var e int = intFromB64("AQAB")
var d *big.Int = bigIntFromB64("bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78eiZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRldY7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-bMwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDjd18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOcOpBrQzwQ")
var p *big.Int = bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc")
var q *big.Int = bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc")

var TheKey rsa.PrivateKey = rsa.PrivateKey{
	PublicKey: rsa.PublicKey{N: n, E: e},
	D:         d,
	Primes:    []*big.Int{p, q},
}

var ident core.AcmeIdentifier = core.AcmeIdentifier{Type: core.IdentifierType("dns"), Value: "localhost"}

func simpleSrv(t *testing.T, token string, stopChan, waitChan chan bool) {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if strings.HasSuffix(r.URL.Path, "404") {
			http.NotFound(w, r)
		} else if strings.HasSuffix(r.URL.Path, "wrongtoken") {
			fmt.Fprintf(w, "wrongtoken")
		}
		fmt.Fprintf(w, "%s", token)
	})

	httpsServer := &http.Server{Addr: "localhost:5001"}
	conn, err := net.Listen("tcp", httpsServer.Addr)
	if err != nil {
		waitChan <- true
		t.Fatalf("Couldn't listen on %s: %s", httpsServer.Addr, err)
	}

	go func() {
		<-stopChan
		conn.Close()
	}()

	waitChan <- true
	t.Fatalf("%s", httpsServer.Serve(conn))
}

func dvsniSrv(t *testing.T, R, S []byte, waitChan chan bool) {
	RS := append(R, S...)
	z := sha256.Sum256(RS)
	zName := fmt.Sprintf("%064x.acme.invalid", z)
	template := &x509.Certificate{
		SerialNumber: big.NewInt(1337),
		Subject: pkix.Name{
			Organization: []string{"tests"},
		},
		NotBefore: time.Now(),
		NotAfter: time.Now().AddDate(0, 0, 1),

		KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,

		DNSNames: []string{zName},
	}

	certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
	cert := &tls.Certificate{
		Certificate: [][]byte{certBytes},
		PrivateKey: &TheKey,
	}

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{*cert},
		ClientAuth: tls.NoClientCert,
		GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
			fmt.Println(clientHello)
			return cert, nil
		},
		NextProtos: []string{"http/1.1"},
	}

	httpsServer := &http.Server{Addr: "localhost:5001"}
	conn, err := net.Listen("tcp", httpsServer.Addr)
	if err != nil {
		waitChan <- true
		t.Fatalf("Couldn't listen on %s: %s", httpsServer.Addr, err)
	}
	tlsListener := tls.NewListener(conn, tlsConfig)
	waitChan <- true
	t.Fatalf("%s", httpsServer.Serve(tlsListener))
}

func TestSimpleHttps(t *testing.T) {
	va := NewValidationAuthorityImpl(true)

	chall := core.Challenge{Path: "test", Token: "THETOKEN"}

	invalidChall := va.validateSimpleHTTPS(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	stopChan := make(chan bool, 1)
	waitChan := make(chan bool, 1)
	go simpleSrv(t, "THETOKEN", stopChan, waitChan)

	finChall := va.validateSimpleHTTPS(ident, chall)
	test.AssertEquals(t, finChall.Status, core.StatusValid)

	chall.Path = "404"
	invalidChall = va.validateSimpleHTTPS(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	chall.Path = "wrongtoken"
	invalidChall = va.validateSimpleHTTPS(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	chall.Path = ""
	invalidChall = va.validateSimpleHTTPS(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	chall.Path = "validish"
	invalidChall = va.validateSimpleHTTPS(core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	stopChan <- true
}

func TestDvsni(t *testing.T) {
	va := NewValidationAuthorityImpl(true)

	a := []byte{1,2,3,4,5,6,7,8,9,0}
	ba := core.B64enc(a)
	chall := core.Challenge{R: ba, S: ba}

	invalidChall := va.validateDvsni(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	waitChan := make(chan bool, 1)
	go dvsniSrv(t, a, a, waitChan)
	<-waitChan

	finChall := va.validateDvsni(ident, chall)
	test.AssertEquals(t, finChall.Status, core.StatusValid)

	chall.R = ba[5:]
	invalidChall = va.validateDvsni(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	invalidChall = va.validateSimpleHTTPS(core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)

	chall.R = ba
	chall.S = "!@#"
	invalidChall = va.validateDvsni(ident, chall)
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
}

type MockRegistrationAuthority struct{}

func (ra *MockRegistrationAuthority) NewRegistration(reg core.Registration, jwk jose.JsonWebKey) (core.Registration, error) {
	return reg, nil
}

func (ra *MockRegistrationAuthority) NewAuthorization(authz core.Authorization, jwk jose.JsonWebKey) (core.Authorization, error) {
	return authz, nil
}

func (ra *MockRegistrationAuthority) NewCertificate(req core.CertificateRequest, jwk jose.JsonWebKey) (core.Certificate, error) {
	return core.Certificate{}, nil
}

func (ra *MockRegistrationAuthority) UpdateRegistration(reg core.Registration, updated core.Registration) (core.Registration, error) {
	return reg, nil
}

func (ra *MockRegistrationAuthority) UpdateAuthorization(authz core.Authorization, foo int, challenge core.Challenge) (core.Authorization, error) {
	return authz, nil
}

func (ra *MockRegistrationAuthority) RevokeCertificate(cert x509.Certificate) error {
	return nil
}

func (ra *MockRegistrationAuthority) OnValidationUpdate(authz core.Authorization) {
}
<|endoftext|>


<commit_before><commit_msg>Added comment to highlight that Reset() can be called<commit_after><|endoftext|>


<commit_before>// tree.go
//
// Copyright (c) 2016, Ayke van Laethem
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Package tree specifies a directory tree interface, for use by the
// synchronization algorithm. Many different kinds of backends could be created
// for this interface, like local filesystem, SFTP, a remote dtsync process over
// SSH, maybe even MTP.
package tree

import (
	"io"
	"sort"
	"strconv"
	"strings"
	"time"
)

// The type used for TYPE_* constants
type Type int

// Constants used as Type() return values.
//
// See also: tree/remote/messages.proto
const (
	TYPE_UNKNOWN   Type = 0
	TYPE_REGULAR   Type = 1
	TYPE_DIRECTORY Type = 2
	TYPE_SYMLINK   Type = 3
)

func (t Type) Char() string {
	switch t {
	case TYPE_REGULAR:
		return "f"
	case TYPE_DIRECTORY:
		return "d"
	case TYPE_SYMLINK:
		return "l"
	default:
		return "?"
	}
}

// Tree is an abstraction layer over various types of trees. It tries to be as
// generic as possible, making it possible to synchronize varying types of trees
// (filesystem, sftp, remote filesystem, mtp, etc.). It may even be possible to
// use it for very different trees, e.g. browser bookmark trees.
type Tree interface {
	// Close clears all resources allocated for this tree, if any. For example,
	// it closes a network connection.
	Close() error

	// mkdir: create a directory in this directory with the given name. The
	// modification time is unknown and will likely be the current time.
	CreateDir(name string, parent FileInfo) (FileInfo, error)
	// Remove removes the indicated file, but checks the fingerprint first. It
	// returns ErrChanged if the metadata does not match, or another error if
	// the remove failed.
	Remove(file FileInfo) (parentInfo FileInfo, err error)

	// Get this file. This only exists to read the status file, not to implement
	// copying in the syncer!
	GetFile(name string) (io.ReadCloser, error)
	// SetFile is analogous to GetFile.
	SetFile(name string) (io.WriteCloser, error)
}

// FileTree is a filesystem (like) tree, with normal mkdir and open calls, and
// objects containing data blobs.
type FileTree interface {
	Tree

	// Open a file for writing with the specified name in the parent, with the
	// metadata as specified in 'source'. Calling Finish() should create the
	// file atomically at the target location. For example, by writing to it at
	// a temporary location and moving it to the destination.
	// Implementations should check that the target doesn't yet exist.
	CreateFile(name string, parent, source FileInfo) (Copier, error)

	// Update this file. May do about the same as CreateFile.
	UpdateFile(file, source FileInfo) (Copier, error)

	// Create a symbolic link.
	CreateSymlink(name string, parent, source FileInfo, contents string) (FileInfo, FileInfo, error)

	// Update a symbolic link.
	UpdateSymlink(file, source FileInfo, contents string) (FileInfo, FileInfo, error)

	// Read the contents (target path) of a symbolic link.
	ReadSymlink(file FileInfo) (string, error)

	// CopySource returns a io.ReadCloser, but checks first whether the FileInfo
	// matches.
	CopySource(info FileInfo) (io.ReadCloser, error)
}

type RemoteTree interface {
	Tree

	// RemoteScan issues a scan on the other end, and returns a reader for the
	// newly created .dtsync file.
	//
	// Implementations may either scan the whole tree and return once finished,
	// or start scanning while returning the data as far as they've scanned.
	//
	// The sendOptions are sent to the remote scanner (scan will start once
	// received), and recvOptions is a channel from which the options sent by
	// the remote can be read.
	RemoteScan(sendOptions, recvOptions chan ScanOptions) (io.Reader, error)
}

type LocalTree interface {
	// Root returns the root entry of this tree.
	Root() Entry
}

type LocalFileTree interface {
	LocalTree
	FileTree
}

// Entry is one object tree, e.g. a file or directory. It can also be something
// else, like bookmarks in a browser, or an email message.
type Entry interface {
	// Name returns the name of this file.
	Name() string
	// RelativePath returns the path relative to the root of this tree.
	RelativePath() []string
	// Type returns the file type (see the Type constants above)
	Type() Type
	// ModTime returns the last modification time.
	ModTime() time.Time
	// Size returns the filesize for regular files. For others, it's
	// implementation-dependent.
	Size() int64
	// Hash calculates the blake2b hash of the file and returns it.
	Hash() ([]byte, error)
	// Info returns a FileInfo without a hash from this Entry.
	// It must be fast (it shouldn't do any I/O).
	Info() FileInfo
	// FullInfo returns a FileInfo with hash from this Entry.
	// The FileInfo comes from Info(), but it has the hash set for regular
	// files.
	FullInfo() (FileInfo, error)
	// Tree returns the tree this entry belongs to.
	Tree() Tree
	// Return a list of children, in alphabetic order.
	// Returns an error when this is not a directory.
	List() ([]Entry, error)
}

// Copy copies the object indicated by the source to the target in the other
// tree. It returns the file info of the copied file (which theoretically may be
// different from the hash in the provided FileInfo) and it's parent.  It may
// also return an error when the source metadata (e.g. modtime) does not match
// the actual file.
// Before the actual copy, the fingerprint of the source is compared with the
// stat results of the source.
func Copy(this, other Tree, source, targetParent FileInfo) (info FileInfo, parentInfo FileInfo, err error) {
	thisFileTree, ok := this.(FileTree)
	if !ok {
		return nil, nil, ErrNotImplemented("source in Copy not a FileTree")
	}
	otherFileTree, ok := other.(FileTree)
	if !ok {
		return nil, nil, ErrNotImplemented("target in Copy not a FileTree")
	}

	switch source.Type() {
	case TYPE_REGULAR:
		inf, err := thisFileTree.CopySource(source)
		if err != nil {
			return nil, nil, err
		}
		defer inf.Close()

		outf, err := otherFileTree.CreateFile(source.Name(), targetParent, source)
		if err != nil {
			return nil, nil, err
		}
		_, err = io.Copy(outf, inf)
		if err != nil {
			// TODO outf.Revert()?
			return nil, nil, err
		}
		// outf.Finish() usually does an fsync and rename (and closes the file)
		return outf.Finish()

	case TYPE_SYMLINK:
		link, err := thisFileTree.ReadSymlink(source)
		if err != nil {
			return nil, nil, err
		}

		return otherFileTree.CreateSymlink(source.Name(), targetParent, source, link)

	default:
		return nil, nil, ErrNotImplemented("copying a non-regular file")
	}
}

// Update replaces this file with the contents and modtime of the other file.
// It is very similar to Copy.
func Update(this, other Tree, source, target FileInfo) (FileInfo, FileInfo, error) {
	thisFileTree, ok := this.(FileTree)
	if !ok {
		return nil, nil, ErrNotImplemented("source in Update not a FileTree")
	}
	otherFileTree, ok := other.(FileTree)
	if !ok {
		return nil, nil, ErrNotImplemented("target in Update not a FileTree")
	}

	switch source.Type() {
	case TYPE_REGULAR:
		inf, err := thisFileTree.CopySource(source)
		if err != nil {
			return nil, nil, err
		}
		defer inf.Close()

		outf, err := otherFileTree.UpdateFile(target, source)
		if err != nil {
			return nil, nil, err
		}

		_, err = io.Copy(outf, inf)
		if err != nil {
			return nil, nil, err
		}

		return outf.Finish()

	case TYPE_SYMLINK:
		link, err := thisFileTree.ReadSymlink(source)
		if err != nil {
			return nil, nil, err
		}

		return otherFileTree.UpdateSymlink(target, source, link)

	case TYPE_UNKNOWN:
		return nil, nil, ErrNotImplemented("Update: file type is TYPE_UNKNOWN")

	default:
		return nil, nil, ErrNotImplemented("Update: unknown file type")
	}
}

// FileInfo is like os.FileInfo, but specific for the Tree interface.
// It is implemented by dtdiff.Entry.
type FileInfo interface {
	// Name returns the name of this file.
	Name() string
	// RelativePath returns the path relative to the root of this tree. The last
	// element is always the same as the name.
	RelativePath() []string
	// Size returns the filesize for regular files. For others, it's
	// implementation-dependent.
	Size() int64
	// Type returns the file type (see the Type constants above)
	Type() Type
	// ModTime returns the last modification time.
	ModTime() time.Time
	// Hash returns the blake2b hash of the file, or nil if no hash is known
	// (e.g. for directories).
	Hash() []byte
}

type FileInfoStruct struct {
	path     []string
	fileType Type
	modTime  time.Time
	size     int64
	hash     []byte
}

func NewFileInfo(path []string, fileType Type, modTime time.Time, size int64, hash []byte) FileInfo {
	return &FileInfoStruct{
		path:     path,
		fileType: fileType,
		modTime:  modTime,
		size:     size,
		hash:     hash,
	}
}

func (fi *FileInfoStruct) Name() string {
	if len(fi.path) == 0 {
		return ""
	}
	return fi.path[len(fi.path)-1]
}

func (fi *FileInfoStruct) RelativePath() []string {
	return fi.path
}

func (fi *FileInfoStruct) Hash() []byte {
	return fi.hash
}

func (fi *FileInfoStruct) ModTime() time.Time {
	return fi.modTime
}

func (fi *FileInfoStruct) Size() int64 {
	return fi.size
}

func (fi *FileInfoStruct) Type() Type {
	return fi.fileType
}

func (fi *FileInfoStruct) String() string {
	return "FileInfoStruct{" + strings.Join(fi.path, "/") + "," + Fingerprint(fi) + "}"
}

// Fingerprint returns a fingerprint for this file.
func Fingerprint(e FileInfo) string {
	parts := make([]string, 0, 4)
	modTime := e.ModTime().UTC().Format(time.RFC3339Nano)
	parts = append(parts, e.Type().Char(), modTime)
	if e.Type() == TYPE_REGULAR {
		parts = append(parts, strconv.FormatInt(e.Size(), 10))
	}
	return strings.Join(parts, "/")
}

// MatchFingerprint returns whether two FileInfos would have the same
// fingerprint without actually creating the fingerprint.
func MatchFingerprint(info1, info2 FileInfo) bool {
	if info1.Type() != info2.Type() {
		return false
	}
	if info1.Type() == TYPE_REGULAR && info1.Size() != info2.Size() {
		return false
	}
	// Equal() only looks at the time instant, not the timezone.
	return info1.ModTime().Equal(info2.ModTime())
}

type FingerprintInfo struct {
	Type    Type
	ModTime time.Time
	Size    int64
}

// ParseFingerprint parses the given fingerprint.
func ParseFingerprint(fingerprint string) (*FingerprintInfo, error) {
	info := &FingerprintInfo{}

	parts := strings.Split(fingerprint, "/")
	if len(parts) < 2 {
		panic(fingerprint)
		return info, ErrParsingFingerprint
	}

	switch parts[0] {
	case "f":
		info.Type = TYPE_REGULAR
	case "d":
		info.Type = TYPE_DIRECTORY
	case "l":
		info.Type = TYPE_SYMLINK
	default:
		info.Type = TYPE_UNKNOWN
	}

	modTime, err := time.Parse(time.RFC3339Nano, parts[1])
	if err != nil {
		return info, err
	}
	info.ModTime = modTime

	if len(parts) < 3 {
		return info, nil
	}

	size, err := strconv.ParseInt(parts[2], 10, 64)
	if err != nil {
		return info, err
	}
	info.Size = size
	return info, nil
}

// EntrySlice is a sortable list of Entries, sorting in incrasing order by the
// name (Entry.Name()).
type EntrySlice []Entry

func (es EntrySlice) Len() int {
	return len(es)
}

func (es EntrySlice) Less(i, j int) bool {
	return es[i].Name() < es[j].Name()
}

func (es EntrySlice) Swap(i, j int) {
	es[i], es[j] = es[j], es[i]
}

// SortEntries sorts the given slice by name.
func SortEntries(slice []Entry) {
	es := EntrySlice(slice)
	sort.Sort(es)
}

// Copier is returned by the Copy and Update methods. Calling Finish() closes
// the file, possibly moves it to the destination location, and returns the
// file's and the file parent's FileInfo. Calling Cancel() tries to undo writing
// the file.
// It also implements io.Writer.
type Copier interface {
	Write([]byte) (n int, err error)
	Finish() (info FileInfo, parentInfo FileInfo, err error)
	Cancel() error
}

// ScanOptions sets some options in a tree.
type ScanOptions interface {
	Ignore() []string
}

// ScanOptionsStruct implements ScanOptions and provides an ignore list to the
// other replica.
type scanOptions []string

func NewScanOptions(ignore []string) ScanOptions {
	return scanOptions(ignore)
}

// Ignore returns all patterns to ignore, to send to the other replica.
func (s scanOptions) Ignore() []string {
	return s
}
<commit_msg>tree: Copy: nicer error on unknown file type<commit_after>// tree.go
//
// Copyright (c) 2016, Ayke van Laethem
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Package tree specifies a directory tree interface, for use by the
// synchronization algorithm. Many different kinds of backends could be created
// for this interface, like local filesystem, SFTP, a remote dtsync process over
// SSH, maybe even MTP.
package tree

import (
	"io"
	"sort"
	"strconv"
	"strings"
	"time"
)

// The type used for TYPE_* constants
type Type int

// Constants used as Type() return values.
//
// See also: tree/remote/messages.proto
const (
	TYPE_UNKNOWN   Type = 0
	TYPE_REGULAR   Type = 1
	TYPE_DIRECTORY Type = 2
	TYPE_SYMLINK   Type = 3
)

func (t Type) Char() string {
	switch t {
	case TYPE_REGULAR:
		return "f"
	case TYPE_DIRECTORY:
		return "d"
	case TYPE_SYMLINK:
		return "l"
	default:
		return "?"
	}
}

// Tree is an abstraction layer over various types of trees. It tries to be as
// generic as possible, making it possible to synchronize varying types of trees
// (filesystem, sftp, remote filesystem, mtp, etc.). It may even be possible to
// use it for very different trees, e.g. browser bookmark trees.
type Tree interface {
	// Close clears all resources allocated for this tree, if any. For example,
	// it closes a network connection.
	Close() error

	// mkdir: create a directory in this directory with the given name. The
	// modification time is unknown and will likely be the current time.
	CreateDir(name string, parent FileInfo) (FileInfo, error)
	// Remove removes the indicated file, but checks the fingerprint first. It
	// returns ErrChanged if the metadata does not match, or another error if
	// the remove failed.
	Remove(file FileInfo) (parentInfo FileInfo, err error)

	// Get this file. This only exists to read the status file, not to implement
	// copying in the syncer!
	GetFile(name string) (io.ReadCloser, error)
	// SetFile is analogous to GetFile.
	SetFile(name string) (io.WriteCloser, error)
}

// FileTree is a filesystem (like) tree, with normal mkdir and open calls, and
// objects containing data blobs.
type FileTree interface {
	Tree

	// Open a file for writing with the specified name in the parent, with the
	// metadata as specified in 'source'. Calling Finish() should create the
	// file atomically at the target location. For example, by writing to it at
	// a temporary location and moving it to the destination.
	// Implementations should check that the target doesn't yet exist.
	CreateFile(name string, parent, source FileInfo) (Copier, error)

	// Update this file. May do about the same as CreateFile.
	UpdateFile(file, source FileInfo) (Copier, error)

	// Create a symbolic link.
	CreateSymlink(name string, parent, source FileInfo, contents string) (FileInfo, FileInfo, error)

	// Update a symbolic link.
	UpdateSymlink(file, source FileInfo, contents string) (FileInfo, FileInfo, error)

	// Read the contents (target path) of a symbolic link.
	ReadSymlink(file FileInfo) (string, error)

	// CopySource returns a io.ReadCloser, but checks first whether the FileInfo
	// matches.
	CopySource(info FileInfo) (io.ReadCloser, error)
}

type RemoteTree interface {
	Tree

	// RemoteScan issues a scan on the other end, and returns a reader for the
	// newly created .dtsync file.
	//
	// Implementations may either scan the whole tree and return once finished,
	// or start scanning while returning the data as far as they've scanned.
	//
	// The sendOptions are sent to the remote scanner (scan will start once
	// received), and recvOptions is a channel from which the options sent by
	// the remote can be read.
	RemoteScan(sendOptions, recvOptions chan ScanOptions) (io.Reader, error)
}

type LocalTree interface {
	// Root returns the root entry of this tree.
	Root() Entry
}

type LocalFileTree interface {
	LocalTree
	FileTree
}

// Entry is one object tree, e.g. a file or directory. It can also be something
// else, like bookmarks in a browser, or an email message.
type Entry interface {
	// Name returns the name of this file.
	Name() string
	// RelativePath returns the path relative to the root of this tree.
	RelativePath() []string
	// Type returns the file type (see the Type constants above)
	Type() Type
	// ModTime returns the last modification time.
	ModTime() time.Time
	// Size returns the filesize for regular files. For others, it's
	// implementation-dependent.
	Size() int64
	// Hash calculates the blake2b hash of the file and returns it.
	Hash() ([]byte, error)
	// Info returns a FileInfo without a hash from this Entry.
	// It must be fast (it shouldn't do any I/O).
	Info() FileInfo
	// FullInfo returns a FileInfo with hash from this Entry.
	// The FileInfo comes from Info(), but it has the hash set for regular
	// files.
	FullInfo() (FileInfo, error)
	// Tree returns the tree this entry belongs to.
	Tree() Tree
	// Return a list of children, in alphabetic order.
	// Returns an error when this is not a directory.
	List() ([]Entry, error)
}

// Copy copies the object indicated by the source to the target in the other
// tree. It returns the file info of the copied file (which theoretically may be
// different from the hash in the provided FileInfo) and it's parent.  It may
// also return an error when the source metadata (e.g. modtime) does not match
// the actual file.
// Before the actual copy, the fingerprint of the source is compared with the
// stat results of the source.
func Copy(this, other Tree, source, targetParent FileInfo) (info FileInfo, parentInfo FileInfo, err error) {
	thisFileTree, ok := this.(FileTree)
	if !ok {
		return nil, nil, ErrNotImplemented("source in Copy not a FileTree")
	}
	otherFileTree, ok := other.(FileTree)
	if !ok {
		return nil, nil, ErrNotImplemented("target in Copy not a FileTree")
	}

	switch source.Type() {
	case TYPE_REGULAR:
		inf, err := thisFileTree.CopySource(source)
		if err != nil {
			return nil, nil, err
		}
		defer inf.Close()

		outf, err := otherFileTree.CreateFile(source.Name(), targetParent, source)
		if err != nil {
			return nil, nil, err
		}
		_, err = io.Copy(outf, inf)
		if err != nil {
			// TODO outf.Revert()?
			return nil, nil, err
		}
		// outf.Finish() usually does an fsync and rename (and closes the file)
		return outf.Finish()

	case TYPE_SYMLINK:
		link, err := thisFileTree.ReadSymlink(source)
		if err != nil {
			return nil, nil, err
		}

		return otherFileTree.CreateSymlink(source.Name(), targetParent, source, link)

	case TYPE_UNKNOWN:
		return nil, nil, ErrNotImplemented("Copy: file type is TYPE_UNKNOWN")

	default:
		return nil, nil, ErrNotImplemented("Copy: unknown file type")
	}
}

// Update replaces this file with the contents and modtime of the other file.
// It is very similar to Copy.
func Update(this, other Tree, source, target FileInfo) (FileInfo, FileInfo, error) {
	thisFileTree, ok := this.(FileTree)
	if !ok {
		return nil, nil, ErrNotImplemented("source in Update not a FileTree")
	}
	otherFileTree, ok := other.(FileTree)
	if !ok {
		return nil, nil, ErrNotImplemented("target in Update not a FileTree")
	}

	switch source.Type() {
	case TYPE_REGULAR:
		inf, err := thisFileTree.CopySource(source)
		if err != nil {
			return nil, nil, err
		}
		defer inf.Close()

		outf, err := otherFileTree.UpdateFile(target, source)
		if err != nil {
			return nil, nil, err
		}

		_, err = io.Copy(outf, inf)
		if err != nil {
			return nil, nil, err
		}

		return outf.Finish()

	case TYPE_SYMLINK:
		link, err := thisFileTree.ReadSymlink(source)
		if err != nil {
			return nil, nil, err
		}

		return otherFileTree.UpdateSymlink(target, source, link)

	case TYPE_UNKNOWN:
		return nil, nil, ErrNotImplemented("Update: file type is TYPE_UNKNOWN")

	default:
		return nil, nil, ErrNotImplemented("Update: unknown file type")
	}
}

// FileInfo is like os.FileInfo, but specific for the Tree interface.
// It is implemented by dtdiff.Entry.
type FileInfo interface {
	// Name returns the name of this file.
	Name() string
	// RelativePath returns the path relative to the root of this tree. The last
	// element is always the same as the name.
	RelativePath() []string
	// Size returns the filesize for regular files. For others, it's
	// implementation-dependent.
	Size() int64
	// Type returns the file type (see the Type constants above)
	Type() Type
	// ModTime returns the last modification time.
	ModTime() time.Time
	// Hash returns the blake2b hash of the file, or nil if no hash is known
	// (e.g. for directories).
	Hash() []byte
}

type FileInfoStruct struct {
	path     []string
	fileType Type
	modTime  time.Time
	size     int64
	hash     []byte
}

func NewFileInfo(path []string, fileType Type, modTime time.Time, size int64, hash []byte) FileInfo {
	return &FileInfoStruct{
		path:     path,
		fileType: fileType,
		modTime:  modTime,
		size:     size,
		hash:     hash,
	}
}

func (fi *FileInfoStruct) Name() string {
	if len(fi.path) == 0 {
		return ""
	}
	return fi.path[len(fi.path)-1]
}

func (fi *FileInfoStruct) RelativePath() []string {
	return fi.path
}

func (fi *FileInfoStruct) Hash() []byte {
	return fi.hash
}

func (fi *FileInfoStruct) ModTime() time.Time {
	return fi.modTime
}

func (fi *FileInfoStruct) Size() int64 {
	return fi.size
}

func (fi *FileInfoStruct) Type() Type {
	return fi.fileType
}

func (fi *FileInfoStruct) String() string {
	return "FileInfoStruct{" + strings.Join(fi.path, "/") + "," + Fingerprint(fi) + "}"
}

// Fingerprint returns a fingerprint for this file.
func Fingerprint(e FileInfo) string {
	parts := make([]string, 0, 4)
	modTime := e.ModTime().UTC().Format(time.RFC3339Nano)
	parts = append(parts, e.Type().Char(), modTime)
	if e.Type() == TYPE_REGULAR {
		parts = append(parts, strconv.FormatInt(e.Size(), 10))
	}
	return strings.Join(parts, "/")
}

// MatchFingerprint returns whether two FileInfos would have the same
// fingerprint without actually creating the fingerprint.
func MatchFingerprint(info1, info2 FileInfo) bool {
	if info1.Type() != info2.Type() {
		return false
	}
	if info1.Type() == TYPE_REGULAR && info1.Size() != info2.Size() {
		return false
	}
	// Equal() only looks at the time instant, not the timezone.
	return info1.ModTime().Equal(info2.ModTime())
}

type FingerprintInfo struct {
	Type    Type
	ModTime time.Time
	Size    int64
}

// ParseFingerprint parses the given fingerprint.
func ParseFingerprint(fingerprint string) (*FingerprintInfo, error) {
	info := &FingerprintInfo{}

	parts := strings.Split(fingerprint, "/")
	if len(parts) < 2 {
		panic(fingerprint)
		return info, ErrParsingFingerprint
	}

	switch parts[0] {
	case "f":
		info.Type = TYPE_REGULAR
	case "d":
		info.Type = TYPE_DIRECTORY
	case "l":
		info.Type = TYPE_SYMLINK
	default:
		info.Type = TYPE_UNKNOWN
	}

	modTime, err := time.Parse(time.RFC3339Nano, parts[1])
	if err != nil {
		return info, err
	}
	info.ModTime = modTime

	if len(parts) < 3 {
		return info, nil
	}

	size, err := strconv.ParseInt(parts[2], 10, 64)
	if err != nil {
		return info, err
	}
	info.Size = size
	return info, nil
}

// EntrySlice is a sortable list of Entries, sorting in incrasing order by the
// name (Entry.Name()).
type EntrySlice []Entry

func (es EntrySlice) Len() int {
	return len(es)
}

func (es EntrySlice) Less(i, j int) bool {
	return es[i].Name() < es[j].Name()
}

func (es EntrySlice) Swap(i, j int) {
	es[i], es[j] = es[j], es[i]
}

// SortEntries sorts the given slice by name.
func SortEntries(slice []Entry) {
	es := EntrySlice(slice)
	sort.Sort(es)
}

// Copier is returned by the Copy and Update methods. Calling Finish() closes
// the file, possibly moves it to the destination location, and returns the
// file's and the file parent's FileInfo. Calling Cancel() tries to undo writing
// the file.
// It also implements io.Writer.
type Copier interface {
	Write([]byte) (n int, err error)
	Finish() (info FileInfo, parentInfo FileInfo, err error)
	Cancel() error
}

// ScanOptions sets some options in a tree.
type ScanOptions interface {
	Ignore() []string
}

// ScanOptionsStruct implements ScanOptions and provides an ignore list to the
// other replica.
type scanOptions []string

func NewScanOptions(ignore []string) ScanOptions {
	return scanOptions(ignore)
}

// Ignore returns all patterns to ignore, to send to the other replica.
func (s scanOptions) Ignore() []string {
	return s
}
<|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 web

import (
	"crypto/tls"
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/control-center/serviced/proxy"

	"github.com/control-center/serviced/coordinator/client"
	"github.com/control-center/serviced/dao"
	domainService "github.com/control-center/serviced/domain/service"
	"github.com/control-center/serviced/utils"
	"github.com/control-center/serviced/zzk"
	"github.com/control-center/serviced/zzk/registry"
	"github.com/control-center/serviced/zzk/service"
	"github.com/zenoss/glog"

	"net/http"
)

var (
	allportsLock sync.RWMutex
	allports     map[string]chan bool // map of port number to channel that destroys the server
	cpDao        dao.ControlPlane
)

func init() {
	allports = make(map[string]chan bool)
}

// Removes the port from our local cache and updates the service so the UI will flip to "disabled".
//  Only needs to be called if the port is being disabled unexpectedly due to an error
func disablePort(node service.ServicePublicEndpointNode) {
	//TODO: Add control plane methods to enable/disable public endpoints so we don't have to do a GetService and then UpdateService

	// remove the port from our local cache
	delete(allports, node.Name)

	// find the endpoint that matches this port number for this service (there will only be 1)
	var myService domainService.Service
	var myEndpoint domainService.ServiceEndpoint
	var unused int
	cpDao.GetService(node.ServiceID, &myService)
	for _, endpoint := range myService.Endpoints {
		for _, endpointPort := range endpoint.PortList {
			if endpointPort.PortAddr == node.Name {
				myEndpoint = endpoint
			}
		}
	}

	// disable port
	myService.EnablePort(myEndpoint.Name, node.Name, false)
	if err := cpDao.UpdateService(myService, &unused); err != nil {
		glog.Errorf("Error in disablePort(%s:%s): %v", node.ServiceID, node.Name, err)
	}
}

func (sc *ServiceConfig) ServePublicPorts(shutdown <-chan (interface{}), dao dao.ControlPlane) {
	cpDao = dao
	go sc.syncAllPublicPorts(shutdown)
}

// For HTTPS connections, we need to inject a header for downstream servers.
func (sc *ServiceConfig) createPortHttpServer(node service.ServicePublicEndpointNode) error {
	port := node.Name
	proto := node.Protocol
	useTLS := node.UseTLS

	glog.V(1).Infof("About to listen on port (%s) %s; UseTLS=%t", proto, port, useTLS)

	// Setup a handler for the port http(s) endpoint.  This differs from the
	// handler for cc/vhosts.
	httphandler := func(w http.ResponseWriter, r *http.Request) {
		glog.V(2).Infof("httphandler (port) handling request: %+v", r)

		pepKey := registry.GetPublicEndpointKey(node.Name, node.Type)
		pepEP, err := sc.getPublicEndpoint(string(pepKey))
		if err != nil {
			http.Error(w, err.Error(), http.StatusNotFound)
			return
		}

		rp := sc.getReverseProxy(pepEP.hostIP, sc.muxPort, pepEP.privateIP, pepEP.epPort, sc.muxTLS && (sc.muxPort > 0))
		glog.V(1).Infof("Time to set up %s public endpoint proxy for %v", pepKey, r.URL)

		// Set up the X-Forwarded-Proto header so that downstream servers know
		// the request originated as HTTPS.
		if _, found := r.Header["X-Forwarded-Proto"]; !found {
			r.Header.Set("X-Forwarded-Proto", proto)
		}

		rp.ServeHTTP(w, r)
		return
	}

	// Create a new port server with a default handler.
	portServer := http.NewServeMux()
	portServer.HandleFunc("/", httphandler)

	// HTTPS requires configuring the certificates for TLS.
	if useTLS {
		// Get the certificates and handle the error.
		glog.V(2).Infof("Getting TLS certificates for port: %s", port)
		certFile, keyFile, err := sc.getCertFiles()
		if err != nil {
			glog.Errorf("Error getting certificates for HTTPS port %s: %s", port, err)
			disablePort(node)
			return err
		}

		// Setup certificates and serve the requests.
		glog.V(2).Infof("Starting secure port endpoint server for port: %s", port)
		go func() {
			// This cipher suites and tls min version change may not be needed with golang 1.5
			// https://github.com/golang/go/issues/10094
			// https://github.com/golang/go/issues/9364
			config := &tls.Config{
				MinVersion:               utils.MinTLS(),
				PreferServerCipherSuites: true,
				CipherSuites:             utils.CipherSuites(),
			}
			server := &http.Server{Addr: port, TLSConfig: config, Handler: portServer}
			err := server.ListenAndServeTLS(certFile, keyFile)
			if err != nil {
				glog.Errorf("could not setup %s (port) webserver: %s", proto, err)
			}
		}()
	} else {
		// HTTP just needs a request server.
		glog.V(2).Infof("Starting port endpoint server for port: %s", port)
		go func() {
			server := &http.Server{Addr: port, Handler: portServer}
			err := server.ListenAndServe()
			if err != nil {
				glog.Errorf("could not setup %s (port) webserver: %s", proto, err)
			}
		}()
	}

	return nil
}

func (sc *ServiceConfig) createPublicPortServer(node service.ServicePublicEndpointNode, stopChan chan bool, shutdown <-chan (interface{})) error {
	port := node.Name
	useTLS := node.UseTLS
	proto := node.Protocol

	// Declare our listener..
	var listener net.Listener
	var err error

	glog.V(1).Infof("About to listen on port %s; UseTLS=%t", port, useTLS)

	if proto == "https" || proto == "http" {
		// We have to set up an HttpListener to inject headers for downstram servers.
		return sc.createPortHttpServer(node)
	} else if useTLS {
		// Gather our certs files and handle the error.
		certFile, keyFile, err := sc.getCertFiles()
		if err != nil {
			glog.Errorf("Error getting certificates for TLS port %s: %s", port, err)
			disablePort(node)
			return err
		}

		// Create our certificate from the cert files (strings).
		glog.V(2).Infof("Loading certs from %s, %s", certFile, keyFile)
		cert, err := tls.LoadX509KeyPair(certFile, keyFile)
		if err != nil {
			glog.Errorf("Could not set up tls certificate for public endpoint on port %s for %s: %s", port, node.ServiceID, err)
			disablePort(node)
			return err
		}

		// The list of certs to use for our secure listener on this port.
		certs := []tls.Certificate { cert }

		// This cipher suites and tls min version change may not be needed with golang 1.5
		// https://github.com/golang/go/issues/10094
		// https://github.com/golang/go/issues/9364
		config := &tls.Config{
			MinVersion:               utils.MinTLS(),
			PreferServerCipherSuites: true,
			CipherSuites:             utils.CipherSuites(),
			Certificates:             certs,
		}

		glog.V(1).Infof("Listening with TLS")
		listener, err = tls.Listen("tcp", port, config)
	} else {
		glog.V(1).Infof("Listening without TLS")
		listener, err = net.Listen("tcp", port)
	}

	if err != nil {
		glog.Errorf("Could not setup TCP listener for port %s for public endpoint %s: %s", port, node.ServiceID, err)
		disablePort(node)
		return err
	}

	glog.Infof("Listening on port %s; UseTLS=%t", port, useTLS)

	go func() {
		for {
			// accept connection on public port
			localConn, err := listener.Accept()
			if err != nil {
				glog.V(1).Infof("Stopping accept on port %s", port)
				return
			}

			// lookup remote endpoint for this public port
			pepEPInfo, err := sc.getPublicEndpoint(fmt.Sprintf("%s-%d", node.Name, int(node.Type)))
			if err != nil {
				// This happens if an endpoint is accessed and the containers have died or not come up yet.
				glog.Errorf("Error retrieving public endpoint %s-%d: %s", node.Name, int(node.Type), err)
				// close the accepted connection and continue waiting for connections.
				if err := localConn.Close(); err != nil {
					glog.Errorf("Error closing client connection: %s", err)
				}
				continue
			}

			// setup remote connection
			var remoteAddr string
			_, isLocalContainer := sc.localAddrs[pepEPInfo.hostIP]
			if isLocalContainer {
				remoteAddr = fmt.Sprintf("%s:%d", pepEPInfo.privateIP, pepEPInfo.epPort)
			} else {
				remoteAddr = fmt.Sprintf("%s:%d", pepEPInfo.hostIP, sc.muxPort)
			}
			remoteConn, err := sc.getRemoteConnection(remoteAddr, isLocalContainer, sc.muxPort, pepEPInfo.privateIP, pepEPInfo.epPort, sc.muxTLS && (sc.muxPort > 0))
			if err != nil {
				glog.Errorf("Error getting remote connection for public endpoint %s-%d: %v", node.Name, int(node.Type), err)
				continue
			}

			glog.V(2).Infof("Established remote connection to %s", remoteConn.RemoteAddr())

			// Serve proxied requests/responses.  We pass our own port stop channel so that
			// all proxy loops end when our port is shutdown.
			go proxy.ProxyLoop(localConn, remoteConn, stopChan)
		}
	}()

	go func() {
		// Wait for shutdown, then kill all your connections
		select {
		case <-shutdown:
			// Received an application shutdown. Close the port channel to halt all proxy loops.
			glog.Infof("Shutting down port %s", port)
			close(stopChan)
		case <-stopChan:
		}

		listener.Close()
		glog.Infof("Closed port %s", port)
		return
	}()

	return nil
}

func (sc *ServiceConfig) syncAllPublicPorts(shutdown <-chan interface{}) error {
	rootConn, err := zzk.GetLocalConnection("/")
	if err != nil {
		glog.Errorf("syncAllPublicPorts - Error getting root zk connection: %v", err)
		return err
	}

	cancelChan := make(chan interface{})
	zkServicePEPService := service.ZKServicePublicEndpoints

	syncPorts := func(conn client.Connection, parentPath string, childIDs ...string) {
		allportsLock.Lock()
		defer allportsLock.Unlock()

		glog.V(1).Infof("syncPorts STARTING for parentPath:%s childIDs:%v", parentPath, childIDs)

		// start all servers that have been not started and enabled
		newPorts := make(map[string]chan bool)
		for _, pepID := range childIDs {

			// The pepID is the ZK child key. Get the node so we have all of the node data.
			glog.V(1).Infof("zkServicePEPService: %s, pepID: %s", zkServicePEPService, pepID)
			nodePath := fmt.Sprintf("%s/%s", zkServicePEPService, pepID)
			var node service.ServicePublicEndpointNode
			err := rootConn.Get(nodePath, &node)
			if err != nil {
				glog.Errorf("Unable to get the ZK Node from PepID")
				continue
			}

			if node.Type == registry.EPTypePort && node.Enabled {
				port := node.Name
				stopChan, running := allports[port]

				if !running {
					// recently enabled port - port should be opened
					stopChan = make(chan bool)
					if err := sc.createPublicPortServer(node, stopChan, shutdown); err != nil {
						continue
					}
				}

				newPorts[port] = stopChan
			}
		}

		// stop all servers that have been deleted or disabled
		for port, stopChan := range allports {
			_, found := newPorts[port]
			if !found {
				glog.V(2).Infof("Stopping port server for port %s", port)
				close(stopChan)
				glog.Infof("Port server shut down for port %s", port)
			}
		}

		allports = newPorts
		glog.V(2).Infof("Portserver allports: %+v", allports)
	}

	for {
		glog.V(1).Infof("Running registry.WatchChildren for zookeeper path: %s", zkServicePEPService)
		err := registry.WatchChildren(rootConn, zkServicePEPService, cancelChan, syncPorts, pepWatchError)
		if err != nil {
			glog.V(1).Infof("Will retry in 10 seconds to WatchChildren(%s) due to error: %v", zkServicePEPService, err)
			<-time.After(time.Second * 10)
			continue
		}
		select {
		case <-shutdown:
			close(cancelChan)
			return nil
		default:
		}
	}
}
<commit_msg>Add endpoint address to ListenAndServe* error message<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 web

import (
	"crypto/tls"
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/control-center/serviced/proxy"

	"github.com/control-center/serviced/coordinator/client"
	"github.com/control-center/serviced/dao"
	domainService "github.com/control-center/serviced/domain/service"
	"github.com/control-center/serviced/utils"
	"github.com/control-center/serviced/zzk"
	"github.com/control-center/serviced/zzk/registry"
	"github.com/control-center/serviced/zzk/service"
	"github.com/zenoss/glog"

	"net/http"
)

var (
	allportsLock sync.RWMutex
	allports     map[string]chan bool // map of port number to channel that destroys the server
	cpDao        dao.ControlPlane
)

func init() {
	allports = make(map[string]chan bool)
}

// Removes the port from our local cache and updates the service so the UI will flip to "disabled".
//  Only needs to be called if the port is being disabled unexpectedly due to an error
func disablePort(node service.ServicePublicEndpointNode) {
	//TODO: Add control plane methods to enable/disable public endpoints so we don't have to do a GetService and then UpdateService

	// remove the port from our local cache
	delete(allports, node.Name)

	// find the endpoint that matches this port number for this service (there will only be 1)
	var myService domainService.Service
	var myEndpoint domainService.ServiceEndpoint
	var unused int
	cpDao.GetService(node.ServiceID, &myService)
	for _, endpoint := range myService.Endpoints {
		for _, endpointPort := range endpoint.PortList {
			if endpointPort.PortAddr == node.Name {
				myEndpoint = endpoint
			}
		}
	}

	// disable port
	myService.EnablePort(myEndpoint.Name, node.Name, false)
	if err := cpDao.UpdateService(myService, &unused); err != nil {
		glog.Errorf("Error in disablePort(%s:%s): %v", node.ServiceID, node.Name, err)
	}
}

func (sc *ServiceConfig) ServePublicPorts(shutdown <-chan (interface{}), dao dao.ControlPlane) {
	cpDao = dao
	go sc.syncAllPublicPorts(shutdown)
}

// For HTTPS connections, we need to inject a header for downstream servers.
func (sc *ServiceConfig) createPortHttpServer(node service.ServicePublicEndpointNode) error {
	port := node.Name
	proto := node.Protocol
	useTLS := node.UseTLS

	glog.V(1).Infof("About to listen on port (%s) %s; UseTLS=%t", proto, port, useTLS)

	// Setup a handler for the port http(s) endpoint.  This differs from the
	// handler for cc/vhosts.
	httphandler := func(w http.ResponseWriter, r *http.Request) {
		glog.V(2).Infof("httphandler (port) handling request: %+v", r)

		pepKey := registry.GetPublicEndpointKey(node.Name, node.Type)
		pepEP, err := sc.getPublicEndpoint(string(pepKey))
		if err != nil {
			http.Error(w, err.Error(), http.StatusNotFound)
			return
		}

		rp := sc.getReverseProxy(pepEP.hostIP, sc.muxPort, pepEP.privateIP, pepEP.epPort, sc.muxTLS && (sc.muxPort > 0))
		glog.V(1).Infof("Time to set up %s public endpoint proxy for %v", pepKey, r.URL)

		// Set up the X-Forwarded-Proto header so that downstream servers know
		// the request originated as HTTPS.
		if _, found := r.Header["X-Forwarded-Proto"]; !found {
			r.Header.Set("X-Forwarded-Proto", proto)
		}

		rp.ServeHTTP(w, r)
		return
	}

	// Create a new port server with a default handler.
	portServer := http.NewServeMux()
	portServer.HandleFunc("/", httphandler)

	// HTTPS requires configuring the certificates for TLS.
	if useTLS {
		// Get the certificates and handle the error.
		glog.V(2).Infof("Getting TLS certificates for port: %s", port)
		certFile, keyFile, err := sc.getCertFiles()
		if err != nil {
			glog.Errorf("Error getting certificates for HTTPS port %s: %s", port, err)
			disablePort(node)
			return err
		}

		// Setup certificates and serve the requests.
		glog.V(2).Infof("Starting secure port endpoint server for port: %s", port)
		go func() {
			// This cipher suites and tls min version change may not be needed with golang 1.5
			// https://github.com/golang/go/issues/10094
			// https://github.com/golang/go/issues/9364
			config := &tls.Config{
				MinVersion:               utils.MinTLS(),
				PreferServerCipherSuites: true,
				CipherSuites:             utils.CipherSuites(),
			}
			server := &http.Server{Addr: port, TLSConfig: config, Handler: portServer}
			err := server.ListenAndServeTLS(certFile, keyFile)
			if err != nil {
				glog.Errorf("could not set up %s server for port endpoint %s: %s", proto, port, err)
			}
		}()
	} else {
		// HTTP just needs a request server.
		glog.V(2).Infof("Starting port endpoint server for port: %s", port)
		go func() {
			server := &http.Server{Addr: port, Handler: portServer}
			err := server.ListenAndServe()
			if err != nil {
				glog.Errorf("could not set up %s server for port endpoint %s: %s", proto, port, err)
			}
		}()
	}

	return nil
}

func (sc *ServiceConfig) createPublicPortServer(node service.ServicePublicEndpointNode, stopChan chan bool, shutdown <-chan (interface{})) error {
	port := node.Name
	useTLS := node.UseTLS
	proto := node.Protocol

	// Declare our listener..
	var listener net.Listener
	var err error

	glog.V(1).Infof("About to listen on port %s; UseTLS=%t", port, useTLS)

	if proto == "https" || proto == "http" {
		// We have to set up an HttpListener to inject headers for downstram servers.
		return sc.createPortHttpServer(node)
	} else if useTLS {
		// Gather our certs files and handle the error.
		certFile, keyFile, err := sc.getCertFiles()
		if err != nil {
			glog.Errorf("Error getting certificates for TLS port %s: %s", port, err)
			disablePort(node)
			return err
		}

		// Create our certificate from the cert files (strings).
		glog.V(2).Infof("Loading certs from %s, %s", certFile, keyFile)
		cert, err := tls.LoadX509KeyPair(certFile, keyFile)
		if err != nil {
			glog.Errorf("Could not set up tls certificate for public endpoint on port %s for %s: %s", port, node.ServiceID, err)
			disablePort(node)
			return err
		}

		// The list of certs to use for our secure listener on this port.
		certs := []tls.Certificate { cert }

		// This cipher suites and tls min version change may not be needed with golang 1.5
		// https://github.com/golang/go/issues/10094
		// https://github.com/golang/go/issues/9364
		config := &tls.Config{
			MinVersion:               utils.MinTLS(),
			PreferServerCipherSuites: true,
			CipherSuites:             utils.CipherSuites(),
			Certificates:             certs,
		}

		glog.V(1).Infof("Listening with TLS")
		listener, err = tls.Listen("tcp", port, config)
	} else {
		glog.V(1).Infof("Listening without TLS")
		listener, err = net.Listen("tcp", port)
	}

	if err != nil {
		glog.Errorf("Could not setup TCP listener for port %s for public endpoint %s: %s", port, node.ServiceID, err)
		disablePort(node)
		return err
	}

	glog.Infof("Listening on port %s; UseTLS=%t", port, useTLS)

	go func() {
		for {
			// accept connection on public port
			localConn, err := listener.Accept()
			if err != nil {
				glog.V(1).Infof("Stopping accept on port %s", port)
				return
			}

			// lookup remote endpoint for this public port
			pepEPInfo, err := sc.getPublicEndpoint(fmt.Sprintf("%s-%d", node.Name, int(node.Type)))
			if err != nil {
				// This happens if an endpoint is accessed and the containers have died or not come up yet.
				glog.Errorf("Error retrieving public endpoint %s-%d: %s", node.Name, int(node.Type), err)
				// close the accepted connection and continue waiting for connections.
				if err := localConn.Close(); err != nil {
					glog.Errorf("Error closing client connection: %s", err)
				}
				continue
			}

			// setup remote connection
			var remoteAddr string
			_, isLocalContainer := sc.localAddrs[pepEPInfo.hostIP]
			if isLocalContainer {
				remoteAddr = fmt.Sprintf("%s:%d", pepEPInfo.privateIP, pepEPInfo.epPort)
			} else {
				remoteAddr = fmt.Sprintf("%s:%d", pepEPInfo.hostIP, sc.muxPort)
			}
			remoteConn, err := sc.getRemoteConnection(remoteAddr, isLocalContainer, sc.muxPort, pepEPInfo.privateIP, pepEPInfo.epPort, sc.muxTLS && (sc.muxPort > 0))
			if err != nil {
				glog.Errorf("Error getting remote connection for public endpoint %s-%d: %v", node.Name, int(node.Type), err)
				continue
			}

			glog.V(2).Infof("Established remote connection to %s", remoteConn.RemoteAddr())

			// Serve proxied requests/responses.  We pass our own port stop channel so that
			// all proxy loops end when our port is shutdown.
			go proxy.ProxyLoop(localConn, remoteConn, stopChan)
		}
	}()

	go func() {
		// Wait for shutdown, then kill all your connections
		select {
		case <-shutdown:
			// Received an application shutdown. Close the port channel to halt all proxy loops.
			glog.Infof("Shutting down port %s", port)
			close(stopChan)
		case <-stopChan:
		}

		listener.Close()
		glog.Infof("Closed port %s", port)
		return
	}()

	return nil
}

func (sc *ServiceConfig) syncAllPublicPorts(shutdown <-chan interface{}) error {
	rootConn, err := zzk.GetLocalConnection("/")
	if err != nil {
		glog.Errorf("syncAllPublicPorts - Error getting root zk connection: %v", err)
		return err
	}

	cancelChan := make(chan interface{})
	zkServicePEPService := service.ZKServicePublicEndpoints

	syncPorts := func(conn client.Connection, parentPath string, childIDs ...string) {
		allportsLock.Lock()
		defer allportsLock.Unlock()

		glog.V(1).Infof("syncPorts STARTING for parentPath:%s childIDs:%v", parentPath, childIDs)

		// start all servers that have been not started and enabled
		newPorts := make(map[string]chan bool)
		for _, pepID := range childIDs {

			// The pepID is the ZK child key. Get the node so we have all of the node data.
			glog.V(1).Infof("zkServicePEPService: %s, pepID: %s", zkServicePEPService, pepID)
			nodePath := fmt.Sprintf("%s/%s", zkServicePEPService, pepID)
			var node service.ServicePublicEndpointNode
			err := rootConn.Get(nodePath, &node)
			if err != nil {
				glog.Errorf("Unable to get the ZK Node from PepID")
				continue
			}

			if node.Type == registry.EPTypePort && node.Enabled {
				port := node.Name
				stopChan, running := allports[port]

				if !running {
					// recently enabled port - port should be opened
					stopChan = make(chan bool)
					if err := sc.createPublicPortServer(node, stopChan, shutdown); err != nil {
						continue
					}
				}

				newPorts[port] = stopChan
			}
		}

		// stop all servers that have been deleted or disabled
		for port, stopChan := range allports {
			_, found := newPorts[port]
			if !found {
				glog.V(2).Infof("Stopping port server for port %s", port)
				close(stopChan)
				glog.Infof("Port server shut down for port %s", port)
			}
		}

		allports = newPorts
		glog.V(2).Infof("Portserver allports: %+v", allports)
	}

	for {
		glog.V(1).Infof("Running registry.WatchChildren for zookeeper path: %s", zkServicePEPService)
		err := registry.WatchChildren(rootConn, zkServicePEPService, cancelChan, syncPorts, pepWatchError)
		if err != nil {
			glog.V(1).Infof("Will retry in 10 seconds to WatchChildren(%s) due to error: %v", zkServicePEPService, err)
			<-time.After(time.Second * 10)
			continue
		}
		select {
		case <-shutdown:
			close(cancelChan)
			return nil
		default:
		}
	}
}
<|endoftext|>