<commit_before>package server_manager_test

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"strings"
	"testing"

	"github.com/hashicorp/consul/consul/server_details"
	"github.com/hashicorp/consul/consul/server_manager"
)

var (
	localLogger    *log.Logger
	localLogBuffer *bytes.Buffer
)

func init() {
	localLogBuffer = new(bytes.Buffer)
	localLogger = log.New(localLogBuffer, "", 0)
}

func GetBufferedLogger() *log.Logger {
	return localLogger
}

type fauxConnPool struct {
}

func (s *fauxConnPool) PingConsulServer(server *server_details.ServerDetails) bool {
	return true
}

type fauxSerf struct {
}

func (s *fauxSerf) NumNodes() int {
	return 16384
}

func testServerManager() (sm *server_manager.ServerManager) {
	logger := GetBufferedLogger()
	logger = log.New(os.Stderr, "", log.LstdFlags)
	shutdownCh := make(chan struct{})
	sm = server_manager.New(logger, shutdownCh, &fauxSerf{}, &fauxConnPool{})
	return sm
}

// func (sm *ServerManager) AddServer(server *server_details.ServerDetails) {
func TestServerManager_AddServer(t *testing.T) {
	sm := testServerManager()
	var num int
	num = sm.NumServers()
	if num != 0 {
		t.Fatalf("Expected zero servers to start")
	}

	s1 := &server_details.ServerDetails{Name: "s1"}
	sm.AddServer(s1)
	num = sm.NumServers()
	if num != 1 {
		t.Fatalf("Expected one server")
	}

	sm.AddServer(s1)
	num = sm.NumServers()
	if num != 1 {
		t.Fatalf("Expected one server (still)")
	}

	s2 := &server_details.ServerDetails{Name: "s2"}
	sm.AddServer(s2)
	num = sm.NumServers()
	if num != 2 {
		t.Fatalf("Expected two servers")
	}
}

// func (sm *ServerManager) FindServer() (server *server_details.ServerDetails) {
func TestServerManager_FindServer(t *testing.T) {
	sm := testServerManager()

	if sm.FindServer() != nil {
		t.Fatalf("Expected nil return")
	}

	sm.AddServer(&server_details.ServerDetails{Name: "s1"})
	if sm.NumServers() != 1 {
		t.Fatalf("Expected one server")
	}

	s1 := sm.FindServer()
	if s1 == nil {
		t.Fatalf("Expected non-nil server")
	}
	if s1.Name != "s1" {
		t.Fatalf("Expected s1 server")
	}

	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server (still)")
	}

	sm.AddServer(&server_details.ServerDetails{Name: "s2"})
	if sm.NumServers() != 2 {
		t.Fatalf("Expected two servers")
	}
	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server (still)")
	}

	sm.NotifyFailedServer(s1)
	s2 := sm.FindServer()
	if s2 == nil || s2.Name != "s2" {
		t.Fatalf("Expected s2 server")
	}

	sm.NotifyFailedServer(s2)
	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server")
	}
}

// func New(logger *log.Logger, shutdownCh chan struct{}) (sm *ServerManager) {
func TestServerManager_New(t *testing.T) {
	logger := GetBufferedLogger()
	logger = log.New(os.Stderr, "", log.LstdFlags)
	shutdownCh := make(chan struct{})
	sm := server_manager.New(logger, shutdownCh, &fauxSerf{}, &fauxConnPool{})
	if sm == nil {
		t.Fatalf("ServerManager nil")
	}
}

// func (sm *ServerManager) NotifyFailedServer(server *server_details.ServerDetails) {
func TestServerManager_NotifyFailedServer(t *testing.T) {
	sm := testServerManager()

	if sm.NumServers() != 0 {
		t.Fatalf("Expected zero servers to start")
	}

	s1 := &server_details.ServerDetails{Name: "s1"}
	s2 := &server_details.ServerDetails{Name: "s2"}

	// Try notifying for a server that is not part of the server manager
	sm.NotifyFailedServer(s1)
	if sm.NumServers() != 0 {
		t.Fatalf("Expected zero servers to start")
	}
	sm.AddServer(s1)

	// Test again w/ a server not in the list
	sm.NotifyFailedServer(s2)
	if sm.NumServers() != 1 {
		t.Fatalf("Expected one server")
	}

	sm.AddServer(s2)
	if sm.NumServers() != 2 {
		t.Fatalf("Expected two servers")
	}

	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server")
	}

	sm.NotifyFailedServer(s2)
	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server (still)")
	}

	sm.NotifyFailedServer(s1)
	s2 = sm.FindServer()
	if s2 == nil || s2.Name != "s2" {
		t.Fatalf("Expected s2 server")
	}

	sm.NotifyFailedServer(s2)
	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server")
	}
}

// func (sm *ServerManager) NumServers() (numServers int) {
func TestServerManager_NumServers(t *testing.T) {
	sm := testServerManager()
	var num int
	num = sm.NumServers()
	if num != 0 {
		t.Fatalf("Expected zero servers to start")
	}

	s := &server_details.ServerDetails{}
	sm.AddServer(s)
	num = sm.NumServers()
	if num != 1 {
		t.Fatalf("Expected one server after AddServer")
	}
}

// func (sm *ServerManager) RebalanceServers() {
func TestServerManager_RebalanceServers(t *testing.T) {
	sm := testServerManager()
	const maxServers = 100
	const numShuffleTests = 100
	const uniquePassRate = 0.5

	// Make a huge list of nodes.
	for i := 0; i < maxServers; i++ {
		nodeName := fmt.Sprintf("s%02d", i)
		sm.AddServer(&server_details.ServerDetails{Name: nodeName})
	}

	// Keep track of how many unique shuffles we get.
	uniques := make(map[string]struct{}, maxServers)
	for i := 0; i < numShuffleTests; i++ {
		sm.RebalanceServers()

		var names []string
		for j := 0; j < maxServers; j++ {
			server := sm.FindServer()
			sm.NotifyFailedServer(server)
			names = append(names, server.Name)
		}
		key := strings.Join(names, "|")
		uniques[key] = struct{}{}
	}

	// We have to allow for the fact that there won't always be a unique
	// shuffle each pass, so we just look for smell here without the test
	// being flaky.
	if len(uniques) < int(maxServers*uniquePassRate) {
		t.Fatalf("unique shuffle ratio too low: %d/%d", len(uniques), maxServers)
	}
}

// func (sm *ServerManager) RemoveServer(server *server_details.ServerDetails) {
func TestServerManager_RemoveServer(t *testing.T) {
	const nodeNameFmt = "s%02d"
	sm := testServerManager()

	if sm.NumServers() != 0 {
		t.Fatalf("Expected zero servers to start")
	}

	// Test removing server before its added
	nodeName := fmt.Sprintf(nodeNameFmt, 1)
	s1 := &server_details.ServerDetails{Name: nodeName}
	sm.RemoveServer(s1)
	sm.AddServer(s1)

	nodeName = fmt.Sprintf(nodeNameFmt, 2)
	s2 := &server_details.ServerDetails{Name: nodeName}
	sm.RemoveServer(s2)
	sm.AddServer(s2)

	const maxServers = 19
	servers := make([]*server_details.ServerDetails, maxServers)
	// Already added two servers above
	for i := maxServers; i > 2; i-- {
		nodeName := fmt.Sprintf(nodeNameFmt, i)
		server := &server_details.ServerDetails{Name: nodeName}
		servers = append(servers, server)
		sm.AddServer(server)
	}
	sm.RebalanceServers()

	if sm.NumServers() != maxServers {
		t.Fatalf("Expected %d servers, received %d", maxServers, sm.NumServers())
	}

	findServer := func(server *server_details.ServerDetails) bool {
		for i := sm.NumServers(); i > 0; i-- {
			s := sm.FindServer()
			if s == server {
				return true
			}
		}
		return false
	}

	expectedNumServers := maxServers
	removedServers := make([]*server_details.ServerDetails, 0, maxServers)

	// Remove servers from the front of the list
	for i := 3; i > 0; i-- {
		server := sm.FindServer()
		if server == nil {
			t.Fatalf("FindServer returned nil")
		}
		sm.RemoveServer(server)
		expectedNumServers--
		if sm.NumServers() != expectedNumServers {
			t.Fatalf("Expected %d servers (got %d)", expectedNumServers, sm.NumServers())
		}
		if findServer(server) == true {
			t.Fatalf("Did not expect to find server %s after removal from the front", server.Name)
		}
		removedServers = append(removedServers, server)
	}

	// Remove server from the end of the list
	for i := 3; i > 0; i-- {
		server := sm.FindServer()
		sm.NotifyFailedServer(server)
		sm.RemoveServer(server)
		expectedNumServers--
		if sm.NumServers() != expectedNumServers {
			t.Fatalf("Expected %d servers (got %d)", expectedNumServers, sm.NumServers())
		}
		if findServer(server) == true {
			t.Fatalf("Did not expect to find server %s", server.Name)
		}
		removedServers = append(removedServers, server)
	}

	// Remove server from the middle of the list
	for i := 3; i > 0; i-- {
		server := sm.FindServer()
		sm.NotifyFailedServer(server)
		server2 := sm.FindServer()
		sm.NotifyFailedServer(server2) // server2 now at end of the list

		sm.RemoveServer(server)
		expectedNumServers--
		if sm.NumServers() != expectedNumServers {
			t.Fatalf("Expected %d servers (got %d)", expectedNumServers, sm.NumServers())
		}
		if findServer(server) == true {
			t.Fatalf("Did not expect to find server %s", server.Name)
		}
		removedServers = append(removedServers, server)
	}

	if sm.NumServers()+len(removedServers) != maxServers {
		t.Fatalf("Expected %d+%d=%d servers", sm.NumServers(), len(removedServers), maxServers)
	}

	// Drain the remaining servers from the middle
	for i := sm.NumServers(); i > 0; i-- {
		server := sm.FindServer()
		sm.NotifyFailedServer(server)
		server2 := sm.FindServer()
		sm.NotifyFailedServer(server2) // server2 now at end of the list
		sm.RemoveServer(server)
		removedServers = append(removedServers, server)
	}

	if sm.NumServers() != 0 {
		t.Fatalf("Expected an empty server list")
	}
	if len(removedServers) != maxServers {
		t.Fatalf("Expected all servers to be in removed server list")
	}
}

// func (sm *ServerManager) Start() {
<commit_msg>Add a quick length check<commit_after>package server_manager_test

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"strings"
	"testing"

	"github.com/hashicorp/consul/consul/server_details"
	"github.com/hashicorp/consul/consul/server_manager"
)

var (
	localLogger    *log.Logger
	localLogBuffer *bytes.Buffer
)

func init() {
	localLogBuffer = new(bytes.Buffer)
	localLogger = log.New(localLogBuffer, "", 0)
}

func GetBufferedLogger() *log.Logger {
	return localLogger
}

type fauxConnPool struct {
}

func (s *fauxConnPool) PingConsulServer(server *server_details.ServerDetails) bool {
	return true
}

type fauxSerf struct {
}

func (s *fauxSerf) NumNodes() int {
	return 16384
}

func testServerManager() (sm *server_manager.ServerManager) {
	logger := GetBufferedLogger()
	logger = log.New(os.Stderr, "", log.LstdFlags)
	shutdownCh := make(chan struct{})
	sm = server_manager.New(logger, shutdownCh, &fauxSerf{}, &fauxConnPool{})
	return sm
}

// func (sm *ServerManager) AddServer(server *server_details.ServerDetails) {
func TestServerManager_AddServer(t *testing.T) {
	sm := testServerManager()
	var num int
	num = sm.NumServers()
	if num != 0 {
		t.Fatalf("Expected zero servers to start")
	}

	s1 := &server_details.ServerDetails{Name: "s1"}
	sm.AddServer(s1)
	num = sm.NumServers()
	if num != 1 {
		t.Fatalf("Expected one server")
	}

	sm.AddServer(s1)
	num = sm.NumServers()
	if num != 1 {
		t.Fatalf("Expected one server (still)")
	}

	s2 := &server_details.ServerDetails{Name: "s2"}
	sm.AddServer(s2)
	num = sm.NumServers()
	if num != 2 {
		t.Fatalf("Expected two servers")
	}
}

