<commit_before>// Copyright 2017 Istio 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 controller

import (
	"errors"
	"fmt"
	"reflect"
	"sync"
	"sync/atomic"
	"time"

	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/tools/cache"

	"istio.io/pkg/log"

	"istio.io/istio/pilot/pkg/config/kube/crd"
	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/serviceregistry/kube"
	controller2 "istio.io/istio/pilot/pkg/serviceregistry/kube/controller"
	"istio.io/istio/pkg/config/schema"
	"istio.io/pkg/monitoring"
)

// controller is a collection of synchronized resource watchers.
// Caches are thread-safe
type controller struct {
	client *Client
	queue  kube.Queue
	kinds  map[string]cacheHandler
}

type cacheHandler struct {
	informer cache.SharedIndexInformer
	handler  *kube.ChainHandler
}

type ValidateFunc func(interface{}) error

var (
	typeTag  = monitoring.MustCreateLabel("type")
	eventTag = monitoring.MustCreateLabel("event")
	nameTag  = monitoring.MustCreateLabel("name")

	// experiment on getting some monitoring on config errors.
	k8sEvents = monitoring.NewSum(
		"pilot_k8s_cfg_events",
		"Events from k8s config.",
		monitoring.WithLabels(typeTag, eventTag),
	)

	k8sErrors = monitoring.NewGauge(
		"pilot_k8s_object_errors",
		"Errors converting k8s CRDs",
		monitoring.WithLabels(nameTag),
	)

	// InvalidCRDs contains a sync.Map keyed by the namespace/name of the entry, and has the error as value.
	// It can be used by tools like ctrlz to display the errors.
	InvalidCRDs atomic.Value
)

func init() {
	monitoring.MustRegister(k8sEvents, k8sErrors)
}

// NewController creates a new Kubernetes controller for CRDs
// Use "" for namespace to listen for all namespace changes
func NewController(client *Client, options controller2.Options) model.ConfigStoreCache {
	log.Infof("CRD controller watching namespaces %q", options.WatchedNamespace)

	// Queue requires a time duration for a retry delay after a handler error
	out := &controller{
		client: client,
		queue:  kube.NewQueue(1 * time.Second),
		kinds:  make(map[string]cacheHandler),
	}

	// add stores for CRD kinds
	for _, s := range client.ConfigDescriptor() {
		out.addInformer(s, options.WatchedNamespace, options.ResyncPeriod)
	}

	return out
}

func (c *controller) addInformer(schema schema.Instance, namespace string, resyncPeriod time.Duration) {
	c.kinds[schema.Type] = c.createInformer(crd.KnownTypes[schema.Type].Object.DeepCopyObject(), schema.Type, resyncPeriod,
		func(opts meta_v1.ListOptions) (result runtime.Object, err error) {
			result = crd.KnownTypes[schema.Type].Collection.DeepCopyObject()
			rc, ok := c.client.clientset[crd.APIVersion(&schema)]
			if !ok {
				return nil, fmt.Errorf("client not initialized %s", schema.Type)
			}
			req := rc.dynamic.Get().
				Resource(crd.ResourceName(schema.Plural)).
				VersionedParams(&opts, meta_v1.ParameterCodec)

			if !schema.ClusterScoped {
				req = req.Namespace(namespace)
			}
			err = req.Do().Into(result)
			return
		},
		func(opts meta_v1.ListOptions) (watch.Interface, error) {
			rc, ok := c.client.clientset[crd.APIVersion(&schema)]
			if !ok {
				return nil, fmt.Errorf("client not initialized %s", schema.Type)
			}
			opts.Watch = true
			req := rc.dynamic.Get().
				Resource(crd.ResourceName(schema.Plural)).
				VersionedParams(&opts, meta_v1.ParameterCodec)
			if !schema.ClusterScoped {
				req = req.Namespace(namespace)
			}
			return req.Watch()
		},
		func(obj interface{}) error {
			rc, ok := c.client.clientset[crd.APIVersion(&schema)]
			if !ok {
				return fmt.Errorf("client not initialized %s", schema.Type)
			}
			s, exists := rc.descriptor.GetByType(schema.Type)
			if !exists {
				return fmt.Errorf("unrecognized type %q", schema.Type)
			}

			item, ok := obj.(crd.IstioObject)
			if !ok {
				return fmt.Errorf("error convert %v to istio CRD", obj)
			}

			config, err := crd.ConvertObject(s, item, c.client.domainSuffix)
			if err != nil {
				return fmt.Errorf("error translating object for schema %#v : %v\n Object:\n%#v", s, err, obj)
			}

			if err := s.Validate(config.Name, config.Namespace, config.Spec); err != nil {
				return fmt.Errorf("failed to validate CRD %v, error: %v", config, err)
			}

			return nil
		})
}

// notify is the first handler in the handler chain.
// Returning an error causes repeated execution of the entire chain.
func (c *controller) notify(obj interface{}, event model.Event) error {
	if !c.HasSynced() {
		return errors.New("waiting till full synchronization")
	}
	_, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err != nil {
		log.Infof("Error retrieving key: %v", err)
	}
	return nil
}

func (c *controller) createInformer(
	o runtime.Object,
	otype string,
	resyncPeriod time.Duration,
	lf cache.ListFunc,
	wf cache.WatchFunc,
	vf ValidateFunc) cacheHandler {
	handler := &kube.ChainHandler{}
	handler.Append(c.notify)

	// TODO: finer-grained index (perf)
	informer := cache.NewSharedIndexInformer(
		&cache.ListWatch{ListFunc: lf, WatchFunc: wf}, o,
		resyncPeriod, cache.Indexers{})

	informer.AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			// TODO: filtering functions to skip over un-referenced resources (perf)
			AddFunc: func(obj interface{}) {
				if err := vf(obj); err != nil {
					log.Errorf("failed to add CRD. New value: %v, error: %v", obj, err)
					return
				}
				incrementEvent(otype, "add")
				c.queue.Push(kube.NewTask(handler.Apply, obj, model.EventAdd))
			},
			UpdateFunc: func(old, cur interface{}) {
				if err := vf(cur); err != nil {
					log.Errorf("failed to update CRD. New value: %v, error: %v", cur, err)
					return
				}
				if !reflect.DeepEqual(old, cur) {
					incrementEvent(otype, "update")
					c.queue.Push(kube.NewTask(handler.Apply, cur, model.EventUpdate))
				} else {
					incrementEvent(otype, "updatesame")
				}
			},
			DeleteFunc: func(obj interface{}) {
				incrementEvent(otype, "delete")
				c.queue.Push(kube.NewTask(handler.Apply, obj, model.EventDelete))
			},
		})

	return cacheHandler{informer: informer, handler: handler}
}

func incrementEvent(kind, event string) {
	k8sEvents.With(typeTag.Value(kind), eventTag.Value(event)).Increment()
}

func (c *controller) RegisterEventHandler(typ string, f func(model.Config, model.Event)) {
	s, exists := c.ConfigDescriptor().GetByType(typ)
	if !exists {
		return
	}
	c.kinds[typ].handler.Append(func(object interface{}, ev model.Event) error {
		item, ok := object.(crd.IstioObject)
		if ok {
			config, err := crd.ConvertObject(s, item, c.client.domainSuffix)
			if err != nil {
				log.Warnf("error translating object for schema %#v : %v\n Object:\n%#v", s, err, object)
			} else {
				f(*config, ev)
			}
		}
		return nil
	})
}

func (c *controller) Version() string {
	return c.client.Version()
}

func (c *controller) GetResourceAtVersion(version string, key string) (resourceVersion string, err error) {
	return c.client.GetResourceAtVersion(version, key)
}

func (c *controller) HasSynced() bool {
	for kind, ctl := range c.kinds {
		if !ctl.informer.HasSynced() {
			log.Infof("controller %q is syncing...", kind)
			return false
		}
	}
	return true
}

func (c *controller) Run(stop <-chan struct{}) {
	go func() {
		cache.WaitForCacheSync(stop, c.HasSynced)
		c.queue.Run(stop)
	}()

	for _, ctl := range c.kinds {
		go ctl.informer.Run(stop)
	}

	<-stop
	log.Info("controller terminated")
}

func (c *controller) ConfigDescriptor() schema.Set {
	return c.client.ConfigDescriptor()
}

func (c *controller) Get(typ, name, namespace string) *model.Config {
	s, exists := c.client.ConfigDescriptor().GetByType(typ)
	if !exists {
		return nil
	}

	store := c.kinds[typ].informer.GetStore()
	data, exists, err := store.GetByKey(kube.KeyFunc(name, namespace))
	if !exists {
		return nil
	}
	if err != nil {
		log.Warna(err)
		return nil
	}

	obj, ok := data.(crd.IstioObject)
	if !ok {
		log.Warn("Cannot convert to config from store")
		return nil
	}

	config, err := crd.ConvertObject(s, obj, c.client.domainSuffix)
	if err != nil {
		return nil
	}

	return config
}

func (c *controller) Create(config model.Config) (string, error) {
	return c.client.Create(config)
}

func (c *controller) Update(config model.Config) (string, error) {
	return c.client.Update(config)
}

func (c *controller) Delete(typ, name, namespace string) error {
	return c.client.Delete(typ, name, namespace)
}

