<commit_before>// Copyright 2015 Igor Dolzhikov. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

package spawn

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"runtime"
	"strconv"

	"github.com/takama/router"
)

// data is a shortcut
type data map[string]interface{}

func isAlphaNumeric(str string) bool {
	isAlphaNum := true
	for _, b := range str {
		if !('0' <= b && b <= '9' || 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' || b == '_' || b == '-' || b == '.') {
			isAlphaNum = false
			break
		}
	}

	return isAlphaNum
}

func decodeRecord(record interface{}, c *router.Control) (*[]byte, bool) {
	body, err := ioutil.ReadAll(c.Request.Body)
	if err != nil {
		c.Code(http.StatusBadRequest).Body(
			data{
				"success": false,
				"error":   http.StatusBadRequest,
				"message": "The body content is absent",
				"info":    err.Error(),
			})
		return &body, false
	}
	if err := json.Unmarshal(body, &record); err != nil {
		c.Code(http.StatusBadRequest).Body(
			data{
				"success": false,
				"error":   http.StatusBadRequest,
				"message": "Could not recognize parameters",
				"info":    err.Error(),
			})
		return &body, false
	}

	return &body, true
}

func decodeRecords(body *[]byte, records interface{}, c *router.Control) bool {
	if err := json.Unmarshal(*body, &records); err != nil {
		c.Code(http.StatusBadRequest).Body(
			data{
				"success": false,
				"error":   http.StatusBadRequest,
				"message": "Could not recognize parameters",
				"info":    err.Error(),
			})
		return false
	}

	return true
}

func decodeNumber(name string, c *router.Control) (uint64, bool) {
	number, err := strconv.ParseUint(c.Get(name), 10, 64)
	if err != nil {
		notRecognizedParameterError(name, err, c)
		return 0, false
	}
	if number == 0 {
		couldNotBeZero(name, c)
		return 0, false
	}

	return number, true
}

func decodeString(name string, c *router.Control) (string, bool) {
	str := c.Get(name)

	if !checkAlphaNumeric(str, c) {
		return "", false
	}
	if str == "" {
		couldNotBeEmpty(str, c)
		return "", false
	}

	return str, true
}

func checkAlphaNumeric(str string, c *router.Control) bool {
	if !isAlphaNumeric(str) {
		err := errors.New(str + " parameter is not alpha-numeric")
		notRecognizedParameterError(str, err, c)
		return false
	}

	return true
}

func couldNotBeZero(param string, c *router.Control) {
	c.Code(http.StatusBadRequest).Body(
		data{
			"success": false,
			"error":   http.StatusBadRequest,
			"message": "The parameter '" + param + "' could not be zero value",
			"info":    "Please apply a non-zero value to the data",
		})
}

func couldNotBeEmpty(param string, c *router.Control) {
	c.Code(http.StatusBadRequest).Body(
		data{
			"success": false,
			"error":   http.StatusBadRequest,
			"message": "The parameter '" + param + "' could not be empty",
			"info":    "Please apply a non-empty value to the data",
		})
}

func notRecognizedParameterError(param string, err error, c *router.Control) {
	c.Code(http.StatusBadRequest).Body(
		data{
			"success": false,
			"error":   http.StatusBadRequest,
			"message": "Could not recognize " + param + " parameter",
			"info":    err.Error(),
		})
}

func recordNotFound(c *router.Control) {
	c.Code(http.StatusNotFound).Body(
		data{
			"success": false,
			"error":   http.StatusNotFound,
			"message": "Record(s) not found",
			"info":    "Please add a record(s) before using",
		})
}

func notFound(c *router.Control) {
	c.Code(http.StatusNotFound).Body(
		data{
			"Message": data{
				"Error":       "Method not found",
				"Information": "Please see list of the methods by using /list",
			},
		})
}

func infoHandler(c *router.Control) {
	host, _ := os.Hostname()
	m := new(runtime.MemStats)
	runtime.ReadMemStats(m)
	c.Code(http.StatusOK).Body(
		data{
			"Spawn Sync Service": data{
				"Host": host,
				"Runtime": data{
					"Compiler": runtime.Version(),
					"CPU":      runtime.NumCPU(),
					"Memory":   fmt.Sprintf("%.2fMB", float64(m.Alloc)/(1<<(10*2))),
					"Threads":  runtime.NumGoroutine(),
				},
				"Release": data{
					"Number": VERSION,
					"Date":   DATE,
				},
			},
		})
}

func logger(c *router.Control) {
	stdlog.Println(c.Request.RemoteAddr, c.Request.Method, c.Request.URL.Path)
}

func baseHandler(handle router.Handle) router.Handle {
	return func(c *router.Control) {
		if c.Get("pretty") != "true" {
			c.CompactJSON(true)
		}
		if origin := c.Request.Header.Get("Origin"); origin != "" {
			c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
			c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		}
		handle(c)
	}
}

func panicHandler(c *router.Control) {
	c.Code(http.StatusInternalServerError).Body(c.Request)
}
<commit_msg>Extend logger for use X-Forwarded-For<commit_after>// Copyright 2015 Igor Dolzhikov. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

package spawn

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"runtime"
	"strconv"

	"github.com/takama/router"
)

// data is a shortcut
type data map[string]interface{}

func isAlphaNumeric(str string) bool {
	isAlphaNum := true
	for _, b := range str {
		if !('0' <= b && b <= '9' || 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' || b == '_' || b == '-' || b == '.') {
			isAlphaNum = false
			break
		}
	}

	return isAlphaNum
}

func decodeRecord(record interface{}, c *router.Control) (*[]byte, bool) {
	body, err := ioutil.ReadAll(c.Request.Body)
	if err != nil {
		c.Code(http.StatusBadRequest).Body(
			data{
				"success": false,
				"error":   http.StatusBadRequest,
				"message": "The body content is absent",
				"info":    err.Error(),
			})
		return &body, false
	}
	if err := json.Unmarshal(body, &record); err != nil {
		c.Code(http.StatusBadRequest).Body(
			data{
				"success": false,
				"error":   http.StatusBadRequest,
				"message": "Could not recognize parameters",
				"info":    err.Error(),
			})
		return &body, false
	}

	return &body, true
}

func decodeRecords(body *[]byte, records interface{}, c *router.Control) bool {
	if err := json.Unmarshal(*body, &records); err != nil {
		c.Code(http.StatusBadRequest).Body(
			data{
				"success": false,
				"error":   http.StatusBadRequest,
				"message": "Could not recognize parameters",
				"info":    err.Error(),
			})
		return false
	}

	return true
}

func decodeNumber(name string, c *router.Control) (uint64, bool) {
	number, err := strconv.ParseUint(c.Get(name), 10, 64)
	if err != nil {
		notRecognizedParameterError(name, err, c)
		return 0, false
	}
	if number == 0 {
		couldNotBeZero(name, c)
		return 0, false
	}

	return number, true
}

func decodeString(name string, c *router.Control) (string, bool) {
	str := c.Get(name)

	if !checkAlphaNumeric(str, c) {
		return "", false
	}
	if str == "" {
		couldNotBeEmpty(str, c)
		return "", false
	}

	return str, true
}

func checkAlphaNumeric(str string, c *router.Control) bool {
	if !isAlphaNumeric(str) {
		err := errors.New(str + " parameter is not alpha-numeric")
		notRecognizedParameterError(str, err, c)
		return false
	}

	return true
}

func couldNotBeZero(param string, c *router.Control) {
	c.Code(http.StatusBadRequest).Body(
		data{
			"success": false,
			"error":   http.StatusBadRequest,
			"message": "The parameter '" + param + "' could not be zero value",
			"info":    "Please apply a non-zero value to the data",
		})
}

func couldNotBeEmpty(param string, c *router.Control) {
	c.Code(http.StatusBadRequest).Body(
		data{
			"success": false,
			"error":   http.StatusBadRequest,
			"message": "The parameter '" + param + "' could not be empty",
			"info":    "Please apply a non-empty value to the data",
		})
}

func notRecognizedParameterError(param string, err error, c *router.Control) {
	c.Code(http.StatusBadRequest).Body(
		data{
			"success": false,
			"error":   http.StatusBadRequest,
			"message": "Could not recognize " + param + " parameter",
			"info":    err.Error(),
		})
}

func recordNotFound(c *router.Control) {
	c.Code(http.StatusNotFound).Body(
		data{
			"success": false,
			"error":   http.StatusNotFound,
			"message": "Record(s) not found",
			"info":    "Please add a record(s) before using",
		})
}

func notFound(c *router.Control) {
	c.Code(http.StatusNotFound).Body(
		data{
			"Message": data{
				"Error":       "Method not found",
				"Information": "Please see list of the methods by using /list",
			},
		})
}

func infoHandler(c *router.Control) {
	host, _ := os.Hostname()
	m := new(runtime.MemStats)
	runtime.ReadMemStats(m)
	c.Code(http.StatusOK).Body(
		data{
			"Spawn Sync Service": data{
				"Host": host,
				"Runtime": data{
					"Compiler": runtime.Version(),
					"CPU":      runtime.NumCPU(),
					"Memory":   fmt.Sprintf("%.2fMB", float64(m.Alloc)/(1<<(10*2))),
					"Threads":  runtime.NumGoroutine(),
				},
				"Release": data{
					"Number": VERSION,
					"Date":   DATE,
				},
			},
		})
}

func logger(c *router.Control) {
	remoteAddr := c.Request.Header.Get("X-Forwarded-For")
	if remoteAddr == "" {
		remoteAddr = c.Request.RemoteAddr
	}
	stdlog.Println(remoteAddr, c.Request.Method, c.Request.URL.Path)
}

func baseHandler(handle router.Handle) router.Handle {
	return func(c *router.Control) {
		if c.Get("pretty") != "true" {
			c.CompactJSON(true)
		}
		if origin := c.Request.Header.Get("Origin"); origin != "" {
			c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
			c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		}
		handle(c)
	}
}

func panicHandler(c *router.Control) {
	c.Code(http.StatusInternalServerError).Body(c.Request)
}
<|endoftext|>


<commit_before>package jiffybox

import (
	"bytes"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	STATUS_START    = "START"
	STATUS_SHUTDOWN = "SHUTDOWN"
	STATUS_PULLPLUG = "PULLPLUG"
	STATUS_FREEZE   = "FREEZE"
	STATUS_THAW     = "THAW"
)

type Disk struct {
	Name       string `json:"name"`       //" Swap",
	Filesystem string `json:"filesystem"` // "swap",
	SizeInMB   int    `json:"sizeInMB"`   // 512,
	Created    int    `json:"created"`    // 1234567890,
	Status     string `json:"status"`     // "READY"
}

type ActiveProfile struct {
	Name         string           `json:"name"`         // "Standard",
	Created      int              `json:"created"`      // 1234567890,
	Runlevel     string           `json:"runlevel"`     // "default",
	Kernel       string           `json:"kernel"`       // "xen-current",
	Rootdisk     string           `json:"rootdisk"`     // "\/dev\/xvda",
	RootdiskMode string           `json:"rootdiskMode"` // "ro",
	Status       string           `json:"status"`       // "READY",
	DisksHash    map[string]*Disk `json:"disks"`
}

func (ap *ActiveProfile) Disks() (disks []*Disk) {
	for _, disk := range ap.DisksHash {
		disks = append(disks, disk)
	}
	return disks

}

type Server struct {
	Id                  int                 `json:"id"`
	Name                string              `json:"name"`
	Ips                 map[string][]string `json:"ips"`
	Status              string              `json:"status"`
	Created             int64               `json:"created"`
	RecoverymodeActive  bool                `json:"recoverymodeActive"`
	ManualBackupRunning bool                `json:"manualBackupRunning"`
	IsBeingCopied       bool                `json:"isBeingCopied"`
	Running             bool                `json:"running"`
	Host                string              `json:"host"`
	Plan                *Plan               `json:"plan"`
	ActiveProfile       *ActiveProfile      `json:"activeProfile"`
	Metadata            map[string]string   `json:"metadata"`
}

