text:
package testing

import (
	"fmt"
	"net"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"
	"time"

	"k8s.io/utils/exec"
	"k8s.io/utils/exec/testing"
	"k8s.io/utils/mount"
	utilstrings "k8s.io/utils/strings"

	authenticationv1 "k8s.io/api/authentication/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/informers"
	clientset "k8s.io/client-go/kubernetes"
	storagelistersv1 "k8s.io/client-go/listers/storage/v1"
	storagelisters "k8s.io/client-go/listers/storage/v1beta1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"
	utiltesting "k8s.io/client-go/util/testing"
	cloudprovider "k8s.io/cloud-provider"
	. "k8s.io/kubernetes/pkg/volume"
	"k8s.io/kubernetes/pkg/volume/util"
	"k8s.io/kubernetes/pkg/volume/util/hostutil"
	"k8s.io/kubernetes/pkg/volume/util/recyclerclient"
	"k8s.io/kubernetes/pkg/volume/util/subpath"
	"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
)

const (
	// A hook specified in storage class to indicate it's provisioning
	// is expected to fail.
	ExpectProvisionFailureKey = "expect-provision-failure"
	// The node is marked as uncertain. The attach operation will fail and return timeout error
	// for the first attach call. The following call will return sucesssfully.
	UncertainAttachNode = "uncertain-attach-node"
	// The node is marked as timeout. The attach operation will always fail and return timeout error
	// but the operation is actually succeeded.
	TimeoutAttachNode = "timeout-attach-node"
	// The node is marked as multi-attach which means it is allowed to attach the volume to multiple nodes.
	MultiAttachNode = "multi-attach-node"
)

// fakeVolumeHost is useful for testing volume plugins.
type fakeVolumeHost struct {
	rootDir         string
	kubeClient      clientset.Interface
	pluginMgr       *VolumePluginMgr
	cloud           cloudprovider.Interface
	mounter         mount.Interface
	hostUtil        hostutil.HostUtils
	exec            *testingexec.FakeExec
	nodeLabels      map[string]string
	nodeName        string
	subpather       subpath.Interface
	csiDriverLister storagelisters.CSIDriverLister
	informerFactory informers.SharedInformerFactory
	kubeletErr      error
	mux             sync.Mutex
}

var _ VolumeHost = &fakeVolumeHost{}
var _ AttachDetachVolumeHost = &fakeVolumeHost{}

func NewFakeVolumeHost(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin) *fakeVolumeHost {
	return newFakeVolumeHost(rootDir, kubeClient, plugins, nil, nil, "", nil)
}

func NewFakeVolumeHostWithCloudProvider(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, cloud cloudprovider.Interface) *fakeVolumeHost {
	return newFakeVolumeHost(rootDir, kubeClient, plugins, cloud, nil, "", nil)
}

func NewFakeVolumeHostWithNodeLabels(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, labels map[string]string) *fakeVolumeHost {
	volHost := newFakeVolumeHost(rootDir, kubeClient, plugins, nil, nil, "", nil)
	volHost.nodeLabels = labels
	return volHost
}

func NewFakeVolumeHostWithCSINodeName(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, nodeName string, driverLister storagelisters.CSIDriverLister) *fakeVolumeHost {
	volHost := newFakeVolumeHost(rootDir, kubeClient, plugins, nil, nil, nodeName, driverLister)
	return volHost
}

func newFakeVolumeHost(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, cloud cloudprovider.Interface, pathToTypeMap map[string]hostutil.FileType, nodeName string, driverLister storagelisters.CSIDriverLister) *fakeVolumeHost {
	host := &fakeVolumeHost{rootDir: rootDir, kubeClient: kubeClient, cloud: cloud, nodeName: nodeName, csiDriverLister: driverLister}
	host.mounter = mount.NewFakeMounter(nil)
	host.hostUtil = hostutil.NewFakeHostUtil(pathToTypeMap)
	host.exec = &testingexec.FakeExec{DisableScripts: true}
	host.pluginMgr = &VolumePluginMgr{}
	if err := host.pluginMgr.InitPlugins(plugins, nil /* prober */, host); err != nil {
		// TODO(dyzz): Pipe testing context through and throw a fatal error instead
		panic(fmt.Sprintf("Failed to init plugins while creating fake volume host: %v", err))
	}
	host.subpather = &subpath.FakeSubpath{}
	host.informerFactory = informers.NewSharedInformerFactory(kubeClient, time.Minute)
	// Wait until the InitPlugins setup is finished before returning from this setup func
	if err := host.WaitForKubeletErrNil(); err != nil {
		panic(fmt.Sprintf("Failed to wait for kubelet err to be nil while creating fake volume host: %v", err))
	}
	return host
}

func NewFakeVolumeHostWithMounterFSType(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, pathToTypeMap map[string]hostutil.FileType) *fakeVolumeHost {
	volHost := newFakeVolumeHost(rootDir, kubeClient, plugins, nil, pathToTypeMap, "", nil)
	return volHost
}

func (f *fakeVolumeHost) GetPluginDir(podUID string) string {
	return filepath.Join(f.rootDir, "plugins", podUID)
}

func (f *fakeVolumeHost) GetVolumeDevicePluginDir(pluginName string) string {
	return filepath.Join(f.rootDir, "plugins", pluginName, "volumeDevices")
}

func (f *fakeVolumeHost) GetPodsDir() string {
	return filepath.Join(f.rootDir, "pods")
}

func (f *fakeVolumeHost) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string {
	return filepath.Join(f.rootDir, "pods", string(podUID), "volumes", pluginName, volumeName)
}

func (f *fakeVolumeHost) GetPodVolumeDeviceDir(podUID types.UID, pluginName string) string {
	return filepath.Join(f.rootDir, "pods", string(podUID), "volumeDevices", pluginName)
}

func (f *fakeVolumeHost) GetPodPluginDir(podUID types.UID, pluginName string) string {
	return filepath.Join(f.rootDir, "pods", string(podUID), "plugins", pluginName)
}

func (f *fakeVolumeHost) GetKubeClient() clientset.Interface {
	return f.kubeClient
}

func (f *fakeVolumeHost) GetCloudProvider() cloudprovider.Interface {
	return f.cloud
}

func (f *fakeVolumeHost) GetMounter(pluginName string) mount.Interface {
	return f.mounter
}

func (f *fakeVolumeHost) GetHostUtil() hostutil.HostUtils {
	return f.hostUtil
}

func (f *fakeVolumeHost) GetSubpather() subpath.Interface {
	return f.subpather
}

func (f *fakeVolumeHost) GetPluginMgr() *VolumePluginMgr {
	return f.pluginMgr
}

func (f *fakeVolumeHost) NewWrapperMounter(volName string, spec Spec, pod *v1.Pod, opts VolumeOptions) (Mounter, error) {
	// The name of wrapper volume is set to "wrapped_{wrapped_volume_name}"
	wrapperVolumeName := "wrapped_" + volName
	if spec.Volume != nil {
		spec.Volume.Name = wrapperVolumeName
	}
	plug, err := f.pluginMgr.FindPluginBySpec(&spec)
	if err != nil {
		return nil, err
	}
	return plug.NewMounter(&spec, pod, opts)
}

func (f *fakeVolumeHost) NewWrapperUnmounter(volName string, spec Spec, podUID types.UID) (Unmounter, error) {
	// The name of wrapper volume is set to "wrapped_{wrapped_volume_name}"
	wrapperVolumeName := "wrapped_" + volName
	if spec.Volume != nil {
		spec.Volume.Name = wrapperVolumeName
	}
	plug, err := f.pluginMgr.FindPluginBySpec(&spec)
	if err != nil {
		return nil, err
	}
	return plug.NewUnmounter(spec.Name(), podUID)
}

// Returns the hostname of the host kubelet is running on
func (f *fakeVolumeHost) GetHostName() string {
	return "fakeHostName"
}

// Returns host IP or nil in the case of error.
func (f *fakeVolumeHost) GetHostIP() (net.IP, error) {
	return nil, fmt.Errorf("GetHostIP() not implemented")
}

func (f *fakeVolumeHost) GetNodeAllocatable() (v1.ResourceList, error) {
	return v1.ResourceList{}, nil
}

func (f *fakeVolumeHost) GetSecretFunc() func(namespace, name string) (*v1.Secret, error) {
	return func(namespace, name string) (*v1.Secret, error) {
		return f.kubeClient.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
	}
}

func (f *fakeVolumeHost) GetExec(pluginName string) exec.Interface {
	return f.exec
}

func (f *fakeVolumeHost) GetConfigMapFunc() func(namespace, name string) (*v1.ConfigMap, error) {
	return func(namespace, name string) (*v1.ConfigMap, error) {
		return f.kubeClient.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{})
	}
}

func (f *fakeVolumeHost) GetServiceAccountTokenFunc() func(string, string, *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
	return func(namespace, name string, tr *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
		return f.kubeClient.CoreV1().ServiceAccounts(namespace).CreateToken(name, tr)
	}
}

func (f *fakeVolumeHost) DeleteServiceAccountTokenFunc() func(types.UID) {
	return func(types.UID) {}
}

func (f *fakeVolumeHost) GetNodeLabels() (map[string]string, error) {
	if f.nodeLabels == nil {
		f.nodeLabels = map[string]string{"test-label": "test-value"}
	}
	return f.nodeLabels, nil
}

func (f *fakeVolumeHost) GetNodeName() types.NodeName {
	return types.NodeName(f.nodeName)
}

func (f *fakeVolumeHost) GetEventRecorder() record.EventRecorder {
	return nil
}

func (f *fakeVolumeHost) ScriptCommands(scripts []CommandScript) {
	ScriptCommands(f.exec, scripts)
}

// CommandScript is used to pre-configure a command that will be executed and
// optionally set it's output (stdout and stderr combined) and return code.
type CommandScript struct {
	// Cmd is the command to execute, e.g. "ls"
	Cmd string
	// Args is a slice of arguments to pass to the command, e.g. "-a"
	Args []string
	// Output is the combined stdout and stderr of the command to return
	Output string
	// ReturnCode is the exit code for the command. Setting this to non-zero will
	// cause the command to return an error with this exit code set.
	ReturnCode int
}

// ScriptCommands configures fe, the FakeExec, to have a pre-configured list of
// commands to expect. Calling more commands using fe than those scripted will
// result in a panic. By default, the fe does not enforce command argument checking
// or order -- if you have given an Output to the command, the first command scripted
// will return its output on the first command call, even if the command called is
// different than the one scripted. This is mostly useful to make sure that the
// right number of commands were called. If you want to check the exact commands
// and arguments were called, set fe.ExectOrder to true.
func ScriptCommands(fe *testingexec.FakeExec, scripts []CommandScript) {
	fe.DisableScripts = false
	for _, script := range scripts {
		fakeCmd := &testingexec.FakeCmd{}
		cmdAction := makeFakeCmd(fakeCmd, script.Cmd, script.Args...)
		outputAction := makeFakeOutput(script.Output, script.ReturnCode)
		fakeCmd.CombinedOutputScript = append(fakeCmd.CombinedOutputScript, outputAction)
		fe.CommandScript = append(fe.CommandScript, cmdAction)
	}
}

func makeFakeCmd(fakeCmd *testingexec.FakeCmd, cmd string, args ...string) testingexec.FakeCommandAction {
	fc := fakeCmd
	c := cmd
	a := args
	return func(cmd string, args ...string) exec.Cmd {
		command := testingexec.InitFakeCmd(fc, c, a...)
		return command
	}
}

func makeFakeOutput(output string, rc int) testingexec.FakeCombinedOutputAction {
	o := output
	var e error
	if rc != 0 {
		e = testingexec.FakeExitError{Status: rc}
	}
	return func() ([]byte, error) {
		return []byte(o), e
	}
}

func ProbeVolumePlugins(config VolumeConfig) []VolumePlugin {
	if _, ok := config.OtherAttributes["fake-property"]; ok {
		return []VolumePlugin{
			&FakeVolumePlugin{
				PluginName: "fake-plugin",
				Host:       nil,
				// SomeFakeProperty: config.OtherAttributes["fake-property"] -- string, may require parsing by plugin
			},
		}
	}
	return []VolumePlugin{&FakeVolumePlugin{PluginName: "fake-plugin"}}
}

