<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 gcsproxy

import (
	"fmt"
	"io/ioutil"
	"math"
	"os"

	"github.com/jacobsa/gcloud/gcs"
	"golang.org/x/net/context"
)

// A view on a particular generation of an object in GCS that allows random
// access reads and writes.
//
// Reads may involve reading from a local cache. Writes are buffered locally
// until the Sync method is called, at which time a new generation of the
// object is created.
//
// This type is not safe for concurrent access. The user must provide external
// synchronization.
type ObjectProxy struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	bucket gcs.Bucket

	/////////////////////////
	// Constant data
	/////////////////////////

	// The name of the GCS object for which we are a proxy. Might not currently
	// exist in the bucket.
	name string

	/////////////////////////
	// Mutable state
	/////////////////////////

	// The specific generation of the object from which our local state is
	// branched. If we have no local state, the contents of this object are
	// exactly our contents. May be zero if our source is a "doesn't exist"
	// generation.
	srcGeneration uint64

	// A local temporary file containing our current contents. When non-nil, this
	// is the authority on our contents. When nil, our contents are defined by
	// the generation identified by srcGeneration.
	localFile *os.File

	// false if localFile is present but its contents may be different from the
	// contents of our source generation. Sync needs to do work iff this is true.
	//
	// INVARIANT: If srcGeneration == 0, then dirty
	// INVARIANT: If dirty, then localFile != nil
	dirty bool
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

// Create a view on the given GCS object generation, or zero if branching from
// a non-existent object (in which case the initial contents are empty).
func NewObjectProxy(
	ctx context.Context,
	bucket gcs.Bucket,
	name string,
	srcGeneration uint64) (op *ObjectProxy, err error) {
	// Set up the basic struct.
	op = &ObjectProxy{
		bucket:        bucket,
		name:          name,
		srcGeneration: srcGeneration,
	}

	// For "doesn't exist" source generations, we must establish an empty local
	// file and mark the proxy dirty.
	if srcGeneration == 0 {
		if err = op.ensureLocalFile(ctx); err != nil {
			return
		}

		op.dirty = true
	}

	return
}

// Return the name of the proxied object. This may or may not be an object that
// currently exists in the bucket.
func (op *ObjectProxy) Name() string {
	return op.name
}

// Panic if any internal invariants are violated. Careful users can call this
// at appropriate times to help debug weirdness. Consider using
// syncutil.InvariantMutex to automate the process.
func (op *ObjectProxy) CheckInvariants() {
	// INVARIANT: If srcGeneration == 0, then dirty
	if op.srcGeneration == 0 && !op.dirty {
		panic("Expected dirty.")
	}

	// INVARIANT: If dirty, then localFile != nil
	if op.dirty && op.localFile == nil {
		panic("Expected non-nil localFile.")
	}
}

// Destroy any local file caches, putting the proxy into an indeterminate
// state. Should be used before dropping the final reference to the proxy.
func (op *ObjectProxy) Destroy() (err error) {
	// Make sure that when we exit no invariants are violated.
	defer func() {
		op.srcGeneration = 1
		op.localFile = nil
		op.dirty = false
	}()

	// If we have no local file, there's nothing to do.
	if op.localFile == nil {
		return
	}

	// Close the local file.
	if err = op.localFile.Close(); err != nil {
		err = fmt.Errorf("Close: %v", err)
		return
	}

	return
}

// Return the current size in bytes of the content and an indication of whether
// the proxied object has changed out from under us (in which case Sync will
// fail).
func (op *ObjectProxy) Stat(
	ctx context.Context) (size uint64, clobbered bool, err error) {
	panic("TODO")
}

// Make a random access read into our view of the content. May block for
// network access.
//
// Guarantees that err != nil if n < len(buf)
func (op *ObjectProxy) ReadAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Make sure we have a local file.
	if err = op.ensureLocalFile(ctx); err != nil {
		err = fmt.Errorf("ensureLocalFile: %v", err)
		return
	}

	// Serve the read from the file.
	n, err = op.localFile.ReadAt(buf, offset)

	return
}

// Make a random access write into our view of the content. May block for
// network access. Not guaranteed to be reflected remotely until after Sync is
// called successfully.
//
// Guarantees that err != nil if n < len(buf)
func (op *ObjectProxy) WriteAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Make sure we have a local file.
	if err = op.ensureLocalFile(ctx); err != nil {
		err = fmt.Errorf("ensureLocalFile: %v", err)
		return
	}

	// TODO(jacobsa): Make sure the dirty flag modification below is tested by
	// removing it and looking for a failure.
	op.dirty = true
	n, err = op.localFile.WriteAt(buf, offset)

	return
}

// Truncate our view of the content to the given number of bytes, extending if
// n is greater than the current size. May block for network access. Not
// guaranteed to be reflected remotely until after Sync is called successfully.
func (op *ObjectProxy) Truncate(ctx context.Context, n uint64) (err error) {
	// Make sure we have a local file.
	if err = op.ensureLocalFile(ctx); err != nil {
		err = fmt.Errorf("ensureLocalFile: %v", err)
		return
	}

	// Convert to signed, which is what os.File wants.
	if n > math.MaxInt64 {
		err = fmt.Errorf("Illegal offset: %v", n)
		return
	}

	// TODO(jacobsa): Make sure the dirty flag modification below is tested by
	// removing it and looking for a failure.
	op.dirty = true
	err = op.localFile.Truncate(int64(n))

	return
}

// If the proxy is dirty due to having been written to or due to having a nil
// source, save its current contents to GCS and return a generation number for
// a generation with exactly those contents. Do so with a precondition such
// that the creation will fail if the source generation is not current. In that
// case, return an error of type *gcs.PreconditionError.
func (op *ObjectProxy) Sync(ctx context.Context) (gen uint64, err error) {
	panic("TODO")
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Set up an unlinked local temporary file for the given generation of the
// given object. Special case: generation == 0 means an empty file.
func makeLocalFile(
	ctx context.Context,
	bucket gcs.Bucket,
	name string,
	generation uint64) (f *os.File, err error) {
	// Create the file.
	f, err = ioutil.TempFile("", "object_proxy")
	if err != nil {
		err = fmt.Errorf("TempFile: %v", err)
		return
	}

	// Unlink the file so that its inode will be garbage collected when the file
	// is closed.
	if err = os.Remove(f.Name()); err != nil {
		f.Close()
		err = fmt.Errorf("Remove: %v", err)
		return
	}

	// Fetch the object's contents if necessary.
	if generation != 0 {
		panic("TODO")
	}

	return
}

// Ensure that op.localFile is non-nil with an authoritative view of op's
// contents.
func (op *ObjectProxy) ensureLocalFile(ctx context.Context) (err error) {
	// Is there anything to do?
	if op.localFile != nil {
		return
	}

	// Set up the file.
	f, err := makeLocalFile(ctx, op.bucket, op.name, op.srcGeneration)
	if err != nil {
		err = fmt.Errorf("makeLocalFile: %v", err)
		return
	}

	op.localFile = f
	return
}
<commit_msg>Implemented much of ObjectProxy.Sync.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 gcsproxy

import (
	"fmt"
	"io/ioutil"
	"math"
	"os"

	"github.com/jacobsa/gcloud/gcs"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

// A view on a particular generation of an object in GCS that allows random
// access reads and writes.
//
// Reads may involve reading from a local cache. Writes are buffered locally
// until the Sync method is called, at which time a new generation of the
// object is created.
//
// This type is not safe for concurrent access. The user must provide external
// synchronization.
type ObjectProxy struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	bucket gcs.Bucket

	/////////////////////////
	// Constant data
	/////////////////////////

	// The name of the GCS object for which we are a proxy. Might not currently
	// exist in the bucket.
	name string

	/////////////////////////
	// Mutable state
	/////////////////////////

	// The specific generation of the object from which our local state is
	// branched. If we have no local state, the contents of this object are
	// exactly our contents. May be zero if our source is a "doesn't exist"
	// generation.
	srcGeneration uint64

	// A local temporary file containing our current contents. When non-nil, this
	// is the authority on our contents. When nil, our contents are defined by
	// the generation identified by srcGeneration.
	localFile *os.File

	// false if localFile is present but its contents may be different from the
	// contents of our source generation. Sync needs to do work iff this is true.
	//
	// INVARIANT: If srcGeneration == 0, then dirty
	// INVARIANT: If dirty, then localFile != nil
	dirty bool
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

// Create a view on the given GCS object generation, or zero if branching from
// a non-existent object (in which case the initial contents are empty).
func NewObjectProxy(
	ctx context.Context,
	bucket gcs.Bucket,
	name string,
	srcGeneration uint64) (op *ObjectProxy, err error) {
	// Set up the basic struct.
	op = &ObjectProxy{
		bucket:        bucket,
		name:          name,
		srcGeneration: srcGeneration,
	}

	// For "doesn't exist" source generations, we must establish an empty local
	// file and mark the proxy dirty.
	if srcGeneration == 0 {
		if err = op.ensureLocalFile(ctx); err != nil {
			return
		}

		op.dirty = true
	}

	return
}

// Return the name of the proxied object. This may or may not be an object that
// currently exists in the bucket.
func (op *ObjectProxy) Name() string {
	return op.name
}

// Panic if any internal invariants are violated. Careful users can call this
// at appropriate times to help debug weirdness. Consider using
// syncutil.InvariantMutex to automate the process.
func (op *ObjectProxy) CheckInvariants() {
	// INVARIANT: If srcGeneration == 0, then dirty
	if op.srcGeneration == 0 && !op.dirty {
		panic("Expected dirty.")
	}

	// INVARIANT: If dirty, then localFile != nil
	if op.dirty && op.localFile == nil {
		panic("Expected non-nil localFile.")
	}
}

// Destroy any local file caches, putting the proxy into an indeterminate
// state. Should be used before dropping the final reference to the proxy.
func (op *ObjectProxy) Destroy() (err error) {
	// Make sure that when we exit no invariants are violated.
	defer func() {
		op.srcGeneration = 1
		op.localFile = nil
		op.dirty = false
	}()

	// If we have no local file, there's nothing to do.
	if op.localFile == nil {
		return
	}

	// Close the local file.
	if err = op.localFile.Close(); err != nil {
		err = fmt.Errorf("Close: %v", err)
		return
	}

	return
}

// Return the current size in bytes of the content and an indication of whether
// the proxied object has changed out from under us (in which case Sync will
// fail).
func (op *ObjectProxy) Stat(
	ctx context.Context) (size uint64, clobbered bool, err error) {
	panic("TODO")
}

// Make a random access read into our view of the content. May block for
// network access.
//
// Guarantees that err != nil if n < len(buf)
func (op *ObjectProxy) ReadAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Make sure we have a local file.
	if err = op.ensureLocalFile(ctx); err != nil {
		err = fmt.Errorf("ensureLocalFile: %v", err)
		return
	}

	// Serve the read from the file.
	n, err = op.localFile.ReadAt(buf, offset)

	return
}

// Make a random access write into our view of the content. May block for
// network access. Not guaranteed to be reflected remotely until after Sync is
// called successfully.
//
// Guarantees that err != nil if n < len(buf)
func (op *ObjectProxy) WriteAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Make sure we have a local file.
	if err = op.ensureLocalFile(ctx); err != nil {
		err = fmt.Errorf("ensureLocalFile: %v", err)
		return
	}

	// TODO(jacobsa): Make sure the dirty flag modification below is tested by
	// removing it and looking for a failure.
	op.dirty = true
	n, err = op.localFile.WriteAt(buf, offset)

	return
}

// Truncate our view of the content to the given number of bytes, extending if
// n is greater than the current size. May block for network access. Not
// guaranteed to be reflected remotely until after Sync is called successfully.
func (op *ObjectProxy) Truncate(ctx context.Context, n uint64) (err error) {
	// Make sure we have a local file.
	if err = op.ensureLocalFile(ctx); err != nil {
		err = fmt.Errorf("ensureLocalFile: %v", err)
		return
	}

	// Convert to signed, which is what os.File wants.
	if n > math.MaxInt64 {
		err = fmt.Errorf("Illegal offset: %v", n)
		return
	}

	// TODO(jacobsa): Make sure the dirty flag modification below is tested by
	// removing it and looking for a failure.
	op.dirty = true
	err = op.localFile.Truncate(int64(n))

	return
}

