<commit_before>package main

import (
	"fmt"

	_ "github.com/ying32/govcl/pkgs/winappres"
	"github.com/ying32/govcl/vcl"
	"github.com/ying32/govcl/vcl/types"
	"github.com/ying32/govcl/vcl/types/colors"
)

// 简单介绍下Delphi中控件的布局方式
func main() {

	vcl.Application.Initialize()
	vcl.Application.SetMainFormOnTaskBar(true)

	mainForm := vcl.Application.CreateForm()
	mainForm.SetCaption("Hello")
	mainForm.SetPosition(types.PoScreenCenter)
	mainForm.SetWidth(700)
	mainForm.SetHeight(500)

	// 为方便演示，这里使用一个TPageControl作为多个布局的演示
	// Delphi中所有可视与非可视组件的owner都可设置为TForm的实例，
	// 本示例中，TForm的实例即mainForm，当owner设置为mainForm时
	// 在TForm销毁前会自动Free相关组件
	pgc := vcl.NewPageControl(mainForm)

	// 如需让一个可视控件显示到某个控件上即可使用SetParent，
	// 这里需要注意并非所有控件都可使用SetParent，Delphi中一般能接受子控件的只有
	// TWinControl，不过这里没有做区分。打个比方如TButton、TImage、TLabel是不可以作
	// 为父控件使用的，能作为父控件的常用的有TForm、TPanel、TPageControl、TTabSheet。
	pgc.SetParent(mainForm)
	// 这里将TPageControl设置为整个窗口客户区大小，并自动调整
	pgc.SetAlign(types.AlClient)

	//
	sheet := vcl.NewTabSheet(mainForm)
	// Delphi中有几个控件的设置Parent属性有些不同，这里需要使用SetPageControl才行。
	// TTabSheet控件默认的Align为alClient，即填充父控件客户区域
	sheet.SetPageControl(pgc)
	sheet.SetCaption("顶-左-客户区")

	// 此时的pnl仅Height属性生效
	pnl := vcl.NewPanel(mainForm)
	pnl.SetCaption("顶")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClRed)
	pnl.SetParent(sheet)
	pnl.SetHeight(100)
	pnl.SetAlign(types.AlTop)

	// 此时的pnl仅Width属性生效
	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("左")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClGreen)
	pnl.SetParent(sheet)
	pnl.SetWidth(100)
	pnl.SetAlign(types.AlLeft)

	// 此时的pnl无法手动调整大小
	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("客户区")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClBlue)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlClient)

	// --------------------------------------------------------------------

	sheet = vcl.NewTabSheet(mainForm)
	sheet.SetPageControl(pgc)
	sheet.SetCaption("顶-客户区-底")

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("顶")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClRed)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlTop)

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("客户区")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClGreen)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlClient)

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("底")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClBlue)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlBottom)

	//--------------------------------------------------------------------

	sheet = vcl.NewTabSheet(mainForm)
	sheet.SetPageControl(pgc)
	sheet.SetCaption("顶-客户区(左|-|右)-底")

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("顶")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClRed)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlTop)

	ppnl := vcl.NewPanel(mainForm)
	ppnl.SetCaption("客户区")
	ppnl.SetParentBackground(false)
	ppnl.SetColor(colors.ClGreen)
	ppnl.SetParent(sheet)
	ppnl.SetAlign(types.AlClient)

	// 上个panel作为父控件
	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("左")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClAqua)
	pnl.SetParent(ppnl)
	pnl.SetAlign(types.AlLeft)

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("右")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClAzure)
	pnl.SetParent(ppnl)
	pnl.SetAlign(types.AlRight)

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("底")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClBlue)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlBottom)

	//----------------------------------Anchors----------------------------------

	sheet = vcl.NewTabSheet(mainForm)
	sheet.SetPageControl(pgc)
	sheet.SetCaption("Anchors")

	pnl = vcl.NewPanel(mainForm)
	pnl.SetParentBackground(false)
	//pnl.SetColor(colors.ClBlue)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlClient)

	w := pnl.Width()
	h := pnl.Height()

	fmt.Println(w, h)

	btn := vcl.NewButton(mainForm)
	btn.SetParent(pnl)
	btn.SetCaption("左")
	btn.SetLeft(10)

	// lcl下使用ClientWidth或者ClientHeight
	// vcl下建议使用Width或者Height
	// 原因估计是两套组件对于某些方面的处理不同
	btn = vcl.NewButton(mainForm)
	btn.SetParent(pnl)
	btn.SetCaption("右")
	btn.SetLeft(w - btn.Width() - 10)
	btn.SetAnchors(types.NewSet(types.AkTop, types.AkRight))

	btn = vcl.NewButton(mainForm)
	btn.SetParent(pnl)
	btn.SetCaption("左下")
	btn.SetLeft(10)
	btn.SetTop(h - btn.Height() - 10)
	btn.SetAnchors(types.NewSet(types.AkLeft, types.AkBottom))

	btn = vcl.NewButton(mainForm)
	btn.SetParent(pnl)
	btn.SetCaption("右下")
	btn.SetLeft(w - btn.Width() - 10)
	btn.SetTop(h - btn.Height() - 10)
	btn.SetAnchors(types.NewSet(types.AkRight, types.AkBottom))

	//----------------------------------Margins----------------------------------

	sheet = vcl.NewTabSheet(mainForm)
	sheet.SetPageControl(pgc)
	sheet.SetCaption("Margins")

	ppnl = vcl.NewPanel(mainForm)
	ppnl.SetParent(sheet)
	ppnl.SetParentBackground(false)
	ppnl.SetColor(colors.ClRed)
	ppnl.SetAlign(types.AlClient)

	pnl = vcl.NewPanel(mainForm)
	pnl.SetParent(ppnl)
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClGreen)

	pnl.SetAlign(types.AlClient)

	m := pnl.Margins()
	m.SetLeft(20)
	m.SetTop(30)
	m.SetBottom(40)
	m.SetRight(50)

	//----------------------------------OnAlignPosition----------------------------------

	sheet = vcl.NewTabSheet(mainForm)
	sheet.SetPageControl(pgc)
	sheet.SetCaption("Align = alCustom = OnAlignPosition")

	pnl = vcl.NewPanel(mainForm)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlClient)
	// 子控件如果有设置为AlCustom的，则会触发这个事件
	pnl.SetOnAlignPosition(onCustomAlignPosiion)

	pnl2 := vcl.NewPanel(mainForm)
	pnl2.SetParent(pnl)
	pnl2.SetAlign(types.AlCustom)
	pnl2.SetBounds(10, 10, 300, 300)
	// 子控件如果有设置为AlCustom的，则会触发这个事件
	pnl2.SetOnAlignPosition(onCustomAlignPosiion)

	btn = vcl.NewButton(mainForm)
	btn.SetParent(pnl2)
	btn.SetAlign(types.AlCustom) // 自定义
	btn.SetCaption("按钮。")

	vcl.Application.Run()
}

// sender 调用此事件的控件
// control 被调整的控件
// newLeft, newTop, newWidth, newHeight 保存被调整的控件原始位置和大小
// alignRect 保存对齐的矩形范围
// alignInfo 对齐信息
func onCustomAlignPosiion(sender *vcl.TWinControl, control *vcl.TControl, newLeft, newTop, newWidth, newHeight *int32, alignRect *types.TRect, alignInfo types.TAlignInfo) {
	*newLeft = (alignRect.Width() - *newWidth) / 2
	*newTop = (alignRect.Height() - *newHeight) / 2
	fmt.Println(*newLeft, *newTop)
}
<commit_msg>Update layout example.<commit_after>package main

import (
	"fmt"

	_ "github.com/ying32/govcl/pkgs/winappres"
	"github.com/ying32/govcl/vcl"
	"github.com/ying32/govcl/vcl/types"
	"github.com/ying32/govcl/vcl/types/colors"
)

// 简单介绍下Delphi中控件的布局方式
func main() {

	vcl.Application.Initialize()
	vcl.Application.SetMainFormOnTaskBar(true)

	mainForm := vcl.Application.CreateForm()
	mainForm.SetCaption("Hello")
	mainForm.SetPosition(types.PoScreenCenter)
	mainForm.SetWidth(700)
	mainForm.SetHeight(500)

	// 为方便演示，这里使用一个TPageControl作为多个布局的演示
	// Delphi中所有可视与非可视组件的owner都可设置为TForm的实例，
	// 本示例中，TForm的实例即mainForm，当owner设置为mainForm时
	// 在TForm销毁前会自动Free相关组件
	pgc := vcl.NewPageControl(mainForm)

	// 如需让一个可视控件显示到某个控件上即可使用SetParent，
	// 这里需要注意并非所有控件都可使用SetParent，Delphi中一般能接受子控件的只有
	// TWinControl，不过这里没有做区分。打个比方如TButton、TImage、TLabel是不可以作
	// 为父控件使用的，能作为父控件的常用的有TForm、TPanel、TPageControl、TTabSheet。
	pgc.SetParent(mainForm)
	// 这里将TPageControl设置为整个窗口客户区大小，并自动调整
	pgc.SetAlign(types.AlClient)

	//
	sheet := vcl.NewTabSheet(mainForm)
	// Delphi中有几个控件的设置Parent属性有些不同，这里需要使用SetPageControl才行。
	// TTabSheet控件默认的Align为alClient，即填充父控件客户区域
	sheet.SetPageControl(pgc)
	sheet.SetCaption("顶-左-客户区")

	// 此时的pnl仅Height属性生效
	pnl := vcl.NewPanel(mainForm)
	pnl.SetCaption("顶")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClRed)
	pnl.SetParent(sheet)
	pnl.SetHeight(100)
	pnl.SetAlign(types.AlTop)

	// 此时的pnl仅Width属性生效
	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("左")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClGreen)
	pnl.SetParent(sheet)
	pnl.SetWidth(100)
	pnl.SetAlign(types.AlLeft)

	// 此时的pnl无法手动调整大小
	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("客户区")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClBlue)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlClient)

	// --------------------------------------------------------------------

	sheet = vcl.NewTabSheet(mainForm)
	sheet.SetPageControl(pgc)
	sheet.SetCaption("顶-客户区-底")

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("顶")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClRed)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlTop)

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("客户区")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClGreen)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlClient)

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("底")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClBlue)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlBottom)

	//--------------------------------------------------------------------

	sheet = vcl.NewTabSheet(mainForm)
	sheet.SetPageControl(pgc)
	sheet.SetCaption("顶-客户区(左|-|右)-底")

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("顶")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClRed)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlTop)

	ppnl := vcl.NewPanel(mainForm)
	ppnl.SetCaption("客户区")
	ppnl.SetParentBackground(false)
	ppnl.SetColor(colors.ClGreen)
	ppnl.SetParent(sheet)
	ppnl.SetAlign(types.AlClient)

	// 上个panel作为父控件
	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("左")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClAqua)
	pnl.SetParent(ppnl)
	pnl.SetAlign(types.AlLeft)

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("右")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClAzure)
	pnl.SetParent(ppnl)
	pnl.SetAlign(types.AlRight)

	pnl = vcl.NewPanel(mainForm)
	pnl.SetCaption("底")
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClBlue)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlBottom)

	//----------------------------------Anchors----------------------------------

	sheet = vcl.NewTabSheet(mainForm)
	sheet.SetPageControl(pgc)
	sheet.SetCaption("Anchors")

	pnl = vcl.NewPanel(mainForm)
	pnl.SetParentBackground(false)
	//pnl.SetColor(colors.ClBlue)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlClient)

	w := pnl.Width()
	h := pnl.Height()

	fmt.Println(w, h)

	btn := vcl.NewButton(mainForm)
	btn.SetParent(pnl)
	btn.SetCaption("左(Left)")
	btn.SetLeft(10)

	// lcl下使用ClientWidth或者ClientHeight
	// vcl下建议使用Width或者Height
	// 原因估计是两套组件对于某些方面的处理不同
	btn = vcl.NewButton(mainForm)
	btn.SetParent(pnl)
	btn.SetCaption("右(Right)")
	btn.SetLeft(w - btn.Width() - 10)
	btn.SetAnchors(types.NewSet(types.AkTop, types.AkRight))

	// 中
	btn = vcl.NewButton(mainForm)
	btn.SetParent(pnl)
	btn.SetCaption("中(Center)")
	btn.SetLeft(w - btn.Width() - 10)
	btn.AnchorHorizontalCenterTo(pnl)
	btn.AnchorVerticalCenterTo(pnl)

	btn = vcl.NewButton(mainForm)
	btn.SetParent(pnl)
	btn.SetCaption("左下(Left-Bottom)")
	btn.SetLeft(10)
	btn.SetTop(h - btn.Height() - 10)
	btn.SetAnchors(types.NewSet(types.AkLeft, types.AkBottom))

	btn = vcl.NewButton(mainForm)
	btn.SetParent(pnl)
	btn.SetCaption("右下(Right-Bottom)")
	btn.SetLeft(w - btn.Width() - 10)
	btn.SetTop(h - btn.Height() - 10)
	btn.SetAnchors(types.NewSet(types.AkRight, types.AkBottom))

	//----------------------------------Margins----------------------------------

	sheet = vcl.NewTabSheet(mainForm)
	sheet.SetPageControl(pgc)
	sheet.SetCaption("BorderSpacing")

	ppnl = vcl.NewPanel(mainForm)
	ppnl.SetParent(sheet)
	ppnl.SetParentBackground(false)
	ppnl.SetColor(colors.ClRed)
	ppnl.SetAlign(types.AlClient)

	pnl = vcl.NewPanel(mainForm)
	pnl.SetParent(ppnl)
	pnl.SetParentBackground(false)
	pnl.SetColor(colors.ClGreen)

	pnl.SetAlign(types.AlClient)

	m := pnl.BorderSpacing()
	m.SetLeft(20)
	m.SetTop(30)
	m.SetBottom(40)
	m.SetRight(50)

	//----------------------------------OnAlignPosition----------------------------------

	sheet = vcl.NewTabSheet(mainForm)
	sheet.SetPageControl(pgc)
	sheet.SetCaption("Align = alCustom = OnAlignPosition")

	pnl = vcl.NewPanel(mainForm)
	pnl.SetParent(sheet)
	pnl.SetAlign(types.AlClient)
	// 子控件如果有设置为AlCustom的，则会触发这个事件
	pnl.SetOnAlignPosition(onCustomAlignPosiion)

	pnl2 := vcl.NewPanel(mainForm)
	pnl2.SetParent(pnl)
	pnl2.SetAlign(types.AlCustom)
	pnl2.SetBounds(10, 10, 300, 300)
	// 子控件如果有设置为AlCustom的，则会触发这个事件
	pnl2.SetOnAlignPosition(onCustomAlignPosiion)

	btn = vcl.NewButton(mainForm)
	btn.SetParent(pnl2)
	btn.SetAlign(types.AlCustom) // 自定义
	btn.SetCaption("按钮。")

	vcl.Application.Run()
}

