<commit_before>// Copyright 2014 Rana Ian. All rights reserved.
// Use of this source code is governed by The MIT License
// found in the accompanying LICENSE file.

package ora

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

// DrvStmt is an Oracle statement associated with a session.
//
// DrvStmt wraps Stmt and is intended for use by the database/sql/driver package.
//
// DrvStmt implements the driver.Stmt interface.
type DrvStmt struct {
	stmt *Stmt
}

// checkIsOpen validates that the server is open.
func (ds *DrvStmt) checkIsOpen() error {
	if ds.stmt == nil {
		return er("DrvStmt is closed.")
	}
	return nil
}

// Close closes the SQL statement.
//
// Close is a member of the driver.Stmt interface.
func (ds *DrvStmt) Close() (err error) {
	ds.log(true)
	if err = ds.checkIsOpen(); err != nil {
		return errE(err)
	}
	err = ds.stmt.Close()
	if err != nil {
		return errE(err)
	}
	return nil
}

// NumInput returns the number of placeholders in a sql statement.
//
// NumInput is a member of the driver.Stmt interface.
func (ds *DrvStmt) NumInput() int {
	if ds.stmt == nil {
		return 0
	}
	return ds.stmt.NumInput()
}

// Exec executes an Oracle SQL statement on a server. Exec returns a driver.Result
// and a possible error.
//
// Exec is a member of the driver.Stmt interface.
func (ds *DrvStmt) Exec(values []driver.Value) (result driver.Result, err error) {
	ds.log(true)
	if err = ds.checkIsOpen(); err != nil {
		return nil, errE(err)
	}
	params := make([]interface{}, len(values))
	for n := range values {
		params[n] = values[n]
	}
	rowsAffected, lastInsertId, err := ds.stmt.exe(params, false)
	if err != nil {
		return nil, errE(err)
	}
	if rowsAffected == 0 {
		result = driver.ResultNoRows
	} else {
		result = &DrvExecResult{rowsAffected: rowsAffected, lastInsertId: lastInsertId}
	}
	return result, nil
}

// Query runs a SQL query on an Oracle server. Query returns driver.Rows and a
// possible error.
//
// Query is a member of the driver.Stmt interface.
func (ds *DrvStmt) Query(values []driver.Value) (driver.Rows, error) {
	ds.log(true)
	if err := ds.checkIsOpen(); err != nil {
		return nil, errE(err)
	}
	params := make([]interface{}, len(values))
	for n := range values {
		params[n] = values[n]
	}
	rset, err := ds.stmt.qry(params)
	if err != nil {
		return nil, errE(err)
	}
	return &DrvQueryResult{rset: rset}, nil
}

// sysName returns a string representing the DrvStmt.
func (ds *DrvStmt) sysName() string {
	if ds == nil {
		return "E_S_S_S_S_"
	}
	return ds.stmt.sysName() + fmt.Sprintf("S%v", ds.stmt.id)
}

// log writes a message with an DrvStmt system name and caller info.
func (ds *DrvStmt) log(enabled bool, v ...interface{}) {
	if !_drv.cfg.Log.IsEnabled(enabled) {
		return
	}
	if len(v) == 0 {
		_drv.cfg.Log.Logger.Infof("%v %v", ds.sysName(), callInfo(1))
	} else {
		_drv.cfg.Log.Logger.Infof("%v %v %v", ds.sysName(), callInfo(1), fmt.Sprint(v...))
	}
}
<commit_msg>don't use name return args if not needed<commit_after>// Copyright 2014 Rana Ian. All rights reserved.
// Use of this source code is governed by The MIT License
// found in the accompanying LICENSE file.

package ora

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

// DrvStmt is an Oracle statement associated with a session.
//
// DrvStmt wraps Stmt and is intended for use by the database/sql/driver package.
//
// DrvStmt implements the driver.Stmt interface.
type DrvStmt struct {
	stmt *Stmt
}

// checkIsOpen validates that the server is open.
func (ds *DrvStmt) checkIsOpen() error {
	if ds.stmt == nil {
		return er("DrvStmt is closed.")
	}
	return nil
}

// Close closes the SQL statement.
//
// Close is a member of the driver.Stmt interface.
func (ds *DrvStmt) Close() error {
	ds.log(true)
	if err := ds.checkIsOpen(); err != nil {
		return errE(err)
	}
	if err := ds.stmt.Close(); err != nil {
		return errE(err)
	}
	return nil
}

// NumInput returns the number of placeholders in a sql statement.
//
// NumInput is a member of the driver.Stmt interface.
func (ds *DrvStmt) NumInput() int {
	if ds.stmt == nil {
		return 0
	}
	return ds.stmt.NumInput()
}

// Exec executes an Oracle SQL statement on a server. Exec returns a driver.Result
// and a possible error.
//
// Exec is a member of the driver.Stmt interface.
func (ds *DrvStmt) Exec(values []driver.Value) (driver.Result, error) {
	ds.log(true)
	if err := ds.checkIsOpen(); err != nil {
		return nil, errE(err)
	}
	params := make([]interface{}, len(values))
	for n := range values {
		params[n] = values[n]
	}
	rowsAffected, lastInsertId, err := ds.stmt.exe(params, false)
	if err != nil {
		return nil, errE(err)
	}
	if rowsAffected == 0 {
		return driver.ResultNoRows, nil
	}
	return &DrvExecResult{rowsAffected: rowsAffected, lastInsertId: lastInsertId}, nil
}

// Query runs a SQL query on an Oracle server. Query returns driver.Rows and a
// possible error.
//
// Query is a member of the driver.Stmt interface.
func (ds *DrvStmt) Query(values []driver.Value) (driver.Rows, error) {
	ds.log(true)
	if err := ds.checkIsOpen(); err != nil {
		return nil, errE(err)
	}
	params := make([]interface{}, len(values))
	for n := range values {
		params[n] = values[n]
	}
	rset, err := ds.stmt.qry(params)
	if err != nil {
		return nil, errE(err)
	}
	return &DrvQueryResult{rset: rset}, nil
}

// sysName returns a string representing the DrvStmt.
func (ds *DrvStmt) sysName() string {
	if ds == nil {
		return "E_S_S_S_S_"
	}
	return ds.stmt.sysName() + fmt.Sprintf("S%v", ds.stmt.id)
}

