<commit_before>package chunk

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"hash/crc32"
	"io"
	"strconv"
	"strings"

	"github.com/golang/snappy"
	"github.com/pkg/errors"
	"github.com/prometheus/common/model"
	prom_chunk "github.com/weaveworks/cortex/pkg/prom1/storage/local/chunk"

	errs "github.com/weaveworks/common/errors"
	"github.com/weaveworks/cortex/pkg/util"
)

// Errors that decode can return
const (
	ErrInvalidChunkID  = errs.Error("invalid chunk ID")
	ErrInvalidChecksum = errs.Error("invalid chunk checksum")
	ErrWrongMetadata   = errs.Error("wrong chunk metadata")
)

var castagnoliTable = crc32.MakeTable(crc32.Castagnoli)

// Chunk contains encoded timeseries data
type Chunk struct {
	// These two fields will be missing from older chunks (as will the hash).
	// On fetch we will initialise these fields from the DynamoDB key.
	Fingerprint model.Fingerprint `json:"fingerprint"`
	UserID      string            `json:"userID"`

	// These fields will be in all chunks, including old ones.
	From    model.Time   `json:"from"`
	Through model.Time   `json:"through"`
	Metric  model.Metric `json:"metric"`

	// The hash is not written to the external storage either.  We use
	// crc32, Castagnoli table.  See http://www.evanjones.ca/crc32c.html.
	// For old chunks, ChecksumSet will be false.
	ChecksumSet bool   `json:"-"`
	Checksum    uint32 `json:"-"`

	// We never use Delta encoding (the zero value), so if this entry is
	// missing, we default to DoubleDelta.
	Encoding prom_chunk.Encoding `json:"encoding"`
	Data     prom_chunk.Chunk    `json:"-"`

	// This flag is used for very old chunks, where the metadata is read out
	// of the index.
	metadataInIndex bool
}

// NewChunk creates a new chunk
func NewChunk(userID string, fp model.Fingerprint, metric model.Metric, c prom_chunk.Chunk, from, through model.Time) Chunk {
	return Chunk{
		Fingerprint: fp,
		UserID:      userID,
		From:        from,
		Through:     through,
		Metric:      metric,
		Encoding:    c.Encoding(),
		Data:        c,
	}
}

// parseExternalKey is used to construct a partially-populated chunk from the
// key in DynamoDB.  This chunk can then be used to calculate the key needed
// to fetch the Chunk data from Memcache/S3, and then fully populate the chunk
// with decode().
//
// Pre-checksums, the keys written to DynamoDB looked like
// `<fingerprint>:<start time>:<end time>` (aka the ID), and the key for
// memcache and S3 was `<user id>/<fingerprint>:<start time>:<end time>.
// Finger prints and times were written in base-10.
//
// Post-checksums, externals keys become the same across DynamoDB, Memcache
// and S3.  Numbers become hex encoded.  Keys look like:
// `<user id>/<fingerprint>:<start time>:<end time>:<checksum>`.
func parseExternalKey(userID, externalKey string) (Chunk, error) {
	if !strings.Contains(externalKey, "/") {
		return parseLegacyChunkID(userID, externalKey)
	}
	chunk, err := parseNewExternalKey(externalKey)
	if err != nil {
		return Chunk{}, err
	}
	if chunk.UserID != userID {
		return Chunk{}, errors.WithStack(ErrWrongMetadata)
	}
	return chunk, nil
}

func parseLegacyChunkID(userID, key string) (Chunk, error) {
	parts := strings.Split(key, ":")
	if len(parts) != 3 {
		return Chunk{}, errors.WithStack(ErrInvalidChunkID)
	}
	fingerprint, err := strconv.ParseUint(parts[0], 10, 64)
	if err != nil {
		return Chunk{}, err
	}
	from, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		return Chunk{}, err
	}
	through, err := strconv.ParseInt(parts[2], 10, 64)
	if err != nil {
		return Chunk{}, err
	}
	return Chunk{
		UserID:      userID,
		Fingerprint: model.Fingerprint(fingerprint),
		From:        model.Time(from),
		Through:     model.Time(through),
	}, nil
}

func parseNewExternalKey(key string) (Chunk, error) {
	parts := strings.Split(key, "/")
	if len(parts) != 2 {
		return Chunk{}, errors.WithStack(ErrInvalidChunkID)
	}
	userID := parts[0]
	hexParts := strings.Split(parts[1], ":")
	if len(hexParts) != 4 {
		return Chunk{}, errors.WithStack(ErrInvalidChunkID)
	}
	fingerprint, err := strconv.ParseUint(hexParts[0], 16, 64)
	if err != nil {
		return Chunk{}, err
	}
	from, err := strconv.ParseInt(hexParts[1], 16, 64)
	if err != nil {
		return Chunk{}, err
	}
	through, err := strconv.ParseInt(hexParts[2], 16, 64)
	if err != nil {
		return Chunk{}, err
	}
	checksum, err := strconv.ParseUint(hexParts[3], 16, 32)
	if err != nil {
		return Chunk{}, err
	}
	return Chunk{
		UserID:      userID,
		Fingerprint: model.Fingerprint(fingerprint),
		From:        model.Time(from),
		Through:     model.Time(through),
		Checksum:    uint32(checksum),
		ChecksumSet: true,
	}, nil
}

// ExternalKey returns the key you can use to fetch this chunk from external
// storage. For newer chunks, this key includes a checksum.
func (c *Chunk) ExternalKey() string {
	// Some chunks have a checksum stored in dynamodb, some do not.  We must
	// generate keys appropriately.
	if c.ChecksumSet {
		// This is the inverse of parseNewExternalKey.
		return fmt.Sprintf("%s/%x:%x:%x:%x", c.UserID, uint64(c.Fingerprint), int64(c.From), int64(c.Through), c.Checksum)
	}
	// This is the inverse of parseLegacyExternalKey, with "<user id>/" prepended.
	// Legacy chunks had the user ID prefix on s3/memcache, but not in DynamoDB.
	// See comment on parseExternalKey.
	return fmt.Sprintf("%s/%d:%d:%d", c.UserID, uint64(c.Fingerprint), int64(c.From), int64(c.Through))
}

// Encode writes the chunk out to a big write buffer, then calculates the checksum.
func (c *Chunk) Encode() ([]byte, error) {
	var buf bytes.Buffer

	// Write 4 empty bytes first - we will come back and put the len in here.
	metadataLenBytes := [4]byte{}
	if _, err := buf.Write(metadataLenBytes[:]); err != nil {
		return nil, err
	}

	// Encode chunk metadata into snappy-compressed buffer
	if err := json.NewEncoder(snappy.NewWriter(&buf)).Encode(c); err != nil {
		return nil, err
	}

	// Write the metadata length back at the start of the buffer.
	binary.BigEndian.PutUint32(metadataLenBytes[:], uint32(buf.Len()))
	copy(buf.Bytes(), metadataLenBytes[:])

	// Write the data length
	dataLenBytes := [4]byte{}
	binary.BigEndian.PutUint32(dataLenBytes[:], uint32(prom_chunk.ChunkLen))
	if _, err := buf.Write(dataLenBytes[:]); err != nil {
		return nil, err
	}

	// And now the chunk data
	if err := c.Data.Marshal(&buf); err != nil {
		return nil, err
	}

	// Now work out the checksum
	output := buf.Bytes()
	c.ChecksumSet = true
	c.Checksum = crc32.Checksum(output, castagnoliTable)
	return output, nil
}

// DecodeContext holds data that can be re-used between decodes of different chunks
type DecodeContext struct {
	reader *snappy.Reader
}

// NewDecodeContext creates a new, blank, DecodeContext
func NewDecodeContext() *DecodeContext {
	return &DecodeContext{
		reader: snappy.NewReader(nil),
	}
}

// Decode the chunk from the given buffer, and confirm the chunk is the one we
// expected.
func (c *Chunk) Decode(decodeContext *DecodeContext, input []byte) error {
	// Legacy chunks were written with metadata in the index.
	if c.metadataInIndex {
		var err error
		c.Data, err = prom_chunk.NewForEncoding(prom_chunk.DoubleDelta)
		if err != nil {
			return err
		}
		return c.Data.UnmarshalFromBuf(input)
	}

	// First, calculate the checksum of the chunk and confirm it matches
	// what we expected.
	if c.ChecksumSet && c.Checksum != crc32.Checksum(input, castagnoliTable) {
		return errors.WithStack(ErrInvalidChecksum)
	}

	// Now unmarshal the chunk metadata.
	r := bytes.NewReader(input)
	var metadataLen uint32
	if err := binary.Read(r, binary.BigEndian, &metadataLen); err != nil {
		return err
	}
	var tempMetadata Chunk
	decodeContext.reader.Reset(&io.LimitedReader{
		N: int64(metadataLen),
		R: r,
	})
	err := json.NewDecoder(decodeContext.reader).Decode(&tempMetadata)
	if err != nil {
		return err
	}

	// Next, confirm the chunks matches what we expected.  Easiest way to do this
	// is to compare what the decoded data thinks its external ID would be, but
	// we don't write the checksum to s3, so we have to copy the checksum in.
	if c.ChecksumSet {
		tempMetadata.Checksum, tempMetadata.ChecksumSet = c.Checksum, c.ChecksumSet
		if c.ExternalKey() != tempMetadata.ExternalKey() {
			return errors.WithStack(ErrWrongMetadata)
		}
	}
	*c = tempMetadata

	// Flag indicates if metadata was written to index, and if false implies
	// we should read a header of the chunk containing the metadata.  Exists
	// for backwards compatibility with older chunks, which did not have header.
	if c.Encoding == prom_chunk.Delta {
		c.Encoding = prom_chunk.DoubleDelta
	}

	// Finally, unmarshal the actual chunk data.
	c.Data, err = prom_chunk.NewForEncoding(c.Encoding)
	if err != nil {
		return err
	}

	var dataLen uint32
	if err := binary.Read(r, binary.BigEndian, &dataLen); err != nil {
		return err
	}

	return c.Data.Unmarshal(&io.LimitedReader{
		N: int64(dataLen),
		R: r,
	})
}

func chunksToMatrix(chunks []Chunk) (model.Matrix, error) {
	// Group chunks by series, sort and dedupe samples.
	sampleStreams := map[model.Fingerprint]*model.SampleStream{}
	for _, c := range chunks {
		fp := c.Metric.Fingerprint()
		ss, ok := sampleStreams[fp]
		if !ok {
			ss = &model.SampleStream{
				Metric: c.Metric,
			}
			sampleStreams[fp] = ss
		}

		samples, err := c.Samples()
		if err != nil {
			return nil, err
		}

		ss.Values = util.MergeSampleSets(ss.Values, samples)
	}

	matrix := make(model.Matrix, 0, len(sampleStreams))
	for _, ss := range sampleStreams {
		matrix = append(matrix, &model.SampleStream{
			Metric: ss.Metric,
			Values: ss.Values,
		})
	}

	return matrix, nil
}

// Samples returns all SamplePairs for the chunk.
func (c *Chunk) Samples() ([]model.SamplePair, error) {
	it := c.Data.NewIterator()
	// TODO(juliusv): Pre-allocate this with the right length again once we
	// add a method upstream to get the number of samples in a chunk.
	var samples []model.SamplePair
	for it.Scan() {
		samples = append(samples, it.Value())
	}
	return samples, nil
}
<commit_msg>Re-use Metric when decoding chunks with the same Fingerprint (#634)<commit_after>package chunk

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"hash/crc32"
	"io"
	"strconv"
	"strings"

	"github.com/golang/snappy"
	"github.com/pkg/errors"
	"github.com/prometheus/common/model"
	prom_chunk "github.com/weaveworks/cortex/pkg/prom1/storage/local/chunk"

	errs "github.com/weaveworks/common/errors"
	"github.com/weaveworks/cortex/pkg/util"
)

// Errors that decode can return
const (
	ErrInvalidChunkID  = errs.Error("invalid chunk ID")
	ErrInvalidChecksum = errs.Error("invalid chunk checksum")
	ErrWrongMetadata   = errs.Error("wrong chunk metadata")
)

var castagnoliTable = crc32.MakeTable(crc32.Castagnoli)

