<commit_before>// Copyright 2020 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 authorizedapp

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/google/exposure-notifications-server/internal/database"
	"github.com/google/exposure-notifications-server/internal/logging"
	"github.com/google/exposure-notifications-server/internal/secrets"
)

// Compile-time check to assert implementation.
var _ Provider = (*DatabaseProvider)(nil)

// DatabaseProvider is a Provider that pulls from the database and caches and
// refreshes values on failure.
type DatabaseProvider struct {
	database      *database.DB
	secretManager secrets.SecretManager
	cacheDuration time.Duration

	cache     map[string]*cacheItem
	cacheLock sync.RWMutex
}

type cacheItem struct {
	value    *database.AuthorizedApp
	cachedAt time.Time
}

// DatabaseProviderOption is used as input to the database provider.
type DatabaseProviderOption func(*DatabaseProvider) *DatabaseProvider

// WithSecretManager sets the secret manager for resolving secrets.
func WithSecretManager(sm secrets.SecretManager) DatabaseProviderOption {
	return func(p *DatabaseProvider) *DatabaseProvider {
		p.secretManager = sm
		return p
	}
}

// NewDatabaseProvider creates a new Provider that reads from a database.
func NewDatabaseProvider(ctx context.Context, db *database.DB, config *Config, opts ...DatabaseProviderOption) (Provider, error) {
	provider := &DatabaseProvider{
		database:      db,
		cacheDuration: config.CacheDuration,
		cache:         make(map[string]*cacheItem),
	}

	// Apply options.
	for _, opt := range opts {
		provider = opt(provider)
	}

	return provider, nil
}

// AppConfig returns the config for the given app package name.
func (p *DatabaseProvider) AppConfig(ctx context.Context, name string) (*database.AuthorizedApp, error) {
	logger := logging.FromContext(ctx)

	// Acquire a read lock first, which allows concurrent readers, to check if
	// there's an item in the cache.
	p.cacheLock.RLock()
	item, ok := p.cache[name]
	if ok && time.Since(item.cachedAt) <= p.cacheDuration {
		if item.value == nil {
			p.cacheLock.RUnlock()
			return nil, AppNotFound
		}
		p.cacheLock.RUnlock()
		return item.value, nil
	}
	p.cacheLock.RUnlock()

	// Acquire a more aggressive lock now because we're about to mutate. However,
	// it's possible that a concurrent routine has already mutated between our
	// read and write locks, so we have to check again.
	p.cacheLock.Lock()
	item, ok = p.cache[name]
	if ok && time.Since(item.cachedAt) <= p.cacheDuration {
		if item.value == nil {
			p.cacheLock.Unlock()
			return nil, AppNotFound
		}
		p.cacheLock.Unlock()
		return item.value, nil
	}

	// Load config.
	config, err := p.loadAuthorizedAppFromDatabase(ctx, name)
	if err != nil {
		return nil, fmt.Errorf("authorizedapp: %w", err)
	}

	// Cache configs.
	logger.Infof("authorizedapp: loaded %v, caching for %s", name, p.cacheDuration)
	p.cache[name] = &cacheItem{
		value:    config,
		cachedAt: time.Now(),
	}

	// Handle not found.
	if config == nil {
		p.cacheLock.Unlock()
		return nil, AppNotFound
	}

	// Returned config.
	return config, nil
}

// loadAuthorizedAppFromDatabase is a lower-level private API that actually loads and parses
// a single AuthorizedApp from the database.
func (p *DatabaseProvider) loadAuthorizedAppFromDatabase(ctx context.Context, name string) (*database.AuthorizedApp, error) {
	logger := logging.FromContext(ctx)

	logger.Infof("authorizedapp: loading %v from database", name)
	config, err := p.database.GetAuthorizedApp(ctx, p.secretManager, name)
	if err != nil {
		return nil, fmt.Errorf("failed to read %v from database: %w", name, err)
	}
	return config, nil
}
<commit_msg>fix locking error in appconfig caching (#351)<commit_after>// Copyright 2020 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 authorizedapp

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/google/exposure-notifications-server/internal/database"
	"github.com/google/exposure-notifications-server/internal/logging"
	"github.com/google/exposure-notifications-server/internal/secrets"
)

// Compile-time check to assert implementation.
var _ Provider = (*DatabaseProvider)(nil)

// DatabaseProvider is a Provider that pulls from the database and caches and
// refreshes values on failure.
type DatabaseProvider struct {
	database      *database.DB
	secretManager secrets.SecretManager
	cacheDuration time.Duration

	cache     map[string]*cacheItem
	cacheLock sync.RWMutex
}

type cacheItem struct {
	value    *database.AuthorizedApp
	cachedAt time.Time
}

// DatabaseProviderOption is used as input to the database provider.
type DatabaseProviderOption func(*DatabaseProvider) *DatabaseProvider

// WithSecretManager sets the secret manager for resolving secrets.
func WithSecretManager(sm secrets.SecretManager) DatabaseProviderOption {
	return func(p *DatabaseProvider) *DatabaseProvider {
		p.secretManager = sm
		return p
	}
}

// NewDatabaseProvider creates a new Provider that reads from a database.
func NewDatabaseProvider(ctx context.Context, db *database.DB, config *Config, opts ...DatabaseProviderOption) (Provider, error) {
	provider := &DatabaseProvider{
		database:      db,
		cacheDuration: config.CacheDuration,
		cache:         make(map[string]*cacheItem),
	}

	// Apply options.
	for _, opt := range opts {
		provider = opt(provider)
	}

	return provider, nil
}

// checkCache checks the local cache whthin a read lock.
// The bool on return is true if there was a hit (And an error is a valid hit)
// or false if there was a miss (or expiry) and the data source should be queried again.
func (p *DatabaseProvider) checkCache(name string) (*database.AuthorizedApp, bool, error) {
	// Acquire a read lock first, which allows concurrent readers, to check if
	// there's an item in the cache.
	p.cacheLock.RLock()
	defer p.cacheLock.RUnlock()

	item, ok := p.cache[name]
	if ok && time.Since(item.cachedAt) <= p.cacheDuration {
		if item.value == nil {
			return nil, true, AppNotFound
		}
		return item.value, true, nil
	}
	return nil, false, nil
}

// AppConfig returns the config for the given app package name.
func (p *DatabaseProvider) AppConfig(ctx context.Context, name string) (*database.AuthorizedApp, error) {
	logger := logging.FromContext(ctx)

	data, cacheHit, error := p.checkCache(name)
	if cacheHit {
		return data, error
	}

	// Acquire a more aggressive lock now because we're about to mutate. However,
	// it's possible that a concurrent routine has already mutated between our
	// read and write locks, so we have to check again.
	p.cacheLock.Lock()
	defer p.cacheLock.Unlock()
	item, ok := p.cache[name]
	if ok && time.Since(item.cachedAt) <= p.cacheDuration {
		if item.value == nil {
			return nil, AppNotFound
		}
		return item.value, nil
	}

	// Load config.
	config, err := p.loadAuthorizedAppFromDatabase(ctx, name)
	if err != nil {
		return nil, fmt.Errorf("authorizedapp: %w", err)
	}

	// Cache configs.
	logger.Infof("authorizedapp: loaded %v, caching for %s", name, p.cacheDuration)
	p.cache[name] = &cacheItem{
		value:    config,
		cachedAt: time.Now(),
	}

	// Handle not found.
	if config == nil {
		return nil, AppNotFound
	}

	// Returned config.
	return config, nil
}

