<commit_before>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package jpeg

import (
	"bytes"
	"image"
	"image/png"
	"io/ioutil"
	"rand"
	"os"
	"testing"
)

var testCase = []struct {
	filename  string
	quality   int
	tolerance int64
}{
	{"../testdata/video-001.png", 1, 24 << 8},
	{"../testdata/video-001.png", 20, 12 << 8},
	{"../testdata/video-001.png", 60, 8 << 8},
	{"../testdata/video-001.png", 80, 6 << 8},
	{"../testdata/video-001.png", 90, 4 << 8},
	{"../testdata/video-001.png", 100, 2 << 8},
}

func delta(u0, u1 uint32) int64 {
	d := int64(u0) - int64(u1)
	if d < 0 {
		return -d
	}
	return d
}

func readPng(filename string) (image.Image, os.Error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return png.Decode(f)
}

func TestWriter(t *testing.T) {
	for _, tc := range testCase {
		// Read the image.
		m0, err := readPng(tc.filename)
		if err != nil {
			t.Error(tc.filename, err)
			continue
		}
		// Encode that image as JPEG.
		buf := bytes.NewBuffer(nil)
		err = Encode(buf, m0, &Options{Quality: tc.quality})
		if err != nil {
			t.Error(tc.filename, err)
			continue
		}
		// Decode that JPEG.
		m1, err := Decode(buf)
		if err != nil {
			t.Error(tc.filename, err)
			continue
		}
		// Compute the average delta in RGB space.
		b := m0.Bounds()
		var sum, n int64
		for y := b.Min.Y; y < b.Max.Y; y++ {
			for x := b.Min.X; x < b.Max.X; x++ {
				c0 := m0.At(x, y)
				c1 := m1.At(x, y)
				r0, g0, b0, _ := c0.RGBA()
				r1, g1, b1, _ := c1.RGBA()
				sum += delta(r0, r1)
				sum += delta(g0, g1)
				sum += delta(b0, b1)
				n += 3
			}
		}
		// Compare the average delta to the tolerance level.
		if sum/n > tc.tolerance {
			t.Errorf("%s, quality=%d: average delta is too high", tc.filename, tc.quality)
			continue
		}
	}
}

func BenchmarkEncodeRGBOpaque(b *testing.B) {
	b.StopTimer()
	img := image.NewRGBA(640, 480)
	// Set all pixels to 0xFF alpha to force opaque mode.
	bo := img.Bounds()
	rnd := rand.New(rand.NewSource(123))
	for y := bo.Min.Y; y < bo.Max.Y; y++ {
		for x := bo.Min.X; x < bo.Max.X; x++ {
			img.Set(x, y, image.RGBAColor{
				uint8(rnd.Intn(256)),
				uint8(rnd.Intn(256)),
				uint8(rnd.Intn(256)),
				255})
		}
	}
	if !img.Opaque() {
		panic("expected image to be opaque")
	}
	b.SetBytes(640 * 480 * 4)
	b.StartTimer()
	options := &Options{Quality: 90}
	for i := 0; i < b.N; i++ {
		Encode(ioutil.Discard, img, options)
	}
}
<commit_msg>image/jpeg: fix build<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package jpeg

import (
	"bytes"
	"image"
	"image/png"
	"io/ioutil"
	"rand"
	"os"
	"testing"
)

var testCase = []struct {
	filename  string
	quality   int
	tolerance int64
}{
	{"../testdata/video-001.png", 1, 24 << 8},
	{"../testdata/video-001.png", 20, 12 << 8},
	{"../testdata/video-001.png", 60, 8 << 8},
	{"../testdata/video-001.png", 80, 6 << 8},
	{"../testdata/video-001.png", 90, 4 << 8},
	{"../testdata/video-001.png", 100, 2 << 8},
}

func delta(u0, u1 uint32) int64 {
	d := int64(u0) - int64(u1)
	if d < 0 {
		return -d
	}
	return d
}

func readPng(filename string) (image.Image, os.Error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return png.Decode(f)
}

func TestWriter(t *testing.T) {
	for _, tc := range testCase {
		// Read the image.
		m0, err := readPng(tc.filename)
		if err != nil {
			t.Error(tc.filename, err)
			continue
		}
		// Encode that image as JPEG.
		buf := bytes.NewBuffer(nil)
		err = Encode(buf, m0, &Options{Quality: tc.quality})
		if err != nil {
			t.Error(tc.filename, err)
			continue
		}
		// Decode that JPEG.
		m1, err := Decode(buf)
		if err != nil {
			t.Error(tc.filename, err)
			continue
		}
		// Compute the average delta in RGB space.
		b := m0.Bounds()
		var sum, n int64
		for y := b.Min.Y; y < b.Max.Y; y++ {
			for x := b.Min.X; x < b.Max.X; x++ {
				c0 := m0.At(x, y)
				c1 := m1.At(x, y)
				r0, g0, b0, _ := c0.RGBA()
				r1, g1, b1, _ := c1.RGBA()
				sum += delta(r0, r1)
				sum += delta(g0, g1)
				sum += delta(b0, b1)
				n += 3
			}
		}
		// Compare the average delta to the tolerance level.
		if sum/n > tc.tolerance {
			t.Errorf("%s, quality=%d: average delta is too high", tc.filename, tc.quality)
			continue
		}
	}
}

func BenchmarkEncodeRGBOpaque(b *testing.B) {
	b.StopTimer()
	img := image.NewRGBA(image.Rect(0, 0, 640, 480))
	// Set all pixels to 0xFF alpha to force opaque mode.
	bo := img.Bounds()
	rnd := rand.New(rand.NewSource(123))
	for y := bo.Min.Y; y < bo.Max.Y; y++ {
		for x := bo.Min.X; x < bo.Max.X; x++ {
			img.Set(x, y, image.RGBAColor{
				uint8(rnd.Intn(256)),
				uint8(rnd.Intn(256)),
				uint8(rnd.Intn(256)),
				255})
		}
	}
	if !img.Opaque() {
		panic("expected image to be opaque")
	}
	b.SetBytes(640 * 480 * 4)
	b.StartTimer()
	options := &Options{Quality: 90}
	for i := 0; i < b.N; i++ {
		Encode(ioutil.Discard, img, options)
	}
}
<|endoftext|>


<commit_before>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package testing_test

import (
	"bytes"
	"runtime"
	"sync/atomic"
	"testing"
	"text/template"
)

var roundDownTests = []struct {
	v, expected int
}{
	{1, 1},
	{9, 1},
	{10, 10},
	{11, 10},
	{100, 100},
	{101, 100},
	{999, 100},
	{1000, 1000},
	{1001, 1000},
}

func TestRoundDown10(t *testing.T) {
	for _, tt := range roundDownTests {
		actual := testing.RoundDown10(tt.v)
		if tt.expected != actual {
			t.Errorf("roundDown10(%d): expected %d, actual %d", tt.v, tt.expected, actual)
		}
	}
}

var roundUpTests = []struct {
	v, expected int
}{
	{0, 1},
	{1, 1},
	{2, 2},
	{5, 5},
	{9, 10},
	{999, 1000},
	{1000, 1000},
	{1400, 2000},
	{1700, 2000},
	{4999, 5000},
	{5000, 5000},
	{5001, 10000},
}

func TestRoundUp(t *testing.T) {
	for _, tt := range roundUpTests {
		actual := testing.RoundUp(tt.v)
		if tt.expected != actual {
			t.Errorf("roundUp(%d): expected %d, actual %d", tt.v, tt.expected, actual)
		}
	}
}

func TestRunParallel(t *testing.T) {
	testing.Benchmark(func(b *testing.B) {
		procs := uint32(0)
		iters := uint64(0)
		b.SetParallelism(3)
		b.RunParallel(func(pb *testing.PB) {
			atomic.AddUint32(&procs, 1)
			for pb.Next() {
				atomic.AddUint64(&iters, 1)
			}
		})
		if want := uint32(3 * runtime.GOMAXPROCS(0)); procs != want {
			t.Errorf("got %v procs, want %v", procs, want)
		}
		if iters != uint64(b.N) {
			t.Errorf("got %v iters, want %v", iters, b.N)
		}
	})
}

func TestRunParallelFail(t *testing.T) {
	testing.Benchmark(func(b *testing.B) {
		b.RunParallel(func(pb *testing.PB) {
			// The function must be able to log/abort
			// w/o crashing/deadlocking the whole benchmark.
			b.Log("log")
			b.Error("error")
			b.Fatal("fatal")
		})
	})
}

func ExampleB_RunParallel() {
	// Parallel benchmark for text/template.Template.Execute on a single object.
	testing.Benchmark(func(b *testing.B) {
		templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
		// RunParallel will create GOMAXPROCS goroutines
		// and distribute work among them.
		b.RunParallel(func(pb *testing.PB) {
			// Each goroutine has its own bytes.Buffer.
			var buf bytes.Buffer
			for pb.Next() {
				// The loop body is executed b.N times total across all goroutines.
				buf.Reset()
				templ.Execute(&buf, "World")
			}
		})
	})
}
<commit_msg>testing: fix bogus benchmark Fatal must not be called from secondary goroutines. Fixes issue 7401.<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package testing_test

import (
	"bytes"
	"runtime"
	"sync/atomic"
	"testing"
	"text/template"
)

var roundDownTests = []struct {
	v, expected int
}{
	{1, 1},
	{9, 1},
	{10, 10},
	{11, 10},
	{100, 100},
	{101, 100},
	{999, 100},
	{1000, 1000},
	{1001, 1000},
}

func TestRoundDown10(t *testing.T) {
	for _, tt := range roundDownTests {
		actual := testing.RoundDown10(tt.v)
		if tt.expected != actual {
			t.Errorf("roundDown10(%d): expected %d, actual %d", tt.v, tt.expected, actual)
		}
	}
}

var roundUpTests = []struct {
	v, expected int
}{
	{0, 1},
	{1, 1},
	{2, 2},
	{5, 5},
	{9, 10},
	{999, 1000},
	{1000, 1000},
	{1400, 2000},
	{1700, 2000},
	{4999, 5000},
	{5000, 5000},
	{5001, 10000},
}

func TestRoundUp(t *testing.T) {
	for _, tt := range roundUpTests {
		actual := testing.RoundUp(tt.v)
		if tt.expected != actual {
			t.Errorf("roundUp(%d): expected %d, actual %d", tt.v, tt.expected, actual)
		}
	}
}

func TestRunParallel(t *testing.T) {
	testing.Benchmark(func(b *testing.B) {
		procs := uint32(0)
		iters := uint64(0)
		b.SetParallelism(3)
		b.RunParallel(func(pb *testing.PB) {
			atomic.AddUint32(&procs, 1)
			for pb.Next() {
				atomic.AddUint64(&iters, 1)
			}
		})
		if want := uint32(3 * runtime.GOMAXPROCS(0)); procs != want {
			t.Errorf("got %v procs, want %v", procs, want)
		}
		if iters != uint64(b.N) {
			t.Errorf("got %v iters, want %v", iters, b.N)
		}
	})
}

func TestRunParallelFail(t *testing.T) {
	testing.Benchmark(func(b *testing.B) {
		b.RunParallel(func(pb *testing.PB) {
			// The function must be able to log/abort
			// w/o crashing/deadlocking the whole benchmark.
			b.Log("log")
			b.Error("error")
		})
	})
}

func ExampleB_RunParallel() {
	// Parallel benchmark for text/template.Template.Execute on a single object.
	testing.Benchmark(func(b *testing.B) {
		templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
		// RunParallel will create GOMAXPROCS goroutines
		// and distribute work among them.
		b.RunParallel(func(pb *testing.PB) {
			// Each goroutine has its own bytes.Buffer.
			var buf bytes.Buffer
			for pb.Next() {
				// The loop body is executed b.N times total across all goroutines.
				buf.Reset()
				templ.Execute(&buf, "World")
			}
		})
	})
}
<|endoftext|>


<commit_before>package wsredis

import (
	"context"
	"log"
	"os"
	"strings"
	"sync"

	"github.com/go-redis/redis/v7"
	"github.com/jeremija/peer-calls/src/server-go/ws/wsmessage"
)

type Client interface {
	ID() string
	Messages() chan<- wsmessage.Message
}

type JSONMessage struct {
	Type    string `json:"type"`
	Payload string `json:"payload"`
}

type Doner interface {
	Done()
}

var serializer wsmessage.ByteSerializer

type RedisAdapter struct {
	clientsMu *sync.RWMutex
	// contains local clients connected to current instance
	clients map[string]Client
	// contains IDs of all clients in room, including those from other instances
	allClients map[string]struct{}
	logger     *log.Logger
	prefix     string
	room       string
	pubRedis   *redis.Client // FIXME replace this with interface
	subRedis   *redis.Client
	channels   struct {
		roomChannel   string
		clientPattern string
	}
}

