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

package archive

import (
	"bytes"
	"debug/elf"
	"debug/macho"
	"debug/pe"
	"fmt"
	"internal/testenv"
	"internal/xcoff"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sync"
	"testing"
	"unicode/utf8"
)

var buildDir string

func TestMain(m *testing.M) {
	if !testenv.HasGoBuild() {
		return
	}

	exit := m.Run()

	if buildDir != "" {
		os.RemoveAll(buildDir)
	}
	os.Exit(exit)
}

func copyDir(dst, src string) error {
	err := os.MkdirAll(dst, 0777)
	if err != nil {
		return err
	}
	entries, err := os.ReadDir(src)
	if err != nil {
		return err
	}
	for _, entry := range entries {
		err = copyFile(filepath.Join(dst, entry.Name()), filepath.Join(src, entry.Name()))
		if err != nil {
			return err
		}
	}
	return nil
}

func copyFile(dst, src string) (err error) {
	var s, d *os.File
	s, err = os.Open(src)
	if err != nil {
		return err
	}
	defer s.Close()
	d, err = os.Create(dst)
	if err != nil {
		return err
	}
	defer func() {
		e := d.Close()
		if err == nil {
			err = e
		}
	}()
	_, err = io.Copy(d, s)
	if err != nil {
		return err
	}
	return nil
}

var (
	buildOnce   sync.Once
	builtGoobjs goobjPaths
	buildErr    error
)

type goobjPaths struct {
	go1obj     string
	go2obj     string
	goarchive  string
	cgoarchive string
}

func buildGoobj(t *testing.T) goobjPaths {
	buildOnce.Do(func() {
		buildErr = func() (err error) {
			buildDir, err = os.MkdirTemp("", "TestGoobj")
			if err != nil {
				return err
			}

			go1obj := filepath.Join(buildDir, "go1.o")
			go2obj := filepath.Join(buildDir, "go2.o")
			goarchive := filepath.Join(buildDir, "go.a")
			cgoarchive := ""

			gotool, err := testenv.GoTool()
			if err != nil {
				return err
			}

			go1src := filepath.Join("testdata", "go1.go")
			go2src := filepath.Join("testdata", "go2.go")

			importcfgfile := filepath.Join(buildDir, "importcfg")
			testenv.WriteImportcfg(t, importcfgfile, nil)

			out, err := exec.Command(gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go1obj, go1src).CombinedOutput()
			if err != nil {
				return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go1obj, go1src, err, out)
			}
			out, err = exec.Command(gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go2obj, go2src).CombinedOutput()
			if err != nil {
				return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go2obj, go2src, err, out)
			}
			out, err = exec.Command(gotool, "tool", "pack", "c", goarchive, go1obj, go2obj).CombinedOutput()
			if err != nil {
				return fmt.Errorf("go tool pack c %s %s %s: %v\n%s", goarchive, go1obj, go2obj, err, out)
			}

			if testenv.HasCGO() {
				cgoarchive = filepath.Join(buildDir, "mycgo.a")
				gopath := filepath.Join(buildDir, "gopath")
				err = copyDir(filepath.Join(gopath, "src", "mycgo"), filepath.Join("testdata", "mycgo"))
				if err == nil {
					err = os.WriteFile(filepath.Join(gopath, "src", "mycgo", "go.mod"), []byte("module mycgo\n"), 0666)
				}
				if err != nil {
					return err
				}
				cmd := exec.Command(gotool, "build", "-buildmode=archive", "-o", cgoarchive, "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "mycgo")
				cmd.Dir = filepath.Join(gopath, "src", "mycgo")
				cmd.Env = append(os.Environ(), "GOPATH="+gopath)
				out, err = cmd.CombinedOutput()
				if err != nil {
					return fmt.Errorf("go install mycgo: %v\n%s", err, out)
				}
			}

			builtGoobjs = goobjPaths{
				go1obj:     go1obj,
				go2obj:     go2obj,
				goarchive:  goarchive,
				cgoarchive: cgoarchive,
			}
			return nil
		}()
	})

	if buildErr != nil {
		t.Helper()
		t.Fatal(buildErr)
	}
	return builtGoobjs
}

func TestParseGoobj(t *testing.T) {
	path := buildGoobj(t).go1obj

	f, err := os.Open(path)
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()

	a, err := Parse(f, false)
	if err != nil {
		t.Fatal(err)
	}
	if len(a.Entries) != 2 {
		t.Errorf("expect 2 entry, found %d", len(a.Entries))
	}
	for _, e := range a.Entries {
		if e.Type == EntryPkgDef {
			continue
		}
		if e.Type != EntryGoObj {
			t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type)
		}
		if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
			t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
		}
	}
}

func TestParseArchive(t *testing.T) {
	path := buildGoobj(t).goarchive

	f, err := os.Open(path)
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()

	a, err := Parse(f, false)
	if err != nil {
		t.Fatal(err)
	}
	if len(a.Entries) != 3 {
		t.Errorf("expect 3 entry, found %d", len(a.Entries))
	}
	var found1 bool
	var found2 bool
	for _, e := range a.Entries {
		if e.Type == EntryPkgDef {
			continue
		}
		if e.Type != EntryGoObj {
			t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type)
		}
		if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
			t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
		}
		if e.Name == "go1.o" {
			found1 = true
		}
		if e.Name == "go2.o" {
			found2 = true
		}
	}
	if !found1 {
		t.Errorf(`object "go1.o" not found`)
	}
	if !found2 {
		t.Errorf(`object "go2.o" not found`)
	}
}

func TestParseCGOArchive(t *testing.T) {
	testenv.MustHaveCGO(t)

	path := buildGoobj(t).cgoarchive

	f, err := os.Open(path)
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()

	a, err := Parse(f, false)
	if err != nil {
		t.Fatal(err)
	}

	c1 := "c1"
	c2 := "c2"
	switch runtime.GOOS {
	case "darwin", "ios":
		c1 = "_" + c1
		c2 = "_" + c2
	case "windows":
		if runtime.GOARCH == "386" {
			c1 = "_" + c1
			c2 = "_" + c2
		}
	case "aix":
		c1 = "." + c1
		c2 = "." + c2
	}

	var foundgo, found1, found2 bool

	for _, e := range a.Entries {
		switch e.Type {
		default:
			t.Errorf("unknown object type")
		case EntryPkgDef:
			continue
		case EntryGoObj:
			foundgo = true
			if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
				t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
			}
			continue
		case EntryNativeObj:
		}

		obj := io.NewSectionReader(f, e.Offset, e.Size)
		switch runtime.GOOS {
		case "darwin", "ios":
			mf, err := macho.NewFile(obj)
			if err != nil {
				t.Fatal(err)
			}
			if mf.Symtab == nil {
				continue
			}
			for _, s := range mf.Symtab.Syms {
				switch s.Name {
				case c1:
					found1 = true
				case c2:
					found2 = true
				}
			}
		case "windows":
			pf, err := pe.NewFile(obj)
			if err != nil {
				t.Fatal(err)
			}
			for _, s := range pf.Symbols {
				switch s.Name {
				case c1:
					found1 = true
				case c2:
					found2 = true
				}
			}
		case "aix":
			xf, err := xcoff.NewFile(obj)
			if err != nil {
				t.Fatal(err)
			}
			for _, s := range xf.Symbols {
				switch s.Name {
				case c1:
					found1 = true
				case c2:
					found2 = true
				}
			}
		default: // ELF
			ef, err := elf.NewFile(obj)
			if err != nil {
				t.Fatal(err)
			}
			syms, err := ef.Symbols()
			if err != nil {
				t.Fatal(err)
			}
			for _, s := range syms {
				switch s.Name {
				case c1:
					found1 = true
				case c2:
					found2 = true
				}
			}
		}
	}

	if !foundgo {
		t.Errorf(`go object not found`)
	}
	if !found1 {
		t.Errorf(`symbol %q not found`, c1)
	}
	if !found2 {
		t.Errorf(`symbol %q not found`, c2)
	}
}

func TestExactly16Bytes(t *testing.T) {
	var tests = []string{
		"",
		"a",
		"日本語",
		"1234567890123456",
		"12345678901234567890",
		"1234567890123本語4567890",
		"12345678901234日本語567890",
		"123456789012345日本語67890",
		"1234567890123456日本語7890",
		"1234567890123456日本語7日本語890",
	}
	for _, str := range tests {
		got := exactly16Bytes(str)
		if len(got) != 16 {
			t.Errorf("exactly16Bytes(%q) is %q, length %d", str, got, len(got))
		}
		// Make sure it is full runes.
		for _, c := range got {
			if c == utf8.RuneError {
				t.Errorf("exactly16Bytes(%q) is %q, has partial rune", str, got)
			}
		}
	}
}
<commit_msg>cmd/internal/archive: use testenv.Command instead of exec.Command in tests<commit_after>// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package archive

import (
	"bytes"
	"debug/elf"
	"debug/macho"
	"debug/pe"
	"fmt"
	"internal/testenv"
	"internal/xcoff"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"testing"
	"unicode/utf8"
)

var buildDir string

func TestMain(m *testing.M) {
	if !testenv.HasGoBuild() {
		return
	}

	exit := m.Run()

	if buildDir != "" {
		os.RemoveAll(buildDir)
	}
	os.Exit(exit)
}

func copyDir(dst, src string) error {
	err := os.MkdirAll(dst, 0777)
	if err != nil {
		return err
	}
	entries, err := os.ReadDir(src)
	if err != nil {
		return err
	}
	for _, entry := range entries {
		err = copyFile(filepath.Join(dst, entry.Name()), filepath.Join(src, entry.Name()))
		if err != nil {
			return err
		}
	}
	return nil
}

func copyFile(dst, src string) (err error) {
	var s, d *os.File
	s, err = os.Open(src)
	if err != nil {
		return err
	}
	defer s.Close()
	d, err = os.Create(dst)
	if err != nil {
		return err
	}
	defer func() {
		e := d.Close()
		if err == nil {
			err = e
		}
	}()
	_, err = io.Copy(d, s)
	if err != nil {
		return err
	}
	return nil
}

var (
	buildOnce   sync.Once
	builtGoobjs goobjPaths
	buildErr    error
)

type goobjPaths struct {
	go1obj     string
	go2obj     string
	goarchive  string
	cgoarchive string
}

func buildGoobj(t *testing.T) goobjPaths {
	buildOnce.Do(func() {
		buildErr = func() (err error) {
			buildDir, err = os.MkdirTemp("", "TestGoobj")
			if err != nil {
				return err
			}

			go1obj := filepath.Join(buildDir, "go1.o")
			go2obj := filepath.Join(buildDir, "go2.o")
			goarchive := filepath.Join(buildDir, "go.a")
			cgoarchive := ""

			gotool, err := testenv.GoTool()
			if err != nil {
				return err
			}

			go1src := filepath.Join("testdata", "go1.go")
			go2src := filepath.Join("testdata", "go2.go")

			importcfgfile := filepath.Join(buildDir, "importcfg")
			testenv.WriteImportcfg(t, importcfgfile, nil)

			out, err := testenv.Command(t, gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go1obj, go1src).CombinedOutput()
			if err != nil {
				return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go1obj, go1src, err, out)
			}
			out, err = testenv.Command(t, gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go2obj, go2src).CombinedOutput()
			if err != nil {
				return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go2obj, go2src, err, out)
			}
			out, err = testenv.Command(t, gotool, "tool", "pack", "c", goarchive, go1obj, go2obj).CombinedOutput()
			if err != nil {
				return fmt.Errorf("go tool pack c %s %s %s: %v\n%s", goarchive, go1obj, go2obj, err, out)
			}

			if testenv.HasCGO() {
				cgoarchive = filepath.Join(buildDir, "mycgo.a")
				gopath := filepath.Join(buildDir, "gopath")
				err = copyDir(filepath.Join(gopath, "src", "mycgo"), filepath.Join("testdata", "mycgo"))
				if err == nil {
					err = os.WriteFile(filepath.Join(gopath, "src", "mycgo", "go.mod"), []byte("module mycgo\n"), 0666)
				}
				if err != nil {
					return err
				}
				cmd := testenv.Command(t, gotool, "build", "-buildmode=archive", "-o", cgoarchive, "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "mycgo")
				cmd.Dir = filepath.Join(gopath, "src", "mycgo")
				cmd.Env = append(os.Environ(), "GOPATH="+gopath)
				out, err = cmd.CombinedOutput()
				if err != nil {
					return fmt.Errorf("go install mycgo: %v\n%s", err, out)
				}
			}

			builtGoobjs = goobjPaths{
				go1obj:     go1obj,
				go2obj:     go2obj,
				goarchive:  goarchive,
				cgoarchive: cgoarchive,
			}
			return nil
		}()
	})

	if buildErr != nil {
		t.Helper()
		t.Fatal(buildErr)
	}
	return builtGoobjs
}

