<commit_before>package wsync

import (
	"bytes"
	"io"
	"math/rand"
	"testing"
)

type RandReader struct {
	rand.Source
}

func (rr RandReader) Read(sink []byte) (int, error) {
	var tail, head int
	buf := make([]byte, 8)
	var r uint64
	for {
		head = min(tail+8, len(sink))
		if tail == head {
			return head, nil
		}

		r = (uint64)(rr.Int63())
		buf[0] = (byte)(r)
		buf[1] = (byte)(r >> 8)
		buf[2] = (byte)(r >> 16)
		buf[3] = (byte)(r >> 24)
		buf[4] = (byte)(r >> 32)
		buf[5] = (byte)(r >> 40)
		buf[6] = (byte)(r >> 48)
		buf[7] = (byte)(r >> 56)

		tail += copy(sink[tail:head], buf)
	}
}

type pair struct {
	Source, Target content
	Description    string
}
type content struct {
	Len   int
	Seed  int64
	Alter int
	Data  []byte
}

func must(t *testing.T, err error) {
	if err != nil {
		t.Error(err.Error())
		t.FailNow()
	}
}

func (c *content) Fill(t *testing.T) {
	c.Data = make([]byte, c.Len)
	src := rand.NewSource(c.Seed)
	_, err := RandReader{src}.Read(c.Data)
	must(t, err)

	if c.Alter > 0 {
		r := rand.New(src)
		for i := 0; i < c.Alter; i++ {
			at := r.Intn(len(c.Data))
			c.Data[at] += byte(r.Int())
		}
	}
}

func Test_GenData(t *testing.T) {
	// Use a seeded generator to get consistent results.
	// This allows testing the package without bundling many test files.

	var pairs = []pair{
		pair{
			Source:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Target:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 5},
			Description: "Same length, slightly different content.",
		},
		pair{
			Source:      content{Len: 8*512*1024 + 89, Seed: 9824, Alter: 0},
			Target:      content{Len: 8*512*1024 + 89, Seed: 2345, Alter: 0},
			Description: "Same length, very different content.",
		},
		pair{
			Source:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Target:      content{Len: 8*256*1024 + 19, Seed: 42, Alter: 0},
			Description: "Target shorter then source, same content.",
		},
		pair{
			Source:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Target:      content{Len: 8*256*1024 + 19, Seed: 42, Alter: 5},
			Description: "Target shorter then source, slightly different content.",
		},
		pair{
			Source:      content{Len: 8*256*1024 + 19, Seed: 42, Alter: 0},
			Target:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Description: "Source shorter then target, same content.",
		},
		pair{
			Source:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 5},
			Target:      content{Len: 8*256*1024 + 19, Seed: 42, Alter: 0},
			Description: "Source shorter then target, slightly different content.",
		},
		pair{
			Source:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Target:      content{Len: 0, Seed: 42, Alter: 0},
			Description: "Target empty and source has content.",
		},
		pair{
			Source:      content{Len: 0, Seed: 42, Alter: 0},
			Target:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Description: "Source empty and target has content.",
		},
		pair{
			Source:      content{Len: 8 * 872, Seed: 9824, Alter: 0},
			Target:      content{Len: 8 * 235, Seed: 2345, Alter: 0},
			Description: "Source and target both smaller then a block size.",
		},
	}
	rs := NewContext(16 * 1024)
	rsDelta := NewContext(16 * 1024)
	for _, p := range pairs {
		(&p.Source).Fill(t)
		(&p.Target).Fill(t)

		sourceBuffer := bytes.NewReader(p.Source.Data)
		targetBuffer := bytes.NewReader(p.Target.Data)

		sig := make([]BlockHash, 0, 10)
		err := rs.CreateSignature(0, targetBuffer, func(bl BlockHash) error {
			sig = append(sig, bl)
			return nil
		})
		if err != nil {
			t.Errorf("Failed to create signature: %s", err)
		}
		lib := NewBlockLibrary(sig)

		opsOut := make(chan Operation)
		go func() {
			var blockRangeCt, dataCt, bytes int
			defer close(opsOut)
			deltaErr := rsDelta.ComputeDiff(sourceBuffer, lib, func(op Operation) error {
				switch op.Type {
				case OpBlockRange:
					blockRangeCt++
				case OpData:
					// Copy data buffer so it may be reused in internal buffer.
					b := make([]byte, len(op.Data))
					copy(b, op.Data)
					op.Data = b
					dataCt++
					bytes += len(op.Data)
				}
				opsOut <- op
				return nil
			}, -1)
			t.Logf("Range Ops:%5d, Data Ops: %5d, Data Len: %5dKiB, For %s.", blockRangeCt, dataCt, bytes/1024, p.Description)
			if deltaErr != nil {
				t.Errorf("Failed to create delta: %s", deltaErr)
			}
		}()

		result := new(bytes.Buffer)
		pool := &SinglePool{reader: targetBuffer}

		_, err = targetBuffer.Seek(0, 0)
		must(t, err)
		err = rs.ApplyPatch(result, pool, opsOut)
		if err != nil {
			t.Errorf("Failed to apply delta: %s", err)
		}

		if result.Len() != len(p.Source.Data) {
			t.Errorf("Result not same size as source: %s", p.Description)
		} else if bytes.Equal(result.Bytes(), p.Source.Data) == false {
			t.Errorf("Result is different from the source: %s", p.Description)
		}

		p.Source.Data = nil
		p.Target.Data = nil
	}
}

type SinglePool struct {
	reader io.ReadSeeker
}

var _ Pool = (*SinglePool)(nil)

func (sp *SinglePool) GetReader(fileIndex int64) (io.Reader, error) {
	return sp.GetReadSeeker(fileIndex)
}

func (sp *SinglePool) GetReadSeeker(fileIndex int64) (io.ReadSeeker, error) {
	return sp.reader, nil
}

func (sp *SinglePool) Close() error {
	return nil
}
<commit_msg>Simplify source in wsync/gen_test.go<commit_after>package wsync

import (
	"bytes"
	"io"
	"math/rand"
	"testing"
)

type RandReader struct {
	rand.Source
}

func (rr RandReader) Read(sink []byte) (int, error) {
	var tail, head int
	buf := make([]byte, 8)
	var r uint64
	for {
		head = min(tail+8, len(sink))
		if tail == head {
			return head, nil
		}

		r = (uint64)(rr.Int63())
		buf[0] = (byte)(r)
		buf[1] = (byte)(r >> 8)
		buf[2] = (byte)(r >> 16)
		buf[3] = (byte)(r >> 24)
		buf[4] = (byte)(r >> 32)
		buf[5] = (byte)(r >> 40)
		buf[6] = (byte)(r >> 48)
		buf[7] = (byte)(r >> 56)

		tail += copy(sink[tail:head], buf)
	}
}

type pair struct {
	Source, Target content
	Description    string
}
type content struct {
	Len   int
	Seed  int64
	Alter int
	Data  []byte
}

func must(t *testing.T, err error) {
	if err != nil {
		t.Error(err.Error())
		t.FailNow()
	}
}

func (c *content) Fill(t *testing.T) {
	c.Data = make([]byte, c.Len)
	src := rand.NewSource(c.Seed)
	_, err := RandReader{src}.Read(c.Data)
	must(t, err)

	if c.Alter > 0 {
		r := rand.New(src)
		for i := 0; i < c.Alter; i++ {
			at := r.Intn(len(c.Data))
			c.Data[at] += byte(r.Int())
		}
	}
}

func Test_GenData(t *testing.T) {
	// Use a seeded generator to get consistent results.
	// This allows testing the package without bundling many test files.

	var pairs = []pair{
		{
			Source:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Target:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 5},
			Description: "Same length, slightly different content.",
		},
		{
			Source:      content{Len: 8*512*1024 + 89, Seed: 9824, Alter: 0},
			Target:      content{Len: 8*512*1024 + 89, Seed: 2345, Alter: 0},
			Description: "Same length, very different content.",
		},
		{
			Source:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Target:      content{Len: 8*256*1024 + 19, Seed: 42, Alter: 0},
			Description: "Target shorter then source, same content.",
		},
		{
			Source:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Target:      content{Len: 8*256*1024 + 19, Seed: 42, Alter: 5},
			Description: "Target shorter then source, slightly different content.",
		},
		{
			Source:      content{Len: 8*256*1024 + 19, Seed: 42, Alter: 0},
			Target:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Description: "Source shorter then target, same content.",
		},
		{
			Source:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 5},
			Target:      content{Len: 8*256*1024 + 19, Seed: 42, Alter: 0},
			Description: "Source shorter then target, slightly different content.",
		},
		{
			Source:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Target:      content{Len: 0, Seed: 42, Alter: 0},
			Description: "Target empty and source has content.",
		},
		{
			Source:      content{Len: 0, Seed: 42, Alter: 0},
			Target:      content{Len: 8*512*1024 + 89, Seed: 42, Alter: 0},
			Description: "Source empty and target has content.",
		},
		{
			Source:      content{Len: 8 * 872, Seed: 9824, Alter: 0},
			Target:      content{Len: 8 * 235, Seed: 2345, Alter: 0},
			Description: "Source and target both smaller then a block size.",
		},
	}
	rs := NewContext(16 * 1024)
	rsDelta := NewContext(16 * 1024)
	for _, p := range pairs {
		(&p.Source).Fill(t)
		(&p.Target).Fill(t)

		sourceBuffer := bytes.NewReader(p.Source.Data)
		targetBuffer := bytes.NewReader(p.Target.Data)

		sig := make([]BlockHash, 0, 10)
		err := rs.CreateSignature(0, targetBuffer, func(bl BlockHash) error {
			sig = append(sig, bl)
			return nil
		})
		if err != nil {
			t.Errorf("Failed to create signature: %s", err)
		}
		lib := NewBlockLibrary(sig)

		opsOut := make(chan Operation)
		go func() {
			var blockRangeCt, dataCt, bytes int
			defer close(opsOut)
			deltaErr := rsDelta.ComputeDiff(sourceBuffer, lib, func(op Operation) error {
				switch op.Type {
				case OpBlockRange:
					blockRangeCt++
				case OpData:
					// Copy data buffer so it may be reused in internal buffer.
					b := make([]byte, len(op.Data))
					copy(b, op.Data)
					op.Data = b
					dataCt++
					bytes += len(op.Data)
				}
				opsOut <- op
				return nil
			}, -1)
			t.Logf("Range Ops:%5d, Data Ops: %5d, Data Len: %5dKiB, For %s.", blockRangeCt, dataCt, bytes/1024, p.Description)
			if deltaErr != nil {
				t.Errorf("Failed to create delta: %s", deltaErr)
			}
		}()

		result := new(bytes.Buffer)
		pool := &SinglePool{reader: targetBuffer}

		_, err = targetBuffer.Seek(0, 0)
		must(t, err)
		err = rs.ApplyPatch(result, pool, opsOut)
		if err != nil {
			t.Errorf("Failed to apply delta: %s", err)
		}

		if result.Len() != len(p.Source.Data) {
			t.Errorf("Result not same size as source: %s", p.Description)
		} else if bytes.Equal(result.Bytes(), p.Source.Data) == false {
			t.Errorf("Result is different from the source: %s", p.Description)
		}

		p.Source.Data = nil
		p.Target.Data = nil
	}
}

type SinglePool struct {
	reader io.ReadSeeker
}

var _ Pool = (*SinglePool)(nil)

func (sp *SinglePool) GetReader(fileIndex int64) (io.Reader, error) {
	return sp.GetReadSeeker(fileIndex)
}

func (sp *SinglePool) GetReadSeeker(fileIndex int64) (io.ReadSeeker, error) {
	return sp.reader, nil
}

func (sp *SinglePool) Close() error {
	return nil
}
<|endoftext|>


