<commit_before>package editor

import "github.com/elpinal/coco3/editor/register"

type opArg struct {
	opType     int // current operator type
	opStart    int
	opCount    int
	motionType int
}

type normalSet struct {
	opArg
	finishOp bool
	count    int
}

type normal struct {
	streamSet
	*editor

	normalSet
}

func (e *normal) Mode() mode {
	return modeNormal
}

func (e *normal) Run() (end bool, next mode, err error) {
	next = modeNormal
	e.finishOp = e.opType != OpNop
	r, _, err := e.streamSet.in.ReadRune()
	if err != nil {
		return end, next, err
	}
	for ('1' <= r && r <= '9') || (e.count != 0 && r == '0') {
		e.count = e.count*10 + int(r-'0')
		r1, _, err := e.streamSet.in.ReadRune()
		if err != nil {
			return end, next, err
		}
		r = r1
	}
	if e.count == 0 {
		e.count = 1
	}
	if e.opCount > 0 {
		e.count *= e.opCount
	}
	for _, cmd := range normalCommands {
		if cmd.r == r {
			next = cmd.fn(e, r)
			if n := e.doPendingOperator(); n != 0 {
				next = n
			}
			e.count = 0
			if next != modeInsert && e.pos == len(e.buf) {
				e.move(e.pos - 1)
			}
			return
		}
	}
	return end, next, err
}

func (e *normal) Runes() []rune {
	return e.editor.buf
}

func (e *normal) Position() int {
	return e.editor.pos
}

type normalCommand struct {
	r   rune                     // first char
	fn  func(*normal, rune) mode // function for this command
	arg int
}

var normalCommands = []normalCommand{
	{'$', (*normal).endline, 0},
	{'0', (*normal).beginline, 0},
	{'A', (*normal).edit, 0},
	{'B', (*normal).wordBack, 0},
	{'C', (*normal).abbrev, 0},
	{'D', (*normal).abbrev, 0},
	{'I', (*normal).edit, 0},
	{'W', (*normal).word, 0},
	{'X', (*normal).abbrev, 0},
	{'Y', (*normal).abbrev, 0},
	{'a', (*normal).edit, 0},
	{'b', (*normal).wordBack, 0},
	{'c', (*normal).operator, 0},
	{'d', (*normal).operator, 0},
	{'h', (*normal).left, 0},
	{'i', (*normal).edit, 0},
	{'j', (*normal).down, 0},
	{'k', (*normal).up, 0},
	{'l', (*normal).right, 0},
	{'p', (*normal).put1, 0},
	{'r', (*normal).replace, 0},
	{'w', (*normal).word, 0},
	{'x', (*normal).abbrev, 0},
	{'y', (*normal).operator, 0},
}

func (e *normal) endline(r rune) mode {
	e.move(len(e.buf))
	return modeNormal
}

func (e *normal) beginline(r rune) mode {
	e.move(0)
	return modeNormal
}

func (e *normal) wordBack(r rune) mode {
	for i := 0; i < e.count; i++ {
		switch r {
		case 'b':
			e.wordBackward()
		case 'B':
			e.wordBackwardNonBlank()
		}
	}
	return modeNormal
}

func (e *normal) operator(r rune) mode {
	op := opChars[r]
	if op == e.opType { // double operator
		e.motionType = mline
	} else {
		e.opStart = e.pos
		e.opType = op
		e.opCount = e.count
	}
	return modeNormal
}

func (e *normal) left(r rune) mode {
	e.move(e.pos - e.count)
	return modeNormal
}

func (e *normal) edit(r rune) mode {
	if (r == 'a' || r == 'i') && e.opType != OpNop {
		e.object(r)
		return modeNormal
	}
	switch r {
	case 'A':
		e.move(len(e.buf))
	case 'I':
		e.move(0)
	case 'a':
		e.move(e.pos + 1)
	}
	return modeInsert
}

func (e *normal) object(r rune) {
	var include bool
	if r == 'a' {
		include = true
	}
	var from, to int
	r1, _, _ := e.streamSet.in.ReadRune()
	switch r1 {
	case 'w':
		from, to = e.currentWord(include)
	case '"', '\'', '`':
		from, to = e.currentQuote(include, r1)
		if from < 0 || to < 0 {
			return
		}
	default:
		return
	}
	e.opStart = from
	e.pos = to
}

func (e *normal) down(r rune) mode {
	if e.age >= len(e.history)-1 {
		return modeNormal
	}
	e.history[e.age] = e.buf
	e.age++
	e.buf = e.history[e.age]
	e.pos = len(e.buf)
	return modeNormal
}

func (e *normal) up(r rune) mode {
	if e.age <= 0 {
		return modeNormal
	}
	if e.age == len(e.history) {
		e.history = append(e.history, e.buf)
	} else {
		e.history[e.age] = e.buf
	}
	e.age--
	e.buf = e.history[e.age]
	e.pos = len(e.buf)
	return modeNormal
}

func (e *normal) right(r rune) mode {
	e.move(e.pos + e.count)
	return modeNormal
}

func (e *normal) put1(r rune) mode {
	for i := 0; i < e.count; i++ {
		e.put(register.Unnamed, e.pos+1)
	}
	return modeNormal
}

func (e *normal) replace(r rune) mode {
	r1, _, _ := e.streamSet.in.ReadRune()
	s := make([]rune, e.count)
	for i := 0; i < e.count; i++ {
		s[i] = r1
	}
	e.editor.replace(s, e.pos)
	e.move(e.pos + e.count - 1)
	return modeNormal
}

func (e *normal) word(r rune) mode {
	for i := 0; i < e.count; i++ {
		switch r {
		case 'w':
			e.wordForward()
		case 'W':
			e.wordForwardNonBlank()
		}
	}
	return modeNormal
}

func (e *normal) doPendingOperator() mode {
	if !e.finishOp {
		return 0
	}
	from := e.opStart
	to := e.pos
	if e.motionType == mline {
		from = 0
		to = len(e.buf)
	}
	switch e.opType {
	case OpDelete:
		e.yank(register.Unnamed, from, to)
		e.delete(from, to)
	case OpYank:
		e.yank(register.Unnamed, from, to)
	case OpChange:
		e.yank(register.Unnamed, from, to)
		e.delete(from, to)
		return modeInsert
	}
	e.clearOp()
	return modeNormal
}

func (e *normal) clearOp() {
	e.opType = OpNop
	e.opCount = 0
}

func (e *normal) abbrev(r rune) mode {
	amap := map[rune][]rune{
		'x': []rune("dl"),
		'X': []rune("dh"),
		'D': []rune("d$"),
		'C': []rune("c$"),
		'Y': []rune("y$"),
	}
	e.streamSet.in.Add(amap[r])
	e.opCount = e.count
	return modeNormal
}
<commit_msg>Clear operation<commit_after>package editor

import "github.com/elpinal/coco3/editor/register"

type opArg struct {
	opType     int // current operator type
	opStart    int
	opCount    int
	motionType int
}

type normalSet struct {
	opArg
	finishOp bool
	count    int
}

type normal struct {
	streamSet
	*editor

	normalSet
}

func (e *normal) Mode() mode {
	return modeNormal
}

func (e *normal) Run() (end bool, next mode, err error) {
	next = modeNormal
	e.finishOp = e.opType != OpNop
	r, _, err := e.streamSet.in.ReadRune()
	if err != nil {
		return end, next, err
	}
	for ('1' <= r && r <= '9') || (e.count != 0 && r == '0') {
		e.count = e.count*10 + int(r-'0')
		r1, _, err := e.streamSet.in.ReadRune()
		if err != nil {
			return end, next, err
		}
		r = r1
	}
	if e.count == 0 {
		e.count = 1
	}
	if e.opCount > 0 {
		e.count *= e.opCount
	}
	for _, cmd := range normalCommands {
		if cmd.r == r {
			next = cmd.fn(e, r)
			if n := e.doPendingOperator(); n != 0 {
				next = n
			}
			e.count = 0
			if next != modeInsert && e.pos == len(e.buf) {
				e.move(e.pos - 1)
			}
			return
		}
	}
	return end, next, err
}

func (e *normal) Runes() []rune {
	return e.editor.buf
}

func (e *normal) Position() int {
	return e.editor.pos
}

type normalCommand struct {
	r   rune                     // first char
	fn  func(*normal, rune) mode // function for this command
	arg int
}

var normalCommands = []normalCommand{
	{'$', (*normal).endline, 0},
	{'0', (*normal).beginline, 0},
	{'A', (*normal).edit, 0},
	{'B', (*normal).wordBack, 0},
	{'C', (*normal).abbrev, 0},
	{'D', (*normal).abbrev, 0},
	{'I', (*normal).edit, 0},
	{'W', (*normal).word, 0},
	{'X', (*normal).abbrev, 0},
	{'Y', (*normal).abbrev, 0},
	{'a', (*normal).edit, 0},
	{'b', (*normal).wordBack, 0},
	{'c', (*normal).operator, 0},
	{'d', (*normal).operator, 0},
	{'h', (*normal).left, 0},
	{'i', (*normal).edit, 0},
	{'j', (*normal).down, 0},
	{'k', (*normal).up, 0},
	{'l', (*normal).right, 0},
	{'p', (*normal).put1, 0},
	{'r', (*normal).replace, 0},
	{'w', (*normal).word, 0},
	{'x', (*normal).abbrev, 0},
	{'y', (*normal).operator, 0},
}

func (e *normal) endline(r rune) mode {
	e.move(len(e.buf))
	return modeNormal
}

func (e *normal) beginline(r rune) mode {
	e.move(0)
	return modeNormal
}

func (e *normal) wordBack(r rune) mode {
	for i := 0; i < e.count; i++ {
		switch r {
		case 'b':
			e.wordBackward()
		case 'B':
			e.wordBackwardNonBlank()
		}
	}
	return modeNormal
}

func (e *normal) operator(r rune) mode {
	op := opChars[r]
	if op == e.opType { // double operator
		e.motionType = mline
	} else {
		e.opStart = e.pos
		e.opType = op
		e.opCount = e.count
	}
	return modeNormal
}

func (e *normal) left(r rune) mode {
	e.move(e.pos - e.count)
	return modeNormal
}

func (e *normal) edit(r rune) mode {
	if (r == 'a' || r == 'i') && e.opType != OpNop {
		e.object(r)
		return modeNormal
	}
	switch r {
	case 'A':
		e.move(len(e.buf))
	case 'I':
		e.move(0)
	case 'a':
		e.move(e.pos + 1)
	}
	return modeInsert
}

func (e *normal) object(r rune) {
	var include bool
	if r == 'a' {
		include = true
	}
	var from, to int
	r1, _, _ := e.streamSet.in.ReadRune()
	switch r1 {
	case 'w':
		from, to = e.currentWord(include)
	case '"', '\'', '`':
		from, to = e.currentQuote(include, r1)
		if from < 0 || to < 0 {
			return
		}
	default:
		return
	}
	e.opStart = from
	e.pos = to
}

func (e *normal) down(r rune) mode {
	if e.age >= len(e.history)-1 {
		return modeNormal
	}
	e.history[e.age] = e.buf
	e.age++
	e.buf = e.history[e.age]
	e.pos = len(e.buf)
	return modeNormal
}

func (e *normal) up(r rune) mode {
	if e.age <= 0 {
		return modeNormal
	}
	if e.age == len(e.history) {
		e.history = append(e.history, e.buf)
	} else {
		e.history[e.age] = e.buf
	}
	e.age--
	e.buf = e.history[e.age]
	e.pos = len(e.buf)
	return modeNormal
}

func (e *normal) right(r rune) mode {
	e.move(e.pos + e.count)
	return modeNormal
}

func (e *normal) put1(r rune) mode {
	for i := 0; i < e.count; i++ {
		e.put(register.Unnamed, e.pos+1)
	}
	return modeNormal
}

func (e *normal) replace(r rune) mode {
	r1, _, _ := e.streamSet.in.ReadRune()
	s := make([]rune, e.count)
	for i := 0; i < e.count; i++ {
		s[i] = r1
	}
	e.editor.replace(s, e.pos)
	e.move(e.pos + e.count - 1)
	return modeNormal
}

func (e *normal) word(r rune) mode {
	for i := 0; i < e.count; i++ {
		switch r {
		case 'w':
			e.wordForward()
		case 'W':
			e.wordForwardNonBlank()
		}
	}
	return modeNormal
}

func (e *normal) doPendingOperator() mode {
	if !e.finishOp {
		return 0
	}
	from := e.opStart
	to := e.pos
	if e.motionType == mline {
		from = 0
		to = len(e.buf)
	}
	switch e.opType {
	case OpDelete:
		e.yank(register.Unnamed, from, to)
		e.delete(from, to)
	case OpYank:
		e.yank(register.Unnamed, from, to)
	case OpChange:
		e.yank(register.Unnamed, from, to)
		e.delete(from, to)
		return modeInsert
	}
	e.clearOp()
	return modeNormal
}

func (e *normal) clearOp() {
	e.opType = OpNop
	e.opCount = 0
	e.motionType = mchar
}

func (e *normal) abbrev(r rune) mode {
	amap := map[rune][]rune{
		'x': []rune("dl"),
		'X': []rune("dh"),
		'D': []rune("d$"),
		'C': []rune("c$"),
		'Y': []rune("y$"),
	}
	e.streamSet.in.Add(amap[r])
	e.opCount = e.count
	return modeNormal
}
<|endoftext|>


<commit_before>package app

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"time"
	"github.com/PuerkitoBio/goquery"

	"golang.org/x/net/context"
	"google.golang.org/appengine"
	"google.golang.org/appengine/urlfetch"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
)

type Schedule struct {
	Teacher  string      // 先生のID
	Date     []time.Time // 予約可能日時
	Updated  time.Time
}