// Chunk contains encoded timeseries data
type Chunk struct {
	// These two fields will be missing from older chunks (as will the hash).
	// On fetch we will initialise these fields from the DynamoDB key.
	Fingerprint model.Fingerprint `json:"fingerprint"`
	UserID      string            `json:"userID"`

	// These fields will be in all chunks, including old ones.
	From    model.Time   `json:"from"`
	Through model.Time   `json:"through"`
	Metric  model.Metric `json:"metric"`

	// The hash is not written to the external storage either.  We use
	// crc32, Castagnoli table.  See http://www.evanjones.ca/crc32c.html.
	// For old chunks, ChecksumSet will be false.
	ChecksumSet bool   `json:"-"`
	Checksum    uint32 `json:"-"`

	// We never use Delta encoding (the zero value), so if this entry is
	// missing, we default to DoubleDelta.
	Encoding prom_chunk.Encoding `json:"encoding"`
	Data     prom_chunk.Chunk    `json:"-"`

	// This flag is used for very old chunks, where the metadata is read out
	// of the index.
	metadataInIndex bool
}

// NewChunk creates a new chunk
func NewChunk(userID string, fp model.Fingerprint, metric model.Metric, c prom_chunk.Chunk, from, through model.Time) Chunk {
	return Chunk{
		Fingerprint: fp,
		UserID:      userID,
		From:        from,
		Through:     through,
		Metric:      metric,
		Encoding:    c.Encoding(),
		Data:        c,
	}
}

// parseExternalKey is used to construct a partially-populated chunk from the
// key in DynamoDB.  This chunk can then be used to calculate the key needed
// to fetch the Chunk data from Memcache/S3, and then fully populate the chunk
// with decode().
//
// Pre-checksums, the keys written to DynamoDB looked like
// `<fingerprint>:<start time>:<end time>` (aka the ID), and the key for
// memcache and S3 was `<user id>/<fingerprint>:<start time>:<end time>.
// Finger prints and times were written in base-10.
//
// Post-checksums, externals keys become the same across DynamoDB, Memcache
// and S3.  Numbers become hex encoded.  Keys look like:
// `<user id>/<fingerprint>:<start time>:<end time>:<checksum>`.
func parseExternalKey(userID, externalKey string) (Chunk, error) {
	if !strings.Contains(externalKey, "/") {
		return parseLegacyChunkID(userID, externalKey)
	}
	chunk, err := parseNewExternalKey(externalKey)
	if err != nil {
		return Chunk{}, err
	}
	if chunk.UserID != userID {
		return Chunk{}, errors.WithStack(ErrWrongMetadata)
	}
	return chunk, nil
}

func parseLegacyChunkID(userID, key string) (Chunk, error) {
	parts := strings.Split(key, ":")
	if len(parts) != 3 {
		return Chunk{}, errors.WithStack(ErrInvalidChunkID)
	}
	fingerprint, err := strconv.ParseUint(parts[0], 10, 64)
	if err != nil {
		return Chunk{}, err
	}
	from, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		return Chunk{}, err
	}
	through, err := strconv.ParseInt(parts[2], 10, 64)
	if err != nil {
		return Chunk{}, err
	}
	return Chunk{
		UserID:      userID,
		Fingerprint: model.Fingerprint(fingerprint),
		From:        model.Time(from),
		Through:     model.Time(through),
	}, nil
}

func parseNewExternalKey(key string) (Chunk, error) {
	parts := strings.Split(key, "/")
	if len(parts) != 2 {
		return Chunk{}, errors.WithStack(ErrInvalidChunkID)
	}
	userID := parts[0]
	hexParts := strings.Split(parts[1], ":")
	if len(hexParts) != 4 {
		return Chunk{}, errors.WithStack(ErrInvalidChunkID)
	}
	fingerprint, err := strconv.ParseUint(hexParts[0], 16, 64)
	if err != nil {
		return Chunk{}, err
	}
	from, err := strconv.ParseInt(hexParts[1], 16, 64)
	if err != nil {
		return Chunk{}, err
	}
	through, err := strconv.ParseInt(hexParts[2], 16, 64)
	if err != nil {
		return Chunk{}, err
	}
	checksum, err := strconv.ParseUint(hexParts[3], 16, 32)
	if err != nil {
		return Chunk{}, err
	}
	return Chunk{
		UserID:      userID,
		Fingerprint: model.Fingerprint(fingerprint),
		From:        model.Time(from),
		Through:     model.Time(through),
		Checksum:    uint32(checksum),
		ChecksumSet: true,
	}, nil
}

// ExternalKey returns the key you can use to fetch this chunk from external
// storage. For newer chunks, this key includes a checksum.
func (c *Chunk) ExternalKey() string {
	// Some chunks have a checksum stored in dynamodb, some do not.  We must
	// generate keys appropriately.
	if c.ChecksumSet {
		// This is the inverse of parseNewExternalKey.
		return fmt.Sprintf("%s/%x:%x:%x:%x", c.UserID, uint64(c.Fingerprint), int64(c.From), int64(c.Through), c.Checksum)
	}
	// This is the inverse of parseLegacyExternalKey, with "<user id>/" prepended.
	// Legacy chunks had the user ID prefix on s3/memcache, but not in DynamoDB.
	// See comment on parseExternalKey.
	return fmt.Sprintf("%s/%d:%d:%d", c.UserID, uint64(c.Fingerprint), int64(c.From), int64(c.Through))
}

// Encode writes the chunk out to a big write buffer, then calculates the checksum.
func (c *Chunk) Encode() ([]byte, error) {
	var buf bytes.Buffer

	// Write 4 empty bytes first - we will come back and put the len in here.
	metadataLenBytes := [4]byte{}
	if _, err := buf.Write(metadataLenBytes[:]); err != nil {
		return nil, err
	}

	// Encode chunk metadata into snappy-compressed buffer
	if err := json.NewEncoder(snappy.NewWriter(&buf)).Encode(c); err != nil {
		return nil, err
	}

	// Write the metadata length back at the start of the buffer.
	binary.BigEndian.PutUint32(metadataLenBytes[:], uint32(buf.Len()))
	copy(buf.Bytes(), metadataLenBytes[:])

	// Write the data length
	dataLenBytes := [4]byte{}
	binary.BigEndian.PutUint32(dataLenBytes[:], uint32(prom_chunk.ChunkLen))
	if _, err := buf.Write(dataLenBytes[:]); err != nil {
		return nil, err
	}

	// And now the chunk data
	if err := c.Data.Marshal(&buf); err != nil {
		return nil, err
	}

	// Now work out the checksum
	output := buf.Bytes()
	c.ChecksumSet = true
	c.Checksum = crc32.Checksum(output, castagnoliTable)
	return output, nil
}

// DecodeContext holds data that can be re-used between decodes of different chunks
type DecodeContext struct {
	reader  *snappy.Reader
	metrics map[model.Fingerprint]model.Metric
}

// NewDecodeContext creates a new, blank, DecodeContext
func NewDecodeContext() *DecodeContext {
	return &DecodeContext{
		reader:  snappy.NewReader(nil),
		metrics: make(map[model.Fingerprint]model.Metric),
	}
}

// If we have decoded a chunk with the same fingerprint before, re-use its Metric, otherwise parse it
func (dc *DecodeContext) metric(fingerprint model.Fingerprint, buf []byte) (model.Metric, error) {
	metric, found := dc.metrics[fingerprint]
	if !found {
		err := json.NewDecoder(bytes.NewReader(buf)).Decode(&metric)
		if err != nil {
			return nil, errors.Wrap(err, "while parsing chunk metric")
		}
		dc.metrics[fingerprint] = metric
	}
	return metric, nil
}

// Decode the chunk from the given buffer, and confirm the chunk is the one we
// expected.
func (c *Chunk) Decode(decodeContext *DecodeContext, input []byte) error {
	// Legacy chunks were written with metadata in the index.
	if c.metadataInIndex {
		var err error
		c.Data, err = prom_chunk.NewForEncoding(prom_chunk.DoubleDelta)
		if err != nil {
			return err
		}
		return c.Data.UnmarshalFromBuf(input)
	}

	// First, calculate the checksum of the chunk and confirm it matches
	// what we expected.
	if c.ChecksumSet && c.Checksum != crc32.Checksum(input, castagnoliTable) {
		return errors.WithStack(ErrInvalidChecksum)
	}

	// Now unmarshal the chunk metadata.
	r := bytes.NewReader(input)
	var metadataLen uint32
	if err := binary.Read(r, binary.BigEndian, &metadataLen); err != nil {
		return err
	}
	var tempMetadata struct {
		Chunk
		RawMetric json.RawMessage `json:"metric"` // Override to defer parsing
	}
	decodeContext.reader.Reset(&io.LimitedReader{
		N: int64(metadataLen),
		R: r,
	})
	err := json.NewDecoder(decodeContext.reader).Decode(&tempMetadata)
	if err != nil {
		return err
	}

	// Next, confirm the chunks matches what we expected.  Easiest way to do this
	// is to compare what the decoded data thinks its external ID would be, but
	// we don't write the checksum to s3, so we have to copy the checksum in.
	if c.ChecksumSet {
		tempMetadata.Checksum, tempMetadata.ChecksumSet = c.Checksum, c.ChecksumSet
		if c.ExternalKey() != tempMetadata.ExternalKey() {
			return errors.WithStack(ErrWrongMetadata)
		}
	}
	*c = tempMetadata.Chunk
	c.Metric, err = decodeContext.metric(tempMetadata.Fingerprint, tempMetadata.RawMetric)
	if err != nil {
		return err
	}

	// Flag indicates if metadata was written to index, and if false implies
	// we should read a header of the chunk containing the metadata.  Exists
	// for backwards compatibility with older chunks, which did not have header.
	if c.Encoding == prom_chunk.Delta {
		c.Encoding = prom_chunk.DoubleDelta
	}

	// Finally, unmarshal the actual chunk data.
	c.Data, err = prom_chunk.NewForEncoding(c.Encoding)
	if err != nil {
		return err
	}

	var dataLen uint32
	if err := binary.Read(r, binary.BigEndian, &dataLen); err != nil {
		return err
	}

	return c.Data.Unmarshal(&io.LimitedReader{
		N: int64(dataLen),
		R: r,
	})
}

func chunksToMatrix(chunks []Chunk) (model.Matrix, error) {
	// Group chunks by series, sort and dedupe samples.
	sampleStreams := map[model.Fingerprint]*model.SampleStream{}
	for _, c := range chunks {
		fp := c.Metric.Fingerprint()
		ss, ok := sampleStreams[fp]
		if !ok {
			ss = &model.SampleStream{
				Metric: c.Metric,
			}
			sampleStreams[fp] = ss
		}

		samples, err := c.Samples()
		if err != nil {
			return nil, err
		}

		ss.Values = util.MergeSampleSets(ss.Values, samples)
	}

	matrix := make(model.Matrix, 0, len(sampleStreams))
	for _, ss := range sampleStreams {
		matrix = append(matrix, &model.SampleStream{
			Metric: ss.Metric,
			Values: ss.Values,
		})
	}

	return matrix, nil
}

// Samples returns all SamplePairs for the chunk.
func (c *Chunk) Samples() ([]model.SamplePair, error) {
	it := c.Data.NewIterator()
	// TODO(juliusv): Pre-allocate this with the right length again once we
	// add a method upstream to get the number of samples in a chunk.
	var samples []model.SamplePair
	for it.Scan() {
		samples = append(samples, it.Value())
	}
	return samples, nil
}
<|endoftext|>


<commit_before>package proxmox

import (
	"context"
	"fmt"
	"strings"

	"github.com/Telmate/proxmox-api-go/proxmox"
	"github.com/hashicorp/packer-plugin-sdk/multistep"
	packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)

// stepFinalizeTemplateConfig does any required modifications to the configuration _after_
// the VM has been converted into a template, such as updating name and description, or
// unmounting the installation ISO.
type stepFinalizeTemplateConfig struct{}

type templateFinalizer interface {
	GetVmConfig(*proxmox.VmRef) (map[string]interface{}, error)
	SetVmConfig(*proxmox.VmRef, map[string]interface{}) (interface{}, error)
}

var _ templateFinalizer = &proxmox.Client{}

