<commit_before>/*
Copyright 2017 Google Inc.
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 stackdriver

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	"github.com/Stackdriver/stackdriver-prometheus/retrieval"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/golang/glog"
	timestamp_pb "github.com/golang/protobuf/ptypes/timestamp"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/prometheus/pkg/labels"
	"github.com/prometheus/prometheus/pkg/timestamp"
	distribution_pb "google.golang.org/genproto/googleapis/api/distribution"
	metric_pb "google.golang.org/genproto/googleapis/api/metric"
	monitoredres_pb "google.golang.org/genproto/googleapis/api/monitoredres"
	monitoring_pb "google.golang.org/genproto/googleapis/monitoring/v3"
)

var supportedMetricTypes = map[dto.MetricType]struct{}{
	dto.MetricType_COUNTER:   struct{}{},
	dto.MetricType_GAUGE:     struct{}{},
	dto.MetricType_HISTOGRAM: struct{}{},
	dto.MetricType_SUMMARY:   struct{}{},
	dto.MetricType_UNTYPED:   struct{}{},
}

const (
	falseValueEpsilon = 0.001
	maxLabelCount     = 10
)

type unsupportedTypeError struct {
	metricType dto.MetricType
}

func (e *unsupportedTypeError) Error() string {
	return e.metricType.String()
}

// Translator allows converting Prometheus samples to Stackdriver TimeSeries.
type Translator struct {
	logger           log.Logger
	metricsPrefix    string
	resourceMappings []ResourceMap
}

// NewTranslator creates a new Translator.
func NewTranslator(logger log.Logger, metricsPrefix string, resourceMappings []ResourceMap) *Translator {
	return &Translator{
		logger:           logger,
		metricsPrefix:    metricsPrefix,
		resourceMappings: resourceMappings,
	}
}

// ToCreateTimeSeriesRequest translates metrics in Prometheus format to Stackdriver format.
func (t *Translator) ToCreateTimeSeriesRequest(
	metrics []*retrieval.MetricFamily) *monitoring_pb.CreateTimeSeriesRequest {

	// TODO(jkohen): See if it's possible for Prometheus to pass two points
	// for the same time series, which isn't accepted by the Stackdriver
	// Monitoring API.
	request := &monitoring_pb.CreateTimeSeriesRequest{}
	for _, family := range metrics {
		tss, err := t.translateFamily(family)
		if err != nil {
			// Ignore unsupported type errors, they're just noise.
			if _, ok := err.(*unsupportedTypeError); !ok {
				level.Warn(t.logger).Log(
					"msg", "error while processing metric",
					"metric", family.GetName(),
					"err", err)
			}
		} else {
			request.TimeSeries = append(request.TimeSeries, tss...)
		}
	}
	return request
}

func (t *Translator) translateFamily(family *retrieval.MetricFamily) ([]*monitoring_pb.TimeSeries, error) {
	if _, found := supportedMetricTypes[family.GetType()]; !found {
		return nil, &unsupportedTypeError{family.GetType()}
	}
	// This isn't exact, because not all metric types map to a single time
	// series. Notoriously, summary maps to 2 or more.
	tss := make([]*monitoring_pb.TimeSeries, 0, len(family.GetMetric()))
	for i, metric := range family.GetMetric() {
		startTime := timestamp.Time(family.MetricResetTimestampMs[i])
		monitoredResource := t.getMonitoredResource(family.TargetLabels, metric.GetLabel())
		if monitoredResource == nil {
			// Metrics are usually independent, so just drop this one.
			level.Warn(t.logger).Log(
				"msg", "cannot extract Stackdriver monitored resource from metric",
				"family", family.GetName(),
				"target_labels", family.TargetLabels,
				"metric", metric)
			continue
		}
		switch family.GetType() {
		case dto.MetricType_SUMMARY:
			ts, err := t.translateSummary(family.GetName(), monitoredResource, metric, startTime)
			if err != nil {
				// Metrics are usually independent, so just drop this one.
				level.Warn(t.logger).Log(
					"msg", "error while processing metric",
					"family", family.GetName(),
					"metric", metric,
					"err", err)
				continue
			}
			tss = append(tss, ts...)
		default:
			ts, err := t.translateOne(family.GetName(), monitoredResource, family.GetType(), metric, startTime)
			if err != nil {
				// Metrics are usually independent, so just drop this one.
				level.Warn(t.logger).Log(
					"msg", "error while processing metric",
					"family", family.GetName(),
					"metric", metric,
					"err", err)
				continue
			}
			tss = append(tss, ts)
		}
	}
	return tss, nil
}

// getMetricType creates metric type name base on the metric prefix, and metric name.
func getMetricType(metricsPrefix string, name string) string {
	// This does no allocations, versus 12 with fmt.Sprintf.
	return metricsPrefix + "/" + name
}

func getTimestamp(ts time.Time) *timestamp_pb.Timestamp {
	return &timestamp_pb.Timestamp{
		Seconds: ts.Truncate(time.Second).Unix(),
		Nanos:   int32(ts.Nanosecond()),
	}
}

// assumes that mType is Counter, Gauge, Untyped, or Histogram. Returns nil on error.
func (t *Translator) translateOne(name string,
	monitoredResource *monitoredres_pb.MonitoredResource,
	mType dto.MetricType,
	metric *dto.Metric,
	start time.Time) (*monitoring_pb.TimeSeries, error) {
	interval := &monitoring_pb.TimeInterval{
		EndTime: getTimestamp(timestamp.Time(metric.GetTimestampMs()).UTC()),
	}
	metricKind := extractMetricKind(mType)
	if metricKind == metric_pb.MetricDescriptor_CUMULATIVE {
		interval.StartTime = getTimestamp(start.UTC())
	}
	valueType := extractValueType(mType)
	point := &monitoring_pb.Point{
		Interval: interval,
	}
	setValue(mType, valueType, metric, point)

	tsLabels := getMetricLabels(metric.GetLabel())
	if len(tsLabels) > maxLabelCount {
		return nil, fmt.Errorf(
			"dropping metric because it has more than %v labels, and Stackdriver would reject it",
			maxLabelCount)
	}
	return &monitoring_pb.TimeSeries{
		Metric: &metric_pb.Metric{
			Labels: tsLabels,
			Type:   getMetricType(t.metricsPrefix, name),
		},
		Resource:   monitoredResource,
		MetricKind: metricKind,
		ValueType:  valueType,
		Points:     []*monitoring_pb.Point{point},
	}, nil
}

// assumes that mType is Counter, Gauge, Untyped, or Histogram. Returns nil on error.
func (t *Translator) translateSummary(name string,
	monitoredResource *monitoredres_pb.MonitoredResource,
	metric *dto.Metric,
	start time.Time) ([]*monitoring_pb.TimeSeries, error) {
	interval := &monitoring_pb.TimeInterval{
		EndTime: getTimestamp(timestamp.Time(metric.GetTimestampMs()).UTC()),
	}
	tsLabels := getMetricLabels(metric.GetLabel())
	if len(tsLabels) > maxLabelCount {
		return nil, fmt.Errorf(
			"dropping metric because it has more than %v labels, and Stackdriver would reject it",
			maxLabelCount)
	}

	baseMetricType := getMetricType(t.metricsPrefix, name)
	summary := metric.GetSummary()
	tss := make([]*monitoring_pb.TimeSeries, 2+len(summary.GetQuantile()))
	// Sum metric. Summary works over a sliding window, so this value could go down, hence GAUGE.
	tss[0] = &monitoring_pb.TimeSeries{
		Metric: &metric_pb.Metric{
			Labels: tsLabels,
			Type:   baseMetricType + "_sum",
		},
		Resource:   monitoredResource,
		MetricKind: metric_pb.MetricDescriptor_GAUGE,
		ValueType:  metric_pb.MetricDescriptor_DOUBLE,
		Points: []*monitoring_pb.Point{
			{
				Interval: interval,
				Value: &monitoring_pb.TypedValue{
					&monitoring_pb.TypedValue_DoubleValue{DoubleValue: summary.GetSampleSum()},
				},
			},
		},
	}
	// Count metric. Summary works over a sliding window, so this value could go down, hence GAUGE.
	tss[1] = &monitoring_pb.TimeSeries{
		Metric: &metric_pb.Metric{
			Labels: tsLabels,
			Type:   baseMetricType + "_count",
		},
		Resource:   monitoredResource,
		MetricKind: metric_pb.MetricDescriptor_CUMULATIVE,
		ValueType:  metric_pb.MetricDescriptor_INT64,
		Points: []*monitoring_pb.Point{
			{
				Interval: &monitoring_pb.TimeInterval{
					StartTime: getTimestamp(start.UTC()),
					EndTime:   getTimestamp(timestamp.Time(metric.GetTimestampMs()).UTC()),
				},
				Value: &monitoring_pb.TypedValue{
					&monitoring_pb.TypedValue_Int64Value{Int64Value: int64(summary.GetSampleCount())},
				},
			},
		},
	}

	for i, quantile := range summary.GetQuantile() {
		qLabels := make(map[string]string, len(tsLabels))
		for k, v := range tsLabels {
			qLabels[k] = v
		}
		// Format using ddd.dddd format (no exponent) with the minimum number of digits necessary.
		qLabels["quantile"] = strconv.FormatFloat(quantile.GetQuantile(), 'f', -1, 64)
		tss[i+2] = &monitoring_pb.TimeSeries{
			Metric: &metric_pb.Metric{
				Labels: qLabels,
				Type:   baseMetricType,
			},
			Resource:   monitoredResource,
			MetricKind: metric_pb.MetricDescriptor_GAUGE,
			ValueType:  metric_pb.MetricDescriptor_DOUBLE,
			Points: []*monitoring_pb.Point{
				{
					Interval: interval,
					Value: &monitoring_pb.TypedValue{
						&monitoring_pb.TypedValue_DoubleValue{DoubleValue: quantile.GetValue()},
					},
				},
			},
		}
	}

	return tss, nil
}

func setValue(
	mType dto.MetricType, valueType metric_pb.MetricDescriptor_ValueType,
	metric *dto.Metric, point *monitoring_pb.Point) {

	point.Value = &monitoring_pb.TypedValue{}
	switch mType {
	case dto.MetricType_GAUGE:
		setValueBaseOnSimpleType(metric.GetGauge().GetValue(), valueType, point)
	case dto.MetricType_COUNTER:
		setValueBaseOnSimpleType(metric.GetCounter().GetValue(), valueType, point)
	case dto.MetricType_HISTOGRAM:
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_DistributionValue{
				DistributionValue: convertToDistributionValue(metric.GetHistogram()),
			},
		}
	case dto.MetricType_UNTYPED:
		setValueBaseOnSimpleType(metric.GetUntyped().GetValue(), valueType, point)
	}
}

func setValueBaseOnSimpleType(
	value float64, valueType metric_pb.MetricDescriptor_ValueType,
	point *monitoring_pb.Point) {

	if valueType == metric_pb.MetricDescriptor_DOUBLE {
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_DoubleValue{DoubleValue: value},
		}
	} else {
		glog.Errorf("Value type '%s' is not supported yet.", valueType)
	}
}

func convertToDistributionValue(h *dto.Histogram) *distribution_pb.Distribution {
	count := int64(h.GetSampleCount())
	mean := float64(0)
	dev := float64(0)
	bounds := make([]float64, 0, len(h.Bucket))
	values := make([]int64, 0, len(h.Bucket))

	if count > 0 {
		mean = h.GetSampleSum() / float64(count)
	}

	prevVal := uint64(0)
	lower := float64(0)
	for _, b := range h.Bucket {
		upper := b.GetUpperBound()
		if math.IsInf(upper, 1) {
			upper = lower
		} else {
			bounds = append(bounds, upper)
		}
		val := b.GetCumulativeCount() - prevVal
		x := (lower + upper) / float64(2)
		dev += float64(val) * (x - mean) * (x - mean)

		values = append(values, int64(b.GetCumulativeCount()-prevVal))

		lower = b.GetUpperBound()
		prevVal = b.GetCumulativeCount()
	}

	return &distribution_pb.Distribution{
		Count: count,
		Mean:  mean,
		SumOfSquaredDeviation: dev,
		BucketOptions: &distribution_pb.Distribution_BucketOptions{
			Options: &distribution_pb.Distribution_BucketOptions_ExplicitBuckets{
				ExplicitBuckets: &distribution_pb.Distribution_BucketOptions_Explicit{
					Bounds: bounds,
				},
			},
		},
		BucketCounts: values,
	}
}

// getMetricLabels returns a Stackdriver label map from the label.
//
// By convention it excludes any Prometheus labels with "_" prefix, which
// includes the labels that correspond to Stackdriver resource labels.
func getMetricLabels(labels []*dto.LabelPair) map[string]string {
	metricLabels := map[string]string{}
	for _, label := range labels {
		if strings.HasPrefix(label.GetName(), "_") {
			continue
		}
		metricLabels[label.GetName()] = label.GetValue()
	}
	return metricLabels
}

func extractMetricKind(mType dto.MetricType) metric_pb.MetricDescriptor_MetricKind {
	if mType == dto.MetricType_COUNTER || mType == dto.MetricType_HISTOGRAM {
		return metric_pb.MetricDescriptor_CUMULATIVE
	}
	return metric_pb.MetricDescriptor_GAUGE
}

func extractValueType(mType dto.MetricType) metric_pb.MetricDescriptor_ValueType {
	if mType == dto.MetricType_HISTOGRAM {
		return metric_pb.MetricDescriptor_DISTRIBUTION
	}
	return metric_pb.MetricDescriptor_DOUBLE
}

func (t *Translator) getMonitoredResource(
	targetLabels labels.Labels, metricLabels []*dto.LabelPair) *monitoredres_pb.MonitoredResource {
	for _, resource := range t.resourceMappings {
		if labels := resource.Translate(targetLabels, metricLabels); labels != nil {
			return &monitoredres_pb.MonitoredResource{
				Type:   resource.Type,
				Labels: labels,
			}
		}
	}
	return nil
}
<commit_msg>Fixed comments for summary.<commit_after>/*
Copyright 2017 Google Inc.
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 stackdriver

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	"github.com/Stackdriver/stackdriver-prometheus/retrieval"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/golang/glog"
	timestamp_pb "github.com/golang/protobuf/ptypes/timestamp"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/prometheus/pkg/labels"
	"github.com/prometheus/prometheus/pkg/timestamp"
	distribution_pb "google.golang.org/genproto/googleapis/api/distribution"
	metric_pb "google.golang.org/genproto/googleapis/api/metric"
	monitoredres_pb "google.golang.org/genproto/googleapis/api/monitoredres"
	monitoring_pb "google.golang.org/genproto/googleapis/monitoring/v3"
)

var supportedMetricTypes = map[dto.MetricType]struct{}{
	dto.MetricType_COUNTER:   struct{}{},
	dto.MetricType_GAUGE:     struct{}{},
	dto.MetricType_HISTOGRAM: struct{}{},
	dto.MetricType_SUMMARY:   struct{}{},
	dto.MetricType_UNTYPED:   struct{}{},
}

const (
	falseValueEpsilon = 0.001
	maxLabelCount     = 10
)

type unsupportedTypeError struct {
	metricType dto.MetricType
}

func (e *unsupportedTypeError) Error() string {
	return e.metricType.String()
}

// Translator allows converting Prometheus samples to Stackdriver TimeSeries.
type Translator struct {
	logger           log.Logger
	metricsPrefix    string
	resourceMappings []ResourceMap
}

// NewTranslator creates a new Translator.
func NewTranslator(logger log.Logger, metricsPrefix string, resourceMappings []ResourceMap) *Translator {
	return &Translator{
		logger:           logger,
		metricsPrefix:    metricsPrefix,
		resourceMappings: resourceMappings,
	}
}

// ToCreateTimeSeriesRequest translates metrics in Prometheus format to Stackdriver format.
func (t *Translator) ToCreateTimeSeriesRequest(
	metrics []*retrieval.MetricFamily) *monitoring_pb.CreateTimeSeriesRequest {

	// TODO(jkohen): See if it's possible for Prometheus to pass two points
	// for the same time series, which isn't accepted by the Stackdriver
	// Monitoring API.
	request := &monitoring_pb.CreateTimeSeriesRequest{}
	for _, family := range metrics {
		tss, err := t.translateFamily(family)
		if err != nil {
			// Ignore unsupported type errors, they're just noise.
			if _, ok := err.(*unsupportedTypeError); !ok {
				level.Warn(t.logger).Log(
					"msg", "error while processing metric",
					"metric", family.GetName(),
					"err", err)
			}
		} else {
			request.TimeSeries = append(request.TimeSeries, tss...)
		}
	}
	return request
}

func (t *Translator) translateFamily(family *retrieval.MetricFamily) ([]*monitoring_pb.TimeSeries, error) {
	if _, found := supportedMetricTypes[family.GetType()]; !found {
		return nil, &unsupportedTypeError{family.GetType()}
	}
	// This isn't exact, because not all metric types map to a single time
	// series. Notoriously, summary maps to 2 or more.
	tss := make([]*monitoring_pb.TimeSeries, 0, len(family.GetMetric()))
	for i, metric := range family.GetMetric() {
		startTime := timestamp.Time(family.MetricResetTimestampMs[i])
		monitoredResource := t.getMonitoredResource(family.TargetLabels, metric.GetLabel())
		if monitoredResource == nil {
			// Metrics are usually independent, so just drop this one.
			level.Warn(t.logger).Log(
				"msg", "cannot extract Stackdriver monitored resource from metric",
				"family", family.GetName(),
				"target_labels", family.TargetLabels,
				"metric", metric)
			continue
		}
		switch family.GetType() {
		case dto.MetricType_SUMMARY:
			ts, err := t.translateSummary(family.GetName(), monitoredResource, metric, startTime)
			if err != nil {
				// Metrics are usually independent, so just drop this one.
				level.Warn(t.logger).Log(
					"msg", "error while processing metric",
					"family", family.GetName(),
					"metric", metric,
					"err", err)
				continue
			}
			tss = append(tss, ts...)
		default:
			ts, err := t.translateOne(family.GetName(), monitoredResource, family.GetType(), metric, startTime)
			if err != nil {
				// Metrics are usually independent, so just drop this one.
				level.Warn(t.logger).Log(
					"msg", "error while processing metric",
					"family", family.GetName(),
					"metric", metric,
					"err", err)
				continue
			}
			tss = append(tss, ts)
		}
	}
	return tss, nil
}

// getMetricType creates metric type name base on the metric prefix, and metric name.
func getMetricType(metricsPrefix string, name string) string {
	// This does no allocations, versus 12 with fmt.Sprintf.
	return metricsPrefix + "/" + name
}

func getTimestamp(ts time.Time) *timestamp_pb.Timestamp {
	return &timestamp_pb.Timestamp{
		Seconds: ts.Truncate(time.Second).Unix(),
		Nanos:   int32(ts.Nanosecond()),
	}
}

// assumes that mType is Counter, Gauge, Untyped, or Histogram. Returns nil on error.
func (t *Translator) translateOne(name string,
	monitoredResource *monitoredres_pb.MonitoredResource,
	mType dto.MetricType,
	metric *dto.Metric,
	start time.Time) (*monitoring_pb.TimeSeries, error) {
	interval := &monitoring_pb.TimeInterval{
		EndTime: getTimestamp(timestamp.Time(metric.GetTimestampMs()).UTC()),
	}
	metricKind := extractMetricKind(mType)
	if metricKind == metric_pb.MetricDescriptor_CUMULATIVE {
		interval.StartTime = getTimestamp(start.UTC())
	}
	valueType := extractValueType(mType)
	point := &monitoring_pb.Point{
		Interval: interval,
	}
	setValue(mType, valueType, metric, point)

	tsLabels := getMetricLabels(metric.GetLabel())
	if len(tsLabels) > maxLabelCount {
		return nil, fmt.Errorf(
			"dropping metric because it has more than %v labels, and Stackdriver would reject it",
			maxLabelCount)
	}
	return &monitoring_pb.TimeSeries{
		Metric: &metric_pb.Metric{
			Labels: tsLabels,
			Type:   getMetricType(t.metricsPrefix, name),
		},
		Resource:   monitoredResource,
		MetricKind: metricKind,
		ValueType:  valueType,
		Points:     []*monitoring_pb.Point{point},
	}, nil
}

// assumes that mType is Counter, Gauge, Untyped, or Histogram. Returns nil on error.
func (t *Translator) translateSummary(name string,
	monitoredResource *monitoredres_pb.MonitoredResource,
	metric *dto.Metric,
	start time.Time) ([]*monitoring_pb.TimeSeries, error) {
	interval := &monitoring_pb.TimeInterval{
		EndTime: getTimestamp(timestamp.Time(metric.GetTimestampMs()).UTC()),
	}
	tsLabels := getMetricLabels(metric.GetLabel())
	if len(tsLabels) > maxLabelCount {
		return nil, fmt.Errorf(
			"dropping metric because it has more than %v labels, and Stackdriver would reject it",
			maxLabelCount)
	}

	baseMetricType := getMetricType(t.metricsPrefix, name)
	summary := metric.GetSummary()
	tss := make([]*monitoring_pb.TimeSeries, 2+len(summary.GetQuantile()))
	// Sum metric. This is a GAUGE because the sum can decrease if the metric tracks negative values.
	tss[0] = &monitoring_pb.TimeSeries{
		Metric: &metric_pb.Metric{
			Labels: tsLabels,
			Type:   baseMetricType + "_sum",
		},
		Resource:   monitoredResource,
		MetricKind: metric_pb.MetricDescriptor_GAUGE,
		ValueType:  metric_pb.MetricDescriptor_DOUBLE,
		Points: []*monitoring_pb.Point{
			{
				Interval: interval,
				Value: &monitoring_pb.TypedValue{
					&monitoring_pb.TypedValue_DoubleValue{DoubleValue: summary.GetSampleSum()},
				},
			},
		},
	}
	// Count metric. While summary works over a sliding window, this value is monotonically increasing:
	// https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations
	tss[1] = &monitoring_pb.TimeSeries{
		Metric: &metric_pb.Metric{
			Labels: tsLabels,
			Type:   baseMetricType + "_count",
		},
		Resource:   monitoredResource,
		MetricKind: metric_pb.MetricDescriptor_CUMULATIVE,
		ValueType:  metric_pb.MetricDescriptor_INT64,
		Points: []*monitoring_pb.Point{
			{
				Interval: &monitoring_pb.TimeInterval{
					StartTime: getTimestamp(start.UTC()),
					EndTime:   getTimestamp(timestamp.Time(metric.GetTimestampMs()).UTC()),
				},
				Value: &monitoring_pb.TypedValue{
					&monitoring_pb.TypedValue_Int64Value{Int64Value: int64(summary.GetSampleCount())},
				},
			},
		},
	}

	for i, quantile := range summary.GetQuantile() {
		qLabels := make(map[string]string, len(tsLabels))
		for k, v := range tsLabels {
			qLabels[k] = v
		}
		// Format using ddd.dddd format (no exponent) with the minimum number of digits necessary.
		qLabels["quantile"] = strconv.FormatFloat(quantile.GetQuantile(), 'f', -1, 64)
		tss[i+2] = &monitoring_pb.TimeSeries{
			Metric: &metric_pb.Metric{
				Labels: qLabels,
				Type:   baseMetricType,
			},
			Resource:   monitoredResource,
			MetricKind: metric_pb.MetricDescriptor_GAUGE,
			ValueType:  metric_pb.MetricDescriptor_DOUBLE,
			Points: []*monitoring_pb.Point{
				{
					Interval: interval,
					Value: &monitoring_pb.TypedValue{
						&monitoring_pb.TypedValue_DoubleValue{DoubleValue: quantile.GetValue()},
					},
				},
			},
		}
	}

	return tss, nil
}

func setValue(
	mType dto.MetricType, valueType metric_pb.MetricDescriptor_ValueType,
	metric *dto.Metric, point *monitoring_pb.Point) {

	point.Value = &monitoring_pb.TypedValue{}
	switch mType {
	case dto.MetricType_GAUGE:
		setValueBaseOnSimpleType(metric.GetGauge().GetValue(), valueType, point)
	case dto.MetricType_COUNTER:
		setValueBaseOnSimpleType(metric.GetCounter().GetValue(), valueType, point)
	case dto.MetricType_HISTOGRAM:
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_DistributionValue{
				DistributionValue: convertToDistributionValue(metric.GetHistogram()),
			},
		}
	case dto.MetricType_UNTYPED:
		setValueBaseOnSimpleType(metric.GetUntyped().GetValue(), valueType, point)
	}
}

func setValueBaseOnSimpleType(
	value float64, valueType metric_pb.MetricDescriptor_ValueType,
	point *monitoring_pb.Point) {

	if valueType == metric_pb.MetricDescriptor_DOUBLE {
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_DoubleValue{DoubleValue: value},
		}
	} else {
		glog.Errorf("Value type '%s' is not supported yet.", valueType)
	}
}

func convertToDistributionValue(h *dto.Histogram) *distribution_pb.Distribution {
	count := int64(h.GetSampleCount())
	mean := float64(0)
	dev := float64(0)
	bounds := make([]float64, 0, len(h.Bucket))
	values := make([]int64, 0, len(h.Bucket))

	if count > 0 {
		mean = h.GetSampleSum() / float64(count)
	}

	prevVal := uint64(0)
	lower := float64(0)
	for _, b := range h.Bucket {
		upper := b.GetUpperBound()
		if math.IsInf(upper, 1) {
			upper = lower
		} else {
			bounds = append(bounds, upper)
		}
		val := b.GetCumulativeCount() - prevVal
		x := (lower + upper) / float64(2)
		dev += float64(val) * (x - mean) * (x - mean)

		values = append(values, int64(b.GetCumulativeCount()-prevVal))

		lower = b.GetUpperBound()
		prevVal = b.GetCumulativeCount()
	}

	return &distribution_pb.Distribution{
		Count: count,
		Mean:  mean,
		SumOfSquaredDeviation: dev,
		BucketOptions: &distribution_pb.Distribution_BucketOptions{
			Options: &distribution_pb.Distribution_BucketOptions_ExplicitBuckets{
				ExplicitBuckets: &distribution_pb.Distribution_BucketOptions_Explicit{
					Bounds: bounds,
				},
			},
		},
		BucketCounts: values,
	}
}

// getMetricLabels returns a Stackdriver label map from the label.
//
// By convention it excludes any Prometheus labels with "_" prefix, which
// includes the labels that correspond to Stackdriver resource labels.
func getMetricLabels(labels []*dto.LabelPair) map[string]string {
	metricLabels := map[string]string{}
	for _, label := range labels {
		if strings.HasPrefix(label.GetName(), "_") {
			continue
		}
		metricLabels[label.GetName()] = label.GetValue()
	}
	return metricLabels
}

func extractMetricKind(mType dto.MetricType) metric_pb.MetricDescriptor_MetricKind {
	if mType == dto.MetricType_COUNTER || mType == dto.MetricType_HISTOGRAM {
		return metric_pb.MetricDescriptor_CUMULATIVE
	}
	return metric_pb.MetricDescriptor_GAUGE
}

func extractValueType(mType dto.MetricType) metric_pb.MetricDescriptor_ValueType {
	if mType == dto.MetricType_HISTOGRAM {
		return metric_pb.MetricDescriptor_DISTRIBUTION
	}
	return metric_pb.MetricDescriptor_DOUBLE
}

func (t *Translator) getMonitoredResource(
	targetLabels labels.Labels, metricLabels []*dto.LabelPair) *monitoredres_pb.MonitoredResource {
	for _, resource := range t.resourceMappings {
		if labels := resource.Translate(targetLabels, metricLabels); labels != nil {
			return &monitoredres_pb.MonitoredResource{
				Type:   resource.Type,
				Labels: labels,
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package openstack_test

import (
	. "launchpad.net/gocheck"
	"launchpad.net/goose/identity"
	"launchpad.net/goose/nova"
	"launchpad.net/juju-core/environs"
	"launchpad.net/goose/testservices/openstackservice"
	"launchpad.net/juju-core/environs/openstack"
	"net/http"
	"net/http/httptest"
)

// Register tests to run against a test Openstack instance (service doubles).
func registerServiceDoubleTests() {
	cred := &identity.Credentials{
		User:       "fred",
		Secrets:    "secret",
		Region:     "some region",
		TenantName: "some tenant",
	}
	Suite(&localLiveSuite{
		LiveTests: LiveTests{
			cred: cred,
		},
	})
}

type localLiveSuite struct {
	LiveTests
	// The following attributes are for using the service doubles.
	Server     *httptest.Server
	Mux        *http.ServeMux
	oldHandler http.Handler
}

func (s *localLiveSuite) SetUpSuite(c *C) {
	c.Logf("Using openstack service test doubles")

	openstack.ShortTimeouts(true)
	// Set up the HTTP server.
	s.Server = httptest.NewServer(nil)
	s.oldHandler = s.Server.Config.Handler
	s.Mux = http.NewServeMux()
	s.Server.Config.Handler = s.Mux

	s.cred.URL = s.Server.URL
	srv := openstackservice.New(s.cred)
	srv.SetupHTTP(s.Mux)

	s.LiveTests.SetUpSuite(c)
}

func (s *localLiveSuite) TearDownSuite(c *C) {
	s.LiveTests.TearDownSuite(c)
	s.Mux = nil
	s.Server.Config.Handler = s.oldHandler
	s.Server.Close()
	openstack.ShortTimeouts(false)
}

func (s *localLiveSuite) SetUpTest(c *C) {
	s.LiveTests.SetUpTest(c)
}

func (s *localLiveSuite) TearDownTest(c *C) {
	s.LiveTests.TearDownTest(c)
}

// ported from lp:juju/juju/providers/openstack/tests/test_machine.py
var addressTests = []struct {
	summary  string
	private  []nova.IPAddress
	public   []nova.IPAddress
	networks []string
	expected string
	failure  error
}{
	{
		summary:  "missing",
		expected: "",
		failure:  environs.ErrNoDNSName,
	},
	{
		summary:  "empty",
		private:  []nova.IPAddress{},
		networks: []string{"private"},
		expected: "",
		failure:  environs.ErrNoDNSName,
	},
	{
		summary:  "private only",
		private:  []nova.IPAddress{{4, "127.0.0.4"}},
		networks: []string{"private"},
		expected: "127.0.0.4",
		failure:  nil,
	},
	{
		summary:  "private plus (HP cloud)",
		private:  []nova.IPAddress{{4, "127.0.0.4"}, {4, "8.8.4.4"}},
		networks: []string{"private"},
		expected: "8.8.4.4",
		failure:  nil,
	},
	{
		summary:  "public only",
		public:   []nova.IPAddress{{4, "8.8.8.8"}},
		networks: []string{"", "public"},
		expected: "8.8.8.8",
		failure:  nil,
	},
	{
		summary:  "public and private",
		private:  []nova.IPAddress{{4, "127.0.0.4"}},
		public:   []nova.IPAddress{{4, "8.8.4.4"}},
		networks: []string{"private", "public"},
		expected: "8.8.4.4",
		failure:  nil,
	},
	{
		summary:  "public private plus",
		private:  []nova.IPAddress{{4, "127.0.0.4"}, {4, "8.8.4.4"}},
		public:   []nova.IPAddress{{4, "8.8.8.8"}},
		networks: []string{"private", "public"},
		expected: "8.8.8.8",
		failure:  nil,
	},
	{
		summary:  "custom only",
		private:  []nova.IPAddress{{4, "127.0.0.2"}},
		networks: []string{"special"},
		expected: "127.0.0.2",
		failure:  nil,
	},
	{
		summary:  "custom and private",
		private:  []nova.IPAddress{{4, "127.0.0.4"}},
		public:   []nova.IPAddress{{4, "127.0.0.2"}},
		networks: []string{"private", "special"},
		expected: "127.0.0.4",
		failure:  nil,
	},
	{
		summary:  "custom and public",
		private:  []nova.IPAddress{{4, "127.0.0.2"}},
		public:   []nova.IPAddress{{4, "8.8.8.8"}},
		networks: []string{"special", "public"},
		expected: "8.8.8.8",
		failure:  nil,
	},
	{
		summary:  "non-IPv4",
		private:  []nova.IPAddress{{6, "::dead:beef:f00d"}},
		networks: []string{"private"},
		expected: "",
		failure:  environs.ErrNoDNSName,
	},
}

func (s *LiveTests) TestGetServerAddresses(c *C) {
	for i, t := range addressTests {
		c.Logf("#%d. %s -> %s (%v)", i, t.summary, t.expected, t.failure)
		addresses := make(map[string][]nova.IPAddress)
		if t.private != nil {
			if len(t.networks) < 1 {
				addresses["private"] = t.private
			} else {
				addresses[t.networks[0]] = t.private
			}
		}
		if t.public != nil {
			if len(t.networks) < 2 {
				addresses["public"] = t.public
			} else {
				addresses[t.networks[1]] = t.public
			}
		}
		addr, err := openstack.GetInstanceAddress(addresses)
		c.Assert(err, Equals, t.failure)
		c.Assert(addr, Equals, t.expected)
	}
}
<commit_msg>Small fix<commit_after>package openstack_test

import (
	. "launchpad.net/gocheck"
	"launchpad.net/goose/identity"
	"launchpad.net/goose/nova"
	"launchpad.net/juju-core/environs"
	"launchpad.net/goose/testservices/openstackservice"
	"launchpad.net/juju-core/environs/openstack"
	"net/http"
	"net/http/httptest"
)

// Register tests to run against a test Openstack instance (service doubles).
func registerServiceDoubleTests() {
	cred := &identity.Credentials{
		User:       "fred",
		Secrets:    "secret",
		Region:     "some region",
		TenantName: "some tenant",
	}
	Suite(&localLiveSuite{
		LiveTests: LiveTests{
			cred: cred,
		},
	})
}

type localLiveSuite struct {
	LiveTests
	// The following attributes are for using the service doubles.
	Server     *httptest.Server
	Mux        *http.ServeMux
	oldHandler http.Handler
}

func (s *localLiveSuite) SetUpSuite(c *C) {
	c.Logf("Using openstack service test doubles")

	openstack.ShortTimeouts(true)
	// Set up the HTTP server.
	s.Server = httptest.NewServer(nil)
	s.oldHandler = s.Server.Config.Handler
	s.Mux = http.NewServeMux()
	s.Server.Config.Handler = s.Mux

	s.cred.URL = s.Server.URL
	srv := openstackservice.New(s.cred)
	srv.SetupHTTP(s.Mux)

	s.LiveTests.SetUpSuite(c)
}

func (s *localLiveSuite) TearDownSuite(c *C) {
	s.LiveTests.TearDownSuite(c)
	s.Mux = nil
	s.Server.Config.Handler = s.oldHandler
	s.Server.Close()
	openstack.ShortTimeouts(false)
}

func (s *localLiveSuite) SetUpTest(c *C) {
	s.LiveTests.SetUpTest(c)
}

func (s *localLiveSuite) TearDownTest(c *C) {
	s.LiveTests.TearDownTest(c)
}

// ported from lp:juju/juju/providers/openstack/tests/test_machine.py
var addressTests = []struct {
	summary  string
	private  []nova.IPAddress
	public   []nova.IPAddress
	networks []string
	expected string
	failure  error
}{
	{
		summary:  "missing",
		expected: "",
		failure:  environs.ErrNoDNSName,
	},
	{
		summary:  "empty",
		private:  []nova.IPAddress{},
		networks: []string{"private"},
		expected: "",
		failure:  environs.ErrNoDNSName,
	},
	{
		summary:  "private only",
		private:  []nova.IPAddress{{4, "127.0.0.4"}},
		networks: []string{"private"},
		expected: "127.0.0.4",
		failure:  nil,
	},
	{
		summary:  "private plus (HP cloud)",
		private:  []nova.IPAddress{{4, "127.0.0.4"}, {4, "8.8.4.4"}},
		networks: []string{"private"},
		expected: "8.8.4.4",
		failure:  nil,
	},
	{
		summary:  "public only",
		public:   []nova.IPAddress{{4, "8.8.8.8"}},
		networks: []string{"", "public"},
		expected: "8.8.8.8",
		failure:  nil,
	},
	{
		summary:  "public and private",
		private:  []nova.IPAddress{{4, "127.0.0.4"}},
		public:   []nova.IPAddress{{4, "8.8.4.4"}},
		networks: []string{"private", "public"},
		expected: "8.8.4.4",
		failure:  nil,
	},
	{
		summary:  "public private plus",
		private:  []nova.IPAddress{{4, "127.0.0.4"}, {4, "8.8.4.4"}},
		public:   []nova.IPAddress{{4, "8.8.8.8"}},
		networks: []string{"private", "public"},
		expected: "8.8.8.8",
		failure:  nil,
	},
	{
		summary:  "custom only",
		private:  []nova.IPAddress{{4, "127.0.0.2"}},
		networks: []string{"special"},
		expected: "127.0.0.2",
		failure:  nil,
	},
	{
		summary:  "custom and public",
		private:  []nova.IPAddress{{4, "127.0.0.2"}},
		public:   []nova.IPAddress{{4, "8.8.8.8"}},
		networks: []string{"special", "public"},
		expected: "8.8.8.8",
		failure:  nil,
	},
	{
		summary:  "non-IPv4",
		private:  []nova.IPAddress{{6, "::dead:beef:f00d"}},
		networks: []string{"private"},
		expected: "",
		failure:  environs.ErrNoDNSName,
	},
}

func (s *LiveTests) TestGetServerAddresses(c *C) {
	for i, t := range addressTests {
		c.Logf("#%d. %s -> %s (%v)", i, t.summary, t.expected, t.failure)
		addresses := make(map[string][]nova.IPAddress)
		if t.private != nil {
			if len(t.networks) < 1 {
				addresses["private"] = t.private
			} else {
				addresses[t.networks[0]] = t.private
			}
		}
		if t.public != nil {
			if len(t.networks) < 2 {
				addresses["public"] = t.public
			} else {
				addresses[t.networks[1]] = t.public
			}
		}
		addr, err := openstack.GetInstanceAddress(addresses)
		c.Assert(err, Equals, t.failure)
		c.Assert(addr, Equals, t.expected)
	}
}
<|endoftext|>


<commit_before>package snapshotbackpopulate

import (
	"context"
	"encoding/json"
	"fmt"
	"sort"

	"github.com/rancher/lasso/pkg/cache"
	"github.com/rancher/lasso/pkg/client"
	"github.com/rancher/lasso/pkg/controller"
	rkev1 "github.com/rancher/rancher/pkg/apis/rke.cattle.io/v1"
	cluster2 "github.com/rancher/rancher/pkg/controllers/provisioningv2/cluster"
	provisioningcontrollers "github.com/rancher/rancher/pkg/generated/controllers/provisioning.cattle.io/v1"
	"github.com/rancher/rancher/pkg/types/config"
	corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var (
	snapshotNames = map[string]bool{
		"k3s-etcd-snapshots":  true,
		"rke2-etcd-snapshots": true,
	}
)

type handler struct {
	clusterName  string
	clusterCache provisioningcontrollers.ClusterCache
	clusters     provisioningcontrollers.ClusterClient
}

func Register(ctx context.Context, userContext *config.UserContext) error {
	h := handler{
		clusterName:  userContext.ClusterName,
		clusterCache: userContext.Management.Wrangler.Provisioning.Cluster().Cache(),
		clusters:     userContext.Management.Wrangler.Provisioning.Cluster(),
	}

	// We want to watch two specific objects, not all config maps.  So we setup a custom controller
	// to just watch those names.
	clientFactory, err := client.NewSharedClientFactory(&userContext.RESTConfig, nil)
	if err != nil {
		return err
	}

	for secretName := range snapshotNames {
		cacheFactory := cache.NewSharedCachedFactory(clientFactory, &cache.SharedCacheFactoryOptions{
			DefaultNamespace: "kube-system",
			DefaultTweakList: func(options *metav1.ListOptions) {
				options.FieldSelector = fmt.Sprintf("metadata.name=%s", secretName)
			},
		})
		controllerFactory := controller.NewSharedControllerFactory(cacheFactory, nil)

		controller := corecontrollers.New(controllerFactory)
		controller.ConfigMap().OnChange(ctx, "snapshotbackpopulate", h.OnChange)
		go controllerFactory.Start(ctx, 1)
	}

	return nil
}

func (h *handler) OnChange(key string, configMap *corev1.ConfigMap) (*corev1.ConfigMap, error) {
	if configMap == nil {
		return nil, nil
	}

	if configMap.Namespace != "kube-system" || !snapshotNames[configMap.Name] {
		return configMap, nil
	}

	cluster, err := h.clusterCache.GetByIndex(cluster2.ByCluster, h.clusterName)
	if err != nil || len(cluster) != 1 {
		return configMap, err
	}

	fromConfigMap, err := configMapToSnapshots(configMap)
	if err != nil {
		return configMap, err
	}

	if !equality.Semantic.DeepEqual(cluster[0].Status.ETCDSnapshots, fromConfigMap) {
		cluster := cluster[0].DeepCopy()
		cluster.Status.ETCDSnapshots = fromConfigMap
		_, err = h.clusters.UpdateStatus(cluster)
		return configMap, err
	}

	return configMap, nil
}

func configMapToSnapshots(configMap *corev1.ConfigMap) (result []rkev1.ETCDSnapshot, _ error) {
	for _, v := range configMap.Data {
		file := &snapshotFile{}
		if err := json.Unmarshal([]byte(v), file); err != nil {
			return nil, err
		}
		snapshot := rkev1.ETCDSnapshot{
			Name:      file.Name,
			NodeName:  file.NodeName,
			CreatedAt: file.CreatedAt,
			Size:      file.Size,
		}
		if file.S3 != nil {
			snapshot.S3 = &rkev1.ETCDSnapshotS3{
				Endpoint:      file.S3.Endpoint,
				EndpointCA:    file.S3.EndpointCA,
				SkipSSLVerify: file.S3.SkipSSLVerify,
				Bucket:        file.S3.Bucket,
				Region:        file.S3.Region,
				Folder:        file.S3.Folder,
			}
		}
		result = append(result, snapshot)
	}
	sort.Slice(result, func(i, j int) bool {
		return result[i].Name < result[j].Name
	})
	return result, nil
}

type s3Config struct {
	Endpoint      string `json:"endpoint,omitempty"`
	EndpointCA    string `json:"endpointCA,omitempty"`
	SkipSSLVerify bool   `json:"skipSSLVerify,omitempty"`
	Bucket        string `json:"bucket,omitempty"`
	Region        string `json:"region,omitempty"`
	Folder        string `json:"folder,omitempty"`
}

// snapshotFile represents a single snapshot and it's
// metadata.
type snapshotFile struct {
	Name      string       `json:"name"`
	NodeName  string       `json:"nodeName,omitempty"`
	CreatedAt *metav1.Time `json:"createdAt,omitempty"`
	Size      int64        `json:"size,omitempty"`
	S3        *s3Config    `json:"s3Config,omitempty"`
}
<commit_msg>Only watch specific configmaps to reduce traffic<commit_after>package snapshotbackpopulate

import (
	"context"
	"encoding/json"
	"fmt"
	"sort"

	"github.com/rancher/lasso/pkg/cache"
	"github.com/rancher/lasso/pkg/client"
	"github.com/rancher/lasso/pkg/controller"
	rkev1 "github.com/rancher/rancher/pkg/apis/rke.cattle.io/v1"
	cluster2 "github.com/rancher/rancher/pkg/controllers/provisioningv2/cluster"
	provisioningcontrollers "github.com/rancher/rancher/pkg/generated/controllers/provisioning.cattle.io/v1"
	"github.com/rancher/rancher/pkg/types/config"
	corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var (
	configMapNames = map[string]bool{
		"k3s-etcd-snapshots":  true,
		"rke2-etcd-snapshots": true,
	}
)

type handler struct {
	clusterName  string
	clusterCache provisioningcontrollers.ClusterCache
	clusters     provisioningcontrollers.ClusterClient
}

func Register(ctx context.Context, userContext *config.UserContext) error {
	h := handler{
		clusterName:  userContext.ClusterName,
		clusterCache: userContext.Management.Wrangler.Provisioning.Cluster().Cache(),
		clusters:     userContext.Management.Wrangler.Provisioning.Cluster(),
	}

	// We want to watch two specific objects, not all config maps.  So we setup a custom controller
	// to just watch those names.
	clientFactory, err := client.NewSharedClientFactory(&userContext.RESTConfig, nil)
	if err != nil {
		return err
	}

	for configMapName := range configMapNames {
		cacheFactory := cache.NewSharedCachedFactory(clientFactory, &cache.SharedCacheFactoryOptions{
			DefaultNamespace: "kube-system",
			DefaultTweakList: func(options *metav1.ListOptions) {
				options.FieldSelector = fmt.Sprintf("metadata.name=%s", configMapName)
			},
		})
		controllerFactory := controller.NewSharedControllerFactory(cacheFactory, nil)

		controller := corecontrollers.New(controllerFactory)
		controller.ConfigMap().OnChange(ctx, "snapshotbackpopulate", h.OnChange)
		if err := controllerFactory.Start(ctx, 1); err != nil {
			return err
		}
	}

	return nil
}

func (h *handler) OnChange(key string, configMap *corev1.ConfigMap) (*corev1.ConfigMap, error) {
	if configMap == nil {
		return nil, nil
	}

	if configMap.Namespace != "kube-system" || !configMapNames[configMap.Name] {
		return configMap, nil
	}

	cluster, err := h.clusterCache.GetByIndex(cluster2.ByCluster, h.clusterName)
	if err != nil || len(cluster) != 1 {
		return configMap, err
	}

	fromConfigMap, err := h.configMapToSnapshots(configMap)
	if err != nil {
		return configMap, err
	}

	if !equality.Semantic.DeepEqual(cluster[0].Status.ETCDSnapshots, fromConfigMap) {
		cluster := cluster[0].DeepCopy()
		cluster.Status.ETCDSnapshots = fromConfigMap
		_, err = h.clusters.UpdateStatus(cluster)
		return configMap, err
	}

	return configMap, nil
}

func (h *handler) configMapToSnapshots(configMap *corev1.ConfigMap) (result []rkev1.ETCDSnapshot, _ error) {
	for k, v := range configMap.Data {
		file := &snapshotFile{}
		if err := json.Unmarshal([]byte(v), file); err != nil {
			logrus.Errorf("invalid non-json value in %s/%s for key %s in cluster %s", configMap.Namespace, configMap.Name, k, h.clusterName)
			return nil, nil
		}
		snapshot := rkev1.ETCDSnapshot{
			Name:      file.Name,
			NodeName:  file.NodeName,
			CreatedAt: file.CreatedAt,
			Size:      file.Size,
		}
		if file.S3 != nil {
			snapshot.S3 = &rkev1.ETCDSnapshotS3{
				Endpoint:      file.S3.Endpoint,
				EndpointCA:    file.S3.EndpointCA,
				SkipSSLVerify: file.S3.SkipSSLVerify,
				Bucket:        file.S3.Bucket,
				Region:        file.S3.Region,
				Folder:        file.S3.Folder,
			}
		}
		result = append(result, snapshot)
	}
	sort.Slice(result, func(i, j int) bool {
		return result[i].Name < result[j].Name
	})
	return result, nil
}

type s3Config struct {
	Endpoint      string `json:"endpoint,omitempty"`
	EndpointCA    string `json:"endpointCA,omitempty"`
	SkipSSLVerify bool   `json:"skipSSLVerify,omitempty"`
	Bucket        string `json:"bucket,omitempty"`
	Region        string `json:"region,omitempty"`
	Folder        string `json:"folder,omitempty"`
}

// snapshotFile represents a single snapshot and it's
// metadata.
type snapshotFile struct {
	Name      string       `json:"name"`
	NodeName  string       `json:"nodeName,omitempty"`
	CreatedAt *metav1.Time `json:"createdAt,omitempty"`
	Size      int64        `json:"size,omitempty"`
	S3        *s3Config    `json:"s3Config,omitempty"`
}
<|endoftext|>


<commit_before>package gce

import (
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/emccode/rexray/core"
	"github.com/emccode/rexray/core/config"
	"github.com/emccode/rexray/core/errors"
	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/compute/v1"
	"io/ioutil"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"strings"
)

const providerName = "gce"

// The GCE storage driver.
type driver struct {
	currentInstanceId string
	client            *compute.Service
	r                 *core.RexRay
	zone              string
	project           string
}

func ef() errors.Fields {
	return errors.Fields{
		"provider": providerName,
	}
}

func eff(fields errors.Fields) map[string]interface{} {
	errFields := map[string]interface{}{
		"provider": providerName,
	}
	if fields != nil {
		for k, v := range fields {
			errFields[k] = v
		}
	}
	return errFields
}

func init() {
	core.RegisterDriver(providerName, newDriver)
	config.Register(configRegistration())
}

func newDriver() core.Driver {
	return &driver{}
}

func (d *driver) Init(r *core.RexRay) error {
	d.r = r

	var err error

	d.zone = d.r.Config.GetString("gce.zone")
	d.project = d.r.Config.GetString("gce.project")
	serviceAccountJSON, err := ioutil.ReadFile(d.r.Config.GetString("gce.keyfile"))
	if err != nil {
		log.WithField("provider", providerName).Fatalf("Could not read service account credentials file, %s => {%s}", d.r.Config.GetString("gce.keyfile"), err)
		return err
	}

	config, err := google.JWTConfigFromJSON(serviceAccountJSON,
		compute.ComputeScope,
	)
	client, err := compute.New(config.Client(context.Background()))

	if err != nil {
		log.WithField("provider", providerName).Fatalf("Could not create compute client => {%s}", err)
	}
	d.client = client
	d.currentInstanceId = getCurrentInstanceId()
	log.WithField("provider", providerName).Info("storage driver initialized")
	return nil
}

func getCurrentInstanceId() (string, error) {
	conn, err := net.DialTimeout("tcp", "metadata.google.internal:80", 50*time.Millisecond)
	if err != nil {
		return "", fmt.Errorf("Error: %v\n", err)
	}
	defer conn.Close()

	url := "http://metadata.google.internal/computeMetadata/v1/instance/id"
	client := &http.Client{}
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Metadata-Flavor", "Google")
	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("Error: %v\n", err)
	}

	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("Error: %v\n", err)
	}
	return data, nil
}

func (d *driver) Name() string {
	return providerName
}

func (d *driver) GetVolumeMapping() ([]*core.BlockDevice, error) {
	log.WithField("provider", providerName).Debug("GetVolumeMapping")

	diskMap := make(map[string]*compute.Disk)
	disks, err := d.client.Disks.List(d.project, d.zone).Do()
	if err != nil {
		return []*core.BlockDevice{}, err
	}
	for _, disk := range disks.Items {
		diskMap[disk.SelfLink] = disk
	}

	instances, err := d.client.Instances.List(d.project, d.zone).Do()
	if err != nil {
		return []*core.BlockDevice{}, err
	}
	var ret []*core.BlockDevice
	for _, instance := range instances.Items {
		for _, disk := range instance.Disks {
			ret = append(ret, &core.BlockDevice{
				ProviderName: "gce",
				InstanceID:   strconv.FormatUint(instance.Id, 10),
				VolumeID:     strconv.FormatUint(diskMap[disk.Source].Id, 10),
				DeviceName:   disk.DeviceName,
				Region:       diskMap[disk.Source].Zone,
				Status:       diskMap[disk.Source].Status,
				NetworkName:  disk.Source,
			})

		}
	}
	return ret, nil
}

func (d *driver) GetInstance() (*core.Instance, error) {
	log.WithField("provider", providerName).Debug("GetInstance")
	var attachments []*core.VolumeAttachment
	query := d.client.Instances.List(d.project, d.zone)
	query.Filter(fmt.Sprintf("id eq %s", d.currentInstanceId))
	instances, err := query.Do()
	if err != nil {
		return []*core.Instance{}, err
	}
	var ret []*core.Instance
	for _, instance := range instances.Items {
		return &core.Instance{
			ProviderName: "gce",
			InstanceID:   strconv.FormatUint(instance.Id, 10),
			Region:       instance.Zone,
			Region:       instance.Status,
			NetworkName:  instance.Name,
		}),nil

	}
	return nil, nil
}

func (d *driver) CreateSnapshot(
	runAsync bool,
	snapshotName, volumeID, description string) ([]*core.Snapshot, error) {

	log.WithField("provider", providerName).Debug("CreateSnapshot")
	return nil, nil

}

func (d *driver) GetSnapshot(
	volumeID, snapshotID, snapshotName string) ([]*core.Snapshot, error) {

	log.WithField("provider", providerName).Debug("GetSnapshot")
	return nil, nil
}

func (d *driver) RemoveSnapshot(snapshotID string) error {
	log.WithField("provider", providerName).Debug("RemoveSnapshot")
	return nil
}

func (d *driver) GetDeviceNextAvailable() (string, error) {
	letters := []string{
		"a", "b", "c", "d", "e", "f", "g", "h",
		"i", "j", "k", "l", "m", "n", "o", "p"}

	blockDeviceNames := make(map[string]bool)

	blockDeviceMapping, err := d.GetVolumeMapping()
	if err != nil {
		return "", err
	}

	for _, blockDevice := range blockDeviceMapping {
		re, _ := regexp.Compile(`^/dev/xvd([a-z])`)
		res := re.FindStringSubmatch(blockDevice.DeviceName)
		if len(res) > 0 {
			blockDeviceNames[res[1]] = true
		}
	}

	localDevices, err := getLocalDevices()
	if err != nil {
		return "", err
	}

	for _, localDevice := range localDevices {
		re, _ := regexp.Compile(`^xvd([a-z])`)
		res := re.FindStringSubmatch(localDevice)
		if len(res) > 0 {
			blockDeviceNames[res[1]] = true
		}
	}

	for _, letter := range letters {
		if !blockDeviceNames[letter] {
			nextDeviceName := "/dev/xvd" + letter
			log.Println("Got next device name: " + nextDeviceName)
			return nextDeviceName, nil
		}
	}
	return "", errors.New("No available device")
}

func getLocalDevices() (deviceNames []string, err error) {
	file := "/proc/partitions"
	contentBytes, err := ioutil.ReadFile(file)
	if err != nil {
		return []string{}, err
	}

	content := string(contentBytes)

	lines := strings.Split(content, "\n")
	for _, line := range lines[2:] {
		fields := strings.Fields(line)
		if len(fields) == 4 {
			deviceNames = append(deviceNames, fields[3])
		}
	}

	return deviceNames, nil
}

func (d *driver) CreateVolume(
	runAsync bool, volumeName, volumeID, snapshotID, volumeType string,
	IOPS, size int64, availabilityZone string) (*core.Volume, error) {
	log.WithField("provider", providerName).Debug("CreateVolume")
	return nil, nil

}

func (d *driver) createVolumeCreateSnapshot(
	volumeID string, snapshotID string) (string, error) {
	log.WithField("provider", providerName).Debug("CreateVolumeCreateSnapshot")
	return "", nil

}

func (d *driver) GetVolume(
	volumeID, volumeName string) ([]*core.Volume, error) {
	log.WithField("provider", providerName).Debugf("GetVolume :%s %s", volumeID, volumeName)

	query := d.client.Disks.List(d.project, d.zone)
	if volumeID != "" {
		query.Filter(fmt.Sprintf("id eq %s", volumeID))
	}
	if volumeName != "" {
		query.Filter(fmt.Sprintf("name eq %s", volumeName))
	}
	var attachments []*core.VolumeAttachment
	instances, err := d.client.Instances.List(d.project, d.zone).Do()
	if err != nil {
		return []*core.Volume{}, err
	}
	for _, instance := range instances.Items {
		for _, disk := range instance.Disks {
			attachment := &core.VolumeAttachment{
				InstanceID: strconv.FormatUint(instance.Id, 10),
				DeviceName: disk.DeviceName,
				Status:     disk.Mode,
				VolumeID:   disk.Source,
			}
			attachments = append(attachments, attachment)

		}
	}

	disks, err := query.Do()
	if err != nil {
		return []*core.Volume{}, err
	}
	var volumesSD []*core.Volume
	for _, disk := range disks.Items {
		var diskAttachments []*core.VolumeAttachment
		for _, attachment := range attachments {
			if attachment.VolumeID == disk.SelfLink {
				diskAttachments = append(diskAttachments, &core.VolumeAttachment{
					InstanceID: attachment.InstanceID,
					DeviceName: attachment.DeviceName,
					Status:     attachment.Status,
					VolumeID:   strconv.FormatUint(disk.Id, 10),
				})
			}
		}
		volumeSD := &core.Volume{
			Name:             disk.Name,
			VolumeID:         strconv.FormatUint(disk.Id, 10),
			AvailabilityZone: disk.Zone,
			Status:           disk.Status,
			VolumeType:       disk.Kind,
			NetworkName:      disk.SelfLink,
			IOPS:             0,
			Size:             strconv.FormatInt(disk.SizeGb, 10),
			Attachments:      diskAttachments,
		}
		volumesSD = append(volumesSD, volumeSD)

	}
	return volumesSD, nil
}

func (d *driver) GetVolumeAttach(
	volumeID, instanceID string) ([]*core.VolumeAttachment, error) {
	log.WithField("provider", providerName).Debugf("GetVolumeAttach :%s %s", volumeID, instanceID)
	var attachments []*core.VolumeAttachment
	query := d.client.Instances.List(d.project, d.zone)
	if instanceID != "" {
		query.Filter(fmt.Sprintf("id eq %s", instanceID))
	}
	instances, err := query.Do()
	if err != nil {
		return []*core.VolumeAttachment{}, err
	}
	for _, instance := range instances.Items {
		for _, disk := range instance.Disks {
			attachment := &core.VolumeAttachment{
				InstanceID: strconv.FormatUint(instance.Id, 10),
				DeviceName: disk.DeviceName,
				Status:     disk.Mode,
				VolumeID:   disk.Source,
			}
			attachments = append(attachments, attachment)

		}
	}
	return attachments, nil
}

func (d *driver) waitSnapshotComplete(snapshotID string) error {
	return nil
}

func (d *driver) waitVolumeComplete(volumeID string) error {
	return nil
}

func (d *driver) waitVolumeAttach(volumeID, instanceID string) error {
	return nil
}

func (d *driver) waitVolumeDetach(volumeID string) error {
	return nil
}

func (d *driver) RemoveVolume(volumeID string) error {
	return nil
}

func (d *driver) AttachVolume(
	runAsync bool,
	volumeID, instanceID string) ([]*core.VolumeAttachment, error) {
	log.WithField("provider", providerName).Debug("AttachVolume")
	return nil, nil

}

func (d *driver) DetachVolume(
	runAsync bool,
	volumeID, blank string) error {
	log.WithField("provider", providerName).Debug("DetachVolume")
	return nil
}

func (d *driver) CopySnapshot(runAsync bool,
	volumeID, snapshotID, snapshotName, destinationSnapshotName,
	destinationRegion string) (*core.Snapshot, error) {
	log.WithField("provider", providerName).Debug("CopySnapshot")
	return nil, nil
}

func configRegistration() *config.Registration {
	r := config.NewRegistration("Google GCE")
	r.Key(config.String, "", "", "", "gce.zone")
	r.Key(config.String, "", "", "", "gce.project")
	r.Key(config.String, "", "", "", "gce.keyfile")
	return r
}
<commit_msg>Implement GetInstance method<commit_after>package gce

import (
	"bytes"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/emccode/rexray/core"
	"github.com/emccode/rexray/core/config"
	"github.com/emccode/rexray/core/errors"
	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/compute/v1"
	"io/ioutil"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const providerName = "gce"

// The GCE storage driver.
type driver struct {
	currentInstanceId string
	client            *compute.Service
	r                 *core.RexRay
	zone              string
	project           string
}

func ef() errors.Fields {
	return errors.Fields{
		"provider": providerName,
	}
}

func eff(fields errors.Fields) map[string]interface{} {
	errFields := map[string]interface{}{
		"provider": providerName,
	}
	if fields != nil {
		for k, v := range fields {
			errFields[k] = v
		}
	}
	return errFields
}

func init() {
	core.RegisterDriver(providerName, newDriver)
	config.Register(configRegistration())
}

func newDriver() core.Driver {
	return &driver{}
}

func (d *driver) Init(r *core.RexRay) error {
	d.r = r

	var err error

	d.zone = d.r.Config.GetString("gce.zone")
	d.project = d.r.Config.GetString("gce.project")
	serviceAccountJSON, err := ioutil.ReadFile(d.r.Config.GetString("gce.keyfile"))
	if err != nil {
		log.WithField("provider", providerName).Fatalf("Could not read service account credentials file, %s => {%s}", d.r.Config.GetString("gce.keyfile"), err)
		return err
	}

	config, err := google.JWTConfigFromJSON(serviceAccountJSON,
		compute.ComputeScope,
	)
	client, err := compute.New(config.Client(context.Background()))

	if err != nil {
		log.WithField("provider", providerName).Fatalf("Could not create compute client => {%s}", err)
	}
	d.client = client
	instanceId, err := getCurrentInstanceId()
	if err != nil {
		return err
	}
	d.currentInstanceId = instanceId
	log.WithField("provider", providerName).Info("storage driver initialized")
	return nil
}

func getCurrentInstanceId() (string, error) {
	conn, err := net.DialTimeout("tcp", "metadata.google.internal:80", 50*time.Millisecond)
	if err != nil {
		return "", err
	}
	defer conn.Close()

	url := "http://metadata.google.internal/computeMetadata/v1/instance/id"
	client := &http.Client{}
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Metadata-Flavor", "Google")
	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("Error: %v\n", err)
	}

	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	n := bytes.Index(data, []byte{0})
	return string(data[:n]), nil
}

func (d *driver) Name() string {
	return providerName
}

func (d *driver) GetVolumeMapping() ([]*core.BlockDevice, error) {
	log.WithField("provider", providerName).Debug("GetVolumeMapping")

	diskMap := make(map[string]*compute.Disk)
	disks, err := d.client.Disks.List(d.project, d.zone).Do()
	if err != nil {
		return []*core.BlockDevice{}, err
	}
	for _, disk := range disks.Items {
		diskMap[disk.SelfLink] = disk
	}

	instances, err := d.client.Instances.List(d.project, d.zone).Do()
	if err != nil {
		return []*core.BlockDevice{}, err
	}
	var ret []*core.BlockDevice
	for _, instance := range instances.Items {
		for _, disk := range instance.Disks {
			ret = append(ret, &core.BlockDevice{
				ProviderName: "gce",
				InstanceID:   strconv.FormatUint(instance.Id, 10),
				VolumeID:     strconv.FormatUint(diskMap[disk.Source].Id, 10),
				DeviceName:   disk.DeviceName,
				Region:       diskMap[disk.Source].Zone,
				Status:       diskMap[disk.Source].Status,
				NetworkName:  disk.Source,
			})

		}
	}
	return ret, nil
}

func (d *driver) GetInstance() (*core.Instance, error) {
	log.WithField("provider", providerName).Debug("GetInstance")
	query := d.client.Instances.List(d.project, d.zone)
	query.Filter(fmt.Sprintf("id eq %s", d.currentInstanceId))
	instances, err := query.Do()
	if err != nil {
		return nil, err
	}
	for _, instance := range instances.Items {
		return &core.Instance{
			ProviderName: "gce",
			InstanceID:   strconv.FormatUint(instance.Id, 10),
			Region:       instance.Zone,
			Name:         instance.Name,
		}, nil

	}
	return nil, nil
}

func (d *driver) CreateSnapshot(
	runAsync bool,
	snapshotName, volumeID, description string) ([]*core.Snapshot, error) {

	log.WithField("provider", providerName).Debug("CreateSnapshot")
	return nil, nil

}

func (d *driver) GetSnapshot(
	volumeID, snapshotID, snapshotName string) ([]*core.Snapshot, error) {

	log.WithField("provider", providerName).Debug("GetSnapshot")
	return nil, nil
}

func (d *driver) RemoveSnapshot(snapshotID string) error {
	log.WithField("provider", providerName).Debug("RemoveSnapshot")
	return nil
}

func (d *driver) GetDeviceNextAvailable() (string, error) {
	letters := []string{
		"a", "b", "c", "d", "e", "f", "g", "h",
		"i", "j", "k", "l", "m", "n", "o", "p"}

	blockDeviceNames := make(map[string]bool)

	blockDeviceMapping, err := d.GetVolumeMapping()
	if err != nil {
		return "", err
	}

	for _, blockDevice := range blockDeviceMapping {
		re, _ := regexp.Compile(`^/dev/xvd([a-z])`)
		res := re.FindStringSubmatch(blockDevice.DeviceName)
		if len(res) > 0 {
			blockDeviceNames[res[1]] = true
		}
	}

	localDevices, err := getLocalDevices()
	if err != nil {
		return "", err
	}

	for _, localDevice := range localDevices {
		re, _ := regexp.Compile(`^xvd([a-z])`)
		res := re.FindStringSubmatch(localDevice)
		if len(res) > 0 {
			blockDeviceNames[res[1]] = true
		}
	}

	for _, letter := range letters {
		if !blockDeviceNames[letter] {
			nextDeviceName := "/dev/xvd" + letter
			log.Println("Got next device name: " + nextDeviceName)
			return nextDeviceName, nil
		}
	}
	return "", errors.New("No available device")
}

func getLocalDevices() (deviceNames []string, err error) {
	file := "/proc/partitions"
	contentBytes, err := ioutil.ReadFile(file)
	if err != nil {
		return []string{}, err
	}

	content := string(contentBytes)

	lines := strings.Split(content, "\n")
	for _, line := range lines[2:] {
		fields := strings.Fields(line)
		if len(fields) == 4 {
			deviceNames = append(deviceNames, fields[3])
		}
	}

	return deviceNames, nil
}

func (d *driver) CreateVolume(
	runAsync bool, volumeName, volumeID, snapshotID, volumeType string,
	IOPS, size int64, availabilityZone string) (*core.Volume, error) {
	log.WithField("provider", providerName).Debug("CreateVolume")
	return nil, nil

}

func (d *driver) createVolumeCreateSnapshot(
	volumeID string, snapshotID string) (string, error) {
	log.WithField("provider", providerName).Debug("CreateVolumeCreateSnapshot")
	return "", nil

}

func (d *driver) GetVolume(
	volumeID, volumeName string) ([]*core.Volume, error) {
	log.WithField("provider", providerName).Debugf("GetVolume :%s %s", volumeID, volumeName)

	query := d.client.Disks.List(d.project, d.zone)
	if volumeID != "" {
		query.Filter(fmt.Sprintf("id eq %s", volumeID))
	}
	if volumeName != "" {
		query.Filter(fmt.Sprintf("name eq %s", volumeName))
	}
	var attachments []*core.VolumeAttachment
	instances, err := d.client.Instances.List(d.project, d.zone).Do()
	if err != nil {
		return []*core.Volume{}, err
	}
	for _, instance := range instances.Items {
		for _, disk := range instance.Disks {
			attachment := &core.VolumeAttachment{
				InstanceID: strconv.FormatUint(instance.Id, 10),
				DeviceName: disk.DeviceName,
				Status:     disk.Mode,
				VolumeID:   disk.Source,
			}
			attachments = append(attachments, attachment)

		}
	}

	disks, err := query.Do()
	if err != nil {
		return []*core.Volume{}, err
	}
	var volumesSD []*core.Volume
	for _, disk := range disks.Items {
		var diskAttachments []*core.VolumeAttachment
		for _, attachment := range attachments {
			if attachment.VolumeID == disk.SelfLink {
				diskAttachments = append(diskAttachments, &core.VolumeAttachment{
					InstanceID: attachment.InstanceID,
					DeviceName: attachment.DeviceName,
					Status:     attachment.Status,
					VolumeID:   strconv.FormatUint(disk.Id, 10),
				})
			}
		}
		volumeSD := &core.Volume{
			Name:             disk.Name,
			VolumeID:         strconv.FormatUint(disk.Id, 10),
			AvailabilityZone: disk.Zone,
			Status:           disk.Status,
			VolumeType:       disk.Kind,
			NetworkName:      disk.SelfLink,
			IOPS:             0,
			Size:             strconv.FormatInt(disk.SizeGb, 10),
			Attachments:      diskAttachments,
		}
		volumesSD = append(volumesSD, volumeSD)

	}
	return volumesSD, nil
}

func (d *driver) GetVolumeAttach(
	volumeID, instanceID string) ([]*core.VolumeAttachment, error) {
	log.WithField("provider", providerName).Debugf("GetVolumeAttach :%s %s", volumeID, instanceID)
	var attachments []*core.VolumeAttachment
	query := d.client.Instances.List(d.project, d.zone)
	if instanceID != "" {
		query.Filter(fmt.Sprintf("id eq %s", instanceID))
	}
	instances, err := query.Do()
	if err != nil {
		return []*core.VolumeAttachment{}, err
	}
	for _, instance := range instances.Items {
		for _, disk := range instance.Disks {
			attachment := &core.VolumeAttachment{
				InstanceID: strconv.FormatUint(instance.Id, 10),
				DeviceName: disk.DeviceName,
				Status:     disk.Mode,
				VolumeID:   disk.Source,
			}
			attachments = append(attachments, attachment)

		}
	}
	return attachments, nil
}

func (d *driver) waitSnapshotComplete(snapshotID string) error {
	return nil
}

func (d *driver) waitVolumeComplete(volumeID string) error {
	return nil
}

func (d *driver) waitVolumeAttach(volumeID, instanceID string) error {
	return nil
}

func (d *driver) waitVolumeDetach(volumeID string) error {
	return nil
}

func (d *driver) RemoveVolume(volumeID string) error {
	return nil
}

func (d *driver) AttachVolume(
	runAsync bool,
	volumeID, instanceID string) ([]*core.VolumeAttachment, error) {
	log.WithField("provider", providerName).Debug("AttachVolume")
	return nil, nil

}

func (d *driver) DetachVolume(
	runAsync bool,
	volumeID, blank string) error {
	log.WithField("provider", providerName).Debug("DetachVolume")
	return nil
}

func (d *driver) CopySnapshot(runAsync bool,
	volumeID, snapshotID, snapshotName, destinationSnapshotName,
	destinationRegion string) (*core.Snapshot, error) {
	log.WithField("provider", providerName).Debug("CopySnapshot")
	return nil, nil
}

func configRegistration() *config.Registration {
	r := config.NewRegistration("Google GCE")
	r.Key(config.String, "", "", "", "gce.zone")
	r.Key(config.String, "", "", "", "gce.project")
	r.Key(config.String, "", "", "", "gce.keyfile")
	return r
}
<|endoftext|>


<commit_before>package config

const (
	Production          = true
	Verbose             = true
	InstaJoinProduction = "#chaos-hd #i3 #noname-ev #test"
	InstaJoinTesting    = "#test #test2"
	NickServPass        = ""
	IrcServer           = "irc.twice-irc.de"
	BotNick             = "frank"
	Master              = "xeen"
	OpOkIn              = "#test #test2 #chaos-hd"
	TopicChanger        = "#test2 #chaos-hd"
	SqlDriver           = "postgres"
	SqlConnect          = "dbname=nnev user=anon host=/var/run/postgresql sslmode=disable"
)
<commit_msg>okay, using production right away wasn’t too clever<commit_after>package config

const (
	Production          = false
	Verbose             = true
	InstaJoinProduction = "#chaos-hd #i3 #noname-ev #test"
	InstaJoinTesting    = "#test #test2"
	NickServPass        = ""
	IrcServer           = "irc.twice-irc.de"
	BotNick             = "frank"
	Master              = "xeen"
	OpOkIn              = "#test #test2 #chaos-hd"
	TopicChanger        = "#test2 #chaos-hd"
	SqlDriver           = "postgres"
	SqlConnect          = "dbname=nnev user=anon host=/var/run/postgresql sslmode=disable"
)
<|endoftext|>


<commit_before>package update

import (
	"errors"
	"fmt"

	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/args"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/flags"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/flagsets"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/with"

	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/util"
	"github.com/BytemarkHosting/bytemark-client/lib"
	brainRequests "github.com/BytemarkHosting/bytemark-client/lib/requests/brain"
	"github.com/urfave/cli"
)

// err.. this whole file is a bit of a fudge at the moment.
// a simpler UpdateVirtualMachine(VirtualMachineName, brain.VirtualMachine)
// would do the job of all the functions completely adequately

func init() {
	Commands = append(Commands, cli.Command{
		Name:      "server",
		Usage:     "update a server's configuration",
		UsageText: "update server [flags] <server>",
		Description: `Updates the configuration of an existing Cloud Server.

Note that for changes to cores, memory or hardware profile to take effect you will need to restart the server.

Hardware profiles can be thought of as what virtual motherboard your server has, and in general it is best to use a pretty recent one for maximum speed. If the server is running an old or experimental OS without support for virtio drivers, or installing an older windows from an ISO without the virtio drivers compiled in, you may require the compatibility profile. See "bytemark show hwprofiles" for which ones are currently available.

Memory is specified in GiB by default, but can be suffixed with an M to indicate that it is provided in MiB.

Updating a server's name also allows it to be moved between groups and accounts you administer.

EXAMPLES
    bytemark update server --memory 768m --hwprofile virtio2018 small-server
	    Changes small-server's memory to 768MiB, and its hwprofile to virtio2018

	bytemark update server --server app-dev --swap-ips-with app-production
	    Swaps the primary IPs (those given to a server upon creation) with another server. Specify --swap-extra-ips to swap both primary and extra IPs with another server. For more granular IP alterations, use the panel for now or petition us to add an 'update ip'. Before swapping the IPs the servers ought to be reconfigured to expect the new IPs and then shut down, otherwise the serial/VNC console will have to be used (see bytemark-console) to configure the networking.

	bytemark update server --server app-dev --swap-ips-with app-production --swap-extra-ips
	    Swaps all the IPs between app-dev and app-production. Before swapping the IPs the servers ought to be reconfigured to expect the new IPs and then shut down, otherwise the serial/VNC console will have to be used (see bytemark-console) to configure the networking.

    bytemark update server --new-name boron oxygen
	    This will rename the server called oxygen in your default group to boron, still in your default group.

	bytemark update server --new-name sunglasses.development sunglasses
		This will move the server called sunglasses into the development group, keeping its name as sunglasses,

	bytemark update server --new-name rennes.bretagne.france charata.chaco.argentina
		This will move the server called charata in the chaco group in the argentina account, placing it in the bretagne group in the france account and rename it to rennes.`,
		Flags: append(app.OutputFlags("server", "object"),
			flagsets.Force,
			cli.GenericFlag{
				Name:  "memory",
				Value: new(flags.SizeSpecFlag),
				Usage: "How much memory the server will have available, specified in GiB or with GiB/MiB units.",
			},
			cli.StringFlag{
				Name:  "hwprofile",
				Usage: "The hardware profile to use. See `bytemark profiles` for a list of hardware profiles available.",
			},
			cli.BoolFlag{
				Name:  "lock-hwprofile",
				Usage: "Locks the hardware profile (prevents it from being automatically upgraded when we release a newer version)",
			},
			cli.BoolFlag{
				Name:  "unlock-hwprofile",
				Usage: "Locks the hardware profile (allows it to be automatically upgraded when we release a newer version)",
			},
			cli.GenericFlag{
				Name:  "new-name",
				Usage: "A new name for the server",
				Value: new(flags.VirtualMachineNameFlag),
			},
			cli.GenericFlag{
				Name:  "swap-ips-with",
				Usage: "A server to swap IP addresses with. Both v4 and v6 are swapped. See description below and --swap-extra-ips",
				Value: new(flags.VirtualMachineNameFlag),
			},
			cli.BoolFlag{
				Name:  "swap-extra-ips",
				Usage: "Swaps extra IPs with the target of --swap-ips-with. When --swap-ips-with is unspecified, this flag is ignored",
			},
			cli.IntFlag{
				Name:  "cores",
				Usage: "the number of cores that should be available to the server",
			},
			cli.StringFlag{
				Name:  "cd-url",
				Usage: "An HTTP(S) URL for an ISO image file to attach as a cdrom",
			},
			cli.BoolFlag{
				Name:  "remove-cd",
				Usage: "Removes any current cdrom, as if the cd were ejected",
			},
			cli.GenericFlag{
				Name:  "server",
				Usage: "The server to update",
				Value: new(flags.VirtualMachineNameFlag),
			},
		),
		Action: app.Action(args.Optional("new-name", "hwprofile", "memory"),
			with.RequiredFlags("server"),
			with.VirtualMachine("server"),
			updateServer),
	})
}

func updateMemory(c *app.Context) error {
	vmName := flags.VirtualMachineName(c, "server")
	memory := flags.Size(c, "memory")

	if memory == 0 {
		return nil
	}
	if c.VirtualMachine.Memory < memory {
		if !flagsets.Forced(c) && !util.PromptYesNo(c.Prompter(), fmt.Sprintf("You're increasing the memory by %dGiB - this may cost more, are you sure?", (memory-c.VirtualMachine.Memory)/1024)) {
			return util.UserRequestedExit{}
		}
	}
	return c.Client().SetVirtualMachineMemory(vmName, memory)
}

func updateHwProfile(c *app.Context) error {
	vmName := flags.VirtualMachineName(c, "server")
	hwProfile := c.String("hwprofile")
	if hwProfile == "" {
		return nil
	}

	return c.Client().SetVirtualMachineHardwareProfile(vmName, hwProfile)
}

func updateLock(c *app.Context) error {
	server := flags.VirtualMachineName(c, "server")

	lockProfile := c.Bool("lock-hwprofile")
	unlockProfile := c.Bool("unlock-hwprofile")
	if lockProfile && unlockProfile {
		return errors.New("--lock-hwprofile and --unlock-hwprofile were both specified - only one may be specified at a time")
	} else if lockProfile {
		return c.Client().SetVirtualMachineHardwareProfileLock(server, true)
	} else if unlockProfile {
		return c.Client().SetVirtualMachineHardwareProfileLock(server, false)
	}
	return nil
}

func updateCores(c *app.Context) error {
	vmName := flags.VirtualMachineName(c, "server")
	cores := c.Int("cores")

	if cores == 0 {
		return nil
	}
	if c.VirtualMachine.Cores < cores {
		if !flagsets.Forced(c) && !util.PromptYesNo(c.Prompter(), fmt.Sprintf("You are increasing the number of cores from %d to %d. This may cause your VM to cost more, are you sure?", c.VirtualMachine.Cores, cores)) {
			return util.UserRequestedExit{}
		}
	}
	return c.Client().SetVirtualMachineCores(vmName, cores)
}

func updateName(c *app.Context) error {
	vmName := flags.VirtualMachineName(c, "server")
	newName := flags.VirtualMachineName(c, "new-name")

	if newName.VirtualMachine == "" {
		return nil
	}
	return c.Client().MoveVirtualMachine(vmName, newName)
}

func updateCdrom(c *app.Context) error {
	vmName := flags.VirtualMachineName(c, "server")
	cdURL := c.String("cd-url")
	removeCD := c.Bool("remove-cd")

	if cdURL == "" && !removeCD {
		return nil
	}
	err := c.Client().SetVirtualMachineCDROM(vmName, cdURL)
	if _, ok := err.(lib.InternalServerError); ok {
		return c.Help("Couldn't set the server's cdrom - check that you have provided a valid public HTTP url")
	}
	return err
}

func swapIPs(ctx *app.Context) error {
	if !ctx.IsSet("swap-ips-with") {
		return nil
	}

	// this is hacky - having to call GetVirtualMachine for the --server vm twice because
	// SwapVirtualMachineIPs calls it internally and we can't pass an ID to it,
	// when ctx.VirtualMachine.ID is already set.
	// TODO: once VirtualMachinePather is in, add VirtualMachineIDer and this
	// function somewhere (brainRequests? It does make a request after all).
	// Then rewrite the hack in the test.
	//
	//         func GetVirtualMachineID(client lib.Client, pather VirtualMachinePather) (id int, err error) {
	//             if ider, ok := pather.(VirtualMachineIDer); ok {
	//                 id = ider.VirtualMachineID()
	//                 return
	//             }
	//             var vm brain.VirtualMachine
	//             vm, err = GetVirtualMachine(client, pather)
	//             return vm.ID, err
	//         }
	server := flags.VirtualMachineName(ctx, "server")
	target := flags.VirtualMachineName(ctx, "swap-ips-with")
	swapExtra := ctx.Bool("swap-extra-ips")
	return brainRequests.SwapVirtualMachineIPs(ctx.Client(), server, target, swapExtra)
}

func updateServer(c *app.Context) error {
	for _, f := range [](func(*app.Context) error){
		swapIPs,
		updateMemory,
		updateHwProfile,
		updateLock,
		updateCores,
		updateCdrom,
		updateName, // needs to be last
	} {
		err := f(c)
		if err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Tweak update server's help text<commit_after>package update

import (
	"errors"
	"fmt"

	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/args"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/flags"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/flagsets"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/with"

	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/util"
	"github.com/BytemarkHosting/bytemark-client/lib"
	brainRequests "github.com/BytemarkHosting/bytemark-client/lib/requests/brain"
	"github.com/urfave/cli"
)

// err.. this whole file is a bit of a fudge at the moment.
// a simpler UpdateVirtualMachine(VirtualMachineName, brain.VirtualMachine)
// would do the job of all the functions completely adequately

func init() {
	Commands = append(Commands, cli.Command{
		Name:      "server",
		Usage:     "update a server's configuration",
		UsageText: "update server [flags] <server>",
		Description: `Updates the configuration of an existing Cloud Server.

    Note that for changes to cores, memory or hardware profile to take effect you will need to restart the server.

    Hardware profiles can be thought of as what virtual motherboard your server has, and in general it is best to use a
    pretty recent one for maximum speed. If the server is running an old or experimental OS without support for virtio
    drivers, or installing an older windows from an ISO without the virtio drivers compiled in, you may require the
    compatibility profile. See "bytemark show hwprofiles" for which ones are currently available.

    Memory is specified in GiB by default, but can be suffixed with an M to indicate that it is provided in MiB.

    Updating a server's name also allows it to be moved between groups and accounts you administer.

EXAMPLES
    bytemark update server --memory 768m --hwprofile virtio2018 small-server
        Changes small-server's memory to 768MiB, and its hwprofile to virtio2018

    bytemark update server --server app-dev --swap-ips-with app-production
        Swaps the primary IPs (those given to a server upon creation) with another server. Specify --swap-extra-ips to
        swap both primary and extra IPs with another server. For more granular IP alterations, use the panel for now or
        petition us to add an 'update ip'. Before swapping the IPs the servers ought to be reconfigured to expect the
        new IPs and then shut down, otherwise the serial/VNC console will have to be used (see bytemark-console) to
        configure the networking.

    bytemark update server --server app-dev --swap-ips-with app-production --swap-extra-ips
        Swaps all the IPs between app-dev and app-production. Before swapping the IPs the servers ought to be
        reconfigured to expect the new IPs and then shut down, otherwise the serial/VNC console will have to be used
        (see bytemark-console) to configure the networking.

    bytemark update server --new-name boron oxygen
        This will rename the server called oxygen in your default group to boron, still in your default group.

    bytemark update server --new-name sunglasses.development sunglasses
        This will move the server called sunglasses into the development group, keeping its name as sunglasses,

    bytemark update server --new-name rennes.bretagne.france charata.chaco.argentina
        This will move the server called charata in the chaco group in the argentina account, placing it in the bretagne
        group in the france account and rename it to rennes.`,
		Flags: append(app.OutputFlags("server", "object"),
			flagsets.Force,
			cli.GenericFlag{
				Name:  "memory",
				Value: new(flags.SizeSpecFlag),
				Usage: "How much memory the server will have available, specified in GiB or with GiB/MiB units.",
			},
			cli.StringFlag{
				Name:  "hwprofile",
				Usage: "The hardware profile to use. See `bytemark profiles` for a list of hardware profiles available.",
			},
			cli.BoolFlag{
				Name:  "lock-hwprofile",
				Usage: "Locks the hardware profile (prevents it from being automatically upgraded when we release a newer version)",
			},
			cli.BoolFlag{
				Name:  "unlock-hwprofile",
				Usage: "Locks the hardware profile (allows it to be automatically upgraded when we release a newer version)",
			},
			cli.GenericFlag{
				Name:  "new-name",
				Usage: "A new name for the server",
				Value: new(flags.VirtualMachineNameFlag),
			},
			cli.GenericFlag{
				Name:  "swap-ips-with",
				Usage: "A server to swap IP addresses with. Both v4 and v6 are swapped. See description and --swap-extra-ips",
				Value: new(flags.VirtualMachineNameFlag),
			},
			cli.BoolFlag{
				Name:  "swap-extra-ips",
				Usage: "Swaps extra IPs with the target of --swap-ips-with. When --swap-ips-with is unspecified, this flag is ignored",
			},
			cli.IntFlag{
				Name:  "cores",
				Usage: "the number of cores that should be available to the server",
			},
			cli.StringFlag{
				Name:  "cd-url",
				Usage: "An HTTP(S) URL for an ISO image file to attach as a cdrom",
			},
			cli.BoolFlag{
				Name:  "remove-cd",
				Usage: "Removes any current cdrom, as if the cd were ejected",
			},
			cli.GenericFlag{
				Name:  "server",
				Usage: "The server to update",
				Value: new(flags.VirtualMachineNameFlag),
			},
		),
		Action: app.Action(args.Optional("new-name", "hwprofile", "memory"),
			with.RequiredFlags("server"),
			with.VirtualMachine("server"),
			updateServer),
	})
}

func updateMemory(c *app.Context) error {
	vmName := flags.VirtualMachineName(c, "server")
	memory := flags.Size(c, "memory")

	if memory == 0 {
		return nil
	}
	if c.VirtualMachine.Memory < memory {
		if !flagsets.Forced(c) && !util.PromptYesNo(c.Prompter(), fmt.Sprintf("You're increasing the memory by %dGiB - this may cost more, are you sure?", (memory-c.VirtualMachine.Memory)/1024)) {
			return util.UserRequestedExit{}
		}
	}
	return c.Client().SetVirtualMachineMemory(vmName, memory)
}

func updateHwProfile(c *app.Context) error {
	vmName := flags.VirtualMachineName(c, "server")
	hwProfile := c.String("hwprofile")
	if hwProfile == "" {
		return nil
	}

	return c.Client().SetVirtualMachineHardwareProfile(vmName, hwProfile)
}

func updateLock(c *app.Context) error {
	server := flags.VirtualMachineName(c, "server")

	lockProfile := c.Bool("lock-hwprofile")
	unlockProfile := c.Bool("unlock-hwprofile")
	if lockProfile && unlockProfile {
		return errors.New("--lock-hwprofile and --unlock-hwprofile were both specified - only one may be specified at a time")
	} else if lockProfile {
		return c.Client().SetVirtualMachineHardwareProfileLock(server, true)
	} else if unlockProfile {
		return c.Client().SetVirtualMachineHardwareProfileLock(server, false)
	}
	return nil
}

func updateCores(c *app.Context) error {
	vmName := flags.VirtualMachineName(c, "server")
	cores := c.Int("cores")

	if cores == 0 {
		return nil
	}
	if c.VirtualMachine.Cores < cores {
		if !flagsets.Forced(c) && !util.PromptYesNo(c.Prompter(), fmt.Sprintf("You are increasing the number of cores from %d to %d. This may cause your VM to cost more, are you sure?", c.VirtualMachine.Cores, cores)) {
			return util.UserRequestedExit{}
		}
	}
	return c.Client().SetVirtualMachineCores(vmName, cores)
}

func updateName(c *app.Context) error {
	vmName := flags.VirtualMachineName(c, "server")
	newName := flags.VirtualMachineName(c, "new-name")

	if newName.VirtualMachine == "" {
		return nil
	}
	return c.Client().MoveVirtualMachine(vmName, newName)
}

func updateCdrom(c *app.Context) error {
	vmName := flags.VirtualMachineName(c, "server")
	cdURL := c.String("cd-url")
	removeCD := c.Bool("remove-cd")

	if cdURL == "" && !removeCD {
		return nil
	}
	err := c.Client().SetVirtualMachineCDROM(vmName, cdURL)
	if _, ok := err.(lib.InternalServerError); ok {
		return c.Help("Couldn't set the server's cdrom - check that you have provided a valid public HTTP url")
	}
	return err
}

func swapIPs(ctx *app.Context) error {
	if !ctx.IsSet("swap-ips-with") {
		return nil
	}

	// this is hacky - having to call GetVirtualMachine for the --server vm twice because
	// SwapVirtualMachineIPs calls it internally and we can't pass an ID to it,
	// when ctx.VirtualMachine.ID is already set.
	// TODO: once VirtualMachinePather is in, add VirtualMachineIDer and this
	// function somewhere (brainRequests? It does make a request after all).
	// Then rewrite the hack in the test.
	//
	//         func GetVirtualMachineID(client lib.Client, pather VirtualMachinePather) (id int, err error) {
	//             if ider, ok := pather.(VirtualMachineIDer); ok {
	//                 id = ider.VirtualMachineID()
	//                 return
	//             }
	//             var vm brain.VirtualMachine
	//             vm, err = GetVirtualMachine(client, pather)
	//             return vm.ID, err
	//         }
	server := flags.VirtualMachineName(ctx, "server")
	target := flags.VirtualMachineName(ctx, "swap-ips-with")
	swapExtra := ctx.Bool("swap-extra-ips")
	return brainRequests.SwapVirtualMachineIPs(ctx.Client(), server, target, swapExtra)
}

func updateServer(c *app.Context) error {
	for _, f := range [](func(*app.Context) error){
		swapIPs,
		updateMemory,
		updateHwProfile,
		updateLock,
		updateCores,
		updateCdrom,
		updateName, // needs to be last
	} {
		err := f(c)
		if err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright (C) 2015-2016 Thomas de Zeeuw.
//
// Licensed under the MIT license that can be found in the LICENSE file.

package logger

import (
	"bytes"
	"errors"
	"reflect"
	"runtime"
	"testing"
	"time"

	"github.com/kylelemons/godebug/pretty"
)

// Time returned in calling now(), setup and test in init.
var t1 = time.Date(2015, 9, 1, 14, 22, 36, 0, time.UTC)

func init() {
	now = func() time.Time {
		return t1
	}
}

// EventWriter that collects the events and errors.
type eventWriter struct {
	events []Event
	errors []error
	closed bool
}

func (ew *eventWriter) Write(event Event) error {
	ew.events = append(ew.events, event)
	return nil
}

func (ew *eventWriter) HandleError(err error) {
	ew.errors = append(ew.errors, err)
}

func (ew *eventWriter) Close() error {
	ew.closed = true
	return nil
}

// A data type to be used in calling Log.
type user struct {
	ID   int
	Name string
}

func TestLog(t *testing.T) {
	defer reset()
	var ew eventWriter
	Start(&ew)

	tags := Tags{"my", "tags"}
	eventType := NewEventType("my-event-type")
	data := user{1, "Thomas"}
	event := Event{
		Type:    eventType,
		Tags:    tags,
		Message: "My event",
		Data:    data,
	}
	recv := getPanicRecoveredValue("Fatal message")

	Debug(tags, "Debug message")
	Debugf(tags, "Debug %s message", "formatted")
	Info(tags, "Info message")
	Infof(tags, "Info %s message", "formatted")
	Warn(tags, "Warn message")
	Warnf(tags, "Warn %s message", "formatted")
	Error(tags, errors.New("Error message"))
	Errorf(tags, "Error %s message", "formatted")
	Fatal(tags, recv)
	testThumstone(tags)
	Log(event)

	if err := Close(); err != nil {
		t.Fatal("Unexpected error closing: " + err.Error())
	}

	if len(ew.errors) != 0 {
		t.Fatalf("Unexpected error(s): %v", ew.errors)
	}

	_, file, _, _ := runtime.Caller(0)

	expected := []Event{
		{Type: DebugEvent, Message: "Debug message"},
		{Type: DebugEvent, Message: "Debug formatted message"},
		{Type: InfoEvent, Message: "Info message"},
		{Type: InfoEvent, Message: "Info formatted message"},
		{Type: WarnEvent, Message: "Warn message"},
		{Type: WarnEvent, Message: "Warn formatted message"},
		{Type: ErrorEvent, Message: "Error message"},
		{Type: ErrorEvent, Message: "Error formatted message"},
		{Type: FatalEvent, Message: "Fatal message"},
		{Type: ThumbEvent, Message: "Function testThumstone called by github.com" +
			"/Thomasdezeeuw/logger.TestLog, from file " + file + " on line 79"},
		event,
	}

	if len(ew.events) != len(expected) {
		t.Fatalf("Expected to have %d events, but got %d",
			len(expected), len(ew.events))
	}

	for i, event := range ew.events {
		expectedEvent := expected[i]
		expectedEvent.Timestamp = now()
		expectedEvent.Tags = tags

		if expectedEvent.Type == FatalEvent {
			// sortof test the stack trace, best we can do.
			stackTrace := event.Data.([]byte)
			if !bytes.HasPrefix(stackTrace, []byte("goroutine")) {
				t.Errorf("Expected a stack trace as data for a Fatal event, but got %s ",
					string(stackTrace))
			} else if bytes.Index(stackTrace, []byte("logger.getStackTrace")) != -1 ||
				bytes.Index(stackTrace, []byte("logger.Fatal")) != -1 {
				t.Errorf("Expected the stack trace to not contain the logger.Fatal and "+
					"logger.getStackTrace, but got %s ", string(stackTrace))
			}

			event.Data = nil
		}

		if !reflect.DeepEqual(expectedEvent, event) {
			diff := pretty.Compare(event, expectedEvent)
			t.Errorf("Unexpected difference in event #%d: %s", i, diff)
		}
	}
}

func getPanicRecoveredValue(msg string) (recv interface{}) {
	defer func() {
		recv = recover()
	}()
	panic(msg)
}

func testThumstone(tags Tags) {
	Thumbstone(tags, "testThumstone")
}

func TestStartTwice(t *testing.T) {
	defer reset()
	var ew eventWriter
	Start(&ew)
	if err := Close(); err != nil {
		t.Fatal("Unexpected error closing initial log: " + err.Error())
	}

	defer expectPanic(t, "logger: can only Start once")
	Start(&ew)
}

func TestStartNoEventWriter(t *testing.T) {
	defer reset()
	defer expectPanic(t, "logger: need atleast a single EventWriter to write to")
	Start()
}

func expectPanic(t *testing.T, expected string) {
	recv := recover()
	if recv == nil {
		t.Fatal(`Expected a panic, but didn't get one`)
	}

	got := recv.(string)
	if got != expected {
		t.Fatalf("Expected panic value to be %s, but got %s", expected, got)
	}
}

// EventWriter that always returns a write error with the event message in it.
type errorEventWriter struct {
	closeError error
	errors     []error
}

func (eew *errorEventWriter) Write(event Event) error {
	return errors.New("Write error: " + event.Message)
}

func (eew *errorEventWriter) HandleError(err error) {
	eew.errors = append(eew.errors, err)
}

func (eew *errorEventWriter) Close() error {
	return eew.closeError
}

func TestErrorEventWriter(t *testing.T) {
	closeError := errors.New("Close error")

	defer reset()
	eew := errorEventWriter{closeError: closeError}
	Start(&eew)

	tags := Tags{"my", "tags"}
	Info(tags, "Info message1")
	Info(tags, "Won't be written to the writer")

	if err := Close(); err != closeError {
		t.Fatalf("Expceted the closing error to be %v, but got %v",
			closeError, err)
	}

	if expected, got := maxNWriteErrors+1, len(eew.errors); got != expected {
		t.Fatalf("Expected %d errors, but only got %d", expected, got)
	}

	// Expected errors:
	// 0 - 4: Write error: Info message1.
	// 5:     EventWriter is bad.
	expected := errors.New("Write error: Info message1")
	for i, got := range eew.errors {
		if i == 5 {
			expected = ErrBadEventWriter
		}

		if got.Error() != expected.Error() {
			t.Errorf("Expected error #%d to be %q, but got %q",
				i, expected.Error(), got.Error())
		}
	}
}

func reset() {
	eventChannel = make(chan Event, defaultEventChannelSize)
	eventChannelClosed = make(chan struct{}, 1)
	eventWriters = []EventWriter{}
	started = false
}

func TestGetStackTrace(t *testing.T) {
	t.Parallel()

	// Fake the Fatal call.
	var stackTrace []byte
	func() {
		stackTrace = getStackTrace()
	}()

	if !bytes.HasPrefix(stackTrace, []byte("goroutine")) {
		t.Errorf("Expected the stack trace to start with goroutine, but got %s ",
			string(stackTrace))
	} else if bytes.Index(stackTrace, []byte("logger.getStackTrace")) != -1 ||
		bytes.Index(stackTrace, []byte("logger.TestGetStackTrace.func1")) != -1 {
		t.Errorf("Expected the stack trace to not contain the "+
			"logger.TestGetStackTrace.func1 and logger.getStackTrace, but got it: %s",
			string(stackTrace))
	}
}
<commit_msg>Simplify test code<commit_after>// Copyright (C) 2015-2016 Thomas de Zeeuw.
//
// Licensed under the MIT license that can be found in the LICENSE file.

package logger

import (
	"bytes"
	"errors"
	"reflect"
	"runtime"
	"testing"
	"time"

	"github.com/kylelemons/godebug/pretty"
)

// Time returned in calling now(), setup and test in init.
var t1 = time.Date(2015, 9, 1, 14, 22, 36, 0, time.UTC)

func init() {
	now = func() time.Time {
		return t1
	}
}

// EventWriter that collects the events and errors.
type eventWriter struct {
	events []Event
	errors []error
	closed bool
}

func (ew *eventWriter) Write(event Event) error {
	ew.events = append(ew.events, event)
	return nil
}

func (ew *eventWriter) HandleError(err error) {
	ew.errors = append(ew.errors, err)
}

func (ew *eventWriter) Close() error {
	ew.closed = true
	return nil
}

// A data type to be used in calling Log.
type user struct {
	ID   int
	Name string
}

func TestLog(t *testing.T) {
	defer reset()
	var ew eventWriter
	Start(&ew)

	tags := Tags{"my", "tags"}
	eventType := NewEventType("my-event-type")
	data := user{1, "Thomas"}
	event := Event{
		Type:    eventType,
		Tags:    tags,
		Message: "My event",
		Data:    data,
	}
	recv := getPanicRecoveredValue("Fatal message")

	Debug(tags, "Debug message")
	Debugf(tags, "Debug %s message", "formatted")
	Info(tags, "Info message")
	Infof(tags, "Info %s message", "formatted")
	Warn(tags, "Warn message")
	Warnf(tags, "Warn %s message", "formatted")
	Error(tags, errors.New("Error message"))
	Errorf(tags, "Error %s message", "formatted")
	Fatal(tags, recv)
	testThumstone(tags)
	Log(event)

	if err := Close(); err != nil {
		t.Fatal("Unexpected error closing: " + err.Error())
	}

	if len(ew.errors) != 0 {
		t.Fatalf("Unexpected error(s): %v", ew.errors)
	}

	_, file, _, _ := runtime.Caller(0)

	expected := []Event{
		{Type: DebugEvent, Message: "Debug message"},
		{Type: DebugEvent, Message: "Debug formatted message"},
		{Type: InfoEvent, Message: "Info message"},
		{Type: InfoEvent, Message: "Info formatted message"},
		{Type: WarnEvent, Message: "Warn message"},
		{Type: WarnEvent, Message: "Warn formatted message"},
		{Type: ErrorEvent, Message: "Error message"},
		{Type: ErrorEvent, Message: "Error formatted message"},
		{Type: FatalEvent, Message: "Fatal message"},
		{Type: ThumbEvent, Message: "Function testThumstone called by github.com" +
			"/Thomasdezeeuw/logger.TestLog, from file " + file + " on line 79"},
		event,
	}

	if len(ew.events) != len(expected) {
		t.Fatalf("Expected to have %d events, but got %d",
			len(expected), len(ew.events))
	}

	for i, event := range ew.events {
		expectedEvent := expected[i]
		expectedEvent.Timestamp = now()
		expectedEvent.Tags = tags

		if expectedEvent.Type == FatalEvent {
			// sortof test the stack trace, best we can do.
			stackTrace := event.Data.([]byte)
			if !bytes.HasPrefix(stackTrace, []byte("goroutine")) {
				t.Errorf("Expected a stack trace as data for a Fatal event, but got %s ",
					string(stackTrace))
			} else if bytes.Contains(stackTrace, []byte("logger.getStackTrace")) ||
				bytes.Contains(stackTrace, []byte("logger.Fatal")) {
				t.Errorf("Expected the stack trace to not contain the logger.Fatal and "+
					"logger.getStackTrace, but got %s ", string(stackTrace))
			}

			event.Data = nil
		}

		if !reflect.DeepEqual(expectedEvent, event) {
			diff := pretty.Compare(event, expectedEvent)
			t.Errorf("Unexpected difference in event #%d: %s", i, diff)
		}
	}
}

func getPanicRecoveredValue(msg string) (recv interface{}) {
	defer func() {
		recv = recover()
	}()
	panic(msg)
}

func testThumstone(tags Tags) {
	Thumbstone(tags, "testThumstone")
}

func TestStartTwice(t *testing.T) {
	defer reset()
	var ew eventWriter
	Start(&ew)
	if err := Close(); err != nil {
		t.Fatal("Unexpected error closing initial log: " + err.Error())
	}

	defer expectPanic(t, "logger: can only Start once")
	Start(&ew)
}

func TestStartNoEventWriter(t *testing.T) {
	defer reset()
	defer expectPanic(t, "logger: need atleast a single EventWriter to write to")
	Start()
}

func expectPanic(t *testing.T, expected string) {
	recv := recover()
	if recv == nil {
		t.Fatal(`Expected a panic, but didn't get one`)
	}

	got := recv.(string)
	if got != expected {
		t.Fatalf("Expected panic value to be %s, but got %s", expected, got)
	}
}

// EventWriter that always returns a write error with the event message in it.
type errorEventWriter struct {
	closeError error
	errors     []error
}

func (eew *errorEventWriter) Write(event Event) error {
	return errors.New("Write error: " + event.Message)
}

func (eew *errorEventWriter) HandleError(err error) {
	eew.errors = append(eew.errors, err)
}

func (eew *errorEventWriter) Close() error {
	return eew.closeError
}

func TestErrorEventWriter(t *testing.T) {
	closeError := errors.New("Close error")

	defer reset()
	eew := errorEventWriter{closeError: closeError}
	Start(&eew)

	tags := Tags{"my", "tags"}
	Info(tags, "Info message1")
	Info(tags, "Won't be written to the writer")

	if err := Close(); err != closeError {
		t.Fatalf("Expceted the closing error to be %v, but got %v",
			closeError, err)
	}

	if expected, got := maxNWriteErrors+1, len(eew.errors); got != expected {
		t.Fatalf("Expected %d errors, but only got %d", expected, got)
	}

	// Expected errors:
	// 0 - 4: Write error: Info message1.
	// 5:     EventWriter is bad.
	expected := errors.New("Write error: Info message1")
	for i, got := range eew.errors {
		if i == 5 {
			expected = ErrBadEventWriter
		}

		if got.Error() != expected.Error() {
			t.Errorf("Expected error #%d to be %q, but got %q",
				i, expected.Error(), got.Error())
		}
	}
}

func reset() {
	eventChannel = make(chan Event, defaultEventChannelSize)
	eventChannelClosed = make(chan struct{}, 1)
	eventWriters = []EventWriter{}
	started = false
}

func TestGetStackTrace(t *testing.T) {
	t.Parallel()

	// Fake the Fatal call.
	var stackTrace []byte
	func() {
		stackTrace = getStackTrace()
	}()

	if !bytes.HasPrefix(stackTrace, []byte("goroutine")) {
		t.Errorf("Expected the stack trace to start with goroutine, but got %s ",
			string(stackTrace))
	} else if bytes.Contains(stackTrace, []byte("logger.getStackTrace")) ||
		bytes.Contains(stackTrace, []byte("logger.TestGetStackTrace.func1")) {
		t.Errorf("Expected the stack trace to not contain the "+
			"logger.TestGetStackTrace.func1 and logger.getStackTrace, but got it: %s",
			string(stackTrace))
	}
}
<|endoftext|>