const (
	maxDays = 2
	form = "2006-01-02 15:04:05"
)

func init() {
	http.HandleFunc("/check", handler)
}

func handler(w http.ResponseWriter, r *http.Request) {

	ctx := appengine.NewContext(r)
	teach := os.Getenv("teacher")
	if teach == "" {
		log.Debugf(ctx, "invalid teacher id: %v", teach)
		return
	}

	teachers := strings.Split(teach, ",")
	log.Debugf(ctx, "teachers: %v", teachers)
	for _, teacher := range teachers {
		err := search(ctx, teacher)
		if err != nil {
			log.Warningf(ctx, "err: %v", err)
		}
	}
}

func search(ctx context.Context, teacher string) error {

	client := urlfetch.Client(ctx)
	url := fmt.Sprintf("http://eikaiwa.dmm.com/teacher/index/%s/", teacher)
	resp, err := client.Get(url)
	if err != nil {
		return fmt.Errorf("access error: %s, context: %v", url, err)
	}

	doc, _ := goquery.NewDocumentFromResponse(resp)
	// get all schedule

	// teacher's name: Second(last) element of document.getElementsByTagName('h1')
	name := doc.Find("h1").Last().Text()
	log.Debugf(ctx, "name : %v", name)

	// teacher's image: document.getElementsByClassName('profile-pic')
	image, _ := doc.Find(".profile-pic").First().Attr("src")
	log.Debugf(ctx, "image : %v", image)

	available := []time.Time{}
	// yyyy-mm-dd HH:MM:ss
	re := regexp.MustCompile("[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) ([01][0-9]|2[0-3]):[03]0:00")

	doc.Find(".oneday").EachWithBreak(func(i int, s *goquery.Selection) bool {
		// 直近のmaxDays日分の予約可能情報を対象とする
		log.Debugf(ctx, "i = %v", i)
		if i >= maxDays {
			return false
		}

		// TODO 受講日情報要らない予感
		date := s.Find(".date").Text() // 受講日
		log.Debugf(ctx, "-----%v-----", date)

		s.Find(".bt-open").Each(func(_ int, s *goquery.Selection) {

			s2, _ := s.Attr("id") // 受講可能時刻
			log.Debugf(ctx, "%v", s2)
			dateString := re.FindString(s2)
			log.Debugf(ctx, "%v", dateString)

			day, _ := time.ParseInLocation(form, dateString, time.FixedZone("Asia/Tokyo", 9*60*60))
			log.Debugf(ctx, "%v", day)

			available = append(available, day)
		})
		return true
	})

	key := datastore.NewKey(ctx, "Schedule", teacher, 0, nil)

	var old Schedule
	if err := datastore.Get(ctx, key, &old); err != nil {
		// Entityが空の場合は見逃す
		if err.Error() != "datastore: no such entity" {
			return fmt.Errorf("datastore access error: %s, context: %v", teacher, err)
		}
	}

	new := Schedule {
		teacher,
		available,
		time.Now().In(time.FixedZone("Asia/Tokyo", 9*60*60)),
	}

	if _, err := datastore.Put(ctx, key, &new); err != nil {
		return fmt.Errorf("datastore access error: %s, context: %v", new.Teacher, err)
	}

	notifications := []string{}
	for _, newVal := range available {
		var notify = true
		for _, oldVal := range old.Date {
			if newVal.Equal(oldVal) {
				notify = false
				break
			}
		}
		if notify {
			notifications = append(notifications, newVal.Format(form))
		}
	}
	log.Debugf(ctx, "notification data: %v, %v", len(notifications), notifications)

	if len(notifications) == 0 {
		return nil
	}

	token := os.Getenv("slack_token")
	if token != "" {

		channel := os.Getenv("channel")
		if channel == "" {
			channel = "#general"
		}

		values := url.Values{}
		values.Add("token", token)
		values.Add("channel", channel)
		values.Add("as_user", "false")
		values.Add("username", fmt.Sprintf("%s from DMM Eikaiwa", name))
		values.Add("icon_url", image)
		values.Add("text", fmt.Sprintf(messageFormat, strings.Join(notifications, "\n"), url))

		res, err := client.PostForm("https://slack.com/api/chat.postMessage", values)
		if err != nil {
			log.Debugf(ctx, "senderror %v", err)
			return fmt.Errorf("noti send error: %s, context: %v", teacher, err)
		}
		defer res.Body.Close()

		b, err := ioutil.ReadAll(res.Body)
		if err == nil {
			log.Debugf(ctx, "response: %v", string(b))
		}
	}
	return nil
}

const messageFormat = `
Hi, you can have a lesson below!
%s

Access to <%s>
`
<commit_msg>通知処理をメソッドに切り出し<commit_after>package app

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"time"
	"github.com/PuerkitoBio/goquery"

	"golang.org/x/net/context"
	"google.golang.org/appengine"
	"google.golang.org/appengine/urlfetch"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
)

type Schedule struct {
	Teacher  string      // 先生のID
	Date     []time.Time // 予約可能日時
	Updated  time.Time
}

const (
	maxDays = 2
	form = "2006-01-02 15:04:05"
)

func init() {
	http.HandleFunc("/check", handler)
}

func handler(w http.ResponseWriter, r *http.Request) {

	ctx := appengine.NewContext(r)
	teach := os.Getenv("teacher")
	if teach == "" {
		log.Debugf(ctx, "invalid teacher id: %v", teach)
		return
	}

	teachers := strings.Split(teach, ",")
	log.Debugf(ctx, "teachers: %v", teachers)
	for _, teacher := range teachers {
		err := search(ctx, teacher)
		if err != nil {
			log.Warningf(ctx, "err: %v", err)
		}
	}
}

func search(ctx context.Context, teacher string) error {

	client := urlfetch.Client(ctx)
	url := fmt.Sprintf("http://eikaiwa.dmm.com/teacher/index/%s/", teacher)
	resp, err := client.Get(url)
	if err != nil {
		return fmt.Errorf("access error: %s, context: %v", url, err)
	}

	doc, _ := goquery.NewDocumentFromResponse(resp)
	// get all schedule

	// teacher's name: Second(last) element of document.getElementsByTagName('h1')
	name := doc.Find("h1").Last().Text()
	log.Debugf(ctx, "name : %v", name)

	// teacher's image: document.getElementsByClassName('profile-pic')
	image, _ := doc.Find(".profile-pic").First().Attr("src")
	log.Debugf(ctx, "image : %v", image)

	available := []time.Time{}
	// yyyy-mm-dd HH:MM:ss
	re := regexp.MustCompile("[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) ([01][0-9]|2[0-3]):[03]0:00")

	doc.Find(".oneday").EachWithBreak(func(i int, s *goquery.Selection) bool {
		// 直近のmaxDays日分の予約可能情報を対象とする
		log.Debugf(ctx, "i = %v", i)
		if i >= maxDays {
			return false
		}

		// TODO 受講日情報要らない予感
		date := s.Find(".date").Text() // 受講日
		log.Debugf(ctx, "-----%v-----", date)

		s.Find(".bt-open").Each(func(_ int, s *goquery.Selection) {

			s2, _ := s.Attr("id") // 受講可能時刻
			log.Debugf(ctx, "%v", s2)
			dateString := re.FindString(s2)
			log.Debugf(ctx, "%v", dateString)

			day, _ := time.ParseInLocation(form, dateString, time.FixedZone("Asia/Tokyo", 9*60*60))
			log.Debugf(ctx, "%v", day)

			available = append(available, day)
		})
		return true
	})

	key := datastore.NewKey(ctx, "Schedule", teacher, 0, nil)

	var old Schedule
	if err := datastore.Get(ctx, key, &old); err != nil {
		// Entityが空の場合は見逃す
		if err.Error() != "datastore: no such entity" {
			return fmt.Errorf("datastore access error: %s, context: %v", teacher, err)
		}
	}

	new := Schedule {
		teacher,
		available,
		time.Now().In(time.FixedZone("Asia/Tokyo", 9*60*60)),
	}

	if _, err := datastore.Put(ctx, key, &new); err != nil {
		return fmt.Errorf("datastore access error: %s, context: %v", new.Teacher, err)
	}

	notifications := []string{}
	for _, newVal := range available {
		var notify = true
		for _, oldVal := range old.Date {
			if newVal.Equal(oldVal) {
				notify = false
				break
			}
		}
		if notify {
			notifications = append(notifications, newVal.Format(form))
		}
	}
	log.Debugf(ctx, "notification data: %v, %v", len(notifications), notifications)

	if len(notifications) == 0 {
		return nil
	}

	noti := Notification {
		Name: name,
		Id: teacher,
		Page: url,
		Icon: image,
		Lessons: notifications,
	}
	go notify(ctx, noti)
	return nil
}

type Notification struct {
	Name    string
	Id      string
	Page    string
	Icon    string
	Lessons []string
}

func notify(ctx context.Context, noti Notification) {
	notiType := os.Getenv("notification_type")
	switch notiType {
	case "slack":
		toSlack(ctx, noti)
	case "mail":
		toMail(ctx, noti)
	default:
		log.Warningf(ctx, "unknown notification type: %v", notiType)
	}
}

func toSlack(ctx context.Context, noti Notification) {

	token := os.Getenv("slack_token")
	if token == "" {
		log.Warningf(ctx, "Invalid ENV value. slack_token: %v", token)
		return
	}

	channel := os.Getenv("channel")
	if channel == "" {
		log.Infof(ctx, "Invalid ENV value. Default value '#general' is set. channel: %v", token)
		channel = "#general"
	}

	values := url.Values{}
	values.Add("token", token)
	values.Add("channel", channel)
	values.Add("as_user", "false")
	values.Add("username", fmt.Sprintf("%s from DMM Eikaiwa", noti.Name))
	values.Add("icon_url", noti.Icon)
	values.Add("text", fmt.Sprintf(messageFormat, strings.Join(noti.Lessons, "\n"), noti.Page))

	client := urlfetch.Client(ctx)
	res, err := client.PostForm("https://slack.com/api/chat.postMessage", values)
	if err != nil {
		log.Debugf(ctx, "noti send error: %s, context: %v", noti.Id, err)
	}
	defer res.Body.Close()

	b, err := ioutil.ReadAll(res.Body)
	if err == nil {
		log.Debugf(ctx, "response: %v", string(b))
	}
}

func toMail(ctx context.Context, noti Notification) {
	// TODO write code
}

const messageFormat = `
Hi, you can have a lesson below!
%s

Access to <%s>
`
<|endoftext|>


<commit_before>package node

import (
	"errors"
	"fmt"
	"net"
	"net/url"
	"os"
	"path/filepath"
	"time"

	dockertypes "github.com/docker/engine-api/types"
	dockerclient "github.com/fsouza/go-dockerclient"
	"github.com/golang/glog"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kubeletapp "k8s.io/kubernetes/cmd/kubelet/app"
	kapiv1 "k8s.io/kubernetes/pkg/api/v1"
	"k8s.io/kubernetes/pkg/kubelet/cadvisor"
	cadvisortesting "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing"
	"k8s.io/kubernetes/pkg/kubelet/cm"
	dockertools "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
	"k8s.io/kubernetes/pkg/volume"

	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	cmdutil "github.com/openshift/origin/pkg/cmd/util"
	dockerutil "github.com/openshift/origin/pkg/cmd/util/docker"
	"github.com/openshift/origin/pkg/volume/emptydir"
)

const minimumDockerAPIVersionWithPullByID = "1.18"

// EnsureKubeletAccess performs a number of test operations that the Kubelet requires to properly function.
// All errors here are fatal.
func (c *NodeConfig) EnsureKubeletAccess() {
	if _, err := os.Stat("/var/lib/docker"); os.IsPermission(err) {
		c.HandleDockerError("Unable to view the /var/lib/docker directory - are you running as root?")
	}
	if c.Containerized {
		if _, err := os.Stat("/rootfs"); os.IsPermission(err) || os.IsNotExist(err) {
			glog.Fatal("error: Running in containerized mode, but cannot find the /rootfs directory - be sure to mount the host filesystem at /rootfs (read-only) in the container.")
		}
		if !sameFileStat(true, "/rootfs/sys", "/sys") {
			glog.Fatal("error: Running in containerized mode, but the /sys directory in the container does not appear to match the host /sys directory - be sure to mount /sys into the container.")
		}
		if !sameFileStat(true, "/rootfs/var/run", "/var/run") {
			glog.Fatal("error: Running in containerized mode, but the /var/run directory in the container does not appear to match the host /var/run directory - be sure to mount /var/run (read-write) into the container.")
		}
	}
	// TODO: check whether we can mount disks (for volumes)
	// TODO: check things cAdvisor needs to properly function
	// TODO: test a cGroup move?
}

// sameFileStat checks whether the provided paths are the same file, to verify that a user has correctly
// mounted those binaries
func sameFileStat(requireMode bool, src, dst string) bool {
	srcStat, err := os.Stat(src)
	if err != nil {
		glog.V(4).Infof("Unable to stat %q: %v", src, err)
		return false
	}
	dstStat, err := os.Stat(dst)
	if err != nil {
		glog.V(4).Infof("Unable to stat %q: %v", dst, err)
		return false
	}
	if requireMode && srcStat.Mode() != dstStat.Mode() {
		glog.V(4).Infof("Mode mismatch between %q (%s) and %q (%s)", src, srcStat.Mode(), dst, dstStat.Mode())
		return false
	}
	if !os.SameFile(srcStat, dstStat) {
		glog.V(4).Infof("inode and device mismatch between %q (%s) and %q (%s)", src, srcStat, dst, dstStat)
		return false
	}
	return true
}

