<commit_before>package qshell

import (
	"bufio"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/qiniu/api/auth/digest"
	fio "github.com/qiniu/api/io"
	rio "github.com/qiniu/api/resumable/io"
	"github.com/qiniu/api/rs"
	"github.com/qiniu/log"
	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/opt"
	"io/ioutil"
	"os"
	"os/user"
	"runtime"
	"strconv"
	"strings"
	"sync"
)

/*
Config file like:

{
	"src_dir" 		:	"/Users/jemy/Photos",
	"access_key" 	:	"<Your AccessKey>",
	"secret_key"	:	"<Your SecretKey>",
	"bucket"		:	"test-bucket",
	"ignore_dir"	:	false,
	"key_prefix"	:	"2014/12/01/",
	"overwrite"		:	false
}

or without key_prefix and ignore_dir

{
	"src_dir" 		:	"/Users/jemy/Photos",
	"access_key" 	:	"<Your AccessKey>",
	"secret_key"	:	"<Your SecretKey>",
	"bucket"		:	"test-bucket",
}
*/

const (
	PUT_THRESHOLD           int64 = 100 * 1 << 20
	MIN_UPLOAD_THREAD_COUNT int64 = 1
	MAX_UPLOAD_THREAD_COUNT int64 = 100
)

type UploadConfig struct {
	SrcDir    string `json:"src_dir"`
	AccessKey string `json:"access_key"`
	SecretKey string `json:"secret_key"`
	Bucket    string `json:"bucket"`
	KeyPrefix string `json:"key_prefix,omitempty"`
	IgnoreDir bool   `json:"ignore_dir,omitempty"`
	Overwrite bool   `json:"overwrite,omitempty"`
}

func QiniuUpload(threadCount int, uploadConfigFile string) {
	fp, err := os.Open(uploadConfigFile)
	if err != nil {
		log.Error(fmt.Sprintf("Open upload config file `%s' error due to `%s'", uploadConfigFile, err))
		return
	}
	defer fp.Close()
	configData, err := ioutil.ReadAll(fp)
	if err != nil {
		log.Error(fmt.Sprintf("Read upload config file `%s' error due to `%s'", uploadConfigFile, err))
		return
	}
	var uploadConfig UploadConfig
	err = json.Unmarshal(configData, &uploadConfig)
	if err != nil {
		log.Error(fmt.Sprintf("Parse upload config file `%s' errror due to `%s'", uploadConfigFile, err))
		return
	}
	if _, err := os.Stat(uploadConfig.SrcDir); err != nil {
		log.Error("Upload config error for parameter `SrcDir`,", err)
		return
	}
	dirCache := DirCache{}
	currentUser, err := user.Current()
	if err != nil {
		log.Error("Failed to get current user", err)
		return
	}
	jobId := base64.URLEncoding.EncodeToString([]byte(uploadConfig.SrcDir + ":" + uploadConfig.Bucket))
	storePath := fmt.Sprintf("%s/.qshell/qupload/%s", currentUser.HomeDir, jobId)
	err = os.MkdirAll(storePath, 0775)
	if err != nil {
		log.Error(fmt.Sprintf("Failed to mkdir `%s' due to `%s'", storePath, err))
		return
	}
	cacheFileName := fmt.Sprintf("%s/%s.cache", storePath, jobId)
	leveldbFileName := fmt.Sprintf("%s/%s.ldb", storePath, jobId)
	totalFileCount := dirCache.Cache(uploadConfig.SrcDir, cacheFileName)
	ldb, err := leveldb.OpenFile(leveldbFileName, nil)
	if err != nil {
		log.Error(fmt.Sprintf("Open leveldb `%s' failed due to `%s'", leveldbFileName, err))
		return
	}
	defer ldb.Close()
	//sync
	ufp, err := os.Open(cacheFileName)
	if err != nil {
		log.Error(fmt.Sprintf("Open cache file `%s' failed due to `%s'", cacheFileName, err))
		return
	}
	defer ufp.Close()
	bScanner := bufio.NewScanner(ufp)
	bScanner.Split(bufio.ScanLines)
	currentFileCount := 0
	ldbWOpt := opt.WriteOptions{
		Sync: true,
	}

	upWorkGroup := sync.WaitGroup{}
	upCounter := 0
	threadThreshold := threadCount + 1

	mac := digest.Mac{uploadConfig.AccessKey, []byte(uploadConfig.SecretKey)}
	//check thread count
	for bScanner.Scan() {
		line := strings.TrimSpace(bScanner.Text())
		items := strings.Split(line, "\t")
		if len(items) > 1 {
			cacheFname := items[0]
			cacheFlmd, _ := strconv.Atoi(items[2])
			uploadFileKey := cacheFname
			if uploadConfig.IgnoreDir {
				if i := strings.LastIndex(uploadFileKey, string(os.PathSeparator)); i != -1 {
					uploadFileKey = uploadFileKey[i+1:]
				}
			}
			if uploadConfig.KeyPrefix != "" {
				uploadFileKey = strings.Join([]string{uploadConfig.KeyPrefix, uploadFileKey}, "")
			}
			//convert \ to / under windows
			if runtime.GOOS == "windows" {
				uploadFileKey = strings.Replace(uploadFileKey, "\\", "/", -1)
			}
			cacheFilePath := strings.Join([]string{uploadConfig.SrcDir, cacheFname}, string(os.PathSeparator))
			fstat, err := os.Stat(cacheFilePath)
			if err != nil {
				log.Error(fmt.Sprintf("Error stat local file `%s' due to `%s'", cacheFilePath, err))
				return
			}
			fsize := fstat.Size()

			//check leveldb
			currentFileCount += 1
			ldbKey := fmt.Sprintf("%s => %s", cacheFilePath, uploadFileKey)
			log.Debug(fmt.Sprintf("Checking %s ...", ldbKey))
			//check last modified
			ldbFlmd, err := ldb.Get([]byte(ldbKey), nil)
			flmd, _ := strconv.Atoi(string(ldbFlmd))
			//not exist, return ErrNotFound
			if err == nil && cacheFlmd == flmd {
				continue
			}

			fmt.Print("\033[2K\r")
			fmt.Printf("Uploading %s (%d/%d, %.0f%%) ...", ldbKey, currentFileCount, totalFileCount,
				float32(currentFileCount)*100/float32(totalFileCount))
			os.Stdout.Sync()
			//worker
			upCounter += 1
			if upCounter%threadThreshold == 0 {
				upWorkGroup.Wait()
			}
			upWorkGroup.Add(1)
			go func() {
				defer upWorkGroup.Done()

				policy := rs.PutPolicy{}
				policy.Scope = uploadConfig.Bucket
				if uploadConfig.Overwrite {
					policy.Scope = uploadConfig.Bucket + ":" + uploadFileKey
					policy.InsertOnly = 0
				}
				policy.Expires = 24 * 3600
				uptoken := policy.Token(&mac)
				if fsize > PUT_THRESHOLD {
					putRet := rio.PutRet{}
					err := rio.PutFile(nil, &putRet, uptoken, uploadFileKey, cacheFilePath, nil)
					if err != nil {
						log.Error(fmt.Sprintf("Put file `%s' => `%s' failed due to `%s'", cacheFilePath, uploadFileKey, err))
					} else {
						perr := ldb.Put([]byte(ldbKey), []byte("Y"), &ldbWOpt)
						if perr != nil {
							log.Error(fmt.Sprintf("Put key `%s' into leveldb error due to `%s'", ldbKey, perr))
						}
					}
				} else {
					putRet := fio.PutRet{}
					err := fio.PutFile(nil, &putRet, uptoken, uploadFileKey, cacheFilePath, nil)
					if err != nil {
						log.Error(fmt.Sprintf("Put file `%s' => `%s' failed due to `%s'", cacheFilePath, uploadFileKey, err))
					} else {
						perr := ldb.Put([]byte(ldbKey), []byte(strconv.Itoa(cacheFlmd)), &ldbWOpt)
						if perr != nil {
							log.Error(fmt.Sprintf("Put key `%s' into leveldb error due to `%s'", ldbKey, perr))
						}
					}
				}
			}()
		} else {
			log.Error(fmt.Sprintf("Error cache line `%s'", line))
		}
	}
	upWorkGroup.Wait()
	fmt.Println()
	fmt.Println("Upload done!")
}
<commit_msg>Use os.Separator to be compatible under windows.<commit_after>package qshell

import (
	"bufio"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/qiniu/api/auth/digest"
	fio "github.com/qiniu/api/io"
	rio "github.com/qiniu/api/resumable/io"
	"github.com/qiniu/api/rs"
	"github.com/qiniu/log"
	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/opt"
	"io/ioutil"
	"os"
	"os/user"
	"runtime"
	"strconv"
	"strings"
	"sync"
)

/*
Config file like:

{
	"src_dir" 		:	"/Users/jemy/Photos",
	"access_key" 	:	"<Your AccessKey>",
	"secret_key"	:	"<Your SecretKey>",
	"bucket"		:	"test-bucket",
	"ignore_dir"	:	false,
	"key_prefix"	:	"2014/12/01/",
	"overwrite"		:	false
}

or without key_prefix and ignore_dir

{
	"src_dir" 		:	"/Users/jemy/Photos",
	"access_key" 	:	"<Your AccessKey>",
	"secret_key"	:	"<Your SecretKey>",
	"bucket"		:	"test-bucket",
}
*/

const (
	PUT_THRESHOLD           int64 = 100 * 1 << 20
	MIN_UPLOAD_THREAD_COUNT int64 = 1
	MAX_UPLOAD_THREAD_COUNT int64 = 100
)

type UploadConfig struct {
	SrcDir    string `json:"src_dir"`
	AccessKey string `json:"access_key"`
	SecretKey string `json:"secret_key"`
	Bucket    string `json:"bucket"`
	KeyPrefix string `json:"key_prefix,omitempty"`
	IgnoreDir bool   `json:"ignore_dir,omitempty"`
	Overwrite bool   `json:"overwrite,omitempty"`
}

