<commit_before>package broadword

import (
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"testing"
	"time"
)

var r *rand.Rand
var w Word
var ws string

func testCase() (w Word, ws string) {
	r = rand.New(rand.NewSource(time.Now().UnixNano()))
	w = Word(r.Uint32()) | Word(r.Uint32()<<32)
	ws = fmt.Sprintf("%064b", w)
	return
}

func TestMain(m *testing.M) {
	w, ws = testCase()
	os.Exit(m.Run())
}

func TestCount1(t *testing.T) {
	got := w.Count1()
	want := strings.Count(ws, "1")
	if got != want {
		t.Errorf("got %d, want %d for %s", got, want, ws)
	}
}

func TestCount0(t *testing.T) {
	want := w.Count0()
	got := strings.Count(ws, "0")
	if got != want {
		t.Errorf("got %d, want %d for %s", got, want, ws)
	}
}

func TestCount(t *testing.T) {
	for i := 0; i < 2; i++ {
		want := w.Count(i)
		got := strings.Count(ws, strconv.Itoa(i))
		if got != want {
			t.Errorf("got %d, want %d for %s", got, want, ws)
		}
	}
}

func TestGet(t *testing.T) {
	for i := 0; i < W; i++ {
		got := w.Get(i)
		want := Word(ws[W-i-1] - '0') // since ws is reversed.
		if got != want {
			t.Errorf("got %d, want %d for %s", got, want, ws)
		}
	}
}

func TestSet1(t *testing.T) {
	for i := 0; i < W; i++ {
		v := w.Set1(i)
		for j := 0; j < W; j++ {
			got := v.Get(j)
			want := w.Get(j)
			if i == j {
				want = 1
			}
			if got != want {
				t.Errorf("got %d, want %d", got, want)
			}
		}
	}
}

func TestSet0(t *testing.T) {
	for i := 0; i < W; i++ {
		v := w.Set0(i)
		for j := 0; j < W; j++ {
			got := v.Get(j)
			want := w.Get(j)
			if i == j {
				want = 0
			}
			if got != want {
				t.Errorf("got %d, want %d", got, want)
			}
		}
	}
}

func TestFlip(t *testing.T) {
	for i := 0; i < W; i++ {
		v := w.Flip(i)
		for j := 0; j < W; j++ {
			got := v.Get(j)
			want := w.Get(j)
			if i == j {
				if want == 1 {
					want = 0
				} else {
					want = 1
				}
			}
			if got != want {
				t.Errorf("got %d, want %d", got, want)
			}
		}
	}
}

func TestRank1(t *testing.T) {
	for i := 0; i < W; i++ {
		got := w.Rank1(i)
		want := strings.Count(ws[len(ws)-i-1:len(ws)], "1")
		if got != want {
			t.Errorf("got %d, want %d", got, want)
		}
	}
}

func BenchmarkCount1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Count1()
	}
}

func BenchmarkCount0(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Count0()
	}
}

func BenchmarkCount(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Count(1)
	}
}

func BenchmarkGet(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Get(i % W)
	}
}

func BenchmarkSet1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Set1(i % W)
	}
}

func BenchmarkSet0(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Set0(i % W)
	}
}

func BenchmarkFlip(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Flip(i % W)
	}
}

func BenchmarkRank1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Rank1(i % W)
	}
}
<commit_msg>Fix testCase<commit_after>package broadword

import (
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"testing"
	"time"
)

var r *rand.Rand
var w Word
var ws string

func testCase() (w Word, ws string) {
	r = rand.New(rand.NewSource(time.Now().UnixNano()))
	w = Word(r.Uint32()) | Word(r.Uint32())<<32
	ws = fmt.Sprintf("%064b", w)
	return
}

func TestMain(m *testing.M) {
	w, ws = testCase()
	os.Exit(m.Run())
}

func TestCount1(t *testing.T) {
	got := w.Count1()
	want := strings.Count(ws, "1")
	if got != want {
		t.Errorf("got %d, want %d for %s", got, want, ws)
	}
}

func TestCount0(t *testing.T) {
	want := w.Count0()
	got := strings.Count(ws, "0")
	if got != want {
		t.Errorf("got %d, want %d for %s", got, want, ws)
	}
}

func TestCount(t *testing.T) {
	for i := 0; i < 2; i++ {
		want := w.Count(i)
		got := strings.Count(ws, strconv.Itoa(i))
		if got != want {
			t.Errorf("got %d, want %d for %s", got, want, ws)
		}
	}
}

func TestGet(t *testing.T) {
	for i := 0; i < W; i++ {
		got := w.Get(i)
		want := Word(ws[W-i-1] - '0') // since ws is reversed.
		if got != want {
			t.Errorf("got %d, want %d for %s", got, want, ws)
		}
	}
}

func TestSet1(t *testing.T) {
	for i := 0; i < W; i++ {
		v := w.Set1(i)
		for j := 0; j < W; j++ {
			got := v.Get(j)
			want := w.Get(j)
			if i == j {
				want = 1
			}
			if got != want {
				t.Errorf("got %d, want %d", got, want)
			}
		}
	}
}

func TestSet0(t *testing.T) {
	for i := 0; i < W; i++ {
		v := w.Set0(i)
		for j := 0; j < W; j++ {
			got := v.Get(j)
			want := w.Get(j)
			if i == j {
				want = 0
			}
			if got != want {
				t.Errorf("got %d, want %d", got, want)
			}
		}
	}
}

func TestFlip(t *testing.T) {
	for i := 0; i < W; i++ {
		v := w.Flip(i)
		for j := 0; j < W; j++ {
			got := v.Get(j)
			want := w.Get(j)
			if i == j {
				if want == 1 {
					want = 0
				} else {
					want = 1
				}
			}
			if got != want {
				t.Errorf("got %d, want %d", got, want)
			}
		}
	}
}

func TestRank1(t *testing.T) {
	for i := 0; i < W; i++ {
		got := w.Rank1(i)
		want := strings.Count(ws[len(ws)-i-1:len(ws)], "1")
		if got != want {
			t.Errorf("got %d, want %d", got, want)
		}
	}
}

func BenchmarkCount1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Count1()
	}
}

func BenchmarkCount0(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Count0()
	}
}

func BenchmarkCount(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Count(1)
	}
}

func BenchmarkGet(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Get(i % W)
	}
}

func BenchmarkSet1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Set1(i % W)
	}
}

func BenchmarkSet0(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Set0(i % W)
	}
}

func BenchmarkFlip(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Flip(i % W)
	}
}

func BenchmarkRank1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		w := Word(i)
		_ = w.Rank1(i % W)
	}
}
<|endoftext|>


<commit_before>package daemon

import (
	"context"
	"sync"

	"github.com/spolu/warp"
	"github.com/spolu/warp/lib/logging"
	"github.com/spolu/warp/lib/plex"
)

// Warp represents a pty served from a remote host attached to a token.
type Warp struct {
	token string

	windowSize warp.Size

	host    *HostState
	clients map[string]*UserState

	data chan []byte

	mutex *sync.Mutex
}

// UserState represents the state of a user along with a list of all his
// sessions.
type UserState struct {
	token    string
	username string
	mode     warp.Mode
	sessions map[string]*Session
}

// User returns a warp.User from the current UserState.
func (u *UserState) User(
	ctx context.Context,
) warp.User {
	return warp.User{
		Token:    u.token,
		Username: u.username,
		Mode:     u.mode,
		Hosting:  false,
	}
}

// HostState represents the state of the host, in particular the host session,
// along with its UserState.
type HostState struct {
	UserState
	session *Session
}

// User returns a warp.User from the current HostState.
func (h *HostState) User(
	ctx context.Context,
) warp.User {
	return warp.User{
		Token:    h.UserState.token,
		Username: h.UserState.username,
		Mode:     h.UserState.mode,
		Hosting:  true,
	}
}

// State computes a warp.State from the current warp. It acquires the warp
// lock.
func (w *Warp) State(
	ctx context.Context,
) warp.State {
	w.mutex.Lock()
	defer w.mutex.Unlock()
	state := warp.State{
		Warp:       w.token,
		WindowSize: w.windowSize,
		Users:      map[string]warp.User{},
	}

	state.Users[w.host.session.session.User] = w.host.User(ctx)

	for token, user := range w.clients {
		state.Users[token] = user.User(ctx)
	}

	return state
}

// CientSessions return all connected sessions that are not the host session.
func (w *Warp) CientSessions(
	ctx context.Context,
) []*Session {
	sessions := []*Session{}
	w.mutex.Lock()
	for _, user := range w.clients {
		for _, c := range user.sessions {
			sessions = append(sessions, c)
		}
	}
	// The host user's shell client sessions, if any.
	for _, c := range w.host.UserState.sessions {
		sessions = append(sessions, c)
	}
	w.mutex.Unlock()
	return sessions
}

// updateClientSessions updates all shell clients with the current warp state.
func (w *Warp) updateClientSessions(
	ctx context.Context,
) {
	st := w.State(ctx)
	sessions := w.CientSessions(ctx)
	for _, ss := range sessions {
		logging.Logf(ctx,
			"Sending (client) state: session=%s cols=%d rows=%d",
			ss.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
		)

		ss.stateW.Encode(st)
	}
}

// updateHost updates the host with the current warp state.
func (w *Warp) updateHost(
	ctx context.Context,
) {
	if !w.host.session.tornDown {
		st := w.State(ctx)

		logging.Logf(ctx,
			"Sending (host) state: session=%s cols=%d rows=%d",
			w.host.session.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
		)

		w.host.session.stateW.Encode(st)
	}
}

// rcvShellClientData handles incoming client data and commits it to the data
// channel if the client is authorized to do so.
func (w *Warp) rcvShellClientData(
	ctx context.Context,
	ss *Session,
	data []byte,
) {
	var mode warp.Mode
	w.mutex.Lock()
	mode = w.clients[ss.session.User].mode
	w.mutex.Unlock()

	if mode&warp.ModeShellWrite != 0 {
		w.data <- data
	}
}

func (w *Warp) rcvHostData(
	ctx context.Context,
	ss *Session,
	data []byte,
) {
	sessions := w.CientSessions(ctx)
	for _, s := range sessions {
		logging.Logf(ctx,
			"Sending data to session: session=%s size=%d",
			s.ToString(), len(data),
		)
		_, err := s.dataC.Write(data)
		if err != nil {
			// If we fail to write to a session, send an internal error there
			// and tear down the session. This will not impact the warp.
			s.SendInternalError(ctx)
			s.TearDown()
		}
	}
}

