<commit_before>package watcher

import (
	"context"
	"strconv"
	"strings"
	"time"

	"github.com/rancher/rancher/pkg/controllers/user/alert/manager"
	nodeHelper "github.com/rancher/rancher/pkg/node"
	"github.com/rancher/rancher/pkg/ticker"
	"github.com/rancher/types/apis/core/v1"
	"github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/rancher/types/config"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/selection"
)

type NodeWatcher struct {
	machineLister      v3.NodeLister
	nodeLister         v1.NodeLister
	clusterAlertLister v3.ClusterAlertLister
	alertManager       *manager.Manager
	clusterName        string
	clusterLister      v3.ClusterLister
}

func StartNodeWatcher(ctx context.Context, cluster *config.UserContext, manager *manager.Manager) {

	n := &NodeWatcher{
		machineLister:      cluster.Management.Management.Nodes(cluster.ClusterName).Controller().Lister(),
		nodeLister:         cluster.Core.Nodes("").Controller().Lister(),
		clusterAlertLister: cluster.Management.Management.ClusterAlerts(cluster.ClusterName).Controller().Lister(),
		alertManager:       manager,
		clusterName:        cluster.ClusterName,
		clusterLister:      cluster.Management.Management.Clusters("").Controller().Lister(),
	}
	go n.watch(ctx, syncInterval)
}

func (w *NodeWatcher) watch(ctx context.Context, interval time.Duration) {
	for range ticker.Context(ctx, interval) {
		err := w.watchRule()
		if err != nil {
			logrus.Infof("Failed to watch node", err)
		}
	}
}

func (w *NodeWatcher) watchRule() error {
	if w.alertManager.IsDeploy == false {
		return nil
	}

	clusterAlerts, err := w.clusterAlertLister.List("", labels.NewSelector())
	if err != nil {
		return err
	}

	machines, err := w.machineLister.List("", labels.NewSelector())
	if err != nil {
		return err
	}

	for _, alert := range clusterAlerts {
		if alert.Status.AlertState == "inactive" {
			continue
		}

		if alert.Spec.TargetNode == nil {
			continue
		}

		if alert.Spec.TargetNode.NodeName != "" {
			parts := strings.Split(alert.Spec.TargetNode.NodeName, ":")
			if len(parts) != 2 {
				continue
			}
			id := parts[1]
			machine := getMachineByID(machines, id)
			w.checkNodeCondition(alert, machine)

		} else if alert.Spec.TargetNode.Selector != nil {

			selector := labels.NewSelector()
			for key, value := range alert.Spec.TargetNode.Selector {
				r, err := labels.NewRequirement(key, selection.Equals, []string{value})
				if err != nil {
					logrus.Warnf("Fail to create new requirement foo %s: %v", key, err)
					continue
				}
				selector = selector.Add(*r)
			}
			nodes, err := w.nodeLister.List("", selector)
			if err != nil {
				logrus.Warnf("Fail to list node: %v", err)
				continue
			}
			for _, node := range nodes {
				machine := nodeHelper.GetNodeByNodeName(machines, node.Name)
				w.checkNodeCondition(alert, machine)
			}
		}
	}
	return nil
}

func getMachineByID(machines []*v3.Node, id string) *v3.Node {
	for _, m := range machines {
		if m.Name == id {
			return m
		}
	}
	return nil
}

func (w *NodeWatcher) checkNodeCondition(alert *v3.ClusterAlert, machine *v3.Node) {
	switch alert.Spec.TargetNode.Condition {
	case "notready":
		w.checkNodeReady(alert, machine)
	case "mem":
		w.checkNodeMemUsage(alert, machine)
	case "cpu":
		w.checkNodeCPUUsage(alert, machine)
	}
}

func (w *NodeWatcher) checkNodeMemUsage(alert *v3.ClusterAlert, machine *v3.Node) {
	alertID := alert.Namespace + "-" + alert.Name
	if machine != nil {
		total := machine.Status.InternalNodeStatus.Allocatable.Memory()
		used := machine.Status.Requested.Memory()

		if used.Value()*100.0/total.Value() > int64(alert.Spec.TargetNode.MemThreshold) {

			clusterDisplayName := w.clusterName
			cluster, err := w.clusterLister.Get("", w.clusterName)
			if err != nil {
				logrus.Warnf("Failed to get cluster for %s: %v", w.clusterName, err)
			} else {
				clusterDisplayName = cluster.Spec.DisplayName
			}

			data := map[string]string{}
			data["alert_type"] = "nodeMemory"
			data["alert_id"] = alertID
			data["severity"] = alert.Spec.Severity
			data["alert_name"] = alert.Spec.DisplayName
			data["cluster_name"] = clusterDisplayName
			data["mem_threshold"] = strconv.Itoa(alert.Spec.TargetNode.MemThreshold)
			data["used_mem"] = used.String()
			data["total_mem"] = total.String()
			data["node_name"] = nodeHelper.GetNodeName(machine)

			if err := w.alertManager.SendAlert(data); err != nil {
				logrus.Debugf("Failed to send alert: %v", err)
			}
		}
	}
}

func (w *NodeWatcher) checkNodeCPUUsage(alert *v3.ClusterAlert, machine *v3.Node) {
	alertID := alert.Namespace + "-" + alert.Name
	if machine != nil {
		total := machine.Status.InternalNodeStatus.Allocatable.Cpu()
		used := machine.Status.Requested.Cpu()

		if used.MilliValue()*100.0/total.MilliValue() > int64(alert.Spec.TargetNode.CPUThreshold) {

			clusterDisplayName := w.clusterName
			cluster, err := w.clusterLister.Get("", w.clusterName)
			if err != nil {
				logrus.Warnf("Failed to get cluster for %s: %v", w.clusterName, err)
			} else {
				clusterDisplayName = cluster.Spec.DisplayName
			}

			data := map[string]string{}
			data["alert_type"] = "nodeCPU"
			data["alert_id"] = alertID
			data["severity"] = alert.Spec.Severity
			data["alert_name"] = alert.Spec.DisplayName
			data["cluster_name"] = clusterDisplayName
			data["cpu_threshold"] = strconv.Itoa(alert.Spec.TargetNode.CPUThreshold)
			data["used_cpu"] = strconv.FormatInt(used.MilliValue(), 10)
			data["total_cpu"] = strconv.FormatInt(total.MilliValue(), 10)
			data["node_name"] = nodeHelper.GetNodeName(machine)

			if err := w.alertManager.SendAlert(data); err != nil {
				logrus.Debugf("Failed to send alert: %v", err)
			}
		}
	}
}

func (w *NodeWatcher) checkNodeReady(alert *v3.ClusterAlert, machine *v3.Node) {
	alertID := alert.Namespace + "-" + alert.Name
	for _, cond := range machine.Status.InternalNodeStatus.Conditions {
		if cond.Type == corev1.NodeReady {
			if cond.Status != corev1.ConditionTrue {
				clusterDisplayName := w.clusterName
				cluster, err := w.clusterLister.Get("", w.clusterName)
				if err != nil {
					logrus.Warnf("Failed to get cluster for %s: %v", w.clusterName, err)
				} else {
					clusterDisplayName = cluster.Spec.DisplayName
				}

				data := map[string]string{}
				data["alert_type"] = "nodeHealthy"
				data["alert_id"] = alertID
				data["severity"] = alert.Spec.Severity
				data["alert_name"] = alert.Spec.DisplayName
				data["cluster_name"] = clusterDisplayName
				data["node_name"] = nodeHelper.GetNodeName(machine)

				if cond.Message != "" {
					data["logs"] = cond.Message
				}
				if err := w.alertManager.SendAlert(data); err != nil {
					logrus.Debugf("Failed to send alert: %v", err)
				}
				return
			}
		}
	}
}
<commit_msg>fix watcher crash<commit_after>package watcher

import (
	"context"
	"strconv"
	"strings"
	"time"

	"github.com/rancher/rancher/pkg/controllers/user/alert/manager"
	nodeHelper "github.com/rancher/rancher/pkg/node"
	"github.com/rancher/rancher/pkg/ticker"
	"github.com/rancher/types/apis/core/v1"
	"github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/rancher/types/config"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/selection"
)

type NodeWatcher struct {
	machineLister      v3.NodeLister
	nodeLister         v1.NodeLister
	clusterAlertLister v3.ClusterAlertLister
	alertManager       *manager.Manager
	clusterName        string
	clusterLister      v3.ClusterLister
}

func StartNodeWatcher(ctx context.Context, cluster *config.UserContext, manager *manager.Manager) {

	n := &NodeWatcher{
		machineLister:      cluster.Management.Management.Nodes(cluster.ClusterName).Controller().Lister(),
		nodeLister:         cluster.Core.Nodes("").Controller().Lister(),
		clusterAlertLister: cluster.Management.Management.ClusterAlerts(cluster.ClusterName).Controller().Lister(),
		alertManager:       manager,
		clusterName:        cluster.ClusterName,
		clusterLister:      cluster.Management.Management.Clusters("").Controller().Lister(),
	}
	go n.watch(ctx, syncInterval)
}

func (w *NodeWatcher) watch(ctx context.Context, interval time.Duration) {
	for range ticker.Context(ctx, interval) {
		err := w.watchRule()
		if err != nil {
			logrus.Infof("Failed to watch node", err)
		}
	}
}

func (w *NodeWatcher) watchRule() error {
	if w.alertManager.IsDeploy == false {
		return nil
	}

	clusterAlerts, err := w.clusterAlertLister.List("", labels.NewSelector())
	if err != nil {
		return err
	}

	machines, err := w.machineLister.List("", labels.NewSelector())
	if err != nil {
		return err
	}

	for _, alert := range clusterAlerts {
		if alert.Status.AlertState == "inactive" {
			continue
		}

		if alert.Spec.TargetNode == nil {
			continue
		}

		if alert.Spec.TargetNode.NodeName != "" {
			parts := strings.Split(alert.Spec.TargetNode.NodeName, ":")
			if len(parts) != 2 {
				continue
			}
			id := parts[1]
			machine := getMachineByID(machines, id)
			w.checkNodeCondition(alert, machine)

		} else if alert.Spec.TargetNode.Selector != nil {

			selector := labels.NewSelector()
			for key, value := range alert.Spec.TargetNode.Selector {
				r, err := labels.NewRequirement(key, selection.Equals, []string{value})
				if err != nil {
					logrus.Warnf("Fail to create new requirement foo %s: %v", key, err)
					continue
				}
				selector = selector.Add(*r)
			}
			nodes, err := w.nodeLister.List("", selector)
			if err != nil {
				logrus.Warnf("Fail to list node: %v", err)
				continue
			}
			for _, node := range nodes {
				machine := nodeHelper.GetNodeByNodeName(machines, node.Name)
				w.checkNodeCondition(alert, machine)
			}
		}
	}
	return nil
}

func getMachineByID(machines []*v3.Node, id string) *v3.Node {
	for _, m := range machines {
		if m.Name == id {
			return m
		}
	}
	return nil
}

func (w *NodeWatcher) checkNodeCondition(alert *v3.ClusterAlert, machine *v3.Node) {
	switch alert.Spec.TargetNode.Condition {
	case "notready":
		w.checkNodeReady(alert, machine)
	case "mem":
		w.checkNodeMemUsage(alert, machine)
	case "cpu":
		w.checkNodeCPUUsage(alert, machine)
	}
}

