<commit_before>package controller

import (
	"fmt"
	"sync"
	"time"

	"github.com/golang/glog"
	"github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	"github.com/sapcc/kubernikus/pkg/client/openstack"
	"github.com/sapcc/kubernikus/pkg/templates"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
)

type LaunchControl struct {
	Factories
	Clients
	queue workqueue.RateLimitingInterface
}

func NewLaunchController(factories Factories, clients Clients) *LaunchControl {
	launchctl := &LaunchControl{
		Factories: factories,
		Clients:   clients,
		queue:     workqueue.NewRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(5*time.Second, 300*time.Second)),
	}

	launchctl.Factories.Kubernikus.Kubernikus().V1().Klusters().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			if err == nil {
				launchctl.queue.Add(key)
			}
		},
		UpdateFunc: func(old interface{}, new interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(new)
			if err == nil {
				launchctl.queue.Add(key)
			}
		},
		DeleteFunc: func(obj interface{}) {
			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			if err == nil {
				launchctl.queue.Add(key)
			}
		},
	})

	return launchctl
}

func (launchctl *LaunchControl) Run(threadiness int, stopCh <-chan struct{}, wg *sync.WaitGroup) {
	defer launchctl.queue.ShutDown()
	defer wg.Done()
	wg.Add(1)
	glog.Infof(`Starting LaunchControl with %d "threads"`, threadiness)

	for i := 0; i < threadiness; i++ {
		go wait.Until(launchctl.runWorker, time.Second, stopCh)
	}

	ticker := time.NewTicker(KLUSTER_RECHECK_INTERVAL)
	go func() {
		for {
			select {
			case <-ticker.C:
				glog.V(2).Infof("Running periodic recheck. Queuing all Klusters...")

				klusters, err := launchctl.Factories.Kubernikus.Kubernikus().V1().Klusters().Lister().List(labels.Everything())
				if err != nil {
					glog.Errorf("Couldn't run periodic recheck. Listing klusters failed: %v", err)
				}

				for _, kluster := range klusters {
					key, err := cache.MetaNamespaceKeyFunc(kluster)
					if err == nil {
						launchctl.queue.Add(key)
					}
				}
			case <-stopCh:
				ticker.Stop()
				return
			}
		}
	}()

	<-stopCh
}

func (launchctl *LaunchControl) runWorker() {
	for launchctl.processNextWorkItem() {
	}
}

func (launchctl *LaunchControl) processNextWorkItem() bool {
	key, quit := launchctl.queue.Get()
	if quit {
		return false
	}
	defer launchctl.queue.Done(key)

	// Invoke the method containing the business logic
	err := launchctl.reconcile(key.(string))
	launchctl.handleErr(err, key)
	return true
}

func (launchctl *LaunchControl) requeue(kluster *v1.Kluster) {
	key, err := cache.MetaNamespaceKeyFunc(kluster)
	if err == nil {
		launchctl.queue.AddAfter(key, 5*time.Second)
	}
}

func (launchctl *LaunchControl) reconcile(key string) error {
	obj, exists, err := launchctl.Factories.Kubernikus.Kubernikus().V1().Klusters().Informer().GetIndexer().GetByKey(key)
	if err != nil {
		return fmt.Errorf("Failed to fetch key %s from cache: %s", key, err)
	}
	if !exists {
		glog.Infof("[%v] Kluster deleted in the meantime", key)
		return nil
	}

	kluster := obj.(*v1.Kluster)
	glog.V(5).Infof("[%v] Reconciling", kluster.Name)

	if !(kluster.Status.State == v1.KlusterReady || kluster.Status.State == v1.KlusterTerminating) {
		return fmt.Errorf("[%v] Kluster is not yet ready. Requeuing.", kluster.Name)
	}

	for _, pool := range kluster.Spec.NodePools {
		err := launchctl.syncPool(kluster, &pool)
		if err != nil {
			return err
		}
	}

	return nil
}

func (launchctl *LaunchControl) syncPool(kluster *v1.Kluster, pool *v1.NodePool) error {
	nodes, err := launchctl.Clients.Openstack.GetNodes(kluster, pool)
	if err != nil {
		return fmt.Errorf("[%v] Couldn't list nodes for pool %v: %v", kluster.Name, pool.Name, err)
	}

	if kluster.Status.State == v1.KlusterTerminating && toBeTerminated(nodes) > 0 {
		glog.V(3).Infof("[%v] Kluster is terminating. Terminating Nodes for Pool %v.", kluster.Name, pool.Name)
		for _, node := range nodes {
			err := launchctl.terminateNode(kluster, node.ID)
			if err != nil {
				return err
			}
		}

		return nil
	}

	ready := ready(nodes)

	switch {
	case ready < pool.Size:
		glog.V(3).Infof("[%v] Pool %v: Running %v/%v. Too few nodes. Need to spawn more.", kluster.Name, pool.Name, ready, pool.Size)
		return launchctl.createNode(kluster, pool)
	case ready > pool.Size:
		glog.V(3).Infof("[%v] Pool %v: Running %v/%v. Too many nodes. Need to delete some.", kluster.Name, pool.Name, ready, pool.Size)
		return launchctl.terminateNode(kluster, nodes[0].ID)
	case ready == pool.Size:
		glog.V(3).Infof("[%v] Pool %v: Running %v/%v. All good. Doing nothing.", kluster.Name, pool.Name, ready, pool.Size)
	}

	return nil
}

func (launchctl *LaunchControl) createNode(kluster *v1.Kluster, pool *v1.NodePool) error {
	glog.V(2).Infof("[%v] Pool %v: Creating new node", kluster.Name, pool.Name)

	userdata, err := templates.Ignition.GenerateNode(kluster, launchctl.Clients.Kubernetes)
	if err != nil {
		glog.Errorf("Ignition userdata couldn't be generated: %v", err)
	}

	id, err := launchctl.Clients.Openstack.CreateNode(kluster, pool, userdata)
	if err != nil {
		return err
	}

	glog.V(2).Infof("[%v]Pool %v: Created node %v.", kluster.Name, pool.Name, id)

	launchctl.requeue(kluster)
	return nil
}

func (launchctl *LaunchControl) terminateNode(kluster *v1.Kluster, id string) error {
	err := launchctl.Clients.Openstack.DeleteNode(kluster, id)
	if err != nil {
		return err
	}

	launchctl.requeue(kluster)
	return nil
}

func (launchctl *LaunchControl) handleErr(err error, key interface{}) {
	if err == nil {
		// Forget about the #AddRateLimited history of the key on every successful synchronization.
		// This ensures that future processing of updates for this key is not delayed because of
		// an outdated error history.
		launchctl.queue.Forget(key)
		return
	}

	// This controller retries 5 times if something goes wrong. After that, it stops trying.
	if launchctl.queue.NumRequeues(key) < 5 {
		glog.V(6).Infof("Error while managing nodes for kluster %q: %v", key, err)

		// Re-enqueue the key rate limited. Based on the rate limiter on the
		// queue and the re-enqueue history, the key will be processed later again.
		launchctl.queue.AddRateLimited(key)
		return
	}

	launchctl.queue.Forget(key)
	glog.V(5).Infof("[%v] Dropping out of the queue. Too many retries...", key)
}

func ready(nodes []openstack.Node) int {
	ready := 0
	for _, n := range nodes {
		if n.Ready() {
			ready = ready + 1
		}
	}

	return ready
}

func toBeTerminated(nodes []openstack.Node) int {
	toBeTerminated := 0
	for _, n := range nodes {
		if n.TaskState == "deleting" {
			continue
		}

		toBeTerminated = toBeTerminated + 1
	}

	return toBeTerminated
}
<commit_msg>exit early before more nodes are being spawned<commit_after>package controller

import (
	"fmt"
	"sync"
	"time"

	"github.com/golang/glog"
	"github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	"github.com/sapcc/kubernikus/pkg/client/openstack"
	"github.com/sapcc/kubernikus/pkg/templates"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
)

type LaunchControl struct {
	Factories
	Clients
	queue workqueue.RateLimitingInterface
}

func NewLaunchController(factories Factories, clients Clients) *LaunchControl {
	launchctl := &LaunchControl{
		Factories: factories,
		Clients:   clients,
		queue:     workqueue.NewRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(5*time.Second, 300*time.Second)),
	}

	launchctl.Factories.Kubernikus.Kubernikus().V1().Klusters().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			if err == nil {
				launchctl.queue.Add(key)
			}
		},
		UpdateFunc: func(old interface{}, new interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(new)
			if err == nil {
				launchctl.queue.Add(key)
			}
		},
		DeleteFunc: func(obj interface{}) {
			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			if err == nil {
				launchctl.queue.Add(key)
			}
		},
	})

	return launchctl
}

func (launchctl *LaunchControl) Run(threadiness int, stopCh <-chan struct{}, wg *sync.WaitGroup) {
	defer launchctl.queue.ShutDown()
	defer wg.Done()
	wg.Add(1)
	glog.Infof(`Starting LaunchControl with %d "threads"`, threadiness)

	for i := 0; i < threadiness; i++ {
		go wait.Until(launchctl.runWorker, time.Second, stopCh)
	}

	ticker := time.NewTicker(KLUSTER_RECHECK_INTERVAL)
	go func() {
		for {
			select {
			case <-ticker.C:
				glog.V(2).Infof("Running periodic recheck. Queuing all Klusters...")

				klusters, err := launchctl.Factories.Kubernikus.Kubernikus().V1().Klusters().Lister().List(labels.Everything())
				if err != nil {
					glog.Errorf("Couldn't run periodic recheck. Listing klusters failed: %v", err)
				}

				for _, kluster := range klusters {
					key, err := cache.MetaNamespaceKeyFunc(kluster)
					if err == nil {
						launchctl.queue.Add(key)
					}
				}
			case <-stopCh:
				ticker.Stop()
				return
			}
		}
	}()

	<-stopCh
}

func (launchctl *LaunchControl) runWorker() {
	for launchctl.processNextWorkItem() {
	}
}

func (launchctl *LaunchControl) processNextWorkItem() bool {
	key, quit := launchctl.queue.Get()
	if quit {
		return false
	}
	defer launchctl.queue.Done(key)

	// Invoke the method containing the business logic
	err := launchctl.reconcile(key.(string))
	launchctl.handleErr(err, key)
	return true
}

func (launchctl *LaunchControl) requeue(kluster *v1.Kluster) {
	key, err := cache.MetaNamespaceKeyFunc(kluster)
	if err == nil {
		launchctl.queue.AddAfter(key, 5*time.Second)
	}
}

func (launchctl *LaunchControl) reconcile(key string) error {
	obj, exists, err := launchctl.Factories.Kubernikus.Kubernikus().V1().Klusters().Informer().GetIndexer().GetByKey(key)
	if err != nil {
		return fmt.Errorf("Failed to fetch key %s from cache: %s", key, err)
	}
	if !exists {
		glog.Infof("[%v] Kluster deleted in the meantime", key)
		return nil
	}

	kluster := obj.(*v1.Kluster)
	glog.V(5).Infof("[%v] Reconciling", kluster.Name)

	if !(kluster.Status.State == v1.KlusterReady || kluster.Status.State == v1.KlusterTerminating) {
		return fmt.Errorf("[%v] Kluster is not yet ready. Requeuing.", kluster.Name)
	}

	for _, pool := range kluster.Spec.NodePools {
		err := launchctl.syncPool(kluster, &pool)
		if err != nil {
			return err
		}
	}

	return nil
}

