<commit_before>// Copyright (c) 1996 Barry Silverman, Brian Silverman, Vadim Gerasimov.
// Portions Copyright (c) 2009 The Go Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// See ../../nacl/README.

package main

import (
	"bytes"
	"exp/draw"
	"exp/nacl/av"
	"exp/nacl/srpc"
	"image"
	"log"
	"os"
	"runtime"
	"time"
	"./pdp1"
)

func main() {
	runtime.LockOSThread()
	if srpc.Enabled() {
		go srpc.ServeRuntime()
	}

	w, err := av.Init(av.SubsystemVideo, 512, 512)
	if err != nil {
		log.Exitf("av.Init: %s", err)
	}

	go quitter(w.QuitChan())

	var m SpacewarPDP1
	m.Init(w)
	m.PC = 4
	f := bytes.NewBuffer([]byte(spacewarCode))
	if err = m.Load(f); err != nil {
		log.Exitf("loading %s: %s", "spacewar.lst", err)
	}
	for err == nil {
		//fmt.Printf("step PC=%06o ", m.PC);
		//fmt.Printf("inst=%06o AC=%06o IO=%06o OV=%o\n",
		//	m.Mem[m.PC], m.AC, m.IO, m.OV);
		err = m.Step()
	}
	log.Exitf("step: %s", err)
}

func quitter(c <-chan bool) {
	<-c
	os.Exit(0)
}

// A SpacewarPDP1 is a PDP-1 machine configured to run Spacewar!
// It responds to traps by drawing on the display, and it flushes the
// display and pauses every second time the program counter reaches
// instruction 02051.
type SpacewarPDP1 struct {
	pdp1.M
	nframe     int
	frameTime  int64
	ctxt       draw.Context
	dx, dy     int
	screen     draw.Image
	ctl        pdp1.Word
	kc         <-chan int
	colorModel image.ColorModel
	cmap       []image.Color
	pix        [][]uint8
}

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

func (m *SpacewarPDP1) Init(ctxt draw.Context) {
	m.ctxt = ctxt
	m.kc = ctxt.KeyboardChan()
	m.screen = ctxt.Screen()
	m.dx = m.screen.Bounds().Dx()
	m.dy = m.screen.Bounds().Dy()
	m.colorModel = m.screen.ColorModel()
	m.pix = make([][]uint8, m.dy)
	for i := range m.pix {
		m.pix[i] = make([]uint8, m.dx)
	}
	m.cmap = make([]image.Color, 256)
	for i := range m.cmap {
		var r, g, b uint8
		r = uint8(min(0, 255))
		g = uint8(min(i*2, 255))
		b = uint8(min(0, 255))
		m.cmap[i] = m.colorModel.Convert(image.RGBAColor{r, g, b, 0xff})
	}
}

const (
	frameDelay = 56 * 1e6 // 56 ms
)

var ctlBits = [...]pdp1.Word{
	'f':  0000001,
	'd':  0000002,
	'a':  0000004,
	's':  0000010,
	'\'': 0040000,
	';':  0100000,
	'k':  0200000,
	'l':  0400000,
}

func (m *SpacewarPDP1) Step() os.Error {
	if m.PC == 02051 {
		m.pollInput()
		m.nframe++
		if m.nframe&1 == 0 {
			m.flush()
			t := time.Nanoseconds()
			if t >= m.frameTime+3*frameDelay {
				m.frameTime = t
			} else {
				m.frameTime += frameDelay
				for t < m.frameTime {
					time.Sleep(m.frameTime - t)
					t = time.Nanoseconds()
				}
			}
		}
	}
	return m.M.Step(m)
}

func (m *SpacewarPDP1) Trap(y pdp1.Word) {
	switch y & 077 {
	case 7:
		x := int(m.AC+0400000) & 0777777
		y := int(m.IO+0400000) & 0777777
		x = x * m.dx / 0777777
		y = y * m.dy / 0777777
		if 0 <= x && x < m.dx && 0 <= y && y < m.dy {
			n := uint8(min(int(m.pix[y][x])+128, 255))
			m.pix[y][x] = n
		}
	case 011:
		m.IO = m.ctl
	}
}

func (m *SpacewarPDP1) flush() {
	// Update screen image; simulate phosphor decay.
	for y := 0; y < m.dy; y++ {
		for x := 0; x < m.dx; x++ {
			m.screen.Set(x, y, m.cmap[m.pix[y][x]])
			m.pix[y][x] >>= 1
		}
	}
	m.ctxt.FlushImage()
}

func (m *SpacewarPDP1) pollInput() {
	for {
		select {
		case ch := <-m.kc:
			if 0 <= ch && ch < len(ctlBits) {
				m.ctl |= ctlBits[ch]
			}
			if 0 <= -ch && -ch < len(ctlBits) {
				m.ctl &^= ctlBits[-ch]
			}
		default:
			return
		}
	}
}
<commit_msg>exp/spacewar: fix build.<commit_after>// Copyright (c) 1996 Barry Silverman, Brian Silverman, Vadim Gerasimov.
// Portions Copyright (c) 2009 The Go Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// See ../../nacl/README.

package main

import (
	"bytes"
	"exp/draw"
	"exp/nacl/av"
	"exp/nacl/srpc"
	"image"
	"log"
	"os"
	"runtime"
	"time"
	"./pdp1"
)

func main() {
	runtime.LockOSThread()
	if srpc.Enabled() {
		go srpc.ServeRuntime()
	}

	w, err := av.Init(av.SubsystemVideo, 512, 512)
	if err != nil {
		log.Exitf("av.Init: %s", err)
	}

	kc := make(chan int)
	go demuxEvents(w, kc)

	var m SpacewarPDP1
	m.Init(w, kc)
	m.PC = 4
	f := bytes.NewBuffer([]byte(spacewarCode))
	if err = m.Load(f); err != nil {
		log.Exitf("loading %s: %s", "spacewar.lst", err)
	}
	for err == nil {
		//fmt.Printf("step PC=%06o ", m.PC);
		//fmt.Printf("inst=%06o AC=%06o IO=%06o OV=%o\n",
		//	m.Mem[m.PC], m.AC, m.IO, m.OV);
		err = m.Step()
	}
	log.Exitf("step: %s", err)
}

func demuxEvents(w draw.Window, kc chan int) {
	for event := range w.EventChan() {
		switch e := event.(type) {
		case draw.KeyEvent:
			kc <- e.Key
		}
	}
	os.Exit(0)
}

// A SpacewarPDP1 is a PDP-1 machine configured to run Spacewar!
// It responds to traps by drawing on the display, and it flushes the
// display and pauses every second time the program counter reaches
// instruction 02051.
type SpacewarPDP1 struct {
	pdp1.M
	nframe     int
	frameTime  int64
	ctxt       draw.Window
	dx, dy     int
	screen     draw.Image
	ctl        pdp1.Word
	kc         <-chan int
	colorModel image.ColorModel
	cmap       []image.Color
	pix        [][]uint8
}

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

func (m *SpacewarPDP1) Init(ctxt draw.Window, kc chan int) {
	m.ctxt = ctxt
	m.kc = kc
	m.screen = ctxt.Screen()
	m.dx = m.screen.Bounds().Dx()
	m.dy = m.screen.Bounds().Dy()
	m.colorModel = m.screen.ColorModel()
	m.pix = make([][]uint8, m.dy)
	for i := range m.pix {
		m.pix[i] = make([]uint8, m.dx)
	}
	m.cmap = make([]image.Color, 256)
	for i := range m.cmap {
		var r, g, b uint8
		r = uint8(min(0, 255))
		g = uint8(min(i*2, 255))
		b = uint8(min(0, 255))
		m.cmap[i] = m.colorModel.Convert(image.RGBAColor{r, g, b, 0xff})
	}
}

const (
	frameDelay = 56 * 1e6 // 56 ms
)

var ctlBits = [...]pdp1.Word{
	'f':  0000001,
	'd':  0000002,
	'a':  0000004,
	's':  0000010,
	'\'': 0040000,
	';':  0100000,
	'k':  0200000,
	'l':  0400000,
}

func (m *SpacewarPDP1) Step() os.Error {
	if m.PC == 02051 {
		m.pollInput()
		m.nframe++
		if m.nframe&1 == 0 {
			m.flush()
			t := time.Nanoseconds()
			if t >= m.frameTime+3*frameDelay {
				m.frameTime = t
			} else {
				m.frameTime += frameDelay
				for t < m.frameTime {
					time.Sleep(m.frameTime - t)
					t = time.Nanoseconds()
				}
			}
		}
	}
	return m.M.Step(m)
}

func (m *SpacewarPDP1) Trap(y pdp1.Word) {
	switch y & 077 {
	case 7:
		x := int(m.AC+0400000) & 0777777
		y := int(m.IO+0400000) & 0777777
		x = x * m.dx / 0777777
		y = y * m.dy / 0777777
		if 0 <= x && x < m.dx && 0 <= y && y < m.dy {
			n := uint8(min(int(m.pix[y][x])+128, 255))
			m.pix[y][x] = n
		}
	case 011:
		m.IO = m.ctl
	}
}

func (m *SpacewarPDP1) flush() {
	// Update screen image; simulate phosphor decay.
	for y := 0; y < m.dy; y++ {
		for x := 0; x < m.dx; x++ {
			m.screen.Set(x, y, m.cmap[m.pix[y][x]])
			m.pix[y][x] >>= 1
		}
	}
	m.ctxt.FlushImage()
}

