<commit_before>package egoscale

import (
	"context"
	"fmt"
	"net/url"
	"strconv"
	"strings"
)

// SecurityGroup represent a firewalling set of rules
type SecurityGroup struct {
	Account     string        `json:"account,omitempty" doc:"the account owning the security group"`
	Description string        `json:"description,omitempty" doc:"the description of the security group"`
	EgressRule  []EgressRule  `json:"egressrule,omitempty" doc:"the list of egress rules associated with the security group"`
	ID          *UUID         `json:"id" doc:"the ID of the security group"`
	IngressRule []IngressRule `json:"ingressrule,omitempty" doc:"the list of ingress rules associated with the security group"`
	Name        string        `json:"name,omitempty" doc:"the name of the security group"`
}

// UserSecurityGroup converts a SecurityGroup to a UserSecurityGroup
func (sg SecurityGroup) UserSecurityGroup() UserSecurityGroup {
	return UserSecurityGroup{
		Group: sg.Name,
	}
}

// ListRequest builds the ListSecurityGroups request
func (sg SecurityGroup) ListRequest() (ListCommand, error) {
	req := &ListSecurityGroups{
		ID:                sg.ID,
		SecurityGroupName: sg.Name,
	}

	return req, nil
}

// Delete deletes the given Security Group
func (sg SecurityGroup) Delete(ctx context.Context, client *Client) error {
	if sg.ID == nil && sg.Name == "" {
		return fmt.Errorf("a SecurityGroup may only be deleted using ID or Name")
	}

	req := &DeleteSecurityGroup{}

	if sg.ID != nil {
		req.ID = sg.ID
	} else {
		req.Name = sg.Name
	}

	return client.BooleanRequestWithContext(ctx, req)
}

// RuleByID returns IngressRule or EgressRule by a rule ID
func (sg SecurityGroup) RuleByID(ruleID UUID) (*IngressRule, *EgressRule) {
	for i, in := range sg.IngressRule {
		if in.RuleID.Equal(ruleID) {
			return &sg.IngressRule[i], nil
		}
	}

	for i, out := range sg.EgressRule {
		if out.RuleID.Equal(ruleID) {
			return nil, &sg.EgressRule[i]
		}
	}

	return nil, nil
}

// IngressRule represents the ingress rule
type IngressRule struct {
	CIDR                  *CIDR               `json:"cidr,omitempty" doc:"the CIDR notation for the base IP address of the security group rule"`
	Description           string              `json:"description,omitempty" doc:"description of the security group rule"`
	EndPort               uint16              `json:"endport,omitempty" doc:"the ending port of the security group rule "`
	IcmpCode              uint8               `json:"icmpcode,omitempty" doc:"the code for the ICMP message response"`
	IcmpType              uint8               `json:"icmptype,omitempty" doc:"the type of the ICMP message response"`
	Protocol              string              `json:"protocol,omitempty" doc:"the protocol of the security group rule"`
	RuleID                *UUID               `json:"ruleid" doc:"the id of the security group rule"`
	SecurityGroupID       *UUID               `json:"securitygroupid,omitempty"`
	SecurityGroupName     string              `json:"securitygroupname,omitempty" doc:"security group name"`
	StartPort             uint16              `json:"startport,omitempty" doc:"the starting port of the security group rule"`
	UserSecurityGroupList []UserSecurityGroup `json:"usersecuritygrouplist,omitempty"`
}

// EgressRule represents the ingress rule
type EgressRule IngressRule

// UserSecurityGroup represents the traffic of another security group
type UserSecurityGroup struct {
	Group string `json:"group,omitempty"`
}

// String gives the UserSecurityGroup name
func (usg UserSecurityGroup) String() string {
	return usg.Group
}

// CreateSecurityGroup represents a security group creation
type CreateSecurityGroup struct {
	Name        string `json:"name" doc:"name of the security group"`
	Description string `json:"description,omitempty" doc:"the description of the security group"`
	_           bool   `name:"createSecurityGroup" description:"Creates a security group"`
}

// Response returns the struct to unmarshal
func (CreateSecurityGroup) Response() interface{} {
	return new(SecurityGroup)
}

// DeleteSecurityGroup represents a security group deletion
type DeleteSecurityGroup struct {
	ID   *UUID  `json:"id,omitempty" doc:"The ID of the security group. Mutually exclusive with name parameter"`
	Name string `json:"name,omitempty" doc:"The ID of the security group. Mutually exclusive with id parameter"`
	_    bool   `name:"deleteSecurityGroup" description:"Deletes security group"`
}

// Response returns the struct to unmarshal
func (DeleteSecurityGroup) Response() interface{} {
	return new(booleanResponse)
}

// AuthorizeSecurityGroupIngress (Async) represents the ingress rule creation
type AuthorizeSecurityGroupIngress struct {
	CIDRList              []CIDR              `json:"cidrlist,omitempty" doc:"the cidr list associated"`
	Description           string              `json:"description,omitempty" doc:"the description of the ingress/egress rule"`
	EndPort               uint16              `json:"endport,omitempty" doc:"end port for this ingress/egress rule"`
	IcmpCode              uint8               `json:"icmpcode,omitempty" doc:"error code for this icmp message"`
	IcmpType              uint8               `json:"icmptype,omitempty" doc:"type of the icmp message being sent"`
	Protocol              string              `json:"protocol,omitempty" doc:"TCP is default. UDP, ICMP, ICMPv6, AH, ESP, GRE, IPIP are the other supported protocols"`
	SecurityGroupID       *UUID               `json:"securitygroupid,omitempty" doc:"The ID of the security group. Mutually exclusive with securitygroupname parameter"`
	SecurityGroupName     string              `json:"securitygroupname,omitempty" doc:"The name of the security group. Mutually exclusive with securitygroupid parameter"`
	StartPort             uint16              `json:"startport,omitempty" doc:"start port for this ingress/egress rule"`
	UserSecurityGroupList []UserSecurityGroup `json:"usersecuritygrouplist,omitempty" doc:"user to security group mapping"`
	_                     bool                `name:"authorizeSecurityGroupIngress" description:"Authorize a particular ingress/egress rule for this security group"`
}

// Response returns the struct to unmarshal
func (AuthorizeSecurityGroupIngress) Response() interface{} {
	return new(AsyncJobResult)
}

// AsyncResponse returns the struct to unmarshal the async job
func (AuthorizeSecurityGroupIngress) AsyncResponse() interface{} {
	return new(SecurityGroup)
}

func (req AuthorizeSecurityGroupIngress) onBeforeSend(params url.Values) error {
	// ICMP code and type may be zero but can also be omitted...
	if strings.HasPrefix(strings.ToLower(req.Protocol), "icmp") {
		params.Set("icmpcode", strconv.FormatInt(int64(req.IcmpCode), 10))
		params.Set("icmptype", strconv.FormatInt(int64(req.IcmpType), 10))
	}
	// StartPort may be zero but can also be omitted...
	if req.EndPort != 0 && req.StartPort == 0 {
		params.Set("startport", "0")
	}
	return nil
}

// AuthorizeSecurityGroupEgress (Async) represents the egress rule creation
type AuthorizeSecurityGroupEgress AuthorizeSecurityGroupIngress

// Response returns the struct to unmarshal
func (AuthorizeSecurityGroupEgress) Response() interface{} {
	return new(AsyncJobResult)
}

// AsyncResponse returns the struct to unmarshal the async job
func (AuthorizeSecurityGroupEgress) AsyncResponse() interface{} {
	return new(SecurityGroup)
}

func (req AuthorizeSecurityGroupEgress) onBeforeSend(params url.Values) error {
	return (AuthorizeSecurityGroupIngress)(req).onBeforeSend(params)
}

// RevokeSecurityGroupIngress (Async) represents the ingress/egress rule deletion
type RevokeSecurityGroupIngress struct {
	ID *UUID `json:"id" doc:"The ID of the ingress rule"`
	_  bool  `name:"revokeSecurityGroupIngress" description:"Deletes a particular ingress rule from this security group"`
}

// Response returns the struct to unmarshal
func (RevokeSecurityGroupIngress) Response() interface{} {
	return new(AsyncJobResult)
}

// AsyncResponse returns the struct to unmarshal the async job
func (RevokeSecurityGroupIngress) AsyncResponse() interface{} {
	return new(booleanResponse)
}

// RevokeSecurityGroupEgress (Async) represents the ingress/egress rule deletion
type RevokeSecurityGroupEgress struct {
	ID *UUID `json:"id" doc:"The ID of the egress rule"`
	_  bool  `name:"revokeSecurityGroupEgress" description:"Deletes a particular egress rule from this security group"`
}

// Response returns the struct to unmarshal
func (RevokeSecurityGroupEgress) Response() interface{} {
	return new(AsyncJobResult)
}

// AsyncResponse returns the struct to unmarshal the async job
func (RevokeSecurityGroupEgress) AsyncResponse() interface{} {
	return new(booleanResponse)
}

//go:generate go run generate/main.go -interface=Listable ListSecurityGroups

// ListSecurityGroups represents a search for security groups
type ListSecurityGroups struct {
	ID                *UUID  `json:"id,omitempty" doc:"list the security group by the id provided"`
	Keyword           string `json:"keyword,omitempty" doc:"List by keyword"`
	Page              int    `json:"page,omitempty"`
	PageSize          int    `json:"pagesize,omitempty"`
	SecurityGroupName string `json:"securitygroupname,omitempty" doc:"lists security groups by name"`
	VirtualMachineID  *UUID  `json:"virtualmachineid,omitempty" doc:"lists security groups by virtual machine id"`
	_                 bool   `name:"listSecurityGroups" description:"Lists security groups"`
}