// loadAuthorizedAppFromDatabase is a lower-level private API that actually loads and parses
// a single AuthorizedApp from the database.
func (p *DatabaseProvider) loadAuthorizedAppFromDatabase(ctx context.Context, name string) (*database.AuthorizedApp, error) {
	logger := logging.FromContext(ctx)

	logger.Infof("authorizedapp: loading %v from database", name)
	config, err := p.database.GetAuthorizedApp(ctx, p.secretManager, name)
	if err != nil {
		return nil, fmt.Errorf("failed to read %v from database: %w", name, err)
	}
	return config, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package aggregator

import (
	"encoding/json"
	"fmt"
	"math"
	"math/rand"
	"reflect"
	"regexp"
	"strings"
	"testing"
	"time"

	"github.com/go-openapi/spec"
	fuzz "github.com/google/gofuzz"
	"k8s.io/kube-openapi/pkg/util/sets"
)

func fuzzFuncs(f *fuzz.Fuzzer, refFunc func(ref *spec.Ref, c fuzz.Continue, visible bool)) {
	invisible := 0 // == 0 means visible, > 0 means invisible
	depth := 0
	maxDepth := 3
	nilChance := func(depth int) float64 {
		return math.Pow(0.9, math.Max(0.0, float64(maxDepth-depth)))
	}
	updateFuzzer := func(depth int) {
		f.NilChance(nilChance(depth))
		f.NumElements(0, max(0, maxDepth-depth))
	}
	updateFuzzer(depth)
	enter := func(o interface{}, recursive bool, c fuzz.Continue) {
		if recursive {
			depth++
			updateFuzzer(depth)
		}

		invisible++
		c.FuzzNoCustom(o)
		invisible--
	}
	leave := func(recursive bool) {
		if recursive {
			depth--
			updateFuzzer(depth)
		}
	}
	f.Funcs(
		func(ref *spec.Ref, c fuzz.Continue) {
			refFunc(ref, c, invisible == 0)
		},
		func(sa *spec.SchemaOrStringArray, c fuzz.Continue) {
			*sa = spec.SchemaOrStringArray{}
			if c.RandBool() {
				c.Fuzz(&sa.Schema)
			} else {
				c.Fuzz(&sa.Property)
			}
			if sa.Schema == nil && len(sa.Property) == 0 {
				*sa = spec.SchemaOrStringArray{Schema: &spec.Schema{}}
			}
		},
		func(url *spec.SchemaURL, c fuzz.Continue) {
			*url = spec.SchemaURL("http://url")
		},
		func(s *spec.Swagger, c fuzz.Continue) {
			enter(s, false, c)
			defer leave(false)

			// only fuzz those fields we walk into with invisible==false
			c.Fuzz(&s.Parameters)
			c.Fuzz(&s.Responses)
			c.Fuzz(&s.Definitions)
			c.Fuzz(&s.Paths)
		},
		func(p *spec.PathItem, c fuzz.Continue) {
			enter(p, false, c)
			defer leave(false)

			// only fuzz those fields we walk into with invisible==false
			c.Fuzz(&p.Parameters)
			c.Fuzz(&p.Delete)
			c.Fuzz(&p.Get)
			c.Fuzz(&p.Head)
			c.Fuzz(&p.Options)
			c.Fuzz(&p.Patch)
			c.Fuzz(&p.Post)
			c.Fuzz(&p.Put)
		},
		func(p *spec.Parameter, c fuzz.Continue) {
			enter(p, false, c)
			defer leave(false)

			// only fuzz those fields we walk into with invisible==false
			c.Fuzz(&p.Ref)
			c.Fuzz(&p.Schema)
			if c.RandBool() {
				p.Items = &spec.Items{}
				c.Fuzz(&p.Items.Ref)
			} else {
				p.Items = nil
			}
		},
		func(s *spec.Response, c fuzz.Continue) {
			enter(s, false, c)
			defer leave(false)

			// only fuzz those fields we walk into with invisible==false
			c.Fuzz(&s.Ref)
			c.Fuzz(&s.Description)
			c.Fuzz(&s.Schema)
			c.Fuzz(&s.Examples)
		},
		func(s *spec.Dependencies, c fuzz.Continue) {
			enter(s, false, c)
			defer leave(false)

			// and nothing with invisible==false
		},
		func(p *spec.SimpleSchema, c fuzz.Continue) {
			// gofuzz is broken and calls this even for *SimpleSchema fields, ignoring NilChance, leading to infinite recursion
			if c.Float64() > nilChance(depth) {
				return
			}

			enter(p, true, c)
			defer leave(true)

			c.FuzzNoCustom(p)
		},
		func(s *spec.SchemaProps, c fuzz.Continue) {
			// gofuzz is broken and calls this even for *SchemaProps fields, ignoring NilChance, leading to infinite recursion
			if c.Float64() > nilChance(depth) {
				return
			}

			enter(s, true, c)
			defer leave(true)

			c.FuzzNoCustom(s)
		},
		func(i *interface{}, c fuzz.Continue) {
			// do nothing for examples and defaults. These are free form JSON fields.
		},
	)
}

func TestReplaceReferences(t *testing.T) {
	visibleRE, err := regexp.Compile("\"\\$ref\":\"(http://ref-[^\"]*)\"")
	if err != nil {
		t.Fatalf("failed to compile ref regex: %v", err)
	}
	invisibleRE, err := regexp.Compile("\"\\$ref\":\"(http://invisible-[^\"]*)\"")
	if err != nil {
		t.Fatalf("failed to compile ref regex: %v", err)
	}

	for i := 0; i < 1000; i++ {
		var visibleRefs, invisibleRefs sets.String
		var seed int64
		var s *spec.Swagger
		for {
			visibleRefs = sets.NewString()
			invisibleRefs = sets.NewString()

			f := fuzz.New()
			seed = time.Now().UnixNano()
			//seed = int64(1548953540354558000)
			f.RandSource(rand.New(rand.NewSource(seed)))

			visibleRefsNum := 0
			invisibleRefsNum := 0
			fuzzFuncs(f,
				func(ref *spec.Ref, c fuzz.Continue, visible bool) {
					var url string
					if visible {
						// this is a ref that is seen by the walker (we have some exceptions where we don't walk into)
						url = fmt.Sprintf("http://ref-%d", visibleRefsNum)
						visibleRefsNum++
					} else {
						// this is a ref that is not seen by the walker (we have some exceptions where we don't walk into)
						url = fmt.Sprintf("http://invisible-%d", invisibleRefsNum)
						invisibleRefsNum++
					}

					r, err := spec.NewRef(url)
					if err != nil {
						t.Fatalf("failed to fuzz ref: %v", err)
					}
					*ref = r
				},
			)

			// create random swagger spec with random URL references, but at least one ref
			s = &spec.Swagger{}
			f.Fuzz(s)

			// clone spec to normalize (fuzz might generate objects which do not roundtrip json marshalling
			var err error
			s, err = cloneSwagger(s)
			if err != nil {
				t.Fatalf("failed to normalize swagger after fuzzing: %v", err)
			}

			// find refs
			bs, err := json.Marshal(s)
			if err != nil {
				t.Fatalf("failed to marshal swagger: %v", err)
			}
			for _, m := range invisibleRE.FindAllStringSubmatch(string(bs), -1) {
				invisibleRefs.Insert(m[1])
			}
			if res := visibleRE.FindAllStringSubmatch(string(bs), -1); len(res) > 0 {
				for _, m := range res {
					visibleRefs.Insert(m[1])
				}
				break
			}
		}

		t.Run(fmt.Sprintf("iteration %d", i), func(t *testing.T) {
			mutatedRef := visibleRefs.List()[rand.Intn(visibleRefs.Len())]
			origString, err := json.Marshal(s)
			if err != nil {
				t.Fatalf("failed to marshal swagger: %v", err)
			}
			t.Logf("created schema with %d walked refs, %d invisible refs, mutating %q, seed %d: %s", visibleRefs.Len(), invisibleRefs.Len(), mutatedRef, seed, string(origString))

			// convert to json string, replace one of the refs, and unmarshal back
			mutatedString := strings.Replace(string(origString), "\""+mutatedRef+"\"", "\"http://mutated-ref\"", -1)
			mutatedViaJSON := &spec.Swagger{}
			if err := json.Unmarshal([]byte(mutatedString), mutatedViaJSON); err != nil {
				t.Fatalf("failed to unmarshal mutated spec: %v", err)
			}

			// replay the same mutation using the mutating walker
			seenRefs := sets.NewString()
			walker := mutatingReferenceWalker{
				walkRefCallback: func(ref *spec.Ref) *spec.Ref {
					seenRefs.Insert(ref.String())
					if ref.String() == mutatedRef {
						r, err := spec.NewRef("http://mutated-ref")
						if err != nil {
							t.Fatalf("failed to create ref: %v", err)
						}
						return &r
					}
					return ref
				},
			}
			mutatedViaWalker := walker.Start(s)

			// compare that we got the same
			if !reflect.DeepEqual(mutatedViaJSON, mutatedViaWalker) {
				t.Errorf("mutation via walker differ from JSON text replacement (got A, expected B): %s", objectDiff(mutatedViaWalker, mutatedViaJSON))
			}
			if !seenRefs.HasAll(visibleRefs.List()...) {
				t.Errorf("expected to see the same refs in the walker as during fuzzing. Not seen: %v", visibleRefs.Difference(seenRefs).List())
			}
			if shouldNotSee := seenRefs.Intersection(invisibleRefs); shouldNotSee.Len() > 0 {
				t.Errorf("refs seen that the walker is not expected to see: %v", shouldNotSee.List())
			}
		})
	}
}

func cloneSwagger(orig *spec.Swagger) (*spec.Swagger, error) {
	bs, err := json.Marshal(orig)
	if err != nil {
		return nil, err
	}
	s := &spec.Swagger{}
	if err := json.Unmarshal(bs, s); err != nil {
		return nil, err
	}
	return s, nil
}

// stringDiff diffs a and b and returns a human readable diff.
func stringDiff(a, b string) string {
	ba := []byte(a)
	bb := []byte(b)
	out := []byte{}
	i := 0
	for ; i < len(ba) && i < len(bb); i++ {
		if ba[i] != bb[i] {
			break
		}
		out = append(out, ba[i])
	}
	out = append(out, []byte("\n\nA: ")...)
	out = append(out, ba[i:]...)
	out = append(out, []byte("\n\nB: ")...)
	out = append(out, bb[i:]...)
	out = append(out, []byte("\n\n")...)
	return string(out)
}

// objectDiff writes the two objects out as JSON and prints out the identical part of
// the objects followed by the remaining part of 'a' and finally the remaining part of 'b'.
// For debugging tests.
func objectDiff(a, b interface{}) string {
	ab, err := json.Marshal(a)
	if err != nil {
		panic(fmt.Sprintf("a: %v", err))
	}
	bb, err := json.Marshal(b)
	if err != nil {
		panic(fmt.Sprintf("b: %v", err))
	}
	return stringDiff(string(ab), string(bb))
}

func max(i, j int) int {
	if i > j {
		return i
	}
	return j
}
<commit_msg>aggregator: add subset mutation tests<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package aggregator

import (
	"encoding/json"
	"fmt"
	"math"
	"math/rand"
	"reflect"
	"regexp"
	"strings"
	"testing"
	"time"

	"github.com/go-openapi/spec"
	fuzz "github.com/google/gofuzz"
	"k8s.io/kube-openapi/pkg/util/sets"
)

func fuzzFuncs(f *fuzz.Fuzzer, refFunc func(ref *spec.Ref, c fuzz.Continue, visible bool)) {
	invisible := 0 // == 0 means visible, > 0 means invisible
	depth := 0
	maxDepth := 3
	nilChance := func(depth int) float64 {
		return math.Pow(0.9, math.Max(0.0, float64(maxDepth-depth)))
	}
	updateFuzzer := func(depth int) {
		f.NilChance(nilChance(depth))
		f.NumElements(0, max(0, maxDepth-depth))
	}
	updateFuzzer(depth)
	enter := func(o interface{}, recursive bool, c fuzz.Continue) {
		if recursive {
			depth++
			updateFuzzer(depth)
		}

		invisible++
		c.FuzzNoCustom(o)
		invisible--
	}
	leave := func(recursive bool) {
		if recursive {
			depth--
			updateFuzzer(depth)
		}
	}
	f.Funcs(
		func(ref *spec.Ref, c fuzz.Continue) {
			refFunc(ref, c, invisible == 0)
		},
		func(sa *spec.SchemaOrStringArray, c fuzz.Continue) {
			*sa = spec.SchemaOrStringArray{}
			if c.RandBool() {
				c.Fuzz(&sa.Schema)
			} else {
				c.Fuzz(&sa.Property)
			}
			if sa.Schema == nil && len(sa.Property) == 0 {
				*sa = spec.SchemaOrStringArray{Schema: &spec.Schema{}}
			}
		},
		func(url *spec.SchemaURL, c fuzz.Continue) {
			*url = spec.SchemaURL("http://url")
		},
		func(s *spec.Swagger, c fuzz.Continue) {
			enter(s, false, c)
			defer leave(false)

			// only fuzz those fields we walk into with invisible==false
			c.Fuzz(&s.Parameters)
			c.Fuzz(&s.Responses)
			c.Fuzz(&s.Definitions)
			c.Fuzz(&s.Paths)
		},
		func(p *spec.PathItem, c fuzz.Continue) {
			enter(p, false, c)
			defer leave(false)

			// only fuzz those fields we walk into with invisible==false
			c.Fuzz(&p.Parameters)
			c.Fuzz(&p.Delete)
			c.Fuzz(&p.Get)
			c.Fuzz(&p.Head)
			c.Fuzz(&p.Options)
			c.Fuzz(&p.Patch)
			c.Fuzz(&p.Post)
			c.Fuzz(&p.Put)
		},
		func(p *spec.Parameter, c fuzz.Continue) {
			enter(p, false, c)
			defer leave(false)

			// only fuzz those fields we walk into with invisible==false
			c.Fuzz(&p.Ref)
			c.Fuzz(&p.Schema)
			if c.RandBool() {
				p.Items = &spec.Items{}
				c.Fuzz(&p.Items.Ref)
			} else {
				p.Items = nil
			}
		},
		func(s *spec.Response, c fuzz.Continue) {
			enter(s, false, c)
			defer leave(false)

			// only fuzz those fields we walk into with invisible==false
			c.Fuzz(&s.Ref)
			c.Fuzz(&s.Description)
			c.Fuzz(&s.Schema)
			c.Fuzz(&s.Examples)
		},
		func(s *spec.Dependencies, c fuzz.Continue) {
			enter(s, false, c)
			defer leave(false)

			// and nothing with invisible==false
		},
		func(p *spec.SimpleSchema, c fuzz.Continue) {
			// gofuzz is broken and calls this even for *SimpleSchema fields, ignoring NilChance, leading to infinite recursion
			if c.Float64() > nilChance(depth) {
				return
			}

			enter(p, true, c)
			defer leave(true)

			c.FuzzNoCustom(p)
		},
		func(s *spec.SchemaProps, c fuzz.Continue) {
			// gofuzz is broken and calls this even for *SchemaProps fields, ignoring NilChance, leading to infinite recursion
			if c.Float64() > nilChance(depth) {
				return
			}

			enter(s, true, c)
			defer leave(true)

			c.FuzzNoCustom(s)
		},
		func(i *interface{}, c fuzz.Continue) {
			// do nothing for examples and defaults. These are free form JSON fields.
		},
	)
}

func TestReplaceReferences(t *testing.T) {
	visibleRE, err := regexp.Compile("\"\\$ref\":\"(http://ref-[^\"]*)\"")
	if err != nil {
		t.Fatalf("failed to compile ref regex: %v", err)
	}
	invisibleRE, err := regexp.Compile("\"\\$ref\":\"(http://invisible-[^\"]*)\"")
	if err != nil {
		t.Fatalf("failed to compile ref regex: %v", err)
	}

	for i := 0; i < 1000; i++ {
		var visibleRefs, invisibleRefs sets.String
		var seed int64
		var randSource rand.Source
		var s *spec.Swagger
		for {
			visibleRefs = sets.NewString()
			invisibleRefs = sets.NewString()

			f := fuzz.New()
			seed = time.Now().UnixNano()
			//seed = int64(1549012506261785182)
			randSource = rand.New(rand.NewSource(seed))
			f.RandSource(randSource)

			visibleRefsNum := 0
			invisibleRefsNum := 0
			fuzzFuncs(f,
				func(ref *spec.Ref, c fuzz.Continue, visible bool) {
					var url string
					if visible {
						// this is a ref that is seen by the walker (we have some exceptions where we don't walk into)
						url = fmt.Sprintf("http://ref-%d", visibleRefsNum)
						visibleRefsNum++
					} else {
						// this is a ref that is not seen by the walker (we have some exceptions where we don't walk into)
						url = fmt.Sprintf("http://invisible-%d", invisibleRefsNum)
						invisibleRefsNum++
					}

					r, err := spec.NewRef(url)
					if err != nil {
						t.Fatalf("failed to fuzz ref: %v", err)
					}
					*ref = r
				},
			)

			// create random swagger spec with random URL references, but at least one ref
			s = &spec.Swagger{}
			f.Fuzz(s)

			// clone spec to normalize (fuzz might generate objects which do not roundtrip json marshalling
			var err error
			s, err = cloneSwagger(s)
			if err != nil {
				t.Fatalf("failed to normalize swagger after fuzzing: %v", err)
			}

			// find refs
			bs, err := json.Marshal(s)
			if err != nil {
				t.Fatalf("failed to marshal swagger: %v", err)
			}
			for _, m := range invisibleRE.FindAllStringSubmatch(string(bs), -1) {
				invisibleRefs.Insert(m[1])
			}
			if res := visibleRE.FindAllStringSubmatch(string(bs), -1); len(res) > 0 {
				for _, m := range res {
					visibleRefs.Insert(m[1])
				}
				break
			}
		}

		t.Run(fmt.Sprintf("iteration %d", i), func(t *testing.T) {
			mutatedRefs := sets.NewString()
			mutationProbability := rand.New(randSource).Float64()
			for _, vr := range visibleRefs.List() {
				if rand.New(randSource).Float64() > mutationProbability {
					mutatedRefs.Insert(vr)
				}
			}

			origString, err := json.Marshal(s)
			if err != nil {
				t.Fatalf("failed to marshal swagger: %v", err)
			}
			t.Logf("created schema with %d walked refs, %d invisible refs, mutating %v, seed %d: %s", visibleRefs.Len(), invisibleRefs.Len(), mutatedRefs.List(), seed, string(origString))

			// convert to json string, replace one of the refs, and unmarshal back
			mutatedString := string(origString)
			for _, r := range mutatedRefs.List() {
				mr := strings.Replace(r, "ref", "mutated", -1)
				mutatedString = strings.Replace(mutatedString, "\""+r+"\"", "\""+mr+"\"", -1)
			}
			mutatedViaJSON := &spec.Swagger{}
			if err := json.Unmarshal([]byte(mutatedString), mutatedViaJSON); err != nil {
				t.Fatalf("failed to unmarshal mutated spec: %v", err)
			}

			// replay the same mutation using the mutating walker
			seenRefs := sets.NewString()
			walker := mutatingReferenceWalker{
				walkRefCallback: func(ref *spec.Ref) *spec.Ref {
					seenRefs.Insert(ref.String())
					if mutatedRefs.Has(ref.String()) {
						r, err := spec.NewRef(strings.Replace(ref.String(), "ref", "mutated", -1))
						if err != nil {
							t.Fatalf("failed to create ref: %v", err)
						}
						return &r
					}
					return ref
				},
			}
			mutatedViaWalker := walker.Start(s)

			// compare that we got the same
			if !reflect.DeepEqual(mutatedViaJSON, mutatedViaWalker) {
				t.Errorf("mutation via walker differ from JSON text replacement (got A, expected B): %s", objectDiff(mutatedViaWalker, mutatedViaJSON))
			}
			if !seenRefs.HasAll(visibleRefs.List()...) {
				t.Errorf("expected to see the same refs in the walker as during fuzzing. Not seen: %v", visibleRefs.Difference(seenRefs).List())
			}
			if shouldNotSee := seenRefs.Intersection(invisibleRefs); shouldNotSee.Len() > 0 {
				t.Errorf("refs seen that the walker is not expected to see: %v", shouldNotSee.List())
			}
		})
	}
}

func cloneSwagger(orig *spec.Swagger) (*spec.Swagger, error) {
	bs, err := json.Marshal(orig)
	if err != nil {
		return nil, err
	}
	s := &spec.Swagger{}
	if err := json.Unmarshal(bs, s); err != nil {
		return nil, err
	}
	return s, nil
}

// stringDiff diffs a and b and returns a human readable diff.
func stringDiff(a, b string) string {
	ba := []byte(a)
	bb := []byte(b)
	out := []byte{}
	i := 0
	for ; i < len(ba) && i < len(bb); i++ {
		if ba[i] != bb[i] {
			break
		}
		out = append(out, ba[i])
	}
	out = append(out, []byte("\n\nA: ")...)
	out = append(out, ba[i:]...)
	out = append(out, []byte("\n\nB: ")...)
	out = append(out, bb[i:]...)
	out = append(out, []byte("\n\n")...)
	return string(out)
}

// objectDiff writes the two objects out as JSON and prints out the identical part of
// the objects followed by the remaining part of 'a' and finally the remaining part of 'b'.
// For debugging tests.
func objectDiff(a, b interface{}) string {
	ab, err := json.Marshal(a)
	if err != nil {
		panic(fmt.Sprintf("a: %v", err))
	}
	bb, err := json.Marshal(b)
	if err != nil {
		panic(fmt.Sprintf("b: %v", err))
	}
	return stringDiff(string(ab), string(bb))
}

func max(i, j int) int {
	if i > j {
		return i
	}
	return j
}
<|endoftext|>


<commit_before>package broadcastwriter

import (
	"bytes"
	"io"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/pkg/jsonlog"
)

// BroadcastWriter accumulate multiple io.WriteCloser by stream.
type BroadcastWriter struct {
	sync.Mutex
	buf      *bytes.Buffer
	jsLogBuf *bytes.Buffer
	streams  map[string](map[io.WriteCloser]struct{})
}

// AddWriter adds new io.WriteCloser for stream.
// If stream is "", then all writes proceed as is. Otherwise every line from
// input will be packed to serialized jsonlog.JSONLog.
func (w *BroadcastWriter) AddWriter(writer io.WriteCloser, stream string) {
	w.Lock()
	if _, ok := w.streams[stream]; !ok {
		w.streams[stream] = make(map[io.WriteCloser]struct{})
	}
	w.streams[stream][writer] = struct{}{}
	w.Unlock()
}

// Write writes bytes to all writers. Failed writers will be evicted during
// this call.
func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
	created := time.Now().UTC()
	w.Lock()
	if writers, ok := w.streams[""]; ok {
		for sw := range writers {
			if n, err := sw.Write(p); err != nil || n != len(p) {
				// On error, evict the writer
				delete(writers, sw)
			}
		}
	}
	if w.jsLogBuf == nil {
		w.jsLogBuf = new(bytes.Buffer)
		w.jsLogBuf.Grow(1024)
	}
	w.buf.Write(p)
	for {
		line, err := w.buf.ReadString('\n')
		if err != nil {
			w.buf.WriteString(line)
			break
		}
		for stream, writers := range w.streams {
			if stream == "" {
				continue
			}
			jsonLog := jsonlog.JSONLog{Log: line, Stream: stream, Created: created}
			err = jsonLog.MarshalJSONBuf(w.jsLogBuf)
			if err != nil {
				logrus.Errorf("Error making JSON log line: %s", err)
				continue
			}
			w.jsLogBuf.WriteByte('\n')
			b := w.jsLogBuf.Bytes()
			for sw := range writers {
				if _, err := sw.Write(b); err != nil {
					delete(writers, sw)
				}
			}
		}
		w.jsLogBuf.Reset()
	}
	w.jsLogBuf.Reset()
	w.Unlock()
	return len(p), nil
}