// EnsureDocker attempts to connect to the Docker daemon defined by the helper,
// and if it is unable to it will print a warning.
func (c *NodeConfig) EnsureDocker(docker *dockerutil.Helper) {
	if c.KubeletServer.ContainerRuntime != "docker" {
		return
	}
	dockerClient, dockerAddr, err := docker.GetKubeClient(c.KubeletServer.RuntimeRequestTimeout.Duration, c.KubeletServer.ImagePullProgressDeadline.Duration)
	if err != nil {
		c.HandleDockerError(fmt.Sprintf("Unable to create a Docker client for %s - Docker must be installed and running to start containers.\n%v", dockerAddr, err))
		return
	}
	if url, err := url.Parse(dockerAddr); err == nil && url.Scheme == "unix" && len(url.Path) > 0 {
		s, err := os.Stat(url.Path)
		switch {
		case os.IsNotExist(err):
			c.HandleDockerError(fmt.Sprintf("No Docker socket found at %s. Have you started the Docker daemon?", url.Path))
			return
		case os.IsPermission(err):
			c.HandleDockerError(fmt.Sprintf("You do not have permission to connect to the Docker daemon (via %s). This process requires running as the root user.", url.Path))
			return
		case err == nil && s.IsDir():
			c.HandleDockerError(fmt.Sprintf("The Docker socket at %s is a directory instead of a unix socket - check that you have configured your connection to the Docker daemon properly.", url.Path))
			return
		}
	}
	if err := dockerClient.Ping(); err != nil {
		c.HandleDockerError(fmt.Sprintf("Docker could not be reached at %s.  Docker must be installed and running to start containers.\n%v", dockerAddr, err))
		return
	}

	glog.Infof("Connecting to Docker at %s", dockerAddr)

	version, err := dockerClient.Version()
	if err != nil {
		c.HandleDockerError(fmt.Sprintf("Unable to check for Docker server version.\n%v", err))
		return
	}

	serverVersion, err := dockerclient.NewAPIVersion(version.APIVersion)
	if err != nil {
		c.HandleDockerError(fmt.Sprintf("Unable to determine Docker server version from %q.\n%v", version.APIVersion, err))
		return
	}

	minimumPullByIDVersion, err := dockerclient.NewAPIVersion(minimumDockerAPIVersionWithPullByID)
	if err != nil {
		c.HandleDockerError(fmt.Sprintf("Unable to check for Docker server version.\n%v", err))
		return
	}

	if serverVersion.LessThan(minimumPullByIDVersion) {
		c.HandleDockerError(fmt.Sprintf("Docker 1.6 or later (server API version 1.18 or later) required."))
		return
	}

	c.DockerClient = dockerClient
}

// HandleDockerError handles an an error from the docker daemon
func (c *NodeConfig) HandleDockerError(message string) {
	if !c.AllowDisabledDocker {
		glog.Fatalf("error: %s", message)
	}
	glog.Errorf("WARNING: %s", message)
	c.DockerClient = &dockertools.FakeDockerClient{VersionInfo: dockertypes.Version{APIVersion: "1.18"}}
}

// EnsureVolumeDir attempts to convert the provided volume directory argument to
// an absolute path and create the directory if it does not exist. Will exit if
// an error is encountered.
func (c *NodeConfig) EnsureVolumeDir() {
	if volumeDir, err := c.initializeVolumeDir(c.VolumeDir); err != nil {
		glog.Fatal(err)
	} else {
		c.VolumeDir = volumeDir
	}
}

func (c *NodeConfig) initializeVolumeDir(path string) (string, error) {
	rootDirectory, err := filepath.Abs(path)
	if err != nil {
		return "", fmt.Errorf("Error converting volume directory to an absolute path: %v", err)
	}

	if _, err := os.Stat(rootDirectory); os.IsNotExist(err) {
		if err := os.MkdirAll(rootDirectory, 0750); err != nil {
			return "", fmt.Errorf("Couldn't create kubelet volume root directory '%s': %s", rootDirectory, err)
		}
	}
	return rootDirectory, nil
}

// EnsureLocalQuota checks if the node config specifies a local storage
// perFSGroup quota, and if so will test that the volumeDirectory is on a
// filesystem suitable for quota enforcement. If checks pass the k8s emptyDir
// volume plugin will be replaced with a wrapper version which adds quota
// functionality.
func (c *NodeConfig) EnsureLocalQuota(nodeConfig configapi.NodeConfig) {
	if nodeConfig.VolumeConfig.LocalQuota.PerFSGroup == nil {
		return
	}
	glog.V(4).Info("Replacing empty-dir volume plugin with quota wrapper")
	wrappedEmptyDirPlugin := false

	quotaApplicator, err := emptydir.NewQuotaApplicator(nodeConfig.VolumeDirectory)
	if err != nil {
		glog.Fatalf("Could not set up local quota, %s", err)
	}

	// Create a volume spec with emptyDir we can use to search for the
	// emptyDir plugin with CanSupport:
	emptyDirSpec := &volume.Spec{
		Volume: &kapiv1.Volume{
			VolumeSource: kapiv1.VolumeSource{
				EmptyDir: &kapiv1.EmptyDirVolumeSource{},
			},
		},
	}

	for idx, plugin := range c.KubeletDeps.VolumePlugins {
		// Can't really do type checking or use a constant here as they are not exported:
		if plugin.CanSupport(emptyDirSpec) {
			wrapper := emptydir.EmptyDirQuotaPlugin{
				VolumePlugin:    plugin,
				Quota:           *nodeConfig.VolumeConfig.LocalQuota.PerFSGroup,
				QuotaApplicator: quotaApplicator,
			}
			c.KubeletDeps.VolumePlugins[idx] = &wrapper
			wrappedEmptyDirPlugin = true
		}
	}
	// Because we can't look for the k8s emptyDir plugin by any means that would
	// survive a refactor, error out if we couldn't find it:
	if !wrappedEmptyDirPlugin {
		glog.Fatal(errors.New("No plugin handling EmptyDir was found, unable to apply local quotas"))
	}
}

// RunKubelet starts the Kubelet.
func (c *NodeConfig) RunKubelet() {
	var clusterDNS net.IP
	if len(c.KubeletServer.ClusterDNS) == 0 {
		if service, err := c.KubeletDeps.KubeClient.Core().Services(metav1.NamespaceDefault).Get("kubernetes", metav1.GetOptions{}); err == nil {
			if includesServicePort(service.Spec.Ports, 53, "dns") {
				// Use master service if service includes "dns" port 53.
				clusterDNS = net.ParseIP(service.Spec.ClusterIP)
			}
		}
	}
	if clusterDNS == nil {
		if endpoint, err := c.KubeletDeps.KubeClient.Core().Endpoints(metav1.NamespaceDefault).Get("kubernetes", metav1.GetOptions{}); err == nil {
			if endpointIP, ok := firstEndpointIPWithNamedPort(endpoint, 53, "dns"); ok {
				// Use first endpoint if endpoint includes "dns" port 53.
				clusterDNS = net.ParseIP(endpointIP)
			} else if endpointIP, ok := firstEndpointIP(endpoint, 53); ok {
				// Test and use first endpoint if endpoint includes any port 53.
				if err := cmdutil.WaitForSuccessfulDial(false, "tcp", fmt.Sprintf("%s:%d", endpointIP, 53), 50*time.Millisecond, 0, 2); err == nil {
					clusterDNS = net.ParseIP(endpointIP)
				}
			}
		}
	}
	if clusterDNS != nil && !clusterDNS.IsUnspecified() {
		c.KubeletServer.ClusterDNS = []string{clusterDNS.String()}
	}

	// only set when ContainerRuntime == "docker"
	c.KubeletDeps.DockerClient = c.DockerClient
	// updated by NodeConfig.EnsureVolumeDir
	c.KubeletServer.RootDirectory = c.VolumeDir

	// hook for overriding the cadvisor interface for integration tests
	c.KubeletDeps.CAdvisorInterface = defaultCadvisorInterface
	// hook for overriding the container manager interface for integration tests
	c.KubeletDeps.ContainerManager = defaultContainerManagerInterface

	go func() {
		glog.Fatal(kubeletapp.Run(c.KubeletServer, c.KubeletDeps))
	}()
}

// defaultCadvisorInterface holds the overridden default interface
// exists only to allow stubbing integration tests, should always be nil in production
var defaultCadvisorInterface cadvisor.Interface = nil

// SetFakeCadvisorInterfaceForIntegrationTest sets a fake cadvisor implementation to allow the node to run in integration tests
func SetFakeCadvisorInterfaceForIntegrationTest() {
	defaultCadvisorInterface = &cadvisortesting.Fake{}
}

// defaultContainerManagerInterface holds the overridden default interface
// exists only to allow stubbing integration tests, should always be nil in production
var defaultContainerManagerInterface cm.ContainerManager = nil

// SetFakeContainerManagerInterfaceForIntegrationTest sets a fake container manager implementation to allow the node to run in integration tests
func SetFakeContainerManagerInterfaceForIntegrationTest() {
	defaultContainerManagerInterface = cm.NewStubContainerManager()
}

// TODO: more generic location
func includesServicePort(ports []kapiv1.ServicePort, port int, portName string) bool {
	for _, p := range ports {
		if p.Port == int32(port) && p.Name == portName {
			return true
		}
	}
	return false
}

// TODO: more generic location
func includesEndpointPort(ports []kapiv1.EndpointPort, port int) bool {
	for _, p := range ports {
		if p.Port == int32(port) {
			return true
		}
	}
	return false
}

// TODO: more generic location
func firstEndpointIP(endpoints *kapiv1.Endpoints, port int) (string, bool) {
	for _, s := range endpoints.Subsets {
		if !includesEndpointPort(s.Ports, port) {
			continue
		}
		for _, a := range s.Addresses {
			return a.IP, true
		}
	}
	return "", false
}

// TODO: more generic location
func firstEndpointIPWithNamedPort(endpoints *kapiv1.Endpoints, port int, portName string) (string, bool) {
	for _, s := range endpoints.Subsets {
		if !includesNamedEndpointPort(s.Ports, port, portName) {
			continue
		}
		for _, a := range s.Addresses {
			return a.IP, true
		}
	}
	return "", false
}

// TODO: more generic location
func includesNamedEndpointPort(ports []kapiv1.EndpointPort, port int, portName string) bool {
	for _, p := range ports {
		if p.Port == int32(port) && p.Name == portName {
			return true
		}
	}
	return false
}
<commit_msg>Update minimum docker version required for the node<commit_after>package node

import (
	"errors"
	"fmt"
	"net"
	"net/url"
	"os"
	"path/filepath"
	"time"

	dockertypes "github.com/docker/engine-api/types"
	dockerclient "github.com/fsouza/go-dockerclient"
	"github.com/golang/glog"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kubeletapp "k8s.io/kubernetes/cmd/kubelet/app"
	kapiv1 "k8s.io/kubernetes/pkg/api/v1"
	"k8s.io/kubernetes/pkg/kubelet/cadvisor"
	cadvisortesting "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing"
	"k8s.io/kubernetes/pkg/kubelet/cm"
	dockertools "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
	"k8s.io/kubernetes/pkg/volume"

	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	cmdutil "github.com/openshift/origin/pkg/cmd/util"
	dockerutil "github.com/openshift/origin/pkg/cmd/util/docker"
	"github.com/openshift/origin/pkg/volume/emptydir"
)

const minimumDockerAPIVersionWithPullByID = "1.22"

// EnsureKubeletAccess performs a number of test operations that the Kubelet requires to properly function.
// All errors here are fatal.
func (c *NodeConfig) EnsureKubeletAccess() {
	if _, err := os.Stat("/var/lib/docker"); os.IsPermission(err) {
		c.HandleDockerError("Unable to view the /var/lib/docker directory - are you running as root?")
	}
	if c.Containerized {
		if _, err := os.Stat("/rootfs"); os.IsPermission(err) || os.IsNotExist(err) {
			glog.Fatal("error: Running in containerized mode, but cannot find the /rootfs directory - be sure to mount the host filesystem at /rootfs (read-only) in the container.")
		}
		if !sameFileStat(true, "/rootfs/sys", "/sys") {
			glog.Fatal("error: Running in containerized mode, but the /sys directory in the container does not appear to match the host /sys directory - be sure to mount /sys into the container.")
		}
		if !sameFileStat(true, "/rootfs/var/run", "/var/run") {
			glog.Fatal("error: Running in containerized mode, but the /var/run directory in the container does not appear to match the host /var/run directory - be sure to mount /var/run (read-write) into the container.")
		}
	}
	// TODO: check whether we can mount disks (for volumes)
	// TODO: check things cAdvisor needs to properly function
	// TODO: test a cGroup move?
}

// sameFileStat checks whether the provided paths are the same file, to verify that a user has correctly
// mounted those binaries
func sameFileStat(requireMode bool, src, dst string) bool {
	srcStat, err := os.Stat(src)
	if err != nil {
		glog.V(4).Infof("Unable to stat %q: %v", src, err)
		return false
	}
	dstStat, err := os.Stat(dst)
	if err != nil {
		glog.V(4).Infof("Unable to stat %q: %v", dst, err)
		return false
	}
	if requireMode && srcStat.Mode() != dstStat.Mode() {
		glog.V(4).Infof("Mode mismatch between %q (%s) and %q (%s)", src, srcStat.Mode(), dst, dstStat.Mode())
		return false
	}
	if !os.SameFile(srcStat, dstStat) {
		glog.V(4).Infof("inode and device mismatch between %q (%s) and %q (%s)", src, srcStat, dst, dstStat)
		return false
	}
	return true
}