// ListSecurityGroupsResponse represents a list of security groups
type ListSecurityGroupsResponse struct {
	Count         int             `json:"count"`
	SecurityGroup []SecurityGroup `json:"securitygroup"`
}
<commit_msg>sg: remove non-existent fields (#373)<commit_after>package egoscale

import (
	"context"
	"fmt"
	"net/url"
	"strconv"
	"strings"
)

// SecurityGroup represent a firewalling set of rules
type SecurityGroup struct {
	Account     string        `json:"account,omitempty" doc:"the account owning the security group"`
	Description string        `json:"description,omitempty" doc:"the description of the security group"`
	EgressRule  []EgressRule  `json:"egressrule,omitempty" doc:"the list of egress rules associated with the security group"`
	ID          *UUID         `json:"id" doc:"the ID of the security group"`
	IngressRule []IngressRule `json:"ingressrule,omitempty" doc:"the list of ingress rules associated with the security group"`
	Name        string        `json:"name,omitempty" doc:"the name of the security group"`
}

// UserSecurityGroup converts a SecurityGroup to a UserSecurityGroup
func (sg SecurityGroup) UserSecurityGroup() UserSecurityGroup {
	return UserSecurityGroup{
		Group: sg.Name,
	}
}

// ListRequest builds the ListSecurityGroups request
func (sg SecurityGroup) ListRequest() (ListCommand, error) {
	req := &ListSecurityGroups{
		ID:                sg.ID,
		SecurityGroupName: sg.Name,
	}

	return req, nil
}

// Delete deletes the given Security Group
func (sg SecurityGroup) Delete(ctx context.Context, client *Client) error {
	if sg.ID == nil && sg.Name == "" {
		return fmt.Errorf("a SecurityGroup may only be deleted using ID or Name")
	}

	req := &DeleteSecurityGroup{}

	if sg.ID != nil {
		req.ID = sg.ID
	} else {
		req.Name = sg.Name
	}

	return client.BooleanRequestWithContext(ctx, req)
}

// RuleByID returns IngressRule or EgressRule by a rule ID
func (sg SecurityGroup) RuleByID(ruleID UUID) (*IngressRule, *EgressRule) {
	for i, in := range sg.IngressRule {
		if in.RuleID.Equal(ruleID) {
			return &sg.IngressRule[i], nil
		}
	}

	for i, out := range sg.EgressRule {
		if out.RuleID.Equal(ruleID) {
			return nil, &sg.EgressRule[i]
		}
	}

	return nil, nil
}

// IngressRule represents the ingress rule
type IngressRule struct {
	CIDR              *CIDR  `json:"cidr,omitempty" doc:"the CIDR notation for the base IP address of the security group rule"`
	Description       string `json:"description,omitempty" doc:"description of the security group rule"`
	EndPort           uint16 `json:"endport,omitempty" doc:"the ending port of the security group rule "`
	IcmpCode          uint8  `json:"icmpcode,omitempty" doc:"the code for the ICMP message response"`
	IcmpType          uint8  `json:"icmptype,omitempty" doc:"the type of the ICMP message response"`
	Protocol          string `json:"protocol,omitempty" doc:"the protocol of the security group rule"`
	RuleID            *UUID  `json:"ruleid" doc:"the id of the security group rule"`
	SecurityGroupName string `json:"securitygroupname,omitempty" doc:"security group name"`
	StartPort         uint16 `json:"startport,omitempty" doc:"the starting port of the security group rule"`
}

// EgressRule represents the ingress rule
type EgressRule IngressRule

// UserSecurityGroup represents the traffic of another security group
type UserSecurityGroup struct {
	Group string `json:"group,omitempty"`
}

// String gives the UserSecurityGroup name
func (usg UserSecurityGroup) String() string {
	return usg.Group
}

// CreateSecurityGroup represents a security group creation
type CreateSecurityGroup struct {
	Name        string `json:"name" doc:"name of the security group"`
	Description string `json:"description,omitempty" doc:"the description of the security group"`
	_           bool   `name:"createSecurityGroup" description:"Creates a security group"`
}

// Response returns the struct to unmarshal
func (CreateSecurityGroup) Response() interface{} {
	return new(SecurityGroup)
}

// DeleteSecurityGroup represents a security group deletion
type DeleteSecurityGroup struct {
	ID   *UUID  `json:"id,omitempty" doc:"The ID of the security group. Mutually exclusive with name parameter"`
	Name string `json:"name,omitempty" doc:"The ID of the security group. Mutually exclusive with id parameter"`
	_    bool   `name:"deleteSecurityGroup" description:"Deletes security group"`
}

// Response returns the struct to unmarshal
func (DeleteSecurityGroup) Response() interface{} {
	return new(booleanResponse)
}

// AuthorizeSecurityGroupIngress (Async) represents the ingress rule creation
type AuthorizeSecurityGroupIngress struct {
	CIDRList              []CIDR              `json:"cidrlist,omitempty" doc:"the cidr list associated"`
	Description           string              `json:"description,omitempty" doc:"the description of the ingress/egress rule"`
	EndPort               uint16              `json:"endport,omitempty" doc:"end port for this ingress/egress rule"`
	IcmpCode              uint8               `json:"icmpcode,omitempty" doc:"error code for this icmp message"`
	IcmpType              uint8               `json:"icmptype,omitempty" doc:"type of the icmp message being sent"`
	Protocol              string              `json:"protocol,omitempty" doc:"TCP is default. UDP, ICMP, ICMPv6, AH, ESP, GRE, IPIP are the other supported protocols"`
	SecurityGroupID       *UUID               `json:"securitygroupid,omitempty" doc:"The ID of the security group. Mutually exclusive with securitygroupname parameter"`
	SecurityGroupName     string              `json:"securitygroupname,omitempty" doc:"The name of the security group. Mutually exclusive with securitygroupid parameter"`
	StartPort             uint16              `json:"startport,omitempty" doc:"start port for this ingress/egress rule"`
	UserSecurityGroupList []UserSecurityGroup `json:"usersecuritygrouplist,omitempty" doc:"user to security group mapping"`
	_                     bool                `name:"authorizeSecurityGroupIngress" description:"Authorize a particular ingress/egress rule for this security group"`
}

// Response returns the struct to unmarshal
func (AuthorizeSecurityGroupIngress) Response() interface{} {
	return new(AsyncJobResult)
}

// AsyncResponse returns the struct to unmarshal the async job
func (AuthorizeSecurityGroupIngress) AsyncResponse() interface{} {
	return new(SecurityGroup)
}

func (req AuthorizeSecurityGroupIngress) onBeforeSend(params url.Values) error {
	// ICMP code and type may be zero but can also be omitted...
	if strings.HasPrefix(strings.ToLower(req.Protocol), "icmp") {
		params.Set("icmpcode", strconv.FormatInt(int64(req.IcmpCode), 10))
		params.Set("icmptype", strconv.FormatInt(int64(req.IcmpType), 10))
	}
	// StartPort may be zero but can also be omitted...
	if req.EndPort != 0 && req.StartPort == 0 {
		params.Set("startport", "0")
	}
	return nil
}

// AuthorizeSecurityGroupEgress (Async) represents the egress rule creation
type AuthorizeSecurityGroupEgress AuthorizeSecurityGroupIngress

// Response returns the struct to unmarshal
func (AuthorizeSecurityGroupEgress) Response() interface{} {
	return new(AsyncJobResult)
}

// AsyncResponse returns the struct to unmarshal the async job
func (AuthorizeSecurityGroupEgress) AsyncResponse() interface{} {
	return new(SecurityGroup)
}

func (req AuthorizeSecurityGroupEgress) onBeforeSend(params url.Values) error {
	return (AuthorizeSecurityGroupIngress)(req).onBeforeSend(params)
}

// RevokeSecurityGroupIngress (Async) represents the ingress/egress rule deletion
type RevokeSecurityGroupIngress struct {
	ID *UUID `json:"id" doc:"The ID of the ingress rule"`
	_  bool  `name:"revokeSecurityGroupIngress" description:"Deletes a particular ingress rule from this security group"`
}

// Response returns the struct to unmarshal
func (RevokeSecurityGroupIngress) Response() interface{} {
	return new(AsyncJobResult)
}

// AsyncResponse returns the struct to unmarshal the async job
func (RevokeSecurityGroupIngress) AsyncResponse() interface{} {
	return new(booleanResponse)
}

// RevokeSecurityGroupEgress (Async) represents the ingress/egress rule deletion
type RevokeSecurityGroupEgress struct {
	ID *UUID `json:"id" doc:"The ID of the egress rule"`
	_  bool  `name:"revokeSecurityGroupEgress" description:"Deletes a particular egress rule from this security group"`
}

// Response returns the struct to unmarshal
func (RevokeSecurityGroupEgress) Response() interface{} {
	return new(AsyncJobResult)
}

// AsyncResponse returns the struct to unmarshal the async job
func (RevokeSecurityGroupEgress) AsyncResponse() interface{} {
	return new(booleanResponse)
}

//go:generate go run generate/main.go -interface=Listable ListSecurityGroups

// ListSecurityGroups represents a search for security groups
type ListSecurityGroups struct {
	ID                *UUID  `json:"id,omitempty" doc:"list the security group by the id provided"`
	Keyword           string `json:"keyword,omitempty" doc:"List by keyword"`
	Page              int    `json:"page,omitempty"`
	PageSize          int    `json:"pagesize,omitempty"`
	SecurityGroupName string `json:"securitygroupname,omitempty" doc:"lists security groups by name"`
	VirtualMachineID  *UUID  `json:"virtualmachineid,omitempty" doc:"lists security groups by virtual machine id"`
	_                 bool   `name:"listSecurityGroups" description:"Lists security groups"`
}

// ListSecurityGroupsResponse represents a list of security groups
type ListSecurityGroupsResponse struct {
	Count         int             `json:"count"`
	SecurityGroup []SecurityGroup `json:"securitygroup"`
}
<|endoftext|>


<commit_before>package health

import (
	"context"
	"time"

	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/config"
	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/metrics"
	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// DiagnosisResult is a possible result after a diagnosis.
type DiagnosisResult int

// Possible diagnosis results.
const (
	Unknown DiagnosisResult = iota
	Inconclusive
	Healthy
	Unhealthy
)

func (d DiagnosisResult) String() string {
	switch d {
	case Inconclusive:
		return "inconclusive"
	case Healthy:
		return "healthy"
	case Unhealthy:
		return "unhealthy"
	default:
		return "unknown"
	}
}

// Diagnosis is the information about the health of the revision.
type Diagnosis struct {
	OverallResult DiagnosisResult
	CheckResults  []CheckResult
}

// CheckResult is information about a metrics criteria check.
type CheckResult struct {
	Threshold     float64
	ActualValue   float64
	IsCriteriaMet bool
}

// Diagnose attempts to determine the health of a revision.
//
// If no health criteria is specified or the size of the health criteria and the
// actual values are not the same, the diagnosis is Unknown and an error is
// returned.
//
// If the minimum number of requests is not met, then health cannot be
// determined and diagnosis is Inconclusive.
//
// Otherwise, all metrics criteria are checked to determine whether the revision
// is healthy or not.
func Diagnose(ctx context.Context, healthCriteria []config.HealthCriterion, actualValues []float64) (Diagnosis, error) {
	logger := util.LoggerFrom(ctx)
	if len(healthCriteria) != len(actualValues) {
		return Diagnosis{Unknown, nil}, errors.New("the size of health criteria is not the same to the size of the actual metrics values")
	}
	if len(healthCriteria) == 0 {
		return Diagnosis{Unknown, nil}, errors.New("health criteria must be specified")
	}

	diagnosis := Unknown
	var results []CheckResult
	for i, value := range actualValues {
		criteria := healthCriteria[i]
		logger := logger.WithFields(logrus.Fields{
			"metrics":       criteria.Metric,
			"threshold":     criteria.Threshold,
			"expectedValue": value,
		})
		if criteria.Metric == config.LatencyMetricsCheck {
			logger = logger.WithField("percentile", criteria.Percentile)
		}

		isMet := isCriteriaMet(criteria.Metric, criteria.Threshold, value)

		// For unmet request count, return inconclusive and empty results.
		if !isMet && criteria.Metric == config.RequestCountMetricsCheck {
			logger.Debug("unmet criterion")
			diagnosis = Inconclusive
			results = nil
			break
		}

		result := CheckResult{Threshold: criteria.Threshold, ActualValue: value}
		if !isMet {
			logger.Debug("unmet criterion")
			diagnosis = Unhealthy
			results = append(results, result)
			continue
		}

		// Only switch to healthy once a first criteria is met.
		if diagnosis == Unknown && criteria.Metric != config.RequestCountMetricsCheck {
			diagnosis = Healthy
		}
		result.IsCriteriaMet = true
		results = append(results, result)
		logger.Debug("met criterion")
	}

	return Diagnosis{diagnosis, results}, nil
}

// CollectMetrics gets a metrics value for each of the given health criteria and
// returns a result for each criterion.
func CollectMetrics(ctx context.Context, provider metrics.Provider, offset time.Duration, healthCriteria []config.HealthCriterion) ([]float64, error) {
	if len(healthCriteria) == 0 {
		return nil, errors.New("health criteria must be specified")
	}
	var metricsValues []float64
	for _, criteria := range healthCriteria {
		var metricsValue float64
		var err error

		switch criteria.Metric {
		case config.RequestCountMetricsCheck:
			metricsValue, err = requestCount(ctx, provider, offset)
		case config.LatencyMetricsCheck:
			metricsValue, err = latency(ctx, provider, offset, criteria.Percentile)
		case config.ErrorRateMetricsCheck:
			metricsValue, err = errorRatePercent(ctx, provider, offset)
		default:
			return nil, errors.Errorf("unimplemented metrics %q", criteria.Metric)
		}

		if err != nil {
			return nil, errors.Wrapf(err, "failed to obtain metrics %q", criteria.Metric)
		}
		metricsValues = append(metricsValues, metricsValue)
	}

	return metricsValues, nil
}

// isCriteriaMet concludes if metrics criteria was met.
func isCriteriaMet(metricsType config.MetricsCheck, threshold float64, actualValue float64) bool {
	// Of all the supported metrics, only the threshold for request count has an
	// expected minimum value.
	if metricsType == config.RequestCountMetricsCheck {
		return actualValue >= threshold
	}
	return actualValue <= threshold
}

// requestCount returns the number of requests during the given offset.
func requestCount(ctx context.Context, provider metrics.Provider, offset time.Duration) (float64, error) {
	logger := util.LoggerFrom(ctx)
	logger.Debug("querying for request count metrics")
	count, err := provider.RequestCount(ctx, offset)
	return float64(count), errors.Wrap(err, "failed to get request count metrics")
}

// latency returns the latency for the given offset and percentile.
func latency(ctx context.Context, provider metrics.Provider, offset time.Duration, percentile float64) (float64, error) {
	alignerReducer, err := metrics.PercentileToAlignReduce(percentile)
	if err != nil {
		return 0, errors.Wrap(err, "failed to parse percentile")
	}

	logger := util.LoggerFrom(ctx).WithField("percentile", percentile)
	logger.Debug("querying for latency metrics")
	latency, err := provider.Latency(ctx, offset, alignerReducer)
	if err != nil {
		return 0, errors.Wrap(err, "failed to get latency metrics")
	}
	logger.WithField("value", latency).Debug("latency successfully retrieved")

	return latency, nil
}

// errorRatePercent returns the percentage of errors during the given offset.
func errorRatePercent(ctx context.Context, provider metrics.Provider, offset time.Duration) (float64, error) {
	logger := util.LoggerFrom(ctx)
	logger.Debug("querying for error rate metrics")
	rate, err := provider.ErrorRate(ctx, offset)
	if err != nil {
		return 0, errors.Wrap(err, "failed to get error rate metrics")
	}

	// Multiply rate by 100 to have a percentage.
	rate *= 100
	logger.WithField("value", rate).Debug("error rate successfully retrieved")
	return rate, nil
}
<commit_msg>health: Log correct fields for criterion (#102)<commit_after>package health

import (
	"context"
	"time"

	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/config"
	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/metrics"
	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// DiagnosisResult is a possible result after a diagnosis.
type DiagnosisResult int

// Possible diagnosis results.
const (
	Unknown DiagnosisResult = iota
	Inconclusive
	Healthy
	Unhealthy
)

func (d DiagnosisResult) String() string {
	switch d {
	case Inconclusive:
		return "inconclusive"
	case Healthy:
		return "healthy"
	case Unhealthy:
		return "unhealthy"
	default:
		return "unknown"
	}
}

// Diagnosis is the information about the health of the revision.
type Diagnosis struct {
	OverallResult DiagnosisResult
	CheckResults  []CheckResult
}

// CheckResult is information about a metrics criteria check.
type CheckResult struct {
	Threshold     float64
	ActualValue   float64
	IsCriteriaMet bool
}

// Diagnose attempts to determine the health of a revision.
//
// If no health criteria is specified or the size of the health criteria and the
// actual values are not the same, the diagnosis is Unknown and an error is
// returned.
//
// If the minimum number of requests is not met, then health cannot be
// determined and diagnosis is Inconclusive.
//
// Otherwise, all metrics criteria are checked to determine whether the revision
// is healthy or not.
func Diagnose(ctx context.Context, healthCriteria []config.HealthCriterion, actualValues []float64) (Diagnosis, error) {
	logger := util.LoggerFrom(ctx)
	if len(healthCriteria) != len(actualValues) {
		return Diagnosis{Unknown, nil}, errors.New("the size of health criteria is not the same to the size of the actual metrics values")
	}
	if len(healthCriteria) == 0 {
		return Diagnosis{Unknown, nil}, errors.New("health criteria must be specified")
	}

	diagnosis := Unknown
	var results []CheckResult
	for i, value := range actualValues {
		criteria := healthCriteria[i]
		logger := logger.WithFields(logrus.Fields{
			"metrics":       criteria.Metric,
			"expectedValue": criteria.Threshold,
			"actualValue":   value,
		})
		if criteria.Metric == config.LatencyMetricsCheck {
			logger = logger.WithField("percentile", criteria.Percentile)
		}

		isMet := isCriteriaMet(criteria.Metric, criteria.Threshold, value)

		// For unmet request count, return inconclusive and empty results.
		if !isMet && criteria.Metric == config.RequestCountMetricsCheck {
			logger.Debug("unmet criterion")
			diagnosis = Inconclusive
			results = nil
			break
		}

		result := CheckResult{Threshold: criteria.Threshold, ActualValue: value}
		if !isMet {
			logger.Debug("unmet criterion")
			diagnosis = Unhealthy
			results = append(results, result)
			continue
		}

		// Only switch to healthy once a first criteria is met.
		if diagnosis == Unknown && criteria.Metric != config.RequestCountMetricsCheck {
			diagnosis = Healthy
		}
		result.IsCriteriaMet = true
		results = append(results, result)
		logger.Debug("met criterion")
	}

	return Diagnosis{diagnosis, results}, nil
}

// CollectMetrics gets a metrics value for each of the given health criteria and
// returns a result for each criterion.
func CollectMetrics(ctx context.Context, provider metrics.Provider, offset time.Duration, healthCriteria []config.HealthCriterion) ([]float64, error) {
	if len(healthCriteria) == 0 {
		return nil, errors.New("health criteria must be specified")
	}
	var metricsValues []float64
	for _, criteria := range healthCriteria {
		var metricsValue float64
		var err error

		switch criteria.Metric {
		case config.RequestCountMetricsCheck:
			metricsValue, err = requestCount(ctx, provider, offset)
		case config.LatencyMetricsCheck:
			metricsValue, err = latency(ctx, provider, offset, criteria.Percentile)
		case config.ErrorRateMetricsCheck:
			metricsValue, err = errorRatePercent(ctx, provider, offset)
		default:
			return nil, errors.Errorf("unimplemented metrics %q", criteria.Metric)
		}

		if err != nil {
			return nil, errors.Wrapf(err, "failed to obtain metrics %q", criteria.Metric)
		}
		metricsValues = append(metricsValues, metricsValue)
	}

	return metricsValues, nil
}

// isCriteriaMet concludes if metrics criteria was met.
func isCriteriaMet(metricsType config.MetricsCheck, threshold float64, actualValue float64) bool {
	// Of all the supported metrics, only the threshold for request count has an
	// expected minimum value.
	if metricsType == config.RequestCountMetricsCheck {
		return actualValue >= threshold
	}
	return actualValue <= threshold
}

// requestCount returns the number of requests during the given offset.
func requestCount(ctx context.Context, provider metrics.Provider, offset time.Duration) (float64, error) {
	logger := util.LoggerFrom(ctx)
	logger.Debug("querying for request count metrics")
	count, err := provider.RequestCount(ctx, offset)
	return float64(count), errors.Wrap(err, "failed to get request count metrics")
}

// latency returns the latency for the given offset and percentile.
func latency(ctx context.Context, provider metrics.Provider, offset time.Duration, percentile float64) (float64, error) {
	alignerReducer, err := metrics.PercentileToAlignReduce(percentile)
	if err != nil {
		return 0, errors.Wrap(err, "failed to parse percentile")
	}

	logger := util.LoggerFrom(ctx).WithField("percentile", percentile)
	logger.Debug("querying for latency metrics")
	latency, err := provider.Latency(ctx, offset, alignerReducer)
	if err != nil {
		return 0, errors.Wrap(err, "failed to get latency metrics")
	}
	logger.WithField("value", latency).Debug("latency successfully retrieved")

	return latency, nil
}

// errorRatePercent returns the percentage of errors during the given offset.
func errorRatePercent(ctx context.Context, provider metrics.Provider, offset time.Duration) (float64, error) {
	logger := util.LoggerFrom(ctx)
	logger.Debug("querying for error rate metrics")
	rate, err := provider.ErrorRate(ctx, offset)
	if err != nil {
		return 0, errors.Wrap(err, "failed to get error rate metrics")
	}

	// Multiply rate by 100 to have a percentage.
	rate *= 100
	logger.WithField("value", rate).Debug("error rate successfully retrieved")
	return rate, nil
}
<|endoftext|>


<commit_before>// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cmd

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"
	"net"

	"golang.org/x/tools/internal/jsonrpc2"
	"golang.org/x/tools/internal/lsp"
	"golang.org/x/tools/internal/tool"
)

// Serve is a struct that exposes the configurable parts of the LSP server as
// flags, in the right form for tool.Main to consume.
type Serve struct {
	Logfile string `flag:"logfile" help:"filename to log to. if value is \"auto\", then logging to a default output file is enabled"`
	Mode    string `flag:"mode" help:"no effect"`
	Port    int    `flag:"port" help:"port on which to run gopls for debugging purposes"`
	Address    string    `flag:"listen" help:"address on which to listen for remote connections"`

	app *Application
}

func (s *Serve) Name() string  { return "serve" }
func (s *Serve) Usage() string { return "" }
func (s *Serve) ShortHelp() string {
	return "run a server for Go code using the Language Server Protocol"
}
func (s *Serve) DetailedHelp(f *flag.FlagSet) {
	fmt.Fprint(f.Output(), `
The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as
a child of an editor process.

gopls server flags are:
`)
	f.PrintDefaults()
}

// Run configures a server based on the flags, and then runs it.
// It blocks until the server shuts down.
func (s *Serve) Run(ctx context.Context, args ...string) error {
	if len(args) > 0 {
		return tool.CommandLineErrorf("server does not take arguments, got %v", args)
	}
	out := os.Stderr
	if s.Logfile != "" {
		filename := s.Logfile
		if filename == "auto" {
			filename = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.log", os.Getpid()))
		}
		f, err := os.Create(filename)
		if err != nil {
			return fmt.Errorf("Unable to create log file: %v", err)
		}
		defer f.Close()
		log.SetOutput(io.MultiWriter(os.Stderr, f))
		out = f
	}
	if s.app.Remote != "" {
		return s.forward()
	}
	logger := func(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) {
		const eol = "\r\n\r\n\r\n"
		if err != nil {
			fmt.Fprintf(out, "[Error - %v] %s %s%s %v%s", time.Now().Format("3:04:05 PM"),
				direction, method, id, err, eol)
			return
		}
		outx := new(strings.Builder)
		fmt.Fprintf(outx, "[Trace - %v] ", time.Now().Format("3:04:05 PM"))
		switch direction {
		case jsonrpc2.Send:
			fmt.Fprint(outx, "Received ")
		case jsonrpc2.Receive:
			fmt.Fprint(outx, "Sending ")
		}
		switch {
		case id == nil:
			fmt.Fprint(outx, "notification ")
		case elapsed >= 0:
			fmt.Fprint(outx, "response ")
		default:
			fmt.Fprint(outx, "request ")
		}
		fmt.Fprintf(outx, "'%s", method)
		switch {
		case id == nil:
			// do nothing
		case id.Name != "":
			fmt.Fprintf(outx, " - (%s)", id.Name)
		default:
			fmt.Fprintf(outx, " - (%d)", id.Number)
		}
		fmt.Fprint(outx, "'")
		if elapsed >= 0 {
			msec := int(elapsed.Round(time.Millisecond) / time.Millisecond)
			fmt.Fprintf(outx, " in %dms", msec)
		}
		params := "null"
		if payload != nil {
			params = string(*payload)
		}
		if params == "null" {
			params = "{}"
		}
		fmt.Fprintf(outx, ".\r\nParams: %s%s", params, eol)
		fmt.Fprintf(out, "%s", outx.String())
	}
	// For debugging purposes only.
	if s.Address != "" {
		return lsp.RunServerOnAddress(ctx, s.Address, logger)
	}
	if s.Port != 0 {
		return lsp.RunServerOnPort(ctx, s.Port, logger)
	}
	stream := jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout)
	return lsp.RunServer(ctx, stream, logger)
}

func (s *Serve) forward() error {
	conn, err := net.Dial("tcp", s.app.Remote)
	if err != nil {
		return err
	}
	errc := make(chan error)

	go func(conn net.Conn) {
		_, err := io.Copy(conn, os.Stdin)
		errc <- err
	}(conn)

	go func(conn net.Conn) {
		_, err := io.Copy(os.Stdout, conn)
		errc <- err
	}(conn)

	return <-errc
}
<commit_msg>lsp/internal: fix incorrectly formatted file<commit_after>// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cmd

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/tools/internal/jsonrpc2"
	"golang.org/x/tools/internal/lsp"
	"golang.org/x/tools/internal/tool"
)

// Serve is a struct that exposes the configurable parts of the LSP server as
// flags, in the right form for tool.Main to consume.
type Serve struct {
	Logfile string `flag:"logfile" help:"filename to log to. if value is \"auto\", then logging to a default output file is enabled"`
	Mode    string `flag:"mode" help:"no effect"`
	Port    int    `flag:"port" help:"port on which to run gopls for debugging purposes"`
	Address string `flag:"listen" help:"address on which to listen for remote connections"`

	app *Application
}

func (s *Serve) Name() string  { return "serve" }
func (s *Serve) Usage() string { return "" }
func (s *Serve) ShortHelp() string {
	return "run a server for Go code using the Language Server Protocol"
}
func (s *Serve) DetailedHelp(f *flag.FlagSet) {
	fmt.Fprint(f.Output(), `
The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as
a child of an editor process.

gopls server flags are:
`)
	f.PrintDefaults()
}

// Run configures a server based on the flags, and then runs it.
// It blocks until the server shuts down.
func (s *Serve) Run(ctx context.Context, args ...string) error {
	if len(args) > 0 {
		return tool.CommandLineErrorf("server does not take arguments, got %v", args)
	}
	out := os.Stderr
	if s.Logfile != "" {
		filename := s.Logfile
		if filename == "auto" {
			filename = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.log", os.Getpid()))
		}
		f, err := os.Create(filename)
		if err != nil {
			return fmt.Errorf("Unable to create log file: %v", err)
		}
		defer f.Close()
		log.SetOutput(io.MultiWriter(os.Stderr, f))
		out = f
	}
	if s.app.Remote != "" {
		return s.forward()
	}
	logger := func(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) {
		const eol = "\r\n\r\n\r\n"
		if err != nil {
			fmt.Fprintf(out, "[Error - %v] %s %s%s %v%s", time.Now().Format("3:04:05 PM"),
				direction, method, id, err, eol)
			return
		}
		outx := new(strings.Builder)
		fmt.Fprintf(outx, "[Trace - %v] ", time.Now().Format("3:04:05 PM"))
		switch direction {
		case jsonrpc2.Send:
			fmt.Fprint(outx, "Received ")
		case jsonrpc2.Receive:
			fmt.Fprint(outx, "Sending ")
		}
		switch {
		case id == nil:
			fmt.Fprint(outx, "notification ")
		case elapsed >= 0:
			fmt.Fprint(outx, "response ")
		default:
			fmt.Fprint(outx, "request ")
		}
		fmt.Fprintf(outx, "'%s", method)
		switch {
		case id == nil:
			// do nothing
		case id.Name != "":
			fmt.Fprintf(outx, " - (%s)", id.Name)
		default:
			fmt.Fprintf(outx, " - (%d)", id.Number)
		}
		fmt.Fprint(outx, "'")
		if elapsed >= 0 {
			msec := int(elapsed.Round(time.Millisecond) / time.Millisecond)
			fmt.Fprintf(outx, " in %dms", msec)
		}
		params := "null"
		if payload != nil {
			params = string(*payload)
		}
		if params == "null" {
			params = "{}"
		}
		fmt.Fprintf(outx, ".\r\nParams: %s%s", params, eol)
		fmt.Fprintf(out, "%s", outx.String())
	}
	// For debugging purposes only.
	if s.Address != "" {
		return lsp.RunServerOnAddress(ctx, s.Address, logger)
	}
	if s.Port != 0 {
		return lsp.RunServerOnPort(ctx, s.Port, logger)
	}
	stream := jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout)
	return lsp.RunServer(ctx, stream, logger)
}