// log writes a message with an DrvStmt system name and caller info.
func (ds *DrvStmt) log(enabled bool, v ...interface{}) {
	if !_drv.cfg.Log.IsEnabled(enabled) {
		return
	}
	if len(v) == 0 {
		_drv.cfg.Log.Logger.Infof("%v %v", ds.sysName(), callInfo(1))
	} else {
		_drv.cfg.Log.Logger.Infof("%v %v %v", ds.sysName(), callInfo(1), fmt.Sprint(v...))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/webhook"
	"github.com/Comcast/webpa-common/wrp"
	"github.com/stretchr/testify/assert"
	"net/http"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

type result struct {
	URL      string
	event    string
	transID  string
	deviceID string
}

// Make a simple RoundTrip implementation that let's me short-circuit the network
type swTransport struct {
	i       int32
	fn      func(*http.Request, int) (*http.Response, error)
	results []result
	mutex   sync.Mutex
}

func (t *swTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	atomic.AddInt32(&t.i, 1)

	r := result{URL: req.URL.String(),
		event:    req.Header.Get("X-Webpa-Event"),
		transID:  req.Header.Get("X-Webpa-Transaction-Id"),
		deviceID: req.Header.Get("X-Webpa-Device-Id"),
	}

	t.mutex.Lock()
	t.results = append(t.results, r)
	t.mutex.Unlock()

	resp := &http.Response{Status: "200 OK", StatusCode: 200}
	return resp, nil
}

func swGetLogger() logging.Logger {
	loggerFactory := logging.DefaultLoggerFactory{}
	logger, _ := loggerFactory.NewLogger("test")

	return logger
}

func TestInvalidLinger(t *testing.T) {
	sw, err := SenderWrapperFactory{
		NumWorkersPerSender: 10,
		QueueSizePerSender:  10,
		CutOffPeriod:        30 * time.Second,
		Logger:              swGetLogger(),
		Linger:              0 * time.Second,
		ProfilerFactory: ServerProfilerFactory{
			Frequency: 10,
			Duration:  6,
			QueueSize: 100,
		},
	}.New()

	assert := assert.New(t)
	assert.Nil(sw)
	assert.NotNil(err)
}

func TestSwSimple(t *testing.T) {
	assert := assert.New(t)

	wrpMessage := wrp.SimpleRequestResponse{
		Source:          "mac:112233445566",
		Destination:     "wrp",
		TransactionUUID: "12345",
	}

	var buffer bytes.Buffer
	encoder := wrp.NewEncoder(&buffer, wrp.Msgpack)
	err := encoder.Encode(&wrpMessage)
	assert.Nil(err)

	iot := CaduceusRequest{
		Payload:     []byte("Hello, world."),
		ContentType: "application/json",
		TargetURL:   "http://foo.com/api/v2/notification/device/mac:112233445566/event/iot",
	}
	test := CaduceusRequest{
		Payload:     []byte("Hello, world."),
		ContentType: "application/json",
		TargetURL:   "http://foo.com/api/v2/notification/device/mac:112233445566/event/test",
	}
	wrp := CaduceusRequest{
		Payload:     buffer.Bytes(),
		ContentType: "application/wrp",
		TargetURL:   "http://foo.com/api/v2/notification/device/mac:112233445566/event/wrp",
	}

	trans := &swTransport{}

	sw, err := SenderWrapperFactory{
		NumWorkersPerSender: 10,
		QueueSizePerSender:  10,
		CutOffPeriod:        30 * time.Second,
		Client:              &http.Client{Transport: trans},
		Logger:              swGetLogger(),
		Linger:              1 * time.Second,
		ProfilerFactory: ServerProfilerFactory{
			Frequency: 10,
			Duration:  6,
			QueueSize: 100,
		},
	}.New()

	assert.Nil(err)
	assert.NotNil(sw)

	// No listeners

	sw.Queue(iot)
	sw.Queue(iot)
	sw.Queue(iot)

	assert.Equal(int32(0), trans.i)

	w1 := webhook.W{
		Duration:   6 * time.Second,
		Until:      time.Now().Add(6 * time.Second),
		Events:     []string{"iot"},
	}
	w1.Config.URL         = "http://localhost:8888/foo"
	w1.Config.ContentType = "application/json"
	w1.Matcher.DeviceId = []string{"mac:112233445566"}

	w2 := webhook.W{
		Duration:   4 * time.Second,
		Until:      time.Now().Add(4 * time.Second),
		Events:     []string{"iot", "test", "wrp"},
	}
	w2.Config.URL         = "http://localhost:9999/foo"
	w2.Config.ContentType = "application/json"
	w2.Matcher.DeviceId = []string{"mac:112233445566"}

	// Add 2 listeners
	list := []webhook.W{w1, w2}

	sw.Update(list)

	// Send iot message
	sw.Queue(iot)
	time.Sleep(time.Second)
	assert.Equal(int32(2), atomic.LoadInt32(&trans.i))

	// Send test message
	sw.Queue(test)
	time.Sleep(time.Second)
	assert.Equal(int32(3), atomic.LoadInt32(&trans.i))

	// Send wrp message
	sw.Queue(wrp)
	time.Sleep(time.Second)
	assert.Equal(int32(4), atomic.LoadInt32(&trans.i))

	// Wait for one to expire & send it again
	time.Sleep(2 * time.Second)
	sw.Queue(test)
	time.Sleep(time.Second)
	assert.Equal(int32(4), atomic.LoadInt32(&trans.i))

	w3 := webhook.W{
		Until:      time.Now().Add(5 * time.Second),
		Events:     []string{"iot"},
	}
	w3.Config.URL         = "http://localhost:9999/foo"
	w3.Config.ContentType = "application/json"

	// We get a registration
	list2 := []webhook.W{w3}
	sw.Update(list2)
	time.Sleep(time.Second)

	// Send iot
	sw.Queue(iot)

	sw.Shutdown(true)
	assert.Equal(int32(5), atomic.LoadInt32(&trans.i))
}
<commit_msg>test fix<commit_after>package main

import (
	"bytes"
	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/webhook"
	"github.com/Comcast/webpa-common/wrp"
	"github.com/stretchr/testify/assert"
	"net/http"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

type result struct {
	URL      string
	event    string
	transID  string
	deviceID string
}

// Make a simple RoundTrip implementation that let's me short-circuit the network
type swTransport struct {
	i       int32
	fn      func(*http.Request, int) (*http.Response, error)
	results []result
	mutex   sync.Mutex
}

func (t *swTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	atomic.AddInt32(&t.i, 1)

	r := result{URL: req.URL.String(),
		event:    req.Header.Get("X-Webpa-Event"),
		transID:  req.Header.Get("X-Webpa-Transaction-Id"),
		deviceID: req.Header.Get("X-Webpa-Device-Id"),
	}

	t.mutex.Lock()
	t.results = append(t.results, r)
	t.mutex.Unlock()

	resp := &http.Response{Status: "200 OK", StatusCode: 200}
	return resp, nil
}

func swGetLogger() logging.Logger {
	loggerFactory := logging.DefaultLoggerFactory{}
	logger, _ := loggerFactory.NewLogger("test")

	return logger
}

func TestInvalidLinger(t *testing.T) {
	sw, err := SenderWrapperFactory{
		NumWorkersPerSender: 10,
		QueueSizePerSender:  10,
		CutOffPeriod:        30 * time.Second,
		Logger:              swGetLogger(),
		Linger:              0 * time.Second,
		ProfilerFactory: ServerProfilerFactory{
			Frequency: 10,
			Duration:  6,
			QueueSize: 100,
		},
	}.New()

	assert := assert.New(t)
	assert.Nil(sw)
	assert.NotNil(err)
}

func TestSwSimple(t *testing.T) {
	assert := assert.New(t)

	wrpMessage := wrp.SimpleRequestResponse{
		Source:          "mac:112233445566",
		Destination:     "wrp",
		TransactionUUID: "12345",
	}

	var buffer bytes.Buffer
	encoder := wrp.NewEncoder(&buffer, wrp.Msgpack)
	err := encoder.Encode(&wrpMessage)
	assert.Nil(err)

	iot := CaduceusRequest{
		Payload:     []byte("Hello, world."),
		ContentType: "application/json",
		TargetURL:   "http://foo.com/api/v2/notification/device/mac:112233445566/event/iot",
	}
	test := CaduceusRequest{
		Payload:     []byte("Hello, world."),
		ContentType: "application/json",
		TargetURL:   "http://foo.com/api/v2/notification/device/mac:112233445566/event/test",
	}
	wrp := CaduceusRequest{
		Payload:     buffer.Bytes(),
		ContentType: "application/wrp",
		TargetURL:   "http://foo.com/api/v2/notification/device/mac:112233445566/event/wrp",
	}

	trans := &swTransport{}

	sw, err := SenderWrapperFactory{
		NumWorkersPerSender: 10,
		QueueSizePerSender:  10,
		CutOffPeriod:        30 * time.Second,
		Client:              &http.Client{Transport: trans},
		Logger:              swGetLogger(),
		Linger:              1 * time.Second,
		ProfilerFactory: ServerProfilerFactory{
			Frequency: 10,
			Duration:  6,
			QueueSize: 100,
		},
	}.New()

	assert.Nil(err)
	assert.NotNil(sw)

	// No listeners

	sw.Queue(iot)
	sw.Queue(iot)
	sw.Queue(iot)

	assert.Equal(int32(0), trans.i)

	w1 := webhook.W{
		Duration:   6 * time.Second,
		Until:      time.Now().Add(6 * time.Second),
		Events:     []string{"iot"},
	}
	w1.Config.URL         = "http://localhost:8888/foo"
	w1.Config.ContentType = "application/json"
	w1.Matcher.DeviceId = []string{"mac:112233445566"}

	w2 := webhook.W{
		Duration:   4 * time.Second,
		Until:      time.Now().Add(4 * time.Second),
		Events:     []string{"iot", "test", "wrp"},
	}
	w2.Config.URL         = "http://localhost:9999/foo"
	w2.Config.ContentType = "application/json"
	w2.Matcher.DeviceId = []string{"mac:112233445566"}

	// Add 2 listeners
	list := []webhook.W{w1, w2}

	sw.Update(list)

	// Send iot message
	sw.Queue(iot)
	time.Sleep(time.Second)
	assert.Equal(int32(2), atomic.LoadInt32(&trans.i))

	// Send test message
	sw.Queue(test)
	time.Sleep(time.Second)
	assert.Equal(int32(3), atomic.LoadInt32(&trans.i))

	// Send wrp message
	sw.Queue(wrp)
	time.Sleep(time.Second)
	assert.Equal(int32(4), atomic.LoadInt32(&trans.i))

	// Wait for one to expire & send it again
	time.Sleep(2 * time.Second)
	sw.Queue(test)
	time.Sleep(time.Second)
	assert.Equal(int32(4), atomic.LoadInt32(&trans.i))

	w3 := webhook.W{
		Duration:   5 * time.Second,
		Until:      time.Now().Add(5 * time.Second),
		Events:     []string{"iot"},
	}
	w3.Config.URL         = "http://localhost:9999/foo"
	w3.Config.ContentType = "application/json"

	// We get a registration
	list2 := []webhook.W{w3}
	sw.Update(list2)
	time.Sleep(time.Second)

	// Send iot
	sw.Queue(iot)

	sw.Shutdown(true)
	assert.Equal(int32(5), atomic.LoadInt32(&trans.i))
}
<|endoftext|>


<commit_before>// Copyright 2013 Jamie Hall. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package spdy

import (
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

// A Transport is an HTTP/SPDY http.RoundTripper.
type Transport struct {
	m sync.Mutex

	// Proxy specifies a function to return a proxy for a given
	// Request. If the function returns a non-nil error, the
	// request is aborted with the provided error.
	// If Proxy is nil or returns a nil *URL, no proxy is used.
	Proxy func(*http.Request) (*url.URL, error)

	// Dial specifies the dial function for creating TCP
	// connections.
	// If Dial is nil, net.Dial is used.
	Dial func(network, addr string) (net.Conn, error) // TODO: use

	// TLSClientConfig specifies the TLS configuration to use with
	// tls.Client. If nil, the default configuration is used.
	TLSClientConfig *tls.Config

	// DisableKeepAlives, if true, prevents re-use of TCP connections
	// between different HTTP requests.
	DisableKeepAlives bool

	// DisableCompression, if true, prevents the Transport from
	// requesting compression with an "Accept-Encoding: gzip"
	// request header when the Request contains no existing
	// Accept-Encoding value. If the Transport requests gzip on
	// its own and gets a gzipped response, it's transparently
	// decoded in the Response.Body. However, if the user
	// explicitly requested gzip it is not automatically
	// uncompressed.
	DisableCompression bool

	// MaxIdleConnsPerHost, if non-zero, controls the maximum idle
	// (keep-alive) to keep per-host.  If zero,
	// DefaultMaxIdleConnsPerHost is used.
	MaxIdleConnsPerHost int

	// ResponseHeaderTimeout, if non-zero, specifies the amount of
	// time to wait for a server's response headers after fully
	// writing the request (including its body, if any). This
	// time does not include the time to read the response body.
	ResponseHeaderTimeout time.Duration

	spdyConns map[string]Conn          // SPDY connections mapped to host:port.
	tcpConns  map[string]chan net.Conn // Non-SPDY connections mapped to host:port.
	connLimit map[string]chan struct{} // Used to enforce the TCP conn limit.

	// Priority is used to determine the request priority of SPDY
	// requests. If nil, spdy.DefaultPriority is used.
	Priority func(*url.URL) Priority

	// Receiver is used to receive the server's response. If left
	// nil, the default Receiver will parse and create a normal
	// Response.
	Receiver Receiver

	// PushReceiver is used to receive server pushes. If left nil,
	// pushes will be refused. The provided Request will be that
	// sent with the server push. See Receiver for more detail on
	// its methods.
	PushReceiver Receiver
}

// dial makes the connection to an endpoint.
func (t *Transport) dial(u *url.URL) (net.Conn, error) {

	if t.TLSClientConfig == nil {
		t.TLSClientConfig = &tls.Config{
			NextProtos: npn(),
		}
	} else if t.TLSClientConfig.NextProtos == nil {
		t.TLSClientConfig.NextProtos = npn()
	}

	// Wait for a connection slot to become available.
	<-t.connLimit[u.Host]

	switch u.Scheme {
	case "http":
		return net.Dial("tcp", u.Host)
	case "https":
		return tls.Dial("tcp", u.Host, t.TLSClientConfig)
	default:
		return nil, errors.New(fmt.Sprintf("Error: URL has invalid scheme %q.", u.Scheme))
	}
}

// doHTTP is used to process an HTTP(S) request, using the TCP connection pool.
func (t *Transport) doHTTP(conn net.Conn, req *http.Request) (*http.Response, error) {
	debug.Printf("Requesting %q over HTTP.\n", req.URL.String())

	// Create the HTTP ClientConn, which handles the
	// HTTP details.
	httpConn := httputil.NewClientConn(conn, nil)
	res, err := httpConn.Do(req)
	if err != nil {
		return nil, err
	}

	if !res.Close {
		t.tcpConns[req.URL.Host] <- conn
	} else {
		// This connection is closing, so another can be used.
		t.connLimit[req.URL.Host] <- struct{}{}
		err = httpConn.Close()
		if err != nil {
			return nil, err
		}
	}

	return res, nil
}

// RoundTrip handles the actual request; ensuring a connection is
// made, determining which protocol to use, and performing the
// request.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	u := req.URL

	// Make sure the URL host contains the port.
	if !strings.Contains(u.Host, ":") {
		switch u.Scheme {
		case "http":
			u.Host += ":80"

		case "https":
			u.Host += ":443"
		}
	}

	t.m.Lock()

	// Initialise structures if necessary.
	if t.spdyConns == nil {
		t.spdyConns = make(map[string]Conn)
	}
	if t.tcpConns == nil {
		t.tcpConns = make(map[string]chan net.Conn)
	}
	if t.connLimit == nil {
		t.connLimit = make(map[string]chan struct{})
	}
	if t.MaxIdleConnsPerHost == 0 {
		t.MaxIdleConnsPerHost = http.DefaultMaxIdleConnsPerHost
	}
	if _, ok := t.connLimit[u.Host]; !ok {
		limitChan := make(chan struct{}, t.MaxIdleConnsPerHost)
		t.connLimit[u.Host] = limitChan
		for i := 0; i < t.MaxIdleConnsPerHost; i++ {
			limitChan <- struct{}{}
		}
	}

	// Check the non-SPDY connection pool.
	if connChan, ok := t.tcpConns[u.Host]; ok {
		select {
		case tcpConn := <-connChan:
			t.m.Unlock()
			// Use a connection from the pool.
			return t.doHTTP(tcpConn, req)
		default:
		}
	} else {
		t.tcpConns[u.Host] = make(chan net.Conn, t.MaxIdleConnsPerHost)
	}

	// Check the SPDY connection pool.
	conn, ok := t.spdyConns[u.Host]
	if !ok || u.Scheme == "http" {
		tcpConn, err := t.dial(req.URL)
		if err != nil {
			t.m.Unlock()
			return nil, err
		}

		// Handle HTTPS/SPDY requests.
		if tlsConn, ok := tcpConn.(*tls.Conn); ok {
			state := tlsConn.ConnectionState()

			// Complete handshake if necessary.
			if !state.HandshakeComplete {
				err = tlsConn.Handshake()
				if err != nil {
					t.m.Unlock()
					return nil, err
				}
			}

			// Verify hostname, unless requested not to.
			if !t.TLSClientConfig.InsecureSkipVerify {
				err = tlsConn.VerifyHostname(req.URL.Host)
				if err != nil {
					// Also try verifying the hostname with/without a port number.
					i := strings.Index(req.URL.Host, ":")
					err = tlsConn.VerifyHostname(req.URL.Host[:i])
					if err != nil {
						t.m.Unlock()
						return nil, err
					}
				}
			}

			// If a protocol could not be negotiated, assume HTTPS.
			if !state.NegotiatedProtocolIsMutual {
				t.m.Unlock()
				return t.doHTTP(tcpConn, req)
			}

			// Scan the list of supported NPN strings.
			supported := false
			for _, proto := range npn() {
				if state.NegotiatedProtocol == proto {
					supported = true
					break
				}
			}

			// Ensure the negotiated protocol is supported.
			if !supported {
				msg := fmt.Sprintf("Error: Unsupported negotiated protocol %q.", state.NegotiatedProtocol)
				t.m.Unlock()
				return nil, errors.New(msg)
			}

			// Handle the protocol.
			switch state.NegotiatedProtocol {
			case "http/1.1", "":
				t.m.Unlock()
				return t.doHTTP(tcpConn, req)

			case "spdy/3.1":
				newConn, err := NewClientConn(tlsConn, t.PushReceiver, 3.1)
				if err != nil {
					return nil, err
				}
				go newConn.Run()
				t.spdyConns[u.Host] = newConn
				conn = newConn

			case "spdy/3":
				newConn, err := NewClientConn(tlsConn, t.PushReceiver, 3)
				if err != nil {
					return nil, err
				}
				go newConn.Run()
				t.spdyConns[u.Host] = newConn
				conn = newConn

			case "spdy/2":
				newConn, err := NewClientConn(tlsConn, t.PushReceiver, 2)
				if err != nil {
					return nil, err
				}
				go newConn.Run()
				t.spdyConns[u.Host] = newConn
				conn = newConn
			}
		} else {
			// Handle HTTP requests.
			t.m.Unlock()
			return t.doHTTP(tcpConn, req)
		}
	}
	t.m.Unlock()

	// The connection has now been established.

	debug.Printf("Requesting %q over SPDY.\n", u.String())

	// Determine the request priority.
	priority := Priority(0)
	if t.Priority != nil {
		priority = t.Priority(req.URL)
	} else {
		priority = DefaultPriority(req.URL)
	}

	return conn.RequestResponse(req, t.Receiver, priority)
}

// response is used in handling responses; storing
// the data as it's received, and producing an
// http.Response once complete.
//
// response may be given a Receiver to enable live
// handling of the response data. This is provided
// by setting spdy.Transport.Receiver.
type response struct {
	StatusCode int

	headerM sync.Mutex
	Header  http.Header

	dataM sync.Mutex
	Data  *bytes.Buffer

	Request  *http.Request
	Receiver Receiver
}

func (r *response) ReceiveData(req *http.Request, data []byte, finished bool) {
	r.dataM.Lock()
	r.Data.Write(data)
	r.dataM.Unlock()
	if r.Receiver != nil {
		r.Receiver.ReceiveData(req, data, finished)
	}
}

var statusRegex = regexp.MustCompile(`\A\s*(?P<code>\d+)`)

func (r *response) ReceiveHeader(req *http.Request, header http.Header) {
	r.headerM.Lock()
	if r.Header == nil {
		r.Header = make(http.Header)
	}
	updateHeader(r.Header, header)
	if status := r.Header.Get(":status"); status != "" && statusRegex.MatchString(status) {
		if matches := statusRegex.FindAllStringSubmatch(status, -1); matches != nil {
			s, err := strconv.Atoi(matches[0][1])
			if err == nil {
				r.StatusCode = s
			}
		}
	}
	if r.Receiver != nil {
		r.Receiver.ReceiveHeader(req, header)
	}
	r.headerM.Unlock()
}

func (r *response) ReceiveRequest(req *http.Request) bool {
	if r.Receiver != nil {
		return r.Receiver.ReceiveRequest(req)
	}
	return false
}

func (r *response) Response() *http.Response {
	if r.Data == nil {
		r.Data = new(bytes.Buffer)
	}
	out := new(http.Response)

	r.headerM.Lock()
	out.Status = fmt.Sprintf("%d %s", r.StatusCode, http.StatusText(r.StatusCode))
	out.StatusCode = r.StatusCode
	out.Header = r.Header
	r.headerM.Unlock()

	out.Proto = "HTTP/1.1"
	out.ProtoMajor = 1
	out.ProtoMinor = 1

	r.dataM.Lock()
	out.Body = &readCloser{r.Data}
	out.ContentLength = int64(r.Data.Len())
	r.dataM.Unlock()

	out.TransferEncoding = nil
	out.Close = true
	out.Trailer = make(http.Header)
	out.Request = r.Request
	return out
}
<commit_msg>Improved status code handling<commit_after>// Copyright 2013 Jamie Hall. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package spdy

import (
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"
)

// A Transport is an HTTP/SPDY http.RoundTripper.
type Transport struct {
	m sync.Mutex

	// Proxy specifies a function to return a proxy for a given
	// Request. If the function returns a non-nil error, the
	// request is aborted with the provided error.
	// If Proxy is nil or returns a nil *URL, no proxy is used.
	Proxy func(*http.Request) (*url.URL, error)

	// Dial specifies the dial function for creating TCP
	// connections.
	// If Dial is nil, net.Dial is used.
	Dial func(network, addr string) (net.Conn, error) // TODO: use

	// TLSClientConfig specifies the TLS configuration to use with
	// tls.Client. If nil, the default configuration is used.
	TLSClientConfig *tls.Config

	// DisableKeepAlives, if true, prevents re-use of TCP connections
	// between different HTTP requests.
	DisableKeepAlives bool

	// DisableCompression, if true, prevents the Transport from
	// requesting compression with an "Accept-Encoding: gzip"
	// request header when the Request contains no existing
	// Accept-Encoding value. If the Transport requests gzip on
	// its own and gets a gzipped response, it's transparently
	// decoded in the Response.Body. However, if the user
	// explicitly requested gzip it is not automatically
	// uncompressed.
	DisableCompression bool

	// MaxIdleConnsPerHost, if non-zero, controls the maximum idle
	// (keep-alive) to keep per-host.  If zero,
	// DefaultMaxIdleConnsPerHost is used.
	MaxIdleConnsPerHost int

	// ResponseHeaderTimeout, if non-zero, specifies the amount of
	// time to wait for a server's response headers after fully
	// writing the request (including its body, if any). This
	// time does not include the time to read the response body.
	ResponseHeaderTimeout time.Duration

	spdyConns map[string]Conn          // SPDY connections mapped to host:port.
	tcpConns  map[string]chan net.Conn // Non-SPDY connections mapped to host:port.
	connLimit map[string]chan struct{} // Used to enforce the TCP conn limit.

	// Priority is used to determine the request priority of SPDY
	// requests. If nil, spdy.DefaultPriority is used.
	Priority func(*url.URL) Priority

	// Receiver is used to receive the server's response. If left
	// nil, the default Receiver will parse and create a normal
	// Response.
	Receiver Receiver

	// PushReceiver is used to receive server pushes. If left nil,
	// pushes will be refused. The provided Request will be that
	// sent with the server push. See Receiver for more detail on
	// its methods.
	PushReceiver Receiver
}

// dial makes the connection to an endpoint.
func (t *Transport) dial(u *url.URL) (net.Conn, error) {

	if t.TLSClientConfig == nil {
		t.TLSClientConfig = &tls.Config{
			NextProtos: npn(),
		}
	} else if t.TLSClientConfig.NextProtos == nil {
		t.TLSClientConfig.NextProtos = npn()
	}

	// Wait for a connection slot to become available.
	<-t.connLimit[u.Host]

	switch u.Scheme {
	case "http":
		return net.Dial("tcp", u.Host)
	case "https":
		return tls.Dial("tcp", u.Host, t.TLSClientConfig)
	default:
		return nil, errors.New(fmt.Sprintf("Error: URL has invalid scheme %q.", u.Scheme))
	}
}

// doHTTP is used to process an HTTP(S) request, using the TCP connection pool.
func (t *Transport) doHTTP(conn net.Conn, req *http.Request) (*http.Response, error) {
	debug.Printf("Requesting %q over HTTP.\n", req.URL.String())

	// Create the HTTP ClientConn, which handles the
	// HTTP details.
	httpConn := httputil.NewClientConn(conn, nil)
	res, err := httpConn.Do(req)
	if err != nil {
		return nil, err
	}

	if !res.Close {
		t.tcpConns[req.URL.Host] <- conn
	} else {
		// This connection is closing, so another can be used.
		t.connLimit[req.URL.Host] <- struct{}{}
		err = httpConn.Close()
		if err != nil {
			return nil, err
		}
	}

	return res, nil
}

// RoundTrip handles the actual request; ensuring a connection is
// made, determining which protocol to use, and performing the
// request.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	u := req.URL

	// Make sure the URL host contains the port.
	if !strings.Contains(u.Host, ":") {
		switch u.Scheme {
		case "http":
			u.Host += ":80"

		case "https":
			u.Host += ":443"
		}
	}

	t.m.Lock()

	// Initialise structures if necessary.
	if t.spdyConns == nil {
		t.spdyConns = make(map[string]Conn)
	}
	if t.tcpConns == nil {
		t.tcpConns = make(map[string]chan net.Conn)
	}
	if t.connLimit == nil {
		t.connLimit = make(map[string]chan struct{})
	}
	if t.MaxIdleConnsPerHost == 0 {
		t.MaxIdleConnsPerHost = http.DefaultMaxIdleConnsPerHost
	}
	if _, ok := t.connLimit[u.Host]; !ok {
		limitChan := make(chan struct{}, t.MaxIdleConnsPerHost)
		t.connLimit[u.Host] = limitChan
		for i := 0; i < t.MaxIdleConnsPerHost; i++ {
			limitChan <- struct{}{}
		}
	}

	// Check the non-SPDY connection pool.
	if connChan, ok := t.tcpConns[u.Host]; ok {
		select {
		case tcpConn := <-connChan:
			t.m.Unlock()
			// Use a connection from the pool.
			return t.doHTTP(tcpConn, req)
		default:
		}
	} else {
		t.tcpConns[u.Host] = make(chan net.Conn, t.MaxIdleConnsPerHost)
	}

	// Check the SPDY connection pool.
	conn, ok := t.spdyConns[u.Host]
	if !ok || u.Scheme == "http" {
		tcpConn, err := t.dial(req.URL)
		if err != nil {
			t.m.Unlock()
			return nil, err
		}

		// Handle HTTPS/SPDY requests.
		if tlsConn, ok := tcpConn.(*tls.Conn); ok {
			state := tlsConn.ConnectionState()

			// Complete handshake if necessary.
			if !state.HandshakeComplete {
				err = tlsConn.Handshake()
				if err != nil {
					t.m.Unlock()
					return nil, err
				}
			}

			// Verify hostname, unless requested not to.
			if !t.TLSClientConfig.InsecureSkipVerify {
				err = tlsConn.VerifyHostname(req.URL.Host)
				if err != nil {
					// Also try verifying the hostname with/without a port number.
					i := strings.Index(req.URL.Host, ":")
					err = tlsConn.VerifyHostname(req.URL.Host[:i])
					if err != nil {
						t.m.Unlock()
						return nil, err
					}
				}
			}

			// If a protocol could not be negotiated, assume HTTPS.
			if !state.NegotiatedProtocolIsMutual {
				t.m.Unlock()
				return t.doHTTP(tcpConn, req)
			}

			// Scan the list of supported NPN strings.
			supported := false
			for _, proto := range npn() {
				if state.NegotiatedProtocol == proto {
					supported = true
					break
				}
			}

			// Ensure the negotiated protocol is supported.
			if !supported {
				msg := fmt.Sprintf("Error: Unsupported negotiated protocol %q.", state.NegotiatedProtocol)
				t.m.Unlock()
				return nil, errors.New(msg)
			}

			// Handle the protocol.
			switch state.NegotiatedProtocol {
			case "http/1.1", "":
				t.m.Unlock()
				return t.doHTTP(tcpConn, req)

			case "spdy/3.1":
				newConn, err := NewClientConn(tlsConn, t.PushReceiver, 3.1)
				if err != nil {
					return nil, err
				}
				go newConn.Run()
				t.spdyConns[u.Host] = newConn
				conn = newConn

			case "spdy/3":
				newConn, err := NewClientConn(tlsConn, t.PushReceiver, 3)
				if err != nil {
					return nil, err
				}
				go newConn.Run()
				t.spdyConns[u.Host] = newConn
				conn = newConn

			case "spdy/2":
				newConn, err := NewClientConn(tlsConn, t.PushReceiver, 2)
				if err != nil {
					return nil, err
				}
				go newConn.Run()
				t.spdyConns[u.Host] = newConn
				conn = newConn
			}
		} else {
			// Handle HTTP requests.
			t.m.Unlock()
			return t.doHTTP(tcpConn, req)
		}
	}
	t.m.Unlock()

	// The connection has now been established.

	debug.Printf("Requesting %q over SPDY.\n", u.String())

	// Determine the request priority.
	priority := Priority(0)
	if t.Priority != nil {
		priority = t.Priority(req.URL)
	} else {
		priority = DefaultPriority(req.URL)
	}

	return conn.RequestResponse(req, t.Receiver, priority)
}

// response is used in handling responses; storing
// the data as it's received, and producing an
// http.Response once complete.
//
// response may be given a Receiver to enable live
// handling of the response data. This is provided
// by setting spdy.Transport.Receiver.
type response struct {
	StatusCode int

	headerM sync.Mutex
	Header  http.Header

	dataM sync.Mutex
	Data  *bytes.Buffer

	Request  *http.Request
	Receiver Receiver
}

func (r *response) ReceiveData(req *http.Request, data []byte, finished bool) {
	r.dataM.Lock()
	r.Data.Write(data)
	r.dataM.Unlock()
	if r.Receiver != nil {
		r.Receiver.ReceiveData(req, data, finished)
	}
}

func (r *response) ReceiveHeader(req *http.Request, header http.Header) {
	r.headerM.Lock()
	if r.Header == nil {
		r.Header = make(http.Header)
	}
	updateHeader(r.Header, header)
	if status := r.Header.Get(":status"); status != "" {
		status = strings.TrimSpace(status)
		if i := strings.Index(status, " "); i >= 0 {
			status = status[:i]
		}
		s, err := strconv.Atoi(status)
		if err == nil {
			r.StatusCode = s
		}
	}
	if r.Receiver != nil {
		r.Receiver.ReceiveHeader(req, header)
	}
	r.headerM.Unlock()
}

func (r *response) ReceiveRequest(req *http.Request) bool {
	if r.Receiver != nil {
		return r.Receiver.ReceiveRequest(req)
	}
	return false
}

func (r *response) Response() *http.Response {
	if r.Data == nil {
		r.Data = new(bytes.Buffer)
	}
	out := new(http.Response)

	r.headerM.Lock()
	out.Status = fmt.Sprintf("%d %s", r.StatusCode, http.StatusText(r.StatusCode))
	out.StatusCode = r.StatusCode
	out.Header = r.Header
	r.headerM.Unlock()

	out.Proto = "HTTP/1.1"
	out.ProtoMajor = 1
	out.ProtoMinor = 1

	r.dataM.Lock()
	out.Body = &readCloser{r.Data}
	out.ContentLength = int64(r.Data.Len())
	r.dataM.Unlock()

	out.TransferEncoding = nil
	out.Close = true
	out.Trailer = make(http.Header)
	out.Request = r.Request
	return out
}
<|endoftext|>


<commit_before>package somaproto

type TreeCheck struct {
	CheckId       string `json:"check_id,omitempty"`
	SourceCheckId string `json:"source_check_id,omitempty"`
	CheckConfigId string `json:"check_config_id,omitempty"`
	SourceType    string `json:"source_type,omitempty"`
	IsInherited   bool   `json:"is_inherited,omitempty"`
	InheritedFrom string `json:"inherited_from,omitempty"`
	Inheritance   bool   `json:"inheritance,omitempty"`
	ChildrenOnly  bool   `json:"children_only,omitempty"`
	RepositoryId  string `json:"repository_id,omitempty"`
	BucketId      string `json:"bucket_id,omitempty"`
	CapabilityId  string `json:"capability_id,omitempty"`
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>Add TreeCheckInstance<commit_after>package somaproto

type TreeCheck struct {
	CheckId       string `json:"check_id,omitempty"`
	SourceCheckId string `json:"source_check_id,omitempty"`
	CheckConfigId string `json:"check_config_id,omitempty"`
	SourceType    string `json:"source_type,omitempty"`
	IsInherited   bool   `json:"is_inherited,omitempty"`
	InheritedFrom string `json:"inherited_from,omitempty"`
	Inheritance   bool   `json:"inheritance,omitempty"`
	ChildrenOnly  bool   `json:"children_only,omitempty"`
	RepositoryId  string `json:"repository_id,omitempty"`
	BucketId      string `json:"bucket_id,omitempty"`
	CapabilityId  string `json:"capability_id,omitempty"`
}

type TreeCheckInstance struct {
	InstanceId            string `json:"instance_id,omitempty"`
	CheckId               string `json:"check_id,omitempty"`
	ConfigId              string `json:"config_id,omitempty"`
	InstanceConfigId      string `json:"instance_config_id,omitempty"`
	Version               uint64 `json:"version,omitempty"`
	ConstraintHash        string `json:"constraint_hash,omitempty"`
	ConstraintValHash     string `json:"constraint_val_hash,omitempty"`
	InstanceSvcCfgHash    string `json:"instance_svc_cfghash,omitempty"`
	InstanceService       string `json:"instance_service,omitempty"`
	InstanceServiceConfig string `json:"instance_service_cfg,omitempty"`
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


<commit_before>package app

import (
	"bytes"
	"github.com/stretchr/testify/assert"
	"os"
	"os/exec"
	"path/filepath"
	"testing"
)

func TestRunningCommands(t *testing.T) {
	stdout, _, err := runCommand(t, "api")
	assert.NoError(t, err)
	assert.Contains(t, stdout, "API endpoint")

	stdout, _, err = runCommand(t, "app")
	assert.Error(t, err)
	assert.Contains(t, stdout, "FAILED")

	stdout, _, err = runCommand(t, "h", "target")
	assert.NoError(t, err)
	assert.Contains(t, stdout, "TIP:")
	assert.Contains(t, stdout, "Use 'cf api' to set or view the target api url.")
}

func runCommand(t *testing.T, params ...string) (stdout, stderr string, err error) {
	currentDir, err := os.Getwd()
	projectDir := filepath.Join(currentDir, "../../..")
	sourceFile := filepath.Join(projectDir, "src", "main", "cf.go")
	goFile := filepath.Join(projectDir, "bin", "go")

	args := append([]string{"run", sourceFile}, params...)
	goCmd := exec.Command(goFile, args...)

	stdoutWriter := bytes.NewBufferString("")
	stderrWriter := bytes.NewBufferString("")
	goCmd.Stdout = stdoutWriter
	goCmd.Stderr = stderrWriter

	err = goCmd.Start()
	assert.NoError(t, err)

	err = goCmd.Wait()

	return string(stdoutWriter.Bytes()), string(stderrWriter.Bytes()), err
}
<commit_msg>Exclude integration test from windows machines until a proper solution is found<commit_after>// Test only works on unix machines at the moment

// +build darwin freebsd linux netbsd openbsd

package app

import (
	"bytes"
	"github.com/stretchr/testify/assert"
	"os"
	"os/exec"
	"path/filepath"
	"testing"
)

func TestRunningCommands(t *testing.T) {
	stdout, _, err := runCommand(t, "api")
	assert.NoError(t, err)
	assert.Contains(t, stdout, "API endpoint")

	stdout, _, err = runCommand(t, "app")
	assert.Error(t, err)
	assert.Contains(t, stdout, "FAILED")

	stdout, _, err = runCommand(t, "h", "target")
	assert.NoError(t, err)
	assert.Contains(t, stdout, "TIP:")
	assert.Contains(t, stdout, "Use 'cf api' to set or view the target api url.")
}

func runCommand(t *testing.T, params ...string) (stdout, stderr string, err error) {
	currentDir, err := os.Getwd()
	projectDir := filepath.Join(currentDir, "../../..")
	sourceFile := filepath.Join(projectDir, "src", "main", "cf.go")
	goFile := filepath.Join(projectDir, "bin", "go")

	args := append([]string{"run", sourceFile}, params...)
	goCmd := exec.Command(goFile, args...)

	stdoutWriter := bytes.NewBufferString("")
	stderrWriter := bytes.NewBufferString("")
	goCmd.Stdout = stdoutWriter
	goCmd.Stderr = stderrWriter

	err = goCmd.Start()
	assert.NoError(t, err)

	err = goCmd.Wait()

	return string(stdoutWriter.Bytes()), string(stderrWriter.Bytes()), err
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"log"
	"net"
	"regexp"
	"strconv"
	"sync"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/fsouza/go-dockerclient"
)

const (
	appNameRegex string = `([a-z0-9-]+)_v([1-9][0-9]*).(cmd|web).([1-9][0-9])*`
)

// Server is the main entrypoint for a publisher. It listens on a docker client for events
// and publishes their host:port to the etcd client.
type Server struct {
	DockerClient *docker.Client
	EtcdClient   *etcd.Client

	host     string
	logLevel string
}

var safeMap = struct {
	sync.RWMutex
	data map[string]string
}{data: make(map[string]string)}

// New returns a new instance of Server.
func New(dockerClient *docker.Client, etcdClient *etcd.Client, host, logLevel string) *Server {
	return &Server{
		DockerClient: dockerClient,
		EtcdClient:   etcdClient,
		host:         host,
		logLevel:     logLevel,
	}
}

// Listen adds an event listener to the docker client and publishes containers that were started.
func (s *Server) Listen(ttl time.Duration) {
	listener := make(chan *docker.APIEvents)
	// TODO: figure out why we need to sleep for 10 milliseconds
	// https://github.com/fsouza/go-dockerclient/blob/0236a64c6c4bd563ec277ba00e370cc753e1677c/event_test.go#L43
	defer func() { time.Sleep(10 * time.Millisecond); s.DockerClient.RemoveEventListener(listener) }()
	if err := s.DockerClient.AddEventListener(listener); err != nil {
		log.Fatal(err)
	}
	for {
		select {
		case event := <-listener:
			if event.Status == "start" {
				container, err := s.getContainer(event.ID)
				if err != nil {
					log.Println(err)
					continue
				}
				s.publishContainer(container, ttl)
			} else if event.Status == "stop" {
				s.removeContainer(event.ID)
			}
		}
	}
}

// Poll lists all containers from the docker client every time the TTL comes up and publishes them to etcd
func (s *Server) Poll(ttl time.Duration) {
	containers, err := s.DockerClient.ListContainers(docker.ListContainersOptions{})
	if err != nil {
		log.Fatal(err)
	}
	for _, container := range containers {
		// send container to channel for processing
		s.publishContainer(&container, ttl)
	}
}

// getContainer retrieves a container from the docker client based on id
func (s *Server) getContainer(id string) (*docker.APIContainers, error) {
	containers, err := s.DockerClient.ListContainers(docker.ListContainersOptions{})
	if err != nil {
		return nil, err
	}
	for _, container := range containers {
		// send container to channel for processing
		if container.ID == id {
			return &container, nil
		}
	}
	return nil, fmt.Errorf("could not find container with id %v", id)
}

// publishContainer publishes the docker container to etcd.
func (s *Server) publishContainer(container *docker.APIContainers, ttl time.Duration) {
	r := regexp.MustCompile(appNameRegex)
	for _, name := range container.Names {
		// HACK: remove slash from container name
		// see https://github.com/docker/docker/issues/7519
		containerName := name[1:]
		match := r.FindStringSubmatch(containerName)
		if match == nil {
			continue
		}
		appName := match[1]
		appPath := fmt.Sprintf("%s/%s", appName, containerName)
		keyPath := fmt.Sprintf("/deis/services/%s", appPath)
		dirPath := fmt.Sprintf("/deis/services/%s", appName)
		for _, p := range container.Ports {
			// lowest port wins (docker sorts the ports)
			// TODO (bacongobbler): support multiple exposed ports
			port := strconv.Itoa(int(p.PublicPort))
			hostAndPort := s.host + ":" + port
			if s.IsPublishableApp(containerName) && s.IsPortOpen(hostAndPort) {
				s.setEtcd(keyPath, hostAndPort, uint64(ttl.Seconds()))
				s.updateDir(dirPath, uint64(ttl.Seconds()))
				safeMap.Lock()
				safeMap.data[container.ID] = appPath
				safeMap.Unlock()
			}
			break
		}
	}
}

// removeContainer remove a container published by this component
func (s *Server) removeContainer(event string) {
	safeMap.RLock()
	appPath := safeMap.data[event]
	safeMap.RUnlock()

	if appPath != "" {
		keyPath := fmt.Sprintf("/deis/services/%s", appPath)
		log.Printf("stopped %s\n", keyPath)
		s.removeEtcd(keyPath, false)
	}
}

// IsPublishableApp determines if the application should be published to etcd.
func (s *Server) IsPublishableApp(name string) bool {
	r := regexp.MustCompile(appNameRegex)
	match := r.FindStringSubmatch(name)
	if match == nil {
		return false
	}
	appName := match[1]
	version, err := strconv.Atoi(match[2])
	if err != nil {
		log.Println(err)
		return false
	}

	if version >= latestRunningVersion(s.EtcdClient, appName) {
		return true
	}
	return false
}

// IsPortOpen checks if the given port is accepting tcp connections
func (s *Server) IsPortOpen(hostAndPort string) bool {
	portOpen := false
	conn, err := net.Dial("tcp", hostAndPort)
	if err == nil {
		portOpen = true
		defer conn.Close()
	}
	return portOpen
}

// latestRunningVersion retrieves the highest version of the application published
// to etcd. If no app has been published, returns 0.
func latestRunningVersion(client *etcd.Client, appName string) int {
	r := regexp.MustCompile(appNameRegex)
	if client == nil {
		// FIXME: client should only be nil during tests. This should be properly refactored.
		if appName == "ceci-nest-pas-une-app" {
			return 3
		}
		return 0
	}
	resp, err := client.Get(fmt.Sprintf("/deis/services/%s", appName), false, true)
	if err != nil {
		// no app has been published here (key not found) or there was an error
		return 0
	}
	var versions []int
	for _, node := range resp.Node.Nodes {
		match := r.FindStringSubmatch(node.Key)
		// account for keys that may not be an application container
		if match == nil {
			continue
		}
		version, err := strconv.Atoi(match[2])
		if err != nil {
			log.Println(err)
			return 0
		}
		versions = append(versions, version)
	}
	return max(versions)
}

// max returns the maximum value in n
func max(n []int) int {
	val := 0
	for _, i := range n {
		if i > val {
			val = i
		}
	}
	return val
}

// setEtcd sets the corresponding etcd key with the value and ttl
func (s *Server) setEtcd(key, value string, ttl uint64) {
	if _, err := s.EtcdClient.Set(key, value, ttl); err != nil {
		log.Println(err)
	}
	if s.logLevel == "debug" {
		log.Println("set", key, "->", value)
	}
}

// removeEtcd removes the corresponding etcd key
func (s *Server) removeEtcd(key string, recursive bool) {
	if _, err := s.EtcdClient.Delete(key, recursive); err != nil {
		log.Println(err)
	}
	if s.logLevel == "debug" {
		log.Println("del", key)
	}
}

// updateDir updates the given directory for a given ttl. It succeeds
// only if the given directory already exists.
func (s *Server) updateDir(directory string, ttl uint64) {
	if _, err := s.EtcdClient.UpdateDir(directory, ttl); err != nil {
		log.Println(err)
	}
	if s.logLevel == "debug" {
		log.Println("updateDir", directory)
	}
}
<commit_msg>fix(publisher): revert TTL in app services etcd dirs<commit_after>package server

import (
	"fmt"
	"log"
	"net"
	"regexp"
	"strconv"
	"sync"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/fsouza/go-dockerclient"
)

const (
	appNameRegex string = `([a-z0-9-]+)_v([1-9][0-9]*).(cmd|web).([1-9][0-9])*`
)

// Server is the main entrypoint for a publisher. It listens on a docker client for events
// and publishes their host:port to the etcd client.
type Server struct {
	DockerClient *docker.Client
	EtcdClient   *etcd.Client

	host     string
	logLevel string
}

var safeMap = struct {
	sync.RWMutex
	data map[string]string
}{data: make(map[string]string)}

// New returns a new instance of Server.
func New(dockerClient *docker.Client, etcdClient *etcd.Client, host, logLevel string) *Server {
	return &Server{
		DockerClient: dockerClient,
		EtcdClient:   etcdClient,
		host:         host,
		logLevel:     logLevel,
	}
}

// Listen adds an event listener to the docker client and publishes containers that were started.
func (s *Server) Listen(ttl time.Duration) {
	listener := make(chan *docker.APIEvents)
	// TODO: figure out why we need to sleep for 10 milliseconds
	// https://github.com/fsouza/go-dockerclient/blob/0236a64c6c4bd563ec277ba00e370cc753e1677c/event_test.go#L43
	defer func() { time.Sleep(10 * time.Millisecond); s.DockerClient.RemoveEventListener(listener) }()
	if err := s.DockerClient.AddEventListener(listener); err != nil {
		log.Fatal(err)
	}
	for {
		select {
		case event := <-listener:
			if event.Status == "start" {
				container, err := s.getContainer(event.ID)
				if err != nil {
					log.Println(err)
					continue
				}
				s.publishContainer(container, ttl)
			} else if event.Status == "stop" {
				s.removeContainer(event.ID)
			}
		}
	}
}

// Poll lists all containers from the docker client every time the TTL comes up and publishes them to etcd
func (s *Server) Poll(ttl time.Duration) {
	containers, err := s.DockerClient.ListContainers(docker.ListContainersOptions{})
	if err != nil {
		log.Fatal(err)
	}
	for _, container := range containers {
		// send container to channel for processing
		s.publishContainer(&container, ttl)
	}
}

// getContainer retrieves a container from the docker client based on id
func (s *Server) getContainer(id string) (*docker.APIContainers, error) {
	containers, err := s.DockerClient.ListContainers(docker.ListContainersOptions{})
	if err != nil {
		return nil, err
	}
	for _, container := range containers {
		// send container to channel for processing
		if container.ID == id {
			return &container, nil
		}
	}
	return nil, fmt.Errorf("could not find container with id %v", id)
}

// publishContainer publishes the docker container to etcd.
func (s *Server) publishContainer(container *docker.APIContainers, ttl time.Duration) {
	r := regexp.MustCompile(appNameRegex)
	for _, name := range container.Names {
		// HACK: remove slash from container name
		// see https://github.com/docker/docker/issues/7519
		containerName := name[1:]
		match := r.FindStringSubmatch(containerName)
		if match == nil {
			continue
		}
		appName := match[1]
		appPath := fmt.Sprintf("%s/%s", appName, containerName)
		keyPath := fmt.Sprintf("/deis/services/%s", appPath)
		for _, p := range container.Ports {
			// lowest port wins (docker sorts the ports)
			// TODO (bacongobbler): support multiple exposed ports
			port := strconv.Itoa(int(p.PublicPort))
			hostAndPort := s.host + ":" + port
			if s.IsPublishableApp(containerName) && s.IsPortOpen(hostAndPort) {
				s.setEtcd(keyPath, hostAndPort, uint64(ttl.Seconds()))
				safeMap.Lock()
				safeMap.data[container.ID] = appPath
				safeMap.Unlock()
			}
			break
		}
	}
}

// removeContainer remove a container published by this component
func (s *Server) removeContainer(event string) {
	safeMap.RLock()
	appPath := safeMap.data[event]
	safeMap.RUnlock()

	if appPath != "" {
		keyPath := fmt.Sprintf("/deis/services/%s", appPath)
		log.Printf("stopped %s\n", keyPath)
		s.removeEtcd(keyPath, false)
	}
}

// IsPublishableApp determines if the application should be published to etcd.
func (s *Server) IsPublishableApp(name string) bool {
	r := regexp.MustCompile(appNameRegex)
	match := r.FindStringSubmatch(name)
	if match == nil {
		return false
	}
	appName := match[1]
	version, err := strconv.Atoi(match[2])
	if err != nil {
		log.Println(err)
		return false
	}

	if version >= latestRunningVersion(s.EtcdClient, appName) {
		return true
	}
	return false
}

// IsPortOpen checks if the given port is accepting tcp connections
func (s *Server) IsPortOpen(hostAndPort string) bool {
	portOpen := false
	conn, err := net.Dial("tcp", hostAndPort)
	if err == nil {
		portOpen = true
		defer conn.Close()
	}
	return portOpen
}

// latestRunningVersion retrieves the highest version of the application published
// to etcd. If no app has been published, returns 0.
func latestRunningVersion(client *etcd.Client, appName string) int {
	r := regexp.MustCompile(appNameRegex)
	if client == nil {
		// FIXME: client should only be nil during tests. This should be properly refactored.
		if appName == "ceci-nest-pas-une-app" {
			return 3
		}
		return 0
	}
	resp, err := client.Get(fmt.Sprintf("/deis/services/%s", appName), false, true)
	if err != nil {
		// no app has been published here (key not found) or there was an error
		return 0
	}
	var versions []int
	for _, node := range resp.Node.Nodes {
		match := r.FindStringSubmatch(node.Key)
		// account for keys that may not be an application container
		if match == nil {
			continue
		}
		version, err := strconv.Atoi(match[2])
		if err != nil {
			log.Println(err)
			return 0
		}
		versions = append(versions, version)
	}
	return max(versions)
}

// max returns the maximum value in n
func max(n []int) int {
	val := 0
	for _, i := range n {
		if i > val {
			val = i
		}
	}
	return val
}

// setEtcd sets the corresponding etcd key with the value and ttl
func (s *Server) setEtcd(key, value string, ttl uint64) {
	if _, err := s.EtcdClient.Set(key, value, ttl); err != nil {
		log.Println(err)
	}
	if s.logLevel == "debug" {
		log.Println("set", key, "->", value)
	}
}

// removeEtcd removes the corresponding etcd key
func (s *Server) removeEtcd(key string, recursive bool) {
	if _, err := s.EtcdClient.Delete(key, recursive); err != nil {
		log.Println(err)
	}
	if s.logLevel == "debug" {
		log.Println("del", key)
	}
}
<|endoftext|>


<commit_before>package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gempir/justlog/helix"
	log "github.com/sirupsen/logrus"

	"github.com/gempir/go-twitch-irc/v2"
	"github.com/gempir/justlog/filelog"
	jsoniter "github.com/json-iterator/go"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"

	_ "github.com/gempir/justlog/docs"
	"github.com/swaggo/echo-swagger"
)

type Server struct {
	listenAddress string
	logPath       string
	fileLogger    *filelog.Logger
	helixClient   *helix.Client
	channels      []string
}

func NewServer(logPath string, listenAddress string, fileLogger *filelog.Logger, helixClient *helix.Client, channels []string) Server {
	return Server{
		listenAddress: listenAddress,
		logPath:       logPath,
		fileLogger:    fileLogger,
		helixClient:   helixClient,
		channels:      channels,
	}
}

func (s *Server) AddChannel(channel string) {
	s.channels = append(s.channels, channel)
}

func (s *Server) Init() {

	e := echo.New()
	e.HideBanner = true

	DefaultCORSConfig := middleware.CORSConfig{
		Skipper:      middleware.DefaultSkipper,
		AllowOrigins: []string{"*"},
		AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
	}
	e.Use(middleware.RemoveTrailingSlashWithConfig(middleware.TrailingSlashConfig{
		RedirectCode: http.StatusMovedPermanently,
	}))
	e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
		XSSProtection:         "", // disabled
		ContentTypeNosniff:    "nosniff",
		XFrameOptions:         "", // disabled
		HSTSMaxAge:            0,  // disabled
		ContentSecurityPolicy: "", // disabled
	}))
	e.Use(middleware.CORSWithConfig(DefaultCORSConfig))

	e.GET("/", func(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, "/index.html")
	})
	e.GET("/*", echoSwagger.WrapHandler)
	e.GET("/channels", s.getAllChannels)

	e.GET("/channel/:channel/user/:username/range", s.getUserLogsRangeByName)
	e.GET("/channelid/:channelid/userid/:userid/range", s.getUserLogsRange)

	e.GET("/channel/:channel/user/:username", s.getLastUserLogsByName)
	e.GET("/channel/:channel/user/:username/:year/:month", s.getUserLogsByName)
	e.GET("/channel/:channel/user/:username/random", s.getRandomQuoteByName)

	e.GET("/channelid/:channelid/user/:userid", s.getLastUserLogs)
	e.GET("/channelid/:channelid/userid/:userid/:year/:month", s.getUserLogs)
	e.GET("/channelid/:channelid/userid/:userid/random", s.getRandomQuote)

	e.GET("/channelid/:channelid/range", s.getChannelLogsRange)
	e.GET("/channel/:channel/range", s.getChannelLogsRangeByName)

	e.GET("/channel/:channel", s.getCurrentChannelLogsByName)
	e.GET("/channel/:channel/:year/:month/:day", s.getChannelLogsByName)
	e.GET("/channelid/:channelid", s.getCurrentChannelLogs)
	e.GET("/channelid/:channelid/:year/:month/:day", s.getChannelLogs)

	e.Logger.Fatal(e.Start(s.listenAddress))
}