func (w *NodeWatcher) checkNodeMemUsage(alert *v3.ClusterAlert, machine *v3.Node) {
	alertID := alert.Namespace + "-" + alert.Name
	if machine != nil && v3.NodeConditionReady.IsTrue(machine) {
		total := machine.Status.InternalNodeStatus.Allocatable.Memory()
		used := machine.Status.Requested.Memory()

		if used.Value()*100.0/total.Value() > int64(alert.Spec.TargetNode.MemThreshold) {

			clusterDisplayName := w.clusterName
			cluster, err := w.clusterLister.Get("", w.clusterName)
			if err != nil {
				logrus.Warnf("Failed to get cluster for %s: %v", w.clusterName, err)
			} else {
				clusterDisplayName = cluster.Spec.DisplayName
			}

			data := map[string]string{}
			data["alert_type"] = "nodeMemory"
			data["alert_id"] = alertID
			data["severity"] = alert.Spec.Severity
			data["alert_name"] = alert.Spec.DisplayName
			data["cluster_name"] = clusterDisplayName
			data["mem_threshold"] = strconv.Itoa(alert.Spec.TargetNode.MemThreshold)
			data["used_mem"] = used.String()
			data["total_mem"] = total.String()
			data["node_name"] = nodeHelper.GetNodeName(machine)

			if err := w.alertManager.SendAlert(data); err != nil {
				logrus.Debugf("Failed to send alert: %v", err)
			}
		}
	}
}

func (w *NodeWatcher) checkNodeCPUUsage(alert *v3.ClusterAlert, machine *v3.Node) {
	alertID := alert.Namespace + "-" + alert.Name
	if machine != nil && v3.NodeConditionReady.IsTrue(machine) {
		total := machine.Status.InternalNodeStatus.Allocatable.Cpu()
		used := machine.Status.Requested.Cpu()

		if used.MilliValue()*100.0/total.MilliValue() > int64(alert.Spec.TargetNode.CPUThreshold) {

			clusterDisplayName := w.clusterName
			cluster, err := w.clusterLister.Get("", w.clusterName)
			if err != nil {
				logrus.Warnf("Failed to get cluster for %s: %v", w.clusterName, err)
			} else {
				clusterDisplayName = cluster.Spec.DisplayName
			}

			data := map[string]string{}
			data["alert_type"] = "nodeCPU"
			data["alert_id"] = alertID
			data["severity"] = alert.Spec.Severity
			data["alert_name"] = alert.Spec.DisplayName
			data["cluster_name"] = clusterDisplayName
			data["cpu_threshold"] = strconv.Itoa(alert.Spec.TargetNode.CPUThreshold)
			data["used_cpu"] = strconv.FormatInt(used.MilliValue(), 10)
			data["total_cpu"] = strconv.FormatInt(total.MilliValue(), 10)
			data["node_name"] = nodeHelper.GetNodeName(machine)

			if err := w.alertManager.SendAlert(data); err != nil {
				logrus.Debugf("Failed to send alert: %v", err)
			}
		}
	}
}

func (w *NodeWatcher) checkNodeReady(alert *v3.ClusterAlert, machine *v3.Node) {
	if machine == nil {
		return
	}
	alertID := alert.Namespace + "-" + alert.Name
	for _, cond := range machine.Status.InternalNodeStatus.Conditions {
		if cond.Type == corev1.NodeReady {
			if cond.Status != corev1.ConditionTrue {
				clusterDisplayName := w.clusterName
				cluster, err := w.clusterLister.Get("", w.clusterName)
				if err != nil {
					logrus.Warnf("Failed to get cluster for %s: %v", w.clusterName, err)
				} else {
					clusterDisplayName = cluster.Spec.DisplayName
				}

				data := map[string]string{}
				data["alert_type"] = "nodeHealthy"
				data["alert_id"] = alertID
				data["severity"] = alert.Spec.Severity
				data["alert_name"] = alert.Spec.DisplayName
				data["cluster_name"] = clusterDisplayName
				data["node_name"] = nodeHelper.GetNodeName(machine)

				if cond.Message != "" {
					data["logs"] = cond.Message
				}
				if err := w.alertManager.SendAlert(data); err != nil {
					logrus.Debugf("Failed to send alert: %v", err)
				}
				return
			}
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    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 rollout

import (
	"fmt"

	"github.com/spf13/cobra"

	"k8s.io/kubernetes/pkg/api/legacyscheme"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
	"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
	"k8s.io/kubernetes/pkg/kubectl/scheme"
	"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

var (
	history_long = templates.LongDesc(`
		View previous rollout revisions and configurations.`)

	history_example = templates.Examples(`
		# View the rollout history of a deployment
		kubectl rollout history deployment/abc

		# View the details of daemonset revision 3
		kubectl rollout history daemonset/abc --revision=3`)
)

type RolloutHistoryOptions struct {
	PrintFlags *genericclioptions.PrintFlags
	ToPrinter  func(string) (printers.ResourcePrinter, error)

	Revision int64

	Builder          func() *resource.Builder
	Resources        []string
	Namespace        string
	EnforceNamespace bool

	HistoryViewer    polymorphichelpers.HistoryViewerFunc
	RESTClientGetter genericclioptions.RESTClientGetter

	resource.FilenameOptions
	genericclioptions.IOStreams
}

func NewRolloutHistoryOptions(streams genericclioptions.IOStreams) *RolloutHistoryOptions {
	return &RolloutHistoryOptions{
		PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme),
		IOStreams:  streams,
	}
}

func NewCmdRolloutHistory(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
	o := NewRolloutHistoryOptions(streams)

	validArgs := []string{"deployment", "daemonset", "statefulset"}

	cmd := &cobra.Command{
		Use: "history (TYPE NAME | TYPE/NAME) [flags]",
		DisableFlagsInUseLine: true,
		Short:   i18n.T("View rollout history"),
		Long:    history_long,
		Example: history_example,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Run())
		},
		ValidArgs: validArgs,
	}

	cmd.Flags().Int64Var(&o.Revision, "revision", o.Revision, "See the details, including podTemplate of the revision specified")

	usage := "identifying the resource to get from a server."
	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)

	o.PrintFlags.AddFlags(cmd)

	return cmd
}

func (o *RolloutHistoryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	o.Resources = args

	var err error
	if o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace(); err != nil {
		return err
	}

	o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
		o.PrintFlags.NamePrintFlags.Operation = operation
		return o.PrintFlags.ToPrinter()
	}

	o.HistoryViewer = polymorphichelpers.HistoryViewerFn
	o.RESTClientGetter = f
	o.Builder = f.NewBuilder

	return nil
}

func (o *RolloutHistoryOptions) Validate() error {
	if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames) {
		return fmt.Errorf("Required resource not specified.")
	}
	if o.Revision < 0 {
		return fmt.Errorf("revision must be a positive integer: %v", o.Revision)
	}

	return nil
}

func (o *RolloutHistoryOptions) Run() error {

	r := o.Builder().
		WithScheme(legacyscheme.Scheme).
		NamespaceParam(o.Namespace).DefaultNamespace().
		FilenameParam(o.EnforceNamespace, &o.FilenameOptions).
		ResourceTypeOrNameArgs(true, o.Resources...).
		ContinueOnError().
		Latest().
		Flatten().
		Do()
	if err := r.Err(); err != nil {
		return err
	}

	return r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}

		mapping := info.ResourceMapping()
		historyViewer, err := o.HistoryViewer(o.RESTClientGetter, mapping)
		if err != nil {
			return err
		}
		historyInfo, err := historyViewer.ViewHistory(info.Namespace, info.Name, o.Revision)
		if err != nil {
			return err
		}

		withRevision := ""
		if o.Revision > 0 {
			withRevision = fmt.Sprintf("with revision #%d", o.Revision)
		}

		printer, err := o.ToPrinter(fmt.Sprintf("%s\n%s", withRevision, historyInfo))
		if err != nil {
			return err
		}

		return printer.PrintObj(cmdutil.AsDefaultVersionedOrOriginal(info.Object, info.Mapping), o.Out)
	})
}
<commit_msg>switch rollout history to external<commit_after>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    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 rollout

import (
	"fmt"

	"github.com/spf13/cobra"

	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
	"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
	"k8s.io/kubernetes/pkg/kubectl/scheme"
	"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

var (
	history_long = templates.LongDesc(`
		View previous rollout revisions and configurations.`)

	history_example = templates.Examples(`
		# View the rollout history of a deployment
		kubectl rollout history deployment/abc

		# View the details of daemonset revision 3
		kubectl rollout history daemonset/abc --revision=3`)
)

type RolloutHistoryOptions struct {
	PrintFlags *genericclioptions.PrintFlags
	ToPrinter  func(string) (printers.ResourcePrinter, error)

	Revision int64

	Builder          func() *resource.Builder
	Resources        []string
	Namespace        string
	EnforceNamespace bool

	HistoryViewer    polymorphichelpers.HistoryViewerFunc
	RESTClientGetter genericclioptions.RESTClientGetter

	resource.FilenameOptions
	genericclioptions.IOStreams
}

func NewRolloutHistoryOptions(streams genericclioptions.IOStreams) *RolloutHistoryOptions {
	return &RolloutHistoryOptions{
		PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme),
		IOStreams:  streams,
	}
}

func NewCmdRolloutHistory(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
	o := NewRolloutHistoryOptions(streams)

	validArgs := []string{"deployment", "daemonset", "statefulset"}

	cmd := &cobra.Command{
		Use: "history (TYPE NAME | TYPE/NAME) [flags]",
		DisableFlagsInUseLine: true,
		Short:   i18n.T("View rollout history"),
		Long:    history_long,
		Example: history_example,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Run())
		},
		ValidArgs: validArgs,
	}

	cmd.Flags().Int64Var(&o.Revision, "revision", o.Revision, "See the details, including podTemplate of the revision specified")

	usage := "identifying the resource to get from a server."
	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)

	o.PrintFlags.AddFlags(cmd)

	return cmd
}

func (o *RolloutHistoryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	o.Resources = args

	var err error
	if o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace(); err != nil {
		return err
	}

	o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
		o.PrintFlags.NamePrintFlags.Operation = operation
		return o.PrintFlags.ToPrinter()
	}

	o.HistoryViewer = polymorphichelpers.HistoryViewerFn
	o.RESTClientGetter = f
	o.Builder = f.NewBuilder

	return nil
}

func (o *RolloutHistoryOptions) Validate() error {
	if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames) {
		return fmt.Errorf("Required resource not specified.")
	}
	if o.Revision < 0 {
		return fmt.Errorf("revision must be a positive integer: %v", o.Revision)
	}

	return nil
}

func (o *RolloutHistoryOptions) Run() error {

	r := o.Builder().
		WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
		NamespaceParam(o.Namespace).DefaultNamespace().
		FilenameParam(o.EnforceNamespace, &o.FilenameOptions).
		ResourceTypeOrNameArgs(true, o.Resources...).
		ContinueOnError().
		Latest().
		Flatten().
		Do()
	if err := r.Err(); err != nil {
		return err
	}

	return r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}

		mapping := info.ResourceMapping()
		historyViewer, err := o.HistoryViewer(o.RESTClientGetter, mapping)
		if err != nil {
			return err
		}
		historyInfo, err := historyViewer.ViewHistory(info.Namespace, info.Name, o.Revision)
		if err != nil {
			return err
		}

		withRevision := ""
		if o.Revision > 0 {
			withRevision = fmt.Sprintf("with revision #%d", o.Revision)
		}

		printer, err := o.ToPrinter(fmt.Sprintf("%s\n%s", withRevision, historyInfo))
		if err != nil {
			return err
		}

		return printer.PrintObj(cmdutil.AsDefaultVersionedOrOriginal(info.Object, info.Mapping), o.Out)
	})
}
<|endoftext|>


<commit_before>/* vim: set autoindent noexpandtab tabstop=4 shiftwidth=4: */
package v1

import (
	"slack"
)

func remove(data *slack.Slack, args []string) (int, string, error) {
	return 200, "", nil
}
<commit_msg>Add support for remove in api<commit_after>/* vim: set autoindent noexpandtab tabstop=4 shiftwidth=4: */
package v1

import (
	"slack"
)

func remove(data *slack.Slack, args []string) (int, string, error) {
	if len(args) < 1 {
		return 400, "", errors.New("Not enough arguments to remove an item.")
	}

	err := Storage.Remove(args[1])
	if err != nil {
		return 400, "", err
	}

	return 200, "Item removed.", nil
}
<|endoftext|>


<commit_before>package logging

import (
	"io"

	"github.com/go-kit/kit/log"
)