func getRoomChannelName(prefix string, room string) string {
	return prefix + ":room:" + room
}

func getClientChannelName(prefix string, room string, clientID string) string {
	return prefix + ":room:" + room + ":client:" + clientID
}

func NewRedisAdapter(
	pubRedis *redis.Client,
	subRedis *redis.Client,
	prefix string,
	room string,
) *RedisAdapter {
	var clientsMu sync.RWMutex

	adapter := RedisAdapter{
		clients:    map[string]Client{},
		allClients: map[string]struct{}{},
		clientsMu:  &clientsMu,
		logger:     log.New(os.Stdout, "wsredis ", log.LstdFlags),
		prefix:     prefix,
		room:       room,
		pubRedis:   pubRedis,
		subRedis:   subRedis,
	}

	adapter.channels.roomChannel = getRoomChannelName(prefix, room)
	adapter.channels.clientPattern = getClientChannelName(prefix, room, "*")

	return &adapter
}

func (a *RedisAdapter) Add(client Client) {
	clientID := client.ID()
	a.logger.Printf("Add clientID: %s to room: %s", clientID, a.room)
	a.clientsMu.Lock()
	a.Broadcast(wsmessage.NewMessageRoomJoin(a.room, clientID))
	a.clients[clientID] = client
	a.logger.Printf("Add clientID: %s to room: %s done", clientID, a.room)
	a.clientsMu.Unlock()
}

func (a *RedisAdapter) Remove(clientID string) {
	a.logger.Printf("Remove clientID: %s from room: %s", clientID, a.room)
	a.clientsMu.Lock()
	if _, ok := a.clients[clientID]; ok {
		// can only remove clients connected to this adapter
		a.Broadcast(wsmessage.NewMessageRoomLeave(a.room, clientID))
		delete(a.clients, clientID)
		delete(a.allClients, clientID)
	}
	a.logger.Printf("Remove clientID: %s from room: %s done", clientID, a.room)
	a.clientsMu.Unlock()
}

// Returns IDs of all known clients connected to this room
func (a *RedisAdapter) Clients() (clientIDs []string) {
	a.logger.Printf("Clients")
	a.clientsMu.RLock()
	size := len(a.allClients)
	clientIDs = make([]string, size)
	i := 0
	for clientID := range a.allClients {
		clientIDs[i] = clientID
		i++
	}
	a.clientsMu.RUnlock()
	a.logger.Printf("Clients size: %d", size)
	return
}

// Returns count of all known clients connected to this room
func (a *RedisAdapter) Size() (size int) {
	a.clientsMu.RLock()
	size = len(a.allClients)
	a.clientsMu.RUnlock()
	return
}

func (a *RedisAdapter) handleMessage(
	pattern string,
	channel string,
	message string,
) {
	msg := serializer.Deserialize([]byte(message))
	a.logger.Printf("handleMessage pattern: %s, channel: %s, type: %s, payload: %s", pattern, channel, msg.Type(), msg.Payload())
	switch {
	case channel == a.channels.roomChannel:
		// localBroadcast to all clients
		switch msg.Type() {
		case wsmessage.MessageTypeRoomJoin:
			a.clientsMu.Lock()
			clientID := string(msg.Payload())
			a.allClients[clientID] = struct{}{}
			a.localBroadcast(msg)
			a.clientsMu.Unlock()
		case wsmessage.MessageTypeRoomLeave:
			a.clientsMu.Lock()
			a.localBroadcast(msg)
			clientID := string(msg.Payload())
			delete(a.allClients, clientID)
			a.clientsMu.Unlock()
		default:
			a.clientsMu.RLock()
			a.localBroadcast(msg)
			a.clientsMu.RUnlock()
		}
	case pattern == a.channels.clientPattern:
		params := strings.Split(channel, ":")
		clientID := params[len(params)-1]
		a.clientsMu.RLock()
		a.localEmit(clientID, msg)
		a.clientsMu.RUnlock()
	}
	a.logger.Println("handleMessage done")
}

// Reads from subscribed channels and dispatches relevant messages to
// client websockets. This method blocks until the context is closed.
func (a *RedisAdapter) subscribe(ctx context.Context, ready func()) error {
	a.logger.Println("Subscribe", a.channels.roomChannel, a.channels.clientPattern)
	pubsub := a.subRedis.PSubscribe(a.channels.roomChannel, a.channels.clientPattern)
	ch := pubsub.ChannelWithSubscriptions(100)

	isReady := false

	for {
		select {
		case msg := <-ch:
			switch msg := msg.(type) {
			case *redis.Subscription:
				if !isReady {
					isReady = true
					ready()
				}
			case *redis.Message:
				a.handleMessage(msg.Pattern, msg.Channel, msg.Payload)
			}
		case <-ctx.Done():
			err := ctx.Err()
			a.logger.Println("Subscribe done", err)
			pubsub.Close()
			return err
		}
	}
}

func (a *RedisAdapter) Subscribe() (stop func() error) {
	var wg sync.WaitGroup
	wg.Add(1)
	errChan := make(chan error)
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		err := a.subscribe(ctx, wg.Done)
		errChan <- err
		close(errChan)
	}()
	wg.Wait()
	return func() error {
		cancel()
		return <-errChan
	}
}

func (a *RedisAdapter) Broadcast(msg wsmessage.Message) {
	channel := a.channels.roomChannel
	a.logger.Printf("Broadcast type: %s, payload: %s to %s", msg.Type(), msg.Payload(), channel)
	a.pubRedis.Publish(channel, string(serializer.Serialize(msg)))
}

func (a *RedisAdapter) localBroadcast(msg wsmessage.Message) {
	a.logger.Printf("localBroadcast in room %s of message type: %s", a.room, msg.Type())
	for clientID := range a.clients {
		a.localEmit(clientID, msg)
	}
}

func (a *RedisAdapter) Emit(clientID string, msg wsmessage.Message) {
	channel := getClientChannelName(a.prefix, a.room, clientID)
	a.logger.Printf("Emit clientID: %s, type: %s, payload: %s to %s", clientID, msg.Type(), msg, channel)
	a.pubRedis.Publish(channel, string(serializer.Serialize(msg)))
}

func (a *RedisAdapter) localEmit(clientID string, msg wsmessage.Message) {
	client, ok := a.clients[clientID]
	if !ok {
		a.logger.Printf("localEmit in room: %s  - no local clientID: %s", a.room, clientID)
		return
	}
	if _, ok := a.allClients[clientID]; !ok {
		// we only want to emit messages to other clients once they are fully registered.
		a.logger.Printf("localEmit in room: %s  - skipping not completely registered clientID: %s", a.room, clientID)
		return
	}
	select {
	case client.Messages() <- msg:
		a.logger.Printf("localEmit in room: %s - sent to local clientID: %s", a.room, clientID)
	default:
		a.logger.Printf("localEmit in room: %s - buffer full for clientID: %s", a.room, clientID)
	}
}
<commit_msg>wsredis: Track users in room in redis<commit_after>package wsredis

import (
	"context"
	"log"
	"os"
	"strings"
	"sync"

	"github.com/go-redis/redis/v7"
	"github.com/jeremija/peer-calls/src/server-go/ws/wsmessage"
)

type Client interface {
	ID() string
	Messages() chan<- wsmessage.Message
}

type JSONMessage struct {
	Type    string `json:"type"`
	Payload string `json:"payload"`
}

type Doner interface {
	Done()
}

var serializer wsmessage.ByteSerializer

type RedisAdapter struct {
	clientsMu *sync.RWMutex
	// contains local clients connected to current instance
	clients map[string]Client
	// contains IDs of all clients in room, including those from other instances
	logger   *log.Logger
	prefix   string
	room     string
	pubRedis *redis.Client // FIXME replace this with interface
	subRedis *redis.Client
	keys     struct {
		roomChannel   string
		roomClients   string
		clientPattern string
	}
}

func getRoomChannelName(prefix string, room string) string {
	return prefix + ":room:" + room + ":broadcast"
}

func getClientChannelName(prefix string, room string, clientID string) string {
	return prefix + ":room:" + room + ":client:" + clientID
}

func getRoomClientsName(prefix string, room string) string {
	return prefix + ":room:" + room + ":clients"
}

func NewRedisAdapter(
	pubRedis *redis.Client,
	subRedis *redis.Client,
	prefix string,
	room string,
) *RedisAdapter {
	var clientsMu sync.RWMutex

	adapter := RedisAdapter{
		clients:   map[string]Client{},
		clientsMu: &clientsMu,
		logger:    log.New(os.Stdout, "wsredis ", log.LstdFlags),
		prefix:    prefix,
		room:      room,
		pubRedis:  pubRedis,
		subRedis:  subRedis,
	}

	adapter.keys.roomChannel = getRoomChannelName(prefix, room)
	adapter.keys.clientPattern = getClientChannelName(prefix, room, "*")
	adapter.keys.roomClients = getRoomClientsName(prefix, room)

	return &adapter
}

func (a *RedisAdapter) Add(client Client) {
	clientID := client.ID()
	a.logger.Printf("Add clientID: %s to room: %s", clientID, a.room)
	a.clientsMu.Lock()
	a.Broadcast(wsmessage.NewMessageRoomJoin(a.room, clientID))
	a.clients[clientID] = client
	a.logger.Printf("Add clientID: %s to room: %s done", clientID, a.room)
	a.clientsMu.Unlock()
}

func (a *RedisAdapter) Remove(clientID string) {
	a.logger.Printf("Remove clientID: %s from room: %s", clientID, a.room)
	a.clientsMu.Lock()
	if _, ok := a.clients[clientID]; ok {
		// can only remove clients connected to this adapter
		if err := a.pubRedis.HDel(a.keys.roomClients, clientID).Err(); err != nil {
			a.logger.Printf("Error deleting clientID from all clients: %s", err)
		}
		a.Broadcast(wsmessage.NewMessageRoomLeave(a.room, clientID))
		delete(a.clients, clientID)
	}
	a.logger.Printf("Remove clientID: %s from room: %s done", clientID, a.room)
	a.clientsMu.Unlock()
}

// Returns IDs of all known clients connected to this room
func (a *RedisAdapter) Clients() (clientIDs []string) {
	a.logger.Printf("Clients")

	r := a.pubRedis.HGetAll(a.keys.roomClients)
	allClients, err := r.Result()

	if err != nil {
		a.logger.Printf("Error retrieving clients in room: %s, reason: %s", a.room, err)
	}

	clientIDs = make([]string, len(allClients))
	i := 0
	for clientID := range allClients {
		clientIDs[i] = clientID
		i++
	}
	a.logger.Printf("Clients size: %d", len(clientIDs))
	return
}

// Returns count of all known clients connected to this room
func (a *RedisAdapter) Size() (size int) {
	return len(a.Clients())
}

func (a *RedisAdapter) handleMessage(
	pattern string,
	channel string,
	message string,
) {
	msg := serializer.Deserialize([]byte(message))
	a.logger.Printf("handleMessage pattern: %s, channel: %s, type: %s, payload: %s", pattern, channel, msg.Type(), msg.Payload())
	switch {
	case channel == a.keys.roomChannel:
		// localBroadcast to all clients
		switch msg.Type() {
		case wsmessage.MessageTypeRoomJoin:
			a.clientsMu.Lock()
			clientID := string(msg.Payload())
			a.pubRedis.HSet(a.keys.roomClients, clientID, "")
			a.localBroadcast(msg)
			a.clientsMu.Unlock()
		case wsmessage.MessageTypeRoomLeave:
			a.clientsMu.Lock()
			a.localBroadcast(msg)
			clientID := string(msg.Payload())
			a.pubRedis.HDel(a.keys.roomClients, clientID)
			a.clientsMu.Unlock()
		default:
			a.clientsMu.RLock()
			a.localBroadcast(msg)
			a.clientsMu.RUnlock()
		}
	case pattern == a.keys.clientPattern:
		params := strings.Split(channel, ":")
		clientID := params[len(params)-1]
		a.clientsMu.RLock()
		a.localEmit(clientID, msg)
		a.clientsMu.RUnlock()
	}
	a.logger.Println("handleMessage done")
}

// Reads from subscribed keys and dispatches relevant messages to
// client websockets. This method blocks until the context is closed.
func (a *RedisAdapter) subscribe(ctx context.Context, ready func()) error {
	a.logger.Println("Subscribe", a.keys.roomChannel, a.keys.clientPattern)
	pubsub := a.subRedis.PSubscribe(a.keys.roomChannel, a.keys.clientPattern)
	ch := pubsub.ChannelWithSubscriptions(100)

	isReady := false

	for {
		select {
		case msg := <-ch:
			switch msg := msg.(type) {
			case *redis.Subscription:
				if !isReady {
					isReady = true
					ready()
				}
			case *redis.Message:
				a.handleMessage(msg.Pattern, msg.Channel, msg.Payload)
			}
		case <-ctx.Done():
			err := ctx.Err()
			a.logger.Println("Subscribe done", err)
			pubsub.Close()
			return err
		}
	}
}

