<commit_before>// monitor.go contains methods of s/s/pps/server/api_server.go:APIServer that
// pertain to these fields of APIServer:
//   - monitorCancels,
//   - crashingMonitorCancels,
//   - pollCancel, and
//   - monitorCancelsMu (which protects all of the other fields).
//
// Other functions of APIServer.go should not access any of
// these fields directly (particularly APIServer.monitorCancelsMu, to avoid
// deadlocks) and should instead interact with monitorPipeline and such via
// methods in this file.
//
// Likewise, to avoid reentrancy deadlocks (A -> B -> A), methods in this file
// should avoid calling other methods of APIServer defined outside this file and
// shouldn't call each other.
package server

import (
	"context"
	"time"

	opentracing "github.com/opentracing/opentracing-go"
	log "github.com/sirupsen/logrus"
	"golang.org/x/sync/errgroup"

	"github.com/pachyderm/pachyderm/src/client"
	"github.com/pachyderm/pachyderm/src/client/pfs"
	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
	"github.com/pachyderm/pachyderm/src/client/pkg/tracing"
	"github.com/pachyderm/pachyderm/src/client/pkg/tracing/extended"
	"github.com/pachyderm/pachyderm/src/client/pps"
	"github.com/pachyderm/pachyderm/src/server/pkg/backoff"
	"github.com/pachyderm/pachyderm/src/server/pkg/ppsconsts"
	"github.com/pachyderm/pachyderm/src/server/pkg/ppsutil"
	workerserver "github.com/pachyderm/pachyderm/src/server/worker/server"
)

// startMonitorThread is a helper used by startMonitor, startCrashingMonitor,
// and startPipelinePoller. It doesn't manipulate any of APIServer's fields,
// just wrapps the passed function in a goroutine, and returns a cancel() fn to
// cancel it and block until it returns.
func startMonitorThread(parent context.Context, name string, f func(context.Context)) func() {
	ctx, cancel := context.WithCancel(parent)
	done := make(chan struct{})
	go func() {
		f(ctx)
		close(done)
	}()
	return func() {
		cancel()
		select {
		case <-done:
			return
		case <-time.After(time.Minute):
			// restart pod rather than permanently locking up the PPS master (which
			// would break the PPS API)
			panic(name + " blocked for over a minute after cancellation; restarting pod")
		}
	}
}

// startMonitor starts a new goroutine running monitorPipeline for
// 'pipelineInfo.Pipeline'.
//
// Every running pipeline with standby == true or a cron input has a
// corresponding goroutine running monitorPipeline() that puts the pipeline in
// and out of standby in response to new output commits appearing in that
// pipeline's output repo.
func (a *apiServer) startMonitor(ppsMasterCtx context.Context, pipelineInfo *pps.PipelineInfo) {
	pipeline := pipelineInfo.Pipeline.Name
	a.monitorCancelsMu.Lock()
	defer a.monitorCancelsMu.Unlock()
	if _, ok := a.monitorCancels[pipeline]; !ok {
		a.monitorCancels[pipeline] = startMonitorThread(
			ppsMasterCtx, "monitorPipeline for "+pipeline, func(ctx context.Context) {
				// monitorPipeline needs auth privileges to call subscribeCommit and
				// blockCommit
				// TODO(msteffen): run the pipeline monitor as the pipeline user, rather
				// than as the PPS superuser
				if err := a.sudo(a.env.GetPachClient(ctx),
					func(superUserClient *client.APIClient) error {
						a.monitorPipeline(superUserClient, pipelineInfo)
						return nil
					}); err != nil {
					log.Errorf("error monitoring %q: %v", pipeline, err)
				}
			})
	}
}

// cancelMonitor cancels the monitorPipeline goroutine for 'pipeline'. See
// a.startMonitor().
func (a *apiServer) cancelMonitor(pipeline string) {
	a.monitorCancelsMu.Lock()
	defer a.monitorCancelsMu.Unlock()
	if cancel, ok := a.monitorCancels[pipeline]; ok {
		cancel()
		delete(a.monitorCancels, pipeline)
	}
}

// startCrashingMonitor starts a new goroutine running monitorCrashingPipeline
// for 'pipelineInfo.Pipeline'
//
// Every crashing pipeline has a corresponding goro running
// monitorCrashingPipeline that checks to see if the issues have resolved
// themselves and moves the pipeline out of crashing if they have.
func (a *apiServer) startCrashingMonitor(ppsMasterCtx context.Context, parallelism uint64, pipelineInfo *pps.PipelineInfo) {
	pipeline := pipelineInfo.Pipeline.Name
	a.monitorCancelsMu.Lock()
	defer a.monitorCancelsMu.Unlock()
	if _, ok := a.crashingMonitorCancels[pipeline]; !ok {
		a.crashingMonitorCancels[pipeline] = startMonitorThread(ppsMasterCtx,
			"monitorCrashingPipeline for "+pipeline,
			func(ctx context.Context) {
				a.monitorCrashingPipeline(ctx, parallelism, pipelineInfo)
			})
	}
}

// cancelCrashingMonitor cancels the monitorCrashingPipeline goroutine for
// 'pipeline'. See a.startCrashingMonitor().
func (a *apiServer) cancelCrashingMonitor(pipeline string) {
	a.monitorCancelsMu.Lock()
	defer a.monitorCancelsMu.Unlock()
	if cancel, ok := a.crashingMonitorCancels[pipeline]; ok {
		cancel()
		delete(a.crashingMonitorCancels, pipeline)
	}
}

// cancelAllMonitorsAndCrashingMonitors overlaps with cancelMonitor and
// cancelCrashingMonitor, but also iterates over the existing members of
// a.{crashingM,m}onitorCancels in the critical section, so that all monitors
// can be cancelled without the risk that a new monitor is added between cancels
func (a *apiServer) cancelAllMonitorsAndCrashingMonitors() {
	// cancel all monitorPipeline goroutines
	a.monitorCancelsMu.Lock()
	defer a.monitorCancelsMu.Unlock()
	for _, c := range a.monitorCancels {
		c()
	}
	for _, c := range a.crashingMonitorCancels {
		c()
	}
	a.monitorCancels = make(map[string]func())
	a.crashingMonitorCancels = make(map[string]func())
}

//////////////////////////////////////////////////////////////////////////////
//                     Monitor Functions                                    //
// - These do not lock monitorCancelsMu, but they are called by the         //
//   functions above, which do. They can in turn call each other but cannot //
//   call any of the functions above or any functions outside this file (or //
//   else they will trigger a reentrancy deadlock:                          //
//                 A (lock succeeds) -> B -> A (lock fails)                 //
//////////////////////////////////////////////////////////////////////////////

func (a *apiServer) monitorPipeline(pachClient *client.APIClient, pipelineInfo *pps.PipelineInfo) {
	log.Printf("PPS master: monitoring pipeline %q", pipelineInfo.Pipeline.Name)
	var eg errgroup.Group
	pps.VisitInput(pipelineInfo.Input, func(in *pps.Input) {
		if in.Cron != nil {
			eg.Go(func() error {
				return backoff.RetryNotify(func() error {
					return a.makeCronCommits(pachClient, in)
				}, backoff.NewInfiniteBackOff(), notifyCtx(pachClient.Ctx(), "cron for "+in.Cron.Name))
			})
		}
	})
	if pipelineInfo.Standby {
		// Capacity 1 gives us a bit of buffer so we don't needlessly go into
		// standby when SubscribeCommit takes too long to return.
		ciChan := make(chan *pfs.CommitInfo, 1)
		eg.Go(func() error {
			return backoff.RetryNotify(func() error {
				return pachClient.SubscribeCommitF(pipelineInfo.Pipeline.Name, "",
					client.NewCommitProvenance(ppsconsts.SpecRepo, pipelineInfo.Pipeline.Name, pipelineInfo.SpecCommit.ID),
					"", pfs.CommitState_READY, func(ci *pfs.CommitInfo) error {
						ciChan <- ci
						return nil
					})
			}, backoff.NewInfiniteBackOff(), notifyCtx(pachClient.Ctx(), "SubscribeCommit"))
		})
		eg.Go(func() error {
			return backoff.RetryNotify(func() error {
				span, ctx := extended.AddPipelineSpanToAnyTrace(pachClient.Ctx(),
					a.env.GetEtcdClient(), pipelineInfo.Pipeline.Name, "/pps.Master/MonitorPipeline",
					"standby", pipelineInfo.Standby)
				if span != nil {
					pachClient = pachClient.WithCtx(ctx)
				}
				defer tracing.FinishAnySpan(span)

				if err := a.transitionPipelineState(pachClient.Ctx(),
					pipelineInfo.Pipeline.Name,
					pps.PipelineState_PIPELINE_RUNNING,
					pps.PipelineState_PIPELINE_STANDBY, ""); err != nil {

					pte := &ppsutil.PipelineTransitionError{}
					if errors.As(err, &pte) && pte.Current == pps.PipelineState_PIPELINE_PAUSED {
						// pipeline is stopped, exit monitorPipeline (which pausing the
						// pipeline should also do). monitorPipeline will be called when
						// it transitions back to running
						// TODO(msteffen): this should happen in the pipeline
						// controller
						return nil
					}
					return err
				}
				var (
					childSpan     opentracing.Span
					oldCtx        = ctx
					oldPachClient = pachClient
				)
				defer func() {
					tracing.FinishAnySpan(childSpan) // Finish any dangling children of 'span'
				}()
				for {
					// finish span from previous loops
					tracing.FinishAnySpan(childSpan)
					childSpan = nil

					var ci *pfs.CommitInfo
					select {
					case ci = <-ciChan:
						if ci.Finished != nil {
							continue
						}
						childSpan, ctx = tracing.AddSpanToAnyExisting(
							oldCtx, "/pps.Master/MonitorPipeline_SpinUp",
							"pipeline", pipelineInfo.Pipeline.Name, "commit", ci.Commit.ID)
						if childSpan != nil {
							pachClient = oldPachClient.WithCtx(ctx)
						}

						if err := a.transitionPipelineState(pachClient.Ctx(),
							pipelineInfo.Pipeline.Name,
							pps.PipelineState_PIPELINE_STANDBY,
							pps.PipelineState_PIPELINE_RUNNING, ""); err != nil {

							pte := &ppsutil.PipelineTransitionError{}
							if errors.As(err, &pte) && pte.Current == pps.PipelineState_PIPELINE_PAUSED {
								// pipeline is stopped, exit monitorPipeline (see above)
								return nil
							}
							return err
						}

						// Stay running while commits are available
					running:
						for {
							// Wait for the commit to be finished before blocking on the
							// job because the job may not exist yet.
							if _, err := pachClient.BlockCommit(ci.Commit.Repo.Name, ci.Commit.ID); err != nil {
								return err
							}
							if _, err := pachClient.InspectJobOutputCommit(ci.Commit.Repo.Name, ci.Commit.ID, true); err != nil {
								return err
							}

							select {
							case ci = <-ciChan:
							default:
								break running
							}
						}

						if err := a.transitionPipelineState(pachClient.Ctx(),
							pipelineInfo.Pipeline.Name,
							pps.PipelineState_PIPELINE_RUNNING,
							pps.PipelineState_PIPELINE_STANDBY, ""); err != nil {

							pte := &ppsutil.PipelineTransitionError{}
							if errors.As(err, &pte) && pte.Current == pps.PipelineState_PIPELINE_PAUSED {
								// pipeline is stopped; monitorPipeline will be called when it
								// transitions back to running
								// TODO(msteffen): this should happen in the pipeline
								// controller
								return nil
							}
							return err
						}
					case <-pachClient.Ctx().Done():
						return context.DeadlineExceeded
					}
				}
			}, backoff.NewInfiniteBackOff(), func(err error, d time.Duration) error {
				select {
				case <-pachClient.Ctx().Done():
					return context.DeadlineExceeded
				default:
					log.Printf("error in monitorPipeline: %v: retrying in: %v", err, d)
				}
				return nil
			})
		})
	}
	if err := eg.Wait(); err != nil {
		log.Printf("error in monitorPipeline: %v", err)
	}
}

// allWorkersUp is a helper used by monitorCrashingPipelinejkjk
func (a *apiServer) allWorkersUp(ctx context.Context, parallelism64 uint64, pipelineInfo *pps.PipelineInfo) (bool, error) {
	parallelism := int(parallelism64)
	if parallelism == 0 {
		parallelism = 1
	}
	workerPoolID := ppsutil.PipelineRcName(pipelineInfo.Pipeline.Name,
		pipelineInfo.Version)
	workerStatus, err := workerserver.Status(ctx, workerPoolID,
		a.env.GetEtcdClient(), a.etcdPrefix, a.workerGrpcPort)
	if err != nil {
		return false, err
	}
	return parallelism == len(workerStatus), nil
}