// If the proxy is dirty due to having been written to or due to having a nil
// source, save its current contents to GCS and return a generation number for
// a generation with exactly those contents. Do so with a precondition such
// that the creation will fail if the source generation is not current. In that
// case, return an error of type *gcs.PreconditionError.
func (op *ObjectProxy) Sync(ctx context.Context) (gen uint64, err error) {
	// Do we need to do anything?
	if !op.dirty {
		gen = op.srcGeneration
		return
	}

	// TODO(jacobsa): Add a test that ensures we don't screw up the seek position
	// within the file when reading below. Sync then dirty then Sync.

	// Write a new generation of the object with the appropriate contents, using
	// an appropriate precondition.
	signedSrcGeneration := int64(op.srcGeneration)
	req := &gcs.CreateObjectRequest{
		Attrs: storage.ObjectAttrs{
			Name: op.name,
		},
		Contents:               op.localFile,
		GenerationPrecondition: &signedSrcGeneration,
	}

	o, err := op.bucket.CreateObject(ctx, req)
	if err != nil {
		return
	}

	// Make sure the server didn't return a silly generation number.
	//
	// TODO(jacobsa): Push unsigned generation numbers and a guarantee on zero
	// into package gcs, including checking results from the server, and remove
	// this.
	if o.Generation <= 0 {
		err = fmt.Errorf("GCS returned invalid generation number: %v", o.Generation)
		return
	}

	gen = uint64(o.Generation)

	// Update our state.
	op.srcGeneration = gen
	op.dirty = false

	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Set up an unlinked local temporary file for the given generation of the
// given object. Special case: generation == 0 means an empty file.
func makeLocalFile(
	ctx context.Context,
	bucket gcs.Bucket,
	name string,
	generation uint64) (f *os.File, err error) {
	// Create the file.
	f, err = ioutil.TempFile("", "object_proxy")
	if err != nil {
		err = fmt.Errorf("TempFile: %v", err)
		return
	}

	// Unlink the file so that its inode will be garbage collected when the file
	// is closed.
	if err = os.Remove(f.Name()); err != nil {
		f.Close()
		err = fmt.Errorf("Remove: %v", err)
		return
	}

	// Fetch the object's contents if necessary.
	if generation != 0 {
		panic("TODO")
	}

	return
}

// Ensure that op.localFile is non-nil with an authoritative view of op's
// contents.
func (op *ObjectProxy) ensureLocalFile(ctx context.Context) (err error) {
	// Is there anything to do?
	if op.localFile != nil {
		return
	}

	// Set up the file.
	f, err := makeLocalFile(ctx, op.bucket, op.name, op.srcGeneration)
	if err != nil {
		err = fmt.Errorf("makeLocalFile: %v", err)
		return
	}

	op.localFile = f
	return
}
<|endoftext|>


<commit_before>package api

// StorageVolumesPost represents the fields of a new LXD storage pool volume
//
// swagger:model
//
// API extension: storage.
type StorageVolumesPost struct {
	StorageVolumePut `yaml:",inline"`

	// Volume name
	// Example: foo
	Name string `json:"name" yaml:"name"`

	// Volume type (container, custom, image or virtual-machine)
	// Example: custom
	Type string `json:"type" yaml:"type"`

	// Migration source
	//
	// API extension: storage_api_local_volume_handling
	Source StorageVolumeSource `json:"source" yaml:"source"`

	// Volume content type (filesystem or block)
	// Example: filesystem
	//
	// API extension: custom_block_volumes
	ContentType string `json:"content_type" yaml:"content_type"`
}

// StorageVolumePost represents the fields required to rename a LXD storage pool volume
//
// swagger:model
//
// API extension: storage_api_volume_rename.
type StorageVolumePost struct {
	// New volume name
	// Example: foo
	Name string `json:"name" yaml:"name"`

	// New storage pool
	// Example: remote
	//
	// API extension: storage_api_local_volume_handling
	Pool string `json:"pool,omitempty" yaml:"pool,omitempty"`

	// Initiate volume migration
	// Example: false
	//
	// API extension: storage_api_remote_volume_handling
	Migration bool `json:"migration" yaml:"migration"`

	// Migration target (for push mode)
	//
	// API extension: storage_api_remote_volume_handling
	Target *StorageVolumePostTarget `json:"target" yaml:"target"`

	// Whether snapshots should be discarded (migration only)
	// Example: false
	//
	// API extension: storage_api_remote_volume_snapshots
	VolumeOnly bool `json:"volume_only" yaml:"volume_only"`

	// New project name
	// Example: foo
	//
	// API extension: storage_volume_project_move
	Project string `json:"project,omitempty" yaml:"project,omitempty"`
}

// StorageVolumePostTarget represents the migration target host and operation
//
// swagger:model
//
// API extension: storage_api_remote_volume_handling.
type StorageVolumePostTarget struct {
	// The certificate of the migration target
	// Example: X509 PEM certificate
	Certificate string `json:"certificate" yaml:"certificate"`

	// Remote operation URL (for migration)
	// Example: https://1.2.3.4:8443/1.0/operations/1721ae08-b6a8-416a-9614-3f89302466e1
	Operation string `json:"operation,omitempty" yaml:"operation,omitempty"`

	// Migration websockets credentials
	// Example: {"migration": "random-string"}
	Websockets map[string]string `json:"secrets,omitempty" yaml:"secrets,omitempty"`
}

// StorageVolume represents the fields of a LXD storage volume.
//
// swagger:model
//
// API extension: storage.
type StorageVolume struct {
	StorageVolumePut `yaml:",inline"`

	// Volume name
	// Example: foo
	Name string `json:"name" yaml:"name"`

	// Volume type
	// Example: custom
	Type string `json:"type" yaml:"type"`

	// List of URLs of objects using this storage volume
	// Example: ["/1.0/instances/blah"]
	UsedBy []string `json:"used_by" yaml:"used_by"`

	// What cluster member this record was found on
	// Example: lxd01
	//
	// API extension: clustering
	Location string `json:"location" yaml:"location"`

	// Volume content type (filesystem or block)
	// Example: filesystem
	//
	// API extension: custom_block_volumes
	ContentType string `json:"content_type" yaml:"content_type"`

	// Project containing the volume.
	// Example: default
	//
	// API extension: storage_volumes_all_projects
	Project string `json:"project" yaml:"project"`
}

// URL returns the URL for the volume.
func (v *StorageVolume) URL(apiVersion string, poolName string, projectName string) *URL {
	u := NewURL()

	volName, snapName, isSnap := GetParentAndSnapshotName(v.Name)
	if isSnap {
		u = u.Path(apiVersion, "storage-pools", poolName, "volumes", v.Type, volName, "snapshots", snapName)
	} else {
		u = u.Path(apiVersion, "storage-pools", poolName, "volumes", v.Type, volName)
	}

	return u.Project(projectName).Target(v.Location)
}

// StorageVolumePut represents the modifiable fields of a LXD storage volume
//
// swagger:model
//
// API extension: storage.
type StorageVolumePut struct {
	// Storage volume configuration map (refer to doc/storage.md)
	// Example: {"zfs.remove_snapshots": "true", "size": "50GiB"}
	Config map[string]string `json:"config" yaml:"config"`

	// Description of the storage volume
	// Example: My custom volume
	//
	// API extension: entity_description
	Description string `json:"description" yaml:"description"`

	// Name of a snapshot to restore
	// Example: snap0
	//
	// API extension: storage_api_volume_snapshots
	Restore string `json:"restore,omitempty" yaml:"restore,omitempty"`
}

// StorageVolumeSource represents the creation source for a new storage volume
//
// swagger:model
//
// API extension: storage_api_local_volume_handling.
type StorageVolumeSource struct {
	// Source volume name (for copy)
	// Example: foo
	Name string `json:"name" yaml:"name"`

	// Source type (copy or migration)
	// Example: copy
	Type string `json:"type" yaml:"type"`

	// Source storage pool (for copy)
	// Example: local
	Pool string `json:"pool" yaml:"pool"`

	// Certificate (for migration)
	// Example: X509 PEM certificate
	//
	// API extension: storage_api_remote_volume_handling
	Certificate string `json:"certificate" yaml:"certificate"`

	// Whether to use pull or push mode (for migration)
	// Example: pull
	//
	// API extension: storage_api_remote_volume_handling
	Mode string `json:"mode,omitempty" yaml:"mode,omitempty"`

	// Remote operation URL (for migration)
	// Example: https://1.2.3.4:8443/1.0/operations/1721ae08-b6a8-416a-9614-3f89302466e1
	//
	// API extension: storage_api_remote_volume_handling
	Operation string `json:"operation,omitempty" yaml:"operation,omitempty"`

	// Map of migration websockets (for migration)
	// Example: {"rsync": "RANDOM-STRING"}
	//
	// API extension: storage_api_remote_volume_handling
	Websockets map[string]string `json:"secrets,omitempty" yaml:"secrets,omitempty"`

	// Whether snapshots should be discarded (for migration)
	// Example: false
	//
	// API extension: storage_api_volume_snapshots
	VolumeOnly bool `json:"volume_only" yaml:"volume_only"`

	// Whether existing destination volume should be refreshed
	// Example: false
	//
	// API extension: custom_volume_refresh
	Refresh bool `json:"refresh" yaml:"refresh"`

	// Source project name
	// Example: foo
	//
	// API extension: storage_api_project
	Project string `json:"project,omitempty" yaml:"project,omitempty"`
}

// Writable converts a full StorageVolume struct into a StorageVolumePut struct (filters read-only fields).
func (storageVolume *StorageVolume) Writable() StorageVolumePut {
	return storageVolume.StorageVolumePut
}
<commit_msg>shared/api/storage/pool/volume: Remove projectName from URL function<commit_after>package api

// StorageVolumesPost represents the fields of a new LXD storage pool volume
//
// swagger:model
//
// API extension: storage.
type StorageVolumesPost struct {
	StorageVolumePut `yaml:",inline"`

	// Volume name
	// Example: foo
	Name string `json:"name" yaml:"name"`

	// Volume type (container, custom, image or virtual-machine)
	// Example: custom
	Type string `json:"type" yaml:"type"`

	// Migration source
	//
	// API extension: storage_api_local_volume_handling
	Source StorageVolumeSource `json:"source" yaml:"source"`

	// Volume content type (filesystem or block)
	// Example: filesystem
	//
	// API extension: custom_block_volumes
	ContentType string `json:"content_type" yaml:"content_type"`
}

// StorageVolumePost represents the fields required to rename a LXD storage pool volume
//
// swagger:model
//
// API extension: storage_api_volume_rename.
type StorageVolumePost struct {
	// New volume name
	// Example: foo
	Name string `json:"name" yaml:"name"`

	// New storage pool
	// Example: remote
	//
	// API extension: storage_api_local_volume_handling
	Pool string `json:"pool,omitempty" yaml:"pool,omitempty"`

	// Initiate volume migration
	// Example: false
	//
	// API extension: storage_api_remote_volume_handling
	Migration bool `json:"migration" yaml:"migration"`

	// Migration target (for push mode)
	//
	// API extension: storage_api_remote_volume_handling
	Target *StorageVolumePostTarget `json:"target" yaml:"target"`

	// Whether snapshots should be discarded (migration only)
	// Example: false
	//
	// API extension: storage_api_remote_volume_snapshots
	VolumeOnly bool `json:"volume_only" yaml:"volume_only"`

	// New project name
	// Example: foo
	//
	// API extension: storage_volume_project_move
	Project string `json:"project,omitempty" yaml:"project,omitempty"`
}

// StorageVolumePostTarget represents the migration target host and operation
//
// swagger:model
//
// API extension: storage_api_remote_volume_handling.
type StorageVolumePostTarget struct {
	// The certificate of the migration target
	// Example: X509 PEM certificate
	Certificate string `json:"certificate" yaml:"certificate"`

	// Remote operation URL (for migration)
	// Example: https://1.2.3.4:8443/1.0/operations/1721ae08-b6a8-416a-9614-3f89302466e1
	Operation string `json:"operation,omitempty" yaml:"operation,omitempty"`

	// Migration websockets credentials
	// Example: {"migration": "random-string"}
	Websockets map[string]string `json:"secrets,omitempty" yaml:"secrets,omitempty"`
}

// StorageVolume represents the fields of a LXD storage volume.
//
// swagger:model
//
// API extension: storage.
type StorageVolume struct {
	StorageVolumePut `yaml:",inline"`

	// Volume name
	// Example: foo
	Name string `json:"name" yaml:"name"`

	// Volume type
	// Example: custom
	Type string `json:"type" yaml:"type"`

	// List of URLs of objects using this storage volume
	// Example: ["/1.0/instances/blah"]
	UsedBy []string `json:"used_by" yaml:"used_by"`

	// What cluster member this record was found on
	// Example: lxd01
	//
	// API extension: clustering
	Location string `json:"location" yaml:"location"`

	// Volume content type (filesystem or block)
	// Example: filesystem
	//
	// API extension: custom_block_volumes
	ContentType string `json:"content_type" yaml:"content_type"`

	// Project containing the volume.
	// Example: default
	//
	// API extension: storage_volumes_all_projects
	Project string `json:"project" yaml:"project"`
}

// URL returns the URL for the volume.
func (v *StorageVolume) URL(apiVersion string, poolName string) *URL {
	u := NewURL()

	volName, snapName, isSnap := GetParentAndSnapshotName(v.Name)
	if isSnap {
		u = u.Path(apiVersion, "storage-pools", poolName, "volumes", v.Type, volName, "snapshots", snapName)
	} else {
		u = u.Path(apiVersion, "storage-pools", poolName, "volumes", v.Type, volName)
	}

	return u.Project(v.Project).Target(v.Location)
}

// StorageVolumePut represents the modifiable fields of a LXD storage volume
//
// swagger:model
//
// API extension: storage.
type StorageVolumePut struct {
	// Storage volume configuration map (refer to doc/storage.md)
	// Example: {"zfs.remove_snapshots": "true", "size": "50GiB"}
	Config map[string]string `json:"config" yaml:"config"`

	// Description of the storage volume
	// Example: My custom volume
	//
	// API extension: entity_description
	Description string `json:"description" yaml:"description"`

	// Name of a snapshot to restore
	// Example: snap0
	//
	// API extension: storage_api_volume_snapshots
	Restore string `json:"restore,omitempty" yaml:"restore,omitempty"`
}

// StorageVolumeSource represents the creation source for a new storage volume
//
// swagger:model
//
// API extension: storage_api_local_volume_handling.
type StorageVolumeSource struct {
	// Source volume name (for copy)
	// Example: foo
	Name string `json:"name" yaml:"name"`

	// Source type (copy or migration)
	// Example: copy
	Type string `json:"type" yaml:"type"`

	// Source storage pool (for copy)
	// Example: local
	Pool string `json:"pool" yaml:"pool"`

	// Certificate (for migration)
	// Example: X509 PEM certificate
	//
	// API extension: storage_api_remote_volume_handling
	Certificate string `json:"certificate" yaml:"certificate"`

	// Whether to use pull or push mode (for migration)
	// Example: pull
	//
	// API extension: storage_api_remote_volume_handling
	Mode string `json:"mode,omitempty" yaml:"mode,omitempty"`

	// Remote operation URL (for migration)
	// Example: https://1.2.3.4:8443/1.0/operations/1721ae08-b6a8-416a-9614-3f89302466e1
	//
	// API extension: storage_api_remote_volume_handling
	Operation string `json:"operation,omitempty" yaml:"operation,omitempty"`

	// Map of migration websockets (for migration)
	// Example: {"rsync": "RANDOM-STRING"}
	//
	// API extension: storage_api_remote_volume_handling
	Websockets map[string]string `json:"secrets,omitempty" yaml:"secrets,omitempty"`

	// Whether snapshots should be discarded (for migration)
	// Example: false
	//
	// API extension: storage_api_volume_snapshots
	VolumeOnly bool `json:"volume_only" yaml:"volume_only"`

	// Whether existing destination volume should be refreshed
	// Example: false
	//
	// API extension: custom_volume_refresh
	Refresh bool `json:"refresh" yaml:"refresh"`

	// Source project name
	// Example: foo
	//
	// API extension: storage_api_project
	Project string `json:"project,omitempty" yaml:"project,omitempty"`
}

// Writable converts a full StorageVolume struct into a StorageVolumePut struct (filters read-only fields).
func (storageVolume *StorageVolume) Writable() StorageVolumePut {
	return storageVolume.StorageVolumePut
}
<|endoftext|>


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

package util

import (
	"crypto/x509"
	"testing"
	"time"
)

var now = time.Now().Round(time.Second).UTC()

func TestGenCertKeyFromOptions(t *testing.T) {
	// set "notBefore" to be one hour ago, this ensures the issued certifiate to
	// be valid as of now.
	caCertNotBefore := now.Add(-time.Hour)
	caCertTTL := 24 * time.Hour

	// Options to generate a CA cert.
	caCertOptions := CertOptions{
		Host:         "test_ca.com",
		NotBefore:    caCertNotBefore,
		TTL:          caCertTTL,
		SignerCert:   nil,
		SignerPriv:   nil,
		Org:          "MyOrg",
		IsCA:         true,
		IsSelfSigned: true,
		IsClient:     false,
		IsServer:     true,
		RSAKeySize:   512,
	}

	caCertPem, caPrivPem, err := GenCertKeyFromOptions(caCertOptions)
	if err != nil {
		t.Error(err)
	}

	fields := &VerifyFields{
		NotBefore:   caCertNotBefore,
		TTL:         caCertTTL,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		KeyUsage:    x509.KeyUsageCertSign,
		IsCA:        true,
		Org:         "MyOrg",
	}
	if VerifyCertificate(caPrivPem, caCertPem, caCertPem, caCertOptions.Host, fields) != nil {
		t.Error(err)
	}

	caCert, err := ParsePemEncodedCertificate(caCertPem)
	if err != nil {
		t.Error(err)
	}

	caPriv, err := ParsePemEncodedKey(caPrivPem)
	if err != nil {
		t.Error(err)
	}

	notBefore := now.Add(-5 * time.Minute)
	ttl := time.Hour
	cases := []struct {
		name         string
		certOptions  CertOptions
		verifyFields *VerifyFields
	}{
		// These certs are signed by the CA cert
		{
			name: "Server cert with DNS SAN",
			certOptions: CertOptions{
				Host:         "test_server.com",
				NotBefore:    notBefore,
				TTL:          ttl,
				SignerCert:   caCert,
				SignerPriv:   caPriv,
				Org:          "",
				IsCA:         false,
				IsSelfSigned: false,
				IsClient:     false,
				IsServer:     true,
				RSAKeySize:   512,
			},
			verifyFields: &VerifyFields{
				ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
				IsCA:        false,
				KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
				NotBefore:   notBefore,
				TTL:         ttl,
				Org:         "MyOrg",
			},
		},
		{
			name: "Server and client cert with DNS SAN",
			certOptions: CertOptions{
				Host:         "test_client.com",
				NotBefore:    notBefore,
				TTL:          ttl,
				SignerCert:   caCert,
				SignerPriv:   caPriv,
				Org:          "",
				IsCA:         false,
				IsSelfSigned: false,
				IsClient:     true,
				IsServer:     true,
				RSAKeySize:   512,
			},
			verifyFields: &VerifyFields{
				ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
				IsCA:        false,
				KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
				NotBefore:   notBefore,
				TTL:         ttl,
				Org:         "MyOrg",
			},
		},
		{
			name: "Server cert with IP SAN",
			certOptions: CertOptions{
				Host:         "1.2.3.4",
				NotBefore:    notBefore,
				TTL:          ttl,
				SignerCert:   caCert,
				SignerPriv:   caPriv,
				Org:          "",
				IsCA:         false,
				IsSelfSigned: false,
				IsClient:     false,
				IsServer:     true,
				RSAKeySize:   512,
			},
			verifyFields: &VerifyFields{
				ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
				IsCA:        false,
				KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
				NotBefore:   notBefore,
				TTL:         ttl,
				Org:         "MyOrg",
			},
		},
		{
			name: "Client cert with URI SAN",
			certOptions: CertOptions{
				Host:         "spiffe://domain/ns/bar/sa/foo",
				NotBefore:    notBefore,
				TTL:          ttl,
				SignerCert:   caCert,
				SignerPriv:   caPriv,
				Org:          "",
				IsCA:         false,
				IsSelfSigned: false,
				IsClient:     true,
				IsServer:     true,
				RSAKeySize:   512,
			},
			verifyFields: &VerifyFields{
				ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
				IsCA:        false,
				KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
				NotBefore:   notBefore,
				TTL:         ttl,
				Org:         "MyOrg",
			},
		},
	}

	for _, c := range cases {
		certOptions := c.certOptions
		certPem, privPem, err := GenCertKeyFromOptions(certOptions)
		if err != nil {
			t.Errorf("[%s] cert/key generation error: %v", c.name, err)
		}
		if err := VerifyCertificate(privPem, certPem, caCertPem, certOptions.Host, c.verifyFields); err != nil {
			t.Errorf("[%s] cert verification error: %v", c.name, err)
		}
	}
}

// TODO(myidpt): Add test cases for GenCertFromCSR.

func TestLoadSignerCredsFromFiles(t *testing.T) {
	testCases := map[string]struct {
		certFile    string
		keyFile     string
		expectedErr string
	}{
		"Good certificates": {
			certFile:    "../testdata/cert.pem",
			keyFile:     "../testdata/key.pem",
			expectedErr: "",
		},
		"Missing cert files": {
			certFile:    "../testdata/cert-not-exist.pem",
			keyFile:     "../testdata/key.pem",
			expectedErr: "certificate file reading failure (open ../testdata/cert-not-exist.pem: no such file or directory)",
		},
		"Missing key files": {
			certFile:    "../testdata/cert.pem",
			keyFile:     "../testdata/key-not-exist.pem",
			expectedErr: "private key file reading failure (open ../testdata/key-not-exist.pem: no such file or directory)",
		},
		"Bad cert files": {
			certFile:    "../testdata/cert-bad.pem",
			keyFile:     "../testdata/key.pem",
			expectedErr: "pem encoded cert parsing failure (invalid PEM encoded certificate)",
		},
		"Bad key files": {
			certFile:    "../testdata/cert.pem",
			keyFile:     "../testdata/key-bad.pem",
			expectedErr: "pem encoded key parsing failure (invalid PEM-encoded key)",
		},
	}

	for id, tc := range testCases {
		cert, key, err := LoadSignerCredsFromFiles(tc.certFile, tc.keyFile)
		if len(tc.expectedErr) > 0 {
			if err == nil {
				t.Errorf("[%s] Succeeded. Error expected: %v", id, err)
			} else if err.Error() != tc.expectedErr {
				t.Errorf("[%s] incorrect error message: %s VS (expected) %s",
					id, err.Error(), tc.expectedErr)
			}
			continue
		} else if err != nil {
			t.Fatalf("[%s] Unexpected Error: %v", id, err)
		}

		if cert == nil || key == nil {
			t.Errorf("[%s] Faild to load signer credeitials from files: %v, %v", id, tc.certFile, tc.keyFile)
		}
	}
}
<commit_msg>Restoring #4055 (#4067)<commit_after>// Copyright 2017 Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"math/big"
	"reflect"
	"testing"
	"time"
)