func (a *RedisAdapter) Subscribe() (stop func() error) {
	var wg sync.WaitGroup
	wg.Add(1)
	errChan := make(chan error)
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		err := a.subscribe(ctx, wg.Done)
		errChan <- err
		close(errChan)
	}()
	wg.Wait()
	return func() error {
		cancel()
		return <-errChan
	}
}

func (a *RedisAdapter) Broadcast(msg wsmessage.Message) {
	channel := a.keys.roomChannel
	a.logger.Printf("Broadcast type: %s, payload: %s to %s", msg.Type(), msg.Payload(), channel)
	a.pubRedis.Publish(channel, string(serializer.Serialize(msg)))
}

func (a *RedisAdapter) localBroadcast(msg wsmessage.Message) {
	a.logger.Printf("localBroadcast in room %s of message type: %s", a.room, msg.Type())
	for clientID := range a.clients {
		a.localEmit(clientID, msg)
	}
}

func (a *RedisAdapter) Emit(clientID string, msg wsmessage.Message) {
	channel := getClientChannelName(a.prefix, a.room, clientID)
	a.logger.Printf("Emit clientID: %s, type: %s, payload: %s to %s", clientID, msg.Type(), msg, channel)
	a.pubRedis.Publish(channel, string(serializer.Serialize(msg)))
}

func (a *RedisAdapter) localEmit(clientID string, msg wsmessage.Message) {
	client, ok := a.clients[clientID]
	if !ok {
		a.logger.Printf("localEmit in room: %s  - no local clientID: %s", a.room, clientID)
		return
	}
	select {
	case client.Messages() <- msg:
		a.logger.Printf("localEmit in room: %s - sent to local clientID: %s", a.room, clientID)
	default:
		a.logger.Printf("localEmit in room: %s - buffer full for clientID: %s", a.room, clientID)
	}
}
<|endoftext|>


<commit_before>package stitchnatc

import (
	"fmt"

	"github.com/artonge/Tamalou/Models"
	"github.com/artonge/Tamalou/Queries"
	"github.com/artonge/Tamalou/indexing"
	"github.com/blevesearch/bleve"
)

var index bleve.Index

// Open the index on startup
func init() {
	var err error
	index, err = indexing.OpenIndex("stitchnatc-search.bleve")
	if err != nil {
		fmt.Println("Error while initing stitchnatc index:\n	Error ==> ", err)
	}
}

// IndexStitchNAtc -
func IndexStitchNAtc() error {
	var err error
	fmt.Println("Indexing stitch & atc file...")
	index, err = indexing.InitIndex("stitchnatc-search.bleve")
	if err != nil {
		return fmt.Errorf("Error while initing stitchnatc index:\n	Error ==> %v", err)
	}
	index, err = indexStitchNAtc()
	if err != nil {
		return fmt.Errorf("Error while indexing stitch & atc file:\n Error ==> %v", err)
	}
	fmt.Println("stitch & atc file indexed.")
	return nil
}

func StitchIdSider2ATC(str string) string {
	return str[:3] + "m" + str[4:]
}

func GetChemicalsFromIds(drugs []*Models.Drug) error {
	for _, drug := range drugs {
		GetChemicalFromID(drug)
	}
}

func GetChemicalFromID(drug *Models.Drug) error {
	drug.STITCH_ID_ATC = StitchIdSider2ATC(drug.STITCH_ID_SIDER)
	var queryString string = drug.STITCH_ID_ATC
	indexQuery := bleve.NewQueryStringQuery(queryString)
	search := bleve.NewSearchRequest(indexQuery)
	searchResults, err := index.Search(search)
	if err != nil {
		// return nil, fmt.Errorf("Error querying index\n	Error ==> %v\n	Index ==> %v", err, index)
		return nil
	}
	var results []StitchNAtcStruct
	for _, hit := range searchResults.Hits {
		doc, err := index.Document(hit.ID)
		if err != nil {
			// return nil, fmt.Errorf("Error building indexable\n	Error ==> %v\n	Index ==> %v", err, index)
			return nil
		}
		results = append(results, BuildStitchNAtcStructFromDoc(doc).(StitchNAtcStruct))
	}

	// Brute force results
	fmt.Println("Got ", len(results), " results")
	for _, r := range results {
			if drug.STITCH_ID_ATC == r.Chemical {
				fmt.Println("\t\tMatch !")
				drug.Name = r.ATCLabel
				break
			}
		}
	}

	// for _, r := range results {
	// 	drug := Models.Drug{
	// 		CUI:  r.Chemical,
	// 		Name: r.ATCLabel,
	// 	}
	// 	drugArray = append(drugArray, &drug)
	// }

	// return drugArray, nil
	return nil
}

func QueryStitchIndex(query Queries.ITamalouQuery) ([]*Models.Drug, error) {
	switch query.Type() {
	case "or":
	case "and":
		var mergeDrug []*Models.Drug
		for _, child := range query.Children() {
			drug, err := QueryStitchIndex(child)
			if err != nil {
				return nil, err
			}
			if len(mergeDrug) > 0 {
				mergeDrug = append(mergeDrug, drug...)
			} else {
				mergeDrug = drug
			}
		}
		return mergeDrug, nil
	default:
		results, err := indexing.QueryIndex(index, query, BuildStitchNAtcStructFromDoc)
		if err != nil {
			return nil, fmt.Errorf("Error while querying omim's index\n	Error ==> %v", err)
		}

		var drugArray []*Models.Drug

		for _, r := range results {
			drug := Models.Drug{
				Name: r.(StitchNAtcStruct).ATCLabel,
			}
			drugArray = append(drugArray, &drug)
		}

		return drugArray, nil
	}
	return nil, nil
}
<commit_msg>fix error<commit_after>package stitchnatc

import (
	"fmt"

	"github.com/artonge/Tamalou/Models"
	"github.com/artonge/Tamalou/Queries"
	"github.com/artonge/Tamalou/indexing"
	"github.com/blevesearch/bleve"
)

var index bleve.Index

// Open the index on startup
func init() {
	var err error
	index, err = indexing.OpenIndex("stitchnatc-search.bleve")
	if err != nil {
		fmt.Println("Error while initing stitchnatc index:\n	Error ==> ", err)
	}
}

// IndexStitchNAtc -
func IndexStitchNAtc() error {
	var err error
	fmt.Println("Indexing stitch & atc file...")
	index, err = indexing.InitIndex("stitchnatc-search.bleve")
	if err != nil {
		return fmt.Errorf("Error while initing stitchnatc index:\n	Error ==> %v", err)
	}
	index, err = indexStitchNAtc()
	if err != nil {
		return fmt.Errorf("Error while indexing stitch & atc file:\n Error ==> %v", err)
	}
	fmt.Println("stitch & atc file indexed.")
	return nil
}

func StitchIdSider2ATC(str string) string {
	return str[:3] + "m" + str[4:]
}

func GetChemicalsFromIds(drugs []*Models.Drug) error {
	for _, drug := range drugs {
		GetChemicalFromID(drug)
	}
}

func GetChemicalFromID(drug *Models.Drug) error {
	drug.STITCH_ID_ATC = StitchIdSider2ATC(drug.STITCH_ID_SIDER)
	var queryString string = drug.STITCH_ID_ATC
	indexQuery := bleve.NewQueryStringQuery(queryString)
	search := bleve.NewSearchRequest(indexQuery)
	searchResults, err := index.Search(search)
	if err != nil {
		// return nil, fmt.Errorf("Error querying index\n	Error ==> %v\n	Index ==> %v", err, index)
		return nil
	}
	var results []StitchNAtcStruct
	for _, hit := range searchResults.Hits {
		doc, err := index.Document(hit.ID)
		if err != nil {
			// return nil, fmt.Errorf("Error building indexable\n	Error ==> %v\n	Index ==> %v", err, index)
			return nil
		}
		results = append(results, BuildStitchNAtcStructFromDoc(doc).(StitchNAtcStruct))
	}

	// Brute force results
	fmt.Println("Got ", len(results), " results")
	for _, r := range results {
		if drug.STITCH_ID_ATC == r.Chemical {
			fmt.Println("\t\tMatch !")
			drug.Name = r.ATCLabel
			break
		}
	}

	// for _, r := range results {
	// 	drug := Models.Drug{
	// 		CUI:  r.Chemical,
	// 		Name: r.ATCLabel,
	// 	}
	// 	drugArray = append(drugArray, &drug)
	// }

	// return drugArray, nil
	return nil
}

func QueryStitchIndex(query Queries.ITamalouQuery) ([]*Models.Drug, error) {
	switch query.Type() {
	case "or":
	case "and":
		var mergeDrug []*Models.Drug
		for _, child := range query.Children() {
			drug, err := QueryStitchIndex(child)
			if err != nil {
				return nil, err
			}
			if len(mergeDrug) > 0 {
				mergeDrug = append(mergeDrug, drug...)
			} else {
				mergeDrug = drug
			}
		}
		return mergeDrug, nil
	default:
		results, err := indexing.QueryIndex(index, query, BuildStitchNAtcStructFromDoc)
		if err != nil {
			return nil, fmt.Errorf("Error while querying omim's index\n	Error ==> %v", err)
		}

		var drugArray []*Models.Drug

		for _, r := range results {
			drug := Models.Drug{
				Name: r.(StitchNAtcStruct).ATCLabel,
			}
			drugArray = append(drugArray, &drug)
		}

		return drugArray, nil
	}
	return nil, nil
}
<|endoftext|>


<commit_before>package controllers

import (
	"net/http"

	"github.com/buro9/microcosm/models"
	"github.com/buro9/microcosm/web/api"
	"github.com/buro9/microcosm/web/bag"
	"github.com/buro9/microcosm/web/errors"
	"github.com/buro9/microcosm/web/templates"
)

// SearchGet will return search results
func SearchGet(w http.ResponseWriter, r *http.Request) {
	// We're not sanitising the input here as the API does that very thoroughly:
	// https://github.com/microcosm-cc/microcosm/blob/main/models/search_query.go
	//
	// Knowing this, we're just going to pass this straight through.
	searchResults, status, err := api.DoSearch(r.Context(), r.URL.Query())
	if err != nil {
		errors.Render(w, r, status, err)
		return
	}

	data := templates.Data{
		Request:    r,
		Site:       bag.GetSite(r.Context()),
		User:       bag.GetProfile(r.Context()),
		Section:    `search`,
		Pagination: models.ParsePagination(searchResults.Items),

		SearchResults: searchResults,
	}

	err = templates.RenderHTML(w, "search", data)
	if err != nil {
		errors.Render(w, r, status, err)
	}
}
<commit_msg>Enabled the default search options from the forms<commit_after>package controllers

import (
	"net/http"

	"github.com/buro9/microcosm/models"
	"github.com/buro9/microcosm/web/api"
	"github.com/buro9/microcosm/web/bag"
	"github.com/buro9/microcosm/web/errors"
	"github.com/buro9/microcosm/web/templates"
)

// SearchGet will return search results
func SearchGet(w http.ResponseWriter, r *http.Request) {
	// We're not sanitising the input here as the API does that very thoroughly:
	// https://github.com/microcosm-cc/microcosm/blob/main/models/search_query.go
	//
	// Knowing this, we're just going to pass this straight through whereas
	// everywhere else we effectively sanitise the input by checking every arg
	// as that would improve cacheability of the underlying resource, but here
	// we're allowing something deep inside the API to do that.
	q := r.URL.Query()

	if q.Has("defaults") {
		q.Set("inTitle", "true")
		q.Set("sort", "date")
	}

	searchResults, status, err := api.DoSearch(r.Context(), q)
	if err != nil {
		errors.Render(w, r, status, err)
		return
	}

	data := templates.Data{
		Request:    r,
		Site:       bag.GetSite(r.Context()),
		User:       bag.GetProfile(r.Context()),
		Section:    `search`,
		Pagination: models.ParsePagination(searchResults.Items),

		SearchResults: searchResults,
	}

	err = templates.RenderHTML(w, "search", data)
	if err != nil {
		errors.Render(w, r, status, err)
	}
}
<|endoftext|>


<commit_before>package plugin_test

