<commit_before>package common

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"time"
	"strings"

	"github.com/denverdino/aliyungo/util"
)

// A Client represents a client of ECS services
type Client struct {
	AccessKeyId     string //Access Key Id
	AccessKeySecret string //Access Key Secret
	debug           bool
	httpClient      *http.Client
	endpoint        string
	version         string
}

// NewClient creates a new instance of ECS client
func (client *Client) Init(endpoint, version, accessKeyId, accessKeySecret string) {
	client.AccessKeyId = accessKeyId
	client.AccessKeySecret = accessKeySecret + "&"
	client.debug = false
	client.httpClient = &http.Client{}
	client.endpoint = endpoint
	client.version = version
}

// SetEndpoint sets custom endpoint
func (client *Client) SetEndpoint(endpoint string) {
	client.endpoint = endpoint
}

// SetEndpoint sets custom version
func (client *Client) SetVersion(version string) {
	client.version = version
}

// SetAccessKeyId sets new AccessKeyId
func (client *Client) SetAccessKeyId(id string) {
	client.AccessKeyId = id
}

// SetAccessKeySecret sets new AccessKeySecret
func (client *Client) SetAccessKeySecret(secret string) {
	client.AccessKeySecret = secret + "&"
}

// SetDebug sets debug mode to log the request/response message
func (client *Client) SetDebug(debug bool) {
	client.debug = debug
}

// Invoke sends the raw HTTP request for ECS services
func (client *Client) Invoke(action string, args interface{}, response interface{}) error {

	request := Request{}
	request.init(client.version, action, client.AccessKeyId)

	query := util.ConvertToQueryValues(request)
	util.SetQueryValues(args, &query)

	// Sign request
	signature := util.CreateSignatureForRequest(ECSRequestMethod, &query, client.AccessKeySecret)

	// Generate the request URL
	requestURL := client.endpoint + "?" + query.Encode() + "&Signature=" + url.QueryEscape(signature)

	httpReq, err := http.NewRequest(ECSRequestMethod, requestURL, nil)

	if err != nil {
		return GetClientError(err)
	}

	// TODO move to util and add build val flag
	httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version)

	t0 := time.Now()
	httpResp, err := client.httpClient.Do(httpReq)
	t1 := time.Now()
	if err != nil {
		return GetClientError(err)
	}
	statusCode := httpResp.StatusCode

	if client.debug {
		log.Printf("Invoke %s %s %d (%v)", ECSRequestMethod, requestURL, statusCode, t1.Sub(t0))
	}

	defer httpResp.Body.Close()
	body, err := ioutil.ReadAll(httpResp.Body)

	if err != nil {
		return GetClientError(err)
	}

	if client.debug {
		var prettyJSON bytes.Buffer
		err = json.Indent(&prettyJSON, body, "", "    ")
		log.Println(string(prettyJSON.Bytes()))
	}

	if statusCode >= 400 && statusCode <= 599 {
		errorResponse := ErrorResponse{}
		err = json.Unmarshal(body, &errorResponse)
		ecsError := &Error{
			ErrorResponse: errorResponse,
			StatusCode:    statusCode,
		}
		return ecsError
	}

	err = json.Unmarshal(body, response)
	//log.Printf("%++v", response)
	if err != nil {
		return GetClientError(err)
	}

	return nil
}

// Invoke sends the raw HTTP request for ECS services
//改进了一下上面那个方法，可以使用各种Http方法
//2017.1.30 增加了一个path参数，用来拓展访问的地址
func (client *Client) InvokeByAnyMethod(method, action, path string, args interface{}, response interface{}) error {

	request := Request{}
	request.init(client.version, action, client.AccessKeyId)

	data := util.ConvertToQueryValues(request)
	util.SetQueryValues(args, &data)

	// Sign request
	signature := util.CreateSignatureForRequest(method, &data, client.AccessKeySecret)

	data.Add("Signature", signature)
	// Generate the request URL
	var (
		httpReq *http.Request
		err error
	)
	if method == http.MethodGet {
		requestURL := client.endpoint + path + "?" + data.Encode()
		//fmt.Println(requestURL)
		httpReq, err = http.NewRequest(method, requestURL, nil)
	} else {
		//fmt.Println(client.endpoint + path)
		httpReq, err = http.NewRequest(method, client.endpoint + path, strings.NewReader(data.Encode()))
		httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	}

	if err != nil {
		return GetClientError(err)
	}

	// TODO move to util and add build val flag
	httpReq.Header.Set("X-SDK-Client", `AliyunGO/` + Version)

	t0 := time.Now()
	httpResp, err := client.httpClient.Do(httpReq)
	t1 := time.Now()
	if err != nil {
		return GetClientError(err)
	}
	statusCode := httpResp.StatusCode

	if client.debug {
		log.Printf("Invoke %s %s %d (%v) %v", ECSRequestMethod, client.endpoint, statusCode, t1.Sub(t0), data.Encode())
	}

	defer httpResp.Body.Close()
	body, err := ioutil.ReadAll(httpResp.Body)

	if err != nil {
		return GetClientError(err)
	}

	if client.debug {
		var prettyJSON bytes.Buffer
		err = json.Indent(&prettyJSON, body, "", "    ")
		log.Println(string(prettyJSON.Bytes()))
	}

	if statusCode >= 400 && statusCode <= 599 {
		errorResponse := ErrorResponse{}
		err = json.Unmarshal(body, &errorResponse)
		ecsError := &Error{
			ErrorResponse: errorResponse,
			StatusCode:    statusCode,
		}
		return ecsError
	}

	err = json.Unmarshal(body, response)
	//log.Printf("%++v", response)
	if err != nil {
		return GetClientError(err)
	}

	return nil
}

// GenerateClientToken generates the Client Token with random string
func (client *Client) GenerateClientToken() string {
	return util.CreateRandomString()
}

func GetClientErrorFromString(str string) error {
	return &Error{
		ErrorResponse: ErrorResponse{
			Code:    "AliyunGoClientFailure",
			Message: str,
		},
		StatusCode: -1,
	}
}

func GetClientError(err error) error {
	return GetClientErrorFromString(err.Error())
}
<commit_msg>add buiness info flags<commit_after>package common

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"time"
	"strings"

	"github.com/denverdino/aliyungo/util"
)

// A Client represents a client of ECS services
type Client struct {
	AccessKeyId     string //Access Key Id
	AccessKeySecret string //Access Key Secret
	debug           bool
	httpClient      *http.Client
	endpoint        string
	version         string
	businessInfo	string
}

// NewClient creates a new instance of ECS client
func (client *Client) Init(endpoint, version, accessKeyId, accessKeySecret string) {
	client.AccessKeyId = accessKeyId
	client.AccessKeySecret = accessKeySecret + "&"
	client.debug = false
	client.httpClient = &http.Client{}
	client.endpoint = endpoint
	client.version = version
}

// SetEndpoint sets custom endpoint
func (client *Client) SetEndpoint(endpoint string) {
	client.endpoint = endpoint
}

// SetEndpoint sets custom version
func (client *Client) SetVersion(version string) {
	client.version = version
}

// SetAccessKeyId sets new AccessKeyId
func (client *Client) SetAccessKeyId(id string) {
	client.AccessKeyId = id
}

// SetAccessKeySecret sets new AccessKeySecret
func (client *Client) SetAccessKeySecret(secret string) {
	client.AccessKeySecret = secret + "&"
}

// SetDebug sets debug mode to log the request/response message
func (client *Client) SetDebug(debug bool) {
	client.debug = debug
}

// SetBusinessInfo sets business info to log the request/response message
func (client *Client) SetBusinessInfo(businessInfo string) {
	if strings.HasPrefix(businessInfo,"/"){
		client.businessInfo = businessInfo
	}else if businessInfo != "" {
		client.businessInfo = "/"+businessInfo
	}
}

// Invoke sends the raw HTTP request for ECS services
func (client *Client) Invoke(action string, args interface{}, response interface{}) error {

	request := Request{}
	request.init(client.version, action, client.AccessKeyId)

	query := util.ConvertToQueryValues(request)
	util.SetQueryValues(args, &query)

	// Sign request
	signature := util.CreateSignatureForRequest(ECSRequestMethod, &query, client.AccessKeySecret)

	// Generate the request URL
	requestURL := client.endpoint + "?" + query.Encode() + "&Signature=" + url.QueryEscape(signature)

	httpReq, err := http.NewRequest(ECSRequestMethod, requestURL, nil)

	if err != nil {
		return GetClientError(err)
	}

	// TODO move to util and add build val flag
	httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo)

	t0 := time.Now()
	httpResp, err := client.httpClient.Do(httpReq)
	t1 := time.Now()
	if err != nil {
		return GetClientError(err)
	}
	statusCode := httpResp.StatusCode

	if client.debug {
		log.Printf("Invoke %s %s %d (%v)", ECSRequestMethod, requestURL, statusCode, t1.Sub(t0))
	}

	defer httpResp.Body.Close()
	body, err := ioutil.ReadAll(httpResp.Body)

	if err != nil {
		return GetClientError(err)
	}

	if client.debug {
		var prettyJSON bytes.Buffer
		err = json.Indent(&prettyJSON, body, "", "    ")
		log.Println(string(prettyJSON.Bytes()))
	}

	if statusCode >= 400 && statusCode <= 599 {
		errorResponse := ErrorResponse{}
		err = json.Unmarshal(body, &errorResponse)
		ecsError := &Error{
			ErrorResponse: errorResponse,
			StatusCode:    statusCode,
		}
		return ecsError
	}

	err = json.Unmarshal(body, response)
	//log.Printf("%++v", response)
	if err != nil {
		return GetClientError(err)
	}

	return nil
}

// Invoke sends the raw HTTP request for ECS services
//改进了一下上面那个方法，可以使用各种Http方法
//2017.1.30 增加了一个path参数，用来拓展访问的地址
func (client *Client) InvokeByAnyMethod(method, action, path string, args interface{}, response interface{}) error {

	request := Request{}
	request.init(client.version, action, client.AccessKeyId)

	data := util.ConvertToQueryValues(request)
	util.SetQueryValues(args, &data)

	// Sign request
	signature := util.CreateSignatureForRequest(method, &data, client.AccessKeySecret)

	data.Add("Signature", signature)
	// Generate the request URL
	var (
		httpReq *http.Request
		err error
	)
	if method == http.MethodGet {
		requestURL := client.endpoint + path + "?" + data.Encode()
		//fmt.Println(requestURL)
		httpReq, err = http.NewRequest(method, requestURL, nil)
	} else {
		//fmt.Println(client.endpoint + path)
		httpReq, err = http.NewRequest(method, client.endpoint + path, strings.NewReader(data.Encode()))
		httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	}

	if err != nil {
		return GetClientError(err)
	}

	// TODO move to util and add build val flag
	httpReq.Header.Set("X-SDK-Client", `AliyunGO/` + Version + client.businessInfo)

	t0 := time.Now()
	httpResp, err := client.httpClient.Do(httpReq)
	t1 := time.Now()
	if err != nil {
		return GetClientError(err)
	}
	statusCode := httpResp.StatusCode

	if client.debug {
		log.Printf("Invoke %s %s %d (%v) %v", ECSRequestMethod, client.endpoint, statusCode, t1.Sub(t0), data.Encode())
	}

	defer httpResp.Body.Close()
	body, err := ioutil.ReadAll(httpResp.Body)

	if err != nil {
		return GetClientError(err)
	}

	if client.debug {
		var prettyJSON bytes.Buffer
		err = json.Indent(&prettyJSON, body, "", "    ")
		log.Println(string(prettyJSON.Bytes()))
	}

	if statusCode >= 400 && statusCode <= 599 {
		errorResponse := ErrorResponse{}
		err = json.Unmarshal(body, &errorResponse)
		ecsError := &Error{
			ErrorResponse: errorResponse,
			StatusCode:    statusCode,
		}
		return ecsError
	}

	err = json.Unmarshal(body, response)
	//log.Printf("%++v", response)
	if err != nil {
		return GetClientError(err)
	}

	return nil
}

// GenerateClientToken generates the Client Token with random string
func (client *Client) GenerateClientToken() string {
	return util.CreateRandomString()
}

