<commit_before>package aws

import (
	"fmt"
	"net/url"
	"regexp"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/iam"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceAwsIamRole() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsIamRoleCreate,
		Read:   resourceAwsIamRoleRead,
		Update: resourceAwsIamRoleUpdate,
		Delete: resourceAwsIamRoleDelete,
		Importer: &schema.ResourceImporter{
			State: resourceAwsIamRoleImport,
		},

		Schema: map[string]*schema.Schema{
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"unique_id": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"name": {
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					// https://github.com/boto/botocore/blob/2485f5c/botocore/data/iam/2010-05-08/service-2.json#L8329-L8334
					value := v.(string)
					if len(value) > 64 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 64 characters", k))
					}
					if !regexp.MustCompile("^[\\w+=,.@-]*$").MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"%q must match [\\w+=,.@-]", k))
					}
					return
				},
			},

			"name_prefix": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					// https://github.com/boto/botocore/blob/2485f5c/botocore/data/iam/2010-05-08/service-2.json#L8329-L8334
					value := v.(string)
					if len(value) > 32 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 32 characters, name is limited to 64", k))
					}
					if !regexp.MustCompile("^[\\w+=,.@-]*$").MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"%q must match [\\w+=,.@-]", k))
					}
					return
				},
			},

			"path": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  "/",
				ForceNew: true,
			},

			"description": {
				Type:         schema.TypeString,
				Optional:     true,
				ValidateFunc: validateIamRoleDescription,
			},

			"assume_role_policy": {
				Type:             schema.TypeString,
				Required:         true,
				DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs,
				ValidateFunc:     validateJsonString,
			},

			"force_detach_policies": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},

			"create_date": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"max_session_duration": {
				Type:         schema.TypeInt,
				Optional:     true,
				Default:      3600,
				ValidateFunc: validation.IntBetween(3600, 43200),
			},
		},
	}
}

func resourceAwsIamRoleImport(
	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
	d.Set("force_detach_policies", false)
	return []*schema.ResourceData{d}, nil
}

func resourceAwsIamRoleCreate(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	var name string
	if v, ok := d.GetOk("name"); ok {
		name = v.(string)
	} else if v, ok := d.GetOk("name_prefix"); ok {
		name = resource.PrefixedUniqueId(v.(string))
	} else {
		name = resource.UniqueId()
	}

	request := &iam.CreateRoleInput{
		Path:                     aws.String(d.Get("path").(string)),
		RoleName:                 aws.String(name),
		AssumeRolePolicyDocument: aws.String(d.Get("assume_role_policy").(string)),
	}

	if v, ok := d.GetOk("description"); ok {
		request.Description = aws.String(v.(string))
	}

	if v, ok := d.GetOk("max_session_duration"); ok {
		request.MaxSessionDuration = aws.Int64(v.(int64))
	}

	var createResp *iam.CreateRoleOutput
	err := resource.Retry(30*time.Second, func() *resource.RetryError {
		var err error
		createResp, err = iamconn.CreateRole(request)
		// IAM users (referenced in Principal field of assume policy)
		// can take ~30 seconds to propagate in AWS
		if isAWSErr(err, "MalformedPolicyDocument", "Invalid principal in policy") {
			return resource.RetryableError(err)
		}
		return resource.NonRetryableError(err)
	})
	if err != nil {
		return fmt.Errorf("Error creating IAM Role %s: %s", name, err)
	}
	d.SetId(*createResp.Role.RoleName)
	return resourceAwsIamRoleRead(d, meta)
}

func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	request := &iam.GetRoleInput{
		RoleName: aws.String(d.Id()),
	}

	getResp, err := iamconn.GetRole(request)
	if err != nil {
		if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { // XXX test me
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error reading IAM Role %s: %s", d.Id(), err)
	}

	role := getResp.Role

	if err := d.Set("name", role.RoleName); err != nil {
		return err
	}
	if err := d.Set("max_session_duration", role.MaxSessionDuration); err != nil {
		return err
	}
	if err := d.Set("arn", role.Arn); err != nil {
		return err
	}
	if err := d.Set("path", role.Path); err != nil {
		return err
	}
	if err := d.Set("unique_id", role.RoleId); err != nil {
		return err
	}
	if err := d.Set("create_date", role.CreateDate.Format(time.RFC3339)); err != nil {
		return err
	}

	if role.Description != nil {
		// the description isn't present in the response to CreateRole.
		if err := d.Set("description", role.Description); err != nil {
			return err
		}
	}

	assumRolePolicy, err := url.QueryUnescape(*role.AssumeRolePolicyDocument)
	if err != nil {
		return err
	}
	if err := d.Set("assume_role_policy", assumRolePolicy); err != nil {
		return err
	}
	return nil
}

func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	if d.HasChange("assume_role_policy") {
		assumeRolePolicyInput := &iam.UpdateAssumeRolePolicyInput{
			RoleName:       aws.String(d.Id()),
			PolicyDocument: aws.String(d.Get("assume_role_policy").(string)),
		}
		_, err := iamconn.UpdateAssumeRolePolicy(assumeRolePolicyInput)
		if err != nil {
			if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" {
				d.SetId("")
				return nil
			}
			return fmt.Errorf("Error Updating IAM Role (%s) Assume Role Policy: %s", d.Id(), err)
		}
	}

	if d.HasChange("description") {
		roleDescriptionInput := &iam.UpdateRoleDescriptionInput{
			RoleName:    aws.String(d.Id()),
			Description: aws.String(d.Get("description").(string)),
		}
		_, err := iamconn.UpdateRoleDescription(roleDescriptionInput)
		if err != nil {
			if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" {
				d.SetId("")
				return nil
			}
			return fmt.Errorf("Error Updating IAM Role (%s) Assume Role Policy: %s", d.Id(), err)
		}
	}

	if d.HasChange("max_session_duration") {
		roleMaxDurationInput := &iam.UpdateRoleInput{
			RoleName:           aws.String(d.Id()),
			MaxSessionDuration: aws.Int64(int64(d.Get("max_session_duration").(int))),
		}
		_, err := iamconn.UpdateRole(roleMaxDurationInput)
		if err != nil {
			if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
				d.SetId("")
				return nil
			}
			return fmt.Errorf("Error Updating IAM Role (%s) Max Session Duration: %s", d.Id(), err)
		}
	}

	return nil
}

func resourceAwsIamRoleDelete(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	// Roles cannot be destroyed when attached to an existing Instance Profile
	resp, err := iamconn.ListInstanceProfilesForRole(&iam.ListInstanceProfilesForRoleInput{
		RoleName: aws.String(d.Id()),
	})
	if err != nil {
		return fmt.Errorf("Error listing Profiles for IAM Role (%s) when trying to delete: %s", d.Id(), err)
	}

	// Loop and remove this Role from any Profiles
	if len(resp.InstanceProfiles) > 0 {
		for _, i := range resp.InstanceProfiles {
			_, err := iamconn.RemoveRoleFromInstanceProfile(&iam.RemoveRoleFromInstanceProfileInput{
				InstanceProfileName: i.InstanceProfileName,
				RoleName:            aws.String(d.Id()),
			})
			if err != nil {
				return fmt.Errorf("Error deleting IAM Role %s: %s", d.Id(), err)
			}
		}
	}

	if d.Get("force_detach_policies").(bool) {
		// For managed policies
		managedPolicies := make([]*string, 0)
		err = iamconn.ListAttachedRolePoliciesPages(&iam.ListAttachedRolePoliciesInput{
			RoleName: aws.String(d.Id()),
		}, func(page *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool {
			for _, v := range page.AttachedPolicies {
				managedPolicies = append(managedPolicies, v.PolicyArn)
			}
			return len(page.AttachedPolicies) > 0
		})
		if err != nil {
			return fmt.Errorf("Error listing Policies for IAM Role (%s) when trying to delete: %s", d.Id(), err)
		}
		if len(managedPolicies) > 0 {
			for _, parn := range managedPolicies {
				_, err = iamconn.DetachRolePolicy(&iam.DetachRolePolicyInput{
					PolicyArn: parn,
					RoleName:  aws.String(d.Id()),
				})
				if err != nil {
					return fmt.Errorf("Error deleting IAM Role %s: %s", d.Id(), err)
				}
			}
		}

		// For inline policies
		inlinePolicies := make([]*string, 0)
		err = iamconn.ListRolePoliciesPages(&iam.ListRolePoliciesInput{
			RoleName: aws.String(d.Id()),
		}, func(page *iam.ListRolePoliciesOutput, lastPage bool) bool {
			for _, v := range page.PolicyNames {
				inlinePolicies = append(inlinePolicies, v)
			}
			return len(page.PolicyNames) > 0
		})
		if err != nil {
			return fmt.Errorf("Error listing inline Policies for IAM Role (%s) when trying to delete: %s", d.Id(), err)
		}
		if len(inlinePolicies) > 0 {
			for _, pname := range inlinePolicies {
				_, err := iamconn.DeleteRolePolicy(&iam.DeleteRolePolicyInput{
					PolicyName: pname,
					RoleName:   aws.String(d.Id()),
				})
				if err != nil {
					return fmt.Errorf("Error deleting inline policy of IAM Role %s: %s", d.Id(), err)
				}
			}
		}
	}

	request := &iam.DeleteRoleInput{
		RoleName: aws.String(d.Id()),
	}

	// IAM is eventually consistent and deletion of attached policies may take time
	return resource.Retry(30*time.Second, func() *resource.RetryError {
		_, err := iamconn.DeleteRole(request)
		if err != nil {
			awsErr, ok := err.(awserr.Error)
			if ok && awsErr.Code() == "DeleteConflict" {
				return resource.RetryableError(err)
			}

			return resource.NonRetryableError(fmt.Errorf("Error deleting IAM Role %s: %s", d.Id(), err))
		}
		return nil
	})
}
<commit_msg>resource/aws_iam_role: Fix panic when using max_session_duration<commit_after>package aws

import (
	"fmt"
	"net/url"
	"regexp"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/iam"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceAwsIamRole() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsIamRoleCreate,
		Read:   resourceAwsIamRoleRead,
		Update: resourceAwsIamRoleUpdate,
		Delete: resourceAwsIamRoleDelete,
		Importer: &schema.ResourceImporter{
			State: resourceAwsIamRoleImport,
		},

		Schema: map[string]*schema.Schema{
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"unique_id": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"name": {
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					// https://github.com/boto/botocore/blob/2485f5c/botocore/data/iam/2010-05-08/service-2.json#L8329-L8334
					value := v.(string)
					if len(value) > 64 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 64 characters", k))
					}
					if !regexp.MustCompile("^[\\w+=,.@-]*$").MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"%q must match [\\w+=,.@-]", k))
					}
					return
				},
			},

			"name_prefix": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					// https://github.com/boto/botocore/blob/2485f5c/botocore/data/iam/2010-05-08/service-2.json#L8329-L8334
					value := v.(string)
					if len(value) > 32 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 32 characters, name is limited to 64", k))
					}
					if !regexp.MustCompile("^[\\w+=,.@-]*$").MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"%q must match [\\w+=,.@-]", k))
					}
					return
				},
			},

			"path": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  "/",
				ForceNew: true,
			},

			"description": {
				Type:         schema.TypeString,
				Optional:     true,
				ValidateFunc: validateIamRoleDescription,
			},

			"assume_role_policy": {
				Type:             schema.TypeString,
				Required:         true,
				DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs,
				ValidateFunc:     validateJsonString,
			},

			"force_detach_policies": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},

			"create_date": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"max_session_duration": {
				Type:         schema.TypeInt,
				Optional:     true,
				Default:      3600,
				ValidateFunc: validation.IntBetween(3600, 43200),
			},
		},
	}
}

func resourceAwsIamRoleImport(
	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
	d.Set("force_detach_policies", false)
	return []*schema.ResourceData{d}, nil
}

func resourceAwsIamRoleCreate(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	var name string
	if v, ok := d.GetOk("name"); ok {
		name = v.(string)
	} else if v, ok := d.GetOk("name_prefix"); ok {
		name = resource.PrefixedUniqueId(v.(string))
	} else {
		name = resource.UniqueId()
	}

	request := &iam.CreateRoleInput{
		Path:                     aws.String(d.Get("path").(string)),
		RoleName:                 aws.String(name),
		AssumeRolePolicyDocument: aws.String(d.Get("assume_role_policy").(string)),
	}

	if v, ok := d.GetOk("description"); ok {
		request.Description = aws.String(v.(string))
	}

	if v, ok := d.GetOk("max_session_duration"); ok {
		request.MaxSessionDuration = aws.Int64(int64(v.(int)))
	}

	var createResp *iam.CreateRoleOutput
	err := resource.Retry(30*time.Second, func() *resource.RetryError {
		var err error
		createResp, err = iamconn.CreateRole(request)
		// IAM users (referenced in Principal field of assume policy)
		// can take ~30 seconds to propagate in AWS
		if isAWSErr(err, "MalformedPolicyDocument", "Invalid principal in policy") {
			return resource.RetryableError(err)
		}
		return resource.NonRetryableError(err)
	})
	if err != nil {
		return fmt.Errorf("Error creating IAM Role %s: %s", name, err)
	}
	d.SetId(*createResp.Role.RoleName)
	return resourceAwsIamRoleRead(d, meta)
}

