<commit_before>package main

import (
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"path/filepath"
	"regexp"
	"time"
)

func init() {
	problemTypeHandlers["armv6asm"] = map[string]nannyHandler{
		"grade": nannyHandler(asGTestGrade),
		"debug": nannyHandler(asGdb),
		"run":   nannyHandler(asRun),
	}
}

// Google test framework types
type GTestProgram struct {
	XMLName  xml.Name     `xml:"testsuites"`
	Name     string       `xml:"name,attr"`
	Tests    int          `xml:"tests,attr"`
	Failures int          `xml:"failures,attr"`
	Disabled int          `xml:"disabled,attr"`
	Errors   int          `xml:"errors,attr"`
	Time     float64      `xml:"time,attr"`
	Cases    []*GTestCase `xml:"testsuite"`
}

type GTestCase struct {
	Name      string           `xml:"name,attr"`
	Tests     int              `xml:"tests,attr"`
	Failures  int              `xml:"failures,attr"`
	Disabled  int              `xml:"disabled,attr"`
	Errors    int              `xml:"errors,attr"`
	Time      float64          `xml:"time,attr"`
	Functions []*GTestFunction `xml:"testcase"`
}

type GTestFunction struct {
	Name      string        `xml:"name,attr"`
	Status    string        `xml:"status,attr"`
	Time      float64       `xml:"time,attr"`
	ClassName string        `xml:"classname,attr"`
	Failure   *GTestFailure `xml:"failure"`
}

type GTestFailure struct {
	Message string `xml:"message,attr"`
	Type    string `xml:"type,attr"`
	Body    string `xml:",chardata"`
}

func asGTestGrade(n *Nanny, args, options []string, files map[string]string, stdin io.Reader) {
	log.Printf("arm as gTest grade")

	// asCompileAndLink(n, files)
	// if !n.ReportCard.Passed {
	// 	return
	// }

	// parse the output (in common with c++)
	// parseXUnit(n, []string{"./a.out", "--gtest_output=xml"}, nil, "test_detail.xml")
	parseXUnit(n, []string{"make", "grade"}, nil, "test_detail.xml")
}

func asCompileAndLink(n *Nanny, files map[string]string) {
	// gather list of *.s files and tests/*.cpp files
	var sourceFiles, testFiles []string
	for path := range files {
		dir, file := filepath.Split(path)
		if dir == "" && filepath.Ext(file) == ".s" {
			sourceFiles = append(sourceFiles, path)
		}
		if dir == "tests/" && filepath.Ext(file) == ".cpp" {
			testFiles = append(testFiles, path)
		}
	}
	if len(sourceFiles) == 0 {
		n.ReportCard.LogAndFailf("no source files found")
		return
	}
	if len(testFiles) == 0 {
		n.ReportCard.LogAndFailf("no test files found")
		return
	}

	// assemble source files
	objectFiles := []string{}
	for _, src := range sourceFiles {
		out := src[:len(src)-len(".s")] + ".o"
		objectFiles = append(objectFiles, out)
		cmd := []string{"as", "-g", "-march=armv6zk", "-mcpu=arm1176jzf-s", "-mfloat-abi=hard", "-mfpu=vfp", src, "-o", out}

		// launch the assembler (ignore stdin)
		if err := n.ExecSimple(cmd, nil, false); err != nil {
			return
		}
	}

	// compile tests and link
	cmd := []string{"g++", "-std=c++11", "-Wpedantic", "-g", "-Wall", "-Wextra", "-Werror", "-I.", "-pthread"}
	cmd = append(cmd, objectFiles...)
	cmd = append(cmd, testFiles...)
	cmd = append(cmd, "-lgtest", "-lregwrapper", "-lpthread")
	if err := n.ExecSimple(cmd, nil, false); err != nil {
		return
	}

	return
}

func parseXUnit(n *Nanny, cmd []string, stdin io.Reader, filename string) {
	// run tests with XML output
	_, _, _, status, err := n.Exec(cmd, stdin, false)
	if err != nil {
		n.ReportCard.LogAndFailf("Error running unit tests: %v", err)
		return
	}

	// did it end in a segfault?
	if status > 127 {
		n.ReportCard.LogAndFailf("Unit tests did not finish normally, exit status %d", status)
		return
	}
	n.ReportCard.Passed = status == 0

	// parse the test results
	xmlfiles, err := n.GetFiles([]string{filename})
	if err != nil {
		n.ReportCard.LogAndFailf("Unit test failed: unable to read results")
		return
	}
	results := new(GTestProgram)
	if err = xml.Unmarshal([]byte(xmlfiles[filename]), results); err != nil {
		n.ReportCard.LogAndFailf("error parsing unit test results: %v", err)
		return
	}

	// form a report card
	fails := results.Failures + results.Disabled + results.Errors
	n.ReportCard.Note = fmt.Sprintf("Passed %d/%d tests in %v",
		results.Tests-fails, results.Tests, time.Since(n.Start))
	n.ReportCard.Passed = n.ReportCard.Passed && results.Tests > 0 && fails == 0 && status == 0

	context := regexp.MustCompile(`^(tests/[^:/]*:\d+)`)

	// prepare a report for each test case
	for _, testcase := range results.Cases {
		for _, test := range testcase.Functions {
			name := fmt.Sprintf("%s -> %s", test.ClassName, test.Name)
			if test.Status == "run" && test.Failure == nil {
				n.ReportCard.AddPassedResult(name, "")
			} else {
				details, ctx := "", ""
				if test.Failure != nil {
					details = htmlEscapePre(test.Failure.Body)

					// extract context
					groups := context.FindStringSubmatch(test.Failure.Body)
					if len(groups) > 1 {
						ctx = groups[1]
					}
				}
				n.ReportCard.AddFailedResult(name, details, ctx)
			}
		}
	}
}

func asRun(n *Nanny, args, options []string, files map[string]string, stdin io.Reader) {
	log.Printf("arm run")

	// gather list of *.s files
	var sourceFiles []string
	for path := range files {
		dir, file := filepath.Split(path)
		if dir == "" && filepath.Ext(file) == ".s" {
			sourceFiles = append(sourceFiles, path)
		}
	}
	if len(sourceFiles) == 0 {
		n.ReportCard.LogAndFailf("no source files found")
		return
	}

	// assemble source files
	objectFiles := []string{}
	for _, src := range sourceFiles {
		out := src[:len(src)-len(".s")] + ".o"
		objectFiles = append(objectFiles, out)
		cmd := []string{"as", "-g", "-march=armv6zk", "-mcpu=arm1176jzf-s", "-mfloat-abi=hard", "-mfpu=vfp", src, "-o", out}

		// launch the assembler (ignore stdin)
		if err := n.ExecSimple(cmd, nil, false); err != nil {
			return
		}
	}

	// link
	cmd := []string{"ld"}
	cmd = append(cmd, objectFiles...)
	if err := n.ExecSimple(cmd, nil, false); err != nil {
		return
	}

	// run gdb
	n.ExecSimple([]string{"./a.out"}, stdin, true)
}

func asGdb(n *Nanny, args, options []string, files map[string]string, stdin io.Reader) {
	log.Printf("arm as gdb")

	// gather list of *.s files
	var sourceFiles []string
	for path := range files {
		dir, file := filepath.Split(path)
		if dir == "" && filepath.Ext(file) == ".s" {
			sourceFiles = append(sourceFiles, path)
		}
	}
	if len(sourceFiles) == 0 {
		n.ReportCard.LogAndFailf("no source files found")
		return
	}

	// assemble source files
	objectFiles := []string{}
	for _, src := range sourceFiles {
		out := src[:len(src)-len(".s")] + ".o"
		objectFiles = append(objectFiles, out)
		cmd := []string{"as", "-g", "-march=armv6zk", "-mcpu=arm1176jzf-s", "-mfloat-abi=hard", "-mfpu=vfp", src, "-o", out}

		// launch the assembler (ignore stdin)
		if err := n.ExecSimple(cmd, nil, false); err != nil {
			return
		}
	}

	// link
	cmd := []string{"ld"}
	cmd = append(cmd, objectFiles...)
	if err := n.ExecSimple(cmd, nil, false); err != nil {
		return
	}

	// run gdb
	n.ExecSimple([]string{"gdb", "a.out"}, stdin, true)
}
<commit_msg>add test action type, use make for all actions in arm<commit_after>package main

import (
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"regexp"
	"time"
)

func init() {
	problemTypeHandlers["armv6asm"] = map[string]nannyHandler{
		"grade": nannyHandler(armAsGrade),
		"test":  nannyHandler(armAsTest),
		"debug": nannyHandler(armAsDebug),
		"run":   nannyHandler(armAsRun),
	}
}

func armAsGrade(n *Nanny, args, options []string, files map[string]string, stdin io.Reader) {
	log.Printf("arm as gTest grade")
	parseXUnit(n, []string{"make", "grade"}, nil, "test_detail.xml")
}

func armAsTest(n *Nanny, args, options []string, files map[string]string, stdin io.Reader) {
	log.Printf("arm as gTest test")
	n.ExecSimple([]string{"make", "test"}, stdin, true)
}

func armAsDebug(n *Nanny, args, options []string, files map[string]string, stdin io.Reader) {
	log.Printf("arm as gdb")
	n.ExecSimple([]string{"make", "debug"}, stdin, true)
}

func armAsRun(n *Nanny, args, options []string, files map[string]string, stdin io.Reader) {
	log.Printf("arm run")
	n.ExecSimple([]string{"make", "run"}, stdin, true)
}

// XUnit types
type XUnitProgram struct {
	XMLName  xml.Name     `xml:"testsuites"`
	Name     string       `xml:"name,attr"`
	Tests    int          `xml:"tests,attr"`
	Failures int          `xml:"failures,attr"`
	Disabled int          `xml:"disabled,attr"`
	Errors   int          `xml:"errors,attr"`
	Time     float64      `xml:"time,attr"`
	Cases    []*XUnitCase `xml:"testsuite"`
}

type XUnitCase struct {
	Name      string           `xml:"name,attr"`
	Tests     int              `xml:"tests,attr"`
	Failures  int              `xml:"failures,attr"`
	Disabled  int              `xml:"disabled,attr"`
	Errors    int              `xml:"errors,attr"`
	Time      float64          `xml:"time,attr"`
	Functions []*XUnitFunction `xml:"testcase"`
}

type XUnitFunction struct {
	Name      string        `xml:"name,attr"`
	Status    string        `xml:"status,attr"`
	Time      float64       `xml:"time,attr"`
	ClassName string        `xml:"classname,attr"`
	Failure   *XUnitFailure `xml:"failure"`
}

type XUnitFailure struct {
	Message string `xml:"message,attr"`
	Type    string `xml:"type,attr"`
	Body    string `xml:",chardata"`
}

func parseXUnit(n *Nanny, cmd []string, stdin io.Reader, filename string) {
	// run tests with XML output
	_, _, _, status, err := n.Exec(cmd, stdin, false)
	if err != nil {
		n.ReportCard.LogAndFailf("Error running unit tests: %v", err)
		return
	}

	// did it end in a segfault?
	if status > 127 {
		n.ReportCard.LogAndFailf("Crashed with exit status %d while running unit tests", status)
		return
	}
	n.ReportCard.Passed = status == 0

	// parse the test results
	xmlfiles, err := n.GetFiles([]string{filename})
	if err != nil {
		n.ReportCard.LogAndFailf("Unit test failed: unable to read results")
		return
	}
	results := new(XUnitProgram)
	if err = xml.Unmarshal([]byte(xmlfiles[filename]), results); err != nil {
		n.ReportCard.LogAndFailf("error parsing unit test results: %v", err)
		return
	}

	// form a report card
	fails := results.Failures + results.Disabled + results.Errors
	n.ReportCard.Note = fmt.Sprintf("Passed %d/%d tests in %v",
		results.Tests-fails, results.Tests, time.Since(n.Start))
	n.ReportCard.Passed = n.ReportCard.Passed && results.Tests > 0 && fails == 0 && status == 0

	context := regexp.MustCompile(`^(tests/[^:/]*:\d+)`)

	// prepare a report for each test case
	for _, testcase := range results.Cases {
		for _, test := range testcase.Functions {
			name := fmt.Sprintf("%s -> %s", test.ClassName, test.Name)
			if test.Status == "run" && test.Failure == nil {
				n.ReportCard.AddPassedResult(name, "")
			} else {
				details, ctx := "", ""
				if test.Failure != nil {
					details = htmlEscapePre(test.Failure.Body)

					// extract context
					groups := context.FindStringSubmatch(test.Failure.Body)
					if len(groups) > 1 {
						ctx = groups[1]
					}
				}
				n.ReportCard.AddFailedResult(name, details, ctx)
			}
		}
	}
}
<|endoftext|>


<commit_before>// Package multilock provide A simple method to lock base on a holder
package multilock

import (
	"errors"
	"sync"
)

type refCounter struct {
	counter int
	lock    sync.RWMutex
}
type lockMap map[interface{}]*refCounter

// MultiLock is the main interface for lock base on holder
type MultiLock interface {
	// Lock base on the holder
	Lock(interface{})

	// RLock lock the rw for reading
	RLock(interface{})

	// Unlock the holder
	Unlock(interface{})

	// RUnlock the the read lock
	RUnlock(interface{})
}

