<commit_before>package asock

import (
	"net"
	"testing"
	"time"
)

// the echo function for our dispatch table
func echo(args [][]byte) ([]byte, error) {
	var bs []byte
	for i, arg := range args {
		bs = append(bs, arg...)
		if i != len(args) - 1 {
			bs = append(bs, byte(32))
		}
	}
	return bs, nil
}

// implement an echo server
func TestEchoServer(t *testing.T) {
	// instantiate an asocket
	c := Config{Sockname: "/tmp/test02.sock", Msglvl: All}
	as, err := NewUnix(c, 700)
	if err != nil {
		t.Errorf("Couldn't create socket: %v", err)
	}
	as.AddHandler("echo", "split", echo)

	// launch echoclient. we should get a message about the
	// connection.
	go echoclient(as.s, t)
	msg := <-as.Msgr
	if msg.Err != nil {
		t.Errorf("connection creation returned error: %v", msg.Err)
	}
	if msg.Txt != "client connected" {
		t.Errorf("unexpected msg.Txt: %v", msg.Txt)
	}
	// and a message about dispatching the command
	msg = <-as.Msgr
	if msg.Err != nil {
		t.Errorf("successful cmd shouldn't be err, but got %v", err)
	}
	if msg.Txt != "dispatching [echo]" {
		t.Errorf("unexpected msg.Txt: %v", msg.Txt)
	}
	if msg.Code != 101 {
		t.Errorf("msg.Code should have been 101 but got: %v", msg.Code)
	}
	// and a message that we have replied
	msg = <-as.Msgr
	if msg.Err != nil {
		t.Errorf("successful cmd shouldn't be err, but got %v", err)
	}
	if msg.Txt != "reply sent" {
		t.Errorf("unexpected msg.Txt: %v", msg.Txt)
	}
	if msg.Code != 200 {
		t.Errorf("msg.Code should have been 200 but got: %v", msg.Code)
	}
	// wait for msg from unsuccessful command
	msg = <-as.Msgr
	if msg.Err != nil {
		t.Errorf("unsuccessful cmd shouldn't be err, but got %v", err)
	}
	if msg.Txt != "bad command 'foo'" {
		t.Errorf("unexpected msg.Txt: %v", msg.Txt)
	}
	if msg.Code != 400 {
		t.Errorf("msg.Code should have been 400 but got: %v", msg.Code)
	}
	// wait for disconnect Msg
	msg = <-as.Msgr
	if msg.Err == nil {
		t.Errorf("connection drop should be an err, but got nil")
	}
	if msg.Txt != "client disconnected" {
		t.Errorf("unexpected msg.Txt: %v", msg.Txt)
	}
	// shut down asocket
	as.Quit()
}

// this time our (less) fake client will send a string over the
// connection and (hopefully) get it echoed back.
func echoclient(sn string, t *testing.T) {
	conn, err := net.Dial("unix", sn)
	defer conn.Close()
	if err != nil {
		t.Errorf("Couldn't connect to %v: %v", sn, err)
	}
	conn.Write([]byte("echo it works!\n\n"))
	res, err := readConn(conn)
	if err != nil {
		t.Errorf("Error on read: %v", err)
	}
	if string(res) != "it works!\n\n" {
		t.Errorf("Expected 'it works!\\n\\n' but got '%v'", string(res))
	}
	// for bonus points, let's send a bad command
	conn.Write([]byte("foo bar\n\n"))
	res, err = readConn(conn)
	if err != nil {
		t.Errorf("Error on read: %v", err)
	}
	if string(res) != "Unknown command 'foo'. Available commands: echo \n\n" {
		t.Errorf("Expected bad command error but got '%v'", string(res))
	}
}

func readConn(conn net.Conn) ([]byte, error) {
	b1 := make([]byte, 64)
	var b2 []byte
	for {
		conn.SetReadDeadline(time.Now().Add(50 * time.Millisecond))
		n, err := conn.Read(b1)
		if err != nil {
			return nil, err
		}
		b2 = append(b2, b1[:n]...)
		if n == 64 {
			continue
		}
		break
	}
	return b2, nil
}
<commit_msg>using aclient<commit_after>package asock

import (
	"testing"

	"firepear.net/aclient"
)

// the echo function for our dispatch table
func echo(args [][]byte) ([]byte, error) {
	var bs []byte
	for i, arg := range args {
		bs = append(bs, arg...)
		if i != len(args) - 1 {
			bs = append(bs, byte(32))
		}
	}
	return bs, nil
}

// implement an echo server
func TestEchoServer(t *testing.T) {
	// instantiate an asocket
	c := Config{Sockname: "/tmp/test02.sock", Msglvl: All}
	as, err := NewUnix(c, 700)
	if err != nil {
		t.Errorf("Couldn't create socket: %v", err)
	}
	as.AddHandler("echo", "split", echo)

	// launch a client and do some things
	go echoclient("/tmp/test02.sock", t)

	// we should get a message about the connection.
	msg := <-as.Msgr
	if msg.Err != nil {
		t.Errorf("connection creation returned error: %v", msg.Err)
	}
	if msg.Txt != "client connected" {
		t.Errorf("unexpected msg.Txt: %v", msg.Txt)
	}
	// and a message about dispatching the command
	msg = <-as.Msgr
	if msg.Err != nil {
		t.Errorf("successful cmd shouldn't be err, but got %v", err)
	}
	if msg.Txt != "dispatching [echo]" {
		t.Errorf("unexpected msg.Txt: %v", msg.Txt)
	}
	if msg.Code != 101 {
		t.Errorf("msg.Code should have been 101 but got: %v", msg.Code)
	}
	// and a message that we have replied
	msg = <-as.Msgr
	if msg.Err != nil {
		t.Errorf("successful cmd shouldn't be err, but got %v", err)
	}
	if msg.Txt != "reply sent" {
		t.Errorf("unexpected msg.Txt: %v", msg.Txt)
	}
	if msg.Code != 200 {
		t.Errorf("msg.Code should have been 200 but got: %v", msg.Code)
	}
	// wait for msg from unsuccessful command
	msg = <-as.Msgr
	if msg.Err != nil {
		t.Errorf("unsuccessful cmd shouldn't be err, but got %v", err)
	}
	if msg.Txt != "bad command 'foo'" {
		t.Errorf("unexpected msg.Txt: %v", msg.Txt)
	}
	if msg.Code != 400 {
		t.Errorf("msg.Code should have been 400 but got: %v", msg.Code)
	}
	// wait for disconnect Msg
	msg = <-as.Msgr
	if msg.Err == nil {
		t.Errorf("connection drop should be an err, but got nil")
	}
	if msg.Txt != "client disconnected" {
		t.Errorf("unexpected msg.Txt: %v", msg.Txt)
	}
	// shut down asocket
	as.Quit()
}

// this time our (less) fake client will send a string over the
// connection and (hopefully) get it echoed back.
func echoclient(sn string, t *testing.T) {
	ac, err := aclient.NewTCP(aclient.Config{Addr: sn})
	if err != nil {
		t.Fatalf("aclient instantiation failed! %s", err)
	}
	defer ac.Close()
	resp, err := ac.Dispatch([]byte("echo it works!"))
	if err != nil {
		t.Errorf("Error on read: %v", err)
	}
	if string(resp) != "it works!" {
		t.Errorf("Expected 'it works!' but got '%v'", string(resp))
	}
	// for bonus points, let's send a bad command
	resp, err = ac.Dispatch([]byte("foo bar"))
	if err != nil {
		t.Errorf("Error on read: %v", err)
	}
	if string(resp) != "Unknown command 'foo'. Available commands: echo" {
		t.Errorf("Expected bad command error but got '%v'", string(resp))
	}
}
<|endoftext|>


<commit_before>package core

import (
	"context"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"pgregory.net/rapid"

	"github.com/livepeer/lpms/ffmpeg"
)

func TestLB_CalculateCost(t *testing.T) {
	assert := assert.New(t)
	profiles := []ffmpeg.VideoProfile{ffmpeg.P144p30fps16x9, ffmpeg.P144p30fps16x9, ffmpeg.P144p30fps16x9}
	profiles[0].Framerate = 1
	profiles[1].Framerate = 0 // passthru; estimated to be 30fps for load
	// rational FPS 29.97
	profiles[2].Framerate = 30000
	profiles[2].FramerateDen = 1001
	// (256 * 144 * 1) + (256 * 144 * 30) + (256 * 144 * 29)
	assert.Equal(2211840, calculateCost(profiles))
}

func TestLB_LeastLoaded(t *testing.T) {
	assert := assert.New(t)
	lb := NewLoadBalancingTranscoder("0,1,2,3,4", newStubTranscoder).(*LoadBalancingTranscoder)
	rapid.Check(t, func(t *rapid.T) {
		cost := rapid.IntRange(1, 10).Draw(t, "cost").(int)
		transcoder := lb.leastLoaded()
		// ensure we selected the minimum cost
		lb.load[transcoder] += cost
		currentLoad := lb.load[transcoder]
		for k, v := range lb.load {
			if k == transcoder {
				continue
			}
			assert.LessOrEqual(currentLoad, v+cost, "Would have been less loaded")
		}
	})
}

func TestLB_Ratchet(t *testing.T) {
	// Property: After assigning a new session to a transcoder,
	//           increment the starting index for the next search
	//           Also ensure wraparound.

	// Test:     Two transcoders, several sessions with the same set of profiles
	//           Run multiple transcodes.
	assert := assert.New(t)
	lb := NewLoadBalancingTranscoder("0,1", newStubTranscoder).(*LoadBalancingTranscoder)
	sessions := []string{"a", "b", "c", "d", "e"}

	rapid.Check(t, func(t *rapid.T) {
		sessIdx := rapid.IntRange(0, len(sessions)-1).Draw(t, "sess").(int)
		sess := sessions[sessIdx]
		_, exists := lb.sessions[sess]
		idx := lb.idx
		lb.Transcode(stubMetadata(sess, ffmpeg.P144p30fps16x9))
		if exists {
			assert.Equal(idx, lb.idx)
		} else {
			assert.Equal((idx+1)%len(lb.transcoders), lb.idx)
		}
	})
}

func TestLB_LoadAssignment(t *testing.T) {

	// Property: Overall load only increases after first segment

	// Test :    Randomize profiles to randomize "load" **per segment**;
	//           Subsequent segments should ignore subsequent load costs.

	assert := assert.New(t)
	lb := NewLoadBalancingTranscoder("0,1,2,3,4", newStubTranscoder).(*LoadBalancingTranscoder)
	sessions := []string{"a", "b", "c", "d", "e"}
	profiles := []ffmpeg.VideoProfile{}
	for _, v := range ffmpeg.VideoProfileLookup {
		profiles = append(profiles, v)
	}

	rapid.Check(t, func(t *rapid.T) {
		sessIdx := rapid.IntRange(0, len(sessions)-1).Draw(t, "sess").(int)
		sessName := sessions[sessIdx]
		profs := shuffleProfiles(t)
		_, exists := lb.sessions[sessName]
		totalLoad := accumLoad(lb)
		lb.Transcode(stubMetadata(sessName, profs...))
		if exists {
			assert.Equal(totalLoad, accumLoad(lb))
		} else {
			assert.Contains(lb.sessions, sessName, "Transcoder did not establish session")
			assert.Equal(totalLoad+calculateCost(profs), accumLoad(lb))
		}
	})
}

