<commit_before>package velox

import (
	"bufio"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httputil"
	"strconv"
	"strings"
	"time"

	"github.com/bernerdschaefer/eventsource"
	"github.com/gorilla/websocket"
)

//a single update
type update struct {
	ID      string          `json:"id,omitempty"`
	Ping    bool            `json:"ping,omitempty"`
	Delta   bool            `json:"delta,omitempty"`
	Version int64           `json:"version,omitempty"` //53 usable bits
	Body    json.RawMessage `json:"body,omitempty"`
}

type transport interface {
	connect(w http.ResponseWriter, r *http.Request) error
	send(upd *update) error
	wait() error
	close() error
}

//=========================

var defaultUpgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

type websocketsTransport struct {
	writeTimeout time.Duration
	conn         *websocket.Conn
}

func (ws *websocketsTransport) connect(w http.ResponseWriter, r *http.Request) error {
	conn, err := defaultUpgrader.Upgrade(w, r, nil)
	if err != nil {
		return fmt.Errorf("[velox] cannot upgrade connection: %s", err)
	}
	ws.conn = conn
	return nil
}

func (ws *websocketsTransport) send(upd *update) error {
	ws.conn.SetWriteDeadline(time.Now().Add(ws.writeTimeout))
	return ws.conn.WriteJSON(upd)
}

func (ws *websocketsTransport) wait() error {
	//block on connection
	for {
		//ws is bi-directional, so we can rely on pings
		//from clients. currently hardcoded to 25s so timeout
		//after 30s.
		ws.conn.SetReadDeadline(time.Now().Add(30 * time.Second))
		if _, _, err := ws.conn.ReadMessage(); err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
	}
}
func (ws *websocketsTransport) close() error {
	return ws.conn.Close()
}

//=========================

type eventSourceTransport struct {
	writeTimeout time.Duration
	conn         net.Conn
	rw           *bufio.ReadWriter
	gw           *gzip.Writer
	enc          *eventsource.Encoder
	chunked      io.WriteCloser
	dst          io.Writer
	isConnected  bool
	connected    chan struct{}
}

func (es *eventSourceTransport) connect(w http.ResponseWriter, r *http.Request) error {
	//hijack
	hj, ok := w.(http.Hijacker)
	if !ok {
		return errors.New("[velox] underlying writer must be an http.Hijacker")
	}
	conn, rw, err := hj.Hijack()
	if err != nil {
		return errors.New("[velox] failed to hijack underlying net.Conn")
	}
	//can we gzip?
	acceptGzip := strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
	//init
	es.conn = conn
	es.rw = rw
	es.chunked = httputil.NewChunkedWriter(rw)
	if acceptGzip {
		es.gw = gzip.NewWriter(es.chunked)
		es.dst = es.gw
	} else {
		es.dst = es.chunked
	}
	//http and eventsource headers
	rw.WriteString("HTTP/1.1 200 OK\r\n")
	h := http.Header{}
	wh := w.Header()
	for k, _ := range wh {
		h.Set(k, wh.Get(k))
	}
	h.Set("Cache-Control", "no-cache")
	h.Set("Vary", "Accept")
	h.Set("Content-Type", "text/event-stream")
	if acceptGzip {
		h.Set("Content-Encoding", "gzip")
	} else {
		h.Del("Content-Encoding")
	}
	h.Write(rw)
	h = http.Header{}
	h.Set("Transfer-Encoding", "chunked")
	h.Write(rw)
	rw.WriteString("\r\n")
	//connection is now expecting a chunked stream of events
	esb := &eventSourceBuffer{es: es}
	es.enc = eventsource.NewEncoder(esb)
	return nil
}

func (es *eventSourceTransport) send(upd *update) error {
	b, err := json.Marshal(upd)
	if err != nil {
		return err
	}
	return es.enc.Encode(eventsource.Event{
		ID:   strconv.FormatInt(upd.Version, 10),
		Data: b,
	})
}

func (es *eventSourceTransport) wait() error {
	//disable readtime outs
	es.conn.SetReadDeadline(time.Time{})
	//read to /dev/null
	_, err := io.Copy(ioutil.Discard, es.rw)
	if err != nil && err != io.EOF {
		return err
	}
	return nil
}

func (es *eventSourceTransport) close() error {
	es.enc.Flush()
	es.chunked.Close()
	es.rw.Flush()
	return es.conn.Close()
}

//implements raw chunked transfer encoding
//over a hijacked read/write buffer while
//setting write deadlines
type eventSourceBuffer struct {
	es   *eventSourceTransport
	buff bytes.Buffer
}

//write to memory
func (esb *eventSourceBuffer) Write(p []byte) (int, error) {
	return esb.buff.Write(p)
}

//flush converts the buffer into chunked then does write
func (esb *eventSourceBuffer) Flush() {
	esb.es.conn.SetWriteDeadline(time.Now().Add(esb.es.writeTimeout))
	io.Copy(esb.es.dst, &esb.buff)
	if esb.es.gw != nil {
		esb.es.gw.Flush()
	}
	esb.es.rw.Flush()
}
<commit_msg>close gzip writer<commit_after>package velox

import (
	"bufio"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httputil"
	"strconv"
	"strings"
	"time"

	"github.com/bernerdschaefer/eventsource"
	"github.com/gorilla/websocket"
)

//a single update
type update struct {
	ID      string          `json:"id,omitempty"`
	Ping    bool            `json:"ping,omitempty"`
	Delta   bool            `json:"delta,omitempty"`
	Version int64           `json:"version,omitempty"` //53 usable bits
	Body    json.RawMessage `json:"body,omitempty"`
}

type transport interface {
	connect(w http.ResponseWriter, r *http.Request) error
	send(upd *update) error
	wait() error
	close() error
}

//=========================

var defaultUpgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

type websocketsTransport struct {
	writeTimeout time.Duration
	conn         *websocket.Conn
}

func (ws *websocketsTransport) connect(w http.ResponseWriter, r *http.Request) error {
	conn, err := defaultUpgrader.Upgrade(w, r, nil)
	if err != nil {
		return fmt.Errorf("[velox] cannot upgrade connection: %s", err)
	}
	ws.conn = conn
	return nil
}

func (ws *websocketsTransport) send(upd *update) error {
	ws.conn.SetWriteDeadline(time.Now().Add(ws.writeTimeout))
	return ws.conn.WriteJSON(upd)
}

func (ws *websocketsTransport) wait() error {
	//block on connection
	for {
		//ws is bi-directional, so we can rely on pings
		//from clients. currently hardcoded to 25s so timeout
		//after 30s.
		ws.conn.SetReadDeadline(time.Now().Add(30 * time.Second))
		if _, _, err := ws.conn.ReadMessage(); err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
	}
}
func (ws *websocketsTransport) close() error {
	return ws.conn.Close()
}

//=========================

type eventSourceTransport struct {
	writeTimeout time.Duration
	conn         net.Conn
	rw           *bufio.ReadWriter
	gw           *gzip.Writer
	enc          *eventsource.Encoder
	chunked      io.WriteCloser
	dst          io.Writer
	isConnected  bool
	connected    chan struct{}
}

func (es *eventSourceTransport) connect(w http.ResponseWriter, r *http.Request) error {
	//hijack
	hj, ok := w.(http.Hijacker)
	if !ok {
		return errors.New("[velox] underlying writer must be an http.Hijacker")
	}
	conn, rw, err := hj.Hijack()
	if err != nil {
		return errors.New("[velox] failed to hijack underlying net.Conn")
	}
	//can we gzip?
	acceptGzip := strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
	//init
	es.conn = conn
	es.rw = rw
	es.chunked = httputil.NewChunkedWriter(rw)
	if acceptGzip {
		es.gw = gzip.NewWriter(es.chunked)
		es.dst = es.gw
	} else {
		es.dst = es.chunked
	}
	//http and eventsource headers
	rw.WriteString("HTTP/1.1 200 OK\r\n")
	h := http.Header{}
	wh := w.Header()
	for k, _ := range wh {
		h.Set(k, wh.Get(k))
	}
	h.Set("Cache-Control", "no-cache")
	h.Set("Vary", "Accept")
	h.Set("Content-Type", "text/event-stream")
	if acceptGzip {
		h.Set("Content-Encoding", "gzip")
	} else {
		h.Del("Content-Encoding")
	}
	h.Write(rw)
	h = http.Header{}
	h.Set("Transfer-Encoding", "chunked")
	h.Write(rw)
	rw.WriteString("\r\n")
	//connection is now expecting a chunked stream of events
	esb := &eventSourceBuffer{es: es}
	es.enc = eventsource.NewEncoder(esb)
	return nil
}

func (es *eventSourceTransport) send(upd *update) error {
	b, err := json.Marshal(upd)
	if err != nil {
		return err
	}
	return es.enc.Encode(eventsource.Event{
		ID:   strconv.FormatInt(upd.Version, 10),
		Data: b,
	})
}

func (es *eventSourceTransport) wait() error {
	//disable readtime outs
	es.conn.SetReadDeadline(time.Time{})
	//read to /dev/null
	_, err := io.Copy(ioutil.Discard, es.rw)
	if err != nil && err != io.EOF {
		return err
	}
	return nil
}

func (es *eventSourceTransport) close() error {
	es.enc.Flush()
	es.chunked.Close()
	if es.gw != nil {
		es.gw.Close()
	}
	es.rw.Flush()
	return es.conn.Close()
}

//implements raw chunked transfer encoding
//over a hijacked read/write buffer while
//setting write deadlines
type eventSourceBuffer struct {
	es   *eventSourceTransport
	buff bytes.Buffer
}

//write to memory
func (esb *eventSourceBuffer) Write(p []byte) (int, error) {
	return esb.buff.Write(p)
}

//flush converts the buffer into chunked then does write
func (esb *eventSourceBuffer) Flush() {
	esb.es.conn.SetWriteDeadline(time.Now().Add(esb.es.writeTimeout))
	io.Copy(esb.es.dst, &esb.buff)
	if esb.es.gw != nil {
		esb.es.gw.Flush()
	}
	esb.es.rw.Flush()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

type transport struct {
	ClientID    string `json:"client_id,omitempty"`
	AccessToken string `json:"access_token,omitempty"`
}

func (t *transport) RoundTrip(r *http.Request) (*http.Response, error) {
	if t.ClientID == "" || t.AccessToken == "" {
		return nil, fmt.Errorf("ClientID and AccessToken must both be set")
	}
	r.Header.Set("X-Client-ID", t.ClientID)
	r.Header.Set("X-Access-Token", t.AccessToken)
	return http.DefaultClient.Do(r)
}

func loadTransport() (c *transport, err error) {
	f, err := os.Open(os.ExpandEnv("$HOME/.wundercli/config.json"))
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return c, json.NewDecoder(f).Decode(&c)
}
<commit_msg>change config location<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

type transport struct {
	ClientID    string `json:"client_id,omitempty"`
	AccessToken string `json:"access_token,omitempty"`
}

func (t *transport) RoundTrip(r *http.Request) (*http.Response, error) {
	if t.ClientID == "" || t.AccessToken == "" {
		return nil, fmt.Errorf("ClientID and AccessToken must both be set")
	}
	r.Header.Set("X-Client-ID", t.ClientID)
	r.Header.Set("X-Access-Token", t.AccessToken)
	return http.DefaultClient.Do(r)
}

func loadTransport() (c *transport, err error) {
	f, err := os.Open(os.ExpandEnv("$HOME/.wlcli/config.json"))
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return c, json.NewDecoder(f).Decode(&c)
}
<|endoftext|>


<commit_before>// Package tree implements a basic balanced binary tree
package tree

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"sync"
	"time"
)

// Node is a fundemental part of what makes a tree a tree. Many Nodes creates a tree
type Node struct {
	Left  *Node
	Right *Node
	Data  int
}

// Tree basic tree structure
type Tree struct {
	Root      *Node
	Total     int
	NodeCount int
}

var (
	// ErrPositiveIntegers reports that only positive intergers may be added to the tree
	ErrPositiveIntegers = fmt.Errorf("only postive integers may be added")
	// ErrNodeNotFound reports that a Node wasn't found
	ErrNodeNotFound = fmt.Errorf("Node not found")
)

// NewTree creates a pointer to the Tree struct
func NewTree() *Tree {
	return new(Tree)
}