// A multi lock type
type lock struct {
	inUse lockMap
	l     *sync.Mutex
	pool  *sync.Pool
}

func (l *lock) Lock(holder interface{}) {
	m := l.getLocker(holder)
	m.lock.Lock()
	l.l.Lock()
	defer l.l.Unlock()
	m.counter++
}

func (l *lock) RLock(holder interface{}) {
	m := l.getLocker(holder)
	m.lock.RLock()
	l.l.Lock()
	defer l.l.Unlock()
	m.counter++
}

func (l *lock) Unlock(holder interface{}) {
	m := l.getLocker(holder)
	m.lock.Unlock()
	l.putBackInPool(holder, m)
}

func (l *lock) RUnlock(holder interface{}) {
	m := l.getLocker(holder)
	m.lock.RUnlock()
	l.putBackInPool(holder, m)
}

func (l *lock) putBackInPool(holder interface{}, m *refCounter) {
	l.l.Lock()
	defer l.l.Unlock()

	m.counter--
	if m.counter <= 0 {
		l.pool.Put(m)
		delete(l.inUse, holder)
	}
}

func (l *lock) getLocker(holder interface{}) *refCounter {
	l.l.Lock()
	defer l.l.Unlock()
	res, ok := l.inUse[holder]
	if !ok {
		p := l.pool.Get()
		res, ok = p.(*refCounter)
		if !ok {
			panic(errors.New("the pool return invalid result"))
		}

		l.inUse[holder] = res
	}
	return res
}

// NewMultiLock create a new multi lock
func NewMultiLock() MultiLock {
	return &lock{
		make(lockMap),
		&sync.Mutex{},
		&sync.Pool{
			New: func() interface{} {
				return &refCounter{0, sync.RWMutex{}}
			},
		},
	}
}
<commit_msg>fix wrong usage of lock<commit_after>// Package multilock provide A simple method to lock base on a holder
package multilock

import (
	"errors"
	"sync"
)

type refCounter struct {
	counter int
	lock    sync.Mutex
}
type lockMap map[interface{}]*refCounter

// MultiLock is the main interface for lock base on holder
type MultiLock interface {
	// Lock base on the holder
	Lock(interface{})

	// RLock lock the rw for reading
	RLock(interface{})

	// Unlock the holder
	Unlock(interface{})

	// RUnlock the the read lock
	RUnlock(interface{})
}

// A multi lock type
type lock struct {
	inUse lockMap
	l     *sync.Mutex
	pool  *sync.Pool
}

func (l *lock) Lock(holder interface{}) {
	m := l.getLocker(holder)
	m.lock.Lock()
	l.l.Lock()
	defer l.l.Unlock()
	m.counter++
}

func (l *lock) RLock(holder interface{}) {
	m := l.getLocker(holder)
	m.lock.Lock()
	l.l.Lock()
	defer l.l.Unlock()
	m.counter++
}

func (l *lock) Unlock(holder interface{}) {
	m := l.getLocker(holder)
	m.lock.Unlock()
	l.putBackInPool(holder, m)
}

func (l *lock) RUnlock(holder interface{}) {
	m := l.getLocker(holder)
	m.lock.Unlock()
	l.putBackInPool(holder, m)
}

func (l *lock) putBackInPool(holder interface{}, m *refCounter) {
	l.l.Lock()
	defer l.l.Unlock()

	m.counter--
	if m.counter <= 0 {
		l.pool.Put(m)
		delete(l.inUse, holder)
	}
}

func (l *lock) getLocker(holder interface{}) *refCounter {
	l.l.Lock()
	defer l.l.Unlock()
	res, ok := l.inUse[holder]
	if !ok {
		p := l.pool.Get()
		res, ok = p.(*refCounter)
		if !ok {
			panic(errors.New("the pool return invalid result"))
		}

		l.inUse[holder] = res
	}
	return res
}

// NewMultiLock create a new multi lock
func NewMultiLock() MultiLock {
	return &lock{
		make(lockMap),
		&sync.Mutex{},
		&sync.Pool{
			New: func() interface{} {
				return &refCounter{0, sync.Mutex{}}
			},
		},
	}
}
<|endoftext|>


<commit_before>package windows

import (
	"encoding/binary"
	"os"
	"runtime"
	"syscall"
	"testing"
	"unsafe"

	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
)

func TestGetProcessImageFileName(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}

	filename, err := GetProcessImageFileName(h)
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("GetProcessImageFileName: %v", filename)
}

func TestGetProcessMemoryInfo(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}
	counters, err := GetProcessMemoryInfo(h)
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("GetProcessMemoryInfo: ProcessMemoryCountersEx=%+v", counters)
}

func TestGetLogicalDriveStrings(t *testing.T) {
	drives, err := GetLogicalDriveStrings()
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("GetLogicalDriveStrings: %v", drives)
}

func TestGetDriveType(t *testing.T) {
	drives, err := GetLogicalDriveStrings()
	if err != nil {
		t.Fatal(err)
	}

	for _, drive := range drives {
		dt, err := GetDriveType(drive)
		if err != nil {
			t.Fatal(err)
		}

		t.Logf("GetDriveType: drive=%v, type=%v", drive, dt)
	}
}

func TestGetSystemTimes(t *testing.T) {
	idle, kernel, user, err := GetSystemTimes()
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("GetSystemTimes: idle=%v, kernel=%v, user=%v", idle, kernel, user)
}

func TestGlobalMemoryStatusEx(t *testing.T) {
	mem, err := GlobalMemoryStatusEx()
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("GlobalMemoryStatusEx: %+v", mem)
}

func TestEnumProcesses(t *testing.T) {
	pids, err := EnumProcesses()
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("EnumProcesses: %v", pids)
}

func TestGetDiskFreeSpaceEx(t *testing.T) {
	drives, err := GetLogicalDriveStrings()
	if err != nil {
		t.Fatal(err)
	}

	for _, drive := range drives {
		dt, err := GetDriveType(drive)
		if err != nil {
			t.Fatal(err)
		}

		// Ignore CDROM drives. They return an error if the drive is emtpy.
		if dt != DRIVE_CDROM {
			free, total, totalFree, err := GetDiskFreeSpaceEx(drive)
			if err != nil {
				t.Fatal(err)
			}
			t.Logf("GetDiskFreeSpaceEx: %v, %v, %v", free, total, totalFree)
		}
	}
}

func TestGetWindowsVersion(t *testing.T) {
	ver := GetWindowsVersion()
	assert.True(t, ver.Major >= 5)
	t.Logf("GetWindowsVersion: %+v", ver)
}

func TestCreateToolhelp32Snapshot(t *testing.T) {
	handle, err := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
	if err != nil {
		t.Fatal(err)
	}
	defer syscall.CloseHandle(syscall.Handle(handle))

	// Iterate over the snapshots until our PID is found.
	pid := uint32(syscall.Getpid())
	for {
		process, err := Process32Next(handle)
		if errors.Cause(err) == syscall.ERROR_NO_MORE_FILES {
			break
		}
		if err != nil {
			t.Fatal(err)
		}

		t.Logf("CreateToolhelp32Snapshot: ProcessEntry32=%v", process)

		if process.ProcessID == pid {
			assert.EqualValues(t, syscall.Getppid(), process.ParentProcessID)
			return
		}
	}

	assert.Fail(t, "Snapshot not found for PID=%v", pid)
}

func TestNtQuerySystemProcessorPerformanceInformation(t *testing.T) {
	cpus, err := NtQuerySystemProcessorPerformanceInformation()
	if err != nil {
		t.Fatal(err)
	}

	assert.Len(t, cpus, runtime.NumCPU())

	for i, cpu := range cpus {
		assert.NotZero(t, cpu.IdleTime)
		assert.NotZero(t, cpu.KernelTime)
		assert.NotZero(t, cpu.UserTime)

		t.Logf("CPU=%v SystemProcessorPerformanceInformation=%v", i, cpu)
	}
}

func TestNtQueryProcessBasicInformation(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}

	info, err := NtQueryProcessBasicInformation(h)
	if err != nil {
		t.Fatal(err)
	}
	defer syscall.CloseHandle(h)
	assert.EqualValues(t, os.Getpid(), info.UniqueProcessID)
	assert.EqualValues(t, os.Getppid(), info.InheritedFromUniqueProcessID)

	t.Logf("NtQueryProcessBasicInformation: %+v", info)
}

func TestGetTickCount64(t *testing.T) {
	uptime, err := GetTickCount64()
	if err != nil {
		t.Fatal(err)
	}
	assert.NotZero(t, uptime)
}

func TestGetUserProcessParams(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}
	info, err := NtQueryProcessBasicInformation(h)
	if err != nil {
		t.Fatal(err)
	}
	userProc, err := GetUserProcessParams(h, info)
	if err != nil {
		t.Fatal(err)
	}
	defer syscall.CloseHandle(h)
	assert.NoError(t, err)
	assert.EqualValues(t, os.Getpid(), info.UniqueProcessID)
	assert.EqualValues(t, os.Getppid(), info.InheritedFromUniqueProcessID)
	assert.NotEmpty(t, userProc.CommandLine)
}

func TestGetUserProcessParamsInvalidHandle(t *testing.T) {
	var handle syscall.Handle
	var info = ProcessBasicInformation{PebBaseAddress: uintptr(0)}
	userProc, err := GetUserProcessParams(handle, info)
	assert.EqualValues(t, err.Error(), "The handle is invalid.")
	assert.Empty(t, userProc)
}

func TestReadProcessUnicodeString(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}
	defer syscall.CloseHandle(h)
	info, err := NtQueryProcessBasicInformation(h)
	if err != nil {
		t.Fatal(err)
	}
	userProc, err := GetUserProcessParams(h, info)
	if err != nil {
		t.Fatal(err)
	}
	read, err := ReadProcessUnicodeString(h, &userProc.CommandLine)
	if err != nil {
		t.Fatal(err)
	}
	assert.NoError(t, err)
	assert.NotEmpty(t, read)
}

const currentProcessHandle = syscall.Handle(^uintptr(0))

func TestReadProcessUnicodeStringTerminator(t *testing.T) {
	data := []byte{'H', 0, 'E', 0, 'L', 0, 'L', 0, 'O', 0, 0, 0}
	for n := len(data); n >= 0; n-- {
		us := UnicodeString{
			Buffer: uintptr(unsafe.Pointer(&data[0])),
			Size:   uint16(n),
		}
		read, err := ReadProcessUnicodeString(currentProcessHandle, &us)
		if err != nil {
			t.Fatal(err)
		}
		nRead := len(read)
		// Strings must match
		assert.True(t, nRead >= n)
		assert.Equal(t, data[:n], read[:n])
		// result is an array of uint16, can't have odd length.
		assert.True(t, nRead&1 == 0)
		// Must include a zero terminator at the end.
		assert.True(t, nRead >= 2)
		assert.Zero(t, read[nRead-1])
		assert.Zero(t, read[nRead-2])
	}
}

func TestReadProcessUnicodeStringInvalidHandle(t *testing.T) {
	var handle syscall.Handle
	var cmd = UnicodeString{Size: 5, MaximumLength: 400, Buffer: 400}
	read, err := ReadProcessUnicodeString(handle, &cmd)
	assert.EqualValues(t, err.Error(), "The handle is invalid.")
	assert.Empty(t, read)
}

func TestByteSliceToStringSlice(t *testing.T) {
	cmd := syscall.GetCommandLine()
	b := make([]byte, unsafe.Sizeof(cmd))
	binary.LittleEndian.PutUint16(b, *cmd)
	hes, err := ByteSliceToStringSlice(b)
	assert.NoError(t, err)
	assert.NotEmpty(t, hes)
}

func TestByteSliceToStringSliceEmptyBytes(t *testing.T) {
	b := make([]byte, 0)
	cmd, err := ByteSliceToStringSlice(b)
	assert.NoError(t, err)
	assert.Empty(t, cmd)
}

func mkUtf16bytes(s string) []byte {
	n := len(s)
	b := make([]byte, n*2)
	for idx, val := range s {
		*(*uint16)(unsafe.Pointer(&b[idx*2])) = uint16(val)
	}
	return b
}

func TestByteSliceToStringSliceNotTerminated(t *testing.T) {
	b := mkUtf16bytes("Hello World")
	cmd, err := ByteSliceToStringSlice(b)
	assert.NoError(t, err)
	assert.Len(t, cmd, 2)
	assert.Equal(t, "Hello", cmd[0])
	assert.Equal(t, "World", cmd[1])
}

func TestByteSliceToStringSliceNotOddSize(t *testing.T) {
	b := mkUtf16bytes("BAD")[:5]
	cmd, err := ByteSliceToStringSlice(b)
	assert.NoError(t, err)
	assert.Len(t, cmd, 1)
	// Odd character is dropped
	assert.Equal(t, "BA", cmd[0])
}

func TestReadProcessMemory(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}
	info, err := NtQueryProcessBasicInformation(h)
	if err != nil {
		t.Fatal(err)
	}
	pebSize := 0x20 + 8
	if unsafe.Sizeof(uintptr(0)) == 4 {
		pebSize = 0x10 + 8
	}
	defer syscall.CloseHandle(h)
	peb := make([]byte, pebSize)
	nRead, err := ReadProcessMemory(h, info.PebBaseAddress, peb)
	assert.NoError(t, err)
	assert.NotEmpty(t, nRead)
	assert.EqualValues(t, nRead, uintptr(pebSize))
}