// FakeVolumePlugin is useful for testing.  It tries to be a fully compliant
// plugin, but all it does is make empty directories.
// Use as:
//   volume.RegisterPlugin(&FakePlugin{"fake-name"})
type FakeVolumePlugin struct {
	sync.RWMutex
	PluginName             string
	Host                   VolumeHost
	Config                 VolumeConfig
	LastProvisionerOptions VolumeOptions
	NewAttacherCallCount   int
	NewDetacherCallCount   int
	VolumeLimits           map[string]int64
	VolumeLimitsError      error
	LimitKey               string
	ProvisionDelaySeconds  int

	// Add callbacks as needed
	WaitForAttachHook func(spec *Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error)
	UnmountDeviceHook func(globalMountPath string) error

	Mounters             []*FakeVolume
	Unmounters           []*FakeVolume
	Attachers            []*FakeVolume
	Detachers            []*FakeVolume
	BlockVolumeMappers   []*FakeVolume
	BlockVolumeUnmappers []*FakeVolume
}

var _ VolumePlugin = &FakeVolumePlugin{}
var _ BlockVolumePlugin = &FakeVolumePlugin{}
var _ RecyclableVolumePlugin = &FakeVolumePlugin{}
var _ DeletableVolumePlugin = &FakeVolumePlugin{}
var _ ProvisionableVolumePlugin = &FakeVolumePlugin{}
var _ AttachableVolumePlugin = &FakeVolumePlugin{}
var _ VolumePluginWithAttachLimits = &FakeVolumePlugin{}
var _ DeviceMountableVolumePlugin = &FakeVolumePlugin{}
var _ NodeExpandableVolumePlugin = &FakeVolumePlugin{}

func (plugin *FakeVolumePlugin) getFakeVolume(list *[]*FakeVolume) *FakeVolume {
	volumeList := *list
	if list != nil && len(volumeList) > 0 {
		volume := volumeList[0]
		volume.Lock()
		defer volume.Unlock()
		volume.WaitForAttachHook = plugin.WaitForAttachHook
		volume.UnmountDeviceHook = plugin.UnmountDeviceHook
		return volume
	}
	volume := &FakeVolume{
		WaitForAttachHook: plugin.WaitForAttachHook,
		UnmountDeviceHook: plugin.UnmountDeviceHook,
	}
	volume.VolumesAttached = make(map[string]types.NodeName)
	*list = append(*list, volume)
	return volume
}

func (plugin *FakeVolumePlugin) Init(host VolumeHost) error {
	plugin.Lock()
	defer plugin.Unlock()
	plugin.Host = host
	return nil
}

func (plugin *FakeVolumePlugin) GetPluginName() string {
	plugin.RLock()
	defer plugin.RUnlock()
	return plugin.PluginName
}

func (plugin *FakeVolumePlugin) GetVolumeName(spec *Spec) (string, error) {
	var volumeName string
	if spec.Volume != nil && spec.Volume.GCEPersistentDisk != nil {
		volumeName = spec.Volume.GCEPersistentDisk.PDName
	} else if spec.PersistentVolume != nil &&
		spec.PersistentVolume.Spec.GCEPersistentDisk != nil {
		volumeName = spec.PersistentVolume.Spec.GCEPersistentDisk.PDName
	}
	if volumeName == "" {
		volumeName = spec.Name()
	}
	return volumeName, nil
}

func (plugin *FakeVolumePlugin) CanSupport(spec *Spec) bool {
	// TODO: maybe pattern-match on spec.Name() to decide?
	return true
}

func (plugin *FakeVolumePlugin) RequiresRemount() bool {
	return false
}

func (plugin *FakeVolumePlugin) SupportsMountOption() bool {
	return true
}

func (plugin *FakeVolumePlugin) SupportsBulkVolumeVerification() bool {
	return false
}

func (plugin *FakeVolumePlugin) NewMounter(spec *Spec, pod *v1.Pod, opts VolumeOptions) (Mounter, error) {
	plugin.Lock()
	defer plugin.Unlock()
	volume := plugin.getFakeVolume(&plugin.Mounters)
	volume.Lock()
	defer volume.Unlock()
	volume.PodUID = pod.UID
	volume.VolName = spec.Name()
	volume.Plugin = plugin
	volume.MetricsNil = MetricsNil{}
	return volume, nil
}

func (plugin *FakeVolumePlugin) GetMounters() (Mounters []*FakeVolume) {
	plugin.RLock()
	defer plugin.RUnlock()
	return plugin.Mounters
}

func (plugin *FakeVolumePlugin) NewUnmounter(volName string, podUID types.UID) (Unmounter, error) {
	plugin.Lock()
	defer plugin.Unlock()
	volume := plugin.getFakeVolume(&plugin.Unmounters)
	volume.Lock()
	defer volume.Unlock()
	volume.PodUID = podUID
	volume.VolName = volName
	volume.Plugin = plugin
	volume.MetricsNil = MetricsNil{}
	return volume, nil
}

func (plugin *FakeVolumePlugin) GetUnmounters() (Unmounters []*FakeVolume) {
	plugin.RLock()
	defer plugin.RUnlock()
	return plugin.Unmounters
}

// Block volume support
func (plugin *FakeVolumePlugin) NewBlockVolumeMapper(spec *Spec, pod *v1.Pod, opts VolumeOptions) (BlockVolumeMapper, error) {
	plugin.Lock()
	defer plugin.Unlock()
	volume := plugin.getFakeVolume(&plugin.BlockVolumeMappers)
	volume.Lock()
	defer volume.Unlock()
	if pod != nil {
		volume.PodUID = pod.UID
	}
	volume.VolName = spec.Name()
	volume.Plugin = plugin
	return volume, nil
}

// Block volume support
func (plugin *FakeVolumePlugin) GetBlockVolumeMapper() (BlockVolumeMappers []*FakeVolume) {
	plugin.RLock()
	defer plugin.RUnlock()
	return plugin.BlockVolumeMappers
}

// Block volume support
func (plugin *FakeVolumePlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (BlockVolumeUnmapper, error) {
	plugin.Lock()
	defer plugin.Unlock()
	volume := plugin.getFakeVolume(&plugin.BlockVolumeUnmappers)
	volume.Lock()
	defer volume.Unlock()
	volume.PodUID = podUID
	volume.VolName = volName
	volume.Plugin = plugin
	return volume, nil
}

// Block volume support
func (plugin *FakeVolumePlugin) GetBlockVolumeUnmapper() (BlockVolumeUnmappers []*FakeVolume) {
	plugin.RLock()
	defer plugin.RUnlock()
	return plugin.BlockVolumeUnmappers
}

func (plugin *FakeVolumePlugin) NewAttacher() (Attacher, error) {
	plugin.Lock()
	defer plugin.Unlock()
	plugin.NewAttacherCallCount = plugin.NewAttacherCallCount + 1
	return plugin.getFakeVolume(&plugin.Attachers), nil
}

func (plugin *FakeVolumePlugin) NewDeviceMounter() (DeviceMounter, error) {
	return plugin.NewAttacher()
}

func (plugin *FakeVolumePlugin) GetAttachers() (Attachers []*FakeVolume) {
	plugin.RLock()
	defer plugin.RUnlock()
	return plugin.Attachers
}

func (plugin *FakeVolumePlugin) GetNewAttacherCallCount() int {
	plugin.RLock()
	defer plugin.RUnlock()
	return plugin.NewAttacherCallCount
}

func (plugin *FakeVolumePlugin) NewDetacher() (Detacher, error) {
	plugin.Lock()
	defer plugin.Unlock()
	plugin.NewDetacherCallCount = plugin.NewDetacherCallCount + 1
	detacher := plugin.getFakeVolume(&plugin.Detachers)
	attacherList := plugin.Attachers
	if attacherList != nil && len(attacherList) > 0 {
		detacherList := plugin.Detachers
		if detacherList != nil && len(detacherList) > 0 {
			detacherList[0].VolumesAttached = attacherList[0].VolumesAttached
		}

	}
	return detacher, nil
}

func (plugin *FakeVolumePlugin) NewDeviceUnmounter() (DeviceUnmounter, error) {
	return plugin.NewDetacher()
}

func (plugin *FakeVolumePlugin) GetDetachers() (Detachers []*FakeVolume) {
	plugin.RLock()
	defer plugin.RUnlock()
	return plugin.Detachers
}

func (plugin *FakeVolumePlugin) GetNewDetacherCallCount() int {
	plugin.RLock()
	defer plugin.RUnlock()
	return plugin.NewDetacherCallCount
}

func (plugin *FakeVolumePlugin) CanAttach(spec *Spec) (bool, error) {
	return true, nil
}

func (plugin *FakeVolumePlugin) CanDeviceMount(spec *Spec) (bool, error) {
	return true, nil
}

func (plugin *FakeVolumePlugin) Recycle(pvName string, spec *Spec, eventRecorder recyclerclient.RecycleEventRecorder) error {
	return nil
}

func (plugin *FakeVolumePlugin) NewDeleter(spec *Spec) (Deleter, error) {
	return &FakeDeleter{"/attributesTransferredFromSpec", MetricsNil{}}, nil
}

func (plugin *FakeVolumePlugin) NewProvisioner(options VolumeOptions) (Provisioner, error) {
	plugin.Lock()
	defer plugin.Unlock()
	plugin.LastProvisionerOptions = options
	return &FakeProvisioner{options, plugin.Host, plugin.ProvisionDelaySeconds}, nil
}

func (plugin *FakeVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
	return []v1.PersistentVolumeAccessMode{}
}

func (plugin *FakeVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*Spec, error) {
	return &Spec{
		Volume: &v1.Volume{
			Name: volumeName,
		},
	}, nil
}

// Block volume support
func (plugin *FakeVolumePlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, mountPath string) (*Spec, error) {
	return &Spec{
		Volume: &v1.Volume{
			Name: volumeName,
		},
	}, nil
}

func (plugin *FakeVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
	return []string{}, nil
}

// Expandable volume support
func (plugin *FakeVolumePlugin) ExpandVolumeDevice(spec *Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) {
	return resource.Quantity{}, nil
}

func (plugin *FakeVolumePlugin) RequiresFSResize() bool {
	return true
}

func (plugin *FakeVolumePlugin) NodeExpand(resizeOptions NodeResizeOptions) (bool, error) {
	return true, nil
}

func (plugin *FakeVolumePlugin) GetVolumeLimits() (map[string]int64, error) {
	return plugin.VolumeLimits, plugin.VolumeLimitsError
}

func (plugin *FakeVolumePlugin) VolumeLimitKey(spec *Spec) string {
	return plugin.LimitKey
}

// FakeBasicVolumePlugin implements a basic volume plugin. It wrappers on
// FakeVolumePlugin but implements VolumePlugin interface only.
// It is useful to test logic involving plugin interfaces.
type FakeBasicVolumePlugin struct {
	Plugin FakeVolumePlugin
}

func (f *FakeBasicVolumePlugin) GetPluginName() string {
	return f.Plugin.GetPluginName()
}

func (f *FakeBasicVolumePlugin) GetVolumeName(spec *Spec) (string, error) {
	return f.Plugin.GetVolumeName(spec)
}

// CanSupport tests whether the plugin supports a given volume specification by
// testing volume spec name begins with plugin name or not.
// This is useful to choose plugin by volume in testing.
func (f *FakeBasicVolumePlugin) CanSupport(spec *Spec) bool {
	return strings.HasPrefix(spec.Name(), f.GetPluginName())
}

func (f *FakeBasicVolumePlugin) ConstructVolumeSpec(ame, mountPath string) (*Spec, error) {
	return f.Plugin.ConstructVolumeSpec(ame, mountPath)
}

func (f *FakeBasicVolumePlugin) Init(ost VolumeHost) error {
	return f.Plugin.Init(ost)
}

func (f *FakeBasicVolumePlugin) NewMounter(spec *Spec, pod *v1.Pod, opts VolumeOptions) (Mounter, error) {
	return f.Plugin.NewMounter(spec, pod, opts)
}

func (f *FakeBasicVolumePlugin) NewUnmounter(volName string, podUID types.UID) (Unmounter, error) {
	return f.Plugin.NewUnmounter(volName, podUID)
}

func (f *FakeBasicVolumePlugin) RequiresRemount() bool {
	return f.Plugin.RequiresRemount()
}

func (f *FakeBasicVolumePlugin) SupportsBulkVolumeVerification() bool {
	return f.Plugin.SupportsBulkVolumeVerification()
}

func (f *FakeBasicVolumePlugin) SupportsMountOption() bool {
	return f.Plugin.SupportsMountOption()
}

var _ VolumePlugin = &FakeBasicVolumePlugin{}

// FakeDeviceMountableVolumePlugin implements an device mountable plugin based on FakeBasicVolumePlugin.
type FakeDeviceMountableVolumePlugin struct {
	FakeBasicVolumePlugin
}

func (f *FakeDeviceMountableVolumePlugin) CanDeviceMount(spec *Spec) (bool, error) {
	return true, nil
}

func (f *FakeDeviceMountableVolumePlugin) NewDeviceMounter() (DeviceMounter, error) {
	return f.Plugin.NewDeviceMounter()
}

func (f *FakeDeviceMountableVolumePlugin) NewDeviceUnmounter() (DeviceUnmounter, error) {
	return f.Plugin.NewDeviceUnmounter()
}

func (f *FakeDeviceMountableVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
	return f.Plugin.GetDeviceMountRefs(deviceMountPath)
}

var _ VolumePlugin = &FakeDeviceMountableVolumePlugin{}
var _ DeviceMountableVolumePlugin = &FakeDeviceMountableVolumePlugin{}

// FakeAttachableVolumePlugin implements an attachable plugin based on FakeDeviceMountableVolumePlugin.
type FakeAttachableVolumePlugin struct {
	FakeDeviceMountableVolumePlugin
}

func (f *FakeAttachableVolumePlugin) NewAttacher() (Attacher, error) {
	return f.Plugin.NewAttacher()
}

func (f *FakeAttachableVolumePlugin) NewDetacher() (Detacher, error) {
	return f.Plugin.NewDetacher()
}

func (f *FakeAttachableVolumePlugin) CanAttach(spec *Spec) (bool, error) {
	return true, nil
}

var _ VolumePlugin = &FakeAttachableVolumePlugin{}
var _ AttachableVolumePlugin = &FakeAttachableVolumePlugin{}

type FakeFileVolumePlugin struct {
}

func (plugin *FakeFileVolumePlugin) Init(host VolumeHost) error {
	return nil
}

func (plugin *FakeFileVolumePlugin) GetPluginName() string {
	return "fake-file-plugin"
}

func (plugin *FakeFileVolumePlugin) GetVolumeName(spec *Spec) (string, error) {
	return "", nil
}

func (plugin *FakeFileVolumePlugin) CanSupport(spec *Spec) bool {
	return true
}

func (plugin *FakeFileVolumePlugin) RequiresRemount() bool {
	return false
}

func (plugin *FakeFileVolumePlugin) SupportsMountOption() bool {
	return false
}

func (plugin *FakeFileVolumePlugin) SupportsBulkVolumeVerification() bool {
	return false
}

func (plugin *FakeFileVolumePlugin) NewMounter(spec *Spec, podRef *v1.Pod, opts VolumeOptions) (Mounter, error) {
	return nil, nil
}

func (plugin *FakeFileVolumePlugin) NewUnmounter(name string, podUID types.UID) (Unmounter, error) {
	return nil, nil
}

func (plugin *FakeFileVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*Spec, error) {
	return nil, nil
}

func NewFakeFileVolumePlugin() []VolumePlugin {
	return []VolumePlugin{&FakeFileVolumePlugin{}}
}

type FakeVolume struct {
	sync.RWMutex
	PodUID  types.UID
	VolName string
	Plugin  *FakeVolumePlugin
	MetricsNil
	VolumesAttached map[string]types.NodeName

	// Add callbacks as needed
	WaitForAttachHook func(spec *Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error)
	UnmountDeviceHook func(globalMountPath string) error

	SetUpCallCount              int
	TearDownCallCount           int
	AttachCallCount             int
	DetachCallCount             int
	WaitForAttachCallCount      int
	MountDeviceCallCount        int
	UnmountDeviceCallCount      int
	GetDeviceMountPathCallCount int
	SetUpDeviceCallCount        int
	TearDownDeviceCallCount     int
	MapPodDeviceCallCount       int
	UnmapPodDeviceCallCount     int
	GlobalMapPathCallCount      int
	PodDeviceMapPathCallCount   int
}

func getUniqueVolumeName(spec *Spec) (string, error) {
	var volumeName string
	if spec.Volume != nil && spec.Volume.GCEPersistentDisk != nil {
		volumeName = spec.Volume.GCEPersistentDisk.PDName
	} else if spec.PersistentVolume != nil &&
		spec.PersistentVolume.Spec.GCEPersistentDisk != nil {
		volumeName = spec.PersistentVolume.Spec.GCEPersistentDisk.PDName
	}
	if volumeName == "" {
		volumeName = spec.Name()
	}
	return volumeName, nil
}

func (_ *FakeVolume) GetAttributes() Attributes {
	return Attributes{
		ReadOnly:        false,
		Managed:         true,
		SupportsSELinux: true,
	}
}

func (fv *FakeVolume) CanMount() error {
	return nil
}

func (fv *FakeVolume) SetUp(mounterArgs MounterArgs) error {
	fv.Lock()
	defer fv.Unlock()
	fv.SetUpCallCount++
	return fv.SetUpAt(fv.getPath(), mounterArgs)
}

func (fv *FakeVolume) GetSetUpCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.SetUpCallCount
}

