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

package main

import (
	"crypto/sha1"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"text/template"
	"time"

	"v.io/v23/context"
	"v.io/v23/conventions"
	"v.io/v23/rpc"
	"v.io/v23/security"
	"v.io/v23/security/access"
	"v.io/v23/verror"
	"v.io/v23/vom"
)

const pkgPath = "v.io/x/ref/services/allocator/allocatord"

var (
	errLimitExceeded       = verror.Register(pkgPath+".errLimitExceeded", verror.NoRetry, "{1:}{2:} limit ({3}) exceeded")
	errGlobalLimitExceeded = verror.Register(pkgPath+".errGlobalLimitExceeded", verror.NoRetry, "{1:}{2:} global limit ({3}) exceeded")
)

type allocatorImpl struct {
	baseBlessings     security.Blessings
	baseBlessingNames []string
}

// Create creates a new instance of the service.
// It returns the object name of the new instance.
func (i *allocatorImpl) Create(ctx *context.T, call rpc.ServerCall) (string, error) {
	b, _ := security.RemoteBlessingNames(ctx, call.Security())
	ctx.Infof("Create() called by %v", b)

	email := emailFromBlessingNames(b)
	if email == "" {
		return "", verror.New(verror.ErrNoAccess, ctx, "unable to determine caller's email address")
	}
	return create(ctx, email, i.baseBlessings, i.baseBlessingNames)
}

// Destroy destroys the instance with the given name.
func (i *allocatorImpl) Destroy(ctx *context.T, call rpc.ServerCall, mName string) error {
	b, _ := security.RemoteBlessingNames(ctx, call.Security())
	ctx.Infof("Destroy(%q) called by %v", mName, b)

	email := emailFromBlessingNames(b)
	if email == "" {
		return verror.New(verror.ErrNoAccess, ctx, "unable to determine caller's email address")
	}
	return destroy(ctx, email, mName)
}

// List returns a list of all the instances owned by the caller.
func (i *allocatorImpl) List(ctx *context.T, call rpc.ServerCall) ([]string, error) {
	b, _ := security.RemoteBlessingNames(ctx, call.Security())
	ctx.Infof("List() called by %v", b)

	email := emailFromBlessingNames(b)
	if email == "" {
		return nil, verror.New(verror.ErrNoAccess, ctx, "unable to determine caller's email address")
	}
	instances, err := list(ctx, email)
	if err != nil {
		return nil, err
	}
	mNames := make([]string, len(instances))
	for i, instance := range instances {
		mNames[i] = instance.mountName
	}
	return mNames, nil
}

func create(ctx *context.T, email string, baseBlessings security.Blessings, baseBlessingNames []string) (string, error) {
	// Enforce a limit on the number of instances. These tests are a little
	// bit racy. It's possible that multiple calls to create() will run
	// concurrently and that we'll end up with too many instances.
	if n, err := serverInstances(ctx, email); err != nil {
		return "", err
	} else if len(n) >= maxInstancesPerUserFlag {
		return "", verror.New(errLimitExceeded, ctx, maxInstancesPerUserFlag)
	}

	if n, err := serverInstances(ctx, ""); err != nil {
		return "", err
	} else if len(n) >= maxInstancesFlag {
		return "", verror.New(errGlobalLimitExceeded, ctx, maxInstancesFlag)
	}

	kName, err := newKubeName()
	if err != nil {
		return "", err
	}
	mName := mountNameFromKubeName(ctx, kName)

	cfg, cleanup, err := createDeploymentConfig(ctx, email, kName, mName, baseBlessingNames)
	defer cleanup()
	if err != nil {
		return "", err
	}

	vomBlessings, err := vom.Encode(baseBlessings)
	if err != nil {
		return "", err
	}

	if err := createPersistentDisk(ctx, kName); err != nil {
		return "", err
	}

	out, err := vkube(
		"start", "-f", cfg,
		"--base-blessings", base64.URLEncoding.EncodeToString(vomBlessings),
		"--wait",
		kName,
	)
	if err != nil {
		ctx.Errorf("vkube start failed: %s", string(out))
		deletePersistentDisk(ctx, kName)
		return "", verror.New(verror.ErrInternal, ctx, err)
	}
	return mName, nil
}

func destroy(ctx *context.T, email, mName string) error {
	kName := kubeNameFromMountName(mName)

	if err := isOwnerOfInstance(ctx, email, kName); err != nil {
		return err
	}

	cfg, cleanup, err := createDeploymentConfig(ctx, email, kName, mName, nil)
	defer cleanup()
	if err != nil {
		return err
	}

	out, err := vkube("stop", "-f", cfg)
	if err != nil {
		ctx.Errorf("vkube stop failed: %s", string(out))
		return verror.New(verror.ErrInternal, ctx, err)
	}
	if err := deletePersistentDisk(ctx, kName); err != nil {
		return err
	}
	return nil
}

func list(ctx *context.T, email string) ([]serverInstance, error) {
	instances, err := serverInstances(ctx, email)
	if err != nil {
		return nil, err
	}
	for i, instance := range instances {
		// TODO(rthellend): Remove when all instances have the new
		// creatorInfo annotations.
		if instances[i].mountName == "" {
			instances[i].mountName = mountNameFromKubeName(ctx, instance.name)
		}
	}
	return instances, err
}

func createDeploymentConfig(ctx *context.T, email, deploymentName, mountName string, baseBlessingNames []string) (string, func(), error) {
	cleanup := func() {}
	acl, err := accessList(ctx, email)
	if err != nil {
		return "", cleanup, err
	}
	blessingNames := make([]string, len(baseBlessingNames))
	for i, b := range baseBlessingNames {
		blessingNames[i] = b + security.ChainSeparator + deploymentName
	}
	creatorInfo, err := creatorInfo(ctx, email, mountName, blessingNames)
	if err != nil {
		return "", cleanup, err
	}

	t, err := template.ParseFiles(deploymentTemplateFlag)
	if err != nil {
		return "", cleanup, err
	}
	data := struct {
		AccessList  string
		CreatorInfo string
		MountName   string
		Name        string
		OwnerHash   string
	}{
		AccessList:  acl,
		CreatorInfo: creatorInfo,
		MountName:   mountName,
		Name:        deploymentName,
		OwnerHash:   emailHash(email),
	}

	f, err := ioutil.TempFile("", "allocator-deployment-")
	if err != nil {
		return "", cleanup, err
	}
	defer f.Close()
	cleanup = func() { os.Remove(f.Name()) }

	if err := t.Execute(f, data); err != nil {
		return "", cleanup, err
	}
	return f.Name(), cleanup, nil
}

// accessList returns a double encoded JSON access list that can be used in a
// Deployment template that contains something like:
//   "--v23.permissions.literal={\"Admin\": {{.AccessList}} }"
// The access list include the creator.
func accessList(ctx *context.T, email string) (string, error) {
	var acl access.AccessList
	if globalAdminsFlag != "" {
		for _, admin := range strings.Split(globalAdminsFlag, ",") {
			acl.In = append(acl.In, security.BlessingPattern(admin))
		}
	}
	for _, blessing := range conventions.ParseBlessingNames(blessingNamesFromEmail(email)...) {
		acl.In = append(acl.In, blessing.UserPattern())
	}
	j, err := json.Marshal(acl)
	if err != nil {
		ctx.Errorf("json.Marshal(%#v) failed: %v", acl, err)
		return "", err
	}
	// JSON encode again, because the access list is in a JSON template.
	str := string(j)
	j, err = json.Marshal(str)
	if err != nil {
		ctx.Errorf("json.Marshal(%#v) failed: %v", str, err)
		return "", err
	}
	// Remove the quotes.
	return string(j[1 : len(j)-1]), nil
}

type creatorInfoData struct {
	Email         string   `json:"email"`
	BlessingNames []string `json:"blessingNames"`
	MountName     string   `json:"mountName"`
}

// creatorInfo returns a double encoded JSON access list that can be used as
// annotation in a Deployment template, e.g.
//   "annotations": {
//     "v.io/allocator-creator-info": {{.CreatorInfo}}
//   }
func creatorInfo(ctx *context.T, email, mountName string, blessingNames []string) (string, error) {
	j, err := json.Marshal(creatorInfoData{email, blessingNames, mountName})
	if err != nil {
		ctx.Errorf("json.Marshal() failed: %v", err)
		return "", err
	}
	// JSON encode again, because the annotation is in a JSON template.
	str := string(j)
	j, err = json.Marshal(str)
	if err != nil {
		ctx.Errorf("json.Marshal(%#v) failed: %v", str, err)
		return "", err
	}
	return string(j), nil
}

func decodeCreatorInfo(s string) (data creatorInfoData, err error) {
	err = json.Unmarshal([]byte(s), &data)
	return
}

func emailHash(email string) string {
	h := sha1.Sum([]byte(email))
	return hex.EncodeToString(h[:])
}

type serverInstance struct {
	name          string
	mountName     string
	blessingNames []string
	creationTime  time.Time
}

func serverInstances(ctx *context.T, email string) ([]serverInstance, error) {
	args := []string{"kubectl", "get", "deployments", "-o", "json"}
	if email != "" {
		args = append(args, "-l", "ownerHash="+emailHash(email))
	}

	var out []byte
	var err error
	for retry := 0; retry < 10; retry++ {
		if out, err = vkube(args...); err == nil {
			break
		}
		time.Sleep(time.Second)
	}
	if err != nil {
		return nil, err
	}

	var list struct {
		Items []struct {
			Metadata struct {
				Name         string            `json:"name"`
				CreationTime time.Time         `json:"creationTimestamp"`
				Annotations  map[string]string `json:"annotations"`
			} `json:"metadata"`
		} `json:"items"`
	}
	if err := json.Unmarshal(out, &list); err != nil {
		return nil, err
	}
	instances := []serverInstance{}
	for _, l := range list.Items {
		if !strings.HasPrefix(l.Metadata.Name, serverNameFlag+"-") {
			continue
		}
		cInfo, err := decodeCreatorInfo(l.Metadata.Annotations["v.io/allocator-creator-info"])
		if err != nil {
			ctx.Errorf("decodeCreatorInfo failed: %v", err)
			continue
		}
		instances = append(instances, serverInstance{
			name:          l.Metadata.Name,
			mountName:     cInfo.MountName,
			blessingNames: cInfo.BlessingNames,
			creationTime:  l.Metadata.CreationTime,
		})
	}
	return instances, nil
}

func isOwnerOfInstance(ctx *context.T, email, kName string) error {
	instances, err := serverInstances(ctx, email)
	if err != nil {
		return err
	}
	for _, i := range instances {
		if i.name == kName {
			return nil
		}
	}
	return verror.New(verror.ErrNoExistOrNoAccess, nil)
}

func createPersistentDisk(ctx *context.T, name string) error {
	if out, err := gcloud("compute", "disks", "create", name, "--size", diskSizeFlag); err != nil {
		ctx.Errorf("disk creation failed: %v: %s", err, string(out))
		return err
	}
	return nil
}

func deletePersistentDisk(ctx *context.T, name string) error {
	var (
		start = time.Now()
		out   []byte
		err   error
	)
	for time.Since(start) < 5*time.Minute {
		if out, err = gcloud("compute", "disks", "delete", name); err == nil {
			return nil
		}
		time.Sleep(time.Second)
	}
	ctx.Errorf("disk deletion failed: %v: %s", err, string(out))
	return err
}

func gcloud(args ...string) ([]byte, error) {
	data, err := ioutil.ReadFile(vkubeCfgFlag)
	if err != nil {
		return nil, err
	}
	var config struct {
		Project string `json:"project"`
		Zone    string `json:"zone"`
	}
	if err := json.Unmarshal(data, &config); err != nil {
		return nil, err
	}

	args = append(args, "--project", config.Project, "--zone", config.Zone)
	return exec.Command(gcloudBinFlag, args...).CombinedOutput()
}

func vkube(args ...string) ([]byte, error) {
	args = append(
		[]string{
			"--config=" + vkubeCfgFlag,
			"--kubectl=" + kubectlBinFlag,
			"--no-headers",
		},
		args...,
	)
	return exec.Command(vkubeBinFlag, args...).CombinedOutput()
}
<commit_msg>allocatord: include vkube stout/stderr in error<commit_after>// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"crypto/sha1"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"text/template"
	"time"

	"v.io/v23/context"
	"v.io/v23/conventions"
	"v.io/v23/rpc"
	"v.io/v23/security"
	"v.io/v23/security/access"
	"v.io/v23/verror"
	"v.io/v23/vom"
)