// FindNode recursively looks for the Node with the specified value
func (t *Tree) FindNode(data int) (err error) {
	newNode := Node{
		Data: data,
	}
	if t.Root != nil {
		if t.findNode(t.Root, newNode) != nil {
			return
		}
	}
	return ErrNodeNotFound
}

func (t *Tree) findNode(search *Node, target Node) *Node {
	var returnNode *Node
	if search == nil {
		return returnNode
	}
	if search.Data == target.Data {
		return search
	}
	returnNode = t.findNode(search.Left, target)
	if returnNode == nil {
		returnNode = t.findNode(search.Right, target)
	}
	return returnNode
}

// Add appends a new Node to a branch in a balanced manner
func (t *Tree) Add(data int) (err error) {
	t.Total += data
	t.NodeCount++
	if data < 0 {
		return ErrPositiveIntegers
	}
	NodeToAdd := Node{
		Data: data,
	}
	if t.Root == nil {
		t.Root = new(Node)
	}
	if t.Root.Data == 0 {
		t.Root = &NodeToAdd
		return
	}
	t.add(t.Root, NodeToAdd)
	return
}

func (t *Tree) add(oldNode *Node, newNode Node) {
	if newNode.Data < oldNode.Data {
		if oldNode.Left == nil {
			oldNode.Left = &newNode
		} else {
			t.add(oldNode.Left, newNode)
		}
	} else if newNode.Data > oldNode.Data {
		if oldNode.Right == nil {
			oldNode.Right = &newNode
		} else {
			t.add(oldNode.Right, newNode)
		}
	}
	return
}

// InOrderTraversal prints out the values in order
func (t *Tree) InOrderTraversal() {
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			fmt.Println(currentNode.Data)
		} else {
			t.inOrderTraversal(currentNode)
		}
	}
	return
}

func (t *Tree) inOrderTraversal(n *Node) {
	if n.Left != nil {
		t.inOrderTraversal(n.Left)
	}
	fmt.Println(n.Data)
	if n.Right != nil {
		t.inOrderTraversal(n.Right)
	}
	return
}

// Traversal prints out the values by branch side, left, right, ect...
func (t *Tree) Traversal() {
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			fmt.Println(currentNode.Data)
		} else {
			t.traversal(currentNode)
		}
	}
	return
}

func (t *Tree) traversal(n *Node) {
	fmt.Println(n.Data)
	if n.Left != nil {
		t.traversal(n.Left)
	}
	if n.Right != nil {
		t.traversal(n.Right)
	}
	return
}

// Sum added up all the values stored in the Nodes.. It is a redundant function because total value is kept as a Tree
// value
func (t *Tree) Sum() (total int) {
	var wg sync.WaitGroup
	c := make(chan int, 100)
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			return 1
		}
		wg.Add(1)
		t.sum(currentNode, c, &wg)
	}
	go func() {
		wg.Wait()
		close(c)
	}()
	for n := range c {
		total += n
	}
	return total
}

func (t *Tree) sum(n *Node, counter chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	if n.Left != nil {
		wg.Add(1)
		go t.sum(n.Left, counter, wg)
	}
	counter <- n.Data
	if n.Right != nil {
		wg.Add(1)
		go t.sum(n.Right, counter, wg)
	}
	return
}

// CountEdges returns the number of edges the tree contains
func (t *Tree) CountEdges() (edges int) {
	var wg sync.WaitGroup
	c := make(chan int, 100)
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			return 1
		}
		wg.Add(1)
		t.countEdges(currentNode, c, &wg)
	}

	go func() {
		wg.Wait()
		close(c)
	}()
	for n := range c {
		edges += n
	}
	return edges
}

func (t *Tree) countEdges(n *Node, counter chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	if n.Left != nil {
		wg.Add(1)
		go t.countEdges(n.Left, counter, wg)
	}
	counter <- 1
	if n.Right != nil {
		wg.Add(1)
		go t.countEdges(n.Right, counter, wg)
	}
	return
}

// GenerateRandomTree uses time (time.Now().Unix()) to create enthorpy for a source of random numbers to append to the Tree
func (t *Tree) GenerateRandomTree(numberOfNodesToCreate int) (err error) {
	if numberOfNodesToCreate < 0 {
		return ErrPositiveIntegers
	}
	u := time.Now()
	source := rand.NewSource(u.Unix())
	r := rand.New(source)
	arr := r.Perm(numberOfNodesToCreate)
	for _, a := range arr {
		t.Add(a)
	}
	return
}

// GetRootData returns the data stored at the root, however this does not return the root Node
func (t *Tree) GetRootData() int {
	return t.Root.Data
}

// GetTreeTotal returns the sum of the collective nodes on the Tree
func (t *Tree) GetTreeTotal() int {
	return t.Total
}

// TreeToArray converts to Tree into an int slice
func (t *Tree) TreeToArray() []int {
	var wg sync.WaitGroup
	c := make(chan int, 100)
	arr := make([]int, 0, t.NodeCount)
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			return []int{currentNode.Data}
		}
		wg.Add(1)
		t.traversalGetVals(currentNode, c, &wg)
	}
	go func() {
		wg.Wait()
		close(c)
	}()
	for n := range c {
		arr = append(arr, n)
	}
	return arr
}

func (t *Tree) traversalGetVals(n *Node, c chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	if n.Left != nil {
		c <- n.Left.Data
		wg.Add(1)
		go t.traversalGetVals(n.Left, c, wg)
	}
	if n.Right != nil {
		c <- n.Right.Data
		wg.Add(1)
		go t.traversalGetVals(n.Right, c, wg)
	}
	return
}

// ShiftRoot rebuilds the tree with a new root
func (t *Tree) ShiftRoot(newRoot int) {
	arr := t.TreeToArray()
	n := Tree{}
	n.Add(newRoot)
	for _, i := range arr {
		n.Add(i)
	}
	*t = n
}

// PrintTree uses json.MarshalIndent() to print the Tree in an organized fashion, which can then be analysized as a JSON
// object
func (t *Tree) PrintTree() {
	b, err := json.MarshalIndent(t, "", " ")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}
<commit_msg>correcting FindNode function to return a node and an error<commit_after>// Package tree implements a basic balanced binary tree
package tree

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"sync"
	"time"
)

// Node is a fundemental part of what makes a tree a tree. Many Nodes creates a tree
type Node struct {
	Left  *Node
	Right *Node
	Data  int
}

// Tree basic tree structure
type Tree struct {
	Root      *Node
	Total     int
	NodeCount int
}

var (
	// ErrPositiveIntegers reports that only positive intergers may be added to the tree
	ErrPositiveIntegers = fmt.Errorf("only postive integers may be added")
	// ErrNodeNotFound reports that a Node wasn't found
	ErrNodeNotFound = fmt.Errorf("Node not found")
)

// NewTree creates a pointer to the Tree struct
func NewTree() *Tree {
	return new(Tree)
}

// FindNode recursively looks for the Node with the specified value
func (t *Tree) FindNode(data int) (node *Node, err error) {
	newNode := Node{
		Data: data,
	}
	if t.Root != nil {
		node := t.findNode(t.Root, newNode)
		if node != nil {
			return node, nil
		}
	}
	return nil, ErrNodeNotFound
}

func (t *Tree) findNode(search *Node, target Node) *Node {
	var returnNode *Node
	if search == nil {
		return returnNode
	}
	if search.Data == target.Data {
		return search
	}
	returnNode = t.findNode(search.Left, target)
	if returnNode == nil {
		returnNode = t.findNode(search.Right, target)
	}
	return returnNode
}

// Add appends a new Node to a branch in a balanced manner
func (t *Tree) Add(data int) (err error) {
	t.Total += data
	t.NodeCount++
	if data < 0 {
		return ErrPositiveIntegers
	}
	NodeToAdd := Node{
		Data: data,
	}
	if t.Root == nil {
		t.Root = new(Node)
	}
	if t.Root.Data == 0 {
		t.Root = &NodeToAdd
		return
	}
	t.add(t.Root, NodeToAdd)
	return
}

func (t *Tree) add(oldNode *Node, newNode Node) {
	if newNode.Data < oldNode.Data {
		if oldNode.Left == nil {
			oldNode.Left = &newNode
		} else {
			t.add(oldNode.Left, newNode)
		}
	} else if newNode.Data > oldNode.Data {
		if oldNode.Right == nil {
			oldNode.Right = &newNode
		} else {
			t.add(oldNode.Right, newNode)
		}
	}
	return
}

// InOrderTraversal prints out the values in order
func (t *Tree) InOrderTraversal() {
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			fmt.Println(currentNode.Data)
		} else {
			t.inOrderTraversal(currentNode)
		}
	}
	return
}

func (t *Tree) inOrderTraversal(n *Node) {
	if n.Left != nil {
		t.inOrderTraversal(n.Left)
	}
	fmt.Println(n.Data)
	if n.Right != nil {
		t.inOrderTraversal(n.Right)
	}
	return
}

// Traversal prints out the values by branch side, left, right, ect...
func (t *Tree) Traversal() {
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			fmt.Println(currentNode.Data)
		} else {
			t.traversal(currentNode)
		}
	}
	return
}

func (t *Tree) traversal(n *Node) {
	fmt.Println(n.Data)
	if n.Left != nil {
		t.traversal(n.Left)
	}
	if n.Right != nil {
		t.traversal(n.Right)
	}
	return
}

// Sum added up all the values stored in the Nodes.. It is a redundant function because total value is kept as a Tree
// value
func (t *Tree) Sum() (total int) {
	var wg sync.WaitGroup
	c := make(chan int, 100)
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			return 1
		}
		wg.Add(1)
		t.sum(currentNode, c, &wg)
	}
	go func() {
		wg.Wait()
		close(c)
	}()
	for n := range c {
		total += n
	}
	return total
}

func (t *Tree) sum(n *Node, counter chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	if n.Left != nil {
		wg.Add(1)
		go t.sum(n.Left, counter, wg)
	}
	counter <- n.Data
	if n.Right != nil {
		wg.Add(1)
		go t.sum(n.Right, counter, wg)
	}
	return
}

// CountEdges returns the number of edges the tree contains
func (t *Tree) CountEdges() (edges int) {
	var wg sync.WaitGroup
	c := make(chan int, 100)
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			return 1
		}
		wg.Add(1)
		t.countEdges(currentNode, c, &wg)
	}

	go func() {
		wg.Wait()
		close(c)
	}()
	for n := range c {
		edges += n
	}
	return edges
}

func (t *Tree) countEdges(n *Node, counter chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	if n.Left != nil {
		wg.Add(1)
		go t.countEdges(n.Left, counter, wg)
	}
	counter <- 1
	if n.Right != nil {
		wg.Add(1)
		go t.countEdges(n.Right, counter, wg)
	}
	return
}

// GenerateRandomTree uses time (time.Now().Unix()) to create enthorpy for a source of random numbers to append to the Tree
func (t *Tree) GenerateRandomTree(numberOfNodesToCreate int) (err error) {
	if numberOfNodesToCreate < 0 {
		return ErrPositiveIntegers
	}
	u := time.Now()
	source := rand.NewSource(u.Unix())
	r := rand.New(source)
	arr := r.Perm(numberOfNodesToCreate)
	for _, a := range arr {
		t.Add(a)
	}
	return
}

// GetRootData returns the data stored at the root, however this does not return the root Node
func (t *Tree) GetRootData() int {
	return t.Root.Data
}

// GetTreeTotal returns the sum of the collective nodes on the Tree
func (t *Tree) GetTreeTotal() int {
	return t.Total
}

// TreeToArray converts to Tree into an int slice
func (t *Tree) TreeToArray() []int {
	var wg sync.WaitGroup
	c := make(chan int, 100)
	arr := make([]int, 0, t.NodeCount)
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			return []int{currentNode.Data}
		}
		wg.Add(1)
		t.traversalGetVals(currentNode, c, &wg)
	}
	go func() {
		wg.Wait()
		close(c)
	}()
	for n := range c {
		arr = append(arr, n)
	}
	return arr
}

func (t *Tree) traversalGetVals(n *Node, c chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	if n.Left != nil {
		c <- n.Left.Data
		wg.Add(1)
		go t.traversalGetVals(n.Left, c, wg)
	}
	if n.Right != nil {
		c <- n.Right.Data
		wg.Add(1)
		go t.traversalGetVals(n.Right, c, wg)
	}
	return
}

