<commit_before>/*
Copyright 2018 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package handler

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"time"

	"go.opencensus.io/plugin/ochttp"
	"go.opencensus.io/trace"
	"go.uber.org/zap"
	activatorconfig "knative.dev/serving/pkg/activator/config"

	"knative.dev/pkg/logging/logkey"
	tracingconfig "knative.dev/pkg/tracing/config"
	"knative.dev/serving/pkg/activator"
	revnet "knative.dev/serving/pkg/activator/net"
	"knative.dev/serving/pkg/activator/util"
	"knative.dev/serving/pkg/apis/serving"
	"knative.dev/serving/pkg/apis/serving/v1alpha1"
	netlisters "knative.dev/serving/pkg/client/listers/networking/v1alpha1"
	servinglisters "knative.dev/serving/pkg/client/listers/serving/v1alpha1"
	pkghttp "knative.dev/serving/pkg/http"
	"knative.dev/serving/pkg/network"
	"knative.dev/serving/pkg/network/prober"
	"knative.dev/serving/pkg/queue"

	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/util/wait"
	corev1listers "k8s.io/client-go/listers/core/v1"
)

// activationHandler will wait for an active endpoint for a revision
// to be available before proxing the request
type activationHandler struct {
	logger    *zap.SugaredLogger
	transport http.RoundTripper
	reporter  activator.StatsReporter
	throttler *activator.Throttler

	probeTimeout    time.Duration
	probeTransport  http.RoundTripper
	endpointTimeout time.Duration

	revisionLister servinglisters.RevisionLister
	serviceLister  corev1listers.ServiceLister
	sksLister      netlisters.ServerlessServiceLister
}

// The default time we'll try to probe the revision for activation.
const defaulTimeout = 2 * time.Minute

// New constructs a new http.Handler that deals with revision activation.
func New(l *zap.SugaredLogger, r activator.StatsReporter, t *activator.Throttler,
	rl servinglisters.RevisionLister, sl corev1listers.ServiceLister,
	sksL netlisters.ServerlessServiceLister) http.Handler {

	return &activationHandler{
		logger:          l,
		transport:       network.AutoTransport,
		reporter:        r,
		throttler:       t,
		revisionLister:  rl,
		sksLister:       sksL,
		serviceLister:   sl,
		probeTimeout:    defaulTimeout,
		probeTransport:  network.NewProberTransport(),
		endpointTimeout: defaulTimeout,
	}
}

func withOrigProto(or *http.Request) prober.Preparer {
	return func(r *http.Request) *http.Request {
		r.Proto = or.Proto
		r.ProtoMajor = or.ProtoMajor
		r.ProtoMinor = or.ProtoMinor
		return r
	}
}

func (a *activationHandler) probeEndpoint(logger *zap.SugaredLogger, r *http.Request, target *url.URL, revID activator.RevisionID) (bool, int) {
	var (
		attempts int
		st       = time.Now()
		url      = target.String()
	)

	logger.Debugf("Actually will be probing %s", url)
	config := activatorconfig.FromContext(r.Context())
	if config.Tracing.Backend != tracingconfig.None {
		// When we collect metrics, we're wrapping the RoundTripper
		// the prober would use inside an annotating transport.
		a.probeTransport = &ochttp.Transport{
			Base: network.NewProberTransport(),
		}
	}

	err := wait.PollImmediate(100*time.Millisecond, a.probeTimeout, func() (bool, error) {
		attempts++
		ret, err := prober.Do(
			r.Context(),
			a.probeTransport,
			url,
			prober.WithHeader(network.ProbeHeaderName, queue.Name),
			prober.ExpectsBody(queue.Name),
			prober.ExpectsStatusCodes([]int{http.StatusOK}),
			withOrigProto(r))
		if err != nil {
			logger.Warnw("Pod probe failed", zap.Error(err))
			return false, nil
		}
		if !ret {
			logger.Warn("Pod probe unsuccessful")
			return false, nil
		}

		// Cache probe success.
		a.throttler.MarkProbe(revID)
		return true, nil
	})

	logger.Debugf("Probing %s took %d attempts and %v time", target.String(), attempts, time.Since(st))
	return (err == nil), attempts
}

func (a *activationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	namespace := pkghttp.LastHeaderValue(r.Header, activator.RevisionHeaderNamespace)
	name := pkghttp.LastHeaderValue(r.Header, activator.RevisionHeaderName)
	revID := activator.RevisionID{Namespace: namespace, Name: name}
	logger := a.logger.With(zap.String(logkey.Key, revID.String()))

	revision, err := a.revisionLister.Revisions(namespace).Get(name)
	if err != nil {
		logger.Errorw("Error while getting revision", zap.Error(err))
		sendError(err, w)
		return
	}

	// SKS name matches that of revision.
	sks, err := a.sksLister.ServerlessServices(namespace).Get(name)
	if err != nil {
		logger.Errorw("Error while getting SKS", zap.Error(err))
		sendError(err, w)
		return
	}
	host, err := a.serviceHostName(revision, sks.Status.PrivateServiceName)
	if err != nil {
		logger.Errorw("Error while getting hostname", zap.Error(err))
		sendError(err, w)
		return
	}

	target := &url.URL{
		Scheme: "http",
		Host:   host,
	}

	tryContext, trySpan := trace.StartSpan(r.Context(), "throttler_try")
	if a.endpointTimeout > 0 {
		var cancel context.CancelFunc
		tryContext, cancel = context.WithTimeout(tryContext, a.endpointTimeout)
		defer cancel()
	}

	tryStart := time.Now()
	err = a.throttler.Try(tryContext, revID, func() {
		trySpan.End()
		logger.Debugf("Waiting for throttler took %v time", time.Since(tryStart))

		// This opportunistically caches the probes, so
		// a few concurrent requests might result in concurrent probes
		// but requests coming after won't. When cached 0 attempts were required.
		success, attempts := true, 0
		if a.throttler.ShouldProbe(revID) {
			probeCtx, probeSpan := trace.StartSpan(r.Context(), "probe")
			success, attempts = a.probeEndpoint(logger, r.WithContext(probeCtx), target, revID)
			probeSpan.End()
		}
		var httpStatus int
		if success {
			// Once we see a successful probe, send traffic.
			attempts++
			proxyCtx, proxySpan := trace.StartSpan(r.Context(), "proxy")
			httpStatus = a.proxyRequest(logger, w, r.WithContext(proxyCtx), target)
			proxySpan.End()
		} else {
			httpStatus = http.StatusInternalServerError
			w.WriteHeader(httpStatus)
		}

		configurationName := revision.Labels[serving.ConfigurationLabelKey]
		serviceName := revision.Labels[serving.ServiceLabelKey]
		a.reporter.ReportRequestCount(namespace, serviceName, configurationName, name, httpStatus, attempts)
	})
	if err != nil {
		// Set error on our capacity waiting span and end it
		trySpan.Annotate([]trace.Attribute{
			trace.StringAttribute("activator.throttler.error", err.Error()),
		}, "ThrottlerTry")
		trySpan.End()

		if err == activator.ErrActivatorOverload {
			http.Error(w, activator.ErrActivatorOverload.Error(), http.StatusServiceUnavailable)
		} else {
			w.WriteHeader(http.StatusInternalServerError)
			logger.Errorw("Error processing request in the activator", zap.Error(err))
		}
	}
}

func (a *activationHandler) proxyRequest(logger *zap.SugaredLogger, w http.ResponseWriter, r *http.Request, target *url.URL) int {
	network.RewriteHostIn(r)

	// Setup the reverse proxy.
	proxy := httputil.NewSingleHostReverseProxy(target)
	proxy.Transport = a.transport
	if config := activatorconfig.FromContext(r.Context()); config.Tracing.Backend != tracingconfig.None {
		// When we collect metrics, we're wrapping the RoundTripper
		// the proxy would use inside an annotating transport.
		proxy.Transport = &ochttp.Transport{
			Base: a.transport,
		}
	}
	proxy.FlushInterval = -1
	proxy.ErrorHandler = func(w http.ResponseWriter, req *http.Request, err error) {
		logger.Infof("error reverse proxying request: %v", err)
		http.Error(w, err.Error(), http.StatusBadGateway)
	}

	r.Header.Set(network.ProxyHeaderName, activator.Name)

	util.SetupHeaderPruning(proxy)

	recorder := pkghttp.NewResponseRecorder(w, http.StatusOK)
	proxy.ServeHTTP(recorder, r)
	return recorder.ResponseCode
}

// serviceHostName obtains the hostname of the underlying service and the correct
// port to send requests to.
func (a *activationHandler) serviceHostName(rev *v1alpha1.Revision, serviceName string) (string, error) {
	svc, err := a.serviceLister.Services(rev.Namespace).Get(serviceName)
	if err != nil {
		return "", err
	}

	// Search for the appropriate port.
	port, ok := revnet.GetServicePort(rev.GetProtocol(), svc)
	if !ok {
		return "", errors.New("revision needs external HTTP port")
	}

	// Use the ClusterIP directly to elide DNS lookup, which both adds latency
	// and hurts reliability when routing through the activator.
	return net.JoinHostPort(svc.Spec.ClusterIP, strconv.Itoa(port)), nil
}

func sendError(err error, w http.ResponseWriter) {
	msg := fmt.Sprintf("Error getting active endpoint: %v", err)
	if k8serrors.IsNotFound(err) {
		http.Error(w, msg, http.StatusNotFound)
		return
	}
	http.Error(w, msg, http.StatusInternalServerError)
}
<commit_msg>Remove extremely chatty log entry (#5388)<commit_after>/*
Copyright 2018 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package handler

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"time"

	"go.opencensus.io/plugin/ochttp"
	"go.opencensus.io/trace"
	"go.uber.org/zap"
	activatorconfig "knative.dev/serving/pkg/activator/config"

	"knative.dev/pkg/logging/logkey"
	tracingconfig "knative.dev/pkg/tracing/config"
	"knative.dev/serving/pkg/activator"
	revnet "knative.dev/serving/pkg/activator/net"
	"knative.dev/serving/pkg/activator/util"
	"knative.dev/serving/pkg/apis/serving"
	"knative.dev/serving/pkg/apis/serving/v1alpha1"
	netlisters "knative.dev/serving/pkg/client/listers/networking/v1alpha1"
	servinglisters "knative.dev/serving/pkg/client/listers/serving/v1alpha1"
	pkghttp "knative.dev/serving/pkg/http"
	"knative.dev/serving/pkg/network"
	"knative.dev/serving/pkg/network/prober"
	"knative.dev/serving/pkg/queue"

	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/util/wait"
	corev1listers "k8s.io/client-go/listers/core/v1"
)

// activationHandler will wait for an active endpoint for a revision
// to be available before proxing the request
type activationHandler struct {
	logger    *zap.SugaredLogger
	transport http.RoundTripper
	reporter  activator.StatsReporter
	throttler *activator.Throttler

	probeTimeout    time.Duration
	probeTransport  http.RoundTripper
	endpointTimeout time.Duration

	revisionLister servinglisters.RevisionLister
	serviceLister  corev1listers.ServiceLister
	sksLister      netlisters.ServerlessServiceLister
}

// The default time we'll try to probe the revision for activation.
const defaulTimeout = 2 * time.Minute

// New constructs a new http.Handler that deals with revision activation.
func New(l *zap.SugaredLogger, r activator.StatsReporter, t *activator.Throttler,
	rl servinglisters.RevisionLister, sl corev1listers.ServiceLister,
	sksL netlisters.ServerlessServiceLister) http.Handler {

	return &activationHandler{
		logger:          l,
		transport:       network.AutoTransport,
		reporter:        r,
		throttler:       t,
		revisionLister:  rl,
		sksLister:       sksL,
		serviceLister:   sl,
		probeTimeout:    defaulTimeout,
		probeTransport:  network.NewProberTransport(),
		endpointTimeout: defaulTimeout,
	}
}

func withOrigProto(or *http.Request) prober.Preparer {
	return func(r *http.Request) *http.Request {
		r.Proto = or.Proto
		r.ProtoMajor = or.ProtoMajor
		r.ProtoMinor = or.ProtoMinor
		return r
	}
}

func (a *activationHandler) probeEndpoint(logger *zap.SugaredLogger, r *http.Request, target *url.URL, revID activator.RevisionID) (bool, int) {
	var (
		attempts int
		st       = time.Now()
		url      = target.String()
	)

	logger.Debugf("Actually will be probing %s", url)
	config := activatorconfig.FromContext(r.Context())
	if config.Tracing.Backend != tracingconfig.None {
		// When we collect metrics, we're wrapping the RoundTripper
		// the prober would use inside an annotating transport.
		a.probeTransport = &ochttp.Transport{
			Base: network.NewProberTransport(),
		}
	}

	err := wait.PollImmediate(100*time.Millisecond, a.probeTimeout, func() (bool, error) {
		attempts++
		ret, err := prober.Do(
			r.Context(),
			a.probeTransport,
			url,
			prober.WithHeader(network.ProbeHeaderName, queue.Name),
			prober.ExpectsBody(queue.Name),
			prober.ExpectsStatusCodes([]int{http.StatusOK}),
			withOrigProto(r))
		if err != nil {
			logger.Warnw("Pod probe failed", zap.Error(err))
			return false, nil
		}
		if !ret {
			logger.Warn("Pod probe unsuccessful")
			return false, nil
		}

		// Cache probe success.
		a.throttler.MarkProbe(revID)
		return true, nil
	})

	logger.Debugf("Probing %s took %d attempts and %v time", target.String(), attempts, time.Since(st))
	return (err == nil), attempts
}

func (a *activationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	namespace := pkghttp.LastHeaderValue(r.Header, activator.RevisionHeaderNamespace)
	name := pkghttp.LastHeaderValue(r.Header, activator.RevisionHeaderName)
	revID := activator.RevisionID{Namespace: namespace, Name: name}
	logger := a.logger.With(zap.String(logkey.Key, revID.String()))

	revision, err := a.revisionLister.Revisions(namespace).Get(name)
	if err != nil {
		logger.Errorw("Error while getting revision", zap.Error(err))
		sendError(err, w)
		return
	}

	// SKS name matches that of revision.
	sks, err := a.sksLister.ServerlessServices(namespace).Get(name)
	if err != nil {
		logger.Errorw("Error while getting SKS", zap.Error(err))
		sendError(err, w)
		return
	}
	host, err := a.serviceHostName(revision, sks.Status.PrivateServiceName)
	if err != nil {
		logger.Errorw("Error while getting hostname", zap.Error(err))
		sendError(err, w)
		return
	}

	target := &url.URL{
		Scheme: "http",
		Host:   host,
	}

	tryContext, trySpan := trace.StartSpan(r.Context(), "throttler_try")
	if a.endpointTimeout > 0 {
		var cancel context.CancelFunc
		tryContext, cancel = context.WithTimeout(tryContext, a.endpointTimeout)
		defer cancel()
	}

	err = a.throttler.Try(tryContext, revID, func() {
		trySpan.End()

		// This opportunistically caches the probes, so
		// a few concurrent requests might result in concurrent probes
		// but requests coming after won't. When cached 0 attempts were required.
		success, attempts := true, 0
		if a.throttler.ShouldProbe(revID) {
			probeCtx, probeSpan := trace.StartSpan(r.Context(), "probe")
			success, attempts = a.probeEndpoint(logger, r.WithContext(probeCtx), target, revID)
			probeSpan.End()
		}
		var httpStatus int
		if success {
			// Once we see a successful probe, send traffic.
			attempts++
			proxyCtx, proxySpan := trace.StartSpan(r.Context(), "proxy")
			httpStatus = a.proxyRequest(logger, w, r.WithContext(proxyCtx), target)
			proxySpan.End()
		} else {
			httpStatus = http.StatusInternalServerError
			w.WriteHeader(httpStatus)
		}

		configurationName := revision.Labels[serving.ConfigurationLabelKey]
		serviceName := revision.Labels[serving.ServiceLabelKey]
		a.reporter.ReportRequestCount(namespace, serviceName, configurationName, name, httpStatus, attempts)
	})
	if err != nil {
		// Set error on our capacity waiting span and end it
		trySpan.Annotate([]trace.Attribute{
			trace.StringAttribute("activator.throttler.error", err.Error()),
		}, "ThrottlerTry")
		trySpan.End()

		if err == activator.ErrActivatorOverload {
			http.Error(w, activator.ErrActivatorOverload.Error(), http.StatusServiceUnavailable)
		} else {
			w.WriteHeader(http.StatusInternalServerError)
			logger.Errorw("Error processing request in the activator", zap.Error(err))
		}
	}
}

func (a *activationHandler) proxyRequest(logger *zap.SugaredLogger, w http.ResponseWriter, r *http.Request, target *url.URL) int {
	network.RewriteHostIn(r)

	// Setup the reverse proxy.
	proxy := httputil.NewSingleHostReverseProxy(target)
	proxy.Transport = a.transport
	if config := activatorconfig.FromContext(r.Context()); config.Tracing.Backend != tracingconfig.None {
		// When we collect metrics, we're wrapping the RoundTripper
		// the proxy would use inside an annotating transport.
		proxy.Transport = &ochttp.Transport{
			Base: a.transport,
		}
	}
	proxy.FlushInterval = -1
	proxy.ErrorHandler = func(w http.ResponseWriter, req *http.Request, err error) {
		logger.Infof("error reverse proxying request: %v", err)
		http.Error(w, err.Error(), http.StatusBadGateway)
	}

	r.Header.Set(network.ProxyHeaderName, activator.Name)

	util.SetupHeaderPruning(proxy)

	recorder := pkghttp.NewResponseRecorder(w, http.StatusOK)
	proxy.ServeHTTP(recorder, r)
	return recorder.ResponseCode
}

// serviceHostName obtains the hostname of the underlying service and the correct
// port to send requests to.
func (a *activationHandler) serviceHostName(rev *v1alpha1.Revision, serviceName string) (string, error) {
	svc, err := a.serviceLister.Services(rev.Namespace).Get(serviceName)
	if err != nil {
		return "", err
	}

	// Search for the appropriate port.
	port, ok := revnet.GetServicePort(rev.GetProtocol(), svc)
	if !ok {
		return "", errors.New("revision needs external HTTP port")
	}

	// Use the ClusterIP directly to elide DNS lookup, which both adds latency
	// and hurts reliability when routing through the activator.
	return net.JoinHostPort(svc.Spec.ClusterIP, strconv.Itoa(port)), nil
}

func sendError(err error, w http.ResponseWriter) {
	msg := fmt.Sprintf("Error getting active endpoint: %v", err)
	if k8serrors.IsNotFound(err) {
		http.Error(w, msg, http.StatusNotFound)
		return
	}
	http.Error(w, msg, http.StatusInternalServerError)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package util

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// IsDefaultStorageClassAnnotation represents a StorageClass annotation that
// marks a class as the default StorageClass
//TODO: Update IsDefaultStorageClassannotation and remove Beta when no longer used
const IsDefaultStorageClassAnnotation = "storageclass.beta.kubernetes.io/is-default-class"
const BetaIsDefaultStorageClassAnnotation = "storageclass.beta.kubernetes.io/is-default-class"

// IsDefaultAnnotationText returns a pretty Yes/No String if
// the annotation is set
// TODO: remove Beta when no longer needed
func IsDefaultAnnotationText(obj metav1.ObjectMeta) string {
	if obj.Annotations[IsDefaultStorageClassAnnotation] == "true" {
		return "Yes"
	}
	if obj.Annotations[BetaIsDefaultStorageClassAnnotation] == "true" {
		return "Yes"
	}

	return "No"
}

// IsDefaultAnnotation returns a boolean if
// the annotation is set
// TODO: remove Beta when no longer needed
func IsDefaultAnnotation(obj metav1.ObjectMeta) bool {
	if obj.Annotations[IsDefaultStorageClassAnnotation] == "true" {
		return true
	}
	if obj.Annotations[BetaIsDefaultStorageClassAnnotation] == "true" {
		return true
	}

	return false
}
<commit_msg>Remove 'beta' from default storage class annotation (storage/util)<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package util

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// IsDefaultStorageClassAnnotation represents a StorageClass annotation that
// marks a class as the default StorageClass
//TODO: remove Beta when no longer used
const IsDefaultStorageClassAnnotation = "storageclass.kubernetes.io/is-default-class"
const BetaIsDefaultStorageClassAnnotation = "storageclass.beta.kubernetes.io/is-default-class"

// IsDefaultAnnotationText returns a pretty Yes/No String if
// the annotation is set
// TODO: remove Beta when no longer needed
func IsDefaultAnnotationText(obj metav1.ObjectMeta) string {
	if obj.Annotations[IsDefaultStorageClassAnnotation] == "true" {
		return "Yes"
	}
	if obj.Annotations[BetaIsDefaultStorageClassAnnotation] == "true" {
		return "Yes"
	}

	return "No"
}

// IsDefaultAnnotation returns a boolean if
// the annotation is set
// TODO: remove Beta when no longer needed
func IsDefaultAnnotation(obj metav1.ObjectMeta) bool {
	if obj.Annotations[IsDefaultStorageClassAnnotation] == "true" {
		return true
	}
	if obj.Annotations[BetaIsDefaultStorageClassAnnotation] == "true" {
		return true
	}

	return false
}
<|endoftext|>


<commit_before>package connector

import (
	"context"
	"fmt"
	"net"
	"sort"
	"strings"

	"github.com/datawire/ambassador/pkg/kates"
	"github.com/datawire/dlib/dgroup"
	"github.com/datawire/dlib/dlog"
	"github.com/datawire/dlib/dutil"
	"github.com/datawire/telepresence2/rpc/v2/daemon"
)

// k8sWatcher watcher of pods and services.
type k8sWatcher struct {
	Pods      []*kates.Pod
	Services  []*kates.Service
	Endpoints []*kates.Endpoints
	cancel    context.CancelFunc
	namespace string
}

// startWatchers initializes a set of Kubernetes watchers that provide information from the cluster
// which is then used for controlling the outbound connectivity of the cluster.
//
// Initially, a watcher that watches the namespaces is created. Once it produces its first
// snapshot, some filtering is done on the list in that snapshot and then one watcher for each
// resulting namespace is created that watches pods and services. When their snapshot arrives,
// these pods and services are used when creating IP-tables that are sent to the NAT-logic in the
// daemon.
//
// The filtered list of namespaces is also used for creating a DNS search path which is propagated to
// the DNS-resolver in the daemon.
//
// When an update arrives in the namespace watcher, it will refresh the DNS-search path and the current
// set of watchers so that new watchers are added for added namespaces and watchers for namespaces that
// have been will be cancelled.
//
// If a pods and services watcher receives an update, it will send an updated IP-table to the daemon.
func (kc *k8sCluster) startWatchers(c context.Context, accWait chan struct{}) (err error) {
	defer func() {
		if r := dutil.PanicToError(recover()); r != nil {
			err = r
		}
	}()

	acc := kc.client.Watch(c,
		kates.Query{
			Name: "Namespaces",
			Kind: "namespace",
		})

	g := dgroup.ParentGroup(c)
	g.Go("watch-k8s-namespaces", func(c context.Context) error {
		for {
			select {
			case <-c.Done():
				return nil
			case <-acc.Changed():
				if kc.onNamespacesChange(c, acc, accWait) {
					accWait = nil // accWait is one-shot
				}
			}
		}
	})

	g.Go("watch-k8s", func(c context.Context) error {
		for {
			select {
			case <-c.Done():
				return nil
			case <-kc.watcherChanged:
				kc.updateDaemonTable(c)
			}
		}
	})
	return nil
}

func (kc *k8sCluster) onNamespacesChange(c context.Context, acc *kates.Accumulator, accWait chan<- struct{}) bool {
	changed := func() bool {
		kc.accLock.Lock()
		defer kc.accLock.Unlock()
		return acc.Update(kc)
	}()
	if changed {
		changed = kc.refreshNamespaces(c, accWait)
	}
	return changed
}

func (kc *k8sCluster) setMappedNamespaces(c context.Context, namespaces []string) {
	sort.Strings(namespaces)
	kc.accLock.Lock()
	kc.mappedNamespaces = namespaces
	kc.accLock.Unlock()
	kc.refreshNamespaces(c, nil)
}

func (kc *k8sCluster) refreshNamespaces(c context.Context, accWait chan<- struct{}) bool {
	kc.accLock.Lock()
	namespaces := make([]string, 0, len(kc.Namespaces))
	for _, ns := range kc.Namespaces {
		if kc.shouldBeWatched(ns.Name) {
			namespaces = append(namespaces, ns.Name)
		}
	}
	sort.Strings(namespaces)

	nsChange := len(namespaces) != len(kc.lastNamespaces)
	if !nsChange {
		for i, ns := range namespaces {
			if ns != kc.lastNamespaces[i] {
				nsChange = true
				break
			}
		}
	}
	if nsChange {
		kc.lastNamespaces = namespaces
	}
	kc.accLock.Unlock()

	if nsChange {
		kc.refreshWatchers(c, namespaces, accWait)
		kc.updateDaemonNamespaces(c)
	}
	return nsChange
}

func (kc *k8sCluster) shouldBeWatched(namespace string) bool {
	if len(kc.mappedNamespaces) == 0 {
		return !(namespace == managerNamespace || strings.HasPrefix(namespace, "kube-"))
	}
	for _, n := range kc.mappedNamespaces {
		if n == namespace {
			return true
		}
	}
	return false
}

// refreshWatchersLocked ensures that the current set of namespaces are watched.
//
// The accWait channel should only be passed once to this function and will be closed once all watchers have
// received their initial snapshot.
func (kc *k8sCluster) refreshWatchers(c context.Context, namespaces []string, accWait chan<- struct{}) {
	var onChange func(int)
	if accWait == nil {
		onChange = func(_ int) {}
	} else {
		// close accWait when all watchers have received an update
		initialReceived := make([]bool, len(namespaces))
		onChange = func(index int) {
			kc.accLock.Lock()
			defer kc.accLock.Unlock()
			if accWait == nil || initialReceived[index] {
				return
			}
			initialReceived[index] = true
			for _, r := range initialReceived {
				if !r {
					return
				}
			}
			al := accWait
			accWait = nil
			close(al)
		}
	}

	kc.accLock.Lock()
	if kc.watchers == nil {
		kc.watchers = make(map[string]*k8sWatcher, len(namespaces))
	}

	keep := make(map[string]struct{})
	for i, namespace := range namespaces {
		keep[namespace] = struct{}{}
		if _, ok := kc.watchers[namespace]; ok {
			continue
		}
		watcher := &k8sWatcher{namespace: namespace}
		var wc context.Context
		wc, watcher.cancel = context.WithCancel(c)
		kc.watchers[namespace] = watcher

		go kc.watchPodsAndServices(wc, watcher, i, onChange)
	}
	kc.accLock.Unlock()

	// Cancel watchers of undesired namespaces
	for namespace, watcher := range kc.watchers {
		if _, ok := keep[namespace]; !ok {
			watcher.cancel()
		}
	}
}

func (kc *k8sCluster) watchPodsAndServices(c context.Context, watcher *k8sWatcher, i int, onChange func(int)) {
	nAcc := kc.client.Watch(c,
		kates.Query{
			Name:      "Services",
			Namespace: watcher.namespace,
			Kind:      "service",
		},
		kates.Query{
			Name:      "Endpoints",
			Namespace: watcher.namespace,
			Kind:      "endpoints",
		},
		kates.Query{
			Name:      "Pods",
			Namespace: watcher.namespace,
			Kind:      "pod",
		})

	dlog.Infof(c, "Watching namespace %q", watcher.namespace)

	for {
		select {
		case <-c.Done():
			dlog.Infof(c, "Watch of namespace %q cancelled", watcher.namespace)
			return
		case x := <-nAcc.Changed():
			if func() bool {
				kc.accLock.Lock()
				defer kc.accLock.Unlock()
				return nAcc.Update(watcher)
			}() {
				kc.watcherChanged <- x
			}
			onChange(i)
		}
	}
}

func (kc *k8sCluster) setInterceptedNamespaces(c context.Context, interceptedNamespaces map[string]struct{}) {
	kc.accLock.Lock()
	kc.interceptedNamespaces = interceptedNamespaces
	kc.accLock.Unlock()
	kc.updateDaemonNamespaces(c)
}

const clusterServerSuffix = ".svc.cluster.local"

// updateDaemonNamespacesLocked will create a new DNS search path from the given namespaces and
// send it to the DNS-resolver in the daemon.
func (kc *k8sCluster) updateDaemonNamespaces(c context.Context) {
	if kc.daemon == nil {
		// NOTE! Some tests dont't set the daemon
		return
	}

	kc.accLock.Lock()
	namespaces := make([]string, 0, len(kc.interceptedNamespaces)+len(kc.localIntercepts))
	for ns := range kc.interceptedNamespaces {
		namespaces = append(namespaces, ns)
	}
	for ns := range kc.localInterceptedNamespaces {
		if _, found := kc.interceptedNamespaces[ns]; !found {
			namespaces = append(namespaces, ns)
		}
	}

	// Pass current mapped namespaces as plain names (no ending dot). The DNS-resolver will
	// create special mapping for those, allowing names like myservice.mynamespace to be resolved
	paths := make([]string, len(kc.lastNamespaces), len(kc.lastNamespaces)+len(namespaces))
	copy(paths, kc.lastNamespaces)

	// Avoid being locked for the remainder of this function.
	kc.accLock.Unlock()

	// Provide direct access to intercepted namespaces
	sort.Strings(namespaces)
	for _, ns := range namespaces {
		paths = append(paths, ns+clusterServerSuffix+".")
	}
	dlog.Debugf(c, "posting search paths to %s", strings.Join(paths, " "))
	if _, err := kc.daemon.SetDnsSearchPath(c, &daemon.Paths{Paths: paths}); err != nil {
		dlog.Errorf(c, "error posting search paths to %s: %v", strings.Join(paths, " "), err)
	}
}

// updateDaemonTable will create IP-tables based on the current snapshots of services and pods
// that the watchers have produced.
func (kc *k8sCluster) updateDaemonTable(c context.Context) {
	if kc.daemon == nil {
		// NOTE! Some tests dont't set the daemon
		return
	}
	table := &daemon.Table{Name: "kubernetes"}
	kc.accLock.Lock()
	for _, watcher := range kc.watchers {
		for _, svc := range watcher.Services {
			updateTableFromService(c, svc, watcher.Endpoints, table)
		}
		for _, pod := range watcher.Pods {
			updateTableFromPod(pod, table)
		}
	}
	kc.accLock.Unlock()

	// Send updated table to daemon
	if _, err := kc.daemon.Update(c, table); err != nil {
		dlog.Errorf(c, "error posting update to %s: %v", table.Name, err)
	}
}

func updateTableFromService(c context.Context, svc *kates.Service, endpoints []*kates.Endpoints, table *daemon.Table) {
	spec := &svc.Spec
	clusterIP := spec.ClusterIP
	var ips []string
	var err error
	// Handle headless services separately since they don't have cluster IPs
	if clusterIP == "" || clusterIP == "None" {
		ips, err = getIPsFromHeadlessService(svc, spec.ExternalName, endpoints)
		if err != nil {
			dlog.Errorf(c, "Error finding IPs for service %s: %s", svc.Name, err)
			return
		}
		if len(ips) == 0 {
			if svc.Name != "traffic-manager" {
				dlog.Errorf(c, "Found no IPs for service %s", svc.Name)
			}
			return
		}
	} else {
		ips = append(ips, clusterIP)
	}
	qName := svc.Name + "." + svc.Namespace + clusterServerSuffix

	ports := ""
	for _, port := range spec.Ports {
		if ports == "" {
			ports = fmt.Sprintf("%d", port.Port)
		} else {
			ports = fmt.Sprintf("%s,%d", ports, port.Port)
		}

		// Kubernetes creates records for all named ports, of the form
		// _my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example
		// https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#srv-records
		if port.Name != "" {
			proto := strings.ToLower(string(port.Protocol))
			table.Routes = append(table.Routes, &daemon.Route{
				Name:  fmt.Sprintf("_%v._%v.%v", port.Name, proto, qName),
				Ips:   ips,
				Port:  ports,
				Proto: proto,
			})
		}
	}

	table.Routes = append(table.Routes, &daemon.Route{
		Name:  qName,
		Ips:   ips,
		Port:  ports,
		Proto: "tcp",
	})
}

// Headless Services don't have a cluster IP so we need to get their IP(s) separately.
// Docs on headless services can be found here
// https://kubernetes.io/docs/concepts/services-networking/service/#headless-services
func getIPsFromHeadlessService(svc *kates.Service, externalName string, endpoints []*kates.Endpoints) ([]string, error) {
	serviceName := svc.Name
	var ips []string
	// ExternalName services don't have endpoints so we handle them separately.
	if externalName != "" {
		domainIPs, err := net.LookupIP(externalName)
		if err != nil {
			return nil, err
		}
		if len(domainIPs) == 0 {
			return nil, nil
		}
		for _, ip := range domainIPs {
			ips = append(ips, ip.String())
		}
	} else {
		// see if there's an associated endpoint with the headless service
		for i := range endpoints {
			if endpoints[i].ObjectMeta.Name == serviceName {
				matchingEndpoint := endpoints[i]
				if len(matchingEndpoint.Subsets) > 0 {
					endpointSubset := matchingEndpoint.Subsets[0]

					// Endpoints have two sets of addresses: Addresses and NotReadyAddresses
					// We only want to forward traffic to pods that are ready one so we only
					// loop through the ready addresses
					for _, address := range endpointSubset.Addresses {
						ips = append(ips, address.IP)
					}
					if len(ips) == 0 {
						return nil, nil
					}
				} else {
					return nil, nil
				}
				break
			}
		}
	}
	return ips, nil
}

func updateTableFromPod(pod *kates.Pod, table *daemon.Table) {
	qname := ""

	hostname := pod.Spec.Hostname
	if hostname != "" {
		qname += hostname
	}

	subdomain := pod.Spec.Subdomain
	if subdomain != "" {
		qname += "." + subdomain
	}

	if qname == "" {
		// Note: this is a departure from kubernetes, kubernetes will
		// simply not publish a dns name in this case.
		qname = pod.Name + "." + pod.Namespace + ".pod.cluster.local"
	} else {
		qname += clusterServerSuffix
	}

	ips := []string{pod.Status.PodIP}
	if len(ips) > 0 {
		table.Routes = append(table.Routes, &daemon.Route{
			Name:  qname,
			Ips:   ips,
			Proto: "tcp",
		})
	}
}
<commit_msg>Defer updateDaemonTable call until all watchers have received a snapshot<commit_after>package connector

import (
	"context"
	"fmt"
	"net"
	"sort"
	"strings"

	"github.com/datawire/ambassador/pkg/kates"
	"github.com/datawire/dlib/dgroup"
	"github.com/datawire/dlib/dlog"
	"github.com/datawire/dlib/dutil"
	"github.com/datawire/telepresence2/rpc/v2/daemon"
)

// k8sWatcher watcher of pods and services.
type k8sWatcher struct {
	Pods      []*kates.Pod
	Services  []*kates.Service
	Endpoints []*kates.Endpoints
	cancel    context.CancelFunc
	namespace string
}

// startWatchers initializes a set of Kubernetes watchers that provide information from the cluster
// which is then used for controlling the outbound connectivity of the cluster.
//
// Initially, a watcher that watches the namespaces is created. Once it produces its first
// snapshot, some filtering is done on the list in that snapshot and then one watcher for each
// resulting namespace is created that watches pods and services. When their snapshot arrives,
// these pods and services are used when creating IP-tables that are sent to the NAT-logic in the
// daemon.
//
// The filtered list of namespaces is also used for creating a DNS search path which is propagated to
// the DNS-resolver in the daemon.
//
// When an update arrives in the namespace watcher, it will refresh the DNS-search path and the current
// set of watchers so that new watchers are added for added namespaces and watchers for namespaces that
// have been will be cancelled.
//
// If a pods and services watcher receives an update, it will send an updated IP-table to the daemon.
func (kc *k8sCluster) startWatchers(c context.Context, accWait chan struct{}) (err error) {
	defer func() {
		if r := dutil.PanicToError(recover()); r != nil {
			err = r
		}
	}()

	_accWait := accWait

	acc := kc.client.Watch(c,
		kates.Query{
			Name: "Namespaces",
			Kind: "namespace",
		})

	g := dgroup.ParentGroup(c)
	g.Go("watch-k8s-namespaces", func(c context.Context) error {
		for {
			select {
			case <-c.Done():
				return nil
			case <-acc.Changed():
				if kc.onNamespacesChange(c, acc, accWait) {
					accWait = nil // accWait is one-shot
				}
			}
		}
	})

	g.Go("watch-k8s", func(c context.Context) error {
		// Don't call kc.updateDaemonTable until we have a complete snapshot, signaled by accWait getting closed.
		needsUpdate := false
		for {
			select {
			case <-c.Done():
				return nil
			case <-kc.watcherChanged:
				needsUpdate = true
				continue
			case <-_accWait:
				if needsUpdate {
					kc.updateDaemonTable(c)
				}
			}
			break
		}
		// OK, accWait is closed, we can loop normally now.
		for {
			select {
			case <-c.Done():
				return nil
			case <-kc.watcherChanged:
				kc.updateDaemonTable(c)
			}
		}
	})
	return nil
}

func (kc *k8sCluster) onNamespacesChange(c context.Context, acc *kates.Accumulator, accWait chan<- struct{}) bool {
	changed := func() bool {
		kc.accLock.Lock()
		defer kc.accLock.Unlock()
		return acc.Update(kc)
	}()
	if changed {
		changed = kc.refreshNamespaces(c, accWait)
	}
	return changed
}

func (kc *k8sCluster) setMappedNamespaces(c context.Context, namespaces []string) {
	sort.Strings(namespaces)
	kc.accLock.Lock()
	kc.mappedNamespaces = namespaces
	kc.accLock.Unlock()
	kc.refreshNamespaces(c, nil)
}

func (kc *k8sCluster) refreshNamespaces(c context.Context, accWait chan<- struct{}) bool {
	kc.accLock.Lock()
	namespaces := make([]string, 0, len(kc.Namespaces))
	for _, ns := range kc.Namespaces {
		if kc.shouldBeWatched(ns.Name) {
			namespaces = append(namespaces, ns.Name)
		}
	}
	sort.Strings(namespaces)

	nsChange := len(namespaces) != len(kc.lastNamespaces)
	if !nsChange {
		for i, ns := range namespaces {
			if ns != kc.lastNamespaces[i] {
				nsChange = true
				break
			}
		}
	}
	if nsChange {
		kc.lastNamespaces = namespaces
	}
	kc.accLock.Unlock()

	if nsChange {
		kc.refreshWatchers(c, namespaces, accWait)
		kc.updateDaemonNamespaces(c)
	}
	return nsChange
}

func (kc *k8sCluster) shouldBeWatched(namespace string) bool {
	if len(kc.mappedNamespaces) == 0 {
		return !(namespace == managerNamespace || strings.HasPrefix(namespace, "kube-"))
	}
	for _, n := range kc.mappedNamespaces {
		if n == namespace {
			return true
		}
	}
	return false
}

// refreshWatchersLocked ensures that the current set of namespaces are watched.
//
// The accWait channel should only be passed once to this function and will be closed once all watchers have
// received their initial snapshot.
func (kc *k8sCluster) refreshWatchers(c context.Context, namespaces []string, accWait chan<- struct{}) {
	var onChange func(int)
	if accWait == nil {
		onChange = func(_ int) {}
	} else {
		// close accWait when all watchers have received an update
		initialReceived := make([]bool, len(namespaces))
		onChange = func(index int) {
			kc.accLock.Lock()
			defer kc.accLock.Unlock()
			if accWait == nil || initialReceived[index] {
				return
			}
			initialReceived[index] = true
			for _, r := range initialReceived {
				if !r {
					return
				}
			}
			al := accWait
			accWait = nil
			close(al)
		}
	}

	kc.accLock.Lock()
	if kc.watchers == nil {
		kc.watchers = make(map[string]*k8sWatcher, len(namespaces))
	}

	keep := make(map[string]struct{})
	for i, namespace := range namespaces {
		keep[namespace] = struct{}{}
		if _, ok := kc.watchers[namespace]; ok {
			continue
		}
		watcher := &k8sWatcher{namespace: namespace}
		var wc context.Context
		wc, watcher.cancel = context.WithCancel(c)
		kc.watchers[namespace] = watcher

		go kc.watchPodsAndServices(wc, watcher, i, onChange)
	}
	kc.accLock.Unlock()

	// Cancel watchers of undesired namespaces
	for namespace, watcher := range kc.watchers {
		if _, ok := keep[namespace]; !ok {
			watcher.cancel()
		}
	}
}

func (kc *k8sCluster) watchPodsAndServices(c context.Context, watcher *k8sWatcher, i int, onChange func(int)) {
	nAcc := kc.client.Watch(c,
		kates.Query{
			Name:      "Services",
			Namespace: watcher.namespace,
			Kind:      "service",
		},
		kates.Query{
			Name:      "Endpoints",
			Namespace: watcher.namespace,
			Kind:      "endpoints",
		},
		kates.Query{
			Name:      "Pods",
			Namespace: watcher.namespace,
			Kind:      "pod",
		})

	dlog.Infof(c, "Watching namespace %q", watcher.namespace)

	for {
		select {
		case <-c.Done():
			dlog.Infof(c, "Watch of namespace %q cancelled", watcher.namespace)
			return
		case x := <-nAcc.Changed():
			if func() bool {
				kc.accLock.Lock()
				defer kc.accLock.Unlock()
				return nAcc.Update(watcher)
			}() {
				kc.watcherChanged <- x
			}
			onChange(i)
		}
	}
}

func (kc *k8sCluster) setInterceptedNamespaces(c context.Context, interceptedNamespaces map[string]struct{}) {
	kc.accLock.Lock()
	kc.interceptedNamespaces = interceptedNamespaces
	kc.accLock.Unlock()
	kc.updateDaemonNamespaces(c)
}

const clusterServerSuffix = ".svc.cluster.local"

// updateDaemonNamespacesLocked will create a new DNS search path from the given namespaces and
// send it to the DNS-resolver in the daemon.
func (kc *k8sCluster) updateDaemonNamespaces(c context.Context) {
	if kc.daemon == nil {
		// NOTE! Some tests dont't set the daemon
		return
	}

	kc.accLock.Lock()
	namespaces := make([]string, 0, len(kc.interceptedNamespaces)+len(kc.localIntercepts))
	for ns := range kc.interceptedNamespaces {
		namespaces = append(namespaces, ns)
	}
	for ns := range kc.localInterceptedNamespaces {
		if _, found := kc.interceptedNamespaces[ns]; !found {
			namespaces = append(namespaces, ns)
		}
	}

	// Pass current mapped namespaces as plain names (no ending dot). The DNS-resolver will
	// create special mapping for those, allowing names like myservice.mynamespace to be resolved
	paths := make([]string, len(kc.lastNamespaces), len(kc.lastNamespaces)+len(namespaces))
	copy(paths, kc.lastNamespaces)

	// Avoid being locked for the remainder of this function.
	kc.accLock.Unlock()

	// Provide direct access to intercepted namespaces
	sort.Strings(namespaces)
	for _, ns := range namespaces {
		paths = append(paths, ns+clusterServerSuffix+".")
	}
	dlog.Debugf(c, "posting search paths to %s", strings.Join(paths, " "))
	if _, err := kc.daemon.SetDnsSearchPath(c, &daemon.Paths{Paths: paths}); err != nil {
		dlog.Errorf(c, "error posting search paths to %s: %v", strings.Join(paths, " "), err)
	}
}

// updateDaemonTable will create IP-tables based on the current snapshots of services and pods
// that the watchers have produced.
func (kc *k8sCluster) updateDaemonTable(c context.Context) {
	if kc.daemon == nil {
		// NOTE! Some tests dont't set the daemon
		return
	}
	table := &daemon.Table{Name: "kubernetes"}
	kc.accLock.Lock()
	for _, watcher := range kc.watchers {
		for _, svc := range watcher.Services {
			updateTableFromService(c, svc, watcher.Endpoints, table)
		}
		for _, pod := range watcher.Pods {
			updateTableFromPod(pod, table)
		}
	}
	kc.accLock.Unlock()

	// Send updated table to daemon
	if _, err := kc.daemon.Update(c, table); err != nil {
		dlog.Errorf(c, "error posting update to %s: %v", table.Name, err)
	}
}

func updateTableFromService(c context.Context, svc *kates.Service, endpoints []*kates.Endpoints, table *daemon.Table) {
	spec := &svc.Spec
	clusterIP := spec.ClusterIP
	var ips []string
	var err error
	// Handle headless services separately since they don't have cluster IPs
	if clusterIP == "" || clusterIP == "None" {
		ips, err = getIPsFromHeadlessService(svc, spec.ExternalName, endpoints)
		if err != nil {
			dlog.Errorf(c, "Error finding IPs for service %s: %s", svc.Name, err)
			return
		}
		if len(ips) == 0 {
			if svc.Name != "traffic-manager" {
				dlog.Errorf(c, "Found no IPs for service %s", svc.Name)
			}
			return
		}
	} else {
		ips = append(ips, clusterIP)
	}
	qName := svc.Name + "." + svc.Namespace + clusterServerSuffix

	ports := ""
	for _, port := range spec.Ports {
		if ports == "" {
			ports = fmt.Sprintf("%d", port.Port)
		} else {
			ports = fmt.Sprintf("%s,%d", ports, port.Port)
		}

		// Kubernetes creates records for all named ports, of the form
		// _my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example
		// https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#srv-records
		if port.Name != "" {
			proto := strings.ToLower(string(port.Protocol))
			table.Routes = append(table.Routes, &daemon.Route{
				Name:  fmt.Sprintf("_%v._%v.%v", port.Name, proto, qName),
				Ips:   ips,
				Port:  ports,
				Proto: proto,
			})
		}
	}

	table.Routes = append(table.Routes, &daemon.Route{
		Name:  qName,
		Ips:   ips,
		Port:  ports,
		Proto: "tcp",
	})
}

// Headless Services don't have a cluster IP so we need to get their IP(s) separately.
// Docs on headless services can be found here
// https://kubernetes.io/docs/concepts/services-networking/service/#headless-services
func getIPsFromHeadlessService(svc *kates.Service, externalName string, endpoints []*kates.Endpoints) ([]string, error) {
	serviceName := svc.Name
	var ips []string
	// ExternalName services don't have endpoints so we handle them separately.
	if externalName != "" {
		domainIPs, err := net.LookupIP(externalName)
		if err != nil {
			return nil, err
		}
		if len(domainIPs) == 0 {
			return nil, nil
		}
		for _, ip := range domainIPs {
			ips = append(ips, ip.String())
		}
	} else {
		// see if there's an associated endpoint with the headless service
		for i := range endpoints {
			if endpoints[i].ObjectMeta.Name == serviceName {
				matchingEndpoint := endpoints[i]
				if len(matchingEndpoint.Subsets) > 0 {
					endpointSubset := matchingEndpoint.Subsets[0]

					// Endpoints have two sets of addresses: Addresses and NotReadyAddresses
					// We only want to forward traffic to pods that are ready one so we only
					// loop through the ready addresses
					for _, address := range endpointSubset.Addresses {
						ips = append(ips, address.IP)
					}
					if len(ips) == 0 {
						return nil, nil
					}
				} else {
					return nil, nil
				}
				break
			}
		}
	}
	return ips, nil
}

func updateTableFromPod(pod *kates.Pod, table *daemon.Table) {
	qname := ""

	hostname := pod.Spec.Hostname
	if hostname != "" {
		qname += hostname
	}

	subdomain := pod.Spec.Subdomain
	if subdomain != "" {
		qname += "." + subdomain
	}

	if qname == "" {
		// Note: this is a departure from kubernetes, kubernetes will
		// simply not publish a dns name in this case.
		qname = pod.Name + "." + pod.Namespace + ".pod.cluster.local"
	} else {
		qname += clusterServerSuffix
	}

	ips := []string{pod.Status.PodIP}
	if len(ips) > 0 {
		table.Routes = append(table.Routes, &daemon.Route{
			Name:  qname,
			Ips:   ips,
			Proto: "tcp",
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2022 The Kubernetes Authors.

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

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package aws

import (
	"net/netip"
	"testing"

	"sigs.k8s.io/oci-proxy/pkg/net/cidrs"
)

var testCases = []struct {
	Addr           netip.Addr
	ExpectedRegion string
}{
	// some known IPs and their regions
	{Addr: netip.MustParseAddr("35.180.1.1"), ExpectedRegion: "eu-west-3"},
	{Addr: netip.MustParseAddr("35.250.1.1"), ExpectedRegion: ""},
	{Addr: netip.MustParseAddr("35.0.1.1"), ExpectedRegion: ""},
	{Addr: netip.MustParseAddr("52.94.76.1"), ExpectedRegion: "us-west-2"},
	{Addr: netip.MustParseAddr("52.94.77.1"), ExpectedRegion: "us-west-2"},
	{Addr: netip.MustParseAddr("52.93.127.172"), ExpectedRegion: "us-east-1"},
	// ipv6
	{Addr: netip.MustParseAddr("2400:6500:0:9::2"), ExpectedRegion: "ap-southeast-3"},
	{Addr: netip.MustParseAddr("2400:6500:0:9::1"), ExpectedRegion: "ap-southeast-3"},
	{Addr: netip.MustParseAddr("2400:6500:0:9::3"), ExpectedRegion: "ap-southeast-3"},
	{Addr: netip.MustParseAddr("2600:1f01:4874::47"), ExpectedRegion: "us-west-2"},
	{Addr: netip.MustParseAddr("2400:6500:0:9::100"), ExpectedRegion: ""},
}

func TestNewAWSRegionMapper(t *testing.T) {
	mapper := NewAWSRegionMapper()
	for i := range testCases {
		tc := testCases[i]
		t.Run(tc.Addr.String(), func(t *testing.T) {
			region, matched := mapper.GetIP(tc.Addr)
			expectMatched := tc.ExpectedRegion != ""
			if matched != expectMatched || region != tc.ExpectedRegion {
				t.Fatalf(
					"result does not match for %v, got: (%q, %t) expected: (%q, %t)",
					tc.Addr, region, matched, tc.ExpectedRegion, expectMatched,
				)
			}
		})
	}
}

/*  for benchmarking memory / init time */