// Clean closes and removes all writers. Last non-eol-terminated part of data
// will be saved.
func (w *BroadcastWriter) Clean() error {
	w.Lock()
	for _, writers := range w.streams {
		for w := range writers {
			w.Close()
		}
	}
	w.streams = make(map[string](map[io.WriteCloser]struct{}))
	w.Unlock()
	return nil
}

func New() *BroadcastWriter {
	return &BroadcastWriter{
		streams: make(map[string](map[io.WriteCloser]struct{})),
		buf:     bytes.NewBuffer(nil),
	}
}
<commit_msg>pkg/broadcastwriter: use []byte to lower alloc<commit_after>package broadcastwriter

import (
	"bytes"
	"io"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/pkg/jsonlog"
	"github.com/docker/docker/pkg/timeutils"
)

// BroadcastWriter accumulate multiple io.WriteCloser by stream.
type BroadcastWriter struct {
	sync.Mutex
	buf      *bytes.Buffer
	jsLogBuf *bytes.Buffer
	streams  map[string](map[io.WriteCloser]struct{})
}

// AddWriter adds new io.WriteCloser for stream.
// If stream is "", then all writes proceed as is. Otherwise every line from
// input will be packed to serialized jsonlog.JSONLog.
func (w *BroadcastWriter) AddWriter(writer io.WriteCloser, stream string) {
	w.Lock()
	if _, ok := w.streams[stream]; !ok {
		w.streams[stream] = make(map[io.WriteCloser]struct{})
	}
	w.streams[stream][writer] = struct{}{}
	w.Unlock()
}

// Write writes bytes to all writers. Failed writers will be evicted during
// this call.
func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
	var timestamp string
	created := time.Now().UTC()
	w.Lock()
	if writers, ok := w.streams[""]; ok {
		for sw := range writers {
			if n, err := sw.Write(p); err != nil || n != len(p) {
				// On error, evict the writer
				delete(writers, sw)
			}
		}
	}
	if w.jsLogBuf == nil {
		w.jsLogBuf = new(bytes.Buffer)
		w.jsLogBuf.Grow(1024)
	}
	w.buf.Write(p)
	for {
		if n := w.buf.Len(); n == 0 {
			break
		}
		i := bytes.IndexByte(w.buf.Bytes(), '\n')
		if i < 0 {
			break
		}
		lineBytes := w.buf.Next(i + 1)
		if timestamp == "" {
			timestamp, err = timeutils.FastMarshalJSON(created)
			if err != nil {
				continue
			}
		}

		for stream, writers := range w.streams {
			if stream == "" {
				continue
			}
			jsonLog := jsonlog.JSONLogBytes{Log: lineBytes, Stream: stream, Created: timestamp}
			err = jsonLog.MarshalJSONBuf(w.jsLogBuf)
			if err != nil {
				logrus.Errorf("Error making JSON log line: %s", err)
				continue
			}
			w.jsLogBuf.WriteByte('\n')
			b := w.jsLogBuf.Bytes()
			for sw := range writers {
				if _, err := sw.Write(b); err != nil {
					delete(writers, sw)
				}
			}
		}
		w.jsLogBuf.Reset()
	}
	w.jsLogBuf.Reset()
	w.Unlock()
	return len(p), nil
}

// Clean closes and removes all writers. Last non-eol-terminated part of data
// will be saved.
func (w *BroadcastWriter) Clean() error {
	w.Lock()
	for _, writers := range w.streams {
		for w := range writers {
			w.Close()
		}
	}
	w.streams = make(map[string](map[io.WriteCloser]struct{}))
	w.Unlock()
	return nil
}

func New() *BroadcastWriter {
	return &BroadcastWriter{
		streams: make(map[string](map[io.WriteCloser]struct{})),
		buf:     bytes.NewBuffer(nil),
	}
}
<|endoftext|>


<commit_before>// Copyright 2021 the Exposure Notifications Verification Server 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 userreport

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/google/exposure-notifications-verification-server/internal/project"
	"github.com/google/exposure-notifications-verification-server/pkg/controller/issueapi"
	"github.com/google/exposure-notifications-verification-server/pkg/database"
)

func TestBuildAndSignPayloadForWebhook(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name    string
		secret  string
		body    interface{}
		expBody string
		expMac  string
	}{
		{
			name:    "empty",
			secret:  "",
			body:    struct{}{},
			expBody: "{}",
			expMac:  "bc7b0c6253e31736a26b597695004434377f48ccf1c5b97a44870c8c929495465b6693b4a7097a8ac6b8ee2f744f4ba6f6b52fcdb74cd5a4ec5611a89024b1f9",
		},
		{
			name:    "with_secret",
			secret:  "foobarbaz",
			body:    struct{}{},
			expBody: "{}",
			expMac:  "e0baf34f99c5eadd808ff0d34326a634a8d8277f1f8839876d6fe7908c6849d559ca3593e16ca68106f93a7f791fec7bcf2d702a6036d84959df32de33f6a1b6",
		},
	}

	for _, tc := range cases {
		tc := tc

		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			b, mac, err := buildAndSignPayloadForWebhook(tc.secret, tc.body)
			if err != nil {
				t.Fatal(err)
			}

			if got, want := string(b), tc.expBody; got != want {
				t.Errorf("expected %q to be %q", got, want)
			}

			if got, want := mac, tc.expMac; got != want {
				t.Errorf("expected %q to be %q", got, want)
			}
		})
	}
}