func QiniuUpload(threadCount int, uploadConfigFile string) {
	fp, err := os.Open(uploadConfigFile)
	if err != nil {
		log.Error(fmt.Sprintf("Open upload config file `%s' error due to `%s'", uploadConfigFile, err))
		return
	}
	defer fp.Close()
	configData, err := ioutil.ReadAll(fp)
	if err != nil {
		log.Error(fmt.Sprintf("Read upload config file `%s' error due to `%s'", uploadConfigFile, err))
		return
	}
	var uploadConfig UploadConfig
	err = json.Unmarshal(configData, &uploadConfig)
	if err != nil {
		log.Error(fmt.Sprintf("Parse upload config file `%s' errror due to `%s'", uploadConfigFile, err))
		return
	}
	if _, err := os.Stat(uploadConfig.SrcDir); err != nil {
		log.Error("Upload config error for parameter `SrcDir`,", err)
		return
	}
	dirCache := DirCache{}
	currentUser, err := user.Current()
	if err != nil {
		log.Error("Failed to get current user", err)
		return
	}
	pathSep:=string(os.PathSeparator)
	jobId := base64.URLEncoding.EncodeToString([]byte(uploadConfig.SrcDir + ":" + uploadConfig.Bucket))
	storePath := fmt.Sprintf("%s%s.qshell%squpload%s%s", currentUser.HomeDir,pathSep,pathSep, pathSep,jobId)
	err = os.MkdirAll(storePath, 0775)
	if err != nil {
		log.Error(fmt.Sprintf("Failed to mkdir `%s' due to `%s'", storePath, err))
		return
	}
	cacheFileName := fmt.Sprintf("%s%s%s.cache", storePath,pathSep, jobId)
	leveldbFileName := fmt.Sprintf("%s%s%s.ldb", storePath,pathSep, jobId)
	totalFileCount := dirCache.Cache(uploadConfig.SrcDir, cacheFileName)
	ldb, err := leveldb.OpenFile(leveldbFileName, nil)
	if err != nil {
		log.Error(fmt.Sprintf("Open leveldb `%s' failed due to `%s'", leveldbFileName, err))
		return
	}
	defer ldb.Close()
	//sync
	ufp, err := os.Open(cacheFileName)
	if err != nil {
		log.Error(fmt.Sprintf("Open cache file `%s' failed due to `%s'", cacheFileName, err))
		return
	}
	defer ufp.Close()
	bScanner := bufio.NewScanner(ufp)
	bScanner.Split(bufio.ScanLines)
	currentFileCount := 0
	ldbWOpt := opt.WriteOptions{
		Sync: true,
	}

	upWorkGroup := sync.WaitGroup{}
	upCounter := 0
	threadThreshold := threadCount + 1

	mac := digest.Mac{uploadConfig.AccessKey, []byte(uploadConfig.SecretKey)}
	//check thread count
	for bScanner.Scan() {
		line := strings.TrimSpace(bScanner.Text())
		items := strings.Split(line, "\t")
		if len(items) > 1 {
			cacheFname := items[0]
			cacheFlmd, _ := strconv.Atoi(items[2])
			uploadFileKey := cacheFname
			if uploadConfig.IgnoreDir {
				if i := strings.LastIndex(uploadFileKey, pathSep); i != -1 {
					uploadFileKey = uploadFileKey[i+1:]
				}
			}
			if uploadConfig.KeyPrefix != "" {
				uploadFileKey = strings.Join([]string{uploadConfig.KeyPrefix, uploadFileKey}, "")
			}
			//convert \ to / under windows
			if runtime.GOOS == "windows" {
				uploadFileKey = strings.Replace(uploadFileKey, "\\", "/", -1)
			}
			cacheFilePath := strings.Join([]string{uploadConfig.SrcDir, cacheFname}, pathSep)
			fstat, err := os.Stat(cacheFilePath)
			if err != nil {
				log.Error(fmt.Sprintf("Error stat local file `%s' due to `%s'", cacheFilePath, err))
				return
			}
			fsize := fstat.Size()

			//check leveldb
			currentFileCount += 1
			ldbKey := fmt.Sprintf("%s => %s", cacheFilePath, uploadFileKey)
			log.Debug(fmt.Sprintf("Checking %s ...", ldbKey))
			//check last modified
			ldbFlmd, err := ldb.Get([]byte(ldbKey), nil)
			flmd, _ := strconv.Atoi(string(ldbFlmd))
			//not exist, return ErrNotFound
			if err == nil && cacheFlmd == flmd {
				continue
			}

			fmt.Print("\033[2K\r")
			fmt.Printf("Uploading %s (%d/%d, %.0f%%) ...", ldbKey, currentFileCount, totalFileCount,
				float32(currentFileCount)*100/float32(totalFileCount))
			os.Stdout.Sync()
			//worker
			upCounter += 1
			if upCounter%threadThreshold == 0 {
				upWorkGroup.Wait()
			}
			upWorkGroup.Add(1)
			go func() {
				defer upWorkGroup.Done()

				policy := rs.PutPolicy{}
				policy.Scope = uploadConfig.Bucket
				if uploadConfig.Overwrite {
					policy.Scope = uploadConfig.Bucket + ":" + uploadFileKey
					policy.InsertOnly = 0
				}
				policy.Expires = 24 * 3600
				uptoken := policy.Token(&mac)
				if fsize > PUT_THRESHOLD {
					putRet := rio.PutRet{}
					err := rio.PutFile(nil, &putRet, uptoken, uploadFileKey, cacheFilePath, nil)
					if err != nil {
						log.Error(fmt.Sprintf("Put file `%s' => `%s' failed due to `%s'", cacheFilePath, uploadFileKey, err))
					} else {
						perr := ldb.Put([]byte(ldbKey), []byte("Y"), &ldbWOpt)
						if perr != nil {
							log.Error(fmt.Sprintf("Put key `%s' into leveldb error due to `%s'", ldbKey, perr))
						}
					}
				} else {
					putRet := fio.PutRet{}
					err := fio.PutFile(nil, &putRet, uptoken, uploadFileKey, cacheFilePath, nil)
					if err != nil {
						log.Error(fmt.Sprintf("Put file `%s' => `%s' failed due to `%s'", cacheFilePath, uploadFileKey, err))
					} else {
						perr := ldb.Put([]byte(ldbKey), []byte(strconv.Itoa(cacheFlmd)), &ldbWOpt)
						if perr != nil {
							log.Error(fmt.Sprintf("Put key `%s' into leveldb error due to `%s'", ldbKey, perr))
						}
					}
				}
			}()
		} else {
			log.Error(fmt.Sprintf("Error cache line `%s'", line))
		}
	}
	upWorkGroup.Wait()
	fmt.Println()
	fmt.Println("Upload done!")
}
<|endoftext|>


<commit_before>package httpclient

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"io"
	"net"
	"net/http"
	"time"

	"github.com/pkg/errors"
	"github.com/quan-xie/tuba/util/retry"
	"github.com/quan-xie/tuba/util/xtime"
)

const (
	minRead               = 16 * 1024 // 16kb
	defaultRetryCount int = 0
)

type Config struct {
	Dial       xtime.Duration
	Timeout    xtime.Duration
	KeepAlive  xtime.Duration
	retryCount int
}

type HttpClient struct {
	conf       *Config
	client     *http.Client
	dialer     *net.Dialer
	transport  *http.Transport
	retryCount int
	retrier    retry.Retriable
}

// NewHTTPClient returns a new instance of httpClient
func NewHTTPClient(c *Config) *HttpClient {
	dialer := &net.Dialer{
		Timeout:   time.Duration(c.Dial),
		KeepAlive: time.Duration(c.KeepAlive),
	}
	transport := &http.Transport{
		DialContext:     dialer.DialContext,
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	return &HttpClient{
		conf: c,
		client: &http.Client{
			Transport: transport,
		},
		retryCount: defaultRetryCount,
		retrier:    retry.NewNoRetrier(),
	}
}

// SetRetryCount sets the retry count for the httpClient
func (c *HttpClient) SetRetryCount(count int) {
	c.retryCount = count
}

// SetRetryCount sets the retry count for the httpClient
func (c *HttpClient) SetRetrier(retrier retry.Retriable) {
	c.retrier = retrier
}

// Get makes a HTTP GET request to provided URL with context passed in
func (c *HttpClient) Get(ctx context.Context, url string, headers http.Header, res interface{}) (err error) {
	request, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return errors.Wrap(err, "GET - request creation failed")
	}

	request.Header = headers

	return c.Do(ctx, request, res)
}

// Post makes a HTTP POST request to provided URL with context passed in
func (c *HttpClient) Post(ctx context.Context, url string, body io.Reader, headers http.Header, res interface{}) (err error) {
	request, err := http.NewRequest(http.MethodPost, url, body)
	if err != nil {
		return errors.Wrap(err, "POST - request creation failed")
	}

	request.Header = headers

	return c.Do(ctx, request, res)
}

// Put makes a HTTP PUT request to provided URL with context passed in
func (c *HttpClient) Put(ctx context.Context, url string, body io.Reader, headers http.Header, res interface{}) (err error) {
	request, err := http.NewRequest(http.MethodPut, url, body)
	if err != nil {
		return errors.Wrap(err, "PUT - request creation failed")
	}

	request.Header = headers

	return c.Do(ctx, request, res)
}

// Patch makes a HTTP PATCH request to provided URL with context passed in
func (c *HttpClient) Patch(ctx context.Context, url string, body io.Reader, headers http.Header, res interface{}) (err error) {
	request, err := http.NewRequest(http.MethodPatch, url, body)
	if err != nil {
		return errors.Wrap(err, "PATCH - request creation failed")
	}

	request.Header = headers

	return c.Do(ctx, request, res)
}

// Delete makes a HTTP DELETE request to provided URL with context passed in
func (c *HttpClient) Delete(ctx context.Context, url string, headers http.Header, res interface{}) (err error) {
	request, err := http.NewRequest(http.MethodDelete, url, nil)
	if err != nil {
		return errors.Wrap(err, "DELETE - request creation failed")
	}

	request.Header = headers

	return c.Do(ctx, request, res)
}

// Do makes an HTTP request with the native `http.Do` interface and context passed in
func (c *HttpClient) Do(ctx context.Context, req *http.Request, res interface{}) (err error) {
	for i := 0; i <= c.retryCount; i++ {
		if err = c.request(ctx, req, res); err != nil {
			err = errors.Wrap(err, "request - request failed")
			backoffTime := c.retrier.NextInterval(i)
			time.Sleep(backoffTime)
			continue
		}
		break
	}
	return
}

func (c *HttpClient) request(ctx context.Context, req *http.Request, res interface{}) (err error) {
	var (
		response *http.Response
		bs       []byte
		cancel   func()
	)
	ctx, cancel = context.WithTimeout(ctx, time.Duration(c.conf.Timeout))
	defer cancel()
	response, err = c.client.Do(req.WithContext(ctx))
	if err != nil {
		select {
		case <-ctx.Done():
			err = ctx.Err()
		}
		return
	}
	defer response.Body.Close()
	if response.StatusCode >= http.StatusInternalServerError {
		return
	}
	bs, err = readAll(response.Body, minRead)
	if err != nil {
		err = errors.Wrap(err, "readAll - readAll failed")
		return err
	}
	if res != nil {
		if err = json.Unmarshal(bs, res); err != nil {
			err = errors.Wrap(err, "Unmarshal failed")
			return err
		}
	}
	return
}

func readAll(r io.Reader, capacity int64) (b []byte, err error) {
	buf := bytes.NewBuffer(make([]byte, 0, capacity))
	// If the buffer overflows, we will get bytes.ErrTooLarge.
	// Return that as an error. Any other panic remains.
	defer func() {
		e := recover()
		if e == nil {
			return
		}
		if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
			err = panicErr
		} else {
			panic(e)
		}
	}()
	_, err = buf.ReadFrom(r)
	return buf.Bytes(), err
}
<commit_msg>format code<commit_after>package httpclient

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"io"
	"net"
	"net/http"
	"time"

	"github.com/pkg/errors"
	"github.com/quan-xie/tuba/util/retry"
	"github.com/quan-xie/tuba/util/xtime"
)

const (
	minRead               = 16 * 1024 // 16kb
	defaultRetryCount int = 0
)

type Config struct {
	Dial       xtime.Duration
	Timeout    xtime.Duration
	KeepAlive  xtime.Duration
	retryCount int
}

type HttpClient struct {
	conf       *Config
	client     *http.Client
	dialer     *net.Dialer
	transport  *http.Transport
	retryCount int
	retrier    retry.Retriable
}