func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	request := &iam.GetRoleInput{
		RoleName: aws.String(d.Id()),
	}

	getResp, err := iamconn.GetRole(request)
	if err != nil {
		if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { // XXX test me
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error reading IAM Role %s: %s", d.Id(), err)
	}

	role := getResp.Role

	if err := d.Set("name", role.RoleName); err != nil {
		return err
	}
	if err := d.Set("max_session_duration", role.MaxSessionDuration); err != nil {
		return err
	}
	if err := d.Set("arn", role.Arn); err != nil {
		return err
	}
	if err := d.Set("path", role.Path); err != nil {
		return err
	}
	if err := d.Set("unique_id", role.RoleId); err != nil {
		return err
	}
	if err := d.Set("create_date", role.CreateDate.Format(time.RFC3339)); err != nil {
		return err
	}

	if role.Description != nil {
		// the description isn't present in the response to CreateRole.
		if err := d.Set("description", role.Description); err != nil {
			return err
		}
	}

	assumRolePolicy, err := url.QueryUnescape(*role.AssumeRolePolicyDocument)
	if err != nil {
		return err
	}
	if err := d.Set("assume_role_policy", assumRolePolicy); err != nil {
		return err
	}
	return nil
}

func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	if d.HasChange("assume_role_policy") {
		assumeRolePolicyInput := &iam.UpdateAssumeRolePolicyInput{
			RoleName:       aws.String(d.Id()),
			PolicyDocument: aws.String(d.Get("assume_role_policy").(string)),
		}
		_, err := iamconn.UpdateAssumeRolePolicy(assumeRolePolicyInput)
		if err != nil {
			if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" {
				d.SetId("")
				return nil
			}
			return fmt.Errorf("Error Updating IAM Role (%s) Assume Role Policy: %s", d.Id(), err)
		}
	}

	if d.HasChange("description") {
		roleDescriptionInput := &iam.UpdateRoleDescriptionInput{
			RoleName:    aws.String(d.Id()),
			Description: aws.String(d.Get("description").(string)),
		}
		_, err := iamconn.UpdateRoleDescription(roleDescriptionInput)
		if err != nil {
			if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" {
				d.SetId("")
				return nil
			}
			return fmt.Errorf("Error Updating IAM Role (%s) Assume Role Policy: %s", d.Id(), err)
		}
	}

	if d.HasChange("max_session_duration") {
		roleMaxDurationInput := &iam.UpdateRoleInput{
			RoleName:           aws.String(d.Id()),
			MaxSessionDuration: aws.Int64(int64(d.Get("max_session_duration").(int))),
		}
		_, err := iamconn.UpdateRole(roleMaxDurationInput)
		if err != nil {
			if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
				d.SetId("")
				return nil
			}
			return fmt.Errorf("Error Updating IAM Role (%s) Max Session Duration: %s", d.Id(), err)
		}
	}

	return nil
}