func BenchmarkNewAWSRegionMapper(b *testing.B) {
	for n := 0; n < b.N; n++ {
		mapper := NewAWSRegionMapper()
		// get any address just to prevent mapper being optimized out
		mapper.GetIP(testCases[0].Addr)
	}
}

func BenchmarkNewAWSegionBruteForce(b *testing.B) {
	for n := 0; n < b.N; n++ {
		mapper := cidrs.NewBruteForceMapper(regionToRanges)
		// get any address just to prevent mapper being optimized out
		mapper.GetIP(testCases[0].Addr)
	}
}

/* for benchmarking matching time */

func BenchmarkAWSRegionTrieMap(b *testing.B) {
	mapper := NewAWSRegionMapper()
	for n := 0; n < b.N; n++ {
		tc := testCases[n%len(testCases)]
		region, matched := mapper.GetIP(tc.Addr)
		expectMatched := tc.ExpectedRegion != ""
		if matched != expectMatched || region != tc.ExpectedRegion {
			b.Fatalf(
				"result does not match for %v, got: (%q, %t) expected: (%q, %t)",
				tc.Addr, region, matched, tc.ExpectedRegion, expectMatched,
			)
		}
	}
}

func BenchmarkAWSRegionBruteForce(b *testing.B) {
	mapper := cidrs.NewBruteForceMapper(regionToRanges)
	for n := 0; n < b.N; n++ {
		tc := testCases[n%len(testCases)]
		region, matched := mapper.GetIP(tc.Addr)
		expectMatched := tc.ExpectedRegion != ""
		if matched != expectMatched || region != tc.ExpectedRegion {
			b.Fatalf(
				"result does not match for %v, got: (%q, %t) expected: (%q, %t)",
				tc.Addr, region, matched, tc.ExpectedRegion, expectMatched,
			)
		}
	}
}
<commit_msg>split IPv4/ IPv6 benchmarks<commit_after>/*
Copyright 2022 The Kubernetes Authors.

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

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package aws

import (
	"net/netip"
	"testing"

	"sigs.k8s.io/oci-proxy/pkg/net/cidrs"
)

type testCase struct {
	Addr           netip.Addr
	ExpectedRegion string
}

// some known IPs and their regions
var testCasesIPv4 = []testCase{
	{Addr: netip.MustParseAddr("35.180.1.1"), ExpectedRegion: "eu-west-3"},
	{Addr: netip.MustParseAddr("35.250.1.1"), ExpectedRegion: ""},
	{Addr: netip.MustParseAddr("35.0.1.1"), ExpectedRegion: ""},
	{Addr: netip.MustParseAddr("52.94.76.1"), ExpectedRegion: "us-west-2"},
	{Addr: netip.MustParseAddr("52.94.77.1"), ExpectedRegion: "us-west-2"},
	{Addr: netip.MustParseAddr("52.93.127.172"), ExpectedRegion: "us-east-1"},
}
var testCasesIPv6 = []testCase{
	// ipv6
	{Addr: netip.MustParseAddr("2400:6500:0:9::2"), ExpectedRegion: "ap-southeast-3"},
	{Addr: netip.MustParseAddr("2400:6500:0:9::1"), ExpectedRegion: "ap-southeast-3"},
	{Addr: netip.MustParseAddr("2400:6500:0:9::3"), ExpectedRegion: "ap-southeast-3"},
	{Addr: netip.MustParseAddr("2600:1f01:4874::47"), ExpectedRegion: "us-west-2"},
	{Addr: netip.MustParseAddr("2400:6500:0:9::100"), ExpectedRegion: ""},
}

// NOTE: we need to append to an empty slice so we do not modify the existing slices
// append may re-use the first existing slice ...
var allTestCases = append(append([]testCase{}, testCasesIPv4...), testCasesIPv6...)

func TestNewAWSRegionMapper(t *testing.T) {
	mapper := NewAWSRegionMapper()
	for i := range allTestCases {
		tc := allTestCases[i]
		t.Run(tc.Addr.String(), func(t *testing.T) {
			region, matched := mapper.GetIP(tc.Addr)
			expectMatched := tc.ExpectedRegion != ""
			if matched != expectMatched || region != tc.ExpectedRegion {
				t.Fatalf(
					"result does not match for %v, got: (%q, %t) expected: (%q, %t)",
					tc.Addr, region, matched, tc.ExpectedRegion, expectMatched,
				)
			}
		})
	}
}

/*  for benchmarking memory / init time */