// ShiftRoot rebuilds the tree with a new root
func (t *Tree) ShiftRoot(newRoot int) {
	arr := t.TreeToArray()
	n := Tree{}
	n.Add(newRoot)
	for _, i := range arr {
		n.Add(i)
	}
	*t = n
}

// PrintTree uses json.MarshalIndent() to print the Tree in an organized fashion, which can then be analysized as a JSON
// object
func (t *Tree) PrintTree() {
	b, err := json.MarshalIndent(t, "", " ")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}
<|endoftext|>


<commit_before>package udp

import (
	"context"
	"log"
	"net"

	"github.com/anacrolix/dht/v2/krpc"
	"github.com/anacrolix/missinggo/v2"
)

type NewConnClientOpts struct {
	Network string
	Host    string
	Ipv6    *bool
}

type ConnClient struct {
	cl      Client
	conn    net.Conn
	d       Dispatcher
	readErr error
	ipv6    bool
}

func (cc *ConnClient) reader() {
	for {
		b := make([]byte, 0x800)
		n, err := cc.conn.Read(b)
		if err != nil {
			// TODO: Do bad things to the dispatcher, and incoming calls to the client if we have a
			// read error.
			cc.readErr = err
			break
		}
		err = cc.d.Dispatch(b[:n])
		if err != nil {
			log.Printf("dispatching packet received on %v: %v", cc.conn, err)
		}
	}
}

func ipv6(opt *bool, network string, conn net.Conn) bool {
	if opt != nil {
		return *opt
	}
	switch network {
	case "udp4":
		return false
	case "udp6":
		return true
	}
	rip := missinggo.AddrIP(conn.RemoteAddr())
	return rip.To16() != nil && rip.To4() == nil
}

func NewConnClient(opts NewConnClientOpts) (cc *ConnClient, err error) {
	conn, err := net.Dial(opts.Network, opts.Host)
	if err != nil {
		return
	}
	cc = &ConnClient{
		cl: Client{
			Writer: conn,
		},
		conn: conn,
		ipv6: ipv6(opts.Ipv6, opts.Network, conn),
	}
	cc.cl.Dispatcher = &cc.d
	go cc.reader()
	return
}

func (c *ConnClient) Close() error {
	return c.conn.Close()
}

func (c *ConnClient) Announce(
	ctx context.Context, req AnnounceRequest, opts Options,
) (
	h AnnounceResponseHeader, nas AnnounceResponsePeers, err error,
) {
	nas = func() AnnounceResponsePeers {
		if c.ipv6 {
			return &krpc.CompactIPv6NodeAddrs{}
		} else {
			return &krpc.CompactIPv4NodeAddrs{}
		}
	}()
	h, err = c.cl.Announce(ctx, req, nas, opts)
	return
}
<commit_msg>Don't log dispatch errors<commit_after>package udp

import (
	"context"
	"net"

	"github.com/anacrolix/dht/v2/krpc"
	"github.com/anacrolix/missinggo/v2"
)

type NewConnClientOpts struct {
	Network string
	Host    string
	Ipv6    *bool
}

type ConnClient struct {
	cl      Client
	conn    net.Conn
	d       Dispatcher
	readErr error
	ipv6    bool
}

func (cc *ConnClient) reader() {
	for {
		b := make([]byte, 0x800)
		n, err := cc.conn.Read(b)
		if err != nil {
			// TODO: Do bad things to the dispatcher, and incoming calls to the client if we have a
			// read error.
			cc.readErr = err
			break
		}
		err = cc.d.Dispatch(b[:n])
		if err != nil {
			//log.Printf("dispatching packet received on %v (%q): %v", cc.conn, string(b[:n]), err)
		}
	}
}

func ipv6(opt *bool, network string, conn net.Conn) bool {
	if opt != nil {
		return *opt
	}
	switch network {
	case "udp4":
		return false
	case "udp6":
		return true
	}
	rip := missinggo.AddrIP(conn.RemoteAddr())
	return rip.To16() != nil && rip.To4() == nil
}

func NewConnClient(opts NewConnClientOpts) (cc *ConnClient, err error) {
	conn, err := net.Dial(opts.Network, opts.Host)
	if err != nil {
		return
	}
	cc = &ConnClient{
		cl: Client{
			Writer: conn,
		},
		conn: conn,
		ipv6: ipv6(opts.Ipv6, opts.Network, conn),
	}
	cc.cl.Dispatcher = &cc.d
	go cc.reader()
	return
}

func (c *ConnClient) Close() error {
	return c.conn.Close()
}

func (c *ConnClient) Announce(
	ctx context.Context, req AnnounceRequest, opts Options,
) (
	h AnnounceResponseHeader, nas AnnounceResponsePeers, err error,
) {
	nas = func() AnnounceResponsePeers {
		if c.ipv6 {
			return &krpc.CompactIPv6NodeAddrs{}
		} else {
			return &krpc.CompactIPv4NodeAddrs{}
		}
	}()
	h, err = c.cl.Announce(ctx, req, nas, opts)
	return
}
<|endoftext|>


<commit_before>package transaction

import (
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/stefankopieczek/gossip/base"
	"github.com/stefankopieczek/gossip/log"
	"github.com/stefankopieczek/gossip/parser"
	"github.com/stefankopieczek/gossip/transport"
)

var c_SERVER string = "localhost:5060"
var c_CLIENT string = "localhost:5061"

var testLog *log.Logger = log.New(os.Stderr, ">>> ", 0)

func TestAAAASetup(t *testing.T) {
	log.SetDefaultLogLevel(log.WARN)
	testLog.Level = log.DEBUG
}

func TestSendInviteUDP(t *testing.T) {
	invite, err := request([]string{
		"INVITE sip:joe@bloggs.com SIP/2.0",
		"Via: SIP/2.0/UDP " + c_CLIENT + ";branch=z9hG4bK776asdhds",
		"",
		"",
	})
	assertNoError(t, err)

	test := transactionTest{actions: []action{
		&clientSend{invite},
		&serverRecv{invite},
	}}
	test.Execute(t)
}

func TestReceiveOKUDP(t *testing.T) {
	invite, err := request([]string{
		"INVITE sip:joe@bloggs.com SIP/2.0",
		"CSeq: 1 INVITE",
		"Via: SIP/2.0/UDP " + c_CLIENT + ";branch=z9hG4bK776asdhds",
		"",
		"",
	})
	assertNoError(t, err)

	ok, err := response([]string{
		"SIP/2.0 200 OK",
		"CSeq: 1 INVITE",
		"Via: SIP/2.0/UDP " + c_SERVER + ";branch=z9hG4bK776asdhds",
		"",
		"",
	})
	assertNoError(t, err)

	test := transactionTest{actions: []action{
		&clientSend{invite},
		&serverRecv{invite},
		&serverSend{ok},
		&clientRecv{ok},
	}}
	test.Execute(t)
}

type action interface {
	Act(test *transactionTest) error
}

type transactionTest struct {
	actions    []action
	client     *Manager
	server     *transport.Manager
	serverRecv chan base.SipMessage
	lastTx     *ClientTransaction
}

func (test *transactionTest) Execute(t *testing.T) {
	var err error
	test.client, err = NewManager("udp", c_CLIENT)
	assertNoError(t, err)
	defer test.client.Stop()

	test.server, err = transport.NewManager("udp")
	assertNoError(t, err)
	defer test.server.Stop()
	test.serverRecv = test.server.GetChannel()
	test.server.Listen(c_SERVER)

	for _, actn := range test.actions {
		testLog.Debug("Performing action %v", actn)
		assertNoError(t, actn.Act(test))
	}
}

type clientSend struct {
	msg *base.Request
}

func (actn *clientSend) Act(test *transactionTest) error {
	testLog.Debug("Client sending message\n%v", actn.msg.String())
	test.lastTx = test.client.Send(actn.msg, c_SERVER)
	return nil
}

type serverSend struct {
	msg base.SipMessage
}

func (actn *serverSend) Act(test *transactionTest) error {
	testLog.Debug("Server sending message\n%v", actn.msg.String())
	return test.server.Send(c_CLIENT, actn.msg)
}

type clientRecv struct {
	expected *base.Response
}

func (actn *clientRecv) Act(test *transactionTest) error {
	responses := test.lastTx.Responses()
	select {
	case response, ok := <-responses:
		if !ok {
			return fmt.Errorf("Response channel prematurely closed")
		} else if response.String() != actn.expected.String() {
			return fmt.Errorf("Unexpected response:\n%s", response.String())
		} else {
			testLog.Debug("Client received correct message\n%v", response.String())
			return nil
		}
	case <-time.After(time.Second):
		return fmt.Errorf("Timed out waiting for response")
	}
}

type serverRecv struct {
	expected base.SipMessage
}

func (actn *serverRecv) Act(test *transactionTest) error {
	select {
	case msg, ok := <-test.serverRecv:
		if !ok {
			return fmt.Errorf("Server receive channel prematurely closed")
		} else if msg.String() != actn.expected.String() {
			return fmt.Errorf("Unexpected message arrived at server:\n%s", msg.String())
		} else {
			testLog.Debug("Server received correct message\n %v", msg.String())
			return nil
		}
	case <-time.After(time.Second):
		return fmt.Errorf("Timed out waiting for message on server")
	}
}

func assert(t *testing.T, b bool, msg string) {
	if !b {
		t.Errorf(msg)
	}
}

func assertNoError(t *testing.T, err error) {
	if err != nil {
		t.Fatalf("Unexpected error: %s", err.Error())
	}
}

func message(rawMsg []string) (base.SipMessage, error) {
	return parser.ParseMessage([]byte(strings.Join(rawMsg, "\r\n")))
}

func request(rawMsg []string) (*base.Request, error) {
	msg, err := message(rawMsg)

	if err != nil {
		return nil, err
	}

	switch msg.(type) {
	case *base.Request:
		return msg.(*base.Request), nil
	default:
		return nil, fmt.Errorf("%s is not a request", msg.Short)
	}
}

func response(rawMsg []string) (*base.Response, error) {
	msg, err := message(rawMsg)

	if err != nil {
		return nil, err
	}

	switch msg.(type) {
	case *base.Response:
		return msg.(*base.Response), nil
	default:
		return nil, fmt.Errorf("%s is not a response", msg.Short)
	}
}
<commit_msg>Add 200ms sleep to transport tests<commit_after>package transaction

import (
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/stefankopieczek/gossip/base"
	"github.com/stefankopieczek/gossip/log"
	"github.com/stefankopieczek/gossip/parser"
	"github.com/stefankopieczek/gossip/transport"
)

var c_SERVER string = "localhost:5060"
var c_CLIENT string = "localhost:5061"

var testLog *log.Logger = log.New(os.Stderr, ">>> ", 0)

func TestAAAASetup(t *testing.T) {
	log.SetDefaultLogLevel(log.WARN)
	testLog.Level = log.DEBUG
}

func TestSendInviteUDP(t *testing.T) {
	invite, err := request([]string{
		"INVITE sip:joe@bloggs.com SIP/2.0",
		"Via: SIP/2.0/UDP " + c_CLIENT + ";branch=z9hG4bK776asdhds",
		"",
		"",
	})
	assertNoError(t, err)

	test := transactionTest{actions: []action{
		&clientSend{invite},
		&serverRecv{invite},
	}}
	test.Execute(t)
}

func TestReceiveOKUDP(t *testing.T) {
	invite, err := request([]string{
		"INVITE sip:joe@bloggs.com SIP/2.0",
		"CSeq: 1 INVITE",
		"Via: SIP/2.0/UDP " + c_CLIENT + ";branch=z9hG4bK776asdhds",
		"",
		"",
	})
	assertNoError(t, err)

	ok, err := response([]string{
		"SIP/2.0 200 OK",
		"CSeq: 1 INVITE",
		"Via: SIP/2.0/UDP " + c_SERVER + ";branch=z9hG4bK776asdhds",
		"",
		"",
	})
	assertNoError(t, err)

	test := transactionTest{actions: []action{
		&clientSend{invite},
		&serverRecv{invite},
		&serverSend{ok},
		&clientRecv{ok},
	}}
	test.Execute(t)
}

type action interface {
	Act(test *transactionTest) error
}

type transactionTest struct {
	actions    []action
	client     *Manager
	server     *transport.Manager
	serverRecv chan base.SipMessage
	lastTx     *ClientTransaction
}

