<commit_before>/*
Copyright 2017 the Velero contributors.

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 discovery

import (
	"sort"
	"sync"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/discovery"
	"k8s.io/client-go/restmapper"

	kcmdutil "github.com/heptio/velero/third_party/kubernetes/pkg/kubectl/cmd/util"
)

// Helper exposes functions for interacting with the Kubernetes discovery
// API.
type Helper interface {
	// Resources gets the current set of resources retrieved from discovery
	// that are backuppable by Velero.
	Resources() []*metav1.APIResourceList

	// ResourceFor gets a fully-resolved GroupVersionResource and an
	// APIResource for the provided partially-specified GroupVersionResource.
	ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, metav1.APIResource, error)

	// Refresh pulls an updated set of Velero-backuppable resources from the
	// discovery API.
	Refresh() error

	// APIGroups gets the current set of supported APIGroups
	// in the cluster.
	APIGroups() []metav1.APIGroup
}

type serverResourcesInterface interface {
	ServerPreferredResources() ([]*metav1.APIResourceList, error)
}

type helper struct {
	discoveryClient discovery.DiscoveryInterface
	logger          logrus.FieldLogger

	// lock guards mapper, resources and resourcesMap
	lock         sync.RWMutex
	mapper       meta.RESTMapper
	resources    []*metav1.APIResourceList
	resourcesMap map[schema.GroupVersionResource]metav1.APIResource
	apiGroups    []metav1.APIGroup
}

var _ Helper = &helper{}

func NewHelper(discoveryClient discovery.DiscoveryInterface, logger logrus.FieldLogger) (Helper, error) {
	h := &helper{
		discoveryClient: discoveryClient,
	}
	if err := h.Refresh(); err != nil {
		return nil, err
	}
	return h, nil
}

func (h *helper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, metav1.APIResource, error) {
	h.lock.RLock()
	defer h.lock.RUnlock()

	gvr, err := h.mapper.ResourceFor(input)
	if err != nil {
		return schema.GroupVersionResource{}, metav1.APIResource{}, err
	}

	apiResource, found := h.resourcesMap[gvr]
	if !found {
		return schema.GroupVersionResource{}, metav1.APIResource{}, errors.Errorf("APIResource not found for GroupVersionResource %s", gvr)
	}

	return gvr, apiResource, nil
}

func (h *helper) Refresh() error {
	h.lock.Lock()
	defer h.lock.Unlock()

	groupResources, err := restmapper.GetAPIGroupResources(h.discoveryClient)
	if err != nil {
		return errors.WithStack(err)
	}
	mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
	shortcutExpander, err := kcmdutil.NewShortcutExpander(mapper, h.discoveryClient, h.logger)
	if err != nil {
		return errors.WithStack(err)
	}
	h.mapper = shortcutExpander

	preferredResources, err := refreshServerPreferredResources(h.discoveryClient, h.logger)
	if err != nil {
		return errors.WithStack(err)
	}

	h.resources = discovery.FilteredBy(
		discovery.ResourcePredicateFunc(filterByVerbs),
		preferredResources,
	)

	sortResources(h.resources)

	h.resourcesMap = make(map[schema.GroupVersionResource]metav1.APIResource)
	for _, resourceGroup := range h.resources {
		gv, err := schema.ParseGroupVersion(resourceGroup.GroupVersion)
		if err != nil {
			return errors.Wrapf(err, "unable to parse GroupVersion %s", resourceGroup.GroupVersion)
		}

		for _, resource := range resourceGroup.APIResources {
			gvr := gv.WithResource(resource.Name)
			h.resourcesMap[gvr] = resource
		}
	}

	apiGroupList, err := h.discoveryClient.ServerGroups()
	if err != nil {
		return errors.WithStack(err)
	}
	h.apiGroups = apiGroupList.Groups

	return nil
}

func refreshServerPreferredResources(discoveryClient serverResourcesInterface, logger logrus.FieldLogger) ([]*metav1.APIResourceList, error) {
	preferredResources, err := discoveryClient.ServerPreferredResources()
	if err != nil {
		if discoveryErr, ok := err.(*discovery.ErrGroupDiscoveryFailed); ok {
			for groupVersion, err := range discoveryErr.Groups {
				logger.WithError(err).Warnf("Failed to discover group: %v", groupVersion)
			}
			return preferredResources, nil
		}
	}
	return preferredResources, err
}

func filterByVerbs(groupVersion string, r *metav1.APIResource) bool {
	return discovery.SupportsAllVerbs{Verbs: []string{"list", "create", "get", "delete"}}.Match(groupVersion, r)
}

// sortResources sources resources by moving extensions to the end of the slice. The order of all
// the other resources is preserved.
func sortResources(resources []*metav1.APIResourceList) {
	sort.SliceStable(resources, func(i, j int) bool {
		left := resources[i]
		leftGV, _ := schema.ParseGroupVersion(left.GroupVersion)
		// not checking error because it should be impossible to fail to parse data coming from the
		// apiserver
		if leftGV.Group == "extensions" {
			// always sort extensions at the bottom by saying left is "greater"
			return false
		}

		right := resources[j]
		rightGV, _ := schema.ParseGroupVersion(right.GroupVersion)
		// not checking error because it should be impossible to fail to parse data coming from the
		// apiserver
		if rightGV.Group == "extensions" {
			// always sort extensions at the bottom by saying left is "less"
			return true
		}

		return i < j
	})
}

func (h *helper) Resources() []*metav1.APIResourceList {
	h.lock.RLock()
	defer h.lock.RUnlock()
	return h.resources
}

func (h *helper) APIGroups() []metav1.APIGroup {
	h.lock.RLock()
	defer h.lock.RUnlock()
	return h.apiGroups
}
<commit_msg>bug fix: set discovery helper's logger in constructor (#1399)<commit_after>/*
Copyright 2017 the Velero contributors.

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 discovery

import (
	"sort"
	"sync"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/discovery"
	"k8s.io/client-go/restmapper"

	kcmdutil "github.com/heptio/velero/third_party/kubernetes/pkg/kubectl/cmd/util"
)

// Helper exposes functions for interacting with the Kubernetes discovery
// API.
type Helper interface {
	// Resources gets the current set of resources retrieved from discovery
	// that are backuppable by Velero.
	Resources() []*metav1.APIResourceList

	// ResourceFor gets a fully-resolved GroupVersionResource and an
	// APIResource for the provided partially-specified GroupVersionResource.
	ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, metav1.APIResource, error)

	// Refresh pulls an updated set of Velero-backuppable resources from the
	// discovery API.
	Refresh() error

	// APIGroups gets the current set of supported APIGroups
	// in the cluster.
	APIGroups() []metav1.APIGroup
}

type serverResourcesInterface interface {
	ServerPreferredResources() ([]*metav1.APIResourceList, error)
}

type helper struct {
	discoveryClient discovery.DiscoveryInterface
	logger          logrus.FieldLogger

	// lock guards mapper, resources and resourcesMap
	lock         sync.RWMutex
	mapper       meta.RESTMapper
	resources    []*metav1.APIResourceList
	resourcesMap map[schema.GroupVersionResource]metav1.APIResource
	apiGroups    []metav1.APIGroup
}

var _ Helper = &helper{}

func NewHelper(discoveryClient discovery.DiscoveryInterface, logger logrus.FieldLogger) (Helper, error) {
	h := &helper{
		discoveryClient: discoveryClient,
		logger:          logger,
	}
	if err := h.Refresh(); err != nil {
		return nil, err
	}
	return h, nil
}

func (h *helper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, metav1.APIResource, error) {
	h.lock.RLock()
	defer h.lock.RUnlock()

	gvr, err := h.mapper.ResourceFor(input)
	if err != nil {
		return schema.GroupVersionResource{}, metav1.APIResource{}, err
	}

	apiResource, found := h.resourcesMap[gvr]
	if !found {
		return schema.GroupVersionResource{}, metav1.APIResource{}, errors.Errorf("APIResource not found for GroupVersionResource %s", gvr)
	}

	return gvr, apiResource, nil
}

func (h *helper) Refresh() error {
	h.lock.Lock()
	defer h.lock.Unlock()

	groupResources, err := restmapper.GetAPIGroupResources(h.discoveryClient)
	if err != nil {
		return errors.WithStack(err)
	}
	mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
	shortcutExpander, err := kcmdutil.NewShortcutExpander(mapper, h.discoveryClient, h.logger)
	if err != nil {
		return errors.WithStack(err)
	}
	h.mapper = shortcutExpander

	preferredResources, err := refreshServerPreferredResources(h.discoveryClient, h.logger)
	if err != nil {
		return errors.WithStack(err)
	}

	h.resources = discovery.FilteredBy(
		discovery.ResourcePredicateFunc(filterByVerbs),
		preferredResources,
	)

	sortResources(h.resources)

	h.resourcesMap = make(map[schema.GroupVersionResource]metav1.APIResource)
	for _, resourceGroup := range h.resources {
		gv, err := schema.ParseGroupVersion(resourceGroup.GroupVersion)
		if err != nil {
			return errors.Wrapf(err, "unable to parse GroupVersion %s", resourceGroup.GroupVersion)
		}

		for _, resource := range resourceGroup.APIResources {
			gvr := gv.WithResource(resource.Name)
			h.resourcesMap[gvr] = resource
		}
	}

	apiGroupList, err := h.discoveryClient.ServerGroups()
	if err != nil {
		return errors.WithStack(err)
	}
	h.apiGroups = apiGroupList.Groups

	return nil
}

func refreshServerPreferredResources(discoveryClient serverResourcesInterface, logger logrus.FieldLogger) ([]*metav1.APIResourceList, error) {
	preferredResources, err := discoveryClient.ServerPreferredResources()
	if err != nil {
		if discoveryErr, ok := err.(*discovery.ErrGroupDiscoveryFailed); ok {
			for groupVersion, err := range discoveryErr.Groups {
				logger.WithError(err).Warnf("Failed to discover group: %v", groupVersion)
			}
			return preferredResources, nil
		}
	}
	return preferredResources, err
}

func filterByVerbs(groupVersion string, r *metav1.APIResource) bool {
	return discovery.SupportsAllVerbs{Verbs: []string{"list", "create", "get", "delete"}}.Match(groupVersion, r)
}

// sortResources sources resources by moving extensions to the end of the slice. The order of all
// the other resources is preserved.
func sortResources(resources []*metav1.APIResourceList) {
	sort.SliceStable(resources, func(i, j int) bool {
		left := resources[i]
		leftGV, _ := schema.ParseGroupVersion(left.GroupVersion)
		// not checking error because it should be impossible to fail to parse data coming from the
		// apiserver
		if leftGV.Group == "extensions" {
			// always sort extensions at the bottom by saying left is "greater"
			return false
		}

		right := resources[j]
		rightGV, _ := schema.ParseGroupVersion(right.GroupVersion)
		// not checking error because it should be impossible to fail to parse data coming from the
		// apiserver
		if rightGV.Group == "extensions" {
			// always sort extensions at the bottom by saying left is "less"
			return true
		}

		return i < j
	})
}

func (h *helper) Resources() []*metav1.APIResourceList {
	h.lock.RLock()
	defer h.lock.RUnlock()
	return h.resources
}

func (h *helper) APIGroups() []metav1.APIGroup {
	h.lock.RLock()
	defer h.lock.RUnlock()
	return h.apiGroups
}
<|endoftext|>


<commit_before>package top

import (
	"io"

	"github.com/spf13/cobra"

	kcmd "k8s.io/kubernetes/pkg/kubectl/cmd"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"

	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
)

const TopRecommendedName = "top"

var topLong = templates.LongDesc(`
	Show usage statistics of resources on the server

	This command analyzes resources managed by the platform and presents current
	usage statistics.`)

func NewCommandTop(name, fullName string, f *clientcmd.Factory, out, errOut io.Writer) *cobra.Command {
	// Parent command to which all subcommands are added.
	cmds := &cobra.Command{
		Use:   name,
		Short: "Show usage statistics of resources on the server",
		Long:  topLong,
		Run:   cmdutil.DefaultSubCommandRun(errOut),
	}

	cmds.AddCommand(NewCmdTopImages(f, fullName, TopImagesRecommendedName, out))
	cmds.AddCommand(NewCmdTopImageStreams(f, fullName, TopImageStreamsRecommendedName, out))
	cmdTopNode := kcmd.NewCmdTopNode(f, out)
	cmdTopNode.Long = templates.LongDesc(cmdTopNode.Long)
	cmdTopNode.Example = templates.Examples(cmdTopNode.Example)
	cmdTopPod := kcmd.NewCmdTopPod(f, out)
	cmdTopPod.Long = templates.LongDesc(cmdTopPod.Long)
	cmdTopPod.Example = templates.Examples(cmdTopPod.Example)
	cmds.AddCommand(cmdTopNode)
	cmds.AddCommand(cmdTopPod)
	return cmds
}
<commit_msg>set openshift default metrics opts<commit_after>package top

import (
	"io"

	"github.com/spf13/cobra"

	kcmd "k8s.io/kubernetes/pkg/kubectl/cmd"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"

	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
)

const (
	TopRecommendedName = "top"

	DefaultHeapsterNamespace = "openshift-infra"
	DefaultHeapsterScheme    = "https"
	DefaultHeapsterService   = "heapster"
)

var topLong = templates.LongDesc(`
	Show usage statistics of resources on the server

	This command analyzes resources managed by the platform and presents current
	usage statistics.`)

func NewCommandTop(name, fullName string, f *clientcmd.Factory, out, errOut io.Writer) *cobra.Command {
	// Parent command to which all subcommands are added.
	cmds := &cobra.Command{
		Use:   name,
		Short: "Show usage statistics of resources on the server",
		Long:  topLong,
		Run:   cmdutil.DefaultSubCommandRun(errOut),
	}

	ocHeapsterTopOpts := kcmd.HeapsterTopOptions{
		Namespace: DefaultHeapsterNamespace,
		Scheme:    DefaultHeapsterScheme,
		Service:   DefaultHeapsterService,
	}

	cmdTopNodeOpts := &kcmd.TopNodeOptions{
		HeapsterOptions: ocHeapsterTopOpts,
	}
	cmdTopNode := kcmd.NewCmdTopNode(f, cmdTopNodeOpts, out)

	cmdTopPodOpts := &kcmd.TopPodOptions{
		HeapsterOptions: ocHeapsterTopOpts,
	}
	cmdTopPod := kcmd.NewCmdTopPod(f, cmdTopPodOpts, out)

	cmds.AddCommand(NewCmdTopImages(f, fullName, TopImagesRecommendedName, out))
	cmds.AddCommand(NewCmdTopImageStreams(f, fullName, TopImageStreamsRecommendedName, out))
	cmdTopNode.Long = templates.LongDesc(cmdTopNode.Long)
	cmdTopNode.Example = templates.Examples(cmdTopNode.Example)
	cmdTopPod.Long = templates.LongDesc(cmdTopPod.Long)
	cmdTopPod.Example = templates.Examples(cmdTopPod.Example)
	cmds.AddCommand(cmdTopNode)
	cmds.AddCommand(cmdTopPod)
	return cmds
}
<|endoftext|>


<commit_before>// Copyright 2016 CodisLabs. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package proxy

import (
	"testing"

	"github.com/CodisLabs/codis/pkg/models"
	"github.com/CodisLabs/codis/pkg/utils/assert"
	"github.com/CodisLabs/codis/pkg/utils/log"
)

var config = newProxyConfig()

func init() {
	log.SetLevel(log.LevelError)
}

func newProxyConfig() *Config {
	config := NewDefaultConfig()
	config.ProxyAddr = "0.0.0.0:0"
	config.AdminAddr = "0.0.0.0:0"
	config.ProxyHeapPlaceholder = 0
	config.ProxyMaxOffheapBytes = 0
	return config
}

func openProxy() (*Proxy, string) {
	s, err := New(config)
	assert.MustNoError(err)
	return s, s.Model().AdminAddr
}

func TestModel(x *testing.T) {
	s, addr := openProxy()
	defer s.Close()

	var c = NewApiClient(addr)

	p, err := c.Model()
	assert.MustNoError(err)
	assert.Must(p.Token == s.Model().Token)
	assert.Must(p.ProductName == config.ProductName)
}

func TestStats(x *testing.T) {
	s, addr := openProxy()
	defer s.Close()

	var c = NewApiClient(addr)

	c.SetXAuth(config.ProductName, config.ProductAuth, "")
	_, err1 := c.Stats()
	assert.Must(err1 != nil)

	c.SetXAuth(config.ProductName, config.ProductAuth, s.Model().Token)
	_, err2 := c.Stats()
	assert.MustNoError(err2)
}

func verifySlots(c *ApiClient, expect map[int]*models.Slot) {
	slots, err := c.Slots()
	assert.MustNoError(err)

	assert.Must(len(slots) == models.MaxSlotNum)

	for i, slot := range expect {
		if slot != nil {
			assert.Must(slots[i].Id == i)
			assert.Must(slot.Locked == slots[i].Locked)
			assert.Must(slot.BackendAddr == slots[i].BackendAddr)
			assert.Must(slot.MigrateFrom == slots[i].MigrateFrom)
		}
	}
}

func TestFillSlot(x *testing.T) {
	s, addr := openProxy()
	defer s.Close()

	var c = NewApiClient(addr)
	c.SetXAuth(config.ProductName, config.ProductAuth, s.Model().Token)

	expect := make(map[int]*models.Slot)

	for i := 0; i < 16; i++ {
		slot := &models.Slot{
			Id:          i,
			Locked:      i%2 == 0,
			BackendAddr: "x.x.x.x:xxxx",
		}
		assert.MustNoError(c.FillSlots(slot))
		expect[i] = slot
	}
	verifySlots(c, expect)

	slots := []*models.Slot{}
	for i := 0; i < 16; i++ {
		slot := &models.Slot{
			Id:          i,
			Locked:      i%2 != 0,
			BackendAddr: "y.y.y.y:yyyy",
			MigrateFrom: "x.x.x.x:xxxx",
		}
		slots = append(slots, slot)
		expect[i] = slot
	}
	assert.MustNoError(c.FillSlots(slots...))
	verifySlots(c, expect)
}

func TestStartAndShutdown(x *testing.T) {
	s, addr := openProxy()
	defer s.Close()

	var c = NewApiClient(addr)
	c.SetXAuth(config.ProductName, config.ProductAuth, s.Model().Token)

	expect := make(map[int]*models.Slot)

	for i := 0; i < 16; i++ {
		slot := &models.Slot{
			Id:          i,
			BackendAddr: "x.x.x.x:xxxx",
		}
		assert.MustNoError(c.FillSlots(slot))
		expect[i] = slot
	}
	verifySlots(c, expect)

	err1 := c.Start()
	assert.MustNoError(err1)

	err2 := c.Shutdown()
	assert.MustNoError(err2)

	err3 := c.Start()
	assert.Must(err3 != nil)
}
<commit_msg>proxy: fix gotest<commit_after>// Copyright 2016 CodisLabs. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package proxy

import (
	"testing"

	"github.com/CodisLabs/codis/pkg/models"
	"github.com/CodisLabs/codis/pkg/utils/assert"
	"github.com/CodisLabs/codis/pkg/utils/log"
)

var config = newProxyConfig()

func init() {
	log.SetLevel(log.LevelError)
}

func newProxyConfig() *Config {
	config := NewDefaultConfig()
	config.ProxyAddr = "0.0.0.0:0"
	config.AdminAddr = "0.0.0.0:0"
	config.ProxyHeapPlaceholder = 0
	config.ProxyMaxOffheapBytes = 0
	return config
}

func openProxy() (*Proxy, string) {
	s, err := New(config)
	assert.MustNoError(err)
	return s, s.Model().AdminAddr
}

func TestModel(x *testing.T) {
	s, addr := openProxy()
	defer s.Close()

	var c = NewApiClient(addr)

	p, err := c.Model()
	assert.MustNoError(err)
	assert.Must(p.Token == s.Model().Token)
	assert.Must(p.ProductName == config.ProductName)
}

func TestStats(x *testing.T) {
	s, addr := openProxy()
	defer s.Close()

	var c = NewApiClient(addr)

	c.SetXAuth(config.ProductName, config.ProductAuth, "")
	_, err1 := c.StatsSimple()
	assert.Must(err1 != nil)

	c.SetXAuth(config.ProductName, config.ProductAuth, s.Model().Token)
	_, err2 := c.Stats(0)
	assert.MustNoError(err2)
}

func verifySlots(c *ApiClient, expect map[int]*models.Slot) {
	slots, err := c.Slots()
	assert.MustNoError(err)

	assert.Must(len(slots) == models.MaxSlotNum)

	for i, slot := range expect {
		if slot != nil {
			assert.Must(slots[i].Id == i)
			assert.Must(slot.Locked == slots[i].Locked)
			assert.Must(slot.BackendAddr == slots[i].BackendAddr)
			assert.Must(slot.MigrateFrom == slots[i].MigrateFrom)
		}
	}
}

func TestFillSlot(x *testing.T) {
	s, addr := openProxy()
	defer s.Close()

	var c = NewApiClient(addr)
	c.SetXAuth(config.ProductName, config.ProductAuth, s.Model().Token)

	expect := make(map[int]*models.Slot)

	for i := 0; i < 16; i++ {
		slot := &models.Slot{
			Id:          i,
			Locked:      i%2 == 0,
			BackendAddr: "x.x.x.x:xxxx",
		}
		assert.MustNoError(c.FillSlots(slot))
		expect[i] = slot
	}
	verifySlots(c, expect)

	slots := []*models.Slot{}
	for i := 0; i < 16; i++ {
		slot := &models.Slot{
			Id:          i,
			Locked:      i%2 != 0,
			BackendAddr: "y.y.y.y:yyyy",
			MigrateFrom: "x.x.x.x:xxxx",
		}
		slots = append(slots, slot)
		expect[i] = slot
	}
	assert.MustNoError(c.FillSlots(slots...))
	verifySlots(c, expect)
}

func TestStartAndShutdown(x *testing.T) {
	s, addr := openProxy()
	defer s.Close()

	var c = NewApiClient(addr)
	c.SetXAuth(config.ProductName, config.ProductAuth, s.Model().Token)

	expect := make(map[int]*models.Slot)

	for i := 0; i < 16; i++ {
		slot := &models.Slot{
			Id:          i,
			BackendAddr: "x.x.x.x:xxxx",
		}
		assert.MustNoError(c.FillSlots(slot))
		expect[i] = slot
	}
	verifySlots(c, expect)

	err1 := c.Start()
	assert.MustNoError(err1)

	err2 := c.Shutdown()
	assert.MustNoError(err2)

	err3 := c.Start()
	assert.Must(err3 != nil)
}
<|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.
*/