// A zero-byte read is a no-op, no error is returned.
func TestReadProcessMemoryZeroByteRead(t *testing.T) {
	peb := make([]byte, 0)
	var h syscall.Handle
	var address uintptr
	nRead, err := ReadProcessMemory(h, address, peb)
	assert.NoError(t, err)
	assert.Empty(t, nRead)
}

func TestReadProcessMemoryInvalidHandler(t *testing.T) {
	peb := make([]byte, 10)
	var h syscall.Handle
	var address uintptr
	nRead, err := ReadProcessMemory(h, address, peb)
	assert.Error(t, err)
	assert.EqualValues(t, err.Error(), "The handle is invalid.")
	assert.Empty(t, nRead)
}

func TestGetAccessPaths(t *testing.T) {
	paths, err := GetAccessPaths()
	if err != nil {
		t.Fatal(err)
	}
	assert.NotEmpty(t, paths)
	assert.True(t, len(paths) >= 1)
}

func TestGetVolumes(t *testing.T) {
	paths, err := GetVolumes()
	if err != nil {
		t.Fatal(err)
	}
	assert.NotEmpty(t, paths)
	assert.True(t, len(paths) >= 1)
}

func TestGetVolumePathsForVolume(t *testing.T) {
	volumes, err := GetVolumes()
	if err != nil {
		t.Fatal(err)
	}
	assert.NotNil(t, volumes)
	assert.True(t, len(volumes) >= 1)
	volumePath, err := GetVolumePathsForVolume(volumes[0])
	if err != nil {
		t.Fatal(err)
	}
	assert.NotNil(t, volumePath)
	assert.True(t, len(volumePath) >= 1)
}
<commit_msg>Add smoke test<commit_after>package windows

import (
	"encoding/binary"
	"os"
	"runtime"
	"syscall"
	"testing"
	"unsafe"

	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
)

func TestGetProcessImageFileName(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}

	filename, err := GetProcessImageFileName(h)
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("GetProcessImageFileName: %v", filename)
}

func TestGetProcessMemoryInfo(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}
	counters, err := GetProcessMemoryInfo(h)
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("GetProcessMemoryInfo: ProcessMemoryCountersEx=%+v", counters)
}

func TestGetLogicalDriveStrings(t *testing.T) {
	drives, err := GetLogicalDriveStrings()
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("GetLogicalDriveStrings: %v", drives)
}

func TestGetDriveType(t *testing.T) {
	drives, err := GetLogicalDriveStrings()
	if err != nil {
		t.Fatal(err)
	}

	for _, drive := range drives {
		dt, err := GetDriveType(drive)
		if err != nil {
			t.Fatal(err)
		}

		t.Logf("GetDriveType: drive=%v, type=%v", drive, dt)
	}
}

func TestGetSystemTimes(t *testing.T) {
	idle, kernel, user, err := GetSystemTimes()
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("GetSystemTimes: idle=%v, kernel=%v, user=%v", idle, kernel, user)
}

func TestGlobalMemoryStatusEx(t *testing.T) {
	mem, err := GlobalMemoryStatusEx()
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("GlobalMemoryStatusEx: %+v", mem)
}

func TestEnumProcesses(t *testing.T) {
	pids, err := EnumProcesses()
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("EnumProcesses: %v", pids)
}

func TestGetDiskFreeSpaceEx(t *testing.T) {
	drives, err := GetLogicalDriveStrings()
	if err != nil {
		t.Fatal(err)
	}

	for _, drive := range drives {
		dt, err := GetDriveType(drive)
		if err != nil {
			t.Fatal(err)
		}

		// Ignore CDROM drives. They return an error if the drive is emtpy.
		if dt != DRIVE_CDROM {
			free, total, totalFree, err := GetDiskFreeSpaceEx(drive)
			if err != nil {
				t.Fatal(err)
			}
			t.Logf("GetDiskFreeSpaceEx: %v, %v, %v", free, total, totalFree)
		}
	}
}

func TestGetVolumeInfo(t *testing.T) {
	drives, err := GetLogicalDriveStrings()
	if err != nil {
		t.Fatal(err)
	}

	for _, drive := range drives {
		volumeType, err := GetVolumeInfo(drive)
		if err != nil {
			t.Fatal(err)
		}
		t.Logf("GetVolumeInfo: %v - %v", drive, volumeType)
	}
}

func TestGetWindowsVersion(t *testing.T) {
	ver := GetWindowsVersion()
	assert.True(t, ver.Major >= 5)
	t.Logf("GetWindowsVersion: %+v", ver)
}

func TestCreateToolhelp32Snapshot(t *testing.T) {
	handle, err := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
	if err != nil {
		t.Fatal(err)
	}
	defer syscall.CloseHandle(syscall.Handle(handle))

	// Iterate over the snapshots until our PID is found.
	pid := uint32(syscall.Getpid())
	for {
		process, err := Process32Next(handle)
		if errors.Cause(err) == syscall.ERROR_NO_MORE_FILES {
			break
		}
		if err != nil {
			t.Fatal(err)
		}

		t.Logf("CreateToolhelp32Snapshot: ProcessEntry32=%v", process)

		if process.ProcessID == pid {
			assert.EqualValues(t, syscall.Getppid(), process.ParentProcessID)
			return
		}
	}

	assert.Fail(t, "Snapshot not found for PID=%v", pid)
}

func TestNtQuerySystemProcessorPerformanceInformation(t *testing.T) {
	cpus, err := NtQuerySystemProcessorPerformanceInformation()
	if err != nil {
		t.Fatal(err)
	}

	assert.Len(t, cpus, runtime.NumCPU())

	for i, cpu := range cpus {
		assert.NotZero(t, cpu.IdleTime)
		assert.NotZero(t, cpu.KernelTime)
		assert.NotZero(t, cpu.UserTime)

		t.Logf("CPU=%v SystemProcessorPerformanceInformation=%v", i, cpu)
	}
}

func TestNtQueryProcessBasicInformation(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}

	info, err := NtQueryProcessBasicInformation(h)
	if err != nil {
		t.Fatal(err)
	}
	defer syscall.CloseHandle(h)
	assert.EqualValues(t, os.Getpid(), info.UniqueProcessID)
	assert.EqualValues(t, os.Getppid(), info.InheritedFromUniqueProcessID)

	t.Logf("NtQueryProcessBasicInformation: %+v", info)
}

func TestGetTickCount64(t *testing.T) {
	uptime, err := GetTickCount64()
	if err != nil {
		t.Fatal(err)
	}
	assert.NotZero(t, uptime)
}

func TestGetUserProcessParams(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}
	info, err := NtQueryProcessBasicInformation(h)
	if err != nil {
		t.Fatal(err)
	}
	userProc, err := GetUserProcessParams(h, info)
	if err != nil {
		t.Fatal(err)
	}
	defer syscall.CloseHandle(h)
	assert.NoError(t, err)
	assert.EqualValues(t, os.Getpid(), info.UniqueProcessID)
	assert.EqualValues(t, os.Getppid(), info.InheritedFromUniqueProcessID)
	assert.NotEmpty(t, userProc.CommandLine)
}

func TestGetUserProcessParamsInvalidHandle(t *testing.T) {
	var handle syscall.Handle
	var info = ProcessBasicInformation{PebBaseAddress: uintptr(0)}
	userProc, err := GetUserProcessParams(handle, info)
	assert.EqualValues(t, err.Error(), "The handle is invalid.")
	assert.Empty(t, userProc)
}

func TestReadProcessUnicodeString(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}
	defer syscall.CloseHandle(h)
	info, err := NtQueryProcessBasicInformation(h)
	if err != nil {
		t.Fatal(err)
	}
	userProc, err := GetUserProcessParams(h, info)
	if err != nil {
		t.Fatal(err)
	}
	read, err := ReadProcessUnicodeString(h, &userProc.CommandLine)
	if err != nil {
		t.Fatal(err)
	}
	assert.NoError(t, err)
	assert.NotEmpty(t, read)
}

const currentProcessHandle = syscall.Handle(^uintptr(0))

func TestReadProcessUnicodeStringTerminator(t *testing.T) {
	data := []byte{'H', 0, 'E', 0, 'L', 0, 'L', 0, 'O', 0, 0, 0}
	for n := len(data); n >= 0; n-- {
		us := UnicodeString{
			Buffer: uintptr(unsafe.Pointer(&data[0])),
			Size:   uint16(n),
		}
		read, err := ReadProcessUnicodeString(currentProcessHandle, &us)
		if err != nil {
			t.Fatal(err)
		}
		nRead := len(read)
		// Strings must match
		assert.True(t, nRead >= n)
		assert.Equal(t, data[:n], read[:n])
		// result is an array of uint16, can't have odd length.
		assert.True(t, nRead&1 == 0)
		// Must include a zero terminator at the end.
		assert.True(t, nRead >= 2)
		assert.Zero(t, read[nRead-1])
		assert.Zero(t, read[nRead-2])
	}
}

func TestReadProcessUnicodeStringInvalidHandle(t *testing.T) {
	var handle syscall.Handle
	var cmd = UnicodeString{Size: 5, MaximumLength: 400, Buffer: 400}
	read, err := ReadProcessUnicodeString(handle, &cmd)
	assert.EqualValues(t, err.Error(), "The handle is invalid.")
	assert.Empty(t, read)
}

func TestByteSliceToStringSlice(t *testing.T) {
	cmd := syscall.GetCommandLine()
	b := make([]byte, unsafe.Sizeof(cmd))
	binary.LittleEndian.PutUint16(b, *cmd)
	hes, err := ByteSliceToStringSlice(b)
	assert.NoError(t, err)
	assert.NotEmpty(t, hes)
}

func TestByteSliceToStringSliceEmptyBytes(t *testing.T) {
	b := make([]byte, 0)
	cmd, err := ByteSliceToStringSlice(b)
	assert.NoError(t, err)
	assert.Empty(t, cmd)
}

func mkUtf16bytes(s string) []byte {
	n := len(s)
	b := make([]byte, n*2)
	for idx, val := range s {
		*(*uint16)(unsafe.Pointer(&b[idx*2])) = uint16(val)
	}
	return b
}

func TestByteSliceToStringSliceNotTerminated(t *testing.T) {
	b := mkUtf16bytes("Hello World")
	cmd, err := ByteSliceToStringSlice(b)
	assert.NoError(t, err)
	assert.Len(t, cmd, 2)
	assert.Equal(t, "Hello", cmd[0])
	assert.Equal(t, "World", cmd[1])
}

func TestByteSliceToStringSliceNotOddSize(t *testing.T) {
	b := mkUtf16bytes("BAD")[:5]
	cmd, err := ByteSliceToStringSlice(b)
	assert.NoError(t, err)
	assert.Len(t, cmd, 1)
	// Odd character is dropped
	assert.Equal(t, "BA", cmd[0])
}

func TestReadProcessMemory(t *testing.T) {
	h, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, false, uint32(syscall.Getpid()))
	if err != nil {
		t.Fatal(err)
	}
	info, err := NtQueryProcessBasicInformation(h)
	if err != nil {
		t.Fatal(err)
	}
	pebSize := 0x20 + 8
	if unsafe.Sizeof(uintptr(0)) == 4 {
		pebSize = 0x10 + 8
	}
	defer syscall.CloseHandle(h)
	peb := make([]byte, pebSize)
	nRead, err := ReadProcessMemory(h, info.PebBaseAddress, peb)
	assert.NoError(t, err)
	assert.NotEmpty(t, nRead)
	assert.EqualValues(t, nRead, uintptr(pebSize))
}

// A zero-byte read is a no-op, no error is returned.
func TestReadProcessMemoryZeroByteRead(t *testing.T) {
	peb := make([]byte, 0)
	var h syscall.Handle
	var address uintptr
	nRead, err := ReadProcessMemory(h, address, peb)
	assert.NoError(t, err)
	assert.Empty(t, nRead)
}

func TestReadProcessMemoryInvalidHandler(t *testing.T) {
	peb := make([]byte, 10)
	var h syscall.Handle
	var address uintptr
	nRead, err := ReadProcessMemory(h, address, peb)
	assert.Error(t, err)
	assert.EqualValues(t, err.Error(), "The handle is invalid.")
	assert.Empty(t, nRead)
}

func TestGetAccessPaths(t *testing.T) {
	paths, err := GetAccessPaths()
	if err != nil {
		t.Fatal(err)
	}
	assert.NotEmpty(t, paths)
	assert.True(t, len(paths) >= 1)
}

func TestGetVolumes(t *testing.T) {
	paths, err := GetVolumes()
	if err != nil {
		t.Fatal(err)
	}
	assert.NotEmpty(t, paths)
	assert.True(t, len(paths) >= 1)
}

