<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.
*/

// evolution of: https://github.com/GoogleCloudPlatform/kubernetes/blob/release-0.6/pkg/client/cache/fifo.go
package queue

import (
	"fmt"
	"reflect"
	"sync"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/config"
)

type EventType int

const (
	ADD_EVENT EventType = iota
	UPDATE_EVENT
	DELETE_EVENT
	POP_EVENT
)

type Entry struct {
	Value UniqueCopyable
	Event EventType
}

type Copyable interface {
	// return an independent copy (deep clone) of the current object
	Copy() Copyable
}

type UniqueID interface {
	GetUID() string
}

type UniqueCopyable interface {
	Copyable
	UniqueID
}

func (e *Entry) Copy() Copyable {
	return &Entry{Value: e.Value.Copy().(UniqueCopyable), Event: e.Event}
}

// deliver a message
type pigeon func(msg *Entry)

func dead(msg *Entry) {
	// intentionally blank
}

// HistoricalFIFO receives adds and updates from a Reflector, and puts them in a queue for
// FIFO order processing. If multiple adds/updates of a single item happen while
// an item is in the queue before it has been processed, it will only be
// processed once, and when it is processed, the most recent version will be
// processed. This can't be done with a channel.
// TODO(jdef): I used to think that I'd need history of state changes recorded
// in `history` but it's turning out that I really only need to maintain the
// the current state, so `history` should revert back into `items`
type HistoricalFIFO struct {
	lock    sync.RWMutex
	cond    sync.Cond
	history map[string][]*Entry // We depend on the property that items in the queue are in the set.
	queue   []string
	carrier pigeon // may be dead, but never nil
}

// panics if obj doesn't implement UniqueCopyable; otherwise returns the same, typecast object
func checkType(obj interface{}) UniqueCopyable {
	if v, ok := obj.(UniqueCopyable); !ok {
		panic(fmt.Sprintf("Illegal object type, expected UniqueCopyable: %T", obj))
	} else {
		return v
	}
}

// Add inserts an item, and puts it in the queue. The item is only enqueued
// if it doesn't already exist in the set.
func (f *HistoricalFIFO) Add(id string, v interface{}) {
	obj := checkType(v)
	notifications := []*Entry(nil)
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	if entries, exists := f.history[id]; !exists {
		f.queue = append(f.queue, id)
	} else {
		head := entries[len(entries)-1]
		if head.Event == DELETE_EVENT || head.Event == POP_EVENT {
			f.queue = append(f.queue, id)
		}
	}
	notifications = f.merge(id, obj, nil)
	f.cond.Broadcast()
}

// Update is the same as Add in this implementation.
func (f *HistoricalFIFO) Update(id string, obj interface{}) {
	f.Add(id, obj)
}

// Add the item to the store, but only if there exists a prior entry for
// for the object in the store whose event type matches that given, and then
// only enqueued if it doesn't already exist in the set.
func (f *HistoricalFIFO) Readd(id string, v interface{}, t EventType) {
	obj := checkType(v)
	notifications := []*Entry(nil)
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	if entries, exists := f.history[id]; exists {
		head := entries[len(entries)-1]
		if head.Event != t {
			return
		} else if head.Event == DELETE_EVENT || head.Event == POP_EVENT {
			f.queue = append(f.queue, id)
		}
	}
	notifications = f.merge(id, obj, nil)
	f.cond.Broadcast()
}

// Delete removes an item. It doesn't add it to the queue, because
// this implementation assumes the consumer only cares about the objects,
// not the order in which they were created/added.
func (f *HistoricalFIFO) Delete(id string) {
	deleteEvent := (*Entry)(nil)
	defer func() {
		f.carrier(deleteEvent)
	}()

	f.lock.Lock()
	defer f.lock.Unlock()
	entries, exists := f.history[id]
	if exists {
		//TODO(jdef): set a timer to expunge the history for this object
		//or else, simply do garbage collection every n'th merge(), removing
		//expired DELETE entries
		head := entries[len(entries)-1]
		deleteEvent = &Entry{Value: head.Value, Event: DELETE_EVENT}
		f.history[id] = append(entries, deleteEvent)
	}
}

// List returns a list of all the items.
func (f *HistoricalFIFO) List() []interface{} {
	f.lock.RLock()
	defer f.lock.RUnlock()

	// TODO(jdef): slightly overallocates b/c of deleted items
	list := make([]interface{}, 0, len(f.queue))

	for _, entries := range f.history {
		head := entries[len(entries)-1]
		if head.Event == DELETE_EVENT || head.Event == POP_EVENT {
			continue
		}
		list = append(list, head.Value.Copy())
	}
	return list
}

// ContainedIDs returns a util.StringSet containing all IDs of the stored items.
// This is a snapshot of a moment in time, and one should keep in mind that
// other go routines can add or remove items after you call this.
func (c *HistoricalFIFO) ContainedIDs() util.StringSet {
	c.lock.RLock()
	defer c.lock.RUnlock()
	set := util.StringSet{}
	for id, entries := range c.history {
		head := entries[len(entries)-1]
		if head.Event == DELETE_EVENT || head.Event == POP_EVENT {
			continue
		}
		set.Insert(id)
	}
	return set
}

// Get returns the requested item, or sets exists=false.
func (f *HistoricalFIFO) Get(id string) (interface{}, bool) {
	f.lock.RLock()
	defer f.lock.RUnlock()
	entries, exists := f.history[id]
	head := entries[len(entries)-1]
	if exists && !(head.Event == DELETE_EVENT || head.Event == POP_EVENT) {
		return head.Value.Copy(), true
	}
	return nil, false
}

// Pop waits until an item is ready and returns it. If multiple items are
// ready, they are returned in the order in which they were added/updated.
// The item is removed from the queue (and the store) before it is returned,
// so if you don't succesfully process it, you need to add it back with Add().
func (f *HistoricalFIFO) Pop() interface{} {
	popEvent := (*Entry)(nil)
	defer func() {
		f.carrier(popEvent)
	}()

	f.lock.Lock()
	defer f.lock.Unlock()
	for {
		for len(f.queue) == 0 {
			f.cond.Wait()
		}
		id := f.queue[0]
		f.queue = f.queue[1:]
		entries, ok := f.history[id]
		head := entries[len(entries)-1]
		if !ok || head.Event == DELETE_EVENT || head.Event == POP_EVENT {
			// Item may have been deleted subsequently.
			continue
		}
		value := head.Value
		popEvent = &Entry{Value: value, Event: POP_EVENT}
		f.history[id] = append(entries, popEvent)
		return value.Copy()
	}
}

// Replace will delete the contents of 'f', using instead the given map.
// 'f' takes ownersip of the map, you should not reference the map again
// after calling this function. f's queue is reset, too; upon return, it
// will contain the items in the map, in no particular order.
func (f *HistoricalFIFO) Replace(idToObj map[string]interface{}) {
	notifications := make([]*Entry, 0, len(idToObj))
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	f.queue = f.queue[:0]
	for id, v := range idToObj {
		obj := checkType(v)
		f.queue = append(f.queue, id)
		n := f.merge(id, obj, nil)
		notifications = append(notifications, n...)
	}
	if len(f.queue) > 0 {
		f.cond.Broadcast()
	}
}

type mergeFilter func(older, newer *Entry) bool

// expects that caller has already locked around state
//TODO(jdef): eliminate the use of mergeFilter if we don't end up needing it
func (f *HistoricalFIFO) merge(id string, obj UniqueCopyable, accepts mergeFilter) (notifications []*Entry) {
	entries, exists := f.history[id]
	if !exists {
		entries = make([]*Entry, 0, 3)
		e := &Entry{Value: obj.Copy().(UniqueCopyable), Event: ADD_EVENT}
		if accepts == nil || accepts(nil, e) {
			f.history[id] = append(entries, e)
			notifications = append(notifications, e)
		}
	} else {
		head := entries[len(entries)-1]
		if head.Event != DELETE_EVENT && head.Value.GetUID() != obj.GetUID() {
			// hidden DELETE!
			// (1) append a DELETE
			// (2) append an ADD
			// .. and notify listeners in that order
			e1 := &Entry{Value: head.Value, Event: DELETE_EVENT}
			e2 := &Entry{Value: obj.Copy().(UniqueCopyable), Event: ADD_EVENT}
			if accepts == nil || accepts(e1, e2) {
				f.history[id] = append(entries, e1, e2)
				notifications = append(notifications, e1, e2)
			}
		} else if !reflect.DeepEqual(obj, head.Value) {
			//TODO(jdef): it would be nice if we could rely on resource versions
			//instead of doing a DeepEqual. Maybe someday we'll be able to.
			e := &Entry{Value: obj.Copy().(UniqueCopyable), Event: UPDATE_EVENT}
			if accepts == nil || accepts(head, e) {
				f.history[id] = append(entries, e)
				notifications = append(notifications, e)
			}
		}
	}
	return
}

// NewFIFO returns a Store which can be used to queue up items to
// process. If a non-nil Mux is provided, then modifications to the
// the FIFO are delivered on a channel specific to this fifo.
func NewFIFO(mux *config.Mux) *HistoricalFIFO {
	carrier := dead
	if mux != nil {
		//TODO(jdef): append a UUID to "fifo" here?
		ch := mux.Channel("fifo")
		carrier = func(msg *Entry) {
			if msg != nil {
				ch <- msg.Copy()
			}
		}
	}
	f := &HistoricalFIFO{
		history: map[string][]*Entry{},
		queue:   []string{},
		carrier: carrier,
	}
	f.cond.L = &f.lock
	return f
}
<commit_msg>revert history to items; update Replace to consider items not present in the replacment map as deleted<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.
*/

// evolution of: https://github.com/GoogleCloudPlatform/kubernetes/blob/release-0.6/pkg/client/cache/fifo.go
package queue

import (
	"fmt"
	"reflect"
	"sync"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/config"
)

type EventType int

const (
	ADD_EVENT EventType = iota
	UPDATE_EVENT
	DELETE_EVENT
	POP_EVENT
)

type Entry struct {
	Value UniqueCopyable
	Event EventType
}

type Copyable interface {
	// return an independent copy (deep clone) of the current object
	Copy() Copyable
}

type UniqueID interface {
	GetUID() string
}

type UniqueCopyable interface {
	Copyable
	UniqueID
}

func (e *Entry) Copy() Copyable {
	return &Entry{Value: e.Value.Copy().(UniqueCopyable), Event: e.Event}
}

// deliver a message
type pigeon func(msg *Entry)

func dead(msg *Entry) {
	// intentionally blank
}

// HistoricalFIFO receives adds and updates from a Reflector, and puts them in a queue for
// FIFO order processing. If multiple adds/updates of a single item happen while
// an item is in the queue before it has been processed, it will only be
// processed once, and when it is processed, the most recent version will be
// processed. This can't be done with a channel.
type HistoricalFIFO struct {
	lock    sync.RWMutex
	cond    sync.Cond
	items   map[string]*Entry // We depend on the property that items in the queue are in the set.
	queue   []string
	carrier pigeon // may be dead, but never nil
}

// panics if obj doesn't implement UniqueCopyable; otherwise returns the same, typecast object
func checkType(obj interface{}) UniqueCopyable {
	if v, ok := obj.(UniqueCopyable); !ok {
		panic(fmt.Sprintf("Illegal object type, expected UniqueCopyable: %T", obj))
	} else {
		return v
	}
}

// Add inserts an item, and puts it in the queue. The item is only enqueued
// if it doesn't already exist in the set.
func (f *HistoricalFIFO) Add(id string, v interface{}) {
	obj := checkType(v)
	notifications := []*Entry(nil)
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	if entry, exists := f.items[id]; !exists {
		f.queue = append(f.queue, id)
	} else {
		if entry.Event == DELETE_EVENT || entry.Event == POP_EVENT {
			f.queue = append(f.queue, id)
		}
	}
	notifications = f.merge(id, obj)
	f.cond.Broadcast()
}

// Update is the same as Add in this implementation.
func (f *HistoricalFIFO) Update(id string, obj interface{}) {
	f.Add(id, obj)
}

// Add the item to the store, but only if there exists a prior entry for
// for the object in the store whose event type matches that given, and then
// only enqueued if it doesn't already exist in the set.
func (f *HistoricalFIFO) Readd(id string, v interface{}, t EventType) {
	obj := checkType(v)
	notifications := []*Entry(nil)
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	if entry, exists := f.items[id]; exists {
		if entry.Event != t {
			return
		} else if entry.Event == DELETE_EVENT || entry.Event == POP_EVENT {
			f.queue = append(f.queue, id)
		}
	}
	notifications = f.merge(id, obj)
	f.cond.Broadcast()
}