func (a *apiServer) monitorCrashingPipeline(ctx context.Context, parallelism uint64, pipelineInfo *pps.PipelineInfo) {
For:
	for {
		select {
		case <-time.After(crashingBackoff):
		case <-ctx.Done():
			break For
		}
		time.Sleep(crashingBackoff)
		workersUp, err := a.allWorkersUp(ctx, parallelism, pipelineInfo)
		if err != nil {
			log.Printf("error in monitorCrashingPipeline: %v", err)
			continue
		}
		if workersUp {
			if err := a.transitionPipelineState(ctx, pipelineInfo.Pipeline.Name,
				pps.PipelineState_PIPELINE_CRASHING,
				pps.PipelineState_PIPELINE_RUNNING, ""); err != nil {

				pte := &ppsutil.PipelineTransitionError{}
				if errors.As(err, &pte) && pte.Current == pps.PipelineState_PIPELINE_CRASHING {
					log.Print(err) // Pipeline has moved to STOPPED or been updated--give up
					return
				}
				log.Printf("error in monitorCrashingPipeline: %v", err)
				continue
			}
			break
		}
	}
}
<commit_msg>Close ciChan in monitorPipeline<commit_after>// monitor.go contains methods of s/s/pps/server/api_server.go:APIServer that
// pertain to these fields of APIServer:
//   - monitorCancels,
//   - crashingMonitorCancels,
//   - pollCancel, and
//   - monitorCancelsMu (which protects all of the other fields).
//
// Other functions of APIServer.go should not access any of
// these fields directly (particularly APIServer.monitorCancelsMu, to avoid
// deadlocks) and should instead interact with monitorPipeline and such via
// methods in this file.
//
// Likewise, to avoid reentrancy deadlocks (A -> B -> A), methods in this file
// should avoid calling other methods of APIServer defined outside this file and
// shouldn't call each other.
package server

import (
	"context"
	"time"

	opentracing "github.com/opentracing/opentracing-go"
	log "github.com/sirupsen/logrus"
	"golang.org/x/sync/errgroup"

	"github.com/pachyderm/pachyderm/src/client"
	"github.com/pachyderm/pachyderm/src/client/pfs"
	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
	"github.com/pachyderm/pachyderm/src/client/pkg/tracing"
	"github.com/pachyderm/pachyderm/src/client/pkg/tracing/extended"
	"github.com/pachyderm/pachyderm/src/client/pps"
	"github.com/pachyderm/pachyderm/src/server/pkg/backoff"
	"github.com/pachyderm/pachyderm/src/server/pkg/ppsconsts"
	"github.com/pachyderm/pachyderm/src/server/pkg/ppsutil"
	workerserver "github.com/pachyderm/pachyderm/src/server/worker/server"
)

// startMonitorThread is a helper used by startMonitor, startCrashingMonitor,
// and startPipelinePoller. It doesn't manipulate any of APIServer's fields,
// just wrapps the passed function in a goroutine, and returns a cancel() fn to
// cancel it and block until it returns.
func startMonitorThread(parent context.Context, name string, f func(context.Context)) func() {
	ctx, cancel := context.WithCancel(parent)
	done := make(chan struct{})
	go func() {
		f(ctx)
		close(done)
	}()
	return func() {
		cancel()
		select {
		case <-done:
			return
		case <-time.After(time.Minute):
			// restart pod rather than permanently locking up the PPS master (which
			// would break the PPS API)
			panic(name + " blocked for over a minute after cancellation; restarting pod")
		}
	}
}

// startMonitor starts a new goroutine running monitorPipeline for
// 'pipelineInfo.Pipeline'.
//
// Every running pipeline with standby == true or a cron input has a
// corresponding goroutine running monitorPipeline() that puts the pipeline in
// and out of standby in response to new output commits appearing in that
// pipeline's output repo.
func (a *apiServer) startMonitor(ppsMasterCtx context.Context, pipelineInfo *pps.PipelineInfo) {
	pipeline := pipelineInfo.Pipeline.Name
	a.monitorCancelsMu.Lock()
	defer a.monitorCancelsMu.Unlock()
	if _, ok := a.monitorCancels[pipeline]; !ok {
		a.monitorCancels[pipeline] = startMonitorThread(
			ppsMasterCtx, "monitorPipeline for "+pipeline, func(ctx context.Context) {
				// monitorPipeline needs auth privileges to call subscribeCommit and
				// blockCommit
				// TODO(msteffen): run the pipeline monitor as the pipeline user, rather
				// than as the PPS superuser
				if err := a.sudo(a.env.GetPachClient(ctx),
					func(superUserClient *client.APIClient) error {
						a.monitorPipeline(superUserClient, pipelineInfo)
						return nil
					}); err != nil {
					log.Errorf("error monitoring %q: %v", pipeline, err)
				}
			})
	}
}

// cancelMonitor cancels the monitorPipeline goroutine for 'pipeline'. See
// a.startMonitor().
func (a *apiServer) cancelMonitor(pipeline string) {
	a.monitorCancelsMu.Lock()
	defer a.monitorCancelsMu.Unlock()
	if cancel, ok := a.monitorCancels[pipeline]; ok {
		cancel()
		delete(a.monitorCancels, pipeline)
	}
}

// startCrashingMonitor starts a new goroutine running monitorCrashingPipeline
// for 'pipelineInfo.Pipeline'
//
// Every crashing pipeline has a corresponding goro running
// monitorCrashingPipeline that checks to see if the issues have resolved
// themselves and moves the pipeline out of crashing if they have.
func (a *apiServer) startCrashingMonitor(ppsMasterCtx context.Context, parallelism uint64, pipelineInfo *pps.PipelineInfo) {
	pipeline := pipelineInfo.Pipeline.Name
	a.monitorCancelsMu.Lock()
	defer a.monitorCancelsMu.Unlock()
	if _, ok := a.crashingMonitorCancels[pipeline]; !ok {
		a.crashingMonitorCancels[pipeline] = startMonitorThread(ppsMasterCtx,
			"monitorCrashingPipeline for "+pipeline,
			func(ctx context.Context) {
				a.monitorCrashingPipeline(ctx, parallelism, pipelineInfo)
			})
	}
}

// cancelCrashingMonitor cancels the monitorCrashingPipeline goroutine for
// 'pipeline'. See a.startCrashingMonitor().
func (a *apiServer) cancelCrashingMonitor(pipeline string) {
	a.monitorCancelsMu.Lock()
	defer a.monitorCancelsMu.Unlock()
	if cancel, ok := a.crashingMonitorCancels[pipeline]; ok {
		cancel()
		delete(a.crashingMonitorCancels, pipeline)
	}
}

// cancelAllMonitorsAndCrashingMonitors overlaps with cancelMonitor and
// cancelCrashingMonitor, but also iterates over the existing members of
// a.{crashingM,m}onitorCancels in the critical section, so that all monitors
// can be cancelled without the risk that a new monitor is added between cancels
func (a *apiServer) cancelAllMonitorsAndCrashingMonitors() {
	// cancel all monitorPipeline goroutines
	a.monitorCancelsMu.Lock()
	defer a.monitorCancelsMu.Unlock()
	for _, c := range a.monitorCancels {
		c()
	}
	for _, c := range a.crashingMonitorCancels {
		c()
	}
	a.monitorCancels = make(map[string]func())
	a.crashingMonitorCancels = make(map[string]func())
}

//////////////////////////////////////////////////////////////////////////////
//                     Monitor Functions                                    //
// - These do not lock monitorCancelsMu, but they are called by the         //
//   functions above, which do. They can in turn call each other but cannot //
//   call any of the functions above or any functions outside this file (or //
//   else they will trigger a reentrancy deadlock:                          //
//                 A (lock succeeds) -> B -> A (lock fails)                 //
//////////////////////////////////////////////////////////////////////////////

func (a *apiServer) monitorPipeline(pachClient *client.APIClient, pipelineInfo *pps.PipelineInfo) {
	log.Printf("PPS master: monitoring pipeline %q", pipelineInfo.Pipeline.Name)
	var eg errgroup.Group
	pps.VisitInput(pipelineInfo.Input, func(in *pps.Input) {
		if in.Cron != nil {
			eg.Go(func() error {
				return backoff.RetryNotify(func() error {
					return a.makeCronCommits(pachClient, in)
				}, backoff.NewInfiniteBackOff(), notifyCtx(pachClient.Ctx(), "cron for "+in.Cron.Name))
			})
		}
	})
	if pipelineInfo.Standby {
		// Capacity 1 gives us a bit of buffer so we don't needlessly go into
		// standby when SubscribeCommit takes too long to return.
		ciChan := make(chan *pfs.CommitInfo, 1)
		eg.Go(func() error {
			defer close(ciChan)
			return backoff.RetryNotify(func() error {
				return pachClient.SubscribeCommitF(pipelineInfo.Pipeline.Name, "",
					client.NewCommitProvenance(ppsconsts.SpecRepo, pipelineInfo.Pipeline.Name, pipelineInfo.SpecCommit.ID),
					"", pfs.CommitState_READY, func(ci *pfs.CommitInfo) error {
						ciChan <- ci
						return nil
					})
			}, backoff.NewInfiniteBackOff(), notifyCtx(pachClient.Ctx(), "SubscribeCommit"))
		})
		eg.Go(func() error {
			return backoff.RetryNotify(func() error {
				span, ctx := extended.AddPipelineSpanToAnyTrace(pachClient.Ctx(),
					a.env.GetEtcdClient(), pipelineInfo.Pipeline.Name, "/pps.Master/MonitorPipeline",
					"standby", pipelineInfo.Standby)
				if span != nil {
					pachClient = pachClient.WithCtx(ctx)
				}
				defer tracing.FinishAnySpan(span)

				if err := a.transitionPipelineState(pachClient.Ctx(),
					pipelineInfo.Pipeline.Name,
					pps.PipelineState_PIPELINE_RUNNING,
					pps.PipelineState_PIPELINE_STANDBY, ""); err != nil {

					pte := &ppsutil.PipelineTransitionError{}
					if errors.As(err, &pte) && pte.Current == pps.PipelineState_PIPELINE_PAUSED {
						// pipeline is stopped, exit monitorPipeline (which pausing the
						// pipeline should also do). monitorPipeline will be called when
						// it transitions back to running
						// TODO(msteffen): this should happen in the pipeline
						// controller
						return nil
					}
					return err
				}
				var (
					childSpan     opentracing.Span
					oldCtx        = ctx
					oldPachClient = pachClient
				)
				defer func() {
					tracing.FinishAnySpan(childSpan) // Finish any dangling children of 'span'
				}()
				for {
					// finish span from previous loops
					tracing.FinishAnySpan(childSpan)
					childSpan = nil

					var ci *pfs.CommitInfo
					select {
					case ci = <-ciChan:
						if ci.Finished != nil {
							continue
						}
						childSpan, ctx = tracing.AddSpanToAnyExisting(
							oldCtx, "/pps.Master/MonitorPipeline_SpinUp",
							"pipeline", pipelineInfo.Pipeline.Name, "commit", ci.Commit.ID)
						if childSpan != nil {
							pachClient = oldPachClient.WithCtx(ctx)
						}

						if err := a.transitionPipelineState(pachClient.Ctx(),
							pipelineInfo.Pipeline.Name,
							pps.PipelineState_PIPELINE_STANDBY,
							pps.PipelineState_PIPELINE_RUNNING, ""); err != nil {

							pte := &ppsutil.PipelineTransitionError{}
							if errors.As(err, &pte) && pte.Current == pps.PipelineState_PIPELINE_PAUSED {
								// pipeline is stopped, exit monitorPipeline (see above)
								return nil
							}
							return err
						}

						// Stay running while commits are available
					running:
						for {
							// Wait for the commit to be finished before blocking on the
							// job because the job may not exist yet.
							if _, err := pachClient.BlockCommit(ci.Commit.Repo.Name, ci.Commit.ID); err != nil {
								return err
							}
							if _, err := pachClient.InspectJobOutputCommit(ci.Commit.Repo.Name, ci.Commit.ID, true); err != nil {
								return err
							}

							select {
							case ci = <-ciChan:
							default:
								break running
							}
						}

						if err := a.transitionPipelineState(pachClient.Ctx(),
							pipelineInfo.Pipeline.Name,
							pps.PipelineState_PIPELINE_RUNNING,
							pps.PipelineState_PIPELINE_STANDBY, ""); err != nil {

							pte := &ppsutil.PipelineTransitionError{}
							if errors.As(err, &pte) && pte.Current == pps.PipelineState_PIPELINE_PAUSED {
								// pipeline is stopped; monitorPipeline will be called when it
								// transitions back to running
								// TODO(msteffen): this should happen in the pipeline
								// controller
								return nil
							}
							return err
						}
					case <-pachClient.Ctx().Done():
						return context.DeadlineExceeded
					}
				}
			}, backoff.NewInfiniteBackOff(), func(err error, d time.Duration) error {
				select {
				case <-pachClient.Ctx().Done():
					return context.DeadlineExceeded
				default:
					log.Printf("error in monitorPipeline: %v: retrying in: %v", err, d)
				}
				return nil
			})
		})
	}
	if err := eg.Wait(); err != nil {
		log.Printf("error in monitorPipeline: %v", err)
	}
}