func (m *SpacewarPDP1) pollInput() {
	for {
		select {
		case ch := <-m.kc:
			if 0 <= ch && ch < len(ctlBits) {
				m.ctl |= ctlBits[ch]
			}
			if 0 <= -ch && -ch < len(ctlBits) {
				m.ctl &^= ctlBits[-ch]
			}
		default:
			return
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2009 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 ioutil_test

import (
	. "io/ioutil"
	"os"
	"testing"
)

func checkSize(t *testing.T, path string, size int64) {
	dir, err := os.Stat(path)
	if err != nil {
		t.Fatalf("Stat %q (looking for size %d): %s", path, size, err)
	}
	if dir.Size != size {
		t.Errorf("Stat %q: size %d want %d", path, dir.Size, size)
	}
}

func TestReadFile(t *testing.T) {
	filename := "rumpelstilzchen"
	contents, err := ReadFile(filename)
	if err == nil {
		t.Fatalf("ReadFile %s: error expected, none found", filename)
	}

	filename = "ioutil_test.go"
	contents, err = ReadFile(filename)
	if err != nil {
		t.Fatalf("ReadFile %s: %v", filename, err)
	}

	checkSize(t, filename, int64(len(contents)))
}

func TestWriteFile(t *testing.T) {
	filename := "_obj/rumpelstilzchen"
	data := "Programming today is a race between software engineers striving to " +
		"build bigger and better idiot-proof programs, and the Universe trying " +
		"to produce bigger and better idiots. So far, the Universe is winning."

	if err := WriteFile(filename, []byte(data), 0644); err != nil {
		t.Fatalf("WriteFile %s: %v", filename, err)
	}

	contents, err := ReadFile(filename)
	if err != nil {
		t.Fatalf("ReadFile %s: %v", filename, err)
	}

	if string(contents) != data {
		t.Fatalf("contents = %q\nexpected = %q", string(contents), data)
	}

	// cleanup
	os.Remove(filename) // ignore error
}

func TestReadDir(t *testing.T) {
	dirname := "rumpelstilzchen"
	_, err := ReadDir(dirname)
	if err == nil {
		t.Fatalf("ReadDir %s: error expected, none found", dirname)
	}

	dirname = "."
	list, err := ReadDir(dirname)
	if err != nil {
		t.Fatalf("ReadDir %s: %v", dirname, err)
	}

	foundTest := false
	foundObj := false
	for _, dir := range list {
		switch {
		case dir.IsRegular() && dir.Name == "ioutil_test.go":
			foundTest = true
		case dir.IsDirectory() && dir.Name == "_obj":
			foundObj = true
		}
	}
	if !foundTest {
		t.Fatalf("ReadDir %s: test file not found", dirname)
	}
	if !foundObj {
		t.Fatalf("ReadDir %s: _obj directory not found", dirname)
	}
}
<commit_msg>io/ioutil: use _test not _obj in test<commit_after>// Copyright 2009 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 ioutil_test

import (
	. "io/ioutil"
	"os"
	"testing"
)

func checkSize(t *testing.T, path string, size int64) {
	dir, err := os.Stat(path)
	if err != nil {
		t.Fatalf("Stat %q (looking for size %d): %s", path, size, err)
	}
	if dir.Size != size {
		t.Errorf("Stat %q: size %d want %d", path, dir.Size, size)
	}
}

func TestReadFile(t *testing.T) {
	filename := "rumpelstilzchen"
	contents, err := ReadFile(filename)
	if err == nil {
		t.Fatalf("ReadFile %s: error expected, none found", filename)
	}

	filename = "ioutil_test.go"
	contents, err = ReadFile(filename)
	if err != nil {
		t.Fatalf("ReadFile %s: %v", filename, err)
	}

	checkSize(t, filename, int64(len(contents)))
}

func TestWriteFile(t *testing.T) {
	filename := "_test/rumpelstilzchen"
	data := "Programming today is a race between software engineers striving to " +
		"build bigger and better idiot-proof programs, and the Universe trying " +
		"to produce bigger and better idiots. So far, the Universe is winning."

	if err := WriteFile(filename, []byte(data), 0644); err != nil {
		t.Fatalf("WriteFile %s: %v", filename, err)
	}

	contents, err := ReadFile(filename)
	if err != nil {
		t.Fatalf("ReadFile %s: %v", filename, err)
	}

	if string(contents) != data {
		t.Fatalf("contents = %q\nexpected = %q", string(contents), data)
	}

	// cleanup
	os.Remove(filename) // ignore error
}

func TestReadDir(t *testing.T) {
	dirname := "rumpelstilzchen"
	_, err := ReadDir(dirname)
	if err == nil {
		t.Fatalf("ReadDir %s: error expected, none found", dirname)
	}

	dirname = "."
	list, err := ReadDir(dirname)
	if err != nil {
		t.Fatalf("ReadDir %s: %v", dirname, err)
	}

	foundTest := false
	foundTestDir := false
	for _, dir := range list {
		switch {
		case dir.IsRegular() && dir.Name == "ioutil_test.go":
			foundTest = true
		case dir.IsDirectory() && dir.Name == "_test":
			foundTestDir = true
		}
	}
	if !foundTest {
		t.Fatalf("ReadDir %s: test file not found", dirname)
	}
	if !foundTestDir {
		t.Fatalf("ReadDir %s: _test directory not found", dirname)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"runtime"
	"sync"

	"github.com/devsisters/goquic"
)

const (
	DEFINITIONS_FILE = "definitions.json"

	// error message templates
	STATUS_CODE_ERRMSG    = "StatusCode: Expected is %d , but actual is %d.\n"
)

type Definitions struct {
	Scenarios []Scenario
}

type Scenario struct {
	Url     string
	Expects map[string]interface{}
}

type TestResult struct {
	Successed    bool
	Url          string
	ErrorMessage string
}

type Stats struct {
	Successed int
	Failed    int
}

// Load json
func load() (Definitions, error) {
	file, err := ioutil.ReadFile(DEFINITIONS_FILE)
	if err != nil {
		return Definitions{}, err
	}

	var defs Definitions
	json.Unmarshal(file, &defs)

	return defs, nil
}

// Do QUIC request
func request(client *http.Client, scenario Scenario, result *TestResult, done *sync.WaitGroup) {
	resp, err := client.Get(scenario.Url)
	spec(scenario, resp, err, result)
	done.Done()
}

// Check QUIC response
func spec(scenario Scenario, resp *http.Response, err error, result *TestResult) {
	expects := scenario.Expects

	result.Successed = true
	result.Url = scenario.Url
	result.ErrorMessage = ""

	if err != nil {
		result.Successed = false
		result.ErrorMessage = err.Error()
		return
	}

	if expectedStatusCode, ok := expects["statuscode"].(int); ok {
		if resp.StatusCode != expectedStatusCode {
			result.Successed = false
			result.ErrorMessage = fmt.Sprintf(STATUS_CODE_ERRMSG, expectedStatusCode, resp.StatusCode)
			return
		}
	}
}

func printResults(results []TestResult) {
	stats := Stats{0, 0}

	for _, result := range results {
		if result.Successed {
			stats.Successed += 1
			fmt.Print(".")
		} else {
			stats.Failed += 1
			fmt.Printf("X%s is failed because %s\n", result.Url, result.ErrorMessage)
		}
	}

	total := stats.Successed + stats.Failed
	fmt.Printf("\n\nTotal requests: %d, successed: %d, failed: %d\n", total, stats.Successed, stats.Failed)
}

func main() {
	defs, err := load()
	if err != nil {
		fmt.Println("elastiquic can't load a JSON file.")
		os.Exit(1)
	}

	// Set concurrency
	procs := os.Getenv("GOMAXPROCS")
	if procs == "" {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	client := &http.Client{
		Transport: goquic.NewRoundTripper(false),
	}

	num := len(defs.Scenarios)
	var done sync.WaitGroup
	done.Add(num)

	// Do requests
	results := make([]TestResult, num)
	for i, scenario := range defs.Scenarios {
		go request(client, scenario, &results[i], &done)
	}
	done.Wait()

	printResults(results)
}
<commit_msg>Fix a type comparison<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"runtime"
	"sync"

	"github.com/devsisters/goquic"
)

const (
	DEFINITIONS_FILE = "definitions.json"

	// error message templates
	STATUS_CODE_ERRMSG = "StatusCode: Expected is %d , but actual is %d.\n"
)

type Definitions struct {
	Scenarios []Scenario
}

type Scenario struct {
	Url     string
	Expects map[string]interface{}
}

type TestResult struct {
	Successed    bool
	Url          string
	ErrorMessage string
}

type Stats struct {
	Successed int
	Failed    int
}

// Load json
func load() (Definitions, error) {
	file, err := ioutil.ReadFile(DEFINITIONS_FILE)
	if err != nil {
		return Definitions{}, err
	}

	var defs Definitions
	json.Unmarshal(file, &defs)

	return defs, nil
}

// Do QUIC request
func request(client *http.Client, scenario Scenario, result *TestResult, done *sync.WaitGroup) {
	resp, err := client.Get(scenario.Url)
	spec(scenario, resp, err, result)
	done.Done()
}

// Check QUIC response
func spec(scenario Scenario, resp *http.Response, err error, result *TestResult) {
	expects := scenario.Expects

	result.Successed = true
	result.Url = scenario.Url
	result.ErrorMessage = ""

	if err != nil {
		result.Successed = false
		result.ErrorMessage = err.Error()
		return
	}

	if val, ok := expects["statuscode"].(float64); ok {
		expectedStatusCode := int(val)
		if resp.StatusCode != expectedStatusCode {
			result.Successed = false
			result.ErrorMessage = fmt.Sprintf(STATUS_CODE_ERRMSG, expectedStatusCode, resp.StatusCode)
			return
		}
	}
}

func printResults(results []TestResult) {
	stats := Stats{0, 0}

	for _, result := range results {
		if result.Successed {
			stats.Successed += 1
			fmt.Print(".")
		} else {
			stats.Failed += 1
			fmt.Printf("X%s is failed because %s\n", result.Url, result.ErrorMessage)
		}
	}

	total := stats.Successed + stats.Failed
	fmt.Printf("\n\nTotal requests: %d, successed: %d, failed: %d\n", total, stats.Successed, stats.Failed)
}

func main() {
	defs, err := load()
	if err != nil {
		fmt.Println("elastiquic can't load a JSON file.")
		os.Exit(1)
	}

	// Set concurrency
	procs := os.Getenv("GOMAXPROCS")
	if procs == "" {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	client := &http.Client{
		Transport: goquic.NewRoundTripper(false),
	}

	num := len(defs.Scenarios)
	var done sync.WaitGroup
	done.Add(num)

	// Do requests
	results := make([]TestResult, num)
	for i, scenario := range defs.Scenarios {
		go request(client, scenario, &results[i], &done)
	}
	done.Wait()

	printResults(results)
}
<|endoftext|>


<commit_before>package api

import (
	"fmt"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"

	"github.com/julienschmidt/httprouter"
)

var (
	// TODO: Replace this function by accepting user input.
	recommendedHosts = func() uint64 {
		if build.Release == "dev" {
			return 2
		}
		if build.Release == "standard" {
			return 24
		}
		if build.Release == "testing" {
			return 1
		}
		panic("unrecognized release constant in api")
	}()
)

type (
	// RenterGET contains various renter metrics.
	RenterGET struct {
		Settings         modules.RenterSettings `json:"settings"`
		FinancialMetrics RenterFinancialMetrics `json:"financialmetrics"`
		CurrentPeriod    types.BlockHeight      `json:"currentperiod"`
	}

	// RenterFinancialMetrics contains metrics about how much the Renter has
	// spent on storage, uploads, and downloads.
	RenterFinancialMetrics struct {
		// Amount of money in the allowance spent on file contracts including
		// fees.
		ContractSpending types.Currency `json:"contractspending"`

		DownloadSpending types.Currency `json:"downloadspending"`
		StorageSpending  types.Currency `json:"storagespending"`
		UploadSpending   types.Currency `json:"uploadspending"`

		// Amount of money in the allowance that has not been spent.
		Unspent types.Currency `json:"unspent"`
	}

	// RenterContract represents a contract formed by the renter.
	RenterContract struct {
		EndHeight   types.BlockHeight    `json:"endheight"`
		ID          types.FileContractID `json:"id"`
		NetAddress  modules.NetAddress   `json:"netaddress"`
		RenterFunds types.Currency       `json:"renterfunds"`
		Size        uint64               `json:"size"`
	}

	// RenterContracts contains the renter's contracts.
	RenterContracts struct {
		Contracts []RenterContract `json:"contracts"`
	}

	// DownloadQueue contains the renter's download queue.
	RenterDownloadQueue struct {
		Downloads []modules.DownloadInfo `json:"downloads"`
	}

	// RenterFiles lists the files known to the renter.
	RenterFiles struct {
		Files []modules.FileInfo `json:"files"`
	}

	// RenterLoad lists files that were loaded into the renter.
	RenterLoad struct {
		FilesAdded []string `json:"filesadded"`
	}

	// RenterShareASCII contains an ASCII-encoded .sia file.
	RenterShareASCII struct {
		ASCIIsia string `json:"asciisia"`
	}

	// ActiveHosts lists active hosts on the network.
	ActiveHosts struct {
		Hosts []modules.HostDBEntry `json:"hosts"`
	}

	// AllHosts lists all hosts that the renter is aware of.
	AllHosts struct {
		Hosts []modules.HostDBEntry `json:"hosts"`
	}
)

// renterHandlerGET handles the API call to /renter.
func (api *API) renterHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	// calculate financial metrics from contracts
	periodStart := api.renter.CurrentPeriod()
	var fm RenterFinancialMetrics
	for _, c := range api.renter.Contracts() {
		if c.StartHeight < periodStart {
			continue
		}
		fm.ContractSpending = fm.ContractSpending.Add(c.TotalCost)
		fm.DownloadSpending = fm.DownloadSpending.Add(c.DownloadSpending)
		fm.UploadSpending = fm.UploadSpending.Add(c.UploadSpending)
		fm.StorageSpending = fm.StorageSpending.Add(c.StorageSpending)
		fm.Unspent = fm.Unspent.Add(c.RenterFunds())
	}
	settings := api.renter.Settings()
	fm.Unspent = fm.Unspent.Add(settings.Allowance.Funds)
	if fm.Unspent.Cmp(fm.ContractSpending) < 0 {
		// avoid negative currency error
		fm.Unspent = types.ZeroCurrency
	} else {
		fm.Unspent = fm.Unspent.Sub(fm.ContractSpending)
	}

	WriteJSON(w, RenterGET{
		Settings:         settings,
		FinancialMetrics: fm,
		CurrentPeriod:    periodStart,
	})
}

// renterHandlerPOST handles the API call to set the Renter's settings.
func (api *API) renterHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	// scan values
	funds, ok := scanAmount(req.FormValue("funds"))
	if !ok {
		WriteError(w, Error{"Couldn't parse funds"}, http.StatusBadRequest)
		return
	}
	// var hosts uint64
	// _, err := fmt.Sscan(req.FormValue("hosts"), &hosts)
	// if err != nil {
	// 	WriteError(w, Error{"Couldn't parse hosts: "+err.Error()}, http.StatusBadRequest)
	// 	return
	// }
	var period types.BlockHeight
	_, err := fmt.Sscan(req.FormValue("period"), &period)
	if err != nil {
		WriteError(w, Error{"Couldn't parse period: " + err.Error()}, http.StatusBadRequest)
		return
	}
	// var renewWindow types.BlockHeight
	// _, err = fmt.Sscan(req.FormValue("renewwindow"), &renewWindow)
	// if err != nil {
	// 	WriteError(w, Error{"Couldn't parse renewwindow: "+err.Error()}, http.StatusBadRequest)
	// 	return
	// }

	err = api.renter.SetSettings(modules.RenterSettings{
		Allowance: modules.Allowance{
			Funds:  funds,
			Period: period,

			// TODO: let user specify these
			Hosts:       recommendedHosts,
			RenewWindow: period / 2,
		},
	})
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}
	WriteSuccess(w)
}

// renterContractsHandler handles the API call to request the Renter's contracts.
func (api *API) renterContractsHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
	contracts := []RenterContract{}
	for _, c := range api.renter.Contracts() {
		contracts = append(contracts, RenterContract{
			EndHeight:   c.EndHeight(),
			ID:          c.ID,
			NetAddress:  c.NetAddress,
			RenterFunds: c.RenterFunds(),
			Size:        modules.SectorSize * uint64(len(c.MerkleRoots)),
		})
	}
	WriteJSON(w, RenterContracts{
		Contracts: contracts,
	})
}

// renterDownloadsHandler handles the API call to request the download queue.
func (api *API) renterDownloadsHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
	WriteJSON(w, RenterDownloadQueue{
		Downloads: api.renter.DownloadQueue(),
	})
}