func resourceAwsIamRoleDelete(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	// Roles cannot be destroyed when attached to an existing Instance Profile
	resp, err := iamconn.ListInstanceProfilesForRole(&iam.ListInstanceProfilesForRoleInput{
		RoleName: aws.String(d.Id()),
	})
	if err != nil {
		return fmt.Errorf("Error listing Profiles for IAM Role (%s) when trying to delete: %s", d.Id(), err)
	}

	// Loop and remove this Role from any Profiles
	if len(resp.InstanceProfiles) > 0 {
		for _, i := range resp.InstanceProfiles {
			_, err := iamconn.RemoveRoleFromInstanceProfile(&iam.RemoveRoleFromInstanceProfileInput{
				InstanceProfileName: i.InstanceProfileName,
				RoleName:            aws.String(d.Id()),
			})
			if err != nil {
				return fmt.Errorf("Error deleting IAM Role %s: %s", d.Id(), err)
			}
		}
	}

	if d.Get("force_detach_policies").(bool) {
		// For managed policies
		managedPolicies := make([]*string, 0)
		err = iamconn.ListAttachedRolePoliciesPages(&iam.ListAttachedRolePoliciesInput{
			RoleName: aws.String(d.Id()),
		}, func(page *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool {
			for _, v := range page.AttachedPolicies {
				managedPolicies = append(managedPolicies, v.PolicyArn)
			}
			return len(page.AttachedPolicies) > 0
		})
		if err != nil {
			return fmt.Errorf("Error listing Policies for IAM Role (%s) when trying to delete: %s", d.Id(), err)
		}
		if len(managedPolicies) > 0 {
			for _, parn := range managedPolicies {
				_, err = iamconn.DetachRolePolicy(&iam.DetachRolePolicyInput{
					PolicyArn: parn,
					RoleName:  aws.String(d.Id()),
				})
				if err != nil {
					return fmt.Errorf("Error deleting IAM Role %s: %s", d.Id(), err)
				}
			}
		}

		// For inline policies
		inlinePolicies := make([]*string, 0)
		err = iamconn.ListRolePoliciesPages(&iam.ListRolePoliciesInput{
			RoleName: aws.String(d.Id()),
		}, func(page *iam.ListRolePoliciesOutput, lastPage bool) bool {
			for _, v := range page.PolicyNames {
				inlinePolicies = append(inlinePolicies, v)
			}
			return len(page.PolicyNames) > 0
		})
		if err != nil {
			return fmt.Errorf("Error listing inline Policies for IAM Role (%s) when trying to delete: %s", d.Id(), err)
		}
		if len(inlinePolicies) > 0 {
			for _, pname := range inlinePolicies {
				_, err := iamconn.DeleteRolePolicy(&iam.DeleteRolePolicyInput{
					PolicyName: pname,
					RoleName:   aws.String(d.Id()),
				})
				if err != nil {
					return fmt.Errorf("Error deleting inline policy of IAM Role %s: %s", d.Id(), err)
				}
			}
		}
	}

	request := &iam.DeleteRoleInput{
		RoleName: aws.String(d.Id()),
	}

	// IAM is eventually consistent and deletion of attached policies may take time
	return resource.Retry(30*time.Second, func() *resource.RetryError {
		_, err := iamconn.DeleteRole(request)
		if err != nil {
			awsErr, ok := err.(awserr.Error)
			if ok && awsErr.Code() == "DeleteConflict" {
				return resource.RetryableError(err)
			}

			return resource.NonRetryableError(fmt.Errorf("Error deleting IAM Role %s: %s", d.Id(), err))
		}
		return nil
	})
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"

	"firepear.net/asock"
	"github.com/sboyettedh/whiplash"
)

var (
	// whiplash configuration file
	whipconf string
	// storage for current status of all reporting services
	svcs = map[string]*whiplash.SvcCore{}
	// per-service update timestamps
	upds = map[string]map[string]int64{}
	// osd status info
	osdstats = map[string]*whiplash.OsdStat{}
	// host-to-service mapping
	svcmap = map[string][]string{}
	// pre-rolled messages
	success = []byte("received")
)

func init() {
	flag.StringVar(&whipconf, "whipconf", "/etc/whiplash.conf", "Whiplash configuration file")
}

func main() {
	// parse flags
	flag.Parse()
	// read the whiplash configuration
	wl, err := whiplash.New(whipconf, false)
	if err != nil {
		log.Fatalf("error reading configuration file: %v\n", err)
	}
	// and do application initialization
	sigchan := whiplash.AppSetup("whiplash-aggregator", "0.3.0", asock.Version)
	defer whiplash.AppCleanup("whiplash-aggregator")

	// setup the client asock instance. first set the msglvl, then
	// instantiate the asock.
	var msglvl int
	switch wl.Aggregator.MsgLvl {
	case "all":
		msglvl = asock.All
	case "conn":
		msglvl = asock.Conn
	case "error":
		msglvl = asock.Error
	case "fatal":
		msglvl = asock.Fatal
	}
	asconf := asock.Config{
		Sockname: wl.Aggregator.BindAddr + ":" + wl.Aggregator.BindPort,
		Msglvl: msglvl,
		Timeout: wl.Aggregator.Timeout,
	}
	cas, err := asock.NewTCP(asconf)
	if err != nil {
		log.Fatal(err)
	}
	// and add command handlers to the asock instance
	handlers := map[string]asock.DispatchFunc{
		"ping": pingHandler,
		"stat": statHandler,
	}
	for name, handler := range handlers {
		err = cas.AddHandler(name, "nosplit", handler)
		if err != nil {
			log.Fatal(err)
		}
	}
	log.Println("client asock instantiated")

	// now setup the query asock instance
	asconf = asock.Config{
		Sockname: wl.Aggregator.BindAddr + ":" + wl.Aggregator.QueryPort,
		Msglvl: msglvl,
		Timeout: wl.Aggregator.QTimeout,
	}
	qas, err := asock.NewTCP(asconf)
	if err != nil {
		log.Fatal(err)
	}
	// add command handlers to the query asock instance
	handlers = map[string]asock.DispatchFunc{
		"echo": qhEcho,
	}
	for name, handler := range handlers {
		err = qas.AddHandler(name, "split", handler)
		if err != nil {
			log.Fatal(err)
		}
	}
	log.Println("query asock instantiated")

	// create a channel for the client asock Msgr handler
	msgchan := make(chan error, 1)
	// and one for the query Msgr handler
	querychan := make(chan error, 1)
	// and launch them
	go msgHandler(cas, msgchan)
	go msgHandler(qas, querychan)
	log.Println("aggregator now listening")

	// this is the mainloop of the application.
	keepalive := true
	for keepalive {
		select {
		case msg := <-msgchan:
			// we've been handed a Msg over msgchan, which means that
			// our Asock has shut itself down for some reason. if this
			// were a more robust server, we would modularize Asock
			// creation and this eventloop, so that should we trap a
			// 599 we could spawn a new Asock and launch it in this
			// one's place. but we're just gonna exit this loop,
			// causing main() to terminate, and with it the server
			// instance.
			log.Println("Asock instance has shut down. Last Msg received was:")
			log.Println(msg)
			keepalive = false
			break
		case msg := <-querychan:
			// the query handler has died. it should be safe to
			// restart.
			log.Println("Query asock instance has shut down. Last Msg received was:")
			log.Println(msg)
			log.Println("Restarting query asock...")
			// TODO what it says ^^there
		case <- sigchan:
			// we've trapped a signal from the OS. tell our Asock to
			// shut down, but don't exit the eventloop because we want
			// to handle the Msgs which will be incoming.
			log.Println("OS signal received; shutting down")
			cas.Quit()
		}
		// there's no default case in the select, as that would cause
		// it to be nonblocking. and that would cause main() to exit
		// immediately.
	}
}

func msgHandler(as *asock.Asock, msgchan chan error) {
	var msg *asock.Msg
	keepalive := true

	for keepalive {
		// wait on a Msg to arrive and do a switch based on status code
		msg = <-as.Msgr
		switch msg.Code {
		case 599:
			// 599 is "the Asock listener has died". this means we're
			// not accepting connections anymore. call as.Quit() to
			// clean things up, send the Msg to our main routine, then
			// kill this for loop
			as.Quit()
			keepalive = false
			msgchan <- msg
		case 199:
			// 199 is "we've been told to quit", so we want to break
			// out of the 'for' here as well
			keepalive = false
			msgchan <- msg
		default:
			// anything else we just log!
			log.Println(msg)
		}
	}
}
<commit_msg>updates for new asock version<commit_after>package main

import (
	"flag"
	"log"

	"firepear.net/asock"
	"github.com/sboyettedh/whiplash"
)

var (
	// whiplash configuration file
	whipconf string
	// storage for current status of all reporting services
	svcs = map[string]*whiplash.SvcCore{}
	// per-service update timestamps
	upds = map[string]map[string]int64{}
	// osd status info
	osdstats = map[string]*whiplash.OsdStat{}
	// host-to-service mapping
	svcmap = map[string][]string{}
	// pre-rolled messages
	success = []byte("received")
)

func init() {
	flag.StringVar(&whipconf, "whipconf", "/etc/whiplash.conf", "Whiplash configuration file")
}

func main() {
	// parse flags
	flag.Parse()
	// read the whiplash configuration
	wl, err := whiplash.New(whipconf, false)
	if err != nil {
		log.Fatalf("error reading configuration file: %v\n", err)
	}
	// and do application initialization
	sigchan := whiplash.AppSetup("whiplash-aggregator", "0.3.0", asock.Version)
	defer whiplash.AppCleanup("whiplash-aggregator")

	// setup the client asock instance. first set the msglvl, then
	// instantiate the asock.
	var msglvl int
	switch wl.Aggregator.MsgLvl {
	case "all":
		msglvl = asock.All
	case "conn":
		msglvl = asock.Conn
	case "error":
		msglvl = asock.Error
	case "fatal":
		msglvl = asock.Fatal
	}
	asconf := &asock.Config{
		Sockname: wl.Aggregator.BindAddr + ":" + wl.Aggregator.BindPort,
		Msglvl: msglvl,
		Timeout: wl.Aggregator.Timeout,
	}
	cas, err := asock.NewTCP(asconf)
	if err != nil {
		log.Fatal(err)
	}
	// and add command handlers to the asock instance
	handlers := map[string]asock.DispatchFunc{
		"ping": pingHandler,
		"stat": statHandler,
	}
	for name, handler := range handlers {
		err = cas.AddHandler(name, "nosplit", handler)
		if err != nil {
			log.Fatal(err)
		}
	}
	log.Println("client asock instantiated")

	// now setup the query asock instance
	asconf = &asock.Config{
		Sockname: wl.Aggregator.BindAddr + ":" + wl.Aggregator.QueryPort,
		Msglvl: msglvl,
		Timeout: wl.Aggregator.QTimeout,
	}
	qas, err := asock.NewTCP(asconf)
	if err != nil {
		log.Fatal(err)
	}
	// add command handlers to the query asock instance
	handlers = map[string]asock.DispatchFunc{
		"echo": qhEcho,
	}
	for name, handler := range handlers {
		err = qas.AddHandler(name, "split", handler)
		if err != nil {
			log.Fatal(err)
		}
	}
	log.Println("query asock instantiated")

	// create a channel for the client asock Msgr handler
	msgchan := make(chan error, 1)
	// and one for the query Msgr handler
	querychan := make(chan error, 1)
	// and launch them
	go msgHandler(cas, msgchan)
	go msgHandler(qas, querychan)
	log.Println("aggregator now listening")

	// this is the mainloop of the application.
	keepalive := true
	for keepalive {
		select {
		case msg := <-msgchan:
			// we've been handed a Msg over msgchan, which means that
			// our Asock has shut itself down for some reason. if this
			// were a more robust server, we would modularize Asock
			// creation and this eventloop, so that should we trap a
			// 599 we could spawn a new Asock and launch it in this
			// one's place. but we're just gonna exit this loop,
			// causing main() to terminate, and with it the server
			// instance.
			log.Println("Asock instance has shut down. Last Msg received was:")
			log.Println(msg)
			keepalive = false
			break
		case msg := <-querychan:
			// the query handler has died. it should be safe to
			// restart.
			log.Println("Query asock instance has shut down. Last Msg received was:")
			log.Println(msg)
			log.Println("Restarting query asock...")
			// TODO what it says ^^there
		case <- sigchan:
			// we've trapped a signal from the OS. tell our Asock to
			// shut down, but don't exit the eventloop because we want
			// to handle the Msgs which will be incoming.
			log.Println("OS signal received; shutting down")
			cas.Quit()
		}
		// there's no default case in the select, as that would cause
		// it to be nonblocking. and that would cause main() to exit
		// immediately.
	}
}

func msgHandler(as *asock.Asock, msgchan chan error) {
	var msg *asock.Msg
	keepalive := true

	for keepalive {
		// wait on a Msg to arrive and do a switch based on status code
		msg = <-as.Msgr
		switch msg.Code {
		case 599:
			// 599 is "the Asock listener has died". this means we're
			// not accepting connections anymore. call as.Quit() to
			// clean things up, send the Msg to our main routine, then
			// kill this for loop
			as.Quit()
			keepalive = false
			msgchan <- msg
		case 199:
			// 199 is "we've been told to quit", so we want to break
			// out of the 'for' here as well
			keepalive = false
			msgchan <- msg
		default:
			// anything else we just log!
			log.Println(msg)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"math"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/gocql/gocql"
	"github.com/kisielk/whisper-go/whisper"
	"github.com/raintank/dur"
	"github.com/raintank/metrictank/api"
	"github.com/raintank/metrictank/cluster"
	"github.com/raintank/metrictank/cluster/partitioner"
	"github.com/raintank/metrictank/idx"
	"github.com/raintank/metrictank/idx/cassandra"
	"github.com/raintank/metrictank/mdata"
	"github.com/raintank/metrictank/mdata/chunk"
	"github.com/raintank/metrictank/mdata/chunk/archive"
)

var (
	exitOnError = flag.Bool(
		"exit-on-error",
		true,
		"Exit with a message when there's an error",
	)
	verbose = flag.Bool(
		"verbose",
		false,
		"Write logs to terminal",
	)
	fakeAvgAggregates = flag.Bool(
		"fake-avg-aggregates",
		true,
		"Generate sum/cnt series out of avg series to accomodate metrictank",
	)
	httpEndpoint = flag.String(
		"http-endpoint",
		"127.0.0.1:8080",
		"The http endpoint to listen on",
	)
	cassandraAddrs = flag.String(
		"cassandra-addrs",
		"localhost",
		"cassandra host (may be given multiple times as comma-separated list)",
	)
	cassandraKeyspace = flag.String(
		"cassandra-keyspace",
		"metrictank",
		"cassandra keyspace to use for storing the metric data table",
	)
	ttlsStr = flag.String(
		"ttls",
		"35d",
		"list of ttl strings used by MT separated by ','",
	)
	windowFactor = flag.Int(
		"window-factor",
		20,
		"the window factor be used when creating the metric table schema",
	)
	partitionScheme = flag.String(
		"partition-scheme",
		"bySeries",
		"method used for partitioning metrics. This should match the settings of tsdb-gw. (byOrg|bySeries)",
	)
	uriPath = flag.String(
		"uri-path",
		"/chunks",
		"the URI on which we expect chunks to get posted",
	)
	numPartitions = flag.Int(
		"num-partitions",
		1,
		"Number of Partitions",
	)
	GitHash   = "(none)"
	printLock sync.Mutex
)

type Server struct {
	Cluster     *gocql.ClusterConfig
	Session     *gocql.Session
	TTLTables   mdata.TTLTables
	Partitioner partitioner.Partitioner
	Index       idx.MetricIndex
}

func main() {
	cassandra.ConfigSetup()
	flag.Parse()

	cassCluster := gocql.NewCluster(strings.Split(*cassandraAddrs, ",")...)
	cassCluster.Consistency = gocql.ParseConsistency("one")
	cassCluster.Timeout = time.Second
	cassCluster.NumConns = 2
	cassCluster.ProtoVersion = 4
	cassCluster.Keyspace = *cassandraKeyspace

	session, err := cassCluster.CreateSession()
	if err != nil {
		panic(fmt.Sprintf("Failed to create cassandra session: %q", err))
	}

	splits := strings.Split(*ttlsStr, ",")
	ttls := make([]uint32, 0)
	for _, split := range splits {
		ttls = append(ttls, dur.MustParseUNsec("ttl", split))
	}
	ttlTables := mdata.GetTTLTables(ttls, *windowFactor, mdata.Table_name_format)

	p, err := partitioner.NewKafka(*partitionScheme)
	if err != nil {
		panic(fmt.Sprintf("Failed to instantiate partitioner: %q", err))
	}

	server := &Server{
		Cluster:     cassCluster,
		Session:     session,
		TTLTables:   ttlTables,
		Partitioner: p,
		Index:       cassandra.New(),
	}
	cluster.Init("mt-whisper-importer-writer", GitHash, time.Now(), "http", int(80))
	server.Index.Init()

	http.HandleFunc(*uriPath, server.chunksHandler)

	log(fmt.Sprintf("Listening on %q", *httpEndpoint))
	err = http.ListenAndServe(*httpEndpoint, nil)
	if err != nil {
		panic(fmt.Sprintf("Error creating listener: %q", err))
	}
}

func throwError(msg string) {
	msg = fmt.Sprintf("%s\n", msg)
	if *exitOnError {
		panic(msg)
	} else {
		printLock.Lock()
		fmt.Fprintln(os.Stderr, msg)
		printLock.Unlock()
	}
}

func log(msg string) {
	if *verbose {
		printLock.Lock()
		fmt.Println(msg)
		printLock.Unlock()
	}
}

func (s *Server) chunksHandler(w http.ResponseWriter, req *http.Request) {
	metric := &archive.Metric{}
	err := metric.UnmarshalCompressed(req.Body)
	if err != nil {
		throwError(fmt.Sprintf("Error decoding metric stream: %q", err))
		return
	}
	log("Handling new metric")

	if len(metric.Archives) == 0 {
		throwError("Metric has no archives")
		return
	}

	avg := false
	if whisper.AggregationMethod(metric.AggregationMethod) == whisper.AggregationAverage {
		avg = true
	}

	partition, err := s.Partitioner.Partition(&metric.MetricData, int32(*numPartitions))
	err = s.Index.AddOrUpdate(&metric.MetricData, partition)
	if err != nil {
		throwError(fmt.Sprintf("Error updating metric index: %q", err))
		return
	}

	for archiveIdx, a := range metric.Archives {
		archiveTTL := a.SecondsPerPoint * a.Points
		tableTTL, err := s.selectTableByTTL(archiveTTL)
		if err != nil {
			throwError(fmt.Sprintf("Failed to select table for ttl %d in %+v: %q", archiveTTL, s.TTLTables, err))
			return
		}
		entry, ok := s.TTLTables[tableTTL]
		if !ok {
			throwError(fmt.Sprintf("Failed to get selected table %d in %+v", tableTTL, s.TTLTables))
			return
		}
		tableName := entry.Table

		if !avg || archiveIdx == 0 || !*fakeAvgAggregates {
			log(fmt.Sprintf(
				"inserting %d chunks of archive %d with ttl %d into table %s with ttl %d and key %s",
				len(a.Chunks), archiveIdx, archiveTTL, tableName, tableTTL, a.RowKey,
			))
			s.insertChunks(tableName, a.RowKey, tableTTL, a.Chunks)
		} else {
			// averaged archives are a special case because mt doesn't store them as such.
			// mt reconstructs the averages on the fly from the sum and cnt series, so we need
			// to generate these two series out of raw averaged data by multiplying each point
			// with the aggregation span and storing the result as sum, cnt is the aggregation span.
			type sumCntChunks struct {
				sum *chunk.Chunk
				cnt *chunk.Chunk
			}

			// seconds per point is assumed to be the aggregation span
			aggSpan := a.SecondsPerPoint

			sumArchive := make([]chunk.IterGen, 0, len(a.Chunks))
			cntArchive := make([]chunk.IterGen, 0, len(a.Chunks))
			for _, ig := range a.Chunks {
				T0 := ig.Ts
				sum := chunk.New(T0)
				cnt := chunk.New(T0)

				it, err := ig.Get()
				if err != nil {
					throwError(fmt.Sprintf("failed to get iterator from itergen: %q", err))
					continue
				}

				for it.Next() {
					ts, val := it.Values()
					cnt.Push(ts, float64(aggSpan))
					sum.Push(ts, val*float64(aggSpan))
				}

				cnt.Finish()
				sum.Finish()

				cntArchive = append(cntArchive, *chunk.NewBareIterGen(cnt.Bytes(), T0, aggSpan))
				sumArchive = append(sumArchive, *chunk.NewBareIterGen(sum.Bytes(), T0, aggSpan))
			}

			cntId := api.AggMetricKey(metric.MetricData.Id, "cnt", aggSpan)
			sumId := api.AggMetricKey(metric.MetricData.Id, "sum", aggSpan)

			log(fmt.Sprintf(
				"inserting 2 archives of %d chunks per archive with ttl %d into table %s with ttl %d and keys %s/%s",
				len(a.Chunks), archiveTTL, tableName, tableTTL, cntId, sumId,
			))

			s.insertChunks(tableName, cntId, tableTTL, cntArchive)
			s.insertChunks(tableName, sumId, tableTTL, sumArchive)
		}
	}
}

func (s *Server) insertChunks(table, id string, ttl uint32, itergens []chunk.IterGen) {
	query := fmt.Sprintf("INSERT INTO %s (key, ts, data) values (?,?,?) USING TTL %d", table, ttl)
	for _, ig := range itergens {
		rowKey := fmt.Sprintf("%s_%d", id, ig.Ts/mdata.Month_sec)
		err := s.Session.Query(query, rowKey, ig.Ts, mdata.PrepareChunkData(ig.Span, ig.Bytes())).Exec()
		if err != nil {
			throwError(fmt.Sprintf("Error in query: %q", err))
		}
	}
}

func (s *Server) selectTableByTTL(ttl uint32) (uint32, error) {
	selectedTTL := uint32(math.MaxUint32)

	// find the table with the smallest TTL that is at least equal to archiveTTL
	for tableTTL := range s.TTLTables {
		if tableTTL >= ttl {
			if selectedTTL > tableTTL {
				selectedTTL = tableTTL
			}
		}
	}

	// we have not found a table that can accommodate the requested ttl
	if selectedTTL == math.MaxUint32 {
		return 0, errors.New(fmt.Sprintf("No Table found that can hold TTL %d", ttl))
	}

	return selectedTTL, nil
}
<commit_msg>remove unused datatype<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"math"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/gocql/gocql"
	"github.com/kisielk/whisper-go/whisper"
	"github.com/raintank/dur"
	"github.com/raintank/metrictank/api"
	"github.com/raintank/metrictank/cluster"
	"github.com/raintank/metrictank/cluster/partitioner"
	"github.com/raintank/metrictank/idx"
	"github.com/raintank/metrictank/idx/cassandra"
	"github.com/raintank/metrictank/mdata"
	"github.com/raintank/metrictank/mdata/chunk"
	"github.com/raintank/metrictank/mdata/chunk/archive"
)

var (
	exitOnError = flag.Bool(
		"exit-on-error",
		true,
		"Exit with a message when there's an error",
	)
	verbose = flag.Bool(
		"verbose",
		false,
		"Write logs to terminal",
	)
	fakeAvgAggregates = flag.Bool(
		"fake-avg-aggregates",
		true,
		"Generate sum/cnt series out of avg series to accomodate metrictank",
	)
	httpEndpoint = flag.String(
		"http-endpoint",
		"127.0.0.1:8080",
		"The http endpoint to listen on",
	)
	cassandraAddrs = flag.String(
		"cassandra-addrs",
		"localhost",
		"cassandra host (may be given multiple times as comma-separated list)",
	)
	cassandraKeyspace = flag.String(
		"cassandra-keyspace",
		"metrictank",
		"cassandra keyspace to use for storing the metric data table",
	)
	ttlsStr = flag.String(
		"ttls",
		"35d",
		"list of ttl strings used by MT separated by ','",
	)
	windowFactor = flag.Int(
		"window-factor",
		20,
		"the window factor be used when creating the metric table schema",
	)
	partitionScheme = flag.String(
		"partition-scheme",
		"bySeries",
		"method used for partitioning metrics. This should match the settings of tsdb-gw. (byOrg|bySeries)",
	)
	uriPath = flag.String(
		"uri-path",
		"/chunks",
		"the URI on which we expect chunks to get posted",
	)
	numPartitions = flag.Int(
		"num-partitions",
		1,
		"Number of Partitions",
	)
	GitHash   = "(none)"
	printLock sync.Mutex
)

type Server struct {
	Cluster     *gocql.ClusterConfig
	Session     *gocql.Session
	TTLTables   mdata.TTLTables
	Partitioner partitioner.Partitioner
	Index       idx.MetricIndex
}

func main() {
	cassandra.ConfigSetup()
	flag.Parse()

	cassCluster := gocql.NewCluster(strings.Split(*cassandraAddrs, ",")...)
	cassCluster.Consistency = gocql.ParseConsistency("one")
	cassCluster.Timeout = time.Second
	cassCluster.NumConns = 2
	cassCluster.ProtoVersion = 4
	cassCluster.Keyspace = *cassandraKeyspace

	session, err := cassCluster.CreateSession()
	if err != nil {
		panic(fmt.Sprintf("Failed to create cassandra session: %q", err))
	}

	splits := strings.Split(*ttlsStr, ",")
	ttls := make([]uint32, 0)
	for _, split := range splits {
		ttls = append(ttls, dur.MustParseUNsec("ttl", split))
	}
	ttlTables := mdata.GetTTLTables(ttls, *windowFactor, mdata.Table_name_format)

	p, err := partitioner.NewKafka(*partitionScheme)
	if err != nil {
		panic(fmt.Sprintf("Failed to instantiate partitioner: %q", err))
	}

	server := &Server{
		Cluster:     cassCluster,
		Session:     session,
		TTLTables:   ttlTables,
		Partitioner: p,
		Index:       cassandra.New(),
	}
	cluster.Init("mt-whisper-importer-writer", GitHash, time.Now(), "http", int(80))
	server.Index.Init()

	http.HandleFunc(*uriPath, server.chunksHandler)

	log(fmt.Sprintf("Listening on %q", *httpEndpoint))
	err = http.ListenAndServe(*httpEndpoint, nil)
	if err != nil {
		panic(fmt.Sprintf("Error creating listener: %q", err))
	}
}

func throwError(msg string) {
	msg = fmt.Sprintf("%s\n", msg)
	if *exitOnError {
		panic(msg)
	} else {
		printLock.Lock()
		fmt.Fprintln(os.Stderr, msg)
		printLock.Unlock()
	}
}

func log(msg string) {
	if *verbose {
		printLock.Lock()
		fmt.Println(msg)
		printLock.Unlock()
	}
}

func (s *Server) chunksHandler(w http.ResponseWriter, req *http.Request) {
	metric := &archive.Metric{}
	err := metric.UnmarshalCompressed(req.Body)
	if err != nil {
		throwError(fmt.Sprintf("Error decoding metric stream: %q", err))
		return
	}
	log("Handling new metric")

	if len(metric.Archives) == 0 {
		throwError("Metric has no archives")
		return
	}

	avg := false
	if whisper.AggregationMethod(metric.AggregationMethod) == whisper.AggregationAverage {
		avg = true
	}

	partition, err := s.Partitioner.Partition(&metric.MetricData, int32(*numPartitions))
	err = s.Index.AddOrUpdate(&metric.MetricData, partition)
	if err != nil {
		throwError(fmt.Sprintf("Error updating metric index: %q", err))
		return
	}

	for archiveIdx, a := range metric.Archives {
		archiveTTL := a.SecondsPerPoint * a.Points
		tableTTL, err := s.selectTableByTTL(archiveTTL)
		if err != nil {
			throwError(fmt.Sprintf("Failed to select table for ttl %d in %+v: %q", archiveTTL, s.TTLTables, err))
			return
		}
		entry, ok := s.TTLTables[tableTTL]
		if !ok {
			throwError(fmt.Sprintf("Failed to get selected table %d in %+v", tableTTL, s.TTLTables))
			return
		}
		tableName := entry.Table

		if !avg || archiveIdx == 0 || !*fakeAvgAggregates {
			log(fmt.Sprintf(
				"inserting %d chunks of archive %d with ttl %d into table %s with ttl %d and key %s",
				len(a.Chunks), archiveIdx, archiveTTL, tableName, tableTTL, a.RowKey,
			))
			s.insertChunks(tableName, a.RowKey, tableTTL, a.Chunks)
		} else {
			// averaged archives are a special case because mt doesn't store them as such.
			// mt reconstructs the averages on the fly from the sum and cnt series, so we need
			// to generate these two series out of raw averaged data by multiplying each point
			// with the aggregation span and storing the result as sum, cnt is the aggregation span.

			// seconds per point is assumed to be the aggregation span
			aggSpan := a.SecondsPerPoint

			sumArchive := make([]chunk.IterGen, 0, len(a.Chunks))
			cntArchive := make([]chunk.IterGen, 0, len(a.Chunks))
			for _, ig := range a.Chunks {
				T0 := ig.Ts
				sum := chunk.New(T0)
				cnt := chunk.New(T0)

				it, err := ig.Get()
				if err != nil {
					throwError(fmt.Sprintf("failed to get iterator from itergen: %q", err))
					continue
				}

				for it.Next() {
					ts, val := it.Values()
					cnt.Push(ts, float64(aggSpan))
					sum.Push(ts, val*float64(aggSpan))
				}

				cnt.Finish()
				sum.Finish()

				cntArchive = append(cntArchive, *chunk.NewBareIterGen(cnt.Bytes(), T0, aggSpan))
				sumArchive = append(sumArchive, *chunk.NewBareIterGen(sum.Bytes(), T0, aggSpan))
			}

			cntId := api.AggMetricKey(metric.MetricData.Id, "cnt", aggSpan)
			sumId := api.AggMetricKey(metric.MetricData.Id, "sum", aggSpan)

			log(fmt.Sprintf(
				"inserting 2 archives of %d chunks per archive with ttl %d into table %s with ttl %d and keys %s/%s",
				len(a.Chunks), archiveTTL, tableName, tableTTL, cntId, sumId,
			))

			s.insertChunks(tableName, cntId, tableTTL, cntArchive)
			s.insertChunks(tableName, sumId, tableTTL, sumArchive)
		}
	}
}

func (s *Server) insertChunks(table, id string, ttl uint32, itergens []chunk.IterGen) {
	query := fmt.Sprintf("INSERT INTO %s (key, ts, data) values (?,?,?) USING TTL %d", table, ttl)
	for _, ig := range itergens {
		rowKey := fmt.Sprintf("%s_%d", id, ig.Ts/mdata.Month_sec)
		err := s.Session.Query(query, rowKey, ig.Ts, mdata.PrepareChunkData(ig.Span, ig.Bytes())).Exec()
		if err != nil {
			throwError(fmt.Sprintf("Error in query: %q", err))
		}
	}
}

func (s *Server) selectTableByTTL(ttl uint32) (uint32, error) {
	selectedTTL := uint32(math.MaxUint32)

	// find the table with the smallest TTL that is at least equal to archiveTTL
	for tableTTL := range s.TTLTables {
		if tableTTL >= ttl {
			if selectedTTL > tableTTL {
				selectedTTL = tableTTL
			}
		}
	}

	// we have not found a table that can accommodate the requested ttl
	if selectedTTL == math.MaxUint32 {
		return 0, errors.New(fmt.Sprintf("No Table found that can hold TTL %d", ttl))
	}

	return selectedTTL, nil
}
<|endoftext|>


<commit_before>// Package intqueue implements a queue for ints.
//
// The internal representation is a slice of ints
// that gets used as a circular buffer.
// This is instead of a more traditional approach
// that would use a linked list of nodes.
// The assumption is that contiguous slabs of RAM
// will generally provide more performance over pointers
// to nodes around the heap.
//
// There is a downside: whereas enqueueing to a
// linked list is always O(1), enqueueing here will
// be O(1) except for when the internal slice of ints
// has to be resized; then, enqueueing will be O(n)
// where n is the size of the queue before being resized.
//
// Therefore, when asking for a new instance of the
// queue, pick a capacity that you think won't need to grow.
package intqueue

import "github.com/pkg/errors"

// IntQueue holds the data and state of the queue.
type IntQueue struct {
	data     []int
	head     int
	tail     int
	capacity int
	length   int
}

// Creates a new empty queue for ints and returns a pointer to it.
func New() (q *IntQueue) {
	q = new(IntQueue)
	q.data = make([]int, 32, 32)
	q.head = -1
	q.tail = -1
	q.capacity = 32
	q.length = 0
	return q
}

// Enqueues an int. Returns an error if the size
// of the queue cannot be grown any more to accommodate
// the added int.
func (q *IntQueue) Enqueue(i int) error {
	if q.length+1 > q.capacity {
		new_capacity := q.capacity << 1
		// if new_cap became negative, we have exceeded
		// our capacity by doing one bit-shift too far
		if new_capacity < 0 {
			return errors.New("Capacity exceeded")
		}
		q.resize(new_capacity)
	}
	q.length++
	q.head++
	if q.head == q.capacity {
		q.head = 0
	}
	q.data[q.head] = i
	return nil
}

// Head can be earlier in array than tail, so
// we can't just copy; we could overwrite the tail.
// Instead, we may as well copy the queue in order
// into the new array. The Dequeue() method gives us
// every element in the correct order already, so we
// just leverage that.
func (q *IntQueue) resize(new_capacity int) {
	new_data := make([]int, new_capacity, new_capacity)
	var err error
	var i int
	for err = nil; err == nil; i, err = q.Dequeue() {
		new_data = append(new_data, i)
	}
	q.head = q.length - 1
	q.tail = 0
	q.capacity = new_capacity
	q.data = new_data
}

// Enqueues an int. Returns an error of the queue is empty.
func (q *IntQueue) Dequeue() (int, error) {
	if q.length-1 < 0 {
		return 0, errors.New("Queue empty")
	}
	q.length--
	q.tail++
	if q.tail == q.capacity {
		q.tail = 0
	}
	return q.data[q.tail], nil
}
<commit_msg>Adds size-picking constructor.<commit_after>// Package intqueue implements a queue for ints.
//
// The internal representation is a slice of ints
// that gets used as a circular buffer.
// This is instead of a more traditional approach
// that would use a linked list of nodes.
// The assumption is that contiguous slabs of RAM
// will generally provide more performance over pointers
// to nodes around the heap.
//
// There is a downside: whereas enqueueing to a
// linked list is always O(1), enqueueing here will
// be O(1) except for when the internal slice of ints
// has to be resized; then, enqueueing will be O(n)
// where n is the size of the queue before being resized.
//
// Therefore, when asking for a new instance of the
// queue, pick a capacity that you think won't need to grow.
//
// When the queue does need to grow, it always uses a capacity
// that is twice the current capacity. This is not tunable.
package intqueue

import "github.com/pkg/errors"

// IntQueue holds the data and state of the queue.
type IntQueue struct {
	data     []int
	head     int
	tail     int
	capacity int
	length   int
}

// DefauiltCapacity is the default capacity of the IntQueue
// when constructed using New() instead of NewWithCapacity().
const DefaultCapacity = 32

// New returns a new empty queue for ints of the default capacity.
func New() (q *IntQueue) {
	return NewWithCapacity(DefaultCapacity)
}

// NewWithCapacity returns a new empty queue for ints with the requested capacity.
func NewWithCapacity(capacity int) (q *IntQueue) {
	q = new(IntQueue)
	q.data = make([]int, capacity, capacity)
	q.head = -1
	q.tail = -1
	q.capacity = capacity
	q.length = 0
	return q
}

// Enqueue enqueues an int. Returns an error if the size
// of the queue cannot be grown any more to accommodate
// the added int.
func (q *IntQueue) Enqueue(i int) error {
	if q.length+1 > q.capacity {
		new_capacity := q.capacity << 1
		// if new_cap became negative, we have exceeded
		// our capacity by doing one bit-shift too far
		if new_capacity < 0 {
			return errors.New("Capacity exceeded")
		}
		q.resize(new_capacity)
	}
	q.length++
	q.head++
	if q.head == q.capacity {
		q.head = 0
	}
	q.data[q.head] = i
	return nil
}

// Head can be earlier in array than tail, so
// we can't just copy; we could overwrite the tail.
// Instead, we may as well copy the queue in order
// into the new array. The Dequeue() method gives us
// every element in the correct order already, so we
// just leverage that.
func (q *IntQueue) resize(new_capacity int) {
	new_data := make([]int, new_capacity, new_capacity)
	var err error
	var i int
	for err = nil; err == nil; i, err = q.Dequeue() {
		new_data = append(new_data, i)
	}
	q.head = q.length - 1
	q.tail = 0
	q.capacity = new_capacity
	q.data = new_data
}

// Dequeue dequeues an int. It returns the dequeued int
// or an error of the queue is empty.
func (q *IntQueue) Dequeue() (int, error) {
	if q.length-1 < 0 {
		return 0, errors.New("Queue empty")
	}
	q.length--
	q.tail++
	if q.tail == q.capacity {
		q.tail = 0
	}
	return q.data[q.tail], nil
}
<|endoftext|>


<commit_before>// Copyright 2010 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.

// This file contains the printf-checker.

package main

import (
	"flag"
	"fmt"
	"go/ast"
	"go/token"
	"strings"
	"unicode/utf8"
)

var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check")

// printfList records the formatted-print functions. The value is the location
// of the format parameter. Names are lower-cased so the lookup is
// case insensitive.
var printfList = map[string]int{
	"errorf":  0,
	"fatalf":  0,
	"fprintf": 1,
	"panicf":  0,
	"printf":  0,
	"sprintf": 0,
}

// printList records the unformatted-print functions. The value is the location
// of the first parameter to be printed.  Names are lower-cased so the lookup is
// case insensitive.
var printList = map[string]int{
	"error":  0,
	"fatal":  0,
	"fprint": 1, "fprintln": 1,
	"panic": 0, "panicln": 0,
	"print": 0, "println": 0,
	"sprint": 0, "sprintln": 0,
}

// checkCall triggers the print-specific checks if the call invokes a print function.
func (f *File) checkFmtPrintfCall(call *ast.CallExpr, Name string) {
	name := strings.ToLower(Name)
	if skip, ok := printfList[name]; ok {
		f.checkPrintf(call, Name, skip)
		return
	}
	if skip, ok := printList[name]; ok {
		f.checkPrint(call, Name, skip)
		return
	}
}

// checkPrintf checks a call to a formatted print routine such as Printf.
// The skip argument records how many arguments to ignore; that is,
// call.Args[skip] is (well, should be) the format argument.
func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) {
	if len(call.Args) <= skip {
		return
	}
	// Common case: literal is first argument.
	arg := call.Args[skip]
	lit, ok := arg.(*ast.BasicLit)
	if !ok {
		// Too hard to check.
		if *verbose {
			f.Warn(call.Pos(), "can't check non-literal format in call to", name)
		}
		return
	}
	if lit.Kind == token.STRING {
		if !strings.Contains(lit.Value, "%") {
			if len(call.Args) > skip+1 {
				f.Badf(call.Pos(), "no formatting directive in %s call", name)
			}
			return
		}
	}
	// Hard part: check formats against args.
	// Trivial but useful test: count.
	numArgs := 0
	for i, w := 0, 0; i < len(lit.Value); i += w {
		w = 1
		if lit.Value[i] == '%' {
			nbytes, nargs := f.parsePrintfVerb(call, lit.Value[i:])
			w = nbytes
			numArgs += nargs
		}
	}
	expect := len(call.Args) - (skip + 1)
	if numArgs != expect {
		f.Badf(call.Pos(), "wrong number of args in %s call: %d needed but %d args", name, numArgs, expect)
	}
}

// parsePrintfVerb returns the number of bytes and number of arguments
// consumed by the Printf directive that begins s, including its percent sign
// and verb.
func (f *File) parsePrintfVerb(call *ast.CallExpr, s string) (nbytes, nargs int) {
	// There's guaranteed a percent sign.
	flags := make([]byte, 0, 5)
	nbytes = 1
	end := len(s)
	// There may be flags.
FlagLoop:
	for nbytes < end {
		switch s[nbytes] {
		case '#', '0', '+', '-', ' ':
			flags = append(flags, s[nbytes])
			nbytes++
		default:
			break FlagLoop
		}
	}
	getNum := func() {
		if nbytes < end && s[nbytes] == '*' {
			nbytes++
			nargs++
		} else {
			for nbytes < end && '0' <= s[nbytes] && s[nbytes] <= '9' {
				nbytes++
			}
		}
	}
	// There may be a width.
	getNum()
	// If there's a period, there may be a precision.
	if nbytes < end && s[nbytes] == '.' {
		flags = append(flags, '.') // Treat precision as a flag.
		nbytes++
		getNum()
	}
	// Now a verb.
	c, w := utf8.DecodeRuneInString(s[nbytes:])
	nbytes += w
	if c != '%' {
		nargs++
		f.checkPrintfVerb(call, c, flags)
	}
	return
}

type printVerb struct {
	verb  rune
	flags string // known flags are all ASCII
}

// Common flag sets for printf verbs.
const (
	numFlag      = " -+.0"
	sharpNumFlag = " -+.0#"
	allFlags     = " -+.0#"
)

// printVerbs identifies which flags are known to printf for each verb.
// TODO: A type that implements Formatter may do what it wants, and vet
// will complain incorrectly.
var printVerbs = []printVerb{
	// '-' is a width modifier, always valid.
	// '.' is a precision for float, max width for strings.
	// '+' is required sign for numbers, Go format for %v.
	// '#' is alternate format for several verbs.
	// ' ' is spacer for numbers
	{'b', numFlag},
	{'c', "-"},
	{'d', numFlag},
	{'e', numFlag},
	{'E', numFlag},
	{'f', numFlag},
	{'F', numFlag},
	{'g', numFlag},
	{'G', numFlag},
	{'o', sharpNumFlag},
	{'p', "-#"},
	{'q', "-+#."},
	{'s', "-."},
	{'t', "-"},
	{'T', "-"},
	{'U', "-#"},
	{'v', allFlags},
	{'x', sharpNumFlag},
	{'X', sharpNumFlag},
}

const printfVerbs = "bcdeEfFgGopqstTvxUX"

func (f *File) checkPrintfVerb(call *ast.CallExpr, verb rune, flags []byte) {
	// Linear scan is fast enough for a small list.
	for _, v := range printVerbs {
		if v.verb == verb {
			for _, flag := range flags {
				if !strings.ContainsRune(v.flags, rune(flag)) {
					f.Badf(call.Pos(), "unrecognized printf flag for verb %q: %q", verb, flag)
				}
			}
			return
		}
	}
	f.Badf(call.Pos(), "unrecognized printf verb %q", verb)
}

// checkPrint checks a call to an unformatted print routine such as Println.
// The skip argument records how many arguments to ignore; that is,
// call.Args[skip] is the first argument to be printed.
func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) {
	isLn := strings.HasSuffix(name, "ln")
	isF := strings.HasPrefix(name, "F")
	args := call.Args
	// check for Println(os.Stderr, ...)
	if skip == 0 && !isF && len(args) > 0 {
		if sel, ok := args[0].(*ast.SelectorExpr); ok {
			if x, ok := sel.X.(*ast.Ident); ok {
				if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
					f.Warnf(call.Pos(), "first argument to %s is %s.%s", name, x.Name, sel.Sel.Name)
				}
			}
		}
	}
	if len(args) <= skip {
		if *verbose && !isLn {
			f.Badf(call.Pos(), "no args in %s call", name)
		}
		return
	}
	arg := args[skip]
	if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
		if strings.Contains(lit.Value, "%") {
			f.Badf(call.Pos(), "possible formatting directive in %s call", name)
		}
	}
	if isLn {
		// The last item, if a string, should not have a newline.
		arg = args[len(call.Args)-1]
		if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
			if strings.HasSuffix(lit.Value, `\n"`) {
				f.Badf(call.Pos(), "%s call ends with newline", name)
			}
		}
	}
}