func (launchctl *LaunchControl) syncPool(kluster *v1.Kluster, pool *v1.NodePool) error {
	nodes, err := launchctl.Clients.Openstack.GetNodes(kluster, pool)
	if err != nil {
		return fmt.Errorf("[%v] Couldn't list nodes for pool %v: %v", kluster.Name, pool.Name, err)
	}

	if kluster.Status.State == v1.KlusterTerminating {
		if toBeTerminated(nodes) > 0 {
			glog.V(3).Infof("[%v] Kluster is terminating. Terminating Nodes for Pool %v.", kluster.Name, pool.Name)
			for _, node := range nodes {
				err := launchctl.terminateNode(kluster, node.ID)
				if err != nil {
					return err
				}
			}
		}

		return nil
	}

	ready := ready(nodes)

	switch {
	case ready < pool.Size:
		glog.V(3).Infof("[%v] Pool %v: Running %v/%v. Too few nodes. Need to spawn more.", kluster.Name, pool.Name, ready, pool.Size)
		return launchctl.createNode(kluster, pool)
	case ready > pool.Size:
		glog.V(3).Infof("[%v] Pool %v: Running %v/%v. Too many nodes. Need to delete some.", kluster.Name, pool.Name, ready, pool.Size)
		return launchctl.terminateNode(kluster, nodes[0].ID)
	case ready == pool.Size:
		glog.V(3).Infof("[%v] Pool %v: Running %v/%v. All good. Doing nothing.", kluster.Name, pool.Name, ready, pool.Size)
	}

	return nil
}

func (launchctl *LaunchControl) createNode(kluster *v1.Kluster, pool *v1.NodePool) error {
	glog.V(2).Infof("[%v] Pool %v: Creating new node", kluster.Name, pool.Name)

	userdata, err := templates.Ignition.GenerateNode(kluster, launchctl.Clients.Kubernetes)
	if err != nil {
		glog.Errorf("Ignition userdata couldn't be generated: %v", err)
	}

	id, err := launchctl.Clients.Openstack.CreateNode(kluster, pool, userdata)
	if err != nil {
		return err
	}

	glog.V(2).Infof("[%v]Pool %v: Created node %v.", kluster.Name, pool.Name, id)

	launchctl.requeue(kluster)
	return nil
}

func (launchctl *LaunchControl) terminateNode(kluster *v1.Kluster, id string) error {
	err := launchctl.Clients.Openstack.DeleteNode(kluster, id)
	if err != nil {
		return err
	}

	launchctl.requeue(kluster)
	return nil
}

func (launchctl *LaunchControl) handleErr(err error, key interface{}) {
	if err == nil {
		// Forget about the #AddRateLimited history of the key on every successful synchronization.
		// This ensures that future processing of updates for this key is not delayed because of
		// an outdated error history.
		launchctl.queue.Forget(key)
		return
	}

	// This controller retries 5 times if something goes wrong. After that, it stops trying.
	if launchctl.queue.NumRequeues(key) < 5 {
		glog.V(6).Infof("Error while managing nodes for kluster %q: %v", key, err)

		// Re-enqueue the key rate limited. Based on the rate limiter on the
		// queue and the re-enqueue history, the key will be processed later again.
		launchctl.queue.AddRateLimited(key)
		return
	}

	launchctl.queue.Forget(key)
	glog.V(5).Infof("[%v] Dropping out of the queue. Too many retries...", key)
}

func ready(nodes []openstack.Node) int {
	ready := 0
	for _, n := range nodes {
		if n.Ready() {
			ready = ready + 1
		}
	}

	return ready
}

func toBeTerminated(nodes []openstack.Node) int {
	toBeTerminated := 0
	for _, n := range nodes {
		if n.TaskState == "deleting" {
			continue
		}

		toBeTerminated = toBeTerminated + 1
	}

	return toBeTerminated
}
<|endoftext|>


<commit_before>package ingester

import (
	"fmt"
	"io"
	"os"
	"time"

	"github.com/cortexproject/cortex/pkg/ingester/client"
	"github.com/cortexproject/cortex/pkg/ring"
	"github.com/cortexproject/cortex/pkg/util"
	"github.com/go-kit/kit/log/level"
	"github.com/grafana/loki/pkg/logproto"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/weaveworks/common/user"
	"golang.org/x/net/context"
)

var (
	sentChunks = promauto.NewCounter(prometheus.CounterOpts{
		Name: "loki_ingester_sent_chunks",
		Help: "The total number of chunks sent by this ingester whilst leaving.",
	})
	receivedChunks = promauto.NewCounter(prometheus.CounterOpts{
		Name: "loki_ingester_received_chunks",
		Help: "The total number of chunks received by this ingester whilst joining.",
	})
)

// TransferChunks receives all chunks from another ingester. The Ingester
// must be in PENDING state or else the call will fail.
func (i *Ingester) TransferChunks(stream logproto.Ingester_TransferChunksServer) error {
	// Entry JOINING state (only valid from PENDING)
	if err := i.lifecycler.ChangeState(stream.Context(), ring.JOINING); err != nil {
		return err
	}

	// The ingesters state effectively works as a giant mutex around this
	// whole method, and as such we have to ensure we unlock the mutex.
	defer func() {
		state := i.lifecycler.GetState()
		if i.lifecycler.GetState() == ring.ACTIVE {
			return
		}

		level.Error(util.Logger).Log("msg", "TransferChunks failed, not in ACTIVE state.", "state", state)

		// Enter PENDING state (only valid from JOINING)
		if i.lifecycler.GetState() == ring.JOINING {
			if err := i.lifecycler.ChangeState(stream.Context(), ring.PENDING); err != nil {
				level.Error(util.Logger).Log("msg", "error rolling back failed TransferChunks", "err", err)
				os.Exit(1)
			}
		}
	}()

	fromIngesterID := ""
	seriesReceived := 0

	for {
		chunkSet, err := stream.Recv()
		if err == io.EOF {
			break
		} else if err != nil {
			return err
		}

		// We can't send "extra" fields with a streaming call, so we repeat
		// chunkSet.FromIngesterId and assume it is the same every time around
		// this loop.
		if fromIngesterID == "" {
			fromIngesterID = chunkSet.FromIngesterId
			level.Info(util.Logger).Log("msg", "processing TransferChunks request", "from_ingester", fromIngesterID)
		}

		userCtx := user.InjectOrgID(stream.Context(), chunkSet.UserId)

		lbls := []client.LabelAdapter{}
		for _, lbl := range chunkSet.Labels {
			lbls = append(lbls, client.LabelAdapter{Name: lbl.Name, Value: lbl.Value})
		}

		instance := i.getOrCreateInstance(chunkSet.UserId)
		for _, chunk := range chunkSet.Chunks {
			if err := instance.consumeChunk(userCtx, lbls, chunk); err != nil {
				return err
			}
		}

		seriesReceived++
		receivedChunks.Add(float64(len(chunkSet.Chunks)))
	}

	if seriesReceived == 0 {
		level.Error(util.Logger).Log("msg", "received TransferChunks request with no series", "from_ingester", fromIngesterID)
		return fmt.Errorf("no series")
	} else if fromIngesterID == "" {
		level.Error(util.Logger).Log("msg", "received TransferChunks request with no ID from ingester")
		return fmt.Errorf("no ingester id")
	}

	if err := i.lifecycler.ClaimTokensFor(stream.Context(), fromIngesterID); err != nil {
		return err
	}

	if err := i.lifecycler.ChangeState(stream.Context(), ring.ACTIVE); err != nil {
		return err
	}

	// Close the stream last, as this is what tells the "from" ingester that
	// it's OK to shut down.
	if err := stream.SendAndClose(&logproto.TransferChunksResponse{}); err != nil {
		level.Error(util.Logger).Log("msg", "Error closing TransferChunks stream", "from_ingester", fromIngesterID, "err", err)
		return err
	}
	level.Info(util.Logger).Log("msg", "Successfully transferred chunks", "from_ingester", fromIngesterID, "series_received", seriesReceived)
	return nil
}

// StopIncomingRequests implements ring.Lifecycler.
func (i *Ingester) StopIncomingRequests() {
	i.instancesMtx.Lock()
	defer i.instancesMtx.Unlock()
	i.readonly = true
}

// TransferOut implements ring.Lifecycler.
func (i *Ingester) TransferOut(ctx context.Context) error {
	backoff := util.NewBackoff(ctx, util.BackoffConfig{
		MinBackoff: 100 * time.Millisecond,
		MaxBackoff: 5 * time.Second,
		MaxRetries: i.cfg.MaxTransferRetries,
	})

	for backoff.Ongoing() {
		err := i.transferOut(ctx)
		if err == nil {
			return nil
		}

		level.Error(util.Logger).Log("msg", "transfer failed", "err", err)
		backoff.Wait()
	}

	return backoff.Err()
}

func (i *Ingester) transferOut(ctx context.Context) error {
	targetIngester, err := i.findTransferTarget(ctx)
	if err != nil {
		return fmt.Errorf("cannot find ingester to transfer chunks to: %v", err)
	}

	level.Info(util.Logger).Log("msg", "sending chunks", "to_ingester", targetIngester.Addr)
	c, err := i.cfg.ingesterClientFactory(i.clientConfig, targetIngester.Addr)
	if err != nil {
		return err
	}
	if c, ok := c.(io.Closer); ok {
		defer c.Close()
	}
	ic := c.(logproto.IngesterClient)

	ctx = user.InjectOrgID(ctx, "-1")
	stream, err := ic.TransferChunks(ctx)
	if err != nil {
		return errors.Wrap(err, "TransferChunks")
	}

	for instanceID, inst := range i.instances {
		for _, istream := range inst.streams {
			chunks := make([]*logproto.Chunk, 0, len(istream.chunks))

			for _, c := range istream.chunks {
				bb, err := c.chunk.Bytes()
				if err != nil {
					return err
				}

				chunks = append(chunks, &logproto.Chunk{
					Data: bb,
				})
			}

			lbls := []*logproto.LabelPair{}
			for _, lbl := range istream.labels {
				lbls = append(lbls, &logproto.LabelPair{Name: lbl.Name, Value: lbl.Value})
			}

			err := stream.Send(&logproto.TimeSeriesChunk{
				Chunks:         chunks,
				UserId:         instanceID,
				Labels:         lbls,
				FromIngesterId: i.lifecycler.ID,
			})
			if err != nil {
				level.Error(util.Logger).Log("msg", "failed sending stream's chunks to ingester", "to_ingester", targetIngester.Addr, "err", err)
				return err
			}

			sentChunks.Add(float64(len(chunks)))
		}
	}

	_, err = stream.CloseAndRecv()
	if err != nil {
		return errors.Wrap(err, "CloseAndRecv")
	}

	for _, flushQueue := range i.flushQueues {
		flushQueue.DiscardAndClose()
	}
	i.flushQueuesDone.Wait()

	level.Info(util.Logger).Log("msg", "successfully sent chunks", "to_ingester", targetIngester.Addr)
	return nil
}

// findTransferTarget finds an ingester in a PENDING state to use for transferring
// chunks to.
func (i *Ingester) findTransferTarget(ctx context.Context) (*ring.IngesterDesc, error) {
	ringDesc, err := i.lifecycler.KVStore.Get(ctx, ring.ConsulKey)
	if err != nil {
		return nil, err
	}

	ingesters := ringDesc.(*ring.Desc).FindIngestersByState(ring.PENDING)
	if len(ingesters) == 0 {
		return nil, fmt.Errorf("no pending ingesters")
	}

	return &ingesters[0], nil
}
<commit_msg>ingester: log error if closing client after transfer fails<commit_after>package ingester