func TestLB_SessionCancel(t *testing.T) {
	// One-off test for session cancellation to work around thread safety issues
	stubCtx, stubCancel := context.WithCancel(context.Background())
	ctxFunc := func() (context.Context, context.CancelFunc) { return stubCtx, stubCancel }

	sess := &transcoderSession{
		transcoder:  newStubTranscoder(""),
		sender:      make(chan *transcoderParams, 1),
		makeContext: ctxFunc,
	}

	wg := newWg(1)
	go func() {
		sess.loop()
		wg.Done()
	}()
	stubCancel()
	wgWait(wg)
	_, err := sess.Transcode(&SegTranscodingMetadata{})
	assert.Equal(t, ErrTranscoderBusy, err)
}

func TestLB_SessionConcurrency(t *testing.T) {

	stubCtx, stubCancel := context.WithCancel(context.Background())
	ctxFunc := func() (context.Context, context.CancelFunc) { return stubCtx, stubCancel }

	sess := &transcoderSession{
		transcoder:  newStubTranscoder(""),
		sender:      make(chan *transcoderParams, 1),
		makeContext: ctxFunc,
	}

	wg := newWg(1)
	go func() {
		sess.loop()
		wg.Done()
	}()

	iters := 100
	for i := 0; i < iters; i++ {
		if i == iters/2 {
			stubCancel()
		}
		wg.Add(1)
		go func() {
			sess.Transcode(&SegTranscodingMetadata{})
			wg.Done()
		}()
	}
	wgWait(wg)
}

func TestLB_ConcurrentSessionErrors(t *testing.T) {
	// Test session error counts under heavy concurrency

	// Race detector is sometimes slow enough to trigger timeouts
	// These numbers may need tweaking
	// (Or a separate non-race test with higher numbers)
	mainIter := 200
	innerIters := 40
	innerTimeout := 10 * time.Second
	mainTimeout := 20 * time.Second

	wg := newWg(mainIter)
	for j := 0; j < mainIter; j++ {
		go func() {
			defer wg.Done()
			transcoder := stubTranscoderWithProfiles(nil)
			transcoder.TranscodeFn = func() error {
				time.Sleep(10 * time.Millisecond)
				// return a random error to distinguish from session errors
				return ErrManifestID
			}
			sess := &transcoderSession{
				key:         "",
				transcoder:  transcoder,
				sender:      make(chan *transcoderParams, 1),
				makeContext: transcodeLoopContext,
			}

			// Sanity check that we actually exit from the transcode loop
			wg.Add(1)
			go func() {
				sess.loop()
				wg.Done()
			}()

			errCh := make(chan int)
			for i := 0; i < innerIters; i++ {
				go func(ch chan int) {
					_, err := sess.Transcode(&SegTranscodingMetadata{})
					if err == nil {
						ch <- 0
					} else {
						ch <- 1
						if err != ErrTranscoderBusy &&
							err != ErrTranscoderStopped &&
							err != ErrManifestID {
							t.Error("Unexpected error from transcoder ", err)
						}
					}
				}(errCh)
			}

			errCount := 0
			timeout := time.After(innerTimeout)
			for i := 0; i < innerIters; i++ {
				select {
				case k := <-errCh:
					errCount += k
				case <-timeout:
					t.Error("Stopped because of timeout")
					break
				}
			}
			assert.Equal(t, innerIters, errCount)
		}()
	}
	assert.True(t, wgWait2(wg, mainTimeout), "Time expired")
}

func accumLoad(lb *LoadBalancingTranscoder) int {
	totalLoad := 0
	for _, v := range lb.load {
		totalLoad += v
	}
	return totalLoad
}

func shuffleProfiles(t *rapid.T) []ffmpeg.VideoProfile {
	// fisher-yates shuffle. or an approximation thereof. (should test this)
	profiles := []ffmpeg.VideoProfile{}
	for _, v := range ffmpeg.VideoProfileLookup {
		profiles = append(profiles, v)
	}
	for i := len(profiles) - 1; i >= 1; i-- {
		j := rapid.IntRange(0, i).Draw(t, "j").(int)
		profiles[i], profiles[j] = profiles[j], profiles[i]
	}
	nbProfs := rapid.IntRange(1, len(profiles)-1).Draw(t, "nbProfs").(int)
	return profiles[:nbProfs]
}

type machineState struct {
	segs     int
	load     int
	cancel   *context.CancelFunc
	profiles []ffmpeg.VideoProfile
}

// Description of a rapid state machine for testing the load balancer
type lbMachine struct {
	lb *LoadBalancingTranscoder

	// Our model: various bits of internal state we want to synchronize with
	states    map[string]*machineState
	totalLoad int
}

func (m *lbMachine) randomSession(t *rapid.T) (string, *machineState) {
	// Create an internal session
	// Doesn't actually create it on the transcoder - should we?
	sessName := strconv.Itoa(rapid.IntRange(0, 25).Draw(t, "sess").(int))

	// Create internal state if necessary
	state, exists := m.states[sessName]
	if exists {
		return sessName, state
	}

	profs := shuffleProfiles(t)
	state = &machineState{profiles: profs}
	m.states[sessName] = state
	m.totalLoad += calculateCost(profs)

	return sessName, state
}

func (m *lbMachine) Init(t *rapid.T) {
	var devices []string
	nbDevices := rapid.IntRange(1, 10).Draw(t, "nbDevices").(int)
	for i := 0; i < nbDevices; i++ {
		devices = append(devices, strconv.Itoa(i))
	}

	m.lb = NewLoadBalancingTranscoder(strings.Join(devices, ","), newStubTranscoder).(*LoadBalancingTranscoder)
	m.states = make(map[string]*machineState)

	assert.Equal(t, devices, m.lb.transcoders) // sanity check
}

func (m *lbMachine) TranscodeOK(t *rapid.T) {
	// Run a successful segment transcode

	sessName, state := m.randomSession(t)
	_, err := m.lb.Transcode(stubMetadata(sessName, state.profiles...))

	assert.Nil(t, err)

	// Update internal state
	state.segs++
}

func (m *lbMachine) TranscodeError(t *rapid.T) {
	// Run a failed segment transcode

	sessName, state := m.randomSession(t)

	// If session doesn't already exist, create it by forcing a transcode
	_, ok := m.lb.sessions[sessName]
	if !ok {
		_, err := m.lb.Transcode(stubMetadata(sessName, state.profiles...))
		assert.Nil(t, err)
		require.Contains(t, m.lb.sessions, sessName)
	}
	transcoder, ok := m.lb.sessions[sessName].transcoder.(*StubTranscoder)
	require.True(t, ok, "Transcoder was not a StubTranscoder")
	require.Equal(t, 0, transcoder.StoppedCount) // Sanity check

	transcoder.FailTranscode = true
	_, err := m.lb.Transcode(stubMetadata(sessName, state.profiles...))
	assert.Equal(t, ErrTranscode, err)

	m.totalLoad -= calculateCost(state.profiles)
	delete(m.states, sessName)

	// Give time for the transcode and session to stop
	for retryCount := 0; retryCount < 100; retryCount++ {
		m.lb.mu.Lock()
		_, exists := m.lb.sessions[sessName]
		m.lb.mu.Unlock()
		if !exists {
			break
		}
		time.Sleep(1 * time.Millisecond)
	}

	// Confirm things stopped
	assert.NotContains(t, m.lb.sessions, sessName, "Transcoder was not removed")
	assert.Equal(t, 1, transcoder.StoppedCount, "Transcoder did not stop")
}

func (m *lbMachine) Check(t *rapid.T) {
	assert := assert.New(t)

	m.lb.mu.RLock()
	defer m.lb.mu.RUnlock()
	assert.Equal(len(m.states), len(m.lb.sessions), "Mismatch in number of sessions")
	assert.Equal(m.totalLoad, accumLoad(m.lb), "Mismatch in load calculation")

	for name, sess := range m.lb.sessions {
		transcoder, ok := sess.transcoder.(*StubTranscoder)
		require.True(t, ok, "Not a stub transcoder!")
		require.Contains(t, m.states, name)
		assert.Equal(m.states[name].segs, transcoder.SegCount)
	}
}

func TestLB_Machine(t *testing.T) {
	rapid.Check(t, rapid.Run(&lbMachine{}))
}
<commit_msg>core: reproduce lb cleanup race condition<commit_after>package core

import (
	"context"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"pgregory.net/rapid"

	"github.com/livepeer/lpms/ffmpeg"
)

func TestLB_CalculateCost(t *testing.T) {
	assert := assert.New(t)
	profiles := []ffmpeg.VideoProfile{ffmpeg.P144p30fps16x9, ffmpeg.P144p30fps16x9, ffmpeg.P144p30fps16x9}
	profiles[0].Framerate = 1
	profiles[1].Framerate = 0 // passthru; estimated to be 30fps for load
	// rational FPS 29.97
	profiles[2].Framerate = 30000
	profiles[2].FramerateDen = 1001
	// (256 * 144 * 1) + (256 * 144 * 30) + (256 * 144 * 29)
	assert.Equal(2211840, calculateCost(profiles))
}

func TestLB_LeastLoaded(t *testing.T) {
	assert := assert.New(t)
	lb := NewLoadBalancingTranscoder("0,1,2,3,4", newStubTranscoder).(*LoadBalancingTranscoder)
	rapid.Check(t, func(t *rapid.T) {
		cost := rapid.IntRange(1, 10).Draw(t, "cost").(int)
		transcoder := lb.leastLoaded()
		// ensure we selected the minimum cost
		lb.load[transcoder] += cost
		currentLoad := lb.load[transcoder]
		for k, v := range lb.load {
			if k == transcoder {
				continue
			}
			assert.LessOrEqual(currentLoad, v+cost, "Would have been less loaded")
		}
	})
}

func TestLB_Ratchet(t *testing.T) {
	// Property: After assigning a new session to a transcoder,
	//           increment the starting index for the next search
	//           Also ensure wraparound.

	// Test:     Two transcoders, several sessions with the same set of profiles
	//           Run multiple transcodes.
	assert := assert.New(t)
	lb := NewLoadBalancingTranscoder("0,1", newStubTranscoder).(*LoadBalancingTranscoder)
	sessions := []string{"a", "b", "c", "d", "e"}

	rapid.Check(t, func(t *rapid.T) {
		sessIdx := rapid.IntRange(0, len(sessions)-1).Draw(t, "sess").(int)
		sess := sessions[sessIdx]
		_, exists := lb.sessions[sess]
		idx := lb.idx
		lb.Transcode(stubMetadata(sess, ffmpeg.P144p30fps16x9))
		if exists {
			assert.Equal(idx, lb.idx)
		} else {
			assert.Equal((idx+1)%len(lb.transcoders), lb.idx)
		}
	})
}

func TestLB_SessionCleanupRace(t *testing.T) {
	// Reproduce race condition around session cleanup #1750

	assert := assert.New(t)
	lb := NewLoadBalancingTranscoder("0", newStubTranscoder).(*LoadBalancingTranscoder)
	sess := "sess"
	// Force create a new session
	_, err := lb.Transcode(stubMetadata(sess, ffmpeg.P144p30fps16x9))
	assert.Nil(err)
	// Mark transcoder to error out
	transcoder := lb.sessions[sess].transcoder.(*StubTranscoder)
	transcoder.FailTranscode = true

	// Send 2 jobs concurrently to trigger the stuck issue
	wg := newWg(2)
	errSignal := make(chan struct{})
	// Error out on Job 1
	go func() {
		lb.mu.Lock() // lock the LB to prevent session cleanup
		_, err = lb.sessions[sess].Transcode(stubMetadata(sess, ffmpeg.P144p30fps16x9))
		assert.Equal(ErrTranscode, err)
		errSignal <- struct{}{}
		wg.Done()
	}()
	// Job 2 arrives when the session loop() has closed, but session isn't cleaned up yet
	go func() {
		<-errSignal
		_, err = lb.sessions[sess].Transcode(stubMetadata(sess, ffmpeg.P144p30fps16x9))
		assert.Equal(ErrTranscoderStopped, err)
		wg.Done()
	}()
	assert.True(wgWait(wg))
	lb.mu.Unlock() // unlock for cleanup
}