// renterLoadHandler handles the API call to load a '.sia' file.
func (api *API) renterLoadHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	source := req.FormValue("source")
	if !filepath.IsAbs(source) {
		WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest)
		return
	}

	files, err := api.renter.LoadSharedFiles(source)
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}

	WriteJSON(w, RenterLoad{FilesAdded: files})
}

// renterLoadAsciiHandler handles the API call to load a '.sia' file
// in ASCII form.
func (api *API) renterLoadAsciiHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	files, err := api.renter.LoadSharedFilesAscii(req.FormValue("asciisia"))
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}

	WriteJSON(w, RenterLoad{FilesAdded: files})
}

// renterRenameHandler handles the API call to rename a file entry in the
// renter.
func (api *API) renterRenameHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := api.renter.RenameFile(strings.TrimPrefix(ps.ByName("siapath"), "/"), req.FormValue("newsiapath"))
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}

	WriteSuccess(w)
}

// renterFilesHandler handles the API call to list all of the files.
func (api *API) renterFilesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	WriteJSON(w, RenterFiles{
		Files: api.renter.FileList(),
	})
}

// renterDeleteHandler handles the API call to delete a file entry from the
// renter.
func (api *API) renterDeleteHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := api.renter.DeleteFile(strings.TrimPrefix(ps.ByName("siapath"), "/"))
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}

	WriteSuccess(w)
}

// renterDownloadHandler handles the API call to download a file.
func (api *API) renterDownloadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	destination := req.FormValue("destination")
	// Check that the destination path is absolute.
	if !filepath.IsAbs(destination) {
		WriteError(w, Error{"destination must be an absolute path"}, http.StatusBadRequest)
		return
	}

	err := api.renter.Download(strings.TrimPrefix(ps.ByName("siapath"), "/"), destination)
	if err != nil {
		WriteError(w, Error{"Download failed: " + err.Error()}, http.StatusInternalServerError)
		return
	}

	WriteSuccess(w)
}

// renterShareHandler handles the API call to create a '.sia' file that
// shares a set of file.
func (api *API) renterShareHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	destination := req.FormValue("destination")
	// Check that the destination path is absolute.
	if !filepath.IsAbs(destination) {
		WriteError(w, Error{"destination must be an absolute path"}, http.StatusBadRequest)
		return
	}

	err := api.renter.ShareFiles(strings.Split(req.FormValue("siapaths"), ","), destination)
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}

	WriteSuccess(w)
}

// renterShareAsciiHandler handles the API call to return a '.sia' file
// in ascii form.
func (api *API) renterShareAsciiHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	ascii, err := api.renter.ShareFilesAscii(strings.Split(req.FormValue("siapaths"), ","))
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}
	WriteJSON(w, RenterShareASCII{
		ASCIIsia: ascii,
	})
}

// renterUploadHandler handles the API call to upload a file.
func (api *API) renterUploadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	source := req.FormValue("source")
	if !filepath.IsAbs(source) {
		WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest)
		return
	}

	err := api.renter.Upload(modules.FileUploadParams{
		Source:  source,
		SiaPath: strings.TrimPrefix(ps.ByName("siapath"), "/"),
		// let the renter decide these values; eventually they will be configurable
		ErasureCode: nil,
	})
	if err != nil {
		WriteError(w, Error{"Upload failed: " + err.Error()}, http.StatusInternalServerError)
		return
	}

	WriteSuccess(w)
}

// renterHostsActiveHandler handles the API call asking for the list of active
// hosts.
func (api *API) renterHostsActiveHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	var numHosts uint64
	hosts := api.renter.ActiveHosts()

	if req.FormValue("numhosts") == "" {
		// Default value for 'numhosts' is all of them.
		numHosts = uint64(len(hosts))
	} else {
		// Parse the value for 'numhosts'.
		_, err := fmt.Sscan(req.FormValue("numhosts"), &numHosts)
		if err != nil {
			WriteError(w, Error{err.Error()}, http.StatusBadRequest)
			return
		}

		// Catch any boundary errors.
		if numHosts > uint64(len(hosts)) {
			numHosts = uint64(len(hosts))
		}
	}

	WriteJSON(w, ActiveHosts{
		Hosts: hosts[:numHosts],
	})
}

// renterHostsAllHandler handles the API call asking for the list of all hosts.
func (api *API) renterHostsAllHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	WriteJSON(w, AllHosts{
		Hosts: api.renter.AllHosts(),
	})
}
<commit_msg>calculate fm.Unspent more obviously<commit_after>package api

import (
	"fmt"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"

	"github.com/julienschmidt/httprouter"
)

var (
	// TODO: Replace this function by accepting user input.
	recommendedHosts = func() uint64 {
		if build.Release == "dev" {
			return 2
		}
		if build.Release == "standard" {
			return 24
		}
		if build.Release == "testing" {
			return 1
		}
		panic("unrecognized release constant in api")
	}()
)

type (
	// RenterGET contains various renter metrics.
	RenterGET struct {
		Settings         modules.RenterSettings `json:"settings"`
		FinancialMetrics RenterFinancialMetrics `json:"financialmetrics"`
		CurrentPeriod    types.BlockHeight      `json:"currentperiod"`
	}

	// RenterFinancialMetrics contains metrics about how much the Renter has
	// spent on storage, uploads, and downloads.
	RenterFinancialMetrics struct {
		// Amount of money in the allowance spent on file contracts including
		// fees.
		ContractSpending types.Currency `json:"contractspending"`

		DownloadSpending types.Currency `json:"downloadspending"`
		StorageSpending  types.Currency `json:"storagespending"`
		UploadSpending   types.Currency `json:"uploadspending"`

		// Amount of money in the allowance that has not been spent.
		Unspent types.Currency `json:"unspent"`
	}

	// RenterContract represents a contract formed by the renter.
	RenterContract struct {
		EndHeight   types.BlockHeight    `json:"endheight"`
		ID          types.FileContractID `json:"id"`
		NetAddress  modules.NetAddress   `json:"netaddress"`
		RenterFunds types.Currency       `json:"renterfunds"`
		Size        uint64               `json:"size"`
	}

	// RenterContracts contains the renter's contracts.
	RenterContracts struct {
		Contracts []RenterContract `json:"contracts"`
	}

	// DownloadQueue contains the renter's download queue.
	RenterDownloadQueue struct {
		Downloads []modules.DownloadInfo `json:"downloads"`
	}

	// RenterFiles lists the files known to the renter.
	RenterFiles struct {
		Files []modules.FileInfo `json:"files"`
	}

	// RenterLoad lists files that were loaded into the renter.
	RenterLoad struct {
		FilesAdded []string `json:"filesadded"`
	}

	// RenterShareASCII contains an ASCII-encoded .sia file.
	RenterShareASCII struct {
		ASCIIsia string `json:"asciisia"`
	}

	// ActiveHosts lists active hosts on the network.
	ActiveHosts struct {
		Hosts []modules.HostDBEntry `json:"hosts"`
	}

	// AllHosts lists all hosts that the renter is aware of.
	AllHosts struct {
		Hosts []modules.HostDBEntry `json:"hosts"`
	}
)

// renterHandlerGET handles the API call to /renter.
func (api *API) renterHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	settings := api.renter.Settings()
	periodStart := api.renter.CurrentPeriod()
	// calculate financial metrics from contracts
	var fm RenterFinancialMetrics
	fm.Unspent = settings.Allowance.Funds
	for _, c := range api.renter.Contracts() {
		if c.StartHeight < periodStart {
			continue
		}
		fm.ContractSpending = fm.ContractSpending.Add(c.TotalCost)
		fm.DownloadSpending = fm.DownloadSpending.Add(c.DownloadSpending)
		fm.UploadSpending = fm.UploadSpending.Add(c.UploadSpending)
		fm.StorageSpending = fm.StorageSpending.Add(c.StorageSpending)
		// total unspent is:
		//    allowance - (cost to form contracts) + (money left in contracts)
		if fm.Unspent.Add(c.RenterFunds()).Cmp(c.TotalCost) > 0 {
			fm.Unspent = fm.Unspent.Add(c.RenterFunds()).Sub(c.TotalCost)
		}
	}

	WriteJSON(w, RenterGET{
		Settings:         settings,
		FinancialMetrics: fm,
		CurrentPeriod:    periodStart,
	})
}

// renterHandlerPOST handles the API call to set the Renter's settings.
func (api *API) renterHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	// scan values
	funds, ok := scanAmount(req.FormValue("funds"))
	if !ok {
		WriteError(w, Error{"Couldn't parse funds"}, http.StatusBadRequest)
		return
	}
	// var hosts uint64
	// _, err := fmt.Sscan(req.FormValue("hosts"), &hosts)
	// if err != nil {
	// 	WriteError(w, Error{"Couldn't parse hosts: "+err.Error()}, http.StatusBadRequest)
	// 	return
	// }
	var period types.BlockHeight
	_, err := fmt.Sscan(req.FormValue("period"), &period)
	if err != nil {
		WriteError(w, Error{"Couldn't parse period: " + err.Error()}, http.StatusBadRequest)
		return
	}
	// var renewWindow types.BlockHeight
	// _, err = fmt.Sscan(req.FormValue("renewwindow"), &renewWindow)
	// if err != nil {
	// 	WriteError(w, Error{"Couldn't parse renewwindow: "+err.Error()}, http.StatusBadRequest)
	// 	return
	// }

	err = api.renter.SetSettings(modules.RenterSettings{
		Allowance: modules.Allowance{
			Funds:  funds,
			Period: period,

			// TODO: let user specify these
			Hosts:       recommendedHosts,
			RenewWindow: period / 2,
		},
	})
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}
	WriteSuccess(w)
}

// renterContractsHandler handles the API call to request the Renter's contracts.
func (api *API) renterContractsHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
	contracts := []RenterContract{}
	for _, c := range api.renter.Contracts() {
		contracts = append(contracts, RenterContract{
			EndHeight:   c.EndHeight(),
			ID:          c.ID,
			NetAddress:  c.NetAddress,
			RenterFunds: c.RenterFunds(),
			Size:        modules.SectorSize * uint64(len(c.MerkleRoots)),
		})
	}
	WriteJSON(w, RenterContracts{
		Contracts: contracts,
	})
}

// renterDownloadsHandler handles the API call to request the download queue.
func (api *API) renterDownloadsHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
	WriteJSON(w, RenterDownloadQueue{
		Downloads: api.renter.DownloadQueue(),
	})
}

// renterLoadHandler handles the API call to load a '.sia' file.
func (api *API) renterLoadHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	source := req.FormValue("source")
	if !filepath.IsAbs(source) {
		WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest)
		return
	}

	files, err := api.renter.LoadSharedFiles(source)
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}

	WriteJSON(w, RenterLoad{FilesAdded: files})
}

// renterLoadAsciiHandler handles the API call to load a '.sia' file
// in ASCII form.
func (api *API) renterLoadAsciiHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	files, err := api.renter.LoadSharedFilesAscii(req.FormValue("asciisia"))
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}

	WriteJSON(w, RenterLoad{FilesAdded: files})
}

// renterRenameHandler handles the API call to rename a file entry in the
// renter.
func (api *API) renterRenameHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := api.renter.RenameFile(strings.TrimPrefix(ps.ByName("siapath"), "/"), req.FormValue("newsiapath"))
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}

	WriteSuccess(w)
}

// renterFilesHandler handles the API call to list all of the files.
func (api *API) renterFilesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	WriteJSON(w, RenterFiles{
		Files: api.renter.FileList(),
	})
}