func (c *controller) List(typ, namespace string) ([]model.Config, error) {
	s, ok := c.client.ConfigDescriptor().GetByType(typ)
	if !ok {
		return nil, fmt.Errorf("missing type %q", typ)
	}

	var newErrors sync.Map
	var errs error
	out := make([]model.Config, 0)
	oldMap := InvalidCRDs.Load()
	if oldMap != nil {
		oldMap.(*sync.Map).Range(func(key, value interface{}) bool {
			k8sErrors.With(nameTag.Value(key.(string))).Record(1)
			return true
		})
	}
	for _, data := range c.kinds[typ].informer.GetStore().List() {
		item, ok := data.(crd.IstioObject)
		if !ok {
			continue
		}

		if namespace != "" && namespace != item.GetObjectMeta().Namespace {
			continue
		}

		config, err := crd.ConvertObject(s, item, c.client.domainSuffix)
		if err != nil {
			key := item.GetObjectMeta().Namespace + "/" + item.GetObjectMeta().Name
			log.Errorf("Failed to convert %s object, ignoring: %s %v %v", typ, key, err, item.GetSpec())
			// DO NOT RETURN ERROR: if a single object is bad, it'll be ignored (with a log message), but
			// the rest should still be processed.
			// TODO: find a way to reset and represent the error !!
			newErrors.Store(key, err)
			k8sErrors.With(nameTag.Value(key)).Record(1)
		} else {
			out = append(out, *config)
		}
	}
	InvalidCRDs.Store(&newErrors)
	return out, errs
}
<commit_msg>add failure stas for crd validations (#18270)<commit_after>// Copyright 2017 Istio 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 controller

import (
	"errors"
	"fmt"
	"reflect"
	"sync"
	"sync/atomic"
	"time"

	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/tools/cache"

	"istio.io/pkg/log"

	"istio.io/istio/pilot/pkg/config/kube/crd"
	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/serviceregistry/kube"
	controller2 "istio.io/istio/pilot/pkg/serviceregistry/kube/controller"
	"istio.io/istio/pkg/config/schema"
	"istio.io/pkg/monitoring"
)

// controller is a collection of synchronized resource watchers.
// Caches are thread-safe
type controller struct {
	client *Client
	queue  kube.Queue
	kinds  map[string]cacheHandler
}

type cacheHandler struct {
	informer cache.SharedIndexInformer
	handler  *kube.ChainHandler
}

type ValidateFunc func(interface{}) error

var (
	typeTag  = monitoring.MustCreateLabel("type")
	eventTag = monitoring.MustCreateLabel("event")
	nameTag  = monitoring.MustCreateLabel("name")

	// experiment on getting some monitoring on config errors.
	k8sEvents = monitoring.NewSum(
		"pilot_k8s_cfg_events",
		"Events from k8s config.",
		monitoring.WithLabels(typeTag, eventTag),
	)

	k8sErrors = monitoring.NewGauge(
		"pilot_k8s_object_errors",
		"Errors converting k8s CRDs",
		monitoring.WithLabels(nameTag),
	)

	// InvalidCRDs contains a sync.Map keyed by the namespace/name of the entry, and has the error as value.
	// It can be used by tools like ctrlz to display the errors.
	InvalidCRDs atomic.Value
)

func init() {
	monitoring.MustRegister(k8sEvents, k8sErrors)
}

// NewController creates a new Kubernetes controller for CRDs
// Use "" for namespace to listen for all namespace changes
func NewController(client *Client, options controller2.Options) model.ConfigStoreCache {
	log.Infof("CRD controller watching namespaces %q", options.WatchedNamespace)

	// Queue requires a time duration for a retry delay after a handler error
	out := &controller{
		client: client,
		queue:  kube.NewQueue(1 * time.Second),
		kinds:  make(map[string]cacheHandler),
	}

	// add stores for CRD kinds
	for _, s := range client.ConfigDescriptor() {
		out.addInformer(s, options.WatchedNamespace, options.ResyncPeriod)
	}

	return out
}

func (c *controller) addInformer(schema schema.Instance, namespace string, resyncPeriod time.Duration) {
	c.kinds[schema.Type] = c.createInformer(crd.KnownTypes[schema.Type].Object.DeepCopyObject(), schema.Type, resyncPeriod,
		func(opts meta_v1.ListOptions) (result runtime.Object, err error) {
			result = crd.KnownTypes[schema.Type].Collection.DeepCopyObject()
			rc, ok := c.client.clientset[crd.APIVersion(&schema)]
			if !ok {
				return nil, fmt.Errorf("client not initialized %s", schema.Type)
			}
			req := rc.dynamic.Get().
				Resource(crd.ResourceName(schema.Plural)).
				VersionedParams(&opts, meta_v1.ParameterCodec)

			if !schema.ClusterScoped {
				req = req.Namespace(namespace)
			}
			err = req.Do().Into(result)
			return
		},
		func(opts meta_v1.ListOptions) (watch.Interface, error) {
			rc, ok := c.client.clientset[crd.APIVersion(&schema)]
			if !ok {
				return nil, fmt.Errorf("client not initialized %s", schema.Type)
			}
			opts.Watch = true
			req := rc.dynamic.Get().
				Resource(crd.ResourceName(schema.Plural)).
				VersionedParams(&opts, meta_v1.ParameterCodec)
			if !schema.ClusterScoped {
				req = req.Namespace(namespace)
			}
			return req.Watch()
		},
		func(obj interface{}) error {
			rc, ok := c.client.clientset[crd.APIVersion(&schema)]
			if !ok {
				return fmt.Errorf("client not initialized %s", schema.Type)
			}
			s, exists := rc.descriptor.GetByType(schema.Type)
			if !exists {
				return fmt.Errorf("unrecognized type %q", schema.Type)
			}

			item, ok := obj.(crd.IstioObject)
			if !ok {
				return fmt.Errorf("error convert %v to istio CRD", obj)
			}

			config, err := crd.ConvertObject(s, item, c.client.domainSuffix)
			if err != nil {
				return fmt.Errorf("error translating object for schema %#v : %v\n Object:\n%#v", s, err, obj)
			}

			if err := s.Validate(config.Name, config.Namespace, config.Spec); err != nil {
				return fmt.Errorf("failed to validate CRD %v, error: %v", config, err)
			}

			return nil
		})
}

// notify is the first handler in the handler chain.
// Returning an error causes repeated execution of the entire chain.
func (c *controller) notify(obj interface{}, event model.Event) error {
	if !c.HasSynced() {
		return errors.New("waiting till full synchronization")
	}
	_, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err != nil {
		log.Infof("Error retrieving key: %v", err)
	}
	return nil
}

func (c *controller) createInformer(
	o runtime.Object,
	otype string,
	resyncPeriod time.Duration,
	lf cache.ListFunc,
	wf cache.WatchFunc,
	vf ValidateFunc) cacheHandler {
	handler := &kube.ChainHandler{}
	handler.Append(c.notify)

	// TODO: finer-grained index (perf)
	informer := cache.NewSharedIndexInformer(
		&cache.ListWatch{ListFunc: lf, WatchFunc: wf}, o,
		resyncPeriod, cache.Indexers{})

	informer.AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			// TODO: filtering functions to skip over un-referenced resources (perf)
			AddFunc: func(obj interface{}) {
				if err := vf(obj); err != nil {
					log.Errorf("failed to add CRD. New value: %v, error: %v", obj, err)
					incrementEvent(otype, "addfailure")
					return
				}
				incrementEvent(otype, "add")
				c.queue.Push(kube.NewTask(handler.Apply, obj, model.EventAdd))
			},
			UpdateFunc: func(old, cur interface{}) {
				if err := vf(cur); err != nil {
					incrementEvent(otype, "updatefailure")
					log.Errorf("failed to update CRD. New value: %v, error: %v", cur, err)
					return
				}
				if !reflect.DeepEqual(old, cur) {
					incrementEvent(otype, "update")
					c.queue.Push(kube.NewTask(handler.Apply, cur, model.EventUpdate))
				} else {
					incrementEvent(otype, "updatesame")
				}
			},
			DeleteFunc: func(obj interface{}) {
				incrementEvent(otype, "delete")
				c.queue.Push(kube.NewTask(handler.Apply, obj, model.EventDelete))
			},
		})

	return cacheHandler{informer: informer, handler: handler}
}

func incrementEvent(kind, event string) {
	k8sEvents.With(typeTag.Value(kind), eventTag.Value(event)).Increment()
}

func (c *controller) RegisterEventHandler(typ string, f func(model.Config, model.Event)) {
	s, exists := c.ConfigDescriptor().GetByType(typ)
	if !exists {
		return
	}
	c.kinds[typ].handler.Append(func(object interface{}, ev model.Event) error {
		item, ok := object.(crd.IstioObject)
		if ok {
			config, err := crd.ConvertObject(s, item, c.client.domainSuffix)
			if err != nil {
				log.Warnf("error translating object for schema %#v : %v\n Object:\n%#v", s, err, object)
			} else {
				f(*config, ev)
			}
		}
		return nil
	})
}

func (c *controller) Version() string {
	return c.client.Version()
}

func (c *controller) GetResourceAtVersion(version string, key string) (resourceVersion string, err error) {
	return c.client.GetResourceAtVersion(version, key)
}

func (c *controller) HasSynced() bool {
	for kind, ctl := range c.kinds {
		if !ctl.informer.HasSynced() {
			log.Infof("controller %q is syncing...", kind)
			return false
		}
	}
	return true
}

func (c *controller) Run(stop <-chan struct{}) {
	go func() {
		cache.WaitForCacheSync(stop, c.HasSynced)
		c.queue.Run(stop)
	}()

	for _, ctl := range c.kinds {
		go ctl.informer.Run(stop)
	}

	<-stop
	log.Info("controller terminated")
}

func (c *controller) ConfigDescriptor() schema.Set {
	return c.client.ConfigDescriptor()
}

func (c *controller) Get(typ, name, namespace string) *model.Config {
	s, exists := c.client.ConfigDescriptor().GetByType(typ)
	if !exists {
		return nil
	}

	store := c.kinds[typ].informer.GetStore()
	data, exists, err := store.GetByKey(kube.KeyFunc(name, namespace))
	if !exists {
		return nil
	}
	if err != nil {
		log.Warna(err)
		return nil
	}

	obj, ok := data.(crd.IstioObject)
	if !ok {
		log.Warn("Cannot convert to config from store")
		return nil
	}

	config, err := crd.ConvertObject(s, obj, c.client.domainSuffix)
	if err != nil {
		return nil
	}

	return config
}

func (c *controller) Create(config model.Config) (string, error) {
	return c.client.Create(config)
}

func (c *controller) Update(config model.Config) (string, error) {
	return c.client.Update(config)
}

func (c *controller) Delete(typ, name, namespace string) error {
	return c.client.Delete(typ, name, namespace)
}

func (c *controller) List(typ, namespace string) ([]model.Config, error) {
	s, ok := c.client.ConfigDescriptor().GetByType(typ)
	if !ok {
		return nil, fmt.Errorf("missing type %q", typ)
	}

	var newErrors sync.Map
	var errs error
	out := make([]model.Config, 0)
	oldMap := InvalidCRDs.Load()
	if oldMap != nil {
		oldMap.(*sync.Map).Range(func(key, value interface{}) bool {
			k8sErrors.With(nameTag.Value(key.(string))).Record(1)
			return true
		})
	}
	for _, data := range c.kinds[typ].informer.GetStore().List() {
		item, ok := data.(crd.IstioObject)
		if !ok {
			continue
		}

		if namespace != "" && namespace != item.GetObjectMeta().Namespace {
			continue
		}

		config, err := crd.ConvertObject(s, item, c.client.domainSuffix)
		if err != nil {
			key := item.GetObjectMeta().Namespace + "/" + item.GetObjectMeta().Name
			log.Errorf("Failed to convert %s object, ignoring: %s %v %v", typ, key, err, item.GetSpec())
			// DO NOT RETURN ERROR: if a single object is bad, it'll be ignored (with a log message), but
			// the rest should still be processed.
			// TODO: find a way to reset and represent the error !!
			newErrors.Store(key, err)
			k8sErrors.With(nameTag.Value(key)).Record(1)
		} else {
			out = append(out, *config)
		}
	}
	InvalidCRDs.Store(&newErrors)
	return out, errs
}
<|endoftext|>


<commit_before>package flows

import (
	"encoding/json"

	"github.com/skygeario/skygear-server/pkg/auth/dependency/challenge"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/identity"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/identity/anonymous"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/interaction"
	"github.com/skygeario/skygear-server/pkg/auth/model"
	"github.com/skygeario/skygear-server/pkg/core/authn"
)

type AnonymousIdentityProvider interface {
	ParseRequest(requestJWT string) (*anonymous.Identity, *anonymous.Request, error)
}

type ChallengeProvider interface {
	Consume(token string) (*challenge.Purpose, error)
}

type AnonymousFlow struct {
	Interactions InteractionProvider
	Anonymous    AnonymousIdentityProvider
	Challenges   ChallengeProvider
}

