<commit_before>package quickpgp

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"golang.org/x/crypto/openpgp"
	openpgperrors "golang.org/x/crypto/openpgp/errors"
	"hostutils"
)

var _ = hostutils.Display

func Decrypt(privateKeyFileName string, publicKeyFileName string, file string) (err error) {

	if filepath.Ext(file) != ".pgp" {
		return fmt.Errorf("quickpgp: filename to decrypt must end in .pgp")
	}

	var signer openpgp.EntityList
	if signer, err = readPublicKeyFile(publicKeyFileName); err != nil {
		return err
	}

	var recipient *openpgp.Entity
	if recipient, err = readPrivateKeyFile(privateKeyFileName); err != nil {
		return err
	}
	if recipient == nil {
		return fmt.Errorf("quickpgp: unable to read %s", privateKeyFileName)
	}

	var keyring openpgp.EntityList
	keyring = append(keyring, signer[0])
	keyring = append(keyring, recipient)

	var cipherTextFile *os.File
	if cipherTextFile, err = os.Open(file); err != nil {
		return err
	}

	md, err := openpgp.ReadMessage(cipherTextFile, keyring, nil, nil)
	if err != nil {
		return err
	}

	var cwd string
	if cwd, err = os.Getwd(); err != nil {
		return err
	}
	var plainTextOutput *os.File
	if plainTextOutput, err = ioutil.TempFile(cwd, ".quickpgp."); err != nil {
		return err
	}
	var cleanExit bool
	defer func() {
		if !cleanExit {
			_ = os.Remove(plainTextOutput.Name())
		}
	}()

	_, err = io.Copy(plainTextOutput, md.UnverifiedBody)
	if err != nil {
		return err
	}
	plainTextOutput.Close()
	if md.SignatureError != nil {
		return err
	}
	if md.Signature == nil {
		return openpgperrors.ErrUnknownIssuer
	}

	bareFilename := strings.TrimSuffix(file, filepath.Ext(file))
	if len(md.LiteralData.FileName) != 0 && md.LiteralData.FileName != bareFilename {
		fmt.Fprintf(os.Stderr, "quickpgp: suggested filename \"%s\"\n", md.LiteralData.FileName)
	}
	var finalFilename string
	if _, err := os.Stat(bareFilename); os.IsNotExist(err) {
		finalFilename = bareFilename
	} else {
		finalFilename = fmt.Sprintf("%s.%X", bareFilename, uint32(md.SignedByKeyId&0xffffffff))
		fmt.Fprintf(os.Stderr, "quickpgp: \"%s\" exists, writing to \"%s\"\n", bareFilename, finalFilename)
	}

	err = os.Rename(plainTextOutput.Name(), finalFilename)
	if err == nil {
		cleanExit = true
	}
	return err
}
<commit_msg>remove extranous debugging line<commit_after>package quickpgp

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"golang.org/x/crypto/openpgp"
	openpgperrors "golang.org/x/crypto/openpgp/errors"
	"hostutils"
)

func Decrypt(privateKeyFileName string, publicKeyFileName string, file string) (err error) {

	if filepath.Ext(file) != ".pgp" {
		return fmt.Errorf("quickpgp: filename to decrypt must end in .pgp")
	}

	var signer openpgp.EntityList
	if signer, err = readPublicKeyFile(publicKeyFileName); err != nil {
		return err
	}

	var recipient *openpgp.Entity
	if recipient, err = readPrivateKeyFile(privateKeyFileName); err != nil {
		return err
	}
	if recipient == nil {
		return fmt.Errorf("quickpgp: unable to read %s", privateKeyFileName)
	}

	var keyring openpgp.EntityList
	keyring = append(keyring, signer[0])
	keyring = append(keyring, recipient)

	var cipherTextFile *os.File
	if cipherTextFile, err = os.Open(file); err != nil {
		return err
	}

	md, err := openpgp.ReadMessage(cipherTextFile, keyring, nil, nil)
	if err != nil {
		return err
	}

	var cwd string
	if cwd, err = os.Getwd(); err != nil {
		return err
	}
	var plainTextOutput *os.File
	if plainTextOutput, err = ioutil.TempFile(cwd, ".quickpgp."); err != nil {
		return err
	}
	var cleanExit bool
	defer func() {
		if !cleanExit {
			_ = os.Remove(plainTextOutput.Name())
		}
	}()

	_, err = io.Copy(plainTextOutput, md.UnverifiedBody)
	if err != nil {
		return err
	}
	plainTextOutput.Close()
	if md.SignatureError != nil {
		return err
	}
	if md.Signature == nil {
		return openpgperrors.ErrUnknownIssuer
	}

	bareFilename := strings.TrimSuffix(file, filepath.Ext(file))
	if len(md.LiteralData.FileName) != 0 && md.LiteralData.FileName != bareFilename {
		fmt.Fprintf(os.Stderr, "quickpgp: suggested filename \"%s\"\n", md.LiteralData.FileName)
	}
	var finalFilename string
	if _, err := os.Stat(bareFilename); os.IsNotExist(err) {
		finalFilename = bareFilename
	} else {
		finalFilename = fmt.Sprintf("%s.%X", bareFilename, uint32(md.SignedByKeyId&0xffffffff))
		fmt.Fprintf(os.Stderr, "quickpgp: \"%s\" exists, writing to \"%s\"\n", bareFilename, finalFilename)
	}

	err = os.Rename(plainTextOutput.Name(), finalFilename)
	if err == nil {
		cleanExit = true
	}
	return err
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/rds"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsDbSubnetGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsDbSubnetGroupCreate,
		Read:   resourceAwsDbSubnetGroupRead,
		Delete: resourceAwsDbSubnetGroupDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				ForceNew: true,
				Required: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if !regexp.MustCompile(`^[.0-9A-Za-z-_]+$`).MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"only alphanumeric characters, hyphens, underscores, and periods allowed in %q", k))
					}
					if len(value) > 255 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 255 characters", k))
					}
					if regexp.MustCompile(`(?i)^default$`).MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"%q is not allowed as %q", "Default", k))
					}
					return
				},
			},

			"description": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"subnet_ids": &schema.Schema{
				Type:     schema.TypeSet,
				Required: true,
				ForceNew: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set:      schema.HashString,
			},
		},
	}
}

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

	subnetIdsSet := d.Get("subnet_ids").(*schema.Set)
	subnetIds := make([]*string, subnetIdsSet.Len())
	for i, subnetId := range subnetIdsSet.List() {
		subnetIds[i] = aws.String(subnetId.(string))
	}

	createOpts := rds.CreateDBSubnetGroupInput{
		DBSubnetGroupName:        aws.String(d.Get("name").(string)),
		DBSubnetGroupDescription: aws.String(d.Get("description").(string)),
		SubnetIds:                subnetIds,
	}

	log.Printf("[DEBUG] Create DB Subnet Group: %#v", createOpts)
	_, err := rdsconn.CreateDBSubnetGroup(&createOpts)
	if err != nil {
		return fmt.Errorf("Error creating DB Subnet Group: %s", err)
	}

	d.SetId(*createOpts.DBSubnetGroupName)
	log.Printf("[INFO] DB Subnet Group ID: %s", d.Id())
	return resourceAwsDbSubnetGroupRead(d, meta)
}

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

	describeOpts := rds.DescribeDBSubnetGroupsInput{
		DBSubnetGroupName: aws.String(d.Id()),
	}

	describeResp, err := rdsconn.DescribeDBSubnetGroups(&describeOpts)
	if err != nil {
		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "DBSubnetGroupNotFoundFault" {
			// Update state to indicate the db subnet no longer exists.
			d.SetId("")
			return nil
		}
		return err
	}

	if len(describeResp.DBSubnetGroups) == 0 {
		return fmt.Errorf("Unable to find DB Subnet Group: %#v", describeResp.DBSubnetGroups)
	}

	var subnetGroup *rds.DBSubnetGroup
	for _, s := range describeResp.DBSubnetGroups {
		// AWS is down casing the name provided, so we compare lower case versions
		// of the names. We lower case both our name and their name in the check,
		// incase they change that someday.
		if strings.ToLower(d.Id()) == strings.ToLower(*s.DBSubnetGroupName) {
			subnetGroup = describeResp.DBSubnetGroups[0]
		}
	}

	if subnetGroup.DBSubnetGroupName == nil {
		return fmt.Errorf("Unable to find DB Subnet Group: %#v", describeResp.DBSubnetGroups)
	}

	d.Set("name", d.Id())
	d.Set("description", *subnetGroup.DBSubnetGroupDescription)

	subnets := make([]string, 0, len(subnetGroup.Subnets))
	for _, s := range subnetGroup.Subnets {
		subnets = append(subnets, *s.SubnetIdentifier)
	}
	d.Set("subnet_ids", subnets)

	return nil
}

func resourceAwsDbSubnetGroupDelete(d *schema.ResourceData, meta interface{}) error {
	stateConf := &resource.StateChangeConf{
		Pending:    []string{"pending"},
		Target:     "destroyed",
		Refresh:    resourceAwsDbSubnetGroupDeleteRefreshFunc(d, meta),
		Timeout:    3 * time.Minute,
		MinTimeout: 1 * time.Second,
	}
	_, err := stateConf.WaitForState()
	return err
}

func resourceAwsDbSubnetGroupDeleteRefreshFunc(
	d *schema.ResourceData,
	meta interface{}) resource.StateRefreshFunc {
	rdsconn := meta.(*AWSClient).rdsconn

	return func() (interface{}, string, error) {

		deleteOpts := rds.DeleteDBSubnetGroupInput{
			DBSubnetGroupName: aws.String(d.Id()),
		}

		if _, err := rdsconn.DeleteDBSubnetGroup(&deleteOpts); err != nil {
			rdserr, ok := err.(awserr.Error)
			if !ok {
				return d, "error", err
			}

			if rdserr.Code() != "DBSubnetGroupNotFoundFault" {
				return d, "error", err
			}
		}

		return d, "destroyed", nil
	}
}
<commit_msg>provider/aws: Add update method to DB Subnet Group<commit_after>package aws

import (
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/rds"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsDbSubnetGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsDbSubnetGroupCreate,
		Read:   resourceAwsDbSubnetGroupRead,
		Update: resourceAwsDbSubnetGroupUpdate,
		Delete: resourceAwsDbSubnetGroupDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				ForceNew: true,
				Required: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if !regexp.MustCompile(`^[.0-9A-Za-z-_]+$`).MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"only alphanumeric characters, hyphens, underscores, and periods allowed in %q", k))
					}
					if len(value) > 255 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 255 characters", k))
					}
					if regexp.MustCompile(`(?i)^default$`).MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"%q is not allowed as %q", "Default", k))
					}
					return
				},
			},

			"description": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"subnet_ids": &schema.Schema{
				Type:     schema.TypeSet,
				Required: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set:      schema.HashString,
			},
		},
	}
}

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

	subnetIdsSet := d.Get("subnet_ids").(*schema.Set)
	subnetIds := make([]*string, subnetIdsSet.Len())
	for i, subnetId := range subnetIdsSet.List() {
		subnetIds[i] = aws.String(subnetId.(string))
	}

	createOpts := rds.CreateDBSubnetGroupInput{
		DBSubnetGroupName:        aws.String(d.Get("name").(string)),
		DBSubnetGroupDescription: aws.String(d.Get("description").(string)),
		SubnetIds:                subnetIds,
	}

	log.Printf("[DEBUG] Create DB Subnet Group: %#v", createOpts)
	_, err := rdsconn.CreateDBSubnetGroup(&createOpts)
	if err != nil {
		return fmt.Errorf("Error creating DB Subnet Group: %s", err)
	}

	d.SetId(*createOpts.DBSubnetGroupName)
	log.Printf("[INFO] DB Subnet Group ID: %s", d.Id())
	return resourceAwsDbSubnetGroupRead(d, meta)
}

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

	describeOpts := rds.DescribeDBSubnetGroupsInput{
		DBSubnetGroupName: aws.String(d.Id()),
	}

	describeResp, err := rdsconn.DescribeDBSubnetGroups(&describeOpts)
	if err != nil {
		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "DBSubnetGroupNotFoundFault" {
			// Update state to indicate the db subnet no longer exists.
			d.SetId("")
			return nil
		}
		return err
	}

	if len(describeResp.DBSubnetGroups) == 0 {
		return fmt.Errorf("Unable to find DB Subnet Group: %#v", describeResp.DBSubnetGroups)
	}

	var subnetGroup *rds.DBSubnetGroup
	for _, s := range describeResp.DBSubnetGroups {
		// AWS is down casing the name provided, so we compare lower case versions
		// of the names. We lower case both our name and their name in the check,
		// incase they change that someday.
		if strings.ToLower(d.Id()) == strings.ToLower(*s.DBSubnetGroupName) {
			subnetGroup = describeResp.DBSubnetGroups[0]
		}
	}

	if subnetGroup.DBSubnetGroupName == nil {
		return fmt.Errorf("Unable to find DB Subnet Group: %#v", describeResp.DBSubnetGroups)
	}

	d.Set("name", d.Id())
	d.Set("description", *subnetGroup.DBSubnetGroupDescription)

	subnets := make([]string, 0, len(subnetGroup.Subnets))
	for _, s := range subnetGroup.Subnets {
		subnets = append(subnets, *s.SubnetIdentifier)
	}
	d.Set("subnet_ids", subnets)

	return nil
}

func resourceAwsDbSubnetGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).rdsconn
	if d.HasChange("subnet_ids") {
		_, n := d.GetChange("subnet_ids")
		if n == nil {
			n = new(schema.Set)
		}
		ns := n.(*schema.Set)

		var sIds []*string
		for _, s := range ns.List() {
			sIds = append(sIds, aws.String(s.(string)))
		}

		_, err := conn.ModifyDBSubnetGroup(&rds.ModifyDBSubnetGroupInput{
			DBSubnetGroupName: aws.String(d.Id()),
			SubnetIds:         sIds,
		})

		if err != nil {
			return err
		}
	}
	return resourceAwsDbSubnetGroupRead(d, meta)
}

func resourceAwsDbSubnetGroupDelete(d *schema.ResourceData, meta interface{}) error {
	stateConf := &resource.StateChangeConf{
		Pending:    []string{"pending"},
		Target:     "destroyed",
		Refresh:    resourceAwsDbSubnetGroupDeleteRefreshFunc(d, meta),
		Timeout:    3 * time.Minute,
		MinTimeout: 1 * time.Second,
	}
	_, err := stateConf.WaitForState()
	return err
}

func resourceAwsDbSubnetGroupDeleteRefreshFunc(
	d *schema.ResourceData,
	meta interface{}) resource.StateRefreshFunc {
	rdsconn := meta.(*AWSClient).rdsconn

	return func() (interface{}, string, error) {

		deleteOpts := rds.DeleteDBSubnetGroupInput{
			DBSubnetGroupName: aws.String(d.Id()),
		}

		if _, err := rdsconn.DeleteDBSubnetGroup(&deleteOpts); err != nil {
			rdserr, ok := err.(awserr.Error)
			if !ok {
				return d, "error", err
			}

			if rdserr.Code() != "DBSubnetGroupNotFoundFault" {
				return d, "error", err
			}
		}

		return d, "destroyed", nil
	}
}
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"log"

	"github.com/hashicorp/terraform/helper/schema"
	"google.golang.org/api/compute/v1"
	"google.golang.org/api/googleapi"
)

func resourceComputeSnapshot() *schema.Resource {
	return &schema.Resource{
		Create: resourceComputeSnapshotCreate,
		Read:   resourceComputeSnapshotRead,
		Delete: resourceComputeSnapshotDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"zone": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"snapshot_encryption_key_raw": &schema.Schema{
				Type:      schema.TypeString,
				Optional:  true,
				ForceNew:  true,
				Sensitive: true,
			},

			"snapshot_encryption_key_sha256": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"sourcedisk_encryption_key_raw": &schema.Schema{
				Type:      schema.TypeString,
				Optional:  true,
				ForceNew:  true,
				Sensitive: true,
			},

			"sourcedisk_encryption_key_sha256": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"sourcedisk_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"sourcedisk": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"disk": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"project": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"self_link": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceComputeSnapshotCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	// Build the snapshot parameter
	snapshot := &compute.Snapshot{
		Name: d.Get("name").(string),
	}

	disk := d.Get("disk").(string)

	if v, ok := d.GetOk("snapshot_encryption_key_raw"); ok {
		snapshot.SnapshotEncryptionKey = &compute.CustomerEncryptionKey{}
		snapshot.SnapshotEncryptionKey.RawKey = v.(string)
	}

	if v, ok := d.GetOk("sourcedisk_encryption_key_raw"); ok {
		snapshot.SourceDiskEncryptionKey = &compute.CustomerEncryptionKey{}
		snapshot.SourceDiskEncryptionKey.RawKey = v.(string)
	}

	op, err := config.clientCompute.Disks.CreateSnapshot(
		project, d.Get("zone").(string), disk, snapshot).Do()
	if err != nil {
		return fmt.Errorf("Error creating snapshot: %s", err)
	}

	// It probably maybe worked, so store the ID now
	d.SetId(snapshot.Name)

	err = computeOperationWaitZone(config, op, project, d.Get("zone").(string), "Creating Snapshot")
	if err != nil {
		return err
	}
	return resourceComputeSnapshotRead(d, meta)
}

func resourceComputeSnapshotRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	snapshot, err := config.clientCompute.Snapshots.Get(
		project, d.Id()).Do()
	if err != nil {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			log.Printf("[WARN] Removing Snapshot %q because it's gone", d.Get("name").(string))
			// The resource doesn't exist anymore
			d.SetId("")

			return nil
		}

		return fmt.Errorf("Error reading snapshot: %s", err)
	}

	d.Set("self_link", snapshot.SelfLink)
	if snapshot.SnapshotEncryptionKey != nil && snapshot.SnapshotEncryptionKey.Sha256 != "" {
		d.Set("snapshot_encryption_key_sha256", snapshot.SnapshotEncryptionKey.Sha256)
	}

	return nil
}

func resourceComputeSnapshotDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	// Delete the snapshot
	op, err := config.clientCompute.Snapshots.Delete(
		project, d.Id()).Do()
	if err != nil {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			log.Printf("[WARN] Removing Snapshot %q because it's gone", d.Get("name").(string))
			// The resource doesn't exist anymore
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error deleting snapshot: %s", err)
	}

	zone := d.Get("zone").(string)
	err = computeOperationWaitZone(config, op, project, zone, "Deleting Snapshot")
	if err != nil {
		return err
	}

	d.SetId("")
	return nil
}
<commit_msg>Snapshot operations are global by project<commit_after>package google

import (
	"fmt"
	"log"

	"github.com/hashicorp/terraform/helper/schema"
	"google.golang.org/api/compute/v1"
	"google.golang.org/api/googleapi"
)

func resourceComputeSnapshot() *schema.Resource {
	return &schema.Resource{
		Create: resourceComputeSnapshotCreate,
		Read:   resourceComputeSnapshotRead,
		Delete: resourceComputeSnapshotDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"zone": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"snapshot_encryption_key_raw": &schema.Schema{
				Type:      schema.TypeString,
				Optional:  true,
				ForceNew:  true,
				Sensitive: true,
			},

			"snapshot_encryption_key_sha256": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"sourcedisk_encryption_key_raw": &schema.Schema{
				Type:      schema.TypeString,
				Optional:  true,
				ForceNew:  true,
				Sensitive: true,
			},

			"sourcedisk_encryption_key_sha256": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"sourcedisk_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"sourcedisk": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"disk": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"project": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"self_link": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceComputeSnapshotCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	// Build the snapshot parameter
	snapshot := &compute.Snapshot{
		Name: d.Get("name").(string),
	}

	disk := d.Get("disk").(string)

	if v, ok := d.GetOk("snapshot_encryption_key_raw"); ok {
		snapshot.SnapshotEncryptionKey = &compute.CustomerEncryptionKey{}
		snapshot.SnapshotEncryptionKey.RawKey = v.(string)
	}

	if v, ok := d.GetOk("sourcedisk_encryption_key_raw"); ok {
		snapshot.SourceDiskEncryptionKey = &compute.CustomerEncryptionKey{}
		snapshot.SourceDiskEncryptionKey.RawKey = v.(string)
	}

	op, err := config.clientCompute.Disks.CreateSnapshot(
		project, d.Get("zone").(string), disk, snapshot).Do()
	if err != nil {
		return fmt.Errorf("Error creating snapshot: %s", err)
	}

	// It probably maybe worked, so store the ID now
	d.SetId(snapshot.Name)

	err = computeOperationWaitZone(config, op, project, d.Get("zone").(string), "Creating Snapshot")
	if err != nil {
		return err
	}
	return resourceComputeSnapshotRead(d, meta)
}

func resourceComputeSnapshotRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	snapshot, err := config.clientCompute.Snapshots.Get(
		project, d.Id()).Do()
	if err != nil {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			log.Printf("[WARN] Removing Snapshot %q because it's gone", d.Get("name").(string))
			// The resource doesn't exist anymore
			d.SetId("")

			return nil
		}

		return fmt.Errorf("Error reading snapshot: %s", err)
	}

	d.Set("self_link", snapshot.SelfLink)
	if snapshot.SnapshotEncryptionKey != nil && snapshot.SnapshotEncryptionKey.Sha256 != "" {
		d.Set("snapshot_encryption_key_sha256", snapshot.SnapshotEncryptionKey.Sha256)
	}

	return nil
}

func resourceComputeSnapshotDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	// Delete the snapshot
	op, err := config.clientCompute.Snapshots.Delete(
		project, d.Id()).Do()
	if err != nil {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			log.Printf("[WARN] Removing Snapshot %q because it's gone", d.Get("name").(string))
			// The resource doesn't exist anymore
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error deleting snapshot: %s", err)
	}

	err = computeOperationWaitGlobal(config, op, project, "Deleting Snapshot")
	if err != nil {
		return err
	}

	d.SetId("")
	return nil
}
<|endoftext|>


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

package prometheus

import (
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/pilosa/pilosa/logger"
	"github.com/pilosa/pilosa/stats"
	"github.com/prometheus/client_golang/prometheus"
)

const (
	// namespace is prepended to each metric event name with "_"
	namespace = "pilosa"
)

// Ensure client implements interface.
var _ stats.StatsClient = &prometheusClient{}

// Module-level mutex to avoid copying in WithTags()
var mu sync.Mutex

// prometheusClient represents a Prometheus implementation of pilosa.statsClient.
type prometheusClient struct {
	tags        []string
	logger      logger.Logger
	counters    map[string]prometheus.Counter
	counterVecs map[string]*prometheus.CounterVec
	gauges      map[string]prometheus.Gauge
	gaugeVecs   map[string]*prometheus.GaugeVec
	observers   map[string]prometheus.Observer
	summaryVecs map[string]*prometheus.SummaryVec
}

// NewPrometheusClient returns a new instance of StatsClient.
func NewPrometheusClient() (*prometheusClient, error) {
	return &prometheusClient{
		logger:      logger.NopLogger,
		counters:    make(map[string]prometheus.Counter),
		counterVecs: make(map[string]*prometheus.CounterVec),
		gauges:      make(map[string]prometheus.Gauge),
		gaugeVecs:   make(map[string]*prometheus.GaugeVec),
		observers:   make(map[string]prometheus.Observer),
		summaryVecs: make(map[string]*prometheus.SummaryVec),
	}, nil
}

// Open no-op to satisfy interface
func (c *prometheusClient) Open() {}

// Close no-op to satisfy interface
func (c *prometheusClient) Close() error {
	return nil
}

// Tags returns a sorted list of tags on the client.
func (c *prometheusClient) Tags() []string {
	return c.tags
}

// labels returns an instance of prometheus.Labels with the value of the set tags.
func (c *prometheusClient) labels() prometheus.Labels {
	return tagsToLabels(c.tags)
}

// WithTags returns a new client with additional tags appended.
func (c *prometheusClient) WithTags(tags ...string) stats.StatsClient {
	return &prometheusClient{
		tags:        unionStringSlice(c.tags, tags),
		logger:      c.logger,
		counters:    c.counters,
		counterVecs: c.counterVecs,
		gauges:      c.gauges,
		gaugeVecs:   c.gaugeVecs,
		observers:   c.observers,
		summaryVecs: c.summaryVecs,
	}
}

// Count tracks the number of times something occurs per second.
func (c *prometheusClient) Count(name string, value int64, rate float64) {
	mu.Lock()
	defer mu.Unlock()

	var counter prometheus.Counter
	var ok bool
	labels := c.labels()
	opts := prometheus.CounterOpts{
		Namespace: namespace,
		Name:      name,
	}
	if len(labels) == 0 {
		counter, ok = c.counters[name]
		if !ok {
			counter = prometheus.NewCounter(opts)
			c.counters[name] = counter
			prometheus.MustRegister(counter)
		}
	} else {
		var counterVec *prometheus.CounterVec
		counterVec, ok = c.counterVecs[name]
		if !ok {
			counterVec = prometheus.NewCounterVec(
				opts,
				labelKeys(labels),
			)
			c.counterVecs[name] = counterVec
			prometheus.MustRegister(counterVec)
		}
		var err error
		counter, err = counterVec.GetMetricWith(labels)
		if err != nil {
			c.logger.Printf("counterVec.GetMetricWith error: %s", err)
		}
	}
	if value == 1 {
		counter.Inc()
	} else {
		counter.Add(float64(value))
	}
}

// CountWithCustomTags tracks the number of times something occurs per second with custom tags.
func (c *prometheusClient) CountWithCustomTags(name string, value int64, rate float64, t []string) {
	c.WithTags(append(c.tags, t...)...).Count(name, value, rate)
}