// renterDeleteHandler handles the API call to delete a file entry from the
// renter.
func (api *API) renterDeleteHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := api.renter.DeleteFile(strings.TrimPrefix(ps.ByName("siapath"), "/"))
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}

	WriteSuccess(w)
}

// renterDownloadHandler handles the API call to download a file.
func (api *API) renterDownloadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	destination := req.FormValue("destination")
	// Check that the destination path is absolute.
	if !filepath.IsAbs(destination) {
		WriteError(w, Error{"destination must be an absolute path"}, http.StatusBadRequest)
		return
	}

	err := api.renter.Download(strings.TrimPrefix(ps.ByName("siapath"), "/"), destination)
	if err != nil {
		WriteError(w, Error{"Download failed: " + err.Error()}, http.StatusInternalServerError)
		return
	}

	WriteSuccess(w)
}

// renterShareHandler handles the API call to create a '.sia' file that
// shares a set of file.
func (api *API) renterShareHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	destination := req.FormValue("destination")
	// Check that the destination path is absolute.
	if !filepath.IsAbs(destination) {
		WriteError(w, Error{"destination must be an absolute path"}, http.StatusBadRequest)
		return
	}

	err := api.renter.ShareFiles(strings.Split(req.FormValue("siapaths"), ","), destination)
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}

	WriteSuccess(w)
}

// renterShareAsciiHandler handles the API call to return a '.sia' file
// in ascii form.
func (api *API) renterShareAsciiHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	ascii, err := api.renter.ShareFilesAscii(strings.Split(req.FormValue("siapaths"), ","))
	if err != nil {
		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
		return
	}
	WriteJSON(w, RenterShareASCII{
		ASCIIsia: ascii,
	})
}

// renterUploadHandler handles the API call to upload a file.
func (api *API) renterUploadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	source := req.FormValue("source")
	if !filepath.IsAbs(source) {
		WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest)
		return
	}

	err := api.renter.Upload(modules.FileUploadParams{
		Source:  source,
		SiaPath: strings.TrimPrefix(ps.ByName("siapath"), "/"),
		// let the renter decide these values; eventually they will be configurable
		ErasureCode: nil,
	})
	if err != nil {
		WriteError(w, Error{"Upload failed: " + err.Error()}, http.StatusInternalServerError)
		return
	}

	WriteSuccess(w)
}

// renterHostsActiveHandler handles the API call asking for the list of active
// hosts.
func (api *API) renterHostsActiveHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	var numHosts uint64
	hosts := api.renter.ActiveHosts()

	if req.FormValue("numhosts") == "" {
		// Default value for 'numhosts' is all of them.
		numHosts = uint64(len(hosts))
	} else {
		// Parse the value for 'numhosts'.
		_, err := fmt.Sscan(req.FormValue("numhosts"), &numHosts)
		if err != nil {
			WriteError(w, Error{err.Error()}, http.StatusBadRequest)
			return
		}

		// Catch any boundary errors.
		if numHosts > uint64(len(hosts)) {
			numHosts = uint64(len(hosts))
		}
	}

	WriteJSON(w, ActiveHosts{
		Hosts: hosts[:numHosts],
	})
}

// renterHostsAllHandler handles the API call asking for the list of all hosts.
func (api *API) renterHostsAllHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	WriteJSON(w, AllHosts{
		Hosts: api.renter.AllHosts(),
	})
}
<|endoftext|>


<commit_before>// Copyright 2013 tsuru 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 api

import (
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/app"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	stdlog "log"
	"log/syslog"
	"net"
	"net/http"
	"os"
)

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	log.Fatal(err)
}

func RunServer(flags map[string]interface{}) {
	logger, err := syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		stdlog.Fatal(err)
	}
	log.SetLogger(logger)
	configFile, ok := flags["config"].(string)
	if !ok {
		configFile = "/etc/tsuru/tsuru.conf"
	}
	dry, ok := flags["dry"].(bool)
	if !ok {
		dry = false
	}
	err = config.ReadAndWatchConfigFile(configFile)
	if err != nil {
		fatal(err)
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		fatal(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		fatal(err)
	}
	fmt.Printf("Using the database %q from the server %q.\n\n", dbName, connString)

	m := pat.New()

	m.Get("/schema/app", authorizationRequiredHandler(appSchema))
	m.Get("/schema/service", authorizationRequiredHandler(serviceSchema))
	m.Get("/schema/services", authorizationRequiredHandler(servicesSchema))

	m.Get("/quota/:owner", authorizationRequiredHandler(quotaByOwner))

	m.Get("/services/instances", authorizationRequiredHandler(serviceInstances))
	m.Get("/services/instances/:name", authorizationRequiredHandler(serviceInstance))
	m.Del("/services/instances/:name", authorizationRequiredHandler(removeServiceInstance))
	m.Post("/services/instances", authorizationRequiredHandler(createServiceInstance))
	m.Put("/services/instances/:instance/:app", authorizationRequiredHandler(bindServiceInstance))
	m.Del("/services/instances/:instance/:app", authorizationRequiredHandler(unbindServiceInstance))
	m.Get("/services/instances/:instance/status", authorizationRequiredHandler(serviceInstanceStatus))

	m.Get("/services", authorizationRequiredHandler(serviceList))
	m.Post("/services", authorizationRequiredHandler(serviceCreate))
	m.Put("/services", authorizationRequiredHandler(serviceUpdate))
	m.Del("/services/:name", authorizationRequiredHandler(serviceDelete))
	m.Get("/services/:name", authorizationRequiredHandler(serviceInfo))
	m.Get("/services/:name/doc", authorizationRequiredHandler(serviceDoc))
	m.Put("/services/:name/doc", authorizationRequiredHandler(serviceAddDoc))
	m.Put("/services/:service/:team", authorizationRequiredHandler(grantServiceAccess))
	m.Del("/services/:service/:team", authorizationRequiredHandler(revokeServiceAccess))

	m.Del("/apps/:app", authorizationRequiredHandler(appDelete))
	m.Get("/apps/:app", authorizationRequiredHandler(appInfo))
	m.Post("/apps/:app", authorizationRequiredHandler(setCName))
	m.Post("/apps/:app/run", authorizationRequiredHandler(runCommand))
	m.Get("/apps/:app/restart", authorizationRequiredHandler(restart))
	m.Get("/apps/:app/env", authorizationRequiredHandler(getEnv))
	m.Post("/apps/:app/env", authorizationRequiredHandler(setEnv))
	m.Del("/apps/:app/env", authorizationRequiredHandler(unsetEnv))
	m.Get("/apps", authorizationRequiredHandler(appList))
	m.Post("/apps", authorizationRequiredHandler(createApp))
	m.Put("/apps/:app/units", authorizationRequiredHandler(addUnits))
	m.Del("/apps/:app/units", authorizationRequiredHandler(removeUnits))
	m.Put("/apps/:app/:team", authorizationRequiredHandler(grantAppAccess))
	m.Del("/apps/:app/:team", authorizationRequiredHandler(revokeAppAccess))
	m.Get("/apps/:app/log", authorizationRequiredHandler(appLog))
	m.Post("/apps/:app/log", authorizationRequiredHandler(addLog))

	m.Get("/platforms", authorizationRequiredHandler(platformList))

	// These handlers don't use :app on purpose. Using :app means that only
	// the token generate for the given app is valid, but these handlers
	// use a token generated for Gandalf.
	m.Get("/apps/:appname/available", authorizationRequiredHandler(appIsAvailable))
	m.Get("/apps/:appname/repository/clone", authorizationRequiredHandler(cloneRepository))

	if registrationEnabled, _ := config.GetBool("auth:user-registration"); registrationEnabled {
		m.Post("/users", handler(createUser))
	}

	m.Post("/users/:email/password", handler(resetPassword))
	m.Post("/users/:email/tokens", handler(login))
	m.Del("/users/tokens", authorizationRequiredHandler(logout))
	m.Put("/users/password", authorizationRequiredHandler(changePassword))
	m.Del("/users", authorizationRequiredHandler(removeUser))
	m.Post("/users/keys", authorizationRequiredHandler(addKeyToUser))
	m.Del("/users/keys", authorizationRequiredHandler(removeKeyFromUser))

	m.Post("/tokens", adminRequiredHandler(generateAppToken))

	m.Get("/teams", authorizationRequiredHandler(teamList))
	m.Post("/teams", authorizationRequiredHandler(createTeam))
	m.Get("/teams/:name", authorizationRequiredHandler(getTeam))
	m.Del("/teams/:name", authorizationRequiredHandler(removeTeam))
	m.Put("/teams/:team/:user", authorizationRequiredHandler(addUserToTeam))
	m.Del("/teams/:team/:user", authorizationRequiredHandler(removeUserFromTeam))

	m.Get("/healers", authorizationRequiredHandler(healers))
	m.Get("/healers/:healer", authorizationRequiredHandler(healer))

	if !dry {
		provisioner, err := config.GetString("provisioner")
		if err != nil {
			fmt.Printf("Warning: %q didn't declare a provisioner, using default provisioner.\n", configFile)
			provisioner = "juju"
		}
		app.Provisioner, err = provision.Get(provisioner)
		if err != nil {
			fatal(err)
		}
		fmt.Printf("Using %q provisioner.\n\n", provisioner)

		listen, err := config.GetString("listen")
		if err != nil {
			fatal(err)
		}
		tls, _ := config.GetBool("use-tls")
		if tls {
			certFile, err := config.GetString("tls-cert-file")
			if err != nil {
				fatal(err)
			}
			keyFile, err := config.GetString("tls-key-file")
			if err != nil {
				fatal(err)
			}
			fmt.Printf("tsuru HTTP/TLS server listening at %s...\n", listen)
			fatal(http.ListenAndServeTLS(listen, certFile, keyFile, m))
		} else {
			listener, err := net.Listen("tcp", listen)
			if err != nil {
				fatal(err)
			}
			fmt.Printf("tsuru HTTP server listening at %s...\n", listen)
			http.Handle("/", m)
			fatal(http.Serve(listener, nil))
		}
	}
}
<commit_msg>api: move tls settings to their own namespace<commit_after>// Copyright 2013 tsuru 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 api

import (
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/app"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	stdlog "log"
	"log/syslog"
	"net"
	"net/http"
	"os"
)

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	log.Fatal(err)
}