func TestParseGoobj(t *testing.T) {
	path := buildGoobj(t).go1obj

	f, err := os.Open(path)
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()

	a, err := Parse(f, false)
	if err != nil {
		t.Fatal(err)
	}
	if len(a.Entries) != 2 {
		t.Errorf("expect 2 entry, found %d", len(a.Entries))
	}
	for _, e := range a.Entries {
		if e.Type == EntryPkgDef {
			continue
		}
		if e.Type != EntryGoObj {
			t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type)
		}
		if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
			t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
		}
	}
}

func TestParseArchive(t *testing.T) {
	path := buildGoobj(t).goarchive

	f, err := os.Open(path)
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()

	a, err := Parse(f, false)
	if err != nil {
		t.Fatal(err)
	}
	if len(a.Entries) != 3 {
		t.Errorf("expect 3 entry, found %d", len(a.Entries))
	}
	var found1 bool
	var found2 bool
	for _, e := range a.Entries {
		if e.Type == EntryPkgDef {
			continue
		}
		if e.Type != EntryGoObj {
			t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type)
		}
		if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
			t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
		}
		if e.Name == "go1.o" {
			found1 = true
		}
		if e.Name == "go2.o" {
			found2 = true
		}
	}
	if !found1 {
		t.Errorf(`object "go1.o" not found`)
	}
	if !found2 {
		t.Errorf(`object "go2.o" not found`)
	}
}

func TestParseCGOArchive(t *testing.T) {
	testenv.MustHaveCGO(t)

	path := buildGoobj(t).cgoarchive

	f, err := os.Open(path)
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()

	a, err := Parse(f, false)
	if err != nil {
		t.Fatal(err)
	}

	c1 := "c1"
	c2 := "c2"
	switch runtime.GOOS {
	case "darwin", "ios":
		c1 = "_" + c1
		c2 = "_" + c2
	case "windows":
		if runtime.GOARCH == "386" {
			c1 = "_" + c1
			c2 = "_" + c2
		}
	case "aix":
		c1 = "." + c1
		c2 = "." + c2
	}

	var foundgo, found1, found2 bool

	for _, e := range a.Entries {
		switch e.Type {
		default:
			t.Errorf("unknown object type")
		case EntryPkgDef:
			continue
		case EntryGoObj:
			foundgo = true
			if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
				t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
			}
			continue
		case EntryNativeObj:
		}

		obj := io.NewSectionReader(f, e.Offset, e.Size)
		switch runtime.GOOS {
		case "darwin", "ios":
			mf, err := macho.NewFile(obj)
			if err != nil {
				t.Fatal(err)
			}
			if mf.Symtab == nil {
				continue
			}
			for _, s := range mf.Symtab.Syms {
				switch s.Name {
				case c1:
					found1 = true
				case c2:
					found2 = true
				}
			}
		case "windows":
			pf, err := pe.NewFile(obj)
			if err != nil {
				t.Fatal(err)
			}
			for _, s := range pf.Symbols {
				switch s.Name {
				case c1:
					found1 = true
				case c2:
					found2 = true
				}
			}
		case "aix":
			xf, err := xcoff.NewFile(obj)
			if err != nil {
				t.Fatal(err)
			}
			for _, s := range xf.Symbols {
				switch s.Name {
				case c1:
					found1 = true
				case c2:
					found2 = true
				}
			}
		default: // ELF
			ef, err := elf.NewFile(obj)
			if err != nil {
				t.Fatal(err)
			}
			syms, err := ef.Symbols()
			if err != nil {
				t.Fatal(err)
			}
			for _, s := range syms {
				switch s.Name {
				case c1:
					found1 = true
				case c2:
					found2 = true
				}
			}
		}
	}

	if !foundgo {
		t.Errorf(`go object not found`)
	}
	if !found1 {
		t.Errorf(`symbol %q not found`, c1)
	}
	if !found2 {
		t.Errorf(`symbol %q not found`, c2)
	}
}

func TestExactly16Bytes(t *testing.T) {
	var tests = []string{
		"",
		"a",
		"日本語",
		"1234567890123456",
		"12345678901234567890",
		"1234567890123本語4567890",
		"12345678901234日本語567890",
		"123456789012345日本語67890",
		"1234567890123456日本語7890",
		"1234567890123456日本語7日本語890",
	}
	for _, str := range tests {
		got := exactly16Bytes(str)
		if len(got) != 16 {
			t.Errorf("exactly16Bytes(%q) is %q, length %d", str, got, len(got))
		}
		// Make sure it is full runes.
		for _, c := range got {
			if c == utf8.RuneError {
				t.Errorf("exactly16Bytes(%q) is %q, has partial rune", str, got)
			}
		}
	}
}
<|endoftext|>


<commit_before>// +build linux,amd64

package devmapper

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"

	"github.com/dotcloud/docker/daemon/graphdriver"
	"github.com/dotcloud/docker/pkg/mount"
	"github.com/dotcloud/docker/utils"
)

func init() {
	graphdriver.Register("devicemapper", Init)
}

// Placeholder interfaces, to be replaced
// at integration.

// End of placeholder interfaces.

type Driver struct {
	*DeviceSet
	home string
}

func Init(home string, options []string) (graphdriver.Driver, error) {
	deviceSet, err := NewDeviceSet(home, true, options)
	if err != nil {
		return nil, err
	}

	if err := graphdriver.MakePrivate(home); err != nil {
		return nil, err
	}

	d := &Driver{
		DeviceSet: deviceSet,
		home:      home,
	}

	return d, nil
}

func (d *Driver) String() string {
	return "devicemapper"
}

func (d *Driver) Status() [][2]string {
	s := d.DeviceSet.Status()

	status := [][2]string{
		{"Pool Name", s.PoolName},
		{"Data file", s.DataLoopback},
		{"Metadata file", s.MetadataLoopback},
		{"Data Space Used", fmt.Sprintf("%.1f Mb", float64(s.Data.Used)/(1024*1024))},
		{"Data Space Total", fmt.Sprintf("%.1f Mb", float64(s.Data.Total)/(1024*1024))},
		{"Metadata Space Used", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Used)/(1024*1024))},
		{"Metadata Space Total", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Total)/(1024*1024))},
	}
	return status
}

func (d *Driver) Cleanup() error {
	err := d.DeviceSet.Shutdown()

	if err2 := mount.Unmount(d.home); err == nil {
		err = err2
	}

	return err
}

func (d *Driver) Create(id, parent string) error {
	if err := d.DeviceSet.AddDevice(id, parent); err != nil {
		return err
	}

	return nil
}

func (d *Driver) Remove(id string) error {
	if !d.DeviceSet.HasDevice(id) {
		// Consider removing a non-existing device a no-op
		// This is useful to be able to progress on container removal
		// if the underlying device has gone away due to earlier errors
		return nil
	}

	// This assumes the device has been properly Get/Put:ed and thus is unmounted
	if err := d.DeviceSet.DeleteDevice(id); err != nil {
		return err
	}

	mp := path.Join(d.home, "mnt", id)
	if err := os.RemoveAll(mp); err != nil && !os.IsNotExist(err) {
		return err
	}

	return nil
}

func (d *Driver) Get(id, mountLabel string) (string, error) {
	mp := path.Join(d.home, "mnt", id)

	// Create the target directories if they don't exist
	if err := os.MkdirAll(mp, 0755); err != nil && !os.IsExist(err) {
		return "", err
	}

	// Mount the device
	if err := d.DeviceSet.MountDevice(id, mp, mountLabel); err != nil {
		return "", err
	}

	rootFs := path.Join(mp, "rootfs")
	if err := os.MkdirAll(rootFs, 0755); err != nil && !os.IsExist(err) {
		d.DeviceSet.UnmountDevice(id)
		return "", err
	}

	idFile := path.Join(mp, "id")
	if _, err := os.Stat(idFile); err != nil && os.IsNotExist(err) {
		// Create an "id" file with the container/image id in it to help reconscruct this in case
		// of later problems
		if err := ioutil.WriteFile(idFile, []byte(id), 0600); err != nil {
			d.DeviceSet.UnmountDevice(id)
			return "", err
		}
	}

	return rootFs, nil
}

func (d *Driver) Put(id string) {
	if err := d.DeviceSet.UnmountDevice(id); err != nil {
		utils.Errorf("Warning: error unmounting device %s: %s\n", id, err)
	}
}

func (d *Driver) Exists(id string) bool {
	return d.DeviceSet.HasDevice(id)
}
<commit_msg>devmapper: add thin-pool blocksize to the 'docker info' output<commit_after>// +build linux,amd64

package devmapper

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"

	"github.com/dotcloud/docker/daemon/graphdriver"
	"github.com/dotcloud/docker/pkg/mount"
	"github.com/dotcloud/docker/utils"
)

func init() {
	graphdriver.Register("devicemapper", Init)
}

// Placeholder interfaces, to be replaced
// at integration.

// End of placeholder interfaces.

type Driver struct {
	*DeviceSet
	home string
}

func Init(home string, options []string) (graphdriver.Driver, error) {
	deviceSet, err := NewDeviceSet(home, true, options)
	if err != nil {
		return nil, err
	}

	if err := graphdriver.MakePrivate(home); err != nil {
		return nil, err
	}

	d := &Driver{
		DeviceSet: deviceSet,
		home:      home,
	}

	return d, nil
}

func (d *Driver) String() string {
	return "devicemapper"
}

func (d *Driver) Status() [][2]string {
	s := d.DeviceSet.Status()

	status := [][2]string{
		{"Pool Name", s.PoolName},
		{"Pool Blocksize", fmt.Sprintf("%d Kb", s.SectorSize/1024)},
		{"Data file", s.DataLoopback},
		{"Metadata file", s.MetadataLoopback},
		{"Data Space Used", fmt.Sprintf("%.1f Mb", float64(s.Data.Used)/(1024*1024))},
		{"Data Space Total", fmt.Sprintf("%.1f Mb", float64(s.Data.Total)/(1024*1024))},
		{"Metadata Space Used", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Used)/(1024*1024))},
		{"Metadata Space Total", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Total)/(1024*1024))},
	}
	return status
}

func (d *Driver) Cleanup() error {
	err := d.DeviceSet.Shutdown()

	if err2 := mount.Unmount(d.home); err == nil {
		err = err2
	}

	return err
}

func (d *Driver) Create(id, parent string) error {
	if err := d.DeviceSet.AddDevice(id, parent); err != nil {
		return err
	}

	return nil
}

func (d *Driver) Remove(id string) error {
	if !d.DeviceSet.HasDevice(id) {
		// Consider removing a non-existing device a no-op
		// This is useful to be able to progress on container removal
		// if the underlying device has gone away due to earlier errors
		return nil
	}

	// This assumes the device has been properly Get/Put:ed and thus is unmounted
	if err := d.DeviceSet.DeleteDevice(id); err != nil {
		return err
	}

	mp := path.Join(d.home, "mnt", id)
	if err := os.RemoveAll(mp); err != nil && !os.IsNotExist(err) {
		return err
	}

	return nil
}

