<commit_before>package main

import (
	"github.com/ActiveState/tail"
	"github.com/ugorji/go/codec"
	"io/ioutil"
	"log"
	"os"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type inputTail struct {
	path     string
	format   string
	tag      string
	pos_file string

	offset        int64
	sync_interval int
	codec         *codec.JsonHandle
	time_key      string
}

func (self *inputTail) Init(f map[string]string) error {

	self.sync_interval = 2

	value := f["path"]
	if len(value) > 0 {
		self.path = value
	}

	value = f["format"]
	if len(value) > 0 {
		self.format = value
		if value == "json" {
			_codec := codec.JsonHandle{}
			_codec.MapType = reflect.TypeOf(map[string]interface{}(nil))
			self.codec = &_codec

			value = f["time_key"]
			if len(value) > 0 {
				self.time_key = value
			} else {
				self.time_key = "time"
			}
		}
	}

	value = f["tag"]
	if len(value) > 0 {
		self.tag = value
	}

	value = f["pos_file"]
	if len(value) > 0 {
		self.pos_file = value

		str, err := ioutil.ReadFile(self.pos_file)
		if err != nil {
			log.Println("ioutil.ReadFile:", err)
		}

		f, err := os.Open(self.path)
		if err != nil {
			log.Println("os.Open:", err)
		}

		info, err := f.Stat()
		if err != nil {
			log.Println("f.Stat:", err)
			self.offset = 0
		} else {
			offset, _ := strconv.Atoi(string(str))
			if int64(offset) > info.Size() {
				self.offset = info.Size()
			} else {
				self.offset = int64(offset)
			}
		}
	}

	value = f["sync_interval"]
	if len(value) > 0 {
		sync_interval, err := strconv.Atoi(value)
		if err != nil {
			return err
		}
		self.sync_interval = sync_interval
	}

	return nil
}

func (self *inputTail) Run(runner InputRunner) error {
	defer func() {
		if err := recover(); err != nil {
			logs.Fatalln("recover panic at err:", err)
		}
	}()

	var seek int
	if self.offset > 0 {
		seek = os.SEEK_SET
	} else {
		seek = os.SEEK_END
	}

	t, err := tail.TailFile(self.path, tail.Config{
		Poll:      true,
		ReOpen:    true,
		Follow:    true,
		MustExist: false,
		Location:  &tail.SeekInfo{int64(self.offset), seek}})
	if err != nil {
		return err
	}

	f, err := os.OpenFile(self.pos_file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
	if err != nil {
		log.Fatalln("os.OpenFile", err)
	}
	defer f.Close()

	var re regexp.Regexp
	if string(self.format[0]) == string("/") || string(self.format[len(self.format)-1]) == string("/") {
		format := strings.Trim(self.format, "/")
		trueformat := regexp.MustCompile("\\(\\?<").ReplaceAllString(format, "(?P<")
		log.Printf("pos_file:%s, format:%s", self.path, trueformat)
		re = *regexp.MustCompile(trueformat)
		self.format = "regexp"
	} else if self.format == "json" {

	}

	tick := time.NewTicker(time.Second * time.Duration(self.sync_interval))
	count := 0

	for {
		select {
		case <-tick.C:
			{
				if count > 0 {
					offset, err := t.Tell()
					if err != nil {
						log.Println("Tell return error: ", err)
						continue
					}

					str := strconv.Itoa(int(offset))

					_, err = f.WriteAt([]byte(str), 0)
					if err != nil {
						log.Println("f.WriteAt", err)
						return err
					}

					count = 0
				}
			}
		case line := <-t.Lines:
			{
				pack := <-runner.InChan()

				pack.MsgBytes = []byte(line.Text)
				pack.Msg.Tag = self.tag
				pack.Msg.Timestamp = line.Time.Unix()

				if self.format == "regexp" {
					text := re.FindSubmatch([]byte(line.Text))
					if text == nil {
						pack.Recycle()
						continue
					}

					for i, name := range re.SubexpNames() {
						if len(name) > 0 {
							pack.Msg.Data[name] = string(text[i])
						}
					}
				} else if self.format == "json" {
					dec := codec.NewDecoderBytes([]byte(line.Text), self.codec)
					err := dec.Decode(&pack.Msg.Data)
					if err != nil {
						log.Println("json.Unmarshal", err)
						pack.Recycle()
						continue
					} else {
						t, ok := pack.Msg.Data[self.time_key]
						if ok {
							if time, xx := t.(uint64); xx {
								pack.Msg.Timestamp = int64(time)
								delete(pack.Msg.Data, self.time_key)
							} else if time64, oo := t.(int64); oo {
								pack.Msg.Timestamp = time64
								delete(pack.Msg.Data, self.time_key)
							} else {
								log.Println("time is not int64, ", t, " typeof:", reflect.TypeOf(t))
								pack.Recycle()
								continue
							}
						}
					}
				}

				count++
				runner.RouterChan() <- pack
			}
		}
	}

	err = t.Wait()
	if err != nil {
		return err
	}

	return err
}

func init() {
	RegisterInput("tail", func() interface{} {
		return new(inputTail)
	})
}
<commit_msg>not log when format is not parsed<commit_after>package main

import (
	"github.com/ActiveState/tail"
	"github.com/ugorji/go/codec"
	"io/ioutil"
	"log"
	"os"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type inputTail struct {
	path     string
	format   string
	tag      string
	pos_file string

	offset        int64
	sync_interval int
	codec         *codec.JsonHandle
	time_key      string
}

func (self *inputTail) Init(f map[string]string) error {

	self.sync_interval = 2

	value := f["path"]
	if len(value) > 0 {
		self.path = value
	}

	value = f["format"]
	if len(value) > 0 {
		self.format = value
		if value == "json" {
			_codec := codec.JsonHandle{}
			_codec.MapType = reflect.TypeOf(map[string]interface{}(nil))
			self.codec = &_codec

			value = f["time_key"]
			if len(value) > 0 {
				self.time_key = value
			} else {
				self.time_key = "time"
			}
		}
	}

	value = f["tag"]
	if len(value) > 0 {
		self.tag = value
	}

	value = f["pos_file"]
	if len(value) > 0 {
		self.pos_file = value

		str, err := ioutil.ReadFile(self.pos_file)
		if err != nil {
			log.Println("ioutil.ReadFile:", err)
		}

		f, err := os.Open(self.path)
		if err != nil {
			log.Println("os.Open:", err)
		}

		info, err := f.Stat()
		if err != nil {
			log.Println("f.Stat:", err)
			self.offset = 0
		} else {
			offset, _ := strconv.Atoi(string(str))
			if int64(offset) > info.Size() {
				self.offset = info.Size()
			} else {
				self.offset = int64(offset)
			}
		}
	}

	value = f["sync_interval"]
	if len(value) > 0 {
		sync_interval, err := strconv.Atoi(value)
		if err != nil {
			return err
		}
		self.sync_interval = sync_interval
	}

	return nil
}

func (self *inputTail) Run(runner InputRunner) error {
	defer func() {
		if err := recover(); err != nil {
			logs.Fatalln("recover panic at err:", err)
		}
	}()

	var seek int
	if self.offset > 0 {
		seek = os.SEEK_SET
	} else {
		seek = os.SEEK_END
	}

	t, err := tail.TailFile(self.path, tail.Config{
		Poll:      true,
		ReOpen:    true,
		Follow:    true,
		MustExist: false,
		Location:  &tail.SeekInfo{int64(self.offset), seek}})
	if err != nil {
		return err
	}

	f, err := os.OpenFile(self.pos_file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
	if err != nil {
		log.Fatalln("os.OpenFile", err)
	}
	defer f.Close()

	var re regexp.Regexp
	if string(self.format[0]) == string("/") || string(self.format[len(self.format)-1]) == string("/") {
		format := strings.Trim(self.format, "/")
		trueformat := regexp.MustCompile("\\(\\?<").ReplaceAllString(format, "(?P<")
		if trueformat != format {
			log.Printf("pos_file:%s, format:%s", self.path, trueformat)
		}
		re = *regexp.MustCompile(trueformat)
		self.format = "regexp"
	} else if self.format == "json" {

	}

	tick := time.NewTicker(time.Second * time.Duration(self.sync_interval))
	count := 0

	for {
		select {
		case <-tick.C:
			{
				if count > 0 {
					offset, err := t.Tell()
					if err != nil {
						log.Println("Tell return error: ", err)
						continue
					}

					str := strconv.Itoa(int(offset))

					_, err = f.WriteAt([]byte(str), 0)
					if err != nil {
						log.Println("f.WriteAt", err)
						return err
					}

					count = 0
				}
			}
		case line := <-t.Lines:
			{
				pack := <-runner.InChan()

				pack.MsgBytes = []byte(line.Text)
				pack.Msg.Tag = self.tag
				pack.Msg.Timestamp = line.Time.Unix()

				if self.format == "regexp" {
					text := re.FindSubmatch([]byte(line.Text))
					if text == nil {
						pack.Recycle()
						continue
					}

					for i, name := range re.SubexpNames() {
						if len(name) > 0 {
							pack.Msg.Data[name] = string(text[i])
						}
					}
				} else if self.format == "json" {
					dec := codec.NewDecoderBytes([]byte(line.Text), self.codec)
					err := dec.Decode(&pack.Msg.Data)
					if err != nil {
						log.Println("json.Unmarshal", err)
						pack.Recycle()
						continue
					} else {
						t, ok := pack.Msg.Data[self.time_key]
						if ok {
							if time, xx := t.(uint64); xx {
								pack.Msg.Timestamp = int64(time)
								delete(pack.Msg.Data, self.time_key)
							} else if time64, oo := t.(int64); oo {
								pack.Msg.Timestamp = time64
								delete(pack.Msg.Data, self.time_key)
							} else {
								log.Println("time is not int64, ", t, " typeof:", reflect.TypeOf(t))
								pack.Recycle()
								continue
							}
						}
					}
				}

				count++
				runner.RouterChan() <- pack
			}
		}
	}

	err = t.Wait()
	if err != nil {
		return err
	}

	return err
}

func init() {
	RegisterInput("tail", func() interface{} {
		return new(inputTail)
	})
}
<|endoftext|>


<commit_before>// Copyright (C) 2015 Thomas de Zeeuw.
//
// Licensed onder the MIT license that can be found in the LICENSE file.

package logger

import (
	"bytes"
	"errors"
	"testing"
	"time"
)

var result string

func BenchmarkTagString(b *testing.B) {
	b.ReportAllocs()
	var str string
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		str = tags.String()
	}
	result = str
}

func BenchmarkTagBytes(b *testing.B) {
	b.ReportAllocs()
	var buf []byte
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		buf = tags.Bytes()
	}
	result = string(buf)
}

func BenchmarkItoa2(b *testing.B) {
	b.ReportAllocs()
	var buf []byte
	for i := 0; i < b.N; i++ {
		itoa(&buf, 12, 2)
		buf = []byte{}
	}
	result = string(buf)
}

func BenchmarkItoa4(b *testing.B) {
	b.ReportAllocs()
	var buf []byte
	for i := 0; i < b.N; i++ {
		itoa(&buf, 2015, 4)
		buf = []byte{}
	}
	result = string(buf)
}

func BenchmarkFormatMsg(b *testing.B) {
	b.ReportAllocs()
	var str string
	t := time.Now()
	var lvl, msg = "ERROR", "Message"
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		str = formatMsg(t, lvl, tags, msg)
	}
	result = str
}

func BenchmarkFatal(b *testing.B) {
	b.ReportAllocs()
	reset()
	var buf bytes.Buffer
	log, err := New("benchmark", 1024, &buf)
	if err != nil {
		b.Fatal(err)
	}
	b.ResetTimer()

	msg := "Message"
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		log.Fatal(tags, msg)
	}

	log.Close()
	result = buf.String()
}