// Delete removes an item. It doesn't add it to the queue, because
// this implementation assumes the consumer only cares about the objects,
// not the order in which they were created/added.
func (f *HistoricalFIFO) Delete(id string) {
	deleteEvent := (*Entry)(nil)
	defer func() {
		f.carrier(deleteEvent)
	}()

	f.lock.Lock()
	defer f.lock.Unlock()
	entry, exists := f.items[id]
	if exists {
		//TODO(jdef): set a timer to expunge the history for this object
		//or else, simply do garbage collection every n'th merge(), removing
		//expired DELETE entries
		deleteEvent = &Entry{Value: entry.Value, Event: DELETE_EVENT}
		f.items[id] = deleteEvent
	}
}

// List returns a list of all the items.
func (f *HistoricalFIFO) List() []interface{} {
	f.lock.RLock()
	defer f.lock.RUnlock()

	// TODO(jdef): slightly overallocates b/c of deleted items
	list := make([]interface{}, 0, len(f.queue))

	for _, entry := range f.items {
		if entry.Event == DELETE_EVENT || entry.Event == POP_EVENT {
			continue
		}
		list = append(list, entry.Value.Copy())
	}
	return list
}

// ContainedIDs returns a util.StringSet containing all IDs of the stored items.
// This is a snapshot of a moment in time, and one should keep in mind that
// other go routines can add or remove items after you call this.
func (c *HistoricalFIFO) ContainedIDs() util.StringSet {
	c.lock.RLock()
	defer c.lock.RUnlock()
	set := util.StringSet{}
	for id, entry := range c.items {
		if entry.Event == DELETE_EVENT || entry.Event == POP_EVENT {
			continue
		}
		set.Insert(id)
	}
	return set
}

// Get returns the requested item, or sets exists=false.
func (f *HistoricalFIFO) Get(id string) (interface{}, bool) {
	f.lock.RLock()
	defer f.lock.RUnlock()
	entry, exists := f.items[id]
	if exists && !(entry.Event == DELETE_EVENT || entry.Event == POP_EVENT) {
		return entry.Value.Copy(), true
	}
	return nil, false
}

// Pop waits until an item is ready and returns it. If multiple items are
// ready, they are returned in the order in which they were added/updated.
// The item is removed from the queue (and the store) before it is returned,
// so if you don't succesfully process it, you need to add it back with Add().
func (f *HistoricalFIFO) Pop() interface{} {
	popEvent := (*Entry)(nil)
	defer func() {
		f.carrier(popEvent)
	}()

	f.lock.Lock()
	defer f.lock.Unlock()
	for {
		for len(f.queue) == 0 {
			f.cond.Wait()
		}
		id := f.queue[0]
		f.queue = f.queue[1:]
		entry, ok := f.items[id]
		if !ok || entry.Event == DELETE_EVENT || entry.Event == POP_EVENT {
			// Item may have been deleted subsequently.
			continue
		}
		value := entry.Value
		popEvent = &Entry{Value: value, Event: POP_EVENT}
		f.items[id] = popEvent
		return value.Copy()
	}
}

// Replace will delete the contents of 'f', using instead the given map.
// 'f' takes ownersip of the map, you should not reference the map again
// after calling this function. f's queue is reset, too; upon return, it
// will contain the items in the map, in no particular order.
func (f *HistoricalFIFO) Replace(idToObj map[string]interface{}) {
	notifications := make([]*Entry, 0, len(idToObj))
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	f.queue = f.queue[:0]
	for id, v := range f.items {
		if _, exists := idToObj[id]; !exists && v.Event != DELETE_EVENT {
			// a non-deleted entry in the items list that doesn't show up in the
			// new list: mark it as deleted
			e := &Entry{Value: v.Value, Event: DELETE_EVENT}
			f.items[id] = e
			notifications = append(notifications, e)
		}
	}
	for id, v := range idToObj {
		obj := checkType(v)
		f.queue = append(f.queue, id)
		n := f.merge(id, obj)
		notifications = append(notifications, n...)
	}
	if len(f.queue) > 0 {
		f.cond.Broadcast()
	}
}

// expects that caller has already locked around state
func (f *HistoricalFIFO) merge(id string, obj UniqueCopyable) (notifications []*Entry) {
	entry, exists := f.items[id]
	if !exists {
		e := &Entry{Value: obj.Copy().(UniqueCopyable), Event: ADD_EVENT}
		f.items[id] = e
		notifications = append(notifications, e)
	} else {
		if entry.Event != DELETE_EVENT && entry.Value.GetUID() != obj.GetUID() {
			// hidden DELETE!
			// (1) append a DELETE
			// (2) append an ADD
			// .. and notify listeners in that order
			e1 := &Entry{Value: entry.Value, Event: DELETE_EVENT}
			e2 := &Entry{Value: obj.Copy().(UniqueCopyable), Event: ADD_EVENT}
			f.items[id] = e2
			notifications = append(notifications, e1, e2)
		} else if !reflect.DeepEqual(obj, entry.Value) {
			//TODO(jdef): it would be nice if we could rely on resource versions
			//instead of doing a DeepEqual. Maybe someday we'll be able to.
			e := &Entry{Value: obj.Copy().(UniqueCopyable), Event: UPDATE_EVENT}
			f.items[id] = e
			notifications = append(notifications, e)
		}
	}
	return
}

// NewFIFO returns a Store which can be used to queue up items to
// process. If a non-nil Mux is provided, then modifications to the
// the FIFO are delivered on a channel specific to this fifo.
func NewFIFO(mux *config.Mux) *HistoricalFIFO {
	carrier := dead
	if mux != nil {
		//TODO(jdef): append a UUID to "fifo" here?
		ch := mux.Channel("fifo")
		carrier = func(msg *Entry) {
			if msg != nil {
				ch <- msg.Copy()
			}
		}
	}
	f := &HistoricalFIFO{
		items:   map[string]*Entry{},
		queue:   []string{},
		carrier: carrier,
	}
	f.cond.L = &f.lock
	return f
}
<|endoftext|>


<commit_before>package app

import (
	"fmt"
	"net/http"
	"sort"
	"sync"

	"github.com/gorilla/mux"
	"golang.org/x/net/context"

	"github.com/weaveworks/scope/probe/kubernetes"
	"github.com/weaveworks/scope/render"
	"github.com/weaveworks/scope/report"
)

const apiTopologyURL = "/api/topology/"

var (
	topologyRegistry = &registry{
		items: map[string]APITopologyDesc{},
	}
)

func init() {
	containerFilters := []APITopologyOptionGroup{
		{
			ID:      "system",
			Default: "application",
			Options: []APITopologyOption{
				{"system", "System containers", render.IsSystem},
				{"application", "Application containers", render.IsApplication},
				{"both", "Both", nil},
			},
		},
		{
			ID:      "stopped",
			Default: "running",
			Options: []APITopologyOption{
				{"stopped", "Stopped containers", render.IsStopped},
				{"running", "Running containers", render.IsRunning},
				{"both", "Both", nil},
			},
		},
	}

	unconnectedFilter := []APITopologyOptionGroup{
		{
			ID:      "unconnected",
			Default: "hide",
			Options: []APITopologyOption{
				// Show the user why there are filtered nodes in this view.
				// Don't give them the option to show those nodes.
				{"hide", "Unconnected nodes hidden", nil},
			},
		},
	}

	// Topology option labels should tell the current state. The first item must
	// be the verb to get to that state
	topologyRegistry.add(
		APITopologyDesc{
			id:       "processes",
			renderer: render.FilterUnconnected(render.ProcessWithContainerNameRenderer),
			Name:     "Processes",
			Rank:     1,
			Options:  unconnectedFilter,
		},
		APITopologyDesc{
			id:       "processes-by-name",
			parent:   "processes",
			renderer: render.FilterUnconnected(render.ProcessNameRenderer),
			Name:     "by name",
			Options:  unconnectedFilter,
		},
		APITopologyDesc{
			id:       "containers",
			renderer: render.ContainerWithImageNameRenderer,
			Name:     "Containers",
			Rank:     2,
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:       "containers-by-image",
			parent:   "containers",
			renderer: render.ContainerImageRenderer,
			Name:     "by image",
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:       "containers-by-hostname",
			parent:   "containers",
			renderer: render.ContainerHostnameRenderer,
			Name:     "by DNS name",
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:          "pods",
			renderer:    render.PodRenderer,
			Name:        "Pods",
			Rank:        3,
			HideIfEmpty: true,
		},
		APITopologyDesc{
			id:          "pods-by-service",
			parent:      "pods",
			renderer:    render.PodServiceRenderer,
			Name:        "by service",
			HideIfEmpty: true,
		},
		APITopologyDesc{
			id:       "hosts",
			renderer: render.HostRenderer,
			Name:     "Hosts",
			Rank:     4,
		},
	)
}

// kubernetesFilters generates the current kubernetes filters based on the
// available k8s topologies.
func kubernetesFilters(namespaces ...string) APITopologyOptionGroup {
	options := APITopologyOptionGroup{ID: "namespace", Default: "all"}
	for _, namespace := range namespaces {
		options.Options = append(options.Options, APITopologyOption{namespace, namespace, render.IsNamespace(namespace)})
	}
	options.Options = append(options.Options, APITopologyOption{"all", "All Namespaces", nil})
	return options
}

// updateFilters updates the available filters based on the current report.
// Currently only kubernetes changes.
func updateFilters(rpt report.Report, topologies []APITopologyDesc) []APITopologyDesc {
	namespaces := map[string]struct{}{}
	for _, t := range []report.Topology{rpt.Pod, rpt.Service} {
		for _, n := range t.Nodes {
			if state, ok := n.Latest.Lookup(kubernetes.PodState); ok && state == kubernetes.StateDeleted {
				continue
			}
			if namespace, ok := n.Latest.Lookup(kubernetes.Namespace); ok {
				namespaces[namespace] = struct{}{}
			}
		}
	}
	var ns []string
	for namespace := range namespaces {
		ns = append(ns, namespace)
	}
	sort.Strings(ns)
	for i, t := range topologies {
		if t.id == "pods" || t.id == "pods-by-service" {
			topologies[i] = updateTopologyFilters(t, []APITopologyOptionGroup{kubernetesFilters(ns...)})
		}
	}
	return topologies
}

// updateTopologyFilters recursively sets the options on a topology description
func updateTopologyFilters(t APITopologyDesc, options []APITopologyOptionGroup) APITopologyDesc {
	t.Options = options
	for i, sub := range t.SubTopologies {
		t.SubTopologies[i] = updateTopologyFilters(sub, options)
	}
	return t
}

// registry is a threadsafe store of the available topologies
type registry struct {
	sync.RWMutex
	items map[string]APITopologyDesc
}

// APITopologyDesc is returned in a list by the /api/topology handler.
type APITopologyDesc struct {
	id       string
	parent   string
	renderer render.Renderer

	Name        string                   `json:"name"`
	Rank        int                      `json:"rank"`
	HideIfEmpty bool                     `json:"hide_if_empty"`
	Options     []APITopologyOptionGroup `json:"options"`

	URL           string            `json:"url"`
	SubTopologies []APITopologyDesc `json:"sub_topologies,omitempty"`
	Stats         topologyStats     `json:"stats,omitempty"`
}

type byName []APITopologyDesc

func (a byName) Len() int           { return len(a) }
func (a byName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name }

// APITopologyOptionGroup describes a group of APITopologyOptions
type APITopologyOptionGroup struct {
	ID      string              `json:"id"`
	Default string              `json:"defaultValue,omitempty"`
	Options []APITopologyOption `json:"options,omitempty"`
}

// APITopologyOption describes a &param=value to a given topology.
type APITopologyOption struct {
	Value string `json:"value"`
	Label string `json:"label"`

	filter render.FilterFunc
}

type topologyStats struct {
	NodeCount          int `json:"node_count"`
	NonpseudoNodeCount int `json:"nonpseudo_node_count"`
	EdgeCount          int `json:"edge_count"`
	FilteredNodes      int `json:"filtered_nodes"`
}

func (r *registry) add(ts ...APITopologyDesc) {
	r.Lock()
	defer r.Unlock()
	for _, t := range ts {
		t.URL = apiTopologyURL + t.id

		if t.parent != "" {
			parent := r.items[t.parent]
			parent.SubTopologies = append(parent.SubTopologies, t)
			sort.Sort(byName(parent.SubTopologies))
			r.items[t.parent] = parent
		}

		r.items[t.id] = t
	}
}

func (r *registry) get(name string) (APITopologyDesc, bool) {
	r.RLock()
	defer r.RUnlock()
	t, ok := r.items[name]
	return t, ok
}

func (r *registry) walk(f func(APITopologyDesc)) {
	r.RLock()
	defer r.RUnlock()
	descs := []APITopologyDesc{}
	for _, desc := range r.items {
		if desc.parent != "" {
			continue
		}
		descs = append(descs, desc)
	}
	sort.Sort(byName(descs))
	for _, desc := range descs {
		f(desc)
	}
}