const pkgPath = "v.io/x/ref/services/allocator/allocatord"

var (
	errLimitExceeded       = verror.Register(pkgPath+".errLimitExceeded", verror.NoRetry, "{1:}{2:} limit ({3}) exceeded")
	errGlobalLimitExceeded = verror.Register(pkgPath+".errGlobalLimitExceeded", verror.NoRetry, "{1:}{2:} global limit ({3}) exceeded")
)

type allocatorImpl struct {
	baseBlessings     security.Blessings
	baseBlessingNames []string
}

// Create creates a new instance of the service.
// It returns the object name of the new instance.
func (i *allocatorImpl) Create(ctx *context.T, call rpc.ServerCall) (string, error) {
	b, _ := security.RemoteBlessingNames(ctx, call.Security())
	ctx.Infof("Create() called by %v", b)

	email := emailFromBlessingNames(b)
	if email == "" {
		return "", verror.New(verror.ErrNoAccess, ctx, "unable to determine caller's email address")
	}
	return create(ctx, email, i.baseBlessings, i.baseBlessingNames)
}

// Destroy destroys the instance with the given name.
func (i *allocatorImpl) Destroy(ctx *context.T, call rpc.ServerCall, mName string) error {
	b, _ := security.RemoteBlessingNames(ctx, call.Security())
	ctx.Infof("Destroy(%q) called by %v", mName, b)

	email := emailFromBlessingNames(b)
	if email == "" {
		return verror.New(verror.ErrNoAccess, ctx, "unable to determine caller's email address")
	}
	return destroy(ctx, email, mName)
}

// List returns a list of all the instances owned by the caller.
func (i *allocatorImpl) List(ctx *context.T, call rpc.ServerCall) ([]string, error) {
	b, _ := security.RemoteBlessingNames(ctx, call.Security())
	ctx.Infof("List() called by %v", b)

	email := emailFromBlessingNames(b)
	if email == "" {
		return nil, verror.New(verror.ErrNoAccess, ctx, "unable to determine caller's email address")
	}
	instances, err := list(ctx, email)
	if err != nil {
		return nil, err
	}
	mNames := make([]string, len(instances))
	for i, instance := range instances {
		mNames[i] = instance.mountName
	}
	return mNames, nil
}

func create(ctx *context.T, email string, baseBlessings security.Blessings, baseBlessingNames []string) (string, error) {
	// Enforce a limit on the number of instances. These tests are a little
	// bit racy. It's possible that multiple calls to create() will run
	// concurrently and that we'll end up with too many instances.
	if n, err := serverInstances(ctx, email); err != nil {
		return "", err
	} else if len(n) >= maxInstancesPerUserFlag {
		return "", verror.New(errLimitExceeded, ctx, maxInstancesPerUserFlag)
	}

	if n, err := serverInstances(ctx, ""); err != nil {
		return "", err
	} else if len(n) >= maxInstancesFlag {
		return "", verror.New(errGlobalLimitExceeded, ctx, maxInstancesFlag)
	}

	kName, err := newKubeName()
	if err != nil {
		return "", err
	}
	mName := mountNameFromKubeName(ctx, kName)

	cfg, cleanup, err := createDeploymentConfig(ctx, email, kName, mName, baseBlessingNames)
	defer cleanup()
	if err != nil {
		return "", err
	}

	vomBlessings, err := vom.Encode(baseBlessings)
	if err != nil {
		return "", err
	}

	if err := createPersistentDisk(ctx, kName); err != nil {
		return "", err
	}

	out, err := vkube(
		"start", "-f", cfg,
		"--base-blessings", base64.URLEncoding.EncodeToString(vomBlessings),
		"--wait",
		kName,
	)
	if err != nil {
		ctx.Errorf("vkube start failed: %s", string(out))
		deletePersistentDisk(ctx, kName)
		return "", verror.New(verror.ErrInternal, ctx, err)
	}
	return mName, nil
}

func destroy(ctx *context.T, email, mName string) error {
	kName := kubeNameFromMountName(mName)

	if err := isOwnerOfInstance(ctx, email, kName); err != nil {
		return err
	}

	cfg, cleanup, err := createDeploymentConfig(ctx, email, kName, mName, nil)
	defer cleanup()
	if err != nil {
		return err
	}

	out, err := vkube("stop", "-f", cfg)
	if err != nil {
		ctx.Errorf("vkube stop failed: %s", string(out))
		return verror.New(verror.ErrInternal, ctx, err)
	}
	if err := deletePersistentDisk(ctx, kName); err != nil {
		return err
	}
	return nil
}

func list(ctx *context.T, email string) ([]serverInstance, error) {
	instances, err := serverInstances(ctx, email)
	if err != nil {
		return nil, err
	}
	for i, instance := range instances {
		// TODO(rthellend): Remove when all instances have the new
		// creatorInfo annotations.
		if instances[i].mountName == "" {
			instances[i].mountName = mountNameFromKubeName(ctx, instance.name)
		}
	}
	return instances, err
}

func createDeploymentConfig(ctx *context.T, email, deploymentName, mountName string, baseBlessingNames []string) (string, func(), error) {
	cleanup := func() {}
	acl, err := accessList(ctx, email)
	if err != nil {
		return "", cleanup, err
	}
	blessingNames := make([]string, len(baseBlessingNames))
	for i, b := range baseBlessingNames {
		blessingNames[i] = b + security.ChainSeparator + deploymentName
	}
	creatorInfo, err := creatorInfo(ctx, email, mountName, blessingNames)
	if err != nil {
		return "", cleanup, err
	}

	t, err := template.ParseFiles(deploymentTemplateFlag)
	if err != nil {
		return "", cleanup, err
	}
	data := struct {
		AccessList  string
		CreatorInfo string
		MountName   string
		Name        string
		OwnerHash   string
	}{
		AccessList:  acl,
		CreatorInfo: creatorInfo,
		MountName:   mountName,
		Name:        deploymentName,
		OwnerHash:   emailHash(email),
	}

	f, err := ioutil.TempFile("", "allocator-deployment-")
	if err != nil {
		return "", cleanup, err
	}
	defer f.Close()
	cleanup = func() { os.Remove(f.Name()) }

	if err := t.Execute(f, data); err != nil {
		return "", cleanup, err
	}
	return f.Name(), cleanup, nil
}

// accessList returns a double encoded JSON access list that can be used in a
// Deployment template that contains something like:
//   "--v23.permissions.literal={\"Admin\": {{.AccessList}} }"
// The access list include the creator.
func accessList(ctx *context.T, email string) (string, error) {
	var acl access.AccessList
	if globalAdminsFlag != "" {
		for _, admin := range strings.Split(globalAdminsFlag, ",") {
			acl.In = append(acl.In, security.BlessingPattern(admin))
		}
	}
	for _, blessing := range conventions.ParseBlessingNames(blessingNamesFromEmail(email)...) {
		acl.In = append(acl.In, blessing.UserPattern())
	}
	j, err := json.Marshal(acl)
	if err != nil {
		ctx.Errorf("json.Marshal(%#v) failed: %v", acl, err)
		return "", err
	}
	// JSON encode again, because the access list is in a JSON template.
	str := string(j)
	j, err = json.Marshal(str)
	if err != nil {
		ctx.Errorf("json.Marshal(%#v) failed: %v", str, err)
		return "", err
	}
	// Remove the quotes.
	return string(j[1 : len(j)-1]), nil
}

type creatorInfoData struct {
	Email         string   `json:"email"`
	BlessingNames []string `json:"blessingNames"`
	MountName     string   `json:"mountName"`
}

// creatorInfo returns a double encoded JSON access list that can be used as
// annotation in a Deployment template, e.g.
//   "annotations": {
//     "v.io/allocator-creator-info": {{.CreatorInfo}}
//   }
func creatorInfo(ctx *context.T, email, mountName string, blessingNames []string) (string, error) {
	j, err := json.Marshal(creatorInfoData{email, blessingNames, mountName})
	if err != nil {
		ctx.Errorf("json.Marshal() failed: %v", err)
		return "", err
	}
	// JSON encode again, because the annotation is in a JSON template.
	str := string(j)
	j, err = json.Marshal(str)
	if err != nil {
		ctx.Errorf("json.Marshal(%#v) failed: %v", str, err)
		return "", err
	}
	return string(j), nil
}

func decodeCreatorInfo(s string) (data creatorInfoData, err error) {
	err = json.Unmarshal([]byte(s), &data)
	return
}

func emailHash(email string) string {
	h := sha1.Sum([]byte(email))
	return hex.EncodeToString(h[:])
}

type serverInstance struct {
	name          string
	mountName     string
	blessingNames []string
	creationTime  time.Time
}

func serverInstances(ctx *context.T, email string) ([]serverInstance, error) {
	args := []string{"kubectl", "get", "deployments", "-o", "json"}
	if email != "" {
		args = append(args, "-l", "ownerHash="+emailHash(email))
	}

	var out []byte
	var err error
	for retry := 0; retry < 10; retry++ {
		if out, err = vkube(args...); err == nil {
			break
		}
		time.Sleep(time.Second)
	}
	if err != nil {
		return nil, err
	}

	var list struct {
		Items []struct {
			Metadata struct {
				Name         string            `json:"name"`
				CreationTime time.Time         `json:"creationTimestamp"`
				Annotations  map[string]string `json:"annotations"`
			} `json:"metadata"`
		} `json:"items"`
	}
	if err := json.Unmarshal(out, &list); err != nil {
		return nil, err
	}
	instances := []serverInstance{}
	for _, l := range list.Items {
		if !strings.HasPrefix(l.Metadata.Name, serverNameFlag+"-") {
			continue
		}
		cInfo, err := decodeCreatorInfo(l.Metadata.Annotations["v.io/allocator-creator-info"])
		if err != nil {
			ctx.Errorf("decodeCreatorInfo failed: %v", err)
			continue
		}
		instances = append(instances, serverInstance{
			name:          l.Metadata.Name,
			mountName:     cInfo.MountName,
			blessingNames: cInfo.BlessingNames,
			creationTime:  l.Metadata.CreationTime,
		})
	}
	return instances, nil
}

func isOwnerOfInstance(ctx *context.T, email, kName string) error {
	instances, err := serverInstances(ctx, email)
	if err != nil {
		return err
	}
	for _, i := range instances {
		if i.name == kName {
			return nil
		}
	}
	return verror.New(verror.ErrNoExistOrNoAccess, nil)
}

func createPersistentDisk(ctx *context.T, name string) error {
	if out, err := gcloud("compute", "disks", "create", name, "--size", diskSizeFlag); err != nil {
		ctx.Errorf("disk creation failed: %v: %s", err, string(out))
		return err
	}
	return nil
}

func deletePersistentDisk(ctx *context.T, name string) error {
	var (
		start = time.Now()
		out   []byte
		err   error
	)
	for time.Since(start) < 5*time.Minute {
		if out, err = gcloud("compute", "disks", "delete", name); err == nil {
			return nil
		}
		time.Sleep(time.Second)
	}
	ctx.Errorf("disk deletion failed: %v: %s", err, string(out))
	return err
}

func gcloud(args ...string) ([]byte, error) {
	data, err := ioutil.ReadFile(vkubeCfgFlag)
	if err != nil {
		return nil, err
	}
	var config struct {
		Project string `json:"project"`
		Zone    string `json:"zone"`
	}
	if err := json.Unmarshal(data, &config); err != nil {
		return nil, err
	}

	args = append(args, "--project", config.Project, "--zone", config.Zone)
	return exec.Command(gcloudBinFlag, args...).CombinedOutput()
}

func vkube(args ...string) (out []byte, err error) {
	args = append(
		[]string{
			"--config=" + vkubeCfgFlag,
			"--kubectl=" + kubectlBinFlag,
			"--no-headers",
		},
		args...,
	)
	out, err = exec.Command(vkubeBinFlag, args...).CombinedOutput()
	if err != nil {
		err = fmt.Errorf("vkube(%v) failed: %v\n%s\n", args, err, string(out))
	}
	return
}
<|endoftext|>


<commit_before>package orderer

