<commit_before>/* This file is part of VoltDB.
 * Copyright (C) 2008-2018 VoltDB Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
 */

package main

import (
	"database/sql"
	"database/sql/driver"
	"flag"
	"fmt"
	"log"
	"os"
	"reflect"
	"runtime"
	"runtime/pprof"
	"strings"
	"sync/atomic"
	"time"

	"github.com/VoltDB/voltdb-client-go/voltdbclient"
)

// horizontalRule is handy to use, rather than typing this out several times
const horizontalRule = "----------" + "----------" + "----------" + "----------" +
	"----------" + "----------" + "----------" + "----------" + "\n"

// voltDBDriver for use sql/driver
const voltDBDriver = "voltdb"

// Initialize some common constants and variables
const contestantNamesCSV = "Edwina Burnam,Tabatha Gehling,Kelly Clauss,Jessie Alloway," +
	"Alana Bregman,Jessie Eichman,Allie Rogalski,Nita Coster," +
	"Kurt Walser,Ericka Dieter,Loraine NygrenTania Mattioli"

// potential return codes (synced with Vote procedure)
const (
	voteSuccessful        int64 = 0
	errInvalidContestant  int64 = 1
	errVoterOverVoteLimit int64 = 2
)

// voter benchmark state
type benchmarkStats struct {
	totalVotes, acceptedVotes, badContestantVotes, badVoteCountVotes, failedVotes uint64
}

// helper function for clearing content of any type
func clear(v interface{}) {
	p := reflect.ValueOf(v).Elem()
	p.Set(reflect.Zero(p.Type()))
}

var (
	periodicStats, fullStats *benchmarkStats
	cpuprofile               = ""
	memprofile               = ""
	config                   *voterConfig
	ticker                   *time.Ticker
	timeStart                time.Time
	bm                       *benchmark
)

type benchmark struct {
	switchboard phoneCallGenerator
	conn        *voltdbclient.Conn
}

func newBenchmark() (*benchmark, error) {
	bmTemp := new(benchmark)
	bmTemp.switchboard = newPhoneCallGenerator(config.contestants)
	periodicStats = new(benchmarkStats)
	fullStats = new(benchmarkStats)
	fmt.Print(horizontalRule)
	fmt.Println(" Command Line Configuration")
	fmt.Println(horizontalRule)
	fmt.Printf("%+v\n", *config)
	return bmTemp, nil
}

func (bm *benchmark) runBenchmark() {
	fmt.Print(horizontalRule)
	fmt.Println(" Setup & Initialization")
	fmt.Println(horizontalRule)

	// connect to one or more servers, loop until success
	bm.conn = connect(config.servers)
	defer bm.conn.Close()

	// initialize using synchronous call
	fmt.Print("\nPopulating Static Tables\n")
	bm.conn.Exec("Initialize", []driver.Value{int32(config.contestants), contestantNamesCSV})

	fmt.Print(horizontalRule)
	fmt.Println(" Starting Benchmark")
	fmt.Println(horizontalRule)

	// Run the benchmark loop for the requested warmup time
	// The throughput may be throttled depending on client configuration
	fmt.Println("Warming up...")
	switch config.runtype {
	case ASYNC:
		vote(config.goroutines, config.warmup, placeVotesAsync)
	case SYNC:
		vote(config.goroutines, config.warmup, placeVotesSync)
	case SQL:
		vote(config.goroutines, config.warmup, placeVotesSQL)
	}

	//reset the stats after warmup
	clear(fullStats)
	clear(periodicStats)

	// print periodic statistics to the console
	ticker = time.NewTicker(config.displayinterval)
	defer ticker.Stop()
	go printStatistics()
	timeStart = time.Now()

	// Run the benchmark loop for the requested duration
	// The throughput may be throttled depending on client configuration
	fmt.Print("\nRunning benchmark...\n")
	switch config.runtype {
	case ASYNC:
		vote(config.goroutines, config.duration, placeVotesAsync)
	case SYNC:
		vote(config.goroutines, config.duration, placeVotesSync)
	case SQL:
		vote(config.goroutines, config.duration, placeVotesSQL)
	}

	timeElapsed := time.Now().Sub(timeStart)
	// print the summary results
	printResults(timeElapsed)
}

func openAndPingDB(servers string) *sql.DB {
	db, err := sql.Open(voltDBDriver, servers)
	if err != nil {
		fmt.Println("open")
		log.Fatal(err)
	}
	err = db.Ping()
	if err != nil {
		fmt.Println("open")
		log.Fatal(err)
	}
	return db
}

func placeVotesSQL(join chan int, duration time.Duration) {
	volt := openAndPingDB(config.servers)
	defer volt.Close()

	timeout := time.After(duration)
	// don't support prepare statement with store procedure

	ops := 0
	for {
		select {
		case <-timeout:
			join <- ops
			return
		default:
			contestantNumber, phoneNumber := bm.switchboard.receive()
			rows, err := volt.Query("Vote", phoneNumber, contestantNumber, int64(config.maxvotes))
			ops += handleSQLRows(rows, err)
		}

	}
}

func placeVotesSync(join chan int, duration time.Duration) {
	volt := connect(config.servers)
	defer volt.Close()
	timeout := time.After(duration)

	ops := 0
	for {
		select {
		case <-timeout:
			join <- ops
			return
		default:
			contestantNumber, phoneNumber := bm.switchboard.receive()
			rows, err := volt.Query("Vote", []driver.Value{phoneNumber, contestantNumber, int64(config.maxvotes)})
			if err != nil {
				ops += handleVoteError(err)
			} else {
				ops += handleVoteReturnCode(rows)
			}
		}

	}

}

func placeVotesAsync(join chan int, duration time.Duration) {
	volt := connect(config.servers)
	defer volt.Close()
	// volt := bm.conn

	timeout := time.After(duration)

	vcb := newVoteCallBack()
	ops := 0
	for {
		select {
		case <-timeout:
			volt.Drain()
			join <- ops
			return
		default:
			contestantNumber, phoneNumber := bm.switchboard.receive()
			volt.QueryAsync(vcb, "Vote", []driver.Value{phoneNumber, contestantNumber, int64(config.maxvotes)})
		}

	}
}

func vote(gorotines int, duration time.Duration,
	fn func(join chan int, duration time.Duration)) {
	var joiners = make([]chan int, 0)
	for i := 0; i < gorotines; i++ {
		var joinchan = make(chan int)
		joiners = append(joiners, joinchan)
		go fn(joinchan, duration)
	}

	// var totalCount = 0
	for _, join := range joiners {
		<-join
		// ops := <-join
		// totalCount += ops
		//fmt.Printf("kver %v finished and acted %v ops.\n", v, ops)
	}
	return
}

type voteCallBack struct {
}

func newVoteCallBack() voteCallBack {
	vcb := new(voteCallBack)
	return *vcb
}

func (vcb voteCallBack) ConsumeError(err error) {
	handleVoteError(err)
}

// shouldn't call this
func (vcb voteCallBack) ConsumeResult(res driver.Result) {
}

func (vcb voteCallBack) ConsumeRows(rows driver.Rows) {
	handleVoteReturnCode(rows)
}

func handleSQLRows(rows *sql.Rows, err error) (success int) {
	if err == nil {
		defer rows.Close()
		if rows.Next() {
			atomic.AddUint64(&(fullStats.totalVotes), 1)
			atomic.AddUint64(&(periodicStats.totalVotes), 1)
			var resultCode int64
			if err = rows.Scan(&resultCode); err == nil {
				switch resultCode {
				case errInvalidContestant:
					atomic.AddUint64(&(fullStats.badContestantVotes), 1)
				case errVoterOverVoteLimit:
					atomic.AddUint64(&(fullStats.badVoteCountVotes), 1)
				case voteSuccessful:
					atomic.AddUint64(&(fullStats.acceptedVotes), 1)
				}
			} else {
				log.Panic(err)
				// shouldn't be here
			}
			return 1
		}
		log.Panic(err)
		atomic.AddUint64(&(fullStats.failedVotes), 1)
		return 0
	}
	return 0
}

func handleVoteError(err error) (success int) {
	log.Println(err)
	atomic.AddUint64(&(fullStats.failedVotes), 1)
	return 0
}

func handleVoteReturnCode(rows driver.Rows) (success int) {
	defer rows.Close()
	if voltRows := rows.(voltdbclient.VoltRows); voltRows.AdvanceRow() {
		resultCode, err := voltRows.GetBigInt(0)
		if err != nil {
			return handleVoteError(err)
		}
		atomic.AddUint64(&(fullStats.totalVotes), 1)
		atomic.AddUint64(&(periodicStats.totalVotes), 1)
		switch resultCode {
		case errInvalidContestant:
			atomic.AddUint64(&(fullStats.badContestantVotes), 1)
		case errVoterOverVoteLimit:
			atomic.AddUint64(&(fullStats.badVoteCountVotes), 1)
		case voteSuccessful:
			atomic.AddUint64(&(fullStats.acceptedVotes), 1)
		}
		return 1
	}
	return 0
}

func connect(servers string) *voltdbclient.Conn {
	conn, err := voltdbclient.OpenConn(servers)
	if err != nil {
		log.Fatal(err)
	}
	return conn
}