func BenchmarkNewAWSRegionMapper(b *testing.B) {
	for n := 0; n < b.N; n++ {
		mapper := NewAWSRegionMapper()
		// get any address just to prevent mapper being optimized out
		mapper.GetIP(allTestCases[0].Addr)
	}
}

func BenchmarkNewAWSegionBruteForce(b *testing.B) {
	for n := 0; n < b.N; n++ {
		mapper := cidrs.NewBruteForceMapper(regionToRanges)
		// get any address just to prevent mapper being optimized out
		mapper.GetIP(allTestCases[0].Addr)
	}
}

/* for benchmarking matching time */

func BenchmarkAWSRegionTrieMapIPv4(b *testing.B) {
	mapper := NewAWSRegionMapper()
	for n := 0; n < b.N; n++ {
		tc := testCasesIPv4[n%len(testCasesIPv4)]
		region, matched := mapper.GetIP(tc.Addr)
		expectMatched := tc.ExpectedRegion != ""
		if matched != expectMatched || region != tc.ExpectedRegion {
			b.Fatalf(
				"result does not match for %v, got: (%q, %t) expected: (%q, %t)",
				tc.Addr, region, matched, tc.ExpectedRegion, expectMatched,
			)
		}
	}
}

func BenchmarkAWSRegionTrieMapIPv6(b *testing.B) {
	mapper := NewAWSRegionMapper()
	for n := 0; n < b.N; n++ {
		tc := testCasesIPv6[n%len(testCasesIPv6)]
		region, matched := mapper.GetIP(tc.Addr)
		expectMatched := tc.ExpectedRegion != ""
		if matched != expectMatched || region != tc.ExpectedRegion {
			b.Fatalf(
				"result does not match for %v, got: (%q, %t) expected: (%q, %t)",
				tc.Addr, region, matched, tc.ExpectedRegion, expectMatched,
			)
		}
	}
}