<commit_before>package models

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

	client "github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/mvcc/mvccpb"

	"sunteng/commons/log"
	"sunteng/cronsun/conf"
)

const (
	DefaultJobGroup = "default"
)

// 需要执行的 cron cmd 命令
// 注册到 /cronsun/cmd/groupName/<id>
type Job struct {
	ID      string     `json:"id"`
	Name    string     `json:"name"`
	Group   string     `json:"group"`
	Command string     `json:"cmd"`
	Rule    []*JobRule `json:"rule"`
	Pause   bool       `json:"pause"` // 可手工控制的状态

	// node 服务使用
	// 每个任务在单个结点上只支持一个时间规则
	// 如果需要多个时间规则，需建新的任务
	schedule string
	gid      string
	build    bool
}

type JobRule struct {
	Timer          string   `json:"timer"`
	GroupIDs       []string `json:"gids"`
	NodeIDs        []string `json:"nids"`
	ExcludeNodeIDs []string `json:"exclude_nids"`
}

func (j *JobRule) included(nid string, gs map[string]*Group) (string, bool) {
	for _, gid := range j.GroupIDs {
		if _, ok := gs[gid]; ok {
			return gid, true
		}
	}

	for i, count := 0, len(j.NodeIDs); i < count; i++ {
		if nid == j.NodeIDs[i] {
			return "", true
		}
	}

	return "", false
}

func GetJob(group, id string) (job *Job, err error) {
	job, _, err = GetJobAndRev(group, id)
	return
}

func GetJobAndRev(group, id string) (job *Job, rev int64, err error) {
	resp, err := DefalutClient.Get(JobKey(group, id))
	if err != nil {
		return
	}

	if resp.Count == 0 {
		err = ErrNotFound
		return
	}

	rev = resp.Kvs[0].ModRevision
	err = json.Unmarshal(resp.Kvs[0].Value, &job)
	return
}

func DeleteJob(group, id string) (resp *client.DeleteResponse, err error) {
	return DefalutClient.Delete(JobKey(group, id))
}

func GetJobs() (jobs map[string]*Job, err error) {
	resp, err := DefalutClient.Get(conf.Config.Cmd, client.WithPrefix())
	if err != nil {
		return
	}

	count := len(resp.Kvs)
	if count == 0 {
		return
	}

	jobs = make(map[string]*Job, count)
	for _, j := range resp.Kvs {
		job := new(Job)
		if e := json.Unmarshal(j.Value, job); e != nil {
			log.Warnf("job[%s] umarshal err: %s", string(j.Key), e.Error())
			continue
		}
		jobs[job.ID] = job
	}
	return
}

func WatchJobs() client.WatchChan {
	return DefalutClient.Watch(conf.Config.Cmd, client.WithPrefix())
}

func GetJobFromKv(kv *mvccpb.KeyValue) (job *Job, err error) {
	job = new(Job)
	if err = json.Unmarshal(kv.Value, job); err != nil {
		err = fmt.Errorf("job[%s] umarshal err: %s", string(kv.Key), err.Error())
	}
	return
}

// Schedule return schedule and group id
func (j *Job) Schedule(nid string, gs map[string]*Group, rebuild bool) (sch string, gid string) {
	if j.Pause {
		return
	}

	if j.build && !rebuild {
		return j.schedule, j.gid
	}

	j.buildSchedule(nid, gs)
	return j.schedule, j.gid
}

func (j *Job) buildSchedule(nid string, gs map[string]*Group) {
	j.build = true
	for _, r := range j.Rule {
		for _, id := range r.ExcludeNodeIDs {
			if nid == id {
				return
			}
		}

		if gid, ok := r.included(nid, gs); ok {
			j.schedule, j.gid = r.Timer, gid
			return
		}
	}
}

func (j *Job) GetID() string {
	return j.ID
}

func (j *Job) Run() {
}

func JobKey(group, id string) string {
	return conf.Config.Cmd + group + "/" + id
}

func (j *Job) Key() string {
	return JobKey(j.Group, j.ID)
}

func (j *Job) Check() error {
	j.ID = strings.TrimSpace(j.ID)
	if !IsValidAsKeyPath(j.ID) {
		return ErrIllegalJobId
	}

	j.Name = strings.TrimSpace(j.Name)
	if len(j.Name) == 0 {
		return ErrEmptyJobName
	}

	j.Group = strings.TrimSpace(j.Group)
	if len(j.Group) == 0 {
		j.Group = DefaultJobGroup
	}

	if !IsValidAsKeyPath(j.Group) {
		return ErrIllegalJobGroupName
	}

	// 不修改 Command 的内容，简单判断是否为空
	if len(strings.TrimSpace(j.Command)) == 0 {
		return ErrEmptyJobCommand
	}

	return nil
}
<commit_msg>字段名Jon.rule改为rules<commit_after>package models

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

	client "github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/mvcc/mvccpb"

	"sunteng/commons/log"
	"sunteng/cronsun/conf"
)

const (
	DefaultJobGroup = "default"
)

// 需要执行的 cron cmd 命令
// 注册到 /cronsun/cmd/groupName/<id>
type Job struct {
	ID      string     `json:"id"`
	Name    string     `json:"name"`
	Group   string     `json:"group"`
	Command string     `json:"cmd"`
	Rules   []*JobRule `json:"rules"`
	Pause   bool       `json:"pause"` // 可手工控制的状态

	// node 服务使用
	// 每个任务在单个结点上只支持一个时间规则
	// 如果需要多个时间规则，需建新的任务
	schedule string
	gid      string
	build    bool
}

type JobRule struct {
	Timer          string   `json:"timer"`
	GroupIDs       []string `json:"gids"`
	NodeIDs        []string `json:"nids"`
	ExcludeNodeIDs []string `json:"exclude_nids"`
}

func (j *JobRule) included(nid string, gs map[string]*Group) (string, bool) {
	for _, gid := range j.GroupIDs {
		if _, ok := gs[gid]; ok {
			return gid, true
		}
	}

	for i, count := 0, len(j.NodeIDs); i < count; i++ {
		if nid == j.NodeIDs[i] {
			return "", true
		}
	}

	return "", false
}

func GetJob(group, id string) (job *Job, err error) {
	job, _, err = GetJobAndRev(group, id)
	return
}

func GetJobAndRev(group, id string) (job *Job, rev int64, err error) {
	resp, err := DefalutClient.Get(JobKey(group, id))
	if err != nil {
		return
	}

	if resp.Count == 0 {
		err = ErrNotFound
		return
	}

	rev = resp.Kvs[0].ModRevision
	err = json.Unmarshal(resp.Kvs[0].Value, &job)
	return
}

func DeleteJob(group, id string) (resp *client.DeleteResponse, err error) {
	return DefalutClient.Delete(JobKey(group, id))
}

func GetJobs() (jobs map[string]*Job, err error) {
	resp, err := DefalutClient.Get(conf.Config.Cmd, client.WithPrefix())
	if err != nil {
		return
	}

	count := len(resp.Kvs)
	if count == 0 {
		return
	}

	jobs = make(map[string]*Job, count)
	for _, j := range resp.Kvs {
		job := new(Job)
		if e := json.Unmarshal(j.Value, job); e != nil {
			log.Warnf("job[%s] umarshal err: %s", string(j.Key), e.Error())
			continue
		}
		jobs[job.ID] = job
	}
	return
}

func WatchJobs() client.WatchChan {
	return DefalutClient.Watch(conf.Config.Cmd, client.WithPrefix())
}

func GetJobFromKv(kv *mvccpb.KeyValue) (job *Job, err error) {
	job = new(Job)
	if err = json.Unmarshal(kv.Value, job); err != nil {
		err = fmt.Errorf("job[%s] umarshal err: %s", string(kv.Key), err.Error())
	}
	return
}

// Schedule return schedule and group id
func (j *Job) Schedule(nid string, gs map[string]*Group, rebuild bool) (sch string, gid string) {
	if j.Pause {
		return
	}

	if j.build && !rebuild {
		return j.schedule, j.gid
	}

	j.buildSchedule(nid, gs)
	return j.schedule, j.gid
}

func (j *Job) buildSchedule(nid string, gs map[string]*Group) {
	j.build = true
	for _, r := range j.Rules {
		for _, id := range r.ExcludeNodeIDs {
			if nid == id {
				return
			}
		}

		if gid, ok := r.included(nid, gs); ok {
			j.schedule, j.gid = r.Timer, gid
			return
		}
	}
}

func (j *Job) GetID() string {
	return j.ID
}

func (j *Job) Run() {
}

func JobKey(group, id string) string {
	return conf.Config.Cmd + group + "/" + id
}

func (j *Job) Key() string {
	return JobKey(j.Group, j.ID)
}

func (j *Job) Check() error {
	j.ID = strings.TrimSpace(j.ID)
	if !IsValidAsKeyPath(j.ID) {
		return ErrIllegalJobId
	}

	j.Name = strings.TrimSpace(j.Name)
	if len(j.Name) == 0 {
		return ErrEmptyJobName
	}

	j.Group = strings.TrimSpace(j.Group)
	if len(j.Group) == 0 {
		j.Group = DefaultJobGroup
	}

	if !IsValidAsKeyPath(j.Group) {
		return ErrIllegalJobGroupName
	}

	// 不修改 Command 的内容，简单判断是否为空
	if len(strings.TrimSpace(j.Command)) == 0 {
		return ErrEmptyJobCommand
	}

	return nil
}
<|endoftext|>


<commit_before>package neustar

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/google/go-querystring/query"
)

const (
	// MonitorURI is the endpoint for calls to the monitoring API
	MonitorURI = "monitor/1.0"

	// LocationsURI is the endpoint for locations calls
	LocationsURI = "/locations"

	// SummaryURI is the endpoint for summary calls
	SummaryURI = "/summary"

	// AggregateURI is the endpoint for aggregate calls
	AggregateURI = "/aggregate"
)

// MonitorTypes is a slice of valid monitor types
var MonitorTypes = []string{"RealBrowserUser", "VirtualUser", "dns"}

// BrowserTypes is a slice of valid browser types
var BrowserTypes = []string{"FF", "CHROME", "IE"}

// UpdateIntervals is a slice of valid intervals
var UpdateIntervals = []int{1, 2, 3, 4, 5, 10, 15, 20, 30, 60}

// AggregateSampleDataFrequency is a slice of valid frequencies
var AggregateSampleDataFrequency = []string{"day", "hour"}

// AggregateSampleGroupBy is a slice of valid groupBy parameters
var AggregateSampleGroupBy = []string{"location", "step"}

// Monitoring holds monitoring config
type Monitoring struct {
	neustar *Neustar
}

// NewMonitor creates a new Monitoring object
func NewMonitor(neustar *Neustar) *Monitoring {
	return &Monitoring{
		neustar: neustar,
	}
}

// Create creates a new monitor and returns the monitor id of the newly
// created monitor. Name, interval, testScript and locations are required.
// Use the Get Monitoring Locations api to retrieve a list of monitoring locations.
func (m *Monitoring) Create() {}