import (
	"fmt"
	"os"
	"sync"
	"time"

	"google.golang.org/grpc"

	"github.com/ellcrys/util"
	"github.com/ncodes/cocoon/core/scheduler"
	logging "github.com/op/go-logging"
)

var discoveryLog = logging.MustGetLogger("orderer.discovery")

// Discovery defines a structure for fetching a list of addresses of orderers
// accessible in the cluster.
type Discovery struct {
	mu           *sync.Mutex
	orderersAddr []string
	ticker       *time.Ticker
	OnUpdateFunc func(addrs []string)
}

// NewDiscovery creates a new discovery object.
func NewDiscovery() *Discovery {
	return &Discovery{
		mu: &sync.Mutex{},
	}
}

// Discover fetches a list of orderer service addresses
// via consul service discovery API.
// For development purpose, If DEV_ORDERER_ADDR is set,
// it will fetch the orderer address from the env variable.
func (od *Discovery) discover() error {

	var err error

	if len(os.Getenv("DEV_ORDERER_ADDR")) > 0 {
		od.orderersAddr = []string{os.Getenv("DEV_ORDERER_ADDR")}
		return nil
	}

	ds := scheduler.NomadServiceDiscovery{
		ConsulAddr: util.Env("CONSUL_ADDR", "localhost:8500"),
		Protocol:   "http",
	}

	_orderers, err := ds.GetByID("orderers", nil)
	if err != nil {
		return err
	}

	var orderers []string
	for _, orderer := range _orderers {
		orderers = append(orderers, fmt.Sprintf("%s:%d", orderer.IP, int(orderer.Port)))
	}

	od.mu.Lock()
	od.orderersAddr = orderers
	od.mu.Unlock()
	return nil
}

// Discover starts a ticker that discovers and updates the list
// of orderer addresses. It will perform the discovery immediately
// and will return error if it fails, otherwise nil is returned and
// subsequent discovery will be performed periodically
func (od *Discovery) Discover() error {

	// run immediately
	if err := od.discover(); err != nil {
		return err
	}

	// run on interval
	od.ticker = time.NewTicker(15 * time.Second)
	for _ = range od.ticker.C {
		err := od.discover()
		if err != nil {
			discoveryLog.Error(err.Error())
			if od.OnUpdateFunc != nil {
				od.OnUpdateFunc(od.GetAddrs())
			}
		}
	}
	return nil
}

// GetAddrs returns the list of discovered addresses
func (od *Discovery) GetAddrs() []string {
	return od.orderersAddr
}

// GetGRPConn dials a random orderer address and returns a
// grpc connection. If no orderer address has been discovered, nil and are error are returned.
func (od *Discovery) GetGRPConn() (*grpc.ClientConn, error) {

	var selected string
	log.Info("Here")
	od.mu.Lock()
	defer od.mu.Unlock()
	log.Info("Here 2")
	if len(od.orderersAddr) == 0 {
		return nil, fmt.Errorf("no known orderer address")
	}
	log.Info("Here 3")
	if len(od.orderersAddr) == 1 {
		selected = od.orderersAddr[0]
	} else {
		selected = od.orderersAddr[util.RandNum(0, len(od.orderersAddr))]
	}
	log.Info("Here 4")
	client, err := grpc.Dial(selected, grpc.WithInsecure())
	if err != nil {
		return nil, err
	}

	od.mu.Unlock()
	log.Info("Here 5")

	return client, nil
}

// Stop stops the discovery ticker
func (od *Discovery) Stop() {
	od.ticker.Stop()
}
<commit_msg>debug<commit_after>package orderer

import (
	"fmt"
	"os"
	"sync"
	"time"

	"google.golang.org/grpc"

	"github.com/ellcrys/util"
	"github.com/ncodes/cocoon/core/scheduler"
	logging "github.com/op/go-logging"
)

var discoveryLog = logging.MustGetLogger("orderer.discovery")

// Discovery defines a structure for fetching a list of addresses of orderers
// accessible in the cluster.
type Discovery struct {
	mu           *sync.Mutex
	orderersAddr []string
	ticker       *time.Ticker
	OnUpdateFunc func(addrs []string)
}

// NewDiscovery creates a new discovery object.
func NewDiscovery() *Discovery {
	return &Discovery{
		mu: &sync.Mutex{},
	}
}

// Discover fetches a list of orderer service addresses
// via consul service discovery API.
// For development purpose, If DEV_ORDERER_ADDR is set,
// it will fetch the orderer address from the env variable.
func (od *Discovery) discover() error {

	var err error

	if len(os.Getenv("DEV_ORDERER_ADDR")) > 0 {
		od.orderersAddr = []string{os.Getenv("DEV_ORDERER_ADDR")}
		return nil
	}

	ds := scheduler.NomadServiceDiscovery{
		ConsulAddr: util.Env("CONSUL_ADDR", "localhost:8500"),
		Protocol:   "http",
	}

	_orderers, err := ds.GetByID("orderers", nil)
	if err != nil {
		return err
	}

	var orderers []string
	for _, orderer := range _orderers {
		orderers = append(orderers, fmt.Sprintf("%s:%d", orderer.IP, int(orderer.Port)))
	}

	od.mu.Lock()
	od.orderersAddr = orderers
	od.mu.Unlock()
	return nil
}

// Discover starts a ticker that discovers and updates the list
// of orderer addresses. It will perform the discovery immediately
// and will return error if it fails, otherwise nil is returned and
// subsequent discovery will be performed periodically
func (od *Discovery) Discover() error {

	// run immediately
	if err := od.discover(); err != nil {
		return err
	}

	// run on interval
	od.ticker = time.NewTicker(15 * time.Second)
	for _ = range od.ticker.C {
		err := od.discover()
		if err != nil {
			discoveryLog.Error(err.Error())
			if od.OnUpdateFunc != nil {
				od.OnUpdateFunc(od.GetAddrs())
			}
		}
	}
	return nil
}

// GetAddrs returns the list of discovered addresses
func (od *Discovery) GetAddrs() []string {
	return od.orderersAddr
}

// GetGRPConn dials a random orderer address and returns a
// grpc connection. If no orderer address has been discovered, nil and are error are returned.
func (od *Discovery) GetGRPConn() (*grpc.ClientConn, error) {

	var selected string
	log.Info("Here")

	if len(od.orderersAddr) == 0 {
		od.mu.Lock()
		return nil, fmt.Errorf("no known orderer address")
	}
	log.Info("Here 3")
	if len(od.orderersAddr) == 1 {
		selected = od.orderersAddr[0]
	} else {
		selected = od.orderersAddr[util.RandNum(0, len(od.orderersAddr))]
	}
	log.Info("Here 4")
	client, err := grpc.Dial(selected, grpc.WithInsecure())
	if err != nil {
		od.mu.Unlock()
		return nil, err
	}

	od.mu.Unlock()
	log.Info("Here 5")

	return client, nil
}

// Stop stops the discovery ticker
func (od *Discovery) Stop() {
	od.ticker.Stop()
}
<|endoftext|>


<commit_before>package switches

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

	"github.com/eikeon/marvin/nog"
)

type Switches struct {
	Switch map[string]bool
}

func (s *Switches) Run(in <-chan nog.Message, out chan<- nog.Message) {
	for {
		select {
		case m := <-in:
			if m.Why == "statechanged" {
				dec := json.NewDecoder(strings.NewReader(m.What))
				if err := dec.Decode(s); err != nil {
					log.Println("switches decode err:", err)
				}
			}
			const TURN = "turn "
			if strings.HasPrefix(m.What, TURN) {
				words := strings.Split(m.What[len(TURN):], " ")
				if len(words) == 2 {
					var value bool
					if words[0] == "on" {
						value = true
					} else {
						value = false
					}
					s.Switch[words[1]] = value
				}
				if what, err := json.Marshal(s); err == nil {
					out <- nog.NewMessage("Switches", string(what), "statechanged")
				} else {
					log.Println("StateChanged err:", err)
				}
			}
		}
	}
}
<commit_msg>Removed as it's now part of nog.<commit_after><|endoftext|>


<commit_before>// Package emdepth uses a simplified EM algorithm to assign copy-number given a set of depths.
// it is based off the implementation in cn.mops.
// Like cn.mops, it works best with more samples and it assumes that most samples will
// have copy-number 2.
// emdepth consists of a single function EMDepth that iteratively assigns depths to copy-numbers, adjusts
// the center of each copy-number bin. and re-assigns...
// This package does no normalization and therefore expects incoming data to be normalized.
package emdepth

import (
	"fmt"
	"math"
	"sort"
	"sync"
)

// mean of all except highest and lowest values.
func median32(a []float32) float64 {
	// make a copy so we dont modify original order
	b := make([]float64, len(a))
	for i, v := range a {
		b[i] = float64(v)
	}
	sort.Float64s(b)
	if len(b)%2 == 1 {
		return b[len(b)/2]
	}
	return (b[len(b)/2] + b[len(b)/2+1]) / 2
}

// mean of all except highest and lowest values.
func mean32(a []float32) float32 {
	var sum float32
	for _, v := range a {
		sum += v
	}
	return sum / float32(len(a))
}

func mean64(a []float64) float64 {
	if len(a) == 0 {
		return 0
	}
	var sum float64
	for _, v := range a {
		sum += v
	}
	return sum / float64(len(a))
}

func abs(a float64) float64 {
	if a < 0 {
		return -a
	}
	return a
}

func abs32(a float32) float32 {
	if a < 0 {
		return -a
	}
	return a
}

const maxCN = 8
const maxiter = 10
const eps = 0.001

// used to test for convergence return the sum of abs differences
// and the largest absolute difference.
func summaxdiff(a, b []float64) (float64, float64) {
	var m, sum float64
	for i, av := range a {
		d := abs(av - b[i])
		sum += d
		if d > m {
			m = d
		}
	}
	return sum, m
}

func getBins(nSamples int) [][]float64 {
	binned := make([][]float64, maxCN)
	for i := 0; i < maxCN; i++ {
		if i == 2 {
			binned[i] = make([]float64, 0, nSamples)
		} else {
			binned[i] = make([]float64, 0)
		}
	}
	return binned
}

var binPool *sync.Pool

func init() {
	binPool = &sync.Pool{New: func() interface{} {
		return getBins(32)
	}}
}

type Position struct {
	Start uint32
	End   uint32
}

func (p *Position) String() string {
	return fmt.Sprintf("%d:%d", p.Start, p.End)
}

// EMDepth returns a slice of integer copy-numbers (CNs) corresponding to the given normalized
// depths. It uses a simple EM to assign depths to copy-numbers bins with a preference for CN 2.
// And to adjust the mean depth of each bin after each iteration.
// I is a unique identifier for the depths that can be use to assiciate the returned struct with
// position info.
func EMDepth(depths []float32, p Position) *EMD {

	m := median32(depths)
	// lambda are the centers of each CN
	lambda := make([]float64, maxCN)
	// put each sample into the bin they belong to.
	// we re-use these since they can take a lot of mem.
	binned := binPool.Get().([][]float64)

	// keep lastCenters to check for convergence.
	lastCenters := make([]float64, len(lambda))

	lambda[0], lambda[2] = eps*m, m
	lastCenters[0] = lambda[0]

	// EXPECTATION:
	// initialize centers of each bin relative to copy-number 2.
	for i := 1; i < len(lambda); i++ {
		lambda[i] = lambda[2] * (float64(i) / 2)
	}

	// iterate at most maxiter times. stop early if the largest depth change
	// from 1 iter to the next is < 0.5 or if the total of all changes is < delta.
	sumd, maxd := float64(100), float64(100)
	for iter := 0; iter < maxiter && sumd > eps && maxd > 0.5; iter++ {
		for i := 1; i < len(lambda); i++ {
			lastCenters[i] = lambda[i]
			binned[i] = binned[i][:0]
		}
		binned[0] = binned[0][:0]
		//fmt.Println(getCNs(depths, centers), centers)
		// MAXIMIZATION
		// put samples in the bin they are closest 2.
		for _, df := range depths {
			d := float64(df)
			// most common case of copy-number 2.
			if d > lambda[1] && d < lambda[3] {
				if abs(d-lambda[2]) < abs(d-lambda[1]) && abs(d-lambda[2]) < abs(d-lambda[3]) {
					binned[2] = append(binned[2], d)
					continue
				}
			}
			idx := sort.SearchFloat64s(lambda, d)
			if idx == 0 {
				binned[0] = append(binned[0], d)
				continue
			}
			if idx == len(lambda) {
				binned[idx-1] = append(binned[idx-1], d)
				continue
			}
			// idx will always be the index of the value >= d so we check if idx or idx-1 is better.
			if abs(d-lambda[idx]) < abs(d-lambda[idx-1]) {
				binned[idx] = append(binned[idx], d)
			} else {
				binned[idx-1] = append(binned[idx-1], d)
			}
		}

		// EXPECTATION:
		// adjust copy-number 2 depth.
		lambda[2] = mean64(binned[2])
		if lambda[2] == 0 {
			n := float64(len(depths))
			// we exclude the top bin to avoid over-adjusting lambda[2] for extreme outliers.
			for i := 1; i < len(lambda)-1; i++ {
				bin := binned[i]
				pdepth := float64(len(bin)) / n
				if lambda[i] < eps {
					lambda[i] = eps
				}
				lambda[2] += mean64(bin) * (2 / float64(i)) * pdepth
			}
		}

		for i := 1; i < len(lambda); i++ {
			// adjust the expected depths of other copy-numbers based on that from CN2.
			lambda[i] = lambda[2] * (float64(i) / 2)
		}
		// make CN 2 more likely by expanding the range between CN1 and CN3.
		span := lambda[2] - lambda[1]
		lambda[1] -= (span / 2.5)
		lambda[3] += (span / 2.5)
		sumd, maxd = summaxdiff(lambda, lastCenters)
	}
	binPool.Put(binned)
	return &EMD{Lambda: lambda, Depths: depths, Position: p}
}