func BenchmarkAWSRegionBruteForceIPv4(b *testing.B) {
	mapper := cidrs.NewBruteForceMapper(regionToRanges)
	for n := 0; n < b.N; n++ {
		tc := testCasesIPv4[n%len(testCasesIPv4)]
		region, matched := mapper.GetIP(tc.Addr)
		expectMatched := tc.ExpectedRegion != ""
		if matched != expectMatched || region != tc.ExpectedRegion {
			b.Fatalf(
				"result does not match for %v, got: (%q, %t) expected: (%q, %t)",
				tc.Addr, region, matched, tc.ExpectedRegion, expectMatched,
			)
		}
	}
}

func BenchmarkAWSRegionBruteForceIPv6(b *testing.B) {
	mapper := cidrs.NewBruteForceMapper(regionToRanges)
	for n := 0; n < b.N; n++ {
		tc := testCasesIPv6[n%len(testCasesIPv6)]
		region, matched := mapper.GetIP(tc.Addr)
		expectMatched := tc.ExpectedRegion != ""
		if matched != expectMatched || region != tc.ExpectedRegion {
			b.Fatalf(
				"result does not match for %v, got: (%q, %t) expected: (%q, %t)",
				tc.Addr, region, matched, tc.ExpectedRegion, expectMatched,
			)
		}
	}
}
<|endoftext|>