// This function never executes, but it serves as a simple test for the program.
// Test with make test.
func BadFunctionUsedInTests() {
	fmt.Println()                      // not an error
	fmt.Println("%s", "hi")            // ERROR "possible formatting directive in Println call"
	fmt.Printf("%s", "hi", 3)          // ERROR "wrong number of args in Printf call"
	fmt.Printf("%s%%%d", "hi", 3)      // correct
	fmt.Printf("%.*d", 3, 3)           // correct
	fmt.Printf("%.*d", 3, 3, 3)        // ERROR "wrong number of args in Printf call"
	printf("now is the time", "buddy") // ERROR "no formatting directive"
	Printf("now is the time", "buddy") // ERROR "no formatting directive"
	Printf("hi")                       // ok
	f := new(File)
	f.Warn(0, "%s", "hello", 3)  // ERROR "possible formatting directive in Warn call"
	f.Warnf(0, "%s", "hello", 3) // ERROR "wrong number of args in Warnf call"
	f.Warnf(0, "%r", "hello")    // ERROR "unrecognized printf verb"
	f.Warnf(0, "%#s", "hello")   // ERROR "unrecognized printf flag"
}

type BadTypeUsedInTests struct {
	X int "hello" // ERROR "struct field tag"
}

func (t *BadTypeUsedInTests) Scan(x fmt.ScanState, c byte) { // ERROR "method Scan[(]x fmt.ScanState, c byte[)] should have signature Scan[(]fmt.ScanState, rune[)] error"
}

