<commit_before>package main

import (
	"log"
	"net/http"
	"os"
	"regexp"
	"runtime"

	"github.com/gorilla/websocket"
	sm "github.com/tchap/go-statemachine"
)

type DeskMsg struct {
	Desk [][]string `json:"desk"`
}

type PlayerMsg struct {
	Cmd  sm.EventType `json:"command"`
	Card string       `json:"card"`
}

//

var Order map[string]int = map[string]int{
	"6": 6, "7": 7, "8": 8, "9": 9, "10": 10,
	"J": 11,
	"Q": 12,
	"K": 13,
	"A": 14,
}

func higher(c0, c1, t string) int {
	// c0 and c1 have the same suit
	if c0[0] == c1[0] {
		if Order[c0[1:]] > Order[c1[1:]] {
			return 1
		}
		if Order[c0[1:]] < Order[c1[1:]] {
			return -1
		}
		return 0
	}
	// c0 is trump, c1 is not
	if c0[:1] == t {
		return 1
	}
	// c1 is trump, c0 is not
	if c1[:1] == t {
		return -1
	}
	// suits are different, both are not trump
	return -2
}

var CardRE *regexp.Regexp = regexp.MustCompile(`[SCHD]([6-9JQKA]|10)`)

func isValid(c string) bool {
	return CardRE.MatchString(c)
}

//

const (
	cmdStart sm.EventType = iota
	cmdMove

	cmdCount
)

const (
	stateCollection sm.State = iota
	stateDistribution
	stateGame

	stateAttack
	stateDefense
	statePoll

	stateCount
)

func stateToString(s sm.State) string {
	return [...]string{
		stateCollection:   "COLLECTION",
		stateDistribution: "DISTRIBUTION",
		stateGame:         "GAME",
	}[s]
}

func cmdToString(t sm.EventType) string {
	return [...]string{
		cmdStart: "START",
		cmdMove:  "MOVE",
	}[t]
}

//

type cmdArgs struct {
	conn *websocket.Conn
	card string
}

//

type gameState struct {
	// trump suit
	trump string

	// attacker
	aconn *websocket.Conn
	// defender
	dconn *websocket.Conn
	// card that should be beaten
	cardToBeat string

	sm *sm.StateMachine
}

func (self *gameState) nextAttacker() *websocket.Conn {
	return nil
}

func (self *gameState) nextDefender() *websocket.Conn {
	return nil
}

func logOutOfTurn(conn *websocket.Conn) {
	log.Printf("out of turn: %v", conn)
}

func logWontBeat(c1, c2, t string) {
	log.Printf("%v won't bit %v, trump is ", c1, c2, t)
}

func NewGameState() *gameState {
	gst := new(gameState)

	gst.sm = sm.New(stateGame, uint(stateCount), uint(cmdCount))

	gst.sm.On(cmdMove,
		[]sm.State{stateAttack},
		gst.handleMoveInAttack,
	)

	gst.sm.On(cmdMove,
		[]sm.State{stateDefense},
		gst.handleMoveInDefense,
	)

	return gst
}

var GSt *gameState = NewGameState()

// event handlers

func (self *gameState) handleMoveInAttack(s sm.State, e *sm.Event) sm.State {
	conn := e.Data.(cmdArgs).conn
	card := e.Data.(cmdArgs).card

	if conn != self.aconn {
		logOutOfTurn(conn)
		return s
	}

	// attacker won't push more cards
	if card == "" {
		self.aconn = self.nextAttacker()
		return s
	}

	self.cardToBeat = card

	return stateDefense
}

func (self *gameState) handleMoveInDefense(s sm.State, e *sm.Event) sm.State {
	conn := e.Data.(cmdArgs).conn
	card := e.Data.(cmdArgs).card

	if conn != self.dconn {
		logOutOfTurn(conn)
		return s
	}

	// defender takes the cards
	if card == "" {
		self.aconn = self.nextAttacker()
		self.dconn = self.nextDefender()

		//distribute cards

		return stateAttack
	}

	// check that the sent card is capable to beat
	if higher(card, self.cardToBeat, self.trump) != 1 {
		logWontBeat(card, self.cardToBeat, self.trump)
		return s
	}

	return statePoll
}

//

type durakSrv struct {
	conn *websocket.Conn
	gst  *gameState
}

func (self *durakSrv) read() {
	var m PlayerMsg

	for {
		err := self.conn.ReadJSON(&m)
		if err != nil {
			log.Println(err)
			return
		}

		switch m.Cmd {
		case cmdStart:
		case cmdMove:
			event := &sm.Event{cmdMove, cmdArgs{self.conn, m.Card}}

			err = self.gst.sm.Emit(event)
			if err != nil {
				log.Println(err)
				return
			}
		}
	}
}

//

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func durakHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	defer conn.Close() //dbg

	d := durakSrv{conn, GSt}

	d.read()
}

//

func main() {
	http.HandleFunc("/", durakHandler)

	log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), nil))
}

func init() {
	runtime.GOMAXPROCS(runtime.NumCPU())
}
<commit_msg>removed statePoll<commit_after>package main

import (
	"log"
	"net/http"
	"os"
	"regexp"
	"runtime"

	"github.com/gorilla/websocket"
	sm "github.com/tchap/go-statemachine"
)

type DeskMsg struct {
	Desk [][]string `json:"desk"`
}

type PlayerMsg struct {
	Cmd  sm.EventType `json:"command"`
	Card string       `json:"card"`
}

//

var Order map[string]int = map[string]int{
	"6": 6, "7": 7, "8": 8, "9": 9, "10": 10,
	"J": 11,
	"Q": 12,
	"K": 13,
	"A": 14,
}

func higher(c0, c1, t string) int {
	// c0 and c1 have the same suit
	if c0[0] == c1[0] {
		if Order[c0[1:]] > Order[c1[1:]] {
			return 1
		}
		if Order[c0[1:]] < Order[c1[1:]] {
			return -1
		}
		return 0
	}
	// c0 is trump, c1 is not
	if c0[:1] == t {
		return 1
	}
	// c1 is trump, c0 is not
	if c1[:1] == t {
		return -1
	}
	// suits are different, both are not trump
	return -2
}

var CardRE *regexp.Regexp = regexp.MustCompile(`[SCHD]([6-9JQKA]|10)`)

func isValid(c string) bool {
	return CardRE.MatchString(c)
}

//

const (
	cmdStart sm.EventType = iota
	cmdMove

	cmdCount
)

const (
	stateCollection sm.State = iota
	stateDistribution
	stateGame

	stateAttack
	stateDefense

	stateCount
)

func stateToString(s sm.State) string {
	return [...]string{
		stateCollection:   "COLLECTION",
		stateDistribution: "DISTRIBUTION",
		stateGame:         "GAME",
	}[s]
}

func cmdToString(t sm.EventType) string {
	return [...]string{
		cmdStart: "START",
		cmdMove:  "MOVE",
	}[t]
}

//

type cmdArgs struct {
	conn *websocket.Conn
	card string
}

//

type gameState struct {
	// trump suit
	trump string

	// attacker
	aconn *websocket.Conn
	// defender
	dconn *websocket.Conn
	// card that should be beaten
	cardToBeat string

	sm *sm.StateMachine
}

func (self *gameState) nextAttacker() *websocket.Conn {
	return nil
}

func (self *gameState) nextDefender() *websocket.Conn {
	return nil
}

func logOutOfTurn(conn *websocket.Conn) {
	log.Printf("out of turn: %v", conn)
}

func logWontBeat(c1, c2, t string) {
	log.Printf("%v won't bit %v, trump is ", c1, c2, t)
}

func NewGameState() *gameState {
	gst := new(gameState)

	gst.sm = sm.New(stateGame, uint(stateCount), uint(cmdCount))

	gst.sm.On(cmdMove,
		[]sm.State{stateAttack},
		gst.handleMoveInAttack,
	)

	gst.sm.On(cmdMove,
		[]sm.State{stateDefense},
		gst.handleMoveInDefense,
	)

	return gst
}

var GSt *gameState = NewGameState()

// event handlers

func (self *gameState) handleMoveInAttack(s sm.State, e *sm.Event) sm.State {
	conn := e.Data.(cmdArgs).conn
	card := e.Data.(cmdArgs).card

	if conn != self.aconn {
		logOutOfTurn(conn)
		return s
	}

	// attacker won't push more cards
	if card == "" {
		self.aconn = self.nextAttacker()
		return s
	}

	self.cardToBeat = card

	return stateDefense
}

func (self *gameState) handleMoveInDefense(s sm.State, e *sm.Event) sm.State {
	conn := e.Data.(cmdArgs).conn
	card := e.Data.(cmdArgs).card

	if conn != self.dconn {
		logOutOfTurn(conn)
		return s
	}

	// defender takes the cards
	if card == "" {
		self.aconn = self.nextAttacker()
		self.dconn = self.nextDefender()

		//distribute cards

		return stateAttack
	}

	// check that the sent card is capable to beat
	if higher(card, self.cardToBeat, self.trump) != 1 {
		logWontBeat(card, self.cardToBeat, self.trump)
		return s
	}

	return stateAttack
}

//

type durakSrv struct {
	conn *websocket.Conn
	gst  *gameState
}

func (self *durakSrv) read() {
	var m PlayerMsg

	for {
		err := self.conn.ReadJSON(&m)
		if err != nil {
			log.Println(err)
			return
		}

		switch m.Cmd {
		case cmdStart:
		case cmdMove:
			event := &sm.Event{cmdMove, cmdArgs{self.conn, m.Card}}

			err = self.gst.sm.Emit(event)
			if err != nil {
				log.Println(err)
				return
			}
		}
	}
}

//

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func durakHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	defer conn.Close() //dbg

	d := durakSrv{conn, GSt}

	d.read()
}

//

func main() {
	http.HandleFunc("/", durakHandler)

	log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), nil))
}

func init() {
	runtime.GOMAXPROCS(runtime.NumCPU())
}
<|endoftext|>


<commit_before>package i18n

import (
	"bytes"
	"strconv"

	"github.com/volatile/core"
)

// Num returns a formatted number with decimal and thousands marks, according to the locale decimalMark and thousandsMark respectively.
// If not set, the decimal mark is "," and the thousands mark is ".".
func Num(c *core.Context, n interface{}) string {
	switch n.(type) {
	case uint:
		return formatNum(c, []byte(strconv.FormatUint(uint64(n.(uint)), 10)))
	case uint8:
		return formatNum(c, []byte(strconv.FormatUint(uint64(n.(uint8)), 10)))
	case uint16:
		return formatNum(c, []byte(strconv.FormatUint(uint64(n.(uint16)), 10)))
	case uint32:
		return formatNum(c, []byte(strconv.FormatUint(uint64(n.(uint32)), 10)))
	case uint64:
		return formatNum(c, []byte(strconv.FormatUint(n.(uint64), 10)))
	case int:
		return formatNum(c, []byte(strconv.Itoa(n.(int))))
	case int8:
		return formatNum(c, []byte(strconv.FormatInt(int64(n.(int8)), 10)))
	case int16:
		return formatNum(c, []byte(strconv.FormatInt(int64(n.(int16)), 10)))
	case int32:
		return formatNum(c, []byte(strconv.FormatInt(int64(n.(int32)), 10)))
	case int64:
		return formatNum(c, []byte(strconv.FormatInt(n.(int64), 10)))
	case float32:
		println(strconv.FormatFloat(float64(n.(float32)), 'f', 8, 32))
		return formatNum(c, []byte(strconv.FormatFloat(float64(n.(float32)), 'f', -1, 32)))
	case float64:
		return formatNum(c, []byte(strconv.FormatFloat(n.(float64), 'f', -1, 64)))
	case string:
		return formatNum(c, []byte(n.(string)))
	case []byte:
		return formatNum(c, n.([]byte))
	default:
		return ""
	}
}

func formatNum(c *core.Context, b []byte) (s string) {
	decimalMark := "."
	thousandsMark := ","
	if locale, ok := locales[ClientLocale(c)]; ok {
		if v, ok := locale["decimalMark"]; ok {
			decimalMark = v
		}
		if v, ok := locale["thousandsMark"]; ok {
			thousandsMark = v
		}
	}

	bb := bytes.Split(b, []byte("."))
	switch len(bb) {
	case 1:
		break
	case 2:
		s = decimalMark + string(bb[1])
		b = bb[0]
	default:
		return "" // Can't have 2 decimal marks in a number so return nothing.
	}

	j := 0
	for i := len(b) - 1; i >= 0; i-- {
		if j != 0 && j%3 == 0 {
			s = thousandsMark + s
		}
		s = string(b[i]) + s
		j++
	}
	return
}
<commit_msg>Remove useless chars<commit_after>package i18n

import (
	"bytes"
	"strconv"

	"github.com/volatile/core"
)

// Num returns a formatted number with decimal and thousands marks, according to the locale decimalMark and thousandsMark respectively.
// If not set, the decimal mark is "," and the thousands mark is ".".
func Num(c *core.Context, n interface{}) string {
	switch n.(type) {
	case uint:
		return formatNum(c, []byte(strconv.FormatUint(uint64(n.(uint)), 10)))
	case uint8:
		return formatNum(c, []byte(strconv.FormatUint(uint64(n.(uint8)), 10)))
	case uint16:
		return formatNum(c, []byte(strconv.FormatUint(uint64(n.(uint16)), 10)))
	case uint32:
		return formatNum(c, []byte(strconv.FormatUint(uint64(n.(uint32)), 10)))
	case uint64:
		return formatNum(c, []byte(strconv.FormatUint(n.(uint64), 10)))
	case int:
		return formatNum(c, []byte(strconv.Itoa(n.(int))))
	case int8:
		return formatNum(c, []byte(strconv.FormatInt(int64(n.(int8)), 10)))
	case int16:
		return formatNum(c, []byte(strconv.FormatInt(int64(n.(int16)), 10)))
	case int32:
		return formatNum(c, []byte(strconv.FormatInt(int64(n.(int32)), 10)))
	case int64:
		return formatNum(c, []byte(strconv.FormatInt(n.(int64), 10)))
	case float32:
		println(strconv.FormatFloat(float64(n.(float32)), 'f', 8, 32))
		return formatNum(c, []byte(strconv.FormatFloat(float64(n.(float32)), 'f', -1, 32)))
	case float64:
		return formatNum(c, []byte(strconv.FormatFloat(n.(float64), 'f', -1, 64)))
	case string:
		return formatNum(c, []byte(n.(string)))
	case []byte:
		return formatNum(c, n.([]byte))
	default:
		return ""
	}
}