<commit_before>// +build !appengine

/*
Copyright 2013 The Camlistore Authors.

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

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package osutil

import (
	"log"
)

func DieOnParentDeath() {
	// TODO(mpl): maybe the way it's done in findproc_normal.go actually works
	// on appengine too? Verify that.
	log.Fatal("DieOnParentDeath not implemented on appengine.")
}
<commit_msg>osutil: fix typo in build tag<commit_after>// +build appengine

/*
Copyright 2013 The Camlistore Authors.

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

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package osutil

import (
	"log"
)

func DieOnParentDeath() {
	// TODO(mpl): maybe the way it's done in findproc_normal.go actually works
	// on appengine too? Verify that.
	log.Fatal("DieOnParentDeath not implemented on appengine.")
}
<|endoftext|>


<commit_before>package synthetictests

import (
	"context"
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
	"strings"

	"github.com/openshift/origin/pkg/monitor/monitorapi"
	"github.com/openshift/origin/pkg/test/ginkgo/junitapi"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

// staticPodFailureRegex trying to pull out information from messages like
// `static pod lifecycle failure - static pod: "etcd" in namespace: "openshift-etcd" for revision: 6 on node: "ovirt10-gh8t5-master-2" didn't show up, waited: 2m30s`
var staticPodFailureRegex = regexp.MustCompile(
	`static pod lifecycle failure - static pod: ".*" in namespace: "(.*)" for revision: (\d) on node: "(.*)" didn't show up, waited: .*`)

type staticPodFailure struct {
	namespace      string
	node           string
	revision       int64
	failureMessage string
}

func staticPodFailureFromMessage(message string) (*staticPodFailure, error) {
	matches := staticPodFailureRegex.FindStringSubmatch(message)
	if len(matches) != 4 {
		return nil, fmt.Errorf("wrong number of matches: %v", matches)
	}
	revision, err := strconv.ParseInt(matches[2], 0, 64)
	if err != nil {
		return nil, err
	}

	return &staticPodFailure{
		namespace:      matches[1],
		node:           matches[3],
		revision:       revision,
		failureMessage: message,
	}, nil
}

func testStaticPodLifecycleFailure(events monitorapi.Intervals, kubeClientConfig *rest.Config, testSuite string) []*junitapi.JUnitTestCase {
	ctx := context.TODO()
	const testName = `[sig-node] static pods should start after being created`
	failures := []string{}

	kubeClient, err := kubernetes.NewForConfig(kubeClientConfig)
	if err != nil {
		return []*junitapi.JUnitTestCase{
			{
				Name: testName,
				FailureOutput: &junitapi.FailureOutput{
					Output: err.Error(),
				},
				SystemOut: err.Error(),
			},
		}
	}

	staticPodNamespaces := []string{
		"openshift-etcd-operator",
		"openshift-kube-apiserver-operator",
		"openshift-kube-controller-manager-operator",
		"openshift-kube-scheduler-operator",
	}
	staticPodFailures := []staticPodFailure{}
	for _, ns := range staticPodNamespaces {
		// we need to get all the events from the cluster, so we cannot use the monitor events.
		events, err := kubeClient.EventsV1().Events(ns).List(ctx, metav1.ListOptions{})
		if err != nil {
			failures = append(failures, err.Error())
			continue
		}

		for _, event := range events.Items {
			if event.Reason == "OperatorStatusChanged" { // skip the clusteroperator status change event.
				continue
			}
			if !strings.Contains(event.Note, "static pod lifecycle failure") {
				continue
			}

			staticPodFailure, err := staticPodFailureFromMessage(event.Note)
			if err != nil {
				failures = append(failures, fmt.Sprintf("%v", err))
				continue
			}
			staticPodFailures = append(staticPodFailures, *staticPodFailure)
		}
	}

	// now check each failure to see if we eventually reached the level.  If we eventually reached the level
	// then don't flag it
	for _, staticPodFailure := range staticPodFailures {
		events, err := kubeClient.EventsV1().Events(staticPodFailure.namespace).List(ctx, metav1.ListOptions{})
		if err != nil {
			failures = append(failures, err.Error())
			continue
		}
		foundEventForProperRevision := false
		for _, event := range events.Items {
			isRevisionUpdate := event.Reason == "NodeCurrentRevisionChanged"
			isForNode := strings.Contains(event.Note, staticPodFailure.node)
			matches := regexp.MustCompile("to ([0-9]+) because static pod is ready").FindStringSubmatch(event.Note)
			if len(matches) == 2 {
				reachedRevision, _ := strconv.ParseInt(matches[1], 0, 64)
				if isRevisionUpdate && isForNode && reachedRevision == staticPodFailure.revision {
					// If we reach the level eventually, don't fail the test. We might choose to add an "it's slow" test, but
					// it hasn't failed. It might be possible to go directly to a later revision, and if we want to account for
					// that, the above could be changed to >= instead of equality.
					foundEventForProperRevision = true
				}
			}
		}

		// We are suspecting events API and core API are not returning the same events. Double check here.
		// For debugging purpose
		if !foundEventForProperRevision {
			coreEvents, err := kubeClient.CoreV1().Events(staticPodFailure.namespace).List(ctx, metav1.ListOptions{})
			if err == nil {
				for _, event := range coreEvents.Items {
					isRevisionUpdate := event.Reason == "NodeCurrentRevisionChanged"
					isForNode := strings.Contains(event.Message, staticPodFailure.node)
					matches := regexp.MustCompile("to ([0-9]+) because static pod is ready").FindStringSubmatch(event.Message)
					if len(matches) == 2 {
						reachedRevision, _ := strconv.ParseInt(matches[1], 0, 64)
						if isRevisionUpdate && isForNode && reachedRevision == staticPodFailure.revision {
							// Found the event in events returned from core API
							foundEventForProperRevision = true
							fmt.Printf("test %s with failure message '%s' recovered\n", testName, staticPodFailure.failureMessage)
							// Log the events API events
							eventString, err := json.Marshal(events)
							if err == nil {
								fmt.Printf("test %s recovered: corresponding events from events API %s\n", testName, eventString)
							}

							// Log the core API events
							eventString, err = json.Marshal(coreEvents)
							if err == nil {
								fmt.Printf("test %s recovered: corresponding events from core API %s\n", testName, eventString)
							}
						}
					}
				}
			}
		}

		if !foundEventForProperRevision {
			failures = append(failures, staticPodFailure.failureMessage)
		}
	}

	if len(failures) > 0 {
		return []*junitapi.JUnitTestCase{
			{
				Name: testName,
				FailureOutput: &junitapi.FailureOutput{
					Output: strings.Join(failures, "\n"),
				},
				SystemOut: strings.Join(failures, "\n"),
			},
		}
	}

	return []*junitapi.JUnitTestCase{
		{
			Name: testName,
		},
	}
}
<commit_msg>Dump debug message whenever static pod test fails<commit_after>package synthetictests

import (
	"context"
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
	"strings"

	"github.com/openshift/origin/pkg/monitor/monitorapi"
	"github.com/openshift/origin/pkg/test/ginkgo/junitapi"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

// staticPodFailureRegex trying to pull out information from messages like
// `static pod lifecycle failure - static pod: "etcd" in namespace: "openshift-etcd" for revision: 6 on node: "ovirt10-gh8t5-master-2" didn't show up, waited: 2m30s`
var staticPodFailureRegex = regexp.MustCompile(
	`static pod lifecycle failure - static pod: ".*" in namespace: "(.*)" for revision: (\d) on node: "(.*)" didn't show up, waited: .*`)

type staticPodFailure struct {
	namespace      string
	node           string
	revision       int64
	failureMessage string
}

func staticPodFailureFromMessage(message string) (*staticPodFailure, error) {
	matches := staticPodFailureRegex.FindStringSubmatch(message)
	if len(matches) != 4 {
		return nil, fmt.Errorf("wrong number of matches: %v", matches)
	}
	revision, err := strconv.ParseInt(matches[2], 0, 64)
	if err != nil {
		return nil, err
	}

	return &staticPodFailure{
		namespace:      matches[1],
		node:           matches[3],
		revision:       revision,
		failureMessage: message,
	}, nil
}

func testStaticPodLifecycleFailure(events monitorapi.Intervals, kubeClientConfig *rest.Config, testSuite string) []*junitapi.JUnitTestCase {
	ctx := context.TODO()
	const testName = `[sig-node] static pods should start after being created`
	failures := []string{}

	kubeClient, err := kubernetes.NewForConfig(kubeClientConfig)
	if err != nil {
		return []*junitapi.JUnitTestCase{
			{
				Name: testName,
				FailureOutput: &junitapi.FailureOutput{
					Output: err.Error(),
				},
				SystemOut: err.Error(),
			},
		}
	}

	staticPodNamespaces := []string{
		"openshift-etcd-operator",
		"openshift-kube-apiserver-operator",
		"openshift-kube-controller-manager-operator",
		"openshift-kube-scheduler-operator",
	}
	staticPodFailures := []staticPodFailure{}
	for _, ns := range staticPodNamespaces {
		// we need to get all the events from the cluster, so we cannot use the monitor events.
		events, err := kubeClient.EventsV1().Events(ns).List(ctx, metav1.ListOptions{})
		if err != nil {
			failures = append(failures, err.Error())
			continue
		}

		for _, event := range events.Items {
			if event.Reason == "OperatorStatusChanged" { // skip the clusteroperator status change event.
				continue
			}
			if !strings.Contains(event.Note, "static pod lifecycle failure") {
				continue
			}

			staticPodFailure, err := staticPodFailureFromMessage(event.Note)
			if err != nil {
				failures = append(failures, fmt.Sprintf("%v", err))
				continue
			}
			staticPodFailures = append(staticPodFailures, *staticPodFailure)
		}
	}

	// now check each failure to see if we eventually reached the level.  If we eventually reached the level
	// then don't flag it
	for _, staticPodFailure := range staticPodFailures {
		events, err := kubeClient.EventsV1().Events(staticPodFailure.namespace).List(ctx, metav1.ListOptions{})
		if err != nil {
			failures = append(failures, err.Error())
			continue
		}
		foundEventForProperRevision := false
		for _, event := range events.Items {
			isRevisionUpdate := event.Reason == "NodeCurrentRevisionChanged"
			isForNode := strings.Contains(event.Note, staticPodFailure.node)
			matches := regexp.MustCompile("to ([0-9]+) because static pod is ready").FindStringSubmatch(event.Note)
			if len(matches) == 2 {
				reachedRevision, _ := strconv.ParseInt(matches[1], 0, 64)
				if isRevisionUpdate && isForNode && reachedRevision == staticPodFailure.revision {
					// If we reach the level eventually, don't fail the test. We might choose to add an "it's slow" test, but
					// it hasn't failed. It might be possible to go directly to a later revision, and if we want to account for
					// that, the above could be changed to >= instead of equality.
					foundEventForProperRevision = true
				}
			}
		}

		// We are suspecting events API and core API are not returning the same events. Double check here.
		// For debugging purpose
		if !foundEventForProperRevision {
			// Log the events API events
			eventString, err := json.Marshal(events)
			if err == nil {
				fmt.Printf("test %s with failure message '%s' failed: corresponding events from events API %s\n", testName, staticPodFailure.failureMessage, eventString)
			}
			coreEvents, err := kubeClient.CoreV1().Events(staticPodFailure.namespace).List(ctx, metav1.ListOptions{})
			if err == nil {
				for _, event := range coreEvents.Items {
					isRevisionUpdate := event.Reason == "NodeCurrentRevisionChanged"
					isForNode := strings.Contains(event.Message, staticPodFailure.node)
					matches := regexp.MustCompile("to ([0-9]+) because static pod is ready").FindStringSubmatch(event.Message)
					if len(matches) == 2 {
						reachedRevision, _ := strconv.ParseInt(matches[1], 0, 64)
						if isRevisionUpdate && isForNode && reachedRevision == staticPodFailure.revision {
							// Found the event in events returned from core API
							foundEventForProperRevision = true
							fmt.Printf("test %s with failure message '%s' recovered\n", testName, staticPodFailure.failureMessage)

							// Log the core API events
							eventString, err = json.Marshal(coreEvents)
							if err == nil {
								fmt.Printf("test %s recovered: corresponding events from core API %s\n", testName, eventString)
							}
						}
					}
				}
			}
		}

		if !foundEventForProperRevision {
			failures = append(failures, staticPodFailure.failureMessage)
		}
	}

	if len(failures) > 0 {
		return []*junitapi.JUnitTestCase{
			{
				Name: testName,
				FailureOutput: &junitapi.FailureOutput{
					Output: strings.Join(failures, "\n"),
				},
				SystemOut: strings.Join(failures, "\n"),
			},
		}
	}

	return []*junitapi.JUnitTestCase{
		{
			Name: testName,
		},
	}
}
<|endoftext|>


<commit_before>package tlsclient

import (
	"crypto/tls"
	"io"
	"math/rand"
	"net"
	"time"
)

type MyConn struct {
	data []byte
}

func (c *MyConn) Read(b []byte) (n int, err error) {
	if len(c.data) == 0 {
		return 0, io.EOF
	}
	n = copy(b, c.data)
	c.data = c.data[n:]
	return
}

func (c *MyConn) Write(b []byte) (n int, err error) {
	return len(b), nil
}

func (c *MyConn) Close() error {
	return nil
}

func (c *MyConn) LocalAddr() net.Addr {
	return &net.TCPAddr{net.IP{127, 0, 0, 1}, 49706, ""}
}

func (c *MyConn) RemoteAddr() net.Addr {
	return &net.TCPAddr{net.IP{127, 0, 0, 1}, 49706, ""}
}

func (c *MyConn) SetDeadline(t time.Time) error {
	return nil
}

func (c *MyConn) SetReadDeadline(t time.Time) error {
	return nil
}

func (c *MyConn) SetWriteDeadline(t time.Time) error {
	return nil
}

type MathRandReader int

func (MathRandReader) Read(buf []byte) (int, error) {
	for i := range buf {
		buf[i] = byte(rand.Intn(256))
	}
	return len(buf), nil
}

func Fuzz(data []byte) int {
	rand.Seed(0)
	c := &MyConn{data}
	tc := tls.Client(c, &tls.Config{
		InsecureSkipVerify: true,
		Rand:               MathRandReader(0),
		Time:               func() time.Time { return time.Date(2000, 1, 1, 1, 1, 1, 1, nil) },
	})
	tc.Handshake()
	return 0
}
<commit_msg>simplify rand reader in tlsclient test math.Rand does not work for some reason (tests are still not reproducible)<commit_after>package tlsclient

import (
	"crypto/tls"
	"io"
	"net"
	"time"
)

type MyConn struct {
	data []byte
}

func (c *MyConn) Read(b []byte) (n int, err error) {
	if len(c.data) == 0 {
		return 0, io.EOF
	}
	n = copy(b, c.data)
	c.data = c.data[n:]
	return
}

func (c *MyConn) Write(b []byte) (n int, err error) {
	return len(b), nil
}

func (c *MyConn) Close() error {
	return nil
}

func (c *MyConn) LocalAddr() net.Addr {
	return &net.TCPAddr{net.IP{127, 0, 0, 1}, 49706, ""}
}

func (c *MyConn) RemoteAddr() net.Addr {
	return &net.TCPAddr{net.IP{127, 0, 0, 1}, 49706, ""}
}

func (c *MyConn) SetDeadline(t time.Time) error {
	return nil
}

func (c *MyConn) SetReadDeadline(t time.Time) error {
	return nil
}

func (c *MyConn) SetWriteDeadline(t time.Time) error {
	return nil
}

type MathRandReader int

func (MathRandReader) Read(buf []byte) (int, error) {
	for i := range buf {
		buf[i] = 0
	}
	return len(buf), nil
}

func Fuzz(data []byte) int {
	c := &MyConn{data}
	tc := tls.Client(c, &tls.Config{
		InsecureSkipVerify: true,
		Rand:               MathRandReader(0),
		Time:               func() time.Time { return time.Date(2000, 1, 1, 1, 1, 1, 1, nil) },
	})
	tc.Handshake()
	return 0
}
<|endoftext|>


<commit_before>// Package forth implements a tiny subset of the Forth language.
package forth

import (
	"errors"
	"strconv"
	"strings"
	"unicode"
)

type operatorFn func(stack *[]int) error

type operatorID byte

const (
	addOp operatorID = iota
	subOp
	mulOp
	divOp
	dropOp
	dupOp
	swapOp
	overOp
	constOp
	userDefOp
	endDefOp
)

type operatorTyp struct {
	fn operatorFn
	id operatorID
}

func Forth(input []string) (result []int, err error) {
	if len(input) == 0 {
		return []int{}, nil
	}

	// Allocate an initially empty stack, with arbitrary starting capacity of 8.
	stack := make([]int, 0, 8)
	// Allocate a map for user defined words.
	userDefs := make(map[string][]operatorTyp, 8)
	for _, phrase := range input {
		// Parse one phrase of input, building up an operator list.
		opList, err := parse(phrase, userDefs)
		if err != nil {
			return nil, err
		}
		// Perform any operators from that phrase, updating stack.
		for _, opr := range opList {
			err = opr.fn(&stack)
			if err != nil {
				return nil, err
			}
		}
	}

	return stack[:len(stack)], nil
}

// parse given phrase, returning an operator list, and updating
// a userDefs map for any user definifion of words in phrase.
func parse(phrase string, userDefs map[string][]operatorTyp) (oplist []operatorTyp, err error) {
	words := strings.FieldsFunc(phrase,
		func(r rune) bool {
			return unicode.IsSpace(r) || unicode.IsControl(r)
		})

	// t is token index into words[]
	for t := 0; t < len(words); t++ {
		w := strings.ToUpper(words[t])
		// Handle reference to user defined word.
		if udef, ok := userDefs[w]; ok {
			oplist = append(oplist, udef...)
		} else if op, ok := builtinOps[w]; ok {
			if op.id == userDefOp {
				// Handle user defined word definition.
				t++
				if t >= len(words)-2 {
					return nil, errEmptyUserDef
				}
				userword := strings.ToUpper(words[t])
				if _, numerr := strconv.Atoi(userword); numerr == nil {
					return nil, errInvalidUserDef
				}
				t++
				var userops []operatorTyp
				for t < len(words) {
					oneOp, err := parse(words[t], userDefs)
					if err != nil {
						return nil, err
					}
					if oneOp[0].id == endDefOp {
						break
					}
					userops = append(userops, oneOp...)
					t++
				}
				if len(userops) == 0 {
					return nil, errEmptyUserDef
				} else {
					userDefs[userword] = userops
				}
			} else {
				// Normal builtin operator.
				oplist = append(oplist, op)
			}
		} else {
			// Handle constant literal.
			var x int
			x, err = strconv.Atoi(w)
			if err != nil {
				return nil, err
			}
			oplist = append(oplist,
				operatorTyp{id: constOp,
					fn: func(stack *[]int) error {
						push(stack, x)
						return nil
					},
				})
		}
	}
	return oplist, nil
}

// builtinOps are the pre-defined operators to support.
var builtinOps = map[string]operatorTyp{
	"+":    {add, addOp},
	"-":    {subtract, subOp},
	"*":    {multiply, mulOp},
	"/":    {divide, divOp},
	"DUP":  {dup, dropOp},
	"DROP": {drop, dupOp},
	"SWAP": {swap, swapOp},
	"OVER": {over, overOp},
	":":    {nil, userDefOp},
	";":    {nil, endDefOp},
}

func pop(stack *[]int) (v int, err error) {
	slen := len(*stack)
	if slen >= 1 {
		v = (*stack)[slen-1]
		*stack = (*stack)[:slen-1]
		return v, nil
	}
	return 0, errNotEnoughOperands
}

func pop2(stack *[]int) (v1, v2 int, err error) {
	v1, err = pop(stack)
	if err != nil {
		return 0, 0, err
	}
	v2, err = pop(stack)
	return v1, v2, err
}

func push(stack *[]int, v int) {
	*stack = append(*stack, v)
}

func binaryOp(stack *[]int, op func(a, b int) int) error {
	v1, v2, err := pop2(stack)
	if err != nil {
		return err
	}
	push(stack, op(v2, v1))
	return nil
}

func add(stack *[]int) (err error) {
	return binaryOp(stack, func(a, b int) int { return a + b })
}

func subtract(stack *[]int) (err error) {
	return binaryOp(stack, func(a, b int) int { return a - b })
}

func multiply(stack *[]int) error {
	return binaryOp(stack, func(a, b int) int { return a * b })
}

func divide(stack *[]int) error {
	v1, v2, err := pop2(stack)
	if err != nil {
		return err
	}
	if v1 == 0 {
		return errDivideByZero
	}
	push(stack, v2/v1)
	return nil
}

func dup(stack *[]int) error {
	v1, err := pop(stack)
	if err != nil {
		return err
	}
	push(stack, v1)
	push(stack, v1)
	return nil
}

func drop(stack *[]int) error {
	_, err := pop(stack)
	return err
}

func over(stack *[]int) error {
	v1, v2, err := pop2(stack)
	if err != nil {
		return err
	}
	push(stack, v2)
	push(stack, v1)
	push(stack, v2)
	return nil
}

func swap(stack *[]int) error {
	v1, v2, err := pop2(stack)
	if err != nil {
		return err
	}
	push(stack, v1)
	push(stack, v2)
	return nil
}

var errNotEnoughOperands = errors.New("not enough operands")
var errDivideByZero = errors.New("attempt to divide by zero")
var errEmptyUserDef = errors.New("empty user definition")
var errInvalidUserDef = errors.New("invalid user def word")

const testVersion = 1
<commit_msg>forth: Remove unnecessary else (go lint)<commit_after>// Package forth implements a tiny subset of the Forth language.
package forth

import (
	"errors"
	"strconv"
	"strings"
	"unicode"
)

type operatorFn func(stack *[]int) error

type operatorID byte

const (
	addOp operatorID = iota
	subOp
	mulOp
	divOp
	dropOp
	dupOp
	swapOp
	overOp
	constOp
	userDefOp
	endDefOp
)

type operatorTyp struct {
	fn operatorFn
	id operatorID
}

func Forth(input []string) (result []int, err error) {
	if len(input) == 0 {
		return []int{}, nil
	}

	// Allocate an initially empty stack, with arbitrary starting capacity of 8.
	stack := make([]int, 0, 8)
	// Allocate a map for user defined words.
	userDefs := make(map[string][]operatorTyp, 8)
	for _, phrase := range input {
		// Parse one phrase of input, building up an operator list.
		opList, err := parse(phrase, userDefs)
		if err != nil {
			return nil, err
		}
		// Perform any operators from that phrase, updating stack.
		for _, opr := range opList {
			err = opr.fn(&stack)
			if err != nil {
				return nil, err
			}
		}
	}

	return stack[:len(stack)], nil
}

// parse given phrase, returning an operator list, and updating
// a userDefs map for any user definifion of words in phrase.
func parse(phrase string, userDefs map[string][]operatorTyp) (oplist []operatorTyp, err error) {
	words := strings.FieldsFunc(phrase,
		func(r rune) bool {
			return unicode.IsSpace(r) || unicode.IsControl(r)
		})

	// t is token index into words[]
	for t := 0; t < len(words); t++ {
		w := strings.ToUpper(words[t])
		// Handle reference to user defined word.
		if udef, ok := userDefs[w]; ok {
			oplist = append(oplist, udef...)
		} else if op, ok := builtinOps[w]; ok {
			if op.id == userDefOp {
				// Handle user defined word definition.
				t++
				if t >= len(words)-2 {
					return nil, errEmptyUserDef
				}
				userword := strings.ToUpper(words[t])
				if _, numerr := strconv.Atoi(userword); numerr == nil {
					return nil, errInvalidUserDef
				}
				t++
				var userops []operatorTyp
				for t < len(words) {
					oneOp, err := parse(words[t], userDefs)
					if err != nil {
						return nil, err
					}
					if oneOp[0].id == endDefOp {
						break
					}
					userops = append(userops, oneOp...)
					t++
				}
				if len(userops) == 0 {
					return nil, errEmptyUserDef
				}
				userDefs[userword] = userops
			} else {
				// Normal builtin operator.
				oplist = append(oplist, op)
			}
		} else {
			// Handle constant literal.
			var x int
			x, err = strconv.Atoi(w)
			if err != nil {
				return nil, err
			}
			oplist = append(oplist,
				operatorTyp{id: constOp,
					fn: func(stack *[]int) error {
						push(stack, x)
						return nil
					},
				})
		}
	}
	return oplist, nil
}

// builtinOps are the pre-defined operators to support.
var builtinOps = map[string]operatorTyp{
	"+":    {add, addOp},
	"-":    {subtract, subOp},
	"*":    {multiply, mulOp},
	"/":    {divide, divOp},
	"DUP":  {dup, dropOp},
	"DROP": {drop, dupOp},
	"SWAP": {swap, swapOp},
	"OVER": {over, overOp},
	":":    {nil, userDefOp},
	";":    {nil, endDefOp},
}

func pop(stack *[]int) (v int, err error) {
	slen := len(*stack)
	if slen >= 1 {
		v = (*stack)[slen-1]
		*stack = (*stack)[:slen-1]
		return v, nil
	}
	return 0, errNotEnoughOperands
}

func pop2(stack *[]int) (v1, v2 int, err error) {
	v1, err = pop(stack)
	if err != nil {
		return 0, 0, err
	}
	v2, err = pop(stack)
	return v1, v2, err
}

func push(stack *[]int, v int) {
	*stack = append(*stack, v)
}

func binaryOp(stack *[]int, op func(a, b int) int) error {
	v1, v2, err := pop2(stack)
	if err != nil {
		return err
	}
	push(stack, op(v2, v1))
	return nil
}

func add(stack *[]int) (err error) {
	return binaryOp(stack, func(a, b int) int { return a + b })
}

func subtract(stack *[]int) (err error) {
	return binaryOp(stack, func(a, b int) int { return a - b })
}

func multiply(stack *[]int) error {
	return binaryOp(stack, func(a, b int) int { return a * b })
}

func divide(stack *[]int) error {
	v1, v2, err := pop2(stack)
	if err != nil {
		return err
	}
	if v1 == 0 {
		return errDivideByZero
	}
	push(stack, v2/v1)
	return nil
}

func dup(stack *[]int) error {
	v1, err := pop(stack)
	if err != nil {
		return err
	}
	push(stack, v1)
	push(stack, v1)
	return nil
}

func drop(stack *[]int) error {
	_, err := pop(stack)
	return err
}

func over(stack *[]int) error {
	v1, v2, err := pop2(stack)
	if err != nil {
		return err
	}
	push(stack, v2)
	push(stack, v1)
	push(stack, v2)
	return nil
}

func swap(stack *[]int) error {
	v1, v2, err := pop2(stack)
	if err != nil {
		return err
	}
	push(stack, v1)
	push(stack, v2)
	return nil
}

var errNotEnoughOperands = errors.New("not enough operands")
var errDivideByZero = errors.New("attempt to divide by zero")
var errEmptyUserDef = errors.New("empty user definition")
var errInvalidUserDef = errors.New("invalid user def word")

const testVersion = 1
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"os/exec"
	"time"

	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-wireless/iwlib"
	"github.com/paypal/gatt"
)

type WifiNetwork struct {
	SSID string `json:"name"`
}

type WifiCredentials struct {
	SSID string `json:"ssid"`
	Key  string `json:"key"`
}

const WLANInterfaceTemplate = "iface wlan0 inet dhcp\n"

// This is ugly... but for some reason go-ninja was only delivering the progress to one of the
// listeners, so rpc and http need to share.
var lastUpdateProgress map[string]interface{}

func GetSetupRPCRouter(conn *ninja.Connection, wifi_manager *WifiManager, srv *gatt.Server, pairing_ui ConsolePairingUI) *JSONRPCRouter {

	rpc_router := &JSONRPCRouter{}
	rpc_router.Init()
	rpc_router.AddHandler("sphere.setup.ping", func(request JSONRPCRequest) chan JSONRPCResponse {
		resp := make(chan JSONRPCResponse, 1)

		pong := JSONRPCResponse{"2.0", request.Id, 1234, nil}
		resp <- pong

		return resp
	})

	rpc_router.AddHandler("sphere.setup.get_visible_wifi_networks", func(request JSONRPCRequest) chan JSONRPCResponse {
		resp := make(chan JSONRPCResponse, 1)

		pairing_ui.DisplayIcon("wifi-searching.gif")

		// Before we search for wifi networks, disable any that are try-fail-ing
		wifi_manager.DisableAllNetworks()

		networks, err := iwlib.GetWirelessNetworks("wlan0")
		if err == nil {
			wifi_networks := make([]WifiNetwork, len(networks))
			for i, network := range networks {
				wifi_networks[i].SSID = network.SSID
			}

			resp <- JSONRPCResponse{"2.0", request.Id, wifi_networks, nil}
		} else {
			resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, "Could not retrieve WiFi networks", nil}}
		}

		return resp
	})

	rpc_router.AddHandler("sphere.setup.connect_wifi_network", func(request JSONRPCRequest) chan JSONRPCResponse {
		resp := make(chan JSONRPCResponse, 1)

		pairing_ui.DisplayIcon("wifi-connecting.gif")

		wifi_creds := new(WifiCredentials)
		b, _ := json.Marshal(request.Params[0])
		json.Unmarshal(b, wifi_creds)

		logger.Debugf("Got wifi credentials %v", wifi_creds)

		go func() {
			success := wifi_manager.SetCredentials(wifi_creds)
			if success {
				var err error
				var path string
				var serial_number string

				pairing_ui.DisplayIcon("wifi-connected.gif")

				path, err = exec.LookPath("sphere-serial")
				if err == nil {
					serial_number, err = exec.Command("sphere-serial").Output()
				}
				if err == nil {
					pong := JSONRPCResponse{"2.0", request.Id, string(serial_number), nil}
					resp <- pong
				} else {
					logger.Errorf("failed to obtain serial number: %v", err)
					resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, "Failed to obtain serial number", nil}}
				}

			} else {
				pairing_ui.DisplayIcon("wifi-failed.gif")
				resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, "Could not connect to specified WiFi network, is the key correct?", nil}}
			}
		}()

		return resp
	})

	rpc_router.AddHandler("sphere.setup.acknowledge_wifi_connected", func(request JSONRPCRequest) chan JSONRPCResponse {
		resp := make(chan JSONRPCResponse, 1)
		go func() {
			wifi_manager.ConnectionAcknowledged()
			logger.Infof("Received acknowledgement of wifi connected from app.")
			pairing_ui.DisplayIcon("wifi-connected.gif")
			resp <- JSONRPCResponse{"2.0", request.Id, nil, nil}
		}()

		return resp
	})

	if !factoryReset {

		updateService := conn.GetServiceClient("$node/" + config.Serial() + "/updates")
		ledService := conn.GetServiceClient("$node/" + config.Serial() + "/led-controller")

		rpc_router.AddHandler("sphere.setup.start_update", func(request JSONRPCRequest) chan JSONRPCResponse {
			resp := make(chan JSONRPCResponse, 1)

			var response bool

			logger.Infof("Starting update id: %d. Waiting for response....", request.Id)

			err := updateService.Call("start", nil, &response, time.Second*10)

			if response {
				lastUpdateProgress = nil
			}

			logger.Infof("Got update start response: %d. %v", request.Id, response)

			if err == nil {
				resp <- JSONRPCResponse{"2.0", request.Id, &response, nil}
			} else {
				resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, fmt.Sprintf("%s", err), nil}}
			}

			return resp
		})

		rpc_router.AddHandler("sphere.setup.get_update_progress", func(request JSONRPCRequest) chan JSONRPCResponse {
			resp := make(chan JSONRPCResponse, 1)

			logger.Infof("Requesting update progress id:%s", request.Id)

			resp <- JSONRPCResponse{"2.0", request.Id, lastUpdateProgress, nil}

			logger.Infof("Sent progress %v", lastUpdateProgress)

			running, ok := lastUpdateProgress["running"]

			// If we have sent back a progress that shows the update is finished... shut down the ble after 5 seconds.
			if ok && running.(bool) == false { /*running:*/
				logger.Infof("Update is finished!")
				go func() {
					time.Sleep(time.Second * 5)
					srv.Close()
				}()
			}

			return resp
		})

		rpc_router.AddHandler("sphere.setup.display_drawing", func(request JSONRPCRequest) chan JSONRPCResponse {
			resp := make(chan JSONRPCResponse, 1)

			var response json.RawMessage

			err := ledService.Call("displayDrawing", request.Params, &response, time.Second*10)

			if err == nil {
				resp <- JSONRPCResponse{"2.0", request.Id, &response, nil}
			} else {
				resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, fmt.Sprintf("%s", err), nil}}
			}

			return resp
		})

		rpc_router.AddHandler("sphere.setup.draw", func(request JSONRPCRequest) chan JSONRPCResponse {
			resp := make(chan JSONRPCResponse, 1)

			var response json.RawMessage

			err := ledService.Call("draw", request.Params, &response, time.Second*10)

			if err == nil {
				resp <- JSONRPCResponse{"2.0", request.Id, &response, nil}
			} else {
				resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, fmt.Sprintf("%s", err), nil}}
			}

			return resp
		})
	}

	return rpc_router
}
<commit_msg>fix: string -> []byte<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"os/exec"
	"time"

	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-wireless/iwlib"
	"github.com/paypal/gatt"
)