func TestSendWebhookRequest(t *testing.T) {
	t.Parallel()

	ctx := project.TestContext(t)

	client := &http.Client{
		Timeout: 2 * time.Second,
	}

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()

		if v := r.Header.Get("Content-Type"); v != "application/json" {
			t.Errorf("expected content-type to be application/json: %q", w.Header())
		}

		if r.Header.Get("X-Signature") == "" {
			t.Errorf("expected signature to be present")
		}

		var m map[string]interface{}
		if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
			t.Errorf("bad json: %s", err)
		}

		if got, want := m["generatedSMS"], "TODO"; got != want {
			t.Errorf("expected %q to be %q: %v", got, want, m)
		}
		if got, want := m["phone"], "+15005550000"; got != want {
			t.Errorf("expected %q to be %q: %v", got, want, m)
		}
		if got, want := m["code"], "abcd1234"; got != want {
			t.Errorf("expected %q to be %q: %v", got, want, m)
		}
		if _, ok := m["uuid"]; !ok {
			t.Errorf("expected uuid to be present: %v", m)
		}

		w.WriteHeader(http.StatusOK)
	}))
	t.Cleanup(func() {
		srv.Close()
	})

	realm := &database.Realm{
		UserReportWebhookURL:    srv.URL,
		UserReportWebhookSecret: "super-secret",
	}

	if err := sendWebhookRequest(ctx, client, realm, &issueapi.IssueResult{
		VerCode: &database.VerificationCode{
			RealmID:     realm.ID,
			PhoneNumber: "+15005550000",
			Code:        "abcd1234",
		},
		GeneratedSMS: "TODO",
	}); err != nil {
		t.Fatal(err)
	}
}
<commit_msg>Clean up some TODOs (#2216)<commit_after>// Copyright 2021 the Exposure Notifications Verification Server 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 userreport

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/google/exposure-notifications-verification-server/internal/project"
	"github.com/google/exposure-notifications-verification-server/pkg/controller/issueapi"
	"github.com/google/exposure-notifications-verification-server/pkg/database"
)

func TestBuildAndSignPayloadForWebhook(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name    string
		secret  string
		body    interface{}
		expBody string
		expMac  string
	}{
		{
			name:    "empty",
			secret:  "",
			body:    struct{}{},
			expBody: "{}",
			expMac:  "bc7b0c6253e31736a26b597695004434377f48ccf1c5b97a44870c8c929495465b6693b4a7097a8ac6b8ee2f744f4ba6f6b52fcdb74cd5a4ec5611a89024b1f9",
		},
		{
			name:    "with_secret",
			secret:  "foobarbaz",
			body:    struct{}{},
			expBody: "{}",
			expMac:  "e0baf34f99c5eadd808ff0d34326a634a8d8277f1f8839876d6fe7908c6849d559ca3593e16ca68106f93a7f791fec7bcf2d702a6036d84959df32de33f6a1b6",
		},
	}

	for _, tc := range cases {
		tc := tc

		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			b, mac, err := buildAndSignPayloadForWebhook(tc.secret, tc.body)
			if err != nil {
				t.Fatal(err)
			}

			if got, want := string(b), tc.expBody; got != want {
				t.Errorf("expected %q to be %q", got, want)
			}

			if got, want := mac, tc.expMac; got != want {
				t.Errorf("expected %q to be %q", got, want)
			}
		})
	}
}

func TestSendWebhookRequest(t *testing.T) {
	t.Parallel()

	ctx := project.TestContext(t)

	client := &http.Client{
		Timeout: 2 * time.Second,
	}

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()

		if v := r.Header.Get("Content-Type"); v != "application/json" {
			t.Errorf("expected content-type to be application/json: %q", w.Header())
		}

		if r.Header.Get("X-Signature") == "" {
			t.Errorf("expected signature to be present")
		}

		var m map[string]interface{}
		if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
			t.Errorf("bad json: %s", err)
		}

		if got, want := m["generatedSMS"], "this is a test message"; got != want {
			t.Errorf("expected %q to be %q: %v", got, want, m)
		}
		if got, want := m["phone"], "+15005550000"; got != want {
			t.Errorf("expected %q to be %q: %v", got, want, m)
		}
		if got, want := m["code"], "abcd1234"; got != want {
			t.Errorf("expected %q to be %q: %v", got, want, m)
		}
		if _, ok := m["uuid"]; !ok {
			t.Errorf("expected uuid to be present: %v", m)
		}

		w.WriteHeader(http.StatusOK)
	}))
	t.Cleanup(func() {
		srv.Close()
	})

	realm := &database.Realm{
		UserReportWebhookURL:    srv.URL,
		UserReportWebhookSecret: "super-secret",
	}

	if err := sendWebhookRequest(ctx, client, realm, &issueapi.IssueResult{
		VerCode: &database.VerificationCode{
			RealmID:     realm.ID,
			PhoneNumber: "+15005550000",
			Code:        "abcd1234",
		},
		GeneratedSMS: "this is a test message",
	}); err != nil {
		t.Fatal(err)
	}
}
<|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 grafeas

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"net"
	"strings"
	"time"

	"github.com/grafeas/kritis/pkg/kritis/cryptolib"
	"google.golang.org/genproto/googleapis/devtools/containeranalysis/v1beta1/discovery"

	"google.golang.org/grpc/credentials"

	kritisv1beta1 "github.com/grafeas/kritis/pkg/kritis/apis/kritis/v1beta1"
	"github.com/grafeas/kritis/pkg/kritis/constants"
	"github.com/grafeas/kritis/pkg/kritis/metadata"
	"github.com/grafeas/kritis/pkg/kritis/secrets"
	"github.com/grafeas/kritis/pkg/kritis/util"
	"golang.org/x/net/context"
	"google.golang.org/genproto/googleapis/devtools/containeranalysis/v1beta1/attestation"
	"google.golang.org/genproto/googleapis/devtools/containeranalysis/v1beta1/grafeas"
	"google.golang.org/grpc"
)

const (
	PkgVulnerability     = "PACKAGE_VULNERABILITY"
	AttestationAuthority = "ATTESTATION_AUTHORITY"
	DefaultProject       = "kritis" // DefaultProject is the default project name, only single project is supported
)

// Client implements the ReadWriteClient and ReadOnlyClient interfaces using grafeas API.
type Client struct {
	client grafeas.GrafeasV1Beta1Client
	ctx    context.Context
}

// ValidateConfig checks whether the specified configuration is valid
func ValidateConfig(config kritisv1beta1.GrafeasConfigSpec) error {
	if config.Addr == "" {
		return fmt.Errorf("missing Grafeas address")
	}
	if strings.HasPrefix(config.Addr, "/") { // Unix socket address
		return nil
	}
	return nil
}

// TODO: separate constructor methods for r/w and r/o clients
func New(config kritisv1beta1.GrafeasConfigSpec, certs *CertConfig) (*Client, error) {
	if err := ValidateConfig(config); err != nil {
		return nil, err
	}
	ctx := context.Background()
	var conn *grpc.ClientConn
	if strings.HasPrefix(config.Addr, "/") {
		var err error
		conn, err = grpc.Dial(config.Addr,
			grpc.WithInsecure(),
			grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
				return net.DialTimeout("unix", addr, timeout)
			}))
		if err != nil {
			return nil, err
		}
	} else {
		certificate, err := tls.LoadX509KeyPair(certs.CertFile, certs.KeyFile)
		if err != nil {
			return nil, fmt.Errorf("could not load client key pair: %s", err)
		}
		certPool := x509.NewCertPool()
		ca, err := ioutil.ReadFile(certs.CAFile)
		if err != nil {
			return nil, fmt.Errorf("could not read ca certificate: %s", err)
		}

		if ok := certPool.AppendCertsFromPEM(ca); !ok {
			return nil, fmt.Errorf("failed to append ca certs")
		}
		server, _, err := net.SplitHostPort(config.Addr)
		if err != nil {
			return nil, err
		}
		creds := credentials.NewTLS(&tls.Config{
			ServerName:   server,
			Certificates: []tls.Certificate{certificate},
			RootCAs:      certPool,
		})
		conn, err = grpc.Dial(config.Addr, grpc.WithTransportCredentials(creds))
		if err != nil {
			return nil, err
		}
	}
	return &Client{
		client: grafeas.NewGrafeasV1Beta1Client(conn),
		ctx:    ctx,
	}, nil
}

// Close closes client connections
func (c Client) Close() {
	// Not Implemented.
	// grafeas.GrafeasV1Beta1Client does not expose Close() method for conn.
}

// Vulnerabilities gets Package Vulnerabilities Occurrences for a specified image.
func (c Client) Vulnerabilities(containerImage string) ([]metadata.Vulnerability, error) {
	occs, err := c.fetchVulnerabilityOccurrence(containerImage, PkgVulnerability)
	if err != nil {
		return nil, err
	}
	var vulnz []metadata.Vulnerability
	for _, occ := range occs {
		if v := metadata.GetVulnerabilityFromOccurrence(occ); v != nil {
			vulnz = append(vulnz, *v)
		}
	}
	return vulnz, nil
}

// Attestations gets Attestations for a specified image and a specified AttestationAuthority.
func (c Client) Attestations(containerImage string, aa *kritisv1beta1.AttestationAuthority) ([]cryptolib.Attestation, error) {
	occs, err := c.fetchAttestationOccurrence(containerImage, AttestationAuthority, aa)
	if err != nil {
		return nil, err
	}

	atts := []cryptolib.Attestation{}
	for _, occ := range occs {
		att, err := metadata.GetAttestationsFromOccurrence(occ)
		if err != nil {
			return nil, err
		}
		atts = append(atts, att...)
	}

	return atts, nil
}

// CreateAttestationNote creates an attestation note from AttestationAuthority
func (c Client) CreateAttestationNote(aa *kritisv1beta1.AttestationAuthority) (*grafeas.Note, error) {
	noteProject, noteId, err := metadata.ParseNoteReference(aa.Spec.NoteReference)
	if err != nil {
		return nil, err
	}

	aaNote := &attestation.Authority{
		Hint: &attestation.Authority_Hint{
			HumanReadableName: aa.Name,
		},
	}
	note := grafeas.Note{
		Name:             aa.Spec.NoteReference,
		ShortDescription: fmt.Sprintf("Image Policy Security Attestor"),
		LongDescription:  fmt.Sprintf("Image Policy Security Attestor deployed in %s namespace", aa.Namespace),
		Type: &grafeas.Note_AttestationAuthority{
			AttestationAuthority: aaNote,
		},
	}

	req := &grafeas.CreateNoteRequest{
		Note:   &note,
		NoteId: noteId,
		Parent: fmt.Sprintf("projects/%s", noteProject),
	}
	return c.client.CreateNote(c.ctx, req)
}

// AttestationNote returns a note if it exists for given AttestationAuthority
func (c Client) AttestationNote(aa *kritisv1beta1.AttestationAuthority) (*grafeas.Note, error) {
	req := &grafeas.GetNoteRequest{
		Name: aa.Spec.NoteReference,
	}
	return c.client.GetNote(c.ctx, req)
}

// CreateAttestationOccurrence creates an Attestation occurrence for a given image, secret, and project.
func (c Client) CreateAttestationOccurrence(noteName string, containerImage string, pgpSigningKey *secrets.PGPSigningSecret, proj string) (*grafeas.Occurrence, error) {
	fingerprint := util.GetAttestationKeyFingerprint(pgpSigningKey)

	// Create Attestation Signature
	sig, err := util.CreateAttestationSignature(containerImage, pgpSigningKey)
	if err != nil {
		return nil, err
	}
	pgpSignedAttestation := &attestation.PgpSignedAttestation{
		Signature: sig,
		KeyId: &attestation.PgpSignedAttestation_PgpKeyId{
			PgpKeyId: fingerprint,
		},
		ContentType: attestation.PgpSignedAttestation_SIMPLE_SIGNING_JSON,
	}

	attestationDetails := &grafeas.Occurrence_Attestation{
		Attestation: &attestation.Details{
			Attestation: &attestation.Attestation{
				Signature: &attestation.Attestation_PgpSignedAttestation{
					PgpSignedAttestation: pgpSignedAttestation,
				}},
		},
	}
	occ := &grafeas.Occurrence{
		Resource: util.GetResource(containerImage),
		NoteName: noteName,
		Details:  attestationDetails,
	}
	// Create the AttestationAuthority Occurrence in the Project AttestationAuthority Note.
	req := &grafeas.CreateOccurrenceRequest{
		Occurrence: occ,
		Parent:     fmt.Sprintf("projects/%s", proj),
	}
	return c.client.CreateOccurrence(c.ctx, req)
}

func (c Client) fetchVulnerabilityOccurrence(containerImage string, kind string) ([]*grafeas.Occurrence, error) {
	req := &grafeas.ListOccurrencesRequest{
		Filter:   fmt.Sprintf("resource_url=%q AND kind=%q", util.GetResourceURL(containerImage), kind),
		PageSize: constants.PageSize,
		Parent:   fmt.Sprintf("projects/%s", DefaultProject),
	}
	var occs []*grafeas.Occurrence
	var nextPageToken string
	for {
		req.PageToken = nextPageToken
		resp, err := c.client.ListOccurrences(c.ctx, req)
		if err != nil {
			return nil, err
		}
		occs = append(occs, resp.Occurrences...)
		nextPageToken = resp.NextPageToken
		if len(occs) == 0 || nextPageToken == "" {
			break
		}
	}
	return occs, nil
}