func BenchmarkError(b *testing.B) {
	b.ReportAllocs()
	reset()
	var buf bytes.Buffer
	log, err := New("benchmark", 1024, &buf)
	if err != nil {
		b.Fatal(err)
	}
	b.ResetTimer()

	err = errors.New("some error")
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		log.Error(tags, err)
	}

	log.Close()
	result = buf.String()
}

func BenchmarkInfo(b *testing.B) {
	b.ReportAllocs()
	reset()
	var buf bytes.Buffer
	log, err := New("benchmark", 1024, &buf)
	if err != nil {
		b.Fatal(err)
	}
	b.ResetTimer()

	msg := "Message"
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		log.Info(tags, msg)
	}

	log.Close()
	result = buf.String()
}

func BenchmarkDebug(b *testing.B) {
	b.ReportAllocs()
	reset()
	var buf bytes.Buffer
	log, err := New("benchmark", 1024, &buf)
	if err != nil {
		b.Fatal(err)
	}
	b.ResetTimer()

	msg := "Message"
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		log.Debug(tags, msg)
	}

	log.Close()
	result = buf.String()
}

func BenchmarkThumbstone(b *testing.B) {
	b.ReportAllocs()
	reset()
	var buf bytes.Buffer
	log, err := New("benchmark", 1024, &buf)
	if err != nil {
		b.Fatal(err)
	}
	b.ResetTimer()

	msg := "Message"
	for i := 0; i < b.N; i++ {
		log.Thumbstone(msg)
	}

	log.Close()
	result = buf.String()
}
<commit_msg>Add enabled Debug benchmark<commit_after>// Copyright (C) 2015 Thomas de Zeeuw.
//
// Licensed onder the MIT license that can be found in the LICENSE file.

package logger

import (
	"bytes"
	"errors"
	"testing"
	"time"
)

var result string

func BenchmarkTagString(b *testing.B) {
	b.ReportAllocs()
	var str string
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		str = tags.String()
	}
	result = str
}

func BenchmarkTagBytes(b *testing.B) {
	b.ReportAllocs()
	var buf []byte
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		buf = tags.Bytes()
	}
	result = string(buf)
}

func BenchmarkItoa2(b *testing.B) {
	b.ReportAllocs()
	var buf []byte
	for i := 0; i < b.N; i++ {
		itoa(&buf, 12, 2)
		buf = []byte{}
	}
	result = string(buf)
}

func BenchmarkItoa4(b *testing.B) {
	b.ReportAllocs()
	var buf []byte
	for i := 0; i < b.N; i++ {
		itoa(&buf, 2015, 4)
		buf = []byte{}
	}
	result = string(buf)
}

func BenchmarkFormatMsg(b *testing.B) {
	b.ReportAllocs()
	var str string
	t := time.Now()
	var lvl, msg = "ERROR", "Message"
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		str = formatMsg(t, lvl, tags, msg)
	}
	result = str
}

func BenchmarkFatal(b *testing.B) {
	b.ReportAllocs()
	reset()
	var buf bytes.Buffer
	log, err := New("benchmark", 1024, &buf)
	if err != nil {
		b.Fatal(err)
	}
	b.ResetTimer()

	msg := "Message"
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		log.Fatal(tags, msg)
	}

	log.Close()
	result = buf.String()
}

func BenchmarkError(b *testing.B) {
	b.ReportAllocs()
	reset()
	var buf bytes.Buffer
	log, err := New("benchmark", 1024, &buf)
	if err != nil {
		b.Fatal(err)
	}
	b.ResetTimer()

	err = errors.New("some error")
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		log.Error(tags, err)
	}

	log.Close()
	result = buf.String()
}

func BenchmarkInfo(b *testing.B) {
	b.ReportAllocs()
	reset()
	var buf bytes.Buffer
	log, err := New("benchmark", 1024, &buf)
	if err != nil {
		b.Fatal(err)
	}
	b.ResetTimer()

	msg := "Message"
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		log.Info(tags, msg)
	}

	log.Close()
	result = buf.String()
}

func BenchmarkDebugDisabled(b *testing.B) {
	b.ReportAllocs()
	reset()
	var buf bytes.Buffer
	log, err := New("benchmark", 1024, &buf)
	if err != nil {
		b.Fatal(err)
	}
	log.ShowDebug = false
	b.ResetTimer()

	msg := "Message"
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		log.Debug(tags, msg)
	}

	log.Close()
	result = buf.String()
}

func BenchmarkDebugEnabled(b *testing.B) {
	b.ReportAllocs()
	reset()
	var buf bytes.Buffer
	log, err := New("benchmark", 1024, &buf)
	if err != nil {
		b.Fatal(err)
	}
	log.ShowDebug = true
	b.ResetTimer()

	msg := "Message"
	tags := Tags{"test", "test2"}
	for i := 0; i < b.N; i++ {
		log.Debug(tags, msg)
	}

	log.Close()
	result = buf.String()
}

func BenchmarkThumbstone(b *testing.B) {
	b.ReportAllocs()
	reset()
	var buf bytes.Buffer
	log, err := New("benchmark", 1024, &buf)
	if err != nil {
		b.Fatal(err)
	}
	b.ResetTimer()

	msg := "Message"
	for i := 0; i < b.N; i++ {
		log.Thumbstone(msg)
	}

	log.Close()
	result = buf.String()
}
<|endoftext|>


<commit_before>package proxy

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/dokku/dokku/plugins/common"
	"github.com/dokku/dokku/plugins/config"
	columnize "github.com/ryanuber/columnize"
)

// PortMap is a struct that contains a scheme:host-port:container-port mapping
type PortMap struct {
	ContainerPort int
	HostPort      int
	Scheme        string
}

func (p PortMap) String() string {
	return fmt.Sprintf("%s:%d:%d", p.Scheme, p.HostPort, p.ContainerPort)
}

// IsAppProxyEnabled returns true if proxy is enabled; otherwise return false
func IsAppProxyEnabled(appName string) bool {
	err := common.VerifyAppName(appName)
	if err != nil {
		common.LogFail(err.Error())
	}

	proxyEnabled := true
	disableProxy := config.GetWithDefault(appName, "DOKKU_DISABLE_PROXY", "")
	if disableProxy != "" {
		proxyEnabled = false
	}
	return proxyEnabled
}

// ReportSingleApp is an internal function that displays the app report for one or more apps
func ReportSingleApp(appName string, infoFlag string) error {
	if err := common.VerifyAppName(appName); err != nil {
		return err
	}

	proxyEnabled := "false"
	if IsAppProxyEnabled(appName) {
		proxyEnabled = "true"
	}

	var proxyPortMap []string
	for _, portMap := range getProxyPortMap(appName) {
		proxyPortMap = append(proxyPortMap, portMap.String())
	}

	infoFlags := map[string]string{
		"--proxy-enabled":  proxyEnabled,
		"--proxy-type":     getAppProxyType(appName),
		"--proxy-port-map": strings.Join(proxyPortMap, " "),
	}

	trimPrefix := false
	uppercaseFirstCharacter := true
	return common.ReportSingleApp("proxy", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter)
}

func addProxyPorts(appName string, proxyPortMap []PortMap) error {
	allPortMaps := getProxyPortMap(appName)
	allPortMaps = append(allPortMaps, proxyPortMap...)

	return setProxyPorts(appName, allPortMaps)
}

func filterAppProxyPorts(appName string, scheme string, hostPort int) []PortMap {
	var filteredProxyMaps []PortMap
	proxyPortMap := getProxyPortMap(appName)
	for _, portMap := range proxyPortMap {
		if portMap.Scheme == scheme && portMap.HostPort == hostPort {
			filteredProxyMaps = append(filteredProxyMaps, portMap)
		}
	}

	return filteredProxyMaps
}

func getAppName(args []string) (appName string, err error) {
	if len(args) >= 1 {
		appName = args[0]
	} else {
		err = errors.New("Please specify an app to run the command on")
	}

	return
}

func getAppProxyType(appName string) string {
	return config.GetWithDefault(appName, "DOKKU_APP_PROXY_TYPE", "nginx")
}

func getProxyPortMap(appName string) []PortMap {
	value := config.GetWithDefault(appName, "DOKKU_PROXY_PORT_MAP", "")
	warn := false
	return parseProxyPortMapString(value, warn)
}

func listAppProxyPorts(appName string) error {
	proxyPortMap := getProxyPortMap(appName)

	if len(proxyPortMap) == 0 {
		return errors.New("No port mappings configured for app")
	}

	var lines []string
	if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
		lines = append(lines, "-----> scheme:host port:container port")
	}

	for _, portMap := range proxyPortMap {
		lines = append(lines, portMap.String())
	}

	common.LogInfo1Quiet(fmt.Sprintf("Port mappings for %s", appName))
	config := columnize.DefaultConfig()
	config.Delim = ":"
	config.Prefix = "    "
	config.Empty = ""
	fmt.Println(columnize.Format(lines, config))
	return nil
}

func setProxyPorts(appName string, proxyPortMap []PortMap) error {
	var value []string
	for _, portMap := range uniqueProxyPortMap(proxyPortMap) {
		value = append(value, portMap.String())
	}

	entries := map[string]string{
		"DOKKU_PROXY_PORT_MAP": strings.Join(value, " "),
	}
	return config.SetMany(appName, entries, false)
}

func removeProxyPorts(appName string, proxyPortMap []PortMap) error {
	toRemove := map[string]bool{}

	for _, portMap := range proxyPortMap {
		toRemove[portMap.String()] = true
	}

	var toSet []PortMap
	existingPortMaps := getProxyPortMap(appName)
	for _, portMap := range existingPortMaps {
		if toRemove[portMap.String()] {
			continue
		}

		toSet = append(toSet, portMap)
	}

	if len(toSet) == 0 {
		keys := []string{"DOKKU_PROXY_PORT_MAP"}
		return config.UnsetMany(appName, keys, false)
	}

	return setProxyPorts(appName, toSet)
}

func parseProxyPortMapString(stringPortMap string, warn bool) []PortMap {
	var proxyPortMap []PortMap

	for _, v := range strings.Split(strings.TrimSpace(stringPortMap), " ") {
		parts := strings.SplitN(v, ":", 3)
		if len(parts) != 3 {
			if warn {
				common.LogWarn(fmt.Sprintf("Invalid port map %s", v))
			}
			continue
		}

		hostPort, err := strconv.Atoi(parts[1])
		if err != nil {
			if warn {
				common.LogWarn(fmt.Sprintf("Invalid port map %s", v))
			}
		}

		containerPort, err := strconv.Atoi(parts[2])
		if err != nil {
			if warn {
				common.LogWarn(fmt.Sprintf("Invalid port map %s", v))
			}
		}

		proxyPortMap = append(proxyPortMap, PortMap{
			ContainerPort: containerPort,
			HostPort:      hostPort,
			Scheme:        parts[0],
		})
	}

	return uniqueProxyPortMap(proxyPortMap)
}

func uniqueProxyPortMap(proxyPortMap []PortMap) []PortMap {
	var unique []PortMap
	existingPortMaps := map[string]bool{}

	for _, portMap := range proxyPortMap {
		if existingPortMaps[portMap.String()] {
			continue
		}

		existingPortMaps[portMap.String()] = true
		unique = append(unique, portMap)
	}

	return unique
}
<commit_msg>fix: ensure proxy ports are sorted<commit_after>package proxy

import (
	"errors"
	"fmt"
	"os"
	"sort"
	"strconv"
	"strings"

	"github.com/dokku/dokku/plugins/common"
	"github.com/dokku/dokku/plugins/config"
	columnize "github.com/ryanuber/columnize"
)

// PortMap is a struct that contains a scheme:host-port:container-port mapping
type PortMap struct {
	ContainerPort int
	HostPort      int
	Scheme        string
}

func (p PortMap) String() string {
	return fmt.Sprintf("%s:%d:%d", p.Scheme, p.HostPort, p.ContainerPort)
}