func (test *transactionTest) Execute(t *testing.T) {
	defer func() { <-time.After(time.Millisecond * 200) }()
	var err error
	test.client, err = NewManager("udp", c_CLIENT)
	assertNoError(t, err)
	defer test.client.Stop()

	test.server, err = transport.NewManager("udp")
	assertNoError(t, err)
	defer test.server.Stop()
	test.serverRecv = test.server.GetChannel()
	test.server.Listen(c_SERVER)

	for _, actn := range test.actions {
		testLog.Debug("Performing action %v", actn)
		assertNoError(t, actn.Act(test))
	}
}

type clientSend struct {
	msg *base.Request
}

func (actn *clientSend) Act(test *transactionTest) error {
	testLog.Debug("Client sending message\n%v", actn.msg.String())
	test.lastTx = test.client.Send(actn.msg, c_SERVER)
	return nil
}

type serverSend struct {
	msg base.SipMessage
}

func (actn *serverSend) Act(test *transactionTest) error {
	testLog.Debug("Server sending message\n%v", actn.msg.String())
	return test.server.Send(c_CLIENT, actn.msg)
}

type clientRecv struct {
	expected *base.Response
}

func (actn *clientRecv) Act(test *transactionTest) error {
	responses := test.lastTx.Responses()
	select {
	case response, ok := <-responses:
		if !ok {
			return fmt.Errorf("Response channel prematurely closed")
		} else if response.String() != actn.expected.String() {
			return fmt.Errorf("Unexpected response:\n%s", response.String())
		} else {
			testLog.Debug("Client received correct message\n%v", response.String())
			return nil
		}
	case <-time.After(time.Second):
		return fmt.Errorf("Timed out waiting for response")
	}
}

type serverRecv struct {
	expected base.SipMessage
}

func (actn *serverRecv) Act(test *transactionTest) error {
	select {
	case msg, ok := <-test.serverRecv:
		if !ok {
			return fmt.Errorf("Server receive channel prematurely closed")
		} else if msg.String() != actn.expected.String() {
			return fmt.Errorf("Unexpected message arrived at server:\n%s", msg.String())
		} else {
			testLog.Debug("Server received correct message\n %v", msg.String())
			return nil
		}
	case <-time.After(time.Second):
		return fmt.Errorf("Timed out waiting for message on server")
	}
}

func assert(t *testing.T, b bool, msg string) {
	if !b {
		t.Errorf(msg)
	}
}

func assertNoError(t *testing.T, err error) {
	if err != nil {
		t.Fatalf("Unexpected error: %s", err.Error())
	}
}

func message(rawMsg []string) (base.SipMessage, error) {
	return parser.ParseMessage([]byte(strings.Join(rawMsg, "\r\n")))
}

func request(rawMsg []string) (*base.Request, error) {
	msg, err := message(rawMsg)

	if err != nil {
		return nil, err
	}

	switch msg.(type) {
	case *base.Request:
		return msg.(*base.Request), nil
	default:
		return nil, fmt.Errorf("%s is not a request", msg.Short)
	}
}

func response(rawMsg []string) (*base.Response, error) {
	msg, err := message(rawMsg)

	if err != nil {
		return nil, err
	}

	switch msg.(type) {
	case *base.Response:
		return msg.(*base.Response), nil
	default:
		return nil, fmt.Errorf("%s is not a response", msg.Short)
	}
}
<|endoftext|>


<commit_before>package transaction

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

const (
	SequenceName string = "nproxy_transaction_id_seq"
)

func GetId(db *sql.DB) (int, error) {
  var value interface{}
	err := db.QueryRow(fmt.Sprintf("SELECT nextval('%s')", SequenceName)).Scan(&value)
  if err != nil {
    log.Println(err)
    return 0, err
  } else {
    return value.(int), nil
  }
}
<commit_msg>fixed transaction.go<commit_after>package transaction

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

const (
	SequenceName string = "nproxy_transaction_id_seq"
)

func GetId(db *sql.DB) (int, error) {
	var value int
	err := db.QueryRow(fmt.Sprintf("SELECT nextval('%s')", SequenceName)).Scan(&value)
	if err != nil {
		log.Println(err)
	}
  return value, err
}
<|endoftext|>


<commit_before>package auctiondistributor

import (
	"fmt"
	"time"

	"github.com/cheggaaa/pb"
	"github.com/cloudfoundry-incubator/auction/auctionrunner"
	"github.com/cloudfoundry-incubator/auction/auctiontypes"
	"github.com/cloudfoundry-incubator/auction/simulation/visualization"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
)

type StartAuctionCommunicator func(auctiontypes.StartAuctionRequest) (auctiontypes.StartAuctionResult, error)
type StopAuctionCommunicator func(auctiontypes.StopAuctionRequest) (auctiontypes.StopAuctionResult, error)

type AuctionDistributor struct {
	client            auctiontypes.SimulationRepPoolClient
	startCommunicator StartAuctionCommunicator
	stopCommunicator  StopAuctionCommunicator
}

func NewInProcessAuctionDistributor(client auctiontypes.SimulationRepPoolClient) *AuctionDistributor {
	auctionRunner := auctionrunner.New(client)
	return &AuctionDistributor{
		client: client,
		startCommunicator: func(auctionRequest auctiontypes.StartAuctionRequest) (auctiontypes.StartAuctionResult, error) {
			return auctionRunner.RunLRPStartAuction(auctionRequest)
		},
		stopCommunicator: func(auctionRequest auctiontypes.StopAuctionRequest) (auctiontypes.StopAuctionResult, error) {
			return auctionRunner.RunLRPStopAuction(auctionRequest)
		},
	}
}

func NewRemoteAuctionDistributor(hosts []string, client auctiontypes.SimulationRepPoolClient) *AuctionDistributor {
	return &AuctionDistributor{
		client:            client,
		startCommunicator: newHttpRemoteAuctions(hosts).RemoteStartAuction,
		stopCommunicator:  newHttpRemoteAuctions(hosts).RemoteStopAuction,
	}
}

func (ad *AuctionDistributor) HoldAuctionsFor(instances []models.LRPStartAuction, representatives []string, rules auctiontypes.StartAuctionRules, maxConcurrent int) *visualization.Report {
	fmt.Printf("\nStarting Auctions\n\n")
	bar := pb.StartNew(len(instances))

	t := time.Now()
	semaphore := make(chan bool, maxConcurrent)
	c := make(chan auctiontypes.StartAuctionResult)
	for _, inst := range instances {
		go func(inst models.LRPStartAuction) {
			semaphore <- true
			result, _ := ad.startCommunicator(auctiontypes.StartAuctionRequest{
				LRPStartAuction: inst,
				RepGuids:        representatives,
				Rules:           rules,
			})
			result.Duration = time.Since(t)
			c <- result
			<-semaphore
		}(inst)
	}

	results := []auctiontypes.StartAuctionResult{}
	for _ = range instances {
		results = append(results, <-c)
		bar.Increment()
	}

	bar.Finish()

	duration := time.Since(t)
	report := &visualization.Report{
		RepGuids:        representatives,
		AuctionResults:  results,
		InstancesByRep:  visualization.FetchAndSortInstances(ad.client, representatives),
		AuctionDuration: duration,
	}

	return report
}

func (ad *AuctionDistributor) HoldStopAuctions(stopAuctions []models.LRPStopAuction, representatives []string) []auctiontypes.StopAuctionResult {
	t := time.Now()

	c := make(chan auctiontypes.StopAuctionResult)
	for _, stopAuction := range stopAuctions {
		go func(stopAuction models.LRPStopAuction) {
			result, _ := ad.stopCommunicator(auctiontypes.StopAuctionRequest{
				LRPStopAuction: stopAuction,
				RepGuids:       representatives,
			})
			result.Duration = time.Since(t)
			c <- result
		}(stopAuction)
	}

	results := []auctiontypes.StopAuctionResult{}
	for _ = range stopAuctions {
		results = append(results, <-c)
	}

	return results
}
<commit_msg>switch to worker-pool approach for simulated auctiondistributor; add opt-in logging<commit_after>package auctiondistributor

import (
	"fmt"
	"runtime"
	"time"

	"github.com/onsi/ginkgo"

	"github.com/cheggaaa/pb"
	"github.com/cloudfoundry-incubator/auction/auctionrunner"
	"github.com/cloudfoundry-incubator/auction/auctiontypes"
	"github.com/cloudfoundry-incubator/auction/simulation/visualization"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
)

type StartAuctionCommunicator func(auctiontypes.StartAuctionRequest) (auctiontypes.StartAuctionResult, error)
type StopAuctionCommunicator func(auctiontypes.StopAuctionRequest) (auctiontypes.StopAuctionResult, error)

type AuctionDistributor struct {
	client            auctiontypes.SimulationRepPoolClient
	startCommunicator StartAuctionCommunicator
	stopCommunicator  StopAuctionCommunicator
}

func NewInProcessAuctionDistributor(client auctiontypes.SimulationRepPoolClient) *AuctionDistributor {
	auctionRunner := auctionrunner.New(client)
	return &AuctionDistributor{
		client: client,
		startCommunicator: func(auctionRequest auctiontypes.StartAuctionRequest) (auctiontypes.StartAuctionResult, error) {
			return auctionRunner.RunLRPStartAuction(auctionRequest)
		},
		stopCommunicator: func(auctionRequest auctiontypes.StopAuctionRequest) (auctiontypes.StopAuctionResult, error) {
			return auctionRunner.RunLRPStopAuction(auctionRequest)
		},
	}
}

func NewRemoteAuctionDistributor(hosts []string, client auctiontypes.SimulationRepPoolClient) *AuctionDistributor {
	return &AuctionDistributor{
		client:            client,
		startCommunicator: newHttpRemoteAuctions(hosts).RemoteStartAuction,
		stopCommunicator:  newHttpRemoteAuctions(hosts).RemoteStopAuction,
	}
}

func (ad *AuctionDistributor) HoldAuctionsFor(instances []models.LRPStartAuction, representatives []string, rules auctiontypes.StartAuctionRules, maxConcurrent int) *visualization.Report {
	fmt.Printf("\nStarting Auctions\n\n")
	bar := pb.StartNew(len(instances))

	t := time.Now()
	workChan := make(chan models.LRPStartAuction)
	resultsChan := make(chan auctiontypes.StartAuctionResult, len(instances))

	for i := 0; i < maxConcurrent; i++ {
		go func(i int) {
			n := 0
			for inst := range workChan {
				n++
				startTime := time.Now()
				result, _ := ad.startCommunicator(auctiontypes.StartAuctionRequest{
					LRPStartAuction: inst,
					RepGuids:        representatives,
					Rules:           rules,
				})
				result.Duration = time.Since(t)
				resultsChan <- result
				fmt.Fprintln(ginkgo.GinkgoWriter, "Finished", inst.InstanceGuid, "on", i, "processed", n, "took:", time.Since(startTime), "communications:", result.NumCommunications, "numrounds:", result.NumRounds, "goroutines:", runtime.NumGoroutine(), "length:", len(resultsChan), "have-run:", n)
			}
			fmt.Fprintln(ginkgo.GinkgoWriter, i, "is gone - processed:", n)
		}(i)
	}

	for _, instance := range instances {
		workChan <- instance
	}

	close(workChan)

	results := []auctiontypes.StartAuctionResult{}
	for _ = range instances {
		results = append(results, <-resultsChan)
		bar.Increment()
	}

	bar.Finish()

	duration := time.Since(t)
	report := &visualization.Report{
		RepGuids:        representatives,
		AuctionResults:  results,
		InstancesByRep:  visualization.FetchAndSortInstances(ad.client, representatives),
		AuctionDuration: duration,
	}

	return report
}

func (ad *AuctionDistributor) HoldStopAuctions(stopAuctions []models.LRPStopAuction, representatives []string) []auctiontypes.StopAuctionResult {
	t := time.Now()

	c := make(chan auctiontypes.StopAuctionResult)
	for _, stopAuction := range stopAuctions {
		go func(stopAuction models.LRPStopAuction) {
			result, _ := ad.stopCommunicator(auctiontypes.StopAuctionRequest{
				LRPStopAuction: stopAuction,
				RepGuids:       representatives,
			})
			result.Duration = time.Since(t)
			c <- result
		}(stopAuction)
	}

	results := []auctiontypes.StopAuctionResult{}
	for _ = range stopAuctions {
		results = append(results, <-c)
	}

	return results
}
<|endoftext|>