// makeTopologyList returns a handler that yields an APITopologyList.
func (r *registry) makeTopologyList(rep Reporter) CtxHandlerFunc {
	return func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
		report, err := rep.Report(ctx)
		if err != nil {
			respondWith(w, http.StatusInternalServerError, err.Error())
			return
		}
		respondWith(w, http.StatusOK, r.renderTopologies(report, req))
	}
}

func (r *registry) renderTopologies(rpt report.Report, req *http.Request) []APITopologyDesc {
	topologies := []APITopologyDesc{}
	req.ParseForm()
	values := map[string]string{}
	for k, vs := range req.Form {
		values[k] = vs[0]
	}
	r.walk(func(desc APITopologyDesc) {
		renderer, decorator, _ := r.rendererForTopology(desc.id, values, rpt)
		desc.Stats = decorateWithStats(rpt, renderer, decorator)
		for i := range desc.SubTopologies {
			renderer, decorator, _ := r.rendererForTopology(desc.id, values, rpt)
			desc.SubTopologies[i].Stats = decorateWithStats(rpt, renderer, decorator)
		}
		topologies = append(topologies, desc)
	})
	return updateFilters(rpt, topologies)
}

func decorateWithStats(rpt report.Report, renderer render.Renderer, decorator render.Decorator) topologyStats {
	var (
		nodes     int
		realNodes int
		edges     int
	)
	for _, n := range renderer.Render(rpt, decorator) {
		nodes++
		if n.Topology != render.Pseudo {
			realNodes++
		}
		edges += len(n.Adjacency)
	}
	renderStats := renderer.Stats(rpt, decorator)
	return topologyStats{
		NodeCount:          nodes,
		NonpseudoNodeCount: realNodes,
		EdgeCount:          edges,
		FilteredNodes:      renderStats.FilteredNodes,
	}
}

func (r *registry) rendererForTopology(id string, values map[string]string, rpt report.Report) (render.Renderer, render.Decorator, error) {
	topology, ok := r.get(id)
	if !ok {
		return nil, nil, fmt.Errorf("topology not found: %s", id)
	}
	topology = updateFilters(rpt, []APITopologyDesc{topology})[0]

	var filters []render.FilterFunc
	for _, group := range topology.Options {
		value := values[group.ID]
		for _, opt := range group.Options {
			if opt.filter == nil {
				continue
			}
			if (value == "" && group.Default == opt.Value) || (opt.Value != "" && opt.Value == value) {
				filters = append(filters, opt.filter)
			}
		}
	}
	var decorator render.Decorator
	if len(filters) > 0 {
		decorator = func(renderer render.Renderer) render.Renderer {
			return render.MakeFilter(render.ComposeFilterFuncs(filters...), renderer)
		}
	}
	return topology.renderer, decorator, nil
}

type reportRenderHandler func(context.Context, Reporter, http.ResponseWriter, *http.Request)

func (r *registry) rendererForRequest(req *http.Request, rpt report.Report) (render.Renderer, render.Decorator, error) {
	req.ParseForm()
	values := map[string]string{}
	for k, vs := range req.Form {
		values[k] = vs[0]
	}
	return r.rendererForTopology(mux.Vars(req)["topology"], values, rpt)
}

func captureReporter(rep Reporter, f reportRenderHandler) CtxHandlerFunc {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		f(ctx, rep, w, r)
	}
}
<commit_msg>fix up stats on sub-topologies<commit_after>package app

import (
	"fmt"
	"net/http"
	"sort"
	"sync"

	"github.com/gorilla/mux"
	"golang.org/x/net/context"

	"github.com/weaveworks/scope/probe/kubernetes"
	"github.com/weaveworks/scope/render"
	"github.com/weaveworks/scope/report"
)

const apiTopologyURL = "/api/topology/"

var (
	topologyRegistry = &registry{
		items: map[string]APITopologyDesc{},
	}
)

func init() {
	containerFilters := []APITopologyOptionGroup{
		{
			ID:      "system",
			Default: "application",
			Options: []APITopologyOption{
				{"system", "System containers", render.IsSystem},
				{"application", "Application containers", render.IsApplication},
				{"both", "Both", nil},
			},
		},
		{
			ID:      "stopped",
			Default: "running",
			Options: []APITopologyOption{
				{"stopped", "Stopped containers", render.IsStopped},
				{"running", "Running containers", render.IsRunning},
				{"both", "Both", nil},
			},
		},
	}

	unconnectedFilter := []APITopologyOptionGroup{
		{
			ID:      "unconnected",
			Default: "hide",
			Options: []APITopologyOption{
				// Show the user why there are filtered nodes in this view.
				// Don't give them the option to show those nodes.
				{"hide", "Unconnected nodes hidden", nil},
			},
		},
	}

	// Topology option labels should tell the current state. The first item must
	// be the verb to get to that state
	topologyRegistry.add(
		APITopologyDesc{
			id:       "processes",
			renderer: render.FilterUnconnected(render.ProcessWithContainerNameRenderer),
			Name:     "Processes",
			Rank:     1,
			Options:  unconnectedFilter,
		},
		APITopologyDesc{
			id:       "processes-by-name",
			parent:   "processes",
			renderer: render.FilterUnconnected(render.ProcessNameRenderer),
			Name:     "by name",
			Options:  unconnectedFilter,
		},
		APITopologyDesc{
			id:       "containers",
			renderer: render.ContainerWithImageNameRenderer,
			Name:     "Containers",
			Rank:     2,
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:       "containers-by-image",
			parent:   "containers",
			renderer: render.ContainerImageRenderer,
			Name:     "by image",
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:       "containers-by-hostname",
			parent:   "containers",
			renderer: render.ContainerHostnameRenderer,
			Name:     "by DNS name",
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:          "pods",
			renderer:    render.PodRenderer,
			Name:        "Pods",
			Rank:        3,
			HideIfEmpty: true,
		},
		APITopologyDesc{
			id:          "pods-by-service",
			parent:      "pods",
			renderer:    render.PodServiceRenderer,
			Name:        "by service",
			HideIfEmpty: true,
		},
		APITopologyDesc{
			id:       "hosts",
			renderer: render.HostRenderer,
			Name:     "Hosts",
			Rank:     4,
		},
	)
}

// kubernetesFilters generates the current kubernetes filters based on the
// available k8s topologies.
func kubernetesFilters(namespaces ...string) APITopologyOptionGroup {
	options := APITopologyOptionGroup{ID: "namespace", Default: "all"}
	for _, namespace := range namespaces {
		options.Options = append(options.Options, APITopologyOption{namespace, namespace, render.IsNamespace(namespace)})
	}
	options.Options = append(options.Options, APITopologyOption{"all", "All Namespaces", nil})
	return options
}

// updateFilters updates the available filters based on the current report.
// Currently only kubernetes changes.
func updateFilters(rpt report.Report, topologies []APITopologyDesc) []APITopologyDesc {
	namespaces := map[string]struct{}{}
	for _, t := range []report.Topology{rpt.Pod, rpt.Service} {
		for _, n := range t.Nodes {
			if state, ok := n.Latest.Lookup(kubernetes.PodState); ok && state == kubernetes.StateDeleted {
				continue
			}
			if namespace, ok := n.Latest.Lookup(kubernetes.Namespace); ok {
				namespaces[namespace] = struct{}{}
			}
		}
	}
	var ns []string
	for namespace := range namespaces {
		ns = append(ns, namespace)
	}
	sort.Strings(ns)
	for i, t := range topologies {
		if t.id == "pods" || t.id == "pods-by-service" {
			topologies[i] = updateTopologyFilters(t, []APITopologyOptionGroup{kubernetesFilters(ns...)})
		}
	}
	return topologies
}

// updateTopologyFilters recursively sets the options on a topology description
func updateTopologyFilters(t APITopologyDesc, options []APITopologyOptionGroup) APITopologyDesc {
	t.Options = options
	for i, sub := range t.SubTopologies {
		t.SubTopologies[i] = updateTopologyFilters(sub, options)
	}
	return t
}

// registry is a threadsafe store of the available topologies
type registry struct {
	sync.RWMutex
	items map[string]APITopologyDesc
}

// APITopologyDesc is returned in a list by the /api/topology handler.
type APITopologyDesc struct {
	id       string
	parent   string
	renderer render.Renderer

	Name        string                   `json:"name"`
	Rank        int                      `json:"rank"`
	HideIfEmpty bool                     `json:"hide_if_empty"`
	Options     []APITopologyOptionGroup `json:"options"`

	URL           string            `json:"url"`
	SubTopologies []APITopologyDesc `json:"sub_topologies,omitempty"`
	Stats         topologyStats     `json:"stats,omitempty"`
}

type byName []APITopologyDesc

func (a byName) Len() int           { return len(a) }
func (a byName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name }

// APITopologyOptionGroup describes a group of APITopologyOptions
type APITopologyOptionGroup struct {
	ID      string              `json:"id"`
	Default string              `json:"defaultValue,omitempty"`
	Options []APITopologyOption `json:"options,omitempty"`
}

// APITopologyOption describes a &param=value to a given topology.
type APITopologyOption struct {
	Value string `json:"value"`
	Label string `json:"label"`

	filter render.FilterFunc
}

type topologyStats struct {
	NodeCount          int `json:"node_count"`
	NonpseudoNodeCount int `json:"nonpseudo_node_count"`
	EdgeCount          int `json:"edge_count"`
	FilteredNodes      int `json:"filtered_nodes"`
}

func (r *registry) add(ts ...APITopologyDesc) {
	r.Lock()
	defer r.Unlock()
	for _, t := range ts {
		t.URL = apiTopologyURL + t.id

		if t.parent != "" {
			parent := r.items[t.parent]
			parent.SubTopologies = append(parent.SubTopologies, t)
			sort.Sort(byName(parent.SubTopologies))
			r.items[t.parent] = parent
		}

		r.items[t.id] = t
	}
}

func (r *registry) get(name string) (APITopologyDesc, bool) {
	r.RLock()
	defer r.RUnlock()
	t, ok := r.items[name]
	return t, ok
}

func (r *registry) walk(f func(APITopologyDesc)) {
	r.RLock()
	defer r.RUnlock()
	descs := []APITopologyDesc{}
	for _, desc := range r.items {
		if desc.parent != "" {
			continue
		}
		descs = append(descs, desc)
	}
	sort.Sort(byName(descs))
	for _, desc := range descs {
		f(desc)
	}
}

// makeTopologyList returns a handler that yields an APITopologyList.
func (r *registry) makeTopologyList(rep Reporter) CtxHandlerFunc {
	return func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
		report, err := rep.Report(ctx)
		if err != nil {
			respondWith(w, http.StatusInternalServerError, err.Error())
			return
		}
		respondWith(w, http.StatusOK, r.renderTopologies(report, req))
	}
}

func (r *registry) renderTopologies(rpt report.Report, req *http.Request) []APITopologyDesc {
	topologies := []APITopologyDesc{}
	req.ParseForm()
	values := map[string]string{}
	for k, vs := range req.Form {
		values[k] = vs[0]
	}
	r.walk(func(desc APITopologyDesc) {
		renderer, decorator, _ := r.rendererForTopology(desc.id, values, rpt)
		desc.Stats = decorateWithStats(rpt, renderer, decorator)
		for i, sub := range desc.SubTopologies {
			renderer, decorator, _ := r.rendererForTopology(sub.id, values, rpt)
			desc.SubTopologies[i].Stats = decorateWithStats(rpt, renderer, decorator)
		}
		topologies = append(topologies, desc)
	})
	return updateFilters(rpt, topologies)
}

func decorateWithStats(rpt report.Report, renderer render.Renderer, decorator render.Decorator) topologyStats {
	var (
		nodes     int
		realNodes int
		edges     int
	)
	for _, n := range renderer.Render(rpt, decorator) {
		nodes++
		if n.Topology != render.Pseudo {
			realNodes++
		}
		edges += len(n.Adjacency)
	}
	renderStats := renderer.Stats(rpt, decorator)
	return topologyStats{
		NodeCount:          nodes,
		NonpseudoNodeCount: realNodes,
		EdgeCount:          edges,
		FilteredNodes:      renderStats.FilteredNodes,
	}
}

func (r *registry) rendererForTopology(id string, values map[string]string, rpt report.Report) (render.Renderer, render.Decorator, error) {
	topology, ok := r.get(id)
	if !ok {
		return nil, nil, fmt.Errorf("topology not found: %s", id)
	}
	topology = updateFilters(rpt, []APITopologyDesc{topology})[0]

	var filters []render.FilterFunc
	for _, group := range topology.Options {
		value := values[group.ID]
		for _, opt := range group.Options {
			if opt.filter == nil {
				continue
			}
			if (value == "" && group.Default == opt.Value) || (opt.Value != "" && opt.Value == value) {
				filters = append(filters, opt.filter)
			}
		}
	}
	var decorator render.Decorator
	if len(filters) > 0 {
		decorator = func(renderer render.Renderer) render.Renderer {
			return render.MakeFilter(render.ComposeFilterFuncs(filters...), renderer)
		}
	}
	return topology.renderer, decorator, nil
}