var (
	userHourLimit    = 744.0
	channelHourLimit = 24.0
)

type channel struct {
	UserID string `json:"userID"`
	Name   string `json:"name"`
}

type AllChannelsJSON struct {
	Channels []channel `json:"channels"`
}

type chatLog struct {
	Messages []chatMessage `json:"messages"`
}

type chatMessage struct {
	Text        string             `json:"text"`
	Username    string             `json:"username"`
	DisplayName string             `json:"displayName"`
	Channel     string             `json:"channel"`
	Timestamp   timestamp          `json:"timestamp"`
	Type        twitch.MessageType `json:"type"`
	Raw         string             `json:"raw"`
}

type ErrorResponse struct {
	Message string `json:"message"`
}

type timestamp struct {
	time.Time
}

func reverse(input []string) []string {
	for i, j := 0, len(input)-1; i < j; i, j = i+1, j-1 {
		input[i], input[j] = input[j], input[i]
	}
	return input
}

// getAllChannels godoc
// @Summary Get all joined channels
// @tags bot
// @Produce  json
// @Success 200 {object} api.RandomQuoteJSON json
// @Failure 500 {object} api.ErrorResponse json
// @Router /channels [get]
func (s *Server) getAllChannels(c echo.Context) error {
	response := new(AllChannelsJSON)
	response.Channels = []channel{}
	users, err := s.helixClient.GetUsersByUserIds(s.channels)

	if err != nil {
		log.Error(err)
		return c.JSON(http.StatusInternalServerError, ErrorResponse{"Failure fetching data from twitch"})
	}

	for _, user := range users {
		response.Channels = append(response.Channels, channel{UserID: user.ID, Name: user.Login})
	}

	return c.JSON(http.StatusOK, response)
}