func TestLB_LoadAssignment(t *testing.T) {

	// Property: Overall load only increases after first segment

	// Test :    Randomize profiles to randomize "load" **per segment**;
	//           Subsequent segments should ignore subsequent load costs.

	assert := assert.New(t)
	lb := NewLoadBalancingTranscoder("0,1,2,3,4", newStubTranscoder).(*LoadBalancingTranscoder)
	sessions := []string{"a", "b", "c", "d", "e"}
	profiles := []ffmpeg.VideoProfile{}
	for _, v := range ffmpeg.VideoProfileLookup {
		profiles = append(profiles, v)
	}

	rapid.Check(t, func(t *rapid.T) {
		sessIdx := rapid.IntRange(0, len(sessions)-1).Draw(t, "sess").(int)
		sessName := sessions[sessIdx]
		profs := shuffleProfiles(t)
		_, exists := lb.sessions[sessName]
		totalLoad := accumLoad(lb)
		lb.Transcode(stubMetadata(sessName, profs...))
		if exists {
			assert.Equal(totalLoad, accumLoad(lb))
		} else {
			assert.Contains(lb.sessions, sessName, "Transcoder did not establish session")
			assert.Equal(totalLoad+calculateCost(profs), accumLoad(lb))
		}
	})
}

func TestLB_SessionCancel(t *testing.T) {
	// One-off test for session cancellation to work around thread safety issues
	stubCtx, stubCancel := context.WithCancel(context.Background())
	ctxFunc := func() (context.Context, context.CancelFunc) { return stubCtx, stubCancel }

	sess := &transcoderSession{
		transcoder:  newStubTranscoder(""),
		sender:      make(chan *transcoderParams, 1),
		makeContext: ctxFunc,
	}

	wg := newWg(1)
	go func() {
		sess.loop()
		wg.Done()
	}()
	stubCancel()
	wgWait(wg)
	_, err := sess.Transcode(&SegTranscodingMetadata{})
	assert.Equal(t, ErrTranscoderBusy, err)
}

func TestLB_SessionConcurrency(t *testing.T) {

	stubCtx, stubCancel := context.WithCancel(context.Background())
	ctxFunc := func() (context.Context, context.CancelFunc) { return stubCtx, stubCancel }

	sess := &transcoderSession{
		transcoder:  newStubTranscoder(""),
		sender:      make(chan *transcoderParams, 1),
		makeContext: ctxFunc,
	}

	wg := newWg(1)
	go func() {
		sess.loop()
		wg.Done()
	}()

	iters := 100
	for i := 0; i < iters; i++ {
		if i == iters/2 {
			stubCancel()
		}
		wg.Add(1)
		go func() {
			sess.Transcode(&SegTranscodingMetadata{})
			wg.Done()
		}()
	}
	wgWait(wg)
}

func TestLB_ConcurrentSessionErrors(t *testing.T) {
	// Test session error counts under heavy concurrency

	// Race detector is sometimes slow enough to trigger timeouts
	// These numbers may need tweaking
	// (Or a separate non-race test with higher numbers)
	mainIter := 200
	innerIters := 40
	innerTimeout := 10 * time.Second
	mainTimeout := 20 * time.Second

	wg := newWg(mainIter)
	for j := 0; j < mainIter; j++ {
		go func() {
			defer wg.Done()
			transcoder := stubTranscoderWithProfiles(nil)
			transcoder.TranscodeFn = func() error {
				time.Sleep(10 * time.Millisecond)
				// return a random error to distinguish from session errors
				return ErrManifestID
			}
			sess := &transcoderSession{
				key:         "",
				transcoder:  transcoder,
				sender:      make(chan *transcoderParams, 1),
				makeContext: transcodeLoopContext,
			}

			// Sanity check that we actually exit from the transcode loop
			wg.Add(1)
			go func() {
				sess.loop()
				wg.Done()
			}()

			errCh := make(chan int)
			for i := 0; i < innerIters; i++ {
				go func(ch chan int) {
					_, err := sess.Transcode(&SegTranscodingMetadata{})
					if err == nil {
						ch <- 0
					} else {
						ch <- 1
						if err != ErrTranscoderBusy &&
							err != ErrTranscoderStopped &&
							err != ErrManifestID {
							t.Error("Unexpected error from transcoder ", err)
						}
					}
				}(errCh)
			}

			errCount := 0
			timeout := time.After(innerTimeout)
			for i := 0; i < innerIters; i++ {
				select {
				case k := <-errCh:
					errCount += k
				case <-timeout:
					t.Error("Stopped because of timeout")
					break
				}
			}
			assert.Equal(t, innerIters, errCount)
		}()
	}
	assert.True(t, wgWait2(wg, mainTimeout), "Time expired")
}

func accumLoad(lb *LoadBalancingTranscoder) int {
	totalLoad := 0
	for _, v := range lb.load {
		totalLoad += v
	}
	return totalLoad
}

func shuffleProfiles(t *rapid.T) []ffmpeg.VideoProfile {
	// fisher-yates shuffle. or an approximation thereof. (should test this)
	profiles := []ffmpeg.VideoProfile{}
	for _, v := range ffmpeg.VideoProfileLookup {
		profiles = append(profiles, v)
	}
	for i := len(profiles) - 1; i >= 1; i-- {
		j := rapid.IntRange(0, i).Draw(t, "j").(int)
		profiles[i], profiles[j] = profiles[j], profiles[i]
	}
	nbProfs := rapid.IntRange(1, len(profiles)-1).Draw(t, "nbProfs").(int)
	return profiles[:nbProfs]
}

type machineState struct {
	segs     int
	load     int
	cancel   *context.CancelFunc
	profiles []ffmpeg.VideoProfile
}

// Description of a rapid state machine for testing the load balancer
type lbMachine struct {
	lb *LoadBalancingTranscoder

	// Our model: various bits of internal state we want to synchronize with
	states    map[string]*machineState
	totalLoad int
}

func (m *lbMachine) randomSession(t *rapid.T) (string, *machineState) {
	// Create an internal session
	// Doesn't actually create it on the transcoder - should we?
	sessName := strconv.Itoa(rapid.IntRange(0, 25).Draw(t, "sess").(int))

	// Create internal state if necessary
	state, exists := m.states[sessName]
	if exists {
		return sessName, state
	}

	profs := shuffleProfiles(t)
	state = &machineState{profiles: profs}
	m.states[sessName] = state
	m.totalLoad += calculateCost(profs)

	return sessName, state
}

func (m *lbMachine) Init(t *rapid.T) {
	var devices []string
	nbDevices := rapid.IntRange(1, 10).Draw(t, "nbDevices").(int)
	for i := 0; i < nbDevices; i++ {
		devices = append(devices, strconv.Itoa(i))
	}

	m.lb = NewLoadBalancingTranscoder(strings.Join(devices, ","), newStubTranscoder).(*LoadBalancingTranscoder)
	m.states = make(map[string]*machineState)

	assert.Equal(t, devices, m.lb.transcoders) // sanity check
}

func (m *lbMachine) TranscodeOK(t *rapid.T) {
	// Run a successful segment transcode

	sessName, state := m.randomSession(t)
	_, err := m.lb.Transcode(stubMetadata(sessName, state.profiles...))

	assert.Nil(t, err)

	// Update internal state
	state.segs++
}

func (m *lbMachine) TranscodeError(t *rapid.T) {
	// Run a failed segment transcode

	sessName, state := m.randomSession(t)

	// If session doesn't already exist, create it by forcing a transcode
	_, ok := m.lb.sessions[sessName]
	if !ok {
		_, err := m.lb.Transcode(stubMetadata(sessName, state.profiles...))
		assert.Nil(t, err)
		require.Contains(t, m.lb.sessions, sessName)
	}
	transcoder, ok := m.lb.sessions[sessName].transcoder.(*StubTranscoder)
	require.True(t, ok, "Transcoder was not a StubTranscoder")
	require.Equal(t, 0, transcoder.StoppedCount) // Sanity check

	transcoder.FailTranscode = true
	_, err := m.lb.Transcode(stubMetadata(sessName, state.profiles...))
	assert.Equal(t, ErrTranscode, err)

	m.totalLoad -= calculateCost(state.profiles)
	delete(m.states, sessName)

	// Give time for the transcode and session to stop
	for retryCount := 0; retryCount < 100; retryCount++ {
		m.lb.mu.Lock()
		_, exists := m.lb.sessions[sessName]
		m.lb.mu.Unlock()
		if !exists {
			break
		}
		time.Sleep(1 * time.Millisecond)
	}

	// Confirm things stopped
	assert.NotContains(t, m.lb.sessions, sessName, "Transcoder was not removed")
	assert.Equal(t, 1, transcoder.StoppedCount, "Transcoder did not stop")
}

func (m *lbMachine) Check(t *rapid.T) {
	assert := assert.New(t)

	m.lb.mu.RLock()
	defer m.lb.mu.RUnlock()
	assert.Equal(len(m.states), len(m.lb.sessions), "Mismatch in number of sessions")
	assert.Equal(m.totalLoad, accumLoad(m.lb), "Mismatch in load calculation")

	for name, sess := range m.lb.sessions {
		transcoder, ok := sess.transcoder.(*StubTranscoder)
		require.True(t, ok, "Not a stub transcoder!")
		require.Contains(t, m.states, name)
		assert.Equal(m.states[name].segs, transcoder.SegCount)
	}
}

func TestLB_Machine(t *testing.T) {
	rapid.Check(t, rapid.Run(&lbMachine{}))
}
<|endoftext|>


<commit_before>package jsonpath_test