// testSink is implemented by testing.T and testing.B
type testSink interface {
	Log(...interface{})
}

// testWriter implements io.Writer and delegates to a testSink
type testWriter struct {
	testSink
}

func (t testWriter) Write(data []byte) (int, error) {
	t.testSink.Log(string(data))
	return len(data), nil
}

// NewTestWriter returns an io.Writer which delegates to a testing log.
// The returned io.Writer does not need to be synchronized.
func NewTestWriter(t testSink) io.Writer {
	return testWriter{t}
}

// NewTestLogger produces a go-kit Logger which delegates to the supplied testing log.
func NewTestLogger(o *Options, t testSink) log.Logger {
	if o == nil {
		// we want to see all log output in tests by default
		o = &Options{Level: "DEBUG"}
	}

	return NewFilter(
		log.With(
			o.loggerFactory()(NewTestWriter(t)),
			TimestampKey(), log.DefaultTimestampUTC,
		),
		o,
	)
}
<commit_msg>Added a caller appropriate for tests<commit_after>package logging

import (
	"io"

	"github.com/go-kit/kit/log"
)

// testSink is implemented by testing.T and testing.B
type testSink interface {
	Log(...interface{})
}

// testWriter implements io.Writer and delegates to a testSink
type testWriter struct {
	testSink
}

func (t testWriter) Write(data []byte) (int, error) {
	t.testSink.Log(string(data))
	return len(data), nil
}

// NewTestWriter returns an io.Writer which delegates to a testing log.
// The returned io.Writer does not need to be synchronized.
func NewTestWriter(t testSink) io.Writer {
	return testWriter{t}
}

// NewTestLogger produces a go-kit Logger which delegates to the supplied testing log.
func NewTestLogger(o *Options, t testSink) log.Logger {
	if o == nil {
		// we want to see all log output in tests by default
		o = &Options{Level: "DEBUG"}
	}

	return NewFilter(
		log.With(
			o.loggerFactory()(NewTestWriter(t)),
			TimestampKey(), log.DefaultTimestampUTC,
			"caller", log.Caller(4), // we need (1) higher than default caller, since we wrap the testSink
		),
		o,
	)
}
<|endoftext|>


<commit_before>package config

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

var sampleConfig = `
apikey = "abcde"
display_name = "fghij"
diagnostic = true

[filesystems]
ignore = "/dev/ram.*"

[connection]
post_metrics_retry_delay_seconds = 600
post_metrics_retry_max = 5

[plugin.metrics.mysql]
command = "ruby /path/to/your/plugin/mysql.rb"
user = "mysql"
custom_identifier = "app1.example.com"

[plugin.checks.heartbeat]
command = "heartbeat.sh"
user = "xyz"
notification_interval = 60
max_check_attempts = 3

[plugin.metadata.hostinfo]
command = "hostinfo.sh"
user = "zzz"
check_interval = 60
`