func (f *AnonymousFlow) Authenticate(requestJWT string, clientID string) (*authn.Attrs, error) {
	iden, request, err := f.Anonymous.ParseRequest(requestJWT)
	if err != nil || request.Action != anonymous.RequestActionAuth {
		return nil, interaction.ErrInvalidCredentials
	}

	// Verify challenge token
	purpose, err := f.Challenges.Consume(request.Challenge)
	if err != nil || *purpose != challenge.PurposeAnonymousRequest {
		return nil, interaction.ErrInvalidCredentials
	}

	var keyID string
	if iden != nil {
		keyID = iden.KeyID
	} else {
		// Sign up if identity does not exist
		jwk, err := json.Marshal(request.Key)
		if err != nil {
			return nil, interaction.ErrInvalidCredentials
		}

		i, err := f.Interactions.NewInteractionSignup(&interaction.IntentSignup{
			Identity: identity.Spec{
				Type: authn.IdentityTypeAnonymous,
				Claims: map[string]interface{}{
					identity.IdentityClaimAnonymousKeyID: request.Key.KeyID(),
					identity.IdentityClaimAnonymousKey:   string(jwk),
				},
			},
			OnUserDuplicate: model.OnUserDuplicateAbort,
		}, clientID)
		if err != nil {
			return nil, err
		}
		s, err := f.Interactions.GetInteractionState(i)
		if err != nil {
			return nil, err
		}
		if s.CurrentStep().Step != interaction.StepCommit {
			panic("interaction_flow_anonymous: unexpected interaction state")
		}
		_, err = f.Interactions.Commit(i)
		if err != nil {
			return nil, err
		}

		keyID = request.Key.KeyID()
	}

	// Login after ensuring user & identity exists
	i, err := f.Interactions.NewInteractionLogin(&interaction.IntentLogin{
		Identity: identity.Spec{
			Type: authn.IdentityTypeAnonymous,
			Claims: map[string]interface{}{
				identity.IdentityClaimAnonymousKeyID: keyID,
			},
		},
	}, clientID)
	if err != nil {
		return nil, err
	}
	s, err := f.Interactions.GetInteractionState(i)
	if err != nil {
		return nil, err
	}
	if s.CurrentStep().Step != interaction.StepCommit {
		panic("interaction_flow_anonymous: unexpected interaction state")
	}
	attrs, err := f.Interactions.Commit(i)
	if err != nil {
		return nil, err
	}

	return attrs, nil
}

func (f *AnonymousFlow) DecodeUserID(requestJWT string) (string, anonymous.RequestAction, error) {
	identity, request, err := f.Anonymous.ParseRequest(requestJWT)
	if err != nil {
		return "", "", interaction.ErrInvalidCredentials
	}

	// Verify challenge token
	purpose, err := f.Challenges.Consume(request.Challenge)
	if err != nil || *purpose != challenge.PurposeAnonymousRequest {
		return "", "", interaction.ErrInvalidCredentials
	}

	return identity.UserID, request.Action, nil
}
<commit_msg>Guard against no identity found in promotion<commit_after>package flows

import (
	"encoding/json"

	"github.com/skygeario/skygear-server/pkg/auth/dependency/challenge"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/identity"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/identity/anonymous"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/interaction"
	"github.com/skygeario/skygear-server/pkg/auth/model"
	"github.com/skygeario/skygear-server/pkg/core/authn"
)

type AnonymousIdentityProvider interface {
	ParseRequest(requestJWT string) (*anonymous.Identity, *anonymous.Request, error)
}

type ChallengeProvider interface {
	Consume(token string) (*challenge.Purpose, error)
}

type AnonymousFlow struct {
	Interactions InteractionProvider
	Anonymous    AnonymousIdentityProvider
	Challenges   ChallengeProvider
}

func (f *AnonymousFlow) Authenticate(requestJWT string, clientID string) (*authn.Attrs, error) {
	iden, request, err := f.Anonymous.ParseRequest(requestJWT)
	if err != nil || request.Action != anonymous.RequestActionAuth {
		return nil, interaction.ErrInvalidCredentials
	}

	// Verify challenge token
	purpose, err := f.Challenges.Consume(request.Challenge)
	if err != nil || *purpose != challenge.PurposeAnonymousRequest {
		return nil, interaction.ErrInvalidCredentials
	}

	var keyID string
	if iden != nil {
		keyID = iden.KeyID
	} else {
		// Sign up if identity does not exist
		jwk, err := json.Marshal(request.Key)
		if err != nil {
			return nil, interaction.ErrInvalidCredentials
		}

		i, err := f.Interactions.NewInteractionSignup(&interaction.IntentSignup{
			Identity: identity.Spec{
				Type: authn.IdentityTypeAnonymous,
				Claims: map[string]interface{}{
					identity.IdentityClaimAnonymousKeyID: request.Key.KeyID(),
					identity.IdentityClaimAnonymousKey:   string(jwk),
				},
			},
			OnUserDuplicate: model.OnUserDuplicateAbort,
		}, clientID)
		if err != nil {
			return nil, err
		}
		s, err := f.Interactions.GetInteractionState(i)
		if err != nil {
			return nil, err
		}
		if s.CurrentStep().Step != interaction.StepCommit {
			panic("interaction_flow_anonymous: unexpected interaction state")
		}
		_, err = f.Interactions.Commit(i)
		if err != nil {
			return nil, err
		}

		keyID = request.Key.KeyID()
	}

	// Login after ensuring user & identity exists
	i, err := f.Interactions.NewInteractionLogin(&interaction.IntentLogin{
		Identity: identity.Spec{
			Type: authn.IdentityTypeAnonymous,
			Claims: map[string]interface{}{
				identity.IdentityClaimAnonymousKeyID: keyID,
			},
		},
	}, clientID)
	if err != nil {
		return nil, err
	}
	s, err := f.Interactions.GetInteractionState(i)
	if err != nil {
		return nil, err
	}
	if s.CurrentStep().Step != interaction.StepCommit {
		panic("interaction_flow_anonymous: unexpected interaction state")
	}
	attrs, err := f.Interactions.Commit(i)
	if err != nil {
		return nil, err
	}

	return attrs, nil
}

func (f *AnonymousFlow) DecodeUserID(requestJWT string) (string, anonymous.RequestAction, error) {
	identity, request, err := f.Anonymous.ParseRequest(requestJWT)
	if err != nil || identity == nil {
		return "", "", interaction.ErrInvalidCredentials
	}

	// Verify challenge token
	purpose, err := f.Challenges.Consume(request.Challenge)
	if err != nil || *purpose != challenge.PurposeAnonymousRequest {
		return "", "", interaction.ErrInvalidCredentials
	}

	return identity.UserID, request.Action, nil
}
<|endoftext|>


<commit_before>package password

import (
	"golang.org/x/crypto/bcrypt"

	"github.com/skygeario/skygear-server/pkg/server/uuid"
)

type Principal struct {
	ID             string
	UserID         string
	AuthData       interface{}
	PlainPassword  string
	HashedPassword []byte
}

func NewPrincipal() Principal {
	return Principal{
		ID: uuid.New(),
	}
}

func NewUniqueAuthData(authRecordKeys [][]string, authData map[string]interface{}) []map[string]interface{} {
	outputs := make([]map[string]interface{}, 0)

	for _, ks := range authRecordKeys {
		m := make(map[string]interface{})
		for _, k := range ks {
			for dk := range authData {
				if k == dk && authData[dk] != nil {
					m[k] = authData[dk]
				}
			}
		}
		if len(m) != 0 { // avoid empty map
			outputs = append(outputs, m)
		}
	}

	return outputs
}

func (p Principal) IsSamePassword(password string) bool {
	return bcrypt.CompareHashAndPassword(p.HashedPassword, []byte(password)) == nil
}
<commit_msg>Add some comments to the function NewUniqueAuthData<commit_after>package password

import (
	"golang.org/x/crypto/bcrypt"

	"github.com/skygeario/skygear-server/pkg/server/uuid"
)

type Principal struct {
	ID             string
	UserID         string
	AuthData       interface{}
	PlainPassword  string
	HashedPassword []byte
}

func NewPrincipal() Principal {
	return Principal{
		ID: uuid.New(),
	}
}

// NewUniqueAuthData converts authData by a list of authData depending on authRecordKeys
// example 1: authRecordKeys = username, email
// - if authData is { "username": "john.doe" }, output is [{ "username": "john.doe" }]
// - if authData is { "username": "john.doe", "email": "john.doe@example.com" }, output is [{ "username": "john.doe" }, { "email": "john.doe@example.com" }]
// example 2: authRecordKeys = (username, nickname), email
// - if authData is { "username": "john.doe", "email": "john.doe@example.com", "nickname": "john.doe" },
// output is [{ "username": "john.doe", "nickname": "john.doe" }, { "email": "john.doe@example.com" }]
func NewUniqueAuthData(authRecordKeys [][]string, authData map[string]interface{}) []map[string]interface{} {
	outputs := make([]map[string]interface{}, 0)

	for _, ks := range authRecordKeys {
		m := make(map[string]interface{})
		for _, k := range ks {
			for dk := range authData {
				if k == dk && authData[dk] != nil {
					m[k] = authData[dk]
				}
			}
		}
		if len(m) != 0 { // avoid empty map
			outputs = append(outputs, m)
		}
	}

	return outputs
}