func setupProfiler() {
	if cpuprofile != "" {
		f, err := os.Create(cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
	}
}

func takeMemProfile() {
	if memprofile != "" {
		f, err := os.Create(memprofile)
		if err != nil {
			log.Fatal(err)
		}
		runtime.GC()
		pprof.WriteHeapProfile(f)
		f.Close()
		return
	}
}

func teardownProfiler() {
	if cpuprofile != "" {
		pprof.StopCPUProfile()
	}
}

func printStatistics() {
	s := time.Now()
	for t := range ticker.C {
		fmt.Print(t.Sub(s))
		fmt.Printf(" Throughput %v/s\n", float64(periodicStats.totalVotes)/config.displayinterval.Seconds())
		// fmt.Printf("Aborts/Failure %v/%v\n", periodicStats.aborts, periodicStats.failures)
		// fmt.Printf("Avg/95%% Latency %.2f/%.2fms\n")
		clear(periodicStats)
	}
}

func printResults(timeElapsed time.Duration) {
	// 1. Voting Board statistics, Voting results and performance statistics
	display := "\n" +
		horizontalRule +
		" Voting Results\n" +
		horizontalRule +
		"\nA total of %9d votes were received during the benchmark...\n" +
		" - %9d Accepted\n" +
		" - %9d Rejected (Invalid Contestant)\n" +
		" - %9d Rejected (Maximum Vote Count Reached)\n" +
		" - %9d Failed (Transaction Error)\n\n"

	fmt.Printf(display,
		fullStats.totalVotes,
		fullStats.acceptedVotes,
		fullStats.badContestantVotes,
		fullStats.badVoteCountVotes,
		fullStats.failedVotes)

	// 2. Voting results
	rows, err := bm.conn.Query("Results", []driver.Value{})
	if err != nil {
		if strings.Contains(err.Error(), "is down") {
			rows, err = bm.conn.Query("Results", []driver.Value{})
			if err != nil {
				bm.conn.DumpConn()
				log.Fatal(err, rows == nil)
			}
		} else {
			log.Fatal(err)
		}
	}

	fmt.Println("Contestant Name\t\tVotes Received")
	voltRows := rows.(voltdbclient.VoltRows)
	defer voltRows.Close()

	for voltRows.AdvanceRow() {
		contestantName, contestantNameErr := voltRows.GetString(0)
		if contestantNameErr != nil {
			log.Fatal(contestantNameErr)
		}
		totalVotes, totalVotesErr := voltRows.GetBigIntByName("total_votes")
		if totalVotesErr != nil {
			log.Fatal(totalVotesErr)
		}
		fmt.Printf("%s\t\t%14d\n", contestantName, totalVotes)
	}
	if voltRows.AdvanceToRow(0) {
		winnerName, winnerErr := voltRows.GetString(0)
		if winnerErr != nil {
			log.Fatal(winnerErr)
		}

		fmt.Printf("\nThe Winner is: %s\n\n", winnerName)
	}

	// 3. Performance statistics

	fmt.Print(horizontalRule)
	fmt.Println(" Client Workload Statistics")
	fmt.Println(horizontalRule)

	fmt.Printf("Generated %v votes in %v seconds (%0.0f ops/second)\n",
		fullStats.totalVotes, timeElapsed.Seconds(),
		float64(fullStats.totalVotes)/timeElapsed.Seconds())
}

func main() {
	flag.StringVar(&cpuprofile, "cpuprofile", "", "name of profile file to write")
	flag.StringVar(&memprofile, "memprofile", "", "write memory profile to this file")
	var err error
	config, err = newVoterConfig()
	if err != nil {
		log.Fatal(err)
	}

	setupProfiler()
	defer teardownProfiler()
	defer takeMemProfile()

	bm, _ = newBenchmark()
	bm.runBenchmark()
}
<commit_msg>Fix duration flag for voter benchmrark<commit_after>/* This file is part of VoltDB.
 * Copyright (C) 2008-2018 VoltDB Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
 */

package main

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"flag"
	"fmt"
	"log"
	"os"
	"reflect"
	"runtime"
	"runtime/pprof"
	"strings"
	"sync/atomic"
	"time"

	"github.com/VoltDB/voltdb-client-go/voltdbclient"
)

// horizontalRule is handy to use, rather than typing this out several times
const horizontalRule = "----------" + "----------" + "----------" + "----------" +
	"----------" + "----------" + "----------" + "----------" + "\n"

// voltDBDriver for use sql/driver
const voltDBDriver = "voltdb"

// Initialize some common constants and variables
const contestantNamesCSV = "Edwina Burnam,Tabatha Gehling,Kelly Clauss,Jessie Alloway," +
	"Alana Bregman,Jessie Eichman,Allie Rogalski,Nita Coster," +
	"Kurt Walser,Ericka Dieter,Loraine NygrenTania Mattioli"

// potential return codes (synced with Vote procedure)
const (
	voteSuccessful        int64 = 0
	errInvalidContestant  int64 = 1
	errVoterOverVoteLimit int64 = 2
)

// voter benchmark state
type benchmarkStats struct {
	totalVotes, acceptedVotes, badContestantVotes, badVoteCountVotes, failedVotes uint64
}

// helper function for clearing content of any type
func clear(v interface{}) {
	p := reflect.ValueOf(v).Elem()
	p.Set(reflect.Zero(p.Type()))
}

var (
	periodicStats, fullStats *benchmarkStats
	cpuprofile               = ""
	memprofile               = ""
	config                   *voterConfig
	ticker                   *time.Ticker
	timeStart                time.Time
	bm                       *benchmark
)

type benchmark struct {
	switchboard phoneCallGenerator
	conn        *voltdbclient.Conn
}

func newBenchmark() (*benchmark, error) {
	bmTemp := new(benchmark)
	bmTemp.switchboard = newPhoneCallGenerator(config.contestants)
	periodicStats = new(benchmarkStats)
	fullStats = new(benchmarkStats)
	fmt.Print(horizontalRule)
	fmt.Println(" Command Line Configuration")
	fmt.Println(horizontalRule)
	fmt.Printf("%+v\n", *config)
	return bmTemp, nil
}

func (bm *benchmark) runBenchmark() {
	fmt.Print(horizontalRule)
	fmt.Println(" Setup & Initialization")
	fmt.Println(horizontalRule)

	// connect to one or more servers, loop until success
	bm.conn = connect(config.servers)
	defer bm.conn.Close()

	// initialize using synchronous call
	fmt.Print("\nPopulating Static Tables\n")
	bm.conn.Exec("Initialize", []driver.Value{int32(config.contestants), contestantNamesCSV})

	fmt.Print(horizontalRule)
	fmt.Println(" Starting Benchmark")
	fmt.Println(horizontalRule)

	// Run the benchmark loop for the requested warmup time
	// The throughput may be throttled depending on client configuration
	fmt.Println("Warming up...")
	switch config.runtype {
	case ASYNC:
		vote(config.goroutines, config.warmup, placeVotesAsync)
	case SYNC:
		vote(config.goroutines, config.warmup, placeVotesSync)
	case SQL:
		vote(config.goroutines, config.warmup, placeVotesSQL)
	}

	//reset the stats after warmup
	clear(fullStats)
	clear(periodicStats)

	// print periodic statistics to the console
	ticker = time.NewTicker(config.displayinterval)
	defer ticker.Stop()
	go printStatistics()
	timeStart = time.Now()

	// Run the benchmark loop for the requested duration
	// The throughput may be throttled depending on client configuration
	fmt.Print("\nRunning benchmark...\n")
	switch config.runtype {
	case ASYNC:
		vote(config.goroutines, config.duration, placeVotesAsync)
	case SYNC:
		vote(config.goroutines, config.duration, placeVotesSync)
	case SQL:
		vote(config.goroutines, config.duration, placeVotesSQL)
	}

	timeElapsed := time.Now().Sub(timeStart)
	// print the summary results
	printResults(timeElapsed)
}

func openAndPingDB(servers string) *sql.DB {
	db, err := sql.Open(voltDBDriver, servers)
	if err != nil {
		fmt.Println("open")
		log.Fatal(err)
	}
	err = db.Ping()
	if err != nil {
		fmt.Println("open")
		log.Fatal(err)
	}
	return db
}

func placeVotesSQL(ctx context.Context, done func()) {
	volt := openAndPingDB(config.servers)
	defer volt.Close()

	// don't support prepare statement with store procedure
	ops := 0
	for {
		select {
		case <-ctx.Done():
			done()
			return
		default:
			contestantNumber, phoneNumber := bm.switchboard.receive()
			rows, err := volt.Query("Vote", phoneNumber, contestantNumber, int64(config.maxvotes))
			ops += handleSQLRows(rows, err)
		}
	}
}

func placeVotesSync(ctx context.Context, done func()) {
	volt := connect(config.servers)
	defer volt.Close()
	ops := 0
	for {
		select {
		case <-ctx.Done():
			done()
			return
		default:
			contestantNumber, phoneNumber := bm.switchboard.receive()
			rows, err := volt.Query("Vote", []driver.Value{phoneNumber, contestantNumber, int64(config.maxvotes)})
			if err != nil {
				ops += handleVoteError(err)
			} else {
				ops += handleVoteReturnCode(rows)
			}
		}

	}

}

func placeVotesAsync(ctx context.Context, done func()) {
	volt := connect(config.servers)
	defer volt.Close()
	// volt := bm.conn
	vcb := newVoteCallBack()
	for {
		select {
		case <-ctx.Done():
			volt.Drain()
			done()
			return
		default:
			contestantNumber, phoneNumber := bm.switchboard.receive()
			volt.QueryAsync(vcb, "Vote", []driver.Value{phoneNumber, contestantNumber, int64(config.maxvotes)})
		}
	}
}

func vote(gorotines int, duration time.Duration,
	fn func(ctx context.Context, complete func())) {
	done := make(chan struct{})
	doneFunc := func() {
		done <- struct{}{}
	}
	ctx, cancel := context.WithTimeout(context.Background(), duration)
	defer cancel()
	for i := 0; i < gorotines; i++ {
		go fn(ctx, doneFunc)
	}
	total := gorotines
	for {
		select {
		case <-done:
			total--
		default:
			if total == 0 {
				return
			}
		}
	}
}

type voteCallBack struct {
}

func newVoteCallBack() voteCallBack {
	vcb := new(voteCallBack)
	return *vcb
}

func (vcb voteCallBack) ConsumeError(err error) {
	handleVoteError(err)
}

// shouldn't call this
func (vcb voteCallBack) ConsumeResult(res driver.Result) {
}

func (vcb voteCallBack) ConsumeRows(rows driver.Rows) {
	handleVoteReturnCode(rows)
}

func handleSQLRows(rows *sql.Rows, err error) (success int) {
	if err == nil {
		defer rows.Close()
		if rows.Next() {
			atomic.AddUint64(&(fullStats.totalVotes), 1)
			atomic.AddUint64(&(periodicStats.totalVotes), 1)
			var resultCode int64
			if err = rows.Scan(&resultCode); err == nil {
				switch resultCode {
				case errInvalidContestant:
					atomic.AddUint64(&(fullStats.badContestantVotes), 1)
				case errVoterOverVoteLimit:
					atomic.AddUint64(&(fullStats.badVoteCountVotes), 1)
				case voteSuccessful:
					atomic.AddUint64(&(fullStats.acceptedVotes), 1)
				}
			} else {
				log.Panic(err)
				// shouldn't be here
			}
			return 1
		}
		log.Panic(err)
		atomic.AddUint64(&(fullStats.failedVotes), 1)
		return 0
	}
	return 0
}

func handleVoteError(err error) (success int) {
	log.Println(err)
	atomic.AddUint64(&(fullStats.failedVotes), 1)
	return 0
}

func handleVoteReturnCode(rows driver.Rows) (success int) {
	defer rows.Close()
	if voltRows := rows.(voltdbclient.VoltRows); voltRows.AdvanceRow() {
		resultCode, err := voltRows.GetBigInt(0)
		if err != nil {
			return handleVoteError(err)
		}
		atomic.AddUint64(&(fullStats.totalVotes), 1)
		atomic.AddUint64(&(periodicStats.totalVotes), 1)
		switch resultCode {
		case errInvalidContestant:
			atomic.AddUint64(&(fullStats.badContestantVotes), 1)
		case errVoterOverVoteLimit:
			atomic.AddUint64(&(fullStats.badVoteCountVotes), 1)
		case voteSuccessful:
			atomic.AddUint64(&(fullStats.acceptedVotes), 1)
		}
		return 1
	}
	return 0
}

func connect(servers string) *voltdbclient.Conn {
	conn, err := voltdbclient.OpenConn(servers)
	if err != nil {
		log.Fatal(err)
	}
	return conn
}