package proxy

import (
	"errors"
	"math/rand"
	"net"
	"reflect"
	"sort"
	"strconv"
	"sync"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/golang/glog"
)

var (
	ErrMissingServiceEntry = errors.New("missing service entry")
	ErrMissingEndpoints    = errors.New("missing endpoints")
)

type sessionAffinityDetail struct {
	clientIPAddress string
	//clientProtocol  api.Protocol //not yet used
	//sessionCookie   string       //not yet used
	endpoint     string
	lastUsedDTTM time.Time
}

type serviceDetail struct {
	name                string
	sessionAffinityType api.AffinityType
	sessionAffinityMap  map[string]*sessionAffinityDetail
	stickyMaxAgeMinutes int
}

// LoadBalancerRR is a round-robin load balancer.
type LoadBalancerRR struct {
	lock          sync.RWMutex
	endpointsMap  map[string][]string
	rrIndex       map[string]int
	serviceDtlMap map[string]serviceDetail
}

func newServiceDetail(service string, sessionAffinityType api.AffinityType, stickyMaxAgeMinutes int) *serviceDetail {
	return &serviceDetail{
		name:                service,
		sessionAffinityType: sessionAffinityType,
		sessionAffinityMap:  make(map[string]*sessionAffinityDetail),
		stickyMaxAgeMinutes: stickyMaxAgeMinutes,
	}
}

// NewLoadBalancerRR returns a new LoadBalancerRR.
func NewLoadBalancerRR() *LoadBalancerRR {
	return &LoadBalancerRR{
		endpointsMap:  make(map[string][]string),
		rrIndex:       make(map[string]int),
		serviceDtlMap: make(map[string]serviceDetail),
	}
}

func (lb *LoadBalancerRR) NewService(service string, sessionAffinityType api.AffinityType, stickyMaxAgeMinutes int) error {
	if stickyMaxAgeMinutes == 0 {
		stickyMaxAgeMinutes = 180 //default to 3 hours if not specified.  Should 0 be unlimeted instead????
	}
	if _, exists := lb.serviceDtlMap[service]; !exists {
		lb.serviceDtlMap[service] = *newServiceDetail(service, sessionAffinityType, stickyMaxAgeMinutes)
		glog.V(4).Infof("NewService.  Service does not exist.  So I created it: %+v", lb.serviceDtlMap[service])
	}
	return nil
}

// return true if this service detail is using some form of session affinity.
func isSessionAffinity(serviceDtl serviceDetail) bool {
	//Should never be empty string, but chekcing for it to be safe.
	if serviceDtl.sessionAffinityType == "" || serviceDtl.sessionAffinityType == api.AffinityTypeNone {
		return false
	}
	return true
}