// EnsureDocker attempts to connect to the Docker daemon defined by the helper,
// and if it is unable to it will print a warning.
func (c *NodeConfig) EnsureDocker(docker *dockerutil.Helper) {
	if c.KubeletServer.ContainerRuntime != "docker" {
		return
	}
	dockerClient, dockerAddr, err := docker.GetKubeClient(c.KubeletServer.RuntimeRequestTimeout.Duration, c.KubeletServer.ImagePullProgressDeadline.Duration)
	if err != nil {
		c.HandleDockerError(fmt.Sprintf("Unable to create a Docker client for %s - Docker must be installed and running to start containers.\n%v", dockerAddr, err))
		return
	}
	if url, err := url.Parse(dockerAddr); err == nil && url.Scheme == "unix" && len(url.Path) > 0 {
		s, err := os.Stat(url.Path)
		switch {
		case os.IsNotExist(err):
			c.HandleDockerError(fmt.Sprintf("No Docker socket found at %s. Have you started the Docker daemon?", url.Path))
			return
		case os.IsPermission(err):
			c.HandleDockerError(fmt.Sprintf("You do not have permission to connect to the Docker daemon (via %s). This process requires running as the root user.", url.Path))
			return
		case err == nil && s.IsDir():
			c.HandleDockerError(fmt.Sprintf("The Docker socket at %s is a directory instead of a unix socket - check that you have configured your connection to the Docker daemon properly.", url.Path))
			return
		}
	}
	if err := dockerClient.Ping(); err != nil {
		c.HandleDockerError(fmt.Sprintf("Docker could not be reached at %s.  Docker must be installed and running to start containers.\n%v", dockerAddr, err))
		return
	}

	glog.Infof("Connecting to Docker at %s", dockerAddr)

	version, err := dockerClient.Version()
	if err != nil {
		c.HandleDockerError(fmt.Sprintf("Unable to check for Docker server version.\n%v", err))
		return
	}

	serverVersion, err := dockerclient.NewAPIVersion(version.APIVersion)
	if err != nil {
		c.HandleDockerError(fmt.Sprintf("Unable to determine Docker server version from %q.\n%v", version.APIVersion, err))
		return
	}

	minimumPullByIDVersion, err := dockerclient.NewAPIVersion(minimumDockerAPIVersionWithPullByID)
	if err != nil {
		c.HandleDockerError(fmt.Sprintf("Unable to check for Docker server version.\n%v", err))
		return
	}

	if serverVersion.LessThan(minimumPullByIDVersion) {
		c.HandleDockerError(fmt.Sprintf("Docker 1.6 or later (server API version %s or later) required.", minimumDockerAPIVersionWithPullByID))
		return
	}

	c.DockerClient = dockerClient
}

// HandleDockerError handles an an error from the docker daemon
func (c *NodeConfig) HandleDockerError(message string) {
	if !c.AllowDisabledDocker {
		glog.Fatalf("error: %s", message)
	}
	glog.Errorf("WARNING: %s", message)
	c.DockerClient = &dockertools.FakeDockerClient{
		VersionInfo: dockertypes.Version{
			APIVersion: minimumDockerAPIVersionWithPullByID,
			Version:    "1.13",
		},
		Information: dockertypes.Info{
			CgroupDriver: "systemd",
		},
	}
}

// EnsureVolumeDir attempts to convert the provided volume directory argument to
// an absolute path and create the directory if it does not exist. Will exit if
// an error is encountered.
func (c *NodeConfig) EnsureVolumeDir() {
	if volumeDir, err := c.initializeVolumeDir(c.VolumeDir); err != nil {
		glog.Fatal(err)
	} else {
		c.VolumeDir = volumeDir
	}
}

func (c *NodeConfig) initializeVolumeDir(path string) (string, error) {
	rootDirectory, err := filepath.Abs(path)
	if err != nil {
		return "", fmt.Errorf("Error converting volume directory to an absolute path: %v", err)
	}

	if _, err := os.Stat(rootDirectory); os.IsNotExist(err) {
		if err := os.MkdirAll(rootDirectory, 0750); err != nil {
			return "", fmt.Errorf("Couldn't create kubelet volume root directory '%s': %s", rootDirectory, err)
		}
	}
	return rootDirectory, nil
}

// EnsureLocalQuota checks if the node config specifies a local storage
// perFSGroup quota, and if so will test that the volumeDirectory is on a
// filesystem suitable for quota enforcement. If checks pass the k8s emptyDir
// volume plugin will be replaced with a wrapper version which adds quota
// functionality.
func (c *NodeConfig) EnsureLocalQuota(nodeConfig configapi.NodeConfig) {
	if nodeConfig.VolumeConfig.LocalQuota.PerFSGroup == nil {
		return
	}
	glog.V(4).Info("Replacing empty-dir volume plugin with quota wrapper")
	wrappedEmptyDirPlugin := false

	quotaApplicator, err := emptydir.NewQuotaApplicator(nodeConfig.VolumeDirectory)
	if err != nil {
		glog.Fatalf("Could not set up local quota, %s", err)
	}

	// Create a volume spec with emptyDir we can use to search for the
	// emptyDir plugin with CanSupport:
	emptyDirSpec := &volume.Spec{
		Volume: &kapiv1.Volume{
			VolumeSource: kapiv1.VolumeSource{
				EmptyDir: &kapiv1.EmptyDirVolumeSource{},
			},
		},
	}

	for idx, plugin := range c.KubeletDeps.VolumePlugins {
		// Can't really do type checking or use a constant here as they are not exported:
		if plugin.CanSupport(emptyDirSpec) {
			wrapper := emptydir.EmptyDirQuotaPlugin{
				VolumePlugin:    plugin,
				Quota:           *nodeConfig.VolumeConfig.LocalQuota.PerFSGroup,
				QuotaApplicator: quotaApplicator,
			}
			c.KubeletDeps.VolumePlugins[idx] = &wrapper
			wrappedEmptyDirPlugin = true
		}
	}
	// Because we can't look for the k8s emptyDir plugin by any means that would
	// survive a refactor, error out if we couldn't find it:
	if !wrappedEmptyDirPlugin {
		glog.Fatal(errors.New("No plugin handling EmptyDir was found, unable to apply local quotas"))
	}
}

// RunKubelet starts the Kubelet.
func (c *NodeConfig) RunKubelet() {
	var clusterDNS net.IP
	if len(c.KubeletServer.ClusterDNS) == 0 {
		if service, err := c.KubeletDeps.KubeClient.Core().Services(metav1.NamespaceDefault).Get("kubernetes", metav1.GetOptions{}); err == nil {
			if includesServicePort(service.Spec.Ports, 53, "dns") {
				// Use master service if service includes "dns" port 53.
				clusterDNS = net.ParseIP(service.Spec.ClusterIP)
			}
		}
	}
	if clusterDNS == nil {
		if endpoint, err := c.KubeletDeps.KubeClient.Core().Endpoints(metav1.NamespaceDefault).Get("kubernetes", metav1.GetOptions{}); err == nil {
			if endpointIP, ok := firstEndpointIPWithNamedPort(endpoint, 53, "dns"); ok {
				// Use first endpoint if endpoint includes "dns" port 53.
				clusterDNS = net.ParseIP(endpointIP)
			} else if endpointIP, ok := firstEndpointIP(endpoint, 53); ok {
				// Test and use first endpoint if endpoint includes any port 53.
				if err := cmdutil.WaitForSuccessfulDial(false, "tcp", fmt.Sprintf("%s:%d", endpointIP, 53), 50*time.Millisecond, 0, 2); err == nil {
					clusterDNS = net.ParseIP(endpointIP)
				}
			}
		}
	}
	if clusterDNS != nil && !clusterDNS.IsUnspecified() {
		c.KubeletServer.ClusterDNS = []string{clusterDNS.String()}
	}

	// only set when ContainerRuntime == "docker"
	c.KubeletDeps.DockerClient = c.DockerClient
	// updated by NodeConfig.EnsureVolumeDir
	c.KubeletServer.RootDirectory = c.VolumeDir

	// hook for overriding the cadvisor interface for integration tests
	c.KubeletDeps.CAdvisorInterface = defaultCadvisorInterface
	// hook for overriding the container manager interface for integration tests
	c.KubeletDeps.ContainerManager = defaultContainerManagerInterface

	go func() {
		glog.Fatal(kubeletapp.Run(c.KubeletServer, c.KubeletDeps))
	}()
}

// defaultCadvisorInterface holds the overridden default interface
// exists only to allow stubbing integration tests, should always be nil in production
var defaultCadvisorInterface cadvisor.Interface = nil

// SetFakeCadvisorInterfaceForIntegrationTest sets a fake cadvisor implementation to allow the node to run in integration tests
func SetFakeCadvisorInterfaceForIntegrationTest() {
	defaultCadvisorInterface = &cadvisortesting.Fake{}
}

// defaultContainerManagerInterface holds the overridden default interface
// exists only to allow stubbing integration tests, should always be nil in production
var defaultContainerManagerInterface cm.ContainerManager = nil

// SetFakeContainerManagerInterfaceForIntegrationTest sets a fake container manager implementation to allow the node to run in integration tests
func SetFakeContainerManagerInterfaceForIntegrationTest() {
	defaultContainerManagerInterface = cm.NewStubContainerManager()
}

// TODO: more generic location
func includesServicePort(ports []kapiv1.ServicePort, port int, portName string) bool {
	for _, p := range ports {
		if p.Port == int32(port) && p.Name == portName {
			return true
		}
	}
	return false
}

// TODO: more generic location
func includesEndpointPort(ports []kapiv1.EndpointPort, port int) bool {
	for _, p := range ports {
		if p.Port == int32(port) {
			return true
		}
	}
	return false
}

// TODO: more generic location
func firstEndpointIP(endpoints *kapiv1.Endpoints, port int) (string, bool) {
	for _, s := range endpoints.Subsets {
		if !includesEndpointPort(s.Ports, port) {
			continue
		}
		for _, a := range s.Addresses {
			return a.IP, true
		}
	}
	return "", false
}

// TODO: more generic location
func firstEndpointIPWithNamedPort(endpoints *kapiv1.Endpoints, port int, portName string) (string, bool) {
	for _, s := range endpoints.Subsets {
		if !includesNamedEndpointPort(s.Ports, port, portName) {
			continue
		}
		for _, a := range s.Addresses {
			return a.IP, true
		}
	}
	return "", false
}