func setupProfiler() {
	if cpuprofile != "" {
		f, err := os.Create(cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
	}
}

func takeMemProfile() {
	if memprofile != "" {
		f, err := os.Create(memprofile)
		if err != nil {
			log.Fatal(err)
		}
		runtime.GC()
		pprof.WriteHeapProfile(f)
		f.Close()
		return
	}
}

func teardownProfiler() {
	if cpuprofile != "" {
		pprof.StopCPUProfile()
	}
}

func printStatistics() {
	s := time.Now()
	for t := range ticker.C {
		fmt.Print(t.Sub(s))
		fmt.Printf(" Throughput %v/s\n", float64(periodicStats.totalVotes)/config.displayinterval.Seconds())
		// fmt.Printf("Aborts/Failure %v/%v\n", periodicStats.aborts, periodicStats.failures)
		// fmt.Printf("Avg/95%% Latency %.2f/%.2fms\n")
		clear(periodicStats)
	}
}

func printResults(timeElapsed time.Duration) {
	// 1. Voting Board statistics, Voting results and performance statistics
	display := "\n" +
		horizontalRule +
		" Voting Results\n" +
		horizontalRule +
		"\nA total of %9d votes were received during the benchmark...\n" +
		" - %9d Accepted\n" +
		" - %9d Rejected (Invalid Contestant)\n" +
		" - %9d Rejected (Maximum Vote Count Reached)\n" +
		" - %9d Failed (Transaction Error)\n\n"

	fmt.Printf(display,
		fullStats.totalVotes,
		fullStats.acceptedVotes,
		fullStats.badContestantVotes,
		fullStats.badVoteCountVotes,
		fullStats.failedVotes)

	// 2. Voting results
	rows, err := bm.conn.Query("Results", []driver.Value{})
	if err != nil {
		if strings.Contains(err.Error(), "is down") {
			rows, err = bm.conn.Query("Results", []driver.Value{})
			if err != nil {
				bm.conn.DumpConn()
				log.Fatal(err, rows == nil)
			}
		} else {
			log.Fatal(err)
		}
	}

	fmt.Println("Contestant Name\t\tVotes Received")
	voltRows := rows.(voltdbclient.VoltRows)
	defer voltRows.Close()

	for voltRows.AdvanceRow() {
		contestantName, contestantNameErr := voltRows.GetString(0)
		if contestantNameErr != nil {
			log.Fatal(contestantNameErr)
		}
		totalVotes, totalVotesErr := voltRows.GetBigIntByName("total_votes")
		if totalVotesErr != nil {
			log.Fatal(totalVotesErr)
		}
		fmt.Printf("%s\t\t%14d\n", contestantName, totalVotes)
	}
	if voltRows.AdvanceToRow(0) {
		winnerName, winnerErr := voltRows.GetString(0)
		if winnerErr != nil {
			log.Fatal(winnerErr)
		}

		fmt.Printf("\nThe Winner is: %s\n\n", winnerName)
	}

	// 3. Performance statistics

	fmt.Print(horizontalRule)
	fmt.Println(" Client Workload Statistics")
	fmt.Println(horizontalRule)

	fmt.Printf("Generated %v votes in %v seconds (%0.0f ops/second)\n",
		fullStats.totalVotes, timeElapsed.Seconds(),
		float64(fullStats.totalVotes)/timeElapsed.Seconds())
}

func main() {
	flag.StringVar(&cpuprofile, "cpuprofile", "", "name of profile file to write")
	flag.StringVar(&memprofile, "memprofile", "", "write memory profile to this file")
	var err error
	config, err = newVoterConfig()
	if err != nil {
		log.Fatal(err)
	}

	setupProfiler()
	defer teardownProfiler()
	defer takeMemProfile()

	bm, _ = newBenchmark()
	bm.runBenchmark()
}
<|endoftext|>


<commit_before>//
// markup.go
// memeposting markup parser
//
package srnd

import (
  "github.com/mvdan/xurls"
  "html"
  "regexp"
  "strings"
)

// copypasted from https://stackoverflow.com/questions/161738/what-is-the-best-regular-expression-to-check-if-a-string-is-a-valid-url
// var re_external_link = regexp.MustCompile(`((?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?)`);
var re_backlink = regexp.MustCompile(`>> ?([0-9a-f]+)`)

// parse backlink
func backlink(word string) (markup string) {
  re := regexp.MustCompile(`>> ?([0-9a-f]+)`)
  link := re.FindString(word)
  if len(link) > 2 {
    link = strings.Trim(link[2:], " ")
    if len(link) > 2 {
      url := template.findLink(link)
      if len(url) == 0 {
        return ">>" + link + ""
      }
      // backlink exists
      return`<a href="`+url+`">>>` + link + ""
    } else {
      return html.EscapeString(word)
    }
  }
  return html.EscapeString(word)
}

func formatline(line string) (markup string) {
  line = strings.Trim(line, "\t\r\n ")
  if len(line) > 0 {
    if strings.HasPrefix(line, ">") && ! ( strings.HasPrefix(line, ">>") && re_backlink.MatchString(strings.Split(line, " ")[0])) {
            // le ebin meme arrows
      markup += ""
      markup += html.EscapeString(line)
      markup += ""
    } else if strings.HasPrefix(line, "==") && strings.HasSuffix(line, "==") {
      // redtext
      markup += ""
      markup += html.EscapeString(line[2:len(line)-2])
      markup += ""
    } else {
      // regular line
      markup += "
"
      // for each word
      for _, word := range strings.Split(line, " ") {
        // check for backlink
        if re_backlink.MatchString(word) {
          markup += backlink(word)
        } else {
          // linkify as needed
          word = html.EscapeString(word)
          markup += xurls.Strict.ReplaceAllString(word, `<a href="$1">$1</a>`)
        }
        markup += " "
      }
    }
  }
  markup += "
"
  return
}

// format lines inside a code tag
func formatcodeline(line string) (markup string) {
  markup += html.EscapeString(line)
  markup += "\n"
  return
}

func memeposting(src string) (markup string) {
  found_tag := false
  tag_content := ""
  tag := ""
  // for each line...
  for _, line := range strings.Split(src, "\n") {
    // beginning of code tag ?
    if strings.Count(line, "[code]") > 0 {
      // yes there's a code tag
      found_tag = true
      tag = "code"
    } else if strings.Count(line, "[spoiler]") > 0 {
      // spoiler tag
      found_tag = true
      tag = "spoiler"
    } else if strings.Count(line, "[psy]") > 0 {
      // psy tag
      found_tag = true
      tag = "psy"
    }
    if found_tag {
      // collect content of tag
      tag_content += line + "\n"
      // end of our tag ?
      if strings.Count(line, "[/"+tag+"]") == 1 {
        // yah
        found_tag = false
        var tag_open, tag_close string
        if tag == "code" {
          tag_open = "
"
          tag_close = "
"
        } else if tag == "spoiler" {
          tag_open = ""
          tag_close = ""
        } else if tag == "psy" {
          tag_open = "
"
          tag_close = "
"
        }
        markup += tag_open
        // remove open tag, only once so we can have a code tag verbatum inside
        tag_content = strings.Replace(tag_content, "["+tag+"]", "", 1)
        // remove all close tags, should only have 1
        tag_content = strings.Replace(tag_content, "[/"+tag+"]", "", -1)
        // make into lines
        for _, tag_line := range strings.Split(tag_content, "\n") {
          if tag == "code" {
            markup += formatcodeline(tag_line)
          } else {
            markup += formatline(tag_line)
          }
        }
        // close pre tag
        markup += tag_close
        // reset content buffer
        tag_content = ""
      }
      // next line
      continue
    }
    // format line regularlly
    markup += formatline(line)
  }
  // flush the rest of an incomplete code tag
  for _, line := range strings.Split(tag_content, "\n") {
    markup += formatline(line)
  }
  return
}
<commit_msg>remove undeeded
 tag<commit_after>//
// markup.go
// memeposting markup parser
//
package srnd

import (
  "github.com/mvdan/xurls"
  "html"
  "regexp"
  "strings"
)

// copypasted from https://stackoverflow.com/questions/161738/what-is-the-best-regular-expression-to-check-if-a-string-is-a-valid-url
// var re_external_link = regexp.MustCompile(`((?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?)`);
var re_backlink = regexp.MustCompile(`>> ?([0-9a-f]+)`)

// parse backlink
func backlink(word string) (markup string) {
  re := regexp.MustCompile(`>> ?([0-9a-f]+)`)
  link := re.FindString(word)
  if len(link) > 2 {
    link = strings.Trim(link[2:], " ")
    if len(link) > 2 {
      url := template.findLink(link)
      if len(url) == 0 {
        return ">>" + link + ""
      }
      // backlink exists
      return`<a href="`+url+`">>>` + link + ""
    } else {
      return html.EscapeString(word)
    }
  }
  return html.EscapeString(word)
}

func formatline(line string) (markup string) {
  line = strings.Trim(line, "\t\r\n ")
  if len(line) > 0 {
    if strings.HasPrefix(line, ">") && ! ( strings.HasPrefix(line, ">>") && re_backlink.MatchString(strings.Split(line, " ")[0])) {
            // le ebin meme arrows
      markup += ""
      markup += html.EscapeString(line)
      markup += ""
    } else if strings.HasPrefix(line, "==") && strings.HasSuffix(line, "==") {
      // redtext
      markup += ""
      markup += html.EscapeString(line[2:len(line)-2])
      markup += ""
    } else {
      // regular line
      // for each word
      for _, word := range strings.Split(line, " ") {
        // check for backlink
        if re_backlink.MatchString(word) {
          markup += backlink(word)
        } else {
          // linkify as needed
          word = html.EscapeString(word)
          markup += xurls.Strict.ReplaceAllString(word, `<a href="$1">$1</a>`)
        }
        markup += " "
      }
    }
  }
  markup += "
"
  return
}

// format lines inside a code tag
func formatcodeline(line string) (markup string) {
  markup += html.EscapeString(line)
  markup += "\n"
  return
}

func memeposting(src string) (markup string) {
  found_tag := false
  tag_content := ""
  tag := ""
  // for each line...
  for _, line := range strings.Split(src, "\n") {
    // beginning of code tag ?
    if strings.Count(line, "[code]") > 0 {
      // yes there's a code tag
      found_tag = true
      tag = "code"
    } else if strings.Count(line, "[spoiler]") > 0 {
      // spoiler tag
      found_tag = true
      tag = "spoiler"
    } else if strings.Count(line, "[psy]") > 0 {
      // psy tag
      found_tag = true
      tag = "psy"
    }
    if found_tag {
      // collect content of tag
      tag_content += line + "\n"
      // end of our tag ?
      if strings.Count(line, "[/"+tag+"]") == 1 {
        // yah
        found_tag = false
        var tag_open, tag_close string
        if tag == "code" {
          tag_open = "
"
          tag_close = "
"
        } else if tag == "spoiler" {
          tag_open = ""
          tag_close = ""
        } else if tag == "psy" {
          tag_open = "
"
          tag_close = "
"
        }
        markup += tag_open
        // remove open tag, only once so we can have a code tag verbatum inside
        tag_content = strings.Replace(tag_content, "["+tag+"]", "", 1)
        // remove all close tags, should only have 1
        tag_content = strings.Replace(tag_content, "[/"+tag+"]", "", -1)
        // make into lines
        for _, tag_line := range strings.Split(tag_content, "\n") {
          if tag == "code" {
            markup += formatcodeline(tag_line)
          } else {
            markup += formatline(tag_line)
          }
        }
        // close pre tag
        markup += tag_close
        // reset content buffer
        tag_content = ""
      }
      // next line
      continue
    }
    // format line regularlly
    markup += formatline(line)
  }
  // flush the rest of an incomplete code tag
  for _, line := range strings.Split(tag_content, "\n") {
    markup += formatline(line)
  }
  return
}
<|endoftext|>


<commit_before>package cmd

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

	"reflect"

	"github.com/bouk/monkey"
	"github.com/evandroflores/claimr/model"
	"github.com/nlopes/slack"
	"github.com/shomali11/proper"
	"github.com/shomali11/slacker"
	"github.com/stretchr/testify/assert"
)