// allWorkersUp is a helper used by monitorCrashingPipelinejkjk
func (a *apiServer) allWorkersUp(ctx context.Context, parallelism64 uint64, pipelineInfo *pps.PipelineInfo) (bool, error) {
	parallelism := int(parallelism64)
	if parallelism == 0 {
		parallelism = 1
	}
	workerPoolID := ppsutil.PipelineRcName(pipelineInfo.Pipeline.Name,
		pipelineInfo.Version)
	workerStatus, err := workerserver.Status(ctx, workerPoolID,
		a.env.GetEtcdClient(), a.etcdPrefix, a.workerGrpcPort)
	if err != nil {
		return false, err
	}
	return parallelism == len(workerStatus), nil
}

func (a *apiServer) monitorCrashingPipeline(ctx context.Context, parallelism uint64, pipelineInfo *pps.PipelineInfo) {
For:
	for {
		select {
		case <-time.After(crashingBackoff):
		case <-ctx.Done():
			break For
		}
		time.Sleep(crashingBackoff)
		workersUp, err := a.allWorkersUp(ctx, parallelism, pipelineInfo)
		if err != nil {
			log.Printf("error in monitorCrashingPipeline: %v", err)
			continue
		}
		if workersUp {
			if err := a.transitionPipelineState(ctx, pipelineInfo.Pipeline.Name,
				pps.PipelineState_PIPELINE_CRASHING,
				pps.PipelineState_PIPELINE_RUNNING, ""); err != nil {

				pte := &ppsutil.PipelineTransitionError{}
				if errors.As(err, &pte) && pte.Current == pps.PipelineState_PIPELINE_CRASHING {
					log.Print(err) // Pipeline has moved to STOPPED or been updated--give up
					return
				}
				log.Printf("error in monitorCrashingPipeline: %v", err)
				continue
			}
			break
		}
	}
}
<|endoftext|>


<commit_before>package osdconfig

import "time"

// NodesConfig contains all of node level data
// swagger:model
type NodesConfig []*NodeConfig

// NodeConfig is a node level config data
// swagger:model
type NodeConfig struct {
	NodeId      string         `json:"node_id,omitempty" enable:"true" hidden:"false" usage:"ID for the node"`
	CSIEndpoint string         `json:"csi_endpoint,omitempty" enable:"true" hidden:"false" usage:"CSI endpoint"`
	Network     *NetworkConfig `json:"network,omitempty" enable:"true" hidden:"false" usage:"Network configuration" description:"Configure network values for a node"`
	Storage     *StorageConfig `json:"storage,omitempty" enable:"true" hidden:"false" usage:"Storage configuration" description:"Configure storage values for a node"`
	Geo         *GeoConfig     `json:"geo,omitempty" enable:"true" hidden:"false" usage:"Geographic configuration" description:"Stores geo info for node"`
	Private     interface{}    `json:"private,omitempty" enable:"false" hidden:"false" usage:"Private node data"`
}

func (conf *NodeConfig) Init() *NodeConfig {
	conf.Network = new(NetworkConfig).Init()
	conf.Storage = new(StorageConfig).Init()
	conf.Geo = new(GeoConfig).Init()
	return conf
}

// KvdbConfig stores parameters defining kvdb configuration
// swagger:model
type KvdbConfig struct {
	Name               string   `json:"name,omitempty" enable:"true" hidden:"false" usage:"Name for kvdb"`
	Username           string   `json:"username,omitempty" enable:"true" hidden:"false" usage:"Username for kvdb"`
	Password           string   `json:"password,omitempty" enable:"true" hidden:"false" usage:"Passwd for kvdb"`
	CAFile             string   `json:"ca_file,omitempty" enable:"true" hidden:"false" usage:"CA file for kvdb"`
	CertFile           string   `json:"cert_file,omitempty" enable:"true" hidden:"false" usage:"Cert file for kvdb"`
	CertKeyFile        string   `json:"cert_key_file,omitempty" enable:"true" hidden:"false" usage:"Cert key file for kvdb"`
	TrustedCAFile      string   `json:"trusted_ca_file,omitempty" enable:"true" hidden:"false" usage:"Trusted CA file for kvdb"`
	ClientCertAuth     string   `json:"client_cert_auth,omitempty" enable:"true" hidden:"false" usage:"Client cert auth"`
	AclToken           string   `json:"acl_token,omitempty" enable:"true" hidden:"false" usage:"ACL token"`
	CAAuthAddress      string   `json:"ca_auth_address,omitempty" enable:"true" hidden:"false" usage:"Address of CA auth server (only for consul)"`
	InsecureSkipVerify bool     `json:"insecure_skip_verify,omitempty" enable:"true" hidden:"false" usage:"Insecure skip verify bool (only for consul)"`
	TransportScheme    string   `json:"transport_scheme,omitempty" enable:"true" hidden:"false" usage:"Transport method http or https (only for consul)"`
	Discovery          []string `json:"discovery,omitempty" enable:"true" hidden:"false" usage:"List of etcd endpoints"`
}

func (conf *KvdbConfig) Init() *KvdbConfig {
	conf.Discovery = make([]string, 0, 0)
	return conf
}

// ClusterConfig is a cluster level config parameter struct
// swagger:model
type ClusterConfig struct {
	Description string         `json:"description,omitempty" enable:"true" hidden:"false" usage:"Cluster description"`
	Mode        string         `json:"mode,omitempty" enable:"true" hidden:"false" usage:"Mode for cluster"`
	Version     string         `json:"version,omitempty" enable:"true" hidden:"false" usage:"Version info for cluster"`
	Created     time.Time      `json:"created,omitempty" enable:"true" hidden:"false" usage:"Creation info for cluster"`
	ClusterId   string         `json:"cluster_id,omitempty" enable:"true" hidden:"false" usage:"Cluster ID info"`
	Domain      string         `json:"domain,omitempty" enable:"true" hidden:"false" usage:"usage to be added"`
	Secrets     *SecretsConfig `json:"secrets,omitempty" enable:"true" hidden:"false" usage:"usage to be added" description:"description to be added"`
	Kvdb        *KvdbConfig    `json:"kvdb,omitempty" enable:"false" hidden:"false" usage:"usage to be added" description:"description to be added"`
	Private     interface{}    `json:"private,omitempty" enable:"true" hidden:"false" usage:"usage to be added"`
}

func (conf *ClusterConfig) Init() *ClusterConfig {
	conf.Secrets = new(SecretsConfig).Init()
	conf.Kvdb = new(KvdbConfig).Init()
	return conf
}

// NetworkConfig is a network configuration parameters struct
// swagger:model
type NetworkConfig struct {
	MgtIface  string `json:"mgt_interface,omitempty" enable:"true" hidden:"false" usage:"Management interface"`
	DataIface string `json:"data_interface,omitempty" enable:"true" hidden:"false" usage:"Data interface"`
}

func (conf *NetworkConfig) Init() *NetworkConfig {
	return conf
}

// GeoConfig holds geographic information
type GeoConfig struct {
	Rack   string `json:"rack,omitempty" enable:"true" hidden:"false" usage:"Rack info"`
	Zone   string `json:"zone,omitempty" enable:"true" hidden:"false" usage:"Zone info"`
	Region string `json:"region,omitempty" enable:"true" hidden:"false" usage:"Region info"`
}

func (conf *GeoConfig) Init() *GeoConfig {
	return conf
}

// SecretsConfig is a secrets configuration parameters struct
// swagger:model
type SecretsConfig struct {
	SecretType       string       `json:"secret_type,omitempty" enable:"true" hidden:"false" usage:"Secret type"`
	ClusterSecretKey string       `json:"cluster_secret_key,omitempty" enable:"true" hidden:"false" usage:"Secret key"`
	Vault            *VaultConfig `json:"vault,omitempty" enable:"true" hidden:"false" usage:"Vault configuration"`
	Aws              *AWSConfig   `json:"aws,omitempty" enable:"true" hidden:"false" usage:"AWS configuration"`
}

func (conf *SecretsConfig) Init() *SecretsConfig {
	conf.Vault = new(VaultConfig).Init()
	conf.Aws = new(AWSConfig).Init()
	return conf
}

// VaultConfig is a vault configuration parameters struct
// swagger:model
type VaultConfig struct {
	Token         string `json:"token,omitempty" enable:"true" hidden:"false" usage:"Vault token"`
	Address       string `json:"address,omitempty" enable:"true" hidden:"false" usage:"Vault address"`
	CACert        string `json:"ca_cert,omitempty" enable:"true" hidden:"false" usage:"Vault CA certificate"`
	CAPath        string `json:"ca_path,omitempty" enable:"true" hidden:"false" usage:"Vault CA path"`
	ClientCert    string `json:"client_cert,omitempty" enable:"true" hidden:"false" usage:"Vault client certificate"`
	ClientKey     string `json:"client_key,omitempty" enable:"true" hidden:"false" usage:"Vault client key"`
	TLSSkipVerify string `json:"skip_verify,omitempty" enable:"true" hidden:"false" usage:"Vault skip verification"`
	TLSServerName string `json:"tls_server_name,omitempty" enable:"true" hidden:"false" usage:"Vault TLS server name"`
	BasePath      string `json:"base_path,omitempty" enable:"true" hidden:"false" usage:"Vault base path"`
	BackendPath   string `json:"backend_path,omitempty" enable:"true" hidden:"false" usage:"Vault secrets backend mount path"`
}

func (conf *VaultConfig) Init() *VaultConfig {
	return conf
}

// AWS configuration parameters struct
// swagger:model
type AWSConfig struct {
	AccessKeyId     string `json:"aws_access_key_id,omitempty" enable:"true" hidden:"false" usage:"AWS access key ID"`
	SecretAccessKey string `json:"aws_secret_access_key,omitempty" enable:"true" hidden:"false" usage:"AWS secret access key"`
	SecretTokenKey  string `json:"aws_secret_token_key,omitempty" enable:"true" hidden:"false" usage:"AWS secret token key"`
	Cmk             string `json:"aws_cmk,omitempty" enable:"true" hidden:"false" usage:"AWS CMK"`
	Region          string `json:"aws_region,omitempty" enable:"true" hidden:"false" usage:"AWS region"`
}

func (conf *AWSConfig) Init() *AWSConfig {
	return conf
}

// StorageConfig is a storage configuration parameters struct
// swagger:model
type StorageConfig struct {
	DevicesMd        []string `json:"devices_md,omitempty" enable:"true" hidden:"false" usage:"Devices MD"`
	Devices          []string `json:"devices,omitempty" enable:"true" hidden:"false" usage:"Devices list"`
	MaxCount         uint32   `json:"max_count,omitempty" enable:"true" hidden:"false" usage:"Maximum count"`
	MaxDriveSetCount uint32   `json:"max_drive_set_count,omitempty" enable:"true" hidden:"false" usage:"Max drive set count"`
	RaidLevel        string   `json:"raid_level,omitempty" enable:"true" hidden:"false" usage:"RAID level info"`
	RaidLevelMd      string   `json:"raid_level_md,omitempty" enable:"true" hidden:"false" usage:"RAID level MD"`
}

func (conf *StorageConfig) Init() *StorageConfig {
	conf.Devices = make([]string, 0, 0)
	conf.Devices = make([]string, 0, 0)
	return conf
}
<commit_msg>Adding yaml tags to osdconfig<commit_after>package osdconfig

import "time"

// NodesConfig contains all of node level data
// swagger:model
type NodesConfig []*NodeConfig

// NodeConfig is a node level config data
// swagger:model
type NodeConfig struct {
	NodeId      string         `json:"node_id,omitempty" yaml:"node_id,omitempty" enable:"true" hidden:"false" usage:"ID for the node"`
	CSIEndpoint string         `json:"csi_endpoint,omitempty" yaml:"csi_endpoint,omitempty" enable:"true" hidden:"false" usage:"CSI endpoint"`
	Network     *NetworkConfig `json:"network,omitempty" yaml:"network,omitempty" enable:"true" hidden:"false" usage:"Network configuration" description:"Configure network values for a node"`
	Storage     *StorageConfig `json:"storage,omitempty" yaml:"storage,omitempty" enable:"true" hidden:"false" usage:"Storage configuration" description:"Configure storage values for a node"`
	Geo         *GeoConfig     `json:"geo,omitempty" yaml:"geo,omitempty" enable:"true" hidden:"false" usage:"Geographic configuration" description:"Stores geo info for node"`
	Private     interface{}    `json:"private,omitempty" yaml:"private,omitempty" enable:"false" hidden:"false" usage:"Private node data"`
}

func (conf *NodeConfig) Init() *NodeConfig {
	conf.Network = new(NetworkConfig).Init()
	conf.Storage = new(StorageConfig).Init()
	conf.Geo = new(GeoConfig).Init()
	return conf
}