// IsAppProxyEnabled returns true if proxy is enabled; otherwise return false
func IsAppProxyEnabled(appName string) bool {
	err := common.VerifyAppName(appName)
	if err != nil {
		common.LogFail(err.Error())
	}

	proxyEnabled := true
	disableProxy := config.GetWithDefault(appName, "DOKKU_DISABLE_PROXY", "")
	if disableProxy != "" {
		proxyEnabled = false
	}
	return proxyEnabled
}

// ReportSingleApp is an internal function that displays the app report for one or more apps
func ReportSingleApp(appName string, infoFlag string) error {
	if err := common.VerifyAppName(appName); err != nil {
		return err
	}

	proxyEnabled := "false"
	if IsAppProxyEnabled(appName) {
		proxyEnabled = "true"
	}

	var proxyPortMap []string
	for _, portMap := range getProxyPortMap(appName) {
		proxyPortMap = append(proxyPortMap, portMap.String())
	}

	infoFlags := map[string]string{
		"--proxy-enabled":  proxyEnabled,
		"--proxy-type":     getAppProxyType(appName),
		"--proxy-port-map": strings.Join(proxyPortMap, " "),
	}

	trimPrefix := false
	uppercaseFirstCharacter := true
	return common.ReportSingleApp("proxy", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter)
}

func addProxyPorts(appName string, proxyPortMap []PortMap) error {
	allPortMaps := getProxyPortMap(appName)
	allPortMaps = append(allPortMaps, proxyPortMap...)

	return setProxyPorts(appName, allPortMaps)
}

func filterAppProxyPorts(appName string, scheme string, hostPort int) []PortMap {
	var filteredProxyMaps []PortMap
	proxyPortMap := getProxyPortMap(appName)
	for _, portMap := range proxyPortMap {
		if portMap.Scheme == scheme && portMap.HostPort == hostPort {
			filteredProxyMaps = append(filteredProxyMaps, portMap)
		}
	}

	return filteredProxyMaps
}

func getAppName(args []string) (appName string, err error) {
	if len(args) >= 1 {
		appName = args[0]
	} else {
		err = errors.New("Please specify an app to run the command on")
	}

	return
}

func getAppProxyType(appName string) string {
	return config.GetWithDefault(appName, "DOKKU_APP_PROXY_TYPE", "nginx")
}

func getProxyPortMap(appName string) []PortMap {
	value := config.GetWithDefault(appName, "DOKKU_PROXY_PORT_MAP", "")
	warn := false
	return parseProxyPortMapString(value, warn)
}

func listAppProxyPorts(appName string) error {
	proxyPortMap := getProxyPortMap(appName)

	if len(proxyPortMap) == 0 {
		return errors.New("No port mappings configured for app")
	}

	var lines []string
	if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
		lines = append(lines, "-----> scheme:host port:container port")
	}

	for _, portMap := range proxyPortMap {
		lines = append(lines, portMap.String())
	}

	sort.Strings(lines)
	common.LogInfo1Quiet(fmt.Sprintf("Port mappings for %s", appName))
	config := columnize.DefaultConfig()
	config.Delim = ":"
	config.Prefix = "    "
	config.Empty = ""
	fmt.Println(columnize.Format(lines, config))
	return nil
}

func setProxyPorts(appName string, proxyPortMap []PortMap) error {
	var value []string
	for _, portMap := range uniqueProxyPortMap(proxyPortMap) {
		value = append(value, portMap.String())
	}

	sort.Strings(value)
	entries := map[string]string{
		"DOKKU_PROXY_PORT_MAP": strings.Join(value, " "),
	}
	return config.SetMany(appName, entries, false)
}

func removeProxyPorts(appName string, proxyPortMap []PortMap) error {
	toRemove := map[string]bool{}

	for _, portMap := range proxyPortMap {
		toRemove[portMap.String()] = true
	}

	var toSet []PortMap
	existingPortMaps := getProxyPortMap(appName)
	for _, portMap := range existingPortMaps {
		if toRemove[portMap.String()] {
			continue
		}

		toSet = append(toSet, portMap)
	}

	if len(toSet) == 0 {
		keys := []string{"DOKKU_PROXY_PORT_MAP"}
		return config.UnsetMany(appName, keys, false)
	}

	return setProxyPorts(appName, toSet)
}

func parseProxyPortMapString(stringPortMap string, warn bool) []PortMap {
	var proxyPortMap []PortMap

	for _, v := range strings.Split(strings.TrimSpace(stringPortMap), " ") {
		parts := strings.SplitN(v, ":", 3)
		if len(parts) != 3 {
			if warn {
				common.LogWarn(fmt.Sprintf("Invalid port map %s", v))
			}
			continue
		}

		hostPort, err := strconv.Atoi(parts[1])
		if err != nil {
			if warn {
				common.LogWarn(fmt.Sprintf("Invalid port map %s", v))
			}
		}

		containerPort, err := strconv.Atoi(parts[2])
		if err != nil {
			if warn {
				common.LogWarn(fmt.Sprintf("Invalid port map %s", v))
			}
		}

		proxyPortMap = append(proxyPortMap, PortMap{
			ContainerPort: containerPort,
			HostPort:      hostPort,
			Scheme:        parts[0],
		})
	}

	return uniqueProxyPortMap(proxyPortMap)
}

func uniqueProxyPortMap(proxyPortMap []PortMap) []PortMap {
	var unique []PortMap
	existingPortMaps := map[string]bool{}

	for _, portMap := range proxyPortMap {
		if existingPortMaps[portMap.String()] {
			continue
		}

		existingPortMaps[portMap.String()] = true
		unique = append(unique, portMap)
	}

	return unique
}
<|endoftext|>


<commit_before>package container

import (
	"fmt"

	executorpkg "github.com/docker/docker/daemon/cluster/executor"
	"github.com/docker/engine-api/types"
	"github.com/docker/engine-api/types/events"
	"github.com/docker/swarmkit/agent/exec"
	"github.com/docker/swarmkit/api"
	"github.com/docker/swarmkit/log"
	"github.com/pkg/errors"
	"golang.org/x/net/context"
)

// controller implements agent.Controller against docker's API.
//
// Most operations against docker's API are done through the container name,
// which is unique to the task.
type controller struct {
	backend executorpkg.Backend
	task    *api.Task
	adapter *containerAdapter
	closed  chan struct{}
	err     error
}

var _ exec.Controller = &controller{}

// NewController returns a dockerexec runner for the provided task.
func newController(b executorpkg.Backend, task *api.Task) (*controller, error) {
	adapter, err := newContainerAdapter(b, task)
	if err != nil {
		return nil, err
	}

	return &controller{
		backend: b,
		task:    task,
		adapter: adapter,
		closed:  make(chan struct{}),
	}, nil
}

func (r *controller) Task() (*api.Task, error) {
	return r.task, nil
}

// ContainerStatus returns the container-specific status for the task.
func (r *controller) ContainerStatus(ctx context.Context) (*api.ContainerStatus, error) {
	ctnr, err := r.adapter.inspect(ctx)
	if err != nil {
		if isUnknownContainer(err) {
			return nil, nil
		}
		return nil, err
	}
	return parseContainerStatus(ctnr)
}

// Update tasks a recent task update and applies it to the container.
func (r *controller) Update(ctx context.Context, t *api.Task) error {
	// TODO(stevvooe): While assignment of tasks is idempotent, we do allow
	// updates of metadata, such as labelling, as well as any other properties
	// that make sense.
	return nil
}

// Prepare creates a container and ensures the image is pulled.
//
// If the container has already be created, exec.ErrTaskPrepared is returned.
func (r *controller) Prepare(ctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	// Make sure all the networks that the task needs are created.
	if err := r.adapter.createNetworks(ctx); err != nil {
		return err
	}

	// Make sure all the volumes that the task needs are created.
	if err := r.adapter.createVolumes(ctx, r.backend); err != nil {
		return err
	}

	if err := r.adapter.pullImage(ctx); err != nil {
		cause := errors.Cause(err)
		if cause == context.Canceled || cause == context.DeadlineExceeded {
			return err
		}

		// NOTE(stevvooe): We always try to pull the image to make sure we have
		// the most up to date version. This will return an error, but we only
		// log it. If the image truly doesn't exist, the create below will
		// error out.
		//
		// This gives us some nice behavior where we use up to date versions of
		// mutable tags, but will still run if the old image is available but a
		// registry is down.
		//
		// If you don't want this behavior, lock down your image to an
		// immutable tag or digest.
		log.G(ctx).WithError(err).Error("pulling image failed")
	}

	if err := r.adapter.create(ctx, r.backend); err != nil {
		if isContainerCreateNameConflict(err) {
			if _, err := r.adapter.inspect(ctx); err != nil {
				return err
			}

			// container is already created. success!
			return exec.ErrTaskPrepared
		}

		return err
	}

	return nil
}

// Start the container. An error will be returned if the container is already started.
func (r *controller) Start(ctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	ctnr, err := r.adapter.inspect(ctx)
	if err != nil {
		return err
	}

	// Detect whether the container has *ever* been started. If so, we don't
	// issue the start.
	//
	// TODO(stevvooe): This is very racy. While reading inspect, another could
	// start the process and we could end up starting it twice.
	if ctnr.State.Status != "created" {
		return exec.ErrTaskStarted
	}

	if err := r.adapter.start(ctx); err != nil {
		return errors.Wrap(err, "starting container failed")
	}

	// no health check
	if ctnr.Config == nil || ctnr.Config.Healthcheck == nil {
		return nil
	}

	healthCmd := ctnr.Config.Healthcheck.Test

	if len(healthCmd) == 0 || healthCmd[0] == "NONE" {
		return nil
	}

	// wait for container to be healthy
	eventq := r.adapter.events(ctx)

	var healthErr error
	for {
		select {
		case event := <-eventq:
			if !r.matchevent(event) {
				continue
			}

			switch event.Action {
			case "die": // exit on terminal events
				ctnr, err := r.adapter.inspect(ctx)
				if err != nil {
					return errors.Wrap(err, "die event received")
				} else if ctnr.State.ExitCode != 0 {
					return &exitError{code: ctnr.State.ExitCode, cause: healthErr}
				}

				return nil
			case "destroy":
				// If we get here, something has gone wrong but we want to exit
				// and report anyways.
				return ErrContainerDestroyed
			case "health_status: unhealthy":
				// in this case, we stop the container and report unhealthy status
				if err := r.Shutdown(ctx); err != nil {
					return errors.Wrap(err, "unhealthy container shutdown failed")
				}
				// set health check error, and wait for container to fully exit ("die" event)
				healthErr = ErrContainerUnhealthy
			case "health_status: healthy":
				return nil
			}
		case <-ctx.Done():
			return ctx.Err()
		case <-r.closed:
			return r.err
		}
	}
}

// Wait on the container to exit.
func (r *controller) Wait(pctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	ctx, cancel := context.WithCancel(pctx)
	defer cancel()

	healthErr := make(chan error, 1)
	go func() {
		ectx, cancel := context.WithCancel(ctx) // cancel event context on first event
		defer cancel()
		if err := r.checkHealth(ectx); err == ErrContainerUnhealthy {
			healthErr <- ErrContainerUnhealthy
			if err := r.Shutdown(ectx); err != nil {
				log.G(ectx).WithError(err).Debug("shutdown failed on unhealthy")
			}
		}
	}()

	err := r.adapter.wait(ctx)
	if ctx.Err() != nil {
		return ctx.Err()
	}

	if err != nil {
		ee := &exitError{}
		if ec, ok := err.(exec.ExitCoder); ok {
			ee.code = ec.ExitCode()
		}
		select {
		case e := <-healthErr:
			ee.cause = e
		default:
			if err.Error() != "" {
				ee.cause = err
			}
		}
		return ee
	}

	return nil
}

// Shutdown the container cleanly.
func (r *controller) Shutdown(ctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	if err := r.adapter.shutdown(ctx); err != nil {
		if isUnknownContainer(err) || isStoppedContainer(err) {
			return nil
		}

		return err
	}

	return nil
}

// Terminate the container, with force.
func (r *controller) Terminate(ctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	if err := r.adapter.terminate(ctx); err != nil {
		if isUnknownContainer(err) {
			return nil
		}

		return err
	}

	return nil
}