// NextEndpoint returns a service endpoint.
// The service endpoint is chosen using the round-robin algorithm.
func (lb *LoadBalancerRR) NextEndpoint(service string, srcAddr net.Addr) (string, error) {
	var ipaddr string
	glog.V(4).Infof("NextEndpoint.  service: %s.  srcAddr: %+v. Endpoints: %+v", service, srcAddr, lb.endpointsMap)

	lb.lock.RLock()
	serviceDtls, exists := lb.serviceDtlMap[service]
	endpoints, _ := lb.endpointsMap[service]
	index := lb.rrIndex[service]
	sessionAffinityEnabled := isSessionAffinity(serviceDtls)

	lb.lock.RUnlock()
	if !exists {
		return "", ErrMissingServiceEntry
	}
	if len(endpoints) == 0 {
		return "", ErrMissingEndpoints
	}
	if sessionAffinityEnabled {
		if _, _, err := net.SplitHostPort(srcAddr.String()); err == nil {
			ipaddr, _, _ = net.SplitHostPort(srcAddr.String())
		}
		sessionAffinity, exists := serviceDtls.sessionAffinityMap[ipaddr]
		glog.V(4).Infof("NextEndpoint.  Key: %s. sessionAffinity: %+v", ipaddr, sessionAffinity)
		if exists && int(time.Now().Sub(sessionAffinity.lastUsedDTTM).Minutes()) < serviceDtls.stickyMaxAgeMinutes {
			endpoint := sessionAffinity.endpoint
			sessionAffinity.lastUsedDTTM = time.Now()
			glog.V(4).Infof("NextEndpoint.  Key: %s. sessionAffinity: %+v", ipaddr, sessionAffinity)
			return endpoint, nil
		}
	}
	endpoint := endpoints[index]
	lb.lock.Lock()
	lb.rrIndex[service] = (index + 1) % len(endpoints)

	if sessionAffinityEnabled {
		var affinity *sessionAffinityDetail
		affinity, _ = lb.serviceDtlMap[service].sessionAffinityMap[ipaddr]
		if affinity == nil {
			affinity = new(sessionAffinityDetail) //&sessionAffinityDetail{ipaddr, "TCP", "", endpoint, time.Now()}
			lb.serviceDtlMap[service].sessionAffinityMap[ipaddr] = affinity
		}
		affinity.lastUsedDTTM = time.Now()
		affinity.endpoint = endpoint
		affinity.clientIPAddress = ipaddr

		glog.V(4).Infof("NextEndpoint. New Affinity key %s: %+v", ipaddr, lb.serviceDtlMap[service].sessionAffinityMap[ipaddr])
	}

	lb.lock.Unlock()
	return endpoint, nil
}

func isValidEndpoint(spec string) bool {
	_, port, err := net.SplitHostPort(spec)
	if err != nil {
		return false
	}
	value, err := strconv.Atoi(port)
	if err != nil {
		return false
	}
	return value > 0
}

func filterValidEndpoints(endpoints []string) []string {
	var result []string
	for _, spec := range endpoints {
		if isValidEndpoint(spec) {
			result = append(result, spec)
		}
	}
	return result
}

func shuffleEndpoints(endpoints []string) []string {
	shuffled := make([]string, len(endpoints))
	perm := rand.Perm(len(endpoints))
	for i, v := range perm {
		shuffled[v] = endpoints[i]
	}
	return shuffled
}

//remove any session affinity records associated to a particular endpoint (for example when a pod goes down).
func removeSessionAffinityByEndpoint(lb *LoadBalancerRR, service string, endpoint string) {
	for _, affinityDetail := range lb.serviceDtlMap[service].sessionAffinityMap {
		if affinityDetail.endpoint == endpoint {
			glog.V(4).Infof("Removing client: %s from sessionAffinityMap for service: %s", affinityDetail.endpoint, service)
			delete(lb.serviceDtlMap[service].sessionAffinityMap, affinityDetail.clientIPAddress)
		}
	}
}

//Loop through the valid endpoints and then the endpoints associated with the Load Balancer.
// 	Then remove any session affinity records that are not in both lists.
func updateServiceDetailMap(lb *LoadBalancerRR, service string, validEndpoints []string) {
	allEndpoints := map[string]int{}
	for _, validEndpoint := range validEndpoints {
		allEndpoints[validEndpoint] = 1
	}
	for _, existingEndpoint := range lb.endpointsMap[service] {
		allEndpoints[existingEndpoint] = allEndpoints[existingEndpoint] + 1
	}
	for mKey, mVal := range allEndpoints {
		if mVal == 1 {
			glog.V(3).Infof("Delete endpoint %s for service: %s", mKey, service)
			removeSessionAffinityByEndpoint(lb, service, mKey)
			delete(lb.serviceDtlMap[service].sessionAffinityMap, mKey)
		}
	}
}

// OnUpdate manages the registered service endpoints.
// Registered endpoints are updated if found in the update set or
// unregistered if missing from the update set.
func (lb *LoadBalancerRR) OnUpdate(endpoints []api.Endpoints) {
	registeredEndpoints := make(map[string]bool)
	lb.lock.Lock()
	defer lb.lock.Unlock()
	// Update endpoints for services.
	for _, endpoint := range endpoints {
		existingEndpoints, exists := lb.endpointsMap[endpoint.Name]
		validEndpoints := filterValidEndpoints(endpoint.Endpoints)
		// Need to compare sorted endpoints here, since they are shuffled below
		// before being put into endpointsMap
		sort.Strings(existingEndpoints)
		sort.Strings(validEndpoints)
		if !exists || !reflect.DeepEqual(existingEndpoints, validEndpoints) {
			glog.V(3).Infof("LoadBalancerRR: Setting endpoints for %s to %+v", endpoint.Name, endpoint.Endpoints)
			updateServiceDetailMap(lb, endpoint.Name, validEndpoints)
			// On update can be called without NewService being called externally.
			// to be safe we will call it here.  A new service will only be created
			// if one does not already exist.
			lb.NewService(endpoint.Name, api.AffinityTypeNone, 0)
			lb.endpointsMap[endpoint.Name] = shuffleEndpoints(validEndpoints)

			// Reset the round-robin index.
			lb.rrIndex[endpoint.Name] = 0
		}
		registeredEndpoints[endpoint.Name] = true
	}
	// Remove endpoints missing from the update.
	for k, v := range lb.endpointsMap {
		if _, exists := registeredEndpoints[k]; !exists {
			glog.V(3).Infof("LoadBalancerRR: Removing endpoints for %s -> %+v", k, v)
			delete(lb.endpointsMap, k)
			delete(lb.serviceDtlMap, k)
		}
	}
}

func (lb *LoadBalancerRR) CleanupStaleStickySessions(service string) {
	stickyMaxAgeMinutes := lb.serviceDtlMap[service].stickyMaxAgeMinutes
	for key, affinityDetail := range lb.serviceDtlMap[service].sessionAffinityMap {
		if int(time.Now().Sub(affinityDetail.lastUsedDTTM).Minutes()) >= stickyMaxAgeMinutes {
			glog.V(4).Infof("Removing client: %s from sessionAffinityMap for service: %s.  Last used is greater than %d minutes....", affinityDetail.clientIPAddress, service, stickyMaxAgeMinutes)
			delete(lb.serviceDtlMap[service].sessionAffinityMap, key)
		}
	}
}
<commit_msg>Copies endpoint slices before any sorting<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.
*/

package proxy

import (
	"errors"
	"math/rand"
	"net"
	"reflect"
	"sort"
	"strconv"
	"sync"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/golang/glog"
)

var (
	ErrMissingServiceEntry = errors.New("missing service entry")
	ErrMissingEndpoints    = errors.New("missing endpoints")
)

type sessionAffinityDetail struct {
	clientIPAddress string
	//clientProtocol  api.Protocol //not yet used
	//sessionCookie   string       //not yet used
	endpoint     string
	lastUsedDTTM time.Time
}

type serviceDetail struct {
	name                string
	sessionAffinityType api.AffinityType
	sessionAffinityMap  map[string]*sessionAffinityDetail
	stickyMaxAgeMinutes int
}

// LoadBalancerRR is a round-robin load balancer.
type LoadBalancerRR struct {
	lock          sync.RWMutex
	endpointsMap  map[string][]string
	rrIndex       map[string]int
	serviceDtlMap map[string]serviceDetail
}

func newServiceDetail(service string, sessionAffinityType api.AffinityType, stickyMaxAgeMinutes int) *serviceDetail {
	return &serviceDetail{
		name:                service,
		sessionAffinityType: sessionAffinityType,
		sessionAffinityMap:  make(map[string]*sessionAffinityDetail),
		stickyMaxAgeMinutes: stickyMaxAgeMinutes,
	}
}

// NewLoadBalancerRR returns a new LoadBalancerRR.
func NewLoadBalancerRR() *LoadBalancerRR {
	return &LoadBalancerRR{
		endpointsMap:  make(map[string][]string),
		rrIndex:       make(map[string]int),
		serviceDtlMap: make(map[string]serviceDetail),
	}
}

func (lb *LoadBalancerRR) NewService(service string, sessionAffinityType api.AffinityType, stickyMaxAgeMinutes int) error {
	if stickyMaxAgeMinutes == 0 {
		stickyMaxAgeMinutes = 180 //default to 3 hours if not specified.  Should 0 be unlimeted instead????
	}
	if _, exists := lb.serviceDtlMap[service]; !exists {
		lb.serviceDtlMap[service] = *newServiceDetail(service, sessionAffinityType, stickyMaxAgeMinutes)
		glog.V(4).Infof("NewService.  Service does not exist.  So I created it: %+v", lb.serviceDtlMap[service])
	}
	return nil
}

// return true if this service detail is using some form of session affinity.
func isSessionAffinity(serviceDtl serviceDetail) bool {
	//Should never be empty string, but chekcing for it to be safe.
	if serviceDtl.sessionAffinityType == "" || serviceDtl.sessionAffinityType == api.AffinityTypeNone {
		return false
	}
	return true
}

// NextEndpoint returns a service endpoint.
// The service endpoint is chosen using the round-robin algorithm.
func (lb *LoadBalancerRR) NextEndpoint(service string, srcAddr net.Addr) (string, error) {
	var ipaddr string
	glog.V(4).Infof("NextEndpoint.  service: %s.  srcAddr: %+v. Endpoints: %+v", service, srcAddr, lb.endpointsMap)

	lb.lock.RLock()
	serviceDtls, exists := lb.serviceDtlMap[service]
	endpoints, _ := lb.endpointsMap[service]
	index := lb.rrIndex[service]
	sessionAffinityEnabled := isSessionAffinity(serviceDtls)

	lb.lock.RUnlock()
	if !exists {
		return "", ErrMissingServiceEntry
	}
	if len(endpoints) == 0 {
		return "", ErrMissingEndpoints
	}
	if sessionAffinityEnabled {
		if _, _, err := net.SplitHostPort(srcAddr.String()); err == nil {
			ipaddr, _, _ = net.SplitHostPort(srcAddr.String())
		}
		sessionAffinity, exists := serviceDtls.sessionAffinityMap[ipaddr]
		glog.V(4).Infof("NextEndpoint.  Key: %s. sessionAffinity: %+v", ipaddr, sessionAffinity)
		if exists && int(time.Now().Sub(sessionAffinity.lastUsedDTTM).Minutes()) < serviceDtls.stickyMaxAgeMinutes {
			endpoint := sessionAffinity.endpoint
			sessionAffinity.lastUsedDTTM = time.Now()
			glog.V(4).Infof("NextEndpoint.  Key: %s. sessionAffinity: %+v", ipaddr, sessionAffinity)
			return endpoint, nil
		}
	}
	endpoint := endpoints[index]
	lb.lock.Lock()
	lb.rrIndex[service] = (index + 1) % len(endpoints)

	if sessionAffinityEnabled {
		var affinity *sessionAffinityDetail
		affinity, _ = lb.serviceDtlMap[service].sessionAffinityMap[ipaddr]
		if affinity == nil {
			affinity = new(sessionAffinityDetail) //&sessionAffinityDetail{ipaddr, "TCP", "", endpoint, time.Now()}
			lb.serviceDtlMap[service].sessionAffinityMap[ipaddr] = affinity
		}
		affinity.lastUsedDTTM = time.Now()
		affinity.endpoint = endpoint
		affinity.clientIPAddress = ipaddr

		glog.V(4).Infof("NextEndpoint. New Affinity key %s: %+v", ipaddr, lb.serviceDtlMap[service].sessionAffinityMap[ipaddr])
	}

	lb.lock.Unlock()
	return endpoint, nil
}