import (
	"fmt"
	"io"
	"os"
	"time"

	"github.com/cortexproject/cortex/pkg/ingester/client"
	"github.com/cortexproject/cortex/pkg/ring"
	"github.com/cortexproject/cortex/pkg/util"
	"github.com/go-kit/kit/log/level"
	"github.com/grafana/loki/pkg/helpers"
	"github.com/grafana/loki/pkg/logproto"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/weaveworks/common/user"
	"golang.org/x/net/context"
)

var (
	sentChunks = promauto.NewCounter(prometheus.CounterOpts{
		Name: "loki_ingester_sent_chunks",
		Help: "The total number of chunks sent by this ingester whilst leaving.",
	})
	receivedChunks = promauto.NewCounter(prometheus.CounterOpts{
		Name: "loki_ingester_received_chunks",
		Help: "The total number of chunks received by this ingester whilst joining.",
	})
)

// TransferChunks receives all chunks from another ingester. The Ingester
// must be in PENDING state or else the call will fail.
func (i *Ingester) TransferChunks(stream logproto.Ingester_TransferChunksServer) error {
	// Entry JOINING state (only valid from PENDING)
	if err := i.lifecycler.ChangeState(stream.Context(), ring.JOINING); err != nil {
		return err
	}

	// The ingesters state effectively works as a giant mutex around this
	// whole method, and as such we have to ensure we unlock the mutex.
	defer func() {
		state := i.lifecycler.GetState()
		if i.lifecycler.GetState() == ring.ACTIVE {
			return
		}

		level.Error(util.Logger).Log("msg", "TransferChunks failed, not in ACTIVE state.", "state", state)

		// Enter PENDING state (only valid from JOINING)
		if i.lifecycler.GetState() == ring.JOINING {
			if err := i.lifecycler.ChangeState(stream.Context(), ring.PENDING); err != nil {
				level.Error(util.Logger).Log("msg", "error rolling back failed TransferChunks", "err", err)
				os.Exit(1)
			}
		}
	}()

	fromIngesterID := ""
	seriesReceived := 0

	for {
		chunkSet, err := stream.Recv()
		if err == io.EOF {
			break
		} else if err != nil {
			return err
		}

		// We can't send "extra" fields with a streaming call, so we repeat
		// chunkSet.FromIngesterId and assume it is the same every time around
		// this loop.
		if fromIngesterID == "" {
			fromIngesterID = chunkSet.FromIngesterId
			level.Info(util.Logger).Log("msg", "processing TransferChunks request", "from_ingester", fromIngesterID)
		}

		userCtx := user.InjectOrgID(stream.Context(), chunkSet.UserId)

		lbls := []client.LabelAdapter{}
		for _, lbl := range chunkSet.Labels {
			lbls = append(lbls, client.LabelAdapter{Name: lbl.Name, Value: lbl.Value})
		}

		instance := i.getOrCreateInstance(chunkSet.UserId)
		for _, chunk := range chunkSet.Chunks {
			if err := instance.consumeChunk(userCtx, lbls, chunk); err != nil {
				return err
			}
		}

		seriesReceived++
		receivedChunks.Add(float64(len(chunkSet.Chunks)))
	}

	if seriesReceived == 0 {
		level.Error(util.Logger).Log("msg", "received TransferChunks request with no series", "from_ingester", fromIngesterID)
		return fmt.Errorf("no series")
	} else if fromIngesterID == "" {
		level.Error(util.Logger).Log("msg", "received TransferChunks request with no ID from ingester")
		return fmt.Errorf("no ingester id")
	}

	if err := i.lifecycler.ClaimTokensFor(stream.Context(), fromIngesterID); err != nil {
		return err
	}

	if err := i.lifecycler.ChangeState(stream.Context(), ring.ACTIVE); err != nil {
		return err
	}

	// Close the stream last, as this is what tells the "from" ingester that
	// it's OK to shut down.
	if err := stream.SendAndClose(&logproto.TransferChunksResponse{}); err != nil {
		level.Error(util.Logger).Log("msg", "Error closing TransferChunks stream", "from_ingester", fromIngesterID, "err", err)
		return err
	}
	level.Info(util.Logger).Log("msg", "Successfully transferred chunks", "from_ingester", fromIngesterID, "series_received", seriesReceived)
	return nil
}

// StopIncomingRequests implements ring.Lifecycler.
func (i *Ingester) StopIncomingRequests() {
	i.instancesMtx.Lock()
	defer i.instancesMtx.Unlock()
	i.readonly = true
}

// TransferOut implements ring.Lifecycler.
func (i *Ingester) TransferOut(ctx context.Context) error {
	backoff := util.NewBackoff(ctx, util.BackoffConfig{
		MinBackoff: 100 * time.Millisecond,
		MaxBackoff: 5 * time.Second,
		MaxRetries: i.cfg.MaxTransferRetries,
	})

	for backoff.Ongoing() {
		err := i.transferOut(ctx)
		if err == nil {
			return nil
		}

		level.Error(util.Logger).Log("msg", "transfer failed", "err", err)
		backoff.Wait()
	}

	return backoff.Err()
}

func (i *Ingester) transferOut(ctx context.Context) error {
	targetIngester, err := i.findTransferTarget(ctx)
	if err != nil {
		return fmt.Errorf("cannot find ingester to transfer chunks to: %v", err)
	}

	level.Info(util.Logger).Log("msg", "sending chunks", "to_ingester", targetIngester.Addr)
	c, err := i.cfg.ingesterClientFactory(i.clientConfig, targetIngester.Addr)
	if err != nil {
		return err
	}
	if c, ok := c.(io.Closer); ok {
		defer helpers.LogError("closing client", c.Close)
	}
	ic := c.(logproto.IngesterClient)

	ctx = user.InjectOrgID(ctx, "-1")
	stream, err := ic.TransferChunks(ctx)
	if err != nil {
		return errors.Wrap(err, "TransferChunks")
	}

	for instanceID, inst := range i.instances {
		for _, istream := range inst.streams {
			chunks := make([]*logproto.Chunk, 0, len(istream.chunks))

			for _, c := range istream.chunks {
				bb, err := c.chunk.Bytes()
				if err != nil {
					return err
				}

				chunks = append(chunks, &logproto.Chunk{
					Data: bb,
				})
			}

			lbls := []*logproto.LabelPair{}
			for _, lbl := range istream.labels {
				lbls = append(lbls, &logproto.LabelPair{Name: lbl.Name, Value: lbl.Value})
			}

			err := stream.Send(&logproto.TimeSeriesChunk{
				Chunks:         chunks,
				UserId:         instanceID,
				Labels:         lbls,
				FromIngesterId: i.lifecycler.ID,
			})
			if err != nil {
				level.Error(util.Logger).Log("msg", "failed sending stream's chunks to ingester", "to_ingester", targetIngester.Addr, "err", err)
				return err
			}

			sentChunks.Add(float64(len(chunks)))
		}
	}

	_, err = stream.CloseAndRecv()
	if err != nil {
		return errors.Wrap(err, "CloseAndRecv")
	}

	for _, flushQueue := range i.flushQueues {
		flushQueue.DiscardAndClose()
	}
	i.flushQueuesDone.Wait()

	level.Info(util.Logger).Log("msg", "successfully sent chunks", "to_ingester", targetIngester.Addr)
	return nil
}

// findTransferTarget finds an ingester in a PENDING state to use for transferring
// chunks to.
func (i *Ingester) findTransferTarget(ctx context.Context) (*ring.IngesterDesc, error) {
	ringDesc, err := i.lifecycler.KVStore.Get(ctx, ring.ConsulKey)
	if err != nil {
		return nil, err
	}

	ingesters := ringDesc.(*ring.Desc).FindIngestersByState(ring.PENDING)
	if len(ingesters) == 0 {
		return nil, fmt.Errorf("no pending ingesters")
	}

	return &ingesters[0], nil
}
<|endoftext|>


<commit_before>package util

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"errors"
	"fmt"
	"math"
	"math/big"
	"net"
	"time"

	certutil "k8s.io/client-go/util/cert"

	"github.com/sapcc/kubernikus/pkg/api/models"
	v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
)

const (
	//by default we generate certs with 2 year validity
	defaultCertValidity = 2 * time.Hour * 24 * 365
	//out CAs are valid for 10 years
	caValidity = 10 * time.Hour * 24 * 365
)

type Bundle struct {
	Certificate *x509.Certificate
	PrivateKey  *rsa.PrivateKey
}

func NewBundle(key, cert []byte) (*Bundle, error) {
	certificates, err := certutil.ParseCertsPEM(cert)
	if err != nil {
		return nil, err
	}
	if len(certificates) < 1 {
		return nil, errors.New("No certificates found")
	}
	k, err := certutil.ParsePrivateKeyPEM(key)
	if err != nil {
		return nil, err
	}
	rsaKey, isRSAKey := k.(*rsa.PrivateKey)
	if !isRSAKey {
		return nil, errors.New("Key does not seem to be of type RSA")
	}

	return &Bundle{PrivateKey: rsaKey, Certificate: certificates[0]}, nil
}

type Config struct {
	Sign               string
	Organization       []string
	OrganizationalUnit []string
	Province           []string
	Locality           []string
	AltNames           AltNames
	Usages             []x509.ExtKeyUsage
	ValidFor           time.Duration
}

type AltNames struct {
	DNSNames []string
	IPs      []net.IP
}

func (ca Bundle) Sign(config Config) (*Bundle, error) {
	if !ca.Certificate.IsCA {
		return nil, errors.New("You can't use this certificate for signing. It's not a CA...")
	}

	if config.ValidFor == 0 {
		config.ValidFor = defaultCertValidity
	}

	key, _ := certutil.NewPrivateKey()
	serial, _ := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))

	certTmpl := x509.Certificate{
		Subject: pkix.Name{
			CommonName:         config.Sign,
			Organization:       config.Organization,
			OrganizationalUnit: config.OrganizationalUnit,
			Province:           config.Province,
			Locality:           config.Locality,
		},
		DNSNames:     config.AltNames.DNSNames,
		IPAddresses:  config.AltNames.IPs,
		SerialNumber: serial,
		NotBefore:    ca.Certificate.NotBefore,
		NotAfter:     time.Now().Add(config.ValidFor).UTC(),
		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  config.Usages,
	}

	certDERBytes, _ := x509.CreateCertificate(rand.Reader, &certTmpl, ca.Certificate, key.Public(), ca.PrivateKey)

	cert, _ := x509.ParseCertificate(certDERBytes)

	return &Bundle{cert, key}, nil
}

type CertificateFactory struct {
	kluster *v1.Kluster
	store   *v1.Certificates
	domain  string
}

func NewCertificateFactory(kluster *v1.Kluster, store *v1.Certificates, domain string) *CertificateFactory {
	return &CertificateFactory{kluster, store, domain}
}