// handleHost is responsible for handling the host session. It is in charge of:
// - receiving and validating host update.
// - multiplexing host data to shell clients.
// - sending received (and authorized) data to the host session.
func (w *Warp) handleHost(
	ctx context.Context,
	ss *Session,
) {
	// Add the host.
	w.mutex.Lock()
	w.host = &HostState{
		UserState: UserState{
			token:    ss.session.User,
			username: ss.username,
			mode:     warp.DefaultHostMode,
			// Initialize host sessions as empty as the current client is
			// the host session and does not act as "client". Subsequent
			// client session coming from the host would be added to this
			// list.
			sessions: map[string]*Session{},
		},
		session: ss,
	}
	w.mutex.Unlock()

	// run state updates
	go func() {
	STATELOOP:
		for {
			var st warp.HostUpdate
			if err := w.host.session.updateR.Decode(&st); err != nil {
				logging.Logf(ctx,
					"Error receiving host update: session=%s error=%v",
					ss.ToString(), err,
				)
				break STATELOOP
			}

			// Check that the warp token is the same.
			if st.Warp != w.token {
				logging.Logf(ctx,
					"Host update warp mismatch: session=%s "+
						"expected=% received=%s",
					ss.ToString, w.token, st.Warp,
				)
				break STATELOOP
			}

			// Check that the session is the same in particular the secret to
			// protect against spoofing attempts.
			if st.From.Token != ss.session.Token ||
				st.From.User != ss.session.User ||
				st.From.Secret != ss.session.Secret {
				logging.Logf(ctx,
					"Host credentials mismatch: session=%s",
					ss.ToString,
				)
				break STATELOOP
			}

			w.mutex.Lock()
			w.windowSize = st.WindowSize
			for user, mode := range st.Modes {
				if _, ok := w.clients[user]; ok {
					w.clients[user].mode = mode
				} else {
					logging.Logf(ctx,
						"Unknown user from host update: session=%s user=%s",
						ss.ToString(), user,
					)
					break STATELOOP
				}
			}
			w.mutex.Unlock()

			logging.Logf(ctx,
				"Received host update: session=%s cols=%d rows=%d",
				ss.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
			)

			w.updateClientSessions(ctx)
		}
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Receive host data.
	go func() {
		plex.Run(ctx, func(data []byte) {
			logging.Logf(ctx,
				"Received data from host: session=%s size=%d",
				ss.ToString(), len(data),
			)
			w.rcvHostData(ctx, ss, data)
		}, ss.dataC)
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Send data to host.
	go func() {
	DATALOOP:
		for {
			buf, ok := <-w.data
			logging.Logf(ctx,
				"Sending data to host: session=%s size=%d",
				ss.ToString(), len(buf),
			)
			_, err := ss.dataC.Write(buf)
			if err != nil {
				break DATALOOP
			}
			if !ok {
				break DATALOOP
			}
		}
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Update host and clients (should be no client).
	w.updateHost(ctx)
	w.updateClientSessions(ctx)

	logging.Logf(ctx,
		"Host session running: session=%s",
		ss.ToString(),
	)

	<-ss.ctx.Done()

	close(w.data)

	// Cancel all clients.
	logging.Logf(ctx,
		"Cancelling all clients: session=%s",
		ss.ToString(),
	)
	sessions := w.CientSessions(ctx)
	for _, s := range sessions {
		s.SendError(ctx,
			"host_disconnected",
			"The warp host disconnected.",
		)
		s.TearDown()
	}
}

// handleShellClient is responsible for handling the SsTpShellClient sessions.
// It is in charge of:
// - receiving shell client data and passing it to the host if authorized.
func (w *Warp) handleShellClient(
	ctx context.Context,
	ss *Session,
) {
	// Add the client.
	w.mutex.Lock()
	isHostSession := false
	if ss.session.User == w.host.UserState.token {
		isHostSession = true
		// If we have a session conflict, let's kill the old one.
		if s, ok := w.host.UserState.sessions[ss.session.Token]; ok {
			s.TearDown()
		}
		w.host.UserState.sessions[ss.session.Token] = ss
	} else {
		if _, ok := w.clients[ss.session.User]; !ok {
			w.clients[ss.session.User] = &UserState{
				token:    ss.session.User,
				username: ss.username,
				mode:     warp.DefaultUserMode,
				sessions: map[string]*Session{},
			}
		}
		// If we have a session conflict, let's kill the old one.
		if s, ok := w.clients[ss.session.User].sessions[ss.session.Token]; ok {
			s.TearDown()
		}
		w.clients[ss.session.User].sessions[ss.session.Token] = ss
	}
	w.mutex.Unlock()

	// Receive shell client data.
	go func() {
		plex.Run(ctx, func(data []byte) {
			logging.Logf(ctx,
				"Received data from client: session=%s size=%d",
				ss.ToString(), len(data),
			)
			w.rcvShellClientData(ctx, ss, data)
		}, ss.dataC)
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Update host and clients (including the new session).
	w.updateHost(ctx)
	w.updateClientSessions(ctx)

	logging.Logf(ctx,
		"Client session running: session=%s",
		ss.ToString(),
	)

	<-ss.ctx.Done()

	// Clean-up client.
	logging.Logf(ctx,
		"Cleaning-up client: session=%s",
		ss.ToString(),
	)

	w.mutex.Lock()
	if isHostSession {
		delete(w.host.sessions, ss.session.Token)
	} else {
		delete(w.clients[ss.session.User].sessions, ss.session.Token)
		if len(w.clients[ss.session.User].sessions) == 0 {
			delete(w.clients, ss.session.User)
		}
	}
	w.mutex.Unlock()

	// Update host and remaining clients
	w.updateHost(ctx)
	w.updateClientSessions(ctx)
}
<commit_msg>Removed data logging<commit_after>package daemon

import (
	"context"
	"sync"

	"github.com/spolu/warp"
	"github.com/spolu/warp/lib/logging"
	"github.com/spolu/warp/lib/plex"
)

// Warp represents a pty served from a remote host attached to a token.
type Warp struct {
	token string

	windowSize warp.Size

	host    *HostState
	clients map[string]*UserState

	data chan []byte

	mutex *sync.Mutex
}

// UserState represents the state of a user along with a list of all his
// sessions.
type UserState struct {
	token    string
	username string
	mode     warp.Mode
	sessions map[string]*Session
}

// User returns a warp.User from the current UserState.
func (u *UserState) User(
	ctx context.Context,
) warp.User {
	return warp.User{
		Token:    u.token,
		Username: u.username,
		Mode:     u.mode,
		Hosting:  false,
	}
}

// HostState represents the state of the host, in particular the host session,
// along with its UserState.
type HostState struct {
	UserState
	session *Session
}

// User returns a warp.User from the current HostState.
func (h *HostState) User(
	ctx context.Context,
) warp.User {
	return warp.User{
		Token:    h.UserState.token,
		Username: h.UserState.username,
		Mode:     h.UserState.mode,
		Hosting:  true,
	}
}

// State computes a warp.State from the current warp. It acquires the warp
// lock.
func (w *Warp) State(
	ctx context.Context,
) warp.State {
	w.mutex.Lock()
	defer w.mutex.Unlock()
	state := warp.State{
		Warp:       w.token,
		WindowSize: w.windowSize,
		Users:      map[string]warp.User{},
	}

	state.Users[w.host.session.session.User] = w.host.User(ctx)

	for token, user := range w.clients {
		state.Users[token] = user.User(ctx)
	}

	return state
}

// CientSessions return all connected sessions that are not the host session.
func (w *Warp) CientSessions(
	ctx context.Context,
) []*Session {
	sessions := []*Session{}
	w.mutex.Lock()
	for _, user := range w.clients {
		for _, c := range user.sessions {
			sessions = append(sessions, c)
		}
	}
	// The host user's shell client sessions, if any.
	for _, c := range w.host.UserState.sessions {
		sessions = append(sessions, c)
	}
	w.mutex.Unlock()
	return sessions
}

// updateClientSessions updates all shell clients with the current warp state.
func (w *Warp) updateClientSessions(
	ctx context.Context,
) {
	st := w.State(ctx)
	sessions := w.CientSessions(ctx)
	for _, ss := range sessions {
		logging.Logf(ctx,
			"Sending (client) state: session=%s cols=%d rows=%d",
			ss.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
		)

		ss.stateW.Encode(st)
	}
}

// updateHost updates the host with the current warp state.
func (w *Warp) updateHost(
	ctx context.Context,
) {
	if !w.host.session.tornDown {
		st := w.State(ctx)

		logging.Logf(ctx,
			"Sending (host) state: session=%s cols=%d rows=%d",
			w.host.session.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
		)

		w.host.session.stateW.Encode(st)
	}
}

// rcvShellClientData handles incoming client data and commits it to the data
// channel if the client is authorized to do so.
func (w *Warp) rcvShellClientData(
	ctx context.Context,
	ss *Session,
	data []byte,
) {
	var mode warp.Mode
	w.mutex.Lock()
	mode = w.clients[ss.session.User].mode
	w.mutex.Unlock()

	if mode&warp.ModeShellWrite != 0 {
		w.data <- data
	}
}

func (w *Warp) rcvHostData(
	ctx context.Context,
	ss *Session,
	data []byte,
) {
	sessions := w.CientSessions(ctx)
	for _, s := range sessions {
		// logging.Logf(ctx,
		// 	"Sending data to session: session=%s size=%d",
		// 	s.ToString(), len(data),
		// )
		_, err := s.dataC.Write(data)
		if err != nil {
			// If we fail to write to a session, send an internal error there
			// and tear down the session. This will not impact the warp.
			s.SendInternalError(ctx)
			s.TearDown()
		}
	}
}

// handleHost is responsible for handling the host session. It is in charge of:
// - receiving and validating host update.
// - multiplexing host data to shell clients.
// - sending received (and authorized) data to the host session.
func (w *Warp) handleHost(
	ctx context.Context,
	ss *Session,
) {
	// Add the host.
	w.mutex.Lock()
	w.host = &HostState{
		UserState: UserState{
			token:    ss.session.User,
			username: ss.username,
			mode:     warp.DefaultHostMode,
			// Initialize host sessions as empty as the current client is
			// the host session and does not act as "client". Subsequent
			// client session coming from the host would be added to this
			// list.
			sessions: map[string]*Session{},
		},
		session: ss,
	}
	w.mutex.Unlock()

	// run state updates
	go func() {
	STATELOOP:
		for {
			var st warp.HostUpdate
			if err := w.host.session.updateR.Decode(&st); err != nil {
				logging.Logf(ctx,
					"Error receiving host update: session=%s error=%v",
					ss.ToString(), err,
				)
				break STATELOOP
			}

			// Check that the warp token is the same.
			if st.Warp != w.token {
				logging.Logf(ctx,
					"Host update warp mismatch: session=%s "+
						"expected=% received=%s",
					ss.ToString, w.token, st.Warp,
				)
				break STATELOOP
			}

			// Check that the session is the same in particular the secret to
			// protect against spoofing attempts.
			if st.From.Token != ss.session.Token ||
				st.From.User != ss.session.User ||
				st.From.Secret != ss.session.Secret {
				logging.Logf(ctx,
					"Host credentials mismatch: session=%s",
					ss.ToString,
				)
				break STATELOOP
			}

			w.mutex.Lock()
			w.windowSize = st.WindowSize
			for user, mode := range st.Modes {
				if _, ok := w.clients[user]; ok {
					w.clients[user].mode = mode
				} else {
					logging.Logf(ctx,
						"Unknown user from host update: session=%s user=%s",
						ss.ToString(), user,
					)
					break STATELOOP
				}
			}
			w.mutex.Unlock()

			logging.Logf(ctx,
				"Received host update: session=%s cols=%d rows=%d",
				ss.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
			)

			w.updateClientSessions(ctx)
		}
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Receive host data.
	go func() {
		plex.Run(ctx, func(data []byte) {
			// logging.Logf(ctx,
			// 	"Received data from host: session=%s size=%d",
			// 	ss.ToString(), len(data),
			// )
			w.rcvHostData(ctx, ss, data)
		}, ss.dataC)
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Send data to host.
	go func() {
	DATALOOP:
		for {
			buf, ok := <-w.data
			// logging.Logf(ctx,
			// 	"Sending data to host: session=%s size=%d",
			// 	ss.ToString(), len(buf),
			// )
			_, err := ss.dataC.Write(buf)
			if err != nil {
				break DATALOOP
			}
			if !ok {
				break DATALOOP
			}
		}
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Update host and clients (should be no client).
	w.updateHost(ctx)
	w.updateClientSessions(ctx)

	logging.Logf(ctx,
		"Host session running: session=%s",
		ss.ToString(),
	)

	<-ss.ctx.Done()

	close(w.data)

	// Cancel all clients.
	logging.Logf(ctx,
		"Cancelling all clients: session=%s",
		ss.ToString(),
	)
	sessions := w.CientSessions(ctx)
	for _, s := range sessions {
		s.SendError(ctx,
			"host_disconnected",
			"The warp host disconnected.",
		)
		s.TearDown()
	}
}

// handleShellClient is responsible for handling the SsTpShellClient sessions.
// It is in charge of:
// - receiving shell client data and passing it to the host if authorized.
func (w *Warp) handleShellClient(
	ctx context.Context,
	ss *Session,
) {
	// Add the client.
	w.mutex.Lock()
	isHostSession := false
	if ss.session.User == w.host.UserState.token {
		isHostSession = true
		// If we have a session conflict, let's kill the old one.
		if s, ok := w.host.UserState.sessions[ss.session.Token]; ok {
			s.TearDown()
		}
		w.host.UserState.sessions[ss.session.Token] = ss
	} else {
		if _, ok := w.clients[ss.session.User]; !ok {
			w.clients[ss.session.User] = &UserState{
				token:    ss.session.User,
				username: ss.username,
				mode:     warp.DefaultUserMode,
				sessions: map[string]*Session{},
			}
		}
		// If we have a session conflict, let's kill the old one.
		if s, ok := w.clients[ss.session.User].sessions[ss.session.Token]; ok {
			s.TearDown()
		}
		w.clients[ss.session.User].sessions[ss.session.Token] = ss
	}
	w.mutex.Unlock()

	// Receive shell client data.
	go func() {
		plex.Run(ctx, func(data []byte) {
			// logging.Logf(ctx,
			// 	"Received data from client: session=%s size=%d",
			// 	ss.ToString(), len(data),
			// )
			w.rcvShellClientData(ctx, ss, data)
		}, ss.dataC)
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Update host and clients (including the new session).
	w.updateHost(ctx)
	w.updateClientSessions(ctx)

	logging.Logf(ctx,
		"Client session running: session=%s",
		ss.ToString(),
	)

	<-ss.ctx.Done()

	// Clean-up client.
	logging.Logf(ctx,
		"Cleaning-up client: session=%s",
		ss.ToString(),
	)

	w.mutex.Lock()
	if isHostSession {
		delete(w.host.sessions, ss.session.Token)
	} else {
		delete(w.clients[ss.session.User].sessions, ss.session.Token)
		if len(w.clients[ss.session.User].sessions) == 0 {
			delete(w.clients, ss.session.User)
		}
	}
	w.mutex.Unlock()

	// Update host and remaining clients
	w.updateHost(ctx)
	w.updateClientSessions(ctx)
}
<|endoftext|>


<commit_before>package trace

// Options contains all the options possible
type Options struct {
	Global     globalOpts      `title:"Global options" json:"global"`
	Resolution resolutionOpts  `title:"Image width and height in pixels" json:"resolution"`
	Background backgroundOpts  `title:"Determines the color when a ray doesn't hit anything" json:"background"`
	Camera     cameraOpts      `title:"Camera position and orientation" json:"camera"`
	AntiAlias  antiAliasOpts   `title:"Anti-aliasing settings" json:"anti_alias"`
	Debug      debugOpts       `title:"Debug options" json:"debug"`
	Lights     []*lightOpts    `title:"List of all lights in the scene" json:"lights"`
	Materials  []*materialOpts `title:"List of all the materials available to objects" json:"materials"`
	Objects    []*objectOpts   `title:"List of all objects in the scene" json:"objects"`
}

type globalOpts struct {
	FastRender       bool `title:"Disable/limit some settings in order to decrease rendering time" id:"fast-render" json:"fast_render"`
	MaxRecursion     int  `title:"Maximum reflective/refractive rays per pixel" min:"0" max:"99" class:"fast-render" json:"max_recursion"`
	SoftShadowDetail int  `title:"Soft shadow detail. 0 disables soft shadows" min:"0" max:"99" class:"fast-render" json:"soft_shadow_detail"`
}

type resolutionOpts struct {
	W int `title:"Width in pixels" min:"1" max:"16000" json:"w"`
	H int `title:"Height in pixels" min:"1" max:"16000" json:"h"`
}

type cameraOpts struct {
	Position vectorOpts `title:"Camera position" json:"position"`
	LookAt   vectorOpts `title:"The point that the camera is looking at" json:"look_at"`
	UpDir    vectorOpts `title:"The up direction" json:"up_dir"`
	Fov      float64    `title:"Field of view in degrees" min:"0.0" max:"360.0" json:"fov"`
	Dof      dofOpts    `title:"Depth of field" json:"dof"`
}

type dofOpts struct {
	Enabled           bool    `title:"Enable depth of field" class:"fast-render" json:"enabled"`
	FocalDistance     float64 `title:"Distance from the camera of the focal point" min:"0.01" max:"999.9" json:"focal_distance"`
	ApertureRadius    float64 `title:"Radius of the aperture" min:"0.00001" max:"999.9" json:"aperture_radius"`
	AdaptiveThreshold float64 `title:"Rays will continue to be created for each pixel until the contribution to the overall color is less than this" min:"0.00001" json:"adaptive_threshold"`
}

type backgroundOpts struct {
	Type  string    `title:"Type of background" choice:"Uniform,Skybox" json:"type"`
	Color colorOpts `title:"Color to use for a solid background (Uniform)" json:"color"`
	Image string    `title:"Path/URL of image to use (Skybox)" json:"image"`
}

type antiAliasOpts struct {
	MaxDivisions int     `title:"Maximum subdivisions of a pixel" min:"0" max:"16" class:"fast-render" json:"max_divisions"`
	Threshold    float64 `title:"Stop subdividing pixels when the difference is less than this" min:"0.0" max:"99" class:"fast-render" json:"threshold"`
}

type debugOpts struct {
	DebugRender bool      `title:"Produce a debug image" json:"debug_render"`
	Type        string    `title:"Type of debug image to produce" choice:"Ray Count,Anti Alias Subdivisions,Singe Pixel" json:"type"`
	SinglePixel bool      `title:"Render one pixel" json:"single_pixel"`
	Pixel       pixelOpts `title:"Pixel to render" json:"pixel"`
}

type pixelOpts struct {
	X int `title:"X value of the pixel" min:"0" valid:"validXPixel" json:"x"`
	Y int `title:"Y value of the pixel" min:"0" valid:"validYPixel" json:"y"`
}

type lightOpts struct {
	Type            string     `title:"Type of light" choice:"Directional,Point,Spot" json:"type"`
	Color           colorOpts  `title:"Color of the light" json:"color"`
	Position        vectorOpts `title:"Position of the light (Point and Spot)" json:"position"`
	Direction       vectorOpts `title:"Direction of the light (Direcitonal and Spot)" json:"direction"`
	IlluminationMap bool       `title:"Generate an illumination map (Point and Spot)" class:"fast-render" json:"illumination_map"`
	Coeff           struct {
		Constant  float64 `json:"constant"`
		Linear    float64 `json:"linear"`
		Quadratic float64 `json:"quadratic"`
	} `title:"Constant, lnear, and quadratic coefficients for point light fall off (Point)" json:"coeff"`
	Angle     float64 `title:"Spotlight angle" Type:"Spot" min:"0.0" max:"360.0" json:"angle"`
	DropOff   float64 `title:"Spotlight drop off angle" Type:"Spot" min:"0.0" max:"360.0" json:"drop_off"`
	FadeAngle float64 `title:"Stoplight fade angle" Type:"Spot" min:"0.0" max:"360.0" json:"fade_angle"`
}

type materialOpts struct {
	Name         string          `title:"Unique name for this material" json:"name"`
	Parent       string          `title:"Name of existing material to inherit attributes from" json:"parent"`
	Emissive     matPropertyOpts `title:"Emissive color of the material" json:"emissive"`
	Ambient      matPropertyOpts `title:"Abmient color of the material" json:"ambient"`
	Diffuse      matPropertyOpts `title:"Diffuse color of the material" json:"diffuse"`
	Specular     matPropertyOpts `title:"Specular color of the material" json:"specular"`
	Reflective   matPropertyOpts `title:"Reflectivness of the material" json:"reflective"`
	Transmissive matPropertyOpts `title:"Transmissivness of the material" json:"transmissive"`
	Smoothness   matPropertyOpts `title:"Smoothness of the material. Affects size of speclar spots" json:"smoothness"`
	Index        float64         `title:"Refractive index of the material" json:"index"`
	Normal       string          `title:"Path/URL of image to use as a normal map" json:"normal"`
	IsLiquid     bool            `title:"Overlapping behavior is only defined for non-liquids inside liquids" json:"is_liquid"`
	Brdf         string          `title:"Shadding algorithm" choice:"Lambert,Blinn-Phong" json:"brdf"`
}

type matPropertyOpts struct {
	Type    string    `title:"Type of material" choice:"Uniform,Texture" json:"type"`
	Color   colorOpts `title:"Uniform color" json:"color"`
	Texture string    `title:"Path to texture file" json:"texture"`
}

type objectOpts struct {
	Type         string        `title:"Type of shape. The 'Transform' type is an invisible object" choice:"Transform,Sphere,Box,Plane,Triangle,Trimesh,Cylinder,Cone" json:"type"`
	Transform    transformOpts `title:"Tranform of the object" json:"transform"`
	Material     string        `title:"Name of the material to use" json:"material"`
	TopRadius    float64       `title:"Top radius for cone objects" json:"top_radius"`
	BottomRadius float64       `title:"Bottom radius for cone objects" json:"bottom_radius"`
	Capped       bool          `title:"Whether to cap the ends of cones/cylinders" json:"capped"`
	Children     []objectOpts  `title:"Child objects that inherit this one's transform" json:"children"`
}

type transformOpts struct {
	Translate   vectorOpts `title:"Translation" json:"translate"`
	RotateAxis  vectorOpts `title:"Axis to rotate around" json:"rotate_axis"`
	RotateAngle float64    `title:"Angle to rotate around the axis in degrees" min:"-360.0" max:"360.0" json:"rotate_angle"`
	Scale       vectorOpts `title:"Scale" json:"scale"`
}

type colorOpts struct {
	R float64 `min:"0.0" max:"1.0" step:"0.1" default:"0.0" json:"r"`
	G float64 `min:"0.0" max:"1.0" step:"0.1" default:"0.0" json:"g"`
	B float64 `min:"0.0" max:"1.0" step:"0.1" default:"0.0" json:"b"`
}

type vectorOpts struct {
	X float64 `min:"-999" max:"999" default:"0.0" json:"x"`
	Y float64 `min:"-999" max:"999" default:"0.0" json:"y"`
	Z float64 `min:"-999" max:"999" default:"0.0" json:"z"`
}

// NewOptions returns an intialized Options.
func NewOptions() *Options {
	opts := &Options{
		Global: globalOpts{
			FastRender:       true,
			MaxRecursion:     2,
			SoftShadowDetail: 0,
		},
		Resolution: resolutionOpts{600, 600},
		Camera: cameraOpts{
			Position: vectorOpts{0, 5, 10},
			LookAt:   vectorOpts{0, 0, 0},
			UpDir:    vectorOpts{0, 1, 0},
			Fov:      58,
			Dof: dofOpts{
				Enabled:           false,
				FocalDistance:     5.0,
				ApertureRadius:    0.001,
				AdaptiveThreshold: 0.1,
			},
		},
		Background: backgroundOpts{
			Type:  "Uniform",
			Color: colorOpts{0, 0, 0},
			Image: "",
		},
		AntiAlias: antiAliasOpts{
			MaxDivisions: 0,
			Threshold:    0,
		},
		Debug: debugOpts{
			DebugRender: false,
		},
		Lights: []*lightOpts{
			{
				Type:      "Directional",
				Color:     colorOpts{1, 1, 1},
				Direction: vectorOpts{-1, -1, -1},
			},
		},
		Materials: []*materialOpts{
			{
				Name:         "white",
				Emissive:     matPropertyOpts{Type: "Uniform", Color: colorOpts{0, 0, 0}},
				Ambient:      matPropertyOpts{Type: "Uniform", Color: colorOpts{0.3, 0.3, 0.3}},
				Diffuse:      matPropertyOpts{Type: "Uniform", Color: colorOpts{1, 1, 1}},
				Specular:     matPropertyOpts{Type: "Uniform", Color: colorOpts{1, 1, 1}},
				Reflective:   matPropertyOpts{Type: "Uniform", Color: colorOpts{0, 0, 0}},
				Transmissive: matPropertyOpts{Type: "Uniform", Color: colorOpts{0, 0, 0}},
				Smoothness:   matPropertyOpts{Type: "Uniform", Color: colorOpts{0.9, 0.9, 0.9}},
				Index:        1,
				Normal:       "",
				IsLiquid:     false,
				Brdf:         "Blinn-Phong",
			},
		},
		Objects: []*objectOpts{
			{
				Type: "Plane",
				Transform: transformOpts{
					Translate:   vectorOpts{0, 0, 0},
					RotateAxis:  vectorOpts{1, 0, 0},
					RotateAngle: -90,
					Scale:       vectorOpts{10, 10, 0},
				},
				Material: "white",
			},
			{
				Type: "Box",
				Transform: transformOpts{
					Translate:   vectorOpts{0, 1, 0},
					RotateAxis:  vectorOpts{0, 0, 0},
					RotateAngle: 0,
					Scale:       vectorOpts{2, 2, 2},
				},
				Material: "white",
			},
		},
	}
	return opts
}
<commit_msg>Add more details to titles.<commit_after>package trace

// Options contains all the options possible
type Options struct {
	Global     globalOpts      `title:"Global options" json:"global"`
	Resolution resolutionOpts  `title:"Image width and height in pixels. Larger resolution will increase rendering time." json:"resolution"`
	Background backgroundOpts  `title:"Determines the color when a ray doesn't hit anything" json:"background"`
	Camera     cameraOpts      `title:"Camera position and orientation" json:"camera"`
	AntiAlias  antiAliasOpts   `title:"Anti-aliasing settings" json:"anti_alias"`
	Debug      debugOpts       `title:"Debug options" json:"debug"`
	Lights     []*lightOpts    `title:"List of all lights in the scene" json:"lights"`
	Materials  []*materialOpts `title:"List of all the materials available to objects" json:"materials"`
	Objects    []*objectOpts   `title:"List of all objects in the scene" json:"objects"`
}

type globalOpts struct {
	FastRender       bool `title:"Disable/limit some settings in order to decrease rendering time" id:"fast-render" json:"fast_render"`
	MaxRecursion     int  `title:"Maximum reflective/refractive rays per pixel. Larger values increase quality and rendering time." min:"0" max:"99" class:"fast-render" json:"max_recursion"`
	SoftShadowDetail int  `title:"Soft shadow detail. 0 disables soft shadows. Larger values increase quality and rendering time. " min:"0" max:"99" class:"fast-render" json:"soft_shadow_detail"`
}

type resolutionOpts struct {
	W int `title:"Width in pixels. Larger values increase rendering time." min:"1" max:"16000" json:"w"`
	H int `title:"Height in pixels. Larger values increase rendering time." min:"1" max:"16000" json:"h"`
}

type cameraOpts struct {
	Position vectorOpts `title:"Camera position" json:"position"`
	LookAt   vectorOpts `title:"The point that the camera is looking at" json:"look_at"`
	UpDir    vectorOpts `title:"The up direction" json:"up_dir"`
	Fov      float64    `title:"Field of view in degrees" min:"0.0" max:"360.0" json:"fov"`
	Dof      dofOpts    `title:"Depth of field" json:"dof"`
}

type dofOpts struct {
	Enabled           bool    `title:"Enable depth of field. Enabling DOF will increase rendering time." class:"fast-render" json:"enabled"`
	FocalDistance     float64 `title:"Distance from the camera of the focal point" min:"0.01" max:"999.9" json:"focal_distance"`
	ApertureRadius    float64 `title:"Radius of the aperture" min:"0.00001" max:"999.9" json:"aperture_radius"`
	AdaptiveThreshold float64 `title:"Rays will continue to be created for each pixel until the contribution to the overall color is less than this. Smaller values increase quality and rendering time." min:"0.00001" json:"adaptive_threshold"`
}

type backgroundOpts struct {
	Type  string    `title:"Type of background" choice:"Uniform,Skybox" json:"type"`
	Color colorOpts `title:"Color to use for a solid background (Uniform)" json:"color"`
	Image string    `title:"Path/URL of image to use (Skybox)" json:"image"`
}

type antiAliasOpts struct {
	MaxDivisions int     `title:"Maximum subdivisions of a pixel. Larger values increase quality and rendering time." min:"0" max:"16" class:"fast-render" json:"max_divisions"`
	Threshold    float64 `title:"Stop subdividing pixels when the difference is less than this. Smaller values increase quality and rendering time." min:"0.0" max:"99" class:"fast-render" json:"threshold"`
}

type debugOpts struct {
	DebugRender bool      `title:"Produce a debug image" json:"debug_render"`
	Type        string    `title:"Type of debug image to produce" choice:"Ray Count,Anti Alias Subdivisions,Singe Pixel" json:"type"`
	SinglePixel bool      `title:"Render one pixel" json:"single_pixel"`
	Pixel       pixelOpts `title:"Pixel to render" json:"pixel"`
}

type pixelOpts struct {
	X int `title:"X value of the pixel" min:"0" valid:"validXPixel" json:"x"`
	Y int `title:"Y value of the pixel" min:"0" valid:"validYPixel" json:"y"`
}

type lightOpts struct {
	Type            string     `title:"Type of light" choice:"Directional,Point,Spot" json:"type"`
	Color           colorOpts  `title:"Color of the light" json:"color"`
	Position        vectorOpts `title:"Position of the light (Point and Spot)" json:"position"`
	Direction       vectorOpts `title:"Direction of the light (Direcitonal and Spot)" json:"direction"`
	IlluminationMap bool       `title:"Generate an illumination map (Point and Spot). Increases rendering time." class:"fast-render" json:"illumination_map"`
	Coeff           struct {
		Constant  float64 `json:"constant"`
		Linear    float64 `json:"linear"`
		Quadratic float64 `json:"quadratic"`
	} `title:"Constant, lnear, and quadratic coefficients for point light fall off (Point)" json:"coeff"`
	Angle     float64 `title:"Spotlight angle" Type:"Spot" min:"0.0" max:"360.0" json:"angle"`
	DropOff   float64 `title:"Spotlight drop off angle" Type:"Spot" min:"0.0" max:"360.0" json:"drop_off"`
	FadeAngle float64 `title:"Stoplight fade angle" Type:"Spot" min:"0.0" max:"360.0" json:"fade_angle"`
}

type materialOpts struct {
	Name         string          `title:"Unique name for this material" json:"name"`
	Parent       string          `title:"Name of existing material to inherit attributes from" json:"parent"`
	Emissive     matPropertyOpts `title:"Emissive color of the material" json:"emissive"`
	Ambient      matPropertyOpts `title:"Abmient color of the material" json:"ambient"`
	Diffuse      matPropertyOpts `title:"Diffuse color of the material" json:"diffuse"`
	Specular     matPropertyOpts `title:"Specular color of the material" json:"specular"`
	Reflective   matPropertyOpts `title:"Reflectivness of the material" json:"reflective"`
	Transmissive matPropertyOpts `title:"Transmissivness of the material" json:"transmissive"`
	Smoothness   matPropertyOpts `title:"Smoothness of the material. Affects size of speclar spots" json:"smoothness"`
	Index        float64         `title:"Refractive index of the material" json:"index"`
	Normal       string          `title:"Path/URL of image to use as a normal map" json:"normal"`
	IsLiquid     bool            `title:"Overlapping behavior is only defined for non-liquids inside liquids" json:"is_liquid"`
	Brdf         string          `title:"Shadding algorithm" choice:"Lambert,Blinn-Phong" json:"brdf"`
}

type matPropertyOpts struct {
	Type    string    `title:"Type of material" choice:"Uniform,Texture" json:"type"`
	Color   colorOpts `title:"Uniform color" json:"color"`
	Texture string    `title:"Path to texture file" json:"texture"`
}

type objectOpts struct {
	Type         string        `title:"Type of shape. The 'Transform' type is an invisible object" choice:"Transform,Sphere,Box,Plane,Triangle,Trimesh,Cylinder,Cone" json:"type"`
	Transform    transformOpts `title:"Tranform of the object" json:"transform"`
	Material     string        `title:"Name of the material to use" json:"material"`
	TopRadius    float64       `title:"Top radius for cone objects" json:"top_radius"`
	BottomRadius float64       `title:"Bottom radius for cone objects" json:"bottom_radius"`
	Capped       bool          `title:"Whether to cap the ends of cones/cylinders" json:"capped"`
	Children     []objectOpts  `title:"Child objects that inherit this one's transform" json:"children"`
}

type transformOpts struct {
	Translate   vectorOpts `title:"Translation" json:"translate"`
	RotateAxis  vectorOpts `title:"Axis to rotate around" json:"rotate_axis"`
	RotateAngle float64    `title:"Angle to rotate around the axis in degrees" min:"-360.0" max:"360.0" json:"rotate_angle"`
	Scale       vectorOpts `title:"Scale" json:"scale"`
}

type colorOpts struct {
	R float64 `min:"0.0" max:"1.0" step:"0.1" default:"0.0" json:"r"`
	G float64 `min:"0.0" max:"1.0" step:"0.1" default:"0.0" json:"g"`
	B float64 `min:"0.0" max:"1.0" step:"0.1" default:"0.0" json:"b"`
}

type vectorOpts struct {
	X float64 `min:"-999" max:"999" default:"0.0" json:"x"`
	Y float64 `min:"-999" max:"999" default:"0.0" json:"y"`
	Z float64 `min:"-999" max:"999" default:"0.0" json:"z"`
}

// NewOptions returns an intialized Options.
func NewOptions() *Options {
	opts := &Options{
		Global: globalOpts{
			FastRender:       true,
			MaxRecursion:     2,
			SoftShadowDetail: 0,
		},
		Resolution: resolutionOpts{600, 600},
		Camera: cameraOpts{
			Position: vectorOpts{0, 5, 10},
			LookAt:   vectorOpts{0, 0, 0},
			UpDir:    vectorOpts{0, 1, 0},
			Fov:      58,
			Dof: dofOpts{
				Enabled:           false,
				FocalDistance:     5.0,
				ApertureRadius:    0.001,
				AdaptiveThreshold: 0.1,
			},
		},
		Background: backgroundOpts{
			Type:  "Uniform",
			Color: colorOpts{0, 0, 0},
			Image: "",
		},
		AntiAlias: antiAliasOpts{
			MaxDivisions: 0,
			Threshold:    0,
		},
		Debug: debugOpts{
			DebugRender: false,
		},
		Lights: []*lightOpts{
			{
				Type:      "Directional",
				Color:     colorOpts{1, 1, 1},
				Direction: vectorOpts{-1, -1, -1},
			},
		},
		Materials: []*materialOpts{
			{
				Name:         "white",
				Emissive:     matPropertyOpts{Type: "Uniform", Color: colorOpts{0, 0, 0}},
				Ambient:      matPropertyOpts{Type: "Uniform", Color: colorOpts{0.3, 0.3, 0.3}},
				Diffuse:      matPropertyOpts{Type: "Uniform", Color: colorOpts{1, 1, 1}},
				Specular:     matPropertyOpts{Type: "Uniform", Color: colorOpts{1, 1, 1}},
				Reflective:   matPropertyOpts{Type: "Uniform", Color: colorOpts{0, 0, 0}},
				Transmissive: matPropertyOpts{Type: "Uniform", Color: colorOpts{0, 0, 0}},
				Smoothness:   matPropertyOpts{Type: "Uniform", Color: colorOpts{0.9, 0.9, 0.9}},
				Index:        1,
				Normal:       "",
				IsLiquid:     false,
				Brdf:         "Blinn-Phong",
			},
		},
		Objects: []*objectOpts{
			{
				Type: "Plane",
				Transform: transformOpts{
					Translate:   vectorOpts{0, 0, 0},
					RotateAxis:  vectorOpts{1, 0, 0},
					RotateAngle: -90,
					Scale:       vectorOpts{10, 10, 0},
				},
				Material: "white",
			},
			{
				Type: "Box",
				Transform: transformOpts{
					Translate:   vectorOpts{0, 1, 0},
					RotateAxis:  vectorOpts{0, 0, 0},
					RotateAngle: 0,
					Scale:       vectorOpts{2, 2, 2},
				},
				Material: "white",
			},
		},
	}
	return opts
}
<|endoftext|>


<commit_before>package odp

import (
	"fmt"
	"net"
	"syscall"

	"github.com/weaveworks/go-odp/odp"
)

// ODP admin functionality

func CreateDatapath(dpname string) (supported bool, err error) {
	dpif, err := odp.NewDpif()
	if err != nil {
		if odp.IsKernelLacksODPError(err) {
			return false, nil
		}

		return true, err
	}

	defer dpif.Close()

	dp, err := dpif.CreateDatapath(dpname)
	if err != nil && !odp.IsDatapathNameAlreadyExistsError(err) {
		return true, err
	}

	// Pick an ephemeral port number to use in probing for vxlan
	// support.
	udpconn, err := net.ListenUDP("udp4", nil)
	if err != nil {
		return true, err
	}

	// we leave the UDP socket open, so creating a vxlan vport on
	// the same port number should fail.  But that's fine: It's
	// still sufficient to probe for support.
	portno := uint16(udpconn.LocalAddr().(*net.UDPAddr).Port)
	vpid, err := dp.CreateVport(odp.NewVxlanVportSpec(
		fmt.Sprintf("vxlan-%d", portno), portno))
	if nlerr, ok := err.(odp.NetlinkError); ok {
		if syscall.Errno(nlerr) == syscall.EAFNOSUPPORT {
			dp.Delete()
			return false, fmt.Errorf("kernel does not have Open vSwitch VXLAN support")
		}
	}

	if err == nil {
		dp.DeleteVport(vpid)
	}

	udpconn.Close()
	return true, nil
}

func DeleteDatapath(dpname string) error {
	dpif, err := odp.NewDpif()
	if err != nil {
		return err
	}

	defer dpif.Close()

	dp, err := dpif.LookupDatapath(dpname)
	if err != nil {
		if odp.IsNoSuchDatapathError(err) {
			return nil
		}

		return err
	}

	return dp.Delete()
}

func AddDatapathInterface(dpname string, ifname string) error {
	dpif, err := odp.NewDpif()
	if err != nil {
		return err
	}

	defer dpif.Close()

	dp, err := dpif.LookupDatapath(dpname)
	if err != nil {
		return err
	}

	_, err = dp.CreateVport(odp.NewNetdevVportSpec(ifname))
	return err
}
<commit_msg>cosmetic(ish)<commit_after>package odp

import (
	"fmt"
	"net"
	"syscall"

	"github.com/weaveworks/go-odp/odp"
)

// ODP admin functionality

func CreateDatapath(dpname string) (supported bool, err error) {
	dpif, err := odp.NewDpif()
	if err != nil {
		if odp.IsKernelLacksODPError(err) {
			return false, nil
		}
		return true, err
	}
	defer dpif.Close()

	dp, err := dpif.CreateDatapath(dpname)
	if err != nil && !odp.IsDatapathNameAlreadyExistsError(err) {
		return true, err
	}

	// Pick an ephemeral port number to use in probing for vxlan
	// support.
	udpconn, err := net.ListenUDP("udp4", nil)
	if err != nil {
		return true, err
	}
	defer udpconn.Close()

	// we leave the UDP socket open, so creating a vxlan vport on
	// the same port number should fail.  But that's fine: It's
	// still sufficient to probe for support.
	portno := uint16(udpconn.LocalAddr().(*net.UDPAddr).Port)
	vpid, err := dp.CreateVport(odp.NewVxlanVportSpec(fmt.Sprintf("vxlan-%d", portno), portno))
	if nlerr, ok := err.(odp.NetlinkError); ok {
		if syscall.Errno(nlerr) == syscall.EAFNOSUPPORT {
			dp.Delete()
			return false, fmt.Errorf("kernel does not have Open vSwitch VXLAN support")
		}
	}
	if err == nil {
		dp.DeleteVport(vpid)
	}

	return true, nil
}

func DeleteDatapath(dpname string) error {
	dpif, err := odp.NewDpif()
	if err != nil {
		return err
	}
	defer dpif.Close()

	dp, err := dpif.LookupDatapath(dpname)
	if err != nil {
		if odp.IsNoSuchDatapathError(err) {
			return nil
		}
		return err
	}

	return dp.Delete()
}

func AddDatapathInterface(dpname string, ifname string) error {
	dpif, err := odp.NewDpif()
	if err != nil {
		return err
	}
	defer dpif.Close()

	dp, err := dpif.LookupDatapath(dpname)
	if err != nil {
		return err
	}

	_, err = dp.CreateVport(odp.NewNetdevVportSpec(ifname))
	return err
}
<|endoftext|>


<commit_before>// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

// Package gredis provides convenient client for redis server.
//
// Redis Client.
//
// Redis Commands Official: https://redis.io/commands
//
// Redis Chinese Documentation: http://redisdoc.com/
package gredis

import (
	"fmt"
	"github.com/gogf/gf/util/gconv"
	"time"

	"github.com/gogf/gf/container/gmap"
	"github.com/gogf/gf/container/gvar"
	"github.com/gomodule/redigo/redis"
)

// Redis client.
type Redis struct {
	pool   *redis.Pool // Underlying connection pool.
	group  string      // Configuration group.
	config Config      // Configuration.
}

// Redis connection.
type Conn struct {
	redis.Conn
}

// Redis configuration.
type Config struct {
	Host            string
	Port            int
	Db              int
	Pass            string        // Password for AUTH.
	MaxIdle         int           // Maximum number of connections allowed to be idle (default is 10)
	MaxActive       int           // Maximum number of connections limit (default is 0 means no limit).
	IdleTimeout     time.Duration // Maximum idle time for connection (default is 10 seconds, not allowed to be set to 0)
	MaxConnLifetime time.Duration // Maximum lifetime of the connection (default is 30 seconds, not allowed to be set to 0)
	ConnectTimeout  time.Duration // Dial connection timeout.
	TLS				bool		  //support tls
	TLSSkipVerify	bool		  //tls skip verify
}

// Pool statistics.
type PoolStats struct {
	redis.PoolStats
}

const (
	gDEFAULT_POOL_IDLE_TIMEOUT  = 10 * time.Second
	gDEFAULT_POOL_CONN_TIMEOUT  = 10 * time.Second
	gDEFAULT_POOL_MAX_IDLE      = 10
	gDEFAULT_POOL_MAX_ACTIVE    = 100
	gDEFAULT_POOL_MAX_LIFE_TIME = 30 * time.Second
)

var (
	// Pool map.
	pools = gmap.NewStrAnyMap(true)
)

// New creates a redis client object with given configuration.
// Redis client maintains a connection pool automatically.
func New(config Config) *Redis {
	// The MaxIdle is the most important attribute of the connection pool.
	// Only if this attribute is set, the created connections from client
	// can not exceed the limit of the server.
	if config.MaxIdle == 0 {
		config.MaxIdle = gDEFAULT_POOL_MAX_IDLE
	}
	// This value SHOULD NOT exceed the connection limit of redis server.
	if config.MaxActive == 0 {
		config.MaxActive = gDEFAULT_POOL_MAX_ACTIVE
	}
	if config.IdleTimeout == 0 {
		config.IdleTimeout = gDEFAULT_POOL_IDLE_TIMEOUT
	}
	if config.ConnectTimeout == 0 {
		config.ConnectTimeout = gDEFAULT_POOL_CONN_TIMEOUT
	}
	if config.MaxConnLifetime == 0 {
		config.MaxConnLifetime = gDEFAULT_POOL_MAX_LIFE_TIME
	}
	return &Redis{
		config: config,
		pool: pools.GetOrSetFuncLock(fmt.Sprintf("%v", config), func() interface{} {
			return &redis.Pool{
				Wait:            true,
				IdleTimeout:     config.IdleTimeout,
				MaxActive:       config.MaxActive,
				MaxIdle:         config.MaxIdle,
				MaxConnLifetime: config.MaxConnLifetime,
				Dial: func() (redis.Conn, error) {
					c, err := redis.Dial(
						"tcp",
						fmt.Sprintf("%s:%d", config.Host, config.Port),
						redis.DialConnectTimeout(config.ConnectTimeout),
						redis.DialUseTLS(config.TLS),
						redis.DialTLSSkipVerify(config.TLSSkipVerify),
					)
					if err != nil {
						return nil, err
					}
					// AUTH
					if len(config.Pass) > 0 {
						if _, err := c.Do("AUTH", config.Pass); err != nil {
							return nil, err
						}
					}
					// DB
					if _, err := c.Do("SELECT", config.Db); err != nil {
						return nil, err
					}
					return c, nil
				},
				// After the conn is taken from the connection pool, to test if the connection is available,
				// If error is returned then it closes the connection object and recreate a new connection.
				TestOnBorrow: func(c redis.Conn, t time.Time) error {
					_, err := c.Do("PING")
					return err
				},
			}
		}).(*redis.Pool),
	}
}

// NewFromStr creates a redis client object with given configuration string.
// Redis client maintains a connection pool automatically.
// The parameter <str> like:
// 127.0.0.1:6379,0
// 127.0.0.1:6379,0,password
func NewFromStr(str string) (*Redis, error) {
	config, err := ConfigFromStr(str)
	if err != nil {
		return nil, err
	}
	return New(config), nil
}

// Close closes the redis connection pool,
// it will release all connections reserved by this pool.
// It is not necessary to call Close manually.
func (r *Redis) Close() error {
	if r.group != "" {
		// If it is an instance object,
		// it needs to remove it from the instance Map.
		instances.Remove(r.group)
	}
	pools.Remove(fmt.Sprintf("%v", r.config))
	return r.pool.Close()
}

// Conn returns a raw underlying connection object,
// which expose more methods to communicate with server.
// **You should call Close function manually if you do not use this connection any further.**
func (r *Redis) Conn() *Conn {
	return &Conn{r.pool.Get()}
}

// Alias of Conn, see Conn.
func (r *Redis) GetConn() *Conn {
	return r.Conn()
}

// SetMaxIdle sets the maximum number of idle connections in the pool.
func (r *Redis) SetMaxIdle(value int) {
	r.pool.MaxIdle = value
}

// SetMaxActive sets the maximum number of connections allocated by the pool at a given time.
// When zero, there is no limit on the number of connections in the pool.
//
// Note that if the pool is at the MaxActive limit, then all the operations will wait for
// a connection to be returned to the pool before returning.
func (r *Redis) SetMaxActive(value int) {
	r.pool.MaxActive = value
}

// SetIdleTimeout sets the IdleTimeout attribute of the connection pool.
// It closes connections after remaining idle for this duration. If the value
// is zero, then idle connections are not closed. Applications should set
// the timeout to a value less than the server's timeout.
func (r *Redis) SetIdleTimeout(value time.Duration) {
	r.pool.IdleTimeout = value
}

// SetMaxConnLifetime sets the MaxConnLifetime attribute of the connection pool.
// It closes connections older than this duration. If the value is zero, then
// the pool does not close connections based on age.
func (r *Redis) SetMaxConnLifetime(value time.Duration) {
	r.pool.MaxConnLifetime = value
}

// Stats returns pool's statistics.
func (r *Redis) Stats() *PoolStats {
	return &PoolStats{r.pool.Stats()}
}

// Do sends a command to the server and returns the received reply.
// Do automatically get a connection from pool, and close it when the reply received.
// It does not really "close" the connection, but drops it back to the connection pool.
func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
	conn := &Conn{r.pool.Get()}
	defer conn.Close()
	return conn.Do(command, args...)
}

// DoVar returns value from Do as gvar.Var.
func (r *Redis) DoVar(command string, args ...interface{}) (*gvar.Var, error) {
	v, err := r.Do(command, args...)
	if result, ok := v.([]byte); ok {
		return gvar.New(gconv.UnsafeBytesToStr(result)), err
	}
	// It treats all returned slice as string slice.
	if result, ok := v.([]interface{}); ok {
		return gvar.New(gconv.Strings(result)), err
	}
	return gvar.New(v), err
}
<commit_msg>update comment<commit_after>// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

// Package gredis provides convenient client for redis server.
//
// Redis Client.
//
// Redis Commands Official: https://redis.io/commands
//
// Redis Chinese Documentation: http://redisdoc.com/
package gredis

import (
	"fmt"
	"github.com/gogf/gf/util/gconv"
	"time"

	"github.com/gogf/gf/container/gmap"
	"github.com/gogf/gf/container/gvar"
	"github.com/gomodule/redigo/redis"
)

// Redis client.
type Redis struct {
	pool   *redis.Pool // Underlying connection pool.
	group  string      // Configuration group.
	config Config      // Configuration.
}

// Redis connection.
type Conn struct {
	redis.Conn
}

// Redis configuration.
type Config struct {
	Host            string
	Port            int
	Db              int
	Pass            string        // Password for AUTH.
	MaxIdle         int           // Maximum number of connections allowed to be idle (default is 10)
	MaxActive       int           // Maximum number of connections limit (default is 0 means no limit).
	IdleTimeout     time.Duration // Maximum idle time for connection (default is 10 seconds, not allowed to be set to 0)
	MaxConnLifetime time.Duration // Maximum lifetime of the connection (default is 30 seconds, not allowed to be set to 0)
	ConnectTimeout  time.Duration // Dial connection timeout.
	TLS				bool		  // Specifies the config to use when a TLS connection is dialed.
	TLSSkipVerify	bool		  // Disables server name verification when connecting over TLS
}

// Pool statistics.
type PoolStats struct {
	redis.PoolStats
}

const (
	gDEFAULT_POOL_IDLE_TIMEOUT  = 10 * time.Second
	gDEFAULT_POOL_CONN_TIMEOUT  = 10 * time.Second
	gDEFAULT_POOL_MAX_IDLE      = 10
	gDEFAULT_POOL_MAX_ACTIVE    = 100
	gDEFAULT_POOL_MAX_LIFE_TIME = 30 * time.Second
)

var (
	// Pool map.
	pools = gmap.NewStrAnyMap(true)
)

// New creates a redis client object with given configuration.
// Redis client maintains a connection pool automatically.
func New(config Config) *Redis {
	// The MaxIdle is the most important attribute of the connection pool.
	// Only if this attribute is set, the created connections from client
	// can not exceed the limit of the server.
	if config.MaxIdle == 0 {
		config.MaxIdle = gDEFAULT_POOL_MAX_IDLE
	}
	// This value SHOULD NOT exceed the connection limit of redis server.
	if config.MaxActive == 0 {
		config.MaxActive = gDEFAULT_POOL_MAX_ACTIVE
	}
	if config.IdleTimeout == 0 {
		config.IdleTimeout = gDEFAULT_POOL_IDLE_TIMEOUT
	}
	if config.ConnectTimeout == 0 {
		config.ConnectTimeout = gDEFAULT_POOL_CONN_TIMEOUT
	}
	if config.MaxConnLifetime == 0 {
		config.MaxConnLifetime = gDEFAULT_POOL_MAX_LIFE_TIME
	}
	return &Redis{
		config: config,
		pool: pools.GetOrSetFuncLock(fmt.Sprintf("%v", config), func() interface{} {
			return &redis.Pool{
				Wait:            true,
				IdleTimeout:     config.IdleTimeout,
				MaxActive:       config.MaxActive,
				MaxIdle:         config.MaxIdle,
				MaxConnLifetime: config.MaxConnLifetime,
				Dial: func() (redis.Conn, error) {
					c, err := redis.Dial(
						"tcp",
						fmt.Sprintf("%s:%d", config.Host, config.Port),
						redis.DialConnectTimeout(config.ConnectTimeout),
						redis.DialUseTLS(config.TLS),
						redis.DialTLSSkipVerify(config.TLSSkipVerify),
					)
					if err != nil {
						return nil, err
					}
					// AUTH
					if len(config.Pass) > 0 {
						if _, err := c.Do("AUTH", config.Pass); err != nil {
							return nil, err
						}
					}
					// DB
					if _, err := c.Do("SELECT", config.Db); err != nil {
						return nil, err
					}
					return c, nil
				},
				// After the conn is taken from the connection pool, to test if the connection is available,
				// If error is returned then it closes the connection object and recreate a new connection.
				TestOnBorrow: func(c redis.Conn, t time.Time) error {
					_, err := c.Do("PING")
					return err
				},
			}
		}).(*redis.Pool),
	}
}

// NewFromStr creates a redis client object with given configuration string.
// Redis client maintains a connection pool automatically.
// The parameter <str> like:
// 127.0.0.1:6379,0
// 127.0.0.1:6379,0,password
func NewFromStr(str string) (*Redis, error) {
	config, err := ConfigFromStr(str)
	if err != nil {
		return nil, err
	}
	return New(config), nil
}

// Close closes the redis connection pool,
// it will release all connections reserved by this pool.
// It is not necessary to call Close manually.
func (r *Redis) Close() error {
	if r.group != "" {
		// If it is an instance object,
		// it needs to remove it from the instance Map.
		instances.Remove(r.group)
	}
	pools.Remove(fmt.Sprintf("%v", r.config))
	return r.pool.Close()
}

// Conn returns a raw underlying connection object,
// which expose more methods to communicate with server.
// **You should call Close function manually if you do not use this connection any further.**
func (r *Redis) Conn() *Conn {
	return &Conn{r.pool.Get()}
}

// Alias of Conn, see Conn.
func (r *Redis) GetConn() *Conn {
	return r.Conn()
}

// SetMaxIdle sets the maximum number of idle connections in the pool.
func (r *Redis) SetMaxIdle(value int) {
	r.pool.MaxIdle = value
}

// SetMaxActive sets the maximum number of connections allocated by the pool at a given time.
// When zero, there is no limit on the number of connections in the pool.
//
// Note that if the pool is at the MaxActive limit, then all the operations will wait for
// a connection to be returned to the pool before returning.
func (r *Redis) SetMaxActive(value int) {
	r.pool.MaxActive = value
}

// SetIdleTimeout sets the IdleTimeout attribute of the connection pool.
// It closes connections after remaining idle for this duration. If the value
// is zero, then idle connections are not closed. Applications should set
// the timeout to a value less than the server's timeout.
func (r *Redis) SetIdleTimeout(value time.Duration) {
	r.pool.IdleTimeout = value
}

// SetMaxConnLifetime sets the MaxConnLifetime attribute of the connection pool.
// It closes connections older than this duration. If the value is zero, then
// the pool does not close connections based on age.
func (r *Redis) SetMaxConnLifetime(value time.Duration) {
	r.pool.MaxConnLifetime = value
}

// Stats returns pool's statistics.
func (r *Redis) Stats() *PoolStats {
	return &PoolStats{r.pool.Stats()}
}

// Do sends a command to the server and returns the received reply.
// Do automatically get a connection from pool, and close it when the reply received.
// It does not really "close" the connection, but drops it back to the connection pool.
func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
	conn := &Conn{r.pool.Get()}
	defer conn.Close()
	return conn.Do(command, args...)
}

// DoVar returns value from Do as gvar.Var.
func (r *Redis) DoVar(command string, args ...interface{}) (*gvar.Var, error) {
	v, err := r.Do(command, args...)
	if result, ok := v.([]byte); ok {
		return gvar.New(gconv.UnsafeBytesToStr(result)), err
	}
	// It treats all returned slice as string slice.
	if result, ok := v.([]interface{}); ok {
		return gvar.New(gconv.Strings(result)), err
	}
	return gvar.New(v), err
}
<|endoftext|>


<commit_before>package tests

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"sync"
	"time"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
	"github.com/portworx/torpedo/drivers/node"
	"github.com/sirupsen/logrus"
	// import aws driver to invoke it's init
	_ "github.com/portworx/torpedo/drivers/node/aws"
	// import ssh driver to invoke it's init
	_ "github.com/portworx/torpedo/drivers/node/ssh"
	"github.com/portworx/torpedo/drivers/scheduler"
	// import scheduler drivers to invoke it's init
	_ "github.com/portworx/torpedo/drivers/scheduler/dcos"
	_ "github.com/portworx/torpedo/drivers/scheduler/k8s"
	"github.com/portworx/torpedo/drivers/volume"
	// import portworx driver to invoke it's init
	_ "github.com/portworx/torpedo/drivers/volume/portworx"
	"github.com/portworx/torpedo/pkg/log"
)

const (
	// defaultSpecsRoot specifies the default location of the base specs directory in the Torpedo container
	defaultSpecsRoot                   = "/specs"
	schedulerCliFlag                   = "scheduler"
	nodeDriverCliFlag                  = "node-driver"
	storageDriverCliFlag               = "storage-driver"
	specDirCliFlag                     = "spec-dir"
	logLocationCliFlag                 = "log-location"
	scaleFactorCliFlag                 = "scale-factor"
	storageDriverUpgradeVersionCliFlag = "storage-driver-upgrade-version"
	storageDriverBaseVersionCliFlag    = "storage-driver-base-version"
)

const (
	defaultScheduler      = "k8s"
	defaultNodeDriver     = "ssh"
	defaultStorageDriver  = "pxd"
	defaultLogLocation    = "/mnt/torpedo_support_dir"
	defaultAppScaleFactor = 10
	// TODO: These are Portworx specific versions and will not work with other storage drivers.
	// Eventually we should remove the defaults and make it mandatory with documentation.
	defaultStorageDriverUpgradeVersion = "1.2.11.6"
	defaultStorageDriverBaseVersion    = "1.2.11.5"
)

var (
	context = ginkgo.Context
	// Step is an alias for ginko "By" which represents a step in the spec
	Step         = ginkgo.By
	expect       = gomega.Expect
	haveOccurred = gomega.HaveOccurred
	beEmpty      = gomega.BeEmpty
)

// InitInstance is the ginkgo spec for initializing torpedo
func InitInstance() {
	var err error
	err = Inst().S.Init(Inst().SpecDir, Inst().V.String(), Inst().N.String())
	expect(err).NotTo(haveOccurred())

	err = Inst().V.Init(Inst().S.String(), Inst().N.String())
	expect(err).NotTo(haveOccurred())

	err = Inst().N.Init()
	expect(err).NotTo(haveOccurred())
}

// ValidateCleanup checks that there are no resource leaks after the test run
func ValidateCleanup() {
	err := Inst().V.ValidateVolumeCleanup()
	expect(err).NotTo(haveOccurred())
}

// ValidateContext is the ginkgo spec for validating a scheduled context
func ValidateContext(ctx *scheduler.Context) {
	ginkgo.Describe(fmt.Sprintf("For validation of %s app", ctx.App.Key), func() {
		generateSupportBundle(ctx)

		Step(fmt.Sprintf("validate %s app's volumes", ctx.App.Key), func() {
			ValidateVolumes(ctx)
		})

		Step(fmt.Sprintf("wait for %s app to start running", ctx.App.Key), func() {
			err := Inst().S.WaitForRunning(ctx)
			expect(err).NotTo(haveOccurred())
		})
	})
}

// ValidateVolumes is the ginkgo spec for validating volumes of a context
func ValidateVolumes(ctx *scheduler.Context) {
	context("For validation of an app's volumes", func() {
		var err error
		Step(fmt.Sprintf("inspect %s app's volumes", ctx.App.Key), func() {
			err = Inst().S.InspectVolumes(ctx)
			expect(err).NotTo(haveOccurred())
		})

		var vols map[string]map[string]string
		Step(fmt.Sprintf("get %s app's volume's custom parameters", ctx.App.Key), func() {
			vols, err = Inst().S.GetVolumeParameters(ctx)
			expect(err).NotTo(haveOccurred())
		})

		for vol, params := range vols {
			Step(fmt.Sprintf("get %s app's volume inspected by the volume driver", ctx.App.Key), func() {
				err = Inst().V.ValidateCreateVolume(vol, params)
				expect(err).NotTo(haveOccurred())
			})
		}
	})
}

// TearDownContext is the ginkgo spec for tearing down a scheduled context
func TearDownContext(ctx *scheduler.Context, opts map[string]bool) {
	context("For tearing down of an app context", func() {
		var err error

		Step(fmt.Sprintf("start destroying %s app", ctx.App.Key), func() {
			err = Inst().S.Destroy(ctx, opts)
			expect(err).NotTo(haveOccurred())
		})

		DeleteVolumesAndWait(ctx)
	})
}

// DeleteVolumesAndWait deletes volumes of given context and waits till they are deleted
func DeleteVolumesAndWait(ctx *scheduler.Context) {
	var err error
	var vols []*volume.Volume
	Step(fmt.Sprintf("destroy the %s app's volumes", ctx.App.Key), func() {
		vols, err = Inst().S.DeleteVolumes(ctx)
		expect(err).NotTo(haveOccurred())
	})

	for _, vol := range vols {
		Step(fmt.Sprintf("validate %s app's volume %s has been deleted in the volume driver",
			ctx.App.Key, vol.Name), func() {
			err = Inst().V.ValidateDeleteVolume(vol)
			expect(err).NotTo(haveOccurred())
		})
	}
}

// ScheduleAndValidate schedules and validates applications
func ScheduleAndValidate(testname string) []*scheduler.Context {
	var contexts []*scheduler.Context
	var err error

	Step("schedule applications", func() {
		taskName := fmt.Sprintf("%s-%v", testname, Inst().InstanceID)
		contexts, err = Inst().S.Schedule(taskName, scheduler.ScheduleOptions{})
		expect(err).NotTo(haveOccurred())
		expect(contexts).NotTo(beEmpty())
	})

	Step("validate applications", func() {
		for _, ctx := range contexts {
			ValidateContext(ctx)
		}
	})

	return contexts
}

// StartVolDriverAndWait starts volume driver on given app nodes
func StartVolDriverAndWait(appNodes []node.Node) {
	context(fmt.Sprintf("starting volume driver %s", Inst().V.String()), func() {
		Step(fmt.Sprintf("start volume driver on nodes: %v", appNodes), func() {
			for _, n := range appNodes {
				err := Inst().V.StartDriver(n)
				expect(err).NotTo(haveOccurred())
			}
		})

		Step(fmt.Sprintf("wait for volume driver to start on nodes: %v", appNodes), func() {
			for _, n := range appNodes {
				err := Inst().V.WaitForNode(n)
				expect(err).NotTo(haveOccurred())
			}
		})

	})
}

// ValidateAndDestroy validates application and then destroys them
func ValidateAndDestroy(ctx *scheduler.Context, opts map[string]bool) {
	ValidateContext(ctx)
	TearDownContext(ctx, opts)
}

// generateSupportBundle gathers logs and any artifacts pertinent to the scheduler and dumps them in the defined location
func generateSupportBundle(ctx *scheduler.Context) {
	context(fmt.Sprintf("generate support bundle for app: %s", ctx.App.Key), func() {
		var out string
		var err error

		Step(fmt.Sprintf("describe scheduler context for app: %s", ctx.App.Key), func() {
			out, err = Inst().S.Describe(ctx)
			expect(err).NotTo(haveOccurred())

			err = ioutil.WriteFile(fmt.Sprintf("%s/supportbundle_%s_%v.log",
				Inst().LogLoc, ctx.UID, time.Now().Format(time.RFC3339)), []byte(out), 0644)
			expect(err).NotTo(haveOccurred())
		})
	})
}

// Inst returns the Torpedo instances
func Inst() *Torpedo {
	return instance
}

var instance *Torpedo
var once sync.Once

// Torpedo is the torpedo testsuite
type Torpedo struct {
	InstanceID                  string
	S                           scheduler.Driver
	V                           volume.Driver
	N                           node.Driver
	SpecDir                     string
	LogLoc                      string
	ScaleFactor                 int
	StorageDriverUpgradeVersion string
	StorageDriverBaseVersion    string
}

// ParseFlags parses command line flags
func ParseFlags() {
	var err error
	var s, n, v, specDir, logLoc string
	var schedulerDriver scheduler.Driver
	var volumeDriver volume.Driver
	var nodeDriver node.Driver
	var appScaleFactor int
	var volUpgradeVersion, volBaseVersion string

	flag.StringVar(&s, schedulerCliFlag, defaultScheduler, "Name of the scheduler to us")
	flag.StringVar(&n, nodeDriverCliFlag, defaultNodeDriver, "Name of the node driver to use")
	flag.StringVar(&v, storageDriverCliFlag, defaultStorageDriver, "Name of the storage driver to use")
	flag.StringVar(&specDir, specDirCliFlag, defaultSpecsRoot,
		"Root directory containing the application spec files")
	flag.StringVar(&logLoc, logLocationCliFlag, defaultLogLocation,
		"Path to save logs/artifacts upon failure. Default: /mnt/torpedo_support_dir")
	flag.IntVar(&appScaleFactor, scaleFactorCliFlag, defaultAppScaleFactor, "Factor by which to scale applications")
	flag.StringVar(&volUpgradeVersion, storageDriverUpgradeVersionCliFlag, defaultStorageDriverUpgradeVersion,
		"Version of storage driver to be upgraded to")
	flag.StringVar(&volBaseVersion, storageDriverBaseVersionCliFlag, defaultStorageDriverBaseVersion,
		"Version of storage driver to be downgraded to")

	flag.Parse()

	if schedulerDriver, err = scheduler.Get(s); err != nil {
		logrus.Fatalf("Cannot find scheduler driver for %v. Err: %v\n", s, err)
		os.Exit(-1)
	} else if volumeDriver, err = volume.Get(v); err != nil {
		logrus.Fatalf("Cannot find volume driver for %v. Err: %v\n", v, err)
		os.Exit(-1)
	} else if nodeDriver, err = node.Get(n); err != nil {
		logrus.Fatalf("Cannot find node driver for %v. Err: %v\n", n, err)
		os.Exit(-1)
	} else if err := os.MkdirAll(logLoc, os.ModeDir); err != nil {
		logrus.Fatalf("Cannot create path %s for saving support bundle. Error: %v", logLoc, err)
		os.Exit(-1)
	} else {
		once.Do(func() {
			instance = &Torpedo{
				InstanceID:                  time.Now().Format("01-02-15h04m05s"),
				S:                           schedulerDriver,
				V:                           volumeDriver,
				N:                           nodeDriver,
				SpecDir:                     specDir,
				LogLoc:                      logLoc,
				ScaleFactor:                 appScaleFactor,
				StorageDriverUpgradeVersion: volUpgradeVersion,
				StorageDriverBaseVersion:    volBaseVersion,
			}
		})
	}
}

func init() {
	logrus.SetLevel(logrus.InfoLevel)
	logrus.StandardLogger().Hooks.Add(log.NewHook())
	logrus.SetOutput(ginkgo.GinkgoWriter)
}
<commit_msg>Adding a wait before we validate the cleanup of resources (#85)<commit_after>package tests

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"sync"
	"time"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
	"github.com/portworx/torpedo/drivers/node"
	"github.com/sirupsen/logrus"
	// import aws driver to invoke it's init
	_ "github.com/portworx/torpedo/drivers/node/aws"
	// import ssh driver to invoke it's init
	_ "github.com/portworx/torpedo/drivers/node/ssh"
	"github.com/portworx/torpedo/drivers/scheduler"
	// import scheduler drivers to invoke it's init
	_ "github.com/portworx/torpedo/drivers/scheduler/dcos"
	_ "github.com/portworx/torpedo/drivers/scheduler/k8s"
	"github.com/portworx/torpedo/drivers/volume"
	// import portworx driver to invoke it's init
	_ "github.com/portworx/torpedo/drivers/volume/portworx"
	"github.com/portworx/torpedo/pkg/log"
)

const (
	// defaultSpecsRoot specifies the default location of the base specs directory in the Torpedo container
	defaultSpecsRoot                   = "/specs"
	schedulerCliFlag                   = "scheduler"
	nodeDriverCliFlag                  = "node-driver"
	storageDriverCliFlag               = "storage-driver"
	specDirCliFlag                     = "spec-dir"
	logLocationCliFlag                 = "log-location"
	scaleFactorCliFlag                 = "scale-factor"
	storageDriverUpgradeVersionCliFlag = "storage-driver-upgrade-version"
	storageDriverBaseVersionCliFlag    = "storage-driver-base-version"
)

const (
	defaultScheduler      = "k8s"
	defaultNodeDriver     = "ssh"
	defaultStorageDriver  = "pxd"
	defaultLogLocation    = "/mnt/torpedo_support_dir"
	defaultAppScaleFactor = 10
	// TODO: These are Portworx specific versions and will not work with other storage drivers.
	// Eventually we should remove the defaults and make it mandatory with documentation.
	defaultStorageDriverUpgradeVersion = "1.2.11.6"
	defaultStorageDriverBaseVersion    = "1.2.11.5"
)

var (
	context = ginkgo.Context
	// Step is an alias for ginko "By" which represents a step in the spec
	Step         = ginkgo.By
	expect       = gomega.Expect
	haveOccurred = gomega.HaveOccurred
	beEmpty      = gomega.BeEmpty
)

// InitInstance is the ginkgo spec for initializing torpedo
func InitInstance() {
	var err error
	err = Inst().S.Init(Inst().SpecDir, Inst().V.String(), Inst().N.String())
	expect(err).NotTo(haveOccurred())

	err = Inst().V.Init(Inst().S.String(), Inst().N.String())
	expect(err).NotTo(haveOccurred())

	err = Inst().N.Init()
	expect(err).NotTo(haveOccurred())
}

// ValidateCleanup checks that there are no resource leaks after the test run
func ValidateCleanup() {
	timeToWait := 60 * time.Second
	Step(fmt.Sprintf("wait for %s before validating resource cleanup", timeToWait), func() {
		time.Sleep(timeToWait)
	})
	Step(fmt.Sprintf("validate cleanup of resources used by the test suite"), func() {
		err := Inst().V.ValidateVolumeCleanup()
		expect(err).NotTo(haveOccurred())
	})
}

// ValidateContext is the ginkgo spec for validating a scheduled context
func ValidateContext(ctx *scheduler.Context) {
	ginkgo.Describe(fmt.Sprintf("For validation of %s app", ctx.App.Key), func() {
		generateSupportBundle(ctx)

		Step(fmt.Sprintf("validate %s app's volumes", ctx.App.Key), func() {
			ValidateVolumes(ctx)
		})

		Step(fmt.Sprintf("wait for %s app to start running", ctx.App.Key), func() {
			err := Inst().S.WaitForRunning(ctx)
			expect(err).NotTo(haveOccurred())
		})
	})
}

// ValidateVolumes is the ginkgo spec for validating volumes of a context
func ValidateVolumes(ctx *scheduler.Context) {
	context("For validation of an app's volumes", func() {
		var err error
		Step(fmt.Sprintf("inspect %s app's volumes", ctx.App.Key), func() {
			err = Inst().S.InspectVolumes(ctx)
			expect(err).NotTo(haveOccurred())
		})

		var vols map[string]map[string]string
		Step(fmt.Sprintf("get %s app's volume's custom parameters", ctx.App.Key), func() {
			vols, err = Inst().S.GetVolumeParameters(ctx)
			expect(err).NotTo(haveOccurred())
		})

		for vol, params := range vols {
			Step(fmt.Sprintf("get %s app's volume inspected by the volume driver", ctx.App.Key), func() {
				err = Inst().V.ValidateCreateVolume(vol, params)
				expect(err).NotTo(haveOccurred())
			})
		}
	})
}

// TearDownContext is the ginkgo spec for tearing down a scheduled context
func TearDownContext(ctx *scheduler.Context, opts map[string]bool) {
	context("For tearing down of an app context", func() {
		var err error

		Step(fmt.Sprintf("start destroying %s app", ctx.App.Key), func() {
			err = Inst().S.Destroy(ctx, opts)
			expect(err).NotTo(haveOccurred())
		})

		DeleteVolumesAndWait(ctx)
	})
}

// DeleteVolumesAndWait deletes volumes of given context and waits till they are deleted
func DeleteVolumesAndWait(ctx *scheduler.Context) {
	var err error
	var vols []*volume.Volume
	Step(fmt.Sprintf("destroy the %s app's volumes", ctx.App.Key), func() {
		vols, err = Inst().S.DeleteVolumes(ctx)
		expect(err).NotTo(haveOccurred())
	})

	for _, vol := range vols {
		Step(fmt.Sprintf("validate %s app's volume %s has been deleted in the volume driver",
			ctx.App.Key, vol.Name), func() {
			err = Inst().V.ValidateDeleteVolume(vol)
			expect(err).NotTo(haveOccurred())
		})
	}
}

// ScheduleAndValidate schedules and validates applications
func ScheduleAndValidate(testname string) []*scheduler.Context {
	var contexts []*scheduler.Context
	var err error

	Step("schedule applications", func() {
		taskName := fmt.Sprintf("%s-%v", testname, Inst().InstanceID)
		contexts, err = Inst().S.Schedule(taskName, scheduler.ScheduleOptions{})
		expect(err).NotTo(haveOccurred())
		expect(contexts).NotTo(beEmpty())
	})

	Step("validate applications", func() {
		for _, ctx := range contexts {
			ValidateContext(ctx)
		}
	})

	return contexts
}

// StartVolDriverAndWait starts volume driver on given app nodes
func StartVolDriverAndWait(appNodes []node.Node) {
	context(fmt.Sprintf("starting volume driver %s", Inst().V.String()), func() {
		Step(fmt.Sprintf("start volume driver on nodes: %v", appNodes), func() {
			for _, n := range appNodes {
				err := Inst().V.StartDriver(n)
				expect(err).NotTo(haveOccurred())
			}
		})

		Step(fmt.Sprintf("wait for volume driver to start on nodes: %v", appNodes), func() {
			for _, n := range appNodes {
				err := Inst().V.WaitForNode(n)
				expect(err).NotTo(haveOccurred())
			}
		})

	})
}

// ValidateAndDestroy validates application and then destroys them
func ValidateAndDestroy(ctx *scheduler.Context, opts map[string]bool) {
	ValidateContext(ctx)
	TearDownContext(ctx, opts)
}

// generateSupportBundle gathers logs and any artifacts pertinent to the scheduler and dumps them in the defined location
func generateSupportBundle(ctx *scheduler.Context) {
	context(fmt.Sprintf("generate support bundle for app: %s", ctx.App.Key), func() {
		var out string
		var err error

		Step(fmt.Sprintf("describe scheduler context for app: %s", ctx.App.Key), func() {
			out, err = Inst().S.Describe(ctx)
			expect(err).NotTo(haveOccurred())

			err = ioutil.WriteFile(fmt.Sprintf("%s/supportbundle_%s_%v.log",
				Inst().LogLoc, ctx.UID, time.Now().Format(time.RFC3339)), []byte(out), 0644)
			expect(err).NotTo(haveOccurred())
		})
	})
}

// Inst returns the Torpedo instances
func Inst() *Torpedo {
	return instance
}

var instance *Torpedo
var once sync.Once

// Torpedo is the torpedo testsuite
type Torpedo struct {
	InstanceID                  string
	S                           scheduler.Driver
	V                           volume.Driver
	N                           node.Driver
	SpecDir                     string
	LogLoc                      string
	ScaleFactor                 int
	StorageDriverUpgradeVersion string
	StorageDriverBaseVersion    string
}

// ParseFlags parses command line flags
func ParseFlags() {
	var err error
	var s, n, v, specDir, logLoc string
	var schedulerDriver scheduler.Driver
	var volumeDriver volume.Driver
	var nodeDriver node.Driver
	var appScaleFactor int
	var volUpgradeVersion, volBaseVersion string

	flag.StringVar(&s, schedulerCliFlag, defaultScheduler, "Name of the scheduler to us")
	flag.StringVar(&n, nodeDriverCliFlag, defaultNodeDriver, "Name of the node driver to use")
	flag.StringVar(&v, storageDriverCliFlag, defaultStorageDriver, "Name of the storage driver to use")
	flag.StringVar(&specDir, specDirCliFlag, defaultSpecsRoot,
		"Root directory containing the application spec files")
	flag.StringVar(&logLoc, logLocationCliFlag, defaultLogLocation,
		"Path to save logs/artifacts upon failure. Default: /mnt/torpedo_support_dir")
	flag.IntVar(&appScaleFactor, scaleFactorCliFlag, defaultAppScaleFactor, "Factor by which to scale applications")
	flag.StringVar(&volUpgradeVersion, storageDriverUpgradeVersionCliFlag, defaultStorageDriverUpgradeVersion,
		"Version of storage driver to be upgraded to")
	flag.StringVar(&volBaseVersion, storageDriverBaseVersionCliFlag, defaultStorageDriverBaseVersion,
		"Version of storage driver to be downgraded to")

	flag.Parse()

	if schedulerDriver, err = scheduler.Get(s); err != nil {
		logrus.Fatalf("Cannot find scheduler driver for %v. Err: %v\n", s, err)
		os.Exit(-1)
	} else if volumeDriver, err = volume.Get(v); err != nil {
		logrus.Fatalf("Cannot find volume driver for %v. Err: %v\n", v, err)
		os.Exit(-1)
	} else if nodeDriver, err = node.Get(n); err != nil {
		logrus.Fatalf("Cannot find node driver for %v. Err: %v\n", n, err)
		os.Exit(-1)
	} else if err := os.MkdirAll(logLoc, os.ModeDir); err != nil {
		logrus.Fatalf("Cannot create path %s for saving support bundle. Error: %v", logLoc, err)
		os.Exit(-1)
	} else {
		once.Do(func() {
			instance = &Torpedo{
				InstanceID:                  time.Now().Format("01-02-15h04m05s"),
				S:                           schedulerDriver,
				V:                           volumeDriver,
				N:                           nodeDriver,
				SpecDir:                     specDir,
				LogLoc:                      logLoc,
				ScaleFactor:                 appScaleFactor,
				StorageDriverUpgradeVersion: volUpgradeVersion,
				StorageDriverBaseVersion:    volBaseVersion,
			}
		})
	}
}

func init() {
	logrus.SetLevel(logrus.InfoLevel)
	logrus.StandardLogger().Hooks.Add(log.NewHook())
	logrus.SetOutput(ginkgo.GinkgoWriter)
}
<|endoftext|>


<commit_before>package webhandler

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

	"github.com/elazarl/go-bindata-assetfs"
	"github.com/pivotal-golang/lager"
	"github.com/tedsuo/rata"

	"github.com/concourse/atc/engine"
	"github.com/concourse/atc/web"
	"github.com/concourse/atc/web/getbuild"
	"github.com/concourse/atc/web/getbuilds"
	"github.com/concourse/atc/web/getjob"
	"github.com/concourse/atc/web/getjoblessbuild"
	"github.com/concourse/atc/web/getresource"
	"github.com/concourse/atc/web/index"
	"github.com/concourse/atc/web/login"
	"github.com/concourse/atc/web/pipeline"
	"github.com/concourse/atc/web/triggerbuild"
	"github.com/concourse/atc/wrappa"
)

func NewHandler(
	logger lager.Logger,
	wrapper wrappa.Wrappa,
	engine engine.Engine,
	clientFactory web.ClientFactory,
) (http.Handler, error) {
	tfuncs := &templateFuncs{
		assetIDs: map[string]string{},
	}

	funcs := template.FuncMap{
		"url":          tfuncs.url,
		"asset":        tfuncs.asset,
		"withRedirect": tfuncs.withRedirect,
	}

	indexTemplate, err := loadTemplate("index.html", funcs)
	if err != nil {
		return nil, err
	}

	pipelineTemplate, err := loadTemplateWithPipeline("pipeline.html", funcs)
	if err != nil {
		return nil, err
	}

	buildTemplate, err := loadTemplateWithPipeline("build.html", funcs)
	if err != nil {
		return nil, err
	}

	oldBuildTemplate, err := loadTemplateWithPipeline("old-build.html", funcs)
	if err != nil {
		return nil, err
	}

	buildsTemplate, err := loadTemplateWithoutPipeline("builds/index.html", funcs)
	if err != nil {
		return nil, err
	}

	joblessBuildTemplate, err := loadTemplateWithoutPipeline("builds/show.html", funcs)
	if err != nil {
		return nil, err
	}

	oldJoblessBuildTemplate, err := loadTemplateWithoutPipeline("builds/old-show.html", funcs)
	if err != nil {
		return nil, err
	}

	resourceTemplate, err := loadTemplateWithPipeline("resource.html", funcs)
	if err != nil {
		return nil, err
	}

	jobTemplate, err := loadTemplateWithPipeline("job.html", funcs)
	if err != nil {
		return nil, err
	}

	logInTemplate, err := loadTemplateWithoutPipeline("login.html", funcs)
	if err != nil {
		return nil, err
	}

	publicFS := &assetfs.AssetFS{
		Asset:     web.Asset,
		AssetDir:  web.AssetDir,
		AssetInfo: web.AssetInfo,
	}

	pipelineHandler := pipeline.NewHandler(logger, clientFactory, pipelineTemplate)

	handlers := map[string]http.Handler{
		web.Index:           index.NewHandler(logger, clientFactory, pipelineHandler, indexTemplate),
		web.Pipeline:        pipelineHandler,
		web.Public:          http.FileServer(publicFS),
		web.GetJob:          getjob.NewHandler(logger, clientFactory, jobTemplate),
		web.GetResource:     getresource.NewHandler(logger, clientFactory, resourceTemplate),
		web.GetBuild:        getbuild.NewHandler(logger, clientFactory, buildTemplate, oldBuildTemplate),
		web.GetBuilds:       getbuilds.NewHandler(logger, clientFactory, buildsTemplate),
		web.GetJoblessBuild: getjoblessbuild.NewHandler(logger, clientFactory, joblessBuildTemplate, oldJoblessBuildTemplate),
		web.LogIn:           login.NewHandler(logger, clientFactory, logInTemplate),
		web.BasicAuth:       login.NewBasicAuthHandler(logger),
		web.TriggerBuild:    triggerbuild.NewHandler(logger, clientFactory),
	}

	return rata.NewRouter(web.Routes, wrapper.Wrap(handlers))
}

func loadTemplate(name string, funcs template.FuncMap) (*template.Template, error) {
	src, err := web.Asset("templates/" + name)
	if err != nil {
		return nil, err
	}

	return template.New(name).Funcs(funcs).Parse(string(src))
}

func loadTemplateWithPipeline(name string, funcs template.FuncMap) (*template.Template, error) {
	layout, err := loadTemplate("layouts/with_pipeline.html", funcs)
	if err != nil {
		return nil, err
	}

	templateSrc, err := web.Asset("templates/" + name)
	if err != nil {
		return nil, err
	}

	_, err = layout.New(name).Parse(string(templateSrc))
	if err != nil {
		return nil, err
	}

	return layout, nil
}

func loadTemplateWithoutPipeline(name string, funcs template.FuncMap) (*template.Template, error) {
	layout, err := loadTemplate("layouts/without_pipeline.html", funcs)
	if err != nil {
		return nil, err
	}

	templateSrc, err := web.Asset("templates/" + name)
	if err != nil {
		return nil, err
	}

	_, err = layout.New(name).Parse(string(templateSrc))
	if err != nil {
		return nil, err
	}

	return layout, nil
}
<commit_msg>remove no longer needed engine arg<commit_after>package webhandler

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

	"github.com/elazarl/go-bindata-assetfs"
	"github.com/pivotal-golang/lager"
	"github.com/tedsuo/rata"

	"github.com/concourse/atc/web"
	"github.com/concourse/atc/web/getbuild"
	"github.com/concourse/atc/web/getbuilds"
	"github.com/concourse/atc/web/getjob"
	"github.com/concourse/atc/web/getjoblessbuild"
	"github.com/concourse/atc/web/getresource"
	"github.com/concourse/atc/web/index"
	"github.com/concourse/atc/web/login"
	"github.com/concourse/atc/web/pipeline"
	"github.com/concourse/atc/web/triggerbuild"
	"github.com/concourse/atc/wrappa"
)

func NewHandler(
	logger lager.Logger,
	wrapper wrappa.Wrappa,
	clientFactory web.ClientFactory,
) (http.Handler, error) {
	tfuncs := &templateFuncs{
		assetIDs: map[string]string{},
	}

	funcs := template.FuncMap{
		"url":          tfuncs.url,
		"asset":        tfuncs.asset,
		"withRedirect": tfuncs.withRedirect,
	}

	indexTemplate, err := loadTemplate("index.html", funcs)
	if err != nil {
		return nil, err
	}

	pipelineTemplate, err := loadTemplateWithPipeline("pipeline.html", funcs)
	if err != nil {
		return nil, err
	}

	buildTemplate, err := loadTemplateWithPipeline("build.html", funcs)
	if err != nil {
		return nil, err
	}

	oldBuildTemplate, err := loadTemplateWithPipeline("old-build.html", funcs)
	if err != nil {
		return nil, err
	}

	buildsTemplate, err := loadTemplateWithoutPipeline("builds/index.html", funcs)
	if err != nil {
		return nil, err
	}

	joblessBuildTemplate, err := loadTemplateWithoutPipeline("builds/show.html", funcs)
	if err != nil {
		return nil, err
	}

	oldJoblessBuildTemplate, err := loadTemplateWithoutPipeline("builds/old-show.html", funcs)
	if err != nil {
		return nil, err
	}

	resourceTemplate, err := loadTemplateWithPipeline("resource.html", funcs)
	if err != nil {
		return nil, err
	}

	jobTemplate, err := loadTemplateWithPipeline("job.html", funcs)
	if err != nil {
		return nil, err
	}

	logInTemplate, err := loadTemplateWithoutPipeline("login.html", funcs)
	if err != nil {
		return nil, err
	}

	publicFS := &assetfs.AssetFS{
		Asset:     web.Asset,
		AssetDir:  web.AssetDir,
		AssetInfo: web.AssetInfo,
	}

	pipelineHandler := pipeline.NewHandler(logger, clientFactory, pipelineTemplate)

	handlers := map[string]http.Handler{
		web.Index:           index.NewHandler(logger, clientFactory, pipelineHandler, indexTemplate),
		web.Pipeline:        pipelineHandler,
		web.Public:          http.FileServer(publicFS),
		web.GetJob:          getjob.NewHandler(logger, clientFactory, jobTemplate),
		web.GetResource:     getresource.NewHandler(logger, clientFactory, resourceTemplate),
		web.GetBuild:        getbuild.NewHandler(logger, clientFactory, buildTemplate, oldBuildTemplate),
		web.GetBuilds:       getbuilds.NewHandler(logger, clientFactory, buildsTemplate),
		web.GetJoblessBuild: getjoblessbuild.NewHandler(logger, clientFactory, joblessBuildTemplate, oldJoblessBuildTemplate),
		web.LogIn:           login.NewHandler(logger, clientFactory, logInTemplate),
		web.BasicAuth:       login.NewBasicAuthHandler(logger),
		web.TriggerBuild:    triggerbuild.NewHandler(logger, clientFactory),
	}

	return rata.NewRouter(web.Routes, wrapper.Wrap(handlers))
}

func loadTemplate(name string, funcs template.FuncMap) (*template.Template, error) {
	src, err := web.Asset("templates/" + name)
	if err != nil {
		return nil, err
	}

	return template.New(name).Funcs(funcs).Parse(string(src))
}

func loadTemplateWithPipeline(name string, funcs template.FuncMap) (*template.Template, error) {
	layout, err := loadTemplate("layouts/with_pipeline.html", funcs)
	if err != nil {
		return nil, err
	}

	templateSrc, err := web.Asset("templates/" + name)
	if err != nil {
		return nil, err
	}

	_, err = layout.New(name).Parse(string(templateSrc))
	if err != nil {
		return nil, err
	}

	return layout, nil
}

func loadTemplateWithoutPipeline(name string, funcs template.FuncMap) (*template.Template, error) {
	layout, err := loadTemplate("layouts/without_pipeline.html", funcs)
	if err != nil {
		return nil, err
	}

	templateSrc, err := web.Asset("templates/" + name)
	if err != nil {
		return nil, err
	}

	_, err = layout.New(name).Parse(string(templateSrc))
	if err != nil {
		return nil, err
	}

	return layout, nil
}
<|endoftext|>