func isValidEndpoint(spec string) bool {
	_, port, err := net.SplitHostPort(spec)
	if err != nil {
		return false
	}
	value, err := strconv.Atoi(port)
	if err != nil {
		return false
	}
	return value > 0
}

func filterValidEndpoints(endpoints []string) []string {
	var result []string
	for _, spec := range endpoints {
		if isValidEndpoint(spec) {
			result = append(result, spec)
		}
	}
	return result
}

func endpointsAreEqual(left, right []string) bool {
	if len(left) != len(right) {
		return false
	}

	leftSorted := make([]string, len(left))
	copy(leftSorted, left)
	sort.Strings(leftSorted)
	rightSorted := make([]string, len(right))
	copy(rightSorted, right)
	sort.Strings(rightSorted)

	return reflect.DeepEqual(leftSorted, rightSorted)
}

func shuffleEndpoints(endpoints []string) []string {
	shuffled := make([]string, len(endpoints))
	perm := rand.Perm(len(endpoints))
	for i, v := range perm {
		shuffled[v] = endpoints[i]
	}
	return shuffled
}

//remove any session affinity records associated to a particular endpoint (for example when a pod goes down).
func removeSessionAffinityByEndpoint(lb *LoadBalancerRR, service string, endpoint string) {
	for _, affinityDetail := range lb.serviceDtlMap[service].sessionAffinityMap {
		if affinityDetail.endpoint == endpoint {
			glog.V(4).Infof("Removing client: %s from sessionAffinityMap for service: %s", affinityDetail.endpoint, service)
			delete(lb.serviceDtlMap[service].sessionAffinityMap, affinityDetail.clientIPAddress)
		}
	}
}

//Loop through the valid endpoints and then the endpoints associated with the Load Balancer.
// 	Then remove any session affinity records that are not in both lists.
func updateServiceDetailMap(lb *LoadBalancerRR, service string, validEndpoints []string) {
	allEndpoints := map[string]int{}
	for _, validEndpoint := range validEndpoints {
		allEndpoints[validEndpoint] = 1
	}
	for _, existingEndpoint := range lb.endpointsMap[service] {
		allEndpoints[existingEndpoint] = allEndpoints[existingEndpoint] + 1
	}
	for mKey, mVal := range allEndpoints {
		if mVal == 1 {
			glog.V(3).Infof("Delete endpoint %s for service: %s", mKey, service)
			removeSessionAffinityByEndpoint(lb, service, mKey)
			delete(lb.serviceDtlMap[service].sessionAffinityMap, mKey)
		}
	}
}

// OnUpdate manages the registered service endpoints.
// Registered endpoints are updated if found in the update set or
// unregistered if missing from the update set.
func (lb *LoadBalancerRR) OnUpdate(endpoints []api.Endpoints) {
	registeredEndpoints := make(map[string]bool)
	lb.lock.Lock()
	defer lb.lock.Unlock()
	// Update endpoints for services.
	for _, endpoint := range endpoints {
		existingEndpoints, exists := lb.endpointsMap[endpoint.Name]
		validEndpoints := filterValidEndpoints(endpoint.Endpoints)
		if !exists || !endpointsAreEqual(existingEndpoints, validEndpoints) {
			glog.V(3).Infof("LoadBalancerRR: Setting endpoints for %s to %+v", endpoint.Name, endpoint.Endpoints)
			updateServiceDetailMap(lb, endpoint.Name, validEndpoints)
			// On update can be called without NewService being called externally.
			// to be safe we will call it here.  A new service will only be created
			// if one does not already exist.
			lb.NewService(endpoint.Name, api.AffinityTypeNone, 0)
			lb.endpointsMap[endpoint.Name] = shuffleEndpoints(validEndpoints)

			// Reset the round-robin index.
			lb.rrIndex[endpoint.Name] = 0
		}
		registeredEndpoints[endpoint.Name] = true
	}
	// Remove endpoints missing from the update.
	for k, v := range lb.endpointsMap {
		if _, exists := registeredEndpoints[k]; !exists {
			glog.V(3).Infof("LoadBalancerRR: Removing endpoints for %s -> %+v", k, v)
			delete(lb.endpointsMap, k)
			delete(lb.serviceDtlMap, k)
		}
	}
}

func (lb *LoadBalancerRR) CleanupStaleStickySessions(service string) {
	stickyMaxAgeMinutes := lb.serviceDtlMap[service].stickyMaxAgeMinutes
	for key, affinityDetail := range lb.serviceDtlMap[service].sessionAffinityMap {
		if int(time.Now().Sub(affinityDetail.lastUsedDTTM).Minutes()) >= stickyMaxAgeMinutes {
			glog.V(4).Infof("Removing client: %s from sessionAffinityMap for service: %s.  Last used is greater than %d minutes....", affinityDetail.clientIPAddress, service, stickyMaxAgeMinutes)
			delete(lb.serviceDtlMap[service].sessionAffinityMap, key)
		}
	}
}
<|endoftext|>


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

package fs

import (
	"sort"

	"gvisor.googlesource.com/gvisor/pkg/sentry/device"
)

// DentAttr is the metadata of a directory entry. It is a subset of StableAttr.
//
// +stateify savable
type DentAttr struct {
	// Type is the InodeType of an Inode.
	Type InodeType

	// InodeID uniquely identifies an Inode on a device.
	InodeID uint64
}

// GenericDentAttr returns a generic DentAttr where:
//
// Type == nt
// InodeID == the inode id of a new inode on device.
func GenericDentAttr(nt InodeType, device *device.Device) DentAttr {
	return DentAttr{
		Type:    nt,
		InodeID: device.NextIno(),
	}
}

// DentrySerializer serializes a directory entry.
type DentrySerializer interface {
	// CopyOut serializes a directory entry based on its name and attributes.
	CopyOut(name string, attributes DentAttr) error

	// Written returns the number of bytes written.
	Written() int
}

// CollectEntriesSerializer copies DentAttrs to Entries. The order in
// which entries are encountered is preserved in Order.
type CollectEntriesSerializer struct {
	Entries map[string]DentAttr
	Order   []string
}

// CopyOut implements DentrySerializer.CopyOut.
func (c *CollectEntriesSerializer) CopyOut(name string, attr DentAttr) error {
	if c.Entries == nil {
		c.Entries = make(map[string]DentAttr)
	}
	c.Entries[name] = attr
	c.Order = append(c.Order, name)
	return nil
}

// Written implements DentrySerializer.Written.
func (c *CollectEntriesSerializer) Written() int {
	return len(c.Entries)
}

// DirCtx is used by node.Readdir to emit directory entries.  It is not
// thread-safe.
type DirCtx struct {
	// Serializer is used to serialize the node attributes.
	Serializer DentrySerializer

	// attrs are DentAttrs
	attrs map[string]DentAttr

	// DirCursor is the directory cursor.
	// TODO(b/67778717): Once Handles are removed this can just live in the
	// respective FileOperations implementations and not need to get
	// plumbed everywhere.
	DirCursor *string
}

// DirEmit is called for each directory entry.
func (c *DirCtx) DirEmit(name string, attr DentAttr) error {
	if c.Serializer != nil {
		if err := c.Serializer.CopyOut(name, attr); err != nil {
			return err
		}
	}
	if c.attrs == nil {
		c.attrs = make(map[string]DentAttr)
	}
	c.attrs[name] = attr
	return nil
}

// DentAttrs returns a map of DentAttrs corresponding to the emitted directory
// entries.
func (c *DirCtx) DentAttrs() map[string]DentAttr {
	if c.attrs == nil {
		c.attrs = make(map[string]DentAttr)
	}
	return c.attrs
}

// GenericReaddir serializes DentAttrs based on a SortedDentryMap that must
// contain _all_ up-to-date DentAttrs under a directory. If ctx.DirCursor is
// not nil, it is updated to the name of the last DentAttr that was
// successfully serialized.
//
// Returns the number of entries serialized.
func GenericReaddir(ctx *DirCtx, s *SortedDentryMap) (int, error) {
	// Retrieve the next directory entries.
	var names []string
	var entries map[string]DentAttr
	if ctx.DirCursor != nil {
		names, entries = s.GetNext(*ctx.DirCursor)
	} else {
		names, entries = s.GetAll()
	}

	// Try to serialize each entry.
	var serialized int
	for _, name := range names {
		// Skip "" per POSIX. Skip "." and ".." which will be added by Dirent.Readdir.
		if name == "" || name == "." || name == ".." {
			continue
		}

		// Emit the directory entry.
		if err := ctx.DirEmit(name, entries[name]); err != nil {
			// Return potentially a partial serialized count.
			return serialized, err
		}

		// We successfully serialized this entry.
		serialized++

		// Update the cursor with the name of the entry last serialized.
		if ctx.DirCursor != nil {
			*ctx.DirCursor = name
		}
	}

	// Everything was serialized.
	return serialized, nil
}

// SortedDentryMap is a sorted map of names and fs.DentAttr entries.
//
// +stateify savable
type SortedDentryMap struct {
	// names is always kept in sorted-order.
	names []string

	// entries maps names to fs.DentAttrs.
	entries map[string]DentAttr
}

// NewSortedDentryMap maintains entries in name sorted order.
func NewSortedDentryMap(entries map[string]DentAttr) *SortedDentryMap {
	s := &SortedDentryMap{
		names:   make([]string, 0, len(entries)),
		entries: entries,
	}
	// Don't allow s.entries to be nil, because nil maps arn't Saveable.
	if s.entries == nil {
		s.entries = make(map[string]DentAttr)
	}

	// Collect names from entries and sort them.
	for name := range s.entries {
		s.names = append(s.names, name)
	}
	sort.Strings(s.names)
	return s
}

// GetAll returns all names and entries in s. Callers should not modify the
// returned values.
func (s *SortedDentryMap) GetAll() ([]string, map[string]DentAttr) {
	return s.names, s.entries
}

// GetNext returns names after cursor in s and all entries.
func (s *SortedDentryMap) GetNext(cursor string) ([]string, map[string]DentAttr) {
	i := sort.SearchStrings(s.names, cursor)
	if i == len(s.names) {
		return nil, s.entries
	}

	// Return everything strictly after the cursor.
	if s.names[i] == cursor {
		i++
	}
	return s.names[i:], s.entries
}

// Add adds an entry with the given name to the map, preserving sort order.  If
// name already exists in the map, its entry will be overwritten.
func (s *SortedDentryMap) Add(name string, entry DentAttr) {
	if _, ok := s.entries[name]; !ok {
		// Map does not yet contain an entry with this name.  We must
		// insert it in s.names at the appropriate spot.
		i := sort.SearchStrings(s.names, name)
		s.names = append(s.names, "")
		copy(s.names[i+1:], s.names[i:])
		s.names[i] = name
	}
	s.entries[name] = entry
}

// Remove removes an entry with the given name from the map, preserving sort order.
func (s *SortedDentryMap) Remove(name string) {
	if _, ok := s.entries[name]; !ok {
		return
	}
	i := sort.SearchStrings(s.names, name)
	copy(s.names[i:], s.names[i+1:])
	s.names = s.names[:len(s.names)-1]
	delete(s.entries, name)
}

// Contains reports whether the map contains an entry with the given name.
func (s *SortedDentryMap) Contains(name string) bool {
	_, ok := s.entries[name]
	return ok
}
<commit_msg>Update reference to old type<commit_after>// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package fs

import (
	"sort"

	"gvisor.googlesource.com/gvisor/pkg/sentry/device"
)

// DentAttr is the metadata of a directory entry. It is a subset of StableAttr.
//
// +stateify savable
type DentAttr struct {
	// Type is the InodeType of an Inode.
	Type InodeType

	// InodeID uniquely identifies an Inode on a device.
	InodeID uint64
}

// GenericDentAttr returns a generic DentAttr where:
//
// Type == nt
// InodeID == the inode id of a new inode on device.
func GenericDentAttr(nt InodeType, device *device.Device) DentAttr {
	return DentAttr{
		Type:    nt,
		InodeID: device.NextIno(),
	}
}

// DentrySerializer serializes a directory entry.
type DentrySerializer interface {
	// CopyOut serializes a directory entry based on its name and attributes.
	CopyOut(name string, attributes DentAttr) error

	// Written returns the number of bytes written.
	Written() int
}

// CollectEntriesSerializer copies DentAttrs to Entries. The order in
// which entries are encountered is preserved in Order.
type CollectEntriesSerializer struct {
	Entries map[string]DentAttr
	Order   []string
}

// CopyOut implements DentrySerializer.CopyOut.
func (c *CollectEntriesSerializer) CopyOut(name string, attr DentAttr) error {
	if c.Entries == nil {
		c.Entries = make(map[string]DentAttr)
	}
	c.Entries[name] = attr
	c.Order = append(c.Order, name)
	return nil
}