func (d *Driver) Get(id, mountLabel string) (string, error) {
	mp := path.Join(d.home, "mnt", id)

	// Create the target directories if they don't exist
	if err := os.MkdirAll(mp, 0755); err != nil && !os.IsExist(err) {
		return "", err
	}

	// Mount the device
	if err := d.DeviceSet.MountDevice(id, mp, mountLabel); err != nil {
		return "", err
	}

	rootFs := path.Join(mp, "rootfs")
	if err := os.MkdirAll(rootFs, 0755); err != nil && !os.IsExist(err) {
		d.DeviceSet.UnmountDevice(id)
		return "", err
	}

	idFile := path.Join(mp, "id")
	if _, err := os.Stat(idFile); err != nil && os.IsNotExist(err) {
		// Create an "id" file with the container/image id in it to help reconscruct this in case
		// of later problems
		if err := ioutil.WriteFile(idFile, []byte(id), 0600); err != nil {
			d.DeviceSet.UnmountDevice(id)
			return "", err
		}
	}

	return rootFs, nil
}

func (d *Driver) Put(id string) {
	if err := d.DeviceSet.UnmountDevice(id); err != nil {
		utils.Errorf("Warning: error unmounting device %s: %s\n", id, err)
	}
}

func (d *Driver) Exists(id string) bool {
	return d.DeviceSet.HasDevice(id)
}
<|endoftext|>


<commit_before>package db

import (
	"fmt"

	"github.com/boltdb/bolt"
)

// LookupItemsMultiModStrideLength determines how many items
// is included in a stride of LookupItemsMultiMod.
//
// Longer strides mean fewer intersections but more potentially useless
// item mods checked.
const LookupItemsMultiModStrideLength = 32

// IndexQuery represents a query running over established indices
//
// An IndexQuery can be rerun by reinitializing the ctx; this typically
// happens when the query is Run.
type IndexQuery struct {
	// Type and flavor of the item we're looking up
	rootType, rootFlavor StringHeapID
	// Mods we are looking for
	mods []StringHeapID
	// Minimum mod values we are required to find
	//
	// Positionally related to mods
	minModValues []uint16
	// League we are searching for
	league LeagueHeapID
	// How many items we are limited to finding
	maxDesired int
	// Context necessary for a query to run
	ctx *indexQueryContext
}

// indexQueryContext represents the necessary transaction-dependent
// context for an IndexQuery to run.
type indexQueryContext struct {
	tx           *bolt.Tx
	validCursors int
	// Cursors we iterate over to perform our query
	//
	// These are positionally related to the parent's IndexQuery.mods
	cursors []*bolt.Cursor
	sets    []map[ID]struct{}
}

// Remove a given cursor from tracking on the context
func (ctx *indexQueryContext) removeCursor(index int) {
	ctx.cursors[index] = nil
	ctx.validCursors--
}

// NewIndexQuery returns an IndexQuery with no context
func NewIndexQuery(rootType, rootFlavor StringHeapID,
	mods []StringHeapID, minModValues []uint16,
	league LeagueHeapID,
	maxDesired int) IndexQuery {

	return IndexQuery{
		rootType, rootFlavor,
		mods, minModValues,
		league, maxDesired, nil,
	}

}

// initContext prepares transaction dependent context for an IndexQuery
func (q *IndexQuery) initContext(tx *bolt.Tx) error {

	// Make a place to keep our cursors
	//
	// NOTE: a cursor can be nil to indicate it should not be queried
	cursors := make([]*bolt.Cursor, len(q.mods))

	// Keep track of how many cursors are valid,
	// this will let us know when we've exhausted our data
	validCursors := len(cursors)

	// Collect our buckets for each mod and establish cursors
	for i, mod := range q.mods {
		itemModBucket, err := getItemModIndexBucketRO(q.rootType, q.rootFlavor,
			mod, q.league, tx)
		if err != nil {
			return fmt.Errorf("faield to get item mod index bucket, mod=%d err=%s",
				mod, err)
		}
		cursors[i] = itemModBucket.Cursor()
	}

	// Create our item sets
	sets := make([]map[ID]struct{}, len(q.mods))
	for i := range sets {
		sets[i] = make(map[ID]struct{})
	}

	q.ctx = &indexQueryContext{
		tx, validCursors, cursors, sets,
	}

	return nil
}

// clearContext removes transaction dependent context from IndexQuery
func (q *IndexQuery) clearContext() {
	q.ctx = nil
}

// checkPair determines if a pair is acceptable for our query
// and modifes the associated modIndex Cursor appropriately.
func (q *IndexQuery) checkPair(k, v []byte, modIndex int) (bool, error) {
	// Grab the value
	values, err := decodeModIndexKey(k)
	if err != nil {
		return false,
			fmt.Errorf("failed to decode mod index key, err=%s", err)
	}
	if len(values) == 0 {
		return false,
			fmt.Errorf("decoded item mod index key to no values, key=%v", k)
	}

	// Ensure the mod is the correct value
	valid := values[0] >= q.minModValues[modIndex]
	if valid {
		if len(v) != IDSize {
			panic(fmt.Sprintf("malformed id value in index, incorrect length; id=%v", v))
		}
		// NOTE: the copy here is actually completely required
		// due to the fact that boltdb makes no guarantee regarding what
		// keys and value slices contain when outside a transaction.
		var id ID
		copy(id[:], v)
		q.ctx.sets[modIndex][id] = struct{}{}
	} else {
		// Remove from cursors we're interested in
		q.ctx.removeCursor(modIndex)
	}

	return valid, nil
}

// stide performs a single stride on the query, filling sets on ctx
// as appropriate and also invalidates cursors which are useless
func (q *IndexQuery) stride() error {

	// Go over each cursor
	for i, c := range q.ctx.cursors {
		// Handle nil cursor indicating that mod
		// has no more legitimate values
		if c == nil {
			continue
		}

		// Perform the actual per-cursor stride
		for index := 0; index < LookupItemsMultiModStrideLength; index++ {

			// Grab a pair
			k, v := c.Prev()
			// Ignore nested buckets but also
			// handle reaching the start of the bucket
			if k == nil {
				// Both nil means we're done
				if v == nil {
					q.ctx.removeCursor(i)
					break
				}
				continue
			}
			valid, err := q.checkPair(k, v, i)
			if err != nil {
				return fmt.Errorf("failed to check value pair, err=%s", err)
			}

			// If its not a valid pair, we're done iterating on this cursor
			if !valid {
				break
			}
		}
	}
	return nil
}

// intersectItemIDMaps returns how many items in the given sets appear
// across all them. The found items have their IDs put in result
// up to its length.
//
// Pass a nil result to obtain just the count
func (q *IndexQuery) intersectIDSets(result []ID) int {

	// Keep track of our maximum matches
	//
	// When nil result, we account for that in logic
	n := len(result)

	// And how many we have found so far
	foundIDs := 0

	// Intersect the sets by taking one of them
	// and seeing how many of its items appear in others
	firstSet := q.ctx.sets[0]
	for id := range firstSet {
		// sharedCount always starts at one because it
		//  is always shared with the firstSet
		sharedCount := 1
		for _, other := range q.ctx.sets[1:] {
			_, ok := other[id]
			if !ok {
				// No point in continuing to look at unshared items
				break
			}
			sharedCount++
		}
		if sharedCount == len(q.ctx.sets) {
			foundIDs++
			// Add the item if we need to
			if result != nil {
				result[foundIDs-1] = id
			}
			// Exit early if we reach capacity
			if result != nil && foundIDs >= n {
				return foundIDs
			}
		}
	}

	return foundIDs
}

// Run initialises transaction context for a query and attempts
// to find desired items.
func (q *IndexQuery) Run(db *bolt.DB) ([]ID, error) {

	// ids presized...
	var ids []ID

	err := db.View(func(tx *bolt.Tx) error {

		err := q.initContext(tx)
		if err != nil {
			return fmt.Errorf("failed to initialize query context")
		}
		// Always clear the context when we exit
		defer q.clearContext()

		// Set all of our cursors to be at their ends
		for i, c := range q.ctx.cursors {
			// Set to last
			k, v := c.Last()
			// Ignore nested buckets
			if k == nil {
				continue
			}
			// Check the pair, we only care about possible errors here
			if _, err := q.checkPair(k, v, i); err != nil {
				return fmt.Errorf("failed to check value in bucekt, err=%s", err)
			}
		}

		// Perform our strides to search
		var foundIDs int
		for foundIDs < q.maxDesired && q.ctx.validCursors > 0 {
			// Iterate for a stride
			err := q.stride()
			if err != nil {
				return fmt.Errorf("failed a stride, err=%s", err)
			}

			foundIDs = q.intersectIDSets(nil)
		}

		// Cap result to desired length as required
		if foundIDs > q.maxDesired {
			foundIDs = q.maxDesired
		}
		// Perform one more intersection to find our return value
		ids = make([]ID, foundIDs)
		q.intersectIDSets(ids)

		return nil
	})

	return ids, err
}
<commit_msg>db IndexQuery LookupItemsMultiModStrideLength to 1<commit_after>package db

import (
	"fmt"

	"github.com/boltdb/bolt"
)

// LookupItemsMultiModStrideLength determines how many items
// is included in a stride of LookupItemsMultiMod.
//
// Longer strides mean fewer intersections but more potentially useless
// item mods checked.
const LookupItemsMultiModStrideLength = 1

// IndexQuery represents a query running over established indices
//
// An IndexQuery can be rerun by reinitializing the ctx; this typically
// happens when the query is Run.
type IndexQuery struct {
	// Type and flavor of the item we're looking up
	rootType, rootFlavor StringHeapID
	// Mods we are looking for
	mods []StringHeapID
	// Minimum mod values we are required to find
	//
	// Positionally related to mods
	minModValues []uint16
	// League we are searching for
	league LeagueHeapID
	// How many items we are limited to finding
	maxDesired int
	// Context necessary for a query to run
	ctx *indexQueryContext
}

// indexQueryContext represents the necessary transaction-dependent
// context for an IndexQuery to run.
type indexQueryContext struct {
	tx           *bolt.Tx
	validCursors int
	// Cursors we iterate over to perform our query
	//
	// These are positionally related to the parent's IndexQuery.mods
	cursors []*bolt.Cursor
	sets    []map[ID]struct{}
}

// Remove a given cursor from tracking on the context
func (ctx *indexQueryContext) removeCursor(index int) {
	ctx.cursors[index] = nil
	ctx.validCursors--
}

// NewIndexQuery returns an IndexQuery with no context
func NewIndexQuery(rootType, rootFlavor StringHeapID,
	mods []StringHeapID, minModValues []uint16,
	league LeagueHeapID,
	maxDesired int) IndexQuery {

	return IndexQuery{
		rootType, rootFlavor,
		mods, minModValues,
		league, maxDesired, nil,
	}

}

// initContext prepares transaction dependent context for an IndexQuery
func (q *IndexQuery) initContext(tx *bolt.Tx) error {

	// Make a place to keep our cursors
	//
	// NOTE: a cursor can be nil to indicate it should not be queried
	cursors := make([]*bolt.Cursor, len(q.mods))

	// Keep track of how many cursors are valid,
	// this will let us know when we've exhausted our data
	validCursors := len(cursors)

	// Collect our buckets for each mod and establish cursors
	for i, mod := range q.mods {
		itemModBucket, err := getItemModIndexBucketRO(q.rootType, q.rootFlavor,
			mod, q.league, tx)
		if err != nil {
			return fmt.Errorf("faield to get item mod index bucket, mod=%d err=%s",
				mod, err)
		}
		cursors[i] = itemModBucket.Cursor()
	}

	// Create our item sets
	sets := make([]map[ID]struct{}, len(q.mods))
	for i := range sets {
		sets[i] = make(map[ID]struct{})
	}

	q.ctx = &indexQueryContext{
		tx, validCursors, cursors, sets,
	}

	return nil
}

// clearContext removes transaction dependent context from IndexQuery
func (q *IndexQuery) clearContext() {
	q.ctx = nil
}