func RunServer(flags map[string]interface{}) {
	logger, err := syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		stdlog.Fatal(err)
	}
	log.SetLogger(logger)
	configFile, ok := flags["config"].(string)
	if !ok {
		configFile = "/etc/tsuru/tsuru.conf"
	}
	dry, ok := flags["dry"].(bool)
	if !ok {
		dry = false
	}
	err = config.ReadAndWatchConfigFile(configFile)
	if err != nil {
		fatal(err)
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		fatal(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		fatal(err)
	}
	fmt.Printf("Using the database %q from the server %q.\n\n", dbName, connString)

	m := pat.New()

	m.Get("/schema/app", authorizationRequiredHandler(appSchema))
	m.Get("/schema/service", authorizationRequiredHandler(serviceSchema))
	m.Get("/schema/services", authorizationRequiredHandler(servicesSchema))

	m.Get("/quota/:owner", authorizationRequiredHandler(quotaByOwner))

	m.Get("/services/instances", authorizationRequiredHandler(serviceInstances))
	m.Get("/services/instances/:name", authorizationRequiredHandler(serviceInstance))
	m.Del("/services/instances/:name", authorizationRequiredHandler(removeServiceInstance))
	m.Post("/services/instances", authorizationRequiredHandler(createServiceInstance))
	m.Put("/services/instances/:instance/:app", authorizationRequiredHandler(bindServiceInstance))
	m.Del("/services/instances/:instance/:app", authorizationRequiredHandler(unbindServiceInstance))
	m.Get("/services/instances/:instance/status", authorizationRequiredHandler(serviceInstanceStatus))

	m.Get("/services", authorizationRequiredHandler(serviceList))
	m.Post("/services", authorizationRequiredHandler(serviceCreate))
	m.Put("/services", authorizationRequiredHandler(serviceUpdate))
	m.Del("/services/:name", authorizationRequiredHandler(serviceDelete))
	m.Get("/services/:name", authorizationRequiredHandler(serviceInfo))
	m.Get("/services/:name/doc", authorizationRequiredHandler(serviceDoc))
	m.Put("/services/:name/doc", authorizationRequiredHandler(serviceAddDoc))
	m.Put("/services/:service/:team", authorizationRequiredHandler(grantServiceAccess))
	m.Del("/services/:service/:team", authorizationRequiredHandler(revokeServiceAccess))

	m.Del("/apps/:app", authorizationRequiredHandler(appDelete))
	m.Get("/apps/:app", authorizationRequiredHandler(appInfo))
	m.Post("/apps/:app", authorizationRequiredHandler(setCName))
	m.Post("/apps/:app/run", authorizationRequiredHandler(runCommand))
	m.Get("/apps/:app/restart", authorizationRequiredHandler(restart))
	m.Get("/apps/:app/env", authorizationRequiredHandler(getEnv))
	m.Post("/apps/:app/env", authorizationRequiredHandler(setEnv))
	m.Del("/apps/:app/env", authorizationRequiredHandler(unsetEnv))
	m.Get("/apps", authorizationRequiredHandler(appList))
	m.Post("/apps", authorizationRequiredHandler(createApp))
	m.Put("/apps/:app/units", authorizationRequiredHandler(addUnits))
	m.Del("/apps/:app/units", authorizationRequiredHandler(removeUnits))
	m.Put("/apps/:app/:team", authorizationRequiredHandler(grantAppAccess))
	m.Del("/apps/:app/:team", authorizationRequiredHandler(revokeAppAccess))
	m.Get("/apps/:app/log", authorizationRequiredHandler(appLog))
	m.Post("/apps/:app/log", authorizationRequiredHandler(addLog))

	m.Get("/platforms", authorizationRequiredHandler(platformList))

	// These handlers don't use :app on purpose. Using :app means that only
	// the token generate for the given app is valid, but these handlers
	// use a token generated for Gandalf.
	m.Get("/apps/:appname/available", authorizationRequiredHandler(appIsAvailable))
	m.Get("/apps/:appname/repository/clone", authorizationRequiredHandler(cloneRepository))

	if registrationEnabled, _ := config.GetBool("auth:user-registration"); registrationEnabled {
		m.Post("/users", handler(createUser))
	}

	m.Post("/users/:email/password", handler(resetPassword))
	m.Post("/users/:email/tokens", handler(login))
	m.Del("/users/tokens", authorizationRequiredHandler(logout))
	m.Put("/users/password", authorizationRequiredHandler(changePassword))
	m.Del("/users", authorizationRequiredHandler(removeUser))
	m.Post("/users/keys", authorizationRequiredHandler(addKeyToUser))
	m.Del("/users/keys", authorizationRequiredHandler(removeKeyFromUser))

	m.Post("/tokens", adminRequiredHandler(generateAppToken))

	m.Get("/teams", authorizationRequiredHandler(teamList))
	m.Post("/teams", authorizationRequiredHandler(createTeam))
	m.Get("/teams/:name", authorizationRequiredHandler(getTeam))
	m.Del("/teams/:name", authorizationRequiredHandler(removeTeam))
	m.Put("/teams/:team/:user", authorizationRequiredHandler(addUserToTeam))
	m.Del("/teams/:team/:user", authorizationRequiredHandler(removeUserFromTeam))

	m.Get("/healers", authorizationRequiredHandler(healers))
	m.Get("/healers/:healer", authorizationRequiredHandler(healer))

	if !dry {
		provisioner, err := config.GetString("provisioner")
		if err != nil {
			fmt.Printf("Warning: %q didn't declare a provisioner, using default provisioner.\n", configFile)
			provisioner = "juju"
		}
		app.Provisioner, err = provision.Get(provisioner)
		if err != nil {
			fatal(err)
		}
		fmt.Printf("Using %q provisioner.\n\n", provisioner)

		listen, err := config.GetString("listen")
		if err != nil {
			fatal(err)
		}
		tls, _ := config.GetBool("use-tls")
		if tls {
			certFile, err := config.GetString("tls:cert-file")
			if err != nil {
				fatal(err)
			}
			keyFile, err := config.GetString("tls:key-file")
			if err != nil {
				fatal(err)
			}
			fmt.Printf("tsuru HTTP/TLS server listening at %s...\n", listen)
			fatal(http.ListenAndServeTLS(listen, certFile, keyFile, m))
		} else {
			listener, err := net.Listen("tcp", listen)
			if err != nil {
				fatal(err)
			}
			fmt.Printf("tsuru HTTP server listening at %s...\n", listen)
			http.Handle("/", m)
			fatal(http.Serve(listener, nil))
		}
	}
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"net"
	"sync"

	"github.com/pion/sctp"
)

// TransportManager is in charge of managing server-to-server transports.
type TransportManager struct {
	params      *TransportManagerParams
	sctpManager *SCTPManager
	closeOnce   sync.Once
	mu          sync.Mutex
	wg          sync.WaitGroup

	rooms        map[string][]*StreamTransport
	associations map[*Association]*ServerTransportFactory
}

type StreamTransport struct {
	Transport
	StreamID uint16
}

type ServerTransportFactory struct {
	loggerFactory LoggerFactory
	association   *Association
	streamCount   uint16
	Transports    map[*StreamTransport]struct{}
	freeBuckets   map[uint16]struct{}
	mu            sync.Mutex
	wg            *sync.WaitGroup
}

type TransportManagerParams struct {
	Conn          net.PacketConn
	LoggerFactory LoggerFactory
}

func NewTransportManager(params TransportManagerParams) *TransportManager {
	sctpManager := NewSCTPManager(SCTPManagerParams{
		LoggerFactory: params.LoggerFactory,
		Conn:          params.Conn,
	})

	t := &TransportManager{
		params:       &params,
		sctpManager:  sctpManager,
		associations: make(map[*Association]*ServerTransportFactory),
	}

	return t
}

// AcceptTransportFactory returns a tranpsort factory after a new Association
// is created. The factory can be used to create new ServerTransports as
// needed.
func (t *TransportManager) AcceptTransportFactory() (*ServerTransportFactory, error) {
	association, err := t.sctpManager.AcceptAssociation()
	if err != nil {
		return nil, fmt.Errorf("Error accepting transport factory: %w", err)
	}

	return t.createServerTransportFactory(association), nil
}

// createServerTransportFactory creates a new ServerTransportFactory for the
// provided association.
func (t *TransportManager) createServerTransportFactory(association *Association) *ServerTransportFactory {
	t.mu.Lock()
	defer t.mu.Unlock()

	factory := NewServerTransportFactory(t.params.LoggerFactory, &t.wg, association)
	t.associations[association] = factory

	t.wg.Add(1)
	go func() {
		defer t.wg.Done()
		<-association.CloseChannel()
		delete(t.associations, association)
	}()

	return factory
}

func (t *TransportManager) GetTransportFactory(raddr net.Addr) (*ServerTransportFactory, error) {
	association, err := t.sctpManager.GetAssociation(raddr)
	if err != nil {
		return nil, fmt.Errorf("Error getting association for raddr %s: %w", raddr, err)
	}

	return t.createServerTransportFactory(association), nil
}

func (t *TransportManager) Close() error {
	err := t.close()

	t.wg.Wait()

	return err
}

func (t *TransportManager) close() error {
	t.mu.Lock()
	defer t.mu.Unlock()

	err := t.sctpManager.Close()

	t.closeOnce.Do(func() {
		for association := range t.associations {
			_ = association.Close()
			delete(t.associations, association)
		}
	})

	return err
}

func NewServerTransportFactory(loggerFactory LoggerFactory, wg *sync.WaitGroup, association *Association) *ServerTransportFactory {
	return &ServerTransportFactory{
		loggerFactory: loggerFactory,
		association:   association,
		streamCount:   0,
		Transports:    make(map[*StreamTransport]struct{}),
		freeBuckets:   make(map[uint16]struct{}),
		wg:            wg,
	}
}

// FIXME TODO
//
// How to keep StreamIDs in sync from two nodes??
//
// Two problems:
//
// 1. node1 and node2 might pick the same StreamIDs. Perhaps the
// initiator could only be allowed to request streams from [0,
// maxuint16/2>, and the other one from [maxuint16/2, maxuint16].
//
// 2. What if node1 and node2 both decide to create 3 streams and a a
// transport for the same room?
//

func (t *ServerTransportFactory) AcceptTransport() (*StreamTransport, error) {
	// TODO wait for 3 consecutive streams to be created.
	return nil, fmt.Errorf("Not implemented")
}

func (t *ServerTransportFactory) NewTransport() (*StreamTransport, error) {
	t.mu.Lock()
	defer t.mu.Unlock()

	var streamID uint16
	found := false
	for streamIDForReuse := range t.freeBuckets {
		streamID = streamIDForReuse
		found = true
		delete(t.freeBuckets, streamIDForReuse)
		break
	}

	if !found {
		streamID = (t.streamCount*3 + 1)
		// TODO error out on overflow, but there will probably be bigger problems when that happens.
		t.streamCount++
	}

	// TODO make handling of these errors nicer
	mediaStream, err := t.association.OpenStream(streamID, sctp.PayloadTypeWebRTCBinary)
	if err != nil {
		return nil, fmt.Errorf("Error opening media stream: %w", err)
	}

	dataStream, err := t.association.OpenStream(streamID+1, sctp.PayloadTypeWebRTCBinary)
	if err != nil {
		_ = mediaStream.Close()
		return nil, fmt.Errorf("Error opening data stream: %w", err)
	}
	metadataStream, err := t.association.OpenStream(streamID+2, sctp.PayloadTypeWebRTCBinary)
	if err != nil {
		_ = dataStream.Close()
		_ = mediaStream.Close()
		return nil, fmt.Errorf("Error opening metadata stream: %w", err)
	}

	transport := NewServerTransport(t.loggerFactory, mediaStream, dataStream, metadataStream)

	streamTransport := &StreamTransport{transport, streamID}
	t.Transports[streamTransport] = struct{}{}

	t.wg.Add(1)
	go func() {
		defer t.wg.Done()
		<-transport.CloseChannel()

		t.mu.Lock()
		defer t.mu.Unlock()

		t.freeBuckets[streamTransport.StreamID] = struct{}{}

		delete(t.Transports, streamTransport)
	}()

	return streamTransport, nil
}
<commit_msg>Fix data race<commit_after>package server

import (
	"fmt"
	"net"
	"sync"

	"github.com/pion/sctp"
)

// TransportManager is in charge of managing server-to-server transports.
type TransportManager struct {
	params      *TransportManagerParams
	sctpManager *SCTPManager
	closeOnce   sync.Once
	mu          sync.Mutex
	wg          sync.WaitGroup

	rooms        map[string][]*StreamTransport
	associations map[*Association]*ServerTransportFactory
}

type StreamTransport struct {
	Transport
	StreamID uint16
}

type ServerTransportFactory struct {
	loggerFactory LoggerFactory
	association   *Association
	streamCount   uint16
	Transports    map[*StreamTransport]struct{}
	freeBuckets   map[uint16]struct{}
	mu            sync.Mutex
	wg            *sync.WaitGroup
}

type TransportManagerParams struct {
	Conn          net.PacketConn
	LoggerFactory LoggerFactory
}

func NewTransportManager(params TransportManagerParams) *TransportManager {
	sctpManager := NewSCTPManager(SCTPManagerParams{
		LoggerFactory: params.LoggerFactory,
		Conn:          params.Conn,
	})

	t := &TransportManager{
		params:       &params,
		sctpManager:  sctpManager,
		associations: make(map[*Association]*ServerTransportFactory),
	}

	return t
}

// AcceptTransportFactory returns a tranpsort factory after a new Association
// is created. The factory can be used to create new ServerTransports as
// needed.
func (t *TransportManager) AcceptTransportFactory() (*ServerTransportFactory, error) {
	association, err := t.sctpManager.AcceptAssociation()
	if err != nil {
		return nil, fmt.Errorf("Error accepting transport factory: %w", err)
	}

	return t.createServerTransportFactory(association), nil
}

// createServerTransportFactory creates a new ServerTransportFactory for the
// provided association.
func (t *TransportManager) createServerTransportFactory(association *Association) *ServerTransportFactory {
	t.mu.Lock()
	defer t.mu.Unlock()

	factory := NewServerTransportFactory(t.params.LoggerFactory, &t.wg, association)
	t.associations[association] = factory

	t.wg.Add(1)
	go func() {
		defer t.wg.Done()
		<-association.CloseChannel()

		t.mu.Lock()
		defer t.mu.Unlock()

		delete(t.associations, association)
	}()

	return factory
}

func (t *TransportManager) GetTransportFactory(raddr net.Addr) (*ServerTransportFactory, error) {
	association, err := t.sctpManager.GetAssociation(raddr)
	if err != nil {
		return nil, fmt.Errorf("Error getting association for raddr %s: %w", raddr, err)
	}

	return t.createServerTransportFactory(association), nil
}