func TestGetVolumePathsForVolume(t *testing.T) {
	volumes, err := GetVolumes()
	if err != nil {
		t.Fatal(err)
	}
	assert.NotNil(t, volumes)
	assert.True(t, len(volumes) >= 1)
	volumePath, err := GetVolumePathsForVolume(volumes[0])
	if err != nil {
		t.Fatal(err)
	}
	assert.NotNil(t, volumePath)
	assert.True(t, len(volumePath) >= 1)
}
<|endoftext|>


<commit_before>package clusterf
/*
 * Internal service state, maintained from config changes.
 */

import (
    "qmsk.net/clusterf/config"
    "log"
)

type Service struct {
    Name        string

    Frontend    *config.ServiceFrontend
    Backends    map[string]config.ServiceBackend

    driverFrontend  *ipvsFrontend
    driverBackends  map[string]*ipvsBackend
}

func newService(name string, driver *IPVSDriver) *Service {
    return &Service{
        Name:           name,
        Backends:       make(map[string]config.ServiceBackend),

        driverFrontend: driver.newFrontend(),
    }
}

func (self *Service) driverError(err error) {
    log.Printf("cluster:Service %s: Error: %s\n", self.Name, err)
}

/* Configuration actions */
func (self *Service) configFrontend(action config.Action, frontendConfig *config.ConfigServiceFrontend) {
    frontend := frontendConfig.Frontend

    log.Printf("clusterf:Service %s: Frontend: %s %+v <- %+v\n", self.Name, action, frontend, self.Frontend)

    switch action {
    case config.NewConfig:
        self.newFrontend(frontend)

        self.Frontend = &frontend // XXX: copy?

    case config.SetConfig:
        if *self.Frontend != frontend {
            self.setFrontend(frontend)
        }

        self.Frontend = &frontend // XXX: copy?

    case config.DelConfig:
        self.delFrontend()

        self.Frontend = nil
    }
}

func (self *Service) configBackend(backendName string, action config.Action, backendConfig *config.ConfigServiceBackend) {
    log.Printf("clusterf:Service %s: Backend %s: %s %+v <- %+v\n", self.Name, backendName, action, backendConfig.Backend, self.Backends[backendName])

    switch action {
    case config.NewConfig:
        self.newBackend(backendName, backendConfig.Backend)

        self.Backends[backendName] = backendConfig.Backend

    case config.SetConfig:
        if self.Backends[backendName] != backendConfig.Backend {
            self.setBackend(backendName, backendConfig.Backend)

            self.Backends[backendName] = backendConfig.Backend
        }

    case config.DelConfig:
        self.delBackend(backendName)

        delete(self.Backends, backendName)
    }
}

/* Frontend actions */
func (self *Service) newFrontend(frontend config.ServiceFrontend) {
    if err := self.driverFrontend.add(frontend); err != nil {
        self.driverError(err)
    } else {
        for backendName, backend := range self.Backends {
            self.newBackend(backendName, backend)
        }
    }
}

func (self *Service) setFrontend(frontend config.ServiceFrontend) {
    // TODO: something more smooth...
    self.delFrontend()
    self.newFrontend(frontend)
}

func (self *Service) delFrontend() {
    // del'ing the frontend will also remove all backend state
    if err := self.driverFrontend.del(); err != nil {
        self.driverError(err)
    }
}

/* Backend actions */
func (self *Service) newBackend(name string, backend config.ServiceBackend) {
    self.driverBackends[name] = self.driverFrontend.newBackend()

    if err := self.driverBackends[name].add(backend); err != nil {
        self.driverError(err)
    }
}

func (self *Service) setBackend(name string, backend config.ServiceBackend) {
    if err := self.driverBackends[name].set(backend); err != nil {
        self.driverError(err)
    }
}

func (self *Service) delBackend(name string) {
    if err := self.driverBackends[name].del(); err != nil {
        self.driverError(err)
    }

    delete(self.driverBackends, name)
}
<commit_msg>clusterf/service: fix nil map; logging<commit_after>package clusterf
/*
 * Internal service state, maintained from config changes.
 */

import (
    "qmsk.net/clusterf/config"
    "log"
)

type Service struct {
    Name        string

    Frontend    *config.ServiceFrontend
    Backends    map[string]config.ServiceBackend

    driverFrontend  *ipvsFrontend
    driverBackends  map[string]*ipvsBackend
}

func newService(name string, driver *IPVSDriver) *Service {
    return &Service{
        Name:           name,
        Backends:       make(map[string]config.ServiceBackend),

        driverFrontend: driver.newFrontend(),
        driverBackends: make(map[string]*ipvsBackend),
    }
}

func (self *Service) driverError(err error) {
    log.Printf("cluster:Service %s: Error: %s\n", self.Name, err)
}

/* Configuration actions */
func (self *Service) configFrontend(action config.Action, frontendConfig *config.ConfigServiceFrontend) {
    frontend := frontendConfig.Frontend

    log.Printf("clusterf:Service %s: Frontend: %s %+v <- %+v\n", self.Name, action, frontend, self.Frontend)

    switch action {
    case config.NewConfig:
        self.newFrontend(frontend)

        self.Frontend = &frontend // XXX: copy?

    case config.SetConfig:
        if *self.Frontend != frontend {
            self.setFrontend(frontend)
        }

        self.Frontend = &frontend // XXX: copy?

    case config.DelConfig:
        self.delFrontend()

        self.Frontend = nil
    }
}

func (self *Service) configBackend(backendName string, action config.Action, backendConfig *config.ConfigServiceBackend) {
    log.Printf("clusterf:Service %s: Backend %s: %s %+v <- %+v\n", self.Name, backendName, action, backendConfig.Backend, self.Backends[backendName])

    switch action {
    case config.NewConfig:
        self.newBackend(backendName, backendConfig.Backend)

        self.Backends[backendName] = backendConfig.Backend

    case config.SetConfig:
        if self.Backends[backendName] != backendConfig.Backend {
            self.setBackend(backendName, backendConfig.Backend)

            self.Backends[backendName] = backendConfig.Backend
        }

    case config.DelConfig:
        self.delBackend(backendName)

        delete(self.Backends, backendName)
    }
}

/* Frontend actions */
func (self *Service) newFrontend(frontend config.ServiceFrontend) {
    log.Printf("clusterf:Service %s: new Frontend: %+v\n", self.Name, frontend)

    if err := self.driverFrontend.add(frontend); err != nil {
        self.driverError(err)
    } else {
        for backendName, backend := range self.Backends {
            self.newBackend(backendName, backend)
        }
    }
}

func (self *Service) setFrontend(frontend config.ServiceFrontend) {
    log.Printf("clusterf:Service %s: set Frontend: %+v\n", self.Name, frontend)

    // TODO: something more smooth...
    self.delFrontend()
    self.newFrontend(frontend)
}

func (self *Service) delFrontend() {
    log.Printf("clusterf:Service %s: del Frontend: %+v\n", self.Name, self.Frontend)

    // del'ing the frontend will also remove all backend state
    if err := self.driverFrontend.del(); err != nil {
        self.driverError(err)
    }
}

/* Backend actions */
func (self *Service) newBackend(backendName string, backend config.ServiceBackend) {
    log.Printf("clusterf:Service %s: new Backend %s: %+v\n", self.Name, backendName, backend)

    self.driverBackends[backendName] = self.driverFrontend.newBackend()

    if err := self.driverBackends[backendName].add(backend); err != nil {
        self.driverError(err)
    }
}

func (self *Service) setBackend(backendName string, backend config.ServiceBackend) {
    log.Printf("clusterf:Service %s: set Backend %s: %+v\n", self.Name, backendName, backend)

    if err := self.driverBackends[backendName].set(backend); err != nil {
        self.driverError(err)
    }
}

func (self *Service) delBackend(backendName string) {
    log.Printf("clusterf:Service %s: del Backend %s: %+v\n", self.Name, backendName, self.Backends[backendName])

    if err := self.driverBackends[backendName].del(); err != nil {
        self.driverError(err)
    }

    delete(self.driverBackends, backendName)
}
<|endoftext|>


<commit_before>package fbb

import "testing"

func TestSubjectDecode(t *testing.T) {
	raw := []byte{0x74, 0x65, 0x73, 0x74, 0x20, 0xe6, 0xf8, 0xe5} // test æøå

	// RMS Express compatibility test (encodes subject as ISO-8859-1)
	msg := &Message{Header: Header{"Subject": []string{string(raw)}}}
	if decoded := msg.Subject(); decoded != "test æøå" {
		t.Errorf("Subject with no word-encoding not decoded as ISO-8859-1.")
	}

	msg.Header["Subject"] = []string{"=?utf-8?q?=C2=A1Hola,_se=C3=B1or!?="}
	if decoded := msg.Subject(); decoded != "¡Hola, señor!" {
		t.Errorf("Subject with Q-encoded utf-8 not decoded correctly.")
	}

	msg.Header["Subject"] = []string{"=?ISO-8859-1?q?Test_=E6=F8=E5_abc?="}
	if decoded := msg.Subject(); decoded != "Test æøå abc" {
		t.Errorf("Subject with Q-encoded ISO-8859-1 not decoded correctly.")
	}
}

func TestSubjectEncode(t *testing.T) {
	msg := &Message{Header: make(Header, 1)}

	msg.SetSubject("Test æøå abc")
	if msg.Header["Subject"][0] != "=?ISO-8859-1?q?Test_=E6=F8=E5_abc?=" {
		t.Errorf("Subject not Q-encoded using ISO-8859-1.")
	}

	msg.SetSubject("Test 123 foo bar")
	if msg.Header["Subject"][0] != "Test 123 foo bar" {
		t.Errorf("ASCII-only subject modified on encode.")
	}
}

func TestSubjectRoundtrip(t *testing.T) {
	msg := &Message{Header: make(Header, 1)}

	str := "Hello, 世界"
	msg.SetSubject(str)

	if msg.Subject() != str {
		t.Errorf("Subject encode/decode roundtrip failed.")
	}
}
<commit_msg>fbb: Fix roundtrip test<commit_after>package fbb

import "testing"

func TestSubjectDecode(t *testing.T) {
	raw := []byte{0x74, 0x65, 0x73, 0x74, 0x20, 0xe6, 0xf8, 0xe5} // test æøå

	// RMS Express compatibility test (encodes subject as ISO-8859-1)
	msg := &Message{Header: Header{"Subject": []string{string(raw)}}}
	if decoded := msg.Subject(); decoded != "test æøå" {
		t.Errorf("Subject with no word-encoding not decoded as ISO-8859-1.")
	}

	msg.Header["Subject"] = []string{"=?utf-8?q?=C2=A1Hola,_se=C3=B1or!?="}
	if decoded := msg.Subject(); decoded != "¡Hola, señor!" {
		t.Errorf("Subject with Q-encoded utf-8 not decoded correctly.")
	}

	msg.Header["Subject"] = []string{"=?ISO-8859-1?q?Test_=E6=F8=E5_abc?="}
	if decoded := msg.Subject(); decoded != "Test æøå abc" {
		t.Errorf("Subject with Q-encoded ISO-8859-1 not decoded correctly.")
	}
}

func TestSubjectEncode(t *testing.T) {
	msg := &Message{Header: make(Header, 1)}

	msg.SetSubject("Test æøå abc")
	if msg.Header["Subject"][0] != "=?ISO-8859-1?q?Test_=E6=F8=E5_abc?=" {
		t.Errorf("Subject not Q-encoded using ISO-8859-1.")
	}

	msg.SetSubject("Test 123 foo bar")
	if msg.Header["Subject"][0] != "Test 123 foo bar" {
		t.Errorf("ASCII-only subject modified on encode.")
	}
}

func TestSubjectRoundtrip(t *testing.T) {
	msg := &Message{Header: make(Header, 1)}

	str := "Hello, æøå"
	msg.SetSubject(str)

	if msg.Subject() != str {
		t.Errorf("Subject encode/decode roundtrip failed. (%s)", msg.Subject())
	}
}
<|endoftext|>


<commit_before>package next

import (
	"github.com/aj0strow/pgschema/ab"
	"github.com/aj0strow/pgschema/db"
)

type AddColumn struct {
	Column *db.Column
}

func addColumns(columns []ab.ColumnMatch) []AddColumn {
	var xs []AddColumn
	for _, column := range columns {
		if column.B == nil {
			x := AddColumn{
				Column: column.A,
			}
			xs = append(xs, x)
		}
	}
	return xs
}

type DropColumn struct {
	Column *db.Column
}

func dropColumns(columns []ab.ColumnMatch) []DropColumn {
	var xs []DropColumn
	for _, column := range columns {
		if column.A == nil {
			x := DropColumn{
				Column: column.B,
			}
			xs = append(xs, x)
		}
	}
	return xs
}

type AlterColumn struct {
	Column      *db.Column
	SetDataType bool
	SetNotNull  bool
	DropNotNull bool
	SetDefault  bool
	DropDefault bool
}

func alterColumns(columns []ab.ColumnMatch) []AlterColumn {
	var xs []AlterColumn
	for _, column := range columns {
		if column.A != nil && column.B != nil {
			x := alterColumn(column.A, column.B)
			xs = append(xs, x)
		}
	}
	return xs
}

func alterColumn(a, b *db.Column) AlterColumn {
	return AlterColumn{
		Column:      a,
		SetNotNull:  setNotNull(a, b),
		DropNotNull: dropNotNull(a, b),
		SetDefault:  setDefault(a, b),
		DropDefault: dropDefault(a, b),
	}
}
<commit_msg>fix build issue<commit_after>package next