var now = time.Now().Round(time.Second).UTC()

func TestGenCertKeyFromOptions(t *testing.T) {
	// set "notBefore" to be one hour ago, this ensures the issued certifiate to
	// be valid as of now.
	caCertNotBefore := now.Add(-time.Hour)
	caCertTTL := 24 * time.Hour

	// Options to generate a CA cert.
	caCertOptions := CertOptions{
		Host:         "test_ca.com",
		NotBefore:    caCertNotBefore,
		TTL:          caCertTTL,
		SignerCert:   nil,
		SignerPriv:   nil,
		Org:          "MyOrg",
		IsCA:         true,
		IsSelfSigned: true,
		IsClient:     false,
		IsServer:     true,
		RSAKeySize:   512,
	}

	caCertPem, caPrivPem, err := GenCertKeyFromOptions(caCertOptions)
	if err != nil {
		t.Error(err)
	}

	fields := &VerifyFields{
		NotBefore:   caCertNotBefore,
		TTL:         caCertTTL,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		KeyUsage:    x509.KeyUsageCertSign,
		IsCA:        true,
		Org:         "MyOrg",
	}
	if VerifyCertificate(caPrivPem, caCertPem, caCertPem, caCertOptions.Host, fields) != nil {
		t.Error(err)
	}

	caCert, err := ParsePemEncodedCertificate(caCertPem)
	if err != nil {
		t.Error(err)
	}

	caPriv, err := ParsePemEncodedKey(caPrivPem)
	if err != nil {
		t.Error(err)
	}

	notBefore := now.Add(-5 * time.Minute)
	ttl := time.Hour
	cases := []struct {
		name         string
		certOptions  CertOptions
		verifyFields *VerifyFields
	}{
		// These certs are signed by the CA cert
		{
			name: "Server cert with DNS SAN",
			certOptions: CertOptions{
				Host:         "test_server.com",
				NotBefore:    notBefore,
				TTL:          ttl,
				SignerCert:   caCert,
				SignerPriv:   caPriv,
				Org:          "",
				IsCA:         false,
				IsSelfSigned: false,
				IsClient:     false,
				IsServer:     true,
				RSAKeySize:   512,
			},
			verifyFields: &VerifyFields{
				ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
				IsCA:        false,
				KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
				NotBefore:   notBefore,
				TTL:         ttl,
				Org:         "MyOrg",
			},
		},
		{
			name: "Server and client cert with DNS SAN",
			certOptions: CertOptions{
				Host:         "test_client.com",
				NotBefore:    notBefore,
				TTL:          ttl,
				SignerCert:   caCert,
				SignerPriv:   caPriv,
				Org:          "",
				IsCA:         false,
				IsSelfSigned: false,
				IsClient:     true,
				IsServer:     true,
				RSAKeySize:   512,
			},
			verifyFields: &VerifyFields{
				ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
				IsCA:        false,
				KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
				NotBefore:   notBefore,
				TTL:         ttl,
				Org:         "MyOrg",
			},
		},
		{
			name: "Server cert with IP SAN",
			certOptions: CertOptions{
				Host:         "1.2.3.4",
				NotBefore:    notBefore,
				TTL:          ttl,
				SignerCert:   caCert,
				SignerPriv:   caPriv,
				Org:          "",
				IsCA:         false,
				IsSelfSigned: false,
				IsClient:     false,
				IsServer:     true,
				RSAKeySize:   512,
			},
			verifyFields: &VerifyFields{
				ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
				IsCA:        false,
				KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
				NotBefore:   notBefore,
				TTL:         ttl,
				Org:         "MyOrg",
			},
		},
		{
			name: "Client cert with URI SAN",
			certOptions: CertOptions{
				Host:         "spiffe://domain/ns/bar/sa/foo",
				NotBefore:    notBefore,
				TTL:          ttl,
				SignerCert:   caCert,
				SignerPriv:   caPriv,
				Org:          "",
				IsCA:         false,
				IsSelfSigned: false,
				IsClient:     true,
				IsServer:     true,
				RSAKeySize:   512,
			},
			verifyFields: &VerifyFields{
				ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
				IsCA:        false,
				KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
				NotBefore:   notBefore,
				TTL:         ttl,
				Org:         "MyOrg",
			},
		},
	}

	for _, c := range cases {
		certOptions := c.certOptions
		certPem, privPem, err := GenCertKeyFromOptions(certOptions)
		if err != nil {
			t.Errorf("[%s] cert/key generation error: %v", c.name, err)
		}
		if err := VerifyCertificate(privPem, certPem, caCertPem, certOptions.Host, c.verifyFields); err != nil {
			t.Errorf("[%s] cert verification error: %v", c.name, err)
		}
	}
}