func formatNum(c *core.Context, b []byte) (s string) {
	decimalMark := "."
	thousandsMark := ","
	if locale, ok := locales[ClientLocale(c)]; ok {
		if v, ok := locale["decimalMark"]; ok {
			decimalMark = v
		}
		if v, ok := locale["thousandsMark"]; ok {
			thousandsMark = v
		}
	}

	bb := bytes.Split(b, []byte("."))
	switch len(bb) {
	case 1:
		break
	case 2:
		s = decimalMark + string(bb[1])
		b = bb[0]
	default:
		return // Can't have 2 decimal marks in a number so return nothing.
	}

	j := 0
	for i := len(b) - 1; i >= 0; i-- {
		if j != 0 && j%3 == 0 {
			s = thousandsMark + s
		}
		s = string(b[i]) + s
		j++
	}
	return
}
<|endoftext|>


<commit_before>package lfs

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/github/git-lfs/git"
)

type Configuration struct {
	CurrentRemote         string
	gitConfig             map[string]string
	remotes               []string
	httpClient            *http.Client
	redirectingHttpClient *http.Client
	isTracingHttp         bool
	loading               sync.Mutex
}

type Endpoint struct {
	Url            string
	SshUserAndHost string
	SshPath        string
}

var (
	Config        = NewConfig()
	httpPrefixRe  = regexp.MustCompile("\\Ahttps?://")
	defaultRemote = "origin"
)

func NewConfig() *Configuration {
	c := &Configuration{
		CurrentRemote: defaultRemote,
		isTracingHttp: len(os.Getenv("GIT_CURL_VERBOSE")) > 0,
	}
	return c
}

func (c *Configuration) Endpoint() Endpoint {
	if url, ok := c.GitConfig("lfs.url"); ok {
		return Endpoint{Url: url}
	}

	if len(c.CurrentRemote) > 0 && c.CurrentRemote != defaultRemote {
		if endpoint := c.RemoteEndpoint(c.CurrentRemote); len(endpoint.Url) > 0 {
			return endpoint
		}
	}

	return c.RemoteEndpoint(defaultRemote)
}

func (c *Configuration) ConcurrentTransfers() int {
	uploads := 3

	if v, ok := c.GitConfig("lfs.concurrenttransfers"); ok {
		n, err := strconv.Atoi(v)
		if err == nil && n > 0 {
			uploads = n
		}
	}

	return uploads
}

func (c *Configuration) BatchTransfer() bool {
	if v, ok := c.GitConfig("lfs.batch"); ok {
		if v == "true" || v == "" {
			return true
		}

		// Any numeric value except 0 is considered true
		if n, err := strconv.Atoi(v); err == nil && n != 0 {
			return true
		}
	}
	return false
}

func (c *Configuration) RemoteEndpoint(remote string) Endpoint {
	if len(remote) == 0 {
		remote = defaultRemote
	}

	if url, ok := c.GitConfig("remote." + remote + ".lfsurl"); ok {
		return Endpoint{Url: url}
	}

	if url, ok := c.GitConfig("remote." + remote + ".url"); ok {
		endpoint := Endpoint{Url: url}

		if !httpPrefixRe.MatchString(url) {
			pieces := strings.SplitN(url, ":", 2)
			hostPieces := strings.SplitN(pieces[0], "@", 2)
			if len(hostPieces) < 2 {
				endpoint.Url = "<unknown>"
				return endpoint
			}

			endpoint.SshUserAndHost = pieces[0]
			endpoint.SshPath = pieces[1]
			endpoint.Url = fmt.Sprintf("https://%s/%s", hostPieces[1], pieces[1])
		}

		if path.Ext(url) == ".git" {
			endpoint.Url += "/info/lfs"
		} else {
			endpoint.Url += ".git/info/lfs"
		}

		return endpoint
	}

	return Endpoint{}
}

func (c *Configuration) Remotes() []string {
	c.loadGitConfig()
	return c.remotes
}

func (c *Configuration) GitConfig(key string) (string, bool) {
	c.loadGitConfig()
	value, ok := c.gitConfig[strings.ToLower(key)]
	return value, ok
}

func (c *Configuration) SetConfig(key, value string) {
	c.loadGitConfig()
	c.gitConfig[key] = value
}

func (c *Configuration) ObjectUrl(oid string) (*url.URL, error) {
	return ObjectUrl(c.Endpoint(), oid)
}

func ObjectUrl(endpoint Endpoint, oid string) (*url.URL, error) {
	u, err := url.Parse(endpoint.Url)
	if err != nil {
		return nil, err
	}

	u.Path = path.Join(u.Path, "objects")
	if len(oid) > 0 {
		u.Path = path.Join(u.Path, oid)
	}
	return u, nil
}

func (c *Configuration) loadGitConfig() {
	c.loading.Lock()
	defer c.loading.Unlock()

	if c.gitConfig != nil {
		return
	}

	uniqRemotes := make(map[string]bool)

	c.gitConfig = make(map[string]string)

	var output string
	listOutput, err := git.Config.List()
	if err != nil {
		panic(fmt.Errorf("Error listing git config: %s", err))
	}

	configFile := filepath.Join(LocalWorkingDir, ".gitconfig")
	fileOutput, err := git.Config.ListFromFile(configFile)
	if err != nil {
		panic(fmt.Errorf("Error listing git config from file: %s", err))
	}

	output = fileOutput + "\n" + listOutput

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		pieces := strings.SplitN(line, "=", 2)
		if len(pieces) < 2 {
			continue
		}
		key := strings.ToLower(pieces[0])
		c.gitConfig[key] = pieces[1]

		keyParts := strings.Split(key, ".")
		if len(keyParts) > 1 && keyParts[0] == "remote" {
			remote := keyParts[1]
			uniqRemotes[remote] = remote == "origin"
		}
	}

	c.remotes = make([]string, 0, len(uniqRemotes))
	for remote, isOrigin := range uniqRemotes {
		if isOrigin {
			continue
		}
		c.remotes = append(c.remotes, remote)
	}
}
<commit_msg>ラララララ ラー ララララー<commit_after>package lfs

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/github/git-lfs/git"
)

type Configuration struct {
	CurrentRemote         string
	gitConfig             map[string]string
	remotes               []string
	httpClient            *http.Client
	redirectingHttpClient *http.Client
	isTracingHttp         bool
	loading               sync.Mutex
}

type Endpoint struct {
	Url            string
	SshUserAndHost string
	SshPath        string
}

var (
	Config        = NewConfig()
	httpPrefixRe  = regexp.MustCompile("\\Ahttps?://")
	defaultRemote = "origin"
)

func NewConfig() *Configuration {
	c := &Configuration{
		CurrentRemote: defaultRemote,
		isTracingHttp: len(os.Getenv("GIT_CURL_VERBOSE")) > 0,
	}
	return c
}

func (c *Configuration) Endpoint() Endpoint {
	if url, ok := c.GitConfig("lfs.url"); ok {
		return Endpoint{Url: url}
	}

	if len(c.CurrentRemote) > 0 && c.CurrentRemote != defaultRemote {
		if endpoint := c.RemoteEndpoint(c.CurrentRemote); len(endpoint.Url) > 0 {
			return endpoint
		}
	}

	return c.RemoteEndpoint(defaultRemote)
}

func (c *Configuration) ConcurrentTransfers() int {
	uploads := 3

	if v, ok := c.GitConfig("lfs.concurrenttransfers"); ok {
		n, err := strconv.Atoi(v)
		if err == nil && n > 0 {
			uploads = n
		}
	}

	return uploads
}

func (c *Configuration) BatchTransfer() bool {
	if v, ok := c.GitConfig("lfs.batch"); ok {
		if v == "true" || v == "" {
			return true
		}

		// Any numeric value except 0 is considered true
		if n, err := strconv.Atoi(v); err == nil && n != 0 {
			return true
		}
	}
	return false
}

func (c *Configuration) RemoteEndpoint(remote string) Endpoint {
	if len(remote) == 0 {
		remote = defaultRemote
	}

	if url, ok := c.GitConfig("remote." + remote + ".lfsurl"); ok {
		return Endpoint{Url: url}
	}

	if url, ok := c.GitConfig("remote." + remote + ".url"); ok {
		return remoteEndpointFromUrl(url)
	}

	return Endpoint{}
}

func remoteEndpointFromUrl(url string) Endpoint {
	e := Endpoint{Url: url}

	if !httpPrefixRe.MatchString(url) {
		pieces := strings.SplitN(url, ":", 2)
		hostPieces := strings.SplitN(pieces[0], "@", 2)
		if len(hostPieces) < 2 {
			e.Url = "<unknown>"
			return e
		}

		e.SshUserAndHost = pieces[0]
		e.SshPath = pieces[1]
		e.Url = fmt.Sprintf("https://%s/%s", hostPieces[1], pieces[1])
	}

	if path.Ext(url) == ".git" {
		e.Url += "/info/lfs"
	} else {
		e.Url += ".git/info/lfs"
	}

	return e
}

func (c *Configuration) Remotes() []string {
	c.loadGitConfig()
	return c.remotes
}

func (c *Configuration) GitConfig(key string) (string, bool) {
	c.loadGitConfig()
	value, ok := c.gitConfig[strings.ToLower(key)]
	return value, ok
}

func (c *Configuration) SetConfig(key, value string) {
	c.loadGitConfig()
	c.gitConfig[key] = value
}

func (c *Configuration) ObjectUrl(oid string) (*url.URL, error) {
	return ObjectUrl(c.Endpoint(), oid)
}

func ObjectUrl(endpoint Endpoint, oid string) (*url.URL, error) {
	u, err := url.Parse(endpoint.Url)
	if err != nil {
		return nil, err
	}

	u.Path = path.Join(u.Path, "objects")
	if len(oid) > 0 {
		u.Path = path.Join(u.Path, oid)
	}
	return u, nil
}

func (c *Configuration) loadGitConfig() {
	c.loading.Lock()
	defer c.loading.Unlock()

	if c.gitConfig != nil {
		return
	}

	uniqRemotes := make(map[string]bool)

	c.gitConfig = make(map[string]string)

	var output string
	listOutput, err := git.Config.List()
	if err != nil {
		panic(fmt.Errorf("Error listing git config: %s", err))
	}

	configFile := filepath.Join(LocalWorkingDir, ".gitconfig")
	fileOutput, err := git.Config.ListFromFile(configFile)
	if err != nil {
		panic(fmt.Errorf("Error listing git config from file: %s", err))
	}

	output = fileOutput + "\n" + listOutput

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		pieces := strings.SplitN(line, "=", 2)
		if len(pieces) < 2 {
			continue
		}
		key := strings.ToLower(pieces[0])
		c.gitConfig[key] = pieces[1]

		keyParts := strings.Split(key, ".")
		if len(keyParts) > 1 && keyParts[0] == "remote" {
			remote := keyParts[1]
			uniqRemotes[remote] = remote == "origin"
		}
	}

	c.remotes = make([]string, 0, len(uniqRemotes))
	for remote, isOrigin := range uniqRemotes {
		if isOrigin {
			continue
		}
		c.remotes = append(c.remotes, remote)
	}
}
<|endoftext|>


<commit_before>package lfs

// The LFS error system provides a simple wrapper around Go errors and the
// ability to inspect errors. It is strongly influenced by Dave Cheney's post
// at http://dave.cheney.net/2014/12/24/inspecting-errors.
//
// When passing errors out of lfs package functions, the return type should
// always be `error`. The wrappedError details are not exported. If an error is
// the kind of error a caller should need to investigate, an IsXError()
// function is provided that tells the caller if the error is of that type.
// There should only be a handfull of cases where a simple `error` is
// insufficient.
//
// The error behaviors can be nested when created. For example, the not
// implemented error can also be marked as a fatal error:
//
//	func LfsFunction() error {
//		err := functionCall()
//		if err != nil {
//			return newFatalError(newNotImplementedError(err))
//		}
//		return nil
//	}
//
// Then in the caller:
//
//	err := lfs.LfsFunction()
//	if lfs.IsNotImplementedError(err) {
//		log.Print("feature not implemented")
//	}
//	if lfs.IsFatalError(err) {
//		os.Exit(1)
//	}
//
// Wrapped errors contain a context, which is a map[string]string. These
// contexts can be accessed through the Error*Context functions. Calling these
// functions on a regular Go error will have no effect.
//
// Example:
//
//	err := lfs.SomeFunction()
//	lfs.ErrorSetContext(err, "foo", "bar")
//	lfs.ErrorGetContext(err, "foo") // => "bar"
//	lfs.ErrorDelContext(err, "foo")
//
// Wrapped errors also contain the stack from the point at which they are
// called. The stack is accessed via ErrorStack(). Calling ErrorStack() on a
// regular Go error will return an empty byte slice.

import (
	"errors"
	"runtime"
)

// IsFatalError indicates that the error is fatal and the process should exit
// immediately after handling the error.
func IsFatalError(err error) bool {
	if e, ok := err.(interface {
		Fatal() bool
	}); ok {
		return e.Fatal()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsFatalError(e.InnerError())
	}
	return false
}