import (
	"github.com/aj0strow/pgschema/ab"
	"github.com/aj0strow/pgschema/db"
)

type AddColumn struct {
	*db.Column
}

func addColumns(columns []ab.ColumnMatch) []AddColumn {
	var xs []AddColumn
	for _, column := range columns {
		if column.B == nil {
			x := AddColumn{
				Column: column.A,
			}
			xs = append(xs, x)
		}
	}
	return xs
}

type DropColumn struct {
	*db.Column
}

func dropColumns(columns []ab.ColumnMatch) []DropColumn {
	var xs []DropColumn
	for _, column := range columns {
		if column.A == nil {
			x := DropColumn{
				Column: column.B,
			}
			xs = append(xs, x)
		}
	}
	return xs
}

type AlterColumn struct {
	*db.Column
	SetDataType bool
	SetNotNull  bool
	DropNotNull bool
	SetDefault  bool
	DropDefault bool
}

func alterColumns(columns []ab.ColumnMatch) []AlterColumn {
	var xs []AlterColumn
	for _, column := range columns {
		if column.A != nil && column.B != nil {
			x := alterColumn(column.A, column.B)
			xs = append(xs, x)
		}
	}
	return xs
}

func alterColumn(a, b *db.Column) AlterColumn {
	return AlterColumn{
		Column:      a,
		SetNotNull:  setNotNull(a, b),
		DropNotNull: dropNotNull(a, b),
		SetDefault:  setDefault(a, b),
		DropDefault: dropDefault(a, b),
	}
}
<|endoftext|>


<commit_before>package nflog

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"sync"
	"time"

	"github.com/golang/protobuf/ptypes"
	"github.com/matttproud/golang_protobuf_extensions/pbutil"
	pb "github.com/prometheus/alertmanager/nflog/nflogpb"
	"github.com/weaveworks/mesh"
)

var ErrNotFound = errors.New("not found")

// Log stores and serves information about notifications
// about byte-slice addressed alert objects to different receivers.
type Log interface {
	// The Log* methods store a notification log entry for
	// a fully qualified receiver and a given IDs identifying the
	// alert object.
	LogActive(r *pb.Receiver, key, hash []byte) error
	LogResolved(r *pb.Receiver, key, hash []byte) error

	// Query the log along the given Paramteres.
	//
	// TODO(fabxc):
	// - extend the interface by a `QueryOne` method?
	// - return an iterator rather than a materialized list?
	Query(p ...QueryParam) ([]*pb.Entry, error)
	// Delete log st along the given Parameters. Returns
	// the number of deleted st.
	Delete(p ...DeleteParam) (int, error)

	// Snapshot the current log state and return the number
	// of bytes written.
	Snapshot(w io.Writer) (int, error)
}

// query currently allows filtering by and/or receiver group key.
// It is configured via QueryParameter functions.
//
// TODO(fabxc): Future versions could allow querying a certain receiver
// group or a given time interval.
type query struct {
	recv     *pb.Receiver
	groupKey []byte
}

// QueryParam is a function that modifies a query to incorporate
// a set of parameters. Returns an error for invalid or conflicting
// parameters.
type QueryParam func(*query) error

// QReceiver adds a receiver parameter to a query.
func QReceiver(r *pb.Receiver) QueryParam {
	return func(q *query) error {
		q.recv = r
		return nil
	}
}

// QGroupKey adds a group key as querying argument.
func QGroupKey(gk []byte) QueryParam {
	return func(q *query) error {
		q.groupKey = gk
		return nil
	}
}

// delQuery holds parameters for a deletion query.
// TODO(fabxc): can this be consolidated with regular QueryParams?
type delQuery struct {
	// Delete log st that are expired. Does NOT delete
	// unexpired st if set to false.
	expired bool
}

// DeleteParam is a function that modifies parameters of a delete request.
// Returns an error for invalid of conflicting parameters.
type DeleteParam func(*delQuery) error

// DExpired adds a parameter to delete expired log st.
func DExpired() DeleteParam {
	return func(d *delQuery) error {
		d.expired = true
		return nil
	}
}

type nlog struct {
	retention time.Duration
	now       func() time.Time

	gossip mesh.Gossip // A gossip channel for sharing log state.

	// For now we only store the most recently added log entry.
	// The key is a serialized concatenation of group key and receiver.
	// Currently our memory state is equivalent to the mesh.GossipData
	// representation. This may change in the future as we support history
	// and indexing.
	mtx sync.RWMutex
	st  gossipData
}

// Option configures a new Log implementation.
type Option func(*nlog) error

// WithMesh registers the log with a mesh network with which
// the log state will be shared.
func WithMesh(create func(g mesh.Gossiper) mesh.Gossip) Option {
	return func(l *nlog) error {
		l.gossip = create(l)
		return nil
	}
}

// WithRetention sets the retention time for log st.
func WithRetention(d time.Duration) Option {
	return func(l *nlog) error {
		l.retention = d
		return nil
	}
}

// WithNow overwrites the function used to retrieve a timestamp
// for the current point in time.
// This is generally useful for injection during tests.
func WithNow(f func() time.Time) Option {
	return func(l *nlog) error {
		l.now = f
		return nil
	}
}

// New creates a new notification log based on the provided options.
// The snapshot is loaded into the Log if it is set.
func New(snapshot io.Reader, opts ...Option) (Log, error) {
	l := &nlog{
		now: time.Now,
		st:  map[string]*pb.MeshEntry{},
	}
	for _, o := range opts {
		if err := o(l); err != nil {
			return nil, err
		}
	}
	if snapshot != nil {
		if err := l.loadSnapshot(snapshot); err != nil {
			return l, err
		}
	}
	return l, nil
}

// LogActive implements the Log interface.
func (l *nlog) LogActive(r *pb.Receiver, key, hash []byte) error {
	return l.log(r, key, hash, false)
}

// LogResolved implements the Log interface.
func (l *nlog) LogResolved(r *pb.Receiver, key, hash []byte) error {
	return l.log(r, key, hash, true)
}

// stateKey returns a string key for a log entry consisting of entrie's group key
// and receiver.
func stateKey(k []byte, r *pb.Receiver) string {
	return fmt.Sprintf("%s:%s", k, r)
}

func (l *nlog) log(r *pb.Receiver, gkey, ghash []byte, resolved bool) error {
	// Write all st with the same timestamp.
	now := l.now()
	key := stateKey(gkey, r)

	l.mtx.Lock()
	defer l.mtx.Unlock()

	if prevle, ok := l.st[key]; ok {
		// Entry already exists, only overwrite if timestamp is newer.
		// This may with raciness or clock-drift across AM nodes.
		prevts, err := ptypes.Timestamp(prevle.Entry.Timestamp)
		if err != nil {
			return err
		}
		if prevts.After(now) {
			return nil
		}
	}

	ts, err := ptypes.TimestampProto(now)
	if err != nil {
		return err
	}
	expts, err := ptypes.TimestampProto(now.Add(l.retention))
	if err != nil {
		return err
	}

	l.st[key] = &pb.MeshEntry{
		Entry: &pb.Entry{
			Receiver:  r,
			GroupKey:  gkey,
			GroupHash: ghash,
			Resolved:  resolved,
			Timestamp: ts,
		},
		ExpiresAt: expts,
	}
	return nil
}

// Delete implements the Log interface.
func (l *nlog) Delete(params ...DeleteParam) (int, error) {
	var del delQuery
	for _, p := range params {
		if err := p(&del); err != nil {
			return 0, err
		}
	}
	if !del.expired {
		return 0, errors.New("only expiration deletion supported")
	}
	now := l.now()
	var n int

	l.mtx.Lock()
	defer l.mtx.Unlock()

	for k, le := range l.st {
		if ets, err := ptypes.Timestamp(le.ExpiresAt); err != nil {
			return n, err
		} else if ets.Before(now) {
			delete(l.st, k)
			n++
		}
	}

	return n, nil
}

// Query implements the Log interface.
func (l *nlog) Query(params ...QueryParam) ([]*pb.Entry, error) {
	q := &query{}
	for _, p := range params {
		if err := p(q); err != nil {
			return nil, err
		}
	}
	// TODO(fabxc): For now our only query mode is the most recent entry for a
	// receiver/group_key combination.
	if q.recv == nil || q.groupKey == nil {
		// TODO(fabxc): allow more complex queries in the future.
		// How to enable pagination?
		return nil, errors.New("no query parameters specified")
	}

	l.mtx.RLock()
	defer l.mtx.RUnlock()

	if le, ok := l.st[stateKey(q.groupKey, q.recv)]; ok {
		return []*pb.Entry{le.Entry}, nil
	}
	return nil, ErrNotFound
}

// loadSnapshot loads a snapshot generated by Snapshot() into the state.
func (l *nlog) loadSnapshot(r io.Reader) error {
	l.mtx.Lock()
	defer l.mtx.Unlock()

	for {
		var e pb.MeshEntry
		if _, err := pbutil.ReadDelimited(r, &e); err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		l.st[stateKey(e.Entry.GroupKey, e.Entry.Receiver)] = &e
	}

	return nil
}

// Snapshot implements the Log interface.
func (l *nlog) Snapshot(w io.Writer) (int, error) {
	l.mtx.RLock()
	defer l.mtx.RUnlock()

	var n int
	for _, e := range l.st {
		m, err := pbutil.WriteDelimited(w, e)
		if err != nil {
			return n + m, err
		}
		n += m
	}
	return n, nil
}

// Gossip implements the mesh.Gossiper interface.
func (l *nlog) Gossip() mesh.GossipData {
	l.mtx.RLock()
	defer l.mtx.RUnlock()

	gd := make(gossipData, len(l.st))
	for k, v := range l.st {
		gd[k] = v
	}
	return gd
}

// OnGossip implements the mesh.Gossiper interface.
func (l *nlog) OnGossip(msg []byte) (mesh.GossipData, error) {
	gd, err := decodeGossipData(msg)
	if err != nil {
		return nil, err
	}
	l.mtx.Lock()
	defer l.mtx.Unlock()

	if delta := l.st.mergeDelta(gd); len(delta) > 0 {
		return delta, nil
	}
	return nil, nil
}

// OnGossipBroadcast implements the mesh.Gossiper interface.
func (l *nlog) OnGossipBroadcast(src mesh.PeerName, msg []byte) (mesh.GossipData, error) {
	gd, err := decodeGossipData(msg)
	if err != nil {
		return nil, err
	}
	l.mtx.Lock()
	defer l.mtx.Unlock()

	return l.st.mergeDelta(gd), nil
}

// OnGossipUnicast implements the mesh.Gossiper interface.
func (l *nlog) OnGossipUnicast(src mesh.PeerName, msg []byte) error {
	panic("not implemented")
}

// gossipData is a representation of the current log state that
// implements the mesh.GossipData interface.
type gossipData map[string]*pb.MeshEntry

func decodeGossipData(msg []byte) (gossipData, error) {
	gd := gossipData{}
	rd := bytes.NewReader(msg)

	for {
		var e pb.MeshEntry
		if _, err := pbutil.ReadDelimited(rd, &e); err != nil {
			if err == io.EOF {
				break
			}
			return gd, err
		}
		gd[stateKey(e.Entry.GroupKey, e.Entry.Receiver)] = &e
	}

	return gd, nil
}

// Encode implements the mesh.GossipData interface.
func (gd gossipData) Encode() [][]byte {
	// Split into sub-messages of ~1MB.
	const maxSize = 1024 * 1024

	var (
		buf bytes.Buffer
		res [][]byte
		n   int
	)
	for _, e := range gd {
		m, err := pbutil.WriteDelimited(&buf, e)
		n += m
		if err != nil {
			// TODO(fabxc): log error and skip entry.
			panic(err)
		}
		if n > maxSize {
			res = append(res, buf.Bytes())
			buf = bytes.Buffer{}
		}
	}
	if buf.Len() > 0 {
		res = append(res, buf.Bytes())
	}
	return res
}

// Merge the notification set with gossip data and return a new notification
// state.
// TODO(fabxc): can we just return the receiver. Does it have to remain
// unmodified. Needs to be clarified upstream.
func (gd gossipData) Merge(other mesh.GossipData) mesh.GossipData {
	for k, e := range other.(gossipData) {
		prev, ok := gd[k]
		if !ok {
			gd[k] = e
			continue
		}
		pts, err := ptypes.Timestamp(prev.Entry.Timestamp)
		if err != nil {
			// TODO(fabxc): log error and skip entry.
			panic(err)
		}
		ets, err := ptypes.Timestamp(e.Entry.Timestamp)
		if err != nil {
			// TODO(fabxc): see above.
			panic(err)
		}
		if pts.Before(ets) {
			gd[k] = e
		}
	}
	return gd
}