func (t timestamp) MarshalJSON() ([]byte, error) {
	return []byte("\"" + t.UTC().Format(time.RFC3339) + "\""), nil
}

func (t *timestamp) UnmarshalJSON(data []byte) error {
	goTime, err := time.Parse(time.RFC3339, strings.TrimSuffix(strings.TrimPrefix(string(data[:]), "\""), "\""))
	if err != nil {
		return err
	}
	*t = timestamp{
		goTime,
	}
	return nil
}

func parseFromTo(from, to string, limit float64) (time.Time, time.Time, error) {
	var fromTime time.Time
	var toTime time.Time

	if from == "" && to == "" {
		fromTime = time.Now().AddDate(0, -1, 0)
		toTime = time.Now()
	} else if from == "" && to != "" {
		var err error
		toTime, err = parseTimestamp(to)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse to timestamp: %s", err)
		}
		fromTime = toTime.AddDate(0, -1, 0)
	} else if from != "" && to == "" {
		var err error
		fromTime, err = parseTimestamp(from)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse from timestamp: %s", err)
		}
		toTime = fromTime.AddDate(0, 1, 0)
	} else {
		var err error

		fromTime, err = parseTimestamp(from)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse from timestamp: %s", err)
		}
		toTime, err = parseTimestamp(to)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse to timestamp: %s", err)
		}

		if toTime.Sub(fromTime).Hours() > limit {
			return fromTime, toTime, errors.New("Timespan too big")
		}
	}

	return fromTime, toTime, nil
}