func (s *Serve) forward() error {
	conn, err := net.Dial("tcp", s.app.Remote)
	if err != nil {
		return err
	}
	errc := make(chan error)

	go func(conn net.Conn) {
		_, err := io.Copy(conn, os.Stdin)
		errc <- err
	}(conn)

	go func(conn net.Conn) {
		_, err := io.Copy(os.Stdout, conn)
		errc <- err
	}(conn)

	return <-errc
}
<|endoftext|>


<commit_before>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package nettest provides utilities for IP testing.
package nettest // import "golang.org/x/net/internal/nettest"

import (
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"runtime"
)

var (
	supportsIPv4 bool
	supportsIPv6 bool
)

func init() {
	if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
		ln.Close()
		supportsIPv4 = true
	}
	if ln, err := net.Listen("tcp6", "[::1]:0"); err == nil {
		ln.Close()
		supportsIPv6 = true
	}
}

// SupportsIPv4 reports whether the platform supports IPv4 networking
// functionality.
func SupportsIPv4() bool { return supportsIPv4 }

// SupportsIPv6 reports whether the platform supports IPv6 networking
// functionality.
func SupportsIPv6() bool { return supportsIPv6 }

// SupportsRawIPSocket reports whether the platform supports raw IP
// sockets.
func SupportsRawIPSocket() (string, bool) {
	return supportsRawIPSocket()
}

