<commit_before>/**
 * Copyright 2016 IBM Corp.
 *
 * 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 session

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"strings"

	"github.ibm.com/riethm/gopherlayer.git/sl"
)

func doRestRequest(sess *Session, service string, method string, args []interface{}, options *sl.Options, pResult interface{}) error {
	restMethod := httpMethod(method, args)

	// Parse any method parameters and determine the HTTP method
	var parameters []byte
	if len(args) > 0 {
		// parse the parameters
		parameters, _ = json.Marshal(
			map[string]interface{}{
				"parameters": args,
			})
	}

	// Start building the request path
	path := service

	if options.Id != nil {
		path = path + "/" + strconv.Itoa(*options.Id)
	}

	// omit the API method name if the method represents one of the basic REST methods
	if method != "getObject" && method != "deleteObject" && method != "createObject" &&
		method != "createObjects" && method != "editObject" && method != "editObjects" {
		path = path + "/" + method
	}

	path = path + ".json"

	resp, code, err := makeHTTPRequest(
		sess,
		path,
		restMethod,
		bytes.NewBuffer(parameters),
		options)

	if err != nil {
		return fmt.Errorf("Error during HTTP request: %s", err)
	}

	if code < 200 || code > 299 {
		e := sl.Error{StatusCode: code}

		err = json.Unmarshal(resp, &e)

		// If unparseable, wrap the json error
		if err != nil {
			e.Wrapped = err
			e.Message = err.Error()
		}

		return e
	}

	returnType := reflect.TypeOf(pResult).String()

	// Some APIs that normally return a collection, omit the []'s when the API returns a single value
	if strings.Index(returnType, "[]") == 1 && strings.Index(string(resp), "[") != 0 {
		resp = []byte("[" + string(resp) + "]")
	}

	// At this point, all that's left to do is parse the return value to the appropriate type, and return
	// any parse errors (or nil if successful)

	err = nil
	switch returnType {
	case "[]byte":
		pResult = &resp
	case "*void":
	case "*uint":
		*pResult.(*int), err = strconv.Atoi(string(resp))
	case "*bool":
		*pResult.(*bool), err = strconv.ParseBool(string(resp))
	case "string":
		*pResult.(*string) = string(resp)
	default:
		// Must be a json representation of one of the many softlayer datatypes
		err = json.Unmarshal(resp, pResult)
	}

	if err != nil {
		err = sl.Error{Message: err.Error(), Wrapped: err}
	}

	return err
}

func encodeQuery(opts *sl.Options) string {
	query := new(url.URL).Query()

	if opts.Mask != "" {
		query.Add("objectMask", opts.Mask)
	}

	if opts.Filter != "" {
		query.Add("objectFilter", opts.Filter)
	}

	// resultLimit=<offset>,<limit>
	// If offset unspecified, default to 0
	if opts.Limit != nil {
		startOffset := 0
		if opts.Offset != nil {
			startOffset = *opts.Offset
		}

		query.Add("resultLimit", fmt.Sprintf("%d,%d", startOffset, *opts.Limit))
	}

	return query.Encode()
}

func makeHTTPRequest(session *Session, path string, requestType string, requestBody *bytes.Buffer, options *sl.Options) ([]byte, int, error) {
	client := http.DefaultClient

	var url string
	if session.Endpoint == "" {
		url = url + DefaultEndpoint
	} else {
		url = url + session.Endpoint
	}
	url = url + "/" + path
	req, err := http.NewRequest(requestType, url, requestBody)
	if err != nil {
		return nil, 0, err
	}

	req.SetBasicAuth(session.UserName, session.APIKey)

	req.URL.RawQuery = encodeQuery(options)

	if session.Debug {
		log.Println("[DEBUG] Path: ", req.URL)
		log.Println("[DEBUG] Parameters: ", requestBody.String())
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, 520, err
	}

	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, resp.StatusCode, err
	}

	return responseBody, resp.StatusCode, nil
}

func httpMethod(name string, args []interface{}) string {
	if name == "deleteObject" {
		return "DELETE"
	} else if name == "editObject" || name == "editObjects" {
		return "PUT"
	} else if name == "createObject" || name == "createObjects" || len(args) > 0 {
		return "POST"
	}

	return "GET"
}
<commit_msg>Use type switch instead of reflection for determining return type<commit_after>/**
 * Copyright 2016 IBM Corp.
 *
 * 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 session

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"strings"

	"github.ibm.com/riethm/gopherlayer.git/datatypes"
	"github.ibm.com/riethm/gopherlayer.git/sl"
)

func doRestRequest(sess *Session, service string, method string, args []interface{}, options *sl.Options, pResult interface{}) error {
	restMethod := httpMethod(method, args)

	// Parse any method parameters and determine the HTTP method
	var parameters []byte
	if len(args) > 0 {
		// parse the parameters
		parameters, _ = json.Marshal(
			map[string]interface{}{
				"parameters": args,
			})
	}

	// Start building the request path
	path := service

	if options.Id != nil {
		path = path + "/" + strconv.Itoa(*options.Id)
	}

	// omit the API method name if the method represents one of the basic REST methods
	if method != "getObject" && method != "deleteObject" && method != "createObject" &&
		method != "createObjects" && method != "editObject" && method != "editObjects" {
		path = path + "/" + method
	}

	path = path + ".json"

	resp, code, err := makeHTTPRequest(
		sess,
		path,
		restMethod,
		bytes.NewBuffer(parameters),
		options)

	if err != nil {
		return fmt.Errorf("Error during HTTP request: %s", err)
	}

	if code < 200 || code > 299 {
		e := sl.Error{StatusCode: code}

		err = json.Unmarshal(resp, &e)

		// If unparseable, wrap the json error
		if err != nil {
			e.Wrapped = err
			e.Message = err.Error()
		}

		return e
	}

	// Some APIs that normally return a collection, omit the []'s when the API returns a single value
	returnType := reflect.TypeOf(pResult).String()
	if strings.Index(returnType, "[]") == 1 && strings.Index(string(resp), "[") != 0 {
		resp = []byte("[" + string(resp) + "]")
	}

	// At this point, all that's left to do is parse the return value to the appropriate type, and return
	// any parse errors (or nil if successful)

	err = nil
	switch pResult.(type) {
	case []uint8:
		pResult = resp
	case *datatypes.Void:
	case *uint:
		*pResult.(*int), err = strconv.Atoi(string(resp))
	case *bool:
		*pResult.(*bool), err = strconv.ParseBool(string(resp))
	case *string:
		*pResult.(*string) = string(resp)
	default:
		// Must be a json representation of one of the many softlayer datatypes
		err = json.Unmarshal(resp, pResult)
	}

	if err != nil {
		err = sl.Error{Message: err.Error(), Wrapped: err}
	}

	return err
}

func encodeQuery(opts *sl.Options) string {
	query := new(url.URL).Query()

	if opts.Mask != "" {
		query.Add("objectMask", opts.Mask)
	}

	if opts.Filter != "" {
		query.Add("objectFilter", opts.Filter)
	}

	// resultLimit=<offset>,<limit>
	// If offset unspecified, default to 0
	if opts.Limit != nil {
		startOffset := 0
		if opts.Offset != nil {
			startOffset = *opts.Offset
		}

		query.Add("resultLimit", fmt.Sprintf("%d,%d", startOffset, *opts.Limit))
	}

	return query.Encode()
}

func makeHTTPRequest(session *Session, path string, requestType string, requestBody *bytes.Buffer, options *sl.Options) ([]byte, int, error) {
	client := http.DefaultClient

	var url string
	if session.Endpoint == "" {
		url = url + DefaultEndpoint
	} else {
		url = url + session.Endpoint
	}
	url = url + "/" + path
	req, err := http.NewRequest(requestType, url, requestBody)
	if err != nil {
		return nil, 0, err
	}

	req.SetBasicAuth(session.UserName, session.APIKey)

	req.URL.RawQuery = encodeQuery(options)

	if session.Debug {
		log.Println("[DEBUG] Path: ", req.URL)
		log.Println("[DEBUG] Parameters: ", requestBody.String())
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, 520, err
	}

	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, resp.StatusCode, err
	}

	return responseBody, resp.StatusCode, nil
}

func httpMethod(name string, args []interface{}) string {
	if name == "deleteObject" {
		return "DELETE"
	} else if name == "editObject" || name == "editObjects" {
		return "PUT"
	} else if name == "createObject" || name == "createObjects" || len(args) > 0 {
		return "POST"
	}

	return "GET"
}
<|endoftext|>


<commit_before>package manifest

import (
	"encoding/json"

	"github.com/docker/distribution/digest"
	"github.com/docker/libtrust"
)

// TODO(stevvooe): When we rev the manifest format, the contents of this
// package should me moved to manifest/v1.

const (
	// ManifestMediaType specifies the mediaType for the current version. Note
	// that for schema version 1, the the media is optionally
	// "application/json".
	ManifestMediaType = "application/vnd.docker.distribution.manifest.v1+json"
)

// Versioned provides a struct with just the manifest schemaVersion. Incoming
// content with unknown schema version can be decoded against this struct to
// check the version.
type Versioned struct {
	// SchemaVersion is the image manifest schema that this image follows
	SchemaVersion int `json:"schemaVersion"`
}

// Manifest provides the base accessible fields for working with V2 image
// format in the registry.
type Manifest struct {
	Versioned

	// Name is the name of the image's repository
	Name string `json:"name"`

	// Tag is the tag of the image specified by this manifest
	Tag string `json:"tag"`

	// Architecture is the host architecture on which this image is intended to
	// run
	Architecture string `json:"architecture"`

	// FSLayers is a list of filesystem layer blobSums contained in this image
	FSLayers []FSLayer `json:"fsLayers"`

	// History is a list of unstructured historical data for v1 compatibility
	History []History `json:"history"`
}

// SignedManifest provides an envelope for a signed image manifest, including
// the format sensitive raw bytes. It contains fields to
type SignedManifest struct {
	Manifest

	// Raw is the byte representation of the ImageManifest, used for signature
	// verification. The value of Raw must be used directly during
	// serialization, or the signature check will fail. The manifest byte
	// representation cannot change or it will have to be re-signed.
	Raw []byte `json:"-"`
}

// UnmarshalJSON populates a new ImageManifest struct from JSON data.
func (sm *SignedManifest) UnmarshalJSON(b []byte) error {
	var manifest Manifest
	if err := json.Unmarshal(b, &manifest); err != nil {
		return err
	}

	sm.Manifest = manifest
	sm.Raw = make([]byte, len(b), len(b))
	copy(sm.Raw, b)

	return nil
}

// Payload returns the raw, signed content of the signed manifest. The
// contents can be used to calculate the content identifier.
func (sm *SignedManifest) Payload() ([]byte, error) {
	jsig, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
	if err != nil {
		return nil, err
	}

	// Resolve the payload in the manifest.
	return jsig.Payload()
}

// Signatures returns the signatures as provided by
// (*libtrust.JSONSignature).Signatures. The byte slices are opaque jws
// signatures.
func (sm *SignedManifest) Signatures() ([][]byte, error) {
	jsig, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
	if err != nil {
		return nil, err
	}

	// Resolve the payload in the manifest.
	return jsig.Signatures()
}

// MarshalJSON returns the contents of raw. If Raw is nil, marshals the inner
// contents. Applications requiring a marshaled signed manifest should simply
// use Raw directly, since the the content produced by json.Marshal will be
// compacted and will fail signature checks.
func (sm *SignedManifest) MarshalJSON() ([]byte, error) {
	if len(sm.Raw) > 0 {
		return sm.Raw, nil
	}

	// If the raw data is not available, just dump the inner content.
	return json.Marshal(&sm.Manifest)
}

// FSLayer is a container struct for BlobSums defined in an image manifest
type FSLayer struct {
	// BlobSum is the tarsum of the referenced filesystem image layer
	BlobSum digest.Digest `json:"blobSum"`
}

// History stores unstructured v1 compatibility information
type History struct {
	// V1Compatibility is the raw v1 compatibility information
	V1Compatibility string `json:"v1Compatibility"`
}
<commit_msg>Fix typo<commit_after>package manifest

import (
	"encoding/json"

	"github.com/docker/distribution/digest"
	"github.com/docker/libtrust"
)

// TODO(stevvooe): When we rev the manifest format, the contents of this
// package should be moved to manifest/v1.

const (
	// ManifestMediaType specifies the mediaType for the current version. Note
	// that for schema version 1, the the media is optionally
	// "application/json".
	ManifestMediaType = "application/vnd.docker.distribution.manifest.v1+json"
)

// Versioned provides a struct with just the manifest schemaVersion. Incoming
// content with unknown schema version can be decoded against this struct to
// check the version.
type Versioned struct {
	// SchemaVersion is the image manifest schema that this image follows
	SchemaVersion int `json:"schemaVersion"`
}

// Manifest provides the base accessible fields for working with V2 image
// format in the registry.
type Manifest struct {
	Versioned

	// Name is the name of the image's repository
	Name string `json:"name"`

	// Tag is the tag of the image specified by this manifest
	Tag string `json:"tag"`

	// Architecture is the host architecture on which this image is intended to
	// run
	Architecture string `json:"architecture"`

	// FSLayers is a list of filesystem layer blobSums contained in this image
	FSLayers []FSLayer `json:"fsLayers"`

	// History is a list of unstructured historical data for v1 compatibility
	History []History `json:"history"`
}

// SignedManifest provides an envelope for a signed image manifest, including
// the format sensitive raw bytes. It contains fields to
type SignedManifest struct {
	Manifest

	// Raw is the byte representation of the ImageManifest, used for signature
	// verification. The value of Raw must be used directly during
	// serialization, or the signature check will fail. The manifest byte
	// representation cannot change or it will have to be re-signed.
	Raw []byte `json:"-"`
}

// UnmarshalJSON populates a new ImageManifest struct from JSON data.
func (sm *SignedManifest) UnmarshalJSON(b []byte) error {
	var manifest Manifest
	if err := json.Unmarshal(b, &manifest); err != nil {
		return err
	}

	sm.Manifest = manifest
	sm.Raw = make([]byte, len(b), len(b))
	copy(sm.Raw, b)

	return nil
}

// Payload returns the raw, signed content of the signed manifest. The
// contents can be used to calculate the content identifier.
func (sm *SignedManifest) Payload() ([]byte, error) {
	jsig, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
	if err != nil {
		return nil, err
	}

	// Resolve the payload in the manifest.
	return jsig.Payload()
}

// Signatures returns the signatures as provided by
// (*libtrust.JSONSignature).Signatures. The byte slices are opaque jws
// signatures.
func (sm *SignedManifest) Signatures() ([][]byte, error) {
	jsig, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
	if err != nil {
		return nil, err
	}

	// Resolve the payload in the manifest.
	return jsig.Signatures()
}

// MarshalJSON returns the contents of raw. If Raw is nil, marshals the inner
// contents. Applications requiring a marshaled signed manifest should simply
// use Raw directly, since the the content produced by json.Marshal will be
// compacted and will fail signature checks.
func (sm *SignedManifest) MarshalJSON() ([]byte, error) {
	if len(sm.Raw) > 0 {
		return sm.Raw, nil
	}

	// If the raw data is not available, just dump the inner content.
	return json.Marshal(&sm.Manifest)
}

// FSLayer is a container struct for BlobSums defined in an image manifest
type FSLayer struct {
	// BlobSum is the tarsum of the referenced filesystem image layer
	BlobSum digest.Digest `json:"blobSum"`
}

// History stores unstructured v1 compatibility information
type History struct {
	// V1Compatibility is the raw v1 compatibility information
	V1Compatibility string `json:"v1Compatibility"`
}
<|endoftext|>


<commit_before>// Package fastsql is a library which extends Go's standard database/sql library.  It provides performance that's easy to take advantage of.
//
// Even better, the fastsql.DB object embeds the standard sql.DB object meaning access to all the standard database/sql library functionality is preserved.  It also means that integrating fastsql into existing codebases is a breeze.
//
// Additional functionality inclues:
//
// 1. Easy, readable, and performant batch insert queries using the BatchInsert method.
// 2. Automatic creation and re-use of prepared statements.
// 3. A convenient holder for manually used prepared statements.
package fastsql

import (
	"database/sql"
	"strings"
	"sync"
)

// DB is a database handle that embeds the standard library's sql.DB struct.
//
//This means the fastsql.DB struct has, and allows, access to all of the standard library functionality while also providng a superset of functionality such as batch operations, autmatically created prepared statmeents, and more.
type DB struct {
	*sql.DB
	PreparedStatements map[string]*sql.Stmt
	prepstmts          map[string]*sql.Stmt
	driverName         string
	flushInterval      uint
	batchInserts       map[string]*insert
}

// Close is the same a sql.Close, but first closes any opened prepared statements.
func (d *DB) Close() error {
	var (
		wg sync.WaitGroup
	)

	if err := d.FlushAll(); err != nil {
		return err
	}

	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()

		for _, stmt := range d.PreparedStatements {
			_ = stmt.Close()
		}
	}(&wg)

	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()

		for _, stmt := range d.prepstmts {
			_ = stmt.Close()
		}
	}(&wg)

	wg.Wait()
	return d.DB.Close()
}

// Open is the same as sql.Open, but returns an *fastsql.DB instead.
func Open(driverName, dataSourceName string, flushInterval uint) (*DB, error) {
	var (
		err error
		dbh *sql.DB
	)

	if dbh, err = sql.Open(driverName, dataSourceName); err != nil {
		return nil, err
	}

	return &DB{
		DB:                 dbh,
		PreparedStatements: make(map[string]*sql.Stmt),
		prepstmts:          make(map[string]*sql.Stmt),
		driverName:         driverName,
		flushInterval:      flushInterval,
		batchInserts:       make(map[string]*insert),
	}, err
}

// BatchInsert takes a singlular INSERT query and converts it to a batch-insert query for the caller.  A batch-insert is ran every time BatchInsert is called a multiple of flushInterval times.
func (d *DB) BatchInsert(query string, params ...interface{}) (err error) {
	if _, ok := d.batchInserts[query]; !ok {
		d.batchInserts[query] = newInsert()
	} //if

	// Only split out query the first time Insert is called
	if d.batchInserts[query].queryPart1 == "" {
		d.batchInserts[query].splitQuery(query)
	}

	d.batchInserts[query].insertCtr++

	// Build VALUES seciton of query and add to parameter slice
	d.batchInserts[query].values += d.batchInserts[query].queryPart2
	d.batchInserts[query].bindParams = append(d.batchInserts[query].bindParams, params...)

	// If the batch interval has been hit, execute a batch insert
	if d.batchInserts[query].insertCtr >= d.flushInterval {
		err = d.flushInsert(d.batchInserts[query])
	} //if

	return err
}

func (d *DB) FlushAll() error {
	for _, in := range d.batchInserts {
		if err := d.flushInsert(in); err != nil {
			return err
		}
	}

	return nil
}

// flushInsert performs the acutal batch-insert query.
func (d *DB) flushInsert(in *insert) (err error) {
	var (
		query string = in.queryPart1 + in.values[:len(in.values)-1]
	)

	// Prepare query
	if _, ok := d.prepstmts[query]; !ok {
		if stmt, err := d.DB.Prepare(query); err == nil {
			d.prepstmts[query] = stmt
		} else {
			return err
		}
	}

	// Executate batch insert
	if _, err = d.prepstmts[query].Exec(in.bindParams...); err != nil {
		return err
	} //if

	// Reset vars
	in.values = " VALUES"
	in.bindParams = make([]interface{}, 0)
	in.insertCtr = 0

	return err
}

func (d *DB) setDB(dbh *sql.DB) (err error) {
	if err = dbh.Ping(); err != nil {
		return err
	}

	d.DB = dbh
	return nil
}

type insert struct {
	bindParams []interface{}
	insertCtr  uint
	queryPart1 string
	queryPart2 string
	queryPart3 string
	values     string
}

func newInsert() *insert {
	return &insert{
		bindParams: make([]interface{}, 0),
		values:     " VALUES",
	}
}

func (in *insert) splitQuery(query string) {
	var (
		ndxParens, ndxValues, ndxOnDupe int
	) //var

	// Normalize and split query
	query = strings.ToLower(query)
	ndxValues = strings.Index(query, "values")
	ndxOnDupe = strings.LastIndex(query, "on duplicate key update")
	ndxParens = strings.LastIndex(query, ")")

	if ndxOnDupe != -1 {
		//If On Duplicate cause exists, separate into 3 parts
		in.queryPart1 = strings.TrimSpace(query[:ndxValues])
		in.queryPart2 = query[ndxValues+6:ndxOnDupe-1] + ","
		in.queryPart3 = query[ndxOnDupe:]
	} else {
		//If On Duplicate does not exist, seperate into 2 parts
		in.queryPart1 = strings.TrimSpace(query[:ndxValues])
		in.queryPart2 = query[ndxValues+6:ndxParens+1] + ","
	}
}
<commit_msg>Minor cleanup #16<commit_after>// Package fastsql is a library which extends Go's standard database/sql library.  It provides performance that's easy to take advantage of.
//
// Even better, the fastsql.DB object embeds the standard sql.DB object meaning access to all the standard database/sql library functionality is preserved.  It also means that integrating fastsql into existing codebases is a breeze.
//
// Additional functionality inclues:
//
// 1. Easy, readable, and performant batch insert queries using the BatchInsert method.
// 2. Automatic creation and re-use of prepared statements.
// 3. A convenient holder for manually used prepared statements.
package fastsql

import (
	"database/sql"
	"strings"
	"sync"
)

// DB is a database handle that embeds the standard library's sql.DB struct.
//
//This means the fastsql.DB struct has, and allows, access to all of the standard library functionality while also providng a superset of functionality such as batch operations, autmatically created prepared statmeents, and more.
type DB struct {
	*sql.DB
	PreparedStatements map[string]*sql.Stmt
	prepstmts          map[string]*sql.Stmt
	driverName         string
	flushInterval      uint
	batchInserts       map[string]*insert
}

// Close is the same a sql.Close, but first closes any opened prepared statements.
func (d *DB) Close() error {
	var (
		wg sync.WaitGroup
	)

	if err := d.FlushAll(); err != nil {
		return err
	}

	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()

		for _, stmt := range d.PreparedStatements {
			_ = stmt.Close()
		}
	}(&wg)

	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()

		for _, stmt := range d.prepstmts {
			_ = stmt.Close()
		}
	}(&wg)

	wg.Wait()
	return d.DB.Close()
}

// Open is the same as sql.Open, but returns an *fastsql.DB instead.
func Open(driverName, dataSourceName string, flushInterval uint) (*DB, error) {
	var (
		err error
		dbh *sql.DB
	)

	if dbh, err = sql.Open(driverName, dataSourceName); err != nil {
		return nil, err
	}

	return &DB{
		DB:                 dbh,
		PreparedStatements: make(map[string]*sql.Stmt),
		prepstmts:          make(map[string]*sql.Stmt),
		driverName:         driverName,
		flushInterval:      flushInterval,
		batchInserts:       make(map[string]*insert),
	}, err
}

// BatchInsert takes a singlular INSERT query and converts it to a batch-insert query for the caller.  A batch-insert is ran every time BatchInsert is called a multiple of flushInterval times.
func (d *DB) BatchInsert(query string, params ...interface{}) (err error) {
	if _, ok := d.batchInserts[query]; !ok {
		d.batchInserts[query] = newInsert()
	} //if

	// Only split out query the first time Insert is called
	if d.batchInserts[query].queryPart1 == "" {
		d.batchInserts[query].splitQuery(query)
	}

	d.batchInserts[query].insertCtr++

	// Build VALUES seciton of query and add to parameter slice
	d.batchInserts[query].values += d.batchInserts[query].queryPart2
	d.batchInserts[query].bindParams = append(d.batchInserts[query].bindParams, params...)

	// If the batch interval has been hit, execute a batch insert
	if d.batchInserts[query].insertCtr >= d.flushInterval {
		err = d.flushInsert(d.batchInserts[query])
	} //if

	return err
}

func (d *DB) FlushAll() error {
	for _, in := range d.batchInserts {
		if err := d.flushInsert(in); err != nil {
			return err
		}
	}

	return nil
}

// flushInsert performs the acutal batch-insert query.
func (d *DB) flushInsert(in *insert) (err error) {
	var (
		query string = in.queryPart1 + in.values[:len(in.values)-1]
	)

	// Prepare query
	if _, ok := d.prepstmts[query]; !ok {
		if stmt, err := d.DB.Prepare(query); err == nil {
			d.prepstmts[query] = stmt
		} else {
			return err
		}
	}

	// Executate batch insert
	if _, err = d.prepstmts[query].Exec(in.bindParams...); err != nil {
		return err
	} //if

	// Reset vars
	in.values = " VALUES"
	in.bindParams = make([]interface{}, 0)
	in.insertCtr = 0

	return err
}

func (d *DB) setDB(dbh *sql.DB) (err error) {
	if err = dbh.Ping(); err != nil {
		return err
	}

	d.DB = dbh
	return nil
}

type insert struct {
	bindParams []interface{}
	insertCtr  uint
	queryPart1 string
	queryPart2 string
	queryPart3 string
	values     string
}

func newInsert() *insert {
	return &insert{
		bindParams: make([]interface{}, 0),
		values:     " VALUES",
	}
}

func (in *insert) splitQuery(query string) {
	var (
		ndxParens, ndxValues, ndxOnDupe int
	) //var

	// Normalize and split query
	query = strings.ToLower(query)
	ndxValues = strings.Index(query, "values")
	ndxOnDupe = strings.LastIndex(query, "on duplicate key update")
	ndxParens = strings.LastIndex(query, ")")

	// Split out first part of query
	in.queryPart1 = strings.TrimSpace(query[:ndxValues])

	// If ON DUPLICATE cause exists, separate into 3 parts
	// If ON DUPLICATE does not exist, seperate into 2 parts
	if ndxOnDupe != -1 {
		in.queryPart2 = query[ndxValues+6:ndxOnDupe-1] + ","
		in.queryPart3 = query[ndxOnDupe:]
	} else {
		in.queryPart2 = query[ndxValues+6:ndxParens+1] + ","
	}
}
<|endoftext|>


<commit_before>// Package fastrand implements a cryptographically secure pseudorandom number
// generator. The generator is seeded using the system's default entropy
// source, and thereafter produces random values via repeated hashing. As a
// result, fastrand can generate randomness much faster than crypto/rand, and
// generation cannot fail.
package fastrand

import (
	"crypto/rand"
	"hash"
	"math"
	"sync"
	"unsafe"

	"github.com/minio/blake2b-simd"
)

// A randReader produces random values via repeated hashing. The entropy field
// is the concatenation of an initial seed and a 128-bit counter. Each time
// the entropy is hashed, the counter is incremented.
type randReader struct {
	entropy  []byte
	h        hash.Hash
	hashSize int
	buf      [32]byte
	mu       sync.Mutex
}

// Read fills b with random data. It always returns len(b), nil.
func (r *randReader) Read(b []byte) (int, error) {
	r.mu.Lock()
	for i := 0; i < len(b); i += r.hashSize {
		// Increment counter.
		*(*uint64)(unsafe.Pointer(&r.entropy[0]))++
		if *(*uint64)(unsafe.Pointer(&r.entropy[0])) == 0 {
			*(*uint64)(unsafe.Pointer(&r.entropy[8]))++
		}
		// Hash the counter + initial seed.
		r.h.Reset()
		r.h.Write(r.entropy)
		r.h.Sum(r.buf[:0])

		// Fill out 'b'.
		copy(b[i:], r.buf[:])
	}
	r.mu.Unlock()
	return len(b), nil
}

// Reader is a global, shared instance of a cryptographically strong pseudo-
// random generator. It uses blake2b as its hashing function. Reader is safe
// for concurrent use by multiple goroutines.
var Reader = func() *randReader {
	// Use 64 bytes in case the first 32 aren't completely random.
	base := make([]byte, 64)
	_, err := rand.Read(base)
	if err != nil {
		panic("fastrand: no entropy available")
	}
	e := blake2b.Sum256(base)
	return &randReader{
		entropy:  append(make([]byte, 16), e[:]...),
		h:        blake2b.New256(),
		hashSize: len(e),
	}
}()

// Read is a helper function that calls Reader.Read on b. It always fills b
// completely.
func Read(b []byte) { Reader.Read(b) }

// Bytes is a helper function that returns n bytes of random data.
func Bytes(n int) []byte {
	b := make([]byte, n)
	Read(b)
	return b
}

// Intn returns a uniform random value in [0,n). It panics if n <= 0.
func Intn(n int) int {
	if n <= 0 {
		panic("fastrand: argument to Intn is <= 0")
	}
	// To eliminate modulo bias, keep selecting at random until we fall within
	// a range that is evenly divisible by n.
	// NOTE: since n is at most math.MaxUint64/2, max is minimized when:
	//    n = math.MaxUint64/4 + 1 -> max = math.MaxUint64 - math.MaxUint64/4
	// This gives an expected 1.333 tries before choosing a value < max.
	max := math.MaxUint64 - math.MaxUint64%uint64(n)
	b := Bytes(8)
	r := *(*uint64)(unsafe.Pointer(&b[0]))
	for r >= max {
		Read(b)
		r = *(*uint64)(unsafe.Pointer(&b[0]))
	}
	return int(r % uint64(n))
}

// Perm returns a random permutation of the integers [0,n).
func Perm(n int) []int {
	m := make([]int, n)
	for i := 0; i < n; i++ {
		j := Intn(i + 1)
		m[i] = m[j]
		m[j] = i
	}
	return m
}
<commit_msg>remove pointless swap in Perm<commit_after>// Package fastrand implements a cryptographically secure pseudorandom number
// generator. The generator is seeded using the system's default entropy
// source, and thereafter produces random values via repeated hashing. As a
// result, fastrand can generate randomness much faster than crypto/rand, and
// generation cannot fail.
package fastrand

import (
	"crypto/rand"
	"hash"
	"math"
	"sync"
	"unsafe"

	"github.com/minio/blake2b-simd"
)

// A randReader produces random values via repeated hashing. The entropy field
// is the concatenation of an initial seed and a 128-bit counter. Each time
// the entropy is hashed, the counter is incremented.
type randReader struct {
	entropy  []byte
	h        hash.Hash
	hashSize int
	buf      [32]byte
	mu       sync.Mutex
}

// Read fills b with random data. It always returns len(b), nil.
func (r *randReader) Read(b []byte) (int, error) {
	r.mu.Lock()
	for i := 0; i < len(b); i += r.hashSize {
		// Increment counter.
		*(*uint64)(unsafe.Pointer(&r.entropy[0]))++
		if *(*uint64)(unsafe.Pointer(&r.entropy[0])) == 0 {
			*(*uint64)(unsafe.Pointer(&r.entropy[8]))++
		}
		// Hash the counter + initial seed.
		r.h.Reset()
		r.h.Write(r.entropy)
		r.h.Sum(r.buf[:0])

		// Fill out 'b'.
		copy(b[i:], r.buf[:])
	}
	r.mu.Unlock()
	return len(b), nil
}

// Reader is a global, shared instance of a cryptographically strong pseudo-
// random generator. It uses blake2b as its hashing function. Reader is safe
// for concurrent use by multiple goroutines.
var Reader = func() *randReader {
	// Use 64 bytes in case the first 32 aren't completely random.
	base := make([]byte, 64)
	_, err := rand.Read(base)
	if err != nil {
		panic("fastrand: no entropy available")
	}
	e := blake2b.Sum256(base)
	return &randReader{
		entropy:  append(make([]byte, 16), e[:]...),
		h:        blake2b.New256(),
		hashSize: len(e),
	}
}()

// Read is a helper function that calls Reader.Read on b. It always fills b
// completely.
func Read(b []byte) { Reader.Read(b) }

// Bytes is a helper function that returns n bytes of random data.
func Bytes(n int) []byte {
	b := make([]byte, n)
	Read(b)
	return b
}

// Intn returns a uniform random value in [0,n). It panics if n <= 0.
func Intn(n int) int {
	if n <= 0 {
		panic("fastrand: argument to Intn is <= 0")
	}
	// To eliminate modulo bias, keep selecting at random until we fall within
	// a range that is evenly divisible by n.
	// NOTE: since n is at most math.MaxUint64/2, max is minimized when:
	//    n = math.MaxUint64/4 + 1 -> max = math.MaxUint64 - math.MaxUint64/4
	// This gives an expected 1.333 tries before choosing a value < max.
	max := math.MaxUint64 - math.MaxUint64%uint64(n)
	b := Bytes(8)
	r := *(*uint64)(unsafe.Pointer(&b[0]))
	for r >= max {
		Read(b)
		r = *(*uint64)(unsafe.Pointer(&b[0]))
	}
	return int(r % uint64(n))
}

// Perm returns a random permutation of the integers [0,n).
func Perm(n int) []int {
	m := make([]int, n)
	for i := 1; i < n; i++ {
		j := Intn(i + 1)
		m[i] = m[j]
		m[j] = i
	}
	return m
}
<|endoftext|>


<commit_before>// Copyright 2017 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author:polaris	polaris@studygolang.com

package logic

import (
	. "db"
	"errors"
	"fmt"
	"math"
	"math/rand"
	"model"
	"strconv"
	"time"

	"github.com/go-xorm/xorm"
	"github.com/polaris1119/goutils"
	"github.com/polaris1119/times"
	"golang.org/x/net/context"
)

type MissionLogic struct{}

var DefaultMission = MissionLogic{}

// HasLoginMission 是否有今日登录奖励
func (self MissionLogic) HasLoginMission(ctx context.Context, me *model.Me) bool {
	// 还没有铜币，当然有可能是消耗尽了
	if me.Balance == 0 {
		// 初始资本没有领取，必须先领取
		if DefaultUserRich.Total(ctx, me.Uid) == 0 {
			return false
		}
	}

	userLoginMission := self.FindLoginMission(ctx, me)
	if userLoginMission == nil {
		return false
	}

	if userLoginMission.Uid == 0 {
		return true
	}

	// 今日是否领取
	if times.Format("Ymd") == strconv.Itoa(userLoginMission.Date) {
		return false
	}
	return true
}

// RedeemLoginAward 领取登录奖励
func (self MissionLogic) RedeemLoginAward(ctx context.Context, me *model.Me) error {
	objLog := GetLogger(ctx)

	mission := self.findMission(ctx, model.MissionTypeLogin)
	if mission.Id == 0 {
		objLog.Errorln("每日登录任务不存在")
		return errors.New("任务不存在")
	}

	userLoginMission := self.FindLoginMission(ctx, me)
	if userLoginMission == nil {
		objLog.Errorln("查询数据库失败")
		return errors.New("服务内部错误")
	}

	session := MasterDB.NewSession()
	defer session.Close()
	session.Begin()

	if userLoginMission.Uid == 0 {
		userLoginMission.Date = goutils.MustInt(time.Now().Format("20060102"))
		userLoginMission.Days = 1
		userLoginMission.TotalDays = 1
		userLoginMission.Award = mission.Min
		userLoginMission.Uid = me.Uid

		_, err := session.Insert(userLoginMission)
		if err != nil {
			session.Rollback()
			objLog.Errorln("insert user_login_mission error:", err)
			return errors.New("服务内部错误")
		}

	} else {
		today := goutils.MustInt(times.Format("Ymd"))
		if today == userLoginMission.Date {
			session.Rollback()
			return errors.New("今日已领取")
		}
		// 昨日是否领取了
		yesterday := goutils.MustInt(times.Format("Ymd", time.Now().Add(-86400*time.Second)))
		if yesterday != userLoginMission.Date {
			userLoginMission.Award = mission.Min
			userLoginMission.Days = 1
		} else {
			userLoginMission.Days++
			if userLoginMission.Award == mission.Max {
				userLoginMission.Award = mission.Min
			} else {
				award := userLoginMission.Award + rand.Intn(mission.Incr) + 1
				userLoginMission.Award = int(math.Min(float64(award), float64(mission.Max)))
			}
		}

		userLoginMission.Date = today
		userLoginMission.TotalDays++
		userLoginMission.UpdatedAt = time.Now()

		_, err := session.Where("uid=?", userLoginMission.Uid).Update(userLoginMission)
		if err != nil {
			session.Rollback()
			objLog.Errorln("update user_login_mission error:", err)
			return errors.New("服务内部错误")
		}
	}

	desc := times.Format("Ymd") + " 的每日登录奖励 " + strconv.Itoa(userLoginMission.Award) + " 铜币"
	err := self.changeUserBalance(session, me, model.MissionTypeLogin, userLoginMission.Award, desc)
	if err != nil {
		session.Rollback()
		objLog.Errorln("changeUserBalance error:", err)
		return errors.New("服务内部错误")
	}

	session.Commit()

	return nil
}

func (MissionLogic) FindLoginMission(ctx context.Context, me *model.Me) *model.UserLoginMission {
	objLog := GetLogger(ctx)

	userLoginMission := &model.UserLoginMission{}
	_, err := MasterDB.Where("uid=?", me.Uid).Get(userLoginMission)
	if err != nil {
		objLog.Errorln("MissionLogic FindLoginMission error:", err)
		return nil
	}

	return userLoginMission
}

// Complete 完成任务（非每日任务）
func (MissionLogic) Complete(ctx context.Context, me *model.Me, id interface{}) error {
	objLog := GetLogger(ctx)

	mission := &model.Mission{}
	_, err := MasterDB.Id(id).Get(mission)
	if err != nil {
		objLog.Errorln("MissionLogic FindLoginMission error:", err)
		return err
	}

	if mission.Id == 0 || mission.State != 0 {
		return errors.New("任务不存在或已过期")
	}

	user := DefaultUser.FindOne(ctx, "uid", me.Uid)
	desc := fmt.Sprintf("获得%s %d 铜币", model.BalanceTypeMap[mission.Type], mission.Fixed)
	DefaultUserRich.IncrUserRich(user, mission.Type, mission.Fixed, desc)

	return nil
}

func (MissionLogic) findMission(ctx context.Context, typ int) *model.Mission {
	mission := &model.Mission{}
	MasterDB.Where("type=?", typ).Get(mission)
	return mission
}

func (self MissionLogic) changeUserBalance(session *xorm.Session, me *model.Me, typ, award int, desc string) error {
	_, err := session.Where("uid=?", me.Uid).Incr("balance", award).Update(new(model.User))
	if err != nil {
		return errors.New("服务内部错误")
	}

	balanceDetail := &model.UserBalanceDetail{
		Uid:     me.Uid,
		Type:    typ,
		Num:     award,
		Balance: me.Balance + award,
		Desc:    desc,
	}
	return DefaultUserRich.add(session, balanceDetail)
}
<commit_msg>初始任务重复提交，导致重复领取铜币 bugfix，感谢 https://studygolang.com/user/windy_ 反馈<commit_after>// Copyright 2017 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author:polaris	polaris@studygolang.com

package logic

import (
	. "db"
	"errors"
	"fmt"
	"math"
	"math/rand"
	"model"
	"strconv"
	"time"

	"github.com/go-xorm/xorm"
	"github.com/polaris1119/goutils"
	"github.com/polaris1119/times"
	"golang.org/x/net/context"
)

type MissionLogic struct{}

var DefaultMission = MissionLogic{}

// HasLoginMission 是否有今日登录奖励
func (self MissionLogic) HasLoginMission(ctx context.Context, me *model.Me) bool {
	// 还没有铜币，当然有可能是消耗尽了
	if me.Balance == 0 {
		// 初始资本没有领取，必须先领取
		if DefaultUserRich.Total(ctx, me.Uid) == 0 {
			return false
		}
	}

	userLoginMission := self.FindLoginMission(ctx, me)
	if userLoginMission == nil {
		return false
	}

	if userLoginMission.Uid == 0 {
		return true
	}

	// 今日是否领取
	if times.Format("Ymd") == strconv.Itoa(userLoginMission.Date) {
		return false
	}
	return true
}

// RedeemLoginAward 领取登录奖励
func (self MissionLogic) RedeemLoginAward(ctx context.Context, me *model.Me) error {
	objLog := GetLogger(ctx)

	mission := self.findMission(ctx, model.MissionTypeLogin)
	if mission.Id == 0 {
		objLog.Errorln("每日登录任务不存在")
		return errors.New("任务不存在")
	}

	userLoginMission := self.FindLoginMission(ctx, me)
	if userLoginMission == nil {
		objLog.Errorln("查询数据库失败")
		return errors.New("服务内部错误")
	}

	session := MasterDB.NewSession()
	defer session.Close()
	session.Begin()

	if userLoginMission.Uid == 0 {
		userLoginMission.Date = goutils.MustInt(time.Now().Format("20060102"))
		userLoginMission.Days = 1
		userLoginMission.TotalDays = 1
		userLoginMission.Award = mission.Min
		userLoginMission.Uid = me.Uid

		_, err := session.Insert(userLoginMission)
		if err != nil {
			session.Rollback()
			objLog.Errorln("insert user_login_mission error:", err)
			return errors.New("服务内部错误")
		}

	} else {
		today := goutils.MustInt(times.Format("Ymd"))
		if today == userLoginMission.Date {
			session.Rollback()
			return errors.New("今日已领取")
		}
		// 昨日是否领取了
		yesterday := goutils.MustInt(times.Format("Ymd", time.Now().Add(-86400*time.Second)))
		if yesterday != userLoginMission.Date {
			userLoginMission.Award = mission.Min
			userLoginMission.Days = 1
		} else {
			userLoginMission.Days++
			if userLoginMission.Award == mission.Max {
				userLoginMission.Award = mission.Min
			} else {
				award := userLoginMission.Award + rand.Intn(mission.Incr) + 1
				userLoginMission.Award = int(math.Min(float64(award), float64(mission.Max)))
			}
		}

		userLoginMission.Date = today
		userLoginMission.TotalDays++
		userLoginMission.UpdatedAt = time.Now()

		_, err := session.Where("uid=?", userLoginMission.Uid).Update(userLoginMission)
		if err != nil {
			session.Rollback()
			objLog.Errorln("update user_login_mission error:", err)
			return errors.New("服务内部错误")
		}
	}

	desc := times.Format("Ymd") + " 的每日登录奖励 " + strconv.Itoa(userLoginMission.Award) + " 铜币"
	err := self.changeUserBalance(session, me, model.MissionTypeLogin, userLoginMission.Award, desc)
	if err != nil {
		session.Rollback()
		objLog.Errorln("changeUserBalance error:", err)
		return errors.New("服务内部错误")
	}

	session.Commit()

	return nil
}

func (MissionLogic) FindLoginMission(ctx context.Context, me *model.Me) *model.UserLoginMission {
	objLog := GetLogger(ctx)

	userLoginMission := &model.UserLoginMission{}
	_, err := MasterDB.Where("uid=?", me.Uid).Get(userLoginMission)
	if err != nil {
		objLog.Errorln("MissionLogic FindLoginMission error:", err)
		return nil
	}

	return userLoginMission
}

// Complete 完成任务（非每日任务）
func (MissionLogic) Complete(ctx context.Context, me *model.Me, id interface{}) error {
	objLog := GetLogger(ctx)

	mission := &model.Mission{}
	_, err := MasterDB.Id(id).Get(mission)
	if err != nil {
		objLog.Errorln("MissionLogic FindLoginMission error:", err)
		return err
	}

	if mission.Id == 0 || mission.State != 0 {
		return errors.New("任务不存在或已过期")
	}

	user := DefaultUser.FindOne(ctx, "uid", me.Uid)

	// 初始任务，不允许重复提交
	if id == model.InitialMissionId {
		if user.Balance > 0 {
			objLog.Errorln("repeat claim init award", user.Username)
			return nil
		}

		details := DefaultUserRich.FindBalanceDetail(ctx, me, mission.Type)
		if len(details) > 0 {
			return nil
		}
	}

	desc := fmt.Sprintf("获得%s %d 铜币", model.BalanceTypeMap[mission.Type], mission.Fixed)
	DefaultUserRich.IncrUserRich(user, mission.Type, mission.Fixed, desc)

	return nil
}

func (MissionLogic) findMission(ctx context.Context, typ int) *model.Mission {
	mission := &model.Mission{}
	MasterDB.Where("type=?", typ).Get(mission)
	return mission
}

func (self MissionLogic) changeUserBalance(session *xorm.Session, me *model.Me, typ, award int, desc string) error {
	_, err := session.Where("uid=?", me.Uid).Incr("balance", award).Update(new(model.User))
	if err != nil {
		return errors.New("服务内部错误")
	}

	balanceDetail := &model.UserBalanceDetail{
		Uid:     me.Uid,
		Type:    typ,
		Num:     award,
		Balance: me.Balance + award,
		Desc:    desc,
	}
	return DefaultUserRich.add(session, balanceDetail)
}
<|endoftext|>


<commit_before><commit_msg>added POC with block cache hit/miss<commit_after><|endoftext|>


<commit_before>// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package firebase is the entry point to the Firebase Admin SDK. It provides functionality for initializing App
// instances, which serve as the central entities that provide access to various other Firebase services exposed
// from the SDK.
package firebase

import (
	"context"
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"

	"cloud.google.com/go/firestore"
	"firebase.google.com/go/v4/auth"
	"firebase.google.com/go/v4/db"
	"firebase.google.com/go/v4/iid"
	"firebase.google.com/go/v4/internal"
	"firebase.google.com/go/v4/messaging"
	"firebase.google.com/go/v4/storage"
	"google.golang.org/api/option"
	"google.golang.org/api/transport"
)

var defaultAuthOverrides = make(map[string]interface{})

// Version of the Firebase Go Admin SDK.
const Version = "4.6.0"

// firebaseEnvName is the name of the environment variable with the Config.
const firebaseEnvName = "FIREBASE_CONFIG"

// An App holds configuration and state common to all Firebase services that are exposed from the SDK.
type App struct {
	authOverride     map[string]interface{}
	dbURL            string
	projectID        string
	serviceAccountID string
	storageBucket    string
	opts             []option.ClientOption
}

// Config represents the configuration used to initialize an App.
type Config struct {
	AuthOverride     *map[string]interface{} `json:"databaseAuthVariableOverride"`
	DatabaseURL      string                  `json:"databaseURL"`
	ProjectID        string                  `json:"projectId"`
	ServiceAccountID string                  `json:"serviceAccountId"`
	StorageBucket    string                  `json:"storageBucket"`
}

// Auth returns an instance of auth.Client.
func (a *App) Auth(ctx context.Context) (*auth.Client, error) {
	conf := &internal.AuthConfig{
		ProjectID:        a.projectID,
		Opts:             a.opts,
		ServiceAccountID: a.serviceAccountID,
		Version:          Version,
	}
	return auth.NewClient(ctx, conf)
}

// Database returns an instance of db.Client to interact with the default Firebase Database
// configured via Config.DatabaseURL.
func (a *App) Database(ctx context.Context) (*db.Client, error) {
	return a.DatabaseWithURL(ctx, a.dbURL)
}

// DatabaseWithURL returns an instance of db.Client to interact with the Firebase Database
// identified by the given URL.
func (a *App) DatabaseWithURL(ctx context.Context, url string) (*db.Client, error) {
	conf := &internal.DatabaseConfig{
		AuthOverride: a.authOverride,
		URL:          url,
		Opts:         a.opts,
		Version:      Version,
	}
	return db.NewClient(ctx, conf)
}

// Storage returns a new instance of storage.Client.
func (a *App) Storage(ctx context.Context) (*storage.Client, error) {
	conf := &internal.StorageConfig{
		Opts:   a.opts,
		Bucket: a.storageBucket,
	}
	return storage.NewClient(ctx, conf)
}

// Firestore returns a new firestore.Client instance from the https://godoc.org/cloud.google.com/go/firestore
// package.
func (a *App) Firestore(ctx context.Context) (*firestore.Client, error) {
	if a.projectID == "" {
		return nil, errors.New("project id is required to access Firestore")
	}
	return firestore.NewClient(ctx, a.projectID, a.opts...)
}

// InstanceID returns an instance of iid.Client.
func (a *App) InstanceID(ctx context.Context) (*iid.Client, error) {
	conf := &internal.InstanceIDConfig{
		ProjectID: a.projectID,
		Opts:      a.opts,
	}
	return iid.NewClient(ctx, conf)
}

// Messaging returns an instance of messaging.Client.
func (a *App) Messaging(ctx context.Context) (*messaging.Client, error) {
	conf := &internal.MessagingConfig{
		ProjectID: a.projectID,
		Opts:      a.opts,
		Version:   Version,
	}
	return messaging.NewClient(ctx, conf)
}

// NewApp creates a new App from the provided config and client options.
//
// If the client options contain a valid credential (a service account file, a refresh token
// file or an oauth2.TokenSource) the App will be authenticated using that credential. Otherwise,
// NewApp attempts to authenticate the App with Google application default credentials.
// If `config` is nil, the SDK will attempt to load the config options from the
// `FIREBASE_CONFIG` environment variable. If the value in it starts with a `{` it is parsed as a
// JSON object, otherwise it is assumed to be the name of the JSON file containing the options.
func NewApp(ctx context.Context, config *Config, opts ...option.ClientOption) (*App, error) {
	o := []option.ClientOption{option.WithScopes(internal.FirebaseScopes...)}
	o = append(o, opts...)
	if config == nil {
		var err error
		if config, err = getConfigDefaults(); err != nil {
			return nil, err
		}
	}

	pid := getProjectID(ctx, config, o...)
	ao := defaultAuthOverrides
	if config.AuthOverride != nil {
		ao = *config.AuthOverride
	}

	return &App{
		authOverride:     ao,
		dbURL:            config.DatabaseURL,
		projectID:        pid,
		serviceAccountID: config.ServiceAccountID,
		storageBucket:    config.StorageBucket,
		opts:             o,
	}, nil
}

// getConfigDefaults reads the default config file, defined by the FIREBASE_CONFIG
// env variable, used only when options are nil.
func getConfigDefaults() (*Config, error) {
	fbc := &Config{}
	confFileName := os.Getenv(firebaseEnvName)
	if confFileName == "" {
		return fbc, nil
	}
	var dat []byte
	if confFileName[0] == byte('{') {
		dat = []byte(confFileName)
	} else {
		var err error
		if dat, err = ioutil.ReadFile(confFileName); err != nil {
			return nil, err
		}
	}
	if err := json.Unmarshal(dat, fbc); err != nil {
		return nil, err
	}

	// Some special handling necessary for db auth overrides
	var m map[string]interface{}
	if err := json.Unmarshal(dat, &m); err != nil {
		return nil, err
	}
	if ao, ok := m["databaseAuthVariableOverride"]; ok && ao == nil {
		// Auth overrides are explicitly set to null
		var nullMap map[string]interface{}
		fbc.AuthOverride = &nullMap
	}
	return fbc, nil
}

func getProjectID(ctx context.Context, config *Config, opts ...option.ClientOption) string {
	if config.ProjectID != "" {
		return config.ProjectID
	}

	creds, _ := transport.Creds(ctx, opts...)
	if creds != nil && creds.ProjectID != "" {
		return creds.ProjectID
	}

	if pid := os.Getenv("GOOGLE_CLOUD_PROJECT"); pid != "" {
		return pid
	}

	return os.Getenv("GCLOUD_PROJECT")
}
<commit_msg>[chore] Release 4.6.1 (#466)<commit_after>// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package firebase is the entry point to the Firebase Admin SDK. It provides functionality for initializing App
// instances, which serve as the central entities that provide access to various other Firebase services exposed
// from the SDK.
package firebase

import (
	"context"
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"

	"cloud.google.com/go/firestore"
	"firebase.google.com/go/v4/auth"
	"firebase.google.com/go/v4/db"
	"firebase.google.com/go/v4/iid"
	"firebase.google.com/go/v4/internal"
	"firebase.google.com/go/v4/messaging"
	"firebase.google.com/go/v4/storage"
	"google.golang.org/api/option"
	"google.golang.org/api/transport"
)

var defaultAuthOverrides = make(map[string]interface{})

// Version of the Firebase Go Admin SDK.
const Version = "4.6.1"

// firebaseEnvName is the name of the environment variable with the Config.
const firebaseEnvName = "FIREBASE_CONFIG"

// An App holds configuration and state common to all Firebase services that are exposed from the SDK.
type App struct {
	authOverride     map[string]interface{}
	dbURL            string
	projectID        string
	serviceAccountID string
	storageBucket    string
	opts             []option.ClientOption
}

// Config represents the configuration used to initialize an App.
type Config struct {
	AuthOverride     *map[string]interface{} `json:"databaseAuthVariableOverride"`
	DatabaseURL      string                  `json:"databaseURL"`
	ProjectID        string                  `json:"projectId"`
	ServiceAccountID string                  `json:"serviceAccountId"`
	StorageBucket    string                  `json:"storageBucket"`
}

// Auth returns an instance of auth.Client.
func (a *App) Auth(ctx context.Context) (*auth.Client, error) {
	conf := &internal.AuthConfig{
		ProjectID:        a.projectID,
		Opts:             a.opts,
		ServiceAccountID: a.serviceAccountID,
		Version:          Version,
	}
	return auth.NewClient(ctx, conf)
}

// Database returns an instance of db.Client to interact with the default Firebase Database
// configured via Config.DatabaseURL.
func (a *App) Database(ctx context.Context) (*db.Client, error) {
	return a.DatabaseWithURL(ctx, a.dbURL)
}

// DatabaseWithURL returns an instance of db.Client to interact with the Firebase Database
// identified by the given URL.
func (a *App) DatabaseWithURL(ctx context.Context, url string) (*db.Client, error) {
	conf := &internal.DatabaseConfig{
		AuthOverride: a.authOverride,
		URL:          url,
		Opts:         a.opts,
		Version:      Version,
	}
	return db.NewClient(ctx, conf)
}

// Storage returns a new instance of storage.Client.
func (a *App) Storage(ctx context.Context) (*storage.Client, error) {
	conf := &internal.StorageConfig{
		Opts:   a.opts,
		Bucket: a.storageBucket,
	}
	return storage.NewClient(ctx, conf)
}

// Firestore returns a new firestore.Client instance from the https://godoc.org/cloud.google.com/go/firestore
// package.
func (a *App) Firestore(ctx context.Context) (*firestore.Client, error) {
	if a.projectID == "" {
		return nil, errors.New("project id is required to access Firestore")
	}
	return firestore.NewClient(ctx, a.projectID, a.opts...)
}

// InstanceID returns an instance of iid.Client.
func (a *App) InstanceID(ctx context.Context) (*iid.Client, error) {
	conf := &internal.InstanceIDConfig{
		ProjectID: a.projectID,
		Opts:      a.opts,
	}
	return iid.NewClient(ctx, conf)
}

// Messaging returns an instance of messaging.Client.
func (a *App) Messaging(ctx context.Context) (*messaging.Client, error) {
	conf := &internal.MessagingConfig{
		ProjectID: a.projectID,
		Opts:      a.opts,
		Version:   Version,
	}
	return messaging.NewClient(ctx, conf)
}

// NewApp creates a new App from the provided config and client options.
//
// If the client options contain a valid credential (a service account file, a refresh token
// file or an oauth2.TokenSource) the App will be authenticated using that credential. Otherwise,
// NewApp attempts to authenticate the App with Google application default credentials.
// If `config` is nil, the SDK will attempt to load the config options from the
// `FIREBASE_CONFIG` environment variable. If the value in it starts with a `{` it is parsed as a
// JSON object, otherwise it is assumed to be the name of the JSON file containing the options.
func NewApp(ctx context.Context, config *Config, opts ...option.ClientOption) (*App, error) {
	o := []option.ClientOption{option.WithScopes(internal.FirebaseScopes...)}
	o = append(o, opts...)
	if config == nil {
		var err error
		if config, err = getConfigDefaults(); err != nil {
			return nil, err
		}
	}

	pid := getProjectID(ctx, config, o...)
	ao := defaultAuthOverrides
	if config.AuthOverride != nil {
		ao = *config.AuthOverride
	}

	return &App{
		authOverride:     ao,
		dbURL:            config.DatabaseURL,
		projectID:        pid,
		serviceAccountID: config.ServiceAccountID,
		storageBucket:    config.StorageBucket,
		opts:             o,
	}, nil
}

// getConfigDefaults reads the default config file, defined by the FIREBASE_CONFIG
// env variable, used only when options are nil.
func getConfigDefaults() (*Config, error) {
	fbc := &Config{}
	confFileName := os.Getenv(firebaseEnvName)
	if confFileName == "" {
		return fbc, nil
	}
	var dat []byte
	if confFileName[0] == byte('{') {
		dat = []byte(confFileName)
	} else {
		var err error
		if dat, err = ioutil.ReadFile(confFileName); err != nil {
			return nil, err
		}
	}
	if err := json.Unmarshal(dat, fbc); err != nil {
		return nil, err
	}

	// Some special handling necessary for db auth overrides
	var m map[string]interface{}
	if err := json.Unmarshal(dat, &m); err != nil {
		return nil, err
	}
	if ao, ok := m["databaseAuthVariableOverride"]; ok && ao == nil {
		// Auth overrides are explicitly set to null
		var nullMap map[string]interface{}
		fbc.AuthOverride = &nullMap
	}
	return fbc, nil
}

func getProjectID(ctx context.Context, config *Config, opts ...option.ClientOption) string {
	if config.ProjectID != "" {
		return config.ProjectID
	}

	creds, _ := transport.Creds(ctx, opts...)
	if creds != nil && creds.ProjectID != "" {
		return creds.ProjectID
	}

	if pid := os.Getenv("GOOGLE_CLOUD_PROJECT"); pid != "" {
		return pid
	}

	return os.Getenv("GCLOUD_PROJECT")
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/autoscaling"
	"github.com/aws/aws-sdk-go/service/ec2"
	"log"
	"time"
)

// How often flywheel will update its internal state and/or check for idle
// timeouts
const SPIN_INTERVAL = time.Second

// HTTP requests "ping" the flywheel goroutine. This updates the idle timeout,
// and returns the current status to the http request.
type Ping struct {
	replyTo      chan Pong
	requestStart bool
	requestStop  bool
	noop         bool
}

type Pong struct {
	Status      int       `json:"-"`
	StatusName  string    `json:"status"`
	Err         error     `json:"error,omitempty"`
	LastStarted time.Time `json:"last-started,omitempty"`
	LastStopped time.Time `json:"last-stopped,omitempty"`
}

// The Flywheel struct holds all the state required by the flywheel goroutine.
type Flywheel struct {
	config      *Config
	running     bool
	pings       chan Ping
	status      int
	ready       bool
	stopAt      time.Time
	lastStarted time.Time
	lastStopped time.Time
	ec2         *ec2.EC2
	autoscaling *autoscaling.AutoScaling
	hcInterval  time.Duration
	idleTimeout time.Duration
}

func New(config *Config) *Flywheel {
	region := "ap-southeast-2"

	var hcInterval time.Duration
	var idleTimeout time.Duration

	s := config.HcInterval
	if s == "" {
		hcInterval = time.Minute
	} else {
		d, err := time.ParseDuration(s)
		if err != nil {
			log.Printf("Invalid duration: %v", err)
			hcInterval = time.Minute
		} else {
			hcInterval = d
		}
	}

	s = config.IdleTimeout
	if s == "" {
		idleTimeout = time.Minute
	} else {
		d, err := time.ParseDuration(s)
		if err != nil {
			log.Printf("Invalid duration: %v", err)
			idleTimeout = time.Minute
		} else {
			idleTimeout = d
		}
	}

	awsConfig := &aws.Config{Region: &region}
	return &Flywheel{
		hcInterval:  hcInterval,
		idleTimeout: idleTimeout,
		config:      config,
		pings:       make(chan Ping),
		stopAt:      time.Now(),
		ec2:         ec2.New(awsConfig),
		autoscaling: autoscaling.New(awsConfig),
	}
}

// Runs the main loop for the Flywheel.
// Never returns, so should probably be run as a goroutine.
func (fw *Flywheel) Spin() {
	hchan := make(chan int, 1)

	go fw.HealthWatcher(hchan)

	ticker := time.NewTicker(SPIN_INTERVAL)
	for {
		select {
		case ping := <-fw.pings:
			fw.RecvPing(&ping)
		case <-ticker.C:
			fw.Poll()
		case status := <-hchan:
			if fw.status != status {
				log.Printf("Healthcheck - status is now %v", StatusString(status))
				if status == STARTED {
					fw.stopAt = time.Now().Add(fw.idleTimeout)
					log.Printf("Timer update. Stop scheduled for %v", fw.stopAt)
				}
				fw.status = status
			}
		}
	}
}

// HTTP requests "ping" the flywheel goroutine. This updates the idle timeout,
// and returns the current status to the http request.
func (fw *Flywheel) RecvPing(ping *Ping) {
	var pong Pong

	ch := ping.replyTo
	defer close(ch)

	switch fw.status {
	case STOPPED:
		if ping.requestStart {
			pong.Err = fw.Start()
		}

	case STARTED:
		if ping.requestStop {
			pong.Err = fw.Stop()
		} else if ping.noop {
			// Status requests, etc. Don't update idle timer
		} else {
			fw.stopAt = time.Now().Add(fw.idleTimeout)
			log.Printf("Timer update. Stop scheduled for %v", fw.stopAt)
		}
	}

	pong.Status = fw.status
	pong.StatusName = StatusString(fw.status)
	pong.LastStarted = fw.lastStarted
	pong.LastStopped = fw.lastStopped

	ch <- pong
}

// The periodic check for starting/stopping state transitions and idle
// timeouts
func (fw *Flywheel) Poll() {
	switch fw.status {
	case STARTED:
		if time.Now().After(fw.stopAt) {
			fw.Stop()
			log.Print("Idle timeout - shutting down")
			fw.status = STOPPING
		}

	case STOPPING:
		if fw.ready {
			log.Print("Shutdown complete")
			fw.status = STOPPED
		}

	case STARTING:
		if fw.ready {
			fw.status = STARTED
			fw.stopAt = time.Now().Add(fw.idleTimeout)
			log.Printf("Startup complete. Stop scheduled for %v", fw.stopAt)
		}
	}
}

// Start all the resources managed by the flywheel.
func (fw *Flywheel) Start() error {
	fw.lastStarted = time.Now()
	log.Print("Startup beginning")

	var err error
	err = fw.StartInstances()

	if err == nil {
		err = fw.UnterminateAutoScaling()
	}
	if err == nil {
		err = fw.StartAutoScaling()
	}

	if err != nil {
		log.Printf("Error starting: %v", err)
		return err
	}

	fw.ready = false
	fw.stopAt = time.Now().Add(fw.idleTimeout)
	fw.status = STARTING
	return nil
}

// Start EC2 instances
func (fw *Flywheel) StartInstances() error {
	if len(fw.config.Instances) == 0 {
		return nil
	}
	_, err := fw.ec2.StartInstances(
		&ec2.StartInstancesInput{
			InstanceIds: fw.config.AwsInstances(),
		},
	)
	return err
}

// Restore autoscaling group instances
func (fw *Flywheel) UnterminateAutoScaling() error {
	var err error
	for groupName, size := range fw.config.AutoScaling.Terminate {
		_, err = fw.autoscaling.UpdateAutoScalingGroup(
			&autoscaling.UpdateAutoScalingGroupInput{
				AutoScalingGroupName: &groupName,
				MaxSize:              &size,
				MinSize:              &size,
			},
		)
		if err != nil {
			return err
		}
	}
	return nil
}

// Start EC2 instances in a suspended autoscale group
// @note The autoscale group isn't unsuspended here. It's done by the
//       healthcheck once all the instances are healthy.
func (fw *Flywheel) StartAutoScaling() error {
	var err error
	var awsGroupNames []*string
	for _, groupName := range fw.config.AutoScaling.Stop {
		awsGroupNames = append(awsGroupNames, &groupName)
	}

	resp, err := fw.autoscaling.DescribeAutoScalingGroups(
		&autoscaling.DescribeAutoScalingGroupsInput{
			AutoScalingGroupNames: awsGroupNames,
		},
	)
	if err != nil {
		return err
	}

	for _, group := range resp.AutoScalingGroups {
		// NOTE: Processes not unsuspended here. Needs to be triggered after
		// startup, before entering STARTED state.
		instanceIds := []*string{}
		for _, instance := range group.Instances {
			instanceIds = append(instanceIds, instance.InstanceId)
		}

		_, err := fw.ec2.StartInstances(
			&ec2.StartInstancesInput{
				InstanceIds: instanceIds,
			},
		)
		if err != nil {
			return err
		}
	}

	return nil
}

// Stop all resources managed by the flywheel
func (fw *Flywheel) Stop() error {
	fw.lastStopped = time.Now()

	var err error
	err = fw.StopInstances()

	if err == nil {
		err = fw.TerminateAutoScaling()
	}
	if err == nil {
		err = fw.StopAutoScaling()
	}

	if err != nil {
		log.Printf("Error stopping: %v", err)
		return err
	}

	fw.ready = false
	fw.status = STOPPING
	return nil
}

// Stop EC2 instances
func (fw *Flywheel) StopInstances() error {
	if len(fw.config.Instances) == 0 {
		return nil
	}
	_, err := fw.ec2.StopInstances(
		&ec2.StopInstancesInput{
			InstanceIds: fw.config.AwsInstances(),
		},
	)
	return err
}

// Suspend ReplaceUnhealthy in an autoscale group and stop the instances.
func (fw *Flywheel) StopAutoScaling() error {
	var err error
	var awsGroupNames []*string

	if len(fw.config.AutoScaling.Stop) == 0 {
		return nil
	}

	for _, groupName := range fw.config.AutoScaling.Stop {
		awsGroupNames = append(awsGroupNames, &groupName)
	}

	resp, err := fw.autoscaling.DescribeAutoScalingGroups(
		&autoscaling.DescribeAutoScalingGroupsInput{
			AutoScalingGroupNames: awsGroupNames,
		},
	)
	if err != nil {
		return err
	}

	for _, group := range resp.AutoScalingGroups {
		_, err = fw.autoscaling.SuspendProcesses(
			&autoscaling.ScalingProcessQuery{
				AutoScalingGroupName: group.AutoScalingGroupName,
				ScalingProcesses: []*string{
					aws.String("ReplaceUnhealthy"),
				},
			},
		)
		if err != nil {
			return err
		}

		instanceIds := []*string{}
		for _, instance := range group.Instances {
			instanceIds = append(instanceIds, instance.InstanceId)
		}

		_, err := fw.ec2.StopInstances(
			&ec2.StopInstancesInput{
				InstanceIds: instanceIds,
			},
		)
		if err != nil {
			return err
		}
	}

	return nil
}

// Reduce autoscaling min/max instances to 0, causing the instances to be terminated.
func (fw *Flywheel) TerminateAutoScaling() error {
	var err error
	var zero int64
	for groupName := range fw.config.AutoScaling.Terminate {
		_, err = fw.autoscaling.UpdateAutoScalingGroup(
			&autoscaling.UpdateAutoScalingGroupInput{
				AutoScalingGroupName: &groupName,
				MaxSize:              &zero,
				MinSize:              &zero,
			},
		)
		if err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Add more verbose stop/start messages<commit_after>package main

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/autoscaling"
	"github.com/aws/aws-sdk-go/service/ec2"
	"log"
	"time"
)

// How often flywheel will update its internal state and/or check for idle
// timeouts
const SPIN_INTERVAL = time.Second

// HTTP requests "ping" the flywheel goroutine. This updates the idle timeout,
// and returns the current status to the http request.
type Ping struct {
	replyTo      chan Pong
	requestStart bool
	requestStop  bool
	noop         bool
}

type Pong struct {
	Status      int       `json:"-"`
	StatusName  string    `json:"status"`
	Err         error     `json:"error,omitempty"`
	LastStarted time.Time `json:"last-started,omitempty"`
	LastStopped time.Time `json:"last-stopped,omitempty"`
}

// The Flywheel struct holds all the state required by the flywheel goroutine.
type Flywheel struct {
	config      *Config
	running     bool
	pings       chan Ping
	status      int
	ready       bool
	stopAt      time.Time
	lastStarted time.Time
	lastStopped time.Time
	ec2         *ec2.EC2
	autoscaling *autoscaling.AutoScaling
	hcInterval  time.Duration
	idleTimeout time.Duration
}

func New(config *Config) *Flywheel {
	region := "ap-southeast-2"

	var hcInterval time.Duration
	var idleTimeout time.Duration

	s := config.HcInterval
	if s == "" {
		hcInterval = time.Minute
	} else {
		d, err := time.ParseDuration(s)
		if err != nil {
			log.Printf("Invalid duration: %v", err)
			hcInterval = time.Minute
		} else {
			hcInterval = d
		}
	}

	s = config.IdleTimeout
	if s == "" {
		idleTimeout = time.Minute
	} else {
		d, err := time.ParseDuration(s)
		if err != nil {
			log.Printf("Invalid duration: %v", err)
			idleTimeout = time.Minute
		} else {
			idleTimeout = d
		}
	}

	awsConfig := &aws.Config{Region: &region}
	return &Flywheel{
		hcInterval:  hcInterval,
		idleTimeout: idleTimeout,
		config:      config,
		pings:       make(chan Ping),
		stopAt:      time.Now(),
		ec2:         ec2.New(awsConfig),
		autoscaling: autoscaling.New(awsConfig),
	}
}

// Runs the main loop for the Flywheel.
// Never returns, so should probably be run as a goroutine.
func (fw *Flywheel) Spin() {
	hchan := make(chan int, 1)

	go fw.HealthWatcher(hchan)

	ticker := time.NewTicker(SPIN_INTERVAL)
	for {
		select {
		case ping := <-fw.pings:
			fw.RecvPing(&ping)
		case <-ticker.C:
			fw.Poll()
		case status := <-hchan:
			if fw.status != status {
				log.Printf("Healthcheck - status is now %v", StatusString(status))
				if status == STARTED {
					fw.stopAt = time.Now().Add(fw.idleTimeout)
					log.Printf("Timer update. Stop scheduled for %v", fw.stopAt)
				}
				fw.status = status
			}
		}
	}
}

// HTTP requests "ping" the flywheel goroutine. This updates the idle timeout,
// and returns the current status to the http request.
func (fw *Flywheel) RecvPing(ping *Ping) {
	var pong Pong

	ch := ping.replyTo
	defer close(ch)

	switch fw.status {
	case STOPPED:
		if ping.requestStart {
			pong.Err = fw.Start()
		}

	case STARTED:
		if ping.requestStop {
			pong.Err = fw.Stop()
		} else if ping.noop {
			// Status requests, etc. Don't update idle timer
		} else {
			fw.stopAt = time.Now().Add(fw.idleTimeout)
			log.Printf("Timer update. Stop scheduled for %v", fw.stopAt)
		}
	}

	pong.Status = fw.status
	pong.StatusName = StatusString(fw.status)
	pong.LastStarted = fw.lastStarted
	pong.LastStopped = fw.lastStopped

	ch <- pong
}

// The periodic check for starting/stopping state transitions and idle
// timeouts
func (fw *Flywheel) Poll() {
	switch fw.status {
	case STARTED:
		if time.Now().After(fw.stopAt) {
			fw.Stop()
			log.Print("Idle timeout - shutting down")
			fw.status = STOPPING
		}

	case STOPPING:
		if fw.ready {
			log.Print("Shutdown complete")
			fw.status = STOPPED
		}

	case STARTING:
		if fw.ready {
			fw.status = STARTED
			fw.stopAt = time.Now().Add(fw.idleTimeout)
			log.Printf("Startup complete. Stop scheduled for %v", fw.stopAt)
		}
	}
}

// Start all the resources managed by the flywheel.
func (fw *Flywheel) Start() error {
	fw.lastStarted = time.Now()
	log.Print("Startup beginning")

	var err error
	err = fw.StartInstances()

	if err == nil {
		err = fw.UnterminateAutoScaling()
	}
	if err == nil {
		err = fw.StartAutoScaling()
	}

	if err != nil {
		log.Printf("Error starting: %v", err)
		return err
	}

	fw.ready = false
	fw.stopAt = time.Now().Add(fw.idleTimeout)
	fw.status = STARTING
	return nil
}

// Start EC2 instances
func (fw *Flywheel) StartInstances() error {
	if len(fw.config.Instances) == 0 {
		return nil
	}
	log.Printf("Starting instances %v", fw.config.Instances)
	_, err := fw.ec2.StartInstances(
		&ec2.StartInstancesInput{
			InstanceIds: fw.config.AwsInstances(),
		},
	)
	return err
}

// Restore autoscaling group instances
func (fw *Flywheel) UnterminateAutoScaling() error {
	var err error
	for groupName, size := range fw.config.AutoScaling.Terminate {
		log.Printf("Restoring autoscaling group %s", groupName)
		_, err = fw.autoscaling.UpdateAutoScalingGroup(
			&autoscaling.UpdateAutoScalingGroupInput{
				AutoScalingGroupName: &groupName,
				MaxSize:              &size,
				MinSize:              &size,
			},
		)
		if err != nil {
			return err
		}
	}
	return nil
}

// Start EC2 instances in a suspended autoscale group
// @note The autoscale group isn't unsuspended here. It's done by the
//       healthcheck once all the instances are healthy.
func (fw *Flywheel) StartAutoScaling() error {
	var err error
	var awsGroupNames []*string
	for _, groupName := range fw.config.AutoScaling.Stop {
		awsGroupNames = append(awsGroupNames, &groupName)
	}

	resp, err := fw.autoscaling.DescribeAutoScalingGroups(
		&autoscaling.DescribeAutoScalingGroupsInput{
			AutoScalingGroupNames: awsGroupNames,
		},
	)
	if err != nil {
		return err
	}

	for _, group := range resp.AutoScalingGroups {
		log.Printf("Starting autoscaling group %s", group.AutoScalingGroupName)
		// NOTE: Processes not unsuspended here. Needs to be triggered after
		// startup, before entering STARTED state.
		instanceIds := []*string{}
		for _, instance := range group.Instances {
			instanceIds = append(instanceIds, instance.InstanceId)
		}

		_, err := fw.ec2.StartInstances(
			&ec2.StartInstancesInput{
				InstanceIds: instanceIds,
			},
		)
		if err != nil {
			return err
		}
	}

	return nil
}

// Stop all resources managed by the flywheel
func (fw *Flywheel) Stop() error {
	fw.lastStopped = time.Now()

	var err error
	err = fw.StopInstances()

	if err == nil {
		err = fw.TerminateAutoScaling()
	}
	if err == nil {
		err = fw.StopAutoScaling()
	}

	if err != nil {
		log.Printf("Error stopping: %v", err)
		return err
	}

	fw.ready = false
	fw.status = STOPPING
	return nil
}

// Stop EC2 instances
func (fw *Flywheel) StopInstances() error {
	if len(fw.config.Instances) == 0 {
		return nil
	}
	log.Printf("Stopping instances %v", fw.config.Instances)
	_, err := fw.ec2.StopInstances(
		&ec2.StopInstancesInput{
			InstanceIds: fw.config.AwsInstances(),
		},
	)
	return err
}

// Suspend ReplaceUnhealthy in an autoscale group and stop the instances.
func (fw *Flywheel) StopAutoScaling() error {
	var err error
	var awsGroupNames []*string

	if len(fw.config.AutoScaling.Stop) == 0 {
		return nil
	}

	for _, groupName := range fw.config.AutoScaling.Stop {
		awsGroupNames = append(awsGroupNames, &groupName)
	}

	resp, err := fw.autoscaling.DescribeAutoScalingGroups(
		&autoscaling.DescribeAutoScalingGroupsInput{
			AutoScalingGroupNames: awsGroupNames,
		},
	)
	if err != nil {
		return err
	}

	for _, group := range resp.AutoScalingGroups {
		log.Printf("Stopping autoscaling group %s", group.AutoScalingGroupName)

		_, err = fw.autoscaling.SuspendProcesses(
			&autoscaling.ScalingProcessQuery{
				AutoScalingGroupName: group.AutoScalingGroupName,
				ScalingProcesses: []*string{
					aws.String("ReplaceUnhealthy"),
				},
			},
		)
		if err != nil {
			return err
		}

		instanceIds := []*string{}
		for _, instance := range group.Instances {
			instanceIds = append(instanceIds, instance.InstanceId)
		}

		_, err := fw.ec2.StopInstances(
			&ec2.StopInstancesInput{
				InstanceIds: instanceIds,
			},
		)
		if err != nil {
			return err
		}
	}

	return nil
}

// Reduce autoscaling min/max instances to 0, causing the instances to be terminated.
func (fw *Flywheel) TerminateAutoScaling() error {
	var err error
	var zero int64
	for groupName := range fw.config.AutoScaling.Terminate {
		log.Printf("Terminating autoscaling group %s", groupName)
		_, err = fw.autoscaling.UpdateAutoScalingGroup(
			&autoscaling.UpdateAutoScalingGroupInput{
				AutoScalingGroupName: &groupName,
				MaxSize:              &zero,
				MinSize:              &zero,
			},
		)
		if err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package swf

import (
	"testing"
)

func TestFSM(t *testing.T) {

	fsm := FSM{
		DecisionWorker: DecisionWorker{StateSerializer: JsonStateSerializer{}, idGenerator: UUIDGenerator{}},
		EmptyData:      func() interface{} { return &TestData{} },
		states:         make(map[string]*FSMState),
	}

	fsm.AddInitialState(&FSMState{
		Name: "start",
		Decider: func(lastEvent HistoryEvent, data interface{}) *Outcome {
			testData := data.(*TestData)
			testData.States = append(testData.States, "start")
			decision, _ := fsm.DecisionWorker.ScheduleActivityTaskDecision("activity", "activityVersion", "taskList", testData)
			return &Outcome{
				NextState: "working",
				Data:      testData,
				Decisions: []*Decision{decision},
			}
		},
	})

	fsm.AddState(&FSMState{
		Name: "working",
		Decider: func(lastEvent HistoryEvent, data interface{}) *Outcome {
			testData := data.(*TestData)
			testData.States = append(testData.States, "working")
			var decision *Decision
			if lastEvent.EventType == "ActivityTaskCompleted" {
				decision, _ = fsm.DecisionWorker.CompleteWorkflowExecution(testData)
			} else if lastEvent.EventType == "ActivityTaskFailed" {
				decision, _ = fsm.DecisionWorker.ScheduleActivityTaskDecision("activity", "activityVersion", "taskList", testData)
			}
			return &Outcome{
				NextState: "working",
				Data:      testData,
				Decisions: []*Decision{decision},
			}
		},
	})

	events := []HistoryEvent{
		HistoryEvent{EventType: "DecisionTaskStarted"},
		HistoryEvent{EventType: "DecisionTaskScheduled"},
		HistoryEvent{
			EventType: "WorkflowExecutionStarted",
			WorkflowExecutionStartedEventAttributes: &WorkflowExecutionStartedEventAttributes{
				Input: "{\"States\":[]}",
			},
		},
	}

	first := &PollForDecisionTaskResponse{
		Events: events,
	}

	decisions := fsm.Tick(first)

	if !Find(decisions, stateMarkerPredicate) {
		t.Fatal("No Record State Marker")
	}

	if !Find(decisions, dataMarkerPredicate) {
		t.Fatal("No Record Data Marker")
	}

	if !Find(decisions, scheduleActivityPredicate) {
		t.Fatal("No ScheduleActivityTask")
	}

	secondEvents := DecisionsToEvents(decisions)
	secondEvents = append(secondEvents, events...)

	second := &PollForDecisionTaskResponse{
		Events: secondEvents,
	}

	if fsm.findCurrentState(secondEvents).Name != "working" {
		t.Fatal("current state is not 'working'", secondEvents)
	}

	var curr = &TestData{}
	fsm.DecisionWorker.StateSerializer.Deserialize(fsm.findCurrentData(secondEvents), curr)

	if len(curr.States) != 1 && curr.States[0] != "start" {
		t.Fatal("current data is not right", curr.States)
	}

	secondDecisions := fsm.Tick(second)

	if !Find(secondDecisions, stateMarkerPredicate) {
		t.Fatal("No Record State Marker")
	}

	if !Find(secondDecisions, dataMarkerPredicate) {
		t.Fatal("No Record Data Marker")
	}

	if !Find(secondDecisions, completeWorkflowPredicate) {
		t.Fatal("No CompleteWorkflow")
	}

}

func Find(decisions []*Decision, predicate func(*Decision) bool) bool {
	for _, d := range decisions {
		if predicate(d) {
			return true
		}
	}
	return false
}

func dataMarkerPredicate(d *Decision) bool {
	return d.DecisionType == "RecordMarker" && d.RecordMarkerDecisionAttributes.MarkerName == DATA_MARKER
}

func stateMarkerPredicate(d *Decision) bool {
	return d.DecisionType == "RecordMarker" && d.RecordMarkerDecisionAttributes.MarkerName == STATE_MARKER
}

func scheduleActivityPredicate(d *Decision) bool {
	return d.DecisionType == "ScheduleActivityTask"
}

func completeWorkflowPredicate(d *Decision) bool {
	return d.DecisionType == "CompleteWorkflowExecution"
}

func DecisionsToEvents(decisions []*Decision) []HistoryEvent {
	events := make([]HistoryEvent, 0)
	for _, d := range decisions {
		if scheduleActivityPredicate(d) {
			event := HistoryEvent{
				EventType: "ActivityTaskCompleted",
			}
			events = append(events, event)
			event = HistoryEvent{
				EventType: "ActivityTaskScheduled",
			}
			events = append(events, event)
		}
		if stateMarkerPredicate(d) {
			event := HistoryEvent{
				EventType: "MarkerRecorded",
				MarkerRecordedEventAttributes: &MarkerRecordedEventAttributes{
					MarkerName: STATE_MARKER,
					Details:    d.RecordMarkerDecisionAttributes.Details,
				},
			}
			events = append(events, event)

		}
		if dataMarkerPredicate(d) {
			event := HistoryEvent{
				EventType: "MarkerRecorded",
				MarkerRecordedEventAttributes: &MarkerRecordedEventAttributes{
					MarkerName: DATA_MARKER,
					Details:    d.RecordMarkerDecisionAttributes.Details,
				},
			}
			events = append(events, event)

		}
	}
	return events
}

type TestData struct {
	States []string
}
<commit_msg>fix a test not compiling<commit_after>package swf

import (
	"testing"
)

func TestFSM(t *testing.T) {

	fsm := FSM{
		DecisionWorker: &DecisionWorker{StateSerializer: JsonStateSerializer{}, idGenerator: UUIDGenerator{}},
		EmptyData:      func() interface{} { return &TestData{} },
		states:         make(map[string]*FSMState),
	}

	fsm.AddInitialState(&FSMState{
		Name: "start",
		Decider: func(lastEvent HistoryEvent, data interface{}) *Outcome {
			testData := data.(*TestData)
			testData.States = append(testData.States, "start")
			decision, _ := fsm.DecisionWorker.ScheduleActivityTaskDecision("activity", "activityVersion", "taskList", testData)
			return &Outcome{
				NextState: "working",
				Data:      testData,
				Decisions: []*Decision{decision},
			}
		},
	})

	fsm.AddState(&FSMState{
		Name: "working",
		Decider: func(lastEvent HistoryEvent, data interface{}) *Outcome {
			testData := data.(*TestData)
			testData.States = append(testData.States, "working")
			var decision *Decision
			if lastEvent.EventType == "ActivityTaskCompleted" {
				decision, _ = fsm.DecisionWorker.CompleteWorkflowExecution(testData)
			} else if lastEvent.EventType == "ActivityTaskFailed" {
				decision, _ = fsm.DecisionWorker.ScheduleActivityTaskDecision("activity", "activityVersion", "taskList", testData)
			}
			return &Outcome{
				NextState: "working",
				Data:      testData,
				Decisions: []*Decision{decision},
			}
		},
	})

	events := []HistoryEvent{
		HistoryEvent{EventType: "DecisionTaskStarted"},
		HistoryEvent{EventType: "DecisionTaskScheduled"},
		HistoryEvent{
			EventType: "WorkflowExecutionStarted",
			WorkflowExecutionStartedEventAttributes: &WorkflowExecutionStartedEventAttributes{
				Input: "{\"States\":[]}",
			},
		},
	}

	first := &PollForDecisionTaskResponse{
		Events: events,
	}

	decisions := fsm.Tick(first)

	if !Find(decisions, stateMarkerPredicate) {
		t.Fatal("No Record State Marker")
	}

	if !Find(decisions, dataMarkerPredicate) {
		t.Fatal("No Record Data Marker")
	}

	if !Find(decisions, scheduleActivityPredicate) {
		t.Fatal("No ScheduleActivityTask")
	}

	secondEvents := DecisionsToEvents(decisions)
	secondEvents = append(secondEvents, events...)

	second := &PollForDecisionTaskResponse{
		Events: secondEvents,
	}

	if fsm.findCurrentState(secondEvents).Name != "working" {
		t.Fatal("current state is not 'working'", secondEvents)
	}

	var curr = &TestData{}
	fsm.DecisionWorker.StateSerializer.Deserialize(fsm.findCurrentData(secondEvents), curr)

	if len(curr.States) != 1 && curr.States[0] != "start" {
		t.Fatal("current data is not right", curr.States)
	}

	secondDecisions := fsm.Tick(second)

	if !Find(secondDecisions, stateMarkerPredicate) {
		t.Fatal("No Record State Marker")
	}

	if !Find(secondDecisions, dataMarkerPredicate) {
		t.Fatal("No Record Data Marker")
	}

	if !Find(secondDecisions, completeWorkflowPredicate) {
		t.Fatal("No CompleteWorkflow")
	}

}

func Find(decisions []*Decision, predicate func(*Decision) bool) bool {
	for _, d := range decisions {
		if predicate(d) {
			return true
		}
	}
	return false
}

func dataMarkerPredicate(d *Decision) bool {
	return d.DecisionType == "RecordMarker" && d.RecordMarkerDecisionAttributes.MarkerName == DATA_MARKER
}

func stateMarkerPredicate(d *Decision) bool {
	return d.DecisionType == "RecordMarker" && d.RecordMarkerDecisionAttributes.MarkerName == STATE_MARKER
}

func scheduleActivityPredicate(d *Decision) bool {
	return d.DecisionType == "ScheduleActivityTask"
}

func completeWorkflowPredicate(d *Decision) bool {
	return d.DecisionType == "CompleteWorkflowExecution"
}

func DecisionsToEvents(decisions []*Decision) []HistoryEvent {
	events := make([]HistoryEvent, 0)
	for _, d := range decisions {
		if scheduleActivityPredicate(d) {
			event := HistoryEvent{
				EventType: "ActivityTaskCompleted",
			}
			events = append(events, event)
			event = HistoryEvent{
				EventType: "ActivityTaskScheduled",
			}
			events = append(events, event)
		}
		if stateMarkerPredicate(d) {
			event := HistoryEvent{
				EventType: "MarkerRecorded",
				MarkerRecordedEventAttributes: &MarkerRecordedEventAttributes{
					MarkerName: STATE_MARKER,
					Details:    d.RecordMarkerDecisionAttributes.Details,
				},
			}
			events = append(events, event)

		}
		if dataMarkerPredicate(d) {
			event := HistoryEvent{
				EventType: "MarkerRecorded",
				MarkerRecordedEventAttributes: &MarkerRecordedEventAttributes{
					MarkerName: DATA_MARKER,
					Details:    d.RecordMarkerDecisionAttributes.Details,
				},
			}
			events = append(events, event)

		}
	}
	return events
}

type TestData struct {
	States []string
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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

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

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

package unversioned

import (
	"net/url"
	"testing"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/testapi"
	"k8s.io/kubernetes/pkg/apis/extensions"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
)

func getHorizontalPodAutoscalersResoureName() string {
	return "horizontalpodautoscalers"
}

func TestHorizontalPodAutoscalerCreate(t *testing.T) {
	ns := api.NamespaceDefault
	horizontalPodAutoscaler := extensions.HorizontalPodAutoscaler{
		ObjectMeta: api.ObjectMeta{
			Name:      "abc",
			Namespace: ns,
		},
	}
	c := &testClient{
		Request: testRequest{
			Method: "POST",
			Path:   testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, ""),
			Query:  buildQueryValues(nil),
			Body:   &horizontalPodAutoscaler,
		},
		Response: Response{StatusCode: 200, Body: &horizontalPodAutoscaler},
	}

	response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Create(&horizontalPodAutoscaler)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerGet(t *testing.T) {
	ns := api.NamespaceDefault
	horizontalPodAutoscaler := &extensions.HorizontalPodAutoscaler{
		ObjectMeta: api.ObjectMeta{
			Name:      "abc",
			Namespace: ns,
		},
	}
	c := &testClient{
		Request: testRequest{
			Method: "GET",
			Path:   testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc"),
			Query:  buildQueryValues(nil),
			Body:   nil,
		},
		Response: Response{StatusCode: 200, Body: horizontalPodAutoscaler},
	}

	response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Get("abc")
	c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerList(t *testing.T) {
	ns := api.NamespaceDefault
	horizontalPodAutoscalerList := &extensions.HorizontalPodAutoscalerList{
		Items: []extensions.HorizontalPodAutoscaler{
			{
				ObjectMeta: api.ObjectMeta{
					Name:      "foo",
					Namespace: ns,
				},
			},
		},
	}
	c := &testClient{
		Request: testRequest{
			Method: "GET",
			Path:   testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, ""),
			Query:  buildQueryValues(nil),
			Body:   nil,
		},
		Response: Response{StatusCode: 200, Body: horizontalPodAutoscalerList},
	}
	response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).List(labels.Everything(), fields.Everything())
	c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerUpdate(t *testing.T) {
	ns := api.NamespaceDefault
	horizontalPodAutoscaler := &extensions.HorizontalPodAutoscaler{
		ObjectMeta: api.ObjectMeta{
			Name:            "abc",
			Namespace:       ns,
			ResourceVersion: "1",
		},
	}
	c := &testClient{
		Request:  testRequest{Method: "PUT", Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc"), Query: buildQueryValues(nil)},
		Response: Response{StatusCode: 200, Body: horizontalPodAutoscaler},
	}
	response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Update(horizontalPodAutoscaler)
	c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerUpdateStatus(t *testing.T) {
	ns := api.NamespaceDefault
	horizontalPodAutoscaler := &extensions.HorizontalPodAutoscaler{
		ObjectMeta: api.ObjectMeta{
			Name:            "abc",
			Namespace:       ns,
			ResourceVersion: "1",
		},
	}
	c := &testClient{
		Request:  testRequest{Method: "PUT", Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc") + "/status", Query: buildQueryValues(nil)},
		Response: Response{StatusCode: 200, Body: horizontalPodAutoscaler},
	}
	response, err := c.Setup(t).Experimental().HorizontalPodAutoscalers(ns).UpdateStatus(horizontalPodAutoscaler)
	c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerDelete(t *testing.T) {
	ns := api.NamespaceDefault
	c := &testClient{
		Request:  testRequest{Method: "DELETE", Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "foo"), Query: buildQueryValues(nil)},
		Response: Response{StatusCode: 200},
	}
	err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Delete("foo", nil)
	c.Validate(t, nil, err)
}

func TestHorizontalPodAutoscalerWatch(t *testing.T) {
	c := &testClient{
		Request: testRequest{
			Method: "GET",
			Path:   testapi.Extensions.ResourcePathWithPrefix("watch", getHorizontalPodAutoscalersResoureName(), "", ""),
			Query:  url.Values{"resourceVersion": []string{}}},
		Response: Response{StatusCode: 200},
	}
	_, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(api.NamespaceAll).Watch(labels.Everything(), fields.Everything(), "")
	c.Validate(t, nil, err)
}
<commit_msg>fix unit test<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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

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

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

package unversioned

import (
	"net/url"
	"testing"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/testapi"
	"k8s.io/kubernetes/pkg/apis/extensions"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
)

func getHorizontalPodAutoscalersResoureName() string {
	return "horizontalpodautoscalers"
}

func TestHorizontalPodAutoscalerCreate(t *testing.T) {
	ns := api.NamespaceDefault
	horizontalPodAutoscaler := extensions.HorizontalPodAutoscaler{
		ObjectMeta: api.ObjectMeta{
			Name:      "abc",
			Namespace: ns,
		},
	}
	c := &testClient{
		Request: testRequest{
			Method: "POST",
			Path:   testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, ""),
			Query:  buildQueryValues(nil),
			Body:   &horizontalPodAutoscaler,
		},
		Response: Response{StatusCode: 200, Body: &horizontalPodAutoscaler},
	}

	response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Create(&horizontalPodAutoscaler)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerGet(t *testing.T) {
	ns := api.NamespaceDefault
	horizontalPodAutoscaler := &extensions.HorizontalPodAutoscaler{
		ObjectMeta: api.ObjectMeta{
			Name:      "abc",
			Namespace: ns,
		},
	}
	c := &testClient{
		Request: testRequest{
			Method: "GET",
			Path:   testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc"),
			Query:  buildQueryValues(nil),
			Body:   nil,
		},
		Response: Response{StatusCode: 200, Body: horizontalPodAutoscaler},
	}

	response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Get("abc")
	c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerList(t *testing.T) {
	ns := api.NamespaceDefault
	horizontalPodAutoscalerList := &extensions.HorizontalPodAutoscalerList{
		Items: []extensions.HorizontalPodAutoscaler{
			{
				ObjectMeta: api.ObjectMeta{
					Name:      "foo",
					Namespace: ns,
				},
			},
		},
	}
	c := &testClient{
		Request: testRequest{
			Method: "GET",
			Path:   testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, ""),
			Query:  buildQueryValues(nil),
			Body:   nil,
		},
		Response: Response{StatusCode: 200, Body: horizontalPodAutoscalerList},
	}
	response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).List(labels.Everything(), fields.Everything())
	c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerUpdate(t *testing.T) {
	ns := api.NamespaceDefault
	horizontalPodAutoscaler := &extensions.HorizontalPodAutoscaler{
		ObjectMeta: api.ObjectMeta{
			Name:            "abc",
			Namespace:       ns,
			ResourceVersion: "1",
		},
	}
	c := &testClient{
		Request:  testRequest{Method: "PUT", Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc"), Query: buildQueryValues(nil)},
		Response: Response{StatusCode: 200, Body: horizontalPodAutoscaler},
	}
	response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Update(horizontalPodAutoscaler)
	c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerUpdateStatus(t *testing.T) {
	ns := api.NamespaceDefault
	horizontalPodAutoscaler := &extensions.HorizontalPodAutoscaler{
		ObjectMeta: api.ObjectMeta{
			Name:            "abc",
			Namespace:       ns,
			ResourceVersion: "1",
		},
	}
	c := &testClient{
		Request:  testRequest{Method: "PUT", Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc") + "/status", Query: buildQueryValues(nil)},
		Response: Response{StatusCode: 200, Body: horizontalPodAutoscaler},
	}
	response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).UpdateStatus(horizontalPodAutoscaler)
	c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerDelete(t *testing.T) {
	ns := api.NamespaceDefault
	c := &testClient{
		Request:  testRequest{Method: "DELETE", Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "foo"), Query: buildQueryValues(nil)},
		Response: Response{StatusCode: 200},
	}
	err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Delete("foo", nil)
	c.Validate(t, nil, err)
}

func TestHorizontalPodAutoscalerWatch(t *testing.T) {
	c := &testClient{
		Request: testRequest{
			Method: "GET",
			Path:   testapi.Extensions.ResourcePathWithPrefix("watch", getHorizontalPodAutoscalersResoureName(), "", ""),
			Query:  url.Values{"resourceVersion": []string{}}},
		Response: Response{StatusCode: 200},
	}
	_, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(api.NamespaceAll).Watch(labels.Everything(), fields.Everything(), "")
	c.Validate(t, nil, err)
}
<|endoftext|>