// EMD holds the posterior maximum depth for each copy-number
type EMD struct {
	Lambda   []float64
	Depths   []float32
	Position Position
	_l2      []float64
}

// Same returns
// 1) slice of indexes for samples that have the same (non 2 CN),
// 2) a slice indicating the sample-set that has changed state in o in a way that
// ends a CNV, either by going back to state2 or by switching from a state > 2
// less <2.
// 3)a float indicating the proportion of samples that in the same copy-number
// state in both.

const lower = -0.80
const upper = 0.40

func (e *EMD) Same(o *EMD) (non2 []int, changed []int, pct float64) {
	ofc := o.Log2FC()
	var nSame float64
	non2 = make([]int, 0, 2)
	changed = make([]int, 0, 1)
	for i, ee := range e.Log2FC() {
		oo := ofc[i]
		if ee > lower && ee < upper && oo > lower && oo < upper {
			nSame++
			continue
		}
		// same direction.
		if (oo >= upper && ee >= upper) || (oo <= lower && ee <= lower) {
			non2 = append(non2, i)
			nSame++
		} else {
			changed = append(changed, i)
		}
	}
	return non2, changed, nSame / float64(len(e.Depths))
}

// Log2FC is the fold-change relative to copy-number 2.
func (e *EMD) Log2FC() []float64 {
	if e._l2 != nil {
		return e._l2
	}
	m := make([]float64, 0, len(e.Depths))
	for _, d := range e.Depths {
		m = append(m, math.Log2(float64(d)/e.Lambda[2]))
	}
	e._l2 = m
	return m
}

// CN finds the posterior maximum CN for each sample.
func (e *EMD) CN() []int {
	cns := make([]int, len(e.Depths))
	for i, df := range e.Depths {
		cns[i] = e.Type(df)
	}
	return cns
}

// Type returns the copy-number for the given depth
func (e *EMD) Type(d float32) int {
	df := float64(d)
	idx := sort.SearchFloat64s(e.Lambda, df)
	var cn int
	if idx == 0 {
		cn = 0
	} else if idx == len(e.Lambda) {
		cn = len(e.Lambda)
	} else {
		// idx will always be the index of the value >= d so we check if idx or idx-1 is better.
		if abs(df-e.Lambda[idx]) < abs(df-e.Lambda[idx-1]) {
			cn = idx
		} else {
			cn = idx - 1
		}
	}
	return e.adjustCN(cn, df)
}

// adjustCN for copy-number 1 and 3. Use poisson PMF to make sure they
// are better than CN2. If not, set to CN2.
func (e *EMD) adjustCN(cn int, depth float64) int {

	// TODO: do this for all cn +/- 1.
	if cn == 1 || cn == 3 {
		dk := int(0.5 + depth)
		o, o2 := pmf(dk, e.Lambda[cn]), pmf(dk, e.Lambda[2])
		if o*0.9 < o2 {
			cn = 2
		}
	}
	return cn
}

// Cache is a way to track depth states.eps
// As new items are added to the cache, the value from EMD.Same
// is compared.
// Each time an EMD is added, a slice of Regions that have ended is returned.
type Cache struct {
	last *EMD
	// for each sample (key by sample index), where is it non-CN2?
	cnvs map[int][]*EMD
}

// CNV holds the information for a CNV for a single sample.
type CNV struct {
	SampleI  int
	Depth    []float32
	Position []Position
	Log2FC   []float32
	CN       []int
	PSize    int
}

func (c *CNV) String() string {
	return fmt.Sprintf("%v  %v", c.Position, c.CN)
}

func (c *Cache) Add(e *EMD) []*CNV {
	// TODO: here we check e.Position and eject anything that doesnt have and End with 3 * len(position).
	if c.last == nil {
		if c.cnvs == nil {
			c.cnvs = make(map[int][]*EMD, 200)
		}
		c.last = e
	}
	ret := c.Clear(&e.Position)
	noncn2, _, _ := c.last.Same(e)
	for _, si := range noncn2 {
		c.cnvs[si] = append(c.cnvs[si], e)
	}
	c.last = e
	return ret

}

func (c *Cache) Clear(p *Position) []*CNV {
	if p == nil {
		if c.last == nil {
			return nil
		}
		p = &Position{End: c.last.Position.End + 100000, Start: c.last.Position.Start + 100000}
	}
	cnvs := make([]*CNV, 0, 5)
	keys := make([]int, len(c.cnvs))
	//L := p.End - p.Start
	for si, emd := range c.cnvs {
		// not a big enough gap yet.
		if p.Start-emd[len(emd)-1].Position.End < 300000 {
			continue
		}
		if putative := makecnvs(emd, si); putative != nil {
			cnvs = append(cnvs, putative)
			cnvs[len(cnvs)-1].PSize = len(c.cnvs)
			keys = append(keys, si)
		}
	}
	for _, key := range keys {
		delete(c.cnvs, key)
	}
	return cnvs
}

// merge individal aberrant depth calls into CNVs.
func makecnvs(es []*EMD, sampleI int) *CNV {
	var cnv *CNV
	// in here, we know we have adjacent calls from  same sample.
	var k int
	for _, es := range es {
		fc := es.Log2FC()[sampleI]
		if fc > -0.5 && fc < 0.3 {
			continue
		}
		cn := es.Type(es.Depths[sampleI])
		if k == 0 {
			cnv = &CNV{SampleI: sampleI, CN: []int{cn}, Depth: []float32{es.Depths[sampleI]},
				Position: []Position{es.Position}, Log2FC: []float32{float32(fc)}}
			k++
			continue
		}
		cnv.CN = append(cnv.CN, cn)
		cnv.Depth = append(cnv.Depth, es.Depths[sampleI])
		cnv.Position = append(cnv.Position, es.Position)
		cnv.Log2FC = append(cnv.Log2FC, float32(fc))
	}
	return cnv
}
<commit_msg>widen bounds for 1 and 3<commit_after>// Package emdepth uses a simplified EM algorithm to assign copy-number given a set of depths.
// it is based off the implementation in cn.mops.
// Like cn.mops, it works best with more samples and it assumes that most samples will
// have copy-number 2.
// emdepth consists of a single function EMDepth that iteratively assigns depths to copy-numbers, adjusts
// the center of each copy-number bin. and re-assigns...
// This package does no normalization and therefore expects incoming data to be normalized.
package emdepth

import (
	"fmt"
	"math"
	"sort"
	"sync"
)

// mean of all except highest and lowest values.
func median32(a []float32) float64 {
	// make a copy so we dont modify original order
	b := make([]float64, len(a))
	for i, v := range a {
		b[i] = float64(v)
	}
	sort.Float64s(b)
	if len(b)%2 == 1 {
		return b[len(b)/2]
	}
	return (b[len(b)/2] + b[len(b)/2+1]) / 2
}

// mean of all except highest and lowest values.
func mean32(a []float32) float32 {
	var sum float32
	for _, v := range a {
		sum += v
	}
	return sum / float32(len(a))
}

func mean64(a []float64) float64 {
	if len(a) == 0 {
		return 0
	}
	var sum float64
	for _, v := range a {
		sum += v
	}
	return sum / float64(len(a))
}

func abs(a float64) float64 {
	if a < 0 {
		return -a
	}
	return a
}

func abs32(a float32) float32 {
	if a < 0 {
		return -a
	}
	return a
}

const maxCN = 8
const maxiter = 10
const eps = 0.001

// used to test for convergence return the sum of abs differences
// and the largest absolute difference.
func summaxdiff(a, b []float64) (float64, float64) {
	var m, sum float64
	for i, av := range a {
		d := abs(av - b[i])
		sum += d
		if d > m {
			m = d
		}
	}
	return sum, m
}

func getBins(nSamples int) [][]float64 {
	binned := make([][]float64, maxCN)
	for i := 0; i < maxCN; i++ {
		if i == 2 {
			binned[i] = make([]float64, 0, nSamples)
		} else {
			binned[i] = make([]float64, 0)
		}
	}
	return binned
}

var binPool *sync.Pool

func init() {
	binPool = &sync.Pool{New: func() interface{} {
		return getBins(32)
	}}
}

type Position struct {
	Start uint32
	End   uint32
}

func (p *Position) String() string {
	return fmt.Sprintf("%d:%d", p.Start, p.End)
}

// EMDepth returns a slice of integer copy-numbers (CNs) corresponding to the given normalized
// depths. It uses a simple EM to assign depths to copy-numbers bins with a preference for CN 2.
// And to adjust the mean depth of each bin after each iteration.
// I is a unique identifier for the depths that can be use to assiciate the returned struct with
// position info.
func EMDepth(depths []float32, p Position) *EMD {

	m := median32(depths)
	// lambda are the centers of each CN
	lambda := make([]float64, maxCN)
	// put each sample into the bin they belong to.
	// we re-use these since they can take a lot of mem.
	binned := binPool.Get().([][]float64)

	// keep lastCenters to check for convergence.
	lastCenters := make([]float64, len(lambda))

	lambda[0], lambda[2] = eps*m, m
	lastCenters[0] = lambda[0]

	// EXPECTATION:
	// initialize centers of each bin relative to copy-number 2.
	for i := 1; i < len(lambda); i++ {
		lambda[i] = lambda[2] * (float64(i) / 2)
	}

	// iterate at most maxiter times. stop early if the largest depth change
	// from 1 iter to the next is < 0.5 or if the total of all changes is < delta.
	sumd, maxd := float64(100), float64(100)
	for iter := 0; iter < maxiter && sumd > eps && maxd > 0.5; iter++ {
		for i := 1; i < len(lambda); i++ {
			lastCenters[i] = lambda[i]
			binned[i] = binned[i][:0]
		}
		binned[0] = binned[0][:0]
		//fmt.Println(getCNs(depths, centers), centers)
		// MAXIMIZATION
		// put samples in the bin they are closest 2.
		for _, df := range depths {
			d := float64(df)
			// most common case of copy-number 2.
			if d > lambda[1] && d < lambda[3] {
				if abs(d-lambda[2]) < abs(d-lambda[1]) && abs(d-lambda[2]) < abs(d-lambda[3]) {
					binned[2] = append(binned[2], d)
					continue
				}
			}
			idx := sort.SearchFloat64s(lambda, d)
			if idx == 0 {
				binned[0] = append(binned[0], d)
				continue
			}
			if idx == len(lambda) {
				binned[idx-1] = append(binned[idx-1], d)
				continue
			}
			// idx will always be the index of the value >= d so we check if idx or idx-1 is better.
			if abs(d-lambda[idx]) < abs(d-lambda[idx-1]) {
				binned[idx] = append(binned[idx], d)
			} else {
				binned[idx-1] = append(binned[idx-1], d)
			}
		}

		// EXPECTATION:
		// adjust copy-number 2 depth.
		lambda[2] = mean64(binned[2])
		if lambda[2] == 0 {
			n := float64(len(depths))
			// we exclude the top bin to avoid over-adjusting lambda[2] for extreme outliers.
			for i := 1; i < len(lambda)-1; i++ {
				bin := binned[i]
				pdepth := float64(len(bin)) / n
				if lambda[i] < eps {
					lambda[i] = eps
				}
				lambda[2] += mean64(bin) * (2 / float64(i)) * pdepth
			}
		}

		for i := 1; i < len(lambda); i++ {
			// adjust the expected depths of other copy-numbers based on that from CN2.
			lambda[i] = lambda[2] * (float64(i) / 2)
		}
		// make CN 2 more likely by expanding the range between CN1 and CN3.
		span := lambda[2] - lambda[1]
		lambda[1] -= (span / 1.5)
		lambda[3] += (span / 1.5)
		sumd, maxd = summaxdiff(lambda, lastCenters)
	}
	binPool.Put(binned)
	return &EMD{Lambda: lambda, Depths: depths, Position: p}
}