// KvdbConfig stores parameters defining kvdb configuration
// swagger:model
type KvdbConfig struct {
	Name               string   `json:"name,omitempty" yaml:"name,omitempty" enable:"true" hidden:"false" usage:"Name for kvdb"`
	Username           string   `json:"username,omitempty" yaml:"username,omitempty" enable:"true" hidden:"false" usage:"Username for kvdb"`
	Password           string   `json:"password,omitempty" yaml:"password,omitempty" enable:"true" hidden:"false" usage:"Passwd for kvdb"`
	CAFile             string   `json:"ca_file,omitempty" yaml:"ca_file,omitempty" enable:"true" hidden:"false" usage:"CA file for kvdb"`
	CertFile           string   `json:"cert_file,omitempty" yaml:"cert_file,omitempty" enable:"true" hidden:"false" usage:"Cert file for kvdb"`
	CertKeyFile        string   `json:"cert_key_file,omitempty" yaml:"cert_key_file,omitempty" enable:"true" hidden:"false" usage:"Cert key file for kvdb"`
	TrustedCAFile      string   `json:"trusted_ca_file,omitempty" yaml:"trusted_ca_file,omitempty" enable:"true" hidden:"false" usage:"Trusted CA file for kvdb"`
	ClientCertAuth     string   `json:"client_cert_auth,omitempty" yaml:"client_cert_auth,omitempty" enable:"true" hidden:"false" usage:"Client cert auth"`
	AclToken           string   `json:"acl_token,omitempty" yaml:"acl_token,omitempty" enable:"true" hidden:"false" usage:"ACL token"`
	CAAuthAddress      string   `json:"ca_auth_address,omitempty" yaml:"ca_auth_address,omitempty" enable:"true" hidden:"false" usage:"Address of CA auth server (only for consul)"`
	InsecureSkipVerify bool     `json:"insecure_skip_verify,omitempty" yaml:"insecure_skip_verify,omitempty" enable:"true" hidden:"false" usage:"Insecure skip verify bool (only for consul)"`
	TransportScheme    string   `json:"transport_scheme,omitempty" yaml:"transport_scheme,omitempty" enable:"true" hidden:"false" usage:"Transport method http or https (only for consul)"`
	Discovery          []string `json:"discovery,omitempty" yaml:"discovery,omitempty" enable:"true" hidden:"false" usage:"List of etcd endpoints"`
}

func (conf *KvdbConfig) Init() *KvdbConfig {
	conf.Discovery = make([]string, 0, 0)
	return conf
}

// ClusterConfig is a cluster level config parameter struct
// swagger:model
type ClusterConfig struct {
	Description string         `json:"description,omitempty" yaml:"description,omitempty" enable:"true" hidden:"false" usage:"Cluster description"`
	Mode        string         `json:"mode,omitempty" yaml:"mode,omitempty" enable:"true" hidden:"false" usage:"Mode for cluster"`
	Version     string         `json:"version,omitempty" yaml:"version,omitempty" enable:"true" hidden:"false" usage:"Version info for cluster"`
	Created     time.Time      `json:"created,omitempty" yaml:"created,omitempty" enable:"true" hidden:"false" usage:"Creation info for cluster"`
	ClusterId   string         `json:"cluster_id,omitempty" yaml:"cluster_id,omitempty" enable:"true" hidden:"false" usage:"Cluster ID info"`
	Domain      string         `json:"domain,omitempty" yaml:"domain,omitempty" enable:"true" hidden:"false" usage:"usage to be added"`
	Secrets     *SecretsConfig `json:"secrets,omitempty" yaml:"secrets,omitempty" enable:"true" hidden:"false" usage:"usage to be added" description:"description to be added"`
	Kvdb        *KvdbConfig    `json:"kvdb,omitempty" yaml:"kvdb,omitempty" enable:"false" hidden:"false" usage:"usage to be added" description:"description to be added"`
	Private     interface{}    `json:"private,omitempty" yaml:"private,omitempty" enable:"true" hidden:"false" usage:"usage to be added"`
}

func (conf *ClusterConfig) Init() *ClusterConfig {
	conf.Secrets = new(SecretsConfig).Init()
	conf.Kvdb = new(KvdbConfig).Init()
	return conf
}

// NetworkConfig is a network configuration parameters struct
// swagger:model
type NetworkConfig struct {
	MgtIface  string `json:"mgt_interface,omitempty" yaml:"mgt_interface,omitempty" enable:"true" hidden:"false" usage:"Management interface"`
	DataIface string `json:"data_interface,omitempty" yaml:"data_interface,omitempty" enable:"true" hidden:"false" usage:"Data interface"`
}

func (conf *NetworkConfig) Init() *NetworkConfig {
	return conf
}

// GeoConfig holds geographic information
type GeoConfig struct {
	Rack   string `json:"rack,omitempty" yaml:"rack,omitempty" enable:"true" hidden:"false" usage:"Rack info"`
	Zone   string `json:"zone,omitempty" yaml:"zone,omitempty" enable:"true" hidden:"false" usage:"Zone info"`
	Region string `json:"region,omitempty" yaml:"region,omitempty" enable:"true" hidden:"false" usage:"Region info"`
}

func (conf *GeoConfig) Init() *GeoConfig {
	return conf
}

// SecretsConfig is a secrets configuration parameters struct
// swagger:model
type SecretsConfig struct {
	SecretType       string       `json:"secret_type,omitempty" yaml:"secret_type,omitempty" enable:"true" hidden:"false" usage:"Secret type"`
	ClusterSecretKey string       `json:"cluster_secret_key,omitempty" yaml:"cluster_secret_key,omitempty" enable:"true" hidden:"false" usage:"Secret key"`
	Vault            *VaultConfig `json:"vault,omitempty" yaml:"vault,omitempty" enable:"true" hidden:"false" usage:"Vault configuration"`
	Aws              *AWSConfig   `json:"aws,omitempty" yaml:"aws,omitempty" enable:"true" hidden:"false" usage:"AWS configuration"`
}

func (conf *SecretsConfig) Init() *SecretsConfig {
	conf.Vault = new(VaultConfig).Init()
	conf.Aws = new(AWSConfig).Init()
	return conf
}

// VaultConfig is a vault configuration parameters struct
// swagger:model
type VaultConfig struct {
	Token         string `json:"token,omitempty" yaml:"token,omitempty" enable:"true" hidden:"false" usage:"Vault token"`
	Address       string `json:"address,omitempty" yaml:"address,omitempty" enable:"true" hidden:"false" usage:"Vault address"`
	CACert        string `json:"ca_cert,omitempty" yaml:"ca_cert,omitempty" enable:"true" hidden:"false" usage:"Vault CA certificate"`
	CAPath        string `json:"ca_path,omitempty" yaml:"ca_path,omitempty" enable:"true" hidden:"false" usage:"Vault CA path"`
	ClientCert    string `json:"client_cert,omitempty" yaml:"client_cert,omitempty" enable:"true" hidden:"false" usage:"Vault client certificate"`
	ClientKey     string `json:"client_key,omitempty" yaml:"client_key,omitempty" enable:"true" hidden:"false" usage:"Vault client key"`
	TLSSkipVerify string `json:"skip_verify,omitempty" yaml:"skip_verify,omitempty" enable:"true" hidden:"false" usage:"Vault skip verification"`
	TLSServerName string `json:"tls_server_name,omitempty" yaml:"tls_server_name,omitempty" enable:"true" hidden:"false" usage:"Vault TLS server name"`
	BasePath      string `json:"base_path,omitempty" yaml:"base_path,omitempty" enable:"true" hidden:"false" usage:"Vault base path"`
	BackendPath   string `json:"backend_path,omitempty" yaml:"backend_path,omitempty" enable:"true" hidden:"false" usage:"Vault secrets backend mount path"`
}

func (conf *VaultConfig) Init() *VaultConfig {
	return conf
}

// AWS configuration parameters struct
// swagger:model
type AWSConfig struct {
	AccessKeyId     string `json:"aws_access_key_id,omitempty" yaml:"aws_access_key_id,omitempty" enable:"true" hidden:"false" usage:"AWS access key ID"`
	SecretAccessKey string `json:"aws_secret_access_key,omitempty" yaml:"aws_secret_access_key,omitempty" enable:"true" hidden:"false" usage:"AWS secret access key"`
	SecretTokenKey  string `json:"aws_secret_token_key,omitempty" yaml:"aws_secret_token_key,omitempty" enable:"true" hidden:"false" usage:"AWS secret token key"`
	Cmk             string `json:"aws_cmk,omitempty" yaml:"aws_cmk,omitempty" enable:"true" hidden:"false" usage:"AWS CMK"`
	Region          string `json:"aws_region,omitempty" yaml:"aws_region,omitempty" enable:"true" hidden:"false" usage:"AWS region"`
}

func (conf *AWSConfig) Init() *AWSConfig {
	return conf
}

// StorageConfig is a storage configuration parameters struct
// swagger:model
type StorageConfig struct {
	DevicesMd        []string `json:"devices_md,omitempty" yaml:"devices_md,omitempty" enable:"true" hidden:"false" usage:"Devices MD"`
	Devices          []string `json:"devices,omitempty" yaml:"devices,omitempty" enable:"true" hidden:"false" usage:"Devices list"`
	MaxCount         uint32   `json:"max_count,omitempty" yaml:"max_count,omitempty" enable:"true" hidden:"false" usage:"Maximum count"`
	MaxDriveSetCount uint32   `json:"max_drive_set_count,omitempty" yaml:"max_drive_set_count,omitempty" enable:"true" hidden:"false" usage:"Max drive set count"`
	RaidLevel        string   `json:"raid_level,omitempty" yaml:"raid_level,omitempty" enable:"true" hidden:"false" usage:"RAID level info"`
	RaidLevelMd      string   `json:"raid_level_md,omitempty" yaml:"raid_level_md,omitempty" enable:"true" hidden:"false" usage:"RAID level MD"`
}

func (conf *StorageConfig) Init() *StorageConfig {
	conf.Devices = make([]string, 0, 0)
	conf.Devices = make([]string, 0, 0)
	return conf
}
<|endoftext|>


<commit_before><commit_msg>fix(build-tools): typo<commit_after><|endoftext|>


<commit_before>package daemon

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"regexp"
	"strconv"

	"github.com/subgraph/oz"
	"github.com/subgraph/oz/ipc"
)

func clientConnect() (*ipc.MsgConn, error) {
	return ipc.Connect(GetSocketName(), messageFactory, nil)
}

func clientSend(msg interface{}) (*ipc.Message, error) {
	c, err := clientConnect()
	if err != nil {
		return nil, err
	}
	defer c.Close()
	rr, err := c.ExchangeMsg(msg)
	if err != nil {
		return nil, err
	}

	resp := <-rr.Chan()
	rr.Done()
	return resp, nil
}

func ListProfiles() ([]Profile, error) {
	resp, err := clientSend(new(ListProfilesMsg))
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListProfilesResp)
	if !ok {
		return nil, errors.New("ListProfiles response was not expected type")
	}
	return body.Profiles, nil
}

func ListForwarders(id int) ([]Forwarder, error) {
	resp, err := clientSend(&ListForwardersMsg{Id: id})
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListForwardersResp)
	if !ok {
		return nil, errors.New("ListForwarders response was not expected type")
	}
	return body.Forwarders, nil
}

func ListSandboxes() ([]SandboxInfo, error) {
	resp, err := clientSend(&ListSandboxesMsg{})
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListSandboxesResp)
	if !ok {
		return nil, errors.New("ListSandboxes response was not expected type")
	}
	return body.Sandboxes, nil
}

func ListBridges() ([]string, error) {
	resp, err := clientSend(&ListBridgesMsg{})
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListBridgesResp)
	if !ok {
		return nil, errors.New("ListBridges response was not expected type")
	}
	return body.Bridges, nil
}

func GetProfile(cpath string) (*oz.Profile, error) {
	groups, _ := os.Getgroups()
	gg := []uint32{}
	if len(groups) > 0 {
		gg = make([]uint32, len(groups))
		for i, v := range groups {
			gg[i] = uint32(v)
		}
	}
	resp, err := clientSend(&GetProfileMsg{
		Path: cpath,
		Gids: gg,
		Env:  os.Environ(),
	})
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*GetProfileResp)
	if !ok {
		return nil, errors.New("GetProfile response was not expected type")
	}
	p := new(oz.Profile)
	if err := json.Unmarshal([]byte(body.Profile), p); err != nil {
		return nil, err
	}
	return p, nil
}

func IsRunning(cpath string, args []string) (bool, error) {
	groups, _ := os.Getgroups()
	gg := []uint32{}
	if len(groups) > 0 {
		gg = make([]uint32, len(groups))
		for i, v := range groups {
			gg[i] = uint32(v)
		}
	}
	resp, err := clientSend(&IsRunningMsg{
		Path: cpath,
		Gids: gg,
		Args: args,
		Env:  os.Environ(),
	})
	if err != nil {
		return false, err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return false, fmt.Errorf("%s", body.Msg)
	case *OkMsg:
		return true, nil
	case *NotOkMsg:
		return false, nil
	default:
		return false, fmt.Errorf("Unexpected message received %+v", body)
	}
	return false, fmt.Errorf("Unexpected error occured")
}