func TestLoadConfig(t *testing.T) {
	tmpFile, err := newTempFileWithContent(sampleConfig)
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	defer os.Remove(tmpFile.Name())

	config, err := LoadConfig(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apibase != "https://mackerel.io" {
		t.Error("should be https://mackerel.io (arg value should be used)")
	}

	if config.Apikey != "abcde" {
		t.Error("should be abcde (config value should be used)")
	}

	if config.DisplayName != "fghij" {
		t.Error("should be fghij (config value should be used)")
	}

	if config.Diagnostic != true {
		t.Error("should be true (config value should be used)")
	}

	if config.Filesystems.UseMountpoint != false {
		t.Error("should be false (default value should be used)")
	}

	if config.Connection.PostMetricsDequeueDelaySeconds != 30 {
		t.Error("should be 30 (default value should be used)")
	}

	if config.Connection.PostMetricsRetryDelaySeconds != 180 {
		t.Error("should be 180 (max retry delay seconds is 180)")
	}

	if config.Connection.PostMetricsRetryMax != 5 {
		t.Error("should be 5 (config value should be used)")
	}
}

var sampleConfigWithHostStatus = `
apikey = "abcde"
display_name = "fghij"

[host_status]
on_start = "working"
on_stop  = "poweroff"
`

func TestLoadConfigWithHostStatus(t *testing.T) {
	tmpFile, err := newTempFileWithContent(sampleConfigWithHostStatus)
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	defer os.Remove(tmpFile.Name())

	config, err := LoadConfig(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apikey != "abcde" {
		t.Error("should be abcde (config value should be used)")
	}

	if config.DisplayName != "fghij" {
		t.Error("should be fghij (config value should be used)")
	}

	if config.HostStatus.OnStart != "working" {
		t.Error(`HostStatus.OnStart should be "working"`)
	}

	if config.HostStatus.OnStop != "poweroff" {
		t.Error(`HostStatus.OnStop should be "poweroff"`)
	}
}

var sampleConfigWithMountPoint = `
apikey = "abcde"
display_name = "fghij"

[filesystems]
use_mountpoint = true
`

func TestLoadConfigWithMountPoint(t *testing.T) {
	tmpFile, err := newTempFileWithContent(sampleConfigWithMountPoint)
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	defer os.Remove(tmpFile.Name())

	config, err := LoadConfig(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Filesystems.UseMountpoint != true {
		t.Error("should be true (config value should be used)")
	}
}

var sampleConfigWithInvalidIgnoreRegexp = `
apikey = "abcde"
display_name = "fghij"

[filesystems]
ignore = "**"
`

func TestLoadConfigWithInvalidIgnoreRegexp(t *testing.T) {
	tmpFile, err := newTempFileWithContent(sampleConfigWithInvalidIgnoreRegexp)
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	defer os.Remove(tmpFile.Name())

	_, err = LoadConfig(tmpFile.Name())
	if err == nil {
		t.Errorf("should raise error: %v", err)
	}
}

func TestLoadConfigFile(t *testing.T) {
	tmpFile, err := newTempFileWithContent(sampleConfig)
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	defer os.Remove(tmpFile.Name())

	config, err := loadConfigFile(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apikey != "abcde" {
		t.Error("Apikey should be abcde")
	}

	if config.DisplayName != "fghij" {
		t.Error("DisplayName should be fghij")
	}

	if config.Diagnostic != true {
		t.Error("Diagnostic should be true")
	}

	if config.Connection.PostMetricsRetryMax != 5 {
		t.Error("PostMetricsRetryMax should be 5")
	}

	if config.MetricPlugins == nil {
		t.Error("plugin should have metrics")
	}
	pluginConf := config.MetricPlugins["mysql"]
	if pluginConf.Command != "ruby /path/to/your/plugin/mysql.rb" {
		t.Errorf("plugin conf command should be 'ruby /path/to/your/plugin/mysql.rb' but %v", pluginConf.Command)
	}
	if pluginConf.User != "mysql" {
		t.Error("plugin user_name should be 'mysql'")
	}
	if *pluginConf.CustomIdentifier != "app1.example.com" {
		t.Errorf("plugin custom_identifier should be 'app1.example.com' but got %v", *pluginConf.CustomIdentifier)
	}
	customIdentifiers := config.ListCustomIdentifiers()
	if len(customIdentifiers) != 1 {
		t.Errorf("config should have 1 custom_identifier")
	}
	if customIdentifiers[0] != "app1.example.com" {
		t.Errorf("first custom_identifier should be 'app1.example.com'")
	}

	if config.CheckPlugins == nil {
		t.Error("plugin should have checks")
	}
	checks := config.CheckPlugins["heartbeat"]
	if checks.Command != "heartbeat.sh" {
		t.Error("check command should be 'heartbeat.sh'")
	}
	if checks.User != "xyz" {
		t.Error("check user_name should be 'xyz'")
	}
	if *checks.NotificationInterval != 60 {
		t.Error("notification_interval should be 60")
	}
	if *checks.MaxCheckAttempts != 3 {
		t.Error("max_check_attempts should be 3")
	}

	if config.MetadataPlugins == nil {
		t.Error("config should have metadata plugin list")
	}
	metadataPlugin := config.MetadataPlugins["hostinfo"]
	if metadataPlugin.Command != "hostinfo.sh" {
		t.Errorf("command of metadata plugin should be 'hostinfo.sh' but got '%v'", metadataPlugin.Command)
	}
	if metadataPlugin.User != "zzz" {
		t.Errorf("user of metadata plugin should be 'zzz' but got '%v'", metadataPlugin.User)
	}
	if *metadataPlugin.CheckInterval != 60 {
		t.Errorf("check interval of metadata plugin should be 60 but got '%v'", *metadataPlugin.CheckInterval)
	}

	if config.Plugin != nil {
		t.Error("plugin config should be set nil, use MetricPlugins, CheckPlugins and MetadataPlugins instead")
	}
}

func assertNoError(t *testing.T, err error) {
	if err != nil {
		t.Error(err)
	}
}

func assert(t *testing.T, ok bool, msg string) {
	if !ok {
		t.Error(msg)
	}
}

var tomlQuotedReplacer = strings.NewReplacer(
	"\t", "\\t",
	"\n", "\\n",
	"\r", "\\r",
	"\"", "\\\"",
	"\\", "\\\\",
)

func TestLoadConfigFileInclude(t *testing.T) {
	configDir, err := ioutil.TempDir("", "mackerel-config-test")
	assertNoError(t, err)
	defer os.RemoveAll(configDir)

	includedFile, err := os.Create(filepath.Join(configDir, "sub1.conf"))
	assertNoError(t, err)

	configContent := fmt.Sprintf(`
apikey = "abcde"
pidfile = "/path/to/pidfile"
root = "/var/lib/mackerel-agent"
verbose = false

roles = [ "roles", "to be overwritten" ]

include = "%s/*.conf"

[plugin.metrics.foo1]
command = "foo1"

[plugin.metrics.bar]
command = "this will be overwritten"
`, tomlQuotedReplacer.Replace(configDir))

	configFile, err := newTempFileWithContent(configContent)
	assertNoError(t, err)
	defer os.Remove(configFile.Name())

	includedContent := `
roles = [ "Service:role" ]

[plugin.metrics.foo2]
command = "foo2"

[plugin.metrics.bar]
command = "bar"
`

	_, err = includedFile.WriteString(includedContent)
	assertNoError(t, err)
	includedFile.Close()

	config, err := loadConfigFile(configFile.Name())
	assertNoError(t, err)

	assert(t, config.Apikey == "abcde", "apikey should be kept as it is when not configured in the included file")
	assert(t, config.Pidfile == "/path/to/pidfile", "pidfile should be kept as it is when not configured in the included file")
	assert(t, config.Root == "/var/lib/mackerel-agent", "root should be kept as it is when not configured in the included file")
	assert(t, config.Verbose == false, "verbose should be kept as it is when not configured in the included file")
	assert(t, len(config.Roles) == 1, "roles should be overwritten")
	assert(t, config.Roles[0] == "Service:role", "roles should be overwritten")
	assert(t, config.MetricPlugins["foo1"].Command == "foo1", "plugin.metrics.foo1 should exist")
	assert(t, config.MetricPlugins["foo2"].Command == "foo2", "plugin.metrics.foo2 should exist")
	assert(t, config.MetricPlugins["bar"].Command == "bar", "plugin.metrics.bar should be overwritten")
}

func TestLoadConfigFileIncludeOverwritten(t *testing.T) {
	configDir, err := ioutil.TempDir("", "mackerel-config-test")
	assertNoError(t, err)
	defer os.RemoveAll(configDir)

	includedFile, err := os.Create(filepath.Join(configDir, "sub2.conf"))
	assertNoError(t, err)

	configContent := fmt.Sprintf(`
apikey = "abcde"
pidfile = "/path/to/pidfile"
root = "/var/lib/mackerel-agent"
verbose = false

include = "%s/*.conf"
`, tomlQuotedReplacer.Replace(configDir))

	configFile, err := newTempFileWithContent(configContent)
	assertNoError(t, err)
	defer os.Remove(configFile.Name())

	includedContent := `
apikey = "new-api-key"
pidfile = "/path/to/pidfile2"
root = "/tmp"
verbose = true
`

	_, err = includedFile.WriteString(includedContent)
	assertNoError(t, err)
	includedFile.Close()

	config, err := loadConfigFile(configFile.Name())
	assertNoError(t, err)

	assert(t, config.Apikey == "new-api-key", "apikey should be overwritten")
	assert(t, config.Pidfile == "/path/to/pidfile2", "pidfile should be overwritten")
	assert(t, config.Root == "/tmp", "root should be overwritten")
	assert(t, config.Verbose == true, "verbose should be overwritten")
}

func TestFileSystemHostIDStorage(t *testing.T) {
	root, err := ioutil.TempDir("", "mackerel-agent-test")
	if err != nil {
		t.Fatal(err)
	}

	s := FileSystemHostIDStorage{Root: root}
	err = s.SaveHostID("test-host-id")
	assertNoError(t, err)

	hostID, err := s.LoadHostID()
	assertNoError(t, err)
	assert(t, hostID == "test-host-id", "SaveHostID and LoadHostID should preserve the host id")

	err = s.DeleteSavedHostID()
	assertNoError(t, err)

	_, err = s.LoadHostID()
	assert(t, err != nil, "LoadHostID after DeleteSavedHostID must fail")
}

func TestConfig_HostIDStorage(t *testing.T) {
	conf := Config{
		Root: "test-root",
	}

	storage, ok := conf.hostIDStorage().(*FileSystemHostIDStorage)
	assert(t, ok, "Default hostIDStorage must be *FileSystemHostIDStorage")
	assert(t, storage.Root == "test-root", "FileSystemHostIDStorage must have the same Root of Config")
}

func TestLoadConfigWithSilent(t *testing.T) {
	conff, err := newTempFileWithContent(`
apikey = "abcde"
silent = true
`)
	if err != nil {
		t.Fatalf("should not raise error: %s", err)
	}
	defer os.Remove(conff.Name())

	config, err := loadConfigFile(conff.Name())
	assertNoError(t, err)

	if !config.Silent {
		t.Error("silent should be ture")
	}
}

func TestLoadConfig_WithCommandArgs(t *testing.T) {
	conff, err := newTempFileWithContent(`
apikey = "abcde"
[plugin.metrics.hoge]
command = ["perl", "-E", "say 'Hello'"]
`)
	if err != nil {
		t.Fatalf("should not raise error: %s", err)
	}
	defer os.Remove(conff.Name())

	config, err := loadConfigFile(conff.Name())
	assertNoError(t, err)

	expected := []string{"perl", "-E", "say 'Hello'"}
	p := config.MetricPlugins["hoge"]
	output := p.CommandArgs

	if !reflect.DeepEqual(expected, output) {
		t.Errorf("command args not expected: %+v", output)
	}

	if p.Command != "" {
		t.Errorf("p.Command should be empty but: %s", p.Command)
	}
}

func newTempFileWithContent(content string) (*os.File, error) {
	tmpf, err := ioutil.TempFile("", "mackerel-config-test")
	if err != nil {
		return nil, err
	}
	if _, err := tmpf.WriteString(content); err != nil {
		os.Remove(tmpf.Name())
		return nil, err
	}
	tmpf.Sync()
	tmpf.Close()
	return tmpf, nil
}
<commit_msg>update test, change the interval field of metadata plugin to execution_interval<commit_after>package config

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

var sampleConfig = `
apikey = "abcde"
display_name = "fghij"
diagnostic = true

[filesystems]
ignore = "/dev/ram.*"

[connection]
post_metrics_retry_delay_seconds = 600
post_metrics_retry_max = 5

[plugin.metrics.mysql]
command = "ruby /path/to/your/plugin/mysql.rb"
user = "mysql"
custom_identifier = "app1.example.com"

[plugin.checks.heartbeat]
command = "heartbeat.sh"
user = "xyz"
notification_interval = 60
max_check_attempts = 3

[plugin.metadata.hostinfo]
command = "hostinfo.sh"
user = "zzz"
execution_interval = 60
`

func TestLoadConfig(t *testing.T) {
	tmpFile, err := newTempFileWithContent(sampleConfig)
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	defer os.Remove(tmpFile.Name())

	config, err := LoadConfig(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apibase != "https://mackerel.io" {
		t.Error("should be https://mackerel.io (arg value should be used)")
	}

	if config.Apikey != "abcde" {
		t.Error("should be abcde (config value should be used)")
	}

	if config.DisplayName != "fghij" {
		t.Error("should be fghij (config value should be used)")
	}

	if config.Diagnostic != true {
		t.Error("should be true (config value should be used)")
	}

	if config.Filesystems.UseMountpoint != false {
		t.Error("should be false (default value should be used)")
	}

	if config.Connection.PostMetricsDequeueDelaySeconds != 30 {
		t.Error("should be 30 (default value should be used)")
	}

	if config.Connection.PostMetricsRetryDelaySeconds != 180 {
		t.Error("should be 180 (max retry delay seconds is 180)")
	}

	if config.Connection.PostMetricsRetryMax != 5 {
		t.Error("should be 5 (config value should be used)")
	}
}

var sampleConfigWithHostStatus = `
apikey = "abcde"
display_name = "fghij"

[host_status]
on_start = "working"
on_stop  = "poweroff"
`

func TestLoadConfigWithHostStatus(t *testing.T) {
	tmpFile, err := newTempFileWithContent(sampleConfigWithHostStatus)
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	defer os.Remove(tmpFile.Name())

	config, err := LoadConfig(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apikey != "abcde" {
		t.Error("should be abcde (config value should be used)")
	}

	if config.DisplayName != "fghij" {
		t.Error("should be fghij (config value should be used)")
	}

	if config.HostStatus.OnStart != "working" {
		t.Error(`HostStatus.OnStart should be "working"`)
	}

	if config.HostStatus.OnStop != "poweroff" {
		t.Error(`HostStatus.OnStop should be "poweroff"`)
	}
}

var sampleConfigWithMountPoint = `
apikey = "abcde"
display_name = "fghij"

[filesystems]
use_mountpoint = true
`

func TestLoadConfigWithMountPoint(t *testing.T) {
	tmpFile, err := newTempFileWithContent(sampleConfigWithMountPoint)
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	defer os.Remove(tmpFile.Name())

	config, err := LoadConfig(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Filesystems.UseMountpoint != true {
		t.Error("should be true (config value should be used)")
	}
}

var sampleConfigWithInvalidIgnoreRegexp = `
apikey = "abcde"
display_name = "fghij"

[filesystems]
ignore = "**"
`

func TestLoadConfigWithInvalidIgnoreRegexp(t *testing.T) {
	tmpFile, err := newTempFileWithContent(sampleConfigWithInvalidIgnoreRegexp)
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	defer os.Remove(tmpFile.Name())

	_, err = LoadConfig(tmpFile.Name())
	if err == nil {
		t.Errorf("should raise error: %v", err)
	}
}

func TestLoadConfigFile(t *testing.T) {
	tmpFile, err := newTempFileWithContent(sampleConfig)
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	defer os.Remove(tmpFile.Name())

	config, err := loadConfigFile(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apikey != "abcde" {
		t.Error("Apikey should be abcde")
	}

	if config.DisplayName != "fghij" {
		t.Error("DisplayName should be fghij")
	}

	if config.Diagnostic != true {
		t.Error("Diagnostic should be true")
	}

	if config.Connection.PostMetricsRetryMax != 5 {
		t.Error("PostMetricsRetryMax should be 5")
	}

	if config.MetricPlugins == nil {
		t.Error("plugin should have metrics")
	}
	pluginConf := config.MetricPlugins["mysql"]
	if pluginConf.Command != "ruby /path/to/your/plugin/mysql.rb" {
		t.Errorf("plugin conf command should be 'ruby /path/to/your/plugin/mysql.rb' but %v", pluginConf.Command)
	}
	if pluginConf.User != "mysql" {
		t.Error("plugin user_name should be 'mysql'")
	}
	if *pluginConf.CustomIdentifier != "app1.example.com" {
		t.Errorf("plugin custom_identifier should be 'app1.example.com' but got %v", *pluginConf.CustomIdentifier)
	}
	customIdentifiers := config.ListCustomIdentifiers()
	if len(customIdentifiers) != 1 {
		t.Errorf("config should have 1 custom_identifier")
	}
	if customIdentifiers[0] != "app1.example.com" {
		t.Errorf("first custom_identifier should be 'app1.example.com'")
	}

	if config.CheckPlugins == nil {
		t.Error("plugin should have checks")
	}
	checks := config.CheckPlugins["heartbeat"]
	if checks.Command != "heartbeat.sh" {
		t.Error("check command should be 'heartbeat.sh'")
	}
	if checks.User != "xyz" {
		t.Error("check user_name should be 'xyz'")
	}
	if *checks.NotificationInterval != 60 {
		t.Error("notification_interval should be 60")
	}
	if *checks.MaxCheckAttempts != 3 {
		t.Error("max_check_attempts should be 3")
	}

	if config.MetadataPlugins == nil {
		t.Error("config should have metadata plugin list")
	}
	metadataPlugin := config.MetadataPlugins["hostinfo"]
	if metadataPlugin.Command != "hostinfo.sh" {
		t.Errorf("command of metadata plugin should be 'hostinfo.sh' but got '%v'", metadataPlugin.Command)
	}
	if metadataPlugin.User != "zzz" {
		t.Errorf("user of metadata plugin should be 'zzz' but got '%v'", metadataPlugin.User)
	}
	if *metadataPlugin.ExecutionInterval != 60 {
		t.Errorf("execution interval of metadata plugin should be 60 but got '%v'", *metadataPlugin.ExecutionInterval)
	}

	if config.Plugin != nil {
		t.Error("plugin config should be set nil, use MetricPlugins, CheckPlugins and MetadataPlugins instead")
	}
}

func assertNoError(t *testing.T, err error) {
	if err != nil {
		t.Error(err)
	}
}

func assert(t *testing.T, ok bool, msg string) {
	if !ok {
		t.Error(msg)
	}
}

var tomlQuotedReplacer = strings.NewReplacer(
	"\t", "\\t",
	"\n", "\\n",
	"\r", "\\r",
	"\"", "\\\"",
	"\\", "\\\\",
)

func TestLoadConfigFileInclude(t *testing.T) {
	configDir, err := ioutil.TempDir("", "mackerel-config-test")
	assertNoError(t, err)
	defer os.RemoveAll(configDir)

	includedFile, err := os.Create(filepath.Join(configDir, "sub1.conf"))
	assertNoError(t, err)

	configContent := fmt.Sprintf(`
apikey = "abcde"
pidfile = "/path/to/pidfile"
root = "/var/lib/mackerel-agent"
verbose = false

roles = [ "roles", "to be overwritten" ]

include = "%s/*.conf"

[plugin.metrics.foo1]
command = "foo1"

[plugin.metrics.bar]
command = "this will be overwritten"
`, tomlQuotedReplacer.Replace(configDir))

	configFile, err := newTempFileWithContent(configContent)
	assertNoError(t, err)
	defer os.Remove(configFile.Name())

	includedContent := `
roles = [ "Service:role" ]

[plugin.metrics.foo2]
command = "foo2"

[plugin.metrics.bar]
command = "bar"
`

	_, err = includedFile.WriteString(includedContent)
	assertNoError(t, err)
	includedFile.Close()

	config, err := loadConfigFile(configFile.Name())
	assertNoError(t, err)

	assert(t, config.Apikey == "abcde", "apikey should be kept as it is when not configured in the included file")
	assert(t, config.Pidfile == "/path/to/pidfile", "pidfile should be kept as it is when not configured in the included file")
	assert(t, config.Root == "/var/lib/mackerel-agent", "root should be kept as it is when not configured in the included file")
	assert(t, config.Verbose == false, "verbose should be kept as it is when not configured in the included file")
	assert(t, len(config.Roles) == 1, "roles should be overwritten")
	assert(t, config.Roles[0] == "Service:role", "roles should be overwritten")
	assert(t, config.MetricPlugins["foo1"].Command == "foo1", "plugin.metrics.foo1 should exist")
	assert(t, config.MetricPlugins["foo2"].Command == "foo2", "plugin.metrics.foo2 should exist")
	assert(t, config.MetricPlugins["bar"].Command == "bar", "plugin.metrics.bar should be overwritten")
}

func TestLoadConfigFileIncludeOverwritten(t *testing.T) {
	configDir, err := ioutil.TempDir("", "mackerel-config-test")
	assertNoError(t, err)
	defer os.RemoveAll(configDir)

	includedFile, err := os.Create(filepath.Join(configDir, "sub2.conf"))
	assertNoError(t, err)

	configContent := fmt.Sprintf(`
apikey = "abcde"
pidfile = "/path/to/pidfile"
root = "/var/lib/mackerel-agent"
verbose = false

include = "%s/*.conf"
`, tomlQuotedReplacer.Replace(configDir))

	configFile, err := newTempFileWithContent(configContent)
	assertNoError(t, err)
	defer os.Remove(configFile.Name())

	includedContent := `
apikey = "new-api-key"
pidfile = "/path/to/pidfile2"
root = "/tmp"
verbose = true
`

	_, err = includedFile.WriteString(includedContent)
	assertNoError(t, err)
	includedFile.Close()

	config, err := loadConfigFile(configFile.Name())
	assertNoError(t, err)

	assert(t, config.Apikey == "new-api-key", "apikey should be overwritten")
	assert(t, config.Pidfile == "/path/to/pidfile2", "pidfile should be overwritten")
	assert(t, config.Root == "/tmp", "root should be overwritten")
	assert(t, config.Verbose == true, "verbose should be overwritten")
}

func TestFileSystemHostIDStorage(t *testing.T) {
	root, err := ioutil.TempDir("", "mackerel-agent-test")
	if err != nil {
		t.Fatal(err)
	}

	s := FileSystemHostIDStorage{Root: root}
	err = s.SaveHostID("test-host-id")
	assertNoError(t, err)

	hostID, err := s.LoadHostID()
	assertNoError(t, err)
	assert(t, hostID == "test-host-id", "SaveHostID and LoadHostID should preserve the host id")

	err = s.DeleteSavedHostID()
	assertNoError(t, err)

	_, err = s.LoadHostID()
	assert(t, err != nil, "LoadHostID after DeleteSavedHostID must fail")
}

func TestConfig_HostIDStorage(t *testing.T) {
	conf := Config{
		Root: "test-root",
	}

	storage, ok := conf.hostIDStorage().(*FileSystemHostIDStorage)
	assert(t, ok, "Default hostIDStorage must be *FileSystemHostIDStorage")
	assert(t, storage.Root == "test-root", "FileSystemHostIDStorage must have the same Root of Config")
}

func TestLoadConfigWithSilent(t *testing.T) {
	conff, err := newTempFileWithContent(`
apikey = "abcde"
silent = true
`)
	if err != nil {
		t.Fatalf("should not raise error: %s", err)
	}
	defer os.Remove(conff.Name())

	config, err := loadConfigFile(conff.Name())
	assertNoError(t, err)

	if !config.Silent {
		t.Error("silent should be ture")
	}
}

func TestLoadConfig_WithCommandArgs(t *testing.T) {
	conff, err := newTempFileWithContent(`
apikey = "abcde"
[plugin.metrics.hoge]
command = ["perl", "-E", "say 'Hello'"]
`)
	if err != nil {
		t.Fatalf("should not raise error: %s", err)
	}
	defer os.Remove(conff.Name())

	config, err := loadConfigFile(conff.Name())
	assertNoError(t, err)

	expected := []string{"perl", "-E", "say 'Hello'"}
	p := config.MetricPlugins["hoge"]
	output := p.CommandArgs

	if !reflect.DeepEqual(expected, output) {
		t.Errorf("command args not expected: %+v", output)
	}

	if p.Command != "" {
		t.Errorf("p.Command should be empty but: %s", p.Command)
	}
}

func newTempFileWithContent(content string) (*os.File, error) {
	tmpf, err := ioutil.TempFile("", "mackerel-config-test")
	if err != nil {
		return nil, err
	}
	if _, err := tmpf.WriteString(content); err != nil {
		os.Remove(tmpf.Name())
		return nil, err
	}
	tmpf.Sync()
	tmpf.Close()
	return tmpf, nil
}
<|endoftext|>


<commit_before>package config_test

import (
	"reflect"
	"strings"
	"testing"

	"github.com/terranodo/tegola/config"
)

func TestParse(t *testing.T) {
	testcases := []struct {
		config   string
		expected config.Config
	}{
		{
			config: `
				[webserver]
				hostname = "cdn.tegola.io"
				port = ":8080"
				log_file = "/var/log/tegola/tegola.log"
				log_format = "{{.Time}}:{{.RequestIP}} —— Tile:{{.Z}}/{{.X}}/{{.Y}}"

				[[providers]]
				name = "provider1"
				type = "postgis"
				host = "localhost"
				port = 5432
				database = "osm_water"
				user = "admin"
				password = ""

				    [[providers.layers]]
				    name = "water"
				    geometry_fieldname = "geom"
				    id_fieldname = "gid"
				    sql = "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!"

				[[maps]]
				name = "osm"
				attribution = "Test Attribution"
				bounds = [-180.0, -85.05112877980659, 180.0, 85.0511287798066]
				center = [-76.275329586789, 39.153492567373, 8.0]

				    [[maps.layers]]
				    provider_layer = "provider1.water"
				    min_zoom = 10
				    max_zoom = 20`,
			expected: config.Config{
				LocationName: "",
				Webserver: config.Webserver{
					ServerName: "cdn.tegola.io",
					Port:       ":8080",
					LogFile:    "/var/log/tegola/tegola.log",
					LogFormat:  "{{.Time}}:{{.RequestIP}} —— Tile:{{.Z}}/{{.X}}/{{.Y}}",
				},
				Providers: []map[string]interface{}{
					{
						"name":     "provider1",
						"type":     "postgis",
						"host":     "localhost",
						"port":     int64(5432),
						"database": "osm_water",
						"user":     "admin",
						"password": "",
						"layers": []map[string]interface{}{
							{
								"name":               "water",
								"geometry_fieldname": "geom",
								"id_fieldname":       "gid",
								"sql":                "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!",
							},
						},
					},
				},
				Maps: []config.Map{
					{
						Name:        "osm",
						Attribution: "Test Attribution",
						Bounds:      []float64{-180, -85.05112877980659, 180, 85.0511287798066},
						Center:      [3]float64{-76.275329586789, 39.153492567373, 8.0},
						Layers: []config.MapLayer{
							{
								ProviderLayer: "provider1.water",
								MinZoom:       10,
								MaxZoom:       20,
							},
						},
					},
				},
			},
		},
	}

	for i, tc := range testcases {
		r := strings.NewReader(tc.config)

		conf, err := config.Parse(r, "")
		if err != nil {
			t.Errorf("test case (%v) failed err: %v", i, err)
			return
		}

		//	compare the various parts fo the config
		if !reflect.DeepEqual(conf.LocationName, tc.expected.LocationName) {
			t.Errorf("test case (%v) failed. LocationName output \n\n (%+v) \n\n does not match expected \n\n (%+v) ", i, conf.LocationName, tc.expected.LocationName)
			return
		}

		if !reflect.DeepEqual(conf.Webserver, tc.expected.Webserver) {
			t.Errorf("test case (%v) failed. Webserver output \n\n (%+v) \n\n does not match expected \n\n (%+v) ", i, conf.Webserver, tc.expected.Webserver)
			return
		}

		if !reflect.DeepEqual(conf.Providers, tc.expected.Providers) {
			t.Errorf("test case (%v) failed. Providers output \n\n (%+v) \n\n does not match expected \n\n (%+v) ", i, conf.Providers, tc.expected.Providers)
			return
		}

		if !reflect.DeepEqual(conf.Maps, tc.expected.Maps) {
			t.Errorf("test case (%v) failed. Maps output \n\n (%+v) \n\n does not match expected \n\n (%+v) ", i, conf.Maps, tc.expected.Maps)
			return
		}
	}
}

func TestValidate(t *testing.T) {
	testcases := []struct {
		config   config.Config
		expected error
	}{
		{
			config: config.Config{
				LocationName: "",
				Webserver: config.Webserver{
					Port:      ":8080",
					LogFile:   "/var/log/tegola/tegola.log",
					LogFormat: "{{.Time}}:{{.RequestIP}} —— Tile:{{.Z}}/{{.X}}/{{.Y}}",
				},
				Providers: []map[string]interface{}{
					{
						"name":     "provider1",
						"type":     "postgis",
						"host":     "localhost",
						"port":     int64(5432),
						"database": "osm_water",
						"user":     "admin",
						"password": "",
						"layers": []map[string]interface{}{
							{
								"name":               "water",
								"geometry_fieldname": "geom",
								"id_fieldname":       "gid",
								"sql":                "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!",
							},
						},
					},
					{
						"name":     "provider2",
						"type":     "postgis",
						"host":     "localhost",
						"port":     int64(5432),
						"database": "osm_water",
						"user":     "admin",
						"password": "",
						"layers": []map[string]interface{}{
							{
								"name":               "water",
								"geometry_fieldname": "geom",
								"id_fieldname":       "gid",
								"sql":                "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!",
							},
						},
					},
				},
				Maps: []config.Map{
					{
						Name:        "osm",
						Attribution: "Test Attribution",
						Bounds:      []float64{-180, -85.05112877980659, 180, 85.0511287798066},
						Center:      [3]float64{-76.275329586789, 39.153492567373, 8.0},
						Layers: []config.MapLayer{
							{
								ProviderLayer: "provider1.water",
								MinZoom:       10,
								MaxZoom:       20,
							},
							{
								ProviderLayer: "provider2.water",
								MinZoom:       10,
								MaxZoom:       20,
							},
						},
					},
				},
			},
			expected: config.ErrLayerCollision{
				ProviderLayer1: "provider1.water",
				ProviderLayer2: "provider2.water",
			},
		},
		{
			config: config.Config{
				LocationName: "",
				Webserver: config.Webserver{
					Port:      ":8080",
					LogFile:   "/var/log/tegola/tegola.log",
					LogFormat: "{{.Time}}:{{.RequestIP}} —— Tile:{{.Z}}/{{.X}}/{{.Y}}",
				},
				Providers: []map[string]interface{}{
					{
						"name":     "provider1",
						"type":     "postgis",
						"host":     "localhost",
						"port":     int64(5432),
						"database": "osm_water",
						"user":     "admin",
						"password": "",
						"layers": []map[string]interface{}{
							{
								"name":               "water",
								"geometry_fieldname": "geom",
								"id_fieldname":       "gid",
								"sql":                "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!",
							},
						},
					},
					{
						"name":     "provider2",
						"type":     "postgis",
						"host":     "localhost",
						"port":     int64(5432),
						"database": "osm_water",
						"user":     "admin",
						"password": "",
						"layers": []map[string]interface{}{
							{
								"name":               "water",
								"geometry_fieldname": "geom",
								"id_fieldname":       "gid",
								"sql":                "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!",
							},
						},
					},
				},
				Maps: []config.Map{
					{
						Name:        "osm",
						Attribution: "Test Attribution",
						Bounds:      []float64{-180, -85.05112877980659, 180, 85.0511287798066},
						Center:      [3]float64{-76.275329586789, 39.153492567373, 8.0},
						Layers: []config.MapLayer{
							{
								ProviderLayer: "provider1.water",
								MinZoom:       10,
								MaxZoom:       15,
							},
							{
								ProviderLayer: "provider2.water",
								MinZoom:       16,
								MaxZoom:       20,
							},
						},
					},
				},
			},
			expected: nil,
		},
	}

	for i, tc := range testcases {
		err := tc.config.Validate()
		if err != tc.expected {
			t.Errorf("test case (%v) failed. \n\n expected \n\n (%v) \n\n got \n\n (%v)", i, tc.expected, err)
			return
		}
	}
}
<commit_msg>fixed config test<commit_after>package config_test

import (
	"reflect"
	"strings"
	"testing"

	"github.com/terranodo/tegola/config"
)

func TestParse(t *testing.T) {
	testcases := []struct {
		config   string
		expected config.Config
	}{
		{
			config: `
				[webserver]
				hostname = "cdn.tegola.io"
				port = ":8080"
				log_file = "/var/log/tegola/tegola.log"
				log_format = "{{.Time}}:{{.RequestIP}} —— Tile:{{.Z}}/{{.X}}/{{.Y}}"

				[[providers]]
				name = "provider1"
				type = "postgis"
				host = "localhost"
				port = 5432
				database = "osm_water"
				user = "admin"
				password = ""

				    [[providers.layers]]
				    name = "water"
				    geometry_fieldname = "geom"
				    id_fieldname = "gid"
				    sql = "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!"

				[[maps]]
				name = "osm"
				attribution = "Test Attribution"
				bounds = [-180.0, -85.05112877980659, 180.0, 85.0511287798066]
				center = [-76.275329586789, 39.153492567373, 8.0]

				    [[maps.layers]]
				    provider_layer = "provider1.water"
				    min_zoom = 10
				    max_zoom = 20`,
			expected: config.Config{
				LocationName: "",
				Webserver: config.Webserver{
					HostName:  "cdn.tegola.io",
					Port:      ":8080",
					LogFile:   "/var/log/tegola/tegola.log",
					LogFormat: "{{.Time}}:{{.RequestIP}} —— Tile:{{.Z}}/{{.X}}/{{.Y}}",
				},
				Providers: []map[string]interface{}{
					{
						"name":     "provider1",
						"type":     "postgis",
						"host":     "localhost",
						"port":     int64(5432),
						"database": "osm_water",
						"user":     "admin",
						"password": "",
						"layers": []map[string]interface{}{
							{
								"name":               "water",
								"geometry_fieldname": "geom",
								"id_fieldname":       "gid",
								"sql":                "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!",
							},
						},
					},
				},
				Maps: []config.Map{
					{
						Name:        "osm",
						Attribution: "Test Attribution",
						Bounds:      []float64{-180, -85.05112877980659, 180, 85.0511287798066},
						Center:      [3]float64{-76.275329586789, 39.153492567373, 8.0},
						Layers: []config.MapLayer{
							{
								ProviderLayer: "provider1.water",
								MinZoom:       10,
								MaxZoom:       20,
							},
						},
					},
				},
			},
		},
	}

	for i, tc := range testcases {
		r := strings.NewReader(tc.config)

		conf, err := config.Parse(r, "")
		if err != nil {
			t.Errorf("test case (%v) failed err: %v", i, err)
			return
		}

		//	compare the various parts fo the config
		if !reflect.DeepEqual(conf.LocationName, tc.expected.LocationName) {
			t.Errorf("test case (%v) failed. LocationName output \n\n (%+v) \n\n does not match expected \n\n (%+v) ", i, conf.LocationName, tc.expected.LocationName)
			return
		}

		if !reflect.DeepEqual(conf.Webserver, tc.expected.Webserver) {
			t.Errorf("test case (%v) failed. Webserver output \n\n (%+v) \n\n does not match expected \n\n (%+v) ", i, conf.Webserver, tc.expected.Webserver)
			return
		}

		if !reflect.DeepEqual(conf.Providers, tc.expected.Providers) {
			t.Errorf("test case (%v) failed. Providers output \n\n (%+v) \n\n does not match expected \n\n (%+v) ", i, conf.Providers, tc.expected.Providers)
			return
		}

		if !reflect.DeepEqual(conf.Maps, tc.expected.Maps) {
			t.Errorf("test case (%v) failed. Maps output \n\n (%+v) \n\n does not match expected \n\n (%+v) ", i, conf.Maps, tc.expected.Maps)
			return
		}
	}
}

func TestValidate(t *testing.T) {
	testcases := []struct {
		config   config.Config
		expected error
	}{
		{
			config: config.Config{
				LocationName: "",
				Webserver: config.Webserver{
					Port:      ":8080",
					LogFile:   "/var/log/tegola/tegola.log",
					LogFormat: "{{.Time}}:{{.RequestIP}} —— Tile:{{.Z}}/{{.X}}/{{.Y}}",
				},
				Providers: []map[string]interface{}{
					{
						"name":     "provider1",
						"type":     "postgis",
						"host":     "localhost",
						"port":     int64(5432),
						"database": "osm_water",
						"user":     "admin",
						"password": "",
						"layers": []map[string]interface{}{
							{
								"name":               "water",
								"geometry_fieldname": "geom",
								"id_fieldname":       "gid",
								"sql":                "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!",
							},
						},
					},
					{
						"name":     "provider2",
						"type":     "postgis",
						"host":     "localhost",
						"port":     int64(5432),
						"database": "osm_water",
						"user":     "admin",
						"password": "",
						"layers": []map[string]interface{}{
							{
								"name":               "water",
								"geometry_fieldname": "geom",
								"id_fieldname":       "gid",
								"sql":                "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!",
							},
						},
					},
				},
				Maps: []config.Map{
					{
						Name:        "osm",
						Attribution: "Test Attribution",
						Bounds:      []float64{-180, -85.05112877980659, 180, 85.0511287798066},
						Center:      [3]float64{-76.275329586789, 39.153492567373, 8.0},
						Layers: []config.MapLayer{
							{
								ProviderLayer: "provider1.water",
								MinZoom:       10,
								MaxZoom:       20,
							},
							{
								ProviderLayer: "provider2.water",
								MinZoom:       10,
								MaxZoom:       20,
							},
						},
					},
				},
			},
			expected: config.ErrLayerCollision{
				ProviderLayer1: "provider1.water",
				ProviderLayer2: "provider2.water",
			},
		},
		{
			config: config.Config{
				LocationName: "",
				Webserver: config.Webserver{
					Port:      ":8080",
					LogFile:   "/var/log/tegola/tegola.log",
					LogFormat: "{{.Time}}:{{.RequestIP}} —— Tile:{{.Z}}/{{.X}}/{{.Y}}",
				},
				Providers: []map[string]interface{}{
					{
						"name":     "provider1",
						"type":     "postgis",
						"host":     "localhost",
						"port":     int64(5432),
						"database": "osm_water",
						"user":     "admin",
						"password": "",
						"layers": []map[string]interface{}{
							{
								"name":               "water",
								"geometry_fieldname": "geom",
								"id_fieldname":       "gid",
								"sql":                "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!",
							},
						},
					},
					{
						"name":     "provider2",
						"type":     "postgis",
						"host":     "localhost",
						"port":     int64(5432),
						"database": "osm_water",
						"user":     "admin",
						"password": "",
						"layers": []map[string]interface{}{
							{
								"name":               "water",
								"geometry_fieldname": "geom",
								"id_fieldname":       "gid",
								"sql":                "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!",
							},
						},
					},
				},
				Maps: []config.Map{
					{
						Name:        "osm",
						Attribution: "Test Attribution",
						Bounds:      []float64{-180, -85.05112877980659, 180, 85.0511287798066},
						Center:      [3]float64{-76.275329586789, 39.153492567373, 8.0},
						Layers: []config.MapLayer{
							{
								ProviderLayer: "provider1.water",
								MinZoom:       10,
								MaxZoom:       15,
							},
							{
								ProviderLayer: "provider2.water",
								MinZoom:       16,
								MaxZoom:       20,
							},
						},
					},
				},
			},
			expected: nil,
		},
	}

	for i, tc := range testcases {
		err := tc.config.Validate()
		if err != tc.expected {
			t.Errorf("test case (%v) failed. \n\n expected \n\n (%v) \n\n got \n\n (%v)", i, tc.expected, err)
			return
		}
	}
}
<|endoftext|>


<commit_before>package release

import (
	"crypto/sha256"
	"fmt"
	"io"
	"io/ioutil"
	"os"

	"github.com/pkg/errors"
)

const (
	shortCheckSumLen int = 7
)

// Binary represents the binary file within release.
type Binary struct {
	Name     string      `yaml:"name"`
	Checksum string      `yaml:"checksum"`
	Mode     os.FileMode `yaml:"mode"`
	Body     io.Reader   `yaml:"-"`
}

// BuildBinary builds a Binary object. Return error if it is failed
// to calculate checksum of the body.
func BuildBinary(name string, mode os.FileMode, body io.Reader) (*Binary, error) {
	sum, err := checksum(body)
	if err != nil {
		return nil, err
	}
	return &Binary{
		Name:     name,
		Checksum: sum,
		Mode:     mode,
		Body:     body,
	}, nil
}

func checksum(r io.Reader) (string, error) {
	if r == nil {
		return "", errors.New("try to read nil")
	}
	body, err := ioutil.ReadAll(r)
	if err != nil {
		return "", errors.New("failed to read data for checksum")
	}
	return fmt.Sprintf("%x", sha256.Sum256(body)), nil
}

// InvalidChecksumError represents an error of the checksum.
type InvalidChecksumError struct {
	got  string
	want string
}

// Error returns the error message for InvalidChecksumError.
func (e *InvalidChecksumError) Error() string {
	return fmt.Sprintf("got: %s, want: %s", e.got, e.want)
}

// IsChecksumError returns that the type of err matches InvalidChecksumError type or not.
func IsChecksumError(err error) bool {
	_, ok := errors.Cause(err).(*InvalidChecksumError)
	return ok
}

// CopyAndValidateChecksum copies src to dst and calculate checksum of src, then check it.
func (b *Binary) CopyAndValidateChecksum(dst io.Writer, src io.Reader) (int64, error) {
	h := sha256.New()
	w := io.MultiWriter(h, dst)

	written, err := io.Copy(w, src)
	if err != nil {
		return written, err
	}
	sum := fmt.Sprintf("%x", h.Sum(nil))
	if b.Checksum != sum {
		return written, errors.WithStack(&InvalidChecksumError{got: sum, want: b.Checksum})
	}

	return written, nil
}

func (b *Binary) shortChecksum() string {
	return b.Checksum[0:shortCheckSumLen]
}

// Inspect prints the binary information.
func (b *Binary) Inspect(w io.Writer) {
	fmt.Fprintf(w, "%s/%s\t", b.Name, b.shortChecksum())
}
<commit_msg>Include mode information to show command<commit_after>package release

import (
	"crypto/sha256"
	"fmt"
	"io"
	"io/ioutil"
	"os"

	"github.com/pkg/errors"
)

const (
	shortCheckSumLen int = 7
)

// Binary represents the binary file within release.
type Binary struct {
	Name     string      `yaml:"name"`
	Checksum string      `yaml:"checksum"`
	Mode     os.FileMode `yaml:"mode"`
	Body     io.Reader   `yaml:"-"`
}

// BuildBinary builds a Binary object. Return error if it is failed
// to calculate checksum of the body.
func BuildBinary(name string, mode os.FileMode, body io.Reader) (*Binary, error) {
	sum, err := checksum(body)
	if err != nil {
		return nil, err
	}
	return &Binary{
		Name:     name,
		Checksum: sum,
		Mode:     mode,
		Body:     body,
	}, nil
}

func checksum(r io.Reader) (string, error) {
	if r == nil {
		return "", errors.New("try to read nil")
	}
	body, err := ioutil.ReadAll(r)
	if err != nil {
		return "", errors.New("failed to read data for checksum")
	}
	return fmt.Sprintf("%x", sha256.Sum256(body)), nil
}

// InvalidChecksumError represents an error of the checksum.
type InvalidChecksumError struct {
	got  string
	want string
}

// Error returns the error message for InvalidChecksumError.
func (e *InvalidChecksumError) Error() string {
	return fmt.Sprintf("got: %s, want: %s", e.got, e.want)
}

// IsChecksumError returns that the type of err matches InvalidChecksumError type or not.
func IsChecksumError(err error) bool {
	_, ok := errors.Cause(err).(*InvalidChecksumError)
	return ok
}

// CopyAndValidateChecksum copies src to dst and calculate checksum of src, then check it.
func (b *Binary) CopyAndValidateChecksum(dst io.Writer, src io.Reader) (int64, error) {
	h := sha256.New()
	w := io.MultiWriter(h, dst)

	written, err := io.Copy(w, src)
	if err != nil {
		return written, err
	}
	sum := fmt.Sprintf("%x", h.Sum(nil))
	if b.Checksum != sum {
		return written, errors.WithStack(&InvalidChecksumError{got: sum, want: b.Checksum})
	}

	return written, nil
}

func (b *Binary) shortChecksum() string {
	return b.Checksum[0:shortCheckSumLen]
}

// Inspect prints the binary information.
func (b *Binary) Inspect(w io.Writer) {
	fmt.Fprintf(w, "%s/%s/%s\t", b.Name, b.Mode, b.shortChecksum())
}
<|endoftext|>


<commit_before>package native

import (
	"fmt"

	sys "golang.org/x/sys/unix"

	"github.com/go-delve/delve/pkg/dwarf/op"
	"github.com/go-delve/delve/pkg/dwarf/regnum"
	"github.com/go-delve/delve/pkg/proc"
	"github.com/go-delve/delve/pkg/proc/amd64util"
	"github.com/go-delve/delve/pkg/proc/fbsdutil"
)

// SetPC sets RIP to the value specified by 'pc'.
func (thread *nativeThread) setPC(pc uint64) error {
	ir, err := registers(thread)
	if err != nil {
		return err
	}
	r := ir.(*fbsdutil.AMD64Registers)
	r.Regs.Rip = int64(pc)
	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.Reg)(r.Regs)) })
	return err
}

// SetReg changes the value of the specified register.
func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) (err error) {
	ir, err := registers(thread)
	if err != nil {
		return err
	}
	r := ir.(*fbsdutil.AMD64Registers)
	switch regNum {
	case regnum.AMD64_Rip:
		r.Regs.Rip = int64(reg.Uint64Val)
	case regnum.AMD64_Rsp:
		r.Regs.Rsp = int64(reg.Uint64Val)
	case regnum.AMD64_Rdx:
		r.Regs.Rdx = int64(reg.Uint64Val)
	default:
		return fmt.Errorf("changing register %d not implemented", regNum)
	}
	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.Reg)(r.Regs)) })
	return
}