import (
	. "github.com/cthulhu/jsonpath"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Jpath", func() {
	Context("Simple key value", func() {
		It("generates json", func() {
			in := map[string]string{"key": "value"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"key":"value"}`)))
		})
	})
	Context("Simple embeddens key value", func() {
		It("generates json", func() {
			in := map[string]string{"price.value": "100.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"price":{"value":"100.00"}}`)))
		})
	})
	Context("Long embeddens key value", func() {
		It("generates json", func() {
			in := map[string]string{"price.value1.value2": "100.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"price":{"value1":{"value2":"100.00"}}}`)))
		})
	})
	Context("Simple embeddens few key values", func() {
		It("generates json", func() {
			in := map[string]string{"price.value": "100.00", "price.currency": "EU"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"price":{"currency":"EU","value":"100.00"}}`)))
		})
	})
	Context("Simple embeddens few key values different levels", func() {
		It("generates json", func() {
			in := map[string]string{"price.value": "100.00", "price.currency": "EU", "shipping.value": "99.00", "shipping.currency": "UA"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"price":{"currency":"EU","value":"100.00"},"shipping":{"currency":"UA","value":"99.00"}}`)))
		})
	})
	Context("Simple embeddens few key values and array with one value", func() {
		It("generates json", func() {
			in := map[string]string{"prices.0": "100.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"prices":["100.00"]}`)))
		})
	})
	Context("Simple embeddens few key values and array with two values", func() {
		It("generates json", func() {
			in := map[string]string{"prices.1": "100.00", "prices.0": "10.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"prices":["10.00","100.00"]}`)))
		})
	})
	Context("Simple embeddens few key values and array with three values", func() {
		It("generates json", func() {
			in := map[string]string{"prices.2": "100.00", "prices.0": "10.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"prices":["10.00",null,"100.00"]}`)))
		})
	})
	Context("Simple embeddens few key values and array with shipping", func() {
		It("generates json", func() {
			in := map[string]string{"price.value": "100.00", "price.currency": "EU", "shipping.0.country": "GB", "shipping.0.service": "Standart shipping", "shipping.0.price.value": "33", "shipping.0.price.curency": "GBP"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"price":{"currency":"EU","value":"100.00"},"shipping":[{"country":"GB","price":{"curency":"GBP","value":"33"},"service":"Standart shipping"}]}`)))
		})
	})

	Context("Simple embeddens key value", func() {
		It("generates json", func() {
			in := map[string]string{"0.value": "100.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`[{"value":"100.00"}]`)))
		})
	})
	Context("Simple embeddens key value with num", func() {
		It("generates json", func() {
			in := map[string]string{"0.value.num()": "100.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`[{"value":100}]`)))
		})
	})
	Context("Simple embeddens key value with float", func() {
		It("generates json", func() {
			in := map[string]string{"0.value.num()": "100.12"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`[{"value":100.12}]`)))
		})
	})

})
<commit_msg>[+] Benchmarks<commit_after>package jsonpath_test

import (
	"encoding/json"
	"testing"

	. "github.com/cthulhu/jsonpath"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Jpath", func() {
	Context("Simple key value", func() {
		It("generates json", func() {
			in := map[string]string{"key": "value"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"key":"value"}`)))
		})
	})
	Context("Simple embeddens key value", func() {
		It("generates json", func() {
			in := map[string]string{"price.value": "100.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"price":{"value":"100.00"}}`)))
		})
	})
	Context("Long embeddens key value", func() {
		It("generates json", func() {
			in := map[string]string{"price.value1.value2": "100.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"price":{"value1":{"value2":"100.00"}}}`)))
		})
	})
	Context("Simple embeddens few key values", func() {
		It("generates json", func() {
			in := map[string]string{"price.value": "100.00", "price.currency": "EU"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"price":{"currency":"EU","value":"100.00"}}`)))
		})
	})
	Context("Simple embeddens few key values different levels", func() {
		It("generates json", func() {
			in := map[string]string{"price.value": "100.00", "price.currency": "EU", "shipping.value": "99.00", "shipping.currency": "UA"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"price":{"currency":"EU","value":"100.00"},"shipping":{"currency":"UA","value":"99.00"}}`)))
		})
	})
	Context("Simple embeddens few key values and array with one value", func() {
		It("generates json", func() {
			in := map[string]string{"prices.0": "100.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"prices":["100.00"]}`)))
		})
	})
	Context("Simple embeddens few key values and array with two values", func() {
		It("generates json", func() {
			in := map[string]string{"prices.1": "100.00", "prices.0": "10.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"prices":["10.00","100.00"]}`)))
		})
	})
	Context("Simple embeddens few key values and array with three values", func() {
		It("generates json", func() {
			in := map[string]string{"prices.2": "100.00", "prices.0": "10.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"prices":["10.00",null,"100.00"]}`)))
		})
	})
	Context("Simple embeddens few key values and array with shipping", func() {
		It("generates json", func() {
			in := map[string]string{"price.value": "100.00", "price.currency": "EU", "shipping.0.country": "GB", "shipping.0.service": "Standart shipping", "shipping.0.price.value": "33", "shipping.0.price.curency": "GBP"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`{"price":{"currency":"EU","value":"100.00"},"shipping":[{"country":"GB","price":{"curency":"GBP","value":"33"},"service":"Standart shipping"}]}`)))
		})
	})

	Context("Simple embeddens key value", func() {
		It("generates json", func() {
			in := map[string]string{"0.value": "100.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`[{"value":"100.00"}]`)))
		})
	})
	Context("Simple embeddens key value with num", func() {
		It("generates json", func() {
			in := map[string]string{"0.value.num()": "100.00"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`[{"value":100}]`)))
		})
	})
	Context("Simple embeddens key value with float", func() {
		It("generates json", func() {
			in := map[string]string{"0.value.num()": "100.12"}
			actual, err := Marshal(in)
			Expect(err).NotTo(HaveOccurred())
			Expect(actual).To(Equal([]byte(`[{"value":100.12}]`)))
		})
	})
})

func BenchmarkComplexJSONPathArray(b *testing.B) {
	in := map[string]string{"price.value": "100.00", "price.currency": "EU", "shipping.0.country": "GB", "shipping.0.service": "Standart shipping", "shipping.0.price.value": "33", "shipping.0.price.curency": "GBP"}
	for n := 0; n < b.N; n++ {
		Marshal(in)
	}
}
func BenchmarkSimpleJSONPathArrayWithNum(b *testing.B) {
	in := map[string]string{"0.value.num()": "100.12"}
	for n := 0; n < b.N; n++ {
		Marshal(in)
	}
}

func BenchmarkSimpleJSONPathSimple(b *testing.B) {
	in := map[string]string{"value": "100.12"}
	for n := 0; n < b.N; n++ {
		Marshal(in)
	}
}
func BenchmarkJSONNative(b *testing.B) {
	in := map[string]string{"0.value.num()": "100.12"}
	for n := 0; n < b.N; n++ {
		json.Marshal(in)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"

	st "honnef.co/go/structlayout"
)

func main() {
	log.SetFlags(0)
	var fields []st.Field
	if err := json.NewDecoder(os.Stdin).Decode(&fields); err != nil {
		log.Fatal(err)
	}
	if len(fields) == 0 {
		return
	}
	max := fields[len(fields)-1].End
	maxLength := len(fmt.Sprintf("%d", max))
	padding := strings.Repeat(" ", maxLength+2)
	format := fmt.Sprintf(" %%%dd ", maxLength)
	pos := int64(0)
	fmt.Println(padding + "+--------+")
	for _, field := range fields {
		name := field.Name + " " + field.Type
		if field.IsPadding {
			name = "padding"
		}
		// TODO calculate max size width
		fmt.Printf(format+"|        | <- %s\n", pos, name)
		fmt.Println(padding + "+--------+")

		if field.Size == 1 {
			pos++
			continue
		}

		if field.Size > 2 {
			fmt.Println(padding + "-........-")
			fmt.Println(padding + "+--------+")
		}
		pos += field.Size
		fmt.Printf(format+"|        |\n", pos-1)
		fmt.Println(padding + "+--------+")
	}
}
<commit_msg>pretty: add verbose flag to disable byte compaction<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"strings"

	st "honnef.co/go/structlayout"
)

var fVerbose bool

func init() {
	flag.BoolVar(&fVerbose, "v", false, "Do not compact consecutive bytes of fields")
}

func main() {
	log.SetFlags(0)
	flag.Parse()

	var fields []st.Field
	if err := json.NewDecoder(os.Stdin).Decode(&fields); err != nil {
		log.Fatal(err)
	}
	if len(fields) == 0 {
		return
	}
	max := fields[len(fields)-1].End
	maxLength := len(fmt.Sprintf("%d", max))
	padding := strings.Repeat(" ", maxLength+2)
	format := fmt.Sprintf(" %%%dd ", maxLength)
	pos := int64(0)
	fmt.Println(padding + "+--------+")
	for _, field := range fields {
		name := field.Name + " " + field.Type
		if field.IsPadding {
			name = "padding"
		}
		fmt.Printf(format+"|        | <- %s\n", pos, name)
		fmt.Println(padding + "+--------+")

		if fVerbose {
			for i := int64(0); i < field.Size-1; i++ {
				fmt.Printf(format+"|        |\n", pos+i+1)
				fmt.Println(padding + "+--------+")
			}
		} else {
			if field.Size > 2 {
				fmt.Println(padding + "-........-")
				fmt.Println(padding + "+--------+")
				fmt.Printf(format+"|        |\n", pos+field.Size-1)
				fmt.Println(padding + "+--------+")
			}
		}
		pos += field.Size
	}
}
<|endoftext|>


<commit_before>package flying_test

import (
	"os"

	"github.com/cloudfoundry-incubator/garden"
	"github.com/concourse/testflight/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pivotal-golang/lager/lagertest"

	gclient "github.com/cloudfoundry-incubator/garden/client"
	gconn "github.com/cloudfoundry-incubator/garden/client/connection"

	"testing"
)

var (
	flyBin  string
	tmpHome string

	gardenClient garden.Client

	// needss git, curl
	gitServerRootfs string
)

var atcURL = helpers.AtcURL()
var targetedConcourse = "testflight"

var _ = SynchronizedBeforeSuite(func() []byte {
	Eventually(helpers.ErrorPolling(atcURL)).ShouldNot(HaveOccurred())

	data, err := helpers.FirstNodeFlySetup(atcURL, targetedConcourse)
	Expect(err).NotTo(HaveOccurred())

	return data
}, func(data []byte) {
	var err error
	flyBin, tmpHome, err = helpers.AllNodeFlySetup(data)
	Expect(err).NotTo(HaveOccurred())

	client, err := helpers.AllNodeClientSetup(data)
	Expect(err).NotTo(HaveOccurred())

	workers, err := client.ListWorkers()
	Expect(err).NotTo(HaveOccurred())

	logger := lagertest.NewTestLogger("testflight")

	gLog := logger.Session("garden-connection")

	for _, w := range workers {
		gitServerRootfs = ""

		for _, r := range w.ResourceTypes {
			if r.Type == "git" {
				gitServerRootfs = r.Image
			}
		}

		if gitServerRootfs != "" {
			gardenClient = gclient.New(gconn.NewWithLogger("tcp", w.GardenAddr, gLog))
		}
	}

	if gitServerRootfs == "" {
		Fail("must have at least one worker that supports git and bosh-deployment resource types")
	}

})

var _ = SynchronizedAfterSuite(func() {
}, func() {
	os.RemoveAll(tmpHome)
})

func TestFlying(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Flying Suite")
}
<commit_msg>skip tagged workers in flying suite<commit_after>package flying_test

import (
	"os"

	"github.com/cloudfoundry-incubator/garden"
	"github.com/concourse/testflight/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pivotal-golang/lager/lagertest"

	gclient "github.com/cloudfoundry-incubator/garden/client"
	gconn "github.com/cloudfoundry-incubator/garden/client/connection"

	"testing"
)

var (
	flyBin  string
	tmpHome string

	gardenClient garden.Client

	// needss git, curl
	gitServerRootfs string
)

var atcURL = helpers.AtcURL()
var targetedConcourse = "testflight"

var _ = SynchronizedBeforeSuite(func() []byte {
	Eventually(helpers.ErrorPolling(atcURL)).ShouldNot(HaveOccurred())

	data, err := helpers.FirstNodeFlySetup(atcURL, targetedConcourse)
	Expect(err).NotTo(HaveOccurred())

	return data
}, func(data []byte) {
	var err error
	flyBin, tmpHome, err = helpers.AllNodeFlySetup(data)
	Expect(err).NotTo(HaveOccurred())

	client, err := helpers.AllNodeClientSetup(data)
	Expect(err).NotTo(HaveOccurred())

	workers, err := client.ListWorkers()
	Expect(err).NotTo(HaveOccurred())

	logger := lagertest.NewTestLogger("testflight")

	gLog := logger.Session("garden-connection")

	for _, w := range workers {
		if len(w.Tags) > 0 {
			continue
		}

		gitServerRootfs = ""

		for _, r := range w.ResourceTypes {
			if r.Type == "git" {
				gitServerRootfs = r.Image
			}
		}

		if gitServerRootfs != "" {
			gardenClient = gclient.New(gconn.NewWithLogger("tcp", w.GardenAddr, gLog))
		}
	}

	if gitServerRootfs == "" {
		Fail("must have at least one worker that supports git and bosh-deployment resource types")
	}

})

var _ = SynchronizedAfterSuite(func() {
}, func() {
	os.RemoveAll(tmpHome)
})

func TestFlying(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Flying Suite")
}
<|endoftext|>


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