// sender 调用此事件的控件
// control 被调整的控件
// newLeft, newTop, newWidth, newHeight 保存被调整的控件原始位置和大小
// alignRect 保存对齐的矩形范围
// alignInfo 对齐信息
func onCustomAlignPosiion(sender *vcl.TWinControl, control *vcl.TControl, newLeft, newTop, newWidth, newHeight *int32, alignRect *types.TRect, alignInfo types.TAlignInfo) {
	*newLeft = (alignRect.Width() - *newWidth) / 2
	*newTop = (alignRect.Height() - *newHeight) / 2
	fmt.Println(*newLeft, *newTop)
}
<|endoftext|>


<commit_before>package scheduler

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net"
	"net/http"
	"os"
	"sync"
	"syscall"
	"time"

	"github.com/cloudfoundry-incubator/runtime-schema/bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	"github.com/cloudfoundry-incubator/runtime-schema/models/executor_api"
	"github.com/cloudfoundry/gosteno"
	"github.com/tedsuo/router"
)

const ServerCloseErrMsg = "use of closed network connection"

type Scheduler struct {
	bbs          bbs.ExecutorBBS
	logger       *gosteno.Logger
	executorURL  string
	reqGen       *router.RequestGenerator
	client       http.Client
	listener     net.Listener
	address      string
	inFlight     *sync.WaitGroup
	completeChan chan executor_api.ContainerRunResult
}

func New(bbs bbs.ExecutorBBS, logger *gosteno.Logger, schedulerAddress, executorURL string) *Scheduler {
	return &Scheduler{
		bbs:          bbs,
		logger:       logger,
		executorURL:  executorURL,
		reqGen:       router.NewRequestGenerator(executorURL, executor_api.Routes),
		client:       http.Client{},
		address:      schedulerAddress,
		inFlight:     &sync.WaitGroup{},
		completeChan: make(chan executor_api.ContainerRunResult),
	}
}

func (s *Scheduler) startServer() {
	err := http.Serve(s.listener, s)
	if err != nil && err.Error() != ServerCloseErrMsg {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.server.failed")
	}
}

func (s *Scheduler) stopServer() {
	err := s.listener.Close()
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.server-close.failed")
	}
}

func (s *Scheduler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	responseBody, err := ioutil.ReadAll(r.Body)
	completeResp := executor_api.ContainerRunResult{}
	err = json.Unmarshal(responseBody, &completeResp)
	if err != nil {
		panic(err)
	}
	s.completeChan <- completeResp
	w.WriteHeader(http.StatusOK)
}

func (s *Scheduler) Run(sigChan chan os.Signal, readyChan chan struct{}) error {
	s.logger.Info("executor.watching-for-desired-task")
	tasks, _, _ := s.bbs.WatchForDesiredTask()

	listener, err := net.Listen("tcp", s.address)
	if err != nil {
		panic(err)
	}
	s.listener = listener

	if readyChan != nil {
		close(readyChan)
	}

	go s.startServer()

	for {
		select {
		case task := <-tasks:
			s.inFlight.Add(1)
			go func() {
				s.handleTaskRequest(task)
				s.inFlight.Done()
			}()

		case runResult := <-s.completeChan:
			s.inFlight.Add(1)
			go func() {
				s.handleRunCompletion(runResult)
				s.inFlight.Done()
			}()

		case sig := <-sigChan:
			switch sig {
			case syscall.SIGINT, syscall.SIGTERM:
				s.stopServer()
				s.inFlight.Wait()
				return nil
			}

		}
	}
}

func (s *Scheduler) handleRunCompletion(runResult executor_api.ContainerRunResult) {
	task := models.Task{}
	err := json.Unmarshal(runResult.Metadata, &task)
	if err != nil {
		panic(err)
	}

	s.bbs.CompleteTask(&task, runResult.Failed, runResult.FailureReason, runResult.Result)
}

func (s *Scheduler) handleTaskRequest(task *models.Task) {
	var err error
	container, succeeded := s.allocateContainer(task)
	if !succeeded {
		return
	}

	s.sleepForARandomInterval()

	err = s.bbs.ClaimTask(task, container.ExecutorGuid)
	if err != nil {
		s.deleteAllocation(container.Guid)
		return
	}

	succeeded = s.initializeContainer(container.Guid)
	if !succeeded {
		s.deleteAllocation(container.Guid)
		return
	}

	err = s.bbs.StartTask(task, container.Guid)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.start-task.failed")
		s.deleteAllocation(container.Guid)
		return
	}

	s.runActions(container.Guid, task)
}

func (s *Scheduler) allocateContainer(task *models.Task) (container executor_api.Container, succeeded bool) {
	reqBody, err := json.Marshal(executor_api.ContainerAllocationRequest{
		MemoryMB:        task.MemoryMB,
		DiskMB:          task.DiskMB,
		CpuPercent:      task.CpuPercent,
		FileDescriptors: task.FileDescriptors,
	})
	if err != nil {
		panic(err)
	}

	req, err := s.reqGen.RequestForHandler(executor_api.AllocateContainer, nil, bytes.NewBuffer(reqBody))
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.allocation-request-generation.failed")
		return
	}
	req.Header.Set("Content-Type", "application/json")

	response, err := s.client.Do(req)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.allocation-request.failed")
		return
	}

	if response.StatusCode == http.StatusRequestEntityTooLarge {
		s.logger.Infod(map[string]interface{}{
			"error": "Executor out of resources",
		}, "game-scheduler.reserve-resource-allocation.full")
		return
	}

	if response.StatusCode != http.StatusCreated {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Executor responded with status code %d", response.StatusCode),
		}, "game-scheduler.reserve-resource-allocation.failed")
		return
	}

	responseBody, err := ioutil.ReadAll(response.Body)
	if err != nil {
		panic(err)
	}

	container = executor_api.Container{}
	err = json.Unmarshal(responseBody, &container)
	if err != nil {
		panic(err)
	}

	return container, true
}

func (s *Scheduler) initializeContainer(allocationGuid string) bool {
	req, err := s.reqGen.RequestForHandler(executor_api.InitializeContainer, router.Params{"guid": allocationGuid}, nil)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.initialize-request-generation.failed")
		return false
	}

	response, err := s.client.Do(req)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.initialize-container-request.failed")
		return false
	}
	if response.StatusCode != http.StatusCreated {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Executor responded with status code %d", response.StatusCode),
		}, "game-scheduler.initialize-container.failed")
		return false
	}
	return true
}

func (s *Scheduler) runActions(allocationGuid string, task *models.Task) {
	reqBody, err := json.Marshal(executor_api.ContainerRunRequest{
		Actions:     task.Actions,
		Metadata:    task.ToJSON(),
		CompleteURL: "http://" + s.address + "/complete/" + allocationGuid,
	})

	req, err := s.reqGen.RequestForHandler(
		executor_api.RunActions,
		router.Params{"guid": allocationGuid},
		bytes.NewReader(reqBody),
	)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.run-actions-request-generation.failed")
		return
	}
	req.Header.Set("Content-Type", "application/json")

	response, err := s.client.Do(req)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.run-actions-request.failed")
	}
	if response.StatusCode != http.StatusOK {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Executor responded with status code %d", response.StatusCode),
		}, "game-scheduler.run-actions.failed")
	}
}

func (s *Scheduler) deleteAllocation(allocationGuid string) {
	req, err := s.reqGen.RequestForHandler(executor_api.DeleteContainer, router.Params{"guid": allocationGuid}, nil)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.delete-container-request.failed")
		return
	}

	response, err := s.client.Do(req)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.delete-contatiner-request.failed")
		return
	}

	if response.StatusCode != http.StatusOK {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Executor responded with status code %d", response.StatusCode),
		}, "game-scheduler.delete-container.failed")
	}
}

func (s *Scheduler) sleepForARandomInterval() {
	interval := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(100)
	time.Sleep(time.Duration(interval) * time.Millisecond)
}
<commit_msg>Better error handling and logging<commit_after>package scheduler

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net"
	"net/http"
	"os"
	"sync"
	"syscall"
	"time"

	"github.com/cloudfoundry-incubator/runtime-schema/bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	"github.com/cloudfoundry-incubator/runtime-schema/models/executor_api"
	"github.com/cloudfoundry/gosteno"
	"github.com/tedsuo/router"
)

const ServerCloseErrMsg = "use of closed network connection"

type Scheduler struct {
	bbs          bbs.ExecutorBBS
	logger       *gosteno.Logger
	executorURL  string
	reqGen       *router.RequestGenerator
	client       http.Client
	listener     net.Listener
	address      string
	inFlight     *sync.WaitGroup
	completeChan chan executor_api.ContainerRunResult
}

func New(bbs bbs.ExecutorBBS, logger *gosteno.Logger, schedulerAddress, executorURL string) *Scheduler {
	return &Scheduler{
		bbs:          bbs,
		logger:       logger,
		executorURL:  executorURL,
		reqGen:       router.NewRequestGenerator(executorURL, executor_api.Routes),
		client:       http.Client{},
		address:      schedulerAddress,
		inFlight:     &sync.WaitGroup{},
		completeChan: make(chan executor_api.ContainerRunResult),
	}
}

func (s *Scheduler) startServer() {
	err := http.Serve(s.listener, s)
	if err != nil && err.Error() != ServerCloseErrMsg {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.server.failed")
	}
}

func (s *Scheduler) stopServer() {
	err := s.listener.Close()
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.server-close.failed")
	}
}

func (s *Scheduler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	responseBody, err := ioutil.ReadAll(r.Body)
	r.Body.Close()

	completeResp := executor_api.ContainerRunResult{}
	err = json.Unmarshal(responseBody, &completeResp)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Could not unmarshal response: %s", err),
		}, "game-scheduler.complete-callback-handler.failed")
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	s.completeChan <- completeResp
	w.WriteHeader(http.StatusOK)
}

func (s *Scheduler) Run(sigChan chan os.Signal, readyChan chan struct{}) error {
	s.logger.Info("executor.watching-for-desired-task")
	tasks, _, _ := s.bbs.WatchForDesiredTask()

	listener, err := net.Listen("tcp", s.address)
	if err != nil {
		return err
	}
	s.listener = listener

	if readyChan != nil {
		close(readyChan)
	}

	go s.startServer()

	for {
		select {
		case task := <-tasks:
			s.inFlight.Add(1)
			go func() {
				s.handleTaskRequest(task)
				s.inFlight.Done()
			}()

		case runResult := <-s.completeChan:
			s.inFlight.Add(1)
			go func() {
				s.handleRunCompletion(runResult)
				s.inFlight.Done()
			}()

		case sig := <-sigChan:
			switch sig {
			case syscall.SIGINT, syscall.SIGTERM:
				s.stopServer()
				s.inFlight.Wait()
				return nil
			}

		}
	}
}

func (s *Scheduler) handleRunCompletion(runResult executor_api.ContainerRunResult) {
	task := models.Task{}
	err := json.Unmarshal(runResult.Metadata, &task)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Could not unmarshal metadata: %s", err),
		}, "game-scheduler.complete-callback-handler.failed")
		return
	}

	s.bbs.CompleteTask(&task, runResult.Failed, runResult.FailureReason, runResult.Result)
}