func writeTextResponse(c echo.Context, cLog *chatLog) error {
	c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextPlainCharsetUTF8)
	c.Response().WriteHeader(http.StatusOK)

	for _, cMessage := range cLog.Messages {
		switch cMessage.Type {
		case twitch.PRIVMSG:
			c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s: %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Username, cMessage.Text)))
		case twitch.CLEARCHAT:
			c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Text)))
		case twitch.USERNOTICE:
			c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Text)))
		}
	}

	return nil
}

func writeRawResponse(c echo.Context, cLog *chatLog) error {
	c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextPlainCharsetUTF8)
	c.Response().WriteHeader(http.StatusOK)

	for _, cMessage := range cLog.Messages {
		c.Response().Write([]byte(cMessage.Raw + "\n"))
	}

	return nil
}

func writeJSONResponse(c echo.Context, logResult *chatLog) error {
	_, stream := c.QueryParams()["stream"]
	if stream {
		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
		c.Response().WriteHeader(http.StatusOK)

		return json.NewEncoder(c.Response()).Encode(logResult)
	}

	json := jsoniter.ConfigCompatibleWithStandardLibrary
	data, err := json.Marshal(logResult)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, err.Error())
	}

	return c.Blob(http.StatusOK, echo.MIMEApplicationJSONCharsetUTF8, data)
}