func (server *Server) IpsString() string {
	ips := []string{}
	for k, v := range server.Ips {
		ips = append(ips, k+":"+strings.Join(v, ","))
	}
	return strings.Join(ips, ", ")
}

func (server *Server) CreatedAt() time.Time {
	return time.Unix(server.Created, 0)
}

func (server *Server) PublicIp() string {
	if ips := server.Ips["public"]; len(ips) > 0 {
		return ips[0]
	}
	return ""
}

func (server *Server) Frozen() bool {
	return server.Status == "FROZEN" || server.Status == "FREEZING"
}

type Message struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

type JiffyBoxResponse struct {
	Messages []*Message
	Server   *Server `json:"result"`
}

type JiffyBoxesResponse struct {
	Messages   []*Message
	ServersMap map[string]*Server `json:"result"`
}

func (rsp *JiffyBoxesResponse) Servers() []*Server {
	servers := make([]*Server, 0, len(rsp.ServersMap))
	for _, server := range rsp.ServersMap {
		servers = append(servers, server)
	}
	return servers
}

func (rsp *JiffyBoxesResponse) Server() *Server {
	for _, server := range rsp.ServersMap {
		return server
	}
	return nil
}

func (client *Client) JiffyBoxes() (servers []*Server, e error) {
	rsp := &JiffyBoxesResponse{}
	e = client.LoadResource("jiffyBoxes", rsp)
	if e != nil {
		return servers, e
	}
	return rsp.Servers(), nil
}

func (client *Client) JiffyBox(id int) (server *Server, e error) {
	rsp := &JiffyBoxResponse{}
	e = client.LoadResource("jiffyBoxes/"+strconv.Itoa(id), rsp)
	if e != nil {
		return server, e
	}
	return rsp.Server, nil
}

func (client *Client) DeleteJiffyBox(id string) (e error) {
	req, e := http.NewRequest("DELETE", client.BaseUrl()+"/jiffyBoxes/"+id, nil)
	if e != nil {
		return e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return e
	}
	rsp := &Response{}
	e = client.unmarshalResponse(httpResponse, rsp)
	if e != nil {
		return e
	}
	return nil
}

type CreateOptions struct {
	Name         string
	PlanId       int
	BackupId     string
	Distribution string
	Password     string
	UseSshKey    bool
	Metadata     string
}

func (client *Client) CreateJiffyBox(options *CreateOptions) (server *Server, e error) {
	values := url.Values{}
	values.Add("name", options.Name)
	values.Add("planid", strconv.Itoa(options.PlanId))
	if options.BackupId != "" {
		values.Add("backupid", options.BackupId)
	} else if options.Distribution != "" {
		values.Add("distribution", options.Distribution)
	}
	if options.Password != "" {
		values.Add("password", options.Password)
	}
	if options.UseSshKey {
		values.Add("use_sshkey", "true")
	}
	if options.Metadata != "" {
		values.Add("metadata", options.Metadata)
	}
	httpResponse, e := http.PostForm(client.BaseUrl()+"/jiffyBoxes", values)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	return rsp.Server, e
}

// unfreeze server
func (client *Client) ThawServer(id int, planId int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_THAW, planId)
}

func (client *Client) FreezeServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_FREEZE, -1)
}

func (client *Client) PullPlugServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_PULLPLUG, -1)
}

func (client *Client) ShutdownServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_SHUTDOWN, -1)
}

func (client *Client) StartServer(id int, planId int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_START, planId)
}

func (client *Client) ChangeState(id int, state string, planId int) (server *Server, e error) {
	values := url.Values{}
	values.Add("status", state)
	if planId > 0 {
		values.Add("planid", strconv.Itoa(planId))
	}
	buf := bytes.Buffer{}
	buf.WriteString(values.Encode())
	req, e := http.NewRequest("PUT", client.BaseUrl()+"/jiffyBoxes/"+strconv.Itoa(id), &buf)
	if e != nil {
		return nil, e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	if e != nil {
		return rsp.Server, e
	}
	errors := []string{}
	for _, message := range rsp.Messages {
		if message.Type == "error" {
			errors = append(errors, message.Message)
		}
	}
	if len(errors) > 0 {
		return rsp.Server, fmt.Errorf(strings.Join(errors, ", "))
	}
	return rsp.Server, e
}

func (client *Client) CloneServer(id int, opts *CreateOptions) (server *Server, e error) {
	values := url.Values{}
	if opts.Name == "" {
		return nil, fmt.Errorf("name must be provided to clone server")
	}
	values.Add("name", opts.Name)
	values.Add("planid", strconv.Itoa(opts.PlanId))
	if opts.Metadata != "" {
		values.Add("metadata", opts.Metadata)
	}
	httpResponse, e := client.PostForm("jiffyBoxes/"+strconv.Itoa(id), values)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshal(httpResponse.Content, rsp)
	if e != nil {
		return nil, e
	}
	return rsp.Server, nil
}

func (client *Client) ChangeStatus(id string, status string, planId int, metadata string) (server *Server, e error) {
	values := url.Values{}
	values.Add("status", status)
	values.Add("planid", strconv.Itoa(planId))
	if metadata != "" {
		values.Add("metadata", metadata)
	}
	buf := &bytes.Buffer{}
	buf.Write([]byte(values.Encode()))
	req, e := http.NewRequest("PUT", client.BaseUrl()+"/JiffyBox/"+id, buf)
	if e != nil {
		return nil, e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	if e != nil {
		return nil, e
	}
	return rsp.Server, nil
}
<commit_msg>fix serialization of jiffybox messages<commit_after>package jiffybox

import (
	"bytes"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	STATUS_START    = "START"
	STATUS_SHUTDOWN = "SHUTDOWN"
	STATUS_PULLPLUG = "PULLPLUG"
	STATUS_FREEZE   = "FREEZE"
	STATUS_THAW     = "THAW"
)

type Disk struct {
	Name       string `json:"name"`       //" Swap",
	Filesystem string `json:"filesystem"` // "swap",
	SizeInMB   int    `json:"sizeInMB"`   // 512,
	Created    int    `json:"created"`    // 1234567890,
	Status     string `json:"status"`     // "READY"
}

type ActiveProfile struct {
	Name         string           `json:"name"`         // "Standard",
	Created      int              `json:"created"`      // 1234567890,
	Runlevel     string           `json:"runlevel"`     // "default",
	Kernel       string           `json:"kernel"`       // "xen-current",
	Rootdisk     string           `json:"rootdisk"`     // "\/dev\/xvda",
	RootdiskMode string           `json:"rootdiskMode"` // "ro",
	Status       string           `json:"status"`       // "READY",
	DisksHash    map[string]*Disk `json:"disks"`
}

func (ap *ActiveProfile) Disks() (disks []*Disk) {
	for _, disk := range ap.DisksHash {
		disks = append(disks, disk)
	}
	return disks

}

type Server struct {
	Id                  int                 `json:"id"`
	Name                string              `json:"name"`
	Ips                 map[string][]string `json:"ips"`
	Status              string              `json:"status"`
	Created             int64               `json:"created"`
	RecoverymodeActive  bool                `json:"recoverymodeActive"`
	ManualBackupRunning bool                `json:"manualBackupRunning"`
	IsBeingCopied       bool                `json:"isBeingCopied"`
	Running             bool                `json:"running"`
	Host                string              `json:"host"`
	Plan                *Plan               `json:"plan"`
	ActiveProfile       *ActiveProfile      `json:"activeProfile"`
	Metadata            map[string]string   `json:"metadata"`
}

func (server *Server) IpsString() string {
	ips := []string{}
	for k, v := range server.Ips {
		ips = append(ips, k+":"+strings.Join(v, ","))
	}
	return strings.Join(ips, ", ")
}

func (server *Server) CreatedAt() time.Time {
	return time.Unix(server.Created, 0)
}

func (server *Server) PublicIp() string {
	if ips := server.Ips["public"]; len(ips) > 0 {
		return ips[0]
	}
	return ""
}

func (server *Server) Frozen() bool {
	return server.Status == "FROZEN" || server.Status == "FREEZING"
}

type Message struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

type JiffyBoxResponse struct {
	Messages []*Message
	Server   *Server `json:"result"`
}

type JiffyBoxesResponse struct {
	Messages   []*Message `json:"messages:"`
	ServersMap map[string]*Server `json:"result"`
}

func (rsp *JiffyBoxesResponse) Servers() []*Server {
	servers := make([]*Server, 0, len(rsp.ServersMap))
	for _, server := range rsp.ServersMap {
		servers = append(servers, server)
	}
	return servers
}

func (rsp *JiffyBoxesResponse) Server() *Server {
	for _, server := range rsp.ServersMap {
		return server
	}
	return nil
}

func (client *Client) JiffyBoxes() (servers []*Server, e error) {
	rsp := &JiffyBoxesResponse{}
	e = client.LoadResource("jiffyBoxes", rsp)
	if e != nil {
		return servers, e
	}
	return rsp.Servers(), nil
}

func (client *Client) JiffyBox(id int) (server *Server, e error) {
	rsp := &JiffyBoxResponse{}
	e = client.LoadResource("jiffyBoxes/"+strconv.Itoa(id), rsp)
	if e != nil {
		return server, e
	}
	return rsp.Server, nil
}

func (client *Client) DeleteJiffyBox(id string) (e error) {
	req, e := http.NewRequest("DELETE", client.BaseUrl()+"/jiffyBoxes/"+id, nil)
	if e != nil {
		return e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return e
	}
	rsp := &Response{}
	e = client.unmarshalResponse(httpResponse, rsp)
	if e != nil {
		return e
	}
	return nil
}

type CreateOptions struct {
	Name         string
	PlanId       int
	BackupId     string
	Distribution string
	Password     string
	UseSshKey    bool
	Metadata     string
}

func (client *Client) CreateJiffyBox(options *CreateOptions) (server *Server, e error) {
	values := url.Values{}
	values.Add("name", options.Name)
	values.Add("planid", strconv.Itoa(options.PlanId))
	if options.BackupId != "" {
		values.Add("backupid", options.BackupId)
	} else if options.Distribution != "" {
		values.Add("distribution", options.Distribution)
	}
	if options.Password != "" {
		values.Add("password", options.Password)
	}
	if options.UseSshKey {
		values.Add("use_sshkey", "true")
	}
	if options.Metadata != "" {
		values.Add("metadata", options.Metadata)
	}
	httpResponse, e := http.PostForm(client.BaseUrl()+"/jiffyBoxes", values)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	return rsp.Server, e
}

// unfreeze server
func (client *Client) ThawServer(id int, planId int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_THAW, planId)
}

func (client *Client) FreezeServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_FREEZE, -1)
}

func (client *Client) PullPlugServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_PULLPLUG, -1)
}

func (client *Client) ShutdownServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_SHUTDOWN, -1)
}

func (client *Client) StartServer(id int, planId int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_START, planId)
}