import (
	"io/ioutil"
	"net/rpc"
	"os"
	"path/filepath"
	"runtime"

	"github.com/cloudfoundry/cli/cf/command"
	testCommand "github.com/cloudfoundry/cli/cf/command/fakes"
	"github.com/cloudfoundry/cli/cf/configuration/plugin_config"
	testconfig "github.com/cloudfoundry/cli/cf/configuration/plugin_config/fakes"
	testcmd "github.com/cloudfoundry/cli/testhelpers/commands"
	testreq "github.com/cloudfoundry/cli/testhelpers/requirements"
	testterm "github.com/cloudfoundry/cli/testhelpers/terminal"

	. "github.com/cloudfoundry/cli/cf/commands/plugin"
	. "github.com/cloudfoundry/cli/testhelpers/matchers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Install", func() {
	var (
		ui                  *testterm.FakeUI
		requirementsFactory *testreq.FakeReqFactory
		config              *testconfig.FakePluginConfiguration

		coreCmds   map[string]command.Command
		pluginFile *os.File
		homeDir    string
		pluginDir  string
		curDir     string

		test_1         string
		test_2         string
		test_with_help string
		test_with_push string
	)

	BeforeEach(func() {
		ui = &testterm.FakeUI{}
		requirementsFactory = &testreq.FakeReqFactory{}
		config = &testconfig.FakePluginConfiguration{}
		coreCmds = make(map[string]command.Command)

		dir, err := os.Getwd()
		if err != nil {
			panic(err)
		}
		test_1 = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_1.exe")
		test_2 = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_2.exe")
		test_with_help = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_with_help.exe")
		test_with_push = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_with_push.exe")

		rpc.DefaultServer = rpc.NewServer()

		homeDir, err = ioutil.TempDir(os.TempDir(), "plugins")
		Expect(err).ToNot(HaveOccurred())

		pluginDir = filepath.Join(homeDir, ".cf", "plugins")
		config.GetPluginPathReturns(pluginDir)

		curDir, err = os.Getwd()
		Expect(err).ToNot(HaveOccurred())
		pluginFile, err = ioutil.TempFile("./", "test_plugin")
		Expect(err).ToNot(HaveOccurred())

		if runtime.GOOS != "windows" {
			err = os.Chmod(test_1, 0700)
			Expect(err).ToNot(HaveOccurred())
		}
	})

	AfterEach(func() {
		os.Remove(filepath.Join(curDir, pluginFile.Name()))
		os.Remove(homeDir)
	})

	runCommand := func(args ...string) bool {
		cmd := NewPluginInstall(ui, config, coreCmds)
		return testcmd.RunCommand(cmd, args, requirementsFactory)
	}

	Describe("requirements", func() {
		It("fails with usage when not provided a path to the plugin executable", func() {
			Expect(runCommand()).ToNot(HavePassedRequirements())
		})
	})

	Describe("failures", func() {
		Context("when the plugin contains a 'help' command", func() {
			It("fails", func() {
				runCommand(test_with_help)

				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Plugin 'TestWithHelp' cannot be installed from", test_with_help, "at this time because the command 'cf help' already exists."},
					[]string{"FAILED"},
				))
			})
		})

		Context("when the plugin contains a core command", func() {
			It("fails", func() {
				coreCmds["push"] = &testCommand.FakeCommand{}
				runCommand(test_with_push)

				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Plugin 'TestWithPush' cannot be installed from", test_with_push, "at this time because the command 'cf push' already exists."},
					[]string{"FAILED"},
				))
			})
		})

		It("plugin binary argument is a bad file path", func() {
			runCommand("path/to/not/a/thing.exe")

			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Binary file 'path/to/not/a/thing.exe' not found"},
				[]string{"FAILED"},
			))
		})

		It("if plugin name is already taken", func() {
			config.PluginsReturns(map[string]plugin_config.PluginMetadata{"Test1": plugin_config.PluginMetadata{}})
			runCommand(test_1)

			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Plugin name", "Test1", "is already taken"},
				[]string{"FAILED"},
			))
		})

		Context("io", func() {
			BeforeEach(func() {
				err := os.MkdirAll(pluginDir, 0700)
				Expect(err).NotTo(HaveOccurred())
			})

			It("if a file with the plugin name already exists under ~/.cf/plugin/", func() {
				config.PluginsReturns(map[string]plugin_config.PluginMetadata{"useless": plugin_config.PluginMetadata{}})
				config.GetPluginPathReturns(curDir)

				runCommand(filepath.Join(curDir, pluginFile.Name()))
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Installing plugin"},
					[]string{"The file", pluginFile.Name(), "already exists"},
					[]string{"FAILED"},
				))
			})
		})
	})

	Describe("success", func() {
		var (
			sourceBinaryPath string
		)

		BeforeEach(func() {
			err := os.MkdirAll(pluginDir, 0700)
			Expect(err).ToNot(HaveOccurred())

			sourceBinaryPath = filepath.Join("..", "..", "..", "fixtures", "plugins", "test_1.exe")
			config.GetPluginPathReturns(pluginDir)
			runCommand(sourceBinaryPath)
		})

		It("copies the plugin into directory <FAKE_HOME_DIR>/.cf/plugins/PLUGIN_FILE_NAME", func() {
			_, err := os.Stat(test_1)
			Expect(err).ToNot(HaveOccurred())
			_, err = os.Stat(filepath.Join(pluginDir, "test_1.exe"))
			Expect(err).ToNot(HaveOccurred())
		})

		if runtime.GOOS != "windows" {
			It("Chmods the plugin so it is executable", func() {
				fileInfo, err := os.Stat(filepath.Join(pluginDir, "test_1.exe"))
				Expect(err).ToNot(HaveOccurred())
				Expect(int(fileInfo.Mode())).To(Equal(0700))
			})
		}

		It("populate the configuration with plugin metadata", func() {
			pluginName, pluginMetadata := config.SetPluginArgsForCall(0)

			Expect(pluginName).To(Equal("Test1"))
			Expect(pluginMetadata.Location).To(Equal(filepath.Join(pluginDir, "test_1.exe")))
			Expect(pluginMetadata.Commands[0].Name).To(Equal("test_1_cmd1"))
			Expect(pluginMetadata.Commands[0].HelpText).To(Equal("help text for test_1_cmd1"))
			Expect(pluginMetadata.Commands[1].Name).To(Equal("test_1_cmd2"))
			Expect(pluginMetadata.Commands[1].HelpText).To(Equal("help text for test_1_cmd2"))
			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Installing plugin", sourceBinaryPath},
				[]string{"OK"},
				[]string{"Plugin", "Test1", "successfully installed"},
			))
		})
	})
})
<commit_msg>Remove duplicated plugin_binary path variable in test<commit_after>package plugin_test

import (
	"io/ioutil"
	"net/rpc"
	"os"
	"path/filepath"
	"runtime"

	"github.com/cloudfoundry/cli/cf/command"
	testCommand "github.com/cloudfoundry/cli/cf/command/fakes"
	"github.com/cloudfoundry/cli/cf/configuration/plugin_config"
	testconfig "github.com/cloudfoundry/cli/cf/configuration/plugin_config/fakes"
	testcmd "github.com/cloudfoundry/cli/testhelpers/commands"
	testreq "github.com/cloudfoundry/cli/testhelpers/requirements"
	testterm "github.com/cloudfoundry/cli/testhelpers/terminal"

	. "github.com/cloudfoundry/cli/cf/commands/plugin"
	. "github.com/cloudfoundry/cli/testhelpers/matchers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Install", func() {
	var (
		ui                  *testterm.FakeUI
		requirementsFactory *testreq.FakeReqFactory
		config              *testconfig.FakePluginConfiguration

		coreCmds   map[string]command.Command
		pluginFile *os.File
		homeDir    string
		pluginDir  string
		curDir     string

		test_1         string
		test_2         string
		test_with_help string
		test_with_push string
	)

	BeforeEach(func() {
		ui = &testterm.FakeUI{}
		requirementsFactory = &testreq.FakeReqFactory{}
		config = &testconfig.FakePluginConfiguration{}
		coreCmds = make(map[string]command.Command)

		dir, err := os.Getwd()
		if err != nil {
			panic(err)
		}
		test_1 = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_1.exe")
		test_2 = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_2.exe")
		test_with_help = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_with_help.exe")
		test_with_push = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_with_push.exe")

		rpc.DefaultServer = rpc.NewServer()

		homeDir, err = ioutil.TempDir(os.TempDir(), "plugins")
		Expect(err).ToNot(HaveOccurred())

		pluginDir = filepath.Join(homeDir, ".cf", "plugins")
		config.GetPluginPathReturns(pluginDir)

		curDir, err = os.Getwd()
		Expect(err).ToNot(HaveOccurred())
		pluginFile, err = ioutil.TempFile("./", "test_plugin")
		Expect(err).ToNot(HaveOccurred())

		if runtime.GOOS != "windows" {
			err = os.Chmod(test_1, 0700)
			Expect(err).ToNot(HaveOccurred())
		}
	})

	AfterEach(func() {
		os.Remove(filepath.Join(curDir, pluginFile.Name()))
		os.Remove(homeDir)
	})

	runCommand := func(args ...string) bool {
		cmd := NewPluginInstall(ui, config, coreCmds)
		return testcmd.RunCommand(cmd, args, requirementsFactory)
	}

	Describe("requirements", func() {
		It("fails with usage when not provided a path to the plugin executable", func() {
			Expect(runCommand()).ToNot(HavePassedRequirements())
		})
	})

	Describe("failures", func() {
		Context("when the plugin contains a 'help' command", func() {
			It("fails", func() {
				runCommand(test_with_help)

				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Plugin 'TestWithHelp' cannot be installed from", test_with_help, "at this time because the command 'cf help' already exists."},
					[]string{"FAILED"},
				))
			})
		})

		Context("when the plugin contains a core command", func() {
			It("fails", func() {
				coreCmds["push"] = &testCommand.FakeCommand{}
				runCommand(test_with_push)

				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Plugin 'TestWithPush' cannot be installed from", test_with_push, "at this time because the command 'cf push' already exists."},
					[]string{"FAILED"},
				))
			})
		})

		It("plugin binary argument is a bad file path", func() {
			runCommand("path/to/not/a/thing.exe")

			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Binary file 'path/to/not/a/thing.exe' not found"},
				[]string{"FAILED"},
			))
		})

		It("if plugin name is already taken", func() {
			config.PluginsReturns(map[string]plugin_config.PluginMetadata{"Test1": plugin_config.PluginMetadata{}})
			runCommand(test_1)

			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Plugin name", "Test1", "is already taken"},
				[]string{"FAILED"},
			))
		})

		Context("io", func() {
			BeforeEach(func() {
				err := os.MkdirAll(pluginDir, 0700)
				Expect(err).NotTo(HaveOccurred())
			})

			It("if a file with the plugin name already exists under ~/.cf/plugin/", func() {
				config.PluginsReturns(map[string]plugin_config.PluginMetadata{"useless": plugin_config.PluginMetadata{}})
				config.GetPluginPathReturns(curDir)

				runCommand(filepath.Join(curDir, pluginFile.Name()))
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Installing plugin"},
					[]string{"The file", pluginFile.Name(), "already exists"},
					[]string{"FAILED"},
				))
			})
		})
	})

	Describe("success", func() {
		BeforeEach(func() {
			err := os.MkdirAll(pluginDir, 0700)
			Expect(err).ToNot(HaveOccurred())
			config.GetPluginPathReturns(pluginDir)
			runCommand(test_1)
		})

		It("copies the plugin into directory <FAKE_HOME_DIR>/.cf/plugins/PLUGIN_FILE_NAME", func() {
			_, err := os.Stat(test_1)
			Expect(err).ToNot(HaveOccurred())
			_, err = os.Stat(filepath.Join(pluginDir, "test_1.exe"))
			Expect(err).ToNot(HaveOccurred())
		})

		if runtime.GOOS != "windows" {
			It("Chmods the plugin so it is executable", func() {
				fileInfo, err := os.Stat(filepath.Join(pluginDir, "test_1.exe"))
				Expect(err).ToNot(HaveOccurred())
				Expect(int(fileInfo.Mode())).To(Equal(0700))
			})
		}

		It("populate the configuration with plugin metadata", func() {
			pluginName, pluginMetadata := config.SetPluginArgsForCall(0)

			Expect(pluginName).To(Equal("Test1"))
			Expect(pluginMetadata.Location).To(Equal(filepath.Join(pluginDir, "test_1.exe")))
			Expect(pluginMetadata.Commands[0].Name).To(Equal("test_1_cmd1"))
			Expect(pluginMetadata.Commands[0].HelpText).To(Equal("help text for test_1_cmd1"))
			Expect(pluginMetadata.Commands[1].Name).To(Equal("test_1_cmd2"))
			Expect(pluginMetadata.Commands[1].HelpText).To(Equal("help text for test_1_cmd2"))
			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Installing plugin", test_1},
				[]string{"OK"},
				[]string{"Plugin", "Test1", "successfully installed"},
			))
		})
	})
})
<|endoftext|>