func createMockReply(t *testing.T, expectedMsg string) (*slacker.Response, *monkey.PatchGuard) {
	var mockResponse *slacker.Response

	patchReply := monkey.PatchInstanceMethod(reflect.TypeOf(mockResponse), "Reply",
		func(response *slacker.Response, msg string) {
			assert.Equal(t, expectedMsg, msg)
		})

	_ = monkey.PatchInstanceMethod(reflect.TypeOf(mockResponse), "Typing",
		func(response *slacker.Response) {})

	return mockResponse, patchReply
}

func createMockRequest(t *testing.T, params map[string]string) (*slacker.Request, *monkey.PatchGuard) {
	var mockRequest *slacker.Request

	patchParam := monkey.PatchInstanceMethod(reflect.TypeOf(mockRequest), "Param",
		func(r *slacker.Request, key string) string {
			if params == nil {
				return ""
			}
			return params[key]
		})
	return mockRequest, patchParam
}

func createMockEvent(t *testing.T, team string, channel string, user string) *monkey.PatchGuard {
	patchGetEvent := monkey.Patch(getEvent,
		func(request *slacker.Request) ClaimrEvent {
			return ClaimrEvent{team, channel, user}
		})
	return patchGetEvent
}

func TestCmdNotImplemented(t *testing.T) {
	mockResponse, patchReply := createMockReply(t, Messages["not-implemented"])

	notImplemented(new(slacker.Request), mockResponse)

	patchReply.Unpatch()
}

func TestCmdCommandList(t *testing.T) {
	usageExpected := []string{
		"add <container-name>",
		"claim <container-name> <reason>",
		"free <container-name>",
		"list",
		"refresh-admins",
		"remove <container-name>",
		"show <container-name>",
		"log-level <level>",
		"purge",
	}
	commands := CommandList()

	assert.Len(t, commands, len(usageExpected))

	usageActual := []string{}

	for _, command := range commands {
		usageActual = append(usageActual, command.Usage)
	}

	assert.Subset(t, usageExpected, usageActual)
}

func TestCmdNotDirect(t *testing.T) {
	isDirect, err := isDirect("CHANNEL")
	assert.False(t, isDirect)
	assert.NoError(t, err)
}

func TestCmdDirect(t *testing.T) {
	direct, err := isDirect("DIRECT")
	assert.True(t, direct)
	assert.Error(t, err, Messages["direct-not-allowed"])
}

func TestMessageHasUser(t *testing.T) {
	hasUser, err := hasUserOnText("lorem ipsum <@USER>")
	assert.True(t, hasUser)
	assert.Error(t, err, Messages["shouldnt-mention-user"])
}

func TestMessageHasntUser(t *testing.T) {
	hasUser, err := hasUserOnText("lorem ipsum")
	assert.False(t, hasUser)
	assert.NoError(t, err)
}

func TestMessageHasChannel(t *testing.T) {
	hasChannel, err := hasChannelOnText("lorem ipsum <#CHANNEL>")
	assert.True(t, hasChannel)
	assert.Error(t, err, Messages["shouldnt-mention-channel"])
}

func TestMessageHasntChannel(t *testing.T) {
	hasChannel, err := hasChannelOnText("lorem ipsum")
	assert.False(t, hasChannel)
	assert.NoError(t, err)
}

func TestAllCmdsCheckingDirect(t *testing.T) {
	mockResponse, patchReply := createMockReply(t, Messages["direct-not-allowed"])
	patchGetEvent := createMockEvent(t, "team", "DIRECT", "user")

	for _, command := range commands {
		if !strings.Contains(command.Description, "admin-only") {
			command.Handler(nil, mockResponse)
		}
	}

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
}

func TestAllCmdsCheckingNoName(t *testing.T) {
	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(Messages["field-name-required"]))
	patchGetEvent := createMockEvent(t, "team", "channel", "user")
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": ""})

	for _, command := range CommandList() {
		if strings.Contains(command.Usage, "<container-name>") {
			command.Handler(mockRequest, mockResponse)
		}
	}

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAllCmdsErrorWhenGettingFromDB(t *testing.T) {

	guard := monkey.Patch(model.GetContainer,
		func(Team string, Channel string, Name string) (model.Container, error) {
			return model.Container{}, fmt.Errorf("simulated error")
		})

	teamName := "TestTeamList"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, "simulated error")
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, nil)

	for _, command := range commands {
		if strings.Contains(command.Usage, "<container-name>") {
			command.Handler(mockRequest, mockResponse)
		}
	}

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
	guard.Unpatch()

}

func TestNilGetEvent(t *testing.T) {
	event := getEvent(nil)
	assert.ObjectsAreEqual(ClaimrEvent{}, event)
}

func TestGetEventFromNSLopesEvent(t *testing.T) {
	var message slack.MessageEvent
	message.Team = "Team"
	message.Channel = "Channel"
	message.User = "User"
	request := slacker.NewRequest(nil, &message, &proper.Properties{})
	event := getEvent(request)
	assert.ObjectsAreEqual(ClaimrEvent{}, event)
}

func TestGetEventText(t *testing.T) {
	text := "Text"
	var message slack.MessageEvent
	message.Team = "Team"
	message.Channel = "Channel"
	message.User = "User"
	message.Text = text
	request := slacker.NewRequest(nil, &message, &proper.Properties{})

	assert.Equal(t, text, GetEventText(request))
}