func (s *Scheduler) handleTaskRequest(task *models.Task) {
	var err error
	container, succeeded := s.allocateContainer(task)
	if !succeeded {
		return
	}

	s.sleepForARandomInterval()

	err = s.bbs.ClaimTask(task, container.ExecutorGuid)
	if err != nil {
		s.deleteAllocation(container.Guid)
		return
	}

	succeeded = s.initializeContainer(container.Guid)
	if !succeeded {
		s.deleteAllocation(container.Guid)
		return
	}

	err = s.bbs.StartTask(task, container.Guid)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.start-task.failed")
		s.deleteAllocation(container.Guid)
		return
	}

	s.runActions(container.Guid, task)
}

func (s *Scheduler) allocateContainer(task *models.Task) (container executor_api.Container, succeeded bool) {
	reqBody, err := json.Marshal(executor_api.ContainerAllocationRequest{
		MemoryMB:        task.MemoryMB,
		DiskMB:          task.DiskMB,
		CpuPercent:      task.CpuPercent,
		FileDescriptors: task.FileDescriptors,
	})
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Could not marshal json: %s", err),
		}, "game-scheduler.allocation-request-json.failed")
	}

	req, err := s.reqGen.RequestForHandler(executor_api.AllocateContainer, nil, bytes.NewBuffer(reqBody))
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.allocation-request-generation.failed")
		return
	}
	req.Header.Set("Content-Type", "application/json")

	response, err := s.client.Do(req)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.allocation-request.failed")
		return
	}

	if response.StatusCode == http.StatusRequestEntityTooLarge {
		s.logger.Infod(map[string]interface{}{
			"error": "Executor out of resources",
		}, "game-scheduler.allocate-container.full")
		return
	}

	if response.StatusCode != http.StatusCreated {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Executor responded with status code %d", response.StatusCode),
		}, "game-scheduler.allocate-container.failed")
		return
	}

	responseBody, err := ioutil.ReadAll(response.Body)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Could not read response body: %s", err),
		}, "game-scheduler.allocate-container.failed")
		return
	}

	response.Body.Close()

	container = executor_api.Container{}
	err = json.Unmarshal(responseBody, &container)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Could not unmarshal json: %s", err),
		}, "game-scheduler.allocate-container.failed")
		return
	}

	return container, true
}

func (s *Scheduler) initializeContainer(allocationGuid string) bool {
	req, err := s.reqGen.RequestForHandler(executor_api.InitializeContainer, router.Params{"guid": allocationGuid}, nil)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.initialize-request-generation.failed")
		return false
	}

	response, err := s.client.Do(req)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.initialize-container-request.failed")
		return false
	}
	if response.StatusCode != http.StatusCreated {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Executor responded with status code %d", response.StatusCode),
		}, "game-scheduler.initialize-container.failed")
		return false
	}
	return true
}

func (s *Scheduler) runActions(allocationGuid string, task *models.Task) {
	reqBody, err := json.Marshal(executor_api.ContainerRunRequest{
		Actions:     task.Actions,
		Metadata:    task.ToJSON(),
		CompleteURL: "http://" + s.address + "/complete/" + allocationGuid,
	})

	req, err := s.reqGen.RequestForHandler(
		executor_api.RunActions,
		router.Params{"guid": allocationGuid},
		bytes.NewReader(reqBody),
	)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.run-actions-request-generation.failed")
		return
	}
	req.Header.Set("Content-Type", "application/json")

	response, err := s.client.Do(req)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.run-actions-request.failed")
	}
	if response.StatusCode != http.StatusOK {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Executor responded with status code %d", response.StatusCode),
		}, "game-scheduler.run-actions.failed")
	}
}

func (s *Scheduler) deleteAllocation(allocationGuid string) {
	req, err := s.reqGen.RequestForHandler(executor_api.DeleteContainer, router.Params{"guid": allocationGuid}, nil)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.delete-container-request.failed")
		return
	}

	response, err := s.client.Do(req)
	if err != nil {
		s.logger.Errord(map[string]interface{}{
			"error": err.Error(),
		}, "game-scheduler.delete-contatiner-request.failed")
		return
	}

	if response.StatusCode != http.StatusOK {
		s.logger.Errord(map[string]interface{}{
			"error": fmt.Sprintf("Executor responded with status code %d", response.StatusCode),
		}, "game-scheduler.delete-container.failed")
	}
}

func (s *Scheduler) sleepForARandomInterval() {
	interval := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(100)
	time.Sleep(time.Duration(interval) * time.Millisecond)
}
<|endoftext|>


<commit_before>package bot

import (
	"fmt"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/belak/irc"
	"github.com/khades/servbot/commandHandlers"
	"github.com/khades/servbot/ircClient"
	"github.com/khades/servbot/models"
	"github.com/khades/servbot/repos"
)

var chatHandler irc.HandlerFunc = func(client *irc.Client, message *irc.Message) {
	//	log.Println(message.String())
	msgID, found := message.Tags.GetTag("msg-id")
	if found {
		switch msgID {
		case "room_mods":
			{
				commaIndex := strings.Index(message.Params[1], ":")
				if commaIndex != -1 {
					//				log.Printf("Channel %v: got mods list", message.Params[0])
					mods := strings.Split(message.Params[1][commaIndex+2:], ", ")
					repos.PushMods(message.Params[0][1:], mods)
				}
			}
		case "resub":
			{
				msgParamMonths, msgParamMonthsFound := message.Tags.GetTag("msg-param-months")
				user, userFound := message.Tags.GetTag("display-name")
				channel := message.Params[0]
				if msgParamMonthsFound && userFound && channel != "" {
					resubCount, resubCountError := strconv.Atoi(msgParamMonths)
					if resubCountError == nil {
						formedMessage := models.ChatMessage{
							Channel:          channel,
							User:             user,
							IsMod:            false,
							IsSub:            true,
							Date:             time.Now(),
							SubscriptionInfo: &models.SubscriptionInfo{Count: resubCount}}
						repos.LogMessage(formedMessage)
						channelInfo, error := repos.GetChannelInfo(channel)
						if error == nil && channelInfo.SubAlert.Enabled == true {
							messageBody := strings.TrimSpace(fmt.Sprintf("%s %s%s",
								channelInfo.SubAlert.RepeatPrefix,
								strings.Repeat(channelInfo.SubAlert.RepeatBody+" ", formedMessage.SubscriptionInfo.Count),
								channelInfo.SubAlert.RepeatPostfix))
							if messageBody != "" {
								IrcClientInstance.SendPublic(models.OutgoingMessage{
									Body:    messageBody,
									Channel: channel,
									User:    user})
							}
						}
						log.Printf("Channel %v: %v resubbed for %v months\n", formedMessage.Channel, formedMessage.User, formedMessage.SubscriptionInfo.Count)
					}
				}
			}
		}
	}

	if message.User == "twitchnotify" {
		log.Println("Got first sub")
		user, userFound := message.Tags.GetTag("display-name")
		channel := message.Params[0]
		log.Println(user)
		log.Println(channel)

		if userFound && channel != "" {
			formedMessage := models.ChatMessage{
				Channel:          channel,
				User:             user,
				IsMod:            false,
				IsSub:            true,
				IsPrime:          strings.Contains(message.String(), "Twitch Prime"),
				Date:             time.Now(),
				SubscriptionInfo: &models.SubscriptionInfo{Count: 1}}
			repos.LogMessage(formedMessage)
			channelInfo, error := repos.GetChannelInfo(channel)
			if error == nil && channelInfo.SubAlert.Enabled == true && channelInfo.SubAlert.FirstMessage != "" {
				IrcClientInstance.SendPublic(models.OutgoingMessage{
					Body:    channelInfo.SubAlert.FirstMessage,
					Channel: channel,
					User:    user})
			}
			log.Printf("Channel %v: %v subbed\n", formedMessage.Channel, formedMessage.User)
		}
	}
	if message.Command == "CLEARCHAT" {
		banDuration, banDurationFound := message.Tags.GetTag("ban-duration")
		intBanDuration := 0
		if banDurationFound {
			parsedValue, parseError := strconv.Atoi(banDuration)
			if parseError == nil {
				intBanDuration = parsedValue
			}
		}
		banReason, _ := message.Tags.GetTag("ban-reason")
		user := message.Params[1]
		channel := message.Params[0]
		formedMessage := models.ChatMessage{
			Channel: channel,
			User:    user,
			IsMod:   false,
			IsSub:   true,
			Date:    time.Now(),
			BanInfo: &models.BanInfo{Duration: intBanDuration, Reason: banReason}}
		repos.LogMessage(formedMessage)
		//	log.Printf("Channel %v: %v is banned for %v \n", channel, user, intBanDuration)
	}
	if message.Command == "PRIVMSG" {
		formedMessage := models.ChatMessage{
			Channel:     message.Params[0][1:],
			User:        message.User,
			MessageBody: message.Params[1],
			IsMod:       message.Tags["mod"] == "1" || message.User == "khadesru",
			IsSub:       message.Tags["subscriber"] == "1",
			IsPrime:     strings.Contains(message.Tags["badges"].Encode(), "premium/1"),
			Date:        time.Now()}
		repos.LogMessage(formedMessage)
		isCommand, commandBody := formedMessage.IsCommand()
		if isCommand {
			handlerFunction := commandHandlers.Router.Go(commandBody.Command)
			handlerFunction(true, &formedMessage, commandBody, &IrcClientInstance)
		}
	}

	if message.Command == "001" {
		client.Write("CAP REQ twitch.tv/tags")
		client.Write("CAP REQ twitch.tv/membership")
		client.Write("CAP REQ twitch.tv/commands")
		for _, value := range repos.Config.Channels {
			client.Write("JOIN #" + value)
		}
		IrcClientInstance = ircClient.IrcClient{Client: client, Bounces: make(map[string]time.Time), Ready: true}
		IrcClientInstance.SendModsCommand()
		log.Println("Bot is started")
	}
}
<commit_msg>Trying to debug broken subalert<commit_after>package bot

import (
	"fmt"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/belak/irc"
	"github.com/khades/servbot/commandHandlers"
	"github.com/khades/servbot/ircClient"
	"github.com/khades/servbot/models"
	"github.com/khades/servbot/repos"
)

var chatHandler irc.HandlerFunc = func(client *irc.Client, message *irc.Message) {
	//	log.Println(message.String())
	msgID, found := message.Tags.GetTag("msg-id")
	if found {
		switch msgID {
		case "room_mods":
			{
				commaIndex := strings.Index(message.Params[1], ":")
				if commaIndex != -1 {
					//				log.Printf("Channel %v: got mods list", message.Params[0])
					mods := strings.Split(message.Params[1][commaIndex+2:], ", ")
					repos.PushMods(message.Params[0][1:], mods)
				}
			}
		case "resub":
			{
				msgParamMonths, msgParamMonthsFound := message.Tags.GetTag("msg-param-months")
				user, userFound := message.Tags.GetTag("display-name")
				channel := message.Params[0]
				if msgParamMonthsFound && userFound && channel != "" {
					resubCount, resubCountError := strconv.Atoi(msgParamMonths)
					if resubCountError == nil {
						formedMessage := models.ChatMessage{
							Channel:          channel,
							User:             user,
							IsMod:            false,
							IsSub:            true,
							Date:             time.Now(),
							SubscriptionInfo: &models.SubscriptionInfo{Count: resubCount}}
						repos.LogMessage(formedMessage)
						channelInfo, error := repos.GetChannelInfo(channel)
						if error == nil && channelInfo.SubAlert.Enabled == true {
							messageBody := strings.TrimSpace(fmt.Sprintf("%s %s%s",
								channelInfo.SubAlert.RepeatPrefix,
								strings.Repeat(channelInfo.SubAlert.RepeatBody+" ", formedMessage.SubscriptionInfo.Count),
								channelInfo.SubAlert.RepeatPostfix))
							if messageBody != "" {
								IrcClientInstance.SendPublic(models.OutgoingMessage{
									Body:    messageBody,
									Channel: channel,
									User:    user})
							}
						}
						log.Printf("Channel %v: %v resubbed for %v months\n", formedMessage.Channel, formedMessage.User, formedMessage.SubscriptionInfo.Count)
					}
				}
			}
		}
	}

	if message.User == "twitchnotify" {
		log.Println("Got first sub")
		user, userFound := message.Tags.GetTag("display-name")
		log.Println(message.Tags.String())
		channel := message.Params[0]
		log.Println(user)
		log.Println(channel)

		if userFound && channel != "" {
			formedMessage := models.ChatMessage{
				Channel:          channel,
				User:             user,
				IsMod:            false,
				IsSub:            true,
				IsPrime:          strings.Contains(message.String(), "Twitch Prime"),
				Date:             time.Now(),
				SubscriptionInfo: &models.SubscriptionInfo{Count: 1}}
			repos.LogMessage(formedMessage)
			channelInfo, error := repos.GetChannelInfo(channel)
			if error == nil && channelInfo.SubAlert.Enabled == true && channelInfo.SubAlert.FirstMessage != "" {
				IrcClientInstance.SendPublic(models.OutgoingMessage{
					Body:    channelInfo.SubAlert.FirstMessage,
					Channel: channel,
					User:    user})
			}
			log.Printf("Channel %v: %v subbed\n", formedMessage.Channel, formedMessage.User)
		}
	}
	if message.Command == "CLEARCHAT" {
		banDuration, banDurationFound := message.Tags.GetTag("ban-duration")
		intBanDuration := 0
		if banDurationFound {
			parsedValue, parseError := strconv.Atoi(banDuration)
			if parseError == nil {
				intBanDuration = parsedValue
			}
		}
		banReason, _ := message.Tags.GetTag("ban-reason")
		user := message.Params[1]
		channel := message.Params[0]
		formedMessage := models.ChatMessage{
			Channel: channel,
			User:    user,
			IsMod:   false,
			IsSub:   true,
			Date:    time.Now(),
			BanInfo: &models.BanInfo{Duration: intBanDuration, Reason: banReason}}
		repos.LogMessage(formedMessage)
		//	log.Printf("Channel %v: %v is banned for %v \n", channel, user, intBanDuration)
	}
	if message.Command == "PRIVMSG" {
		formedMessage := models.ChatMessage{
			Channel:     message.Params[0][1:],
			User:        message.User,
			MessageBody: message.Params[1],
			IsMod:       message.Tags["mod"] == "1" || message.User == "khadesru",
			IsSub:       message.Tags["subscriber"] == "1",
			IsPrime:     strings.Contains(message.Tags["badges"].Encode(), "premium/1"),
			Date:        time.Now()}
		repos.LogMessage(formedMessage)
		isCommand, commandBody := formedMessage.IsCommand()
		if isCommand {
			handlerFunction := commandHandlers.Router.Go(commandBody.Command)
			handlerFunction(true, &formedMessage, commandBody, &IrcClientInstance)
		}
	}

	if message.Command == "001" {
		client.Write("CAP REQ twitch.tv/tags")
		client.Write("CAP REQ twitch.tv/membership")
		client.Write("CAP REQ twitch.tv/commands")
		for _, value := range repos.Config.Channels {
			client.Write("JOIN #" + value)
		}
		IrcClientInstance = ircClient.IrcClient{Client: client, Bounces: make(map[string]time.Time), Ready: true}
		IrcClientInstance.SendModsCommand()
		log.Println("Bot is started")
	}
}
<|endoftext|>