// Written implements DentrySerializer.Written.
func (c *CollectEntriesSerializer) Written() int {
	return len(c.Entries)
}

// DirCtx is used in FileOperations.IterateDir to emit directory entries. It is
// not thread-safe.
type DirCtx struct {
	// Serializer is used to serialize the node attributes.
	Serializer DentrySerializer

	// attrs are DentAttrs
	attrs map[string]DentAttr

	// DirCursor is the directory cursor.
	// TODO(b/67778717): Once Handles are removed this can just live in the
	// respective FileOperations implementations and not need to get
	// plumbed everywhere.
	DirCursor *string
}

// DirEmit is called for each directory entry.
func (c *DirCtx) DirEmit(name string, attr DentAttr) error {
	if c.Serializer != nil {
		if err := c.Serializer.CopyOut(name, attr); err != nil {
			return err
		}
	}
	if c.attrs == nil {
		c.attrs = make(map[string]DentAttr)
	}
	c.attrs[name] = attr
	return nil
}

// DentAttrs returns a map of DentAttrs corresponding to the emitted directory
// entries.
func (c *DirCtx) DentAttrs() map[string]DentAttr {
	if c.attrs == nil {
		c.attrs = make(map[string]DentAttr)
	}
	return c.attrs
}

// GenericReaddir serializes DentAttrs based on a SortedDentryMap that must
// contain _all_ up-to-date DentAttrs under a directory. If ctx.DirCursor is
// not nil, it is updated to the name of the last DentAttr that was
// successfully serialized.
//
// Returns the number of entries serialized.
func GenericReaddir(ctx *DirCtx, s *SortedDentryMap) (int, error) {
	// Retrieve the next directory entries.
	var names []string
	var entries map[string]DentAttr
	if ctx.DirCursor != nil {
		names, entries = s.GetNext(*ctx.DirCursor)
	} else {
		names, entries = s.GetAll()
	}

	// Try to serialize each entry.
	var serialized int
	for _, name := range names {
		// Skip "" per POSIX. Skip "." and ".." which will be added by Dirent.Readdir.
		if name == "" || name == "." || name == ".." {
			continue
		}

		// Emit the directory entry.
		if err := ctx.DirEmit(name, entries[name]); err != nil {
			// Return potentially a partial serialized count.
			return serialized, err
		}

		// We successfully serialized this entry.
		serialized++

		// Update the cursor with the name of the entry last serialized.
		if ctx.DirCursor != nil {
			*ctx.DirCursor = name
		}
	}

	// Everything was serialized.
	return serialized, nil
}

// SortedDentryMap is a sorted map of names and fs.DentAttr entries.
//
// +stateify savable
type SortedDentryMap struct {
	// names is always kept in sorted-order.
	names []string

	// entries maps names to fs.DentAttrs.
	entries map[string]DentAttr
}

// NewSortedDentryMap maintains entries in name sorted order.
func NewSortedDentryMap(entries map[string]DentAttr) *SortedDentryMap {
	s := &SortedDentryMap{
		names:   make([]string, 0, len(entries)),
		entries: entries,
	}
	// Don't allow s.entries to be nil, because nil maps arn't Saveable.
	if s.entries == nil {
		s.entries = make(map[string]DentAttr)
	}

	// Collect names from entries and sort them.
	for name := range s.entries {
		s.names = append(s.names, name)
	}
	sort.Strings(s.names)
	return s
}

// GetAll returns all names and entries in s. Callers should not modify the
// returned values.
func (s *SortedDentryMap) GetAll() ([]string, map[string]DentAttr) {
	return s.names, s.entries
}

// GetNext returns names after cursor in s and all entries.
func (s *SortedDentryMap) GetNext(cursor string) ([]string, map[string]DentAttr) {
	i := sort.SearchStrings(s.names, cursor)
	if i == len(s.names) {
		return nil, s.entries
	}

	// Return everything strictly after the cursor.
	if s.names[i] == cursor {
		i++
	}
	return s.names[i:], s.entries
}

// Add adds an entry with the given name to the map, preserving sort order.  If
// name already exists in the map, its entry will be overwritten.
func (s *SortedDentryMap) Add(name string, entry DentAttr) {
	if _, ok := s.entries[name]; !ok {
		// Map does not yet contain an entry with this name.  We must
		// insert it in s.names at the appropriate spot.
		i := sort.SearchStrings(s.names, name)
		s.names = append(s.names, "")
		copy(s.names[i+1:], s.names[i:])
		s.names[i] = name
	}
	s.entries[name] = entry
}

// Remove removes an entry with the given name from the map, preserving sort order.
func (s *SortedDentryMap) Remove(name string) {
	if _, ok := s.entries[name]; !ok {
		return
	}
	i := sort.SearchStrings(s.names, name)
	copy(s.names[i:], s.names[i+1:])
	s.names = s.names[:len(s.names)-1]
	delete(s.entries, name)
}

// Contains reports whether the map contains an entry with the given name.
func (s *SortedDentryMap) Contains(name string) bool {
	_, ok := s.entries[name]
	return ok
}
<|endoftext|>


<commit_before>package ts3sqlib

import (
	"fmt"
	"strconv"
	"strings"
)

var (
	//End of Message Error (the normal error message from the ts3 server)
	MsgEndError = NewError(0, "ok", "")
	//Connection closed Error
	ClosedError = NewError(-1, "connection closed", "")
	//Invalid Login Error, because of a wrong username or password.
	InvalidLoginError = NewError(520, "invalid loginname or password", "")
)

//Error contains additional error information.
type Error struct {
	ID       int
	Msg      string
	ExtraMsg string
}

//Error returns the error in a string representation.
func (err Error) Error() string {
	s := fmt.Sprintf("error id=%d msg=%s", err.ID, err.Msg)
	if err.ExtraMsg != "" {
		s += fmt.Sprintf(" extra_msg=%s", err.ExtraMsg)
	}
	return s
}

//NewError creates a new Error from an id, message and an extra_message.
func NewError(id int, msg, extramsg string) Error {
	return Error{id, msg, extramsg}
}

//Equals compares an Error with another Error
func (err Error) Equals(compareErr error) bool {
	if err2, ok := compareErr.(Error); ok {
		return err.ID == err2.ID
	}
	return false
}

//isError tests if a given string is a ts3 server query error
func isError(line string) bool {
	return strings.HasPrefix(line, "error") &&
		strings.Contains(line, "id=") && strings.Contains(line, "msg=")
}

//toError converts a given string into a ts3 server query error.
func toError(line string) (err Error) {
	if !isError(line) {
		err = NewError(666, "line is not an error!", "")
		return
	}

	parts := strings.Split(line, " ")
	for i := range parts {
		if strings.Contains(parts[i], "=") {
			key, value, err2 := splitAtEqual(parts[i])
			if err2 {
				continue
			}

			switch {
			case strings.Contains(key, "id"):
				var err3 error
				err.ID, err3 = strconv.Atoi(value)
				if err3 != nil {
					err.ID = 999
				}
			case strings.Contains(key, "extra_msg"):
				err.ExtraMsg = Unescape(value)
			case strings.Contains(key, "msg"):
				err.Msg = Unescape(value)
			}
		}
	}
	return
}

func splitAtEqual(s string) (key string, value string, err bool) {
	if strings.Contains(s, "=") {
		tmp := strings.Split(s, "=")
		//fmt.Println(" -> ", s, " => ", tmp)

		key = tmp[0]
		value = tmp[1]

		err = false
	} else {
		err = true
	}
	return
}
<commit_msg>changing some more comments<commit_after>package ts3sqlib

import (
	"fmt"
	"strconv"
	"strings"
)

var (
	//MsgEndError is the normal Error at the end of each message.
	MsgEndError = NewError(0, "ok", "")
	//ClosedError is the Error of a closed connection.
	ClosedError = NewError(-1, "connection closed", "")
	//InvalidLoginError is the Error for an invalid loginname or password.
	InvalidLoginError = NewError(520, "invalid loginname or password", "")
)

//Error contains additional error information.
type Error struct {
	ID       int
	Msg      string
	ExtraMsg string
}

//Error returns the error in a string representation.
func (err Error) Error() string {
	s := fmt.Sprintf("error id=%d msg=%s", err.ID, err.Msg)
	if err.ExtraMsg != "" {
		s += fmt.Sprintf(" extra_msg=%s", err.ExtraMsg)
	}
	return s
}

//NewError creates a new Error from an id, message and an extra_message.
func NewError(id int, msg, extramsg string) Error {
	return Error{id, msg, extramsg}
}

//Equals compares an Error with another Error
func (err Error) Equals(compareErr error) bool {
	if err2, ok := compareErr.(Error); ok {
		return err.ID == err2.ID
	}
	return false
}

//isError tests if a given string is a ts3 server query error
func isError(line string) bool {
	return strings.HasPrefix(line, "error") &&
		strings.Contains(line, "id=") && strings.Contains(line, "msg=")
}

//toError converts a given string into a ts3 server query error.
func toError(line string) (err Error) {
	if !isError(line) {
		err = NewError(666, "line is not an error!", "")
		return
	}

	parts := strings.Split(line, " ")
	for i := range parts {
		if strings.Contains(parts[i], "=") {
			key, value, err2 := splitAtEqual(parts[i])
			if err2 {
				continue
			}

			switch {
			case strings.Contains(key, "id"):
				var err3 error
				err.ID, err3 = strconv.Atoi(value)
				if err3 != nil {
					err.ID = 999
				}
			case strings.Contains(key, "extra_msg"):
				err.ExtraMsg = Unescape(value)
			case strings.Contains(key, "msg"):
				err.Msg = Unescape(value)
			}
		}
	}
	return
}

func splitAtEqual(s string) (key string, value string, err bool) {
	if strings.Contains(s, "=") {
		tmp := strings.Split(s, "=")
		//fmt.Println(" -> ", s, " => ", tmp)

		key = tmp[0]
		value = tmp[1]

		err = false
	} else {
		err = true
	}
	return
}
<|endoftext|>


<commit_before>// Copyright © 2016 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package cmd

import (
	"fmt"
	"os"
	"strings"

	"github.com/apex/log"
	"github.com/apex/log/handlers/cli"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var cfgFile string

var ctx log.Interface

// RootCmd is the entrypoint for handlerctl
var RootCmd = &cobra.Command{
	Use:   "ttnctl",
	Short: "Control The Things Network from the command line",
	Long:  `ttnctl controls The Things Network from the command line.`,
	PersistentPreRun: func(cmd *cobra.Command, args []string) {
		var logLevel = log.InfoLevel
		if viper.GetBool("debug") {
			logLevel = log.DebugLevel
		}
		cli.Colors[log.DebugLevel] = 90
		cli.Colors[log.InfoLevel] = 32
		ctx = &log.Logger{
			Level:   logLevel,
			Handler: cli.New(os.Stdout),
		}
	},
}

// Execute runs on start
func Execute() {
	if err := RootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
}

// init initializes the configuration and command line flags
func init() {
	cobra.OnInitialize(initConfig)

	RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yaml)")

	RootCmd.PersistentFlags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router")
	viper.BindPFlag("ttn-router", RootCmd.PersistentFlags().Lookup("ttn-router"))

	RootCmd.PersistentFlags().String("ttn-handler", "0.0.0.0:1782", "The net address of the TTN Handler")
	viper.BindPFlag("ttn-handler", RootCmd.PersistentFlags().Lookup("ttn-handler"))

	RootCmd.PersistentFlags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker")
	viper.BindPFlag("mqtt-broker", RootCmd.PersistentFlags().Lookup("mqtt-broker"))

	RootCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use")
	viper.BindPFlag("app-eui", RootCmd.PersistentFlags().Lookup("app-eui"))

	RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethings.network", "The address of the OAuth 2.0 server")
	viper.BindPFlag("ttn-account-server", RootCmd.PersistentFlags().Lookup("ttn-account-server"))
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
	if cfgFile != "" {
		viper.SetConfigFile(cfgFile)
	}

	viper.SetConfigName(".ttnctl")
	viper.AddConfigPath("$HOME")
	viper.SetEnvPrefix("ttnctl") // set environment prefix
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
	viper.AutomaticEnv()

	// If a config file is found, read it in.
	if err := viper.ReadInConfig(); err == nil {
		fmt.Println("Using config file:", viper.ConfigFileUsed())
	}
}
<commit_msg>[ttnctl-user] Set default account server to account.thethingsnetwork.org<commit_after>// Copyright © 2016 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package cmd