// EMD holds the posterior maximum depth for each copy-number
type EMD struct {
	Lambda   []float64
	Depths   []float32
	Position Position
	_l2      []float64
}

// Same returns
// 1) slice of indexes for samples that have the same (non 2 CN),
// 2) a slice indicating the sample-set that has changed state in o in a way that
// ends a CNV, either by going back to state2 or by switching from a state > 2
// less <2.
// 3)a float indicating the proportion of samples that in the same copy-number
// state in both.

const lower = -0.80
const upper = 0.40

func (e *EMD) Same(o *EMD) (non2 []int, changed []int, pct float64) {
	ofc := o.Log2FC()
	var nSame float64
	non2 = make([]int, 0, 2)
	changed = make([]int, 0, 1)
	for i, ee := range e.Log2FC() {
		oo := ofc[i]
		if ee > lower && ee < upper && oo > lower && oo < upper {
			nSame++
			continue
		}
		// same direction.
		if (oo >= upper && ee >= upper) || (oo <= lower && ee <= lower) {
			non2 = append(non2, i)
			nSame++
		} else {
			changed = append(changed, i)
		}
	}
	return non2, changed, nSame / float64(len(e.Depths))
}

// Log2FC is the fold-change relative to copy-number 2.
func (e *EMD) Log2FC() []float64 {
	if e._l2 != nil {
		return e._l2
	}
	m := make([]float64, 0, len(e.Depths))
	for _, d := range e.Depths {
		m = append(m, math.Log2(float64(d)/e.Lambda[2]))
	}
	e._l2 = m
	return m
}

// CN finds the posterior maximum CN for each sample.
func (e *EMD) CN() []int {
	cns := make([]int, len(e.Depths))
	for i, df := range e.Depths {
		cns[i] = e.Type(df)
	}
	return cns
}

// Type returns the copy-number for the given depth
func (e *EMD) Type(d float32) int {
	df := float64(d)
	idx := sort.SearchFloat64s(e.Lambda, df)
	var cn int
	if idx == 0 {
		cn = 0
	} else if idx == len(e.Lambda) {
		cn = len(e.Lambda)
	} else {
		// idx will always be the index of the value >= d so we check if idx or idx-1 is better.
		if abs(df-e.Lambda[idx]) < abs(df-e.Lambda[idx-1]) {
			cn = idx
		} else {
			cn = idx - 1
		}
	}
	return e.adjustCN(cn, df)
}

// adjustCN for copy-number 1 and 3. Use poisson PMF to make sure they
// are better than CN2. If not, set to CN2.
func (e *EMD) adjustCN(cn int, depth float64) int {

	// TODO: do this for all cn +/- 1.
	if cn == 1 || cn == 3 {
		dk := int(0.5 + depth)
		o, o2 := pmf(dk, e.Lambda[cn]), pmf(dk, e.Lambda[2])
		if o*0.9 < o2 {
			cn = 2
		}
	}
	return cn
}

// Cache is a way to track depth states.eps
// As new items are added to the cache, the value from EMD.Same
// is compared.
// Each time an EMD is added, a slice of Regions that have ended is returned.
type Cache struct {
	last *EMD
	// for each sample (key by sample index), where is it non-CN2?
	cnvs map[int][]*EMD
}

// CNV holds the information for a CNV for a single sample.
type CNV struct {
	SampleI  int
	Depth    []float32
	Position []Position
	Log2FC   []float32
	CN       []int
	PSize    int
}

func (c *CNV) String() string {
	return fmt.Sprintf("%v  %v", c.Position, c.CN)
}

func (c *Cache) Add(e *EMD) []*CNV {
	// TODO: here we check e.Position and eject anything that doesnt have and End with 3 * len(position).
	if c.last == nil {
		if c.cnvs == nil {
			c.cnvs = make(map[int][]*EMD, 200)
		}
		c.last = e
	}
	ret := c.Clear(&e.Position)
	noncn2, _, _ := c.last.Same(e)
	for _, si := range noncn2 {
		c.cnvs[si] = append(c.cnvs[si], e)
	}
	c.last = e
	return ret

}

func (c *Cache) Clear(p *Position) []*CNV {
	if p == nil {
		if c.last == nil {
			return nil
		}
		p = &Position{End: c.last.Position.End + 100000, Start: c.last.Position.Start + 100000}
	}
	cnvs := make([]*CNV, 0, 5)
	keys := make([]int, len(c.cnvs))
	//L := p.End - p.Start
	for si, emd := range c.cnvs {
		// not a big enough gap yet.
		if p.Start-emd[len(emd)-1].Position.End < 300000 {
			continue
		}
		if putative := makecnvs(emd, si); putative != nil {
			cnvs = append(cnvs, putative)
			cnvs[len(cnvs)-1].PSize = len(c.cnvs)
			keys = append(keys, si)
		}
	}
	for _, key := range keys {
		delete(c.cnvs, key)
	}
	return cnvs
}

// merge individal aberrant depth calls into CNVs.
func makecnvs(es []*EMD, sampleI int) *CNV {
	var cnv *CNV
	// in here, we know we have adjacent calls from  same sample.
	var k int
	for _, es := range es {
		fc := es.Log2FC()[sampleI]
		if fc > -0.5 && fc < 0.3 {
			continue
		}
		cn := es.Type(es.Depths[sampleI])
		if k == 0 {
			cnv = &CNV{SampleI: sampleI, CN: []int{cn}, Depth: []float32{es.Depths[sampleI]},
				Position: []Position{es.Position}, Log2FC: []float32{float32(fc)}}
			k++
			continue
		}
		cnv.CN = append(cnv.CN, cn)
		cnv.Depth = append(cnv.Depth, es.Depths[sampleI])
		cnv.Position = append(cnv.Position, es.Position)
		cnv.Log2FC = append(cnv.Log2FC, float32(fc))
	}
	return cnv
}
<|endoftext|>


<commit_before>package emitter

import (
	"fmt"
	"time"
	"gopkg.in/vmihailenco/msgpack.v1"
	"github.com/garyburd/redigo/redis"
)

const (
	event       = 2
	binaryEvent = 5
)

type EmitterOptions struct {
	Host       string
	Password   string
	Key        string
}

type Emitter struct {
	_opts      EmitterOptions
	_key       string
	_flags     map[string]string
	_rooms     map[string]bool
	_pool      *redis.Pool
}

func New(opts EmitterOptions) Emitter {
	emitter := Emitter{_opts:opts}

	initRedisConnPool(&emitter,opts)

	if opts.Key != "" {
		emitter._key = fmt.Sprintf("%s#emitter", opts.Key)
	}else {
		emitter._key = "socket.io#emitter"
	}

	emitter._rooms = make(map[string]bool)
	emitter._flags = make(map[string]string)

	return emitter
}

func (emitter Emitter) In(room string) Emitter {
	if _, ok := emitter._rooms[room]; ok == false {
		emitter._rooms[room] = true
	}
	return emitter
}

func (emitter Emitter) To(room string) Emitter {
	return emitter.In(room)
}

func (emitter Emitter) Of(nsp string) Emitter {
	emitter._flags["nsp"] = nsp
	return emitter
}

func (emitter Emitter) Emit(args ...interface{}) bool {
	packet := make(map[string]interface{})
	extras := make(map[string]interface{})

	if ok := emitter.hasBin(args); ok {
		packet["type"] = binaryEvent
	}else {
		packet["type"] = event
	}

	packet["data"] = args

	if value, ok := emitter._flags["nsp"]; ok {
		packet["nsp"] = value
		delete(emitter._flags, "nsp")
	}else {
		packet["nsp"] = "/"
	}

	if ok := len(emitter._rooms); ok > 0 {
		//TODO:Cast??
		extras["rooms"] = getKeys(emitter._rooms)
	}else {
		extras["rooms"] = make([]string, 0, 0)
	}

	if ok := len(emitter._flags); ok > 0 {
		extras["flags"] = emitter._flags
	}else {
		extras["flags"] = make(map[string]string)
	}

	//TODO: Gorotunes
	//Pack & Publish
	b, err := msgpack.Marshal([]interface{}{packet, extras})
	if err != nil {
		panic(err)
	}else {
		publish(emitter,emitter._key, b)
	}

	emitter._rooms = make(map[string]bool)
	emitter._flags = make(map[string]string)

	return true
}

func (emitter Emitter) hasBin(args ...interface{}) bool {
	//NOT implemented yet!
	return true
}

func initRedisConnPool(emitter *Emitter ,opts EmitterOptions) () {
	if opts.Host == "" {
		panic("Missing redis `host`")
	}

	emitter._pool = newPool(opts)
}

func newPool(opts EmitterOptions) *redis.Pool {
	return &redis.Pool{
		MaxIdle: 80,
		MaxActive: 12000, // max number of connections
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", opts.Host)
			if err != nil {
				return nil, err
			}

			if opts.Password != "" {
				if _, err := c.Do("AUTH", opts.Password); err != nil {
					c.Close()
					return nil, err
				}
				return c, err
			}

			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}
}

func publish(emitter Emitter, channel string, value interface{}) {
	c := emitter._pool.Get()
	defer c.Close()
	c.Do("PUBLISH", channel, value)

}

func getKeys(m map[string]bool) []string {
	keys := make([]string, 0, len(m))

	for key := range m {
		keys = append(keys, key)
	}

	return keys
}

func dump(emitter Emitter, args ...interface{}) {
	fmt.Println("Emit params : ", args)
	fmt.Println("Emitter key: ", emitter._key)
	fmt.Println("Emitter flags: ", emitter._flags)
	fmt.Println("Emitter rooms: ", emitter._rooms)
}<commit_msg>Code refactoring<commit_after>package emitter

import (
	"fmt"
	"time"
	"gopkg.in/vmihailenco/msgpack.v1"
	"github.com/garyburd/redigo/redis"
)

const (
	event              = 2
	binaryEvent        = 5
	redisPoolMaxIdle   = 80
	redisPoolMaxActive = 12000 // max number of connections
)

type EmitterOptions struct {
	Host       string
	Password   string
	Key        string
}

type Emitter struct {
	_opts      EmitterOptions
	_key       string
	_flags     map[string]string
	_rooms     map[string]bool
	_pool      *redis.Pool
}

func New(opts EmitterOptions) Emitter {
	emitter := Emitter{_opts:opts}

	initRedisConnPool(&emitter, opts)

	if opts.Key != "" {
		emitter._key = fmt.Sprintf("%s#emitter", opts.Key)
	}else {
		emitter._key = "socket.io#emitter"
	}

	emitter._rooms = make(map[string]bool)
	emitter._flags = make(map[string]string)

	return emitter
}

func (emitter Emitter) In(room string) Emitter {
	if _, ok := emitter._rooms[room]; ok == false {
		emitter._rooms[room] = true
	}
	return emitter
}

func (emitter Emitter) To(room string) Emitter {
	return emitter.In(room)
}

func (emitter Emitter) Of(nsp string) Emitter {
	emitter._flags["nsp"] = nsp
	return emitter
}