func (p Principal) IsSamePassword(password string) bool {
	return bcrypt.CompareHashAndPassword(p.HashedPassword, []byte(password)) == nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google LLC

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 resolve

import (
	"fmt"
	"sort"
	"testing"

	"github.com/grafeas/kritis/pkg/kritis/testutil"
	"gopkg.in/yaml.v2"
)

var testYaml1 = `apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: docker
    image: golang:1.10
    args: ["--arg1=<first>",
           "--arg2=<second>",
           "--arg3=<third>"]
`

var testYaml2 = `apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: digest
    image: gcr.io/google-appengine/debian9@sha256:547f82a1a5a194b22d1178f4c6aae3de006152757c0da267fd3a68b03e8b6d85
    env:
    key: ENV
    value: ENV_VALUE
    moreImages:
        image: gcr.io/distroless/base:debug
  - name: no-tag
    image: gcr.io/distroless/base
  - name: docker
    image: busybox
`

func Test_recursiveGetTaggedImages(t *testing.T) {
	tests := []struct {
		name     string
		yaml     string
		expected []string
	}{
		{
			name: "test one tagged image",
			yaml: testYaml1,
			expected: []string{
				"golang:1.10",
			},
		},
		{
			name: "test multiple tagged images",
			yaml: testYaml2,
			expected: []string{
				"busybox",
				"gcr.io/distroless/base",
				"gcr.io/distroless/base:debug",
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			m := yaml.MapSlice{}
			if err := yaml.Unmarshal([]byte(test.yaml), &m); err != nil {
				t.Fatalf("couldn't unmarshal yaml: %v", err)
			}
			actual := recursiveGetTaggedImages(m)
			sort.Strings(actual)
			testutil.CheckErrorAndDeepEqual(t, false, nil, test.expected, actual)
		})
	}
}

type fakeResolver struct {
	tagMap map[string]string
}

func newFakeResolver() fakeResolver {
	f := fakeResolver{
		tagMap: map[string]string{},
	}
	return f
}

func (f *fakeResolver) resolve(image string) (string, error) {
	digest, ok := f.tagMap[image]
	if !ok {
		return "", fmt.Errorf("image %s not found", image)
	}
	return digest, nil
}

func setResolver(f func(string) (string, error)) func() {
	oldResolver := resolver
	resolver = f
	return func() {
		resolver = oldResolver
	}
}

func Test_resolveTagsToDigests(t *testing.T) {
	r := newFakeResolver()
	r.tagMap["gcr.io/google-appengine/debian9:2017-09-07-161610"] = "gcr.io/google-appengine/debian9@sha256:foo"
	r.tagMap["golang:1.10"] = "index.docker.io/library/golang@sha256:bar"

	defer setResolver(r.resolve)()

	tests := []struct {
		name     string
		images   []string
		expected map[string]string
	}{
		{
			name: "gcr image",
			images: []string{
				"gcr.io/google-appengine/debian9:2017-09-07-161610",
			},
			expected: map[string]string{
				"gcr.io/google-appengine/debian9:2017-09-07-161610": "gcr.io/google-appengine/debian9@sha256:foo",
			},
		},
		{
			name: "docker registry image",
			images: []string{
				"golang:1.10",
			},
			expected: map[string]string{
				"golang:1.10": "index.docker.io/library/golang@sha256:bar",
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actual, err := resolveTagsToDigests(test.images)
			testutil.CheckErrorAndDeepEqual(t, false, err, test.expected, actual)
		})
	}
}

func Test_recursiveReplaceImage(t *testing.T) {
	tests := []struct {
		name         string
		yaml         interface{}
		replacements map[string]string
		expected     interface{}
	}{
		{
			name: "replace one image",
			yaml: yaml.MapSlice{
				yaml.MapItem{
					Key:   "image",
					Value: "image:tag",
				},
			},
			replacements: map[string]string{
				"image:tag": "image:digest",
			},
			expected: yaml.MapSlice{
				yaml.MapItem{
					Key:   "image",
					Value: "image:digest",
				},
			},
		},
		{
			name: "yaml without image field",
			yaml: yaml.MapSlice{
				yaml.MapItem{
					Key:   "key",
					Value: "image:tag",
				},
			},
			replacements: map[string]string{
				"image:tag": "image:digest",
			},
			expected: yaml.MapSlice{
				yaml.MapItem{
					Key:   "key",
					Value: "image:tag",
				},
			},
		},
		{
			name: "replace some images",
			yaml: formatMapSlice([]string{"image:tag", "something", "image:tag2"}),
			replacements: map[string]string{
				"image:tag":  "image:digest",
				"image:tag2": "image:digest2",
			},
			expected: formatMapSlice([]string{"image:digest", "something", "image:digest2"}),
		},
		{
			name:         "replace no images",
			yaml:         formatTestYaml1(t),
			replacements: map[string]string{},
			expected:     formatTestYaml1(t),
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actual := recursiveReplaceImage(test.yaml, test.replacements)
			testutil.CheckErrorAndDeepEqual(t, false, nil, test.expected, actual)
		})
	}
}

func Test_MultiYaml(t *testing.T) {
	r := newFakeResolver()
	r.tagMap["image:tag"] = "image:digest"
	r.tagMap["image:tag2"] = "image:digest2"

	defer setResolver(r.resolve)()
	multiYaml := `apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: first
    image: %s
---
apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: second
    image: %s
`
	expected := fmt.Sprintf(multiYaml, "image:digest", "image:digest2")
	actual, err := executeSubstitution(fmt.Sprintf(multiYaml, "image:tag", "image:tag2"))
	testutil.CheckErrorAndDeepEqual(t, false, err, expected, actual)
}

func formatTestYaml1(t *testing.T) yaml.MapSlice {
	m := yaml.MapSlice{}
	err := yaml.Unmarshal([]byte(testYaml1), &m)
	if err != nil {
		t.Fatalf("unmarshal failed: %v", err)
	}
	return m
}