// IsNotImplementedError indicates the client attempted to use a feature the
// server has not implemented (e.g. the batch endpoint).
func IsNotImplementedError(err error) bool {
	if e, ok := err.(interface {
		NotImplemented() bool
	}); ok {
		return e.NotImplemented()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsNotImplementedError(e.InnerError())
	}
	return false
}

// IsInvalidPointerError indicates an attempt to parse data that was not a
// valid pointer.
func IsInvalidPointerError(err error) bool {
	if e, ok := err.(interface {
		InvalidPointer() bool
	}); ok {
		return e.InvalidPointer()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsInvalidPointerError(e.InnerError())
	}
	return false
}

// IsInvalidRepoError indicates an operation was attempted from outside a git
// repository.
func IsInvalidRepoError(err error) bool {
	if e, ok := err.(interface {
		InvalidRepo() bool
	}); ok {
		return e.InvalidRepo()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsInvalidRepoError(e.InnerError())
	}
	return false
}

// IsSmudgeError indicates an error while smudging a files.
func IsSmudgeError(err error) bool {
	if e, ok := err.(interface {
		SmudgeError() bool
	}); ok {
		return e.SmudgeError()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsSmudgeError(e.InnerError())
	}
	return false
}

// IsCleanPointerError indicates an error while cleaning a file. Because of the
// structure of the code, this check also returns a *cleanPointerError because
// that's how a Pointer and []byte were passed up to the caller. This is not
// very clean and should be refactored. The returned *cleanPointerError MUST
// NOT be accessed if the bool value is false.
func IsCleanPointerError(err error) (*cleanPointerError, bool) {
	if e, ok := err.(interface {
		CleanPointerError() bool
	}); ok {
		cpe := err.(cleanPointerError)
		return &cpe, e.CleanPointerError()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsCleanPointerError(e.InnerError())
	}
	return nil, false
}

// IsNotAPointerError indicates the parsed data is not an LFS pointer.
func IsNotAPointerError(err error) bool {
	if e, ok := err.(interface {
		NotAPointerError() bool
	}); ok {
		return e.NotAPointerError()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsNotAPointerError(e.InnerError())
	}
	return false
}

// IsDownloadDeclinedError indicates that the smudge operation should not download.
// TODO: I don't really like using errors to control that flow, it should be refactored.
func IsDownloadDeclinedError(err error) bool {
	if e, ok := err.(interface {
		DownloadDeclinedError() bool
	}); ok {
		return e.DownloadDeclinedError()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsDownloadDeclinedError(e.InnerError())
	}
	return false
}

// Error wraps an error with an empty message.
func Error(err error) error {
	return Errorf(err, "")
}

// Errorf wraps an error with an additional formatted message.
func Errorf(err error, format string, args ...interface{}) error {
	if err == nil {
		return nil
	}

	e := newWrappedError(err)

	// ERRTODO this isn't right
	/*
		if len(format) > 0 {
			we := e.(wrappedError)
			we.Message = fmt.Sprintf(format, args...)
		}
	*/

	return e
}

// ErrorSetContext sets a value in the error's context. If the error has not
// been wrapped, it does nothing.
func ErrorSetContext(err error, key, value string) {
	if e, ok := err.(errorWrapper); ok {
		e.Set(key, value)
	}
}

// ErrorGetContext gets a value from the error's context. If the error has not
// been wrapped, it returns an empty string.
func ErrorGetContext(err error, key string) string {
	if e, ok := err.(errorWrapper); ok {
		return e.Get(key)
	}
	return ""
}

// ErrorDelContext removes a value from the error's context. If the error has
// not been wrapped, it does nothing.
func ErrorDelContext(err error, key string) {
	if e, ok := err.(errorWrapper); ok {
		e.Del(key)
	}
}

// ErrorStack returns the stack for an error if it is a wrappedError. If it is
// not a wrappedError it will return an empty byte slice.
func ErrorStack(err error) []byte {
	if e, ok := err.(errorWrapper); ok {
		return e.Stack()
	}
	return nil
}

// ErrorContext returns the context map for an error if it is a wrappedError.
// If it is not a wrappedError it will return an empty map.
func ErrorContext(err error) map[string]string {
	if e, ok := err.(errorWrapper); ok {
		return e.Context()
	}
	return nil
}

type errorWrapper interface {
	InnerError() error
	Error() string
	Set(string, string)
	Get(string) string
	Del(string)
	Context() map[string]string
	Stack() []byte
}

// wrappedError is the base error wrapper. It provides a Message string, a
// stack, and a context map around a regular Go error.
type wrappedError struct {
	Message string
	stack   []byte
	context map[string]string
	error
}

// newWrappedError creates a wrappedError. If the error has already been
// wrapped it is simply returned as is.
func newWrappedError(err error) errorWrapper {
	if e, ok := err.(errorWrapper); ok {
		return e
	}

	if err == nil {
		err = errors.New("LFS Error")
	}

	return wrappedError{
		Message: err.Error(),
		stack:   Stack(),
		context: make(map[string]string),
		error:   err,
	}
}

// InnerError returns the underlying error. This could be a Go error or another wrappedError.
func (e wrappedError) InnerError() error {
	return e.error
}

// Set sets the value for the key in the context.
func (e wrappedError) Set(key, val string) {
	e.context[key] = val
}

// Get gets the value for a key in the context.
func (e wrappedError) Get(key string) string {
	return e.context[key]
}

// Del removes a key from the context.
func (e wrappedError) Del(key string) {
	delete(e.context, key)
}

// Context returns the underlying context.
func (e wrappedError) Context() map[string]string {
	return e.context
}

// Stack returns the stack.
func (e wrappedError) Stack() []byte {
	return e.stack
}

// Definitions for IsFatalError()

type fatalError struct {
	errorWrapper
}

func (e fatalError) InnerError() error {
	return e.errorWrapper
}

func (e fatalError) Fatal() bool {
	return true
}

func newFatalError(err error) error {
	return fatalError{newWrappedError(err)}
}

// Definitions for IsNotImplementedError()

type notImplementedError struct {
	errorWrapper
}

func (e notImplementedError) InnerError() error {
	return e.errorWrapper
}

func (e notImplementedError) NotImplemented() bool {
	return true
}

func newNotImplementedError(err error) error {
	return notImplementedError{newWrappedError(err)}
}

// Definitions for IsInvalidPointerError()

type invalidPointerError struct {
	errorWrapper
}

func (e invalidPointerError) InnerError() error {
	return e.errorWrapper
}

func (e invalidPointerError) InvalidPointer() bool {
	return true
}

func newInvalidPointerError(err error) error {
	return invalidPointerError{newWrappedError(err)}
}

// Definitions for IsInvalidRepoError()

type invalidRepoError struct {
	errorWrapper
}

func (e invalidRepoError) InnerError() error {
	return e.errorWrapper
}

func (e invalidRepoError) InvalidRepo() bool {
	return true
}

func newInvalidRepoError(err error) error {
	if err == nil {
		err = errors.New("Not in a repository")
	}

	return invalidRepoError{newWrappedError(err)}
}

// Definitions for IsSmudgeError()

type smudgeError struct {
	errorWrapper
}

func (e smudgeError) InnerError() error {
	return e.errorWrapper
}

func (e smudgeError) SmudgeError() bool {
	return true
}

func newSmudgeError(err error, oid, filename string) error {
	e := smudgeError{newWrappedError(err)}
	ErrorSetContext(e, "OID", oid)
	ErrorSetContext(e, "FileName", filename)
	return e
}

// Definitions for IsCleanPointerError()

type cleanPointerError struct {
	pointer *Pointer
	bytes   []byte
	errorWrapper
}

func (e cleanPointerError) InnerError() error {
	return e.errorWrapper
}

func (e cleanPointerError) CleanPointerError() bool {
	return true
}

func (e cleanPointerError) Pointer() *Pointer {
	return e.pointer
}

func (e cleanPointerError) Bytes() []byte {
	return e.bytes
}

func newCleanPointerError(err error, pointer *Pointer, bytes []byte) error {
	return cleanPointerError{
		pointer,
		bytes,
		newWrappedError(err),
	}
}

// Definitions for IsNotAPointerError()

type notAPointerError struct {
	errorWrapper
}

func (e notAPointerError) InnerError() error {
	return e.errorWrapper
}

func (e notAPointerError) NotAPointerError() bool {
	return true
}

func newNotAPointerError(err error) error {
	if err == nil {
		err = errors.New("Not a valid Git LFS pointer file.")
	}
	return notAPointerError{newWrappedError(err)}
}

// Definitions for IsDownloadDeclinedError()

type downloadDeclinedError struct {
	errorWrapper
}

func (e downloadDeclinedError) InnerError() error {
	return e.errorWrapper
}

func (e downloadDeclinedError) DownloadDeclinedError() bool {
	return true
}

func newDownloadDeclinedError(err error) error {
	if err == nil {
		err = errors.New("File missing and download is not allowed")
	}
	return downloadDeclinedError{newWrappedError(err)}
}

// Stack returns a byte slice containing the runtime.Stack()
func Stack() []byte {
	stackBuf := make([]byte, 1024*1024)
	written := runtime.Stack(stackBuf, false)
	return stackBuf[:written]
}
<commit_msg>アーア アアアア アーアー<commit_after>package lfs

// The LFS error system provides a simple wrapper around Go errors and the
// ability to inspect errors. It is strongly influenced by Dave Cheney's post
// at http://dave.cheney.net/2014/12/24/inspecting-errors.
//
// When passing errors out of lfs package functions, the return type should
// always be `error`. The wrappedError details are not exported. If an error is
// the kind of error a caller should need to investigate, an IsXError()
// function is provided that tells the caller if the error is of that type.
// There should only be a handfull of cases where a simple `error` is
// insufficient.
//
// The error behaviors can be nested when created. For example, the not
// implemented error can also be marked as a fatal error:
//
//	func LfsFunction() error {
//		err := functionCall()
//		if err != nil {
//			return newFatalError(newNotImplementedError(err))
//		}
//		return nil
//	}
//
// Then in the caller:
//
//	err := lfs.LfsFunction()
//	if lfs.IsNotImplementedError(err) {
//		log.Print("feature not implemented")
//	}
//	if lfs.IsFatalError(err) {
//		os.Exit(1)
//	}
//
// Wrapped errors contain a context, which is a map[string]string. These
// contexts can be accessed through the Error*Context functions. Calling these
// functions on a regular Go error will have no effect.
//
// Example:
//
//	err := lfs.SomeFunction()
//	lfs.ErrorSetContext(err, "foo", "bar")
//	lfs.ErrorGetContext(err, "foo") // => "bar"
//	lfs.ErrorDelContext(err, "foo")
//
// Wrapped errors also contain the stack from the point at which they are
// called. The stack is accessed via ErrorStack(). Calling ErrorStack() on a
// regular Go error will return an empty byte slice.

import (
	"errors"
	"runtime"
)

// IsFatalError indicates that the error is fatal and the process should exit
// immediately after handling the error.
func IsFatalError(err error) bool {
	if e, ok := err.(interface {
		Fatal() bool
	}); ok {
		return e.Fatal()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsFatalError(e.InnerError())
	}
	return false
}

// IsNotImplementedError indicates the client attempted to use a feature the
// server has not implemented (e.g. the batch endpoint).
func IsNotImplementedError(err error) bool {
	if e, ok := err.(interface {
		NotImplemented() bool
	}); ok {
		return e.NotImplemented()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsNotImplementedError(e.InnerError())
	}
	return false
}

// IsInvalidPointerError indicates an attempt to parse data that was not a
// valid pointer.
func IsInvalidPointerError(err error) bool {
	if e, ok := err.(interface {
		InvalidPointer() bool
	}); ok {
		return e.InvalidPointer()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsInvalidPointerError(e.InnerError())
	}
	return false
}

// IsInvalidRepoError indicates an operation was attempted from outside a git
// repository.
func IsInvalidRepoError(err error) bool {
	if e, ok := err.(interface {
		InvalidRepo() bool
	}); ok {
		return e.InvalidRepo()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsInvalidRepoError(e.InnerError())
	}
	return false
}

// IsSmudgeError indicates an error while smudging a files.
func IsSmudgeError(err error) bool {
	if e, ok := err.(interface {
		SmudgeError() bool
	}); ok {
		return e.SmudgeError()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsSmudgeError(e.InnerError())
	}
	return false
}

// IsCleanPointerError indicates an error while cleaning a file. Because of the
// structure of the code, this check also returns a *CleanPointerError because
// that's how a Pointer and []byte were passed up to the caller. This is not
// very clean and should be refactored. The returned *CleanPointerError MUST
// NOT be accessed if the bool value is false.
func IsCleanPointerError(err error) (*CleanPointerError, bool) {
	if e, ok := err.(interface {
		CleanPointerError() bool
	}); ok {
		cpe := err.(CleanPointerError)
		return &cpe, e.CleanPointerError()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsCleanPointerError(e.InnerError())
	}
	return nil, false
}

// IsNotAPointerError indicates the parsed data is not an LFS pointer.
func IsNotAPointerError(err error) bool {
	if e, ok := err.(interface {
		NotAPointerError() bool
	}); ok {
		return e.NotAPointerError()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsNotAPointerError(e.InnerError())
	}
	return false
}

// IsDownloadDeclinedError indicates that the smudge operation should not download.
// TODO: I don't really like using errors to control that flow, it should be refactored.
func IsDownloadDeclinedError(err error) bool {
	if e, ok := err.(interface {
		DownloadDeclinedError() bool
	}); ok {
		return e.DownloadDeclinedError()
	}
	if e, ok := err.(errorWrapper); ok {
		return IsDownloadDeclinedError(e.InnerError())
	}
	return false
}

// Error wraps an error with an empty message.
func Error(err error) error {
	return Errorf(err, "")
}