// SupportsIPv6MulticastDeliveryOnLoopback reports whether the
// platform supports IPv6 multicast packet delivery on software
// loopback interface.
func SupportsIPv6MulticastDeliveryOnLoopback() bool {
	return supportsIPv6MulticastDeliveryOnLoopback()
}

// ProtocolNotSupported reports whether err is a protocol not
// supported error.
func ProtocolNotSupported(err error) bool {
	return protocolNotSupported(err)
}

// TestableNetwork reports whether network is testable on the current
// platform configuration.
func TestableNetwork(network string) bool {
	// This is based on logic from standard library's
	// net/platform_test.go.
	switch network {
	case "unix":
		switch runtime.GOOS {
		case "android", "nacl", "plan9", "windows":
			return false
		}
		if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
			return false
		}
	case "unixpacket":
		switch runtime.GOOS {
		case "android", "darwin", "freebsd", "nacl", "plan9", "windows":
			return false
		}
	}
	return true
}

// NewLocalListener returns a listener which listens to a loopback IP
// address or local file system path.
// Network must be "tcp", "tcp4", "tcp6", "unix" or "unixpacket".
func NewLocalListener(network string) (net.Listener, error) {
	switch network {
	case "tcp":
		if supportsIPv4 {
			if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
				return ln, nil
			}
		}
		if supportsIPv6 {
			return net.Listen("tcp6", "[::1]:0")
		}
	case "tcp4":
		if supportsIPv4 {
			return net.Listen("tcp4", "127.0.0.1:0")
		}
	case "tcp6":
		if supportsIPv6 {
			return net.Listen("tcp6", "[::1]:0")
		}
	case "unix", "unixpacket":
		return net.Listen(network, localPath())
	}
	return nil, fmt.Errorf("%s is not supported", network)
}