<commit_before>package cliconfigmap

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"

	"github.com/containerum/chkit/pkg/context"
	"github.com/containerum/chkit/pkg/model/configmap"
	"github.com/containerum/chkit/pkg/model/configmap/activeconfigmap"
	"github.com/containerum/chkit/pkg/util/activekit"
	"github.com/containerum/chkit/pkg/util/coblog"
	"github.com/containerum/chkit/pkg/util/namegen"
	"github.com/containerum/kube-client/pkg/model"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	flag "github.com/spf13/pflag"
	"gopkg.in/yaml.v2"
)

var aliases = []string{"cm", "confmap", "conf-map", "comap"}

func Create(ctx *context.Context) *cobra.Command {
	comand := &cobra.Command{
		Use:     "configmap",
		Aliases: aliases,
		Run: func(cmd *cobra.Command, args []string) {
			var logger = coblog.Logger(cmd)
			var flags = cmd.Flags()
			var config, err = buildConfigMapFromFlags(flags, logger)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			force, _ := flags.GetBool("force")
			if !force {
				config = activeconfigmap.Config{
					EditName:  true,
					ConfigMap: &config,
				}.Wizard()
				fmt.Println(config.RenderTable())
			}
			if force || activekit.YesNo("Are you sure you want to create configmap %s?", config.Name) {
				if err := config.Validate(); err != nil {
					fmt.Println(err)
					os.Exit(1)
				}
				if err := ctx.Client.CreateConfigMap(ctx.Namespace.ID, config); err != nil {
					logger.WithError(err).Errorf("unable to create configmap %q", config.Name)
					fmt.Println(err)
					os.Exit(1)
				}
				fmt.Println("OK")
			} else if !force {
				config = activeconfigmap.Config{
					EditName:  false,
					ConfigMap: &config,
				}.Wizard()
				fmt.Println(config.RenderTable())
			}
		},
	}
	var persistentFlags = comand.PersistentFlags()
	persistentFlags.String("name", namegen.Aster()+"-"+namegen.Physicist(), "configmap name")
	persistentFlags.StringSlice("item-string", nil, "configmap item, KEY:VALUE string pair")
	persistentFlags.StringSlice("item-file", nil, "configmap file, KEY:FILE_PATH or FILE_PATH")
	persistentFlags.String("file", "", "file with configmap data")
	persistentFlags.BoolP("force", "f", false, "suppress confirmation")
	return comand
}

func buildConfigMapFromFlags(flags *flag.FlagSet, logger logrus.FieldLogger) (configmap.ConfigMap, error) {
	var config = configmap.ConfigMap{
		Data: make(model.ConfigMapData, 16),
	}
	if flags.Changed("file") {
		var err error
		var fName, _ = flags.GetString("file")
		data, err := ioutil.ReadFile(fName)
		if err != nil {
			logger.WithError(err).Error("unable to load configmap data from file")
			fmt.Println(err)
			os.Exit(1)
		}
		switch path.Ext(fName) {
		case "json":
			err = json.Unmarshal(data, &config)
		case "yaml":
			err = yaml.Unmarshal(data, &config)
		}
		return config, err
	} else {
		config.Name, _ = flags.GetString("name")
		if flags.Changed("item-string") {
			rawItems, _ := flags.GetStringSlice("item-string")
			items, err := getStringItems(rawItems)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			config = config.AddItems(items...)
		}
		if flags.Changed("item-file") {
			rawItems, _ := flags.GetStringSlice("item-file")
			items, err := getFileItems(rawItems)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			config = config.AddItems(items...)
		}
		return config, nil
	}
}

func getFileItems(rawItems []string) ([]configmap.Item, error) {
	var items = make([]configmap.Item, 0, len(rawItems))
	for _, rawItem := range rawItems {
		var filepath string
		var key string
		if tokens := strings.SplitN(rawItem, ":", 2); len(tokens) == 2 {
			key = strings.TrimSpace(tokens[0])
			filepath = tokens[1]
		} else if len(tokens) == 1 {
			key = path.Base(tokens[0])
			filepath = tokens[0]
		} else {
			logrus.Panicf("[chkit/pkg/cli/configmap.getFileItems] ivalid token number in raw file item", len(tokens))
		}
		value, err := ioutil.ReadFile(filepath)
		if err != nil {
			return nil, err
		}
		items = append(items, configmap.Item{
			Key:   key,
			Value: base64.StdEncoding.EncodeToString(value),
		})
	}
	return items, nil
}

func getStringItems(rawItems []string) ([]configmap.Item, error) {
	var items = make([]configmap.Item, 0, len(rawItems))
	for _, rawItem := range rawItems {
		var key string
		var value string
		if tokens := strings.SplitN(rawItem, ":", 2); len(tokens) == 2 {
			key = strings.TrimSpace(tokens[0])
			value = strings.TrimSpace(tokens[1])
		} else {
			logrus.Panicf("[chkit/pkg/cli/configmap.getStringItems] ivalid token number in raw string item", len(tokens))
		}
		items = append(items, configmap.Item{
			Key:   key,
			Value: value,
		})
	}
	return items, nil
}
<commit_msg>Fix create configmap panic<commit_after>package cliconfigmap

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"

	"github.com/containerum/chkit/pkg/context"
	"github.com/containerum/chkit/pkg/model/configmap"
	"github.com/containerum/chkit/pkg/model/configmap/activeconfigmap"
	"github.com/containerum/chkit/pkg/util/activekit"
	"github.com/containerum/chkit/pkg/util/coblog"
	"github.com/containerum/chkit/pkg/util/namegen"
	"github.com/containerum/kube-client/pkg/model"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	flag "github.com/spf13/pflag"
	"gopkg.in/yaml.v2"
)

var aliases = []string{"cm", "confmap", "conf-map", "comap"}

func Create(ctx *context.Context) *cobra.Command {
	comand := &cobra.Command{
		Use:     "configmap",
		Aliases: aliases,
		Run: func(cmd *cobra.Command, args []string) {
			var logger = coblog.Logger(cmd)
			var flags = cmd.Flags()
			var config, err = buildConfigMapFromFlags(flags, logger)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			force, _ := flags.GetBool("force")
			if !force {
				config = activeconfigmap.Config{
					EditName:  true,
					ConfigMap: &config,
				}.Wizard()
				fmt.Println(config.RenderTable())
			}
			if force || activekit.YesNo("Are you sure you want to create configmap %s?", config.Name) {
				if err := config.Validate(); err != nil {
					fmt.Println(err)
					os.Exit(1)
				}
				if err := ctx.Client.CreateConfigMap(ctx.Namespace.ID, config); err != nil {
					logger.WithError(err).Errorf("unable to create configmap %q", config.Name)
					fmt.Println(err)
					os.Exit(1)
				}
				fmt.Println("OK")
			} else if !force {
				config = activeconfigmap.Config{
					EditName:  false,
					ConfigMap: &config,
				}.Wizard()
				fmt.Println(config.RenderTable())
			}
		},
	}
	var persistentFlags = comand.PersistentFlags()
	persistentFlags.String("name", namegen.Aster()+"-"+namegen.Physicist(), "configmap name")
	persistentFlags.StringSlice("item-string", nil, "configmap item, KEY:VALUE string pair")
	persistentFlags.StringSlice("item-file", nil, "configmap file, KEY:FILE_PATH or FILE_PATH")
	persistentFlags.String("file", "", "file with configmap data")
	persistentFlags.BoolP("force", "f", false, "suppress confirmation")
	return comand
}

func buildConfigMapFromFlags(flags *flag.FlagSet, logger logrus.FieldLogger) (configmap.ConfigMap, error) {
	var config = configmap.ConfigMap{
		Data: make(model.ConfigMapData, 16),
	}
	if flags.Changed("file") {
		var err error
		var fName, _ = flags.GetString("file")
		data, err := ioutil.ReadFile(fName)
		if err != nil {
			logger.WithError(err).Error("unable to load configmap data from file")
			fmt.Println(err)
			os.Exit(1)
		}
		switch path.Ext(fName) {
		case "json":
			err = json.Unmarshal(data, &config)
		case "yaml":
			err = yaml.Unmarshal(data, &config)
		}
		return config, err
	} else {
		config.Name, _ = flags.GetString("name")
		if flags.Changed("item-string") {
			rawItems, _ := flags.GetStringSlice("item-string")
			items, err := getStringItems(rawItems)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			config = config.AddItems(items...)
		}
		if flags.Changed("item-file") {
			rawItems, _ := flags.GetStringSlice("item-file")
			items, err := getFileItems(rawItems)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			config = config.AddItems(items...)
		}
		return config, nil
	}
}

func getFileItems(rawItems []string) ([]configmap.Item, error) {
	var items = make([]configmap.Item, 0, len(rawItems))
	for _, rawItem := range rawItems {
		var filepath string
		var key string
		if tokens := strings.SplitN(rawItem, ":", 2); len(tokens) == 2 {
			key = strings.TrimSpace(tokens[0])
			filepath = tokens[1]
		} else if len(tokens) == 1 {
			key = path.Base(tokens[0])
			filepath = tokens[0]
		} else {
			logrus.Panicf("[chkit/pkg/cli/configmap.getFileItems] ivalid token number in raw file item", len(tokens))
		}
		value, err := ioutil.ReadFile(filepath)
		if err != nil {
			return nil, err
		}
		items = append(items, configmap.Item{
			Key:   key,
			Value: base64.StdEncoding.EncodeToString(value),
		})
	}
	return items, nil
}

func getStringItems(rawItems []string) ([]configmap.Item, error) {
	var items = make([]configmap.Item, 0, len(rawItems))
	for _, rawItem := range rawItems {
		var key string
		var value string
		if tokens := strings.SplitN(rawItem, ":", 2); len(tokens) == 2 {
			key = strings.TrimSpace(tokens[0])
			value = strings.TrimSpace(tokens[1])
		} else {
			return nil, fmt.Errorf("invalid token number in raw string item (got %v, required 2)", len(tokens))
		}
		items = append(items, configmap.Item{
			Key:   key,
			Value: value,
		})
	}
	return items, nil
}
<|endoftext|>