// TODO: more generic location
func includesNamedEndpointPort(ports []kapiv1.EndpointPort, port int, portName string) bool {
	for _, p := range ports {
		if p.Port == int32(port) && p.Name == portName {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package controller

import (
	"context"
	"time"

	"github.com/ash2k/stager"
	"github.com/atlassian/smith/pkg/store"
	"github.com/pkg/errors"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
)

const (
	// Work queue deduplicates scheduled keys. This is the period it waits for duplicate keys before letting the work
	// to be dequeued.
	workDeduplicationPeriod = 50 * time.Millisecond
)

type Generic struct {
	logger      *zap.Logger
	queue       workQueue
	workers     int
	multi       *store.MultiBasic
	Controllers map[schema.GroupVersionKind]ControllerHolder
	Informers   map[schema.GroupVersionKind]cache.SharedIndexInformer
}

func NewGeneric(config *Config, logger *zap.Logger, queue workqueue.RateLimitingInterface, workers int, constructors ...Constructor) (*Generic, error) {
	controllers := make(map[schema.GroupVersionKind]Interface, len(constructors))
	holders := make(map[schema.GroupVersionKind]ControllerHolder, len(constructors))
	informers := make(map[schema.GroupVersionKind]cache.SharedIndexInformer)
	multi := store.NewMultiBasic()
	wq := workQueue{
		queue: queue,
		workDeduplicationPeriod: workDeduplicationPeriod,
	}
	for _, constr := range constructors {
		descr := constr.Describe()
		if _, ok := controllers[descr.Gvk]; ok {
			return nil, errors.Errorf("duplicate controller for GVK %s", descr.Gvk)
		}
		readyForWork := make(chan struct{})
		queueGvk := wq.NewQueueForGvk(descr.Gvk)
		iface, err := constr.New(config, &Context{
			ReadyForWork: func() {
				close(readyForWork)
			},
			Informers:   informers,
			Controllers: controllers,
			WorkQueue:   queueGvk,
		})
		if err != nil {
			return nil, errors.Wrapf(err, "failed to construct controller for GVK %s", descr.Gvk)
		}
		inf, ok := informers[descr.Gvk]
		if !ok {
			return nil, errors.Errorf("controller for GVK %s should have registered an informer for that GVK", descr.Gvk)
		}
		err = multi.AddInformer(descr.Gvk, inf)
		if err != nil {
			return nil, errors.Errorf("failed to register informer for GVK %s in multistore", descr.Gvk)
		}
		inf.AddEventHandler(&handler{
			logger:       logger,
			queue:        queueGvk,
			zapNameField: descr.ZapNameField,
		})
		controllers[descr.Gvk] = iface
		holders[descr.Gvk] = ControllerHolder{
			Cntrlr:       iface,
			ZapNameField: descr.ZapNameField,
			ReadyForWork: readyForWork,
		}
	}
	return &Generic{
		logger:      logger,
		queue:       wq,
		workers:     workers,
		multi:       multi,
		Controllers: holders,
		Informers:   informers,
	}, nil
}

func (g *Generic) Run(ctx context.Context) {
	// Stager will perform ordered, graceful shutdown
	stgr := stager.New()
	defer stgr.Shutdown()

	// Stage: start all informers then wait on them
	stage := stgr.NextStage()
	for _, inf := range g.Informers {
		stage.StartWithChannel(inf.Run)
	}
	g.logger.Info("Waiting for informers to sync")
	for _, inf := range g.Informers {
		if !cache.WaitForCacheSync(ctx.Done(), inf.HasSynced) {
			return
		}
	}
	g.logger.Info("Informers synced")

	// Stage: start all controllers then wait for them to signal ready for work
	stage = stgr.NextStage()
	for _, c := range g.Controllers {
		stage.StartWithContext(c.Cntrlr.Run)
	}
	for gvk, c := range g.Controllers {
		select {
		case <-ctx.Done():
			g.logger.Sugar().Infof("Was waiting for the controller for %s to become ready for processing", gvk)
			return
		case <-c.ReadyForWork:
		}
	}

	// Stage: start workers
	stage = stgr.NextStage()
	defer g.queue.ShutDown()
	for i := 0; i < g.workers; i++ {
		stage.Start(g.worker)
	}

	<-ctx.Done()
}

type ControllerHolder struct {
	Cntrlr       Interface
	ZapNameField ZapNameField
	ReadyForWork <-chan struct{}
}
<commit_msg>Shut the queue down even if workers were not started<commit_after>package controller

import (
	"context"
	"time"

	"github.com/ash2k/stager"
	"github.com/atlassian/smith/pkg/store"
	"github.com/pkg/errors"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
)

const (
	// Work queue deduplicates scheduled keys. This is the period it waits for duplicate keys before letting the work
	// to be dequeued.
	workDeduplicationPeriod = 50 * time.Millisecond
)

type Generic struct {
	logger      *zap.Logger
	queue       workQueue
	workers     int
	multi       *store.MultiBasic
	Controllers map[schema.GroupVersionKind]ControllerHolder
	Informers   map[schema.GroupVersionKind]cache.SharedIndexInformer
}

func NewGeneric(config *Config, logger *zap.Logger, queue workqueue.RateLimitingInterface, workers int, constructors ...Constructor) (*Generic, error) {
	controllers := make(map[schema.GroupVersionKind]Interface, len(constructors))
	holders := make(map[schema.GroupVersionKind]ControllerHolder, len(constructors))
	informers := make(map[schema.GroupVersionKind]cache.SharedIndexInformer)
	multi := store.NewMultiBasic()
	wq := workQueue{
		queue: queue,
		workDeduplicationPeriod: workDeduplicationPeriod,
	}
	for _, constr := range constructors {
		descr := constr.Describe()
		if _, ok := controllers[descr.Gvk]; ok {
			return nil, errors.Errorf("duplicate controller for GVK %s", descr.Gvk)
		}
		readyForWork := make(chan struct{})
		queueGvk := wq.NewQueueForGvk(descr.Gvk)
		iface, err := constr.New(config, &Context{
			ReadyForWork: func() {
				close(readyForWork)
			},
			Informers:   informers,
			Controllers: controllers,
			WorkQueue:   queueGvk,
		})
		if err != nil {
			return nil, errors.Wrapf(err, "failed to construct controller for GVK %s", descr.Gvk)
		}
		inf, ok := informers[descr.Gvk]
		if !ok {
			return nil, errors.Errorf("controller for GVK %s should have registered an informer for that GVK", descr.Gvk)
		}
		err = multi.AddInformer(descr.Gvk, inf)
		if err != nil {
			return nil, errors.Errorf("failed to register informer for GVK %s in multistore", descr.Gvk)
		}
		inf.AddEventHandler(&handler{
			logger:       logger,
			queue:        queueGvk,
			zapNameField: descr.ZapNameField,
		})
		controllers[descr.Gvk] = iface
		holders[descr.Gvk] = ControllerHolder{
			Cntrlr:       iface,
			ZapNameField: descr.ZapNameField,
			ReadyForWork: readyForWork,
		}
	}
	return &Generic{
		logger:      logger,
		queue:       wq,
		workers:     workers,
		multi:       multi,
		Controllers: holders,
		Informers:   informers,
	}, nil
}

func (g *Generic) Run(ctx context.Context) {
	// Stager will perform ordered, graceful shutdown
	stgr := stager.New()
	defer stgr.Shutdown()
	defer g.queue.ShutDown()

	// Stage: start all informers then wait on them
	stage := stgr.NextStage()
	for _, inf := range g.Informers {
		stage.StartWithChannel(inf.Run)
	}
	g.logger.Info("Waiting for informers to sync")
	for _, inf := range g.Informers {
		if !cache.WaitForCacheSync(ctx.Done(), inf.HasSynced) {
			return
		}
	}
	g.logger.Info("Informers synced")

	// Stage: start all controllers then wait for them to signal ready for work
	stage = stgr.NextStage()
	for _, c := range g.Controllers {
		stage.StartWithContext(c.Cntrlr.Run)
	}
	for gvk, c := range g.Controllers {
		select {
		case <-ctx.Done():
			g.logger.Sugar().Infof("Was waiting for the controller for %s to become ready for processing", gvk)
			return
		case <-c.ReadyForWork:
		}
	}

	// Stage: start workers
	stage = stgr.NextStage()
	for i := 0; i < g.workers; i++ {
		stage.Start(g.worker)
	}

	<-ctx.Done()
}

type ControllerHolder struct {
	Cntrlr       Interface
	ZapNameField ZapNameField
	ReadyForWork <-chan struct{}
}
<|endoftext|>


<commit_before>package servicing

import (
	"time"

	"github.com/pkg/errors"

	"github.com/go-kit/kit/log"
	"k8s.io/client-go/tools/record"

	v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	"github.com/sapcc/kubernikus/pkg/controller/base"
	"github.com/sapcc/kubernikus/pkg/controller/config"
)

const (
	// AnnotationServicingSafeguard must be set to enable servicing
	AnnotationServicingSafeguard = "kubernikus.cloud.sap/servicing"
)

var (
	// Now is a poor-man's facility to change time during testing
	Now = time.Now
)

// Controller periodically checks for nodes requiting updates or upgrades
//
// This controller handles node upgrades when the Kubernetes or CoreOS versions
// are changed. It gracefully drains nodes before performing any action.
//
// For Kubernetes upgrades the strategy is to replace the fleet by terminating
// the nodes. CoreOS updates are handled by a soft reboot.
//
// In order to allow the payload to settle only a single node per cluster is
// processed at a time. Between updates there's a 1h grace period.
//
// In case any node in the cluster is unhealthy the upgrades are skipped. This
// is to safeguard against failed upgrades destroying the universe.
//
// For rollout and testing purposed the node upgrades are disabled by default.
// They can manually be enabled by setting the node annotaion:
//
//    kubernikus.cloud.sap/servicing=true
//
type Controller struct {
	Logger     log.Logger
	Reconciler ReconcilerFactory
}

// NewController is a helper to create a Servicing Controller instance
func NewController(threadiness int, factories config.Factories, clients config.Clients, recorder record.EventRecorder, logger log.Logger) base.Controller {
	logger = log.With(logger, "controller", "servicing")

	var controller base.Reconciler
	controller = &Controller{
		Logger:     logger,
		Reconciler: NewKlusterReconcilerFactory(logger, recorder, factories, clients),
	}

	RegisterServicingNodesCollector(logger, factories)

	return base.NewController(threadiness, factories, controller, logger, nil, "servicing")
}

// Reconcile checks a kluster for node updates
func (d *Controller) Reconcile(k *v1.Kluster) (requeue bool, err error) {
	reconciler, err := d.Reconciler.Make(k)
	if err != nil {
		d.Logger.Log("msg", "skippig upgrades. Internal server error.", "err", err)
		return true, errors.Wrap(err, "Couldn't make Servicing Reconciler.")
	}

	return false, reconciler.Do()
}
<commit_msg>Have servicing controller consider running clusters only<commit_after>package servicing

import (
	"time"

	"github.com/pkg/errors"

	"github.com/go-kit/kit/log"
	"k8s.io/client-go/tools/record"

	"github.com/sapcc/kubernikus/pkg/api/models"
	v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	"github.com/sapcc/kubernikus/pkg/controller/base"
	"github.com/sapcc/kubernikus/pkg/controller/config"
)

const (
	// AnnotationServicingSafeguard must be set to enable servicing
	AnnotationServicingSafeguard = "kubernikus.cloud.sap/servicing"
)

var (
	// Now is a poor-man's facility to change time during testing
	Now = time.Now
)

// Controller periodically checks for nodes requiting updates or upgrades
//
// This controller handles node upgrades when the Kubernetes or CoreOS versions
// are changed. It gracefully drains nodes before performing any action.
//
// For Kubernetes upgrades the strategy is to replace the fleet by terminating
// the nodes. CoreOS updates are handled by a soft reboot.
//
// In order to allow the payload to settle only a single node per cluster is
// processed at a time. Between updates there's a 1h grace period.
//
// In case any node in the cluster is unhealthy the upgrades are skipped. This
// is to safeguard against failed upgrades destroying the universe.
//
// For rollout and testing purposed the node upgrades are disabled by default.
// They can manually be enabled by setting the node annotaion:
//
//    kubernikus.cloud.sap/servicing=true
//
type Controller struct {
	Logger     log.Logger
	Reconciler ReconcilerFactory
}

// NewController is a helper to create a Servicing Controller instance
func NewController(threadiness int, factories config.Factories, clients config.Clients, recorder record.EventRecorder, logger log.Logger) base.Controller {
	logger = log.With(logger, "controller", "servicing")

	var controller base.Reconciler
	controller = &Controller{
		Logger:     logger,
		Reconciler: NewKlusterReconcilerFactory(logger, recorder, factories, clients),
	}

	RegisterServicingNodesCollector(logger, factories)

	return base.NewController(threadiness, factories, controller, logger, nil, "servicing")
}

// Reconcile checks a kluster for node updates
func (d *Controller) Reconcile(k *v1.Kluster) (requeue bool, err error) {
	//Skip klusters not in state running
	if k.Status.Phase != models.KlusterPhaseRunning {
		return false, nil
	}
	reconciler, err := d.Reconciler.Make(k)
	if err != nil {
		d.Logger.Log("msg", "skippig upgrades. Internal server error.", "err", err)
		return true, errors.Wrap(err, "Couldn't make Servicing Reconciler.")
	}

	return false, reconciler.Do()
}
<|endoftext|>


<commit_before>package main_test

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"syscall"

	"github.com/cloudfoundry-incubator/warden-linux/ptyutil"
	"github.com/kr/pty"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

var _ = Describe("Iodaemon", func() {
	It("can exhaust a single link's stdin", func() {
		spawnS, err := gexec.Start(exec.Command(
			iodaemon,
			"spawn",
			socketPath,
			"bash", "-c", "cat <&0; exit 42",
		), GinkgoWriter, GinkgoWriter)
		Ω(err).ShouldNot(HaveOccurred())

		defer spawnS.Kill()

		Eventually(spawnS).Should(gbytes.Say("ready\n"))
		Consistently(spawnS).ShouldNot(gbytes.Say("pid:"))

		link := exec.Command(iodaemon, "link", socketPath)
		link.Stdin = bytes.NewBufferString("hello\ngoodbye")

		linkS, err := gexec.Start(link, GinkgoWriter, GinkgoWriter)
		Ω(err).ShouldNot(HaveOccurred())

		Eventually(spawnS).Should(gbytes.Say("pid:"))

		Eventually(linkS).Should(gbytes.Say("hello\ngoodbye"))
		Eventually(linkS).Should(gexec.Exit(42))
	})

	It("can read some stdin, have a link break, and exhaust more stdin", func() {
		spawnS, err := gexec.Start(exec.Command(
			iodaemon,
			"spawn",
			socketPath,
			"bash", "-c", "cat <&0; exit 42",
		), GinkgoWriter, GinkgoWriter)
		Ω(err).ShouldNot(HaveOccurred())

		defer spawnS.Kill()

		Eventually(spawnS).Should(gbytes.Say("ready\n"))
		Consistently(spawnS).ShouldNot(gbytes.Say("pid:"))

		r, w, err := os.Pipe()
		Ω(err).ShouldNot(HaveOccurred())

		link := exec.Command(iodaemon, "link", socketPath)
		link.Stdin = r

		linkS, err := gexec.Start(link, GinkgoWriter, GinkgoWriter)
		Ω(err).ShouldNot(HaveOccurred())

		_, err = fmt.Fprintf(w, "hello\n")
		Ω(err).ShouldNot(HaveOccurred())

		Eventually(spawnS).Should(gbytes.Say("pid:"))

		Eventually(linkS).Should(gbytes.Say("hello\n"))
		Consistently(linkS).ShouldNot(gexec.Exit(42))

		linkS.Terminate().Wait()

		link = exec.Command(iodaemon, "link", socketPath)
		link.Stdin = r

		linkS, err = gexec.Start(link, GinkgoWriter, GinkgoWriter)
		Ω(err).ShouldNot(HaveOccurred())

		_, err = fmt.Fprintf(w, "goodbye")
		Ω(err).ShouldNot(HaveOccurred())

		Eventually(linkS).Should(gbytes.Say("goodbye"))

		err = w.Close()
		Ω(err).ShouldNot(HaveOccurred())

		Eventually(linkS).Should(gexec.Exit(42))
	})

	Describe("spawning with -tty", func() {
		It("transports stdin, stdout, and stderr", func() {
			spawnS, err := gexec.Start(exec.Command(
				iodaemon,
				"-tty",
				"spawn",
				socketPath,
				"bash", "-c", "read foo; echo hi $foo; echo hi err >&2",
			), GinkgoWriter, GinkgoWriter)
			Ω(err).ShouldNot(HaveOccurred())

			defer spawnS.Kill()

			Eventually(spawnS).Should(gbytes.Say("ready\n"))

			pty, tty, err := pty.Open()
			Ω(err).ShouldNot(HaveOccurred())

			link := exec.Command(iodaemon, "-tty", "link", socketPath)
			link.Stdin = tty

			linkS, err := gexec.Start(link, GinkgoWriter, GinkgoWriter)
			Ω(err).ShouldNot(HaveOccurred())

			_, err = pty.WriteString("out\n")
			Ω(err).ShouldNot(HaveOccurred())

			Eventually(linkS).Should(gbytes.Say("hi out\r\n"))
			Eventually(linkS).Should(gbytes.Say("hi err\r\n"))

			Eventually(linkS).Should(gexec.Exit(0))
		})

		It("starts with an 80x24 tty, and can be resized", func() {
			spawnS, err := gexec.Start(exec.Command(
				iodaemon,
				"-tty",
				"spawn",
				socketPath,
				winsizeReporter,
			), GinkgoWriter, GinkgoWriter)
			Ω(err).ShouldNot(HaveOccurred())

			defer spawnS.Kill()

			Eventually(spawnS).Should(gbytes.Say("ready\n"))

			pty, tty, err := pty.Open()
			Ω(err).ShouldNot(HaveOccurred())

			link := exec.Command(iodaemon, "-tty", "link", socketPath)
			link.Stdin = tty

			linkS, err := gexec.Start(link, GinkgoWriter, GinkgoWriter)
			Ω(err).ShouldNot(HaveOccurred())

			Eventually(linkS).Should(gbytes.Say("rows: 24, cols: 80\r\n"))

			err = ptyutil.SetWinSize(pty, 123, 456)
			Ω(err).ShouldNot(HaveOccurred())

			err = link.Process.Signal(syscall.SIGWINCH)
			Ω(err).ShouldNot(HaveOccurred())

			Eventually(linkS).Should(gbytes.Say("rows: 456, cols: 123\r\n"))
			Eventually(linkS).Should(gexec.Exit(0))
		})
	})
})
<commit_msg>don't exercise flaky tty code path in test<commit_after>package main_test

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"syscall"

	"github.com/cloudfoundry-incubator/warden-linux/ptyutil"
	"github.com/kr/pty"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

var _ = Describe("Iodaemon", func() {
	It("can exhaust a single link's stdin", func() {
		spawnS, err := gexec.Start(exec.Command(
			iodaemon,
			"spawn",
			socketPath,
			"bash", "-c", "cat <&0; exit 42",
		), GinkgoWriter, GinkgoWriter)
		Ω(err).ShouldNot(HaveOccurred())

		defer spawnS.Kill()

		Eventually(spawnS).Should(gbytes.Say("ready\n"))
		Consistently(spawnS).ShouldNot(gbytes.Say("pid:"))

		link := exec.Command(iodaemon, "link", socketPath)
		link.Stdin = bytes.NewBufferString("hello\ngoodbye")

		linkS, err := gexec.Start(link, GinkgoWriter, GinkgoWriter)
		Ω(err).ShouldNot(HaveOccurred())

		Eventually(spawnS).Should(gbytes.Say("pid:"))

		Eventually(linkS).Should(gbytes.Say("hello\ngoodbye"))
		Eventually(linkS).Should(gexec.Exit(42))
	})

	It("can read some stdin, have a link break, and exhaust more stdin", func() {
		spawnS, err := gexec.Start(exec.Command(
			iodaemon,
			"spawn",
			socketPath,
			"bash", "-c", "cat <&0; exit 42",
		), GinkgoWriter, GinkgoWriter)
		Ω(err).ShouldNot(HaveOccurred())

		defer spawnS.Kill()

		Eventually(spawnS).Should(gbytes.Say("ready\n"))
		Consistently(spawnS).ShouldNot(gbytes.Say("pid:"))

		r, w, err := os.Pipe()
		Ω(err).ShouldNot(HaveOccurred())

		link := exec.Command(iodaemon, "link", socketPath)
		link.Stdin = r

		linkS, err := gexec.Start(link, GinkgoWriter, GinkgoWriter)
		Ω(err).ShouldNot(HaveOccurred())

		_, err = fmt.Fprintf(w, "hello\n")
		Ω(err).ShouldNot(HaveOccurred())

		Eventually(spawnS).Should(gbytes.Say("pid:"))

		Eventually(linkS).Should(gbytes.Say("hello\n"))
		Consistently(linkS).ShouldNot(gexec.Exit(42))

		linkS.Terminate().Wait()

		link = exec.Command(iodaemon, "link", socketPath)
		link.Stdin = r

		linkS, err = gexec.Start(link, GinkgoWriter, GinkgoWriter)
		Ω(err).ShouldNot(HaveOccurred())

		_, err = fmt.Fprintf(w, "goodbye")
		Ω(err).ShouldNot(HaveOccurred())

		Eventually(linkS).Should(gbytes.Say("goodbye"))

		err = w.Close()
		Ω(err).ShouldNot(HaveOccurred())

		Eventually(linkS).Should(gexec.Exit(42))
	})

	Describe("spawning with -tty", func() {
		It("transports stdin, stdout, and stderr", func() {
			spawnS, err := gexec.Start(exec.Command(
				iodaemon,
				"-tty",
				"spawn",
				socketPath,
				"bash", "-c", "read foo; echo hi $foo; echo hi err >&2",
			), GinkgoWriter, GinkgoWriter)
			Ω(err).ShouldNot(HaveOccurred())

			defer spawnS.Kill()

			Eventually(spawnS).Should(gbytes.Say("ready\n"))

			inR, inW := io.Pipe()
			Ω(err).ShouldNot(HaveOccurred())

			link := exec.Command(iodaemon, "link", socketPath)
			link.Stdin = inR

			linkS, err := gexec.Start(link, GinkgoWriter, GinkgoWriter)
			Ω(err).ShouldNot(HaveOccurred())

			_, err = inW.Write([]byte("out\r\n"))
			Ω(err).ShouldNot(HaveOccurred())

			err = inW.Close()
			Ω(err).ShouldNot(HaveOccurred())

			Eventually(linkS).Should(gbytes.Say("hi out\r\n"))
			Eventually(linkS).Should(gbytes.Say("hi err\r\n"))

			Eventually(linkS).Should(gexec.Exit(0))
		})

		It("starts with an 80x24 tty, and can be resized", func() {
			spawnS, err := gexec.Start(exec.Command(
				iodaemon,
				"-tty",
				"spawn",
				socketPath,
				winsizeReporter,
			), GinkgoWriter, GinkgoWriter)
			Ω(err).ShouldNot(HaveOccurred())

			defer spawnS.Kill()

			Eventually(spawnS).Should(gbytes.Say("ready\n"))

			pty, tty, err := pty.Open()
			Ω(err).ShouldNot(HaveOccurred())

			link := exec.Command(iodaemon, "link", socketPath)
			link.Stdin = tty

			linkS, err := gexec.Start(link, GinkgoWriter, GinkgoWriter)
			Ω(err).ShouldNot(HaveOccurred())

			Eventually(linkS).Should(gbytes.Say("rows: 24, cols: 80\r\n"))

			err = ptyutil.SetWinSize(pty, 123, 456)
			Ω(err).ShouldNot(HaveOccurred())

			err = link.Process.Signal(syscall.SIGWINCH)
			Ω(err).ShouldNot(HaveOccurred())

			Eventually(linkS).Should(gbytes.Say("rows: 456, cols: 123\r\n"))
			Eventually(linkS).Should(gexec.Exit(0))
		})
	})
})
<|endoftext|>


<commit_before>package docker_registry

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/name"
	"github.com/google/go-containerregistry/pkg/v1/remote/transport"

	"github.com/werf/logboek"

	"github.com/werf/werf/pkg/image"
)

const GitLabRegistryImplementationName = "gitlab"

var (
	gitlabPatterns = []string{`^gitlab\.com`}

	fullScopeFunc = func(ref name.Reference) []string {
		completeScopeFunc := []string{ref.Scope("push"), ref.Scope("pull"), ref.Scope("delete")}
		return completeScopeFunc
	}

	universalScopeFunc = func(ref name.Reference) []string {
		return []string{ref.Scope("*")}
	}
)

type gitLabRegistry struct {
	*defaultImplementation
	deleteRepoImageFunc func(repoImage *image.Info) error
}

type gitLabRegistryOptions struct {
	defaultImplementationOptions
}

func newGitLabRegistry(options gitLabRegistryOptions) (*gitLabRegistry, error) {
	d, err := newDefaultImplementation(options.defaultImplementationOptions)
	if err != nil {
		return nil, err
	}

	gitLab := &gitLabRegistry{defaultImplementation: d}

	return gitLab, nil
}

func (r *gitLabRegistry) DeleteRepoImage(repoImageList ...*image.Info) error {
	for _, repoImage := range repoImageList {
		if err := r.deleteRepoImage(repoImage); err != nil {
			return err
		}
	}

	return nil
}

func (r *gitLabRegistry) deleteRepoImage(repoImage *image.Info) error {
	deleteRepoImageFunc := r.deleteRepoImageFunc
	if deleteRepoImageFunc != nil {
		return deleteRepoImageFunc(repoImage)
	} else {
		var err error
		for _, deleteFunc := range []func(repoImage *image.Info) error{
			r.deleteRepoImageTagWithFullScope,
			r.deleteRepoImageTagWithUniversalScope,
			r.deleteRepoImageWithFullScope,
			r.deleteRepoImageWithUniversalScope,
			r.defaultImplementation.deleteRepoImage,
		} {
			if err = deleteFunc(repoImage); err != nil {
				if strings.Contains(err.Error(), "UNAUTHORIZED") {
					reference := strings.Join([]string{repoImage.Repository, repoImage.Tag}, ":")
					logboek.Debug.LogF("DEBUG: Tag %s deletion failed: %s", reference, err)
					continue
				}

				return err
			}

			r.deleteRepoImageFunc = deleteFunc
			break
		}

		if err != nil {
			return err
		}
	}

	return nil
}

func (r *gitLabRegistry) deleteRepoImageTagWithUniversalScope(repoImage *image.Info) error {
	return r.deleteRepoImageTagWithCustomScope(repoImage, universalScopeFunc)
}

func (r *gitLabRegistry) deleteRepoImageTagWithFullScope(repoImage *image.Info) error {
	return r.deleteRepoImageTagWithCustomScope(repoImage, fullScopeFunc)
}

func (r *gitLabRegistry) deleteRepoImageWithUniversalScope(repoImage *image.Info) error {
	return r.deleteRepoImageWithCustomScope(repoImage, universalScopeFunc)
}

func (r *gitLabRegistry) deleteRepoImageWithFullScope(repoImage *image.Info) error {
	return r.deleteRepoImageWithCustomScope(repoImage, fullScopeFunc)
}

func (r *gitLabRegistry) deleteRepoImageTagWithCustomScope(repoImage *image.Info, scopeFunc func(ref name.Reference) []string) error {
	reference := strings.Join([]string{repoImage.Repository, repoImage.Tag}, ":")
	return r.customDeleteRepoImage("/v2/%s/tags/reference/%s", reference, scopeFunc)
}

func (r *gitLabRegistry) deleteRepoImageWithCustomScope(repoImage *image.Info, scopeFunc func(ref name.Reference) []string) error {
	reference := strings.Join([]string{repoImage.Repository, repoImage.RepoDigest}, "@")
	return r.customDeleteRepoImage("/v2/%s/manifests/%s", reference, scopeFunc)
}

func (r *gitLabRegistry) customDeleteRepoImage(endpointFormat, reference string, scopeFunc func(ref name.Reference) []string) error {
	ref, err := name.ParseReference(reference, r.api.parseReferenceOptions()...)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", reference, err)
	}

	auth, authErr := authn.DefaultKeychain.Resolve(ref.Context().Registry)
	if authErr != nil {
		return fmt.Errorf("getting creds for %q: %v", ref, authErr)
	}

	scope := scopeFunc(ref)
	tr, err := transport.New(ref.Context().Registry, auth, r.api.getHttpTransport(), scope)
	if err != nil {
		return err
	}
	c := &http.Client{Transport: tr}

	u := url.URL{
		Scheme: ref.Context().Registry.Scheme(),
		Host:   ref.Context().RegistryStr(),
		Path:   fmt.Sprintf(endpointFormat, ref.Context().RepositoryStr(), ref.Identifier()),
	}

	req, err := http.NewRequest(http.MethodDelete, u.String(), nil)
	if err != nil {
		return err
	}

	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK, http.StatusAccepted:
		return nil
	default:
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		return fmt.Errorf("unrecognized status code during DELETE: %v; %v", resp.Status, string(b))
	}
}