func (client *Client) ChangeState(id int, state string, planId int) (server *Server, e error) {
	values := url.Values{}
	values.Add("status", state)
	if planId > 0 {
		values.Add("planid", strconv.Itoa(planId))
	}
	buf := bytes.Buffer{}
	buf.WriteString(values.Encode())
	req, e := http.NewRequest("PUT", client.BaseUrl()+"/jiffyBoxes/"+strconv.Itoa(id), &buf)
	if e != nil {
		return nil, e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	if e != nil {
		return rsp.Server, e
	}
	errors := []string{}
	for _, message := range rsp.Messages {
		if message.Type == "error" {
			errors = append(errors, message.Message)
		}
	}
	if len(errors) > 0 {
		return rsp.Server, fmt.Errorf(strings.Join(errors, ", "))
	}
	return rsp.Server, e
}

func (client *Client) CloneServer(id int, opts *CreateOptions) (server *Server, e error) {
	values := url.Values{}
	if opts.Name == "" {
		return nil, fmt.Errorf("name must be provided to clone server")
	}
	values.Add("name", opts.Name)
	values.Add("planid", strconv.Itoa(opts.PlanId))
	if opts.Metadata != "" {
		values.Add("metadata", opts.Metadata)
	}
	httpResponse, e := client.PostForm("jiffyBoxes/"+strconv.Itoa(id), values)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshal(httpResponse.Content, rsp)
	if e != nil {
		return nil, e
	}
	return rsp.Server, nil
}

func (client *Client) ChangeStatus(id string, status string, planId int, metadata string) (server *Server, e error) {
	values := url.Values{}
	values.Add("status", status)
	values.Add("planid", strconv.Itoa(planId))
	if metadata != "" {
		values.Add("metadata", metadata)
	}
	buf := &bytes.Buffer{}
	buf.Write([]byte(values.Encode()))
	req, e := http.NewRequest("PUT", client.BaseUrl()+"/JiffyBox/"+id, buf)
	if e != nil {
		return nil, e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	if e != nil {
		return nil, e
	}
	return rsp.Server, nil
}
<|endoftext|>


<commit_before>// Copyright 2019 Istio 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 helmreconciler

import (
	"context"
	"fmt"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"istio.io/api/operator/v1alpha1"

	valuesv1alpha1 "istio.io/istio/operator/pkg/apis/istio/v1alpha1"
	"istio.io/istio/operator/pkg/name"
	"istio.io/istio/operator/pkg/object"
	"istio.io/istio/operator/pkg/util"
	"istio.io/istio/operator/pkg/util/clog"
)

type componentNameToListMap map[name.ComponentName][]name.ComponentName
type componentTree map[name.ComponentName]interface{}

const (
	// Time to wait for internal dependencies before proceeding to installing the next component.
	internalDepTimeout = 10 * time.Minute
)

var (
	componentDependencies = componentNameToListMap{
		name.PilotComponentName: {
			name.PolicyComponentName,
			name.TelemetryComponentName,
			name.CNIComponentName,
			name.IngressComponentName,
			name.EgressComponentName,
			name.AddonComponentName,
		},
		name.IstioBaseComponentName: {
			name.PilotComponentName,
		},
	}

	installTree      = make(componentTree)
	dependencyWaitCh = make(map[name.ComponentName]chan struct{})
)

func init() {
	buildInstallTree()
	for _, parent := range componentDependencies {
		for _, child := range parent {
			dependencyWaitCh[child] = make(chan struct{}, 1)
		}
	}

}

// HelmReconciler reconciles resources rendered by a set of helm charts.
type HelmReconciler struct {
	client             client.Client
	restConfig         *rest.Config
	clientSet          *kubernetes.Clientset
	iop                *valuesv1alpha1.IstioOperator
	pruningDetails     PruningDetails
	opts               *Options
	needUpdateAndPrune bool
	// copy of the last generated manifests.
	manifests name.ManifestMap
}

// Options are options for HelmReconciler.
type Options struct {
	// DryRun executes all actions but does not write anything to the cluster.
	DryRun bool
	// Log is a console logger for user visible CLI output.
	Log clog.Logger
	// Wait determines if we will wait for resources to be fully applied.
	Wait        bool
	ProgressLog *util.ProgressLog
}

var defaultOptions = &Options{Log: clog.NewDefaultLogger()}

// NewHelmReconciler creates a HelmReconciler and returns a ptr to it
func NewHelmReconciler(client client.Client, restConfig *rest.Config, iop *valuesv1alpha1.IstioOperator, opts *Options) (*HelmReconciler, error) {
	if opts == nil {
		opts = defaultOptions
	}
	var cs *kubernetes.Clientset
	var err error
	if restConfig != nil {
		cs, err = kubernetes.NewForConfig(restConfig)
	}
	if err != nil {
		return nil, err
	}
	return &HelmReconciler{
		client:             client,
		restConfig:         restConfig,
		clientSet:          cs,
		iop:                iop,
		pruningDetails:     NewIstioPruningDetails(iop),
		opts:               opts,
		needUpdateAndPrune: true,
	}, nil
}

// Reconcile reconciles the associated resources.
func (h *HelmReconciler) Reconcile() (*v1alpha1.InstallStatus, error) {
	manifestMap, err := h.RenderCharts()
	if err != nil {
		return nil, err
	}

	status := h.processRecursive(manifestMap)

	// Delete any resources not in the manifest but managed by operator.
	if h.needUpdateAndPrune {
		err = h.Prune(allObjectHashes(manifestMap), false)
	}

	return status, err
}

// processRecursive processes the given manifests in an order of dependencies defined in h. Dependencies are a tree,
// where a child must wait for the parent to complete before starting.
func (h *HelmReconciler) processRecursive(manifests ChartManifestsMap) *v1alpha1.InstallStatus {
	componentStatus := make(map[string]*v1alpha1.InstallStatus_VersionStatus)

	// mu protects the shared InstallStatus componentStatus across goroutines
	var mu sync.Mutex
	// wg waits for all manifest processing goroutines to finish
	var wg sync.WaitGroup

	for c, m := range manifests {
		c, m := c, m
		wg.Add(1)
		go func() {
			var processedObjs object.K8sObjects
			defer wg.Done()
			cn := name.ComponentName(c)
			if s := dependencyWaitCh[cn]; s != nil {
				scope.Infof("%s is waiting on dependency...", c)
				<-s
				scope.Infof("Dependency for %s has completed, proceeding.", c)
			}

			// Possible paths for status are RECONCILING -> {NONE, ERROR, HEALTHY}. NONE means component has no resources.
			// In NONE case, the component is not shown in overall status.
			mu.Lock()
			setStatus(componentStatus, c, v1alpha1.InstallStatus_RECONCILING, nil)
			mu.Unlock()

			status := v1alpha1.InstallStatus_NONE
			var err error
			if len(m) != 0 {
				if processedObjs, err = h.ProcessManifest(m, len(componentDependencies[cn]) > 0); err != nil {
					status = v1alpha1.InstallStatus_ERROR
				} else if len(processedObjs) != 0 {
					status = v1alpha1.InstallStatus_HEALTHY
				}
			}

			mu.Lock()
			setStatus(componentStatus, c, status, err)
			mu.Unlock()

			// Signal all the components that depend on us.
			for _, ch := range componentDependencies[cn] {
				scope.Infof("Unblocking dependency %s.", ch)
				dependencyWaitCh[ch] <- struct{}{}
			}
		}()
	}
	wg.Wait()

	out := &v1alpha1.InstallStatus{
		Status:          overallStatus(componentStatus),
		ComponentStatus: componentStatus,
	}

	return out
}

// Delete resources associated with the custom resource instance
func (h *HelmReconciler) Delete() error {
	h.needUpdateAndPrune = true
	return h.Prune(nil, true)
}

// SetStatusBegin updates the status field on the IstioOperator instance before reconciling.
func (h *HelmReconciler) SetStatusBegin() error {
	isop := &valuesv1alpha1.IstioOperator{}
	namespacedName := types.NamespacedName{
		Name:      h.iop.Name,
		Namespace: h.iop.Namespace,
	}
	if err := h.GetClient().Get(context.TODO(), namespacedName, isop); err != nil {
		if runtime.IsNotRegisteredError(err) {
			// CRD not yet installed in cluster, nothing to update.
			return nil
		}
		return fmt.Errorf("failed to get IstioOperator before updating status due to %v", err)
	}
	if isop.Status == nil {
		isop.Status = &v1alpha1.InstallStatus{Status: v1alpha1.InstallStatus_RECONCILING}
	} else {
		cs := isop.Status.ComponentStatus
		for cn := range cs {
			cs[cn] = &v1alpha1.InstallStatus_VersionStatus{
				Status: v1alpha1.InstallStatus_RECONCILING,
			}
		}
		isop.Status.Status = v1alpha1.InstallStatus_RECONCILING
	}
	return h.GetClient().Status().Update(context.TODO(), isop)
}

// SetStatusComplete updates the status field on the IstioOperator instance based on the resulting err parameter.
func (h *HelmReconciler) SetStatusComplete(status *v1alpha1.InstallStatus) error {
	iop := &valuesv1alpha1.IstioOperator{}
	namespacedName := types.NamespacedName{
		Name:      h.iop.Name,
		Namespace: h.iop.Namespace,
	}
	if err := h.GetClient().Get(context.TODO(), namespacedName, iop); err != nil {
		return fmt.Errorf("failed to get IstioOperator before updating status due to %v", err)
	}
	iop.Status = status
	return h.GetClient().Status().Update(context.TODO(), iop)
}

// setStatus sets the status for the component with the given name, which is a key in the given map.
// If the status is InstallStatus_NONE, the component name is deleted from the map.
// Otherwise, if the map key/value is missing, one is created.
func setStatus(s map[string]*v1alpha1.InstallStatus_VersionStatus, componentName string, status v1alpha1.InstallStatus_Status, err error) {
	if status == v1alpha1.InstallStatus_NONE {
		delete(s, componentName)
		return
	}
	if _, ok := s[componentName]; !ok {
		s[componentName] = &v1alpha1.InstallStatus_VersionStatus{}
	}
	s[componentName].Status = status
	if err != nil {
		s[componentName].Error = err.Error()
	}
}

// overallStatus returns the summary status over all components.
// - If all components are HEALTHY, overall status is HEALTHY.
// - If one or more components are RECONCILING and others are HEALTHY, overall status is RECONCILING.
// - If one or more components are UPDATING and others are HEALTHY, overall status is UPDATING.
// - If components are a mix of RECONCILING, UPDATING and HEALTHY, overall status is UPDATING.
// - If any component is in ERROR state, overall status is ERROR.
func overallStatus(componentStatus map[string]*v1alpha1.InstallStatus_VersionStatus) v1alpha1.InstallStatus_Status {
	ret := v1alpha1.InstallStatus_HEALTHY
	for _, cs := range componentStatus {
		if cs.Status == v1alpha1.InstallStatus_ERROR {
			ret = v1alpha1.InstallStatus_ERROR
			break
		} else if cs.Status == v1alpha1.InstallStatus_UPDATING {
			ret = v1alpha1.InstallStatus_UPDATING
			break
		} else if cs.Status == v1alpha1.InstallStatus_RECONCILING {
			ret = v1alpha1.InstallStatus_RECONCILING
			break
		}
	}
	return ret
}

// allObjectHashes returns a map with object hashes of all the objects contained in cmm as the keys.
func allObjectHashes(cmm ChartManifestsMap) map[string]bool {
	ret := make(map[string]bool)
	for _, mm := range cmm {
		for _, m := range mm {
			objs, err := object.ParseK8sObjectsFromYAMLManifest(m.Content)
			if err != nil {
				scope.Error(err.Error())
			}
			for _, o := range objs {
				ret[o.Hash()] = true
			}
		}
	}
	return ret
}

// GetClient returns the kubernetes client associated with this HelmReconciler
func (h *HelmReconciler) GetClient() client.Client {
	return h.client
}

func buildInstallTree() {
	// Starting with root, recursively insert each first level child into each node.
	insertChildrenRecursive(name.IstioBaseComponentName, installTree, componentDependencies)
}

func insertChildrenRecursive(componentName name.ComponentName, tree componentTree, children componentNameToListMap) {
	tree[componentName] = make(componentTree)
	for _, child := range children[componentName] {
		insertChildrenRecursive(child, tree[componentName].(componentTree), children)
	}
}
<commit_msg>init progressLog for helm reconciler. (#23222)<commit_after>// Copyright 2019 Istio 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 helmreconciler

import (
	"context"
	"fmt"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"istio.io/api/operator/v1alpha1"

	valuesv1alpha1 "istio.io/istio/operator/pkg/apis/istio/v1alpha1"
	"istio.io/istio/operator/pkg/name"
	"istio.io/istio/operator/pkg/object"
	"istio.io/istio/operator/pkg/util"
	"istio.io/istio/operator/pkg/util/clog"
)

type componentNameToListMap map[name.ComponentName][]name.ComponentName
type componentTree map[name.ComponentName]interface{}

const (
	// Time to wait for internal dependencies before proceeding to installing the next component.
	internalDepTimeout = 10 * time.Minute
)

var (
	componentDependencies = componentNameToListMap{
		name.PilotComponentName: {
			name.PolicyComponentName,
			name.TelemetryComponentName,
			name.CNIComponentName,
			name.IngressComponentName,
			name.EgressComponentName,
			name.AddonComponentName,
		},
		name.IstioBaseComponentName: {
			name.PilotComponentName,
		},
	}

	installTree      = make(componentTree)
	dependencyWaitCh = make(map[name.ComponentName]chan struct{})
)

func init() {
	buildInstallTree()
	for _, parent := range componentDependencies {
		for _, child := range parent {
			dependencyWaitCh[child] = make(chan struct{}, 1)
		}
	}

}

// HelmReconciler reconciles resources rendered by a set of helm charts.
type HelmReconciler struct {
	client             client.Client
	restConfig         *rest.Config
	clientSet          *kubernetes.Clientset
	iop                *valuesv1alpha1.IstioOperator
	pruningDetails     PruningDetails
	opts               *Options
	needUpdateAndPrune bool
	// copy of the last generated manifests.
	manifests name.ManifestMap
}

// Options are options for HelmReconciler.
type Options struct {
	// DryRun executes all actions but does not write anything to the cluster.
	DryRun bool
	// Log is a console logger for user visible CLI output.
	Log clog.Logger
	// Wait determines if we will wait for resources to be fully applied.
	Wait        bool
	ProgressLog *util.ProgressLog
}

var defaultOptions = &Options{
	Log:         clog.NewDefaultLogger(),
	ProgressLog: util.NewProgressLog(),
}

// NewHelmReconciler creates a HelmReconciler and returns a ptr to it
func NewHelmReconciler(client client.Client, restConfig *rest.Config, iop *valuesv1alpha1.IstioOperator, opts *Options) (*HelmReconciler, error) {
	if opts == nil {
		opts = defaultOptions
	}
	var cs *kubernetes.Clientset
	var err error
	if restConfig != nil {
		cs, err = kubernetes.NewForConfig(restConfig)
	}
	if err != nil {
		return nil, err
	}
	return &HelmReconciler{
		client:             client,
		restConfig:         restConfig,
		clientSet:          cs,
		iop:                iop,
		pruningDetails:     NewIstioPruningDetails(iop),
		opts:               opts,
		needUpdateAndPrune: true,
	}, nil
}

// Reconcile reconciles the associated resources.
func (h *HelmReconciler) Reconcile() (*v1alpha1.InstallStatus, error) {
	manifestMap, err := h.RenderCharts()
	if err != nil {
		return nil, err
	}

	status := h.processRecursive(manifestMap)

	// Delete any resources not in the manifest but managed by operator.
	if h.needUpdateAndPrune {
		err = h.Prune(allObjectHashes(manifestMap), false)
	}

	return status, err
}

// processRecursive processes the given manifests in an order of dependencies defined in h. Dependencies are a tree,
// where a child must wait for the parent to complete before starting.
func (h *HelmReconciler) processRecursive(manifests ChartManifestsMap) *v1alpha1.InstallStatus {
	componentStatus := make(map[string]*v1alpha1.InstallStatus_VersionStatus)

	// mu protects the shared InstallStatus componentStatus across goroutines
	var mu sync.Mutex
	// wg waits for all manifest processing goroutines to finish
	var wg sync.WaitGroup

	for c, m := range manifests {
		c, m := c, m
		wg.Add(1)
		go func() {
			var processedObjs object.K8sObjects
			defer wg.Done()
			cn := name.ComponentName(c)
			if s := dependencyWaitCh[cn]; s != nil {
				scope.Infof("%s is waiting on dependency...", c)
				<-s
				scope.Infof("Dependency for %s has completed, proceeding.", c)
			}

			// Possible paths for status are RECONCILING -> {NONE, ERROR, HEALTHY}. NONE means component has no resources.
			// In NONE case, the component is not shown in overall status.
			mu.Lock()
			setStatus(componentStatus, c, v1alpha1.InstallStatus_RECONCILING, nil)
			mu.Unlock()

			status := v1alpha1.InstallStatus_NONE
			var err error
			if len(m) != 0 {
				if processedObjs, err = h.ProcessManifest(m, len(componentDependencies[cn]) > 0); err != nil {
					status = v1alpha1.InstallStatus_ERROR
				} else if len(processedObjs) != 0 {
					status = v1alpha1.InstallStatus_HEALTHY
				}
			}

			mu.Lock()
			setStatus(componentStatus, c, status, err)
			mu.Unlock()

			// Signal all the components that depend on us.
			for _, ch := range componentDependencies[cn] {
				scope.Infof("Unblocking dependency %s.", ch)
				dependencyWaitCh[ch] <- struct{}{}
			}
		}()
	}
	wg.Wait()

	out := &v1alpha1.InstallStatus{
		Status:          overallStatus(componentStatus),
		ComponentStatus: componentStatus,
	}

	return out
}

// Delete resources associated with the custom resource instance
func (h *HelmReconciler) Delete() error {
	h.needUpdateAndPrune = true
	return h.Prune(nil, true)
}

// SetStatusBegin updates the status field on the IstioOperator instance before reconciling.
func (h *HelmReconciler) SetStatusBegin() error {
	isop := &valuesv1alpha1.IstioOperator{}
	namespacedName := types.NamespacedName{
		Name:      h.iop.Name,
		Namespace: h.iop.Namespace,
	}
	if err := h.GetClient().Get(context.TODO(), namespacedName, isop); err != nil {
		if runtime.IsNotRegisteredError(err) {
			// CRD not yet installed in cluster, nothing to update.
			return nil
		}
		return fmt.Errorf("failed to get IstioOperator before updating status due to %v", err)
	}
	if isop.Status == nil {
		isop.Status = &v1alpha1.InstallStatus{Status: v1alpha1.InstallStatus_RECONCILING}
	} else {
		cs := isop.Status.ComponentStatus
		for cn := range cs {
			cs[cn] = &v1alpha1.InstallStatus_VersionStatus{
				Status: v1alpha1.InstallStatus_RECONCILING,
			}
		}
		isop.Status.Status = v1alpha1.InstallStatus_RECONCILING
	}
	return h.GetClient().Status().Update(context.TODO(), isop)
}

// SetStatusComplete updates the status field on the IstioOperator instance based on the resulting err parameter.
func (h *HelmReconciler) SetStatusComplete(status *v1alpha1.InstallStatus) error {
	iop := &valuesv1alpha1.IstioOperator{}
	namespacedName := types.NamespacedName{
		Name:      h.iop.Name,
		Namespace: h.iop.Namespace,
	}
	if err := h.GetClient().Get(context.TODO(), namespacedName, iop); err != nil {
		return fmt.Errorf("failed to get IstioOperator before updating status due to %v", err)
	}
	iop.Status = status
	return h.GetClient().Status().Update(context.TODO(), iop)
}

// setStatus sets the status for the component with the given name, which is a key in the given map.
// If the status is InstallStatus_NONE, the component name is deleted from the map.
// Otherwise, if the map key/value is missing, one is created.
func setStatus(s map[string]*v1alpha1.InstallStatus_VersionStatus, componentName string, status v1alpha1.InstallStatus_Status, err error) {
	if status == v1alpha1.InstallStatus_NONE {
		delete(s, componentName)
		return
	}
	if _, ok := s[componentName]; !ok {
		s[componentName] = &v1alpha1.InstallStatus_VersionStatus{}
	}
	s[componentName].Status = status
	if err != nil {
		s[componentName].Error = err.Error()
	}
}

// overallStatus returns the summary status over all components.
// - If all components are HEALTHY, overall status is HEALTHY.
// - If one or more components are RECONCILING and others are HEALTHY, overall status is RECONCILING.
// - If one or more components are UPDATING and others are HEALTHY, overall status is UPDATING.
// - If components are a mix of RECONCILING, UPDATING and HEALTHY, overall status is UPDATING.
// - If any component is in ERROR state, overall status is ERROR.
func overallStatus(componentStatus map[string]*v1alpha1.InstallStatus_VersionStatus) v1alpha1.InstallStatus_Status {
	ret := v1alpha1.InstallStatus_HEALTHY
	for _, cs := range componentStatus {
		if cs.Status == v1alpha1.InstallStatus_ERROR {
			ret = v1alpha1.InstallStatus_ERROR
			break
		} else if cs.Status == v1alpha1.InstallStatus_UPDATING {
			ret = v1alpha1.InstallStatus_UPDATING
			break
		} else if cs.Status == v1alpha1.InstallStatus_RECONCILING {
			ret = v1alpha1.InstallStatus_RECONCILING
			break
		}
	}
	return ret
}

// allObjectHashes returns a map with object hashes of all the objects contained in cmm as the keys.
func allObjectHashes(cmm ChartManifestsMap) map[string]bool {
	ret := make(map[string]bool)
	for _, mm := range cmm {
		for _, m := range mm {
			objs, err := object.ParseK8sObjectsFromYAMLManifest(m.Content)
			if err != nil {
				scope.Error(err.Error())
			}
			for _, o := range objs {
				ret[o.Hash()] = true
			}
		}
	}
	return ret
}

// GetClient returns the kubernetes client associated with this HelmReconciler
func (h *HelmReconciler) GetClient() client.Client {
	return h.client
}

func buildInstallTree() {
	// Starting with root, recursively insert each first level child into each node.
	insertChildrenRecursive(name.IstioBaseComponentName, installTree, componentDependencies)
}

func insertChildrenRecursive(componentName name.ComponentName, tree componentTree, children componentNameToListMap) {
	tree[componentName] = make(componentTree)
	for _, child := range children[componentName] {
		insertChildrenRecursive(child, tree[componentName].(componentTree), children)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/andrewslotin/doppelganger/git"
)

// MirrorHandler is a type that implements http.Handler interface and is used to handle requests to "/mirror".
// An action that needs to be executed is defined by "action" form variable. Target repository is specified by its name passed in
// request parameter "name".
//
//   // Create a new mirror of andrewslotin/doppelganger
//   curl http://doppelganger/mirror?action=create&name=andrewslotin/doppelganger
//   // Update an existing mirror of andrewslotin/doppelganger
//   curl http://doppelganger/mirror?action=update&name=andrewslotin/doppelganger
//   // Set up tracking of changes in andrewslotin/doppelganger
//   curl http://doppelganger/mirror?action=track&name=andrewslotin/doppelganger
type MirrorHandler struct {
	githubRepos      git.RepositoryService
	mirroredRepos    git.MirrorService
	trackRepoService git.TrackingService
}

// NewMirrorHandler creates and initializes a new handler.
func NewMirrorHandler(githubRepos git.RepositoryService, mirroredRepos git.MirrorService, trackingService git.TrackingService) *MirrorHandler {
	return &MirrorHandler{
		githubRepos:      githubRepos,
		mirroredRepos:    mirroredRepos,
		trackRepoService: trackingService,
	}
}

func (handler *MirrorHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	startTime := time.Now()

	repoName, ok := handler.fetchRepoFromRequest(req)
	if !ok {
		http.Error(w, "Missing source repository name", http.StatusBadRequest)
		return
	}

	switch action := strings.ToLower(req.FormValue("action")); action {
	case "create":
		if err := handler.CreateMirror(w, repoName); err != nil {
			log.Printf("failed to create mirror %s: %s", repoName, err)
			http.Error(w, "Internal server error", http.StatusInternalServerError)
			return
		}

		if req.FormValue("notrack") == "" && handler.trackRepoService != nil {
			if err := handler.SetupChangeTracking(w, req, repoName); err != nil {
				log.Printf("failed to track changes for mirror %s: %s", repoName, err)
				http.Error(w, "Failed to set up push web hook, please check logs for details", http.StatusInternalServerError)
				return
			}
		}

		log.Printf("mirrored %s [%s]", repoName, time.Since(startTime))
		handler.redirectToRepository(w, req, repoName)
	case "update":
		if err := handler.UpdateMirror(w, repoName); err != nil {
			log.Printf("failed to update mirror %s: %s", repoName, err)
			http.Error(w, "Internal server error", http.StatusInternalServerError)
			return
		}

		log.Printf("updated mirror %s [%s]", repoName, time.Since(startTime))
		handler.redirectToRepository(w, req, repoName)
	case "track":
		if handler.trackRepoService == nil {
			http.Error(w, "Tracking changes not supported", http.StatusNotImplemented)
			return
		}

		if err := handler.SetupChangeTracking(w, req, repoName); err != nil {
			log.Printf("failed to track changes for mirror %s: %s", repoName, err)
			http.Error(w, "Failed to set up push web hook, please check logs for details", http.StatusInternalServerError)
			return
		}

		log.Printf("set up push changes hook for %s [%s]", repoName, time.Since(startTime))
		handler.redirectToRepository(w, req, repoName)
	default:
		http.Error(w, fmt.Sprintf("Unsupported action %q", action), http.StatusBadRequest)
	}
}

// CreateMirror searches for a repository in githubRepos and creates its mirror.
// If there is no such repository an HTTP 404 response will be sent.
func (handler *MirrorHandler) CreateMirror(w http.ResponseWriter, repoName string) error {
	switch repo, err := handler.githubRepos.Get(repoName); err {
	case nil:
		return handler.mirroredRepos.Create(repo.FullName, repo.GitURL)
	case git.ErrorNotFound:
		http.Error(w, "Source repository not found", http.StatusNotFound)
		return nil
	default:
		return err
	}
}

// SetupChangeTracking searches for a repository in githubRepos and sets up changes tracker using trackingService.Track().
// If there is no such repository an HTTP 404 response will be sent.
func (handler *MirrorHandler) SetupChangeTracking(w http.ResponseWriter, req *http.Request, repoName string) error {
	switch repo, err := handler.mirroredRepos.Get(repoName); err {
	case nil:
		return handler.trackRepoService.Track(repo.FullName, apiHookURL(req.Host, req.TLS != nil).String())
	case git.ErrorNotMirrored:
		http.Error(w, "Repository not mirrored", http.StatusNotFound)
		return nil
	default:
		return err
	}
}

// UpdateMirror updates an existing mirror synchronizing its with source.
// If there is no such repository an HTTP 404 response will be sent.
func (handler *MirrorHandler) UpdateMirror(w http.ResponseWriter, repoName string) error {
	switch repo, err := handler.mirroredRepos.Get(repoName); err {
	case nil:
		return handler.mirroredRepos.Update(repo.FullName)
	case git.ErrorNotMirrored:
		http.Error(w, "Source repository not mirrored", http.StatusNotFound)
		return nil
	default:
		return err
	}
}

func (handler *MirrorHandler) redirectToRepository(w http.ResponseWriter, req *http.Request, repoName string) {
	http.Redirect(w, req, "/mirror/"+repoName, http.StatusSeeOther)
}

func (handler *MirrorHandler) fetchRepoFromRequest(req *http.Request) (string, bool) {
	owner, repo := req.URL.Query().Get(":owner"), req.URL.Query().Get(":repo")
	if owner != "" && repo != "" {
		return "", false
	}

	return owner + "/" + repo, true
}

func apiHookURL(host string, isSSL bool) *url.URL {
	scheme := "http"
	if isSSL {
		scheme = "https"
	}

	return &url.URL{
		Scheme: scheme,
		Host:   host,
		Path:   "/apihook",
	}
}
<commit_msg>When creating a mirror fetch repo name from form value<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/andrewslotin/doppelganger/git"
)

// MirrorHandler is a type that implements http.Handler interface and is used to handle requests to "/mirror".
// An action that needs to be executed is defined by "action" form variable. Target repository is specified by its name passed in
// request parameter "name".
//
//   // Create a new mirror of andrewslotin/doppelganger
//   curl http://doppelganger/mirror?action=create&name=andrewslotin/doppelganger
//   // Update an existing mirror of andrewslotin/doppelganger
//   curl http://doppelganger/mirror?action=update&name=andrewslotin/doppelganger
//   // Set up tracking of changes in andrewslotin/doppelganger
//   curl http://doppelganger/mirror?action=track&name=andrewslotin/doppelganger
type MirrorHandler struct {
	githubRepos      git.RepositoryService
	mirroredRepos    git.MirrorService
	trackRepoService git.TrackingService
}

// NewMirrorHandler creates and initializes a new handler.
func NewMirrorHandler(githubRepos git.RepositoryService, mirroredRepos git.MirrorService, trackingService git.TrackingService) *MirrorHandler {
	return &MirrorHandler{
		githubRepos:      githubRepos,
		mirroredRepos:    mirroredRepos,
		trackRepoService: trackingService,
	}
}

func (handler *MirrorHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	startTime := time.Now()

	repoName, ok := handler.fetchRepoFromRequest(req)
	if !ok {
		http.Error(w, "Missing source repository name", http.StatusBadRequest)
		return
	}

	switch action := strings.ToLower(req.FormValue("action")); action {
	case "create":
		if err := handler.CreateMirror(w, repoName); err != nil {
			log.Printf("failed to create mirror %s: %s", repoName, err)
			http.Error(w, "Internal server error", http.StatusInternalServerError)
			return
		}

		if req.FormValue("notrack") == "" && handler.trackRepoService != nil {
			if err := handler.SetupChangeTracking(w, req, repoName); err != nil {
				log.Printf("failed to track changes for mirror %s: %s", repoName, err)
				http.Error(w, "Failed to set up push web hook, please check logs for details", http.StatusInternalServerError)
				return
			}
		}

		log.Printf("mirrored %s [%s]", repoName, time.Since(startTime))
		handler.redirectToRepository(w, req, repoName)
	case "update":
		if err := handler.UpdateMirror(w, repoName); err != nil {
			log.Printf("failed to update mirror %s: %s", repoName, err)
			http.Error(w, "Internal server error", http.StatusInternalServerError)
			return
		}

		log.Printf("updated mirror %s [%s]", repoName, time.Since(startTime))
		handler.redirectToRepository(w, req, repoName)
	case "track":
		if handler.trackRepoService == nil {
			http.Error(w, "Tracking changes not supported", http.StatusNotImplemented)
			return
		}

		if err := handler.SetupChangeTracking(w, req, repoName); err != nil {
			log.Printf("failed to track changes for mirror %s: %s", repoName, err)
			http.Error(w, "Failed to set up push web hook, please check logs for details", http.StatusInternalServerError)
			return
		}

		log.Printf("set up push changes hook for %s [%s]", repoName, time.Since(startTime))
		handler.redirectToRepository(w, req, repoName)
	default:
		http.Error(w, fmt.Sprintf("Unsupported action %q", action), http.StatusBadRequest)
	}
}

// CreateMirror searches for a repository in githubRepos and creates its mirror.
// If there is no such repository an HTTP 404 response will be sent.
func (handler *MirrorHandler) CreateMirror(w http.ResponseWriter, repoName string) error {
	switch repo, err := handler.githubRepos.Get(repoName); err {
	case nil:
		return handler.mirroredRepos.Create(repo.FullName, repo.GitURL)
	case git.ErrorNotFound:
		http.Error(w, "Source repository not found", http.StatusNotFound)
		return nil
	default:
		return err
	}
}

// SetupChangeTracking searches for a repository in githubRepos and sets up changes tracker using trackingService.Track().
// If there is no such repository an HTTP 404 response will be sent.
func (handler *MirrorHandler) SetupChangeTracking(w http.ResponseWriter, req *http.Request, repoName string) error {
	switch repo, err := handler.mirroredRepos.Get(repoName); err {
	case nil:
		return handler.trackRepoService.Track(repo.FullName, apiHookURL(req.Host, req.TLS != nil).String())
	case git.ErrorNotMirrored:
		http.Error(w, "Repository not mirrored", http.StatusNotFound)
		return nil
	default:
		return err
	}
}

// UpdateMirror updates an existing mirror synchronizing its with source.
// If there is no such repository an HTTP 404 response will be sent.
func (handler *MirrorHandler) UpdateMirror(w http.ResponseWriter, repoName string) error {
	switch repo, err := handler.mirroredRepos.Get(repoName); err {
	case nil:
		return handler.mirroredRepos.Update(repo.FullName)
	case git.ErrorNotMirrored:
		http.Error(w, "Source repository not mirrored", http.StatusNotFound)
		return nil
	default:
		return err
	}
}

func (handler *MirrorHandler) redirectToRepository(w http.ResponseWriter, req *http.Request, repoName string) {
	http.Redirect(w, req, "/mirror/"+repoName, http.StatusSeeOther)
}

func (handler *MirrorHandler) fetchRepoFromRequest(req *http.Request) (string, bool) {
	fullRepoName := req.FormValue("repo")
	return fullRepoName, fullRepoName != ""
}

func apiHookURL(host string, isSSL bool) *url.URL {
	scheme := "http"
	if isSSL {
		scheme = "https"
	}

	return &url.URL{
		Scheme: scheme,
		Host:   host,
		Path:   "/apihook",
	}
}
<|endoftext|>


<commit_before>// The reblance algorithm of kafka consumer group
package main

import (
	"flag"
	"fmt"
	"sort"
)

func min(a, b int) int {
	if a > b {
		return b
	}
	return a
}

func main() {
	var (
		curPartitions int
		curConsumers  int
	)
	flag.IntVar(&curPartitions, "p", 17, "partitions count")
	flag.IntVar(&curConsumers, "c", 4, "consumers count")
	flag.Parse()

	fmt.Printf("The topic has %d partitions, and consumer group has %d threads\n",
		curPartitions, curConsumers)

	nPartsPerConsumer := curPartitions / curConsumers
	nConsumersWithExtraPart := curPartitions % curConsumers

	partitionOwnershipDecision := make(map[int]int)

	for myConsumerPosition := 0; myConsumerPosition < curConsumers; myConsumerPosition++ {
		startPart := nPartsPerConsumer*myConsumerPosition + min(myConsumerPosition, nConsumersWithExtraPart)
		x := 1
		if myConsumerPosition+1 > nConsumersWithExtraPart {
			x = 0
		}
		nParts := nPartsPerConsumer + x

		for i := startPart; i < startPart+nParts; i++ {
			fmt.Printf("thread[%d] consumes %2d\n", myConsumerPosition, i)
			partitionOwnershipDecision[myConsumerPosition]++
		}
	}

	sortedConsumerIds := make([]int, 0, len(partitionOwnershipDecision))
	for cid, _ := range partitionOwnershipDecision {
		sortedConsumerIds = append(sortedConsumerIds, cid)
	}
	sort.Ints(sortedConsumerIds)
	for _, cid := range sortedConsumerIds {
		fmt.Printf("thread[%d] got %2d\n", cid, partitionOwnershipDecision[cid])
	}

}
<commit_msg>visualize the kafka consumer group rebalancing algorithm<commit_after>// The reblance algorithm of kafka consumer group
package main

import (
	"flag"
	"fmt"
	"sort"
)

func min(a, b int) int {
	if a > b {
		return b
	}
	return a
}

func main() {
	var (
		curPartitions int
		curConsumers  int
	)
	flag.IntVar(&curPartitions, "p", 17, "partitions count")
	flag.IntVar(&curConsumers, "c", 4, "consumers count")
	flag.Parse()

	fmt.Printf("The topic has %d partitions, and consumer group has %d threads\n",
		curPartitions, curConsumers)

	nPartsPerConsumer := curPartitions / curConsumers
	nConsumersWithExtraPart := curPartitions % curConsumers

	partitionOwnershipDecision := make(map[int][]int)

	for myConsumerPosition := 0; myConsumerPosition < curConsumers; myConsumerPosition++ {
		startPart := nPartsPerConsumer*myConsumerPosition + min(myConsumerPosition, nConsumersWithExtraPart)
		x := 1
		if myConsumerPosition+1 > nConsumersWithExtraPart {
			x = 0
		}
		nParts := nPartsPerConsumer + x

		for pid := startPart; pid < startPart+nParts; pid++ {
			if _, present := partitionOwnershipDecision[myConsumerPosition]; !present {
				partitionOwnershipDecision[myConsumerPosition] = []int{pid}
			} else {
				partitionOwnershipDecision[myConsumerPosition] = append(partitionOwnershipDecision[myConsumerPosition], pid)
			}
		}
	}

	sortedConsumerIds := make([]int, 0, len(partitionOwnershipDecision))
	for cid, _ := range partitionOwnershipDecision {
		sortedConsumerIds = append(sortedConsumerIds, cid)
	}
	sort.Ints(sortedConsumerIds)
	for _, cid := range sortedConsumerIds {
		fmt.Printf("thread[%d] got %2d: %+v\n", cid,
			len(partitionOwnershipDecision[cid]),
			partitionOwnershipDecision[cid])
	}

}
<|endoftext|>


<commit_before>// Copyright (c) 2014-2019 Bitmark Inc.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package transactionrecord_test

import (
	"encoding/json"
	"fmt"
	"testing"

	"github.com/bitmark-inc/bitmarkd/fault"
	"github.com/bitmark-inc/bitmarkd/transactionrecord"
	"github.com/bitmark-inc/bitmarkd/util"
)

// test invalid asset identifiers
func TestInvalidAssetIdentifiers(t *testing.T) {

	invalid := []string{
		"",
		"4b",                         // one byte
		"4bf",                        // odd number of chars
		"4473fb34cc05ed9599935a0098", // truncated
		"4473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de3",    // just one short
		"4473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de379",  // just one char over
		"4473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de3745", // just one byte over

		"BAM04473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // bad prefix
		"ABM04473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // bad prefix
		"QWRT4473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // bad prefix

		"4473fb34cc05ed9599x35a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // invalid hex char x
		"4473fb34cc05ed9599X35a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // invalid hex char X
		"4473fb34cc05ed9599k35a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // invalid hex char k
		"4473fb34cc05ed9599K35a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // invalid hex char K
	}

	for i, textAssetIdentifier := range invalid {
		var link transactionrecord.AssetIdentifier
		n, err := fmt.Sscan(textAssetIdentifier, &link)
		if fault.ErrNotAssetIdentifier != err {
			t.Errorf("%d: testing: %q", i, textAssetIdentifier)
			t.Errorf("%d: expected ErrNotAssetIdentifier but got: %v", i, err)
			return
		}
		if 0 != n {
			t.Errorf("%d: testing: %q", i, textAssetIdentifier)
			t.Errorf("%d: hex to link scanned: %d  expected: 0", i, n)
			return
		}
	}
}

// test asset id conversion
func TestAssetIdentifier(t *testing.T) {

	expectedAssetIdentifier := transactionrecord.AssetIdentifier{
		0x37, 0xde, 0x31, 0x95, 0xb8, 0x79, 0xcf, 0x56,
		0x92, 0xa5, 0x64, 0xe7, 0xf1, 0x9b, 0xd6, 0x20,
		0x3e, 0x3f, 0xd8, 0xcf, 0xfa, 0x80, 0x64, 0xeb,
		0xe8, 0x6e, 0xc8, 0xff, 0xf8, 0xdb, 0xf3, 0x26,
		0xff, 0xa4, 0xd6, 0x5c, 0xfe, 0xf8, 0x35, 0x0d,
		0xb4, 0xd7, 0x2d, 0x93, 0x40, 0x6f, 0x54, 0xfa,
		0x0d, 0x06, 0xce, 0x98, 0x00, 0x5a, 0x93, 0x99,
		0x95, 0xed, 0x05, 0xcc, 0x34, 0xfb, 0x73, 0x44,
	}

	textAssetIdentifier := "37de3195b879cf5692a564e7f19bd6203e3fd8cffa8064ebe86ec8fff8dbf326ffa4d65cfef8350db4d72d93406f54fa0d06ce98005a939995ed05cc34fb7344"

	if expectedAssetIdentifier.String() != textAssetIdentifier {
		t.Errorf("asset id(%%s): %s  expected: %s", expectedAssetIdentifier, textAssetIdentifier)
	}

	if fmt.Sprintf("%v", expectedAssetIdentifier) != textAssetIdentifier {
		t.Errorf("asset id(%%v): %v  expected: %s", expectedAssetIdentifier, textAssetIdentifier)
	}

	if fmt.Sprintf("%#v", expectedAssetIdentifier) != "<asset:"+textAssetIdentifier+">" {
		t.Errorf("asset id(%%#v): %#v  expected: %s", expectedAssetIdentifier, "<asset:"+textAssetIdentifier+">")
	}

	var asset transactionrecord.AssetIdentifier
	n, err := fmt.Sscan("37de3195b879cf5692a564e7f19bd6203e3fd8cffa8064ebe86ec8fff8dbf326ffa4d65cfef8350db4d72d93406f54fa0d06ce98005a939995ed05cc34fb7344", &asset)
	if nil != err {
		t.Fatalf("hex to link error: %s", err)
	}
	if 1 != n {
		t.Fatalf("hex to link scanned: %d  expected: 1", n)
	}

	if asset != expectedAssetIdentifier {
		t.Errorf("asset: %#v  expected: %#v", asset, expectedAssetIdentifier)
		t.Errorf("*** GENERATED asset:\n%s", util.FormatBytes("expectedAssetIdentifier", asset[:]))
	}

	// check JSON conversion
	expectedJSON := `{"AssetIdentifier":"37de3195b879cf5692a564e7f19bd6203e3fd8cffa8064ebe86ec8fff8dbf326ffa4d65cfef8350db4d72d93406f54fa0d06ce98005a939995ed05cc34fb7344"}`

	item := struct {
		AssetIdentifier transactionrecord.AssetIdentifier
	}{
		asset,
	}
	convertedJSON, err := json.Marshal(item)
	if nil != err {
		t.Fatalf("marshal json error: %s", err)
	}
	if expectedJSON != string(convertedJSON) {
		t.Errorf("JSON converted: %q", convertedJSON)
		t.Errorf("     expected:  %q", expectedJSON)
	}

	// test json unmarshal
	var newItem struct {
		AssetIdentifier transactionrecord.AssetIdentifier
	}
	err = json.Unmarshal([]byte(expectedJSON), &newItem)
	if nil != err {
		t.Fatalf("unmarshal json error: %s", err)
	}

	if newItem.AssetIdentifier != expectedAssetIdentifier {
		t.Errorf("link: %#v  expected: %#v", newItem.AssetIdentifier, expectedAssetIdentifier)
	}

}

// test asset id bytes
func TestAssetIdentifierFromBytes(t *testing.T) {

	expectedAssetId := transactionrecord.AssetIdentifier{
		0x37, 0xde, 0x31, 0x95, 0xb8, 0x79, 0xcf, 0x56,
		0x92, 0xa5, 0x64, 0xe7, 0xf1, 0x9b, 0xd6, 0x20,
		0x3e, 0x3f, 0xd8, 0xcf, 0xfa, 0x80, 0x64, 0xeb,
		0xe8, 0x6e, 0xc8, 0xff, 0xf8, 0xdb, 0xf3, 0x26,
		0xff, 0xa4, 0xd6, 0x5c, 0xfe, 0xf8, 0x35, 0x0d,
		0xb4, 0xd7, 0x2d, 0x93, 0x40, 0x6f, 0x54, 0xfa,
		0x0d, 0x06, 0xce, 0x98, 0x00, 0x5a, 0x93, 0x99,
		0x95, 0xed, 0x05, 0xcc, 0x34, 0xfb, 0x73, 0x44,
	}

	valid := []byte{
		0x37, 0xde, 0x31, 0x95, 0xb8, 0x79, 0xcf, 0x56,
		0x92, 0xa5, 0x64, 0xe7, 0xf1, 0x9b, 0xd6, 0x20,
		0x3e, 0x3f, 0xd8, 0xcf, 0xfa, 0x80, 0x64, 0xeb,
		0xe8, 0x6e, 0xc8, 0xff, 0xf8, 0xdb, 0xf3, 0x26,
		0xff, 0xa4, 0xd6, 0x5c, 0xfe, 0xf8, 0x35, 0x0d,
		0xb4, 0xd7, 0x2d, 0x93, 0x40, 0x6f, 0x54, 0xfa,
		0x0d, 0x06, 0xce, 0x98, 0x00, 0x5a, 0x93, 0x99,
		0x95, 0xed, 0x05, 0xcc, 0x34, 0xfb, 0x73, 0x44,
	}

	var assetId transactionrecord.AssetIdentifier
	err := transactionrecord.AssetIdentifierFromBytes(&assetId, valid)
	if nil != err {
		t.Fatalf("AssetIdentifierFromBytes error: %s", err)
	}

	if assetId != expectedAssetId {
		t.Fatalf("assetIdentifier expected: %v  actual: %v", expectedAssetId, assetId)
	}

	err = transactionrecord.AssetIdentifierFromBytes(&assetId, valid[1:])
	if fault.ErrNotAssetIdentifier != err {
		t.Fatalf("AssetIdentifierFromBytes error: %s", err)
	}
}
<commit_msg>[test] fix fault message rename<commit_after>// Copyright (c) 2014-2019 Bitmark Inc.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package transactionrecord_test

import (
	"encoding/json"
	"fmt"
	"testing"

	"github.com/bitmark-inc/bitmarkd/fault"
	"github.com/bitmark-inc/bitmarkd/transactionrecord"
	"github.com/bitmark-inc/bitmarkd/util"
)

// test invalid asset identifiers
func TestInvalidAssetIdentifiers(t *testing.T) {

	invalid := []string{
		"",
		"4b",                         // one byte
		"4bf",                        // odd number of chars
		"4473fb34cc05ed9599935a0098", // truncated
		"4473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de3",    // just one short
		"4473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de379",  // just one char over
		"4473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de3745", // just one byte over

		"BAM04473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // bad prefix
		"ABM04473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // bad prefix
		"QWRT4473fb34cc05ed9599935a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // bad prefix

		"4473fb34cc05ed9599x35a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // invalid hex char x
		"4473fb34cc05ed9599X35a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // invalid hex char X
		"4473fb34cc05ed9599k35a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // invalid hex char k
		"4473fb34cc05ed9599K35a0098ce060dfa546f40932dd7b40d35f8fe5cd6a4ff26f3dbf8ffc86ee8eb6480facfd83f3e20d69bf1e764a59256cf79b89531de37", // invalid hex char K
	}

	for i, textAssetIdentifier := range invalid {
		var link transactionrecord.AssetIdentifier
		n, err := fmt.Sscan(textAssetIdentifier, &link)
		if fault.ErrNotAssetId != err {
			t.Errorf("%d: testing: %q", i, textAssetIdentifier)
			t.Errorf("%d: expected ErrNotAssetIdentifier but got: %v", i, err)
			return
		}
		if 0 != n {
			t.Errorf("%d: testing: %q", i, textAssetIdentifier)
			t.Errorf("%d: hex to link scanned: %d  expected: 0", i, n)
			return
		}
	}
}

// test asset id conversion
func TestAssetIdentifier(t *testing.T) {

	expectedAssetIdentifier := transactionrecord.AssetIdentifier{
		0x37, 0xde, 0x31, 0x95, 0xb8, 0x79, 0xcf, 0x56,
		0x92, 0xa5, 0x64, 0xe7, 0xf1, 0x9b, 0xd6, 0x20,
		0x3e, 0x3f, 0xd8, 0xcf, 0xfa, 0x80, 0x64, 0xeb,
		0xe8, 0x6e, 0xc8, 0xff, 0xf8, 0xdb, 0xf3, 0x26,
		0xff, 0xa4, 0xd6, 0x5c, 0xfe, 0xf8, 0x35, 0x0d,
		0xb4, 0xd7, 0x2d, 0x93, 0x40, 0x6f, 0x54, 0xfa,
		0x0d, 0x06, 0xce, 0x98, 0x00, 0x5a, 0x93, 0x99,
		0x95, 0xed, 0x05, 0xcc, 0x34, 0xfb, 0x73, 0x44,
	}

	textAssetIdentifier := "37de3195b879cf5692a564e7f19bd6203e3fd8cffa8064ebe86ec8fff8dbf326ffa4d65cfef8350db4d72d93406f54fa0d06ce98005a939995ed05cc34fb7344"

	if expectedAssetIdentifier.String() != textAssetIdentifier {
		t.Errorf("asset id(%%s): %s  expected: %s", expectedAssetIdentifier, textAssetIdentifier)
	}

	if fmt.Sprintf("%v", expectedAssetIdentifier) != textAssetIdentifier {
		t.Errorf("asset id(%%v): %v  expected: %s", expectedAssetIdentifier, textAssetIdentifier)
	}

	if fmt.Sprintf("%#v", expectedAssetIdentifier) != "<asset:"+textAssetIdentifier+">" {
		t.Errorf("asset id(%%#v): %#v  expected: %s", expectedAssetIdentifier, "<asset:"+textAssetIdentifier+">")
	}

	var asset transactionrecord.AssetIdentifier
	n, err := fmt.Sscan("37de3195b879cf5692a564e7f19bd6203e3fd8cffa8064ebe86ec8fff8dbf326ffa4d65cfef8350db4d72d93406f54fa0d06ce98005a939995ed05cc34fb7344", &asset)
	if nil != err {
		t.Fatalf("hex to link error: %s", err)
	}
	if 1 != n {
		t.Fatalf("hex to link scanned: %d  expected: 1", n)
	}

	if asset != expectedAssetIdentifier {
		t.Errorf("asset: %#v  expected: %#v", asset, expectedAssetIdentifier)
		t.Errorf("*** GENERATED asset:\n%s", util.FormatBytes("expectedAssetIdentifier", asset[:]))
	}

	// check JSON conversion
	expectedJSON := `{"AssetIdentifier":"37de3195b879cf5692a564e7f19bd6203e3fd8cffa8064ebe86ec8fff8dbf326ffa4d65cfef8350db4d72d93406f54fa0d06ce98005a939995ed05cc34fb7344"}`

	item := struct {
		AssetIdentifier transactionrecord.AssetIdentifier
	}{
		asset,
	}
	convertedJSON, err := json.Marshal(item)
	if nil != err {
		t.Fatalf("marshal json error: %s", err)
	}
	if expectedJSON != string(convertedJSON) {
		t.Errorf("JSON converted: %q", convertedJSON)
		t.Errorf("     expected:  %q", expectedJSON)
	}

	// test json unmarshal
	var newItem struct {
		AssetIdentifier transactionrecord.AssetIdentifier
	}
	err = json.Unmarshal([]byte(expectedJSON), &newItem)
	if nil != err {
		t.Fatalf("unmarshal json error: %s", err)
	}

	if newItem.AssetIdentifier != expectedAssetIdentifier {
		t.Errorf("link: %#v  expected: %#v", newItem.AssetIdentifier, expectedAssetIdentifier)
	}

}

// test asset id bytes
func TestAssetIdentifierFromBytes(t *testing.T) {

	expectedAssetId := transactionrecord.AssetIdentifier{
		0x37, 0xde, 0x31, 0x95, 0xb8, 0x79, 0xcf, 0x56,
		0x92, 0xa5, 0x64, 0xe7, 0xf1, 0x9b, 0xd6, 0x20,
		0x3e, 0x3f, 0xd8, 0xcf, 0xfa, 0x80, 0x64, 0xeb,
		0xe8, 0x6e, 0xc8, 0xff, 0xf8, 0xdb, 0xf3, 0x26,
		0xff, 0xa4, 0xd6, 0x5c, 0xfe, 0xf8, 0x35, 0x0d,
		0xb4, 0xd7, 0x2d, 0x93, 0x40, 0x6f, 0x54, 0xfa,
		0x0d, 0x06, 0xce, 0x98, 0x00, 0x5a, 0x93, 0x99,
		0x95, 0xed, 0x05, 0xcc, 0x34, 0xfb, 0x73, 0x44,
	}

	valid := []byte{
		0x37, 0xde, 0x31, 0x95, 0xb8, 0x79, 0xcf, 0x56,
		0x92, 0xa5, 0x64, 0xe7, 0xf1, 0x9b, 0xd6, 0x20,
		0x3e, 0x3f, 0xd8, 0xcf, 0xfa, 0x80, 0x64, 0xeb,
		0xe8, 0x6e, 0xc8, 0xff, 0xf8, 0xdb, 0xf3, 0x26,
		0xff, 0xa4, 0xd6, 0x5c, 0xfe, 0xf8, 0x35, 0x0d,
		0xb4, 0xd7, 0x2d, 0x93, 0x40, 0x6f, 0x54, 0xfa,
		0x0d, 0x06, 0xce, 0x98, 0x00, 0x5a, 0x93, 0x99,
		0x95, 0xed, 0x05, 0xcc, 0x34, 0xfb, 0x73, 0x44,
	}

	var assetId transactionrecord.AssetIdentifier
	err := transactionrecord.AssetIdentifierFromBytes(&assetId, valid)
	if nil != err {
		t.Fatalf("AssetIdentifierFromBytes error: %s", err)
	}

	if assetId != expectedAssetId {
		t.Fatalf("assetIdentifier expected: %v  actual: %v", expectedAssetId, assetId)
	}

	err = transactionrecord.AssetIdentifierFromBytes(&assetId, valid[1:])
	if fault.ErrNotAssetId != err {
		t.Fatalf("AssetIdentifierFromBytes error: %s", err)
	}
}
<|endoftext|>


<commit_before>package mit

import (
	"testing"
	"time"
)

var testTerm = TermInfo{
	Code:              "2021FA",
	FirstDayOfClasses: time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC),
	LastDayOfClasses:  time.Date(2020, 12, 9, 0, 0, 0, 0, time.UTC),
	ExceptionDays:     map[string]time.Weekday{},
}

func termDate(year int, month time.Month, day int) *time.Time {
	result := time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
	return &result
}

func compareWeekdays(a []time.Weekday, b []time.Weekday) bool {
	if len(a) != len(b) {
		return false
	}

	for i := 0; i < len(a); i++ {
		if a[i] != b[i] {
			return false
		}
	}

	return true
}

func testSingleTime(t *testing.T, input string, expectedScheduledMeeting *ScheduledMeeting, expectedStart *time.Time, expectedEnd *time.Time) {
	resultScheduledMeeting, resultStart, resultEnd, err := ParseScheduledMeeting(input, testTerm)
	if err != nil {
		t.Errorf("ParseScheduledMeeting('%s'): got error '%s'", input, err.Error())
		return
	}

	if !compareWeekdays(resultScheduledMeeting.Weekdays, expectedScheduledMeeting.Weekdays) {
		t.Errorf("ParseScheduledMeeting('%s'): ScheduledMeeting.Weekdays: got %#v, expected %#v", input, resultScheduledMeeting.Weekdays, expectedScheduledMeeting.Weekdays)
	}

	if resultScheduledMeeting.StartSeconds != expectedScheduledMeeting.StartSeconds {
		t.Errorf("ParseScheduledMeeting('%s'): ScheduledMeeting.StartSeconds: got %d, expected %d", input, resultScheduledMeeting.StartSeconds, expectedScheduledMeeting.StartSeconds)
	}

	if resultScheduledMeeting.EndSeconds != expectedScheduledMeeting.EndSeconds {
		t.Errorf("ParseScheduledMeeting('%s'): ScheduledMeeting.EndSeconds: got %d, expected %d", input, resultScheduledMeeting.EndSeconds, expectedScheduledMeeting.EndSeconds)
	}

	if (resultStart == nil && resultStart != expectedStart) || (resultStart != nil && *resultStart != *expectedStart) {
		t.Errorf("ParseScheduledMeeting('%s'): start date: got %s, expected %s", input, resultStart, expectedStart)
	}

	if (resultEnd == nil && resultEnd != expectedEnd) || (resultEnd != nil && *resultEnd != *expectedEnd) {
		t.Errorf("ParseScheduledMeeting('%s'): end date: got %s, expected %s", input, resultEnd, expectedEnd)
	}
}

const minuteSeconds = 60
const hourSeconds = 60 * minuteSeconds

func TestTimeParser(t *testing.T) {
	/*
	 * well-formed
	 */

	testSingleTime(t, "T3", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Tuesday},
		StartSeconds: 15 * hourSeconds,
		EndSeconds:   16 * hourSeconds,
	}, nil, nil)
	testSingleTime(t, "W3.30", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Wednesday},
		StartSeconds: (15 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (16 * hourSeconds) + (30 * minuteSeconds),
	}, nil, nil)
	testSingleTime(t, "R1.30-3.30", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Thursday},
		StartSeconds: (13 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (15 * hourSeconds) + (30 * minuteSeconds),
	}, nil, nil)
	testSingleTime(t, "S4", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Saturday},
		StartSeconds: 16 * hourSeconds,
		EndSeconds:   17 * hourSeconds,
	}, nil, nil)

	testSingleTime(t, "MTF11", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Monday, time.Tuesday, time.Friday},
		StartSeconds: 11 * hourSeconds,
		EndSeconds:   12 * hourSeconds,
	}, nil, nil)

	testSingleTime(t, "TR10.30-12 (BEGINS OCT 21)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Tuesday, time.Thursday},
		StartSeconds: (10 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   12 * hourSeconds,
	}, termDate(2020, time.October, 21), nil)

	testSingleTime(t, "RF11.30 (ENDS DEC 2)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Thursday, time.Friday},
		StartSeconds: (11 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (12 * hourSeconds) + (30 * minuteSeconds),
	}, nil, termDate(2020, time.December, 2))

	testSingleTime(t, "MT9 (MEETS 9/4 TO 10/6)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Monday, time.Tuesday},
		StartSeconds: 9 * hourSeconds,
		EndSeconds:   10 * hourSeconds,
	}, termDate(2020, time.September, 4), termDate(2020, time.October, 6))

	testSingleTime(t, "WF EVE (5-7)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Wednesday, time.Friday},
		StartSeconds: 17 * hourSeconds,
		EndSeconds:   19 * hourSeconds,
	}, nil, nil)
	testSingleTime(t, "TR EVE (4-6 PM)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Tuesday, time.Thursday},
		StartSeconds: 16 * hourSeconds,
		EndSeconds:   18 * hourSeconds,
	}, nil, nil)
	testSingleTime(t, "MW EVE (4.30-5.30 PM) (BEGINS NOV 2)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Monday, time.Wednesday},
		StartSeconds: (16 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (17 * hourSeconds) + (30 * minuteSeconds),
	}, termDate(2020, time.November, 2), nil)

	testSingleTime(t, "MW10 (LIMITED)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Monday, time.Wednesday},
		StartSeconds: 10 * hourSeconds,
		EndSeconds:   11 * hourSeconds,
	}, nil, nil)

	testSingleTime(t, "WR3.30-4.45 (LIMITED) (BEGINS OCT 14) (ENDS OCT 23)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Wednesday, time.Thursday},
		StartSeconds: (15 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (16 * hourSeconds) + (45 * minuteSeconds),
	}, termDate(2020, time.October, 14), termDate(2020, time.October, 23))

	testSingleTime(t, "WR EVE (3.30-4.45 PM) (LIMITED) (BEGINS OCT 14) (ENDS OCT 23)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Wednesday, time.Thursday},
		StartSeconds: (15 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (16 * hourSeconds) + (45 * minuteSeconds),
	}, termDate(2020, time.October, 14), termDate(2020, time.October, 23))

	/*
	 * not well-formed
	 */

	testSingleTime(t, "W 4:30", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Wednesday},
		StartSeconds: (16 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (17 * hourSeconds) + (30 * minuteSeconds),
	}, nil, nil)
}
<commit_msg>mit/parse_test: add test case for stating '12p' explicitly in a time string<commit_after>package mit