func parseTimestamp(timestamp string) (time.Time, error) {

	i, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		return time.Now(), err
	}
	return time.Unix(i, 0), nil
}

func shouldReverse(c echo.Context) bool {
	_, ok := c.QueryParams()["reverse"]

	return c.QueryParam("order") == "reverse" || ok
}

func shouldRespondWithJson(c echo.Context) bool {
	_, ok := c.QueryParams()["json"]

	return c.Request().Header.Get("Content-Type") == "application/json" || c.Request().Header.Get("accept") == "application/json" || c.QueryParam("type") == "json" || ok
}

func shouldRespondWithRaw(c echo.Context) bool {
	_, ok := c.QueryParams()["raw"]

	return c.QueryParam("type") == "raw" || ok
}
<commit_msg>fix api inconsistency<commit_after>package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gempir/justlog/helix"
	log "github.com/sirupsen/logrus"

	"github.com/gempir/go-twitch-irc/v2"
	"github.com/gempir/justlog/filelog"
	jsoniter "github.com/json-iterator/go"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"

	_ "github.com/gempir/justlog/docs"
	echoSwagger "github.com/swaggo/echo-swagger"
)

type Server struct {
	listenAddress string
	logPath       string
	fileLogger    *filelog.Logger
	helixClient   *helix.Client
	channels      []string
}

func NewServer(logPath string, listenAddress string, fileLogger *filelog.Logger, helixClient *helix.Client, channels []string) Server {
	return Server{
		listenAddress: listenAddress,
		logPath:       logPath,
		fileLogger:    fileLogger,
		helixClient:   helixClient,
		channels:      channels,
	}
}

func (s *Server) AddChannel(channel string) {
	s.channels = append(s.channels, channel)
}

func (s *Server) Init() {

	e := echo.New()
	e.HideBanner = true

	DefaultCORSConfig := middleware.CORSConfig{
		Skipper:      middleware.DefaultSkipper,
		AllowOrigins: []string{"*"},
		AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
	}
	e.Use(middleware.RemoveTrailingSlashWithConfig(middleware.TrailingSlashConfig{
		RedirectCode: http.StatusMovedPermanently,
	}))
	e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
		XSSProtection:         "", // disabled
		ContentTypeNosniff:    "nosniff",
		XFrameOptions:         "", // disabled
		HSTSMaxAge:            0,  // disabled
		ContentSecurityPolicy: "", // disabled
	}))
	e.Use(middleware.CORSWithConfig(DefaultCORSConfig))

	e.GET("/", func(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, "/index.html")
	})
	e.GET("/*", echoSwagger.WrapHandler)
	e.GET("/channels", s.getAllChannels)

	e.GET("/channel/:channel/user/:username/range", s.getUserLogsRangeByName)
	e.GET("/channelid/:channelid/userid/:userid/range", s.getUserLogsRange)

	e.GET("/channel/:channel/user/:username", s.getLastUserLogsByName)
	e.GET("/channel/:channel/user/:username/:year/:month", s.getUserLogsByName)
	e.GET("/channel/:channel/user/:username/random", s.getRandomQuoteByName)

	e.GET("/channelid/:channelid/userid/:userid", s.getLastUserLogs)
	e.GET("/channelid/:channelid/userid/:userid/:year/:month", s.getUserLogs)
	e.GET("/channelid/:channelid/userid/:userid/random", s.getRandomQuote)

	e.GET("/channelid/:channelid/range", s.getChannelLogsRange)
	e.GET("/channel/:channel/range", s.getChannelLogsRangeByName)

	e.GET("/channel/:channel", s.getCurrentChannelLogsByName)
	e.GET("/channel/:channel/:year/:month/:day", s.getChannelLogsByName)
	e.GET("/channelid/:channelid", s.getCurrentChannelLogs)
	e.GET("/channelid/:channelid/:year/:month/:day", s.getChannelLogs)

	e.Logger.Fatal(e.Start(s.listenAddress))
}

var (
	userHourLimit    = 744.0
	channelHourLimit = 24.0
)

type channel struct {
	UserID string `json:"userID"`
	Name   string `json:"name"`
}

type AllChannelsJSON struct {
	Channels []channel `json:"channels"`
}

type chatLog struct {
	Messages []chatMessage `json:"messages"`
}

type chatMessage struct {
	Text        string             `json:"text"`
	Username    string             `json:"username"`
	DisplayName string             `json:"displayName"`
	Channel     string             `json:"channel"`
	Timestamp   timestamp          `json:"timestamp"`
	Type        twitch.MessageType `json:"type"`
	Raw         string             `json:"raw"`
}

type ErrorResponse struct {
	Message string `json:"message"`
}

type timestamp struct {
	time.Time
}

func reverse(input []string) []string {
	for i, j := 0, len(input)-1; i < j; i, j = i+1, j-1 {
		input[i], input[j] = input[j], input[i]
	}
	return input
}

// getAllChannels godoc
// @Summary Get all joined channels
// @tags bot
// @Produce  json
// @Success 200 {object} api.RandomQuoteJSON json
// @Failure 500 {object} api.ErrorResponse json
// @Router /channels [get]
func (s *Server) getAllChannels(c echo.Context) error {
	response := new(AllChannelsJSON)
	response.Channels = []channel{}
	users, err := s.helixClient.GetUsersByUserIds(s.channels)

	if err != nil {
		log.Error(err)
		return c.JSON(http.StatusInternalServerError, ErrorResponse{"Failure fetching data from twitch"})
	}

	for _, user := range users {
		response.Channels = append(response.Channels, channel{UserID: user.ID, Name: user.Login})
	}

	return c.JSON(http.StatusOK, response)
}

func (t timestamp) MarshalJSON() ([]byte, error) {
	return []byte("\"" + t.UTC().Format(time.RFC3339) + "\""), nil
}

func (t *timestamp) UnmarshalJSON(data []byte) error {
	goTime, err := time.Parse(time.RFC3339, strings.TrimSuffix(strings.TrimPrefix(string(data[:]), "\""), "\""))
	if err != nil {
		return err
	}
	*t = timestamp{
		goTime,
	}
	return nil
}

func parseFromTo(from, to string, limit float64) (time.Time, time.Time, error) {
	var fromTime time.Time
	var toTime time.Time

	if from == "" && to == "" {
		fromTime = time.Now().AddDate(0, -1, 0)
		toTime = time.Now()
	} else if from == "" && to != "" {
		var err error
		toTime, err = parseTimestamp(to)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse to timestamp: %s", err)
		}
		fromTime = toTime.AddDate(0, -1, 0)
	} else if from != "" && to == "" {
		var err error
		fromTime, err = parseTimestamp(from)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse from timestamp: %s", err)
		}
		toTime = fromTime.AddDate(0, 1, 0)
	} else {
		var err error

		fromTime, err = parseTimestamp(from)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse from timestamp: %s", err)
		}
		toTime, err = parseTimestamp(to)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse to timestamp: %s", err)
		}

		if toTime.Sub(fromTime).Hours() > limit {
			return fromTime, toTime, errors.New("Timespan too big")
		}
	}

	return fromTime, toTime, nil
}

func writeTextResponse(c echo.Context, cLog *chatLog) error {
	c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextPlainCharsetUTF8)
	c.Response().WriteHeader(http.StatusOK)

	for _, cMessage := range cLog.Messages {
		switch cMessage.Type {
		case twitch.PRIVMSG:
			c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s: %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Username, cMessage.Text)))
		case twitch.CLEARCHAT:
			c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Text)))
		case twitch.USERNOTICE:
			c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Text)))
		}
	}

	return nil
}

func writeRawResponse(c echo.Context, cLog *chatLog) error {
	c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextPlainCharsetUTF8)
	c.Response().WriteHeader(http.StatusOK)

	for _, cMessage := range cLog.Messages {
		c.Response().Write([]byte(cMessage.Raw + "\n"))
	}

	return nil
}

func writeJSONResponse(c echo.Context, logResult *chatLog) error {
	_, stream := c.QueryParams()["stream"]
	if stream {
		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
		c.Response().WriteHeader(http.StatusOK)

		return json.NewEncoder(c.Response()).Encode(logResult)
	}

	json := jsoniter.ConfigCompatibleWithStandardLibrary
	data, err := json.Marshal(logResult)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, err.Error())
	}

	return c.Blob(http.StatusOK, echo.MIMEApplicationJSONCharsetUTF8, data)
}

func parseTimestamp(timestamp string) (time.Time, error) {

	i, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		return time.Now(), err
	}
	return time.Unix(i, 0), nil
}

func shouldReverse(c echo.Context) bool {
	_, ok := c.QueryParams()["reverse"]

	return c.QueryParam("order") == "reverse" || ok
}