// checkPair determines if a pair is acceptable for our query
// and modifes the associated modIndex Cursor appropriately.
func (q *IndexQuery) checkPair(k, v []byte, modIndex int) (bool, error) {
	// Grab the value
	values, err := decodeModIndexKey(k)
	if err != nil {
		return false,
			fmt.Errorf("failed to decode mod index key, err=%s", err)
	}
	if len(values) == 0 {
		return false,
			fmt.Errorf("decoded item mod index key to no values, key=%v", k)
	}

	// Ensure the mod is the correct value
	valid := values[0] >= q.minModValues[modIndex]
	if valid {
		if len(v) != IDSize {
			panic(fmt.Sprintf("malformed id value in index, incorrect length; id=%v", v))
		}
		// NOTE: the copy here is actually completely required
		// due to the fact that boltdb makes no guarantee regarding what
		// keys and value slices contain when outside a transaction.
		var id ID
		copy(id[:], v)
		q.ctx.sets[modIndex][id] = struct{}{}
	} else {
		// Remove from cursors we're interested in
		q.ctx.removeCursor(modIndex)
	}

	return valid, nil
}

// stide performs a single stride on the query, filling sets on ctx
// as appropriate and also invalidates cursors which are useless
func (q *IndexQuery) stride() error {

	// Go over each cursor
	for i, c := range q.ctx.cursors {
		// Handle nil cursor indicating that mod
		// has no more legitimate values
		if c == nil {
			continue
		}

		// Perform the actual per-cursor stride
		for index := 0; index < LookupItemsMultiModStrideLength; index++ {

			// Grab a pair
			k, v := c.Prev()
			// Ignore nested buckets but also
			// handle reaching the start of the bucket
			if k == nil {
				// Both nil means we're done
				if v == nil {
					q.ctx.removeCursor(i)
					break
				}
				continue
			}
			valid, err := q.checkPair(k, v, i)
			if err != nil {
				return fmt.Errorf("failed to check value pair, err=%s", err)
			}

			// If its not a valid pair, we're done iterating on this cursor
			if !valid {
				break
			}
		}
	}
	return nil
}

// intersectItemIDMaps returns how many items in the given sets appear
// across all them. The found items have their IDs put in result
// up to its length.
//
// Pass a nil result to obtain just the count
func (q *IndexQuery) intersectIDSets(result []ID) int {

	// Keep track of our maximum matches
	//
	// When nil result, we account for that in logic
	n := len(result)

	// And how many we have found so far
	foundIDs := 0

	// Intersect the sets by taking one of them
	// and seeing how many of its items appear in others
	firstSet := q.ctx.sets[0]
	for id := range firstSet {
		// sharedCount always starts at one because it
		//  is always shared with the firstSet
		sharedCount := 1
		for _, other := range q.ctx.sets[1:] {
			_, ok := other[id]
			if !ok {
				// No point in continuing to look at unshared items
				break
			}
			sharedCount++
		}
		if sharedCount == len(q.ctx.sets) {
			foundIDs++
			// Add the item if we need to
			if result != nil {
				result[foundIDs-1] = id
			}
			// Exit early if we reach capacity
			if result != nil && foundIDs >= n {
				return foundIDs
			}
		}
	}

	return foundIDs
}

// Run initialises transaction context for a query and attempts
// to find desired items.
func (q *IndexQuery) Run(db *bolt.DB) ([]ID, error) {

	// ids presized...
	var ids []ID

	err := db.View(func(tx *bolt.Tx) error {

		err := q.initContext(tx)
		if err != nil {
			return fmt.Errorf("failed to initialize query context")
		}
		// Always clear the context when we exit
		defer q.clearContext()

		// Set all of our cursors to be at their ends
		for i, c := range q.ctx.cursors {
			// Set to last
			k, v := c.Last()
			// Ignore nested buckets
			if k == nil {
				continue
			}
			// Check the pair, we only care about possible errors here
			if _, err := q.checkPair(k, v, i); err != nil {
				return fmt.Errorf("failed to check value in bucekt, err=%s", err)
			}
		}

		// Perform our strides to search
		var foundIDs int
		for foundIDs < q.maxDesired && q.ctx.validCursors > 0 {
			// Iterate for a stride
			err := q.stride()
			if err != nil {
				return fmt.Errorf("failed a stride, err=%s", err)
			}

			foundIDs = q.intersectIDSets(nil)
		}

		// Cap result to desired length as required
		if foundIDs > q.maxDesired {
			foundIDs = q.maxDesired
		}
		// Perform one more intersection to find our return value
		ids = make([]ID, foundIDs)
		q.intersectIDSets(ids)

		return nil
	})

	return ids, err
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

// Package reporter implements a reporter interface for github
// TODO(krzyzacy): move logic from report.go here
package github

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/sirupsen/logrus"
	ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"

	v1 "k8s.io/test-infra/prow/apis/prowjobs/v1"
	"k8s.io/test-infra/prow/config"
	"k8s.io/test-infra/prow/crier/reporters/criercommonlib"
	"k8s.io/test-infra/prow/gerrit/client"
	"k8s.io/test-infra/prow/github/report"
	"k8s.io/test-infra/prow/kube"
)

const (
	// GitHubReporterName is the name for github reporter
	GitHubReporterName = "github-reporter"
)

// Client is a github reporter client
type Client struct {
	gc          report.GitHubClient
	config      config.Getter
	reportAgent v1.ProwJobAgent
	prLocks     *criercommonlib.ShardedLock
	lister      ctrlruntimeclient.Reader
}

// NewReporter returns a reporter client
func NewReporter(gc report.GitHubClient, cfg config.Getter, reportAgent v1.ProwJobAgent, lister ctrlruntimeclient.Reader) *Client {
	c := &Client{
		gc:          gc,
		config:      cfg,
		reportAgent: reportAgent,
		prLocks:     criercommonlib.NewShardedLock(),
		lister:      lister,
	}
	c.prLocks.RunCleanup()
	return c
}

// GetName returns the name of the reporter
func (c *Client) GetName() string {
	return GitHubReporterName
}

// ShouldReport returns if this prowjob should be reported by the github reporter
func (c *Client) ShouldReport(_ context.Context, _ *logrus.Entry, pj *v1.ProwJob) bool {
	if !pj.Spec.Report {
		return false
	}

	switch {
	case pj.Labels[client.GerritReportLabel] != "":
		return false // TODO(fejta): opt-in to github reporting
	case pj.Spec.Type != v1.PresubmitJob && pj.Spec.Type != v1.PostsubmitJob:
		return false // Report presubmit and postsubmit github jobs for github reporter
	case c.reportAgent != "" && pj.Spec.Agent != c.reportAgent:
		return false // Only report for specified agent
	}

	return true
}

// Report will report via reportlib
func (c *Client) Report(ctx context.Context, log *logrus.Entry, pj *v1.ProwJob) ([]*v1.ProwJob, *reconcile.Result, error) {
	ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
	defer cancel()

	// TODO(krzyzacy): ditch ReportTemplate, and we can drop reference to config.Getter
	err := report.ReportStatusContext(ctx, c.gc, *pj, c.config().GitHubReporter)
	if err != nil {
		if strings.Contains(err.Error(), "This SHA and context has reached the maximum number of statuses") {
			// This is completely unrecoverable, so just swallow the error to make sure we wont retry, even when crier gets restarted.
			log.WithError(err).Debug("Encountered an error, skipping retries")
			err = nil
		} else if strings.Contains(err.Error(), "\"message\":\"Not Found\"") || strings.Contains(err.Error(), "\"message\":\"No commit found for SHA:") {
			// "message":"Not Found" error occurs when someone force push, which is not a crier error
			log.WithError(err).Debug("Could not find PR commit, skipping retries")
			err = nil
		}
		// Always return when there is any error reporting status context.
		return []*v1.ProwJob{pj}, nil, err
	}

	// The github comment create/update/delete done for presubmits
	// needs pr-level locking to avoid racing when reporting multiple
	// jobs in parallel.
	if pj.Spec.Type == v1.PresubmitJob {
		key, err := lockKeyForPJ(pj)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to get lockkey for job: %w", err)
		}
		lock, err := c.prLocks.GetLock(ctx, *key)
		if err != nil {
			return nil, nil, err
		}
		if err := lock.Acquire(ctx, 1); err != nil {
			return nil, nil, err
		}
		defer lock.Release(1)
	}

	// Check if this org or repo has opted out of failure report comments.
	// This check has to be here and not in ShouldReport as we always need to report
	// the status context, just potentially not creating a comment.
	refs := pj.Spec.Refs
	fullRepo := fmt.Sprintf("%s/%s", refs.Org, refs.Repo)
	for _, ident := range c.config().GitHubReporter.NoCommentRepos {
		if refs.Org == ident || fullRepo == ident {
			return []*v1.ProwJob{pj}, nil, nil
		}
	}
	// Check if this org or repo has opted out of failure report comments
	toReport := []v1.ProwJob{*pj}
	var mustCreateComment bool
	for _, ident := range c.config().GitHubReporter.SummaryCommentRepos {
		if pj.Spec.Refs.Org == ident || fullRepo == ident {
			mustCreateComment = true
			toReport, err = pjsToReport(ctx, log, c.lister, pj)
			if err != nil {
				return []*v1.ProwJob{pj}, nil, err
			}
		}
	}
	err = report.ReportComment(ctx, c.gc, c.config().Plank.ReportTemplateForRepo(pj.Spec.Refs), toReport, c.config().GitHubReporter, mustCreateComment)

	return []*v1.ProwJob{pj}, nil, err
}

func pjsToReport(ctx context.Context, log *logrus.Entry, lister ctrlruntimeclient.Reader, pj *v1.ProwJob) ([]v1.ProwJob, error) {
	if len(pj.Spec.Refs.Pulls) != 1 {
		return nil, nil
	}
	// find all prowjobs from this PR
	selector := map[string]string{}
	for _, l := range []string{kube.OrgLabel, kube.RepoLabel, kube.PullLabel} {
		selector[l] = pj.ObjectMeta.Labels[l]
	}
	var pjs v1.ProwJobList
	if err := lister.List(ctx, &pjs, ctrlruntimeclient.MatchingLabels(selector)); err != nil {
		return nil, fmt.Errorf("Cannot list prowjob with selector %v", selector)
	}

	latestBatch := make(map[string]v1.ProwJob)
	for _, pjob := range pjs.Items {
		if !pjob.Complete() { // Any job still running should prevent from comments
			return nil, nil
		}
		if !pj.Spec.Report { // Filtering out non-reporting jobs
			continue
		}
		// Now you have convinced me that you are the same job from my revision,
		// continue convince me that you are the last one of your kind
		if existing, ok := latestBatch[pjob.Spec.Job]; !ok {
			latestBatch[pjob.Spec.Job] = pjob
		} else if pjob.CreationTimestamp.After(existing.CreationTimestamp.Time) {
			latestBatch[pjob.Spec.Job] = pjob
		}
	}

	var toReport []v1.ProwJob
	for _, pjob := range latestBatch {
		toReport = append(toReport, pjob)
	}

	return toReport, nil
}

func lockKeyForPJ(pj *v1.ProwJob) (*criercommonlib.SimplePull, error) {
	if pj.Spec.Type != v1.PresubmitJob {
		return nil, fmt.Errorf("can only get lock key for presubmit jobs, was %q", pj.Spec.Type)
	}
	if pj.Spec.Refs == nil {
		return nil, errors.New("pj.Spec.Refs is nil")
	}
	if n := len(pj.Spec.Refs.Pulls); n != 1 {
		return nil, fmt.Errorf("prowjob doesn't have one but %d pulls", n)
	}
	return criercommonlib.NewSimplePull(pj.Spec.Refs.Org, pj.Spec.Refs.Repo, pj.Spec.Refs.Pulls[0].Number), nil
}
<commit_msg>checking report field on the correct pj<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

// Package reporter implements a reporter interface for github
// TODO(krzyzacy): move logic from report.go here
package github

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/sirupsen/logrus"
	ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"

	v1 "k8s.io/test-infra/prow/apis/prowjobs/v1"
	"k8s.io/test-infra/prow/config"
	"k8s.io/test-infra/prow/crier/reporters/criercommonlib"
	"k8s.io/test-infra/prow/gerrit/client"
	"k8s.io/test-infra/prow/github/report"
	"k8s.io/test-infra/prow/kube"
)