func TestNonAdminTryAccessAdminOnlyCmds(t *testing.T) {
	teamName := "TestPurge"
	channelName := "TestChannel"
	userName := "NotAAdmin"

	mockResponse, patchReply := createMockReply(t, Messages["admin-only"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, _ := createMockRequest(t, nil)

	for _, command := range commands {
		if strings.Contains(command.Description, "admin-only") {
			command.Handler(mockRequest, mockResponse)
		}
	}

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
}

func TestNonAdminTryAccessAdminOnlyCmdsWhenEnvIsNotSet(t *testing.T) {
	teamName := "TestPurge"
	channelName := "TestChannel"
	userName := "NotAAdmin"

	currentEnv := os.Getenv("CLAIMR_SUPERUSER")
	os.Unsetenv("CLAIMR_SUPERUSER")
	defer func() { os.Setenv("CLAIMR_SUPERUSER", currentEnv) }()

	mockResponse, patchReply := createMockReply(t, Messages["admin-only"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, _ := createMockRequest(t, nil)

	for _, command := range commands {
		if strings.Contains(command.Description, "admin-only") {
			command.Handler(mockRequest, mockResponse)
		}
	}

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
}

func TestIsSuperUserAdmin(t *testing.T) {
	assert.True(t, isAdmin(os.Getenv("CLAIMR_SUPERUSER")))
}

func TestIsSuperUserAdminCaseInsensitive(t *testing.T) {
	assert.True(t, isAdmin(strings.ToLower(os.Getenv("CLAIMR_SUPERUSER"))))
}

func TestIsNotAdmin(t *testing.T) {
	assert.False(t, isAdmin("ANOTHER-USER"))
}
<commit_msg>isAdmin slack admin<commit_after>package cmd

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

	"reflect"

	"github.com/bouk/monkey"
	"github.com/evandroflores/claimr/model"
	"github.com/nlopes/slack"
	"github.com/shomali11/proper"
	"github.com/shomali11/slacker"
	"github.com/stretchr/testify/assert"
)

func createMockReply(t *testing.T, expectedMsg string) (*slacker.Response, *monkey.PatchGuard) {
	var mockResponse *slacker.Response

	patchReply := monkey.PatchInstanceMethod(reflect.TypeOf(mockResponse), "Reply",
		func(response *slacker.Response, msg string) {
			assert.Equal(t, expectedMsg, msg)
		})

	_ = monkey.PatchInstanceMethod(reflect.TypeOf(mockResponse), "Typing",
		func(response *slacker.Response) {})

	return mockResponse, patchReply
}

func createMockRequest(t *testing.T, params map[string]string) (*slacker.Request, *monkey.PatchGuard) {
	var mockRequest *slacker.Request

	patchParam := monkey.PatchInstanceMethod(reflect.TypeOf(mockRequest), "Param",
		func(r *slacker.Request, key string) string {
			if params == nil {
				return ""
			}
			return params[key]
		})
	return mockRequest, patchParam
}

func createMockEvent(t *testing.T, team string, channel string, user string) *monkey.PatchGuard {
	patchGetEvent := monkey.Patch(getEvent,
		func(request *slacker.Request) ClaimrEvent {
			return ClaimrEvent{team, channel, user}
		})
	return patchGetEvent
}

func TestCmdNotImplemented(t *testing.T) {
	mockResponse, patchReply := createMockReply(t, Messages["not-implemented"])

	notImplemented(new(slacker.Request), mockResponse)

	patchReply.Unpatch()
}

func TestCmdCommandList(t *testing.T) {
	usageExpected := []string{
		"add <container-name>",
		"claim <container-name> <reason>",
		"free <container-name>",
		"list",
		"refresh-admins",
		"remove <container-name>",
		"show <container-name>",
		"log-level <level>",
		"purge",
	}
	commands := CommandList()

	assert.Len(t, commands, len(usageExpected))

	usageActual := []string{}

	for _, command := range commands {
		usageActual = append(usageActual, command.Usage)
	}

	assert.Subset(t, usageExpected, usageActual)
}

func TestCmdNotDirect(t *testing.T) {
	isDirect, err := isDirect("CHANNEL")
	assert.False(t, isDirect)
	assert.NoError(t, err)
}

func TestCmdDirect(t *testing.T) {
	direct, err := isDirect("DIRECT")
	assert.True(t, direct)
	assert.Error(t, err, Messages["direct-not-allowed"])
}

func TestMessageHasUser(t *testing.T) {
	hasUser, err := hasUserOnText("lorem ipsum <@USER>")
	assert.True(t, hasUser)
	assert.Error(t, err, Messages["shouldnt-mention-user"])
}

func TestMessageHasntUser(t *testing.T) {
	hasUser, err := hasUserOnText("lorem ipsum")
	assert.False(t, hasUser)
	assert.NoError(t, err)
}

func TestMessageHasChannel(t *testing.T) {
	hasChannel, err := hasChannelOnText("lorem ipsum <#CHANNEL>")
	assert.True(t, hasChannel)
	assert.Error(t, err, Messages["shouldnt-mention-channel"])
}

func TestMessageHasntChannel(t *testing.T) {
	hasChannel, err := hasChannelOnText("lorem ipsum")
	assert.False(t, hasChannel)
	assert.NoError(t, err)
}

func TestAllCmdsCheckingDirect(t *testing.T) {
	mockResponse, patchReply := createMockReply(t, Messages["direct-not-allowed"])
	patchGetEvent := createMockEvent(t, "team", "DIRECT", "user")

	for _, command := range commands {
		if !strings.Contains(command.Description, "admin-only") {
			command.Handler(nil, mockResponse)
		}
	}

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
}

func TestAllCmdsCheckingNoName(t *testing.T) {
	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(Messages["field-name-required"]))
	patchGetEvent := createMockEvent(t, "team", "channel", "user")
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": ""})

	for _, command := range CommandList() {
		if strings.Contains(command.Usage, "<container-name>") {
			command.Handler(mockRequest, mockResponse)
		}
	}

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAllCmdsErrorWhenGettingFromDB(t *testing.T) {

	guard := monkey.Patch(model.GetContainer,
		func(Team string, Channel string, Name string) (model.Container, error) {
			return model.Container{}, fmt.Errorf("simulated error")
		})

	teamName := "TestTeamList"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, "simulated error")
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, nil)

	for _, command := range commands {
		if strings.Contains(command.Usage, "<container-name>") {
			command.Handler(mockRequest, mockResponse)
		}
	}

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
	guard.Unpatch()

}

func TestNilGetEvent(t *testing.T) {
	event := getEvent(nil)
	assert.ObjectsAreEqual(ClaimrEvent{}, event)
}

func TestGetEventFromNSLopesEvent(t *testing.T) {
	var message slack.MessageEvent
	message.Team = "Team"
	message.Channel = "Channel"
	message.User = "User"
	request := slacker.NewRequest(nil, &message, &proper.Properties{})
	event := getEvent(request)
	assert.ObjectsAreEqual(ClaimrEvent{}, event)
}

func TestGetEventText(t *testing.T) {
	text := "Text"
	var message slack.MessageEvent
	message.Team = "Team"
	message.Channel = "Channel"
	message.User = "User"
	message.Text = text
	request := slacker.NewRequest(nil, &message, &proper.Properties{})

	assert.Equal(t, text, GetEventText(request))
}

func TestNonAdminTryAccessAdminOnlyCmds(t *testing.T) {
	teamName := "TestPurge"
	channelName := "TestChannel"
	userName := "NotAAdmin"

	mockResponse, patchReply := createMockReply(t, Messages["admin-only"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, _ := createMockRequest(t, nil)

	for _, command := range commands {
		if strings.Contains(command.Description, "admin-only") {
			command.Handler(mockRequest, mockResponse)
		}
	}

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
}

func TestNonAdminTryAccessAdminOnlyCmdsWhenEnvIsNotSet(t *testing.T) {
	teamName := "TestPurge"
	channelName := "TestChannel"
	userName := "NotAAdmin"

	currentEnv := os.Getenv("CLAIMR_SUPERUSER")
	os.Unsetenv("CLAIMR_SUPERUSER")
	defer func() { os.Setenv("CLAIMR_SUPERUSER", currentEnv) }()

	mockResponse, patchReply := createMockReply(t, Messages["admin-only"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, _ := createMockRequest(t, nil)

	for _, command := range commands {
		if strings.Contains(command.Description, "admin-only") {
			command.Handler(mockRequest, mockResponse)
		}
	}

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
}

func TestIsSuperUserAdmin(t *testing.T) {
	assert.True(t, isAdmin(os.Getenv("CLAIMR_SUPERUSER")))
}

func TestIsSuperUserAdminCaseInsensitive(t *testing.T) {
	assert.True(t, isAdmin(strings.ToLower(os.Getenv("CLAIMR_SUPERUSER"))))
}

func TestIsNotAdmin(t *testing.T) {
	assert.False(t, isAdmin("ANOTHER-USER"))
}

func TestIsSlackAdmin(t *testing.T) {
	currentAdmins := model.Admins
	model.Admins = []model.Admin{}
	defer func() {
		model.Admins = currentAdmins
	}()
	model.Admins = []model.Admin{{ID: "SlackAdmin", RealName: "Fake Slack Admin"}}

	assert.True(t, isAdmin("SlackAdmin"))
}
<|endoftext|>


<commit_before>package scheduler

import (
	"github.com/jinzhu/gorm"
	"github.com/nladuo/DLocker"
	"github.com/nladuo/go-webcrawler/model"
	"log"
	"sync"
	"time"
)

//scheduler use sql database as task and result queue
type SqlScheduler struct {
	db            *gorm.DB
	tasks         chan model.Task
	results       chan model.Result
	dLocker       *DLocker.Dlocker
	basicLocker   *sync.Mutex
	isCluster     bool
	getTaskChan   chan byte
	getResultChan chan byte
	addTaskChan   chan byte
	addResultChan chan byte
}

func newSqlScheduler(db *gorm.DB) *SqlScheduler {
	var scheduler SqlScheduler
	scheduler.db = db
	scheduler.tasks = make(chan model.Task, chan_buffer_size)
	scheduler.results = make(chan model.Result, chan_buffer_size)
	scheduler.addResultChan = make(chan byte, chan_buffer_size)
	scheduler.getResultChan = make(chan byte, chan_buffer_size)
	scheduler.addTaskChan = make(chan byte, chan_buffer_size)
	scheduler.getTaskChan = make(chan byte, chan_buffer_size)
	createTable(db)
	go scheduler.logTaskAndResultNum()
	return &scheduler
}

func (this *SqlScheduler) logTaskAndResultNum() {
	for {
		time.Sleep(3 * time.Minute)
		log.Println("task num:", len(this.tasks))
		log.Println("result num:", len(this.results))
	}
}

func NewDistributedSqlScheduler(db *gorm.DB, basePath, prefix string, timeout time.Duration) *SqlScheduler {
	scheduler := newSqlScheduler(db)
	scheduler.dLocker = DLocker.NewLocker(basePath, prefix, timeout)
	scheduler.isCluster = true
	go scheduler.manipulateDataLoop()
	return scheduler
}

func NewLocalSqlScheduler(db *gorm.DB) *SqlScheduler {
	scheduler := newSqlScheduler(db)
	scheduler.basicLocker = &sync.Mutex{}
	scheduler.isCluster = false
	go scheduler.manipulateDataLoop()
	return scheduler
}

func (this *SqlScheduler) lock() {
	if this.isCluster {
		for !this.dLocker.Lock() {
		}
	} else {
		this.basicLocker.Lock()
	}
}

func (this *SqlScheduler) unLock() {
	if this.isCluster {
		this.dLocker.Unlock()
	} else {
		this.basicLocker.Unlock()
	}
}

//serialize data(task or result) and store into sql db.
//  or unserialize data from sql db.
func (this *SqlScheduler) manipulateDataLoop() {
	for {
		select {
		case <-this.addTaskChan: // add task does not need lock
			if len(this.tasks) > store_to_sql_count {
				for i := 0; i < store_count; i++ {
					t := <-this.tasks
					taskStr, err := t.Serialize()
					if err != nil {
						continue
					}
					addTask(this.db, taskStr)
				}
			}
		case <-this.getTaskChan: // get task does need lock
			this.lock()
			if len(this.tasks) < extract_count {
				tasks := getTasks(this.db, extract_count)
				for i := 0; i < len(tasks); i++ {
					t, err := model.UnSerializeTask(tasks[i].Data)
					if err != nil {
						continue
					}
					this.tasks <- t
				}
			}
			this.unLock()
		case <-this.addResultChan: // add result does not need lock
			if len(this.results) > store_to_sql_count {
				for i := 0; i < store_count; i++ {
					r := <-this.results
					resultStr, err := r.Serialize()
					if err != nil {
						continue
					}
					addResult(this.db, resultStr)
				}
			}
		case <-this.getResultChan: // get result does need lock
			this.lock()
			if len(this.results) < extract_from_sql_count {
				results := getResults(this.db, extract_count)
				for i := 0; i < len(results); i++ {
					r, err := model.UnSerializeResult(results[i].Data)
					if err != nil {
						continue
					}
					this.results <- r
				}
			}
			this.unLock()
		}
	}
}

func (this *SqlScheduler) AddTask(task model.Task) {
	if len(this.tasks) > store_to_sql_count {
		this.addTaskChan <- byte(1)
	}
	this.tasks <- task
}

func (this *SqlScheduler) GetTask() model.Task {
	if len(this.tasks) < extract_from_sql_count {
		this.getTaskChan <- byte(1)
	}
	return <-this.tasks
}

func (this *SqlScheduler) AddResult(result model.Result) {
	if len(this.results) > store_to_sql_count {
		this.addResultChan <- byte(1)
	}
	this.results <- result
}

func (this *SqlScheduler) GetResult() model.Result {
	if len(this.results) < extract_from_sql_count {
		this.getResultChan <- byte(1)
	}
	return <-this.results
}

// the serialization for model.Task in sql database
type Task struct {
	ID   uint   `sql:"AUTO_INCREMENT"`
	Data string `sql:"size:max"`
}

// the serialization for model.Result in sql database
type Result struct {
	ID   uint   `sql:"AUTO_INCREMENT"`
	Data string `sql:"size:max"`
}

func createTable(db *gorm.DB) {
	if !db.HasTable(&Task{}) {
		db.CreateTable(&Task{})
	}
	if !db.HasTable(&Result{}) {
		db.CreateTable(&Result{})
	}
}

func addResult(db *gorm.DB, data string) {
	db.Create(&Result{Data: data})
}

func getResults(db *gorm.DB, limit int) []Result {
	results := []Result{}
	t := db.Begin()
	t.Limit(limit).Find(&results)
	for i := 0; i < len(results); i++ {
		rowsAffected := t.Delete(&results[i]).RowsAffected
		if rowsAffected == 0 {
			t.Rollback()
			log.Println("getResults---->rollback")
			return []Result{}
		}
	}
	t.Commit()
	return results
}

func getResultSize(db *gorm.DB) int {
	count := 0
	db.Model(&Result{}).Count(&count)
	return count
}

func addTask(db *gorm.DB, data string) {
	db.Create(&Task{Data: data})
}

func getTasks(db *gorm.DB, limit int) []Task {
	tasks := []Task{}
	t := db.Begin()
	//get tasks form db
	t.Limit(limit).Find(&tasks)
	// delete the tasks
	for i := 0; i < len(tasks); i++ {
		rowsAffected := t.Delete(&tasks[i]).RowsAffected
		if rowsAffected == 0 {
			t.Rollback()
			log.Println("getTasks---->rollback")
			return []Task{}
		}
	}
	t.Commit()
	return tasks
}

func getTaskSize(db *gorm.DB) int {
	count := 0
	db.Model(&Task{}).Count(&count)
	return count
}
<commit_msg>update api with DLocker.<commit_after>package scheduler

import (
	"github.com/jinzhu/gorm"
	"github.com/nladuo/DLocker"
	"github.com/nladuo/go-webcrawler/model"
	"log"
	"sync"
	"time"
)

//scheduler use sql database as task and result queue
type SqlScheduler struct {
	db            *gorm.DB
	tasks         chan model.Task
	results       chan model.Result
	dLocker       *DLocker.Dlocker
	basicLocker   *sync.Mutex
	isCluster     bool
	getTaskChan   chan byte
	getResultChan chan byte
	addTaskChan   chan byte
	addResultChan chan byte
}

func newSqlScheduler(db *gorm.DB) *SqlScheduler {
	var scheduler SqlScheduler
	scheduler.db = db
	scheduler.tasks = make(chan model.Task, chan_buffer_size)
	scheduler.results = make(chan model.Result, chan_buffer_size)
	scheduler.addResultChan = make(chan byte, chan_buffer_size)
	scheduler.getResultChan = make(chan byte, chan_buffer_size)
	scheduler.addTaskChan = make(chan byte, chan_buffer_size)
	scheduler.getTaskChan = make(chan byte, chan_buffer_size)
	createTable(db)
	go scheduler.logTaskAndResultNum()
	return &scheduler
}

func (this *SqlScheduler) logTaskAndResultNum() {
	for {
		time.Sleep(3 * time.Minute)
		log.Println("task num:", len(this.tasks))
		log.Println("result num:", len(this.results))
	}
}

func NewDistributedSqlScheduler(db *gorm.DB, basePath, prefix string, timeout time.Duration) *SqlScheduler {
	scheduler := newSqlScheduler(db)
	scheduler.dLocker = DLocker.NewLocker(basePath, prefix, timeout)
	scheduler.isCluster = true
	go scheduler.manipulateDataLoop()
	return scheduler
}

func NewLocalSqlScheduler(db *gorm.DB) *SqlScheduler {
	scheduler := newSqlScheduler(db)
	scheduler.basicLocker = &sync.Mutex{}
	scheduler.isCluster = false
	go scheduler.manipulateDataLoop()
	return scheduler
}

func (this *SqlScheduler) lock() {
	if this.isCluster {
		this.dLocker.Lock()
	} else {
		this.basicLocker.Lock()
	}
}

func (this *SqlScheduler) unLock() {
	if this.isCluster {
		this.dLocker.Unlock()
	} else {
		this.basicLocker.Unlock()
	}
}

//serialize data(task or result) and store into sql db.
//  or unserialize data from sql db.
func (this *SqlScheduler) manipulateDataLoop() {
	for {
		select {
		case <-this.addTaskChan: // add task does not need lock
			if len(this.tasks) > store_to_sql_count {
				for i := 0; i < store_count; i++ {
					t := <-this.tasks
					taskStr, err := t.Serialize()
					if err != nil {
						continue
					}
					addTask(this.db, taskStr)
				}
			}
		case <-this.getTaskChan: // get task does need lock
			this.lock()
			if len(this.tasks) < extract_count {
				tasks := getTasks(this.db, extract_count)
				for i := 0; i < len(tasks); i++ {
					t, err := model.UnSerializeTask(tasks[i].Data)
					if err != nil {
						continue
					}
					this.tasks <- t
				}
			}
			this.unLock()
		case <-this.addResultChan: // add result does not need lock
			if len(this.results) > store_to_sql_count {
				for i := 0; i < store_count; i++ {
					r := <-this.results
					resultStr, err := r.Serialize()
					if err != nil {
						continue
					}
					addResult(this.db, resultStr)
				}
			}
		case <-this.getResultChan: // get result does need lock
			this.lock()
			if len(this.results) < extract_from_sql_count {
				results := getResults(this.db, extract_count)
				for i := 0; i < len(results); i++ {
					r, err := model.UnSerializeResult(results[i].Data)
					if err != nil {
						continue
					}
					this.results <- r
				}
			}
			this.unLock()
		}
	}
}

func (this *SqlScheduler) AddTask(task model.Task) {
	if len(this.tasks) > store_to_sql_count {
		this.addTaskChan <- byte(1)
	}
	this.tasks <- task
}

func (this *SqlScheduler) GetTask() model.Task {
	if len(this.tasks) < extract_from_sql_count {
		this.getTaskChan <- byte(1)
	}
	return <-this.tasks
}

func (this *SqlScheduler) AddResult(result model.Result) {
	if len(this.results) > store_to_sql_count {
		this.addResultChan <- byte(1)
	}
	this.results <- result
}

func (this *SqlScheduler) GetResult() model.Result {
	if len(this.results) < extract_from_sql_count {
		this.getResultChan <- byte(1)
	}
	return <-this.results
}

// the serialization for model.Task in sql database
type Task struct {
	ID   uint   `sql:"AUTO_INCREMENT"`
	Data string `sql:"size:max"`
}

// the serialization for model.Result in sql database
type Result struct {
	ID   uint   `sql:"AUTO_INCREMENT"`
	Data string `sql:"size:max"`
}

func createTable(db *gorm.DB) {
	if !db.HasTable(&Task{}) {
		db.CreateTable(&Task{})
	}
	if !db.HasTable(&Result{}) {
		db.CreateTable(&Result{})
	}
}

func addResult(db *gorm.DB, data string) {
	db.Create(&Result{Data: data})
}

func getResults(db *gorm.DB, limit int) []Result {
	results := []Result{}
	t := db.Begin()
	t.Limit(limit).Find(&results)
	for i := 0; i < len(results); i++ {
		rowsAffected := t.Delete(&results[i]).RowsAffected
		if rowsAffected == 0 {
			t.Rollback()
			log.Println("getResults---->rollback")
			return []Result{}
		}
	}
	t.Commit()
	return results
}

func getResultSize(db *gorm.DB) int {
	count := 0
	db.Model(&Result{}).Count(&count)
	return count
}

func addTask(db *gorm.DB, data string) {
	db.Create(&Task{Data: data})
}

func getTasks(db *gorm.DB, limit int) []Task {
	tasks := []Task{}
	t := db.Begin()
	//get tasks form db
	t.Limit(limit).Find(&tasks)
	// delete the tasks
	for i := 0; i < len(tasks); i++ {
		rowsAffected := t.Delete(&tasks[i]).RowsAffected
		if rowsAffected == 0 {
			t.Rollback()
			log.Println("getTasks---->rollback")
			return []Task{}
		}
	}
	t.Commit()
	return tasks
}

func getTaskSize(db *gorm.DB) int {
	count := 0
	db.Model(&Task{}).Count(&count)
	return count
}
<|endoftext|>


<commit_before>// BaruwaAPI Golang bindings for Baruwa REST API
// Copyright (C) 2019 Andrew Colin Kissa <andrew@topdog.za.net>

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

// Package cmd cmdline client for the Baruwa REST API
package cmd

import (
	cli "github.com/jawher/mow.cli"
)

// RegisterCommands registers all CLI commands
func (c *CLI) RegisterCommands() {
	// user
	c.Command("user", "manage user accounts", func(cmd *cli.Cmd) {
		cmd.Command("show", "show detailed information of a user account", userShow)
		cmd.Command("create", "create a new user account", userCreate)
		cmd.Command("update", "update a user account", userUpdate)
		cmd.Command("delete", "delete a user account", userDelete)
		cmd.Command("alias", "manage user alias addresses", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of an alias address", aliasShow)
			cmd.Command("create", "create a new alias address", aliasCreate)
			cmd.Command("update", "update an alias address", aliasUpdate)
			cmd.Command("delete", "delete an alias address", aliasDelete)
		})
	})
	// users
	c.Command("users", "list user accounts", usersList)
	// domain
	c.Command("domain", "manage domains", func(cmd *cli.Cmd) {
		cmd.Command("show", "show detailed information of a domain", domainShow)
		cmd.Command("create", "create a new domain", domainCreate)
		cmd.Command("update", "update a domain", domainUpdate)
		cmd.Command("delete", "delete a domain", domainDelete)
		cmd.Command("alias", "manage alias domains", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a domain alias", domainAliasShow)
			cmd.Command("create", "create a new domain alias", domainAliasCreate)
			cmd.Command("update", "update a domain alias", domainAliasUpdate)
			cmd.Command("delete", "delete a domain alias", domainAliasDelete)
		})
		cmd.Command("aliases", "list domain aliases", domainAliasList)
		cmd.Command("deliveryserver", "manage domain delivery servers", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a domain delivery server", domainDSShow)
			cmd.Command("create", "create a new domain delivery server", domainDSCreate)
			cmd.Command("update", "update a domain delivery server", domainDSUpdate)
			cmd.Command("delete", "delete a domain delivery server", domainDSDelete)
		})
		cmd.Command("deliveryservers", "list domain delivery servers", domainDSList)
		cmd.Command("userdeliveryserver", "manage user delivery servers", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a user delivery server", userDSShow)
			cmd.Command("create", "create a new user delivery server", userDSCreate)
			cmd.Command("update", "update a user delivery server", userDSUpdate)
			cmd.Command("delete", "delete a user delivery server", userDSDelete)
		})
		cmd.Command("userdeliveryservers", "list user delivery servers", userDSList)
		cmd.Command("authsetting", "manage authentication settings", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of an authentication setting", domainASShow)
			cmd.Command("create", "create a new authentication setting", domainASCreate)
			cmd.Command("update", "update a authentication setting", domainASUpdate)
			cmd.Command("delete", "delete a authentication setting", domainASDelete)
			cmd.Command("ldapsetting", "manage ldap settings", func(cmd *cli.Cmd) {
				cmd.Command("show", "show detailed information of an ldap setting", ldapShow)
				cmd.Command("create", "create a new ldap setting", ldapCreate)
				cmd.Command("update", "update a ldap setting", ldapUpdate)
				cmd.Command("delete", "delete a ldap setting", ldapDelete)
			})
			cmd.Command("radiussetting", "manage radius settings", func(cmd *cli.Cmd) {
				cmd.Command("show", "show detailed information of an radius setting", radiusShow)
				cmd.Command("create", "create a new radius setting", radiusCreate)
				cmd.Command("update", "update a radius setting", radiusUpdate)
				cmd.Command("delete", "delete a radius setting", radiusDelete)
			})
		})
		cmd.Command("authsettings", "list authentication settings", domainASList)
		cmd.Command("smarthost", "manage smarthosts", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a smarthost", domainSMShow)
			cmd.Command("create", "create a new smarthost", domainSMCreate)
			cmd.Command("update", "update a smarthost", domainSMUpdate)
			cmd.Command("delete", "delete a smarthost", domainSMDelete)
		})
		cmd.Command("smarthosts", "list smarthosts", domainSMList)
	})
	// domains
	c.Command("domains", "list domains", domainsList)
	// organization
	c.Command("organization", "manage organizations", func(cmd *cli.Cmd) {
		cmd.Command("show", "show detailed information of an organization", organizationShow)
		cmd.Command("create", "create a new organization", organizationCreate)
		cmd.Command("update", "update a organization", organizationUpdate)
		cmd.Command("delete", "delete a organization", organizationDelete)
		cmd.Command("smarthost", "manage smarthosts", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a smarthost", organizationSMShow)
			cmd.Command("create", "create a new smarthost", organizationSMCreate)
			cmd.Command("update", "update a smarthost", organizationSMUpdate)
			cmd.Command("delete", "delete a smarthost", organizationSMDelete)
		})
		cmd.Command("smarthosts", "list smarthosts", organizationSMList)
		cmd.Command("fallbackserver", "manage fallback servers", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a fallback server", organizationFSShow)
			cmd.Command("create", "create a new fallback server", organizationFSCreate)
			cmd.Command("update", "update a fallback server", organizationFSUpdate)
			cmd.Command("delete", "delete a fallback server", organizationFSDelete)
		})
		cmd.Command("fallbackservers", "list fallback servers", organizationFSList)
		cmd.Command("relaysetting", "manage relay settings", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a relay setting", organizationRelayShow)
			cmd.Command("create", "create a new relay setting", organizationRelayCreate)
			cmd.Command("update", "update a relay setting", organizationRelayUpdate)
			cmd.Command("delete", "delete a relay setting", organizationRelayDelete)
		})
	})
	// organizations
	c.Command("organizations", "list organizations", organizationsList)
	// systemstatus
	c.Command("systemstatus", "show system status", systemStatus)
}
<commit_msg>DOC: Add command docs<commit_after>// BaruwaAPI Golang bindings for Baruwa REST API
// Copyright (C) 2019 Andrew Colin Kissa <andrew@topdog.za.net>

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