func (fv *FakeVolume) SetUpAt(dir string, mounterArgs MounterArgs) error {
	return os.MkdirAll(dir, 0750)
}

func (fv *FakeVolume) GetPath() string {
	fv.RLock()
	defer fv.RUnlock()
	return fv.getPath()
}

func (fv *FakeVolume) getPath() string {
	return filepath.Join(fv.Plugin.Host.GetPodVolumeDir(fv.PodUID, utilstrings.EscapeQualifiedName(fv.Plugin.PluginName), fv.VolName))
}

func (fv *FakeVolume) TearDown() error {
	fv.Lock()
	defer fv.Unlock()
	fv.TearDownCallCount++
	return fv.TearDownAt(fv.getPath())
}

func (fv *FakeVolume) GetTearDownCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.TearDownCallCount
}

func (fv *FakeVolume) TearDownAt(dir string) error {
	return os.RemoveAll(dir)
}

// Block volume support
func (fv *FakeVolume) SetUpDevice() error {
	fv.Lock()
	defer fv.Unlock()
	fv.SetUpDeviceCallCount++
	return nil
}

// Block volume support
func (fv *FakeVolume) GetSetUpDeviceCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.SetUpDeviceCallCount
}

// Block volume support
func (fv *FakeVolume) GetGlobalMapPath(spec *Spec) (string, error) {
	fv.RLock()
	defer fv.RUnlock()
	fv.GlobalMapPathCallCount++
	return fv.getGlobalMapPath()
}

// Block volume support
func (fv *FakeVolume) getGlobalMapPath() (string, error) {
	return filepath.Join(fv.Plugin.Host.GetVolumeDevicePluginDir(utilstrings.EscapeQualifiedName(fv.Plugin.PluginName)), "pluginDependentPath"), nil
}

// Block volume support
func (fv *FakeVolume) GetGlobalMapPathCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.GlobalMapPathCallCount
}

// Block volume support
func (fv *FakeVolume) GetPodDeviceMapPath() (string, string) {
	fv.RLock()
	defer fv.RUnlock()
	fv.PodDeviceMapPathCallCount++
	return fv.getPodDeviceMapPath()
}

// Block volume support
func (fv *FakeVolume) getPodDeviceMapPath() (string, string) {
	return filepath.Join(fv.Plugin.Host.GetPodVolumeDeviceDir(fv.PodUID, utilstrings.EscapeQualifiedName(fv.Plugin.PluginName))), fv.VolName
}

// Block volume support
func (fv *FakeVolume) GetPodDeviceMapPathCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.PodDeviceMapPathCallCount
}

// Block volume support
func (fv *FakeVolume) TearDownDevice(mapPath string, devicePath string) error {
	fv.Lock()
	defer fv.Unlock()
	fv.TearDownDeviceCallCount++
	return nil
}

// Block volume support
func (fv *FakeVolume) GetTearDownDeviceCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.TearDownDeviceCallCount
}

// Block volume support
func (fv *FakeVolume) UnmapPodDevice() error {
	fv.Lock()
	defer fv.Unlock()
	fv.UnmapPodDeviceCallCount++
	return nil
}

// Block volume support
func (fv *FakeVolume) GetUnmapPodDeviceCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.UnmapPodDeviceCallCount
}

// Block volume support
func (fv *FakeVolume) MapPodDevice() (string, error) {
	fv.Lock()
	defer fv.Unlock()
	fv.MapPodDeviceCallCount++
	return "", nil
}

// Block volume support
func (fv *FakeVolume) GetMapPodDeviceCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.MapPodDeviceCallCount
}

func (fv *FakeVolume) Attach(spec *Spec, nodeName types.NodeName) (string, error) {
	fv.Lock()
	defer fv.Unlock()
	fv.AttachCallCount++
	volumeName, err := getUniqueVolumeName(spec)
	if err != nil {
		return "", err
	}
	volumeNode, exist := fv.VolumesAttached[volumeName]
	if exist {
		if nodeName == UncertainAttachNode {
			return "/dev/vdb-test", nil
		}
		// even if volume was previously attached to time out, we need to keep returning error
		// so as reconciler can not confirm this volume as attached.
		if nodeName == TimeoutAttachNode {
			return "", fmt.Errorf("Timed out to attach volume %q to node %q", volumeName, nodeName)
		}
		if volumeNode == nodeName || volumeNode == MultiAttachNode || nodeName == MultiAttachNode {
			return "/dev/vdb-test", nil
		}
		return "", fmt.Errorf("volume %q trying to attach to node %q is already attached to node %q", volumeName, nodeName, volumeNode)
	}

	fv.VolumesAttached[volumeName] = nodeName
	if nodeName == UncertainAttachNode || nodeName == TimeoutAttachNode {
		return "", fmt.Errorf("Timed out to attach volume %q to node %q", volumeName, nodeName)
	}
	return "/dev/vdb-test", nil
}

func (fv *FakeVolume) GetAttachCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.AttachCallCount
}

func (fv *FakeVolume) WaitForAttach(spec *Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error) {
	fv.Lock()
	defer fv.Unlock()
	fv.WaitForAttachCallCount++
	if fv.WaitForAttachHook != nil {
		return fv.WaitForAttachHook(spec, devicePath, pod, spectimeout)
	}
	return "/dev/sdb", nil
}

func (fv *FakeVolume) GetWaitForAttachCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.WaitForAttachCallCount
}

func (fv *FakeVolume) GetDeviceMountPath(spec *Spec) (string, error) {
	fv.Lock()
	defer fv.Unlock()
	fv.GetDeviceMountPathCallCount++
	return "", nil
}

func (fv *FakeVolume) MountDevice(spec *Spec, devicePath string, deviceMountPath string) error {
	fv.Lock()
	defer fv.Unlock()
	fv.MountDeviceCallCount++
	return nil
}

func (fv *FakeVolume) GetMountDeviceCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.MountDeviceCallCount
}

func (fv *FakeVolume) Detach(volumeName string, nodeName types.NodeName) error {
	fv.Lock()
	defer fv.Unlock()
	fv.DetachCallCount++
	if _, exist := fv.VolumesAttached[volumeName]; !exist {
		return fmt.Errorf("Trying to detach volume %q that is not attached to the node %q", volumeName, nodeName)
	}
	delete(fv.VolumesAttached, volumeName)
	return nil
}