type BadInterfaceUsedInTests interface {
	ReadByte() byte // ERROR "method ReadByte[(][)] byte should have signature ReadByte[(][)] [(]byte, error[)]"
}

// printf is used by the test.
func printf(format string, args ...interface{}) {
	panic("don't call - testing only")
}
<commit_msg>vet: check values for named constants as well as literals. As in:         const format = "%s"         fmt.Printf(format, "hi") Also fix a couple of bugs by rewriting the routine.<commit_after>// Copyright 2010 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.

// This file contains the printf-checker.

package main

import (
	"flag"
	"fmt"
	"go/ast"
	"go/token"
	"strings"
	"unicode/utf8"
)

var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check")

// printfList records the formatted-print functions. The value is the location
// of the format parameter. Names are lower-cased so the lookup is
// case insensitive.
var printfList = map[string]int{
	"errorf":  0,
	"fatalf":  0,
	"fprintf": 1,
	"panicf":  0,
	"printf":  0,
	"sprintf": 0,
}

// printList records the unformatted-print functions. The value is the location
// of the first parameter to be printed.  Names are lower-cased so the lookup is
// case insensitive.
var printList = map[string]int{
	"error":  0,
	"fatal":  0,
	"fprint": 1, "fprintln": 1,
	"panic": 0, "panicln": 0,
	"print": 0, "println": 0,
	"sprint": 0, "sprintln": 0,
}