package cmd

import (
	cli "github.com/jawher/mow.cli"
)

// RegisterCommands registers all CLI commands
func (c *CLI) RegisterCommands() {
	// user
	c.Command("user", "manage user accounts", func(cmd *cli.Cmd) {
		cmd.Command("show", "show detailed information of a user account", userShow)
		cmd.Command("create", "create a new user account", userCreate)
		cmd.Command("update", "update a user account", userUpdate)
		cmd.Command("delete", "delete a user account", userDelete)
		cmd.Command("alias", "manage user alias addresses", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of an alias address", aliasShow)
			cmd.Command("create", "create a new alias address", aliasCreate)
			cmd.Command("update", "update an alias address", aliasUpdate)
			cmd.Command("delete", "delete an alias address", aliasDelete)
		})
	})
	// users
	c.Command("users", "list user accounts", usersList)
	// domain
	c.Command("domain", "manage domains", func(cmd *cli.Cmd) {
		cmd.Command("show", "show detailed information of a domain", domainShow)
		cmd.Command("create", "create a new domain", domainCreate)
		cmd.Command("update", "update a domain", domainUpdate)
		cmd.Command("delete", "delete a domain", domainDelete)
		cmd.Command("alias", "manage alias domains", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a domain alias", domainAliasShow)
			cmd.Command("create", "create a new domain alias", domainAliasCreate)
			cmd.Command("update", "update a domain alias", domainAliasUpdate)
			cmd.Command("delete", "delete a domain alias", domainAliasDelete)
		})
		cmd.Command("aliases", "list domain aliases", domainAliasList)
		cmd.Command("deliveryserver", "manage domain delivery servers", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a domain delivery server", domainDSShow)
			cmd.Command("create", "create a new domain delivery server", domainDSCreate)
			cmd.Command("update", "update a domain delivery server", domainDSUpdate)
			cmd.Command("delete", "delete a domain delivery server", domainDSDelete)
		})
		cmd.Command("deliveryservers", "list domain delivery servers", domainDSList)
		cmd.Command("userdeliveryserver", "manage user delivery servers", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a user delivery server", userDSShow)
			cmd.Command("create", "create a new user delivery server", userDSCreate)
			cmd.Command("update", "update a user delivery server", userDSUpdate)
			cmd.Command("delete", "delete a user delivery server", userDSDelete)
		})
		cmd.Command("userdeliveryservers", "list user delivery servers", userDSList)
		cmd.Command("authsetting", "manage authentication settings", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of an authentication setting", domainASShow)
			cmd.Command("create", "create a new authentication setting", domainASCreate)
			cmd.Command("update", "update a authentication setting", domainASUpdate)
			cmd.Command("delete", "delete a authentication setting", domainASDelete)
			cmd.Command("ldapsetting", "manage ldap settings", func(cmd *cli.Cmd) {
				cmd.Command("show", "show detailed information of an ldap setting", ldapShow)
				cmd.Command("create", "create a new ldap setting", ldapCreate)
				cmd.Command("update", "update a ldap setting", ldapUpdate)
				cmd.Command("delete", "delete a ldap setting", ldapDelete)
			})
			cmd.Command("radiussetting", "manage radius settings", func(cmd *cli.Cmd) {
				cmd.Command("show", "show detailed information of an radius setting", radiusShow)
				cmd.Command("create", "create a new radius setting", radiusCreate)
				cmd.Command("update", "update a radius setting", radiusUpdate)
				cmd.Command("delete", "delete a radius setting", radiusDelete)
			})
		})
		cmd.Command("authsettings", "list authentication settings", domainASList)
		cmd.Command("smarthost", "manage smarthosts", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a smarthost", domainSMShow)
			cmd.Command("create", "create a new smarthost", domainSMCreate)
			cmd.Command("update", "update a smarthost", domainSMUpdate)
			cmd.Command("delete", "delete a smarthost", domainSMDelete)
		})
		cmd.Command("smarthosts", "list smarthosts", domainSMList)
	})
	// domains
	c.Command("domains", "list domains", domainsList)
	// organization
	c.Command("organization", "manage organizations", func(cmd *cli.Cmd) {
		cmd.Command("show", "show detailed information of an organization", organizationShow)
		cmd.Command("create", "create a new organization", organizationCreate)
		cmd.Command("update", "update a organization", organizationUpdate)
		cmd.Command("delete", "delete a organization", organizationDelete)
		cmd.Command("smarthost", "manage smarthosts", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a smarthost", organizationSMShow)
			cmd.Command("create", "create a new smarthost", organizationSMCreate)
			cmd.Command("update", "update a smarthost", organizationSMUpdate)
			cmd.Command("delete", "delete a smarthost", organizationSMDelete)
		})
		cmd.Command("smarthosts", "list smarthosts", organizationSMList)
		cmd.Command("fallbackserver", "manage fallback servers", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a fallback server", organizationFSShow)
			cmd.Command("create", "create a new fallback server", organizationFSCreate)
			cmd.Command("update", "update a fallback server", organizationFSUpdate)
			cmd.Command("delete", "delete a fallback server", organizationFSDelete)
		})
		cmd.Command("fallbackservers", "list fallback servers", organizationFSList)
		cmd.Command("relaysetting", "manage relay settings", func(cmd *cli.Cmd) {
			cmd.Command("show", "show detailed information of a relay setting", organizationRelayShow)
			cmd.Command("create", "create a new relay setting", organizationRelayCreate)
			cmd.Command("update", "update a relay setting", organizationRelayUpdate)
			cmd.Command("delete", "delete a relay setting", organizationRelayDelete)
		})
	})
	// organizations
	c.Command("organizations", "list organizations", organizationsList)
	// systemstatus
	c.Command("systemstatus", "show system status", systemStatus)
}
<|endoftext|>