func (emitter Emitter) Emit(args ...interface{}) bool {
	packet := make(map[string]interface{})
	extras := make(map[string]interface{})

	if ok := emitter.hasBin(args); ok {
		packet["type"] = binaryEvent
	}else {
		packet["type"] = event
	}

	packet["data"] = args

	if value, ok := emitter._flags["nsp"]; ok {
		packet["nsp"] = value
		delete(emitter._flags, "nsp")
	}else {
		packet["nsp"] = "/"
	}

	if ok := len(emitter._rooms); ok > 0 {
		//TODO:Cast??
		extras["rooms"] = getKeys(emitter._rooms)
	}else {
		extras["rooms"] = make([]string, 0, 0)
	}

	if ok := len(emitter._flags); ok > 0 {
		extras["flags"] = emitter._flags
	}else {
		extras["flags"] = make(map[string]string)
	}

	//TODO: Gorotunes
	//Pack & Publish
	b, err := msgpack.Marshal([]interface{}{packet, extras})
	if err != nil {
		panic(err)
	}else {
		publish(emitter, emitter._key, b)
	}

	emitter._rooms = make(map[string]bool)
	emitter._flags = make(map[string]string)

	return true
}

func (emitter Emitter) hasBin(args ...interface{}) bool {
	//NOT implemented yet!
	return true
}

func initRedisConnPool(emitter *Emitter , opts EmitterOptions) () {
	if opts.Host == "" {
		panic("Missing redis `host`")
	}

	emitter._pool = newPool(opts)
}

func newPool(opts EmitterOptions) *redis.Pool {
	return &redis.Pool{
		MaxIdle: redisPoolMaxIdle,
		MaxActive: redisPoolMaxActive,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", opts.Host)
			if err != nil {
				return nil, err
			}

			if opts.Password != "" {
				if _, err := c.Do("AUTH", opts.Password); err != nil {
					c.Close()
					return nil, err
				}
				return c, err
			}

			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}

}

func publish(emitter Emitter, channel string, value interface{}) {
	c := emitter._pool.Get()
	defer c.Close()
	c.Do("PUBLISH", channel, value)

}

func getKeys(m map[string]bool) []string {
	keys := make([]string, 0, len(m))

	for key := range m {
		keys = append(keys, key)
	}

	return keys
}

func dump(emitter Emitter, args ...interface{}) {
	fmt.Println("Emit params : ", args)
	fmt.Println("Emitter key: ", emitter._key)
	fmt.Println("Emitter flags: ", emitter._flags)
	fmt.Println("Emitter rooms: ", emitter._rooms)
}<|endoftext|>


<commit_before>package database

import "testing"

func TestNewMock(t *testing.T) {
	db, err := NewMock()
	if err != nil {
		t.Fatalf("got NewMock() = _, %v, want = _, nil", err)
	}
	if err := db.Close(); err != nil {
		t.Fatalf("got db.Close() = %v, want = nil", err)
	}
}
<commit_msg>Added TestSetMaxOpenConns.<commit_after>package database

import (
	"testing"

	sqlmock "github.com/DATA-DOG/go-sqlmock"
)

func TestNewMock(t *testing.T) {
	db, err := NewMock()
	if err != nil {
		t.Fatalf("got NewMock() = _, %v, want = _, nil", err)
	}
	if err := db.Close(); err != nil {
		t.Fatalf("got db.Close() = %v, want = nil", err)
	}
}

func TestSetMaxOpenConns(t *testing.T) {
	conn, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("got sqlmock.New() = _, _, %v, want = _, _, nil", err)
	}
	mock.ExpectClose()
	db := DB{conn}

	db.SetMaxOpenConns(5)
	if err := db.Close(); err != nil {
		t.Fatalf("got db.Close() = %v, want = nil", err)
	}
}
<|endoftext|>


<commit_before>package v1alpha1

import "time"

const (
	RoleMaster    = "master"
	RoleNode      = "node"
	RoleKeyPrefix = "node-role.kubernetes.io/"
	RoleMasterKey = RoleKeyPrefix + RoleMaster
	RoleNodeKey   = RoleKeyPrefix + RoleNode

	KubeadmVersionKey = "cloud.appscode.com/kubeadm-version"
	NodePoolKey       = "cloud.appscode.com/pool"
	KubeSystem_App    = "k8s-app"

	HostnameKey     = "kubernetes.io/hostname"
	ArchKey         = "beta.kubernetes.io/arch"
	InstanceTypeKey = "beta.kubernetes.io/instance-type"
	OSKey           = "beta.kubernetes.io/os"
	RegionKey       = "failure-domain.beta.kubernetes.io/region"
	ZoneKey         = "failure-domain.beta.kubernetes.io/zone"

	TokenDuration_10yr = 10 * 365 * 24 * time.Hour

	// ref: https://github.com/kubernetes/kubeadm/issues/629
	DeprecatedV19AdmissionControl = "NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ValidatingAdmissionWebhook,DefaultTolerationSeconds,MutatingAdmissionWebhook,ResourceQuota"
	DefaultV19AdmissionControl    = "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ValidatingAdmissionWebhook,DefaultTolerationSeconds,MutatingAdmissionWebhook,ResourceQuota"
	DefaultV111AdmissionControl   = "Initializers,NodeRestriction"
)
<commit_msg>Admission control value update for 1.11 (#449)<commit_after>package v1alpha1

import "time"

const (
	RoleMaster    = "master"
	RoleNode      = "node"
	RoleKeyPrefix = "node-role.kubernetes.io/"
	RoleMasterKey = RoleKeyPrefix + RoleMaster
	RoleNodeKey   = RoleKeyPrefix + RoleNode

	KubeadmVersionKey = "cloud.appscode.com/kubeadm-version"
	NodePoolKey       = "cloud.appscode.com/pool"
	KubeSystem_App    = "k8s-app"

	HostnameKey     = "kubernetes.io/hostname"
	ArchKey         = "beta.kubernetes.io/arch"
	InstanceTypeKey = "beta.kubernetes.io/instance-type"
	OSKey           = "beta.kubernetes.io/os"
	RegionKey       = "failure-domain.beta.kubernetes.io/region"
	ZoneKey         = "failure-domain.beta.kubernetes.io/zone"

	TokenDuration_10yr = 10 * 365 * 24 * time.Hour

	// ref: https://github.com/kubernetes/kubeadm/issues/629
	DeprecatedV19AdmissionControl = "NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ValidatingAdmissionWebhook,DefaultTolerationSeconds,MutatingAdmissionWebhook,ResourceQuota"
	DefaultV19AdmissionControl    = "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ValidatingAdmissionWebhook,DefaultTolerationSeconds,MutatingAdmissionWebhook,ResourceQuota"
	DefaultV111AdmissionControl   = "Initializers,NodeRestriction,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ValidatingAdmissionWebhook,DefaultTolerationSeconds,MutatingAdmissionWebhook,ResourceQuota"
)
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	kubeclient "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/fake"
)

type ExpectedCommand struct {
	Args   []string
	Output string
	Err    error
}

func run(expectedCmds []ExpectedCommand, runFn func() error) error {
	getClientset = func(kubeconfigPath string) (kubeclient.Interface, error) {
		return &fake.Clientset{}, nil
	}

	i := 0
	executeCommand = func(args []string) (string, error) {
		if i >= len(expectedCmds) {
			return "", fmt.Errorf("unexpected command: %s", strings.Join(args, " "))
		}
		if !reflect.DeepEqual(args, expectedCmds[i].Args) {
			return "", fmt.Errorf("unexpected command: %s, was expecting   : %s", strings.Join(args, " "), strings.Join(expectedCmds[i].Args, " "))
		}
		output, err := expectedCmds[i].Output, expectedCmds[i].Err
		i++
		return output, err
	}
	err := runFn()
	if err != nil {
		return err
	}
	if i != len(expectedCmds) {
		return fmt.Errorf("expected commands not called: %s", expectedCmds[i:])
	}
	return nil
}

func TestValidateArgs(t *testing.T) {
	// ValidateArgs should return an error with empty options.
	options := &CreateOptions{}
	if err := ValidateArgs(options, []string{}); err == nil {
		t.Errorf("Expected error for missing ingress")
	}
	// ValidateArgs should return an error with missing load balancer name.
	options.IngressFilename = "ingress.yaml"
	if err := ValidateArgs(options, []string{}); err == nil {
		t.Errorf("Expected error for missing load balancer name")
	}

	// ValidateArgs should succeed when IngressFilename is set.
	options.IngressFilename = "ingress.yaml"
	if err := ValidateArgs(options, []string{"lbname"}); err != nil {
		t.Errorf("unexpected error from ValidateArgs: %s", err)
	}
}

func TestRunCreate(t *testing.T) {
	options := &CreateOptions{
		IngressFilename: "../../../testdata/ingress.yaml",
		Kubeconfig:      "kubeconfig",
	}
	runFn := func() error {
		return RunCreate(options, []string{"lbname"})
	}
	expectedCommands := []ExpectedCommand{
		{
			Args:   []string{"kubectl", "--kubeconfig=kubeconfig", "config", "get-contexts", "-o=name"},
			Output: "context-1\ncontext-2",
			Err:    nil,
		},
		{
			Args:   []string{"kubectl", "--kubeconfig=kubeconfig", "create", "--filename=../../../testdata/ingress.yaml", "--context=context-1"},
			Output: "ingress created",
			Err:    nil,
		},
		{
			Args:   []string{"kubectl", "--kubeconfig=kubeconfig", "create", "--filename=../../../testdata/ingress.yaml", "--context=context-2"},
			Output: "ingress created",
			Err:    nil,
		},
	}
	if err := run(expectedCommands, runFn); err != nil {
		t.Errorf("%s", err)
	}
}
<commit_msg>Add missing License info<commit_after>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	kubeclient "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/fake"
)

type ExpectedCommand struct {
	Args   []string
	Output string
	Err    error
}

func run(expectedCmds []ExpectedCommand, runFn func() error) error {
	getClientset = func(kubeconfigPath string) (kubeclient.Interface, error) {
		return &fake.Clientset{}, nil
	}

	i := 0
	executeCommand = func(args []string) (string, error) {
		if i >= len(expectedCmds) {
			return "", fmt.Errorf("unexpected command: %s", strings.Join(args, " "))
		}
		if !reflect.DeepEqual(args, expectedCmds[i].Args) {
			return "", fmt.Errorf("unexpected command: %s, was expecting   : %s", strings.Join(args, " "), strings.Join(expectedCmds[i].Args, " "))
		}
		output, err := expectedCmds[i].Output, expectedCmds[i].Err
		i++
		return output, err
	}
	err := runFn()
	if err != nil {
		return err
	}
	if i != len(expectedCmds) {
		return fmt.Errorf("expected commands not called: %s", expectedCmds[i:])
	}
	return nil
}

func TestValidateArgs(t *testing.T) {
	// ValidateArgs should return an error with empty options.
	options := &CreateOptions{}
	if err := ValidateArgs(options, []string{}); err == nil {
		t.Errorf("Expected error for missing ingress")
	}
	// ValidateArgs should return an error with missing load balancer name.
	options.IngressFilename = "ingress.yaml"
	if err := ValidateArgs(options, []string{}); err == nil {
		t.Errorf("Expected error for missing load balancer name")
	}

	// ValidateArgs should succeed when IngressFilename is set.
	options.IngressFilename = "ingress.yaml"
	if err := ValidateArgs(options, []string{"lbname"}); err != nil {
		t.Errorf("unexpected error from ValidateArgs: %s", err)
	}
}