<commit_before>package git

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"

	. "github.com/polydawn/go-errcat"
	"gopkg.in/src-d/go-git.v4/plumbing/filemode"
	"gopkg.in/src-d/go-git.v4/plumbing/object"

	"go.polydawn.net/go-timeless-api"
	"go.polydawn.net/go-timeless-api/rio"
	"go.polydawn.net/go-timeless-api/util"
	"go.polydawn.net/rio/config"
	"go.polydawn.net/rio/fs"
	"go.polydawn.net/rio/fs/osfs"
	"go.polydawn.net/rio/fsOp"
	"go.polydawn.net/rio/transmat/mixins/cache"
	"go.polydawn.net/rio/transmat/mixins/filters"
	"go.polydawn.net/rio/transmat/mixins/fshash"
)

var (
	_ rio.UnpackFunc = Unpack
)

func Unpack(
	ctx context.Context, // Long-running call.  Cancellable.
	wareID api.WareID, // What wareID to fetch for unpacking.
	path string, // Where to unpack the fileset (absolute path).
	filt api.FilesetFilters, // Optionally: filters we should apply while unpacking.
	placementMode rio.PlacementMode, // Optionally: a placement mode (default is "copy").
	warehouses []api.WarehouseAddr, // Warehouses we can try to fetch from.
	mon rio.Monitor, // Optionally: callbacks for progress monitoring.
) (_ api.WareID, err error) {
	if mon.Chan != nil {
		defer close(mon.Chan)
	}
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	// Sanitize arguments.
	if wareID.Type != PackType {
		return api.WareID{}, Errorf(rio.ErrUsage, "this transmat implementation only supports packtype %q (not %q)", PackType, wareID.Type)
	}
	if placementMode == "" {
		placementMode = rio.Placement_Copy
	}
	// Wrap the direct unpack func with cache behavior; call that.
	return cache.Lrn2Cache(
		osfs.New(config.GetCacheBasePath()),
		unpack,
	)(ctx, wareID, path, filt, placementMode, warehouses, mon)
}

func unpack(
	ctx context.Context,
	wareID api.WareID,
	path string,
	filt api.FilesetFilters,
	placementMode rio.PlacementMode,
	warehouses []api.WarehouseAddr,
	mon rio.Monitor,
) (_ api.WareID, err error) {
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	// Sanitize arguments.
	path2 := fs.MustAbsolutePath(path)
	filt2, err := apiutil.ProcessFilters(filt, apiutil.FilterPurposeUnpack)
	if err != nil {
		return api.WareID{}, Errorf(rio.ErrUsage, "invalid filter specification: %s", err)
	}

	// Pick a warehouse and get a reader.
	//  This is a *very* expensive operation for git.  It's less
	//  of "pick a warehouse" and more "download the whole thing and hope we
	//  get what we wanted" (which is very ironic for a system that has
	//  a CAS system on its inside, yes).
	whCtrl, err := pick(ctx,
		wareID,
		warehouses,
		osfs.New(config.GetCacheBasePath().Join(fs.MustRelPath("git/objs"))),
		mon,
	)
	if err != nil {
		return api.WareID{}, err
	}
	tr, err := whCtrl.GetTree(wareID.Hash)
	if err != nil {
		panic(err)
	}
	tw := object.NewTreeWalker(tr, true, nil)

	// Construct filesystem wrapper to use for all our ops.
	afs := osfs.New(path2)

	// Make the root dir.  Git doesn't have metadata for the tree root.
	conjuredFmeta := fshash.DefaultDirMetadata()
	filters.Apply(filt2, &conjuredFmeta)
	if err := fsOp.PlaceFile(afs, conjuredFmeta, nil, filt2.SkipChown); err != nil {
		return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
	}

	// Extract.
	// Iterate over each entry, mutating filesystem as we go.
	for {
		fmeta := fs.Metadata{}
		name, te, err := tw.Next()

		// Check for done.
		if err == io.EOF {
			break // sucess!  end of archive.
		}
		if err != nil {
			return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
		}
		if ctx.Err() != nil {
			return api.WareID{}, Errorf(rio.ErrCancelled, "cancelled")
		}
		//fmt.Fprintf(os.Stderr, "walking git tree %s -- %#v\n", name, te)

		// Reshuffle metainfo to our default format.
		fmeta.Name = fs.MustRelPath(name)
		switch te.Mode {
		case filemode.Dir:
			fmeta.Type = fs.Type_Dir
			fmeta.Perms = 0755
		case filemode.Regular:
			fmeta.Type = fs.Type_File
			fmeta.Perms = 0644
		case filemode.Executable:
			fmeta.Type = fs.Type_File
			fmeta.Perms = 0755
		case filemode.Symlink:
			fmeta.Type = fs.Type_Symlink
			fmeta.Perms = 0644
			// Hang on, extracting a symlink is actually rough.
			tf, err := tr.TreeEntryFile(&te)
			if err != nil {
				return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
			}
			reader, err := tf.Blob.Reader()
			if err != nil {
				return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
			}
			blob, err := ioutil.ReadAll(reader)
			if err != nil {
				return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
			}
			fmeta.Linkname = string(blob)
		case filemode.Submodule:
			// TODO
			continue
		case filemode.Empty:
			fallthrough
		case filemode.Deprecated:
			fallthrough
		default:
			panic(fmt.Errorf("unknown git filemode %#v", te.Mode))
		}
		fmeta.Mtime = apiutil.DefaultMtime // git doesn't have time info

		// Apply filters.
		filters.Apply(filt2, &fmeta)

		// Place the file.
		switch fmeta.Type {
		case fs.Type_File:
			tf, err := tr.TreeEntryFile(&te)
			if err != nil {
				return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
			}
			reader, err := tf.Blob.Reader()
			if err != nil {
				return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
			}
			if err := fsOp.PlaceFile(afs, fmeta, reader, filt2.SkipChown); err != nil {
				return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
			}
			reader.Close()
		default:
			if err := fsOp.PlaceFile(afs, fmeta, nil, filt2.SkipChown); err != nil {
				return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
			}
		}
	}

	// Cleanup dir times with a post-order traversal over the bucket.
	//  Files and dirs placed inside dirs cause the parent's mtime to update, so we have to re-pave them.
	// TODO we don't have the state kept for this

	// That's it.  Checkout should have already checked the hash, so we just return it.
	return wareID, nil
}
<commit_msg>git: implement correct determistic dir times.<commit_after>package git

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"

	. "github.com/polydawn/go-errcat"
	"gopkg.in/src-d/go-git.v4/plumbing/filemode"
	"gopkg.in/src-d/go-git.v4/plumbing/object"

	"go.polydawn.net/go-timeless-api"
	"go.polydawn.net/go-timeless-api/rio"
	"go.polydawn.net/go-timeless-api/util"
	"go.polydawn.net/rio/config"
	"go.polydawn.net/rio/fs"
	"go.polydawn.net/rio/fs/osfs"
	"go.polydawn.net/rio/fsOp"
	"go.polydawn.net/rio/transmat/mixins/cache"
	"go.polydawn.net/rio/transmat/mixins/filters"
	"go.polydawn.net/rio/transmat/mixins/fshash"
)

var (
	_ rio.UnpackFunc = Unpack
)

func Unpack(
	ctx context.Context, // Long-running call.  Cancellable.
	wareID api.WareID, // What wareID to fetch for unpacking.
	path string, // Where to unpack the fileset (absolute path).
	filt api.FilesetFilters, // Optionally: filters we should apply while unpacking.
	placementMode rio.PlacementMode, // Optionally: a placement mode (default is "copy").
	warehouses []api.WarehouseAddr, // Warehouses we can try to fetch from.
	mon rio.Monitor, // Optionally: callbacks for progress monitoring.
) (_ api.WareID, err error) {
	if mon.Chan != nil {
		defer close(mon.Chan)
	}
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	// Sanitize arguments.
	if wareID.Type != PackType {
		return api.WareID{}, Errorf(rio.ErrUsage, "this transmat implementation only supports packtype %q (not %q)", PackType, wareID.Type)
	}
	if placementMode == "" {
		placementMode = rio.Placement_Copy
	}
	// Wrap the direct unpack func with cache behavior; call that.
	return cache.Lrn2Cache(
		osfs.New(config.GetCacheBasePath()),
		unpack,
	)(ctx, wareID, path, filt, placementMode, warehouses, mon)
}

func unpack(
	ctx context.Context,
	wareID api.WareID,
	path string,
	filt api.FilesetFilters,
	placementMode rio.PlacementMode,
	warehouses []api.WarehouseAddr,
	mon rio.Monitor,
) (_ api.WareID, err error) {
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	// Sanitize arguments.
	path2 := fs.MustAbsolutePath(path)
	filt2, err := apiutil.ProcessFilters(filt, apiutil.FilterPurposeUnpack)
	if err != nil {
		return api.WareID{}, Errorf(rio.ErrUsage, "invalid filter specification: %s", err)
	}

	// Pick a warehouse and get a reader.
	//  This is a *very* expensive operation for git.  It's less
	//  of "pick a warehouse" and more "download the whole thing and hope we
	//  get what we wanted" (which is very ironic for a system that has
	//  a CAS system on its inside, yes).
	whCtrl, err := pick(ctx,
		wareID,
		warehouses,
		osfs.New(config.GetCacheBasePath().Join(fs.MustRelPath("git/objs"))),
		mon,
	)
	if err != nil {
		return api.WareID{}, err
	}
	tr, err := whCtrl.GetTree(wareID.Hash)
	if err != nil {
		panic(err)
	}
	tw := object.NewTreeWalker(tr, true, nil)

	// Construct filesystem wrapper to use for all our ops.
	afs := osfs.New(path2)

	// Make the root dir.  Git doesn't have metadata for the tree root.
	conjuredFmeta := fshash.DefaultDirMetadata()
	filters.Apply(filt2, &conjuredFmeta)
	if err := fsOp.PlaceFile(afs, conjuredFmeta, nil, filt2.SkipChown); err != nil {
		return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
	}

	// Extract.
	// Iterate over each entry, mutating filesystem as we go.
	dirs := make([]fs.RelPath, 1, 200) // Keep for dir time repair at end.
	dirs[0] = fs.RelPath{}
	for {
		fmeta := fs.Metadata{}
		name, te, err := tw.Next()

		// Check for done.
		if err == io.EOF {
			break // sucess!  end of archive.
		}
		if err != nil {
			return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
		}
		if ctx.Err() != nil {
			return api.WareID{}, Errorf(rio.ErrCancelled, "cancelled")
		}
		//fmt.Fprintf(os.Stderr, "walking git tree %s -- %#v\n", name, te)

		// Reshuffle metainfo to our default format.
		fmeta.Name = fs.MustRelPath(name)
		switch te.Mode {
		case filemode.Dir:
			fmeta.Type = fs.Type_Dir
			fmeta.Perms = 0755
			dirs = append(dirs, fmeta.Name)
		case filemode.Regular:
			fmeta.Type = fs.Type_File
			fmeta.Perms = 0644
		case filemode.Executable:
			fmeta.Type = fs.Type_File
			fmeta.Perms = 0755
		case filemode.Symlink:
			fmeta.Type = fs.Type_Symlink
			fmeta.Perms = 0644
			// Hang on, extracting a symlink is actually rough.
			tf, err := tr.TreeEntryFile(&te)
			if err != nil {
				return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
			}
			reader, err := tf.Blob.Reader()
			if err != nil {
				return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
			}
			blob, err := ioutil.ReadAll(reader)
			if err != nil {
				return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
			}
			fmeta.Linkname = string(blob)
		case filemode.Submodule:
			// TODO
			continue
		case filemode.Empty:
			fallthrough
		case filemode.Deprecated:
			fallthrough
		default:
			panic(fmt.Errorf("unknown git filemode %#v", te.Mode))
		}
		fmeta.Mtime = apiutil.DefaultMtime // git doesn't have time info

		// Apply filters.
		filters.Apply(filt2, &fmeta)

		// Place the file.
		switch fmeta.Type {
		case fs.Type_File:
			tf, err := tr.TreeEntryFile(&te)
			if err != nil {
				return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
			}
			reader, err := tf.Blob.Reader()
			if err != nil {
				return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt git tree: %s", err)
			}
			if err := fsOp.PlaceFile(afs, fmeta, reader, filt2.SkipChown); err != nil {
				return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
			}
			reader.Close()
		default:
			if err := fsOp.PlaceFile(afs, fmeta, nil, filt2.SkipChown); err != nil {
				return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
			}
		}
	}

	// Cleanup dir times with a post-order traversal over the bucket.
	//  Files and dirs placed inside dirs cause the parent's mtime to update, so we have to re-pave them.
	for i := len(dirs) - 1; i >= 0; i-- {
		if err := afs.SetTimesNano(dirs[i], fs.DefaultAtime, fs.DefaultAtime); err != nil {
			return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
		}
	}

	// That's it.  Checkout should have already checked the hash, so we just return it.
	return wareID, nil
}
<|endoftext|>