func (c Client) fetchAttestationOccurrence(containerImage string, kind string, aa *kritisv1beta1.AttestationAuthority) ([]*grafeas.Occurrence, error) {
	req := &grafeas.ListNoteOccurrencesRequest{
		Name:     aa.Spec.NoteReference,
		Filter:   fmt.Sprintf("resource_url=%q AND kind=%q", util.GetResourceURL(containerImage), kind),
		PageSize: constants.PageSize,
	}
	var occs []*grafeas.Occurrence
	var nextPageToken string
	for {
		req.PageToken = nextPageToken
		resp, err := c.client.ListNoteOccurrences(c.ctx, req)
		if err != nil {
			return nil, err
		}
		occs = append(occs, resp.Occurrences...)
		nextPageToken = resp.NextPageToken
		if len(occs) == 0 || nextPageToken == "" {
			break
		}
	}
	return occs, nil
}

func getProjectFromContainerImage(image string) string {
	tok := strings.Split(image, "/")
	if len(tok) < 2 {
		return ""
	}
	return tok[1]
}

// Poll discovery occurrence for an image and wait until container analysis
// finishes. Throws an error if analysis is not successful or timeouts.
func (c Client) WaitForVulnzAnalysis(containerImage string, timeout time.Duration) error {
	// resourceURL := fmt.Sprintf("https://gcr.io/my-project/my-image")
	// timeout := time.Duration(5) * time.Second

	// Backoff time between tries, exponentially grows after each failure.
	nextTryWait := 1
	// Timeout clock.
	timeoutTimer := time.NewTimer(timeout)
	defer timeoutTimer.Stop()

	// Find the discovery occurrence using a filter string.
	var discoveryOccurrence *grafeas.Occurrence
	for {
		// Waiting for discovery occurrence to appear.
		if discoveryOccurrence == nil {
			req := &grafeas.ListOccurrencesRequest{
				Parent: fmt.Sprintf("projects/%s", getProjectFromContainerImage(containerImage)),
				// Vulnerability discovery occurrences are always associated with the
				// PACKAGE_VULNERABILITY note.
				Filter: fmt.Sprintf(`resourceUrl=%q AND noteProjectId="%q" AND noteId="PACKAGE_VULNERABILITY"`, DefaultProject, util.GetResourceURL(containerImage)),
			}

			// There should be only one discovery occurrence.
			resp, err := c.client.ListOccurrences(c.ctx, req)
			if err != nil {
				return err
			}
			if len(resp.Occurrences) == 0 {
				continue
			}
			if resp.Occurrences[0].GetDiscovered() != nil {
				discoveryOccurrence = resp.Occurrences[0]
			}
		}

		// Update analysis status and check.
		if discoveryOccurrence != nil {
			// Update the occurrence.
			req := &grafeas.GetOccurrenceRequest{Name: discoveryOccurrence.GetName()}
			updated, err := c.client.GetOccurrence(c.ctx, req)
			if err != nil {
				return fmt.Errorf("GetOccurrence: %v", err)
			}
			switch updated.GetDiscovered().GetDiscovered().GetAnalysisStatus() {
			case discovery.Discovered_FINISHED_SUCCESS:
				return nil
			case discovery.Discovered_FINISHED_FAILED:
				return fmt.Errorf("container analysis has finished unsuccessfully")
			case discovery.Discovered_FINISHED_UNSUPPORTED:
				return fmt.Errorf("container analysis resource is known not to be supported")
			}
		}

		select {
		case <-timeoutTimer.C:
			return fmt.Errorf("timeout while retrieving discovery occurrence")
		case <-time.Tick(time.Duration(nextTryWait)):
			// exponential backoff
			nextTryWait = nextTryWait * 2
		}
	}
}
<commit_msg>Fix link in grafeas.go<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 grafeas

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"net"
	"strings"
	"time"

	"github.com/grafeas/kritis/pkg/kritis/cryptolib"
	"google.golang.org/genproto/googleapis/devtools/containeranalysis/v1beta1/discovery"

	"google.golang.org/grpc/credentials"

	kritisv1beta1 "github.com/grafeas/kritis/pkg/kritis/apis/kritis/v1beta1"
	"github.com/grafeas/kritis/pkg/kritis/constants"
	"github.com/grafeas/kritis/pkg/kritis/metadata"
	"github.com/grafeas/kritis/pkg/kritis/secrets"
	"github.com/grafeas/kritis/pkg/kritis/util"
	"golang.org/x/net/context"
	"google.golang.org/genproto/googleapis/devtools/containeranalysis/v1beta1/attestation"
	"google.golang.org/genproto/googleapis/devtools/containeranalysis/v1beta1/grafeas"
	"google.golang.org/grpc"
)

const (
	PkgVulnerability     = "PACKAGE_VULNERABILITY"
	AttestationAuthority = "ATTESTATION_AUTHORITY"
	DefaultProject       = "kritis" // DefaultProject is the default project name, only single project is supported
)

// Client implements the ReadWriteClient and ReadOnlyClient interfaces using grafeas API.
type Client struct {
	client grafeas.GrafeasV1Beta1Client
	ctx    context.Context
}

// ValidateConfig checks whether the specified configuration is valid
func ValidateConfig(config kritisv1beta1.GrafeasConfigSpec) error {
	if config.Addr == "" {
		return fmt.Errorf("missing Grafeas address")
	}
	if strings.HasPrefix(config.Addr, "/") { // Unix socket address
		return nil
	}
	return nil
}

// TODO: separate constructor methods for r/w and r/o clients
func New(config kritisv1beta1.GrafeasConfigSpec, certs *CertConfig) (*Client, error) {
	if err := ValidateConfig(config); err != nil {
		return nil, err
	}
	ctx := context.Background()
	var conn *grpc.ClientConn
	if strings.HasPrefix(config.Addr, "/") {
		var err error
		conn, err = grpc.Dial(config.Addr,
			grpc.WithInsecure(),
			grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
				return net.DialTimeout("unix", addr, timeout)
			}))
		if err != nil {
			return nil, err
		}
	} else {
		certificate, err := tls.LoadX509KeyPair(certs.CertFile, certs.KeyFile)
		if err != nil {
			return nil, fmt.Errorf("could not load client key pair: %s", err)
		}
		certPool := x509.NewCertPool()
		ca, err := ioutil.ReadFile(certs.CAFile)
		if err != nil {
			return nil, fmt.Errorf("could not read ca certificate: %s", err)
		}

		if ok := certPool.AppendCertsFromPEM(ca); !ok {
			return nil, fmt.Errorf("failed to append ca certs")
		}
		server, _, err := net.SplitHostPort(config.Addr)
		if err != nil {
			return nil, err
		}
		creds := credentials.NewTLS(&tls.Config{
			ServerName:   server,
			Certificates: []tls.Certificate{certificate},
			RootCAs:      certPool,
		})
		conn, err = grpc.Dial(config.Addr, grpc.WithTransportCredentials(creds))
		if err != nil {
			return nil, err
		}
	}
	return &Client{
		client: grafeas.NewGrafeasV1Beta1Client(conn),
		ctx:    ctx,
	}, nil
}

// Close closes client connections
func (c Client) Close() {
	// Not Implemented.
	// grafeas.GrafeasV1Beta1Client does not expose Close() method for conn.
}

// Vulnerabilities gets Package Vulnerabilities Occurrences for a specified image.
func (c Client) Vulnerabilities(containerImage string) ([]metadata.Vulnerability, error) {
	occs, err := c.fetchVulnerabilityOccurrence(containerImage, PkgVulnerability)
	if err != nil {
		return nil, err
	}
	var vulnz []metadata.Vulnerability
	for _, occ := range occs {
		if v := metadata.GetVulnerabilityFromOccurrence(occ); v != nil {
			vulnz = append(vulnz, *v)
		}
	}
	return vulnz, nil
}

// Attestations gets Attestations for a specified image and a specified AttestationAuthority.
func (c Client) Attestations(containerImage string, aa *kritisv1beta1.AttestationAuthority) ([]cryptolib.Attestation, error) {
	occs, err := c.fetchAttestationOccurrence(containerImage, AttestationAuthority, aa)
	if err != nil {
		return nil, err
	}

	atts := []cryptolib.Attestation{}
	for _, occ := range occs {
		att, err := metadata.GetAttestationsFromOccurrence(occ)
		if err != nil {
			return nil, err
		}
		atts = append(atts, att...)
	}

	return atts, nil
}

// CreateAttestationNote creates an attestation note from AttestationAuthority
func (c Client) CreateAttestationNote(aa *kritisv1beta1.AttestationAuthority) (*grafeas.Note, error) {
	noteProject, noteId, err := metadata.ParseNoteReference(aa.Spec.NoteReference)
	if err != nil {
		return nil, err
	}

	aaNote := &attestation.Authority{
		Hint: &attestation.Authority_Hint{
			HumanReadableName: aa.Name,
		},
	}
	note := grafeas.Note{
		Name:             aa.Spec.NoteReference,
		ShortDescription: fmt.Sprintf("Image Policy Security Attestor"),
		LongDescription:  fmt.Sprintf("Image Policy Security Attestor deployed in %s namespace", aa.Namespace),
		Type: &grafeas.Note_AttestationAuthority{
			AttestationAuthority: aaNote,
		},
	}

	req := &grafeas.CreateNoteRequest{
		Note:   &note,
		NoteId: noteId,
		Parent: fmt.Sprintf("projects/%s", noteProject),
	}
	return c.client.CreateNote(c.ctx, req)
}

// AttestationNote returns a note if it exists for given AttestationAuthority
func (c Client) AttestationNote(aa *kritisv1beta1.AttestationAuthority) (*grafeas.Note, error) {
	req := &grafeas.GetNoteRequest{
		Name: aa.Spec.NoteReference,
	}
	return c.client.GetNote(c.ctx, req)
}

// CreateAttestationOccurrence creates an Attestation occurrence for a given image, secret, and project.
func (c Client) CreateAttestationOccurrence(noteName string, containerImage string, pgpSigningKey *secrets.PGPSigningSecret, proj string) (*grafeas.Occurrence, error) {
	fingerprint := util.GetAttestationKeyFingerprint(pgpSigningKey)

	// Create Attestation Signature
	sig, err := util.CreateAttestationSignature(containerImage, pgpSigningKey)
	if err != nil {
		return nil, err
	}
	pgpSignedAttestation := &attestation.PgpSignedAttestation{
		Signature: sig,
		KeyId: &attestation.PgpSignedAttestation_PgpKeyId{
			PgpKeyId: fingerprint,
		},
		ContentType: attestation.PgpSignedAttestation_SIMPLE_SIGNING_JSON,
	}

	attestationDetails := &grafeas.Occurrence_Attestation{
		Attestation: &attestation.Details{
			Attestation: &attestation.Attestation{
				Signature: &attestation.Attestation_PgpSignedAttestation{
					PgpSignedAttestation: pgpSignedAttestation,
				}},
		},
	}
	occ := &grafeas.Occurrence{
		Resource: util.GetResource(containerImage),
		NoteName: noteName,
		Details:  attestationDetails,
	}
	// Create the AttestationAuthority Occurrence in the Project AttestationAuthority Note.
	req := &grafeas.CreateOccurrenceRequest{
		Occurrence: occ,
		Parent:     fmt.Sprintf("projects/%s", proj),
	}
	return c.client.CreateOccurrence(c.ctx, req)
}

func (c Client) fetchVulnerabilityOccurrence(containerImage string, kind string) ([]*grafeas.Occurrence, error) {
	req := &grafeas.ListOccurrencesRequest{
		Filter:   fmt.Sprintf("resource_url=%q AND kind=%q", util.GetResourceURL(containerImage), kind),
		PageSize: constants.PageSize,
		Parent:   fmt.Sprintf("projects/%s", DefaultProject),
	}
	var occs []*grafeas.Occurrence
	var nextPageToken string
	for {
		req.PageToken = nextPageToken
		resp, err := c.client.ListOccurrences(c.ctx, req)
		if err != nil {
			return nil, err
		}
		occs = append(occs, resp.Occurrences...)
		nextPageToken = resp.NextPageToken
		if len(occs) == 0 || nextPageToken == "" {
			break
		}
	}
	return occs, nil
}