func Launch(arg, cpath string, args []string, noexec, ephemeral bool) error {
	idx, name, err := parseProfileArg(arg)
	if err != nil {
		return err
	}
	pwd, _ := os.Getwd()
	groups, _ := os.Getgroups()
	gg := []uint32{}
	if len(groups) > 0 {
		gg = make([]uint32, len(groups))
		for i, v := range groups {
			gg[i] = uint32(v)
		}
	}
	resp, err := clientSend(&LaunchMsg{
		Index:     idx,
		Name:      name,
		Path:      cpath,
		Pwd:       pwd,
		Gids:      gg,
		Args:      args,
		Env:       os.Environ(),
		Noexec:    noexec,
		Ephemeral: ephemeral,
	})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		fmt.Printf("error was %s\n", body.Msg)
	case *OkMsg:
		fmt.Println("ok received from application launch request")
	default:
		fmt.Printf("Unexpected message received %+v", body)
	}
	return nil
}

func KillAllSandboxes() error {
	return KillSandbox(-1)
}

func KillSandbox(id int) error {
	resp, err := clientSend(&KillSandboxMsg{Id: id})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func RelaunchXpraClient(id int) error {
	resp, err := clientSend(&RelaunchXpraClientMsg{Id: id})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func RelaunchAllXpraClient() error {
	return RelaunchXpraClient(-1)
}

func MountFiles(id int, files []string, readOnly bool) error {
	mountFilesMsg := MountFilesMsg{
		Id:       id,
		Files:    files,
		ReadOnly: readOnly,
	}
	resp, err := clientSend(&mountFilesMsg)
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func UnmountFile(id int, file string) error {
	unmountFileMsg := UnmountFileMsg{
		Id:   id,
		File: file,
	}
	resp, err := clientSend(&unmountFileMsg)
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func AskForwarder(id int, name, port string) (string, error) {
	askForwarderMsg := AskForwarderMsg{
		Id:   id,
		Name: name,
		Port: port,
	}
	resp, err := clientSend(&askForwarderMsg)
	if err != nil {
		return "", err
	}
	body, ok := resp.Body.(*ForwarderSuccessMsg)
	if !ok {
		return "", fmt.Errorf("Unexpected message received %+v", body)
	} else {
		return body.Addr, nil
	}
}

func parseProfileArg(arg string) (int, string, error) {
	if len(arg) == 0 {
		return 0, "", errors.New("profile argument needed")
	}
	if n, err := strconv.Atoi(arg); err == nil {
		return n, "", nil
	}
	return 0, arg, nil
}

func Logs(count int, follow bool) (chan string, error) {
	c, err := clientConnect()
	if err != nil {
		return nil, err
	}
	rr, err := c.ExchangeMsg(&LogsMsg{Count: count, Follow: follow})
	if err != nil {
		return nil, err
	}
	out := make(chan string)
	go dumpLogs(out, rr)
	return out, nil
}

func dumpLogs(out chan<- string, rr ipc.ResponseReader) {
	for resp := range rr.Chan() {
		switch body := resp.Body.(type) {
		case *OkMsg:
			rr.Done()
			close(out)
			return
		case *LogData:
			for _, ll := range body.Lines {
				out <- ll
			}
		default:
			out <- fmt.Sprintf("Unexpected response type (%T)", body)
		}
	}
}

var isSocketName = regexp.MustCompile(`^@[A-Za-z0-9_-]+$`).MatchString
var sSocketName = ""

func GetSocketName() string {
	if sSocketName != "" {
		return sSocketName
	}
	bSockName := os.Getenv("SOCKET_NAME")

	if bSockName != "" {
		fmt.Println("Attempting to connect on custom socket provided through environment: ", bSockName)
		if bSockName[0:1] != "@" {
			bSockName = "@" + bSockName
		}
		if !isSocketName(bSockName) {
			fmt.Fprintf(os.Stderr, "Invalid socket name `%s`, reverting to `%s`\n", bSockName, SocketName)
			bSockName = SocketName
		}
	} else {
		bSockName = SocketName
	}
	sSocketName = bSockName
	return sSocketName
}
<commit_msg>Fixed socket name env var...<commit_after>package daemon

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"regexp"
	"strconv"

	"github.com/subgraph/oz"
	"github.com/subgraph/oz/ipc"
)

func clientConnect() (*ipc.MsgConn, error) {
	return ipc.Connect(GetSocketName(), messageFactory, nil)
}

func clientSend(msg interface{}) (*ipc.Message, error) {
	c, err := clientConnect()
	if err != nil {
		return nil, err
	}
	defer c.Close()
	rr, err := c.ExchangeMsg(msg)
	if err != nil {
		return nil, err
	}

	resp := <-rr.Chan()
	rr.Done()
	return resp, nil
}

func ListProfiles() ([]Profile, error) {
	resp, err := clientSend(new(ListProfilesMsg))
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListProfilesResp)
	if !ok {
		return nil, errors.New("ListProfiles response was not expected type")
	}
	return body.Profiles, nil
}

func ListForwarders(id int) ([]Forwarder, error) {
	resp, err := clientSend(&ListForwardersMsg{Id: id})
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListForwardersResp)
	if !ok {
		return nil, errors.New("ListForwarders response was not expected type")
	}
	return body.Forwarders, nil
}

func ListSandboxes() ([]SandboxInfo, error) {
	resp, err := clientSend(&ListSandboxesMsg{})
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListSandboxesResp)
	if !ok {
		return nil, errors.New("ListSandboxes response was not expected type")
	}
	return body.Sandboxes, nil
}

func ListBridges() ([]string, error) {
	resp, err := clientSend(&ListBridgesMsg{})
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListBridgesResp)
	if !ok {
		return nil, errors.New("ListBridges response was not expected type")
	}
	return body.Bridges, nil
}

func GetProfile(cpath string) (*oz.Profile, error) {
	groups, _ := os.Getgroups()
	gg := []uint32{}
	if len(groups) > 0 {
		gg = make([]uint32, len(groups))
		for i, v := range groups {
			gg[i] = uint32(v)
		}
	}
	resp, err := clientSend(&GetProfileMsg{
		Path: cpath,
		Gids: gg,
		Env:  os.Environ(),
	})
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*GetProfileResp)
	if !ok {
		return nil, errors.New("GetProfile response was not expected type")
	}
	p := new(oz.Profile)
	if err := json.Unmarshal([]byte(body.Profile), p); err != nil {
		return nil, err
	}
	return p, nil
}

func IsRunning(cpath string, args []string) (bool, error) {
	groups, _ := os.Getgroups()
	gg := []uint32{}
	if len(groups) > 0 {
		gg = make([]uint32, len(groups))
		for i, v := range groups {
			gg[i] = uint32(v)
		}
	}
	resp, err := clientSend(&IsRunningMsg{
		Path: cpath,
		Gids: gg,
		Args: args,
		Env:  os.Environ(),
	})
	if err != nil {
		return false, err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return false, fmt.Errorf("%s", body.Msg)
	case *OkMsg:
		return true, nil
	case *NotOkMsg:
		return false, nil
	default:
		return false, fmt.Errorf("Unexpected message received %+v", body)
	}
	return false, fmt.Errorf("Unexpected error occured")
}

func Launch(arg, cpath string, args []string, noexec, ephemeral bool) error {
	idx, name, err := parseProfileArg(arg)
	if err != nil {
		return err
	}
	pwd, _ := os.Getwd()
	groups, _ := os.Getgroups()
	gg := []uint32{}
	if len(groups) > 0 {
		gg = make([]uint32, len(groups))
		for i, v := range groups {
			gg[i] = uint32(v)
		}
	}
	resp, err := clientSend(&LaunchMsg{
		Index:     idx,
		Name:      name,
		Path:      cpath,
		Pwd:       pwd,
		Gids:      gg,
		Args:      args,
		Env:       os.Environ(),
		Noexec:    noexec,
		Ephemeral: ephemeral,
	})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		fmt.Printf("error was %s\n", body.Msg)
	case *OkMsg:
		fmt.Println("ok received from application launch request")
	default:
		fmt.Printf("Unexpected message received %+v", body)
	}
	return nil
}

func KillAllSandboxes() error {
	return KillSandbox(-1)
}

func KillSandbox(id int) error {
	resp, err := clientSend(&KillSandboxMsg{Id: id})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func RelaunchXpraClient(id int) error {
	resp, err := clientSend(&RelaunchXpraClientMsg{Id: id})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func RelaunchAllXpraClient() error {
	return RelaunchXpraClient(-1)
}

func MountFiles(id int, files []string, readOnly bool) error {
	mountFilesMsg := MountFilesMsg{
		Id:       id,
		Files:    files,
		ReadOnly: readOnly,
	}
	resp, err := clientSend(&mountFilesMsg)
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func UnmountFile(id int, file string) error {
	unmountFileMsg := UnmountFileMsg{
		Id:   id,
		File: file,
	}
	resp, err := clientSend(&unmountFileMsg)
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func AskForwarder(id int, name, port string) (string, error) {
	askForwarderMsg := AskForwarderMsg{
		Id:   id,
		Name: name,
		Port: port,
	}
	resp, err := clientSend(&askForwarderMsg)
	if err != nil {
		return "", err
	}
	body, ok := resp.Body.(*ForwarderSuccessMsg)
	if !ok {
		return "", fmt.Errorf("Unexpected message received %+v", body)
	} else {
		return body.Addr, nil
	}
}

func parseProfileArg(arg string) (int, string, error) {
	if len(arg) == 0 {
		return 0, "", errors.New("profile argument needed")
	}
	if n, err := strconv.Atoi(arg); err == nil {
		return n, "", nil
	}
	return 0, arg, nil
}

func Logs(count int, follow bool) (chan string, error) {
	c, err := clientConnect()
	if err != nil {
		return nil, err
	}
	rr, err := c.ExchangeMsg(&LogsMsg{Count: count, Follow: follow})
	if err != nil {
		return nil, err
	}
	out := make(chan string)
	go dumpLogs(out, rr)
	return out, nil
}

func dumpLogs(out chan<- string, rr ipc.ResponseReader) {
	for resp := range rr.Chan() {
		switch body := resp.Body.(type) {
		case *OkMsg:
			rr.Done()
			close(out)
			return
		case *LogData:
			for _, ll := range body.Lines {
				out <- ll
			}
		default:
			out <- fmt.Sprintf("Unexpected response type (%T)", body)
		}
	}
}

var isSocketName = regexp.MustCompile(`^@[A-Za-z0-9_-]+$`).MatchString
var sSocketName = ""

func GetSocketName() string {
	if sSocketName != "" {
		return sSocketName
	}
	bSockName := os.Getenv("OZ_SOCKET_NAME")

	if bSockName != "" {
		fmt.Println("Attempting to connect on custom socket provided through environment: ", bSockName)
		if bSockName[0:1] != "@" {
			bSockName = "@" + bSockName
		}
		if !isSocketName(bSockName) {
			fmt.Fprintf(os.Stderr, "Invalid socket name `%s`, reverting to `%s`\n", bSockName, SocketName)
			bSockName = SocketName
		}
	} else {
		bSockName = SocketName
	}
	sSocketName = bSockName
	return sSocketName
}
<|endoftext|>


<commit_before><commit_msg>update kubernetes locator using k8s client<commit_after><|endoftext|>


<commit_before>package pagination

import (
	"errors"
	"fmt"
	"net/http"
	"reflect"

	"github.com/rackspace/gophercloud"
)

var (
	// ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
	ErrPageNotAvailable = errors.New("The requested page does not exist.")
)

// Page must be satisfied by the result type of any resource collection.
// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs,
// instead.
// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
// will need to implement.
type Page interface {

	// NextPageURL generates the URL for the page of data that follows this collection.
	// Return "" if no such page exists.
	NextPageURL() (string, error)

	// IsEmpty returns true if this Page has no items in it.
	IsEmpty() (bool, error)

	// GetBody returns the Page Body. This is used in the `AllPages`.
	GetBody() interface{}
}

// Pager knows how to advance through a specific resource collection, one page at a time.
type Pager struct {
	client *gophercloud.ServiceClient

	initialURL string

	createPage func(r PageResult) Page

	Err error

	// Headers supplies additional HTTP headers to populate on each paged request.
	Headers map[string]string

	PageType Page
}

// NewPager constructs a manually-configured pager.
// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager {
	return Pager{
		client:     client,
		initialURL: initialURL,
		createPage: createPage,
	}
}

// WithPageCreator returns a new Pager that substitutes a different page creation function. This is
// useful for overriding List functions in delegation.
func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager {
	return Pager{
		client:     p.client,
		initialURL: p.initialURL,
		createPage: createPage,
	}
}

func (p Pager) fetchNextPage(url string) (Page, error) {
	resp, err := Request(p.client, p.Headers, url)
	if err != nil {
		return nil, err
	}

	remembered, err := PageResultFrom(resp)
	if err != nil {
		return nil, err
	}

	return p.createPage(remembered), nil
}

// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
// Return "false" from the handler to prematurely stop iterating.
func (p Pager) EachPage(handler func(Page) (bool, error)) error {
	if p.Err != nil {
		return p.Err
	}
	currentURL := p.initialURL
	for {
		currentPage, err := p.fetchNextPage(currentURL)
		if err != nil {
			return err
		}

		empty, err := currentPage.IsEmpty()
		if err != nil {
			return err
		}
		if empty {
			return nil
		}

		ok, err := handler(currentPage)
		if err != nil {
			return err
		}
		if !ok {
			return nil
		}

		currentURL, err = currentPage.NextPageURL()
		if err != nil {
			return err
		}
		if currentURL == "" {
			return nil
		}
	}
}

// AllPages returns all the pages from a `List` operation in a single page,
// allowing the user to retrieve all the pages at once.
func (p Pager) AllPages() (Page, error) {
	// Having a value of `nil` for `p.PageType` will cause a run-time error.
	if p.PageType == nil {
		return nil, fmt.Errorf("Pager field PageType must be set to successfully call pagination.AllPages method.")
	}
	// pagesSlice holds all the pages until they get converted into as Page Body.
	var pagesSlice []interface{}
	// body will contain the final concatenated Page body.
	var body reflect.Value

	// Grab a test page to ascertain the page body type.
	testPage, err := p.fetchNextPage(p.initialURL)
	if err != nil {
		return nil, err
	}

	// Switch on the page body type. Recognized types are `map[string]interface{}`
	// and `[]byte`.
	switch testPage.GetBody().(type) {
	case map[string]interface{}:
		// key is the map key for the page body if the body type is `map[string]interface{}`.
		var key string
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().(map[string]interface{})
			for k := range b {
				// If it's a linked page, we don't want the `links`, we want the other one.
				if k != "links" {
					key = k
				}
			}
			pagesSlice = append(pagesSlice, b[key].([]interface{})...)
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		// Set body to value of type `map[string]interface{}`
		body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice)))
		body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
	case []byte:
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().([]byte)
			pagesSlice = append(pagesSlice, b)
			// seperate pages with a comma
			pagesSlice = append(pagesSlice, []byte{10})
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		// Remove the trailing comma.
		pagesSlice = pagesSlice[:len(pagesSlice)-1]
		var b []byte
		// Combine the slice of slices in to a single slice.
		for _, slice := range pagesSlice {
			b = append(b, slice.([]byte)...)
		}
		// Set body to value of type `bytes`.
		body = reflect.New(reflect.TypeOf(b)).Elem()
		body.SetBytes(b)
	case []interface{}:
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().([]interface{})
			pagesSlice = append(pagesSlice, b...)
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice))
		for i, s := range pagesSlice {
			body.Index(i).Set(reflect.ValueOf(s))
		}
	default:
		return nil, fmt.Errorf("Page body has unrecognized type.")
	}

	// Each `Extract*` function is expecting a specific type of page coming back,
	// otherwise the type assertion in those functions will fail. PageType is needed
	// to create a type in this method that has the same type that the `Extract*`
	// function is expecting and set the Body of that object to the concatenated
	// pages.
	page := reflect.New(reflect.TypeOf(p.PageType))
	// Set the page body to be the concatenated pages.
	page.Elem().FieldByName("Body").Set(body)
	// Set any additional headers that were pass along. The `objectstorage` pacakge,
	// for example, passes a Content-Type header.
	h := make(http.Header)
	for k, v := range p.Headers {
		h.Add(k, v)
	}
	page.Elem().FieldByName("Header").Set(reflect.ValueOf(h))
	// Type assert the page to a Page interface so that the type assertion in the
	// `Extract*` methods will work.
	return page.Elem().Interface().(Page), err
}
<commit_msg>add more comments to AllPages method<commit_after>package pagination