// Remove the container and its resources.
func (r *controller) Remove(ctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	// It may be necessary to shut down the task before removing it.
	if err := r.Shutdown(ctx); err != nil {
		if isUnknownContainer(err) {
			return nil
		}
		// This may fail if the task was already shut down.
		log.G(ctx).WithError(err).Debug("shutdown failed on removal")
	}

	// Try removing networks referenced in this task in case this
	// task is the last one referencing it
	if err := r.adapter.removeNetworks(ctx); err != nil {
		if isUnknownContainer(err) {
			return nil
		}
		return err
	}

	if err := r.adapter.remove(ctx); err != nil {
		if isUnknownContainer(err) {
			return nil
		}

		return err
	}
	return nil
}

// Close the runner and clean up any ephemeral resources.
func (r *controller) Close() error {
	select {
	case <-r.closed:
		return r.err
	default:
		r.err = exec.ErrControllerClosed
		close(r.closed)
	}
	return nil
}

func (r *controller) matchevent(event events.Message) bool {
	if event.Type != events.ContainerEventType {
		return false
	}

	// TODO(stevvooe): Filter based on ID matching, in addition to name.

	// Make sure the events are for this container.
	if event.Actor.Attributes["name"] != r.adapter.container.name() {
		return false
	}

	return true
}

func (r *controller) checkClosed() error {
	select {
	case <-r.closed:
		return r.err
	default:
		return nil
	}
}

func parseContainerStatus(ctnr types.ContainerJSON) (*api.ContainerStatus, error) {
	status := &api.ContainerStatus{
		ContainerID: ctnr.ID,
		PID:         int32(ctnr.State.Pid),
		ExitCode:    int32(ctnr.State.ExitCode),
	}

	return status, nil
}

type exitError struct {
	code  int
	cause error
}

func (e *exitError) Error() string {
	if e.cause != nil {
		return fmt.Sprintf("task: non-zero exit (%v): %v", e.code, e.cause)
	}

	return fmt.Sprintf("task: non-zero exit (%v)", e.code)
}

func (e *exitError) ExitCode() int {
	return int(e.code)
}

func (e *exitError) Cause() error {
	return e.cause
}

// checkHealth blocks until unhealthy container is detected or ctx exits
func (r *controller) checkHealth(ctx context.Context) error {
	eventq := r.adapter.events(ctx)

	for {
		select {
		case <-ctx.Done():
			return nil
		case <-r.closed:
			return nil
		case event := <-eventq:
			if !r.matchevent(event) {
				continue
			}

			switch event.Action {
			case "health_status: unhealthy":
				return ErrContainerUnhealthy
			}
		}
	}
}
<commit_msg>container/controller: avoid cancellation with forked pull context<commit_after>package container

import (
	"fmt"

	executorpkg "github.com/docker/docker/daemon/cluster/executor"
	"github.com/docker/engine-api/types"
	"github.com/docker/engine-api/types/events"
	"github.com/docker/swarmkit/agent/exec"
	"github.com/docker/swarmkit/api"
	"github.com/docker/swarmkit/log"
	"github.com/pkg/errors"
	"golang.org/x/net/context"
)

// controller implements agent.Controller against docker's API.
//
// Most operations against docker's API are done through the container name,
// which is unique to the task.
type controller struct {
	backend executorpkg.Backend
	task    *api.Task
	adapter *containerAdapter
	closed  chan struct{}
	err     error

	pulled     chan struct{} // closed after pull
	cancelPull func()        // cancels pull context if not nil
	pullErr    error         // pull error, only read after pulled closed
}

var _ exec.Controller = &controller{}

// NewController returns a dockerexec runner for the provided task.
func newController(b executorpkg.Backend, task *api.Task) (*controller, error) {
	adapter, err := newContainerAdapter(b, task)
	if err != nil {
		return nil, err
	}

	return &controller{
		backend: b,
		task:    task,
		adapter: adapter,
		closed:  make(chan struct{}),
	}, nil
}

func (r *controller) Task() (*api.Task, error) {
	return r.task, nil
}

// ContainerStatus returns the container-specific status for the task.
func (r *controller) ContainerStatus(ctx context.Context) (*api.ContainerStatus, error) {
	ctnr, err := r.adapter.inspect(ctx)
	if err != nil {
		if isUnknownContainer(err) {
			return nil, nil
		}
		return nil, err
	}
	return parseContainerStatus(ctnr)
}

// Update tasks a recent task update and applies it to the container.
func (r *controller) Update(ctx context.Context, t *api.Task) error {
	// TODO(stevvooe): While assignment of tasks is idempotent, we do allow
	// updates of metadata, such as labelling, as well as any other properties
	// that make sense.
	return nil
}

// Prepare creates a container and ensures the image is pulled.
//
// If the container has already be created, exec.ErrTaskPrepared is returned.
func (r *controller) Prepare(ctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	// Make sure all the networks that the task needs are created.
	if err := r.adapter.createNetworks(ctx); err != nil {
		return err
	}

	// Make sure all the volumes that the task needs are created.
	if err := r.adapter.createVolumes(ctx, r.backend); err != nil {
		return err
	}

	if r.pulled == nil {
		// Fork the pull to a different context to allow pull to continue
		// on re-entrant calls to Prepare. This ensures that Prepare can be
		// idempotent and not incur the extra cost of pulling when
		// cancelled on updates.
		var pctx context.Context

		r.pulled = make(chan struct{})
		pctx, r.cancelPull = context.WithCancel(context.Background()) // TODO(stevvooe): Bind a context to the entire controller.

		go func() {
			defer close(r.pulled)
			r.pullErr = r.adapter.pullImage(pctx) // protected by closing r.pulled
		}()
	}

	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-r.pulled:
		if r.pullErr != nil {
			// NOTE(stevvooe): We always try to pull the image to make sure we have
			// the most up to date version. This will return an error, but we only
			// log it. If the image truly doesn't exist, the create below will
			// error out.
			//
			// This gives us some nice behavior where we use up to date versions of
			// mutable tags, but will still run if the old image is available but a
			// registry is down.
			//
			// If you don't want this behavior, lock down your image to an
			// immutable tag or digest.
			log.G(ctx).WithError(r.pullErr).Error("pulling image failed")
		}
	}

	if err := r.adapter.create(ctx, r.backend); err != nil {
		if isContainerCreateNameConflict(err) {
			if _, err := r.adapter.inspect(ctx); err != nil {
				return err
			}

			// container is already created. success!
			return exec.ErrTaskPrepared
		}

		return err
	}

	return nil
}

// Start the container. An error will be returned if the container is already started.
func (r *controller) Start(ctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	ctnr, err := r.adapter.inspect(ctx)
	if err != nil {
		return err
	}

	// Detect whether the container has *ever* been started. If so, we don't
	// issue the start.
	//
	// TODO(stevvooe): This is very racy. While reading inspect, another could
	// start the process and we could end up starting it twice.
	if ctnr.State.Status != "created" {
		return exec.ErrTaskStarted
	}

	if err := r.adapter.start(ctx); err != nil {
		return errors.Wrap(err, "starting container failed")
	}

	// no health check
	if ctnr.Config == nil || ctnr.Config.Healthcheck == nil {
		return nil
	}

	healthCmd := ctnr.Config.Healthcheck.Test

	if len(healthCmd) == 0 || healthCmd[0] == "NONE" {
		return nil
	}

	// wait for container to be healthy
	eventq := r.adapter.events(ctx)

	var healthErr error
	for {
		select {
		case event := <-eventq:
			if !r.matchevent(event) {
				continue
			}

			switch event.Action {
			case "die": // exit on terminal events
				ctnr, err := r.adapter.inspect(ctx)
				if err != nil {
					return errors.Wrap(err, "die event received")
				} else if ctnr.State.ExitCode != 0 {
					return &exitError{code: ctnr.State.ExitCode, cause: healthErr}
				}

				return nil
			case "destroy":
				// If we get here, something has gone wrong but we want to exit
				// and report anyways.
				return ErrContainerDestroyed
			case "health_status: unhealthy":
				// in this case, we stop the container and report unhealthy status
				if err := r.Shutdown(ctx); err != nil {
					return errors.Wrap(err, "unhealthy container shutdown failed")
				}
				// set health check error, and wait for container to fully exit ("die" event)
				healthErr = ErrContainerUnhealthy
			case "health_status: healthy":
				return nil
			}
		case <-ctx.Done():
			return ctx.Err()
		case <-r.closed:
			return r.err
		}
	}
}

// Wait on the container to exit.
func (r *controller) Wait(pctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	ctx, cancel := context.WithCancel(pctx)
	defer cancel()

	healthErr := make(chan error, 1)
	go func() {
		ectx, cancel := context.WithCancel(ctx) // cancel event context on first event
		defer cancel()
		if err := r.checkHealth(ectx); err == ErrContainerUnhealthy {
			healthErr <- ErrContainerUnhealthy
			if err := r.Shutdown(ectx); err != nil {
				log.G(ectx).WithError(err).Debug("shutdown failed on unhealthy")
			}
		}
	}()

	err := r.adapter.wait(ctx)
	if ctx.Err() != nil {
		return ctx.Err()
	}

	if err != nil {
		ee := &exitError{}
		if ec, ok := err.(exec.ExitCoder); ok {
			ee.code = ec.ExitCode()
		}
		select {
		case e := <-healthErr:
			ee.cause = e
		default:
			if err.Error() != "" {
				ee.cause = err
			}
		}
		return ee
	}

	return nil
}

// Shutdown the container cleanly.
func (r *controller) Shutdown(ctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	if r.cancelPull != nil {
		r.cancelPull()
	}

	if err := r.adapter.shutdown(ctx); err != nil {
		if isUnknownContainer(err) || isStoppedContainer(err) {
			return nil
		}

		return err
	}

	return nil
}

// Terminate the container, with force.
func (r *controller) Terminate(ctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	if r.cancelPull != nil {
		r.cancelPull()
	}

	if err := r.adapter.terminate(ctx); err != nil {
		if isUnknownContainer(err) {
			return nil
		}

		return err
	}

	return nil
}

// Remove the container and its resources.
func (r *controller) Remove(ctx context.Context) error {
	if err := r.checkClosed(); err != nil {
		return err
	}

	if r.cancelPull != nil {
		r.cancelPull()
	}

	// It may be necessary to shut down the task before removing it.
	if err := r.Shutdown(ctx); err != nil {
		if isUnknownContainer(err) {
			return nil
		}
		// This may fail if the task was already shut down.
		log.G(ctx).WithError(err).Debug("shutdown failed on removal")
	}

	// Try removing networks referenced in this task in case this
	// task is the last one referencing it
	if err := r.adapter.removeNetworks(ctx); err != nil {
		if isUnknownContainer(err) {
			return nil
		}
		return err
	}

	if err := r.adapter.remove(ctx); err != nil {
		if isUnknownContainer(err) {
			return nil
		}

		return err
	}
	return nil
}

// Close the runner and clean up any ephemeral resources.
func (r *controller) Close() error {
	select {
	case <-r.closed:
		return r.err
	default:
		if r.cancelPull != nil {
			r.cancelPull()
		}

		r.err = exec.ErrControllerClosed
		close(r.closed)
	}
	return nil
}

func (r *controller) matchevent(event events.Message) bool {
	if event.Type != events.ContainerEventType {
		return false
	}

	// TODO(stevvooe): Filter based on ID matching, in addition to name.

	// Make sure the events are for this container.
	if event.Actor.Attributes["name"] != r.adapter.container.name() {
		return false
	}

	return true
}

func (r *controller) checkClosed() error {
	select {
	case <-r.closed:
		return r.err
	default:
		return nil
	}
}

func parseContainerStatus(ctnr types.ContainerJSON) (*api.ContainerStatus, error) {
	status := &api.ContainerStatus{
		ContainerID: ctnr.ID,
		PID:         int32(ctnr.State.Pid),
		ExitCode:    int32(ctnr.State.ExitCode),
	}

	return status, nil
}