// mergeDelta behaves like Merge but returns a gossipData only containing
// things that have changed.
func (gd gossipData) mergeDelta(od gossipData) gossipData {
	delta := gossipData{}
	for k, e := range od {
		prev, ok := gd[k]
		if !ok {
			gd[k] = e
			delta[k] = e
			continue
		}
		pts, err := ptypes.Timestamp(prev.Entry.Timestamp)
		if err != nil {
			// TODO(fabxc): log error and skip entry.
			panic(err)
		}
		ets, err := ptypes.Timestamp(e.Entry.Timestamp)
		if err != nil {
			// TODO(fabxc): see above.
			panic(err)
		}
		if pts.Before(ets) {
			gd[k] = e
			delta[k] = e
		}
	}
	return delta
}
<commit_msg>nflog: add gc/snapshotting maintenance, remove delete<commit_after>package nflog

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"math/rand"
	"os"
	"sync"
	"time"

	"github.com/golang/protobuf/ptypes"
	"github.com/matttproud/golang_protobuf_extensions/pbutil"
	pb "github.com/prometheus/alertmanager/nflog/nflogpb"
	"github.com/weaveworks/mesh"
)

var ErrNotFound = errors.New("not found")

// Log stores and serves information about notifications
// about byte-slice addressed alert objects to different receivers.
type Log interface {
	// The Log* methods store a notification log entry for
	// a fully qualified receiver and a given IDs identifying the
	// alert object.
	LogActive(r *pb.Receiver, key, hash []byte) error
	LogResolved(r *pb.Receiver, key, hash []byte) error

	// Query the log along the given Paramteres.
	//
	// TODO(fabxc):
	// - extend the interface by a `QueryOne` method?
	// - return an iterator rather than a materialized list?
	Query(p ...QueryParam) ([]*pb.Entry, error)

	// Snapshot the current log state and return the number
	// of bytes written.
	Snapshot(w io.Writer) (int, error)
	// GC removes expired entries from the log. It returns
	// the total number of deleted entries.
	GC() (int, error)
}

// query currently allows filtering by and/or receiver group key.
// It is configured via QueryParameter functions.
//
// TODO(fabxc): Future versions could allow querying a certain receiver
// group or a given time interval.
type query struct {
	recv     *pb.Receiver
	groupKey []byte
}

// QueryParam is a function that modifies a query to incorporate
// a set of parameters. Returns an error for invalid or conflicting
// parameters.
type QueryParam func(*query) error

// QReceiver adds a receiver parameter to a query.
func QReceiver(r *pb.Receiver) QueryParam {
	return func(q *query) error {
		q.recv = r
		return nil
	}
}

// QGroupKey adds a group key as querying argument.
func QGroupKey(gk []byte) QueryParam {
	return func(q *query) error {
		q.groupKey = gk
		return nil
	}
}

type nlog struct {
	now       func() time.Time
	retention time.Duration

	runInterval time.Duration
	snapf       string
	stopc       chan struct{}
	done        func()

	gossip mesh.Gossip // gossip channel for sharing log state.

	// For now we only store the most recently added log entry.
	// The key is a serialized concatenation of group key and receiver.
	// Currently our memory state is equivalent to the mesh.GossipData
	// representation. This may change in the future as we support history
	// and indexing.
	mtx sync.RWMutex
	st  gossipData
}

// Option configures a new Log implementation.
type Option func(*nlog) error

// WithMesh registers the log with a mesh network with which
// the log state will be shared.
func WithMesh(create func(g mesh.Gossiper) mesh.Gossip) Option {
	return func(l *nlog) error {
		l.gossip = create(l)
		return nil
	}
}

// WithRetention sets the retention time for log st.
func WithRetention(d time.Duration) Option {
	return func(l *nlog) error {
		l.retention = d
		return nil
	}
}

// WithNow overwrites the function used to retrieve a timestamp
// for the current point in time.
// This is generally useful for injection during tests.
func WithNow(f func() time.Time) Option {
	return func(l *nlog) error {
		l.now = f
		return nil
	}
}

// WithMaintenance configures the Log to run garbage collection
// and snapshotting to the provided file at the given interval.
// On startup the a snapshot is also loaded from the given file.
//
// The maintenance terminates on receiving from the provided channel.
// The done function is called after the final snapshot was completed.
//
// Providing a 0 duration will not run background processing.
// Providing an empty file name will skip snapshotting.
func WithMaintenance(sf string, d time.Duration, stopc chan struct{}, done func()) Option {
	return func(l *nlog) error {
		l.runInterval = d
		l.stopc = stopc
		l.done = done
		l.snapf = sf
		return nil
	}
}

// New creates a new notification log based on the provided options.
// The snapshot is loaded into the Log if it is set.
func New(opts ...Option) (Log, error) {
	l := &nlog{
		now: time.Now,
		st:  map[string]*pb.MeshEntry{},
	}
	for _, o := range opts {
		if err := o(l); err != nil {
			return nil, err
		}
	}
	if l.snapf != "" {
		f, err := os.Open(l.snapf)
		if err != nil {
			return l, err
		}
		defer f.Close()

		if err := l.loadSnapshot(f); err != nil {
			return l, err
		}
	}
	go l.run()

	return l, nil
}

// run periodic background maintenance.
func (l *nlog) run() {
	if l.runInterval == 0 || l.stopc == nil {
		return
	}
	t := time.NewTicker(l.runInterval)
	defer t.Stop()

	if l.done != nil {
		defer l.done()
	}

	f := func() {
		if _, err := l.GC(); err != nil {
			// TODO(fabxc): log error instead
			panic(err)
		}
		if l.snapf == "" {
			return
		}
		f, err := openReplace(l.snapf)
		if err != nil {
			panic(err)
		}
		if _, err := l.Snapshot(f); err != nil {
			panic(err)
		}
		if err := f.Close(); err != nil {
			panic(err)
		}
	}

	for {
		select {
		case <-l.stopc:
			return
		case <-t.C:
			f()
		}
	}
	f()
}

// LogActive implements the Log interface.
func (l *nlog) LogActive(r *pb.Receiver, key, hash []byte) error {
	return l.log(r, key, hash, false)
}

// LogResolved implements the Log interface.
func (l *nlog) LogResolved(r *pb.Receiver, key, hash []byte) error {
	return l.log(r, key, hash, true)
}

// stateKey returns a string key for a log entry consisting of entrie's group key
// and receiver.
func stateKey(k []byte, r *pb.Receiver) string {
	return fmt.Sprintf("%s:%s", k, r)
}

func (l *nlog) log(r *pb.Receiver, gkey, ghash []byte, resolved bool) error {
	// Write all st with the same timestamp.
	now := l.now()
	key := stateKey(gkey, r)

	l.mtx.Lock()
	defer l.mtx.Unlock()

	if prevle, ok := l.st[key]; ok {
		// Entry already exists, only overwrite if timestamp is newer.
		// This may with raciness or clock-drift across AM nodes.
		prevts, err := ptypes.Timestamp(prevle.Entry.Timestamp)
		if err != nil {
			return err
		}
		if prevts.After(now) {
			return nil
		}
	}

	ts, err := ptypes.TimestampProto(now)
	if err != nil {
		return err
	}
	expts, err := ptypes.TimestampProto(now.Add(l.retention))
	if err != nil {
		return err
	}

	l.st[key] = &pb.MeshEntry{
		Entry: &pb.Entry{
			Receiver:  r,
			GroupKey:  gkey,
			GroupHash: ghash,
			Resolved:  resolved,
			Timestamp: ts,
		},
		ExpiresAt: expts,
	}
	return nil
}

// GC implements the Log interface.
func (l *nlog) GC() (int, error) {
	now := l.now()
	var n int

	l.mtx.Lock()
	defer l.mtx.Unlock()

	for k, le := range l.st {
		if ets, err := ptypes.Timestamp(le.ExpiresAt); err != nil {
			return n, err
		} else if ets.Before(now) {
			delete(l.st, k)
			n++
		}
	}

	return n, nil
}

// Query implements the Log interface.
func (l *nlog) Query(params ...QueryParam) ([]*pb.Entry, error) {
	q := &query{}
	for _, p := range params {
		if err := p(q); err != nil {
			return nil, err
		}
	}
	// TODO(fabxc): For now our only query mode is the most recent entry for a
	// receiver/group_key combination.
	if q.recv == nil || q.groupKey == nil {
		// TODO(fabxc): allow more complex queries in the future.
		// How to enable pagination?
		return nil, errors.New("no query parameters specified")
	}

	l.mtx.RLock()
	defer l.mtx.RUnlock()

	if le, ok := l.st[stateKey(q.groupKey, q.recv)]; ok {
		return []*pb.Entry{le.Entry}, nil
	}
	return nil, ErrNotFound
}

// loadSnapshot loads a snapshot generated by Snapshot() into the state.
func (l *nlog) loadSnapshot(r io.Reader) error {
	l.mtx.Lock()
	defer l.mtx.Unlock()

	st := gossipData{}

	for {
		var e pb.MeshEntry
		if _, err := pbutil.ReadDelimited(r, &e); err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		st[stateKey(e.Entry.GroupKey, e.Entry.Receiver)] = &e
	}
	l.st = st

	return nil
}

// Snapshot implements the Log interface.
func (l *nlog) Snapshot(w io.Writer) (int, error) {
	l.mtx.RLock()
	defer l.mtx.RUnlock()

	var n int
	for _, e := range l.st {
		m, err := pbutil.WriteDelimited(w, e)
		if err != nil {
			return n + m, err
		}
		n += m
	}
	return n, nil
}

// Gossip implements the mesh.Gossiper interface.
func (l *nlog) Gossip() mesh.GossipData {
	l.mtx.RLock()
	defer l.mtx.RUnlock()

	gd := make(gossipData, len(l.st))
	for k, v := range l.st {
		gd[k] = v
	}
	return gd
}

// OnGossip implements the mesh.Gossiper interface.
func (l *nlog) OnGossip(msg []byte) (mesh.GossipData, error) {
	gd, err := decodeGossipData(msg)
	if err != nil {
		return nil, err
	}
	l.mtx.Lock()
	defer l.mtx.Unlock()

	if delta := l.st.mergeDelta(gd); len(delta) > 0 {
		return delta, nil
	}
	return nil, nil
}

// OnGossipBroadcast implements the mesh.Gossiper interface.
func (l *nlog) OnGossipBroadcast(src mesh.PeerName, msg []byte) (mesh.GossipData, error) {
	gd, err := decodeGossipData(msg)
	if err != nil {
		return nil, err
	}
	l.mtx.Lock()
	defer l.mtx.Unlock()

	return l.st.mergeDelta(gd), nil
}

// OnGossipUnicast implements the mesh.Gossiper interface.
func (l *nlog) OnGossipUnicast(src mesh.PeerName, msg []byte) error {
	panic("not implemented")
}

// gossipData is a representation of the current log state that
// implements the mesh.GossipData interface.
type gossipData map[string]*pb.MeshEntry

func decodeGossipData(msg []byte) (gossipData, error) {
	gd := gossipData{}
	rd := bytes.NewReader(msg)

	for {
		var e pb.MeshEntry
		if _, err := pbutil.ReadDelimited(rd, &e); err != nil {
			if err == io.EOF {
				break
			}
			return gd, err
		}
		gd[stateKey(e.Entry.GroupKey, e.Entry.Receiver)] = &e
	}

	return gd, nil
}

// Encode implements the mesh.GossipData interface.
func (gd gossipData) Encode() [][]byte {
	// Split into sub-messages of ~1MB.
	const maxSize = 1024 * 1024

	var (
		buf bytes.Buffer
		res [][]byte
		n   int
	)
	for _, e := range gd {
		m, err := pbutil.WriteDelimited(&buf, e)
		n += m
		if err != nil {
			// TODO(fabxc): log error and skip entry.
			panic(err)
		}
		if n > maxSize {
			res = append(res, buf.Bytes())
			buf = bytes.Buffer{}
		}
	}
	if buf.Len() > 0 {
		res = append(res, buf.Bytes())
	}
	return res
}

// Merge the notification set with gossip data and return a new notification
// state.
// TODO(fabxc): can we just return the receiver. Does it have to remain
// unmodified. Needs to be clarified upstream.
func (gd gossipData) Merge(other mesh.GossipData) mesh.GossipData {
	for k, e := range other.(gossipData) {
		prev, ok := gd[k]
		if !ok {
			gd[k] = e
			continue
		}
		pts, err := ptypes.Timestamp(prev.Entry.Timestamp)
		if err != nil {
			// TODO(fabxc): log error and skip entry.
			panic(err)
		}
		ets, err := ptypes.Timestamp(e.Entry.Timestamp)
		if err != nil {
			// TODO(fabxc): see above.
			panic(err)
		}
		if pts.Before(ets) {
			gd[k] = e
		}
	}
	return gd
}

// mergeDelta behaves like Merge but returns a gossipData only containing
// things that have changed.
func (gd gossipData) mergeDelta(od gossipData) gossipData {
	delta := gossipData{}
	for k, e := range od {
		prev, ok := gd[k]
		if !ok {
			gd[k] = e
			delta[k] = e
			continue
		}
		pts, err := ptypes.Timestamp(prev.Entry.Timestamp)
		if err != nil {
			// TODO(fabxc): log error and skip entry.
			panic(err)
		}
		ets, err := ptypes.Timestamp(e.Entry.Timestamp)
		if err != nil {
			// TODO(fabxc): see above.
			panic(err)
		}
		if pts.Before(ets) {
			gd[k] = e
			delta[k] = e
		}
	}
	return delta
}