func (t *TransportManager) Close() error {
	err := t.close()

	t.wg.Wait()

	return err
}

func (t *TransportManager) close() error {
	t.mu.Lock()
	defer t.mu.Unlock()

	err := t.sctpManager.Close()

	t.closeOnce.Do(func() {
		for association := range t.associations {
			_ = association.Close()
			delete(t.associations, association)
		}
	})

	return err
}

func NewServerTransportFactory(loggerFactory LoggerFactory, wg *sync.WaitGroup, association *Association) *ServerTransportFactory {
	return &ServerTransportFactory{
		loggerFactory: loggerFactory,
		association:   association,
		streamCount:   0,
		Transports:    make(map[*StreamTransport]struct{}),
		freeBuckets:   make(map[uint16]struct{}),
		wg:            wg,
	}
}

// FIXME TODO
//
// How to keep StreamIDs in sync from two nodes??
//
// Two problems:
//
// 1. node1 and node2 might pick the same StreamIDs. Perhaps the
// initiator could only be allowed to request streams from [0,
// maxuint16/2>, and the other one from [maxuint16/2, maxuint16].
//
// 2. What if node1 and node2 both decide to create 3 streams and a a
// transport for the same room?
//

func (t *ServerTransportFactory) AcceptTransport() (*StreamTransport, error) {
	// TODO wait for 3 consecutive streams to be created.
	return nil, fmt.Errorf("Not implemented")
}

func (t *ServerTransportFactory) NewTransport() (*StreamTransport, error) {
	t.mu.Lock()
	defer t.mu.Unlock()

	var streamID uint16
	found := false
	for streamIDForReuse := range t.freeBuckets {
		streamID = streamIDForReuse
		found = true
		delete(t.freeBuckets, streamIDForReuse)
		break
	}

	if !found {
		streamID = (t.streamCount*3 + 1)
		// TODO error out on overflow, but there will probably be bigger problems when that happens.
		t.streamCount++
	}

	// TODO make handling of these errors nicer
	mediaStream, err := t.association.OpenStream(streamID, sctp.PayloadTypeWebRTCBinary)
	if err != nil {
		return nil, fmt.Errorf("Error opening media stream: %w", err)
	}

	dataStream, err := t.association.OpenStream(streamID+1, sctp.PayloadTypeWebRTCBinary)
	if err != nil {
		_ = mediaStream.Close()
		return nil, fmt.Errorf("Error opening data stream: %w", err)
	}
	metadataStream, err := t.association.OpenStream(streamID+2, sctp.PayloadTypeWebRTCBinary)
	if err != nil {
		_ = dataStream.Close()
		_ = mediaStream.Close()
		return nil, fmt.Errorf("Error opening metadata stream: %w", err)
	}

	transport := NewServerTransport(t.loggerFactory, mediaStream, dataStream, metadataStream)

	streamTransport := &StreamTransport{transport, streamID}
	t.Transports[streamTransport] = struct{}{}

	t.wg.Add(1)
	go func() {
		defer t.wg.Done()
		<-transport.CloseChannel()

		t.mu.Lock()
		defer t.mu.Unlock()

		t.freeBuckets[streamTransport.StreamID] = struct{}{}

		delete(t.Transports, streamTransport)
	}()

	return streamTransport, nil
}
<|endoftext|>


<commit_before>package datastoredb

import (
	"cloud.google.com/go/datastore"
	"context"
	"google.golang.org/api/option"
)

// DatastoreDB implements the slackscot StringStorer interface. It maps
// the given name (usually a plugin name) to the datastore entity Kind
// to isolate data between different plugins
type DatastoreDB struct {
	*datastore.Client
	kind string
}

// EntryValue represents an entity/entry value mapped to a datastore key
type EntryValue struct {
	Value string `datastore:",noindex"`
}

// NewDatastoreDB returns a new instance of DatastoreDB for the given name (which maps to the datastore entity "Kind" and can
// be thought of as the namespace). This function also requires a gcloudProjectID as well as at least one option to provide gcloud client credentials
func NewDatastoreDB(name string, gcloudProjectID string, gcloudClientOpts ...option.ClientOption) (dsdb *DatastoreDB, err error) {
	ctx := context.Background()
	client, err := datastore.NewClient(ctx, gcloudProjectID, gcloudClientOpts...)
	if err != nil {
		return nil, err
	}

	dsdb = new(DatastoreDB)
	dsdb.Client = client
	dsdb.kind = name

	err = dsdb.testDB()
	if err = dsdb.testDB(); err != nil {
		dsdb.Close()
		return nil, err
	}

	return dsdb, nil
}

// testDB makes a lightweight call to the datastore to validate connectivity and credentials
func (dsdb *DatastoreDB) testDB() (err error) {
	_, err = dsdb.GetString("testConnectivity")

	if err != nil && err != datastore.ErrNoSuchEntity {
		return err
	}

	return nil
}

// GetString returns the value associated to a given key. If the value is not
// found or an error occured, the zero-value string is returned along with
// the error
func (dsdb *DatastoreDB) GetString(key string) (value string, err error) {
	ctx := context.Background()

	var e EntryValue
	k := datastore.NameKey(dsdb.kind, key, nil)
	if err := dsdb.Get(ctx, k, &e); err != nil {
		return "", err
	}

	return e.Value, nil
}

// PutString stores the key/value to the database
func (dsdb *DatastoreDB) PutString(key string, value string) (err error) {
	ctx := context.Background()
	k := datastore.NameKey(dsdb.kind, key, nil)

	_, err = dsdb.Put(ctx, k, &EntryValue{Value: value})
	return err
}

// DeleteString deletes the entry for the given key. If the entry is not found
// an error is returned
func (dsdb *DatastoreDB) DeleteString(key string) (err error) {
	ctx := context.Background()
	k := datastore.NameKey(dsdb.kind, key, nil)

	return dsdb.Delete(ctx, k)
}

// Scan returns all key/values from the database
func (dsdb *DatastoreDB) Scan() (entries map[string]string, err error) {
	entries = make(map[string]string)

	ctx := context.Background()
	var vals []*EntryValue

	keys, err := dsdb.GetAll(ctx, datastore.NewQuery(dsdb.kind), &vals)
	if err != nil {
		return nil, err
	}

	for i, key := range keys {
		entries[key.Name] = vals[i].Value
	}

	return entries, nil
}

// Close closes the underlying datastore client
func (dsdb *DatastoreDB) Close() (err error) {
	return dsdb.Client.Close()
}
<commit_msg>Rename datastoredb.NewDatastoreDB to datastoredb.New()<commit_after>package datastoredb

import (
	"cloud.google.com/go/datastore"
	"context"
	"google.golang.org/api/option"
)

// DatastoreDB implements the slackscot StringStorer interface. It maps
// the given name (usually a plugin name) to the datastore entity Kind
// to isolate data between different plugins
type DatastoreDB struct {
	*datastore.Client
	kind string
}

// EntryValue represents an entity/entry value mapped to a datastore key
type EntryValue struct {
	Value string `datastore:",noindex"`
}

// New returns a new instance of DatastoreDB for the given name (which maps to the datastore entity "Kind" and can
// be thought of as the namespace). This function also requires a gcloudProjectID as well as at least one option to provide gcloud client credentials
func New(name string, gcloudProjectID string, gcloudClientOpts ...option.ClientOption) (dsdb *DatastoreDB, err error) {
	ctx := context.Background()
	client, err := datastore.NewClient(ctx, gcloudProjectID, gcloudClientOpts...)
	if err != nil {
		return nil, err
	}

	dsdb = new(DatastoreDB)
	dsdb.Client = client
	dsdb.kind = name

	err = dsdb.testDB()
	if err = dsdb.testDB(); err != nil {
		dsdb.Close()
		return nil, err
	}

	return dsdb, nil
}

// testDB makes a lightweight call to the datastore to validate connectivity and credentials
func (dsdb *DatastoreDB) testDB() (err error) {
	_, err = dsdb.GetString("testConnectivity")

	if err != nil && err != datastore.ErrNoSuchEntity {
		return err
	}

	return nil
}

// GetString returns the value associated to a given key. If the value is not
// found or an error occured, the zero-value string is returned along with
// the error
func (dsdb *DatastoreDB) GetString(key string) (value string, err error) {
	ctx := context.Background()

	var e EntryValue
	k := datastore.NameKey(dsdb.kind, key, nil)
	if err := dsdb.Get(ctx, k, &e); err != nil {
		return "", err
	}

	return e.Value, nil
}

// PutString stores the key/value to the database
func (dsdb *DatastoreDB) PutString(key string, value string) (err error) {
	ctx := context.Background()
	k := datastore.NameKey(dsdb.kind, key, nil)

	_, err = dsdb.Put(ctx, k, &EntryValue{Value: value})
	return err
}

// DeleteString deletes the entry for the given key. If the entry is not found
// an error is returned
func (dsdb *DatastoreDB) DeleteString(key string) (err error) {
	ctx := context.Background()
	k := datastore.NameKey(dsdb.kind, key, nil)

	return dsdb.Delete(ctx, k)
}

// Scan returns all key/values from the database
func (dsdb *DatastoreDB) Scan() (entries map[string]string, err error) {
	entries = make(map[string]string)

	ctx := context.Background()
	var vals []*EntryValue

	keys, err := dsdb.GetAll(ctx, datastore.NewQuery(dsdb.kind), &vals)
	if err != nil {
		return nil, err
	}

	for i, key := range keys {
		entries[key.Name] = vals[i].Value
	}

	return entries, nil
}

// Close closes the underlying datastore client
func (dsdb *DatastoreDB) Close() (err error) {
	return dsdb.Client.Close()
}
<|endoftext|>


<commit_before>package gc

import (
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/coreos/agro"
)

type blocksByINode struct {
	mut       sync.Mutex
	mds       agro.MetadataService
	blocks    agro.BlockStore
	stopChan  chan bool
	forceChan chan bool
	last      time.Time
}

func NewBlocksByINodeGC(mds agro.MetadataService, blocks agro.BlockStore) GC {
	return &blocksByINode{
		mds:    mds,
		blocks: blocks,
		last:   time.Unix(0, 0),
	}
}

func (b *blocksByINode) Start() {
	sc := make(chan bool)
	fc := make(chan bool)
	b.stopChan = sc
	b.forceChan = fc
	go blocksByINodeMain(b, sc, fc)
}

func (b *blocksByINode) Stop() {
	close(b.stopChan)
}

func (b *blocksByINode) Force() {
	b.forceChan <- true
}

func (b *blocksByINode) LastComplete() time.Time {
	return b.last
}

func (b *blocksByINode) gc(volume string) {
	vid, err := b.mds.GetVolumeID(volume)
	if err != nil {
		clog.Errorf("bbi: got error getting volume ID for %s %v", volume, err)
	}
	deadmap, held, err := b.mds.GetVolumeLiveness(volume)
	if err != nil {
		clog.Errorf("bbi: got error gcing volume %s %v", volume, err)
	}
	for _, x := range held {
		deadmap.AndNot(x)
	}
	it := deadmap.Iterator()
	for it.HasNext() {
		i := it.Next()
		ref := agro.INodeRef{
			Volume: vid,
			INode:  agro.INodeID(i),
		}
		err := b.blocks.DeleteINodeBlocks(context.TODO(), ref)
		if err != nil {
			clog.Errorf("bbi: got error gcing volume %s deleting INode %v: %v", volume, ref, err)
		}
	}
}

func blocksByINodeMain(b *blocksByINode, stop chan bool, force chan bool) {
	clog.Debug("bbi: starting blocksByInode")
all:
	for {
		forced := false
		select {
		case <-time.After(DefaultGCWait):
			clog.Trace("bbi: top of gc")
		case <-stop:
			break all
		case <-force:
			forced = true
			clog.Debug("bbi: forcing")
		}
		volumes, err := b.mds.GetVolumes()
		if err != nil {
			clog.Error("couldn't get volumes", err)
		}
		for _, v := range volumes {
			b.gc(v)
			if forced {
				continue
			}
			select {
			case <-time.After(DefaultGCWait):
			case <-stop:
				break all
			}
		}
		b.last = time.Now()
	}
	clog.Debug("bbi: ending blocksByInode")
}
<commit_msg>make Stop() block until gc stops<commit_after>package gc

import (
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/coreos/agro"
)

type blocksByINode struct {
	mut       sync.Mutex
	mds       agro.MetadataService
	blocks    agro.BlockStore
	stopChan  chan bool
	forceChan chan bool
	doneChan  chan bool
	last      time.Time
}

func NewBlocksByINodeGC(mds agro.MetadataService, blocks agro.BlockStore) GC {
	return &blocksByINode{
		mds:    mds,
		blocks: blocks,
		last:   time.Unix(0, 0),
	}
}