type reportRenderHandler func(context.Context, Reporter, http.ResponseWriter, *http.Request)

func (r *registry) rendererForRequest(req *http.Request, rpt report.Report) (render.Renderer, render.Decorator, error) {
	req.ParseForm()
	values := map[string]string{}
	for k, vs := range req.Form {
		values[k] = vs[0]
	}
	return r.rendererForTopology(mux.Vars(req)["topology"], values, rpt)
}

func captureReporter(rep Reporter, f reportRenderHandler) CtxHandlerFunc {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		f(ctx, rep, w, r)
	}
}
<|endoftext|>


<commit_before>package client

import (
	// Stdlib
	"log"

	// Vendor
	"github.com/pkg/errors"
)

//We check whether there is a voter on the list of those who have already voted
func (api *Client) Verify_Voter(author, permlink, voter string) bool {
	ans, err := api.Rpc.Database.GetActiveVotes(author, permlink)
	if err != nil {
		log.Println(errors.Wrapf(err, "Error Verify Voter: "))
		return false
	} else {
		for _, v := range ans {
			if v.Voter == voter {
				return true
			}
		}
		return false
	}
}

//We check whether there are voted
func (api *Client) Verify_Votes(author, permlink string) bool {
	ans, err := api.Rpc.Database.GetActiveVotes(author, permlink)
	if err != nil {
		log.Println(errors.Wrapf(err, "Error Verify Votes: "))
		return false
	} else {
		if len(ans) > 0 {
			return true
		} else {
			return false
		}
	}
}

func (api *Client) Verify_Comments(author, permlink string) bool {
	ans, err := api.Rpc.Database.GetContentReplies(author, permlink)
	if err != nil {
		log.Println(errors.Wrapf(err, "Error Verify Comments: "))
		return false
	} else {
		if len(ans) > 0 {
			return true
		} else {
			return false
		}
	}
}

func (api *Client) Verify_Reblogs(author, permlink, rebloger string) bool {
	ans, err := api.Rpc.Follow.GetRebloggedBy(author, permlink)
	if err != nil {
		log.Println(errors.Wrapf(err, "Error Verify Reblogs: "))
		return false
	} else {
		for _, v := range ans {
			if v == rebloger {
				return true
			}
		}
		return false
	}
}

func (api *Client) Verify_Follow(follower, following string) bool {
	ans, err := api.Rpc.Follow.GetFollowing(follower, following, "blog", 1)
	if err != nil {
		log.Println(errors.Wrapf(err, "Error Verify Follow: "))
		return false
	} else {
		for _, v := range ans {
			if (v.Follower == follower) && (v.Following == following) {
				return true
			} else {
				return false
			}
		}
		return false
	}
}
<commit_msg>Add Verify_Post function in Client<commit_after>package client

import (
	// Stdlib
	"log"

	// Vendor
	"github.com/pkg/errors"
)

//We check whether there is a voter on the list of those who have already voted
func (api *Client) Verify_Voter(author, permlink, voter string) bool {
	ans, err := api.Rpc.Database.GetActiveVotes(author, permlink)
	if err != nil {
		log.Println(errors.Wrapf(err, "Error Verify Voter: "))
		return false
	} else {
		for _, v := range ans {
			if v.Voter == voter {
				return true
			}
		}
		return false
	}
}

//We check whether there are voted
func (api *Client) Verify_Votes(author, permlink string) bool {
	ans, err := api.Rpc.Database.GetActiveVotes(author, permlink)
	if err != nil {
		log.Println(errors.Wrapf(err, "Error Verify Votes: "))
		return false
	} else {
		if len(ans) > 0 {
			return true
		} else {
			return false
		}
	}
}

func (api *Client) Verify_Comments(author, permlink string) bool {
	ans, err := api.Rpc.Database.GetContentReplies(author, permlink)
	if err != nil {
		log.Println(errors.Wrapf(err, "Error Verify Comments: "))
		return false
	} else {
		if len(ans) > 0 {
			return true
		} else {
			return false
		}
	}
}

func (api *Client) Verify_Reblogs(author, permlink, rebloger string) bool {
	ans, err := api.Rpc.Follow.GetRebloggedBy(author, permlink)
	if err != nil {
		log.Println(errors.Wrapf(err, "Error Verify Reblogs: "))
		return false
	} else {
		for _, v := range ans {
			if v == rebloger {
				return true
			}
		}
		return false
	}
}

func (api *Client) Verify_Follow(follower, following string) bool {
	ans, err := api.Rpc.Follow.GetFollowing(follower, following, "blog", 1)
	if err != nil {
		log.Println(errors.Wrapf(err, "Error Verify Follow: "))
		return false
	} else {
		for _, v := range ans {
			if (v.Follower == follower) && (v.Following == following) {
				return true
			} else {
				return false
			}
		}
		return false
	}
}

func (api *Client) Verify_Post(author, permlink string) bool {
	ans, err := api.Rpc.Database.GetContent(author, permlink)
	if err != nil {
		log.Println(errors.Wrapf(err, "Error Verify Post: "))
		return false
	} else {
		for _, v := range ans {
			if (v.Author == author) && (v.Permlink == permlink) {
				return true
			} else {
				return false
			}
		}
		return false
	}
}
<|endoftext|>


<commit_before>package app_test

import (
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/weaveworks/scope/app"
	"github.com/weaveworks/scope/report"
	"github.com/weaveworks/scope/test"
	"github.com/weaveworks/scope/test/reflect"
)

func TestCollector(t *testing.T) {
	ctx := context.Background()
	window := time.Millisecond
	c := app.NewCollector(window)

	r1 := report.MakeReport()
	r1.Endpoint.AddNode("foo", report.MakeNode())

	r2 := report.MakeReport()
	r2.Endpoint.AddNode("bar", report.MakeNode())

	if want, have := report.MakeReport(), c.Report(ctx); !reflect.DeepEqual(want, have) {
		t.Error(test.Diff(want, have))
	}

	c.Add(ctx, r1)
	if want, have := r1, c.Report(ctx); !reflect.DeepEqual(want, have) {
		t.Error(test.Diff(want, have))
	}

	c.Add(ctx, r2)

	merged := report.MakeReport()
	merged = merged.Merge(r1)
	merged = merged.Merge(r2)
	if want, have := merged, c.Report(ctx); !reflect.DeepEqual(want, have) {
		t.Error(test.Diff(want, have))
	}
}

func TestCollectorWait(t *testing.T) {
	ctx := context.Background()
	window := time.Millisecond
	c := app.NewCollector(window)

	waiter := make(chan struct{}, 1)
	c.WaitOn(ctx, waiter)
	defer c.UnWait(ctx, waiter)
	c.(interface {
		Broadcast()
	}).Broadcast()

	select {
	case <-waiter:
	default:
		t.Fatal("Didn't unblock")
	}
}
<commit_msg>A 1ms window is ludicrously small; GC can take longer.<commit_after>package app_test

import (
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/weaveworks/scope/app"
	"github.com/weaveworks/scope/report"
	"github.com/weaveworks/scope/test"
	"github.com/weaveworks/scope/test/reflect"
)

func TestCollector(t *testing.T) {
	ctx := context.Background()
	window := 10 * time.Second
	c := app.NewCollector(window)

	r1 := report.MakeReport()
	r1.Endpoint.AddNode("foo", report.MakeNode())

	r2 := report.MakeReport()
	r2.Endpoint.AddNode("bar", report.MakeNode())

	if want, have := report.MakeReport(), c.Report(ctx); !reflect.DeepEqual(want, have) {
		t.Error(test.Diff(want, have))
	}

	c.Add(ctx, r1)
	if want, have := r1, c.Report(ctx); !reflect.DeepEqual(want, have) {
		t.Error(test.Diff(want, have))
	}

	c.Add(ctx, r2)
	merged := report.MakeReport()
	merged = merged.Merge(r1)
	merged = merged.Merge(r2)
	if want, have := merged, c.Report(ctx); !reflect.DeepEqual(want, have) {
		t.Error(test.Diff(want, have))
	}
}

func TestCollectorWait(t *testing.T) {
	ctx := context.Background()
	window := time.Millisecond
	c := app.NewCollector(window)

	waiter := make(chan struct{}, 1)
	c.WaitOn(ctx, waiter)
	defer c.UnWait(ctx, waiter)
	c.(interface {
		Broadcast()
	}).Broadcast()

	select {
	case <-waiter:
	default:
		t.Fatal("Didn't unblock")
	}
}
<|endoftext|>


<commit_before>package run

import (
	"bufio"
	"fmt"
	"net/http"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"
)

func performRequest(url string) (bool, time.Duration) {
	t0 := time.Now()
	res, err := http.Get(url)
	t1 := time.Now()
	if err == nil {
		res.Body.Close()
	}

	return err == nil && res.StatusCode == 200, t1.Sub(t0)
}

func sleepUntil(t time.Time, c chan time.Duration) {
	tDone := t.Sub(time.Now())
	c <- -tDone

	for tDone > 0 {
		var sleepDuration time.Duration
		if tDone < 10*time.Millisecond {
			sleepDuration = tDone
		} else {
			sleepDuration = tDone / 2
		}

		time.Sleep(sleepDuration)
		tDone = t.Sub(time.Now())
		c <- -tDone
	}
}

func printStatusHeader() {
	fmt.Println("lag [ms]       sent      done   waiting        successful")
}

func printStatus(sent int, done int, successful int, lag time.Duration) {
	nanoLag := float64(lag.Nanoseconds()) / float64(1000000)
	fmt.Printf("\r%8.3f   %8d  %8d  %8d  %8d %3.2f%%", nanoLag, sent, done, sent-done, successful, 100*float64(successful)/float64(done))
}

func Run(prefix *string, flood *bool, speedup *float64) {
	var wg sync.WaitGroup

	t0 := time.Now()
	var tCorrection float64

	first := true

	chanSent := make(chan int)
	chanDone := make(chan int)
	chanSuccess := make(chan int)
	chanLag := make(chan time.Duration)

	printStatusHeader()

	go func() {
		sent := 0
		done := 0
		success := 0
		v := 0
		var lag time.Duration

		for {
			select {
			case v = <-chanSent:
				sent += v
			case v = <-chanDone:
				done += v
			case v = <-chanSuccess:
				success += v
			case lag = <-chanLag:
			}

			printStatus(sent, done, success, lag)
		}
	}()

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		line := scanner.Text()

		lineParts := strings.SplitN(line, " ", 2)
		delay, _ := strconv.ParseFloat(lineParts[0], 64)
		url := *prefix + lineParts[1]

		if first {
			tCorrection = delay
			first = false
		}

		if !*flood {
			runAt := t0.Add(time.Duration((delay - tCorrection) / *speedup * 1000000000))
			sleepUntil(runAt, chanLag)
		}

		wg.Add(1)
		go func() {
			defer wg.Done()

			chanSent <- 1
			var success, _ = performRequest(url)
			chanDone <- 1
			if success {
				chanSuccess <- 1
			}
		}()
	}

	wg.Wait()
	fmt.Println()
}
<commit_msg>increase width of lag-column<commit_after>package run

import (
	"bufio"
	"fmt"
	"net/http"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"
)

func performRequest(url string) (bool, time.Duration) {
	t0 := time.Now()
	res, err := http.Get(url)
	t1 := time.Now()
	if err == nil {
		res.Body.Close()
	}

	return err == nil && res.StatusCode == 200, t1.Sub(t0)
}

func sleepUntil(t time.Time, c chan time.Duration) {
	tDone := t.Sub(time.Now())
	c <- -tDone

	for tDone > 0 {
		var sleepDuration time.Duration
		if tDone < 10*time.Millisecond {
			sleepDuration = tDone
		} else {
			sleepDuration = tDone / 2
		}

		time.Sleep(sleepDuration)
		tDone = t.Sub(time.Now())
		c <- -tDone
	}
}

func printStatusHeader() {
	fmt.Println("    lag [ms]       sent      done   waiting        successful")
}

func printStatus(sent int, done int, successful int, lag time.Duration) {
	nanoLag := float64(lag.Nanoseconds()) / float64(1000000)
	fmt.Printf("\r%12.3f   %8d  %8d  %8d  %8d %3.2f%%", nanoLag, sent, done, sent-done, successful, 100*float64(successful)/float64(done))
}