// replaceFile wraps a file that is moved to another filename on closing.
type replaceFile struct {
	*os.File
	filename string
}

func (f *replaceFile) Close() error {
	if err := f.File.Sync(); err != nil {
		return err
	}
	if err := f.File.Close(); err != nil {
		return err
	}
	return os.Rename(f.File.Name(), f.filename)
}

// openReplace opens a new temporary file that is moved to filename on closing.
func openReplace(filename string) (*replaceFile, error) {
	tmpFilename := fmt.Sprintf("%s.%x", filename, uint64(rand.Int63()))

	f, err := os.Create(tmpFilename)
	if err != nil {
		return nil, err
	}

	rf := &replaceFile{
		File:     f,
		filename: filename,
	}
	return rf, nil
}
<|endoftext|>


<commit_before>// Copyright 2018 Nicholas Ng <nickng@nickng.io>
//
// 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.

// Command asyncpi is a REPL frontend for the asyncpi package.
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"os"
	"os/signal"
	"strings"

	"github.com/fatih/color"
	"go.nickng.io/asyncpi"
)

var (
	flagColour bool
)

// Command is an interface of a runnable command.
type Command interface {
	// Desc is a short description of the command.
	Desc() string

	// Run is the function to call to execute the command.
	Run()
}

// REPL is the base struct for a REPL-loop.
// Use Prompt() method to start running the REPL loop.
type REPL struct {
	Cmd         map[string]Command
	Interrupted chan os.Signal
	Done        chan error
	hist        []asyncpi.Process

	in  io.Reader
	out io.Writer
	err io.Writer
}

func NewREPL() *REPL {
	r := REPL{
		Done: make(chan error),
		in:   os.Stdin,
		out:  os.Stdout,
		err:  os.Stderr,
	}
	r.Cmd = map[string]Command{
		"help":    &helpCmd{r: &r},
		"exit":    &exitCmd{r: &r},
		"history": &histCmd{r: &r},
		"parse":   &parseCmd{r: &r},
		"reduce":  &reduceCmd{r: &r},
		"show":    &subprocCmd{r: &r},
		"codegen": &codegenCmd{r: &r},
	}
	return &r
}

func (r *REPL) appendHistory(p asyncpi.Process) {
	r.hist = append(r.hist, p)
}

func (r *REPL) replaceHistory(p asyncpi.Process) {
	r.hist[len(r.hist)-1] = p
}

const (
	PromptInit = "async-π> "
	PromptMore = ".......> "
)

var (
	outFprintf = color.New(color.FgCyan).FprintfFunc()
	errFprintf = color.New(color.FgRed).FprintfFunc()
)

func (r *REPL) Responsef(fmt string, a ...interface{}) {
	outFprintf(r.out, fmt, a...)
}

func (r *REPL) Errorf(fmt string, a ...interface{}) {
	errFprintf(r.err, fmt, a...)
}

func (r *REPL) Prompt() {
	for {
		var command string
		fmt.Fprint(r.out, PromptInit)
		select {
		case <-r.Interrupted:
			r.Cmd["exit"].Run()
		default:
			// read first space-delimited string (the command).
			_, err := fmt.Fscanf(r.in, "%s", &command)
			if err != nil {
				if err == io.EOF {
					command = "exit"
				}
			}
			command = strings.TrimSpace(command)
			if cmd, ok := r.Cmd[command]; ok {
				cmd.Run()
			} else {
				r.Errorf("Unrecognised command: %s\n", command)
				r.Cmd["help"].Run()
			}
		}
	}
}

func (r *REPL) Usage() string {
	var buf bytes.Buffer
	buf.WriteString("Commands available:\n")
	for name, cmd := range r.Cmd {
		buf.WriteString(fmt.Sprintf("\t%s\t%s\n", name, cmd.Desc()))
	}
	return buf.String()
}

type helpCmd struct {
	r *REPL
}

func (cmd *helpCmd) Desc() string { return "Display this help message." }
func (cmd *helpCmd) Run()         { cmd.r.Responsef(cmd.r.Usage()) }

type exitCmd struct {
	r *REPL
}

func (cmd *exitCmd) Desc() string { return "Exit." }
func (cmd *exitCmd) Run()         { close(cmd.r.Done) }

type histCmd struct {
	r *REPL
}

func (cmd *histCmd) Desc() string { return "Display history." }
func (cmd *histCmd) Run() {
	if len(cmd.r.hist) == 0 {
		cmd.r.Responsef("History is empty.\n")
	}
	for i, p := range cmd.r.hist {
		cmd.r.Responsef("%d:\t%s\n", i, p.Calculi())
	}
}

func init() {
	flag.BoolVar(&flagColour, "colour", true, "Output with colour (needs ANSI colour support)")
}

func main() {
	flag.Parse()
	color.NoColor = !flagColour
	repl := NewREPL()
	repl.Interrupted = make(chan os.Signal, 1)
	signal.Notify(repl.Interrupted, os.Interrupt)
	go repl.Prompt()
	err := <-repl.Done
	if err != nil {
		repl.Errorf("asyncpi error: %v\n", err)
		os.Exit(1)
	}
}
<commit_msg>cmd/asyncpi: Add a load command.<commit_after>// Copyright 2018 Nicholas Ng <nickng@nickng.io>
//
// 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.

// Command asyncpi is a REPL frontend for the asyncpi package.
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/signal"
	"strings"

	"github.com/fatih/color"
	"go.nickng.io/asyncpi"
)

var (
	flagColour bool
)

// Command is an interface of a runnable command.
type Command interface {
	// Desc is a short description of the command.
	Desc() string

	// Run is the function to call to execute the command.
	Run()
}

// REPL is the base struct for a REPL-loop.
// Use Prompt() method to start running the REPL loop.
type REPL struct {
	Cmd         map[string]Command
	Interrupted chan os.Signal
	Done        chan error
	hist        []asyncpi.Process

	in  io.Reader
	out io.Writer
	err io.Writer
}

// Core commands.
const (
	CmdExit  = "exit"
	CmdHelp  = "help"
	CmdLoad  = "load"
	CmdParse = "parse"
)

func NewREPL() *REPL {
	r := REPL{
		Done: make(chan error),
		in:   os.Stdin,
		out:  os.Stdout,
		err:  os.Stderr,
	}
	r.Cmd = map[string]Command{
		CmdExit:   &exitCmd{r: &r},
		CmdHelp:   &helpCmd{r: &r},
		CmdLoad:   &loadCmd{r: &r},
		CmdParse:  &parseCmd{r: &r},
		"history": &histCmd{r: &r},
		"reduce":  &reduceCmd{r: &r},
		"show":    &subprocCmd{r: &r},
		"codegen": &codegenCmd{r: &r},
	}
	return &r
}

func (r *REPL) appendHistory(p asyncpi.Process) {
	r.hist = append(r.hist, p)
}

func (r *REPL) replaceHistory(p asyncpi.Process) {
	r.hist[len(r.hist)-1] = p
}

const (
	PromptInit = "async-π> "
	PromptMore = ".......> "
)

var (
	outFprintf = color.New(color.FgCyan).FprintfFunc()
	errFprintf = color.New(color.FgRed).FprintfFunc()
)

func (r *REPL) Responsef(fmt string, a ...interface{}) {
	outFprintf(r.out, fmt, a...)
}

func (r *REPL) Errorf(fmt string, a ...interface{}) {
	errFprintf(r.err, fmt, a...)
}

func (r *REPL) Prompt() {
	for {
		var command string
		fmt.Fprint(r.out, PromptInit)
		select {
		case <-r.Interrupted:
			r.Cmd["exit"].Run()
		default:
			// read first space-delimited string (the command).
			_, err := fmt.Fscanf(r.in, "%s", &command)
			if err != nil {
				if err == io.EOF {
					command = "exit"
				}
			}
			command = strings.TrimSpace(command)
			if cmd, ok := r.Cmd[command]; ok {
				cmd.Run()
			} else {
				r.Errorf("Unrecognised command: %s\n", command)
				r.Cmd["help"].Run()
			}
		}
	}
}

func (r *REPL) Usage() string {
	var buf bytes.Buffer
	buf.WriteString("Commands available:\n")
	for name, cmd := range r.Cmd {
		buf.WriteString(fmt.Sprintf("\t%s\t%s\n", name, cmd.Desc()))
	}
	return buf.String()
}

type helpCmd struct {
	r *REPL
}

func (cmd *helpCmd) Desc() string { return "Display this help message." }
func (cmd *helpCmd) Run()         { cmd.r.Responsef(cmd.r.Usage()) }

type exitCmd struct {
	r *REPL
}

func (cmd *exitCmd) Desc() string { return "Exit." }
func (cmd *exitCmd) Run()         { close(cmd.r.Done) }

type histCmd struct {
	r *REPL
}

func (cmd *histCmd) Desc() string { return "Display history." }
func (cmd *histCmd) Run() {
	if len(cmd.r.hist) == 0 {
		cmd.r.Responsef("History is empty.\n")
	}
	for i, p := range cmd.r.hist {
		cmd.r.Responsef("%d:\t%s\n", i, p.Calculi())
	}
}

type loadCmd struct {
	r *REPL
}

func (cmd *loadCmd) Desc() string { return "Load a file to parse." }
func (cmd *loadCmd) Run() {
	r := bufio.NewReader(cmd.r.in)
	filepath, err := r.ReadString('\n') // Read newline delimited filename.
	if err != nil {
		if err == io.EOF {
			cmd.r.Cmd[CmdExit].Run()
			return
		}
		cmd.r.Done <- err
		return
	}
	filepath = strings.TrimSpace(filepath)
	cmd.r.Responsef("Loading: %s\n", filepath)
	file, err := os.Open(filepath)
	if err != nil {
		cmd.r.Errorf("cannot open file: %v\n", err)
		return
	}
	b, err := ioutil.ReadAll(file)
	if err != nil {
		cmd.r.Errorf("cannot read file: %v\n", err)
		return
	}
	if err := file.Close(); err != nil {
		cmd.r.Errorf("cannot close file: %v\n", err)
		return
	}
	origIn := cmd.r.in
	cmd.r.in = bytes.NewReader(b)
	cmd.r.Cmd[CmdParse].Run()
	cmd.r.in = origIn
}

func init() {
	flag.BoolVar(&flagColour, "colour", true, "Output with colour (needs ANSI colour support)")
}