// func (sm *ServerManager) FindServer() (server *server_details.ServerDetails) {
func TestServerManager_FindServer(t *testing.T) {
	sm := testServerManager()

	if sm.FindServer() != nil {
		t.Fatalf("Expected nil return")
	}

	sm.AddServer(&server_details.ServerDetails{Name: "s1"})
	if sm.NumServers() != 1 {
		t.Fatalf("Expected one server")
	}

	s1 := sm.FindServer()
	if s1 == nil {
		t.Fatalf("Expected non-nil server")
	}
	if s1.Name != "s1" {
		t.Fatalf("Expected s1 server")
	}

	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server (still)")
	}

	sm.AddServer(&server_details.ServerDetails{Name: "s2"})
	if sm.NumServers() != 2 {
		t.Fatalf("Expected two servers")
	}
	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server (still)")
	}

	sm.NotifyFailedServer(s1)
	s2 := sm.FindServer()
	if s2 == nil || s2.Name != "s2" {
		t.Fatalf("Expected s2 server")
	}

	sm.NotifyFailedServer(s2)
	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server")
	}
}

// func New(logger *log.Logger, shutdownCh chan struct{}) (sm *ServerManager) {
func TestServerManager_New(t *testing.T) {
	logger := GetBufferedLogger()
	logger = log.New(os.Stderr, "", log.LstdFlags)
	shutdownCh := make(chan struct{})
	sm := server_manager.New(logger, shutdownCh, &fauxSerf{}, &fauxConnPool{})
	if sm == nil {
		t.Fatalf("ServerManager nil")
	}
}

// func (sm *ServerManager) NotifyFailedServer(server *server_details.ServerDetails) {
func TestServerManager_NotifyFailedServer(t *testing.T) {
	sm := testServerManager()

	if sm.NumServers() != 0 {
		t.Fatalf("Expected zero servers to start")
	}

	s1 := &server_details.ServerDetails{Name: "s1"}
	s2 := &server_details.ServerDetails{Name: "s2"}

	// Try notifying for a server that is not part of the server manager
	sm.NotifyFailedServer(s1)
	if sm.NumServers() != 0 {
		t.Fatalf("Expected zero servers to start")
	}
	sm.AddServer(s1)

	// Test again w/ a server not in the list
	sm.NotifyFailedServer(s2)
	if sm.NumServers() != 1 {
		t.Fatalf("Expected one server")
	}

	sm.AddServer(s2)
	if sm.NumServers() != 2 {
		t.Fatalf("Expected two servers")
	}

	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server")
	}

	sm.NotifyFailedServer(s2)
	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server (still)")
	}

	sm.NotifyFailedServer(s1)
	s2 = sm.FindServer()
	if s2 == nil || s2.Name != "s2" {
		t.Fatalf("Expected s2 server")
	}

	sm.NotifyFailedServer(s2)
	s1 = sm.FindServer()
	if s1 == nil || s1.Name != "s1" {
		t.Fatalf("Expected s1 server")
	}
}

// func (sm *ServerManager) NumServers() (numServers int) {
func TestServerManager_NumServers(t *testing.T) {
	sm := testServerManager()
	var num int
	num = sm.NumServers()
	if num != 0 {
		t.Fatalf("Expected zero servers to start")
	}

	s := &server_details.ServerDetails{}
	sm.AddServer(s)
	num = sm.NumServers()
	if num != 1 {
		t.Fatalf("Expected one server after AddServer")
	}
}

// func (sm *ServerManager) RebalanceServers() {
func TestServerManager_RebalanceServers(t *testing.T) {
	sm := testServerManager()
	const maxServers = 100
	const numShuffleTests = 100
	const uniquePassRate = 0.5

	// Make a huge list of nodes.
	for i := 0; i < maxServers; i++ {
		nodeName := fmt.Sprintf("s%02d", i)
		sm.AddServer(&server_details.ServerDetails{Name: nodeName})
	}

	// Keep track of how many unique shuffles we get.
	uniques := make(map[string]struct{}, maxServers)
	for i := 0; i < numShuffleTests; i++ {
		sm.RebalanceServers()

		var names []string
		for j := 0; j < maxServers; j++ {
			server := sm.FindServer()
			sm.NotifyFailedServer(server)
			names = append(names, server.Name)
		}
		key := strings.Join(names, "|")
		uniques[key] = struct{}{}
	}

	// We have to allow for the fact that there won't always be a unique
	// shuffle each pass, so we just look for smell here without the test
	// being flaky.
	if len(uniques) < int(maxServers*uniquePassRate) {
		t.Fatalf("unique shuffle ratio too low: %d/%d", len(uniques), maxServers)
	}
}

// func (sm *ServerManager) RemoveServer(server *server_details.ServerDetails) {
func TestServerManager_RemoveServer(t *testing.T) {
	const nodeNameFmt = "s%02d"
	sm := testServerManager()

	if sm.NumServers() != 0 {
		t.Fatalf("Expected zero servers to start")
	}

	// Test removing server before its added
	nodeName := fmt.Sprintf(nodeNameFmt, 1)
	s1 := &server_details.ServerDetails{Name: nodeName}
	sm.RemoveServer(s1)
	sm.AddServer(s1)

	nodeName = fmt.Sprintf(nodeNameFmt, 2)
	s2 := &server_details.ServerDetails{Name: nodeName}
	sm.RemoveServer(s2)
	sm.AddServer(s2)

	const maxServers = 19
	servers := make([]*server_details.ServerDetails, maxServers)
	// Already added two servers above
	for i := maxServers; i > 2; i-- {
		nodeName := fmt.Sprintf(nodeNameFmt, i)
		server := &server_details.ServerDetails{Name: nodeName}
		servers = append(servers, server)
		sm.AddServer(server)
	}
	if sm.NumServers() != maxServers {
		t.Fatalf("Expected %d servers, received %d", maxServers, sm.NumServers())
	}

	sm.RebalanceServers()

	if sm.NumServers() != maxServers {
		t.Fatalf("Expected %d servers, received %d", maxServers, sm.NumServers())
	}

	findServer := func(server *server_details.ServerDetails) bool {
		for i := sm.NumServers(); i > 0; i-- {
			s := sm.FindServer()
			if s == server {
				return true
			}
		}
		return false
	}

	expectedNumServers := maxServers
	removedServers := make([]*server_details.ServerDetails, 0, maxServers)

	// Remove servers from the front of the list
	for i := 3; i > 0; i-- {
		server := sm.FindServer()
		if server == nil {
			t.Fatalf("FindServer returned nil")
		}
		sm.RemoveServer(server)
		expectedNumServers--
		if sm.NumServers() != expectedNumServers {
			t.Fatalf("Expected %d servers (got %d)", expectedNumServers, sm.NumServers())
		}
		if findServer(server) == true {
			t.Fatalf("Did not expect to find server %s after removal from the front", server.Name)
		}
		removedServers = append(removedServers, server)
	}

	// Remove server from the end of the list
	for i := 3; i > 0; i-- {
		server := sm.FindServer()
		sm.NotifyFailedServer(server)
		sm.RemoveServer(server)
		expectedNumServers--
		if sm.NumServers() != expectedNumServers {
			t.Fatalf("Expected %d servers (got %d)", expectedNumServers, sm.NumServers())
		}
		if findServer(server) == true {
			t.Fatalf("Did not expect to find server %s", server.Name)
		}
		removedServers = append(removedServers, server)
	}

	// Remove server from the middle of the list
	for i := 3; i > 0; i-- {
		server := sm.FindServer()
		sm.NotifyFailedServer(server)
		server2 := sm.FindServer()
		sm.NotifyFailedServer(server2) // server2 now at end of the list

		sm.RemoveServer(server)
		expectedNumServers--
		if sm.NumServers() != expectedNumServers {
			t.Fatalf("Expected %d servers (got %d)", expectedNumServers, sm.NumServers())
		}
		if findServer(server) == true {
			t.Fatalf("Did not expect to find server %s", server.Name)
		}
		removedServers = append(removedServers, server)
	}

	if sm.NumServers()+len(removedServers) != maxServers {
		t.Fatalf("Expected %d+%d=%d servers", sm.NumServers(), len(removedServers), maxServers)
	}

	// Drain the remaining servers from the middle
	for i := sm.NumServers(); i > 0; i-- {
		server := sm.FindServer()
		sm.NotifyFailedServer(server)
		server2 := sm.FindServer()
		sm.NotifyFailedServer(server2) // server2 now at end of the list
		sm.RemoveServer(server)
		removedServers = append(removedServers, server)
	}

	if sm.NumServers() != 0 {
		t.Fatalf("Expected an empty server list")
	}
	if len(removedServers) != maxServers {
		t.Fatalf("Expected all servers to be in removed server list")
	}
}

// func (sm *ServerManager) Start() {
<|endoftext|>


<commit_before>package k8sutil

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/coreos/kube-etcd-controller/pkg/util/etcdutil"
	"k8s.io/kubernetes/pkg/api"
	apierrors "k8s.io/kubernetes/pkg/api/errors"
	unversionedAPI "k8s.io/kubernetes/pkg/api/unversioned"
	k8sv1api "k8s.io/kubernetes/pkg/api/v1"
	"k8s.io/kubernetes/pkg/client/restclient"
	"k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/labels"
	"k8s.io/kubernetes/pkg/util/intstr"
	"k8s.io/kubernetes/pkg/util/wait"
	"k8s.io/kubernetes/pkg/watch"
)

const (
	etcdImage = "quay.io/coreos/etcd:v3.1.0-alpha.1"

	// TODO: This is constant for current purpose. We might make it configurable later.
	etcdDir    = "/var/etcd"
	dataDir    = etcdDir + "/data"
	backupFile = "/var/etcd/latest.backup"
)

func makeRestoreInitContainerSpec(backupAddr, name, token string) string {
	spec := []api.Container{
		{
			Name:  "fetch-backup",
			Image: "tutum/curl",
			Command: []string{
				"/bin/sh", "-c",
				fmt.Sprintf("curl -o %s http://%s/backup", backupFile, backupAddr),
			},
			VolumeMounts: []api.VolumeMount{
				{Name: "etcd-data", MountPath: etcdDir},
			},
		},
		{
			Name:  "restore-datadir",
			Image: etcdImage,
			Command: []string{
				"/bin/sh", "-c",
				fmt.Sprintf("ETCDCTL_API=3 etcdctl snapshot restore %[1]s"+
					" --name %[2]s"+
					" --initial-cluster %[2]s=http://%[2]s:2380"+
					" --initial-cluster-token %[3]s"+
					" --initial-advertise-peer-urls http://%[2]s:2380"+
					" --data-dir %[4]s", backupFile, name, token, dataDir),
			},
			VolumeMounts: []api.VolumeMount{
				{Name: "etcd-data", MountPath: etcdDir},
			},
		},
	}
	b, err := json.Marshal(spec)
	if err != nil {
		panic(err)
	}
	return string(b)
}

func GetNodePortString(srv *api.Service) string {
	return fmt.Sprint(srv.Spec.Ports[0].NodePort)
}

func MakeBackupHostPort(clusterName string) string {
	return fmt.Sprintf("%s:19999", makeBackupName(clusterName))
}

func WithAddMemberInitContainer(p *api.Pod, endpoints []string, name string, peerURLs []string) *api.Pod {
	spec := []api.Container{
		{
			Name:  "add-member",
			Image: etcdImage,
			Command: []string{
				"/bin/sh", "-c",
				fmt.Sprintf("ETCDCTL_API=3 etcdctl --endpoints=%s member add %s --peer-urls=%s", strings.Join(endpoints, ","), name, strings.Join(peerURLs, ",")),
			},
		},
	}
	b, err := json.Marshal(spec)
	if err != nil {
		panic(err)
	}
	if p.Annotations == nil {
		p.Annotations = map[string]string{}
	}
	p.Annotations[k8sv1api.PodInitContainersAnnotationKey] = string(b)
	return p
}