func (fv *FakeVolume) VolumesAreAttached(spec []*Spec, nodeName types.NodeName) (map[*Spec]bool, error) {
	fv.Lock()
	defer fv.Unlock()
	return nil, nil
}

func (fv *FakeVolume) GetDetachCallCount() int {
	fv.RLock()
	defer fv.RUnlock()
	return fv.DetachCallCount
}

func (fv *FakeVolume) UnmountDevice(globalMountPath string) error {
	fv.Lock()
	defer fv.Unlock()
	fv.UnmountDeviceCallCount++
	if fv.UnmountDeviceHook != nil {
		return fv.UnmountDeviceHook(globalMountPath)
	}
	return nil
}

type FakeDeleter struct {
	path string
	MetricsNil
}

func (fd *FakeDeleter) Delete() error {
	// nil is success, else error
	return nil
}

func (fd *FakeDeleter) GetPath() string {
	return fd.path
}

type FakeProvisioner struct {
	Options               VolumeOptions
	Host                  VolumeHost
	ProvisionDelaySeconds int
}

func (fc *FakeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
	// Add provision failure hook
	if fc.Options.Parameters != nil {
		if _, ok := fc.Options.Parameters[ExpectProvisionFailureKey]; ok {
			return nil, fmt.Errorf("expected error")
		}
	}
	fullpath := fmt.Sprintf("/tmp/hostpath_pv/%s", uuid.NewUUID())

	pv := &v1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			Name: fc.Options.PVName,
			Annotations: map[string]string{
				util.VolumeDynamicallyCreatedByKey: "fakeplugin-provisioner",
			},
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeReclaimPolicy: fc.Options.PersistentVolumeReclaimPolicy,
			AccessModes:                   fc.Options.PVC.Spec.AccessModes,
			Capacity: v1.ResourceList{
				v1.ResourceName(v1.ResourceStorage): fc.Options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)],
			},
			PersistentVolumeSource: v1.PersistentVolumeSource{
				HostPath: &v1.HostPathVolumeSource{
					Path: fullpath,
				},
			},
		},
	}

	if fc.ProvisionDelaySeconds > 0 {
		time.Sleep(time.Duration(fc.ProvisionDelaySeconds) * time.Second)
	}

	return pv, nil
}

var _ volumepathhandler.BlockVolumePathHandler = &FakeVolumePathHandler{}

//NewDeviceHandler Create a new IoHandler implementation
func NewBlockVolumePathHandler() volumepathhandler.BlockVolumePathHandler {
	return &FakeVolumePathHandler{}
}

type FakeVolumePathHandler struct {
	sync.RWMutex
}

func (fv *FakeVolumePathHandler) MapDevice(devicePath string, mapDir string, linkName string, bindMount bool) error {
	// nil is success, else error
	return nil
}

func (fv *FakeVolumePathHandler) UnmapDevice(mapDir string, linkName string, bindMount bool) error {
	// nil is success, else error
	return nil
}

func (fv *FakeVolumePathHandler) RemoveMapPath(mapPath string) error {
	// nil is success, else error
	return nil
}

func (fv *FakeVolumePathHandler) IsSymlinkExist(mapPath string) (bool, error) {
	// nil is success, else error
	return true, nil
}

func (fv *FakeVolumePathHandler) IsDeviceBindMountExist(mapPath string) (bool, error) {
	// nil is success, else error
	return true, nil
}

func (fv *FakeVolumePathHandler) GetDeviceBindMountRefs(devPath string, mapPath string) ([]string, error) {
	// nil is success, else error
	return []string{}, nil
}

func (fv *FakeVolumePathHandler) FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error) {
	// nil is success, else error
	return "", nil
}

func (fv *FakeVolumePathHandler) AttachFileDevice(path string) (string, error) {
	// nil is success, else error
	return "", nil
}

func (fv *FakeVolumePathHandler) DetachFileDevice(path string) error {
	// nil is success, else error
	return nil
}

func (fv *FakeVolumePathHandler) GetLoopDevice(path string) (string, error) {
	// nil is success, else error
	return "/dev/loop1", nil
}

// FindEmptyDirectoryUsageOnTmpfs finds the expected usage of an empty directory existing on
// a tmpfs filesystem on this system.
func FindEmptyDirectoryUsageOnTmpfs() (*resource.Quantity, error) {
	tmpDir, err := utiltesting.MkTmpdir("metrics_du_test")
	if err != nil {
		return nil, err
	}
	defer os.RemoveAll(tmpDir)
	out, err := exec.New().Command("nice", "-n", "19", "du", "-x", "-s", "-B", "1", tmpDir).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed command 'du' on %s with error %v", tmpDir, err)
	}
	used, err := resource.ParseQuantity(strings.Fields(string(out))[0])
	if err != nil {
		return nil, fmt.Errorf("failed to parse 'du' output %s due to error %v", out, err)
	}
	used.Format = resource.BinarySI
	return &used, nil
}

// VerifyAttachCallCount ensures that at least one of the Attachers for this
// plugin has the expectedAttachCallCount number of calls. Otherwise it returns
// an error.
func VerifyAttachCallCount(
	expectedAttachCallCount int,
	fakeVolumePlugin *FakeVolumePlugin) error {
	for _, attacher := range fakeVolumePlugin.GetAttachers() {
		actualCallCount := attacher.GetAttachCallCount()
		if actualCallCount >= expectedAttachCallCount {
			return nil
		}
	}

	return fmt.Errorf(
		"No attachers have expected AttachCallCount. Expected: <%v>.",
		expectedAttachCallCount)
}

// VerifyZeroAttachCalls ensures that all of the Attachers for this plugin have
// a zero AttachCallCount. Otherwise it returns an error.
func VerifyZeroAttachCalls(fakeVolumePlugin *FakeVolumePlugin) error {
	for _, attacher := range fakeVolumePlugin.GetAttachers() {
		actualCallCount := attacher.GetAttachCallCount()
		if actualCallCount != 0 {
			return fmt.Errorf(
				"At least one attacher has non-zero AttachCallCount: <%v>.",
				actualCallCount)
		}
	}

	return nil
}

// VerifyWaitForAttachCallCount ensures that at least one of the Mounters for
// this plugin has the expectedWaitForAttachCallCount number of calls. Otherwise
// it returns an error.
func VerifyWaitForAttachCallCount(
	expectedWaitForAttachCallCount int,
	fakeVolumePlugin *FakeVolumePlugin) error {
	for _, attacher := range fakeVolumePlugin.GetAttachers() {
		actualCallCount := attacher.GetWaitForAttachCallCount()
		if actualCallCount >= expectedWaitForAttachCallCount {
			return nil
		}
	}

	return fmt.Errorf(
		"No Attachers have expected WaitForAttachCallCount. Expected: <%v>.",
		expectedWaitForAttachCallCount)
}

// VerifyZeroWaitForAttachCallCount ensures that all Attachers for this plugin
// have a zero WaitForAttachCallCount. Otherwise it returns an error.
func VerifyZeroWaitForAttachCallCount(fakeVolumePlugin *FakeVolumePlugin) error {
	for _, attacher := range fakeVolumePlugin.GetAttachers() {
		actualCallCount := attacher.GetWaitForAttachCallCount()
		if actualCallCount != 0 {
			return fmt.Errorf(
				"At least one attacher has non-zero WaitForAttachCallCount: <%v>.",
				actualCallCount)
		}
	}

	return nil
}

// VerifyMountDeviceCallCount ensures that at least one of the Mounters for
// this plugin has the expectedMountDeviceCallCount number of calls. Otherwise
// it returns an error.
func VerifyMountDeviceCallCount(
	expectedMountDeviceCallCount int,
	fakeVolumePlugin *FakeVolumePlugin) error {
	for _, attacher := range fakeVolumePlugin.GetAttachers() {
		actualCallCount := attacher.GetMountDeviceCallCount()
		if actualCallCount >= expectedMountDeviceCallCount {
			return nil
		}
	}

	return fmt.Errorf(
		"No Attachers have expected MountDeviceCallCount. Expected: <%v>.",
		expectedMountDeviceCallCount)
}

// VerifyZeroMountDeviceCallCount ensures that all Attachers for this plugin
// have a zero MountDeviceCallCount. Otherwise it returns an error.
func VerifyZeroMountDeviceCallCount(fakeVolumePlugin *FakeVolumePlugin) error {
	for _, attacher := range fakeVolumePlugin.GetAttachers() {
		actualCallCount := attacher.GetMountDeviceCallCount()
		if actualCallCount != 0 {
			return fmt.Errorf(
				"At least one attacher has non-zero MountDeviceCallCount: <%v>.",
				actualCallCount)
		}
	}

	return nil
}

// VerifySetUpCallCount ensures that at least one of the Mounters for this
// plugin has the expectedSetUpCallCount number of calls. Otherwise it returns
// an error.
func VerifySetUpCallCount(
	expectedSetUpCallCount int,
	fakeVolumePlugin *FakeVolumePlugin) error {
	for _, mounter := range fakeVolumePlugin.GetMounters() {
		actualCallCount := mounter.GetSetUpCallCount()
		if actualCallCount >= expectedSetUpCallCount {
			return nil
		}
	}

	return fmt.Errorf(
		"No Mounters have expected SetUpCallCount. Expected: <%v>.",
		expectedSetUpCallCount)
}

// VerifyZeroSetUpCallCount ensures that all Mounters for this plugin have a
// zero SetUpCallCount. Otherwise it returns an error.
func VerifyZeroSetUpCallCount(fakeVolumePlugin *FakeVolumePlugin) error {
	for _, mounter := range fakeVolumePlugin.GetMounters() {
		actualCallCount := mounter.GetSetUpCallCount()
		if actualCallCount != 0 {
			return fmt.Errorf(
				"At least one mounter has non-zero SetUpCallCount: <%v>.",
				actualCallCount)
		}
	}

	return nil
}

// VerifyTearDownCallCount ensures that at least one of the Unounters for this
// plugin has the expectedTearDownCallCount number of calls. Otherwise it
// returns an error.
func VerifyTearDownCallCount(
	expectedTearDownCallCount int,
	fakeVolumePlugin *FakeVolumePlugin) error {
	for _, unmounter := range fakeVolumePlugin.GetUnmounters() {
		actualCallCount := unmounter.GetTearDownCallCount()
		if actualCallCount >= expectedTearDownCallCount {
			return nil
		}
	}

	return fmt.Errorf(
		"No Unmounters have expected SetUpCallCount. Expected: <%v>.",
		expectedTearDownCallCount)
}

// VerifyZeroTearDownCallCount ensures that all Mounters for this plugin have a
// zero TearDownCallCount. Otherwise it returns an error.
func VerifyZeroTearDownCallCount(fakeVolumePlugin *FakeVolumePlugin) error {
	for _, mounter := range fakeVolumePlugin.GetMounters() {
		actualCallCount := mounter.GetTearDownCallCount()
		if actualCallCount != 0 {
			return fmt.Errorf(
				"At least one mounter has non-zero TearDownCallCount: <%v>.",
				actualCallCount)
		}
	}

	return nil
}

// VerifyDetachCallCount ensures that at least one of the Attachers for this
// plugin has the expectedDetachCallCount number of calls. Otherwise it returns
// an error.
func VerifyDetachCallCount(
	expectedDetachCallCount int,
	fakeVolumePlugin *FakeVolumePlugin) error {
	for _, detacher := range fakeVolumePlugin.GetDetachers() {
		actualCallCount := detacher.GetDetachCallCount()
		if actualCallCount == expectedDetachCallCount {
			return nil
		}
	}

	return fmt.Errorf(
		"No Detachers have expected DetachCallCount. Expected: <%v>.",
		expectedDetachCallCount)
}

// VerifyZeroDetachCallCount ensures that all Detachers for this plugin have a
// zero DetachCallCount. Otherwise it returns an error.
func VerifyZeroDetachCallCount(fakeVolumePlugin *FakeVolumePlugin) error {
	for _, detacher := range fakeVolumePlugin.GetDetachers() {
		actualCallCount := detacher.GetDetachCallCount()
		if actualCallCount != 0 {
			return fmt.Errorf(
				"At least one detacher has non-zero DetachCallCount: <%v>.",
				actualCallCount)
		}
	}

	return nil
}