func (cf *CertificateFactory) Ensure() error {
	apiServiceIP, err := cf.kluster.ApiServiceIP()
	if err != nil {
		return err
	}

	apiIP := net.ParseIP(cf.kluster.Spec.AdvertiseAddress)
	if apiIP == nil {
		return fmt.Errorf("Failed to parse clusters advertise address: %s", cf.kluster.Spec.AdvertiseAddress)
	}

	etcdClientsCA, err := loadOrCreateCA(cf.kluster, "Etcd Clients", &cf.store.EtcdClientsCACertificate, &cf.store.EtcdClientsCAPrivateKey)
	if err != nil {
		return err
	}
	_, err = loadOrCreateCA(cf.kluster, "Etcd Peers", &cf.store.EtcdPeersCACertificate, &cf.store.EtcdPeersCAPrivateKey)
	if err != nil {
		return err
	}
	apiserverClientsCA, err := loadOrCreateCA(cf.kluster, "ApiServer Clients", &cf.store.ApiserverClientsCACertifcate, &cf.store.ApiserverClientsCAPrivateKey)
	if err != nil {
		return err
	}
	_, err = loadOrCreateCA(cf.kluster, "ApiServer Nodes", &cf.store.ApiserverNodesCACertificate, &cf.store.ApiserverNodesCAPrivateKey)
	if err != nil {
		return err
	}
	kubeletClientsCA, err := loadOrCreateCA(cf.kluster, "Kubelet Clients", &cf.store.KubeletClientsCACertificate, &cf.store.KubeletClientsCAPrivateKey)
	if err != nil {
		return err
	}
	tlsCA, err := loadOrCreateCA(cf.kluster, "TLS", &cf.store.TLSCACertificate, &cf.store.TLSCAPrivateKey)
	if err != nil {
		return err
	}
	aggregationCA, err := loadOrCreateCA(cf.kluster, "Aggregation", &cf.store.AggregationCACertificate, &cf.store.AggregationCAPrivateKey)
	if err != nil {
		return err
	}

	if err := ensureClientCertificate(etcdClientsCA, "apiserver", nil, &cf.store.EtcdClientsApiserverCertificate, &cf.store.EtcdClientsApiserverPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(apiserverClientsCA, "cluster-admin", []string{"system:masters"}, &cf.store.ApiserverClientsClusterAdminCertificate, &cf.store.ApiserverClientsClusterAdminPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(apiserverClientsCA, "system:kube-controller-manager", nil, &cf.store.ApiserverClientsKubeControllerManagerCertificate, &cf.store.ApiserverClientsKubeControllerManagerPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(apiserverClientsCA, "system:kube-proxy", nil, &cf.store.ApiserverClientsKubeProxyCertificate, &cf.store.ApiserverClientsKubeProxyPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(apiserverClientsCA, "system:kube-scheduler", nil, &cf.store.ApiserverClientsKubeSchedulerCertificate, &cf.store.ApiserverClientsKubeSchedulerPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(apiserverClientsCA, "kubernikus:wormhole", nil, &cf.store.ApiserverClientsKubernikusWormholeCertificate, &cf.store.ApiserverClientsKubernikusWormholePrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(kubeletClientsCA, "apiserver", nil, &cf.store.KubeletClientsApiserverCertificate, &cf.store.KubeletClientsApiserverPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(aggregationCA, "aggregator", nil, &cf.store.AggregationAggregatorCertificate, &cf.store.AggregationAggregatorPrivateKey); err != nil {
		return err
	}

	if err := ensureServerCertificate(tlsCA, "apiserver",
		[]string{"kubernetes", "kubernetes.default", "kubernetes.default.svc", "apiserver", cf.kluster.Name, fmt.Sprintf("%s.%s", cf.kluster.Name, cf.kluster.Namespace), fmt.Sprintf("%v.%v", cf.kluster.Name, cf.domain)},
		[]net.IP{net.IPv4(127, 0, 0, 1), apiServiceIP, apiIP},
		&cf.store.TLSApiserverCertificate,
		&cf.store.TLSApiserverPrivateKey); err != nil {
		return err
	}
	if err := ensureServerCertificate(tlsCA, "wormhole",
		[]string{fmt.Sprintf("%v-wormhole.%v", cf.kluster.Name, cf.domain)},
		nil,
		&cf.store.TLSWormholeCertificate,
		&cf.store.TLSWormholePrivateKey); err != nil {
		return err
	}

	return nil
}

func (cf *CertificateFactory) UserCert(principal *models.Principal, apiURL string) (*Bundle, error) {

	caBundle, err := NewBundle([]byte(cf.store.ApiserverClientsCAPrivateKey), []byte(cf.store.ApiserverClientsCACertifcate))
	if err != nil {
		return nil, err
	}

	var organizations []string
	for _, role := range principal.Roles {
		organizations = append(organizations, "os:"+role)
	}

	return caBundle.Sign(Config{
		Sign:         fmt.Sprintf("%s@%s", principal.Name, principal.Domain),
		Organization: organizations,
		Province:     []string{principal.AuthURL, cf.kluster.Spec.Openstack.ProjectID},
		Locality:     []string{apiURL},
		Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
		ValidFor:     24 * time.Hour,
	})

}

func loadOrCreateCA(kluster *v1.Kluster, name string, cert, key *string) (*Bundle, error) {
	if *cert != "" && *key != "" {
		return NewBundle([]byte(*key), []byte(*cert))
	}
	caBundle, err := createCA(kluster.Name, name)
	if err != nil {
		return nil, err
	}
	*cert = string(certutil.EncodeCertPEM(caBundle.Certificate))
	*key = string(certutil.EncodePrivateKeyPEM(caBundle.PrivateKey))
	return caBundle, nil
}

func ensureClientCertificate(ca *Bundle, cn string, groups []string, cert, key *string) error {
	certificate, err := ca.Sign(Config{
		Sign:         cn,
		Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
		Organization: groups,
	})
	if err != nil {
		return err
	}

	*cert = string(certutil.EncodeCertPEM(certificate.Certificate))
	*key = string(certutil.EncodePrivateKeyPEM(certificate.PrivateKey))
	return nil

}

func ensureServerCertificate(ca *Bundle, cn string, dnsNames []string, ips []net.IP, cert, key *string) error {
	c, err := ca.Sign(Config{
		Sign:   cn,
		Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		AltNames: AltNames{
			DNSNames: dnsNames,
			IPs:      ips,
		},
	})
	if err != nil {
		return err
	}

	*cert = string(certutil.EncodeCertPEM(c.Certificate))
	*key = string(certutil.EncodePrivateKeyPEM(c.PrivateKey))
	return nil

}

func createCA(klusterName, name string) (*Bundle, error) {
	privateKey, err := certutil.NewPrivateKey()
	if err != nil {
		return nil, fmt.Errorf("Failed to generate private key for %s ca: %s", name, err)
	}

	now := time.Now()
	tmpl := x509.Certificate{
		SerialNumber: new(big.Int).SetInt64(0),
		Subject: pkix.Name{
			CommonName:         name,
			OrganizationalUnit: []string{CA_ISSUER_KUBERNIKUS_IDENTIFIER_0, CA_ISSUER_KUBERNIKUS_IDENTIFIER_1, klusterName},
		},
		NotBefore:             now.UTC(),
		NotAfter:              now.Add(caValidity).UTC(),
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA:                  true,
	}

	certDERBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, privateKey.Public(), privateKey)
	if err != nil {
		return nil, fmt.Errorf("Failed to create certificate for %s CA: %s", name, err)
	}
	certificate, err := x509.ParseCertificate(certDERBytes)
	if err != nil {
		return nil, fmt.Errorf("Failed to parse cert for %s CA: %s", name, err)
	}
	return &Bundle{PrivateKey: privateKey, Certificate: certificate}, nil
}
<commit_msg>fix kubernikusctl auth refresh for nocloud clusters.<commit_after>package util

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"errors"
	"fmt"
	"math"
	"math/big"
	"net"
	"time"

	certutil "k8s.io/client-go/util/cert"

	"github.com/sapcc/kubernikus/pkg/api/models"
	v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
)

const (
	//by default we generate certs with 2 year validity
	defaultCertValidity = 2 * time.Hour * 24 * 365
	//out CAs are valid for 10 years
	caValidity = 10 * time.Hour * 24 * 365
)

type Bundle struct {
	Certificate *x509.Certificate
	PrivateKey  *rsa.PrivateKey
}

func NewBundle(key, cert []byte) (*Bundle, error) {
	certificates, err := certutil.ParseCertsPEM(cert)
	if err != nil {
		return nil, err
	}
	if len(certificates) < 1 {
		return nil, errors.New("No certificates found")
	}
	k, err := certutil.ParsePrivateKeyPEM(key)
	if err != nil {
		return nil, err
	}
	rsaKey, isRSAKey := k.(*rsa.PrivateKey)
	if !isRSAKey {
		return nil, errors.New("Key does not seem to be of type RSA")
	}

	return &Bundle{PrivateKey: rsaKey, Certificate: certificates[0]}, nil
}

type Config struct {
	Sign               string
	Organization       []string
	OrganizationalUnit []string
	Province           []string
	Locality           []string
	AltNames           AltNames
	Usages             []x509.ExtKeyUsage
	ValidFor           time.Duration
}

type AltNames struct {
	DNSNames []string
	IPs      []net.IP
}

func (ca Bundle) Sign(config Config) (*Bundle, error) {
	if !ca.Certificate.IsCA {
		return nil, errors.New("You can't use this certificate for signing. It's not a CA...")
	}

	if config.ValidFor == 0 {
		config.ValidFor = defaultCertValidity
	}

	key, _ := certutil.NewPrivateKey()
	serial, _ := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))

	certTmpl := x509.Certificate{
		Subject: pkix.Name{
			CommonName:         config.Sign,
			Organization:       config.Organization,
			OrganizationalUnit: config.OrganizationalUnit,
			Province:           config.Province,
			Locality:           config.Locality,
		},
		DNSNames:     config.AltNames.DNSNames,
		IPAddresses:  config.AltNames.IPs,
		SerialNumber: serial,
		NotBefore:    ca.Certificate.NotBefore,
		NotAfter:     time.Now().Add(config.ValidFor).UTC(),
		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  config.Usages,
	}

	certDERBytes, _ := x509.CreateCertificate(rand.Reader, &certTmpl, ca.Certificate, key.Public(), ca.PrivateKey)

	cert, _ := x509.ParseCertificate(certDERBytes)

	return &Bundle{cert, key}, nil
}

type CertificateFactory struct {
	kluster *v1.Kluster
	store   *v1.Certificates
	domain  string
}

func NewCertificateFactory(kluster *v1.Kluster, store *v1.Certificates, domain string) *CertificateFactory {
	return &CertificateFactory{kluster, store, domain}
}

func (cf *CertificateFactory) Ensure() error {
	apiServiceIP, err := cf.kluster.ApiServiceIP()
	if err != nil {
		return err
	}

	apiIP := net.ParseIP(cf.kluster.Spec.AdvertiseAddress)
	if apiIP == nil {
		return fmt.Errorf("Failed to parse clusters advertise address: %s", cf.kluster.Spec.AdvertiseAddress)
	}

	etcdClientsCA, err := loadOrCreateCA(cf.kluster, "Etcd Clients", &cf.store.EtcdClientsCACertificate, &cf.store.EtcdClientsCAPrivateKey)
	if err != nil {
		return err
	}
	_, err = loadOrCreateCA(cf.kluster, "Etcd Peers", &cf.store.EtcdPeersCACertificate, &cf.store.EtcdPeersCAPrivateKey)
	if err != nil {
		return err
	}
	apiserverClientsCA, err := loadOrCreateCA(cf.kluster, "ApiServer Clients", &cf.store.ApiserverClientsCACertifcate, &cf.store.ApiserverClientsCAPrivateKey)
	if err != nil {
		return err
	}
	_, err = loadOrCreateCA(cf.kluster, "ApiServer Nodes", &cf.store.ApiserverNodesCACertificate, &cf.store.ApiserverNodesCAPrivateKey)
	if err != nil {
		return err
	}
	kubeletClientsCA, err := loadOrCreateCA(cf.kluster, "Kubelet Clients", &cf.store.KubeletClientsCACertificate, &cf.store.KubeletClientsCAPrivateKey)
	if err != nil {
		return err
	}
	tlsCA, err := loadOrCreateCA(cf.kluster, "TLS", &cf.store.TLSCACertificate, &cf.store.TLSCAPrivateKey)
	if err != nil {
		return err
	}
	aggregationCA, err := loadOrCreateCA(cf.kluster, "Aggregation", &cf.store.AggregationCACertificate, &cf.store.AggregationCAPrivateKey)
	if err != nil {
		return err
	}

	if err := ensureClientCertificate(etcdClientsCA, "apiserver", nil, &cf.store.EtcdClientsApiserverCertificate, &cf.store.EtcdClientsApiserverPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(apiserverClientsCA, "cluster-admin", []string{"system:masters"}, &cf.store.ApiserverClientsClusterAdminCertificate, &cf.store.ApiserverClientsClusterAdminPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(apiserverClientsCA, "system:kube-controller-manager", nil, &cf.store.ApiserverClientsKubeControllerManagerCertificate, &cf.store.ApiserverClientsKubeControllerManagerPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(apiserverClientsCA, "system:kube-proxy", nil, &cf.store.ApiserverClientsKubeProxyCertificate, &cf.store.ApiserverClientsKubeProxyPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(apiserverClientsCA, "system:kube-scheduler", nil, &cf.store.ApiserverClientsKubeSchedulerCertificate, &cf.store.ApiserverClientsKubeSchedulerPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(apiserverClientsCA, "kubernikus:wormhole", nil, &cf.store.ApiserverClientsKubernikusWormholeCertificate, &cf.store.ApiserverClientsKubernikusWormholePrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(kubeletClientsCA, "apiserver", nil, &cf.store.KubeletClientsApiserverCertificate, &cf.store.KubeletClientsApiserverPrivateKey); err != nil {
		return err
	}
	if err := ensureClientCertificate(aggregationCA, "aggregator", nil, &cf.store.AggregationAggregatorCertificate, &cf.store.AggregationAggregatorPrivateKey); err != nil {
		return err
	}

	if err := ensureServerCertificate(tlsCA, "apiserver",
		[]string{"kubernetes", "kubernetes.default", "kubernetes.default.svc", "apiserver", cf.kluster.Name, fmt.Sprintf("%s.%s", cf.kluster.Name, cf.kluster.Namespace), fmt.Sprintf("%v.%v", cf.kluster.Name, cf.domain)},
		[]net.IP{net.IPv4(127, 0, 0, 1), apiServiceIP, apiIP},
		&cf.store.TLSApiserverCertificate,
		&cf.store.TLSApiserverPrivateKey); err != nil {
		return err
	}
	if err := ensureServerCertificate(tlsCA, "wormhole",
		[]string{fmt.Sprintf("%v-wormhole.%v", cf.kluster.Name, cf.domain)},
		nil,
		&cf.store.TLSWormholeCertificate,
		&cf.store.TLSWormholePrivateKey); err != nil {
		return err
	}

	return nil
}

func (cf *CertificateFactory) UserCert(principal *models.Principal, apiURL string) (*Bundle, error) {

	caBundle, err := NewBundle([]byte(cf.store.ApiserverClientsCAPrivateKey), []byte(cf.store.ApiserverClientsCACertifcate))
	if err != nil {
		return nil, err
	}

	var organizations []string
	for _, role := range principal.Roles {
		organizations = append(organizations, "os:"+role)
	}
	projectid := cf.kluster.Spec.Openstack.ProjectID
	//nocloud clusters don't have the projectID in the spec
	if projectid == "" {
		projectid = cf.kluster.Account()
	}

	return caBundle.Sign(Config{
		Sign:         fmt.Sprintf("%s@%s", principal.Name, principal.Domain),
		Organization: organizations,
		Province:     []string{principal.AuthURL, projectid},
		Locality:     []string{apiURL},
		Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
		ValidFor:     24 * time.Hour,
	})

}

func loadOrCreateCA(kluster *v1.Kluster, name string, cert, key *string) (*Bundle, error) {
	if *cert != "" && *key != "" {
		return NewBundle([]byte(*key), []byte(*cert))
	}
	caBundle, err := createCA(kluster.Name, name)
	if err != nil {
		return nil, err
	}
	*cert = string(certutil.EncodeCertPEM(caBundle.Certificate))
	*key = string(certutil.EncodePrivateKeyPEM(caBundle.PrivateKey))
	return caBundle, nil
}

func ensureClientCertificate(ca *Bundle, cn string, groups []string, cert, key *string) error {
	certificate, err := ca.Sign(Config{
		Sign:         cn,
		Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
		Organization: groups,
	})
	if err != nil {
		return err
	}

	*cert = string(certutil.EncodeCertPEM(certificate.Certificate))
	*key = string(certutil.EncodePrivateKeyPEM(certificate.PrivateKey))
	return nil

}

func ensureServerCertificate(ca *Bundle, cn string, dnsNames []string, ips []net.IP, cert, key *string) error {
	c, err := ca.Sign(Config{
		Sign:   cn,
		Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		AltNames: AltNames{
			DNSNames: dnsNames,
			IPs:      ips,
		},
	})
	if err != nil {
		return err
	}

	*cert = string(certutil.EncodeCertPEM(c.Certificate))
	*key = string(certutil.EncodePrivateKeyPEM(c.PrivateKey))
	return nil

}

func createCA(klusterName, name string) (*Bundle, error) {
	privateKey, err := certutil.NewPrivateKey()
	if err != nil {
		return nil, fmt.Errorf("Failed to generate private key for %s ca: %s", name, err)
	}

	now := time.Now()
	tmpl := x509.Certificate{
		SerialNumber: new(big.Int).SetInt64(0),
		Subject: pkix.Name{
			CommonName:         name,
			OrganizationalUnit: []string{CA_ISSUER_KUBERNIKUS_IDENTIFIER_0, CA_ISSUER_KUBERNIKUS_IDENTIFIER_1, klusterName},
		},
		NotBefore:             now.UTC(),
		NotAfter:              now.Add(caValidity).UTC(),
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA:                  true,
	}

	certDERBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, privateKey.Public(), privateKey)
	if err != nil {
		return nil, fmt.Errorf("Failed to create certificate for %s CA: %s", name, err)
	}
	certificate, err := x509.ParseCertificate(certDERBytes)
	if err != nil {
		return nil, fmt.Errorf("Failed to parse cert for %s CA: %s", name, err)
	}
	return &Bundle{PrivateKey: privateKey, Certificate: certificate}, nil
}
<|endoftext|>


<commit_before>// Copyright © 2017 National Library of Norway.
// 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 util

import (
	"fmt"
	tm "github.com/buger/goterm"
	"github.com/golang/protobuf/proto"
	"io"
	"reflect"
	"strings"
)

var debugEnabled = false

func MarshalTable(w io.Writer, msg proto.Message) error {
	table := tm.NewTable(2, 10, 2, ' ', 0)

	var values reflect.Value
	values = reflect.ValueOf(msg).Elem().FieldByName("Value")
	if values.IsValid() {
		if values.Len() == 0 {
			fmt.Println("Empty result")
			return nil
		}

		m := values.Index(0).Interface().(proto.Message)
		tableDef := GetTableDef(m)
		formatHeader(table, tableDef, m)

		for i := 0; i < values.Len(); i++ {
			m := values.Index(i).Interface().(proto.Message)
			err := formatData(table, tableDef, m)
			if err != nil {
				return err
			}
			debug(m)
		}
	} else {
		m := reflect.ValueOf(msg).Interface().(proto.Message)

		debug(m)

		tableDef := GetTableDef(m)
		formatHeader(table, tableDef, m)

		err := formatData(table, tableDef, m)
		if err != nil {
			return err
		}
	}

	fmt.Fprintln(w, table)

	return nil
}

func debug(m proto.Message) {
	if debugEnabled {
		v := reflect.ValueOf(m).Elem()
		t := v.Type()
		for i := 0; i < v.NumField(); i++ {
			f := v.Field(i)
			fmt.Printf("%d: %s %s = %v\n", i,
				t.Field(i).Name, f.Type(), f.Interface())
		}
	}
}

func formatHeader(t *tm.Table, tableDef []string, msg proto.Message) error {
	var header string
	for idx, tab := range tableDef {
		if idx > 0 {
			header += "\t"
		}
		header += tab
	}

	fmt.Fprintf(t, "\n%s\n", header)
	return nil
}

func formatData(t *tm.Table, tableDef []string, msg proto.Message) error {
	var line string
	for idx, tab := range tableDef {
		if idx > 0 {
			line += "\t"
		}
		line += fmt.Sprint(getField(msg, tab))
	}

	fmt.Fprintf(t, "%s\n", line)
	return nil
}

func getField(msg proto.Message, fieldName string) reflect.Value {
	tokens := strings.Split(fieldName, ".")
	v := reflect.ValueOf(msg)
	for _, tok := range tokens {
		v = reflect.Indirect(v)
		if v.Kind() == reflect.Interface {
			v = reflect.Indirect(reflect.ValueOf(v.Interface()))
		}
		v = v.FieldByName(tok)
		if v.Kind() == reflect.Interface && v.IsNil() {
			return v
		}
	}
	return v
}
<commit_msg>Revert "Make output in table format work with output option -o and behave as expected when paging overflows screen"<commit_after>// Copyright © 2017 National Library of Norway.
// 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 util

import (
	"fmt"
	tm "github.com/buger/goterm"
	"github.com/golang/protobuf/proto"
	"io"
	"reflect"
	"strings"
)

var debugEnabled = false

func MarshalTable(w io.Writer, msg proto.Message) error {
	table := tm.NewTable(2, 10, 2, ' ', 0)

	var values reflect.Value
	values = reflect.ValueOf(msg).Elem().FieldByName("Value")
	if values.IsValid() {
		if values.Len() == 0 {
			fmt.Println("Empty result")
			return nil
		}

		m := values.Index(0).Interface().(proto.Message)
		tableDef := GetTableDef(m)
		formatHeader(table, tableDef, m)

		for i := 0; i < values.Len(); i++ {
			m := values.Index(i).Interface().(proto.Message)
			err := formatData(table, tableDef, m)
			if err != nil {
				return err
			}
			debug(m)
		}
	} else {
		m := reflect.ValueOf(msg).Interface().(proto.Message)

		debug(m)

		tableDef := GetTableDef(m)
		formatHeader(table, tableDef, m)

		err := formatData(table, tableDef, m)
		if err != nil {
			return err
		}
	}

	tm.Println(table)
	tm.Flush()

	return nil
}

func debug(m proto.Message) {
	if debugEnabled {
		v := reflect.ValueOf(m).Elem()
		t := v.Type()
		for i := 0; i < v.NumField(); i++ {
			f := v.Field(i)
			fmt.Printf("%d: %s %s = %v\n", i,
				t.Field(i).Name, f.Type(), f.Interface())
		}
	}
}

func formatHeader(t *tm.Table, tableDef []string, msg proto.Message) error {
	var header string
	for idx, tab := range tableDef {
		if idx > 0 {
			header += "\t"
		}
		header += tab
	}

	fmt.Fprintf(t, "\n%s\n", header)
	return nil
}

func formatData(t *tm.Table, tableDef []string, msg proto.Message) error {
	var line string
	for idx, tab := range tableDef {
		if idx > 0 {
			line += "\t"
		}
		line += fmt.Sprint(getField(msg, tab))
	}

	fmt.Fprintf(t, "%s\n", line)
	return nil
}

func getField(msg proto.Message, fieldName string) reflect.Value {
	tokens := strings.Split(fieldName, ".")
	v := reflect.ValueOf(msg)
	for _, tok := range tokens {
		v = reflect.Indirect(v)
		if v.Kind() == reflect.Interface {
			v = reflect.Indirect(reflect.ValueOf(v.Interface()))
		}
		v = v.FieldByName(tok)
		if v.Kind() == reflect.Interface && v.IsNil() {
			return v
		}
	}
	return v
}
<|endoftext|>


<commit_before>package receiver

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"math"
	"net"
	"net/http"
	"net/url"
	"sort"
	"sync/atomic"
	"time"

	"github.com/lomik/carbon-clickhouse/helper/RowBinary"
	"github.com/lomik/stop"
	"go.uber.org/zap"
)

type TelegrafHttpMetric struct {
	Name      string             `json:"name"`
	Timestamp int64              `json:"timestamp"`
	Fields    map[string]float64 `json:"fields"`
	Tags      map[string]string  `json:"tags"`
}

type TelegrafHttpPayload struct {
	Metrics []TelegrafHttpMetric `json:"metrics"`
}

type TelegrafHttpJson struct {
	stop.Struct
	stat struct {
		samplesReceived uint32 // atomic
		errors          uint32 // atomic
		active          int32  // atomic
	}
	listener  *net.TCPListener
	writeChan chan *RowBinary.WriteBuffer
	logger    *zap.Logger
}

func TelegrafEncodeTags(tags map[string]string) string {
	if len(tags) < 1 {
		return ""
	}

	if len(tags) == 1 {
		var res bytes.Buffer
		for k, v := range tags {
			res.WriteString(url.QueryEscape(k))
			res.WriteByte('=')
			res.WriteString(url.QueryEscape(v))
			return res.String()
		}
	}

	keys := make([]string, 0, len(tags))
	for k, _ := range tags {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var res bytes.Buffer
	for i := 0; i < len(keys); i++ {
		if i > 1 {
			res.WriteByte('&')
		}
		res.WriteString(url.QueryEscape(keys[i]))
		res.WriteByte('=')
		res.WriteString(url.QueryEscape(tags[keys[i]]))
	}
	return res.String()
}

func (rcv *TelegrafHttpJson) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var data TelegrafHttpPayload
	err = json.Unmarshal(body, &data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	writer := RowBinary.NewWriter(r.Context(), rcv.writeChan)

	var pathBuf bytes.Buffer

	for i := 0; i < len(data.Metrics); i++ {
		m := data.Metrics[i]
		tags := TelegrafEncodeTags(m.Tags)

		for f, v := range m.Fields {
			pathBuf.Reset()
			pathBuf.WriteString(url.PathEscape(m.Name))

			if math.IsNaN(v) {
				continue
			}

			if f != "value" {
				pathBuf.WriteByte('_')
				pathBuf.WriteString(url.PathEscape(f))
			}

			pathBuf.WriteByte('?')
			pathBuf.WriteString(tags)

			writer.WritePoint(pathBuf.String(), v, m.Timestamp)
		}
	}

	writer.Flush()
	if samplesCount := writer.PointsWritten(); samplesCount > 0 {
		atomic.AddUint32(&rcv.stat.samplesReceived, samplesCount)
	}

	if writeErrors := writer.WriteErrors(); writeErrors > 0 {
		atomic.AddUint32(&rcv.stat.errors, writeErrors)
	}
}

// Addr returns binded socket address. For bind port 0 in tests
func (rcv *TelegrafHttpJson) Addr() net.Addr {
	if rcv.listener == nil {
		return nil
	}
	return rcv.listener.Addr()
}

func (rcv *TelegrafHttpJson) Stat(send func(metric string, value float64)) {
	samplesReceived := atomic.LoadUint32(&rcv.stat.samplesReceived)
	atomic.AddUint32(&rcv.stat.samplesReceived, -samplesReceived)
	send("samplesReceived", float64(samplesReceived))

	errors := atomic.LoadUint32(&rcv.stat.errors)
	atomic.AddUint32(&rcv.stat.errors, -errors)
	send("errors", float64(errors))
}

// Listen bind port. Receive messages and send to out channel
func (rcv *TelegrafHttpJson) Listen(addr *net.TCPAddr) error {
	return rcv.StartFunc(func() error {

		tcpListener, err := net.ListenTCP("tcp", addr)
		if err != nil {
			return err
		}

		s := &http.Server{
			Handler:        rcv,
			ReadTimeout:    10 * time.Second,
			WriteTimeout:   10 * time.Second,
			MaxHeaderBytes: 1 << 20,
		}

		rcv.Go(func(exit chan struct{}) {
			<-exit
			tcpListener.Close()
		})

		rcv.Go(func(exit chan struct{}) {
			if err := s.Serve(tcpListener); err != nil {
				rcv.logger.Fatal("failed to serve", zap.Error(err))
			}

		})

		rcv.listener = tcpListener

		return nil
	})
}
<commit_msg>skip not_number and not_bool values in telegraf_http_json receiver #20<commit_after>package receiver

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"math"
	"net"
	"net/http"
	"net/url"
	"sort"
	"sync/atomic"
	"time"

	"github.com/lomik/carbon-clickhouse/helper/RowBinary"
	"github.com/lomik/stop"
	"go.uber.org/zap"
)

type TelegrafHttpMetric struct {
	Name      string                 `json:"name"`
	Timestamp int64                  `json:"timestamp"`
	Fields    map[string]interface{} `json:"fields"`
	Tags      map[string]string      `json:"tags"`
}

type TelegrafHttpPayload struct {
	Metrics []TelegrafHttpMetric `json:"metrics"`
}

type TelegrafHttpJson struct {
	stop.Struct
	stat struct {
		samplesReceived uint32 // atomic
		errors          uint32 // atomic
		active          int32  // atomic
	}
	listener  *net.TCPListener
	writeChan chan *RowBinary.WriteBuffer
	logger    *zap.Logger
}

func TelegrafEncodeTags(tags map[string]string) string {
	if len(tags) < 1 {
		return ""
	}

	if len(tags) == 1 {
		var res bytes.Buffer
		for k, v := range tags {
			res.WriteString(url.QueryEscape(k))
			res.WriteByte('=')
			res.WriteString(url.QueryEscape(v))
			return res.String()
		}
	}

	keys := make([]string, 0, len(tags))
	for k, _ := range tags {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var res bytes.Buffer
	for i := 0; i < len(keys); i++ {
		if i > 1 {
			res.WriteByte('&')
		}
		res.WriteString(url.QueryEscape(keys[i]))
		res.WriteByte('=')
		res.WriteString(url.QueryEscape(tags[keys[i]]))
	}
	return res.String()
}

func (rcv *TelegrafHttpJson) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var data TelegrafHttpPayload
	err = json.Unmarshal(body, &data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	writer := RowBinary.NewWriter(r.Context(), rcv.writeChan)

	var pathBuf bytes.Buffer

	for i := 0; i < len(data.Metrics); i++ {
		m := data.Metrics[i]
		tags := TelegrafEncodeTags(m.Tags)

		for f, vi := range m.Fields {
			v, ok := vi.(float64)
			if !ok {
				vb, ok := vi.(bool)
				if !ok {
					continue
				}
				if vb {
					v = 1
				} else {
					v = 0
				}
			}
			pathBuf.Reset()
			pathBuf.WriteString(url.PathEscape(m.Name))

			if math.IsNaN(v) {
				continue
			}

			if f != "value" {
				pathBuf.WriteByte('_')
				pathBuf.WriteString(url.PathEscape(f))
			}

			pathBuf.WriteByte('?')
			pathBuf.WriteString(tags)

			writer.WritePoint(pathBuf.String(), v, m.Timestamp)
		}
	}

	writer.Flush()
	if samplesCount := writer.PointsWritten(); samplesCount > 0 {
		atomic.AddUint32(&rcv.stat.samplesReceived, samplesCount)
	}

	if writeErrors := writer.WriteErrors(); writeErrors > 0 {
		atomic.AddUint32(&rcv.stat.errors, writeErrors)
	}
}

// Addr returns binded socket address. For bind port 0 in tests
func (rcv *TelegrafHttpJson) Addr() net.Addr {
	if rcv.listener == nil {
		return nil
	}
	return rcv.listener.Addr()
}

func (rcv *TelegrafHttpJson) Stat(send func(metric string, value float64)) {
	samplesReceived := atomic.LoadUint32(&rcv.stat.samplesReceived)
	atomic.AddUint32(&rcv.stat.samplesReceived, -samplesReceived)
	send("samplesReceived", float64(samplesReceived))

	errors := atomic.LoadUint32(&rcv.stat.errors)
	atomic.AddUint32(&rcv.stat.errors, -errors)
	send("errors", float64(errors))
}

// Listen bind port. Receive messages and send to out channel
func (rcv *TelegrafHttpJson) Listen(addr *net.TCPAddr) error {
	return rcv.StartFunc(func() error {

		tcpListener, err := net.ListenTCP("tcp", addr)
		if err != nil {
			return err
		}

		s := &http.Server{
			Handler:        rcv,
			ReadTimeout:    10 * time.Second,
			WriteTimeout:   10 * time.Second,
			MaxHeaderBytes: 1 << 20,
		}

		rcv.Go(func(exit chan struct{}) {
			<-exit
			tcpListener.Close()
		})

		rcv.Go(func(exit chan struct{}) {
			if err := s.Serve(tcpListener); err != nil {
				rcv.logger.Fatal("failed to serve", zap.Error(err))
			}

		})

		rcv.listener = tcpListener

		return nil
	})
}
<|endoftext|>


<commit_before>package clt

import (
	"bufio"
	"fmt"
	"io"

	"os"
	"strings"

	"golang.org/x/crypto/ssh/terminal"
)

type getOpt int

const (
	noColon getOpt = iota
)

// InteractiveSession creates a system for collecting user input
// in response to questions and choices
type InteractiveSession struct {
	Prompt  string
	Default string
	ValHint string

	response string
	input    *bufio.Reader
	output   io.Writer
}

// NewInteractiveSession returns a new InteractiveSession outputting to Stdout
// and reading from Stdin by default, but other inputs and outputs may be specified
// with SessionOptions
func NewInteractiveSession(opts ...SessionOption) *InteractiveSession {
	i := &InteractiveSession{
		input:  bufio.NewReader(os.Stdin),
		output: os.Stdout,
	}
	for _, opt := range opts {
		opt(i)
	}
	return i
}

// SessionOption optionally configures aspects of the interactive session
type SessionOption func(i *InteractiveSession)

// WithInput uses an input other than os.Stdin
func WithInput(r io.Reader) SessionOption {
	return func(i *InteractiveSession) {
		i.input = bufio.NewReader(r)
	}
}

// WithOutput uses an output other than os.Stdout
func WithOutput(w io.Writer) SessionOption {
	return func(i *InteractiveSession) {
		i.output = w
	}
}

// Say is a short form of fmt.Fprintf but allows you to chain additional terminators to
// the interactive session to collect user input
func (i *InteractiveSession) Say(format string, args ...interface{}) *InteractiveSession {
	fmt.Fprintf(i.output, fmt.Sprintf("\n%s\n", format), args...)
	return i
}

// Pause is a terminator that will render long-form text added via the another method
// that returns *InteractiveSession and will wait for the user to press enter to continue.
// It is useful for long-form content or paging.
func (i *InteractiveSession) Pause() {
	i.Prompt = "\nPress [Enter] to continue."
	i.get(noColon)
}

// PauseWithPrompt is a terminator that will render long-form text added via the another method
// that returns *InteractiveSession and will wait for the user to press enter to continue.
// This will use the custom prompt specified by format and args.
func (i *InteractiveSession) PauseWithPrompt(format string, args ...interface{}) {
	i.Prompt = fmt.Sprintf(format, args...)
	i.get(noColon)
}

func (i *InteractiveSession) get(opts ...getOpt) (err error) {
	contains := func(wanted getOpt) bool {
		for _, opt := range opts {
			if opt == wanted {
				return true
			}
		}
		return false
	}

	if i.output == nil {
		i.output = bufio.NewWriter(os.Stdout)
	}
	if i.input == nil {
		i.input = bufio.NewReader(os.Stdin)
	}

	switch {
	case len(i.Default) > 0:
		fmt.Fprintf(i.output, "%s  [%s]: ", i.Prompt, i.Default)
	case len(i.ValHint) > 0:
		fmt.Fprintf(i.output, "%s (%s): ", i.Prompt, i.ValHint)
	case contains(noColon):
		fmt.Fprintf(i.output, "%s", i.Prompt)
	case len(i.Prompt) > 0:
		fmt.Fprintf(i.output, "%s: ", i.Prompt)
	default:
	}

	i.response, err = i.input.ReadString('\n')
	if err != nil {
		return err
	}
	i.response = strings.TrimRight(i.response, " \n")
	if len(i.Default) > 0 && len(i.response) == 0 {
		switch i.Default {
		case "y/N":
			i.response = "n"
		case "Y/n":
			i.response = "y"
		default:
			i.response = i.Default
		}
	}

	return nil
}

// Warn adds an informational warning message to the user in format
// Warning: <user defined string>
func (i *InteractiveSession) Warn(format string, args ...interface{}) *InteractiveSession {
	fmt.Fprintf(i.output, "\n%s: %s\n", Styled(Yellow).ApplyTo("Warning"), fmt.Sprintf(format, args...))
	return i
}

// Error is a terminator that gives an informational error message to the user in format
// Error: <user defined string>.  Exits the program returning status code 1
func (i *InteractiveSession) Error(format string, args ...interface{}) {
	fmt.Fprintf(i.output, "\n\n%s: %s\n", Styled(Red).ApplyTo("Error:"), fmt.Sprintf(format, args...))
	os.Exit(1)
}

// Ask is a terminator for an interactive session that results in returning the user's
// input.  Validators can optionally be applied to ensure that acceptable input is returned
// or the question will be asked again.
func (i *InteractiveSession) Ask(prompt string, validators ...ValidationFunc) string {
	return i.ask(prompt, "", "", validators...)
}

// AskWithDefault is like ask, but sets a default choice that the user can select by pressing enter.
func (i *InteractiveSession) AskWithDefault(prompt string, defaultChoice string, validators ...ValidationFunc) string {
	return i.ask(prompt, defaultChoice, "", validators...)
}

// AskWithHint is like ask, but gives a hint about the proper format of the response.  This is useful
// combined with a validation function to get input in the right format.
func (i *InteractiveSession) AskWithHint(prompt string, hint string, validators ...ValidationFunc) string {
	return i.ask(prompt, "", hint, validators...)
}

func (i *InteractiveSession) ask(prompt string, def string, hint string, validators ...ValidationFunc) string {
	i.Prompt = prompt
	i.Default = def
	i.ValHint = hint
	i.get()
	for _, validator := range validators {
		if ok, err := validator(i.response); !ok {
			i.Say("\nError: %s\n\n", err)
			i.ask(prompt, def, hint, validators...)
		}
	}
	return i.response
}

// AskPassword is a terminator that asks for a password and does not echo input
// to the terminal.
func (i *InteractiveSession) AskPassword(validators ...ValidationFunc) string {
	return askPassword(i, "Password: ", validators...)
}

// AskPasswordPrompt is a terminator that asks for a password with a custom prompt
func (i *InteractiveSession) AskPasswordPrompt(prompt string, validators ...ValidationFunc) string {
	return askPassword(i, prompt, validators...)
}

func askPassword(i *InteractiveSession, prompt string, validators ...ValidationFunc) string {
	fmt.Fprintf(i.output, "Password: ")
	pw, err := terminal.ReadPassword(0)
	if err != nil {
		i.Error("\n%s\n", err)
	}

	pwS := strings.TrimSpace(string(pw))
	for _, validator := range validators {
		if ok, err := validator(pwS); !ok {
			i.Say("\nError: %s\n\n", err)
			i.AskPassword(validators...)
		}
	}
	return pwS
}

// AskYesNo asks the user a yes or no question with a default value.  Defaults of `y` or `yes` will
// set the default to yes.  Anything else will default to no.  You can use IsYes or IsNo to act on the response
// without worrying about what version of y, Y, YES, yes, etc. that the user entered.
func (i *InteractiveSession) AskYesNo(prompt string, defaultChoice string) string {
	switch def := strings.ToLower(defaultChoice); def {
	case "y", "yes":
		i.Default = "Y/n"
	default:
		i.Default = "y/N"
	}
	return i.ask(prompt, i.Default, "", ValidateYesNo())
}

// AskFromTable creates a table to select choices from.  It has a built-in validation function that will
// ensure that only the options listed are valid choices.
func (i *InteractiveSession) AskFromTable(prompt string, choices map[string]string, def string) string {
	t := NewTable(2).
		ColumnHeaders("Option", "")
	var allKeys []string
	for key, choice := range choices {
		t.AddRow(key, choice)
		allKeys = append(allKeys, key)
	}
	tAsString := t.AsString()

	i.Prompt = fmt.Sprintf("\n%s%s\nChoice", prompt, tAsString)
	i.Default = def
	i.get()
	if ok, err := AllowedOptions(allKeys)(i.response); !ok {
		i.Say("\nError: %s\n\n", err)
		i.AskFromTable(prompt, choices, def)
	}

	return strings.TrimSpace(i.response)
}
<commit_msg>allow reset of interactive session<commit_after>package clt

import (
	"bufio"
	"fmt"
	"io"

	"os"
	"strings"

	"golang.org/x/crypto/ssh/terminal"
)

type getOpt int

const (
	noColon getOpt = iota
)

// InteractiveSession creates a system for collecting user input
// in response to questions and choices
type InteractiveSession struct {
	Prompt  string
	Default string
	ValHint string

	response string
	input    *bufio.Reader
	output   io.Writer
}

// NewInteractiveSession returns a new InteractiveSession outputting to Stdout
// and reading from Stdin by default, but other inputs and outputs may be specified
// with SessionOptions
func NewInteractiveSession(opts ...SessionOption) *InteractiveSession {
	i := &InteractiveSession{
		input:  bufio.NewReader(os.Stdin),
		output: os.Stdout,
	}
	for _, opt := range opts {
		opt(i)
	}
	return i
}

// SessionOption optionally configures aspects of the interactive session
type SessionOption func(i *InteractiveSession)

// WithInput uses an input other than os.Stdin
func WithInput(r io.Reader) SessionOption {
	return func(i *InteractiveSession) {
		i.input = bufio.NewReader(r)
	}
}

// WithOutput uses an output other than os.Stdout
func WithOutput(w io.Writer) SessionOption {
	return func(i *InteractiveSession) {
		i.output = w
	}
}

// Reset allows reuse of the same interactive session by reseting its state and keeping
// its current input and output
func (i *InteractiveSession) Reset() {
	i.Prompt = ""
	i.Default = ""
	i.ValHint = ""
	i.response = ""
}

// Say is a short form of fmt.Fprintf but allows you to chain additional terminators to
// the interactive session to collect user input
func (i *InteractiveSession) Say(format string, args ...interface{}) *InteractiveSession {
	fmt.Fprintf(i.output, fmt.Sprintf("\n%s\n", format), args...)
	return i
}

// Pause is a terminator that will render long-form text added via the another method
// that returns *InteractiveSession and will wait for the user to press enter to continue.
// It is useful for long-form content or paging.
func (i *InteractiveSession) Pause() {
	i.Prompt = "\nPress [Enter] to continue."
	i.get(noColon)
}

// PauseWithPrompt is a terminator that will render long-form text added via the another method
// that returns *InteractiveSession and will wait for the user to press enter to continue.
// This will use the custom prompt specified by format and args.
func (i *InteractiveSession) PauseWithPrompt(format string, args ...interface{}) {
	i.Prompt = fmt.Sprintf(format, args...)
	i.get(noColon)
}

func (i *InteractiveSession) get(opts ...getOpt) (err error) {
	contains := func(wanted getOpt) bool {
		for _, opt := range opts {
			if opt == wanted {
				return true
			}
		}
		return false
	}

	if i.output == nil {
		i.output = bufio.NewWriter(os.Stdout)
	}
	if i.input == nil {
		i.input = bufio.NewReader(os.Stdin)
	}

	switch {
	case len(i.Default) > 0:
		fmt.Fprintf(i.output, "%s  [%s]: ", i.Prompt, i.Default)
	case len(i.ValHint) > 0:
		fmt.Fprintf(i.output, "%s (%s): ", i.Prompt, i.ValHint)
	case contains(noColon):
		fmt.Fprintf(i.output, "%s", i.Prompt)
	case len(i.Prompt) > 0:
		fmt.Fprintf(i.output, "%s: ", i.Prompt)
	default:
	}

	i.response, err = i.input.ReadString('\n')
	if err != nil {
		return err
	}
	i.response = strings.TrimRight(i.response, " \n")
	if len(i.Default) > 0 && len(i.response) == 0 {
		switch i.Default {
		case "y/N":
			i.response = "n"
		case "Y/n":
			i.response = "y"
		default:
			i.response = i.Default
		}
	}

	return nil
}

// Warn adds an informational warning message to the user in format
// Warning: <user defined string>
func (i *InteractiveSession) Warn(format string, args ...interface{}) *InteractiveSession {
	fmt.Fprintf(i.output, "\n%s: %s\n", Styled(Yellow).ApplyTo("Warning"), fmt.Sprintf(format, args...))
	return i
}

// Error is a terminator that gives an informational error message to the user in format
// Error: <user defined string>.  Exits the program returning status code 1
func (i *InteractiveSession) Error(format string, args ...interface{}) {
	fmt.Fprintf(i.output, "\n\n%s: %s\n", Styled(Red).ApplyTo("Error:"), fmt.Sprintf(format, args...))
	os.Exit(1)
}

// Ask is a terminator for an interactive session that results in returning the user's
// input.  Validators can optionally be applied to ensure that acceptable input is returned
// or the question will be asked again.
func (i *InteractiveSession) Ask(prompt string, validators ...ValidationFunc) string {
	return i.ask(prompt, "", "", validators...)
}

// AskWithDefault is like ask, but sets a default choice that the user can select by pressing enter.
func (i *InteractiveSession) AskWithDefault(prompt string, defaultChoice string, validators ...ValidationFunc) string {
	return i.ask(prompt, defaultChoice, "", validators...)
}

// AskWithHint is like ask, but gives a hint about the proper format of the response.  This is useful
// combined with a validation function to get input in the right format.
func (i *InteractiveSession) AskWithHint(prompt string, hint string, validators ...ValidationFunc) string {
	return i.ask(prompt, "", hint, validators...)
}

func (i *InteractiveSession) ask(prompt string, def string, hint string, validators ...ValidationFunc) string {
	i.Prompt = prompt
	i.Default = def
	i.ValHint = hint
	i.get()
	for _, validator := range validators {
		if ok, err := validator(i.response); !ok {
			i.Say("\nError: %s\n\n", err)
			i.ask(prompt, def, hint, validators...)
		}
	}
	return i.response
}

// AskPassword is a terminator that asks for a password and does not echo input
// to the terminal.
func (i *InteractiveSession) AskPassword(validators ...ValidationFunc) string {
	return askPassword(i, "Password: ", validators...)
}

// AskPasswordPrompt is a terminator that asks for a password with a custom prompt
func (i *InteractiveSession) AskPasswordPrompt(prompt string, validators ...ValidationFunc) string {
	return askPassword(i, prompt, validators...)
}

func askPassword(i *InteractiveSession, prompt string, validators ...ValidationFunc) string {
	fmt.Fprintf(i.output, "Password: ")
	pw, err := terminal.ReadPassword(0)
	if err != nil {
		i.Error("\n%s\n", err)
	}

	pwS := strings.TrimSpace(string(pw))
	for _, validator := range validators {
		if ok, err := validator(pwS); !ok {
			i.Say("\nError: %s\n\n", err)
			i.AskPassword(validators...)
		}
	}
	return pwS
}

// AskYesNo asks the user a yes or no question with a default value.  Defaults of `y` or `yes` will
// set the default to yes.  Anything else will default to no.  You can use IsYes or IsNo to act on the response
// without worrying about what version of y, Y, YES, yes, etc. that the user entered.
func (i *InteractiveSession) AskYesNo(prompt string, defaultChoice string) string {
	switch def := strings.ToLower(defaultChoice); def {
	case "y", "yes":
		i.Default = "Y/n"
	default:
		i.Default = "y/N"
	}
	return i.ask(prompt, i.Default, "", ValidateYesNo())
}

// AskFromTable creates a table to select choices from.  It has a built-in validation function that will
// ensure that only the options listed are valid choices.
func (i *InteractiveSession) AskFromTable(prompt string, choices map[string]string, def string) string {
	t := NewTable(2).
		ColumnHeaders("Option", "")
	var allKeys []string
	for key, choice := range choices {
		t.AddRow(key, choice)
		allKeys = append(allKeys, key)
	}
	tAsString := t.AsString()

	i.Prompt = fmt.Sprintf("\n%s%s\nChoice", prompt, tAsString)
	i.Default = def
	i.get()
	if ok, err := AllowedOptions(allKeys)(i.response); !ok {
		i.Say("\nError: %s\n\n", err)
		i.AskFromTable(prompt, choices, def)
	}

	return strings.TrimSpace(i.response)
}
<|endoftext|>


<commit_before>// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package whitelist

import (
	"github.com/goharbor/harbor/src/common/dao"
	"github.com/goharbor/harbor/src/common/models"
	"github.com/goharbor/harbor/src/common/utils/log"
	"github.com/goharbor/harbor/src/jobservice/logger"
)

// Manager defines the interface of CVE whitelist manager, it support both system level and project level whitelists
type Manager interface {
	// CreateEmpty creates empty whitelist for given project
	CreateEmpty(projectID int64) error
	// Set sets the whitelist for given project (create or update)
	Set(projectID int64, list models.CVEWhitelist) error
	// Get gets the whitelist for given project
	Get(projectID int64) (*models.CVEWhitelist, error)
	// SetSys sets system level whitelist
	SetSys(list models.CVEWhitelist) error
	// GetSys gets system level whitelist
	GetSys() (*models.CVEWhitelist, error)
}

type defaultManager struct{}

// CreateEmpty creates empty whitelist for given project
func (d *defaultManager) CreateEmpty(projectID int64) error {
	l := models.CVEWhitelist{
		ProjectID: projectID,
	}
	_, err := dao.CreateCVEWhitelist(l)
	if err != nil {
		logger.Errorf("Failed to create empty CVE whitelist for project: %d, error: %v", projectID, err)
	}
	return err
}

// Set sets the whitelist for given project (create or update)
func (d *defaultManager) Set(projectID int64, list models.CVEWhitelist) error {
	list.ProjectID = projectID
	if err := Validate(list); err != nil {
		return err
	}
	_, err := dao.UpdateCVEWhitelist(list)
	return err
}

// Get gets the whitelist for given project
func (d *defaultManager) Get(projectID int64) (*models.CVEWhitelist, error) {
	wl, err := dao.GetCVEWhitelist(projectID)
	if wl == nil && err == nil {
		log.Debugf("No CVE whitelist found for project %d, returning empty list.", projectID)
		return &models.CVEWhitelist{ProjectID: projectID, Items: []models.CVEWhitelistItem{}}, nil
	}
	return wl, err
}

// SetSys sets the system level whitelist
func (d *defaultManager) SetSys(list models.CVEWhitelist) error {
	return d.Set(0, list)
}

// GetSys gets the system level whitelist
func (d *defaultManager) GetSys() (*models.CVEWhitelist, error) {
	return d.Get(0)
}

// NewDefaultManager return a new instance of defaultManager
func NewDefaultManager() Manager {
	return &defaultManager{}
}
<commit_msg>The default item list should be empty list,not null<commit_after>// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package whitelist

import (
	"github.com/goharbor/harbor/src/common/dao"
	"github.com/goharbor/harbor/src/common/models"
	"github.com/goharbor/harbor/src/common/utils/log"
	"github.com/goharbor/harbor/src/jobservice/logger"
)

// Manager defines the interface of CVE whitelist manager, it support both system level and project level whitelists
type Manager interface {
	// CreateEmpty creates empty whitelist for given project
	CreateEmpty(projectID int64) error
	// Set sets the whitelist for given project (create or update)
	Set(projectID int64, list models.CVEWhitelist) error
	// Get gets the whitelist for given project
	Get(projectID int64) (*models.CVEWhitelist, error)
	// SetSys sets system level whitelist
	SetSys(list models.CVEWhitelist) error
	// GetSys gets system level whitelist
	GetSys() (*models.CVEWhitelist, error)
}

type defaultManager struct{}

// CreateEmpty creates empty whitelist for given project
func (d *defaultManager) CreateEmpty(projectID int64) error {
	l := models.CVEWhitelist{
		ProjectID: projectID,
		Items:     []models.CVEWhitelistItem{},
	}
	_, err := dao.CreateCVEWhitelist(l)
	if err != nil {
		logger.Errorf("Failed to create empty CVE whitelist for project: %d, error: %v", projectID, err)
	}
	return err
}

// Set sets the whitelist for given project (create or update)
func (d *defaultManager) Set(projectID int64, list models.CVEWhitelist) error {
	list.ProjectID = projectID
	if err := Validate(list); err != nil {
		return err
	}
	_, err := dao.UpdateCVEWhitelist(list)
	return err
}

// Get gets the whitelist for given project
func (d *defaultManager) Get(projectID int64) (*models.CVEWhitelist, error) {
	wl, err := dao.GetCVEWhitelist(projectID)
	if wl == nil && err == nil {
		log.Debugf("No CVE whitelist found for project %d, returning empty list.", projectID)
		return &models.CVEWhitelist{ProjectID: projectID, Items: []models.CVEWhitelistItem{}}, nil
	}
	if wl.Items == nil {
		wl.Items = []models.CVEWhitelistItem{}
	}
	return wl, err
}

// SetSys sets the system level whitelist
func (d *defaultManager) SetSys(list models.CVEWhitelist) error {
	return d.Set(0, list)
}

// GetSys gets the system level whitelist
func (d *defaultManager) GetSys() (*models.CVEWhitelist, error) {
	return d.Get(0)
}

// NewDefaultManager return a new instance of defaultManager
func NewDefaultManager() Manager {
	return &defaultManager{}
}
<|endoftext|>


<commit_before>// Package binnedstats contains several structures and methods
// to facilitate the computation of statistics over partitions,
// such as histograms.
package binnedstats

// BinnedStats is a general purpose container representing a statistic
// function mapped across an arbitrary partition of the data.  When
// the statistic is just a count we have a histogram.
type BinnedStats struct {
	Bins []*Bin
}

func (bs *BinnedStats) Counts() []int {
	vals := make([]int, len(bs.Bins))
	for i, bin := range bs.Bins {
		vals[i] = bin.Count
	}
	return vals
}

func (bs *BinnedStats) Stats() []float64 {
	vals := make([]float64, len(bs.Bins))
	for i, bin := range bs.Bins {
		vals[i] = bin.Stat
	}
	return vals
}

func (bs *BinnedStats) Edges() []float64 {
	vals := make([]float64, len(bs.Bins))
	for i, bin := range bs.Bins {
		vals[i] = bin.RightEdge
	}
	return vals
}

func (bs *BinnedStats) Widths() []float64 {
	vals := make([]float64, len(bs.Bins))
	for i, bin := range bs.Bins {
		vals[i] = bin.Width
	}
	return vals
}

// A Bin containing statistics computed over a cell of some partition.
type Bin struct {
	LeftEdge  float64
	RightEdge float64
	Width     float64
	Count     int
	Stat      float64
}

// binnedStats computes the input statistic over each cell/bin of
// the input partition.
func binnedStats(par *Partition, stat func([]float64) float64) *BinnedStats {
	bins := par.Bins
	bs := &BinnedStats{
		Bins: make([]*Bin, len(bins)),
	}
	for i, bin := range bins {
		var leftEdge float64
		if i == 0 {
			leftEdge = par.Min
		} else {
			leftEdge = par.Edges[i-1]
		}
		bin := &Bin{
			Count:     len(bin),
			Stat:      stat(bin),
			LeftEdge:  leftEdge,
			RightEdge: par.Edges[i],
		}
		bin.Width = bin.RightEdge - bin.LeftEdge
		bs.Bins[i] = bin
	}
	return bs
}

func EqualWidthStats(data []float64, numBins int, stat func([]float64) float64) *BinnedStats {
	par := equalWidthPartition(data, numBins)
	return binnedStats(par, stat)
}
<commit_msg>Add json tags.<commit_after>// Package binnedstats contains several structures and methods
// to facilitate the computation of statistics over partitions,
// such as histograms.
package binnedstats

// BinnedStats is a general purpose container representing a statistic
// function mapped across an arbitrary partition of the data.  When
// the statistic is just a count we have a histogram.
type BinnedStats struct {
	Bins []*Bin `json:"bins"`
}

func (bs *BinnedStats) Counts() []int {
	vals := make([]int, len(bs.Bins))
	for i, bin := range bs.Bins {
		vals[i] = bin.Count
	}
	return vals
}

func (bs *BinnedStats) Stats() []float64 {
	vals := make([]float64, len(bs.Bins))
	for i, bin := range bs.Bins {
		vals[i] = bin.Stat
	}
	return vals
}

func (bs *BinnedStats) Edges() []float64 {
	vals := make([]float64, len(bs.Bins))
	for i, bin := range bs.Bins {
		vals[i] = bin.RightEdge
	}
	return vals
}

func (bs *BinnedStats) Widths() []float64 {
	vals := make([]float64, len(bs.Bins))
	for i, bin := range bs.Bins {
		vals[i] = bin.Width
	}
	return vals
}

// A Bin containing statistics computed over a cell of some partition.
type Bin struct {
	LeftEdge  float64 `json:"left_edge"`
	RightEdge float64 `json:"right_edge"`
	Width     float64 `json:"width"`
	Count     int     `json:"count"`
	Stat      float64 `json:"stat"`
}

// binnedStats computes the input statistic over each cell/bin of
// the input partition.
func binnedStats(par *Partition, stat func([]float64) float64) *BinnedStats {
	bins := par.Bins
	bs := &BinnedStats{
		Bins: make([]*Bin, len(bins)),
	}
	for i, bin := range bins {
		var leftEdge float64
		if i == 0 {
			leftEdge = par.Min
		} else {
			leftEdge = par.Edges[i-1]
		}
		bin := &Bin{
			Count:     len(bin),
			Stat:      stat(bin),
			LeftEdge:  leftEdge,
			RightEdge: par.Edges[i],
		}
		bin.Width = bin.RightEdge - bin.LeftEdge
		bs.Bins[i] = bin
	}
	return bs
}

func EqualWidthStats(data []float64, numBins int, stat func([]float64) float64) *BinnedStats {
	par := equalWidthPartition(data, numBins)
	return binnedStats(par, stat)
}
<|endoftext|>