type exitError struct {
	code  int
	cause error
}

func (e *exitError) Error() string {
	if e.cause != nil {
		return fmt.Sprintf("task: non-zero exit (%v): %v", e.code, e.cause)
	}

	return fmt.Sprintf("task: non-zero exit (%v)", e.code)
}

func (e *exitError) ExitCode() int {
	return int(e.code)
}

func (e *exitError) Cause() error {
	return e.cause
}

// checkHealth blocks until unhealthy container is detected or ctx exits
func (r *controller) checkHealth(ctx context.Context) error {
	eventq := r.adapter.events(ctx)

	for {
		select {
		case <-ctx.Done():
			return nil
		case <-r.closed:
			return nil
		case event := <-eventq:
			if !r.matchevent(event) {
				continue
			}

			switch event.Action {
			case "health_status: unhealthy":
				return ErrContainerUnhealthy
			}
		}
	}
}
<|endoftext|>


<commit_before>package manners

import (
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"os"
	"sync"
	"time"
)

// NewListener wraps an existing listener for use with
// GracefulServer.
//
// Note that you generally don't need to use this directly as
// GracefulServer will automatically wrap any non-graceful listeners
// supplied to it.
func NewListener(l net.Listener) *GracefulListener {
	return &GracefulListener{
		listener: l,
		mutex:    &sync.RWMutex{},
		open:     true,
	}
}

// A GracefulListener differs from a standard net.Listener in one way: if
// Accept() is called after it is gracefully closed, it returns a
// listenerAlreadyClosed error. The GracefulServer will ignore this error.
type GracefulListener struct {
	listener net.Listener
	open     bool
	mutex    *sync.RWMutex
}

func (l *GracefulListener) isClosed() bool {
	l.mutex.RLock()
	defer l.mutex.RUnlock()
	return !l.open
}

func (l *GracefulListener) Addr() net.Addr {
	return l.listener.Addr()
}

// Accept implements the Accept method in the Listener interface.
func (l *GracefulListener) Accept() (net.Conn, error) {
	conn, err := l.listener.Accept()
	if err != nil {
		if l.isClosed() {
			err = fmt.Errorf("listener already closed: err=(%s)", err)
		}
		return nil, err
	}
	return conn, nil
}

// Close tells the wrapped listener to stop listening.  It is idempotent.
func (l *GracefulListener) Close() error {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	if !l.open {
		return nil
	}
	l.open = false
	return l.listener.Close()
}

func (l *GracefulListener) GetFile() (*os.File, error) {
	return getListenerFile(l.listener)
}

func (l *GracefulListener) Clone() (net.Listener, error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()

	if !l.open {
		return nil, errors.New("listener is already closed")
	}

	file, err := l.GetFile()
	if err != nil {
		return nil, err
	}
	defer file.Close()

	fl, err := net.FileListener(file)
	if nil != err {
		return nil, err
	}
	return fl, nil
}

// A listener implements a network listener (net.Listener) for TLS connections.
// direct lift from crypto/tls.go
type TLSListener struct {
	net.Listener
	config *tls.Config
}

// Accept waits for and returns the next incoming TLS connection.
// The returned connection c is a *tls.Conn.
func (l *TLSListener) Accept() (c net.Conn, err error) {
	c, err = l.Listener.Accept()
	if err != nil {
		return
	}
	c = tls.Server(c, l.config)
	return
}

// NewListener creates a Listener which accepts connections from an inner
// Listener and wraps each connection with Server.
// The configuration config must be non-nil and must have
// at least one certificate.
func NewTLSListener(inner net.Listener, config *tls.Config) net.Listener {
	l := new(TLSListener)
	l.Listener = inner
	l.config = config
	return l
}

// TCPKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
//
// direct lift from net/http/server.go
type TCPKeepAliveListener struct {
	*net.TCPListener
}

func (ln TCPKeepAliveListener) Accept() (c net.Conn, err error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(3 * time.Minute)
	return tc, nil
}

func getListenerFile(listener net.Listener) (*os.File, error) {
	switch t := listener.(type) {
	case *net.TCPListener:
		return t.File()
	case *net.UnixListener:
		return t.File()
	case TCPKeepAliveListener:
		return t.TCPListener.File()
	case *TLSListener:
		return getListenerFile(t.Listener)
	}
	return nil, fmt.Errorf("Unsupported listener: %T", listener)
}
<commit_msg>Add upstream patch for getting file FD from *TCPKeepAliveListener https://github.com/mailgun/manners/pull/15<commit_after>package manners

import (
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"os"
	"sync"
	"time"
)

// NewListener wraps an existing listener for use with
// GracefulServer.
//
// Note that you generally don't need to use this directly as
// GracefulServer will automatically wrap any non-graceful listeners
// supplied to it.
func NewListener(l net.Listener) *GracefulListener {
	return &GracefulListener{
		listener: l,
		mutex:    &sync.RWMutex{},
		open:     true,
	}
}

// A GracefulListener differs from a standard net.Listener in one way: if
// Accept() is called after it is gracefully closed, it returns a
// listenerAlreadyClosed error. The GracefulServer will ignore this error.
type GracefulListener struct {
	listener net.Listener
	open     bool
	mutex    *sync.RWMutex
}

func (l *GracefulListener) isClosed() bool {
	l.mutex.RLock()
	defer l.mutex.RUnlock()
	return !l.open
}

func (l *GracefulListener) Addr() net.Addr {
	return l.listener.Addr()
}

// Accept implements the Accept method in the Listener interface.
func (l *GracefulListener) Accept() (net.Conn, error) {
	conn, err := l.listener.Accept()
	if err != nil {
		if l.isClosed() {
			err = fmt.Errorf("listener already closed: err=(%s)", err)
		}
		return nil, err
	}
	return conn, nil
}

// Close tells the wrapped listener to stop listening.  It is idempotent.
func (l *GracefulListener) Close() error {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	if !l.open {
		return nil
	}
	l.open = false
	return l.listener.Close()
}

func (l *GracefulListener) GetFile() (*os.File, error) {
	return getListenerFile(l.listener)
}

func (l *GracefulListener) Clone() (net.Listener, error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()

	if !l.open {
		return nil, errors.New("listener is already closed")
	}

	file, err := l.GetFile()
	if err != nil {
		return nil, err
	}
	defer file.Close()

	fl, err := net.FileListener(file)
	if nil != err {
		return nil, err
	}
	return fl, nil
}

// A listener implements a network listener (net.Listener) for TLS connections.
// direct lift from crypto/tls.go
type TLSListener struct {
	net.Listener
	config *tls.Config
}

// Accept waits for and returns the next incoming TLS connection.
// The returned connection c is a *tls.Conn.
func (l *TLSListener) Accept() (c net.Conn, err error) {
	c, err = l.Listener.Accept()
	if err != nil {
		return
	}
	c = tls.Server(c, l.config)
	return
}

// NewListener creates a Listener which accepts connections from an inner
// Listener and wraps each connection with Server.
// The configuration config must be non-nil and must have
// at least one certificate.
func NewTLSListener(inner net.Listener, config *tls.Config) net.Listener {
	l := new(TLSListener)
	l.Listener = inner
	l.config = config
	return l
}

// TCPKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
//
// direct lift from net/http/server.go
type TCPKeepAliveListener struct {
	*net.TCPListener
}

func (ln TCPKeepAliveListener) Accept() (c net.Conn, err error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(3 * time.Minute)
	return tc, nil
}

func getListenerFile(listener net.Listener) (*os.File, error) {
	switch t := listener.(type) {
	case *net.TCPListener:
		return t.File()
	case *net.UnixListener:
		return t.File()
	case TCPKeepAliveListener:
		return t.TCPListener.File()
	case *TCPKeepAliveListener:
		return t.TCPListener.File()
	case *TLSListener:
		return getListenerFile(t.Listener)
	}
	return nil, fmt.Errorf("Unsupported listener: %T", listener)
}
<|endoftext|>


<commit_before>package config

import (
	"github.com/workfit/tester/assert"
	"path/filepath"
	"testing"
)

func TestUpdate(t *testing.T) {
	tests := []struct {
		description    string
		inPublic       *RawConfig
		inSecret       *RawConfig
		inType         ConfigModeType
		inIsSecret     bool
		inUpdater      ConfigUpdater
		errExpected    bool
		expectedPublic *RawConfig
		expectedSecret *RawConfig
	}{
		{
			"Simple public",
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			nil,
			TypeBase,
			false,
			SetString("allowedORIGINs ", "after"),
			false,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "after",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			nil,
		},
		{
			"Simple secret",
			nil,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", privateConfigFileName),
			},
			TypeBase,
			true,
			SetString("allowedORIGINs ", "after"),
			false,
			nil,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "after",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", privateConfigFileName),
			},
		},
		{
			"Set on nil mode",
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			nil,
			TypeDev,
			false,
			SetString("allowedORIGINs ", "after"),
			false,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "after",
					},
				},
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			nil,
		},
		{
			"Nil updater",
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			nil,
			TypeBase,
			false,
			SetString("NOTAPROPERTs ", "after"),
			true,
			nil,
			nil,
		},
		//TODO: test totally nil configs in
		//TODO: test set on nil secret with non-nil public
		//TODO: test set on nil public with nil secret
		//TODO: test set on nil public with non-nil secret
		//TODO: test invalid type
	}

	for i, test := range tests {
		config := NewConfig(test.inPublic, test.inSecret)

		err := config.Update(test.inType, test.inIsSecret, test.inUpdater)

		if test.errExpected {
			assert.For(t, i, test.description).ThatActual(err).IsNotNil()
			continue
		} else {
			assert.For(t, i, test.description).ThatActual(err).IsNil()
		}

		assert.For(t, i, test.description).ThatActual(config.RawConfig()).Equals(test.expectedPublic)
		assert.For(t, i, test.description).ThatActual(config.RawSecretConfig()).Equals(test.expectedSecret)

	}
}
<commit_msg>Complete the set of tests in update_test. Part of #655.<commit_after>package config

import (
	"github.com/workfit/tester/assert"
	"path/filepath"
	"testing"
)

func TestUpdate(t *testing.T) {
	tests := []struct {
		description    string
		inPublic       *RawConfig
		inSecret       *RawConfig
		inType         ConfigModeType
		inIsSecret     bool
		inUpdater      ConfigUpdater
		errExpected    bool
		expectedPublic *RawConfig
		expectedSecret *RawConfig
	}{
		{
			"Simple public",
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			nil,
			TypeBase,
			false,
			SetString("allowedORIGINs ", "after"),
			false,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "after",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			nil,
		},
		{
			"Simple secret",
			nil,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", privateConfigFileName),
			},
			TypeBase,
			true,
			SetString("allowedORIGINs ", "after"),
			false,
			nil,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "after",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", privateConfigFileName),
			},
		},
		{
			"Set on nil mode",
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			nil,
			TypeDev,
			false,
			SetString("allowedORIGINs ", "after"),
			false,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "after",
					},
				},
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			nil,
		},
		{
			"Nil updater",
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			nil,
			TypeBase,
			false,
			SetString("NOTAPROPERTs ", "after"),
			true,
			nil,
			nil,
		},
		{
			"Public on fully nil config",
			nil,
			nil,
			TypeBase,
			false,
			SetString("allowedORIGINs ", "after"),
			false,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "after",
					},
				},
				nil,
				nil,
				publicConfigFileName,
			},
			nil,
		},
		{
			"Set on nil secret with non-nil public",
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			nil,
			TypeBase,
			true,
			SetString("allowedORIGINs ", "after"),
			false,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "after",
					},
				},
				nil,
				nil,
				filepath.Join("folder", privateConfigFileName),
			},
		},
		{
			"Set on nil public with non-nil secret",
			nil,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", privateConfigFileName),
			},
			TypeBase,
			false,
			SetString("allowedORIGINs ", "after"),
			false,
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "after",
					},
				},
				nil,
				nil,
				filepath.Join("folder", publicConfigFileName),
			},
			&RawConfig{
				&RawConfigMode{
					ConfigModeCommon: ConfigModeCommon{
						AllowedOrigins: "before",
						DefaultPort:    "before",
					},
				},
				nil,
				nil,
				filepath.Join("folder", privateConfigFileName),
			},
		},
	}

	for i, test := range tests {
		config := NewConfig(test.inPublic, test.inSecret)

		err := config.Update(test.inType, test.inIsSecret, test.inUpdater)

		if test.errExpected {
			assert.For(t, i, test.description).ThatActual(err).IsNotNil()
			continue
		} else {
			assert.For(t, i, test.description).ThatActual(err).IsNil()
		}

		assert.For(t, i, test.description).ThatActual(config.RawConfig()).Equals(test.expectedPublic)
		assert.For(t, i, test.description).ThatActual(config.RawSecretConfig()).Equals(test.expectedSecret)

	}
}
<|endoftext|>