<commit_before>package main

import (
	gocontext "context"
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/containerd/containerd/api/services/execution"
	"github.com/urfave/cli"
)

var listCommand = cli.Command{
	Name:  "list",
	Usage: "list containers",
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "quiet, q",
			Usage: "print only the container id",
		},
	},
	Action: func(context *cli.Context) error {
		quiet := context.Bool("quiet")
		containers, err := getExecutionService(context)
		if err != nil {
			return err
		}
		response, err := containers.List(gocontext.Background(), &execution.ListRequest{})
		if err != nil {
			return err
		}

		if quiet {
			for _, c := range response.Containers {
				fmt.Println(c.ID)
			}
		} else {
			w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
			fmt.Fprintln(w, "ID\tPID\tSTATUS")
			for _, c := range response.Containers {
				if _, err := fmt.Fprintf(w, "%s\t%d\t%s\n",
					c.ID,
					c.Pid,
					c.Status.String(),
				); err != nil {
					return err
				}
				if err := w.Flush(); err != nil {
					return err
				}
			}
		}

		return nil
	},
}
<commit_msg>Add an alias for ctr list<commit_after>package main

import (
	gocontext "context"
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/containerd/containerd/api/services/execution"
	"github.com/urfave/cli"
)

var listCommand = cli.Command{
	Name:    "list",
	Aliases: []string{"ls"},
	Usage:   "list containers",
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "quiet, q",
			Usage: "print only the container id",
		},
	},
	Action: func(context *cli.Context) error {
		quiet := context.Bool("quiet")
		containers, err := getExecutionService(context)
		if err != nil {
			return err
		}
		response, err := containers.List(gocontext.Background(), &execution.ListRequest{})
		if err != nil {
			return err
		}

		if quiet {
			for _, c := range response.Containers {
				fmt.Println(c.ID)
			}
		} else {
			w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
			fmt.Fprintln(w, "ID\tPID\tSTATUS")
			for _, c := range response.Containers {
				if _, err := fmt.Fprintf(w, "%s\t%d\t%s\n",
					c.ID,
					c.Pid,
					c.Status.String(),
				); err != nil {
					return err
				}
				if err := w.Flush(); err != nil {
					return err
				}
			}
		}

		return nil
	},
}
<|endoftext|>


<commit_before>package main

// BUG(ssw): JAP does not support TLS. To access the service with TLS (which you
//           really should be doing), use a reverse proxy such as Nginx.

import (
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"flag"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/rpc"
	"net/rpc/jsonrpc"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"

	"github.com/jitsi/jap"
	"golang.org/x/net/context"
	"golang.org/x/net/netutil"
	"golang.org/x/net/trace"
	"golang.org/x/text/language"
)

var (
	addr, pubDir, tmplDir, keyPath     string
	googleClientSecret, googleClientID string
	originURL                          string
	maxConns, rpcRetries               int
	rpcAddr, rpcMethod, rpcCodec       string

	tmpl *template.Template
)

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "%s\n\nUsage of %s:\n", help, os.Args[0])
		flag.PrintDefaults()
	}

	flag.StringVar(&addr, "http", ":http-alt", "The address to listen on.")
	flag.StringVar(&pubDir, "public", "public/", "A directory containing static files to serve.")
	flag.StringVar(&tmplDir, "templates", "templates/", "A directory containing templates to render.")
	flag.StringVar(&keyPath, "key", os.Getenv("JAP_PRIVATE_KEY_PATH"), "An RSA private key in PEM format to use for signing tokens. Defaults to $JAP_PRIVATE_KEY_PATH.")
	flag.StringVar(&originURL, "origin", "", "A domain that the /login endpoint will send a postMessage too (eg. https://meet.jit.si).")
	flag.StringVar(&rpcAddr, "rpcaddr", "", "An address that can be used to make RPC calls to verify permissions for a user.")
	flag.StringVar(&rpcMethod, "rpc", "Permissions.Check", "The RPC call to make to rcpaddr. This should be a function that takes a string (the token) and replies with a boolean. It should be compatible with Go's net/rpc package.")
	flag.StringVar(&rpcCodec, "rpccodec", "gob", `The type of RPC call to make (either "gob" for Go gobs or "json" for JSON-RPC).`)
	flag.IntVar(&rpcRetries, "rpcretries", 3, "The number of times to retry making RPC calls.")
	flag.IntVar(&maxConns, "maxconns", 0, "The maximum number of connections to service at once or 0 for unlimited.")
	flag.Parse()

	googleClientID = os.Getenv("GOOGLE_CLIENT_ID")
	googleClientSecret = os.Getenv("GOOGLE_CLIENT_SECRET")

	loadTemplates()

	sigs := make(chan os.Signal)
	signal.Notify(sigs, syscall.SIGHUP)

	// Handle signals
	go func() {
		var s os.Signal
		for {
			s = <-sigs
			switch s {
			case syscall.SIGHUP:
				log.Println("Received SIGHUP: reloading templates…")
				loadTemplates()
			}
		}
	}()
}