func makeBackupName(clusterName string) string {
	return fmt.Sprintf("%s-backup-tool", clusterName)
}

func CreateEtcdService(kclient *unversioned.Client, etcdName, clusterName, ns string) error {
	svc := makeEtcdService(etcdName, clusterName)
	if _, err := kclient.Services(ns).Create(svc); err != nil {
		return err
	}
	return nil
}

func CreateEtcdNodePortService(kclient *unversioned.Client, etcdName, clusterName, ns string) (*api.Service, error) {
	svc := makeEtcdNodePortService(etcdName, clusterName)
	return kclient.Services(ns).Create(svc)
}

// TODO: use a struct to replace the huge arg list.
func CreateAndWaitPod(kclient *unversioned.Client, pod *api.Pod, m *etcdutil.Member, ns string) error {
	if _, err := kclient.Pods(ns).Create(pod); err != nil {
		return err
	}
	w, err := kclient.Pods(ns).Watch(api.SingleObject(api.ObjectMeta{Name: m.Name}))
	if err != nil {
		return err
	}
	_, err = watch.Until(100*time.Second, w, unversioned.PodRunningAndReady)
	// TODO: cleanup pod on failure
	return err
}

// TODO: converge the port logic with member ClientAddr() and PeerAddr()
func makeEtcdService(etcdName, clusterName string) *api.Service {
	labels := map[string]string{
		"etcd_node":    etcdName,
		"etcd_cluster": clusterName,
	}
	svc := &api.Service{
		ObjectMeta: api.ObjectMeta{
			Name:   etcdName,
			Labels: labels,
		},
		Spec: api.ServiceSpec{
			Ports: []api.ServicePort{
				{
					Name:       "server",
					Port:       2380,
					TargetPort: intstr.FromInt(2380),
					Protocol:   api.ProtocolTCP,
				},
				{
					Name:       "client",
					Port:       2379,
					TargetPort: intstr.FromInt(2379),
					Protocol:   api.ProtocolTCP,
				},
			},
			Selector: labels,
		},
	}
	return svc
}

func makeEtcdNodePortService(etcdName, clusterName string) *api.Service {
	labels := map[string]string{
		"etcd_node":    etcdName,
		"etcd_cluster": clusterName,
	}
	svc := &api.Service{
		ObjectMeta: api.ObjectMeta{
			Name:   etcdName + "-nodeport",
			Labels: labels,
		},
		Spec: api.ServiceSpec{
			Ports: []api.ServicePort{
				{
					Name:       "server",
					Port:       2380,
					TargetPort: intstr.FromInt(2380),
					Protocol:   api.ProtocolTCP,
				},
			},
			Type:     api.ServiceTypeNodePort,
			Selector: labels,
		},
	}
	return svc
}

func AddRecoveryToPod(pod *api.Pod, clusterName, name, token string) {
	if pod.Annotations == nil {
		pod.Annotations = map[string]string{}
	}
	pod.Annotations[k8sv1api.PodInitContainersAnnotationKey] = makeRestoreInitContainerSpec(MakeBackupHostPort(clusterName), name, token)
}

// todo: use a struct to replace the huge arg list.
func MakeEtcdPod(m *etcdutil.Member, initialCluster []string, clusterName, state, token string, antiAffinity bool, hostNet bool) *api.Pod {
	commands := []string{
		"/usr/local/bin/etcd",
		"--data-dir",
		dataDir,
		"--name",
		m.Name,
		"--initial-advertise-peer-urls",
		m.PeerAddr(),
		"--listen-peer-urls",
		"http://0.0.0.0:2380",
		"--listen-client-urls",
		"http://0.0.0.0:2379",
		"--advertise-client-urls",
		m.ClientAddr(),
		"--initial-cluster",
		strings.Join(initialCluster, ","),
		"--initial-cluster-state",
		state,
	}
	if state == "new" {
		commands = append(commands, "--initial-cluster-token", token)
	}
	pod := &api.Pod{
		ObjectMeta: api.ObjectMeta{
			Name: m.Name,
			Labels: map[string]string{
				"app":          "etcd",
				"etcd_node":    m.Name,
				"etcd_cluster": clusterName,
			},
		},
		Spec: api.PodSpec{
			Containers: []api.Container{
				{
					Command: commands,
					Name:    m.Name,
					Image:   etcdImage,
					Ports: []api.ContainerPort{
						{
							Name:          "server",
							ContainerPort: int32(2380),
							Protocol:      api.ProtocolTCP,
						},
					},
					// set pod to ready when a get succeeds
					ReadinessProbe: &api.Probe{
						Handler: api.Handler{
							Exec: &api.ExecAction{
								Command: []string{"/bin/sh", "-c",
									"ETCDCTL_API=3 etcdctl get foo"},
							},
						},
						InitialDelaySeconds: 1,
						TimeoutSeconds:      10,
					},
					// a pod is alive only if a get succeeds
					// the etcd pod should die if liveness probing fails.
					LivenessProbe: &api.Probe{
						Handler: api.Handler{
							Exec: &api.ExecAction{
								Command: []string{"/bin/sh", "-c",
									"ETCDCTL_API=3 etcdctl get foo"},
							},
						},
						InitialDelaySeconds: 10,
						TimeoutSeconds:      10,
						// probe every 60 seconds
						PeriodSeconds: 60,
						// failed for 3 minutes
						FailureThreshold: 3,
					},
					VolumeMounts: []api.VolumeMount{
						{Name: "etcd-data", MountPath: etcdDir},
					},
				},
			},
			RestartPolicy: api.RestartPolicyNever,
			SecurityContext: &api.PodSecurityContext{
				HostNetwork: hostNet,
			},
			Volumes: []api.Volume{
				{Name: "etcd-data", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
			},
		},
	}

	if !antiAffinity {
		return pod
	}

	// set pod anti-affinity with the pods that belongs to the same etcd cluster
	affinity := api.Affinity{
		PodAntiAffinity: &api.PodAntiAffinity{
			RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
				{
					LabelSelector: &unversionedAPI.LabelSelector{
						MatchLabels: map[string]string{
							"etcd_cluster": clusterName,
						},
					},
				},
			},
		},
	}

	affinityb, err := json.Marshal(affinity)
	if err != nil {
		panic("failed to marshal affinty struct")
	}

	pod.Annotations[api.AffinityAnnotationKey] = string(affinityb)

	return pod
}

func MustGetInClusterMasterHost() string {
	cfg, err := restclient.InClusterConfig()
	if err != nil {
		panic(err)
	}
	return cfg.Host
}

func MustCreateClient(host string, tlsInsecure bool, tlsConfig *restclient.TLSClientConfig) *unversioned.Client {
	if len(host) == 0 {
		c, err := unversioned.NewInCluster()
		if err != nil {
			panic(err)
		}
		return c
	}
	cfg := &restclient.Config{
		Host:  host,
		QPS:   100,
		Burst: 100,
	}
	hostUrl, err := url.Parse(host)
	if err != nil {
		panic(fmt.Sprintf("error parsing host url %s : %v", host, err))
	}
	if hostUrl.Scheme == "https" {
		cfg.TLSClientConfig = *tlsConfig
		cfg.Insecure = tlsInsecure
	}
	c, err := unversioned.New(cfg)
	if err != nil {
		panic(err)
	}
	return c
}

func IsKubernetesResourceAlreadyExistError(err error) bool {
	se, ok := err.(*apierrors.StatusError)
	if !ok {
		return false
	}
	if se.Status().Code == http.StatusConflict && se.Status().Reason == unversionedAPI.StatusReasonAlreadyExists {
		return true
	}
	return false
}

func IsKubernetesResourceNotFoundError(err error) bool {
	se, ok := err.(*apierrors.StatusError)
	if !ok {
		return false
	}
	if se.Status().Code == http.StatusNotFound && se.Status().Reason == unversionedAPI.StatusReasonNotFound {
		return true
	}
	return false
}

func ListETCDCluster(host, ns string, httpClient *http.Client) (*http.Response, error) {
	return httpClient.Get(fmt.Sprintf("%s/apis/coreos.com/v1/namespaces/%s/etcdclusters",
		host, ns))
}

func WatchETCDCluster(host, ns string, httpClient *http.Client, resourceVersion string) (*http.Response, error) {
	return httpClient.Get(fmt.Sprintf("%s/apis/coreos.com/v1/namespaces/%s/etcdclusters?watch=true&resourceVersion=%s",
		host, ns, resourceVersion))
}

func WaitEtcdTPRReady(httpClient *http.Client, interval, timeout time.Duration, host, ns string) error {
	return wait.Poll(interval, timeout, func() (bool, error) {
		resp, err := ListETCDCluster(host, ns, httpClient)
		if err != nil {
			return false, err
		}
		defer resp.Body.Close()

		switch resp.StatusCode {
		case http.StatusOK:
			return true, nil
		case http.StatusNotFound: // not set up yet. wait.
			return false, nil
		default:
			return false, fmt.Errorf("invalid status code: %v", resp.Status)
		}
	})
}

func SliceReadyAndUnreadyPods(podList *api.PodList) (ready, unready []string) {
	for i := range podList.Items {
		pod := &podList.Items[i]
		if pod.Status.Phase == api.PodRunning && api.IsPodReady(pod) {
			ready = append(ready, pod.Name)
			continue
		}
		unready = append(unready, pod.Name)
	}
	return
}

func EtcdPodListOpt(clusterName string) api.ListOptions {
	return api.ListOptions{
		LabelSelector: labels.SelectorFromSet(map[string]string{
			"etcd_cluster": clusterName,
			"app":          "etcd",
		}),
	}
}
<commit_msg>refine etcd pod readiness probe spec<commit_after>package k8sutil

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/coreos/kube-etcd-controller/pkg/util/etcdutil"
	"k8s.io/kubernetes/pkg/api"
	apierrors "k8s.io/kubernetes/pkg/api/errors"
	unversionedAPI "k8s.io/kubernetes/pkg/api/unversioned"
	k8sv1api "k8s.io/kubernetes/pkg/api/v1"
	"k8s.io/kubernetes/pkg/client/restclient"
	"k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/labels"
	"k8s.io/kubernetes/pkg/util/intstr"
	"k8s.io/kubernetes/pkg/util/wait"
	"k8s.io/kubernetes/pkg/watch"
)

const (
	etcdImage = "quay.io/coreos/etcd:v3.1.0-alpha.1"

	// TODO: This is constant for current purpose. We might make it configurable later.
	etcdDir    = "/var/etcd"
	dataDir    = etcdDir + "/data"
	backupFile = "/var/etcd/latest.backup"
)

func makeRestoreInitContainerSpec(backupAddr, name, token string) string {
	spec := []api.Container{
		{
			Name:  "fetch-backup",
			Image: "tutum/curl",
			Command: []string{
				"/bin/sh", "-c",
				fmt.Sprintf("curl -o %s http://%s/backup", backupFile, backupAddr),
			},
			VolumeMounts: []api.VolumeMount{
				{Name: "etcd-data", MountPath: etcdDir},
			},
		},
		{
			Name:  "restore-datadir",
			Image: etcdImage,
			Command: []string{
				"/bin/sh", "-c",
				fmt.Sprintf("ETCDCTL_API=3 etcdctl snapshot restore %[1]s"+
					" --name %[2]s"+
					" --initial-cluster %[2]s=http://%[2]s:2380"+
					" --initial-cluster-token %[3]s"+
					" --initial-advertise-peer-urls http://%[2]s:2380"+
					" --data-dir %[4]s", backupFile, name, token, dataDir),
			},
			VolumeMounts: []api.VolumeMount{
				{Name: "etcd-data", MountPath: etcdDir},
			},
		},
	}
	b, err := json.Marshal(spec)
	if err != nil {
		panic(err)
	}
	return string(b)
}

func GetNodePortString(srv *api.Service) string {
	return fmt.Sprint(srv.Spec.Ports[0].NodePort)
}