// VerifySetUpDeviceCallCount ensures that at least one of the Mappers for this
// plugin has the expectedSetUpDeviceCallCount number of calls. Otherwise it
// returns an error.
func VerifySetUpDeviceCallCount(
	expectedSetUpDeviceCallCount int,
	fakeVolumePlugin *FakeVolumePlugin) error {
	for _, mapper := range fakeVolumePlugin.GetBlockVolumeMapper() {
		actualCallCount := mapper.GetSetUpDeviceCallCount()
		if actualCallCount >= expectedSetUpDeviceCallCount {
			return nil
		}
	}

	return fmt.Errorf(
		"No Mapper have expected SetUpDeviceCallCount. Expected: <%v>.",
		expectedSetUpDeviceCallCount)
}

// VerifyTearDownDeviceCallCount ensures that at least one of the Unmappers for this
// plugin has the expectedTearDownDeviceCallCount number of calls. Otherwise it
// returns an error.
func VerifyTearDownDeviceCallCount(
	expectedTearDownDeviceCallCount int,
	fakeVolumePlugin *FakeVolumePlugin) error {
	for _, unmapper := range fakeVolumePlugin.GetBlockVolumeUnmapper() {
		actualCallCount := unmapper.GetTearDownDeviceCallCount()
		if actualCallCount >= expectedTearDownDeviceCallCount {
			return nil
		}
	}

	return fmt.Errorf(
		"No Unmapper have expected TearDownDeviceCallCount. Expected: <%v>.",
		expectedTearDownDeviceCallCount)
}

// VerifyZeroTearDownDeviceCallCount ensures that all Mappers for this plugin have a
// zero TearDownDeviceCallCount. Otherwise it returns an error.
func VerifyZeroTearDownDeviceCallCount(fakeVolumePlugin *FakeVolumePlugin) error {
	for _, unmapper := range fakeVolumePlugin.GetBlockVolumeUnmapper() {
		actualCallCount := unmapper.GetTearDownDeviceCallCount()
		if actualCallCount != 0 {
			return fmt.Errorf(
				"At least one unmapper has non-zero TearDownDeviceCallCount: <%v>.",
				actualCallCount)
		}
	}

	return nil
}

// VerifyGetGlobalMapPathCallCount ensures that at least one of the Mappers for this
// plugin has the expectedGlobalMapPathCallCount number of calls. Otherwise it returns
// an error.
func VerifyGetGlobalMapPathCallCount(
	expectedGlobalMapPathCallCount int,
	fakeVolumePlugin *FakeVolumePlugin) error {
	for _, mapper := range fakeVolumePlugin.GetBlockVolumeMapper() {
		actualCallCount := mapper.GetGlobalMapPathCallCount()
		if actualCallCount == expectedGlobalMapPathCallCount {
			return nil
		}
	}

	return fmt.Errorf(
		"No Mappers have expected GetGlobalMapPathCallCount. Expected: <%v>.",
		expectedGlobalMapPathCallCount)
}

// VerifyGetPodDeviceMapPathCallCount ensures that at least one of the Mappers for this
// plugin has the expectedPodDeviceMapPathCallCount number of calls. Otherwise it returns
// an error.
func VerifyGetPodDeviceMapPathCallCount(
	expectedPodDeviceMapPathCallCount int,
	fakeVolumePlugin *FakeVolumePlugin) error {
	for _, mapper := range fakeVolumePlugin.GetBlockVolumeMapper() {
		actualCallCount := mapper.GetPodDeviceMapPathCallCount()
		if actualCallCount == expectedPodDeviceMapPathCallCount {
			return nil
		}
	}

	return fmt.Errorf(
		"No Mappers have expected GetPodDeviceMapPathCallCount. Expected: <%v>.",
		expectedPodDeviceMapPathCallCount)
}

// VerifyGetMapPodDeviceCallCount ensures that at least one of the Mappers for this
// plugin has the expectedMapPodDeviceCallCount number of calls. Otherwise it
// returns an error.
func VerifyGetMapPodDeviceCallCount(
	expectedMapPodDeviceCallCount int,
	fakeVolumePlugin *FakeVolumePlugin) error {
	for _, mapper := range fakeVolumePlugin.GetBlockVolumeMapper() {
		actualCallCount := mapper.GetMapPodDeviceCallCount()
		if actualCallCount >= expectedMapPodDeviceCallCount {
			return nil
		}
	}

	return fmt.Errorf(
		"No Mapper have expected MapPodDeviceCallCount. Expected: <%v>.",
		expectedMapPodDeviceCallCount)
}

// GetTestVolumePluginMgr creates, initializes, and returns a test volume plugin
// manager and fake volume plugin using a fake volume host.
func GetTestVolumePluginMgr(
	t *testing.T) (*VolumePluginMgr, *FakeVolumePlugin) {
	plugins := ProbeVolumePlugins(VolumeConfig{})
	v := NewFakeVolumeHost(
		"",      /* rootDir */
		nil,     /* kubeClient */
		plugins, /* plugins */
	)
	return v.pluginMgr, plugins[0].(*FakeVolumePlugin)
}

// CreateTestPVC returns a provisionable PVC for tests
func CreateTestPVC(capacity string, accessModes []v1.PersistentVolumeAccessMode) *v1.PersistentVolumeClaim {
	claim := v1.PersistentVolumeClaim{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "dummy",
			Namespace: "default",
		},
		Spec: v1.PersistentVolumeClaimSpec{
			AccessModes: accessModes,
			Resources: v1.ResourceRequirements{
				Requests: v1.ResourceList{
					v1.ResourceName(v1.ResourceStorage): resource.MustParse(capacity),
				},
			},
		},
	}
	return &claim
}

func MetricsEqualIgnoreTimestamp(a *Metrics, b *Metrics) bool {
	available := a.Available == b.Available
	capacity := a.Capacity == b.Capacity
	used := a.Used == b.Used
	inodes := a.Inodes == b.Inodes
	inodesFree := a.InodesFree == b.InodesFree
	inodesUsed := a.InodesUsed == b.InodesUsed
	return available && capacity && used && inodes && inodesFree && inodesUsed
}

func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
	for _, m := range modes {
		if m == mode {
			return true
		}
	}
	return false
}

func (f *fakeVolumeHost) CSIDriverLister() storagelisters.CSIDriverLister {
	return f.csiDriverLister
}

func (f *fakeVolumeHost) CSIDriversSynced() cache.InformerSynced {
	// not needed for testing
	return nil
}

func (f *fakeVolumeHost) CSINodeLister() storagelistersv1.CSINodeLister {
	// not needed for testing
	return nil
}

func (f *fakeVolumeHost) GetInformerFactory() informers.SharedInformerFactory {
	return f.informerFactory
}

func (f *fakeVolumeHost) IsAttachDetachController() bool {
	return true
}

func (f *fakeVolumeHost) SetKubeletError(err error) {
	f.mux.Lock()
	defer f.mux.Unlock()
	f.kubeletErr = err
	return
}

func (f *fakeVolumeHost) WaitForCacheSync() error {
	return nil
}

func (f *fakeVolumeHost) WaitForKubeletErrNil() error {
	return wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
		f.mux.Lock()
		defer f.mux.Unlock()
		return f.kubeletErr == nil, nil
	})
}


text:
Providing Sanctuary
Sermons & More
Safe Church Program
Creativity Group
Advocacy Actions
Eliseo Bird Sanctuaries
Meet Some of Us
The UCC
UPUCC Officers 2019-20
Potential 'Garden' Design
Sermons and More
How do you put your faith into action? At Umstead Park UCC we are guided by our Covenant, in particular the section about our commitment to the wider world:
With our steps toward a mutual relationship with the wider world, we will:
Share freely of our wealth and resources;
Recognize our identity as global citizens, intimately tied to and partnered with all of God's children in every land;
Demonstrate our concern for neighbor through service and prophetic voice;
Claim our inescapable connection with the sacred earth and all of creation;
Live justice as the social expression of love, and peace as the present manifestation of the world to come.
Every day we put faith into action through use of our (LEED certified) building, as a sanctuary for Eliseo (while he awaits resolution of his residency status), as offices, storage, and rehearsal space for the Justice Theater Project, and as meeting space for organizations like Healing Transitions and other service and social justice groups. We will host a LGBTQ Informational Workshop in our building on Saturday, November 10, 2018.
At least once a month we make food donations to the Urban Ministries Food Pantry. Our Creativity Group meets regularly to craft beautiful greeting cards which are offered for donation and all proceeds fund various charitable needs. The Creativity Group also conducts sessions at Healing Transitions. We actively participate through the course of the year with several organizations, including Rise Against Hunger, Out! Raleigh, Pride:Durham, Church World Service, the North Carolina Council of Churches, and Curamericas Global, and participate in activities like the Neuse River Cleanup (youth led this year), voter registration, the Martin Luther King Jr. Day of Service, various advocacy activities on topics like a non-partisan solution to gerrymandering (with Common Cause and DemocracyNC) and the NCAE Rally for Teachers. We also sponsor hurricane assistance workdays, typically in Eastern NC, as the need arises (starting with the aftermath of Hurricane Floyd in 1999), and have partnered on occasion with Habitat for Humanity. For several years we were a sponsor of the Magnificent Mile Race and Fundraiser for PLS research, the inspiration of our own Sarah Roberts-Witt who has PLS, a motor neuron disease related to ALS.
We collect donations for several groups and in the last 12 months they included the following:
UCC Our Church Wider Mission programs like One Great Hour of Sharing, Strengthen the Church, Neighbors in Need, and The Christmas Fund.
Urban Ministries: Really Souper Sunday
Healing Transitions: Sock It To Me Sunday
A Note in the Pocket Sunday
Oxford House: Second Chance Sunday
Heifer Project
Healing Transitions Toy Drive
UCC Southern Conference
UCC Eastern NC Association
Our History:
When UPUCC first came into existence in 1999 (then called North Raleigh UCC), we rented space in a middle school, later in a YMCA and did not have a building. As a UCC subsidized new church start, we committed 51% of weekly donations to Faith in Action. Through the years, as the church grew and became self-sufficient, we decided to purchase a building, in partnership with an architectural firm as co-tenants. Staying true to our covenant, the building qualified for LEED gold certification and we immediately looked for ways to apply this significant investment to Faith in Action. Our building is now in use 24/7 for Faith in Action.
(from the UPUCC Covenant)
Umstead Park United Church of Christ
8208 Brownleigh Drive
office@upucc.org

A thoughtful and progressive

redpajama_set_name: RedPajamaCommonCrawl


A customer visiting the Shipley store has praised staff for their outstanding customer service.
Whilst visiting the store for an eye test, the customer described their experience as 'excellent', particularly commending team member Andrew Tetley for his customer service whilst choosing a new pair of frames.
The customer commented: 'I visited the Shipley branch for an eye test and then went on to purchase new glasses. From start to finish my experience was excellent, with the staff member I would particularly like to commend on his fantastic service being Andrew Tetley.
'I have worked in retail for over 20 years and know that most people don't take the time to praise excellent customer service so I felt the need to acknowledge the great team in Shipley. Andrew helped me select new frames, and explained the different lenses ensuring I had the most suitable type for my needs.
Bhrat Phakey, store director at Specsavers in Shipley, said: 'When a customer comes in store for assistance, we always do our best to support them. It's great to hear that our team are providing excellent customer service.