// Load all templates found in the tmplDir directory; if any of them contain
// errors, panic.
func loadTemplates() {
	files, err := filepath.Glob(filepath.Join(tmplDir, "*.tmpl"))
	switch {
	case err != nil:
		log.Fatal(err)
	case len(files) < 1:
		log.Fatalf("No templates found in %s", tmplDir)
	}
	tmpl = template.Must(template.New("jap").ParseFiles(files...))
}

func loadRSAKeyFromPEM(pembytes []byte) (*rsa.PrivateKey, error) {
	if len(pembytes) == 0 {
		return nil, errors.New("No pem data found")
	}
	var blk *pem.Block
	for {
		blk, pembytes = pem.Decode(pembytes)
		if blk.Type == "RSA PRIVATE KEY" {
			return x509.ParsePKCS1PrivateKey(blk.Bytes)
		}
	}
	return nil, fmt.Errorf("No RSA private key found in pem file %s", keyPath)
}

// TODO: Add incremental backoff and dialer retries.
func dialRPC() (rpcClient *rpc.Client, err error) {
	if rpcAddr != "" && rpcMethod != "" {
		log.Printf("Dialing RPC server at %s…", rpcAddr)
		switch rpcCodec {
		case "json":
			return jsonrpc.Dial("tcp", rpcAddr)
		case "gob":
			return rpc.DialHTTP("tcp", rpcAddr)
		default:
			return nil, fmt.Errorf("No such RPC codec %s", rpcCodec)
		}
	}
	return nil, nil
}

func main() {
	if keyPath == "" && os.Getenv("JAP_PRIVATE_KEY") == "" {
		log.Fatalf("No private key specified. Try: %s -help", os.Args[0])
	}

	var pembytes []byte
	var err error
	if keyenv := os.Getenv("JAP_PRIVATE_KEY"); keyenv != "" {
		pembytes = []byte(keyenv)
	} else {
		pembytes, err = ioutil.ReadFile(keyPath)
	}
	if err != nil {
		log.Fatal(err)
	}
	key, err := loadRSAKeyFromPEM(pembytes)
	if err != nil {
		log.Fatal(err)
	}

	var rpcClient *rpc.Client
	if rpcClient, err = dialRPC(); err != nil {
		log.Println("Failed to dial RPC server:", err)
	}

	log.Printf("Starting server on %s…\n", addr)

	var permCheck jap.PermissionChecker
	if rpcAddr != "" && rpcMethod != "" {
		permCheck = func(tok string) (b bool, err error) {
			for i := 0; i < rpcRetries; i++ {
				err = rpcClient.Call(rpcMethod, tok, &b)
				log.Println("CHECKED:", b, err)
				switch err {
				case nil:
					return b, err
				case rpc.ErrShutdown, io.ErrUnexpectedEOF:
					rpcClient, _ = dialRPC()
					continue
				}
			}
			return false, err
		}
	}
	http.HandleFunc("/googlelogin", jap.GoogleLogin(
		jap.NewCIDContext(context.Background(), googleClientID), key, permCheck))
	http.HandleFunc("/login", loginHandler(context.Background()))
	if pubDir != "" {
		http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(pubDir))))
	}

	l, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatal(err)
	}
	if maxConns > 0 {
		l = netutil.LimitListener(l, maxConns)
	}
	log.Fatal(http.Serve(l, nil))
}

func loginHandler(ctx context.Context) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		tr := trace.New("jap.login", r.URL.Path)
		defer tr.Finish()

		tr.LazyPrintf("Executing login.tmpl…")
		err := tmpl.ExecuteTemplate(w, "login.tmpl", Login{
			Lang:           language.English,
			GoogleClientID: googleClientID,
			TargetOrigin:   originURL,
		})
		if err != nil {
			tr.LazyPrintf("Error exeuting login.tmpl:", err.Error())
			tr.SetError()
			return
		}
		tr.LazyPrintf("Done executing login.tmpl…")
	}
}

// Login represents all the information we need to show the login window.
type Login struct {
	Lang           language.Tag
	GoogleClientID string
	TargetOrigin   string
}
<commit_msg>Fix potential infinite loop<commit_after>package main

// BUG(ssw): JAP does not support TLS. To access the service with TLS (which you
//           really should be doing), use a reverse proxy such as Nginx.

import (
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"flag"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/rpc"
	"net/rpc/jsonrpc"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"

	"github.com/jitsi/jap"
	"golang.org/x/net/context"
	"golang.org/x/net/netutil"
	"golang.org/x/net/trace"
	"golang.org/x/text/language"
)

var (
	addr, pubDir, tmplDir, keyPath     string
	googleClientSecret, googleClientID string
	originURL                          string
	maxConns, rpcRetries               int
	rpcAddr, rpcMethod, rpcCodec       string

	tmpl *template.Template
)

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "%s\n\nUsage of %s:\n", help, os.Args[0])
		flag.PrintDefaults()
	}

	flag.StringVar(&addr, "http", ":http-alt", "The address to listen on.")
	flag.StringVar(&pubDir, "public", "public/", "A directory containing static files to serve.")
	flag.StringVar(&tmplDir, "templates", "templates/", "A directory containing templates to render.")
	flag.StringVar(&keyPath, "key", os.Getenv("JAP_PRIVATE_KEY_PATH"), "An RSA private key in PEM format to use for signing tokens. Defaults to $JAP_PRIVATE_KEY_PATH.")
	flag.StringVar(&originURL, "origin", "", "A domain that the /login endpoint will send a postMessage too (eg. https://meet.jit.si).")
	flag.StringVar(&rpcAddr, "rpcaddr", "", "An address that can be used to make RPC calls to verify permissions for a user.")
	flag.StringVar(&rpcMethod, "rpc", "Permissions.Check", "The RPC call to make to rcpaddr. This should be a function that takes a string (the token) and replies with a boolean. It should be compatible with Go's net/rpc package.")
	flag.StringVar(&rpcCodec, "rpccodec", "gob", `The type of RPC call to make (either "gob" for Go gobs or "json" for JSON-RPC).`)
	flag.IntVar(&rpcRetries, "rpcretries", 3, "The number of times to retry making RPC calls.")
	flag.IntVar(&maxConns, "maxconns", 0, "The maximum number of connections to service at once or 0 for unlimited.")
	flag.Parse()

	googleClientID = os.Getenv("GOOGLE_CLIENT_ID")
	googleClientSecret = os.Getenv("GOOGLE_CLIENT_SECRET")

	loadTemplates()

	sigs := make(chan os.Signal)
	signal.Notify(sigs, syscall.SIGHUP)

	// Handle signals
	go func() {
		var s os.Signal
		for {
			s = <-sigs
			switch s {
			case syscall.SIGHUP:
				log.Println("Received SIGHUP: reloading templates…")
				loadTemplates()
			}
		}
	}()
}

// Load all templates found in the tmplDir directory; if any of them contain
// errors, panic.
func loadTemplates() {
	files, err := filepath.Glob(filepath.Join(tmplDir, "*.tmpl"))
	switch {
	case err != nil:
		log.Fatal(err)
	case len(files) < 1:
		log.Fatalf("No templates found in %s", tmplDir)
	}
	tmpl = template.Must(template.New("jap").ParseFiles(files...))
}

func loadRSAKeyFromPEM(pembytes []byte) (*rsa.PrivateKey, error) {
	if len(pembytes) == 0 {
		return nil, errors.New("No pem data found")
	}
	var blk *pem.Block
	for {
		blk, pembytes = pem.Decode(pembytes)
		if blk == nil {
			break
		}
		if blk.Type == "RSA PRIVATE KEY" {
			return x509.ParsePKCS1PrivateKey(blk.Bytes)
		}
	}
	return nil, fmt.Errorf("No RSA private key found in pem file %s", keyPath)
}

// TODO: Add incremental backoff and dialer retries.
func dialRPC() (rpcClient *rpc.Client, err error) {
	if rpcAddr != "" && rpcMethod != "" {
		log.Printf("Dialing RPC server at %s…", rpcAddr)
		switch rpcCodec {
		case "json":
			return jsonrpc.Dial("tcp", rpcAddr)
		case "gob":
			return rpc.DialHTTP("tcp", rpcAddr)
		default:
			return nil, fmt.Errorf("No such RPC codec %s", rpcCodec)
		}
	}
	return nil, nil
}

func main() {
	if keyPath == "" && os.Getenv("JAP_PRIVATE_KEY") == "" {
		log.Fatalf("No private key specified. Try: %s -help", os.Args[0])
	}

	var pembytes []byte
	var err error
	if keyenv := os.Getenv("JAP_PRIVATE_KEY"); keyenv != "" {
		pembytes = []byte(keyenv)
	} else {
		pembytes, err = ioutil.ReadFile(keyPath)
	}
	if err != nil {
		log.Fatal(err)
	}
	key, err := loadRSAKeyFromPEM(pembytes)
	if err != nil {
		log.Fatal(err)
	}

	var rpcClient *rpc.Client
	if rpcClient, err = dialRPC(); err != nil {
		log.Println("Failed to dial RPC server:", err)
	}

	log.Printf("Starting server on %s…\n", addr)

	var permCheck jap.PermissionChecker
	if rpcAddr != "" && rpcMethod != "" {
		permCheck = func(tok string) (b bool, err error) {
			for i := 0; i < rpcRetries; i++ {
				err = rpcClient.Call(rpcMethod, tok, &b)
				log.Println("CHECKED:", b, err)
				switch err {
				case nil:
					return b, err
				case rpc.ErrShutdown, io.ErrUnexpectedEOF:
					rpcClient, _ = dialRPC()
					continue
				}
			}
			return false, err
		}
	}
	http.HandleFunc("/googlelogin", jap.GoogleLogin(
		jap.NewCIDContext(context.Background(), googleClientID), key, permCheck))
	http.HandleFunc("/login", loginHandler(context.Background()))
	if pubDir != "" {
		http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(pubDir))))
	}

	l, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatal(err)
	}
	if maxConns > 0 {
		l = netutil.LimitListener(l, maxConns)
	}
	log.Fatal(http.Serve(l, nil))
}

func loginHandler(ctx context.Context) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		tr := trace.New("jap.login", r.URL.Path)
		defer tr.Finish()

		tr.LazyPrintf("Executing login.tmpl…")
		err := tmpl.ExecuteTemplate(w, "login.tmpl", Login{
			Lang:           language.English,
			GoogleClientID: googleClientID,
			TargetOrigin:   originURL,
		})
		if err != nil {
			tr.LazyPrintf("Error exeuting login.tmpl:", err.Error())
			tr.SetError()
			return
		}
		tr.LazyPrintf("Done executing login.tmpl…")
	}
}

// Login represents all the information we need to show the login window.
type Login struct {
	Lang           language.Tag
	GoogleClientID string
	TargetOrigin   string
}
<|endoftext|>