<commit_before>package formula

import (
	"fmt"
	"testing"
)

type Test struct {
	Expr  string
	N     int
	Tests map[int]int
}

var formulas = []*Test{
	// Asian
	{"0", 1, map[int]int{0: 0, 1: 0, 2: 0, 100: 0}},
	// Romanic
	{"n != 1", 2, map[int]int{0: 1, 1: 0, 2: 1, 100: 1}},
	// Brazilian Portuguese
	{"n > 1", 2, map[int]int{0: 0, 1: 0, 2: 1, 100: 1}},
	// Latvian
	{"n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2", 3, map[int]int{1: 0, 21: 0, 11: 1, 0: 2}},
	// Irish
	{"n==1 ? 0 : n==2 ? 1 : 2", 3, map[int]int{1: 0, 2: 1, 3: 2, 4: 2, 300: 2}},
	// Romanian
	{"n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2", 3, map[int]int{1: 0, 0: 1, 10: 1, 20: 2, 30: 2}},
	// Lithuanian
	{"n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2", 3, map[int]int{0: 2, 1: 0, 2: 1, 3: 1, 11: 2, 12: 2, 15: 2, 22: 1}},
	// Russian
	{"n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", 3, map[int]int{1: 0, 11: 2, 14: 2, 34: 1}},
	// Czech
	{"(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2", 3, map[int]int{1: 0, 2: 1, 3: 1, 4: 1, 10: 2, 5: 2}},
	// Polish
	{"n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", 4, map[int]int{1: 0, 0: 2, 5: 2, 8: 2, 12: 2, 15: 2, 22: 1, 103: 1}},
	// Slovenian
	{"n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3", 4, map[int]int{0: 3, 1: 0, 101: 0, 2: 1, 3: 2, 4: 2, 204: 2}},
}

func compile(code []byte, optimize bool, jit bool) (Formula, error) {
	p, err := vmCompile(code)
	if err != nil {
		return nil, err
	}
	if optimize {
		p = vmOptimize(p)
	}
	if jit {
		return vmJit(p)
	}
	return makeVmFunc(p), nil
}

func compiler(optimize bool, jit bool) func([]byte) (Formula, error) {
	return func(code []byte) (Formula, error) {
		return compile(code, optimize, jit)
	}
}

func testCompile(t *testing.T, comp func([]byte) (Formula, error)) {
	for _, v := range formulas {
		t.Logf("Compiling formula %q", v.Expr)
		fn, err := comp([]byte(v.Expr))
		if err != nil {
			t.Error(err)
			continue
		}
		for k, val := range v.Tests {
			x := fn(k)
			t.Logf("(%s)(%d) = %d", v.Expr, k, x)
			if x != val {
				t.Errorf("Bad result for %q with value %d. Want %d, got %d.", v.Expr, k, val, x)
			}
		}
	}
}

func TestVm(t *testing.T) {
	testCompile(t, compiler(false, false))
}

func TestVmOptimized(t *testing.T) {
	testCompile(t, compiler(true, false))
}

func TestJit(t *testing.T) {
	if _, err := vmJit(nil); err != errJitNotSupported {
		testCompile(t, compiler(true, true))
	}
}

func TestBytecode(t *testing.T) {
	for _, v := range formulas {
		t.Logf("Compiling formula %q", v.Expr)
		code, err := vmCompile([]byte(v.Expr))
		if err != nil {
			t.Error(err)
			continue
		}
		t.Log("Bytecode")
		for ii, i := range code {
			t.Logf("%d:%s\t%d", ii, i.opCode.String(), i.value)
		}
		optimized := vmOptimize(code)
		t.Log("Optimized")
		for ii, i := range optimized {
			t.Logf("%d:%s\t%d", ii, i.opCode.String(), i.value)
		}
	}
}

func benchmarkCompile(b *testing.B, fn func([]byte) (Formula, error)) {
	for ii := 0; ii < b.N; ii++ {
		for _, v := range formulas {
			_, err := fn([]byte(v.Expr))
			if err != nil {
				b.Fatal(err)
			}
		}
	}
}

func BenchmarkCompileVm(b *testing.B) {
	benchmarkCompile(b, compileVmFormula)
}

func BenchmarkFindCompiled(b *testing.B) {
	forms := make([]string, len(formulas))
	for ii, v := range formulas {
		forms[ii] = fmt.Sprintf("nplurals=%d; plural=(%s);", v.N, v.Expr)
	}
	b.ResetTimer()
	for ii := 0; ii < b.N; ii++ {
		for jj := range formulas {
			_, _, err := Make(forms[jj])
			if err != nil {
				b.Fatal(err)
			}
		}
	}
}

func benchmarkFormulas(b *testing.B, fns []Formula) {
	b.ResetTimer()
	for ii := 0; ii < b.N; ii++ {
		for jj, v := range formulas {
			fn := fns[jj]
			for k, val := range v.Tests {
				x := fn(k)
				if x != val {
					b.Errorf("Bad result for %q with value %d. Want %d, got %d.", v.Expr, k, val, x)
				}
			}
		}
	}
}

func compileAndBenchmark(b *testing.B, fn func([]byte) (Formula, error)) {
	fns := make([]Formula, len(formulas))
	var err error
	for ii, v := range formulas {
		fns[ii], err = fn([]byte(v.Expr))
		if err != nil {
			b.Fatal(err)
		}
	}
	benchmarkFormulas(b, fns)
}

func BenchmarkVmInterpreted(b *testing.B) {
	compileAndBenchmark(b, compiler(false, false))
}

func BenchmarkVmInterpretedOptimized(b *testing.B) {
	compileAndBenchmark(b, compiler(true, false))
}

func BenchmarkJit(b *testing.B) {
	compileAndBenchmark(b, compiler(true, true))
}

func BenchmarkCompiled(b *testing.B) {
	fns := make([]Formula, len(formulas))
	var err error
	for ii, v := range formulas {
		text := fmt.Sprintf("nplurals=%d; plural=(%s);", v.N, v.Expr)
		fns[ii], _, err = Make(text)
		if err != nil {
			b.Fatal(err)
		}
	}
	benchmarkFormulas(b, fns)
}
<commit_msg>Add some made up formulas to tests<commit_after>package formula

import (
	"fmt"
	"testing"
)

type Test struct {
	Expr  string
	N     int
	Tests map[int]int
}

var formulas = []*Test{
	// Asian
	{"0", 1, map[int]int{0: 0, 1: 0, 2: 0, 100: 0}},
	// Romanic
	{"n != 1", 2, map[int]int{0: 1, 1: 0, 2: 1, 100: 1}},
	// Brazilian Portuguese
	{"n > 1", 2, map[int]int{0: 0, 1: 0, 2: 1, 100: 1}},
	// Latvian
	{"n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2", 3, map[int]int{1: 0, 21: 0, 11: 1, 0: 2}},
	// Irish
	{"n==1 ? 0 : n==2 ? 1 : 2", 3, map[int]int{1: 0, 2: 1, 3: 2, 4: 2, 300: 2}},
	// Romanian
	{"n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2", 3, map[int]int{1: 0, 0: 1, 10: 1, 20: 2, 30: 2}},
	// Lithuanian
	{"n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2", 3, map[int]int{0: 2, 1: 0, 2: 1, 3: 1, 11: 2, 12: 2, 15: 2, 22: 1}},
	// Russian
	{"n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", 3, map[int]int{1: 0, 11: 2, 14: 2, 34: 1}},
	// Czech
	{"(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2", 3, map[int]int{1: 0, 2: 1, 3: 1, 4: 1, 10: 2, 5: 2}},
	// Polish
	{"n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", 4, map[int]int{1: 0, 0: 2, 5: 2, 8: 2, 12: 2, 15: 2, 22: 1, 103: 1}},
	// Slovenian
	{"n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3", 4, map[int]int{0: 3, 1: 0, 101: 0, 2: 1, 3: 2, 4: 2, 204: 2}},
	// Some made up formulas
	{"n + 7 < 10 ? 1 : 0", 2, map[int]int{0: 1, 2: 1, 3: 0}},
	{"n - 5 > 0", 2, map[int]int{0: 0, 5: 0, 6: 1}},
	{"n / 10 > 0 ? 1 : 0", 2, map[int]int{0: 0, 9: 0, 10: 1}},
	{"n * 3 >= 9", 2, map[int]int{0: 0, 2: 0, 3: 1, 4: 1}},
}

func compile(code []byte, optimize bool, jit bool) (Formula, error) {
	p, err := vmCompile(code)
	if err != nil {
		return nil, err
	}
	if optimize {
		p = vmOptimize(p)
	}
	if jit {
		return vmJit(p)
	}
	return makeVmFunc(p), nil
}

func compiler(optimize bool, jit bool) func([]byte) (Formula, error) {
	return func(code []byte) (Formula, error) {
		return compile(code, optimize, jit)
	}
}

func testCompile(t *testing.T, comp func([]byte) (Formula, error)) {
	for _, v := range formulas {
		t.Logf("Compiling formula %q", v.Expr)
		fn, err := comp([]byte(v.Expr))
		if err != nil {
			t.Error(err)
			continue
		}
		for k, val := range v.Tests {
			x := fn(k)
			t.Logf("(%s)(%d) = %d", v.Expr, k, x)
			if x != val {
				t.Errorf("Bad result for %q with value %d. Want %d, got %d.", v.Expr, k, val, x)
			}
		}
	}
}

func TestVm(t *testing.T) {
	testCompile(t, compiler(false, false))
}

func TestVmOptimized(t *testing.T) {
	testCompile(t, compiler(true, false))
}

func TestJit(t *testing.T) {
	if _, err := vmJit(nil); err != errJitNotSupported {
		testCompile(t, compiler(true, true))
	}
}

func TestBytecode(t *testing.T) {
	for _, v := range formulas {
		t.Logf("Compiling formula %q", v.Expr)
		code, err := vmCompile([]byte(v.Expr))
		if err != nil {
			t.Error(err)
			continue
		}
		t.Log("Bytecode")
		for ii, i := range code {
			t.Logf("%d:%s\t%d", ii, i.opCode.String(), i.value)
		}
		optimized := vmOptimize(code)
		t.Log("Optimized")
		for ii, i := range optimized {
			t.Logf("%d:%s\t%d", ii, i.opCode.String(), i.value)
		}
	}
}

func benchmarkCompile(b *testing.B, fn func([]byte) (Formula, error)) {
	for ii := 0; ii < b.N; ii++ {
		for _, v := range formulas {
			_, err := fn([]byte(v.Expr))
			if err != nil {
				b.Fatal(err)
			}
		}
	}
}

func BenchmarkCompileVm(b *testing.B) {
	benchmarkCompile(b, compileVmFormula)
}

func BenchmarkFindCompiled(b *testing.B) {
	forms := make([]string, len(formulas))
	for ii, v := range formulas {
		forms[ii] = fmt.Sprintf("nplurals=%d; plural=(%s);", v.N, v.Expr)
	}
	b.ResetTimer()
	for ii := 0; ii < b.N; ii++ {
		for jj := range formulas {
			_, _, err := Make(forms[jj])
			if err != nil {
				b.Fatal(err)
			}
		}
	}
}