import (
	"testing"
	"time"
)

var testTerm = TermInfo{
	Code:              "2021FA",
	FirstDayOfClasses: time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC),
	LastDayOfClasses:  time.Date(2020, 12, 9, 0, 0, 0, 0, time.UTC),
	ExceptionDays:     map[string]time.Weekday{},
}

func termDate(year int, month time.Month, day int) *time.Time {
	result := time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
	return &result
}

func compareWeekdays(a []time.Weekday, b []time.Weekday) bool {
	if len(a) != len(b) {
		return false
	}

	for i := 0; i < len(a); i++ {
		if a[i] != b[i] {
			return false
		}
	}

	return true
}

func testSingleTime(t *testing.T, input string, expectedScheduledMeeting *ScheduledMeeting, expectedStart *time.Time, expectedEnd *time.Time) {
	resultScheduledMeeting, resultStart, resultEnd, err := ParseScheduledMeeting(input, testTerm)
	if err != nil {
		t.Errorf("ParseScheduledMeeting('%s'): got error '%s'", input, err.Error())
		return
	}

	if !compareWeekdays(resultScheduledMeeting.Weekdays, expectedScheduledMeeting.Weekdays) {
		t.Errorf("ParseScheduledMeeting('%s'): ScheduledMeeting.Weekdays: got %#v, expected %#v", input, resultScheduledMeeting.Weekdays, expectedScheduledMeeting.Weekdays)
	}

	if resultScheduledMeeting.StartSeconds != expectedScheduledMeeting.StartSeconds {
		t.Errorf("ParseScheduledMeeting('%s'): ScheduledMeeting.StartSeconds: got %d, expected %d", input, resultScheduledMeeting.StartSeconds, expectedScheduledMeeting.StartSeconds)
	}

	if resultScheduledMeeting.EndSeconds != expectedScheduledMeeting.EndSeconds {
		t.Errorf("ParseScheduledMeeting('%s'): ScheduledMeeting.EndSeconds: got %d, expected %d", input, resultScheduledMeeting.EndSeconds, expectedScheduledMeeting.EndSeconds)
	}

	if (resultStart == nil && resultStart != expectedStart) || (resultStart != nil && *resultStart != *expectedStart) {
		t.Errorf("ParseScheduledMeeting('%s'): start date: got %s, expected %s", input, resultStart, expectedStart)
	}

	if (resultEnd == nil && resultEnd != expectedEnd) || (resultEnd != nil && *resultEnd != *expectedEnd) {
		t.Errorf("ParseScheduledMeeting('%s'): end date: got %s, expected %s", input, resultEnd, expectedEnd)
	}
}