func Run(prefix *string, flood *bool, speedup *float64) {
	var wg sync.WaitGroup

	t0 := time.Now()
	var tCorrection float64

	first := true

	chanSent := make(chan int)
	chanDone := make(chan int)
	chanSuccess := make(chan int)
	chanLag := make(chan time.Duration)

	printStatusHeader()

	go func() {
		sent := 0
		done := 0
		success := 0
		v := 0
		var lag time.Duration

		for {
			select {
			case v = <-chanSent:
				sent += v
			case v = <-chanDone:
				done += v
			case v = <-chanSuccess:
				success += v
			case lag = <-chanLag:
			}

			printStatus(sent, done, success, lag)
		}
	}()

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		line := scanner.Text()

		lineParts := strings.SplitN(line, " ", 2)
		delay, _ := strconv.ParseFloat(lineParts[0], 64)
		url := *prefix + lineParts[1]

		if first {
			tCorrection = delay
			first = false
		}

		if !*flood {
			runAt := t0.Add(time.Duration((delay - tCorrection) / *speedup * 1000000000))
			sleepUntil(runAt, chanLag)
		}

		wg.Add(1)
		go func() {
			defer wg.Done()

			chanSent <- 1
			var success, _ = performRequest(url)
			chanDone <- 1
			if success {
				chanSuccess <- 1
			}
		}()
	}

	wg.Wait()
	fmt.Println()
}
<|endoftext|>


<commit_before>package sudoku

import (
	"math"
)

//1.0 is a no op. 0.0 to 1.0 is increase goodness; 1.0 and above is decraase good
type probabilityTweak float64

type probabilityTwiddler func(*SolveStep, []*SolveStep, []*CompoundSolveStep, *Grid) probabilityTweak

type probabilityTwiddlerItem struct {
	f    probabilityTwiddler
	name string
}

//twiddlers is the list of all of the twiddlers we should apply to change the
//probability distribution of possibilities at each step. They capture biases
//that humans have about which cells to focus on (which is separate from
//Technique.humanLikelihood, since that is about how common a technique in
//general, not in a specific context.)
var twiddlers []probabilityTwiddlerItem

func init() {
	//twiddlers is not a map because a) we need to attach more info anyway,
	//and b) we want a stable ordering.
	twiddlers = []probabilityTwiddlerItem{
		{
			f:    twiddleHumanLikelihood,
			name: "Human Likelihood",
		},
		{
			f:    twiddleChainedSteps,
			name: "Chained Steps",
		},
		{
			f:    twiddleCommonNumbers,
			name: "Common Numbers",
		},
		{
			f:    twiddlePointingTargetOverlap,
			name: "Pointing Target Overlap",
		},
	}
}

//twiddlePointingTargetOverlap twiddles based on how much the targetcells
//overlap with the pointingcells of the proposed step. This tries to capture
//the fact that for cull steps in particular, we want to heavily incentivize
//steps that directly reduce possibilities in the next round of steps. This is
//conceptually similar to ChainSimilarity, but more targeted.
func twiddlePointingTargetOverlap(currentStep *SolveStep, inProgressCompoundStep []*SolveStep, pastSteps []*CompoundSolveStep, grid *Grid) probabilityTweak {
	if len(inProgressCompoundStep) == 0 {
		return 1.0
	}
	lastStep := inProgressCompoundStep[len(inProgressCompoundStep)-1]

	if currentStep == nil {
		return 1.0
	}

	//We're going to look for two kinds of overlap: targetCell to targetCell,
	//and targetCell to PointerCell, because some techniques want one or the
	//other (do any want both?). We'll use the higher overlap.

	//Compute Target --> Pointer overlap

	currentStepPointerSet := currentStep.PointerCells.toCellSet()
	lastStepTargetSet := lastStep.TargetCells.toCellSet()

	targetPointerUnion := currentStepPointerSet.union(lastStepTargetSet)
	targetPointerIntersection := currentStepPointerSet.intersection(lastStepTargetSet)

	targetPointerOverlap := float64(len(targetPointerIntersection)) / float64(len(targetPointerUnion))

	if math.IsNaN(targetPointerOverlap) {
		targetPointerOverlap = 0.0
	}

	//Compute Target --> Target overlap

	currentStepTargetSet := currentStep.TargetCells.toCellSet()

	targetTargetUnion := currentStepTargetSet.union(lastStepTargetSet)
	targetTargetIntersection := currentStepTargetSet.intersection(lastStepTargetSet)

	targetTargetOverlap := float64(len(targetTargetIntersection)) / float64(len(targetTargetUnion))

	if math.IsNaN(targetTargetOverlap) {
		targetTargetOverlap = 0.0
	}

	//Pick the larger overlap to go with.

	overlap := targetPointerOverlap

	if targetTargetOverlap > targetPointerOverlap {
		overlap = targetTargetOverlap
		//TargetTargetOverlap is slightly better than targetPointer overlap.
		//This number will be flipped in the next step, so bigger is better.
		overlap *= 1.1
	}

	//The more overlap, the better, at an increasing rate. And the smaller the
	//output, the better the twiddle is.

	flippedOverlap := 1.0 - overlap

	//A value of 0--if there's perfect overlap--is nonsense. It's too strong!
	if flippedOverlap == 0.0 {
		flippedOverlap = 0.001
	}

	//Squaring the flipped overlap will accelerate small ones.
	return probabilityTweak(flippedOverlap * flippedOverlap)

}

//twiddleTechniqueWeight is a fundamental twiddler based on the
//HumanLikeliehood of the current technique. In fact, it's so fundamental that
//it's arguably not even a twiddler at all.
func twiddleHumanLikelihood(currentStep *SolveStep, inProgressCompoundStep []*SolveStep, pastSteps []*CompoundSolveStep, grid *Grid) probabilityTweak {
	if currentStep == nil {
		return 1.0
	}
	return probabilityTweak(currentStep.HumanLikelihood())
}

//twiddleCommonNumbers will twiddle up steps whose TargetNumbers are over-
//represented in the grid (but not DIM). This captures that humans, in
//practice, will often choose to look for cells to fill for a number that is
//represented more in the grid, since they're more likely to be constrained by
//neighorbors with the same number.
func twiddleCommonNumbers(currentStep *SolveStep, inProgressCompoundStep []*SolveStep, pastSteps []*CompoundSolveStep, grid *Grid) probabilityTweak {

	//Skip steps that aren't fill or fill multiple
	if !currentStep.Technique.IsFill() || len(currentStep.TargetNums) > 1 {
		return 1.0
	}

	keyNum := currentStep.TargetNums[0]

	count := 0

	for _, cell := range grid.Cells() {
		if cell.Number() == keyNum {
			count++
		}
	}

	if count == 0 || count == DIM {
		count = 1
	}

	return probabilityTweak(count)

}

//This function will tweak weights quite a bit to make it more likely that we will pick a subsequent step that
// is 'related' to the cells modified in the last step. For example, if the
// last step had targetCells that shared a row, then a step with
//target cells in that same row will be more likely this step. This captures the fact that humans, in practice,
//will have 'chains' of steps that are all related.
func twiddleChainedSteps(currentStep *SolveStep, inProgressCompoundStep []*SolveStep, pastSteps []*CompoundSolveStep, grid *Grid) probabilityTweak {

	var lastModifiedCells CellSlice

	if len(inProgressCompoundStep) > 0 {
		lastModifiedCells = inProgressCompoundStep[len(inProgressCompoundStep)-1].TargetCells
	} else if len(pastSteps) > 0 {
		lastCompoundStep := pastSteps[len(pastSteps)-1]
		if lastCompoundStep.FillStep != nil {
			lastModifiedCells = lastCompoundStep.FillStep.TargetCells
		}
	}

	if lastModifiedCells == nil {
		return 1.0
	}

	//Tweak every weight by how related they are.
	//Remember: these are INVERTED weights, so tweaking them down is BETTER.

	//Logically we should be attenuating Dissimilarity here, but for some reason the math.Pow(dissimilairty, 10) doesn't actually
	//appear to work here, which is maddening.

	similarity := currentStep.TargetCells.chainSimilarity(lastModifiedCells)

	//We want it to be dissimilar is larger; flip it.
	dissimilarity := 1.0 - similarity

	return probabilityTweak(math.Pow(10, dissimilarity))

}
<commit_msg>Added a TODO for a new perofmrnace technique to try<commit_after>package sudoku

import (
	"math"
)

//1.0 is a no op. 0.0 to 1.0 is increase goodness; 1.0 and above is decraase good
type probabilityTweak float64

//TODO: performance idea: the grid in this interface should be previousGrid:
//the grid BEFORE the proposed step is applied. That would allow us to not
//have to calculate grid when we create a humanSolveItem, but only when we
//explore it. That could potentially have a big impact. Instrument number of
//times Grid is reified in humanSolveItem.
type probabilityTwiddler func(*SolveStep, []*SolveStep, []*CompoundSolveStep, *Grid) probabilityTweak

type probabilityTwiddlerItem struct {
	f    probabilityTwiddler
	name string
}

//twiddlers is the list of all of the twiddlers we should apply to change the
//probability distribution of possibilities at each step. They capture biases
//that humans have about which cells to focus on (which is separate from
//Technique.humanLikelihood, since that is about how common a technique in
//general, not in a specific context.)
var twiddlers []probabilityTwiddlerItem

func init() {
	//twiddlers is not a map because a) we need to attach more info anyway,
	//and b) we want a stable ordering.
	twiddlers = []probabilityTwiddlerItem{
		{
			f:    twiddleHumanLikelihood,
			name: "Human Likelihood",
		},
		{
			f:    twiddleChainedSteps,
			name: "Chained Steps",
		},
		{
			f:    twiddleCommonNumbers,
			name: "Common Numbers",
		},
		{
			f:    twiddlePointingTargetOverlap,
			name: "Pointing Target Overlap",
		},
	}
}

//twiddlePointingTargetOverlap twiddles based on how much the targetcells
//overlap with the pointingcells of the proposed step. This tries to capture
//the fact that for cull steps in particular, we want to heavily incentivize
//steps that directly reduce possibilities in the next round of steps. This is
//conceptually similar to ChainSimilarity, but more targeted.
func twiddlePointingTargetOverlap(currentStep *SolveStep, inProgressCompoundStep []*SolveStep, pastSteps []*CompoundSolveStep, grid *Grid) probabilityTweak {
	if len(inProgressCompoundStep) == 0 {
		return 1.0
	}
	lastStep := inProgressCompoundStep[len(inProgressCompoundStep)-1]

	if currentStep == nil {
		return 1.0
	}

	//We're going to look for two kinds of overlap: targetCell to targetCell,
	//and targetCell to PointerCell, because some techniques want one or the
	//other (do any want both?). We'll use the higher overlap.

	//Compute Target --> Pointer overlap

	currentStepPointerSet := currentStep.PointerCells.toCellSet()
	lastStepTargetSet := lastStep.TargetCells.toCellSet()

	targetPointerUnion := currentStepPointerSet.union(lastStepTargetSet)
	targetPointerIntersection := currentStepPointerSet.intersection(lastStepTargetSet)

	targetPointerOverlap := float64(len(targetPointerIntersection)) / float64(len(targetPointerUnion))

	if math.IsNaN(targetPointerOverlap) {
		targetPointerOverlap = 0.0
	}

	//Compute Target --> Target overlap

	currentStepTargetSet := currentStep.TargetCells.toCellSet()

	targetTargetUnion := currentStepTargetSet.union(lastStepTargetSet)
	targetTargetIntersection := currentStepTargetSet.intersection(lastStepTargetSet)

	targetTargetOverlap := float64(len(targetTargetIntersection)) / float64(len(targetTargetUnion))

	if math.IsNaN(targetTargetOverlap) {
		targetTargetOverlap = 0.0
	}

	//Pick the larger overlap to go with.

	overlap := targetPointerOverlap

	if targetTargetOverlap > targetPointerOverlap {
		overlap = targetTargetOverlap
		//TargetTargetOverlap is slightly better than targetPointer overlap.
		//This number will be flipped in the next step, so bigger is better.
		overlap *= 1.1
	}

	//The more overlap, the better, at an increasing rate. And the smaller the
	//output, the better the twiddle is.

	flippedOverlap := 1.0 - overlap

	//A value of 0--if there's perfect overlap--is nonsense. It's too strong!
	if flippedOverlap == 0.0 {
		flippedOverlap = 0.001
	}

	//Squaring the flipped overlap will accelerate small ones.
	return probabilityTweak(flippedOverlap * flippedOverlap)

}

//twiddleTechniqueWeight is a fundamental twiddler based on the
//HumanLikeliehood of the current technique. In fact, it's so fundamental that
//it's arguably not even a twiddler at all.
func twiddleHumanLikelihood(currentStep *SolveStep, inProgressCompoundStep []*SolveStep, pastSteps []*CompoundSolveStep, grid *Grid) probabilityTweak {
	if currentStep == nil {
		return 1.0
	}
	return probabilityTweak(currentStep.HumanLikelihood())
}