// checkCall triggers the print-specific checks if the call invokes a print function.
func (f *File) checkFmtPrintfCall(call *ast.CallExpr, Name string) {
	name := strings.ToLower(Name)
	if skip, ok := printfList[name]; ok {
		f.checkPrintf(call, Name, skip)
		return
	}
	if skip, ok := printList[name]; ok {
		f.checkPrint(call, Name, skip)
		return
	}
}

// literal returns the literal value represented by the expression, or nil if it is not a literal.
func (f *File) literal(value ast.Expr) *ast.BasicLit {
	switch v := value.(type) {
	case *ast.BasicLit:
		return v
	case *ast.Ident:
		// See if it's a constant or initial value (we can't tell the difference).
		if v.Obj == nil || v.Obj.Decl == nil {
			return nil
		}
		valueSpec, ok := v.Obj.Decl.(*ast.ValueSpec)
		if ok && len(valueSpec.Names) == len(valueSpec.Values) {
			// Find the index in the list of names
			var i int
			for i = 0; i < len(valueSpec.Names); i++ {
				if valueSpec.Names[i].Name == v.Name {
					if lit, ok := valueSpec.Values[i].(*ast.BasicLit); ok {
						return lit
					}
					return nil
				}
			}
		}
	}
	return nil
}

// checkPrintf checks a call to a formatted print routine such as Printf.
// The skip argument records how many arguments to ignore; that is,
// call.Args[skip] is (well, should be) the format argument.
func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) {
	if len(call.Args) <= skip {
		return
	}
	lit := f.literal(call.Args[skip])
	if lit == nil {
		if *verbose {
			f.Warn(call.Pos(), "can't check non-literal format in call to", name)
		}
		return
	}
	if lit.Kind != token.STRING {
		f.Badf(call.Pos(), "literal %v not a string in call to", lit.Value, name)
	}
	format := lit.Value
	if !strings.Contains(format, "%") {
		if len(call.Args) > skip+1 {
			f.Badf(call.Pos(), "no formatting directive in %s call", name)
		}
		return
	}
	// Hard part: check formats against args.
	// Trivial but useful test: count.
	numArgs := 0
	for i, w := 0, 0; i < len(format); i += w {
		w = 1
		if format[i] == '%' {
			nbytes, nargs := f.parsePrintfVerb(call, format[i:])
			w = nbytes
			numArgs += nargs
		}
	}
	expect := len(call.Args) - (skip + 1)
	if numArgs != expect {
		f.Badf(call.Pos(), "wrong number of args in %s call: %d needed but %d args", name, numArgs, expect)
	}
}

// parsePrintfVerb returns the number of bytes and number of arguments
// consumed by the Printf directive that begins s, including its percent sign
// and verb.
func (f *File) parsePrintfVerb(call *ast.CallExpr, s string) (nbytes, nargs int) {
	// There's guaranteed a percent sign.
	flags := make([]byte, 0, 5)
	nbytes = 1
	end := len(s)
	// There may be flags.
FlagLoop:
	for nbytes < end {
		switch s[nbytes] {
		case '#', '0', '+', '-', ' ':
			flags = append(flags, s[nbytes])
			nbytes++
		default:
			break FlagLoop
		}
	}
	getNum := func() {
		if nbytes < end && s[nbytes] == '*' {
			nbytes++
			nargs++
		} else {
			for nbytes < end && '0' <= s[nbytes] && s[nbytes] <= '9' {
				nbytes++
			}
		}
	}
	// There may be a width.
	getNum()
	// If there's a period, there may be a precision.
	if nbytes < end && s[nbytes] == '.' {
		flags = append(flags, '.') // Treat precision as a flag.
		nbytes++
		getNum()
	}
	// Now a verb.
	c, w := utf8.DecodeRuneInString(s[nbytes:])
	nbytes += w
	if c != '%' {
		nargs++
		f.checkPrintfVerb(call, c, flags)
	}
	return
}

type printVerb struct {
	verb  rune
	flags string // known flags are all ASCII
}

// Common flag sets for printf verbs.
const (
	numFlag      = " -+.0"
	sharpNumFlag = " -+.0#"
	allFlags     = " -+.0#"
)

// printVerbs identifies which flags are known to printf for each verb.
// TODO: A type that implements Formatter may do what it wants, and vet
// will complain incorrectly.
var printVerbs = []printVerb{
	// '-' is a width modifier, always valid.
	// '.' is a precision for float, max width for strings.
	// '+' is required sign for numbers, Go format for %v.
	// '#' is alternate format for several verbs.
	// ' ' is spacer for numbers
	{'b', numFlag},
	{'c', "-"},
	{'d', numFlag},
	{'e', numFlag},
	{'E', numFlag},
	{'f', numFlag},
	{'F', numFlag},
	{'g', numFlag},
	{'G', numFlag},
	{'o', sharpNumFlag},
	{'p', "-#"},
	{'q', "-+#."},
	{'s', "-."},
	{'t', "-"},
	{'T', "-"},
	{'U', "-#"},
	{'v', allFlags},
	{'x', sharpNumFlag},
	{'X', sharpNumFlag},
}

const printfVerbs = "bcdeEfFgGopqstTvxUX"

func (f *File) checkPrintfVerb(call *ast.CallExpr, verb rune, flags []byte) {
	// Linear scan is fast enough for a small list.
	for _, v := range printVerbs {
		if v.verb == verb {
			for _, flag := range flags {
				if !strings.ContainsRune(v.flags, rune(flag)) {
					f.Badf(call.Pos(), "unrecognized printf flag for verb %q: %q", verb, flag)
				}
			}
			return
		}
	}
	f.Badf(call.Pos(), "unrecognized printf verb %q", verb)
}

// checkPrint checks a call to an unformatted print routine such as Println.
// The skip argument records how many arguments to ignore; that is,
// call.Args[skip] is the first argument to be printed.
func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) {
	isLn := strings.HasSuffix(name, "ln")
	isF := strings.HasPrefix(name, "F")
	args := call.Args
	// check for Println(os.Stderr, ...)
	if skip == 0 && !isF && len(args) > 0 {
		if sel, ok := args[0].(*ast.SelectorExpr); ok {
			if x, ok := sel.X.(*ast.Ident); ok {
				if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
					f.Warnf(call.Pos(), "first argument to %s is %s.%s", name, x.Name, sel.Sel.Name)
				}
			}
		}
	}
	if len(args) <= skip {
		if *verbose && !isLn {
			f.Badf(call.Pos(), "no args in %s call", name)
		}
		return
	}
	arg := args[skip]
	if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
		if strings.Contains(lit.Value, "%") {
			f.Badf(call.Pos(), "possible formatting directive in %s call", name)
		}
	}
	if isLn {
		// The last item, if a string, should not have a newline.
		arg = args[len(call.Args)-1]
		if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
			if strings.HasSuffix(lit.Value, `\n"`) {
				f.Badf(call.Pos(), "%s call ends with newline", name)
			}
		}
	}
}

// This function never executes, but it serves as a simple test for the program.
// Test with make test.
func BadFunctionUsedInTests() {
	fmt.Println()                      // not an error
	fmt.Println("%s", "hi")            // ERROR "possible formatting directive in Println call"
	fmt.Printf("%s", "hi", 3)          // ERROR "wrong number of args in Printf call"
	fmt.Printf("%s%%%d", "hi", 3)      // correct
	fmt.Printf("%.*d", 3, 3)           // correct
	fmt.Printf("%.*d", 3, 3, 3)        // ERROR "wrong number of args in Printf call"
	printf("now is the time", "buddy") // ERROR "no formatting directive"
	Printf("now is the time", "buddy") // ERROR "no formatting directive"
	Printf("hi")                       // ok
	const format = "%s %s\n"
	Printf(format, "hi", "there")
	Printf(format, "hi") // ERROR "wrong number of args in Printf call"
	f := new(File)
	f.Warn(0, "%s", "hello", 3)  // ERROR "possible formatting directive in Warn call"
	f.Warnf(0, "%s", "hello", 3) // ERROR "wrong number of args in Warnf call"
	f.Warnf(0, "%r", "hello")    // ERROR "unrecognized printf verb"
	f.Warnf(0, "%#s", "hello")   // ERROR "unrecognized printf flag"
}

type BadTypeUsedInTests struct {
	X int "hello" // ERROR "struct field tag"
}

func (t *BadTypeUsedInTests) Scan(x fmt.ScanState, c byte) { // ERROR "method Scan[(]x fmt.ScanState, c byte[)] should have signature Scan[(]fmt.ScanState, rune[)] error"
}

type BadInterfaceUsedInTests interface {
	ReadByte() byte // ERROR "method ReadByte[(][)] byte should have signature ReadByte[(][)] [(]byte, error[)]"
}

// printf is used by the test.
func printf(format string, args ...interface{}) {
	panic("don't call - testing only")
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/karver/karver/migrations"
	"os"
)

var Create = cli.Command{
	Name:   "create",
	Usage:  "Create migrations",
	Action: createCmd,
}

func createCmd(c *cli.Context) {
	migrationsPath := c.GlobalString("migrations")

	var title string

	if len(c.Args()) > 0 {
		title = c.Args()[0]
	} else {
		fmt.Println("No migration title provided on create!")
		os.Exit(1)
	}

	migrationsPath, err := migrations.AbsMigrationsPath(migrationsPath)
	if err != nil {
		fmt.Printf("Error determining the migrations path: %s\n", err.Error())
		os.Exit(1)
	}

	m, err := migrations.Create(title, migrationsPath)
	if err != nil {
		fmt.Printf("Error creating the migration file: %s\n", err.Error())
		os.Exit(1)
	}

	fmt.Printf("New migration: %s - %s\n", m.Name, m.Path)
}
<commit_msg>Less verbose<commit_after>package commands

import (
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/karver/karver/migrations"
	"os"
)

var Create = cli.Command{
	Name:   "create",
	Usage:  "Create migrations",
	Action: createCmd,
}