// Errorf wraps an error with an additional formatted message.
func Errorf(err error, format string, args ...interface{}) error {
	if err == nil {
		return nil
	}

	e := newWrappedError(err)

	// ERRTODO this isn't right
	/*
		if len(format) > 0 {
			we := e.(wrappedError)
			we.Message = fmt.Sprintf(format, args...)
		}
	*/

	return e
}

// ErrorSetContext sets a value in the error's context. If the error has not
// been wrapped, it does nothing.
func ErrorSetContext(err error, key, value string) {
	if e, ok := err.(errorWrapper); ok {
		e.Set(key, value)
	}
}

// ErrorGetContext gets a value from the error's context. If the error has not
// been wrapped, it returns an empty string.
func ErrorGetContext(err error, key string) string {
	if e, ok := err.(errorWrapper); ok {
		return e.Get(key)
	}
	return ""
}

// ErrorDelContext removes a value from the error's context. If the error has
// not been wrapped, it does nothing.
func ErrorDelContext(err error, key string) {
	if e, ok := err.(errorWrapper); ok {
		e.Del(key)
	}
}

// ErrorStack returns the stack for an error if it is a wrappedError. If it is
// not a wrappedError it will return an empty byte slice.
func ErrorStack(err error) []byte {
	if e, ok := err.(errorWrapper); ok {
		return e.Stack()
	}
	return nil
}

// ErrorContext returns the context map for an error if it is a wrappedError.
// If it is not a wrappedError it will return an empty map.
func ErrorContext(err error) map[string]string {
	if e, ok := err.(errorWrapper); ok {
		return e.Context()
	}
	return nil
}

type errorWrapper interface {
	InnerError() error
	Error() string
	Set(string, string)
	Get(string) string
	Del(string)
	Context() map[string]string
	Stack() []byte
}

// wrappedError is the base error wrapper. It provides a Message string, a
// stack, and a context map around a regular Go error.
type wrappedError struct {
	Message string
	stack   []byte
	context map[string]string
	error
}

// newWrappedError creates a wrappedError. If the error has already been
// wrapped it is simply returned as is.
func newWrappedError(err error) errorWrapper {
	if e, ok := err.(errorWrapper); ok {
		return e
	}

	if err == nil {
		err = errors.New("LFS Error")
	}

	return wrappedError{
		Message: err.Error(),
		stack:   Stack(),
		context: make(map[string]string),
		error:   err,
	}
}

// InnerError returns the underlying error. This could be a Go error or another wrappedError.
func (e wrappedError) InnerError() error {
	return e.error
}

// Set sets the value for the key in the context.
func (e wrappedError) Set(key, val string) {
	e.context[key] = val
}

// Get gets the value for a key in the context.
func (e wrappedError) Get(key string) string {
	return e.context[key]
}

// Del removes a key from the context.
func (e wrappedError) Del(key string) {
	delete(e.context, key)
}

// Context returns the underlying context.
func (e wrappedError) Context() map[string]string {
	return e.context
}

// Stack returns the stack.
func (e wrappedError) Stack() []byte {
	return e.stack
}

// Definitions for IsFatalError()

type fatalError struct {
	errorWrapper
}

func (e fatalError) InnerError() error {
	return e.errorWrapper
}

func (e fatalError) Fatal() bool {
	return true
}

func newFatalError(err error) error {
	return fatalError{newWrappedError(err)}
}

// Definitions for IsNotImplementedError()

type notImplementedError struct {
	errorWrapper
}

func (e notImplementedError) InnerError() error {
	return e.errorWrapper
}

func (e notImplementedError) NotImplemented() bool {
	return true
}

func newNotImplementedError(err error) error {
	return notImplementedError{newWrappedError(err)}
}

// Definitions for IsInvalidPointerError()

type invalidPointerError struct {
	errorWrapper
}

func (e invalidPointerError) InnerError() error {
	return e.errorWrapper
}

func (e invalidPointerError) InvalidPointer() bool {
	return true
}

func newInvalidPointerError(err error) error {
	return invalidPointerError{newWrappedError(err)}
}

// Definitions for IsInvalidRepoError()

type invalidRepoError struct {
	errorWrapper
}

func (e invalidRepoError) InnerError() error {
	return e.errorWrapper
}

func (e invalidRepoError) InvalidRepo() bool {
	return true
}

func newInvalidRepoError(err error) error {
	if err == nil {
		err = errors.New("Not in a repository")
	}

	return invalidRepoError{newWrappedError(err)}
}

// Definitions for IsSmudgeError()

type smudgeError struct {
	errorWrapper
}

func (e smudgeError) InnerError() error {
	return e.errorWrapper
}

func (e smudgeError) SmudgeError() bool {
	return true
}

func newSmudgeError(err error, oid, filename string) error {
	e := smudgeError{newWrappedError(err)}
	ErrorSetContext(e, "OID", oid)
	ErrorSetContext(e, "FileName", filename)
	return e
}

// Definitions for IsCleanPointerError()

// CleanPointerError is used to pass the Pointer and contents back up to the
// caller.
// TODO: This should be refactored to avoid using error types in this way.
type CleanPointerError struct {
	pointer *Pointer
	bytes   []byte
	errorWrapper
}

func (e CleanPointerError) InnerError() error {
	return e.errorWrapper
}

func (e CleanPointerError) CleanPointerError() bool {
	return true
}

func (e CleanPointerError) Pointer() *Pointer {
	return e.pointer
}

func (e CleanPointerError) Bytes() []byte {
	return e.bytes
}

func newCleanPointerError(err error, pointer *Pointer, bytes []byte) error {
	return CleanPointerError{
		pointer,
		bytes,
		newWrappedError(err),
	}
}

// Definitions for IsNotAPointerError()

type notAPointerError struct {
	errorWrapper
}

func (e notAPointerError) InnerError() error {
	return e.errorWrapper
}

func (e notAPointerError) NotAPointerError() bool {
	return true
}

func newNotAPointerError(err error) error {
	if err == nil {
		err = errors.New("Not a valid Git LFS pointer file.")
	}
	return notAPointerError{newWrappedError(err)}
}

// Definitions for IsDownloadDeclinedError()

type downloadDeclinedError struct {
	errorWrapper
}

func (e downloadDeclinedError) InnerError() error {
	return e.errorWrapper
}

func (e downloadDeclinedError) DownloadDeclinedError() bool {
	return true
}

func newDownloadDeclinedError(err error) error {
	if err == nil {
		err = errors.New("File missing and download is not allowed")
	}
	return downloadDeclinedError{newWrappedError(err)}
}

// Stack returns a byte slice containing the runtime.Stack()
func Stack() []byte {
	stackBuf := make([]byte, 1024*1024)
	written := runtime.Stack(stackBuf, false)
	return stackBuf[:written]
}
<|endoftext|>


<commit_before>/*
 * Copyright 2014 Branimir Karadzic. All rights reserved.
 * https://github.com/bkaradzic/go-emailvalidator/blob/master/LICENSE
 */

package emailvalidator

import (
	"regexp"
	"strings"
)

var (
	// List from:
	// github.com/abgoyal/disposable-email-domains
	disposableEmailDomains = []string{
		"e4ward.com",
		"mailexpire.com",
		"0815.ru",
		"0clickemail.com",
		"0wnd.net",
		"0wnd.org",
		"10minutemail.com",
		"20minutemail.com",
		"2prong.com",
		"9ox.net",
		"a-bc.net",
		"amiri.net",
		"amiriindustries.com",
		"anonymbox.com",
		"antichef.com",
		"antichef.net",
		"antispam.de",
		"baxomale.ht.cx",
		"beefmilk.com",
		"binkmail.com",
		"bio-muesli.net",
		"blogmyway.org",
		"bobmail.info",
		"bofthew.com",
		"brefmail.com",
		"bsnow.net",
		"bspamfree.org",
		"bugmenot.com",
		"casualdx.com",
		"centermail.com",
		"centermail.net",
		"chogmail.com",
		"choicemail1.com",
		"cool.fr.nf",
		"correo.blogos.net",
		"courriel.fr.nf",
		"courrieltemporaire.com",
		"cust.in",
		"dacoolest.com",
		"deadaddress.com",
		"deadspam.com",
		"despam.it",
		"despammed.com",
		"devnullmail.com",
		"dfgh.net",
		"digitalsanctuary.com",
		"disposableaddress.com",
		"disposemail.com",
		"dispostable.com",
		"dm.w3internet.co.uk",
		"dodgeit.com",
		"dodgit.com",
		"dontreg.com",
		"dontsendmespam.de",
		"dumpandjunk.com",
		"dumpyemail.com",
		"e4ward.com",
		"email60.com",
		"emailias.com",
		"emailmiser.com",
		"emailtemporario.com.br",
		"emailthe.net",
		"emailwarden.com",
		"etranquil.com",
		"explodemail.com",
		"fakeinbox.com",
		"fakeinformation.com",
		"fakemailz.com",
		"filzmail.com",
		"footard.com",
		"forgetmail.com",
		"garliclife.com",
		"getonemail.com",
		"gishpuppy.com",
		"gsrv.co.uk",
		"guerrillamail.biz",
		"guerrillamail.com",
		"guerrillamail.de",
		"guerrillamail.net",
		"guerrillamail.org",
		"guerrillamailblock.com",
		"hidemail.de",
		"hotpop.com",
		"ichimail.com",
		"iheartspam.org",
		"imails.info",
		"inboxclean.com",
		"inboxclean.org",
		"irish2me.com",
		"jetable.fr.nf",
		"jetable.org",
		"jnxjn.com",
		"junk1e.com",
		"kasmail.com",
		"kaspop.com",
		"killmail.com",
		"killmail.net",
		"klassmaster.com",
		"klzlk.com",
		"kulturbetrieb.info",
		"kurzepost.de",
		"lifebyfood.com",
		"link2mail.net",
		"lookugly.com",
		"lortemail.dk",
		"lr78.com",
		"mail.by",
		"mail333.com",
		"mail4trash.com",
		"mailbidon.com",
		"mailblocks.com",
		"mailcatch.com",
		"mailexpire.com",
		"mailfreeonline.com",
		"mailin8r.com",
		"mailinater.com",
		"mailinator.com",
		"mailinator.net",
		"mailinator2.com",
		"mailincubator.com",
		"mailme.lv",
		"mailmetrash.com",
		"mailmoat.com",
		"mailnator.com",
		"mailnesia.com",
		"mailnull.com",
		"mailquack.com",
		"mailshell.com",
		"mailsiphon.com",
		"mailslapping.com",
		"mailzilla.com",
		"mbx.cc",
		"mega.zik.dj",
		"meinspamschutz.de",
		"meltmail.com",
		"messagebeamer.de",
		"mintemail.com",
		"mmmmail.com",
		"moncourrier.fr.nf",
		"monemail.fr.nf",
		"monmail.fr.nf",
		"mt2009.com",
		"mycleaninbox.net",
		"mytrashmail.com",
		"neomailbox.com",
		"nepwk.com",
		"nervmich.net",
		"nervtmich.net",
		"netmails.net",
		"neverbox.com",
		"no-spam.hu",
		"no-spam.ws",
		"noclickemail.com",
		"nomail.xl.cx",
		"nomail2me.com",
		"nospam.ze.tc",
		"nospam4.us",
		"nospamfor.us",
		"nowmymail.com",
		"nurfuerspam.de",
		"objectmail.com",
		"oneoffemail.com",
		"oneoffmail.com",
		"onewaymail.com",
		"ordinaryamerican.net",
		"otherinbox.com",
		"owlpic.com",
		"pancakemail.com",
		"poofy.org",
		"pookmail.com",
		"privacy.net",
		"proxymail.eu",
		"punkass.com",
		"putthisinyourspamdatabase.com",
		"rcpt.at",
		"recode.me",
		"recursor.net",
		"rtrtr.com",
		"safersignup.de",
		"safetymail.info",
		"sharklasers.com",
		"shiftmail.com",
		"shitmail.me",
		"shortmail.net",
		"sibmail.com",
		"skeefmail.com",
		"slaskpost.se",
		"smellfear.com",
		"sneakemail.com",
		"sogetthis.com",
		"soodonims.com",
		"spamavert.com",
		"spambob.net",
		"spambob.org",
		"spambog.ru",
		"spambox.info",
		"spambox.us",
		"spamcannon.com",
		"spamcannon.net",
		"spamcon.org",
		"spamcorptastic.com",
		"spamcowboy.com",
		"spamcowboy.net",
		"spamcowboy.org",
		"spamday.com",
		"spamex.com",
		"spamfree24.com",
		"spamfree24.org",
		"spamgourmet.com",
		"spamgourmet.net",
		"spamgourmet.org",
		"spamherelots.com",
		"spamhereplease.com",
		"spamify.com",
		"spaml.com",
		"spaml.de",
		"spammotel.com",
		"spamobox.com",
		"spamslicer.com",
		"spamspot.com",
		"spamthis.co.uk",
		"spamtrail.com",
		"speed.1s.fr",
		"spoofmail.de",
		"suremail.info",
		"tempemail.net",
		"tempinbox.co.uk",
		"tempinbox.com",
		"tempomail.fr",
		"temporaryemail.net",
		"temporaryforwarding.com",
		"temporaryinbox.com",
		"thankyou2010.com",
		"thisisnotmyrealemail.com",
		"tmailinator.com",
		"tradermail.info",
		"trash-mail.at",
		"trash-mail.com",
		"trash-mail.de",
		"trash2009.com",
		"trashdevil.com",
		"trashemail.de",
		"trashmail.at",
		"trashmail.de",
		"trashmail.me",
		"trashmail.net",
		"trashmailer.com",
		"trashymail.com",
		"twinmail.de",
		"tyldd.com",
		"venompen.com",
		"walala.org",
		"wegwerfadresse.de",
		"wegwerfmail.de",
		"wegwerfmail.net",
		"wegwerfmail.org",
		"wh4f.org",
		"whopy.com",
		"whyspam.me",
		"winemaven.info",
		"wronghead.com",
		"wuzupmail.net",
		"wwwnew.eu",
		"xagloo.com",
		"xemaps.com",
		"xents.com",
		"xmaily.com",
		"xoxy.net",
		"yep.it",
		"yogamaven.com",
		"yopmail.com",
		"yopmail.fr",
		"yopmail.net",
		"yuurok.com",
		"zippymail.info",
		"objectmail.net",
	}
)