func TestGenCertFromCSR(t *testing.T) {
	// First Creates a self-signed CA cert.
	caPK, err := rsa.GenerateKey(rand.Reader, 1024)
	if err != nil {
		t.Errorf("failed to generate ca's key pair %v", err)
	}
	caTmpl := &x509.Certificate{
		SerialNumber:          big.NewInt(-1),
		NotBefore:             time.Now().Add(-time.Hour),
		NotAfter:              time.Now().Add(time.Hour),
		SignatureAlgorithm:    x509.SHA256WithRSA,
		KeyUsage:              x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA: true,
	}
	der, err := x509.CreateCertificate(rand.Reader, caTmpl, caTmpl, &caPK.PublicKey, caPK)
	if err != nil {
		t.Errorf("failed to Create self signed ca cert %v", err)
	}
	caCert, err := x509.ParseCertificate(der)
	if err != nil {
		t.Errorf("failed to parse generated ca certificate %v", err)
	}

	// Then generates signee's key pairs.
	signeePK, err := rsa.GenerateKey(rand.Reader, 1024)
	if err != nil {
		t.Errorf("failed to generate signee key pair %v", err)
	}

	tmpl := &x509.CertificateRequest{
		SignatureAlgorithm: x509.SHA256WithRSA,
		DNSNames:           []string{"test.example.com"},
		Version:            3,
	}
	derBytes, err := x509.CreateCertificateRequest(rand.Reader, tmpl, signeePK)
	if err != nil {
		t.Error("failed to create certificate request")
	}
	csr, err := x509.ParseCertificateRequest(derBytes)
	if err != nil {
		t.Errorf("failed to parse certificate request %v", err)
	}

	derBytes, err = GenCertFromCSR(csr, caCert, &signeePK.PublicKey, caPK, time.Hour, false)
	if err != nil {
		t.Errorf("failed to GenCertFromCSR, error %v", err)
	}

	// Verifies the certificate.
	out, err := x509.ParseCertificate(derBytes)
	if err != nil {
		t.Errorf("failed to parse generated certificate %v", err)
	}
	if !reflect.DeepEqual(out.DNSNames, tmpl.DNSNames) {
		t.Errorf("generated cert dns name is unexpected, got %v, want %v", out.DNSNames, tmpl.DNSNames)
	}
	pool := x509.NewCertPool()
	pool.AddCert(caCert)
	vo := x509.VerifyOptions{
		Roots: pool,
	}
	if _, err := out.Verify(vo); err != nil {
		t.Errorf("verification of the signed certificate failed %v", err)
	}
}

func TestLoadSignerCredsFromFiles(t *testing.T) {
	testCases := map[string]struct {
		certFile    string
		keyFile     string
		expectedErr string
	}{
		"Good certificates": {
			certFile:    "../testdata/cert.pem",
			keyFile:     "../testdata/key.pem",
			expectedErr: "",
		},
		"Missing cert files": {
			certFile:    "../testdata/cert-not-exist.pem",
			keyFile:     "../testdata/key.pem",
			expectedErr: "certificate file reading failure (open ../testdata/cert-not-exist.pem: no such file or directory)",
		},
		"Missing key files": {
			certFile:    "../testdata/cert.pem",
			keyFile:     "../testdata/key-not-exist.pem",
			expectedErr: "private key file reading failure (open ../testdata/key-not-exist.pem: no such file or directory)",
		},
		"Bad cert files": {
			certFile:    "../testdata/cert-bad.pem",
			keyFile:     "../testdata/key.pem",
			expectedErr: "pem encoded cert parsing failure (invalid PEM encoded certificate)",
		},
		"Bad key files": {
			certFile:    "../testdata/cert.pem",
			keyFile:     "../testdata/key-bad.pem",
			expectedErr: "pem encoded key parsing failure (invalid PEM-encoded key)",
		},
	}

	for id, tc := range testCases {
		cert, key, err := LoadSignerCredsFromFiles(tc.certFile, tc.keyFile)
		if len(tc.expectedErr) > 0 {
			if err == nil {
				t.Errorf("[%s] Succeeded. Error expected: %v", id, err)
			} else if err.Error() != tc.expectedErr {
				t.Errorf("[%s] incorrect error message: %s VS (expected) %s",
					id, err.Error(), tc.expectedErr)
			}
			continue
		} else if err != nil {
			t.Fatalf("[%s] Unexpected Error: %v", id, err)
		}

		if cert == nil || key == nil {
			t.Errorf("[%s] Faild to load signer credeitials from files: %v, %v", id, tc.certFile, tc.keyFile)
		}
	}
}
<|endoftext|>


<commit_before>// +build integration

package cache

import (
	"context"
	"testing"
	"time"

	"github.com/zitryss/aye-and-nay/domain/model"
	_ "github.com/zitryss/aye-and-nay/internal/config"
	"github.com/zitryss/aye-and-nay/pkg/errors"
)