func formatMapSlice(args []string) yaml.MapSlice {
	testYaml := `apiVersion: v1
kind: Pod
metadata:
    name: test
    label: test
spec:
    containers:
    - name: tag
      image: %s
    env:
        key: ENV
        value: ENV_VALUE
    containers:
    - name: key1
        values:
        image: image:digest
    - name: key2
        value: value
    - name: key3
        value: 6
    moreImages:
    image: %s
    nest:
        value:  0
        value1: 1
        value2: 2
        nest:
        nest:
            nest:
            - name: digest
            image: %s
`
	y := fmt.Sprintf(testYaml, args[0], args[1], args[2])

	m := yaml.MapSlice{}
	yaml.Unmarshal([]byte(y), &m)
	return m
}
<commit_msg>Add t.Helper()<commit_after>/*
Copyright 2018 Google LLC

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 resolve

import (
	"fmt"
	"sort"
	"testing"

	"github.com/grafeas/kritis/pkg/kritis/testutil"
	"gopkg.in/yaml.v2"
)

var testYaml1 = `apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: docker
    image: golang:1.10
    args: ["--arg1=<first>",
           "--arg2=<second>",
           "--arg3=<third>"]
`

var testYaml2 = `apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: digest
    image: gcr.io/google-appengine/debian9@sha256:547f82a1a5a194b22d1178f4c6aae3de006152757c0da267fd3a68b03e8b6d85
    env:
    key: ENV
    value: ENV_VALUE
    moreImages:
        image: gcr.io/distroless/base:debug
  - name: no-tag
    image: gcr.io/distroless/base
  - name: docker
    image: busybox
`

func Test_recursiveGetTaggedImages(t *testing.T) {
	tests := []struct {
		name     string
		yaml     string
		expected []string
	}{
		{
			name: "test one tagged image",
			yaml: testYaml1,
			expected: []string{
				"golang:1.10",
			},
		},
		{
			name: "test multiple tagged images",
			yaml: testYaml2,
			expected: []string{
				"busybox",
				"gcr.io/distroless/base",
				"gcr.io/distroless/base:debug",
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			m := yaml.MapSlice{}
			if err := yaml.Unmarshal([]byte(test.yaml), &m); err != nil {
				t.Fatalf("couldn't unmarshal yaml: %v", err)
			}
			actual := recursiveGetTaggedImages(m)
			sort.Strings(actual)
			testutil.CheckErrorAndDeepEqual(t, false, nil, test.expected, actual)
		})
	}
}

type fakeResolver struct {
	tagMap map[string]string
}

func newFakeResolver() fakeResolver {
	f := fakeResolver{
		tagMap: map[string]string{},
	}
	return f
}

func (f *fakeResolver) resolve(image string) (string, error) {
	digest, ok := f.tagMap[image]
	if !ok {
		return "", fmt.Errorf("image %s not found", image)
	}
	return digest, nil
}

func setResolver(f func(string) (string, error)) func() {
	oldResolver := resolver
	resolver = f
	return func() {
		resolver = oldResolver
	}
}

func Test_resolveTagsToDigests(t *testing.T) {
	r := newFakeResolver()
	r.tagMap["gcr.io/google-appengine/debian9:2017-09-07-161610"] = "gcr.io/google-appengine/debian9@sha256:foo"
	r.tagMap["golang:1.10"] = "index.docker.io/library/golang@sha256:bar"

	defer setResolver(r.resolve)()

	tests := []struct {
		name     string
		images   []string
		expected map[string]string
	}{
		{
			name: "gcr image",
			images: []string{
				"gcr.io/google-appengine/debian9:2017-09-07-161610",
			},
			expected: map[string]string{
				"gcr.io/google-appengine/debian9:2017-09-07-161610": "gcr.io/google-appengine/debian9@sha256:foo",
			},
		},
		{
			name: "docker registry image",
			images: []string{
				"golang:1.10",
			},
			expected: map[string]string{
				"golang:1.10": "index.docker.io/library/golang@sha256:bar",
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actual, err := resolveTagsToDigests(test.images)
			testutil.CheckErrorAndDeepEqual(t, false, err, test.expected, actual)
		})
	}
}

func Test_recursiveReplaceImage(t *testing.T) {
	tests := []struct {
		name         string
		yaml         interface{}
		replacements map[string]string
		expected     interface{}
	}{
		{
			name: "replace one image",
			yaml: yaml.MapSlice{
				yaml.MapItem{
					Key:   "image",
					Value: "image:tag",
				},
			},
			replacements: map[string]string{
				"image:tag": "image:digest",
			},
			expected: yaml.MapSlice{
				yaml.MapItem{
					Key:   "image",
					Value: "image:digest",
				},
			},
		},
		{
			name: "yaml without image field",
			yaml: yaml.MapSlice{
				yaml.MapItem{
					Key:   "key",
					Value: "image:tag",
				},
			},
			replacements: map[string]string{
				"image:tag": "image:digest",
			},
			expected: yaml.MapSlice{
				yaml.MapItem{
					Key:   "key",
					Value: "image:tag",
				},
			},
		},
		{
			name: "replace some images",
			yaml: formatMapSlice([]string{"image:tag", "something", "image:tag2"}),
			replacements: map[string]string{
				"image:tag":  "image:digest",
				"image:tag2": "image:digest2",
			},
			expected: formatMapSlice([]string{"image:digest", "something", "image:digest2"}),
		},
		{
			name:         "replace no images",
			yaml:         formatTestYaml1(t),
			replacements: map[string]string{},
			expected:     formatTestYaml1(t),
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actual := recursiveReplaceImage(test.yaml, test.replacements)
			testutil.CheckErrorAndDeepEqual(t, false, nil, test.expected, actual)
		})
	}
}

func Test_MultiYaml(t *testing.T) {
	r := newFakeResolver()
	r.tagMap["image:tag"] = "image:digest"
	r.tagMap["image:tag2"] = "image:digest2"

	defer setResolver(r.resolve)()
	multiYaml := `apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: first
    image: %s
---
apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: second
    image: %s
`
	expected := fmt.Sprintf(multiYaml, "image:digest", "image:digest2")
	actual, err := executeSubstitution(fmt.Sprintf(multiYaml, "image:tag", "image:tag2"))
	testutil.CheckErrorAndDeepEqual(t, false, err, expected, actual)
}

func formatTestYaml1(t *testing.T) yaml.MapSlice {
	t.Helper()
	m := yaml.MapSlice{}
	err := yaml.Unmarshal([]byte(testYaml1), &m)
	if err != nil {
		t.Fatalf("unmarshal failed: %v", err)
	}
	return m
}

func formatMapSlice(args []string) yaml.MapSlice {
	testYaml := `apiVersion: v1
kind: Pod
metadata:
    name: test
    label: test
spec:
    containers:
    - name: tag
      image: %s
    env:
        key: ENV
        value: ENV_VALUE
    containers:
    - name: key1
        values:
        image: image:digest
    - name: key2
        value: value
    - name: key3
        value: 6
    moreImages:
    image: %s
    nest:
        value:  0
        value1: 1
        value2: 2
        nest:
        nest:
            nest:
            - name: digest
            image: %s
`
	y := fmt.Sprintf(testYaml, args[0], args[1], args[2])

	m := yaml.MapSlice{}
	yaml.Unmarshal([]byte(y), &m)
	return m
}
<|endoftext|>


<commit_before>// Copyright Istio 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 kube

import (
	"fmt"
	"sync"
	"time"

	"github.com/hashicorp/go-multierror"

	"istio.io/istio/pkg/test"
	"istio.io/istio/pkg/test/framework/components/echo"
	"istio.io/istio/pkg/test/framework/resource"
	"istio.io/istio/pkg/test/kube"
	"istio.io/istio/pkg/test/scopes"
	"istio.io/istio/pkg/test/util/retry"
)

var _ echo.Builder = &builder{}

type builder struct {
	ctx        resource.Context
	references []*echo.Instance
	configs    []echo.Config
}

func NewBuilder(ctx resource.Context) echo.Builder {
	return &builder{
		ctx: ctx,
	}
}

func (b *builder) With(i *echo.Instance, cfg echo.Config) echo.Builder {
	b.references = append(b.references, i)
	b.configs = append(b.configs, cfg)
	return b
}

func (b *builder) Build() (echo.Instances, error) {
	t0 := time.Now()
	instances, err := b.newInstances()
	if err != nil {
		return nil, fmt.Errorf("build instance: %v", err)
	}

	if err := b.initializeInstances(instances); err != nil {
		return nil, fmt.Errorf("initialize instances: %v", err)
	}
	scopes.Framework.Debugf("initialized echo deployments in %v", time.Since(t0))

	if err := b.waitUntilAllCallable(instances); err != nil {
		return nil, fmt.Errorf("wait until callable: %v", err)
	}
	scopes.Framework.Debugf("echo deployments ready in %v", time.Since(t0))

	// Success... update the caller's references.
	for i, inst := range instances {
		if b.references[i] != nil {
			*b.references[i] = inst
		}
	}
	return instances, nil
}

func (b *builder) BuildOrFail(t test.Failer) echo.Instances {
	t.Helper()
	res, err := b.Build()
	if err != nil {
		t.Fatal(err)
	}
	return res
}

func (b *builder) newInstances() ([]echo.Instance, error) {
	instances := make([]echo.Instance, 0, len(b.configs))
	for _, cfg := range b.configs {
		inst, err := newInstance(b.ctx, cfg)
		if err != nil {
			return nil, err
		}
		instances = append(instances, inst)
	}
	return instances, nil
}

func (b *builder) initializeInstances(instances []echo.Instance) error {
	// Wait to receive the k8s Endpoints for each Echo Instance.
	wg := sync.WaitGroup{}
	aggregateErrMux := &sync.Mutex{}
	var aggregateErr error
	for _, inst := range instances {
		wg.Add(1)

		inst := inst
		serviceName := inst.Config().Service
		serviceNamespace := inst.Config().Namespace.Name()
		timeout := inst.Config().ReadinessTimeout
		cluster := inst.(*instance).cluster

		// Run the waits in parallel.
		go func() {
			defer wg.Done()
			selector := "app"
			if inst.Config().DeployAsVM {
				selector = "istio.io/test-vm"
			}
			// Wait until all the pods are ready for this service
			fetch := kube.NewPodMustFetch(cluster, serviceNamespace, fmt.Sprintf("%s=%s", selector, serviceName))
			pods, err := kube.WaitUntilPodsAreReady(fetch, retry.Timeout(timeout))
			if err != nil {
				aggregateErrMux.Lock()
				aggregateErr = multierror.Append(aggregateErr, err)
				aggregateErrMux.Unlock()
				return
			}
			if err := inst.(*instance).initialize(pods); err != nil {
				aggregateErrMux.Lock()
				aggregateErr = multierror.Append(aggregateErr, fmt.Errorf("initialize %v: %v", inst.ID(), err))
				aggregateErrMux.Unlock()
			}
		}()
	}

	wg.Wait()

	if aggregateErr != nil {
		return aggregateErr
	}

	return nil
}

func (b *builder) waitUntilAllCallable(instances []echo.Instance) error {
	// Now wait for each endpoint to be callable from all others.
	wg := sync.WaitGroup{}
	aggregateErrMux := &sync.Mutex{}
	var aggregateErr error
	for _, inst := range instances {
		wg.Add(1)

		source := inst
		go func() {
			defer wg.Done()

			if err := source.WaitUntilCallable(instances...); err != nil {
				aggregateErrMux.Lock()
				aggregateErr = multierror.Append(aggregateErr, err)
				aggregateErrMux.Unlock()
			}
		}()
	}
	wg.Wait()

	return aggregateErr
}
<commit_msg>Add more context to error log on test framework init (#26121)<commit_after>// Copyright Istio 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 kube

import (
	"fmt"
	"sync"
	"time"

	"github.com/hashicorp/go-multierror"

	"istio.io/istio/pkg/test"
	"istio.io/istio/pkg/test/framework/components/echo"
	"istio.io/istio/pkg/test/framework/resource"
	"istio.io/istio/pkg/test/kube"
	"istio.io/istio/pkg/test/scopes"
	"istio.io/istio/pkg/test/util/retry"
)

var _ echo.Builder = &builder{}

type builder struct {
	ctx        resource.Context
	references []*echo.Instance
	configs    []echo.Config
}

func NewBuilder(ctx resource.Context) echo.Builder {
	return &builder{
		ctx: ctx,
	}
}

func (b *builder) With(i *echo.Instance, cfg echo.Config) echo.Builder {
	b.references = append(b.references, i)
	b.configs = append(b.configs, cfg)
	return b
}

func (b *builder) Build() (echo.Instances, error) {
	t0 := time.Now()
	instances, err := b.newInstances()
	if err != nil {
		return nil, fmt.Errorf("build instance: %v", err)
	}

	if err := b.initializeInstances(instances); err != nil {
		return nil, fmt.Errorf("initialize instances: %v", err)
	}
	scopes.Framework.Debugf("initialized echo deployments in %v", time.Since(t0))

	if err := b.waitUntilAllCallable(instances); err != nil {
		return nil, fmt.Errorf("wait until callable: %v", err)
	}
	scopes.Framework.Debugf("echo deployments ready in %v", time.Since(t0))

	// Success... update the caller's references.
	for i, inst := range instances {
		if b.references[i] != nil {
			*b.references[i] = inst
		}
	}
	return instances, nil
}

func (b *builder) BuildOrFail(t test.Failer) echo.Instances {
	t.Helper()
	res, err := b.Build()
	if err != nil {
		t.Fatal(err)
	}
	return res
}

func (b *builder) newInstances() ([]echo.Instance, error) {
	instances := make([]echo.Instance, 0, len(b.configs))
	for _, cfg := range b.configs {
		inst, err := newInstance(b.ctx, cfg)
		if err != nil {
			return nil, err
		}
		instances = append(instances, inst)
	}
	return instances, nil
}

func (b *builder) initializeInstances(instances []echo.Instance) error {
	// Wait to receive the k8s Endpoints for each Echo Instance.
	wg := sync.WaitGroup{}
	aggregateErrMux := &sync.Mutex{}
	var aggregateErr error
	for _, inst := range instances {
		wg.Add(1)

		inst := inst
		serviceName := inst.Config().Service
		serviceNamespace := inst.Config().Namespace.Name()
		timeout := inst.Config().ReadinessTimeout
		cluster := inst.(*instance).cluster

		// Run the waits in parallel.
		go func() {
			defer wg.Done()
			selector := "app"
			if inst.Config().DeployAsVM {
				selector = "istio.io/test-vm"
			}
			// Wait until all the pods are ready for this service
			fetch := kube.NewPodMustFetch(cluster, serviceNamespace, fmt.Sprintf("%s=%s", selector, serviceName))
			pods, err := kube.WaitUntilPodsAreReady(fetch, retry.Timeout(timeout))
			if err != nil {
				aggregateErrMux.Lock()
				aggregateErr = multierror.Append(aggregateErr, err)
				aggregateErrMux.Unlock()
				return
			}
			if err := inst.(*instance).initialize(pods); err != nil {
				aggregateErrMux.Lock()
				aggregateErr = multierror.Append(aggregateErr, fmt.Errorf("initialize %v/%v/%v: %v", inst.ID(), inst.Config().Service, inst.Address(), err))
				aggregateErrMux.Unlock()
			}
		}()
	}

	wg.Wait()

	if aggregateErr != nil {
		return aggregateErr
	}

	return nil
}

func (b *builder) waitUntilAllCallable(instances []echo.Instance) error {
	// Now wait for each endpoint to be callable from all others.
	wg := sync.WaitGroup{}
	aggregateErrMux := &sync.Mutex{}
	var aggregateErr error
	for _, inst := range instances {
		wg.Add(1)

		source := inst
		go func() {
			defer wg.Done()

			if err := source.WaitUntilCallable(instances...); err != nil {
				aggregateErrMux.Lock()
				aggregateErr = multierror.Append(aggregateErr, err)
				aggregateErrMux.Unlock()
			}
		}()
	}
	wg.Wait()

	return aggregateErr
}
<|endoftext|>


<commit_before>package device_manager

import (
	"os"
	"strings"

	"github.com/golang/mock/gomock"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

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

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

	v1 "kubevirt.io/api/core/v1"

	"k8s.io/apimachinery/pkg/util/yaml"

	"kubevirt.io/kubevirt/pkg/testutils"
	virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
)

const (
	fakeName       = "example.org/deadbeef"
	fakeID         = "dead:beef"
	fakeDriver     = "vfio-pci"
	fakeAddress    = "0000:00:00.0"
	fakeIommuGroup = "0"
	fakeNumaNode   = 0
)

var _ = Describe("PCI Device", func() {
	var mockPCI *MockDeviceHandler
	var fakePermittedHostDevicesConfig string
	var fakePermittedHostDevices v1.PermittedHostDevices
	var ctrl *gomock.Controller
	var clientTest *fake.Clientset

	BeforeEach(func() {
		clientTest = fake.NewSimpleClientset()
		By("making sure the environment has a PCI device at " + fakeAddress)
		_, err := os.Stat("/sys/bus/pci/devices/" + fakeAddress)
		if os.IsNotExist(err) {
			Skip("No PCI device found at " + fakeAddress + ", can't run PCI tests")
		}

		By("mocking PCI functions to simulate a vfio-pci device at " + fakeAddress)
		ctrl = gomock.NewController(GinkgoT())
		mockPCI = NewMockDeviceHandler(ctrl)
		Handler = mockPCI
		// Force pre-defined returned values and ensure the function only get called exacly once each on 0000:00:00.0
		mockPCI.EXPECT().GetDeviceIOMMUGroup(pciBasePath, fakeAddress).Return(fakeIommuGroup, nil).Times(1)
		mockPCI.EXPECT().GetDeviceDriver(pciBasePath, fakeAddress).Return(fakeDriver, nil).Times(1)
		mockPCI.EXPECT().GetDeviceNumaNode(pciBasePath, fakeAddress).Return(fakeNumaNode).Times(1)
		mockPCI.EXPECT().GetDevicePCIID(pciBasePath, fakeAddress).Return(fakeID, nil).Times(1)
		// Allow the regular functions to be called for all the other devices, they're harmless.
		// Just force the driver to NOT vfio-pci to ensure they all get ignored.
		mockPCI.EXPECT().GetDeviceIOMMUGroup(pciBasePath, gomock.Any()).AnyTimes()
		mockPCI.EXPECT().GetDeviceDriver(pciBasePath, gomock.Any()).Return("definitely-not-vfio-pci", nil).AnyTimes()
		mockPCI.EXPECT().GetDeviceNumaNode(pciBasePath, gomock.Any()).AnyTimes()
		mockPCI.EXPECT().GetDevicePCIID(pciBasePath, gomock.Any()).AnyTimes()

		By("creating a list of fake device using the yaml decoder")
		fakePermittedHostDevicesConfig = `
pciHostDevices:
- pciVendorSelector: "` + fakeID + `"
  resourceName: "` + fakeName + `"
`
		err = yaml.NewYAMLOrJSONDecoder(strings.NewReader(fakePermittedHostDevicesConfig), 1024).Decode(&fakePermittedHostDevices)
		Expect(err).ToNot(HaveOccurred())
		Expect(fakePermittedHostDevices.PciHostDevices).To(HaveLen(1))
		Expect(fakePermittedHostDevices.PciHostDevices[0].PCIVendorSelector).To(Equal(fakeID))
		Expect(fakePermittedHostDevices.PciHostDevices[0].ResourceName).To(Equal(fakeName))
	})

	AfterEach(func() {
		ctrl.Finish()
	})

	It("Should parse the permitted devices and find 1 matching PCI device", func() {
		supportedPCIDeviceMap := make(map[string]string)
		for _, pciDev := range fakePermittedHostDevices.PciHostDevices {
			// do not add a device plugin for this resource if it's being provided via an external device plugin
			if !pciDev.ExternalResourceProvider {
				supportedPCIDeviceMap[pciDev.PCIVendorSelector] = pciDev.ResourceName
			}
		}
		// discoverPermittedHostPCIDevices() will walk real PCI devices wherever the tests are running
		// It's assumed here that it will find a PCI device at 0000:00:00.0
		devices := discoverPermittedHostPCIDevices(supportedPCIDeviceMap)
		Expect(devices).To(HaveLen(1))
		Expect(devices[fakeName]).To(HaveLen(1))
		Expect(devices[fakeName][0].pciID).To(Equal(fakeID))
		Expect(devices[fakeName][0].driver).To(Equal(fakeDriver))
		Expect(devices[fakeName][0].pciAddress).To(Equal(fakeAddress))
		Expect(devices[fakeName][0].iommuGroup).To(Equal(fakeIommuGroup))
		Expect(devices[fakeName][0].numaNode).To(Equal(fakeNumaNode))
	})

	It("Should validate DPI devices", func() {
		iommuToPCIMap := make(map[string]string)
		supportedPCIDeviceMap := make(map[string]string)
		for _, pciDev := range fakePermittedHostDevices.PciHostDevices {
			// do not add a device plugin for this resource if it's being provided via an external device plugin
			if !pciDev.ExternalResourceProvider {
				supportedPCIDeviceMap[pciDev.PCIVendorSelector] = pciDev.ResourceName
			}
		}
		// discoverPermittedHostPCIDevices() will walk real PCI devices wherever the tests are running
		// It's assumed here that it will find a PCI device at 0000:00:00.0
		pciDevices := discoverPermittedHostPCIDevices(supportedPCIDeviceMap)
		devs := constructDPIdevices(pciDevices[fakeName], iommuToPCIMap)
		Expect(devs[0].ID).To(Equal(fakeIommuGroup))
		Expect(devs[0].Topology.Nodes[0].ID).To(Equal(int64(fakeNumaNode)))
	})
	It("Should update the device list according to the configmap", func() {
		By("creating a cluster config")
		kv := &v1.KubeVirt{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "kubevirt",
				Namespace: "kubevirt",
			},
			Spec: v1.KubeVirtSpec{
				Configuration: v1.KubeVirtConfiguration{
					DeveloperConfiguration: &v1.DeveloperConfiguration{},
				},
			},
			Status: v1.KubeVirtStatus{
				Phase: v1.KubeVirtPhaseDeploying,
			},
		}
		fakeClusterConfig, _, kvInformer := testutils.NewFakeClusterConfigUsingKV(kv)

		By("creating an empty device controller")
		var noDevices []Device
		deviceController := NewDeviceController("master", noDevices, fakeClusterConfig, clientTest.CoreV1())

		By("adding a host device to the cluster config")
		kvConfig := kv.DeepCopy()
		kvConfig.Spec.Configuration.DeveloperConfiguration.FeatureGates = []string{virtconfig.HostDevicesGate}
		kvConfig.Spec.Configuration.PermittedHostDevices = &v1.PermittedHostDevices{
			PciHostDevices: []v1.PciHostDevice{
				{
					PCIVendorSelector: fakeID,
					ResourceName:      fakeName,
				},
			},
		}
		testutils.UpdateFakeKubeVirtClusterConfig(kvInformer, kvConfig)
		permittedDevices := fakeClusterConfig.GetPermittedHostDevices()
		Expect(permittedDevices).ToNot(BeNil(), "something went wrong while parsing the configmap(s)")
		Expect(len(permittedDevices.PciHostDevices)).To(Equal(1), "the fake device was not found")

		By("ensuring a device plugin gets created for our fake device")
		enabledDevicePlugins, disabledDevicePlugins := deviceController.splitPermittedDevices(
			deviceController.updatePermittedHostDevicePlugins(),
		)
		Expect(len(enabledDevicePlugins)).To(Equal(1), "a device plugin wasn't created for the fake device")
		Expect(disabledDevicePlugins).To(HaveLen(0))
		Ω(enabledDevicePlugins).Should(HaveKey(fakeName))
		// Manually adding the enabled plugin, since the device controller is not actually running
		deviceController.startedPlugins[fakeName] = controlledDevice{devicePlugin: enabledDevicePlugins[fakeName]}

		By("deletting the device from the configmap")
		kvConfig.Spec.Configuration.PermittedHostDevices = &v1.PermittedHostDevices{}
		testutils.UpdateFakeKubeVirtClusterConfig(kvInformer, kvConfig)
		permittedDevices = fakeClusterConfig.GetPermittedHostDevices()
		Expect(permittedDevices).ToNot(BeNil(), "something went wrong while parsing the configmap(s)")
		Expect(len(permittedDevices.PciHostDevices)).To(Equal(0), "the fake device was not deleted")

		By("ensuring the device plugin gets stopped")
		enabledDevicePlugins, disabledDevicePlugins = deviceController.splitPermittedDevices(
			deviceController.updatePermittedHostDevicePlugins(),
		)
		Expect(enabledDevicePlugins).To(HaveLen(0))
		Expect(len(disabledDevicePlugins)).To(Equal(1), "the fake device plugin did not get disabled")
		Ω(disabledDevicePlugins).Should(HaveKey(fakeName))
	})
})
<commit_msg>Change HaveLen(0) to BeEmpty()<commit_after>package device_manager

import (
	"os"
	"strings"

	"github.com/golang/mock/gomock"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

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

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

	v1 "kubevirt.io/api/core/v1"

	"k8s.io/apimachinery/pkg/util/yaml"

	"kubevirt.io/kubevirt/pkg/testutils"
	virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
)

const (
	fakeName       = "example.org/deadbeef"
	fakeID         = "dead:beef"
	fakeDriver     = "vfio-pci"
	fakeAddress    = "0000:00:00.0"
	fakeIommuGroup = "0"
	fakeNumaNode   = 0
)

var _ = Describe("PCI Device", func() {
	var mockPCI *MockDeviceHandler
	var fakePermittedHostDevicesConfig string
	var fakePermittedHostDevices v1.PermittedHostDevices
	var ctrl *gomock.Controller
	var clientTest *fake.Clientset

	BeforeEach(func() {
		clientTest = fake.NewSimpleClientset()
		By("making sure the environment has a PCI device at " + fakeAddress)
		_, err := os.Stat("/sys/bus/pci/devices/" + fakeAddress)
		if os.IsNotExist(err) {
			Skip("No PCI device found at " + fakeAddress + ", can't run PCI tests")
		}

		By("mocking PCI functions to simulate a vfio-pci device at " + fakeAddress)
		ctrl = gomock.NewController(GinkgoT())
		mockPCI = NewMockDeviceHandler(ctrl)
		Handler = mockPCI
		// Force pre-defined returned values and ensure the function only get called exacly once each on 0000:00:00.0
		mockPCI.EXPECT().GetDeviceIOMMUGroup(pciBasePath, fakeAddress).Return(fakeIommuGroup, nil).Times(1)
		mockPCI.EXPECT().GetDeviceDriver(pciBasePath, fakeAddress).Return(fakeDriver, nil).Times(1)
		mockPCI.EXPECT().GetDeviceNumaNode(pciBasePath, fakeAddress).Return(fakeNumaNode).Times(1)
		mockPCI.EXPECT().GetDevicePCIID(pciBasePath, fakeAddress).Return(fakeID, nil).Times(1)
		// Allow the regular functions to be called for all the other devices, they're harmless.
		// Just force the driver to NOT vfio-pci to ensure they all get ignored.
		mockPCI.EXPECT().GetDeviceIOMMUGroup(pciBasePath, gomock.Any()).AnyTimes()
		mockPCI.EXPECT().GetDeviceDriver(pciBasePath, gomock.Any()).Return("definitely-not-vfio-pci", nil).AnyTimes()
		mockPCI.EXPECT().GetDeviceNumaNode(pciBasePath, gomock.Any()).AnyTimes()
		mockPCI.EXPECT().GetDevicePCIID(pciBasePath, gomock.Any()).AnyTimes()

		By("creating a list of fake device using the yaml decoder")
		fakePermittedHostDevicesConfig = `
pciHostDevices:
- pciVendorSelector: "` + fakeID + `"
  resourceName: "` + fakeName + `"
`
		err = yaml.NewYAMLOrJSONDecoder(strings.NewReader(fakePermittedHostDevicesConfig), 1024).Decode(&fakePermittedHostDevices)
		Expect(err).ToNot(HaveOccurred())
		Expect(fakePermittedHostDevices.PciHostDevices).To(HaveLen(1))
		Expect(fakePermittedHostDevices.PciHostDevices[0].PCIVendorSelector).To(Equal(fakeID))
		Expect(fakePermittedHostDevices.PciHostDevices[0].ResourceName).To(Equal(fakeName))
	})

	AfterEach(func() {
		ctrl.Finish()
	})

	It("Should parse the permitted devices and find 1 matching PCI device", func() {
		supportedPCIDeviceMap := make(map[string]string)
		for _, pciDev := range fakePermittedHostDevices.PciHostDevices {
			// do not add a device plugin for this resource if it's being provided via an external device plugin
			if !pciDev.ExternalResourceProvider {
				supportedPCIDeviceMap[pciDev.PCIVendorSelector] = pciDev.ResourceName
			}
		}
		// discoverPermittedHostPCIDevices() will walk real PCI devices wherever the tests are running
		// It's assumed here that it will find a PCI device at 0000:00:00.0
		devices := discoverPermittedHostPCIDevices(supportedPCIDeviceMap)
		Expect(devices).To(HaveLen(1))
		Expect(devices[fakeName]).To(HaveLen(1))
		Expect(devices[fakeName][0].pciID).To(Equal(fakeID))
		Expect(devices[fakeName][0].driver).To(Equal(fakeDriver))
		Expect(devices[fakeName][0].pciAddress).To(Equal(fakeAddress))
		Expect(devices[fakeName][0].iommuGroup).To(Equal(fakeIommuGroup))
		Expect(devices[fakeName][0].numaNode).To(Equal(fakeNumaNode))
	})

	It("Should validate DPI devices", func() {
		iommuToPCIMap := make(map[string]string)
		supportedPCIDeviceMap := make(map[string]string)
		for _, pciDev := range fakePermittedHostDevices.PciHostDevices {
			// do not add a device plugin for this resource if it's being provided via an external device plugin
			if !pciDev.ExternalResourceProvider {
				supportedPCIDeviceMap[pciDev.PCIVendorSelector] = pciDev.ResourceName
			}
		}
		// discoverPermittedHostPCIDevices() will walk real PCI devices wherever the tests are running
		// It's assumed here that it will find a PCI device at 0000:00:00.0
		pciDevices := discoverPermittedHostPCIDevices(supportedPCIDeviceMap)
		devs := constructDPIdevices(pciDevices[fakeName], iommuToPCIMap)
		Expect(devs[0].ID).To(Equal(fakeIommuGroup))
		Expect(devs[0].Topology.Nodes[0].ID).To(Equal(int64(fakeNumaNode)))
	})
	It("Should update the device list according to the configmap", func() {
		By("creating a cluster config")
		kv := &v1.KubeVirt{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "kubevirt",
				Namespace: "kubevirt",
			},
			Spec: v1.KubeVirtSpec{
				Configuration: v1.KubeVirtConfiguration{
					DeveloperConfiguration: &v1.DeveloperConfiguration{},
				},
			},
			Status: v1.KubeVirtStatus{
				Phase: v1.KubeVirtPhaseDeploying,
			},
		}
		fakeClusterConfig, _, kvInformer := testutils.NewFakeClusterConfigUsingKV(kv)

		By("creating an empty device controller")
		var noDevices []Device
		deviceController := NewDeviceController("master", noDevices, fakeClusterConfig, clientTest.CoreV1())

		By("adding a host device to the cluster config")
		kvConfig := kv.DeepCopy()
		kvConfig.Spec.Configuration.DeveloperConfiguration.FeatureGates = []string{virtconfig.HostDevicesGate}
		kvConfig.Spec.Configuration.PermittedHostDevices = &v1.PermittedHostDevices{
			PciHostDevices: []v1.PciHostDevice{
				{
					PCIVendorSelector: fakeID,
					ResourceName:      fakeName,
				},
			},
		}
		testutils.UpdateFakeKubeVirtClusterConfig(kvInformer, kvConfig)
		permittedDevices := fakeClusterConfig.GetPermittedHostDevices()
		Expect(permittedDevices).ToNot(BeNil(), "something went wrong while parsing the configmap(s)")
		Expect(len(permittedDevices.PciHostDevices)).To(Equal(1), "the fake device was not found")

		By("ensuring a device plugin gets created for our fake device")
		enabledDevicePlugins, disabledDevicePlugins := deviceController.splitPermittedDevices(
			deviceController.updatePermittedHostDevicePlugins(),
		)
		Expect(len(enabledDevicePlugins)).To(Equal(1), "a device plugin wasn't created for the fake device")
		Expect(disabledDevicePlugins).To(BeEmpty())
		Ω(enabledDevicePlugins).Should(HaveKey(fakeName))
		// Manually adding the enabled plugin, since the device controller is not actually running
		deviceController.startedPlugins[fakeName] = controlledDevice{devicePlugin: enabledDevicePlugins[fakeName]}

		By("deletting the device from the configmap")
		kvConfig.Spec.Configuration.PermittedHostDevices = &v1.PermittedHostDevices{}
		testutils.UpdateFakeKubeVirtClusterConfig(kvInformer, kvConfig)
		permittedDevices = fakeClusterConfig.GetPermittedHostDevices()
		Expect(permittedDevices).ToNot(BeNil(), "something went wrong while parsing the configmap(s)")
		Expect(len(permittedDevices.PciHostDevices)).To(Equal(0), "the fake device was not deleted")

		By("ensuring the device plugin gets stopped")
		enabledDevicePlugins, disabledDevicePlugins = deviceController.splitPermittedDevices(
			deviceController.updatePermittedHostDevicePlugins(),
		)
		Expect(enabledDevicePlugins).To(BeEmpty())
		Expect(len(disabledDevicePlugins)).To(Equal(1), "the fake device plugin did not get disabled")
		Ω(disabledDevicePlugins).Should(HaveKey(fakeName))
	})
})
<|endoftext|>


<commit_before>package gce

import (
	"context"
	"fmt"
	"io"
	"time"

	"github.com/sirupsen/logrus"
	"google.golang.org/api/compute/v1"

	"github.com/pkg/errors"
	"github.com/supergiant/control/pkg/workflows/steps"
)

const CreateForwardingRulesStepName = "gce_create_forwarding_rules"

type CreateForwardingRules struct {
	timeout time.Duration
	attemptCount int

	getComputeSvc func(context.Context, steps.GCEConfig) (*computeService, error)
}

func NewCreateForwardingRulesStep() *CreateForwardingRules {
	return &CreateForwardingRules{
		timeout: time.Second * 10,
		attemptCount: 10,
		getComputeSvc: func(ctx context.Context, config steps.GCEConfig) (*computeService, error) {
			client, err := GetClient(ctx, config)

			if err != nil {
				return nil, err
			}

			return &computeService{
				insertForwardingRule: func(ctx context.Context, config steps.GCEConfig, rule *compute.ForwardingRule) (*compute.Operation, error) {
					return client.ForwardingRules.Insert(config.ServiceAccount.ProjectID, config.Region, rule).Do()
				},
			}, nil
		},
	}
}

func (s *CreateForwardingRules) Run(ctx context.Context, output io.Writer,
	config *steps.Config) error {
	// Skip this step for the rest of nodes
	if !config.IsBootstrap {
		return nil
	}

	logrus.Debugf("Step %s", CreateForwardingRulesStepName)

	svc, err := s.getComputeSvc(ctx, config.GCEConfig)

	if err != nil {
		logrus.Errorf("Error getting service %v", err)
		return errors.Wrapf(err, "%s getting service caused", CreateForwardingRulesStepName)
	}

	exName := fmt.Sprintf("exrule-%s", config.ClusterID)
	externalForwardingRule := &compute.ForwardingRule{
		Name:                exName,
		IPAddress:           config.GCEConfig.ExternalIPAddressLink,
		LoadBalancingScheme: "EXTERNAL",
		Description:         "External forwarding rule to target pool",
		IPProtocol:          "TCP",
		Target:              config.GCEConfig.TargetPoolLink,
	}

	timeout := s.timeout

	for i := 0;i < s.attemptCount; i++ {
		_, err = svc.insertForwardingRule(ctx, config.GCEConfig, externalForwardingRule)

		if err == nil {
			break
		}

		time.Sleep(timeout)
		timeout = timeout * 2
	}

	if err != nil {
		logrus.Errorf("Error creating external forwarding rule %v", err)
		return errors.Wrapf(err, "%s creating external forwarding rule caused", CreateForwardingRulesStepName)
	}

	logrus.Debugf("Created external forwarding rule %s", exName)
	config.GCEConfig.ForwardingRuleName = exName

	return nil
}

func (s *CreateForwardingRules) Name() string {
	return CreateForwardingRulesStepName
}

func (s *CreateForwardingRules) Depends() []string {
	return []string{CreateTargetPullStepName, CreateIPAddressStepName}
}

func (s *CreateForwardingRules) Description() string {
	return "Create forwarding rules to pass traffic to nodes"
}

func (s *CreateForwardingRules) Rollback(context.Context, io.Writer, *steps.Config) error {
	return nil
}
<commit_msg>Fix bug creating forwarding rules (#1392)<commit_after>package gce

import (
	"context"
	"fmt"
	"io"
	"time"

	"github.com/sirupsen/logrus"
	"google.golang.org/api/compute/v1"

	"github.com/pkg/errors"
	"github.com/supergiant/control/pkg/workflows/steps"
)

const CreateForwardingRulesStepName = "gce_create_forwarding_rules"

type CreateForwardingRules struct {
	timeout time.Duration
	attemptCount int

	getComputeSvc func(context.Context, steps.GCEConfig) (*computeService, error)
}

func NewCreateForwardingRulesStep() *CreateForwardingRules {
	return &CreateForwardingRules{
		timeout: time.Second * 10,
		attemptCount: 10,
		getComputeSvc: func(ctx context.Context, config steps.GCEConfig) (*computeService, error) {
			client, err := GetClient(ctx, config)

			if err != nil {
				return nil, err
			}

			return &computeService{
				insertForwardingRule: func(ctx context.Context, config steps.GCEConfig, rule *compute.ForwardingRule) (*compute.Operation, error) {
					return client.ForwardingRules.Insert(config.ServiceAccount.ProjectID, config.Region, rule).Do()
				},
			}, nil
		},
	}
}

func (s *CreateForwardingRules) Run(ctx context.Context, output io.Writer,
	config *steps.Config) error {
	logrus.Debugf("Step %s", CreateForwardingRulesStepName)

	svc, err := s.getComputeSvc(ctx, config.GCEConfig)

	if err != nil {
		logrus.Errorf("Error getting service %v", err)
		return errors.Wrapf(err, "%s getting service caused", CreateForwardingRulesStepName)
	}

	exName := fmt.Sprintf("exrule-%s", config.ClusterID)
	externalForwardingRule := &compute.ForwardingRule{
		Name:                exName,
		IPAddress:           config.GCEConfig.ExternalIPAddressLink,
		LoadBalancingScheme: "EXTERNAL",
		Description:         "External forwarding rule to target pool",
		IPProtocol:          "TCP",
		Target:              config.GCEConfig.TargetPoolLink,
	}

	timeout := s.timeout

	for i := 0;i < s.attemptCount; i++ {
		_, err = svc.insertForwardingRule(ctx, config.GCEConfig, externalForwardingRule)

		if err == nil {
			break
		}

		time.Sleep(timeout)
		timeout = timeout * 2
	}

	if err != nil {
		logrus.Errorf("Error creating external forwarding rule %v", err)
		return errors.Wrapf(err, "%s creating external forwarding rule caused", CreateForwardingRulesStepName)
	}

	logrus.Debugf("Created external forwarding rule %s", exName)
	config.GCEConfig.ForwardingRuleName = exName

	return nil
}

func (s *CreateForwardingRules) Name() string {
	return CreateForwardingRulesStepName
}

func (s *CreateForwardingRules) Depends() []string {
	return []string{CreateTargetPullStepName, CreateIPAddressStepName}
}

func (s *CreateForwardingRules) Description() string {
	return "Create forwarding rules to pass traffic to nodes"
}

func (s *CreateForwardingRules) Rollback(context.Context, io.Writer, *steps.Config) error {
	return nil
}
<|endoftext|>


<commit_before>/* Copyright 2020 Google Inc.

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

    https://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 os

import (
	"bytes"
	"context"
	"crypto/rand"
	"errors"
	"fmt"
	"io"
	"reflect"
	"testing"
	"time"

	"github.com/google/gnxi/gnoi/os/pb"
	"google.golang.org/grpc"
)

type activateRPC func(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error)
type verifyRPC func(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error)

type mockClient struct {
	pb.OSClient
	activate      activateRPC
	verify        verifyRPC
	installClient pb.OS_InstallClient
}

type installRequestMap struct {
	req  *pb.InstallRequest
	resp *pb.InstallResponse
}

type mockInstallClient struct {
	pb.OS_InstallClient
	reqResp []*installRequestMap
	i       int
	recv    chan int
	recvErr chan *pb.InstallResponse_InstallError
}

func (c *mockInstallClient) Send(req *pb.InstallRequest) error {
	if c.i < len(c.reqResp) {
		if reflect.TypeOf(req.Request).String() == reflect.TypeOf(c.reqResp[c.i].req.Request).String() {
			c.recv <- c.i
		} else {
			c.recvErr <- &pb.InstallResponse_InstallError{
				InstallError: &pb.InstallError{Type: pb.InstallError_UNSPECIFIED, Detail: "Invalid command"},
			}
		}
		c.i++
	}
	return nil
}

func (c *mockInstallClient) Recv() (*pb.InstallResponse, error) {
	select {
	case i := <-c.recv:
		res := c.reqResp[i].resp
		if res == nil {
			<-time.After(200 * time.Millisecond)
		}
		return res, nil
	case err := <-c.recvErr:
		return &pb.InstallResponse{Response: err}, nil
	}
}

func (c *mockInstallClient) CloseSend() error {
	return nil
}

func (c *mockClient) Activate(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
	return c.activate(ctx, in, opts...)
}

func (c *mockClient) Verify(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error) {
	return c.verify(ctx, in, opts...)
}

func (c *mockClient) Install(ctx context.Context, opts ...grpc.CallOption) (pb.OS_InstallClient, error) {
	return c.installClient, nil
}

func activateErrorRPC(errType pb.ActivateError_Type, detail string) activateRPC {
	return func(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
		return &pb.ActivateResponse{
			Response: &pb.ActivateResponse_ActivateError{
				ActivateError: &pb.ActivateError{Type: errType, Detail: detail},
			}}, nil
	}
}

func activateSuccessRPC(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
	return &pb.ActivateResponse{
		Response: &pb.ActivateResponse_ActivateOk{}}, nil
}

func readBytes(num int) func(string) (io.ReaderAt, uint64, func() error, error) {
	b := make([]byte, num)
	rand.Read(b)
	return func(_ string) (io.ReaderAt, uint64, func() error, error) {
		return bytes.NewReader(b), uint64(num), func() error { return nil }, nil
	}
}

func TestInstall(t *testing.T) {
	installTests := []struct {
		name    string
		reqResp []*installRequestMap
		read    func(string) (io.ReaderAt, uint64, func() error, error)
		err     error
	}{
		{
			"Already validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes(0),
			nil,
		},
		{
			"File size of one chunk then validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: chunkSize}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferEnd{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes(chunkSize),
			nil,
		},
		{
			"File size of two chunks + 1 then validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: chunkSize}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: (chunkSize * 2)}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: (chunkSize * 2) + 1}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferEnd{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes((chunkSize * 2) + 1),
			nil,
		},
		{
			"File size of one chunk but INCOMPATIBLE InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_INCOMPATIBLE}}},
				},
			},
			readBytes(chunkSize),
			errors.New("InstallError occured: INCOMPATIBLE"),
		},
		{
			"File size of two chunks but Unspecified InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_UNSPECIFIED, Detail: "Unspecified"}}},
				},
			},
			readBytes(chunkSize * 2),
			errors.New("Unspecified InstallError error: Unspecified"),
		},
	}
	for _, test := range installTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{
				client: &mockClient{installClient: &mockInstallClient{
					reqResp: test.reqResp,
					recv:    make(chan int, 2),
					recvErr: make(chan *pb.InstallResponse_InstallError, 2),
				}},
			}
			fileReader = test.read
			if err := client.Install(context.Background(), "", "version", 100*time.Millisecond); fmt.Sprintf("%v", err) != fmt.Sprintf("%v", test.err) {
				t.Errorf("Wanted error: **%v** but got error: **%v**", test.err, err)
			}
		})
	}
}

func TestActivate(t *testing.T) {
	activateTests := []struct {
		name    string
		client  *mockClient
		wantErr bool
	}{
		{
			"Success",
			&mockClient{activate: activateSuccessRPC},
			false,
		},
		{
			"Unspecified",
			&mockClient{activate: activateErrorRPC(pb.ActivateError_UNSPECIFIED, "detail")},
			true,
		},
		{
			"Non Existent Version",
			&mockClient{activate: activateErrorRPC(pb.ActivateError_NON_EXISTENT_VERSION, "")},
			true,
		},
	}
	for _, test := range activateTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{client: test.client}
			got := client.Activate(context.Background(), "version")
			if test.wantErr {
				if got == nil {
					t.Error("want error, got nil")
				}
			} else if got != nil {
				t.Errorf("want <nil>, got: %v", got)
			}
		})
	}
}

func TestVerify(t *testing.T) {
	verifyTests := []struct {
		name,
		runningVersion,
		failMessage string
	}{
		{"Is Running", "version", ""},
		{"Previous Activation Fail", "version", "Activation fail"},
	}
	for _, test := range verifyTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{client: &mockClient{
				verify: func(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error) {
					return &pb.VerifyResponse{Version: test.runningVersion, ActivationFailMessage: test.failMessage}, nil
				},
			}}
			version, activationErr, err := client.Verify(context.Background())
			if err != nil {
				t.Errorf("Verify RPC error: %w", err)
			}
			if activationErr != test.failMessage || version != test.runningVersion {
				t.Errorf(
					"Expected VerifyResponse(%s, %s) but got VerifyResponse(%s, %s)",
					test.runningVersion,
					test.failMessage,
					version,
					activationErr,
				)
			}
		})
	}
}
<commit_msg>Renamed fields<commit_after>/* Copyright 2020 Google Inc.

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

    https://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 os

import (
	"bytes"
	"context"
	"crypto/rand"
	"errors"
	"fmt"
	"io"
	"reflect"
	"testing"
	"time"

	"github.com/google/gnxi/gnoi/os/pb"
	"google.golang.org/grpc"
)

type activateRPC func(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error)
type verifyRPC func(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error)

type mockClient struct {
	pb.OSClient
	activate      activateRPC
	verify        verifyRPC
	installClient pb.OS_InstallClient
}

type installRequestMap struct {
	req  *pb.InstallRequest
	resp *pb.InstallResponse
}

type mockInstallClient struct {
	pb.OS_InstallClient
	reqResp []*installRequestMap
	i       int
	recv    chan int
	recvErr chan *pb.InstallResponse_InstallError
}

func (c *mockInstallClient) Send(req *pb.InstallRequest) error {
	if c.i < len(c.reqResp) {
		if reflect.TypeOf(req.Request).String() == reflect.TypeOf(c.reqResp[c.i].req.Request).String() {
			c.recv <- c.i
		} else {
			c.recvErr <- &pb.InstallResponse_InstallError{
				InstallError: &pb.InstallError{Type: pb.InstallError_UNSPECIFIED, Detail: "Invalid command"},
			}
		}
		c.i++
	}
	return nil
}

func (c *mockInstallClient) Recv() (*pb.InstallResponse, error) {
	select {
	case i := <-c.recv:
		res := c.reqResp[i].resp
		if res == nil {
			<-time.After(200 * time.Millisecond)
		}
		return res, nil
	case err := <-c.recvErr:
		return &pb.InstallResponse{Response: err}, nil
	}
}

func (c *mockInstallClient) CloseSend() error {
	return nil
}

func (c *mockClient) Activate(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
	return c.activate(ctx, in, opts...)
}

func (c *mockClient) Verify(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error) {
	return c.verify(ctx, in, opts...)
}

func (c *mockClient) Install(ctx context.Context, opts ...grpc.CallOption) (pb.OS_InstallClient, error) {
	return c.installClient, nil
}

func activateErrorRPC(errType pb.ActivateError_Type, detail string) activateRPC {
	return func(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
		return &pb.ActivateResponse{
			Response: &pb.ActivateResponse_ActivateError{
				ActivateError: &pb.ActivateError{Type: errType, Detail: detail},
			}}, nil
	}
}

func activateSuccessRPC(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
	return &pb.ActivateResponse{
		Response: &pb.ActivateResponse_ActivateOk{}}, nil
}

func readBytes(num int) func(string) (io.ReaderAt, uint64, func() error, error) {
	b := make([]byte, num)
	rand.Read(b)
	return func(_ string) (io.ReaderAt, uint64, func() error, error) {
		return bytes.NewReader(b), uint64(num), func() error { return nil }, nil
	}
}

func TestInstall(t *testing.T) {
	installTests := []struct {
		name   string
		reqMap []*installRequestMap
		reader func(string) (io.ReaderAt, uint64, func() error, error)
		err    error
	}{
		{
			"Already validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes(0),
			nil,
		},
		{
			"File size of one chunk then validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: chunkSize}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferEnd{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes(chunkSize),
			nil,
		},
		{
			"File size of two chunks + 1 then validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: chunkSize}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: (chunkSize * 2)}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: (chunkSize * 2) + 1}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferEnd{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes((chunkSize * 2) + 1),
			nil,
		},
		{
			"File size of one chunk but INCOMPATIBLE InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_INCOMPATIBLE}}},
				},
			},
			readBytes(chunkSize),
			errors.New("InstallError occured: INCOMPATIBLE"),
		},
		{
			"File size of two chunks but Unspecified InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_UNSPECIFIED, Detail: "Unspecified"}}},
				},
			},
			readBytes(chunkSize * 2),
			errors.New("Unspecified InstallError error: Unspecified"),
		},
	}
	for _, test := range installTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{
				client: &mockClient{installClient: &mockInstallClient{
					reqResp: test.reqMap,
					recv:    make(chan int, 2),
					recvErr: make(chan *pb.InstallResponse_InstallError, 2),
				}},
			}
			fileReader = test.reader
			if err := client.Install(context.Background(), "", "version", 100*time.Millisecond); fmt.Sprintf("%v", err) != fmt.Sprintf("%v", test.err) {
				t.Errorf("Wanted error: **%v** but got error: **%v**", test.err, err)
			}
		})
	}
}

func TestActivate(t *testing.T) {
	activateTests := []struct {
		name    string
		client  *mockClient
		wantErr bool
	}{
		{
			"Success",
			&mockClient{activate: activateSuccessRPC},
			false,
		},
		{
			"Unspecified",
			&mockClient{activate: activateErrorRPC(pb.ActivateError_UNSPECIFIED, "detail")},
			true,
		},
		{
			"Non Existent Version",
			&mockClient{activate: activateErrorRPC(pb.ActivateError_NON_EXISTENT_VERSION, "")},
			true,
		},
	}
	for _, test := range activateTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{client: test.client}
			got := client.Activate(context.Background(), "version")
			if test.wantErr {
				if got == nil {
					t.Error("want error, got nil")
				}
			} else if got != nil {
				t.Errorf("want <nil>, got: %v", got)
			}
		})
	}
}

func TestVerify(t *testing.T) {
	verifyTests := []struct {
		name,
		runningVersion,
		failMessage string
	}{
		{"Is Running", "version", ""},
		{"Previous Activation Fail", "version", "Activation fail"},
	}
	for _, test := range verifyTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{client: &mockClient{
				verify: func(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error) {
					return &pb.VerifyResponse{Version: test.runningVersion, ActivationFailMessage: test.failMessage}, nil
				},
			}}
			version, activationErr, err := client.Verify(context.Background())
			if err != nil {
				t.Errorf("Verify RPC error: %w", err)
			}
			if activationErr != test.failMessage || version != test.runningVersion {
				t.Errorf(
					"Expected VerifyResponse(%s, %s) but got VerifyResponse(%s, %s)",
					test.runningVersion,
					test.failMessage,
					version,
					activationErr,
				)
			}
		})
	}
}
<|endoftext|>