// NewLocalPacketListener returns a packet listener which listens to a
// loopback IP address or local file system path.
// Network must be "udp", "udp4", "udp6" or "unixgram".
func NewLocalPacketListener(network string) (net.PacketConn, error) {
	switch network {
	case "udp":
		if supportsIPv4 {
			if c, err := net.ListenPacket("udp4", "127.0.0.1:0"); err == nil {
				return c, nil
			}
		}
		if supportsIPv6 {
			return net.ListenPacket("udp6", "[::1]:0")
		}
	case "udp4":
		if supportsIPv4 {
			return net.ListenPacket("udp4", "127.0.0.1:0")
		}
	case "udp6":
		if supportsIPv6 {
			return net.ListenPacket("udp6", "[::1]:0")
		}
	case "unixgram":
		return net.ListenPacket(network, localPath())
	}
	return nil, fmt.Errorf("%s is not supported", network)
}

func localPath() string {
	f, err := ioutil.TempFile("", "nettest")
	if err != nil {
		panic(err)
	}
	path := f.Name()
	f.Close()
	os.Remove(path)
	return path
}
<commit_msg>internal/nettest: add missing support for "unixgram" to TestableNetwork<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package nettest provides utilities for network testing.
package nettest // import "golang.org/x/net/internal/nettest"

import (
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"runtime"
)

var (
	supportsIPv4 bool
	supportsIPv6 bool
)

func init() {
	if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
		ln.Close()
		supportsIPv4 = true
	}
	if ln, err := net.Listen("tcp6", "[::1]:0"); err == nil {
		ln.Close()
		supportsIPv6 = true
	}
}

// SupportsIPv4 reports whether the platform supports IPv4 networking
// functionality.
func SupportsIPv4() bool { return supportsIPv4 }

// SupportsIPv6 reports whether the platform supports IPv6 networking
// functionality.
func SupportsIPv6() bool { return supportsIPv6 }

// SupportsRawIPSocket reports whether the platform supports raw IP
// sockets.
func SupportsRawIPSocket() (string, bool) {
	return supportsRawIPSocket()
}

// SupportsIPv6MulticastDeliveryOnLoopback reports whether the
// platform supports IPv6 multicast packet delivery on software
// loopback interface.
func SupportsIPv6MulticastDeliveryOnLoopback() bool {
	return supportsIPv6MulticastDeliveryOnLoopback()
}

// ProtocolNotSupported reports whether err is a protocol not
// supported error.
func ProtocolNotSupported(err error) bool {
	return protocolNotSupported(err)
}

// TestableNetwork reports whether network is testable on the current
// platform configuration.
func TestableNetwork(network string) bool {
	// This is based on logic from standard library's
	// net/platform_test.go.
	switch network {
	case "unix", "unixgram":
		switch runtime.GOOS {
		case "android", "nacl", "plan9", "windows":
			return false
		}
		if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
			return false
		}
	case "unixpacket":
		switch runtime.GOOS {
		case "android", "darwin", "freebsd", "nacl", "plan9", "windows":
			return false
		}
	}
	return true
}

// NewLocalListener returns a listener which listens to a loopback IP
// address or local file system path.
// Network must be "tcp", "tcp4", "tcp6", "unix" or "unixpacket".
func NewLocalListener(network string) (net.Listener, error) {
	switch network {
	case "tcp":
		if supportsIPv4 {
			if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
				return ln, nil
			}
		}
		if supportsIPv6 {
			return net.Listen("tcp6", "[::1]:0")
		}
	case "tcp4":
		if supportsIPv4 {
			return net.Listen("tcp4", "127.0.0.1:0")
		}
	case "tcp6":
		if supportsIPv6 {
			return net.Listen("tcp6", "[::1]:0")
		}
	case "unix", "unixpacket":
		return net.Listen(network, localPath())
	}
	return nil, fmt.Errorf("%s is not supported", network)
}

// NewLocalPacketListener returns a packet listener which listens to a
// loopback IP address or local file system path.
// Network must be "udp", "udp4", "udp6" or "unixgram".
func NewLocalPacketListener(network string) (net.PacketConn, error) {
	switch network {
	case "udp":
		if supportsIPv4 {
			if c, err := net.ListenPacket("udp4", "127.0.0.1:0"); err == nil {
				return c, nil
			}
		}
		if supportsIPv6 {
			return net.ListenPacket("udp6", "[::1]:0")
		}
	case "udp4":
		if supportsIPv4 {
			return net.ListenPacket("udp4", "127.0.0.1:0")
		}
	case "udp6":
		if supportsIPv6 {
			return net.ListenPacket("udp6", "[::1]:0")
		}
	case "unixgram":
		return net.ListenPacket(network, localPath())
	}
	return nil, fmt.Errorf("%s is not supported", network)
}

func localPath() string {
	f, err := ioutil.TempFile("", "nettest")
	if err != nil {
		panic(err)
	}
	path := f.Name()
	f.Close()
	os.Remove(path)
	return path
}
<|endoftext|>


<commit_before>// Copyright 2018 The Go Cloud Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://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 pubsub

import (
	"context"
	"errors"
	"time"

	"github.com/google/go-cloud/internal/pubsub/driver"
	"github.com/google/go-cloud/internal/retry"
	gax "github.com/googleapis/gax-go"
	"google.golang.org/api/support/bundler"
)

// Message contains data to be published.
type Message struct {
	// Body contains the content of the message.
	Body []byte

	// Metadata has key/value metadata for the message.
	Metadata map[string]string

	// ack is a closure that queues this message for acknowledgement.
	ack func()
}

// Ack acknowledges the message, telling the server that it does not need to be
// sent again to the associated Subscription. It returns immediately, but the
// actual ack is sent in the background, and is not guaranteed to succeed.
func (m *Message) Ack() {
	go m.ack()
}

// Topic publishes messages to all its subscribers.
type Topic struct {
	driver  driver.Topic
	batcher *bundler.Bundler
}

type msgErrChan struct {
	msg     *Message
	errChan chan error
}

// Send publishes a message. It only returns after the message has been
// sent, or failed to be sent. Send can be called from multiple goroutines
// at once.
func (t *Topic) Send(ctx context.Context, m *Message) error {
	// Check for doneness before we do any work.
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
	}
	mec := msgErrChan{
		msg:     m,
		errChan: make(chan error),
	}
	size := len(m.Body)
	for k, v := range m.Metadata {
		size += len(k)
		size += len(v)
	}
	if err := t.batcher.AddWait(ctx, mec, size); err != nil {
		return err
	}
	return <-mec.errChan
}

// Close flushes pending message sends and disconnects the Topic.
// It only returns after all pending messages have been sent.
func (t *Topic) Close() error {
	t.batcher.Flush()
	return t.driver.Close()
}

// NewTopic makes a pubsub.Topic from a driver.Topic and opts to
// tune how messages are sent. Behind the scenes, NewTopic spins up a goroutine
// to bundle messages into batches and send them to the server.
// It is for use by provider implementations.
func NewTopic(d driver.Topic) *Topic {
	handler := func(item interface{}) {
		mecs, ok := item.([]msgErrChan)
		if !ok {
			panic("failed conversion to []msgErrChan in bundler handler")
		}
		var dms []*driver.Message
		for _, mec := range mecs {
			m := mec.msg
			dm := &driver.Message{
				Body:     m.Body,
				Metadata: m.Metadata,
			}
			dms = append(dms, dm)
		}

		callCtx := context.TODO()
		err := retry.Call(callCtx, gax.Backoff{}, d.IsRetryable, func() error {
			return d.SendBatch(callCtx, dms)
		})
		for _, mec := range mecs {
			mec.errChan <- err
		}
	}
	b := bundler.NewBundler(msgErrChan{}, handler)
	b.DelayThreshold = time.Millisecond
	t := &Topic{
		driver:  d,
		batcher: b,
	}
	return t
}

// Subscription receives published messages.
type Subscription struct {
	driver driver.Subscription

	// ackBatcher makes batches of acks and sends them to the server.
	ackBatcher *bundler.Bundler

	// sem is a semaphore guarding q. It is used instead of a mutex to work
	// with context cancellation.
	sem chan struct{}

	// q is the local queue of messages downloaded from the server.
	q []*Message
}

// Receive receives and returns the next message from the Subscription's queue,
// blocking and polling if none are available. This method can be called
// concurrently from multiple goroutines. The Ack() method of the returned
// Message has to be called once the message has been processed, to prevent it
// from being received again.
func (s *Subscription) Receive(ctx context.Context) (*Message, error) {
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}
	<-s.sem
	defer func() {
		s.sem <- struct{}{}
	}()
	if len(s.q) == 0 {
		if err := s.getNextBatch(ctx); err != nil {
			return nil, err
		}
	}
	m := s.q[0]
	s.q = s.q[1:]
	return m, nil
}

// getNextBatch gets the next batch of messages from the server and saves it in
// s.q.
func (s *Subscription) getNextBatch(ctx context.Context) error {
	var msgs []*driver.Message
	err := retry.Call(ctx, gax.Backoff{}, s.driver.IsRetryable, func() error {
		var err error
		// TODO(#691): dynamically adjust maxMessages
		const maxMessages = 10
		msgs, err = s.driver.ReceiveBatch(ctx, maxMessages)
		return err
	})
	if err != nil {
		return err
	}
	if len(msgs) == 0 {
		return errors.New("subscription driver bug: received empty batch")
	}
	s.q = nil
	for _, m := range msgs {
		id := m.AckID
		// size is an estimate of the size of a single AckID in bytes.
		const size = 8
		s.q = append(s.q, &Message{
			Body:     m.Body,
			Metadata: m.Metadata,
			ack: func() {
				s.ackBatcher.Add(ackIDBox{id}, size)
			},
		})
	}
	return nil
}

// Close flushes pending ack sends and disconnects the Subscription.
func (s *Subscription) Close() error {
	s.ackBatcher.Flush()
	return s.driver.Close()
}

// ackIDBox makes it possible to use a driver.AckID with bundler.
type ackIDBox struct {
	ackID driver.AckID
}