func TestRedisQueue(t *testing.T) {
	redis, err := NewRedis()
	if err != nil {
		t.Fatal(err)
	}
	n, err := redis.Size(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if n != 0 {
		t.Error("n != 0")
	}
	err = redis.Add(context.Background(), 0x5D6D, 0x1ED1)
	if err != nil {
		t.Error(err)
	}
	err = redis.Add(context.Background(), 0x5D6D, 0x1ED1)
	if err != nil {
		t.Error(err)
	}
	err = redis.Add(context.Background(), 0x5D6D, 0xF612)
	if err != nil {
		t.Error(err)
	}
	err = redis.Add(context.Background(), 0x5D6D, 0x1A83)
	if err != nil {
		t.Error(err)
	}
	err = redis.Add(context.Background(), 0x5D6D, 0xF612)
	if err != nil {
		t.Error(err)
	}
	n, err = redis.Size(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if n != 3 {
		t.Error("n != 3")
	}
	album, err := redis.Poll(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if album != 0x1ED1 {
		t.Error("album != 0x1ED1")
	}
	n, err = redis.Size(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if n != 2 {
		t.Error("n != 2")
	}
	album, err = redis.Poll(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if album != 0xF612 {
		t.Error("album != 0xF612")
	}
	album, err = redis.Poll(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if album != 0x1A83 {
		t.Error("album != 0x1A83")
	}
	n, err = redis.Size(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if n != 0 {
		t.Error("n != 0")
	}
	album, err = redis.Poll(context.Background(), 0x5D6D)
	if err == nil {
		t.Error(err)
	}
	if album != 0x0 {
		t.Error("album != \"0x0\"")
	}
	n, err = redis.Size(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if n != 0 {
		t.Error("n != 0")
	}
	_, err = redis.Poll(context.Background(), 0x5D6D)
	if !errors.Is(err, model.ErrUnknown) {
		t.Error(err)
	}
}

func TestRedisPQueue(t *testing.T) {
	redis, err := NewRedis()
	if err != nil {
		t.Fatal(err)
	}
	n, err := redis.PSize(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if n != 0 {
		t.Error("n != 0")
	}
	err = redis.PAdd(context.Background(), 0x7D31, 0xE976, time.Unix(904867200, 0))
	if err != nil {
		t.Error(err)
	}
	err = redis.PAdd(context.Background(), 0x7D31, 0xEC0E, time.Unix(1075852800, 0))
	if err != nil {
		t.Error(err)
	}
	err = redis.PAdd(context.Background(), 0x7D31, 0x4CAF, time.Unix(681436800, 0))
	if err != nil {
		t.Error(err)
	}
	n, err = redis.PSize(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if n != 3 {
		t.Error("n != 3")
	}
	album, expires, err := redis.PPoll(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if album != 0x4CAF {
		t.Error("album != 0x4CAF")
	}
	if !expires.Equal(time.Unix(681436800, 0)) {
		t.Error("!expires.Equal(time.Unix(681436800, 0))")
	}
	n, err = redis.PSize(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if n != 2 {
		t.Error("n != 2")
	}
	album, expires, err = redis.PPoll(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if album != 0xE976 {
		t.Error("album != 0xE976")
	}
	if !expires.Equal(time.Unix(904867200, 0)) {
		t.Error("!expires.Equal(time.Unix(904867200, 0))")
	}
	album, expires, err = redis.PPoll(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if album != 0xEC0E {
		t.Error("album != 0xEC0E")
	}
	if !expires.Equal(time.Unix(1075852800, 0)) {
		t.Error("!expires.Equal(time.Unix(1075852800, 0))")
	}
	n, err = redis.PSize(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if n != 0 {
		t.Error("n != 0")
	}
	_, _, err = redis.PPoll(context.Background(), 0x7D31)
	if !errors.Is(err, model.ErrUnknown) {
		t.Error(err)
	}
}

func TestRedisPair(t *testing.T) {
	t.Run("Positive", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		image1 := uint64(0x3E3D)
		image2 := uint64(0xB399)
		err = redis.Push(context.Background(), 0x23D2, [][2]uint64{{image1, image2}})
		if err != nil {
			t.Error(err)
		}
		image3, image4, err := redis.Pop(context.Background(), 0x23D2)
		if err != nil {
			t.Error(err)
		}
		if image1 != image3 {
			t.Error("image1 != image3")
		}
		if image2 != image4 {
			t.Error("image2 != image4")
		}
	})
	t.Run("Negative1", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		_, _, err = redis.Pop(context.Background(), 0x73BF)
		if !errors.Is(err, model.ErrPairNotFound) {
			t.Error(err)
		}
	})
	t.Run("Negative2", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		image1 := uint64(0x44DC)
		image2 := uint64(0x721B)
		err = redis.Push(context.Background(), 0x1AE9, [][2]uint64{{image1, image2}})
		if err != nil {
			t.Error(err)
		}
		_, _, err = redis.Pop(context.Background(), 0x1AE9)
		if err != nil {
			t.Error(err)
		}
		_, _, err = redis.Pop(context.Background(), 0x1AE9)
		if !errors.Is(err, model.ErrPairNotFound) {
			t.Error(err)
		}
	})
}

func TestRedisToken(t *testing.T) {
	t.Run("Positive", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		image1 := uint64(0x52BD)
		token := uint64(0xB41C)
		err = redis.Set(context.Background(), 0xC2E7, token, image1)
		if err != nil {
			t.Error(err)
		}
		image2, err := redis.Get(context.Background(), 0xC2E7, token)
		if err != nil {
			t.Error(err)
		}
		if image1 != image2 {
			t.Error("image1 != image2")
		}
	})
	t.Run("Negative1", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		image := uint64(0x583C)
		token := uint64(0xF0EE)
		err = redis.Set(context.Background(), 0x1C4A, token, image)
		if err != nil {
			t.Error(err)
		}
		err = redis.Set(context.Background(), 0x1C4A, token, image)
		if !errors.Is(err, model.ErrTokenAlreadyExists) {
			t.Error(err)
		}
	})
	t.Run("Negative2", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		_, err = redis.Get(context.Background(), 0x1C4A, 0xC4F8)
		if !errors.Is(err, model.ErrTokenNotFound) {
			t.Error(err)
		}
	})
	t.Run("Negative3", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		image := uint64(0x7C45)
		token := uint64(0xC67F)
		err = redis.Set(context.Background(), 0xEB96, token, image)
		if err != nil {
			t.Error(err)
		}
		_, err = redis.Get(context.Background(), 0xEB96, token)
		if err != nil {
			t.Error(err)
		}
		_, err = redis.Get(context.Background(), 0xEB96, token)
		if !errors.Is(err, model.ErrTokenNotFound) {
			t.Error(err)
		}
	})
}
<commit_msg>Test Redis rate limiter<commit_after>// +build integration

package cache

import (
	"context"
	"testing"
	"time"

	"github.com/zitryss/aye-and-nay/domain/model"
	_ "github.com/zitryss/aye-and-nay/internal/config"
	"github.com/zitryss/aye-and-nay/pkg/errors"
)

func TestRedisAllow(t *testing.T) {
	t.Run("Positive", func(t *testing.T) {
		if testing.Short() {
			t.Skip("short flag is set")
		}
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		rpm := redis.conf.limiterRequestsPerMinute
		for j := 0; j < rpm; j++ {
			allowed, err := redis.Allow(context.Background(), 0xDEAD)
			if err != nil {
				t.Error(err)
			}
			if !allowed {
				t.Error("!allowed")
			}
		}
		time.Sleep(60 * time.Second)
		for j := 0; j < rpm; j++ {
			allowed, err := redis.Allow(context.Background(), 0xDEAD)
			if err != nil {
				t.Error(err)
			}
			if !allowed {
				t.Error("!allowed")
			}
		}
	})
	t.Run("Negative", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		rps := redis.conf.limiterRequestsPerMinute
		for i := 0; i < rps; i++ {
			allowed, err := redis.Allow(context.Background(), 0xBEEF)
			if err != nil {
				t.Error(err)
			}
			if !allowed {
				t.Error("!allowed")
			}
		}
		allowed, err := redis.Allow(context.Background(), 0xBEEF)
		if err != nil {
			t.Error(err)
		}
		if allowed {
			t.Error("allowed")
		}
	})
}

func TestRedisQueue(t *testing.T) {
	redis, err := NewRedis()
	if err != nil {
		t.Fatal(err)
	}
	n, err := redis.Size(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if n != 0 {
		t.Error("n != 0")
	}
	err = redis.Add(context.Background(), 0x5D6D, 0x1ED1)
	if err != nil {
		t.Error(err)
	}
	err = redis.Add(context.Background(), 0x5D6D, 0x1ED1)
	if err != nil {
		t.Error(err)
	}
	err = redis.Add(context.Background(), 0x5D6D, 0xF612)
	if err != nil {
		t.Error(err)
	}
	err = redis.Add(context.Background(), 0x5D6D, 0x1A83)
	if err != nil {
		t.Error(err)
	}
	err = redis.Add(context.Background(), 0x5D6D, 0xF612)
	if err != nil {
		t.Error(err)
	}
	n, err = redis.Size(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if n != 3 {
		t.Error("n != 3")
	}
	album, err := redis.Poll(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if album != 0x1ED1 {
		t.Error("album != 0x1ED1")
	}
	n, err = redis.Size(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if n != 2 {
		t.Error("n != 2")
	}
	album, err = redis.Poll(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if album != 0xF612 {
		t.Error("album != 0xF612")
	}
	album, err = redis.Poll(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if album != 0x1A83 {
		t.Error("album != 0x1A83")
	}
	n, err = redis.Size(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if n != 0 {
		t.Error("n != 0")
	}
	album, err = redis.Poll(context.Background(), 0x5D6D)
	if err == nil {
		t.Error(err)
	}
	if album != 0x0 {
		t.Error("album != \"0x0\"")
	}
	n, err = redis.Size(context.Background(), 0x5D6D)
	if err != nil {
		t.Error(err)
	}
	if n != 0 {
		t.Error("n != 0")
	}
	_, err = redis.Poll(context.Background(), 0x5D6D)
	if !errors.Is(err, model.ErrUnknown) {
		t.Error(err)
	}
}

func TestRedisPQueue(t *testing.T) {
	redis, err := NewRedis()
	if err != nil {
		t.Fatal(err)
	}
	n, err := redis.PSize(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if n != 0 {
		t.Error("n != 0")
	}
	err = redis.PAdd(context.Background(), 0x7D31, 0xE976, time.Unix(904867200, 0))
	if err != nil {
		t.Error(err)
	}
	err = redis.PAdd(context.Background(), 0x7D31, 0xEC0E, time.Unix(1075852800, 0))
	if err != nil {
		t.Error(err)
	}
	err = redis.PAdd(context.Background(), 0x7D31, 0x4CAF, time.Unix(681436800, 0))
	if err != nil {
		t.Error(err)
	}
	n, err = redis.PSize(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if n != 3 {
		t.Error("n != 3")
	}
	album, expires, err := redis.PPoll(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if album != 0x4CAF {
		t.Error("album != 0x4CAF")
	}
	if !expires.Equal(time.Unix(681436800, 0)) {
		t.Error("!expires.Equal(time.Unix(681436800, 0))")
	}
	n, err = redis.PSize(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if n != 2 {
		t.Error("n != 2")
	}
	album, expires, err = redis.PPoll(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if album != 0xE976 {
		t.Error("album != 0xE976")
	}
	if !expires.Equal(time.Unix(904867200, 0)) {
		t.Error("!expires.Equal(time.Unix(904867200, 0))")
	}
	album, expires, err = redis.PPoll(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if album != 0xEC0E {
		t.Error("album != 0xEC0E")
	}
	if !expires.Equal(time.Unix(1075852800, 0)) {
		t.Error("!expires.Equal(time.Unix(1075852800, 0))")
	}
	n, err = redis.PSize(context.Background(), 0x7D31)
	if err != nil {
		t.Error(err)
	}
	if n != 0 {
		t.Error("n != 0")
	}
	_, _, err = redis.PPoll(context.Background(), 0x7D31)
	if !errors.Is(err, model.ErrUnknown) {
		t.Error(err)
	}
}

func TestRedisPair(t *testing.T) {
	t.Run("Positive", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		image1 := uint64(0x3E3D)
		image2 := uint64(0xB399)
		err = redis.Push(context.Background(), 0x23D2, [][2]uint64{{image1, image2}})
		if err != nil {
			t.Error(err)
		}
		image3, image4, err := redis.Pop(context.Background(), 0x23D2)
		if err != nil {
			t.Error(err)
		}
		if image1 != image3 {
			t.Error("image1 != image3")
		}
		if image2 != image4 {
			t.Error("image2 != image4")
		}
	})
	t.Run("Negative1", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		_, _, err = redis.Pop(context.Background(), 0x73BF)
		if !errors.Is(err, model.ErrPairNotFound) {
			t.Error(err)
		}
	})
	t.Run("Negative2", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		image1 := uint64(0x44DC)
		image2 := uint64(0x721B)
		err = redis.Push(context.Background(), 0x1AE9, [][2]uint64{{image1, image2}})
		if err != nil {
			t.Error(err)
		}
		_, _, err = redis.Pop(context.Background(), 0x1AE9)
		if err != nil {
			t.Error(err)
		}
		_, _, err = redis.Pop(context.Background(), 0x1AE9)
		if !errors.Is(err, model.ErrPairNotFound) {
			t.Error(err)
		}
	})
}

func TestRedisToken(t *testing.T) {
	t.Run("Positive", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		image1 := uint64(0x52BD)
		token := uint64(0xB41C)
		err = redis.Set(context.Background(), 0xC2E7, token, image1)
		if err != nil {
			t.Error(err)
		}
		image2, err := redis.Get(context.Background(), 0xC2E7, token)
		if err != nil {
			t.Error(err)
		}
		if image1 != image2 {
			t.Error("image1 != image2")
		}
	})
	t.Run("Negative1", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		image := uint64(0x583C)
		token := uint64(0xF0EE)
		err = redis.Set(context.Background(), 0x1C4A, token, image)
		if err != nil {
			t.Error(err)
		}
		err = redis.Set(context.Background(), 0x1C4A, token, image)
		if !errors.Is(err, model.ErrTokenAlreadyExists) {
			t.Error(err)
		}
	})
	t.Run("Negative2", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		_, err = redis.Get(context.Background(), 0x1C4A, 0xC4F8)
		if !errors.Is(err, model.ErrTokenNotFound) {
			t.Error(err)
		}
	})
	t.Run("Negative3", func(t *testing.T) {
		redis, err := NewRedis()
		if err != nil {
			t.Fatal(err)
		}
		image := uint64(0x7C45)
		token := uint64(0xC67F)
		err = redis.Set(context.Background(), 0xEB96, token, image)
		if err != nil {
			t.Error(err)
		}
		_, err = redis.Get(context.Background(), 0xEB96, token)
		if err != nil {
			t.Error(err)
		}
		_, err = redis.Get(context.Background(), 0xEB96, token)
		if !errors.Is(err, model.ErrTokenNotFound) {
			t.Error(err)
		}
	})
}
<|endoftext|>


<commit_before>// +build !windows

package main

import (
	"bufio"
	"os/exec"
	"strings"
	"time"

	"github.com/docker/docker/pkg/stringid"
	"github.com/go-check/check"
	"github.com/kr/pty"
)

// #9860 Make sure attach ends when container ends (with no errors)
func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) {

	out, _ := dockerCmd(c, "run", "-dti", "busybox", "/bin/sh", "-c", `trap 'exit 0' SIGTERM; while true; do sleep 1; done`)

	id := strings.TrimSpace(out)
	c.Assert(waitRun(id), check.IsNil)

	_, tty, err := pty.Open()
	c.Assert(err, check.IsNil)

	attachCmd := exec.Command(dockerBinary, "attach", id)
	attachCmd.Stdin = tty
	attachCmd.Stdout = tty
	attachCmd.Stderr = tty
	err = attachCmd.Start()
	c.Assert(err, check.IsNil)

	errChan := make(chan error)
	go func() {
		defer close(errChan)
		// Container is wating for us to signal it to stop
		dockerCmd(c, "stop", id)
		// And wait for the attach command to end
		errChan <- attachCmd.Wait()
	}()

	// Wait for the docker to end (should be done by the
	// stop command in the go routine)
	dockerCmd(c, "wait", id)

	select {
	case err := <-errChan:
		c.Assert(err, check.IsNil)
	case <-time.After(attachWait):
		c.Fatal("timed out without attach returning")
	}

}

func (s *DockerSuite) TestAttachAfterDetach(c *check.C) {

	name := "detachtest"

	cpty, tty, err := pty.Open()
	if err != nil {
		c.Fatalf("Could not open pty: %v", err)
	}
	cmd := exec.Command(dockerBinary, "run", "-ti", "--name", name, "busybox")
	cmd.Stdin = tty
	cmd.Stdout = tty
	cmd.Stderr = tty

	errChan := make(chan error)
	go func() {
		errChan <- cmd.Run()
		close(errChan)
	}()

	c.Assert(waitRun(name), check.IsNil)

	cpty.Write([]byte{16})
	time.Sleep(100 * time.Millisecond)
	cpty.Write([]byte{17})

	select {
	case err := <-errChan:
		c.Assert(err, check.IsNil)
	case <-time.After(5 * time.Second):
		c.Fatal("timeout while detaching")
	}

	cpty, tty, err = pty.Open()
	if err != nil {
		c.Fatalf("Could not open pty: %v", err)
	}

	cmd = exec.Command(dockerBinary, "attach", name)
	cmd.Stdin = tty
	cmd.Stdout = tty
	cmd.Stderr = tty

	if err := cmd.Start(); err != nil {
		c.Fatal(err)
	}

	bytes := make([]byte, 10)
	var nBytes int
	readErr := make(chan error, 1)

	go func() {
		time.Sleep(500 * time.Millisecond)
		cpty.Write([]byte("\n"))
		time.Sleep(500 * time.Millisecond)

		nBytes, err = cpty.Read(bytes)
		cpty.Close()
		readErr <- err
	}()

	select {
	case err := <-readErr:
		c.Assert(err, check.IsNil)
	case <-time.After(2 * time.Second):
		c.Fatal("timeout waiting for attach read")
	}

	if err := cmd.Wait(); err != nil {
		c.Fatal(err)
	}

	if !strings.Contains(string(bytes[:nBytes]), "/ #") {
		c.Fatalf("failed to get a new prompt. got %s", string(bytes[:nBytes]))
	}

}

// TestAttachDetach checks that attach in tty mode can be detached using the long container ID
func (s *DockerSuite) TestAttachDetach(c *check.C) {
	out, _ := dockerCmd(c, "run", "-itd", "busybox", "cat")
	id := strings.TrimSpace(out)
	c.Assert(waitRun(id), check.IsNil)

	cpty, tty, err := pty.Open()
	if err != nil {
		c.Fatal(err)
	}
	defer cpty.Close()

	cmd := exec.Command(dockerBinary, "attach", id)
	cmd.Stdin = tty
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		c.Fatal(err)
	}
	defer stdout.Close()
	if err := cmd.Start(); err != nil {
		c.Fatal(err)
	}
	c.Assert(waitRun(id), check.IsNil)

	if _, err := cpty.Write([]byte("hello\n")); err != nil {
		c.Fatal(err)
	}
	out, err = bufio.NewReader(stdout).ReadString('\n')
	if err != nil {
		c.Fatal(err)
	}
	if strings.TrimSpace(out) != "hello" {
		c.Fatalf("expected 'hello', got %q", out)
	}

	// escape sequence
	if _, err := cpty.Write([]byte{16}); err != nil {
		c.Fatal(err)
	}
	time.Sleep(100 * time.Millisecond)
	if _, err := cpty.Write([]byte{17}); err != nil {
		c.Fatal(err)
	}

	ch := make(chan struct{})
	go func() {
		cmd.Wait()
		ch <- struct{}{}
	}()

	running, err := inspectField(id, "State.Running")
	if err != nil {
		c.Fatal(err)
	}
	if running != "true" {
		c.Fatal("expected container to still be running")
	}

	go func() {
		dockerCmd(c, "kill", id)
	}()

	select {
	case <-ch:
	case <-time.After(10 * time.Millisecond):
		c.Fatal("timed out waiting for container to exit")
	}

}

// TestAttachDetachTruncatedID checks that attach in tty mode can be detached
func (s *DockerSuite) TestAttachDetachTruncatedID(c *check.C) {
	out, _ := dockerCmd(c, "run", "-itd", "busybox", "cat")
	id := stringid.TruncateID(strings.TrimSpace(out))
	c.Assert(waitRun(id), check.IsNil)

	cpty, tty, err := pty.Open()
	if err != nil {
		c.Fatal(err)
	}
	defer cpty.Close()

	cmd := exec.Command(dockerBinary, "attach", id)
	cmd.Stdin = tty
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		c.Fatal(err)
	}
	defer stdout.Close()
	if err := cmd.Start(); err != nil {
		c.Fatal(err)
	}

	if _, err := cpty.Write([]byte("hello\n")); err != nil {
		c.Fatal(err)
	}
	out, err = bufio.NewReader(stdout).ReadString('\n')
	if err != nil {
		c.Fatal(err)
	}
	if strings.TrimSpace(out) != "hello" {
		c.Fatalf("expected 'hello', got %q", out)
	}

	// escape sequence
	if _, err := cpty.Write([]byte{16}); err != nil {
		c.Fatal(err)
	}
	time.Sleep(100 * time.Millisecond)
	if _, err := cpty.Write([]byte{17}); err != nil {
		c.Fatal(err)
	}

	ch := make(chan struct{})
	go func() {
		cmd.Wait()
		ch <- struct{}{}
	}()

	running, err := inspectField(id, "State.Running")
	if err != nil {
		c.Fatal(err)
	}
	if running != "true" {
		c.Fatal("expected container to still be running")
	}

	go func() {
		dockerCmd(c, "kill", id)
	}()

	select {
	case <-ch:
	case <-time.After(10 * time.Millisecond):
		c.Fatal("timed out waiting for container to exit")
	}

}
<commit_msg>Using checkers assert for integration-cli/docker_cli_attach_unix_test.go<commit_after>// +build !windows

package main

import (
	"bufio"
	"os/exec"
	"strings"
	"time"

	"github.com/docker/docker/pkg/integration/checker"
	"github.com/docker/docker/pkg/stringid"
	"github.com/go-check/check"
	"github.com/kr/pty"
)

// #9860 Make sure attach ends when container ends (with no errors)
func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) {

	out, _ := dockerCmd(c, "run", "-dti", "busybox", "/bin/sh", "-c", `trap 'exit 0' SIGTERM; while true; do sleep 1; done`)

	id := strings.TrimSpace(out)
	c.Assert(waitRun(id), check.IsNil)

	_, tty, err := pty.Open()
	c.Assert(err, check.IsNil)

	attachCmd := exec.Command(dockerBinary, "attach", id)
	attachCmd.Stdin = tty
	attachCmd.Stdout = tty
	attachCmd.Stderr = tty
	err = attachCmd.Start()
	c.Assert(err, check.IsNil)

	errChan := make(chan error)
	go func() {
		defer close(errChan)
		// Container is wating for us to signal it to stop
		dockerCmd(c, "stop", id)
		// And wait for the attach command to end
		errChan <- attachCmd.Wait()
	}()

	// Wait for the docker to end (should be done by the
	// stop command in the go routine)
	dockerCmd(c, "wait", id)

	select {
	case err := <-errChan:
		c.Assert(err, check.IsNil)
	case <-time.After(attachWait):
		c.Fatal("timed out without attach returning")
	}

}

func (s *DockerSuite) TestAttachAfterDetach(c *check.C) {

	name := "detachtest"

	cpty, tty, err := pty.Open()
	c.Assert(err, checker.IsNil, check.Commentf("Could not open pty: %v", err))
	cmd := exec.Command(dockerBinary, "run", "-ti", "--name", name, "busybox")
	cmd.Stdin = tty
	cmd.Stdout = tty
	cmd.Stderr = tty

	errChan := make(chan error)
	go func() {
		errChan <- cmd.Run()
		close(errChan)
	}()

	c.Assert(waitRun(name), check.IsNil)

	cpty.Write([]byte{16})
	time.Sleep(100 * time.Millisecond)
	cpty.Write([]byte{17})

	select {
	case err := <-errChan:
		c.Assert(err, check.IsNil)
	case <-time.After(5 * time.Second):
		c.Fatal("timeout while detaching")
	}

	cpty, tty, err = pty.Open()
	c.Assert(err, checker.IsNil, check.Commentf("Could not open pty: %v", err))

	cmd = exec.Command(dockerBinary, "attach", name)
	cmd.Stdin = tty
	cmd.Stdout = tty
	cmd.Stderr = tty

	err = cmd.Start()
	c.Assert(err, checker.IsNil)

	bytes := make([]byte, 10)
	var nBytes int
	readErr := make(chan error, 1)

	go func() {
		time.Sleep(500 * time.Millisecond)
		cpty.Write([]byte("\n"))
		time.Sleep(500 * time.Millisecond)

		nBytes, err = cpty.Read(bytes)
		cpty.Close()
		readErr <- err
	}()

	select {
	case err := <-readErr:
		c.Assert(err, check.IsNil)
	case <-time.After(2 * time.Second):
		c.Fatal("timeout waiting for attach read")
	}

	err = cmd.Wait()
	c.Assert(err, checker.IsNil)

	c.Assert(string(bytes[:nBytes]), checker.Contains, "/ #")

}

// TestAttachDetach checks that attach in tty mode can be detached using the long container ID
func (s *DockerSuite) TestAttachDetach(c *check.C) {
	out, _ := dockerCmd(c, "run", "-itd", "busybox", "cat")
	id := strings.TrimSpace(out)
	c.Assert(waitRun(id), check.IsNil)

	cpty, tty, err := pty.Open()
	c.Assert(err, check.IsNil)
	defer cpty.Close()

	cmd := exec.Command(dockerBinary, "attach", id)
	cmd.Stdin = tty
	stdout, err := cmd.StdoutPipe()
	c.Assert(err, check.IsNil)
	defer stdout.Close()
	err = cmd.Start()
	c.Assert(err, check.IsNil)
	c.Assert(waitRun(id), check.IsNil)

	_, err = cpty.Write([]byte("hello\n"))
	c.Assert(err, check.IsNil)
	out, err = bufio.NewReader(stdout).ReadString('\n')
	c.Assert(err, check.IsNil)
	c.Assert(strings.TrimSpace(out), checker.Equals, "hello", check.Commentf("expected 'hello', got %q", out))

	// escape sequence
	_, err = cpty.Write([]byte{16})
	c.Assert(err, checker.IsNil)
	time.Sleep(100 * time.Millisecond)
	_, err = cpty.Write([]byte{17})
	c.Assert(err, checker.IsNil)

	ch := make(chan struct{})
	go func() {
		cmd.Wait()
		ch <- struct{}{}
	}()

	running, err := inspectField(id, "State.Running")
	c.Assert(err, checker.IsNil)
	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))

	go func() {
		dockerCmd(c, "kill", id)
	}()

	select {
	case <-ch:
	case <-time.After(10 * time.Millisecond):
		c.Fatal("timed out waiting for container to exit")
	}

}

// TestAttachDetachTruncatedID checks that attach in tty mode can be detached
func (s *DockerSuite) TestAttachDetachTruncatedID(c *check.C) {
	out, _ := dockerCmd(c, "run", "-itd", "busybox", "cat")
	id := stringid.TruncateID(strings.TrimSpace(out))
	c.Assert(waitRun(id), check.IsNil)

	cpty, tty, err := pty.Open()
	c.Assert(err, checker.IsNil)
	defer cpty.Close()

	cmd := exec.Command(dockerBinary, "attach", id)
	cmd.Stdin = tty
	stdout, err := cmd.StdoutPipe()
	c.Assert(err, checker.IsNil)
	defer stdout.Close()
	err = cmd.Start()
	c.Assert(err, checker.IsNil)

	_, err = cpty.Write([]byte("hello\n"))
	c.Assert(err, checker.IsNil)
	out, err = bufio.NewReader(stdout).ReadString('\n')
	c.Assert(err, checker.IsNil)
	c.Assert(strings.TrimSpace(out), checker.Equals, "hello", check.Commentf("expected 'hello', got %q", out))

	// escape sequence
	_, err = cpty.Write([]byte{16})
	c.Assert(err, checker.IsNil)
	time.Sleep(100 * time.Millisecond)
	_, err = cpty.Write([]byte{17})
	c.Assert(err, checker.IsNil)

	ch := make(chan struct{})
	go func() {
		cmd.Wait()
		ch <- struct{}{}
	}()

	running, err := inspectField(id, "State.Running")
	c.Assert(err, checker.IsNil)
	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))

	go func() {
		dockerCmd(c, "kill", id)
	}()

	select {
	case <-ch:
	case <-time.After(10 * time.Millisecond):
		c.Fatal("timed out waiting for container to exit")
	}

}
<|endoftext|>


<commit_before>package integration

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"os"
	"os/exec"
	"strings"
	"testing"

	"github.com/sclevine/spec"
	"github.com/stretchr/testify/require"
)

var _ = suite("kubernetes/clusters/create", func(t *testing.T, when spec.G, it spec.S) {
	var (
		expect *require.Assertions
		server *httptest.Server
	)

	it.Before(func() {
		expect = require.New(t)

		server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			switch req.URL.Path {
			case "/v2/kubernetes/options":
				auth := req.Header.Get("Authorization")
				if auth != "Bearer some-magic-token" {
					w.WriteHeader(http.StatusUnauthorized)
					return
				}

				if req.Method != http.MethodGet {
					w.WriteHeader(http.StatusMethodNotAllowed)
					return
				}

				w.Write([]byte(kubeClustersCreateOptResponse))
			case "/v2/kubernetes/clusters":
				if req.Method != http.MethodPost {
					w.WriteHeader(http.StatusMethodNotAllowed)
					return
				}
				reqBody, err := ioutil.ReadAll(req.Body)
				expect.NoError(err)

				matchedRequest := kubeClustersCreateJSONReq
				if strings.Contains(string(reqBody), "some-node-pool-cluster") {
					matchedRequest = kubeNodePoolCreateJSONReq
				}

				expect.JSONEq(string(reqBody), matchedRequest)

				w.Write([]byte(kubeClustersCreateResponse))
			case "/v2/kubernetes/clusters/some-cluster-id":
				if req.Method != http.MethodGet {
					w.WriteHeader(http.StatusMethodNotAllowed)
					return
				}

				w.Write([]byte(kubeClustersWaitResponse))
			case "/v2/kubernetes/clusters/some-cluster-id/kubeconfig":
				if req.Method != http.MethodGet {
					w.WriteHeader(http.StatusMethodNotAllowed)
					return
				}

				w.Write([]byte(kubeClustersConfigResponse))
			default:
				dump, err := httputil.DumpRequest(req, true)
				if err != nil {
					t.Fatal("failed to dump request")
				}

				t.Fatalf("received unknown request: %s", dump)
			}
		}))
	})

	when("not using node-pool", func() {
		it("creates a kube cluster with defaults", func() {
			f, err := ioutil.TempFile("", "fake-kube-config")
			expect.NoError(err)

			err = f.Close()
			expect.NoError(err)
			defer os.Remove(f.Name())

			cmd := exec.Command(builtBinaryPath,
				"-t", "some-magic-token",
				"-u", server.URL,
				"kubernetes",
				"clusters",
				"create",
				"some-cluster-name",
				"--region", "mars",
				"--version", "some-kube-version",
			)

			cmd.Env = append(os.Environ(),
				fmt.Sprintf("KUBECONFIG=%s", f.Name()),
			)

			output, err := cmd.CombinedOutput()
			expect.NoError(err, fmt.Sprintf("received error output: %s", output))
			expect.Equal(strings.TrimSpace(fmt.Sprintf(kubeClustersCreateOutput, f.Name())), strings.TrimSpace(string(output)))
		})
	})

	when("using node-pool", func() {
		it("creates a kube cluster with the node-pool", func() {
			f, err := ioutil.TempFile("", "fake-kube-config")
			expect.NoError(err)

			err = f.Close()
			expect.NoError(err)
			defer os.Remove(f.Name())

			cmd := exec.Command(builtBinaryPath,
				"-t", "some-magic-token",
				"-u", server.URL,
				"kubernetes",
				"clusters",
				"create",
				"some-node-pool-cluster",
				"--region", "mars",
				"--version", "some-kube-version",
				"--node-pool", "name=default;auto-scale=true;min-nodes=2;max-nodes=5;count=2",
			)

			cmd.Env = append(os.Environ(),
				fmt.Sprintf("KUBECONFIG=%s", f.Name()),
			)

			output, err := cmd.CombinedOutput()
			expect.NoError(err, fmt.Sprintf("received error output: %s", output))
		})

		when("specifying size as well", func() {
			it("returns an error", func() {
				f, err := ioutil.TempFile("", "fake-kube-config")
				expect.NoError(err)

				err = f.Close()
				expect.NoError(err)
				defer os.Remove(f.Name())

				cmd := exec.Command(builtBinaryPath,
					"-t", "some-magic-token",
					"-u", server.URL,
					"kubernetes",
					"clusters",
					"create",
					"some-cluster-name",
					"--region", "mars",
					"--version", "some-kube-version",
					"--size", "the-biggest",
					"--node-pool", "name=default;auto-scale=true;min-nodes=2;max-nodes=5;count=2",
				)

				cmd.Env = append(os.Environ(),
					fmt.Sprintf("KUBECONFIG=%s", f.Name()),
				)

				output, err := cmd.CombinedOutput()
				expect.Error(err)
				expect.Equal(`Error: flags "size" and "count" cannot be provided when "node-pool" is present`, strings.TrimSpace(string(output)))
			})
		})
	})
})

const (
	kubeClustersCreateOptResponse = `
{
"options":{
    "versions": [{"slug":"version-slug","kubernetes_version": "some-kube-version"}],
    "regions": [{"name": "region-name", "slug": "some-region-slug"}],
    "sizes": [{"name":"size-name", "slug": "some-size-slug"}]
  }
}
`

	kubeClustersCreateOutput = `
Notice: cluster is provisioning, waiting for cluster to be running
Notice: cluster created, fetching credentials
Notice: adding cluster credentials to kubeconfig file found in %q
Notice: setting current-context to some-context
ID                 Name                 Region    Version              Auto Upgrade    Status     Node Pools
some-cluster-id    some-cluster-name    mars      some-kube-version    false           running    frontend-pool
`
	kubeClustersCreateJSONReq = `
{
  "name": "some-cluster-name",
  "region": "mars",
  "version": "some-kube-version",
  "auto_upgrade": false,
  "maintenance_policy": {
    "day": "any",
    "duration": "",
    "start_time": "00:00"
  },
  "node_pools": [
    {
      "size": "s-1vcpu-2gb",
      "count": 3,
      "name": "some-cluster-name-default-pool"
    }
  ]
}
`
	kubeNodePoolCreateJSONReq = `
{
  "name": "some-node-pool-cluster",
  "region": "mars",
  "version": "some-kube-version",
  "auto_upgrade": false,
  "maintenance_policy": {
    "day": "any",
    "duration": "",
    "start_time": "00:00"
  },
  "node_pools": [
    {
      "min_nodes": 2,
      "max_nodes": 5,
      "count": 2,
      "auto_scale": true,
      "name": "default",
      "size": "s-1vcpu-2gb"
    }
  ]
}
`
	kubeClustersCreateResponse = `
{
  "kubernetes_cluster": {
    "id": "some-cluster-id"
  }
}
`
	kubeClustersWaitResponse = `
{
  "kubernetes_cluster": {
    "id": "some-cluster-id",
    "name": "some-cluster-name",
    "region": "mars",
    "version": "some-kube-version",
    "tags": ["production"],
    "node_pools": [
      {
        "name": "frontend-pool"
      }
    ],
    "status": {
     "state": "running",
     "message": "yas"
    },
    "created_at": "2018-11-15T16:00:11Z",
    "updated_at": "2018-11-15T16:00:11Z"
  }
}
`
	kubeClustersConfigResponse = `
---
apiVersion: v1
kind: Config
users:
- name: some-user
  user:
    token: some-token
clusters:
- cluster:
    server: https://example.com
  name: some-cluster
contexts:
- context:
    cluster: some-cluster
    user: some-user
  name: some-context
current-context: some-context
`
)
<commit_msg>Update copy-dependent integration test.<commit_after>package integration

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"os"
	"os/exec"
	"strings"
	"testing"

	"github.com/sclevine/spec"
	"github.com/stretchr/testify/require"
)

var _ = suite("kubernetes/clusters/create", func(t *testing.T, when spec.G, it spec.S) {
	var (
		expect *require.Assertions
		server *httptest.Server
	)

	it.Before(func() {
		expect = require.New(t)

		server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			switch req.URL.Path {
			case "/v2/kubernetes/options":
				auth := req.Header.Get("Authorization")
				if auth != "Bearer some-magic-token" {
					w.WriteHeader(http.StatusUnauthorized)
					return
				}

				if req.Method != http.MethodGet {
					w.WriteHeader(http.StatusMethodNotAllowed)
					return
				}

				w.Write([]byte(kubeClustersCreateOptResponse))
			case "/v2/kubernetes/clusters":
				if req.Method != http.MethodPost {
					w.WriteHeader(http.StatusMethodNotAllowed)
					return
				}
				reqBody, err := ioutil.ReadAll(req.Body)
				expect.NoError(err)

				matchedRequest := kubeClustersCreateJSONReq
				if strings.Contains(string(reqBody), "some-node-pool-cluster") {
					matchedRequest = kubeNodePoolCreateJSONReq
				}

				expect.JSONEq(string(reqBody), matchedRequest)

				w.Write([]byte(kubeClustersCreateResponse))
			case "/v2/kubernetes/clusters/some-cluster-id":
				if req.Method != http.MethodGet {
					w.WriteHeader(http.StatusMethodNotAllowed)
					return
				}

				w.Write([]byte(kubeClustersWaitResponse))
			case "/v2/kubernetes/clusters/some-cluster-id/kubeconfig":
				if req.Method != http.MethodGet {
					w.WriteHeader(http.StatusMethodNotAllowed)
					return
				}

				w.Write([]byte(kubeClustersConfigResponse))
			default:
				dump, err := httputil.DumpRequest(req, true)
				if err != nil {
					t.Fatal("failed to dump request")
				}

				t.Fatalf("received unknown request: %s", dump)
			}
		}))
	})

	when("not using node-pool", func() {
		it("creates a kube cluster with defaults", func() {
			f, err := ioutil.TempFile("", "fake-kube-config")
			expect.NoError(err)

			err = f.Close()
			expect.NoError(err)
			defer os.Remove(f.Name())

			cmd := exec.Command(builtBinaryPath,
				"-t", "some-magic-token",
				"-u", server.URL,
				"kubernetes",
				"clusters",
				"create",
				"some-cluster-name",
				"--region", "mars",
				"--version", "some-kube-version",
			)

			cmd.Env = append(os.Environ(),
				fmt.Sprintf("KUBECONFIG=%s", f.Name()),
			)

			output, err := cmd.CombinedOutput()
			expect.NoError(err, fmt.Sprintf("received error output: %s", output))
			expect.Equal(strings.TrimSpace(fmt.Sprintf(kubeClustersCreateOutput, f.Name())), strings.TrimSpace(string(output)))
		})
	})

	when("using node-pool", func() {
		it("creates a kube cluster with the node-pool", func() {
			f, err := ioutil.TempFile("", "fake-kube-config")
			expect.NoError(err)

			err = f.Close()
			expect.NoError(err)
			defer os.Remove(f.Name())

			cmd := exec.Command(builtBinaryPath,
				"-t", "some-magic-token",
				"-u", server.URL,
				"kubernetes",
				"clusters",
				"create",
				"some-node-pool-cluster",
				"--region", "mars",
				"--version", "some-kube-version",
				"--node-pool", "name=default;auto-scale=true;min-nodes=2;max-nodes=5;count=2",
			)

			cmd.Env = append(os.Environ(),
				fmt.Sprintf("KUBECONFIG=%s", f.Name()),
			)

			output, err := cmd.CombinedOutput()
			expect.NoError(err, fmt.Sprintf("received error output: %s", output))
		})

		when("specifying size as well", func() {
			it("returns an error", func() {
				f, err := ioutil.TempFile("", "fake-kube-config")
				expect.NoError(err)

				err = f.Close()
				expect.NoError(err)
				defer os.Remove(f.Name())

				cmd := exec.Command(builtBinaryPath,
					"-t", "some-magic-token",
					"-u", server.URL,
					"kubernetes",
					"clusters",
					"create",
					"some-cluster-name",
					"--region", "mars",
					"--version", "some-kube-version",
					"--size", "the-biggest",
					"--node-pool", "name=default;auto-scale=true;min-nodes=2;max-nodes=5;count=2",
				)

				cmd.Env = append(os.Environ(),
					fmt.Sprintf("KUBECONFIG=%s", f.Name()),
				)

				output, err := cmd.CombinedOutput()
				expect.Error(err)
				expect.Equal(`Error: Flags "size" and "count" cannot be provided when "node-pool" is present`, strings.TrimSpace(string(output)))
			})
		})
	})
})