func TestRunCreate(t *testing.T) {
	options := &CreateOptions{
		IngressFilename: "../../../testdata/ingress.yaml",
		Kubeconfig:      "kubeconfig",
	}
	runFn := func() error {
		return RunCreate(options, []string{"lbname"})
	}
	expectedCommands := []ExpectedCommand{
		{
			Args:   []string{"kubectl", "--kubeconfig=kubeconfig", "config", "get-contexts", "-o=name"},
			Output: "context-1\ncontext-2",
			Err:    nil,
		},
		{
			Args:   []string{"kubectl", "--kubeconfig=kubeconfig", "create", "--filename=../../../testdata/ingress.yaml", "--context=context-1"},
			Output: "ingress created",
			Err:    nil,
		},
		{
			Args:   []string{"kubectl", "--kubeconfig=kubeconfig", "create", "--filename=../../../testdata/ingress.yaml", "--context=context-2"},
			Output: "ingress created",
			Err:    nil,
		},
	}
	if err := run(expectedCommands, runFn); err != nil {
		t.Errorf("%s", err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 integration

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"testing"
	"time"

	heapster_api "github.com/GoogleCloudPlatform/heapster/api/v1"
	sink_api "github.com/GoogleCloudPlatform/heapster/sinks/api/v1"
	"github.com/GoogleCloudPlatform/heapster/sinks/cache"
	kube_api "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/golang/glog"
	"github.com/stretchr/testify/require"
)

const (
	kTestZone        = "us-central1-b"
	targetTags       = "kubernetes-minion"
	heapsterBuildDir = "../deploy/docker"
)

var (
	kubeVersions           = flag.String("kube_versions", "", "Comma separated list of kube versions to test against. By default will run the test against an existing cluster")
	heapsterControllerFile = flag.String("heapster_controller", "../deploy/kube-config/standalone/heapster-controller.json", "Path to heapster replication controller file.")
	heapsterServiceFile    = flag.String("heapster_service", "../deploy/kube-config/standalone/heapster-service.json", "Path to heapster service file.")
	heapsterImage          = flag.String("heapster_image", "heapster:e2e_test", "heapster docker image that needs to be tested.")
	avoidBuild             = flag.Bool("nobuild", false, "When true, a new heapster docker image will not be created and pushed to test cluster nodes.")
	namespace              = flag.String("namespace", "default", "namespace to be used for testing")
	maxRetries             = flag.Int("retries", 100, "Number of attempts before failing this test.")
	runForever             = flag.Bool("run_forever", false, "If true, the tests are run in a loop forever.")
)

func deleteAll(fm kubeFramework, ns string, service *kube_api.Service, rc *kube_api.ReplicationController) {
	if err := fm.DeleteRC(ns, rc); err != nil {
		glog.Error(err)
	}

	if err := fm.DeleteService(ns, service); err != nil {
		glog.Error(err)
	}
}

func createAll(fm kubeFramework, ns string, service **kube_api.Service, rc **kube_api.ReplicationController) error {
	if newRc, err := fm.CreateRC(ns, *rc); err != nil {
		return err
	} else {
		*rc = newRc
	}

	if newSvc, err := fm.CreateService(ns, *service); err != nil {
		return err
	} else {
		*service = newSvc
	}
	return nil
}

func removeHeapsterImage(fm kubeFramework) error {
	if err := removeDockerImage(*heapsterImage); err != nil {
		glog.Error(err)
	}
	if nodes, err := fm.GetNodes(); err == nil {
		for _, node := range nodes {
			host := strings.Split(node, ".")[0]
			cleanupRemoteHost(host, kTestZone)
		}
	} else {
		glog.Errorf("failed to cleanup nodes - %v", err)
	}
	return nil
}

func buildAndPushHeapsterImage(hostnames []string) error {
	curwd, err := os.Getwd()
	if err != nil {
		return err
	}
	if err := os.Chdir(heapsterBuildDir); err != nil {
		return err
	}
	if err := buildDockerImage(*heapsterImage); err != nil {
		return err
	}
	for _, host := range hostnames {
		if err := copyDockerImage(*heapsterImage, host, kTestZone); err != nil {
			return err
		}
	}
	glog.V(2).Info("built and pushed heapster image")
	return os.Chdir(curwd)
}

func getHeapsterRcAndSvc(fm kubeFramework) (*kube_api.Service, *kube_api.ReplicationController, error) {
	// Add test docker image
	rc, err := fm.ParseRC(*heapsterControllerFile)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to parse heapster controller - %v", err)
	}
	rc.Spec.Template.Spec.Containers[0].Image = *heapsterImage
	rc.Spec.Template.Spec.Containers[0].ImagePullPolicy = kube_api.PullNever
	// increase logging level
	rc.Spec.Template.Spec.Containers[0].Env = append(rc.Spec.Template.Spec.Containers[0].Env, kube_api.EnvVar{Name: "FLAGS", Value: "--vmodule=*=3"})
	svc, err := fm.ParseService(*heapsterServiceFile)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to parse heapster service - %v", err)
	}

	return svc, rc, nil
}

func buildAndPushDockerImages(fm kubeFramework) error {
	if *avoidBuild {
		return nil
	}
	nodes, err := fm.GetNodes()
	if err != nil {
		return err
	}
	hostnames := []string{}
	for _, node := range nodes {
		hostnames = append(hostnames, strings.Split(node, ".")[0])
	}

	return buildAndPushHeapsterImage(hostnames)
}

const (
	metricsEndpoint       = "/api/v1/metric-export"
	metricsSchemaEndpoint = "/api/v1/metric-export-schema"
)

func getTimeseries(fm kubeFramework, svc *kube_api.Service) ([]*heapster_api.Timeseries, error) {
	body, err := fm.Client().Get().
		Namespace(svc.Namespace).
		Prefix("proxy").
		Resource("services").
		Name(svc.Name).
		Suffix(metricsEndpoint).
		Do().Raw()
	if err != nil {
		return nil, err
	}
	var timeseries []*heapster_api.Timeseries
	if err := json.Unmarshal(body, &timeseries); err != nil {
		glog.V(2).Infof("response body: %v", string(body))
		return nil, err
	}
	return timeseries, nil
}

func getSchema(fm kubeFramework, svc *kube_api.Service) (*heapster_api.TimeseriesSchema, error) {
	body, err := fm.Client().Get().
		Namespace(svc.Namespace).
		Prefix("proxy").
		Resource("services").
		Name(svc.Name).
		Suffix(metricsSchemaEndpoint).
		Do().Raw()
	if err != nil {
		return nil, err
	}
	var timeseriesSchema heapster_api.TimeseriesSchema
	if err := json.Unmarshal(body, &timeseriesSchema); err != nil {
		glog.V(2).Infof("response body: %v", string(body))
		return nil, err
	}
	return &timeseriesSchema, nil
}

var expectedSystemContainers = map[string]struct{}{
	"machine":       struct{}{},
	"kubelet":       struct{}{},
	"kube-proxy":    struct{}{},
	"system":        struct{}{},
	"docker-daemon": struct{}{},
}

func runHeapsterMetricsTest(fm kubeFramework, svc *kube_api.Service, expectedNodes, expectedPods []string) error {
	timeseries, err := getTimeseries(fm, svc)
	if err != nil {
		return err
	}
	if len(timeseries) == 0 {
		return fmt.Errorf("expected non zero timeseries")
	}
	schema, err := getSchema(fm, svc)
	if err != nil {
		return err
	}
	// Build a map of metric names to metric descriptors.
	mdMap := map[string]*heapster_api.MetricDescriptor{}
	for idx := range schema.Metrics {
		mdMap[schema.Metrics[idx].Name] = &schema.Metrics[idx]
	}
	actualPods := map[string]bool{}
	actualNodes := map[string]bool{}
	actualSystemContainers := map[string]map[string]struct{}{}
	for _, ts := range timeseries {
		// Verify the relevant labels are present.
		// All common labels must be present.
		for _, label := range sink_api.CommonLabels() {
			_, exists := ts.Labels[label.Key]
			if !exists {
				return fmt.Errorf("timeseries: %v does not contain common label: %v", ts, label)
			}
		}
		podName, podMetric := ts.Labels[sink_api.LabelPodName.Key]
		if podMetric {
			for _, label := range sink_api.PodLabels() {
				_, exists := ts.Labels[label.Key]
				if !exists {
					return fmt.Errorf("timeseries: %v does not contain pod label: %v", ts, label)
				}
			}
		}
		if podMetric {
			actualPods[podName] = true
		} else {
			if cName, ok := ts.Labels[sink_api.LabelContainerName.Key]; ok {
				hostname, ok := ts.Labels[sink_api.LabelHostname.Key]
				if !ok {
					return fmt.Errorf("hostname label missing on container %+v", ts)
				}

				if cName == cache.NodeContainerName {
					actualNodes[hostname] = true
				}
				if _, exists := expectedSystemContainers[cName]; exists {
					if actualSystemContainers[cName] == nil {
						actualSystemContainers[cName] = map[string]struct{}{}
					}
					actualSystemContainers[cName][hostname] = struct{}{}
				}
			} else {
				return fmt.Errorf("container_name label missing on timeseries - %v", ts)
			}
		}

		for metricName, points := range ts.Metrics {
			md, exists := mdMap[metricName]
			if !exists {
				return fmt.Errorf("unexpected metric %q", metricName)
			}
			for _, point := range points {
				for _, label := range md.Labels {
					_, exists := point.Labels[label.Key]
					if !exists {
						return fmt.Errorf("metric %q point %v does not contain metric label: %v", metricName, point, label)
					}
				}
			}

		}
	}
	// Validate that system containers are running on all the nodes.
	// This test could fail if one of the containers was down while the metrics sample was collected.
	for cName, hosts := range actualSystemContainers {
		for _, host := range expectedNodes {
			if _, ok := hosts[host]; !ok {
				return fmt.Errorf("System container %q not found on host: %q - %v", cName, host, actualSystemContainers)
			}
		}
	}

	if !expectedItemsExist(expectedPods, actualPods) {
		return fmt.Errorf("expected pods don't exist.\nExpected: %v\nActual:%v", expectedPods, actualPods)
	}
	if !expectedItemsExist(expectedNodes, actualNodes) {
		return fmt.Errorf("expected nodes don't exist.\nExpected: %v\nActual:%v", expectedNodes, actualNodes)
	}

	return nil
}

func expectedItemsExist(expectedItems []string, actualItems map[string]bool) bool {
	if len(actualItems) < len(expectedItems) {
		return false
	}
	for _, item := range expectedItems {
		if _, found := actualItems[item]; !found {
			return false
		}
	}
	return true
}

func apiTest(kubeVersion string) error {
	fm, err := newKubeFramework(kubeVersion)
	if err != nil {
		return err
	}
	if err := buildAndPushDockerImages(fm); err != nil {
		return err
	}
	// Create heapster pod and service.
	svc, rc, err := getHeapsterRcAndSvc(fm)
	if err != nil {
		return err
	}
	ns := *namespace
	deleteAll(fm, ns, svc, rc)
	if err = createAll(fm, ns, &svc, &rc); err != nil {
		return err
	}
	defer func() {
		//		deleteAll(fm, ns, svc, rc)
		//	removeHeapsterImage(fm)
	}()
	if err := fm.WaitUntilPodRunning(ns, rc.Spec.Template.Labels, time.Minute); err != nil {
		return err
	}
	if err := fm.WaitUntilServiceActive(svc, time.Minute); err != nil {
		return err
	}
	expectedPods, err := fm.GetPods()
	if err != nil {
		return err
	}
	expectedNodes, err := fm.GetNodes()
	if err != nil {
		return err
	}
	attempts := *maxRetries
	for {
		err := runHeapsterMetricsTest(fm, svc, expectedNodes, expectedPods)
		if *runForever {
			continue
		}
		if err == nil {
			return nil
		}
		if attempts == 0 {
			return err
		}
		attempts--
		time.Sleep(time.Second)
	}
}

func runApiTest() error {
	tempDir, err := ioutil.TempDir("", "deploy")
	if err != nil {
		return nil
	}
	defer os.RemoveAll(tempDir)
	if *kubeVersions == "" {
		return apiTest("")
	}
	kubeVersionsList := strings.Split(*kubeVersions, ",")
	for _, kubeVersion := range kubeVersionsList {
		if err := apiTest(kubeVersion); err != nil {
			return err
		}
	}
	return nil
}

func TestHeapster(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping heapster kubernetes integration test.")
	}
	require.NoError(t, runApiTest())
}
<commit_msg>Add mutable sinks endpoint integration test<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 integration

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"strings"
	"testing"
	"time"

	heapster_api "github.com/GoogleCloudPlatform/heapster/api/v1"
	sink_api "github.com/GoogleCloudPlatform/heapster/sinks/api/v1"
	"github.com/GoogleCloudPlatform/heapster/sinks/cache"
	kube_api "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/golang/glog"
	"github.com/stretchr/testify/require"
)

const (
	kTestZone        = "us-central1-b"
	targetTags       = "kubernetes-minion"
	heapsterBuildDir = "../deploy/docker"
)