func (c Client) fetchAttestationOccurrence(containerImage string, kind string, aa *kritisv1beta1.AttestationAuthority) ([]*grafeas.Occurrence, error) {
	req := &grafeas.ListNoteOccurrencesRequest{
		Name:     aa.Spec.NoteReference,
		Filter:   fmt.Sprintf("resource_url=%q AND kind=%q", util.GetResourceURL(containerImage), kind),
		PageSize: constants.PageSize,
	}
	var occs []*grafeas.Occurrence
	var nextPageToken string
	for {
		req.PageToken = nextPageToken
		resp, err := c.client.ListNoteOccurrences(c.ctx, req)
		if err != nil {
			return nil, err
		}
		occs = append(occs, resp.Occurrences...)
		nextPageToken = resp.NextPageToken
		if len(occs) == 0 || nextPageToken == "" {
			break
		}
	}
	return occs, nil
}

func getProjectFromContainerImage(image string) string {
	tok := strings.Split(image, "/")
	if len(tok) < 2 {
		return ""
	}
	return tok[1]
}

// Poll discovery occurrence for an image and wait until container analysis
// finishes. Throws an error if analysis is not successful or timeouts.
func (c Client) WaitForVulnzAnalysis(containerImage string, timeout time.Duration) error {
	// resourceURL := fmt.Sprintf("https://gcr.io/my-project/my-image")
	// timeout := time.Duration(5) * time.Second

	// Backoff time between tries, exponentially grows after each failure.
	nextTryWait := 1
	// Timeout clock.
	timeoutTimer := time.NewTimer(timeout)
	defer timeoutTimer.Stop()

	// Find the discovery occurrence using a filter string.
	var discoveryOccurrence *grafeas.Occurrence
	for {
		// Waiting for discovery occurrence to appear.
		if discoveryOccurrence == nil {
			req := &grafeas.ListOccurrencesRequest{
				Parent: fmt.Sprintf("projects/%s", getProjectFromContainerImage(containerImage)),
				// Vulnerability discovery occurrences are always associated with the
				// PACKAGE_VULNERABILITY note.
				Filter: fmt.Sprintf(`resourceUrl=%q AND noteProjectId=%q AND noteId="PACKAGE_VULNERABILITY"`, util.GetResourceURL(containerImage), DefaultProject),
			}

			// There should be only one discovery occurrence.
			resp, err := c.client.ListOccurrences(c.ctx, req)
			if err != nil {
				return err
			}
			if len(resp.Occurrences) == 0 {
				continue
			}
			if resp.Occurrences[0].GetDiscovered() != nil {
				discoveryOccurrence = resp.Occurrences[0]
			}
		}

		// Update analysis status and check.
		if discoveryOccurrence != nil {
			// Update the occurrence.
			req := &grafeas.GetOccurrenceRequest{Name: discoveryOccurrence.GetName()}
			updated, err := c.client.GetOccurrence(c.ctx, req)
			if err != nil {
				return fmt.Errorf("GetOccurrence: %v", err)
			}
			switch updated.GetDiscovered().GetDiscovered().GetAnalysisStatus() {
			case discovery.Discovered_FINISHED_SUCCESS:
				return nil
			case discovery.Discovered_FINISHED_FAILED:
				return fmt.Errorf("container analysis has finished unsuccessfully")
			case discovery.Discovered_FINISHED_UNSUPPORTED:
				return fmt.Errorf("container analysis resource is known not to be supported")
			}
		}

		select {
		case <-timeoutTimer.C:
			return fmt.Errorf("timeout while retrieving discovery occurrence")
		case <-time.Tick(time.Duration(nextTryWait)):
			// exponential backoff
			nextTryWait = nextTryWait * 2
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package images

import (
	goerrors "errors"
	"fmt"
	"math"
	"sort"
	"sync"
	"time"

	"k8s.io/klog/v2"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/tools/record"
	statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
	"k8s.io/kubernetes/pkg/kubelet/container"
	"k8s.io/kubernetes/pkg/kubelet/events"
	"k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
)

// StatsProvider is an interface for fetching stats used during image garbage
// collection.
type StatsProvider interface {
	// ImageFsStats returns the stats of the image filesystem.
	ImageFsStats() (*statsapi.FsStats, error)
}

// ImageGCManager is an interface for managing lifecycle of all images.
// Implementation is thread-safe.
type ImageGCManager interface {
	// Applies the garbage collection policy. Errors include being unable to free
	// enough space as per the garbage collection policy.
	GarbageCollect() error

	// Start async garbage collection of images.
	Start()

	GetImageList() ([]container.Image, error)

	// Delete all unused images.
	DeleteUnusedImages() error
}

// ImageGCPolicy is a policy for garbage collecting images. Policy defines an allowed band in
// which garbage collection will be run.
type ImageGCPolicy struct {
	// Any usage above this threshold will always trigger garbage collection.
	// This is the highest usage we will allow.
	HighThresholdPercent int

	// Any usage below this threshold will never trigger garbage collection.
	// This is the lowest threshold we will try to garbage collect to.
	LowThresholdPercent int

	// Minimum age at which an image can be garbage collected.
	MinAge time.Duration
}

type realImageGCManager struct {
	// Container runtime
	runtime container.Runtime

	// Records of images and their use.
	imageRecords     map[string]*imageRecord
	imageRecordsLock sync.Mutex

	// The image garbage collection policy in use.
	policy ImageGCPolicy

	// statsProvider provides stats used during image garbage collection.
	statsProvider StatsProvider

	// Recorder for Kubernetes events.
	recorder record.EventRecorder

	// Reference to this node.
	nodeRef *v1.ObjectReference

	// Track initialization
	initialized bool

	// imageCache is the cache of latest image list.
	imageCache imageCache

	// sandbox image exempted from GC
	sandboxImage string
}

// imageCache caches latest result of ListImages.
type imageCache struct {
	// sync.Mutex is the mutex protects the image cache.
	sync.Mutex
	// images is the image cache.
	images []container.Image
}

// set sorts the input list and updates image cache.
// 'i' takes ownership of the list, you should not reference the list again
// after calling this function.
func (i *imageCache) set(images []container.Image) {
	i.Lock()
	defer i.Unlock()
	// The image list needs to be sorted when it gets read and used in
	// setNodeStatusImages. We sort the list on write instead of on read,
	// because the image cache is more often read than written
	sort.Sort(sliceutils.ByImageSize(images))
	i.images = images
}

// get gets image list from image cache.
// NOTE: The caller of get() should not do mutating operations on the
// returned list that could cause data race against other readers (e.g.
// in-place sorting the returned list)
func (i *imageCache) get() []container.Image {
	i.Lock()
	defer i.Unlock()
	return i.images
}

// Information about the images we track.
type imageRecord struct {
	// Time when this image was first detected.
	firstDetected time.Time

	// Time when we last saw this image being used.
	lastUsed time.Time

	// Size of the image in bytes.
	size int64
}

// NewImageGCManager instantiates a new ImageGCManager object.
func NewImageGCManager(runtime container.Runtime, statsProvider StatsProvider, recorder record.EventRecorder, nodeRef *v1.ObjectReference, policy ImageGCPolicy, sandboxImage string) (ImageGCManager, error) {
	// Validate policy.
	if policy.HighThresholdPercent < 0 || policy.HighThresholdPercent > 100 {
		return nil, fmt.Errorf("invalid HighThresholdPercent %d, must be in range [0-100]", policy.HighThresholdPercent)
	}
	if policy.LowThresholdPercent < 0 || policy.LowThresholdPercent > 100 {
		return nil, fmt.Errorf("invalid LowThresholdPercent %d, must be in range [0-100]", policy.LowThresholdPercent)
	}
	if policy.LowThresholdPercent > policy.HighThresholdPercent {
		return nil, fmt.Errorf("LowThresholdPercent %d can not be higher than HighThresholdPercent %d", policy.LowThresholdPercent, policy.HighThresholdPercent)
	}
	im := &realImageGCManager{
		runtime:       runtime,
		policy:        policy,
		imageRecords:  make(map[string]*imageRecord),
		statsProvider: statsProvider,
		recorder:      recorder,
		nodeRef:       nodeRef,
		initialized:   false,
		sandboxImage:  sandboxImage,
	}

	return im, nil
}

func (im *realImageGCManager) Start() {
	go wait.Until(func() {
		// Initial detection make detected time "unknown" in the past.
		var ts time.Time
		if im.initialized {
			ts = time.Now()
		}
		_, err := im.detectImages(ts)
		if err != nil {
			klog.Warningf("[imageGCManager] Failed to monitor images: %v", err)
		} else {
			im.initialized = true
		}
	}, 5*time.Minute, wait.NeverStop)

	// Start a goroutine periodically updates image cache.
	go wait.Until(func() {
		images, err := im.runtime.ListImages()
		if err != nil {
			klog.Warningf("[imageGCManager] Failed to update image list: %v", err)
		} else {
			im.imageCache.set(images)
		}
	}, 30*time.Second, wait.NeverStop)

}

// Get a list of images on this node
func (im *realImageGCManager) GetImageList() ([]container.Image, error) {
	return im.imageCache.get(), nil
}

func (im *realImageGCManager) detectImages(detectTime time.Time) (sets.String, error) {
	imagesInUse := sets.NewString()

	// Always consider the container runtime pod sandbox image in use
	imageRef, err := im.runtime.GetImageRef(container.ImageSpec{Image: im.sandboxImage})
	if err == nil && imageRef != "" {
		imagesInUse.Insert(imageRef)
	}

	images, err := im.runtime.ListImages()
	if err != nil {
		return imagesInUse, err
	}
	pods, err := im.runtime.GetPods(true)
	if err != nil {
		return imagesInUse, err
	}

	// Make a set of images in use by containers.
	for _, pod := range pods {
		for _, container := range pod.Containers {
			klog.V(5).Infof("Pod %s/%s, container %s uses image %s(%s)", pod.Namespace, pod.Name, container.Name, container.Image, container.ImageID)
			imagesInUse.Insert(container.ImageID)
		}
	}

	// Add new images and record those being used.
	now := time.Now()
	currentImages := sets.NewString()
	im.imageRecordsLock.Lock()
	defer im.imageRecordsLock.Unlock()
	for _, image := range images {
		klog.V(5).Infof("Adding image ID %s to currentImages", image.ID)
		currentImages.Insert(image.ID)

		// New image, set it as detected now.
		if _, ok := im.imageRecords[image.ID]; !ok {
			klog.V(5).Infof("Image ID %s is new", image.ID)
			im.imageRecords[image.ID] = &imageRecord{
				firstDetected: detectTime,
			}
		}

		// Set last used time to now if the image is being used.
		if isImageUsed(image.ID, imagesInUse) {
			klog.V(5).Infof("Setting Image ID %s lastUsed to %v", image.ID, now)
			im.imageRecords[image.ID].lastUsed = now
		}

		klog.V(5).Infof("Image ID %s has size %d", image.ID, image.Size)
		im.imageRecords[image.ID].size = image.Size
	}

	// Remove old images from our records.
	for image := range im.imageRecords {
		if !currentImages.Has(image) {
			klog.V(5).Infof("Image ID %s is no longer present; removing from imageRecords", image)
			delete(im.imageRecords, image)
		}
	}

	return imagesInUse, nil
}

func (im *realImageGCManager) GarbageCollect() error {
	// Get disk usage on disk holding images.
	fsStats, err := im.statsProvider.ImageFsStats()
	if err != nil {
		return err
	}

	var capacity, available int64
	if fsStats.CapacityBytes != nil {
		capacity = int64(*fsStats.CapacityBytes)
	}
	if fsStats.AvailableBytes != nil {
		available = int64(*fsStats.AvailableBytes)
	}

	if available > capacity {
		klog.Warningf("available %d is larger than capacity %d", available, capacity)
		available = capacity
	}

	// Check valid capacity.
	if capacity == 0 {
		err := goerrors.New("invalid capacity 0 on image filesystem")
		im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.InvalidDiskCapacity, err.Error())
		return err
	}

	// If over the max threshold, free enough to place us at the lower threshold.
	usagePercent := 100 - int(available*100/capacity)
	if usagePercent >= im.policy.HighThresholdPercent {
		amountToFree := capacity*int64(100-im.policy.LowThresholdPercent)/100 - available
		klog.Infof("[imageGCManager]: Disk usage on image filesystem is at %d%% which is over the high threshold (%d%%). Trying to free %d bytes down to the low threshold (%d%%).", usagePercent, im.policy.HighThresholdPercent, amountToFree, im.policy.LowThresholdPercent)
		freed, err := im.freeSpace(amountToFree, time.Now())
		if err != nil {
			return err
		}

		if freed < amountToFree {
			err := fmt.Errorf("failed to garbage collect required amount of images. Wanted to free %d bytes, but freed %d bytes", amountToFree, freed)
			im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.FreeDiskSpaceFailed, err.Error())
			return err
		}
	}

	return nil
}

func (im *realImageGCManager) DeleteUnusedImages() error {
	klog.Infof("attempting to delete unused images")
	_, err := im.freeSpace(math.MaxInt64, time.Now())
	return err
}

// Tries to free bytesToFree worth of images on the disk.
//
// Returns the number of bytes free and an error if any occurred. The number of
// bytes freed is always returned.
// Note that error may be nil and the number of bytes free may be less
// than bytesToFree.
func (im *realImageGCManager) freeSpace(bytesToFree int64, freeTime time.Time) (int64, error) {
	imagesInUse, err := im.detectImages(freeTime)
	if err != nil {
		return 0, err
	}

	im.imageRecordsLock.Lock()
	defer im.imageRecordsLock.Unlock()

	// Get all images in eviction order.
	images := make([]evictionInfo, 0, len(im.imageRecords))
	for image, record := range im.imageRecords {
		if isImageUsed(image, imagesInUse) {
			klog.V(5).Infof("Image ID %s is being used", image)
			continue
		}
		images = append(images, evictionInfo{
			id:          image,
			imageRecord: *record,
		})
	}
	sort.Sort(byLastUsedAndDetected(images))

	// Delete unused images until we've freed up enough space.
	var deletionErrors []error
	spaceFreed := int64(0)
	for _, image := range images {
		klog.V(5).Infof("Evaluating image ID %s for possible garbage collection", image.id)
		// Images that are currently in used were given a newer lastUsed.
		if image.lastUsed.Equal(freeTime) || image.lastUsed.After(freeTime) {
			klog.V(5).Infof("Image ID %s has lastUsed=%v which is >= freeTime=%v, not eligible for garbage collection", image.id, image.lastUsed, freeTime)
			continue
		}

		// Avoid garbage collect the image if the image is not old enough.
		// In such a case, the image may have just been pulled down, and will be used by a container right away.

		if freeTime.Sub(image.firstDetected) < im.policy.MinAge {
			klog.V(5).Infof("Image ID %s has age %v which is less than the policy's minAge of %v, not eligible for garbage collection", image.id, freeTime.Sub(image.firstDetected), im.policy.MinAge)
			continue
		}

		// Remove image. Continue despite errors.
		klog.Infof("[imageGCManager]: Removing image %q to free %d bytes", image.id, image.size)
		err := im.runtime.RemoveImage(container.ImageSpec{Image: image.id})
		if err != nil {
			deletionErrors = append(deletionErrors, err)
			continue
		}
		delete(im.imageRecords, image.id)
		spaceFreed += image.size

		if spaceFreed >= bytesToFree {
			break
		}
	}

	if len(deletionErrors) > 0 {
		return spaceFreed, fmt.Errorf("wanted to free %d bytes, but freed %d bytes space with errors in image deletion: %v", bytesToFree, spaceFreed, errors.NewAggregate(deletionErrors))
	}
	return spaceFreed, nil
}

type evictionInfo struct {
	id string
	imageRecord
}

type byLastUsedAndDetected []evictionInfo

func (ev byLastUsedAndDetected) Len() int      { return len(ev) }
func (ev byLastUsedAndDetected) Swap(i, j int) { ev[i], ev[j] = ev[j], ev[i] }
func (ev byLastUsedAndDetected) Less(i, j int) bool {
	// Sort by last used, break ties by detected.
	if ev[i].lastUsed.Equal(ev[j].lastUsed) {
		return ev[i].firstDetected.Before(ev[j].firstDetected)
	}
	return ev[i].lastUsed.Before(ev[j].lastUsed)
}

func isImageUsed(imageID string, imagesInUse sets.String) bool {
	// Check the image ID.
	if _, ok := imagesInUse[imageID]; ok {
		return true
	}
	return false
}
<commit_msg>Migrate image_gc_manager.go to structured logs<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package images

import (
	goerrors "errors"
	"fmt"
	"math"
	"sort"
	"sync"
	"time"

	"k8s.io/klog/v2"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/tools/record"
	statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
	"k8s.io/kubernetes/pkg/kubelet/container"
	"k8s.io/kubernetes/pkg/kubelet/events"
	"k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
)

// StatsProvider is an interface for fetching stats used during image garbage
// collection.
type StatsProvider interface {
	// ImageFsStats returns the stats of the image filesystem.
	ImageFsStats() (*statsapi.FsStats, error)
}

// ImageGCManager is an interface for managing lifecycle of all images.
// Implementation is thread-safe.
type ImageGCManager interface {
	// Applies the garbage collection policy. Errors include being unable to free
	// enough space as per the garbage collection policy.
	GarbageCollect() error

	// Start async garbage collection of images.
	Start()

	GetImageList() ([]container.Image, error)

	// Delete all unused images.
	DeleteUnusedImages() error
}

// ImageGCPolicy is a policy for garbage collecting images. Policy defines an allowed band in
// which garbage collection will be run.
type ImageGCPolicy struct {
	// Any usage above this threshold will always trigger garbage collection.
	// This is the highest usage we will allow.
	HighThresholdPercent int

	// Any usage below this threshold will never trigger garbage collection.
	// This is the lowest threshold we will try to garbage collect to.
	LowThresholdPercent int

	// Minimum age at which an image can be garbage collected.
	MinAge time.Duration
}

type realImageGCManager struct {
	// Container runtime
	runtime container.Runtime

	// Records of images and their use.
	imageRecords     map[string]*imageRecord
	imageRecordsLock sync.Mutex

	// The image garbage collection policy in use.
	policy ImageGCPolicy

	// statsProvider provides stats used during image garbage collection.
	statsProvider StatsProvider

	// Recorder for Kubernetes events.
	recorder record.EventRecorder

	// Reference to this node.
	nodeRef *v1.ObjectReference

	// Track initialization
	initialized bool

	// imageCache is the cache of latest image list.
	imageCache imageCache

	// sandbox image exempted from GC
	sandboxImage string
}

// imageCache caches latest result of ListImages.
type imageCache struct {
	// sync.Mutex is the mutex protects the image cache.
	sync.Mutex
	// images is the image cache.
	images []container.Image
}

// set sorts the input list and updates image cache.
// 'i' takes ownership of the list, you should not reference the list again
// after calling this function.
func (i *imageCache) set(images []container.Image) {
	i.Lock()
	defer i.Unlock()
	// The image list needs to be sorted when it gets read and used in
	// setNodeStatusImages. We sort the list on write instead of on read,
	// because the image cache is more often read than written
	sort.Sort(sliceutils.ByImageSize(images))
	i.images = images
}

// get gets image list from image cache.
// NOTE: The caller of get() should not do mutating operations on the
// returned list that could cause data race against other readers (e.g.
// in-place sorting the returned list)
func (i *imageCache) get() []container.Image {
	i.Lock()
	defer i.Unlock()
	return i.images
}

// Information about the images we track.
type imageRecord struct {
	// Time when this image was first detected.
	firstDetected time.Time

	// Time when we last saw this image being used.
	lastUsed time.Time

	// Size of the image in bytes.
	size int64
}

// NewImageGCManager instantiates a new ImageGCManager object.
func NewImageGCManager(runtime container.Runtime, statsProvider StatsProvider, recorder record.EventRecorder, nodeRef *v1.ObjectReference, policy ImageGCPolicy, sandboxImage string) (ImageGCManager, error) {
	// Validate policy.
	if policy.HighThresholdPercent < 0 || policy.HighThresholdPercent > 100 {
		return nil, fmt.Errorf("invalid HighThresholdPercent %d, must be in range [0-100]", policy.HighThresholdPercent)
	}
	if policy.LowThresholdPercent < 0 || policy.LowThresholdPercent > 100 {
		return nil, fmt.Errorf("invalid LowThresholdPercent %d, must be in range [0-100]", policy.LowThresholdPercent)
	}
	if policy.LowThresholdPercent > policy.HighThresholdPercent {
		return nil, fmt.Errorf("LowThresholdPercent %d can not be higher than HighThresholdPercent %d", policy.LowThresholdPercent, policy.HighThresholdPercent)
	}
	im := &realImageGCManager{
		runtime:       runtime,
		policy:        policy,
		imageRecords:  make(map[string]*imageRecord),
		statsProvider: statsProvider,
		recorder:      recorder,
		nodeRef:       nodeRef,
		initialized:   false,
		sandboxImage:  sandboxImage,
	}

	return im, nil
}

func (im *realImageGCManager) Start() {
	go wait.Until(func() {
		// Initial detection make detected time "unknown" in the past.
		var ts time.Time
		if im.initialized {
			ts = time.Now()
		}
		_, err := im.detectImages(ts)
		if err != nil {
			klog.InfoS("Failed to monitor images", "err", err)
		} else {
			im.initialized = true
		}
	}, 5*time.Minute, wait.NeverStop)

	// Start a goroutine periodically updates image cache.
	go wait.Until(func() {
		images, err := im.runtime.ListImages()
		if err != nil {
			klog.InfoS("Failed to update image list", "err", err)
		} else {
			im.imageCache.set(images)
		}
	}, 30*time.Second, wait.NeverStop)

}

// Get a list of images on this node
func (im *realImageGCManager) GetImageList() ([]container.Image, error) {
	return im.imageCache.get(), nil
}

func (im *realImageGCManager) detectImages(detectTime time.Time) (sets.String, error) {
	imagesInUse := sets.NewString()

	// Always consider the container runtime pod sandbox image in use
	imageRef, err := im.runtime.GetImageRef(container.ImageSpec{Image: im.sandboxImage})
	if err == nil && imageRef != "" {
		imagesInUse.Insert(imageRef)
	}

	images, err := im.runtime.ListImages()
	if err != nil {
		return imagesInUse, err
	}
	pods, err := im.runtime.GetPods(true)
	if err != nil {
		return imagesInUse, err
	}

	// Make a set of images in use by containers.
	for _, pod := range pods {
		for _, container := range pod.Containers {
			klog.V(5).InfoS("Container uses image", "pod", klog.KRef(pod.Namespace, pod.Name), "containerName", container.Name, "containerImage", container.Image, "imageID", container.ImageID)
			imagesInUse.Insert(container.ImageID)
		}
	}

	// Add new images and record those being used.
	now := time.Now()
	currentImages := sets.NewString()
	im.imageRecordsLock.Lock()
	defer im.imageRecordsLock.Unlock()
	for _, image := range images {
		klog.V(5).InfoS("Adding image ID to currentImages", "imageID", image.ID)
		currentImages.Insert(image.ID)

		// New image, set it as detected now.
		if _, ok := im.imageRecords[image.ID]; !ok {
			klog.V(5).InfoS("Image ID is new", "imageID", image.ID)
			im.imageRecords[image.ID] = &imageRecord{
				firstDetected: detectTime,
			}
		}

		// Set last used time to now if the image is being used.
		if isImageUsed(image.ID, imagesInUse) {
			klog.V(5).InfoS("Setting Image ID lastUsed", "imageID", image.ID, "lastUsed", now)
			im.imageRecords[image.ID].lastUsed = now
		}

		klog.V(5).InfoS("Image ID has size", "imageID", image.ID, "size", image.Size)
		im.imageRecords[image.ID].size = image.Size
	}

	// Remove old images from our records.
	for image := range im.imageRecords {
		if !currentImages.Has(image) {
			klog.V(5).InfoS("Image ID is no longer present; removing from imageRecords", "imageID", image)
			delete(im.imageRecords, image)
		}
	}

	return imagesInUse, nil
}

func (im *realImageGCManager) GarbageCollect() error {
	// Get disk usage on disk holding images.
	fsStats, err := im.statsProvider.ImageFsStats()
	if err != nil {
		return err
	}

	var capacity, available int64
	if fsStats.CapacityBytes != nil {
		capacity = int64(*fsStats.CapacityBytes)
	}
	if fsStats.AvailableBytes != nil {
		available = int64(*fsStats.AvailableBytes)
	}

	if available > capacity {
		klog.InfoS("Availability is larger than capacity", "available", available, "capacity", capacity)
		available = capacity
	}

	// Check valid capacity.
	if capacity == 0 {
		err := goerrors.New("invalid capacity 0 on image filesystem")
		im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.InvalidDiskCapacity, err.Error())
		return err
	}

	// If over the max threshold, free enough to place us at the lower threshold.
	usagePercent := 100 - int(available*100/capacity)
	if usagePercent >= im.policy.HighThresholdPercent {
		amountToFree := capacity*int64(100-im.policy.LowThresholdPercent)/100 - available
		klog.InfoS("Disk usage on image filesystem is over the high threshold, trying to free bytes down to the low threshold", "usage", usagePercent, "highThreshold", im.policy.HighThresholdPercent, "amountToFree", amountToFree, "lowThreshold", im.policy.LowThresholdPercent)
		freed, err := im.freeSpace(amountToFree, time.Now())
		if err != nil {
			return err
		}

		if freed < amountToFree {
			err := fmt.Errorf("failed to garbage collect required amount of images. Wanted to free %d bytes, but freed %d bytes", amountToFree, freed)
			im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.FreeDiskSpaceFailed, err.Error())
			return err
		}
	}

	return nil
}