func shouldRespondWithJson(c echo.Context) bool {
	_, ok := c.QueryParams()["json"]

	return c.Request().Header.Get("Content-Type") == "application/json" || c.Request().Header.Get("accept") == "application/json" || c.QueryParam("type") == "json" || ok
}

func shouldRespondWithRaw(c echo.Context) bool {
	_, ok := c.QueryParams()["raw"]

	return c.QueryParam("type") == "raw" || ok
}
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package internal_test

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"testing"

	"v.io/v23"
	"v.io/v23/context"
	"v.io/v23/naming"
	"v.io/v23/rpc"
	"v.io/v23/security"
	"v.io/v23/verror"
	vsecurity "v.io/x/ref/lib/security"
	"v.io/x/ref/services/role"
	irole "v.io/x/ref/services/role/roled/internal"
	"v.io/x/ref/test/testutil"

	_ "v.io/x/ref/runtime/factories/generic"
)

func TestSeekBlessings(t *testing.T) {
	ctx, shutdown := v23.Init()
	defer shutdown()

	workdir, err := ioutil.TempDir("", "test-role-server-")
	if err != nil {
		t.Fatalf("ioutil.TempDir failed: %v", err)
	}
	defer os.RemoveAll(workdir)

	// Role A is a restricted role, i.e. it can be used in sensitive Permissions.
	roleAConf := irole.Config{
		Members: []security.BlessingPattern{
			"root/users/user1/_role",
			"root/users/user2/_role",
			"root/users/user3", // _role implied
		},
		Extend: true,
	}
	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "A.conf"))

	// Role B is an unrestricted role.
	roleBConf := irole.Config{
		Members: []security.BlessingPattern{
			"root/users/user1/_role",
			"root/users/user3/_role",
		},
		Audit:  true,
		Extend: false,
	}
	irole.WriteConfig(t, roleBConf, filepath.Join(workdir, "B.conf"))

	root := testutil.NewIDProvider("root")

	var (
		user1  = newPrincipalContext(t, ctx, root, "users/user1")
		user1R = newPrincipalContext(t, ctx, root, "users/user1/_role")
		user2  = newPrincipalContext(t, ctx, root, "users/user2")
		user2R = newPrincipalContext(t, ctx, root, "users/user2/_role")
		user3  = newPrincipalContext(t, ctx, root, "users/user3")
		user3R = newPrincipalContext(t, ctx, root, "users/user3", "users/user3/_role/foo", "users/user3/_role/bar")
	)

	testServerCtx := newPrincipalContext(t, ctx, root, "testserver")
	server, testAddr := newServer(t, testServerCtx)
	tDisp := &testDispatcher{}
	if err := server.ServeDispatcher("", tDisp); err != nil {
		t.Fatalf("server.ServeDispatcher failed: %v", err)
	}

	const noErr = ""
	testcases := []struct {
		ctx       *context.T
		role      string
		errID     verror.ID
		blessings []string
	}{
		{user1, "", verror.ErrUnknownMethod.ID, nil},
		{user1, "unknown", verror.ErrNoAccess.ID, nil},
		{user2, "unknown", verror.ErrNoAccess.ID, nil},
		{user3, "unknown", verror.ErrNoAccess.ID, nil},

		{user1, "A", verror.ErrNoAccess.ID, nil},
		{user1R, "A", noErr, []string{"root/roles/A/root/users/user1"}},
		{user2, "A", verror.ErrNoAccess.ID, nil},
		{user2R, "A", noErr, []string{"root/roles/A/root/users/user2"}},
		{user3, "A", verror.ErrNoAccess.ID, nil},
		{user3R, "A", noErr, []string{"root/roles/A/root/users/user3/_role/bar", "root/roles/A/root/users/user3/_role/foo"}},

		{user1, "B", verror.ErrNoAccess.ID, nil},
		{user1R, "B", noErr, []string{"root/roles/B"}},
		{user2, "B", verror.ErrNoAccess.ID, nil},
		{user2R, "B", verror.ErrNoAccess.ID, nil},
		{user3, "B", verror.ErrNoAccess.ID, nil},
		{user3R, "B", noErr, []string{"root/roles/B"}},
	}
	addr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)
	for _, tc := range testcases {
		user := v23.GetPrincipal(tc.ctx).BlessingStore().Default()
		c := role.RoleClient(naming.Join(addr, tc.role))
		blessings, err := c.SeekBlessings(tc.ctx)
		if verror.ErrorID(err) != tc.errID {
			t.Errorf("unexpected error ID for (%q, %q). Got %#v, expected %#v", user, tc.role, verror.ErrorID(err), tc.errID)
		}
		if err != nil {
			continue
		}
		previousBlessings, _ := v23.GetPrincipal(tc.ctx).BlessingStore().Set(blessings, security.AllPrincipals)
		blessingNames, rejected := callTest(t, tc.ctx, testAddr)
		if !reflect.DeepEqual(blessingNames, tc.blessings) {
			t.Errorf("unexpected blessings for (%q, %q). Got %q, expected %q", user, tc.role, blessingNames, tc.blessings)
		}
		if len(rejected) != 0 {
			t.Errorf("unexpected rejected blessings for (%q, %q): %q", user, tc.role, rejected)
		}
		v23.GetPrincipal(tc.ctx).BlessingStore().Set(previousBlessings, security.AllPrincipals)
	}
}

func TestPeerBlessingCaveats(t *testing.T) {
	ctx, shutdown := v23.Init()
	defer shutdown()

	workdir, err := ioutil.TempDir("", "test-role-server-")
	if err != nil {
		t.Fatalf("ioutil.TempDir failed: %v", err)
	}
	defer os.RemoveAll(workdir)

	roleConf := irole.Config{
		Members: []security.BlessingPattern{"root/users/user/_role"},
		Peers: []security.BlessingPattern{
			security.BlessingPattern("root/peer1"),
			security.BlessingPattern("root/peer3"),
		},
	}
	irole.WriteConfig(t, roleConf, filepath.Join(workdir, "role.conf"))

	var (
		root  = testutil.NewIDProvider("root")
		user  = newPrincipalContext(t, ctx, root, "users/user/_role")
		peer1 = newPrincipalContext(t, ctx, root, "peer1")
		peer2 = newPrincipalContext(t, ctx, root, "peer2")
		peer3 = newPrincipalContext(t, ctx, root, "peer3")
	)

	roleAddr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)

	tDisp := &testDispatcher{}
	server1, testPeer1 := newServer(t, peer1)
	if err := server1.ServeDispatcher("", tDisp); err != nil {
		t.Fatalf("server.ServeDispatcher failed: %v", err)
	}
	server2, testPeer2 := newServer(t, peer2)
	if err := server2.ServeDispatcher("", tDisp); err != nil {
		t.Fatalf("server.ServeDispatcher failed: %v", err)
	}
	server3, testPeer3 := newServer(t, peer3)
	if err := server3.ServeDispatcher("", tDisp); err != nil {
		t.Fatalf("server.ServeDispatcher failed: %v", err)
	}

	c := role.RoleClient(naming.Join(roleAddr, "role"))
	blessings, err := c.SeekBlessings(user)
	if err != nil {
		t.Error("unexpected error:", err)
	}
	v23.GetPrincipal(user).BlessingStore().Set(blessings, security.AllPrincipals)

	testcases := []struct {
		peer          string
		blessingNames []string
		rejectedNames []string
	}{
		{testPeer1, []string{"root/roles/role"}, nil},
		{testPeer2, nil, []string{"root/roles/role"}},
		{testPeer3, []string{"root/roles/role"}, nil},
	}
	for i, tc := range testcases {
		blessingNames, rejected := callTest(t, user, tc.peer)
		var rejectedNames []string
		for _, r := range rejected {
			rejectedNames = append(rejectedNames, r.Blessing)
		}
		if !reflect.DeepEqual(blessingNames, tc.blessingNames) {
			t.Errorf("Unexpected blessing names for #%d. Got %q, expected %q", i, blessingNames, tc.blessingNames)
		}
		if !reflect.DeepEqual(rejectedNames, tc.rejectedNames) {
			t.Errorf("Unexpected rejected names for #%d. Got %q, expected %q", i, rejectedNames, tc.rejectedNames)
		}
	}
}

func TestGlob(t *testing.T) {
	ctx, shutdown := v23.Init()
	defer shutdown()

	workdir, err := ioutil.TempDir("", "test-role-server-")
	if err != nil {
		t.Fatalf("ioutil.TempDir failed: %v", err)
	}
	defer os.RemoveAll(workdir)
	os.Mkdir(filepath.Join(workdir, "sub1"), 0700)
	os.Mkdir(filepath.Join(workdir, "sub1", "sub2"), 0700)
	os.Mkdir(filepath.Join(workdir, "sub3"), 0700)

	// Role that user1 has access to.
	roleAConf := irole.Config{Members: []security.BlessingPattern{"root/user1"}}
	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "A.conf"))
	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/B.conf"))
	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/C.conf"))
	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/sub2/D.conf"))

	// Role that user2 has access to.
	roleBConf := irole.Config{Members: []security.BlessingPattern{"root/user2"}}
	irole.WriteConfig(t, roleBConf, filepath.Join(workdir, "sub1/sub2/X.conf"))

	root := testutil.NewIDProvider("root")
	user1 := newPrincipalContext(t, ctx, root, "user1/_role")
	user2 := newPrincipalContext(t, ctx, root, "user2/_role")
	user3 := newPrincipalContext(t, ctx, root, "user3/_role")
	addr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)

	testcases := []struct {
		user    *context.T
		name    string
		pattern string
		results []string
	}{
		{user1, "", "*", []string{"A", "sub1"}},
		{user1, "sub1", "*", []string{"B", "C", "sub2"}},
		{user1, "sub1/sub2", "*", []string{"D"}},
		{user1, "", "...", []string{"", "A", "sub1", "sub1/B", "sub1/C", "sub1/sub2", "sub1/sub2/D"}},
		{user2, "", "*", []string{"sub1"}},
		{user2, "", "...", []string{"", "sub1", "sub1/sub2", "sub1/sub2/X"}},
		{user3, "", "*", []string{}},
		{user3, "", "...", []string{""}},
	}
	for i, tc := range testcases {
		matches, _, _ := testutil.GlobName(tc.user, naming.Join(addr, tc.name), tc.pattern)
		if !reflect.DeepEqual(matches, tc.results) {
			t.Errorf("unexpected results for tc #%d. Got %q, expected %q", i, matches, tc.results)
		}
	}
}

func newPrincipalContext(t *testing.T, ctx *context.T, root *testutil.IDProvider, names ...string) *context.T {
	principal := testutil.NewPrincipal()
	var blessings []security.Blessings
	for _, n := range names {
		blessing, err := root.NewBlessings(principal, n)
		if err != nil {
			t.Fatalf("root.Bless failed for %q: %v", n, err)
		}
		blessings = append(blessings, blessing)
	}
	bUnion, err := security.UnionOfBlessings(blessings...)
	if err != nil {
		t.Fatalf("security.UnionOfBlessings failed: %v", err)
	}
	vsecurity.SetDefaultBlessings(principal, bUnion)
	ctx, err = v23.WithPrincipal(ctx, principal)
	if err != nil {
		t.Fatalf("v23.WithPrincipal failed: %v", err)
	}
	return ctx
}

func newRoleServer(t *testing.T, ctx *context.T, dir string) string {
	server, addr := newServer(t, ctx)
	if err := server.ServeDispatcher("", irole.NewDispatcher(dir, addr)); err != nil {
		t.Fatalf("ServeDispatcher failed: %v", err)
	}
	return addr
}

func newServer(t *testing.T, ctx *context.T) (rpc.Server, string) {
	server, err := v23.NewServer(ctx)
	if err != nil {
		t.Fatalf("NewServer() failed: %v", err)
	}
	spec := rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
	endpoints, err := server.Listen(spec)
	if err != nil {
		t.Fatalf("Listen(%v) failed: %v", spec, err)
	}
	return server, endpoints[0].Name()
}

func callTest(t *testing.T, ctx *context.T, addr string) (blessingNames []string, rejected []security.RejectedBlessing) {
	call, err := v23.GetClient(ctx).StartCall(ctx, addr, "Test", nil)
	if err != nil {
		t.Fatalf("StartCall failed: %v", err)
	}
	if err := call.Finish(&blessingNames, &rejected); err != nil {
		t.Fatalf("Finish failed: %v", err)
	}
	return
}

type testDispatcher struct {
}

func (d *testDispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
	return d, d, nil
}

func (d *testDispatcher) Authorize(*context.T, security.Call) error {
	return nil
}