import (
	"fmt"
	"os"
	"strings"

	"github.com/apex/log"
	"github.com/apex/log/handlers/cli"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var cfgFile string

var ctx log.Interface

// RootCmd is the entrypoint for handlerctl
var RootCmd = &cobra.Command{
	Use:   "ttnctl",
	Short: "Control The Things Network from the command line",
	Long:  `ttnctl controls The Things Network from the command line.`,
	PersistentPreRun: func(cmd *cobra.Command, args []string) {
		var logLevel = log.InfoLevel
		if viper.GetBool("debug") {
			logLevel = log.DebugLevel
		}
		cli.Colors[log.DebugLevel] = 90
		cli.Colors[log.InfoLevel] = 32
		ctx = &log.Logger{
			Level:   logLevel,
			Handler: cli.New(os.Stdout),
		}
	},
}

// Execute runs on start
func Execute() {
	if err := RootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
}

// init initializes the configuration and command line flags
func init() {
	cobra.OnInitialize(initConfig)

	RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yaml)")

	RootCmd.PersistentFlags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router")
	viper.BindPFlag("ttn-router", RootCmd.PersistentFlags().Lookup("ttn-router"))

	RootCmd.PersistentFlags().String("ttn-handler", "0.0.0.0:1782", "The net address of the TTN Handler")
	viper.BindPFlag("ttn-handler", RootCmd.PersistentFlags().Lookup("ttn-handler"))

	RootCmd.PersistentFlags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker")
	viper.BindPFlag("mqtt-broker", RootCmd.PersistentFlags().Lookup("mqtt-broker"))

	RootCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use")
	viper.BindPFlag("app-eui", RootCmd.PersistentFlags().Lookup("app-eui"))

	RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server")
	viper.BindPFlag("ttn-account-server", RootCmd.PersistentFlags().Lookup("ttn-account-server"))
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
	if cfgFile != "" {
		viper.SetConfigFile(cfgFile)
	}

	viper.SetConfigName(".ttnctl")
	viper.AddConfigPath("$HOME")
	viper.SetEnvPrefix("ttnctl") // set environment prefix
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
	viper.AutomaticEnv()

	// If a config file is found, read it in.
	if err := viper.ReadInConfig(); err == nil {
		fmt.Println("Using config file:", viper.ConfigFileUsed())
	}
}
<|endoftext|>


<commit_before>package mc_conn_handler

import . "./mc_constants"
import . "./byte_manipulation"

import (
	"log";
	"net";
	"io";
	"bufio";
)

func HandleIO(s net.Conn, reqChannel chan MCRequest) {
	log.Stdout("Processing input from %s", s);
	for handleMessage(s, reqChannel) {
	}
	s.Close();
	log.Stdout("Hung up on a connection");
}

func handleMessage(s net.Conn, reqChannel chan MCRequest) (ret bool) {
	log.Stdoutf("Handling a message...");
	hdrBytes := make([]byte, HDR_LEN);
	ret = false;

	log.Stdoutf("Reading header...");
	bytesRead, err := io.ReadFull(s, hdrBytes);
	if err != nil || bytesRead != HDR_LEN {
		log.Stderr("Error reading message: %s (%d bytes)", err, bytesRead);
		return;
	}

	req, ok := grokHeader(hdrBytes);
	if !ok {
		return
	}

	if !readContents(s, req) {
		return
	}

	log.Stdout("Processing message %s", req);
	req.ResponseChannel = make(chan MCResponse);
	reqChannel <- req;
	res := <-req.ResponseChannel;
	ret = !res.Fatal;
	if ret {
		log.Stdoutf("Got response %s", res);
		ret = transmitResponse(s, req, res);
	} else {
		log.Stderr("Something went wrong, hanging up...")
	}

	return;
}

func readContents(s net.Conn, req MCRequest) (rv bool) {
	if !readOb(s, req.Extras) {
		return
	}

	if !readOb(s, req.Key) {
		return
	}

	if readOb(s, req.Body) {
		rv = true
	}
	return;
}

func transmitResponse(s net.Conn, req MCRequest, res MCResponse) (rv bool) {
	rv = true;
	o := bufio.NewWriter(s);
	rv = writeByte(o, RES_MAGIC, rv);
	rv = writeByte(o, req.Opcode, rv);
	rv = writeUint16(o, uint16(len(res.Key)), rv);
	rv = writeByte(o, uint8(len(res.Extras)), rv);
	rv = writeByte(o, 0, rv);
	rv = writeUint16(o, res.Status, rv);
	rv = writeUint32(o, uint32(len(res.Body))+
		uint32(len(res.Key))+
		uint32(len(res.Extras)),
		rv);
	rv = writeUint32(o, req.Opaque, rv);
	rv = writeUint64(o, res.Cas, rv);
	rv = writeBytes(o, res.Extras, rv);
	rv = writeBytes(o, res.Key, rv);
	rv = writeBytes(o, res.Body, rv);
	o.Flush();
	return;
}

func writeBytes(s *bufio.Writer, data []byte, ok bool) (rv bool) {
	rv = ok;
	if ok && len(data) > 0 {
		written, err := s.Write(data);
		if err != nil || written != len(data) {
			log.Stderrf("Error writing bytes:  %s", err);
			rv = false;
		}
	}
	return;

}

func writeByte(s *bufio.Writer, b byte, ok bool) (rv bool) {
	var data [1]byte;
	data[0] = b;
	rv = writeBytes(s, &data, ok);
	return;
}

func writeUint16(s *bufio.Writer, n uint16, ok bool) (rv bool) {
	data := WriteUint16(n);
	rv = writeBytes(s, data, ok);
	return;
}

func writeUint32(s *bufio.Writer, n uint32, ok bool) (rv bool) {
	data := WriteUint32(n);
	rv = writeBytes(s, data, ok);
	return;
}

func writeUint64(s *bufio.Writer, n uint64, ok bool) (rv bool) {
	data := WriteUint64(n);
	rv = writeBytes(s, data, ok);
	return;
}

func readOb(s net.Conn, buf []byte) (rv bool) {
	rv = true;
	x, err := io.ReadFull(s, buf);
	if err != nil || x != len(buf) {
		log.Stderrf("Error reading part: %s", err);
		rv = false;
	}
	return;
}

func grokHeader(hdrBytes []byte) (rv MCRequest, ok bool) {
	ok = true;
	if hdrBytes[0] != REQ_MAGIC {
		log.Stderrf("Bad magic: %x", hdrBytes[0]);
		ok = false;
		return;
	}
	rv.Opcode = hdrBytes[1];
	rv.Key = make([]byte, ReadInt16(hdrBytes, 2));
	rv.Extras = make([]byte, hdrBytes[4]);
	bodyLen := ReadInt32(hdrBytes, 8) - uint32(len(rv.Key)) - uint32(len(rv.Extras));
	rv.Body = make([]byte, bodyLen);
	rv.Opaque = ReadInt32(hdrBytes, 12);
	rv.Cas = ReadInt64(hdrBytes, 16);

	return;
}
<commit_msg>Simplify the write functions.<commit_after>package mc_conn_handler

import . "./mc_constants"
import . "./byte_manipulation"

import (
	"log";
	"net";
	"io";
	"bufio";
)

func HandleIO(s net.Conn, reqChannel chan MCRequest) {
	log.Stdout("Processing input from %s", s);
	for handleMessage(s, reqChannel) {
	}
	s.Close();
	log.Stdout("Hung up on a connection");
}

func handleMessage(s net.Conn, reqChannel chan MCRequest) (ret bool) {
	log.Stdoutf("Handling a message...");
	hdrBytes := make([]byte, HDR_LEN);
	ret = false;

	log.Stdoutf("Reading header...");
	bytesRead, err := io.ReadFull(s, hdrBytes);
	if err != nil || bytesRead != HDR_LEN {
		log.Stderr("Error reading message: %s (%d bytes)", err, bytesRead);
		return;
	}

	req, ok := grokHeader(hdrBytes);
	if !ok {
		return
	}

	if !readContents(s, req) {
		return
	}

	log.Stdout("Processing message %s", req);
	req.ResponseChannel = make(chan MCResponse);
	reqChannel <- req;
	res := <-req.ResponseChannel;
	ret = !res.Fatal;
	if ret {
		log.Stdoutf("Got response %s", res);
		ret = transmitResponse(s, req, res);
	} else {
		log.Stderr("Something went wrong, hanging up...")
	}

	return;
}

func readContents(s net.Conn, req MCRequest) (rv bool) {
	if !readOb(s, req.Extras) {
		return
	}

	if !readOb(s, req.Key) {
		return
	}

	if readOb(s, req.Body) {
		rv = true
	}
	return;
}

func transmitResponse(s net.Conn, req MCRequest, res MCResponse) (rv bool) {
	rv = true;
	o := bufio.NewWriter(s);
	rv = writeByte(o, RES_MAGIC, rv);
	rv = writeByte(o, req.Opcode, rv);
	rv = writeUint16(o, uint16(len(res.Key)), rv);
	rv = writeByte(o, uint8(len(res.Extras)), rv);
	rv = writeByte(o, 0, rv);
	rv = writeUint16(o, res.Status, rv);
	rv = writeUint32(o, uint32(len(res.Body))+
		uint32(len(res.Key))+
		uint32(len(res.Extras)),
		rv);
	rv = writeUint32(o, req.Opaque, rv);
	rv = writeUint64(o, res.Cas, rv);
	rv = writeBytes(o, res.Extras, rv);
	rv = writeBytes(o, res.Key, rv);
	rv = writeBytes(o, res.Body, rv);
	o.Flush();
	return;
}

func writeBytes(s *bufio.Writer, data []byte, ok bool) (rv bool) {
	rv = ok;
	if ok && len(data) > 0 {
		written, err := s.Write(data);
		if err != nil || written != len(data) {
			log.Stderrf("Error writing bytes:  %s", err);
			rv = false;
		}
	}
	return;

}

func writeByte(s *bufio.Writer, b byte, ok bool) bool {
	var data [1]byte;
	data[0] = b;
	return writeBytes(s, &data, ok);
}

func writeUint16(s *bufio.Writer, n uint16, ok bool) bool {
	data := WriteUint16(n);
	return writeBytes(s, data, ok);
}

func writeUint32(s *bufio.Writer, n uint32, ok bool) bool {
	data := WriteUint32(n);
	return writeBytes(s, data, ok);
}

func writeUint64(s *bufio.Writer, n uint64, ok bool) bool {
	data := WriteUint64(n);
	return writeBytes(s, data, ok);
}

func readOb(s net.Conn, buf []byte) (rv bool) {
	rv = true;
	x, err := io.ReadFull(s, buf);
	if err != nil || x != len(buf) {
		log.Stderrf("Error reading part: %s", err);
		rv = false;
	}
	return;
}

func grokHeader(hdrBytes []byte) (rv MCRequest, ok bool) {
	ok = true;
	if hdrBytes[0] != REQ_MAGIC {
		log.Stderrf("Bad magic: %x", hdrBytes[0]);
		ok = false;
		return;
	}
	rv.Opcode = hdrBytes[1];
	rv.Key = make([]byte, ReadInt16(hdrBytes, 2));
	rv.Extras = make([]byte, hdrBytes[4]);
	bodyLen := ReadInt32(hdrBytes, 8) - uint32(len(rv.Key)) - uint32(len(rv.Extras));
	rv.Body = make([]byte, bodyLen);
	rv.Opaque = ReadInt32(hdrBytes, 12);
	rv.Cas = ReadInt64(hdrBytes, 16);

	return;
}
<|endoftext|>


<commit_before>package cache

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/MG-RAST/AWE/lib/conf"
	"github.com/MG-RAST/AWE/lib/core"
	"github.com/MG-RAST/AWE/lib/httpclient"
	"github.com/MG-RAST/AWE/lib/logger"
	"github.com/MG-RAST/AWE/lib/logger/event"
	"io"
	"io/ioutil"
	"os"
	"time"
)

