<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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

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

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

package k8sclient

import (
	"fmt"
	"strings"

	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	apiv1 "k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/rest"

	"github.com/golang/glog"
)

// K8sClient - Wraps all needed client functionalities for autoscaler
type K8sClient interface {
	// FetchConfigMap fetches the requested configmap from the Apiserver
	FetchConfigMap(namespace, configmap string) (*apiv1.ConfigMap, error)
	// CreateConfigMap creates a configmap with given namespace, name and params
	CreateConfigMap(namespace, configmap string, params map[string]string) (*apiv1.ConfigMap, error)
	// UpdateConfigMap updates a configmap with given namespace, name and params
	UpdateConfigMap(namespace, configmap string, params map[string]string) (*apiv1.ConfigMap, error)
	// GetClusterStatus counts schedulable nodes and cores in the cluster
	GetClusterStatus() (clusterStatus *ClusterStatus, err error)
	// GetNamespace returns the namespace of target resource.
	GetNamespace() (namespace string)
	// UpdateReplicas updates the number of replicas for the resource and return the previous replicas count
	UpdateReplicas(expReplicas int32) (prevReplicas int32, err error)
}

// k8sClient - Wraps all Kubernetes API client functionalities
type k8sClient struct {
	target        *scaleTarget
	clientset     *kubernetes.Clientset
	clusterStatus *ClusterStatus
}

// NewK8sClient gives a k8sClient with the given dependencies.
func NewK8sClient(namespace, target string) (K8sClient, error) {
	config, err := rest.InClusterConfig()
	if err != nil {
		return nil, err
	}
	// Use protobufs for communication with apiserver.
	config.ContentType = "application/vnd.kubernetes.protobuf"
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}

	scaleTarget, err := getScaleTarget(target, namespace)
	if err != nil {
		return nil, err
	}

	return &k8sClient{
		clientset: clientset,
		target:    scaleTarget,
	}, nil
}

func getScaleTarget(target, namespace string) (*scaleTarget, error) {
	splits := strings.Split(target, "/")
	if len(splits) != 2 {
		return &scaleTarget{}, fmt.Errorf("target format error: %v", target)
	}
	kind := splits[0]
	name := splits[1]
	return &scaleTarget{kind, name, namespace}, nil
}

// scaleTarget stores the scalable target recourse
type scaleTarget struct {
	kind      string
	name      string
	namespace string
}

func (k *k8sClient) GetNamespace() (namespace string) {
	return k.target.namespace
}

func (k *k8sClient) FetchConfigMap(namespace, configmap string) (*apiv1.ConfigMap, error) {
	cm, err := k.clientset.CoreV1().ConfigMaps(namespace).Get(configmap, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}
	return cm, nil
}

func (k *k8sClient) CreateConfigMap(namespace, configmap string, params map[string]string) (*apiv1.ConfigMap, error) {
	providedConfigMap := apiv1.ConfigMap{}
	providedConfigMap.ObjectMeta.Name = configmap
	providedConfigMap.ObjectMeta.Namespace = namespace
	providedConfigMap.Data = params
	cm, err := k.clientset.CoreV1().ConfigMaps(namespace).Create(&providedConfigMap)
	if err != nil {
		return nil, err
	}
	glog.V(0).Infof("Created ConfigMap %v in namespace %v", configmap, namespace)
	return cm, nil
}

func (k *k8sClient) UpdateConfigMap(namespace, configmap string, params map[string]string) (*apiv1.ConfigMap, error) {
	providedConfigMap := apiv1.ConfigMap{}
	providedConfigMap.ObjectMeta.Name = configmap
	providedConfigMap.ObjectMeta.Namespace = namespace
	providedConfigMap.Data = params
	cm, err := k.clientset.CoreV1().ConfigMaps(namespace).Update(&providedConfigMap)
	if err != nil {
		return nil, err
	}
	glog.V(0).Infof("Updated ConfigMap %v in namespace %v", configmap, namespace)
	return cm, nil
}

// ClusterStatus defines the cluster status
type ClusterStatus struct {
	TotalNodes       int32
	SchedulableNodes int32
	TotalCores       int32
	SchedulableCores int32
}

func (k *k8sClient) GetClusterStatus() (clusterStatus *ClusterStatus, err error) {
	opt := metav1.ListOptions{Watch: false}

	nodes, err := k.clientset.CoreV1().Nodes().List(opt)
	if err != nil || nodes == nil {
		return nil, err
	}
	clusterStatus = &ClusterStatus{}
	clusterStatus.TotalNodes = int32(len(nodes.Items))
	var tc resource.Quantity
	var sc resource.Quantity
	for _, node := range nodes.Items {
		tc.Add(node.Status.Capacity[apiv1.ResourceCPU])
		if !node.Spec.Unschedulable {
			clusterStatus.SchedulableNodes++
			sc.Add(node.Status.Capacity[apiv1.ResourceCPU])
		}
	}

	tcInt64, tcOk := tc.AsInt64()
	scInt64, scOk := sc.AsInt64()
	if !tcOk || !scOk {
		return nil, fmt.Errorf("unable to compute integer values of schedulable cores in the cluster")
	}
	clusterStatus.TotalCores = int32(tcInt64)
	clusterStatus.SchedulableCores = int32(scInt64)
	k.clusterStatus = clusterStatus
	return clusterStatus, nil
}

func (k *k8sClient) UpdateReplicas(expReplicas int32) (prevRelicas int32, err error) {
	scale, err := k.clientset.Extensions().Scales(k.target.namespace).Get(k.target.kind, k.target.name)
	if err != nil {
		return 0, err
	}
	prevRelicas = scale.Spec.Replicas
	if expReplicas != prevRelicas {
		glog.V(0).Infof("Cluster status: SchedulableNodes[%v], SchedulableCores[%v]", k.clusterStatus.SchedulableNodes, k.clusterStatus.SchedulableCores)
		glog.V(0).Infof("Replicas are not as expected : updating replicas from %d to %d", prevRelicas, expReplicas)
		scale.Spec.Replicas = expReplicas
		_, err = k.clientset.Extensions().Scales(k.target.namespace).Update(k.target.kind, scale)
		if err != nil {
			return 0, err
		}
	}
	return prevRelicas, nil
}
<commit_msg>Watch nodes instead of periodic polls<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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

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

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

package k8sclient

import (
	"fmt"
	"strings"
	"time"

	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/kubernetes"
	apiv1 "k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/cache"

	"github.com/golang/glog"
)

// K8sClient - Wraps all needed client functionalities for autoscaler
type K8sClient interface {
	// FetchConfigMap fetches the requested configmap from the Apiserver
	FetchConfigMap(namespace, configmap string) (*apiv1.ConfigMap, error)
	// CreateConfigMap creates a configmap with given namespace, name and params
	CreateConfigMap(namespace, configmap string, params map[string]string) (*apiv1.ConfigMap, error)
	// UpdateConfigMap updates a configmap with given namespace, name and params
	UpdateConfigMap(namespace, configmap string, params map[string]string) (*apiv1.ConfigMap, error)
	// GetClusterStatus counts schedulable nodes and cores in the cluster
	GetClusterStatus() (clusterStatus *ClusterStatus, err error)
	// GetNamespace returns the namespace of target resource.
	GetNamespace() (namespace string)
	// UpdateReplicas updates the number of replicas for the resource and return the previous replicas count
	UpdateReplicas(expReplicas int32) (prevReplicas int32, err error)
}

// k8sClient - Wraps all Kubernetes API client functionalities
type k8sClient struct {
	target        *scaleTarget
	clientset     *kubernetes.Clientset
	clusterStatus *ClusterStatus
	nodeStore     cache.Store
	reflector     *cache.Reflector
}

// NewK8sClient gives a k8sClient with the given dependencies.
func NewK8sClient(namespace, target string) (K8sClient, error) {
	config, err := rest.InClusterConfig()
	if err != nil {
		return nil, err
	}
	// Use protobufs for communication with apiserver.
	config.ContentType = "application/vnd.kubernetes.protobuf"
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}

	scaleTarget, err := getScaleTarget(target, namespace)
	if err != nil {
		return nil, err
	}

	// Start propagating contents of the nodeStore.
	nodeListWatch := &cache.ListWatch{
		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
			return clientset.Core().Nodes().List(options)
		},
		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
			return clientset.Core().Nodes().Watch(options)
		},
	}
	nodeStore := cache.NewStore(cache.MetaNamespaceKeyFunc)
	reflector := cache.NewReflector(nodeListWatch, &apiv1.Node{}, nodeStore, 0)
	reflector.Run()

	return &k8sClient{
		target:    scaleTarget,
		clientset: clientset,
		nodeStore: nodeStore,
		reflector: reflector,
	}, nil
}

func getScaleTarget(target, namespace string) (*scaleTarget, error) {
	splits := strings.Split(target, "/")
	if len(splits) != 2 {
		return &scaleTarget{}, fmt.Errorf("target format error: %v", target)
	}
	kind := splits[0]
	name := splits[1]
	return &scaleTarget{kind, name, namespace}, nil
}

// scaleTarget stores the scalable target recourse
type scaleTarget struct {
	kind      string
	name      string
	namespace string
}

func (k *k8sClient) GetNamespace() (namespace string) {
	return k.target.namespace
}

func (k *k8sClient) FetchConfigMap(namespace, configmap string) (*apiv1.ConfigMap, error) {
	cm, err := k.clientset.CoreV1().ConfigMaps(namespace).Get(configmap, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}
	return cm, nil
}

func (k *k8sClient) CreateConfigMap(namespace, configmap string, params map[string]string) (*apiv1.ConfigMap, error) {
	providedConfigMap := apiv1.ConfigMap{}
	providedConfigMap.ObjectMeta.Name = configmap
	providedConfigMap.ObjectMeta.Namespace = namespace
	providedConfigMap.Data = params
	cm, err := k.clientset.CoreV1().ConfigMaps(namespace).Create(&providedConfigMap)
	if err != nil {
		return nil, err
	}
	glog.V(0).Infof("Created ConfigMap %v in namespace %v", configmap, namespace)
	return cm, nil
}

func (k *k8sClient) UpdateConfigMap(namespace, configmap string, params map[string]string) (*apiv1.ConfigMap, error) {
	providedConfigMap := apiv1.ConfigMap{}
	providedConfigMap.ObjectMeta.Name = configmap
	providedConfigMap.ObjectMeta.Namespace = namespace
	providedConfigMap.Data = params
	cm, err := k.clientset.CoreV1().ConfigMaps(namespace).Update(&providedConfigMap)
	if err != nil {
		return nil, err
	}
	glog.V(0).Infof("Updated ConfigMap %v in namespace %v", configmap, namespace)
	return cm, nil
}

// ClusterStatus defines the cluster status
type ClusterStatus struct {
	TotalNodes       int32
	SchedulableNodes int32
	TotalCores       int32
	SchedulableCores int32
}