import (
	"errors"
	"fmt"
	"net/http"
	"reflect"

	"github.com/rackspace/gophercloud"
)

var (
	// ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
	ErrPageNotAvailable = errors.New("The requested page does not exist.")
)

// Page must be satisfied by the result type of any resource collection.
// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs,
// instead.
// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
// will need to implement.
type Page interface {

	// NextPageURL generates the URL for the page of data that follows this collection.
	// Return "" if no such page exists.
	NextPageURL() (string, error)

	// IsEmpty returns true if this Page has no items in it.
	IsEmpty() (bool, error)

	// GetBody returns the Page Body. This is used in the `AllPages`.
	GetBody() interface{}
}

// Pager knows how to advance through a specific resource collection, one page at a time.
type Pager struct {
	client *gophercloud.ServiceClient

	initialURL string

	createPage func(r PageResult) Page

	Err error

	// Headers supplies additional HTTP headers to populate on each paged request.
	Headers map[string]string

	PageType Page
}

// NewPager constructs a manually-configured pager.
// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager {
	return Pager{
		client:     client,
		initialURL: initialURL,
		createPage: createPage,
	}
}

// WithPageCreator returns a new Pager that substitutes a different page creation function. This is
// useful for overriding List functions in delegation.
func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager {
	return Pager{
		client:     p.client,
		initialURL: p.initialURL,
		createPage: createPage,
	}
}

func (p Pager) fetchNextPage(url string) (Page, error) {
	resp, err := Request(p.client, p.Headers, url)
	if err != nil {
		return nil, err
	}

	remembered, err := PageResultFrom(resp)
	if err != nil {
		return nil, err
	}

	return p.createPage(remembered), nil
}

// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
// Return "false" from the handler to prematurely stop iterating.
func (p Pager) EachPage(handler func(Page) (bool, error)) error {
	if p.Err != nil {
		return p.Err
	}
	currentURL := p.initialURL
	for {
		currentPage, err := p.fetchNextPage(currentURL)
		if err != nil {
			return err
		}

		empty, err := currentPage.IsEmpty()
		if err != nil {
			return err
		}
		if empty {
			return nil
		}

		ok, err := handler(currentPage)
		if err != nil {
			return err
		}
		if !ok {
			return nil
		}

		currentURL, err = currentPage.NextPageURL()
		if err != nil {
			return err
		}
		if currentURL == "" {
			return nil
		}
	}
}

// AllPages returns all the pages from a `List` operation in a single page,
// allowing the user to retrieve all the pages at once.
func (p Pager) AllPages() (Page, error) {
	// Having a value of `nil` for `p.PageType` will cause a run-time error.
	if p.PageType == nil {
		return nil, fmt.Errorf("Pager field PageType must be set to successfully call pagination.AllPages method.")
	}
	// pagesSlice holds all the pages until they get converted into as Page Body.
	var pagesSlice []interface{}
	// body will contain the final concatenated Page body.
	var body reflect.Value

	// Grab a test page to ascertain the page body type.
	testPage, err := p.fetchNextPage(p.initialURL)
	if err != nil {
		return nil, err
	}

	// Switch on the page body type. Recognized types are `map[string]interface{}`,
	// `[]byte`, and `[]interface{}`.
	switch testPage.GetBody().(type) {
	case map[string]interface{}:
		// key is the map key for the page body if the body type is `map[string]interface{}`.
		var key string
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().(map[string]interface{})
			for k := range b {
				// If it's a linked page, we don't want the `links`, we want the other one.
				if k != "links" {
					key = k
				}
			}
			pagesSlice = append(pagesSlice, b[key].([]interface{})...)
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		// Set body to value of type `map[string]interface{}`
		body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice)))
		body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
	case []byte:
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().([]byte)
			pagesSlice = append(pagesSlice, b)
			// seperate pages with a comma
			pagesSlice = append(pagesSlice, []byte{10})
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		// Remove the trailing comma.
		pagesSlice = pagesSlice[:len(pagesSlice)-1]
		var b []byte
		// Combine the slice of slices in to a single slice.
		for _, slice := range pagesSlice {
			b = append(b, slice.([]byte)...)
		}
		// Set body to value of type `bytes`.
		body = reflect.New(reflect.TypeOf(b)).Elem()
		body.SetBytes(b)
	case []interface{}:
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().([]interface{})
			pagesSlice = append(pagesSlice, b...)
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		// Set body to value of type `[]interface{}`
		body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice))
		for i, s := range pagesSlice {
			body.Index(i).Set(reflect.ValueOf(s))
		}
	default:
		return nil, fmt.Errorf("Page body has unrecognized type.")
	}

	// Each `Extract*` function is expecting a specific type of page coming back,
	// otherwise the type assertion in those functions will fail. PageType is needed
	// to create a type in this method that has the same type that the `Extract*`
	// function is expecting and set the Body of that object to the concatenated
	// pages.
	page := reflect.New(reflect.TypeOf(p.PageType))
	// Set the page body to be the concatenated pages.
	page.Elem().FieldByName("Body").Set(body)
	// Set any additional headers that were pass along. The `objectstorage` pacakge,
	// for example, passes a Content-Type header.
	h := make(http.Header)
	for k, v := range p.Headers {
		h.Add(k, v)
	}
	page.Elem().FieldByName("Header").Set(reflect.ValueOf(h))
	// Type assert the page to a Page interface so that the type assertion in the
	// `Extract*` methods will work.
	return page.Elem().Interface().(Page), err
}
<|endoftext|>


<commit_before>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package refactor

import (
	"fmt"
	"reflect"
	"regexp"
	"testing"

	"github.com/TheThingsNetwork/ttn/utils/errors"
	. "github.com/TheThingsNetwork/ttn/utils/testing"
)

// Checks that two packets match
func checkPackets(t *testing.T, want Packet, got Packet) {
	if want == nil {
		if got == nil {
			Ok(t, "Check packets")
			return
		}
		Ko(t, "No packet was expected but got %s", got.String())
		return
	}

	if got == nil {
		Ko(t, "Was expecting %s but got nothing", want.String())
		return
	}

	if reflect.DeepEqual(want, got) {
		Ok(t, "Check packets")
		return
	}

	Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot:  \n%s", want.String(), got.String())
}

// Checks that errors match
func checkErrors(t *testing.T, want *string, got error) {
	if want == nil && got == nil || got.(errors.Failure).Nature == *want {
		Ok(t, "Check errors")
		return
	}

	Ko(t, "Expected error to be %s but got %v", want, got)
}

// Checks that obtained json matches expected one
func checkJSON(t *testing.T, want string, got []byte) {
	str := string(got)
	if str == want {
		Ok(t, "check JSON")
		return
	}
	Ko(t, "Marshaled data does not match expectations.\nWant: %s\nGot:  %s", want, str)
	return
}

// Checks that obtained metadata matches expected one
func checkMetadata(t *testing.T, want Metadata, got Metadata) {
	if reflect.DeepEqual(want, got) {
		Ok(t, "check Metadata")
		return
	}
	Ko(t, "Unmarshaled json does not match expectations. \nWant: %s\nGot:  %s", want.String(), got.String())
}

// Check that obtained json strings contains the required field
func checkFields(t *testing.T, want []string, got []byte) {
	for _, field := range want {
		ok, err := regexp.Match(fmt.Sprintf("\"%s\":", field), got)
		if !ok || err != nil {
			Ko(t, "Expected field %s in %s", field, string(got))
			return
		}
	}
	Ok(t, "Check fields")
}
<commit_msg>[refactor] Fix error checking in core<commit_after>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package refactor

import (
	"fmt"
	"reflect"
	"regexp"
	"testing"

	"github.com/TheThingsNetwork/ttn/utils/errors"
	. "github.com/TheThingsNetwork/ttn/utils/testing"
)

// Checks that two packets match
func checkPackets(t *testing.T, want Packet, got Packet) {
	if want == nil {
		if got == nil {
			Ok(t, "Check packets")
			return
		}
		Ko(t, "No packet was expected but got %s", got.String())
		return
	}

	if got == nil {
		Ko(t, "Was expecting %s but got nothing", want.String())
		return
	}

	if reflect.DeepEqual(want, got) {
		Ok(t, "Check packets")
		return
	}

	Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot:  \n%s", want.String(), got.String())
}

// Checks that errors match
func checkErrors(t *testing.T, want *string, got error) {
	if got == nil {
		if want == nil {
			Ok(t, "Check errors")
			return
		}
		Ko(t, "Expected error to be {%s} but got nothing", *want)
		return
	}

	if want == nil {
		Ko(t, "Expected no error but got {%v}", got)
		return
	}

	if got.(errors.Failure).Nature == *want {
		Ok(t, "Check errors")
		return
	}
	Ko(t, "Expected error to be {%s} but got {%v}", *want, got)
}

// Checks that obtained json matches expected one
func checkJSON(t *testing.T, want string, got []byte) {
	str := string(got)
	if str == want {
		Ok(t, "check JSON")
		return
	}
	Ko(t, "Marshaled data does not match expectations.\nWant: %s\nGot:  %s", want, str)
	return
}