<commit_before>package main

import (
	"github.com/piotrkowalczuk/charon"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
)

type listGroupPermissionsHandler struct {
	*handler
}

func (lgph *listGroupPermissionsHandler) handle(ctx context.Context, req *charon.ListGroupPermissionsRequest) (*charon.ListGroupPermissionsResponse, error) {
	lgph.loggerWith("group_id", req.Id)

	permissions, err := lgph.repository.permission.FindByUserID(req.Id)
	if err != nil {
		if err == sql.ErrNoRows {
			sklog.Debug(lgph.logger, "user permissions retrieved", "user_id", req.Id, "count", len(permissions))

			return &charon.ListUserPermissionsResponse{}, nil
		}
		return nil, err
	}

	perms := make([]string, 0, len(permissions))
	for _, p := range permissions {
		perms = append(perms, p.Permission().String())
	}

	lgph.loggerWith("results", len(permissions))

	return &charon.ListUserPermissionsResponse{
		Permissions: perms,
	}, nil
}
<commit_msg>wrong response fix<commit_after>package main

import (
	"database/sql"

	"github.com/piotrkowalczuk/charon"
	"github.com/piotrkowalczuk/sklog"
	"golang.org/x/net/context"
)

type listGroupPermissionsHandler struct {
	*handler
}

