<commit_before>package flotilla

import (
	"fmt"
	mdb "github.com/jbooth/gomdb"
	"sync"
)

// wrapper for mdb env that waits until all outstanding read txns closed
// before closing env
type env struct {
	e           *mdb.Env
	l           *sync.Mutex
	numOpen     uint64
	shouldClose bool
	closed      bool
}

func newenv(filePath string) (*env, error) {
	e, err := mdb.NewEnv()
	if err != nil {
		return nil, err
	}
	fmt.Printf("Building new MDB env at %s\n", filePath)
	// TODO make these configurable
	e.SetMaxReaders(1024)
	e.SetMaxDBs(1024)
	e.SetMapSize(32 * 1024 * 1024 * 1024)
	// disable fsync because we are guaranteeing consistency/durability via raft snapshotting and edit logs
	err = e.Open(filePath, mdb.WRITEMAP|mdb.NOSYNC|mdb.NOTLS, 0766)
	if err != nil {
		e.Close()
		return nil, err
	}
	return &env{e, new(sync.Mutex), 0, false, false}, nil
}

// opens a read transaction,
func (e *env) readTxn() (*mdb.Txn, error) {
	e.l.Lock()
	defer e.l.Unlock()
	if e.shouldClose {
		// should never happen
		return nil, fmt.Errorf("Environment is marked as closing, no new txns allowed!")
	}
	t, err := e.e.BeginTxn(nil, mdb.RDONLY)
	if err != nil {
		return nil, err
	}
	e.numOpen++
	return t, nil
}

// opens a write transaction, we don't track numOpen with these because
// it shouldn't be called at the same time as a snapshot
func (e *env) writeTxn() (*mdb.Txn, error) {
	e.l.Lock()
	defer e.l.Unlock()
	if e.shouldClose {
		// should never happen
		return nil, fmt.Errorf("Environment is marked as closing, no new txns allowed!")
	}
	t, err := e.e.BeginTxn(nil, 0)
	if err != nil {
		return nil, err
	}
	return t, nil
}

// aborts transactionsuppressing any errors, and frees
func (e *env) CloseTransaction(t *mdb.Txn) error {
	t.Abort() // ignore error
	e.l.Lock()
	defer e.l.Unlock()
	e.numOpen--
	if e.shouldClose && e.numOpen == 0 && !e.closed {
		e.e.Close()
		e.closed = true
	}
	e.e.Close()
	return nil
}

// marks this env as pending-closed.  closing the last open transaction will close
// env handle
func (e *env) Close() error {
	e.l.Lock()
	defer e.l.Unlock()
	e.shouldClose = true
	e.e.Sync(1)
	e.e.Close()
	return nil
}
<commit_msg>max size 512GB<commit_after>package flotilla

import (
	"fmt"
	mdb "github.com/jbooth/gomdb"
	"sync"
)

// wrapper for mdb env that waits until all outstanding read txns closed
// before closing env
type env struct {
	e           *mdb.Env
	l           *sync.Mutex
	numOpen     uint64
	shouldClose bool
	closed      bool
}

func newenv(filePath string) (*env, error) {
	e, err := mdb.NewEnv()
	if err != nil {
		return nil, err
	}
	fmt.Printf("Building new MDB env at %s\n", filePath)
	// TODO make these configurable
	e.SetMaxReaders(1024)
	e.SetMaxDBs(1024)
	e.SetMapSize(512 * 1024 * 1024 * 1024)
	// disable fsync because we are guaranteeing consistency/durability via raft snapshotting and edit logs
	err = e.Open(filePath, mdb.WRITEMAP|mdb.NOSYNC|mdb.NOTLS, 0766)
	if err != nil {
		e.Close()
		return nil, err
	}
	return &env{e, new(sync.Mutex), 0, false, false}, nil
}

// opens a read transaction,
func (e *env) readTxn() (*mdb.Txn, error) {
	e.l.Lock()
	defer e.l.Unlock()
	if e.shouldClose {
		// should never happen
		return nil, fmt.Errorf("Environment is marked as closing, no new txns allowed!")
	}
	t, err := e.e.BeginTxn(nil, mdb.RDONLY)
	if err != nil {
		return nil, err
	}
	e.numOpen++
	return t, nil
}

// opens a write transaction, we don't track numOpen with these because
// it shouldn't be called at the same time as a snapshot
func (e *env) writeTxn() (*mdb.Txn, error) {
	e.l.Lock()
	defer e.l.Unlock()
	if e.shouldClose {
		// should never happen
		return nil, fmt.Errorf("Environment is marked as closing, no new txns allowed!")
	}
	t, err := e.e.BeginTxn(nil, 0)
	if err != nil {
		return nil, err
	}
	return t, nil
}

// aborts transactionsuppressing any errors, and frees
func (e *env) CloseTransaction(t *mdb.Txn) error {
	t.Abort() // ignore error
	e.l.Lock()
	defer e.l.Unlock()
	e.numOpen--
	if e.shouldClose && e.numOpen == 0 && !e.closed {
		e.e.Close()
		e.closed = true
	}
	e.e.Close()
	return nil
}

// marks this env as pending-closed.  closing the last open transaction will close
// env handle
func (e *env) Close() error {
	e.l.Lock()
	defer e.l.Unlock()
	e.shouldClose = true
	e.e.Sync(1)
	e.e.Close()
	return nil
}
<|endoftext|>