type WifiNetwork struct {
	SSID string `json:"name"`
}

type WifiCredentials struct {
	SSID string `json:"ssid"`
	Key  string `json:"key"`
}

const WLANInterfaceTemplate = "iface wlan0 inet dhcp\n"

// This is ugly... but for some reason go-ninja was only delivering the progress to one of the
// listeners, so rpc and http need to share.
var lastUpdateProgress map[string]interface{}

func GetSetupRPCRouter(conn *ninja.Connection, wifi_manager *WifiManager, srv *gatt.Server, pairing_ui ConsolePairingUI) *JSONRPCRouter {

	rpc_router := &JSONRPCRouter{}
	rpc_router.Init()
	rpc_router.AddHandler("sphere.setup.ping", func(request JSONRPCRequest) chan JSONRPCResponse {
		resp := make(chan JSONRPCResponse, 1)

		pong := JSONRPCResponse{"2.0", request.Id, 1234, nil}
		resp <- pong

		return resp
	})

	rpc_router.AddHandler("sphere.setup.get_visible_wifi_networks", func(request JSONRPCRequest) chan JSONRPCResponse {
		resp := make(chan JSONRPCResponse, 1)

		pairing_ui.DisplayIcon("wifi-searching.gif")

		// Before we search for wifi networks, disable any that are try-fail-ing
		wifi_manager.DisableAllNetworks()

		networks, err := iwlib.GetWirelessNetworks("wlan0")
		if err == nil {
			wifi_networks := make([]WifiNetwork, len(networks))
			for i, network := range networks {
				wifi_networks[i].SSID = network.SSID
			}

			resp <- JSONRPCResponse{"2.0", request.Id, wifi_networks, nil}
		} else {
			resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, "Could not retrieve WiFi networks", nil}}
		}

		return resp
	})

	rpc_router.AddHandler("sphere.setup.connect_wifi_network", func(request JSONRPCRequest) chan JSONRPCResponse {
		resp := make(chan JSONRPCResponse, 1)

		pairing_ui.DisplayIcon("wifi-connecting.gif")

		wifi_creds := new(WifiCredentials)
		b, _ := json.Marshal(request.Params[0])
		json.Unmarshal(b, wifi_creds)

		logger.Debugf("Got wifi credentials %v", wifi_creds)

		go func() {
			success := wifi_manager.SetCredentials(wifi_creds)
			if success {
				var err error
				var path string
				var serial_number []byte

				pairing_ui.DisplayIcon("wifi-connected.gif")

				path, err = exec.LookPath("sphere-serial")
				if err == nil {
					serial_number, err = exec.Command("sphere-serial").Output()
				}
				if err == nil {
					pong := JSONRPCResponse{"2.0", request.Id, string(serial_number), nil}
					resp <- pong
				} else {
					logger.Errorf("failed to obtain serial number: %v", err)
					resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, "Failed to obtain serial number", nil}}
				}

			} else {
				pairing_ui.DisplayIcon("wifi-failed.gif")
				resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, "Could not connect to specified WiFi network, is the key correct?", nil}}
			}
		}()

		return resp
	})

	rpc_router.AddHandler("sphere.setup.acknowledge_wifi_connected", func(request JSONRPCRequest) chan JSONRPCResponse {
		resp := make(chan JSONRPCResponse, 1)
		go func() {
			wifi_manager.ConnectionAcknowledged()
			logger.Infof("Received acknowledgement of wifi connected from app.")
			pairing_ui.DisplayIcon("wifi-connected.gif")
			resp <- JSONRPCResponse{"2.0", request.Id, nil, nil}
		}()

		return resp
	})

	if !factoryReset {

		updateService := conn.GetServiceClient("$node/" + config.Serial() + "/updates")
		ledService := conn.GetServiceClient("$node/" + config.Serial() + "/led-controller")

		rpc_router.AddHandler("sphere.setup.start_update", func(request JSONRPCRequest) chan JSONRPCResponse {
			resp := make(chan JSONRPCResponse, 1)

			var response bool

			logger.Infof("Starting update id: %d. Waiting for response....", request.Id)

			err := updateService.Call("start", nil, &response, time.Second*10)

			if response {
				lastUpdateProgress = nil
			}

			logger.Infof("Got update start response: %d. %v", request.Id, response)

			if err == nil {
				resp <- JSONRPCResponse{"2.0", request.Id, &response, nil}
			} else {
				resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, fmt.Sprintf("%s", err), nil}}
			}

			return resp
		})

		rpc_router.AddHandler("sphere.setup.get_update_progress", func(request JSONRPCRequest) chan JSONRPCResponse {
			resp := make(chan JSONRPCResponse, 1)

			logger.Infof("Requesting update progress id:%s", request.Id)

			resp <- JSONRPCResponse{"2.0", request.Id, lastUpdateProgress, nil}

			logger.Infof("Sent progress %v", lastUpdateProgress)

			running, ok := lastUpdateProgress["running"]

			// If we have sent back a progress that shows the update is finished... shut down the ble after 5 seconds.
			if ok && running.(bool) == false { /*running:*/
				logger.Infof("Update is finished!")
				go func() {
					time.Sleep(time.Second * 5)
					srv.Close()
				}()
			}

			return resp
		})

		rpc_router.AddHandler("sphere.setup.display_drawing", func(request JSONRPCRequest) chan JSONRPCResponse {
			resp := make(chan JSONRPCResponse, 1)

			var response json.RawMessage

			err := ledService.Call("displayDrawing", request.Params, &response, time.Second*10)

			if err == nil {
				resp <- JSONRPCResponse{"2.0", request.Id, &response, nil}
			} else {
				resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, fmt.Sprintf("%s", err), nil}}
			}

			return resp
		})

		rpc_router.AddHandler("sphere.setup.draw", func(request JSONRPCRequest) chan JSONRPCResponse {
			resp := make(chan JSONRPCResponse, 1)

			var response json.RawMessage

			err := ledService.Call("draw", request.Params, &response, time.Second*10)

			if err == nil {
				resp <- JSONRPCResponse{"2.0", request.Id, &response, nil}
			} else {
				resp <- JSONRPCResponse{"2.0", request.Id, nil, &JSONRPCError{500, fmt.Sprintf("%s", err), nil}}
			}

			return resp
		})
	}

	return rpc_router
}
<|endoftext|>