var (
	kubeVersions           = flag.String("kube_versions", "", "Comma separated list of kube versions to test against. By default will run the test against an existing cluster")
	heapsterControllerFile = flag.String("heapster_controller", "../deploy/kube-config/standalone/heapster-controller.json", "Path to heapster replication controller file.")
	heapsterServiceFile    = flag.String("heapster_service", "../deploy/kube-config/standalone/heapster-service.json", "Path to heapster service file.")
	heapsterImage          = flag.String("heapster_image", "heapster:e2e_test", "heapster docker image that needs to be tested.")
	avoidBuild             = flag.Bool("nobuild", false, "When true, a new heapster docker image will not be created and pushed to test cluster nodes.")
	namespace              = flag.String("namespace", "default", "namespace to be used for testing")
	maxRetries             = flag.Int("retries", 100, "Number of attempts before failing this test.")
	runForever             = flag.Bool("run_forever", false, "If true, the tests are run in a loop forever.")
)

func deleteAll(fm kubeFramework, ns string, service *kube_api.Service, rc *kube_api.ReplicationController) {
	if err := fm.DeleteRC(ns, rc); err != nil {
		glog.Error(err)
	}

	if err := fm.DeleteService(ns, service); err != nil {
		glog.Error(err)
	}
}

func createAll(fm kubeFramework, ns string, service **kube_api.Service, rc **kube_api.ReplicationController) error {
	if newRc, err := fm.CreateRC(ns, *rc); err != nil {
		return err
	} else {
		*rc = newRc
	}

	if newSvc, err := fm.CreateService(ns, *service); err != nil {
		return err
	} else {
		*service = newSvc
	}
	return nil
}

func removeHeapsterImage(fm kubeFramework) error {
	if err := removeDockerImage(*heapsterImage); err != nil {
		glog.Error(err)
	}
	if nodes, err := fm.GetNodes(); err == nil {
		for _, node := range nodes {
			host := strings.Split(node, ".")[0]
			cleanupRemoteHost(host, kTestZone)
		}
	} else {
		glog.Errorf("failed to cleanup nodes - %v", err)
	}
	return nil
}

func buildAndPushHeapsterImage(hostnames []string) error {
	curwd, err := os.Getwd()
	if err != nil {
		return err
	}
	if err := os.Chdir(heapsterBuildDir); err != nil {
		return err
	}
	if err := buildDockerImage(*heapsterImage); err != nil {
		return err
	}
	for _, host := range hostnames {
		if err := copyDockerImage(*heapsterImage, host, kTestZone); err != nil {
			return err
		}
	}
	glog.V(2).Info("built and pushed heapster image")
	return os.Chdir(curwd)
}

func getHeapsterRcAndSvc(fm kubeFramework) (*kube_api.Service, *kube_api.ReplicationController, error) {
	// Add test docker image
	rc, err := fm.ParseRC(*heapsterControllerFile)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to parse heapster controller - %v", err)
	}
	rc.Spec.Template.Spec.Containers[0].Image = *heapsterImage
	rc.Spec.Template.Spec.Containers[0].ImagePullPolicy = kube_api.PullNever
	// increase logging level
	rc.Spec.Template.Spec.Containers[0].Env = append(rc.Spec.Template.Spec.Containers[0].Env, kube_api.EnvVar{Name: "FLAGS", Value: "--vmodule=*=3"})
	svc, err := fm.ParseService(*heapsterServiceFile)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to parse heapster service - %v", err)
	}

	return svc, rc, nil
}

func buildAndPushDockerImages(fm kubeFramework) error {
	if *avoidBuild {
		return nil
	}
	nodes, err := fm.GetNodes()
	if err != nil {
		return err
	}
	hostnames := []string{}
	for _, node := range nodes {
		hostnames = append(hostnames, strings.Split(node, ".")[0])
	}

	return buildAndPushHeapsterImage(hostnames)
}

const (
	metricsEndpoint       = "/api/v1/metric-export"
	metricsSchemaEndpoint = "/api/v1/metric-export-schema"
	sinksEndpoint         = "/api/v1/sinks"
)

func getTimeseries(fm kubeFramework, svc *kube_api.Service) ([]*heapster_api.Timeseries, error) {
	body, err := fm.Client().Get().
		Namespace(svc.Namespace).
		Prefix("proxy").
		Resource("services").
		Name(svc.Name).
		Suffix(metricsEndpoint).
		Do().Raw()
	if err != nil {
		return nil, err
	}
	var timeseries []*heapster_api.Timeseries
	if err := json.Unmarshal(body, &timeseries); err != nil {
		glog.V(2).Infof("response body: %v", string(body))
		return nil, err
	}
	return timeseries, nil
}

func getSchema(fm kubeFramework, svc *kube_api.Service) (*heapster_api.TimeseriesSchema, error) {
	body, err := fm.Client().Get().
		Namespace(svc.Namespace).
		Prefix("proxy").
		Resource("services").
		Name(svc.Name).
		Suffix(metricsSchemaEndpoint).
		Do().Raw()
	if err != nil {
		return nil, err
	}
	var timeseriesSchema heapster_api.TimeseriesSchema
	if err := json.Unmarshal(body, &timeseriesSchema); err != nil {
		glog.V(2).Infof("response body: %v", string(body))
		return nil, err
	}
	return &timeseriesSchema, nil
}

var expectedSystemContainers = map[string]struct{}{
	"machine":       struct{}{},
	"kubelet":       struct{}{},
	"kube-proxy":    struct{}{},
	"system":        struct{}{},
	"docker-daemon": struct{}{},
}

func runHeapsterMetricsTest(fm kubeFramework, svc *kube_api.Service, expectedNodes, expectedPods []string) error {
	timeseries, err := getTimeseries(fm, svc)
	if err != nil {
		return err
	}
	if len(timeseries) == 0 {
		return fmt.Errorf("expected non zero timeseries")
	}
	schema, err := getSchema(fm, svc)
	if err != nil {
		return err
	}
	// Build a map of metric names to metric descriptors.
	mdMap := map[string]*heapster_api.MetricDescriptor{}
	for idx := range schema.Metrics {
		mdMap[schema.Metrics[idx].Name] = &schema.Metrics[idx]
	}
	actualPods := map[string]bool{}
	actualNodes := map[string]bool{}
	actualSystemContainers := map[string]map[string]struct{}{}
	for _, ts := range timeseries {
		// Verify the relevant labels are present.
		// All common labels must be present.
		for _, label := range sink_api.CommonLabels() {
			_, exists := ts.Labels[label.Key]
			if !exists {
				return fmt.Errorf("timeseries: %v does not contain common label: %v", ts, label)
			}
		}
		podName, podMetric := ts.Labels[sink_api.LabelPodName.Key]
		if podMetric {
			for _, label := range sink_api.PodLabels() {
				_, exists := ts.Labels[label.Key]
				if !exists {
					return fmt.Errorf("timeseries: %v does not contain pod label: %v", ts, label)
				}
			}
		}
		if podMetric {
			actualPods[podName] = true
		} else {
			if cName, ok := ts.Labels[sink_api.LabelContainerName.Key]; ok {
				hostname, ok := ts.Labels[sink_api.LabelHostname.Key]
				if !ok {
					return fmt.Errorf("hostname label missing on container %+v", ts)
				}

				if cName == cache.NodeContainerName {
					actualNodes[hostname] = true
				}
				if _, exists := expectedSystemContainers[cName]; exists {
					if actualSystemContainers[cName] == nil {
						actualSystemContainers[cName] = map[string]struct{}{}
					}
					actualSystemContainers[cName][hostname] = struct{}{}
				}
			} else {
				return fmt.Errorf("container_name label missing on timeseries - %v", ts)
			}
		}

		for metricName, points := range ts.Metrics {
			md, exists := mdMap[metricName]
			if !exists {
				return fmt.Errorf("unexpected metric %q", metricName)
			}
			for _, point := range points {
				for _, label := range md.Labels {
					_, exists := point.Labels[label.Key]
					if !exists {
						return fmt.Errorf("metric %q point %v does not contain metric label: %v", metricName, point, label)
					}
				}
			}

		}
	}
	// Validate that system containers are running on all the nodes.
	// This test could fail if one of the containers was down while the metrics sample was collected.
	for cName, hosts := range actualSystemContainers {
		for _, host := range expectedNodes {
			if _, ok := hosts[host]; !ok {
				return fmt.Errorf("System container %q not found on host: %q - %v", cName, host, actualSystemContainers)
			}
		}
	}

	if !expectedItemsExist(expectedPods, actualPods) {
		return fmt.Errorf("expected pods don't exist.\nExpected: %v\nActual:%v", expectedPods, actualPods)
	}
	if !expectedItemsExist(expectedNodes, actualNodes) {
		return fmt.Errorf("expected nodes don't exist.\nExpected: %v\nActual:%v", expectedNodes, actualNodes)
	}

	return nil
}

func expectedItemsExist(expectedItems []string, actualItems map[string]bool) bool {
	if len(actualItems) < len(expectedItems) {
		return false
	}
	for _, item := range expectedItems {
		if _, found := actualItems[item]; !found {
			return false
		}
	}
	return true
}

func getSinks(fm kubeFramework, svc *kube_api.Service) ([]string, error) {
	body, err := fm.Client().Get().
		Namespace(svc.Namespace).
		Prefix("proxy").
		Resource("services").
		Name(svc.Name).
		Suffix(sinksEndpoint).
		Do().Raw()
	if err != nil {
		return nil, err
	}
	var sinks []string
	if err := json.Unmarshal(body, &sinks); err != nil {
		glog.V(2).Infof("response body: %v", string(body))
		return nil, err
	}
	return sinks, nil
}

func setSinks(fm kubeFramework, svc *kube_api.Service, sinks []string) error {
	data, err := json.Marshal(sinks)
	if err != nil {
		return err
	}
	return fm.Client().Post().
		Namespace(svc.Namespace).
		Prefix("proxy").
		Resource("services").
		Name(svc.Name).
		Suffix(sinksEndpoint).
		SetHeader("Content-Type", "application/json").
		Body(data).
		Do().Error()
}

func runSinksTest(fm kubeFramework, svc *kube_api.Service) error {
	for _, newSinks := range [...][]string{
		[]string{},
		[]string{
			"gcm",
		},
		[]string{},
	} {
		if err := setSinks(fm, svc, newSinks); err != nil {
			return err
		}
		sinks, err := getSinks(fm, svc)
		if err != nil {
			return err
		}
		if !reflect.DeepEqual(sinks, newSinks) {
			return fmt.Errorf("expected %v sinks, found %v", newSinks, sinks)
		}
	}
	return nil
}

func apiTest(kubeVersion string) error {
	fm, err := newKubeFramework(kubeVersion)
	if err != nil {
		return err
	}
	if err := buildAndPushDockerImages(fm); err != nil {
		return err
	}
	// Create heapster pod and service.
	svc, rc, err := getHeapsterRcAndSvc(fm)
	if err != nil {
		return err
	}
	ns := *namespace
	deleteAll(fm, ns, svc, rc)
	if err = createAll(fm, ns, &svc, &rc); err != nil {
		return err
	}
	defer func() {
		//		deleteAll(fm, ns, svc, rc)
		//	removeHeapsterImage(fm)
	}()
	if err := fm.WaitUntilPodRunning(ns, rc.Spec.Template.Labels, time.Minute); err != nil {
		return err
	}
	if err := fm.WaitUntilServiceActive(svc, time.Minute); err != nil {
		return err
	}
	expectedPods, err := fm.GetPods()
	if err != nil {
		return err
	}
	expectedNodes, err := fm.GetNodes()
	if err != nil {
		return err
	}
	testFuncs := []func() error{
		func() error {
			return runHeapsterMetricsTest(fm, svc, expectedNodes, expectedPods)
		},
		func() error {
			return runSinksTest(fm, svc)
		},
	}
	attempts := *maxRetries
	for {
		var err error
		for _, testFunc := range testFuncs {
			if err = testFunc(); err != nil {
				break
			}
		}
		if *runForever {
			continue
		}
		if err == nil {
			return nil
		}
		if attempts == 0 {
			return err
		}
		attempts--
		time.Sleep(time.Second)
	}
}

func runApiTest() error {
	tempDir, err := ioutil.TempDir("", "deploy")
	if err != nil {
		return nil
	}
	defer os.RemoveAll(tempDir)
	if *kubeVersions == "" {
		return apiTest("")
	}
	kubeVersionsList := strings.Split(*kubeVersions, ",")
	for _, kubeVersion := range kubeVersionsList {
		if err := apiTest(kubeVersion); err != nil {
			return err
		}
	}
	return nil
}

func TestHeapster(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping heapster kubernetes integration test.")
	}
	require.NoError(t, runApiTest())
}
<|endoftext|>