const (
	kubeClustersCreateOptResponse = `
{
"options":{
    "versions": [{"slug":"version-slug","kubernetes_version": "some-kube-version"}],
    "regions": [{"name": "region-name", "slug": "some-region-slug"}],
    "sizes": [{"name":"size-name", "slug": "some-size-slug"}]
  }
}
`

	kubeClustersCreateOutput = `
Notice: cluster is provisioning, waiting for cluster to be running
Notice: cluster created, fetching credentials
Notice: adding cluster credentials to kubeconfig file found in %q
Notice: setting current-context to some-context
ID                 Name                 Region    Version              Auto Upgrade    Status     Node Pools
some-cluster-id    some-cluster-name    mars      some-kube-version    false           running    frontend-pool
`
	kubeClustersCreateJSONReq = `
{
  "name": "some-cluster-name",
  "region": "mars",
  "version": "some-kube-version",
  "auto_upgrade": false,
  "maintenance_policy": {
    "day": "any",
    "duration": "",
    "start_time": "00:00"
  },
  "node_pools": [
    {
      "size": "s-1vcpu-2gb",
      "count": 3,
      "name": "some-cluster-name-default-pool"
    }
  ]
}
`
	kubeNodePoolCreateJSONReq = `
{
  "name": "some-node-pool-cluster",
  "region": "mars",
  "version": "some-kube-version",
  "auto_upgrade": false,
  "maintenance_policy": {
    "day": "any",
    "duration": "",
    "start_time": "00:00"
  },
  "node_pools": [
    {
      "min_nodes": 2,
      "max_nodes": 5,
      "count": 2,
      "auto_scale": true,
      "name": "default",
      "size": "s-1vcpu-2gb"
    }
  ]
}
`
	kubeClustersCreateResponse = `
{
  "kubernetes_cluster": {
    "id": "some-cluster-id"
  }
}
`
	kubeClustersWaitResponse = `
{
  "kubernetes_cluster": {
    "id": "some-cluster-id",
    "name": "some-cluster-name",
    "region": "mars",
    "version": "some-kube-version",
    "tags": ["production"],
    "node_pools": [
      {
        "name": "frontend-pool"
      }
    ],
    "status": {
     "state": "running",
     "message": "yas"
    },
    "created_at": "2018-11-15T16:00:11Z",
    "updated_at": "2018-11-15T16:00:11Z"
  }
}
`
	kubeClustersConfigResponse = `
---
apiVersion: v1
kind: Config
users:
- name: some-user
  user:
    token: some-token
clusters:
- cluster:
    server: https://example.com
  name: some-cluster
contexts:
- context:
    cluster: some-cluster
    user: some-user
  name: some-context
current-context: some-context
`
)
<|endoftext|>