func (b *blocksByINode) Start() {
	sc := make(chan bool)
	fc := make(chan bool)
	dc := make(chan bool)
	b.stopChan = sc
	b.forceChan = fc
	b.doneChan = dc
	go blocksByINodeMain(b, sc, fc, dc)
}

func (b *blocksByINode) Stop() {
	close(b.stopChan)
	<-b.doneChan
}

func (b *blocksByINode) Force() {
	b.forceChan <- true
}

func (b *blocksByINode) LastComplete() time.Time {
	return b.last
}

func (b *blocksByINode) gc(volume string) {
	vid, err := b.mds.GetVolumeID(volume)
	if err != nil {
		clog.Errorf("bbi: got error getting volume ID for %s %v", volume, err)
	}
	deadmap, held, err := b.mds.GetVolumeLiveness(volume)
	if err != nil {
		clog.Errorf("bbi: got error gcing volume %s %v", volume, err)
	}
	for _, x := range held {
		deadmap.AndNot(x)
	}
	it := deadmap.Iterator()
	for it.HasNext() {
		i := it.Next()
		ref := agro.INodeRef{
			Volume: vid,
			INode:  agro.INodeID(i),
		}
		err := b.blocks.DeleteINodeBlocks(context.TODO(), ref)
		if err != nil {
			clog.Errorf("bbi: got error gcing volume %s deleting INode %v: %v", volume, ref, err)
		}
	}
}