const (
	// GitHubReporterName is the name for github reporter
	GitHubReporterName = "github-reporter"
)

// Client is a github reporter client
type Client struct {
	gc          report.GitHubClient
	config      config.Getter
	reportAgent v1.ProwJobAgent
	prLocks     *criercommonlib.ShardedLock
	lister      ctrlruntimeclient.Reader
}

// NewReporter returns a reporter client
func NewReporter(gc report.GitHubClient, cfg config.Getter, reportAgent v1.ProwJobAgent, lister ctrlruntimeclient.Reader) *Client {
	c := &Client{
		gc:          gc,
		config:      cfg,
		reportAgent: reportAgent,
		prLocks:     criercommonlib.NewShardedLock(),
		lister:      lister,
	}
	c.prLocks.RunCleanup()
	return c
}

// GetName returns the name of the reporter
func (c *Client) GetName() string {
	return GitHubReporterName
}

// ShouldReport returns if this prowjob should be reported by the github reporter
func (c *Client) ShouldReport(_ context.Context, _ *logrus.Entry, pj *v1.ProwJob) bool {
	if !pj.Spec.Report {
		return false
	}

	switch {
	case pj.Labels[client.GerritReportLabel] != "":
		return false // TODO(fejta): opt-in to github reporting
	case pj.Spec.Type != v1.PresubmitJob && pj.Spec.Type != v1.PostsubmitJob:
		return false // Report presubmit and postsubmit github jobs for github reporter
	case c.reportAgent != "" && pj.Spec.Agent != c.reportAgent:
		return false // Only report for specified agent
	}

	return true
}

// Report will report via reportlib
func (c *Client) Report(ctx context.Context, log *logrus.Entry, pj *v1.ProwJob) ([]*v1.ProwJob, *reconcile.Result, error) {
	ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
	defer cancel()

	// TODO(krzyzacy): ditch ReportTemplate, and we can drop reference to config.Getter
	err := report.ReportStatusContext(ctx, c.gc, *pj, c.config().GitHubReporter)
	if err != nil {
		if strings.Contains(err.Error(), "This SHA and context has reached the maximum number of statuses") {
			// This is completely unrecoverable, so just swallow the error to make sure we wont retry, even when crier gets restarted.
			log.WithError(err).Debug("Encountered an error, skipping retries")
			err = nil
		} else if strings.Contains(err.Error(), "\"message\":\"Not Found\"") || strings.Contains(err.Error(), "\"message\":\"No commit found for SHA:") {
			// "message":"Not Found" error occurs when someone force push, which is not a crier error
			log.WithError(err).Debug("Could not find PR commit, skipping retries")
			err = nil
		}
		// Always return when there is any error reporting status context.
		return []*v1.ProwJob{pj}, nil, err
	}

	// The github comment create/update/delete done for presubmits
	// needs pr-level locking to avoid racing when reporting multiple
	// jobs in parallel.
	if pj.Spec.Type == v1.PresubmitJob {
		key, err := lockKeyForPJ(pj)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to get lockkey for job: %w", err)
		}
		lock, err := c.prLocks.GetLock(ctx, *key)
		if err != nil {
			return nil, nil, err
		}
		if err := lock.Acquire(ctx, 1); err != nil {
			return nil, nil, err
		}
		defer lock.Release(1)
	}

	// Check if this org or repo has opted out of failure report comments.
	// This check has to be here and not in ShouldReport as we always need to report
	// the status context, just potentially not creating a comment.
	refs := pj.Spec.Refs
	fullRepo := fmt.Sprintf("%s/%s", refs.Org, refs.Repo)
	for _, ident := range c.config().GitHubReporter.NoCommentRepos {
		if refs.Org == ident || fullRepo == ident {
			return []*v1.ProwJob{pj}, nil, nil
		}
	}
	// Check if this org or repo has opted out of failure report comments
	toReport := []v1.ProwJob{*pj}
	var mustCreateComment bool
	for _, ident := range c.config().GitHubReporter.SummaryCommentRepos {
		if pj.Spec.Refs.Org == ident || fullRepo == ident {
			mustCreateComment = true
			toReport, err = pjsToReport(ctx, log, c.lister, pj)
			if err != nil {
				return []*v1.ProwJob{pj}, nil, err
			}
		}
	}
	err = report.ReportComment(ctx, c.gc, c.config().Plank.ReportTemplateForRepo(pj.Spec.Refs), toReport, c.config().GitHubReporter, mustCreateComment)

	return []*v1.ProwJob{pj}, nil, err
}

func pjsToReport(ctx context.Context, log *logrus.Entry, lister ctrlruntimeclient.Reader, pj *v1.ProwJob) ([]v1.ProwJob, error) {
	if len(pj.Spec.Refs.Pulls) != 1 {
		return nil, nil
	}
	// find all prowjobs from this PR
	selector := map[string]string{}
	for _, l := range []string{kube.OrgLabel, kube.RepoLabel, kube.PullLabel} {
		selector[l] = pj.ObjectMeta.Labels[l]
	}
	var pjs v1.ProwJobList
	if err := lister.List(ctx, &pjs, ctrlruntimeclient.MatchingLabels(selector)); err != nil {
		return nil, fmt.Errorf("Cannot list prowjob with selector %v", selector)
	}

	latestBatch := make(map[string]v1.ProwJob)
	for _, pjob := range pjs.Items {
		if !pjob.Complete() { // Any job still running should prevent from comments
			return nil, nil
		}
		if !pjob.Spec.Report { // Filtering out non-reporting jobs
			continue
		}
		// Now you have convinced me that you are the same job from my revision,
		// continue convince me that you are the last one of your kind
		if existing, ok := latestBatch[pjob.Spec.Job]; !ok {
			latestBatch[pjob.Spec.Job] = pjob
		} else if pjob.CreationTimestamp.After(existing.CreationTimestamp.Time) {
			latestBatch[pjob.Spec.Job] = pjob
		}
	}

	var toReport []v1.ProwJob
	for _, pjob := range latestBatch {
		toReport = append(toReport, pjob)
	}

	return toReport, nil
}

func lockKeyForPJ(pj *v1.ProwJob) (*criercommonlib.SimplePull, error) {
	if pj.Spec.Type != v1.PresubmitJob {
		return nil, fmt.Errorf("can only get lock key for presubmit jobs, was %q", pj.Spec.Type)
	}
	if pj.Spec.Refs == nil {
		return nil, errors.New("pj.Spec.Refs is nil")
	}
	if n := len(pj.Spec.Refs.Pulls); n != 1 {
		return nil, fmt.Errorf("prowjob doesn't have one but %d pulls", n)
	}
	return criercommonlib.NewSimplePull(pj.Spec.Refs.Org, pj.Spec.Refs.Repo, pj.Spec.Refs.Pulls[0].Number), nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package main

import (
	"fmt"
	"io"
	"log"
	"mime"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/prometheus/client_golang/prometheus/promhttp"
)

const (
	// FormatHeader name of the header used to extract the format
	FormatHeader = "X-Format"

	// CodeHeader name of the header used as source of the HTTP status code to return
	CodeHeader = "X-Code"

	// ContentType name of the header that defines the format of the reply
	ContentType = "Content-Type"

	// OriginalURI name of the header with the original URL from NGINX
	OriginalURI = "X-Original-URI"

	// Namespace name of the header that contains information about the Ingress namespace
	Namespace = "X-Namespace"

	// IngressName name of the header that contains the matched Ingress
	IngressName = "X-Ingress-Name"

	// ServiceName name of the header that contains the matched Service in the Ingress
	ServiceName = "X-Service-Name"

	// ServicePort name of the header that contains the matched Service port in the Ingress
	ServicePort = "X-Service-Port"

	// RequestId is a unique ID that identifies the request - same as for backend service
	RequestId = "X-Request-ID"

	// ErrFilesPathVar is the name of the environment variable indicating
	// the location on disk of files served by the handler.
	ErrFilesPathVar = "ERROR_FILES_PATH"
)

func main() {
	errFilesPath := "/www"
	if os.Getenv(ErrFilesPathVar) != "" {
		errFilesPath = os.Getenv(ErrFilesPathVar)
	}

	http.HandleFunc("/", errorHandler(errFilesPath))

	http.Handle("/metrics", promhttp.Handler())

	http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	http.ListenAndServe(fmt.Sprintf(":8080"), nil)
}

func errorHandler(path string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		ext := "html"

		if os.Getenv("DEBUG") != "" {
			w.Header().Set(FormatHeader, r.Header.Get(FormatHeader))
			w.Header().Set(CodeHeader, r.Header.Get(CodeHeader))
			w.Header().Set(ContentType, r.Header.Get(ContentType))
			w.Header().Set(OriginalURI, r.Header.Get(OriginalURI))
			w.Header().Set(Namespace, r.Header.Get(Namespace))
			w.Header().Set(IngressName, r.Header.Get(IngressName))
			w.Header().Set(ServiceName, r.Header.Get(ServiceName))
			w.Header().Set(ServicePort, r.Header.Get(ServicePort))
			w.Header().Set(RequestId, r.Header.Get(RequestId))
		}

		format := r.Header.Get(FormatHeader)
		if format == "" {
			format = "text/html"
			log.Printf("format not specified. Using %v", format)
		}

		cext, err := mime.ExtensionsByType(format)
		if err != nil {
			log.Printf("unexpected error reading media type extension: %v. Using %v", err, ext)
			format = "text/html"
		} else if len(cext) == 0 {
			log.Printf("couldn't get media type extension. Using %v", ext)
		} else {
			ext = cext[0]
		}
		w.Header().Set(ContentType, format)

		errCode := r.Header.Get(CodeHeader)
		code, err := strconv.Atoi(errCode)
		if err != nil {
			code = 404
			log.Printf("unexpected error reading return code: %v. Using %v", err, code)
		}
		w.WriteHeader(code)

		if !strings.HasPrefix(ext, ".") {
			ext = "." + ext
		}
		file := fmt.Sprintf("%v/%v%v", path, code, ext)
		f, err := os.Open(file)
		if err != nil {
			log.Printf("unexpected error opening file: %v", err)
			scode := strconv.Itoa(code)
			file := fmt.Sprintf("%v/%cxx%v", path, scode[0], ext)
			f, err := os.Open(file)
			if err != nil {
				log.Printf("unexpected error opening file: %v", err)
				http.NotFound(w, r)
				return
			}
			defer f.Close()
			log.Printf("serving custom error response for code %v and format %v from file %v", code, format, file)
			io.Copy(w, f)
			return
		}
		defer f.Close()
		log.Printf("serving custom error response for code %v and format %v from file %v", code, format, file)
		io.Copy(w, f)

		duration := time.Now().Sub(start).Seconds()

		proto := strconv.Itoa(r.ProtoMajor)
		proto = fmt.Sprintf("%s.%s", proto, strconv.Itoa(r.ProtoMinor))

		requestCount.WithLabelValues(proto).Inc()
		requestDuration.WithLabelValues(proto).Observe(duration)
	}
}
<commit_msg>Register metrics for custom-error-pages<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package main