func registers(thread *nativeThread) (proc.Registers, error) {
	var (
		regs fbsdutil.AMD64PtraceRegs
		err  error
	)
	thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.ID, (*sys.Reg)(&regs)) })
	if err != nil {
		return nil, err
	}
	var fsbase int64
	thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetFsBase(thread.ID, &fsbase) })
	if err != nil {
		return nil, err
	}
	r := fbsdutil.NewAMD64Registers(&regs, uint64(fsbase), func(r *fbsdutil.AMD64Registers) error {
		var fpregset amd64util.AMD64Xstate
		var floatLoadError error
		r.Fpregs, fpregset, floatLoadError = thread.fpRegisters()
		r.Fpregset = &fpregset
		return floatLoadError
	})
	return r, nil
}

const _NT_X86_XSTATE = 0x202

func (thread *nativeThread) fpRegisters() (regs []proc.Register, fpregs amd64util.AMD64Xstate, err error) {
	thread.dbp.execPtraceFunc(func() { fpregs, err = ptraceGetRegset(thread.ID) })
	if err != nil {
		err = fmt.Errorf("could not get floating point registers: %v", err.Error())
	}
	regs = fpregs.Decode()
	return
}
<commit_msg>proc: add support for setting additional registers on freebsd/amd64 (#2981)<commit_after>package native

import (
	"fmt"

	sys "golang.org/x/sys/unix"

	"github.com/go-delve/delve/pkg/dwarf/op"
	"github.com/go-delve/delve/pkg/dwarf/regnum"
	"github.com/go-delve/delve/pkg/proc"
	"github.com/go-delve/delve/pkg/proc/amd64util"
	"github.com/go-delve/delve/pkg/proc/fbsdutil"
)

// SetPC sets RIP to the value specified by 'pc'.
func (thread *nativeThread) setPC(pc uint64) error {
	ir, err := registers(thread)
	if err != nil {
		return err
	}
	r := ir.(*fbsdutil.AMD64Registers)
	r.Regs.Rip = int64(pc)
	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.Reg)(r.Regs)) })
	return err
}