<commit_before>package userd

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/spf13/cobra"
	"google.golang.org/grpc"

	"github.com/datawire/dlib/dgroup"
	"github.com/datawire/dlib/dhttp"
	"github.com/datawire/dlib/dlog"
	rpc "github.com/telepresenceio/telepresence/rpc/v2/connector"
	"github.com/telepresenceio/telepresence/rpc/v2/daemon"
	"github.com/telepresenceio/telepresence/rpc/v2/manager"
	"github.com/telepresenceio/telepresence/v2/pkg/client"
	"github.com/telepresenceio/telepresence/v2/pkg/client/cli/cliutil"
	"github.com/telepresenceio/telepresence/v2/pkg/client/logging"
	"github.com/telepresenceio/telepresence/v2/pkg/client/scout"
	"github.com/telepresenceio/telepresence/v2/pkg/client/userd/auth"
	"github.com/telepresenceio/telepresence/v2/pkg/client/userd/internal/broadcastqueue"
	"github.com/telepresenceio/telepresence/v2/pkg/client/userd/trafficmgr"
	"github.com/telepresenceio/telepresence/v2/pkg/filelocation"
	"github.com/telepresenceio/telepresence/v2/pkg/log"
)

const ProcessName = "connector"
const titleName = "Connector"

var help = `The Telepresence ` + titleName + ` is a background component that manages a connection. It
requires that a daemon is already running.

Launch the Telepresence ` + titleName + `:
    telepresence connect

Examine the ` + titleName + `'s log output in
    ` + filepath.Join(func() string { dir, _ := filelocation.AppUserLogDir(context.Background()); return dir }(), ProcessName+".log") + `
to troubleshoot problems.
`

type WithSession func(c context.Context, callName string, f func(context.Context, trafficmgr.Session) error) (err error)

// A daemon service is one that runs during the entire lifecycle of the daemon.
// This should be used to augment the daemon with GRPC services.
type DaemonService interface {
	Name() string
	// Start should start the daemon service. It's expected that it returns and does not block. Any long-running tasks should be
	// managed as goroutines started by Start.
	Start(ctx context.Context, scout *scout.Reporter, grpcServer *grpc.Server, withSession WithSession) error
}

type CommandFactory func() cliutil.CommandGroups

// service represents the long running state of the Telepresence User Daemon
type service struct {
	rpc.UnsafeConnectorServer

	svc               *grpc.Server
	managerProxy      trafficmgr.ManagerProxy
	procName          string
	timedLogLevel     log.TimedLevel
	daemonClient      daemon.DaemonClient
	loginExecutor     auth.LoginExecutor
	userNotifications func(context.Context) <-chan string
	ucn               int64

	scout *scout.Reporter

	quit func()

	session        trafficmgr.Session
	sessionCancel  context.CancelFunc
	sessionContext context.Context
	sessionLock    sync.RWMutex

	// These are used to communicate between the various goroutines.
	connectRequest  chan *rpc.ConnectRequest // server-grpc.connect() -> connectWorker
	connectResponse chan *rpc.ConnectInfo    // connectWorker -> server-grpc.connect()

	// This is used for the service to know which CLI commands it supports
	getCommands CommandFactory
}

func (s *service) SetManagerClient(managerClient manager.ManagerClient, callOptions ...grpc.CallOption) {
	s.managerProxy.SetClient(managerClient, callOptions...)
}

func (s *service) RootDaemonClient(c context.Context) (daemon.DaemonClient, error) {
	if s.daemonClient != nil {
		return s.daemonClient, nil
	}
	// establish a connection to the root daemon gRPC grpcService
	dlog.Info(c, "Connecting to root daemon...")
	conn, err := client.DialSocket(c, client.DaemonSocketName)
	if err != nil {
		dlog.Errorf(c, "unable to connect to root daemon: %+v", err)
		return nil, err
	}
	s.daemonClient = daemon.NewDaemonClient(conn)
	return s.daemonClient, nil
}

func (s *service) LoginExecutor() auth.LoginExecutor {
	return s.loginExecutor
}

// Command returns the CLI sub-command for "connector-foreground"
func Command(getCommands CommandFactory, daemonServices []DaemonService, sessionServices []trafficmgr.SessionService) *cobra.Command {
	c := &cobra.Command{
		Use:    ProcessName + "-foreground",
		Short:  "Launch Telepresence " + titleName + " in the foreground (debug)",
		Args:   cobra.ExactArgs(0),
		Hidden: true,
		Long:   help,
		RunE: func(cmd *cobra.Command, args []string) error {
			return run(cmd.Context(), getCommands, daemonServices, sessionServices)
		},
	}
	return c
}

func (s *service) configReload(c context.Context) error {
	return client.Watch(c, func(c context.Context) error {
		return logging.ReloadDaemonConfig(c, false)
	})
}

// manageSessions is the counterpart to the Connect method. It reads the connectCh, creates
// a session and writes a reply to the connectErrCh. The session is then started if it was
// successfully created.
func (s *service) manageSessions(c context.Context, sessionServices []trafficmgr.SessionService) error {
	// The d.quit is called when we receive a Quit. Since it
	// terminates this function, it terminates the whole process.
	c, s.quit = context.WithCancel(c)
	for {
		// Wait for a connection request
		var oi *rpc.ConnectRequest
		select {
		case <-c.Done():
			return nil
		case oi = <-s.connectRequest:
		}

		// Respond by setting the session and returning the error (or nil
		// if everything is ok)
		s.sessionLock.Lock() // Locked until Run
		var rsp *rpc.ConnectInfo
		s.session, rsp = trafficmgr.NewSession(c, s.scout, oi, s, sessionServices)
		select {
		case <-c.Done():
			s.sessionLock.Unlock()
			return nil
		case s.connectResponse <- rsp:
		}
		if rsp.Error != rpc.ConnectInfo_UNSPECIFIED {
			s.session = nil
			s.sessionLock.Unlock()
			continue
		}

		// Run the session synchronously and ensure that it is cleaned
		// up properly when the context is cancelled
		func(c context.Context) {
			defer func() {
				s.sessionLock.Lock()
				s.session = nil
				s.sessionLock.Unlock()
			}()

			// The d.session.Cancel is called from Disconnect
			c, s.sessionCancel = context.WithCancel(c)
			c = s.session.WithK8sInterface(c)
			s.sessionContext = c
			s.sessionLock.Unlock()
			if err := s.session.Run(c); err != nil {
				dlog.Error(c, err)
			}
		}(c)
	}
}

// run is the main function when executing as the connector
func run(c context.Context, getCommands CommandFactory, daemonServices []DaemonService, sessionServices []trafficmgr.SessionService) error {
	cfg, err := client.LoadConfig(c)
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}
	c = client.WithConfig(c, cfg)
	c = dgroup.WithGoroutineName(c, "/"+ProcessName)
	c, err = logging.InitContext(c, ProcessName, logging.NewRotateOnce())
	if err != nil {
		return err
	}

	// Listen on domain unix domain socket or windows named pipe. The listener must be opened
	// before other tasks because the CLI client will only wait for a short period of time for
	// the socket/pipe to appear before it gives up.
	grpcListener, err := client.ListenSocket(c, ProcessName, client.ConnectorSocketName)
	if err != nil {
		return err
	}
	defer func() {
		_ = client.RemoveSocket(grpcListener)
	}()
	dlog.Debug(c, "Listener opened")

	dlog.Info(c, "---")
	dlog.Infof(c, "Telepresence %s %s starting...", titleName, client.DisplayVersion())
	dlog.Infof(c, "PID is %d", os.Getpid())
	dlog.Info(c, "")

	// Don't bother calling 'conn.Close()', it should remain open until we shut down, and just
	// prefer to let the OS close it when we exit.

	sr := scout.NewReporter(c, "connector")
	cliio := &broadcastqueue.BroadcastQueue{}

	s := &service{
		scout:             sr,
		connectRequest:    make(chan *rpc.ConnectRequest),
		connectResponse:   make(chan *rpc.ConnectInfo),
		managerProxy:      trafficmgr.NewManagerProxy(),
		loginExecutor:     auth.NewStandardLoginExecutor(cliio, sr),
		userNotifications: func(ctx context.Context) <-chan string { return cliio.Subscribe(ctx) },
		timedLogLevel:     log.NewTimedLevel(cfg.LogLevels.UserDaemon.String(), log.SetLevel),
		getCommands:       getCommands,
	}
	if err := logging.LoadTimedLevelFromCache(c, s.timedLogLevel, s.procName); err != nil {
		return err
	}

	g := dgroup.NewGroup(c, dgroup.GroupConfig{
		SoftShutdownTimeout:  2 * time.Second,
		EnableSignalHandling: true,
		ShutdownOnNonError:   true,
	})

	quitOnce := sync.Once{}
	s.quit = func() {
		quitOnce.Do(func() {
			g.Go("quit", func(_ context.Context) error {
				cliio.Close()
				return nil
			})
		})
	}

	g.Go("server-grpc", func(c context.Context) (err error) {
		opts := []grpc.ServerOption{}
		cfg := client.GetConfig(c)
		if !cfg.Grpc.MaxReceiveSize.IsZero() {
			if mz, ok := cfg.Grpc.MaxReceiveSize.AsInt64(); ok {
				opts = append(opts, grpc.MaxRecvMsgSize(int(mz)))
			}
		}
		s.svc = grpc.NewServer(opts...)
		rpc.RegisterConnectorServer(s.svc, s)
		manager.RegisterManagerServer(s.svc, s.managerProxy)
		for _, ds := range daemonServices {
			dlog.Infof(c, "Starting additional daemon service %s", ds.Name())
			ds.Start(c, sr, s.svc, s.withSession)
		}

		sc := &dhttp.ServerConfig{Handler: s.svc}
		dlog.Info(c, "gRPC server started")
		if err = sc.Serve(c, grpcListener); err != nil && c.Err() != nil {
			err = nil // Normal shutdown
		}
		if err != nil {
			dlog.Errorf(c, "gRPC server ended with: %v", err)
		} else {
			dlog.Debug(c, "gRPC server ended")
		}
		return err
	})

	g.Go("config-reload", s.configReload)
	g.Go("session", func(c context.Context) error {
		return s.manageSessions(c, sessionServices)
	})

	// background-systema runs a localhost HTTP server for handling callbacks from the
	// Ambassador Cloud login flow.
	g.Go("background-systema", s.loginExecutor.Worker)

	// background-metriton is the goroutine that handles all telemetry reports, so that calls to
	// metriton don't block the functional goroutines.
	g.Go("background-metriton", s.scout.Run)

	err = g.Wait()
	if err != nil {
		dlog.Error(c, err)
	}
	return err
}
<commit_msg>Fix merge glitch<commit_after>package userd

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/spf13/cobra"
	"google.golang.org/grpc"

	"github.com/datawire/dlib/dgroup"
	"github.com/datawire/dlib/dhttp"
	"github.com/datawire/dlib/dlog"
	rpc "github.com/telepresenceio/telepresence/rpc/v2/connector"
	"github.com/telepresenceio/telepresence/rpc/v2/daemon"
	"github.com/telepresenceio/telepresence/rpc/v2/manager"
	"github.com/telepresenceio/telepresence/v2/pkg/client"
	"github.com/telepresenceio/telepresence/v2/pkg/client/cli/cliutil"
	"github.com/telepresenceio/telepresence/v2/pkg/client/logging"
	"github.com/telepresenceio/telepresence/v2/pkg/client/scout"
	"github.com/telepresenceio/telepresence/v2/pkg/client/userd/auth"
	"github.com/telepresenceio/telepresence/v2/pkg/client/userd/internal/broadcastqueue"
	"github.com/telepresenceio/telepresence/v2/pkg/client/userd/trafficmgr"
	"github.com/telepresenceio/telepresence/v2/pkg/filelocation"
	"github.com/telepresenceio/telepresence/v2/pkg/log"
)

const ProcessName = "connector"
const titleName = "Connector"

var help = `The Telepresence ` + titleName + ` is a background component that manages a connection. It
requires that a daemon is already running.

Launch the Telepresence ` + titleName + `:
    telepresence connect

Examine the ` + titleName + `'s log output in
    ` + filepath.Join(func() string { dir, _ := filelocation.AppUserLogDir(context.Background()); return dir }(), ProcessName+".log") + `
to troubleshoot problems.
`

type WithSession func(c context.Context, callName string, f func(context.Context, trafficmgr.Session) error) (err error)

// A DaemonService is one that runs during the entire lifecycle of the daemon.
// This should be used to augment the daemon with GRPC services.
type DaemonService interface {
	Name() string
	// Start should start the daemon service. It's expected that it returns and does not block. Any long-running tasks should be
	// managed as goroutines started by Start.
	Start(ctx context.Context, scout *scout.Reporter, grpcServer *grpc.Server, withSession WithSession) error
}

type CommandFactory func() cliutil.CommandGroups