<commit_before>package micro

import (
	"context"

	"github.com/micro/go-micro/client"
)

type publisher struct {
	c     client.Client
	topic string
}

func (p *publisher) Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error {
	return p.c.Publish(ctx, p.c.NewMessage(p.topic, msg))
}
<commit_msg>accept publisher options<commit_after>package micro

import (
	"context"

	"github.com/micro/go-micro/client"
)

type publisher struct {
	c     client.Client
	topic string
}

func (p *publisher) Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error {
	return p.c.Publish(ctx, p.c.NewMessage(p.topic, msg), opts...)
}
<|endoftext|>


<commit_before><commit_msg>Add test for json, yaml and toml<commit_after><|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"strings"
	"sync"

	"github.com/google/go-github/github"

	"github.com/wsxiaoys/terminal"
	"github.com/wsxiaoys/terminal/color"
)

var (
	mut sync.Mutex
)

func NewPullRequestor(config *Config, client *github.Client) *PullRequestor {
	return &PullRequestor{config, client}
}

type PullRequestor struct {
	config *Config
	client *github.Client
}

func (self *PullRequestor) printPullRequest(organization string, project string, pr github.PullRequest, wg *sync.WaitGroup) {
	if self.config.Debug {
		log.Println("Listing comments")
	}
	comments, _, err := self.client.PullRequests.ListComments(organization, project, *pr.Number, nil)

	if err != nil {
		log.Fatal(err)
	}

	// Keep the mutex down here so we can make the list requests in parallel, but
	// not clobber stdout while printing stuff. A better approach would be to
	// throw this in a channel.
	mut.Lock()

	color.Printf("@w[%d] %s\n", *pr.Number, *pr.Title)

	color.Printf("@{/}(Created on %s :: %d comments)\n", *pr.CreatedAt, len(comments))

	if pr.Body == nil || len(*pr.Body) == 0 {
		color.Println("@r<no body>")
	} else {
		bodyLen := len(*pr.Body)
		if bodyLen > 120 {
			bodyLen = 120
		}

		color.Printf("@b%s\n", (*pr.Body)[:bodyLen])
	}

	color.Printf("@g%s\n", (*pr.HTMLURL))
	color.Println()
	mut.Unlock()

	// Signals we're done printing this one element.
	wg.Done()
}