// SetReg changes the value of the specified register.
func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) (err error) {
	ir, err := registers(thread)
	if err != nil {
		return err
	}
	r := ir.(*fbsdutil.AMD64Registers)
	switch regNum {
	case regnum.AMD64_Rax:
		r.Regs.Rax = int64(reg.Uint64Val)
	case regnum.AMD64_Rbx:
		r.Regs.Rbx = int64(reg.Uint64Val)
	case regnum.AMD64_Rcx:
		r.Regs.Rcx = int64(reg.Uint64Val)
	case regnum.AMD64_Rdx:
		r.Regs.Rdx = int64(reg.Uint64Val)
	case regnum.AMD64_Rsi:
		r.Regs.Rsi = int64(reg.Uint64Val)
	case regnum.AMD64_Rdi:
		r.Regs.Rdi = int64(reg.Uint64Val)
	case regnum.AMD64_Rbp:
		r.Regs.Rbp = int64(reg.Uint64Val)
	case regnum.AMD64_Rsp:
		r.Regs.Rsp = int64(reg.Uint64Val)
	case regnum.AMD64_R8:
		r.Regs.R8 = int64(reg.Uint64Val)
	case regnum.AMD64_R9:
		r.Regs.R9 = int64(reg.Uint64Val)
	case regnum.AMD64_R10:
		r.Regs.R10 = int64(reg.Uint64Val)
	case regnum.AMD64_R11:
		r.Regs.R11 = int64(reg.Uint64Val)
	case regnum.AMD64_R12:
		r.Regs.R12 = int64(reg.Uint64Val)
	case regnum.AMD64_R13:
		r.Regs.R13 = int64(reg.Uint64Val)
	case regnum.AMD64_R14:
		r.Regs.R14 = int64(reg.Uint64Val)
	case regnum.AMD64_R15:
		r.Regs.R15 = int64(reg.Uint64Val)
	case regnum.AMD64_Rip:
		r.Regs.Rip = int64(reg.Uint64Val)
	default:
		return fmt.Errorf("changing register %d not implemented", regNum)
	}
	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.Reg)(r.Regs)) })
	return
}