func createCmd(c *cli.Context) {
	migrationsPath := c.GlobalString("migrations")

	var title string

	if len(c.Args()) > 0 {
		title = c.Args()[0]
	} else {
		fmt.Println("No migration title provided on create!")
		os.Exit(1)
	}

	migrationsPath, err := migrations.AbsMigrationsPath(migrationsPath)
	if err != nil {
		fmt.Printf("Error determining the migrations path: %s\n", err.Error())
		os.Exit(1)
	}

	m, err := migrations.Create(title, migrationsPath)
	if err != nil {
		fmt.Printf("Error creating the migration file: %s\n", err.Error())
		os.Exit(1)
	}

	fmt.Printf("New migration: %s\n", m.Path)
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package path

import (
	"fmt"
	"testing"

	"github.com/aristanetworks/goarista/key"
	"github.com/aristanetworks/goarista/value"
)

func TestNewPath(t *testing.T) {
	tcases := []struct {
		in  []interface{}
		out Path
	}{
		{
			in:  nil,
			out: nil,
		}, {
			in:  []interface{}{},
			out: Path{},
		}, {
			in:  []interface{}{""},
			out: Path{key.New("")},
		}, {
			in:  []interface{}{key.New("")},
			out: Path{key.New("")},
		}, {
			in:  []interface{}{"foo"},
			out: Path{key.New("foo")},
		}, {
			in:  []interface{}{key.New("foo")},
			out: Path{key.New("foo")},
		}, {
			in:  []interface{}{"foo", key.New("bar")},
			out: Path{key.New("foo"), key.New("bar")},
		}, {
			in:  []interface{}{key.New("foo"), "bar", key.New("baz")},
			out: Path{key.New("foo"), key.New("bar"), key.New("baz")},
		},
	}
	for i, tcase := range tcases {
		if p := New(tcase.in...); !p.Equal(tcase.out) {
			t.Fatalf("Test %d failed: %#v != %#v", i, p, tcase.out)
		}
	}
}

func TestAppendPath(t *testing.T) {
	tcases := []struct {
		base     Path
		elements []interface{}
		expected Path
	}{
		{
			base:     Path{},
			elements: []interface{}{},
			expected: Path{},
		}, {
			base:     Path{},
			elements: []interface{}{""},
			expected: Path{key.New("")},
		}, {
			base:     Path{},
			elements: []interface{}{key.New("")},
			expected: Path{key.New("")},
		}, {
			base:     Path{},
			elements: []interface{}{"foo", key.New("bar")},
			expected: Path{key.New("foo"), key.New("bar")},
		}, {
			base:     Path{key.New("foo")},
			elements: []interface{}{key.New("bar"), "baz"},
			expected: Path{key.New("foo"), key.New("bar"), key.New("baz")},
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			elements: []interface{}{key.New("baz")},
			expected: Path{key.New("foo"), key.New("bar"), key.New("baz")},
		},
	}
	for i, tcase := range tcases {
		if p := Append(tcase.base, tcase.elements...); !p.Equal(tcase.expected) {
			t.Fatalf("Test %d failed: %#v != %#v", i, p, tcase.expected)
		}
	}
}

type customKey struct {
	i *int
}

func (c customKey) String() string {
	return fmt.Sprintf("customKey=%d", *c.i)
}

func (c customKey) MarshalJSON() ([]byte, error) {
	return nil, nil
}

func (c customKey) ToBuiltin() interface{} {
	return nil
}

func (c customKey) Equal(other interface{}) bool {
	o, ok := other.(customKey)
	return ok && *c.i == *o.i
}

var (
	_ value.Value    = customKey{}
	_ key.Comparable = customKey{}
	a                = 1
	b                = 1
)

func TestPathEquality(t *testing.T) {
	tcases := []struct {
		base     Path
		other    Path
		expected bool
	}{
		{
			base:     Path{},
			other:    Path{},
			expected: true,
		}, {
			base:     Path{},
			other:    Path{key.New("")},
			expected: false,
		}, {
			base:     Path{key.New("foo")},
			other:    Path{key.New("foo")},
			expected: true,
		}, {
			base:     Path{key.New("foo")},
			other:    Path{key.New("bar")},
			expected: false,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			other:    Path{key.New("foo")},
			expected: false,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			other:    Path{key.New("bar"), key.New("foo")},
			expected: false,
		}, {
			base:     Path{key.New("foo"), key.New("bar"), key.New("baz")},
			other:    Path{key.New("foo"), key.New("bar"), key.New("baz")},
			expected: true,
		},
		// Ensure that we check deep equality.
		{
			base:     Path{key.New(map[string]interface{}{})},
			other:    Path{key.New(map[string]interface{}{})},
			expected: true,
		}, {
			base:     Path{key.New(customKey{i: &a})},
			other:    Path{key.New(customKey{i: &b})},
			expected: true,
		},
	}
	for i, tcase := range tcases {
		if result := tcase.base.Equal(tcase.other); result != tcase.expected {
			t.Fatalf("Test %d failed: base: %#v; other: %#v, expected: %t",
				i, tcase.base, tcase.other, tcase.expected)
		}
	}
}

func TestPathHasPrefix(t *testing.T) {
	tcases := []struct {
		base     Path
		prefix   Path
		expected bool
	}{
		{
			base:     Path{},
			prefix:   Path{},
			expected: true,
		}, {
			base:     Path{key.New("foo")},
			prefix:   Path{},
			expected: true,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			prefix:   Path{key.New("foo")},
			expected: true,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			prefix:   Path{key.New("bar")},
			expected: false,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			prefix:   Path{key.New("bar"), key.New("foo")},
			expected: false,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			prefix:   Path{key.New("foo"), key.New("bar")},
			expected: true,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			prefix:   Path{key.New("foo"), key.New("bar"), key.New("baz")},
			expected: false,
		},
	}
	for i, tcase := range tcases {
		if result := tcase.base.HasPrefix(tcase.prefix); result != tcase.expected {
			t.Fatalf("Test %d failed: %t != %t", i, result, tcase.expected)
		}
	}
}

func TestPathToString(t *testing.T) {
	tcases := []struct {
		in  Path
		out string
	}{
		{
			in:  Path{},
			out: "",
		}, {
			in:  Path{key.New("")},
			out: "/",
		}, {
			in:  Path{key.New("foo")},
			out: "/foo",
		}, {
			in:  Path{key.New("foo"), key.New("bar")},
			out: "/foo/bar",
		}, {
			in:  Path{key.New("/foo"), key.New("bar")},
			out: "//foo/bar",
		}, {
			in:  Path{key.New("foo"), key.New("bar/")},
			out: "/foo/bar/",
		}, {
			in:  Path{key.New(""), key.New("foo"), key.New("bar")},
			out: "//foo/bar",
		}, {
			in:  Path{key.New("foo"), key.New("bar"), key.New("")},
			out: "/foo/bar/",
		}, {
			in:  Path{key.New("/"), key.New("foo"), key.New("bar")},
			out: "///foo/bar",
		}, {
			in:  Path{key.New("foo"), key.New("bar"), key.New("/")},
			out: "/foo/bar//",
		},
	}
	for i, tcase := range tcases {
		if s := tcase.in.String(); s != tcase.out {
			t.Fatalf("Test %d failed: %s != %s", i, s, tcase.out)
		}
	}
}
<commit_msg>path: Add a better test failure log for TestPathHasPrefix<commit_after>// Copyright (c) 2017 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package path

import (
	"fmt"
	"testing"

	"github.com/aristanetworks/goarista/key"
	"github.com/aristanetworks/goarista/value"
)

func TestNewPath(t *testing.T) {
	tcases := []struct {
		in  []interface{}
		out Path
	}{
		{
			in:  nil,
			out: nil,
		}, {
			in:  []interface{}{},
			out: Path{},
		}, {
			in:  []interface{}{""},
			out: Path{key.New("")},
		}, {
			in:  []interface{}{key.New("")},
			out: Path{key.New("")},
		}, {
			in:  []interface{}{"foo"},
			out: Path{key.New("foo")},
		}, {
			in:  []interface{}{key.New("foo")},
			out: Path{key.New("foo")},
		}, {
			in:  []interface{}{"foo", key.New("bar")},
			out: Path{key.New("foo"), key.New("bar")},
		}, {
			in:  []interface{}{key.New("foo"), "bar", key.New("baz")},
			out: Path{key.New("foo"), key.New("bar"), key.New("baz")},
		},
	}
	for i, tcase := range tcases {
		if p := New(tcase.in...); !p.Equal(tcase.out) {
			t.Fatalf("Test %d failed: %#v != %#v", i, p, tcase.out)
		}
	}
}

func TestAppendPath(t *testing.T) {
	tcases := []struct {
		base     Path
		elements []interface{}
		expected Path
	}{
		{
			base:     Path{},
			elements: []interface{}{},
			expected: Path{},
		}, {
			base:     Path{},
			elements: []interface{}{""},
			expected: Path{key.New("")},
		}, {
			base:     Path{},
			elements: []interface{}{key.New("")},
			expected: Path{key.New("")},
		}, {
			base:     Path{},
			elements: []interface{}{"foo", key.New("bar")},
			expected: Path{key.New("foo"), key.New("bar")},
		}, {
			base:     Path{key.New("foo")},
			elements: []interface{}{key.New("bar"), "baz"},
			expected: Path{key.New("foo"), key.New("bar"), key.New("baz")},
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			elements: []interface{}{key.New("baz")},
			expected: Path{key.New("foo"), key.New("bar"), key.New("baz")},
		},
	}
	for i, tcase := range tcases {
		if p := Append(tcase.base, tcase.elements...); !p.Equal(tcase.expected) {
			t.Fatalf("Test %d failed: %#v != %#v", i, p, tcase.expected)
		}
	}
}

type customKey struct {
	i *int
}

func (c customKey) String() string {
	return fmt.Sprintf("customKey=%d", *c.i)
}

func (c customKey) MarshalJSON() ([]byte, error) {
	return nil, nil
}

func (c customKey) ToBuiltin() interface{} {
	return nil
}

func (c customKey) Equal(other interface{}) bool {
	o, ok := other.(customKey)
	return ok && *c.i == *o.i
}

var (
	_ value.Value    = customKey{}
	_ key.Comparable = customKey{}
	a                = 1
	b                = 1
)

func TestPathEquality(t *testing.T) {
	tcases := []struct {
		base     Path
		other    Path
		expected bool
	}{
		{
			base:     Path{},
			other:    Path{},
			expected: true,
		}, {
			base:     Path{},
			other:    Path{key.New("")},
			expected: false,
		}, {
			base:     Path{key.New("foo")},
			other:    Path{key.New("foo")},
			expected: true,
		}, {
			base:     Path{key.New("foo")},
			other:    Path{key.New("bar")},
			expected: false,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			other:    Path{key.New("foo")},
			expected: false,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			other:    Path{key.New("bar"), key.New("foo")},
			expected: false,
		}, {
			base:     Path{key.New("foo"), key.New("bar"), key.New("baz")},
			other:    Path{key.New("foo"), key.New("bar"), key.New("baz")},
			expected: true,
		},
		// Ensure that we check deep equality.
		{
			base:     Path{key.New(map[string]interface{}{})},
			other:    Path{key.New(map[string]interface{}{})},
			expected: true,
		}, {
			base:     Path{key.New(customKey{i: &a})},
			other:    Path{key.New(customKey{i: &b})},
			expected: true,
		},
	}
	for i, tcase := range tcases {
		if result := tcase.base.Equal(tcase.other); result != tcase.expected {
			t.Fatalf("Test %d failed: base: %#v; other: %#v, expected: %t",
				i, tcase.base, tcase.other, tcase.expected)
		}
	}
}

func TestPathHasPrefix(t *testing.T) {
	tcases := []struct {
		base     Path
		prefix   Path
		expected bool
	}{
		{
			base:     Path{},
			prefix:   Path{},
			expected: true,
		}, {
			base:     Path{key.New("foo")},
			prefix:   Path{},
			expected: true,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			prefix:   Path{key.New("foo")},
			expected: true,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			prefix:   Path{key.New("bar")},
			expected: false,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			prefix:   Path{key.New("bar"), key.New("foo")},
			expected: false,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			prefix:   Path{key.New("foo"), key.New("bar")},
			expected: true,
		}, {
			base:     Path{key.New("foo"), key.New("bar")},
			prefix:   Path{key.New("foo"), key.New("bar"), key.New("baz")},
			expected: false,
		},
	}
	for i, tcase := range tcases {
		if result := tcase.base.HasPrefix(tcase.prefix); result != tcase.expected {
			t.Fatalf("Test %d failed: base: %#v; prefix: %#v, expected: %t",
				i, tcase.base, tcase.prefix, tcase.expected)
		}
	}
}

func TestPathToString(t *testing.T) {
	tcases := []struct {
		in  Path
		out string
	}{
		{
			in:  Path{},
			out: "",
		}, {
			in:  Path{key.New("")},
			out: "/",
		}, {
			in:  Path{key.New("foo")},
			out: "/foo",
		}, {
			in:  Path{key.New("foo"), key.New("bar")},
			out: "/foo/bar",
		}, {
			in:  Path{key.New("/foo"), key.New("bar")},
			out: "//foo/bar",
		}, {
			in:  Path{key.New("foo"), key.New("bar/")},
			out: "/foo/bar/",
		}, {
			in:  Path{key.New(""), key.New("foo"), key.New("bar")},
			out: "//foo/bar",
		}, {
			in:  Path{key.New("foo"), key.New("bar"), key.New("")},
			out: "/foo/bar/",
		}, {
			in:  Path{key.New("/"), key.New("foo"), key.New("bar")},
			out: "///foo/bar",
		}, {
			in:  Path{key.New("foo"), key.New("bar"), key.New("/")},
			out: "/foo/bar//",
		},
	}
	for i, tcase := range tcases {
		if s := tcase.in.String(); s != tcase.out {
			t.Fatalf("Test %d failed: %s != %s", i, s, tcase.out)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package leadership_test

import (
	"time"

	"github.com/juju/errors"
	"github.com/juju/testing"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
	"gopkg.in/mgo.v2/txn"

	coreleadership "github.com/juju/juju/leadership"
	"github.com/juju/juju/state/leadership"
	"github.com/juju/juju/state/lease"
)

type LeadershipCheckSuite struct {
	testing.IsolationSuite
}

var _ = gc.Suite(&LeadershipCheckSuite{})

func (s *LeadershipCheckSuite) TestSuccess(c *gc.C) {
	fix := &Fixture{
		leases: map[string]lease.Info{
			"redis": lease.Info{
				Holder:   "redis/0",
				Expiry:   offset(time.Second),
				AssertOp: txn.Op{C: "fake", Id: "fake"},
			},
		},
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(assertOps(c, token), jc.DeepEquals, []txn.Op{{
			C: "fake", Id: "fake",
		}})
	})
}

func (s *LeadershipCheckSuite) TestMissingRefresh_Success(c *gc.C) {
	fix := &Fixture{
		expectCalls: []call{{
			method: "Refresh",
			callback: func(leases map[string]lease.Info) {
				leases["redis"] = lease.Info{
					Holder:   "redis/0",
					Expiry:   offset(time.Second),
					AssertOp: txn.Op{C: "fake", Id: "fake"},
				}
			},
		}},
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(assertOps(c, token), jc.DeepEquals, []txn.Op{{
			C: "fake", Id: "fake",
		}})
	})
}

func (s *LeadershipCheckSuite) TestOtherHolderRefresh_Success(c *gc.C) {
	fix := &Fixture{
		expectCalls: []call{{
			method: "Refresh",
			callback: func(leases map[string]lease.Info) {
				leases["redis"] = lease.Info{
					Holder:   "redis/0",
					Expiry:   offset(time.Second),
					AssertOp: txn.Op{C: "fake", Id: "fake"},
				}
			},
		}},
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(assertOps(c, token), jc.DeepEquals, []txn.Op{{
			C: "fake", Id: "fake",
		}})
	})
}

func (s *LeadershipCheckSuite) TestRefresh_Failure_Missing(c *gc.C) {
	fix := &Fixture{
		expectCalls: []call{{
			method: "Refresh",
		}},
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(token.Check(nil), gc.ErrorMatches, `"redis/0" is not leader of "redis"`)
	})
}