import (
	"fmt"
	"io"
	"log"
	"mime"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

const (
	// FormatHeader name of the header used to extract the format
	FormatHeader = "X-Format"

	// CodeHeader name of the header used as source of the HTTP status code to return
	CodeHeader = "X-Code"

	// ContentType name of the header that defines the format of the reply
	ContentType = "Content-Type"

	// OriginalURI name of the header with the original URL from NGINX
	OriginalURI = "X-Original-URI"

	// Namespace name of the header that contains information about the Ingress namespace
	Namespace = "X-Namespace"

	// IngressName name of the header that contains the matched Ingress
	IngressName = "X-Ingress-Name"

	// ServiceName name of the header that contains the matched Service in the Ingress
	ServiceName = "X-Service-Name"

	// ServicePort name of the header that contains the matched Service port in the Ingress
	ServicePort = "X-Service-Port"

	// RequestId is a unique ID that identifies the request - same as for backend service
	RequestId = "X-Request-ID"

	// ErrFilesPathVar is the name of the environment variable indicating
	// the location on disk of files served by the handler.
	ErrFilesPathVar = "ERROR_FILES_PATH"
)

func init() {
	prometheus.MustRegister(requestCount)
	prometheus.MustRegister(requestDuration)
}

func main() {
	errFilesPath := "/www"
	if os.Getenv(ErrFilesPathVar) != "" {
		errFilesPath = os.Getenv(ErrFilesPathVar)
	}

	http.HandleFunc("/", errorHandler(errFilesPath))

	http.Handle("/metrics", promhttp.Handler())

	http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	http.ListenAndServe(fmt.Sprintf(":8080"), nil)
}

func errorHandler(path string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		ext := "html"

		if os.Getenv("DEBUG") != "" {
			w.Header().Set(FormatHeader, r.Header.Get(FormatHeader))
			w.Header().Set(CodeHeader, r.Header.Get(CodeHeader))
			w.Header().Set(ContentType, r.Header.Get(ContentType))
			w.Header().Set(OriginalURI, r.Header.Get(OriginalURI))
			w.Header().Set(Namespace, r.Header.Get(Namespace))
			w.Header().Set(IngressName, r.Header.Get(IngressName))
			w.Header().Set(ServiceName, r.Header.Get(ServiceName))
			w.Header().Set(ServicePort, r.Header.Get(ServicePort))
			w.Header().Set(RequestId, r.Header.Get(RequestId))
		}

		format := r.Header.Get(FormatHeader)
		if format == "" {
			format = "text/html"
			log.Printf("format not specified. Using %v", format)
		}

		cext, err := mime.ExtensionsByType(format)
		if err != nil {
			log.Printf("unexpected error reading media type extension: %v. Using %v", err, ext)
			format = "text/html"
		} else if len(cext) == 0 {
			log.Printf("couldn't get media type extension. Using %v", ext)
		} else {
			ext = cext[0]
		}
		w.Header().Set(ContentType, format)

		errCode := r.Header.Get(CodeHeader)
		code, err := strconv.Atoi(errCode)
		if err != nil {
			code = 404
			log.Printf("unexpected error reading return code: %v. Using %v", err, code)
		}
		w.WriteHeader(code)

		if !strings.HasPrefix(ext, ".") {
			ext = "." + ext
		}
		file := fmt.Sprintf("%v/%v%v", path, code, ext)
		f, err := os.Open(file)
		if err != nil {
			log.Printf("unexpected error opening file: %v", err)
			scode := strconv.Itoa(code)
			file := fmt.Sprintf("%v/%cxx%v", path, scode[0], ext)
			f, err := os.Open(file)
			if err != nil {
				log.Printf("unexpected error opening file: %v", err)
				http.NotFound(w, r)
				return
			}
			defer f.Close()
			log.Printf("serving custom error response for code %v and format %v from file %v", code, format, file)
			io.Copy(w, f)
			return
		}
		defer f.Close()
		log.Printf("serving custom error response for code %v and format %v from file %v", code, format, file)
		io.Copy(w, f)

		duration := time.Now().Sub(start).Seconds()

		proto := strconv.Itoa(r.ProtoMajor)
		proto = fmt.Sprintf("%s.%s", proto, strconv.Itoa(r.ProtoMinor))

		requestCount.WithLabelValues(proto).Inc()
		requestDuration.WithLabelValues(proto).Observe(duration)
	}
}
<|endoftext|>


<commit_before>// Package gotool contains utility functions used to implement the standard
// "cmd/go" tool, provided as a convenience to developers who want to write
// tools with similar semantics.
package gotool

import "go/build"

// Export functions here to make it easier to keep the implementations up to date with upstream.

// DefaultContext is the default context that uses build.Default.
var DefaultContext = Context{
	BuildContext: build.Default,
}

// A Context specifies the supporting context.
type Context struct {
	// BuildContext is the build.Context that is used when computing import paths.
	BuildContext build.Context
}

// ImportPaths returns the import paths to use for the given command line.
//
// The path "all" is expanded to all packages in $GOPATH and $GOROOT.
// The path "std" is expanded to all packages in the Go standard library.
// The path "cmd" is expanded to all Go standard commands.
// The string "..." is treated as a wildcard within a path.
// Relative import paths are not converted to full import paths.
// If args is empty, a single element "." is returned.
func (c *Context) ImportPaths(args []string) []string {
	return c.importPaths(args)
}

// ImportPaths returns the import paths to use for the given command line
// using default context.
//
// The path "all" is expanded to all packages in $GOPATH and $GOROOT.
// The path "std" is expanded to all packages in the Go standard library.
// The path "cmd" is expanded to all Go standard commands.
// The string "..." is treated as a wildcard within a path.
// Relative import paths are not converted to full import paths.
// If args is empty, a single element "." is returned.
func ImportPaths(args []string) []string {
	return DefaultContext.importPaths(args)
}
<commit_msg>Document that recursive matching excludes some directories<commit_after>// Package gotool contains utility functions used to implement the standard
// "cmd/go" tool, provided as a convenience to developers who want to write
// tools with similar semantics.
package gotool

import "go/build"

// Export functions here to make it easier to keep the implementations up to date with upstream.

// DefaultContext is the default context that uses build.Default.
var DefaultContext = Context{
	BuildContext: build.Default,
}

// A Context specifies the supporting context.
type Context struct {
	// BuildContext is the build.Context that is used when computing import paths.
	BuildContext build.Context
}

// ImportPaths returns the import paths to use for the given command line.
//
// The path "all" is expanded to all packages in $GOPATH and $GOROOT.
// The path "std" is expanded to all packages in the Go standard library.
// The path "cmd" is expanded to all Go standard commands.
// The string "..." is treated as a wildcard within a path.
// When matching recursively, directories are ignored if they are prefixed with
// a dot or an underscore (such as ".foo" or "_foo"), or are named "testdata".
// Relative import paths are not converted to full import paths.
// If args is empty, a single element "." is returned.
func (c *Context) ImportPaths(args []string) []string {
	return c.importPaths(args)
}

// ImportPaths returns the import paths to use for the given command line
// using default context.
//
// The path "all" is expanded to all packages in $GOPATH and $GOROOT.
// The path "std" is expanded to all packages in the Go standard library.
// The path "cmd" is expanded to all Go standard commands.
// The string "..." is treated as a wildcard within a path.
// When matching recursively, directories are ignored if they are prefixed with
// a dot or an underscore (such as ".foo" or "_foo"), or are named "testdata".
// Relative import paths are not converted to full import paths.
// If args is empty, a single element "." is returned.
func ImportPaths(args []string) []string {
	return DefaultContext.importPaths(args)
}
<|endoftext|>


<commit_before>package main

import (
  "strings"
  "log"
  // "fmt"
//  "regexp"
  "bytes"
  "encoding/json"
  "os"
  "strconv"
  "github.com/lookify/town/cluster"
  dockerapi "github.com/fsouza/go-dockerclient"
)

const DEFAULT_ENDPOINT = "unix:///var/run/docker.sock"

var (
  SCALE_NUM_REG, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
//  SCALE_TOTAL_REG, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
  HOSTS_REG, _ = regexp.Compile("\\$\\{(.+)_HOSTS\\}")
)

type Town struct {
  cluster *cluster.Cluster
  docker *dockerapi.Client

//  containers []*dockerapi.Container
}

func NewTown() *Town {
  return &Town{
    cluster: nil,
    docker: nil,
    // containers: []*dockerapi.Container{}
  }
}

func (t *Town) ReadFile() {
  t.cluster = cluster.NewCluster()
  t.cluster.ReadFile()
}

func (t *Town) Connect() {
  envHost := os.Getenv("DOCKER_HOST")
  endpoint := DEFAULT_ENDPOINT
  if (envHost != "") {
    endpoint = envHost;
  }
  log.Println("Using Docker API endpont: ", endpoint)
  docker, err := dockerapi.NewClient( endpoint )
  if err != nil {
    log.Println("Can't connect to the docker")
  }
  t.docker = docker
}

func (t *Town) Provision(checkChanged bool) {
  allContainers, err := t.docker.ListContainers(dockerapi.ListContainersOptions{
    All: true,
  })
  for _, listing := range allContainers {
    container, err := t.docker.InspectContainer(listing.ID)
    if err == nil {
      name := container.Name[1:]
      node, index := t.cluster.FindNodeByName(name)
      if node != nil && index > 0 {
        if node.Container.Exist == nil {
          node.Container.Exist = []cluster.ExistContainer{}
        }
        runningContainer := cluster.NewExistContainer(listing.ID, name, index, container.State.Running)
        if checkChanged {
          node.Container.Changed = t.isChangedImage(node, container)
        }
        node.Container.Exist = append(node.Container.Exist, runningContainer);
      }
    } else {
      log.Println("[ERROR] Unable to inspect container:", listing.ID[:12], err)
    }
  }

  if checkChanged {
    t.cluster.AddChangeDependant()
  }
}

/**
 * Check node and running container for changes.
 * TODO: add cache to docker call.
 **/
func (t *Town) isChangedImage(node *cluster.Node, container *dockerapi.Container) bool {
  var imageName = container.Image
  image , error := t.docker.InspectImage(imageName)
  if error == nil {
    secondImage , secondError := t.docker.InspectImage(node.Container.Image)
    if secondError == nil {
      return secondImage.Created.After(image.Created)
    } else {
      log.Println("[ERROR] Could not inspect image ", node.Container.Name)
    }
  } else {
    log.Println("[ERROR] Could not inspect image ", imageName)
  }
  return false
}

func (t *Town) StopContainers(checkChanged bool) {
  log.Println("Stop...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        if container.Running {
          err := t.docker.StopContainer(container.ID, 10)
          if err == nil {
            log.Println("   -  ", container.Name)
          } else {
            log.Println("   -  ", container.Name, " failed ", err)
          }
        }
      }
    }
  }
  log.Println("=============================")
}

func (t *Town) RemoveContainers(checkChanged bool) {
  log.Println("Remove...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        err := t.docker.RemoveContainer(dockerapi.RemoveContainerOptions{
          ID: container.ID,
          RemoveVolumes: false,
        })
        if err == nil {
          log.Println("   -  ", container.Name)
        } else {
          log.Println("   -  ", container.Name, " failed ", err)
        }
      }
    }
  }
  log.Println("=============================")
}