func registers(thread *nativeThread) (proc.Registers, error) {
	var (
		regs fbsdutil.AMD64PtraceRegs
		err  error
	)
	thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.ID, (*sys.Reg)(&regs)) })
	if err != nil {
		return nil, err
	}
	var fsbase int64
	thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetFsBase(thread.ID, &fsbase) })
	if err != nil {
		return nil, err
	}
	r := fbsdutil.NewAMD64Registers(&regs, uint64(fsbase), func(r *fbsdutil.AMD64Registers) error {
		var fpregset amd64util.AMD64Xstate
		var floatLoadError error
		r.Fpregs, fpregset, floatLoadError = thread.fpRegisters()
		r.Fpregset = &fpregset
		return floatLoadError
	})
	return r, nil
}

const _NT_X86_XSTATE = 0x202

func (thread *nativeThread) fpRegisters() (regs []proc.Register, fpregs amd64util.AMD64Xstate, err error) {
	thread.dbp.execPtraceFunc(func() { fpregs, err = ptraceGetRegset(thread.ID) })
	if err != nil {
		err = fmt.Errorf("could not get floating point registers: %v", err.Error())
	}
	regs = fpregs.Decode()
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    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 main

import (
	"io/ioutil"
	"strings"
	"testing"

	"gopkg.in/yaml.v2"
	"k8s.io/test-infra/testgrid/config/yaml2proto"
)

type SQConfig struct {
	Data map[string]string `yaml:"data,omitempty"`
}

func TestConfig(t *testing.T) {
	yamlData, err := ioutil.ReadFile("config.yaml")
	if err != nil {
		t.Errorf("IO Error : Cannot Open File config.yaml")
	}

	c := yaml2proto.Config{}
	if err := c.Update(yamlData); err != nil {
		t.Errorf("Yaml2Proto - Conversion Error %v", err)
	}

	config, err := c.Raw()
	if err != nil {
		t.Errorf("Error validating config: %v", err)
	}

	// Validate config.yaml -

	// testgroup - occurance map, validate testgroups
	testgroupMap := make(map[string]int32)

	for testgroupidx, testgroup := range config.TestGroups {
		// All testgroup must have a name and a query
		if testgroup.Name == "" || testgroup.GcsPrefix == "" {
			t.Errorf("Testgroup %v: - Must have a name and query", testgroupidx)
		}

		// All testgroup must not have duplicated names
		if testgroupMap[testgroup.Name] > 0 {
			t.Errorf("Duplicated Testgroup: %v", testgroup.Name)
		} else {
			testgroupMap[testgroup.Name] = 1
		}

		if !testgroup.IsExternal {
			t.Errorf("Testgroup %v: IsExternal should always be true!", testgroup.Name)
		}
		if !testgroup.UseKubernetesClient {
			t.Errorf("Testgroup %v: UseKubernetesClient should always be true!", testgroup.Name)
		}

		// All testgroup from kubernetes must have testgroup name match its bucket name
		if strings.HasPrefix(testgroup.GcsPrefix, "kubernetes-jenkins/logs/") {
			if strings.TrimPrefix(testgroup.GcsPrefix, "kubernetes-jenkins/logs/") != testgroup.Name {
				t.Errorf("Kubernetes Testgroup %v, name does not match GCS Bucket %v", testgroup.Name, testgroup.GcsPrefix)
			}
		}

		if testgroup.TestNameConfig != nil {
			if testgroup.TestNameConfig.NameFormat == "" {
				t.Errorf("Testgroup %v: NameFormat must not be empty!", testgroup.Name)
			}

			if len(testgroup.TestNameConfig.NameElements) != strings.Count(testgroup.TestNameConfig.NameFormat, "%") {
				t.Errorf("Testgroup %v: TestNameConfig must have number NameElement equal to format count in NameFormat!", testgroup.Name)
			}
		}
	}

	// dashboard name set
	dashboardmap := make(map[string]bool)

	for dashboardidx, dashboard := range config.Dashboards {
		// All dashboard must have a name
		if dashboard.Name == "" {
			t.Errorf("Dashboard %v: - Must have a name", dashboardidx)
		}

		// All dashboard must not have duplicated names
		if dashboardmap[dashboard.Name] {
			t.Errorf("Duplicated dashboard: %v", dashboard.Name)
		} else {
			dashboardmap[dashboard.Name] = true
		}

		// All dashboard must have at least one tab
		if len(dashboard.DashboardTab) == 0 {
			t.Errorf("Dashboard %v: - Must have more than one dashboardtab", dashboard.Name)
		}

		// dashboardtab name set, to check duplicated tabs within each dashboard
		dashboardtabmap := make(map[string]bool)

		// All notifications in dashboard must have a summary
		if len(dashboard.Notifications) != 0 {
			for notificationindex, notification := range dashboard.Notifications {
				if notification.Summary == "" {
					t.Errorf("Notification %v in dashboard %v: - Must have a summary", notificationindex, dashboard.Name)
				}
			}
		}

		for tabindex, dashboardtab := range dashboard.DashboardTab {

			// All dashboardtab must have a name and a testgroup
			if dashboardtab.Name == "" || dashboardtab.TestGroupName == "" {
				t.Errorf("Dashboard %v, tab %v: - Must have a name and a testgroup name", dashboard.Name, tabindex)
			}

			// All dashboardtab within a dashboard must not have duplicated names
			if dashboardtabmap[dashboardtab.Name] {
				t.Errorf("Duplicated dashboardtab: %v", dashboardtab.Name)
			} else {
				dashboardtabmap[dashboardtab.Name] = true
			}

			// All testgroup in dashboard must be defined in testgroups
			if testgroupMap[dashboardtab.TestGroupName] == 0 {
				t.Errorf("Dashboard %v, tab %v: - Testgroup %v must be defined first",
					dashboard.Name, dashboardtab.Name, dashboardtab.TestGroupName)
			} else {
				testgroupMap[dashboardtab.TestGroupName] += 1
			}
		}
	}

	// No dup of dashboard groups, and no dup dashboard in a dashboard group
	groups := make(map[string]bool)
	tabs := make(map[string]string)

	for idx, dashboardGroup := range config.DashboardGroups {
		// All dashboard must have a name
		if dashboardGroup.Name == "" {
			t.Errorf("DashboardGroup %v: - DashboardGroup must have a name", idx)
		}

		// All dashboardgroup must not have duplicated names
		if _, ok := groups[dashboardGroup.Name]; ok {
			t.Errorf("Duplicated dashboard: %v", dashboardGroup.Name)
		} else {
			groups[dashboardGroup.Name] = true
		}

		for _, dashboard := range dashboardGroup.DashboardNames {
			// All dashboard must not have duplicated names
			if exist, ok := tabs[dashboard]; ok {
				t.Errorf("Duplicated dashboard %v in dashboard group %v and %v", dashboard, exist, dashboardGroup.Name)
			} else {
				tabs[dashboard] = dashboardGroup.Name
			}

			if _, ok := dashboardmap[dashboard]; !ok {
				t.Errorf("Dashboard %v needs to be defined before adding to a dashboard group!", dashboard)
			}
		}
	}

	// All Testgroup should be mapped to one or more tabs
	for testgroupname, occurance := range testgroupMap {
		if occurance == 1 {
			t.Errorf("Testgroup %v - defined but not used in any dashboards", testgroupname)
		}
	}

	// make sure items in sq-blocking dashboard matches sq configmap
	sqJobPool := []string{}
	for _, d := range config.Dashboards {
		if d.Name != "sq-blocking" {
			continue
		}

		for _, tab := range d.DashboardTab {
			for _, t := range config.TestGroups {
				if t.Name == tab.TestGroupName {
					job := strings.TrimPrefix(t.GcsPrefix, "kubernetes-jenkins/logs/")
					sqJobPool = append(sqJobPool, job)
					break
				}
			}
		}
	}

	sqConfigPath := "../../mungegithub/submit-queue/deployment/kubernetes/configmap.yaml"
	configData, err := ioutil.ReadFile(sqConfigPath)
	if err != nil {
		t.Errorf("Read Buffer Error for SQ Data : %v", err)
	}

	sqData := &SQConfig{}
	err = yaml.Unmarshal([]byte(configData), &sqData)
	if err != nil {
		t.Errorf("Unmarshal Error for SQ Data : %v", err)
	}

	sqJobs := strings.Split(sqData.Data["submit-queue.jenkins-jobs"], ",")
	for _, sqJob := range sqJobs {
		if sqJob == "\"\"" { // ignore empty list of jobs
			continue
		}
		found := false
		for i, job := range sqJobPool {
			if sqJob == job {
				found = true
				sqJobPool = append(sqJobPool[:i], sqJobPool[i+1:]...)
				break
			}
		}

		if !found {
			t.Errorf("Err : %v not found in testgrid config", sqJob)
		}
	}

	for _, testgridJob := range sqJobPool {
		t.Errorf("Err : testgrid job %v not found in SQ config", testgridJob)
	}

	sqNonBlockingJobs := strings.Split(sqData.Data["submit-queue.nonblocking-jenkins-jobs"], ",")
	for _, sqJob := range sqNonBlockingJobs {
		if sqJob == "\"\"" { // ignore empty list of jobs
			continue
		}
		found := false
		for _, testgroup := range config.TestGroups {
			if testgroup.Name == sqJob {
				found = true
				break
			}
		}

		if !found {
			t.Errorf("Err : %v not found in testgrid config", sqJob)
		}
	}
}
<commit_msg>Remove BlockingJobs, PresubmitJobs, and WeakStableJobs from mungegithub.<commit_after>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    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 main

import (
	"io/ioutil"
	"strings"
	"testing"

	"gopkg.in/yaml.v2"
	"k8s.io/test-infra/testgrid/config/yaml2proto"
)

type SQConfig struct {
	Data map[string]string `yaml:"data,omitempty"`
}

func TestConfig(t *testing.T) {
	yamlData, err := ioutil.ReadFile("config.yaml")
	if err != nil {
		t.Errorf("IO Error : Cannot Open File config.yaml")
	}

	c := yaml2proto.Config{}
	if err := c.Update(yamlData); err != nil {
		t.Errorf("Yaml2Proto - Conversion Error %v", err)
	}

	config, err := c.Raw()
	if err != nil {
		t.Errorf("Error validating config: %v", err)
	}

	// Validate config.yaml -

	// testgroup - occurance map, validate testgroups
	testgroupMap := make(map[string]int32)

	for testgroupidx, testgroup := range config.TestGroups {
		// All testgroup must have a name and a query
		if testgroup.Name == "" || testgroup.GcsPrefix == "" {
			t.Errorf("Testgroup %v: - Must have a name and query", testgroupidx)
		}

		// All testgroup must not have duplicated names
		if testgroupMap[testgroup.Name] > 0 {
			t.Errorf("Duplicated Testgroup: %v", testgroup.Name)
		} else {
			testgroupMap[testgroup.Name] = 1
		}

		if !testgroup.IsExternal {
			t.Errorf("Testgroup %v: IsExternal should always be true!", testgroup.Name)
		}
		if !testgroup.UseKubernetesClient {
			t.Errorf("Testgroup %v: UseKubernetesClient should always be true!", testgroup.Name)
		}

		// All testgroup from kubernetes must have testgroup name match its bucket name
		if strings.HasPrefix(testgroup.GcsPrefix, "kubernetes-jenkins/logs/") {
			if strings.TrimPrefix(testgroup.GcsPrefix, "kubernetes-jenkins/logs/") != testgroup.Name {
				t.Errorf("Kubernetes Testgroup %v, name does not match GCS Bucket %v", testgroup.Name, testgroup.GcsPrefix)
			}
		}

		if testgroup.TestNameConfig != nil {
			if testgroup.TestNameConfig.NameFormat == "" {
				t.Errorf("Testgroup %v: NameFormat must not be empty!", testgroup.Name)
			}

			if len(testgroup.TestNameConfig.NameElements) != strings.Count(testgroup.TestNameConfig.NameFormat, "%") {
				t.Errorf("Testgroup %v: TestNameConfig must have number NameElement equal to format count in NameFormat!", testgroup.Name)
			}
		}
	}

	// dashboard name set
	dashboardmap := make(map[string]bool)

	for dashboardidx, dashboard := range config.Dashboards {
		// All dashboard must have a name
		if dashboard.Name == "" {
			t.Errorf("Dashboard %v: - Must have a name", dashboardidx)
		}

		// All dashboard must not have duplicated names
		if dashboardmap[dashboard.Name] {
			t.Errorf("Duplicated dashboard: %v", dashboard.Name)
		} else {
			dashboardmap[dashboard.Name] = true
		}

		// All dashboard must have at least one tab
		if len(dashboard.DashboardTab) == 0 {
			t.Errorf("Dashboard %v: - Must have more than one dashboardtab", dashboard.Name)
		}

		// dashboardtab name set, to check duplicated tabs within each dashboard
		dashboardtabmap := make(map[string]bool)

		// All notifications in dashboard must have a summary
		if len(dashboard.Notifications) != 0 {
			for notificationindex, notification := range dashboard.Notifications {
				if notification.Summary == "" {
					t.Errorf("Notification %v in dashboard %v: - Must have a summary", notificationindex, dashboard.Name)
				}
			}
		}

		for tabindex, dashboardtab := range dashboard.DashboardTab {

			// All dashboardtab must have a name and a testgroup
			if dashboardtab.Name == "" || dashboardtab.TestGroupName == "" {
				t.Errorf("Dashboard %v, tab %v: - Must have a name and a testgroup name", dashboard.Name, tabindex)
			}

			// All dashboardtab within a dashboard must not have duplicated names
			if dashboardtabmap[dashboardtab.Name] {
				t.Errorf("Duplicated dashboardtab: %v", dashboardtab.Name)
			} else {
				dashboardtabmap[dashboardtab.Name] = true
			}

			// All testgroup in dashboard must be defined in testgroups
			if testgroupMap[dashboardtab.TestGroupName] == 0 {
				t.Errorf("Dashboard %v, tab %v: - Testgroup %v must be defined first",
					dashboard.Name, dashboardtab.Name, dashboardtab.TestGroupName)
			} else {
				testgroupMap[dashboardtab.TestGroupName] += 1
			}
		}
	}

	// No dup of dashboard groups, and no dup dashboard in a dashboard group
	groups := make(map[string]bool)
	tabs := make(map[string]string)

	for idx, dashboardGroup := range config.DashboardGroups {
		// All dashboard must have a name
		if dashboardGroup.Name == "" {
			t.Errorf("DashboardGroup %v: - DashboardGroup must have a name", idx)
		}

		// All dashboardgroup must not have duplicated names
		if _, ok := groups[dashboardGroup.Name]; ok {
			t.Errorf("Duplicated dashboard: %v", dashboardGroup.Name)
		} else {
			groups[dashboardGroup.Name] = true
		}

		for _, dashboard := range dashboardGroup.DashboardNames {
			// All dashboard must not have duplicated names
			if exist, ok := tabs[dashboard]; ok {
				t.Errorf("Duplicated dashboard %v in dashboard group %v and %v", dashboard, exist, dashboardGroup.Name)
			} else {
				tabs[dashboard] = dashboardGroup.Name
			}

			if _, ok := dashboardmap[dashboard]; !ok {
				t.Errorf("Dashboard %v needs to be defined before adding to a dashboard group!", dashboard)
			}
		}
	}

	// All Testgroup should be mapped to one or more tabs
	for testgroupname, occurance := range testgroupMap {
		if occurance == 1 {
			t.Errorf("Testgroup %v - defined but not used in any dashboards", testgroupname)
		}
	}

	// make sure items in sq-blocking dashboard matches sq configmap
	sqJobPool := []string{}
	for _, d := range config.Dashboards {
		if d.Name != "sq-blocking" {
			continue
		}

		for _, tab := range d.DashboardTab {
			for _, t := range config.TestGroups {
				if t.Name == tab.TestGroupName {
					job := strings.TrimPrefix(t.GcsPrefix, "kubernetes-jenkins/logs/")
					sqJobPool = append(sqJobPool, job)
					break
				}
			}
		}
	}

	sqConfigPath := "../../mungegithub/submit-queue/deployment/kubernetes/configmap.yaml"
	configData, err := ioutil.ReadFile(sqConfigPath)
	if err != nil {
		t.Errorf("Read Buffer Error for SQ Data : %v", err)
	}

	sqData := &SQConfig{}
	err = yaml.Unmarshal([]byte(configData), &sqData)
	if err != nil {
		t.Errorf("Unmarshal Error for SQ Data : %v", err)
	}

	for _, testgridJob := range sqJobPool {
		t.Errorf("Err : testgrid job %v not found in SQ config", testgridJob)
	}

	sqNonBlockingJobs := strings.Split(sqData.Data["submit-queue.nonblocking-jenkins-jobs"], ",")
	for _, sqJob := range sqNonBlockingJobs {
		if sqJob == "\"\"" { // ignore empty list of jobs
			continue
		}
		found := false
		for _, testgroup := range config.TestGroups {
			if testgroup.Name == sqJob {
				found = true
				break
			}
		}

		if !found {
			t.Errorf("Err : %v not found in testgrid config", sqJob)
		}
	}
}
<|endoftext|>