const minuteSeconds = 60
const hourSeconds = 60 * minuteSeconds

func TestTimeParser(t *testing.T) {
	/*
	 * well-formed
	 */

	testSingleTime(t, "T3", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Tuesday},
		StartSeconds: 15 * hourSeconds,
		EndSeconds:   16 * hourSeconds,
	}, nil, nil)
	testSingleTime(t, "W3.30", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Wednesday},
		StartSeconds: (15 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (16 * hourSeconds) + (30 * minuteSeconds),
	}, nil, nil)
	testSingleTime(t, "R1.30-3.30", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Thursday},
		StartSeconds: (13 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (15 * hourSeconds) + (30 * minuteSeconds),
	}, nil, nil)
	testSingleTime(t, "S4", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Saturday},
		StartSeconds: 16 * hourSeconds,
		EndSeconds:   17 * hourSeconds,
	}, nil, nil)

	testSingleTime(t, "MTF11", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Monday, time.Tuesday, time.Friday},
		StartSeconds: 11 * hourSeconds,
		EndSeconds:   12 * hourSeconds,
	}, nil, nil)

	testSingleTime(t, "TR10.30-12 (BEGINS OCT 21)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Tuesday, time.Thursday},
		StartSeconds: (10 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   12 * hourSeconds,
	}, termDate(2020, time.October, 21), nil)

	testSingleTime(t, "RF11.30 (ENDS DEC 2)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Thursday, time.Friday},
		StartSeconds: (11 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (12 * hourSeconds) + (30 * minuteSeconds),
	}, nil, termDate(2020, time.December, 2))

	testSingleTime(t, "MT9 (MEETS 9/4 TO 10/6)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Monday, time.Tuesday},
		StartSeconds: 9 * hourSeconds,
		EndSeconds:   10 * hourSeconds,
	}, termDate(2020, time.September, 4), termDate(2020, time.October, 6))

	testSingleTime(t, "WF EVE (5-7)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Wednesday, time.Friday},
		StartSeconds: 17 * hourSeconds,
		EndSeconds:   19 * hourSeconds,
	}, nil, nil)
	testSingleTime(t, "TR EVE (4-6 PM)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Tuesday, time.Thursday},
		StartSeconds: 16 * hourSeconds,
		EndSeconds:   18 * hourSeconds,
	}, nil, nil)
	testSingleTime(t, "MW EVE (4.30-5.30 PM) (BEGINS NOV 2)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Monday, time.Wednesday},
		StartSeconds: (16 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (17 * hourSeconds) + (30 * minuteSeconds),
	}, termDate(2020, time.November, 2), nil)

	testSingleTime(t, "MW10 (LIMITED)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Monday, time.Wednesday},
		StartSeconds: 10 * hourSeconds,
		EndSeconds:   11 * hourSeconds,
	}, nil, nil)

	testSingleTime(t, "WR3.30-4.45 (LIMITED) (BEGINS OCT 14) (ENDS OCT 23)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Wednesday, time.Thursday},
		StartSeconds: (15 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (16 * hourSeconds) + (45 * minuteSeconds),
	}, termDate(2020, time.October, 14), termDate(2020, time.October, 23))

	testSingleTime(t, "WR EVE (3.30-4.45 PM) (LIMITED) (BEGINS OCT 14) (ENDS OCT 23)", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Wednesday, time.Thursday},
		StartSeconds: (15 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (16 * hourSeconds) + (45 * minuteSeconds),
	}, termDate(2020, time.October, 14), termDate(2020, time.October, 23))

	/*
	 * not well-formed
	 */

	testSingleTime(t, "W 4:30", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Wednesday},
		StartSeconds: (16 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   (17 * hourSeconds) + (30 * minuteSeconds),
	}, nil, nil)
	testSingleTime(t, "TR 10:30-12p", &ScheduledMeeting{
		Weekdays:     []time.Weekday{time.Tuesday, time.Thursday},
		StartSeconds: (10 * hourSeconds) + (30 * minuteSeconds),
		EndSeconds:   12 * hourSeconds,
	}, nil, nil)
}
<|endoftext|>