func (d *testDispatcher) Test(ctx *context.T, call rpc.ServerCall) ([]string, []security.RejectedBlessing, error) {
	blessings, rejected := security.RemoteBlessingNames(ctx, call.Security())
	return blessings, rejected, nil
}
<commit_msg>ref/services/role: Move the role tests to the new XServer API.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package internal_test

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"testing"

	"v.io/v23"
	"v.io/v23/context"
	"v.io/v23/naming"
	"v.io/v23/rpc"
	"v.io/v23/security"
	"v.io/v23/verror"
	vsecurity "v.io/x/ref/lib/security"
	"v.io/x/ref/lib/xrpc"
	"v.io/x/ref/services/role"
	irole "v.io/x/ref/services/role/roled/internal"
	"v.io/x/ref/test"
	"v.io/x/ref/test/testutil"

	_ "v.io/x/ref/runtime/factories/generic"
)

func TestSeekBlessings(t *testing.T) {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	workdir, err := ioutil.TempDir("", "test-role-server-")
	if err != nil {
		t.Fatalf("ioutil.TempDir failed: %v", err)
	}
	defer os.RemoveAll(workdir)

	// Role A is a restricted role, i.e. it can be used in sensitive Permissions.
	roleAConf := irole.Config{
		Members: []security.BlessingPattern{
			"test-blessing/users/user1/_role",
			"test-blessing/users/user2/_role",
			"test-blessing/users/user3", // _role implied
		},
		Extend: true,
	}
	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "A.conf"))

	// Role B is an unrestricted role.
	roleBConf := irole.Config{
		Members: []security.BlessingPattern{
			"test-blessing/users/user1/_role",
			"test-blessing/users/user3/_role",
		},
		Audit:  true,
		Extend: false,
	}
	irole.WriteConfig(t, roleBConf, filepath.Join(workdir, "B.conf"))

	root := testutil.IDProviderFromPrincipal(v23.GetPrincipal(ctx))

	var (
		user1  = newPrincipalContext(t, ctx, root, "users/user1")
		user1R = newPrincipalContext(t, ctx, root, "users/user1/_role")
		user2  = newPrincipalContext(t, ctx, root, "users/user2")
		user2R = newPrincipalContext(t, ctx, root, "users/user2/_role")
		user3  = newPrincipalContext(t, ctx, root, "users/user3")
		user3R = newPrincipalContext(t, ctx, root, "users/user3", "users/user3/_role/foo", "users/user3/_role/bar")
	)

	testServerCtx := newPrincipalContext(t, ctx, root, "testserver")
	tDisp := &testDispatcher{}
	_, err = xrpc.NewDispatchingServer(testServerCtx, "test", tDisp)
	if err != nil {
		t.Fatalf("NewDispatchingServer failed: %v", err)
	}

	const noErr = ""
	testcases := []struct {
		ctx       *context.T
		role      string
		errID     verror.ID
		blessings []string
	}{
		{user1, "", verror.ErrUnknownMethod.ID, nil},
		{user1, "unknown", verror.ErrNoAccess.ID, nil},
		{user2, "unknown", verror.ErrNoAccess.ID, nil},
		{user3, "unknown", verror.ErrNoAccess.ID, nil},

		{user1, "A", verror.ErrNoAccess.ID, nil},
		{user1R, "A", noErr, []string{"test-blessing/roles/A/test-blessing/users/user1"}},
		{user2, "A", verror.ErrNoAccess.ID, nil},
		{user2R, "A", noErr, []string{"test-blessing/roles/A/test-blessing/users/user2"}},
		{user3, "A", verror.ErrNoAccess.ID, nil},
		{user3R, "A", noErr, []string{"test-blessing/roles/A/test-blessing/users/user3/_role/bar", "test-blessing/roles/A/test-blessing/users/user3/_role/foo"}},

		{user1, "B", verror.ErrNoAccess.ID, nil},
		{user1R, "B", noErr, []string{"test-blessing/roles/B"}},
		{user2, "B", verror.ErrNoAccess.ID, nil},
		{user2R, "B", verror.ErrNoAccess.ID, nil},
		{user3, "B", verror.ErrNoAccess.ID, nil},
		{user3R, "B", noErr, []string{"test-blessing/roles/B"}},
	}
	addr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)
	for _, tc := range testcases {
		user := v23.GetPrincipal(tc.ctx).BlessingStore().Default()
		c := role.RoleClient(naming.Join(addr, tc.role))
		blessings, err := c.SeekBlessings(tc.ctx)
		if verror.ErrorID(err) != tc.errID {
			t.Errorf("unexpected error ID for (%q, %q). Got %#v, expected %#v", user, tc.role, verror.ErrorID(err), tc.errID)
		}
		if err != nil {
			continue
		}
		previousBlessings, _ := v23.GetPrincipal(tc.ctx).BlessingStore().Set(blessings, security.AllPrincipals)
		blessingNames, rejected := callTest(t, tc.ctx, "test")
		if !reflect.DeepEqual(blessingNames, tc.blessings) {
			t.Errorf("unexpected blessings for (%q, %q). Got %q, expected %q", user, tc.role, blessingNames, tc.blessings)
		}
		if len(rejected) != 0 {
			t.Errorf("unexpected rejected blessings for (%q, %q): %q", user, tc.role, rejected)
		}
		v23.GetPrincipal(tc.ctx).BlessingStore().Set(previousBlessings, security.AllPrincipals)
	}
}

func TestPeerBlessingCaveats(t *testing.T) {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	workdir, err := ioutil.TempDir("", "test-role-server-")
	if err != nil {
		t.Fatalf("ioutil.TempDir failed: %v", err)
	}
	defer os.RemoveAll(workdir)

	roleConf := irole.Config{
		Members: []security.BlessingPattern{"test-blessing/users/user/_role"},
		Peers: []security.BlessingPattern{
			security.BlessingPattern("test-blessing/peer1"),
			security.BlessingPattern("test-blessing/peer3"),
		},
	}
	irole.WriteConfig(t, roleConf, filepath.Join(workdir, "role.conf"))

	var (
		root  = testutil.IDProviderFromPrincipal(v23.GetPrincipal(ctx))
		user  = newPrincipalContext(t, ctx, root, "users/user/_role")
		peer1 = newPrincipalContext(t, ctx, root, "peer1")
		peer2 = newPrincipalContext(t, ctx, root, "peer2")
		peer3 = newPrincipalContext(t, ctx, root, "peer3")
	)

	roleAddr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)

	tDisp := &testDispatcher{}
	_, err = xrpc.NewDispatchingServer(peer1, "peer1", tDisp)
	if err != nil {
		t.Fatalf("NewDispatchingServer failed: %v", err)
	}
	_, err = xrpc.NewDispatchingServer(peer2, "peer2", tDisp)
	if err != nil {
		t.Fatalf("NewDispatchingServer failed: %v", err)
	}
	_, err = xrpc.NewDispatchingServer(peer3, "peer3", tDisp)
	if err != nil {
		t.Fatalf("NewDispatchingServer failed: %v", err)
	}

	c := role.RoleClient(naming.Join(roleAddr, "role"))
	blessings, err := c.SeekBlessings(user)
	if err != nil {
		t.Error("unexpected error:", err)
	}
	v23.GetPrincipal(user).BlessingStore().Set(blessings, security.AllPrincipals)

	testcases := []struct {
		peer          string
		blessingNames []string
		rejectedNames []string
	}{
		{"peer1", []string{"test-blessing/roles/role"}, nil},
		{"peer2", nil, []string{"test-blessing/roles/role"}},
		{"peer3", []string{"test-blessing/roles/role"}, nil},
	}
	for i, tc := range testcases {
		blessingNames, rejected := callTest(t, user, tc.peer)
		var rejectedNames []string
		for _, r := range rejected {
			rejectedNames = append(rejectedNames, r.Blessing)
		}
		if !reflect.DeepEqual(blessingNames, tc.blessingNames) {
			t.Errorf("Unexpected blessing names for #%d. Got %q, expected %q", i, blessingNames, tc.blessingNames)
		}
		if !reflect.DeepEqual(rejectedNames, tc.rejectedNames) {
			t.Errorf("Unexpected rejected names for #%d. Got %q, expected %q", i, rejectedNames, tc.rejectedNames)
		}
	}
}

func TestGlob(t *testing.T) {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	workdir, err := ioutil.TempDir("", "test-role-server-")
	if err != nil {
		t.Fatalf("ioutil.TempDir failed: %v", err)
	}
	defer os.RemoveAll(workdir)
	os.Mkdir(filepath.Join(workdir, "sub1"), 0700)
	os.Mkdir(filepath.Join(workdir, "sub1", "sub2"), 0700)
	os.Mkdir(filepath.Join(workdir, "sub3"), 0700)

	// Role that user1 has access to.
	roleAConf := irole.Config{Members: []security.BlessingPattern{"test-blessing/user1"}}
	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "A.conf"))
	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/B.conf"))
	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/C.conf"))
	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/sub2/D.conf"))

	// Role that user2 has access to.
	roleBConf := irole.Config{Members: []security.BlessingPattern{"test-blessing/user2"}}
	irole.WriteConfig(t, roleBConf, filepath.Join(workdir, "sub1/sub2/X.conf"))

	root := testutil.IDProviderFromPrincipal(v23.GetPrincipal(ctx))
	user1 := newPrincipalContext(t, ctx, root, "user1/_role")
	user2 := newPrincipalContext(t, ctx, root, "user2/_role")
	user3 := newPrincipalContext(t, ctx, root, "user3/_role")
	addr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)

	testcases := []struct {
		user    *context.T
		name    string
		pattern string
		results []string
	}{
		{user1, "", "*", []string{"A", "sub1"}},
		{user1, "sub1", "*", []string{"B", "C", "sub2"}},
		{user1, "sub1/sub2", "*", []string{"D"}},
		{user1, "", "...", []string{"", "A", "sub1", "sub1/B", "sub1/C", "sub1/sub2", "sub1/sub2/D"}},
		{user2, "", "*", []string{"sub1"}},
		{user2, "", "...", []string{"", "sub1", "sub1/sub2", "sub1/sub2/X"}},
		{user3, "", "*", []string{}},
		{user3, "", "...", []string{""}},
	}
	for i, tc := range testcases {
		matches, _, _ := testutil.GlobName(tc.user, naming.Join(addr, tc.name), tc.pattern)
		if !reflect.DeepEqual(matches, tc.results) {
			t.Errorf("unexpected results for tc #%d. Got %q, expected %q", i, matches, tc.results)
		}
	}
}

func newPrincipalContext(t *testing.T, ctx *context.T, root *testutil.IDProvider, names ...string) *context.T {
	principal := testutil.NewPrincipal()
	var blessings []security.Blessings
	for _, n := range names {
		blessing, err := root.NewBlessings(principal, n)
		if err != nil {
			t.Fatalf("root.Bless failed for %q: %v", n, err)
		}
		blessings = append(blessings, blessing)
	}
	bUnion, err := security.UnionOfBlessings(blessings...)
	if err != nil {
		t.Fatalf("security.UnionOfBlessings failed: %v", err)
	}
	vsecurity.SetDefaultBlessings(principal, bUnion)
	ctx, err = v23.WithPrincipal(ctx, principal)
	if err != nil {
		t.Fatalf("v23.WithPrincipal failed: %v", err)
	}
	return ctx
}

func newRoleServer(t *testing.T, ctx *context.T, dir string) string {
	_, err := xrpc.NewDispatchingServer(ctx, "role", irole.NewDispatcher(dir, "role"))
	if err != nil {
		t.Fatalf("ServeDispatcher failed: %v", err)
	}
	return "role"
}

func callTest(t *testing.T, ctx *context.T, addr string) (blessingNames []string, rejected []security.RejectedBlessing) {
	call, err := v23.GetClient(ctx).StartCall(ctx, addr, "Test", nil)
	if err != nil {
		t.Fatalf("StartCall failed: %v", err)
	}
	if err := call.Finish(&blessingNames, &rejected); err != nil {
		t.Fatalf("Finish failed: %v", err)
	}
	return
}

type testDispatcher struct {
}

func (d *testDispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
	return d, d, nil
}

func (d *testDispatcher) Authorize(*context.T, security.Call) error {
	return nil
}

func (d *testDispatcher) Test(ctx *context.T, call rpc.ServerCall) ([]string, []security.RejectedBlessing, error) {
	blessings, rejected := security.RemoteBlessingNames(ctx, call.Security())
	return blessings, rejected, nil
}
<|endoftext|>