func (r *gitLabRegistry) String() string {
	return GitLabRegistryImplementationName
}
<commit_msg>[cleanup] GitLab: fix unrecognized status code during DELETE<commit_after>package docker_registry

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/name"
	"github.com/google/go-containerregistry/pkg/v1/remote/transport"

	"github.com/werf/logboek"

	"github.com/werf/werf/pkg/image"
)

const GitLabRegistryImplementationName = "gitlab"

var (
	gitlabPatterns = []string{`^gitlab\.com`}

	fullScopeFunc = func(ref name.Reference) []string {
		completeScopeFunc := []string{ref.Scope("push"), ref.Scope("pull"), ref.Scope("delete")}
		return completeScopeFunc
	}

	universalScopeFunc = func(ref name.Reference) []string {
		return []string{ref.Scope("*")}
	}
)

type gitLabRegistry struct {
	*defaultImplementation
	deleteRepoImageFunc func(repoImage *image.Info) error
}

type gitLabRegistryOptions struct {
	defaultImplementationOptions
}

func newGitLabRegistry(options gitLabRegistryOptions) (*gitLabRegistry, error) {
	d, err := newDefaultImplementation(options.defaultImplementationOptions)
	if err != nil {
		return nil, err
	}

	gitLab := &gitLabRegistry{defaultImplementation: d}

	return gitLab, nil
}

func (r *gitLabRegistry) DeleteRepoImage(repoImageList ...*image.Info) error {
	for _, repoImage := range repoImageList {
		if err := r.deleteRepoImage(repoImage); err != nil {
			return err
		}
	}

	return nil
}