func IsDisposable(email string) bool {

	for _, suffix := range disposableEmailDomains {
		if strings.HasSuffix(email, suffix) {
			return true
		}
	}

	return false
}

// Reference:
// http://www.linuxjournal.com/article/9585?page=0,0
// http://www.regular-expressions.info/email.html
// http://fightingforalostcause.net/misc/2006/compare-email-regex.php

var (
	dotsCheck   = regexp.MustCompile("\\.\\.")
	domainCheck = regexp.MustCompile("^[A-Za-z0-9\\-\\.]+$")
	localCheck  = regexp.MustCompile("^(\\\\.|[A-Za-z0-9!#%&`_=\\/$'*+-?^{}|~.])+$")
	quotedCheck = regexp.MustCompile("^\"(\\\\\"|[^\"])+\"$")
)

func IsValid(email string) bool {

	at := strings.LastIndex(email, "@")

	if at == -1 {
		return false
	}

	local := email[0:at]
	localLen := len(local)
	domain := email[at+1:]
	domainLen := len(domain)

	if localLen < 1 || localLen > 64 {
		return false
	}

	if domainLen < 1 || domainLen > 255 {
		return false
	}

	if local[0] == '.' || local[localLen-1] == '.' {
		return false
	}

	if domain[0] == '.' || domain[domainLen-1] == '.' {
		return false
	}

	if dotsCheck.MatchString(local) || dotsCheck.MatchString(domain) {
		return false
	}

	if !domainCheck.MatchString(domain) {
		return false
	}

	local = strings.Replace(local, "\\\\", "", -1)
	if !localCheck.MatchString(local) && !quotedCheck.MatchString(local) {
		return false
	}

	return true
}
<commit_msg>Updated disposable email list.<commit_after>/*
 * Copyright 2014 Branimir Karadzic. All rights reserved.
 * https://github.com/bkaradzic/go-emailvalidator/blob/master/LICENSE
 */

package emailvalidator

import (
	"regexp"
	"strings"
)

var (
	// List from:
	// https://github.com/jamesaustin/disposable-email-domains
	// https://github.com/martenson/disposable-email-domains
	disposableEmailDomains = []string{
		"0815.ru",
		"0clickemail.com",
		"0wnd.net",
		"0wnd.org",
		"10minutemail.com",
		"123-m.com",
		"126.com",
		"139.com",
		"163.com",
		"20minutemail.com",
		"21cn.com",
		"2prong.com",
		"30minutemail.com",
		"33mail.com",
		"4warding.com",
		"6paq.com",
		"7tags.com",
		"99experts.com",
		"9ox.net",
		"a-bc.net",
		"afrobacon.com",
		"ag.us.to",
		"agedmail.com",
		"alivance.com",
		"amilegit.com",
		"amiri.net",
		"amiriindustries.com",
		"anappthat.com",
		"anonymbox.com",
		"antichef.com",
		"antichef.net",
		"antispam.de",
		"armyspy.com",
		"azmeil.tk",
		"baxomale.ht.cx",
		"beefmilk.com",
		"bigprofessor.so",
		"bigstring.com",
		"binkmail.com",
		"bio-muesli.net",
		"blogmyway.org",
		"bobmail.info",
		"bofthew.com",
		"bootybay.de",
		"boun.cr",
		"bouncr.com",
		"brefmail.com",
		"brennendesreich.de",
		"bsnow.net",
		"buffemail.com",
		"bugmenot.com",
		"bund.us",
		"burnthespam.info",
		"c2.hu",
		"casualdx.com",
		"centermail.com",
		"centermail.net",
		"chammy.info",
		"cheatmail.de",
		"chogmail.com",
		"choicemail1.com",
		"clixser.com",
		"cmail.net",
		"consumerriot.com",
		"cool.fr.nf",
		"courriel.fr.nf",
		"courrieltemporaire.com",
		"crapmail.org",
		"crazymailing.com",
		"cubiclink.com",
		"cust.in",
		"cuvox.de",
		"dacoolest.com",
		"dandikmail.com",
		"dayrep.com",
		"dcemail.com",
		"deadaddress.com",
		"deadspam.com",
		"dealja.com",
		"despam.it",
		"despammed.com",
		"devnullmail.com",
		"dfgh.net",
		"digitalsanctuary.com",
		"dingbone.com",
		"discardmail.com",
		"discardmail.de",
		"disposableaddress.com",
		"disposableemailaddresses.com",
		"disposableinbox.com",
		"dispose.it",
		"dispostable.com",
		"dodgeit.com",
		"dodgit.com",
		"doiea.com",
		"domozmail.com",
		"donemail.ru",
		"dontreg.com",
		"dontsendmespam.de",
		"dotmsg.com",
		"dump-email.info",
		"dumpandjunk.com",
		"duskmail.com",
		"e-mail.com",
		"e-mail.org",
		"e4ward.com",
		"easytrashmail.com",
		"einrot.com",
		"email60.com",
		"emailgo.de",
		"emailias.com",
		"emaillime.com",
		"emailmiser.com",
		"emailsensei.com",
		"emailtemporario.com.br",
		"emailthe.net",
		"emailtmp.com",
		"emailwarden.com",
		"emailx.at.hm",
		"emailxfer.com",
		"emeil.in",
		"emeil.ir",
		"emz.net",
		"explodemail.com",
		"explodemail.com ",
		"eyepaste.com",
		"fakeinbox.com",
		"fakeinformation.com",
		"fakemailz.com",
		"fammix.com",
		"fansworldwide.de",
		"fantasymail.de",
		"fatflap.com ",
		"fightallspam.com",
		"filzmail.com",
		"fixmail.tk",
		"fleckens.hu",
		"flyspam.com",
		"footard.com",
		"frapmail.com",
		"friendlymail.co.uk",
		"fuckingduh.com",
		"fudgerub.com",
		"garliclife.com",
		"get2mail.fr",
		"getairmail.com",
		"getmails.eu",
		"getonemail.com",
		"girlsundertheinfluence.com",
		"gishpuppy.com",
		"goemailgo.com",
		"gotmail.net",
		"gotmail.org",
		"gotti.otherinbox.com",
		"grandmasmail.com",
		"great-host.in",
		"greensloth.com",
		"grr.la",
		"gsrv.co.uk",
		"guerillamail.biz",
		"guerillamail.com",
		"guerrillamail.biz",
		"guerrillamail.com",
		"guerrillamail.de",
		"guerrillamail.net",
		"guerrillamail.org",
		"guerrillamailblock.com",
		"gustr.com",
		"haltospam.com",
		"hatespam.org",
		"herp.in",
		"hidemail.de",
		"hidzz.com",
		"hochsitze.com",
		"hulapla.de",
		"hushmail.com",
		"ieh-mail.de",
		"imails.info",
		"imgof.com",
		"imstations.com",
		"inbax.tk",
		"inbox.si",
		"inboxalias.com",
		"inboxclean.com",
		"inboxclean.org",
		"instant-mail.de",
		"ipoo.org",
		"irish2me.com",
		"iwi.net",
		"jetable.com",
		"jetable.fr.nf",
		"jetable.net",
		"jetable.org",
		"jnxjn.com",
		"jourrapide.com",
		"jsrsolutions.com",
		"kasmail.com",
		"kaspop.com",
		"killmail.com",
		"killmail.net",
		"klassmaster.com",
		"klzlk.com",
		"koszmail.pl",
		"kurzepost.de",
		"lackmail.net",
		"lavabit.com",
		"lhsdv.com",
		"lifebyfood.com",
		"link2mail.net",
		"lol.ovpn.to",
		"lookugly.com",
		"lortemail.dk",
		"lr78.com",
		"lroid.com",
		"mail-filter.com",
		"mail-temporaire.fr",
		"mail.by",
		"mail.mezimages.net",
		"mail333.com",
		"mailbidon.com",
		"mailblocks.com",
		"mailbucket.org",
		"mailcatch.com",
		"maildrop.cc",
		"mailexpire.com",
		"mailfa.tk",
		"mailfreeonline.com",
		"mailguard.me",
		"mailimate.com",
		"mailin8r.com",
		"mailinater.com",
		"mailinator.com",
		"mailinator.net",
		"mailinator.org",
		"mailinator2.com",
		"mailincubator.com",
		"mailismagic.com",
		"mailme.lv",
		"mailmetrash.com",
		"mailmoat.com",
		"mailnesia.com",
		"mailnull.com",
		"mailscrap.com",
		"mailshell.com",
		"mailsiphon.com",
		"mailslapping.com",
		"mailtemp.info",
		"mailtothis.com",
		"mailzilla.com",
		"makemetheking.com",
		"manybrain.com",
		"mbx.cc",
		"mega.zik.dj",
		"meinspamschutz.de",
		"meltmail.com",
		"messagebeamer.de",
		"mezimages.net",
		"mintemail.com",
		"mobileninja.co.uk",
		"moncourrier.fr.nf",
		"monemail.fr.nf",
		"monmail.fr.nf",
		"monumentmail.com",
		"mt2009.com",
		"mt2014.com",
		"mycleaninbox.net",
		"mymail-in.net",
		"mypacks.net",
		"mypartyclip.de",
		"myphantomemail.com",
		"mytempemail.com",
		"mytrashmail.com",
		"neomailbox.com",
		"nepwk.com",
		"nervmich.net",
		"nervtmich.net",
		"netmails.com",
		"netmails.net",
		"neverbox.com",
		"nice-4u.com",
		"no-spam.ws",
		"nogmailspam.info",
		"nomail.xl.cx",
		"nomail2me.com",
		"nomorespamemails.com",
		"nospam.ze.tc",
		"nospam4.us",
		"notmailinator.com",
		"notsharingmy.info",
		"nowhere.org",
		"nowmymail.com",
		"nurfuerspam.de",
		"nus.edu.sg",
		"objectmail.com",
		"obobbo.com",
		"oneoffemail.com",
		"onewaymail.com",
		"online.ms",
		"oopi.org",
		"opayq.com",
		"ordinaryamerican.net",
		"otherinbox.com",
		"ovpn.to",
		"owlpic.com",
		"pancakemail.com",
		"paplease.com",
		"pcusers.otherinbox.com",
		"pepbot.com",
		"pjjkp.com",
		"plexolan.de",
		"poczta.onet.pl",
		"politikerclub.de",
		"poofy.org",
		"pookmail.com",
		"privacy.net",
		"proxymail.eu",
		"prtnx.com",
		"putthisinyourspamdatabase.com",
		"pwrby.com",
		"qq.com",
		"quickinbox.com",
		"rcpt.at",
		"reallymymail.com",
		"recode.me",
		"recursor.net",
		"rhyta.com",
		"royal.net",
		"rtrtr.com",
		"s0ny.net",
		"safe-mail.net",
		"safersignup.de",
		"safetymail.info",
		"safetypost.de",
		"schafmail.de ",
		"sharklasers.com",
		"shieldedmail.com",
		"shiftmail.com",
		"shitmail.me",
		"shitmail.org",
		"shitware.nl",
		"shortmail.net",
		"sibmail.com",
		"sinnlos-mail.de",
		"slaskpost.se",
		"slushmail.com",
		"smashmail.de",
		"smellfear.com",
		"snakemail.com",
		"sneakemail.com",
		"snkmail.com",
		"sofimail.com",
		"sogetthis.com",
		"sohu.com",
		"soodonims.com",
		"spam4.me",
		"spambob.net",
		"spambog.com",
		"spambog.de",
		"spambog.ru",
		"spambox.us",
		"spamcannon.com",
		"spamcannon.net",
		"spamcon.org",
		"spamcorptastic.com",
		"spamcowboy.com",
		"spamcowboy.net",
		"spamcowboy.org",
		"spamday.com",
		"spamex.com",
		"spamfree.eu",
		"spamfree24.com",
		"spamfree24.de",
		"spamfree24.org",
		"spamgoes.in",
		"spamgourmet.com",
		"spamgourmet.net",
		"spamgourmet.org",
		"SpamHerePlease.com",
		"spamhole.com",
		"spamify.com",
		"spaml.de",
		"spammotel.com",
		"spamobox.com",
		"spamsalad.in",
		"spamslicer.com",
		"spamspot.com",
		"spamthis.co.uk",
		"spamtroll.net",
		"speed.1s.fr",
		"spoofmail.de",
		"startkeys.com",
		"stuffmail.de",
		"supergreatmail.com",
		"supermailer.jp",
		"superrito.com",
		"superstachel.de",
		"suremail.info",
		"talkinator.com",
		"teewars.org",
		"teleworm.com",
		"teleworm.us",
		"temp.emeraldwebmail.com",
		"tempe-mail.com",
		"tempemail.co.za",
		"tempemail.com",
		"TempEMail.net",
		"tempinbox.co.uk",
		"tempinbox.com",
		"tempmaildemo.com",
		"tempomail.fr",
		"temporaryforwarding.com",
		"temporaryinbox.com",
		"tempthe.net",
		"thankyou2010.com",
		"thecloudindex.com",
		"thisisnotmyrealemail.com",
		"throwawayemailaddress.com",
		"tilien.com",
		"tittbit.in",
		"tmailinator.com",
		"toiea.com",
		"tradermail.info",
		"trash-mail.at",
		"trash-mail.com",
		"trash-mail.de",
		"trash2009.com",
		"trashdevil.com",
		"trashemail.de",
		"trashmail.at",
		"trashmail.com",
		"trashmail.de",
		"trashmail.me",
		"trashmail.net",
		"trashmail.org",
		"trashmailer.com",
		"trashymail.com",
		"trbvm.com",
		"trillianpro.com",
		"tryalert.com",
		"twinmail.de",
		"tyldd.com",
		"uggsrock.com",
		"umail.net",
		"upliftnow.com",
		"uroid.com",
		"venompen.com",
		"vubby.com",
		"walkmail.net",
		"webemail.me",
		"weg-werf-email.de",
		"wegwerf-emails.de ",
		"wegwerfadresse.de",
		"wegwerfemail.de",
		"wegwerfmail.de",
		"wegwerfmail.info",
		"wegwerfmail.net",
		"wegwerfmail.org",
		"wh4f.org",
		"whyspam.me",
		"willselfdestruct.com",
		"winemaven.info",
		"wronghead.com",
		"wwwnew.eu",
		"xagloo.com",
		"xemaps.com",
		"xents.com",
		"xmaily.com",
		"xoxy.net",
		"yahoo.com.ph",
		"yahoo.com.vn",
		"yeah.net",
		"yep.it",
		"yogamaven.com",
		"yopmail.com",
		"yopmail.fr",
		"yopmail.net",
		"yuurok.com",
		"za.com",
		"zehnminutenmail.de",
		"zetmail.com",
		"zippymail.info",
		"zoemail.net",
	}
)