func UploadOutputData(work *core.Workunit) (size int64, err error) {
	for name, io := range work.Outputs {
		var local_filepath string //local file name generated by the cmd
		var file_path string      //file name to be uploaded to shock

		if io.Directory != "" {
			local_filepath = fmt.Sprintf("%s/%s/%s", work.Path(), io.Directory, name)
			//if specified, rename the local file name to the specified shock node file name
			//otherwise use the local name as shock file name
			file_path = local_filepath
			if io.ShockFilename != "" {
				file_path = fmt.Sprintf("%s/%s/%s", work.Path(), io.Directory, io.ShockFilename)
				os.Rename(local_filepath, file_path)
			}
		} else {
			local_filepath = fmt.Sprintf("%s/%s", work.Path(), name)
			file_path = local_filepath
			if io.ShockFilename != "" {
				file_path = fmt.Sprintf("%s/%s", work.Path(), io.ShockFilename)
				os.Rename(local_filepath, file_path)
			}
		}
		//use full path here, cwd could be changed by Worker (likely in worker-overlapping mode)
		if fi, err := os.Stat(file_path); err != nil {
			//ignore missing file if type=copy or type==update or nofile=true
			//skip this output if missing file and optional
			if (io.Type == "copy") || (io.Type == "update") || io.NoFile {
				file_path = ""
			} else if io.Optional {
				continue
			} else {
				return size, errors.New(fmt.Sprintf("output %s not generated for workunit %s", name, work.Id))
			}
		} else {
			if io.Nonzero && fi.Size() == 0 {
				return size, errors.New(fmt.Sprintf("workunit %s generated zero-sized output %s while non-zero-sized file required", work.Id, name))
			}
			size += fi.Size()
		}
		logger.Debug(2, "deliverer: push output to shock, filename="+name)
		logger.Event(event.FILE_OUT,
			"workid="+work.Id,
			"filename="+name,
			fmt.Sprintf("url=%s/node/%s", io.Host, io.Node))

		//upload attribute file to shock IF attribute file is specified in outputs AND it is found in local directory.
		var attrfile_path string = ""
		if io.AttrFile != "" {
			attrfile_path = fmt.Sprintf("%s/%s", work.Path(), io.AttrFile)
			if fi, err := os.Stat(attrfile_path); err != nil || fi.Size() == 0 {
				attrfile_path = ""
			}
		}

		//set io.FormOptions["parent_node"] if not present and io.FormOptions["parent_name"] exists
		if parent_name, ok := io.FormOptions["parent_name"]; ok {
			for in_name, in_io := range work.Inputs {
				if in_name == parent_name {
					io.FormOptions["parent_node"] = in_io.Node
				}
			}
		}

		if err := core.PutFileToShock(file_path, io.Host, io.Node, work.Rank, work.Info.DataToken, attrfile_path, io.Type, io.FormOptions); err != nil {
			time.Sleep(3 * time.Second) //wait for 3 seconds and try again
			if err := core.PutFileToShock(file_path, io.Host, io.Node, work.Rank, work.Info.DataToken, attrfile_path, io.Type, io.FormOptions); err != nil {
				fmt.Errorf("push file error\n")
				logger.Error("op=pushfile,err=" + err.Error())
				return size, err
			}
		}
		logger.Event(event.FILE_DONE,
			"workid="+work.Id,
			"filename="+name,
			fmt.Sprintf("url=%s/node/%s", io.Host, io.Node))

		if io.ShockIndex != "" {
			if err := core.ShockPutIndex(io.Host, io.Node, io.ShockIndex, work.Info.DataToken); err != nil {
				logger.Error("warning: fail to create index on shock for shock node: " + io.Node)
			}
		}

		if conf.CACHE_ENABLED {
			//move output files to cache
			cacheDir := getCacheDir(io.Node)
			if err := os.MkdirAll(cacheDir, 0777); err != nil {
				logger.Error("cache os.MkdirAll():" + err.Error())
			}
			cacheFilePath := getCacheFilePath(io.Node) //use the same naming mechanism used by shock server
			//fmt.Printf("moving file from %s to %s\n", file_path, cacheFilePath)
			if err := os.Rename(file_path, cacheFilePath); err != nil {
				logger.Error("cache os.Rename():" + err.Error())
			}
		}
	}
	return
}

func getCacheDir(id string) string {
	if len(id) < 7 {
		return conf.DATA_PATH
	}
	return fmt.Sprintf("%s/%s/%s/%s/%s", conf.DATA_PATH, id[0:2], id[2:4], id[4:6], id)
}

func getCacheFilePath(id string) string {
	cacheDir := getCacheDir(id)
	return fmt.Sprintf("%s/%s.data", cacheDir, id)
}

func StatCacheFilePath(id string) (file_path string, err error) {
	file_path = getCacheFilePath(id)
	_, err = os.Stat(file_path)
	return file_path, err
}

//fetch input data
func MoveInputData(work *core.Workunit) (size int64, err error) {
	for inputname, io := range work.Inputs {

		// skip if NoFile == true
		if !io.NoFile {
			var dataUrl string
			inputFilePath := fmt.Sprintf("%s/%s", work.Path(), inputname)

			if work.Rank == 0 {
				if conf.CACHE_ENABLED && io.Node != "" {
					if file_path, err := StatCacheFilePath(io.Node); err == nil {
						//make a link in work dir from cached file
						linkname := fmt.Sprintf("%s/%s", work.Path(), inputname)
						fmt.Printf("input found in cache, making link: " + file_path + " -> " + linkname + "\n")
						err = os.Symlink(file_path, linkname)
						if err == nil {
							logger.Event(event.FILE_READY, "workid="+work.Id+";url="+dataUrl)
						}
						return 0, err
					}
				}
				dataUrl = io.DataUrl()
			} else {
				dataUrl = fmt.Sprintf("%s&index=%s&part=%s", io.DataUrl(), work.IndexType(), work.Part())
			}
			logger.Debug(2, "mover: fetching input from url:"+dataUrl)
			logger.Event(event.FILE_IN, "workid="+work.Id+" url="+dataUrl)

			// download file
			if datamoved, err := fetchFile(inputFilePath, dataUrl, work.Info.DataToken); err != nil {
				return size, err
			} else {
				size += datamoved
			}
			logger.Event(event.FILE_READY, "workid="+work.Id+";url="+dataUrl)
		}

		// download node attributes if requested
		if io.AttrFile != "" {
			if node, err := io.GetShockNode(); err != nil {
				return size, err
			} else {
				attrFilePath := fmt.Sprintf("%s/%s", work.Path(), io.AttrFile)
				attr_json, _ := json.Marshal(node.Attributes)
				if err := ioutil.WriteFile(attrFilePath, attr_json, 0644); err != nil {
					return size, err
				}
			}
		}
	}
	return
}

func isFileExistingInCache(id string) bool {
	file_path := getCacheFilePath(id)
	if _, err := os.Stat(file_path); err == nil {
		return true
	}
	return false
}

//fetch file by shock url
func fetchFile(filename string, url string, token string) (size int64, err error) {
	fmt.Printf("fetching file name=%s, url=%s\n", filename, url)
	localfile, err := os.Create(filename)
	if err != nil {
		return 0, err
	}
	defer localfile.Close()

	var user *httpclient.Auth
	if token != "" {
		user = httpclient.GetUserByTokenAuth(token)
	}

	//download file from Shock
	res, err := httpclient.Get(url, httpclient.Header{}, nil, user)
	if err != nil {
		return 0, err
	}

	defer res.Body.Close()

	if res.StatusCode != 200 { //err in fetching data
		resbody, _ := ioutil.ReadAll(res.Body)
		msg := fmt.Sprintf("op=fetchFile, url=%s, res=%s", url, resbody)
		return 0, errors.New(msg)
	}

	size, err = io.Copy(localfile, res.Body)
	if err != nil {
		return 0, err
	}
	return
}
<commit_msg>update attribute download with logging, fix missing datatoken<commit_after>package cache

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/MG-RAST/AWE/lib/conf"
	"github.com/MG-RAST/AWE/lib/core"
	"github.com/MG-RAST/AWE/lib/httpclient"
	"github.com/MG-RAST/AWE/lib/logger"
	"github.com/MG-RAST/AWE/lib/logger/event"
	"io"
	"io/ioutil"
	"os"
	"time"
)

func UploadOutputData(work *core.Workunit) (size int64, err error) {
	for name, io := range work.Outputs {
		var local_filepath string //local file name generated by the cmd
		var file_path string      //file name to be uploaded to shock

		if io.Directory != "" {
			local_filepath = fmt.Sprintf("%s/%s/%s", work.Path(), io.Directory, name)
			//if specified, rename the local file name to the specified shock node file name
			//otherwise use the local name as shock file name
			file_path = local_filepath
			if io.ShockFilename != "" {
				file_path = fmt.Sprintf("%s/%s/%s", work.Path(), io.Directory, io.ShockFilename)
				os.Rename(local_filepath, file_path)
			}
		} else {
			local_filepath = fmt.Sprintf("%s/%s", work.Path(), name)
			file_path = local_filepath
			if io.ShockFilename != "" {
				file_path = fmt.Sprintf("%s/%s", work.Path(), io.ShockFilename)
				os.Rename(local_filepath, file_path)
			}
		}
		//use full path here, cwd could be changed by Worker (likely in worker-overlapping mode)
		if fi, err := os.Stat(file_path); err != nil {
			//ignore missing file if type=copy or type==update or nofile=true
			//skip this output if missing file and optional
			if (io.Type == "copy") || (io.Type == "update") || io.NoFile {
				file_path = ""
			} else if io.Optional {
				continue
			} else {
				return size, errors.New(fmt.Sprintf("output %s not generated for workunit %s", name, work.Id))
			}
		} else {
			if io.Nonzero && fi.Size() == 0 {
				return size, errors.New(fmt.Sprintf("workunit %s generated zero-sized output %s while non-zero-sized file required", work.Id, name))
			}
			size += fi.Size()
		}
		logger.Debug(2, "deliverer: push output to shock, filename="+name)
		logger.Event(event.FILE_OUT,
			"workid="+work.Id,
			"filename="+name,
			fmt.Sprintf("url=%s/node/%s", io.Host, io.Node))

		//upload attribute file to shock IF attribute file is specified in outputs AND it is found in local directory.
		var attrfile_path string = ""
		if io.AttrFile != "" {
			attrfile_path = fmt.Sprintf("%s/%s", work.Path(), io.AttrFile)
			if fi, err := os.Stat(attrfile_path); err != nil || fi.Size() == 0 {
				attrfile_path = ""
			}
		}

		//set io.FormOptions["parent_node"] if not present and io.FormOptions["parent_name"] exists
		if parent_name, ok := io.FormOptions["parent_name"]; ok {
			for in_name, in_io := range work.Inputs {
				if in_name == parent_name {
					io.FormOptions["parent_node"] = in_io.Node
				}
			}
		}

		if err := core.PutFileToShock(file_path, io.Host, io.Node, work.Rank, work.Info.DataToken, attrfile_path, io.Type, io.FormOptions); err != nil {
			time.Sleep(3 * time.Second) //wait for 3 seconds and try again
			if err := core.PutFileToShock(file_path, io.Host, io.Node, work.Rank, work.Info.DataToken, attrfile_path, io.Type, io.FormOptions); err != nil {
				fmt.Errorf("push file error\n")
				logger.Error("op=pushfile,err=" + err.Error())
				return size, err
			}
		}
		logger.Event(event.FILE_DONE,
			"workid="+work.Id,
			"filename="+name,
			fmt.Sprintf("url=%s/node/%s", io.Host, io.Node))

		if io.ShockIndex != "" {
			if err := core.ShockPutIndex(io.Host, io.Node, io.ShockIndex, work.Info.DataToken); err != nil {
				logger.Error("warning: fail to create index on shock for shock node: " + io.Node)
			}
		}

		if conf.CACHE_ENABLED {
			//move output files to cache
			cacheDir := getCacheDir(io.Node)
			if err := os.MkdirAll(cacheDir, 0777); err != nil {
				logger.Error("cache os.MkdirAll():" + err.Error())
			}
			cacheFilePath := getCacheFilePath(io.Node) //use the same naming mechanism used by shock server
			//fmt.Printf("moving file from %s to %s\n", file_path, cacheFilePath)
			if err := os.Rename(file_path, cacheFilePath); err != nil {
				logger.Error("cache os.Rename():" + err.Error())
			}
		}
	}
	return
}

func getCacheDir(id string) string {
	if len(id) < 7 {
		return conf.DATA_PATH
	}
	return fmt.Sprintf("%s/%s/%s/%s/%s", conf.DATA_PATH, id[0:2], id[2:4], id[4:6], id)
}

func getCacheFilePath(id string) string {
	cacheDir := getCacheDir(id)
	return fmt.Sprintf("%s/%s.data", cacheDir, id)
}

func StatCacheFilePath(id string) (file_path string, err error) {
	file_path = getCacheFilePath(id)
	_, err = os.Stat(file_path)
	return file_path, err
}