func (im *realImageGCManager) DeleteUnusedImages() error {
	klog.InfoS("Attempting to delete unused images")
	_, err := im.freeSpace(math.MaxInt64, time.Now())
	return err
}

// Tries to free bytesToFree worth of images on the disk.
//
// Returns the number of bytes free and an error if any occurred. The number of
// bytes freed is always returned.
// Note that error may be nil and the number of bytes free may be less
// than bytesToFree.
func (im *realImageGCManager) freeSpace(bytesToFree int64, freeTime time.Time) (int64, error) {
	imagesInUse, err := im.detectImages(freeTime)
	if err != nil {
		return 0, err
	}

	im.imageRecordsLock.Lock()
	defer im.imageRecordsLock.Unlock()

	// Get all images in eviction order.
	images := make([]evictionInfo, 0, len(im.imageRecords))
	for image, record := range im.imageRecords {
		if isImageUsed(image, imagesInUse) {
			klog.V(5).InfoS("Image ID is being used", "imageID", image)
			continue
		}
		images = append(images, evictionInfo{
			id:          image,
			imageRecord: *record,
		})
	}
	sort.Sort(byLastUsedAndDetected(images))

	// Delete unused images until we've freed up enough space.
	var deletionErrors []error
	spaceFreed := int64(0)
	for _, image := range images {
		klog.V(5).InfoS("Evaluating image ID for possible garbage collection", "imageID", image.id)
		// Images that are currently in used were given a newer lastUsed.
		if image.lastUsed.Equal(freeTime) || image.lastUsed.After(freeTime) {
			klog.V(5).InfoS("Image ID was used too recently, not eligible for garbage collection", "imageID", image.id, "lastUsed", image.lastUsed, "freeTime", freeTime)
			continue
		}

		// Avoid garbage collect the image if the image is not old enough.
		// In such a case, the image may have just been pulled down, and will be used by a container right away.

		if freeTime.Sub(image.firstDetected) < im.policy.MinAge {
			klog.V(5).InfoS("Image ID's age is less than the policy's minAge, not eligible for garbage collection", "imageID", image.id, "age", freeTime.Sub(image.firstDetected), "minAge", im.policy.MinAge)
			continue
		}

		// Remove image. Continue despite errors.
		klog.InfoS("Removing image to free bytes", "imageID", image.id, "size", image.size)
		err := im.runtime.RemoveImage(container.ImageSpec{Image: image.id})
		if err != nil {
			deletionErrors = append(deletionErrors, err)
			continue
		}
		delete(im.imageRecords, image.id)
		spaceFreed += image.size

		if spaceFreed >= bytesToFree {
			break
		}
	}

	if len(deletionErrors) > 0 {
		return spaceFreed, fmt.Errorf("wanted to free %d bytes, but freed %d bytes space with errors in image deletion: %v", bytesToFree, spaceFreed, errors.NewAggregate(deletionErrors))
	}
	return spaceFreed, nil
}