func IsDisposable(email string) bool {

	for _, suffix := range disposableEmailDomains {
		if strings.HasSuffix(email, suffix) {
			return true
		}
	}

	return false
}

// Reference:
// http://www.linuxjournal.com/article/9585?page=0,0
// http://www.regular-expressions.info/email.html
// http://fightingforalostcause.net/misc/2006/compare-email-regex.php

var (
	dotsCheck   = regexp.MustCompile("\\.\\.")
	domainCheck = regexp.MustCompile("^[A-Za-z0-9\\-\\.]+$")
	localCheck  = regexp.MustCompile("^(\\\\.|[A-Za-z0-9!#%&`_=\\/$'*+-?^{}|~.])+$")
	quotedCheck = regexp.MustCompile("^\"(\\\\\"|[^\"])+\"$")
)

func IsValid(email string) bool {

	at := strings.LastIndex(email, "@")

	if at == -1 {
		return false
	}

	local := email[0:at]
	localLen := len(local)
	domain := email[at+1:]
	domainLen := len(domain)

	if localLen < 1 || localLen > 64 {
		return false
	}

	if domainLen < 1 || domainLen > 255 {
		return false
	}

	if local[0] == '.' || local[localLen-1] == '.' {
		return false
	}

	if domain[0] == '.' || domain[domainLen-1] == '.' {
		return false
	}

	if dotsCheck.MatchString(local) || dotsCheck.MatchString(domain) {
		return false
	}

	if !domainCheck.MatchString(domain) {
		return false
	}

	local = strings.Replace(local, "\\\\", "", -1)
	if !localCheck.MatchString(local) && !quotedCheck.MatchString(local) {
		return false
	}

	return true
}
<|endoftext|>


<commit_before>// +build e2e