func benchmarkFormulas(b *testing.B, fns []Formula) {
	b.ResetTimer()
	for ii := 0; ii < b.N; ii++ {
		for jj, v := range formulas {
			fn := fns[jj]
			for k, val := range v.Tests {
				x := fn(k)
				if x != val {
					b.Errorf("Bad result for %q with value %d. Want %d, got %d.", v.Expr, k, val, x)
				}
			}
		}
	}
}

func compileAndBenchmark(b *testing.B, fn func([]byte) (Formula, error)) {
	fns := make([]Formula, len(formulas))
	var err error
	for ii, v := range formulas {
		fns[ii], err = fn([]byte(v.Expr))
		if err != nil {
			b.Fatal(err)
		}
	}
	benchmarkFormulas(b, fns)
}

func BenchmarkVmInterpreted(b *testing.B) {
	compileAndBenchmark(b, compiler(false, false))
}

func BenchmarkVmInterpretedOptimized(b *testing.B) {
	compileAndBenchmark(b, compiler(true, false))
}

func BenchmarkJit(b *testing.B) {
	compileAndBenchmark(b, compiler(true, true))
}

func BenchmarkCompiled(b *testing.B) {
	fns := make([]Formula, len(formulas))
	var err error
	for ii, v := range formulas {
		text := fmt.Sprintf("nplurals=%d; plural=(%s);", v.N, v.Expr)
		fns[ii], _, err = Make(text)
		if err != nil {
			b.Fatal(err)
		}
	}
	benchmarkFormulas(b, fns)
}
<|endoftext|>


<commit_before>package cgroup

import (
	"bufio"
	"os"
	"path/filepath"
	"strings"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
)

var cgControllers = map[string]Backend{}
var cgNamespace bool

// Layout determines the cgroup layout on this system
type Layout int

const (
	// CgroupsDisabled indicates that cgroups are not supported
	CgroupsDisabled Layout = iota
	// CgroupsUnified indicates that this is a pure cgroup2 layout
	CgroupsUnified
	// CgroupsHybrid indicates that this is a mixed cgroup1 and cgroup2 layout
	CgroupsHybrid
	// CgroupsLegacy indicates that this is a pure cgroup1 layout
	CgroupsLegacy
)

var cgLayout Layout

// Info contains system cgroup information
type Info struct {
	// Layout is one of CgroupsDisabled, CgroupsUnified, CgroupsHybrid, CgroupsLegacy
	Layout Layout

	// Namespacing indicates support for the cgroup namespace
	Namespacing bool
}

// GetInfo returns basic system cgroup information
func GetInfo() Info {
	info := Info{}
	info.Namespacing = cgNamespace
	info.Layout = cgLayout

	return info
}

// Mode returns the cgroup layout name
func (info *Info) Mode() string {
	switch info.Layout {
	case CgroupsDisabled:
		return "disabled"
	case CgroupsUnified:
		return "cgroup2"
	case CgroupsHybrid:
		return "hybrid"
	case CgroupsLegacy:
		return "legacy"
	}

	return "unknown"
}

// Resource is a generic type used to abstract resource control features
// support for the legacy and unified hierarchy.
type Resource int

const (
	// Blkio resource control
	Blkio Resource = iota

	// BlkioWeight resource control
	BlkioWeight

	// CPU resource control
	CPU

	// CPUAcct resource control
	CPUAcct

	// CPUSet resource control
	CPUSet

	// Devices resource control
	Devices

	// Freezer resource control
	Freezer

	// Hugetlb resource control
	Hugetlb

	// Memory resource control
	Memory

	// MemoryMaxUsage resource control
	MemoryMaxUsage

	// MemorySwap resource control
	MemorySwap

	// MemorySwapMaxUsage resource control
	MemorySwapMaxUsage

	// MemorySwapUsage resource control
	MemorySwapUsage

	// MemorySwappiness resource control
	MemorySwappiness

	// NetPrio resource control
	NetPrio

	// Pids resource control
	Pids
)

// SupportsVersion indicates whether or not a given cgroup resource is
// controllable and in which type of cgroup filesystem.
func (info *Info) SupportsVersion(resource Resource) (Backend, bool) {
	switch resource {
	case Blkio:
		val, ok := cgControllers["blkio"]
		if ok {
			return val, ok
		}

		val, ok = cgControllers["io"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case BlkioWeight:
		val, ok := cgControllers["blkio.weight"]
		if ok {
			return val, ok
		}

		val, ok = cgControllers["io"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case CPU:
		val, ok := cgControllers["cpu"]
		return val, ok
	case CPUAcct:
		val, ok := cgControllers["cpuacct"]
		if ok {
			return val, ok
		}

		val, ok = cgControllers["cpu"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case CPUSet:
		val, ok := cgControllers["cpuset"]
		return val, ok
	case Devices:
		val, ok := cgControllers["devices"]
		return val, ok
	case Freezer:
		val, ok := cgControllers["freezer"]
		return val, ok
	case Hugetlb:
		val, ok := cgControllers["hugetlb"]
		return val, ok
	case Memory:
		val, ok := cgControllers["memory"]
		return val, ok
	case MemoryMaxUsage:
		val, ok := cgControllers["memory.max_usage_in_bytes"]
		return val, ok
	case MemorySwap:
		val, ok := cgControllers["memory.memsw.limit_in_bytes"]
		if ok {
			return val, ok
		}

		val, ok = cgControllers["memory.swap.max"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case MemorySwapMaxUsage:
		val, ok := cgControllers["memory.memsw.max_usage_in_bytes"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case MemorySwapUsage:
		val, ok := cgControllers["memory.memsw.usage_in_bytes"]
		if ok {
			return val, ok
		}

		val, ok = cgControllers["memory.swap.current"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case MemorySwappiness:
		val, ok := cgControllers["memory.swappiness"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case NetPrio:
		val, ok := cgControllers["net_prio"]
		return val, ok
	case Pids:
		val, ok := cgControllers["pids"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	}

	return Unavailable, false
}

// Supports indicates whether or not a given resource is controllable.
func (info *Info) Supports(resource Resource, cgroup *CGroup) bool {
	val, ok := info.SupportsVersion(resource)
	if val == V2 && cgroup != nil && !cgroup.UnifiedCapable {
		ok = false
	}

	return ok
}

// Warnings returns a list of CGroup warnings.
func (info *Info) Warnings() []db.Warning {
	warnings := []db.Warning{}

	if !info.Supports(Blkio, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupBlkio,
			LastMessage: "disk I/O limits will be ignored",
		})
	}

	if !info.Supports(BlkioWeight, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupBlkioWeight,
			LastMessage: "disk priority will be ignored",
		})
	}

	if !info.Supports(CPU, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupCPUController,
			LastMessage: "CPU time limits will be ignored",
		})
	}

	if !info.Supports(CPUAcct, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupCPUacctController,
			LastMessage: "CPU accounting will not be available",
		})
	}

	if !info.Supports(CPUSet, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupCPUController,
			LastMessage: "CPU pinning will be ignored",
		})
	}

	if !info.Supports(Devices, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupDevicesController,
			LastMessage: "device access control won't work",
		})
	}

	if !info.Supports(Freezer, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupFreezerController,
			LastMessage: "pausing/resuming containers won't work",
		})
	}

	if !info.Supports(Hugetlb, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupHugetlbController,
			LastMessage: "hugepage limits will be ignored",
		})
	}

	if !info.Supports(Memory, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupMemoryController,
			LastMessage: "memory limits will be ignored",
		})
	}

	if !info.Supports(NetPrio, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupNetworkPriorityController,
			LastMessage: "network priority will be ignored",
		})
	}

	if !info.Supports(Pids, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupPidsController,
			LastMessage: "process limits will be ignored",
		})
	}

	if !info.Supports(MemorySwap, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupMemorySwapAccounting,
			LastMessage: "swap limits will be ignored",
		})
	}

	return warnings
}

// Init initializes cgroups.
func Init() {
	_, err := os.Stat("/proc/self/ns/cgroup")
	if err == nil {
		cgNamespace = true
	}

	// Go through the list of resource controllers for LXD.
	selfCg, err := os.Open("/proc/self/cgroup")
	if err != nil {
		if os.IsNotExist(err) {
			logger.Warnf("System doesn't appear to support CGroups")
		} else {
			logger.Errorf("Unable to load list of cgroups: %v", err)
		}

		cgLayout = CgroupsDisabled
		return
	}
	defer func() { _ = selfCg.Close() }()

	hasV1 := false
	hasV2 := false
	// Go through the file line by line.
	scanSelfCg := bufio.NewScanner(selfCg)
	for scanSelfCg.Scan() {
		line := strings.TrimSpace(scanSelfCg.Text())
		fields := strings.SplitN(line, ":", 3)

		// Deal with the V1 controllers.
		if fields[1] != "" {
			controllers := strings.Split(fields[1], ",")
			for _, controller := range controllers {
				cgControllers[controller] = V1
			}

			hasV1 = true
			continue
		}

		// Parse V2 controllers.
		path := fields[2]
		hybridPath := filepath.Join(cgPath, "unified", path, "cgroup.controllers")
		dedicatedPath := ""

		controllers, err := os.Open(hybridPath)
		if err != nil {
			if !os.IsNotExist(err) {
				logger.Errorf("Unable to load cgroup.controllers")
				return
			}

			dedicatedPath = filepath.Join(cgPath, path, "cgroup.controllers")
			controllers, err = os.Open(dedicatedPath)
			if err != nil && !os.IsNotExist(err) {
				logger.Errorf("Unable to load cgroup.controllers")
				return
			}
		}

		if err == nil {
			unifiedControllers := map[string]Backend{}

			// Record the fact that V2 is present at all.
			unifiedControllers["unified"] = V2

			scanControllers := bufio.NewScanner(controllers)
			for scanControllers.Scan() {
				line := strings.TrimSpace(scanControllers.Text())
				for _, entry := range strings.Split(line, " ") {
					unifiedControllers[entry] = V2
				}
			}
			hasV2 = true

			if dedicatedPath != "" {
				cgControllers = unifiedControllers
				break
			} else {
				for k, v := range unifiedControllers {
					cgControllers[k] = v
				}
			}
		}
		_ = controllers.Close()
	}

	// Check for additional legacy cgroup features
	val, ok := cgControllers["blkio"]
	if ok && val == V1 && shared.PathExists("/sys/fs/cgroup/blkio/blkio.weight") {
		cgControllers["blkio.weight"] = V1
	} else {
		val, ok := cgControllers["blkio"]
		if ok && val == V1 && shared.PathExists("/sys/fs/cgroup/blkio/blkio.bfq.weight") {
			cgControllers["blkio.weight"] = V1
		}
	}

	val, ok = cgControllers["memory"]
	if ok && val == V1 {
		if shared.PathExists("/sys/fs/cgroup/memory/memory.max_usage_in_bytes") {
			cgControllers["memory.max_usage_in_bytes"] = V1
		}

		if shared.PathExists("/sys/fs/cgroup/memory/memory.swappiness") {
			cgControllers["memory.swappiness"] = V1
		}

		if shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes") {
			cgControllers["memory.memsw.limit_in_bytes"] = V1
		}

		if shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.usage_in_bytes") {
			cgControllers["memory.memsw.usage_in_bytes"] = V1
		}

		if shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.max_usage_in_bytes") {
			cgControllers["memory.memsw.max_usage_in_bytes"] = V1
		}
	}

	val, ok = cgControllers["memory"]
	if ok && val == V2 {
		if shared.PathExists("/sys/fs/cgroup/init.scope/memory.swap.max") {
			cgControllers["memory.swap.max"] = V2
		}

		if shared.PathExists("/sys/fs/cgroup/init.scope/memory.swap.current") {
			cgControllers["memory.swap.current"] = V2
		}
	}

	if hasV1 && hasV2 {
		cgLayout = CgroupsHybrid
	} else if hasV1 {
		cgLayout = CgroupsLegacy
	} else if hasV2 {
		cgLayout = CgroupsUnified
	}

	// "io" and "blkio" controllers are the same thing.
	val, ok = cgControllers["io"]
	if ok {
		cgControllers["blkio"] = val
	}

	if cgLayout == CgroupsUnified {
		// With Cgroup2 devices is built-in (through eBPF).
		cgControllers["devices"] = V2

		// With Cgroup2 freezer is built-in.
		cgControllers["freezer"] = V2
	}
}
<commit_msg>lxd/cgroup: Better handle broken CGroupV2 setups<commit_after>package cgroup