//twiddleCommonNumbers will twiddle up steps whose TargetNumbers are over-
//represented in the grid (but not DIM). This captures that humans, in
//practice, will often choose to look for cells to fill for a number that is
//represented more in the grid, since they're more likely to be constrained by
//neighorbors with the same number.
func twiddleCommonNumbers(currentStep *SolveStep, inProgressCompoundStep []*SolveStep, pastSteps []*CompoundSolveStep, grid *Grid) probabilityTweak {

	//Skip steps that aren't fill or fill multiple
	if !currentStep.Technique.IsFill() || len(currentStep.TargetNums) > 1 {
		return 1.0
	}

	keyNum := currentStep.TargetNums[0]

	count := 0

	for _, cell := range grid.Cells() {
		if cell.Number() == keyNum {
			count++
		}
	}

	if count == 0 || count == DIM {
		count = 1
	}

	return probabilityTweak(count)

}

//This function will tweak weights quite a bit to make it more likely that we will pick a subsequent step that
// is 'related' to the cells modified in the last step. For example, if the
// last step had targetCells that shared a row, then a step with
//target cells in that same row will be more likely this step. This captures the fact that humans, in practice,
//will have 'chains' of steps that are all related.
func twiddleChainedSteps(currentStep *SolveStep, inProgressCompoundStep []*SolveStep, pastSteps []*CompoundSolveStep, grid *Grid) probabilityTweak {

	var lastModifiedCells CellSlice

	if len(inProgressCompoundStep) > 0 {
		lastModifiedCells = inProgressCompoundStep[len(inProgressCompoundStep)-1].TargetCells
	} else if len(pastSteps) > 0 {
		lastCompoundStep := pastSteps[len(pastSteps)-1]
		if lastCompoundStep.FillStep != nil {
			lastModifiedCells = lastCompoundStep.FillStep.TargetCells
		}
	}

	if lastModifiedCells == nil {
		return 1.0
	}

	//Tweak every weight by how related they are.
	//Remember: these are INVERTED weights, so tweaking them down is BETTER.

	//Logically we should be attenuating Dissimilarity here, but for some reason the math.Pow(dissimilairty, 10) doesn't actually
	//appear to work here, which is maddening.

	similarity := currentStep.TargetCells.chainSimilarity(lastModifiedCells)

	//We want it to be dissimilar is larger; flip it.
	dissimilarity := 1.0 - similarity

	return probabilityTweak(math.Pow(10, dissimilarity))

}
<|endoftext|>