<commit_before>package unionfs

import (
	"fmt"
	"github.com/hanwen/go-fuse/fuse"
	"log"
	"os"
	"path/filepath"
	"strings"
	"syscall"
	"sync"
	"time"
)

// Creates unions for all files under a given directory,
// walking the tree and looking for directories D which have a
// D/READONLY symlink.
//
// A union for A/B/C will placed under directory A-B-C.
type AutoUnionFs struct {
	fuse.DefaultFileSystem

	lock             sync.RWMutex
	knownFileSystems map[string]*UnionFs
	nameRootMap      map[string]string
	root             string

	connector *fuse.FileSystemConnector

	options *AutoUnionFsOptions
}

type AutoUnionFsOptions struct {
	UnionFsOptions
	fuse.FileSystemOptions

	// If set, run updateKnownFses() after mounting.
	UpdateOnMount bool
}

const (
	_READONLY    = "READONLY"
	_STATUS      = "status"
	_CONFIG      = "config"
	_ROOT        = "root"
	_VERSION     = "gounionfs_version"
	_SCAN_CONFIG = ".scan_config"
)

func NewAutoUnionFs(directory string, options AutoUnionFsOptions) *AutoUnionFs {
	a := new(AutoUnionFs)
	a.knownFileSystems = make(map[string]*UnionFs)
	a.nameRootMap = make(map[string]string)
	a.options = &options
	directory, err := filepath.Abs(directory)
	if err != nil {
		panic("filepath.Abs returned err")
	}
	a.root = directory
	return a
}

func (me *AutoUnionFs) Mount(connector *fuse.FileSystemConnector) {
	me.connector = connector
	if me.options.UpdateOnMount {
		time.AfterFunc(0.1e9, func() { me.updateKnownFses() })
	}
}

func (me *AutoUnionFs) addAutomaticFs(roots []string) {
	relative := strings.TrimLeft(strings.Replace(roots[0], me.root, "", -1), "/")
	name := strings.Replace(relative, "/", "-", -1)

	if me.getUnionFs(name) == nil {
		me.addFs(name, roots)
	}
}

func (me *AutoUnionFs) createFs(name string, roots []string) fuse.Status {
	me.lock.Lock()
	defer me.lock.Unlock()

	for workspace, root := range me.nameRootMap {
		if root == roots[0] && workspace != name {
			log.Printf("Already have a union FS for directory %s in workspace %s",
				roots[0], workspace)
			return fuse.EBUSY
		}
	}

	ufs := me.knownFileSystems[name]
	if ufs != nil {
		log.Println("Already have a workspace:", name)
		return fuse.EBUSY
	}

	ufs, err := NewUnionFsFromRoots(roots, &me.options.UnionFsOptions, true)
	if err != nil {
		log.Println("Could not create UnionFs:", err)
		return fuse.EPERM
	}

	log.Printf("Adding workspace %v for roots %v", name, ufs.Name())
	code := me.connector.Mount("/"+name, ufs, &me.options.FileSystemOptions)
	if code.Ok() {
		me.knownFileSystems[name] = ufs
		me.nameRootMap[name] = roots[0]
	}
	return code
}

func (me *AutoUnionFs) rmFs(name string) (code fuse.Status) {
	me.lock.Lock()
	defer me.lock.Unlock()

	fs := me.knownFileSystems[name]
	if fs == nil {
		return fuse.ENOENT
	}

	code = me.connector.Unmount(name)
	if code.Ok() {
		me.knownFileSystems[name] = nil, false
		me.nameRootMap[name] = "", false
	} else {
		log.Printf("Unmount failed for %s.  Code %v", name, code)
	}

	return code
}

func (me *AutoUnionFs) addFs(name string, roots []string) (code fuse.Status) {
	if name == _CONFIG || name == _STATUS || name == _SCAN_CONFIG {
		log.Println("Illegal name for overlay", roots)
		return fuse.EINVAL
	}
	return me.createFs(name, roots)
}

// TODO - should hide these methods.
func (me *AutoUnionFs) VisitDir(path string, f *os.FileInfo) bool {
	roots := me.getRoots(path)
	if roots != nil {
		me.addAutomaticFs(roots)
	}
	return true
}

func (me *AutoUnionFs) getRoots(path string) []string {
	ro := filepath.Join(path, _READONLY)
	fi, err := os.Lstat(ro)
	fiDir, errDir := os.Stat(ro)
	if err == nil && errDir == nil && fi.IsSymlink() && fiDir.IsDirectory() {
		// TODO - should recurse and chain all READONLYs
		// together.
		return []string{path, ro}
	}
	return nil
}

func (me *AutoUnionFs) VisitFile(path string, f *os.FileInfo) {

}

func (me *AutoUnionFs) updateKnownFses() {
	log.Println("Looking for new filesystems")
	filepath.Walk(me.root, me, nil)
	log.Println("Done looking")
}

func (me *AutoUnionFs) Readlink(path string) (out string, code fuse.Status) {
	comps := strings.Split(path, fuse.SeparatorString, -1)
	if comps[0] == _STATUS && comps[1] == _ROOT {
		return me.root, fuse.OK
	}

	if comps[0] != _CONFIG {
		return "", fuse.ENOENT
	}
	name := comps[1]
	me.lock.RLock()
	defer me.lock.RUnlock()

	root, ok := me.nameRootMap[name]
	if ok {
		return root, fuse.OK
	}
	return "", fuse.ENOENT
}

func (me *AutoUnionFs) getUnionFs(name string) *UnionFs {
	me.lock.RLock()
	defer me.lock.RUnlock()
	return me.knownFileSystems[name]
}

func (me *AutoUnionFs) Symlink(pointedTo string, linkName string) (code fuse.Status) {
	comps := strings.Split(linkName, "/", -1)
	if len(comps) != 2 {
		return fuse.EPERM
	}

	if comps[0] == _CONFIG {
		roots := me.getRoots(pointedTo)
		if roots == nil {
			return syscall.ENOTDIR
		}

		name := comps[1]
		return me.addFs(name, roots)
	}
	return fuse.EPERM
}

func (me *AutoUnionFs) Unlink(path string) (code fuse.Status) {
	comps := strings.Split(path, "/", -1)
	if len(comps) != 2 {
		return fuse.EPERM
	}

	if comps[0] == _CONFIG && comps[1] != _SCAN_CONFIG {
		code = me.rmFs(comps[1])
	} else {
		code = fuse.ENOENT
	}
	return code
}

// Must define this, because ENOSYS will suspend all GetXAttr calls.
func (me *AutoUnionFs) GetXAttr(name string, attr string) ([]byte, fuse.Status) {
	return nil, syscall.ENODATA
}

func (me *AutoUnionFs) GetAttr(path string) (*os.FileInfo, fuse.Status) {
	if path == "" || path == _CONFIG || path == _STATUS {
		a := &os.FileInfo{
			Mode: fuse.S_IFDIR | 0755,
		}
		return a, fuse.OK
	}

	if path == filepath.Join(_STATUS, _VERSION) {
		a := &os.FileInfo{
			Mode: fuse.S_IFREG | 0644,
			Size: int64(len(fuse.Version())),
		}
		return a, fuse.OK
	}

	if path == filepath.Join(_STATUS, _ROOT) {
		a := &os.FileInfo{
			Mode: syscall.S_IFLNK | 0644,
		}
		return a, fuse.OK
	}

	if path == filepath.Join(_CONFIG, _SCAN_CONFIG) {
		a := &os.FileInfo{
			Mode: fuse.S_IFREG | 0644,
		}
		return a, fuse.OK
	}
	comps := strings.Split(path, fuse.SeparatorString, -1)

	if len(comps) > 1 && comps[0] == _CONFIG {
		fs := me.getUnionFs(comps[1])

		if fs == nil {
			return nil, fuse.ENOENT
		}

		a := &os.FileInfo{
			Mode: syscall.S_IFLNK | 0644,
		}
		return a, fuse.OK
	}

	if me.getUnionFs(path) != nil {
		return &os.FileInfo{
			Mode: fuse.S_IFDIR | 0755,
		},fuse.OK
	}

	return nil, fuse.ENOENT
}

func (me *AutoUnionFs) StatusDir() (stream chan fuse.DirEntry, status fuse.Status) {
	stream = make(chan fuse.DirEntry, 10)
	stream <- fuse.DirEntry{
		Name: _VERSION,
		Mode: fuse.S_IFREG | 0644,
	}
	stream <- fuse.DirEntry{
		Name: _ROOT,
		Mode: syscall.S_IFLNK | 0644,
	}

	close(stream)
	return stream, fuse.OK
}

func (me *AutoUnionFs) Open(path string, flags uint32) (fuse.File, fuse.Status) {
	if path == filepath.Join(_STATUS, _VERSION) {
		if flags&fuse.O_ANYWRITE != 0 {
			return nil, fuse.EPERM
		}
		return fuse.NewReadOnlyFile([]byte(fuse.Version())), fuse.OK
	}
	if path == filepath.Join(_CONFIG, _SCAN_CONFIG) {
		if flags&fuse.O_ANYWRITE != 0 {
			me.updateKnownFses()
		}
		return fuse.NewDevNullFile(), fuse.OK
	}
	return nil, fuse.ENOENT
}
func (me *AutoUnionFs) Truncate(name string, offset uint64) (code fuse.Status) {
	if name != filepath.Join(_CONFIG, _SCAN_CONFIG) {
		log.Println("Huh? Truncating unsupported write file", name)
		return fuse.EPERM
	}
	return fuse.OK
}

func (me *AutoUnionFs) OpenDir(name string) (stream chan fuse.DirEntry, status fuse.Status) {
	switch name {
	case _STATUS:
		return me.StatusDir()
	case _CONFIG:
	case "/":
		name = ""
	case "":
	default:
		panic(fmt.Sprintf("Don't know how to list dir %v", name))
	}

	me.lock.RLock()
	defer me.lock.RUnlock()

	stream = make(chan fuse.DirEntry, len(me.knownFileSystems)+5)
	if name == _CONFIG {
		for k, _ := range me.knownFileSystems {
			stream <- fuse.DirEntry{
				Name: k,
				Mode: syscall.S_IFLNK | 0644,
			}
		}
	}

	if name == "" {
		stream <- fuse.DirEntry{
			Name: _CONFIG,
			Mode: uint32(fuse.S_IFDIR | 0755),
		}
		stream <- fuse.DirEntry{
			Name: _STATUS,
			Mode: uint32(fuse.S_IFDIR | 0755),
		}
	}
	close(stream)
	return stream, status
}
<commit_msg>Change panic in AutoUnionFs in log, and return ENOSYS.<commit_after>package unionfs

import (
	"fmt"
	"github.com/hanwen/go-fuse/fuse"
	"log"
	"os"
	"path/filepath"
	"strings"
	"syscall"
	"sync"
	"time"
)

// Creates unions for all files under a given directory,
// walking the tree and looking for directories D which have a
// D/READONLY symlink.
//
// A union for A/B/C will placed under directory A-B-C.
type AutoUnionFs struct {
	fuse.DefaultFileSystem

	lock             sync.RWMutex
	knownFileSystems map[string]*UnionFs
	nameRootMap      map[string]string
	root             string

	connector *fuse.FileSystemConnector

	options *AutoUnionFsOptions
}

type AutoUnionFsOptions struct {
	UnionFsOptions
	fuse.FileSystemOptions

	// If set, run updateKnownFses() after mounting.
	UpdateOnMount bool
}

const (
	_READONLY    = "READONLY"
	_STATUS      = "status"
	_CONFIG      = "config"
	_ROOT        = "root"
	_VERSION     = "gounionfs_version"
	_SCAN_CONFIG = ".scan_config"
)