func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
	ui := state.Get("ui").(packersdk.Ui)
	client := state.Get("proxmoxClient").(templateFinalizer)
	c := state.Get("config").(*Config)
	vmRef := state.Get("vmRef").(*proxmox.VmRef)

	changes := make(map[string]interface{})

	if c.TemplateName != "" {
		changes["name"] = c.TemplateName
	}

	// During build, the description is "Packer ephemeral build VM", so if no description is
	// set, we need to clear it
	changes["description"] = c.TemplateDescription

	if c.CloudInit {
		vmParams, err := client.GetVmConfig(vmRef)
		if err != nil {
			err := fmt.Errorf("Error fetching template config: %s", err)
			state.Put("error", err)
			ui.Error(err.Error())
			return multistep.ActionHalt
		}

		cloudInitStoragePool := c.CloudInitStoragePool
		if cloudInitStoragePool == "" {
			if vmParams["bootdisk"] != nil && vmParams[vmParams["bootdisk"].(string)] != nil {
				bootDisk := vmParams[vmParams["bootdisk"].(string)].(string)
				cloudInitStoragePool = strings.Split(bootDisk, ":")[0]
			}
		}
		if cloudInitStoragePool != "" {
			ideControllers := []string{"ide3", "ide2", "ide1", "ide0"}
			cloudInitAttached := false
			// find a free ide controller
			for _, controller := range ideControllers {
				if vmParams[controller] == nil {
					ui.Say("Adding a cloud-init cdrom in storage pool " + cloudInitStoragePool)
					changes[controller] = cloudInitStoragePool + ":cloudinit"
					cloudInitAttached = true
					break
				}
			}
			if cloudInitAttached == false {
				err := fmt.Errorf("Found no free ide controller for a cloud-init cdrom")
				state.Put("error", err)
				ui.Error(err.Error())
				return multistep.ActionHalt
			}
		}
	}

	if len(changes) > 0 {
		_, err := client.SetVmConfig(vmRef, changes)
		if err != nil {
			err := fmt.Errorf("Error updating template: %s", err)
			state.Put("error", err)
			ui.Error(err.Error())
			return multistep.ActionHalt
		}
	}

	return multistep.ActionContinue
}

func (s *stepFinalizeTemplateConfig) Cleanup(state multistep.StateBag) {}
<commit_msg>Improve cloud init logging for proxmox builder (#10499)<commit_after>package proxmox

import (
	"context"
	"fmt"
	"strings"

	"github.com/Telmate/proxmox-api-go/proxmox"
	"github.com/hashicorp/packer-plugin-sdk/multistep"
	packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)

// stepFinalizeTemplateConfig does any required modifications to the configuration _after_
// the VM has been converted into a template, such as updating name and description, or
// unmounting the installation ISO.
type stepFinalizeTemplateConfig struct{}

type templateFinalizer interface {
	GetVmConfig(*proxmox.VmRef) (map[string]interface{}, error)
	SetVmConfig(*proxmox.VmRef, map[string]interface{}) (interface{}, error)
}

var _ templateFinalizer = &proxmox.Client{}

func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
	ui := state.Get("ui").(packersdk.Ui)
	client := state.Get("proxmoxClient").(templateFinalizer)
	c := state.Get("config").(*Config)
	vmRef := state.Get("vmRef").(*proxmox.VmRef)

	changes := make(map[string]interface{})

	if c.TemplateName != "" {
		changes["name"] = c.TemplateName
	}

	// During build, the description is "Packer ephemeral build VM", so if no description is
	// set, we need to clear it
	changes["description"] = c.TemplateDescription

	if c.CloudInit {
		vmParams, err := client.GetVmConfig(vmRef)
		if err != nil {
			err := fmt.Errorf("Error fetching template config: %s", err)
			state.Put("error", err)
			ui.Error(err.Error())
			return multistep.ActionHalt
		}

		cloudInitStoragePool := c.CloudInitStoragePool
		if cloudInitStoragePool == "" {
			if vmParams["bootdisk"] != nil && vmParams[vmParams["bootdisk"].(string)] != nil {
				bootDisk := vmParams[vmParams["bootdisk"].(string)].(string)
				cloudInitStoragePool = strings.Split(bootDisk, ":")[0]
			}
		}
		if cloudInitStoragePool != "" {
			ideControllers := []string{"ide3", "ide2", "ide1", "ide0"}
			cloudInitAttached := false
			// find a free ide controller
			for _, controller := range ideControllers {
				if vmParams[controller] == nil {
					ui.Say("Adding a cloud-init cdrom in storage pool " + cloudInitStoragePool)
					changes[controller] = cloudInitStoragePool + ":cloudinit"
					cloudInitAttached = true
					break
				}
			}
			if cloudInitAttached == false {
				err := fmt.Errorf("Found no free ide controller for a cloud-init cdrom")
				state.Put("error", err)
				ui.Error(err.Error())
				return multistep.ActionHalt
			}
		} else {
			err := fmt.Errorf("cloud_init is set to true, but cloud_init_storage_pool is empty and could not be set automatically. set cloud_init_storage_pool in your configuration")
			state.Put("error", err)
			ui.Error(err.Error())
			return multistep.ActionHalt
		}
	}

	if len(changes) > 0 {
		_, err := client.SetVmConfig(vmRef, changes)
		if err != nil {
			err := fmt.Errorf("Error updating template: %s", err)
			state.Put("error", err)
			ui.Error(err.Error())
			return multistep.ActionHalt
		}
	}

	return multistep.ActionContinue
}

func (s *stepFinalizeTemplateConfig) Cleanup(state multistep.StateBag) {}
<|endoftext|>


<commit_before>package graphicsmagick

import (
	"fmt"
	"github.com/pierrre/imageproxy"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
)

type GraphicsMagickConverter struct {
	Executable string
	TempDir    string
}

func (converter *GraphicsMagickConverter) Convert(sourceImage *imageproxy.Image, parameters imageproxy.Parameters) (image *imageproxy.Image, err error) {
	tempDir, err := ioutil.TempDir(converter.TempDir, "imageproxy_")
	if err != nil {
		return
	}
	defer os.RemoveAll(tempDir)

	filePath := filepath.Join(tempDir, "image")
	err = ioutil.WriteFile(filePath, sourceImage.Data, os.FileMode(0600))
	if err != nil {
		return
	}

	var arguments []string
	arguments = append(arguments, "mogrify")
	width, _ := parameters.GetInt("width")
	height, _ := parameters.GetInt("height")
	if width != 0 && height != 0 {
		if width <= 0 {
			err = fmt.Errorf("Invalid width")
		}
		if height <= 0 {
			err = fmt.Errorf("Invalid height")
		}
		arguments = append(arguments, "-resize", fmt.Sprintf("%dx%d", width, height))
	}
	arguments = append(arguments, filePath)

	cmd := exec.Command(converter.Executable, arguments...)
	err = cmd.Run()
	if err != nil {
		return
	}

	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		return
	}

	image = &imageproxy.Image{}
	image.Data = data
	//FIX type

	return image, nil
}
<commit_msg>Fix image type<commit_after>package graphicsmagick

import (
	"fmt"
	"github.com/pierrre/imageproxy"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
)

type GraphicsMagickConverter struct {
	Executable string
	TempDir    string
}

func (converter *GraphicsMagickConverter) Convert(sourceImage *imageproxy.Image, parameters imageproxy.Parameters) (image *imageproxy.Image, err error) {
	tempDir, err := ioutil.TempDir(converter.TempDir, "imageproxy_")
	if err != nil {
		return
	}
	defer os.RemoveAll(tempDir)

	filePath := filepath.Join(tempDir, "image")
	err = ioutil.WriteFile(filePath, sourceImage.Data, os.FileMode(0600))
	if err != nil {
		return
	}

	var arguments []string
	arguments = append(arguments, "mogrify")
	width, _ := parameters.GetInt("width")
	height, _ := parameters.GetInt("height")
	if width != 0 && height != 0 {
		if width <= 0 {
			err = fmt.Errorf("Invalid width")
		}
		if height <= 0 {
			err = fmt.Errorf("Invalid height")
		}
		arguments = append(arguments, "-resize", fmt.Sprintf("%dx%d", width, height))
	}
	arguments = append(arguments, filePath)

	cmd := exec.Command(converter.Executable, arguments...)
	err = cmd.Run()
	if err != nil {
		return
	}

	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		return
	}

	image = &imageproxy.Image{}
	image.Data = data
	image.Type = sourceImage.Type

	return image, nil
}
<|endoftext|>


<commit_before>package subtitles

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// Eol is the end of line characters to use when writing .srt data
const eol = "\n"

func looksLikeSRT(s string) bool {
	if strings.HasPrefix(s, "1\n") || strings.HasPrefix(s, "1\r\n") {
		return true
	}
	return false
}

// NewFromSRT parses a .srt text into Subtitle, assumes s is a clean utf8 string
func NewFromSRT(s string) (res Subtitle, err error) {
	r1 := regexp.MustCompile("([0-9:.,]*) --> ([0-9:.,]*)")
	lines := strings.Split(s, "\n")
	outSeq := 1

	for i := 0; i < len(lines); i++ {
		seq := strings.Trim(lines[i], "\r ")
		if seq == "" {
			continue
		}

		_, err := strconv.Atoi(seq)
		if err != nil {
			err = fmt.Errorf("srt: atoi error at line %d: %v", i, err)
			break
		}

		var o Caption
		o.Seq = outSeq

		i++
		if i >= len(lines) {
			break
		}

		matches := r1.FindStringSubmatch(lines[i])
		if len(matches) < 3 {
			err = fmt.Errorf("srt: parse error at line %d (idx out of range)", i)
			break
		}

		o.Start, err = parseTime(matches[1])
		if err != nil {
			err = fmt.Errorf("srt: start error at line %d: %v", i, err)
			break
		}

		o.End, err = parseTime(matches[2])
		if err != nil {
			err = fmt.Errorf("srt: end error at line %d: %v", i, err)
			break
		}

		i++
		if i >= len(lines) {
			break
		}

		textLine := 1
		for {
			line := strings.Trim(lines[i], "\r ")
			if line == "" && textLine > 1 {
				break
			}
			if line != "" {
				o.Text = append(o.Text, line)
			}

			i++
			if i >= len(lines) {
				break
			}

			textLine++
		}

		if len(o.Text) > 0 {
			res.Captions = append(res.Captions, o)
			outSeq++
		}
	}
	return
}

// AsSRT renders the sub in .srt format
func (subtitle *Subtitle) AsSRT() (res string) {
	for _, sub := range subtitle.Captions {
		res += sub.AsSRT()
	}
	return
}

// AsSRT renders the caption as srt
func (cap Caption) AsSRT() string {
	res := fmt.Sprintf("%d", cap.Seq) + eol +
		TimeSRT(cap.Start) + " --> " + TimeSRT(cap.End) + eol
	for _, line := range cap.Text {
		res += line + eol
	}
	return res + eol
}

// TimeSRT renders a timestamp for use in .srt
func TimeSRT(t time.Time) string {
	res := t.Format("15:04:05.000")
	return strings.Replace(res, ".", ",", 1)
}
<commit_msg>use Windows new lines if we are on Windows. Fixes #2<commit_after>package subtitles

import (
	"fmt"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"
)

// Eol is the end of line characters to use when writing .srt data
var eol = "\n"
func init() {
	if runtime.GOOS == "windows" {
		eol = "\r\n"
	}
}

func looksLikeSRT(s string) bool {
	if strings.HasPrefix(s, "1\n") || strings.HasPrefix(s, "1\r\n") {
		return true
	}
	return false
}

// NewFromSRT parses a .srt text into Subtitle, assumes s is a clean utf8 string
func NewFromSRT(s string) (res Subtitle, err error) {
	r1 := regexp.MustCompile("([0-9:.,]*) --> ([0-9:.,]*)")
	lines := strings.Split(s, "\n")
	outSeq := 1

	for i := 0; i < len(lines); i++ {
		seq := strings.Trim(lines[i], "\r ")
		if seq == "" {
			continue
		}

		_, err := strconv.Atoi(seq)
		if err != nil {
			err = fmt.Errorf("srt: atoi error at line %d: %v", i, err)
			break
		}

		var o Caption
		o.Seq = outSeq

		i++
		if i >= len(lines) {
			break
		}

		matches := r1.FindStringSubmatch(lines[i])
		if len(matches) < 3 {
			err = fmt.Errorf("srt: parse error at line %d (idx out of range)", i)
			break
		}

		o.Start, err = parseTime(matches[1])
		if err != nil {
			err = fmt.Errorf("srt: start error at line %d: %v", i, err)
			break
		}

		o.End, err = parseTime(matches[2])
		if err != nil {
			err = fmt.Errorf("srt: end error at line %d: %v", i, err)
			break
		}

		i++
		if i >= len(lines) {
			break
		}

		textLine := 1
		for {
			line := strings.Trim(lines[i], "\r ")
			if line == "" && textLine > 1 {
				break
			}
			if line != "" {
				o.Text = append(o.Text, line)
			}

			i++
			if i >= len(lines) {
				break
			}

			textLine++
		}

		if len(o.Text) > 0 {
			res.Captions = append(res.Captions, o)
			outSeq++
		}
	}
	return
}