func GetClientErrorFromString(str string) error {
	return &Error{
		ErrorResponse: ErrorResponse{
			Code:    "AliyunGoClientFailure",
			Message: str,
		},
		StatusCode: -1,
	}
}

func GetClientError(err error) error {
	return GetClientErrorFromString(err.Error())
}
<|endoftext|>


<commit_before>package common

// transfer2go agent common structures
// Copyright (c) 2017 - Valentin Kuznetsov <vkuznet@gmail.com>

// AgentStatus data type
type AgentStatus struct {
	Url             string            `json:"url"`      // agent url
	Name            string            `json:"name"`     // agent name or alias
	TimeStamp       int64             `json:"ts"`       // time stamp
	TransferCounter int32             `json:"tc"`       // number of transfers at a given time
	Catalog         string            `json:"catalog"`  // underlying TFC catalog
	Protocol        string            `json:"protocol"` // underlying transfer protocol
	Backend         string            `json:"backend"`  // underlying transfer backend
	Tool            string            `json:"tool"`     // underlying transfer tool, e.g. xrdcp
	ToolOpts        string            `json:"toolopts"` // options for backend tool
	Agents          map[string]string `json:"agents"`   // list of known agents
	Addrs           []string          `json:"addrs"`    // list of all IP addresses
}
<commit_msg>Add String method<commit_after>package common

import "fmt"

// transfer2go agent common structures
// Copyright (c) 2017 - Valentin Kuznetsov <vkuznet@gmail.com>

// AgentStatus data type
type AgentStatus struct {
	Url             string            `json:"url"`      // agent url
	Name            string            `json:"name"`     // agent name or alias
	TimeStamp       int64             `json:"ts"`       // time stamp
	TransferCounter int32             `json:"tc"`       // number of transfers at a given time
	Catalog         string            `json:"catalog"`  // underlying TFC catalog
	Protocol        string            `json:"protocol"` // underlying transfer protocol
	Backend         string            `json:"backend"`  // underlying transfer backend
	Tool            string            `json:"tool"`     // underlying transfer tool, e.g. xrdcp
	ToolOpts        string            `json:"toolopts"` // options for backend tool
	Agents          map[string]string `json:"agents"`   // list of known agents
	Addrs           []string          `json:"addrs"`    // list of all IP addresses
}

// String provides string representation of given agent status
func (a *AgentStatus) String() string {
	return fmt.Sprintf("<Agent name=%s url=%s catalog=%s protocol=%s backend=%s tool=%s toolOpts=%s transfers=%d agents=%v addrs=%v>", a.Name, a.Url, a.Catalog, a.Protocol, a.Backend, a.Tool, a.ToolOpts, a.TransferCounter, a.Agents, a.Addrs)
}
<|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 common shares methods for local builder.
package common

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"time"

	pb "google.golang.org/genproto/googleapis/devtools/cloudbuild/v1"
	"github.com/GoogleCloudPlatform/cloud-build-local/runner"
	"github.com/GoogleCloudPlatform/cloud-build-local/subst"
	"github.com/GoogleCloudPlatform/cloud-build-local/validate"
	"golang.org/x/oauth2"
)

const (
	backoffFactor = 1.3 // backoff increases by this factor on each retry
	backoffRange  = 0.4 // backoff is randomized downwards by this factor
)

// Backoff returns a value in [0, maxDelay] that increases exponentially with
// retries, starting from baseDelay.
func Backoff(baseDelay, maxDelay time.Duration, retries int) time.Duration {
	backoff, max := float64(baseDelay), float64(maxDelay)
	for backoff < max && retries > 0 {
		backoff = backoff * backoffFactor
		retries--
	}
	if backoff > max {
		backoff = max
	}

	// Randomize backoff delays so that if a cluster of requests start at
	// the same time, they won't operate in lockstep.  We just subtract up
	// to 40% so that we obey maxDelay.
	backoff -= backoff * backoffRange * rand.Float64()
	if backoff < 0 {
		return 0
	}
	return time.Duration(backoff)
}

// ParseSubstitutionsFlag parses a substitutions string into a map.
func ParseSubstitutionsFlag(substitutions string) (map[string]string, error) {
	substitutionsMap := make(map[string]string)
	list := strings.Split(substitutions, ",")
	for _, s := range list {
		keyValue := strings.Split(s, "=")
		if len(keyValue) != 2 {
			return substitutionsMap, fmt.Errorf("The substitution key value pair is not valid: %s", s)
		}
		substitutionsMap[strings.TrimSpace(keyValue[0])] = strings.TrimSpace(keyValue[1])
	}
	return substitutionsMap, nil
}

// Clean removes left-over containers, networks, and volumes from a previous
// run of the local builder. This happens when ctrl+c is used during a local
// build.
// Each cleaning is defined by a get command, a warning to print if the get
// command returns something, and a delete command to apply in that case.
func Clean(ctx context.Context, r runner.Runner) error {
	items := []struct {
		getCmd, deleteCmd []string
		warning           string
	}{{
		getCmd:    []string{"docker", "ps", "-a", "-q", "--filter", "name=step_[0-9]+|cloudbuild_|metadata"},
		deleteCmd: []string{"docker", "rm", "-f"},
		warning:   "Warning: there are left over step containers from a previous build, cleaning them.",
	}, {
		getCmd:    []string{"docker", "network", "ls", "-q", "--filter", "name=cloudbuild"},
		deleteCmd: []string{"docker", "network", "rm"},
		warning:   "Warning: a network is left over from a previous build, cleaning it.",
	}, {
		getCmd:    []string{"docker", "volume", "ls", "-q", "--filter", "name=homevol|cloudbuild_"},
		deleteCmd: []string{"docker", "volume", "rm"},
		warning:   "Warning: there are left over step volumes from a previous build, cleaning it.",
	}}

	for _, item := range items {
		var output bytes.Buffer
		if err := r.Run(ctx, item.getCmd, nil, &output, os.Stderr, ""); err != nil {
			return err
		}

		str := strings.TrimSpace(output.String())
		if str == "" {
			continue
		}
		log.Println(item.warning)

		args := strings.Split(str, "\n")
		deleteCmd := append(item.deleteCmd, args...)
		if err := r.Run(ctx, deleteCmd, nil, nil, os.Stderr, ""); err != nil {
			return err
		}
	}

	return nil
}

// For unit tests.
var now = time.Now

// RefreshDuration calculates when to refresh the access token. We refresh a
// bit prior to the token's expiration.
func RefreshDuration(expiration time.Time) time.Duration {
	d := expiration.Sub(now())
	if d > 4*time.Second {
		d = time.Duration(float64(d)*.75) + time.Second
	}

	return d
}

// SubstituteAndValidate merges the substitutions from the build
// config and the local flag, validates them and the build.
func SubstituteAndValidate(b *pb.Build, substMap map[string]string) error {
	if b.Substitutions == nil {
		b.Substitutions = make(map[string]string)
	}

	// Do not accept built-in substitutions in the build config.
	if err := validate.CheckSubstitutions(b.Substitutions); err != nil {
		return fmt.Errorf("Error validating build's substitutions: %v", err)
	}

	if err := validate.CheckSubstitutionsLoose(substMap); err != nil {
		return err
	}
	// Add any flag substitutions to the existing substitution map.
	// If the substitution is already defined in the template, the
	// substitutions flag will override the value.
	for k, v := range substMap {
		b.Substitutions[k] = v
	}

	// Validate the build.
	if err := validate.CheckBuild(b); err != nil {
		return fmt.Errorf("Error validating build: %v", err)
	}

	// Apply substitutions.
	if err := subst.SubstituteBuildFields(b); err != nil {
		return fmt.Errorf("Error applying substitutions: %v", err)
	}

	// Validate the build after substitutions.
	if err := validate.CheckBuildAfterSubstitutions(b); err != nil {
		return fmt.Errorf("Error validating build after substitutions: %v", err)
	}

	return nil
}

// TokenTransport is a RoundTripper that automatically applies OAuth
// credentials from the token source.
//
// This can be replaced by google.DefaultClient when metadata spoofing works by
// IP address (b/33233310).
type TokenTransport struct {
	Ts oauth2.TokenSource
}

// RoundTrip executes a single HTTP transaction, obtaining the Response for a given Request.
func (t *TokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	tok, err := t.Ts.Token()
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+tok.AccessToken)
	return http.DefaultTransport.RoundTrip(req)
}
<commit_msg>Project import generated by Copybara.<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 common shares methods for local builder.
package common

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"time"

	pb "google.golang.org/genproto/googleapis/devtools/cloudbuild/v1"
	"github.com/GoogleCloudPlatform/cloud-build-local/runner"
	"github.com/GoogleCloudPlatform/cloud-build-local/subst"
	"github.com/GoogleCloudPlatform/cloud-build-local/validate"
	"golang.org/x/oauth2"
)

const (
	backoffFactor = 1.3 // backoff increases by this factor on each retry
	backoffRange  = 0.4 // backoff is randomized downwards by this factor
)

// Backoff returns a value in [0, maxDelay] that increases exponentially with
// retries, starting from baseDelay.
func Backoff(baseDelay, maxDelay time.Duration, retries int) time.Duration {
	backoff, max := float64(baseDelay), float64(maxDelay)
	for backoff < max && retries > 0 {
		backoff = backoff * backoffFactor
		retries--
	}
	if backoff > max {
		backoff = max
	}

	// Randomize backoff delays so that if a cluster of requests start at
	// the same time, they won't operate in lockstep.  We just subtract up
	// to 40% so that we obey maxDelay.
	backoff -= backoff * backoffRange * rand.Float64()
	if backoff < 0 {
		return 0
	}
	return time.Duration(backoff)
}

// ParseSubstitutionsFlag parses a substitutions string into a map.
func ParseSubstitutionsFlag(substitutions string) (map[string]string, error) {
	substitutionsMap := make(map[string]string)
	list := strings.Split(substitutions, ",")
	for _, s := range list {
		keyValue := strings.Split(s, "=")
		if len(keyValue) != 2 {
			return substitutionsMap, fmt.Errorf("The substitution key value pair is not valid: %s", s)
		}
		substitutionsMap[strings.TrimSpace(keyValue[0])] = strings.TrimSpace(keyValue[1])
	}
	return substitutionsMap, nil
}

// Clean removes left-over containers, networks, and volumes from a previous
// run of the local builder. This happens when ctrl+c is used during a local
// build.
// Each cleaning is defined by a get command, a warning to print if the get
// command returns something, and a delete command to apply in that case.
func Clean(ctx context.Context, r runner.Runner) error {
	items := []struct {
		getCmd, deleteCmd []string
		warning           string
	}{{
		getCmd:    []string{"docker", "ps", "-a", "-q", "--filter", "name=step_[0-9]+|cloudbuild_|metadata"},
		deleteCmd: []string{"docker", "rm", "-f"},
		warning:   "Warning: there are left over step containers from a previous build, cleaning them.",
	}, {
		getCmd:    []string{"docker", "network", "ls", "-q", "--filter", "name=cloudbuild"},
		deleteCmd: []string{"docker", "network", "rm"},
		warning:   "Warning: a network is left over from a previous build, cleaning it.",
	}, {
		getCmd:    []string{"docker", "volume", "ls", "-q", "--filter", "name=homevol|cloudbuild_"},
		deleteCmd: []string{"docker", "volume", "rm"},
		warning:   "Warning: there are left over step volumes from a previous build, cleaning it.",
	}}

	for _, item := range items {
		var output bytes.Buffer
		if err := r.Run(ctx, item.getCmd, nil, &output, os.Stderr, ""); err != nil {
			return err
		}

		str := strings.TrimSpace(output.String())
		if str == "" {
			continue
		}
		log.Println(item.warning)

		args := strings.Split(str, "\n")
		deleteCmd := append(item.deleteCmd, args...)
		if err := r.Run(ctx, deleteCmd, nil, nil, os.Stderr, ""); err != nil {
			return err
		}
	}

	return nil
}

// For unit tests.
var now = time.Now

// RefreshDuration calculates when to refresh the access token. We refresh a
// bit prior to the token's expiration.
func RefreshDuration(expiration time.Time) time.Duration {
	d := expiration.Sub(now())
	if d > 4*time.Second {
		d = time.Duration(float64(d)*.75) + time.Second
	}

	return d
}