text:
Instagram's New Feature Shares Your Location & Battery Life
Instagram's New Feature Shares Your Location & Even Battery Life
By Ian Servantes in Tech
The Instagram app is seen on an iPhone on 16 March, 2017. (Photo by Jaap Arriens/NurPhoto)
Getty Images / OnlyPhoto
Instagram is getting into the dystopian sharing-your-location business and even further into the encroaching-on-Snapchat business. A new app called Threads, a companion to Instagram, will serve as an all-encompassing messaging app that will allow users to automatically share not only their location, but also their speed and battery life with friends, according to The Verge. This precious information will be catered around users' "close friends" list on IG.
Threads is still in development, and it's unclear when it'll be rolled out. The Verge was able to see screenshots of the app in beta, and it looks similar to Instagram's existing messaging section. A green dot will show you if your friends are online, and you'll also be able to view their stories through Threads. One feature allows you to share your status, which includes presets such as "free," "busy," "bored," and… "piña colada."
The move comes as Snapchat users reportedly spend more time inside the app than those of Instagram. "The Youth" already uses it as a messaging app — a development I was shocked to learn when my 17-year-old brother insisted upon Snapping me instead of, you know, an actual text message.
Of course, there are practical considerations when it comes to location sharing, which Snapchat already offers. It can let your friends know if it's cool to pull up… or if you're safe when you're engaging in activities like, say, online dating.
On this episode of The Dropcast, we are joined by Juice Rodriguez, the VP of Marketing at Concepts, the brand that is known for their footwear collabs. Listen below.
Words by Ian Servantes
ian_servantes
Dog with a blog.
Instagram Debuts New Anti-Bullying Feature
DesignsponsoredJuly 10, 2019
Nike's Air Jordan 1 "Obsidian" & More Feature in This Week's Best Instagram Sneaker Photos
Instagram's New Checkout Feature Makes Online Shopping Easier
This Hefty New Smartphone Packs 50 Days of Battery Life
DesignsponsoredMarch 21, 2019
New Apple AirPods Have a Wireless Charging Case & Longer Battery Life
adidas' New Hoverturf & More Feature in This Week's Best Instagram Sneaker Photos
SneakerssponsoredOctober 12, 2019
Highsnobiety Design


The Club Scrub Endless Summer River Paddle & Rocking Sandbar Party with a Live Band.
Jonathan Dickinson State Park to the Rocking Jupiter Sandbar Party on October 14, 2017!
A Guided Paddle from Jonathan Dickinson State Park down the NW Fork of the Loxahatchee River to our Jupiter Sandbar Party (approx. 5-miles). We paddle both ways with current.
Launch at JD Park's River Area Swim Beach. Paddle down the Loxahatchee River to our Floating Refreshment Stops, relax then paddle the rest of the way to the Rocking Jupiter Sandbar Party! After a few hours of great food, drinks, live music and fun, then paddle back to JD with current.
See manatees, fish, birds, turtles and other wildlife as we paddle by mangrove islands and estuaries.
Grab your Kayaks, Paddleboards & Canoes!! If you don't have your own equipment, we have several outfitters who will deliver and pick up for the event. You will need make advanced reservations. Go to clubscrub.org for a list of outfitters.
Includes your park entry, a "Rocking Sandbar Party" with Lunch at the Jupiter sandbar, along with floating refreshment stops, and paddle support (boats and guides).
REGISTER NOW! ClubScrub.org Use PROMO CODE – IRMA to save $ 5 on your fees.


text:
You are here: Home › Salut! commentary › Can Laurent Blanc clean up French football?
Can Laurent Blanc clean up French football?
French football is in an appalling state. It was bad enough, following the Thierry Henry handball, even before the team reached South Africa. Once they got there, the rot really set in, from the awful performances to Anelka's foul-mouthed rant, from the players' revolt to the manager Raymond Domenech's disgraceful refusal to shake the hand of his South African counterpart. But wIll the sidelining of Domenech and a single match ban on the striking players make everything all right? …
Even I am beginning to tire of hearing about the rotten state of French football. But comments posted here recently prompt me to reflect on the latest developments.
The millionaires' mutiny in South Africa was a shameful but logical extension of the self-centred, scowling arrogance of the modern game.
I have reported here on some of the subsequent comments of players involved in that grim moment in French sporting history.
It is a mixture, if you leave aside Franck Ribery's separate "my conscience is clear – I didn't know she was under age", of blame-shifting, attempted justification of the unjustifiable and unconvincing contrition.
And now Laurent Blanc and the French Football Federation have acted: none of the squad of 23 who took part in the revolt, refusing to train two days ahead of the last group stage match, will be chosen for his first game in charge of the French national side.
Since the match is a friendly against Norway, we can safely take this to be a token sanction; it may or may not be followed by further punishment but Blanc is said to hope any such measures will not deprive him of a free choice for the forthcoming Euro 2012 qualifying ties.
Interestingly, the FFF session that agreed to the ban took place in the absence of a key member, the former French international Lilian Thuram, all the more odd because he had been banging on for the need for serious punishments for men who brought disgrace on their country and their sport.
I have no idea why he failed to turn up; one other member of the FFF pointed out that his own attendance had meant breaking into his holiday. My admiration for Thuram had already taken a knock when I read, in the doctor's surgery, a depressing report in the French magazine Le Point.
Describing the rebellious World Cup squad as well-paid but not well-behaved, a surly mob of spoilt idols cut off from the real world by their earphones and pulled-down baseball caps, the magazine nevertheless pointed out that there wasn't so much that was new in the players' attitude.
And even members of the glorious 1998 World Cup winning team had been contaminated. It recounted the story that Thuram had rejected an invitation to speak on the issue of tolerance to children in Alsace when the authorities' offer of a €1,500 fee fell €18,500 short of his requirements.
I believe Thuram has challenged the accuracy of the tale but I have been unable to locate the terms of his denial, beyond a third-party suggestion that he refused to undertake the engagement only after an original offer of the higher figure was reduced to the much lower one. I prefer to give him the benefit of the doubt, but the allegation has been repeatedly aired in France, where Thuram is rightly praised for his campaigning on racism and justice, leaves a nasty taste in the mouth.
What football needs to accept is that such stories, if true or allowed to go without full rebuttal, add to the general feeling of distance between super-rich players and the supporting public.
There is much more to put right in football than just making players behave responsibly and respectfully. And that applies way beyond France's borders.
But there does need to be a more generous approach to public relations generally. The contempt shown by football clubs and authorities towards fanzines, fan sites and other independent voices of support is scandalous, but perhaps a different argument.
In terms of the mainstream media, tennis gets it right in making post-match press conferences an obligation, not a choice. It doesn't deprive players of the right to say "no comment" to impertinent, intrusive or merely unwelcome questions but imposes upon them a reasonable duty to speak (or be seen to refuse to speak), via the media, to the public.
Blanc has his work cut out if he is going to clean up French football, force the players out of their brat bubbles and act more like ambassadors for France than dodgy mercenaries – AND win games. Didier Deschamps says he cannot do it alone, that a collective effort is necessary.
But then, since Blanc is a man who – along with his then chairman at Bordeaux – adopted an arrogant attitude of his own, belittling Sunderland AFC when Steve Bruce tried to sign Marouane Chamakh. I am not even sure I care whether he succeeds.
Tags: France, Laurent Blanc, Lilian Thuram, Marouane Chamakh, Sunderland
Sunderland 4 Hull City 2: Campbell souped up
Soapbox: the Algarve or East Neuk
4 Responses to "Can Laurent Blanc clean up French football?" Subscribe
Perhaps that arrogant attitude is actually what he needs to reshape the team. I wish him the best of luck and I'll be watching with great interest the comparative progress of France and England. Speaking of "brat bubbles" and "dodgy mercenaries." And the need to win games.
anal shera July 25, 2010 at 3:20 pm #
Larry will make it a total whitewash
Keith Hutton africa July 26, 2010 at 6:46 am #
Why is Platini so silent. Uefa have allowed political intervention in the FFF and allowed their own rules to be broken. The raw onion eating Platini is very quick off the draw when it comes to critising English football has his holier than thou attitude been lost. Get rid of Platini and Blatter and football will be the better for it. As for the French and the underage shaggers why do we even bother to worry about the principles involved they don't have any, why should we.
Kathaleen Ganem August 23, 2010 at 6:26 pm #
Comme tout les sportifs de haut niveau, Ribery a un super salaire. Ces émoluments sont juste honteux même si il est loin d'être nul tant de gens dans le monde crèvent de faim !!