func (r *gitLabRegistry) deleteRepoImage(repoImage *image.Info) error {
	deleteRepoImageFunc := r.deleteRepoImageFunc
	if deleteRepoImageFunc != nil {
		return deleteRepoImageFunc(repoImage)
	}

	// DELETE /v2/<name>/tags/reference/<reference> method is available since the v2.8.0-gitlab
	var err error
	for _, deleteFunc := range []func(repoImage *image.Info) error{
		r.deleteRepoImageTagWithFullScope,
		r.deleteRepoImageTagWithUniversalScope,
	} {
		if err := deleteFunc(repoImage); err != nil {
			reference := strings.Join([]string{repoImage.Repository, repoImage.Tag}, ":")
			if strings.Contains(err.Error(), "404 Not Found; 404 page not found") {
				logboek.Debug.LogF("DEBUG: %s: %s", reference, err)
				break
			} else if strings.Contains(err.Error(), "UNAUTHORIZED") {
				logboek.Debug.LogF("DEBUG: %s: %s", reference, err)
				continue
			}

			return err
		}

		r.deleteRepoImageFunc = deleteRepoImageFunc
		return nil
	}

	for _, deleteFunc := range []func(repoImage *image.Info) error{
		r.deleteRepoImageWithFullScope,
		r.deleteRepoImageWithUniversalScope,
	} {
		if err := deleteFunc(repoImage); err != nil {
			reference := strings.Join([]string{repoImage.Repository, repoImage.Tag}, ":")
			if strings.Contains(err.Error(), "UNAUTHORIZED") {
				logboek.Debug.LogF("DEBUG: %s: %s", reference, err)
				continue
			}

			return err
		}

		r.deleteRepoImageFunc = deleteRepoImageFunc
		return nil
	}

	err = r.defaultImplementation.deleteRepoImage(repoImage)
	if err != nil {
		return err
	}

	r.deleteRepoImageFunc = r.defaultImplementation.deleteRepoImage
	return nil
}

func (r *gitLabRegistry) deleteRepoImageTagWithUniversalScope(repoImage *image.Info) error {
	return r.deleteRepoImageTagWithCustomScope(repoImage, universalScopeFunc)
}

func (r *gitLabRegistry) deleteRepoImageTagWithFullScope(repoImage *image.Info) error {
	return r.deleteRepoImageTagWithCustomScope(repoImage, fullScopeFunc)
}

func (r *gitLabRegistry) deleteRepoImageWithUniversalScope(repoImage *image.Info) error {
	return r.deleteRepoImageWithCustomScope(repoImage, universalScopeFunc)
}

func (r *gitLabRegistry) deleteRepoImageWithFullScope(repoImage *image.Info) error {
	return r.deleteRepoImageWithCustomScope(repoImage, fullScopeFunc)
}

func (r *gitLabRegistry) deleteRepoImageTagWithCustomScope(repoImage *image.Info, scopeFunc func(ref name.Reference) []string) error {
	reference := strings.Join([]string{repoImage.Repository, repoImage.Tag}, ":")
	return r.customDeleteRepoImage("/v2/%s/tags/reference/%s", reference, scopeFunc)
}

func (r *gitLabRegistry) deleteRepoImageWithCustomScope(repoImage *image.Info, scopeFunc func(ref name.Reference) []string) error {
	reference := strings.Join([]string{repoImage.Repository, repoImage.RepoDigest}, "@")
	return r.customDeleteRepoImage("/v2/%s/manifests/%s", reference, scopeFunc)
}

func (r *gitLabRegistry) customDeleteRepoImage(endpointFormat, reference string, scopeFunc func(ref name.Reference) []string) error {
	ref, err := name.ParseReference(reference, r.api.parseReferenceOptions()...)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", reference, err)
	}

	auth, authErr := authn.DefaultKeychain.Resolve(ref.Context().Registry)
	if authErr != nil {
		return fmt.Errorf("getting creds for %q: %v", ref, authErr)
	}

	scope := scopeFunc(ref)
	tr, err := transport.New(ref.Context().Registry, auth, r.api.getHttpTransport(), scope)
	if err != nil {
		return err
	}
	c := &http.Client{Transport: tr}

	u := url.URL{
		Scheme: ref.Context().Registry.Scheme(),
		Host:   ref.Context().RegistryStr(),
		Path:   fmt.Sprintf(endpointFormat, ref.Context().RepositoryStr(), ref.Identifier()),
	}

	req, err := http.NewRequest(http.MethodDelete, u.String(), nil)
	if err != nil {
		return err
	}

	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK, http.StatusAccepted:
		return nil
	default:
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		return fmt.Errorf("unrecognized status code during DELETE: %v; %v", resp.Status, string(b))
	}
}

func (r *gitLabRegistry) String() string {
	return GitLabRegistryImplementationName
}
<|endoftext|>


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

// The srv package provides definitions and functions used to implement
// a 9P2000 file client.
package clnt

import (
	"fmt"
	"log"
	"net"
	"sync"
	"syscall"
	"go9p.googlecode.com/hg/p"
)

// Debug flags
const (
	DbgPrintFcalls  = (1 << iota) // print all 9P messages on stderr
	DbgPrintPackets               // print the raw packets on stderr
	DbgLogFcalls                  // keep the last N 9P messages (can be accessed over http)
	DbgLogPackets                 // keep the last N 9P messages (can be accessed over http)
)

// The Clnt type represents a 9P2000 client. The client is connected to
// a 9P2000 file server and its methods can be used to access and manipulate
// the files exported by the server.
type Clnt struct {
	sync.Mutex
	Finished   bool   // client is no longer connected to server
	Debuglevel int    // =0 don't print anything, >0 print Fcalls, >1 print raw packets
	Msize      uint32 // Maximum size of the 9P messages
	Dotu       bool   // If true, 9P2000.u protocol is spoken
	Root       *Fid   // Fid that points to the rood directory
	Id         string // Used when printing debug messages
	Log        *p.Logger

	conn     net.Conn
	tagpool  *pool
	fidpool  *pool
	reqout   chan *Req
	done     chan bool
	reqfirst *Req
	reqlast  *Req
	err      *p.Error

	reqchan chan *Req
	tchan   chan *p.Fcall

	next, prev *Clnt
}

// A Fid type represents a file on the server. Fids are used for the
// low level methods that correspond directly to the 9P2000 message requests
type Fid struct {
	sync.Mutex
	Clnt   *Clnt // Client the fid belongs to
	Iounit uint32
	p.Qid         // The Qid description for the file
	Mode   uint8  // Open mode (one of p.O* values) (if file is open)
	Fid    uint32 // Fid number
	p.User        // The user the fid belongs to
	walked bool   // true if the fid points to a walked file on the server
}

// The file is similar to the Fid, but is used in the high-level client
// interface.
type File struct {
	fid    *Fid
	offset uint64
}

type pool struct {
	sync.Mutex
	need  int
	nchan chan uint32
	maxid uint32
	imap  []byte
}

type Req struct {
	sync.Mutex
	Clnt       *Clnt
	Tc         *p.Fcall
	Rc         *p.Fcall
	Err        *p.Error
	Done       chan *Req
	tag        uint16
	prev, next *Req
}

var DefaultDebuglevel int
var DefaultLogger *p.Logger

var clntLock sync.Mutex
var clntList, clntLast *Clnt

func (clnt *Clnt) Rpcnb(r *Req) *p.Error {
	var tag uint16

	if clnt.Finished {
		return &p.Error{"Client no longer connected", 0}
	}
	if r.Tc.Type == p.Tversion {
		tag = p.NOTAG
	} else {
		tag = r.tag
	}

	p.SetTag(r.Tc, tag)
	clnt.Lock()
	if clnt.err != nil {
		clnt.Unlock()
		return clnt.err
	}

	if clnt.reqlast != nil {
		clnt.reqlast.next = r
	} else {
		clnt.reqfirst = r
	}

	r.prev = clnt.reqlast
	clnt.reqlast = r
	clnt.Unlock()

	clnt.reqout <- r
	return nil
}

func (clnt *Clnt) Rpc(tc *p.Fcall) (rc *p.Fcall, err *p.Error) {
	r := clnt.ReqAlloc()
	r.Tc = tc
	r.Done = make(chan *Req)
	err = clnt.Rpcnb(r)
	if err != nil {
		return
	}

	<-r.Done
	rc = r.Rc
	err = r.Err
	clnt.ReqFree(r)
	return
}

func (clnt *Clnt) recv() {
	var err *p.Error

	err = nil
	buf := make([]byte, clnt.Msize*8)
	pos := 0
	for {
		if len(buf) < int(clnt.Msize) {
		resize:
			b := make([]byte, clnt.Msize*8)
			copy(b, buf[0:pos])
			buf = b
			b = nil
		}

		n, oerr := clnt.conn.Read(buf[pos:len(buf)])
		if oerr != nil || n == 0 {
			err = &p.Error{oerr.String(), syscall.EIO}
			goto closed
		}

		pos += n
		for pos > 4 {
			sz, _ := p.Gint32(buf)
			if pos < int(sz) {
				if len(buf) < int(sz) {
					goto resize
				}

				break
			}

			fc, err, fcsize := p.Unpack(buf, clnt.Dotu)
			clnt.Lock()
			if err != nil {
				clnt.err = err
				clnt.conn.Close()
				clnt.Unlock()
				goto closed
			}

			if clnt.Debuglevel > 0 {
				clnt.logFcall(fc)
				if clnt.Debuglevel&DbgPrintPackets != 0 {
					log.Println("}-}", clnt.Id, fmt.Sprint(fc.Pkt))
				}

				if clnt.Debuglevel&DbgPrintFcalls != 0 {
					log.Println("}}}", clnt.Id, fc.String())
				}
			}

			var r *Req = nil
			for r = clnt.reqfirst; r != nil; r = r.next {
				if r.Tc.Tag == fc.Tag {
					break
				}
			}

			if r == nil {
				clnt.err = &p.Error{"unexpected response", syscall.EINVAL}
				clnt.conn.Close()
				clnt.Unlock()
				goto closed
			}

			r.Rc = fc
			if r.prev != nil {
				r.prev.next = r.next
			} else {
				clnt.reqfirst = r.next
			}

			if r.next != nil {
				r.next.prev = r.prev
			} else {
				clnt.reqlast = r.prev
			}
			clnt.Unlock()

			if r.Tc.Type != r.Rc.Type-1 {
				if r.Rc.Type != p.Rerror {
					r.Err = &p.Error{"invalid response", syscall.EINVAL}
					log.Println(fmt.Sprintf("TTT %v", r.Tc))
					log.Println(fmt.Sprintf("RRR %v", r.Rc))
				} else {
					if r.Err != nil {
						r.Err = &p.Error{r.Rc.Error, int(r.Rc.Errornum)}
					}
				}
			}

			if r.Done != nil {
				r.Done <- r
			}

			pos -= fcsize
			buf = buf[fcsize:]
		}
	}

closed:
	clnt.done <- true

	/* send error to all pending requests */
	clnt.Lock()
	r := clnt.reqfirst
	clnt.reqfirst = nil
	clnt.reqlast = nil
	if err == nil {
		err = clnt.err
	}
	clnt.Unlock()
	for ; r != nil; r = r.next {
		r.Err = err
		if r.Done != nil {
			r.Done <- r
		}
	}

	clntLock.Lock()
	if clnt.prev != nil {
		clnt.prev.next = clnt.next
	} else {
		clntList = clnt.next
	}

	if clnt.next != nil {
		clnt.next.prev = clnt.prev
	} else {
		clntLast = clnt.prev
	}
	clntLock.Unlock()
}

func (clnt *Clnt) send() {
	for {
		select {
		case <-clnt.done:
			clnt.Finished = true
			return

		case req := <-clnt.reqout:
			if clnt.Debuglevel > 0 {
				clnt.logFcall(req.Tc)
				if clnt.Debuglevel&DbgPrintPackets != 0 {
					log.Println("{-{", clnt.Id, fmt.Sprint(req.Tc.Pkt))
				}

				if clnt.Debuglevel&DbgPrintFcalls != 0 {
					log.Println("{{{", clnt.Id, req.Tc.String())
				}
			}

			for buf := req.Tc.Pkt; len(buf) > 0; {
				n, err := clnt.conn.Write(buf)
				if err != nil {
					/* just close the socket, will get signal on clnt.done */
					clnt.conn.Close()
					break
				}

				buf = buf[n:len(buf)]
			}
		}
	}
}

// Creates and initializes a new Clnt object. Doesn't send any data
// on the wire.
func NewClnt(c net.Conn, msize uint32, dotu bool) *Clnt {
	clnt := new(Clnt)
	clnt.conn = c
	clnt.Msize = msize
	clnt.Dotu = dotu
	clnt.Debuglevel = DefaultDebuglevel
	clnt.Log = DefaultLogger
	clnt.tagpool = newPool(uint32(p.NOTAG))
	clnt.fidpool = newPool(p.NOFID)
	clnt.reqout = make(chan *Req)
	clnt.done = make(chan bool)
	clnt.reqchan = make(chan *Req, 16)
	clnt.tchan = make(chan *p.Fcall, 16)

	go clnt.recv()
	go clnt.send()

	clntLock.Lock()
	if clntLast != nil {
		clntLast.next = clnt
	} else {
		clntList = clnt
	}

	clnt.prev = clntLast
	clntLast = clnt
	clntLock.Unlock()

	return clnt
}