func NewAutoUnionFs(directory string, options AutoUnionFsOptions) *AutoUnionFs {
	a := new(AutoUnionFs)
	a.knownFileSystems = make(map[string]*UnionFs)
	a.nameRootMap = make(map[string]string)
	a.options = &options
	directory, err := filepath.Abs(directory)
	if err != nil {
		panic("filepath.Abs returned err")
	}
	a.root = directory
	return a
}

func (me *AutoUnionFs) Mount(connector *fuse.FileSystemConnector) {
	me.connector = connector
	if me.options.UpdateOnMount {
		time.AfterFunc(0.1e9, func() { me.updateKnownFses() })
	}
}

func (me *AutoUnionFs) addAutomaticFs(roots []string) {
	relative := strings.TrimLeft(strings.Replace(roots[0], me.root, "", -1), "/")
	name := strings.Replace(relative, "/", "-", -1)

	if me.getUnionFs(name) == nil {
		me.addFs(name, roots)
	}
}

func (me *AutoUnionFs) createFs(name string, roots []string) fuse.Status {
	me.lock.Lock()
	defer me.lock.Unlock()

	for workspace, root := range me.nameRootMap {
		if root == roots[0] && workspace != name {
			log.Printf("Already have a union FS for directory %s in workspace %s",
				roots[0], workspace)
			return fuse.EBUSY
		}
	}

	ufs := me.knownFileSystems[name]
	if ufs != nil {
		log.Println("Already have a workspace:", name)
		return fuse.EBUSY
	}

	ufs, err := NewUnionFsFromRoots(roots, &me.options.UnionFsOptions, true)
	if err != nil {
		log.Println("Could not create UnionFs:", err)
		return fuse.EPERM
	}

	log.Printf("Adding workspace %v for roots %v", name, ufs.Name())
	code := me.connector.Mount("/"+name, ufs, &me.options.FileSystemOptions)
	if code.Ok() {
		me.knownFileSystems[name] = ufs
		me.nameRootMap[name] = roots[0]
	}
	return code
}

func (me *AutoUnionFs) rmFs(name string) (code fuse.Status) {
	me.lock.Lock()
	defer me.lock.Unlock()

	fs := me.knownFileSystems[name]
	if fs == nil {
		return fuse.ENOENT
	}

	code = me.connector.Unmount(name)
	if code.Ok() {
		me.knownFileSystems[name] = nil, false
		me.nameRootMap[name] = "", false
	} else {
		log.Printf("Unmount failed for %s.  Code %v", name, code)
	}

	return code
}

func (me *AutoUnionFs) addFs(name string, roots []string) (code fuse.Status) {
	if name == _CONFIG || name == _STATUS || name == _SCAN_CONFIG {
		log.Println("Illegal name for overlay", roots)
		return fuse.EINVAL
	}
	return me.createFs(name, roots)
}

// TODO - should hide these methods.
func (me *AutoUnionFs) VisitDir(path string, f *os.FileInfo) bool {
	roots := me.getRoots(path)
	if roots != nil {
		me.addAutomaticFs(roots)
	}
	return true
}

func (me *AutoUnionFs) getRoots(path string) []string {
	ro := filepath.Join(path, _READONLY)
	fi, err := os.Lstat(ro)
	fiDir, errDir := os.Stat(ro)
	if err == nil && errDir == nil && fi.IsSymlink() && fiDir.IsDirectory() {
		// TODO - should recurse and chain all READONLYs
		// together.
		return []string{path, ro}
	}
	return nil
}

func (me *AutoUnionFs) VisitFile(path string, f *os.FileInfo) {

}

func (me *AutoUnionFs) updateKnownFses() {
	log.Println("Looking for new filesystems")
	filepath.Walk(me.root, me, nil)
	log.Println("Done looking")
}

func (me *AutoUnionFs) Readlink(path string) (out string, code fuse.Status) {
	comps := strings.Split(path, fuse.SeparatorString, -1)
	if comps[0] == _STATUS && comps[1] == _ROOT {
		return me.root, fuse.OK
	}

	if comps[0] != _CONFIG {
		return "", fuse.ENOENT
	}
	name := comps[1]
	me.lock.RLock()
	defer me.lock.RUnlock()

	root, ok := me.nameRootMap[name]
	if ok {
		return root, fuse.OK
	}
	return "", fuse.ENOENT
}

func (me *AutoUnionFs) getUnionFs(name string) *UnionFs {
	me.lock.RLock()
	defer me.lock.RUnlock()
	return me.knownFileSystems[name]
}

func (me *AutoUnionFs) Symlink(pointedTo string, linkName string) (code fuse.Status) {
	comps := strings.Split(linkName, "/", -1)
	if len(comps) != 2 {
		return fuse.EPERM
	}

	if comps[0] == _CONFIG {
		roots := me.getRoots(pointedTo)
		if roots == nil {
			return syscall.ENOTDIR
		}

		name := comps[1]
		return me.addFs(name, roots)
	}
	return fuse.EPERM
}

func (me *AutoUnionFs) Unlink(path string) (code fuse.Status) {
	comps := strings.Split(path, "/", -1)
	if len(comps) != 2 {
		return fuse.EPERM
	}

	if comps[0] == _CONFIG && comps[1] != _SCAN_CONFIG {
		code = me.rmFs(comps[1])
	} else {
		code = fuse.ENOENT
	}
	return code
}

// Must define this, because ENOSYS will suspend all GetXAttr calls.
func (me *AutoUnionFs) GetXAttr(name string, attr string) ([]byte, fuse.Status) {
	return nil, syscall.ENODATA
}

func (me *AutoUnionFs) GetAttr(path string) (*os.FileInfo, fuse.Status) {
	if path == "" || path == _CONFIG || path == _STATUS {
		a := &os.FileInfo{
			Mode: fuse.S_IFDIR | 0755,
		}
		return a, fuse.OK
	}

	if path == filepath.Join(_STATUS, _VERSION) {
		a := &os.FileInfo{
			Mode: fuse.S_IFREG | 0644,
			Size: int64(len(fuse.Version())),
		}
		return a, fuse.OK
	}

	if path == filepath.Join(_STATUS, _ROOT) {
		a := &os.FileInfo{
			Mode: syscall.S_IFLNK | 0644,
		}
		return a, fuse.OK
	}

	if path == filepath.Join(_CONFIG, _SCAN_CONFIG) {
		a := &os.FileInfo{
			Mode: fuse.S_IFREG | 0644,
		}
		return a, fuse.OK
	}
	comps := strings.Split(path, fuse.SeparatorString, -1)

	if len(comps) > 1 && comps[0] == _CONFIG {
		fs := me.getUnionFs(comps[1])

		if fs == nil {
			return nil, fuse.ENOENT
		}

		a := &os.FileInfo{
			Mode: syscall.S_IFLNK | 0644,
		}
		return a, fuse.OK
	}

	if me.getUnionFs(path) != nil {
		return &os.FileInfo{
			Mode: fuse.S_IFDIR | 0755,
		},fuse.OK
	}

	return nil, fuse.ENOENT
}

func (me *AutoUnionFs) StatusDir() (stream chan fuse.DirEntry, status fuse.Status) {
	stream = make(chan fuse.DirEntry, 10)
	stream <- fuse.DirEntry{
		Name: _VERSION,
		Mode: fuse.S_IFREG | 0644,
	}
	stream <- fuse.DirEntry{
		Name: _ROOT,
		Mode: syscall.S_IFLNK | 0644,
	}

	close(stream)
	return stream, fuse.OK
}

func (me *AutoUnionFs) Open(path string, flags uint32) (fuse.File, fuse.Status) {
	if path == filepath.Join(_STATUS, _VERSION) {
		if flags&fuse.O_ANYWRITE != 0 {
			return nil, fuse.EPERM
		}
		return fuse.NewReadOnlyFile([]byte(fuse.Version())), fuse.OK
	}
	if path == filepath.Join(_CONFIG, _SCAN_CONFIG) {
		if flags&fuse.O_ANYWRITE != 0 {
			me.updateKnownFses()
		}
		return fuse.NewDevNullFile(), fuse.OK
	}
	return nil, fuse.ENOENT
}
func (me *AutoUnionFs) Truncate(name string, offset uint64) (code fuse.Status) {
	if name != filepath.Join(_CONFIG, _SCAN_CONFIG) {
		log.Println("Huh? Truncating unsupported write file", name)
		return fuse.EPERM
	}
	return fuse.OK
}

func (me *AutoUnionFs) OpenDir(name string) (stream chan fuse.DirEntry, status fuse.Status) {
	switch name {
	case _STATUS:
		return me.StatusDir()
	case _CONFIG:
	case "/":
		name = ""
	case "":
	default:
		log.Sprintf("Argh! Don't know how to list dir %v", name)
		return fuse.ENOSYS
	}

	me.lock.RLock()
	defer me.lock.RUnlock()

	stream = make(chan fuse.DirEntry, len(me.knownFileSystems)+5)
	if name == _CONFIG {
		for k, _ := range me.knownFileSystems {
			stream <- fuse.DirEntry{
				Name: k,
				Mode: syscall.S_IFLNK | 0644,
			}
		}
	}

	if name == "" {
		stream <- fuse.DirEntry{
			Name: _CONFIG,
			Mode: uint32(fuse.S_IFDIR | 0755),
		}
		stream <- fuse.DirEntry{
			Name: _STATUS,
			Mode: uint32(fuse.S_IFDIR | 0755),
		}
	}
	close(stream)
	return stream, status
}
<|endoftext|>


<commit_before>// Copyright 2022 Google LLC
//
// 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 delete

import (
	"context"
	"fmt"
	"time"

	"github.com/GoogleContainerTools/kpt/internal/cmdsync"
	"github.com/GoogleContainerTools/kpt/internal/errors"
	"github.com/GoogleContainerTools/kpt/internal/util/porch"
	"github.com/spf13/cobra"
	coreapi "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	command = "cmdsync.delete"
	longMsg = `
kpt alpha sync delete NAME [flags]

Deletes a package RootSync resource.

Args:

NAME:
  Name of the sync resource. Required argument.

Flags:

--keep-auth-secret
  Do not delete the repository authentication secret, if it exists.
`
	// TODO: Find a better location for this git repo.
	emptyRepo       = "https://github.com/mortent/empty"
	emptyRepoBranch = "main"
	defaultTimeout  = 2 * time.Minute
)

var (
	rootSyncGVK = schema.GroupVersionKind{
		Group:   "configsync.gke.io",
		Version: "v1beta1",
		Kind:    "RootSync",
	}
	resourceGroupGVK = schema.GroupVersionKind{
		Group:   "kpt.dev",
		Version: "v1alpha1",
		Kind:    "ResourceGroup",
	}
)

func NewCommand(ctx context.Context, rcg *genericclioptions.ConfigFlags) *cobra.Command {
	return newRunner(ctx, rcg).Command
}

func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner {
	r := &runner{
		ctx: ctx,
		cfg: rcg,
	}
	c := &cobra.Command{
		Use:     "del REPOSITORY [flags]",
		Aliases: []string{"delete"},
		Short:   "Deletes the package RootSync.",
		Long:    longMsg,
		Example: "kpt alpha sync del deployed-blueprint",
		PreRunE: r.preRunE,
		RunE:    r.runE,
		Hidden:  porch.HidePorchCommands,
	}
	r.Command = c

	c.Flags().BoolVar(&r.keepSecret, "keep-auth-secret", false, "Keep the auth secret associated with the RootSync resource, if any")
	c.Flags().DurationVar(&r.timeout, "timeout", defaultTimeout, "How long to wait for Config Sync to delete package RootSync")

	return r
}

type runner struct {
	ctx     context.Context
	cfg     *genericclioptions.ConfigFlags
	client  client.WithWatch
	Command *cobra.Command

	// Flags
	keepSecret bool
	timeout    time.Duration
}

func (r *runner) preRunE(cmd *cobra.Command, args []string) error {
	const op errors.Op = command + ".preRunE"
	client, err := porch.CreateDynamicClient(r.cfg)
	if err != nil {
		return errors.E(op, err)
	}
	r.client = client
	return nil
}