// Checks that obtained metadata matches expected one
func checkMetadata(t *testing.T, want Metadata, got Metadata) {
	if reflect.DeepEqual(want, got) {
		Ok(t, "check Metadata")
		return
	}
	Ko(t, "Unmarshaled json does not match expectations. \nWant: %s\nGot:  %s", want.String(), got.String())
}

// Check that obtained json strings contains the required field
func checkFields(t *testing.T, want []string, got []byte) {
	for _, field := range want {
		ok, err := regexp.Match(fmt.Sprintf("\"%s\":", field), got)
		if !ok || err != nil {
			Ko(t, "Expected field %s in %s", field, string(got))
			return
		}
	}
	Ok(t, "Check fields")
}
<|endoftext|>


<commit_before>package tcp

import (
	"github.com/davyxu/cellnet"
	"github.com/davyxu/cellnet/peer"
	"github.com/davyxu/cellnet/util"
	"net"
	"sync"
	"sync/atomic"
	"time"
)

// Socket会话
type tcpSession struct {
	peer.CoreContextSet
	peer.CoreSessionIdentify
	*peer.CoreProcBundle

	pInterface cellnet.Peer

	// Socket原始连接
	conn net.Conn

	// 退出同步器
	exitSync sync.WaitGroup

	// 发送队列
	sendQueue *cellnet.Pipe

	cleanupGuard sync.Mutex

	endNotify func()

	closing int64
}

func (self *tcpSession) Peer() cellnet.Peer {
	return self.pInterface
}

// 取原始连接
func (self *tcpSession) Raw() interface{} {
	return self.conn
}

func (self *tcpSession) Close() {

	closing := atomic.SwapInt64(&self.closing, 1)
	if closing != 0 {
		return
	}

	if self.conn != nil {
		// 关闭读
		con := self.conn.(*net.TCPConn)
		// 关闭读
		con.CloseRead()
		// 手动读超时
		con.SetReadDeadline(time.Now())
	}
}

// 发送封包
func (self *tcpSession) Send(msg interface{}) {

	// 只能通过Close关闭连接
	if msg == nil {
		return
	}

	// 已经关闭，不再发送
	if self.IsManualClosed() {
		return
	}

	self.sendQueue.Add(msg)
}

func (self *tcpSession) IsManualClosed() bool {
	return atomic.LoadInt64(&self.closing) != 0
}

func (self *tcpSession) protectedReadMessage() (msg interface{}, err error) {

	defer func() {

		if err := recover(); err != nil {
			log.Errorf("IO panic: %s", err)
			self.conn.Close()
		}

	}()

	msg, err = self.ReadMessage(self)

	return
}

// 接收循环
func (self *tcpSession) recvLoop() {

	var capturePanic bool

	if i, ok := self.Peer().(cellnet.PeerCaptureIOPanic); ok {
		capturePanic = i.CaptureIOPanic()
	}

	for self.conn != nil {

		var msg interface{}
		var err error

		if capturePanic {
			msg, err = self.protectedReadMessage()
		} else {
			msg, err = self.ReadMessage(self)
		}

		if err != nil {
			if !util.IsEOFOrNetReadError(err) {
				log.Errorf("session closed, sesid: %d, err: %s", self.ID(), err)
			}

			self.sendQueue.Add(nil)

			// 标记为手动关闭原因
			closedMsg := &cellnet.SessionClosed{}
			if self.IsManualClosed() {
				closedMsg.Reason = cellnet.CloseReason_Manual
			}

			self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self, Msg: closedMsg})
			break
		}

		self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self, Msg: msg})
	}

	// 通知完成
	self.exitSync.Done()
}

// 发送循环
func (self *tcpSession) sendLoop() {

	var writeList []interface{}

	for {
		writeList = writeList[0:0]
		exit := self.sendQueue.Pick(&writeList)

		// 遍历要发送的数据
		for _, msg := range writeList {

			self.SendMessage(&cellnet.SendMsgEvent{self, msg})
		}

		if exit {
			break
		}
	}

	// 完整关闭
	self.conn.Close()

	// 通知完成
	self.exitSync.Done()
}

// 启动会话的各种资源
func (self *tcpSession) Start() {

	atomic.StoreInt64(&self.closing, 0)

	// connector复用session时，上一次发送队列未释放可能造成问题
	self.sendQueue.Reset()

	// 需要接收和发送线程同时完成时才算真正的完成
	self.exitSync.Add(2)

	go func() {

		// 等待2个任务结束
		self.exitSync.Wait()

		// 将会话从管理器移除
		self.Peer().(peer.SessionManager).Remove(self)

		if self.endNotify != nil {
			self.endNotify()
		}

	}()

	// 启动并发接收goroutine
	go self.recvLoop()

	// 启动并发发送goroutine
	go self.sendLoop()

	// 将会话添加到管理器
	self.Peer().(peer.SessionManager).Add(self)
}

func newSession(conn net.Conn, p cellnet.Peer, endNotify func()) *tcpSession {
	self := &tcpSession{
		conn:       conn,
		endNotify:  endNotify,
		sendQueue:  cellnet.NewPipe(),
		pInterface: p,
		CoreProcBundle: p.(interface {
			GetBundle() *peer.CoreProcBundle
		}).GetBundle(),
	}

	return self
}
<commit_msg>处理id竞态问题<commit_after>package tcp

import (
	"github.com/davyxu/cellnet"
	"github.com/davyxu/cellnet/peer"
	"github.com/davyxu/cellnet/util"
	"net"
	"sync"
	"sync/atomic"
	"time"
)

// Socket会话
type tcpSession struct {
	peer.CoreContextSet
	peer.CoreSessionIdentify
	*peer.CoreProcBundle

	pInterface cellnet.Peer

	// Socket原始连接
	conn net.Conn

	// 退出同步器
	exitSync sync.WaitGroup

	// 发送队列
	sendQueue *cellnet.Pipe

	cleanupGuard sync.Mutex

	endNotify func()

	closing int64
}

func (self *tcpSession) Peer() cellnet.Peer {
	return self.pInterface
}

// 取原始连接
func (self *tcpSession) Raw() interface{} {
	return self.conn
}

func (self *tcpSession) Close() {

	closing := atomic.SwapInt64(&self.closing, 1)
	if closing != 0 {
		return
	}

	if self.conn != nil {
		// 关闭读
		con := self.conn.(*net.TCPConn)
		// 关闭读
		con.CloseRead()
		// 手动读超时
		con.SetReadDeadline(time.Now())
	}
}

// 发送封包
func (self *tcpSession) Send(msg interface{}) {

	// 只能通过Close关闭连接
	if msg == nil {
		return
	}

	// 已经关闭，不再发送
	if self.IsManualClosed() {
		return
	}

	self.sendQueue.Add(msg)
}

func (self *tcpSession) IsManualClosed() bool {
	return atomic.LoadInt64(&self.closing) != 0
}

func (self *tcpSession) protectedReadMessage() (msg interface{}, err error) {

	defer func() {

		if err := recover(); err != nil {
			log.Errorf("IO panic: %s", err)
			self.conn.Close()
		}

	}()

	msg, err = self.ReadMessage(self)

	return
}

// 接收循环
func (self *tcpSession) recvLoop() {

	var capturePanic bool

	if i, ok := self.Peer().(cellnet.PeerCaptureIOPanic); ok {
		capturePanic = i.CaptureIOPanic()
	}

	for self.conn != nil {

		var msg interface{}
		var err error

		if capturePanic {
			msg, err = self.protectedReadMessage()
		} else {
			msg, err = self.ReadMessage(self)
		}

		if err != nil {
			if !util.IsEOFOrNetReadError(err) {
				log.Errorf("session closed, sesid: %d, err: %s", self.ID(), err)
			}

			self.sendQueue.Add(nil)

			// 标记为手动关闭原因
			closedMsg := &cellnet.SessionClosed{}
			if self.IsManualClosed() {
				closedMsg.Reason = cellnet.CloseReason_Manual
			}

			self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self, Msg: closedMsg})
			break
		}

		self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self, Msg: msg})
	}

	// 通知完成
	self.exitSync.Done()
}

// 发送循环
func (self *tcpSession) sendLoop() {

	var writeList []interface{}

	for {
		writeList = writeList[0:0]
		exit := self.sendQueue.Pick(&writeList)

		// 遍历要发送的数据
		for _, msg := range writeList {

			self.SendMessage(&cellnet.SendMsgEvent{self, msg})
		}

		if exit {
			break
		}
	}

	// 完整关闭
	self.conn.Close()

	// 通知完成
	self.exitSync.Done()
}

// 启动会话的各种资源
func (self *tcpSession) Start() {

	atomic.StoreInt64(&self.closing, 0)

	// connector复用session时，上一次发送队列未释放可能造成问题
	self.sendQueue.Reset()

	// 需要接收和发送线程同时完成时才算真正的完成
	self.exitSync.Add(2)

	// 将会话添加到管理器, 在线程处理前添加到管理器(分配id), 避免ID还未分配,就开始使用id的竞态问题
	self.Peer().(peer.SessionManager).Add(self)

	go func() {

		// 等待2个任务结束
		self.exitSync.Wait()

		// 将会话从管理器移除
		self.Peer().(peer.SessionManager).Remove(self)

		if self.endNotify != nil {
			self.endNotify()
		}

	}()

	// 启动并发接收goroutine
	go self.recvLoop()

	// 启动并发发送goroutine
	go self.sendLoop()
}

func newSession(conn net.Conn, p cellnet.Peer, endNotify func()) *tcpSession {
	self := &tcpSession{
		conn:       conn,
		endNotify:  endNotify,
		sendQueue:  cellnet.NewPipe(),
		pInterface: p,
		CoreProcBundle: p.(interface {
			GetBundle() *peer.CoreProcBundle
		}).GetBundle(),
	}

	return self
}
<|endoftext|>


<commit_before>package pep

//go:generate bash -c "mkdir -p $GOPATH/src/github.com/infobloxopen/themis/pdp-service && protoc -I $GOPATH/src/github.com/infobloxopen/themis/proto/ $GOPATH/src/github.com/infobloxopen/themis/proto/service.proto --go_out=plugins=grpc:$GOPATH/src/github.com/infobloxopen/themis/pdp-service && ls $GOPATH/src/github.com/infobloxopen/themis/pdp-service"

import (
	"fmt"
	"sync"

	"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
	ot "github.com/opentracing/opentracing-go"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pb "github.com/infobloxopen/themis/pdp-service"
)

type unaryClient struct {
	lock   *sync.RWMutex
	conn   *grpc.ClientConn
	client *pb.PDPClient

	opts options
}

func newUnaryClient(opts options) *unaryClient {
	return &unaryClient{
		lock: &sync.RWMutex{},
		opts: opts,
	}
}

func (c *unaryClient) Connect(addr string) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	if c.conn != nil {
		return ErrorConnected
	}

	opts := []grpc.DialOption{
		grpc.WithInsecure(),
	}

	if len(c.opts.addresses) > 0 {
		addr = virtualServerAddress
		switch c.opts.balancer {
		default:
			panic(fmt.Errorf("Invalid balancer %d", c.opts.balancer))

		case roundRobinBalancer:
			opts = append(opts, grpc.WithBalancer(grpc.RoundRobin(newStaticResolver(addr, c.opts.addresses...))))

		case hotSpotBalancer:
			return ErrorHotSpotBalancerUnsupported
		}
	}

	if c.opts.tracer != nil {
		opts = append(opts,
			grpc.WithUnaryInterceptor(
				otgrpc.OpenTracingClientInterceptor(
					c.opts.tracer,
					otgrpc.IncludingSpans(
						func(parentSpanCtx ot.SpanContext, method string, req, resp interface{}) bool {
							return parentSpanCtx != nil
						},
					),
				),
			),
		)
	}

	conn, err := grpc.Dial(addr, opts...)
	if err != nil {
		return err
	}

	c.conn = conn

	client := pb.NewPDPClient(c.conn)
	c.client = &client

	return nil
}

func (c *unaryClient) Close() {
	c.lock.Lock()
	defer c.lock.Unlock()

	if c.conn != nil {
		c.conn.Close()
		c.conn = nil
	}

	c.client = nil
}