// SubstituteAndValidate merges the substitutions from the build
// config and the local flag, validates them and the build.
func SubstituteAndValidate(b *pb.Build, substMap map[string]string) error {
	if b.Substitutions == nil {
		b.Substitutions = make(map[string]string)
	}

	// Do not accept built-in substitutions in the build config.
	if err := validate.CheckSubstitutions(b.Substitutions); err != nil {
		return fmt.Errorf("Error validating build's substitutions: %v", err)
	}

	if err := validate.CheckSubstitutionsLoose(substMap); err != nil {
		return err
	}
	// Add any flag substitutions to the existing substitution map.
	// If the substitution is already defined in the template, the
	// substitutions flag will override the value.
	for k, v := range substMap {
		b.Substitutions[k] = v
	}

	// Validate the build.
	if err := validate.CheckBuild(b); err != nil {
		return fmt.Errorf("Error validating build: %v", err)
	}

	// Apply substitutions.
	if err := subst.SubstituteBuildFields(b); err != nil {
		return fmt.Errorf("Error applying substitutions: %v", err)
	}

	// Validate the build after substitutions.
	if err := validate.CheckBuildAfterSubstitutions(b); err != nil {
		return fmt.Errorf("Error validating build after substitutions: %v", err)
	}

	return nil
}

// TokenTransport is a RoundTripper that automatically applies OAuth
// credentials from the token source.
type TokenTransport struct {
	Ts oauth2.TokenSource
}

// RoundTrip executes a single HTTP transaction, obtaining the Response for a given Request.
func (t *TokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	tok, err := t.Ts.Token()
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+tok.AccessToken)
	return http.DefaultTransport.RoundTrip(req)
}
<|endoftext|>


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

package upstream

import (
	"encoding/binary"
	"fmt"
	"sync"
	"time"

	zmq "github.com/pebbe/zmq4"

	"github.com/bitmark-inc/bitmarkd/announce"
	"github.com/bitmark-inc/bitmarkd/blockdigest"
	"github.com/bitmark-inc/bitmarkd/blockheader"
	"github.com/bitmark-inc/bitmarkd/counter"
	"github.com/bitmark-inc/bitmarkd/messagebus"
	"github.com/bitmark-inc/bitmarkd/mode"
	"github.com/bitmark-inc/bitmarkd/util"
	"github.com/bitmark-inc/bitmarkd/zmqutil"
	"github.com/bitmark-inc/logger"
)

// Upstream - upstream connection
type Upstream interface {
	ActiveInThePast(time.Duration) bool
	CachedRemoteDigestOfLocalHeight() blockdigest.Digest
	CachedRemoteHeight() uint64
	Connect(*util.Connection, []byte) error
	ConnectedTo() *zmqutil.Connected
	Destroy()
	GetBlockData(uint64) ([]byte, error)
	IsConnectedTo([]byte) bool
	IsConnected() bool
	LocalHeight() uint64
	Name() string
	RemoteAddr() (string, error)
	RemoteDigestOfHeight(uint64) (blockdigest.Digest, error)
	RemoteHeight() (uint64, error)
	ResetServer()
	ServerPublicKey() []byte
}

// upstreamData - structure to hold an upstream connection
type upstreamData struct {
	Upstream
	sync.RWMutex

	log                       *logger.L
	name                      string
	client                    zmqutil.Client
	connected                 bool
	remoteHeight              uint64
	localHeight               uint64
	remoteDigestOfLocalHeight blockdigest.Digest
	shutdown                  chan<- struct{}
	lastResponseTime          time.Time
}

const (
	cycleInterval = 30 * time.Second
)

// state of connection
type connectedState int

const (
	stateDisconnected connectedState = iota
	stateConnected    connectedState = iota
)

// atomically incremented counter for log names
var upstreamCounter counter.Counter

// New - create a connection to an upstream server
func New(privateKey []byte, publicKey []byte, timeout time.Duration) (Upstream, error) {

	client, event, err := zmqutil.NewClient(zmq.REQ, privateKey, publicKey, timeout, zmq.EVENT_ALL)
	if nil != err {
		return nil, err
	}

	n := upstreamCounter.Increment()

	shutdown := make(chan struct{})
	upstreamStr := fmt.Sprintf("upstream@%d", n)
	u := &upstreamData{
		name:      upstreamStr,
		log:       logger.New(upstreamStr),
		client:    client,
		connected: false,
		shutdown:  shutdown,
	}
	go u.runner(shutdown)
	go u.poller(shutdown, event)
	return u, nil
}

// loop to handle upstream communication
func (u *upstreamData) runner(shutdown <-chan struct{}) {
	log := u.log

	log.Debug("starting…")

	// use default queue size
	queue := messagebus.Bus.Broadcast.Chan(messagebus.Default)
	cycleTimer := time.After(cycleInterval)

loop:
	for {
		log.Debug("waiting…")

		select {
		case <-shutdown:
			break loop

		case <-cycleTimer:
			cycleTimer = time.After(cycleInterval)

			u.RLock()
			if u.connected {
				u.RUnlock()

				remoteHeight, err := u.height()
				if nil == err {
					u.lastResponseTime = time.Now()

					u.Lock()
					u.remoteHeight = remoteHeight
					u.Unlock()

					publicKey := u.client.ServerPublicKey()
					messagebus.Bus.Announce.Send("updatetime", publicKey)
				} else {
					log.Warnf("highest block error: %s", err)
				}

				localHeight := blockheader.Height()
				digest, err := u.RemoteDigestOfHeight(localHeight)
				if nil != err {
					log.Errorf("getBlockDigest error: %s", err)
					continue loop
				}
				u.Lock()
				u.localHeight = localHeight
				u.remoteDigestOfLocalHeight = digest
				u.Unlock()
			} else {
				u.RUnlock()
				log.Trace("upstream not connected")
			}

		case item := <-queue:
			log.Debugf("from queue: %q  %x", item.Command, item.Parameters)

			u.RLock()
			if u.connected {
				u.RUnlock()
				err := u.push(&item)
				if nil != err {
					log.Errorf("push: error: %s", err)
				}
			} else {
				u.RUnlock()
				log.Trace("upstream not connected")
			}
		}
	}
	log.Info("shutting down…")
	u.client.Close()
	log.Info("stopped")
}

// start polling the socket
//
// it should be called as a goroutine to avoid blocking
func (u *upstreamData) poller(shutdown <-chan struct{}, event <-chan zmqutil.Event) {

	log := u.log

	log.Debug("start polling…")

	// assumes connected
	state := stateDisconnected

loop:
	for {
		select {
		case <-shutdown:
			break loop
		case e := <-event:
			u.handleEvent(e, &state)
		}
	}
	log.Debug("stopped polling")
}

// process the socket events
func (u *upstreamData) handleEvent(event zmqutil.Event, state *connectedState) {
	log := u.log

	switch event.Event {
	case zmqutil.EVENT_DISCONNECTED,
		zmqutil.EVENT_CLOSED,
		zmqutil.EVENT_CONNECT_RETRIED,
		zmqutil.EVENT_HANDSHAKE_FAILED_NO_DETAIL,
		zmqutil.EVENT_HANDSHAKE_FAILED_PROTOCOL,
		zmqutil.EVENT_HANDSHAKE_FAILED_AUTH:

		log.Warnf("socket %q is disconnected. event: %q (0x%x)", event.Address, event.Event, int(event.Event))

		if *state == stateConnected {
			*state = stateDisconnected

			u.Lock()
			u.connected = false
			u.Unlock()

			// the socket is automatically recovered after disconnected by zmq is not useful.
			// the request by this socket always return error `resource temporarily unavailable`
			// try to close/open the socket makes the socket works as expectation.
			log.Infof("reconnecting to %q", event.Address)
			err := u.client.Reconnect()
			if nil != err {
				u.log.Warnf("reconnect error: %s", err)
				return
			}
			log.Infof("reconnect to %q successful", event.Address)
		}

	case zmqutil.EVENT_CONNECT_DELAYED:
		log.Infof("socket %q is connected. event: %q (0x%x)", event.Address, event.Event, int(event.Event))

	case zmqutil.EVENT_CONNECTED, zmqutil.EVENT_HANDSHAKE_SUCCEEDED:

		log.Infof("socket %q is connected. event: %q (0x%x)", event.Address, event.Event, int(event.Event))

		if *state == stateDisconnected {
			err := u.requestBlockchainInfo()
			if nil == err {
				*state = stateConnected
				u.Lock()
				u.connected = true
				u.Unlock()
			} else {
				u.log.Debugf("request peer connection error: %s", err)
			}
		}

	default:
		log.Warnf("socket %q unhandled event: %q (0x%x) value: %d", event.Address, event.Event, int(event.Event), event.Value)
	}

}

// register with server and check chain information
func (u *upstreamData) requestBlockchainInfo() error {
	log := u.log
	client := u.client
	log.Debugf("register: client: %s", client)

	u.RLock()
	err := announce.SendRegistration(client, "R")
	if nil != err {
		u.RUnlock()
		log.Errorf("register: %s send error: %s", client, err)
		return err
	}
	data, err := client.Receive(0)
	u.RUnlock()

	if nil != err {
		log.Errorf("register: %s receive error: %s", client, err)
		return err
	}

	if len(data) < 2 {
		return fmt.Errorf("register: received: %d  expected at least: 2", len(data))
	}

	switch string(data[0]) {
	case "E":
		return fmt.Errorf("register: connection error: %q", data[1])
	case "R":
		if len(data) < 5 {
			return fmt.Errorf("register: response error: %x", data)
		}
		chain := mode.ChainName()
		received := string(data[1])
		if received != chain {
			log.Errorf("connection refused. Expected chain: %q but received: %q", chain, received)
			return fmt.Errorf("connection refused.  expected chain: %q but received: %q ", chain, received)
		}
		timestamp := binary.BigEndian.Uint64(data[4])
		log.Infof("connection established. register replied: public key: %x:  listeners: %x  timestamp: %d", data[2], data[3], timestamp)
		announce.AddPeer(data[2], data[3], timestamp) // publicKey, broadcasts, listeners
		return nil
	default:
		return fmt.Errorf("register: unexpected response: %q", data[0])
	}
}

func (u *upstreamData) height() (uint64, error) {
	log := u.log
	client := u.client
	log.Infof("height: client: %s", client)

	u.RLock()
	err := client.Send("N")
	if nil != err {
		u.RUnlock()
		log.Errorf("height: %s send error: %s", client, err)
		return 0, err
	}

	data, err := client.Receive(0)
	u.RUnlock()

	if nil != err {
		log.Errorf("height: %s receive error: %s", client, err)
		return 0, err
	}
	if 2 != len(data) {
		return 0, fmt.Errorf("height received: %d  expected: 2", len(data))
	}

	switch string(data[0]) {
	case "E":
		return 0, fmt.Errorf("height: error response: %q", data[1])
	case "N":
		if 8 != len(data[1]) {
			return 0, fmt.Errorf("hight: invalid response: %q", data[1])
		}
		height := binary.BigEndian.Uint64(data[1])
		log.Infof("height: %d", height)
		return height, nil
	default:
		return 0, fmt.Errorf("height: unexpected response: %q", data[0])
	}
}

func (u *upstreamData) push(item *messagebus.Message) error {
	log := u.log
	client := u.client
	log.Infof("push: client: %s  %q %x", client, item.Command, item.Parameters)

	u.RLock()
	err := client.Send(item.Command, item.Parameters)
	if nil != err {
		u.RUnlock()
		log.Errorf("push: %s send error: %s", client, err)
		return err
	}

	data, err := client.Receive(0)
	u.RUnlock()

	if nil != err {
		log.Errorf("push: %s receive error: %s", client, err)
		return err
	}
	if 2 != len(data) {
		return fmt.Errorf("push received: %d  expected: 2", len(data))
	}

	switch string(data[0]) {
	case "E":
		return fmt.Errorf("push: error response: %q", data[1])
	case item.Command:
		log.Debugf("push: client: %s complete: %q", client, data[1])
		return nil
	default:
		return fmt.Errorf("push: unexpected response: %q", data[0])
	}
}
<commit_msg>[peer] change error to warning<commit_after>// SPDX-License-Identifier: ISC
// Copyright (c) 2014-2019 Bitmark Inc.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package upstream