// Gauge sets the value of a metric.
func (c *prometheusClient) Gauge(name string, value float64, rate float64) {
	mu.Lock()
	defer mu.Unlock()

	var gauge prometheus.Gauge
	var ok bool
	labels := c.labels()
	opts := prometheus.GaugeOpts{
		Namespace: namespace,
		Name:      name,
	}
	if len(labels) == 0 {
		gauge, ok = c.gauges[name]
		if !ok {
			gauge = prometheus.NewGauge(opts)
			c.gauges[name] = gauge
			prometheus.MustRegister(gauge)
		}
	} else {
		var gaugeVec *prometheus.GaugeVec
		gaugeVec, ok = c.gaugeVecs[name]
		if !ok {
			gaugeVec = prometheus.NewGaugeVec(
				opts,
				labelKeys(labels),
			)
			c.gaugeVecs[name] = gaugeVec
			prometheus.MustRegister(gaugeVec)
		}
		var err error
		gauge, err = gaugeVec.GetMetricWith(labels)
		if err != nil {
			c.logger.Printf("gaugeVec.GetMetricWith error: %s", err)
		}
	}
	gauge.Set(float64(value))
}

// Histogram tracks statistical distribution of a metric.
func (c *prometheusClient) Histogram(name string, value float64, rate float64) {
	mu.Lock()
	defer mu.Unlock()

	var observer prometheus.Observer
	var ok bool
	labels := c.labels()
	opts := prometheus.SummaryOpts{
		Namespace:  namespace,
		Name:       name,
		Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
	}
	if len(labels) == 0 {
		observer, ok = c.observers[name]
		if !ok {
			summary := prometheus.NewSummary(opts)
			observer = summary
			c.observers[name] = observer
			prometheus.MustRegister(summary)
		}
	} else {
		var summaryVec *prometheus.SummaryVec
		summaryVec, ok = c.summaryVecs[name]
		if !ok {
			summaryVec = prometheus.NewSummaryVec(
				opts,
				labelKeys(labels),
			)
			c.summaryVecs[name] = summaryVec
			prometheus.MustRegister(summaryVec)
		}
		var err error
		observer, err = summaryVec.GetMetricWith(labels)
		if err != nil {
			c.logger.Printf("summaryVec.GetMetricWith error: %s", err)
		}
	}
	observer.Observe(value)
}

// Set tracks number of unique elements.
func (c *prometheusClient) Set(name string, value string, rate float64) {
	c.logger.Printf("prometheusClient.Set unimplemented: %s=%s", name, value)
}

// Timing tracks timing information for a metric.
func (c *prometheusClient) Timing(name string, value time.Duration, rate float64) {
	durationMs := value / time.Millisecond
	c.Histogram(name, float64(durationMs), rate)
}

// SetLogger sets the logger for client.
func (c *prometheusClient) SetLogger(logger logger.Logger) {
	c.logger = logger
}

// unionStringSlice returns a sorted set of tags which combine a & b.
func unionStringSlice(a, b []string) []string {
	// Sort both sets first.
	sort.Strings(a)
	sort.Strings(b)

	// Find size of largest slice.
	n := len(a)
	if len(b) > n {
		n = len(b)
	}

	// Exit if both sets are empty.
	if n == 0 {
		return nil
	}

	// Iterate over both in order and merge.
	other := make([]string, 0, n)
	for len(a) > 0 || len(b) > 0 {
		if len(a) == 0 {
			other, b = append(other, b[0]), b[1:]
		} else if len(b) == 0 {
			other, a = append(other, a[0]), a[1:]
		} else if a[0] < b[0] {
			other, a = append(other, a[0]), a[1:]
		} else if b[0] < a[0] {
			other, b = append(other, b[0]), b[1:]
		} else {
			other, a, b = append(other, a[0]), a[1:], b[1:]
		}
	}
	return other
}

func tagsToLabels(tags []string) (labels prometheus.Labels) {
	labels = make(prometheus.Labels)
	for _, tag := range tags {
		tagParts := strings.SplitAfterN(tag, ":", 2)
		if len(tagParts) != 2 {
			// only process tags in "key:value" form
			continue
		}
		labels[tagParts[0][0:len(tagParts[0])-1]] = tagParts[1]
	}
	return labels
}

func labelKeys(labels prometheus.Labels) (keys []string) {
	keys = make([]string, len(labels))
	i := 0
	for k := range labels {
		keys[i] = k
		i++
	}
	return keys
}
<commit_msg>Use prometheus-compatible metric naming<commit_after>// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package prometheus

import (
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/pilosa/pilosa/logger"
	"github.com/pilosa/pilosa/stats"
	"github.com/prometheus/client_golang/prometheus"
)

const (
	// namespace is prepended to each metric event name with "_"
	namespace = "pilosa"
)

// Ensure client implements interface.
var _ stats.StatsClient = &prometheusClient{}

// Module-level mutex to avoid copying in WithTags()
var mu sync.Mutex

// prometheusClient represents a Prometheus implementation of pilosa.statsClient.
type prometheusClient struct {
	tags        []string
	logger      logger.Logger
	counters    map[string]prometheus.Counter
	counterVecs map[string]*prometheus.CounterVec
	gauges      map[string]prometheus.Gauge
	gaugeVecs   map[string]*prometheus.GaugeVec
	observers   map[string]prometheus.Observer
	summaryVecs map[string]*prometheus.SummaryVec
}

// NewPrometheusClient returns a new instance of StatsClient.
func NewPrometheusClient() (*prometheusClient, error) {
	return &prometheusClient{
		logger:      logger.NopLogger,
		counters:    make(map[string]prometheus.Counter),
		counterVecs: make(map[string]*prometheus.CounterVec),
		gauges:      make(map[string]prometheus.Gauge),
		gaugeVecs:   make(map[string]*prometheus.GaugeVec),
		observers:   make(map[string]prometheus.Observer),
		summaryVecs: make(map[string]*prometheus.SummaryVec),
	}, nil
}

// Open no-op to satisfy interface
func (c *prometheusClient) Open() {}

// Close no-op to satisfy interface
func (c *prometheusClient) Close() error {
	return nil
}

// Tags returns a sorted list of tags on the client.
func (c *prometheusClient) Tags() []string {
	return c.tags
}

// labels returns an instance of prometheus.Labels with the value of the set tags.
func (c *prometheusClient) labels() prometheus.Labels {
	return tagsToLabels(c.tags)
}

// WithTags returns a new client with additional tags appended.
func (c *prometheusClient) WithTags(tags ...string) stats.StatsClient {
	return &prometheusClient{
		tags:        unionStringSlice(c.tags, tags),
		logger:      c.logger,
		counters:    c.counters,
		counterVecs: c.counterVecs,
		gauges:      c.gauges,
		gaugeVecs:   c.gaugeVecs,
		observers:   c.observers,
		summaryVecs: c.summaryVecs,
	}
}

// Count tracks the number of times something occurs per second.
func (c *prometheusClient) Count(name string, value int64, rate float64) {
	mu.Lock()
	defer mu.Unlock()

	var counter prometheus.Counter
	var ok bool
	name = strings.Replace(name, ".", "_", -1)
	labels := c.labels()
	opts := prometheus.CounterOpts{
		Namespace: namespace,
		Name:      name,
	}
	if len(labels) == 0 {
		counter, ok = c.counters[name]
		if !ok {
			counter = prometheus.NewCounter(opts)
			c.counters[name] = counter
			prometheus.MustRegister(counter)
		}
	} else {
		var counterVec *prometheus.CounterVec
		counterVec, ok = c.counterVecs[name]
		if !ok {
			counterVec = prometheus.NewCounterVec(
				opts,
				labelKeys(labels),
			)
			c.counterVecs[name] = counterVec
			prometheus.MustRegister(counterVec)
		}
		var err error
		counter, err = counterVec.GetMetricWith(labels)
		if err != nil {
			c.logger.Printf("counterVec.GetMetricWith error: %s", err)
		}
	}
	if value == 1 {
		counter.Inc()
	} else {
		counter.Add(float64(value))
	}
}

// CountWithCustomTags tracks the number of times something occurs per second with custom tags.
func (c *prometheusClient) CountWithCustomTags(name string, value int64, rate float64, t []string) {
	c.WithTags(append(c.tags, t...)...).Count(name, value, rate)
}

// Gauge sets the value of a metric.
func (c *prometheusClient) Gauge(name string, value float64, rate float64) {
	mu.Lock()
	defer mu.Unlock()

	var gauge prometheus.Gauge
	var ok bool
	name = strings.Replace(name, ".", "_", -1)
	labels := c.labels()
	opts := prometheus.GaugeOpts{
		Namespace: namespace,
		Name:      name,
	}
	if len(labels) == 0 {
		gauge, ok = c.gauges[name]
		if !ok {
			gauge = prometheus.NewGauge(opts)
			c.gauges[name] = gauge
			prometheus.MustRegister(gauge)
		}
	} else {
		var gaugeVec *prometheus.GaugeVec
		gaugeVec, ok = c.gaugeVecs[name]
		if !ok {
			gaugeVec = prometheus.NewGaugeVec(
				opts,
				labelKeys(labels),
			)
			c.gaugeVecs[name] = gaugeVec
			prometheus.MustRegister(gaugeVec)
		}
		var err error
		gauge, err = gaugeVec.GetMetricWith(labels)
		if err != nil {
			c.logger.Printf("gaugeVec.GetMetricWith error: %s", err)
		}
	}
	gauge.Set(float64(value))
}

// Histogram tracks statistical distribution of a metric.
func (c *prometheusClient) Histogram(name string, value float64, rate float64) {
	mu.Lock()
	defer mu.Unlock()

	var observer prometheus.Observer
	var ok bool
	name = strings.Replace(name, ".", "_", -1)
	labels := c.labels()
	opts := prometheus.SummaryOpts{
		Namespace:  namespace,
		Name:       name,
		Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
	}
	if len(labels) == 0 {
		observer, ok = c.observers[name]
		if !ok {
			summary := prometheus.NewSummary(opts)
			observer = summary
			c.observers[name] = observer
			prometheus.MustRegister(summary)
		}
	} else {
		var summaryVec *prometheus.SummaryVec
		summaryVec, ok = c.summaryVecs[name]
		if !ok {
			summaryVec = prometheus.NewSummaryVec(
				opts,
				labelKeys(labels),
			)
			c.summaryVecs[name] = summaryVec
			prometheus.MustRegister(summaryVec)
		}
		var err error
		observer, err = summaryVec.GetMetricWith(labels)
		if err != nil {
			c.logger.Printf("summaryVec.GetMetricWith error: %s", err)
		}
	}
	observer.Observe(value)
}

// Set tracks number of unique elements.
func (c *prometheusClient) Set(name string, value string, rate float64) {
	c.logger.Printf("prometheusClient.Set unimplemented: %s=%s", name, value)
}

// Timing tracks timing information for a metric.
func (c *prometheusClient) Timing(name string, value time.Duration, rate float64) {
	durationMs := value / time.Millisecond
	c.Histogram(name, float64(durationMs), rate)
}

// SetLogger sets the logger for client.
func (c *prometheusClient) SetLogger(logger logger.Logger) {
	c.logger = logger
}

// unionStringSlice returns a sorted set of tags which combine a & b.
func unionStringSlice(a, b []string) []string {
	// Sort both sets first.
	sort.Strings(a)
	sort.Strings(b)

	// Find size of largest slice.
	n := len(a)
	if len(b) > n {
		n = len(b)
	}

	// Exit if both sets are empty.
	if n == 0 {
		return nil
	}

	// Iterate over both in order and merge.
	other := make([]string, 0, n)
	for len(a) > 0 || len(b) > 0 {
		if len(a) == 0 {
			other, b = append(other, b[0]), b[1:]
		} else if len(b) == 0 {
			other, a = append(other, a[0]), a[1:]
		} else if a[0] < b[0] {
			other, a = append(other, a[0]), a[1:]
		} else if b[0] < a[0] {
			other, b = append(other, b[0]), b[1:]
		} else {
			other, a, b = append(other, a[0]), a[1:], b[1:]
		}
	}
	return other
}

func tagsToLabels(tags []string) (labels prometheus.Labels) {
	labels = make(prometheus.Labels)
	for _, tag := range tags {
		tagParts := strings.SplitAfterN(tag, ":", 2)
		if len(tagParts) != 2 {
			// only process tags in "key:value" form
			continue
		}
		labels[tagParts[0][0:len(tagParts[0])-1]] = tagParts[1]
	}
	return labels
}

func labelKeys(labels prometheus.Labels) (keys []string) {
	keys = make([]string, len(labels))
	i := 0
	for k := range labels {
		keys[i] = k
		i++
	}
	return keys
}
<|endoftext|>


<commit_before>package prometheus

import (
	"log"
	"net/http"

	"github.com/bbqgophers/qpid"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
)

// Sink is a qpid.MetricSink that
// Reports metrics to Prometheus
type Sink struct {
}

//NewSink returns a new PrometheusSink
func NewSink() *Sink {
	return &Sink{}
}