// service represents the long running state of the Telepresence User Daemon
type service struct {
	rpc.UnsafeConnectorServer

	svc               *grpc.Server
	managerProxy      trafficmgr.ManagerProxy
	procName          string
	timedLogLevel     log.TimedLevel
	daemonClient      daemon.DaemonClient
	loginExecutor     auth.LoginExecutor
	userNotifications func(context.Context) <-chan string
	ucn               int64

	scout *scout.Reporter

	quit func()

	session        trafficmgr.Session
	sessionCancel  context.CancelFunc
	sessionContext context.Context
	sessionLock    sync.RWMutex

	// These are used to communicate between the various goroutines.
	connectRequest  chan *rpc.ConnectRequest // server-grpc.connect() -> connectWorker
	connectResponse chan *rpc.ConnectInfo    // connectWorker -> server-grpc.connect()

	// This is used for the service to know which CLI commands it supports
	getCommands CommandFactory
}

func (s *service) SetManagerClient(managerClient manager.ManagerClient, callOptions ...grpc.CallOption) {
	s.managerProxy.SetClient(managerClient, callOptions...)
}

func (s *service) RootDaemonClient(c context.Context) (daemon.DaemonClient, error) {
	if s.daemonClient != nil {
		return s.daemonClient, nil
	}
	// establish a connection to the root daemon gRPC grpcService
	dlog.Info(c, "Connecting to root daemon...")
	conn, err := client.DialSocket(c, client.DaemonSocketName)
	if err != nil {
		dlog.Errorf(c, "unable to connect to root daemon: %+v", err)
		return nil, err
	}
	s.daemonClient = daemon.NewDaemonClient(conn)
	return s.daemonClient, nil
}

func (s *service) LoginExecutor() auth.LoginExecutor {
	return s.loginExecutor
}

// Command returns the CLI sub-command for "connector-foreground"
func Command(getCommands CommandFactory, daemonServices []DaemonService, sessionServices []trafficmgr.SessionService) *cobra.Command {
	c := &cobra.Command{
		Use:    ProcessName + "-foreground",
		Short:  "Launch Telepresence " + titleName + " in the foreground (debug)",
		Args:   cobra.ExactArgs(0),
		Hidden: true,
		Long:   help,
		RunE: func(cmd *cobra.Command, args []string) error {
			return run(cmd.Context(), getCommands, daemonServices, sessionServices)
		},
	}
	return c
}

func (s *service) configReload(c context.Context) error {
	return client.Watch(c, func(c context.Context) error {
		return logging.ReloadDaemonConfig(c, false)
	})
}

// manageSessions is the counterpart to the Connect method. It reads the connectCh, creates
// a session and writes a reply to the connectErrCh. The session is then started if it was
// successfully created.
func (s *service) manageSessions(c context.Context, sessionServices []trafficmgr.SessionService) error {
	// The d.quit is called when we receive a Quit. Since it
	// terminates this function, it terminates the whole process.
	c, s.quit = context.WithCancel(c)
	for {
		// Wait for a connection request
		var oi *rpc.ConnectRequest
		select {
		case <-c.Done():
			return nil
		case oi = <-s.connectRequest:
		}

		// Respond by setting the session and returning the error (or nil
		// if everything is ok)
		s.sessionLock.Lock() // Locked until Run
		var rsp *rpc.ConnectInfo
		s.session, rsp = trafficmgr.NewSession(c, s.scout, oi, s, sessionServices)
		select {
		case <-c.Done():
			s.sessionLock.Unlock()
			return nil
		case s.connectResponse <- rsp:
		}
		if rsp.Error != rpc.ConnectInfo_UNSPECIFIED {
			s.session = nil
			s.sessionLock.Unlock()
			continue
		}

		// Run the session synchronously and ensure that it is cleaned
		// up properly when the context is cancelled
		func(c context.Context) {
			defer func() {
				s.sessionLock.Lock()
				s.session = nil
				s.sessionLock.Unlock()
			}()

			// The d.session.Cancel is called from Disconnect
			c, s.sessionCancel = context.WithCancel(c)
			c = s.session.WithK8sInterface(c)
			s.sessionContext = c
			s.sessionLock.Unlock()
			if err := s.session.Run(c); err != nil {
				dlog.Error(c, err)
			}
		}(c)
	}
}

// run is the main function when executing as the connector
func run(c context.Context, getCommands CommandFactory, daemonServices []DaemonService, sessionServices []trafficmgr.SessionService) error {
	cfg, err := client.LoadConfig(c)
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}
	c = client.WithConfig(c, cfg)
	c = dgroup.WithGoroutineName(c, "/"+ProcessName)
	c, err = logging.InitContext(c, ProcessName, logging.NewRotateOnce())
	if err != nil {
		return err
	}

	// Listen on domain unix domain socket or windows named pipe. The listener must be opened
	// before other tasks because the CLI client will only wait for a short period of time for
	// the socket/pipe to appear before it gives up.
	grpcListener, err := client.ListenSocket(c, ProcessName, client.ConnectorSocketName)
	if err != nil {
		return err
	}
	defer func() {
		_ = client.RemoveSocket(grpcListener)
	}()
	dlog.Debug(c, "Listener opened")

	dlog.Info(c, "---")
	dlog.Infof(c, "Telepresence %s %s starting...", titleName, client.DisplayVersion())
	dlog.Infof(c, "PID is %d", os.Getpid())
	dlog.Info(c, "")

	// Don't bother calling 'conn.Close()', it should remain open until we shut down, and just
	// prefer to let the OS close it when we exit.

	sr := scout.NewReporter(c, "connector")
	cliio := &broadcastqueue.BroadcastQueue{}

	s := &service{
		scout:             sr,
		connectRequest:    make(chan *rpc.ConnectRequest),
		connectResponse:   make(chan *rpc.ConnectInfo),
		managerProxy:      trafficmgr.NewManagerProxy(),
		loginExecutor:     auth.NewStandardLoginExecutor(cliio, sr),
		userNotifications: func(ctx context.Context) <-chan string { return cliio.Subscribe(ctx) },
		timedLogLevel:     log.NewTimedLevel(cfg.LogLevels.UserDaemon.String(), log.SetLevel),
		getCommands:       getCommands,
	}
	if err := logging.LoadTimedLevelFromCache(c, s.timedLogLevel, s.procName); err != nil {
		return err
	}

	g := dgroup.NewGroup(c, dgroup.GroupConfig{
		SoftShutdownTimeout:  2 * time.Second,
		EnableSignalHandling: true,
		ShutdownOnNonError:   true,
	})

	quitOnce := sync.Once{}
	s.quit = func() {
		quitOnce.Do(func() {
			g.Go("quit", func(_ context.Context) error {
				cliio.Close()
				return nil
			})
		})
	}

	g.Go("server-grpc", func(c context.Context) (err error) {
		opts := []grpc.ServerOption{}
		cfg := client.GetConfig(c)
		if !cfg.Grpc.MaxReceiveSize.IsZero() {
			if mz, ok := cfg.Grpc.MaxReceiveSize.AsInt64(); ok {
				opts = append(opts, grpc.MaxRecvMsgSize(int(mz)))
			}
		}
		s.svc = grpc.NewServer(opts...)
		rpc.RegisterConnectorServer(s.svc, s)
		manager.RegisterManagerServer(s.svc, s.managerProxy)
		for _, ds := range daemonServices {
			dlog.Infof(c, "Starting additional daemon service %s", ds.Name())
			ds.Start(c, sr, s.svc, s.withSession)
		}

		sc := &dhttp.ServerConfig{Handler: s.svc}
		dlog.Info(c, "gRPC server started")
		if err = sc.Serve(c, grpcListener); err != nil && c.Err() != nil {
			err = nil // Normal shutdown
		}
		if err != nil {
			dlog.Errorf(c, "gRPC server ended with: %v", err)
		} else {
			dlog.Debug(c, "gRPC server ended")
		}
		return err
	})

	g.Go("config-reload", s.configReload)
	g.Go("session", func(c context.Context) error {
		return s.manageSessions(c, sessionServices)
	})

	// background-systema runs a localhost HTTP server for handling callbacks from the
	// Ambassador Cloud login flow.
	g.Go("background-systema", s.loginExecutor.Worker)

	// background-metriton is the goroutine that handles all telemetry reports, so that calls to
	// metriton don't block the functional goroutines.
	g.Go("background-metriton", s.scout.Run)

	err = g.Wait()
	if err != nil {
		dlog.Error(c, err)
	}
	return err
}
<|endoftext|>


<commit_before>package dashboard

import (
	"reflect"

	"github.com/rancher/rancher/pkg/features"
	"github.com/rancher/rancher/pkg/wrangler"
	rbacv1 "github.com/rancher/wrangler/pkg/generated/controllers/rbac/v1"
	v1 "k8s.io/api/rbac/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func AddFleetRoles(wrangler *wrangler.Context) error {
	f, err := wrangler.Mgmt.Feature().Get("fleet", metav1.GetOptions{})
	if err != nil {
		return err
	}

	if !features.IsEnabled(f) {
		return nil
	}

	return ensureFleetRoles(wrangler.RBAC)
}

func ensureFleetRoles(rbac rbacv1.Interface) error {
	fleetWorkspaceAdminRole := v1.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name: "fleetworkspace-admin",
		},
		Rules: []v1.PolicyRule{
			{
				APIGroups: []string{
					"fleet.cattle.io",
				},
				Resources: []string{
					"clusterregistrationtokens",
					"gitreporestrictions",
					"clusterregistrations",
					"clusters",
					"gitrepos",
					"bundles",
					"clustergroups",
				},
				Verbs: []string{
					"*",
				},
			},
			{
				APIGroups: []string{
					"rbac.authorization.k8s.io",
				},
				Resources: []string{
					"rolebindings",
				},
				Verbs: []string{
					"*",
				},
			},
		},
	}

	fleetWorkspaceMemberRole := v1.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name: "fleetworkspace-member",
		},
		Rules: []v1.PolicyRule{
			{
				APIGroups: []string{
					"fleet.cattle.io",
				},
				Resources: []string{
					"gitrepos",
					"bundles",
				},
				Verbs: []string{
					"*",
				},
			},
			{
				APIGroups: []string{
					"fleet.cattle.io",
				},
				Resources: []string{
					"clusterregistrationtokens",
					"gitreporestrictions",
					"clusterregistrations",
					"clusters",
					"clustergroups",
				},
				Verbs: []string{
					"get",
					"list",
					"watch",
				},
			},
		},
	}

	fleetWorkspaceReadonlyRole := v1.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name: "fleetworkspace-readonly",
		},
		Rules: []v1.PolicyRule{
			{
				APIGroups: []string{
					"fleet.cattle.io",
				},
				Resources: []string{
					"clusterregistrationtokens",
					"gitreporestrictions",
					"clusterregistrations",
					"clusters",
					"gitrepos",
					"bundles",
					"clustergroups",
				},
				Verbs: []string{
					"get",
					"list",
					"watch",
				},
			},
		},
	}

	clusterRoles := []v1.ClusterRole{
		fleetWorkspaceAdminRole,
		fleetWorkspaceMemberRole,
		fleetWorkspaceReadonlyRole,
	}

	for _, role := range clusterRoles {
		existing, err := rbac.ClusterRole().Get(role.Name, metav1.GetOptions{})
		if err != nil && !errors.IsNotFound(err) {
			return err
		} else if errors.IsNotFound(err) {
			if _, err := rbac.ClusterRole().Create(&role); err != nil {
				return err
			}
		} else {
			if !reflect.DeepEqual(existing.Rules, role.Rules) {
				if _, err := rbac.ClusterRole().Update(&role); err != nil {
					return err
				}
			}
		}
	}

	return nil
}
<commit_msg>Add UI labels for fleet roles<commit_after>package dashboard

import (
	"reflect"

	"github.com/rancher/rancher/pkg/features"
	"github.com/rancher/rancher/pkg/wrangler"
	rbacv1 "github.com/rancher/wrangler/pkg/generated/controllers/rbac/v1"
	v1 "k8s.io/api/rbac/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func AddFleetRoles(wrangler *wrangler.Context) error {
	f, err := wrangler.Mgmt.Feature().Get("fleet", metav1.GetOptions{})
	if err != nil {
		return err
	}

	if !features.IsEnabled(f) {
		return nil
	}

	return ensureFleetRoles(wrangler.RBAC)
}