import (
	"encoding/binary"
	"fmt"
	"sync"
	"time"

	zmq "github.com/pebbe/zmq4"

	"github.com/bitmark-inc/bitmarkd/announce"
	"github.com/bitmark-inc/bitmarkd/blockdigest"
	"github.com/bitmark-inc/bitmarkd/blockheader"
	"github.com/bitmark-inc/bitmarkd/counter"
	"github.com/bitmark-inc/bitmarkd/messagebus"
	"github.com/bitmark-inc/bitmarkd/mode"
	"github.com/bitmark-inc/bitmarkd/util"
	"github.com/bitmark-inc/bitmarkd/zmqutil"
	"github.com/bitmark-inc/logger"
)

// Upstream - upstream connection
type Upstream interface {
	ActiveInThePast(time.Duration) bool
	CachedRemoteDigestOfLocalHeight() blockdigest.Digest
	CachedRemoteHeight() uint64
	Connect(*util.Connection, []byte) error
	ConnectedTo() *zmqutil.Connected
	Destroy()
	GetBlockData(uint64) ([]byte, error)
	IsConnectedTo([]byte) bool
	IsConnected() bool
	LocalHeight() uint64
	Name() string
	RemoteAddr() (string, error)
	RemoteDigestOfHeight(uint64) (blockdigest.Digest, error)
	RemoteHeight() (uint64, error)
	ResetServer()
	ServerPublicKey() []byte
}

// upstreamData - structure to hold an upstream connection
type upstreamData struct {
	Upstream
	sync.RWMutex

	log                       *logger.L
	name                      string
	client                    zmqutil.Client
	connected                 bool
	remoteHeight              uint64
	localHeight               uint64
	remoteDigestOfLocalHeight blockdigest.Digest
	shutdown                  chan<- struct{}
	lastResponseTime          time.Time
}

const (
	cycleInterval = 30 * time.Second
)

// state of connection
type connectedState int

const (
	stateDisconnected connectedState = iota
	stateConnected    connectedState = iota
)

// atomically incremented counter for log names
var upstreamCounter counter.Counter

// New - create a connection to an upstream server
func New(privateKey []byte, publicKey []byte, timeout time.Duration) (Upstream, error) {

	client, event, err := zmqutil.NewClient(zmq.REQ, privateKey, publicKey, timeout, zmq.EVENT_ALL)
	if nil != err {
		return nil, err
	}

	n := upstreamCounter.Increment()

	shutdown := make(chan struct{})
	upstreamStr := fmt.Sprintf("upstream@%d", n)
	u := &upstreamData{
		name:      upstreamStr,
		log:       logger.New(upstreamStr),
		client:    client,
		connected: false,
		shutdown:  shutdown,
	}
	go u.runner(shutdown)
	go u.poller(shutdown, event)
	return u, nil
}

// loop to handle upstream communication
func (u *upstreamData) runner(shutdown <-chan struct{}) {
	log := u.log

	log.Debug("starting…")

	// use default queue size
	queue := messagebus.Bus.Broadcast.Chan(messagebus.Default)
	cycleTimer := time.After(cycleInterval)

loop:
	for {
		log.Debug("waiting…")

		select {
		case <-shutdown:
			break loop

		case <-cycleTimer:
			cycleTimer = time.After(cycleInterval)

			u.RLock()
			if u.connected {
				u.RUnlock()

				remoteHeight, err := u.height()
				if nil == err {
					u.lastResponseTime = time.Now()

					u.Lock()
					u.remoteHeight = remoteHeight
					u.Unlock()

					publicKey := u.client.ServerPublicKey()
					messagebus.Bus.Announce.Send("updatetime", publicKey)
				} else {
					log.Warnf("highest block error: %s", err)
				}

				localHeight := blockheader.Height()
				digest, err := u.RemoteDigestOfHeight(localHeight)
				if nil != err {
					log.Warnf("remote digest for local height: %d  error: %s", localHeight, err)
					continue loop
				}
				u.Lock()
				u.localHeight = localHeight
				u.remoteDigestOfLocalHeight = digest
				u.Unlock()
			} else {
				u.RUnlock()
				log.Trace("upstream not connected")
			}

		case item := <-queue:
			log.Debugf("from queue: %q  %x", item.Command, item.Parameters)

			u.RLock()
			if u.connected {
				u.RUnlock()
				err := u.push(&item)
				if nil != err {
					log.Errorf("push: error: %s", err)
				}
			} else {
				u.RUnlock()
				log.Trace("upstream not connected")
			}
		}
	}
	log.Info("shutting down…")
	u.client.Close()
	log.Info("stopped")
}

// start polling the socket
//
// it should be called as a goroutine to avoid blocking
func (u *upstreamData) poller(shutdown <-chan struct{}, event <-chan zmqutil.Event) {

	log := u.log

	log.Debug("start polling…")

	// assumes connected
	state := stateDisconnected

loop:
	for {
		select {
		case <-shutdown:
			break loop
		case e := <-event:
			u.handleEvent(e, &state)
		}
	}
	log.Debug("stopped polling")
}

// process the socket events
func (u *upstreamData) handleEvent(event zmqutil.Event, state *connectedState) {
	log := u.log

	switch event.Event {
	case zmqutil.EVENT_DISCONNECTED,
		zmqutil.EVENT_CLOSED,
		zmqutil.EVENT_CONNECT_RETRIED,
		zmqutil.EVENT_HANDSHAKE_FAILED_NO_DETAIL,
		zmqutil.EVENT_HANDSHAKE_FAILED_PROTOCOL,
		zmqutil.EVENT_HANDSHAKE_FAILED_AUTH:

		log.Warnf("socket %q is disconnected. event: %q (0x%x)", event.Address, event.Event, int(event.Event))

		if *state == stateConnected {
			*state = stateDisconnected

			u.Lock()
			u.connected = false
			u.Unlock()

			// the socket is automatically recovered after disconnected by zmq is not useful.
			// the request by this socket always return error `resource temporarily unavailable`
			// try to close/open the socket makes the socket works as expectation.
			log.Infof("reconnecting to %q", event.Address)
			err := u.client.Reconnect()
			if nil != err {
				u.log.Warnf("reconnect error: %s", err)
				return
			}
			log.Infof("reconnect to %q successful", event.Address)
		}

	case zmqutil.EVENT_CONNECT_DELAYED:
		log.Infof("socket %q is connected. event: %q (0x%x)", event.Address, event.Event, int(event.Event))

	case zmqutil.EVENT_CONNECTED, zmqutil.EVENT_HANDSHAKE_SUCCEEDED:

		log.Infof("socket %q is connected. event: %q (0x%x)", event.Address, event.Event, int(event.Event))

		if *state == stateDisconnected {
			err := u.requestBlockchainInfo()
			if nil == err {
				*state = stateConnected
				u.Lock()
				u.connected = true
				u.Unlock()
			} else {
				u.log.Debugf("request peer connection error: %s", err)
			}
		}

	default:
		log.Warnf("socket %q unhandled event: %q (0x%x) value: %d", event.Address, event.Event, int(event.Event), event.Value)
	}

}

// register with server and check chain information
func (u *upstreamData) requestBlockchainInfo() error {
	log := u.log
	client := u.client
	log.Debugf("register: client: %s", client)

	u.RLock()
	err := announce.SendRegistration(client, "R")
	if nil != err {
		u.RUnlock()
		log.Errorf("register: %s send error: %s", client, err)
		return err
	}
	data, err := client.Receive(0)
	u.RUnlock()

	if nil != err {
		log.Errorf("register: %s receive error: %s", client, err)
		return err
	}

	if len(data) < 2 {
		return fmt.Errorf("register: received: %d  expected at least: 2", len(data))
	}

	switch string(data[0]) {
	case "E":
		return fmt.Errorf("register: connection error: %q", data[1])
	case "R":
		if len(data) < 5 {
			return fmt.Errorf("register: response error: %x", data)
		}
		chain := mode.ChainName()
		received := string(data[1])
		if received != chain {
			log.Errorf("connection refused. Expected chain: %q but received: %q", chain, received)
			return fmt.Errorf("connection refused.  expected chain: %q but received: %q ", chain, received)
		}
		timestamp := binary.BigEndian.Uint64(data[4])
		log.Infof("connection established. register replied: public key: %x:  listeners: %x  timestamp: %d", data[2], data[3], timestamp)
		announce.AddPeer(data[2], data[3], timestamp) // publicKey, broadcasts, listeners
		return nil
	default:
		return fmt.Errorf("register: unexpected response: %q", data[0])
	}
}

func (u *upstreamData) height() (uint64, error) {
	log := u.log
	client := u.client
	log.Infof("height: client: %s", client)

	u.RLock()
	err := client.Send("N")
	if nil != err {
		u.RUnlock()
		log.Errorf("height: %s send error: %s", client, err)
		return 0, err
	}

	data, err := client.Receive(0)
	u.RUnlock()

	if nil != err {
		log.Errorf("height: %s receive error: %s", client, err)
		return 0, err
	}
	if 2 != len(data) {
		return 0, fmt.Errorf("height received: %d  expected: 2", len(data))
	}

	switch string(data[0]) {
	case "E":
		return 0, fmt.Errorf("height: error response: %q", data[1])
	case "N":
		if 8 != len(data[1]) {
			return 0, fmt.Errorf("hight: invalid response: %q", data[1])
		}
		height := binary.BigEndian.Uint64(data[1])
		log.Infof("height: %d", height)
		return height, nil
	default:
		return 0, fmt.Errorf("height: unexpected response: %q", data[0])
	}
}

func (u *upstreamData) push(item *messagebus.Message) error {
	log := u.log
	client := u.client
	log.Infof("push: client: %s  %q %x", client, item.Command, item.Parameters)

	u.RLock()
	err := client.Send(item.Command, item.Parameters)
	if nil != err {
		u.RUnlock()
		log.Errorf("push: %s send error: %s", client, err)
		return err
	}

	data, err := client.Receive(0)
	u.RUnlock()

	if nil != err {
		log.Errorf("push: %s receive error: %s", client, err)
		return err
	}
	if 2 != len(data) {
		return fmt.Errorf("push received: %d  expected: 2", len(data))
	}

	switch string(data[0]) {
	case "E":
		return fmt.Errorf("push: error response: %q", data[1])
	case item.Command:
		log.Debugf("push: client: %s complete: %q", client, data[1])
		return nil
	default:
		return fmt.Errorf("push: unexpected response: %q", data[0])
	}
}
<|endoftext|>


<commit_before>// Package human provides human friendly display formats.
package human

import (
	"fmt"
	"time"

	"github.com/golang/glog"
)

const MIN_TICKS = 2

// TimeOp is a function that given a time will return a string that would be a
// good label for that time.
type TimeOp func(time.Time) string

// choices is the list of hour increments and associated TimeOps for those
// durations, sorted from largest to smallest hour duration.
var choices = []struct {
	Duration time.Duration
	Op       TimeOp
}{
	{24 * 7 * 4 * time.Hour, func(t time.Time) string { return t.Format("Jan") }},             // Month
	{24 * 3 * time.Hour, func(t time.Time) string { return t.Format("2") + suffix(t.Day()) }}, // Day of month
	{24 * time.Hour, func(t time.Time) string { return t.Format("Mon") }},                     // Weekdays
	{2 * time.Hour, func(t time.Time) string { return t.Format("3pm") }},                      // Hours
}

// suffix returns the correct suffix for a day number.
//
// For making human friendly dates:
//
//  1st, 2nd, 3rd, etc.
//
func suffix(day int) string {
	// The rules are pretty simple, everything ends in "th", except numbers that
	// end in 1, 2 or 3 which end in "st", "nd" and "rd" respectively. The only
	// exceptions are the teens (10 - 19) which always end in "th".
	if day >= 4 && day <= 20 {
		return "th"
	} else if day%10 == 1 {
		return "st"
	} else if day%10 == 2 {
		return "nd"
	} else if day%10 == 3 {
		return "rd"
	}
	return "th"
}