func (s *LeadershipCheckSuite) TestRefresh_Failure_OtherHolder(c *gc.C) {
	fix := &Fixture{
		expectCalls: []call{{
			method: "Refresh",
			callback: func(leases map[string]lease.Info) {
				leases["redis"] = lease.Info{
					Holder:   "redis/1",
					Expiry:   offset(time.Second),
					AssertOp: txn.Op{C: "fake", Id: "fake"},
				}
			},
		}},
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(token.Check(nil), gc.ErrorMatches, `"redis/0" is not leader of "redis"`)
	})
}

func (s *LeadershipCheckSuite) TestRefresh_Error(c *gc.C) {
	fix := &Fixture{
		expectCalls: []call{{
			method: "Refresh",
			err:    errors.New("crunch squish"),
		}},
		expectDirty: true,
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(token.Check(nil), gc.ErrorMatches, "leadership manager stopped")
		err := manager.Wait()
		c.Check(err, gc.ErrorMatches, "crunch squish")
	})
}

func assertOps(c *gc.C, token coreleadership.Token) (out []txn.Op) {
	err := token.Check(&out)
	c.Check(err, jc.ErrorIsNil)
	return out
}
<commit_msg>fix tests (except featuretests/leadership_test which was not adding value)<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package leadership_test

import (
	"time"

	"github.com/juju/errors"
	"github.com/juju/testing"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
	"gopkg.in/mgo.v2/txn"

	coreleadership "github.com/juju/juju/leadership"
	"github.com/juju/juju/state/leadership"
	"github.com/juju/juju/state/lease"
)

type LeadershipCheckSuite struct {
	testing.IsolationSuite
}

var _ = gc.Suite(&LeadershipCheckSuite{})

func (s *LeadershipCheckSuite) TestSuccess(c *gc.C) {
	fix := &Fixture{
		leases: map[string]lease.Info{
			"redis": lease.Info{
				Holder:   "redis/0",
				Expiry:   offset(time.Second),
				AssertOp: txn.Op{C: "fake", Id: "fake"},
			},
		},
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(assertOps(c, token), jc.DeepEquals, []txn.Op{{
			C: "fake", Id: "fake",
		}})
	})
}

func (s *LeadershipCheckSuite) TestMissingRefresh_Success(c *gc.C) {
	fix := &Fixture{
		expectCalls: []call{{
			method: "Refresh",
			callback: func(leases map[string]lease.Info) {
				leases["redis"] = lease.Info{
					Holder:   "redis/0",
					Expiry:   offset(time.Second),
					AssertOp: txn.Op{C: "fake", Id: "fake"},
				}
			},
		}},
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(assertOps(c, token), jc.DeepEquals, []txn.Op{{
			C: "fake", Id: "fake",
		}})
	})
}

func (s *LeadershipCheckSuite) TestOtherHolderRefresh_Success(c *gc.C) {
	fix := &Fixture{
		expectCalls: []call{{
			method: "Refresh",
			callback: func(leases map[string]lease.Info) {
				leases["redis"] = lease.Info{
					Holder:   "redis/0",
					Expiry:   offset(time.Second),
					AssertOp: txn.Op{C: "fake", Id: "fake"},
				}
			},
		}},
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(assertOps(c, token), jc.DeepEquals, []txn.Op{{
			C: "fake", Id: "fake",
		}})
	})
}

func (s *LeadershipCheckSuite) TestRefresh_Failure_Missing(c *gc.C) {
	fix := &Fixture{
		expectCalls: []call{{
			method: "Refresh",
		}},
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(token.Check(nil), gc.ErrorMatches, `"redis/0" is not leader of "redis"`)
	})
}

func (s *LeadershipCheckSuite) TestRefresh_Failure_OtherHolder(c *gc.C) {
	fix := &Fixture{
		expectCalls: []call{{
			method: "Refresh",
			callback: func(leases map[string]lease.Info) {
				leases["redis"] = lease.Info{
					Holder:   "redis/1",
					Expiry:   offset(time.Second),
					AssertOp: txn.Op{C: "fake", Id: "fake"},
				}
			},
		}},
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(token.Check(nil), gc.ErrorMatches, `"redis/0" is not leader of "redis"`)
	})
}

func (s *LeadershipCheckSuite) TestRefresh_Error(c *gc.C) {
	fix := &Fixture{
		expectCalls: []call{{
			method: "Refresh",
			err:    errors.New("crunch squish"),
		}},
		expectDirty: true,
	}
	fix.RunTest(c, func(manager leadership.ManagerWorker, _ *Clock) {
		token := manager.LeadershipCheck("redis", "redis/0")
		c.Check(token.Check(nil), gc.ErrorMatches, "leadership manager stopped")
		err := manager.Wait()
		c.Check(err, gc.ErrorMatches, "crunch squish")
	})
}

func assertOps(c *gc.C, token coreleadership.Token) (out []txn.Op) {
	err := token.Check(&out)
	err := token.Read(&out)
	c.Check(err, jc.ErrorIsNil)
	return out
}
<|endoftext|>


<commit_before>package feature

import (
	"fmt"
	"log"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"sync"
)

type FeatureSet struct {
	mu       sync.Mutex
	features map[string]*Feature
}

func NewFeatureSet() *FeatureSet {
	return &FeatureSet{
		features: make(map[string]*Feature),
	}
}

func (fs *FeatureSet) Add(f *Feature) error {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	if _, ok := fs.features[f.Name()]; ok {
		return fmt.Errorf("duplicate feature %q", f.Name())
	}

	fs.features[f.Name()] = f
	return nil
}

func (fs *FeatureSet) NewFeature(name string) (*Feature, error) {
	f := &Feature{
		name: name,

		enabled: false,
	}
	err := fs.Add(f)
	return f, err
}

func (fs *FeatureSet) Get(name string) *Feature {
	fs.mu.Lock()
	f := fs.features[name]
	fs.mu.Unlock()
	return f
}

func (fs *FeatureSet) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	lastSlash := strings.LastIndex(req.URL.Path, "/")

	// index
	if lastSlash == -1 || lastSlash == len(req.URL.Path)-1 {
		fs.handleIndex(w, req)
		return
	}

	name := req.URL.Path[lastSlash+1:]
	fs.handleFeature(w, req, name)
}

type featuresByName []*Feature

func (f featuresByName) Len() int           { return len(f) }
func (f featuresByName) Swap(i, j int)      { f[i], f[j] = f[j], f[i] }
func (f featuresByName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }

func (fs *FeatureSet) handleIndex(w http.ResponseWriter, req *http.Request) {
	fs.mu.Lock()
	features := make([]*Feature, 0, len(fs.features))
	for _, f := range fs.features {
		features = append(features, f)
	}
	fs.mu.Unlock()

	sort.Sort(featuresByName(features))

	fmt.Fprintf(w, "Features:\n\n")
	for _, f := range features {
		fmt.Fprintf(w, "%s: %v\n", f.Name(), f.IsEnabled())
	}
}

func (fs *FeatureSet) handleFeature(w http.ResponseWriter, req *http.Request, name string) {
	feature := fs.Get(name)

	if feature == nil {
		http.Error(w, "no such feature", http.StatusNotFound)
		return
	}

	switch req.Method {
	case "GET":
		fmt.Fprintf(w, "%s: %v", name, feature.IsEnabled())
	case "POST":
		enabledRaw := req.URL.Query().Get("enabled")
		if strings.TrimSpace(enabledRaw) == "" {
			http.Error(w, "missing 'enabled' parameter", http.StatusBadRequest)
			return
		}

		enabled, err := strconv.ParseBool(enabledRaw)
		if err != nil {
			log.Printf("Error: parsing bool param %q: %s\n", enabledRaw, err)
			http.Error(w, "invalid parameter", http.StatusBadRequest)
			return
		}

		feature.Set(enabled)
	default:
		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
	}
}

var defaultFeatureSet = NewFeatureSet()

type Feature struct {
	name string

	mu      sync.Mutex
	enabled bool
}

func NewFeature(name string) *Feature {
	f := &Feature{
		name: name,

		enabled: false,
	}
	defaultFeatureSet.Add(f)
	return f
}

func (f *Feature) Name() string {
	return f.name
}

func (f *Feature) IsEnabled() bool {
	f.mu.Lock()
	isEnabled := f.enabled
	f.mu.Unlock()
	return isEnabled
}

func (f *Feature) Set(enabled bool) {
	f.mu.Lock()
	f.enabled = enabled
	f.mu.Unlock()
}
<commit_msg>Remove global feature set<commit_after>package feature

import (
	"fmt"
	"log"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"sync"
)

type FeatureSet struct {
	mu       sync.Mutex
	features map[string]*Feature
}

func NewFeatureSet() *FeatureSet {
	return &FeatureSet{
		features: make(map[string]*Feature),
	}
}

func (fs *FeatureSet) Add(f *Feature) error {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	if _, ok := fs.features[f.Name()]; ok {
		return fmt.Errorf("duplicate feature %q", f.Name())
	}

	fs.features[f.Name()] = f
	return nil
}

func (fs *FeatureSet) NewFeature(name string) (*Feature, error) {
	f := &Feature{
		name: name,

		enabled: false,
	}
	err := fs.Add(f)
	return f, err
}

func (fs *FeatureSet) Get(name string) *Feature {
	fs.mu.Lock()
	f := fs.features[name]
	fs.mu.Unlock()
	return f
}

func (fs *FeatureSet) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	lastSlash := strings.LastIndex(req.URL.Path, "/")

	// index
	if lastSlash == -1 || lastSlash == len(req.URL.Path)-1 {
		fs.handleIndex(w, req)
		return
	}

	name := req.URL.Path[lastSlash+1:]
	fs.handleFeature(w, req, name)
}

type featuresByName []*Feature

func (f featuresByName) Len() int           { return len(f) }
func (f featuresByName) Swap(i, j int)      { f[i], f[j] = f[j], f[i] }
func (f featuresByName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }

func (fs *FeatureSet) handleIndex(w http.ResponseWriter, req *http.Request) {
	fs.mu.Lock()
	features := make([]*Feature, 0, len(fs.features))
	for _, f := range fs.features {
		features = append(features, f)
	}
	fs.mu.Unlock()

	sort.Sort(featuresByName(features))

	fmt.Fprintf(w, "Features:\n\n")
	for _, f := range features {
		fmt.Fprintf(w, "%s: %v\n", f.Name(), f.IsEnabled())
	}
}

func (fs *FeatureSet) handleFeature(w http.ResponseWriter, req *http.Request, name string) {
	feature := fs.Get(name)

	if feature == nil {
		http.Error(w, "no such feature", http.StatusNotFound)
		return
	}

	switch req.Method {
	case "GET":
		fmt.Fprintf(w, "%s: %v", name, feature.IsEnabled())
	case "POST":
		enabledRaw := req.URL.Query().Get("enabled")
		if strings.TrimSpace(enabledRaw) == "" {
			http.Error(w, "missing 'enabled' parameter", http.StatusBadRequest)
			return
		}

		enabled, err := strconv.ParseBool(enabledRaw)
		if err != nil {
			log.Printf("Error: parsing bool param %q: %s\n", enabledRaw, err)
			http.Error(w, "invalid parameter", http.StatusBadRequest)
			return
		}

		feature.Set(enabled)
	default:
		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
	}
}

type Feature struct {
	name string

	mu      sync.Mutex
	enabled bool
}

func NewFeature(name string) *Feature {
	f := &Feature{
		name: name,

		enabled: false,
	}
	return f
}

func (f *Feature) Name() string {
	return f.name
}

func (f *Feature) IsEnabled() bool {
	f.mu.Lock()
	isEnabled := f.enabled
	f.mu.Unlock()
	return isEnabled
}

func (f *Feature) Set(enabled bool) {
	f.mu.Lock()
	f.enabled = enabled
	f.mu.Unlock()
}
<|endoftext|>