func (k *k8sClient) GetClusterStatus() (clusterStatus *ClusterStatus, err error) {
	// TODO: Consider moving this to NewK8sClient method and failing fast when
	// reflector can't initialize. That is a tradeoff between silently non-working
	// component and explicit restarts of it. In majority of the cases the restart
	// won't repair it - though it may give better visibility into problems.
	err = wait.PollImmediate(250*time.Millisecond, 5*time.Second, func() (bool, error) {
		if k.reflector.LastSyncResourceVersion() == "" {
			return false, nil
		}
		return true, nil
	})
	if err != nil {
		return nil, err
	}
	nodes := k.nodeStore.List()

	clusterStatus = &ClusterStatus{}
	clusterStatus.TotalNodes = int32(len(nodes))
	var tc resource.Quantity
	var sc resource.Quantity
	for i := range nodes {
		node, ok := nodes[i].(*apiv1.Node)
		if !ok {
			glog.Errorf("Unexpected object: %#v", nodes[i])
			continue
		}
		tc.Add(node.Status.Capacity[apiv1.ResourceCPU])
		if !node.Spec.Unschedulable {
			clusterStatus.SchedulableNodes++
			sc.Add(node.Status.Capacity[apiv1.ResourceCPU])
		}
	}

	tcInt64, tcOk := tc.AsInt64()
	scInt64, scOk := sc.AsInt64()
	if !tcOk || !scOk {
		return nil, fmt.Errorf("unable to compute integer values of schedulable cores in the cluster")
	}
	clusterStatus.TotalCores = int32(tcInt64)
	clusterStatus.SchedulableCores = int32(scInt64)
	k.clusterStatus = clusterStatus
	return clusterStatus, nil
}

func (k *k8sClient) UpdateReplicas(expReplicas int32) (prevRelicas int32, err error) {
	scale, err := k.clientset.Extensions().Scales(k.target.namespace).Get(k.target.kind, k.target.name)
	if err != nil {
		return 0, err
	}
	prevRelicas = scale.Spec.Replicas
	if expReplicas != prevRelicas {
		glog.V(0).Infof("Cluster status: SchedulableNodes[%v], SchedulableCores[%v]", k.clusterStatus.SchedulableNodes, k.clusterStatus.SchedulableCores)
		glog.V(0).Infof("Replicas are not as expected : updating replicas from %d to %d", prevRelicas, expReplicas)
		scale.Spec.Replicas = expReplicas
		_, err = k.clientset.Extensions().Scales(k.target.namespace).Update(k.target.kind, scale)
		if err != nil {
			return 0, err
		}
	}
	return prevRelicas, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2011 Google Inc.

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

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

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

/*
Package localdisk registers the "filesystem" blobserver storage type,
storing blobs in a forest of sharded directories at the specified root.

Example low-level config:

     "/storage/": {
         "handler": "storage-filesystem",
         "handlerArgs": {
            "path": "/var/camlistore/blobs"
          }
     },

*/
package localdisk

import (
	"fmt"
	"io"
	"os"
	"sync"

	"camlistore.org/pkg/blob"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/blobserver/local"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/types"
)

// DiskStorage implements the blobserver.Storage interface using the
// local filesystem.
type DiskStorage struct {
	root string

	// dirLockMu must be held for writing when deleting an empty directory
	// and for read when receiving blobs.
	dirLockMu *sync.RWMutex

	// gen will be nil if partition != ""
	gen *local.Generationer
}

// New returns a new local disk storage implementation at the provided
// root directory, which must already exist.
func New(root string) (*DiskStorage, error) {
	// Local disk.
	fi, err := os.Stat(root)
	if os.IsNotExist(err) {
		return nil, fmt.Errorf("Storage root %q doesn't exist", root)
	}
	if err != nil {
		return nil, fmt.Errorf("Failed to stat directory %q: %v", root, err)
	}
	if !fi.IsDir() {
		return nil, fmt.Errorf("Storage root %q exists but is not a directory.", root)
	}
	ds := &DiskStorage{
		root:      root,
		dirLockMu: new(sync.RWMutex),
		gen:       local.NewGenerationer(root),
	}
	if err := ds.migrate3to2(); err != nil {
		return nil, fmt.Errorf("Error updating localdisk format: %v", err)
	}
	if _, _, err := ds.StorageGeneration(); err != nil {
		return nil, fmt.Errorf("Error initialization generation for %q: %v", root, err)
	}
	return ds, nil
}

func newFromConfig(_ blobserver.Loader, config jsonconfig.Obj) (storage blobserver.Storage, err error) {
	path := config.RequiredString("path")
	if err := config.Validate(); err != nil {
		return nil, err
	}
	return New(path)
}

func init() {
	blobserver.RegisterStorageConstructor("filesystem", blobserver.StorageConstructor(newFromConfig))
}

func (ds *DiskStorage) tryRemoveDir(dir string) {
	ds.dirLockMu.Lock()
	defer ds.dirLockMu.Unlock()
	os.Remove(dir) // ignore error
}

func (ds *DiskStorage) FetchStreaming(blob blob.Ref) (io.ReadCloser, int64, error) {
	return ds.Fetch(blob)
}

func (ds *DiskStorage) Fetch(blob blob.Ref) (types.ReadSeekCloser, int64, error) {
	fileName := ds.blobPath(blob)
	stat, err := os.Stat(fileName)
	if os.IsNotExist(err) {
		return nil, 0, os.ErrNotExist
	}
	file, err := os.Open(fileName)
	if err != nil {
		if os.IsNotExist(err) {
			err = os.ErrNotExist
		}
		return nil, 0, err
	}
	return file, stat.Size(), nil
}

func (ds *DiskStorage) RemoveBlobs(blobs []blob.Ref) error {
	for _, blob := range blobs {
		fileName := ds.blobPath(blob)
		err := os.Remove(fileName)
		switch {
		case err == nil:
			continue
		case os.IsNotExist(err):
			// deleting already-deleted file; harmless.
			continue
		default:
			return err
		}
	}
	return nil
}
<commit_msg>localdisk: add IsDir<commit_after>/*
Copyright 2011 Google Inc.

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

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

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

/*
Package localdisk registers the "filesystem" blobserver storage type,
storing blobs in a forest of sharded directories at the specified root.

Example low-level config:

     "/storage/": {
         "handler": "storage-filesystem",
         "handlerArgs": {
            "path": "/var/camlistore/blobs"
          }
     },

*/
package localdisk

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sync"

	"camlistore.org/pkg/blob"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/blobserver/local"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/osutil"
	"camlistore.org/pkg/types"
)

// DiskStorage implements the blobserver.Storage interface using the
// local filesystem.
type DiskStorage struct {
	root string

	// dirLockMu must be held for writing when deleting an empty directory
	// and for read when receiving blobs.
	dirLockMu *sync.RWMutex

	// gen will be nil if partition != ""
	gen *local.Generationer
}

// IsDir reports whether root is a localdisk (file-per-blob) storage directory.
func IsDir(root string) (bool, error) {
	if osutil.DirExists(filepath.Join(root, blob.RefFromString("").HashName())) {
		return true, nil
	}
	return false, nil
}

// New returns a new local disk storage implementation at the provided
// root directory, which must already exist.
func New(root string) (*DiskStorage, error) {
	// Local disk.
	fi, err := os.Stat(root)
	if os.IsNotExist(err) {
		return nil, fmt.Errorf("Storage root %q doesn't exist", root)
	}
	if err != nil {
		return nil, fmt.Errorf("Failed to stat directory %q: %v", root, err)
	}
	if !fi.IsDir() {
		return nil, fmt.Errorf("Storage root %q exists but is not a directory.", root)
	}
	ds := &DiskStorage{
		root:      root,
		dirLockMu: new(sync.RWMutex),
		gen:       local.NewGenerationer(root),
	}
	if err := ds.migrate3to2(); err != nil {
		return nil, fmt.Errorf("Error updating localdisk format: %v", err)
	}
	if _, _, err := ds.StorageGeneration(); err != nil {
		return nil, fmt.Errorf("Error initialization generation for %q: %v", root, err)
	}
	return ds, nil
}

func newFromConfig(_ blobserver.Loader, config jsonconfig.Obj) (storage blobserver.Storage, err error) {
	path := config.RequiredString("path")
	if err := config.Validate(); err != nil {
		return nil, err
	}
	return New(path)
}

func init() {
	blobserver.RegisterStorageConstructor("filesystem", blobserver.StorageConstructor(newFromConfig))
}

func (ds *DiskStorage) tryRemoveDir(dir string) {
	ds.dirLockMu.Lock()
	defer ds.dirLockMu.Unlock()
	os.Remove(dir) // ignore error
}

func (ds *DiskStorage) FetchStreaming(blob blob.Ref) (io.ReadCloser, int64, error) {
	return ds.Fetch(blob)
}

func (ds *DiskStorage) Fetch(blob blob.Ref) (types.ReadSeekCloser, int64, error) {
	fileName := ds.blobPath(blob)
	stat, err := os.Stat(fileName)
	if os.IsNotExist(err) {
		return nil, 0, os.ErrNotExist
	}
	file, err := os.Open(fileName)
	if err != nil {
		if os.IsNotExist(err) {
			err = os.ErrNotExist
		}
		return nil, 0, err
	}
	return file, stat.Size(), nil
}