// NewSubscription creates a Subscription from a driver.Subscription and opts to
// tune sending and receiving of acks and messages. Behind the scenes,
// NewSubscription spins up a goroutine to gather acks into batches and
// periodically send them to the server.
// It is for use by provider implementations.
func NewSubscription(d driver.Subscription) *Subscription {
	handler := func(item interface{}) {
		boxes := item.([]ackIDBox)
		var ids []driver.AckID
		for _, box := range boxes {
			id := box.ackID
			ids = append(ids, id)
		}
		// TODO: Consider providing a way to stop this call. See #766.
		callCtx := context.Background()
		err := retry.Call(callCtx, gax.Backoff{}, d.IsRetryable, func() error {
			return d.SendAcks(callCtx, ids)
		})
		// TODO(#695): Do something sensible if SendAcks returns an error.
		_ = err
	}
	ab := bundler.NewBundler(ackIDBox{}, handler)
	ab.DelayThreshold = time.Millisecond
	s := &Subscription{
		driver:     d,
		ackBatcher: ab,
		sem:        make(chan struct{}, 1),
	}
	s.sem <- struct{}{}
	return s
}
<commit_msg>internal/pubsub: replace channel with mutex (#780)<commit_after>// Copyright 2018 The Go Cloud Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://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 pubsub

import (
	"context"
	"errors"
	"sync"
	"time"

	"github.com/google/go-cloud/internal/pubsub/driver"
	"github.com/google/go-cloud/internal/retry"
	gax "github.com/googleapis/gax-go"
	"google.golang.org/api/support/bundler"
)

// Message contains data to be published.
type Message struct {
	// Body contains the content of the message.
	Body []byte

	// Metadata has key/value metadata for the message.
	Metadata map[string]string

	// ack is a closure that queues this message for acknowledgement.
	ack func()
}

// Ack acknowledges the message, telling the server that it does not need to be
// sent again to the associated Subscription. It returns immediately, but the
// actual ack is sent in the background, and is not guaranteed to succeed.
func (m *Message) Ack() {
	go m.ack()
}

// Topic publishes messages to all its subscribers.
type Topic struct {
	driver  driver.Topic
	batcher *bundler.Bundler
}

type msgErrChan struct {
	msg     *Message
	errChan chan error
}

// Send publishes a message. It only returns after the message has been
// sent, or failed to be sent. Send can be called from multiple goroutines
// at once.
func (t *Topic) Send(ctx context.Context, m *Message) error {
	// Check for doneness before we do any work.
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
	}
	mec := msgErrChan{
		msg:     m,
		errChan: make(chan error),
	}
	size := len(m.Body)
	for k, v := range m.Metadata {
		size += len(k)
		size += len(v)
	}
	if err := t.batcher.AddWait(ctx, mec, size); err != nil {
		return err
	}
	return <-mec.errChan
}

// Close flushes pending message sends and disconnects the Topic.
// It only returns after all pending messages have been sent.
func (t *Topic) Close() error {
	t.batcher.Flush()
	return t.driver.Close()
}

// NewTopic makes a pubsub.Topic from a driver.Topic and opts to
// tune how messages are sent. Behind the scenes, NewTopic spins up a goroutine
// to bundle messages into batches and send them to the server.
// It is for use by provider implementations.
func NewTopic(d driver.Topic) *Topic {
	handler := func(item interface{}) {
		mecs, ok := item.([]msgErrChan)
		if !ok {
			panic("failed conversion to []msgErrChan in bundler handler")
		}
		var dms []*driver.Message
		for _, mec := range mecs {
			m := mec.msg
			dm := &driver.Message{
				Body:     m.Body,
				Metadata: m.Metadata,
			}
			dms = append(dms, dm)
		}

		callCtx := context.TODO()
		err := retry.Call(callCtx, gax.Backoff{}, d.IsRetryable, func() error {
			return d.SendBatch(callCtx, dms)
		})
		for _, mec := range mecs {
			mec.errChan <- err
		}
	}
	b := bundler.NewBundler(msgErrChan{}, handler)
	b.DelayThreshold = time.Millisecond
	t := &Topic{
		driver:  d,
		batcher: b,
	}
	return t
}

// Subscription receives published messages.
type Subscription struct {
	driver driver.Subscription

	// ackBatcher makes batches of acks and sends them to the server.
	ackBatcher *bundler.Bundler

	mu sync.Mutex

	// q is the local queue of messages downloaded from the server.
	q []*Message
}

// Receive receives and returns the next message from the Subscription's queue,
// blocking and polling if none are available. This method can be called
// concurrently from multiple goroutines. The Ack() method of the returned
// Message has to be called once the message has been processed, to prevent it
// from being received again.
func (s *Subscription) Receive(ctx context.Context) (*Message, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	if len(s.q) == 0 {
		if err := s.getNextBatch(ctx); err != nil {
			return nil, err
		}
	}
	m := s.q[0]
	s.q = s.q[1:]
	return m, nil
}

// getNextBatch gets the next batch of messages from the server and saves it in
// s.q.
func (s *Subscription) getNextBatch(ctx context.Context) error {
	var msgs []*driver.Message
	err := retry.Call(ctx, gax.Backoff{}, s.driver.IsRetryable, func() error {
		var err error
		// TODO(#691): dynamically adjust maxMessages
		const maxMessages = 10
		msgs, err = s.driver.ReceiveBatch(ctx, maxMessages)
		return err
	})
	if err != nil {
		return err
	}
	if len(msgs) == 0 {
		return errors.New("subscription driver bug: received empty batch")
	}
	s.q = nil
	for _, m := range msgs {
		id := m.AckID
		// size is an estimate of the size of a single AckID in bytes.
		const size = 8
		s.q = append(s.q, &Message{
			Body:     m.Body,
			Metadata: m.Metadata,
			ack: func() {
				s.ackBatcher.Add(ackIDBox{id}, size)
			},
		})
	}
	return nil
}

// Close flushes pending ack sends and disconnects the Subscription.
func (s *Subscription) Close() error {
	s.ackBatcher.Flush()
	return s.driver.Close()
}

// ackIDBox makes it possible to use a driver.AckID with bundler.
type ackIDBox struct {
	ackID driver.AckID
}

// NewSubscription creates a Subscription from a driver.Subscription and opts to
// tune sending and receiving of acks and messages. Behind the scenes,
// NewSubscription spins up a goroutine to gather acks into batches and
// periodically send them to the server.
// It is for use by provider implementations.
func NewSubscription(d driver.Subscription) *Subscription {
	handler := func(item interface{}) {
		boxes := item.([]ackIDBox)
		var ids []driver.AckID
		for _, box := range boxes {
			id := box.ackID
			ids = append(ids, id)
		}
		// TODO: Consider providing a way to stop this call. See #766.
		callCtx := context.Background()
		err := retry.Call(callCtx, gax.Backoff{}, d.IsRetryable, func() error {
			return d.SendAcks(callCtx, ids)
		})
		// TODO(#695): Do something sensible if SendAcks returns an error.
		_ = err
	}
	ab := bundler.NewBundler(ackIDBox{}, handler)
	ab.DelayThreshold = time.Millisecond
	return &Subscription{
		driver:     d,
		ackBatcher: ab,
	}
}
<|endoftext|>


<commit_before>// Package test contains functionality that should be available to
// all unit tests (which live in separate packages).
package test

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/go-errors/errors"
	"github.com/privacybydesign/irmago/internal/common"
	"github.com/stretchr/testify/require"
)

func checkError(t *testing.T, err error) {
	if err == nil {
		return
	}
	if t != nil {
		require.NoError(t, err)
	} else {
		panic(err)
	}
}

var schemeServer *http.Server
var badServer *http.Server
var badServerCount int
var testStorageDir = "client"

func StartSchemeManagerHttpServer() {
	path := FindTestdataFolder(nil)
	schemeServer = &http.Server{Addr: "localhost:48681", Handler: http.FileServer(http.Dir(path))}
	go func() {
		_ = schemeServer.ListenAndServe()
	}()
	time.Sleep(100 * time.Millisecond) // Give server time to start
}

func StopSchemeManagerHttpServer() {
	_ = schemeServer.Close()
}

// StartBadHttpServer starts an HTTP server that times out and returns 500 on the first few times.
func StartBadHttpServer(count int, timeout time.Duration, success string) {
	badServer = &http.Server{Addr: "localhost:48682", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if badServerCount >= count {
			_, _ = fmt.Fprintln(w, success)
			return
		} else {
			badServerCount++
			time.Sleep(timeout)
		}
	})}

	go func() {
		_ = badServer.ListenAndServe()
	}()
	time.Sleep(100 * time.Millisecond) // Give server time to start
}

func StopBadHttpServer() {
	_ = badServer.Close()
}

// FindTestdataFolder finds the "testdata" folder which is in . or ..
// depending on which package is calling us.
func FindTestdataFolder(t *testing.T) string {
	path := "testdata"

	for i := 0; i < 4; i++ {
		exists, err := common.PathExists(path)
		checkError(t, err)
		if exists {
			return path
		}
		path = filepath.Join("..", path)
	}

	checkError(t, errors.New("testdata folder not found"))
	return ""
}

// ClearTestStorage removes any output from previously run tests.
func ClearTestStorage(t *testing.T, storage string) {
	checkError(t, os.RemoveAll(storage))
}

func ClearAllTestStorage() {
	dir := filepath.Join(os.TempDir(), "irmatest*")
	matches, err := filepath.Glob(dir)
	checkError(nil, err)
	for _, match := range matches {
		checkError(nil, os.RemoveAll(match))
	}
}

func CreateTestStorage(t *testing.T) string {
	tmp, err := ioutil.TempDir("", "irmatest")
	require.NoError(t, err)
	checkError(t, common.EnsureDirectoryExists(filepath.Join(tmp, "client")))
	return tmp
}

func SetupTestStorage(t *testing.T) string {
	storage := CreateTestStorage(t)
	path := FindTestdataFolder(t)
	err := common.CopyDirectory(filepath.Join(path, testStorageDir), filepath.Join(storage, "client"))
	checkError(t, err)
	return storage
}

func PrettyPrint(t *testing.T, ob interface{}) string {
	b, err := json.MarshalIndent(ob, "", "  ")
	require.NoError(t, err)
	return string(b)
}

func SetTestStorageDir(dir string) {
	testStorageDir = dir
}
<commit_msg>chore: remove unused test PrettyPrint function<commit_after>// Package test contains functionality that should be available to
// all unit tests (which live in separate packages).
package test

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/go-errors/errors"
	"github.com/privacybydesign/irmago/internal/common"
	"github.com/stretchr/testify/require"
)

func checkError(t *testing.T, err error) {
	if err == nil {
		return
	}
	if t != nil {
		require.NoError(t, err)
	} else {
		panic(err)
	}
}

var schemeServer *http.Server
var badServer *http.Server
var badServerCount int
var testStorageDir = "client"

func StartSchemeManagerHttpServer() {
	path := FindTestdataFolder(nil)
	schemeServer = &http.Server{Addr: "localhost:48681", Handler: http.FileServer(http.Dir(path))}
	go func() {
		_ = schemeServer.ListenAndServe()
	}()
	time.Sleep(100 * time.Millisecond) // Give server time to start
}

func StopSchemeManagerHttpServer() {
	_ = schemeServer.Close()
}