// NewHTTPClient returns a new instance of httpClient
func NewHTTPClient(c *Config) *HttpClient {
	dialer := &net.Dialer{
		Timeout:   time.Duration(c.Dial),
		KeepAlive: time.Duration(c.KeepAlive),
	}
	transport := &http.Transport{
		DialContext:     dialer.DialContext,
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	return &HttpClient{
		conf: c,
		client: &http.Client{
			Transport: transport,
		},
		retryCount: defaultRetryCount,
		retrier:    retry.NewNoRetrier(),
	}
}

// SetRetryCount sets the retry count for the httpClient
func (c *HttpClient) SetRetryCount(count int) {
	c.retryCount = count
}

// SetRetryCount sets the retry count for the httpClient
func (c *HttpClient) SetRetrier(retrier retry.Retriable) {
	c.retrier = retrier
}

// Get makes a HTTP GET request to provided URL with context passed in
func (c *HttpClient) Get(ctx context.Context, url string, headers http.Header, res interface{}) (err error) {
	request, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return errors.Wrap(err, "GET - request creation failed")
	}

	request.Header = headers

	return c.Do(ctx, request, res)
}

// Post makes a HTTP POST request to provided URL with context passed in
func (c *HttpClient) Post(ctx context.Context, url string, body io.Reader, headers http.Header, res interface{}) (err error) {
	request, err := http.NewRequest(http.MethodPost, url, body)
	if err != nil {
		return errors.Wrap(err, "POST - request creation failed")
	}

	request.Header = headers

	return c.Do(ctx, request, res)
}

// Put makes a HTTP PUT request to provided URL with context passed in
func (c *HttpClient) Put(ctx context.Context, url string, body io.Reader, headers http.Header, res interface{}) (err error) {
	request, err := http.NewRequest(http.MethodPut, url, body)
	if err != nil {
		return errors.Wrap(err, "PUT - request creation failed")
	}

	request.Header = headers

	return c.Do(ctx, request, res)
}

// Patch makes a HTTP PATCH request to provided URL with context passed in
func (c *HttpClient) Patch(ctx context.Context, url string, body io.Reader, headers http.Header, res interface{}) (err error) {
	request, err := http.NewRequest(http.MethodPatch, url, body)
	if err != nil {
		return errors.Wrap(err, "PATCH - request creation failed")
	}

	request.Header = headers

	return c.Do(ctx, request, res)
}

// Delete makes a HTTP DELETE request to provided URL with context passed in
func (c *HttpClient) Delete(ctx context.Context, url string, headers http.Header, res interface{}) (err error) {
	request, err := http.NewRequest(http.MethodDelete, url, nil)
	if err != nil {
		return errors.Wrap(err, "DELETE - request creation failed")
	}

	request.Header = headers

	return c.Do(ctx, request, res)
}

// Do makes an HTTP request with the native `http.Do` interface and context passed in
func (c *HttpClient) Do(ctx context.Context, req *http.Request, res interface{}) (err error) {
	for i := 0; i <= c.retryCount; i++ {
		if err = c.request(ctx, req, res); err != nil {
			err = errors.Wrap(err, "request - request failed")
			backoffTime := c.retrier.NextInterval(i)
			time.Sleep(backoffTime)
			continue
		}
		break
	}
	return
}

func (c *HttpClient) request(ctx context.Context, req *http.Request, res interface{}) (err error) {
	var (
		response *http.Response
		bs       []byte
		cancel   func()
	)
	ctx, cancel = context.WithTimeout(ctx, time.Duration(c.conf.Timeout))
	defer cancel()
	response, err = c.client.Do(req.WithContext(ctx))
	if err != nil {
		select {
		case <-ctx.Done():
			err = ctx.Err()
		}
		return
	}
	defer response.Body.Close()
	if response.StatusCode >= http.StatusInternalServerError {
		err = errors.Wrap(err, "StatusInternalServerError - Status Internal ServerError")
		return
	}
	if bs, err = readAll(response.Body, minRead); err != nil {
		err = errors.Wrap(err, "readAll - readAll failed")
		return
	}
	if res != nil {
		if err = json.Unmarshal(bs, res); err != nil {
			err = errors.Wrap(err, "Unmarshal failed")
		}
	}
	return
}

func readAll(r io.Reader, capacity int64) (b []byte, err error) {
	buf := bytes.NewBuffer(make([]byte, 0, capacity))
	// If the buffer overflows, we will get bytes.ErrTooLarge.
	// Return that as an error. Any other panic remains.
	defer func() {
		e := recover()
		if e == nil {
			return
		}
		if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
			err = panicErr
		} else {
			panic(e)
		}
	}()
	_, err = buf.ReadFrom(r)
	return buf.Bytes(), err
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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 node

import (
	"errors"
	"testing"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/fake"
	k8stesting "k8s.io/client-go/testing"
)

// TestCheckReadyForTests specifically is concerned about the multi-node logic
// since single node checks are in TestReadyForTests.
func TestCheckReadyForTests(t *testing.T) {
	// This is a duplicate definition of the constant in pkg/controller/service/controller.go
	labelNodeRoleControlPlane := "node-role.kubernetes.io/control-plane"

	fromVanillaNode := func(f func(*v1.Node)) v1.Node {
		vanillaNode := &v1.Node{
			ObjectMeta: metav1.ObjectMeta{Name: "test-node"},
			Status: v1.NodeStatus{
				Conditions: []v1.NodeCondition{
					{Type: v1.NodeReady, Status: v1.ConditionTrue},
				},
			},
		}
		f(vanillaNode)
		return *vanillaNode
	}

	tcs := []struct {
		desc                 string
		nonblockingTaints    string
		allowedNotReadyNodes int
		nodes                []v1.Node
		nodeListErr          error
		expected             bool
		expectedErr          string
	}{
		{
			desc: "Vanilla node should pass",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
			},
			expected: true,
		}, {
			desc:              "Default value for nonblocking taints tolerates control plane taint",
			nonblockingTaints: `node-role.kubernetes.io/control-plane`,
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: labelNodeRoleControlPlane, Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: true,
		}, {
			desc:              "Tainted node should fail if effect is TaintEffectNoExecute",
			nonblockingTaints: "bar",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoExecute}}
				})},
			expected: false,
		}, {
			desc:                 "Tainted node can be allowed via allowedNotReadyNodes",
			nonblockingTaints:    "bar",
			allowedNotReadyNodes: 1,
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoExecute}}
				})},
			expected: true,
		}, {
			desc: "Multi-node, all OK",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {}),
			},
			expected: true,
		}, {
			desc: "Multi-node, single blocking node blocks",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: false,
		}, {
			desc:                 "Multi-node, single blocking node allowed via allowedNotReadyNodes",
			allowedNotReadyNodes: 1,
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: true,
		}, {
			desc:              "Multi-node, single blocking node allowed via nonblocking taint",
			nonblockingTaints: "foo",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: true,
		}, {
			desc:              "Multi-node, both blocking nodes allowed via separate nonblocking taints",
			nonblockingTaints: "foo,bar",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
				}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "bar", Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: true,
		}, {
			desc:              "Multi-node, one blocking node allowed via nonblocking taints still blocked",
			nonblockingTaints: "foo,notbar",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
				}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "bar", Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: false,
		}, {
			desc:        "Errors from node list are reported",
			nodeListErr: errors.New("Forced error"),
			expected:    false,
			expectedErr: "Forced error",
		},
	}

	// Only determines some logging functionality; not relevant so set to a large value.
	testLargeClusterThreshold := 1000

	for _, tc := range tcs {
		t.Run(tc.desc, func(t *testing.T) {
			c := fake.NewSimpleClientset()
			c.PrependReactor("list", "nodes", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
				nodeList := &v1.NodeList{Items: tc.nodes}
				return true, nodeList, tc.nodeListErr
			})
			checkFunc := CheckReadyForTests(c, tc.nonblockingTaints, tc.allowedNotReadyNodes, testLargeClusterThreshold)
			out, err := checkFunc()
			if out != tc.expected {
				t.Errorf("Expected %v but got %v", tc.expected, out)
			}
			switch {
			case err == nil && len(tc.expectedErr) > 0:
				t.Errorf("Expected error %q nil", tc.expectedErr)
			case err != nil && err.Error() != tc.expectedErr:
				t.Errorf("Expected error %q but got %q", tc.expectedErr, err.Error())
			}
		})
	}
}

func TestReadyForTests(t *testing.T) {
	fromVanillaNode := func(f func(*v1.Node)) *v1.Node {
		vanillaNode := &v1.Node{
			ObjectMeta: metav1.ObjectMeta{Name: "test-node"},
			Status: v1.NodeStatus{
				Conditions: []v1.NodeCondition{
					{Type: v1.NodeReady, Status: v1.ConditionTrue},
				},
			},
		}
		f(vanillaNode)
		return vanillaNode
	}
	_ = fromVanillaNode
	tcs := []struct {
		desc              string
		node              *v1.Node
		nonblockingTaints string
		expected          bool
	}{
		{
			desc: "Vanilla node should pass",
			node: fromVanillaNode(func(n *v1.Node) {
			}),
			expected: true,
		}, {
			desc:              "Vanilla node should pass with non-applicable nonblocking taint",
			nonblockingTaints: "foo",
			node: fromVanillaNode(func(n *v1.Node) {
			}),
			expected: true,
		}, {
			desc: "Tainted node should pass if effect is TaintEffectPreferNoSchedule",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectPreferNoSchedule}}
			}),
			expected: true,
		}, {
			desc: "Tainted node should fail if effect is TaintEffectNoExecute",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoExecute}}
			}),
			expected: false,
		}, {
			desc: "Tainted node should fail",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
			}),
			expected: false,
		}, {
			desc:              "Tainted node should pass if nonblocking",
			nonblockingTaints: "foo",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
			}),
			expected: true,
		}, {
			desc: "Node with network not ready fails",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Status.Conditions = append(n.Status.Conditions,
					v1.NodeCondition{Type: v1.NodeNetworkUnavailable, Status: v1.ConditionTrue},
				)
			}),
			expected: false,
		}, {
			desc: "Node fails unless NodeReady status",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Status.Conditions = []v1.NodeCondition{}
			}),
			expected: false,
		},
	}

	for _, tc := range tcs {
		t.Run(tc.desc, func(t *testing.T) {
			out := readyForTests(tc.node, tc.nonblockingTaints)
			if out != tc.expected {
				t.Errorf("Expected %v but got %v", tc.expected, out)
			}
		})
	}
}
<commit_msg>e2e: fix node wait test<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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 node

import (
	"errors"
	"testing"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/fake"
	k8stesting "k8s.io/client-go/testing"
)