import (
	"bufio"
	"os"
	"path/filepath"
	"strings"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
)

var cgControllers = map[string]Backend{}
var cgNamespace bool

// Layout determines the cgroup layout on this system
type Layout int

const (
	// CgroupsDisabled indicates that cgroups are not supported
	CgroupsDisabled Layout = iota
	// CgroupsUnified indicates that this is a pure cgroup2 layout
	CgroupsUnified
	// CgroupsHybrid indicates that this is a mixed cgroup1 and cgroup2 layout
	CgroupsHybrid
	// CgroupsLegacy indicates that this is a pure cgroup1 layout
	CgroupsLegacy
)

var cgLayout Layout

// Info contains system cgroup information
type Info struct {
	// Layout is one of CgroupsDisabled, CgroupsUnified, CgroupsHybrid, CgroupsLegacy
	Layout Layout

	// Namespacing indicates support for the cgroup namespace
	Namespacing bool
}

// GetInfo returns basic system cgroup information
func GetInfo() Info {
	info := Info{}
	info.Namespacing = cgNamespace
	info.Layout = cgLayout

	return info
}

// Mode returns the cgroup layout name
func (info *Info) Mode() string {
	switch info.Layout {
	case CgroupsDisabled:
		return "disabled"
	case CgroupsUnified:
		return "cgroup2"
	case CgroupsHybrid:
		return "hybrid"
	case CgroupsLegacy:
		return "legacy"
	}

	return "unknown"
}

// Resource is a generic type used to abstract resource control features
// support for the legacy and unified hierarchy.
type Resource int

const (
	// Blkio resource control
	Blkio Resource = iota

	// BlkioWeight resource control
	BlkioWeight

	// CPU resource control
	CPU

	// CPUAcct resource control
	CPUAcct

	// CPUSet resource control
	CPUSet

	// Devices resource control
	Devices

	// Freezer resource control
	Freezer

	// Hugetlb resource control
	Hugetlb

	// Memory resource control
	Memory

	// MemoryMaxUsage resource control
	MemoryMaxUsage

	// MemorySwap resource control
	MemorySwap

	// MemorySwapMaxUsage resource control
	MemorySwapMaxUsage

	// MemorySwapUsage resource control
	MemorySwapUsage

	// MemorySwappiness resource control
	MemorySwappiness

	// NetPrio resource control
	NetPrio

	// Pids resource control
	Pids
)

// SupportsVersion indicates whether or not a given cgroup resource is
// controllable and in which type of cgroup filesystem.
func (info *Info) SupportsVersion(resource Resource) (Backend, bool) {
	switch resource {
	case Blkio:
		val, ok := cgControllers["blkio"]
		if ok {
			return val, ok
		}

		val, ok = cgControllers["io"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case BlkioWeight:
		val, ok := cgControllers["blkio.weight"]
		if ok {
			return val, ok
		}

		val, ok = cgControllers["io"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case CPU:
		val, ok := cgControllers["cpu"]
		return val, ok
	case CPUAcct:
		val, ok := cgControllers["cpuacct"]
		if ok {
			return val, ok
		}

		val, ok = cgControllers["cpu"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case CPUSet:
		val, ok := cgControllers["cpuset"]
		return val, ok
	case Devices:
		val, ok := cgControllers["devices"]
		return val, ok
	case Freezer:
		val, ok := cgControllers["freezer"]
		return val, ok
	case Hugetlb:
		val, ok := cgControllers["hugetlb"]
		return val, ok
	case Memory:
		val, ok := cgControllers["memory"]
		return val, ok
	case MemoryMaxUsage:
		val, ok := cgControllers["memory.max_usage_in_bytes"]
		return val, ok
	case MemorySwap:
		val, ok := cgControllers["memory.memsw.limit_in_bytes"]
		if ok {
			return val, ok
		}

		val, ok = cgControllers["memory.swap.max"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case MemorySwapMaxUsage:
		val, ok := cgControllers["memory.memsw.max_usage_in_bytes"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case MemorySwapUsage:
		val, ok := cgControllers["memory.memsw.usage_in_bytes"]
		if ok {
			return val, ok
		}

		val, ok = cgControllers["memory.swap.current"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case MemorySwappiness:
		val, ok := cgControllers["memory.swappiness"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	case NetPrio:
		val, ok := cgControllers["net_prio"]
		return val, ok
	case Pids:
		val, ok := cgControllers["pids"]
		if ok {
			return val, ok
		}

		return Unavailable, false
	}

	return Unavailable, false
}

// Supports indicates whether or not a given resource is controllable.
func (info *Info) Supports(resource Resource, cgroup *CGroup) bool {
	val, ok := info.SupportsVersion(resource)
	if val == V2 && cgroup != nil && !cgroup.UnifiedCapable {
		ok = false
	}

	return ok
}

// Warnings returns a list of CGroup warnings.
func (info *Info) Warnings() []db.Warning {
	warnings := []db.Warning{}

	if !info.Supports(Blkio, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupBlkio,
			LastMessage: "disk I/O limits will be ignored",
		})
	}

	if !info.Supports(BlkioWeight, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupBlkioWeight,
			LastMessage: "disk priority will be ignored",
		})
	}

	if !info.Supports(CPU, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupCPUController,
			LastMessage: "CPU time limits will be ignored",
		})
	}

	if !info.Supports(CPUAcct, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupCPUacctController,
			LastMessage: "CPU accounting will not be available",
		})
	}

	if !info.Supports(CPUSet, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupCPUController,
			LastMessage: "CPU pinning will be ignored",
		})
	}

	if !info.Supports(Devices, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupDevicesController,
			LastMessage: "device access control won't work",
		})
	}

	if !info.Supports(Freezer, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupFreezerController,
			LastMessage: "pausing/resuming containers won't work",
		})
	}

	if !info.Supports(Hugetlb, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupHugetlbController,
			LastMessage: "hugepage limits will be ignored",
		})
	}

	if !info.Supports(Memory, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupMemoryController,
			LastMessage: "memory limits will be ignored",
		})
	}

	if !info.Supports(NetPrio, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupNetworkPriorityController,
			LastMessage: "network priority will be ignored",
		})
	}

	if !info.Supports(Pids, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupPidsController,
			LastMessage: "process limits will be ignored",
		})
	}

	if !info.Supports(MemorySwap, nil) {
		warnings = append(warnings, db.Warning{
			TypeCode:    db.WarningMissingCGroupMemorySwapAccounting,
			LastMessage: "swap limits will be ignored",
		})
	}

	return warnings
}

// Init initializes cgroups.
func Init() {
	_, err := os.Stat("/proc/self/ns/cgroup")
	if err == nil {
		cgNamespace = true
	}

	// Go through the list of resource controllers for LXD.
	selfCg, err := os.Open("/proc/self/cgroup")
	if err != nil {
		if os.IsNotExist(err) {
			logger.Warnf("System doesn't appear to support CGroups")
		} else {
			logger.Errorf("Unable to load list of cgroups: %v", err)
		}

		cgLayout = CgroupsDisabled
		return
	}
	defer func() { _ = selfCg.Close() }()

	hasV1 := false
	hasV2 := false
	hasV2Root := false

	// Go through the file line by line.
	scanSelfCg := bufio.NewScanner(selfCg)
	for scanSelfCg.Scan() {
		line := strings.TrimSpace(scanSelfCg.Text())
		fields := strings.SplitN(line, ":", 3)

		// Deal with the V1 controllers.
		if fields[1] != "" {
			controllers := strings.Split(fields[1], ",")
			for _, controller := range controllers {
				cgControllers[controller] = V1
			}

			hasV1 = true
			continue
		}

		// Parse V2 controllers.
		path := fields[2]
		hybridPath := filepath.Join(cgPath, "unified", path, "cgroup.controllers")
		dedicatedPath := ""

		controllers, err := os.Open(hybridPath)
		if err != nil {
			if !os.IsNotExist(err) {
				logger.Errorf("Unable to load cgroup.controllers")
				return
			}

			dedicatedPath = filepath.Join(cgPath, path, "cgroup.controllers")
			controllers, err = os.Open(dedicatedPath)
			if err != nil && !os.IsNotExist(err) {
				logger.Errorf("Unable to load cgroup.controllers")
				return
			}
		}

		if err == nil {
			unifiedControllers := map[string]Backend{}

			// Record the fact that V2 is present at all.
			unifiedControllers["unified"] = V2

			scanControllers := bufio.NewScanner(controllers)
			for scanControllers.Scan() {
				line := strings.TrimSpace(scanControllers.Text())
				for _, entry := range strings.Split(line, " ") {
					unifiedControllers[entry] = V2
				}
			}
			hasV2 = true

			if dedicatedPath != "" {
				cgControllers = unifiedControllers
				hasV2Root = true
				break
			} else {
				for k, v := range unifiedControllers {
					cgControllers[k] = v
				}
			}
		}
		_ = controllers.Close()
	}

	// Discard weird setups that apply CGroupV1 trees on top of a CGroupV2 root.
	if hasV2Root && hasV1 {
		logger.Warn("Unsupported CGroup setup detected, V1 controllers on top of V2 root")
		hasV1 = false
	}

	// Check for additional legacy cgroup features
	val, ok := cgControllers["blkio"]
	if ok && val == V1 && shared.PathExists("/sys/fs/cgroup/blkio/blkio.weight") {
		cgControllers["blkio.weight"] = V1
	} else {
		val, ok := cgControllers["blkio"]
		if ok && val == V1 && shared.PathExists("/sys/fs/cgroup/blkio/blkio.bfq.weight") {
			cgControllers["blkio.weight"] = V1
		}
	}

	val, ok = cgControllers["memory"]
	if ok && val == V1 {
		if shared.PathExists("/sys/fs/cgroup/memory/memory.max_usage_in_bytes") {
			cgControllers["memory.max_usage_in_bytes"] = V1
		}

		if shared.PathExists("/sys/fs/cgroup/memory/memory.swappiness") {
			cgControllers["memory.swappiness"] = V1
		}

		if shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes") {
			cgControllers["memory.memsw.limit_in_bytes"] = V1
		}

		if shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.usage_in_bytes") {
			cgControllers["memory.memsw.usage_in_bytes"] = V1
		}

		if shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.max_usage_in_bytes") {
			cgControllers["memory.memsw.max_usage_in_bytes"] = V1
		}
	}

	val, ok = cgControllers["memory"]
	if ok && val == V2 {
		if shared.PathExists("/sys/fs/cgroup/init.scope/memory.swap.max") {
			cgControllers["memory.swap.max"] = V2
		}

		if shared.PathExists("/sys/fs/cgroup/init.scope/memory.swap.current") {
			cgControllers["memory.swap.current"] = V2
		}
	}

	if hasV1 && hasV2 {
		cgLayout = CgroupsHybrid
	} else if hasV1 {
		cgLayout = CgroupsLegacy
	} else if hasV2 {
		cgLayout = CgroupsUnified
	}

	// "io" and "blkio" controllers are the same thing.
	val, ok = cgControllers["io"]
	if ok {
		cgControllers["blkio"] = val
	}

	if cgLayout == CgroupsUnified {
		// With Cgroup2 devices is built-in (through eBPF).
		cgControllers["devices"] = V2

		// With Cgroup2 freezer is built-in.
		cgControllers["freezer"] = V2
	}
}
<|endoftext|>