func (t *Town) CreateContainer(node *cluster.Node, index int) (string, string) {
  containerName := node.Container.Name + "-" + strconv.Itoa(index)

  log.Println("   -  ", containerName)

  node.Container.Hostname = containerName // ?? Help !!!!

  env :=  make([]string, 0, cap(node.Container.Environment))
  for _, e := range node.Container.Environment {
    env = append(env, t.exec(e, index))
  }

  volumes := make(map[string]struct{})
  binds := make([]string, 0, cap(node.Container.Volumes))
  if len(node.Container.Volumes) > 0 {
    for _, volume := range node.Container.Volumes {
      volume = t.exec(volume, index)
      vol := strings.Split(volume, ":")
      if len(vol) > 1 {
        volumes[vol[1]] = struct{}{}
      } else {
        volumes[vol[0]] = struct{}{}
      }
      binds = append(binds, volume)
    }
  }

  dockerConfig := dockerapi.Config{
      Image: node.Container.Image,
      Hostname: node.Container.Hostname,
      PortSpecs: node.Container.Ports,
      Env: env,
      Volumes: volumes,

      AttachStdout: false,
      AttachStdin: false,
      AttachStderr: false,

      Tty: false,

      //Cmd: []
  }

  if len(node.Container.Command) > 0 {
    cmd := t.exec(node.Container.Command, index)
    dockerConfig.Cmd = []string{ cmd }
  }

  // just info
  //for _, l := range node.status.links {
  //  log.Println("     * ", l)
  //}

  // create links
  links := t.cluster.GetLinks(node)

  portBindings := map[dockerapi.Port][]dockerapi.PortBinding{}
  // create ports
  for _, ports := range node.Container.Ports {

    port := strings.Split(ports, ":")
    var p dockerapi.Port

    if len(port) > 1 {
      p = dockerapi.Port(port[1] + "/tcp")
    } else {
      p = dockerapi.Port(port[0] + "/tcp")
    }

    if portBindings[p] == nil {
      portBindings[p] = [] dockerapi.PortBinding {}
    }

    portBindings[p] = append(portBindings[p], dockerapi.PortBinding{
      HostIP: "",
      HostPort: port[0],
    })
  }

  hostConfig := dockerapi.HostConfig{
    Binds: binds,
    Links: links, //, [],
    PortBindings: portBindings,
    NetworkMode: "bridge",
    PublishAllPorts: false,
    Privileged: node.Container.Privileged,
  }

  opts := dockerapi.CreateContainerOptions{Name: containerName, Config: &dockerConfig, HostConfig: &hostConfig}
  container, err := t.docker.CreateContainer(opts)
  if err == nil {
    retry := 5
    for retry > 0 {
      error := t.docker.StartContainer(container.ID, &hostConfig)
      if error != nil {
        // log.Println("start error: ", error);

        out, err := json.Marshal(container)
        if err != nil {
            panic (err)
        }
        // fmt.Println(string(out))

        retry--;
        if retry == 0 {
          log.Println(" Strat failed after 5 retries: ", string(out))
        }
        // log.Println("retry: ", retry);
      } else {
        inspect, inspectError := t.docker.InspectContainer(container.ID)
        if inspectError == nil {
          //links = append(links, inspect.NetworkSettings.IPAddress + "  " + containerName)
          //ids = append(ids, container.ID)
          return container.ID, inspect.NetworkSettings.IPAddress + "  " + containerName
        } else {
          log.Println("Inpect ", container.ID, " error ", inspectError)
        }
        //retry = 0
        break;
      }
    }
  } else {
    log.Println("error: ", err);
  }

  return "", ""
}

func (t *Town) CreateContainers(checkChanged bool) {
  log.Println("Create...")
  for _, node := range t.cluster.Nodes {

    if !checkChanged || node.Container.Changed {
      ids := make([]string, 0, node.Container.Scale )

      hosts := make([]string, 0, node.Container.Scale)

      for i := 1; i <= node.Container.Scale; i++ {
        log.Println(node.Container.Name, "  image: ", node.Container.Image)
        id, host := t.CreateContainer(node, i)
        ids = append(ids, id)
        hosts = append(hosts, host)
      }

      if len(ids) > 1 {
        for index, id := range ids {
          var buffer bytes.Buffer

          buffer.WriteString("echo -e '")
          for i := 0; i < len(hosts); i++ {
            if i != index {
              buffer.WriteString("\n")
              buffer.WriteString(hosts[i])
            }
          }
          buffer.WriteString("' >> /etc/hosts; touch /tmp/host-generated")

          config := dockerapi.CreateExecOptions{
            Container:    id,
            AttachStdin:  true,
            AttachStdout: true,
            AttachStderr: false,
            Tty:          false,
            Cmd:          []string{"bash", "-c", buffer.String()},
          }
          execObj, err := t.docker.CreateExec(config)
          if err == nil {
            config := dockerapi.StartExecOptions{
              Detach: true,
            }
            err := t.docker.StartExec(execObj.ID, config)
            if err != nil {
              log.Println("Start exec failed ", id, " error: ", err)
            }
          } else {
            log.Println("Create exec failed ", id, " error: ", err)
          }
        }
      }
    }
  }
}

func (t *Town) exec(text string, scale int) string {
  replace := strings.Replace(text, "${SCALE_NUM}", strconv.Itoa(scale), -1)
  match := SCALE_NUM_REG.FindAllStringSubmatch(replace, -1)
  hostMatch := HOSTS_REG.FindAllStringSubmatch(replace, -1)
  if len(match) > 0 {
    if len(match[0]) > 1 {
      nums := strings.Split(match[0][1], ",")
      if len(nums) > (scale - 1) {
        replace = strings.Replace(replace, match[0][0], nums[scale - 1], -1)
      }
    }
  }
  if len(hostMatch) > 0 {
    if len(hostMatch[0]) > 1 {
      //nums := strings.Split(, ",")
      name := strings.ToLower(hostMatch[0][1])
      node := t.cluster.FindNodeByID(name)

      var buffer bytes.Buffer
      for i := 1; i <= node.Container.Scale; i++ {
        buffer.WriteString(name)
        buffer.WriteString("-")
        buffer.WriteString(strconv.Itoa( i ))
        if i != node.Container.Scale {
          buffer.WriteString(",")
        }
      }
      replace = strings.Replace(replace, hostMatch[0][0], buffer.String(), -1)
    }
  }
  return replace
}
<commit_msg>cahnge findByNode<commit_after>package main

import (
  "strings"
  "log"
  // "fmt"
//  "regexp"
  "bytes"
  "encoding/json"
  "os"
  "strconv"
  "regexp"
  "github.com/lookify/town/cluster"
  dockerapi "github.com/fsouza/go-dockerclient"
)

const DEFAULT_ENDPOINT = "unix:///var/run/docker.sock"

var (
  SCALE_NUM_REG, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
//  SCALE_TOTAL_REG, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
  HOSTS_REG, _ = regexp.Compile("\\$\\{(.+)_HOSTS\\}")
)

type Town struct {
  cluster *cluster.Cluster
  docker *dockerapi.Client

//  containers []*dockerapi.Container
}

func NewTown() *Town {
  return &Town{
    cluster: nil,
    docker: nil,
    // containers: []*dockerapi.Container{}
  }
}

func (t *Town) ReadFile() {
  t.cluster = cluster.NewCluster()
  t.cluster.ReadFile()
}

func (t *Town) Connect() {
  envHost := os.Getenv("DOCKER_HOST")
  endpoint := DEFAULT_ENDPOINT
  if (envHost != "") {
    endpoint = envHost;
  }
  log.Println("Using Docker API endpont: ", endpoint)
  docker, err := dockerapi.NewClient( endpoint )
  if err != nil {
    log.Println("Can't connect to the docker")
  }
  t.docker = docker
}

func (t *Town) Provision(checkChanged bool) {
  allContainers, err := t.docker.ListContainers(dockerapi.ListContainersOptions{
    All: true,
  })
  for _, listing := range allContainers {
    container, err := t.docker.InspectContainer(listing.ID)
    if err == nil {
      name := container.Name[1:]
      node, index := t.cluster.FindNodeByName(name)
      if node != nil && index > 0 {
        if node.Container.Exist == nil {
          node.Container.Exist = []cluster.ExistContainer{}
        }
        runningContainer := cluster.NewExistContainer(listing.ID, name, index, container.State.Running)
        if checkChanged {
          node.Container.Changed = t.isChangedImage(node, container)
        }
        node.Container.Exist = append(node.Container.Exist, runningContainer);
      }
    } else {
      log.Println("[ERROR] Unable to inspect container:", listing.ID[:12], err)
    }
  }

  if checkChanged {
    t.cluster.AddChangeDependant()
  }
}

/**
 * Check node and running container for changes.
 * TODO: add cache to docker call.
 **/
func (t *Town) isChangedImage(node *cluster.Node, container *dockerapi.Container) bool {
  var imageName = container.Image
  image , error := t.docker.InspectImage(imageName)
  if error == nil {
    secondImage , secondError := t.docker.InspectImage(node.Container.Image)
    if secondError == nil {
      return secondImage.Created.After(image.Created)
    } else {
      log.Println("[ERROR] Could not inspect image ", node.Container.Name)
    }
  } else {
    log.Println("[ERROR] Could not inspect image ", imageName)
  }
  return false
}

func (t *Town) StopContainers(checkChanged bool) {
  log.Println("Stop...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        if container.Running {
          err := t.docker.StopContainer(container.ID, 10)
          if err == nil {
            log.Println("   -  ", container.Name)
          } else {
            log.Println("   -  ", container.Name, " failed ", err)
          }
        }
      }
    }
  }
  log.Println("=============================")
}

func (t *Town) RemoveContainers(checkChanged bool) {
  log.Println("Remove...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        err := t.docker.RemoveContainer(dockerapi.RemoveContainerOptions{
          ID: container.ID,
          RemoveVolumes: false,
        })
        if err == nil {
          log.Println("   -  ", container.Name)
        } else {
          log.Println("   -  ", container.Name, " failed ", err)
        }
      }
    }
  }
  log.Println("=============================")
}

func (t *Town) CreateContainer(node *cluster.Node, index int) (string, string) {
  containerName := node.Container.Name + "-" + strconv.Itoa(index)

  log.Println("   -  ", containerName)

  node.Container.Hostname = containerName // ?? Help !!!!

  env :=  make([]string, 0, cap(node.Container.Environment))
  for _, e := range node.Container.Environment {
    env = append(env, t.exec(e, index))
  }

  volumes := make(map[string]struct{})
  binds := make([]string, 0, cap(node.Container.Volumes))
  if len(node.Container.Volumes) > 0 {
    for _, volume := range node.Container.Volumes {
      volume = t.exec(volume, index)
      vol := strings.Split(volume, ":")
      if len(vol) > 1 {
        volumes[vol[1]] = struct{}{}
      } else {
        volumes[vol[0]] = struct{}{}
      }
      binds = append(binds, volume)
    }
  }

  dockerConfig := dockerapi.Config{
      Image: node.Container.Image,
      Hostname: node.Container.Hostname,
      PortSpecs: node.Container.Ports,
      Env: env,
      Volumes: volumes,

      AttachStdout: false,
      AttachStdin: false,
      AttachStderr: false,

      Tty: false,

      //Cmd: []
  }

  if len(node.Container.Command) > 0 {
    cmd := t.exec(node.Container.Command, index)
    dockerConfig.Cmd = []string{ cmd }
  }

  // just info
  //for _, l := range node.status.links {
  //  log.Println("     * ", l)
  //}

  // create links
  links := t.cluster.GetLinks(node)

  portBindings := map[dockerapi.Port][]dockerapi.PortBinding{}
  // create ports
  for _, ports := range node.Container.Ports {

    port := strings.Split(ports, ":")
    var p dockerapi.Port

    if len(port) > 1 {
      p = dockerapi.Port(port[1] + "/tcp")
    } else {
      p = dockerapi.Port(port[0] + "/tcp")
    }

    if portBindings[p] == nil {
      portBindings[p] = [] dockerapi.PortBinding {}
    }

    portBindings[p] = append(portBindings[p], dockerapi.PortBinding{
      HostIP: "",
      HostPort: port[0],
    })
  }

  hostConfig := dockerapi.HostConfig{
    Binds: binds,
    Links: links, //, [],
    PortBindings: portBindings,
    NetworkMode: "bridge",
    PublishAllPorts: false,
    Privileged: node.Container.Privileged,
  }

  opts := dockerapi.CreateContainerOptions{Name: containerName, Config: &dockerConfig, HostConfig: &hostConfig}
  container, err := t.docker.CreateContainer(opts)
  if err == nil {
    retry := 5
    for retry > 0 {
      error := t.docker.StartContainer(container.ID, &hostConfig)
      if error != nil {
        // log.Println("start error: ", error);

        out, err := json.Marshal(container)
        if err != nil {
            panic (err)
        }
        // fmt.Println(string(out))

        retry--;
        if retry == 0 {
          log.Println(" Strat failed after 5 retries: ", string(out))
        }
        // log.Println("retry: ", retry);
      } else {
        inspect, inspectError := t.docker.InspectContainer(container.ID)
        if inspectError == nil {
          //links = append(links, inspect.NetworkSettings.IPAddress + "  " + containerName)
          //ids = append(ids, container.ID)
          return container.ID, inspect.NetworkSettings.IPAddress + "  " + containerName
        } else {
          log.Println("Inpect ", container.ID, " error ", inspectError)
        }
        //retry = 0
        break;
      }
    }
  } else {
    log.Println("error: ", err);
  }

  return "", ""
}