// StartBadHttpServer starts an HTTP server that times out and returns 500 on the first few times.
func StartBadHttpServer(count int, timeout time.Duration, success string) {
	badServer = &http.Server{Addr: "localhost:48682", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if badServerCount >= count {
			_, _ = fmt.Fprintln(w, success)
			return
		} else {
			badServerCount++
			time.Sleep(timeout)
		}
	})}

	go func() {
		_ = badServer.ListenAndServe()
	}()
	time.Sleep(100 * time.Millisecond) // Give server time to start
}

func StopBadHttpServer() {
	_ = badServer.Close()
}

// FindTestdataFolder finds the "testdata" folder which is in . or ..
// depending on which package is calling us.
func FindTestdataFolder(t *testing.T) string {
	path := "testdata"

	for i := 0; i < 4; i++ {
		exists, err := common.PathExists(path)
		checkError(t, err)
		if exists {
			return path
		}
		path = filepath.Join("..", path)
	}

	checkError(t, errors.New("testdata folder not found"))
	return ""
}

// ClearTestStorage removes any output from previously run tests.
func ClearTestStorage(t *testing.T, storage string) {
	checkError(t, os.RemoveAll(storage))
}

func ClearAllTestStorage() {
	dir := filepath.Join(os.TempDir(), "irmatest*")
	matches, err := filepath.Glob(dir)
	checkError(nil, err)
	for _, match := range matches {
		checkError(nil, os.RemoveAll(match))
	}
}

func CreateTestStorage(t *testing.T) string {
	tmp, err := ioutil.TempDir("", "irmatest")
	require.NoError(t, err)
	checkError(t, common.EnsureDirectoryExists(filepath.Join(tmp, "client")))
	return tmp
}

func SetupTestStorage(t *testing.T) string {
	storage := CreateTestStorage(t)
	path := FindTestdataFolder(t)
	err := common.CopyDirectory(filepath.Join(path, testStorageDir), filepath.Join(storage, "client"))
	checkError(t, err)
	return storage
}

func SetTestStorageDir(dir string) {
	testStorageDir = dir
}
<|endoftext|>