// TestCheckReadyForTests specifically is concerned about the multi-node logic
// since single node checks are in TestReadyForTests.
func TestCheckReadyForTests(t *testing.T) {
	// This is a duplicate definition of the constant in pkg/controller/service/controller.go
	labelNodeRoleControlPlane := "node-role.kubernetes.io/control-plane"

	fromVanillaNode := func(f func(*v1.Node)) v1.Node {
		vanillaNode := &v1.Node{
			ObjectMeta: metav1.ObjectMeta{Name: "test-node"},
			Status: v1.NodeStatus{
				Conditions: []v1.NodeCondition{
					{Type: v1.NodeReady, Status: v1.ConditionTrue},
				},
			},
		}
		f(vanillaNode)
		return *vanillaNode
	}

	tcs := []struct {
		desc                 string
		nonblockingTaints    string
		allowedNotReadyNodes int
		nodes                []v1.Node
		nodeListErr          error
		expected             bool
		expectedErr          string
	}{
		{
			desc: "Vanilla node should pass",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
			},
			expected: true,
		}, {
			desc:              "Default value for nonblocking taints tolerates control plane taint",
			nonblockingTaints: `node-role.kubernetes.io/control-plane`,
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: labelNodeRoleControlPlane, Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: true,
		}, {
			desc:              "Tainted node should fail if effect is TaintEffectNoExecute",
			nonblockingTaints: "bar",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoExecute}}
				})},
			expected: false,
		}, {
			desc:                 "Tainted node can be allowed via allowedNotReadyNodes",
			nonblockingTaints:    "bar",
			allowedNotReadyNodes: 1,
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoExecute}}
				})},
			expected: true,
		}, {
			desc: "Multi-node, all OK",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {}),
			},
			expected: true,
		}, {
			desc: "Multi-node, single blocking node blocks",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: false,
		}, {
			desc:                 "Multi-node, single blocking node allowed via allowedNotReadyNodes",
			allowedNotReadyNodes: 1,
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: true,
		}, {
			desc:              "Multi-node, single blocking node allowed via nonblocking taint",
			nonblockingTaints: "foo",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: true,
		}, {
			desc:              "Multi-node, both blocking nodes allowed via separate nonblocking taints",
			nonblockingTaints: "foo,bar",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
				}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "bar", Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: true,
		}, {
			desc:              "Multi-node, one blocking node allowed via nonblocking taints still blocked",
			nonblockingTaints: "foo,notbar",
			nodes: []v1.Node{
				fromVanillaNode(func(n *v1.Node) {}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
				}),
				fromVanillaNode(func(n *v1.Node) {
					n.Spec.Taints = []v1.Taint{{Key: "bar", Effect: v1.TaintEffectNoSchedule}}
				}),
			},
			expected: false,
		}, {
			desc:        "Errors from node list are reported",
			nodeListErr: errors.New("Forced error"),
			expected:    false,
			expectedErr: "Forced error",
		},
	}

	// Only determines some logging functionality; not relevant so set to a large value.
	testLargeClusterThreshold := 1000

	for _, tc := range tcs {
		t.Run(tc.desc, func(t *testing.T) {
			c := fake.NewSimpleClientset()
			c.PrependReactor("list", "nodes", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
				nodeList := &v1.NodeList{Items: tc.nodes}
				return true, nodeList, tc.nodeListErr
			})
			checkFunc := CheckReadyForTests(c, tc.nonblockingTaints, tc.allowedNotReadyNodes, testLargeClusterThreshold)
			// The check function returns "false, nil" during its
			// first two calls, therefore we have to try several
			// times until we get the expected error.
			for attempt := 0; attempt <= 3; attempt++ {
				out, err := checkFunc()
				expected := tc.expected
				expectedErr := tc.expectedErr
				if tc.nodeListErr != nil && attempt < 2 {
					expected = false
					expectedErr = ""
				}
				if out != expected {
					t.Errorf("Expected %v but got %v", expected, out)
				}
				switch {
				case err == nil && expectedErr != "":
					t.Errorf("attempt #%d: expected error %q nil", attempt, expectedErr)
				case err != nil && err.Error() != expectedErr:
					t.Errorf("attempt #%d: expected error %q but got %q", attempt, expectedErr, err.Error())
				}
			}
		})
	}
}

func TestReadyForTests(t *testing.T) {
	fromVanillaNode := func(f func(*v1.Node)) *v1.Node {
		vanillaNode := &v1.Node{
			ObjectMeta: metav1.ObjectMeta{Name: "test-node"},
			Status: v1.NodeStatus{
				Conditions: []v1.NodeCondition{
					{Type: v1.NodeReady, Status: v1.ConditionTrue},
				},
			},
		}
		f(vanillaNode)
		return vanillaNode
	}
	_ = fromVanillaNode
	tcs := []struct {
		desc              string
		node              *v1.Node
		nonblockingTaints string
		expected          bool
	}{
		{
			desc: "Vanilla node should pass",
			node: fromVanillaNode(func(n *v1.Node) {
			}),
			expected: true,
		}, {
			desc:              "Vanilla node should pass with non-applicable nonblocking taint",
			nonblockingTaints: "foo",
			node: fromVanillaNode(func(n *v1.Node) {
			}),
			expected: true,
		}, {
			desc: "Tainted node should pass if effect is TaintEffectPreferNoSchedule",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectPreferNoSchedule}}
			}),
			expected: true,
		}, {
			desc: "Tainted node should fail if effect is TaintEffectNoExecute",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoExecute}}
			}),
			expected: false,
		}, {
			desc: "Tainted node should fail",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
			}),
			expected: false,
		}, {
			desc:              "Tainted node should pass if nonblocking",
			nonblockingTaints: "foo",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
			}),
			expected: true,
		}, {
			desc: "Node with network not ready fails",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Status.Conditions = append(n.Status.Conditions,
					v1.NodeCondition{Type: v1.NodeNetworkUnavailable, Status: v1.ConditionTrue},
				)
			}),
			expected: false,
		}, {
			desc: "Node fails unless NodeReady status",
			node: fromVanillaNode(func(n *v1.Node) {
				n.Status.Conditions = []v1.NodeCondition{}
			}),
			expected: false,
		},
	}

	for _, tc := range tcs {
		t.Run(tc.desc, func(t *testing.T) {
			out := readyForTests(tc.node, tc.nonblockingTaints)
			if out != tc.expected {
				t.Errorf("Expected %v but got %v", tc.expected, out)
			}
		})
	}
}
<|endoftext|>


<commit_before>//go:build integration
// +build integration