package lease_test

import (
	"io"
	"testing"

	"github.com/googlecloudplatform/gcsfuse/lease"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

func TestFileLeaser(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

const limitBytes = 17

type FileLeaserTest struct {
	fl *lease.FileLeaser
}

var _ SetUpInterface = &FileLeaserTest{}

func init() { RegisterTestSuite(&FileLeaserTest{}) }

func (t *FileLeaserTest) SetUp(ti *TestInfo) {
	t.fl = lease.NewFileLeaser("", limitBytes)
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *FileLeaserTest) ReadWriteLeaseInitialState() {
	var n int
	var off int64
	var err error
	buf := make([]byte, 1024)

	// Create
	rwl, err := t.fl.NewFile()
	AssertEq(nil, err)

	// Size
	size, err := rwl.Size()
	AssertEq(nil, err)
	ExpectEq(0, size)

	// Seek
	off, err = rwl.Seek(0, 2)
	AssertEq(nil, err)
	ExpectEq(0, off)

	// Read
	n, err = rwl.Read(buf)
	ExpectEq(io.EOF, err)
	ExpectEq(0, n)

	// ReadAt
	n, err = rwl.ReadAt(buf, 0)
	ExpectEq(io.EOF, err)
	ExpectEq(0, n)
}

func (t *FileLeaserTest) ModifyThenObserveReadWriteLease() {
	var n int
	var off int64
	var size int64
	var err error
	buf := make([]byte, 1024)

	// Create
	rwl, err := t.fl.NewFile()
	AssertEq(nil, err)

	// Write, then check size and offset.
	n, err = rwl.Write([]byte("tacoburrito"))
	AssertEq(nil, err)
	ExpectEq(len("tacoburrito"), n)

	size, err = rwl.Size()
	AssertEq(nil, err)
	ExpectEq(len("tacoburrito"), size)

	off, err = rwl.Seek(0, 1)
	AssertEq(nil, err)
	ExpectEq(len("tacoburrito"), off)

	// Pwrite, then check size.
	n, err = rwl.WriteAt([]byte("enchilada"), 4)
	AssertEq(nil, err)
	ExpectEq(len("enchilada"), n)

	size, err = rwl.Size()
	AssertEq(nil, err)
	ExpectEq(len("tacoenchilada"), size)

	// Truncate downward, then check size.
	err = rwl.Truncate(4)
	AssertEq(nil, err)

	size, err = rwl.Size()
	AssertEq(nil, err)
	ExpectEq(len("taco"), size)

	// Seek, then read everything.
	off, err = rwl.Seek(0, 0)
	AssertEq(nil, err)
	ExpectEq(0, off)

	n, err = rwl.Read(buf)
	ExpectThat(err, AnyOf(nil, io.EOF))
	ExpectEq("taco", string(buf[0:n]))
}

func (t *FileLeaserTest) DowngradeThenObserve() {
	var n int
	var off int64
	var size int64
	var err error
	buf := make([]byte, 1024)

	// Create and write some data.
	rwl, err := t.fl.NewFile()
	AssertEq(nil, err)

	n, err = rwl.Write([]byte("taco"))
	AssertEq(nil, err)

	// Downgrade.
	rl, err := rwl.Downgrade()
	AssertEq(nil, err)

	// Interacting with the read/write lease should no longer work.
	_, err = rwl.Read(buf)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.Write(buf)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.Seek(0, 0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.ReadAt(buf, 0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.WriteAt(buf, 0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	err = rwl.Truncate(0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.Size()
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.Downgrade()
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	// Observing via the read lease should work fine.
	size, err = rl.Size()
	AssertEq(nil, err)
	ExpectEq(len("taco"), size)

	off, err = rl.Seek(-4, 2)
	AssertEq(nil, err)
	ExpectEq(0, off)

	n, err = rl.Read(buf)
	ExpectThat(err, AnyOf(nil, io.EOF))
	ExpectEq("taco", string(buf[0:n]))

	n, err = rl.ReadAt(buf[0:2], 1)
	AssertEq(nil, err)
	ExpectEq("ac", string(buf[0:2]))
}

func (t *FileLeaserTest) DowngradeThenUpgradeThenObserve() {
	var n int
	var off int64
	var size int64
	var err error
	buf := make([]byte, 1024)

	// Create and write some data.
	rwl, err := t.fl.NewFile()
	AssertEq(nil, err)

	n, err = rwl.Write([]byte("taco"))
	AssertEq(nil, err)

	// Downgrade.
	rl, err := rwl.Downgrade()
	AssertEq(nil, err)

	// Upgrade again.
	rwl = rl.Upgrade()
	AssertNe(nil, rwl)

	// Interacting with the read lease should no longer work.
	_, err = rl.Read(buf)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rl.Seek(0, 0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rl.ReadAt(buf, 0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rl.Size()
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	tmp := rl.Upgrade()
	ExpectEq(nil, tmp)

	// Calling Revoke should cause nothing nasty to happen.
	rl.Revoke()

	// Observing via the new read/write lease should work fine.
	size, err = rwl.Size()
	AssertEq(nil, err)
	ExpectEq(len("taco"), size)

	off, err = rwl.Seek(-4, 2)
	AssertEq(nil, err)
	ExpectEq(0, off)

	n, err = rwl.Read(buf)
	ExpectThat(err, AnyOf(nil, io.EOF))
	ExpectEq("taco", string(buf[0:n]))

	n, err = rwl.ReadAt(buf[0:2], 1)
	AssertEq(nil, err)
	ExpectEq("ac", string(buf[0:2]))
}

func (t *FileLeaserTest) DowngradeAboveCapacity() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) WriteCausesEviction() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) WriteAtCausesEviction() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) TruncateCausesEviction() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) EvictionIsLRU() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) NothingAvailableToEvict() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) RevokeVoluntarily() {
	// TODO(jacobsa): Test that methods return RevokedError and that capacity in
	// the leaser is freed up.
	AssertFalse(true, "TODO")
}
<commit_msg>Expanded a test name.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package lease_test

import (
	"io"
	"testing"

	"github.com/googlecloudplatform/gcsfuse/lease"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

func TestFileLeaser(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

const limitBytes = 17

type FileLeaserTest struct {
	fl *lease.FileLeaser
}

var _ SetUpInterface = &FileLeaserTest{}

func init() { RegisterTestSuite(&FileLeaserTest{}) }

func (t *FileLeaserTest) SetUp(ti *TestInfo) {
	t.fl = lease.NewFileLeaser("", limitBytes)
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *FileLeaserTest) ReadWriteLeaseInitialState() {
	var n int
	var off int64
	var err error
	buf := make([]byte, 1024)

	// Create
	rwl, err := t.fl.NewFile()
	AssertEq(nil, err)

	// Size
	size, err := rwl.Size()
	AssertEq(nil, err)
	ExpectEq(0, size)

	// Seek
	off, err = rwl.Seek(0, 2)
	AssertEq(nil, err)
	ExpectEq(0, off)

	// Read
	n, err = rwl.Read(buf)
	ExpectEq(io.EOF, err)
	ExpectEq(0, n)

	// ReadAt
	n, err = rwl.ReadAt(buf, 0)
	ExpectEq(io.EOF, err)
	ExpectEq(0, n)
}

func (t *FileLeaserTest) ModifyThenObserveReadWriteLease() {
	var n int
	var off int64
	var size int64
	var err error
	buf := make([]byte, 1024)

	// Create
	rwl, err := t.fl.NewFile()
	AssertEq(nil, err)

	// Write, then check size and offset.
	n, err = rwl.Write([]byte("tacoburrito"))
	AssertEq(nil, err)
	ExpectEq(len("tacoburrito"), n)

	size, err = rwl.Size()
	AssertEq(nil, err)
	ExpectEq(len("tacoburrito"), size)

	off, err = rwl.Seek(0, 1)
	AssertEq(nil, err)
	ExpectEq(len("tacoburrito"), off)

	// Pwrite, then check size.
	n, err = rwl.WriteAt([]byte("enchilada"), 4)
	AssertEq(nil, err)
	ExpectEq(len("enchilada"), n)

	size, err = rwl.Size()
	AssertEq(nil, err)
	ExpectEq(len("tacoenchilada"), size)

	// Truncate downward, then check size.
	err = rwl.Truncate(4)
	AssertEq(nil, err)

	size, err = rwl.Size()
	AssertEq(nil, err)
	ExpectEq(len("taco"), size)

	// Seek, then read everything.
	off, err = rwl.Seek(0, 0)
	AssertEq(nil, err)
	ExpectEq(0, off)

	n, err = rwl.Read(buf)
	ExpectThat(err, AnyOf(nil, io.EOF))
	ExpectEq("taco", string(buf[0:n]))
}

func (t *FileLeaserTest) DowngradeThenObserve() {
	var n int
	var off int64
	var size int64
	var err error
	buf := make([]byte, 1024)

	// Create and write some data.
	rwl, err := t.fl.NewFile()
	AssertEq(nil, err)

	n, err = rwl.Write([]byte("taco"))
	AssertEq(nil, err)

	// Downgrade.
	rl, err := rwl.Downgrade()
	AssertEq(nil, err)

	// Interacting with the read/write lease should no longer work.
	_, err = rwl.Read(buf)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.Write(buf)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.Seek(0, 0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.ReadAt(buf, 0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.WriteAt(buf, 0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	err = rwl.Truncate(0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.Size()
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rwl.Downgrade()
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	// Observing via the read lease should work fine.
	size, err = rl.Size()
	AssertEq(nil, err)
	ExpectEq(len("taco"), size)

	off, err = rl.Seek(-4, 2)
	AssertEq(nil, err)
	ExpectEq(0, off)

	n, err = rl.Read(buf)
	ExpectThat(err, AnyOf(nil, io.EOF))
	ExpectEq("taco", string(buf[0:n]))

	n, err = rl.ReadAt(buf[0:2], 1)
	AssertEq(nil, err)
	ExpectEq("ac", string(buf[0:2]))
}

func (t *FileLeaserTest) DowngradeThenUpgradeThenObserve() {
	var n int
	var off int64
	var size int64
	var err error
	buf := make([]byte, 1024)

	// Create and write some data.
	rwl, err := t.fl.NewFile()
	AssertEq(nil, err)

	n, err = rwl.Write([]byte("taco"))
	AssertEq(nil, err)

	// Downgrade.
	rl, err := rwl.Downgrade()
	AssertEq(nil, err)

	// Upgrade again.
	rwl = rl.Upgrade()
	AssertNe(nil, rwl)

	// Interacting with the read lease should no longer work.
	_, err = rl.Read(buf)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rl.Seek(0, 0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rl.ReadAt(buf, 0)
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	_, err = rl.Size()
	ExpectThat(err, HasSameTypeAs(&lease.RevokedError{}))

	tmp := rl.Upgrade()
	ExpectEq(nil, tmp)

	// Calling Revoke should cause nothing nasty to happen.
	rl.Revoke()

	// Observing via the new read/write lease should work fine.
	size, err = rwl.Size()
	AssertEq(nil, err)
	ExpectEq(len("taco"), size)

	off, err = rwl.Seek(-4, 2)
	AssertEq(nil, err)
	ExpectEq(0, off)

	n, err = rwl.Read(buf)
	ExpectThat(err, AnyOf(nil, io.EOF))
	ExpectEq("taco", string(buf[0:n]))

	n, err = rwl.ReadAt(buf[0:2], 1)
	AssertEq(nil, err)
	ExpectEq("ac", string(buf[0:2]))
}

func (t *FileLeaserTest) DowngradeFileWhoseSizeIsAboveCapacity() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) WriteCausesEviction() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) WriteAtCausesEviction() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) TruncateCausesEviction() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) EvictionIsLRU() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) NothingAvailableToEvict() {
	AssertFalse(true, "TODO")
}

func (t *FileLeaserTest) RevokeVoluntarily() {
	// TODO(jacobsa): Test that methods return RevokedError and that capacity in
	// the leaser is freed up.
	AssertFalse(true, "TODO")
}
<|endoftext|>


<commit_before>package policies

import (
	"github.com/keel-hq/keel/types"
)

// GetTriggerPolicy - checks for trigger label, if not set - returns
// default trigger type
func GetTriggerPolicy(labels map[string]string) types.TriggerType {
	trigger, ok := labels[types.KeelTriggerLabel]
	if ok {
		return types.ParseTrigger(trigger)
	}
	return types.TriggerTypeDefault
}
<commit_msg>trigger type from annotations<commit_after>package policies

import (
	"github.com/keel-hq/keel/types"
)

// GetTriggerPolicy - checks for trigger label, if not set - returns
// default trigger type
func GetTriggerPolicy(labels map[string]string, annotations map[string]string) types.TriggerType {

	triggerAnn, ok := annotations[types.KeelTriggerLabel]
	if ok {
		return types.ParseTrigger(triggerAnn)
	}

	// checking labels
	trigger, ok := labels[types.KeelTriggerLabel]
	if ok {
		return types.ParseTrigger(trigger)
	}

	return types.TriggerTypeDefault
}
<|endoftext|>


<commit_before>package keypaircommands

import (
	"flag"
	"net/http"
	"testing"

	"github.com/codegangsta/cli"
	"github.com/jrperritt/rack/handler"
	th "github.com/rackspace/gophercloud/testhelper"
	"github.com/rackspace/gophercloud/testhelper/client"
)

func TestDeleteContext(t *testing.T) {
	app := cli.NewApp()
	flagset := flag.NewFlagSet("flags", 1)
	c := cli.NewContext(app, flagset, nil)
	cmd := &commandDelete{
		Ctx: &handler.Context{
			CLIContext: c,
		},
	}
	expected := cmd.Ctx
	actual := cmd.Context()
	th.AssertDeepEquals(t, expected, actual)
}

func TestDeleteKeys(t *testing.T) {
	cmd := &commandDelete{}
	expected := keysDelete
	actual := cmd.Keys()
	th.AssertDeepEquals(t, expected, actual)
}

func TestDeleteServiceClientType(t *testing.T) {
	cmd := &commandDelete{}
	expected := serviceClientType
	actual := cmd.ServiceClientType()
	th.AssertEquals(t, expected, actual)
}

func TestDeleteHandleFlags(t *testing.T) {
	cmd := &commandDelete{
		Ctx: &handler.Context{},
	}
	expected := &handler.Resource{
		Params: &paramsDelete{},
	}
	actual := &handler.Resource{}
	err := cmd.HandleFlags(actual)
	th.AssertNoErr(t, err)
	th.AssertDeepEquals(t, expected.Params.(*paramsDelete), actual.Params.(*paramsDelete))
}

func TestDeleteHandlePipe(t *testing.T) {
	cmd := &commandDelete{}
	expected := &handler.Resource{
		Params: &paramsDelete{
			keypair: "kepair1",
		},
	}
	actual := &handler.Resource{
		Params: &paramsDelete{},
	}
	err := cmd.HandlePipe(actual, "kepair1")
	th.AssertNoErr(t, err)
	th.AssertEquals(t, expected.Params.(*paramsDelete).keypair, actual.Params.(*paramsDelete).keypair)
}

func TestDeleteHandleSingle(t *testing.T) {
	app := cli.NewApp()
	flagset := flag.NewFlagSet("flags", 1)
	flagset.String("name", "", "")
	flagset.Set("name", "keypair1Name")
	c := cli.NewContext(app, flagset, nil)
	cmd := &commandDelete{
		Ctx: &handler.Context{
			CLIContext: c,
		},
	}
	expected := &handler.Resource{
		Params: &paramsDelete{
			keypair: "keypair1Name",
		},
	}
	actual := &handler.Resource{
		Params: &paramsDelete{},
	}
	err := cmd.HandleSingle(actual)
	th.AssertNoErr(t, err)
	th.AssertEquals(t, expected.Params.(*paramsDelete).keypair, actual.Params.(*paramsDelete).keypair)
}

func TestDeleteExecute(t *testing.T) {
	th.SetupHTTP()
	defer th.TeardownHTTP()
	th.Mux.HandleFunc("/os-keypairs/keypair1Name", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusAccepted)
	})
	cmd := &commandDelete{
		Ctx: &handler.Context{
			ServiceClient: client.ServiceClient(),
		},
	}
	actual := &handler.Resource{
		Params: &paramsDelete{
			keypair: "keypair1Name",
		},
	}
	cmd.Execute(actual)
	th.AssertNoErr(t, actual.Err)
}

func TestDeleteStdinField(t *testing.T) {
	cmd := &commandDelete{}
	expected := "name"
	actual := cmd.StdinField()
	th.AssertEquals(t, expected, actual)
}
<commit_msg>typo fix<commit_after>package keypaircommands

import (
	"flag"
	"net/http"
	"testing"

	"github.com/codegangsta/cli"
	"github.com/jrperritt/rack/handler"
	th "github.com/rackspace/gophercloud/testhelper"
	"github.com/rackspace/gophercloud/testhelper/client"
)

func TestDeleteContext(t *testing.T) {
	app := cli.NewApp()
	flagset := flag.NewFlagSet("flags", 1)
	c := cli.NewContext(app, flagset, nil)
	cmd := &commandDelete{
		Ctx: &handler.Context{
			CLIContext: c,
		},
	}
	expected := cmd.Ctx
	actual := cmd.Context()
	th.AssertDeepEquals(t, expected, actual)
}

func TestDeleteKeys(t *testing.T) {
	cmd := &commandDelete{}
	expected := keysDelete
	actual := cmd.Keys()
	th.AssertDeepEquals(t, expected, actual)
}

func TestDeleteServiceClientType(t *testing.T) {
	cmd := &commandDelete{}
	expected := serviceClientType
	actual := cmd.ServiceClientType()
	th.AssertEquals(t, expected, actual)
}

func TestDeleteHandleFlags(t *testing.T) {
	cmd := &commandDelete{
		Ctx: &handler.Context{},
	}
	expected := &handler.Resource{
		Params: &paramsDelete{},
	}
	actual := &handler.Resource{}
	err := cmd.HandleFlags(actual)
	th.AssertNoErr(t, err)
	th.AssertDeepEquals(t, expected.Params.(*paramsDelete), actual.Params.(*paramsDelete))
}

func TestDeleteHandlePipe(t *testing.T) {
	cmd := &commandDelete{}
	expected := &handler.Resource{
		Params: &paramsDelete{
			keypair: "keypair1",
		},
	}
	actual := &handler.Resource{
		Params: &paramsDelete{},
	}
	err := cmd.HandlePipe(actual, "keypair1")
	th.AssertNoErr(t, err)
	th.AssertEquals(t, expected.Params.(*paramsDelete).keypair, actual.Params.(*paramsDelete).keypair)
}

func TestDeleteHandleSingle(t *testing.T) {
	app := cli.NewApp()
	flagset := flag.NewFlagSet("flags", 1)
	flagset.String("name", "", "")
	flagset.Set("name", "keypair1Name")
	c := cli.NewContext(app, flagset, nil)
	cmd := &commandDelete{
		Ctx: &handler.Context{
			CLIContext: c,
		},
	}
	expected := &handler.Resource{
		Params: &paramsDelete{
			keypair: "keypair1Name",
		},
	}
	actual := &handler.Resource{
		Params: &paramsDelete{},
	}
	err := cmd.HandleSingle(actual)
	th.AssertNoErr(t, err)
	th.AssertEquals(t, expected.Params.(*paramsDelete).keypair, actual.Params.(*paramsDelete).keypair)
}

func TestDeleteExecute(t *testing.T) {
	th.SetupHTTP()
	defer th.TeardownHTTP()
	th.Mux.HandleFunc("/os-keypairs/keypair1Name", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusAccepted)
	})
	cmd := &commandDelete{
		Ctx: &handler.Context{
			ServiceClient: client.ServiceClient(),
		},
	}
	actual := &handler.Resource{
		Params: &paramsDelete{
			keypair: "keypair1Name",
		},
	}
	cmd.Execute(actual)
	th.AssertNoErr(t, actual.Err)
}