func ensureFleetRoles(rbac rbacv1.Interface) error {
	uiLabels := map[string]string{
		"management.cattle.io/ui-product": "fleet",
	}
	fleetWorkspaceAdminRole := v1.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name:   "fleetworkspace-admin",
			Labels: uiLabels,
		},
		Rules: []v1.PolicyRule{
			{
				APIGroups: []string{
					"fleet.cattle.io",
				},
				Resources: []string{
					"clusterregistrationtokens",
					"gitreporestrictions",
					"clusterregistrations",
					"clusters",
					"gitrepos",
					"bundles",
					"clustergroups",
				},
				Verbs: []string{
					"*",
				},
			},
			{
				APIGroups: []string{
					"rbac.authorization.k8s.io",
				},
				Resources: []string{
					"rolebindings",
				},
				Verbs: []string{
					"*",
				},
			},
		},
	}

	fleetWorkspaceMemberRole := v1.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name:   "fleetworkspace-member",
			Labels: uiLabels,
		},
		Rules: []v1.PolicyRule{
			{
				APIGroups: []string{
					"fleet.cattle.io",
				},
				Resources: []string{
					"gitrepos",
					"bundles",
				},
				Verbs: []string{
					"*",
				},
			},
			{
				APIGroups: []string{
					"fleet.cattle.io",
				},
				Resources: []string{
					"clusterregistrationtokens",
					"gitreporestrictions",
					"clusterregistrations",
					"clusters",
					"clustergroups",
				},
				Verbs: []string{
					"get",
					"list",
					"watch",
				},
			},
		},
	}

	fleetWorkspaceReadonlyRole := v1.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name:   "fleetworkspace-readonly",
			Labels: uiLabels,
		},
		Rules: []v1.PolicyRule{
			{
				APIGroups: []string{
					"fleet.cattle.io",
				},
				Resources: []string{
					"clusterregistrationtokens",
					"gitreporestrictions",
					"clusterregistrations",
					"clusters",
					"gitrepos",
					"bundles",
					"clustergroups",
				},
				Verbs: []string{
					"get",
					"list",
					"watch",
				},
			},
		},
	}

	clusterRoles := []v1.ClusterRole{
		fleetWorkspaceAdminRole,
		fleetWorkspaceMemberRole,
		fleetWorkspaceReadonlyRole,
	}

	for _, role := range clusterRoles {
		existing, err := rbac.ClusterRole().Get(role.Name, metav1.GetOptions{})
		if err != nil && !errors.IsNotFound(err) {
			return err
		} else if errors.IsNotFound(err) {
			if _, err := rbac.ClusterRole().Create(&role); err != nil {
				return err
			}
		} else {
			if !reflect.DeepEqual(existing.Rules, role.Rules) || !reflect.DeepEqual(existing.Labels, role.Labels) {
				if _, err := rbac.ClusterRole().Update(&role); err != nil {
					return err
				}
			}
		}
	}

	return nil
}
<|endoftext|>


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

package loader

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"

	"github.com/cilium/cilium/pkg/bpf"
	"github.com/cilium/cilium/pkg/byteorder"
	"github.com/cilium/cilium/pkg/cgroups"
	"github.com/cilium/cilium/pkg/command/exec"
	"github.com/cilium/cilium/pkg/common"
	"github.com/cilium/cilium/pkg/datapath"
	"github.com/cilium/cilium/pkg/datapath/alignchecker"
	datapathOption "github.com/cilium/cilium/pkg/datapath/option"
	"github.com/cilium/cilium/pkg/datapath/prefilter"
	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/node"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/sysctl"
	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
)

const (
	initArgLib int = iota
	initArgRundir
	initArgIPv4NodeIP
	initArgIPv6NodeIP
	initArgMode
	initArgDevices
	initArgXDPDevice
	initArgXDPMode
	initArgMTU
	initArgIPSec
	initArgEncryptInterface
	initArgHostReachableServices
	initArgHostReachableServicesUDP
	initArgHostReachableServicesPeer
	initArgCgroupRoot
	initArgBpffsRoot
	initArgNodePort
	initArgNodePortBind
	initBPFCPU
	initArgNodePortIPv4Addrs
	initArgNodePortIPv6Addrs
	initArgMax
)

// firstInitialization is true when Reinitialize() is called for the first
// time. It can only be accessed when GetCompilationLock() is being held.
var firstInitialization = true

func (l *Loader) writeNetdevHeader(dir string, o datapath.BaseProgramOwner) error {
	headerPath := filepath.Join(dir, common.NetdevHeaderFileName)
	log.WithField(logfields.Path, headerPath).Debug("writing configuration")

	f, err := os.Create(headerPath)
	if err != nil {
		return fmt.Errorf("failed to open file %s for writing: %s", headerPath, err)

	}
	defer f.Close()

	if err := l.templateCache.WriteNetdevConfig(f, o); err != nil {
		return err
	}
	return nil
}

// Must be called with option.Config.EnablePolicyMU locked.
func writePreFilterHeader(preFilter *prefilter.PreFilter, dir string) error {
	headerPath := filepath.Join(dir, common.PreFilterHeaderFileName)
	log.WithField(logfields.Path, headerPath).Debug("writing configuration")
	f, err := os.Create(headerPath)
	if err != nil {
		return fmt.Errorf("failed to open file %s for writing: %s", headerPath, err)

	}
	defer f.Close()
	fw := bufio.NewWriter(f)
	fmt.Fprint(fw, "/*\n")
	fmt.Fprintf(fw, " * XDP device: %s\n", option.Config.DevicePreFilter)
	fmt.Fprintf(fw, " * XDP mode: %s\n", option.Config.ModePreFilter)
	fmt.Fprint(fw, " */\n\n")
	preFilter.WriteConfig(fw)
	return fw.Flush()
}

// Reinitialize (re-)configures the base datapath configuration including global
// BPF programs, netfilter rule configuration and reserving routes in IPAM for
// locally detected prefixes. It may be run upon initial Cilium startup, after
// restore from a previous Cilium run, or during regular Cilium operation.
func (l *Loader) Reinitialize(ctx context.Context, o datapath.BaseProgramOwner, deviceMTU int, iptMgr datapath.IptablesManager, p datapath.Proxy, r datapath.RouteReserver) error {
	var (
		args []string
		mode string
		ret  error
	)

	type setting struct {
		name      string
		val       string
		ignoreErr bool
	}

	args = make([]string, initArgMax)

	sysSettings := []setting{
		{"net.core.bpf_jit_enable", "1", true},
		{"net.ipv4.conf.all.rp_filter", "0", false},
		{"kernel.unprivileged_bpf_disabled", "1", true},
	}

	// Lock so that endpoints cannot be built while we are compile base programs.
	o.GetCompilationLock().Lock()
	defer o.GetCompilationLock().Unlock()
	defer func() { firstInitialization = false }()

	l.init(o.Datapath(), o.LocalConfig())

	if err := l.writeNetdevHeader("./", o); err != nil {
		log.WithError(err).Warn("Unable to write netdev header")
		return err
	}

	if option.Config.XDPDevice != "undefined" {
		args[initArgXDPDevice] = option.Config.XDPDevice
		args[initArgXDPMode] = option.Config.XDPMode
	} else {
		args[initArgXDPDevice] = "<nil>"
		args[initArgXDPMode] = "<nil>"
	}

	if option.Config.DevicePreFilter != "undefined" {
		scopedLog := log.WithField(logfields.XDPDevice, option.Config.XDPDevice)

		preFilter, err := prefilter.NewPreFilter()
		if err != nil {
			scopedLog.WithError(ret).Warn("Unable to init prefilter")
			return ret
		}

		if err := writePreFilterHeader(preFilter, "./"); err != nil {
			scopedLog.WithError(err).Warn("Unable to write prefilter header")
			return err
		}

		o.SetPrefilter(preFilter)
	}

	args[initArgLib] = option.Config.BpfDir
	args[initArgRundir] = option.Config.StateDir
	args[initArgCgroupRoot] = cgroups.GetCgroupRoot()
	args[initArgBpffsRoot] = bpf.GetMapRoot()

	if option.Config.EnableIPv4 {
		args[initArgIPv4NodeIP] = node.GetInternalIPv4().String()
	} else {
		args[initArgIPv4NodeIP] = "<nil>"
	}

	if option.Config.EnableIPv6 {
		args[initArgIPv6NodeIP] = node.GetIPv6().String()
		// Docker <17.05 has an issue which causes IPv6 to be disabled in the initns for all
		// interface (https://github.com/docker/libnetwork/issues/1720)
		// Enable IPv6 for now
		sysSettings = append(sysSettings,
			setting{"net.ipv6.conf.all.disable_ipv6", "0", false})
	} else {
		args[initArgIPv6NodeIP] = "<nil>"
	}

	args[initArgMTU] = fmt.Sprintf("%d", deviceMTU)

	if option.Config.EnableIPSec {
		args[initArgIPSec] = "true"
	} else {
		args[initArgIPSec] = "false"
	}

	if option.Config.EnableHostReachableServices {
		args[initArgHostReachableServices] = "true"
		if option.Config.EnableHostServicesUDP {
			args[initArgHostReachableServicesUDP] = "true"
		} else {
			args[initArgHostReachableServicesUDP] = "false"
		}
		if option.Config.EnableHostServicesPeer {
			args[initArgHostReachableServicesPeer] = "true"
		} else {
			args[initArgHostReachableServicesPeer] = "false"
		}
	} else {
		args[initArgHostReachableServices] = "false"
		args[initArgHostReachableServicesUDP] = "false"
		args[initArgHostReachableServicesPeer] = "false"
	}

	if option.Config.EncryptInterface != "" {
		args[initArgEncryptInterface] = option.Config.EncryptInterface
	} else {
		args[initArgEncryptInterface] = "<nil>"
	}

	if len(option.Config.Devices) != 0 {
		for _, device := range option.Config.Devices {
			_, err := netlink.LinkByName(device)
			if err != nil {
				log.WithError(err).WithField("device", device).Warn("Link does not exist")
				return err
			}
		}

		if option.Config.DatapathMode == datapathOption.DatapathModeIpvlan {
			mode = "ipvlan"
		} else {
			mode = "direct"
		}

		args[initArgMode] = mode
		if option.Config.EnableNodePort &&
			strings.ToLower(option.Config.Tunnel) != "disabled" {
			args[initArgMode] = option.Config.Tunnel
		}
		args[initArgDevices] = strings.Join(option.Config.Devices, ";")
	} else {
		args[initArgMode] = option.Config.Tunnel
		args[initArgDevices] = "<nil>"

		if option.Config.IsFlannelMasterDeviceSet() {
			args[initArgMode] = "flannel"
			args[initArgDevices] = option.Config.FlannelMasterDevice
		}
	}

	if option.Config.EnableEndpointRoutes == true {
		args[initArgMode] = "routed"
	}

	if option.Config.EnableNodePort {
		args[initArgNodePort] = "true"
		if option.Config.EnableIPv4 {
			addrs := node.GetNodePortIPv4AddrsWithDevices()
			tmp := make([]string, 0, len(addrs))
			for iface, ipv4 := range addrs {
				tmp = append(tmp,
					fmt.Sprintf("%s=%#x", iface,
						byteorder.HostSliceToNetwork(ipv4, reflect.Uint32).(uint32)))
			}
			args[initArgNodePortIPv4Addrs] = strings.Join(tmp, ";")
		} else {
			args[initArgNodePortIPv4Addrs] = "<nil>"
		}
		if option.Config.EnableIPv6 {
			addrs := node.GetNodePortIPv6AddrsWithDevices()
			tmp := make([]string, 0, len(addrs))
			for iface, ipv6 := range addrs {
				tmp = append(tmp, fmt.Sprintf("%s=%s", iface, common.GoArray2CNoSpaces(ipv6)))
			}
			args[initArgNodePortIPv6Addrs] = strings.Join(tmp, ";")
		} else {
			args[initArgNodePortIPv6Addrs] = "<nil>"
		}
	} else {
		args[initArgNodePort] = "false"
		args[initArgNodePortIPv4Addrs] = "<nil>"
		args[initArgNodePortIPv6Addrs] = "<nil>"
	}

	if option.Config.NodePortBindProtection {
		args[initArgNodePortBind] = "true"
	} else {
		args[initArgNodePortBind] = "false"
	}

	args[initBPFCPU] = GetBPFCPU()

	clockSource := []string{"ktime", "jiffies"}
	log.Infof("Setting up base BPF datapath (BPF %s instruction set, %s clock source)",
		args[initBPFCPU], clockSource[option.Config.ClockSource])

	for _, s := range sysSettings {
		log.Infof("Setting sysctl %s=%s", s.name, s.val)
		if err := sysctl.Write(s.name, s.val); err != nil {
			if !s.ignoreErr {
				return fmt.Errorf("Failed to sysctl -w %s=%s: %s", s.name, s.val, err)
			}
			log.WithError(err).WithFields(logrus.Fields{
				logfields.SysParamName:  s.name,
				logfields.SysParamValue: s.val,
			}).Warning("Failed to sysctl -w")
		}
	}

	for i, arg := range args {
		if arg == "" {
			log.Warningf("empty argument passed to bpf/init.sh at position %d", i)
		}
	}

	prog := filepath.Join(option.Config.BpfDir, "init.sh")
	ctx, cancel := context.WithTimeout(ctx, defaults.ExecTimeout)
	defer cancel()
	cmd := exec.CommandContext(ctx, prog, args...)
	cmd.Env = bpf.Environment()
	if _, err := cmd.CombinedOutput(log, true); err != nil {
		return err
	}

	if l.canDisableDwarfRelocations {
		// Validate alignments of C and Go equivalent structs
		if err := alignchecker.CheckStructAlignments(defaults.AlignCheckerName); err != nil {
			log.WithError(err).Fatal("C and Go structs alignment check failed")
		}
	} else {
		log.Warning("Cannot check matching of C and Go common struct alignments due to old LLVM/clang version")
	}

	if !option.Config.IsFlannelMasterDeviceSet() {
		r.ReserveLocalRoutes()
	}

	if err := o.Datapath().Node().NodeConfigurationChanged(*o.LocalConfig()); err != nil {
		return err
	}

	if option.Config.InstallIptRules {
		if err := iptMgr.TransientRulesStart(option.Config.HostDevice); err != nil {
			return err
		}
	}
	// The iptables rules are only removed on the first initialization to
	// remove stale rules or when iptables is enabled. The first invocation
	// is silent as rules may not exist.
	if firstInitialization || option.Config.InstallIptRules {
		iptMgr.RemoveRules(firstInitialization)
	}
	if option.Config.InstallIptRules {
		err := iptMgr.InstallRules(option.Config.HostDevice)
		iptMgr.TransientRulesEnd(false)
		if err != nil {
			return err
		}
	}
	// Reinstall proxy rules for any running proxies
	if p != nil {
		p.ReinstallRules()
	}

	return nil
}
<commit_msg>loader: Fix tunneling when device is set without NodePort<commit_after>// Copyright 2016-2019 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package loader

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"

	"github.com/cilium/cilium/pkg/bpf"
	"github.com/cilium/cilium/pkg/byteorder"
	"github.com/cilium/cilium/pkg/cgroups"
	"github.com/cilium/cilium/pkg/command/exec"
	"github.com/cilium/cilium/pkg/common"
	"github.com/cilium/cilium/pkg/datapath"
	"github.com/cilium/cilium/pkg/datapath/alignchecker"
	datapathOption "github.com/cilium/cilium/pkg/datapath/option"
	"github.com/cilium/cilium/pkg/datapath/prefilter"
	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/node"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/sysctl"
	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
)