// opFromHours takes a number of hours and from that returns a function that
// will produce good labels for tick marks in that time range. For example, if
// the time range is small enough then the ticks will be marked with the
// weekday, e.g.  "Sun", if the time range is much larger the ticks may be
// marked with the month, e.g. "Jul".
func opFromHours(duration time.Duration) (TimeOp, error) {
	// Move down the list of choices from the largest granularity to the finest.
	// The first one that would generate more than MIN_TICKS for the given number
	// of hours is chosen and that TimeOp is returned.
	var Op TimeOp = nil
	for i, c := range choices {
		if duration > c.Duration {
			Op = c.Op
			break
		}
	}
	if Op == nil {
		return nil, fmt.Errorf("Couldn't calculate TimeOp")
	}
	return Op, nil
}

// Tick represents a single tick mark.
type Tick struct {
	X     float64
	Value string
}

// ToFlot converts a slice of Ticks into something that will serialize into
// JSON that Flot consumes, which is an array of 2 element arrays. The 2 element
// arrays contain an x offset and then a label as a string. For example:
//
//   [ [ 0.5, "Saturday" ], [ 1.5, "Sunday" ] ]
//
func ToFlot(ticks []*Tick) []interface{} {
	ret := []interface{}{}
	for _, t := range ticks {
		ret = append(ret, []interface{}{t.X, t.Value})
	}
	return ret
}

// TickMarks produces human readable tickmarks to span the given timestamps.
//
// The array of timestamps are presumed to be in ascending order.
// If 'in' is nil then the "Local" time zone is used.
//
// The choice of how to label the tick marks is based on the full time
// range of the timestamps.
func TickMarks(timestamps []int64, in *time.Location) []*Tick {
	loc := in
	if loc == nil {
		var err error
		loc, err = time.LoadLocation("Local")
		if err != nil {
			loc = time.UTC
		}
	}
	ret := []*Tick{}
	if len(timestamps) < 2 {
		glog.Warning("Insufficient number of commits: %d", len(timestamps))
		return ret
	}

	begin := time.Unix(timestamps[0], 0).In(loc)
	end := time.Unix(timestamps[len(timestamps)-1], 0).In(loc)
	duration := end.Sub(begin)
	op, err := opFromHours(duration)
	if err != nil {
		glog.Errorf("Failed to calculate tickmarks for: %s %s: %s", begin, end, err)
		return ret
	}
	last := op(begin)
	ret = append(ret, &Tick{X: 0, Value: last})
	for i, t := range timestamps {
		if tickValue := op(time.Unix(t, 0).In(loc)); last != tickValue {
			last = tickValue
			ret = append(ret, &Tick{X: float64(i) - 0.5, Value: tickValue})
		}
	}

	return ret
}

// FlotTickMarks returns a struct that will serialize into JSON that Flot
// expects for a value for tick marks.
//
// If an error occurs the tick list will be empty.
func FlotTickMarks(ts []int64) []interface{} {
	loc, err := time.LoadLocation("America/New_York")
	if err != nil {
		glog.Errorf("Failed to load the timezone: %s", err)
		return []interface{}{}
	}
	return ToFlot(TickMarks(ts, loc))
}
<commit_msg>Fix human.go, straggler i variable.<commit_after>// Package human provides human friendly display formats.
package human

import (
	"fmt"
	"time"

	"github.com/golang/glog"
)

const MIN_TICKS = 2

// TimeOp is a function that given a time will return a string that would be a
// good label for that time.
type TimeOp func(time.Time) string

// choices is the list of hour increments and associated TimeOps for those
// durations, sorted from largest to smallest hour duration.
var choices = []struct {
	Duration time.Duration
	Op       TimeOp
}{
	{24 * 7 * 4 * time.Hour, func(t time.Time) string { return t.Format("Jan") }},             // Month
	{24 * 3 * time.Hour, func(t time.Time) string { return t.Format("2") + suffix(t.Day()) }}, // Day of month
	{24 * time.Hour, func(t time.Time) string { return t.Format("Mon") }},                     // Weekdays
	{2 * time.Hour, func(t time.Time) string { return t.Format("3pm") }},                      // Hours
}

// suffix returns the correct suffix for a day number.
//
// For making human friendly dates:
//
//  1st, 2nd, 3rd, etc.
//
func suffix(day int) string {
	// The rules are pretty simple, everything ends in "th", except numbers that
	// end in 1, 2 or 3 which end in "st", "nd" and "rd" respectively. The only
	// exceptions are the teens (10 - 19) which always end in "th".
	if day >= 4 && day <= 20 {
		return "th"
	} else if day%10 == 1 {
		return "st"
	} else if day%10 == 2 {
		return "nd"
	} else if day%10 == 3 {
		return "rd"
	}
	return "th"
}

// opFromHours takes a number of hours and from that returns a function that
// will produce good labels for tick marks in that time range. For example, if
// the time range is small enough then the ticks will be marked with the
// weekday, e.g.  "Sun", if the time range is much larger the ticks may be
// marked with the month, e.g. "Jul".
func opFromHours(duration time.Duration) (TimeOp, error) {
	// Move down the list of choices from the largest granularity to the finest.
	// The first one that would generate more than MIN_TICKS for the given number
	// of hours is chosen and that TimeOp is returned.
	var Op TimeOp = nil
	for _, c := range choices {
		if duration > c.Duration {
			Op = c.Op
			break
		}
	}
	if Op == nil {
		return nil, fmt.Errorf("Couldn't calculate TimeOp")
	}
	return Op, nil
}

// Tick represents a single tick mark.
type Tick struct {
	X     float64
	Value string
}

// ToFlot converts a slice of Ticks into something that will serialize into
// JSON that Flot consumes, which is an array of 2 element arrays. The 2 element
// arrays contain an x offset and then a label as a string. For example:
//
//   [ [ 0.5, "Saturday" ], [ 1.5, "Sunday" ] ]
//
func ToFlot(ticks []*Tick) []interface{} {
	ret := []interface{}{}
	for _, t := range ticks {
		ret = append(ret, []interface{}{t.X, t.Value})
	}
	return ret
}

// TickMarks produces human readable tickmarks to span the given timestamps.
//
// The array of timestamps are presumed to be in ascending order.
// If 'in' is nil then the "Local" time zone is used.
//
// The choice of how to label the tick marks is based on the full time
// range of the timestamps.
func TickMarks(timestamps []int64, in *time.Location) []*Tick {
	loc := in
	if loc == nil {
		var err error
		loc, err = time.LoadLocation("Local")
		if err != nil {
			loc = time.UTC
		}
	}
	ret := []*Tick{}
	if len(timestamps) < 2 {
		glog.Warning("Insufficient number of commits: %d", len(timestamps))
		return ret
	}

	begin := time.Unix(timestamps[0], 0).In(loc)
	end := time.Unix(timestamps[len(timestamps)-1], 0).In(loc)
	duration := end.Sub(begin)
	op, err := opFromHours(duration)
	if err != nil {
		glog.Errorf("Failed to calculate tickmarks for: %s %s: %s", begin, end, err)
		return ret
	}
	last := op(begin)
	ret = append(ret, &Tick{X: 0, Value: last})
	for i, t := range timestamps {
		if tickValue := op(time.Unix(t, 0).In(loc)); last != tickValue {
			last = tickValue
			ret = append(ret, &Tick{X: float64(i) - 0.5, Value: tickValue})
		}
	}

	return ret
}

// FlotTickMarks returns a struct that will serialize into JSON that Flot
// expects for a value for tick marks.
//
// If an error occurs the tick list will be empty.
func FlotTickMarks(ts []int64) []interface{} {
	loc, err := time.LoadLocation("America/New_York")
	if err != nil {
		glog.Errorf("Failed to load the timezone: %s", err)
		return []interface{}{}
	}
	return ToFlot(TickMarks(ts, loc))
}
<|endoftext|>


<commit_before>package postgresql

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/vault/sdk/physical"

	log "github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-uuid"

	"github.com/armon/go-metrics"
	"github.com/lib/pq"
)

const (

	// The lock TTL matches the default that Consul API uses, 15 seconds.
	// Used as part of SQL commands to set/extend lock expiry time relative to
	// database clock.
	PostgreSQLLockTTLSeconds = 15

	// The amount of time to wait between the lock renewals
	PostgreSQLLockRenewInterval = 5 * time.Second

	// PostgreSQLLockRetryInterval is the amount of time to wait
	// if a lock fails before trying again.
	PostgreSQLLockRetryInterval = time.Second
)

// Verify PostgreSQLBackend satisfies the correct interfaces
var _ physical.Backend = (*PostgreSQLBackend)(nil)

//
// HA backend was implemented based on the DynamoDB backend pattern
// With distinction using central postgres clock, hereby avoiding
// possible issues with multiple clocks
//
var _ physical.HABackend = (*PostgreSQLBackend)(nil)
var _ physical.Lock = (*PostgreSQLLock)(nil)

// PostgreSQL Backend is a physical backend that stores data
// within a PostgreSQL database.
type PostgreSQLBackend struct {
	table        string
	client       *sql.DB
	put_query    string
	get_query    string
	delete_query string
	list_query   string

	ha_table                 string
	haGetLockValueQuery      string
	haUpsertLockIdentityExec string
	haDeleteLockExec         string

	haEnabled  bool
	logger     log.Logger
	permitPool *physical.PermitPool
}

// PostgreSQLLock implements a lock using an PostgreSQL client.
type PostgreSQLLock struct {
	backend    *PostgreSQLBackend
	value, key string
	identity   string
	lock       sync.Mutex

	renewTicker *time.Ticker

	// ttlSeconds is how long a lock is valid for
	ttlSeconds int

	// renewInterval is how much time to wait between lock renewals.  must be << ttl
	renewInterval time.Duration

	// retryInterval is how much time to wait between attempts to grab the lock
	retryInterval time.Duration
}

// NewPostgreSQLBackend constructs a PostgreSQL backend using the given
// API client, server address, credentials, and database.
func NewPostgreSQLBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) {
	// Get the PostgreSQL credentials to perform read/write operations.
	connURL, ok := conf["connection_url"]
	if !ok || connURL == "" {
		return nil, fmt.Errorf("missing connection_url")
	}

	unquoted_table, ok := conf["table"]
	if !ok {
		unquoted_table = "vault_kv_store"
	}
	quoted_table := pq.QuoteIdentifier(unquoted_table)

	maxParStr, ok := conf["max_parallel"]
	var maxParInt int
	var err error
	if ok {
		maxParInt, err = strconv.Atoi(maxParStr)
		if err != nil {
			return nil, errwrap.Wrapf("failed parsing max_parallel parameter: {{err}}", err)
		}
		if logger.IsDebug() {
			logger.Debug("max_parallel set", "max_parallel", maxParInt)
		}
	} else {
		maxParInt = physical.DefaultParallelOperations
	}

	// Create PostgreSQL handle for the database.
	db, err := sql.Open("postgres", connURL)
	if err != nil {
		return nil, errwrap.Wrapf("failed to connect to postgres: {{err}}", err)
	}
	db.SetMaxOpenConns(maxParInt)

	// Determine if we should use a function to work around lack of upsert (versions < 9.5)
	var upsertAvailable bool
	upsertAvailableQuery := "SELECT current_setting('server_version_num')::int >= 90500"
	if err := db.QueryRow(upsertAvailableQuery).Scan(&upsertAvailable); err != nil {
		return nil, errwrap.Wrapf("failed to check for native upsert: {{err}}", err)
	}

	if !upsertAvailable && conf["ha_enabled"] == "true" {
		return nil, fmt.Errorf("ha_enabled=true in config but PG version doesn't support HA, must be at least 9.5")
	}

	// Setup our put strategy based on the presence or absence of a native
	// upsert.
	var put_query string
	if !upsertAvailable {
		put_query = "SELECT vault_kv_put($1, $2, $3, $4)"
	} else {
		put_query = "INSERT INTO " + quoted_table + " VALUES($1, $2, $3, $4)" +
			" ON CONFLICT (path, key) DO " +
			" UPDATE SET (parent_path, path, key, value) = ($1, $2, $3, $4)"
	}

	unquoted_ha_table, ok := conf["ha_table"]
	if !ok {
		unquoted_ha_table = "vault_ha_locks"
	}
	quoted_ha_table := pq.QuoteIdentifier(unquoted_ha_table)

	// Setup the backend.
	m := &PostgreSQLBackend{
		table:        quoted_table,
		client:       db,
		put_query:    put_query,
		get_query:    "SELECT value FROM " + quoted_table + " WHERE path = $1 AND key = $2",
		delete_query: "DELETE FROM " + quoted_table + " WHERE path = $1 AND key = $2",
		list_query: "SELECT key FROM " + quoted_table + " WHERE path = $1" +
			" UNION ALL SELECT DISTINCT substring(substr(path, length($1)+1) from '^.*?/') FROM " + quoted_table +
			" WHERE parent_path LIKE $1 || '%'",
		haGetLockValueQuery:
		// only read non expired data
		" SELECT ha_value FROM " + quoted_ha_table + " WHERE NOW() <= valid_until AND ha_key = $1 ",
		haUpsertLockIdentityExec:
		// $1=identity $2=ha_key $3=ha_value $4=TTL in seconds
		// update either steal expired lock OR update expiry for lock owned by me
		" INSERT INTO " + quoted_ha_table + " as t (ha_identity, ha_key, ha_value, valid_until) VALUES ($1, $2, $3, NOW() + $4 * INTERVAL '1 seconds'  ) " +
			" ON CONFLICT (ha_key) DO " +
			" UPDATE SET (ha_identity, ha_key, ha_value, valid_until) = ($1, $2, $3, NOW() + $4 * INTERVAL '1 seconds') " +
			" WHERE (t.valid_until < NOW() AND t.ha_key = $2) OR " +
			" (t.ha_identity = $1 AND t.ha_key = $2)  ",
		haDeleteLockExec:
		// $1=ha_identity $2=ha_key
		" DELETE FROM " + quoted_ha_table + " WHERE ha_identity=$1 AND ha_key=$2 ",
		logger:     logger,
		permitPool: physical.NewPermitPool(maxParInt),
		haEnabled:  conf["ha_enabled"] == "true",
	}

	return m, nil
}