// List retrieves a list of all monitors associated with your account,
//along with information about each. The monitor id that is returned
// is used to make other api calls.
func (m *Monitoring) List() ([]Monitor, int, error) {
	var response *http.Response
	var data map[string]map[string][]Monitor
	response, err := http.Get(fmt.Sprintf(
		"%s%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// Get retrieves information for a specific monitor associated with your
// account. The monitor id that is returned is used to make other api calls.
func (m *Monitoring) Get(id string) ([]Monitor, int, error) {
	var response *http.Response
	var data map[string]map[string][]Monitor
	response, err := http.Get(fmt.Sprintf(
		"%s%s/%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, id, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// Update changes some or all of the parameters of an existing monitor.
// Requires the monitor ID retrieved from the List Monitors api.
func (m *Monitoring) Update() {}

// Delete deletes the given monitor, stopping it from monitoring and removing
// all its monitoring data.
func (m *Monitoring) Delete(id string) (int, error) {
	var response *http.Response
	response, err := http.Get(fmt.Sprintf(
		"%s%s/%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, id, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return response.StatusCode, err
	}
	if response.StatusCode != 200 {
		return response.StatusCode, nil
	}
	return response.StatusCode, nil
}

// Samples returns all samples associated to this monitor for a given time period.
// This data is returned at a high level, which timing for the overall sample. To
// get the details for the specific sample, call the get raw sample data api. At a
// maximum, this api will return 2000 samples. If there are more than 2000 results
// returned, the 'more' field will be set to true and you can make another api call
// specifying an offset which would be equal to the number of results returned in the
// first api call plus the offset of that call.
func (m *Monitoring) Samples() ([]string, int, error) {
	var response *http.Response
	var data map[string]map[string][]string
	response, err := http.Get(fmt.Sprintf(
		"%s%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// RawSampleData retrieves the raw, HTTP Archive (HAR) data for a particular sample
func (m *Monitoring) RawSampleData(monitorID, sampleID string) {}

// AggregateSampleData retrieves the aggregated sample information for a given period
// of time. You can choose to aggregate the data for each hour or each day. This is
// more effecient than getting all the individual samples for a period of time and
// performing the aggregation yourself.
func (m *Monitoring) AggregateSampleData(monitorID string, asp *AggregateSampleParameters) ([]AggregateSampleDataResponse, int, error) {
	var response *http.Response
	v, err := query.Values(asp)
	if err != nil {
		return nil, 0, err
	}
	var data map[string]map[string][]AggregateSampleDataResponse
	response, err = http.Get(fmt.Sprintf(
		"%s%s/%s%s?%s&apikey=%s&sig=%s",
		BaseURL, MonitorURI, monitorID, AggregateURI, v.Encode(), m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// Summary provides the monitor summary api returns all of the data that is found when looking at your
// list of monitors in the web portal. This includes things such as the average load
// time, sample count and uptime for the day, week, month or year, the last time an
// error occurred, and the last error message.
func (m *Monitoring) Summary(monitorID string) ([]SummaryDataResponse, int, error) {
	var response *http.Response
	var data map[string]map[string][]SummaryDataResponse
	response, err := http.Get(fmt.Sprintf(
		"%s%s/%s%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, monitorID, SummaryURI, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// Locations gets a list of all monitoring locations available
func (m *Monitoring) Locations() ([]string, int, error) {
	var response *http.Response
	var data map[string]map[string][]string
	response, err := http.Get(fmt.Sprintf(
		"%s%s%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, LocationsURI, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// ValidMonitorType validates the given monitor type is valid
func ValidMonitorType(monitorType string) bool {
	for _, i := range MonitorTypes {
		if i == monitorType {
			return true
		}
	}
	return false
}

// ValidBrowserType validates the given browser type is valid
func ValidBrowserType(browserType string) bool {
	for _, i := range BrowserTypes {
		if i == browserType {
			return true
		}
	}
	return false
}

// ValidUpdateInterval validates the given interval is valid
func ValidUpdateInterval(interval int) bool {
	for _, i := range UpdateIntervals {
		if i == interval {
			return true
		}
	}
	return false
}

// ValidAggregateSampleDataFrequency validates the given frequency is valid
func ValidAggregateSampleDataFrequency(frequency string) bool {
	for _, i := range AggregateSampleDataFrequency {
		if i == frequency {
			return true
		}
	}
	return false
}

// ValidAggregateSampleGroupBy validates the given groupBy is valid
func ValidAggregateSampleGroupBy(groupBy string) bool {
	for _, i := range AggregateSampleGroupBy {
		if i == groupBy {
			return true
		}
	}
	return false
}
<commit_msg>bugfix for testing<commit_after>package neustar

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/google/go-querystring/query"
)

const (
	// MonitorURI is the endpoint for calls to the monitoring API
	MonitorURI = "monitor/1.0"

	// LocationsURI is the endpoint for locations calls
	LocationsURI = "/locations"

	// SummaryURI is the endpoint for summary calls
	SummaryURI = "/summary"

	// AggregateURI is the endpoint for aggregate calls
	AggregateURI = "/aggregate"
)

// MonitorTypes is a slice of valid monitor types
var MonitorTypes = []string{"RealBrowserUser", "VirtualUser", "dns"}

// BrowserTypes is a slice of valid browser types
var BrowserTypes = []string{"FF", "CHROME", "IE"}

// UpdateIntervals is a slice of valid intervals
var UpdateIntervals = []int{1, 2, 3, 4, 5, 10, 15, 20, 30, 60}

// AggregateSampleDataFrequency is a slice of valid frequencies
var AggregateSampleDataFrequency = []string{"day", "hour"}

// AggregateSampleGroupBy is a slice of valid groupBy parameters
var AggregateSampleGroupBy = []string{"location", "step"}

// Monitoring holds monitoring config
type Monitoring struct {
	neustar *Neustar
}

// NewMonitor creates a new Monitoring object
func NewMonitor(neustar *Neustar) *Monitoring {
	return &Monitoring{
		neustar: neustar,
	}
}

// Create creates a new monitor and returns the monitor id of the newly
// created monitor. Name, interval, testScript and locations are required.
// Use the Get Monitoring Locations api to retrieve a list of monitoring locations.
func (m *Monitoring) Create() {}

// List retrieves a list of all monitors associated with your account,
//along with information about each. The monitor id that is returned
// is used to make other api calls.
func (m *Monitoring) List() ([]Monitor, int, error) {
	var response *http.Response
	var data map[string]map[string][]Monitor
	response, err := http.Get(fmt.Sprintf(
		"%s%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// Get retrieves information for a specific monitor associated with your
// account. The monitor id that is returned is used to make other api calls.
func (m *Monitoring) Get(id string) ([]Monitor, int, error) {
	var response *http.Response
	var data map[string]map[string][]Monitor
	response, err := http.Get(fmt.Sprintf(
		"%s%s/%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, id, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// Update changes some or all of the parameters of an existing monitor.
// Requires the monitor ID retrieved from the List Monitors api.
func (m *Monitoring) Update() {}

// Delete deletes the given monitor, stopping it from monitoring and removing
// all its monitoring data.
func (m *Monitoring) Delete(id string) (int, error) {
	var response *http.Response
	response, err := http.Get(fmt.Sprintf(
		"%s%s/%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, id, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return response.StatusCode, err
	}
	if response.StatusCode != 200 {
		return response.StatusCode, nil
	}
	return response.StatusCode, nil
}

// Samples returns all samples associated to this monitor for a given time period.
// This data is returned at a high level, which timing for the overall sample. To
// get the details for the specific sample, call the get raw sample data api. At a
// maximum, this api will return 2000 samples. If there are more than 2000 results
// returned, the 'more' field will be set to true and you can make another api call
// specifying an offset which would be equal to the number of results returned in the
// first api call plus the offset of that call.
func (m *Monitoring) Samples() ([]string, int, error) {
	var response *http.Response
	var data map[string]map[string][]string
	response, err := http.Get(fmt.Sprintf(
		"%s%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// RawSampleData retrieves the raw, HTTP Archive (HAR) data for a particular sample
func (m *Monitoring) RawSampleData(monitorID, sampleID string) {}

// AggregateSampleData retrieves the aggregated sample information for a given period
// of time. You can choose to aggregate the data for each hour or each day. This is
// more effecient than getting all the individual samples for a period of time and
// performing the aggregation yourself.
func (m *Monitoring) AggregateSampleData(monitorID string, asp *AggregateSampleParameters) ([]AggregateSampleDataResponse, int, error) {
	var response *http.Response
	v, err := query.Values(asp)
	if err != nil {
		return nil, 0, err
	}
	var data map[string]map[string][]AggregateSampleDataResponse
	response, err = http.Get(fmt.Sprintf(
		"%s%s/%s%s?%s&apikey=%s&sig=%s",
		BaseURL, MonitorURI, monitorID, AggregateURI, v.Encode(), m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// Summary provides the monitor summary api returns all of the data that is found when looking at your
// list of monitors in the web portal. This includes things such as the average load
// time, sample count and uptime for the day, week, month or year, the last time an
// error occurred, and the last error message.
func (m *Monitoring) Summary(monitorID string) ([]SummaryDataResponse, int, error) {
	var response *http.Response
	var data map[string]map[string][]SummaryDataResponse
	endpoint := fmt.Sprintf("%s%s/%s%s?apikey=%s&sig=%s", BaseURL, MonitorURI, monitorID, SummaryURI, m.neustar.Key, m.neustar.DigitalSignature())
	fmt.Println(endpoint)
	response, err := http.Get(fmt.Sprintf(
		"%s%s/%s%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, monitorID, SummaryURI, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// Locations gets a list of all monitoring locations available
func (m *Monitoring) Locations() ([]string, int, error) {
	var response *http.Response
	var data map[string]map[string][]string
	response, err := http.Get(fmt.Sprintf(
		"%s%s%s?apikey=%s&sig=%s",
		BaseURL, MonitorURI, LocationsURI, m.neustar.Key, m.neustar.DigitalSignature()))
	if err != nil {
		return nil, response.StatusCode, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return nil, response.StatusCode, err
	}
	return data["data"]["items"], response.StatusCode, nil
}

// ValidMonitorType validates the given monitor type is valid
func ValidMonitorType(monitorType string) bool {
	for _, i := range MonitorTypes {
		if i == monitorType {
			return true
		}
	}
	return false
}

// ValidBrowserType validates the given browser type is valid
func ValidBrowserType(browserType string) bool {
	for _, i := range BrowserTypes {
		if i == browserType {
			return true
		}
	}
	return false
}

// ValidUpdateInterval validates the given interval is valid
func ValidUpdateInterval(interval int) bool {
	for _, i := range UpdateIntervals {
		if i == interval {
			return true
		}
	}
	return false
}

// ValidAggregateSampleDataFrequency validates the given frequency is valid
func ValidAggregateSampleDataFrequency(frequency string) bool {
	for _, i := range AggregateSampleDataFrequency {
		if i == frequency {
			return true
		}
	}
	return false
}

// ValidAggregateSampleGroupBy validates the given groupBy is valid
func ValidAggregateSampleGroupBy(groupBy string) bool {
	for _, i := range AggregateSampleGroupBy {
		if i == groupBy {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2018 Dell Inc., or its subsidiaries. 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
 */

package v1beta1

import (
	"fmt"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	// DefaultZkContainerRepository is the default docker repo for the zookeeper
	// container
	DefaultZkContainerRepository = "pravega/zookeeper"

	// DefaultZkContainerVersion is the default tag used for for the zookeeper
	// container
	DefaultZkContainerVersion = "latest"

	// DefaultZkContainerPolicy is the default container pull policy used
	DefaultZkContainerPolicy = "Always"

	// DefaultTerminationGracePeriod is the default time given before the
	// container is stopped. This gives clients time to disconnect from a
	// specific node gracefully.
	DefaultTerminationGracePeriod = 30
)

// ZookeeperClusterSpec defines the desired state of ZookeeperCluster
type ZookeeperClusterSpec struct {
	// Image is the  container image. default is zookeeper:latest
	Image ContainerImage `json:"image,omitempty"`

	// Labels specifies the labels to attach to pods the operator creates for
	// the zookeeper cluster.
	Labels map[string]string `json:"labels,omitempty"`

	// Replicas is the expected size of the zookeeper cluster.
	// The pravega-operator will eventually make the size of the running cluster
	// equal to the expected size.
	//
	// The valid range of size is from 1 to 7.
	Replicas int32 `json:"replicas"`

	Ports []v1.ContainerPort `json:"ports,omitempty"`

	// Pod defines the policy to create pod for the zookeeper cluster.
	//
	// Updating the Pod does not take effect on any existing pods.
	Pod PodPolicy `json:"pod,omitempty"`

	// Persistence is the configuration for zookeeper persistent layer.
	// PersistentVolumeClaimSpec and VolumeReclaimPolicy can be specified in here.
	Persistence *Persistence `json:"persistence,omitempty"`

	// Conf is the zookeeper configuration, which will be used to generate the
	// static zookeeper configuration. If no configuration is provided required
	// default values will be provided, and optional values will be excluded.
	Conf ZookeeperConfig `json:"config,omitempty"`

	// Domain Name to be used for DNS
	DomainName string `json:"domainName,omitempty"`
}

func (s *ZookeeperClusterSpec) withDefaults(z *ZookeeperCluster) (changed bool) {
	changed = s.Image.withDefaults()
	if s.Conf.withDefaults() {
		changed = true
	}
	if s.Replicas == 0 {
		s.Replicas = 3
		changed = true
	}
	if s.Ports == nil {
		s.Ports = []v1.ContainerPort{
			{
				Name:          "client",
				ContainerPort: 2181,
			},
			{
				Name:          "quorum",
				ContainerPort: 2888,
			},
			{
				Name:          "leader-election",
				ContainerPort: 3888,
			},
		}
		changed = true
	}
	if z.Spec.Labels == nil {
		z.Spec.Labels = map[string]string{}
		changed = true
	}
	if _, ok := z.Spec.Labels["app"]; !ok {
		z.Spec.Labels["app"] = z.GetName()
		changed = true
	}
	if _, ok := z.Spec.Labels["release"]; !ok {
		z.Spec.Labels["release"] = z.GetName()
		changed = true
	}
	if s.Pod.withDefaults(z) {
		changed = true
	}
	if s.Persistence == nil {
		s.Persistence = &Persistence{}
		changed = true
	}
	if s.Persistence.withDefaults() {
		changed = true
	}
	return changed
}

// ZookeeperClusterStatus defines the observed state of ZookeeperCluster
type ZookeeperClusterStatus struct {
	// Members is the zookeeper members in the cluster
	Members MembersStatus `json:"members"`

	// Replicas is the number of number of desired replicas in the cluster
	Replicas int32 `json:"replicas"`

	// ReadyReplicas is the number of number of ready replicas in the cluster
	ReadyReplicas int32 `json:"readyReplicas"`

	// InternalClientEndpoint is the internal client IP and port
	InternalClientEndpoint string `json:"internalClientEndpoint"`

	// ExternalClientEndpoint is the internal client IP and port
	ExternalClientEndpoint string `json:"externalClientEndpoint"`

	MetaRootCreated bool `json:"metaRootCreated"`
}

// MembersStatus is the status of the members of the cluster with both
// ready and unready node membership lists
type MembersStatus struct {
	Ready   []string `json:"ready"`
	Unready []string `json:"unready"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// ZookeeperCluster is the Schema for the zookeeperclusters API
// +k8s:openapi-gen=true
type ZookeeperCluster struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   ZookeeperClusterSpec   `json:"spec,omitempty"`
	Status ZookeeperClusterStatus `json:"status,omitempty"`
}

// WithDefaults set default values when not defined in the spec.
func (z *ZookeeperCluster) WithDefaults() bool {
	return z.Spec.withDefaults(z)
}

// ConfigMapName returns the name of the cluster config-map
func (z *ZookeeperCluster) ConfigMapName() string {
	return fmt.Sprintf("%s-configmap", z.GetName())
}

// ZookeeperPorts returns a struct of ports
func (z *ZookeeperCluster) ZookeeperPorts() Ports {
	ports := Ports{}
	for _, p := range z.Spec.Ports {
		if p.Name == "client" {
			ports.Client = p.ContainerPort
		} else if p.Name == "quorum" {
			ports.Quorum = p.ContainerPort
		} else if p.Name == "leader-election" {
			ports.Leader = p.ContainerPort
		}
	}
	return ports
}

// GetClientServiceName returns the name of the client service for the cluster
func (z *ZookeeperCluster) GetClientServiceName() string {
	return fmt.Sprintf("%s-client", z.GetName())
}

// Ports groups the ports for a zookeeper cluster node for easy access
type Ports struct {
	Client int32
	Quorum int32
	Leader int32
}

// ContainerImage defines the fields needed for a Docker repository image. The
// format here matches the predominant format used in Helm charts.
type ContainerImage struct {
	Repository string        `json:"repository"`
	Tag        string        `json:"tag"`
	PullPolicy v1.PullPolicy `json:"pullPolicy"`
}

func (c *ContainerImage) withDefaults() (changed bool) {
	if c.Repository == "" {
		changed = true
		c.Repository = DefaultZkContainerRepository
	}
	if c.Tag == "" {
		changed = true
		c.Tag = DefaultZkContainerVersion
	}
	if c.PullPolicy == "" {
		changed = true
		c.PullPolicy = DefaultZkContainerPolicy
	}
	return changed
}

// ToString formats a container image struct as a docker compatible repository
// string.
func (c *ContainerImage) ToString() string {
	return fmt.Sprintf("%s:%s", c.Repository, c.Tag)
}

// PodPolicy defines the common pod configuration for Pods, including when used
// in deployments, stateful-sets, etc.
type PodPolicy struct {
	// Labels specifies the labels to attach to pods the operator creates for
	// the zookeeper cluster.
	Labels map[string]string `json:"labels,omitempty"`

	// NodeSelector specifies a map of key-value pairs. For the pod to be
	// eligible to run on a node, the node must have each of the indicated
	// key-value pairs as labels.
	NodeSelector map[string]string `json:"nodeSelector,omitempty"`

	// The scheduling constraints on pods.
	Affinity *v1.Affinity `json:"affinity,omitempty"`

	// Resources is the resource requirements for the container.
	// This field cannot be updated once the cluster is created.
	Resources v1.ResourceRequirements `json:"resources,omitempty"`

	// Tolerations specifies the pod's tolerations.
	Tolerations []v1.Toleration `json:"tolerations,omitempty"`

	// List of environment variables to set in the container.
	// This field cannot be updated.
	Env []v1.EnvVar `json:"env,omitempty"`

	// Annotations specifies the annotations to attach to pods the operator
	// creates.
	Annotations map[string]string `json:"annotations,omitempty"`

	// SecurityContext specifies the security context for the entire pod
	// More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context
	SecurityContext *v1.PodSecurityContext `json:"securityContext,omitempty"`

	// TerminationGracePeriodSeconds is the amount of time that kubernetes will
	// give for a pod instance to shutdown normally.
	// The default value is 1800.
	TerminationGracePeriodSeconds int64 `json:"terminationGracePeriodSeconds"`
}

func (p *PodPolicy) withDefaults(z *ZookeeperCluster) (changed bool) {
	if p.Labels == nil {
		p.Labels = map[string]string{}
		changed = true
	}
	if p.TerminationGracePeriodSeconds == 0 {
		p.TerminationGracePeriodSeconds = DefaultTerminationGracePeriod
		changed = true
	}
	if z.Spec.Pod.Labels == nil {
		p.Labels = map[string]string{}
		changed = true
	}
	if _, ok := p.Labels["app"]; !ok {
		p.Labels["app"] = z.GetName()
		changed = true
	}
	if _, ok := p.Labels["release"]; !ok {
		p.Labels["release"] = z.GetName()
		changed = true
	}
	if p.Affinity == nil {
		p.Affinity = &v1.Affinity{
			PodAntiAffinity: &v1.PodAntiAffinity{
				PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
					{
						Weight: 20,
						PodAffinityTerm: v1.PodAffinityTerm{
							TopologyKey: "kubernetes.io/hostname",
							LabelSelector: &metav1.LabelSelector{
								MatchExpressions: []metav1.LabelSelectorRequirement{
									{
										Key:      "app",
										Operator: metav1.LabelSelectorOpIn,
										Values:   []string{z.GetName()},
									},
								},
							},
						},
					},
				},
			},
		}
		changed = true
	}
	return changed
}

// ZookeeperConfig is the current configuration of each Zookeeper node, which
// sets these values in the config-map
type ZookeeperConfig struct {
	// InitLimit is the amount of time, in ticks, to allow followers to connect
	// and sync to a leader.
	//
	// Default value is 10.
	InitLimit int `json:"initLimit"`

	// TickTime is the length of a single tick, which is the basic time unit used
	// by Zookeeper, as measured in milliseconds
	//
	// The default value is 2000.
	TickTime int `json:"tickTime"`

	// SyncLimit is the amount of time, in ticks, to allow followers to sync with
	// Zookeeper.
	//
	// The default value is 2.
	SyncLimit int `json:"syncLimit"`
}

func (c *ZookeeperConfig) withDefaults() (changed bool) {
	if c.InitLimit == 0 {
		changed = true
		c.InitLimit = 10
	}
	if c.TickTime == 0 {
		changed = true
		c.TickTime = 2000
	}
	if c.SyncLimit == 0 {
		changed = true
		c.SyncLimit = 2
	}
	return changed
}

type Persistence struct {
	// VolumeReclaimPolicy is a zookeeper operator configuration. If it's set to Delete,
	// the corresponding PVCs will be deleted by the operator when zookeeper cluster is deleted.
	// The default value is Retain.
	VolumeReclaimPolicy VolumeReclaimPolicy `json:"reclaimPolicy,omitempty"`
	// PersistentVolumeClaimSpec is the spec to describe PVC for the container
	// This field is optional. If no PVC spec, stateful containers will use
	// emptyDir as volume.
	PersistentVolumeClaimSpec v1.PersistentVolumeClaimSpec `json:"spec,omitempty"`
}

func (p *Persistence) withDefaults() (changed bool) {
	if !p.VolumeReclaimPolicy.isValid() {
		changed = true
		p.VolumeReclaimPolicy = VolumeReclaimPolicyRetain
	}

	p.PersistentVolumeClaimSpec.AccessModes = []v1.PersistentVolumeAccessMode{
		v1.ReadWriteOnce,
	}

	if len(p.PersistentVolumeClaimSpec.Resources.Requests) == 0 {
		p.PersistentVolumeClaimSpec.Resources.Requests = v1.ResourceList{
			v1.ResourceStorage: resource.MustParse("20Gi"),
		}
		changed = true
	}
	return changed
}

func (v VolumeReclaimPolicy) isValid() bool {
	if v != VolumeReclaimPolicyDelete && v != VolumeReclaimPolicyRetain {
		return false
	}
	return true
}

type VolumeReclaimPolicy string

const (
	VolumeReclaimPolicyRetain VolumeReclaimPolicy = "Retain"
	VolumeReclaimPolicyDelete VolumeReclaimPolicy = "Delete"
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// ZookeeperClusterList contains a list of ZookeeperCluster
type ZookeeperClusterList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []ZookeeperCluster `json:"items"`
}

func init() {
	SchemeBuilder.Register(&ZookeeperCluster{}, &ZookeeperClusterList{})
}
<commit_msg>Made changes to make cachevolume size configurable (#179)<commit_after>/**
 * Copyright (c) 2018 Dell Inc., or its subsidiaries. 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
 */

package v1beta1

import (
	"fmt"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	// DefaultZkContainerRepository is the default docker repo for the zookeeper
	// container
	DefaultZkContainerRepository = "pravega/zookeeper"

	// DefaultZkContainerVersion is the default tag used for for the zookeeper
	// container
	DefaultZkContainerVersion = "latest"

	// DefaultZkContainerPolicy is the default container pull policy used
	DefaultZkContainerPolicy = "Always"

	// DefaultTerminationGracePeriod is the default time given before the
	// container is stopped. This gives clients time to disconnect from a
	// specific node gracefully.
	DefaultTerminationGracePeriod = 30

	// DefaultZookeeperCacheVolumeSize is the default volume size for the
	// Zookeeper cache volume
	DefaultZookeeperCacheVolumeSize = "20Gi"
)

// ZookeeperClusterSpec defines the desired state of ZookeeperCluster
type ZookeeperClusterSpec struct {
	// Image is the  container image. default is zookeeper:latest
	Image ContainerImage `json:"image,omitempty"`

	// Labels specifies the labels to attach to pods the operator creates for
	// the zookeeper cluster.
	Labels map[string]string `json:"labels,omitempty"`

	// Replicas is the expected size of the zookeeper cluster.
	// The pravega-operator will eventually make the size of the running cluster
	// equal to the expected size.
	//
	// The valid range of size is from 1 to 7.
	Replicas int32 `json:"replicas"`

	Ports []v1.ContainerPort `json:"ports,omitempty"`

	// Pod defines the policy to create pod for the zookeeper cluster.
	//
	// Updating the Pod does not take effect on any existing pods.
	Pod PodPolicy `json:"pod,omitempty"`

	// Persistence is the configuration for zookeeper persistent layer.
	// PersistentVolumeClaimSpec and VolumeReclaimPolicy can be specified in here.
	Persistence *Persistence `json:"persistence,omitempty"`

	// Conf is the zookeeper configuration, which will be used to generate the
	// static zookeeper configuration. If no configuration is provided required
	// default values will be provided, and optional values will be excluded.
	Conf ZookeeperConfig `json:"config,omitempty"`

	// Domain Name to be used for DNS
	DomainName string `json:"domainName,omitempty"`
}

func (s *ZookeeperClusterSpec) withDefaults(z *ZookeeperCluster) (changed bool) {
	changed = s.Image.withDefaults()
	if s.Conf.withDefaults() {
		changed = true
	}
	if s.Replicas == 0 {
		s.Replicas = 3
		changed = true
	}
	if s.Ports == nil {
		s.Ports = []v1.ContainerPort{
			{
				Name:          "client",
				ContainerPort: 2181,
			},
			{
				Name:          "quorum",
				ContainerPort: 2888,
			},
			{
				Name:          "leader-election",
				ContainerPort: 3888,
			},
		}
		changed = true
	}
	if z.Spec.Labels == nil {
		z.Spec.Labels = map[string]string{}
		changed = true
	}
	if _, ok := z.Spec.Labels["app"]; !ok {
		z.Spec.Labels["app"] = z.GetName()
		changed = true
	}
	if _, ok := z.Spec.Labels["release"]; !ok {
		z.Spec.Labels["release"] = z.GetName()
		changed = true
	}
	if s.Pod.withDefaults(z) {
		changed = true
	}
	if s.Persistence == nil {
		s.Persistence = &Persistence{}
		changed = true
	}
	if s.Persistence.withDefaults() {
		changed = true
	}
	return changed
}

// ZookeeperClusterStatus defines the observed state of ZookeeperCluster
type ZookeeperClusterStatus struct {
	// Members is the zookeeper members in the cluster
	Members MembersStatus `json:"members"`

	// Replicas is the number of number of desired replicas in the cluster
	Replicas int32 `json:"replicas"`

	// ReadyReplicas is the number of number of ready replicas in the cluster
	ReadyReplicas int32 `json:"readyReplicas"`

	// InternalClientEndpoint is the internal client IP and port
	InternalClientEndpoint string `json:"internalClientEndpoint"`

	// ExternalClientEndpoint is the internal client IP and port
	ExternalClientEndpoint string `json:"externalClientEndpoint"`

	MetaRootCreated bool `json:"metaRootCreated"`
}

// MembersStatus is the status of the members of the cluster with both
// ready and unready node membership lists
type MembersStatus struct {
	Ready   []string `json:"ready"`
	Unready []string `json:"unready"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// ZookeeperCluster is the Schema for the zookeeperclusters API
// +k8s:openapi-gen=true
type ZookeeperCluster struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   ZookeeperClusterSpec   `json:"spec,omitempty"`
	Status ZookeeperClusterStatus `json:"status,omitempty"`
}

// WithDefaults set default values when not defined in the spec.
func (z *ZookeeperCluster) WithDefaults() bool {
	return z.Spec.withDefaults(z)
}

// ConfigMapName returns the name of the cluster config-map
func (z *ZookeeperCluster) ConfigMapName() string {
	return fmt.Sprintf("%s-configmap", z.GetName())
}

// ZookeeperPorts returns a struct of ports
func (z *ZookeeperCluster) ZookeeperPorts() Ports {
	ports := Ports{}
	for _, p := range z.Spec.Ports {
		if p.Name == "client" {
			ports.Client = p.ContainerPort
		} else if p.Name == "quorum" {
			ports.Quorum = p.ContainerPort
		} else if p.Name == "leader-election" {
			ports.Leader = p.ContainerPort
		}
	}
	return ports
}

// GetClientServiceName returns the name of the client service for the cluster
func (z *ZookeeperCluster) GetClientServiceName() string {
	return fmt.Sprintf("%s-client", z.GetName())
}

// Ports groups the ports for a zookeeper cluster node for easy access
type Ports struct {
	Client int32
	Quorum int32
	Leader int32
}

// ContainerImage defines the fields needed for a Docker repository image. The
// format here matches the predominant format used in Helm charts.
type ContainerImage struct {
	Repository string        `json:"repository"`
	Tag        string        `json:"tag"`
	PullPolicy v1.PullPolicy `json:"pullPolicy"`
}

func (c *ContainerImage) withDefaults() (changed bool) {
	if c.Repository == "" {
		changed = true
		c.Repository = DefaultZkContainerRepository
	}
	if c.Tag == "" {
		changed = true
		c.Tag = DefaultZkContainerVersion
	}
	if c.PullPolicy == "" {
		changed = true
		c.PullPolicy = DefaultZkContainerPolicy
	}
	return changed
}

// ToString formats a container image struct as a docker compatible repository
// string.
func (c *ContainerImage) ToString() string {
	return fmt.Sprintf("%s:%s", c.Repository, c.Tag)
}

// PodPolicy defines the common pod configuration for Pods, including when used
// in deployments, stateful-sets, etc.
type PodPolicy struct {
	// Labels specifies the labels to attach to pods the operator creates for
	// the zookeeper cluster.
	Labels map[string]string `json:"labels,omitempty"`

	// NodeSelector specifies a map of key-value pairs. For the pod to be
	// eligible to run on a node, the node must have each of the indicated
	// key-value pairs as labels.
	NodeSelector map[string]string `json:"nodeSelector,omitempty"`

	// The scheduling constraints on pods.
	Affinity *v1.Affinity `json:"affinity,omitempty"`

	// Resources is the resource requirements for the container.
	// This field cannot be updated once the cluster is created.
	Resources v1.ResourceRequirements `json:"resources,omitempty"`

	// Tolerations specifies the pod's tolerations.
	Tolerations []v1.Toleration `json:"tolerations,omitempty"`

	// List of environment variables to set in the container.
	// This field cannot be updated.
	Env []v1.EnvVar `json:"env,omitempty"`

	// Annotations specifies the annotations to attach to pods the operator
	// creates.
	Annotations map[string]string `json:"annotations,omitempty"`

	// SecurityContext specifies the security context for the entire pod
	// More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context
	SecurityContext *v1.PodSecurityContext `json:"securityContext,omitempty"`

	// TerminationGracePeriodSeconds is the amount of time that kubernetes will
	// give for a pod instance to shutdown normally.
	// The default value is 1800.
	TerminationGracePeriodSeconds int64 `json:"terminationGracePeriodSeconds"`
}

func (p *PodPolicy) withDefaults(z *ZookeeperCluster) (changed bool) {
	if p.Labels == nil {
		p.Labels = map[string]string{}
		changed = true
	}
	if p.TerminationGracePeriodSeconds == 0 {
		p.TerminationGracePeriodSeconds = DefaultTerminationGracePeriod
		changed = true
	}
	if z.Spec.Pod.Labels == nil {
		p.Labels = map[string]string{}
		changed = true
	}
	if _, ok := p.Labels["app"]; !ok {
		p.Labels["app"] = z.GetName()
		changed = true
	}
	if _, ok := p.Labels["release"]; !ok {
		p.Labels["release"] = z.GetName()
		changed = true
	}
	if p.Affinity == nil {
		p.Affinity = &v1.Affinity{
			PodAntiAffinity: &v1.PodAntiAffinity{
				PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
					{
						Weight: 20,
						PodAffinityTerm: v1.PodAffinityTerm{
							TopologyKey: "kubernetes.io/hostname",
							LabelSelector: &metav1.LabelSelector{
								MatchExpressions: []metav1.LabelSelectorRequirement{
									{
										Key:      "app",
										Operator: metav1.LabelSelectorOpIn,
										Values:   []string{z.GetName()},
									},
								},
							},
						},
					},
				},
			},
		}
		changed = true
	}
	return changed
}

// ZookeeperConfig is the current configuration of each Zookeeper node, which
// sets these values in the config-map
type ZookeeperConfig struct {
	// InitLimit is the amount of time, in ticks, to allow followers to connect
	// and sync to a leader.
	//
	// Default value is 10.
	InitLimit int `json:"initLimit"`

	// TickTime is the length of a single tick, which is the basic time unit used
	// by Zookeeper, as measured in milliseconds
	//
	// The default value is 2000.
	TickTime int `json:"tickTime"`

	// SyncLimit is the amount of time, in ticks, to allow followers to sync with
	// Zookeeper.
	//
	// The default value is 2.
	SyncLimit int `json:"syncLimit"`
}

func (c *ZookeeperConfig) withDefaults() (changed bool) {
	if c.InitLimit == 0 {
		changed = true
		c.InitLimit = 10
	}
	if c.TickTime == 0 {
		changed = true
		c.TickTime = 2000
	}
	if c.SyncLimit == 0 {
		changed = true
		c.SyncLimit = 2
	}
	return changed
}

type Persistence struct {
	// VolumeReclaimPolicy is a zookeeper operator configuration. If it's set to Delete,
	// the corresponding PVCs will be deleted by the operator when zookeeper cluster is deleted.
	// The default value is Retain.
	VolumeReclaimPolicy VolumeReclaimPolicy `json:"reclaimPolicy,omitempty"`
	// PersistentVolumeClaimSpec is the spec to describe PVC for the container
	// This field is optional. If no PVC spec, stateful containers will use
	// emptyDir as volume.
	PersistentVolumeClaimSpec v1.PersistentVolumeClaimSpec `json:"spec,omitempty"`
}

func (p *Persistence) withDefaults() (changed bool) {
	if !p.VolumeReclaimPolicy.isValid() {
		changed = true
		p.VolumeReclaimPolicy = VolumeReclaimPolicyRetain
	}

	p.PersistentVolumeClaimSpec.AccessModes = []v1.PersistentVolumeAccessMode{
		v1.ReadWriteOnce,
	}

	storage, _ := p.PersistentVolumeClaimSpec.Resources.Requests["storage"]
	if storage.IsZero() {
		p.PersistentVolumeClaimSpec.Resources.Requests = v1.ResourceList{
			v1.ResourceStorage: resource.MustParse(DefaultZookeeperCacheVolumeSize),
		}
		changed = true
	}
	return changed
}

func (v VolumeReclaimPolicy) isValid() bool {
	if v != VolumeReclaimPolicyDelete && v != VolumeReclaimPolicyRetain {
		return false
	}
	return true
}

type VolumeReclaimPolicy string

const (
	VolumeReclaimPolicyRetain VolumeReclaimPolicy = "Retain"
	VolumeReclaimPolicyDelete VolumeReclaimPolicy = "Delete"
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// ZookeeperClusterList contains a list of ZookeeperCluster
type ZookeeperClusterList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []ZookeeperCluster `json:"items"`
}

func init() {
	SchemeBuilder.Register(&ZookeeperCluster{}, &ZookeeperClusterList{})
}
<|endoftext|>


<commit_before>package gnat

import (
	"errors"
	"fmt"
	"net"
	"os/exec"
	"strconv"
	"strings"
	"sync"

	"time"

	"github.com/ccding/go-stun/stun"
)

type networking interface {
	sendMessage(*message, bool, int64) (*expectedResponse, error)
	getMessage() chan (*message)
	messagesFin()
	timersFin()
	getDisconnect() chan (int)
	init(self *NetworkNode)
	createSocket(host string, port string, useStun bool, stunAddr string) (publicHost string, publicPort string, err error)
	listen() error
	disconnect() error
	cancelResponse(*expectedResponse)
	isInitialized() bool
	getNetworkAddr() string
}

type realNetworking struct {
	socket        net.Listener
	sendChan      chan (*message)
	recvChan      chan (*message)
	dcStartChan   chan (int)
	dcEndChan     chan (int)
	dcTimersChan  chan (int)
	dcMessageChan chan (int)
	address       *net.TCPAddr
	connection    *net.TCPConn
	mutex         *sync.Mutex
	connected     bool
	initialized   bool
	responseMap   map[int64]*expectedResponse
	aliveConns    *sync.WaitGroup
	self          *NetworkNode
	msgCounter    int64
	remoteAddress string
}

type expectedResponse struct {
	ch    chan (*message)
	query *message
	node  *NetworkNode
	id    int64
}

func (rn *realNetworking) init(self *NetworkNode) {
	rn.self = self
	rn.mutex = &sync.Mutex{}
	rn.sendChan = make(chan (*message))
	rn.recvChan = make(chan (*message))
	rn.dcStartChan = make(chan (int), 10)
	rn.dcEndChan = make(chan (int))
	rn.dcTimersChan = make(chan (int))
	rn.dcMessageChan = make(chan (int))
	rn.responseMap = make(map[int64]*expectedResponse)
	rn.aliveConns = &sync.WaitGroup{}
	rn.connected = false
	rn.initialized = true
}

func (rn *realNetworking) isInitialized() bool {
	return rn.initialized
}

func (rn *realNetworking) getMessage() chan (*message) {
	return rn.recvChan
}

func (rn *realNetworking) getNetworkAddr() string {
	return rn.remoteAddress
}

func (rn *realNetworking) messagesFin() {
	rn.dcMessageChan <- 1
}

func (rn *realNetworking) getDisconnect() chan (int) {
	return rn.dcStartChan
}

func (rn *realNetworking) timersFin() {
	rn.dcTimersChan <- 1
}

func (rn *realNetworking) createSocket(host string, port string, useStun bool, stunAddr string) (publicHost string, publicPort string, err error) {
	rn.mutex.Lock()
	defer rn.mutex.Unlock()
	if rn.connected {
		return "", "", errors.New("already connected")
	}
	remoteAddress := "[" + host + "]" + ":" + port

	socket, err := net.Listen("tcp", ":"+port)
	if err != nil {
		return "", "", err
	}

	if useStun {
		_, h, err := stun.NewClient().Discover()

		if h == nil || err != nil {
			out, _ := exec.Command("curl", "ipinfo.io/ip").Output()
			host = strings.Split(string(out), "\n")[0]
		} else {
			host = h.IP()
		}
	}

	rn.remoteAddress = remoteAddress
	rn.connected = true
	rn.socket = socket

	return host, port, nil
}

func (rn *realNetworking) sendMessage(msg *message, expectResponse bool, id int64) (*expectedResponse, error) {

	rn.mutex.Lock()

	if id == -1 {
		id = rn.msgCounter
		rn.msgCounter++
	}

	msg.ID = id
	rn.mutex.Unlock()

	conn, err := net.DialTimeout("tcp", "["+msg.Receiver.IP.String()+"]:"+strconv.Itoa(msg.Receiver.Port), time.Second)
	if err != nil {
		return nil, err
	}

	data, err := serializeMessage(msg)
	if err != nil {
		return nil, err
	}

	_, err = conn.Write(data)
	if err != nil {
		return nil, err
	}

	if expectResponse {
		rn.mutex.Lock()
		defer rn.mutex.Unlock()

		expectedResponse := &expectedResponse{
			ch:    make(chan (*message)),
			node:  msg.Receiver,
			query: msg,
			id:    id,
		}

		// TODO we need a way to automatically clean these up as there are
		// cases where they won't be removed manually
		rn.responseMap[id] = expectedResponse
		return expectedResponse, nil
	}

	return nil, nil
}

func (rn *realNetworking) cancelResponse(res *expectedResponse) {
	rn.mutex.Lock()
	defer rn.mutex.Unlock()
	close(rn.responseMap[res.query.ID].ch)
	delete(rn.responseMap, res.query.ID)
}

func (rn *realNetworking) disconnect() error {
	rn.mutex.Lock()
	defer rn.mutex.Unlock()
	if !rn.connected {
		return errors.New("not connected")
	}
	rn.dcStartChan <- 1
	rn.dcStartChan <- 1
	<-rn.dcTimersChan
	<-rn.dcMessageChan
	close(rn.sendChan)
	close(rn.recvChan)
	close(rn.dcTimersChan)
	close(rn.dcMessageChan)
	err := rn.socket.Close()
	rn.connected = false
	rn.initialized = false
	close(rn.dcEndChan)
	return err
}

func (rn *realNetworking) listen() error {
	for {
		conn, err := rn.socket.Accept()

		if err != nil {
			rn.disconnect()
			<-rn.dcEndChan
			return err
		}

		go func(conn net.Conn) {
			for {
				// Wait for messages
				msg, err := deserializeMessage(conn)

				if err != nil {
					if err.Error() == "EOF" {
						// Node went bye bye
						return
					}
					// TODO should we penalize this node somehow? Ban it?
					fmt.Printf("networking: %v\n", err)
					return
				}

				isPing := msg.Type == messageTypePing || msg.Type == messageTypePong
				messageType := "FIND_NODE"
				switch msg.Type {
				case messageTypePing:
					messageType = "PING"
					break
				case messageTypePong:
					messageType = "PONG"
					break
				case messageTypeForwardingRequest:
					messageType = "FORWARDING_REQUEST"
					break
				case messageTypeForwardingAck:
					messageType = "FORWARDING_ACKNOWLEDGMENT"
					break
				case messageTypeFindNode:
					messageType = "FIND_NODE"
					break
				}

				fmt.Printf("networking: %v message received from %v\n", messageType, msg.Sender.IP.String())

				if !areNodesEqual(msg.Receiver, rn.self, isPing) {
					fmt.Printf("networking: receiver doesn't match self. Intended receiver %v, actual %v\n",
						msg.Receiver.IP.String()+":"+strconv.Itoa(msg.Receiver.Port),
						rn.self.IP.String()+":"+strconv.Itoa(rn.self.Port))

					// TODO should we penalize this node somehow? Ban it?
					continue
				}

				if msg.ID < 0 {
					// TODO should we penalize this node somehow? Ban it?
					fmt.Println("networking: invalid message id")
					continue
				}

				rn.mutex.Lock()
				if rn.connected {
					if msg.IsResponse {
						if rn.responseMap[msg.ID] == nil {
							// We were not expecting this response
							fmt.Println("networking: unsolicited reponse message received")
							rn.mutex.Unlock()
							continue
						}

						if !areNodesEqual(rn.responseMap[msg.ID].node, msg.Sender, isPing) {
							// TODO should we penalize this node somehow? Ban it?
							fmt.Println("networking: received response from unexpected node")
							rn.mutex.Unlock()
							continue
						}

						resChan := rn.responseMap[msg.ID].ch
						rn.mutex.Unlock()
						resChan <- msg
						rn.mutex.Lock()
						close(rn.responseMap[msg.ID].ch)
						delete(rn.responseMap, msg.ID)
						rn.mutex.Unlock()

					} else {

						assertion := false
						switch msg.Type {
						case messageTypeFindNode:
							_, assertion = msg.Data.(*queryDataFindNode)
						default:
							assertion = true
						}

						if !assertion {
							fmt.Printf("Received bad message %v from %+v", msg.Type, msg.Sender)
							close(rn.responseMap[msg.ID].ch)
							delete(rn.responseMap, msg.ID)
							rn.mutex.Unlock()
							continue
						}

						rn.recvChan <- msg
						rn.mutex.Unlock()
					}

				} else {
					rn.mutex.Unlock()
				}
			}

		}(conn)
	}
}
<commit_msg>added error log<commit_after>package gnat

import (
	"errors"
	"fmt"
	"net"
	"os/exec"
	"strconv"
	"strings"
	"sync"

	"os"

	"github.com/ccding/go-stun/stun"
)

type networking interface {
	sendMessage(*message, bool, int64) (*expectedResponse, error)
	getMessage() chan (*message)
	messagesFin()
	timersFin()
	getDisconnect() chan (int)
	init(self *NetworkNode)
	createSocket(host string, port string, useStun bool, stunAddr string) (publicHost string, publicPort string, err error)
	listen() error
	disconnect() error
	cancelResponse(*expectedResponse)
	isInitialized() bool
	getNetworkAddr() string
}

type realNetworking struct {
	socket        net.Listener
	sendChan      chan (*message)
	recvChan      chan (*message)
	dcStartChan   chan (int)
	dcEndChan     chan (int)
	dcTimersChan  chan (int)
	dcMessageChan chan (int)
	address       *net.TCPAddr
	connection    *net.TCPConn
	mutex         *sync.Mutex
	connected     bool
	initialized   bool
	responseMap   map[int64]*expectedResponse
	aliveConns    *sync.WaitGroup
	self          *NetworkNode
	msgCounter    int64
	remoteAddress string
}

type expectedResponse struct {
	ch    chan (*message)
	query *message
	node  *NetworkNode
	id    int64
}

func (rn *realNetworking) init(self *NetworkNode) {
	rn.self = self
	rn.mutex = &sync.Mutex{}
	rn.sendChan = make(chan (*message))
	rn.recvChan = make(chan (*message))
	rn.dcStartChan = make(chan (int), 10)
	rn.dcEndChan = make(chan (int))
	rn.dcTimersChan = make(chan (int))
	rn.dcMessageChan = make(chan (int))
	rn.responseMap = make(map[int64]*expectedResponse)
	rn.aliveConns = &sync.WaitGroup{}
	rn.connected = false
	rn.initialized = true
}

func (rn *realNetworking) isInitialized() bool {
	return rn.initialized
}

func (rn *realNetworking) getMessage() chan (*message) {
	return rn.recvChan
}

func (rn *realNetworking) getNetworkAddr() string {
	return rn.remoteAddress
}

func (rn *realNetworking) messagesFin() {
	rn.dcMessageChan <- 1
}

func (rn *realNetworking) getDisconnect() chan (int) {
	return rn.dcStartChan
}

func (rn *realNetworking) timersFin() {
	rn.dcTimersChan <- 1
}

func (rn *realNetworking) createSocket(host string, port string, useStun bool, stunAddr string) (publicHost string, publicPort string, err error) {
	rn.mutex.Lock()
	defer rn.mutex.Unlock()
	if rn.connected {
		return "", "", errors.New("already connected")
	}
	remoteAddress := "[" + host + "]" + ":" + port

	socket, err := net.Listen("tcp", ":"+port)
	if err != nil {
		return "", "", err
	}

	if useStun {
		_, h, err := stun.NewClient().Discover()

		if h == nil || err != nil {
			out, _ := exec.Command("curl", "ipinfo.io/ip").Output()
			host = strings.Split(string(out), "\n")[0]
		} else {
			host = h.IP()
		}
	}

	rn.remoteAddress = remoteAddress
	rn.connected = true
	rn.socket = socket

	return host, port, nil
}

func (rn *realNetworking) sendMessage(msg *message, expectResponse bool, id int64) (*expectedResponse, error) {

	rn.mutex.Lock()

	if id == -1 {
		id = rn.msgCounter
		rn.msgCounter++
	}

	msg.ID = id
	rn.mutex.Unlock()

	conn, err := net.Dial("tcp", "["+msg.Receiver.IP.String()+"]:"+strconv.Itoa(msg.Receiver.Port))
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return nil, err
	}

	data, err := serializeMessage(msg)
	if err != nil {
		return nil, err
	}

	_, err = conn.Write(data)
	if err != nil {
		return nil, err
	}

	if expectResponse {
		rn.mutex.Lock()
		defer rn.mutex.Unlock()

		expectedResponse := &expectedResponse{
			ch:    make(chan (*message)),
			node:  msg.Receiver,
			query: msg,
			id:    id,
		}

		// TODO we need a way to automatically clean these up as there are
		// cases where they won't be removed manually
		rn.responseMap[id] = expectedResponse
		return expectedResponse, nil
	}

	return nil, nil
}

func (rn *realNetworking) cancelResponse(res *expectedResponse) {
	rn.mutex.Lock()
	defer rn.mutex.Unlock()
	close(rn.responseMap[res.query.ID].ch)
	delete(rn.responseMap, res.query.ID)
}

func (rn *realNetworking) disconnect() error {
	rn.mutex.Lock()
	defer rn.mutex.Unlock()
	if !rn.connected {
		return errors.New("not connected")
	}
	rn.dcStartChan <- 1
	rn.dcStartChan <- 1
	<-rn.dcTimersChan
	<-rn.dcMessageChan
	close(rn.sendChan)
	close(rn.recvChan)
	close(rn.dcTimersChan)
	close(rn.dcMessageChan)
	err := rn.socket.Close()
	rn.connected = false
	rn.initialized = false
	close(rn.dcEndChan)
	return err
}

func (rn *realNetworking) listen() error {
	for {
		conn, err := rn.socket.Accept()

		if err != nil {
			rn.disconnect()
			<-rn.dcEndChan
			return err
		}

		go func(conn net.Conn) {
			for {
				// Wait for messages
				msg, err := deserializeMessage(conn)

				if err != nil {
					if err.Error() == "EOF" {
						// Node went bye bye
						return
					}
					// TODO should we penalize this node somehow? Ban it?
					fmt.Printf("networking: %v\n", err)
					return
				}

				isPing := msg.Type == messageTypePing || msg.Type == messageTypePong
				messageType := "FIND_NODE"
				switch msg.Type {
				case messageTypePing:
					messageType = "PING"
					break
				case messageTypePong:
					messageType = "PONG"
					break
				case messageTypeForwardingRequest:
					messageType = "FORWARDING_REQUEST"
					break
				case messageTypeForwardingAck:
					messageType = "FORWARDING_ACKNOWLEDGMENT"
					break
				case messageTypeFindNode:
					messageType = "FIND_NODE"
					break
				}

				fmt.Printf("networking: %v message received from %v\n", messageType, msg.Sender.IP.String())

				if !areNodesEqual(msg.Receiver, rn.self, isPing) {
					fmt.Printf("networking: receiver doesn't match self. Intended receiver %v, actual %v\n",
						msg.Receiver.IP.String()+":"+strconv.Itoa(msg.Receiver.Port),
						rn.self.IP.String()+":"+strconv.Itoa(rn.self.Port))

					// TODO should we penalize this node somehow? Ban it?
					continue
				}

				if msg.ID < 0 {
					// TODO should we penalize this node somehow? Ban it?
					fmt.Println("networking: invalid message id")
					continue
				}

				rn.mutex.Lock()
				if rn.connected {
					if msg.IsResponse {
						if rn.responseMap[msg.ID] == nil {
							// We were not expecting this response
							fmt.Println("networking: unsolicited reponse message received")
							rn.mutex.Unlock()
							continue
						}

						if !areNodesEqual(rn.responseMap[msg.ID].node, msg.Sender, isPing) {
							// TODO should we penalize this node somehow? Ban it?
							fmt.Println("networking: received response from unexpected node")
							rn.mutex.Unlock()
							continue
						}

						resChan := rn.responseMap[msg.ID].ch
						rn.mutex.Unlock()
						resChan <- msg
						rn.mutex.Lock()
						close(rn.responseMap[msg.ID].ch)
						delete(rn.responseMap, msg.ID)
						rn.mutex.Unlock()

					} else {

						assertion := false
						switch msg.Type {
						case messageTypeFindNode:
							_, assertion = msg.Data.(*queryDataFindNode)
						default:
							assertion = true
						}

						if !assertion {
							fmt.Printf("Received bad message %v from %+v", msg.Type, msg.Sender)
							close(rn.responseMap[msg.ID].ch)
							delete(rn.responseMap, msg.ID)
							rn.mutex.Unlock()
							continue
						}

						rn.recvChan <- msg
						rn.mutex.Unlock()
					}

				} else {
					rn.mutex.Unlock()
				}
			}

		}(conn)
	}
}
<|endoftext|>


<commit_before>package postbird

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"reflect"
	"sync"
	"time"

	"github.com/googollee/go-socket.io"
)

// Info struct
// PostBird 에서 사용될 값들
type Info struct {
	BindPort      uint
	BindAddress   string
	RemotePort    uint
	RemoteAddress string
	Mode          uint
	Protocol      uint
}

type Client struct {
	Socket     socketio.Socket
	Connection net.Conn
	ClientID   string
}

type Any interface{}

type CallEvent struct {
	FunctionName string
	Params       []Any
}

type CalledEvent struct {
	FunctionName string
	Params       json.RawMessage
}

const DefaultPort uint = 8787                   // Default Bind Port
const DefaultBindAddress string = "127.0.0.1"   // Default Bind Address
const DefaultRemoteAddress string = "127.0.0.1" // Defualt Server Address
const DefaultProtocol uint = SocketIO

const (
	ServerMode = 0
	ClientMode = 1
)

const (
	TCP      = 0
	SocketIO = 1
)

var info Info
var ServerConnection net.Conn

var isConnected bool
var Clients []Client = make([]Client, 5)

// funcs map
// 원격에서 호출가능한 함수들을 등록해놓은 map
// RegisterFunc 함수로 이 맵에 등록한다
var funcs map[string]interface{} = make(map[string]interface{})

// SetBindAddress func
// StartServer로 ServerMode 로 실행할때 바인드될 아이피 주소. ""로 설정하면 모든 NIC에 바인딩된다.
// 이 함수를 호출하지 않으면 DefaultBindAddress인 127.0.0.1로 바인딩된다.
func SetBindAddress(BindAddress string) {
	info.BindAddress = BindAddress
}

// SetBindPort func
// StartServer로 ServerMode 로 실행할때 바인드될 포트 번호.
// 이 함수를 호출하지 않으면 DefaultPortd인 8787로 바인딩된다.
func SetBindPort(BindPort uint) {
	info.BindPort = BindPort
}

// SetRemoteAddress func
//
func SetRemoteAddress(ServerAddress string) {
	info.RemoteAddress = ServerAddress
}

func SetRemotePort(ServerPort uint) {
	info.RemotePort = ServerPort
}

func SetProtocol(Protocol uint) {
	info.Protocol = Protocol
}

func init() {

	if info.BindAddress == "" {
		info.BindAddress = DefaultBindAddress
	}

	if info.BindPort == 0 {
		info.BindPort = DefaultPort
	}

	if info.RemoteAddress == "" {
		info.RemoteAddress = DefaultRemoteAddress
	}

	if info.RemotePort == 0 {
		info.RemotePort = DefaultPort
	}

	if info.Protocol == 0 {
		info.Protocol = DefaultProtocol
	}

}

// RegisterFunc func
// CallLocalFunc 함수에 의해 실행될 수 있는, 즉 원격에서 호출가능한 함수를 등록하는 함수
// funcs 맵에 등록되며 이 함수에 등록되지 않은 함수는 원격에서 호출할 수 없다.
func RegisterFunc(FuncName string, Function interface{}) {
	funcs[FuncName] = Function
}

// StartServer func
// 프로그램을 서버역할로 사용하려면 이 함수를 호출해서 tcp 서버를 시작하면 된다.
// 시작되면 Binder 함수를 비동기로 호출하여 비동기로 tcp Listen
// 이 함수가 호출되면 무조건 Mode가 ServerMode 로 바뀐다
func StartServer(Protocol uint) {
	var wg sync.WaitGroup

	info.Mode = ServerMode
	info.Protocol = Protocol

	switch Protocol {
	case TCP:
		wg.Add(1)
		go Binder(&wg, info.BindAddress, info.BindPort)
	case SocketIO:
		wg.Add(1)
		go Listener(&wg, info.BindAddress, info.BindPort)
	default:
		log.Println("Protocol not match. 0 for TCP, 1 for Socket.io.")
	}
	wg.Wait()
}

// Listener func
// ServerMode 일때 tcp대신 socket.io 사용
func Listener(wg *sync.WaitGroup, BindAddr string, Port uint) {
	server, err := socketio.NewServer(nil)
	if err != nil {
		log.Fatal(err)
	}

	server.On("connection", func(so socketio.Socket) {
		Clients = append(Clients, Client{so, nil, so.Id()})

		so.On("call", func(FunctionName string, args ...string) {
			//go CallLocalFunc(FunctionName, args)
		})

		var i int

		so.On("disconnection", func() {
			for i = 0; i < len(Clients); i++ {
				if so.Id() == Clients[i].ClientID {
					break
				}
			}

			if !(i > len(Clients)) {
				copy(Clients[i:], Clients[i+1:])
				Clients[len(Clients)-1] = Client{}
				Clients = Clients[:len(Clients)-1]
			}

		})
	})

	http.Handle("/socket.io/", server)
	http.Handle("/", http.FileServer(http.Dir("./asset")))
	http.ListenAndServe(BindAddr+":"+string(Port), nil)
}

// Binder func
// ServerMode일때 main func
func Binder(wg *sync.WaitGroup, BindAddr string, Port uint) {
	defer wg.Done()
	var WaitHandler sync.WaitGroup

	Addr, err := net.ResolveTCPAddr("tcp", BindAddr+":"+fmt.Sprint(Port))
	if err != nil {
		log.Println(err)
		return
	}

	ln, err := net.ListenTCP("tcp", Addr) // 전달받은 BindAddr:Port 에 TCP로 바인딩
	if err != nil {
		log.Println(err)
		return
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Println(err)
			panic(err)
		}
		defer conn.Close()

		rand.Seed(time.Now().UnixNano())

		ClientId := RandStringRunes(17)
		Clients = append(Clients, Client{nil, conn, ClientId})
		WaitHandler.Add(1)
		go requestHandler(&WaitHandler, conn)
	}

	WaitHandler.Wait()
}

// requestHandler func
// tcp 연결되었을때 request 핸들러
func requestHandler(wg *sync.WaitGroup, c net.Conn) {
	defer wg.Done()
	data := json.NewDecoder(c)

	var FuncWaiter sync.WaitGroup
	var event CalledEvent

	for {
		err := data.Decode(&event)
		if err != nil {
			log.Println("Invalid json format")
			return
		}
		FuncWaiter.Add(1)
		fmt.Println(event.FunctionName)
		go CallLocalFunc(&FuncWaiter, event.FunctionName, event.Params)
	}

	FuncWaiter.Wait()
}

func ConnectToRemote() {
	client, err := net.Dial("tcp", info.RemoteAddress+":"+fmt.Sprint(info.RemotePort))
	if err != nil {
		fmt.Println(err)
		return
	}
	ServerConnection = client
}

// CallLocalFunc func
// RegisterFunc 로 등록된 함수가 원격에서 함수를 호출했을때
// 이함수를 통해 실행된다
func CallLocalFunc(wg *sync.WaitGroup, name string, params ...Any) (result []reflect.Value, err error) {
	defer wg.Done()
	f := reflect.ValueOf(funcs[name])
	if len(params) != f.Type().NumIn() {
		err = errors.New("The number of params is not adapted.")
		return
	}
	in := make([]reflect.Value, len(params))
	for k, param := range params {
		in[k] = reflect.ValueOf(param)
	}
	result = f.Call(in)
	return
}

// CallRemoteFunc func
// 연결된 (서버)의 함수를 호출하고 싶을때 사용하는 함수
// json형식으로 변환해서 tcp로 서버에 전달.
func CallRemoteFunc(FunctionName string, args ...Any) {
	var i int

	if Clients[0].ClientID != "" {
		switch info.Protocol {
		case TCP:
			Event := CallEvent{FunctionName, args}
			/*
				tmpStr := "{\"funcname\":\"" + FunctionName + "\"" + "\"args\":["
				for i = 0; i < len(args)-1; i++ {
					switch args[i].(type) {
					case string:
						tmpStr += "\"" + args[i].(string) + "\","
						break
					default:
						tmpStr += args[i].(string) + ","
					}
				}

				switch args[i].(type) {
				case string:
					tmpStr += "\"" + args[i].(string) + "\""
					break
				default:
					tmpStr += args[i].(string)
				}

				tmpStr += "]}"
			*/
			call, _ := json.Marshal(Event)

			fmt.Println(string(call))

			for i = 0; i < len(Clients); i++ {
				if info.Mode == ServerMode {
					Clients[i].Connection.Write(call)
				} else {
					//encoder := json.NewEncoder(ServerConnection)
					//encoder.Encode(Event)
					ServerConnection.Write(call)
				}
			}
			break
		case SocketIO:
			break
		default:
			log.Fatal("Protocol Type not match")

		}
	}
}

func readFully(conn net.Conn) ([]byte, error) {
	result := bytes.NewBuffer(nil)
	var buf [512]byte
	for {
		n, err := conn.Read(buf[0:])
		result.Write(buf[0:n])
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
	}
	return result.Bytes(), nil
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")

// RandStringRunes func
// 랜덤 문자열 생성 함수
func RandStringRunes(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}
<commit_msg>some changes<commit_after>package postbird

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"reflect"
	"sync"
	"time"

	"github.com/googollee/go-socket.io"
)

// Info struct
// PostBird 에서 사용될 값들
type Info struct {
	BindPort      uint
	BindAddress   string
	RemotePort    uint
	RemoteAddress string
	Mode          uint
	Protocol      uint
}

type Client struct {
	Socket     socketio.Socket
	Connection net.Conn
	ClientID   string
}

type Any interface{}

type CallEvent struct {
	FunctionName string
	Params       []Any
}

type CalledEvent struct {
	FunctionName string
	Params       json.RawMessage
}

const DefaultPort uint = 8787                   // Default Bind Port
const DefaultBindAddress string = "127.0.0.1"   // Default Bind Address
const DefaultRemoteAddress string = "127.0.0.1" // Defualt Server Address
const DefaultProtocol uint = SocketIO

const (
	ServerMode = 0
	ClientMode = 1
)

const (
	TCP      = 0
	SocketIO = 1
)

var info Info
var ServerConnection net.Conn

var isConnected bool
var Clients []Client = make([]Client, 5)

// funcs map
// 원격에서 호출가능한 함수들을 등록해놓은 map
// RegisterFunc 함수로 이 맵에 등록한다
var funcs map[string]interface{} = make(map[string]interface{})

// SetBindAddress func
// StartServer로 ServerMode 로 실행할때 바인드될 아이피 주소. ""로 설정하면 모든 NIC에 바인딩된다.
// 이 함수를 호출하지 않으면 DefaultBindAddress인 127.0.0.1로 바인딩된다.
func SetBindAddress(BindAddress string) {
	info.BindAddress = BindAddress
}

// SetBindPort func
// StartServer로 ServerMode 로 실행할때 바인드될 포트 번호.
// 이 함수를 호출하지 않으면 DefaultPortd인 8787로 바인딩된다.
func SetBindPort(BindPort uint) {
	info.BindPort = BindPort
}

// SetRemoteAddress func
//
func SetRemoteAddress(ServerAddress string) {
	info.RemoteAddress = ServerAddress
}

func SetRemotePort(ServerPort uint) {
	info.RemotePort = ServerPort
}

func SetProtocol(Protocol uint) {
	info.Protocol = Protocol
}

func init() {

	if info.BindAddress == "" {
		info.BindAddress = DefaultBindAddress
	}

	if info.BindPort == 0 {
		info.BindPort = DefaultPort
	}

	if info.RemoteAddress == "" {
		info.RemoteAddress = DefaultRemoteAddress
	}

	if info.RemotePort == 0 {
		info.RemotePort = DefaultPort
	}

	if info.Protocol == 0 {
		info.Protocol = DefaultProtocol
	}

}

// RegisterFunc func
// CallLocalFunc 함수에 의해 실행될 수 있는, 즉 원격에서 호출가능한 함수를 등록하는 함수
// funcs 맵에 등록되며 이 함수에 등록되지 않은 함수는 원격에서 호출할 수 없다.
func RegisterFunc(FuncName string, Function interface{}) {
	funcs[FuncName] = Function
}

// StartServer func
// 프로그램을 서버역할로 사용하려면 이 함수를 호출해서 tcp 서버를 시작하면 된다.
// 시작되면 Binder 함수를 비동기로 호출하여 비동기로 tcp Listen
// 이 함수가 호출되면 무조건 Mode가 ServerMode 로 바뀐다
func StartServer(Protocol uint) {
	var wg sync.WaitGroup

	info.Mode = ServerMode
	info.Protocol = Protocol

	switch Protocol {
	case TCP:
		wg.Add(1)
		go Binder(&wg, info.BindAddress, info.BindPort)
	case SocketIO:
		wg.Add(1)
		go Listener(&wg, info.BindAddress, info.BindPort)
	default:
		log.Println("Protocol not match. 0 for TCP, 1 for Socket.io.")
	}
	wg.Wait()
}

// Listener func
// ServerMode 일때 tcp대신 socket.io 사용
func Listener(wg *sync.WaitGroup, BindAddr string, Port uint) {
	server, err := socketio.NewServer(nil)
	if err != nil {
		log.Fatal(err)
	}

	server.On("connection", func(so socketio.Socket) {
		Clients = append(Clients, Client{so, nil, so.Id()})

		so.On("call", func(FunctionName string, args ...string) {
			//go CallLocalFunc(FunctionName, args)
		})

		var i int

		so.On("disconnection", func() {
			for i = 0; i < len(Clients); i++ {
				if so.Id() == Clients[i].ClientID {
					break
				}
			}

			if !(i > len(Clients)) {
				copy(Clients[i:], Clients[i+1:])
				Clients[len(Clients)-1] = Client{}
				Clients = Clients[:len(Clients)-1]
			}

		})
	})

	http.Handle("/socket.io/", server)
	http.Handle("/", http.FileServer(http.Dir("./asset")))
	http.ListenAndServe(BindAddr+":"+string(Port), nil)
}

// Binder func
// ServerMode일때 main func
func Binder(wg *sync.WaitGroup, BindAddr string, Port uint) {
	defer wg.Done()
	var WaitHandler sync.WaitGroup

	Addr, err := net.ResolveTCPAddr("tcp", BindAddr+":"+fmt.Sprint(Port))
	if err != nil {
		log.Println(err)
		return
	}

	ln, err := net.ListenTCP("tcp", Addr) // 전달받은 BindAddr:Port 에 TCP로 바인딩
	if err != nil {
		log.Println(err)
		return
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Println(err)
			panic(err)
		}
		defer conn.Close()

		rand.Seed(time.Now().UnixNano())

		ClientId := RandStringRunes(17)
		Clients = append(Clients, Client{nil, conn, ClientId})
		WaitHandler.Add(1)
		go requestHandler(&WaitHandler, conn)
	}

	WaitHandler.Wait()
}

// requestHandler func
// tcp 연결되었을때 request 핸들러
func requestHandler(wg *sync.WaitGroup, c net.Conn) {
	defer wg.Done()
	data := json.NewDecoder(c)

	var FuncWaiter sync.WaitGroup
	var event CalledEvent

	for {
		err := data.Decode(&event)
		if err != nil {
			log.Println("Invalid json format")
			return
		}
		FuncWaiter.Add(1)
		fmt.Println(event.FunctionName)
		go CallLocalFunc(&FuncWaiter, event.FunctionName, event.Params)
	}

	FuncWaiter.Wait()
}

func ConnectToRemote() {
	client, err := net.Dial("tcp", info.RemoteAddress+":"+fmt.Sprint(info.RemotePort))
	if err != nil {
		fmt.Println(err)
		return
	}
	ServerConnection = client
}

// CallLocalFunc func
// RegisterFunc 로 등록된 함수가 원격에서 함수를 호출했을때
// 이함수를 통해 실행된다
func CallLocalFunc(wg *sync.WaitGroup, name string, params ...Any) (result []reflect.Value, err error) {
	defer wg.Done()
	f := reflect.ValueOf(funcs[name])
	if len(params) != f.Type().NumIn() {
		err = errors.New("The number of params is not adapted.")
		return
	}
	in := make([]reflect.Value, len(params))
	for k, param := range params {
		in[k] = reflect.ValueOf(param)
	}
	result = f.Call(in)
	return
}

// CallRemoteFunc func
// 연결된 (서버)의 함수를 호출하고 싶을때 사용하는 함수
// json형식으로 변환해서 tcp로 서버에 전달.
func CallRemoteFunc(FunctionName string, args ...Any) {
	var i int

	if Clients[0].ClientID != "" {
		switch info.Protocol {
		case TCP:
			Event := CallEvent{FunctionName, args}
			/*
				tmpStr := "{\"funcname\":\"" + FunctionName + "\"" + "\"args\":["
				for i = 0; i < len(args)-1; i++ {
					switch args[i].(type) {
					case string:
						tmpStr += "\"" + args[i].(string) + "\","
						break
					default:
						tmpStr += args[i].(string) + ","
					}
				}

				switch args[i].(type) {
				case string:
					tmpStr += "\"" + args[i].(string) + "\""
					break
				default:
					tmpStr += args[i].(string)
				}

				tmpStr += "]}"
			*/
			call, _ := json.Marshal(Event)

			fmt.Println(string(call))

			if info.Mode == ServerMode {
				for i = 0; i < len(Clients); i++ {
					encoder := json.NewEncoder(Clients[i].Connection)
					encoder.Encode(Event)
				}
			} else {
				encoder := json.NewEncoder(ServerConnection)
				encoder.Encode(Event)
				//ServerConnection.Write(call)
			}

			break
		case SocketIO:
			break
		default:
			log.Fatal("Protocol Type not match")

		}
	}
}

func readFully(conn net.Conn) ([]byte, error) {
	result := bytes.NewBuffer(nil)
	var buf [512]byte
	for {
		n, err := conn.Read(buf[0:])
		result.Write(buf[0:n])
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
	}
	return result.Bytes(), nil
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")

// RandStringRunes func
// 랜덤 문자열 생성 함수
func RandStringRunes(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}
<|endoftext|>