// Listen starts a GrillStatus listener on GrillStatus channel
// reporting messages received to Prometheus.  Must be started
// in a goroutine before starting grill run loop or grill will block
// when it tries to send first status
func (p *Sink) Listen(s chan qpid.GrillStatus) {

	tempGauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: "qpid",
		Subsystem: "grill",
		Name:      "temp_f",
		Help:      "Grill temperature readings.",
	}, []string{
		"sensor",
	},
	)
	setGauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: "qpid",
		Subsystem: "grill",
		Name:      "setpoint_f",
		Help:      "Grill setpoint.",
	}, []string{
		"sensor",
	},
	)
	prometheus.MustRegister(setGauge)

	fanGauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: "qpid",
		Subsystem: "grill",
		Name:      "fan_bool",
		Help:      "Blower ON = 1 OFF = 0.",
	}, []string{
		"sensor",
	},
	)
	prometheus.MustRegister(fanGauge)
	http.Handle("/metrics", prometheus.Handler())
	go http.ListenAndServe(":8080", nil)

	for message := range s {
		for _, s := range message.GrillSensors {
			t, err := s.Temperature()
			if err != nil {
				log.Println(errors.Wrap(err, "get temperature"))
			}
			tempGauge.WithLabelValues(s.Description()).Set(float64(t.F()))

			set, err := s.Setpoint()
			if err != nil {
				log.Println(errors.Wrap(err, "get setpoint"))
			}
			setGauge.WithLabelValues(s.Description()).Set(float64(set.F()))

			fan := message.FanOn
			v := 0.0
			if fan {
				v = 1.0
			}
			fanGauge.WithLabelValues(s.Description()).Set(float64(v))

		}

	}

}
<commit_msg>debug<commit_after>package prometheus

import (
	"log"
	"net/http"

	"github.com/bbqgophers/qpid"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
)

// Sink is a qpid.MetricSink that
// Reports metrics to Prometheus
type Sink struct {
}

//NewSink returns a new PrometheusSink
func NewSink() *Sink {
	return &Sink{}
}

// Listen starts a GrillStatus listener on GrillStatus channel
// reporting messages received to Prometheus.  Must be started
// in a goroutine before starting grill run loop or grill will block
// when it tries to send first status
func (p *Sink) Listen(s chan qpid.GrillStatus) {

	tempGauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: "qpid",
		Subsystem: "grill",
		Name:      "temp_f",
		Help:      "Grill temperature readings.",
	}, []string{
		"sensor",
	},
	)
	setGauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: "qpid",
		Subsystem: "grill",
		Name:      "setpoint_f",
		Help:      "Grill setpoint.",
	}, []string{
		"sensor",
	},
	)
	prometheus.MustRegister(setGauge)

	fanGauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: "qpid",
		Subsystem: "grill",
		Name:      "fan_bool",
		Help:      "Blower ON = 1 OFF = 0.",
	}, []string{
		"sensor",
	},
	)
	prometheus.MustRegister(fanGauge)
	http.Handle("/metrics", prometheus.Handler())
	go http.ListenAndServe(":8080", nil)

	for message := range s {
		for _, s := range message.GrillSensors {
			t, err := s.Temperature()
			if err != nil {
				log.Println(errors.Wrap(err, "get temperature"))
			}
			log.Println("Temp: c ", s.Temperature())
			tempGauge.WithLabelValues(s.Description()).Set(float64(t.F()))

			set, err := s.Setpoint()
			if err != nil {
				log.Println(errors.Wrap(err, "get setpoint"))
			}
			setGauge.WithLabelValues(s.Description()).Set(float64(set.F()))

			fan := message.FanOn
			v := 0.0
			if fan {
				v = 1.0
			}
			fanGauge.WithLabelValues(s.Description()).Set(float64(v))

		}

	}

}
<|endoftext|>


<commit_before>// Package server provides common http server functionality
package http

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"time"

	"github.com/gorilla/mux"
)

var (
	ErrNoEndpointsDefined = errors.New("service has no endpoints defined")
	ErrNoServices         = errors.New("no services provided")
)

// NewServer creates new http server instance
func NewServer(opts ...ServerOption) *Server {
	srv := Server{
		httpServer: &http.Server{
			ReadTimeout:  5 * time.Second,
			WriteTimeout: 10 * time.Second,
			IdleTimeout:  120 * time.Second,
		},
		mux: mux.NewRouter().StrictSlash(true),
	}

	srv.httpServer.Handler = srv.mux

	for _, o := range opts {
		o(&srv)
	}

	if srv.logger == nil {
		srv.logger = log.New(os.Stdout, "http ", log.Ldate|log.Ltime|log.Llongfile)
	}

	if srv.tlsEnabled() {
		srv.httpServer.TLSConfig = &tls.Config{
			PreferServerCipherSuites: true,
			CurvePreferences: []tls.CurveID{
				tls.CurveP256,
				tls.X25519,
			},
		}
	}

	return &srv
}

// Server represents http server implementation
type Server struct {
	httpServer *http.Server
	logger     *log.Logger
	certFile   string
	keyFile    string
	mux        *mux.Router
}

// Run will start a server listening on a given port
func (s *Server) Run(port int) error {
	addr := fmt.Sprintf("0.0.0.0:%d", port)
	s.httpServer.Addr = addr

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, os.Kill)

	var err error

	go func() {
		s.logger.Printf("Starting server at: %s", s.httpServer.Addr)
		if s.tlsEnabled() {
			err = s.runTLS()
		} else {
			err = s.httpServer.ListenAndServe()
		}
	}()

	if err != nil {
		return err
	}

	<-stop

	s.logger.Println("Server shutting down...")
	err = s.Stop()
	if err != nil {
		return err
	}

	s.logger.Println("Server stopped.")

	return nil
}

func (s *Server) tlsEnabled() bool {
	return (s.certFile != "" && s.keyFile != "")
}

func (s *Server) runTLS() error {
	srv := &http.Server{
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 5 * time.Second,
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Connection", "close")
			url := "https://" + r.Host + r.URL.String()
			http.Redirect(w, r, url, http.StatusMovedPermanently)
		}),
	}
	go func() { log.Fatal(srv.ListenAndServe()) }()
	return s.httpServer.ListenAndServeTLS(s.certFile, s.keyFile)
}

// Stop attempts to gracefully shutdown the server
func (s *Server) Stop() error {
	return s.httpServer.Shutdown(context.Background())
}

// RegisterServices registers given http Services with
// the server and sets up routes
func (s *Server) RegisterServices(svcs ...Service) error {
	if svcs == nil {
		return ErrNoServices
	}

	for _, svc := range svcs {
		err := s.RegisterService(svc)
		if err != nil {
			return err
		}
	}

	return nil
}

// RegisterService registers a given http Service with
// the server and sets up routes
func (s *Server) RegisterService(svc Service) error {
	endpoints := svc.Endpoints()

	if endpoints == nil {
		return ErrNoEndpointsDefined
	}

	for path, endpoint := range endpoints {
		if path == "/" {
			path = ""
		} else {
			path = "/" + path
		}
		p := fmt.Sprintf("/%s%s", svc.Prefix(), path)
		route := s.mux.Handle(p, endpoint.Handler)
		if endpoint.Methods != nil {
			route.Methods(endpoint.Methods...)
		}
	}

	return nil
}
<commit_msg>Added default / handler<commit_after>// Package server provides common http server functionality
package http

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"time"

	"github.com/gorilla/mux"
)

var (
	ErrNoEndpointsDefined = errors.New("service has no endpoints defined")
	ErrNoServices         = errors.New("no services provided")
)

// NewServer creates new http server instance
func NewServer(opts ...ServerOption) *Server {
	srv := Server{
		httpServer: &http.Server{
			ReadTimeout:  5 * time.Second,
			WriteTimeout: 10 * time.Second,
			IdleTimeout:  120 * time.Second,
		},
		mux: mux.NewRouter().StrictSlash(true),
	}

	srv.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })

	srv.httpServer.Handler = srv.mux

	for _, o := range opts {
		o(&srv)
	}

	if srv.logger == nil {
		srv.logger = log.New(os.Stdout, "http ", log.Ldate|log.Ltime|log.Llongfile)
	}

	if srv.tlsEnabled() {
		srv.httpServer.TLSConfig = &tls.Config{
			PreferServerCipherSuites: true,
			CurvePreferences: []tls.CurveID{
				tls.CurveP256,
				tls.X25519,
			},
		}
	}

	return &srv
}

// Server represents http server implementation
type Server struct {
	httpServer *http.Server
	logger     *log.Logger
	certFile   string
	keyFile    string
	mux        *mux.Router
}

// Run will start a server listening on a given port
func (s *Server) Run(port int) error {
	addr := fmt.Sprintf("0.0.0.0:%d", port)
	s.httpServer.Addr = addr

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, os.Kill)

	var err error

	go func() {
		s.logger.Printf("Starting server at: %s", s.httpServer.Addr)
		if s.tlsEnabled() {
			err = s.runTLS()
		} else {
			err = s.httpServer.ListenAndServe()
		}
	}()

	if err != nil {
		return err
	}

	<-stop

	s.logger.Println("Server shutting down...")
	err = s.Stop()
	if err != nil {
		return err
	}

	s.logger.Println("Server stopped.")

	return nil
}

func (s *Server) tlsEnabled() bool {
	return (s.certFile != "" && s.keyFile != "")
}

func (s *Server) runTLS() error {
	srv := &http.Server{
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 5 * time.Second,
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Connection", "close")
			url := "https://" + r.Host + r.URL.String()
			http.Redirect(w, r, url, http.StatusMovedPermanently)
		}),
	}
	go func() { log.Fatal(srv.ListenAndServe()) }()
	return s.httpServer.ListenAndServeTLS(s.certFile, s.keyFile)
}

// Stop attempts to gracefully shutdown the server
func (s *Server) Stop() error {
	return s.httpServer.Shutdown(context.Background())
}

// RegisterServices registers given http Services with
// the server and sets up routes
func (s *Server) RegisterServices(svcs ...Service) error {
	if svcs == nil {
		return ErrNoServices
	}

	for _, svc := range svcs {
		err := s.RegisterService(svc)
		if err != nil {
			return err
		}
	}

	return nil
}