// splitKey is a helper to split a full path key into individual
// parts: parentPath, path, key
func (m *PostgreSQLBackend) splitKey(fullPath string) (string, string, string) {
	var parentPath string
	var path string

	pieces := strings.Split(fullPath, "/")
	depth := len(pieces)
	key := pieces[depth-1]

	if depth == 1 {
		parentPath = ""
		path = "/"
	} else if depth == 2 {
		parentPath = "/"
		path = "/" + pieces[0] + "/"
	} else {
		parentPath = "/" + strings.Join(pieces[:depth-2], "/") + "/"
		path = "/" + strings.Join(pieces[:depth-1], "/") + "/"
	}

	return parentPath, path, key
}

// Put is used to insert or update an entry.
func (m *PostgreSQLBackend) Put(ctx context.Context, entry *physical.Entry) error {
	defer metrics.MeasureSince([]string{"postgres", "put"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	parentPath, path, key := m.splitKey(entry.Key)

	_, err := m.client.Exec(m.put_query, parentPath, path, key, entry.Value)
	if err != nil {
		return err
	}
	return nil
}

// Get is used to fetch and entry.
func (m *PostgreSQLBackend) Get(ctx context.Context, fullPath string) (*physical.Entry, error) {
	defer metrics.MeasureSince([]string{"postgres", "get"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, path, key := m.splitKey(fullPath)

	var result []byte
	err := m.client.QueryRow(m.get_query, path, key).Scan(&result)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	ent := &physical.Entry{
		Key:   fullPath,
		Value: result,
	}
	return ent, nil
}

// Delete is used to permanently delete an entry
func (m *PostgreSQLBackend) Delete(ctx context.Context, fullPath string) error {
	defer metrics.MeasureSince([]string{"postgres", "delete"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, path, key := m.splitKey(fullPath)

	_, err := m.client.Exec(m.delete_query, path, key)
	if err != nil {
		return err
	}
	return nil
}

// List is used to list all the keys under a given
// prefix, up to the next prefix.
func (m *PostgreSQLBackend) List(ctx context.Context, prefix string) ([]string, error) {
	defer metrics.MeasureSince([]string{"postgres", "list"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	rows, err := m.client.Query(m.list_query, "/"+prefix)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var keys []string
	for rows.Next() {
		var key string
		err = rows.Scan(&key)
		if err != nil {
			return nil, errwrap.Wrapf("failed to scan rows: {{err}}", err)
		}

		keys = append(keys, key)
	}

	return keys, nil
}

// LockWith is used for mutual exclusion based on the given key.
func (p *PostgreSQLBackend) LockWith(key, value string) (physical.Lock, error) {
	identity, err := uuid.GenerateUUID()
	if err != nil {
		return nil, err
	}
	return &PostgreSQLLock{
		backend:       p,
		key:           key,
		value:         value,
		identity:      identity,
		ttlSeconds:    PostgreSQLLockTTLSeconds,
		renewInterval: PostgreSQLLockRenewInterval,
		retryInterval: PostgreSQLLockRetryInterval,
	}, nil
}

func (p *PostgreSQLBackend) HAEnabled() bool {
	return p.haEnabled
}

// Lock tries to acquire the lock by repeatedly trying to create a record in the
// PostgreSQL table. It will block until either the stop channel is closed or
// the lock could be acquired successfully. The returned channel will be closed
// once the lock in the PostgreSQL table cannot be renewed, either due to an
// error speaking to PostgresSQL or because someone else has taken it.
func (l *PostgreSQLLock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
	l.lock.Lock()
	defer l.lock.Unlock()

	var (
		success = make(chan struct{})
		errors  = make(chan error)
		leader  = make(chan struct{})
	)
	// try to acquire the lock asynchronously
	go l.tryToLock(stopCh, success, errors)

	select {
	case <-success:
		// after acquiring it successfully, we must renew the lock periodically
		l.renewTicker = time.NewTicker(l.renewInterval)
		go l.periodicallyRenewLock(leader)
	case err := <-errors:
		return nil, err
	case <-stopCh:
		return nil, nil
	}

	return leader, nil
}

// Unlock releases the lock by deleting the lock record from the
// PostgreSQL table.
func (l *PostgreSQLLock) Unlock() error {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()

	if l.renewTicker != nil {
		l.renewTicker.Stop()
	}

	// Delete lock owned by me
	_, err := pg.client.Exec(pg.haDeleteLockExec, l.identity, l.key)
	return err
}

// Value checks whether or not the lock is held by any instance of PostgreSQLLock,
// including this one, and returns the current value.
func (l *PostgreSQLLock) Value() (bool, string, error) {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()
	var result string
	err := pg.client.QueryRow(pg.haGetLockValueQuery, l.key).Scan(&result)

	switch err {
	case nil:
		return true, result, nil
	case sql.ErrNoRows:
		return false, "", nil
	default:
		return false, "", err

	}
}

// tryToLock tries to create a new item in PostgreSQL every `retryInterval`.
// As long as the item cannot be created (because it already exists), it will
// be retried. If the operation fails due to an error, it is sent to the errors
// channel. When the lock could be acquired successfully, the success channel
// is closed.
func (l *PostgreSQLLock) tryToLock(stop <-chan struct{}, success chan struct{}, errors chan error) {
	ticker := time.NewTicker(l.retryInterval)
	defer ticker.Stop()

	for {
		select {
		case <-stop:
			return
		case <-ticker.C:
			gotlock, err := l.writeItem()
			switch {
			case err != nil:
				errors <- err
				return
			case gotlock:
				close(success)
				return
			}
		}
	}
}

func (l *PostgreSQLLock) periodicallyRenewLock(done chan struct{}) {
	for range l.renewTicker.C {
		gotlock, err := l.writeItem()
		if err != nil || !gotlock {
			close(done)
			l.renewTicker.Stop()
			return
		}
	}
}

// Attempts to put/update the PostgreSQL item using condition expressions to
// evaluate the TTL.  Returns true if the lock was obtained, false if not.
// If false error may be nil or non-nil: nil indicates simply that someone
// else has the lock, whereas non-nil means that something unexpected happened.
func (l *PostgreSQLLock) writeItem() (bool, error) {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()

	// Try steal lock or update expiry on my lock

	sqlResult, err := pg.client.Exec(pg.haUpsertLockIdentityExec, l.identity, l.key, l.value, l.ttlSeconds)
	if err != nil {
		return false, err
	}
	if sqlResult == nil {
		return false, fmt.Errorf("empty SQL response received")
	}

	ar, err := sqlResult.RowsAffected()
	if err != nil {
		return false, err
	}
	return ar == 1, nil
}
<commit_msg>Update comment spelling in postgresql.go (#6817)<commit_after>package postgresql

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/vault/sdk/physical"

	log "github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-uuid"

	"github.com/armon/go-metrics"
	"github.com/lib/pq"
)

const (

	// The lock TTL matches the default that Consul API uses, 15 seconds.
	// Used as part of SQL commands to set/extend lock expiry time relative to
	// database clock.
	PostgreSQLLockTTLSeconds = 15

	// The amount of time to wait between the lock renewals
	PostgreSQLLockRenewInterval = 5 * time.Second

	// PostgreSQLLockRetryInterval is the amount of time to wait
	// if a lock fails before trying again.
	PostgreSQLLockRetryInterval = time.Second
)

// Verify PostgreSQLBackend satisfies the correct interfaces
var _ physical.Backend = (*PostgreSQLBackend)(nil)

//
// HA backend was implemented based on the DynamoDB backend pattern
// With distinction using central postgres clock, hereby avoiding
// possible issues with multiple clocks
//
var _ physical.HABackend = (*PostgreSQLBackend)(nil)
var _ physical.Lock = (*PostgreSQLLock)(nil)

// PostgreSQL Backend is a physical backend that stores data
// within a PostgreSQL database.
type PostgreSQLBackend struct {
	table        string
	client       *sql.DB
	put_query    string
	get_query    string
	delete_query string
	list_query   string

	ha_table                 string
	haGetLockValueQuery      string
	haUpsertLockIdentityExec string
	haDeleteLockExec         string

	haEnabled  bool
	logger     log.Logger
	permitPool *physical.PermitPool
}

// PostgreSQLLock implements a lock using an PostgreSQL client.
type PostgreSQLLock struct {
	backend    *PostgreSQLBackend
	value, key string
	identity   string
	lock       sync.Mutex

	renewTicker *time.Ticker

	// ttlSeconds is how long a lock is valid for
	ttlSeconds int

	// renewInterval is how much time to wait between lock renewals.  must be << ttl
	renewInterval time.Duration

	// retryInterval is how much time to wait between attempts to grab the lock
	retryInterval time.Duration
}

// NewPostgreSQLBackend constructs a PostgreSQL backend using the given
// API client, server address, credentials, and database.
func NewPostgreSQLBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) {
	// Get the PostgreSQL credentials to perform read/write operations.
	connURL, ok := conf["connection_url"]
	if !ok || connURL == "" {
		return nil, fmt.Errorf("missing connection_url")
	}

	unquoted_table, ok := conf["table"]
	if !ok {
		unquoted_table = "vault_kv_store"
	}
	quoted_table := pq.QuoteIdentifier(unquoted_table)

	maxParStr, ok := conf["max_parallel"]
	var maxParInt int
	var err error
	if ok {
		maxParInt, err = strconv.Atoi(maxParStr)
		if err != nil {
			return nil, errwrap.Wrapf("failed parsing max_parallel parameter: {{err}}", err)
		}
		if logger.IsDebug() {
			logger.Debug("max_parallel set", "max_parallel", maxParInt)
		}
	} else {
		maxParInt = physical.DefaultParallelOperations
	}

	// Create PostgreSQL handle for the database.
	db, err := sql.Open("postgres", connURL)
	if err != nil {
		return nil, errwrap.Wrapf("failed to connect to postgres: {{err}}", err)
	}
	db.SetMaxOpenConns(maxParInt)

	// Determine if we should use a function to work around lack of upsert (versions < 9.5)
	var upsertAvailable bool
	upsertAvailableQuery := "SELECT current_setting('server_version_num')::int >= 90500"
	if err := db.QueryRow(upsertAvailableQuery).Scan(&upsertAvailable); err != nil {
		return nil, errwrap.Wrapf("failed to check for native upsert: {{err}}", err)
	}

	if !upsertAvailable && conf["ha_enabled"] == "true" {
		return nil, fmt.Errorf("ha_enabled=true in config but PG version doesn't support HA, must be at least 9.5")
	}

	// Setup our put strategy based on the presence or absence of a native
	// upsert.
	var put_query string
	if !upsertAvailable {
		put_query = "SELECT vault_kv_put($1, $2, $3, $4)"
	} else {
		put_query = "INSERT INTO " + quoted_table + " VALUES($1, $2, $3, $4)" +
			" ON CONFLICT (path, key) DO " +
			" UPDATE SET (parent_path, path, key, value) = ($1, $2, $3, $4)"
	}

	unquoted_ha_table, ok := conf["ha_table"]
	if !ok {
		unquoted_ha_table = "vault_ha_locks"
	}
	quoted_ha_table := pq.QuoteIdentifier(unquoted_ha_table)

	// Setup the backend.
	m := &PostgreSQLBackend{
		table:        quoted_table,
		client:       db,
		put_query:    put_query,
		get_query:    "SELECT value FROM " + quoted_table + " WHERE path = $1 AND key = $2",
		delete_query: "DELETE FROM " + quoted_table + " WHERE path = $1 AND key = $2",
		list_query: "SELECT key FROM " + quoted_table + " WHERE path = $1" +
			" UNION ALL SELECT DISTINCT substring(substr(path, length($1)+1) from '^.*?/') FROM " + quoted_table +
			" WHERE parent_path LIKE $1 || '%'",
		haGetLockValueQuery:
		// only read non expired data
		" SELECT ha_value FROM " + quoted_ha_table + " WHERE NOW() <= valid_until AND ha_key = $1 ",
		haUpsertLockIdentityExec:
		// $1=identity $2=ha_key $3=ha_value $4=TTL in seconds
		// update either steal expired lock OR update expiry for lock owned by me
		" INSERT INTO " + quoted_ha_table + " as t (ha_identity, ha_key, ha_value, valid_until) VALUES ($1, $2, $3, NOW() + $4 * INTERVAL '1 seconds'  ) " +
			" ON CONFLICT (ha_key) DO " +
			" UPDATE SET (ha_identity, ha_key, ha_value, valid_until) = ($1, $2, $3, NOW() + $4 * INTERVAL '1 seconds') " +
			" WHERE (t.valid_until < NOW() AND t.ha_key = $2) OR " +
			" (t.ha_identity = $1 AND t.ha_key = $2)  ",
		haDeleteLockExec:
		// $1=ha_identity $2=ha_key
		" DELETE FROM " + quoted_ha_table + " WHERE ha_identity=$1 AND ha_key=$2 ",
		logger:     logger,
		permitPool: physical.NewPermitPool(maxParInt),
		haEnabled:  conf["ha_enabled"] == "true",
	}

	return m, nil
}

// splitKey is a helper to split a full path key into individual
// parts: parentPath, path, key
func (m *PostgreSQLBackend) splitKey(fullPath string) (string, string, string) {
	var parentPath string
	var path string

	pieces := strings.Split(fullPath, "/")
	depth := len(pieces)
	key := pieces[depth-1]

	if depth == 1 {
		parentPath = ""
		path = "/"
	} else if depth == 2 {
		parentPath = "/"
		path = "/" + pieces[0] + "/"
	} else {
		parentPath = "/" + strings.Join(pieces[:depth-2], "/") + "/"
		path = "/" + strings.Join(pieces[:depth-1], "/") + "/"
	}

	return parentPath, path, key
}

// Put is used to insert or update an entry.
func (m *PostgreSQLBackend) Put(ctx context.Context, entry *physical.Entry) error {
	defer metrics.MeasureSince([]string{"postgres", "put"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	parentPath, path, key := m.splitKey(entry.Key)

	_, err := m.client.Exec(m.put_query, parentPath, path, key, entry.Value)
	if err != nil {
		return err
	}
	return nil
}

// Get is used to fetch and entry.
func (m *PostgreSQLBackend) Get(ctx context.Context, fullPath string) (*physical.Entry, error) {
	defer metrics.MeasureSince([]string{"postgres", "get"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, path, key := m.splitKey(fullPath)

	var result []byte
	err := m.client.QueryRow(m.get_query, path, key).Scan(&result)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	ent := &physical.Entry{
		Key:   fullPath,
		Value: result,
	}
	return ent, nil
}

// Delete is used to permanently delete an entry
func (m *PostgreSQLBackend) Delete(ctx context.Context, fullPath string) error {
	defer metrics.MeasureSince([]string{"postgres", "delete"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, path, key := m.splitKey(fullPath)

	_, err := m.client.Exec(m.delete_query, path, key)
	if err != nil {
		return err
	}
	return nil
}

// List is used to list all the keys under a given
// prefix, up to the next prefix.
func (m *PostgreSQLBackend) List(ctx context.Context, prefix string) ([]string, error) {
	defer metrics.MeasureSince([]string{"postgres", "list"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	rows, err := m.client.Query(m.list_query, "/"+prefix)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var keys []string
	for rows.Next() {
		var key string
		err = rows.Scan(&key)
		if err != nil {
			return nil, errwrap.Wrapf("failed to scan rows: {{err}}", err)
		}

		keys = append(keys, key)
	}

	return keys, nil
}

// LockWith is used for mutual exclusion based on the given key.
func (p *PostgreSQLBackend) LockWith(key, value string) (physical.Lock, error) {
	identity, err := uuid.GenerateUUID()
	if err != nil {
		return nil, err
	}
	return &PostgreSQLLock{
		backend:       p,
		key:           key,
		value:         value,
		identity:      identity,
		ttlSeconds:    PostgreSQLLockTTLSeconds,
		renewInterval: PostgreSQLLockRenewInterval,
		retryInterval: PostgreSQLLockRetryInterval,
	}, nil
}

func (p *PostgreSQLBackend) HAEnabled() bool {
	return p.haEnabled
}

// Lock tries to acquire the lock by repeatedly trying to create a record in the
// PostgreSQL table. It will block until either the stop channel is closed or
// the lock could be acquired successfully. The returned channel will be closed
// once the lock in the PostgreSQL table cannot be renewed, either due to an
// error speaking to PostgreSQL or because someone else has taken it.
func (l *PostgreSQLLock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
	l.lock.Lock()
	defer l.lock.Unlock()

	var (
		success = make(chan struct{})
		errors  = make(chan error)
		leader  = make(chan struct{})
	)
	// try to acquire the lock asynchronously
	go l.tryToLock(stopCh, success, errors)

	select {
	case <-success:
		// after acquiring it successfully, we must renew the lock periodically
		l.renewTicker = time.NewTicker(l.renewInterval)
		go l.periodicallyRenewLock(leader)
	case err := <-errors:
		return nil, err
	case <-stopCh:
		return nil, nil
	}

	return leader, nil
}

// Unlock releases the lock by deleting the lock record from the
// PostgreSQL table.
func (l *PostgreSQLLock) Unlock() error {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()

	if l.renewTicker != nil {
		l.renewTicker.Stop()
	}

	// Delete lock owned by me
	_, err := pg.client.Exec(pg.haDeleteLockExec, l.identity, l.key)
	return err
}

// Value checks whether or not the lock is held by any instance of PostgreSQLLock,
// including this one, and returns the current value.
func (l *PostgreSQLLock) Value() (bool, string, error) {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()
	var result string
	err := pg.client.QueryRow(pg.haGetLockValueQuery, l.key).Scan(&result)

	switch err {
	case nil:
		return true, result, nil
	case sql.ErrNoRows:
		return false, "", nil
	default:
		return false, "", err

	}
}

// tryToLock tries to create a new item in PostgreSQL every `retryInterval`.
// As long as the item cannot be created (because it already exists), it will
// be retried. If the operation fails due to an error, it is sent to the errors
// channel. When the lock could be acquired successfully, the success channel
// is closed.
func (l *PostgreSQLLock) tryToLock(stop <-chan struct{}, success chan struct{}, errors chan error) {
	ticker := time.NewTicker(l.retryInterval)
	defer ticker.Stop()

	for {
		select {
		case <-stop:
			return
		case <-ticker.C:
			gotlock, err := l.writeItem()
			switch {
			case err != nil:
				errors <- err
				return
			case gotlock:
				close(success)
				return
			}
		}
	}
}

func (l *PostgreSQLLock) periodicallyRenewLock(done chan struct{}) {
	for range l.renewTicker.C {
		gotlock, err := l.writeItem()
		if err != nil || !gotlock {
			close(done)
			l.renewTicker.Stop()
			return
		}
	}
}

// Attempts to put/update the PostgreSQL item using condition expressions to
// evaluate the TTL.  Returns true if the lock was obtained, false if not.
// If false error may be nil or non-nil: nil indicates simply that someone
// else has the lock, whereas non-nil means that something unexpected happened.
func (l *PostgreSQLLock) writeItem() (bool, error) {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()

	// Try steal lock or update expiry on my lock

	sqlResult, err := pg.client.Exec(pg.haUpsertLockIdentityExec, l.identity, l.key, l.value, l.ttlSeconds)
	if err != nil {
		return false, err
	}
	if sqlResult == nil {
		return false, fmt.Errorf("empty SQL response received")
	}

	ar, err := sqlResult.RowsAffected()
	if err != nil {
		return false, err
	}
	return ar == 1, nil
}
<|endoftext|>


<commit_before>package main

// #cgo LDFLAGS: -framework GLUT -framework OpenGL
//
// #include <stdlib.h>
// #include <GLUT/glut.h>
//
// void display(void);
// void idle(void);
//
// static void setGlutFuncs(void) {
//   glutDisplayFunc(display);
//   glutIdleFunc(idle);
// }
//
import "C"
import (
	"image/color"
	"os"
	"time"
	"unsafe"
	"github.com/hajimehoshi/go-ebiten/graphics"
)

var device *graphics.Device

type DemoGame struct {
}

func (game *DemoGame) Update() {
}

func (game *DemoGame) Draw(g *graphics.GraphicsContext, offscreen *graphics.Texture) {
	g.Fill(&color.RGBA{R: 128, G: 128, B: 255, A: 255})
}

//export display
func display() {
	device.Update()
	C.glutSwapBuffers()
}

//export idle
func idle() {
	C.glutPostRedisplay()
}

func main() {
	cargs := []*C.char{}
	for _, arg := range os.Args {
		cargs = append(cargs, C.CString(arg))
	}
	defer func() {
		for _, carg := range cargs {
			C.free(unsafe.Pointer(carg))
		}
	}()
	cargc := C.int(len(cargs))

	screenWidth  := 256
	screenHeight := 240
	screenScale  := 2

	C.glutInit(&cargc, &cargs[0])
	C.glutInitDisplayMode(C.GLUT_RGBA);
	C.glutInitWindowSize(C.int(screenWidth * screenScale),
		C.int(screenHeight * screenScale))

	title := C.CString("Ebiten Demo")
	defer C.free(unsafe.Pointer(title))
	C.glutCreateWindow(title)

	C.setGlutFuncs()

	ch := make(chan bool, 1)
	game := &DemoGame{}
	device = graphics.NewDevice(screenWidth, screenHeight, screenScale,
		func(g *graphics.GraphicsContext, offscreen *graphics.Texture) {
			ch<- true
			game.Draw(g, offscreen)
			<-ch
		})

	go func() {
		const frameTime = time.Second / 60
		lastTime := time.Now()
		for {
			ch<- true
			game.Update()
			<-ch
			time.Sleep(frameTime - time.Since(lastTime))
			lastTime = time.Now()
		}
	}()

	C.glutMainLoop()
}
<commit_msg>Refactoring<commit_after>package main

// #cgo LDFLAGS: -framework GLUT -framework OpenGL
//
// #include <stdlib.h>
// #include <GLUT/glut.h>
//
// void display(void);
// void idle(void);
//
// static void setGlutFuncs(void) {
//   glutDisplayFunc(display);
//   glutIdleFunc(idle);
// }
//
import "C"
import (
	"image/color"
	"os"
	"time"
	"unsafe"
	"github.com/hajimehoshi/go-ebiten/graphics"
)

var device *graphics.Device

type DemoGame struct {
}

func (game *DemoGame) Update() {
}

func (game *DemoGame) Draw(g *graphics.GraphicsContext, offscreen *graphics.Texture) {
	g.Fill(&color.RGBA{R: 128, G: 128, B: 255, A: 255})
}

//export display
func display() {
	device.Update()
	C.glutSwapBuffers()
}

//export idle
func idle() {
	C.glutPostRedisplay()
}

func main() {
	cargs := []*C.char{}
	for _, arg := range os.Args {
		cargs = append(cargs, C.CString(arg))
	}
	defer func() {
		for _, carg := range cargs {
			C.free(unsafe.Pointer(carg))
		}
	}()
	cargc := C.int(len(cargs))

	screenWidth  := 256
	screenHeight := 240
	screenScale  := 2

	C.glutInit(&cargc, &cargs[0])
	C.glutInitDisplayMode(C.GLUT_RGBA);
	C.glutInitWindowSize(C.int(screenWidth * screenScale),
		C.int(screenHeight * screenScale))

	title := C.CString("Ebiten Demo")
	defer C.free(unsafe.Pointer(title))
	C.glutCreateWindow(title)

	C.setGlutFuncs()

	ch := make(chan bool, 1)
	game := &DemoGame{}
	device = graphics.NewDevice(screenWidth, screenHeight, screenScale,
		func(g *graphics.GraphicsContext, offscreen *graphics.Texture) {
			<-ch
			game.Draw(g, offscreen)
			ch<- true
		})

	go func() {
		const frameTime = time.Second / 60
		lastTime := time.Now()
		for {
			<-ch
			game.Update()
			ch<- true
			now := time.Now()
			time.Sleep(frameTime - now.Sub(lastTime))
			lastTime = now
		}
	}()
	ch<- true

	C.glutMainLoop()
}
<|endoftext|>


<commit_before>// Copyright (C) 2011 by Krzysztof Kowalik <chris@nu7hat.ch>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package webrocket

import (
	"../uuid"
	"io"
	"sync"
	"websocket"
)

// WebsocketConnection represents a single WebSockets connection and
// implements an API for managing its subscriptions and authentication.
type WebsocketConnection struct {
	*websocket.Conn

	// Unique identifier of the connected client.
	id string
	// If authenticated, contains an access control information.
	permission *Permission
	// List of client's subscriptions
	subscriptions map[string]*Channel
	// Internal semaphore
	mtx sync.Mutex
}

// Internal constructor
// -----------------------------------------------------------------------------

// newWebsocketConnection wraps given WebSocket connection within the newly
// created WebsocketConnection structure.
//
// ws - The raw websocket connection to be wrapped.
//
// Returns wrapped websocket connection.
func newWebsocketConnection(ws *websocket.Conn) (c *WebsocketConnection) {
	uuid, _ := uuid.NewV4()
	c = &WebsocketConnection{
		Conn:          ws,
		id:            uuid.String(),
		subscriptions: make(map[string]*Channel),
	}
	// Send info that connection has been approved. Yeah,
	// Bruce Lee approves!
	c.Send(map[string]interface{}{
		"__connected": map[string]interface{}{
			"sid": c.Id(),
		},
	})
	return
}

// Internal
// -----------------------------------------------------------------------------

// authenticate marks the connection as authenticated by assigning given
// permissions information to it. Not threadsafe, used only from within
// websocket protocol's handlers which is blocking for specified connection.
//
// p - The permission information to be assigned to this connection.
//
func (c *WebsocketConnection) authenticate(p *Permission) {
	c.permission = p
	if p != nil {
		c.Send(map[string]interface{}{
			"__authenticated": map[string]interface{}{},
		})
	}
}

// reauthenticate cleans up previously set permissions, unsubscribes from
// all the channels and authenticates the client again using given permissions
// information.
//
// p - The permission information to be assigned to this connection.
//
func (c *WebsocketConnection) reauthenticate(p *Permission) {
	c.clearSubscriptions()
	c.authenticate(p)
}

// Removes all subscriptions created by this client.
func (c *WebsocketConnection) clearSubscriptions() {
	for _, ch := range c.subscriptions {
		ch.unsubscribe(c, map[string]interface{}{}, false)
	}
}

// Exported
// -----------------------------------------------------------------------------

// Id returns an unique session identifier attached to this connection.
func (c *WebsocketConnection) Id() string {
	return c.id
}

// IsAuthenticated returns whether this connection is authenticated or not.
// Not threadsafe, Used only from within websocket protocol's handlers which
// is blocking for specified connection.
func (c *WebsocketConnection) IsAuthenticated() bool {
	return c.permission != nil
}

// IsAllowed returns whether this connections is authenticated and has
// sufficient permissions to operate on a given channel. Not threadsafe,
// used only from within websocket protocol's handlers which is blocking
// for specified connection.
//
// channel - The channel to check permissions for.
//
func (c *WebsocketConnection) IsAllowed(channel string) bool {
	return c.IsAuthenticated() && c.permission.IsMatching(channel)
}

// Send serializes given payload with JSON and sends it to the client.
// Threadsafe, may be used from the websocket protocol's handlers and
// the channel's broadcaster.
//
// payload - A data to be send to the client.
//
func (c *WebsocketConnection) Send(payload interface{}) {
	c.mtx.Lock()
	defer c.mtx.Unlock()
	if c.Conn == nil {
		return
	}
	if err := websocket.JSON.Send(c.Conn, payload); err != nil {
		// TODO: error log!
		//websocketStatusLog(c, "Not sent", 597, err.Error())
	}
}

// Receive reads a message from the client and parses it into the internal
// message object. If there is no data to read from the connection then it
// shall block until new data arrive. Not threadsafe, used only from within
// websocket handler's event loop.
//
// Returns message received from the connection.
func (c *WebsocketConnection) Receive() (*WebsocketMessage, error) {
	var recv map[string]interface{}
	if !c.IsAlive() {
		return nil, io.EOF
	}
	recv = make(map[string]interface{})
	if err := websocket.JSON.Receive(c.Conn, &recv); err != nil {
		return nil, err
	}
	return newWebsocketMessage(recv)
}

// IsAlive returns whether the connection is alive or not. Threadsafe, so far
// used internally only, but the related Kill function may be called from
// many goroutines.
func (c *WebsocketConnection) IsAlive() bool {
	c.mtx.Lock()
	defer c.mtx.Unlock()
	return c.Conn != nil
}

// Kill cleans up all subscriptions and closes the connection. This operation
// will mark connection as dead. Threadsafe, Used in the websocket endpoint
// and handlers.
func (c *WebsocketConnection) Kill() {
	c.mtx.Lock()
	defer c.mtx.Unlock()
	c.clearSubscriptions()
	if c.Conn != nil {
		// TODO: figure out if we actually have an use case for the __closed
		// event if there is onclose operation supported by the WebSockets
		// implementation.
		c.Send(map[string]interface{}{
			"__closed": map[string]interface{}{
				"sid": c.Id(),
			},
		})
		c.Conn.Close()
		c.Conn = nil
	}
}
<commit_msg>removed __closed event<commit_after>// Copyright (C) 2011 by Krzysztof Kowalik <chris@nu7hat.ch>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package webrocket

import (
	"../uuid"
	"io"
	"sync"
	"websocket"
)

// WebsocketConnection represents a single WebSockets connection and
// implements an API for managing its subscriptions and authentication.
type WebsocketConnection struct {
	*websocket.Conn

	// Unique identifier of the connected client.
	id string
	// If authenticated, contains an access control information.
	permission *Permission
	// List of client's subscriptions
	subscriptions map[string]*Channel
	// Internal semaphore
	mtx sync.Mutex
}

// Internal constructor
// -----------------------------------------------------------------------------

// newWebsocketConnection wraps given WebSocket connection within the newly
// created WebsocketConnection structure.
//
// ws - The raw websocket connection to be wrapped.
//
// Returns wrapped websocket connection.
func newWebsocketConnection(ws *websocket.Conn) (c *WebsocketConnection) {
	uuid, _ := uuid.NewV4()
	c = &WebsocketConnection{
		Conn:          ws,
		id:            uuid.String(),
		subscriptions: make(map[string]*Channel),
	}
	// Send info that connection has been approved. Yeah,
	// Bruce Lee approves!
	c.Send(map[string]interface{}{
		"__connected": map[string]interface{}{
			"sid": c.Id(),
		},
	})
	return
}

// Internal
// -----------------------------------------------------------------------------

// authenticate marks the connection as authenticated by assigning given
// permissions information to it. Not threadsafe, used only from within
// websocket protocol's handlers which is blocking for specified connection.
//
// p - The permission information to be assigned to this connection.
//
func (c *WebsocketConnection) authenticate(p *Permission) {
	c.permission = p
	if p != nil {
		c.Send(map[string]interface{}{
			"__authenticated": map[string]interface{}{},
		})
	}
}

// reauthenticate cleans up previously set permissions, unsubscribes from
// all the channels and authenticates the client again using given permissions
// information.
//
// p - The permission information to be assigned to this connection.
//
func (c *WebsocketConnection) reauthenticate(p *Permission) {
	c.clearSubscriptions()
	c.authenticate(p)
}

// Removes all subscriptions created by this client.
func (c *WebsocketConnection) clearSubscriptions() {
	for _, ch := range c.subscriptions {
		ch.unsubscribe(c, map[string]interface{}{}, false)
	}
}

// Exported
// -----------------------------------------------------------------------------

// Id returns an unique session identifier attached to this connection.
func (c *WebsocketConnection) Id() string {
	return c.id
}

// IsAuthenticated returns whether this connection is authenticated or not.
// Not threadsafe, Used only from within websocket protocol's handlers which
// is blocking for specified connection.
func (c *WebsocketConnection) IsAuthenticated() bool {
	return c.permission != nil
}

// IsAllowed returns whether this connections is authenticated and has
// sufficient permissions to operate on a given channel. Not threadsafe,
// used only from within websocket protocol's handlers which is blocking
// for specified connection.
//
// channel - The channel to check permissions for.
//
func (c *WebsocketConnection) IsAllowed(channel string) bool {
	return c.IsAuthenticated() && c.permission.IsMatching(channel)
}

// Send serializes given payload with JSON and sends it to the client.
// Threadsafe, may be used from the websocket protocol's handlers and
// the channel's broadcaster.
//
// payload - A data to be send to the client.
//
func (c *WebsocketConnection) Send(payload interface{}) {
	c.mtx.Lock()
	defer c.mtx.Unlock()
	if c.Conn == nil {
		return
	}
	if err := websocket.JSON.Send(c.Conn, payload); err != nil {
		// TODO: error log!
		//websocketStatusLog(c, "Not sent", 597, err.Error())
	}
}

// Receive reads a message from the client and parses it into the internal
// message object. If there is no data to read from the connection then it
// shall block until new data arrive. Not threadsafe, used only from within
// websocket handler's event loop.
//
// Returns message received from the connection.
func (c *WebsocketConnection) Receive() (*WebsocketMessage, error) {
	var recv map[string]interface{}
	if !c.IsAlive() {
		return nil, io.EOF
	}
	recv = make(map[string]interface{})
	if err := websocket.JSON.Receive(c.Conn, &recv); err != nil {
		return nil, err
	}
	return newWebsocketMessage(recv)
}

// IsAlive returns whether the connection is alive or not. Threadsafe, so far
// used internally only, but the related Kill function may be called from
// many goroutines.
func (c *WebsocketConnection) IsAlive() bool {
	c.mtx.Lock()
	defer c.mtx.Unlock()
	return c.Conn != nil
}

// Kill cleans up all subscriptions and closes the connection. This operation
// will mark connection as dead. Threadsafe, Used in the websocket endpoint
// and handlers.
func (c *WebsocketConnection) Kill() {
	c.mtx.Lock()
	defer c.mtx.Unlock()
	c.clearSubscriptions()
	if c.Conn != nil {
		c.Conn.Close()
		c.Conn = nil
	}
}
<|endoftext|>