func (r *runner) runE(cmd *cobra.Command, args []string) error {
	const op errors.Op = command + ".runE"

	if len(args) == 0 {
		return errors.E(op, fmt.Errorf("NAME is a required positional argument"))
	}

	name := args[0]
	namespace := cmdsync.RootSyncNamespace
	if *r.cfg.Namespace != "" {
		namespace = *r.cfg.Namespace
	}
	key := client.ObjectKey{
		Namespace: namespace,
		Name:      name,
	}
	rs := unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": rootSyncGVK.GroupVersion().String(),
			"kind":       rootSyncGVK.Kind,
		},
	}
	if err := r.client.Get(r.ctx, key, &rs); err != nil {
		return errors.E(op, fmt.Errorf("cannot get %s: %v", key, err))
	}

	git, found, err := unstructured.NestedMap(rs.Object, "spec", "git")
	if err != nil || !found {
		return errors.E(op, fmt.Errorf("couldn't find `spec.git`: %v", err))
	}

	git["repo"] = emptyRepo
	git["branch"] = emptyRepoBranch
	git["dir"] = ""
	git["revision"] = ""

	if err := unstructured.SetNestedMap(rs.Object, git, "spec", "git"); err != nil {
		return errors.E(op, err)
	}

	fmt.Println("Deleting synced resources..")
	if err := r.client.Update(r.ctx, &rs); err != nil {
		return errors.E(op, err)
	}

	if err := func() error {
		ctx, cancel := context.WithTimeout(r.ctx, r.timeout)
		defer cancel()

		if err := r.waitForRootSync(ctx, name, namespace); err != nil {
			return err
		}

		fmt.Println("Waiting for deleted resources to be removed..")
		if err := r.waitForResourceGroup(ctx, name, namespace); err != nil {
			return err
		}
		return nil
	}(); err != nil {
		// TODO: See if we can expose more information here about what might have prevented a package
		// from being deleted.
		e := fmt.Errorf("package %s failed to be deleted after %f seconds: %v", name, r.timeout.Seconds(), err)
		return errors.E(op, e)
	}

	if err := r.client.Delete(r.ctx, &rs); err != nil {
		return errors.E(op, err)
	}

	if r.keepSecret {
		return nil
	}

	secret := getSecretName(&rs)
	if secret == "" {
		return nil
	}

	if err := r.client.Delete(r.ctx, &coreapi.Secret{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Secret",
			APIVersion: coreapi.SchemeGroupVersion.Identifier(),
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      secret,
			Namespace: namespace,
		},
	}); err != nil {
		return errors.E(op, fmt.Errorf("failed to delete Secret %s: %w", secret, err))
	}

	fmt.Printf("Sync %s successfully deleted\n", name)
	return nil
}

func (r *runner) waitForRootSync(ctx context.Context, name string, namespace string) error {
	const op errors.Op = command + ".waitForRootSync"

	return r.waitForResource(ctx, resourceGroupGVK, name, namespace, func(u *unstructured.Unstructured) (bool, error) {
		res, err := status.Compute(u)
		if err != nil {
			return false, errors.E(op, err)
		}
		if res.Status == status.CurrentStatus {
			return true, nil
		}
		return false, nil
	})
}

func (r *runner) waitForResourceGroup(ctx context.Context, name string, namespace string) error {
	const op errors.Op = command + ".waitForResourceGroup"

	return r.waitForResource(ctx, resourceGroupGVK, name, namespace, func(u *unstructured.Unstructured) (bool, error) {
		resources, found, err := unstructured.NestedSlice(u.Object, "spec", "resources")
		if err != nil {
			return false, errors.E(op, err)
		}
		if !found {
			return true, nil
		}
		if len(resources) == 0 {
			return true, nil
		}
		return false, nil
	})
}

type ReconcileFunc func(*unstructured.Unstructured) (bool, error)

func (r *runner) waitForResource(ctx context.Context, gvk schema.GroupVersionKind, name, namespace string, reconcileFunc ReconcileFunc) error {
	const op errors.Op = command + ".waitForResource"

	u := unstructured.UnstructuredList{}
	u.SetGroupVersionKind(gvk)
	watch, err := r.client.Watch(r.ctx, &u)
	if err != nil {
		return errors.E(op, err)
	}
	defer watch.Stop()

	for {
		select {
		case ev, ok := <-watch.ResultChan():
			if !ok {
				return errors.E(op, fmt.Errorf("watch closed unexpectedly"))
			}
			if ev.Object == nil {
				continue
			}

			u := ev.Object.(*unstructured.Unstructured)

			if u.GetName() != name || u.GetNamespace() != namespace {
				continue
			}

			reconciled, err := reconcileFunc(u)
			if err != nil {
				return err
			}
			if reconciled {
				return nil
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

func getSecretName(repo *unstructured.Unstructured) string {
	name, _, _ := unstructured.NestedString(repo.Object, "spec", "git", "secretRef", "name")
	return name
}
<commit_msg>Clean up ResourceGroup with sync del (#3072)<commit_after>// Copyright 2022 Google LLC
//
// 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 delete

import (
	"context"
	"fmt"
	"time"

	"github.com/GoogleContainerTools/kpt/internal/cmdsync"
	"github.com/GoogleContainerTools/kpt/internal/errors"
	"github.com/GoogleContainerTools/kpt/internal/util/porch"
	"github.com/spf13/cobra"
	coreapi "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	command = "cmdsync.delete"
	longMsg = `
kpt alpha sync delete NAME [flags]

Deletes a package RootSync resource.

Args:

NAME:
  Name of the sync resource. Required argument.

Flags:

--keep-auth-secret
  Do not delete the repository authentication secret, if it exists.
`
	// TODO: Find a better location for this git repo.
	emptyRepo       = "https://github.com/mortent/empty"
	emptyRepoBranch = "main"
	defaultTimeout  = 2 * time.Minute
)

var (
	rootSyncGVK = schema.GroupVersionKind{
		Group:   "configsync.gke.io",
		Version: "v1beta1",
		Kind:    "RootSync",
	}
	resourceGroupGVK = schema.GroupVersionKind{
		Group:   "kpt.dev",
		Version: "v1alpha1",
		Kind:    "ResourceGroup",
	}
)

func NewCommand(ctx context.Context, rcg *genericclioptions.ConfigFlags) *cobra.Command {
	return newRunner(ctx, rcg).Command
}

func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner {
	r := &runner{
		ctx: ctx,
		cfg: rcg,
	}
	c := &cobra.Command{
		Use:     "del REPOSITORY [flags]",
		Aliases: []string{"delete"},
		Short:   "Deletes the package RootSync.",
		Long:    longMsg,
		Example: "kpt alpha sync del deployed-blueprint",
		PreRunE: r.preRunE,
		RunE:    r.runE,
		Hidden:  porch.HidePorchCommands,
	}
	r.Command = c

	c.Flags().BoolVar(&r.keepSecret, "keep-auth-secret", false, "Keep the auth secret associated with the RootSync resource, if any")
	c.Flags().DurationVar(&r.timeout, "timeout", defaultTimeout, "How long to wait for Config Sync to delete package RootSync")

	return r
}

type runner struct {
	ctx     context.Context
	cfg     *genericclioptions.ConfigFlags
	client  client.WithWatch
	Command *cobra.Command

	// Flags
	keepSecret bool
	timeout    time.Duration
}

func (r *runner) preRunE(cmd *cobra.Command, args []string) error {
	const op errors.Op = command + ".preRunE"
	client, err := porch.CreateDynamicClient(r.cfg)
	if err != nil {
		return errors.E(op, err)
	}
	r.client = client
	return nil
}

func (r *runner) runE(cmd *cobra.Command, args []string) error {
	const op errors.Op = command + ".runE"

	if len(args) == 0 {
		return errors.E(op, fmt.Errorf("NAME is a required positional argument"))
	}

	name := args[0]
	namespace := cmdsync.RootSyncNamespace
	if *r.cfg.Namespace != "" {
		namespace = *r.cfg.Namespace
	}
	key := client.ObjectKey{
		Namespace: namespace,
		Name:      name,
	}
	rs := unstructured.Unstructured{}
	rs.SetGroupVersionKind(rootSyncGVK)
	if err := r.client.Get(r.ctx, key, &rs); err != nil {
		return errors.E(op, fmt.Errorf("cannot get %s: %v", key, err))
	}

	git, found, err := unstructured.NestedMap(rs.Object, "spec", "git")
	if err != nil || !found {
		return errors.E(op, fmt.Errorf("couldn't find `spec.git`: %v", err))
	}

	git["repo"] = emptyRepo
	git["branch"] = emptyRepoBranch
	git["dir"] = ""
	git["revision"] = ""

	if err := unstructured.SetNestedMap(rs.Object, git, "spec", "git"); err != nil {
		return errors.E(op, err)
	}

	fmt.Println("Deleting synced resources..")
	if err := r.client.Update(r.ctx, &rs); err != nil {
		return errors.E(op, err)
	}

	if err := func() error {
		ctx, cancel := context.WithTimeout(r.ctx, r.timeout)
		defer cancel()

		if err := r.waitForRootSync(ctx, name, namespace); err != nil {
			return err
		}

		fmt.Println("Waiting for deleted resources to be removed..")
		if err := r.waitForResourceGroup(ctx, name, namespace); err != nil {
			return err
		}
		return nil
	}(); err != nil {
		// TODO: See if we can expose more information here about what might have prevented a package
		// from being deleted.
		e := fmt.Errorf("package %s failed to be deleted after %f seconds: %v", name, r.timeout.Seconds(), err)
		return errors.E(op, e)
	}

	if err := r.client.Delete(r.ctx, &rs); err != nil {
		return errors.E(op, fmt.Errorf("failed to clean up RootSync: %w", err))
	}

	rg := unstructured.Unstructured{}
	rg.SetGroupVersionKind(resourceGroupGVK)
	rg.SetName(rs.GetName())
	rg.SetNamespace(rs.GetNamespace())
	if err := r.client.Delete(r.ctx, &rg); err != nil {
		return errors.E(op, fmt.Errorf("failed to clean up ResourceGroup: %w", err))
	}

	if r.keepSecret {
		return nil
	}

	secret := getSecretName(&rs)
	if secret == "" {
		return nil
	}

	if err := r.client.Delete(r.ctx, &coreapi.Secret{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Secret",
			APIVersion: coreapi.SchemeGroupVersion.Identifier(),
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      secret,
			Namespace: namespace,
		},
	}); err != nil {
		return errors.E(op, fmt.Errorf("failed to delete Secret %s: %w", secret, err))
	}

	fmt.Printf("Sync %s successfully deleted\n", name)
	return nil
}

func (r *runner) waitForRootSync(ctx context.Context, name string, namespace string) error {
	const op errors.Op = command + ".waitForRootSync"

	return r.waitForResource(ctx, resourceGroupGVK, name, namespace, func(u *unstructured.Unstructured) (bool, error) {
		res, err := status.Compute(u)
		if err != nil {
			return false, errors.E(op, err)
		}
		if res.Status == status.CurrentStatus {
			return true, nil
		}
		return false, nil
	})
}

func (r *runner) waitForResourceGroup(ctx context.Context, name string, namespace string) error {
	const op errors.Op = command + ".waitForResourceGroup"

	return r.waitForResource(ctx, resourceGroupGVK, name, namespace, func(u *unstructured.Unstructured) (bool, error) {
		resources, found, err := unstructured.NestedSlice(u.Object, "spec", "resources")
		if err != nil {
			return false, errors.E(op, err)
		}
		if !found {
			return true, nil
		}
		if len(resources) == 0 {
			return true, nil
		}
		return false, nil
	})
}

type ReconcileFunc func(*unstructured.Unstructured) (bool, error)

func (r *runner) waitForResource(ctx context.Context, gvk schema.GroupVersionKind, name, namespace string, reconcileFunc ReconcileFunc) error {
	const op errors.Op = command + ".waitForResource"

	u := unstructured.UnstructuredList{}
	u.SetGroupVersionKind(gvk)
	watch, err := r.client.Watch(r.ctx, &u)
	if err != nil {
		return errors.E(op, err)
	}
	defer watch.Stop()

	for {
		select {
		case ev, ok := <-watch.ResultChan():
			if !ok {
				return errors.E(op, fmt.Errorf("watch closed unexpectedly"))
			}
			if ev.Object == nil {
				continue
			}

			u := ev.Object.(*unstructured.Unstructured)

			if u.GetName() != name || u.GetNamespace() != namespace {
				continue
			}

			reconciled, err := reconcileFunc(u)
			if err != nil {
				return err
			}
			if reconciled {
				return nil
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

func getSecretName(repo *unstructured.Unstructured) string {
	name, _, _ := unstructured.NestedString(repo.Object, "spec", "git", "secretRef", "name")
	return name
}
<|endoftext|>