func (self *PullRequestor) PrintPullRequests(repo Repository) {
	for _, project := range repo.Projects {
		if self.config.Debug {
			log.Println("Getting pull requests for", project)
		}
		prs, _, err := self.client.PullRequests.List(repo.Organization, project, nil)

		if err != nil {
			log.Fatal(err)
		}

		if len(prs) == 0 {
			continue
		}

		terminal.Stdout.Color("y")

		// Get color codes here: https://github.com/wsxiaoys/terminal/blob/master/color/color.go
		title := fmt.Sprintf(" [ %s ] ", strings.ToUpper(project))
		paddingWidth := (80 - len(title)) / 2

		color.Println(strings.Repeat("=", 80))
		color.Printf("@{!m}%s%s%s", strings.Repeat("-", paddingWidth), title, strings.Repeat("-", paddingWidth))
		color.Println()

		var wg sync.WaitGroup

		for _, pr := range prs {
			wg.Add(1)

			go self.printPullRequest(repo.Organization, project, pr, &wg)
		}

		// Wait for the threads to finish their stuff.
		wg.Wait()
	}
}

func (self *PullRequestor) ListRepos() {
	// list all repositories for the authenticated user
	for _, repo := range self.config.Repositories {
		self.PrintPullRequests(repo)
	}
}
<commit_msg>Try to use go functions wherever possible<commit_after>package main

import (
	"bytes"
	"fmt"
	"log"
	"strings"

	"github.com/google/go-github/github"

	"github.com/wsxiaoys/terminal"
	"github.com/wsxiaoys/terminal/color"
)

func NewPullRequestor(config *Config, client *github.Client) *PullRequestor {
	return &PullRequestor{config, client}
}

type PullRequestor struct {
	config *Config
	client *github.Client
}

func (self *PullRequestor) writePullRequest(organization string, project string, pr github.PullRequest, out chan<- string) {
	if self.config.Debug {
		log.Println("Listing comments")
	}
	comments, _, err := self.client.PullRequests.ListComments(organization, project, *pr.Number, nil)

	if err != nil {
		log.Fatal(err)
	}

	var buffer bytes.Buffer

	buffer.WriteString(color.Sprintf("@w[%d] %s\n", *pr.Number, *pr.Title))

	buffer.WriteString(color.Sprintf("@{/}(Created on %s :: %d comments)\n", *pr.CreatedAt, len(comments)))

	if pr.Body == nil || len(*pr.Body) == 0 {
		buffer.WriteString(color.Sprint("@r<no body>\n"))
	} else {
		bodyLen := len(*pr.Body)
		if bodyLen > 120 {
			bodyLen = 120
		}

		buffer.WriteString(color.Sprintf("@b%s\n", (*pr.Body)[:bodyLen]))
	}

	buffer.WriteString(color.Sprintf("@g%s\n\n", (*pr.HTMLURL)))

	if self.config.Debug {
		log.Println("Done printing pull requests")
	}

	out <- buffer.String()
}

func (self *PullRequestor) PrintPullRequests(repo Repository, done chan<- bool) {
	for _, project := range repo.Projects {
		var buffer bytes.Buffer

		if self.config.Debug {
			log.Println("Getting pull requests for", project)
		}
		prs, _, err := self.client.PullRequests.List(repo.Organization, project, nil)

		if err != nil {
			log.Fatal(err)
		}

		if len(prs) == 0 {
			continue
		}

		// use a channel to collect all of the writes to a buffer then write it all at once
		printer := make(chan string)
		defer close(printer)
		for _, pr := range prs {
			go self.writePullRequest(repo.Organization, project, pr, printer)
		}

		// write out the printer to the terminal after everything has been collected
		terminal.Stdout.Color("y")

		// Get color codes here: https://github.com/wsxiaoys/terminal/blob/master/color/color.go
		title := fmt.Sprintf(" [ %s ] ", strings.ToUpper(project))
		paddingWidth := (80 - len(title)) / 2

		buffer.WriteString(color.Sprintf("%s\n", strings.Repeat("=", 80)))
		buffer.WriteString(color.Sprintf("@{!m}%s%s%s\n", strings.Repeat("-", paddingWidth), title, strings.Repeat("-", paddingWidth)))

		for i := 0; i < len(prs); i++ {
			buffer.WriteString(<-printer)
		}
		color.Print(buffer.String())
	}
	done <- true
}

func (self *PullRequestor) ListRepos() {
	// list all repositories for the authenticated user
	done := make(chan bool)
	defer close(done)
	for _, repo := range self.config.Repositories {
		go self.PrintPullRequests(repo, done)
	}

	for i := 0; i < len(self.config.Repositories); i++ {
		<-done
	}
}
<|endoftext|>