type evictionInfo struct {
	id string
	imageRecord
}

type byLastUsedAndDetected []evictionInfo

func (ev byLastUsedAndDetected) Len() int      { return len(ev) }
func (ev byLastUsedAndDetected) Swap(i, j int) { ev[i], ev[j] = ev[j], ev[i] }
func (ev byLastUsedAndDetected) Less(i, j int) bool {
	// Sort by last used, break ties by detected.
	if ev[i].lastUsed.Equal(ev[j].lastUsed) {
		return ev[i].firstDetected.Before(ev[j].firstDetected)
	}
	return ev[i].lastUsed.Before(ev[j].lastUsed)
}

func isImageUsed(imageID string, imagesInUse sets.String) bool {
	// Check the image ID.
	if _, ok := imagesInUse[imageID]; ok {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package paypal

import (
	"socialapi/workers/payment/paymentmodels"
	"time"

	"github.com/koding/paypal"
)

func CreateSubscription(token string, plan *paymentmodels.Plan, customer *paymentmodels.Customer) error {
	args := paypal.NewExpressCheckoutSingleArgs()
	args.ReturnURL = returnURL
	args.CancelURL = cancelURL
	args.Item = paypal.NewDigitalGood(plan.Title, 0)

	response, err := client.SetExpressCheckoutSingle(args)
	err = handlePaypalErr(response, err)
	if err != nil {
		return err
	}

	params := map[string]string{
		"PROFILESTARTDATE": time.Now().String(),
		"SUBSCRIBERNAME":   customer.OldId,
		"BILLINGPERIOD":    getInterval(plan.Interval),
		"AMT":              normalizeAmount(plan.AmountInCents),
		"BILLINGFREQUENCY": "1",
		"CURRENCYCODE":     CurrencyCode,
		"DESC":             plan.Title,
		"AUTOBILLOUTAMT":   "AddToNextBilling",
	}

	response, err = client.CreateRecurringPaymentsProfile(token, params)
	err = handlePaypalErr(response, err)
	if err != nil {
		return err
	}

	subModel := &paymentmodels.Subscription{
		PlanId:                 plan.Id,
		CustomerId:             customer.Id,
		ProviderSubscriptionId: response.Values.Get("PROFILEID"),
		Provider:               ProviderName,
		State:                  "active",
		CurrentPeriodStart:     time.Now(),
		AmountInCents:          plan.AmountInCents,
	}
	err = subModel.Create()

	return err
}
<commit_msg>payment: build item name with plan title and interval<commit_after>package paypal

import (
	"fmt"
	"socialapi/workers/payment/paymentmodels"
	"time"
)

func CreateSubscription(token string, plan *paymentmodels.Plan, customer *paymentmodels.Customer) error {
	digitalGoodName := fmt.Sprintf("%s-%s", plan.Title, plan.Interval)
	params := map[string]string{
		"PROFILESTARTDATE": time.Now().String(),
		"SUBSCRIBERNAME":   customer.OldId,
		"BILLINGPERIOD":    getInterval(plan.Interval),
		"AMT":              normalizeAmount(plan.AmountInCents),
		"BILLINGFREQUENCY": "1",
		"CURRENCYCODE":     CurrencyCode,
		"DESC":             digitalGoodName,
		"AUTOBILLOUTAMT":   "AddToNextBilling",
	}

	response, err := client.CreateRecurringPaymentsProfile(token, params)
	err = handlePaypalErr(response, err)
	if err != nil {
		return err
	}

	subModel := &paymentmodels.Subscription{
		PlanId:                 plan.Id,
		CustomerId:             customer.Id,
		ProviderSubscriptionId: response.Values.Get("PROFILEID"),
		Provider:               ProviderName,
		State:                  "active",
		CurrentPeriodStart:     time.Now(),
		AmountInCents:          plan.AmountInCents,
	}
	err = subModel.Create()

	return err
}
<|endoftext|>


<commit_before><commit_msg>add tag for reused slb<commit_after><|endoftext|>


<commit_before>package actor

import "github.com/AsynkronIT/protoactor-go/mailbox"

type AutoReceiveMessage interface {
	AutoReceiveMessage()
}

// An actor which receives a NotInfluenceReceiveTimeout message will not reset the ReceiveTimeout timer
type NotInfluenceReceiveTimeout interface {
	NotInfluenceReceiveTimeout()
}

// A SystemMessage message is reserved for specific lifecycle messages used by the actor system
type SystemMessage interface {
	SystemMessage()
}

// A ReceiveTimeout message is sent to an actor after the Context.ReceiveTimeout duration has expired
type ReceiveTimeout struct{}

// A Restarting message is sent to an actor when the actor is being restarted by the system due to a failure
type Restarting struct{}

// A Stopping message is sent to an actor prior to the actor being stopped
type Stopping struct{}

// A Stopped message is sent to the actor once it has been stopped. A stopped actor will receive no further messages
type Stopped struct{}

// A Started message is sent to an actor once it has been started and ready to begin receiving messages.
type Started struct{}

// Restart is message sent by the actor system to control the lifecycle of an actor
type Restart struct{}

// Stop is message sent by the actor system to control the lifecycle of an actor
//
// This will not be forwarded to the Receive method
type Stop struct{}

//TODO: make private?
type Failure struct {
	Who          *PID
	Reason       interface{}
	RestartStats *RestartStatistics
	Message      interface{}
}

type messageSender struct {
	Message interface{}
	Sender  *PID
}

func (*Restarting) AutoReceiveMessage() {}
func (*Stopping) AutoReceiveMessage()   {}
func (*Stopped) AutoReceiveMessage()    {}
func (*PoisonPill) AutoReceiveMessage() {}

func (*Started) SystemMessage()        {}
func (*Stop) SystemMessage()           {}
func (*Watch) SystemMessage()          {}
func (*Unwatch) SystemMessage()        {}
func (*Terminated) SystemMessage()     {}
func (*Failure) SystemMessage()        {}
func (*Restart) SystemMessage()        {}

var (
	restartingMessage     interface{} = &Restarting{}
	stoppingMessage       interface{} = &Stopping{}
	stoppedMessage        interface{} = &Stopped{}
	poisonPillMessage     interface{} = &PoisonPill{}
	receiveTimeoutMessage interface{} = &ReceiveTimeout{}
)

var (
	restartMessage        interface{} = &Restart{}
	startedMessage        interface{} = &Started{}
	stopMessage           interface{} = &Stop{}
	resumeMailboxMessage  interface{} = &mailbox.ResumeMailbox{}
	suspendMailboxMessage interface{} = &mailbox.SuspendMailbox{}
)
<commit_msg>gofmt<commit_after>package actor

import "github.com/AsynkronIT/protoactor-go/mailbox"

type AutoReceiveMessage interface {
	AutoReceiveMessage()
}

// An actor which receives a NotInfluenceReceiveTimeout message will not reset the ReceiveTimeout timer
type NotInfluenceReceiveTimeout interface {
	NotInfluenceReceiveTimeout()
}

// A SystemMessage message is reserved for specific lifecycle messages used by the actor system
type SystemMessage interface {
	SystemMessage()
}

// A ReceiveTimeout message is sent to an actor after the Context.ReceiveTimeout duration has expired
type ReceiveTimeout struct{}

// A Restarting message is sent to an actor when the actor is being restarted by the system due to a failure
type Restarting struct{}

// A Stopping message is sent to an actor prior to the actor being stopped
type Stopping struct{}

// A Stopped message is sent to the actor once it has been stopped. A stopped actor will receive no further messages
type Stopped struct{}

// A Started message is sent to an actor once it has been started and ready to begin receiving messages.
type Started struct{}

// Restart is message sent by the actor system to control the lifecycle of an actor
type Restart struct{}

// Stop is message sent by the actor system to control the lifecycle of an actor
//
// This will not be forwarded to the Receive method
type Stop struct{}

//TODO: make private?
type Failure struct {
	Who          *PID
	Reason       interface{}
	RestartStats *RestartStatistics
	Message      interface{}
}

type messageSender struct {
	Message interface{}
	Sender  *PID
}

func (*Restarting) AutoReceiveMessage() {}
func (*Stopping) AutoReceiveMessage()   {}
func (*Stopped) AutoReceiveMessage()    {}
func (*PoisonPill) AutoReceiveMessage() {}

func (*Started) SystemMessage()    {}
func (*Stop) SystemMessage()       {}
func (*Watch) SystemMessage()      {}
func (*Unwatch) SystemMessage()    {}
func (*Terminated) SystemMessage() {}
func (*Failure) SystemMessage()    {}
func (*Restart) SystemMessage()    {}

var (
	restartingMessage     interface{} = &Restarting{}
	stoppingMessage       interface{} = &Stopping{}
	stoppedMessage        interface{} = &Stopped{}
	poisonPillMessage     interface{} = &PoisonPill{}
	receiveTimeoutMessage interface{} = &ReceiveTimeout{}
)

var (
	restartMessage        interface{} = &Restart{}
	startedMessage        interface{} = &Started{}
	stopMessage           interface{} = &Stop{}
	resumeMailboxMessage  interface{} = &mailbox.ResumeMailbox{}
	suspendMailboxMessage interface{} = &mailbox.SuspendMailbox{}
)
<|endoftext|>