const (
	initArgLib int = iota
	initArgRundir
	initArgIPv4NodeIP
	initArgIPv6NodeIP
	initArgMode
	initArgDevices
	initArgXDPDevice
	initArgXDPMode
	initArgMTU
	initArgIPSec
	initArgEncryptInterface
	initArgHostReachableServices
	initArgHostReachableServicesUDP
	initArgHostReachableServicesPeer
	initArgCgroupRoot
	initArgBpffsRoot
	initArgNodePort
	initArgNodePortBind
	initBPFCPU
	initArgNodePortIPv4Addrs
	initArgNodePortIPv6Addrs
	initArgMax
)

// firstInitialization is true when Reinitialize() is called for the first
// time. It can only be accessed when GetCompilationLock() is being held.
var firstInitialization = true

func (l *Loader) writeNetdevHeader(dir string, o datapath.BaseProgramOwner) error {
	headerPath := filepath.Join(dir, common.NetdevHeaderFileName)
	log.WithField(logfields.Path, headerPath).Debug("writing configuration")

	f, err := os.Create(headerPath)
	if err != nil {
		return fmt.Errorf("failed to open file %s for writing: %s", headerPath, err)

	}
	defer f.Close()

	if err := l.templateCache.WriteNetdevConfig(f, o); err != nil {
		return err
	}
	return nil
}

// Must be called with option.Config.EnablePolicyMU locked.
func writePreFilterHeader(preFilter *prefilter.PreFilter, dir string) error {
	headerPath := filepath.Join(dir, common.PreFilterHeaderFileName)
	log.WithField(logfields.Path, headerPath).Debug("writing configuration")
	f, err := os.Create(headerPath)
	if err != nil {
		return fmt.Errorf("failed to open file %s for writing: %s", headerPath, err)

	}
	defer f.Close()
	fw := bufio.NewWriter(f)
	fmt.Fprint(fw, "/*\n")
	fmt.Fprintf(fw, " * XDP device: %s\n", option.Config.DevicePreFilter)
	fmt.Fprintf(fw, " * XDP mode: %s\n", option.Config.ModePreFilter)
	fmt.Fprint(fw, " */\n\n")
	preFilter.WriteConfig(fw)
	return fw.Flush()
}

// Reinitialize (re-)configures the base datapath configuration including global
// BPF programs, netfilter rule configuration and reserving routes in IPAM for
// locally detected prefixes. It may be run upon initial Cilium startup, after
// restore from a previous Cilium run, or during regular Cilium operation.
func (l *Loader) Reinitialize(ctx context.Context, o datapath.BaseProgramOwner, deviceMTU int, iptMgr datapath.IptablesManager, p datapath.Proxy, r datapath.RouteReserver) error {
	var (
		args []string
		ret  error
	)

	type setting struct {
		name      string
		val       string
		ignoreErr bool
	}

	args = make([]string, initArgMax)

	sysSettings := []setting{
		{"net.core.bpf_jit_enable", "1", true},
		{"net.ipv4.conf.all.rp_filter", "0", false},
		{"kernel.unprivileged_bpf_disabled", "1", true},
	}

	// Lock so that endpoints cannot be built while we are compile base programs.
	o.GetCompilationLock().Lock()
	defer o.GetCompilationLock().Unlock()
	defer func() { firstInitialization = false }()

	l.init(o.Datapath(), o.LocalConfig())

	if err := l.writeNetdevHeader("./", o); err != nil {
		log.WithError(err).Warn("Unable to write netdev header")
		return err
	}

	if option.Config.XDPDevice != "undefined" {
		args[initArgXDPDevice] = option.Config.XDPDevice
		args[initArgXDPMode] = option.Config.XDPMode
	} else {
		args[initArgXDPDevice] = "<nil>"
		args[initArgXDPMode] = "<nil>"
	}

	if option.Config.DevicePreFilter != "undefined" {
		scopedLog := log.WithField(logfields.XDPDevice, option.Config.XDPDevice)

		preFilter, err := prefilter.NewPreFilter()
		if err != nil {
			scopedLog.WithError(ret).Warn("Unable to init prefilter")
			return ret
		}

		if err := writePreFilterHeader(preFilter, "./"); err != nil {
			scopedLog.WithError(err).Warn("Unable to write prefilter header")
			return err
		}

		o.SetPrefilter(preFilter)
	}

	args[initArgLib] = option.Config.BpfDir
	args[initArgRundir] = option.Config.StateDir
	args[initArgCgroupRoot] = cgroups.GetCgroupRoot()
	args[initArgBpffsRoot] = bpf.GetMapRoot()

	if option.Config.EnableIPv4 {
		args[initArgIPv4NodeIP] = node.GetInternalIPv4().String()
	} else {
		args[initArgIPv4NodeIP] = "<nil>"
	}

	if option.Config.EnableIPv6 {
		args[initArgIPv6NodeIP] = node.GetIPv6().String()
		// Docker <17.05 has an issue which causes IPv6 to be disabled in the initns for all
		// interface (https://github.com/docker/libnetwork/issues/1720)
		// Enable IPv6 for now
		sysSettings = append(sysSettings,
			setting{"net.ipv6.conf.all.disable_ipv6", "0", false})
	} else {
		args[initArgIPv6NodeIP] = "<nil>"
	}

	args[initArgMTU] = fmt.Sprintf("%d", deviceMTU)

	if option.Config.EnableIPSec {
		args[initArgIPSec] = "true"
	} else {
		args[initArgIPSec] = "false"
	}

	if option.Config.EnableHostReachableServices {
		args[initArgHostReachableServices] = "true"
		if option.Config.EnableHostServicesUDP {
			args[initArgHostReachableServicesUDP] = "true"
		} else {
			args[initArgHostReachableServicesUDP] = "false"
		}
		if option.Config.EnableHostServicesPeer {
			args[initArgHostReachableServicesPeer] = "true"
		} else {
			args[initArgHostReachableServicesPeer] = "false"
		}
	} else {
		args[initArgHostReachableServices] = "false"
		args[initArgHostReachableServicesUDP] = "false"
		args[initArgHostReachableServicesPeer] = "false"
	}

	if option.Config.EncryptInterface != "" {
		args[initArgEncryptInterface] = option.Config.EncryptInterface
	} else {
		args[initArgEncryptInterface] = "<nil>"
	}

	if len(option.Config.Devices) != 0 {
		for _, device := range option.Config.Devices {
			_, err := netlink.LinkByName(device)
			if err != nil {
				log.WithError(err).WithField("device", device).Warn("Link does not exist")
				return err
			}
		}

		if option.Config.Tunnel != option.TunnelDisabled {
			args[initArgMode] = option.Config.Tunnel
		} else if option.Config.DatapathMode == datapathOption.DatapathModeIpvlan {
			args[initArgMode] = "ipvlan"
		} else {
			args[initArgMode] = "direct"
		}

		args[initArgDevices] = strings.Join(option.Config.Devices, ";")
	} else {
		args[initArgMode] = option.Config.Tunnel
		args[initArgDevices] = "<nil>"

		if option.Config.IsFlannelMasterDeviceSet() {
			args[initArgMode] = "flannel"
			args[initArgDevices] = option.Config.FlannelMasterDevice
		}
	}

	if option.Config.EnableEndpointRoutes == true {
		args[initArgMode] = "routed"
	}

	if option.Config.EnableNodePort {
		args[initArgNodePort] = "true"
		if option.Config.EnableIPv4 {
			addrs := node.GetNodePortIPv4AddrsWithDevices()
			tmp := make([]string, 0, len(addrs))
			for iface, ipv4 := range addrs {
				tmp = append(tmp,
					fmt.Sprintf("%s=%#x", iface,
						byteorder.HostSliceToNetwork(ipv4, reflect.Uint32).(uint32)))
			}
			args[initArgNodePortIPv4Addrs] = strings.Join(tmp, ";")
		} else {
			args[initArgNodePortIPv4Addrs] = "<nil>"
		}
		if option.Config.EnableIPv6 {
			addrs := node.GetNodePortIPv6AddrsWithDevices()
			tmp := make([]string, 0, len(addrs))
			for iface, ipv6 := range addrs {
				tmp = append(tmp, fmt.Sprintf("%s=%s", iface, common.GoArray2CNoSpaces(ipv6)))
			}
			args[initArgNodePortIPv6Addrs] = strings.Join(tmp, ";")
		} else {
			args[initArgNodePortIPv6Addrs] = "<nil>"
		}
	} else {
		args[initArgNodePort] = "false"
		args[initArgNodePortIPv4Addrs] = "<nil>"
		args[initArgNodePortIPv6Addrs] = "<nil>"
	}

	if option.Config.NodePortBindProtection {
		args[initArgNodePortBind] = "true"
	} else {
		args[initArgNodePortBind] = "false"
	}

	args[initBPFCPU] = GetBPFCPU()

	clockSource := []string{"ktime", "jiffies"}
	log.Infof("Setting up base BPF datapath (BPF %s instruction set, %s clock source)",
		args[initBPFCPU], clockSource[option.Config.ClockSource])

	for _, s := range sysSettings {
		log.Infof("Setting sysctl %s=%s", s.name, s.val)
		if err := sysctl.Write(s.name, s.val); err != nil {
			if !s.ignoreErr {
				return fmt.Errorf("Failed to sysctl -w %s=%s: %s", s.name, s.val, err)
			}
			log.WithError(err).WithFields(logrus.Fields{
				logfields.SysParamName:  s.name,
				logfields.SysParamValue: s.val,
			}).Warning("Failed to sysctl -w")
		}
	}

	for i, arg := range args {
		if arg == "" {
			log.Warningf("empty argument passed to bpf/init.sh at position %d", i)
		}
	}

	prog := filepath.Join(option.Config.BpfDir, "init.sh")
	ctx, cancel := context.WithTimeout(ctx, defaults.ExecTimeout)
	defer cancel()
	cmd := exec.CommandContext(ctx, prog, args...)
	cmd.Env = bpf.Environment()
	if _, err := cmd.CombinedOutput(log, true); err != nil {
		return err
	}

	if l.canDisableDwarfRelocations {
		// Validate alignments of C and Go equivalent structs
		if err := alignchecker.CheckStructAlignments(defaults.AlignCheckerName); err != nil {
			log.WithError(err).Fatal("C and Go structs alignment check failed")
		}
	} else {
		log.Warning("Cannot check matching of C and Go common struct alignments due to old LLVM/clang version")
	}

	if !option.Config.IsFlannelMasterDeviceSet() {
		r.ReserveLocalRoutes()
	}

	if err := o.Datapath().Node().NodeConfigurationChanged(*o.LocalConfig()); err != nil {
		return err
	}

	if option.Config.InstallIptRules {
		if err := iptMgr.TransientRulesStart(option.Config.HostDevice); err != nil {
			return err
		}
	}
	// The iptables rules are only removed on the first initialization to
	// remove stale rules or when iptables is enabled. The first invocation
	// is silent as rules may not exist.
	if firstInitialization || option.Config.InstallIptRules {
		iptMgr.RemoveRules(firstInitialization)
	}
	if option.Config.InstallIptRules {
		err := iptMgr.InstallRules(option.Config.HostDevice)
		iptMgr.TransientRulesEnd(false)
		if err != nil {
			return err
		}
	}
	// Reinstall proxy rules for any running proxies
	if p != nil {
		p.ReinstallRules()
	}

	return nil
}
<|endoftext|>