<commit_before>package decode

import (
	"testing"
)

func NewPacketConfig(chipLength int) (cfg PacketConfig) {
	cfg.CenterFreq = 912600155
	cfg.DataRate = 32768
	cfg.ChipLength = chipLength
	cfg.PreambleSymbols = 21
	cfg.PacketSymbols = 96

	cfg.Preamble = "111110010101001100000"

	return
}

func TestSymbolLength(t *testing.T) {
	for idx := 7; idx <= 72; idx++ {
		d := NewDecoder(NewPacketConfig(idx))
		pLen := d.Cfg.BlockSize + d.Cfg.PreambleLength
		t.Logf("%d: %d/8 = %0.2f (%d)\n", idx, pLen, float64(pLen)/8.0, len(d.packed))

		block := make([]byte, d.Cfg.BlockSize2)
		d.Decode(block)
		d.Decode(block)
		d.Decode(block)
		d.Decode(block)
	}
}

func BenchmarkMagLUT(b *testing.B) {
	d := NewDecoder(NewPacketConfig(72))

	input := make([]byte, d.Cfg.BlockSize2)

	b.SetBytes(int64(d.Cfg.BlockSize))
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		d.demod.Execute(input, d.Signal[d.Cfg.SymbolLength:])
	}
}

func BenchmarkFilter(b *testing.B) {
	d := NewDecoder(NewPacketConfig(72))

	b.SetBytes(int64(d.Cfg.BlockSize))
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		d.Filter(d.Signal, d.Filtered)
	}
}