<commit_before>/**
 * (C) Copyright 2014, Deft Labs
 *
 * 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 dlshared

import(
	"fmt"
	"time"
	"sync"
	"strings"
	"encoding/json"
)

// The google cloud messaging service. This component dispatches JSON messages to
// Google via HTTP and implements exponential backoff to ensure that if there is a
// failure, everything slows down as to not overload the consuming service. This
// component wraps a consumer component. The component is configured by passing in
// the configuration path.
// 		"gcm": {
//			"postUrl": "https://android.googleapis.com/gcm/send",
//			"authKey": "WHATEVER_IT_IS_FOR_YOUR_API_ACCESS",
//			"acceptableGoogleCloudMsgFailurePercent": 10,
//			"initialGoogleCloudMsgBackoffInMs": 100,
//			"maxGoogleCloudMsgBackoffInMs": 10000,
//			"consumer": {
//				"maxGoroutines": 1000,
//				"maxWaitOnStopInMs": 30000
//          }
//		}
//
// In the example above, the configuration path would be "gcm" (passed in New function). This assumes
// that "gcm" is located as child of the root of the document.
//
type GoogleCloudMessagingSvc struct {
	Logger
	consumer *Consumer
	configPath string

	consumerChannel chan interface{}
	requestChannel chan interface{}
	responseChannel chan interface{}

	authKey string
	postUrl string

	updateStatsTicker *time.Ticker

	httpClient HttpRequestClient

	stats *GoogleCloudMsgSendStats
	sync.WaitGroup

	responseHandlers []GoogleCloudMessagingMsgResponseHandler
}

type GoogleCloudMessagingMsgResponseHandler func(*GoogleCloudMsgResponse)

const(
	gcmAuthHeader = "Authorization"
	gcmAuthHeaderKeyPrefix = "key="
)

// The user must set the required fields in the message.
type GoogleCloudMsg struct {
	RegistrationIds []string `json:"registration_ids"`
	NotificationKey string `json:"notification_key,omitempty"`
	CollapseKey string `json:"collapse_key,omitempty"`
	DelayWhileIdle bool `json:"delay_while_idle,omitempty"`
	TimeToLive int `json:"time_to_live,omitempty"`
	RestrictedPackageName string `json:"restricted_package_name,omitempty"`
	DryRun bool `json:"dry_run,omitempty"`
	Data map[string]interface{} `json:"data,omitempty"`

	// This can contain anything - usually, it is source info/data that can be used
	// when the response is processed.
	AdditionalInfo map[string]interface{} `json:"-"`
}

// The response object returned by google.
type GoogleCloudMsgResponse struct {
	MulticastId float64 `json:"multicast_id"`
	Success float64 `json:"success"`
	Failure float64 `json:"failure"`
	CanonicalIds float64 `json:"canonical_ids"`
	Results []*GoogleCloudMsgResponseResult `json:"results"`
	OrigMsg *GoogleCloudMsg `json:"-"`
	Err error `json:"-"` // If there is an error (usually io) returned by send POST operation
	HttpStatusCode int `json:"-"`
}

type GoogleCloudMsgResponseResult struct {
	MessageId string `json:"message_id"`
	RegistrationId string `json:"registration_id"`
	Error string `json:"error"`
}

func NewGoogleCloudMessagingSvc(configPath string, httpClient HttpRequestClient, requestChannel, responseChannel chan interface{}) *GoogleCloudMessagingSvc {
	return &GoogleCloudMessagingSvc{	configPath: configPath,
										requestChannel: requestChannel,
										responseChannel: responseChannel,
										consumerChannel: make(chan interface{}),
										updateStatsTicker: time.NewTicker(1 * time.Second),
										httpClient: httpClient,
										responseHandlers: make([]GoogleCloudMessagingMsgResponseHandler, 0),
										stats: &GoogleCloudMsgSendStats{},
	}
}

func (self *GoogleCloudMessagingSvc) sendResponse(response *GoogleCloudMsgResponse, msgCount int, err error) {
	self.Add(1)
	defer self.Done()

	if err != nil {
		self.stats.updateFailureCount(msgCount)
		response.Err = err
	} else {
		self.stats.updateSuccessCount(msgCount)
	}

	self.responseChannel <- response
}

// This is the internal method called by the consumer to actually send the message.
func (self *GoogleCloudMessagingSvc) processMsg(msg interface{}) {

	gcmMsg := msg.(*GoogleCloudMsg)

	response := GoogleCloudMsgResponse{ OrigMsg: gcmMsg }

	msgCount := len(gcmMsg.RegistrationIds)

	var result []byte
	var err error

	response.HttpStatusCode,
	result,
	err = self.httpClient.PostJson(self.postUrl, gcmMsg, map[string]string { gcmAuthHeader:  fmt.Sprintf(gcmAuthHeaderKeyPrefix, self.authKey) })

	if err != nil {

		self.sendResponse(&response, msgCount, NewStackError("Problem with gcm post - err: %v", err))

	} else if response.HttpStatusCode >= 500 {

		self.sendResponse(&response, msgCount, NewStackError("Problem with gcm servers - http status error code: %d", response.HttpStatusCode))

	} else if response.HttpStatusCode == 400 {

		self.sendResponse(&response, msgCount, NewStackError("Problem with gcm post - http status: 400 - problem with JSON sent"))

	} else if response.HttpStatusCode == 401 {

		self.sendResponse(&response, msgCount, NewStackError("Problem with gcm post - http status: 401 - unable to authenticate - check your auth key"))

	} else if response.HttpStatusCode != 200 {

		self.sendResponse(&response, msgCount, NewStackError("Problem with gcm post - http status: %d", response.HttpStatusCode))

	} else {

		if err = json.Unmarshal(result, &response); err != nil {

			self.sendResponse(&response, msgCount, NewStackError("Unable to parse gcm response - err: %v", err))

		} else {

			self.sendResponse(&response, msgCount, nil)

		}
	}
}

func (self *GoogleCloudMessagingSvc) listenForRequests() {
	self.Add(1)
	defer self.Done()
	for msg := range self.requestChannel {
		backoff := self.stats.backoffTime()
		if backoff > 0 { time.Sleep(time.Duration(backoff) * time.Millisecond) }
		self.consumerChannel <- msg
	}
}

func (self *GoogleCloudMessagingSvc) updateStats() { for now := range self.updateStatsTicker.C { self.stats.update(&now) } }

func (self *GoogleCloudMessagingSvc) Start(kernel *Kernel) error {

	self.authKey = strings.TrimSpace(kernel.Configuration.StringWithPath(self.configPath, "authKey", ""))
	self.postUrl = strings.TrimSpace(kernel.Configuration.StringWithPath(self.configPath, "postUrl", "https://android.googleapis.com/gcm/send"))

	if len(self.authKey) == 0 { return NewStackError("Unable to create GoogleCloudMessagingSvc - no \"authKey\" field in config file - path: %s", self.configPath) }

	self.stats.acceptableGcmFailurePercent = kernel.Configuration.IntWithPath(self.configPath, "acceptableGoogleCloudMsgFailurePercent", 10)
	self.stats.initialGcmBackoffInMs = kernel.Configuration.IntWithPath(self.configPath, "initialGoogleCloudMsgBackoffInMs", 100)
	self.stats.maxGcmBackoffInMs = kernel.Configuration.IntWithPath(self.configPath, "maxGoogleCloudMsgBackoffInMs", 10000)

	self.consumer = NewConsumer("GoogleCloudMessagingSvcConsumer",
								self.consumerChannel,
								self.processMsg,
								kernel.Configuration.IntWithPath(self.configPath, "consumer.maxGoroutines", 1000),
								kernel.Configuration.IntWithPath(self.configPath, "consumer.maxWaitOnStopInMs", 30000),
								kernel.Logger)

	if err := self.consumer.Start(); err != nil { return err }

	go self.updateStats()

	go self.listenForRequests()

	return nil
}

func (self *GoogleCloudMessagingSvc) Stop(kernel *Kernel) error {

	close(self.consumerChannel)

	if self.consumer != nil { if err := self.consumer.Stop(); err != nil { return err } }

	self.Wait()

	return nil
}

// The cloud message stats.
type GoogleCloudMsgSendStats struct {
	BackoffTimeInMs int
	CurrentSuccessCount int
	CurrentFailureCount int
	PreviousSuccessCount int
	PreviousFailureCount int

	acceptableGcmFailurePercent int
	initialGcmBackoffInMs int
	maxGcmBackoffInMs int
	sync.Mutex
}

func (self *GoogleCloudMsgSendStats) update(now *time.Time) {
	self.Lock()
	defer self.Unlock()

	if self.currentFailurePercent() <= self.acceptableGcmFailurePercent { self.reduceBackoff() } else { self.increaseBackoff() }

	// Update the current and previous values.
	self.updateCurrent()
}

func (self *GoogleCloudMsgSendStats) updateCurrent() {
	self.PreviousSuccessCount = self.CurrentSuccessCount
	self.PreviousFailureCount = self.CurrentFailureCount
	self.CurrentSuccessCount = 0
	self.CurrentFailureCount = 0
}

func (self *GoogleCloudMsgSendStats) reduceBackoff() {
	if self.BackoffTimeInMs == 0 { return }
	self.BackoffTimeInMs /= 2
	if self.BackoffTimeInMs <= self.initialGcmBackoffInMs { self.BackoffTimeInMs = 0 }
}

func (self *GoogleCloudMsgSendStats) increaseBackoff() {
	if self.BackoffTimeInMs == 0 { self.BackoffTimeInMs = self.initialGcmBackoffInMs
	} else { self.BackoffTimeInMs *= 2 }
	if self.BackoffTimeInMs > self.maxGcmBackoffInMs { self.BackoffTimeInMs = self.maxGcmBackoffInMs  }
}

func (self *GoogleCloudMsgSendStats) currentFailurePercent() int {
	totalMsgs := self.CurrentSuccessCount + self.CurrentFailureCount
	if totalMsgs == 0 { return 0 } else { return (self.CurrentFailureCount / totalMsgs) * 100 }
}

func (self *GoogleCloudMsgSendStats) backoffTime() int {
	self.Lock()
	defer self.Unlock()
	return self.BackoffTimeInMs
}

func (self *GoogleCloudMsgSendStats) updateFailureCount(count int) {
	self.Lock()
	defer self.Unlock()
	self.CurrentFailureCount += count
}

func (self *GoogleCloudMsgSendStats) updateSuccessCount(count int) {
	self.Lock()
	defer self.Unlock()
	self.CurrentSuccessCount += count
}

<commit_msg>updated comment.<commit_after>/**
 * (C) Copyright 2014, Deft Labs
 *
 * 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 dlshared

import(
	"fmt"
	"time"
	"sync"
	"strings"
	"encoding/json"
)

// The google cloud messaging service. This component dispatches JSON messages to
// Google via HTTP and implements exponential backoff to ensure that if there is a
// failure, everything slows down as to not overload the consuming service. This
// component wraps a consumer component. The component is configured by passing in
// the configuration path.
// 		"gcm": {
//			"postUrl": "https://android.googleapis.com/gcm/send",
//			"authKey": "WHATEVER_IT_IS_FOR_YOUR_API_ACCESS",
//			"acceptableGoogleCloudMsgFailurePercent": 10,
//			"initialGoogleCloudMsgBackoffInMs": 100,
//			"maxGoogleCloudMsgBackoffInMs": 10000,
//			"consumer": {
//				"maxGoroutines": 1000,
//				"maxWaitOnStopInMs": 30000
//          }
//		}
//
// In the example above, the configuration path would be "gcm" (passed in New function). This assumes
// that "gcm" is located as child of the root of the document.
//
type GoogleCloudMessagingSvc struct {
	Logger
	consumer *Consumer
	configPath string

	consumerChannel chan interface{}
	requestChannel chan interface{}
	responseChannel chan interface{}

	authKey string
	postUrl string

	updateStatsTicker *time.Ticker

	httpClient HttpRequestClient

	stats *GoogleCloudMsgSendStats
	sync.WaitGroup

	responseHandlers []GoogleCloudMessagingMsgResponseHandler
}

type GoogleCloudMessagingMsgResponseHandler func(*GoogleCloudMsgResponse)

const(
	gcmAuthHeader = "Authorization"
	gcmAuthHeaderKeyPrefix = "key="
)

// The user must set the required fields in the message.
type GoogleCloudMsg struct {
	RegistrationIds []string `json:"registration_ids"`
	NotificationKey string `json:"notification_key,omitempty"`
	CollapseKey string `json:"collapse_key,omitempty"`
	DelayWhileIdle bool `json:"delay_while_idle,omitempty"`
	TimeToLive int `json:"time_to_live,omitempty"`
	RestrictedPackageName string `json:"restricted_package_name,omitempty"`
	DryRun bool `json:"dry_run,omitempty"`
	Data map[string]interface{} `json:"data,omitempty"`

	// This can contain anything - usually, it is source info/data that can be used
	// when the response is processed.
	AdditionalInfo map[string]interface{} `json:"-"`
}

// The response object returned by google.
type GoogleCloudMsgResponse struct {
	MulticastId float64 `json:"multicast_id"`
	Success float64 `json:"success"`
	Failure float64 `json:"failure"`
	CanonicalIds float64 `json:"canonical_ids"`
	Results []*GoogleCloudMsgResponseResult `json:"results"`
	OrigMsg *GoogleCloudMsg `json:"-"`
	Err error `json:"-"` // If there is an error caused by io or a problem with gcm.
	HttpStatusCode int `json:"-"`
}

type GoogleCloudMsgResponseResult struct {
	MessageId string `json:"message_id"`
	RegistrationId string `json:"registration_id"`
	Error string `json:"error"`
}

func NewGoogleCloudMessagingSvc(configPath string, httpClient HttpRequestClient, requestChannel, responseChannel chan interface{}) *GoogleCloudMessagingSvc {
	return &GoogleCloudMessagingSvc{	configPath: configPath,
										requestChannel: requestChannel,
										responseChannel: responseChannel,
										consumerChannel: make(chan interface{}),
										updateStatsTicker: time.NewTicker(1 * time.Second),
										httpClient: httpClient,
										responseHandlers: make([]GoogleCloudMessagingMsgResponseHandler, 0),
										stats: &GoogleCloudMsgSendStats{},
	}
}

func (self *GoogleCloudMessagingSvc) sendResponse(response *GoogleCloudMsgResponse, msgCount int, err error) {
	self.Add(1)
	defer self.Done()

	if err != nil {
		self.stats.updateFailureCount(msgCount)
		response.Err = err
	} else {
		self.stats.updateSuccessCount(msgCount)
	}

	self.responseChannel <- response
}

// This is the internal method called by the consumer to actually send the message.
func (self *GoogleCloudMessagingSvc) processMsg(msg interface{}) {

	gcmMsg := msg.(*GoogleCloudMsg)

	response := GoogleCloudMsgResponse{ OrigMsg: gcmMsg }

	msgCount := len(gcmMsg.RegistrationIds)

	var result []byte
	var err error

	response.HttpStatusCode,
	result,
	err = self.httpClient.PostJson(self.postUrl, gcmMsg, map[string]string { gcmAuthHeader:  fmt.Sprintf(gcmAuthHeaderKeyPrefix, self.authKey) })

	if err != nil {

		self.sendResponse(&response, msgCount, NewStackError("Problem with gcm post - err: %v", err))

	} else if response.HttpStatusCode >= 500 {

		self.sendResponse(&response, msgCount, NewStackError("Problem with gcm servers - http status error code: %d", response.HttpStatusCode))

	} else if response.HttpStatusCode == 400 {

		self.sendResponse(&response, msgCount, NewStackError("Problem with gcm post - http status: 400 - problem with JSON sent"))

	} else if response.HttpStatusCode == 401 {

		self.sendResponse(&response, msgCount, NewStackError("Problem with gcm post - http status: 401 - unable to authenticate - check your auth key"))

	} else if response.HttpStatusCode != 200 {

		self.sendResponse(&response, msgCount, NewStackError("Problem with gcm post - http status: %d", response.HttpStatusCode))

	} else {

		if err = json.Unmarshal(result, &response); err != nil {

			self.sendResponse(&response, msgCount, NewStackError("Unable to parse gcm response - err: %v", err))

		} else {

			self.sendResponse(&response, msgCount, nil)

		}
	}
}

func (self *GoogleCloudMessagingSvc) listenForRequests() {
	self.Add(1)
	defer self.Done()
	for msg := range self.requestChannel {
		backoff := self.stats.backoffTime()
		if backoff > 0 { time.Sleep(time.Duration(backoff) * time.Millisecond) }
		self.consumerChannel <- msg
	}
}

func (self *GoogleCloudMessagingSvc) updateStats() { for now := range self.updateStatsTicker.C { self.stats.update(&now) } }

func (self *GoogleCloudMessagingSvc) Start(kernel *Kernel) error {

	self.authKey = strings.TrimSpace(kernel.Configuration.StringWithPath(self.configPath, "authKey", ""))
	self.postUrl = strings.TrimSpace(kernel.Configuration.StringWithPath(self.configPath, "postUrl", "https://android.googleapis.com/gcm/send"))

	if len(self.authKey) == 0 { return NewStackError("Unable to create GoogleCloudMessagingSvc - no \"authKey\" field in config file - path: %s", self.configPath) }

	self.stats.acceptableGcmFailurePercent = kernel.Configuration.IntWithPath(self.configPath, "acceptableGoogleCloudMsgFailurePercent", 10)
	self.stats.initialGcmBackoffInMs = kernel.Configuration.IntWithPath(self.configPath, "initialGoogleCloudMsgBackoffInMs", 100)
	self.stats.maxGcmBackoffInMs = kernel.Configuration.IntWithPath(self.configPath, "maxGoogleCloudMsgBackoffInMs", 10000)

	self.consumer = NewConsumer("GoogleCloudMessagingSvcConsumer",
								self.consumerChannel,
								self.processMsg,
								kernel.Configuration.IntWithPath(self.configPath, "consumer.maxGoroutines", 1000),
								kernel.Configuration.IntWithPath(self.configPath, "consumer.maxWaitOnStopInMs", 30000),
								kernel.Logger)

	if err := self.consumer.Start(); err != nil { return err }

	go self.updateStats()

	go self.listenForRequests()

	return nil
}

func (self *GoogleCloudMessagingSvc) Stop(kernel *Kernel) error {

	close(self.consumerChannel)

	if self.consumer != nil { if err := self.consumer.Stop(); err != nil { return err } }

	self.Wait()

	return nil
}

// The cloud message stats.
type GoogleCloudMsgSendStats struct {
	BackoffTimeInMs int
	CurrentSuccessCount int
	CurrentFailureCount int
	PreviousSuccessCount int
	PreviousFailureCount int

	acceptableGcmFailurePercent int
	initialGcmBackoffInMs int
	maxGcmBackoffInMs int
	sync.Mutex
}

func (self *GoogleCloudMsgSendStats) update(now *time.Time) {
	self.Lock()
	defer self.Unlock()

	if self.currentFailurePercent() <= self.acceptableGcmFailurePercent { self.reduceBackoff() } else { self.increaseBackoff() }

	// Update the current and previous values.
	self.updateCurrent()
}

func (self *GoogleCloudMsgSendStats) updateCurrent() {
	self.PreviousSuccessCount = self.CurrentSuccessCount
	self.PreviousFailureCount = self.CurrentFailureCount
	self.CurrentSuccessCount = 0
	self.CurrentFailureCount = 0
}

func (self *GoogleCloudMsgSendStats) reduceBackoff() {
	if self.BackoffTimeInMs == 0 { return }
	self.BackoffTimeInMs /= 2
	if self.BackoffTimeInMs <= self.initialGcmBackoffInMs { self.BackoffTimeInMs = 0 }
}

func (self *GoogleCloudMsgSendStats) increaseBackoff() {
	if self.BackoffTimeInMs == 0 { self.BackoffTimeInMs = self.initialGcmBackoffInMs
	} else { self.BackoffTimeInMs *= 2 }
	if self.BackoffTimeInMs > self.maxGcmBackoffInMs { self.BackoffTimeInMs = self.maxGcmBackoffInMs  }
}

func (self *GoogleCloudMsgSendStats) currentFailurePercent() int {
	totalMsgs := self.CurrentSuccessCount + self.CurrentFailureCount
	if totalMsgs == 0 { return 0 } else { return (self.CurrentFailureCount / totalMsgs) * 100 }
}

func (self *GoogleCloudMsgSendStats) backoffTime() int {
	self.Lock()
	defer self.Unlock()
	return self.BackoffTimeInMs
}

func (self *GoogleCloudMsgSendStats) updateFailureCount(count int) {
	self.Lock()
	defer self.Unlock()
	self.CurrentFailureCount += count
}

func (self *GoogleCloudMsgSendStats) updateSuccessCount(count int) {
	self.Lock()
	defer self.Unlock()
	self.CurrentSuccessCount += count
}

<|endoftext|>


<commit_before>package gdb

import (
	"github.com/kr/pty"
	"io"
	"os"
	"os/exec"
	"sync"
)

// Gdb represents a GDB instance. It implements the ReadWriter interface to
// read/write data from/to the target program's TTY.
type Gdb struct {
	io.ReadWriter

	ptm *os.File
	pts *os.File
	cmd *exec.Cmd

	mutex  sync.RWMutex
	stdin  io.WriteCloser
	stdout io.ReadCloser

	sequence int64
	pending  map[string]chan map[string]interface{}

	onNotification NotificationCallback

	recordReaderDone chan bool
}

// New creates and starts a new GDB instance. onNotification if not nil is the
// callback used to deliver to the client the asynchronous notifications sent by
// GDB. It returns a pointer to the newly created instance handled or an error.
func New(onNotification NotificationCallback) (*Gdb, error) {
	// open a new terminal (master and slave) for the target program, they are
	// both saved so that they are nore garbage collected after this function
	// ends
	ptm, pts, err := pty.Open()
	if err != nil {
		return nil, err
	}

	// create GDB command
	cmd := []string{"gdb", "--nx", "--quiet", "--interpreter=mi2", "--tty", pts.Name()}
	gdb, err := NewCmd(cmd, onNotification)

	if err != nil {
		ptm.Close()
		pts.Close()
		return nil, err
	}

	gdb.ptm = ptm
	gdb.pts = pts

	return gdb, nil
}

// NewCmd creates a new GDB instance like New, but allows explicitely
// passing the gdb command to run (including all arguments). cmd is a
// which is passed as-is to exec.Command, so the first element should be
// the command to run, and the remaining elements should each contain a
// single argument.
func NewCmd(cmd []string, onNotification NotificationCallback) (*Gdb, error) {
	gdb := Gdb{onNotification: onNotification}

	gdb.cmd = exec.Command(cmd[0], cmd[1:]...)

	// GDB standard input
	stdin, err := gdb.cmd.StdinPipe()
	if err != nil {
		return nil, err
	}
	gdb.stdin = stdin

	// GDB standard ouput
	stdout, err := gdb.cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	gdb.stdout = stdout

	// start GDB
	if err := gdb.cmd.Start(); err != nil {
		return nil, err
	}

	// prepare the command interface
	gdb.sequence = 1
	gdb.pending = make(map[string]chan map[string]interface{})

	gdb.recordReaderDone = make(chan bool)

	// start the line reader
	go gdb.recordReader()

	return &gdb, nil
}

// Read reads a number of bytes from the target program's output.
func (gdb *Gdb) Read(p []byte) (n int, err error) {
	return gdb.ptm.Read(p)
}

// Write writes a number of bytes to the target program's input.
func (gdb *Gdb) Write(p []byte) (n int, err error) {
	return gdb.ptm.Write(p)
}

// Interrupt sends a signal (SIGINT) to GDB so it can stop the target program
// and resume the processing of commands.
func (gdb *Gdb) Interrupt() error {
	return gdb.cmd.Process.Signal(os.Interrupt)
}

// Exit sends the exit command to GDB and waits for the process to exit.
func (gdb *Gdb) Exit() error {
	// send the exit command and wait for the GDB process
	if _, err := gdb.Send("gdb-exit"); err != nil {
		return err
	}

	// Wait for the recordReader to finish (due to EOF on the pipe).
	// If we do not wait for this, Wait() might clean up stdout
	// while recordReader is still using it, leading to
	// panic: read |0: bad file descriptor
	<-gdb.recordReaderDone

	if err := gdb.cmd.Wait(); err != nil {
		return err
	}

	// close the target program's terminal, since the lifetime of the terminal
	// is longer that the one of the targer program's instances reading from a
	// Gdb object ,i.e., the master side will never return EOF (at least on
	// Linux) so the only way to stop reading is to intercept the I/O error
	// caused by closing the terminal
	if gdb.ptm != nil {
		if err := gdb.ptm.Close(); err != nil {
			return err
		}
	}
	if gdb.pts != nil {
		if err := gdb.pts.Close(); err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>Fix typo in comment<commit_after>package gdb

import (
	"github.com/kr/pty"
	"io"
	"os"
	"os/exec"
	"sync"
)

// Gdb represents a GDB instance. It implements the ReadWriter interface to
// read/write data from/to the target program's TTY.
type Gdb struct {
	io.ReadWriter

	ptm *os.File
	pts *os.File
	cmd *exec.Cmd

	mutex  sync.RWMutex
	stdin  io.WriteCloser
	stdout io.ReadCloser

	sequence int64
	pending  map[string]chan map[string]interface{}

	onNotification NotificationCallback

	recordReaderDone chan bool
}

// New creates and starts a new GDB instance. onNotification if not nil is the
// callback used to deliver to the client the asynchronous notifications sent by
// GDB. It returns a pointer to the newly created instance handled or an error.
func New(onNotification NotificationCallback) (*Gdb, error) {
	// open a new terminal (master and slave) for the target program, they are
	// both saved so that they are nore garbage collected after this function
	// ends
	ptm, pts, err := pty.Open()
	if err != nil {
		return nil, err
	}

	// create GDB command
	cmd := []string{"gdb", "--nx", "--quiet", "--interpreter=mi2", "--tty", pts.Name()}
	gdb, err := NewCmd(cmd, onNotification)

	if err != nil {
		ptm.Close()
		pts.Close()
		return nil, err
	}

	gdb.ptm = ptm
	gdb.pts = pts

	return gdb, nil
}

// NewCmd creates a new GDB instance like New, but allows explicitely passing
// the gdb command to run (including all arguments). cmd is passed as-is to
// exec.Command, so the first element should be the command to run, and the
// remaining elements should each contain a single argument.
func NewCmd(cmd []string, onNotification NotificationCallback) (*Gdb, error) {
	gdb := Gdb{onNotification: onNotification}

	gdb.cmd = exec.Command(cmd[0], cmd[1:]...)

	// GDB standard input
	stdin, err := gdb.cmd.StdinPipe()
	if err != nil {
		return nil, err
	}
	gdb.stdin = stdin

	// GDB standard ouput
	stdout, err := gdb.cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	gdb.stdout = stdout

	// start GDB
	if err := gdb.cmd.Start(); err != nil {
		return nil, err
	}

	// prepare the command interface
	gdb.sequence = 1
	gdb.pending = make(map[string]chan map[string]interface{})

	gdb.recordReaderDone = make(chan bool)

	// start the line reader
	go gdb.recordReader()

	return &gdb, nil
}

// Read reads a number of bytes from the target program's output.
func (gdb *Gdb) Read(p []byte) (n int, err error) {
	return gdb.ptm.Read(p)
}

// Write writes a number of bytes to the target program's input.
func (gdb *Gdb) Write(p []byte) (n int, err error) {
	return gdb.ptm.Write(p)
}

// Interrupt sends a signal (SIGINT) to GDB so it can stop the target program
// and resume the processing of commands.
func (gdb *Gdb) Interrupt() error {
	return gdb.cmd.Process.Signal(os.Interrupt)
}

// Exit sends the exit command to GDB and waits for the process to exit.
func (gdb *Gdb) Exit() error {
	// send the exit command and wait for the GDB process
	if _, err := gdb.Send("gdb-exit"); err != nil {
		return err
	}

	// Wait for the recordReader to finish (due to EOF on the pipe).
	// If we do not wait for this, Wait() might clean up stdout
	// while recordReader is still using it, leading to
	// panic: read |0: bad file descriptor
	<-gdb.recordReaderDone

	if err := gdb.cmd.Wait(); err != nil {
		return err
	}

	// close the target program's terminal, since the lifetime of the terminal
	// is longer that the one of the targer program's instances reading from a
	// Gdb object ,i.e., the master side will never return EOF (at least on
	// Linux) so the only way to stop reading is to intercept the I/O error
	// caused by closing the terminal
	if gdb.ptm != nil {
		if err := gdb.ptm.Close(); err != nil {
			return err
		}
	}
	if gdb.pts != nil {
		if err := gdb.pts.Close(); err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package huaweicloud

import (
	"fmt"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
	huaweicloudsdkas "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3/services/as/v1"
	huaweicloudsdkasmodel "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3/services/as/v1/model"
	huaweicloudsdkecs "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3/services/ecs/v2"
	huaweicloudsdkecsmodel "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3/services/ecs/v2/model"
	"k8s.io/klog/v2"
)

// ElasticCloudServerService represents the elastic cloud server interfaces.
// It should contains all request against elastic cloud server service.
type ElasticCloudServerService interface {
	// DeleteServers deletes a group of server by ID.
	DeleteServers(serverIDs []string) error
}

// AutoScalingService represents the auto scaling service interfaces.
// It should contains all request against auto scaling service.
type AutoScalingService interface {
	// ListScalingGroups list all scaling groups.
	ListScalingGroups() ([]AutoScalingGroup, error)

	// GetDesireInstanceNumber gets the desire instance number of specific auto scaling group.
	GetDesireInstanceNumber(groupID string) (int, error)

	// GetInstances gets the instances in an auto scaling group.
	GetInstances(groupID string) ([]cloudprovider.Instance, error)

	// IncreaseSizeInstance increases the instance number of specific auto scaling group.
	// The delta should be non-negative.
	// IncreaseSizeInstance wait until instance number is updated.
	IncreaseSizeInstance(groupID string, delta int) error
}

// CloudServiceManager represents the cloud service interfaces.
// It should contains all requests against cloud services.
type CloudServiceManager interface {
	// ElasticCloudServerService represents the elastic cloud server interfaces.
	ElasticCloudServerService

	// AutoScalingService represents the auto scaling service interfaces.
	AutoScalingService
}

type cloudServiceManager struct {
	cloudConfig      *CloudConfig
	getECSClientFunc func() *huaweicloudsdkecs.EcsClient
	getASClientFunc  func() *huaweicloudsdkas.AsClient
}

func newCloudServiceManager(cloudConfig *CloudConfig) *cloudServiceManager {
	return &cloudServiceManager{
		cloudConfig:      cloudConfig,
		getECSClientFunc: cloudConfig.getECSClient,
		getASClientFunc:  cloudConfig.getASClient,
	}
}

// DeleteServers deletes a group of server by ID.
func (csm *cloudServiceManager) DeleteServers(serverIDs []string) error {
	ecsClient := csm.getECSClientFunc()
	if ecsClient == nil {
		return fmt.Errorf("failed to delete servers due to can not get ecs client")
	}

	servers := make([]huaweicloudsdkecsmodel.ServerId, 0, len(serverIDs))
	for i := range serverIDs {
		s := huaweicloudsdkecsmodel.ServerId{
			Id: serverIDs[i],
		}
		servers = append(servers, s)
	}

	deletePublicIP := false
	deleteVolume := false
	opts := &huaweicloudsdkecsmodel.DeleteServersRequest{
		Body: &huaweicloudsdkecsmodel.DeleteServersRequestBody{
			DeletePublicip: &deletePublicIP,
			DeleteVolume:   &deleteVolume,
			Servers:        servers,
		},
	}
	deleteResponse, err := ecsClient.DeleteServers(opts)
	if err != nil {
		return fmt.Errorf("failed to delete servers. error: %v", err)
	}
	jobID := deleteResponse.JobId

	err = wait.Poll(5*time.Second, 300*time.Second, func() (bool, error) {
		showJobOpts := &huaweicloudsdkecsmodel.ShowJobRequest{
			JobId: *jobID,
		}
		showJobResponse, err := ecsClient.ShowJob(showJobOpts)
		if err != nil {
			return false, err
		}

		jobStatusEnum := huaweicloudsdkecsmodel.GetShowJobResponseStatusEnum()
		if *showJobResponse.Status == jobStatusEnum.FAIL {
			errCode := *showJobResponse.ErrorCode
			failReason := *showJobResponse.FailReason
			return false, fmt.Errorf("job failed. error code: %s, error msg: %s", errCode, failReason)
		} else if *showJobResponse.Status == jobStatusEnum.SUCCESS {
			return true, nil
		}

		return true, nil
	})

	if err != nil {
		klog.Warningf("failed to delete servers, error: %v", err)
		return err
	}

	return nil
}

func (csm *cloudServiceManager) GetDesireInstanceNumber(groupID string) (int, error) {
	Instances, err := csm.GetInstances(groupID)
	if err != nil {
		klog.Errorf("failed to list scaling group instances. group: %s, error: %v", groupID, err)
		return 0, fmt.Errorf("failed to get instance list")
	}

	// Get desire instance number by total instance number minus the one which is in deleting state.
	desireInstanceNumber := len(Instances)
	for _, instance := range Instances {
		if instance.Status.State == cloudprovider.InstanceDeleting {
			desireInstanceNumber--
		}
	}

	return desireInstanceNumber, nil
}

func (csm *cloudServiceManager) GetInstances(groupID string) ([]cloudprovider.Instance, error) {
	asClient := csm.getASClientFunc()
	if asClient == nil {
		return nil, fmt.Errorf("failed to list scaling groups due to can not get as client")
	}

	// SDK 'ListScalingInstances' only return no more than 20 instances.
	// If there is a need in the future, need to retrieve by pages.
	opts := &huaweicloudsdkasmodel.ListScalingInstancesRequest{
		ScalingGroupId: groupID,
	}
	response, err := asClient.ListScalingInstances(opts)
	if err != nil {
		klog.Errorf("failed to list scaling group instances. group: %s, error: %v", groupID, err)
		return nil, err
	}
	if response == nil || response.ScalingGroupInstances == nil {
		klog.Infof("no instance in scaling group: %s", groupID)
		return nil, nil
	}

	instances := make([]cloudprovider.Instance, 0, len(*response.ScalingGroupInstances))
	for _, sgi := range *response.ScalingGroupInstances {
		instance := cloudprovider.Instance{
			Id:     *sgi.InstanceId,
			Status: csm.transformInstanceState(*sgi.LifeCycleState, *sgi.HealthStatus),
		}
		instances = append(instances, instance)
	}

	return instances, nil
}

func (csm *cloudServiceManager) IncreaseSizeInstance(groupID string, delta int) error {
	// TODO(RainbowMango) finish implementation later
	return nil
}

func (csm *cloudServiceManager) ListScalingGroups() ([]AutoScalingGroup, error) {
	asClient := csm.getASClientFunc()
	if asClient == nil {
		return nil, fmt.Errorf("failed to list scaling groups due to can not get as client")
	}

	requiredState := huaweicloudsdkasmodel.GetListScalingGroupsRequestScalingGroupStatusEnum().INSERVICE
	opts := &huaweicloudsdkasmodel.ListScalingGroupsRequest{
		ScalingGroupStatus: &requiredState,
	}
	response, err := asClient.ListScalingGroups(opts)
	if err != nil {
		klog.Errorf("failed to list scaling groups. error: %v", err)
		return nil, err
	}

	if response == nil || response.ScalingGroups == nil {
		klog.Info("no scaling group yet.")
		return nil, nil
	}

	autoScalingGroups := make([]AutoScalingGroup, 0, len(*response.ScalingGroups))
	for _, sg := range *response.ScalingGroups {
		autoScalingGroup := newAutoScalingGroup(csm, sg)
		autoScalingGroups = append(autoScalingGroups, autoScalingGroup)
		klog.Infof("found autoscaling group: %s", autoScalingGroup.groupName)
	}

	return autoScalingGroups, nil
}

func (csm *cloudServiceManager) transformInstanceState(lifeCycleState huaweicloudsdkasmodel.ScalingGroupInstanceLifeCycleState,
	healthStatus huaweicloudsdkasmodel.ScalingGroupInstanceHealthStatus) *cloudprovider.InstanceStatus {
	instanceStatus := &cloudprovider.InstanceStatus{}

	lifeCycleStateEnum := huaweicloudsdkasmodel.GetScalingGroupInstanceLifeCycleStateEnum()
	switch lifeCycleState {
	case lifeCycleStateEnum.INSERVICE:
		instanceStatus.State = cloudprovider.InstanceRunning
	case lifeCycleStateEnum.PENDING:
		instanceStatus.State = cloudprovider.InstanceCreating
	case lifeCycleStateEnum.PENDING_WAIT:
		instanceStatus.State = cloudprovider.InstanceCreating
	case lifeCycleStateEnum.REMOVING:
		instanceStatus.State = cloudprovider.InstanceDeleting
	case lifeCycleStateEnum.REMOVING_WAIT:
		instanceStatus.State = cloudprovider.InstanceDeleting
	default:
		instanceStatus.ErrorInfo = &cloudprovider.InstanceErrorInfo{
			ErrorClass:   cloudprovider.OtherErrorClass,
			ErrorMessage: fmt.Sprintf("invalid instance lifecycle state: %v", lifeCycleState),
		}
		return instanceStatus
	}

	healthStatusEnum := huaweicloudsdkasmodel.GetScalingGroupInstanceHealthStatusEnum()
	switch healthStatus {
	case healthStatusEnum.NORMAL:
	case healthStatusEnum.INITAILIZING:
	case healthStatusEnum.ERROR:
		instanceStatus.ErrorInfo = &cloudprovider.InstanceErrorInfo{
			ErrorClass:   cloudprovider.OtherErrorClass,
			ErrorMessage: fmt.Sprintf("%v", healthStatus),
		}
		return instanceStatus
	default:
		instanceStatus.ErrorInfo = &cloudprovider.InstanceErrorInfo{
			ErrorClass:   cloudprovider.OtherErrorClass,
			ErrorMessage: fmt.Sprintf("invalid instance health state: %v", healthStatus),
		}
		return instanceStatus
	}

	return instanceStatus
}
<commit_msg>implements node group increase size<commit_after>/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package huaweicloud

import (
	"fmt"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3/core/sdktime"
	huaweicloudsdkas "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3/services/as/v1"
	huaweicloudsdkasmodel "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3/services/as/v1/model"
	huaweicloudsdkecs "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3/services/ecs/v2"
	huaweicloudsdkecsmodel "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3/services/ecs/v2/model"
	"k8s.io/klog/v2"
)

// ElasticCloudServerService represents the elastic cloud server interfaces.
// It should contains all request against elastic cloud server service.
type ElasticCloudServerService interface {
	// DeleteServers deletes a group of server by ID.
	DeleteServers(serverIDs []string) error
}

// AutoScalingService represents the auto scaling service interfaces.
// It should contains all request against auto scaling service.
type AutoScalingService interface {
	// ListScalingGroups list all scaling groups.
	ListScalingGroups() ([]AutoScalingGroup, error)

	// GetDesireInstanceNumber gets the desire instance number of specific auto scaling group.
	GetDesireInstanceNumber(groupID string) (int, error)

	// GetInstances gets the instances in an auto scaling group.
	GetInstances(groupID string) ([]cloudprovider.Instance, error)

	// IncreaseSizeInstance increases the instance number of specific auto scaling group.
	// The delta should be non-negative.
	// IncreaseSizeInstance wait until instance number is updated.
	IncreaseSizeInstance(groupID string, delta int) error
}

// CloudServiceManager represents the cloud service interfaces.
// It should contains all requests against cloud services.
type CloudServiceManager interface {
	// ElasticCloudServerService represents the elastic cloud server interfaces.
	ElasticCloudServerService

	// AutoScalingService represents the auto scaling service interfaces.
	AutoScalingService
}

type cloudServiceManager struct {
	cloudConfig      *CloudConfig
	getECSClientFunc func() *huaweicloudsdkecs.EcsClient
	getASClientFunc  func() *huaweicloudsdkas.AsClient
}

func newCloudServiceManager(cloudConfig *CloudConfig) *cloudServiceManager {
	return &cloudServiceManager{
		cloudConfig:      cloudConfig,
		getECSClientFunc: cloudConfig.getECSClient,
		getASClientFunc:  cloudConfig.getASClient,
	}
}

// DeleteServers deletes a group of server by ID.
func (csm *cloudServiceManager) DeleteServers(serverIDs []string) error {
	ecsClient := csm.getECSClientFunc()
	if ecsClient == nil {
		return fmt.Errorf("failed to delete servers due to can not get ecs client")
	}

	servers := make([]huaweicloudsdkecsmodel.ServerId, 0, len(serverIDs))
	for i := range serverIDs {
		s := huaweicloudsdkecsmodel.ServerId{
			Id: serverIDs[i],
		}
		servers = append(servers, s)
	}

	deletePublicIP := false
	deleteVolume := false
	opts := &huaweicloudsdkecsmodel.DeleteServersRequest{
		Body: &huaweicloudsdkecsmodel.DeleteServersRequestBody{
			DeletePublicip: &deletePublicIP,
			DeleteVolume:   &deleteVolume,
			Servers:        servers,
		},
	}
	deleteResponse, err := ecsClient.DeleteServers(opts)
	if err != nil {
		return fmt.Errorf("failed to delete servers. error: %v", err)
	}
	jobID := deleteResponse.JobId

	err = wait.Poll(5*time.Second, 300*time.Second, func() (bool, error) {
		showJobOpts := &huaweicloudsdkecsmodel.ShowJobRequest{
			JobId: *jobID,
		}
		showJobResponse, err := ecsClient.ShowJob(showJobOpts)
		if err != nil {
			return false, err
		}

		jobStatusEnum := huaweicloudsdkecsmodel.GetShowJobResponseStatusEnum()
		if *showJobResponse.Status == jobStatusEnum.FAIL {
			errCode := *showJobResponse.ErrorCode
			failReason := *showJobResponse.FailReason
			return false, fmt.Errorf("job failed. error code: %s, error msg: %s", errCode, failReason)
		} else if *showJobResponse.Status == jobStatusEnum.SUCCESS {
			return true, nil
		}

		return true, nil
	})

	if err != nil {
		klog.Warningf("failed to delete servers, error: %v", err)
		return err
	}

	return nil
}

func (csm *cloudServiceManager) GetDesireInstanceNumber(groupID string) (int, error) {
	Instances, err := csm.GetInstances(groupID)
	if err != nil {
		klog.Errorf("failed to list scaling group instances. group: %s, error: %v", groupID, err)
		return 0, fmt.Errorf("failed to get instance list")
	}

	// Get desire instance number by total instance number minus the one which is in deleting state.
	desireInstanceNumber := len(Instances)
	for _, instance := range Instances {
		if instance.Status.State == cloudprovider.InstanceDeleting {
			desireInstanceNumber--
		}
	}

	return desireInstanceNumber, nil
}

func (csm *cloudServiceManager) GetInstances(groupID string) ([]cloudprovider.Instance, error) {
	asClient := csm.getASClientFunc()
	if asClient == nil {
		return nil, fmt.Errorf("failed to list scaling groups due to can not get as client")
	}

	// SDK 'ListScalingInstances' only return no more than 20 instances.
	// If there is a need in the future, need to retrieve by pages.
	opts := &huaweicloudsdkasmodel.ListScalingInstancesRequest{
		ScalingGroupId: groupID,
	}
	response, err := asClient.ListScalingInstances(opts)
	if err != nil {
		klog.Errorf("failed to list scaling group instances. group: %s, error: %v", groupID, err)
		return nil, err
	}
	if response == nil || response.ScalingGroupInstances == nil {
		klog.Infof("no instance in scaling group: %s", groupID)
		return nil, nil
	}

	instances := make([]cloudprovider.Instance, 0, len(*response.ScalingGroupInstances))
	for _, sgi := range *response.ScalingGroupInstances {
		instance := cloudprovider.Instance{
			Id:     *sgi.InstanceId,
			Status: csm.transformInstanceState(*sgi.LifeCycleState, *sgi.HealthStatus),
		}
		instances = append(instances, instance)
	}

	return instances, nil
}

// IncreaseSizeInstance increases a scaling group's instance size.
// The workflow works as follows:
// 1. create scaling policy with scheduled type.
// 2. execute the scaling policy immediately(not waiting the policy's launch time).
// 3. wait for the instance number be increased and remove the scaling policy.
func (csm *cloudServiceManager) IncreaseSizeInstance(groupID string, delta int) error {
	originalInstanceSize, err := csm.GetDesireInstanceNumber(groupID)
	if err != nil {
		return err
	}

	// create a scaling policy
	launchTime := sdktime.SdkTime(time.Now().Add(time.Hour))
	addOperation := huaweicloudsdkasmodel.GetScalingPolicyActionOperationEnum().ADD
	instanceNum := int32(delta)
	opts := &huaweicloudsdkasmodel.CreateScalingPolicyRequest{
		Body: &huaweicloudsdkasmodel.CreateScalingPolicyRequestBody{
			// It's not mandatory for AS service to set a unique policy name.
			ScalingPolicyName: "huaweicloudautoscaler",
			ScalingGroupId:    groupID,
			ScalingPolicyType: huaweicloudsdkasmodel.GetCreateScalingPolicyRequestBodyScalingPolicyTypeEnum().SCHEDULED,
			ScheduledPolicy: &huaweicloudsdkasmodel.ScheduledPolicy{
				LaunchTime: &launchTime,
			},
			ScalingPolicyAction: &huaweicloudsdkasmodel.ScalingPolicyAction{
				Operation:      &addOperation,
				InstanceNumber: &instanceNum,
			},
		},
	}

	spID, err := csm.createScalingPolicy(opts)
	if err != nil {
		return err
	}

	// make sure scaling policy will be cleaned up.
	deletePolicyOps := &huaweicloudsdkasmodel.DeleteScalingPolicyRequest{
		ScalingPolicyId: spID,
	}
	defer csm.deleteScalingPolicy(deletePolicyOps)

	// execute policy immediately
	executeAction := huaweicloudsdkasmodel.GetExecuteScalingPolicyRequestBodyActionEnum()
	executeOpts := &huaweicloudsdkasmodel.ExecuteScalingPolicyRequest{
		ScalingPolicyId: spID,
		Body: &huaweicloudsdkasmodel.ExecuteScalingPolicyRequestBody{
			Action: &executeAction.EXECUTE,
		},
	}
	err = csm.executeScalingPolicy(executeOpts)
	if err != nil {
		return err
	}

	// wait for instance number indeed be increased
	return wait.Poll(5*time.Second, 300*time.Second, func() (done bool, err error) {
		currentInstanceSize, err := csm.GetDesireInstanceNumber(groupID)
		if err != nil {
			return false, err
		}

		if currentInstanceSize == originalInstanceSize+delta {
			return true, nil
		}
		klog.V(1).Infof("waiting instance increase from %d to %d, now is: %d", originalInstanceSize, originalInstanceSize+delta, currentInstanceSize)

		return false, nil
	})
}

func (csm *cloudServiceManager) ListScalingGroups() ([]AutoScalingGroup, error) {
	asClient := csm.getASClientFunc()
	if asClient == nil {
		return nil, fmt.Errorf("failed to list scaling groups due to can not get as client")
	}

	requiredState := huaweicloudsdkasmodel.GetListScalingGroupsRequestScalingGroupStatusEnum().INSERVICE
	opts := &huaweicloudsdkasmodel.ListScalingGroupsRequest{
		ScalingGroupStatus: &requiredState,
	}
	response, err := asClient.ListScalingGroups(opts)
	if err != nil {
		klog.Errorf("failed to list scaling groups. error: %v", err)
		return nil, err
	}

	if response == nil || response.ScalingGroups == nil {
		klog.Info("no scaling group yet.")
		return nil, nil
	}

	autoScalingGroups := make([]AutoScalingGroup, 0, len(*response.ScalingGroups))
	for _, sg := range *response.ScalingGroups {
		autoScalingGroup := newAutoScalingGroup(csm, sg)
		autoScalingGroups = append(autoScalingGroups, autoScalingGroup)
		klog.Infof("found autoscaling group: %s", autoScalingGroup.groupName)
	}

	return autoScalingGroups, nil
}

func (csm *cloudServiceManager) transformInstanceState(lifeCycleState huaweicloudsdkasmodel.ScalingGroupInstanceLifeCycleState,
	healthStatus huaweicloudsdkasmodel.ScalingGroupInstanceHealthStatus) *cloudprovider.InstanceStatus {
	instanceStatus := &cloudprovider.InstanceStatus{}

	lifeCycleStateEnum := huaweicloudsdkasmodel.GetScalingGroupInstanceLifeCycleStateEnum()
	switch lifeCycleState {
	case lifeCycleStateEnum.INSERVICE:
		instanceStatus.State = cloudprovider.InstanceRunning
	case lifeCycleStateEnum.PENDING:
		instanceStatus.State = cloudprovider.InstanceCreating
	case lifeCycleStateEnum.PENDING_WAIT:
		instanceStatus.State = cloudprovider.InstanceCreating
	case lifeCycleStateEnum.REMOVING:
		instanceStatus.State = cloudprovider.InstanceDeleting
	case lifeCycleStateEnum.REMOVING_WAIT:
		instanceStatus.State = cloudprovider.InstanceDeleting
	default:
		instanceStatus.ErrorInfo = &cloudprovider.InstanceErrorInfo{
			ErrorClass:   cloudprovider.OtherErrorClass,
			ErrorMessage: fmt.Sprintf("invalid instance lifecycle state: %v", lifeCycleState),
		}
		return instanceStatus
	}

	healthStatusEnum := huaweicloudsdkasmodel.GetScalingGroupInstanceHealthStatusEnum()
	switch healthStatus {
	case healthStatusEnum.NORMAL:
	case healthStatusEnum.INITAILIZING:
	case healthStatusEnum.ERROR:
		instanceStatus.ErrorInfo = &cloudprovider.InstanceErrorInfo{
			ErrorClass:   cloudprovider.OtherErrorClass,
			ErrorMessage: fmt.Sprintf("%v", healthStatus),
		}
		return instanceStatus
	default:
		instanceStatus.ErrorInfo = &cloudprovider.InstanceErrorInfo{
			ErrorClass:   cloudprovider.OtherErrorClass,
			ErrorMessage: fmt.Sprintf("invalid instance health state: %v", healthStatus),
		}
		return instanceStatus
	}

	return instanceStatus
}

func (csm *cloudServiceManager) createScalingPolicy(opts *huaweicloudsdkasmodel.CreateScalingPolicyRequest) (scalingPolicyID string, err error) {
	asClient := csm.getASClientFunc()
	if asClient == nil {
		return "", fmt.Errorf("failed to get as client")
	}

	response, err := asClient.CreateScalingPolicy(opts)
	if err != nil {
		klog.Warningf("create scaling policy failed. policy: %s, error: %v", opts.String(), err)
		return "", err
	}

	klog.V(1).Infof("create scaling policy succeed. policy id: %s", *(response.ScalingPolicyId))

	return *(response.ScalingPolicyId), nil
}

func (csm *cloudServiceManager) executeScalingPolicy(opts *huaweicloudsdkasmodel.ExecuteScalingPolicyRequest) error {
	asClient := csm.getASClientFunc()
	if asClient == nil {
		return fmt.Errorf("failed to get as client")
	}

	_, err := asClient.ExecuteScalingPolicy(opts)
	if err != nil {
		klog.Warningf("execute scaling policy failed. policy id: %s, error: %v", opts.ScalingPolicyId, err)
		return err
	}

	klog.V(1).Infof("execute scaling policy succeed. policy id: %s", opts.ScalingPolicyId)
	return nil
}

func (csm *cloudServiceManager) deleteScalingPolicy(opts *huaweicloudsdkasmodel.DeleteScalingPolicyRequest) error {
	asClient := csm.getASClientFunc()
	if asClient == nil {
		return fmt.Errorf("failed to get as client")
	}

	_, err := asClient.DeleteScalingPolicy(opts)
	if err != nil {
		klog.Warningf("failed to delete scaling policy. policy id: %s, error: %v", opts.ScalingPolicyId, err)
		return err
	}

	klog.V(1).Infof("delete scaling policy succeed. policy id: %s", opts.ScalingPolicyId)
	return nil
}
<|endoftext|>


<commit_before>//go:generate templates -s templates -o templates/templates.go
package schematic

import (
	"bytes"
	"fmt"
	"go/format"
	"strings"
	"text/template"

	bundle "github.com/interagent/schematic/templates"
)

var templates *template.Template

func init() {
	templates = template.New("package.tmpl").Funcs(helpers)
	templates = template.Must(bundle.Parse(templates))
}

// ResolvedSet stores a set of pointers to objects that have already been
// resolved to prevent infinite loops.
type ResolvedSet map[interface{}]bool

// Insert marks a pointer as resolved.
func (rs ResolvedSet) Insert(o interface{}) {
	rs[o] = true
}

// Has returns if a pointer has already been resolved.
func (rs ResolvedSet) Has(o interface{}) bool {
	return rs[o]
}

// Generate generates code according to the schema.
func (s *Schema) Generate() ([]byte, error) {
	var buf bytes.Buffer

	s = s.Resolve(nil, ResolvedSet{})

	name := strings.ToLower(strings.Split(s.Title, " ")[0])
	templates.ExecuteTemplate(&buf, "package.tmpl", name)

	// TODO: Check if we need time.
	templates.ExecuteTemplate(&buf, "imports.tmpl", []string{
		"encoding/json", "fmt", "io", "reflect", "net/http", "runtime",
		"time", "bytes", "context", "strings",
		"github.com/google/go-querystring/query",
	})
	templates.ExecuteTemplate(&buf, "service.tmpl", struct {
		Name    string
		URL     string
		Version string
	}{
		Name:    name,
		URL:     s.URL(),
		Version: s.Version,
	})

	for _, name := range sortedKeys(s.Properties) {
		schema := s.Properties[name]
		// Skipping definitions because there is no links, nor properties.
		if schema.Links == nil && schema.Properties == nil {
			continue
		}

		context := struct {
			Name       string
			Definition *Schema
		}{
			Name:       name,
			Definition: schema,
		}

		if !context.Definition.AreTitleLinksUnique() {
			return nil, fmt.Errorf("duplicate titles detected for %s", context.Name)
		}

		templates.ExecuteTemplate(&buf, "struct.tmpl", context)
		templates.ExecuteTemplate(&buf, "funcs.tmpl", context)
	}

	// Remove blank lines added by text/template
	bytes := newlines.ReplaceAll(buf.Bytes(), []byte(""))

	// Format sources
	clean, err := format.Source(bytes)
	if err != nil {
		return buf.Bytes(), err
	}
	return clean, nil
}

// Resolve resolves reference inside the schema.
func (s *Schema) Resolve(r *Schema, rs ResolvedSet) *Schema {
	if r == nil {
		r = s
	}

	for {
		if s.Ref != nil {
			s = s.Ref.Resolve(r)
		} else if len(s.OneOf) > 0 {
			s = s.OneOf[0].Ref.Resolve(r)
		} else if len(s.AnyOf) > 0 {
			s = s.AnyOf[0].Ref.Resolve(r)
		} else {
			break
		}
	}

	if rs.Has(s) {
		// Already resolved
		return s
	}
	rs.Insert(s)

	for n, d := range s.Definitions {
		s.Definitions[n] = d.Resolve(r, rs)
	}
	for n, p := range s.Properties {
		s.Properties[n] = p.Resolve(r, rs)
	}
	for n, p := range s.PatternProperties {
		s.PatternProperties[n] = p.Resolve(r, rs)
	}
	if s.Items != nil {
		s.Items = s.Items.Resolve(r, rs)
	}
	for _, l := range s.Links {
		l.Resolve(r, rs)
	}
	return s
}

// Types returns the array of types described by this schema.
func (s *Schema) Types() (types []string, err error) {
	if arr, ok := s.Type.([]interface{}); ok {
		for _, v := range arr {
			types = append(types, v.(string))
		}
	} else if str, ok := s.Type.(string); ok {
		types = append(types, str)
	} else {
		err = fmt.Errorf("unknown type %v", s.Type)
	}
	return types, err
}

// GoType returns the Go type for the given schema as string.
func (s *Schema) GoType() string {
	return s.goType(true, true)
}

// IsCustomType returns true if the schema declares a custom type.
func (s *Schema) IsCustomType() bool {
	return len(s.Properties) > 0
}

func (s *Schema) goType(required bool, force bool) (goType string) {
	// Resolve JSON reference/pointer
	types, err := s.Types()
	if err != nil {
		panic(err)
	}
	for _, kind := range types {
		switch kind {
		case "boolean":
			goType = "bool"
		case "string":
			switch s.Format {
			case "date-time":
				goType = "time.Time"
			default:
				goType = "string"
			}
		case "number":
			goType = "float64"
		case "integer":
			goType = "int"
		case "any":
			goType = "interface{}"
		case "array":
			if s.Items != nil {
				goType = "[]" + s.Items.goType(required, force)
			} else {
				goType = "[]interface{}"
			}
		case "object":
			// Check if patternProperties exists.
			if s.PatternProperties != nil {
				for _, prop := range s.PatternProperties {
					goType = fmt.Sprintf("map[string]%s", prop.GoType())
					break // We don't support more than one pattern for now.
				}
				continue
			}
			buf := bytes.NewBufferString("struct {")
			for _, name := range sortedKeys(s.Properties) {
				prop := s.Properties[name]
				req := contains(name, s.Required) || force
				templates.ExecuteTemplate(buf, "field.tmpl", struct {
					Definition *Schema
					Name       string
					Required   bool
					Type       string
				}{
					Definition: prop,
					Name:       name,
					Required:   req,
					Type:       prop.goType(req, force),
				})
			}
			buf.WriteString("}")
			goType = buf.String()
		case "null":
			continue
		default:
			panic(fmt.Sprintf("unknown type %s", kind))
		}
	}
	if goType == "" {
		panic(fmt.Sprintf("type not found : %s", types))
	}
	// Types allow null
	if contains("null", types) || !(required || force) {
		// Don't need a pointer for these types to be "nilable"
		if goType != "interface{}" && !strings.HasPrefix(goType, "[]") && !strings.HasPrefix(goType, "map[") {
			return "*" + goType
		}
	}
	return goType
}

// Values returns function return values types.
func (s *Schema) Values(name string, l *Link) []string {
	var values []string
	name = returnType(name, s, l)
	if s.EmptyResult(l) {
		values = append(values, "error")
	} else if s.ReturnsCustomType(l) {
		values = append(values, fmt.Sprintf("*%s", name), "error")
	} else {
		values = append(values, name, "error")
	}
	return values
}

// AreTitleLinksUnique ensures that all titles are unique for a schema.
//
// If more than one link in a given schema has the same title, we cannot
// accurately generate the client from the schema. Although it's not strictly a
// schema violation, it needs to be fixed before the client can be properly
// generated.
func (s *Schema) AreTitleLinksUnique() bool {
	titles := map[string]bool{}
	var uniqueLinks []*Link
	for _, link := range s.Links {
		title := strings.ToLower(link.Title)
		if _, ok := titles[title]; !ok {
			uniqueLinks = append(uniqueLinks, link)
		}
		titles[title] = true
	}

	return len(uniqueLinks) == len(s.Links)
}

// URL returns schema base URL.
func (s *Schema) URL() string {
	for _, l := range s.Links {
		if l.Rel == "self" {
			return l.HRef.String()
		}
	}
	return ""
}

// ReturnsCustomType returns true if the link returns a custom type.
func (s *Schema) ReturnsCustomType(l *Link) bool {
	if l.TargetSchema != nil {
		return len(l.TargetSchema.Properties) > 0
	}
	return len(s.Properties) > 0
}

// ReturnedGoType returns Go type returned by the given link as a string.
func (s *Schema) ReturnedGoType(name string, l *Link) string {
	if l.TargetSchema != nil {
		if l.TargetSchema.Items == s {
			return "[]" + initialCap(name)
		}
		return l.TargetSchema.goType(true, true)
	}
	return s.goType(true, true)
}

// EmptyResult retursn true if the link result should be empty.
func (s *Schema) EmptyResult(l *Link) bool {
	var (
		types []string
		err   error
	)
	if l.TargetSchema != nil {
		types, err = l.TargetSchema.Types()
	} else {
		types, err = s.Types()
	}
	if err != nil {
		return true
	}
	return len(types) == 1 && types[0] == "null"
}

// Parameters returns function parameters names and types.
func (l *Link) Parameters(name string) ([]string, map[string]string) {
	if l.HRef == nil {
		// No HRef property
		panic(fmt.Errorf("no href property declared for %s", l.Title))
	}
	var order []string
	params := make(map[string]string)
	for _, name := range l.HRef.Order {
		def := l.HRef.Schemas[name]
		order = append(order, name)
		params[name] = def.GoType()
	}
	if l.Schema != nil {
		order = append(order, "o")
		t, required := l.GoType()
		if l.AcceptsCustomType() {
			params["o"] = paramType(name, l)
		} else {
			params["o"] = t
		}
		if !required {
			params["o"] = "*" + params["o"]
		}
	}
	if l.Rel == "instances" && strings.ToUpper(l.Method) == "GET" {
		order = append(order, "lr")
		params["lr"] = "*ListRange"
	}
	return order, params
}

// AcceptsCustomType returns true if the link schema is not a primitive type
func (l *Link) AcceptsCustomType() bool {
	if l.Schema != nil && l.Schema.IsCustomType() {
		return true
	}
	return false
}

// Resolve resolve link schema and href.
func (l *Link) Resolve(r *Schema, rs ResolvedSet) {
	if l.Schema != nil {
		l.Schema = l.Schema.Resolve(r, rs)
	}
	if l.TargetSchema != nil {
		l.TargetSchema = l.TargetSchema.Resolve(r, rs)
	}
	l.HRef.Resolve(r, rs)
}

// GoType returns Go type for the given schema as string and a bool specifying whether it is required
func (l *Link) GoType() (string, bool) {
	t := l.Schema.goType(true, false)
	if t[0] == '*' {
		return t[1:], false
	}
	return t, true
}
<commit_msg>Fail on schema problems with context<commit_after>//go:generate templates -s templates -o templates/templates.go
package schematic

import (
	"bytes"
	"encoding/json"
	"fmt"
	"go/format"
	"os"
	"strings"
	"text/template"

	bundle "github.com/interagent/schematic/templates"
)

var templates *template.Template

func init() {
	templates = template.New("package.tmpl").Funcs(helpers)
	templates = template.Must(bundle.Parse(templates))
}

// ResolvedSet stores a set of pointers to objects that have already been
// resolved to prevent infinite loops.
type ResolvedSet map[interface{}]bool

// Insert marks a pointer as resolved.
func (rs ResolvedSet) Insert(o interface{}) {
	rs[o] = true
}

// Has returns if a pointer has already been resolved.
func (rs ResolvedSet) Has(o interface{}) bool {
	return rs[o]
}

// Generate generates code according to the schema.
func (s *Schema) Generate() ([]byte, error) {
	var buf bytes.Buffer

	s = s.Resolve(nil, ResolvedSet{})

	name := strings.ToLower(strings.Split(s.Title, " ")[0])
	templates.ExecuteTemplate(&buf, "package.tmpl", name)

	// TODO: Check if we need time.
	templates.ExecuteTemplate(&buf, "imports.tmpl", []string{
		"encoding/json", "fmt", "io", "reflect", "net/http", "runtime",
		"time", "bytes", "context", "strings",
		"github.com/google/go-querystring/query",
	})
	templates.ExecuteTemplate(&buf, "service.tmpl", struct {
		Name    string
		URL     string
		Version string
	}{
		Name:    name,
		URL:     s.URL(),
		Version: s.Version,
	})

	for _, name := range sortedKeys(s.Properties) {
		schema := s.Properties[name]
		// Skipping definitions because there is no links, nor properties.
		if schema.Links == nil && schema.Properties == nil {
			continue
		}

		context := struct {
			Name       string
			Definition *Schema
		}{
			Name:       name,
			Definition: schema,
		}

		if !context.Definition.AreTitleLinksUnique() {
			return nil, fmt.Errorf("duplicate titles detected for %s", context.Name)
		}

		templates.ExecuteTemplate(&buf, "struct.tmpl", context)
		templates.ExecuteTemplate(&buf, "funcs.tmpl", context)
	}

	// Remove blank lines added by text/template
	bytes := newlines.ReplaceAll(buf.Bytes(), []byte(""))

	// Format sources
	clean, err := format.Source(bytes)
	if err != nil {
		return buf.Bytes(), err
	}
	return clean, nil
}

// Resolve resolves reference inside the schema.
func (s *Schema) Resolve(r *Schema, rs ResolvedSet) *Schema {
	if r == nil {
		r = s
	}

	for {
		if s.Ref != nil {
			s = s.Ref.Resolve(r)
		} else if len(s.OneOf) > 0 {
			s = s.OneOf[0].Ref.Resolve(r)
		} else if len(s.AnyOf) > 0 {
			s = s.AnyOf[0].Ref.Resolve(r)
		} else {
			break
		}
	}

	if rs.Has(s) {
		// Already resolved
		return s
	}
	rs.Insert(s)

	for n, d := range s.Definitions {
		s.Definitions[n] = d.Resolve(r, rs)
	}
	for n, p := range s.Properties {
		s.Properties[n] = p.Resolve(r, rs)
	}
	for n, p := range s.PatternProperties {
		s.PatternProperties[n] = p.Resolve(r, rs)
	}
	if s.Items != nil {
		s.Items = s.Items.Resolve(r, rs)
	}
	for _, l := range s.Links {
		l.Resolve(r, rs)
	}
	return s
}

// Types returns the array of types described by this schema.
func (s *Schema) Types() (types []string, err error) {
	if arr, ok := s.Type.([]interface{}); ok {
		for _, v := range arr {
			types = append(types, v.(string))
		}
	} else if str, ok := s.Type.(string); ok {
		types = append(types, str)
	} else {
		err = fmt.Errorf("unknown type %v", s.Type)
	}
	return types, err
}

// GoType returns the Go type for the given schema as string.
func (s *Schema) GoType() string {
	return s.goType(true, true)
}

// IsCustomType returns true if the schema declares a custom type.
func (s *Schema) IsCustomType() bool {
	return len(s.Properties) > 0
}

func (s *Schema) goType(required bool, force bool) (goType string) {
	// Resolve JSON reference/pointer
	types, err := s.Types()
	if err != nil {
		fail(s, err)
	}
	for _, kind := range types {
		switch kind {
		case "boolean":
			goType = "bool"
		case "string":
			switch s.Format {
			case "date-time":
				goType = "time.Time"
			default:
				goType = "string"
			}
		case "number":
			goType = "float64"
		case "integer":
			goType = "int"
		case "any":
			goType = "interface{}"
		case "array":
			if s.Items != nil {
				goType = "[]" + s.Items.goType(required, force)
			} else {
				goType = "[]interface{}"
			}
		case "object":
			// Check if patternProperties exists.
			if s.PatternProperties != nil {
				for _, prop := range s.PatternProperties {
					goType = fmt.Sprintf("map[string]%s", prop.GoType())
					break // We don't support more than one pattern for now.
				}
				continue
			}
			buf := bytes.NewBufferString("struct {")
			for _, name := range sortedKeys(s.Properties) {
				prop := s.Properties[name]
				req := contains(name, s.Required) || force
				templates.ExecuteTemplate(buf, "field.tmpl", struct {
					Definition *Schema
					Name       string
					Required   bool
					Type       string
				}{
					Definition: prop,
					Name:       name,
					Required:   req,
					Type:       prop.goType(req, force),
				})
			}
			buf.WriteString("}")
			goType = buf.String()
		case "null":
			continue
		default:
			fail(s, fmt.Errorf("unknown type %s", kind))
		}
	}
	if goType == "" {
		fail(s, fmt.Errorf("type not found : %s", types))
	}
	// Types allow null
	if contains("null", types) || !(required || force) {
		// Don't need a pointer for these types to be "nilable"
		if goType != "interface{}" && !strings.HasPrefix(goType, "[]") && !strings.HasPrefix(goType, "map[") {
			return "*" + goType
		}
	}
	return goType
}

// Values returns function return values types.
func (s *Schema) Values(name string, l *Link) []string {
	var values []string
	name = returnType(name, s, l)
	if s.EmptyResult(l) {
		values = append(values, "error")
	} else if s.ReturnsCustomType(l) {
		values = append(values, fmt.Sprintf("*%s", name), "error")
	} else {
		values = append(values, name, "error")
	}
	return values
}

// AreTitleLinksUnique ensures that all titles are unique for a schema.
//
// If more than one link in a given schema has the same title, we cannot
// accurately generate the client from the schema. Although it's not strictly a
// schema violation, it needs to be fixed before the client can be properly
// generated.
func (s *Schema) AreTitleLinksUnique() bool {
	titles := map[string]bool{}
	var uniqueLinks []*Link
	for _, link := range s.Links {
		title := strings.ToLower(link.Title)
		if _, ok := titles[title]; !ok {
			uniqueLinks = append(uniqueLinks, link)
		}
		titles[title] = true
	}

	return len(uniqueLinks) == len(s.Links)
}

// URL returns schema base URL.
func (s *Schema) URL() string {
	for _, l := range s.Links {
		if l.Rel == "self" {
			return l.HRef.String()
		}
	}
	return ""
}

// ReturnsCustomType returns true if the link returns a custom type.
func (s *Schema) ReturnsCustomType(l *Link) bool {
	if l.TargetSchema != nil {
		return len(l.TargetSchema.Properties) > 0
	}
	return len(s.Properties) > 0
}

// ReturnedGoType returns Go type returned by the given link as a string.
func (s *Schema) ReturnedGoType(name string, l *Link) string {
	if l.TargetSchema != nil {
		if l.TargetSchema.Items == s {
			return "[]" + initialCap(name)
		}
		return l.TargetSchema.goType(true, true)
	}
	return s.goType(true, true)
}

// EmptyResult retursn true if the link result should be empty.
func (s *Schema) EmptyResult(l *Link) bool {
	var (
		types []string
		err   error
	)
	if l.TargetSchema != nil {
		types, err = l.TargetSchema.Types()
	} else {
		types, err = s.Types()
	}
	if err != nil {
		return true
	}
	return len(types) == 1 && types[0] == "null"
}

// Parameters returns function parameters names and types.
func (l *Link) Parameters(name string) ([]string, map[string]string) {
	if l.HRef == nil {
		// No HRef property
		panic(fmt.Errorf("no href property declared for %s", l.Title))
	}
	var order []string
	params := make(map[string]string)
	for _, name := range l.HRef.Order {
		def := l.HRef.Schemas[name]
		order = append(order, name)
		params[name] = def.GoType()
	}
	if l.Schema != nil {
		order = append(order, "o")
		t, required := l.GoType()
		if l.AcceptsCustomType() {
			params["o"] = paramType(name, l)
		} else {
			params["o"] = t
		}
		if !required {
			params["o"] = "*" + params["o"]
		}
	}
	if l.Rel == "instances" && strings.ToUpper(l.Method) == "GET" {
		order = append(order, "lr")
		params["lr"] = "*ListRange"
	}
	return order, params
}

// AcceptsCustomType returns true if the link schema is not a primitive type
func (l *Link) AcceptsCustomType() bool {
	if l.Schema != nil && l.Schema.IsCustomType() {
		return true
	}
	return false
}

// Resolve resolve link schema and href.
func (l *Link) Resolve(r *Schema, rs ResolvedSet) {
	if l.Schema != nil {
		l.Schema = l.Schema.Resolve(r, rs)
	}
	if l.TargetSchema != nil {
		l.TargetSchema = l.TargetSchema.Resolve(r, rs)
	}
	l.HRef.Resolve(r, rs)
}

// GoType returns Go type for the given schema as string and a bool specifying whether it is required
func (l *Link) GoType() (string, bool) {
	t := l.Schema.goType(true, false)
	if t[0] == '*' {
		return t[1:], false
	}
	return t, true
}

func fail(v interface{}, err error) {
	el, _ := json.MarshalIndent(v, "    ", "  ")

	fmt.Printf(
		"Error processing schema element:\n    %s\n\nFailed with: %s\n",
		el,
		err,
	)
	os.Exit(1)
}
<|endoftext|>


<commit_before>package grpccmd

import (
	"fmt"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/golang/protobuf/protoc-gen-go/generator"
)

type grpccmd struct {
	gen *generator.Generator
}

func init() {
	generator.RegisterPlugin(new(grpccmd))
}

// Name returns the name of this plugin, "grpc".
func (g *grpccmd) Name() string {
	return "grpccmd"
}

// P forwards to g.gen.P.
func (g *grpccmd) P(args ...interface{}) { g.gen.P(args...) }

func (g *grpccmd) Init(gen *generator.Generator) {
	g.gen = gen
}
func (g *grpccmd) GenerateImports(file *generator.FileDescriptor) {
	g.P("// grpccmd imports")
	g.P(`import (
	"github.com/spf13/cobra"
	"github.com/nathanielc/grpccmd"
)`)
}

func (g *grpccmd) Generate(file *generator.FileDescriptor) {
	g.P("// Begin grpccmd ")

	g.P("var _ = grpccmd.RunE")

	for _, f := range g.gen.Request.ProtoFile {
		for _, s := range f.GetService() {
			var methodVars []string
			name := s.GetName()

			g.P("// ", name)
			serviceCmdVar := fmt.Sprintf("_%sCmd", name)
			g.P("var ", serviceCmdVar, " = &cobra.Command{")
			g.P(`Use: "`, lowerFirst(name), ` [method]",`)
			g.P(`Short: "Subcommand for the `, name, ` service.",`)
			g.P("}")
			g.P()

			for _, m := range s.GetMethod() {
				methodName := m.GetName()
				methodCmdVar := fmt.Sprintf("_%s_%sCmd", name, methodName)
				methodVars = append(methodVars, methodCmdVar)
				g.P("var ", methodCmdVar, " = &cobra.Command{")
				g.P(`Use: "`, lowerFirst(methodName), `",`)
				g.P(fmt.Sprintf(
					`Short: "Make the %s method call, input-type: %s output-type: %s",`,
					methodName,
					toTypeName(m.GetInputType()),
					toTypeName(m.GetOutputType()),
				))
				g.P(fmt.Sprintf(
					`RunE: grpccmd.RunE(
						"%s",
						"%s",
						func(c *grpc.ClientConn) interface{} {
						return New%sClient(c)
					},
				),`,
					methodName,
					toTypeName(m.GetInputType()),
					name,
				))
				g.P("}")
				g.P()

			}

			g.P("// Register commands with the root command and service command")
			g.P("func init() {")
			g.P("grpccmd.RegisterServiceCmd(", serviceCmdVar, ")")
			g.P(serviceCmdVar, ".AddCommand(")
			for _, n := range methodVars {
				g.P(n, ",")
			}
			g.P(")")
			g.P("}")
			g.P()
		}
	}

	g.P("// End grpccmd")
}

func toTypeName(t string) string {
	// Understand the correct rules here
	return strings.TrimPrefix(t, ".")
}

func lowerFirst(s string) string {
	if s == "" {
		return ""
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToLower(r)) + s[n:]
}
<commit_msg>add imports in sorted order<commit_after>package grpccmd

import (
	"fmt"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/golang/protobuf/protoc-gen-go/generator"
)

type grpccmd struct {
	gen *generator.Generator
}

func init() {
	generator.RegisterPlugin(new(grpccmd))
}

// Name returns the name of this plugin, "grpc".
func (g *grpccmd) Name() string {
	return "grpccmd"
}

// P forwards to g.gen.P.
func (g *grpccmd) P(args ...interface{}) { g.gen.P(args...) }

func (g *grpccmd) Init(gen *generator.Generator) {
	g.gen = gen
}
func (g *grpccmd) GenerateImports(file *generator.FileDescriptor) {
	g.P("// grpccmd imports")
	g.P(`import (
	"github.com/nathanielc/grpccmd"
	"github.com/spf13/cobra"
)`)
}

func (g *grpccmd) Generate(file *generator.FileDescriptor) {
	g.P("// Begin grpccmd ")

	g.P("var _ = grpccmd.RunE")

	for _, f := range g.gen.Request.ProtoFile {
		for _, s := range f.GetService() {
			var methodVars []string
			name := s.GetName()

			g.P("// ", name)
			serviceCmdVar := fmt.Sprintf("_%sCmd", name)
			g.P("var ", serviceCmdVar, " = &cobra.Command{")
			g.P(`Use: "`, lowerFirst(name), ` [method]",`)
			g.P(`Short: "Subcommand for the `, name, ` service.",`)
			g.P("}")
			g.P()

			for _, m := range s.GetMethod() {
				methodName := m.GetName()
				methodCmdVar := fmt.Sprintf("_%s_%sCmd", name, methodName)
				methodVars = append(methodVars, methodCmdVar)
				g.P("var ", methodCmdVar, " = &cobra.Command{")
				g.P(`Use: "`, lowerFirst(methodName), `",`)
				g.P(fmt.Sprintf(
					`Short: "Make the %s method call, input-type: %s output-type: %s",`,
					methodName,
					toTypeName(m.GetInputType()),
					toTypeName(m.GetOutputType()),
				))
				g.P(fmt.Sprintf(
					`RunE: grpccmd.RunE(
						"%s",
						"%s",
						func(c *grpc.ClientConn) interface{} {
						return New%sClient(c)
					},
				),`,
					methodName,
					toTypeName(m.GetInputType()),
					name,
				))
				g.P("}")
				g.P()

			}

			g.P("// Register commands with the root command and service command")
			g.P("func init() {")
			g.P("grpccmd.RegisterServiceCmd(", serviceCmdVar, ")")
			g.P(serviceCmdVar, ".AddCommand(")
			for _, n := range methodVars {
				g.P(n, ",")
			}
			g.P(")")
			g.P("}")
			g.P()
		}
	}

	g.P("// End grpccmd")
}

func toTypeName(t string) string {
	// Understand the correct rules here
	return strings.TrimPrefix(t, ".")
}

func lowerFirst(s string) string {
	if s == "" {
		return ""
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToLower(r)) + s[n:]
}
<|endoftext|>


<commit_before>package async

import (
	"net/http"
	"time"
)

type Response struct {
	http *http.Response
	err  error
}

func Get(urls ...string) <-chan *Response {
	channel := make(chan *Response, len(urls))
	client := &http.Client{
		Timeout: 60 * time.Second,
	}

	for _, url := range urls {
		go func(url string) {
			response, err := client.Get(url)

			if err != nil {
				defer response.Body.Close()
			}

			channel <- &Response{response, err}
		}(url)
	}

	return channel
}
<commit_msg>Export fields<commit_after>package async

import (
	"net/http"
	"time"
)

type Response struct {
	Http *http.Response
	Err  error
}

func Get(urls ...string) <-chan *Response {
	channel := make(chan *Response, len(urls))
	client := &http.Client{
		Timeout: 60 * time.Second,
	}

	for _, url := range urls {
		go func(url string) {
			response, err := client.Get(url)

			if err != nil {
				defer response.Body.Close()
			}

			channel <- &Response{response, err}
		}(url)
	}

	return channel
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os/exec"
	"strings"
)

// getGitBranch returns the current git branch name
func getGitBranch() string {
	out, err := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD").Output()
	if err != nil {
		return ""
	}
	return strings.Trim(string(out), "\n")
}

// getGitChanges counts the number of changes on this repo
func getGitChanges() int {
	out, err := exec.Command("git", "status", "-s", "--porcelain").Output()
	if err != nil {
		return -1
	}
	changes := 0
	s := bufio.NewScanner(bytes.NewReader(out))
	for s.Scan() {
		changes++
	}
	return changes
}

// getGitPartial builds Git partial string
func getGitPartial() string {
	partial := ""
	changes := getGitChanges()
	if changes > 0 {
		partial = fmt.Sprintf("%d  ", changes)
	}
	partial = fmt.Sprintf("%s%s", partial, getGitBranch())
	return partial
}
<commit_msg>Always show git icon if there is a repo<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os/exec"
	"strings"
)

// getGitBranch returns the current git branch name
func getGitBranch() string {
	out, err := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD").Output()
	if err != nil {
		return ""
	}
	return strings.Trim(string(out), "\n")
}

// getGitChanges counts the number of changes on this repo
func getGitChanges() int {
	out, err := exec.Command("git", "status", "-s", "--porcelain").Output()
	if err != nil {
		return -1
	}
	changes := 0
	s := bufio.NewScanner(bytes.NewReader(out))
	for s.Scan() {
		changes++
	}
	return changes
}

// getGitPartial builds Git partial string
func getGitPartial() string {
	partial := ""
	changes := getGitChanges()
	partial = fmt.Sprintf("%s%s", partial, " ")
	if changes > 0 {
		partial = fmt.Sprintf("%s%d ", partial, changes)
	}
	partial = fmt.Sprintf("%s%s", partial, getGitBranch())
	return partial
}
<|endoftext|>


<commit_before>package gol

import (
	"github.com/ianremmler/chipmunk"
	"github.com/ianremmler/gordian"

	"fmt"
	"math"
	"math/rand"
	"sync"
	"time"
)

const (
	simTime       = time.Second / 60
	updateTime    = time.Second / 24
	pauseTime     = time.Second
	headStartTime = time.Second
	maxScore      = 10
	fieldWidth    = 1000
	fieldHeight   = 500
	edgeRadius    = 8
	goalSize      = 200
	playerRadius  = 10
	playerMass    = 1
	ballRadius    = 10
	ballMass      = 0.1
)

const (
	normLayer = 1 << iota
	goalLayer
)

type player struct {
	id          gordian.ClientId
	team        int
	body        chipmunk.Body
	shape       chipmunk.Shape
	cursorBody  chipmunk.Body
	cursorJoint chipmunk.Constraint
}

func (p *player) place() {
	widthRange := 0.5*fieldWidth - 2*playerRadius
	heightRange := 0.5*fieldHeight - playerRadius
	sign := float64(2*p.team - 1)
	pos := chipmunk.Vect{
		(widthRange*rand.Float64() + playerRadius) * sign,
		heightRange * (2*rand.Float64() - 1),
	}
	minDist := 0.25*fieldHeight + playerRadius
	dist := pos.Length()
	if dist < minDist {
		pos = pos.Mul(minDist / dist)
	}
	p.body.SetPosition(pos)
}

func (p *player) enableCursorJoint(enable bool) {
	sp := p.body.Space()
	isEnabled := sp.Contains(p.cursorJoint.(chipmunk.PivotJoint))
	switch {
	case enable && !isEnabled:
		sp.AddConstraint(p.cursorJoint)
	case !enable && isEnabled:
		sp.RemoveConstraint(p.cursorJoint)
	}
}

type ball struct {
	body  chipmunk.Body
	shape chipmunk.Shape
}

type Player struct {
	Pos  chipmunk.Vect
	Team int
}

type Ball struct {
	Pos chipmunk.Vect
}

type configMsg struct {
	Id           string
	FieldWidth   float64
	FieldHeight  float64
	GoalSize     float64
	PlayerRadius float64
	BallRadius   float64
}

type stateMsg struct {
	Players map[string]Player
	Ball    Ball
	Score   []int
}

type Gol struct {
	players     map[gordian.ClientId]*player
	ball        ball
	score       []int
	pauseTicks  []int
	simTimer    <-chan time.Time
	updateTimer <-chan time.Time
	curId       int
	space       *chipmunk.Space
	mu          sync.Mutex
	*gordian.Gordian
}

func New() *Gol {
	g := &Gol{
		players:     map[gordian.ClientId]*player{},
		score:       []int{0, 0},
		pauseTicks:  []int{0, 0},
		simTimer:    time.Tick(simTime),
		updateTimer: time.Tick(updateTime),
		Gordian:     gordian.New(24),
	}
	g.setup()
	return g
}

func (g *Gol) setup() {
	g.space = chipmunk.SpaceNew()
	g.space.SetDamping(0.1)
	hfw, hfh, hgs := 0.5*fieldWidth, 0.5*fieldHeight, 0.5*goalSize
	sidePts := []chipmunk.Vect{{-hfw, hgs}, {-hfw, hfh}, {hfw, hfh}, {hfw, hgs}}
	numSideSegs := len(sidePts) - 1
	for i := 0; i < 2; i++ {
		sign := float64(2*i - 1)
		for j := 0; j < numSideSegs; j++ {
			p0, p1 := sidePts[j], sidePts[j+1]
			p0.Y *= sign
			p1.Y *= sign
			fieldSeg := chipmunk.SegmentShapeNew(g.space.StaticBody(), p0, p1, edgeRadius)
			fieldSeg.SetLayers(normLayer)
			fieldSeg.SetElasticity(1.0)
			fieldSeg.SetFriction(1.0)
			g.space.AddShape(fieldSeg)
		}
		p0, p1 := chipmunk.Vect{sign * hfw, -hgs}, chipmunk.Vect{sign * hfw, hgs}
		goal := chipmunk.SegmentShapeNew(g.space.StaticBody(), p0, p1, edgeRadius)
		goal.SetLayers(goalLayer)
		goal.SetElasticity(1.0)
		goal.SetFriction(1.0)
		g.space.AddShape(goal)
	}
	moment := chipmunk.MomentForCircle(ballMass, 0, ballRadius, chipmunk.Origin())
	g.ball.body = chipmunk.BodyNew(ballMass, moment)
	g.space.AddBody(g.ball.body)
	g.ball.shape = chipmunk.CircleShapeNew(g.ball.body, ballRadius, chipmunk.Origin())
	g.ball.shape.SetLayers(normLayer)
	g.ball.shape.SetElasticity(0.9)
	g.ball.shape.SetFriction(0.1)
	g.space.AddShape(g.ball.shape)
}

func (g *Gol) Run() {
	go g.run()
	go g.sim()
	g.Gordian.Run()
}

func (g *Gol) run() {
	for {
		select {
		case client := <-g.Control:
			g.clientCtrl(client)
		case msg := <-g.InBox:
			g.handleMessage(&msg)
		case <-g.updateTimer:
			g.update()
		}
	}
}

func (g *Gol) sim() {
	for {
		<-g.simTimer

		g.mu.Lock()

		g.space.Step(simTime.Seconds())
		g.handlePauses()
		g.handleGoals()

		g.mu.Unlock()
	}
}

func (g *Gol) handlePauses() {
	// enable control if pause is ending
	for _, player := range g.players {
		if g.pauseTicks[player.team] == 1 {
			player.enableCursorJoint(true)
		}
	}
	// update pause countdown
	for i := range g.pauseTicks {
		if g.pauseTicks[i] > 0 {
			g.pauseTicks[i]--
		}
	}
}

func (g *Gol) handleGoals() {
	ballX := g.ball.body.Position().X
	if math.Abs(ballX) > fieldWidth/2 { // GOL!
		team := 0
		if ballX < 0 {
			team = 1
		}
		g.score[team]++
		if g.score[0] >= maxScore || g.score[1] >= maxScore {
			g.score[0], g.score[1] = 0, 0
		}
		g.kickoff(team)
	}
}

func (g *Gol) kickoff(team int) {
	otherTeam := 1 - team

	g.ball.body.SetPosition(chipmunk.Vect{})
	g.ball.body.SetVelocity(chipmunk.Vect{})
	for _, player := range g.players {
		player.place()
		player.body.SetVelocity(chipmunk.Vect{})
		player.enableCursorJoint(false) // disable control for a bit
	}
	// give the team that was scored on a little head start for "kickoff"
	g.pauseTicks[team] = int((pauseTime + headStartTime) / simTime)
	g.pauseTicks[otherTeam] = int(pauseTime / simTime)
}

func (g *Gol) clientCtrl(client *gordian.Client) {
	switch client.Ctrl {
	case gordian.Connect:
		g.connect(client)
	case gordian.Close:
		g.close(client)
	}
}

func (g *Gol) nextTeam() int {
	teamSize := []int{0, 0}
	for _, player := range g.players {
		teamSize[player.team]++
	}
	switch {
	case teamSize[0] < teamSize[1]:
		return 0
	case teamSize[0] > teamSize[1]:
		return 1
	default:
		return rand.Intn(2)
	}
}

func (g *Gol) addPlayer(id gordian.ClientId) *player {
	player := &player{id: id, team: g.nextTeam()}

	moment := chipmunk.MomentForCircle(playerMass, 0, playerRadius, chipmunk.Origin())
	player.body = chipmunk.BodyNew(playerMass, moment)
	g.space.AddBody(player.body)

	player.shape = chipmunk.CircleShapeNew(player.body, playerRadius, chipmunk.Origin())
	player.shape.SetLayers(normLayer | goalLayer)
	player.shape.SetElasticity(0.9)
	player.shape.SetFriction(0.1)
	g.space.AddShape(player.shape)

	player.cursorBody = chipmunk.BodyNew(math.Inf(0), math.Inf(0))
	player.cursorJoint = chipmunk.PivotJointNew2(player.cursorBody, player.body,
		chipmunk.Vect{}, chipmunk.Vect{})
	player.cursorJoint.SetMaxForce(1000.0)
	player.enableCursorJoint(true)

	g.players[player.id] = player

	return player
}

func (g *Gol) removePlayer(id gordian.ClientId) {
	player, ok := g.players[id]
	if !ok {
		return
	}
	player.enableCursorJoint(false)
	player.cursorJoint.Free()
	g.space.RemoveBody(player.body)
	g.space.RemoveShape(player.shape)
	player.body.Free()
	player.shape.Free()
	player.cursorBody.Free()

	delete(g.players, id)
}

func (g *Gol) connect(client *gordian.Client) {
	g.curId++

	client.Id = g.curId
	client.Ctrl = gordian.Register
	g.Control <- client
	client = <-g.Control
	if client.Ctrl != gordian.Establish {
		return
	}

	g.mu.Lock()

	player := g.addPlayer(client.Id)
	player.place()

	g.mu.Unlock()

	data := configMsg{
		FieldWidth:   fieldWidth,
		FieldHeight:  fieldHeight,
		GoalSize:     goalSize,
		PlayerRadius: playerRadius,
		BallRadius:   ballRadius,
		Id:           fmt.Sprintf("%d", client.Id),
	}
	msg := gordian.Message{
		To:   client.Id,
		Type: "config",
		Data: data,
	}
	g.OutBox <- msg
}

func (g *Gol) close(client *gordian.Client) {
	g.mu.Lock()
	defer g.mu.Unlock()

	g.removePlayer(client.Id)
}

func (g *Gol) handleMessage(msg *gordian.Message) {
	g.mu.Lock()
	defer g.mu.Unlock()

	id := msg.From
	player, ok := g.players[id]
	if !ok {
		return
	}
	switch msg.Type {
	case "player":
		state := &Player{}
		err := msg.Unmarshal(state)
		if err != nil {
			return
		}
		player.cursorBody.SetPosition(state.Pos)
	}
}

func (g *Gol) update() {
	g.mu.Lock()

	state := stateMsg{
		Players: map[string]Player{},
		Ball:    Ball{g.ball.body.Position()},
		Score:   g.score,
	}
	for i, player := range g.players {
		state.Players[fmt.Sprintf("%d", i)] = Player{
			Pos:  player.body.Position(),
			Team: player.team,
		}
	}

	g.mu.Unlock()

	msg := gordian.Message{
		Type: "state",
		Data: state,
	}
	for id := range g.players {
		msg.To = id
		g.OutBox <- msg
	}
}
<commit_msg>Changed uses of Origin() to Vect{}.<commit_after>package gol

import (
	"github.com/ianremmler/chipmunk"
	"github.com/ianremmler/gordian"

	"fmt"
	"math"
	"math/rand"
	"sync"
	"time"
)

const (
	simTime       = time.Second / 60
	updateTime    = time.Second / 24
	pauseTime     = time.Second
	headStartTime = time.Second
	maxScore      = 10
	fieldWidth    = 1000
	fieldHeight   = 500
	edgeRadius    = 8
	goalSize      = 200
	playerRadius  = 10
	playerMass    = 1
	ballRadius    = 10
	ballMass      = 0.1
)

const (
	normLayer = 1 << iota
	goalLayer
)

type player struct {
	id          gordian.ClientId
	team        int
	body        chipmunk.Body
	shape       chipmunk.Shape
	cursorBody  chipmunk.Body
	cursorJoint chipmunk.Constraint
}

func (p *player) place() {
	widthRange := 0.5*fieldWidth - 2*playerRadius
	heightRange := 0.5*fieldHeight - playerRadius
	sign := float64(2*p.team - 1)
	pos := chipmunk.Vect{
		(widthRange*rand.Float64() + playerRadius) * sign,
		heightRange * (2*rand.Float64() - 1),
	}
	minDist := 0.25*fieldHeight + playerRadius
	dist := pos.Length()
	if dist < minDist {
		pos = pos.Mul(minDist / dist)
	}
	p.body.SetPosition(pos)
}

func (p *player) enableCursorJoint(enable bool) {
	sp := p.body.Space()
	isEnabled := sp.Contains(p.cursorJoint.(chipmunk.PivotJoint))
	switch {
	case enable && !isEnabled:
		sp.AddConstraint(p.cursorJoint)
	case !enable && isEnabled:
		sp.RemoveConstraint(p.cursorJoint)
	}
}

type ball struct {
	body  chipmunk.Body
	shape chipmunk.Shape
}

type Player struct {
	Pos  chipmunk.Vect
	Team int
}

type Ball struct {
	Pos chipmunk.Vect
}

type configMsg struct {
	Id           string
	FieldWidth   float64
	FieldHeight  float64
	GoalSize     float64
	PlayerRadius float64
	BallRadius   float64
}

type stateMsg struct {
	Players map[string]Player
	Ball    Ball
	Score   []int
}

type Gol struct {
	players     map[gordian.ClientId]*player
	ball        ball
	score       []int
	pauseTicks  []int
	simTimer    <-chan time.Time
	updateTimer <-chan time.Time
	curId       int
	space       *chipmunk.Space
	mu          sync.Mutex
	*gordian.Gordian
}

func New() *Gol {
	g := &Gol{
		players:     map[gordian.ClientId]*player{},
		score:       []int{0, 0},
		pauseTicks:  []int{0, 0},
		simTimer:    time.Tick(simTime),
		updateTimer: time.Tick(updateTime),
		Gordian:     gordian.New(24),
	}
	g.setup()
	return g
}

func (g *Gol) setup() {
	g.space = chipmunk.SpaceNew()
	g.space.SetDamping(0.1)
	hfw, hfh, hgs := 0.5*fieldWidth, 0.5*fieldHeight, 0.5*goalSize
	sidePts := []chipmunk.Vect{{-hfw, hgs}, {-hfw, hfh}, {hfw, hfh}, {hfw, hgs}}
	numSideSegs := len(sidePts) - 1
	for i := 0; i < 2; i++ {
		sign := float64(2*i - 1)
		for j := 0; j < numSideSegs; j++ {
			p0, p1 := sidePts[j], sidePts[j+1]
			p0.Y *= sign
			p1.Y *= sign
			fieldSeg := chipmunk.SegmentShapeNew(g.space.StaticBody(), p0, p1, edgeRadius)
			fieldSeg.SetLayers(normLayer)
			fieldSeg.SetElasticity(1.0)
			fieldSeg.SetFriction(1.0)
			g.space.AddShape(fieldSeg)
		}
		p0, p1 := chipmunk.Vect{sign * hfw, -hgs}, chipmunk.Vect{sign * hfw, hgs}
		goal := chipmunk.SegmentShapeNew(g.space.StaticBody(), p0, p1, edgeRadius)
		goal.SetLayers(goalLayer)
		goal.SetElasticity(1.0)
		goal.SetFriction(1.0)
		g.space.AddShape(goal)
	}
	moment := chipmunk.MomentForCircle(ballMass, 0, ballRadius, chipmunk.Vect{})
	g.ball.body = chipmunk.BodyNew(ballMass, moment)
	g.space.AddBody(g.ball.body)
	g.ball.shape = chipmunk.CircleShapeNew(g.ball.body, ballRadius, chipmunk.Vect{})
	g.ball.shape.SetLayers(normLayer)
	g.ball.shape.SetElasticity(0.9)
	g.ball.shape.SetFriction(0.1)
	g.space.AddShape(g.ball.shape)
}

func (g *Gol) Run() {
	go g.run()
	go g.sim()
	g.Gordian.Run()
}

func (g *Gol) run() {
	for {
		select {
		case client := <-g.Control:
			g.clientCtrl(client)
		case msg := <-g.InBox:
			g.handleMessage(&msg)
		case <-g.updateTimer:
			g.update()
		}
	}
}

func (g *Gol) sim() {
	for {
		<-g.simTimer

		g.mu.Lock()

		g.space.Step(simTime.Seconds())
		g.handlePauses()
		g.handleGoals()

		g.mu.Unlock()
	}
}

func (g *Gol) handlePauses() {
	// enable control if pause is ending
	for _, player := range g.players {
		if g.pauseTicks[player.team] == 1 {
			player.enableCursorJoint(true)
		}
	}
	// update pause countdown
	for i := range g.pauseTicks {
		if g.pauseTicks[i] > 0 {
			g.pauseTicks[i]--
		}
	}
}

func (g *Gol) handleGoals() {
	ballX := g.ball.body.Position().X
	if math.Abs(ballX) > fieldWidth/2 { // GOL!
		team := 0
		if ballX < 0 {
			team = 1
		}
		g.score[team]++
		if g.score[0] >= maxScore || g.score[1] >= maxScore {
			g.score[0], g.score[1] = 0, 0
		}
		g.kickoff(team)
	}
}

func (g *Gol) kickoff(team int) {
	otherTeam := 1 - team

	g.ball.body.SetPosition(chipmunk.Vect{})
	g.ball.body.SetVelocity(chipmunk.Vect{})
	for _, player := range g.players {
		player.place()
		player.body.SetVelocity(chipmunk.Vect{})
		player.enableCursorJoint(false) // disable control for a bit
	}
	// give the team that was scored on a little head start for "kickoff"
	g.pauseTicks[team] = int((pauseTime + headStartTime) / simTime)
	g.pauseTicks[otherTeam] = int(pauseTime / simTime)
}

func (g *Gol) clientCtrl(client *gordian.Client) {
	switch client.Ctrl {
	case gordian.Connect:
		g.connect(client)
	case gordian.Close:
		g.close(client)
	}
}

func (g *Gol) nextTeam() int {
	teamSize := []int{0, 0}
	for _, player := range g.players {
		teamSize[player.team]++
	}
	switch {
	case teamSize[0] < teamSize[1]:
		return 0
	case teamSize[0] > teamSize[1]:
		return 1
	default:
		return rand.Intn(2)
	}
}

func (g *Gol) addPlayer(id gordian.ClientId) *player {
	player := &player{id: id, team: g.nextTeam()}

	moment := chipmunk.MomentForCircle(playerMass, 0, playerRadius, chipmunk.Vect{})
	player.body = chipmunk.BodyNew(playerMass, moment)
	g.space.AddBody(player.body)

	player.shape = chipmunk.CircleShapeNew(player.body, playerRadius, chipmunk.Vect{})
	player.shape.SetLayers(normLayer | goalLayer)
	player.shape.SetElasticity(0.9)
	player.shape.SetFriction(0.1)
	g.space.AddShape(player.shape)

	player.cursorBody = chipmunk.BodyNew(math.Inf(0), math.Inf(0))
	player.cursorJoint = chipmunk.PivotJointNew2(player.cursorBody, player.body,
		chipmunk.Vect{}, chipmunk.Vect{})
	player.cursorJoint.SetMaxForce(1000.0)
	player.enableCursorJoint(true)

	g.players[player.id] = player

	return player
}

func (g *Gol) removePlayer(id gordian.ClientId) {
	player, ok := g.players[id]
	if !ok {
		return
	}
	player.enableCursorJoint(false)
	player.cursorJoint.Free()
	g.space.RemoveBody(player.body)
	g.space.RemoveShape(player.shape)
	player.body.Free()
	player.shape.Free()
	player.cursorBody.Free()

	delete(g.players, id)
}

func (g *Gol) connect(client *gordian.Client) {
	g.curId++

	client.Id = g.curId
	client.Ctrl = gordian.Register
	g.Control <- client
	client = <-g.Control
	if client.Ctrl != gordian.Establish {
		return
	}

	g.mu.Lock()

	player := g.addPlayer(client.Id)
	player.place()

	g.mu.Unlock()

	data := configMsg{
		FieldWidth:   fieldWidth,
		FieldHeight:  fieldHeight,
		GoalSize:     goalSize,
		PlayerRadius: playerRadius,
		BallRadius:   ballRadius,
		Id:           fmt.Sprintf("%d", client.Id),
	}
	msg := gordian.Message{
		To:   client.Id,
		Type: "config",
		Data: data,
	}
	g.OutBox <- msg
}

func (g *Gol) close(client *gordian.Client) {
	g.mu.Lock()
	defer g.mu.Unlock()

	g.removePlayer(client.Id)
}

func (g *Gol) handleMessage(msg *gordian.Message) {
	g.mu.Lock()
	defer g.mu.Unlock()

	id := msg.From
	player, ok := g.players[id]
	if !ok {
		return
	}
	switch msg.Type {
	case "player":
		state := &Player{}
		err := msg.Unmarshal(state)
		if err != nil {
			return
		}
		player.cursorBody.SetPosition(state.Pos)
	}
}

func (g *Gol) update() {
	g.mu.Lock()

	state := stateMsg{
		Players: map[string]Player{},
		Ball:    Ball{g.ball.body.Position()},
		Score:   g.score,
	}
	for i, player := range g.players {
		state.Players[fmt.Sprintf("%d", i)] = Player{
			Pos:  player.body.Position(),
			Team: player.team,
		}
	}

	g.mu.Unlock()

	msg := gordian.Message{
		Type: "state",
		Data: state,
	}
	for id := range g.players {
		msg.To = id
		g.OutBox <- msg
	}
}
<|endoftext|>