func (c *unaryClient) Validate(in, out interface{}) error {
	c.lock.RLock()
	uc := c.client
	c.lock.RUnlock()

	if uc == nil {
		return ErrorNotConnected
	}

	req, err := makeRequest(in)
	if err != nil {
		return err
	}

	res, err := (*uc).Validate(context.Background(), &req)
	if err != nil {
		return err
	}

	return fillResponse(res, out)
}
<commit_msg>Fixed validation failure for unary gRPC (#93)<commit_after>package pep

//go:generate bash -c "mkdir -p $GOPATH/src/github.com/infobloxopen/themis/pdp-service && protoc -I $GOPATH/src/github.com/infobloxopen/themis/proto/ $GOPATH/src/github.com/infobloxopen/themis/proto/service.proto --go_out=plugins=grpc:$GOPATH/src/github.com/infobloxopen/themis/pdp-service && ls $GOPATH/src/github.com/infobloxopen/themis/pdp-service"

import (
	"fmt"
	"sync"

	"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
	ot "github.com/opentracing/opentracing-go"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pb "github.com/infobloxopen/themis/pdp-service"
)

type unaryClient struct {
	lock   *sync.RWMutex
	conn   *grpc.ClientConn
	client *pb.PDPClient

	opts options
}

func newUnaryClient(opts options) *unaryClient {
	return &unaryClient{
		lock: &sync.RWMutex{},
		opts: opts,
	}
}

func (c *unaryClient) Connect(addr string) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	if c.conn != nil {
		return ErrorConnected
	}

	opts := []grpc.DialOption{
		grpc.WithInsecure(),
	}

	if len(c.opts.addresses) > 0 {
		addr = virtualServerAddress
		switch c.opts.balancer {
		default:
			panic(fmt.Errorf("Invalid balancer %d", c.opts.balancer))

		case roundRobinBalancer:
			opts = append(opts, grpc.WithBalancer(grpc.RoundRobin(newStaticResolver(addr, c.opts.addresses...))))

		case hotSpotBalancer:
			return ErrorHotSpotBalancerUnsupported
		}
	}

	if c.opts.tracer != nil {
		opts = append(opts,
			grpc.WithUnaryInterceptor(
				otgrpc.OpenTracingClientInterceptor(
					c.opts.tracer,
					otgrpc.IncludingSpans(
						func(parentSpanCtx ot.SpanContext, method string, req, resp interface{}) bool {
							return parentSpanCtx != nil
						},
					),
				),
			),
		)
	}

	conn, err := grpc.Dial(addr, opts...)
	if err != nil {
		return err
	}

	c.conn = conn

	client := pb.NewPDPClient(c.conn)
	c.client = &client

	return nil
}

func (c *unaryClient) Close() {
	c.lock.Lock()
	defer c.lock.Unlock()

	if c.conn != nil {
		c.conn.Close()
		c.conn = nil
	}

	c.client = nil
}

func (c *unaryClient) Validate(in, out interface{}) error {
	c.lock.RLock()
	uc := c.client
	c.lock.RUnlock()

	if uc == nil {
		return ErrorNotConnected
	}

	req, err := makeRequest(in)
	if err != nil {
		return err
	}

	res, err := (*uc).Validate(context.Background(), &req, grpc.FailFast(false))
	if err != nil {
		return err
	}

	return fillResponse(res, out)
}
<|endoftext|>


<commit_before>package logger

import (
	"bytes"
	"fmt"
	"github.com/Sirupsen/logrus"
	"github.com/kr/pretty"
	"runtime"
	"sort"
	"strings"
	"time"
)

const (
	nocolor = 0
	red     = 31
	green   = 32
	yellow  = 33
	blue    = 36
	gray    = 37
	debug   = 33
	nsColor = 100
)

var (
	baseTimestamp time.Time
	isTerminal    bool
)

func init() {
	baseTimestamp = time.Now()
	isTerminal = logrus.IsTerminal()
}

type TextFormatter struct {
	// Set to true to bypass checking for a TTY before outputting colors.
	ForceColors bool

	// Force disabling colors.
	DisableColors bool

	// Disable timestamp logging. useful when output is redirected to logging
	// system that already adds timestamps.
	DisableTimestamp bool

	// Enable logging the full timestamp when a TTY is attached instead of just
	// the time passed since beginning of execution.
	FullTimestamp bool

	// TimestampFormat to use for display when a full timestamp is printed
	TimestampFormat string

	// The fields are sorted by default for a consistent output. For applications
	// that log extremely frequently and don't use the JSON formatter this may not
	// be desired.
	DisableSorting bool
}

func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	var b *bytes.Buffer
	keys := make([]string, 0, len(entry.Data))
	for k := range entry.Data {
		if k != "ns" {
			keys = append(keys, k)
		}
	}
	ns := entry.Data["ns"]

	if !f.DisableSorting {
		sort.Strings(keys)
	}
	if entry.Buffer != nil {
		b = entry.Buffer
	} else {
		b = &bytes.Buffer{}
	}

	prefixFieldClashes(entry.Data)

	isColorTerminal := isTerminal && (runtime.GOOS != "windows")
	isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors

	timestampFormat := f.TimestampFormat
	if timestampFormat == "" {
		timestampFormat = logrus.DefaultTimestampFormat
	}
	if isColored {
		f.printColored(b, entry, keys, timestampFormat, ns.(string))
	} else {
		if !f.DisableTimestamp {
			f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
		}
		f.appendKeyValue(b, "level", entry.Level.String())
		if entry.Message != "" {
			f.appendKeyValue(b, "msg", entry.Message)
		}
		for _, key := range keys {
			f.appendKeyValue(b, key, entry.Data[key])
		}
	}

	b.WriteByte('\n')
	return b.Bytes(), nil
}

func (f *TextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string, ns string) {
	var levelColor int
	switch entry.Level {
	case logrus.DebugLevel:
		levelColor = debug
	case logrus.WarnLevel:
		levelColor = yellow
	case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
		levelColor = red
	default:
		levelColor = blue
	}

	levelText := strings.ToUpper(entry.Level.String())[0:4]

	if f.DisableTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m \x1b[%dm%-10s\x1b[0m %-30s ", levelColor, levelText, nsColor, ns, entry.Message)
	} else if !f.FullTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] \x1b[%dm%-10s\x1b[0m %-30s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), nsColor, ns, entry.Message)
	} else {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] \x1b[%dm%-10s\x1b[0m %-30s ", levelColor, levelText, entry.Time.Format(timestampFormat), nsColor, ns, entry.Message)
	}
	for _, k := range keys {
		v := entry.Data[k]
		switch v.(type) {
		case string:
		case int:
		case int8:
		case int16:
		case int32:
		case int64:
		case uint8:
		case uint16:
		case uint32:
		case uint64:
		case complex64:
		case complex128:
		case float32:
		case float64:
		case bool:
		case fmt.Stringer:
		case error:
		default:
			v = pretty.Sprint(v)
		}
		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
		//f.appendValue(b, v)
	}
}

func needsQuoting(text string) bool {
	for _, ch := range text {
		if !((ch >= 'a' && ch <= 'z') ||
			(ch >= 'A' && ch <= 'Z') ||
			(ch >= '0' && ch <= '9') ||
			ch == '-' || ch == '.') {
			return true
		}
	}
	return false
}

func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {

	b.WriteString(key)
	b.WriteByte('=')
	f.appendValue(b, value)
	b.WriteByte(' ')
}

func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
	switch value := value.(type) {
	case string:
		if !needsQuoting(value) {
			b.WriteString(value)
		} else {
			fmt.Fprintf(b, "%q", value)
		}
	case error:
		errmsg := value.Error()
		if !needsQuoting(errmsg) {
			b.WriteString(errmsg)
		} else {
			fmt.Fprintf(b, "%q", errmsg)
		}
	default:
		fmt.Fprint(b, value)
	}
}

// This is to not silently overwrite `time`, `msg` and `level` fields when
// dumping it. If this code wasn't there doing:
//
//  logrus.WithField("level", 1).Info("hello")
//
// Would just silently drop the user provided level. Instead with this code
// it'll logged as:
//
//  {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
//
// It's not exported because it's still using Data in an opinionated way. It's to
// avoid code duplication between the two default formatters.
func prefixFieldClashes(data logrus.Fields) {
	if t, ok := data["time"]; ok {
		data["fields.time"] = t
	}

	if m, ok := data["msg"]; ok {
		data["fields.msg"] = m
	}

	if l, ok := data["level"]; ok {
		data["fields.level"] = l
	}
}
<commit_msg>Log formatting for protobuf messages<commit_after>package logger

import (
	"bytes"
	"fmt"
	"github.com/Sirupsen/logrus"
	"github.com/golang/protobuf/proto"
	"github.com/kr/pretty"
	"runtime"
	"sort"
	"strings"
	"time"
)

const (
	nocolor = 0
	red     = 31
	green   = 32
	yellow  = 33
	blue    = 36
	gray    = 37
	debug   = 33
	nsColor = 100
)

var (
	baseTimestamp time.Time
	isTerminal    bool
)

func init() {
	baseTimestamp = time.Now()
	isTerminal = logrus.IsTerminal()
}

type TextFormatter struct {
	// Set to true to bypass checking for a TTY before outputting colors.
	ForceColors bool

	// Force disabling colors.
	DisableColors bool

	// Disable timestamp logging. useful when output is redirected to logging
	// system that already adds timestamps.
	DisableTimestamp bool

	// Enable logging the full timestamp when a TTY is attached instead of just
	// the time passed since beginning of execution.
	FullTimestamp bool

	// TimestampFormat to use for display when a full timestamp is printed
	TimestampFormat string

	// The fields are sorted by default for a consistent output. For applications
	// that log extremely frequently and don't use the JSON formatter this may not
	// be desired.
	DisableSorting bool
}

func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	var b *bytes.Buffer
	keys := make([]string, 0, len(entry.Data))
	for k := range entry.Data {
		if k != "ns" {
			keys = append(keys, k)
		}
	}
	ns := entry.Data["ns"]

	if !f.DisableSorting {
		sort.Strings(keys)
	}
	if entry.Buffer != nil {
		b = entry.Buffer
	} else {
		b = &bytes.Buffer{}
	}

	prefixFieldClashes(entry.Data)

	isColorTerminal := isTerminal && (runtime.GOOS != "windows")
	isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors

	timestampFormat := f.TimestampFormat
	if timestampFormat == "" {
		timestampFormat = logrus.DefaultTimestampFormat
	}
	if isColored {
		f.printColored(b, entry, keys, timestampFormat, ns.(string))
	} else {
		if !f.DisableTimestamp {
			f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
		}
		f.appendKeyValue(b, "level", entry.Level.String())
		if entry.Message != "" {
			f.appendKeyValue(b, "msg", entry.Message)
		}
		for _, key := range keys {
			f.appendKeyValue(b, key, entry.Data[key])
		}
	}

	b.WriteByte('\n')
	return b.Bytes(), nil
}

func (f *TextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string, ns string) {
	var levelColor int
	switch entry.Level {
	case logrus.DebugLevel:
		levelColor = debug
	case logrus.WarnLevel:
		levelColor = yellow
	case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
		levelColor = red
	default:
		levelColor = blue
	}

	levelText := strings.ToUpper(entry.Level.String())[0:4]

	if f.DisableTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m \x1b[%dm%-10s\x1b[0m %-30s ", levelColor, levelText, nsColor, ns, entry.Message)
	} else if !f.FullTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] \x1b[%dm%-10s\x1b[0m %-30s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), nsColor, ns, entry.Message)
	} else {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] \x1b[%dm%-10s\x1b[0m %-30s ", levelColor, levelText, entry.Time.Format(timestampFormat), nsColor, ns, entry.Message)
	}
	for _, k := range keys {
		v := entry.Data[k]
		switch x := v.(type) {
		case string:
		case int:
		case int8:
		case int16:
		case int32:
		case int64:
		case uint8:
		case uint16:
		case uint32:
		case uint64:
		case complex64:
		case complex128:
		case float32:
		case float64:
		case bool:
		case proto.Message:
			v = proto.MarshalTextString(x)
		case fmt.Stringer:
		case error:
		default:
			v = pretty.Sprint(v)
		}
		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
		//f.appendValue(b, v)
	}
}

func needsQuoting(text string) bool {
	for _, ch := range text {
		if !((ch >= 'a' && ch <= 'z') ||
			(ch >= 'A' && ch <= 'Z') ||
			(ch >= '0' && ch <= '9') ||
			ch == '-' || ch == '.') {
			return true
		}
	}
	return false
}

func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {

	b.WriteString(key)
	b.WriteByte('=')
	f.appendValue(b, value)
	b.WriteByte(' ')
}

func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
	switch value := value.(type) {
	case string:
		if !needsQuoting(value) {
			b.WriteString(value)
		} else {
			fmt.Fprintf(b, "%q", value)
		}
	case error:
		errmsg := value.Error()
		if !needsQuoting(errmsg) {
			b.WriteString(errmsg)
		} else {
			fmt.Fprintf(b, "%q", errmsg)
		}
	default:
		fmt.Fprint(b, value)
	}
}

// This is to not silently overwrite `time`, `msg` and `level` fields when
// dumping it. If this code wasn't there doing:
//
//  logrus.WithField("level", 1).Info("hello")
//
// Would just silently drop the user provided level. Instead with this code
// it'll logged as:
//
//  {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
//
// It's not exported because it's still using Data in an opinionated way. It's to
// avoid code duplication between the two default formatters.
func prefixFieldClashes(data logrus.Fields) {
	if t, ok := data["time"]; ok {
		data["fields.time"] = t
	}

	if m, ok := data["msg"]; ok {
		data["fields.msg"] = m
	}

	if l, ok := data["level"]; ok {
		data["fields.level"] = l
	}
}
<|endoftext|>