/*
Copyright 2021 The Kubernetes Authors 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 integration

import (
	"context"
	"fmt"
	"os/exec"
	"strings"
	"testing"
	"time"
)

const (
	mountGID   = "0"
	mountMSize = "6543"
	mountMode  = "0777"
	mountPort  = "46464"
	mountUID   = "0"
)

// TestMountStart tests using the mount command on start
func TestMountStart(t *testing.T) {
	if NoneDriver() {
		t.Skip("skipping: none driver does not support mount")
	}

	type validateFunc func(context.Context, *testing.T, string)
	profile1 := UniqueProfileName("mount-start-1")
	profile2 := UniqueProfileName("mount-start-2")
	ctx, cancel := context.WithTimeout(context.Background(), Minutes(15))
	defer Cleanup(t, profile1, cancel)
	defer Cleanup(t, profile2, cancel)

	// Serial tests
	t.Run("serial", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
			profile   string
		}{
			{"StartWithMountFirst", validateStartWithMount, profile1},
			{"StartWithMountSecond", validateStartWithMount, profile2},
			{"VerifyMountFirst", validateMount, profile1},
			{"VerifyMountSecond", validateMount, profile2},
			{"DeleteFirst", validateDelete, profile1},
			{"VerifyMountPostDelete", validateMount, profile2},
			{"Stop", validateMountStop, profile2},
			{"RestartStopped", validateRestart, profile2},
			{"VerifyMountPostStop", validateMount, profile2},
		}

		for _, test := range tests {
			if ctx.Err() == context.DeadlineExceeded {
				t.Fatalf("Unable to run more tests (deadline exceeded)")
			}

			t.Run(test.name, func(t *testing.T) {
				test.validator(ctx, t, test.profile)
			})
		}
	})
}

// validateStartWithMount starts a cluster with mount enabled
func validateStartWithMount(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"start", "-p", profile, "--memory=2048", "--mount", "--mount-gid", mountGID, "--mount-msize", mountMSize, "--mount-mode", mountMode, "--mount-port", mountPort, "--mount-uid", mountUID}
	args = append(args, StartArgs()...)
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to start minikube with args: %q : %v", rr.Command(), err)
	}
}

// validateMount checks if the cluster has a folder mounted
func validateMount(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	sshArgs := []string{"-p", profile, "ssh", "--"}

	args := sshArgs
	args = append(args, "ls", "/minikube-host")
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("mount failed: %q : %v", rr.Command(), err)
	}

	// Docker has it's own mounting method, it doesn't respect the mounting flags
	if DockerDriver() {
		return
	}

	args = sshArgs
	args = append(args, "stat", "--format", "'%a'", "/minikube-host")
	rr, err = Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to get directory mode: %v", err)
	}

	want := "777"
	if !strings.Contains(rr.Output(), want) {
		t.Errorf("wanted mode to be %q; got: %q", want, rr.Output())
	}

	// We can't get the mount details with Hyper-V
	if HyperVDriver() {
		return
        }

	args = sshArgs
	args = append(args, "mount", "|", "grep", "9p")
	rr, err = Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to get mount information: %v", err)
	}

	flags := []struct {
		key      string
		expected string
	}{
		{"gid", mountGID},
		{"msize", mountMSize},
		{"port", mountPort},
		{"uid", mountUID},
	}

	for _, flag := range flags {
		want := fmt.Sprintf("%s=%s", flag.key, flag.expected)
		if !strings.Contains(rr.Output(), want) {
			t.Errorf("wanted gid to be: %q; got: %q", want, rr.Output())
		}
	}
}

// validateMountStop stops a cluster
func validateMountStop(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"stop", "-p", profile}
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("stop failed: %q : %v", rr.Command(), err)
	}
}

// validateRestart restarts a cluster
func validateRestart(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"start", "-p", profile}
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("restart failed: %q : %v", rr.Command(), err)
	}
}
<commit_msg>format<commit_after>//go:build integration
// +build integration

/*
Copyright 2021 The Kubernetes Authors 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 integration

import (
	"context"
	"fmt"
	"os/exec"
	"strings"
	"testing"
)

const (
	mountGID   = "0"
	mountMSize = "6543"
	mountMode  = "0777"
	mountPort  = "46464"
	mountUID   = "0"
)

// TestMountStart tests using the mount command on start
func TestMountStart(t *testing.T) {
	if NoneDriver() {
		t.Skip("skipping: none driver does not support mount")
	}

	type validateFunc func(context.Context, *testing.T, string)
	profile1 := UniqueProfileName("mount-start-1")
	profile2 := UniqueProfileName("mount-start-2")
	ctx, cancel := context.WithTimeout(context.Background(), Minutes(15))
	defer Cleanup(t, profile1, cancel)
	defer Cleanup(t, profile2, cancel)

	// Serial tests
	t.Run("serial", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
			profile   string
		}{
			{"StartWithMountFirst", validateStartWithMount, profile1},
			{"StartWithMountSecond", validateStartWithMount, profile2},
			{"VerifyMountFirst", validateMount, profile1},
			{"VerifyMountSecond", validateMount, profile2},
			{"DeleteFirst", validateDelete, profile1},
			{"VerifyMountPostDelete", validateMount, profile2},
			{"Stop", validateMountStop, profile2},
			{"RestartStopped", validateRestart, profile2},
			{"VerifyMountPostStop", validateMount, profile2},
		}

		for _, test := range tests {
			if ctx.Err() == context.DeadlineExceeded {
				t.Fatalf("Unable to run more tests (deadline exceeded)")
			}

			t.Run(test.name, func(t *testing.T) {
				test.validator(ctx, t, test.profile)
			})
		}
	})
}

// validateStartWithMount starts a cluster with mount enabled
func validateStartWithMount(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"start", "-p", profile, "--memory=2048", "--mount", "--mount-gid", mountGID, "--mount-msize", mountMSize, "--mount-mode", mountMode, "--mount-port", mountPort, "--mount-uid", mountUID}
	args = append(args, StartArgs()...)
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to start minikube with args: %q : %v", rr.Command(), err)
	}
}

// validateMount checks if the cluster has a folder mounted
func validateMount(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	sshArgs := []string{"-p", profile, "ssh", "--"}

	args := sshArgs
	args = append(args, "ls", "/minikube-host")
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("mount failed: %q : %v", rr.Command(), err)
	}

	// Docker has it's own mounting method, it doesn't respect the mounting flags
	if DockerDriver() {
		return
	}

	args = sshArgs
	args = append(args, "stat", "--format", "'%a'", "/minikube-host")
	rr, err = Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to get directory mode: %v", err)
	}

	want := "777"
	if !strings.Contains(rr.Output(), want) {
		t.Errorf("wanted mode to be %q; got: %q", want, rr.Output())
	}

	// We can't get the mount details with Hyper-V
	if HyperVDriver() {
		return
	}

	args = sshArgs
	args = append(args, "mount", "|", "grep", "9p")
	rr, err = Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to get mount information: %v", err)
	}

	flags := []struct {
		key      string
		expected string
	}{
		{"gid", mountGID},
		{"msize", mountMSize},
		{"port", mountPort},
		{"uid", mountUID},
	}

	for _, flag := range flags {
		want := fmt.Sprintf("%s=%s", flag.key, flag.expected)
		if !strings.Contains(rr.Output(), want) {
			t.Errorf("wanted gid to be: %q; got: %q", want, rr.Output())
		}
	}
}

// validateMountStop stops a cluster
func validateMountStop(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"stop", "-p", profile}
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("stop failed: %q : %v", rr.Command(), err)
	}
}

// validateRestart restarts a cluster
func validateRestart(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"start", "-p", profile}
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("restart failed: %q : %v", rr.Command(), err)
	}
}
<|endoftext|>


<commit_before>package multibot

import (
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	"torpedobot/common"
	"torpedobot/memcache"
	"sync"

	"github.com/getsentry/raven-go"
)

var bot *TorpedoBot
var once sync.Once

type TorpedoBot struct {
	caches          map[string]*memcache.MemCacheType
	commandHandlers map[string]func(*TorpedoBotAPI, interface{}, string)
	config          struct {
		SkypeIncomingAddr string
	}
	logger *log.Logger
}

func (tb *TorpedoBot) PostMessage(channel interface{}, message string, api *TorpedoBotAPI, richmsgs ...RichMessage) {
	if len(richmsgs) > 0 {
		api.PostMessage(channel, message, richmsgs[0])
	} else {
		api.PostMessage(channel, message)
	}

}

func (tb *TorpedoBot) processChannelEvent(api *TorpedoBotAPI, channel interface{}, incoming_message string) {
	if strings.HasPrefix(incoming_message, api.CommandPrefix) {
		command := strings.TrimPrefix(incoming_message, api.CommandPrefix)
		found := 0
		for handler := range tb.commandHandlers {
			if strings.HasPrefix(strings.Split(command, " ")[0], handler) {
				found += 1
				tb.commandHandlers[handler](api, channel, incoming_message)
				break
			}
		}
		tb.logger.Printf("PROCESS! -> `%s`", command)
		if found == 0 {
			api.PostMessage(channel, fmt.Sprintf("Could not process your message: %s%s. Command unknown. Send %shelp for list of valid commands.", api.CommandPrefix, command, api.CommandPrefix))
		}
	}
}

func (tb *TorpedoBot) RunLoop() {
	for {
		time.Sleep(time.Second)
	}
}

func (tb *TorpedoBot) RunBotsCSV(method func(apiKey, cmd_prefix string), CSV, cmd_prefix string) {
	wrapped := func(a, b string) {}
	if os.Getenv("SENTRY_DSN") != "" {
		tb.logger.Print("Using Sentry error reporting...\n")
		wrapped = func(apiKey, cmd_prefix string) {
			raven.CapturePanic(func() {
				method(apiKey, cmd_prefix)
			}, nil)
		}
	} else {
		wrapped = method
	}
	for _, key := range strings.Split(CSV, ",") {
		if key == "" {
			continue
		}
		go wrapped(key, cmd_prefix)
	}
}

func (tb *TorpedoBot) RegisterHandlers(handlers map[string]func(*TorpedoBotAPI, interface{}, string)) {
	tb.commandHandlers = handlers
	return
}

func (tb *TorpedoBot) GetCommandHandlers() (handlers map[string]func(*TorpedoBotAPI, interface{}, string)) {
	return tb.commandHandlers
}

func (tb *TorpedoBot) GetCreateCache(name string) (cache *memcache.MemCacheType) {
	value, success := tb.caches[name]
	if !success {
		cache = memcache.New()
		tb.caches[name] = cache
	} else {
		cache = value
	}
	return
}

func (tb *TorpedoBot) GetCachedItem(name string) (item string) {
	cache := *tb.GetCreateCache(name)
	if cache.Len() > 0 {
		tb.logger.Printf("\nUsing cached quote...%v\n", cache.Len())
		key := ""
		for key = range cache.Cache() {
			break
		}
		quote, _ := cache.Get(key)
		cache.Delete(key)
		item = quote
	}
	return
}

func (tb *TorpedoBot) SetCachedItems(name string, items map[int]string) (item string) {
	cache := *tb.GetCreateCache(name)
	for idx := range items {
		message := common.MD5Hash(items[idx])
		_, ok := cache.Get(message)
		if !ok {
			cache.Set(message, items[idx])
		}
	}

	item = items[0]
	message := common.MD5Hash(item)
	cache.Delete(message)
	return
}

func New(skype_incoming_addr string) *TorpedoBot {
	once.Do(func() {
		bot = &TorpedoBot{}
		bot.logger = log.New(os.Stdout, "torpedo-bot: ", log.Lshortfile|log.LstdFlags)
		bot.caches = make(map[string]*memcache.MemCacheType)
		bot.config.SkypeIncomingAddr = skype_incoming_addr
	})
	return bot
}
<commit_msg>raven wait<commit_after>package multibot

import (
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	"torpedobot/common"
	"torpedobot/memcache"
	"sync"

	"github.com/getsentry/raven-go"
)

var bot *TorpedoBot
var once sync.Once

type TorpedoBot struct {
	caches          map[string]*memcache.MemCacheType
	commandHandlers map[string]func(*TorpedoBotAPI, interface{}, string)
	config          struct {
		SkypeIncomingAddr string
	}
	logger *log.Logger
}

func (tb *TorpedoBot) PostMessage(channel interface{}, message string, api *TorpedoBotAPI, richmsgs ...RichMessage) {
	if len(richmsgs) > 0 {
		api.PostMessage(channel, message, richmsgs[0])
	} else {
		api.PostMessage(channel, message)
	}

}

func (tb *TorpedoBot) processChannelEvent(api *TorpedoBotAPI, channel interface{}, incoming_message string) {
	if strings.HasPrefix(incoming_message, api.CommandPrefix) {
		command := strings.TrimPrefix(incoming_message, api.CommandPrefix)
		found := 0
		for handler := range tb.commandHandlers {
			if strings.HasPrefix(strings.Split(command, " ")[0], handler) {
				found += 1
				tb.commandHandlers[handler](api, channel, incoming_message)
				break
			}
		}
		tb.logger.Printf("PROCESS! -> `%s`", command)
		if found == 0 {
			api.PostMessage(channel, fmt.Sprintf("Could not process your message: %s%s. Command unknown. Send %shelp for list of valid commands.", api.CommandPrefix, command, api.CommandPrefix))
		}
	}
}

func (tb *TorpedoBot) RunLoop() {
	for {
		time.Sleep(time.Second)
	}
}

func (tb *TorpedoBot) RunBotsCSV(method func(apiKey, cmd_prefix string), CSV, cmd_prefix string) {
	wrapped := func(a, b string) {}
	env_dsn := os.Getenv("SENTRY_DSN")
	if  env_dsn != "" {
		tb.logger.Print("Using Sentry error reporting...\n")
		raven.SetDSN(env_dsn)
		wrapped = func(apiKey, cmd_prefix string) {
			raven.CapturePanicAndWait(func() {
				method(apiKey, cmd_prefix)
			}, nil)
		}
	} else {
		wrapped = method
	}
	for _, key := range strings.Split(CSV, ",") {
		if key == "" {
			continue
		}
		go wrapped(key, cmd_prefix)
	}
}

func (tb *TorpedoBot) RegisterHandlers(handlers map[string]func(*TorpedoBotAPI, interface{}, string)) {
	tb.commandHandlers = handlers
	return
}

func (tb *TorpedoBot) GetCommandHandlers() (handlers map[string]func(*TorpedoBotAPI, interface{}, string)) {
	return tb.commandHandlers
}

func (tb *TorpedoBot) GetCreateCache(name string) (cache *memcache.MemCacheType) {
	value, success := tb.caches[name]
	if !success {
		cache = memcache.New()
		tb.caches[name] = cache
	} else {
		cache = value
	}
	return
}

func (tb *TorpedoBot) GetCachedItem(name string) (item string) {
	cache := *tb.GetCreateCache(name)
	if cache.Len() > 0 {
		tb.logger.Printf("\nUsing cached quote...%v\n", cache.Len())
		key := ""
		for key = range cache.Cache() {
			break
		}
		quote, _ := cache.Get(key)
		cache.Delete(key)
		item = quote
	}
	return
}

func (tb *TorpedoBot) SetCachedItems(name string, items map[int]string) (item string) {
	cache := *tb.GetCreateCache(name)
	for idx := range items {
		message := common.MD5Hash(items[idx])
		_, ok := cache.Get(message)
		if !ok {
			cache.Set(message, items[idx])
		}
	}

	item = items[0]
	message := common.MD5Hash(item)
	cache.Delete(message)
	return
}

func New(skype_incoming_addr string) *TorpedoBot {
	once.Do(func() {
		bot = &TorpedoBot{}
		bot.logger = log.New(os.Stdout, "torpedo-bot: ", log.Lshortfile|log.LstdFlags)
		bot.caches = make(map[string]*memcache.MemCacheType)
		bot.config.SkypeIncomingAddr = skype_incoming_addr
	})
	return bot
}
<|endoftext|>


<commit_before>package logic

import "sync"

type Rule interface {
	CondState() bool
	SetCondState(bool)
	RunEnter()
	RunExit()
	AddExitAction(RuleAction)
	AddEnterAction(RuleAction)
	AddCondition(RuleCondition)
	Conditions() []RuleCondition
	Uuid() string
	SetUuid(string)
}

type rule struct {
	Name          string          `json:"name"`
	Uuid_         string          `json:"uuid"`
	Conditions_   []RuleCondition `json:"conditions"`
	EnterActions_ []RuleAction    `json:"enterActions"`
	ExitActions_  []RuleAction    `json:"exitActions"`
	condState     bool
	sync.RWMutex
}

func (r *rule) Uuid() string {
	r.RLock()
	defer r.RUnlock()
	return r.Uuid_
}
func (r *rule) SetUuid(uuid string) {
	r.Lock()
	r.Uuid_ = uuid
	r.Unlock()
}
func (r *rule) CondState() bool {
	r.RLock()
	defer r.RUnlock()
	return r.condState
}
func (r *rule) Conditions() []RuleCondition {
	r.RLock()
	defer r.RUnlock()
	return r.Conditions_
}
func (r *rule) SetCondState(cond bool) {
	r.RLock()
	r.condState = cond
	r.RUnlock()
}
func (r *rule) RunEnter() {
	for _, a := range r.EnterActions_ {
		a.RunCommand()
	}
}
func (r *rule) RunExit() {
	for _, a := range r.ExitActions_ {
		a.RunCommand()
	}
}
func (r *rule) AddExitAction(a RuleAction) {
	r.Lock()
	r.ExitActions_ = append(r.ExitActions_, a)
	r.Unlock()
}
func (r *rule) AddEnterAction(a RuleAction) {
	r.Lock()
	r.EnterActions_ = append(r.EnterActions_, a)
	r.Unlock()
}
func (r *rule) AddCondition(a RuleCondition) {
	r.Lock()
	r.Conditions_ = append(r.Conditions_, a)
	r.Unlock()
}
<commit_msg>separated rule and ruleaction into own files<commit_after>package logic

import "sync"

type Rule interface {
	Uuid() string
	SetUuid(string)
	CondState() bool
	SetCondState(bool)
	RunEnter()
	RunExit()
	AddExitAction(RuleAction)
	AddEnterAction(RuleAction)
	AddCondition(RuleCondition)
	Conditions() []RuleCondition
}

type rule struct {
	Name          string          `json:"name"`
	Uuid_         string          `json:"uuid"`
	Conditions_   []RuleCondition `json:"conditions"`
	EnterActions_ []RuleAction    `json:"enterActions"`
	ExitActions_  []RuleAction    `json:"exitActions"`
	condState     bool
	sync.RWMutex
}

func (r *rule) Uuid() string {
	r.RLock()
	defer r.RUnlock()
	return r.Uuid_
}
func (r *rule) SetUuid(uuid string) {
	r.Lock()
	r.Uuid_ = uuid
	r.Unlock()
}
func (r *rule) CondState() bool {
	r.RLock()
	defer r.RUnlock()
	return r.condState
}
func (r *rule) Conditions() []RuleCondition {
	r.RLock()
	defer r.RUnlock()
	return r.Conditions_
}
func (r *rule) SetCondState(cond bool) {
	r.RLock()
	r.condState = cond
	r.RUnlock()
}
func (r *rule) RunEnter() {
	for _, a := range r.EnterActions_ {
		a.RunCommand()
	}
}
func (r *rule) RunExit() {
	for _, a := range r.ExitActions_ {
		a.RunCommand()
	}
}
func (r *rule) AddExitAction(a RuleAction) {
	r.Lock()
	r.ExitActions_ = append(r.ExitActions_, a)
	r.Unlock()
}
func (r *rule) AddEnterAction(a RuleAction) {
	r.Lock()
	r.EnterActions_ = append(r.EnterActions_, a)
	r.Unlock()
}
func (r *rule) AddCondition(a RuleCondition) {
	r.Lock()
	r.Conditions_ = append(r.Conditions_, a)
	r.Unlock()
}
<|endoftext|>


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

// Package gdb provides ORM features for popular relationship databases.
// 数据库ORM.
// 默认内置支持MySQL, 其他数据库需要手动import对应的数据库引擎第三方包.
package gdb

import (
    "database/sql"
    "errors"
    "fmt"
    "gitee.com/johng/gf/g/container/gring"
    "gitee.com/johng/gf/g/container/gtype"
    "gitee.com/johng/gf/g/container/gvar"
    "gitee.com/johng/gf/g/os/gcache"
    "gitee.com/johng/gf/g/util/grand"
    _ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
    "time"
)

// 数据库操作接口
type DB interface {
    // 建立数据库连接方法(开发者一般不需要直接调用)
    Open(config *ConfigNode) (*sql.DB, error)

	// SQL操作方法 API
	Query(query string, args ...interface{}) (*sql.Rows, error)
	Exec(sql string, args ...interface{}) (sql.Result, error)
	Prepare(sql string, execOnMaster...bool) (*sql.Stmt, error)

    // 内部实现API的方法(不同数据库可覆盖这些方法实现自定义的操作)
    doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error)
    doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error)
    doPrepare(link dbLink, query string) (*sql.Stmt, error)
    doInsert(link dbLink, table string, data Map, option int) (result sql.Result, err error)
    doBatchInsert(link dbLink, table string, list List, batch int, option int) (result sql.Result, err error)
    doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error)
    doDelete(link dbLink, table string, condition interface{}, args ...interface{}) (result sql.Result, err error)

	// 数据库查询
	GetAll(query string, args ...interface{}) (Result, error)
	GetOne(query string, args ...interface{}) (Record, error)
	GetValue(query string, args ...interface{}) (Value, error)
    GetCount(query string, args ...interface{}) (int, error)
    GetStruct(obj interface{}, query string, args ...interface{}) error

    // 创建底层数据库master/slave链接对象
    Master() (*sql.DB, error)
    Slave() (*sql.DB, error)

    // Ping
	PingMaster() error
	PingSlave() error

	// 开启事务操作
	Begin() (*TX, error)

	// 数据表插入/更新/保存操作
	Insert(table string, data Map) (sql.Result, error)
	Replace(table string, data Map) (sql.Result, error)
	Save(table string, data Map) (sql.Result, error)

	// 数据表插入/更新/保存操作(批量)
	BatchInsert(table string, list List, batch int) (sql.Result, error)
	BatchReplace(table string, list List, batch int) (sql.Result, error)
	BatchSave(table string, list List, batch int) (sql.Result, error)

	// 数据修改/删除
	Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
	Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)

	// 创建链式操作对象(Table为From的别名)
	Table(tables string) *Model
	From(tables string) *Model

	// 设置管理
    SetDebug(debug bool)
    SetSchema(schema string)
    GetQueriedSqls() []*Sql
    PrintQueriedSqls()
    SetMaxIdleConns(n int)
    SetMaxOpenConns(n int)
    SetConnMaxLifetime(n int)

	// 内部方法接口
	getCache() (*gcache.Cache)
	getChars() (charLeft string, charRight string)
	getDebug() bool
    filterFields(table string, data map[string]interface{}) map[string]interface{}
    getTableFields(table string) (map[string]string, error)
    handleSqlBeforeExec(sql string) string
}

// 执行底层数据库操作的核心接口
type dbLink interface {
    Query(query string, args ...interface{}) (*sql.Rows, error)
    Exec(sql string, args ...interface{}) (sql.Result, error)
    Prepare(sql string) (*sql.Stmt, error)
}

// 数据库链接对象
type dbBase struct {
	db               DB            // 数据库对象
	group            string        // 配置分组名称
	debug            *gtype.Bool   // (默认关闭)是否开启调试模式，当开启时会启用一些调试特性
	sqls             *gring.Ring   // (debug=true时有效)已执行的SQL列表
	cache            *gcache.Cache // 数据库缓存，包括底层连接池对象缓存及查询缓存；需要注意的是，事务查询不支持查询缓存
    schema           *gtype.String // 手动切换的数据库名称
	maxIdleConnCount *gtype.Int    // 连接池最大限制的连接数
    maxOpenConnCount *gtype.Int    // 连接池最大打开的连接数
    maxConnLifetime  *gtype.Int    // (单位秒)连接对象可重复使用的时间长度
}

// 执行的SQL对象
type Sql struct {
	Sql   string        // SQL语句(可能带有预处理占位符)
	Args  []interface{} // 预处理参数值列表
	Error error         // 执行结果(nil为成功)
	Start int64         // 执行开始时间(毫秒)
	End   int64         // 执行结束时间(毫秒)
	Func  string        // 执行方法
}

// 返回数据表记录值
type Value = gvar.VarRead

// 返回数据表记录Map
type Record map[string]Value

// 返回数据表记录List
type Result []Record

// 关联数组，绑定一条数据表记录(使用别名)
type Map  = map[string]interface{}

// 关联数组列表(索引从0开始的数组)，绑定多条记录(使用别名)
type List = []Map

const (
    OPTION_INSERT  = 0
    OPTION_REPLACE = 1
    OPTION_SAVE    = 2
    OPTION_IGNORE  = 3
    // 默认的连接池连接存活时间(秒)
    gDEFAULT_CONN_MAX_LIFE = 10
)

// 使用默认/指定分组配置进行连接，数据库集群配置项：default
func New(groupName ...string) (db DB, err error) {
	group := config.d
	if len(groupName) > 0 {
        group = groupName[0]
	}
	config.RLock()
	defer config.RUnlock()

	if len(config.c) < 1 {
		return nil, errors.New("empty database configuration")
	}
	if _, ok := config.c[group]; ok {
	    if node, err := getConfigNodeByGroup(group, true); err == nil {
	        base := &dbBase {
                group            : group,
                debug            : gtype.NewBool(),
                cache            : gcache.New(),
                schema           : gtype.NewString(),
                maxIdleConnCount : gtype.NewInt(),
                maxOpenConnCount : gtype.NewInt(),
                maxConnLifetime  : gtype.NewInt(gDEFAULT_CONN_MAX_LIFE),
            }
            switch node.Type {
                case "mysql":
                    base.db = &dbMysql{dbBase  : base}
                case "pgsql":
                    base.db = &dbPgsql{dbBase  : base}
                case "mssql":
                    base.db = &dbMssql{dbBase  : base}
                case "sqlite":
                    base.db = &dbSqlite{dbBase : base}
                case "oracle":
                    base.db = &dbOracle{dbBase : base}
                default:
                    return nil, errors.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
            }
            return base.db, nil
        } else {
            return nil, err
        }
	} else {
		return nil, errors.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
	}
}

// 获取指定数据库角色的一个配置项，内部根据权重计算负载均衡
func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
    if list, ok := config.c[group]; ok {
        // 将master, slave集群列表拆分出来
        masterList := make(ConfigGroup, 0)
        slaveList  := make(ConfigGroup, 0)
        for i := 0; i < len(list); i++ {
            if list[i].Role == "slave" {
                slaveList = append(slaveList, list[i])
            } else {
                masterList = append(masterList, list[i])
            }
        }
        if len(masterList) < 1 {
            return nil, errors.New("at least one master node configuration's need to make sense")
        }
        if len(slaveList) < 1 {
            slaveList = masterList
        }
        if master {
            return getConfigNodeByPriority(masterList), nil
        } else {
            return getConfigNodeByPriority(slaveList), nil
        }
    } else {
        return nil, errors.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
    }
}

// 按照负载均衡算法(优先级配置)从数据库集群中选择一个配置节点出来使用
// 算法说明举例，
// 1、假如2个节点的priority都是1，那么随机大小范围为[0, 199]；
// 2、那么节点1的权重范围为[0, 99]，节点2的权重范围为[100, 199]，比例为1:1；
// 3、假如计算出的随机数为99;
// 4、那么选择的配置为节点1;
func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode {
	if len(cg) < 2 {
		return &cg[0]
	}
	var total int
	for i := 0; i < len(cg); i++ {
		total += cg[i].Priority * 100
	}
	// 如果total为0表示所有连接都没有配置priority属性，那么默认都是1
	if total == 0 {
        for i := 0; i < len(cg); i++ {
            cg[i].Priority = 1
            total         += cg[i].Priority * 100
        }
    }
	// 不能取到末尾的边界点
	r := grand.Rand(0, total)
	if r > 0 {
		r -= 1
	}
	min := 0
	max := 0
	for i := 0; i < len(cg); i++ {
		max = min + cg[i].Priority*100
		//fmt.Printf("r: %d, min: %d, max: %d\n", r, min, max)
		if r >= min && r < max {
			return &cg[i]
		} else {
			min = max
		}
	}
	return nil
}

// 获得底层数据库链接对象
func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) {
    // 负载均衡
    node, err := getConfigNodeByGroup(bs.group, master)
    if err != nil {
        return nil, err
    }
    // 默认值设定
    if node.Charset == "" {
        node.Charset = "utf8"
    }
    v := bs.cache.GetOrSetFuncLock(node.String(), func() interface{} {
        sqlDb, err = bs.db.Open(node)
        if err != nil {
            return nil
        }

        if n := bs.maxIdleConnCount.Val(); n > 0 {
            sqlDb.SetMaxIdleConns(n)
        } else if node.MaxIdleConnCount > 0 {
            sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
        }

        if n := bs.maxOpenConnCount.Val(); n > 0 {
            sqlDb.SetMaxOpenConns(n)
        } else if node.MaxOpenConnCount > 0 {
            sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
        }

        if n := bs.maxConnLifetime.Val(); n > 0 {
            sqlDb.SetConnMaxLifetime(time.Duration(n) * time.Second)
        } else if node.MaxConnLifetime > 0 {
            sqlDb.SetConnMaxLifetime(time.Duration(node.MaxConnLifetime) * time.Second)
        }
        return sqlDb
    }, 0)
    if v != nil && sqlDb == nil {
        sqlDb = v.(*sql.DB)
    }
    // 是否手动选择数据库
    if v := bs.schema.Val(); v != "" {
        sqlDb.Exec("USE " + v)
    }
    return
}

// 切换操作的数据库(注意该切换是全局的)
func (bs *dbBase) SetSchema(schema string) {
    bs.schema.Set(schema)
}

// 创建底层数据库master链接对象
func (bs *dbBase) Master() (*sql.DB, error) {
	return bs.getSqlDb(true)
}

// 创建底层数据库slave链接对象
func (bs *dbBase) Slave() (*sql.DB, error) {
    return bs.getSqlDb(false)
}
<commit_msg>update default ConnMaxLifeTime to 30 seconds in gdb package<commit_after>// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.

// Package gdb provides ORM features for popular relationship databases.
// 数据库ORM.
// 默认内置支持MySQL, 其他数据库需要手动import对应的数据库引擎第三方包.
package gdb

import (
    "database/sql"
    "errors"
    "fmt"
    "gitee.com/johng/gf/g/container/gring"
    "gitee.com/johng/gf/g/container/gtype"
    "gitee.com/johng/gf/g/container/gvar"
    "gitee.com/johng/gf/g/os/gcache"
    "gitee.com/johng/gf/g/util/grand"
    _ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
    "time"
)

// 数据库操作接口
type DB interface {
    // 建立数据库连接方法(开发者一般不需要直接调用)
    Open(config *ConfigNode) (*sql.DB, error)

	// SQL操作方法 API
	Query(query string, args ...interface{}) (*sql.Rows, error)
	Exec(sql string, args ...interface{}) (sql.Result, error)
	Prepare(sql string, execOnMaster...bool) (*sql.Stmt, error)

    // 内部实现API的方法(不同数据库可覆盖这些方法实现自定义的操作)
    doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error)
    doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error)
    doPrepare(link dbLink, query string) (*sql.Stmt, error)
    doInsert(link dbLink, table string, data Map, option int) (result sql.Result, err error)
    doBatchInsert(link dbLink, table string, list List, batch int, option int) (result sql.Result, err error)
    doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error)
    doDelete(link dbLink, table string, condition interface{}, args ...interface{}) (result sql.Result, err error)

	// 数据库查询
	GetAll(query string, args ...interface{}) (Result, error)
	GetOne(query string, args ...interface{}) (Record, error)
	GetValue(query string, args ...interface{}) (Value, error)
    GetCount(query string, args ...interface{}) (int, error)
    GetStruct(obj interface{}, query string, args ...interface{}) error

    // 创建底层数据库master/slave链接对象
    Master() (*sql.DB, error)
    Slave() (*sql.DB, error)

    // Ping
	PingMaster() error
	PingSlave() error

	// 开启事务操作
	Begin() (*TX, error)

	// 数据表插入/更新/保存操作
	Insert(table string, data Map) (sql.Result, error)
	Replace(table string, data Map) (sql.Result, error)
	Save(table string, data Map) (sql.Result, error)

	// 数据表插入/更新/保存操作(批量)
	BatchInsert(table string, list List, batch int) (sql.Result, error)
	BatchReplace(table string, list List, batch int) (sql.Result, error)
	BatchSave(table string, list List, batch int) (sql.Result, error)

	// 数据修改/删除
	Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
	Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)

	// 创建链式操作对象(Table为From的别名)
	Table(tables string) *Model
	From(tables string) *Model

	// 设置管理
    SetDebug(debug bool)
    SetSchema(schema string)
    GetQueriedSqls() []*Sql
    PrintQueriedSqls()
    SetMaxIdleConns(n int)
    SetMaxOpenConns(n int)
    SetConnMaxLifetime(n int)

	// 内部方法接口
	getCache() (*gcache.Cache)
	getChars() (charLeft string, charRight string)
	getDebug() bool
    filterFields(table string, data map[string]interface{}) map[string]interface{}
    getTableFields(table string) (map[string]string, error)
    handleSqlBeforeExec(sql string) string
}

// 执行底层数据库操作的核心接口
type dbLink interface {
    Query(query string, args ...interface{}) (*sql.Rows, error)
    Exec(sql string, args ...interface{}) (sql.Result, error)
    Prepare(sql string) (*sql.Stmt, error)
}

// 数据库链接对象
type dbBase struct {
	db               DB            // 数据库对象
	group            string        // 配置分组名称
	debug            *gtype.Bool   // (默认关闭)是否开启调试模式，当开启时会启用一些调试特性
	sqls             *gring.Ring   // (debug=true时有效)已执行的SQL列表
	cache            *gcache.Cache // 数据库缓存，包括底层连接池对象缓存及查询缓存；需要注意的是，事务查询不支持查询缓存
    schema           *gtype.String // 手动切换的数据库名称
	maxIdleConnCount *gtype.Int    // 连接池最大限制的连接数
    maxOpenConnCount *gtype.Int    // 连接池最大打开的连接数
    maxConnLifetime  *gtype.Int    // (单位秒)连接对象可重复使用的时间长度
}

// 执行的SQL对象
type Sql struct {
	Sql   string        // SQL语句(可能带有预处理占位符)
	Args  []interface{} // 预处理参数值列表
	Error error         // 执行结果(nil为成功)
	Start int64         // 执行开始时间(毫秒)
	End   int64         // 执行结束时间(毫秒)
	Func  string        // 执行方法
}

// 返回数据表记录值
type Value = gvar.VarRead

// 返回数据表记录Map
type Record map[string]Value

// 返回数据表记录List
type Result []Record

// 关联数组，绑定一条数据表记录(使用别名)
type Map  = map[string]interface{}

// 关联数组列表(索引从0开始的数组)，绑定多条记录(使用别名)
type List = []Map

const (
    OPTION_INSERT  = 0
    OPTION_REPLACE = 1
    OPTION_SAVE    = 2
    OPTION_IGNORE  = 3
    // 默认的连接池连接存活时间(秒)
    gDEFAULT_CONN_MAX_LIFE = 30
)

// 使用默认/指定分组配置进行连接，数据库集群配置项：default
func New(groupName ...string) (db DB, err error) {
	group := config.d
	if len(groupName) > 0 {
        group = groupName[0]
	}
	config.RLock()
	defer config.RUnlock()

	if len(config.c) < 1 {
		return nil, errors.New("empty database configuration")
	}
	if _, ok := config.c[group]; ok {
	    if node, err := getConfigNodeByGroup(group, true); err == nil {
	        base := &dbBase {
                group            : group,
                debug            : gtype.NewBool(),
                cache            : gcache.New(),
                schema           : gtype.NewString(),
                maxIdleConnCount : gtype.NewInt(),
                maxOpenConnCount : gtype.NewInt(),
                maxConnLifetime  : gtype.NewInt(gDEFAULT_CONN_MAX_LIFE),
            }
            switch node.Type {
                case "mysql":
                    base.db = &dbMysql{dbBase  : base}
                case "pgsql":
                    base.db = &dbPgsql{dbBase  : base}
                case "mssql":
                    base.db = &dbMssql{dbBase  : base}
                case "sqlite":
                    base.db = &dbSqlite{dbBase : base}
                case "oracle":
                    base.db = &dbOracle{dbBase : base}
                default:
                    return nil, errors.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
            }
            return base.db, nil
        } else {
            return nil, err
        }
	} else {
		return nil, errors.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
	}
}

// 获取指定数据库角色的一个配置项，内部根据权重计算负载均衡
func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
    if list, ok := config.c[group]; ok {
        // 将master, slave集群列表拆分出来
        masterList := make(ConfigGroup, 0)
        slaveList  := make(ConfigGroup, 0)
        for i := 0; i < len(list); i++ {
            if list[i].Role == "slave" {
                slaveList = append(slaveList, list[i])
            } else {
                masterList = append(masterList, list[i])
            }
        }
        if len(masterList) < 1 {
            return nil, errors.New("at least one master node configuration's need to make sense")
        }
        if len(slaveList) < 1 {
            slaveList = masterList
        }
        if master {
            return getConfigNodeByPriority(masterList), nil
        } else {
            return getConfigNodeByPriority(slaveList), nil
        }
    } else {
        return nil, errors.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
    }
}

// 按照负载均衡算法(优先级配置)从数据库集群中选择一个配置节点出来使用
// 算法说明举例，
// 1、假如2个节点的priority都是1，那么随机大小范围为[0, 199]；
// 2、那么节点1的权重范围为[0, 99]，节点2的权重范围为[100, 199]，比例为1:1；
// 3、假如计算出的随机数为99;
// 4、那么选择的配置为节点1;
func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode {
	if len(cg) < 2 {
		return &cg[0]
	}
	var total int
	for i := 0; i < len(cg); i++ {
		total += cg[i].Priority * 100
	}
	// 如果total为0表示所有连接都没有配置priority属性，那么默认都是1
	if total == 0 {
        for i := 0; i < len(cg); i++ {
            cg[i].Priority = 1
            total         += cg[i].Priority * 100
        }
    }
	// 不能取到末尾的边界点
	r := grand.Rand(0, total)
	if r > 0 {
		r -= 1
	}
	min := 0
	max := 0
	for i := 0; i < len(cg); i++ {
		max = min + cg[i].Priority*100
		//fmt.Printf("r: %d, min: %d, max: %d\n", r, min, max)
		if r >= min && r < max {
			return &cg[i]
		} else {
			min = max
		}
	}
	return nil
}

// 获得底层数据库链接对象
func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) {
    // 负载均衡
    node, err := getConfigNodeByGroup(bs.group, master)
    if err != nil {
        return nil, err
    }
    // 默认值设定
    if node.Charset == "" {
        node.Charset = "utf8"
    }
    v := bs.cache.GetOrSetFuncLock(node.String(), func() interface{} {
        sqlDb, err = bs.db.Open(node)
        if err != nil {
            return nil
        }

        if n := bs.maxIdleConnCount.Val(); n > 0 {
            sqlDb.SetMaxIdleConns(n)
        } else if node.MaxIdleConnCount > 0 {
            sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
        }

        if n := bs.maxOpenConnCount.Val(); n > 0 {
            sqlDb.SetMaxOpenConns(n)
        } else if node.MaxOpenConnCount > 0 {
            sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
        }

        if n := bs.maxConnLifetime.Val(); n > 0 {
            sqlDb.SetConnMaxLifetime(time.Duration(n) * time.Second)
        } else if node.MaxConnLifetime > 0 {
            sqlDb.SetConnMaxLifetime(time.Duration(node.MaxConnLifetime) * time.Second)
        }
        return sqlDb
    }, 0)
    if v != nil && sqlDb == nil {
        sqlDb = v.(*sql.DB)
    }
    // 是否手动选择数据库
    if v := bs.schema.Val(); v != "" {
        sqlDb.Exec("USE " + v)
    }
    return
}

// 切换操作的数据库(注意该切换是全局的)
func (bs *dbBase) SetSchema(schema string) {
    bs.schema.Set(schema)
}

// 创建底层数据库master链接对象
func (bs *dbBase) Master() (*sql.DB, error) {
	return bs.getSqlDb(true)
}

// 创建底层数据库slave链接对象
func (bs *dbBase) Slave() (*sql.DB, error) {
    return bs.getSqlDb(false)
}
<|endoftext|>


<commit_before>package main

import (
  "fmt"
  "math"
  "math/rand"
  "time"

  log "github.com/Sirupsen/logrus"
)

const (
  xMax           = 500
  yMax           = 500
  xMin           = 0
  yMin           = 0
  levelUpSeconds = 600
  levelUpBase    = float64(1.16)
)

type Game struct {
  startedAt        time.Time
  heroes           []Hero
  adminToken       string
  joinChan         chan JoinRequest
  activateHeroChan chan ActivateHeroRequest
  exitChan         chan []byte
}

type Hero struct {
  Name        string `json:"name"`
  Email       string `json:"email"`
  Class       string `json:"class"`
  Enabled     bool   `json:"enabled"`
  token       string
  Level       int `json:"level"`
  nextLevelAt time.Time
  createdAt   time.Time
  Equipment   Equipment `json:"equipment"`
  Xpos        int       `json:"x_pos"`
  Ypos        int       `json:"y_pos"`
}

type Equipment struct {
  Ring     int
  Amulet   int
  Charm    int
  Weapon   int
  Helm     int
  Tunic    int
  Gloves   int
  Shield   int
  Leggings int
  Boots    int
}

// NewGame creates a new game
func NewGame(adminToken string) *Game {
  game := &Game{
    startedAt:        time.Now(),
    heroes:           []Hero{},
    joinChan:         make(chan JoinRequest),
    activateHeroChan: make(chan ActivateHeroRequest),
    exitChan:         make(chan []byte),
    adminToken:       adminToken,
  }
  return game
}

// StartGame starts the game
func StartGame(adminToken string) {
  game := NewGame(adminToken)

  go game.StartEngine()
  game.StartAPI()
}

// StartEngine starts the engine
func (g *Game) StartEngine() {
  ticker := time.NewTicker(time.Second * 2)
  for {
    select {
    case <-ticker.C:
      g.moveHeroes()
      g.checkLevels()
      //TODO: check battles
    case req := <-g.joinChan:
      log.Info("Join hero")
      success, message := g.joinHero(req.name, req.email, req.heroClass, req.TokenRequest.token)
      req.Response <- GameResponse{success: success, message: message}
      close(req.Response)
    case req := <-g.activateHeroChan:
      log.Info("Activate hero")
      success := g.activateHero(req.name, req.TokenRequest.token)
      req.Response <- GameResponse{success: success, message: ""}
      close(req.Response)
    case <-g.exitChan:
      log.Info("Exiting game")
      return
    }
  }

}

func (g *Game) joinHero(name, email, class, adminToken string) (bool, string) {

  if !g.authorizeAdmin(adminToken) {
    return false, "You are not authorized to perform this action."
  }

  hero := &Hero{
    Name:        name,
    Email:       email,
    Class:       class,
    Enabled:     false,
    token:       randToken(),
    Level:       0,
    nextLevelAt: time.Now().Add(99999 * time.Hour),
    createdAt:   time.Now(),
    Equipment: Equipment{
      Ring:     0,
      Amulet:   0,
      Charm:    0,
      Weapon:   0,
      Helm:     0,
      Tunic:    0,
      Gloves:   0,
      Shield:   0,
      Leggings: 0,
      Boots:    0,
    },
    Xpos: rand.Intn(xMax-xMin) + xMin,
    Ypos: rand.Intn(yMax-yMin) + yMin,
  }

  g.heroes = append(g.heroes, *hero)
  log.Infof("Hero %s has been created, but will not play until it's activated.", hero.Name)
  return true, fmt.Sprintf("Token: %s", hero.token)
}

func (g *Game) activateHero(name, token string) bool {
  i := g.getHeroIndex(name)
  if i == -1 {
    return false
  }
  if g.heroes[i].token != token {
    return false
  }

  ttl := getTTL(1) // Time to level 1
  g.heroes[i].Enabled = true
  g.heroes[i].nextLevelAt = time.Now().Add(ttl * time.Second)
  log.Infof("Success! Hero %s has been activated and will reach level 1 in %d seconds.", g.heroes[i].Name, ttl)
  return true
}

func (g *Game) moveHeroes() {
  for i := range g.heroes {
    if !g.heroes[i].Enabled {
      continue
    }
    g.heroes[i].Xpos = truncateInt(g.heroes[i].Xpos+(rand.Intn(3)-1), xMin, xMax)
    g.heroes[i].Ypos = truncateInt(g.heroes[i].Ypos+(rand.Intn(3)-1), yMin, yMax)
  }
}

func (g *Game) checkLevels() {
  for i := range g.heroes {
    if !g.heroes[i].Enabled {
      continue
    }

    if g.heroes[i].nextLevelAt.Before(time.Now()) {
      level := g.heroes[i].Level + 1
      ttl := getTTL(level)
      g.heroes[i].nextLevelAt = time.Now().Add(ttl * time.Second)
      g.heroes[i].Level = level
      log.Infof("Hero %s reached level %d. Next level in %d seconds.", g.heroes[i].Name, level, ttl)
    }
  }
}

func (g *Game) authorizeAdmin(token string) bool {
  return g.adminToken == token
}

func (g *Game) getHeroIndex(name string) int {
  for i, hero := range g.heroes {
    if hero.Name == name {
      return i
    }
  }
  return -1
}

func getTTL(level int) time.Duration {
  return time.Duration(levelUpSeconds * (math.Pow(levelUpBase, float64(level))))
}
<commit_msg>Fixes bug calculating TTL<commit_after>package main

import (
  "fmt"
  "math"
  "math/rand"
  "time"

  log "github.com/Sirupsen/logrus"
)

const (
  xMax           = 500
  yMax           = 500
  xMin           = 0
  yMin           = 0
  levelUpSeconds = 600
  levelUpBase    = float64(1.16)
)

type Game struct {
  startedAt        time.Time
  heroes           []Hero
  adminToken       string
  joinChan         chan JoinRequest
  activateHeroChan chan ActivateHeroRequest
  exitChan         chan []byte
}

type Hero struct {
  Name        string `json:"name"`
  Email       string `json:"email"`
  Class       string `json:"class"`
  Enabled     bool   `json:"enabled"`
  token       string
  Level       int `json:"level"`
  nextLevelAt time.Time
  createdAt   time.Time
  Equipment   Equipment `json:"equipment"`
  Xpos        int       `json:"x_pos"`
  Ypos        int       `json:"y_pos"`
}

type Equipment struct {
  Ring     int
  Amulet   int
  Charm    int
  Weapon   int
  Helm     int
  Tunic    int
  Gloves   int
  Shield   int
  Leggings int
  Boots    int
}

// NewGame creates a new game
func NewGame(adminToken string) *Game {
  game := &Game{
    startedAt:        time.Now(),
    heroes:           []Hero{},
    joinChan:         make(chan JoinRequest),
    activateHeroChan: make(chan ActivateHeroRequest),
    exitChan:         make(chan []byte),
    adminToken:       adminToken,
  }
  return game
}

// StartGame starts the game
func StartGame(adminToken string) {
  game := NewGame(adminToken)

  go game.StartEngine()
  game.StartAPI()
}

// StartEngine starts the engine
func (g *Game) StartEngine() {
  ticker := time.NewTicker(time.Second * 2)
  for {
    select {
    case <-ticker.C:
      g.moveHeroes()
      g.checkLevels()
      //TODO: check battles
    case req := <-g.joinChan:
      log.Info("Join hero")
      success, message := g.joinHero(req.name, req.email, req.heroClass, req.TokenRequest.token)
      req.Response <- GameResponse{success: success, message: message}
      close(req.Response)
    case req := <-g.activateHeroChan:
      log.Info("Activate hero")
      success := g.activateHero(req.name, req.TokenRequest.token)
      req.Response <- GameResponse{success: success, message: ""}
      close(req.Response)
    case <-g.exitChan:
      log.Info("Exiting game")
      return
    }
  }

}

func (g *Game) joinHero(name, email, class, adminToken string) (bool, string) {

  if !g.authorizeAdmin(adminToken) {
    return false, "You are not authorized to perform this action."
  }

  hero := &Hero{
    Name:        name,
    Email:       email,
    Class:       class,
    Enabled:     false,
    token:       randToken(),
    Level:       0,
    nextLevelAt: time.Now().Add(99999 * time.Hour),
    createdAt:   time.Now(),
    Equipment: Equipment{
      Ring:     0,
      Amulet:   0,
      Charm:    0,
      Weapon:   0,
      Helm:     0,
      Tunic:    0,
      Gloves:   0,
      Shield:   0,
      Leggings: 0,
      Boots:    0,
    },
    Xpos: rand.Intn(xMax-xMin) + xMin,
    Ypos: rand.Intn(yMax-yMin) + yMin,
  }

  g.heroes = append(g.heroes, *hero)
  log.Infof("Hero %s has been created, but will not play until it's activated.", hero.Name)
  return true, fmt.Sprintf("Token: %s", hero.token)
}

func (g *Game) activateHero(name, token string) bool {
  i := g.getHeroIndex(name)
  if i == -1 {
    return false
  }
  if g.heroes[i].token != token {
    return false
  }

  ttl := getTTL(1) // Time to level 1
  g.heroes[i].Enabled = true
  g.heroes[i].nextLevelAt = time.Now().Add(ttl * time.Second)
  log.Infof("Success! Hero %s has been activated and will reach level 1 in %d seconds.", g.heroes[i].Name, ttl)
  return true
}

func (g *Game) moveHeroes() {
  for i := range g.heroes {
    if !g.heroes[i].Enabled {
      continue
    }
    g.heroes[i].Xpos = truncateInt(g.heroes[i].Xpos+(rand.Intn(3)-1), xMin, xMax)
    g.heroes[i].Ypos = truncateInt(g.heroes[i].Ypos+(rand.Intn(3)-1), yMin, yMax)
  }
}

func (g *Game) checkLevels() {
  for i := range g.heroes {
    if !g.heroes[i].Enabled {
      continue
    }

    if g.heroes[i].nextLevelAt.Before(time.Now()) {
      level := g.heroes[i].Level + 1
      ttl := getTTL(level + 1)
      g.heroes[i].nextLevelAt = time.Now().Add(ttl * time.Second)
      g.heroes[i].Level = level
      log.Infof("Hero %s reached level %d. Next level in %d seconds.", g.heroes[i].Name, level, ttl)
    }
  }
}

func (g *Game) authorizeAdmin(token string) bool {
  return g.adminToken == token
}

func (g *Game) getHeroIndex(name string) int {
  for i, hero := range g.heroes {
    if hero.Name == name {
      return i
    }
  }
  return -1
}

func getTTL(level int) time.Duration {
  return time.Duration(levelUpSeconds * (math.Pow(levelUpBase, float64(level))))
}
<|endoftext|>