//fetch input data
func MoveInputData(work *core.Workunit) (size int64, err error) {
	for inputname, io := range work.Inputs {

		// skip if NoFile == true
		if !io.NoFile {
			var dataUrl string
			inputFilePath := fmt.Sprintf("%s/%s", work.Path(), inputname)

			if work.Rank == 0 {
				if conf.CACHE_ENABLED && io.Node != "" {
					if file_path, err := StatCacheFilePath(io.Node); err == nil {
						//make a link in work dir from cached file
						linkname := fmt.Sprintf("%s/%s", work.Path(), inputname)
						fmt.Printf("input found in cache, making link: " + file_path + " -> " + linkname + "\n")
						err = os.Symlink(file_path, linkname)
						if err == nil {
							logger.Event(event.FILE_READY, "workid="+work.Id+";url="+dataUrl)
						}
						return 0, err
					}
				}
				dataUrl = io.DataUrl()
			} else {
				dataUrl = fmt.Sprintf("%s&index=%s&part=%s", io.DataUrl(), work.IndexType(), work.Part())
			}
			logger.Debug(2, "mover: fetching input file from url:"+dataUrl)
			logger.Event(event.FILE_IN, "workid="+work.Id+";url="+dataUrl)

			// download file
			if datamoved, err := fetchFile(inputFilePath, dataUrl, work.Info.DataToken); err != nil {
				return size, err
			} else {
				size += datamoved
			}
			logger.Event(event.FILE_READY, "workid="+work.Id+";url="+dataUrl)
		}

		// download node attributes if requested
		if io.AttrFile != "" {
			// get node
			node, err := core.ShockGet(io.Host, io.Node, work.Info.DataToken)
			if err != nil {
				return size, err
			}
			logger.Debug(2, "mover: fetching input attributes from node:"+node.Id)
			logger.Event(event.ATTR_IN, "workid="+work.Id+";node="+node.Id)
			// print node attributes
			attrFilePath := fmt.Sprintf("%s/%s", work.Path(), io.AttrFile)
			attr_json, _ := json.Marshal(node.Attributes)
			if err := ioutil.WriteFile(attrFilePath, attr_json, 0644); err != nil {
				return size, err
			}
			logger.Event(event.ATTR_READY, "workid="+work.Id+";path="+attrFilePath)
		}
	}
	return
}

func isFileExistingInCache(id string) bool {
	file_path := getCacheFilePath(id)
	if _, err := os.Stat(file_path); err == nil {
		return true
	}
	return false
}

//fetch file by shock url
func fetchFile(filename string, url string, token string) (size int64, err error) {
	fmt.Printf("fetching file name=%s, url=%s\n", filename, url)
	localfile, err := os.Create(filename)
	if err != nil {
		return 0, err
	}
	defer localfile.Close()

	var user *httpclient.Auth
	if token != "" {
		user = httpclient.GetUserByTokenAuth(token)
	}

	//download file from Shock
	res, err := httpclient.Get(url, httpclient.Header{}, nil, user)
	if err != nil {
		return 0, err
	}

	defer res.Body.Close()

	if res.StatusCode != 200 { //err in fetching data
		resbody, _ := ioutil.ReadAll(res.Body)
		msg := fmt.Sprintf("op=fetchFile, url=%s, res=%s", url, resbody)
		return 0, errors.New(msg)
	}

	size, err = io.Copy(localfile, res.Body)
	if err != nil {
		return 0, err
	}
	return
}
<|endoftext|>


<commit_before>package ui

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"log"
	"time"

	"github.com/ninjasphere/go-gestic"
	"github.com/ninjasphere/sphere-go-led-controller/fonts/O4b03b"
)

type ColorPane struct {
	image  *image.RGBA
	color  func() color.Color
	draw   func()
	bounds func() image.Rectangle
}

func NewColorPane(in color.Color) *ColorPane {
	pane := &ColorPane{
		color: func() color.Color {
			return in
		},
		image: image.NewRGBA(image.Rect(0, 0, width, height)),
	}
	pane.draw = func() {
		draw.Draw(pane.image, pane.bounds(), &image.Uniform{pane.color()}, image.ZP, draw.Src)
	}
	pane.bounds = func() image.Rectangle {
		return pane.image.Bounds()
	}
	return pane
}

func NewFadingColorPane(in color.Color, d time.Duration) *ColorPane {

	pane := NewColorPane(in)
	start := time.Now()
	pane.color = func() color.Color {
		n := time.Now().Sub(start)
		ratio := 1.0
		if n < d {
			ratio = float64(n) / float64(d)
		}
		r, g, b, a := in.RGBA()
		return color.RGBA{
			R: uint8(uint16((1.0-ratio)*float64(r)) >> 8),
			G: uint8(uint16((1.0-ratio)*float64(g)) >> 8),
			B: uint8(uint16((1.0-ratio)*float64(b)) >> 8),
			A: uint8(a),
		}
	}
	return pane
}

// creates a pane that fades and shrinks towards the center as time progresses
func NewFadingShrinkingColorPane(in color.Color, d time.Duration) *ColorPane {

	pane := NewFadingColorPane(in, d)
	basicDraw := pane.draw
	start := time.Now()
	black := color.RGBA{
		R: 0,
		G: 0,
		B: 0,
		A: 0,
	}

	pane.bounds = func() image.Rectangle {
		n := time.Now().Sub(start)
		dim := 0
		if d > n && d > 0 {
			dim = int(float64(d-n) * 8.0 / float64(d))
		}
		rect := image.Rectangle{
			Min: image.Point{
				X: 8 - dim,
				Y: 8 - dim,
			},
			Max: image.Point{
				X: 8 + dim,
				Y: 8 + dim,
			},
		}
		return rect
	}

	pane.draw = func() {
		draw.Draw(pane.image, pane.image.Bounds(), &image.Uniform{black}, image.ZP, draw.Src)
		basicDraw()
	}

	return pane
}

func (p *ColorPane) Gesture(gesture *gestic.GestureData) {

}

func (p *ColorPane) Render() (*image.RGBA, error) {
	p.draw()
	return p.image, nil
}

func (p *ColorPane) IsDirty() bool {
	return false
}

type TextScrollPane struct {
	text      string
	textWidth int
	position  int
	start     time.Time
}

func NewTextScrollPane(text string) *TextScrollPane {

	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	width := O4b03b.Font.DrawString(img, 0, 0, text, color.Black)
	log.Printf("Text '%s' width: %d", text, width)

	return &TextScrollPane{
		text:      text,
		textWidth: width,
		position:  17,
		start:     time.Now(),
	}
}

func (p *TextScrollPane) Gesture(gesture *gestic.GestureData) {

}

func (p *TextScrollPane) Render() (*image.RGBA, error) {
	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	p.position = p.position - 1
	if p.position < -p.textWidth {
		p.position = 17
	}

	log.Printf("Rendering text '%s' at position %d", p.text, p.position)

	O4b03b.Font.DrawString(img, p.position, 0, p.text, color.White)

	elapsed := time.Now().Sub(p.start)

	elapsedSeconds := int(elapsed.Seconds())

	O4b03b.Font.DrawString(img, 0, 5, "Hey! :)", color.RGBA{0, 255, 255, 255})

	O4b03b.Font.DrawString(img, 0, 11, "02", color.RGBA{255, 0, 0, 255})

	O4b03b.Font.DrawString(img, 9, 11, fmt.Sprintf("%0d", elapsedSeconds), color.RGBA{255, 0, 0, 255})

	O4b03b.Font.DrawString(img, 8, 11, ":", color.RGBA{255, 255, 255, 255})

	return img, nil
}

func (p *TextScrollPane) IsDirty() bool {
	return true
}

type PairingCodePane struct {
	text      string
	textWidth int
}

func NewPairingCodePane(text string) *PairingCodePane {

	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	width := O4b03b.Font.DrawString(img, 0, 0, text, color.Black)
	log.Printf("Text '%s' width: %d", text, width)

	return &PairingCodePane{
		text:      text,
		textWidth: width,
	}
}

func (p *PairingCodePane) Gesture(gesture *gestic.GestureData) {

}

func (p *PairingCodePane) Render() (*image.RGBA, error) {
	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	log.Printf("Rendering text '%s'")

	start := 8 - int((float64(p.textWidth) / float64(2)))

	O4b03b.Font.DrawString(img, start, 4, p.text, color.White)

	return img, nil
}

func (p *PairingCodePane) IsDirty() bool {
	return true
}
<commit_msg>Fix log message.<commit_after>package ui

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"log"
	"time"

	"github.com/ninjasphere/go-gestic"
	"github.com/ninjasphere/sphere-go-led-controller/fonts/O4b03b"
)

type ColorPane struct {
	image  *image.RGBA
	color  func() color.Color
	draw   func()
	bounds func() image.Rectangle
}

func NewColorPane(in color.Color) *ColorPane {
	pane := &ColorPane{
		color: func() color.Color {
			return in
		},
		image: image.NewRGBA(image.Rect(0, 0, width, height)),
	}
	pane.draw = func() {
		draw.Draw(pane.image, pane.bounds(), &image.Uniform{pane.color()}, image.ZP, draw.Src)
	}
	pane.bounds = func() image.Rectangle {
		return pane.image.Bounds()
	}
	return pane
}

func NewFadingColorPane(in color.Color, d time.Duration) *ColorPane {

	pane := NewColorPane(in)
	start := time.Now()
	pane.color = func() color.Color {
		n := time.Now().Sub(start)
		ratio := 1.0
		if n < d {
			ratio = float64(n) / float64(d)
		}
		r, g, b, a := in.RGBA()
		return color.RGBA{
			R: uint8(uint16((1.0-ratio)*float64(r)) >> 8),
			G: uint8(uint16((1.0-ratio)*float64(g)) >> 8),
			B: uint8(uint16((1.0-ratio)*float64(b)) >> 8),
			A: uint8(a),
		}
	}
	return pane
}

// creates a pane that fades and shrinks towards the center as time progresses
func NewFadingShrinkingColorPane(in color.Color, d time.Duration) *ColorPane {

	pane := NewFadingColorPane(in, d)
	basicDraw := pane.draw
	start := time.Now()
	black := color.RGBA{
		R: 0,
		G: 0,
		B: 0,
		A: 0,
	}

	pane.bounds = func() image.Rectangle {
		n := time.Now().Sub(start)
		dim := 0
		if d > n && d > 0 {
			dim = int(float64(d-n) * 8.0 / float64(d))
		}
		rect := image.Rectangle{
			Min: image.Point{
				X: 8 - dim,
				Y: 8 - dim,
			},
			Max: image.Point{
				X: 8 + dim,
				Y: 8 + dim,
			},
		}
		return rect
	}

	pane.draw = func() {
		draw.Draw(pane.image, pane.image.Bounds(), &image.Uniform{black}, image.ZP, draw.Src)
		basicDraw()
	}

	return pane
}

func (p *ColorPane) Gesture(gesture *gestic.GestureData) {

}

func (p *ColorPane) Render() (*image.RGBA, error) {
	p.draw()
	return p.image, nil
}

func (p *ColorPane) IsDirty() bool {
	return false
}

type TextScrollPane struct {
	text      string
	textWidth int
	position  int
	start     time.Time
}

func NewTextScrollPane(text string) *TextScrollPane {

	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	width := O4b03b.Font.DrawString(img, 0, 0, text, color.Black)
	log.Printf("Text '%s' width: %d", text, width)

	return &TextScrollPane{
		text:      text,
		textWidth: width,
		position:  17,
		start:     time.Now(),
	}
}

func (p *TextScrollPane) Gesture(gesture *gestic.GestureData) {

}

func (p *TextScrollPane) Render() (*image.RGBA, error) {
	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	p.position = p.position - 1
	if p.position < -p.textWidth {
		p.position = 17
	}

	log.Printf("Rendering text '%s' at position %d", p.text, p.position)

	O4b03b.Font.DrawString(img, p.position, 0, p.text, color.White)

	elapsed := time.Now().Sub(p.start)

	elapsedSeconds := int(elapsed.Seconds())

	O4b03b.Font.DrawString(img, 0, 5, "Hey! :)", color.RGBA{0, 255, 255, 255})

	O4b03b.Font.DrawString(img, 0, 11, "02", color.RGBA{255, 0, 0, 255})

	O4b03b.Font.DrawString(img, 9, 11, fmt.Sprintf("%0d", elapsedSeconds), color.RGBA{255, 0, 0, 255})

	O4b03b.Font.DrawString(img, 8, 11, ":", color.RGBA{255, 255, 255, 255})

	return img, nil
}

func (p *TextScrollPane) IsDirty() bool {
	return true
}

type PairingCodePane struct {
	text      string
	textWidth int
}

func NewPairingCodePane(text string) *PairingCodePane {

	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	width := O4b03b.Font.DrawString(img, 0, 0, text, color.Black)
	log.Printf("Text '%s' width: %d", text, width)

	return &PairingCodePane{
		text:      text,
		textWidth: width,
	}
}

func (p *PairingCodePane) Gesture(gesture *gestic.GestureData) {

}

func (p *PairingCodePane) Render() (*image.RGBA, error) {
	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	log.Printf("Rendering text '%s'", text)

	start := 8 - int((float64(p.textWidth) / float64(2)))

	O4b03b.Font.DrawString(img, start, 4, p.text, color.White)

	return img, nil
}

func (p *PairingCodePane) IsDirty() bool {
	return true
}
<|endoftext|>