func MakeBackupHostPort(clusterName string) string {
	return fmt.Sprintf("%s:19999", makeBackupName(clusterName))
}

func WithAddMemberInitContainer(p *api.Pod, endpoints []string, name string, peerURLs []string) *api.Pod {
	spec := []api.Container{
		{
			Name:  "add-member",
			Image: etcdImage,
			Command: []string{
				"/bin/sh", "-c",
				fmt.Sprintf("ETCDCTL_API=3 etcdctl --endpoints=%s member add %s --peer-urls=%s", strings.Join(endpoints, ","), name, strings.Join(peerURLs, ",")),
			},
		},
	}
	b, err := json.Marshal(spec)
	if err != nil {
		panic(err)
	}
	if p.Annotations == nil {
		p.Annotations = map[string]string{}
	}
	p.Annotations[k8sv1api.PodInitContainersAnnotationKey] = string(b)
	return p
}

func makeBackupName(clusterName string) string {
	return fmt.Sprintf("%s-backup-tool", clusterName)
}

func CreateEtcdService(kclient *unversioned.Client, etcdName, clusterName, ns string) error {
	svc := makeEtcdService(etcdName, clusterName)
	if _, err := kclient.Services(ns).Create(svc); err != nil {
		return err
	}
	return nil
}

func CreateEtcdNodePortService(kclient *unversioned.Client, etcdName, clusterName, ns string) (*api.Service, error) {
	svc := makeEtcdNodePortService(etcdName, clusterName)
	return kclient.Services(ns).Create(svc)
}

// TODO: use a struct to replace the huge arg list.
func CreateAndWaitPod(kclient *unversioned.Client, pod *api.Pod, m *etcdutil.Member, ns string) error {
	if _, err := kclient.Pods(ns).Create(pod); err != nil {
		return err
	}
	w, err := kclient.Pods(ns).Watch(api.SingleObject(api.ObjectMeta{Name: m.Name}))
	if err != nil {
		return err
	}
	_, err = watch.Until(100*time.Second, w, unversioned.PodRunningAndReady)
	// TODO: cleanup pod on failure
	return err
}

// TODO: converge the port logic with member ClientAddr() and PeerAddr()
func makeEtcdService(etcdName, clusterName string) *api.Service {
	labels := map[string]string{
		"etcd_node":    etcdName,
		"etcd_cluster": clusterName,
	}
	svc := &api.Service{
		ObjectMeta: api.ObjectMeta{
			Name:   etcdName,
			Labels: labels,
		},
		Spec: api.ServiceSpec{
			Ports: []api.ServicePort{
				{
					Name:       "server",
					Port:       2380,
					TargetPort: intstr.FromInt(2380),
					Protocol:   api.ProtocolTCP,
				},
				{
					Name:       "client",
					Port:       2379,
					TargetPort: intstr.FromInt(2379),
					Protocol:   api.ProtocolTCP,
				},
			},
			Selector: labels,
		},
	}
	return svc
}

func makeEtcdNodePortService(etcdName, clusterName string) *api.Service {
	labels := map[string]string{
		"etcd_node":    etcdName,
		"etcd_cluster": clusterName,
	}
	svc := &api.Service{
		ObjectMeta: api.ObjectMeta{
			Name:   etcdName + "-nodeport",
			Labels: labels,
		},
		Spec: api.ServiceSpec{
			Ports: []api.ServicePort{
				{
					Name:       "server",
					Port:       2380,
					TargetPort: intstr.FromInt(2380),
					Protocol:   api.ProtocolTCP,
				},
			},
			Type:     api.ServiceTypeNodePort,
			Selector: labels,
		},
	}
	return svc
}

func AddRecoveryToPod(pod *api.Pod, clusterName, name, token string) {
	if pod.Annotations == nil {
		pod.Annotations = map[string]string{}
	}
	pod.Annotations[k8sv1api.PodInitContainersAnnotationKey] = makeRestoreInitContainerSpec(MakeBackupHostPort(clusterName), name, token)
}

// todo: use a struct to replace the huge arg list.
func MakeEtcdPod(m *etcdutil.Member, initialCluster []string, clusterName, state, token string, antiAffinity bool, hostNet bool) *api.Pod {
	commands := []string{
		"/usr/local/bin/etcd",
		"--data-dir",
		dataDir,
		"--name",
		m.Name,
		"--initial-advertise-peer-urls",
		m.PeerAddr(),
		"--listen-peer-urls",
		"http://0.0.0.0:2380",
		"--listen-client-urls",
		"http://0.0.0.0:2379",
		"--advertise-client-urls",
		m.ClientAddr(),
		"--initial-cluster",
		strings.Join(initialCluster, ","),
		"--initial-cluster-state",
		state,
	}
	if state == "new" {
		commands = append(commands, "--initial-cluster-token", token)
	}
	pod := &api.Pod{
		ObjectMeta: api.ObjectMeta{
			Name: m.Name,
			Labels: map[string]string{
				"app":          "etcd",
				"etcd_node":    m.Name,
				"etcd_cluster": clusterName,
			},
		},
		Spec: api.PodSpec{
			Containers: []api.Container{
				{
					Command: commands,
					Name:    m.Name,
					Image:   etcdImage,
					Ports: []api.ContainerPort{
						{
							Name:          "server",
							ContainerPort: int32(2380),
							Protocol:      api.ProtocolTCP,
						},
					},
					// set pod to ready when a get succeeds
					ReadinessProbe: &api.Probe{
						Handler: api.Handler{
							Exec: &api.ExecAction{
								Command: []string{"/bin/sh", "-c",
									"ETCDCTL_API=3 etcdctl get foo"},
							},
						},
						// if an etcd member tries to join quorum, it has 5s strict check
						// before it can serve any client request
						InitialDelaySeconds: 7,
						TimeoutSeconds:      10,
						PeriodSeconds:       10,
						FailureThreshold:    3,
					},
					// a pod is alive only if a get succeeds
					// the etcd pod should die if liveness probing fails.
					LivenessProbe: &api.Probe{
						Handler: api.Handler{
							Exec: &api.ExecAction{
								Command: []string{"/bin/sh", "-c",
									"ETCDCTL_API=3 etcdctl get foo"},
							},
						},
						InitialDelaySeconds: 10,
						TimeoutSeconds:      10,
						// probe every 60 seconds
						PeriodSeconds: 60,
						// failed for 3 minutes
						FailureThreshold: 3,
					},
					VolumeMounts: []api.VolumeMount{
						{Name: "etcd-data", MountPath: etcdDir},
					},
				},
			},
			RestartPolicy: api.RestartPolicyNever,
			SecurityContext: &api.PodSecurityContext{
				HostNetwork: hostNet,
			},
			Volumes: []api.Volume{
				{Name: "etcd-data", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
			},
		},
	}

	if !antiAffinity {
		return pod
	}

	// set pod anti-affinity with the pods that belongs to the same etcd cluster
	affinity := api.Affinity{
		PodAntiAffinity: &api.PodAntiAffinity{
			RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
				{
					LabelSelector: &unversionedAPI.LabelSelector{
						MatchLabels: map[string]string{
							"etcd_cluster": clusterName,
						},
					},
				},
			},
		},
	}

	affinityb, err := json.Marshal(affinity)
	if err != nil {
		panic("failed to marshal affinty struct")
	}

	pod.Annotations[api.AffinityAnnotationKey] = string(affinityb)

	return pod
}

func MustGetInClusterMasterHost() string {
	cfg, err := restclient.InClusterConfig()
	if err != nil {
		panic(err)
	}
	return cfg.Host
}

func MustCreateClient(host string, tlsInsecure bool, tlsConfig *restclient.TLSClientConfig) *unversioned.Client {
	if len(host) == 0 {
		c, err := unversioned.NewInCluster()
		if err != nil {
			panic(err)
		}
		return c
	}
	cfg := &restclient.Config{
		Host:  host,
		QPS:   100,
		Burst: 100,
	}
	hostUrl, err := url.Parse(host)
	if err != nil {
		panic(fmt.Sprintf("error parsing host url %s : %v", host, err))
	}
	if hostUrl.Scheme == "https" {
		cfg.TLSClientConfig = *tlsConfig
		cfg.Insecure = tlsInsecure
	}
	c, err := unversioned.New(cfg)
	if err != nil {
		panic(err)
	}
	return c
}

func IsKubernetesResourceAlreadyExistError(err error) bool {
	se, ok := err.(*apierrors.StatusError)
	if !ok {
		return false
	}
	if se.Status().Code == http.StatusConflict && se.Status().Reason == unversionedAPI.StatusReasonAlreadyExists {
		return true
	}
	return false
}

func IsKubernetesResourceNotFoundError(err error) bool {
	se, ok := err.(*apierrors.StatusError)
	if !ok {
		return false
	}
	if se.Status().Code == http.StatusNotFound && se.Status().Reason == unversionedAPI.StatusReasonNotFound {
		return true
	}
	return false
}

func ListETCDCluster(host, ns string, httpClient *http.Client) (*http.Response, error) {
	return httpClient.Get(fmt.Sprintf("%s/apis/coreos.com/v1/namespaces/%s/etcdclusters",
		host, ns))
}

func WatchETCDCluster(host, ns string, httpClient *http.Client, resourceVersion string) (*http.Response, error) {
	return httpClient.Get(fmt.Sprintf("%s/apis/coreos.com/v1/namespaces/%s/etcdclusters?watch=true&resourceVersion=%s",
		host, ns, resourceVersion))
}

func WaitEtcdTPRReady(httpClient *http.Client, interval, timeout time.Duration, host, ns string) error {
	return wait.Poll(interval, timeout, func() (bool, error) {
		resp, err := ListETCDCluster(host, ns, httpClient)
		if err != nil {
			return false, err
		}
		defer resp.Body.Close()

		switch resp.StatusCode {
		case http.StatusOK:
			return true, nil
		case http.StatusNotFound: // not set up yet. wait.
			return false, nil
		default:
			return false, fmt.Errorf("invalid status code: %v", resp.Status)
		}
	})
}

func SliceReadyAndUnreadyPods(podList *api.PodList) (ready, unready []string) {
	for i := range podList.Items {
		pod := &podList.Items[i]
		if pod.Status.Phase == api.PodRunning && api.IsPodReady(pod) {
			ready = append(ready, pod.Name)
			continue
		}
		unready = append(unready, pod.Name)
	}
	return
}

func EtcdPodListOpt(clusterName string) api.ListOptions {
	return api.ListOptions{
		LabelSelector: labels.SelectorFromSet(map[string]string{
			"etcd_cluster": clusterName,
			"app":          "etcd",
		}),
	}
}
<|endoftext|>


<commit_before>// +build linux,native darwin,native

package nativetest

import (
	"testing"

	"github.com/taskcluster/slugid-go/slugid"
	"github.com/taskcluster/taskcluster-worker/worker/workertest"
)

/*
 - folder with multiple artifacts, and sub-folders
 - content type for folder artifacts
 + file artifact is a folder
*/

func TestArtifacts(t *testing.T) {
	debug("### Testing artifact plugin with native engine")
	debug("generated filename: %s", filename)
	artifactCase.Test(t)
}