<commit_before>/**
 * (c) 2014, Tonnerre Lombard <tonnerre@ancient-solutions.com>,
 *	     Ancient Solutions. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the  name  of  Ancient Solutions  nor  the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package serialdata

import (
	"bytes"
	"testing"
)

// Write a bit of test data and test the io.Reader interface.
func TestSerializeAndReadBytes(t *testing.T) {
	var buf *bytes.Buffer = new(bytes.Buffer)
	var writer *SerialDataWriter = NewSerialDataWriter(buf)
	var reader *SerialDataReader
	var rbuf []byte
	var err error
	var l int

	l, err = writer.Write([]byte("Hello"))
	if err != nil {
		t.Error("Error writing record: ", err)
	}

	if l != 9 {
		t.Error("Write length mismatched (expected 9, got ", l, ")")
	}

	if buf.Len() != 9 {
		t.Error("Expected length to be 9, was ", buf.Len())
	}

	l, err = writer.Write([]byte("World"))
	if err != nil {
		t.Error("Error writing record: ", err)
	}

	if l != 9 {
		t.Error("Write length mismatched (expected 9, got ", l, ")")
	}

	if buf.Len() != 18 {
		t.Error("Expected length to be 18, was ", buf.Len())
	}

	reader = NewSerialDataReader(bytes.NewReader(buf.Bytes()))
	rbuf = make([]byte, 20)

	l, err = reader.Read(rbuf)
	if err != nil {
		t.Error("Error reading record: ", err)
	}

	if l != 5 {
		t.Error("Read length mismatched (expected 5, got ", l, ")")
	}

	if string(rbuf[0:l]) != "Hello" {
		t.Error("Unexpected data: got ", string(rbuf), " (", rbuf,
			"), expected Hello")
	}

	rbuf = make([]byte, 20)

	l, err = reader.Read(rbuf)
	if err != nil {
		t.Error("Error reading record: ", err)
	}

	if l != 5 {
		t.Error("Read length mismatched (expected 5, got ", l, ")")
	}

	if string(rbuf[0:l]) != "World" {
		t.Error("Unexpected data: got ", string(rbuf), " (", rbuf,
			"), expected World")
	}
}

// Write a bit of test data and read individual records off it.
func TestSerializeAndReadRecord(t *testing.T) {
	var buf *bytes.Buffer = new(bytes.Buffer)
	var writer *SerialDataWriter = NewSerialDataWriter(buf)
	var reader *SerialDataReader
	var rbuf []byte
	var err error
	var l int

	l, err = writer.Write([]byte("Hello"))
	if err != nil {
		t.Error("Error writing record: ", err)
	}

	if l != 9 {
		t.Error("Write length mismatched (expected 9, got ", l, ")")
	}

	if buf.Len() != 9 {
		t.Error("Expected length to be 9, was ", buf.Len())
	}

	l, err = writer.Write([]byte("World"))
	if err != nil {
		t.Error("Error writing record: ", err)
	}

	if l != 9 {
		t.Error("Write length mismatched (expected 9, got ", l, ")")
	}

	if buf.Len() != 18 {
		t.Error("Expected length to be 18, was ", buf.Len())
	}

	reader = NewSerialDataReader(bytes.NewReader(buf.Bytes()))

	rbuf, err = reader.ReadRecord()
	if err != nil {
		t.Error("Error reading record: ", err)
	}

	if len(rbuf) != 5 {
		t.Error("Read length mismatched (expected 5, got ", len(rbuf), ")")
	}

	if string(rbuf) != "Hello" {
		t.Error("Unexpected data: got ", string(rbuf), " (", rbuf,
			"), expected Hello")
	}

	rbuf, err = reader.ReadRecord()
	if err != nil {
		t.Error("Error reading record: ", err)
	}

	if len(rbuf) != 5 {
		t.Error("Read length mismatched (expected 5, got ", len(rbuf), ")")
	}

	if string(rbuf) != "World" {
		t.Error("Unexpected data: got ", string(rbuf), " (", rbuf,
			"), expected World")
	}
}
<commit_msg>Add a benchmark for reading and writing records.<commit_after>/**
 * (c) 2014, Tonnerre Lombard <tonnerre@ancient-solutions.com>,
 *	     Ancient Solutions. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the  name  of  Ancient Solutions  nor  the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package serialdata

import (
	"bytes"
	"testing"
)

// Write a bit of test data and test the io.Reader interface.
func TestSerializeAndReadBytes(t *testing.T) {
	var buf *bytes.Buffer = new(bytes.Buffer)
	var writer *SerialDataWriter = NewSerialDataWriter(buf)
	var reader *SerialDataReader
	var rbuf []byte
	var err error
	var l int

	l, err = writer.Write([]byte("Hello"))
	if err != nil {
		t.Error("Error writing record: ", err)
	}

	if l != 9 {
		t.Error("Write length mismatched (expected 9, got ", l, ")")
	}

	if buf.Len() != 9 {
		t.Error("Expected length to be 9, was ", buf.Len())
	}

	l, err = writer.Write([]byte("World"))
	if err != nil {
		t.Error("Error writing record: ", err)
	}

	if l != 9 {
		t.Error("Write length mismatched (expected 9, got ", l, ")")
	}

	if buf.Len() != 18 {
		t.Error("Expected length to be 18, was ", buf.Len())
	}

	reader = NewSerialDataReader(bytes.NewReader(buf.Bytes()))
	rbuf = make([]byte, 20)

	l, err = reader.Read(rbuf)
	if err != nil {
		t.Error("Error reading record: ", err)
	}

	if l != 5 {
		t.Error("Read length mismatched (expected 5, got ", l, ")")
	}

	if string(rbuf[0:l]) != "Hello" {
		t.Error("Unexpected data: got ", string(rbuf), " (", rbuf,
			"), expected Hello")
	}

	rbuf = make([]byte, 20)

	l, err = reader.Read(rbuf)
	if err != nil {
		t.Error("Error reading record: ", err)
	}

	if l != 5 {
		t.Error("Read length mismatched (expected 5, got ", l, ")")
	}

	if string(rbuf[0:l]) != "World" {
		t.Error("Unexpected data: got ", string(rbuf), " (", rbuf,
			"), expected World")
	}
}

// Write a bit of test data and read individual records off it.
func TestSerializeAndReadRecord(t *testing.T) {
	var buf *bytes.Buffer = new(bytes.Buffer)
	var writer *SerialDataWriter = NewSerialDataWriter(buf)
	var reader *SerialDataReader
	var rbuf []byte
	var err error
	var l int

	l, err = writer.Write([]byte("Hello"))
	if err != nil {
		t.Error("Error writing record: ", err)
	}

	if l != 9 {
		t.Error("Write length mismatched (expected 9, got ", l, ")")
	}

	if buf.Len() != 9 {
		t.Error("Expected length to be 9, was ", buf.Len())
	}

	l, err = writer.Write([]byte("World"))
	if err != nil {
		t.Error("Error writing record: ", err)
	}

	if l != 9 {
		t.Error("Write length mismatched (expected 9, got ", l, ")")
	}

	if buf.Len() != 18 {
		t.Error("Expected length to be 18, was ", buf.Len())
	}

	reader = NewSerialDataReader(bytes.NewReader(buf.Bytes()))

	rbuf, err = reader.ReadRecord()
	if err != nil {
		t.Error("Error reading record: ", err)
	}

	if len(rbuf) != 5 {
		t.Error("Read length mismatched (expected 5, got ", len(rbuf), ")")
	}

	if string(rbuf) != "Hello" {
		t.Error("Unexpected data: got ", string(rbuf), " (", rbuf,
			"), expected Hello")
	}

	rbuf, err = reader.ReadRecord()
	if err != nil {
		t.Error("Error reading record: ", err)
	}

	if len(rbuf) != 5 {
		t.Error("Read length mismatched (expected 5, got ", len(rbuf), ")")
	}

	if string(rbuf) != "World" {
		t.Error("Unexpected data: got ", string(rbuf), " (", rbuf,
			"), expected World")
	}
}

// Write a bunch of records to a memory buffer and read them back.
// Essentially, desperately shouting "Hello" into the void 10'000 times.
func BenchmarkRecordWriterAndReader(b *testing.B) {
	var buf *bytes.Buffer = new(bytes.Buffer)
	var writer *SerialDataWriter = NewSerialDataWriter(buf)
	var reader *SerialDataReader
	var rbuf []byte
	var err error
	var i, l int

	b.StartTimer()

	for i = 0; i < b.N; i++ {
		l, err = writer.Write([]byte("Hello"))
		if err != nil {
			b.Error("Error writing record: ", err)
		}

		if l != 9 {
			b.Error("Write length mismatched (expected 9, got ", l, ")")
		}
	}

	reader = NewSerialDataReader(bytes.NewReader(buf.Bytes()))
	for i = 0; i < b.N; i++ {
		rbuf, err = reader.ReadRecord()
		if err != nil {
			b.Error("Error reading record: ", err)
		}

		if len(rbuf) != 5 {
			b.Error("Read length mismatched (expected 5, got ", len(rbuf), ")")
		}

		if string(rbuf) != "Hello" {
			b.Error("Unexpected data: got ", string(rbuf), " (", rbuf,
				"), expected Hello")
		}
	}

	b.StopTimer()
	b.ReportAllocs()
}
<|endoftext|>


<commit_before>package api

import (
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

const cookieName = "c"

//authCookieHandler gets the JWT and the uid and the cookie. If the given uid
//is  already tied to the given cookie, it does nothing and returns success.
//If the cookie is tied to a different uid, it barfs. If there is no UID, but
//there is a cookie, it removes that row in the DB and Set-Cookie's to remove
//the cookie. If there is no cookie, it validates the JWT, and then creates a
//new cookie tyied to that uid (creating that user record if necessary), and
//Set-Cookie's it back.
func (s *Server) authCookieHandler(c *gin.Context) {
	if c.Request.Method != http.MethodPost {
		panic("This can only be called as a post.")
	}

	uid := c.PostForm("uid")
	token := c.PostForm("token")

	cookie, _ := c.Cookie(cookieName)

	log.Println("Auth Cookie Handler called", uid, token, cookie, "*")

	//If the user is already associated with that cookie it's a success, nothing more to do.

	if cookie != "" {
		userRecord := s.storage.GetUserByCookie(cookie)

		if userRecord != nil {
			if userRecord.Id == uid {
				c.JSON(http.StatusOK, gin.H{
					"Status": "Success",
				})
			}
		}
	}

	if uid == "" && cookie != "" {
		//We must have an old cookie set. Clear it out.
		if err := s.storage.ConnectCookieToUser(cookie, nil); err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  err.Error(),
			})
		} else {

			//TODO: use Set-Cookie to remove the cookie from the client.

			c.JSON(http.StatusOK, gin.H{
				"Status": "Success",
			})
		}
	}

	c.JSON(http.StatusOK, gin.H{
		"Status": "Failure",
		"Error":  "Not Yet Implemented",
	})
}
<commit_msg>If the cookie is not set and the uid is, connect cookie to user. Doesn't yet actually set-cookie to client. NOTE: doesn't actually check JWT yet. This MUST be resolved before being rolled to production.<commit_after>package api

import (
	"github.com/gin-gonic/gin"
	"github.com/jkomoros/boardgame/server/api/users"
	"log"
	"math/rand"
	"net/http"
)

const cookieName = "c"
const cookieLength = 64

const randomStringChars = "abcdefghijklmnopqrstuvwxyz0123456789"

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

//authCookieHandler gets the JWT and the uid and the cookie. If the given uid
//is  already tied to the given cookie, it does nothing and returns success.
//If the cookie is tied to a different uid, it barfs. If there is no UID, but
//there is a cookie, it removes that row in the DB and Set-Cookie's to remove
//the cookie. If there is no cookie, it validates the JWT, and then creates a
//new cookie tyied to that uid (creating that user record if necessary), and
//Set-Cookie's it back.
func (s *Server) authCookieHandler(c *gin.Context) {
	if c.Request.Method != http.MethodPost {
		panic("This can only be called as a post.")
	}

	uid := c.PostForm("uid")
	token := c.PostForm("token")

	cookie, _ := c.Cookie(cookieName)

	log.Println("Auth Cookie Handler called", uid, token, cookie, "*")

	//If the user is already associated with that cookie it's a success, nothing more to do.

	if cookie != "" {
		userRecord := s.storage.GetUserByCookie(cookie)

		if userRecord != nil {
			if userRecord.Id == uid {
				c.JSON(http.StatusOK, gin.H{
					"Status": "Success",
				})
				return
			}
		}
	}

	if uid == "" && cookie != "" {
		//We must have an old cookie set. Clear it out.
		if err := s.storage.ConnectCookieToUser(cookie, nil); err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  err.Error(),
			})
			return
		}

		//TODO: use Set-Cookie to remove the cookie from the client.

		c.JSON(http.StatusOK, gin.H{
			"Status": "Success",
		})

		return

	}

	if cookie == "" && uid != "" {

		//********************************
		//TODO: (IMPORTANT) actually validate JWT
		//********************************

		user := s.storage.GetUserById(uid)

		//If we've never seen this Uid before, store it.
		if user == nil {

			user = &users.StorageRecord{
				Id: uid,
			}
			s.storage.UpdateUser(user)

		}

		cookie = randomString(cookieLength)

		if err := s.storage.ConnectCookieToUser(cookie, user); err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  "Couldn't connect cookie to user: " + err.Error(),
			})
			return
		}

		//TODO: Set-Cookie to client

		c.JSON(http.StatusOK, gin.H{
			"Status": "Success",
		})

	}

	c.JSON(http.StatusOK, gin.H{
		"Status": "Failure",
		"Error":  "Not Yet Implemented",
	})
}
<|endoftext|>


<commit_before>package api

import (
	"github.com/gin-gonic/gin"
	"github.com/jkomoros/boardgame/server/api/users"
	"log"
	"math/rand"
	"net/http"
)

const cookieName = "c"
const cookieLength = 64

const randomStringChars = "abcdefghijklmnopqrstuvwxyz0123456789"

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

//authCookieHandler gets the JWT and the uid and the cookie. If the given uid
//is  already tied to the given cookie, it does nothing and returns success.
//If the cookie is tied to a different uid, it barfs. If there is no UID, but
//there is a cookie, it removes that row in the DB and Set-Cookie's to remove
//the cookie. If there is no cookie, it validates the JWT, and then creates a
//new cookie tyied to that uid (creating that user record if necessary), and
//Set-Cookie's it back.
func (s *Server) authCookieHandler(c *gin.Context) {
	if c.Request.Method != http.MethodPost {
		panic("This can only be called as a post.")
	}

	uid := c.PostForm("uid")
	token := c.PostForm("token")

	cookie, _ := c.Cookie(cookieName)

	log.Println("Auth Cookie Handler called", uid, token, cookie, "*")

	//If the user is already associated with that cookie it's a success, nothing more to do.

	if cookie != "" {
		userRecord := s.storage.GetUserByCookie(cookie)

		if userRecord != nil {
			if userRecord.Id == uid {
				c.JSON(http.StatusOK, gin.H{
					"Status": "Success",
				})
				return
			}
		}
	}

	if uid == "" && cookie != "" {
		//We must have an old cookie set. Clear it out.
		if err := s.storage.ConnectCookieToUser(cookie, nil); err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  err.Error(),
			})
			return
		}

		//TODO: use Set-Cookie to remove the cookie from the client.

		c.JSON(http.StatusOK, gin.H{
			"Status": "Success",
		})

		return

	}

	if cookie == "" && uid != "" {

		//********************************
		//TODO: (IMPORTANT) actually validate JWT
		//********************************

		user := s.storage.GetUserById(uid)

		//If we've never seen this Uid before, store it.
		if user == nil {

			user = &users.StorageRecord{
				Id: uid,
			}
			s.storage.UpdateUser(user)

		}

		cookie = randomString(cookieLength)

		if err := s.storage.ConnectCookieToUser(cookie, user); err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  "Couldn't connect cookie to user: " + err.Error(),
			})
			return
		}

		//TODO: Set-Cookie to client

		c.JSON(http.StatusOK, gin.H{
			"Status": "Success",
		})

	}

	c.JSON(http.StatusOK, gin.H{
		"Status": "Failure",
		"Error":  "Not Yet Implemented",
	})
}
<commit_msg>Actually SetCookie when cookies change. Not yet tested.<commit_after>package api

import (
	"github.com/gin-gonic/gin"
	"github.com/jkomoros/boardgame/server/api/users"
	"log"
	"math/rand"
	"net/http"
	"time"
)

const cookieName = "c"
const cookieLength = 64

const randomStringChars = "abcdefghijklmnopqrstuvwxyz0123456789"

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

//authCookieHandler gets the JWT and the uid and the cookie. If the given uid
//is  already tied to the given cookie, it does nothing and returns success.
//If the cookie is tied to a different uid, it barfs. If there is no UID, but
//there is a cookie, it removes that row in the DB and Set-Cookie's to remove
//the cookie. If there is no cookie, it validates the JWT, and then creates a
//new cookie tyied to that uid (creating that user record if necessary), and
//Set-Cookie's it back.
func (s *Server) authCookieHandler(c *gin.Context) {
	if c.Request.Method != http.MethodPost {
		panic("This can only be called as a post.")
	}

	uid := c.PostForm("uid")
	token := c.PostForm("token")

	cookie, _ := c.Cookie(cookieName)

	log.Println("Auth Cookie Handler called", uid, token, cookie, "*")

	//If the user is already associated with that cookie it's a success, nothing more to do.

	if cookie != "" {
		userRecord := s.storage.GetUserByCookie(cookie)

		if userRecord != nil {
			if userRecord.Id == uid {
				c.JSON(http.StatusOK, gin.H{
					"Status": "Success",
				})
				return
			}
		}
	}

	if uid == "" && cookie != "" {
		//We must have an old cookie set. Clear it out.
		if err := s.storage.ConnectCookieToUser(cookie, nil); err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  err.Error(),
			})
			return
		}

		//Delete the cookie on the client.

		//TODO: might need to set the domain in production.
		c.SetCookie(cookieName, "", int(time.Now().Add(time.Hour*10000*-1).Unix()), "", "", false, false)

		c.JSON(http.StatusOK, gin.H{
			"Status": "Success",
		})

		return

	}

	if cookie == "" && uid != "" {

		//********************************
		//TODO: (IMPORTANT) actually validate JWT
		//********************************

		user := s.storage.GetUserById(uid)

		//If we've never seen this Uid before, store it.
		if user == nil {

			user = &users.StorageRecord{
				Id: uid,
			}
			s.storage.UpdateUser(user)

		}

		cookie = randomString(cookieLength)

		if err := s.storage.ConnectCookieToUser(cookie, user); err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  "Couldn't connect cookie to user: " + err.Error(),
			})
			return
		}

		//TODO: might need to set the domain in production
		c.SetCookie(cookieName, cookie, int(time.Now().Add(time.Hour*100).Unix()), "", "", false, false)

		c.JSON(http.StatusOK, gin.H{
			"Status": "Success",
		})

	}

	c.JSON(http.StatusOK, gin.H{
		"Status": "Failure",
		"Error":  "Not Yet Implemented",
	})
}
<|endoftext|>