<commit_before>/*
Copyright 2012 Google Inc.

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

     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 singleflight

import (
	"errors"
	"fmt"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

func TestDo(t *testing.T) {
	var g Group
	v, err := g.Do("key", func() (interface{}, error) {
		return "bar", nil
	})
	if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
		t.Errorf("Do = %v; want %v", got, want)
	}
	if err != nil {
		t.Errorf("Do error = %v", err)
	}
}

func TestDoErr(t *testing.T) {
	var g Group
	someErr := errors.New("Some error")
	v, err := g.Do("key", func() (interface{}, error) {
		return nil, someErr
	})
	if err != someErr {
		t.Errorf("Do error = %v; want someErr", err, someErr)
	}
	if v != nil {
		t.Errorf("unexpected non-nil value %#v", v)
	}
}

func TestDoDupSuppress(t *testing.T) {
	var g Group
	c := make(chan string)
	var calls int32
	fn := func() (interface{}, error) {
		atomic.AddInt32(&calls, 1)
		return <-c, nil
	}

	const n = 10
	var wg sync.WaitGroup
	for i := 0; i < n; i++ {
		wg.Add(1)
		go func() {
			v, err := g.Do("key", fn)
			if err != nil {
				t.Errorf("Do error: %v", err)
			}
			if v.(string) != "bar" {
				t.Errorf("got %q; want %q", v, "bar")
			}
			wg.Done()
		}()
	}
	time.Sleep(100 * time.Millisecond) // let goroutines above block
	c <- "bar"
	wg.Wait()
	if got := atomic.LoadInt32(&calls); got != 1 {
		t.Errorf("number of calls = %d; want 1", got)
	}
}
<commit_msg>fix wrong number of args for format in Errorf call<commit_after>/*
Copyright 2012 Google Inc.

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

     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 singleflight

import (
	"errors"
	"fmt"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

func TestDo(t *testing.T) {
	var g Group
	v, err := g.Do("key", func() (interface{}, error) {
		return "bar", nil
	})
	if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
		t.Errorf("Do = %v; want %v", got, want)
	}
	if err != nil {
		t.Errorf("Do error = %v", err)
	}
}

func TestDoErr(t *testing.T) {
	var g Group
	someErr := errors.New("Some error")
	v, err := g.Do("key", func() (interface{}, error) {
		return nil, someErr
	})
	if err != someErr {
		t.Errorf("Do error = %v; want someErr", err)
	}
	if v != nil {
		t.Errorf("unexpected non-nil value %#v", v)
	}
}

func TestDoDupSuppress(t *testing.T) {
	var g Group
	c := make(chan string)
	var calls int32
	fn := func() (interface{}, error) {
		atomic.AddInt32(&calls, 1)
		return <-c, nil
	}

	const n = 10
	var wg sync.WaitGroup
	for i := 0; i < n; i++ {
		wg.Add(1)
		go func() {
			v, err := g.Do("key", fn)
			if err != nil {
				t.Errorf("Do error: %v", err)
			}
			if v.(string) != "bar" {
				t.Errorf("got %q; want %q", v, "bar")
			}
			wg.Done()
		}()
	}
	time.Sleep(100 * time.Millisecond) // let goroutines above block
	c <- "bar"
	wg.Wait()
	if got := atomic.LoadInt32(&calls); got != 1 {
		t.Errorf("number of calls = %d; want 1", got)
	}
}
<|endoftext|>


<commit_before>package stripe

// SubItemParams is the set of parameters that can be used when creating or updating a subscription item.
// For more details see https://stripe.com/docs/api#create_subscription_item and https://stripe.com/docs/api#update_subscription_item.
type SubItemParams struct {
	Params
	Sub                     string
	ID                      string
	Quantity                uint64
	Plan                    string
	ProrationDate           int64
	NoProrate, QuantityZero bool
	Deleted                 bool
}

// SubItemListParams is the set of parameters that can be used when listing invoice items.
// For more details see https://stripe.com/docs/api#list_invoiceitems.
type SubItemListParams struct {
	ListParams
	Sub string
}

// SubItem is the resource represneting a Stripe subscription item.
// For more details see https://stripe.com/docs/api#subscription_items.
type SubItem struct {
	ID       string `json:"id"`
	Plan     *Plan  `json:"plan"`
	Quantity int64  `json:"quantity"`
	Created  int64  `json:"created"`
	Deleted  bool   `json:"deleted"`
}

// SubItemList is a list of invoice items as retrieved from a list endpoint.
type SubItemList struct {
	ListMeta
	Values []*SubItem `json:"data"`
}
<commit_msg>Ensure Quantity types match for SubItem<commit_after>package stripe

// SubItemParams is the set of parameters that can be used when creating or updating a subscription item.
// For more details see https://stripe.com/docs/api#create_subscription_item and https://stripe.com/docs/api#update_subscription_item.
type SubItemParams struct {
	Params
	Sub                     string
	ID                      string
	Quantity                uint64
	Plan                    string
	ProrationDate           int64
	NoProrate, QuantityZero bool
	Deleted                 bool
}

// SubItemListParams is the set of parameters that can be used when listing invoice items.
// For more details see https://stripe.com/docs/api#list_invoiceitems.
type SubItemListParams struct {
	ListParams
	Sub string
}

// SubItem is the resource represneting a Stripe subscription item.
// For more details see https://stripe.com/docs/api#subscription_items.
type SubItem struct {
	ID       string `json:"id"`
	Plan     *Plan  `json:"plan"`
	Quantity uint64 `json:"quantity"`
	Created  int64  `json:"created"`
	Deleted  bool   `json:"deleted"`
}

// SubItemList is a list of invoice items as retrieved from a list endpoint.
type SubItemList struct {
	ListMeta
	Values []*SubItem `json:"data"`
}
<|endoftext|>


<commit_before>package backends

import (
	"bytes"
	"fmt"
	"github.com/boltdb/bolt"
)

type AuthBackend interface {
	SetValue(key, value []byte) error
	GetValue(key []byte) ([]byte, error)
	Delete(key []byte) error
}

func NewBoltDBAuthBackend(db *bolt.DB, tokenBucket, userBucket []byte) *BoltAuth {
	return &BoltAuth{
		DS:          db,
		TokenBucket: []byte(tokenBucket),
		UserBucket:  []byte(userBucket),
	}
}

// UserBucketName - default name for BoltDB bucket that stores user info
const UserBucketName = "authbucket"

// TokenBucketName
const TokenBucketName = "tokenbucket"

// BoltCache - container to implement Cache instance with BoltDB backend for storage
type BoltAuth struct {
	DS          *bolt.DB
	TokenBucket []byte
	UserBucket  []byte
}

func (b *BoltAuth) SetValue(key, value []byte) error {
	err := b.DS.Update(func(tx *bolt.Tx) error {
		bucket, err := tx.CreateBucketIfNotExists(b.TokenBucket)
		if err != nil {
			return err
		}
		err = bucket.Put(key, value)
		if err != nil {
			return err
		}
		return nil
	})

	return err
}

<commit_msg>delete key<commit_after>package backends

import (
	"bytes"
	"fmt"
	"github.com/boltdb/bolt"
)

type AuthBackend interface {
	SetValue(key, value []byte) error
	GetValue(key []byte) ([]byte, error)
	Delete(key []byte) error
}

func NewBoltDBAuthBackend(db *bolt.DB, tokenBucket, userBucket []byte) *BoltAuth {
	return &BoltAuth{
		DS:          db,
		TokenBucket: []byte(tokenBucket),
		UserBucket:  []byte(userBucket),
	}
}

// UserBucketName - default name for BoltDB bucket that stores user info
const UserBucketName = "authbucket"

// TokenBucketName
const TokenBucketName = "tokenbucket"

// BoltCache - container to implement Cache instance with BoltDB backend for storage
type BoltAuth struct {
	DS          *bolt.DB
	TokenBucket []byte
	UserBucket  []byte
}

func (b *BoltAuth) SetValue(key, value []byte) error {
	err := b.DS.Update(func(tx *bolt.Tx) error {
		bucket, err := tx.CreateBucketIfNotExists(b.TokenBucket)
		if err != nil {
			return err
		}
		err = bucket.Put(key, value)
		if err != nil {
			return err
		}
		return nil
	})

	return err
}

func (b *BoltAuth) Delete(key []byte) error {
	return b.Delete(key)
}

<|endoftext|>