text:
Transparency in the Art World
by Charles Thomson
At 5.30pm today in the Park Avenue Armory, New York, The Art Newspaper in association with ADAA (Art Dealers Association of America) presents an "Art Industry Summit" with the title "Transparency in the market: can we have more of it…" This robust start is somewhat undermined by the panel, which consists of an art lawyer, an art consultant, two art dealers and the chairman of Christie's International.
There is only one real issue related to transparency and that is the financial value of artwork in what Robert Hiscox, a major art insurer, has termed "the last unregulated financial market". Secrecy allows the kind of manipulation forbidden in other commercial sectors and the main beneficiaries are those whose job is selling art. Even the panel's art lawyer, Peter Stern, has a conflict of interest, having been an advisor to over 100 galleries and dealers (and sporting a photo of himself by Jeff Koons). The panel has a complete absence of anyone who is likely to benefit from transparency, namely the buyers of art, or anyone who poses a challenge to the status quo, or even anyone to stand up for the value of disinterested scholarship.
The truth is that the art market is highly regulated, but by those who serve their own ends, rather than any independent body in service to the consumer and society in general. One of the most despicable practices of certain top-end galleries is the restricted list of those who are allowed the privilege of buying work by a sought-after artist. Money is not enough: discretion and collaboration are required, particularly the discretion to not put the work on the open market. The simple reason for this anti-competitive practice is that the work may well at auction reach a lower price than that fixed by the gallery. This is likely to put the gallery to the inconvenience of bidding up the work at auction to the "right" price and possibly having to buy it back again.
As the ADAA Collector's Guide states, "Dealers appreciate clients who turn to them when re-selling, and are more likely to offer these collectors important works in the future." Ironically, the Guide was sponsored by Lehman Brothers, whose spectacular bankruptcy the following year led to revelations of how its value had been artificially increased by sleight-of-hand accountancy. The spin of the artworld is equally deceptive.
One excellent missing panel member is Don Thompson, a business professor and author of The $12 Million Stuffed Shark: the Curious Economics of Contemporary Art and Auction Houses. Those with any interest in transparency should make it mandatory reading, especially for anyone tempted into art acquisition by its oft-touted investment potential. His book is salutary reading. Of the 1,000 serious artists with gallery shows in New York and London in the 1980s, only 20 or so featured in Christie's or Sotheby's evening auctions in 2007.
80% of work bought from artists and 50% of work bought at auction will fail to reach its original purchase price ever again. Less than 50% of artists featured 25 years ago in Christie's or Sotheby's modern and contemporary auctions are still featured in a major auction. 80% of the work brought into the leading auction houses is rejected for the evening auctions. It is not even necessary to wait for a quarter of a decade: recently a glut of Damien Hirst's from those fallen on hard times led potential vendors being turned away.
The ADAA Guide makes a claim for its members that any art professional would also make, namely that they are "skilled at assessing the relative aesthetic merits of a given work, evaluating it both within the larger context of art history and within the specific context of the artist's oeuvre." Any evaluation within the larger context would have to include the caveat that many of the artists considered today to be leading exponents will be of little interest in the future, just as Edwin Long, who held the record price for a living artist in 1882 for his painting, The Babylonian Marriage Market, is now merely another Victorian also-ran, whose work fell to 10% of its previous value within 10 years of his death.
However, even the smaller context of contemporary art study is brushed aside if it proves inconvenient. There are obviously many vested interests, both financial and reputational, in maintaining the value of Damien Hirst's work, and this, as with the value of all art, is dependent on market confidence, in his case deriving to a large extent from his status as an innovative practitioner.
On 14 October 2010, Christie's in London auctioned Hirst's work, I am Become Death, Shatterer of Worlds, a large image made from butterflies. The accompanying text from the auction house described the background to this: "In 2003, Hirst took the butterfly paintings to a new level, concentrating the dazzling visual effect of their wings and creating the first of his so-called 'kaleidoscope' butterfly paintings, of which I Am Become Death, Shatterer of Worlds is arguably the greatest example."
In the September 2010 issue of The Jackdaw magazine I wrote an article based on Hirst's statement that he "didn't have any shame about stealing other people's ideas" and cited 15 instances where this might apply. These included John LeKay's exhibition of an anatomical model nine years before Hirst, LeKay's work of a crucified sheep carcass nine years before Hirst, and spin paintings done by, amongst others, Walter Robinson a decade before Hirst. Another example was Lori Precious, an LA artist, who started making "kaleidoscope" butterfly paintings in 1992, eleven years before Hirst. She exhibited them in US and the UK galleries and on the internet. The Guardian ran a page based on my article, showing images of comparative works by Hirst and other artists, including Precious, and asking which one was by Hirst. There was no significant difference.
As Christie's had failed to draw the attention of potential purchasers to this essential background information, I arrived outside their building in King Street on the evening of the Hirst auction, accompanied by fellow Stuckists, Emma Pugmire, Edgeworth Johnstone, Jasmine Maddock and Kyla Bullows. We set up a display of informative placards with slogans and comparative images of Precious's and Hirst's butterfly work. Immediately an imposing security official emerged and told us we would be leaving in ten minutes. My argument that we were quite entitled to be there led to him pushing me, which led to me phoning the police to report assault and threatening behaviour. After this, we were left alone.
The Hirst work performed poorly, selling at £1.9 million against an estimate of £2.5 – 3.5 million (both prices excluding buyer's premium, which is usually in media reports added to the sale price and omitted from the estimate, thus making the poor performance seem not quite as bad). I like to think some transparency contributed to the result.
Read The Jackdaw article: http://stuckism.com/Hirst/StoleArt.html
CHARLES THOMSON is co-founder of the Stuckists art group www.stuckism.com
More articles by:Charles Thomson


text:
Gratifying Play
By Rob Haines • April 23rd, 2013
Sex is many things to many people. Sex is love, power, release, revenge, an endless affirmation of lifelong companionship or an urgent night of lust between strangers. Sex is a defining moment, or a comfortable constant, or a swirling haze of passion and snatched recollection as time quickens and slows with reckless abandon. It's a spectrum of wants and desires stretching beyond the horizon in every direction, an intrinsic part of our cultural existence.
Yet while sex may be the underlying theme of 90 percent of popular music and countless novels and movies hailed as classics, videogames have struggled to keep up.
You fuck, you save the galaxy, credits roll.
It's no surprise that gaming and sex have a dysfunctional relationship. Even as the average age of the gaming population has soared upward, videogames have remained mired in immaturity. When a player's interactions with their world are limited to increasingly graphic displays of violence, slow-motion headshots and decapitations, sex can never be more than a distraction from the chaos – God of War's ridiculous vase-breaking QTE, basically, and that merely a brief moment of vapid titillation between tearing the heads from mythology's finest creations.
Even if game developers want to broach the topic, the enjoyable elements of sex are notoriously difficult to reproduce in isolation, entwined so deeply with emotion, connection and the specific desires of the individual; the very idea of translating such an act in full into satisfying game mechanics seems both futile and more than a little creepy to modern sensibilities.
So if the physicality of sex has no place in modern mainstream games, what about its emotional elements? What about sex as a narrative device, a lens to draw focus onto a character and their relationships with others?
Unfortunately – with the occasional notable exception – even games which attempt to handle relationships with a greater degree of maturity tend to give sex a raw deal. For example, despite an increasing focus game-to-game on shooting humanoids in the face, Mass Effect and its sequels were at their best when dealing with the interactions between Commander Shepard and the crew of the Normandy.
As long as you could ignore the cutscenes' obsession with Miranda's infeasibly shiny buttocks Mass Effect gave you time to come to know your companions and to understand them, culminating in the opportunity to become involved in an extended intimate relationship across all three games (provided you didn't get them killed along the way).
It's a brave attempt to treat sex and relationships as more than just an empty bullet-point, though it's by no means perfect. Despite all the moves in the right direction, sex in Mass Effect is still only ever treated as one thing: an achievement. The pinnacle of a relationship to which you must aspire.
Spend your in-game interactions saying the right things to the right person and you too can be rewarded with a shiny metal heart-shaped badge and a short cutscene of implied xenosexual intimacy. Nothing says sexy like completing the romance subplot.
Sex as achievement may be a step up from a QTE, but it's still entangling the sexual act in a game mechanic. The very existence of such a link creates an implicit expectation that the player can – and perhaps, should– manipulate the game to obtain the achievement.
A relationship becomes less about making a connection with another person, even a fictional one, and more about the reward at the end, less sex and more orgasm. Perhaps Bioware are victims of their own success in building convincing and sympathetic characters, but the juxtaposition of genuine emotions towards the crew and the allure of meta-gaming to obtain the paramour achievement left me with an unpleasant cognitive dissonance.
Was I choosing the responses appropriate to Shepard's current situation and her feelings on the matter, or did I just want to tick getting down off my list? And if so, did that mean I – as Shepard – was actually toying with the supposed object of my affections, trying to trick them into bed like some creepy intergalactic pickup artist?
It's a disparity only emphasized by the placement of the sex scenes themselves, each occurring in the lull before the storm just before Shepard plunges into battle to save the galaxy. The timing is clearly intended to make best use of sex's power as a narrative device – a punctuation to all your efforts reminding you of the personal factors at stake to complement the impending epic climax – while also maximizing the player's opportunities to meet the criteria to trigger the scene.
However, it also has the unintended side effect of leaving no time to reflect on the relationship afterwards. There's literally nothing more you can say to or do with your partner. You fuck, you save the galaxy, credits roll.
Persona 4 has a similarly dichotomous attitude to relationships, once again as an unfortunate result of its superb character work. A significant part of Persona's appeal is in forging relationships with the people you meet, in spending time with them to boost both your understanding of them as people and your social links which grant you various bonuses in combat.
Thanks to the excellent writing, the former often succeeds in taking precedence over the latter. You want to know who your friends are, what makes them tick, their ambitions and their fears.
Personally, I ended up in a relationship with a character not because I wanted the power she offered, nor because I wanted some achievement, but because she was clearly going through some shit and needed someone to be there. If I didn't step up to the plate, who else would?
Yet once again, sex was the death-knell of the relationship. In keeping with the game's high-school milieu, sexual activity is simply implied; once you're in an "intimate" relationship with one of the girls, they'll invite themselves over to stay the night in your room.
That said, since Persona 4's day-to-day mechanics are built on time-management, having a girl stay the night wastes a block of time with no benefit. The first time can be justified as curiosity, but after that the game actively dissuades you from repeating the act. If you've had sex once, Persona 4 promises, you'll never see the point in doing so again.
(Okay, maybe twice, but that'll just be to check that nothing was any different the second time.)
Clearly the game industry is inching forward, pushing the boundaries, but success means walking a knife-edge between the ridiculous and the obscene. If sex can mean so many things to so many people, isn't it time we figured out how to incorporate the concept of sex into games without tittering like schoolchildren, shoe-horning it into ridiculous QTEs or manipulating dialogue trees for sex points?
Aren't we as consumers ready for mature characters who include sex in their lives without it being a masturbatory fantasy for the player or another check-box on the increasing to-do list of modern gaming?
Maybe we should be.
Rob Haines's sexy Twitter can be found @rob_haines.
God of War, Mass Effect, Persona 4, QTE, Sex, Sex Week
Commentary, Games
Fragile Toys Living Again Craving Disappointment Tits or GTFO
Breasts and Books: The Knowledge of Charmed
Last Week's Comics 4/24/2013


text:
Peter Anson and a horse-drawn caravan
Chris Loughran
I have been a support of Stella Maris, the Apostleship of the Sea for a number of years now. My connection with the charity is a family one and stems from two quite separate elements of family history.
Firstly, my family is connected with one of the founders of Stella Maris, Peter Anson. Amongst his many accomplishments, he was a prolific author and talented artist. In 1934, Peter determined to tour England and Scotland in a horse-drawn caravan and advertised for a farrier to accompany him and to look after the horses. The successful applicant for this role (from 200 who applied) was my great-uncle, Anthony Rowe, the brother of my paternal grandmother. Beginning their journey at St Augustine's Datchet (where my father went to school and where two of Anthony's brothers were priests and taught) on Ash Wednesday 1934, they travelled through England into Scotland, turning around at Fort William to begin their journey south. Two books emerged from this journey:
"The Caravan Pilgrim" by Peter Anson and "The Brown Caravan – A Yorkshire Tyke's Wanderings in the South" by Anthony Rowe (with illustrations by Peter Anson).
As you might imagine, Anthony is something of a legend in my family and from an early age I was keen to learn more about Peter Anson. Doing so led me, in part, to Stella Maris.
The other connection is closer to home. My paternal grandfather was a ship's engineer and worked on and around the Tyne. My father developed an interest in ships and seafaring which has lasted throughout his life. My formative years were spent in Preston, Lancashire which at the time had a thriving dock. My father's interest in ships led to he and I spending many Saturday mornings watching the various ships arrive and unload, load and leave and – from time to time – accepting invitations to board ships and visit the captain in his cabin. My father's principal hobby is marine vexilology – the study of the flag and funnel insignia of ships and shipping companies – and in his prime was corresponding around the world in connection with his hobby. He still possesses one of the world's largest collections of ship's flags and funnels and has himself been published on this topic:
"Survey Of Mercantile Houseflags & Funnels" by J.L.Loughran (1979).


Members of the Easton Police Department take pride in thier professional accomplishments and ability to protect the citizens of Easton. They are dedicated to the concept of personal excellence at the highest level with uncompromising integrity. The core values of the Department are Fairness, Accountability, Integrity, and Respect.
The Easton Police Department is currently seeking applications for a Police Officer.
For information on the position and the hiring process, please click here.


modern entryway lighting ideas design entry lights foyer hallway with large medium size of upscale chandeliers crystal chandelier then tips successfully image an.
remote control battery operated track lighting controlled light captivating switch bulb lamp dimmer.
120 volt flexible led strip lights light reel single row.
levolor sliding panel track shades super value.
childrens light fixtures canada boys bedroom ceiling charming baby room lights medium size of lighti.
stella starfish table lamp harbor coast with nightlight font.
bailey mouth blown glass metal table lamp antique brass finish bulb shaped lovely designer lamps clear hand.
cordless dual sheer shades custom budget blackout cellular shade.
nutone exhaust fan light cover bathroom fans bath the home depot ceiling with and heater photos bulb.
navy blue roman shades target grey custom trellis shade.


text:
Jim Spore to Retire After Six Years as First and Only President & CEO of Reinvent Hampton Roads
Jim Spore, who was city manager of Virginia Beach for 24 years before becoming the charter President & CEO of Reinvent Hampton Roads (RHR), will retire June 15 after six years with the start-up regional economic development organization. The RHR Board of Directors has elected Nancy L. Grden, Executive Director of the Hampton Roads Maritime Collaborative for Growth & Innovation (HRMC) and Associate Vice President of Old Dominion University's Institute for Innovation & Entrepreneurship, as new President & CEO. Spore will continue in an advisory role through mid-August.
RHR is a non-profit whose mission is to create more and higher paying jobs, build a culture where entrepreneurship can thrive, and expand the region's economy.
"Jim has given outstanding service for RHR and defined its path," says John O. "Dubby" Wynne, Chairman of the Board of RHR. "With Jim's leadership, RHR has developed a framework for progress on a host of initiatives, including encouraging regional collaboration, retaining and attracting talent, and advancing infrastructure and resiliency. Hampton Roads is in a much better place thanks to Jim's leadership, relationships, and vision, as well as his long and distinguished career serving the community."
Wynne said that the RHR Board was unanimous in its decision to hire Grden. "Nancy has significant experience as an entrepreneur and in corporate boardrooms and university administration. She is also well aware that RHR needs to 'reinvent' itself, and we have already begun the process to redefine its function and structure. Nancy is eager to lead that evolution."
"Our region has a truly unique array of assets and opportunities to be leveraged and scaled," says Grden. "In collaboration with our community partners and municipal leaders, I look forward to building on RHR's foundation for the region's economic development, innovation, and leadership ahead." Grden's business credentials include Chief Development/Marketing Officer for Medicaid/Medicare services company Amerigroup (acquired by Anthem), co-founder and General Manager for biotech company Genomind, and in strategic planning and other executive roles with predecessor banks to Bank of America, healthcare company ValueOptions, as well as in federal and state government relations in Washington, D.C. She is active in community leadership organizations for entrepreneurship and innovation, including as inaugural chair of 757 Collab and immediate past Chair of 757 Angels, an angel-funding network.
"It has been a tremendous honor to head up RHR these past six plus years," says Spore, who was city manager in Garland, Texas before assuming the same post in Virginia Beach in 1991. "I have worked with a stellar staff and volunteer business leaders to help position our community for further growth. I have also collaborated closely with Nancy and am confident that she will take the organization and the region to even greater heights."
"We thank Jim for his service in guiding RHR these past years and since its inception," said Deborah M. DiCroce, President and CEO, Hampton Roads Community Foundation (HRCF) and RHR Executive Committee member. "We all look forward to RHR's future with Nancy's leadership and background in business, innovation, and education." HRCF was instrumental in the establishment of RHR and is a continued RHR supporter.
Under Spore's leadership, RHR assumed responsibility to administer the highly successful and innovative GO VA Program for Hampton Roads. The Virginia General Assembly created GO VA in 2016 to incentivize collaboration between two or more local jurisdictions to produce more higher paying jobs. To date, RHR has recommended and the Regional Council has approved 37 grants, totaling over $55M of investment in the regional economy when coupled with the required local match.
"Nancy Grden has championed ODU's role as a critical contributor to economic growth in Hampton Roads," says ODU President Brian O. Hemphill, PhD. "She has served as a catalyst for connecting ODU to the business community to advance the region. We are excited to continue working closely with her in our shared goal of making Hampton Roads a premier destination for investment."
At ODU, Grden was co-chair of the Economic Development Catalyst Task Force, a joint university and community initiative to identify talent and innovation pathways for the region. Grden, who holds an MBA from the University of South Carolina, a Masters in Regional Planning from the University of North Carolina at Chapel Hill, and a B.A. in Economics from Bucknell University, also directs a major strategic initiative to have ODU be globally recognized as a maritime focused institution. "We will immediately begin a search for her successor," says Hemphill, "although we know hers will be big shoes to fill."


It's been one of those weeks on the Gold Coast with a strong south easterly wind kicking in on Tuesday and showers making fishing conditions tough. We were lucky to have a good weekend and our customers made the most of the conditions scoring a feed of fish and crabs.
We have seen some good catches of Mud Crabs around the Gold Coast, a bit more rain will get them on the move over the weekend. I have been catching a few good full crabs in the Coomera River between Santa Barbara and the railway bridge. Houseboat customers have scored a feed around Tipplers Passage, Brown Island, Jacobs Well, Curtis Anchorage, Cabbage Tree Point, Cobby Passage, Tiger Mullet Channel, the Neverfail Islands and the Pimpama River. Mullet is one of my favourite baits, using a bait bag and clip stops the smaller fish and eels from wrecking your bait and increases your chances of catching for crab. Don't forget to have your Crab pot and float clearly marked or Fisheries will confiscate your gear. Crab ID kits are available from $4.90 for 5 tags.
The Rivers and calm anchorages in the Broadwater and Jumpinpin should produce a feed of Whiting, Bream and Flathead. Try fishing around the sandbanks, creek mouths and drop offs with yabbies, prawns, white pilchards, mullet fillet and worms. Some good spots out of the wind are Tiger Mullet Channel, Coomera River, Pimpama River, Woogoompah Island, Coby Passage, Logan River, Eden Island and the Nerang River.
As the temp heats back up over the weekend with the northerly wind kicking in, Mangrove Jack will be a bit more active if you're keen to brave a few showers of rain. Customers have been finding some nice fish in the North Arm of the Coomera River at Bullock head creek, Jewel creek, the rock walls at Sanctuary Cove and around the Santa Barbara boat ramp. Other good spots to try are Sovereign Island, Ephraim Island, Local Canal Pontoons and Bridges around Runaway Bay and Paradise Point, Marina Mirage and Nerang River. Best baits are live Mullet, Pike or Herring. For the lure fishermen both soft plastics and hard body's work well depending of the areas you are targeting.
At the time of writing my report the forecast for Friday will see moderate easterly winds, by Saturday winds will shift to a Northerly direction at 15 knots and much the same for Sunday. Just make sure you have the most up to date weather forecast before heading out on the water.


Labs naturally would like to minimize pre-analytical errors as much as possible, to improve the quality of blood test results, reduce the number of specimens requiring re-collection, reduce turnaround time, and improve patient management. Blood collection tubes (BCTs) typically have been regarded as inert specimen carriers; however, studies have shown that BCTs can contribute to pre-analytical errors.
We don't fully understand how these devices affect blood samples, but they may have a greater influence on test results than many health professionals realize. BCT components, including tube walls, rubber stoppers, lubricants, anticoagulants, separator gels, clot activators, and surfactants, can affect the quality of specimens and accuracy of laboratory tests. These components and additives have the potential to alter the composition of serum and plasma fractions by adding constituents to blood, adsorbing elements, interacting with protein and cellular components, or altering the stability of analytes in blood specimens.
Other than increasing vigilance when inspecting laboratory test results and improving communication between the clinical laboratory and clinicians, laboratorians can't do much to detect BCT problems readily without looking at population means (moving averages) of their assays. Following population means at regular timed intervals can alert the laboratory of potential problems with BCTs.
To evaluate interferences from collection device components in clinical tests, laboratorians should (1) test the same analyte with an alternative assay; (2) incubate the sample with the different parts of the collection device to identify the source of potential interferences; (3) contact both the collection device and assay manufacturers; (4) if necessary, file a medical device alert with the Food and Drug Administration; and (5) if possible, switch to a new BCT manufacturer.
For any new or substantially modified BCTs introduced to the laboratory, a well-planned validation protocol should be written and reviewed for scientific validity by qualified individuals. This protocol should describe the validation procedure in detail, include predefined acceptance criteria and statistical methods, and follow the policies and procedures related to testing human subjects established by the institution's review board or ethics committee. Blood specimens from both patients and apparently healthy individuals should be collected and included in the tube validation study. This study also should include the entire blood collection system (needles, holders, tubing, etc.) rather than a particular tube or component.
To determine the accuracy of assay results obtained from new or substantially modified BCTs, labs should conduct a tube comparison study similar to that described in the CLSI EP9-A (1992) guideline. Specimens that cover the reportable range for each analyte should be evaluated with an adequate number of samples. This will provide sufficient power to conduct statistical analyses of the data. Linear regression analysis or a similar type of regression method and Bland-Altman type plots should be used to analyze the tube comparison data.
To assess imprecision of assay results collected in new or substantially modified BCTs, laboratorians can compare the variability of results for the samples collected in new tubes with the variability obtained from samples collected in their current BCTs. Another means to this end is replicate testing of quality control (QC) material and/or patient specimens, as described in the CLSI EP5-A (1992) guideline. For analytes that are physiologically undetectable or those that exist in low concentrations in healthy individuals, or to generate samples that cover the reportable range, samples should be spiked with the analyte of interest when feasible. The total number of assays for tube validation studies will depend on the intended use of the blood collection device. Laboratories can select for evaluation representative assays from different testing methodologies, such as ion-specific electrode, immunoassay, and spectrophotometry. CLSI GP-34A covers validation and verification of BCTs. BCT-related problems are difficult to recognize in a timely manner because routine QC testing may not use the problematic collection devices. Likewise, proficiency testing will not detect BCT-related problems because proficiency specimens do not require collection with routinely used blood collection devices.
Comparing results for the control sera exposed and unexposed to BCTs should reveal adverse effects from tube additives, but this testing is uncommon in most clinical laboratories. It is also impractical for most because of the diversity of tubes used and frequent changes in tube lots. It may therefore be more appropriate for tube manufacturers to expose QC sera to BCTs on a lot-by-lot basis. Labs should implement a well-planned tube verification protocol whenever they change the tubes they use.
Looking into the future, emerging technologies promise greater analytical sensitivities and lower specimen volumes. Yet they may be more susceptible to analytical interference with even small amounts of interferents from BCT components, potentially altering assays and producing erroneous test results. BCT manufacturers, researchers, and laboratorians alike will need to remain diligent about these challenges and develop products like a chemically modified BCT wall to eliminate tube surfactant assay interference.
Since effective clinical decision-making depends on accurate and precise laboratory test results, BCTs should be manufactured according to an extremely high standard like other medical devices. And as medical devices, BCTs should achieve the intended performance levels during defined conditions of use. Known and foreseeable risks as well as undesirable effects should be eliminated and minimized. Tube manufacturers, in vitro diagnostic companies, and laboratorians should all remain alert in protecting against the potential adverse effects of BCTs.
For any new or substantially modified BCTs introduced to the laboratory, a well-planned validation protocol should be written and reviewed for scientific validity by qualified individuals.


A BI solution helps decision makers rely less on instinct and perception and more on data.
MAM Software to illustrate how its point of sale and parts cataloging solutions.
Electronic parts catalog now up and running in 1,500+ locations.
Business management software provider adds new customer locations.
MAM Software will be exhibiting at the 2019 APA Annual Shareholders & Manufacturers Conference!
MAM Software will be showcasing their range of aftermarket software solutions.
MAM Software to showcase VAST Online, cloud based point of sale solution.
MAM Software to showcase VAST, point of sale solution for tire dealers and auto service shops.
Brown Distributing Company of Indiana chooses Autopart as their point of sale solution.
Point of sale solution provider adds new customers locations to their already extensive user base.
PPi sees success through the use of Autopart and Autocat.
See latest releases of MAM Software's point of sale and e-commerce applications.
See latest releases of MAM Software's business management suite and e-commerce applications.
MAM Software Group to exhibit at Automechanika Frankfurt 2018.
MAM Software's cloud-based Autocat reduces the time it takes to get parts data to the counter.
Bay Area Tire & Service will be rolling out VAST to 6 locations and approximately 18-20 users.
First joint MAM Software Customer Conference for Autopart & VAST customers held on April 16-18.
Tire and automotive services provider Midtown Tire, sees over 5% revenue increase per vehicle.
Chicago Parts and Sound has chosen MAM's Autopart software for its 11 locations.
Ranshu has chosen MAM's business management solution to maintain excellent service standards.
VAST 9.0 business management software adds new functionality and improves existing features.
MAM Software congratulates Plaza Tire for winning Tire Dealer of the Year award.
AAPEX visitors will see latest release of MAM's business software and e-commerce applications.
Attendees at SEMA will get to see the latest release of VAST, MAM's business management suite.
Attendees will see the latest features added to MAM's business management suite.
MAM Software to showcase their integrated point of sale and catalog solutions.
A new partnership between MAM Software and customer management and marketing analytics company.
Poorman Automotive Warehouse has chosen to implement Autopart Online.
Events generate high levels of interest in MAM's innovative jobber and shop management solutions.
ALLDATA Manage Online users can now order parts directly from Manage Online.
MAM is an approved receiver of Motorcraft's full line product catalog.
MAM Software has announced the latest brands to join their Autocat+ web-enabled catalog.
Learn more about the advantages of new technology and receive tips on choosing the right solution.
Includes major new features, modifications and integrations.
Lee Broad appointed Chief Technology Officer for MAM Software Group, Inc.
Ransomware - could you also be at risk?
Cyber crime is on the rise - Could you be at risk?
Recent cyber-attacks stress the importance of anti-virus software and prevention measures.


How to set PATTERN LOCK on Oppo A3s .
Knitting Pattern For Baby Set, Cardigans, Mufflers, Shawls, Pullover, Hindi/English Subtitles.
How to set PATTERN LOCK on Oppo A7.
How to set PATTERN LOCK on Oppo F9 !!
SET 2019 exam Question paper pattern.
HOW TO QUALIFY SET/SLET EXAMS ? TIPS AND STRATEGY WITH PATTERN OF EXAMS.


You don't have permission to access /blog/ on this server.
So, there are other captions for images and they are concentrated as one? want confirmation is this a problem?
Have you tried to check contact.php files? are all the necessary fields filled in the file?