// AsSRT renders the sub in .srt format
func (subtitle *Subtitle) AsSRT() (res string) {
	for _, sub := range subtitle.Captions {
		res += sub.AsSRT()
	}
	return
}

// AsSRT renders the caption as srt
func (cap Caption) AsSRT() string {
	res := fmt.Sprintf("%d", cap.Seq) + eol +
		TimeSRT(cap.Start) + " --> " + TimeSRT(cap.End) + eol
	for _, line := range cap.Text {
		res += line + eol
	}
	return res + eol
}

// TimeSRT renders a timestamp for use in .srt
func TimeSRT(t time.Time) string {
	res := t.Format("15:04:05.000")
	return strings.Replace(res, ".", ",", 1)
}
<|endoftext|>


<commit_before>/*
 Copyright 2016 Padduck, LLC

 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 programs

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

	"github.com/pufferpanel/pufferd/environments"
	"github.com/pufferpanel/pufferd/logging"
	"github.com/pufferpanel/pufferd/programs/install"
	"github.com/pufferpanel/pufferd/utils"
)

type Program interface {
	//Starts the program.
	//This includes starting the environment if it is not running.
	Start() (err error)

	//Stops the program.
	//This will also stop the environment it is ran in.
	Stop() (err error)

	//Kills the program.
	//This will also stop the environment it is ran in.
	Kill() (err error)

	//Creates any files needed for the program.
	//This includes creating the environment.
	Create() (err error)

	//Destroys the server.
	//This will delete the server, environment, and any files related to it.
	Destroy() (err error)

	Update() (err error)

	Install() (err error)

	//Determines if the server is running.
	IsRunning() (isRunning bool)

	//Sends a command to the process
	//If the program supports input, this will send the arguments to that.
	Execute(command string) (err error)

	SetEnabled(isEnabled bool) (err error)

	IsEnabled() (isEnabled bool)

	SetAutoStart(isAutoStart bool) (err error)

	IsAutoStart() (isAutoStart bool)

	SetEnvironment(environment environments.Environment) (err error)

	Id() string

	GetEnvironment() environments.Environment

	Save(file string) (err error)

	Edit(data map[string]interface{}) (err error)

	Reload(data Program)

	GetData() map[string]interface{}

	GetNetwork() string
}

type programData struct {
	RunData     Runtime
	InstallData install.InstallSection
	Environment environments.Environment
	Identifier  string
	Data        map[string]interface{}
}

//Starts the program.
//This includes starting the environment if it is not running.
func (p *programData) Start() (err error) {
	logging.Debugf("Starting server %s", p.Id())
	p.Environment.DisplayToConsole("Starting server\n")
	data := make(map[string]interface{})
	for k, v := range p.Data {
		data[k] = v.(map[string]interface{})["value"]
	}
	err = p.Environment.ExecuteAsync(p.RunData.Program, utils.ReplaceTokensInArr(p.RunData.Arguments, data))
	if err != nil {
		p.Environment.DisplayToConsole("Failed to start server\n")
	} else {
		//p.Environment.DisplayToConsole("Server started\n")
	}
	return
}

//Stops the program.
//This will also stop the environment it is ran in.
func (p *programData) Stop() (err error) {
	logging.Debugf("Stopping server %s", p.Id())
	err = p.Environment.ExecuteInMainProcess(p.RunData.Stop)
	if err != nil {
		p.Environment.DisplayToConsole("Failed to stop server\n")
	} else {
		p.Environment.DisplayToConsole("Server stopped\n")
	}
	return
}

//Kills the program.
//This will also stop the environment it is ran in.
func (p *programData) Kill() (err error) {
	logging.Debugf("Killing server %s", p.Id())
	err = p.Environment.Kill()
	if err != nil {
		p.Environment.DisplayToConsole("Failed to kill server\n")
	} else {
		p.Environment.DisplayToConsole("Server killed\n")
	}
	return
}

//Creates any files needed for the program.
//This includes creating the environment.
func (p *programData) Create() (err error) {
	logging.Debugf("Creating server %s", p.Id())
	p.Environment.DisplayToConsole("Allocating server\n")
	err = p.Environment.Create()
	p.Environment.DisplayToConsole("Server allocated\n")
	return
}

//Destroys the server.
//This will delete the server, environment, and any files related to it.
func (p *programData) Destroy() (err error) {
	logging.Debugf("Destroying server %s", p.Id())
	err = p.Environment.Delete()
	return
}

func (p *programData) Update() (err error) {
	logging.Debugf("Updating server %s", p.Id())
	err = p.Install()
	return
}

func (p *programData) Install() (err error) {
	logging.Debugf("Installing server %s", p.Id())
	if p.IsRunning() {
		err = p.Stop()
	}

	if err != nil {
		logging.Error("Error stopping server to install: ", err)
		p.Environment.DisplayToConsole("Error stopping server\n")
		return
	}

	p.Environment.DisplayToConsole("Installing server\n")

	os.MkdirAll(p.Environment.GetRootDirectory(), 0755)

	process := install.GenerateInstallProcess(&p.InstallData, p.Environment, p.Data)
	for process.HasNext() {
		err = process.RunNext()
		if err != nil {
			logging.Error("Error running installer: ", err)
			p.Environment.DisplayToConsole("Error installing server\n")
			break
		}
	}
	p.Environment.DisplayToConsole("Server installed\n")
	return
}

//Determines if the server is running.
func (p *programData) IsRunning() (isRunning bool) {
	isRunning = p.Environment.IsRunning()
	return
}

//Sends a command to the process
//If the program supports input, this will send the arguments to that.
func (p *programData) Execute(command string) (err error) {
	err = p.Environment.ExecuteInMainProcess(command)
	return
}

func (p *programData) SetEnabled(isEnabled bool) (err error) {
	p.RunData.Enabled = isEnabled
	return
}

func (p *programData) IsEnabled() (isEnabled bool) {
	isEnabled = p.RunData.Enabled
	return
}

func (p *programData) SetEnvironment(environment environments.Environment) (err error) {
	p.Environment = environment
	return
}

func (p *programData) Id() string {
	return p.Identifier
}

func (p *programData) GetEnvironment() environments.Environment {
	return p.Environment
}

func (p *programData) SetAutoStart(isAutoStart bool) (err error) {
	p.RunData.AutoStart = isAutoStart
	return
}

func (p *programData) IsAutoStart() (isAutoStart bool) {
	isAutoStart = p.RunData.AutoStart
	return
}

func (p *programData) Save(file string) (err error) {
	logging.Debugf("Saving server %s", p.Id())

	result := make(map[string]interface{})
	result["data"] = p.Data
	result["install"] = p.InstallData
	result["run"] = p.RunData

	endResult := make(map[string]interface{})
	endResult["pufferd"] = result

	data, err := json.MarshalIndent(endResult, "", "  ")
	if err != nil {
		return
	}

	err = ioutil.WriteFile(file, data, 0664)
	return
}

func (p *programData) Edit(data map[string]interface{}) (err error) {
	for k, v := range data {
		if v == nil || v == "" {
			delete(p.Data, k)
		}
		p.Data[k] = v
	}
	err = Save(p.Id())
	return
}

func (p *programData) Reload(data Program) {
	logging.Debugf("Reloading server %s", p.Id())
	replacement := data.(*programData)
	p.Data = replacement.Data
	p.InstallData = replacement.InstallData
	p.RunData = replacement.RunData
}

func (p *programData) GetData() map[string]interface{} {
	return p.Data
}

func (p *programData) GetNetwork() string {
	data := p.GetData()
	ip := "0.0.0.0"
	port := "0"

	ipData := data["ip"]
	if ipData != nil {
		ip = ipData.(map[string]interface{})["value"].(string)
	}

	portData := data["port"]
	if portData != nil {
		port = portData.(map[string]interface{})["value"].(string)
	}

	return ip + ":" + port
}

type Runtime struct {
	Stop      string   `json:"stop"`
	Pre       []string `json:"pre,omitempty"`
	Post      []string `json:"post,omitempty"`
	Program   string   `json:"program"`
	Arguments []string `json:"arguments"`
	Enabled   bool     `json:"enabled"`
	AutoStart bool     `json:"autostart"`
}
<commit_msg>Add message prompting user to install server<commit_after>/*
 Copyright 2016 Padduck, LLC

 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 programs

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

	"github.com/pufferpanel/pufferd/environments"
	"github.com/pufferpanel/pufferd/logging"
	"github.com/pufferpanel/pufferd/programs/install"
	"github.com/pufferpanel/pufferd/utils"
)

type Program interface {
	//Starts the program.
	//This includes starting the environment if it is not running.
	Start() (err error)

	//Stops the program.
	//This will also stop the environment it is ran in.
	Stop() (err error)

	//Kills the program.
	//This will also stop the environment it is ran in.
	Kill() (err error)

	//Creates any files needed for the program.
	//This includes creating the environment.
	Create() (err error)

	//Destroys the server.
	//This will delete the server, environment, and any files related to it.
	Destroy() (err error)

	Update() (err error)

	Install() (err error)

	//Determines if the server is running.
	IsRunning() (isRunning bool)

	//Sends a command to the process
	//If the program supports input, this will send the arguments to that.
	Execute(command string) (err error)

	SetEnabled(isEnabled bool) (err error)

	IsEnabled() (isEnabled bool)

	SetAutoStart(isAutoStart bool) (err error)

	IsAutoStart() (isAutoStart bool)

	SetEnvironment(environment environments.Environment) (err error)

	Id() string

	GetEnvironment() environments.Environment

	Save(file string) (err error)

	Edit(data map[string]interface{}) (err error)

	Reload(data Program)

	GetData() map[string]interface{}

	GetNetwork() string
}

type programData struct {
	RunData     Runtime
	InstallData install.InstallSection
	Environment environments.Environment
	Identifier  string
	Data        map[string]interface{}
}

//Starts the program.
//This includes starting the environment if it is not running.
func (p *programData) Start() (err error) {
	logging.Debugf("Starting server %s", p.Id())
	p.Environment.DisplayToConsole("Starting server\n")
	data := make(map[string]interface{})
	for k, v := range p.Data {
		data[k] = v.(map[string]interface{})["value"]
	}
	err = p.Environment.ExecuteAsync(p.RunData.Program, utils.ReplaceTokensInArr(p.RunData.Arguments, data))
	if err != nil {
		p.Environment.DisplayToConsole("Failed to start server\n")
	} else {
		//p.Environment.DisplayToConsole("Server started\n")
	}
	return
}

//Stops the program.
//This will also stop the environment it is ran in.
func (p *programData) Stop() (err error) {
	logging.Debugf("Stopping server %s", p.Id())
	err = p.Environment.ExecuteInMainProcess(p.RunData.Stop)
	if err != nil {
		p.Environment.DisplayToConsole("Failed to stop server\n")
	} else {
		p.Environment.DisplayToConsole("Server stopped\n")
	}
	return
}

//Kills the program.
//This will also stop the environment it is ran in.
func (p *programData) Kill() (err error) {
	logging.Debugf("Killing server %s", p.Id())
	err = p.Environment.Kill()
	if err != nil {
		p.Environment.DisplayToConsole("Failed to kill server\n")
	} else {
		p.Environment.DisplayToConsole("Server killed\n")
	}
	return
}

//Creates any files needed for the program.
//This includes creating the environment.
func (p *programData) Create() (err error) {
	logging.Debugf("Creating server %s", p.Id())
	p.Environment.DisplayToConsole("Allocating server\n")
	err = p.Environment.Create()
	p.Environment.DisplayToConsole("Server allocated\n")
	p.Environment.DisplayToConsole("Ready to be installed\n")
	return
}

//Destroys the server.
//This will delete the server, environment, and any files related to it.
func (p *programData) Destroy() (err error) {
	logging.Debugf("Destroying server %s", p.Id())
	err = p.Environment.Delete()
	return
}

func (p *programData) Update() (err error) {
	logging.Debugf("Updating server %s", p.Id())
	err = p.Install()
	return
}

func (p *programData) Install() (err error) {
	logging.Debugf("Installing server %s", p.Id())
	if p.IsRunning() {
		err = p.Stop()
	}

	if err != nil {
		logging.Error("Error stopping server to install: ", err)
		p.Environment.DisplayToConsole("Error stopping server\n")
		return
	}

	p.Environment.DisplayToConsole("Installing server\n")

	os.MkdirAll(p.Environment.GetRootDirectory(), 0755)

	process := install.GenerateInstallProcess(&p.InstallData, p.Environment, p.Data)
	for process.HasNext() {
		err = process.RunNext()
		if err != nil {
			logging.Error("Error running installer: ", err)
			p.Environment.DisplayToConsole("Error installing server\n")
			break
		}
	}
	p.Environment.DisplayToConsole("Server installed\n")
	return
}

//Determines if the server is running.
func (p *programData) IsRunning() (isRunning bool) {
	isRunning = p.Environment.IsRunning()
	return
}

//Sends a command to the process
//If the program supports input, this will send the arguments to that.
func (p *programData) Execute(command string) (err error) {
	err = p.Environment.ExecuteInMainProcess(command)
	return
}

func (p *programData) SetEnabled(isEnabled bool) (err error) {
	p.RunData.Enabled = isEnabled
	return
}

func (p *programData) IsEnabled() (isEnabled bool) {
	isEnabled = p.RunData.Enabled
	return
}

func (p *programData) SetEnvironment(environment environments.Environment) (err error) {
	p.Environment = environment
	return
}

func (p *programData) Id() string {
	return p.Identifier
}

func (p *programData) GetEnvironment() environments.Environment {
	return p.Environment
}

func (p *programData) SetAutoStart(isAutoStart bool) (err error) {
	p.RunData.AutoStart = isAutoStart
	return
}

func (p *programData) IsAutoStart() (isAutoStart bool) {
	isAutoStart = p.RunData.AutoStart
	return
}

func (p *programData) Save(file string) (err error) {
	logging.Debugf("Saving server %s", p.Id())

	result := make(map[string]interface{})
	result["data"] = p.Data
	result["install"] = p.InstallData
	result["run"] = p.RunData

	endResult := make(map[string]interface{})
	endResult["pufferd"] = result

	data, err := json.MarshalIndent(endResult, "", "  ")
	if err != nil {
		return
	}

	err = ioutil.WriteFile(file, data, 0664)
	return
}

func (p *programData) Edit(data map[string]interface{}) (err error) {
	for k, v := range data {
		if v == nil || v == "" {
			delete(p.Data, k)
		}
		p.Data[k] = v
	}
	err = Save(p.Id())
	return
}

func (p *programData) Reload(data Program) {
	logging.Debugf("Reloading server %s", p.Id())
	replacement := data.(*programData)
	p.Data = replacement.Data
	p.InstallData = replacement.InstallData
	p.RunData = replacement.RunData
}

func (p *programData) GetData() map[string]interface{} {
	return p.Data
}

func (p *programData) GetNetwork() string {
	data := p.GetData()
	ip := "0.0.0.0"
	port := "0"

	ipData := data["ip"]
	if ipData != nil {
		ip = ipData.(map[string]interface{})["value"].(string)
	}

	portData := data["port"]
	if portData != nil {
		port = portData.(map[string]interface{})["value"].(string)
	}

	return ip + ":" + port
}

type Runtime struct {
	Stop      string   `json:"stop"`
	Pre       []string `json:"pre,omitempty"`
	Post      []string `json:"post,omitempty"`
	Program   string   `json:"program"`
	Arguments []string `json:"arguments"`
	Enabled   bool     `json:"enabled"`
	AutoStart bool     `json:"autostart"`
}
<|endoftext|>


<commit_before>package rpc

import (
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"sync"
	"time"
)

// MuxConn is able to multiplex multiple streams on top of any
// io.ReadWriteCloser. These streams act like TCP connections (Dial, Accept,
// Close, full duplex, etc.).
//
// The underlying io.ReadWriteCloser is expected to guarantee delivery
// and ordering, such as TCP. Congestion control and such aren't implemented
// by the streams, so that is also up to the underlying connection.
//
// MuxConn works using a fairly dumb multiplexing technique of simply
// framing every piece of data sent into a prefix + data format. Streams
// are established using a subset of the TCP protocol. Only a subset is
// necessary since we assume ordering on the underlying RWC.
type MuxConn struct {
	curId   uint32
	rwc     io.ReadWriteCloser
	streams map[uint32]*Stream
	mu      sync.RWMutex
	wlock   sync.Mutex
}

type muxPacketType byte

const (
	muxPacketSyn muxPacketType = iota
	muxPacketSynAck
	muxPacketAck
	muxPacketFin
	muxPacketData
)

// Create a new MuxConn around any io.ReadWriteCloser.
func NewMuxConn(rwc io.ReadWriteCloser) *MuxConn {
	m := &MuxConn{
		rwc:     rwc,
		streams: make(map[uint32]*Stream),
	}

	go m.loop()

	return m
}

// Close closes the underlying io.ReadWriteCloser. This will also close
// all streams that are open.
func (m *MuxConn) Close() error {
	m.mu.Lock()
	defer m.mu.Unlock()

	// Close all the streams
	for _, w := range m.streams {
		w.Close()
	}
	m.streams = make(map[uint32]*Stream)

	// Close the actual connection. This will also force the loop
	// to end since it'll read EOF or closed connection.
	return m.rwc.Close()
}

// Accept accepts a multiplexed connection with the given ID. This
// will block until a request is made to connect.
func (m *MuxConn) Accept(id uint32) (io.ReadWriteCloser, error) {
	stream, err := m.openStream(id)
	if err != nil {
		return nil, err
	}

	// If the stream isn't closed, then it is already open somehow
	stream.mu.Lock()
	defer stream.mu.Unlock()
	if stream.state != streamStateSynRecv && stream.state != streamStateClosed {
		return nil, fmt.Errorf("Stream %d already open in bad state: %d", id, stream.state)
	}

	if stream.state == streamStateClosed {
		// Go into the listening state and wait for a syn
		stream.setState(streamStateListen)
		if err := stream.waitState(streamStateSynRecv); err != nil {
			return nil, err
		}
	}

	if stream.state == streamStateSynRecv {
		// Send a syn-ack
		if _, err := m.write(stream.id, muxPacketSynAck, nil); err != nil {
			return nil, err
		}
	}

	if err := stream.waitState(streamStateEstablished); err != nil {
		return nil, err
	}

	return stream, nil
}

// Dial opens a connection to the remote end using the given stream ID.
// An Accept on the remote end will only work with if the IDs match.
func (m *MuxConn) Dial(id uint32) (io.ReadWriteCloser, error) {
	stream, err := m.openStream(id)
	if err != nil {
		return nil, err
	}

	// If the stream isn't closed, then it is already open somehow
	stream.mu.Lock()
	defer stream.mu.Unlock()
	if stream.state != streamStateClosed {
		return nil, fmt.Errorf("Stream %d already open in bad state: %d", id, stream.state)
	}

	// Open a connection
	if _, err := m.write(stream.id, muxPacketSyn, nil); err != nil {
		return nil, err
	}
	stream.setState(streamStateSynSent)

	if err := stream.waitState(streamStateEstablished); err != nil {
		return nil, err
	}

	m.write(id, muxPacketAck, nil)
	return stream, nil
}

// NextId returns the next available stream ID that isn't currently
// taken.
func (m *MuxConn) NextId() uint32 {
	m.mu.Lock()
	defer m.mu.Unlock()

	for {
		result := m.curId
		m.curId++
		if _, ok := m.streams[result]; !ok {
			return result
		}
	}
}

func (m *MuxConn) openStream(id uint32) (*Stream, error) {
	// First grab a read-lock if we have the stream already we can
	// cheaply return it.
	m.mu.RLock()
	if stream, ok := m.streams[id]; ok {
		m.mu.RUnlock()
		return stream, nil
	}

	// Now acquire a full blown write lock so we can create the stream
	m.mu.RUnlock()
	m.mu.Lock()
	defer m.mu.Unlock()

	// Make sure we attempt to use the next biggest stream ID
	if id >= m.curId {
		m.curId = id + 1
	}

	// We have to check this again because there is a time period
	// above where we couldn't lost this lock.
	if stream, ok := m.streams[id]; ok {
		return stream, nil
	}

	// Create the stream object and channel where data will be sent to
	dataR, dataW := io.Pipe()
	writeCh := make(chan []byte, 256)

	// Set the data channel so we can write to it.
	stream := &Stream{
		id:          id,
		mux:         m,
		reader:      dataR,
		writeCh:     writeCh,
		stateChange: make(map[chan<- streamState]struct{}),
	}
	stream.setState(streamStateClosed)

	// Start the goroutine that will read from the queue and write
	// data out.
	go func() {
		defer dataW.Close()

		for {
			data := <-writeCh
			if data == nil {
				// A nil is a tombstone letting us know we're done
				// accepting data.
				return
			}

			if _, err := dataW.Write(data); err != nil {
				return
			}
		}
	}()

	m.streams[id] = stream
	return m.streams[id], nil
}

func (m *MuxConn) loop() {
	// Force close every stream that we know about when we exit so
	// that they all read EOF and don't block forever.
	defer func() {
		log.Printf("[INFO] Mux connection loop exiting")
		m.mu.Lock()
		defer m.mu.Unlock()
		for _, w := range m.streams {
			w.mu.Lock()
			w.remoteClose()
			w.mu.Unlock()
		}
	}()

	var id uint32
	var packetType muxPacketType
	var length int32
	for {
		if err := binary.Read(m.rwc, binary.BigEndian, &id); err != nil {
			log.Printf("[ERR] Error reading stream ID: %s", err)
			return
		}
		if err := binary.Read(m.rwc, binary.BigEndian, &packetType); err != nil {
			log.Printf("[ERR] Error reading packet type: %s", err)
			return
		}
		if err := binary.Read(m.rwc, binary.BigEndian, &length); err != nil {
			log.Printf("[ERR] Error reading length: %s", err)
			return
		}

		// TODO(mitchellh): probably would be better to re-use a buffer...
		data := make([]byte, length)
		if length > 0 {
			if _, err := m.rwc.Read(data); err != nil {
				log.Printf("[ERR] Error reading data: %s", err)
				return
			}
		}

		stream, err := m.openStream(id)
		if err != nil {
			log.Printf("[ERR] Error opening stream %d: %s", id, err)
			return
		}

		log.Printf("[TRACE] Stream %d received packet %d", id, packetType)
		switch packetType {
		case muxPacketSyn:
			stream.mu.Lock()
			switch stream.state {
			case streamStateClosed:
				fallthrough
			case streamStateListen:
				stream.setState(streamStateSynRecv)
			default:
				log.Printf("[ERR] Syn received for stream in state: %d", stream.state)
			}
			stream.mu.Unlock()
		case muxPacketAck:
			stream.mu.Lock()
			switch stream.state {
			case streamStateSynRecv:
				stream.setState(streamStateEstablished)
			case streamStateFinWait1:
				stream.setState(streamStateFinWait2)
			default:
				log.Printf("[ERR] Ack received for stream in state: %d", stream.state)
			}
			stream.mu.Unlock()
		case muxPacketSynAck:
			stream.mu.Lock()
			switch stream.state {
			case streamStateSynSent:
				stream.setState(streamStateEstablished)
			default:
				log.Printf("[ERR] SynAck received for stream in state: %d", stream.state)
			}
			stream.mu.Unlock()
		case muxPacketFin:
			stream.mu.Lock()
			switch stream.state {
			case streamStateEstablished:
				stream.setState(streamStateCloseWait)
				m.write(id, muxPacketAck, nil)

				// Close the writer on our end since we won't receive any
				// more data.
				stream.writeCh <- nil
			case streamStateFinWait1:
				fallthrough
			case streamStateFinWait2:
				stream.remoteClose()

				m.mu.Lock()
				delete(m.streams, stream.id)
				m.mu.Unlock()
			default:
				log.Printf("[ERR] Fin received for stream %d in state: %d", id, stream.state)
			}
			stream.mu.Unlock()

		case muxPacketData:
			stream.mu.Lock()
			switch stream.state {
			case streamStateFinWait1:
				fallthrough
			case streamStateFinWait2:
				fallthrough
			case streamStateEstablished:
				select {
				case stream.writeCh <- data:
				default:
					panic(fmt.Sprintf("Failed to write data, buffer full for stream %d", id))
				}
			default:
				log.Printf("[ERR] Data received for stream in state: %d", stream.state)
			}
			stream.mu.Unlock()
		}
	}
}

func (m *MuxConn) write(id uint32, dataType muxPacketType, p []byte) (int, error) {
	m.wlock.Lock()
	defer m.wlock.Unlock()

	if err := binary.Write(m.rwc, binary.BigEndian, id); err != nil {
		return 0, err
	}
	if err := binary.Write(m.rwc, binary.BigEndian, byte(dataType)); err != nil {
		return 0, err
	}
	if err := binary.Write(m.rwc, binary.BigEndian, int32(len(p))); err != nil {
		return 0, err
	}
	if len(p) == 0 {
		return 0, nil
	}
	return m.rwc.Write(p)
}

// Stream is a single stream of data and implements io.ReadWriteCloser.
// A Stream is full-duplex so you can write data as well as read data.
type Stream struct {
	id           uint32
	mux          *MuxConn
	reader       io.Reader
	state        streamState
	stateChange  map[chan<- streamState]struct{}
	stateUpdated time.Time
	mu           sync.Mutex
	writeCh      chan<- []byte
}

type streamState byte

const (
	streamStateClosed streamState = iota
	streamStateListen
	streamStateSynRecv
	streamStateSynSent
	streamStateEstablished
	streamStateFinWait1
	streamStateFinWait2
	streamStateCloseWait
)

func (s *Stream) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.state != streamStateEstablished && s.state != streamStateCloseWait {
		return fmt.Errorf("Stream in bad state: %d", s.state)
	}

	if s.state == streamStateEstablished {
		s.setState(streamStateFinWait1)
	} else {
		s.remoteClose()
	}

	s.mux.write(s.id, muxPacketFin, nil)
	return nil
}

func (s *Stream) Read(p []byte) (int, error) {
	return s.reader.Read(p)
}

func (s *Stream) Write(p []byte) (int, error) {
	s.mu.Lock()
	state := s.state
	s.mu.Unlock()

	if state != streamStateEstablished && state != streamStateCloseWait {
		return 0, fmt.Errorf("Stream %d in bad state to send: %d", s.id, state)
	}

	return s.mux.write(s.id, muxPacketData, p)
}

func (s *Stream) remoteClose() {
	s.setState(streamStateClosed)
	s.writeCh <- nil
}

func (s *Stream) registerStateListener(ch chan<- streamState) {
	s.stateChange[ch] = struct{}{}
}

func (s *Stream) deregisterStateListener(ch chan<- streamState) {
	delete(s.stateChange, ch)
}

func (s *Stream) setState(state streamState) {
	log.Printf("[TRACE] Stream %d went to state %d", s.id, state)
	s.state = state
	s.stateUpdated = time.Now().UTC()
	for ch, _ := range s.stateChange {
		select {
		case ch <- state:
		default:
		}
	}
}

func (s *Stream) waitState(target streamState) error {
	// Register a state change listener to wait for changes
	stateCh := make(chan streamState, 10)
	s.registerStateListener(stateCh)
	s.mu.Unlock()

	defer func() {
		s.mu.Lock()
		s.deregisterStateListener(stateCh)
	}()

	state := <-stateCh
	if state == target {
		return nil
	} else {
		return fmt.Errorf("Stream %d went to bad state: %d", s.id, state)
	}
}
<commit_msg>packer/rpc: clean up unnecessary functions<commit_after>package rpc

import (
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"sync"
	"time"
)

// MuxConn is able to multiplex multiple streams on top of any
// io.ReadWriteCloser. These streams act like TCP connections (Dial, Accept,
// Close, full duplex, etc.).
//
// The underlying io.ReadWriteCloser is expected to guarantee delivery
// and ordering, such as TCP. Congestion control and such aren't implemented
// by the streams, so that is also up to the underlying connection.
//
// MuxConn works using a fairly dumb multiplexing technique of simply
// framing every piece of data sent into a prefix + data format. Streams
// are established using a subset of the TCP protocol. Only a subset is
// necessary since we assume ordering on the underlying RWC.
type MuxConn struct {
	curId   uint32
	rwc     io.ReadWriteCloser
	streams map[uint32]*Stream
	mu      sync.RWMutex
	wlock   sync.Mutex
}

type muxPacketType byte

const (
	muxPacketSyn muxPacketType = iota
	muxPacketSynAck
	muxPacketAck
	muxPacketFin
	muxPacketData
)

// Create a new MuxConn around any io.ReadWriteCloser.
func NewMuxConn(rwc io.ReadWriteCloser) *MuxConn {
	m := &MuxConn{
		rwc:     rwc,
		streams: make(map[uint32]*Stream),
	}

	go m.loop()

	return m
}

// Close closes the underlying io.ReadWriteCloser. This will also close
// all streams that are open.
func (m *MuxConn) Close() error {
	m.mu.Lock()
	defer m.mu.Unlock()

	// Close all the streams
	for _, w := range m.streams {
		w.Close()
	}
	m.streams = make(map[uint32]*Stream)

	// Close the actual connection. This will also force the loop
	// to end since it'll read EOF or closed connection.
	return m.rwc.Close()
}

// Accept accepts a multiplexed connection with the given ID. This
// will block until a request is made to connect.
func (m *MuxConn) Accept(id uint32) (io.ReadWriteCloser, error) {
	stream, err := m.openStream(id)
	if err != nil {
		return nil, err
	}

	// If the stream isn't closed, then it is already open somehow
	stream.mu.Lock()
	defer stream.mu.Unlock()
	if stream.state != streamStateSynRecv && stream.state != streamStateClosed {
		return nil, fmt.Errorf("Stream %d already open in bad state: %d", id, stream.state)
	}

	if stream.state == streamStateClosed {
		// Go into the listening state and wait for a syn
		stream.setState(streamStateListen)
		if err := stream.waitState(streamStateSynRecv); err != nil {
			return nil, err
		}
	}

	if stream.state == streamStateSynRecv {
		// Send a syn-ack
		if _, err := m.write(stream.id, muxPacketSynAck, nil); err != nil {
			return nil, err
		}
	}

	if err := stream.waitState(streamStateEstablished); err != nil {
		return nil, err
	}

	return stream, nil
}

// Dial opens a connection to the remote end using the given stream ID.
// An Accept on the remote end will only work with if the IDs match.
func (m *MuxConn) Dial(id uint32) (io.ReadWriteCloser, error) {
	stream, err := m.openStream(id)
	if err != nil {
		return nil, err
	}

	// If the stream isn't closed, then it is already open somehow
	stream.mu.Lock()
	defer stream.mu.Unlock()
	if stream.state != streamStateClosed {
		return nil, fmt.Errorf("Stream %d already open in bad state: %d", id, stream.state)
	}

	// Open a connection
	if _, err := m.write(stream.id, muxPacketSyn, nil); err != nil {
		return nil, err
	}
	stream.setState(streamStateSynSent)

	if err := stream.waitState(streamStateEstablished); err != nil {
		return nil, err
	}

	m.write(id, muxPacketAck, nil)
	return stream, nil
}

// NextId returns the next available stream ID that isn't currently
// taken.
func (m *MuxConn) NextId() uint32 {
	m.mu.Lock()
	defer m.mu.Unlock()

	for {
		result := m.curId
		m.curId++
		if _, ok := m.streams[result]; !ok {
			return result
		}
	}
}

func (m *MuxConn) openStream(id uint32) (*Stream, error) {
	// First grab a read-lock if we have the stream already we can
	// cheaply return it.
	m.mu.RLock()
	if stream, ok := m.streams[id]; ok {
		m.mu.RUnlock()
		return stream, nil
	}

	// Now acquire a full blown write lock so we can create the stream
	m.mu.RUnlock()
	m.mu.Lock()
	defer m.mu.Unlock()

	// Make sure we attempt to use the next biggest stream ID
	if id >= m.curId {
		m.curId = id + 1
	}

	// We have to check this again because there is a time period
	// above where we couldn't lost this lock.
	if stream, ok := m.streams[id]; ok {
		return stream, nil
	}

	// Create the stream object and channel where data will be sent to
	dataR, dataW := io.Pipe()
	writeCh := make(chan []byte, 256)

	// Set the data channel so we can write to it.
	stream := &Stream{
		id:          id,
		mux:         m,
		reader:      dataR,
		writeCh:     writeCh,
		stateChange: make(map[chan<- streamState]struct{}),
	}
	stream.setState(streamStateClosed)

	// Start the goroutine that will read from the queue and write
	// data out.
	go func() {
		defer dataW.Close()

		for {
			data := <-writeCh
			if data == nil {
				// A nil is a tombstone letting us know we're done
				// accepting data.
				return
			}

			if _, err := dataW.Write(data); err != nil {
				return
			}
		}
	}()

	m.streams[id] = stream
	return m.streams[id], nil
}

func (m *MuxConn) loop() {
	// Force close every stream that we know about when we exit so
	// that they all read EOF and don't block forever.
	defer func() {
		log.Printf("[INFO] Mux connection loop exiting")
		m.mu.Lock()
		defer m.mu.Unlock()
		for _, w := range m.streams {
			w.mu.Lock()
			w.remoteClose()
			w.mu.Unlock()
		}
	}()

	var id uint32
	var packetType muxPacketType
	var length int32
	for {
		if err := binary.Read(m.rwc, binary.BigEndian, &id); err != nil {
			log.Printf("[ERR] Error reading stream ID: %s", err)
			return
		}
		if err := binary.Read(m.rwc, binary.BigEndian, &packetType); err != nil {
			log.Printf("[ERR] Error reading packet type: %s", err)
			return
		}
		if err := binary.Read(m.rwc, binary.BigEndian, &length); err != nil {
			log.Printf("[ERR] Error reading length: %s", err)
			return
		}

		// TODO(mitchellh): probably would be better to re-use a buffer...
		data := make([]byte, length)
		if length > 0 {
			if _, err := m.rwc.Read(data); err != nil {
				log.Printf("[ERR] Error reading data: %s", err)
				return
			}
		}

		stream, err := m.openStream(id)
		if err != nil {
			log.Printf("[ERR] Error opening stream %d: %s", id, err)
			return
		}

		log.Printf("[TRACE] Stream %d received packet %d", id, packetType)
		switch packetType {
		case muxPacketSyn:
			stream.mu.Lock()
			switch stream.state {
			case streamStateClosed:
				fallthrough
			case streamStateListen:
				stream.setState(streamStateSynRecv)
			default:
				log.Printf("[ERR] Syn received for stream in state: %d", stream.state)
			}
			stream.mu.Unlock()
		case muxPacketAck:
			stream.mu.Lock()
			switch stream.state {
			case streamStateSynRecv:
				stream.setState(streamStateEstablished)
			case streamStateFinWait1:
				stream.setState(streamStateFinWait2)
			default:
				log.Printf("[ERR] Ack received for stream in state: %d", stream.state)
			}
			stream.mu.Unlock()
		case muxPacketSynAck:
			stream.mu.Lock()
			switch stream.state {
			case streamStateSynSent:
				stream.setState(streamStateEstablished)
			default:
				log.Printf("[ERR] SynAck received for stream in state: %d", stream.state)
			}
			stream.mu.Unlock()
		case muxPacketFin:
			stream.mu.Lock()
			switch stream.state {
			case streamStateEstablished:
				stream.setState(streamStateCloseWait)
				m.write(id, muxPacketAck, nil)

				// Close the writer on our end since we won't receive any
				// more data.
				stream.writeCh <- nil
			case streamStateFinWait1:
				fallthrough
			case streamStateFinWait2:
				stream.remoteClose()

				m.mu.Lock()
				delete(m.streams, stream.id)
				m.mu.Unlock()
			default:
				log.Printf("[ERR] Fin received for stream %d in state: %d", id, stream.state)
			}
			stream.mu.Unlock()

		case muxPacketData:
			stream.mu.Lock()
			switch stream.state {
			case streamStateFinWait1:
				fallthrough
			case streamStateFinWait2:
				fallthrough
			case streamStateEstablished:
				select {
				case stream.writeCh <- data:
				default:
					panic(fmt.Sprintf("Failed to write data, buffer full for stream %d", id))
				}
			default:
				log.Printf("[ERR] Data received for stream in state: %d", stream.state)
			}
			stream.mu.Unlock()
		}
	}
}

func (m *MuxConn) write(id uint32, dataType muxPacketType, p []byte) (int, error) {
	m.wlock.Lock()
	defer m.wlock.Unlock()

	if err := binary.Write(m.rwc, binary.BigEndian, id); err != nil {
		return 0, err
	}
	if err := binary.Write(m.rwc, binary.BigEndian, byte(dataType)); err != nil {
		return 0, err
	}
	if err := binary.Write(m.rwc, binary.BigEndian, int32(len(p))); err != nil {
		return 0, err
	}
	if len(p) == 0 {
		return 0, nil
	}
	return m.rwc.Write(p)
}

// Stream is a single stream of data and implements io.ReadWriteCloser.
// A Stream is full-duplex so you can write data as well as read data.
type Stream struct {
	id           uint32
	mux          *MuxConn
	reader       io.Reader
	state        streamState
	stateChange  map[chan<- streamState]struct{}
	stateUpdated time.Time
	mu           sync.Mutex
	writeCh      chan<- []byte
}

type streamState byte

const (
	streamStateClosed streamState = iota
	streamStateListen
	streamStateSynRecv
	streamStateSynSent
	streamStateEstablished
	streamStateFinWait1
	streamStateFinWait2
	streamStateCloseWait
)

func (s *Stream) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.state != streamStateEstablished && s.state != streamStateCloseWait {
		return fmt.Errorf("Stream in bad state: %d", s.state)
	}

	if s.state == streamStateEstablished {
		s.setState(streamStateFinWait1)
	} else {
		s.remoteClose()
	}

	s.mux.write(s.id, muxPacketFin, nil)
	return nil
}

func (s *Stream) Read(p []byte) (int, error) {
	return s.reader.Read(p)
}

func (s *Stream) Write(p []byte) (int, error) {
	s.mu.Lock()
	state := s.state
	s.mu.Unlock()

	if state != streamStateEstablished && state != streamStateCloseWait {
		return 0, fmt.Errorf("Stream %d in bad state to send: %d", s.id, state)
	}

	return s.mux.write(s.id, muxPacketData, p)
}

func (s *Stream) remoteClose() {
	s.setState(streamStateClosed)
	s.writeCh <- nil
}

func (s *Stream) setState(state streamState) {
	log.Printf("[TRACE] Stream %d went to state %d", s.id, state)
	s.state = state
	s.stateUpdated = time.Now().UTC()
	for ch, _ := range s.stateChange {
		select {
		case ch <- state:
		default:
		}
	}
}

func (s *Stream) waitState(target streamState) error {
	// Register a state change listener to wait for changes
	stateCh := make(chan streamState, 10)
	s.stateChange[stateCh] = struct{}{}
	s.mu.Unlock()

	defer func() {
		s.mu.Lock()
		delete(s.stateChange, stateCh)
	}()

	state := <-stateCh
	if state == target {
		return nil
	} else {
		return fmt.Errorf("Stream %d went to bad state: %d", s.id, state)
	}
}
<|endoftext|>


<commit_before>// Copyright 2011-2018 visualfc <visualfc@gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gomod

import (
	"encoding/json"
	"os/exec"
	"strings"
)

func LooupModList(dir string) *ModuleList {
	data := ListModuleJson(dir)
	if data == nil {
		return nil
	}
	ms := parseModuleJson(data)
	return &ms
}

func LookupModFile(dir string) string {
	command := exec.Command("go", "env", "GOMOD")
	command.Dir = dir
	data, err := command.Output()
	if err != nil {
		return ""
	}
	return strings.TrimSpace(string(data))
}

func ListModuleJson(dir string) []byte {
	command := exec.Command("go", "list", "-m", "-json", "all")
	command.Dir = dir
	data, err := command.Output()
	if err != nil {
		return nil
	}
	return data
}

type ModuleList struct {
	Module  Module
	Require []*Module
}

func (m *ModuleList) LookupModule(pkgname string) *Module {
	for _, r := range m.Require {
		if r.Path == pkgname {
			return r
		}
	}
	return nil
}

type Module struct {
	Path    string
	Version string
	Time    string
	Dir     string
	Main    bool
}

func parseModuleJson(data []byte) ModuleList {
	var ms ModuleList
	var index int
	for i, v := range data {
		switch v {
		case '{':
			index = i
		case '}':
			var m Module
			err := json.Unmarshal(data[index:i+1], &m)
			if err == nil {
				if m.Main {
					ms.Module = m
				} else {
					ms.Require = append(ms.Require, &m)
				}
			}
		}
	}
	return ms
}
<commit_msg>fix gomod<commit_after>// Copyright 2011-2018 visualfc <visualfc@gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gomod

import (
	"encoding/json"
	"os/exec"
	"strings"
)

func LooupModList(dir string) *ModuleList {
	data := ListModuleJson(dir)
	if data == nil {
		return nil
	}
	ms := parseModuleJson(data)
	return &ms
}

func LookupModFile(dir string) string {
	command := exec.Command("go", "env", "GOMOD")
	command.Dir = dir
	data, err := command.Output()
	if err != nil {
		return ""
	}
	return strings.TrimSpace(string(data))
}

func ListModuleJson(dir string) []byte {
	command := exec.Command("go", "list", "-m", "-json", "all")
	command.Dir = dir
	data, err := command.Output()
	if err != nil {
		return nil
	}
	return data
}

type ModuleList struct {
	Module  Module
	Require []*Module
}

func (m *ModuleList) LookupModule(pkgname string) *Module {
	for _, r := range m.Require {
		if strings.Index(pkgname, r.Path) == 0 {
			return r
		}
	}
	return nil
}

type Module struct {
	Path    string
	Version string
	Time    string
	Dir     string
	Main    bool
}

func parseModuleJson(data []byte) ModuleList {
	var ms ModuleList
	var index int
	for i, v := range data {
		switch v {
		case '{':
			index = i
		case '}':
			var m Module
			err := json.Unmarshal(data[index:i+1], &m)
			if err == nil {
				if m.Main {
					ms.Module = m
				} else {
					ms.Require = append(ms.Require, &m)
				}
			}
		}
	}
	return ms
}
<|endoftext|>


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

// +build darwin freebsd linux netbsd openbsd windows

// Base posix socket functions.

package net

import (
	"os"
	"syscall"
	"time"
)

type conn struct {
	fd *netFD
}

func (c *conn) ok() bool { return c != nil && c.fd != nil }

// Implementation of the Conn interface - see Conn for documentation.

// Read implements the Conn Read method.
func (c *conn) Read(b []byte) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	return c.fd.Read(b)
}

// Write implements the Conn Write method.
func (c *conn) Write(b []byte) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	return c.fd.Write(b)
}

// LocalAddr returns the local network address.
func (c *conn) LocalAddr() Addr {
	if !c.ok() {
		return nil
	}
	return c.fd.laddr
}

// RemoteAddr returns the remote network address, a *UDPAddr.
func (c *conn) RemoteAddr() Addr {
	if !c.ok() {
		return nil
	}
	return c.fd.raddr
}

// SetDeadline implements the Conn SetDeadline method.
func (c *conn) SetDeadline(t time.Time) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setDeadline(c.fd, t)
}

// SetReadDeadline implements the Conn SetReadDeadline method.
func (c *conn) SetReadDeadline(t time.Time) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setReadDeadline(c.fd, t)
}

// SetWriteDeadline implements the Conn SetWriteDeadline method.
func (c *conn) SetWriteDeadline(t time.Time) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setWriteDeadline(c.fd, t)
}

// SetReadBuffer sets the size of the operating system's
// receive buffer associated with the connection.
func (c *conn) SetReadBuffer(bytes int) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setReadBuffer(c.fd, bytes)
}

// SetWriteBuffer sets the size of the operating system's
// transmit buffer associated with the connection.
func (c *conn) SetWriteBuffer(bytes int) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setWriteBuffer(c.fd, bytes)
}

// File returns a copy of the underlying os.File, set to blocking mode.
// It is the caller's responsibility to close f when finished.
// Closing c does not affect f, and closing f does not affect c.
func (c *conn) File() (f *os.File, err error) { return c.fd.dup() }

// Close closes the connection.
func (c *conn) Close() error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return c.fd.Close()
}
<commit_msg>net: fix comment<commit_after>// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin freebsd linux netbsd openbsd windows

// Base posix socket functions.

package net

import (
	"os"
	"syscall"
	"time"
)

type conn struct {
	fd *netFD
}

func (c *conn) ok() bool { return c != nil && c.fd != nil }

// Implementation of the Conn interface - see Conn for documentation.

// Read implements the Conn Read method.
func (c *conn) Read(b []byte) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	return c.fd.Read(b)
}

// Write implements the Conn Write method.
func (c *conn) Write(b []byte) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	return c.fd.Write(b)
}

// LocalAddr returns the local network address.
func (c *conn) LocalAddr() Addr {
	if !c.ok() {
		return nil
	}
	return c.fd.laddr
}

// RemoteAddr returns the remote network address.
func (c *conn) RemoteAddr() Addr {
	if !c.ok() {
		return nil
	}
	return c.fd.raddr
}

// SetDeadline implements the Conn SetDeadline method.
func (c *conn) SetDeadline(t time.Time) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setDeadline(c.fd, t)
}

// SetReadDeadline implements the Conn SetReadDeadline method.
func (c *conn) SetReadDeadline(t time.Time) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setReadDeadline(c.fd, t)
}

// SetWriteDeadline implements the Conn SetWriteDeadline method.
func (c *conn) SetWriteDeadline(t time.Time) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setWriteDeadline(c.fd, t)
}

// SetReadBuffer sets the size of the operating system's
// receive buffer associated with the connection.
func (c *conn) SetReadBuffer(bytes int) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setReadBuffer(c.fd, bytes)
}

// SetWriteBuffer sets the size of the operating system's
// transmit buffer associated with the connection.
func (c *conn) SetWriteBuffer(bytes int) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setWriteBuffer(c.fd, bytes)
}

// File returns a copy of the underlying os.File, set to blocking mode.
// It is the caller's responsibility to close f when finished.
// Closing c does not affect f, and closing f does not affect c.
func (c *conn) File() (f *os.File, err error) { return c.fd.dup() }

// Close closes the connection.
func (c *conn) Close() error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return c.fd.Close()
}
<|endoftext|>


<commit_before>package openstack

import (
	"github.com/haklop/gophercloud-extensions/network"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/rackspace/gophercloud"
	"github.com/racker/perigee"
	"fmt"
	"log"
)

func resourceLBaaS() *schema.Resource {
	return &schema.Resource{
		Create: resourceLBaaSCreate,
		Read:   resourceLBaaSRead,
		Update: resourceLBaaSUpdate,
		Delete: resourceLBaaSDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"provider": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"subnet_id": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"protocol": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"lb_method": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"vip_protocol_port": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
			},
			"floating_ip_pool_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"floating_ip": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"member": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"port": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},
						"instance_id": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"member_id": &schema.Schema{
							Type:     schema.TypeString,
							Computed: true,
						},
					},
				},
			},
			"monitor": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"type": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"delay": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},
						"timeout": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},
						"max_retries": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},
						"expected_codes": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
						"http_method": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
						"monitor_id": &schema.Schema{
							Type:     schema.TypeString,
							Computed: true,
						},
					},
				},
			},
		},
	}
}

func resourceLBaaSCreate(d *schema.ResourceData, meta interface{}) error {

	p := meta.(*Config)
	networksApi, err := p.getNetworkApi()
	if err != nil {
		return err
	}

	pool, err := networksApi.CreatePool(network.NewPool{
		Name:        d.Get("name").(string),
		SubnetId:    d.Get("subnet_id").(string),
		LoadMethod:  d.Get("lb_method").(string),
		Protocol:    d.Get("protocol").(string),
		Description: d.Get("description").(string),
		Provider: d.Get("provider").(string),
	})

	if err != nil {
		return err
	}

	d.SetId(pool.Id)

	membersCount := d.Get("member.#").(int)
	members := make([]*poolMember, 0, membersCount)
	for i := 0; i < membersCount; i++ {
		prefix := fmt.Sprintf("member.%d", i)

		var member poolMember
		member.ProtocolPort = d.Get(prefix + ".port").(int)
		member.InstanceId = d.Get(prefix + ".instance_id").(string)

		members = append(members, &member)
	}

	for _, member := range members {
		// TODO order ports
		ports, err := networksApi.GetPorts()
		if err != nil {
			return err
		}

		var address string
		for _, port := range ports {
			if port.DeviceId == member.InstanceId {
				for _, ips := range port.FixedIps {
					// if possible, select a port on pool subnet
					if ips.SubnetId == d.Get("subnet_id").(string) || address == "" {
						address = ips.IpAddress
					}
				}
			}
		}

		newMember := network.NewMember{
			ProtocolPort: member.ProtocolPort,
			PoolId:       d.Id(),
			AdminStateUp: true,
			Address:      address,
		}

		result, err := networksApi.CreateMember(newMember)
		if err != nil {
			return err
		}
		member.MemberId = result.Id

		// TODO save memberId
	}

	monitorsCount := d.Get("monitor.#").(int)
	for i := 0; i < monitorsCount; i++ {
		prefix := fmt.Sprintf("monitor.%d", i)

		var monitor network.NewMonitor
		monitor.Type = d.Get(prefix + ".type").(string)
		monitor.Delay = d.Get(prefix + ".delay").(int)
		monitor.Timeout = d.Get(prefix + ".timeout").(int)
		monitor.MaxRetries = d.Get(prefix + ".max_retries").(int)
		monitor.ExpectedCodes = d.Get(prefix + ".expected_codes").(string)
		monitor.HttpMethod = d.Get(prefix + ".http_method").(string)

		result, err := networksApi.CreateMonitor(monitor)
		if err != nil {
			return err
		}

		log.Printf("monitor: %#v", result)

		err = networksApi.AssociateMonitor(result.Id, d.Id())
		if err != nil {
			return err
		}
		// TODO save monitor id

	}

	vipProtocolPort, ok := d.GetOk("vip_protocol_port")
	if (!ok) {
		return nil
	}

	vip, err := networksApi.CreateVip(network.NewVip{
		Name: d.Get("name").(string) + "_vip",
		Protocol: d.Get("protocol").(string),
		SubnetId: d.Get("subnet_id").(string),
		ProtocolPort: vipProtocolPort.(int),
		PoolId: d.Id(),
	})

	// TODO floating ip
	floatingIpPoolId, ok := d.GetOk("floating_ip_pool_id")
	if (!ok) {
		return nil
	}

	serversApi, err := p.getServersApi()
	if err != nil {
		return err
	}

	floaingIps, err := serversApi.ListFloatingIps()
	if err != nil {
		return err
	}

	var newIp gophercloud.FloatingIp
	hasFloatingIps := false

	for _, element := range floaingIps {
		// use first floating ip available on the pool
		if element.Pool == floatingIpPoolId.(string) && element.InstanceId == "" {
			newIp = element
			hasFloatingIps = true
		}
	}

	// if there is no available floating ips, try to create a new one
	if !hasFloatingIps {
		newIp, err = serversApi.CreateFloatingIp(floatingIpPoolId.(string))
		if err != nil {
			return err
		}
	}

	err = serversApi.AssociateFloatingIp(vip.Id, newIp)
	if err != nil {
		return err
	}

	d.Set("floating_ip", newIp.Ip)

	if err != nil {
		return err
	}

	return nil

}

func resourceLBaaSDelete(d *schema.ResourceData, meta interface{}) error {
	p := meta.(*Config)
	networksApi, err := p.getNetworkApi()
	if err != nil {
		return err
	}

	pool, err := networksApi.GetPool(d.Id())
	if err != nil {
		return err
	}

	for _, member := range pool.Members {
		err = networksApi.DeleteMember(member)
		if err != nil {
			return err
		}
	}

	for _, monitor := range pool.HealthMonitors {

		err = networksApi.UnassociateMonitor(monitor, d.Id())
		if err != nil {
			return err
		}

		err = networksApi.DeleteMonitor(monitor)
		if err != nil {
			return err
		}
	}

	if len(pool.VipId) > 0 {
		err = networksApi.DeleteVip(pool.VipId)
		if err != nil {
			return err
		}
	}

	return networksApi.DeletePool(d.Id())

}

func resourceLBaaSRead(d *schema.ResourceData, meta interface{}) error {
	p := meta.(*Config)
	networksApi, err := p.getNetworkApi()
	if err != nil {
		return err
	}

	pool, err := networksApi.GetPool(d.Id())
	if err != nil {
		httpError, ok := err.(*perigee.UnexpectedResponseCodeError)
		if !ok {
			return err
		}

		if httpError.Actual == 404 {
			d.SetId("")
			return nil
		}

		return err
	}

	d.Set("name", pool.Name)
	d.Set("description", pool.Description)
	d.Set("lb_method", pool.LoadMethod)

	// TODO compare pool.Members and pool.HealthMonitors

	return nil

}

func resourceLBaaSUpdate(d *schema.ResourceData, meta interface{}) error {
	p := meta.(*Config)
	networksApi, err := p.getNetworkApi()
	if err != nil {
		return err
	}

	updatedPool := network.Pool{
		Id: d.Id(),
	}

	if d.HasChange("name") {
		updatedPool.Name = d.Get("name").(string)
	}

	if d.HasChange("lb_method") {
		updatedPool.LoadMethod = d.Get("lb_method").(string)
	}

	if d.HasChange("description") {
		updatedPool.Description = d.Get("description").(string)
	}

	_, err = networksApi.UpdatePool(updatedPool)

	// TODO update members and HealthMonitors

	return err

}

type poolMember struct {
	ProtocolPort int
	InstanceId   string
	MemberId     string
}
<commit_msg>LBaaS resource can allocate a floating ip<commit_after>package openstack

import (
	"github.com/haklop/gophercloud-extensions/network"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/racker/perigee"
	"fmt"
	"log"
)

func resourceLBaaS() *schema.Resource {
	return &schema.Resource{
		Create: resourceLBaaSCreate,
		Read:   resourceLBaaSRead,
		Update: resourceLBaaSUpdate,
		Delete: resourceLBaaSDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"provider": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"subnet_id": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"protocol": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"lb_method": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"vip_protocol_port": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
			},
			"floating_ip_pool_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"floating_ip": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"member": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"port": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},
						"instance_id": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"member_id": &schema.Schema{
							Type:     schema.TypeString,
							Computed: true,
						},
					},
				},
			},
			"monitor": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"type": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"delay": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},
						"timeout": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},
						"max_retries": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},
						"expected_codes": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
						"http_method": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
						"monitor_id": &schema.Schema{
							Type:     schema.TypeString,
							Computed: true,
						},
					},
				},
			},
		},
	}
}

func resourceLBaaSCreate(d *schema.ResourceData, meta interface{}) error {

	p := meta.(*Config)
	networksApi, err := p.getNetworkApi()
	if err != nil {
		return err
	}

	pool, err := networksApi.CreatePool(network.NewPool{
		Name:        d.Get("name").(string),
		SubnetId:    d.Get("subnet_id").(string),
		LoadMethod:  d.Get("lb_method").(string),
		Protocol:    d.Get("protocol").(string),
		Description: d.Get("description").(string),
		Provider: d.Get("provider").(string),
	})

	if err != nil {
		return err
	}

	d.SetId(pool.Id)

	membersCount := d.Get("member.#").(int)
	members := make([]*poolMember, 0, membersCount)
	for i := 0; i < membersCount; i++ {
		prefix := fmt.Sprintf("member.%d", i)

		var member poolMember
		member.ProtocolPort = d.Get(prefix + ".port").(int)
		member.InstanceId = d.Get(prefix + ".instance_id").(string)

		members = append(members, &member)
	}

	for _, member := range members {
		// TODO order ports
		ports, err := networksApi.GetPorts()
		if err != nil {
			return err
		}

		var address string
		for _, port := range ports {
			if port.DeviceId == member.InstanceId {
				for _, ips := range port.FixedIps {
					// if possible, select a port on pool subnet
					if ips.SubnetId == d.Get("subnet_id").(string) || address == "" {
						address = ips.IpAddress
					}
				}
			}
		}

		newMember := network.NewMember{
			ProtocolPort: member.ProtocolPort,
			PoolId:       d.Id(),
			AdminStateUp: true,
			Address:      address,
		}

		result, err := networksApi.CreateMember(newMember)
		if err != nil {
			return err
		}
		member.MemberId = result.Id

		// TODO save memberId
	}

	monitorsCount := d.Get("monitor.#").(int)
	for i := 0; i < monitorsCount; i++ {
		prefix := fmt.Sprintf("monitor.%d", i)

		var monitor network.NewMonitor
		monitor.Type = d.Get(prefix + ".type").(string)
		monitor.Delay = d.Get(prefix + ".delay").(int)
		monitor.Timeout = d.Get(prefix + ".timeout").(int)
		monitor.MaxRetries = d.Get(prefix + ".max_retries").(int)
		monitor.ExpectedCodes = d.Get(prefix + ".expected_codes").(string)
		monitor.HttpMethod = d.Get(prefix + ".http_method").(string)

		result, err := networksApi.CreateMonitor(monitor)
		if err != nil {
			return err
		}

		log.Printf("monitor: %#v", result)

		err = networksApi.AssociateMonitor(result.Id, d.Id())
		if err != nil {
			return err
		}
		// TODO save monitor id

	}

	vipProtocolPort, ok := d.GetOk("vip_protocol_port")
	if (!ok) {
		return nil
	}

	vip, err := networksApi.CreateVip(network.NewVip{
		Name: d.Get("name").(string) + "_vip",
		Protocol: d.Get("protocol").(string),
		SubnetId: d.Get("subnet_id").(string),
		ProtocolPort: vipProtocolPort.(int),
		PoolId: d.Id(),
	})

	if err != nil {
		return err
	}

	// TODO retrieve pool_id corresponding to the pool name
	_, ok = d.GetOk("floating_ip_pool_id")
	if (!ok) {
		return nil
	}

	floatingIps, err := networksApi.ListFloatingIps()
	if err != nil {
		return err
	}

	var newIp network.FloatingIp
	hasFloatingIps := false

	for _, element := range floatingIps {
		// TODO check the pool id
		// use first floating ip available on the pool
		log.Printf("ips: %#v", element)
		if len(element.PortId) == 0 {
			newIp = element
			hasFloatingIps = true
		}
	}

	// if there is no available floating ips, try to create a new one
	// FIXME create floatingIp with neutron
	/*if !hasFloatingIps {
		newIp, err = serversApi.CreateFloatingIp(floatingIpPoolId.(string))
		if err != nil {
			return err
		}
	}
	*/

	if hasFloatingIps {
		log.Printf("associate %#v with  %#v", vip, newIp)
		err = networksApi.AssociateFloatingIp(vip.PortId, newIp.Id)
		if err != nil {
			return err
		}

		d.Set("floating_ip", newIp.FloatingIpAddress)

		if err != nil {
			return err
		}
	} else {
		return fmt.Errorf("Unable to associate a floating ip")
	}

	return nil

}