<commit_before>/*
Copyright 2017 - The TXTdirect 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 txtdirect

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"
)

const (
	basezone        = "_redirect"
	defaultSub      = "www"
	defaultProtocol = "https"
)

type record struct {
	Version string
	To      string
	Code    int
	Type    string
	Vcs     string
	From    string
	Default string
}

// Config contains the middleware's configuration
type Config struct {
	Enable   []string
	Redirect string
}

func (r *record) Parse(str string) error {
	s := strings.Split(str, ";")
	for _, l := range s {
		switch {
		case strings.HasPrefix(l, "v="):
			l = strings.TrimPrefix(l, "v=")
			r.Version = l
			if r.Version != "txtv0" {
				return fmt.Errorf("unhandled version '%s'", r.Version)
			}
			log.Print("WARN: txtv0 is not suitable for production")

		case strings.HasPrefix(l, "to="):
			l = strings.TrimPrefix(l, "to=")
			r.To = l

		case strings.HasPrefix(l, "code="):
			l = strings.TrimPrefix(l, "code=")
			i, err := strconv.Atoi(l)
			if err != nil {
				return fmt.Errorf("could not parse status code: %s", err)
			}
			r.Code = i

		case strings.HasPrefix(l, "type="):
			l = strings.TrimPrefix(l, "type=")
			r.Type = l

		case strings.HasPrefix(l, "vcs="):
			l = strings.TrimPrefix(l, "vcs=")
			r.Vcs = l

		default:
			tuple := strings.Split(l, "=")
			if len(tuple) != 2 {
				return fmt.Errorf("arbitrary data now allowed")
			}
			continue
		}
	}

	if r.Code == 0 {
		r.Code = 301
	}

	if r.Vcs == "" && r.Type == "gometa" {
		r.Vcs = "git"
	}

	if r.Type == "" {
		r.Type = "host"
	}

	return nil
}

func getBaseTarget(rec record) (string, int) {
	return rec.To, rec.Code
}

func getRecord(host, path string) (record, error) {
	if strings.Contains(host, ":") {
		hostSlice := strings.Split(host, ":")
		host = hostSlice[0]
	}
	zone := strings.Join([]string{basezone, host}, ".")

	var absoluteZone string
	if strings.HasSuffix(zone, ".") {
		absoluteZone = zone
	} else {
		absoluteZone = strings.Join([]string{zone, "."}, "")
	}
	s, err := net.LookupTXT(absoluteZone)

	if err != nil {
		return record{}, fmt.Errorf("could not get TXT record: %s", err)
	}

	rec := record{}
	if err = rec.Parse(s[0]); err != nil {
		return rec, fmt.Errorf("could not parse record: %s", err)
	}

	if rec.To == "" {
		s := []string{defaultProtocol, "://", defaultSub, ".", host}
		rec.To = strings.Join(s, "")
	}

	return rec, nil
}

func contains(array []string, word string) bool {
	for _, w := range array {
		if w == word {
			return true
		}
	}
	return false
}

// Redirect the request depending on the redirect record found
func Redirect(w http.ResponseWriter, r *http.Request, c Config) error {
	host := r.Host
	path := r.URL.Path

	bl := make(map[string]bool)
	bl["/favicon.ico"] = true

	if bl[path] {
		http.Redirect(w, r, strings.Join([]string{host, path}, ""), 200)
		return nil
	}

	rec, err := getRecord(host, path)
	if err != nil {
		if strings.HasSuffix(err.Error(), "no such host") {
			if c.Redirect != "" {
				http.Redirect(w, r, c.Redirect, http.StatusMovedPermanently)
				return nil
			}
			if contains(c.Enable, "www") {
				s := []string{defaultProtocol, "://", defaultSub, ".", host}
				http.Redirect(w, r, strings.Join(s, ""), 301)
				return nil
			}
			http.NotFound(w, r)
			return nil
		}
		return err
	}

	if !contains(c.Enable, rec.Type) {
		return fmt.Errorf("option disabled")
	}

	if path == "/" {
		http.Redirect(w, r, rec.Default, rec.Code)
		return nil
	}

	if rec.Type == "path" && path != "" {
		zone, from, _ := zoneFromPath(host, path)
		rec, err = getFinalRecord(zone, from)
		if err != nil {
			return err
		}
	}

	if rec.Type == "host" {
		to, code := getBaseTarget(rec)
		http.Redirect(w, r, to, code)
		return nil
	}

	if rec.Type == "gometa" {
		return gometa(w, rec, host, path)
	}

	return fmt.Errorf("record type %s unsupported", rec.Type)
}
<commit_msg>Fix typo in error message<commit_after>/*
Copyright 2017 - The TXTdirect 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 txtdirect

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"
)

const (
	basezone        = "_redirect"
	defaultSub      = "www"
	defaultProtocol = "https"
)

type record struct {
	Version string
	To      string
	Code    int
	Type    string
	Vcs     string
	From    string
	Default string
}

// Config contains the middleware's configuration
type Config struct {
	Enable   []string
	Redirect string
}

func (r *record) Parse(str string) error {
	s := strings.Split(str, ";")
	for _, l := range s {
		switch {
		case strings.HasPrefix(l, "v="):
			l = strings.TrimPrefix(l, "v=")
			r.Version = l
			if r.Version != "txtv0" {
				return fmt.Errorf("unhandled version '%s'", r.Version)
			}
			log.Print("WARN: txtv0 is not suitable for production")

		case strings.HasPrefix(l, "to="):
			l = strings.TrimPrefix(l, "to=")
			r.To = l

		case strings.HasPrefix(l, "code="):
			l = strings.TrimPrefix(l, "code=")
			i, err := strconv.Atoi(l)
			if err != nil {
				return fmt.Errorf("could not parse status code: %s", err)
			}
			r.Code = i

		case strings.HasPrefix(l, "type="):
			l = strings.TrimPrefix(l, "type=")
			r.Type = l

		case strings.HasPrefix(l, "vcs="):
			l = strings.TrimPrefix(l, "vcs=")
			r.Vcs = l

		default:
			tuple := strings.Split(l, "=")
			if len(tuple) != 2 {
				return fmt.Errorf("arbitrary data not allowed")
			}
			continue
		}
	}

	if r.Code == 0 {
		r.Code = 301
	}

	if r.Vcs == "" && r.Type == "gometa" {
		r.Vcs = "git"
	}

	if r.Type == "" {
		r.Type = "host"
	}

	return nil
}

func getBaseTarget(rec record) (string, int) {
	return rec.To, rec.Code
}

func getRecord(host, path string) (record, error) {
	if strings.Contains(host, ":") {
		hostSlice := strings.Split(host, ":")
		host = hostSlice[0]
	}
	zone := strings.Join([]string{basezone, host}, ".")

	var absoluteZone string
	if strings.HasSuffix(zone, ".") {
		absoluteZone = zone
	} else {
		absoluteZone = strings.Join([]string{zone, "."}, "")
	}
	s, err := net.LookupTXT(absoluteZone)

	if err != nil {
		return record{}, fmt.Errorf("could not get TXT record: %s", err)
	}

	rec := record{}
	if err = rec.Parse(s[0]); err != nil {
		return rec, fmt.Errorf("could not parse record: %s", err)
	}

	if rec.To == "" {
		s := []string{defaultProtocol, "://", defaultSub, ".", host}
		rec.To = strings.Join(s, "")
	}

	return rec, nil
}

func contains(array []string, word string) bool {
	for _, w := range array {
		if w == word {
			return true
		}
	}
	return false
}

// Redirect the request depending on the redirect record found
func Redirect(w http.ResponseWriter, r *http.Request, c Config) error {
	host := r.Host
	path := r.URL.Path

	bl := make(map[string]bool)
	bl["/favicon.ico"] = true

	if bl[path] {
		http.Redirect(w, r, strings.Join([]string{host, path}, ""), 200)
		return nil
	}

	rec, err := getRecord(host, path)
	if err != nil {
		if strings.HasSuffix(err.Error(), "no such host") {
			if c.Redirect != "" {
				http.Redirect(w, r, c.Redirect, http.StatusMovedPermanently)
				return nil
			}
			if contains(c.Enable, "www") {
				s := []string{defaultProtocol, "://", defaultSub, ".", host}
				http.Redirect(w, r, strings.Join(s, ""), 301)
				return nil
			}
			http.NotFound(w, r)
			return nil
		}
		return err
	}

	if !contains(c.Enable, rec.Type) {
		return fmt.Errorf("option disabled")
	}

	if path == "/" {
		http.Redirect(w, r, rec.Default, rec.Code)
		return nil
	}

	if rec.Type == "path" && path != "" {
		zone, from, _ := zoneFromPath(host, path)
		rec, err = getFinalRecord(zone, from)
		if err != nil {
			return err
		}
	}

	if rec.Type == "host" {
		to, code := getBaseTarget(rec)
		http.Redirect(w, r, to, code)
		return nil
	}

	if rec.Type == "gometa" {
		return gometa(w, rec, host, path)
	}

	return fmt.Errorf("record type %s unsupported", rec.Type)
}
<|endoftext|>


<commit_before>package main

import "webbrowser"

func main() {
	webbrowser.Open("http://google.es")
	webbrowser.Open("http://reddit.com")
}
<commit_msg>examples: golang instead of google.<commit_after>package main

import "webbrowser"

func main() {
	webbrowser.Open("http://golang.org")
	webbrowser.Open("http://reddit.com")
}
<|endoftext|>


<commit_before>// Copyright 2015 Hajime Hoshi
//
// 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 audio

import (
	"io"
	"runtime"
	"sync"
	"time"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/exp/audio/internal/driver"
)

type mixingStream struct {
	sampleRate   int
	writtenBytes int
	frames       int
	players      map[*Player]struct{}

	// Note that Read (and other methods) need to be concurrent safe
	// because Read is called from another groutine (see NewContext).
	sync.RWMutex
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

const (
	channelNum     = 2
	bytesPerSample = 2

	// TODO: This assumes that channelNum is a power of 2.
	mask = ^(channelNum*bytesPerSample - 1)
)

func (s *mixingStream) SampleRate() int {
	s.RLock()
	defer s.RUnlock()
	return s.sampleRate
}

func (s *mixingStream) Read(b []byte) (int, error) {
	s.Lock()
	defer s.Unlock()

	bytesPerFrame := s.sampleRate * bytesPerSample * channelNum / ebiten.FPS
	x := s.frames*bytesPerFrame + len(b)
	if x <= s.writtenBytes {
		return 0, nil
	}

	if len(s.players) == 0 {
		l := min(len(b), x-s.writtenBytes)
		l &= mask
		copy(b, make([]byte, l))
		s.writtenBytes += l
		return l, nil
	}
	closed := []*Player{}
	l := len(b)
	for p := range s.players {
		err := p.readToBuffer(l)
		if err == io.EOF {
			closed = append(closed, p)
		} else if err != nil {
			return 0, err
		}
		l = min(p.bufferLength(), l)
	}
	l &= mask
	b16s := [][]int16{}
	for p := range s.players {
		b16s = append(b16s, p.bufferToInt16(l))
	}
	for i := 0; i < l/2; i++ {
		x := 0
		for _, b16 := range b16s {
			x += int(b16[i])
		}
		if x > (1<<15)-1 {
			x = (1 << 15) - 1
		}
		if x < -(1 << 15) {
			x = -(1 << 15)
		}
		b[2*i] = byte(x)
		b[2*i+1] = byte(x >> 8)
	}
	for p := range s.players {
		p.proceed(l)
	}
	for _, p := range closed {
		delete(s.players, p)
	}
	s.writtenBytes += l
	return l, nil
}

func (s *mixingStream) update() error {
	s.Lock()
	defer s.Unlock()
	s.frames++
	return nil
}

func (s *mixingStream) newPlayer(src ReadSeekCloser) (*Player, error) {
	s.Lock()
	defer s.Unlock()
	p := &Player{
		stream: s,
		src:    src,
		buf:    []byte{},
		volume: 1,
	}
	// Get the current position of the source.
	pos, err := p.src.Seek(0, 1)
	if err != nil {
		return nil, err
	}
	p.pos = pos
	runtime.SetFinalizer(p, (*Player).Close)
	return p, nil
}

func (s *mixingStream) closePlayer(player *Player) error {
	s.Lock()
	defer s.Unlock()
	runtime.SetFinalizer(player, nil)
	return player.src.Close()
}

func (s *mixingStream) addPlayer(player *Player) {
	s.Lock()
	defer s.Unlock()
	s.players[player] = struct{}{}
}

func (s *mixingStream) removePlayer(player *Player) {
	s.Lock()
	defer s.Unlock()
	delete(s.players, player)
}

func (s *mixingStream) hasPlayer(player *Player) bool {
	s.RLock()
	defer s.RUnlock()
	_, ok := s.players[player]
	return ok
}

func (s *mixingStream) seekPlayer(player *Player, offset time.Duration) error {
	s.Lock()
	defer s.Unlock()
	o := int64(offset) * bytesPerSample * channelNum * int64(s.sampleRate) / int64(time.Second)
	o &= mask
	return player.seek(o)
}

func (s *mixingStream) playerCurrent(player *Player) time.Duration {
	s.RLock()
	defer s.RUnlock()
	sample := player.pos / bytesPerSample / channelNum
	return time.Duration(sample) * time.Second / time.Duration(s.sampleRate)
}

// TODO: Enable to specify the format like Mono8?

type Context struct {
	stream  *mixingStream
	errorCh chan error
}

func NewContext(sampleRate int) (*Context, error) {
	// TODO: Panic if one context exists.
	c := &Context{
		errorCh: make(chan error),
	}
	c.stream = &mixingStream{
		sampleRate: sampleRate,
		players:    map[*Player]struct{}{},
	}
	// TODO: Rename this other than player
	p, err := driver.NewPlayer(c.stream, sampleRate, channelNum, bytesPerSample)
	if err != nil {
		return nil, err
	}
	go func() {
		// TODO: Is it OK to close asap?
		defer p.Close()
		for {
			err := p.Proceed()
			if err == io.EOF {
				break
			}
			if err != nil {
				c.errorCh <- err
				return
			}
			time.Sleep(1 * time.Millisecond)
		}
	}()
	return c, nil
}

// Update proceeds the inner (logical) time of the context by 1/60 second.
// This is expected to be called in the game's updating function (sync mode)
// or an independent goroutine with timers (unsync mode).
// In sync mode, the game logical time syncs the audio logical time and
// you will find audio stops when the game stops e.g. when the window is deactivated.
// In unsync mode, the audio never stops even when the game stops.
func (c *Context) Update() error {
	select {
	case err := <-c.errorCh:
		return err
	default:
	}
	return c.stream.update()
}

// SampleRate returns the sample rate.
// All audio source must have the same sample rate.
func (c *Context) SampleRate() int {
	return c.stream.SampleRate()
}

// ReadSeekCloser is an io.ReadSeeker and io.Closer.
type ReadSeekCloser interface {
	io.ReadSeeker
	io.Closer
}

// Player is an audio player which has one stream.
type Player struct {
	stream *mixingStream
	src    ReadSeekCloser
	buf    []byte
	pos    int64
	volume float64
}

// NewPlayer creates a new player with the given data to the given channel.
// The given data is queued to the end of the buffer.
// This may not be played immediately when data already exists in the buffer.
//
// src's format must be linear PCM (16bits, 2 channel stereo, little endian)
// without a header (e.g. RIFF header).
func (c *Context) NewPlayer(src ReadSeekCloser) (*Player, error) {
	return c.stream.newPlayer(src)
}

func (p *Player) Close() error {
	return p.stream.closePlayer(p)
}

func (p *Player) readToBuffer(length int) error {
	bb := make([]byte, length)
	n, err := p.src.Read(bb)
	if 0 < n {
		p.buf = append(p.buf, bb[:n]...)
	}
	return err
}

func (p *Player) bufferToInt16(lengthInBytes int) []int16 {
	r := make([]int16, lengthInBytes/2)
	for i := 0; i < lengthInBytes/2; i++ {
		r[i] = int16(p.buf[2*i]) | (int16(p.buf[2*i+1]) << 8)
		r[i] = int16(float64(r[i]) * p.volume)
	}
	return r
}

func (p *Player) proceed(length int) {
	p.buf = p.buf[length:]
	p.pos += int64(length)
}

func (p *Player) bufferLength() int {
	return len(p.buf)
}

func (p *Player) Play() error {
	p.stream.addPlayer(p)
	return nil
}

func (p *Player) IsPlaying() bool {
	return p.stream.hasPlayer(p)
}

func (p *Player) Rewind() error {
	return p.Seek(0)
}

func (p *Player) Seek(offset time.Duration) error {
	return p.stream.seekPlayer(p, offset)
}

func (p *Player) seek(offset int64) error {
	p.buf = []byte{}
	pos, err := p.src.Seek(offset, 0)
	if err != nil {
		return err
	}
	p.pos = pos
	return nil
}

func (p *Player) Pause() error {
	p.stream.removePlayer(p)
	return nil
}

func (p *Player) Current() time.Duration {
	return p.stream.playerCurrent(p)
}

func (p *Player) Volume() float64 {
	return p.volume
}

// SetVolume sets the volume.
// volume must be in between 0 and 1. This function panics otherwise.
func (p *Player) SetVolume(volume float64) {
	// The condition must be true when volume is NaN.
	if !(0 <= volume && volume <= 1) {
		panic("audio: volume must be in between 0 and 1")
	}
	p.volume = volume
}

// TODO: Panning
<commit_msg>audio: Add doc<commit_after>// Copyright 2015 Hajime Hoshi
//
// 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 audio provides audio players. This can be used with or without ebiten package.
//
// The stream format must be 16-bit little endian and 2 channels.
//
// An audio context has a sample rate you can set and all streams you want to play must have the same
// sample rate.
//
// An audio context can generate 'players' (instances of audio.Player),
// and you can play sound by calling Play function of players.
// When multiple players play, mixing is automatically done.
// Note that too many players may cause distortion.
package audio

import (
	"io"
	"runtime"
	"sync"
	"time"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/exp/audio/internal/driver"
)

type mixingStream struct {
	sampleRate   int
	writtenBytes int
	frames       int
	players      map[*Player]struct{}

	// Note that Read (and other methods) need to be concurrent safe
	// because Read is called from another groutine (see NewContext).
	sync.RWMutex
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

const (
	channelNum     = 2
	bytesPerSample = 2

	// TODO: This assumes that channelNum is a power of 2.
	mask = ^(channelNum*bytesPerSample - 1)
)

func (s *mixingStream) SampleRate() int {
	s.RLock()
	defer s.RUnlock()
	return s.sampleRate
}

func (s *mixingStream) Read(b []byte) (int, error) {
	s.Lock()
	defer s.Unlock()

	bytesPerFrame := s.sampleRate * bytesPerSample * channelNum / ebiten.FPS
	x := s.frames*bytesPerFrame + len(b)
	if x <= s.writtenBytes {
		return 0, nil
	}

	if len(s.players) == 0 {
		l := min(len(b), x-s.writtenBytes)
		l &= mask
		copy(b, make([]byte, l))
		s.writtenBytes += l
		return l, nil
	}
	closed := []*Player{}
	l := len(b)
	for p := range s.players {
		err := p.readToBuffer(l)
		if err == io.EOF {
			closed = append(closed, p)
		} else if err != nil {
			return 0, err
		}
		l = min(p.bufferLength(), l)
	}
	l &= mask
	b16s := [][]int16{}
	for p := range s.players {
		b16s = append(b16s, p.bufferToInt16(l))
	}
	for i := 0; i < l/2; i++ {
		x := 0
		for _, b16 := range b16s {
			x += int(b16[i])
		}
		if x > (1<<15)-1 {
			x = (1 << 15) - 1
		}
		if x < -(1 << 15) {
			x = -(1 << 15)
		}
		b[2*i] = byte(x)
		b[2*i+1] = byte(x >> 8)
	}
	for p := range s.players {
		p.proceed(l)
	}
	for _, p := range closed {
		delete(s.players, p)
	}
	s.writtenBytes += l
	return l, nil
}

func (s *mixingStream) update() error {
	s.Lock()
	defer s.Unlock()
	s.frames++
	return nil
}

func (s *mixingStream) newPlayer(src ReadSeekCloser) (*Player, error) {
	s.Lock()
	defer s.Unlock()
	p := &Player{
		stream: s,
		src:    src,
		buf:    []byte{},
		volume: 1,
	}
	// Get the current position of the source.
	pos, err := p.src.Seek(0, 1)
	if err != nil {
		return nil, err
	}
	p.pos = pos
	runtime.SetFinalizer(p, (*Player).Close)
	return p, nil
}

func (s *mixingStream) closePlayer(player *Player) error {
	s.Lock()
	defer s.Unlock()
	runtime.SetFinalizer(player, nil)
	return player.src.Close()
}

func (s *mixingStream) addPlayer(player *Player) {
	s.Lock()
	defer s.Unlock()
	s.players[player] = struct{}{}
}

func (s *mixingStream) removePlayer(player *Player) {
	s.Lock()
	defer s.Unlock()
	delete(s.players, player)
}

func (s *mixingStream) hasPlayer(player *Player) bool {
	s.RLock()
	defer s.RUnlock()
	_, ok := s.players[player]
	return ok
}

func (s *mixingStream) seekPlayer(player *Player, offset time.Duration) error {
	s.Lock()
	defer s.Unlock()
	o := int64(offset) * bytesPerSample * channelNum * int64(s.sampleRate) / int64(time.Second)
	o &= mask
	return player.seek(o)
}

func (s *mixingStream) playerCurrent(player *Player) time.Duration {
	s.RLock()
	defer s.RUnlock()
	sample := player.pos / bytesPerSample / channelNum
	return time.Duration(sample) * time.Second / time.Duration(s.sampleRate)
}

// TODO: Enable to specify the format like Mono8?

// A Context is a current state of audio.
//
// The typical usage with ebiten package is:
//
//    var audioContext *audio.Context
//
//    func update(screen *ebiten.Image) error {
//        // Update updates the audio stream by 1/60 [sec].
//        if err := audioContext.Update(); err != nil {
//            return err
//        }
//        // ...
//    }
//
//    func main() {
//        audioContext, err = audio.NewContext(sampleRate)
//        if err != nil {
//            panic(err)
//        }
//        ebiten.Run(run, update, 320, 240, 2, "Audio test")
//    }
//
// This is 'sync mode' in that game's (logical) time and audio time are synchronized.
// You can also call Update independently from the game loop as 'async mode'.
// In this case, audio goes on even when the game stops e.g. by diactivating the screen.
type Context struct {
	stream  *mixingStream
	errorCh chan error
}

// NewContext creates a new audio context with the given sample rate (e.g. 44100).
func NewContext(sampleRate int) (*Context, error) {
	// TODO: Panic if one context exists.
	c := &Context{
		errorCh: make(chan error),
	}
	c.stream = &mixingStream{
		sampleRate: sampleRate,
		players:    map[*Player]struct{}{},
	}
	// TODO: Rename this other than player
	p, err := driver.NewPlayer(c.stream, sampleRate, channelNum, bytesPerSample)
	if err != nil {
		return nil, err
	}
	go func() {
		// TODO: Is it OK to close asap?
		defer p.Close()
		for {
			err := p.Proceed()
			if err == io.EOF {
				break
			}
			if err != nil {
				c.errorCh <- err
				return
			}
			time.Sleep(1 * time.Millisecond)
		}
	}()
	return c, nil
}

// Update proceeds the inner (logical) time of the context by 1/60 second.
//
// This is expected to be called in the game's updating function (sync mode)
// or an independent goroutine with timers (async mode).
// In sync mode, the game logical time syncs the audio logical time and
// you will find audio stops when the game stops e.g. when the window is deactivated.
// In async mode, the audio never stops even when the game stops.
func (c *Context) Update() error {
	select {
	case err := <-c.errorCh:
		return err
	default:
	}
	return c.stream.update()
}

// SampleRate returns the sample rate.
// All audio source must have the same sample rate.
func (c *Context) SampleRate() int {
	return c.stream.SampleRate()
}

// ReadSeekCloser is an io.ReadSeeker and io.Closer.
type ReadSeekCloser interface {
	io.ReadSeeker
	io.Closer
}

// Player is an audio player which has one stream.
type Player struct {
	stream *mixingStream
	src    ReadSeekCloser
	buf    []byte
	pos    int64
	volume float64
}

// NewPlayer creates a new player with the given stream.
//
// src's format must be linear PCM (16bits little endian, 2 channel stereo)
// without a header (e.g. RIFF header).
// The sample rate must be same as that of the audio context.
func (c *Context) NewPlayer(src ReadSeekCloser) (*Player, error) {
	return c.stream.newPlayer(src)
}

// Close closes the stream. Ths source stream passed by NewPlayer will also be closed.
func (p *Player) Close() error {
	return p.stream.closePlayer(p)
}

func (p *Player) readToBuffer(length int) error {
	bb := make([]byte, length)
	n, err := p.src.Read(bb)
	if 0 < n {
		p.buf = append(p.buf, bb[:n]...)
	}
	return err
}

func (p *Player) bufferToInt16(lengthInBytes int) []int16 {
	r := make([]int16, lengthInBytes/2)
	for i := 0; i < lengthInBytes/2; i++ {
		r[i] = int16(p.buf[2*i]) | (int16(p.buf[2*i+1]) << 8)
		r[i] = int16(float64(r[i]) * p.volume)
	}
	return r
}

func (p *Player) proceed(length int) {
	p.buf = p.buf[length:]
	p.pos += int64(length)
}

func (p *Player) bufferLength() int {
	return len(p.buf)
}

// Play plays the stream.
func (p *Player) Play() error {
	p.stream.addPlayer(p)
	return nil
}

// IsPlaying returns boolean indicating whether the player is playing.
func (p *Player) IsPlaying() bool {
	return p.stream.hasPlayer(p)
}

// Rewind rewinds the current position to the start.
func (p *Player) Rewind() error {
	return p.Seek(0)
}

// Seek seeks the position with the given offset.
func (p *Player) Seek(offset time.Duration) error {
	return p.stream.seekPlayer(p, offset)
}

func (p *Player) seek(offset int64) error {
	p.buf = []byte{}
	pos, err := p.src.Seek(offset, 0)
	if err != nil {
		return err
	}
	p.pos = pos
	return nil
}

// Pause pauses the playing.
func (p *Player) Pause() error {
	p.stream.removePlayer(p)
	return nil
}

// Current returns the current position.
func (p *Player) Current() time.Duration {
	return p.stream.playerCurrent(p)
}

// Volume returns the current volume of this player [0-1].
func (p *Player) Volume() float64 {
	return p.volume
}

// SetVolume sets the volume of this player.
// volume must be in between 0 and 1. This function panics otherwise.
func (p *Player) SetVolume(volume float64) {
	// The condition must be true when volume is NaN.
	if !(0 <= volume && volume <= 1) {
		panic("audio: volume must be in between 0 and 1")
	}
	p.volume = volume
}

// TODO: Panning
<|endoftext|>


<commit_before>package installertest

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"io/ioutil"
	"math/big"
	"net"
	"os"
	"path/filepath"
	"time"
)

func GenerateKeyPair() (privateKey *rsa.PrivateKey, err error) {
	privateKey, err = rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return
	}
	return
}

func CertTemplateGenerator() (*x509.Certificate, error) {
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return nil, err
	}
	tmpl := x509.Certificate{
		SerialNumber:          serialNumber,
		Subject:               pkix.Name{Organization: []string{"tsuru Inc."}},
		SignatureAlgorithm:    x509.SHA256WithRSA,
		NotBefore:             time.Now(),
		NotAfter:              time.Now().Add(3650 * 24 * time.Hour),
		BasicConstraintsValid: true,
	}
	return &tmpl, nil
}

func CreateCert(template, parent *x509.Certificate, pub interface{}, parentPrivateKey interface{}) (cert *x509.Certificate, certPEM []byte, err error) {
	certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentPrivateKey)
	if err != nil {
		return
	}
	cert, err = x509.ParseCertificate(certDER)
	if err != nil {
		return
	}
	b := pem.Block{Type: "CERTIFICATE", Bytes: certDER}
	certPEM = pem.EncodeToMemory(&b)
	return
}

type CertsPath struct {
	ClientCert string
	ClientKey  string
	ServerCert string
	ServerKey  string
	RootKey    string
	RootCert   string
	RootDir    string
}

func CreateTestCerts() (CertsPath, error) {
	var path CertsPath
	rootKey, err := GenerateKeyPair()
	if err != nil {
		return path, err
	}
	rootCertTmpl, err := CertTemplateGenerator()
	if err != nil {
		return path, err
	}
	rootCertTmpl.IsCA = true
	rootCertTmpl.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature
	rootCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
	rootCertTmpl.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
	rootCert, rootCertPEM, err := CreateCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey)
	if err != nil {
		return path, err
	}
	rootKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rootKey),
	})
	serverKey, err := GenerateKeyPair()
	if err != nil {
		return path, err
	}
	serverCertTmpl, err := CertTemplateGenerator()
	serverCertTmpl.KeyUsage = x509.KeyUsageDigitalSignature
	serverCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
	serverCertTmpl.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
	_, serverCertPEM, err := CreateCert(serverCertTmpl, rootCert, &serverKey.PublicKey, rootKey)
	if err != nil {
		return path, err
	}
	serverKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(serverKey),
	})
	clientKey, err := GenerateKeyPair()
	if err != nil {
		return path, err
	}
	clientCertTmpl, err := CertTemplateGenerator()
	clientCertTmpl.KeyUsage = x509.KeyUsageDigitalSignature
	clientCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
	_, clientCertPEM, err := CreateCert(clientCertTmpl, rootCert, &clientKey.PublicKey, rootKey)
	if err != nil {
		return path, err
	}
	clientKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey),
	})
	absPath, err := ioutil.TempDir("", "installer_test_certs")
	println(absPath)
	if err != nil {
		return path, err
	}
	path = CertsPath{
		RootDir:    absPath,
		RootCert:   filepath.Join(absPath, "ca.pem"),
		RootKey:    filepath.Join(absPath, "ca-key.pem"),
		ServerCert: filepath.Join(absPath, "server-cert.pem"),
		ServerKey:  filepath.Join(absPath, "server-key.pem"),
		ClientCert: filepath.Join(absPath, "cert.pem"),
		ClientKey:  filepath.Join(absPath, "key.pem"),
	}
	err = ioutil.WriteFile(path.RootCert, rootCertPEM, 0644)
	if err != nil {
		return path, err
	}
	err = ioutil.WriteFile(path.RootKey, rootKeyPEM, 0644)
	if err != nil {
		return path, err
	}
	err = ioutil.WriteFile(path.ServerCert, serverCertPEM, 0644)
	if err != nil {
		return path, err
	}
	err = ioutil.WriteFile(path.ServerKey, serverKeyPEM, 0644)
	if err != nil {
		return path, err
	}
	err = ioutil.WriteFile(path.ClientCert, clientCertPEM, 0644)
	if err != nil {
		return path, err
	}
	err = ioutil.WriteFile(path.ClientKey, clientKeyPEM, 0644)
	if err != nil {
		return path, err
	}
	return path, nil
}

func CleanCerts(path string) error {
	return os.Remove(path)
}
<commit_msg>installer: removes print<commit_after>package installertest

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"io/ioutil"
	"math/big"
	"net"
	"os"
	"path/filepath"
	"time"
)

func GenerateKeyPair() (privateKey *rsa.PrivateKey, err error) {
	privateKey, err = rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return
	}
	return
}

func CertTemplateGenerator() (*x509.Certificate, error) {
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return nil, err
	}
	tmpl := x509.Certificate{
		SerialNumber:          serialNumber,
		Subject:               pkix.Name{Organization: []string{"tsuru Inc."}},
		SignatureAlgorithm:    x509.SHA256WithRSA,
		NotBefore:             time.Now(),
		NotAfter:              time.Now().Add(3650 * 24 * time.Hour),
		BasicConstraintsValid: true,
	}
	return &tmpl, nil
}

func CreateCert(template, parent *x509.Certificate, pub interface{}, parentPrivateKey interface{}) (cert *x509.Certificate, certPEM []byte, err error) {
	certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentPrivateKey)
	if err != nil {
		return
	}
	cert, err = x509.ParseCertificate(certDER)
	if err != nil {
		return
	}
	b := pem.Block{Type: "CERTIFICATE", Bytes: certDER}
	certPEM = pem.EncodeToMemory(&b)
	return
}

type CertsPath struct {
	ClientCert string
	ClientKey  string
	ServerCert string
	ServerKey  string
	RootKey    string
	RootCert   string
	RootDir    string
}

func CreateTestCerts() (CertsPath, error) {
	var path CertsPath
	rootKey, err := GenerateKeyPair()
	if err != nil {
		return path, err
	}
	rootCertTmpl, err := CertTemplateGenerator()
	if err != nil {
		return path, err
	}
	rootCertTmpl.IsCA = true
	rootCertTmpl.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature
	rootCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
	rootCertTmpl.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
	rootCert, rootCertPEM, err := CreateCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey)
	if err != nil {
		return path, err
	}
	rootKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rootKey),
	})
	serverKey, err := GenerateKeyPair()
	if err != nil {
		return path, err
	}
	serverCertTmpl, err := CertTemplateGenerator()
	serverCertTmpl.KeyUsage = x509.KeyUsageDigitalSignature
	serverCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
	serverCertTmpl.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
	_, serverCertPEM, err := CreateCert(serverCertTmpl, rootCert, &serverKey.PublicKey, rootKey)
	if err != nil {
		return path, err
	}
	serverKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(serverKey),
	})
	clientKey, err := GenerateKeyPair()
	if err != nil {
		return path, err
	}
	clientCertTmpl, err := CertTemplateGenerator()
	clientCertTmpl.KeyUsage = x509.KeyUsageDigitalSignature
	clientCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
	_, clientCertPEM, err := CreateCert(clientCertTmpl, rootCert, &clientKey.PublicKey, rootKey)
	if err != nil {
		return path, err
	}
	clientKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey),
	})
	absPath, err := ioutil.TempDir("", "installer_test_certs")
	if err != nil {
		return path, err
	}
	path = CertsPath{
		RootDir:    absPath,
		RootCert:   filepath.Join(absPath, "ca.pem"),
		RootKey:    filepath.Join(absPath, "ca-key.pem"),
		ServerCert: filepath.Join(absPath, "server-cert.pem"),
		ServerKey:  filepath.Join(absPath, "server-key.pem"),
		ClientCert: filepath.Join(absPath, "cert.pem"),
		ClientKey:  filepath.Join(absPath, "key.pem"),
	}
	err = ioutil.WriteFile(path.RootCert, rootCertPEM, 0644)
	if err != nil {
		return path, err
	}
	err = ioutil.WriteFile(path.RootKey, rootKeyPEM, 0644)
	if err != nil {
		return path, err
	}
	err = ioutil.WriteFile(path.ServerCert, serverCertPEM, 0644)
	if err != nil {
		return path, err
	}
	err = ioutil.WriteFile(path.ServerKey, serverKeyPEM, 0644)
	if err != nil {
		return path, err
	}
	err = ioutil.WriteFile(path.ClientCert, clientCertPEM, 0644)
	if err != nil {
		return path, err
	}
	err = ioutil.WriteFile(path.ClientKey, clientKeyPEM, 0644)
	if err != nil {
		return path, err
	}
	return path, nil
}

func CleanCerts(path string) error {
	return os.Remove(path)
}
<|endoftext|>