var filename = slugid.Nice()
var artifactCase = workertest.Case{
	Engine:       "native",
	Concurrency:  1,
	EngineConfig: engineConfig,
	PluginConfig: pluginConfig,
	Tasks: []workertest.Task{{
		Title:   "Artifact File",
		Success: true,
		Payload: `{
			"command": ["sh", "-c", "echo 'hello-world' && echo 42 > ` + filename + `.txt"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public/result.txt",
					"type": "file",
					"path": "` + filename + `.txt"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			"public/logs/live.log":         workertest.ReferenceArtifact(),
			"public/logs/live_backing.log": workertest.GrepArtifact("hello-world"),
			"public/result.txt":            workertest.GrepArtifact("42"),
		},
	}, {
		Title:   "Artifact Directory",
		Success: true,
		Payload: `{
			"command": ["sh", "-c", "echo 'hello-world' && mkdir -p sub/subsub/ && echo 42 > sub/subsub/result.txt"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public",
					"type": "directory",
					"path": "sub"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			"public/logs/live.log":         workertest.ReferenceArtifact(),
			"public/logs/live_backing.log": workertest.GrepArtifact("hello-world"),
			"public/subsub/result.txt":     workertest.GrepArtifact("42"),
		},
	}, {
		Title:   "Artifact Directory Is File",
		Success: false,
		Payload: `{
			"command": ["sh", "-c", "echo 42 > notafolder"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public/myfolder",
					"type": "directory",
					"path": "notafolder"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			"public/logs/live.log":         workertest.ReferenceArtifact(),
			"public/logs/live_backing.log": workertest.GrepArtifact("notafolder"),
			"public/myfolder":              workertest.ErrorArtifact(),
		},
	}, {
		Title:   "Artifact File Is Directory",
		Success: false,
		Payload: `{
			"command": ["sh", "-c", "mkdir -p sub/subsub/ && echo 42 > sub/subsub/result.txt"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public/subsub",
					"type": "file",
					"path": "sub/subsub"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			// Expect some error message saying "sub/subsub"
			"public/logs/live.log":         workertest.ReferenceArtifact(),
			"public/logs/live_backing.log": workertest.GrepArtifact("sub/subsub"),
			"public/subsub":                workertest.ErrorArtifact(),
		},
	}, {
		Title:   "Artifact File Not Found",
		Success: false,
		Payload: `{
			"command": ["true"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public/result.txt",
					"type": "file",
					"path": "no-such-file-` + filename + `.txt"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			"public/logs/live.log":         workertest.ReferenceArtifact(),
			"public/logs/live_backing.log": workertest.GrepArtifact("no-such-file-" + filename + ".txt"),
			"public/result.txt":            workertest.ErrorArtifact(),
		},
	}, {
		Title:   "Artifact Directory Not Found",
		Success: false,
		Payload: `{
			"command": ["sh", "-c", "true"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public/myfolder",
					"type": "directory",
					"path": "no-such-folder/no-sub-folder"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			"public/logs/live.log":         workertest.ReferenceArtifact(),
			"public/logs/live_backing.log": workertest.GrepArtifact("no-such-folder/no-sub-folder"),
			"public/myfolder":              workertest.ErrorArtifact(),
		},
		// NOTE: If anyone can come up with an artifact path is illegal please add a test case
	}},
}
<commit_msg>More artifact tests covering directory artifacts better, and testing content-type<commit_after>// +build linux,native darwin,native

package nativetest

import (
	"strings"
	"testing"

	"github.com/taskcluster/slugid-go/slugid"
	"github.com/taskcluster/taskcluster-worker/worker/workertest"
)

func TestArtifacts(t *testing.T) {
	debug("### Testing artifact plugin with native engine")
	debug("generated filename: %s", filename)
	artifactCase.Test(t)
}

var filename = slugid.Nice()
var artifactCase = workertest.Case{
	Engine:       "native",
	Concurrency:  1,
	EngineConfig: engineConfig,
	PluginConfig: pluginConfig,
	Tasks: []workertest.Task{{
		Title:   "Artifact File",
		Success: true,
		Payload: `{
			"command": ["sh", "-c", "echo 'hello-world' && echo 42 > ` + filename + `.txt"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public/result.txt",
					"type": "file",
					"path": "` + filename + `.txt"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			"public/logs/live.log":         workertest.ReferenceArtifact(),
			"public/logs/live_backing.log": workertest.GrepArtifact("hello-world"),
			"public/result.txt":            workertest.GrepArtifact("42"),
		},
	}, {
		Title:   "Artifact Directory",
		Success: true,
		Payload: `{
			"command": ["sh", "-c", "` + strings.Join([]string{
			"echo 'hello-world'",
			"mkdir -p sub/subsub/subsubsub",
			"echo 42 > sub/subsub/result.txt",
			"echo -n '' > sub/subsub/subsubsub/result.html",
		}, " && ") + `"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public",
					"type": "directory",
					"path": "sub"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			"public/logs/live.log":                workertest.ReferenceArtifact(),
			"public/logs/live_backing.log":        workertest.GrepArtifact("hello-world"),
			"public/subsub/result.txt":            workertest.GrepArtifact("42"),
			"public/subsub/subsubsub/result.html": workertest.MatchArtifact("", "text/html; charset=utf-8"),
		},
	}, {
		Title:   "Artifact Directory Is File",
		Success: false,
		Payload: `{
			"command": ["sh", "-c", "echo 42 > notafolder"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public/myfolder",
					"type": "directory",
					"path": "notafolder"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			"public/logs/live.log":         workertest.ReferenceArtifact(),
			"public/logs/live_backing.log": workertest.GrepArtifact("notafolder"),
			"public/myfolder":              workertest.ErrorArtifact(),
		},
	}, {
		Title:   "Artifact File Is Directory",
		Success: false,
		Payload: `{
			"command": ["sh", "-c", "mkdir -p sub/subsub/ && echo 42 > sub/subsub/result.txt"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public/subsub",
					"type": "file",
					"path": "sub/subsub"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			// Expect some error message saying "sub/subsub"
			"public/logs/live.log":         workertest.ReferenceArtifact(),
			"public/logs/live_backing.log": workertest.GrepArtifact("sub/subsub"),
			"public/subsub":                workertest.ErrorArtifact(),
		},
	}, {
		Title:   "Artifact File Not Found",
		Success: false,
		Payload: `{
			"command": ["true"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public/result.txt",
					"type": "file",
					"path": "no-such-file-` + filename + `.txt"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			"public/logs/live.log":         workertest.ReferenceArtifact(),
			"public/logs/live_backing.log": workertest.GrepArtifact("no-such-file-" + filename + ".txt"),
			"public/result.txt":            workertest.ErrorArtifact(),
		},
	}, {
		Title:   "Artifact Directory Not Found",
		Success: false,
		Payload: `{
			"command": ["sh", "-c", "true"],
			"env": {},
			"maxRunTime": "10 minutes",
			"artifacts": [
				{
					"name": "public/myfolder",
					"type": "directory",
					"path": "no-such-folder/no-sub-folder"
				}
			]
		}`,
		Artifacts: workertest.ArtifactAssertions{
			"public/logs/live.log":         workertest.ReferenceArtifact(),
			"public/logs/live_backing.log": workertest.GrepArtifact("no-such-folder/no-sub-folder"),
			"public/myfolder":              workertest.ErrorArtifact(),
		},
		// NOTE: If anyone can come up with an artifact path is illegal please add a test case
	}},
}
<|endoftext|>


<commit_before>package local_get

import (
	"fmt"
	"strings"

	"github.com/creativesoftwarefdn/weaviate/database/schema"
	"github.com/creativesoftwarefdn/weaviate/graphqlapi/local/get/refclasses"
	"github.com/creativesoftwarefdn/weaviate/models"
	"github.com/creativesoftwarefdn/weaviate/network/crossrefs"
	"github.com/graphql-go/graphql"
)

// NetworkRef is a WIP, it will most likely change
type NetworkRef struct {
	AtClass string
	RawRef  map[string]interface{}
}

// LocalRef to be filled by the database connector to indicate that the
// particular reference field is a local ref and does not require further
// resolving, as opposed to a NetworkRef.
type LocalRef struct {
	AtClass string
	Fields  map[string]interface{}
}

func buildReferenceField(propertyType schema.PropertyDataType,
	property *models.SemanticSchemaClassProperty, kindName, className string,
	knownClasses *map[string]*graphql.Object, knownRefClasses refclasses.ByNetworkClass) *graphql.Field {
	refClasses := propertyType.Classes()
	propertyName := strings.Title(property.Name)
	dataTypeClasses := []*graphql.Object{}

	for _, refClassName := range refClasses {
		if desiredRefClass, err := crossrefs.ParseClass(string(refClassName)); err == nil {
			// is a network ref
			refClass, ok := knownRefClasses[desiredRefClass]
			if !ok {
				// we seem to have referenced a network class that doesn't exist
				// (anymore). This is unfortunate, but there are many good reasons for
				// this to happen. For example a peer could have left the network. We
				// therefore simply skip this refprop, so we don't destroy the entire
				// graphql api every time a peer leaves unexpectedly
				continue
			}

			dataTypeClasses = append(dataTypeClasses, refClass)
		} else {
			// is a local ref
			refClass, ok := (*knownClasses)[string(refClassName)]
			if !ok {
				panic(fmt.Sprintf("buildGetClass: unknown referenced class type for %s.%s.%s; %s",
					kindName, className, property.Name, refClassName))
			}

			dataTypeClasses = append(dataTypeClasses, refClass)
		}
	}

	if (len(dataTypeClasses)) == 0 {
		// this could be the case when we only have network-refs, but all network
		// refs were invalid (e.g. because the peers are gone). In this case we
		// must return (nil) early, otherwise graphql will error because it has a
		// union field with an empty list of unions.
		return nil
	}

	classUnion := graphql.NewUnion(graphql.UnionConfig{
		Name:        fmt.Sprintf("%s%s%s", className, propertyName, "Obj"),
		Types:       dataTypeClasses,
		ResolveType: makeResolveClassUnionType(knownClasses, knownRefClasses),
		Description: property.Description,
	})

	return &graphql.Field{
		Type:        graphql.NewList(classUnion),
		Description: property.Description,
		Resolve: func(p graphql.ResolveParams) (interface{}, error) {
			items := p.Source.(map[string]interface{})[p.Info.FieldName].([]interface{})
			results := make([]interface{}, len(items), len(items))
			for i, item := range items {
				switch v := item.(type) {
				case LocalRef:
					localRef := v.Fields
					localRef["__refClassType"] = "local"
					localRef["__refClassName"] = v.AtClass
					results[i] = localRef

				case NetworkRef:
					networkRef := map[string]interface{}{
						"__refClassType":     "network",
						"__refClassName":     "Country",
						"__refClassPeerName": "WeaviateB",
						"name":               "hard-coded, but should be network resolved",
					}
					results[i] = networkRef

				default:
					return nil, fmt.Errorf("unsupported type %t", v)
				}
			}
			return results, nil
		},
	}
}

func makeResolveClassUnionType(knownClasses *map[string]*graphql.Object,
	knownRefClasses refclasses.ByNetworkClass) graphql.ResolveTypeFn {
	return func(p graphql.ResolveTypeParams) *graphql.Object {
		valueMap := p.Value.(map[string]interface{})
		refType := valueMap["__refClassType"].(string)
		switch refType {
		case "local":
			className := valueMap["__refClassName"].(string)
			return (*knownClasses)[className]
		case "network":
			className := valueMap["__refClassName"].(string)
			peerName := valueMap["__refClassPeerName"].(string)
			return knownRefClasses[crossrefs.NetworkClass{ClassName: className, PeerName: peerName}]
		default:
			panic(fmt.Sprintf("unknown ref type %#v", refType))
		}
	}
}
<commit_msg>gh-660 return thunks on network refs<commit_after>package local_get

import (
	"fmt"
	"strings"

	"github.com/creativesoftwarefdn/weaviate/database/schema"
	"github.com/creativesoftwarefdn/weaviate/graphqlapi/local/get/refclasses"
	"github.com/creativesoftwarefdn/weaviate/models"
	"github.com/creativesoftwarefdn/weaviate/network/crossrefs"
	"github.com/graphql-go/graphql"
)

// NetworkRef is a WIP, it will most likely change
type NetworkRef struct {
	AtClass string
	RawRef  map[string]interface{}
}

// LocalRef to be filled by the database connector to indicate that the
// particular reference field is a local ref and does not require further
// resolving, as opposed to a NetworkRef.
type LocalRef struct {
	AtClass string
	Fields  map[string]interface{}
}

func buildReferenceField(propertyType schema.PropertyDataType,
	property *models.SemanticSchemaClassProperty, kindName, className string,
	knownClasses *map[string]*graphql.Object, knownRefClasses refclasses.ByNetworkClass) *graphql.Field {
	refClasses := propertyType.Classes()
	propertyName := strings.Title(property.Name)
	dataTypeClasses := []*graphql.Object{}

	for _, refClassName := range refClasses {
		if desiredRefClass, err := crossrefs.ParseClass(string(refClassName)); err == nil {
			// is a network ref
			refClass, ok := knownRefClasses[desiredRefClass]
			if !ok {
				// we seem to have referenced a network class that doesn't exist
				// (anymore). This is unfortunate, but there are many good reasons for
				// this to happen. For example a peer could have left the network. We
				// therefore simply skip this refprop, so we don't destroy the entire
				// graphql api every time a peer leaves unexpectedly
				continue
			}

			dataTypeClasses = append(dataTypeClasses, refClass)
		} else {
			// is a local ref
			refClass, ok := (*knownClasses)[string(refClassName)]
			if !ok {
				panic(fmt.Sprintf("buildGetClass: unknown referenced class type for %s.%s.%s; %s",
					kindName, className, property.Name, refClassName))
			}

			dataTypeClasses = append(dataTypeClasses, refClass)
		}
	}

	if (len(dataTypeClasses)) == 0 {
		// this could be the case when we only have network-refs, but all network
		// refs were invalid (e.g. because the peers are gone). In this case we
		// must return (nil) early, otherwise graphql will error because it has a
		// union field with an empty list of unions.
		return nil
	}

	classUnion := graphql.NewUnion(graphql.UnionConfig{
		Name:        fmt.Sprintf("%s%s%s", className, propertyName, "Obj"),
		Types:       dataTypeClasses,
		ResolveType: makeResolveClassUnionType(knownClasses, knownRefClasses),
		Description: property.Description,
	})

	return &graphql.Field{
		Type:        graphql.NewList(classUnion),
		Description: property.Description,
		Resolve: func(p graphql.ResolveParams) (interface{}, error) {
			items := p.Source.(map[string]interface{})[p.Info.FieldName].([]interface{})
			results := make([]interface{}, len(items), len(items))
			for i, item := range items {
				switch v := item.(type) {
				case LocalRef:
					localRef := v.Fields
					localRef["__refClassType"] = "local"
					localRef["__refClassName"] = v.AtClass
					results[i] = localRef

				case NetworkRef:
					networkRef := func() (interface{}, error) {
						return map[string]interface{}{
							"__refClassType":     "network",
							"__refClassName":     "Country",
							"__refClassPeerName": "WeaviateB",
							"name":               "hard-coded, but should be network resolved",
						}, nil
					}
					results[i] = networkRef

				default:
					return nil, fmt.Errorf("unsupported type %t", v)
				}
			}
			return results, nil
		},
	}
}

func makeResolveClassUnionType(knownClasses *map[string]*graphql.Object,
	knownRefClasses refclasses.ByNetworkClass) graphql.ResolveTypeFn {
	return func(p graphql.ResolveTypeParams) *graphql.Object {
		valueMap := p.Value.(map[string]interface{})
		refType := valueMap["__refClassType"].(string)
		switch refType {
		case "local":
			className := valueMap["__refClassName"].(string)
			return (*knownClasses)[className]
		case "network":
			className := valueMap["__refClassName"].(string)
			peerName := valueMap["__refClassPeerName"].(string)
			return knownRefClasses[crossrefs.NetworkClass{ClassName: className, PeerName: peerName}]
		default:
			panic(fmt.Sprintf("unknown ref type %#v", refType))
		}
	}
}
<|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2019 Red Hat, Inc.
 *
 */

package tests_test

import (
	"fmt"
	"strings"
	"time"

	v1 "k8s.io/api/core/v1"
	"k8s.io/api/policy/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"kubevirt.io/client-go/kubecli"
	"kubevirt.io/kubevirt/tests"
)

const (
	DefaultStabilizationTimeoutInSeconds = 300
	DefaultPollIntervalInSeconds         = 3
	labelKey                             = "control-plane-test"
	labelValue                           = "selected"
)

var _ = Describe("KubeVirt control plane resilience", func() {

	var nodeNames []string
	var selectedNodeName string

	controlPlaneDeploymentNames := []string{"virt-api", "virt-controller"}

	tests.FlagParse()

	getPodsInStatus := func(podList *v1.PodList, expectedPhase v1.PodPhase, expectedConditionStatus v1.ConditionStatus, podNames []string, nodeNames ...string) (pods []*v1.Pod) {
		pods = make([]*v1.Pod, 0)
		for _, pod := range podList.Items {
			if pod.Status.Phase != expectedPhase {
				continue
			}
			podReady := tests.PodReady(&pod)
			if podReady != expectedConditionStatus {
				continue
			}
			for _, podName := range podNames {
				if strings.HasPrefix(pod.Name, podName) {
					if len(nodeNames) > 0 {
						for _, nodeName := range nodeNames {
							if pod.Spec.NodeName == nodeName {
								deepCopy := pod.DeepCopy()
								pods = append(pods, deepCopy)
							}
						}
					} else {
						deepCopy := pod.DeepCopy()
						pods = append(pods, deepCopy)
					}
				}
			}
		}
		return
	}

	countPodsInStatus := func(podList *v1.PodList, expectedPhase v1.PodPhase, expectedConditionStatus v1.ConditionStatus, podNames []string, nodeNames ...string) int {
		pods := getPodsInStatus(podList, expectedPhase, expectedConditionStatus, podNames, nodeNames...)
		return len(pods)
	}

	getPodList := func() (podList *v1.PodList, err error) {
		virtCli, err := kubecli.GetKubevirtClient()
		if err != nil {
			return
		}
		podList, err = virtCli.CoreV1().Pods(tests.KubeVirtInstallNamespace).List(metav1.ListOptions{})
		return
	}

	countReadyPodsOnNodes := func(podNames []string, nodeNames ...string) int {
		podList, err := getPodList()
		if err != nil {
			return -1
		}
		return countPodsInStatus(podList, v1.PodRunning, v1.ConditionTrue, podNames, nodeNames...)
	}

	countPendingPods := func(podNames []string) int {
		podList, err := getPodList()
		if err != nil {
			return -1
		}
		return countPodsInStatus(podList, v1.PodPending, v1.ConditionFalse, podNames)
	}

	getSelectedNode := func() (selectedNode *v1.Node) {
		virtCli, err := kubecli.GetKubevirtClient()
		tests.PanicOnError(err)
		selectedNode, err = kubecli.KubevirtClient.CoreV1(virtCli).Nodes().Get(selectedNodeName, metav1.GetOptions{})
		tests.PanicOnError(err)
		return
	}

	waitForDeploymentsToStabilize := func() (bool, error) {
		virtCli, err := kubecli.GetKubevirtClient()
		tests.PanicOnError(err)
		deploymentsClient := kubecli.KubevirtClient.AppsV1(virtCli).Deployments(tests.KubeVirtInstallNamespace)
		for numberOfSuccessfulChecks := 0; numberOfSuccessfulChecks < 3; numberOfSuccessfulChecks++ {
			for _, deploymentName := range controlPlaneDeploymentNames {
				deployment, err := deploymentsClient.Get(deploymentName, metav1.GetOptions{})
				if err != nil {
					return false, err
				}

				if !(deployment.Status.UpdatedReplicas == *(deployment.Spec.Replicas) &&
					deployment.Status.Replicas == *(deployment.Spec.Replicas) &&
					deployment.Status.AvailableReplicas == *(deployment.Spec.Replicas)) {
					return false, err
				}
			}
			time.Sleep(time.Second)
		}
		return true, nil
	}

	BeforeEach(func() {
		tests.SkipIfNoCmd("kubectl")
		tests.BeforeTestCleanup()

		virtCli, err := kubecli.GetKubevirtClient()
		tests.PanicOnError(err)

		nodes := tests.GetAllSchedulableNodes(virtCli).Items
		nodeNames = make([]string, len(nodes))
		for index, node := range nodes {
			nodeNames[index] = node.Name
		}

		Eventually(func() int { return countReadyPodsOnNodes(controlPlaneDeploymentNames, nodeNames...) },
			DefaultStabilizationTimeoutInSeconds, DefaultPollIntervalInSeconds,
		).Should(Equal(4))
		Eventually(func() int { return countPendingPods(controlPlaneDeploymentNames) },
			DefaultStabilizationTimeoutInSeconds, DefaultPollIntervalInSeconds,
		).Should(Equal(0))

		// select node for test
		selectedNodeName = nodes[0].Name
		selectedNode, err := kubecli.KubevirtClient.CoreV1(virtCli).Nodes().Get(selectedNodeName, metav1.GetOptions{})
		tests.PanicOnError(err)

		// Add label to node that was selected for test
		for {
			if selectedNode.Labels == nil {
				selectedNode.Labels = make(map[string]string)
			}
			selectedNode.Labels[labelKey] = labelValue
			_, err = kubecli.KubevirtClient.CoreV1(virtCli).Nodes().Update(selectedNode)
			if err == nil {
				break
			}
		}

		// Add nodeSelector to deployments so that they get scheduled to selectedNode
		deploymentsClient := kubecli.KubevirtClient.AppsV1(virtCli).Deployments(tests.KubeVirtInstallNamespace)
		tests.PanicOnError(err)
		for _, deploymentName := range controlPlaneDeploymentNames {
			for {
				deployment, err := deploymentsClient.Get(deploymentName, metav1.GetOptions{})
				tests.PanicOnError(err)

				labelMap := make(map[string]string)
				labelMap[labelKey] = labelValue
				if deployment.Spec.Template.Spec.NodeSelector == nil {
					deployment.Spec.Template.Spec.NodeSelector = make(map[string]string)
				}
				deployment.Spec.Template.Spec.NodeSelector[labelKey] = labelValue
				_, err = deploymentsClient.Update(deployment)
				if err == nil {
					break
				}
			}
		}

		Eventually(waitForDeploymentsToStabilize,
			DefaultStabilizationTimeoutInSeconds, DefaultPollIntervalInSeconds,
		).Should(BeTrue())
	})

	AfterEach(func() {
		virtCli, err := kubecli.GetKubevirtClient()
		tests.PanicOnError(err)

		// Remove nodeSelector from deployments
		deploymentsClient := kubecli.KubevirtClient.AppsV1(virtCli).Deployments(tests.KubeVirtInstallNamespace)
		for _, deploymentName := range controlPlaneDeploymentNames {
			for {
				deployment, err := deploymentsClient.Get(deploymentName, metav1.GetOptions{})
				if err != nil {
					continue
				}
				delete(deployment.Spec.Template.Spec.NodeSelector, labelKey)
				_, err = deploymentsClient.Update(deployment)
				if err == nil {
					break
				}
			}
		}

		// Clean up selectedNode: Remove label and make schedulable again
		for {
			selectedNode := getSelectedNode()
			selectedNode.Spec.Unschedulable = false
			delete(selectedNode.Labels, labelKey)
			_, err = kubecli.KubevirtClient.CoreV1(virtCli).Nodes().Update(selectedNode)
			if err == nil {
				break
			}
		}

		Eventually(waitForDeploymentsToStabilize,
			DefaultStabilizationTimeoutInSeconds, DefaultPollIntervalInSeconds,
		).Should(BeTrue())
	})

	When("evicting pods of control plane, last eviction should fail", func() {

		test := func(podName string) {
			virtCli, err := kubecli.GetKubevirtClient()
			tests.PanicOnError(err)

			By(fmt.Sprintf("set node %s unschedulable\n", selectedNodeName))
			selectedNode := getSelectedNode()
			selectedNode.Spec.Unschedulable = true
			_, err = kubecli.KubevirtClient.CoreV1(virtCli).Nodes().Update(selectedNode)

			By(fmt.Sprintf("Try to evict all pods %s from node %s\n", podName, selectedNodeName))
			podList, err := getPodList()
			Expect(err).ToNot(HaveOccurred())
			runningPods := getPodsInStatus(podList, v1.PodRunning, v1.ConditionTrue, []string{podName})
			for index, pod := range runningPods {
				err = virtCli.CoreV1().Pods(tests.KubeVirtInstallNamespace).Evict(&v1beta1.Eviction{ObjectMeta: metav1.ObjectMeta{Name: pod.Name}})
				if index < len(runningPods)-1 {
					Expect(err).ToNot(HaveOccurred())
				}
			}
			Expect(err).To(HaveOccurred(), "no error occurred on evict of last pod")
		}

		It("for virt-controller pods", func() { test("virt-controller") })
		It("for virt-api pods", func() { test("virt-api") })

	})

})
<commit_msg>Add check for nodeSelector present on pods, put updates into eventually<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2019 Red Hat, Inc.
 *
 */

package tests_test

import (
	"fmt"
	"strings"
	"time"

	v1 "k8s.io/api/core/v1"
	"k8s.io/api/policy/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"kubevirt.io/client-go/kubecli"
	"kubevirt.io/kubevirt/tests"
)

const (
	WaitSecondsBeforeDeploymentCheck     = 2 * time.Second
	DefaultStabilizationTimeoutInSeconds = 300
	DefaultPollIntervalInSeconds         = 3
	labelKey                             = "control-plane-test"
	labelValue                           = "selected"
)

var _ = Describe("KubeVirt control plane resilience", func() {

	var nodeNames []string
	var selectedNodeName string

	controlPlaneDeploymentNames := []string{"virt-api", "virt-controller"}

	tests.FlagParse()

	getRunningReadyPods := func(podList *v1.PodList, podNames []string, nodeNames ...string) (pods []*v1.Pod) {
		pods = make([]*v1.Pod, 0)
		for _, pod := range podList.Items {
			if pod.Status.Phase != v1.PodRunning {
				continue
			}
			podReady := tests.PodReady(&pod)
			if podReady != v1.ConditionTrue {
				continue
			}
			for _, podName := range podNames {
				if strings.HasPrefix(pod.Name, podName) {
					if len(nodeNames) > 0 {
						for _, nodeName := range nodeNames {
							if pod.Spec.NodeName == nodeName {
								deepCopy := pod.DeepCopy()
								pods = append(pods, deepCopy)
							}
						}
					} else {
						deepCopy := pod.DeepCopy()
						pods = append(pods, deepCopy)
					}
				}
			}
		}
		return
	}

	getPodList := func() (podList *v1.PodList, err error) {
		virtCli, err := kubecli.GetKubevirtClient()
		if err != nil {
			return
		}
		podList, err = virtCli.CoreV1().Pods(tests.KubeVirtInstallNamespace).List(metav1.ListOptions{})
		return
	}

	getSelectedNode := func() (selectedNode *v1.Node) {
		virtCli, err := kubecli.GetKubevirtClient()
		tests.PanicOnError(err)
		selectedNode, err = kubecli.KubevirtClient.CoreV1(virtCli).Nodes().Get(selectedNodeName, metav1.GetOptions{})
		tests.PanicOnError(err)
		return
	}

	waitForDeploymentsToStabilize := func() (bool, error) {
		virtCli, err := kubecli.GetKubevirtClient()
		tests.PanicOnError(err)
		deploymentsClient := kubecli.KubevirtClient.AppsV1(virtCli).Deployments(tests.KubeVirtInstallNamespace)
		for _, deploymentName := range controlPlaneDeploymentNames {
			deployment, err := deploymentsClient.Get(deploymentName, metav1.GetOptions{})
			if err != nil {
				return false, err
			}

			if !(deployment.Status.UpdatedReplicas == *(deployment.Spec.Replicas) &&
				deployment.Status.Replicas == *(deployment.Spec.Replicas) &&
				deployment.Status.AvailableReplicas == *(deployment.Spec.Replicas)) {
				return false, err
			}
		}
		return true, nil
	}

	addLabelToSelectedNode := func() (bool, error) {
		virtCli, err := kubecli.GetKubevirtClient()
		tests.PanicOnError(err)
		selectedNode, err := kubecli.KubevirtClient.CoreV1(virtCli).Nodes().Get(selectedNodeName, metav1.GetOptions{})
		tests.PanicOnError(err)
		if selectedNode.Labels == nil {
			selectedNode.Labels = make(map[string]string)
		}
		selectedNode.Labels[labelKey] = labelValue
		_, err = kubecli.KubevirtClient.CoreV1(virtCli).Nodes().Update(selectedNode)
		if err != nil {
			return false, fmt.Errorf("failed to update node: %v", err)
		}
		return true, nil
	}

	// Add nodeSelector to deployments so that they get scheduled to selectedNode
	addNodeSelectorToDeployments := func() (bool, error) {
		virtCli, err := kubecli.GetKubevirtClient()
		tests.PanicOnError(err)
		deploymentsClient := kubecli.KubevirtClient.AppsV1(virtCli).Deployments(tests.KubeVirtInstallNamespace)
		tests.PanicOnError(err)
		for _, deploymentName := range controlPlaneDeploymentNames {
			deployment, err := deploymentsClient.Get(deploymentName, metav1.GetOptions{})
			tests.PanicOnError(err)

			labelMap := make(map[string]string)
			labelMap[labelKey] = labelValue
			if deployment.Spec.Template.Spec.NodeSelector == nil {
				deployment.Spec.Template.Spec.NodeSelector = make(map[string]string)
			}
			deployment.Spec.Template.Spec.NodeSelector[labelKey] = labelValue
			_, err = deploymentsClient.Update(deployment)
			if err != nil {
				return false, err
			}
		}
		return true, nil
	}

	checkControlPlanePodsHaveNodeSelector := func() (bool, error) {
		podList, err := getPodList()
		tests.PanicOnError(err)
		runningControlPlanePods := getRunningReadyPods(podList, controlPlaneDeploymentNames)
		for _, pod := range runningControlPlanePods {
			if actualLabelValue, ok := pod.Spec.NodeSelector[labelKey]; ok {
				if actualLabelValue != labelValue {
					return false, fmt.Errorf("pod %s has node selector %s with value %s, expected was %s", pod.Name, labelKey, actualLabelValue, labelValue)
				}
			} else {
				return false, fmt.Errorf("pod %s has no node selector %s", pod.Name, labelKey)
			}
		}
		return true, nil
	}

	BeforeEach(func() {
		tests.SkipIfNoCmd("kubectl")
		tests.BeforeTestCleanup()

		virtCli, err := kubecli.GetKubevirtClient()
		tests.PanicOnError(err)

		nodes := tests.GetAllSchedulableNodes(virtCli).Items
		nodeNames = make([]string, len(nodes))
		for index, node := range nodes {
			nodeNames[index] = node.Name
		}

		// select one node from result for test, first node will do
		selectedNodeName = nodes[0].Name

		Eventually(addLabelToSelectedNode,
			DefaultStabilizationTimeoutInSeconds, DefaultPollIntervalInSeconds,
		).Should(BeTrue())

		Eventually(addNodeSelectorToDeployments,
			DefaultStabilizationTimeoutInSeconds, DefaultPollIntervalInSeconds,
		).Should(BeTrue())

		time.Sleep(WaitSecondsBeforeDeploymentCheck)

		Eventually(checkControlPlanePodsHaveNodeSelector,
			DefaultStabilizationTimeoutInSeconds, DefaultPollIntervalInSeconds,
		).Should(BeTrue())

		Eventually(waitForDeploymentsToStabilize,
			DefaultStabilizationTimeoutInSeconds, DefaultPollIntervalInSeconds,
		).Should(BeTrue())
	})

	removeNodeSelectorFromDeployments := func() (bool, error) {
		virtCli, err := kubecli.GetKubevirtClient()
		tests.PanicOnError(err)
		deploymentsClient := kubecli.KubevirtClient.AppsV1(virtCli).Deployments(tests.KubeVirtInstallNamespace)
		for _, deploymentName := range controlPlaneDeploymentNames {
			deployment, err := deploymentsClient.Get(deploymentName, metav1.GetOptions{})
			if err != nil {
				return false, err
			}
			delete(deployment.Spec.Template.Spec.NodeSelector, labelKey)
			_, err = deploymentsClient.Update(deployment)
			if err != nil {
				return false, err
			}
		}
		return true, nil
	}

	// Clean up selectedNode: Remove label and make schedulable again
	cleanUpSelectedNode := func() (bool, error) {
		virtCli, err := kubecli.GetKubevirtClient()
		tests.PanicOnError(err)
		selectedNode := getSelectedNode()
		selectedNode.Spec.Unschedulable = false
		delete(selectedNode.Labels, labelKey)
		_, err = kubecli.KubevirtClient.CoreV1(virtCli).Nodes().Update(selectedNode)
		if err != nil {
			return false, err
		}
		return true, nil
	}

	AfterEach(func() {
		Eventually(removeNodeSelectorFromDeployments,
			DefaultStabilizationTimeoutInSeconds, DefaultPollIntervalInSeconds,
		).Should(BeTrue())

		Eventually(cleanUpSelectedNode,
			DefaultStabilizationTimeoutInSeconds, DefaultPollIntervalInSeconds,
		).Should(BeTrue())

		time.Sleep(WaitSecondsBeforeDeploymentCheck)

		Eventually(waitForDeploymentsToStabilize,
			DefaultStabilizationTimeoutInSeconds, DefaultPollIntervalInSeconds,
		).Should(BeTrue())
	})

	When("evicting pods of control plane, last eviction should fail", func() {

		test := func(podName string) {
			virtCli, err := kubecli.GetKubevirtClient()
			tests.PanicOnError(err)

			By(fmt.Sprintf("set node %s unschedulable\n", selectedNodeName))
			selectedNode := getSelectedNode()
			selectedNode.Spec.Unschedulable = true
			_, err = kubecli.KubevirtClient.CoreV1(virtCli).Nodes().Update(selectedNode)

			By(fmt.Sprintf("Try to evict all pods %s from node %s\n", podName, selectedNodeName))
			podList, err := getPodList()
			Expect(err).ToNot(HaveOccurred())
			runningPods := getRunningReadyPods(podList, []string{podName})
			for index, pod := range runningPods {
				err = virtCli.CoreV1().Pods(tests.KubeVirtInstallNamespace).Evict(&v1beta1.Eviction{ObjectMeta: metav1.ObjectMeta{Name: pod.Name}})
				if index < len(runningPods)-1 {
					Expect(err).ToNot(HaveOccurred())
				}
			}
			Expect(err).To(HaveOccurred(), "no error occurred on evict of last pod")
		}

		It("for virt-controller pods", func() { test("virt-controller") })
		It("for virt-api pods", func() { test("virt-api") })

	})

})
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/andelf/go-curl"
	"github.com/nu7hatch/gouuid"
)

type Config struct {
	Location         string
	ChecksUrl        string
	MeasurementsUrl  string
	MeasurementsUser string
	MeasurementsPass string
	MeasurerCount    int
	RecorderCount    int
}

type Check struct {
	Id  string `json:"id"`
	Url string `json:"url"`
}

type Measurement struct {
	Check             Check   `json:"check"`
	Id                string  `json:"id"`
	Location          string  `json:"location"`
	T                 int     `json:"t"`
	ExitStatus        int     `json:"exit_status"`
	ConnectTime       float64 `json:"connect_time,omitempty"`
	StartTransferTime float64 `json:"starttransfer_time,omitempty"`
	LocalIp           string  `json:"local_ip,omitempty"`
	PrimaryIp         string  `json:"primary_ip,omitempty"`
	TotalTime         float64 `json:"total_time,omitempty"`
	HttpStatus        int     `json:"http_status,omitempty"`
	NameLookupTime    float64 `json:"namelookup_time,omitempty"`
}

func (c *Check) Measure(config Config) Measurement {
	var m Measurement

	id, _ := uuid.NewV4()
	m.Id = id.String()
	m.Check = *c
	m.Location = config.Location

	easy := curl.EasyInit()
	defer easy.Cleanup()

	easy.Setopt(curl.OPT_URL, c.Url)

	// dummy func for curl output
	noOut := func(buf []byte, userdata interface{}) bool {
		return true
	}

	easy.Setopt(curl.OPT_WRITEFUNCTION, noOut)
	easy.Setopt(curl.OPT_CONNECTTIMEOUT, 10)
	easy.Setopt(curl.OPT_TIMEOUT, 10)

	now := time.Now()
	m.T = int(now.Unix())

	if err := easy.Perform(); err != nil {
		if e, ok := err.(curl.CurlError); ok {
			m.ExitStatus = (int(e))
			return m
		}
		os.Exit(1)
	}

	m.ExitStatus = 0
	http_status, _ := easy.Getinfo(curl.INFO_RESPONSE_CODE)
	m.HttpStatus = http_status.(int)

	connect_time, _ := easy.Getinfo(curl.INFO_CONNECT_TIME)
	m.ConnectTime = connect_time.(float64)

	namelookup_time, _ := easy.Getinfo(curl.INFO_NAMELOOKUP_TIME)
	m.NameLookupTime = namelookup_time.(float64)

	starttransfer_time, _ := easy.Getinfo(curl.INFO_STARTTRANSFER_TIME)
	m.StartTransferTime = starttransfer_time.(float64)

	total_time, _ := easy.Getinfo(curl.INFO_TOTAL_TIME)
	m.TotalTime = total_time.(float64)

	local_ip, _ := easy.Getinfo(curl.INFO_LOCAL_IP)
	m.LocalIp = local_ip.(string)

	primary_ip, _ := easy.Getinfo(curl.INFO_PRIMARY_IP)
	m.PrimaryIp = primary_ip.(string)

	return m
}

func MeasureLoop(config Config, checks chan Check, measurements chan Measurement) {
	for {
		c := <-checks
		m := c.Measure(config)

		measurements <- m
	}
}

func Record(config Config, payload []Measurement) {
	s, err := json.Marshal(&payload)
	if err != nil {
		panic(err)
	}

	body := bytes.NewBuffer(s)
	req, err := http.NewRequest("POST", config.MeasurementsUrl, body)
	if err != nil {
		panic(err)
	}

	req.Header.Add("Content-Type", "application/json")

	if config.MeasurementsUser != "" {
		req.SetBasicAuth(config.MeasurementsUser, config.MeasurementsPass)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}

	fmt.Printf("fn=Record http_code=%d\n", resp.StatusCode)
	resp.Body.Close()
}

func RecordLoop(config Config, measurements chan Measurement) {
	tickChan := time.NewTicker(time.Millisecond * 1000).C
	payload := make([]Measurement, 0, 100)

	for {
		select {
		case m := <-measurements:
			payload = append(payload, m)
		case <-tickChan:
			l := len(payload)
			fmt.Printf("fn=RecordLoop payload_size=%d\n", l)

			if l > 0 {
				Record(config, payload)
				payload = make([]Measurement, 0, 100)
			}
		}
	}
}

func GetChecks(config Config) []Check {
	url := config.ChecksUrl

	res, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}

	var checks []Check
	err = json.Unmarshal(body, &checks)
	if err != nil {
		panic(err)
	}

	return checks
}

func ScheduleLoop(check Check, checks chan Check) {
	for {
		checks <- check
		time.Sleep(1000 * time.Millisecond)
	}
}

func main() {
	config := Config{}
	flag.StringVar(&config.Location, "location", "undefined", "location of this sensor")
	flag.StringVar(&config.ChecksUrl, "checks_url", "https://s3.amazonaws.com/canary-public-data/checks.json", "URL for check data")
	flag.StringVar(&config.MeasurementsUrl, "measurements_url", "http://localhost:5000/measurements", "URL to POST measurements to")
	flag.IntVar(&config.MeasurerCount, "measurer_count", 1, "number of measurers to run")
	flag.IntVar(&config.RecorderCount, "recorder_count", 1, "number of recorders to run")
	flag.Parse()

	u, err := url.Parse(config.MeasurementsUrl)
	if err != nil {
		panic(err)
	}

	if u.User != nil {
		config.MeasurementsUser = u.User.Username()
		config.MeasurementsPass, _ = u.User.Password()
	}

	check_list := GetChecks(config)

	checks := make(chan Check)
	measurements := make(chan Measurement)

	for i := 0; i < config.MeasurerCount; i++ {
		go MeasureLoop(config, checks, measurements)
	}

	for i := 0; i < config.RecorderCount; i++ {
		go RecordLoop(config, measurements)
	}

	for _, c := range check_list {
		go ScheduleLoop(c, checks)
	}

	select {}
}
<commit_msg>casing<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/andelf/go-curl"
	"github.com/nu7hatch/gouuid"
)

type Config struct {
	Location         string
	ChecksUrl        string
	MeasurementsUrl  string
	MeasurementsUser string
	MeasurementsPass string
	MeasurerCount    int
	RecorderCount    int
}

type Check struct {
	Id  string `json:"id"`
	Url string `json:"url"`
}

type Measurement struct {
	Check             Check   `json:"check"`
	Id                string  `json:"id"`
	Location          string  `json:"location"`
	T                 int     `json:"t"`
	ExitStatus        int     `json:"exit_status"`
	ConnectTime       float64 `json:"connect_time,omitempty"`
	StartTransferTime float64 `json:"starttransfer_time,omitempty"`
	LocalIp           string  `json:"local_ip,omitempty"`
	PrimaryIp         string  `json:"primary_ip,omitempty"`
	TotalTime         float64 `json:"total_time,omitempty"`
	HttpStatus        int     `json:"http_status,omitempty"`
	NameLookupTime    float64 `json:"namelookup_time,omitempty"`
}

func (c *Check) Measure(config Config) Measurement {
	var m Measurement

	id, _ := uuid.NewV4()
	m.Id = id.String()
	m.Check = *c
	m.Location = config.Location

	easy := curl.EasyInit()
	defer easy.Cleanup()

	easy.Setopt(curl.OPT_URL, c.Url)

	// dummy func for curl output
	noOut := func(buf []byte, userdata interface{}) bool {
		return true
	}

	easy.Setopt(curl.OPT_WRITEFUNCTION, noOut)
	easy.Setopt(curl.OPT_CONNECTTIMEOUT, 10)
	easy.Setopt(curl.OPT_TIMEOUT, 10)

	now := time.Now()
	m.T = int(now.Unix())

	if err := easy.Perform(); err != nil {
		if e, ok := err.(curl.CurlError); ok {
			m.ExitStatus = (int(e))
			return m
		}
		os.Exit(1)
	}

	m.ExitStatus = 0
	http_status, _ := easy.Getinfo(curl.INFO_RESPONSE_CODE)
	m.HttpStatus = http_status.(int)

	connect_time, _ := easy.Getinfo(curl.INFO_CONNECT_TIME)
	m.ConnectTime = connect_time.(float64)

	namelookup_time, _ := easy.Getinfo(curl.INFO_NAMELOOKUP_TIME)
	m.NameLookupTime = namelookup_time.(float64)

	starttransfer_time, _ := easy.Getinfo(curl.INFO_STARTTRANSFER_TIME)
	m.StartTransferTime = starttransfer_time.(float64)

	total_time, _ := easy.Getinfo(curl.INFO_TOTAL_TIME)
	m.TotalTime = total_time.(float64)

	local_ip, _ := easy.Getinfo(curl.INFO_LOCAL_IP)
	m.LocalIp = local_ip.(string)

	primary_ip, _ := easy.Getinfo(curl.INFO_PRIMARY_IP)
	m.PrimaryIp = primary_ip.(string)

	return m
}

func measurer(config Config, checks chan Check, measurements chan Measurement) {
	for {
		c := <-checks
		m := c.Measure(config)

		measurements <- m
	}
}

func record(config Config, payload []Measurement) {
	s, err := json.Marshal(&payload)
	if err != nil {
		panic(err)
	}

	body := bytes.NewBuffer(s)
	req, err := http.NewRequest("POST", config.MeasurementsUrl, body)
	if err != nil {
		panic(err)
	}

	req.Header.Add("Content-Type", "application/json")

	if config.MeasurementsUser != "" {
		req.SetBasicAuth(config.MeasurementsUser, config.MeasurementsPass)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}

	fmt.Printf("fn=Record http_code=%d\n", resp.StatusCode)
	resp.Body.Close()
}

func recorder(config Config, measurements chan Measurement) {
	tickChan := time.NewTicker(time.Millisecond * 1000).C
	payload := make([]Measurement, 0, 100)

	for {
		select {
		case m := <-measurements:
			payload = append(payload, m)
		case <-tickChan:
			l := len(payload)
			fmt.Printf("fn=RecordLoop payload_size=%d\n", l)

			if l > 0 {
				record(config, payload)
				payload = make([]Measurement, 0, 100)
			}
		}
	}
}

func getChecks(config Config) []Check {
	url := config.ChecksUrl

	res, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}

	var checks []Check
	err = json.Unmarshal(body, &checks)
	if err != nil {
		panic(err)
	}

	return checks
}

func scheduler(check Check, checks chan Check) {
	for {
		checks <- check
		time.Sleep(1000 * time.Millisecond)
	}
}

func main() {
	config := Config{}
	flag.StringVar(&config.Location, "location", "undefined", "location of this sensor")
	flag.StringVar(&config.ChecksUrl, "checks_url", "https://s3.amazonaws.com/canary-public-data/checks.json", "URL for check data")
	flag.StringVar(&config.MeasurementsUrl, "measurements_url", "http://localhost:5000/measurements", "URL to POST measurements to")
	flag.IntVar(&config.MeasurerCount, "measurer_count", 1, "number of measurers to run")
	flag.IntVar(&config.RecorderCount, "recorder_count", 1, "number of recorders to run")
	flag.Parse()

	u, err := url.Parse(config.MeasurementsUrl)
	if err != nil {
		panic(err)
	}

	if u.User != nil {
		config.MeasurementsUser = u.User.Username()
		config.MeasurementsPass, _ = u.User.Password()
	}

	check_list := getChecks(config)

	checks := make(chan Check)
	measurements := make(chan Measurement)

	for i := 0; i < config.MeasurerCount; i++ {
		go measurer(config, checks, measurements)
	}

	for i := 0; i < config.RecorderCount; i++ {
		go recorder(config, measurements)
	}

	for _, c := range check_list {
		go scheduler(c, checks)
	}

	select {}
}
<|endoftext|>


<commit_before>package thankyouplugin

import (
	"encoding/json"
	"log"
	"strings"
	"sync"

	"github.com/iopred/bruxism"
)

type thankyouPlugin struct {
	sync.Mutex
	bruxism.SimplePlugin
	Thanks int
}

// Load will load plugin state from a byte array.
func (p *thankyouPlugin) Load(bot *bruxism.Bot, service bruxism.Service, data []byte) error {
	if data != nil {
		if err := json.Unmarshal(data, p); err != nil {
			log.Println("Error loading data", err)
		}
	}

	return nil
}

// Save will save plugin state to a byte array.
func (p *thankyouPlugin) Save() ([]byte, error) {
	return json.Marshal(p)
}

// Help returns a list of help strings that are printed when the user requests them.
func (p *thankyouPlugin) Help(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, detailed bool) []string {
	if detailed {
		return nil
	}

	return bruxism.CommandHelp(service, "thankyou", "", "Thank you to all my patrons.")
}

// Message handler.
func (p *thankyouPlugin) Message(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) {
	if service.IsMe(message) || !bruxism.MatchesCommand(service, "thankyou", message) {
		return
	}

	p.Lock()
	p.Thanks++
	p.Unlock()

	out := "Septapus currently has no patrons.\nBecome a patron at: <https://www.patreon.com/iopred>"

	if service.SupportsMultiline() {
		service.SendMessage(message.Channel(), out)
	} else {
		service.SendMessage(message.Channel(), strings.Join(strings.Split(out, "\n"), " "))
	}
}

// Name returns the name of the plugin.
func (p *thankyouPlugin) Name() string {
	return "Thankyou"
}

// New will create a new thankyou plugin.
func New() bruxism.Plugin {
	return &thankyouPlugin{}
}
<commit_msg>Remove thankyou plugin. This is septapus specific.<commit_after><|endoftext|>