func resourceLBaaSDelete(d *schema.ResourceData, meta interface{}) error {
	p := meta.(*Config)
	networksApi, err := p.getNetworkApi()
	if err != nil {
		return err
	}

	pool, err := networksApi.GetPool(d.Id())
	if err != nil {
		return err
	}

	for _, member := range pool.Members {
		err = networksApi.DeleteMember(member)
		if err != nil {
			return err
		}
	}

	for _, monitor := range pool.HealthMonitors {

		err = networksApi.UnassociateMonitor(monitor, d.Id())
		if err != nil {
			return err
		}

		err = networksApi.DeleteMonitor(monitor)
		if err != nil {
			return err
		}
	}

	if len(pool.VipId) > 0 {
		err = networksApi.DeleteVip(pool.VipId)
		if err != nil {
			return err
		}
	}

	return networksApi.DeletePool(d.Id())

}

func resourceLBaaSRead(d *schema.ResourceData, meta interface{}) error {
	p := meta.(*Config)
	networksApi, err := p.getNetworkApi()
	if err != nil {
		return err
	}

	pool, err := networksApi.GetPool(d.Id())
	if err != nil {
		httpError, ok := err.(*perigee.UnexpectedResponseCodeError)
		if !ok {
			return err
		}

		if httpError.Actual == 404 {
			d.SetId("")
			return nil
		}

		return err
	}

	d.Set("name", pool.Name)
	d.Set("description", pool.Description)
	d.Set("lb_method", pool.LoadMethod)

	// TODO compare pool.Members and pool.HealthMonitors

	return nil

}

func resourceLBaaSUpdate(d *schema.ResourceData, meta interface{}) error {
	p := meta.(*Config)
	networksApi, err := p.getNetworkApi()
	if err != nil {
		return err
	}

	updatedPool := network.Pool{
		Id: d.Id(),
	}

	if d.HasChange("name") {
		updatedPool.Name = d.Get("name").(string)
	}

	if d.HasChange("lb_method") {
		updatedPool.LoadMethod = d.Get("lb_method").(string)
	}

	if d.HasChange("description") {
		updatedPool.Description = d.Get("description").(string)
	}

	_, err = networksApi.UpdatePool(updatedPool)

	// TODO update members and HealthMonitors

	return err

}

type poolMember struct {
	ProtocolPort int
	InstanceId   string
	MemberId     string
}
<|endoftext|>