func (lgph *listGroupPermissionsHandler) handle(ctx context.Context, req *charon.ListGroupPermissionsRequest) (*charon.ListGroupPermissionsResponse, error) {
	lgph.loggerWith("group_id", req.Id)

	permissions, err := lgph.repository.permission.FindByUserID(req.Id)
	if err != nil {
		if err == sql.ErrNoRows {
			sklog.Debug(lgph.logger, "group permissions retrieved", "user_id", req.Id, "count", len(permissions))

			return &charon.ListGroupPermissionsResponse{}, nil
		}
		return nil, err
	}

	perms := make([]string, 0, len(permissions))
	for _, p := range permissions {
		perms = append(perms, p.Permission().String())
	}

	lgph.loggerWith("results", len(permissions))

	return &charon.ListGroupPermissionsResponse{
		Permissions: perms,
	}, nil
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 main

import (
	"flag"
	"fmt"
	"github.com/jacobsa/comeback/backup"
	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/config"
	"github.com/jacobsa/comeback/fs"
	"github.com/jacobsa/comeback/kv/disk"
	"github.com/jacobsa/comeback/sys"
	"io/ioutil"
	"log"
	"os"
)

var g_configFile = flag.String("config", "", "Path to config file.")
var g_jobName = flag.String("job", "", "Job name within the config file.")

func main() {
	var err error
	flag.Parse()

	// Attempt to read the user's config data.
	if *g_configFile == "" {
		fmt.Println("You must set -config.")
		os.Exit(1)
	}

	configData, err := ioutil.ReadFile(*g_configFile)
	if err != nil {
		fmt.Println("Error reading config file:", err)
		os.Exit(1)
	}

	// Parse the config file.
	cfg, err := config.Parse(configData)
	if err != nil {
		fmt.Println("Parsing config file:", err)
		os.Exit(1)
	}

	// Validate the config file.
	if err := config.Validate(cfg); err != nil {
		fmt.Printf("Config file invalid: %v", err)
		os.Exit(1)
	}

	// Look for the specified job.
	if *g_jobName == "" {
		fmt.Println("You must set -job.")
		os.Exit(1)
	}

	job, ok := cfg.Jobs[*g_jobName]
	if !ok {
		fmt.Println("Unknown job:", *g_jobName)
		os.Exit(1)
	}

	// Create a user registry.
	userRegistry, err := sys.NewUserRegistry()
	if err != nil {
		log.Fatalf("Creating user registry: %v", err)
	}

	// Create a group registry.
	groupRegistry, err := sys.NewGroupRegistry()
	if err != nil {
		log.Fatalf("Creating group registry: %v", err)
	}

	// Create a file system.
	fileSystem, err := fs.NewFileSystem(userRegistry, groupRegistry)
	if err != nil {
		log.Fatalf("Creating file system: %v", err)
	}

	// Create the kv store.
	kvStore, err := disk.NewDiskKvStore("/tmp/blobs", fileSystem)
	if err != nil {
		log.Fatalf("Creating kv store: %v", err)
	}

	// Create the blob store.
	blobStore := blob.NewKvBasedBlobStore(kvStore)

	// Create the file saver.
	fileSaver, err := backup.NewFileSaver(blobStore, 1<<24)
	if err != nil {
		log.Fatalf("Creating file saver: %v", err)
	}

	// Create a directory saver.
	dirSaver, err := backup.NewDirectorySaver(
		blobStore,
		fileSystem,
		fileSaver)

	if err != nil {
		log.Fatalf("Creating directory saver: %v", err)
	}

	// Run the job.
	score, err := dirSaver.Save(job.BasePath, "", job.Excludes)
	if err != nil {
		log.Fatalf("Saving: %v", err)
	}

	// Print the score.
	fmt.Printf("Score: %s\n", score.Hex())
}
<commit_msg>Partially updated the save tool.<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 main

import (
	"flag"
	"fmt"
	"github.com/jacobsa/aws/s3"
	"github.com/jacobsa/aws/sdb"
	"github.com/jacobsa/comeback/backup"
	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/crypto"
	"github.com/jacobsa/comeback/config"
	"github.com/jacobsa/comeback/fs"
	s3_kv "github.com/jacobsa/comeback/kv/s3"
	"github.com/jacobsa/comeback/sys"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"time"
)

var g_configFile = flag.String("config", "", "Path to config file.")
var g_jobName = flag.String("job", "", "Job name within the config file.")

func main() {
	var err error
	flag.Parse()

	// Attempt to read the user's config data.
	if *g_configFile == "" {
		fmt.Println("You must set -config.")
		os.Exit(1)
	}

	configData, err := ioutil.ReadFile(*g_configFile)
	if err != nil {
		fmt.Println("Error reading config file:", err)
		os.Exit(1)
	}

	// Parse the config file.
	cfg, err := config.Parse(configData)
	if err != nil {
		fmt.Println("Parsing config file:", err)
		os.Exit(1)
	}

	// Validate the config file.
	if err := config.Validate(cfg); err != nil {
		fmt.Printf("Config file invalid: %v", err)
		os.Exit(1)
	}

	// Look for the specified job.
	if *g_jobName == "" {
		fmt.Println("You must set -job.")
		os.Exit(1)
	}

	job, ok := cfg.Jobs[*g_jobName]
	if !ok {
		fmt.Println("Unknown job:", *g_jobName)
		os.Exit(1)
	}

	// Create a user registry.
	userRegistry, err := sys.NewUserRegistry()
	if err != nil {
		log.Fatalf("Creating user registry: %v", err)
	}

	// Create a group registry.
	groupRegistry, err := sys.NewGroupRegistry()
	if err != nil {
		log.Fatalf("Creating group registry: %v", err)
	}

	// Create a file system.
	fileSystem, err := fs.NewFileSystem(userRegistry, groupRegistry)
	if err != nil {
		log.Fatalf("Creating file system: %v", err)
	}

	// Open a connection to SimpleDB.
	db, err := sdb.NewSimpleDB(cfg.SdbRegion, cfg.AccessKey)
	if err != nil {
		log.Fatalf("Creating SimpleDB: %v", err)
	}

	// Open a connection to S3.
	bucket, err := s3.OpenBucket(cfg.S3Bucket, cfg.S3Region, cfg.AccessKey)
	if err != nil {
		log.Fatalf("Creating bucket: %v", err)
	}

	// Create the crypter.
	crypter, err := crypto.NewCrypter(cryptoKey)
	if err != nil {
		log.Fatalf("Creating crypter: %v", err)
	}

	// Create the backup registry.
	randSrc := rand.New(rand.NewSource(time.Now().UnixNano()))
	registry, err := backup.NewRegistry(db, cfg.SdbDomain, crypter, randSrc)
	if err != nil {
		log.Fatalf("Creating registry: %v", err)
	}

	// Create the kv store.
	kvStore, err := s3_kv.NewS3KvStore(bucket)
	if err != nil {
		log.Fatalf("Creating kv store: %v", err)
	}

	// Create the blob store.
	blobStore := blob.NewKvBasedBlobStore(kvStore)
	blobStore = blob.NewCheckingStore(blobStore)
	blobStore = blob.NewEncryptingStore(crypter, blobStore)

	// Create the file saver.
	fileSaver, err := backup.NewFileSaver(blobStore, 1<<24)
	if err != nil {
		log.Fatalf("Creating file saver: %v", err)
	}

	// Create a directory saver.
	dirSaver, err := backup.NewDirectorySaver(
		blobStore,
		fileSystem,
		fileSaver)

	if err != nil {
		log.Fatalf("Creating directory saver: %v", err)
	}

	// Run the job.
	score, err := dirSaver.Save(job.BasePath, "", job.Excludes)
	if err != nil {
		log.Fatalf("Saving: %v", err)
	}

	// Print the score.
	fmt.Printf("Score: %s\n", score.Hex())
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes 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 huaweicloud

import (
	"fmt"

	apiv1 "k8s.io/api/core/v1"
	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
	huaweicloudsdkasmodel "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3/services/as/v1/model"
	"k8s.io/klog/v2"
	schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
)

// AutoScalingGroup represents a HuaweiCloud's 'Auto Scaling Group' which also can be treated as a node group.
type AutoScalingGroup struct {
	cloudServiceManager CloudServiceManager
	groupName           string
	groupID             string
	minInstanceNumber   int
	maxInstanceNumber   int
}

// Check if our AutoScalingGroup implements necessary interface.
var _ cloudprovider.NodeGroup = &AutoScalingGroup{}

func newAutoScalingGroup(csm CloudServiceManager, sg huaweicloudsdkasmodel.ScalingGroups) AutoScalingGroup {
	return AutoScalingGroup{
		cloudServiceManager: csm,
		groupName:           *sg.ScalingGroupName,
		groupID:             *sg.ScalingGroupId,
		minInstanceNumber:   int(*sg.MinInstanceNumber),
		maxInstanceNumber:   int(*sg.MaxInstanceNumber),
	}
}

// MaxSize returns maximum size of the node group.
func (asg *AutoScalingGroup) MaxSize() int {
	return asg.maxInstanceNumber
}

// MinSize returns minimum size of the node group.
func (asg *AutoScalingGroup) MinSize() int {
	return asg.minInstanceNumber
}

// TargetSize returns the current target size of the node group. It is possible that the
// number of nodes in Kubernetes is different at the moment but should be equal
// to Size() once everything stabilizes (new nodes finish startup and registration or
// removed nodes are deleted completely). Implementation required.
//
// Target size is desire instance number of the auto scaling group, and not equal to current instance number if the
// auto scaling group is in increasing or decreasing process.
func (asg *AutoScalingGroup) TargetSize() (int, error) {
	desireNumber, err := asg.cloudServiceManager.GetDesireInstanceNumber(asg.groupID)
	if err != nil {
		klog.Warningf("failed to get group target size. groupID: %s, error: %v", asg.groupID, err)
		return 0, err
	}

	return desireNumber, nil
}

// IncreaseSize increases the size of the node group. To delete a node you need
// to explicitly name it and use DeleteNode. This function should wait until
// node group size is updated. Implementation required.
func (asg *AutoScalingGroup) IncreaseSize(delta int) error {
	err := asg.cloudServiceManager.IncreaseSizeInstance(asg.groupID, delta)
	if err != nil {
		klog.Warningf("failed to increase size for group: %s, error: %v", asg.groupID, err)
		return err
	}

	return nil
}

// DeleteNodes deletes nodes from this node group. Error is returned either on
// failure or if the given node doesn't belong to this node group. This function
// should wait until node group size is updated. Implementation required.
func (asg *AutoScalingGroup) DeleteNodes(nodes []*apiv1.Node) error {
	instances, err := asg.cloudServiceManager.GetInstances(asg.groupID)
	if err != nil {
		klog.Warningf("failed to get instances from group: %s, error: %v", asg.groupID, err)
		return err
	}

	instanceIds := make([]string, 0, len(instances))
	for _, instance := range instances {
		for _, n := range nodes {
			if n.Spec.ProviderID == instance.Id {
				instanceIds = append(instanceIds, instance.Id)
			}
		}
	}

	err = asg.cloudServiceManager.DeleteScalingInstances(asg.groupID, instanceIds)
	if err != nil {
		klog.Warningf("failed to delete scaling instances. error: %v", err)
		return err
	}

	return nil
}

// DecreaseTargetSize decreases the target size of the node group. This function
// doesn't permit to delete any existing node and can be used only to reduce the
// request for new nodes that have not been yet fulfilled. Delta should be negative.
// It is assumed that cloud provider will not delete the existing nodes when there
// is an option to just decrease the target. Implementation required.
func (asg *AutoScalingGroup) DecreaseTargetSize(delta int) error {
	// TODO(RainbowMango): Just remove nodes from group not delete them?
	return cloudprovider.ErrNotImplemented
}

// Id returns an unique identifier of the node group.
func (asg *AutoScalingGroup) Id() string {
	return asg.groupID
}

// Debug returns a string containing all information regarding this node group.
func (asg *AutoScalingGroup) Debug() string {
	return asg.String()
}

// Nodes returns a list of all nodes that belong to this node group.
// It is required that Instance objects returned by this method have Id field set.
// Other fields are optional.
// This list should include also instances that might have not become a kubernetes node yet.
func (asg *AutoScalingGroup) Nodes() ([]cloudprovider.Instance, error) {
	instances, err := asg.cloudServiceManager.GetInstances(asg.groupID)
	if err != nil {
		klog.Warningf("failed to get nodes from group: %s, error: %v", asg.groupID, err)
		return nil, err
	}

	return instances, nil
}

// TemplateNodeInfo returns a schedulerframework.NodeInfo structure of an empty
// (as if just started) node. This will be used in scale-up simulations to
// predict what would a new node look like if a node group was expanded. The returned
// NodeInfo is expected to have a fully populated Node object, with all of the labels,
// capacity and allocatable information as well as all pods that are started on
// the node by default, using manifest (most likely only kube-proxy). Implementation optional.
func (asg *AutoScalingGroup) TemplateNodeInfo() (*schedulerframework.NodeInfo, error) {
	template, err := asg.cloudServiceManager.getAsgTemplate(asg.groupID)
	if err != nil {
		return nil, err
	}
	node, err := asg.cloudServiceManager.buildNodeFromTemplate(asg.groupName, template)
	if err != nil {
		return nil, err
	}
	nodeInfo := schedulerframework.NewNodeInfo(cloudprovider.BuildKubeProxy(asg.groupName))
	nodeInfo.SetNode(node)
	return nodeInfo, nil
}

// Exist checks if the node group really exists on the cloud provider side. Allows to tell the
// theoretical node group from the real one. Implementation required.
func (asg *AutoScalingGroup) Exist() bool {
	// Since all group synced from remote and we do not support auto provision,
	// so we can assume that the group always exist.
	return true
}

// Create creates the node group on the cloud provider side. Implementation optional.
func (asg *AutoScalingGroup) Create() (cloudprovider.NodeGroup, error) {
	return nil, cloudprovider.ErrNotImplemented
}

// Delete deletes the node group on the cloud provider side.
// This will be executed only for autoprovisioned node groups, once their size drops to 0.
// Implementation optional.
func (asg *AutoScalingGroup) Delete() error {
	return cloudprovider.ErrNotImplemented
}

// Autoprovisioned returns true if the node group is autoprovisioned. An autoprovisioned group
// was created by CA and can be deleted when scaled to 0.
//
// Always return false because the node group should maintained by user.
func (asg *AutoScalingGroup) Autoprovisioned() bool {
	return false
}

// String dumps current groups meta data.
func (asg *AutoScalingGroup) String() string {
	return fmt.Sprintf("group: %s min=%d max=%d", asg.groupID, asg.minInstanceNumber, asg.maxInstanceNumber)
}
<commit_msg>makes sure all nodes belong to the node group before removement.<commit_after>/*
Copyright 2020 The Kubernetes 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 huaweicloud

import (
	"fmt"

	apiv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
	huaweicloudsdkasmodel "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3/services/as/v1/model"
	"k8s.io/klog/v2"
	schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
)

// AutoScalingGroup represents a HuaweiCloud's 'Auto Scaling Group' which also can be treated as a node group.
type AutoScalingGroup struct {
	cloudServiceManager CloudServiceManager
	groupName           string
	groupID             string
	minInstanceNumber   int
	maxInstanceNumber   int
}

// Check if our AutoScalingGroup implements necessary interface.
var _ cloudprovider.NodeGroup = &AutoScalingGroup{}

func newAutoScalingGroup(csm CloudServiceManager, sg huaweicloudsdkasmodel.ScalingGroups) AutoScalingGroup {
	return AutoScalingGroup{
		cloudServiceManager: csm,
		groupName:           *sg.ScalingGroupName,
		groupID:             *sg.ScalingGroupId,
		minInstanceNumber:   int(*sg.MinInstanceNumber),
		maxInstanceNumber:   int(*sg.MaxInstanceNumber),
	}
}

// MaxSize returns maximum size of the node group.
func (asg *AutoScalingGroup) MaxSize() int {
	return asg.maxInstanceNumber
}

// MinSize returns minimum size of the node group.
func (asg *AutoScalingGroup) MinSize() int {
	return asg.minInstanceNumber
}

// TargetSize returns the current target size of the node group. It is possible that the
// number of nodes in Kubernetes is different at the moment but should be equal
// to Size() once everything stabilizes (new nodes finish startup and registration or
// removed nodes are deleted completely). Implementation required.
//
// Target size is desire instance number of the auto scaling group, and not equal to current instance number if the
// auto scaling group is in increasing or decreasing process.
func (asg *AutoScalingGroup) TargetSize() (int, error) {
	desireNumber, err := asg.cloudServiceManager.GetDesireInstanceNumber(asg.groupID)
	if err != nil {
		klog.Warningf("failed to get group target size. groupID: %s, error: %v", asg.groupID, err)
		return 0, err
	}

	return desireNumber, nil
}

// IncreaseSize increases the size of the node group. To delete a node you need
// to explicitly name it and use DeleteNode. This function should wait until
// node group size is updated. Implementation required.
func (asg *AutoScalingGroup) IncreaseSize(delta int) error {
	err := asg.cloudServiceManager.IncreaseSizeInstance(asg.groupID, delta)
	if err != nil {
		klog.Warningf("failed to increase size for group: %s, error: %v", asg.groupID, err)
		return err
	}

	return nil
}

// DeleteNodes deletes nodes from this node group. Error is returned either on
// failure or if the given node doesn't belong to this node group. This function
// should wait until node group size is updated. Implementation required.
func (asg *AutoScalingGroup) DeleteNodes(nodes []*apiv1.Node) error {
	instances, err := asg.cloudServiceManager.GetInstances(asg.groupID)
	if err != nil {
		klog.Warningf("failed to get instances from group: %s, error: %v", asg.groupID, err)
		return err
	}

	instanceSet := sets.NewString()
	for _, instance := range instances {
		instanceSet.Insert(instance.Id)
	}

	instanceIds := make([]string, 0, len(instances))
	for _, node := range nodes {
		providerID := node.Spec.ProviderID

		// If one of the nodes not belongs to this auto scaling group, means there is something wrong happened,
		// so, we should reject the whole deleting request.
		if !instanceSet.Has(providerID) {
			klog.Errorf("delete node not belongs this node group is not allowed. group: %s, node: %s", asg.groupID, providerID)
			return fmt.Errorf("node does not belong to this node group")
		}

		klog.V(1).Infof("going to remove node from scaling group. group: %s, node: %s", asg.groupID, providerID)
		instanceIds = append(instanceIds, providerID)
	}

	err = asg.cloudServiceManager.DeleteScalingInstances(asg.groupID, instanceIds)
	if err != nil {
		klog.Warningf("failed to delete scaling instances. error: %v", err)
		return err
	}

	return nil
}

// DecreaseTargetSize decreases the target size of the node group. This function
// doesn't permit to delete any existing node and can be used only to reduce the
// request for new nodes that have not been yet fulfilled. Delta should be negative.
// It is assumed that cloud provider will not delete the existing nodes when there
// is an option to just decrease the target. Implementation required.
func (asg *AutoScalingGroup) DecreaseTargetSize(delta int) error {
	// TODO(RainbowMango): Just remove nodes from group not delete them?
	return cloudprovider.ErrNotImplemented
}

// Id returns an unique identifier of the node group.
func (asg *AutoScalingGroup) Id() string {
	return asg.groupID
}

// Debug returns a string containing all information regarding this node group.
func (asg *AutoScalingGroup) Debug() string {
	return asg.String()
}

// Nodes returns a list of all nodes that belong to this node group.
// It is required that Instance objects returned by this method have Id field set.
// Other fields are optional.
// This list should include also instances that might have not become a kubernetes node yet.
func (asg *AutoScalingGroup) Nodes() ([]cloudprovider.Instance, error) {
	instances, err := asg.cloudServiceManager.GetInstances(asg.groupID)
	if err != nil {
		klog.Warningf("failed to get nodes from group: %s, error: %v", asg.groupID, err)
		return nil, err
	}

	return instances, nil
}

// TemplateNodeInfo returns a schedulerframework.NodeInfo structure of an empty
// (as if just started) node. This will be used in scale-up simulations to
// predict what would a new node look like if a node group was expanded. The returned
// NodeInfo is expected to have a fully populated Node object, with all of the labels,
// capacity and allocatable information as well as all pods that are started on
// the node by default, using manifest (most likely only kube-proxy). Implementation optional.
func (asg *AutoScalingGroup) TemplateNodeInfo() (*schedulerframework.NodeInfo, error) {
	template, err := asg.cloudServiceManager.getAsgTemplate(asg.groupID)
	if err != nil {
		return nil, err
	}
	node, err := asg.cloudServiceManager.buildNodeFromTemplate(asg.groupName, template)
	if err != nil {
		return nil, err
	}
	nodeInfo := schedulerframework.NewNodeInfo(cloudprovider.BuildKubeProxy(asg.groupName))
	nodeInfo.SetNode(node)
	return nodeInfo, nil
}

// Exist checks if the node group really exists on the cloud provider side. Allows to tell the
// theoretical node group from the real one. Implementation required.
func (asg *AutoScalingGroup) Exist() bool {
	// Since all group synced from remote and we do not support auto provision,
	// so we can assume that the group always exist.
	return true
}

// Create creates the node group on the cloud provider side. Implementation optional.
func (asg *AutoScalingGroup) Create() (cloudprovider.NodeGroup, error) {
	return nil, cloudprovider.ErrNotImplemented
}

// Delete deletes the node group on the cloud provider side.
// This will be executed only for autoprovisioned node groups, once their size drops to 0.
// Implementation optional.
func (asg *AutoScalingGroup) Delete() error {
	return cloudprovider.ErrNotImplemented
}

// Autoprovisioned returns true if the node group is autoprovisioned. An autoprovisioned group
// was created by CA and can be deleted when scaled to 0.
//
// Always return false because the node group should maintained by user.
func (asg *AutoScalingGroup) Autoprovisioned() bool {
	return false
}

// String dumps current groups meta data.
func (asg *AutoScalingGroup) String() string {
	return fmt.Sprintf("group: %s min=%d max=%d", asg.groupID, asg.minInstanceNumber, asg.maxInstanceNumber)
}
<|endoftext|>


<commit_before>package delta_test

import (
	"testing"

	"github.com/GeoNet/delta/meta"
)

var testDeployedReceivers = map[string]func([]meta.DeployedReceiver) func(t *testing.T){
	"check for receiver installation overlaps": func(receivers []meta.DeployedReceiver) func(t *testing.T) {
		return func(t *testing.T) {

			installs := make(map[string]meta.DeployedReceiverList)
			for _, s := range receivers {
				if _, ok := installs[s.Model]; !ok {
					installs[s.Model] = meta.DeployedReceiverList{}
				}
				installs[s.Model] = append(installs[s.Model], s)
			}

			for _, v := range installs {
				for i := 0; i < len(v); i++ {
					for j := i + 1; j < len(v); j++ {
						if v[i].Serial != v[j].Serial {
							continue
						}
						if v[i].End.Before(v[j].Start) {
							continue
						}
						if v[i].Start.After(v[j].End) {
							continue
						}
						if v[i].End.Equal(v[j].Start) {
							continue
						}
						if v[i].Start.Equal(v[j].End) {
							continue
						}

						t.Errorf("receiver %s [%s] at %s has overlap with %s between times %s and %s",
							v[i].Model, v[i].Serial, v[i].Mark, v[j].Mark, v[i].Start.Format(meta.DateTimeFormat), v[i].End.Format(meta.DateTimeFormat))
					}
				}
			}
		}
	},

	"check for receiver installation equipment overlaps": func(receivers []meta.DeployedReceiver) func(t *testing.T) {
		return func(t *testing.T) {

			installs := make(map[string]meta.DeployedReceiverList)
			for _, s := range receivers {
				if _, ok := installs[s.Mark]; !ok {
					installs[s.Mark] = meta.DeployedReceiverList{}
				}
				installs[s.Mark] = append(installs[s.Mark], s)
			}

			for _, v := range installs {
				for i := 0; i < len(v); i++ {
					for j := i + 1; j < len(v); j++ {
						if v[i].End.Before(v[j].Start) {
							continue
						}
						if v[i].Start.After(v[j].End) {
							continue
						}
						if v[i].End.Equal(v[j].Start) {
							continue
						}
						if v[i].Start.Equal(v[j].End) {
							continue
						}

						t.Errorf("receivers %s [%s] / %s [%s] at %s has overlap between %s and %s",
							v[i].Model, v[i].Serial, v[j].Model, v[j].Serial, v[i].Mark, v[i].Start.Format(meta.DateTimeFormat), v[i].End.Format(meta.DateTimeFormat))
					}
				}
			}
		}
	},
}

var testDeployedReceiversMarks = map[string]func([]meta.DeployedReceiver, []meta.Mark) func(t *testing.T){
	"check for missing receiver marks": func(receivers []meta.DeployedReceiver, marks []meta.Mark) func(t *testing.T) {
		return func(t *testing.T) {

			keys := make(map[string]interface{})
			for _, m := range marks {
				keys[m.Code] = true
			}

			for _, r := range receivers {
				if _, ok := keys[r.Mark]; ok {
					continue
				}
				t.Errorf("unable to find receiver mark %-5s", r.Mark)
			}
		}
	},
}

var testDeployedReceiversAssets = map[string]func([]meta.DeployedReceiver, []meta.Asset) func(t *testing.T){
	"check for missing receiver assets": func(receivers []meta.DeployedReceiver, assets []meta.Asset) func(t *testing.T) {
		return func(t *testing.T) {
			for _, r := range receivers {
				var found bool
				for _, a := range assets {
					if a.Model != r.Model {
						continue
					}
					if a.Serial != r.Serial {
						continue
					}
					found = true
				}
				if found {
					continue
				}
				t.Errorf("unable to find receiver asset: %s [%s]", r.Model, r.Serial)
			}
		}
	},
}

func TestDeployedReceivers(t *testing.T) {
	var receivers meta.DeployedReceiverList
	loadListFile(t, "../install/receivers.csv", &receivers)

	for k, fn := range testDeployedReceivers {
		t.Run(k, fn(receivers))
	}
}

func TestDeployedReceiversMarks(t *testing.T) {
	var receivers meta.DeployedReceiverList
	loadListFile(t, "../install/receivers.csv", &receivers)

	var marks meta.MarkList
	loadListFile(t, "../network/marks.csv", &marks)

	for k, fn := range testDeployedReceiversMarks {
		t.Run(k, fn(receivers, marks))
	}
}

func TestDeployedReceiversAssets(t *testing.T) {
	var receivers meta.DeployedReceiverList
	loadListFile(t, "../install/receivers.csv", &receivers)

	var assets meta.AssetList
	loadListFile(t, "../assets/receivers.csv", &assets)

	for k, fn := range testDeployedReceiversAssets {
		t.Run(k, fn(receivers, assets))
	}
}
<commit_msg>update test on GNSS receiver timestamp<commit_after>package delta_test

import (
	"testing"

	"github.com/GeoNet/delta/meta"
)

var testDeployedReceivers = map[string]func([]meta.DeployedReceiver) func(t *testing.T){
	"check for receiver installation overlaps": func(receivers []meta.DeployedReceiver) func(t *testing.T) {
		return func(t *testing.T) {

			installs := make(map[string]meta.DeployedReceiverList)
			for _, s := range receivers {
				if _, ok := installs[s.Model]; !ok {
					installs[s.Model] = meta.DeployedReceiverList{}
				}
				installs[s.Model] = append(installs[s.Model], s)
			}

			for _, v := range installs {
				for i := 0; i < len(v); i++ {
					for j := i + 1; j < len(v); j++ {
						if v[i].Serial != v[j].Serial {
							continue
						}
						if v[i].End.Before(v[j].Start) {
							continue
						}
						if v[i].Start.After(v[j].End) {
							continue
						}

						t.Errorf("receiver %s [%s] at %s has overlap with %s between times %s and %s",
							v[i].Model, v[i].Serial, v[i].Mark, v[j].Mark, v[i].Start.Format(meta.DateTimeFormat), v[i].End.Format(meta.DateTimeFormat))
					}
				}
			}
		}
	},

	"check for receiver installation equipment overlaps": func(receivers []meta.DeployedReceiver) func(t *testing.T) {
		return func(t *testing.T) {

			installs := make(map[string]meta.DeployedReceiverList)
			for _, s := range receivers {
				if _, ok := installs[s.Mark]; !ok {
					installs[s.Mark] = meta.DeployedReceiverList{}
				}
				installs[s.Mark] = append(installs[s.Mark], s)
			}

			for _, v := range installs {
				for i := 0; i < len(v); i++ {
					for j := i + 1; j < len(v); j++ {
						if v[i].End.Before(v[j].Start) {
							continue
						}
						if v[i].Start.After(v[j].End) {
							continue
						}

						t.Errorf("receivers %s [%s] / %s [%s] at %s has overlap between %s and %s",
							v[i].Model, v[i].Serial, v[j].Model, v[j].Serial, v[i].Mark, v[i].Start.Format(meta.DateTimeFormat), v[i].End.Format(meta.DateTimeFormat))
					}
				}
			}
		}
	},
}

var testDeployedReceiversMarks = map[string]func([]meta.DeployedReceiver, []meta.Mark) func(t *testing.T){
	"check for missing receiver marks": func(receivers []meta.DeployedReceiver, marks []meta.Mark) func(t *testing.T) {
		return func(t *testing.T) {

			keys := make(map[string]interface{})
			for _, m := range marks {
				keys[m.Code] = true
			}

			for _, r := range receivers {
				if _, ok := keys[r.Mark]; ok {
					continue
				}
				t.Errorf("unable to find receiver mark %-5s", r.Mark)
			}
		}
	},
}

var testDeployedReceiversAssets = map[string]func([]meta.DeployedReceiver, []meta.Asset) func(t *testing.T){
	"check for missing receiver assets": func(receivers []meta.DeployedReceiver, assets []meta.Asset) func(t *testing.T) {
		return func(t *testing.T) {
			for _, r := range receivers {
				var found bool
				for _, a := range assets {
					if a.Model != r.Model {
						continue
					}
					if a.Serial != r.Serial {
						continue
					}
					found = true
				}
				if found {
					continue
				}
				t.Errorf("unable to find receiver asset: %s [%s]", r.Model, r.Serial)
			}
		}
	},
}

func TestDeployedReceivers(t *testing.T) {
	var receivers meta.DeployedReceiverList
	loadListFile(t, "../install/receivers.csv", &receivers)

	for k, fn := range testDeployedReceivers {
		t.Run(k, fn(receivers))
	}
}

func TestDeployedReceiversMarks(t *testing.T) {
	var receivers meta.DeployedReceiverList
	loadListFile(t, "../install/receivers.csv", &receivers)

	var marks meta.MarkList
	loadListFile(t, "../network/marks.csv", &marks)

	for k, fn := range testDeployedReceiversMarks {
		t.Run(k, fn(receivers, marks))
	}
}

func TestDeployedReceiversAssets(t *testing.T) {
	var receivers meta.DeployedReceiverList
	loadListFile(t, "../install/receivers.csv", &receivers)

	var assets meta.AssetList
	loadListFile(t, "../assets/receivers.csv", &assets)

	for k, fn := range testDeployedReceiversAssets {
		t.Run(k, fn(receivers, assets))
	}
}
<|endoftext|>


<commit_before>// The depgraph package is used to create and model a dependency graph
// of nouns. Each noun can represent a service, server, application,
// network switch, etc. Nouns can depend on other nouns, and provide
// versioning constraints. Nouns can also have various meta data that
// may be relevant to their construction or configuration.
package depgraph

import (
	"bytes"
	"fmt"
	"sort"
	"strings"
	"sync"

	"github.com/hashicorp/terraform/digraph"
)

// WalkFunc is the type used for the callback for Walk.
type WalkFunc func(*Noun) error

// Graph is used to represent a dependency graph.
type Graph struct {
	Name  string
	Meta  interface{}
	Nouns []*Noun
	Root  *Noun
}

// ValidateError implements the Error interface but provides
// additional information on a validation error.
type ValidateError struct {
	// If set, then the graph is missing a single root, on which
	// there are no depdendencies
	MissingRoot bool

	// Unreachable are nodes that could not be reached from
	// the root noun.
	Unreachable []*Noun

	// Cycles are groups of strongly connected nodes, which
	// form a cycle. This is disallowed.
	Cycles [][]*Noun
}

func (v *ValidateError) Error() string {
	var msgs []string

	if v.MissingRoot {
		msgs = append(msgs, "The graph has no single root")
	}

	for _, n := range v.Unreachable {
		msgs = append(msgs, fmt.Sprintf(
			"Unreachable node: %s", n.Name))
	}

	for _, c := range v.Cycles {
		cycleNodes := make([]string, len(c))
		for i, n := range c {
			cycleNodes[i] = n.Name
		}

		msgs = append(msgs, fmt.Sprintf(
			"Cycle: %s", strings.Join(cycleNodes, " -> ")))
	}

	for i, m := range msgs {
		msgs[i] = fmt.Sprintf("* %s", m)
	}

	return fmt.Sprintf(
		"The dependency graph is not valid:\n\n%s",
		strings.Join(msgs, "\n"))
}

// ConstraintError is used to return detailed violation
// information from CheckConstraints
type ConstraintError struct {
	Violations []*Violation
}

func (c *ConstraintError) Error() string {
	return fmt.Sprintf("%d constraint violations", len(c.Violations))
}

// Violation is used to pass along information about
// a constraint violation
type Violation struct {
	Source     *Noun
	Target     *Noun
	Dependency *Dependency
	Constraint Constraint
	Err        error
}

func (v *Violation) Error() string {
	return fmt.Sprintf("Constraint %v between %v and %v violated: %v",
		v.Constraint, v.Source, v.Target, v.Err)
}

// CheckConstraints walks the graph and ensures that all
// user imposed constraints are satisfied.
func (g *Graph) CheckConstraints() error {
	// Ensure we have a root
	if g.Root == nil {
		return fmt.Errorf("Graph must be validated before checking constraint violations")
	}

	// Create a constraint error
	cErr := &ConstraintError{}

	// Walk from the root
	digraph.DepthFirstWalk(g.Root, func(n digraph.Node) bool {
		noun := n.(*Noun)
		for _, dep := range noun.Deps {
			target := dep.Target
			for _, constraint := range dep.Constraints {
				ok, err := constraint.Satisfied(noun, target)
				if ok {
					continue
				}
				violation := &Violation{
					Source:     noun,
					Target:     target,
					Dependency: dep,
					Constraint: constraint,
					Err:        err,
				}
				cErr.Violations = append(cErr.Violations, violation)
			}
		}
		return true
	})

	if cErr.Violations != nil {
		return cErr
	}
	return nil
}

// Noun returns the noun with the given name, or nil if it cannot be found.
func (g *Graph) Noun(name string) *Noun {
	for _, n := range g.Nouns {
		if n.Name == name {
			return n
		}
	}

	return nil
}

// String generates a little ASCII string of the graph, useful in
// debugging output.
func (g *Graph) String() string {
	var buf bytes.Buffer

	// Alphabetize the output based on the noun name
	keys := make([]string, 0, len(g.Nouns))
	mapping := make(map[string]*Noun)
	for _, n := range g.Nouns {
		mapping[n.Name] = n
		keys = append(keys, n.Name)
	}
	sort.Strings(keys)

	buf.WriteString(fmt.Sprintf("root: %s\n", g.Root.Name))
	for _, k := range keys {
		n := mapping[k]
		buf.WriteString(fmt.Sprintf("%s\n", n.Name))

		// Alphabetize the dependency names
		depKeys := make([]string, 0, len(n.Deps))
		depMapping := make(map[string]*Dependency)
		for _, d := range n.Deps {
			depMapping[d.Target.Name] = d
			depKeys = append(depKeys, d.Target.Name)
		}
		sort.Strings(depKeys)

		for _, k := range depKeys {
			dep := depMapping[k]
			buf.WriteString(fmt.Sprintf(
				"  %s -> %s\n",
				dep.Source,
				dep.Target))
		}
	}

	return buf.String()
}

// Validate is used to ensure that a few properties of the graph are not violated:
// 1) There must be a single "root", or source on which nothing depends.
// 2) All nouns in the graph must be reachable from the root
// 3) The graph must be cycle free, meaning there are no cicular dependencies
func (g *Graph) Validate() error {
	// Convert to node list
	nodes := make([]digraph.Node, len(g.Nouns))
	for i, n := range g.Nouns {
		nodes[i] = n
	}

	// Create a validate erro
	vErr := &ValidateError{}

	// Search for all the sources, if we have only 1, it must be the root
	if sources := digraph.Sources(nodes); len(sources) != 1 {
		vErr.MissingRoot = true
		goto CHECK_CYCLES
	} else {
		g.Root = sources[0].(*Noun)
	}

	// Check reachability
	if unreached := digraph.Unreachable(g.Root, nodes); len(unreached) > 0 {
		vErr.Unreachable = make([]*Noun, len(unreached))
		for i, u := range unreached {
			vErr.Unreachable[i] = u.(*Noun)
		}
	}

CHECK_CYCLES:
	// Check for cycles
	if cycles := digraph.StronglyConnectedComponents(nodes, true); len(cycles) > 0 {
		vErr.Cycles = make([][]*Noun, len(cycles))
		for i, cycle := range cycles {
			group := make([]*Noun, len(cycle))
			for j, n := range cycle {
				group[j] = n.(*Noun)
			}
			vErr.Cycles[i] = group
		}
	}

	// Return the detailed error
	if vErr.MissingRoot || vErr.Unreachable != nil || vErr.Cycles != nil {
		return vErr
	}
	return nil
}

// Walk will walk the tree depth-first (dependency first) and call
// the callback.
//
// The callbacks will be called in parallel, so if you need non-parallelism,
// then introduce a lock in your callback.
func (g *Graph) Walk(fn WalkFunc) error {
	// Set so we don't callback for a single noun multiple times
	var seenMapL sync.RWMutex
	seenMap := make(map[*Noun]chan struct{})
	seenMap[g.Root] = make(chan struct{})

	// Build the list of things to visit
	tovisit := make([]*Noun, 1, len(g.Nouns))
	tovisit[0] = g.Root

	// Spawn off all our goroutines to walk the tree
	errCh := make(chan error)
	quitCh := make(chan struct{})
	for len(tovisit) > 0 {
		// Grab the current thing to use
		n := len(tovisit)
		current := tovisit[n-1]
		tovisit = tovisit[:n-1]

		// Go through each dependency and run that first
		for _, dep := range current.Deps {
			if _, ok := seenMap[dep.Target]; !ok {
				seenMapL.Lock()
				seenMap[dep.Target] = make(chan struct{})
				seenMapL.Unlock()
				tovisit = append(tovisit, dep.Target)
			}
		}

		// Spawn off a goroutine to execute our callback once
		// all our dependencies are satisified.
		go func(current *Noun) {
			seenMapL.RLock()
			closeCh := seenMap[current]
			seenMapL.RUnlock()

			defer close(closeCh)

			// Wait for all our dependencies
			for _, dep := range current.Deps {
				seenMapL.RLock()
				ch := seenMap[dep.Target]
				seenMapL.RUnlock()

				select {
				case <-ch:
				case <-quitCh:
					return
				}
			}

			// Call our callback!
			if err := fn(current); err != nil {
				errCh <- err
			}
		}(current)
	}

	// Aggregate channel that is closed when all goroutines finish
	doneCh := make(chan struct{})
	go func() {
		defer close(doneCh)

		for _, ch := range seenMap {
			<-ch
		}
	}()

	// Wait for finish OR an error
	select {
	case <-doneCh:
		return nil
	case err := <-errCh:
		// Close the quit channel so all our goroutines will end now
		close(quitCh)

		// Drain the error channel
		go func() {
			for _ = range errCh {
				// Nothing
			}
		}()

		// Wait for the goroutines to end
		<-doneCh
		close(errCh)

		return err
	}
}
<commit_msg>depgraph: detect loops to yourself<commit_after>// The depgraph package is used to create and model a dependency graph
// of nouns. Each noun can represent a service, server, application,
// network switch, etc. Nouns can depend on other nouns, and provide
// versioning constraints. Nouns can also have various meta data that
// may be relevant to their construction or configuration.
package depgraph

import (
	"bytes"
	"fmt"
	"sort"
	"strings"
	"sync"

	"github.com/hashicorp/terraform/digraph"
)

// WalkFunc is the type used for the callback for Walk.
type WalkFunc func(*Noun) error

// Graph is used to represent a dependency graph.
type Graph struct {
	Name  string
	Meta  interface{}
	Nouns []*Noun
	Root  *Noun
}

// ValidateError implements the Error interface but provides
// additional information on a validation error.
type ValidateError struct {
	// If set, then the graph is missing a single root, on which
	// there are no depdendencies
	MissingRoot bool

	// Unreachable are nodes that could not be reached from
	// the root noun.
	Unreachable []*Noun

	// Cycles are groups of strongly connected nodes, which
	// form a cycle. This is disallowed.
	Cycles [][]*Noun
}

func (v *ValidateError) Error() string {
	var msgs []string

	if v.MissingRoot {
		msgs = append(msgs, "The graph has no single root")
	}

	for _, n := range v.Unreachable {
		msgs = append(msgs, fmt.Sprintf(
			"Unreachable node: %s", n.Name))
	}

	for _, c := range v.Cycles {
		cycleNodes := make([]string, len(c))
		for i, n := range c {
			cycleNodes[i] = n.Name
		}

		msgs = append(msgs, fmt.Sprintf(
			"Cycle: %s", strings.Join(cycleNodes, " -> ")))
	}

	for i, m := range msgs {
		msgs[i] = fmt.Sprintf("* %s", m)
	}

	return fmt.Sprintf(
		"The dependency graph is not valid:\n\n%s",
		strings.Join(msgs, "\n"))
}

// ConstraintError is used to return detailed violation
// information from CheckConstraints
type ConstraintError struct {
	Violations []*Violation
}

func (c *ConstraintError) Error() string {
	return fmt.Sprintf("%d constraint violations", len(c.Violations))
}

// Violation is used to pass along information about
// a constraint violation
type Violation struct {
	Source     *Noun
	Target     *Noun
	Dependency *Dependency
	Constraint Constraint
	Err        error
}

func (v *Violation) Error() string {
	return fmt.Sprintf("Constraint %v between %v and %v violated: %v",
		v.Constraint, v.Source, v.Target, v.Err)
}

// CheckConstraints walks the graph and ensures that all
// user imposed constraints are satisfied.
func (g *Graph) CheckConstraints() error {
	// Ensure we have a root
	if g.Root == nil {
		return fmt.Errorf("Graph must be validated before checking constraint violations")
	}

	// Create a constraint error
	cErr := &ConstraintError{}

	// Walk from the root
	digraph.DepthFirstWalk(g.Root, func(n digraph.Node) bool {
		noun := n.(*Noun)
		for _, dep := range noun.Deps {
			target := dep.Target
			for _, constraint := range dep.Constraints {
				ok, err := constraint.Satisfied(noun, target)
				if ok {
					continue
				}
				violation := &Violation{
					Source:     noun,
					Target:     target,
					Dependency: dep,
					Constraint: constraint,
					Err:        err,
				}
				cErr.Violations = append(cErr.Violations, violation)
			}
		}
		return true
	})

	if cErr.Violations != nil {
		return cErr
	}
	return nil
}

// Noun returns the noun with the given name, or nil if it cannot be found.
func (g *Graph) Noun(name string) *Noun {
	for _, n := range g.Nouns {
		if n.Name == name {
			return n
		}
	}

	return nil
}

// String generates a little ASCII string of the graph, useful in
// debugging output.
func (g *Graph) String() string {
	var buf bytes.Buffer

	// Alphabetize the output based on the noun name
	keys := make([]string, 0, len(g.Nouns))
	mapping := make(map[string]*Noun)
	for _, n := range g.Nouns {
		mapping[n.Name] = n
		keys = append(keys, n.Name)
	}
	sort.Strings(keys)

	buf.WriteString(fmt.Sprintf("root: %s\n", g.Root.Name))
	for _, k := range keys {
		n := mapping[k]
		buf.WriteString(fmt.Sprintf("%s\n", n.Name))

		// Alphabetize the dependency names
		depKeys := make([]string, 0, len(n.Deps))
		depMapping := make(map[string]*Dependency)
		for _, d := range n.Deps {
			depMapping[d.Target.Name] = d
			depKeys = append(depKeys, d.Target.Name)
		}
		sort.Strings(depKeys)

		for _, k := range depKeys {
			dep := depMapping[k]
			buf.WriteString(fmt.Sprintf(
				"  %s -> %s\n",
				dep.Source,
				dep.Target))
		}
	}

	return buf.String()
}

// Validate is used to ensure that a few properties of the graph are not violated:
// 1) There must be a single "root", or source on which nothing depends.
// 2) All nouns in the graph must be reachable from the root
// 3) The graph must be cycle free, meaning there are no cicular dependencies
func (g *Graph) Validate() error {
	// Convert to node list
	nodes := make([]digraph.Node, len(g.Nouns))
	for i, n := range g.Nouns {
		nodes[i] = n
	}

	// Create a validate erro
	vErr := &ValidateError{}

	// Search for all the sources, if we have only 1, it must be the root
	if sources := digraph.Sources(nodes); len(sources) != 1 {
		vErr.MissingRoot = true
		goto CHECK_CYCLES
	} else {
		g.Root = sources[0].(*Noun)
	}

	// Check reachability
	if unreached := digraph.Unreachable(g.Root, nodes); len(unreached) > 0 {
		vErr.Unreachable = make([]*Noun, len(unreached))
		for i, u := range unreached {
			vErr.Unreachable[i] = u.(*Noun)
		}
	}

CHECK_CYCLES:
	// Check for cycles
	if cycles := digraph.StronglyConnectedComponents(nodes, true); len(cycles) > 0 {
		vErr.Cycles = make([][]*Noun, len(cycles))
		for i, cycle := range cycles {
			group := make([]*Noun, len(cycle))
			for j, n := range cycle {
				group[j] = n.(*Noun)
			}
			vErr.Cycles[i] = group
		}
	}

	// Check for loops to yourself
	for _, n := range g.Nouns {
		for _, d := range n.Deps {
			if d.Source == d.Target {
				vErr.Cycles = append(vErr.Cycles, []*Noun{n})
			}
		}
	}

	// Return the detailed error
	if vErr.MissingRoot || vErr.Unreachable != nil || vErr.Cycles != nil {
		return vErr
	}
	return nil
}

// Walk will walk the tree depth-first (dependency first) and call
// the callback.
//
// The callbacks will be called in parallel, so if you need non-parallelism,
// then introduce a lock in your callback.
func (g *Graph) Walk(fn WalkFunc) error {
	// Set so we don't callback for a single noun multiple times
	var seenMapL sync.RWMutex
	seenMap := make(map[*Noun]chan struct{})
	seenMap[g.Root] = make(chan struct{})

	// Build the list of things to visit
	tovisit := make([]*Noun, 1, len(g.Nouns))
	tovisit[0] = g.Root

	// Spawn off all our goroutines to walk the tree
	errCh := make(chan error)
	quitCh := make(chan struct{})
	for len(tovisit) > 0 {
		// Grab the current thing to use
		n := len(tovisit)
		current := tovisit[n-1]
		tovisit = tovisit[:n-1]

		// Go through each dependency and run that first
		for _, dep := range current.Deps {
			if _, ok := seenMap[dep.Target]; !ok {
				seenMapL.Lock()
				seenMap[dep.Target] = make(chan struct{})
				seenMapL.Unlock()
				tovisit = append(tovisit, dep.Target)
			}
		}

		// Spawn off a goroutine to execute our callback once
		// all our dependencies are satisified.
		go func(current *Noun) {
			seenMapL.RLock()
			closeCh := seenMap[current]
			seenMapL.RUnlock()

			defer close(closeCh)

			// Wait for all our dependencies
			for _, dep := range current.Deps {
				seenMapL.RLock()
				ch := seenMap[dep.Target]
				seenMapL.RUnlock()

				select {
				case <-ch:
				case <-quitCh:
					return
				}
			}

			// Call our callback!
			if err := fn(current); err != nil {
				errCh <- err
			}
		}(current)
	}

	// Aggregate channel that is closed when all goroutines finish
	doneCh := make(chan struct{})
	go func() {
		defer close(doneCh)

		for _, ch := range seenMap {
			<-ch
		}
	}()

	// Wait for finish OR an error
	select {
	case <-doneCh:
		return nil
	case err := <-errCh:
		// Close the quit channel so all our goroutines will end now
		close(quitCh)

		// Drain the error channel
		go func() {
			for _ = range errCh {
				// Nothing
			}
		}()

		// Wait for the goroutines to end
		<-doneCh
		close(errCh)

		return err
	}
}
<|endoftext|>