func BenchmarkQuantize(b *testing.B) {
	d := NewDecoder(NewPacketConfig(72))

	b.SetBytes(int64(d.Cfg.BlockSize))
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		Quantize(d.Filtered[d.Cfg.SymbolLength:], d.Quantized[d.Cfg.PacketLength:])
	}
}

func BenchmarkSearch(b *testing.B) {
	d := NewDecoder(NewPacketConfig(72))

	b.SetBytes(int64(d.Cfg.BlockSize))
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		_ = d.Search()
	}
}

func BenchmarkDecode(b *testing.B) {
	d := NewDecoder(NewPacketConfig(72))

	block := make([]byte, d.Cfg.BlockSize2)

	b.SetBytes(int64(d.Cfg.BlockSize))
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		_ = d.Decode(block)
	}
}
<commit_msg>Correct filter benchmark. Remove symbol length test.<commit_after>package decode

import (
	"testing"
)

func NewPacketConfig(chipLength int) (cfg PacketConfig) {
	cfg.CenterFreq = 912600155
	cfg.DataRate = 32768
	cfg.ChipLength = chipLength
	cfg.PreambleSymbols = 21
	cfg.PacketSymbols = 96

	cfg.Preamble = "111110010101001100000"

	return
}

func BenchmarkMagLUT(b *testing.B) {
	d := NewDecoder(NewPacketConfig(72))

	input := make([]byte, d.Cfg.BlockSize2)

	b.SetBytes(int64(d.Cfg.BlockSize))
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		d.demod.Execute(input, d.Signal[d.Cfg.SymbolLength:])
	}
}

func BenchmarkFilter(b *testing.B) {
	d := NewDecoder(NewPacketConfig(72))

	b.SetBytes(int64(d.Cfg.BlockSize))
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		d.Filter(d.Signal, d.Quantized[d.Cfg.PacketLength:])
	}
}

func BenchmarkSearch(b *testing.B) {
	d := NewDecoder(NewPacketConfig(72))

	b.SetBytes(int64(d.Cfg.BlockSize))
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		_ = d.Search()
	}
}

func BenchmarkDecode(b *testing.B) {
	d := NewDecoder(NewPacketConfig(72))

	block := make([]byte, d.Cfg.BlockSize2)

	b.SetBytes(int64(d.Cfg.BlockSize))
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		_ = d.Decode(block)
	}
}
<|endoftext|>