func main() {
	flag.Parse()
	color.NoColor = !flagColour
	repl := NewREPL()
	repl.Interrupted = make(chan os.Signal, 1)
	signal.Notify(repl.Interrupted, os.Interrupt)
	go repl.Prompt()
	err := <-repl.Done
	if err != nil {
		repl.Errorf("asyncpi error: %v\n", err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	is "github.com/containers/image/storage"
	"github.com/containers/storage"
	units "github.com/docker/go-units"
	"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
	"github.com/kubernetes-incubator/cri-o/libpod/common"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

const (
	createdByTruncLength = 45
	idTruncLength        = 13
)

// historyOutputParams stores info about each layer
type historyOutputParams struct {
	ID        string `json:"id"`
	Created   string `json:"created"`
	CreatedBy string `json:"createdby"`
	Size      string `json:"size"`
	Comment   string `json:"comment"`
}

// historyOptions stores cli flag values
type historyOptions struct {
	image    string
	human    bool
	truncate bool
	quiet    bool
	format   string
}

var (
	historyFlags = []cli.Flag{
		cli.BoolFlag{
			Name:  "human, H",
			Usage: "Display sizes and dates in human readable format",
		},
		cli.BoolFlag{
			Name:  "no-trunc, notruncate",
			Usage: "Do not truncate the output",
		},
		cli.BoolFlag{
			Name:  "quiet, q",
			Usage: "Display the numeric IDs only",
		},
		cli.StringFlag{
			Name:  "format",
			Usage: "Change the output to JSON or a Go template",
		},
	}

	historyDescription = "Displays the history of an image. The information can be printed out in an easy to read, " +
		"or user specified format, and can be truncated."
	historyCommand = cli.Command{
		Name:        "history",
		Usage:       "Show history of a specified image",
		Description: historyDescription,
		Flags:       historyFlags,
		Action:      historyCmd,
		ArgsUsage:   "",
	}
)

func historyCmd(c *cli.Context) error {
	config, err := getConfig(c)
	if err != nil {
		return errors.Wrapf(err, "Could not get config")
	}
	store, err := getStore(config)
	if err != nil {
		return err
	}

	human := true
	if c.IsSet("human") {
		human = c.Bool("human")
	}

	truncate := true
	if c.IsSet("no-trunc") {
		truncate = !c.Bool("no-trunc")
	}
	quiet := false
	if c.IsSet("quiet") {
		quiet = c.Bool("quiet")
	}
	format := ""
	if c.IsSet("format") {
		format = c.String("format")
	}
	args := c.Args()
	if len(args) == 0 {
		logrus.Errorf("an image name must be specified")
		return nil
	}
	if len(args) > 1 {
		logrus.Errorf("Kpod history takes at most 1 argument")
		return nil
	}
	imgName := args[0]

	opts := historyOptions{
		image:    imgName,
		human:    human,
		truncate: truncate,
		quiet:    quiet,
		format:   format,
	}
	err = outputHistory(store, opts)
	return err
}

func genHistoryFormat(quiet, truncate, human bool) (format string) {
	if quiet {
		return formats.IDString
	}

	if truncate {
		format = "table {{ .ID | printf \"%-12.12s\" }} {{ .Created | printf \"%-16s\" }} {{ .CreatedBy | " +
			"printf \"%-45.45s\" }} {{ .Size | printf \"%-16s\" }} {{ .Comment | printf \"%s\" }}"
	} else {
		format = "table {{ .ID | printf \"%-64s\" }} {{ .Created | printf \"%-18s\" }} {{ .CreatedBy | " +
			"printf \"%-60s\" }} {{ .Size | printf \"%-16s\" }} {{ .Comment | printf \"%s\"}}"
	}
	return
}

// outputTime displays the time stamp in "2017-06-20T20:24:10Z" format
func outputTime(tm *time.Time) string {
	return tm.Format(time.RFC3339)
}

// outputHumanTime displays the time elapsed since creation
func outputHumanTime(tm *time.Time) string {
	return units.HumanDuration(time.Since(*tm))
}

// createJSON retrieves the history of the image and returns a JSON object
func createJSON(store storage.Store, opts historyOptions) ([]byte, error) {
	var (
		size        int64
		img         *storage.Image
		imageID     string
		layerAll    []historyOutputParams
		createdTime string
		outputSize  string
	)

	ref, err := is.Transport.ParseStoreReference(store, opts.image)
	if err != nil {
		return nil, errors.Errorf("error parsing reference to image %q: %v", opts.image, err)
	}

	img, err = is.Transport.GetStoreImage(store, ref)
	if err != nil {
		return nil, errors.Errorf("no such image %q: %v", opts.image, err)
	}

	systemContext := common.GetSystemContext("")

	src, err := ref.NewImage(systemContext)
	if err != nil {
		return nil, errors.Errorf("error instantiating image %q: %v", opts.image, err)
	}

	oci, err := src.OCIConfig()
	if err != nil {
		return nil, err
	}

	history := oci.History
	layers := src.LayerInfos()
	count := 1
	// iterating backwards to get newwest to oldest
	for i := len(history) - 1; i >= 0; i-- {
		if i == len(history)-1 {
			imageID = img.ID
		} else {
			imageID = "<missing>"
		}

		if !history[i].EmptyLayer {
			size = layers[len(layers)-count].Size
			count++
		} else {
			size = 0
		}

		if opts.human {
			createdTime = outputHumanTime(history[i].Created) + " ago"
			outputSize = units.HumanSize(float64(size))
		} else {
			createdTime = outputTime(history[i].Created)
			outputSize = strconv.FormatInt(size, 10)
		}
		params := historyOutputParams{
			ID:        imageID,
			Created:   createdTime,
			CreatedBy: history[i].CreatedBy,
			Size:      outputSize,
			Comment:   history[i].Comment,
		}

		layerAll = append(layerAll, params)
	}

	output, err := json.MarshalIndent(layerAll, "", "\t\t")
	if err != nil {
		return nil, errors.Errorf("error marshalling to JSON: %v", err)
	}

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

	return output, nil
}

// historyToGeneric makes an empty array of interfaces for output
func historyToGeneric(params []historyOutputParams) []interface{} {
	genericParams := make([]interface{}, len(params))
	for i, v := range params {
		genericParams[i] = interface{}(v)
	}
	return genericParams
}

// outputHistory gets the history of the image from the JSON object
// and pretty prints it to the screen
func outputHistory(store storage.Store, opts historyOptions) error {
	var (
		outputCreatedBy string
		imageID         string
		history         []historyOutputParams
	)

	raw, err := createJSON(store, opts)
	if err != nil {
		return errors.Errorf("error creating JSON: %v", err)
	}

	if err = json.Unmarshal(raw, &history); err != nil {
		return errors.Errorf("error Unmarshalling JSON: %v", err)
	}

	historyOutput := []historyOutputParams{}

	historyFormat := opts.format
	if historyFormat == "" {
		historyFormat = genHistoryFormat(opts.quiet, opts.truncate, opts.human)
	}

	for i := 0; i < len(history); i++ {
		imageID = history[i].ID

		outputCreatedBy = strings.Join(strings.Fields(history[i].CreatedBy), " ")
		if opts.truncate && len(outputCreatedBy) > createdByTruncLength {
			outputCreatedBy = outputCreatedBy[:createdByTruncLength-3] + "..."
		}

		if opts.truncate && i == 0 {
			imageID = history[i].ID[:idTruncLength]
		}

		params := historyOutputParams{
			ID:        imageID,
			Created:   history[i].Created,
			CreatedBy: outputCreatedBy,
			Size:      history[i].Size,
			Comment:   history[i].Comment,
		}
		historyOutput = append(historyOutput, params)
	}

	var out formats.Writer
	switch opts.format {
	case formats.JSONString:
		out = formats.JSONStructArray{Output: historyToGeneric(historyOutput)}
	default:
		out = formats.StdoutTemplateArray{Output: historyToGeneric(historyOutput), Template: historyFormat, Fields: historyOutput[0].headerMap()}

	}
	formats.Writer(out).Out()
	return nil
}

func (h *historyOutputParams) headerMap() map[string]string {
	v := reflect.Indirect(reflect.ValueOf(h))
	values := make(map[string]string)
	for h := 0; h < v.NumField(); h++ {
		key := v.Type().Field(h).Name
		value := key
		values[key] = fmt.Sprintf("%s        ", strings.ToUpper(splitCamelCase(value)))
	}
	return values
}
<commit_msg>Modify the JSON output of kpod history<commit_after>package main

import (
	"reflect"
	"strconv"
	"strings"
	"time"

	is "github.com/containers/image/storage"
	"github.com/containers/image/types"
	"github.com/containers/storage"
	units "github.com/docker/go-units"
	"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
	"github.com/kubernetes-incubator/cri-o/libpod/common"
	"github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"github.com/urfave/cli"
)

const (
	createdByTruncLength = 45
	idTruncLength        = 13
)

// historyTemplateParams stores info about each layer
type historyTemplateParams struct {
	ID        string
	Created   string
	CreatedBy string
	Size      string
	Comment   string
}

// historyJSONParams is only used when the JSON format is specified,
// and is better for data processing from JSON.
// historyJSONParams will be populated by data from v1.History and types.BlobInfo,
// the members of the struct are the sama data types as their sources.
type historyJSONParams struct {
	ID        string     `json:"id"`
	Created   *time.Time `json:"created"`
	CreatedBy string     `json:"createdBy"`
	Size      int64      `json:"size"`
	Comment   string     `json:"comment"`
}

// historyOptions stores cli flag values
type historyOptions struct {
	image   string
	human   bool
	noTrunc bool
	quiet   bool
	format  string
}

var (
	historyFlags = []cli.Flag{
		cli.BoolTFlag{
			Name:  "human, H",
			Usage: "Display sizes and dates in human readable format",
		},
		cli.BoolFlag{
			Name:  "no-trunc, notruncate",
			Usage: "Do not truncate the output",
		},
		cli.BoolFlag{
			Name:  "quiet, q",
			Usage: "Display the numeric IDs only",
		},
		cli.StringFlag{
			Name:  "format",
			Usage: "Change the output to JSON or a Go template",
		},
	}

	historyDescription = "Displays the history of an image. The information can be printed out in an easy to read, " +
		"or user specified format, and can be truncated."
	historyCommand = cli.Command{
		Name:        "history",
		Usage:       "Show history of a specified image",
		Description: historyDescription,
		Flags:       historyFlags,
		Action:      historyCmd,
		ArgsUsage:   "",
	}
)

func historyCmd(c *cli.Context) error {
	config, err := getConfig(c)
	if err != nil {
		return errors.Wrapf(err, "Could not get config")
	}
	store, err := getStore(config)
	if err != nil {
		return err
	}

	format := genHistoryFormat(c.Bool("quiet"))
	if c.IsSet("format") {
		format = c.String("format")
	}

	args := c.Args()
	if len(args) == 0 {
		return errors.Errorf("an image name must be specified")
	}
	if len(args) > 1 {
		return errors.Errorf("Kpod history takes at most 1 argument")
	}
	imgName := args[0]

	opts := historyOptions{
		image:   imgName,
		human:   c.BoolT("human"),
		noTrunc: c.Bool("no-trunc"),
		quiet:   c.Bool("quiet"),
		format:  format,
	}
	return generateHistoryOutput(store, opts)
}

func genHistoryFormat(quiet bool) (format string) {
	if quiet {
		return formats.IDString
	}
	return "table {{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\t"
}

// historyToGeneric makes an empty array of interfaces for output
func historyToGeneric(templParams []historyTemplateParams, JSONParams []historyJSONParams) (genericParams []interface{}) {
	if len(templParams) > 0 {
		for _, v := range templParams {
			genericParams = append(genericParams, interface{}(v))
		}
		return
	}
	for _, v := range JSONParams {
		genericParams = append(genericParams, interface{}(v))
	}
	return
}

// generate the header based on the template provided
func (h *historyTemplateParams) headerMap() map[string]string {
	v := reflect.Indirect(reflect.ValueOf(h))
	values := make(map[string]string)
	for h := 0; h < v.NumField(); h++ {
		key := v.Type().Field(h).Name
		value := key
		values[key] = strings.ToUpper(splitCamelCase(value))
	}
	return values
}

// getHistory gets the history of an image and information about its layers
func getHistory(store storage.Store, image string) ([]v1.History, []types.BlobInfo, string, error) {
	ref, err := is.Transport.ParseStoreReference(store, image)
	if err != nil {
		return nil, nil, "", errors.Wrapf(err, "error parsing reference to image %q", image)
	}

	img, err := is.Transport.GetStoreImage(store, ref)
	if err != nil {
		return nil, nil, "", errors.Wrapf(err, "no such image %q", image)
	}

	systemContext := common.GetSystemContext("")

	src, err := ref.NewImage(systemContext)
	if err != nil {
		return nil, nil, "", errors.Wrapf(err, "error instantiating image %q", image)
	}

	oci, err := src.OCIConfig()
	if err != nil {
		return nil, nil, "", err
	}

	return oci.History, src.LayerInfos(), img.ID, nil
}

// getHistorytemplateOutput gets the modified history information to be printed in human readable format
func getHistoryTemplateOutput(history []v1.History, layers []types.BlobInfo, imageID string, opts historyOptions) (historyOutput []historyTemplateParams) {
	var (
		outputSize  string
		createdTime string
		createdBy   string
		count       = 1
	)
	for i := len(history) - 1; i >= 0; i-- {
		if i != len(history)-1 {
			imageID = "<missing>"
		}
		if !opts.noTrunc && i == len(history)-1 {
			imageID = imageID[:idTruncLength]
		}

		var size int64
		if !history[i].EmptyLayer {
			size = layers[len(layers)-count].Size
			count++
		}

		if opts.human {
			createdTime = units.HumanDuration(time.Since((*history[i].Created))) + " ago"
			outputSize = units.HumanSize(float64(size))
		} else {
			createdTime = (history[i].Created).Format(time.RFC3339)
			outputSize = strconv.FormatInt(size, 10)
		}

		createdBy = strings.Join(strings.Fields(history[i].CreatedBy), " ")
		if !opts.noTrunc && len(createdBy) > createdByTruncLength {
			createdBy = createdBy[:createdByTruncLength-3] + "..."
		}

		params := historyTemplateParams{
			ID:        imageID,
			Created:   createdTime,
			CreatedBy: createdBy,
			Size:      outputSize,
			Comment:   history[i].Comment,
		}
		historyOutput = append(historyOutput, params)
	}
	return
}

// getHistoryJSONOutput returns the history information in its raw form
func getHistoryJSONOutput(history []v1.History, layers []types.BlobInfo, imageID string) (historyOutput []historyJSONParams) {
	count := 1
	for i := len(history) - 1; i >= 0; i-- {
		var size int64
		if !history[i].EmptyLayer {
			size = layers[len(layers)-count].Size
			count++
		}

		params := historyJSONParams{
			ID:        imageID,
			Created:   history[i].Created,
			CreatedBy: history[i].CreatedBy,
			Size:      size,
			Comment:   history[i].Comment,
		}
		historyOutput = append(historyOutput, params)
	}
	return
}

// generateHistoryOutput generates the history based on the format given
func generateHistoryOutput(store storage.Store, opts historyOptions) error {
	history, layers, imageID, err := getHistory(store, opts.image)
	if err != nil {
		return errors.Wrapf(err, "error getting history of image %q", opts.image)
	}
	if len(history) == 0 {
		return nil
	}

	var out formats.Writer

	switch opts.format {
	case formats.JSONString:
		historyOutput := getHistoryJSONOutput(history, layers, imageID)
		out = formats.JSONStructArray{Output: historyToGeneric([]historyTemplateParams{}, historyOutput)}
	default:
		historyOutput := getHistoryTemplateOutput(history, layers, imageID, opts)
		out = formats.StdoutTemplateArray{Output: historyToGeneric(historyOutput, []historyJSONParams{}), Template: opts.format, Fields: historyOutput[0].headerMap()}
	}

	return formats.Writer(out).Out()
}
<|endoftext|>