// RegisterService registers a given http Service with
// the server and sets up routes
func (s *Server) RegisterService(svc Service) error {
	endpoints := svc.Endpoints()

	if endpoints == nil {
		return ErrNoEndpointsDefined
	}

	for path, endpoint := range endpoints {
		if path == "/" {
			path = ""
		} else {
			path = "/" + path
		}
		p := fmt.Sprintf("/%s%s", svc.Prefix(), path)
		route := s.mux.Handle(p, endpoint.Handler)
		if endpoint.Methods != nil {
			route.Methods(endpoint.Methods...)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2017 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package http

import (
	"encoding/json"
	"errors"
	"net/http"
	"sync"
	"time"

	"github.com/abbot/go-http-auth"
	"github.com/gorilla/websocket"
	"github.com/nu7hatch/gouuid"

	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/logging"
)

const (
	// WilcardNamespace is the namespace used as wildcard. It is used by listeners to filter callbacks.
	WilcardNamespace = "*"
	BulkMsgType      = "BulkMessage"
)

// DefaultRequestTimeout default timeout used for Request/Reply JSON message.
var DefaultRequestTimeout = 10 * time.Second

// WSJSONMessage is JSON based message on top of WSMessage. It implements to
// WSMessage interface and can be sent with via a WSSpeaker.
type WSJSONMessage struct {
	Namespace string
	Type      string
	UUID      string `json:",omitempty"`
	Obj       *json.RawMessage
	Status    int
}

// Marshal serializes the WSJSONMessage into a JSON string.
func (g WSJSONMessage) Marshal() []byte {
	j, _ := json.Marshal(g)
	return j
}

// Bytes see Marshal
func (g WSJSONMessage) Bytes() []byte {
	return g.Marshal()
}

// Reply returns a reply message with the given value, type and status.
// Basically it return a new WSJSONMessage with the correct Namespace and UUID.
func (g *WSJSONMessage) Reply(v interface{}, kind string, status int) *WSJSONMessage {
	b, _ := json.Marshal(v)
	raw := json.RawMessage(b)

	return &WSJSONMessage{
		Namespace: g.Namespace,
		UUID:      g.UUID,
		Obj:       &raw,
		Type:      kind,
		Status:    status,
	}
}

// NewWSJSONMessage creates a new WSJSONMessage with the given namespace, type, value
// and optionally the UUID.
func NewWSJSONMessage(ns string, tp string, v interface{}, uuids ...string) *WSJSONMessage {
	var u string
	if len(uuids) != 0 {
		u = uuids[0]
	} else {
		v4, _ := uuid.NewV4()
		u = v4.String()
	}

	b, _ := json.Marshal(v)
	raw := json.RawMessage(b)

	return &WSJSONMessage{
		Namespace: ns,
		Type:      tp,
		UUID:      u,
		Obj:       &raw,
		Status:    http.StatusOK,
	}
}

// WSBulkMessage bulk of RawMessage.
type WSBulkMessage []json.RawMessage

// WSSpeakerJSONMessageHandler interface used to receive JSON messages.
type WSSpeakerJSONMessageHandler interface {
	OnWSJSONMessage(c WSSpeaker, m *WSJSONMessage)
}

// WSSpeakerJSONMessageDispatcher interface is used to dispatch OnWSJSONMessage events.
type WSSpeakerJSONMessageDispatcher interface {
	AddJSONMessageHandler(h WSSpeakerJSONMessageHandler, namespaces []string)
}

type wsJSONSpeakerEventDispatcher struct {
	eventHandlersLock sync.RWMutex
	nsEventHandlers   map[string][]WSSpeakerJSONMessageHandler
}

func newWSJSONSpeakerEventDispatcher() *wsJSONSpeakerEventDispatcher {
	return &wsJSONSpeakerEventDispatcher{
		nsEventHandlers: make(map[string][]WSSpeakerJSONMessageHandler),
	}
}

// AddJSONMessageHandler adds a new listener for JSON messages.
func (a *wsJSONSpeakerEventDispatcher) AddJSONMessageHandler(h WSSpeakerJSONMessageHandler, namespaces []string) {
	a.eventHandlersLock.Lock()
	// add this handler per namespace
	for _, ns := range namespaces {
		if _, ok := a.nsEventHandlers[ns]; !ok {
			a.nsEventHandlers[ns] = []WSSpeakerJSONMessageHandler{h}
		} else {
			a.nsEventHandlers[ns] = append(a.nsEventHandlers[ns], h)
		}
	}
	a.eventHandlersLock.Unlock()
}

func (a *wsJSONSpeakerEventDispatcher) dispatchMessage(c *WSJSONSpeaker, m *WSJSONMessage) {
	// check whether it is a reply
	if c.onReply(m) {
		return
	}

	a.eventHandlersLock.RLock()
	for _, l := range a.nsEventHandlers[m.Namespace] {
		l.OnWSJSONMessage(c, m)
	}
	for _, l := range a.nsEventHandlers[WilcardNamespace] {
		l.OnWSJSONMessage(c, m)
	}
	a.eventHandlersLock.RUnlock()
}

// OnDisconnected is implemented here to avoid infinite loop since the default
// implemtation is triggering OnDisconnected too.
func (p *wsJSONSpeakerEventDispatcher) OnDisconnected(c WSSpeaker) {
}

// OnConnected is implemented here to avoid infinite loop since the default
// implemtation is triggering OnDisconnected too.
func (p *wsJSONSpeakerEventDispatcher) OnConnected(c WSSpeaker) {
}

type wsJSONSpeakerPoolEventDispatcher struct {
	dispatcher *wsJSONSpeakerEventDispatcher
	pool       WSSpeakerPool
}

// AddJSONMessageHandler adds a new listener for JSON messages.
func (d *wsJSONSpeakerPoolEventDispatcher) AddJSONMessageHandler(h WSSpeakerJSONMessageHandler, namespaces []string) {
	d.dispatcher.AddJSONMessageHandler(h, namespaces)
	for _, client := range d.pool.GetSpeakers() {
		client.(*WSJSONSpeaker).AddJSONMessageHandler(h, namespaces)
	}
}

func (d *wsJSONSpeakerPoolEventDispatcher) AddJSONSpeaker(c *WSJSONSpeaker) {
	d.dispatcher.eventHandlersLock.RLock()
	for ns, handlers := range d.dispatcher.nsEventHandlers {
		for _, handler := range handlers {
			c.AddJSONMessageHandler(handler, []string{ns})
		}
	}
	d.dispatcher.eventHandlersLock.RUnlock()
}

func newWSJSONSpeakerPoolEventDispatcher(pool WSSpeakerPool) *wsJSONSpeakerPoolEventDispatcher {
	return &wsJSONSpeakerPoolEventDispatcher{
		dispatcher: newWSJSONSpeakerEventDispatcher(),
		pool:       pool,
	}
}

// WSJSONSpeaker is a WSSpeaker able to handle JSON Message and Request/Reply calls.
type WSJSONSpeaker struct {
	WSSpeaker
	*wsJSONSpeakerEventDispatcher
	nsSubscribed   map[string]bool
	replyChanMutex sync.RWMutex
	replyChan      map[string]chan *WSJSONMessage
}

// Send sends a message according to the namespace.
func (s *WSJSONSpeaker) Send(m WSMessage) {
	if msg, ok := m.(WSJSONMessage); ok {
		if _, ok := s.nsSubscribed[msg.Namespace]; !ok {
			if _, ok := s.nsSubscribed[WilcardNamespace]; !ok {
				return
			}
		}
	}

	s.WSSpeaker.SendMessage(m)
}

func (s *WSJSONSpeaker) onReply(m *WSJSONMessage) bool {
	s.replyChanMutex.RLock()
	ch, ok := s.replyChan[m.UUID]
	if ok {
		ch <- m
	}
	s.replyChanMutex.RUnlock()

	return ok
}

// Request sends a JSON message request waiting for a reply using the given timeout.
func (s *WSJSONSpeaker) Request(m *WSJSONMessage, timeout time.Duration) (*WSJSONMessage, error) {
	ch := make(chan *WSJSONMessage, 1)

	s.replyChanMutex.Lock()
	s.replyChan[m.UUID] = ch
	s.replyChanMutex.Unlock()

	defer func() {
		s.replyChanMutex.Lock()
		delete(s.replyChan, m.UUID)
		close(ch)
		s.replyChanMutex.Unlock()
	}()

	s.Send(m)

	select {
	case resp := <-ch:
		return resp, nil
	case <-time.After(timeout):
		return nil, common.ErrTimeout
	}
}

// OnMessage checks that the WSMessage comes from a WSJSONSpeaker. It parses
// the JSON message and then dispatch the message to the proper listeners according
// to the namespace.
func (s *WSJSONSpeaker) OnMessage(c WSSpeaker, m WSMessage) {
	if c, ok := c.(*WSJSONSpeaker); ok {
		jm := &WSJSONMessage{}
		if err := json.Unmarshal(m.Bytes(), jm); err != nil {
			logging.GetLogger().Errorf("Error while decoding WSJSONMessage %s", err.Error())
			return
		}

		if jm.Type == BulkMsgType {
			var bulkMessage WSBulkMessage
			if err := json.Unmarshal([]byte(*jm.Obj), &bulkMessage); err != nil {
				for _, jm := range bulkMessage {
					s.OnMessage(c, WSRawMessage([]byte(jm)))
				}
			}
			return
		}

		s.wsJSONSpeakerEventDispatcher.dispatchMessage(c, jm)
	}
}

func newWSJSONSpeaker(c WSSpeaker) *WSJSONSpeaker {
	s := &WSJSONSpeaker{
		WSSpeaker:                    c,
		wsJSONSpeakerEventDispatcher: newWSJSONSpeakerEventDispatcher(),
		nsSubscribed:                 make(map[string]bool),
		replyChan:                    make(map[string]chan *WSJSONMessage),
	}

	// subscribing to itself so that the WSJSONSpeaker can get WSMessage and can convert them
	// to WSJSONMessage and then forward them to its own even listeners.
	s.AddEventHandler(s)
	return s
}

func (c *WSClient) UpgradeToWSJSONSpeaker() *WSJSONSpeaker {
	js := newWSJSONSpeaker(c)
	c.wsSpeaker = js
	return js
}

func (c *wsIncomingClient) upgradeToWSJSONSpeaker() *WSJSONSpeaker {
	js := newWSJSONSpeaker(c)
	c.wsSpeaker = js
	return js
}

// WSJSONSpeakerPool is the interface of a pool of WSJSONSpeakers.
type WSJSONSpeakerPool interface {
	WSSpeakerPool
	WSSpeakerJSONMessageDispatcher
	Request(host string, request *WSJSONMessage, timeout time.Duration) (*WSJSONMessage, error)
}

// WSJSONClientPool is a WSClientPool able to send WSJSONMessage.
type WSJSONClientPool struct {
	*WSClientPool
	*wsJSONSpeakerPoolEventDispatcher
}

// AddClient adds a WSClient to the pool.
func (a *WSJSONClientPool) AddClient(c WSSpeaker) error {
	if wc, ok := c.(*WSClient); ok {
		jsonSpeaker := wc.UpgradeToWSJSONSpeaker()
		a.WSClientPool.AddClient(jsonSpeaker)
		a.wsJSONSpeakerPoolEventDispatcher.AddJSONSpeaker(jsonSpeaker)
	} else {
		return errors.New("wrong client type")
	}
	return nil
}

// Request sends a Request JSON message to the WSSpeaker of the given host.
func (s *WSJSONClientPool) Request(host string, request *WSJSONMessage, timeout time.Duration) (*WSJSONMessage, error) {
	c := s.WSClientPool.GetSpeakerByHost(host)
	if c == nil {
		return nil, common.ErrNotFound
	}

	return c.(*WSJSONSpeaker).Request(request, timeout)
}

// NewWSJSONClientPool returns a new WSJSONClientPool.
func NewWSJSONClientPool(name string) *WSJSONClientPool {
	pool := NewWSClientPool(name)
	return &WSJSONClientPool{
		WSClientPool:                     pool,
		wsJSONSpeakerPoolEventDispatcher: newWSJSONSpeakerPoolEventDispatcher(pool),
	}
}

// WSJSONServer is a WSServer able to handle WSJSONSpeaker.
type WSJSONServer struct {
	*WSServer
	*wsJSONSpeakerPoolEventDispatcher
}

// Request sends a Request JSON message to the WSSpeaker of the given host.
func (s *WSJSONServer) Request(host string, request *WSJSONMessage, timeout time.Duration) (*WSJSONMessage, error) {
	c := s.WSServer.GetSpeakerByHost(host)
	if c == nil {
		return nil, common.ErrNotFound
	}

	return c.(*WSJSONSpeaker).Request(request, timeout)
}

// OnMessage websocket event.
func (s *WSJSONServer) OnMessage(c WSSpeaker, m WSMessage) {
}

// OnConnected websocket event.
func (s *WSJSONServer) OnConnected(c WSSpeaker) {
}

// OnDisconnected removes the WSSpeaker from the incomer pool.
func (s *WSJSONServer) OnDisconnected(c WSSpeaker) {
	s.WSServer.wsIncomerPool.RemoveClient(c)
}

// NewWSJSONServer returns a new WSJSONServer
func NewWSJSONServer(server *WSServer) *WSJSONServer {
	s := &WSJSONServer{
		WSServer: server,
		wsJSONSpeakerPoolEventDispatcher: newWSJSONSpeakerPoolEventDispatcher(server),
	}

	s.WSServer.wsIncomerPool.AddEventHandler(s)

	// This incomerHandler upgrades the incomers to WSJSONSpeaker thus being able to parse JSONMessage.
	// The server set also the WSJsonSpeaker with the proper namspaces it subscribes to thanks to the
	// headers.
	s.WSServer.incomerHandler = func(conn *websocket.Conn, r *auth.AuthenticatedRequest) WSSpeaker {
		// the default incomer handler creates a standard wsIncomerClient that we upgrade to a WSJSONSpeaker
		// being able to handle the JSONMessage
		c := defaultIncomerHandler(conn, r).upgradeToWSJSONSpeaker()

		// from headers
		if namespaces, ok := r.Header["X-Websocket-Namespace"]; ok {
			for _, ns := range namespaces {
				c.nsSubscribed[ns] = true
			}
		}

		// from parameter, useful for browser client
		if namespaces, ok := r.URL.Query()["x-websocket-namespace"]; ok {
			for _, ns := range namespaces {
				c.nsSubscribed[ns] = true
			}
		}

		// if empty use wilcard for backward compatibility
		if len(c.nsSubscribed) == 0 {
			c.nsSubscribed[WilcardNamespace] = true
		}

		s.wsJSONSpeakerPoolEventDispatcher.AddJSONSpeaker(c)

		return c
	}

	return s
}
<commit_msg>websocket: marshal Content only when marshalling message<commit_after>/*
 * Copyright (C) 2017 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package http

import (
	"encoding/json"
	"errors"
	"net/http"
	"sync"
	"time"

	"github.com/abbot/go-http-auth"
	"github.com/gorilla/websocket"
	"github.com/nu7hatch/gouuid"

	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/logging"
)

const (
	// WilcardNamespace is the namespace used as wildcard. It is used by listeners to filter callbacks.
	WilcardNamespace = "*"
	BulkMsgType      = "BulkMessage"
)

// DefaultRequestTimeout default timeout used for Request/Reply JSON message.
var DefaultRequestTimeout = 10 * time.Second

// WSJSONMessage is JSON based message on top of WSMessage. It implements to
// WSMessage interface and can be sent with via a WSSpeaker.
type WSJSONMessage struct {
	Namespace string
	Type      string
	UUID      string `json:",omitempty"`
	Obj       *json.RawMessage
	Status    int
	value     interface{}
}

// Marshal serializes the WSJSONMessage into a JSON string.
func (g WSJSONMessage) Marshal() []byte {
	if g.Obj == nil {
		b, _ := json.Marshal(g.value)
		raw := json.RawMessage(b)
		g.Obj = &raw
	}

	j, _ := json.Marshal(g)
	return j
}

// Bytes see Marshal
func (g WSJSONMessage) Bytes() []byte {
	return g.Marshal()
}

// Reply returns a reply message with the given value, type and status.
// Basically it return a new WSJSONMessage with the correct Namespace and UUID.
func (g *WSJSONMessage) Reply(v interface{}, kind string, status int) *WSJSONMessage {
	return &WSJSONMessage{
		Namespace: g.Namespace,
		UUID:      g.UUID,
		Type:      kind,
		Status:    status,
		value:     v,
	}
}

// NewWSJSONMessage creates a new WSJSONMessage with the given namespace, type, value
// and optionally the UUID.
func NewWSJSONMessage(ns string, tp string, v interface{}, uuids ...string) *WSJSONMessage {
	var u string
	if len(uuids) != 0 {
		u = uuids[0]
	} else {
		v4, _ := uuid.NewV4()
		u = v4.String()
	}

	return &WSJSONMessage{
		Namespace: ns,
		Type:      tp,
		UUID:      u,
		Status:    http.StatusOK,
		value:     v,
	}
}

// WSBulkMessage bulk of RawMessage.
type WSBulkMessage []json.RawMessage

// WSSpeakerJSONMessageHandler interface used to receive JSON messages.
type WSSpeakerJSONMessageHandler interface {
	OnWSJSONMessage(c WSSpeaker, m *WSJSONMessage)
}

// WSSpeakerJSONMessageDispatcher interface is used to dispatch OnWSJSONMessage events.
type WSSpeakerJSONMessageDispatcher interface {
	AddJSONMessageHandler(h WSSpeakerJSONMessageHandler, namespaces []string)
}

type wsJSONSpeakerEventDispatcher struct {
	eventHandlersLock sync.RWMutex
	nsEventHandlers   map[string][]WSSpeakerJSONMessageHandler
}

func newWSJSONSpeakerEventDispatcher() *wsJSONSpeakerEventDispatcher {
	return &wsJSONSpeakerEventDispatcher{
		nsEventHandlers: make(map[string][]WSSpeakerJSONMessageHandler),
	}
}

// AddJSONMessageHandler adds a new listener for JSON messages.
func (a *wsJSONSpeakerEventDispatcher) AddJSONMessageHandler(h WSSpeakerJSONMessageHandler, namespaces []string) {
	a.eventHandlersLock.Lock()
	// add this handler per namespace
	for _, ns := range namespaces {
		if _, ok := a.nsEventHandlers[ns]; !ok {
			a.nsEventHandlers[ns] = []WSSpeakerJSONMessageHandler{h}
		} else {
			a.nsEventHandlers[ns] = append(a.nsEventHandlers[ns], h)
		}
	}
	a.eventHandlersLock.Unlock()
}

func (a *wsJSONSpeakerEventDispatcher) dispatchMessage(c *WSJSONSpeaker, m *WSJSONMessage) {
	// check whether it is a reply
	if c.onReply(m) {
		return
	}

	a.eventHandlersLock.RLock()
	for _, l := range a.nsEventHandlers[m.Namespace] {
		l.OnWSJSONMessage(c, m)
	}
	for _, l := range a.nsEventHandlers[WilcardNamespace] {
		l.OnWSJSONMessage(c, m)
	}
	a.eventHandlersLock.RUnlock()
}

// OnDisconnected is implemented here to avoid infinite loop since the default
// implemtation is triggering OnDisconnected too.
func (p *wsJSONSpeakerEventDispatcher) OnDisconnected(c WSSpeaker) {
}

// OnConnected is implemented here to avoid infinite loop since the default
// implemtation is triggering OnDisconnected too.
func (p *wsJSONSpeakerEventDispatcher) OnConnected(c WSSpeaker) {
}

type wsJSONSpeakerPoolEventDispatcher struct {
	dispatcher *wsJSONSpeakerEventDispatcher
	pool       WSSpeakerPool
}

// AddJSONMessageHandler adds a new listener for JSON messages.
func (d *wsJSONSpeakerPoolEventDispatcher) AddJSONMessageHandler(h WSSpeakerJSONMessageHandler, namespaces []string) {
	d.dispatcher.AddJSONMessageHandler(h, namespaces)
	for _, client := range d.pool.GetSpeakers() {
		client.(*WSJSONSpeaker).AddJSONMessageHandler(h, namespaces)
	}
}

func (d *wsJSONSpeakerPoolEventDispatcher) AddJSONSpeaker(c *WSJSONSpeaker) {
	d.dispatcher.eventHandlersLock.RLock()
	for ns, handlers := range d.dispatcher.nsEventHandlers {
		for _, handler := range handlers {
			c.AddJSONMessageHandler(handler, []string{ns})
		}
	}
	d.dispatcher.eventHandlersLock.RUnlock()
}

func newWSJSONSpeakerPoolEventDispatcher(pool WSSpeakerPool) *wsJSONSpeakerPoolEventDispatcher {
	return &wsJSONSpeakerPoolEventDispatcher{
		dispatcher: newWSJSONSpeakerEventDispatcher(),
		pool:       pool,
	}
}

// WSJSONSpeaker is a WSSpeaker able to handle JSON Message and Request/Reply calls.
type WSJSONSpeaker struct {
	WSSpeaker
	*wsJSONSpeakerEventDispatcher
	nsSubscribed   map[string]bool
	replyChanMutex sync.RWMutex
	replyChan      map[string]chan *WSJSONMessage
}

// Send sends a message according to the namespace.
func (s *WSJSONSpeaker) Send(m WSMessage) {
	if msg, ok := m.(WSJSONMessage); ok {
		if _, ok := s.nsSubscribed[msg.Namespace]; !ok {
			if _, ok := s.nsSubscribed[WilcardNamespace]; !ok {
				return
			}
		}
	}

	s.WSSpeaker.SendMessage(m)
}

func (s *WSJSONSpeaker) onReply(m *WSJSONMessage) bool {
	s.replyChanMutex.RLock()
	ch, ok := s.replyChan[m.UUID]
	if ok {
		ch <- m
	}
	s.replyChanMutex.RUnlock()

	return ok
}

// Request sends a JSON message request waiting for a reply using the given timeout.
func (s *WSJSONSpeaker) Request(m *WSJSONMessage, timeout time.Duration) (*WSJSONMessage, error) {
	ch := make(chan *WSJSONMessage, 1)

	s.replyChanMutex.Lock()
	s.replyChan[m.UUID] = ch
	s.replyChanMutex.Unlock()

	defer func() {
		s.replyChanMutex.Lock()
		delete(s.replyChan, m.UUID)
		close(ch)
		s.replyChanMutex.Unlock()
	}()

	s.Send(m)

	select {
	case resp := <-ch:
		return resp, nil
	case <-time.After(timeout):
		return nil, common.ErrTimeout
	}
}

// OnMessage checks that the WSMessage comes from a WSJSONSpeaker. It parses
// the JSON message and then dispatch the message to the proper listeners according
// to the namespace.
func (s *WSJSONSpeaker) OnMessage(c WSSpeaker, m WSMessage) {
	if c, ok := c.(*WSJSONSpeaker); ok {
		jm := &WSJSONMessage{}
		if err := json.Unmarshal(m.Bytes(), jm); err != nil {
			logging.GetLogger().Errorf("Error while decoding WSJSONMessage %s", err.Error())
			return
		}

		if jm.Type == BulkMsgType {
			var bulkMessage WSBulkMessage
			if err := json.Unmarshal([]byte(*jm.Obj), &bulkMessage); err != nil {
				for _, jm := range bulkMessage {
					s.OnMessage(c, WSRawMessage([]byte(jm)))
				}
			}
			return
		}

		s.wsJSONSpeakerEventDispatcher.dispatchMessage(c, jm)
	}
}

func newWSJSONSpeaker(c WSSpeaker) *WSJSONSpeaker {
	s := &WSJSONSpeaker{
		WSSpeaker:                    c,
		wsJSONSpeakerEventDispatcher: newWSJSONSpeakerEventDispatcher(),
		nsSubscribed:                 make(map[string]bool),
		replyChan:                    make(map[string]chan *WSJSONMessage),
	}

	// subscribing to itself so that the WSJSONSpeaker can get WSMessage and can convert them
	// to WSJSONMessage and then forward them to its own even listeners.
	s.AddEventHandler(s)
	return s
}

func (c *WSClient) UpgradeToWSJSONSpeaker() *WSJSONSpeaker {
	js := newWSJSONSpeaker(c)
	c.wsSpeaker = js
	return js
}

func (c *wsIncomingClient) upgradeToWSJSONSpeaker() *WSJSONSpeaker {
	js := newWSJSONSpeaker(c)
	c.wsSpeaker = js
	return js
}

// WSJSONSpeakerPool is the interface of a pool of WSJSONSpeakers.
type WSJSONSpeakerPool interface {
	WSSpeakerPool
	WSSpeakerJSONMessageDispatcher
	Request(host string, request *WSJSONMessage, timeout time.Duration) (*WSJSONMessage, error)
}

// WSJSONClientPool is a WSClientPool able to send WSJSONMessage.
type WSJSONClientPool struct {
	*WSClientPool
	*wsJSONSpeakerPoolEventDispatcher
}

// AddClient adds a WSClient to the pool.
func (a *WSJSONClientPool) AddClient(c WSSpeaker) error {
	if wc, ok := c.(*WSClient); ok {
		jsonSpeaker := wc.UpgradeToWSJSONSpeaker()
		a.WSClientPool.AddClient(jsonSpeaker)
		a.wsJSONSpeakerPoolEventDispatcher.AddJSONSpeaker(jsonSpeaker)
	} else {
		return errors.New("wrong client type")
	}
	return nil
}

// Request sends a Request JSON message to the WSSpeaker of the given host.
func (s *WSJSONClientPool) Request(host string, request *WSJSONMessage, timeout time.Duration) (*WSJSONMessage, error) {
	c := s.WSClientPool.GetSpeakerByHost(host)
	if c == nil {
		return nil, common.ErrNotFound
	}

	return c.(*WSJSONSpeaker).Request(request, timeout)
}

// NewWSJSONClientPool returns a new WSJSONClientPool.
func NewWSJSONClientPool(name string) *WSJSONClientPool {
	pool := NewWSClientPool(name)
	return &WSJSONClientPool{
		WSClientPool:                     pool,
		wsJSONSpeakerPoolEventDispatcher: newWSJSONSpeakerPoolEventDispatcher(pool),
	}
}

// WSJSONServer is a WSServer able to handle WSJSONSpeaker.
type WSJSONServer struct {
	*WSServer
	*wsJSONSpeakerPoolEventDispatcher
}

// Request sends a Request JSON message to the WSSpeaker of the given host.
func (s *WSJSONServer) Request(host string, request *WSJSONMessage, timeout time.Duration) (*WSJSONMessage, error) {
	c := s.WSServer.GetSpeakerByHost(host)
	if c == nil {
		return nil, common.ErrNotFound
	}

	return c.(*WSJSONSpeaker).Request(request, timeout)
}

// OnMessage websocket event.
func (s *WSJSONServer) OnMessage(c WSSpeaker, m WSMessage) {
}

// OnConnected websocket event.
func (s *WSJSONServer) OnConnected(c WSSpeaker) {
}

// OnDisconnected removes the WSSpeaker from the incomer pool.
func (s *WSJSONServer) OnDisconnected(c WSSpeaker) {
	s.WSServer.wsIncomerPool.RemoveClient(c)
}

// NewWSJSONServer returns a new WSJSONServer
func NewWSJSONServer(server *WSServer) *WSJSONServer {
	s := &WSJSONServer{
		WSServer: server,
		wsJSONSpeakerPoolEventDispatcher: newWSJSONSpeakerPoolEventDispatcher(server),
	}

	s.WSServer.wsIncomerPool.AddEventHandler(s)

	// This incomerHandler upgrades the incomers to WSJSONSpeaker thus being able to parse JSONMessage.
	// The server set also the WSJsonSpeaker with the proper namspaces it subscribes to thanks to the
	// headers.
	s.WSServer.incomerHandler = func(conn *websocket.Conn, r *auth.AuthenticatedRequest) WSSpeaker {
		// the default incomer handler creates a standard wsIncomerClient that we upgrade to a WSJSONSpeaker
		// being able to handle the JSONMessage
		c := defaultIncomerHandler(conn, r).upgradeToWSJSONSpeaker()

		// from headers
		if namespaces, ok := r.Header["X-Websocket-Namespace"]; ok {
			for _, ns := range namespaces {
				c.nsSubscribed[ns] = true
			}
		}

		// from parameter, useful for browser client
		if namespaces, ok := r.URL.Query()["x-websocket-namespace"]; ok {
			for _, ns := range namespaces {
				c.nsSubscribed[ns] = true
			}
		}

		// if empty use wilcard for backward compatibility
		if len(c.nsSubscribed) == 0 {
			c.nsSubscribed[WilcardNamespace] = true
		}

		s.wsJSONSpeakerPoolEventDispatcher.AddJSONSpeaker(c)

		return c
	}

	return s
}
<|endoftext|>


<commit_before><commit_msg>dev login link text change<commit_after><|endoftext|>


<commit_before>package integration_test

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os/exec"
	"policy-server/config"
	"policy-server/integration/helpers"
	"strings"
	"time"

	"code.cloudfoundry.org/cf-networking-helpers/db"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport/metrics"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport/ports"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

var _ = Describe("Integration", func() {

	Context("with a database", func() {
		var (
			session           *gexec.Session
			sessions          []*gexec.Session
			conf              config.Config
			dbConf            db.Config
			headers           map[string]string
			policyServerConfs []config.Config
			fakeMetron        metrics.FakeMetron
		)

		BeforeEach(func() {
			fakeMetron = metrics.NewFakeMetron()

			dbConf = testsupport.GetDBConfig()
			dbConf.DatabaseName = fmt.Sprintf("integration_test_node_%d", ports.PickAPort())

			template, _ := helpers.DefaultTestConfig(dbConf, fakeMetron.Address(), "fixtures")
			policyServerConfs = configurePolicyServers(template, 1)
			sessions = startPolicyServers(policyServerConfs)
			session = sessions[0]
			conf = policyServerConfs[0]
		})

		AfterEach(func() {
			stopPolicyServers(sessions, policyServerConfs)

			Expect(fakeMetron.Close()).To(Succeed())
		})

		Describe("boring server behavior", func() {
			It("should boot and gracefully terminate", func() {
				Consistently(session).ShouldNot(gexec.Exit())

				session.Interrupt()
				Eventually(session, helpers.DEFAULT_TIMEOUT).Should(gexec.Exit())
			})

			It("responds with uptime when accessed on the root path", func() {
				req, err := http.NewRequest("GET", fmt.Sprintf("http://%s:%d/", conf.ListenHost, conf.ListenPort), nil)
				Expect(err).NotTo(HaveOccurred())

				resp, err := http.DefaultClient.Do(req)
				Expect(err).NotTo(HaveOccurred())

				Expect(resp.StatusCode).To(Equal(http.StatusOK))
				responseString, err := ioutil.ReadAll(resp.Body)
				Expect(responseString).To(ContainSubstring("Network policy server, up for"))
			})

			It("responds with uptime when accessed on the context path", func() {
				req, err := http.NewRequest("GET", fmt.Sprintf("http://%s:%d/networking", conf.ListenHost, conf.ListenPort), nil)
				Expect(err).NotTo(HaveOccurred())

				resp, err := http.DefaultClient.Do(req)
				Expect(err).NotTo(HaveOccurred())

				Expect(resp.StatusCode).To(Equal(http.StatusOK))
				responseString, err := ioutil.ReadAll(resp.Body)
				Expect(responseString).To(ContainSubstring("Network policy server, up for"))

				Eventually(fakeMetron.AllEvents, "5s").Should(ContainElement(
					HaveName("UptimeRequestTime"),
				))
			})

			It("has a whoami endpoint", func() {
				resp := helpers.MakeAndDoRequest(
					"GET",
					fmt.Sprintf("http://%s:%d/networking/v0/external/whoami", conf.ListenHost, conf.ListenPort),
					headers,
					nil,
				)

				Expect(resp.StatusCode).To(Equal(http.StatusOK))
				responseString, err := ioutil.ReadAll(resp.Body)
				Expect(err).NotTo(HaveOccurred())
				Expect(responseString).To(ContainSubstring("some-user"))

				Eventually(fakeMetron.AllEvents, "5s").Should(ContainElement(
					HaveName("WhoAmIRequestTime"),
				))
			})

			It("has a log level thats configurable at runtime", func() {
				resp := helpers.MakeAndDoRequest(
					"GET",
					fmt.Sprintf("http://%s:%d/networking/v0/external/whoami", conf.ListenHost, conf.ListenPort),
					headers,
					nil,
				)

				Expect(resp.StatusCode).To(Equal(http.StatusOK))
				responseString, err := ioutil.ReadAll(resp.Body)
				Expect(err).NotTo(HaveOccurred())
				Expect(responseString).To(ContainSubstring("some-user"))

				Expect(session.Out).To(gbytes.Say("testprefix.policy-server"))
				Expect(session.Out).NotTo(gbytes.Say("request made to whoami endpoint"))

				_ = helpers.MakeAndDoRequest(
					"POST",
					fmt.Sprintf("http://%s:%d/log-level", conf.DebugServerHost, conf.DebugServerPort),
					headers,
					strings.NewReader("debug"),
				)

				resp = helpers.MakeAndDoRequest(
					"GET",
					fmt.Sprintf("http://%s:%d/networking/v0/external/whoami", conf.ListenHost, conf.ListenPort),
					headers,
					nil,
				)

				Expect(resp.StatusCode).To(Equal(http.StatusOK))
				responseString, err = ioutil.ReadAll(resp.Body)
				Expect(err).NotTo(HaveOccurred())
				Expect(responseString).To(ContainSubstring("some-user"))

				Expect(session.Out).To(gbytes.Say("testprefix.policy-server.request_.*serving"))
				Expect(session.Out).To(gbytes.Say("testprefix.policy-server.request_.*done"))
			})

			It("should emit some metrics", func() {
				Eventually(fakeMetron.AllEvents, "5s").Should(
					ContainElement(HaveOriginAndName("policy-server", "uptime")),
				)

				Eventually(fakeMetron.AllEvents, "5s").Should(
					ContainElement(HaveOriginAndName("policy-server", "totalPolicies")),
				)

				Eventually(fakeMetron.AllEvents, "5s").Should(
					ContainElement(HaveOriginAndName("policy-server", "DBOpenConnections")),
				)
			})
		})
	})

	Context("when connection to the database times out", func() {
		var (
			session *gexec.Session
		)

		BeforeEach(func() {
			badDbConfig := db.Config{
				Type:         "postgres",
				User:         "invalidUser",
				Password:     "badPassword",
				Host:         "badHost",
				Port:         9999,
				DatabaseName: "nonexistentDatabase",
				Timeout:      1,
			}
			conf, _ := helpers.DefaultTestConfig(badDbConfig, "some-address", "fixtures")
			configFilePath := helpers.WriteConfigFile(conf)

			policyServerCmd := exec.Command(policyServerPath, "-config-file", configFilePath)
			var err error
			session, err = gexec.Start(policyServerCmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())
		})

		AfterEach(func() {
			session.Interrupt()
			Eventually(session, helpers.DEFAULT_TIMEOUT).Should(gexec.Exit())
		})

		It("should log and exit after ~1 second", func() {
			Eventually(session, 5*time.Second).Should(gexec.Exit())
			Expect(session.Err).To(gbytes.Say("testprefix.policy-server: db connect: unable to ping: context deadline exceeded"))
		})
	})

	Describe("Config file errors", func() {
		var (
			session *gexec.Session
		)
		Context("when the config file is invalid", func() {
			BeforeEach(func() {
				badDbConfig := db.Config{
					Type:         "",
					User:         "",
					Password:     "",
					Host:         "",
					Port:         0,
					DatabaseName: "nonexistentDatabase",
					Timeout:      0,
				}
				conf, _ := helpers.DefaultTestConfig(badDbConfig, "some-address", "fixtures")
				configFilePath := helpers.WriteConfigFile(conf)

				policyServerCmd := exec.Command(policyServerPath, "-config-file", configFilePath)
				var err error
				session, err = gexec.Start(policyServerCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

			})
			It("exits and errors", func() {
				Eventually(session).Should(gexec.Exit(1))
				Expect(session.Err).To(gbytes.Say("cfnetworking.policy-server: could not read config file: invalid config: "))
			})
		})
		Context("when the config file argument is not included", func() {
			BeforeEach(func() {
				policyServerCmd := exec.Command(policyServerPath)
				var err error
				session, err = gexec.Start(policyServerCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())
			})
			It("exits and errors", func() {
				Eventually(session).Should(gexec.Exit(1))
				Expect(session.Err).To(gbytes.Say("cfnetworking.policy-server: could not read config file: reading config: open"))
			})
		})
	})
})
<commit_msg>db connection integration test waits until connecting to db<commit_after>package integration_test

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os/exec"
	"policy-server/config"
	"policy-server/integration/helpers"
	"strings"
	"time"

	"code.cloudfoundry.org/cf-networking-helpers/db"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport/metrics"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport/ports"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

var _ = Describe("Integration", func() {

	Context("with a database", func() {
		var (
			session           *gexec.Session
			sessions          []*gexec.Session
			conf              config.Config
			dbConf            db.Config
			headers           map[string]string
			policyServerConfs []config.Config
			fakeMetron        metrics.FakeMetron
		)

		BeforeEach(func() {
			fakeMetron = metrics.NewFakeMetron()

			dbConf = testsupport.GetDBConfig()
			dbConf.DatabaseName = fmt.Sprintf("integration_test_node_%d", ports.PickAPort())

			template, _ := helpers.DefaultTestConfig(dbConf, fakeMetron.Address(), "fixtures")
			policyServerConfs = configurePolicyServers(template, 1)
			sessions = startPolicyServers(policyServerConfs)
			session = sessions[0]
			conf = policyServerConfs[0]
		})

		AfterEach(func() {
			stopPolicyServers(sessions, policyServerConfs)

			Expect(fakeMetron.Close()).To(Succeed())
		})

		Describe("boring server behavior", func() {
			It("should boot and gracefully terminate", func() {
				Consistently(session).ShouldNot(gexec.Exit())

				session.Interrupt()
				Eventually(session, helpers.DEFAULT_TIMEOUT).Should(gexec.Exit())
			})

			It("responds with uptime when accessed on the root path", func() {
				req, err := http.NewRequest("GET", fmt.Sprintf("http://%s:%d/", conf.ListenHost, conf.ListenPort), nil)
				Expect(err).NotTo(HaveOccurred())

				resp, err := http.DefaultClient.Do(req)
				Expect(err).NotTo(HaveOccurred())

				Expect(resp.StatusCode).To(Equal(http.StatusOK))
				responseString, err := ioutil.ReadAll(resp.Body)
				Expect(responseString).To(ContainSubstring("Network policy server, up for"))
			})

			It("responds with uptime when accessed on the context path", func() {
				req, err := http.NewRequest("GET", fmt.Sprintf("http://%s:%d/networking", conf.ListenHost, conf.ListenPort), nil)
				Expect(err).NotTo(HaveOccurred())

				resp, err := http.DefaultClient.Do(req)
				Expect(err).NotTo(HaveOccurred())

				Expect(resp.StatusCode).To(Equal(http.StatusOK))
				responseString, err := ioutil.ReadAll(resp.Body)
				Expect(responseString).To(ContainSubstring("Network policy server, up for"))

				Eventually(fakeMetron.AllEvents, "5s").Should(ContainElement(
					HaveName("UptimeRequestTime"),
				))
			})

			It("has a whoami endpoint", func() {
				resp := helpers.MakeAndDoRequest(
					"GET",
					fmt.Sprintf("http://%s:%d/networking/v0/external/whoami", conf.ListenHost, conf.ListenPort),
					headers,
					nil,
				)

				Expect(resp.StatusCode).To(Equal(http.StatusOK))
				responseString, err := ioutil.ReadAll(resp.Body)
				Expect(err).NotTo(HaveOccurred())
				Expect(responseString).To(ContainSubstring("some-user"))

				Eventually(fakeMetron.AllEvents, "5s").Should(ContainElement(
					HaveName("WhoAmIRequestTime"),
				))
			})

			It("has a log level thats configurable at runtime", func() {
				resp := helpers.MakeAndDoRequest(
					"GET",
					fmt.Sprintf("http://%s:%d/networking/v0/external/whoami", conf.ListenHost, conf.ListenPort),
					headers,
					nil,
				)

				Expect(resp.StatusCode).To(Equal(http.StatusOK))
				responseString, err := ioutil.ReadAll(resp.Body)
				Expect(err).NotTo(HaveOccurred())
				Expect(responseString).To(ContainSubstring("some-user"))

				Expect(session.Out).To(gbytes.Say("testprefix.policy-server"))
				Expect(session.Out).NotTo(gbytes.Say("request made to whoami endpoint"))

				_ = helpers.MakeAndDoRequest(
					"POST",
					fmt.Sprintf("http://%s:%d/log-level", conf.DebugServerHost, conf.DebugServerPort),
					headers,
					strings.NewReader("debug"),
				)

				resp = helpers.MakeAndDoRequest(
					"GET",
					fmt.Sprintf("http://%s:%d/networking/v0/external/whoami", conf.ListenHost, conf.ListenPort),
					headers,
					nil,
				)

				Expect(resp.StatusCode).To(Equal(http.StatusOK))
				responseString, err = ioutil.ReadAll(resp.Body)
				Expect(err).NotTo(HaveOccurred())
				Expect(responseString).To(ContainSubstring("some-user"))

				Expect(session.Out).To(gbytes.Say("testprefix.policy-server.request_.*serving"))
				Expect(session.Out).To(gbytes.Say("testprefix.policy-server.request_.*done"))
			})

			It("should emit some metrics", func() {
				Eventually(fakeMetron.AllEvents, "5s").Should(
					ContainElement(HaveOriginAndName("policy-server", "uptime")),
				)

				Eventually(fakeMetron.AllEvents, "5s").Should(
					ContainElement(HaveOriginAndName("policy-server", "totalPolicies")),
				)

				Eventually(fakeMetron.AllEvents, "5s").Should(
					ContainElement(HaveOriginAndName("policy-server", "DBOpenConnections")),
				)
			})
		})
	})

	Context("when connection to the database times out", func() {
		var (
			session *gexec.Session
		)

		BeforeEach(func() {
			badDbConfig := db.Config{
				Type:         "postgres",
				User:         "invalidUser",
				Password:     "badPassword",
				Host:         "badHost",
				Port:         9999,
				DatabaseName: "nonexistentDatabase",
				Timeout:      1,
			}
			conf, _ := helpers.DefaultTestConfig(badDbConfig, "some-address", "fixtures")
			configFilePath := helpers.WriteConfigFile(conf)

			policyServerCmd := exec.Command(policyServerPath, "-config-file", configFilePath)
			var err error
			session, err = gexec.Start(policyServerCmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())
			Eventually(session.Out).Should(gbytes.Say("getting db connection"))
		})

		AfterEach(func() {
			session.Interrupt()
			Eventually(session, helpers.DEFAULT_TIMEOUT).Should(gexec.Exit())
		})

		It("should log and exit with a timeout error", func() {
			Eventually(session, 5*time.Second).Should(gexec.Exit())
			Expect(session.Err).To(gbytes.Say("testprefix.policy-server: db connect: unable to ping: context deadline exceeded"))
		})
	})

	Describe("Config file errors", func() {
		var (
			session *gexec.Session
		)
		Context("when the config file is invalid", func() {
			BeforeEach(func() {
				badDbConfig := db.Config{
					Type:         "",
					User:         "",
					Password:     "",
					Host:         "",
					Port:         0,
					DatabaseName: "nonexistentDatabase",
					Timeout:      0,
				}
				conf, _ := helpers.DefaultTestConfig(badDbConfig, "some-address", "fixtures")
				configFilePath := helpers.WriteConfigFile(conf)

				policyServerCmd := exec.Command(policyServerPath, "-config-file", configFilePath)
				var err error
				session, err = gexec.Start(policyServerCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

			})
			It("exits and errors", func() {
				Eventually(session).Should(gexec.Exit(1))
				Expect(session.Err).To(gbytes.Say("cfnetworking.policy-server: could not read config file: invalid config: "))
			})
		})
		Context("when the config file argument is not included", func() {
			BeforeEach(func() {
				policyServerCmd := exec.Command(policyServerPath)
				var err error
				session, err = gexec.Start(policyServerCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())
			})
			It("exits and errors", func() {
				Eventually(session).Should(gexec.Exit(1))
				Expect(session.Err).To(gbytes.Say("cfnetworking.policy-server: could not read config file: reading config: open"))
			})
		})
	})
})
<|endoftext|>


<commit_before>package proto

import (
	"bufio"
	"firempq/common"
	"firempq/facade"
	"firempq/svcerr"
	"firempq/util"
	"github.com/op/go-logging"
	"io"
	"strings"
	"unicode"
)

var log = logging.MustGetLogger("firempq")

const (
	ENDL          = "\n"
	ENDL_BYTE     = '\n'
	SIMPLE_SERVER = "simple"
)

type FuncHandler func([]string) (common.IResponse, error)

type SessionHandler struct {
	conn         *bufio.ReadWriter
	mainHandlers map[string]FuncHandler
	active       bool
	ctx          common.ISvc
	svcs         *facade.ServiceFacade
}

func NewSessionHandler(conn *bufio.ReadWriter, services *facade.ServiceFacade) *SessionHandler {
	handlerMap := map[string]FuncHandler{
		CMD_PING:    pingHandler,
		CMD_UNIX_TS: tsHandler,
	}
	sh := &SessionHandler{
		conn:         conn,
		mainHandlers: handlerMap,
		ctx:          nil,
		active:       true,
		svcs:         services,
	}
	sh.mainHandlers[CMD_QUIT] = sh.quitHandler
	sh.mainHandlers[CMD_SETCTX] = sh.setCtxHandler
	return sh
}

func (s *SessionHandler) DispatchConn() {
	s.writeResponse(common.NewStrResponse("HELLO FIREMPQ-0.1"))
	for s.active {
		cmdTokens, err := s.readCommand()
		if err != nil {
			if err == io.EOF {
				log.Info("Client disconnected")
				break
			}
			log.Error(err.Error())
		}
		err = s.processCmdTokens(cmdTokens)
	}
	s.conn.Flush()
}

func (s *SessionHandler) readCommand() ([]string, error) {
	data, err := s.conn.ReadString(ENDL_BYTE)

	if err != nil {
		return nil, err
	}

	data = strings.TrimRightFunc(data, unicode.IsSpace)
	splits := strings.Split(data, " ")

	var tokens []string
	for _, s := range splits {
		if len(s) > 0 {
			tokens = append(tokens, s)
		}
	}

	return tokens, nil
}

func (s *SessionHandler) processCmdTokens(cmdTokens []string) error {
	var resp common.IResponse
	var err error
	cmd := cmdTokens[0]
	tokens := cmdTokens[1:]
	handler, ok := s.mainHandlers[cmd]
	if !ok {
		resp = svcerr.ERR_UNKNOW_CMD
	} else {
		resp, err = handler(tokens)
	}
	if err != nil {
		return err
	}
	return s.writeResponse(resp)
}

func (s *SessionHandler) writeResponse(resp common.IResponse) error {

	if _, err := s.conn.WriteString(resp.GetResponse()); err != nil {
		return err
	}
	if _, err := s.conn.WriteString("\n"); err != nil {
		return err
	}
	s.conn.Flush()
	return nil
}

func (s *SessionHandler) setCtxHandler(tokens []string) (common.IResponse, error) {
	if len(tokens) > 1 {
		return svcerr.InvalidRequest("SETCTX accept only service name to be provided"), nil
	}
	if len(tokens) == 0 {
		return svcerr.InvalidRequest("Service name must be provided"), nil
	}
	svcName := tokens[0]
	svc, exists := s.svcs.GetService(svcName)
	if !exists {
		return svcerr.ERR_NO_SVC, nil
	}
	s.ctx = svc
	return common.NewStrResponse("OK"), nil
}

func (s *SessionHandler) quitHandler(tokens []string) (common.IResponse, error) {
	if len(tokens) > 0 {
		return svcerr.ERR_CMD_WITH_NO_PARAMS, nil
	}
	s.active = false
	return common.NewStrResponse("OK"), nil
}

func pingHandler(tokens []string) (common.IResponse, error) {
	if len(tokens) > 0 {
		return svcerr.ERR_CMD_WITH_NO_PARAMS, nil
	}
	return common.NewStrResponse("PONG"), nil
}

func tsHandler(tokens []string) (common.IResponse, error) {
	if len(tokens) > 0 {
		return svcerr.ERR_CMD_WITH_NO_PARAMS, nil
	}
	return common.NewIntResponse(util.Uts()), nil
}
<commit_msg>Added command processing to create, delete and list available services.<commit_after>package proto

import (
	"bufio"
	"firempq/common"
	"firempq/facade"
	"firempq/svcerr"
	"firempq/util"
	"github.com/op/go-logging"
	"io"
	"strings"
	"unicode"
)

var log = logging.MustGetLogger("firempq")

const (
	ENDL          = "\n"
	ENDL_BYTE     = '\n'
	SIMPLE_SERVER = "simple"
)

type FuncHandler func([]string) (common.IResponse, error)

type SessionHandler struct {
	conn         *bufio.ReadWriter
	mainHandlers map[string]FuncHandler
	active       bool
	ctx          common.ISvc
	svcs         *facade.ServiceFacade
}

func NewSessionHandler(conn *bufio.ReadWriter, services *facade.ServiceFacade) *SessionHandler {
	handlerMap := map[string]FuncHandler{
		CMD_PING:    pingHandler,
		CMD_UNIX_TS: tsHandler,
	}
	sh := &SessionHandler{
		conn:         conn,
		mainHandlers: handlerMap,
		ctx:          nil,
		active:       true,
		svcs:         services,
	}
	sh.mainHandlers[CMD_QUIT] = sh.quitHandler
	sh.mainHandlers[CMD_SETCTX] = sh.setCtxHandler
	sh.mainHandlers[CMD_CREATE_SVC] = sh.createServiceHandler
	sh.mainHandlers[CMD_DROP_SVC] = sh.dropServiceHandler
	sh.mainHandlers[CMD_LIST] = sh.listServicesHandler
	return sh
}

func (s *SessionHandler) DispatchConn() {
	s.writeResponse(common.NewStrResponse("HELLO FIREMPQ-0.1"))
	for s.active {
		cmdTokens, err := s.readCommand()
		if err != nil {
			if err == io.EOF {
				log.Info("Client disconnected")
				break
			}
			log.Error(err.Error())
		}
		err = s.processCmdTokens(cmdTokens)
	}
	s.conn.Flush()
}

func (s *SessionHandler) readCommand() ([]string, error) {
	data, err := s.conn.ReadString(ENDL_BYTE)

	if err != nil {
		return nil, err
	}

	data = strings.TrimRightFunc(data, unicode.IsSpace)
	splits := strings.Split(data, " ")

	var tokens []string
	for _, s := range splits {
		if len(s) > 0 {
			tokens = append(tokens, s)
		}
	}

	return tokens, nil
}

func (s *SessionHandler) processCmdTokens(cmdTokens []string) error {
	var resp common.IResponse
	var err error
	if len(cmdTokens) == 0 {
		return s.writeResponse(common.RESP_OK)
	}
	cmd := cmdTokens[0]
	tokens := cmdTokens[1:]
	handler, ok := s.mainHandlers[cmd]
	if !ok {
		resp = svcerr.ERR_UNKNOW_CMD
	} else {
		resp, err = handler(tokens)
	}
	if err != nil {
		return err
	}
	return s.writeResponse(resp)
}

func (s *SessionHandler) writeResponse(resp common.IResponse) error {

	if _, err := s.conn.WriteString(resp.GetResponse()); err != nil {
		return err
	}
	if _, err := s.conn.WriteString("\n"); err != nil {
		return err
	}
	s.conn.Flush()
	return nil
}

func (s *SessionHandler) createServiceHandler(tokens []string) (common.IResponse, error) {
	if len(tokens) < 2 {
		return svcerr.InvalidRequest("At least service type and name should be provided"), nil
	}
	svcName := tokens[0]
	svcType := tokens[1]

	_, exists := s.svcs.GetService(svcName)
	if exists {
		return svcerr.ConflictRequest("Service exists already"), nil
	}

	resp := s.svcs.CreateService(svcType, svcName, make(map[string]string))
	return common.TranslateError(resp), nil
}

func (s *SessionHandler) dropServiceHandler(tokens []string) (common.IResponse, error) {
	if len(tokens) == 0 {
		return svcerr.InvalidRequest("Service name must be provided"), nil
	}
	if len(tokens) > 1 {
		return svcerr.InvalidRequest("DROP accept service name only"), nil
	}
	svcName := tokens[0]
	res := s.svcs.DropService(svcName)
	return common.TranslateError(res), nil
}

func (s *SessionHandler) setCtxHandler(tokens []string) (common.IResponse, error) {
	if len(tokens) > 1 {
		return svcerr.InvalidRequest("SETCTX accept service name only"), nil
	}
	if len(tokens) == 0 {
		return svcerr.InvalidRequest("Service name must be provided"), nil
	}
	svcName := tokens[0]
	svc, exists := s.svcs.GetService(svcName)
	if !exists {
		return svcerr.ERR_NO_SVC, nil
	}
	s.ctx = svc
	return common.RESP_OK, nil
}

func (s *SessionHandler) quitHandler(tokens []string) (common.IResponse, error) {
	if len(tokens) > 0 {
		return svcerr.ERR_CMD_WITH_NO_PARAMS, nil
	}
	s.active = false
	return common.RESP_OK, nil
}

func (s *SessionHandler) listServicesHandler(tokens []string) (common.IResponse, error) {
	svcPrefix := ""
	svcType := ""
	if len(tokens) == 1 {
		svcPrefix = tokens[0]
	} else if len(tokens) == 2 {
		svcType = tokens[1]
	} else if len(tokens) > 2 {
		return svcerr.InvalidRequest("LIST accept service name prefix and service type only"), nil
	}

	list, err := s.svcs.ListServices(svcPrefix, svcType)
	if err != nil {
		return common.TranslateError(err), nil
	}
	resp := common.NewStrArrayResponse(list)
	return resp, nil
}

func pingHandler(tokens []string) (common.IResponse, error) {
	if len(tokens) > 0 {
		return svcerr.ERR_CMD_WITH_NO_PARAMS, nil
	}
	return common.RESP_PONG, nil
}

func tsHandler(tokens []string) (common.IResponse, error) {
	if len(tokens) > 0 {
		return svcerr.ERR_CMD_WITH_NO_PARAMS, nil
	}
	return common.NewIntResponse(util.Uts()), nil
}
<|endoftext|>