func (ds *DiskStorage) RemoveBlobs(blobs []blob.Ref) error {
	for _, blob := range blobs {
		fileName := ds.blobPath(blob)
		err := os.Remove(fileName)
		switch {
		case err == nil:
			continue
		case os.IsNotExist(err):
			// deleting already-deleted file; harmless.
			continue
		default:
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package dns

import (
	"context"

	"github.com/datawire/dlib/dexec"
)

func Flush(c context.Context) {
	// As of macOS 11 (Big Sur), how to flush the DNS cache hasn't changed since 10.10.4 (a Yosemite version release in mid 2015),
	// other than that in 10.12 (Sierra) it became necessary to also kill mDNSResponderHelper. On older versions the call to kill
	// mDNSResponderHelper is unnecessary but harmless, as the process doesn't exist.
	_ = dexec.CommandContext(c, "killall", "-HUP", "mDNSResponder").Run()
	_ = dexec.CommandContext(c, "killall", "mDNSResponderHelper").Run()
	_ = dexec.CommandContext(c, "dscacheutil", "-flushcache").Run()
}
<commit_msg>Ensure that msdDSNResponderHelper isn't killed on Big Sur.<commit_after>package dns

import (
	"context"
	"os"
	"os/exec" //nolint:depguard // don't want context or logging
	"regexp"
	"strconv"
	"strings"

	"github.com/datawire/dlib/dlog"
)

var devNullReader *os.File
var devNullWriter *os.File
var majorProductVersion int

func init() {
	var err error
	devNullReader, err = os.Open(os.DevNull)
	if err != nil {
		panic(err)
	}
	devNullWriter, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
	if err != nil {
		panic(err)
	}
	if swOut, err := exec.Command("sw_vers", "-productVersion").Output(); err == nil {
		if m := regexp.MustCompile(`^(\d+)\.`).FindStringSubmatch(strings.TrimSpace(string(swOut))); m != nil {
			majorProductVersion, _ = strconv.Atoi(m[1])
		}
	}
}

func devNullCmd(c context.Context, executable string, args ...string) {
	// Run the command without having the OS bother about opening or closing file descriptors or pipes
	cmd := exec.CommandContext(c, executable, args...)
	cmd.Stdin = devNullReader
	cmd.Stdout = devNullWriter
	cmd.Stderr = devNullWriter
	_ = cmd.Run()
}

func Flush(c context.Context) {
	// As of macOS 11 (Big Sur), how to flush the DNS cache hasn't changed since 10.10.4 (a Yosemite version release in mid 2015),
	// other than that in 10.12 (Sierra) it became necessary to also kill mDNSResponderHelper. On older versions the call to kill
	// mDNSResponderHelper is unnecessary but harmless, as the process doesn't exist.
	dlog.Debug(c, "Flushing DNS")
	if majorProductVersion >= 11 {
		// Big Sur, a killall -HUP mDNSResponder doesn't restart the mDNSResponderHelper but killall -TERM of both, does`
		dlog.Debug(c, "Flushing DNS")
		devNullCmd(c, "killall", "-HUP", "mDNSResponder")
	} else {
		dlog.Debug(c, "Flushing DNS on version < Big Sur")
		devNullCmd(c, "killall", "mDNSResponderHelper")
		devNullCmd(c, "killall", "-HUP", "mDNSResponder", "mDNSResponderHelper")
	}
	devNullCmd(c, "dscacheutil", "-flushcache")
}
<|endoftext|>


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

// Per-container manager.

package manager

import (
	"fmt"
	"reflect"
	"sync"
	"testing"
	"time"

	"github.com/google/cadvisor/cache/memory"
	"github.com/google/cadvisor/collector"
	"github.com/google/cadvisor/container"
	containertest "github.com/google/cadvisor/container/testing"
	info "github.com/google/cadvisor/info/v1"
	itest "github.com/google/cadvisor/info/v1/test"

	"github.com/google/cadvisor/accelerators"
	"github.com/mindprince/gonvml"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	clock "k8s.io/utils/clock/testing"
)

const (
	containerName        = "/container"
	testLongHousekeeping = time.Second
)

// Create a containerData instance for a test.
func setupContainerData(t *testing.T, spec info.ContainerSpec) (*containerData, *containertest.MockContainerHandler, *memory.InMemoryCache, *clock.FakeClock) {
	mockHandler := containertest.NewMockContainerHandler(containerName)
	mockHandler.On("GetSpec").Return(
		spec,
		nil,
	)
	memoryCache := memory.New(60, nil)
	fakeClock := clock.NewFakeClock(time.Now())
	ret, err := newContainerData(containerName, memoryCache, mockHandler, false, &collector.GenericCollectorManager{}, 60*time.Second, true, fakeClock)
	if err != nil {
		t.Fatal(err)
	}
	return ret, mockHandler, memoryCache, fakeClock
}

// Create a containerData instance for a test and add a default GetSpec mock.
func newTestContainerData(t *testing.T) (*containerData, *containertest.MockContainerHandler, *memory.InMemoryCache, *clock.FakeClock) {
	return setupContainerData(t, itest.GenerateRandomContainerSpec(4))
}

func TestUpdateSubcontainers(t *testing.T) {
	subcontainers := []info.ContainerReference{
		{Name: "/container/ee0103"},
		{Name: "/container/abcd"},
		{Name: "/container/something"},
	}
	cd, mockHandler, _, _ := newTestContainerData(t)
	mockHandler.On("ListContainers", container.ListSelf).Return(
		subcontainers,
		nil,
	)

	err := cd.updateSubcontainers()
	if err != nil {
		t.Fatal(err)
	}

	if len(cd.info.Subcontainers) != len(subcontainers) {
		t.Errorf("Received %v subcontainers, should be %v", len(cd.info.Subcontainers), len(subcontainers))
	}

	for _, sub := range cd.info.Subcontainers {
		found := false
		for _, sub2 := range subcontainers {
			if sub.Name == sub2.Name {
				found = true
			}
		}
		if !found {
			t.Errorf("Received unknown sub container %v", sub)
		}
	}

	mockHandler.AssertExpectations(t)
}

func TestUpdateSubcontainersWithError(t *testing.T) {
	cd, mockHandler, _, _ := newTestContainerData(t)
	mockHandler.On("ListContainers", container.ListSelf).Return(
		[]info.ContainerReference{},
		fmt.Errorf("some error"),
	)
	mockHandler.On("Exists").Return(true)

	assert.NotNil(t, cd.updateSubcontainers())
	assert.Empty(t, cd.info.Subcontainers, "subcontainers should not be populated on failure")
	mockHandler.AssertExpectations(t)
}

func TestUpdateSubcontainersWithErrorOnDeadContainer(t *testing.T) {
	cd, mockHandler, _, _ := newTestContainerData(t)
	mockHandler.On("ListContainers", container.ListSelf).Return(
		[]info.ContainerReference{},
		fmt.Errorf("some error"),
	)
	mockHandler.On("Exists").Return(false)

	assert.Nil(t, cd.updateSubcontainers())
	mockHandler.AssertExpectations(t)
}

func checkNumStats(t *testing.T, memoryCache *memory.InMemoryCache, numStats int) {
	var empty time.Time
	stats, err := memoryCache.RecentStats(containerName, empty, empty, -1)
	require.Nil(t, err)
	assert.Len(t, stats, numStats)
}

func TestUpdateStats(t *testing.T) {
	statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
	stats := statsList[0]

	cd, mockHandler, memoryCache, _ := newTestContainerData(t)
	mockHandler.On("GetStats").Return(
		stats,
		nil,
	)

	err := cd.updateStats()
	if err != nil {
		t.Fatal(err)
	}

	checkNumStats(t, memoryCache, 1)
	mockHandler.AssertExpectations(t)
}

func TestUpdateSpec(t *testing.T) {
	spec := itest.GenerateRandomContainerSpec(4)
	cd, mockHandler, _, _ := newTestContainerData(t)
	mockHandler.On("GetSpec").Return(
		spec,
		nil,
	)

	err := cd.updateSpec()
	if err != nil {
		t.Fatal(err)
	}

	mockHandler.AssertExpectations(t)
}

func TestGetInfo(t *testing.T) {
	spec := itest.GenerateRandomContainerSpec(4)
	subcontainers := []info.ContainerReference{
		{Name: "/container/ee0103"},
		{Name: "/container/abcd"},
		{Name: "/container/something"},
	}
	cd, mockHandler, _, _ := setupContainerData(t, spec)
	mockHandler.On("ListContainers", container.ListSelf).Return(
		subcontainers,
		nil,
	)
	mockHandler.Aliases = []string{"a1", "a2"}

	info, err := cd.GetInfo(true)
	if err != nil {
		t.Fatal(err)
	}

	mockHandler.AssertExpectations(t)

	if len(info.Subcontainers) != len(subcontainers) {
		t.Errorf("Received %v subcontainers, should be %v", len(info.Subcontainers), len(subcontainers))
	}

	for _, sub := range info.Subcontainers {
		found := false
		for _, sub2 := range subcontainers {
			if sub.Name == sub2.Name {
				found = true
			}
		}
		if !found {
			t.Errorf("Received unknown sub container %v", sub)
		}
	}

	if !reflect.DeepEqual(spec, info.Spec) {
		t.Errorf("received wrong container spec")
	}

	if info.Name != mockHandler.Name {
		t.Errorf("received wrong container name: received %v; should be %v", info.Name, mockHandler.Name)
	}
}

func TestUpdateNvidiaStats(t *testing.T) {
	cd, _, _, _ := newTestContainerData(t)
	stats := info.ContainerStats{}

	// When there are no devices, we should not get an error and stats should not change.
	cd.nvidiaCollector = accelerators.NewNvidiaCollector([]gonvml.Device{})
	err := cd.nvidiaCollector.UpdateStats(&stats)
	assert.Nil(t, err)
	assert.Equal(t, info.ContainerStats{}, stats)

	// This is an impossible situation (there are devices but nvml is not initialized).
	// Here I am testing that the CGo gonvml library doesn't panic when passed bad
	// input and instead returns an error.
	cd.nvidiaCollector = accelerators.NewNvidiaCollector([]gonvml.Device{{}, {}})
	err = cd.nvidiaCollector.UpdateStats(&stats)
	assert.NotNil(t, err)
	assert.Equal(t, info.ContainerStats{}, stats)
}

func TestOnDemandHousekeeping(t *testing.T) {
	statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
	stats := statsList[0]

	cd, mockHandler, memoryCache, fakeClock := newTestContainerData(t)
	mockHandler.On("GetStats").Return(stats, nil)
	defer func() {
		err := cd.Stop()
		assert.NoError(t, err)
	}()

	// 0 seconds should always trigger an update
	go cd.OnDemandHousekeeping(0 * time.Second)
	cd.housekeepingTick(fakeClock.NewTimer(time.Minute).C(), testLongHousekeeping)

	fakeClock.Step(2 * time.Second)

	// This should return without requiring a housekeepingTick because stats have been updated recently enough
	cd.OnDemandHousekeeping(3 * time.Second)

	go cd.OnDemandHousekeeping(1 * time.Second)
	cd.housekeepingTick(fakeClock.NewTimer(time.Minute).C(), testLongHousekeeping)

	checkNumStats(t, memoryCache, 2)
	mockHandler.AssertExpectations(t)
}

func TestConcurrentOnDemandHousekeeping(t *testing.T) {
	statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
	stats := statsList[0]

	cd, mockHandler, memoryCache, fakeClock := newTestContainerData(t)
	mockHandler.On("GetStats").Return(stats, nil)
	defer func() {
		err := cd.Stop()
		assert.NoError(t, err)
	}()

	numConcurrentCalls := 5
	var waitForHousekeeping sync.WaitGroup
	waitForHousekeeping.Add(numConcurrentCalls)
	onDemandCache := []chan struct{}{}
	for i := 0; i < numConcurrentCalls; i++ {
		go func() {
			cd.OnDemandHousekeeping(0 * time.Second)
			waitForHousekeeping.Done()
		}()
		// Wait for work to be queued
		onDemandCache = append(onDemandCache, <-cd.onDemandChan)
	}
	// Requeue work:
	for _, ch := range onDemandCache {
		cd.onDemandChan <- ch
	}

	go cd.housekeepingTick(fakeClock.NewTimer(time.Minute).C(), testLongHousekeeping)
	// Ensure that all queued calls return with only a single call to housekeepingTick
	waitForHousekeeping.Wait()

	checkNumStats(t, memoryCache, 1)
	mockHandler.AssertExpectations(t)
}

func TestOnDemandHousekeepingReturnsAfterStopped(t *testing.T) {
	statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
	stats := statsList[0]

	cd, mockHandler, memoryCache, fakeClock := newTestContainerData(t)
	mockHandler.On("GetStats").Return(stats, nil)

	// trigger housekeeping update
	go cd.OnDemandHousekeeping(0 * time.Second)
	cd.housekeepingTick(fakeClock.NewTimer(time.Minute).C(), testLongHousekeeping)

	checkNumStats(t, memoryCache, 1)

	fakeClock.Step(2 * time.Second)

	err := cd.Stop()
	assert.NoError(t, err)
	// housekeeping tick should detect stop and not store any more metrics
	assert.False(t, cd.housekeepingTick(fakeClock.NewTimer(time.Minute).C(), testLongHousekeeping))
	fakeClock.Step(1 * time.Second)
	// on demand housekeeping should not block and return
	cd.OnDemandHousekeeping(-1 * time.Second)

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

// Per-container manager.

package manager

import (
	"fmt"
	"reflect"
	"sync"
	"testing"
	"time"

	"github.com/google/cadvisor/cache/memory"
	"github.com/google/cadvisor/collector"
	"github.com/google/cadvisor/container"
	containertest "github.com/google/cadvisor/container/testing"
	info "github.com/google/cadvisor/info/v1"
	itest "github.com/google/cadvisor/info/v1/test"

	"github.com/google/cadvisor/accelerators"
	"github.com/mindprince/gonvml"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	clock "k8s.io/utils/clock/testing"
)

const (
	containerName        = "/container"
	testLongHousekeeping = time.Second
)

// Create a containerData instance for a test.
func setupContainerData(t *testing.T, spec info.ContainerSpec) (*containerData, *containertest.MockContainerHandler, *memory.InMemoryCache, *clock.FakeClock) {
	mockHandler := containertest.NewMockContainerHandler(containerName)
	mockHandler.On("GetSpec").Return(
		spec,
		nil,
	)
	memoryCache := memory.New(60, nil)
	fakeClock := clock.NewFakeClock(time.Now())
	ret, err := newContainerData(containerName, memoryCache, mockHandler, false, &collector.GenericCollectorManager{}, 60*time.Second, true, fakeClock)
	if err != nil {
		t.Fatal(err)
	}
	return ret, mockHandler, memoryCache, fakeClock
}

// Create a containerData instance for a test and add a default GetSpec mock.
func newTestContainerData(t *testing.T) (*containerData, *containertest.MockContainerHandler, *memory.InMemoryCache, *clock.FakeClock) {
	return setupContainerData(t, itest.GenerateRandomContainerSpec(4))
}

func TestUpdateSubcontainers(t *testing.T) {
	subcontainers := []info.ContainerReference{
		{Name: "/container/ee0103"},
		{Name: "/container/abcd"},
		{Name: "/container/something"},
	}
	cd, mockHandler, _, _ := newTestContainerData(t)
	mockHandler.On("ListContainers", container.ListSelf).Return(
		subcontainers,
		nil,
	)

	err := cd.updateSubcontainers()
	if err != nil {
		t.Fatal(err)
	}

	if len(cd.info.Subcontainers) != len(subcontainers) {
		t.Errorf("Received %v subcontainers, should be %v", len(cd.info.Subcontainers), len(subcontainers))
	}

	for _, sub := range cd.info.Subcontainers {
		found := false
		for _, sub2 := range subcontainers {
			if sub.Name == sub2.Name {
				found = true
			}
		}
		if !found {
			t.Errorf("Received unknown sub container %v", sub)
		}
	}

	mockHandler.AssertExpectations(t)
}

func TestUpdateSubcontainersWithError(t *testing.T) {
	cd, mockHandler, _, _ := newTestContainerData(t)
	mockHandler.On("ListContainers", container.ListSelf).Return(
		[]info.ContainerReference{},
		fmt.Errorf("some error"),
	)
	mockHandler.On("Exists").Return(true)

	assert.NotNil(t, cd.updateSubcontainers())
	assert.Empty(t, cd.info.Subcontainers, "subcontainers should not be populated on failure")
	mockHandler.AssertExpectations(t)
}

func TestUpdateSubcontainersWithErrorOnDeadContainer(t *testing.T) {
	cd, mockHandler, _, _ := newTestContainerData(t)
	mockHandler.On("ListContainers", container.ListSelf).Return(
		[]info.ContainerReference{},
		fmt.Errorf("some error"),
	)
	mockHandler.On("Exists").Return(false)

	assert.Nil(t, cd.updateSubcontainers())
	mockHandler.AssertExpectations(t)
}

func checkNumStats(t *testing.T, memoryCache *memory.InMemoryCache, numStats int) {
	var empty time.Time
	stats, err := memoryCache.RecentStats(containerName, empty, empty, -1)
	require.Nil(t, err)
	assert.Len(t, stats, numStats)
}

func TestUpdateStats(t *testing.T) {
	statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
	stats := statsList[0]

	cd, mockHandler, memoryCache, _ := newTestContainerData(t)
	mockHandler.On("GetStats").Return(
		stats,
		nil,
	)

	err := cd.updateStats()
	if err != nil {
		t.Fatal(err)
	}

	checkNumStats(t, memoryCache, 1)
	mockHandler.AssertExpectations(t)
}

func TestUpdateSpec(t *testing.T) {
	spec := itest.GenerateRandomContainerSpec(4)
	cd, mockHandler, _, _ := newTestContainerData(t)
	mockHandler.On("GetSpec").Return(
		spec,
		nil,
	)

	err := cd.updateSpec()
	if err != nil {
		t.Fatal(err)
	}

	mockHandler.AssertExpectations(t)
}

func TestGetInfo(t *testing.T) {
	spec := itest.GenerateRandomContainerSpec(4)
	subcontainers := []info.ContainerReference{
		{Name: "/container/ee0103"},
		{Name: "/container/abcd"},
		{Name: "/container/something"},
	}
	cd, mockHandler, _, _ := setupContainerData(t, spec)
	mockHandler.On("ListContainers", container.ListSelf).Return(
		subcontainers,
		nil,
	)
	mockHandler.Aliases = []string{"a1", "a2"}

	info, err := cd.GetInfo(true)
	if err != nil {
		t.Fatal(err)
	}

	mockHandler.AssertExpectations(t)

	if len(info.Subcontainers) != len(subcontainers) {
		t.Errorf("Received %v subcontainers, should be %v", len(info.Subcontainers), len(subcontainers))
	}

	for _, sub := range info.Subcontainers {
		found := false
		for _, sub2 := range subcontainers {
			if sub.Name == sub2.Name {
				found = true
			}
		}
		if !found {
			t.Errorf("Received unknown sub container %v", sub)
		}
	}

	if !reflect.DeepEqual(spec, info.Spec) {
		t.Errorf("received wrong container spec")
	}

	if info.Name != mockHandler.Name {
		t.Errorf("received wrong container name: received %v; should be %v", info.Name, mockHandler.Name)
	}
}

func TestUpdateNvidiaStats(t *testing.T) {
	cd, _, _, _ := newTestContainerData(t)
	stats := info.ContainerStats{}

	// When there are no devices, we should not get an error and stats should not change.
	cd.nvidiaCollector = accelerators.NewNvidiaCollector([]gonvml.Device{})
	err := cd.nvidiaCollector.UpdateStats(&stats)
	assert.Nil(t, err)
	assert.Equal(t, info.ContainerStats{}, stats)

	// This is an impossible situation (there are devices but nvml is not initialized).
	// Here I am testing that the CGo gonvml library doesn't panic when passed bad
	// input and instead returns an error.
	cd.nvidiaCollector = accelerators.NewNvidiaCollector([]gonvml.Device{{}, {}})
	err = cd.nvidiaCollector.UpdateStats(&stats)
	assert.NotNil(t, err)
	assert.Equal(t, info.ContainerStats{}, stats)
}

func TestOnDemandHousekeeping(t *testing.T) {
	statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
	stats := statsList[0]

	cd, mockHandler, memoryCache, fakeClock := newTestContainerData(t)
	mockHandler.On("GetStats").Return(stats, nil)
	defer func() {
		err := cd.Stop()
		assert.NoError(t, err)
	}()

	// 0 seconds should always trigger an update
	go cd.OnDemandHousekeeping(0 * time.Second)
	cd.housekeepingTick(fakeClock.NewTimer(time.Minute).C(), testLongHousekeeping)

	fakeClock.Step(2 * time.Second)

	// This should return without requiring a housekeepingTick because stats have been updated recently enough
	cd.OnDemandHousekeeping(3 * time.Second)

	go cd.OnDemandHousekeeping(1 * time.Second)
	cd.housekeepingTick(fakeClock.NewTimer(time.Minute).C(), testLongHousekeeping)

	checkNumStats(t, memoryCache, 2)
	mockHandler.AssertExpectations(t)
}

func TestConcurrentOnDemandHousekeeping(t *testing.T) {
	statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
	stats := statsList[0]

	cd, mockHandler, memoryCache, fakeClock := newTestContainerData(t)
	mockHandler.On("GetStats").Return(stats, nil)
	defer func() {
		err := cd.Stop()
		assert.NoError(t, err)
	}()

	numConcurrentCalls := 5
	var waitForHousekeeping sync.WaitGroup
	waitForHousekeeping.Add(numConcurrentCalls)
	onDemandCache := []chan struct{}{}
	for i := 0; i < numConcurrentCalls; i++ {
		go func() {
			cd.OnDemandHousekeeping(0 * time.Second)
			waitForHousekeeping.Done()
		}()
		// Wait for work to be queued
		onDemandCache = append(onDemandCache, <-cd.onDemandChan)
	}
	// Requeue work:
	for _, ch := range onDemandCache {
		cd.onDemandChan <- ch
	}

	go cd.housekeepingTick(fakeClock.NewTimer(time.Minute).C(), testLongHousekeeping)
	// Ensure that all queued calls return with only a single call to housekeepingTick
	waitForHousekeeping.Wait()

	checkNumStats(t, memoryCache, 1)
	mockHandler.AssertExpectations(t)
}

func TestOnDemandHousekeepingReturnsAfterStopped(t *testing.T) {
	statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
	stats := statsList[0]

	cd, mockHandler, memoryCache, fakeClock := newTestContainerData(t)
	mockHandler.On("GetStats").Return(stats, nil)

	// trigger housekeeping update
	go cd.OnDemandHousekeeping(0 * time.Second)
	cd.housekeepingTick(fakeClock.NewTimer(time.Minute).C(), testLongHousekeeping)

	checkNumStats(t, memoryCache, 1)

	fakeClock.Step(2 * time.Second)

	err := cd.Stop()
	assert.NoError(t, err)
	// housekeeping tick should detect stop and not store any more metrics
	assert.False(t, cd.housekeepingTick(fakeClock.NewTimer(time.Minute).C(), testLongHousekeeping))
	fakeClock.Step(1 * time.Second)
	// on demand housekeeping should not block and return
	cd.OnDemandHousekeeping(-1 * time.Second)

	mockHandler.AssertExpectations(t)
}

func TestOnDemandHousekeepingRace(t *testing.T) {
	statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
	stats := statsList[0]

	cd, mockHandler, _, _ := newTestContainerData(t)
	mockHandler.On("GetStats").Return(stats, nil)

	wg := sync.WaitGroup{}
	wg.Add(1002)

	go func() {
		time.Sleep(10 * time.Millisecond)
		err := cd.Start()
		assert.NoError(t, err)
		wg.Done()
	}()

	go func() {
		t.Log("starting on demand goroutine")
		for i := 0; i < 1000; i++ {
			go func() {
				time.Sleep(1 * time.Microsecond)
				cd.OnDemandHousekeeping(0 * time.Millisecond)
				wg.Done()
			}()
		}
		wg.Done()
	}()
	wg.Wait()
}
<|endoftext|>


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

// +build !privileged_tests

package loadbalancer

import (
	"net"
	"testing"

	"gopkg.in/check.v1"
)

// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) {
	check.TestingT(t)
}

type TypesSuite struct{}

var _ = check.Suite(&TypesSuite{})

func TestL4Addr_Equals(t *testing.T) {
	type args struct {
		o *L4Addr
	}
	tests := []struct {
		name   string
		fields *L4Addr
		args   args
		want   bool
	}{
		{
			name: "both equal",
			fields: &L4Addr{
				Protocol: NONE,
				Port:     1,
			},
			args: args{
				o: &L4Addr{
					Protocol: NONE,
					Port:     1,
				},
			},
			want: true,
		},
		{
			name: "both different",
			fields: &L4Addr{
				Protocol: NONE,
				Port:     0,
			},
			args: args{
				o: &L4Addr{
					Protocol: NONE,
					Port:     1,
				},
			},
			want: false,
		},
		{
			name: "both nil",
			args: args{},
			want: true,
		},
		{
			name: "other nil",
			fields: &L4Addr{
				Protocol: NONE,
				Port:     1,
			},
			args: args{},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := tt.fields
			if got := l.Equals(tt.args.o); got != tt.want {
				t.Errorf("L4Addr.Equals() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestL3n4AddrID_Equals(t *testing.T) {
	type args struct {
		o *L3n4AddrID
	}
	tests := []struct {
		name   string
		fields *L3n4AddrID
		args   args
		want   bool
	}{
		{
			name: "both equal",
			fields: &L3n4AddrID{
				L3n4Addr: L3n4Addr{
					L4Addr: L4Addr{
						Protocol: NONE,
						Port:     1,
					},
					IP: net.IPv4(1, 1, 1, 1),
				},
				ID: 1,
			},
			args: args{
				o: &L3n4AddrID{
					L3n4Addr: L3n4Addr{
						L4Addr: L4Addr{
							Protocol: NONE,
							Port:     1,
						},
						IP: net.IPv4(1, 1, 1, 1),
					},
					ID: 1,
				},
			},
			want: true,
		},
		{
			name: "IDs different",
			fields: &L3n4AddrID{
				L3n4Addr: L3n4Addr{
					L4Addr: L4Addr{
						Protocol: NONE,
						Port:     1,
					},
					IP: net.IPv4(1, 1, 1, 1),
				},
				ID: 1,
			},
			args: args{
				o: &L3n4AddrID{
					L3n4Addr: L3n4Addr{
						L4Addr: L4Addr{
							Protocol: NONE,
							Port:     1,
						},
						IP: net.IPv4(1, 1, 1, 1),
					},
					ID: 2,
				},
			},
			want: false,
		},
		{
			name: "IPs different",
			fields: &L3n4AddrID{
				L3n4Addr: L3n4Addr{
					L4Addr: L4Addr{
						Protocol: NONE,
						Port:     1,
					},
					IP: net.IPv4(2, 2, 2, 2),
				},
				ID: 1,
			},
			args: args{
				o: &L3n4AddrID{
					L3n4Addr: L3n4Addr{
						L4Addr: L4Addr{
							Protocol: NONE,
							Port:     1,
						},
						IP: net.IPv4(1, 1, 1, 1),
					},
					ID: 1,
				},
			},
			want: false,
		},
		{
			name: "Ports different",
			fields: &L3n4AddrID{
				L3n4Addr: L3n4Addr{
					L4Addr: L4Addr{
						Protocol: NONE,
						Port:     2,
					},
					IP: net.IPv4(1, 1, 1, 1),
				},
				ID: 1,
			},
			args: args{
				o: &L3n4AddrID{
					L3n4Addr: L3n4Addr{
						L4Addr: L4Addr{
							Protocol: NONE,
							Port:     1,
						},
						IP: net.IPv4(1, 1, 1, 1),
					},
					ID: 1,
				},
			},
			want: false,
		},
		{
			name: "both nil",
			args: args{},
			want: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			f := tt.fields
			if got := f.Equals(tt.args.o); got != tt.want {
				t.Errorf("L3n4AddrID.Equals() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCreateSvcFlag(t *testing.T) {
	type args struct {
		svcTypes []SVCType
		svcLocal bool
	}
	tests := []struct {
		name string
		args args
		want ServiceFlags
	}{
		{
			args: args{
				svcTypes: []SVCType{SVCTypeClusterIP},
				svcLocal: false,
			},
			want: serviceFlagNone,
		},
		{
			args: args{
				svcTypes: []SVCType{SVCTypeNodePort},
				svcLocal: false,
			},
			want: serviceFlagNodePort,
		},
		{
			args: args{
				svcTypes: []SVCType{SVCTypeExternalIPs},
				svcLocal: false,
			},
			want: serviceFlagExternalIPs,
		},
		{
			args: args{
				svcTypes: []SVCType{SVCTypeClusterIP},
				svcLocal: true,
			},
			want: serviceFlagNone | serviceFlagLocalScope,
		},
		{
			args: args{
				svcTypes: []SVCType{SVCTypeNodePort},
				svcLocal: true,
			},
			want: serviceFlagNodePort | serviceFlagLocalScope,
		},
		{
			args: args{
				svcTypes: []SVCType{SVCTypeExternalIPs},
				svcLocal: true,
			},
			want: serviceFlagExternalIPs | serviceFlagLocalScope,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := CreateSvcFlag(tt.args.svcLocal, false, tt.args.svcTypes...); got != tt.want {
				t.Errorf("CreateSvcFlag() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestServiceFlags_IsSvcType(t *testing.T) {
	type args struct {
		svcType  SVCType
		svcLocal bool
	}
	tests := []struct {
		name string
		s    ServiceFlags
		args args
		want bool
	}{
		{
			args: args{
				svcType:  SVCTypeExternalIPs,
				svcLocal: false,
			},
			s:    serviceFlagExternalIPs,
			want: true,
		},
		{
			args: args{
				svcType:  SVCTypeNodePort,
				svcLocal: false,
			},
			s:    serviceFlagExternalIPs,
			want: false,
		},
		{
			args: args{
				svcType:  SVCTypeExternalIPs,
				svcLocal: true,
			},
			s:    serviceFlagExternalIPs | serviceFlagLocalScope,
			want: true,
		},
		{
			args: args{
				svcType:  SVCTypeNodePort,
				svcLocal: true,
			},
			s:    serviceFlagExternalIPs | serviceFlagLocalScope,
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.s.IsSvcType(tt.args.svcLocal, false, tt.args.svcType); got != tt.want {
				t.Errorf("IsSvcType() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestServiceFlags_String(t *testing.T) {
	tests := []struct {
		name string
		s    ServiceFlags
		want string
	}{
		{
			name: "Test-1",
			s:    serviceFlagExternalIPs,
			want: "ExternalIPs",
		},
		{
			name: "Test-2",
			s:    serviceFlagNone,
			want: "ClusterIP",
		},
		{
			name: "Test-3",
			s:    serviceFlagNodePort | serviceFlagLocalScope,
			want: "NodePort, Local",
		},
		{
			name: "Test-4",
			s:    serviceFlagExternalIPs | serviceFlagLocalScope,
			want: "ExternalIPs, Local",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.s.String(); got != tt.want {
				t.Errorf("String() = %v, want %v", got, tt.want)
			}
		})
	}
}
<commit_msg>test: add svc flags unit test for LoadBalancer<commit_after>// Copyright 2018-2019 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build !privileged_tests

package loadbalancer

import (
	"net"
	"testing"

	"gopkg.in/check.v1"
)

// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) {
	check.TestingT(t)
}

type TypesSuite struct{}

var _ = check.Suite(&TypesSuite{})

func TestL4Addr_Equals(t *testing.T) {
	type args struct {
		o *L4Addr
	}
	tests := []struct {
		name   string
		fields *L4Addr
		args   args
		want   bool
	}{
		{
			name: "both equal",
			fields: &L4Addr{
				Protocol: NONE,
				Port:     1,
			},
			args: args{
				o: &L4Addr{
					Protocol: NONE,
					Port:     1,
				},
			},
			want: true,
		},
		{
			name: "both different",
			fields: &L4Addr{
				Protocol: NONE,
				Port:     0,
			},
			args: args{
				o: &L4Addr{
					Protocol: NONE,
					Port:     1,
				},
			},
			want: false,
		},
		{
			name: "both nil",
			args: args{},
			want: true,
		},
		{
			name: "other nil",
			fields: &L4Addr{
				Protocol: NONE,
				Port:     1,
			},
			args: args{},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := tt.fields
			if got := l.Equals(tt.args.o); got != tt.want {
				t.Errorf("L4Addr.Equals() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestL3n4AddrID_Equals(t *testing.T) {
	type args struct {
		o *L3n4AddrID
	}
	tests := []struct {
		name   string
		fields *L3n4AddrID
		args   args
		want   bool
	}{
		{
			name: "both equal",
			fields: &L3n4AddrID{
				L3n4Addr: L3n4Addr{
					L4Addr: L4Addr{
						Protocol: NONE,
						Port:     1,
					},
					IP: net.IPv4(1, 1, 1, 1),
				},
				ID: 1,
			},
			args: args{
				o: &L3n4AddrID{
					L3n4Addr: L3n4Addr{
						L4Addr: L4Addr{
							Protocol: NONE,
							Port:     1,
						},
						IP: net.IPv4(1, 1, 1, 1),
					},
					ID: 1,
				},
			},
			want: true,
		},
		{
			name: "IDs different",
			fields: &L3n4AddrID{
				L3n4Addr: L3n4Addr{
					L4Addr: L4Addr{
						Protocol: NONE,
						Port:     1,
					},
					IP: net.IPv4(1, 1, 1, 1),
				},
				ID: 1,
			},
			args: args{
				o: &L3n4AddrID{
					L3n4Addr: L3n4Addr{
						L4Addr: L4Addr{
							Protocol: NONE,
							Port:     1,
						},
						IP: net.IPv4(1, 1, 1, 1),
					},
					ID: 2,
				},
			},
			want: false,
		},
		{
			name: "IPs different",
			fields: &L3n4AddrID{
				L3n4Addr: L3n4Addr{
					L4Addr: L4Addr{
						Protocol: NONE,
						Port:     1,
					},
					IP: net.IPv4(2, 2, 2, 2),
				},
				ID: 1,
			},
			args: args{
				o: &L3n4AddrID{
					L3n4Addr: L3n4Addr{
						L4Addr: L4Addr{
							Protocol: NONE,
							Port:     1,
						},
						IP: net.IPv4(1, 1, 1, 1),
					},
					ID: 1,
				},
			},
			want: false,
		},
		{
			name: "Ports different",
			fields: &L3n4AddrID{
				L3n4Addr: L3n4Addr{
					L4Addr: L4Addr{
						Protocol: NONE,
						Port:     2,
					},
					IP: net.IPv4(1, 1, 1, 1),
				},
				ID: 1,
			},
			args: args{
				o: &L3n4AddrID{
					L3n4Addr: L3n4Addr{
						L4Addr: L4Addr{
							Protocol: NONE,
							Port:     1,
						},
						IP: net.IPv4(1, 1, 1, 1),
					},
					ID: 1,
				},
			},
			want: false,
		},
		{
			name: "both nil",
			args: args{},
			want: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			f := tt.fields
			if got := f.Equals(tt.args.o); got != tt.want {
				t.Errorf("L3n4AddrID.Equals() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCreateSvcFlag(t *testing.T) {
	type args struct {
		svcTypes []SVCType
		svcLocal bool
	}
	tests := []struct {
		name string
		args args
		want ServiceFlags
	}{
		{
			args: args{
				svcTypes: []SVCType{SVCTypeClusterIP},
				svcLocal: false,
			},
			want: serviceFlagNone,
		},
		{
			args: args{
				svcTypes: []SVCType{SVCTypeNodePort},
				svcLocal: false,
			},
			want: serviceFlagNodePort,
		},
		{
			args: args{
				svcTypes: []SVCType{SVCTypeExternalIPs},
				svcLocal: false,
			},
			want: serviceFlagExternalIPs,
		},
		{
			args: args{
				svcTypes: []SVCType{SVCTypeClusterIP},
				svcLocal: true,
			},
			want: serviceFlagNone | serviceFlagLocalScope,
		},
		{
			args: args{
				svcTypes: []SVCType{SVCTypeNodePort},
				svcLocal: true,
			},
			want: serviceFlagNodePort | serviceFlagLocalScope,
		},
		{
			args: args{
				svcTypes: []SVCType{SVCTypeExternalIPs},
				svcLocal: true,
			},
			want: serviceFlagExternalIPs | serviceFlagLocalScope,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := CreateSvcFlag(tt.args.svcLocal, false, tt.args.svcTypes...); got != tt.want {
				t.Errorf("CreateSvcFlag() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestServiceFlags_IsSvcType(t *testing.T) {
	type args struct {
		svcType  SVCType
		svcLocal bool
	}
	tests := []struct {
		name string
		s    ServiceFlags
		args args
		want bool
	}{
		{
			args: args{
				svcType:  SVCTypeExternalIPs,
				svcLocal: false,
			},
			s:    serviceFlagExternalIPs,
			want: true,
		},
		{
			args: args{
				svcType:  SVCTypeNodePort,
				svcLocal: false,
			},
			s:    serviceFlagExternalIPs,
			want: false,
		},
		{
			args: args{
				svcType:  SVCTypeExternalIPs,
				svcLocal: true,
			},
			s:    serviceFlagExternalIPs | serviceFlagLocalScope,
			want: true,
		},
		{
			args: args{
				svcType:  SVCTypeNodePort,
				svcLocal: true,
			},
			s:    serviceFlagExternalIPs | serviceFlagLocalScope,
			want: false,
		},
		{
			args: args{
				svcType:  SVCTypeLoadBalancer,
				svcLocal: false,
			},
			s:    serviceFlagLoadBalancer,
			want: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.s.IsSvcType(tt.args.svcLocal, false, tt.args.svcType); got != tt.want {
				t.Errorf("IsSvcType() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestServiceFlags_String(t *testing.T) {
	tests := []struct {
		name string
		s    ServiceFlags
		want string
	}{
		{
			name: "Test-1",
			s:    serviceFlagExternalIPs,
			want: "ExternalIPs",
		},
		{
			name: "Test-2",
			s:    serviceFlagNone,
			want: "ClusterIP",
		},
		{
			name: "Test-3",
			s:    serviceFlagNodePort | serviceFlagLocalScope,
			want: "NodePort, Local",
		},
		{
			name: "Test-4",
			s:    serviceFlagExternalIPs | serviceFlagLocalScope,
			want: "ExternalIPs, Local",
		},
		{
			name: "Test-5",
			s:    serviceFlagLoadBalancer,
			want: "LoadBalancer",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.s.String(); got != tt.want {
				t.Errorf("String() = %v, want %v", got, tt.want)
			}
		})
	}
}
<|endoftext|>


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

package handler

import (
	"github.com/dgrijalva/jwt-go"
	"github.com/mitchellh/mapstructure"

	"github.com/skygeario/skygear-server/pkg/server/asset"
	"github.com/skygeario/skygear-server/pkg/server/authtoken"
	"github.com/skygeario/skygear-server/pkg/server/plugin/hook"
	"github.com/skygeario/skygear-server/pkg/server/plugin/provider"
	"github.com/skygeario/skygear-server/pkg/server/router"
	"github.com/skygeario/skygear-server/pkg/server/skydb"
	"github.com/skygeario/skygear-server/pkg/server/skyerr"
)

type ssoCustomTokenClaims struct {
	Profile skydb.Data `json:"skyprofile"`
	jwt.StandardClaims
}

type ssoCustomTokenLoginPayload struct {
	keyFunc     jwt.Keyfunc
	TokenString string               `mapstructure:"token"`
	Token       *jwt.Token           `mapstructure:"-"`
	Claims      ssoCustomTokenClaims `mapstructure:"-"`
}

func (payload *ssoCustomTokenLoginPayload) Decode(data map[string]interface{}) skyerr.Error {
	if err := mapstructure.Decode(data, payload); err != nil {
		return skyerr.NewError(skyerr.BadRequest, "fails to decode the request payload")
	}

	if parsedToken, err := jwt.ParseWithClaims(
		payload.TokenString,
		&payload.Claims,
		payload.keyFunc,
	); err != nil {
		return skyerr.NewError(skyerr.BadRequest, "fails to decode the request payload")
	} else {
		payload.Token = parsedToken
	}

	return payload.Validate()
}

func (payload *ssoCustomTokenLoginPayload) Validate() skyerr.Error {
	claims := payload.Claims
	if claims.Subject == "" {
		return skyerr.NewError(
			skyerr.InvalidCredentials,
			"invalid token: subject (sub) not specified",
		)
	}

	if claims.ExpiresAt == 0 {
		return skyerr.NewError(
			skyerr.InvalidCredentials,
			"invalid token: expires at (exp) not specified",
		)
	}

	if claims.IssuedAt == 0 {
		return skyerr.NewError(
			skyerr.InvalidCredentials,
			"invalid token: issued at (iat) not specified",
		)
	}

	if claims.Valid() != nil {
		return skyerr.NewError(
			skyerr.InvalidCredentials,
			"invalid token: token is not valid at this time",
		)
	}

	return nil
}

/*
SSOCustomTokenLoginHandler authenticate user with password

The user can be either identified by username or password.

curl -X POST -H "Content-Type: application/json" \
  -d @- http://localhost:3000/ <<EOF
{
	"action": "sso:custom_token:login",
	"token": "eyXXXXXXX",
}
EOF
*/
type SSOCustomTokenLoginHandler struct {
	CustomTokenSecret string

	TokenStore       authtoken.Store    `inject:"TokenStore"`
	ProviderRegistry *provider.Registry `inject:"ProviderRegistry"`
	HookRegistry     *hook.Registry     `inject:"HookRegistry"`
	AssetStore       asset.Store        `inject:"AssetStore"`
	AuthRecordKeys   [][]string         `inject:"AuthRecordKeys"`
	AccessKey        router.Processor   `preprocessor:"accesskey"`
	DBConn           router.Processor   `preprocessor:"dbconn"`
	InjectPublicDB   router.Processor   `preprocessor:"inject_public_db"`
	PluginReady      router.Processor   `preprocessor:"plugin_ready"`
	preprocessors    []router.Processor
}

func (h *SSOCustomTokenLoginHandler) Setup() {
	h.preprocessors = []router.Processor{
		h.AccessKey,
		h.DBConn,
		h.InjectPublicDB,
		h.PluginReady,
	}
}

func (h *SSOCustomTokenLoginHandler) GetPreprocessors() []router.Processor {
	return h.preprocessors
}

func (h *SSOCustomTokenLoginHandler) Handle(payload *router.Payload, response *router.Response) {
	if h.CustomTokenSecret == "" {
		response.Err = skyerr.NewError(
			skyerr.NotConfigured,
			"login with custom token requires CUSTOM_TOKEN_SECRET config",
		)
		return
	}

	p := &ssoCustomTokenLoginPayload{
		keyFunc: func(token *jwt.Token) (interface{}, error) {
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, skyerr.NewInvalidArgument("invalid token", []string{"token"})
			}
			return []byte(h.CustomTokenSecret), nil
		},
	}
	skyErr := p.Decode(payload.Data)
	if skyErr != nil {
		response.Err = skyErr
		return
	}

	if h.TokenStore == nil {
		panic("token store is nil")
	}
	store := h.TokenStore

	info := skydb.AuthInfo{}
	user := skydb.Record{}

	if skyErr = h.handleLogin(payload, p, &info, &user); skyErr != nil {
		response.Err = skyErr
		return
	}

	// generate access-token
	token, err := store.NewToken(payload.AppName, info.ID)
	if err != nil {
		panic(err)
	}

	if err = store.Put(&token); err != nil {
		panic(err)
	}

	authResponse, err := AuthResponseFactory{
		AssetStore: h.AssetStore,
		Conn:       payload.DBConn,
	}.NewAuthResponse(info, user, token.AccessToken, payload.HasMasterKey())
	if err != nil {
		response.Err = skyerr.MakeError(err)
		return
	}

	// Populate the activity time to user
	now := timeNow()
	info.LastSeenAt = &now
	if err := payload.DBConn.UpdateAuth(&info); err != nil {
		response.Err = skyerr.MakeError(err)
		return
	}

	// update user record last login time
	user.UpdatedAt = now
	user.UpdaterID = info.ID
	user.Data[UserRecordLastLoginAtKey] = now
	if err := payload.Database.Save(&user); err != nil {
		response.Err = skyerr.MakeError(err)
		return
	}

	response.Result = authResponse
}

func (h *SSOCustomTokenLoginHandler) handleLogin(payload *router.Payload, p *ssoCustomTokenLoginPayload, authinfo *skydb.AuthInfo, user *skydb.Record) skyerr.Error {
	principalID := p.Claims.Subject
	var customTokenInfo skydb.CustomTokenInfo
	createNewUser := false

	if err := payload.DBConn.GetCustomTokenInfo(principalID, &customTokenInfo); err != nil {
		if err != skydb.ErrUserNotFound {
			return skyerr.MakeError(err)
		}
		createNewUser = true
		*authinfo = skydb.NewAnonymousAuthInfo()
	}

	if !createNewUser {
		if err := payload.DBConn.GetAuth(customTokenInfo.UserID, authinfo); err != nil {
			if err != skydb.ErrUserNotFound {
				return skyerr.MakeError(err)
			}
			createNewUser = true
			*authinfo = skydb.NewAnonymousAuthInfo()
		}
	}

	userRecordContext := &authUserRecordContext{
		DBConn:         payload.DBConn,
		Database:       payload.Database,
		AssetStore:     h.AssetStore,
		HookRegistry:   h.HookRegistry,
		AuthRecordKeys: h.AuthRecordKeys,
		Context:        payload.Context,
	}
	if createNewUser {
		userRecordContext.BeforeSaveFunc = func(conn skydb.Conn, info *skydb.AuthInfo) error {
			if err := conn.CreateAuth(info); err != nil {
				if err == skydb.ErrUserDuplicated {
					return errUserDuplicated
				}

				return skyerr.MakeError(err)
			}
			return nil
		}
		userRecordContext.BeforeCommitFunc = func(conn skydb.Conn, user *skydb.Record) error {
			now := timeNow()
			if err := conn.CreateCustomTokenInfo(&skydb.CustomTokenInfo{
				UserID:      user.ID.Key,
				PrincipalID: principalID,
				CreatedAt:   &now,
			}); err != nil {
				return skyerr.MakeError(err)
			}
			return nil
		}

	} else {
		userRecordContext.BeforeSaveFunc = func(conn skydb.Conn, info *skydb.AuthInfo) error {
			if err := conn.UpdateAuth(info); err != nil {
				return skyerr.MakeError(err)
			}
			return nil
		}
		userRecordContext.BeforeCommitFunc = func(conn skydb.Conn, user *skydb.Record) error {
			now := timeNow()
			if err := conn.CreateCustomTokenInfo(&skydb.CustomTokenInfo{
				UserID:      user.ID.Key,
				PrincipalID: principalID,
				CreatedAt:   &now,
			}); err != nil {
				return skyerr.MakeError(err)
			}
			return nil
		}
	}

	if modifiedUser, err := userRecordContext.execute(
		authinfo,
		skydb.AuthData{},
		p.Claims.Profile,
	); err != nil {
		return skyerr.MakeError(err)
	} else {
		*user = *modifiedUser
	}

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

package handler

import (
	"github.com/dgrijalva/jwt-go"
	"github.com/mitchellh/mapstructure"

	"github.com/skygeario/skygear-server/pkg/server/asset"
	"github.com/skygeario/skygear-server/pkg/server/authtoken"
	"github.com/skygeario/skygear-server/pkg/server/plugin/hook"
	"github.com/skygeario/skygear-server/pkg/server/plugin/provider"
	"github.com/skygeario/skygear-server/pkg/server/router"
	"github.com/skygeario/skygear-server/pkg/server/skydb"
	"github.com/skygeario/skygear-server/pkg/server/skyerr"
)

type ssoCustomTokenClaims struct {
	Profile skydb.Data `json:"skyprofile"`
	jwt.StandardClaims
}

type ssoCustomTokenLoginPayload struct {
	keyFunc     jwt.Keyfunc
	TokenString string               `mapstructure:"token"`
	Token       *jwt.Token           `mapstructure:"-"`
	Claims      ssoCustomTokenClaims `mapstructure:"-"`
}

func (payload *ssoCustomTokenLoginPayload) Decode(data map[string]interface{}) skyerr.Error {
	if err := mapstructure.Decode(data, payload); err != nil {
		return skyerr.NewError(skyerr.BadRequest, "fails to decode the request payload")
	}

	parsedToken, err := jwt.ParseWithClaims(
		payload.TokenString,
		&payload.Claims,
		payload.keyFunc,
	)
	if err != nil {
		return skyerr.NewError(skyerr.BadRequest, "fails to decode the request payload")
	}

	payload.Token = parsedToken
	return payload.Validate()
}

func (payload *ssoCustomTokenLoginPayload) Validate() skyerr.Error {
	claims := payload.Claims
	if claims.Subject == "" {
		return skyerr.NewError(
			skyerr.InvalidCredentials,
			"invalid token: subject (sub) not specified",
		)
	}

	if claims.ExpiresAt == 0 {
		return skyerr.NewError(
			skyerr.InvalidCredentials,
			"invalid token: expires at (exp) not specified",
		)
	}

	if claims.IssuedAt == 0 {
		return skyerr.NewError(
			skyerr.InvalidCredentials,
			"invalid token: issued at (iat) not specified",
		)
	}

	if claims.Valid() != nil {
		return skyerr.NewError(
			skyerr.InvalidCredentials,
			"invalid token: token is not valid at this time",
		)
	}

	return nil
}

/*
SSOCustomTokenLoginHandler authenticate user with password

The user can be either identified by username or password.

curl -X POST -H "Content-Type: application/json" \
  -d @- http://localhost:3000/ <<EOF
{
	"action": "sso:custom_token:login",
	"token": "eyXXXXXXX",
}
EOF
*/
type SSOCustomTokenLoginHandler struct {
	CustomTokenSecret string

	TokenStore       authtoken.Store    `inject:"TokenStore"`
	ProviderRegistry *provider.Registry `inject:"ProviderRegistry"`
	HookRegistry     *hook.Registry     `inject:"HookRegistry"`
	AssetStore       asset.Store        `inject:"AssetStore"`
	AuthRecordKeys   [][]string         `inject:"AuthRecordKeys"`
	AccessKey        router.Processor   `preprocessor:"accesskey"`
	DBConn           router.Processor   `preprocessor:"dbconn"`
	InjectPublicDB   router.Processor   `preprocessor:"inject_public_db"`
	PluginReady      router.Processor   `preprocessor:"plugin_ready"`
	preprocessors    []router.Processor
}

func (h *SSOCustomTokenLoginHandler) Setup() {
	h.preprocessors = []router.Processor{
		h.AccessKey,
		h.DBConn,
		h.InjectPublicDB,
		h.PluginReady,
	}
}

func (h *SSOCustomTokenLoginHandler) GetPreprocessors() []router.Processor {
	return h.preprocessors
}

func (h *SSOCustomTokenLoginHandler) Handle(payload *router.Payload, response *router.Response) {
	if h.CustomTokenSecret == "" {
		response.Err = skyerr.NewError(
			skyerr.NotConfigured,
			"login with custom token requires CUSTOM_TOKEN_SECRET config",
		)
		return
	}

	p := &ssoCustomTokenLoginPayload{
		keyFunc: func(token *jwt.Token) (interface{}, error) {
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, skyerr.NewInvalidArgument("invalid token", []string{"token"})
			}
			return []byte(h.CustomTokenSecret), nil
		},
	}
	skyErr := p.Decode(payload.Data)
	if skyErr != nil {
		response.Err = skyErr
		return
	}

	if h.TokenStore == nil {
		panic("token store is nil")
	}
	store := h.TokenStore

	info := skydb.AuthInfo{}
	user := skydb.Record{}

	if skyErr = h.handleLogin(payload, p, &info, &user); skyErr != nil {
		response.Err = skyErr
		return
	}

	// generate access-token
	token, err := store.NewToken(payload.AppName, info.ID)
	if err != nil {
		panic(err)
	}

	if err = store.Put(&token); err != nil {
		panic(err)
	}

	authResponse, err := AuthResponseFactory{
		AssetStore: h.AssetStore,
		Conn:       payload.DBConn,
	}.NewAuthResponse(info, user, token.AccessToken, payload.HasMasterKey())
	if err != nil {
		response.Err = skyerr.MakeError(err)
		return
	}

	// Populate the activity time to user
	now := timeNow()
	info.LastSeenAt = &now
	if err := payload.DBConn.UpdateAuth(&info); err != nil {
		response.Err = skyerr.MakeError(err)
		return
	}

	// update user record last login time
	user.UpdatedAt = now
	user.UpdaterID = info.ID
	user.Data[UserRecordLastLoginAtKey] = now
	if err := payload.Database.Save(&user); err != nil {
		response.Err = skyerr.MakeError(err)
		return
	}

	response.Result = authResponse
}

func (h *SSOCustomTokenLoginHandler) handleLogin(payload *router.Payload, p *ssoCustomTokenLoginPayload, authinfo *skydb.AuthInfo, user *skydb.Record) skyerr.Error {
	principalID := p.Claims.Subject
	var customTokenInfo skydb.CustomTokenInfo
	createNewUser := false

	if err := payload.DBConn.GetCustomTokenInfo(principalID, &customTokenInfo); err != nil {
		if err != skydb.ErrUserNotFound {
			return skyerr.MakeError(err)
		}
		createNewUser = true
		*authinfo = skydb.NewAnonymousAuthInfo()
	}

	if !createNewUser {
		if err := payload.DBConn.GetAuth(customTokenInfo.UserID, authinfo); err != nil {
			if err != skydb.ErrUserNotFound {
				return skyerr.MakeError(err)
			}
			createNewUser = true
			*authinfo = skydb.NewAnonymousAuthInfo()
		}
	}

	userRecordContext := &authUserRecordContext{
		DBConn:         payload.DBConn,
		Database:       payload.Database,
		AssetStore:     h.AssetStore,
		HookRegistry:   h.HookRegistry,
		AuthRecordKeys: h.AuthRecordKeys,
		Context:        payload.Context,
	}
	if createNewUser {
		userRecordContext.BeforeSaveFunc = func(conn skydb.Conn, info *skydb.AuthInfo) error {
			if err := conn.CreateAuth(info); err != nil {
				if err == skydb.ErrUserDuplicated {
					return errUserDuplicated
				}

				return skyerr.MakeError(err)
			}
			return nil
		}
		userRecordContext.BeforeCommitFunc = func(conn skydb.Conn, user *skydb.Record) error {
			now := timeNow()
			if err := conn.CreateCustomTokenInfo(&skydb.CustomTokenInfo{
				UserID:      user.ID.Key,
				PrincipalID: principalID,
				CreatedAt:   &now,
			}); err != nil {
				return skyerr.MakeError(err)
			}
			return nil
		}

	} else {
		userRecordContext.BeforeSaveFunc = func(conn skydb.Conn, info *skydb.AuthInfo) error {
			if err := conn.UpdateAuth(info); err != nil {
				return skyerr.MakeError(err)
			}
			return nil
		}
		userRecordContext.BeforeCommitFunc = func(conn skydb.Conn, user *skydb.Record) error {
			now := timeNow()
			if err := conn.CreateCustomTokenInfo(&skydb.CustomTokenInfo{
				UserID:      user.ID.Key,
				PrincipalID: principalID,
				CreatedAt:   &now,
			}); err != nil {
				return skyerr.MakeError(err)
			}
			return nil
		}
	}

	modifiedUser, err := userRecordContext.execute(
		authinfo,
		skydb.AuthData{},
		p.Claims.Profile,
	)
	if err != nil {
		return skyerr.MakeError(err)
	}

	*user = *modifiedUser
	return nil
}
<|endoftext|>


<commit_before>// datatable.go  (c) 2011-2014 David Rook

package datatable

/*
 *
 *
 *
 *
 */

import (
	"errors"
	"fmt"
	//"os"
)

type VerboseType bool

var Verbose VerboseType

func (v VerboseType) Printf(s string, a ...interface{}) {
	if v {
		fmt.Printf(s, a...)
	}
}

//  ----------------------------------------
//  |        pkg   d a t a t a b l e       |
//  ----------------------------------------
//

type DblPair struct {
	Left, Right float64
}

type Table struct {
	Name            string
	Data            []DblPair
	RevData         []DblPair
	high, low       float64
	revHigh, revLow float64
}

var (
	ForceRangeFlag    bool = true // forces out of range values to fit
	CantHappenError        = errors.New("Can't-happen")
	DivideByZeroError      = errors.New("Divide By Zero")
	OutOfRangeError        = errors.New("Value outside expected range")
	InvalidTableError      = errors.New("Not a valid table")
)

func init() {
	Verbose = false
}

func (d *Table) Setup() error {
	if len(d.Name) <= 0 {
		fmt.Printf("table really should be named\n")
	}
	length := len(d.Data)
	if length < 2 {
		fmt.Printf("table not valid, length must be >= 2 but we found %d\n", length)
		return InvalidTableError
	}
	last := length - 1

	d.high = d.Data[0].Left
	d.low = d.high
	for i := 0; i < length; i++ {
		datum := d.Data[i].Left
		if datum > d.high {
			d.high = datum
		}
		if datum < d.low {
			d.low = datum
		}
		// table left values must increase each time in a valid table
		if i > 0 {
			if d.Data[i].Left <= d.Data[i-1].Left {
				fmt.Printf("table not valid, left values must increase with each row\n")
				return InvalidTableError
			}
		}
	}
	for i := last; i >= 0; i-- {
		d.RevData = append(d.RevData, DblPair{Left: d.Data[i].Right, Right: d.Data[i].Left})
	}
	d.revHigh = d.RevData[0].Left
	d.revLow = d.revHigh
	for i := 0; i < length; i++ {
		datum := d.RevData[i].Left
		if datum > d.revHigh {
			d.revHigh = datum
		}
		if datum < d.revLow {
			d.revLow = datum
		}
	}
	Verbose.Printf("RevData = %v\n", d.RevData)
	return nil
}

func (d *Table) Dump() {
	fmt.Printf("Name = %s\n", d.Name)
	fmt.Printf("Data = %v\n", d.Data)
	fmt.Printf("Reverse Data = %v\n", d.RevData)
	fmt.Printf("Data Length = %d\n", len(d.Data))
}

func inrange(a, b, c float64) bool {
	if a > c {
		a, c = c, a
	}
	if b < a {
		return false
	}
	if b > c {
		return false
	}
	return true
}

/*
 * x1,y1 and x2,y2 define a line
 * given v use linear interpolation to find its y value
 * x1 <= v <= x2
 */
func interp(x1, y1, x2, y2, v float64) (rc float64, err error) {
	Verbose.Printf("interp: x1[%v] y1[%v] x2[%v] y2[%v] v %v\n", x1, y1, x2, y2, v)
	if x2 == x1 {
		return 0.0, DivideByZeroError
	}
	rc = ((y2-y1)/(x2-x1))*(v-x1) + y1
	Verbose.Printf("interp returned %v %v\n", rc, err)
	return rc, nil
}

func (d *Table) Eval(v float64) (rc float64, err error) {
	length := len(d.Data)
	last := length - 1
	Verbose.Printf("%s : evaluate(%g) with %d items in lookup\n", d.Name, v, length)
	if !inrange(d.Data[0].Left, v, d.Data[last].Left) {
		if ForceRangeFlag {
			if v < d.Data[0].Left {
				Verbose.Printf(" low side is %g\n", d.Data[0].Left)
				Verbose.Printf("forced low end value match \n")
				return d.Data[0].Right, nil
			}

			if v > d.Data[last].Left {
				Verbose.Printf(" high side is %g\n", d.Data[last].Left)
				Verbose.Printf("forced high end value match \n")
				return d.Data[last].Right, nil
			}

		} else {
			return 0.0, OutOfRangeError
		}
	}

	Verbose.Printf(" low side is %g\n", d.Data[0].Left)
	if v == d.Data[0].Left {
		Verbose.Printf("extreme low end value match \n")
		return d.Data[0].Right, nil
	}

	Verbose.Printf(" high side is %g\n", d.Data[last].Left)
	if v == d.Data[last].Left {
		Verbose.Printf("extreme high end value match \n")
		return d.Data[last].Right, nil
	}

	n := 1 // work table from bottom up, starting with second row
	for {
		if n > last {
			break // cant happen
		}
		if d.Data[n].Left > v { // must be in this range
			rc, err = interp(d.Data[n-1].Left, d.Data[n-1].Right, d.Data[n].Left, d.Data[n].Right, v)
			if err == nil {
				return rc, nil
			}
			if (err == OutOfRangeError) && ForceRangeFlag {
				return rc, nil
			}
			return rc, err
		}
		n++
	}
	// since we've already tested high & low we should never get here, but need a return anyway
	fmt.Printf("Cant happen\n")
	return 0.0, CantHappenError // 0.0 and failed
}

// returns multiple hits where appropriate
func (d *Table) ReverseEval(val float64) (rc []float64, err error) {
	var rv float64
	rc = make([]float64, 0, 10)
	length := len(d.Data)
	last := length - 1

	Verbose.Printf(" low side is %g high side is %g\n", d.RevData[0].Left, d.RevData[last].Left)
	Verbose.Printf("d.RevLow %v d.RevHigh %v\n", d.revLow, d.revHigh)
	if !inrange(d.revLow, val, d.revHigh) {
		return nil, OutOfRangeError
	}
	n := last
	//    TODO:? out of range values force to endpoints bug/feature?
	// I can see an argument for NOT doing out of bounds stuff

	for n > 0 {
		Verbose.Printf("n = %d\n", n)
		if inrange(d.RevData[n-1].Left, val, d.RevData[n].Left) {
			rv, err = interp(d.RevData[n-1].Left, d.RevData[n-1].Right, d.RevData[n].Left, d.RevData[n].Right, val)
			if err == nil {
				rc = append(rc, rv)
			} else {
				return nil, err // divide by zero should never happen with valid table
			}
		}
		// keep looking
		n--
	}
	if len(rc) <= 0 {
		return nil, nil
	}
	// BUG(mdr) remove duplicates if necessary, these happen in adjacent spots when a value is on
	// the end of a pair, can happen elsewhere as well, but not treating those yet.  This method is
	// quick, but inadequate for long run.  Need insertion sort or map for final solution.
	// currently just document that the resulting array may not have unique values
	Verbose.Printf("rv before 'uniq' = %v\n", rc)
	var newrc = []float64{rc[0]}
	for i := 1; i < len(rc); i++ {
		if rc[i] != rc[i-1] {
			newrc = append(newrc, rc[i])
		}
	}
	return newrc, nil
}
<commit_msg>check that setup is valid before using a table<commit_after>// datatable.go  (c) 2011-2014 David Rook

package datatable

/*
 *
 *
 *
 *
 */

import (
	"errors"
	"fmt"
	"log"
)

type VerboseType bool

var Verbose VerboseType

func (v VerboseType) Printf(s string, a ...interface{}) {
	if v {
		fmt.Printf(s, a...)
	}
}

//  ----------------------------------------
//  |        pkg   d a t a t a b l e       |
//  ----------------------------------------
//

type DblPair struct {
	Left, Right float64
}

type Table struct {
	Name            string
	Data            []DblPair
	RevData         []DblPair
	high, low       float64
	revHigh, revLow float64
	isSetup bool
}

var (
	ForceRangeFlag    bool = true // forces out of range values to fit
	CantHappenError        = errors.New("Can't-happen")
	DivideByZeroError      = errors.New("Divide By Zero")
	OutOfRangeError        = errors.New("Value outside expected range")
	InvalidTableError      = errors.New("Not a valid table")
)

func init() {
	Verbose = false
}

func (d *Table) Setup() error {
	if len(d.Name) <= 0 {
		fmt.Printf("table really should be named\n")
	}
	length := len(d.Data)
	if length < 2 {
		fmt.Printf("table not valid, length must be >= 2 but we found %d\n", length)
		return InvalidTableError
	}
	last := length - 1

	d.high = d.Data[0].Left
	d.low = d.high
	for i := 0; i < length; i++ {
		datum := d.Data[i].Left
		if datum > d.high {
			d.high = datum
		}
		if datum < d.low {
			d.low = datum
		}
		// table left values must increase each time in a valid table
		if i > 0 {
			if d.Data[i].Left <= d.Data[i-1].Left {
				fmt.Printf("table not valid, left values must increase with each row\n")
				return InvalidTableError
			}
		}
	}
	for i := last; i >= 0; i-- {
		d.RevData = append(d.RevData, DblPair{Left: d.Data[i].Right, Right: d.Data[i].Left})
	}
	d.revHigh = d.RevData[0].Left
	d.revLow = d.revHigh
	for i := 0; i < length; i++ {
		datum := d.RevData[i].Left
		if datum > d.revHigh {
			d.revHigh = datum
		}
		if datum < d.revLow {
			d.revLow = datum
		}
	}
	Verbose.Printf("RevData = %v\n", d.RevData)
	d.isSetup = true
	return nil
}

func (d *Table) Dump() {
	if !d.isSetup {
		log.Fatalf("Table %s is not setup\n",d.Name)
	}
	fmt.Printf("Name = %s\n", d.Name)
	fmt.Printf("Data = %v\n", d.Data)
	fmt.Printf("Reverse Data = %v\n", d.RevData)
	fmt.Printf("Data Length = %d\n", len(d.Data))
}

func inrange(a, b, c float64) bool {
	if a > c {
		a, c = c, a
	}
	if b < a {
		return false
	}
	if b > c {
		return false
	}
	return true
}

/*
 * x1,y1 and x2,y2 define a line
 * given v use linear interpolation to find its y value
 * x1 <= v <= x2
 */
func interp(x1, y1, x2, y2, v float64) (rc float64, err error) {
	Verbose.Printf("interp: x1[%v] y1[%v] x2[%v] y2[%v] v %v\n", x1, y1, x2, y2, v)
	if x2 == x1 {
		return 0.0, DivideByZeroError
	}
	rc = ((y2-y1)/(x2-x1))*(v-x1) + y1
	Verbose.Printf("interp returned %v %v\n", rc, err)
	return rc, nil
}

func (d *Table) Eval(v float64) (rc float64, err error) {
	if !d.isSetup {
		log.Fatalf("Table %s is not setup\n",d.Name)
	}
	length := len(d.Data)
	last := length - 1
	Verbose.Printf("%s : evaluate(%g) with %d items in lookup\n", d.Name, v, length)
	if !inrange(d.Data[0].Left, v, d.Data[last].Left) {
		if ForceRangeFlag {
			if v < d.Data[0].Left {
				Verbose.Printf(" low side is %g\n", d.Data[0].Left)
				Verbose.Printf("forced low end value match \n")
				return d.Data[0].Right, nil
			}

			if v > d.Data[last].Left {
				Verbose.Printf(" high side is %g\n", d.Data[last].Left)
				Verbose.Printf("forced high end value match \n")
				return d.Data[last].Right, nil
			}

		} else {
			return 0.0, OutOfRangeError
		}
	}

	Verbose.Printf(" low side is %g\n", d.Data[0].Left)
	if v == d.Data[0].Left {
		Verbose.Printf("extreme low end value match \n")
		return d.Data[0].Right, nil
	}

	Verbose.Printf(" high side is %g\n", d.Data[last].Left)
	if v == d.Data[last].Left {
		Verbose.Printf("extreme high end value match \n")
		return d.Data[last].Right, nil
	}

	n := 1 // work table from bottom up, starting with second row
	for {
		if n > last {
			break // cant happen
		}
		if d.Data[n].Left > v { // must be in this range
			rc, err = interp(d.Data[n-1].Left, d.Data[n-1].Right, d.Data[n].Left, d.Data[n].Right, v)
			if err == nil {
				return rc, nil
			}
			if (err == OutOfRangeError) && ForceRangeFlag {
				return rc, nil
			}
			return rc, err
		}
		n++
	}
	// since we've already tested high & low we should never get here, but need a return anyway
	fmt.Printf("Cant happen\n")
	return 0.0, CantHappenError // 0.0 and failed
}

// returns multiple hits where appropriate
func (d *Table) ReverseEval(val float64) (rc []float64, err error) {
	if !d.isSetup {
		log.Fatalf("Table %s is not setup\n",d.Name)
	}
	var rv float64
	rc = make([]float64, 0, 10)
	length := len(d.Data)
	last := length - 1

	Verbose.Printf(" low side is %g high side is %g\n", d.RevData[0].Left, d.RevData[last].Left)
	Verbose.Printf("d.RevLow %v d.RevHigh %v\n", d.revLow, d.revHigh)
	if !inrange(d.revLow, val, d.revHigh) {
		return nil, OutOfRangeError
	}
	n := last
	//    TODO:? out of range values force to endpoints bug/feature?
	// I can see an argument for NOT doing out of bounds stuff

	for n > 0 {
		Verbose.Printf("n = %d\n", n)
		if inrange(d.RevData[n-1].Left, val, d.RevData[n].Left) {
			rv, err = interp(d.RevData[n-1].Left, d.RevData[n-1].Right, d.RevData[n].Left, d.RevData[n].Right, val)
			if err == nil {
				rc = append(rc, rv)
			} else {
				return nil, err // divide by zero should never happen with valid table
			}
		}
		// keep looking
		n--
	}
	if len(rc) <= 0 {
		return nil, nil
	}
	// BUG(mdr) remove duplicates if necessary, these happen in adjacent spots when a value is on
	// the end of a pair, can happen elsewhere as well, but not treating those yet.  This method is
	// quick, but inadequate for long run.  Need insertion sort or map for final solution.
	// currently just document that the resulting array may not have unique values
	Verbose.Printf("rv before 'uniq' = %v\n", rc)
	var newrc = []float64{rc[0]}
	for i := 1; i < len(rc); i++ {
		if rc[i] != rc[i-1] {
			newrc = append(newrc, rc[i])
		}
	}
	return newrc, nil
}
<|endoftext|>


<commit_before>package unmount

import (
	"fmt"
	"os/exec"
)

// Unmount un mounts Fuse mounted local folder. Mount exists separate to
// lifecycle of this program and needs to be cleaned up when this exists.
func Unmount(folder string) error {
	if _, err := exec.Command("sudo", "umount", folder).CombinedOutput(); err != nil {
		fmt.Printf("Unmounting failed. Please do `sudo umount %s`.\n", folder)
	}

	return err
}
<commit_msg>fix error in unmount_linux<commit_after>package unmount

import (
	"fmt"
	"os/exec"
)

// Unmount un mounts Fuse mounted local folder. Mount exists separate to
// lifecycle of this program and needs to be cleaned up when this exists.
func Unmount(folder string) error {
	if _, err := exec.Command("sudo", "umount", folder).CombinedOutput(); err != nil {
		fmt.Printf("Unmounting failed. Please do `sudo umount %s`.\n", folder)
	}

	return nil
}
<|endoftext|>