func blocksByINodeMain(b *blocksByINode, stop chan bool, force chan bool, done chan bool) {
	clog.Debug("bbi: starting blocksByInode")
all:
	for {
		forced := false
		select {
		case <-time.After(DefaultGCWait):
			clog.Trace("bbi: top of gc")
		case <-stop:
			break all
		case <-force:
			forced = true
			clog.Debug("bbi: forcing")
		}
		volumes, err := b.mds.GetVolumes()
		if err != nil {
			clog.Error("couldn't get volumes", err)
		}
		for _, v := range volumes {
			b.gc(v)
			if forced {
				continue
			}
			select {
			case <-time.After(DefaultGCWait):
			case <-stop:
				break all
			}
		}
		b.last = time.Now()
	}
	clog.Debug("bbi: ending blocksByInode")
	close(done)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 - 2017 Huawei Technologies Co., Ltd. All rights reserved.

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

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

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

package controller

import (
	"fmt"
	"html/template"
	"os"
	"reflect"
	"regexp"
	"strings"

	"github.com/Huawei/containerops/singular/model"
)

type HtmlSingular struct {
	Namespace   string
	Repository  string
	Name        string
	Result      bool
	Deployments []HtmlDeployment
}

type HtmlDeployment struct {
	SingularNamespace  string
	SingularRepository string
	SingularName       string
	CreatedTime        string
	ID                 int64
	Infras             []HtmlInfra
	Data               template.HTML // The YAML file content
	CA                 template.HTML // The YAML file content
	Log                template.HTML // The log

	Tag     string
	Version int64

	InfraName   string
	InfraLogo   string
	InfraLog    template.HTML
	StatusIcon  string
	StatusColor string
}

type HtmlInfra struct {
	ID         int64
	Name       string
	Version    string
	Logo       string
	Link       string
	Log        template.HTML
	Components []HtmlComponent
}

type HtmlComponent struct {
	// Log           template.HTML
	Name          string
	Log           string
	ImageSrc, Alt string
	Width, Height int
}

type HtmlInfraTitle struct {
	Name string
	Logo string
	Link string
}

func GetHtmlDeploymentList() ([]HtmlDeployment, error) {
	var deployments []model.DeploymentV1
	err := model.DB.Order("create_at desc").Find(&deployments).Error
	if err != nil {
		return nil, err
	}

	var singular model.SingularV1
	htmlDeployments := []HtmlDeployment{}
	for i := 0; i < len(deployments); i++ {
		deployment := deployments[i]

		statusIcon, statusColor := "clear", "red"
		if deployment.Result == true {
			statusIcon, statusColor = "check", "green"
		}
		model.DB.Where("ID=?", deployment.SingularV1).First(&singular)
		htmlDeployment := HtmlDeployment{
			// Name: deployment.Name,
			CreatedTime:        deployment.CreatedAt.Format("2006-01-02 15:04:05"),
			ID:                 deployment.ID,
			SingularName:       singular.Name,
			SingularNamespace:  singular.Namespace,
			SingularRepository: singular.Repository,
			Tag:                deployment.Tag,
			Version:            deployment.Version,
			StatusIcon:         statusIcon,
			StatusColor:        statusColor,
		}
		htmlDeployments = append(htmlDeployments, htmlDeployment)
	}
	// Find the corresponding infrastructure & components info
	sig := make(chan error)
	for i := 0; i < len(deployments); i++ {
		go func(htmlDeployment *HtmlDeployment) {
			var infras []model.InfraV1
			err := model.DB.Where("deployment_v1=?", htmlDeployment.ID).Find(&infras).Error
			if err != nil {
				sig <- err
				return
			}

			htmlInfras := []HtmlInfra{}
			for i := 0; i < len(InfraOrder); i++ {
				found := false
				var htmlInfra HtmlInfra
				for j := 0; j < len(infras); j++ {
					infra := infras[j]
					if infra.Name == InfraOrder[i] {
						found = true
						htmlInfra = HtmlInfra{
							// ID:      infra.ID,
							Logo:    getInfraLogo(infra.Name),
							Name:    infra.Name,
							Version: getInfraSemver(infra.Name, infra.Version),
						}
						break
					}
				}

				if !found {
					htmlInfra = HtmlInfra{
						Name:    InfraOrder[i],
						Version: "N/A",
					}
				}
				htmlInfras = append(htmlInfras, htmlInfra)
			}
			htmlDeployment.Infras = htmlInfras
			sig <- nil

			// htmlDeployments = append(htmlDeployments, htmlDeployment)
		}(&htmlDeployments[i])
	}

	for i := 0; i < len(deployments); i++ {
		if err := <-sig; err != nil {
			return nil, err
		}
	}
	return htmlDeployments, nil
}

func GetHtmlDeploymentDetail(namespace, repository, name, tag string, version int64) *HtmlDeployment {
	var singular model.SingularV1
	var deployment model.DeploymentV1
	var infras []model.InfraV1
	htmlInfras := []HtmlInfra{}

	// Get Singular
	model.DB.Where("namespace=? AND repository=? AND name=?", namespace, repository, name).First(&singular)

	// Get the infra and components
	err := model.DB.Where("singular_v1=? AND tag=? AND version=?", singular.ID, tag, version).First(&deployment).Error
	if err != nil && err.Error() == "record not found" {
		return nil
	}

	model.DB.Where("deployment_v1=?", deployment.ID).Find(&infras)
	for i := 0; i < len(infras); i++ {
		infra := infras[i]
		var components []model.ComponentV1
		model.DB.Where("infra_v1=?", infra.ID).Find(&components)

		htmlComponents := []HtmlComponent{}
		for j := 0; j < len(components); j++ {
			component := components[j]
			htmlComponents = append(htmlComponents, HtmlComponent{
				Name: component.Name,
				Log:  component.Log,
			})
		}

		htmlInfra := convertInfra(&infra)
		htmlInfra.Components = htmlComponents
		htmlInfras = append(htmlInfras, htmlInfra)
	}

	htmlDeployment := HtmlDeployment{
		Version:            deployment.Version,
		Tag:                deployment.Tag,
		Log:                convertLog(deployment.Log),
		Data:               convertToBr(deployment.Data),
		CA:                 convertToBr(deployment.CA),
		Infras:             htmlInfras,
		SingularName:       singular.Name,
		SingularNamespace:  singular.Namespace,
		SingularRepository: singular.Repository,
	}

	return &htmlDeployment
}

func StringifyComponentsNames(args ...interface{}) (string, error) {
	v := reflect.ValueOf(args)
	numArgs := v.Len()
	if numArgs != 1 {
		fmt.Fprintf(os.Stderr, "component_names function expect 1 argument, but got %d", numArgs)
		return "", fmt.Errorf("expect 1 argument")
	}

	if components, ok := v.Index(0).Interface().([]HtmlComponent); !ok {
		fmt.Fprintln(os.Stderr, "function component_names receives an argument which is not []Component")
		return "", fmt.Errorf("argument is not []Component")
	} else {
		s := ""
		for i := 0; i < len(components); i++ {
			c := components[i]
			s += c.Name + ", "
		}
		return s[:len(s)-1], nil
	}
}

// Convert \n to

func convertToBr(src string) template.HTML {
	replaced := strings.Replace(src, "\n", "\n
", -1)
	return template.HTML(replaced)
	// return strings.Join(strings.Split(src, "\n"), "
")
}

func convertInfra(input *model.InfraV1) HtmlInfra {
	infra := HtmlInfra{
		Name:    input.Name,
		Version: getInfraSemver(input.Name, input.Version),
		Log:     convertLog(input.Log),
	}
	// Got the size and icon
	switch infra.Name {
	case "kubernetes":
		// infra.Width = 40
		// infra.Height = 40
		infra.Logo = "/public/icons/kubernetes.svg"
		break
	case "docker":
		// infra.Width = 40
		// infra.Height = 40
		infra.Logo = "/public/icons/docker.svg"
		break
	case "flannel":
		// infra.Width = 40
		// infra.Height = 40
		infra.Logo = "/public/icons/flannel.svg"
		break
	case "etcd":
		// infra.Width = 40
		// infra.Height = 40
		infra.Logo = "/public/icons/etcd.svg"
		break
	default:
		break
	}

	return infra
}

// The log are stored in array, each element started by -space and quoted by '
func convertLog(input string) template.HTML {
	lines := strings.Split(input, "\n")
	outputStr := ""
	for i := 0; i < len(lines); i++ {
		line := lines[i]
		line = strings.Trim(line, "- '")
		outputStr += line + "
"
	}
	return template.HTML(outputStr)
}

func getInfraSemver(name, version string) string {
	re := regexp.MustCompile(fmt.Sprintf("%s-", name))
	locations := re.FindStringIndex(version)
	if locations == nil {
		return "N/A"
	}
	return version[locations[1]:]
}

var infraNames map[string]string = map[string]string{
	"kubernetes":  "Kubernetes",
	"etcd":        "etcd",
	"flannel":     "Flannel",
	"docker":      "Docker",
	"prometheus":  "Prometheus",
	"opentracing": "OpenTracking",
	"fluentd":     "Fluentd",
	"linerd":      "linkerd",
	"grpc":        "gRPC",
	"coredns":     "CoreDNS",
	"containerd":  "containerd",
	"rkt":         "rkt",
	"cni":         "CNI",
	"envoy":       "Envoy",
	"jaeger":      "Jaeger",
}

var infraLogos map[string]string = map[string]string{
	"kubernetes":  "/public/icons/kubernetes.png",
	"etcd":        "/public/icons/etcd.svg",
	"flannel":     "/public/icons/flannel.svg",
	"docker":      "/public/icons/docker.svg",
	"prometheus":  "/public/icons/prometheus.png",
	"opentracing": "/public/icons/opentracing.png",
	"fluentd":     "/public/icons/fluentd.png",
	"linkerd":     "/public/icons/linkerd.png",
	"grpc":        "/public/icons/grpc.png",
	"coredns":     "/public/icons/coredns.png",
	"containerd":  "/public/icons/containerd.png",
	"rkt":         "/public/icons/rkt.png",
	"cni":         "/public/icons/cni.png",
	"envoy":       "/public/icons/envoy.png",
	"jaeger":      "/public/icons/jaeger.png",
}

var infraLinks map[string]string = map[string]string{
	"kubernetes":  "https://kubernetes.io",
	"etcd":        "https://coreos.com/etcd",
	"flannel":     "https://coreos.com/flannel",
	"docker":      "https://www.docker.com",
	"prometheus":  "http://prometheus.io",
	"opentracing": "http://opentracing.io",
	"fluentd":     "http://fluentd.org",
	"linkerd":     "https://www.linkerd.io",
	"grpc":        "http://www.grpc.io",
	"coredns":     "https://coredns.io",
	"containerd":  "http://containerd.io",
	"rkt":         "https://github.com/rkt/rkt",
	"cni":         "https://github.com/containernetworking",
	"envoy":       "https://github.com/envoyproxy/envoy",
	"jaeger":      "https://github.com/uber/jaeger",
}

var InfraOrder []string = []string{"kubernetes", "etcd", "flannel", "docker", "prometheus", "opentracing", "fluentd", "linerd", "grpc", "coredns", "containerd", "rkt", "cni", "envoy", "jaeger"}
var InfraTitles []HtmlInfraTitle

func init() {
	for i := 0; i < len(InfraOrder); i++ {
		infra_name := InfraOrder[i]
		InfraTitles = append(InfraTitles, HtmlInfraTitle{
			Name: getInfraName(infra_name),
			Logo: getInfraLogo(infra_name),
			Link: getInfraLink(infra_name),
		})
	}
}

func getInfraName(key string) string {
	name := infraNames[key]
	if name == "" {
		name = "Unsupported Cloud Service Provider"
	}
	return name
}

func getInfraLogo(name string) string {
	logo := infraLogos[name]
	if logo == "" {
		logo = "/public/icons/google-cloud.svg"
	}
	return logo
}

func getInfraLink(name string) string {
	link := infraLinks[name]
	if link == "" {
		link = "https://www.github.com"
	}
	return link
}
<commit_msg>Select the latest deployment of each singular configs<commit_after>/*
Copyright 2016 - 2017 Huawei Technologies Co., Ltd. All rights reserved.

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

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

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

package controller

import (
	"fmt"
	"html/template"
	"os"
	"reflect"
	"regexp"
	"strings"

	"github.com/Huawei/containerops/singular/model"
)

type HtmlSingular struct {
	Namespace   string
	Repository  string
	Name        string
	Result      bool
	Deployments []HtmlDeployment
}

type HtmlDeployment struct {
	SingularNamespace  string
	SingularRepository string
	SingularName       string
	CreatedTime        string
	ID                 int64
	Infras             []HtmlInfra
	Data               template.HTML // The YAML file content
	CA                 template.HTML // The YAML file content
	Log                template.HTML // The log

	Tag     string
	Version int64

	InfraName   string
	InfraLogo   string
	InfraLog    template.HTML
	StatusIcon  string
	StatusColor string
}

type HtmlInfra struct {
	ID         int64
	Name       string
	Version    string
	Logo       string
	Link       string
	Log        template.HTML
	Components []HtmlComponent
}

type HtmlComponent struct {
	// Log           template.HTML
	Name          string
	Log           string
	ImageSrc, Alt string
	Width, Height int
}

type HtmlInfraTitle struct {
	Name string
	Logo string
	Link string
}

func GetHtmlDeploymentList() ([]HtmlDeployment, error) {
	var deployments []model.DeploymentV1
	// TODO See if there are some better ways
	// The original SQL:
	// select id, singular_v1, create_at from deployment_v1 where create_at in
	// ( select max(create_at) from deployment_v1 group by singular_v1)
	// order by create_at desc
	err := model.DB.Where("create_at in ( select max(create_at) from deployment_v1 group by singular_v1)").Order("create_at desc").Find(&deployments).Error
	if err != nil {
		return nil, err
	}

	var singular model.SingularV1
	htmlDeployments := []HtmlDeployment{}
	for i := 0; i < len(deployments); i++ {
		deployment := deployments[i]

		statusIcon, statusColor := "clear", "red"
		if deployment.Result == true {
			statusIcon, statusColor = "check", "green"
		}
		model.DB.Where("ID=?", deployment.SingularV1).First(&singular)
		htmlDeployment := HtmlDeployment{
			// Name: deployment.Name,
			CreatedTime:        deployment.CreatedAt.Format("2006-01-02 15:04:05"),
			ID:                 deployment.ID,
			SingularName:       singular.Name,
			SingularNamespace:  singular.Namespace,
			SingularRepository: singular.Repository,
			Tag:                deployment.Tag,
			Version:            deployment.Version,
			StatusIcon:         statusIcon,
			StatusColor:        statusColor,
		}
		htmlDeployments = append(htmlDeployments, htmlDeployment)
	}
	// Find the corresponding infrastructure & components info
	sig := make(chan error)
	for i := 0; i < len(deployments); i++ {
		go func(htmlDeployment *HtmlDeployment) {
			var infras []model.InfraV1
			err := model.DB.Where("deployment_v1=?", htmlDeployment.ID).Find(&infras).Error
			if err != nil {
				sig <- err
				return
			}

			htmlInfras := []HtmlInfra{}
			for i := 0; i < len(InfraOrder); i++ {
				found := false
				var htmlInfra HtmlInfra
				for j := 0; j < len(infras); j++ {
					infra := infras[j]
					if infra.Name == InfraOrder[i] {
						found = true
						htmlInfra = HtmlInfra{
							// ID:      infra.ID,
							Logo:    getInfraLogo(infra.Name),
							Name:    infra.Name,
							Version: getInfraSemver(infra.Name, infra.Version),
						}
						break
					}
				}

				if !found {
					htmlInfra = HtmlInfra{
						Name:    InfraOrder[i],
						Version: "N/A",
					}
				}
				htmlInfras = append(htmlInfras, htmlInfra)
			}
			htmlDeployment.Infras = htmlInfras
			sig <- nil

			// htmlDeployments = append(htmlDeployments, htmlDeployment)
		}(&htmlDeployments[i])
	}

	for i := 0; i < len(deployments); i++ {
		if err := <-sig; err != nil {
			return nil, err
		}
	}
	return htmlDeployments, nil
}

func GetHtmlDeploymentDetail(namespace, repository, name, tag string, version int64) *HtmlDeployment {
	var singular model.SingularV1
	var deployment model.DeploymentV1
	var infras []model.InfraV1
	htmlInfras := []HtmlInfra{}

	// Get Singular
	model.DB.Where("namespace=? AND repository=? AND name=?", namespace, repository, name).First(&singular)

	// Get the infra and components
	err := model.DB.Where("singular_v1=? AND tag=? AND version=?", singular.ID, tag, version).First(&deployment).Error
	if err != nil && err.Error() == "record not found" {
		return nil
	}

	model.DB.Where("deployment_v1=?", deployment.ID).Find(&infras)
	for i := 0; i < len(infras); i++ {
		infra := infras[i]
		var components []model.ComponentV1
		model.DB.Where("infra_v1=?", infra.ID).Find(&components)

		htmlComponents := []HtmlComponent{}
		for j := 0; j < len(components); j++ {
			component := components[j]
			htmlComponents = append(htmlComponents, HtmlComponent{
				Name: component.Name,
				Log:  component.Log,
			})
		}

		htmlInfra := convertInfra(&infra)
		htmlInfra.Components = htmlComponents
		htmlInfras = append(htmlInfras, htmlInfra)
	}

	htmlDeployment := HtmlDeployment{
		Version:            deployment.Version,
		Tag:                deployment.Tag,
		Log:                convertLog(deployment.Log),
		Data:               convertToBr(deployment.Data),
		CA:                 convertToBr(deployment.CA),
		Infras:             htmlInfras,
		SingularName:       singular.Name,
		SingularNamespace:  singular.Namespace,
		SingularRepository: singular.Repository,
	}

	return &htmlDeployment
}

func StringifyComponentsNames(args ...interface{}) (string, error) {
	v := reflect.ValueOf(args)
	numArgs := v.Len()
	if numArgs != 1 {
		fmt.Fprintf(os.Stderr, "component_names function expect 1 argument, but got %d", numArgs)
		return "", fmt.Errorf("expect 1 argument")
	}

	if components, ok := v.Index(0).Interface().([]HtmlComponent); !ok {
		fmt.Fprintln(os.Stderr, "function component_names receives an argument which is not []Component")
		return "", fmt.Errorf("argument is not []Component")
	} else {
		s := ""
		for i := 0; i < len(components); i++ {
			c := components[i]
			s += c.Name + ", "
		}
		return s[:len(s)-1], nil
	}
}

// Convert \n to

func convertToBr(src string) template.HTML {
	replaced := strings.Replace(src, "\n", "\n
", -1)
	return template.HTML(replaced)
	// return strings.Join(strings.Split(src, "\n"), "
")
}

func convertInfra(input *model.InfraV1) HtmlInfra {
	infra := HtmlInfra{
		Name:    input.Name,
		Version: getInfraSemver(input.Name, input.Version),
		Log:     convertLog(input.Log),
	}
	// Got the size and icon
	switch infra.Name {
	case "kubernetes":
		// infra.Width = 40
		// infra.Height = 40
		infra.Logo = "/public/icons/kubernetes.svg"
		break
	case "docker":
		// infra.Width = 40
		// infra.Height = 40
		infra.Logo = "/public/icons/docker.svg"
		break
	case "flannel":
		// infra.Width = 40
		// infra.Height = 40
		infra.Logo = "/public/icons/flannel.svg"
		break
	case "etcd":
		// infra.Width = 40
		// infra.Height = 40
		infra.Logo = "/public/icons/etcd.svg"
		break
	default:
		break
	}

	return infra
}

// The log are stored in array, each element started by -space and quoted by '
func convertLog(input string) template.HTML {
	lines := strings.Split(input, "\n")
	outputStr := ""
	for i := 0; i < len(lines); i++ {
		line := lines[i]
		line = strings.Trim(line, "- '")
		outputStr += line + "
"
	}
	return template.HTML(outputStr)
}

func getInfraSemver(name, version string) string {
	re := regexp.MustCompile(fmt.Sprintf("%s-", name))
	locations := re.FindStringIndex(version)
	if locations == nil {
		return "N/A"
	}
	return version[locations[1]:]
}

var infraNames map[string]string = map[string]string{
	"kubernetes":  "Kubernetes",
	"etcd":        "etcd",
	"flannel":     "Flannel",
	"docker":      "Docker",
	"prometheus":  "Prometheus",
	"opentracing": "OpenTracking",
	"fluentd":     "Fluentd",
	"linerd":      "linkerd",
	"grpc":        "gRPC",
	"coredns":     "CoreDNS",
	"containerd":  "containerd",
	"rkt":         "rkt",
	"cni":         "CNI",
	"envoy":       "Envoy",
	"jaeger":      "Jaeger",
}

var infraLogos map[string]string = map[string]string{
	"kubernetes":  "/public/icons/kubernetes.png",
	"etcd":        "/public/icons/etcd.svg",
	"flannel":     "/public/icons/flannel.svg",
	"docker":      "/public/icons/docker.svg",
	"prometheus":  "/public/icons/prometheus.png",
	"opentracing": "/public/icons/opentracing.png",
	"fluentd":     "/public/icons/fluentd.png",
	"linkerd":     "/public/icons/linkerd.png",
	"grpc":        "/public/icons/grpc.png",
	"coredns":     "/public/icons/coredns.png",
	"containerd":  "/public/icons/containerd.png",
	"rkt":         "/public/icons/rkt.png",
	"cni":         "/public/icons/cni.png",
	"envoy":       "/public/icons/envoy.png",
	"jaeger":      "/public/icons/jaeger.png",
}

var infraLinks map[string]string = map[string]string{
	"kubernetes":  "https://kubernetes.io",
	"etcd":        "https://coreos.com/etcd",
	"flannel":     "https://coreos.com/flannel",
	"docker":      "https://www.docker.com",
	"prometheus":  "http://prometheus.io",
	"opentracing": "http://opentracing.io",
	"fluentd":     "http://fluentd.org",
	"linkerd":     "https://www.linkerd.io",
	"grpc":        "http://www.grpc.io",
	"coredns":     "https://coredns.io",
	"containerd":  "http://containerd.io",
	"rkt":         "https://github.com/rkt/rkt",
	"cni":         "https://github.com/containernetworking",
	"envoy":       "https://github.com/envoyproxy/envoy",
	"jaeger":      "https://github.com/uber/jaeger",
}

var InfraOrder []string = []string{"kubernetes", "etcd", "flannel", "docker", "prometheus", "opentracing", "fluentd", "linerd", "grpc", "coredns", "containerd", "rkt", "cni", "envoy", "jaeger"}
var InfraTitles []HtmlInfraTitle

func init() {
	for i := 0; i < len(InfraOrder); i++ {
		infra_name := InfraOrder[i]
		InfraTitles = append(InfraTitles, HtmlInfraTitle{
			Name: getInfraName(infra_name),
			Logo: getInfraLogo(infra_name),
			Link: getInfraLink(infra_name),
		})
	}
}

func getInfraName(key string) string {
	name := infraNames[key]
	if name == "" {
		name = "Unsupported Cloud Service Provider"
	}
	return name
}

func getInfraLogo(name string) string {
	logo := infraLogos[name]
	if logo == "" {
		logo = "/public/icons/google-cloud.svg"
	}
	return logo
}

func getInfraLink(name string) string {
	link := infraLinks[name]
	if link == "" {
		link = "https://www.github.com"
	}
	return link
}
<|endoftext|>