func TestDeleteStdinField(t *testing.T) {
	cmd := &commandDelete{}
	expected := "name"
	actual := cmd.StdinField()
	th.AssertEquals(t, expected, actual)
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package replicator

import (
	"fmt"
	"math/rand"
	"net"
	"strconv"
	"strings"
	"sync"

	"github.com/uber/cherami-server/common"
	"github.com/uber/cherami-server/common/configure"
	"github.com/uber/cherami-thrift/.generated/go/replicator"
	"github.com/uber/cherami-thrift/.generated/go/shared"

	"github.com/uber-common/bark"
	"github.com/uber/tchannel-go"
	"github.com/uber/tchannel-go/thrift"
)

type (

	// ClientFactory manage discovery of remote replicator
	// and make sure we only allocate new thrift clients when necessary
	ClientFactory interface {
		// GetReplicatorClient gets the thrift client for making calls to remote Replicator
		GetReplicatorClient(zone string) (replicator.TChanReplicator, error)

		// SetTChannel sets the tchannel used by thrift client
		SetTChannel(ch *tchannel.Channel)

		// get hosts for a given deployment
		GetHostsForDeployment(deployment string) []string

		// set hosts for a given deployment
		UpdateHostsForDeployment(deployment string, hosts []string)

		// get hosts for all deployments
		GetHostsForAllDeployment() map[string][]string
	}

	// replicatorClientCache is the cache to hold a client for local replicator instance
	replicatorClienstCache struct {
	}

	// repCltFactoryImpl implements of ReplicatorClientFactory
	repCltFactoryImpl struct {
		AppConfig         configure.CommonAppConfig
		logger            bark.Logger
		ch                *tchannel.Channel
		deploymentClients map[string][]replicator.TChanReplicator
		lk                sync.RWMutex

		hosts  map[string][]string
		hostLk sync.RWMutex
	}
)

// NewReplicatorClientFactory instantiates a ReplicatorClientFactory object
func NewReplicatorClientFactory(config configure.CommonAppConfig, logger bark.Logger, hosts map[string][]string) ClientFactory {
	factory := &repCltFactoryImpl{
		AppConfig:         config,
		logger:            logger,
		deploymentClients: make(map[string][]replicator.TChanReplicator),
		hosts:             hosts,
	}
	return factory
}

// GetReplicatorClient gets the thrift client for making calls to remote Replicator
func (f *repCltFactoryImpl) GetReplicatorClient(zone string) (replicator.TChanReplicator, error) {
	_, tenancy := common.GetLocalClusterInfo(strings.ToLower(f.AppConfig.GetServiceConfig(common.ReplicatorServiceName).GetDeploymentName()))
	deployment := fmt.Sprintf("%v_%v", tenancy, zone)
	var client replicator.TChanReplicator

	// take a reader lock to see if we already have a valid client
	f.lk.RLock()
	if clients, ok := f.deploymentClients[deployment]; ok && len(clients) > 0 {
		// randomly select a client and return
		// TODO health check and proper load routing
		client = clients[rand.Intn(len(clients))]
	}
	f.lk.RUnlock()
	if client != nil {
		return client, nil
	}

	if !f.hasHostsForDeployment(deployment) {
		err := &shared.BadRequestError{Message: fmt.Sprintf("Deployment [%v] is not configured", deployment)}
		f.logger.WithFields(bark.Fields{common.TagErr: err, common.TagDeploymentName: deployment}).Error("Deployment is not configured")
		return nil, err
	}

	if f.ch == nil {
		err := &shared.BadRequestError{Message: "TChannel not set"}
		f.logger.WithFields(bark.Fields{common.TagErr: err, common.TagDeploymentName: deployment}).Error("TChannel not set")
		return nil, err
	}

	// take a writer lock to create replicator clients and update cache
	f.lk.Lock()
	defer f.lk.Unlock()

	// Check again to see if clients were already created by someone else
	if clients, ok := f.deploymentClients[deployment]; ok && len(clients) > 0 {
		return clients[rand.Intn(len(clients))], nil
	}

	hosts := f.GetHostsForDeployment(deployment)
	clients := make([]replicator.TChanReplicator, 0, len(hosts))

	for _, host := range hosts {
		clients = append(clients, replicator.NewTChanReplicatorClient(thrift.NewClient(
			f.ch,
			common.ReplicatorServiceName,
			&thrift.ClientOptions{HostPort: net.JoinHostPort(host, strconv.Itoa(f.AppConfig.GetServiceConfig(common.ReplicatorServiceName).GetPort()))},
		)))
	}
	f.deploymentClients[deployment] = clients

	return clients[rand.Intn(len(clients))], nil
}

// SetTChannel sets the tchannel used by thrift client
func (f *repCltFactoryImpl) SetTChannel(ch *tchannel.Channel) {
	f.ch = ch
}

func (f *repCltFactoryImpl) hasHostsForDeployment(deployment string) bool {
	f.hostLk.RLock()
	defer f.hostLk.RUnlock()

	_, hasHosts := f.hosts[deployment]
	return hasHosts
}

func (f *repCltFactoryImpl) GetHostsForDeployment(deployment string) []string {
	f.hostLk.RLock()
	defer f.hostLk.RUnlock()

	return f.hosts[deployment]
}

func (f *repCltFactoryImpl) GetHostsForAllDeployment() map[string][]string {
	f.hostLk.RLock()
	defer f.hostLk.RUnlock()

	return f.hosts
}

func (f *repCltFactoryImpl) UpdateHostsForDeployment(deployment string, hosts []string) {
	f.hostLk.Lock()
	defer f.hostLk.Unlock()

	f.hosts[deployment] = hosts
}
<commit_msg>replicator: clear the client cache when host list changes (#297)<commit_after>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package replicator

import (
	"fmt"
	"math/rand"
	"net"
	"strconv"
	"strings"
	"sync"

	"github.com/uber/cherami-server/common"
	"github.com/uber/cherami-server/common/configure"
	"github.com/uber/cherami-thrift/.generated/go/replicator"
	"github.com/uber/cherami-thrift/.generated/go/shared"

	"github.com/uber-common/bark"
	"github.com/uber/tchannel-go"
	"github.com/uber/tchannel-go/thrift"
)

type (

	// ClientFactory manage discovery of remote replicator
	// and make sure we only allocate new thrift clients when necessary
	ClientFactory interface {
		// GetReplicatorClient gets the thrift client for making calls to remote Replicator
		GetReplicatorClient(zone string) (replicator.TChanReplicator, error)

		// SetTChannel sets the tchannel used by thrift client
		SetTChannel(ch *tchannel.Channel)

		// get hosts for a given deployment
		GetHostsForDeployment(deployment string) []string

		// set hosts for a given deployment
		UpdateHostsForDeployment(deployment string, hosts []string)

		// get hosts for all deployments
		GetHostsForAllDeployment() map[string][]string
	}

	// replicatorClientCache is the cache to hold a client for local replicator instance
	replicatorClienstCache struct {
	}

	// repCltFactoryImpl implements of ReplicatorClientFactory
	repCltFactoryImpl struct {
		AppConfig configure.CommonAppConfig
		logger    bark.Logger
		ch        *tchannel.Channel

		deploymentClients   map[string][]replicator.TChanReplicator
		deploymentClientsLk sync.RWMutex

		hosts  map[string][]string
		hostLk sync.RWMutex
	}
)

// NewReplicatorClientFactory instantiates a ReplicatorClientFactory object
func NewReplicatorClientFactory(config configure.CommonAppConfig, logger bark.Logger, hosts map[string][]string) ClientFactory {
	factory := &repCltFactoryImpl{
		AppConfig:         config,
		logger:            logger,
		deploymentClients: make(map[string][]replicator.TChanReplicator),
		hosts:             hosts,
	}
	return factory
}

// GetReplicatorClient gets the thrift client for making calls to remote Replicator
func (f *repCltFactoryImpl) GetReplicatorClient(zone string) (replicator.TChanReplicator, error) {
	_, tenancy := common.GetLocalClusterInfo(strings.ToLower(f.AppConfig.GetServiceConfig(common.ReplicatorServiceName).GetDeploymentName()))
	deployment := fmt.Sprintf("%v_%v", tenancy, zone)
	var client replicator.TChanReplicator

	// take a reader lock to see if we already have a valid client
	f.deploymentClientsLk.RLock()
	if clients, ok := f.deploymentClients[deployment]; ok && len(clients) > 0 {
		// randomly select a client and return
		// TODO health check and proper load routing
		client = clients[rand.Intn(len(clients))]
	}
	f.deploymentClientsLk.RUnlock()
	if client != nil {
		return client, nil
	}

	if !f.hasHostsForDeployment(deployment) {
		err := &shared.BadRequestError{Message: fmt.Sprintf("Deployment [%v] is not configured", deployment)}
		f.logger.WithFields(bark.Fields{common.TagErr: err, common.TagDeploymentName: deployment}).Error("Deployment is not configured")
		return nil, err
	}

	if f.ch == nil {
		err := &shared.BadRequestError{Message: "TChannel not set"}
		f.logger.WithFields(bark.Fields{common.TagErr: err, common.TagDeploymentName: deployment}).Error("TChannel not set")
		return nil, err
	}

	// take a writer lock to create replicator clients and update cache
	f.deploymentClientsLk.Lock()
	defer f.deploymentClientsLk.Unlock()

	// Check again to see if clients were already created by someone else
	if clients, ok := f.deploymentClients[deployment]; ok && len(clients) > 0 {
		return clients[rand.Intn(len(clients))], nil
	}

	hosts := f.GetHostsForDeployment(deployment)
	clients := make([]replicator.TChanReplicator, 0, len(hosts))

	for _, host := range hosts {
		clients = append(clients, replicator.NewTChanReplicatorClient(thrift.NewClient(
			f.ch,
			common.ReplicatorServiceName,
			&thrift.ClientOptions{HostPort: net.JoinHostPort(host, strconv.Itoa(f.AppConfig.GetServiceConfig(common.ReplicatorServiceName).GetPort()))},
		)))
	}
	f.deploymentClients[deployment] = clients

	return clients[rand.Intn(len(clients))], nil
}

// SetTChannel sets the tchannel used by thrift client
func (f *repCltFactoryImpl) SetTChannel(ch *tchannel.Channel) {
	f.ch = ch
}

func (f *repCltFactoryImpl) hasHostsForDeployment(deployment string) bool {
	f.hostLk.RLock()
	defer f.hostLk.RUnlock()

	_, hasHosts := f.hosts[deployment]
	return hasHosts
}

func (f *repCltFactoryImpl) GetHostsForDeployment(deployment string) []string {
	f.hostLk.RLock()
	defer f.hostLk.RUnlock()

	return f.hosts[deployment]
}

func (f *repCltFactoryImpl) GetHostsForAllDeployment() map[string][]string {
	f.hostLk.RLock()
	defer f.hostLk.RUnlock()

	return f.hosts
}

func (f *repCltFactoryImpl) UpdateHostsForDeployment(deployment string, hosts []string) {
	f.hostLk.Lock()
	defer f.hostLk.Unlock()

	f.deploymentClientsLk.Lock()
	defer f.deploymentClientsLk.Unlock()

	f.hosts[deployment] = hosts

	// clear the client cache as we get a new host list for the deployment
	delete(f.deploymentClients, deployment)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/jmhodges/levigo"
	"github.com/titanous/setdb/lockring"
)

var KeyMutex = lockring.NewLockRing(1024)

// Key/Value type identifiers, only append to this list
const (
	MetaKey byte = iota
	StringKey
	HashKey
	ListKey
	SetKey
	ZSetKey
	ZScoreKey
	StringLengthValue
	HashLengthValue
	ListLengthValue
	SetCardValue
	ZCardValue
)

var InvalidKeyTypeError = fmt.Errorf("Operation against a key holding the wrong kind of value")
var InvalidDataError = fmt.Errorf("Invalid data")
var SyntaxError = fmt.Errorf("syntax error")

// A cmdReply is a response to a command, and wraps one of these types:
//
// string - single line reply, automatically prefixed with "+"
// error - error message, automatically prefixed with "-"
// int - integer number, automatically encoded and prefixed with ":"
// []byte - bulk reply, automatically prefixed with the length like "$3\r\n"
// nil []byte - nil response (must be part of multi-bulk reply), encoded as "$-1\r\n"
// nil - nil multi-bulk reply, encoded as "*-1"
// []cmdReply - multi-bulk reply, automatically serialized, members can be nil, []byte, or int
// *cmdReplyStream - multi-bulk reply sent over a channel
type cmdReply interface{}

// if the number of items is known before the items,
// they do not need to be buffered into memory, and can be streamed over a channel
type cmdReplyStream struct {
	size  int64         // the number of items that will be sent
	items chan cmdReply // a multi-bulk reply item, one of nil, []byte, or int
}

type cmdFunc func(args [][]byte, wb *levigo.WriteBatch) cmdReply

type cmdDesc struct {
	name     string
	function cmdFunc
	arity    int  // the number of required arguments, -n means >= n
	writes   bool // false if the command doesn't write data (the WriteBatch will not be passed in)
	firstKey int  // first argument that is a key (-1 for none)
	lastKey  int  // last argument that is a key (-1 for unbounded)
	keyStep  int  // step to get all the keys from first to last. For instance MSET is 2 since the arguments are KEY VAL KEY VAL...
}

var commandList = []cmdDesc{
	{"del", Del, -1, true, 0, -1, 1},
	{"echo", Echo, 1, false, -1, 0, 0},
	{"get", Get, 1, false, 0, 0, 0},
	{"hdel", Hdel, -2, true, 0, 0, 0},
	{"hexists", Hexists, 2, false, 0, 0, 0},
	{"hget", Hget, 2, false, 0, 0, 0},
	{"hgetall", Hgetall, 1, false, 0, 0, 0},
	{"hincrby", Hincrby, 3, true, 0, 0, 0},
	{"hincrbyfloat", Hincrbyfloat, 3, true, 0, 0, 0},
	{"hkeys", Hkeys, 1, false, 0, 0, 0},
	{"hlen", Hlen, 1, false, 0, 0, 0},
	{"hmget", Hmget, -2, false, 0, 0, 0},
	{"hmset", Hmset, -3, true, 0, 0, 0},
	{"hset", Hset, 3, true, 0, 0, 0},
	{"hsetnx", Hsetnx, 3, true, 0, 0, 0},
	{"hvals", Hvals, 1, false, 0, 0, 0},
	{"ping", Ping, 0, false, -1, 0, 0},
	{"set", Set, 2, true, 0, 0, 0},
	{"sadd", Sadd, -2, true, 0, 0, 0},
	{"scard", Scard, 1, false, 0, 0, 0},
	{"sismember", Sismember, 2, false, 0, 0, 0},
	{"smembers", Smembers, 1, false, 0, 0, 0},
	{"spop", Spop, 1, true, 0, 0, 0},
	{"srem", Srem, -2, true, 0, 0, 0},
	{"zadd", Zadd, -3, true, 0, 0, 0},
	{"zcard", Zcard, 1, false, 0, 0, 0},
	{"zincrby", Zincrby, 3, true, 0, 0, 0},
	{"zrange", Zrange, -3, false, 0, 0, 0},
	{"zrem", Zrem, -2, true, 0, 0, 0},
	{"zrevrange", Zrevrange, -3, false, 0, 0, 0},
	{"zscore", Zscore, 2, false, 0, 0, 0},
}

// extract the keys from the command args
func (c *cmdDesc) getKeys(args [][]byte) [][]byte {
	// if no keys are expected, or the argument with the first key doesn't exist
	if c.firstKey < 0 || len(args) <= c.firstKey {
		return nil
	}
	// shortcut: if the keystep is 0 or 1, we can slice the array
	if c.keyStep <= 1 {
		return args[c.firstKey : c.lastKey+1]
	}
	keys := make([][]byte, 0, 1)
	for i := c.firstKey; i < len(args) && (c.lastKey == -1 || i <= c.lastKey); i += c.keyStep {
		keys = append(keys, args[i])
	}
	return keys
}

// acquires a read or write lock for the keys in arguments using the cmdDesc
func (c *cmdDesc) lockKeys(args [][]byte) {
	if !c.writes {
		return
	}
	for _, k := range c.getKeys(args) {
		KeyMutex.Lock(k)
	}
}

func (c *cmdDesc) unlockKeys(args [][]byte) {
	if !c.writes {
		return
	}
	for _, k := range c.getKeys(args) {
		KeyMutex.Unlock(k)
	}
}

var commands = make(map[string]cmdDesc, len(commandList))

func Ping(args [][]byte, wb *levigo.WriteBatch) cmdReply {
	return "PONG"
}

func Echo(args [][]byte, wb *levigo.WriteBatch) cmdReply {
	return args[0]
}

func Del(args [][]byte, wb *levigo.WriteBatch) cmdReply {
	deleted := 0
	k := make([]byte, 1, len(args[0])) // make a reusable slice with room for the first metakey

	for _, key := range args {
		k = bufMetaKey(k, key)
		res, err := DB.Get(ReadWithoutCacheFill, k)
		if err != nil {
			return err
		}
		if res == nil {
			continue
		}
		if len(res) == 0 {
			return InvalidDataError
		}
		del(key, res[0], wb)
		wb.Delete(k)
		deleted++
	}
	return deleted
}

func del(key []byte, t byte, wb *levigo.WriteBatch) {
	switch t {
	case StringLengthValue:
		DelString(key, wb)
	case HashLengthValue:
		DelHash(key, wb)
	case SetCardValue:
		DelSet(key, wb)
	case ZCardValue:
		DelZset(key, wb)
	default:
		panic("unknown key type")
	}
}

// set buf to the metaKey for key
func bufMetaKey(buf []byte, key []byte) []byte {
	buf[0] = MetaKey
	return append(buf[:1], key...)
}

func metaKey(k []byte) []byte {
	key := make([]byte, 1+len(k))
	key[0] = MetaKey
	copy(key[1:], k)
	return key
}

func init() {
	for _, c := range commandList {
		commands[c.name] = c
	}
}
<commit_msg>Add a list of currently unimplemented commands<commit_after>package main

import (
	"fmt"

	"github.com/jmhodges/levigo"
	"github.com/titanous/setdb/lockring"
)

var KeyMutex = lockring.NewLockRing(1024)

// Key/Value type identifiers, only append to this list
const (
	MetaKey byte = iota
	StringKey
	HashKey
	ListKey
	SetKey
	ZSetKey
	ZScoreKey
	StringLengthValue
	HashLengthValue
	ListLengthValue
	SetCardValue
	ZCardValue
)

var InvalidKeyTypeError = fmt.Errorf("Operation against a key holding the wrong kind of value")
var InvalidDataError = fmt.Errorf("Invalid data")
var SyntaxError = fmt.Errorf("syntax error")

// A cmdReply is a response to a command, and wraps one of these types:
//
// string - single line reply, automatically prefixed with "+"
// error - error message, automatically prefixed with "-"
// int - integer number, automatically encoded and prefixed with ":"
// []byte - bulk reply, automatically prefixed with the length like "$3\r\n"
// nil []byte - nil response (must be part of multi-bulk reply), encoded as "$-1\r\n"
// nil - nil multi-bulk reply, encoded as "*-1"
// []cmdReply - multi-bulk reply, automatically serialized, members can be nil, []byte, or int
// *cmdReplyStream - multi-bulk reply sent over a channel
type cmdReply interface{}

// if the number of items is known before the items,
// they do not need to be buffered into memory, and can be streamed over a channel
type cmdReplyStream struct {
	size  int64         // the number of items that will be sent
	items chan cmdReply // a multi-bulk reply item, one of nil, []byte, or int
}

type cmdFunc func(args [][]byte, wb *levigo.WriteBatch) cmdReply

type cmdDesc struct {
	name     string
	function cmdFunc
	arity    int  // the number of required arguments, -n means >= n
	writes   bool // false if the command doesn't write data (the WriteBatch will not be passed in)
	firstKey int  // first argument that is a key (-1 for none)
	lastKey  int  // last argument that is a key (-1 for unbounded)
	keyStep  int  // step to get all the keys from first to last. For instance MSET is 2 since the arguments are KEY VAL KEY VAL...
}

var commandList = []cmdDesc{
	{"del", Del, -1, true, 0, -1, 1},
	{"echo", Echo, 1, false, -1, 0, 0},
	{"get", Get, 1, false, 0, 0, 0},
	{"hdel", Hdel, -2, true, 0, 0, 0},
	{"hexists", Hexists, 2, false, 0, 0, 0},
	{"hget", Hget, 2, false, 0, 0, 0},
	{"hgetall", Hgetall, 1, false, 0, 0, 0},
	{"hincrby", Hincrby, 3, true, 0, 0, 0},
	{"hincrbyfloat", Hincrbyfloat, 3, true, 0, 0, 0},
	{"hkeys", Hkeys, 1, false, 0, 0, 0},
	{"hlen", Hlen, 1, false, 0, 0, 0},
	{"hmget", Hmget, -2, false, 0, 0, 0},
	{"hmset", Hmset, -3, true, 0, 0, 0},
	{"hset", Hset, 3, true, 0, 0, 0},
	{"hsetnx", Hsetnx, 3, true, 0, 0, 0},
	{"hvals", Hvals, 1, false, 0, 0, 0},
	{"ping", Ping, 0, false, -1, 0, 0},
	{"set", Set, 2, true, 0, 0, 0},
	{"sadd", Sadd, -2, true, 0, 0, 0},
	{"scard", Scard, 1, false, 0, 0, 0},
	{"sismember", Sismember, 2, false, 0, 0, 0},
	{"smembers", Smembers, 1, false, 0, 0, 0},
	{"spop", Spop, 1, true, 0, 0, 0},
	{"srem", Srem, -2, true, 0, 0, 0},
	{"zadd", Zadd, -3, true, 0, 0, 0},
	{"zcard", Zcard, 1, false, 0, 0, 0},
	{"zincrby", Zincrby, 3, true, 0, 0, 0},
	{"zrange", Zrange, -3, false, 0, 0, 0},
	{"zrem", Zrem, -2, true, 0, 0, 0},
	{"zrevrange", Zrevrange, -3, false, 0, 0, 0},
	{"zscore", Zscore, 2, false, 0, 0, 0},
}

// extract the keys from the command args
func (c *cmdDesc) getKeys(args [][]byte) [][]byte {
	// if no keys are expected, or the argument with the first key doesn't exist
	if c.firstKey < 0 || len(args) <= c.firstKey {
		return nil
	}
	// shortcut: if the keystep is 0 or 1, we can slice the array
	if c.keyStep <= 1 {
		return args[c.firstKey : c.lastKey+1]
	}
	keys := make([][]byte, 0, 1)
	for i := c.firstKey; i < len(args) && (c.lastKey == -1 || i <= c.lastKey); i += c.keyStep {
		keys = append(keys, args[i])
	}
	return keys
}

// acquires a read or write lock for the keys in arguments using the cmdDesc
func (c *cmdDesc) lockKeys(args [][]byte) {
	if !c.writes {
		return
	}
	for _, k := range c.getKeys(args) {
		KeyMutex.Lock(k)
	}
}

func (c *cmdDesc) unlockKeys(args [][]byte) {
	if !c.writes {
		return
	}
	for _, k := range c.getKeys(args) {
		KeyMutex.Unlock(k)
	}
}

var commands = make(map[string]cmdDesc, len(commandList))

func Ping(args [][]byte, wb *levigo.WriteBatch) cmdReply {
	return "PONG"
}

func Echo(args [][]byte, wb *levigo.WriteBatch) cmdReply {
	return args[0]
}

func Del(args [][]byte, wb *levigo.WriteBatch) cmdReply {
	deleted := 0
	k := make([]byte, 1, len(args[0])) // make a reusable slice with room for the first metakey

	for _, key := range args {
		k = bufMetaKey(k, key)
		res, err := DB.Get(ReadWithoutCacheFill, k)
		if err != nil {
			return err
		}
		if res == nil {
			continue
		}
		if len(res) == 0 {
			return InvalidDataError
		}
		del(key, res[0], wb)
		wb.Delete(k)
		deleted++
	}
	return deleted
}

func del(key []byte, t byte, wb *levigo.WriteBatch) {
	switch t {
	case StringLengthValue:
		DelString(key, wb)
	case HashLengthValue:
		DelHash(key, wb)
	case SetCardValue:
		DelSet(key, wb)
	case ZCardValue:
		DelZset(key, wb)
	default:
		panic("unknown key type")
	}
}

// set buf to the metaKey for key
func bufMetaKey(buf []byte, key []byte) []byte {
	buf[0] = MetaKey
	return append(buf[:1], key...)
}

func metaKey(k []byte) []byte {
	key := make([]byte, 1+len(k))
	key[0] = MetaKey
	copy(key[1:], k)
	return key
}

func init() {
	for _, c := range commandList {
		commands[c.name] = c
	}
}

// Keys
// DUMP
// KEYS
// EXISTS
// EXPIRE
// EXPIREAT
// MIGRATE
// MOVE
// OBJECT?
// PERSIST
// PEXPIRE
// PEXPIREAT
// PTTL
// RANDOMKEY
// RENAME
// RENAMENX
// RESTORE
// SORT
// TTL
// TYPE
//
// Lists
// BLPOP
// BRPOP
// BRPOPLPUSH
// LINDEX
// LINSERT
// LLEN
// LPOP
// LPUSH
// LPUSHX
// LRANGE
// LREM
// LSET
// LTRIM
// RPOP
// RPOPLPUSH
// RPUSH
// RPUSHX
//
// Pub/Sub
// PSUBSCRIBE
// PUNSUBSCRIBE
// UNSUBSCRIBE
// PUBLISH
// SUBSCRIBE
//
// Transactions
// DISCARD
// MULTI
// EXEC
// WATCH
// UNWATCH
//
// Scripting
// EVAL
// EVALSHA
// SCRIPT EXISTS
// SCRIPT KILL
// SCRIPT FLUSH
// SCRIPT LOAD
//
// Connection
// AUTH
// SELECT
// QUIT
//
// Server
// FLUSHALL
// FLUSHDB
// SYNC
// TIME
// CONFIG RESETSTAT
// INFO
// DBSIZE
// CLIENT LIST
// CLIENT KILL
// MONITOR
// CONFIG GET
// CONFIG SET
// SLAVEOF
// SHUTDOWN
// SAVE
// SLOWLOG
<|endoftext|>