// Establishes a new socket connection to the 9P server and creates
// a client object for it. Negotiates the dialect and msize for the
// connection. Returns a Clnt object, or Error.
func Connect(ntype, addr string, msize uint32, dotu bool) (*Clnt, *p.Error) {
	c, e := net.Dial(ntype, "", addr)
	if e != nil {
		return nil, &p.Error{e.String(), syscall.EIO}
	}

	clnt := NewClnt(c, msize, dotu)
	clnt.Id = addr + ":"
	ver := "9P2000"
	if clnt.Dotu {
		ver = "9P2000.u"
	}

	tc := p.NewFcall(clnt.Msize)
	err := p.PackTversion(tc, clnt.Msize, ver)
	if err != nil {
		return nil, err
	}

	rc, err := clnt.Rpc(tc)
	if err != nil {
		return nil, err
	}

	if rc.Msize < clnt.Msize {
		clnt.Msize = rc.Msize
	}

	clnt.Dotu = rc.Version == "9P2000.u" && clnt.Dotu
	return clnt, nil
}

// Creates a new Fid object for the client
func (clnt *Clnt) FidAlloc() *Fid {
	fid := new(Fid)
	fid.Fid = clnt.fidpool.getId()
	fid.Clnt = clnt

	return fid
}

func (clnt *Clnt) NewFcall() *p.Fcall {
	tc, ok := <-clnt.tchan
	if !ok {
		tc = p.NewFcall(clnt.Msize)
	}

	return tc
}

func (clnt *Clnt) ReqAlloc() *Req {
	req, ok := <-clnt.reqchan
	if !ok {
		req = new(Req)
		req.Clnt = clnt
		req.tag = uint16(clnt.tagpool.getId())
	}

	return req
}

func (clnt *Clnt) ReqFree(req *Req) {
	if req.Tc != nil && len(req.Tc.Buf) >= int(clnt.Msize) {
		_ = clnt.tchan <- req.Tc
	}

	req.Tc = nil
	req.Rc = nil
	req.Err = nil
	req.Done = nil
	req.next = nil
	req.prev = nil

	if ok := clnt.reqchan <- req; !ok {
		clnt.tagpool.putId(uint32(req.tag))
	}
}

func (clnt *Clnt) logFcall(fc *p.Fcall) {
	if clnt.Debuglevel&DbgLogPackets != 0 {
		pkt := make([]byte, len(fc.Pkt))
		copy(pkt, fc.Pkt)
		clnt.Log.Log(pkt, clnt, DbgLogPackets)
	}

	if clnt.Debuglevel&DbgLogFcalls != 0 {
		f := new(p.Fcall)
		*f = *fc
		f.Pkt = nil
		clnt.Log.Log(f, clnt, DbgLogFcalls)
	}
}

func init() {
	statsRegister()
}
<commit_msg>Remove Clnt.Finished, it is not needed and causes race conditions.<commit_after>// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The srv package provides definitions and functions used to implement
// a 9P2000 file client.
package clnt

import (
	"fmt"
	"log"
	"net"
	"sync"
	"syscall"
	"go9p.googlecode.com/hg/p"
)

// Debug flags
const (
	DbgPrintFcalls  = (1 << iota) // print all 9P messages on stderr
	DbgPrintPackets               // print the raw packets on stderr
	DbgLogFcalls                  // keep the last N 9P messages (can be accessed over http)
	DbgLogPackets                 // keep the last N 9P messages (can be accessed over http)
)

// The Clnt type represents a 9P2000 client. The client is connected to
// a 9P2000 file server and its methods can be used to access and manipulate
// the files exported by the server.
type Clnt struct {
	sync.Mutex
	Debuglevel int    // =0 don't print anything, >0 print Fcalls, >1 print raw packets
	Msize      uint32 // Maximum size of the 9P messages
	Dotu       bool   // If true, 9P2000.u protocol is spoken
	Root       *Fid   // Fid that points to the rood directory
	Id         string // Used when printing debug messages
	Log        *p.Logger

	conn     net.Conn
	tagpool  *pool
	fidpool  *pool
	reqout   chan *Req
	done     chan bool
	reqfirst *Req
	reqlast  *Req
	err      *p.Error

	reqchan chan *Req
	tchan   chan *p.Fcall

	next, prev *Clnt
}

// A Fid type represents a file on the server. Fids are used for the
// low level methods that correspond directly to the 9P2000 message requests
type Fid struct {
	sync.Mutex
	Clnt   *Clnt // Client the fid belongs to
	Iounit uint32
	p.Qid         // The Qid description for the file
	Mode   uint8  // Open mode (one of p.O* values) (if file is open)
	Fid    uint32 // Fid number
	p.User        // The user the fid belongs to
	walked bool   // true if the fid points to a walked file on the server
}

// The file is similar to the Fid, but is used in the high-level client
// interface.
type File struct {
	fid    *Fid
	offset uint64
}

type pool struct {
	sync.Mutex
	need  int
	nchan chan uint32
	maxid uint32
	imap  []byte
}

type Req struct {
	sync.Mutex
	Clnt       *Clnt
	Tc         *p.Fcall
	Rc         *p.Fcall
	Err        *p.Error
	Done       chan *Req
	tag        uint16
	prev, next *Req
}

var DefaultDebuglevel int
var DefaultLogger *p.Logger

var clntLock sync.Mutex
var clntList, clntLast *Clnt

func (clnt *Clnt) Rpcnb(r *Req) *p.Error {
	var tag uint16

	if r.Tc.Type == p.Tversion {
		tag = p.NOTAG
	} else {
		tag = r.tag
	}

	p.SetTag(r.Tc, tag)
	clnt.Lock()
	if clnt.err != nil {
		clnt.Unlock()
		return clnt.err
	}

	if clnt.reqlast != nil {
		clnt.reqlast.next = r
	} else {
		clnt.reqfirst = r
	}

	r.prev = clnt.reqlast
	clnt.reqlast = r
	clnt.Unlock()

	clnt.reqout <- r
	return nil
}

func (clnt *Clnt) Rpc(tc *p.Fcall) (rc *p.Fcall, err *p.Error) {
	r := clnt.ReqAlloc()
	r.Tc = tc
	r.Done = make(chan *Req)
	err = clnt.Rpcnb(r)
	if err != nil {
		return
	}

	<-r.Done
	rc = r.Rc
	err = r.Err
	clnt.ReqFree(r)
	return
}

func (clnt *Clnt) recv() {
	var err *p.Error

	err = nil
	buf := make([]byte, clnt.Msize*8)
	pos := 0
	for {
		if len(buf) < int(clnt.Msize) {
		resize:
			b := make([]byte, clnt.Msize*8)
			copy(b, buf[0:pos])
			buf = b
			b = nil
		}

		n, oerr := clnt.conn.Read(buf[pos:len(buf)])
		if oerr != nil || n == 0 {
			err = &p.Error{oerr.String(), syscall.EIO}
			goto closed
		}

		pos += n
		for pos > 4 {
			sz, _ := p.Gint32(buf)
			if pos < int(sz) {
				if len(buf) < int(sz) {
					goto resize
				}

				break
			}

			fc, err, fcsize := p.Unpack(buf, clnt.Dotu)
			clnt.Lock()
			if err != nil {
				clnt.err = err
				clnt.conn.Close()
				clnt.Unlock()
				goto closed
			}

			if clnt.Debuglevel > 0 {
				clnt.logFcall(fc)
				if clnt.Debuglevel&DbgPrintPackets != 0 {
					log.Println("}-}", clnt.Id, fmt.Sprint(fc.Pkt))
				}

				if clnt.Debuglevel&DbgPrintFcalls != 0 {
					log.Println("}}}", clnt.Id, fc.String())
				}
			}

			var r *Req = nil
			for r = clnt.reqfirst; r != nil; r = r.next {
				if r.Tc.Tag == fc.Tag {
					break
				}
			}

			if r == nil {
				clnt.err = &p.Error{"unexpected response", syscall.EINVAL}
				clnt.conn.Close()
				clnt.Unlock()
				goto closed
			}

			r.Rc = fc
			if r.prev != nil {
				r.prev.next = r.next
			} else {
				clnt.reqfirst = r.next
			}

			if r.next != nil {
				r.next.prev = r.prev
			} else {
				clnt.reqlast = r.prev
			}
			clnt.Unlock()

			if r.Tc.Type != r.Rc.Type-1 {
				if r.Rc.Type != p.Rerror {
					r.Err = &p.Error{"invalid response", syscall.EINVAL}
					log.Println(fmt.Sprintf("TTT %v", r.Tc))
					log.Println(fmt.Sprintf("RRR %v", r.Rc))
				} else {
					if r.Err != nil {
						r.Err = &p.Error{r.Rc.Error, int(r.Rc.Errornum)}
					}
				}
			}

			if r.Done != nil {
				r.Done <- r
			}

			pos -= fcsize
			buf = buf[fcsize:]
		}
	}

closed:
	clnt.done <- true

	/* send error to all pending requests */
	clnt.Lock()
	r := clnt.reqfirst
	clnt.reqfirst = nil
	clnt.reqlast = nil
	if err == nil {
		err = clnt.err
	}
	clnt.Unlock()
	for ; r != nil; r = r.next {
		r.Err = err
		if r.Done != nil {
			r.Done <- r
		}
	}

	clntLock.Lock()
	if clnt.prev != nil {
		clnt.prev.next = clnt.next
	} else {
		clntList = clnt.next
	}

	if clnt.next != nil {
		clnt.next.prev = clnt.prev
	} else {
		clntLast = clnt.prev
	}
	clntLock.Unlock()
}

func (clnt *Clnt) send() {
	for {
		select {
		case <-clnt.done:
			return

		case req := <-clnt.reqout:
			if clnt.Debuglevel > 0 {
				clnt.logFcall(req.Tc)
				if clnt.Debuglevel&DbgPrintPackets != 0 {
					log.Println("{-{", clnt.Id, fmt.Sprint(req.Tc.Pkt))
				}

				if clnt.Debuglevel&DbgPrintFcalls != 0 {
					log.Println("{{{", clnt.Id, req.Tc.String())
				}
			}

			for buf := req.Tc.Pkt; len(buf) > 0; {
				n, err := clnt.conn.Write(buf)
				if err != nil {
					/* just close the socket, will get signal on clnt.done */
					clnt.conn.Close()
					break
				}

				buf = buf[n:len(buf)]
			}
		}
	}
}

// Creates and initializes a new Clnt object. Doesn't send any data
// on the wire.
func NewClnt(c net.Conn, msize uint32, dotu bool) *Clnt {
	clnt := new(Clnt)
	clnt.conn = c
	clnt.Msize = msize
	clnt.Dotu = dotu
	clnt.Debuglevel = DefaultDebuglevel
	clnt.Log = DefaultLogger
	clnt.tagpool = newPool(uint32(p.NOTAG))
	clnt.fidpool = newPool(p.NOFID)
	clnt.reqout = make(chan *Req)
	clnt.done = make(chan bool)
	clnt.reqchan = make(chan *Req, 16)
	clnt.tchan = make(chan *p.Fcall, 16)

	go clnt.recv()
	go clnt.send()

	clntLock.Lock()
	if clntLast != nil {
		clntLast.next = clnt
	} else {
		clntList = clnt
	}

	clnt.prev = clntLast
	clntLast = clnt
	clntLock.Unlock()

	return clnt
}

// Establishes a new socket connection to the 9P server and creates
// a client object for it. Negotiates the dialect and msize for the
// connection. Returns a Clnt object, or Error.
func Connect(ntype, addr string, msize uint32, dotu bool) (*Clnt, *p.Error) {
	c, e := net.Dial(ntype, "", addr)
	if e != nil {
		return nil, &p.Error{e.String(), syscall.EIO}
	}

	clnt := NewClnt(c, msize, dotu)
	clnt.Id = addr + ":"
	ver := "9P2000"
	if clnt.Dotu {
		ver = "9P2000.u"
	}

	tc := p.NewFcall(clnt.Msize)
	err := p.PackTversion(tc, clnt.Msize, ver)
	if err != nil {
		return nil, err
	}

	rc, err := clnt.Rpc(tc)
	if err != nil {
		return nil, err
	}

	if rc.Msize < clnt.Msize {
		clnt.Msize = rc.Msize
	}

	clnt.Dotu = rc.Version == "9P2000.u" && clnt.Dotu
	return clnt, nil
}

// Creates a new Fid object for the client
func (clnt *Clnt) FidAlloc() *Fid {
	fid := new(Fid)
	fid.Fid = clnt.fidpool.getId()
	fid.Clnt = clnt

	return fid
}

func (clnt *Clnt) NewFcall() *p.Fcall {
	tc, ok := <-clnt.tchan
	if !ok {
		tc = p.NewFcall(clnt.Msize)
	}

	return tc
}

func (clnt *Clnt) ReqAlloc() *Req {
	req, ok := <-clnt.reqchan
	if !ok {
		req = new(Req)
		req.Clnt = clnt
		req.tag = uint16(clnt.tagpool.getId())
	}

	return req
}

func (clnt *Clnt) ReqFree(req *Req) {
	if req.Tc != nil && len(req.Tc.Buf) >= int(clnt.Msize) {
		_ = clnt.tchan <- req.Tc
	}

	req.Tc = nil
	req.Rc = nil
	req.Err = nil
	req.Done = nil
	req.next = nil
	req.prev = nil

	if ok := clnt.reqchan <- req; !ok {
		clnt.tagpool.putId(uint32(req.tag))
	}
}

func (clnt *Clnt) logFcall(fc *p.Fcall) {
	if clnt.Debuglevel&DbgLogPackets != 0 {
		pkt := make([]byte, len(fc.Pkt))
		copy(pkt, fc.Pkt)
		clnt.Log.Log(pkt, clnt, DbgLogPackets)
	}

	if clnt.Debuglevel&DbgLogFcalls != 0 {
		f := new(p.Fcall)
		*f = *fc
		f.Pkt = nil
		clnt.Log.Log(f, clnt, DbgLogFcalls)
	}
}

func init() {
	statsRegister()
}
<|endoftext|>