/*
Copyright 2019 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package e2e

import (
	"errors"
	"fmt"
	"path/filepath"
	"runtime"
	"testing"

	mf "github.com/jcrossley3/manifestival"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"knative.dev/pkg/test/logstream"
	"knative.dev/serving-operator/pkg/apis/serving/v1alpha1"
	"knative.dev/serving-operator/test"
	"knative.dev/serving-operator/test/resources"
)

// TestKnativeServingDeployment verifies the KnativeServing creation, deployment recreation, and KnativeServing deletion.
func TestKnativeServingDeployment(t *testing.T) {
	cancel := logstream.Start(t)
	defer cancel()
	clients := Setup(t)

	names := test.ResourceNames{
		KnativeServing: test.ServingOperatorName,
		Namespace:      test.ServingOperatorNamespace,
	}

	test.CleanupOnInterrupt(func() { test.TearDown(clients, names) })
	defer test.TearDown(clients, names)

	// Create a KnativeServing
	if _, err := resources.CreateKnativeServing(clients.KnativeServing(), names); err != nil {
		t.Fatalf("KnativeService %q failed to create: %v", names.KnativeServing, err)
	}

	// Test if KnativeServing can reach the READY status
	t.Run("create", func(t *testing.T) {
		knativeServingVerify(t, clients, names)
	})

	t.Run("configure", func(t *testing.T) {
		knativeServingVerify(t, clients, names)
		knativeServingConfigure(t, clients, names)
	})

	// Delete the deployments one by one to see if they will be recreated.
	t.Run("restore", func(t *testing.T) {
		knativeServingVerify(t, clients, names)
		deploymentRecreation(t, clients, names)
	})

	// Delete the KnativeServing to see if all the deployments will be removed as well
	t.Run("delete", func(t *testing.T) {
		knativeServingVerify(t, clients, names)
		knativeServingDeletion(t, clients, names)
		verifyClusterResourceDeletion(t, clients)
	})
}

// knativeServingVerify verifies if the KnativeServing can reach the READY status.
func knativeServingVerify(t *testing.T, clients *test.Clients, names test.ResourceNames) {
	if _, err := resources.WaitForKnativeServingState(clients.KnativeServing(), names.KnativeServing,
		resources.IsKnativeServingReady); err != nil {
		t.Fatalf("KnativeService %q failed to get to the READY status: %v", names.KnativeServing, err)
	}

}

// knativeServingConfigure verifies that KnativeServing config is set properly
func knativeServingConfigure(t *testing.T, clients *test.Clients, names test.ResourceNames) {
	// We'll arbitrarily choose the logging config
	configKey := "logging"
	configMapName := fmt.Sprintf("%s/config-%s", names.Namespace, configKey)
	// Get the existing KS without any spec
	ks, err := clients.KnativeServing().Get(names.KnativeServing, metav1.GetOptions{})
	// Add config to its spec
	ks.Spec = v1alpha1.KnativeServingSpec{
		Config: map[string]map[string]string{
			configKey: {
				"loglevel.controller": "debug",
			},
		},
	}
	// Update it
	if ks, err = clients.KnativeServing().Update(ks); err != nil {
		t.Fatalf("KnativeServing %q failed to update: %v", names.KnativeServing, err)
	}
	// Verifty the relevant configmap has been updated
	err = resources.WaitForConfigMap(configMapName, clients.KubeClient.Kube, func(m map[string]string) bool {
		return m["loglevel.controller"] == "debug"
	})
	if err != nil {
		t.Fatal("The operator failed to update the configmap")
	}
	// Now remove the config from the spec and update
	ks.Spec = v1alpha1.KnativeServingSpec{}
	if ks, err = clients.KnativeServing().Update(ks); err != nil {
		t.Fatalf("KnativeServing %q failed to update: %v", names.KnativeServing, err)
	}
	// And verify the configmap entry is gone
	err = resources.WaitForConfigMap(configMapName, clients.KubeClient.Kube, func(m map[string]string) bool {
		_, exists := m["loglevel.controller"]
		return !exists
	})
	if err != nil {
		t.Fatal("The operator failed to revert the configmap")
	}
}

// deploymentRecreation verify whether all the deployments for knative serving are able to recreate, when they are deleted.
func deploymentRecreation(t *testing.T, clients *test.Clients, names test.ResourceNames) {
	dpList, err := clients.KubeClient.Kube.AppsV1().Deployments(names.Namespace).List(metav1.ListOptions{})
	if err != nil {
		t.Fatalf("Failed to get any deployment under the namespace %q: %v",
			test.ServingOperatorNamespace, err)
	}
	if len(dpList.Items) == 0 {
		t.Fatalf("No deployment under the namespace %q was found",
			test.ServingOperatorNamespace)
	}
	// Delete the first deployment and verify the operator recreates it
	deployment := dpList.Items[0]
	if err := clients.KubeClient.Kube.AppsV1().Deployments(deployment.Namespace).Delete(deployment.Name,
		&metav1.DeleteOptions{}); err != nil {
		t.Fatalf("Failed to delete deployment %s/%s: %v", deployment.Namespace, deployment.Name, err)
	}

	waitErr := wait.PollImmediate(resources.Interval, resources.Timeout, func() (bool, error) {
		dep, err := clients.KubeClient.Kube.AppsV1().Deployments(deployment.Namespace).Get(deployment.Name, metav1.GetOptions{})
		if err != nil {
			// If the deployment is not found, we continue to wait for the availability.
			if apierrs.IsNotFound(err) {
				return false, nil
			}
			return false, err
		}
		return resources.IsDeploymentAvailable(dep)
	})

	if waitErr != nil {
		t.Fatalf("The deployment %s/%s failed to reach the desired state: %v", deployment.Namespace, deployment.Name, err)
	}

	if _, err := resources.WaitForKnativeServingState(clients.KnativeServing(), test.ServingOperatorName,
		resources.IsKnativeServingReady); err != nil {
		t.Fatalf("KnativeService %q failed to reach the desired state: %v", test.ServingOperatorName, err)
	}
	t.Logf("The deployment %s/%s reached the desired state.", deployment.Namespace, deployment.Name)
}

// knativeServingDeletion deletes tha KnativeServing to see if all the deployments will be removed.
func knativeServingDeletion(t *testing.T, clients *test.Clients, names test.ResourceNames) {
	if err := clients.KnativeServing().Delete(names.KnativeServing, &metav1.DeleteOptions{}); err != nil {
		t.Fatalf("KnativeService %q failed to delete: %v", names.KnativeServing, err)
	}

	dpList, err := clients.KubeClient.Kube.AppsV1().Deployments(names.Namespace).List(metav1.ListOptions{})
	if err != nil {
		t.Fatalf("Error getting any deployment under the namespace %q: %v", names.Namespace, err)
	}

	for _, deployment := range dpList.Items {
		waitErr := wait.PollImmediate(resources.Interval, resources.Timeout, func() (bool, error) {
			if _, err := clients.KubeClient.Kube.AppsV1().Deployments(deployment.Namespace).Get(deployment.Name, metav1.GetOptions{}); err != nil {
				if apierrs.IsNotFound(err) {
					return true, nil
				}
				return false, err
			}
			return false, nil
		})

		if waitErr != nil {
			t.Fatalf("The deployment %s/%s failed to be deleted: %v", deployment.Namespace, deployment.Name, waitErr)
		}
		t.Logf("The deployment %s/%s has been deleted.", deployment.Namespace, deployment.Name)
	}
}

func verifyClusterResourceDeletion(t *testing.T, clients *test.Clients) {
	_, b, _, _ := runtime.Caller(0)
	m, err := mf.NewManifest(filepath.Join((filepath.Dir(b)+"/.."), "config/"), false, clients.Config)
	if err != nil {
		t.Fatal("Failed to load manifest", err)
	}
	if err := verifyNoKnativeServings(clients); err != nil {
		t.Fatal(err)
	}
	for _, u := range m.Resources {
		if u.GetNamespace() == "" && u.GetKind() != "Namespace" {
			waitErr := wait.PollImmediate(resources.Interval, resources.Timeout, func() (bool, error) {
				gvrs, _ := meta.UnsafeGuessKindToResource(u.GroupVersionKind())
				if _, err := clients.Dynamic.Resource(gvrs).Get(u.GetName(), metav1.GetOptions{}); apierrs.IsNotFound(err) {
					return true, nil
				} else {
					return false, err
				}
			})

			if waitErr != nil {
				t.Fatalf("The %s %s failed to be deleted: %v", u.GetKind(), u.GetName(), waitErr)
			}
			t.Logf("The %s %s has been deleted.", u.GetKind(), u.GetName())
		}
	}
}

func verifyNoKnativeServings(clients *test.Clients) error {
	servings, err := clients.KnativeServingAll().List(metav1.ListOptions{})
	if err != nil {
		return err
	}
	if len(servings.Items) > 0 {
		return errors.New("Unable to verify cluster-scoped resources are deleted if any KnativeServing exists")
	}
	return nil
}
<commit_msg>Additional tests for resource deletion and update (#178)<commit_after>// +build e2e

/*
Copyright 2019 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package e2e

import (
	"errors"
	"fmt"
	"path/filepath"
	"runtime"
	"testing"

	mf "github.com/jcrossley3/manifestival"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"knative.dev/pkg/test/logstream"
	"knative.dev/serving-operator/pkg/apis/serving/v1alpha1"
	"knative.dev/serving-operator/test"
	"knative.dev/serving-operator/test/resources"
)

// TestKnativeServingDeployment verifies the KnativeServing creation, deployment recreation, and KnativeServing deletion.
func TestKnativeServingDeployment(t *testing.T) {
	cancel := logstream.Start(t)
	defer cancel()
	clients := Setup(t)

	names := test.ResourceNames{
		KnativeServing: test.ServingOperatorName,
		Namespace:      test.ServingOperatorNamespace,
	}

	test.CleanupOnInterrupt(func() { test.TearDown(clients, names) })
	defer test.TearDown(clients, names)

	// Create a KnativeServing
	if _, err := resources.CreateKnativeServing(clients.KnativeServing(), names); err != nil {
		t.Fatalf("KnativeService %q failed to create: %v", names.KnativeServing, err)
	}

	// Test if KnativeServing can reach the READY status
	t.Run("create", func(t *testing.T) {
		knativeServingVerify(t, clients, names)
	})

	t.Run("configure", func(t *testing.T) {
		knativeServingVerify(t, clients, names)
		knativeServingConfigure(t, clients, names)
	})

	// Delete the deployments one by one to see if they will be recreated.
	t.Run("restore", func(t *testing.T) {
		knativeServingVerify(t, clients, names)
		deploymentRecreation(t, clients, names)
	})

	// Delete the KnativeServing to see if all resources will be removed
	t.Run("delete", func(t *testing.T) {
		knativeServingVerify(t, clients, names)
		knativeServingDelete(t, clients, names)
	})
}

// knativeServingVerify verifies if the KnativeServing can reach the READY status.
func knativeServingVerify(t *testing.T, clients *test.Clients, names test.ResourceNames) {
	if _, err := resources.WaitForKnativeServingState(clients.KnativeServing(), names.KnativeServing,
		resources.IsKnativeServingReady); err != nil {
		t.Fatalf("KnativeService %q failed to get to the READY status: %v", names.KnativeServing, err)
	}

}

// knativeServingConfigure verifies that KnativeServing config is set properly
func knativeServingConfigure(t *testing.T, clients *test.Clients, names test.ResourceNames) {
	// We'll arbitrarily choose logging and defaults config
	loggingConfigKey := "logging"
	loggingConfigMapName := fmt.Sprintf("%s/config-%s", names.Namespace, loggingConfigKey)
	defaultsConfigKey := "defaults"
	defaultsConfigMapName := fmt.Sprintf("%s/config-%s", names.Namespace, defaultsConfigKey)
	// Get the existing KS without any spec
	ks, err := clients.KnativeServing().Get(names.KnativeServing, metav1.GetOptions{})
	// Add config to its spec
	ks.Spec = v1alpha1.KnativeServingSpec{
		Config: map[string]map[string]string{
			defaultsConfigKey: {
				"revision-timeout-seconds": "200",
			},
			loggingConfigKey: {
				"loglevel.controller": "debug",
				"loglevel.autoscaler": "debug",
			},
		},
	}
	// Update it
	if ks, err = clients.KnativeServing().Update(ks); err != nil {
		t.Fatalf("KnativeServing %q failed to update: %v", names.KnativeServing, err)
	}
	// Verify the relevant configmaps have been updated
	err = resources.WaitForConfigMap(defaultsConfigMapName, clients.KubeClient.Kube, func(m map[string]string) bool {
		return m["revision-timeout-seconds"] == "200"
	})
	if err != nil {
		t.Fatalf("The operator failed to update %s configmap", defaultsConfigMapName)
	}
	err = resources.WaitForConfigMap(loggingConfigMapName, clients.KubeClient.Kube, func(m map[string]string) bool {
		return m["loglevel.controller"] == "debug" && m["loglevel.autoscaler"] == "debug"
	})
	if err != nil {
		t.Fatalf("The operator failed to update %s configmap", loggingConfigMapName)
	}

	// Delete a single key/value pair
	delete(ks.Spec.Config[loggingConfigKey], "loglevel.autoscaler")
	// Update it
	if ks, err = clients.KnativeServing().Update(ks); err != nil {
		t.Fatalf("KnativeServing %q failed to update: %v", names.KnativeServing, err)
	}
	// Verify the relevant configmap has been updated
	err = resources.WaitForConfigMap(loggingConfigMapName, clients.KubeClient.Kube, func(m map[string]string) bool {
		_, autoscalerKeyExists := m["loglevel.autoscaler"]
		// deleted key/value pair should be removed from the target config map
		return m["loglevel.controller"] == "debug" && !autoscalerKeyExists
	})
	if err != nil {
		t.Fatal("The operator failed to update the configmap")
	}

	// Use an empty map as the value
	ks.Spec.Config[defaultsConfigKey] = map[string]string{}
	// Update it
	if ks, err = clients.KnativeServing().Update(ks); err != nil {
		t.Fatalf("KnativeServing %q failed to update: %v", names.KnativeServing, err)
	}
	// Verify the relevant configmap has been updated and does not contain any keys except "_example"
	err = resources.WaitForConfigMap(defaultsConfigMapName, clients.KubeClient.Kube, func(m map[string]string) bool {
		_, exampleExists := m["_example"]
		return len(m) == 1 && exampleExists
	})
	if err != nil {
		t.Fatal("The operator failed to update the configmap")
	}

	// Now remove the config from the spec and update
	ks.Spec = v1alpha1.KnativeServingSpec{}
	if ks, err = clients.KnativeServing().Update(ks); err != nil {
		t.Fatalf("KnativeServing %q failed to update: %v", names.KnativeServing, err)
	}
	// And verify the configmap entry is gone
	err = resources.WaitForConfigMap(loggingConfigMapName, clients.KubeClient.Kube, func(m map[string]string) bool {
		_, exists := m["loglevel.controller"]
		return !exists
	})
	if err != nil {
		t.Fatal("The operator failed to revert the configmap")
	}
}

// deploymentRecreation verify whether all the deployments for knative serving are able to recreate, when they are deleted.
func deploymentRecreation(t *testing.T, clients *test.Clients, names test.ResourceNames) {
	dpList, err := clients.KubeClient.Kube.AppsV1().Deployments(names.Namespace).List(metav1.ListOptions{})
	if err != nil {
		t.Fatalf("Failed to get any deployment under the namespace %q: %v",
			test.ServingOperatorNamespace, err)
	}
	if len(dpList.Items) == 0 {
		t.Fatalf("No deployment under the namespace %q was found",
			test.ServingOperatorNamespace)
	}
	// Delete the first deployment and verify the operator recreates it
	deployment := dpList.Items[0]
	if err := clients.KubeClient.Kube.AppsV1().Deployments(deployment.Namespace).Delete(deployment.Name,
		&metav1.DeleteOptions{}); err != nil {
		t.Fatalf("Failed to delete deployment %s/%s: %v", deployment.Namespace, deployment.Name, err)
	}

	waitErr := wait.PollImmediate(resources.Interval, resources.Timeout, func() (bool, error) {
		dep, err := clients.KubeClient.Kube.AppsV1().Deployments(deployment.Namespace).Get(deployment.Name, metav1.GetOptions{})
		if err != nil {
			// If the deployment is not found, we continue to wait for the availability.
			if apierrs.IsNotFound(err) {
				return false, nil
			}
			return false, err
		}
		return resources.IsDeploymentAvailable(dep)
	})

	if waitErr != nil {
		t.Fatalf("The deployment %s/%s failed to reach the desired state: %v", deployment.Namespace, deployment.Name, err)
	}

	if _, err := resources.WaitForKnativeServingState(clients.KnativeServing(), test.ServingOperatorName,
		resources.IsKnativeServingReady); err != nil {
		t.Fatalf("KnativeService %q failed to reach the desired state: %v", test.ServingOperatorName, err)
	}
	t.Logf("The deployment %s/%s reached the desired state.", deployment.Namespace, deployment.Name)
}

// knativeServingDelete deletes tha KnativeServing to see if all resources will be deleted
func knativeServingDelete(t *testing.T, clients *test.Clients, names test.ResourceNames) {
	if err := clients.KnativeServing().Delete(names.KnativeServing, &metav1.DeleteOptions{}); err != nil {
		t.Fatalf("KnativeServing %q failed to delete: %v", names.KnativeServing, err)
	}
	_, b, _, _ := runtime.Caller(0)
	m, err := mf.NewManifest(filepath.Join((filepath.Dir(b)+"/.."), "config/"), false, clients.Config)
	if err != nil {
		t.Fatal("Failed to load manifest", err)
	}
	if err := verifyNoKnativeServings(clients); err != nil {
		t.Fatal(err)
	}
	for _, u := range m.Resources {
		waitErr := wait.PollImmediate(resources.Interval, resources.Timeout, func() (bool, error) {
			gvrs, _ := meta.UnsafeGuessKindToResource(u.GroupVersionKind())
			if _, err := clients.Dynamic.Resource(gvrs).Get(u.GetName(), metav1.GetOptions{}); apierrs.IsNotFound(err) {
				return true, nil
			}
			return false, err
		})

		if waitErr != nil {
			t.Fatalf("The %s %s failed to be deleted: %v", u.GetKind(), u.GetName(), waitErr)
		}
		t.Logf("The %s %s has been deleted.", u.GetKind(), u.GetName())
	}
}

func verifyNoKnativeServings(clients *test.Clients) error {
	servings, err := clients.KnativeServingAll().List(metav1.ListOptions{})
	if err != nil {
		return err
	}
	if len(servings.Items) > 0 {
		return errors.New("Unable to verify cluster-scoped resources are deleted if any KnativeServing exists")
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

// This test references
// persistent_volumes.go
// volumes.go

package e2e

import (
	"strings"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"k8s.io/kubernetes/pkg/api/v1"
	metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
	"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
	"k8s.io/kubernetes/test/e2e/framework"
)

type testBody func(c clientset.Interface, f *framework.Framework, clientPod *v1.Pod, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume)
type disruptiveTest struct {
	testItStmt string
	runTest    testBody
}
type kubeletOpt string

const (
	MIN_NODES                  = 2
	NODE_STATE_WAIT            = 1 * time.Minute
	kStart          kubeletOpt = "start"
	kStop           kubeletOpt = "stop"
	kRestart        kubeletOpt = "restart"
)

var _ = framework.KubeDescribe("PersistentVolumes:Disruptive", func() {

	f := framework.NewDefaultFramework("disruptive-pv")
	var (
		c                         clientset.Interface
		ns                        string
		nfsServerPod              *v1.Pod
		nfsPVconfig               persistentVolumeConfig
		nfsServerIP, clientNodeIP string
		clientNode                *v1.Node
	)

	nfsServerConfig := VolumeTestConfig{
		namespace:   v1.NamespaceDefault,
		prefix:      "nfs",
		serverImage: "gcr.io/google_containers/volume-nfs:0.7",
		serverPorts: []int{2049},
		serverArgs:  []string{"-G", "777", "/exports"},
	}

	BeforeEach(func() {
		// To protect the NFS volume pod from the kubelet restart, we isolate it on its own node.
		framework.SkipUnlessNodeCountIsAtLeast(MIN_NODES)
		c = f.ClientSet
		ns = f.Namespace.Name

		// Start the NFS server pod.
		if nfsServerPod == nil {
			framework.Logf("[BeforeEach] Initializing NFS Server Pod")
			nfsServerPod = startVolumeServer(c, nfsServerConfig)

			framework.Logf("[BeforeEach] Configuring PersistentVolume")
			nfsServerIP = nfsServerPod.Status.PodIP
			Expect(nfsServerIP).NotTo(BeEmpty())
			nfsPVconfig = persistentVolumeConfig{
				namePrefix: "nfs-",
				pvSource: v1.PersistentVolumeSource{
					NFS: &v1.NFSVolumeSource{
						Server:   nfsServerIP,
						Path:     "/exports",
						ReadOnly: false,
					},
				},
			}
		}
		// Get the first ready node IP that is not hosting the NFS pod.
		if clientNodeIP == "" {
			framework.Logf("Designating test node")
			nodes := framework.GetReadySchedulableNodesOrDie(c)
			for _, node := range nodes.Items {
				if node.Name != nfsServerPod.Spec.NodeName {
					clientNode = &node
					clientNodeIP = framework.GetNodeExternalIP(clientNode)
					Expect(clientNodeIP).NotTo(BeEmpty())
					break
				}
			}
		}
	})

	AddCleanupAction(func() {
		if nfsServerPod != nil && c != nil {
			By("Deleting NFS server pod")
			nfsServerPodCleanup(c, nfsServerConfig)
			nfsServerPod = nil
		}
	})

	Context("when kubelet restarts", func() {

		var (
			clientPod *v1.Pod
			pv        *v1.PersistentVolume
			pvc       *v1.PersistentVolumeClaim
		)

		BeforeEach(func() {
			framework.Logf("Initializing test spec")
			clientPod, pv, pvc = initTestCase(f, c, nfsPVconfig, ns, clientNode.Name)
		})

		AfterEach(func() {
			framework.Logf("Tearing down test spec")
			tearDownTestCase(c, f, ns, clientPod, pvc, pv)
			pv, pvc, clientPod = nil, nil, nil
		})

		// Test table housing the It() title string and test spec.  runTest is type testBody, defined at
		// the start of this file.  To add tests, define a function mirroring the testBody signature and assign
		// to runTest.
		disruptiveTestTable := []disruptiveTest{
			{
				testItStmt: "Should test that a file written to the mount before kubelet restart is stat-able after restart.",
				runTest:    testKubeletRestartsAndRestoresMount,
			},
			{
				testItStmt: "Should test that a volume mount to a pod that is deleted while the kubelet is down unmounts when the kubelet returns.",
				runTest:    testVolumeUnmountsFromDeletedPod,
			},
		}

		// Test loop executes each disruptiveTest iteratively.
		for _, test := range disruptiveTestTable {
			func(t disruptiveTest) {
				It(t.testItStmt+" [Disruptive]", func() {
					By("Executing Spec")
					t.runTest(c, f, clientPod, pvc, pv)
				})
			}(test)
		}
	})
})

// SPEC: Test that a volume mounted to a pod remains mounted after a kubelet restarts
func testKubeletRestartsAndRestoresMount(c clientset.Interface, f *framework.Framework, clientPod *v1.Pod, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) {
	file := "/mnt/_SUCCESS"
	_, err := podExec(clientPod, "touch "+file)
	Expect(err).NotTo(HaveOccurred())

	kubeletCommand(kRestart, c, clientPod)

	_, err = podExec(clientPod, "cat "+file)
	Expect(err).NotTo(HaveOccurred())
	framework.Logf("Pod %s detected %s after kubelet restart", clientPod.Name, file)
}

// SPEC: Test that a volume unmounts if the client pod was deleted while the kubelet was down.
func testVolumeUnmountsFromDeletedPod(c clientset.Interface, f *framework.Framework, clientPod *v1.Pod, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) {

	nodeIP, err := framework.GetHostExternalAddress(c, clientPod)
	Expect(err).NotTo(HaveOccurred())
	nodeIP = nodeIP + ":22"

	result, err := framework.SSH("mount | grep "+string(clientPod.UID), nodeIP, framework.TestContext.Provider)
	Expect(err).NotTo(HaveOccurred())
	Expect(result.Code).To(BeZero())

	file := "/mnt/_SUCCESS"
	_, err = podExec(clientPod, "touch "+file)
	Expect(err).NotTo(HaveOccurred())

	kubeletCommand(kStop, c, clientPod)
	deletePod(f, c, clientPod.Namespace, clientPod)
	kubeletCommand(kStart, c, clientPod)

	result, err = framework.SSH("mount| grep "+string(clientPod.UID), nodeIP, framework.TestContext.Provider)
	Expect(err).NotTo(HaveOccurred())
	Expect(result.Code).NotTo(BeZero())
}

// Initialize a test spec with a pv, pvc, and nfs client pod
func initTestCase(f *framework.Framework, c clientset.Interface, pvConfig persistentVolumeConfig, ns, nodeName string) (*v1.Pod, *v1.PersistentVolume, *v1.PersistentVolumeClaim) {
	pv, pvc := createPVPVC(c, pvConfig, ns, false)
	pod := makePod(ns, pvc.Name)
	pod.Spec.NodeName = nodeName
	framework.Logf("Creating nfs client Pod %s on node %s", pod.Name, nodeName)
	pod, err := c.Core().Pods(ns).Create(pod)
	Expect(err).NotTo(HaveOccurred())
	err = framework.WaitForPodRunningInNamespace(c, pod)
	Expect(err).NotTo(HaveOccurred())

	pod, err = c.Core().Pods(ns).Get(pod.Name, metav1.GetOptions{})
	Expect(err).NotTo(HaveOccurred())
	pvc, err = c.Core().PersistentVolumeClaims(ns).Get(pvc.Name, metav1.GetOptions{})
	Expect(err).NotTo(HaveOccurred())
	pv, err = c.Core().PersistentVolumes().Get(pv.Name, metav1.GetOptions{})
	Expect(err).NotTo(HaveOccurred())
	return pod, pv, pvc
}

// Post-spec clean up
func tearDownTestCase(c clientset.Interface, f *framework.Framework, ns string, pod *v1.Pod, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) {
	deletePod(f, c, ns, pod)
	deletePersistentVolumeClaim(c, pvc.Name, ns)
	deletePersistentVolume(c, pv.Name)
}

// Start, Restart, or Stop the kubelet running on the node of the target pod.
// Allowed kubeltOps are `kStart`, `kStop`, and `kRestart`
func kubeletCommand(kOp kubeletOpt, c clientset.Interface, pod *v1.Pod) {
	nodeIP, err := framework.GetHostExternalAddress(c, pod)
	Expect(err).NotTo(HaveOccurred())
	nodeIP = nodeIP + ":22"
	sshResult, err := framework.SSH("/etc/init.d/kubelet "+string(kOp), nodeIP, framework.TestContext.Provider)
	Expect(err).NotTo(HaveOccurred())
	framework.LogSSHResult(sshResult)

	// On restart, waiting for node NotReady prevents a race condition where the node takes a few moments to leave the
	// Ready state which in turn short circuits WaitForNodeToBeReady()
	if kOp == kStop || kOp == kRestart {
		if ok := framework.WaitForNodeToBeNotReady(c, pod.Spec.NodeName, NODE_STATE_WAIT); !ok {
			framework.Failf("Node %s failed to enter NotReady state", pod.Spec.NodeName)
		}
	}
	if kOp == kStart || kOp == kRestart {
		if ok := framework.WaitForNodeToBeReady(c, pod.Spec.NodeName, NODE_STATE_WAIT); !ok {
			framework.Failf("Node %s failed to enter Ready state", pod.Spec.NodeName)
		}
	}
}

// Wraps RunKubectl to execute a bash cmd in target pod
func podExec(pod *v1.Pod, bashExec string) (string, error) {
	args := strings.Split(bashExec, " ")
	cmd := []string{"exec", "--namespace=" + pod.Namespace, pod.Name}
	cmd = append(cmd, args...)
	return framework.RunKubectl(cmd...)
}
<commit_msg>camel case constants, tweak function documentation<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

// This test references
// persistent_volumes.go
// volumes.go

package e2e

import (
	"strings"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"k8s.io/kubernetes/pkg/api/v1"
	metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
	"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
	"k8s.io/kubernetes/test/e2e/framework"
)

type testBody func(c clientset.Interface, f *framework.Framework, clientPod *v1.Pod, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume)
type disruptiveTest struct {
	testItStmt string
	runTest    testBody
}
type kubeletOpt string

const (
	MinNodes                    = 2
	NodeStateTimeout            = 1 * time.Minute
	kStart           kubeletOpt = "start"
	kStop            kubeletOpt = "stop"
	kRestart         kubeletOpt = "restart"
)

var _ = framework.KubeDescribe("PersistentVolumes:Disruptive", func() {

	f := framework.NewDefaultFramework("disruptive-pv")
	var (
		c                         clientset.Interface
		ns                        string
		nfsServerPod              *v1.Pod
		nfsPVconfig               persistentVolumeConfig
		nfsServerIP, clientNodeIP string
		clientNode                *v1.Node
	)

	nfsServerConfig := VolumeTestConfig{
		namespace:   v1.NamespaceDefault,
		prefix:      "nfs",
		serverImage: "gcr.io/google_containers/volume-nfs:0.7",
		serverPorts: []int{2049},
		serverArgs:  []string{"-G", "777", "/exports"},
	}

	BeforeEach(func() {
		// To protect the NFS volume pod from the kubelet restart, we isolate it on its own node.
		framework.SkipUnlessNodeCountIsAtLeast(MinNodes)
		c = f.ClientSet
		ns = f.Namespace.Name

		// Start the NFS server pod.
		if nfsServerPod == nil {
			framework.Logf("[BeforeEach] Initializing NFS Server Pod")
			nfsServerPod = startVolumeServer(c, nfsServerConfig)

			framework.Logf("[BeforeEach] Configuring PersistentVolume")
			nfsServerIP = nfsServerPod.Status.PodIP
			Expect(nfsServerIP).NotTo(BeEmpty())
			nfsPVconfig = persistentVolumeConfig{
				namePrefix: "nfs-",
				pvSource: v1.PersistentVolumeSource{
					NFS: &v1.NFSVolumeSource{
						Server:   nfsServerIP,
						Path:     "/exports",
						ReadOnly: false,
					},
				},
			}
		}
		// Get the first ready node IP that is not hosting the NFS pod.
		if clientNodeIP == "" {
			framework.Logf("Designating test node")
			nodes := framework.GetReadySchedulableNodesOrDie(c)
			for _, node := range nodes.Items {
				if node.Name != nfsServerPod.Spec.NodeName {
					clientNode = &node
					clientNodeIP = framework.GetNodeExternalIP(clientNode)
					Expect(clientNodeIP).NotTo(BeEmpty())
					break
				}
			}
		}
	})

	AddCleanupAction(func() {
		if nfsServerPod != nil && c != nil {
			By("Deleting NFS server pod")
			nfsServerPodCleanup(c, nfsServerConfig)
			nfsServerPod = nil
		}
	})

	Context("when kubelet restarts", func() {

		var (
			clientPod *v1.Pod
			pv        *v1.PersistentVolume
			pvc       *v1.PersistentVolumeClaim
		)

		BeforeEach(func() {
			framework.Logf("Initializing test spec")
			clientPod, pv, pvc = initTestCase(f, c, nfsPVconfig, ns, clientNode.Name)
		})

		AfterEach(func() {
			framework.Logf("Tearing down test spec")
			tearDownTestCase(c, f, ns, clientPod, pvc, pv)
			pv, pvc, clientPod = nil, nil, nil
		})

		// Test table housing the It() title string and test spec.  runTest is type testBody, defined at
		// the start of this file.  To add tests, define a function mirroring the testBody signature and assign
		// to runTest.
		disruptiveTestTable := []disruptiveTest{
			{
				testItStmt: "Should test that a file written to the mount before kubelet restart is stat-able after restart.",
				runTest:    testKubeletRestartsAndRestoresMount,
			},
			{
				testItStmt: "Should test that a volume mounted to a pod that is deleted while the kubelet is down unmounts when the kubelet returns.",
				runTest:    testVolumeUnmountsFromDeletedPod,
			},
		}

		// Test loop executes each disruptiveTest iteratively.
		for _, test := range disruptiveTestTable {
			func(t disruptiveTest) {
				It(t.testItStmt+" [Disruptive]", func() {
					By("Executing Spec")
					t.runTest(c, f, clientPod, pvc, pv)
				})
			}(test)
		}
	})
})

// testKubeletRestartsAndRestoresMount tests that a volume mounted to a pod remains mounted after a kubelet restarts
func testKubeletRestartsAndRestoresMount(c clientset.Interface, f *framework.Framework, clientPod *v1.Pod, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) {
	By("Writing to the volume.")
	file := "/mnt/_SUCCESS"
	_, err := podExec(clientPod, "touch "+file)
	Expect(err).NotTo(HaveOccurred())

	By("Restarting kubelet")
	kubeletCommand(kRestart, c, clientPod)

	By("Testing that written file is accessible.")
	_, err = podExec(clientPod, "cat "+file)
	Expect(err).NotTo(HaveOccurred())
	framework.Logf("Pod %s detected %s after kubelet restart", clientPod.Name, file)
}

// testVolumeUnmountsFromDeletedPod tests that a volume unmounts if the client pod was deleted while the kubelet was down.
func testVolumeUnmountsFromDeletedPod(c clientset.Interface, f *framework.Framework, clientPod *v1.Pod, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) {
	nodeIP, err := framework.GetHostExternalAddress(c, clientPod)
	Expect(err).NotTo(HaveOccurred())
	nodeIP = nodeIP + ":22"

	By("Expecting the volume mount to be found.")
	result, err := framework.SSH("mount | grep "+string(clientPod.UID), nodeIP, framework.TestContext.Provider)
	Expect(err).NotTo(HaveOccurred())
	Expect(result.Code).To(BeZero())

	By("Restarting the kubelet.")
	kubeletCommand(kStop, c, clientPod)
	deletePod(f, c, clientPod.Namespace, clientPod)
	kubeletCommand(kStart, c, clientPod)

	By("Expecting the volume mount not to be found.")
	result, err = framework.SSH("mount| grep "+string(clientPod.UID), nodeIP, framework.TestContext.Provider)
	Expect(err).NotTo(HaveOccurred())
	Expect(result.Code).NotTo(BeZero())

	framework.Logf("Volume mount detected on pod and written file is readable post-restart.")
}

// initTestCase initializes spec resources (pv, pvc, and pod) and returns pointers to be consumed by the test
func initTestCase(f *framework.Framework, c clientset.Interface, pvConfig persistentVolumeConfig, ns, nodeName string) (*v1.Pod, *v1.PersistentVolume, *v1.PersistentVolumeClaim) {
	pv, pvc := createPVPVC(c, pvConfig, ns, false)
	pod := makePod(ns, pvc.Name)
	pod.Spec.NodeName = nodeName
	framework.Logf("Creating nfs client Pod %s on node %s", pod.Name, nodeName)
	pod, err := c.Core().Pods(ns).Create(pod)
	Expect(err).NotTo(HaveOccurred())
	err = framework.WaitForPodRunningInNamespace(c, pod)
	Expect(err).NotTo(HaveOccurred())

	pod, err = c.Core().Pods(ns).Get(pod.Name, metav1.GetOptions{})
	Expect(err).NotTo(HaveOccurred())
	pvc, err = c.Core().PersistentVolumeClaims(ns).Get(pvc.Name, metav1.GetOptions{})
	Expect(err).NotTo(HaveOccurred())
	pv, err = c.Core().PersistentVolumes().Get(pv.Name, metav1.GetOptions{})
	Expect(err).NotTo(HaveOccurred())
	return pod, pv, pvc
}

// tearDownTestCase destroy resources created by initTestCase.
func tearDownTestCase(c clientset.Interface, f *framework.Framework, ns string, pod *v1.Pod, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) {
	deletePod(f, c, ns, pod)
	deletePersistentVolumeClaim(c, pvc.Name, ns)
	deletePersistentVolume(c, pv.Name)
}

// kubeletCommand performs `start`, `restart`, or `stop` on the kubelet running on the node of the target pod.
// Allowed kubeltOps are `kStart`, `kStop`, and `kRestart`
func kubeletCommand(kOp kubeletOpt, c clientset.Interface, pod *v1.Pod) {
	nodeIP, err := framework.GetHostExternalAddress(c, pod)
	Expect(err).NotTo(HaveOccurred())
	nodeIP = nodeIP + ":22"
	sshResult, err := framework.SSH("/etc/init.d/kubelet "+string(kOp), nodeIP, framework.TestContext.Provider)
	Expect(err).NotTo(HaveOccurred())
	framework.LogSSHResult(sshResult)

	// On restart, waiting for node NotReady prevents a race condition where the node takes a few moments to leave the
	// Ready state which in turn short circuits WaitForNodeToBeReady()
	if kOp == kStop || kOp == kRestart {
		if ok := framework.WaitForNodeToBeNotReady(c, pod.Spec.NodeName, NodeStateTimeout); !ok {
			framework.Failf("Node %s failed to enter NotReady state", pod.Spec.NodeName)
		}
	}
	if kOp == kStart || kOp == kRestart {
		if ok := framework.WaitForNodeToBeReady(c, pod.Spec.NodeName, NodeStateTimeout); !ok {
			framework.Failf("Node %s failed to enter Ready state", pod.Spec.NodeName)
		}
	}
}

// podExec wraps RunKubectl to execute a bash cmd in target pod
func podExec(pod *v1.Pod, bashExec string) (string, error) {
	args := strings.Split(bashExec, " ")
	cmd := []string{"exec", "--namespace=" + pod.Namespace, pod.Name}
	cmd = append(cmd, args...)
	return framework.RunKubectl(cmd...)
}
<|endoftext|>