func (t *Town) CreateContainers(checkChanged bool) {
  log.Println("Create...")
  for _, node := range t.cluster.Nodes {

    if !checkChanged || node.Container.Changed {
      ids := make([]string, 0, node.Container.Scale )

      hosts := make([]string, 0, node.Container.Scale)

      for i := 1; i <= node.Container.Scale; i++ {
        log.Println(node.Container.Name, "  image: ", node.Container.Image)
        id, host := t.CreateContainer(node, i)
        ids = append(ids, id)
        hosts = append(hosts, host)
      }

      if len(ids) > 1 {
        for index, id := range ids {
          var buffer bytes.Buffer

          buffer.WriteString("echo -e '")
          for i := 0; i < len(hosts); i++ {
            if i != index {
              buffer.WriteString("\n")
              buffer.WriteString(hosts[i])
            }
          }
          buffer.WriteString("' >> /etc/hosts; touch /tmp/host-generated")

          config := dockerapi.CreateExecOptions{
            Container:    id,
            AttachStdin:  true,
            AttachStdout: true,
            AttachStderr: false,
            Tty:          false,
            Cmd:          []string{"bash", "-c", buffer.String()},
          }
          execObj, err := t.docker.CreateExec(config)
          if err == nil {
            config := dockerapi.StartExecOptions{
              Detach: true,
            }
            err := t.docker.StartExec(execObj.ID, config)
            if err != nil {
              log.Println("Start exec failed ", id, " error: ", err)
            }
          } else {
            log.Println("Create exec failed ", id, " error: ", err)
          }
        }
      }
    }
  }
}

func (t *Town) exec(text string, scale int) string {
  replace := strings.Replace(text, "${SCALE_NUM}", strconv.Itoa(scale), -1)
  match := SCALE_NUM_REG.FindAllStringSubmatch(replace, -1)
  hostMatch := HOSTS_REG.FindAllStringSubmatch(replace, -1)
  if len(match) > 0 {
    if len(match[0]) > 1 {
      nums := strings.Split(match[0][1], ",")
      if len(nums) > (scale - 1) {
        replace = strings.Replace(replace, match[0][0], nums[scale - 1], -1)
      }
    }
  }
  if len(hostMatch) > 0 {
    if len(hostMatch[0]) > 1 {
      //nums := strings.Split(, ",")
      name := strings.ToLower(hostMatch[0][1])
      node := t.cluster.FindNodeByID(name)

      var buffer bytes.Buffer
      for i := 1; i <= node.Container.Scale; i++ {
        buffer.WriteString(name)
        buffer.WriteString("-")
        buffer.WriteString(strconv.Itoa( i ))
        if i != node.Container.Scale {
          buffer.WriteString(",")
        }
      }
      replace = strings.Replace(replace, hostMatch[0][0], buffer.String(), -1)
    }
  }
  return replace
}
<|endoftext|>


<commit_before>package game

import (
	"context"
	"fmt"
	"github.com/Sirupsen/logrus"
	"github.com/verath/archipelago/lib/common"
	"github.com/verath/archipelago/lib/game/actions"
	"github.com/verath/archipelago/lib/game/events"
	"github.com/verath/archipelago/lib/game/model"
	"sync"
	"time"
)

const defaultTickInterval time.Duration = (time.Second / 2)

// The gameLoop is what updates the game model instance that it is
// associated with. The updates are performed in ticks. Each tick
// applies all actions that has been added since the last tick
// sequentially on the model. For each such actions, zero or more
// events are created. Those events are dispatched as the last stage
// of the tick.
//
// Notice that any reads or writes (outside of actions) on the model
// once the gameLoop is started is not safe.
type gameLoop struct {
	logEntry *logrus.Entry

	tickInterval time.Duration
	game         *model.Game

	// A signaling channel that is sent a value each time
	// the events *might* have been updated.
	eventsSCh chan bool
	eventsMu  sync.Mutex
	events    []events.Event

	actionsMu sync.Mutex
	actions   []actions.Action
}

// Perform a tick on the game; Applies all queued actions on the game
// sequentially, making it safe for the applied actions to modify the
// game state. An additional TickAction is always performed as the
// last actions.
func (gl *gameLoop) tick(delta time.Duration) error {
	// We make a copy of the current gl.actions and replace gl.actions
	// with a new array so that we can release the lock asap
	gl.actionsMu.Lock()
	acts := gl.actions
	gl.actions = make([]actions.Action, 0, len(acts))
	gl.actionsMu.Unlock()

	// Add a tick actions as the last actions
	tickAction, err := actions.NewTickAction(delta)
	if err != nil {
		return fmt.Errorf("Error creating tick action: %v", err)
	}
	acts = append(acts, tickAction)

	// Process actions
	evts := make([]events.Event, 0)
	for _, act := range acts {
		actionEvts, err := act.Apply(gl.game)
		if err != nil {
			return fmt.Errorf("Error applying actions: %v", err)
		}
		evts = append(evts, actionEvts...)
	}

	// Append the new events to the gl.events slice
	gl.eventsMu.Lock()
	gl.events = append(gl.events, evts...)
	gl.eventsMu.Unlock()

	// Signal that events (might have) been added
	select {
	case gl.eventsSCh <- true:
	default:
	}
	return nil
}

// Performs a "tick" each tickInterval. The tick is what updates the game.
// This method blocks, and always returns a non-nil error .
func (gl *gameLoop) tickLoop(ctx context.Context) error {
	tickInterval := gl.tickInterval
	ticker := time.NewTicker(tickInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-ticker.C:
			if err := gl.tick(tickInterval); err != nil {
				return err
			}
		}
	}
}

// Adds an actions to the actions to be processed.
func (gl *gameLoop) AddAction(action actions.Action) {
	gl.actionsMu.Lock()
	gl.logEntry.Debug("Adding action: %v", action)
	gl.actions = append(gl.actions, action)
	gl.actionsMu.Unlock()
}

// Returns the next event from the list of events. Blocks until an event
// can be returned or the context is cancelled.
func (gl *gameLoop) NextEvent(ctx context.Context) (events.Event, error) {
	var evt events.Event
	for {
		// Try get the first event
		gl.eventsMu.Lock()
		if len(gl.events) > 0 {
			evt, gl.events = gl.events[0], gl.events[1:]
		}
		gl.eventsMu.Unlock()

		if evt != nil {
			return evt, nil
		}

		// If we did not find an event, wait for the gl.eventsSCh
		// and try again.
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case <-gl.eventsSCh:
		}
	}
}

// Runs the game loop. Run blocks until the context is cancelled and
// always returns a non-nil error.
func (gl *gameLoop) Run(ctx context.Context) error {
	gl.logEntry.Debug("Starting")
	defer gl.logEntry.Debug("Stopped")

	// Add a game start event as the first event
	// TODO: should probably move this somewhere else
	createdEvt := events.NewGameStartEvent()
	gl.eventsMu.Lock()
	gl.events = append(gl.events, createdEvt)
	gl.eventsMu.Unlock()

	err := gl.tickLoop(ctx)
	return fmt.Errorf("tickLoop quit: %v", err)
}

func newGameLoop(log *logrus.Logger, game *model.Game) (*gameLoop, error) {
	logEntry := common.ModuleLogEntryWithID(log, "gameLoop")

	return &gameLoop{
		logEntry:     logEntry,
		tickInterval: defaultTickInterval,
		game:         game,
		eventsSCh:    make(chan bool, 0),
		events:       make([]events.Event, 0),
		actions:      make([]actions.Action, 0),
	}, nil
}
<commit_msg>Remove log message<commit_after>package game

import (
	"context"
	"fmt"
	"github.com/Sirupsen/logrus"
	"github.com/verath/archipelago/lib/common"
	"github.com/verath/archipelago/lib/game/actions"
	"github.com/verath/archipelago/lib/game/events"
	"github.com/verath/archipelago/lib/game/model"
	"sync"
	"time"
)

const defaultTickInterval time.Duration = (time.Second / 2)

// The gameLoop is what updates the game model instance that it is
// associated with. The updates are performed in ticks. Each tick
// applies all actions that has been added since the last tick
// sequentially on the model. For each such actions, zero or more
// events are created. Those events are dispatched as the last stage
// of the tick.
//
// Notice that any reads or writes (outside of actions) on the model
// once the gameLoop is started is not safe.
type gameLoop struct {
	logEntry *logrus.Entry

	tickInterval time.Duration
	game         *model.Game

	// A signaling channel that is sent a value each time
	// the events *might* have been updated.
	eventsSCh chan bool
	eventsMu  sync.Mutex
	events    []events.Event

	actionsMu sync.Mutex
	actions   []actions.Action
}

// Perform a tick on the game; Applies all queued actions on the game
// sequentially, making it safe for the applied actions to modify the
// game state. An additional TickAction is always performed as the
// last actions.
func (gl *gameLoop) tick(delta time.Duration) error {
	// We make a copy of the current gl.actions and replace gl.actions
	// with a new array so that we can release the lock asap
	gl.actionsMu.Lock()
	acts := gl.actions
	gl.actions = make([]actions.Action, 0, len(acts))
	gl.actionsMu.Unlock()

	// Add a tick actions as the last actions
	tickAction, err := actions.NewTickAction(delta)
	if err != nil {
		return fmt.Errorf("Error creating tick action: %v", err)
	}
	acts = append(acts, tickAction)

	// Process actions
	evts := make([]events.Event, 0)
	for _, act := range acts {
		actionEvts, err := act.Apply(gl.game)
		if err != nil {
			return fmt.Errorf("Error applying actions: %v", err)
		}
		evts = append(evts, actionEvts...)
	}

	// Append the new events to the gl.events slice
	gl.eventsMu.Lock()
	gl.events = append(gl.events, evts...)
	gl.eventsMu.Unlock()

	// Signal that events (might have) been added
	select {
	case gl.eventsSCh <- true:
	default:
	}
	return nil
}

// Performs a "tick" each tickInterval. The tick is what updates the game.
// This method blocks, and always returns a non-nil error .
func (gl *gameLoop) tickLoop(ctx context.Context) error {
	tickInterval := gl.tickInterval
	ticker := time.NewTicker(tickInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-ticker.C:
			if err := gl.tick(tickInterval); err != nil {
				return err
			}
		}
	}
}

// Adds an actions to the actions to be processed.
func (gl *gameLoop) AddAction(action actions.Action) {
	gl.actionsMu.Lock()
	gl.actions = append(gl.actions, action)
	gl.actionsMu.Unlock()
}

// Returns the next event from the list of events. Blocks until an event
// can be returned or the context is cancelled.
func (gl *gameLoop) NextEvent(ctx context.Context) (events.Event, error) {
	var evt events.Event
	for {
		// Try get the first event
		gl.eventsMu.Lock()
		if len(gl.events) > 0 {
			evt, gl.events = gl.events[0], gl.events[1:]
		}
		gl.eventsMu.Unlock()

		if evt != nil {
			return evt, nil
		}

		// If we did not find an event, wait for the gl.eventsSCh
		// and try again.
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case <-gl.eventsSCh:
		}
	}
}

// Runs the game loop. Run blocks until the context is cancelled and
// always returns a non-nil error.
func (gl *gameLoop) Run(ctx context.Context) error {
	gl.logEntry.Debug("Starting")
	defer gl.logEntry.Debug("Stopped")

	// Add a game start event as the first event
	// TODO: should probably move this somewhere else
	createdEvt := events.NewGameStartEvent()
	gl.eventsMu.Lock()
	gl.events = append(gl.events, createdEvt)
	gl.eventsMu.Unlock()

	err := gl.tickLoop(ctx)
	return fmt.Errorf("tickLoop quit: %v", err)
}

func newGameLoop(log *logrus.Logger, game *model.Game) (*gameLoop, error) {
	logEntry := common.ModuleLogEntryWithID(log, "gameLoop")

	return &gameLoop{
		logEntry:     logEntry,
		tickInterval: defaultTickInterval,
		game:         game,
		eventsSCh:    make(chan bool, 0),
		events:       make([]events.Event, 0),
		actions:      make([]actions.Action, 0),
	}, nil
}
<|endoftext|>