<commit_before>package controllers

import (
	"bytes"
	"fmt"
	"github.com/astaxie/beego"
	"github.com/citycloud/citycloud.cf-deploy-ui/logger"
	"github.com/citycloud/citycloud.cf-deploy-ui/utils"
	"github.com/gorilla/websocket"
	"net/http"
	"time"
)

type MicroBOSHWebSocketController struct {
	beego.Controller
}

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

const (
	EndEOF = "da39a3ee5e6b4b0d3255bfef95601890afd80709"
)

// becareful
//此处未判断任务是否完成，需前端判断控制任务流程
func (this *MicroBOSHWebSocketController) Get() {

	ws, err := upgrader.Upgrade(this.Ctx.ResponseWriter, this.Ctx.Request, nil)
	if _, ok := err.(websocket.HandshakeError); ok {
		http.Error(this.Ctx.ResponseWriter, "Not a websocket handshake", 400)
		return
	} else if err != nil {
		return
	}
	for {
		time.Sleep(2 * time.Second)
		mesgType, message, _ := ws.ReadMessage()
		if message != nil && mesgType == websocket.TextMessage {

			var action = string(message)
			switch {
			case action == "AllStep":
				var success bool = false
				success = this.setMicroBOSHDeployment(ws)
				if !success {
					writeStringMessage(ws, "设置deployment出现了错误，需要检查MicroBOSH Deployment的配置")
				}
				if success {
					success = this.deployMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "部署 MicroBOSH 实例出现了错误！需要检查MicroBOSH Deployment的配置")
					}
				}
				if success {
					success = this.targetMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "设置bosh target失败！")
					}
				}

				if success {
					success = this.loginMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "登录失败！")
					}
				}

				if success {
					success = this.statusMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "查看信息失败！")
					}
				}
			case action == "SetDeploy":
				var success bool = false
				success = this.setMicroBOSHDeployment(ws)
				if !success {
					writeStringMessage(ws, "设置deployment出现了错误，需要检查MicroBOSH Deployment的配置")
				}
			case action == "Deploy":
				var success bool = false
				success = this.deployMicroBOSH(ws)
				if !success {
					writeStringMessage(ws, "部署 MicroBOSH 实例出现了错误！需要检查MicroBOSH Deployment的配置")
				}
			case action == "ReDeploy":
				var success bool = false
				success = this.deleteMicroBOSH(ws)
				if !success {
					writeStringMessage(ws, "检查IaaS中是否还有MicroBOSH的实例，如过有，请删除。")
				}
				if success {
					success = this.deployMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "部署 MicroBOSH 实例出现了错误！需要检查MicroBOSH Deployment的配置")
					}
				}
			case action == "Login":
				var success bool = false
				success = this.targetMicroBOSH(ws)
				if !success {
					writeStringMessage(ws, "设置bosh target失败！")
				}
				if success {
					success = this.loginMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "登录失败！")
					}
				}

			case action == "Status":
				var success bool = false
				success = this.statusMicroBOSH(ws)
				if !success {
					writeStringMessage(ws, "查看信息失败！")
				}
			default:
				writeStringMessage(ws, fmt.Sprintf("未知的执行命令！%s", action))
			}

			//write a message to tell me running over
			writeStringMessage(ws, EndEOF)
		}

	}
}

//set deployment
func (this *MicroBOSHWebSocketController) setMicroBOSHDeployment(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Set MicroBOSH Deployment")

	var out bytes.Buffer
	cmdCommand := utils.Command{Name: "bosh", Args: []string{"micro", "deployment", microManiest}, Dir: workDir, Stdin: ""}

	cmdRunner := utils.NewDeployCmdRunner()
	cmdRunner.RunCommandAsyncCmd(cmdCommand, &out)
	writeBytesBufferMessage(&out, &cmdRunner, ws)

	writeStringMessage(ws, "Finished Set MicroBOSH Deployment")
	writeStringMessage(ws, "============================================")

	return cmdRunner.Success()
}

//deploy microbosh
func (this *MicroBOSHWebSocketController) deployMicroBOSH(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Deploying MicroBOSH instances")
	var out bytes.Buffer
	cmdCommand := utils.Command{Name: "bosh", Args: []string{"micro", "deploy", stemcellRelease}, Dir: workDir, Stdin: "yes"}
	cmdRunner := utils.NewDeployCmdRunner()
	cmdRunner.RunCommandAsyncCmd(cmdCommand, &out)
	writeBytesBufferMessage(&out, &cmdRunner, ws)

	writeStringMessage(ws, "Finished deploying MicroBOSH instances")
	writeStringMessage(ws, "============================================")
	return cmdRunner.Success()
}

//delete microbosh
func (this *MicroBOSHWebSocketController) deleteMicroBOSH(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Deleting MicroBOSH instances")
	var out bytes.Buffer
	cmdCommand := utils.Command{Name: "bosh", Args: []string{"micro", "delete"}, Dir: workDir, Stdin: "yes"}
	cmdRunner := utils.NewDeployCmdRunner()
	cmdRunner.RunCommandAsyncCmd(cmdCommand, &out)
	writeBytesBufferMessage(&out, &cmdRunner, ws)

	cleanCmdCommand := utils.Command{Name: "sh", Args: []string{"rm_bosh_deployment.sh"}, Dir: workDir, Stdin: ""}
	cleanCmdRunner := utils.NewDeployCmdRunner()
	cleanCmdRunner.RunCommandAsyncCmd(cleanCmdCommand, &out)
	writeBytesBufferMessage(&out, &cleanCmdRunner, ws)
	writeStringMessage(ws, "Finished deleting MicroBOSH instances")
	writeStringMessage(ws, "============================================")
	return cmdRunner.Success()
}

//target to microbosh
func (this *MicroBOSHWebSocketController) targetMicroBOSH(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Target to MicroBOSH instances")
	var out bytes.Buffer
	var ip string = mi.NetWork.Vip
	target := fmt.Sprintf("https://%s:25555", ip)

	loginCommand := utils.Command{Name: "bosh", Args: []string{"target", target}, Dir: workDir, Stdin: "admin\nadmin\n"}

	cmdRunner := utils.NewDeployCmdRunner()

	cmdRunner.RunCommandAsyncCmd(loginCommand, &out)

	writeBytesBufferMessage(&out, &cmdRunner, ws)

	writeStringMessage(ws, "Finished target to MicroBOSH instances")
	writeStringMessage(ws, "============================================")
	return cmdRunner.Success()
}

//login into
func (this *MicroBOSHWebSocketController) loginMicroBOSH(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Login MicroBOSH instances")
	var out bytes.Buffer

	loginCommand := utils.Command{Name: "bosh", Args: []string{"login"}, Dir: workDir, Stdin: "admin\nadmin\n"}

	cmdRunner := utils.NewDeployCmdRunner()

	cmdRunner.RunCommandAsyncCmd(loginCommand, &out)

	writeBytesBufferMessage(&out, &cmdRunner, ws)

	writeStringMessage(ws, "Finished login MicroBOSH instances")
	writeStringMessage(ws, "============================================")
	return cmdRunner.Success()
}

func (this *MicroBOSHWebSocketController) statusMicroBOSH(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Status MicroBOSH instances")
	var out bytes.Buffer

	loginCommand := utils.Command{Name: "bosh", Args: []string{"status"}, Dir: workDir, Stdin: ""}

	cmdRunner := utils.NewDeployCmdRunner()

	cmdRunner.RunCommandAsyncCmd(loginCommand, &out)

	writeBytesBufferMessage(&out, &cmdRunner, ws)

	writeStringMessage(ws, "Finished Status MicroBOSH")
	writeStringMessage(ws, "============================================")
	return cmdRunner.Success()
}

func writeStringMessage(ws *websocket.Conn, message string) {
	logger.Debug("[Command INFO]: %s", message)
	ws.WriteMessage(websocket.TextMessage, []byte(message))
}

func writeBytesMessage(ws *websocket.Conn, message []byte) {
	logger.Debug("[Command INFO]: %s", string(message))
	ws.WriteMessage(websocket.TextMessage, message)
}

func writeBytesBufferMessage(out *bytes.Buffer, cmdRunner *utils.DeployCmdRunner, ws *websocket.Conn) {
	for {
	loop:
		if out.Len() == 0 {
			if cmdRunner.Exited() {
				break
			} else {
				time.Sleep(time.Second)
				goto loop
			}
		}
	again:
		line, _ := out.ReadBytes('\n') //按行读取
		if line != nil {
			writeBytesMessage(ws, line)
			goto again
		} else {
			goto loop
		}
	}
}

func writeBufferMessage(ws *websocket.Conn, out *bytes.Buffer) {
	for {
		line, _ := out.ReadBytes('\n') //按行读取
		if line != nil {
			writeBytesMessage(ws, line)
		}
		if out.Len() == 0 {
			break
		}
	}
}

//	if action == "MicroBOSH" {
//		var success bool = false
//		success = this.setMicroBOSHDeployment(ws)
//		if !success {
//			writeStringMessage(ws, "设置deployment出现了错误，需要检查MicroBOSH Deployment的配置")
//		}
//		if success {
//			success = this.deployMicroBOSH(ws)
//			if !success {
//				writeStringMessage(ws, "部署 MicroBOSH 实例出现了错误！需要检查MicroBOSH Deployment的配置")
//			}
//		}

//		if success {
//			success = this.targetMicroBOSH(ws)
//			if !success {
//				writeStringMessage(ws, "设置bosh target失败！")
//			}

//		}

//		if success {
//			success = this.loginMicroBOSH(ws)
//			if !success {
//				writeStringMessage(ws, "登录失败！")
//			}
//		}
//	}
//	if action == "SetDeployment" {
//		var success bool = false
//		success = this.setMicroBOSHDeployment(ws)
//		if !success {
//			writeStringMessage(ws, "设置deployment出现了错误，需要检查MicroBOSH Deployment的配置")
//		}
//	}

//	if action == "Deploy" {
//		var success bool = false
//		success = this.deployMicroBOSH(ws)
//		if !success {
//			writeStringMessage(ws, "部署 MicroBOSH 实例出现了错误！需要检查MicroBOSH Deployment的配置")
//			ws.WriteMessage(websocket.CloseMessage, []byte("Closed"))
//			return
//		}
//	}

//	if action == "Login" {
//		var success bool = false
//		success = this.targetMicroBOSH(ws)
//		if !success {
//			writeStringMessage(ws, "设置bosh target失败！")
//		}
//		if success {
//			success = this.loginMicroBOSH(ws)
//			if !success {
//				writeStringMessage(ws, "登录失败！")
//			}
//		}
//	}
<commit_msg>fixed bug<commit_after>package controllers

import (
	"bytes"
	"fmt"
	"github.com/astaxie/beego"
	"github.com/citycloud/citycloud.cf-deploy-ui/logger"
	"github.com/citycloud/citycloud.cf-deploy-ui/utils"
	"github.com/gorilla/websocket"
	"net/http"
	"time"
)

type MicroBOSHWebSocketController struct {
	beego.Controller
}

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

const (
	EndEOF = "da39a3ee5e6b4b0d3255bfef95601890afd80709"
)

// becareful
//此处未判断任务是否完成，需前端判断控制任务流程
func (this *MicroBOSHWebSocketController) Get() {

	ws, err := upgrader.Upgrade(this.Ctx.ResponseWriter, this.Ctx.Request, nil)
	if _, ok := err.(websocket.HandshakeError); ok {
		http.Error(this.Ctx.ResponseWriter, "Not a websocket handshake", 400)
		return
	} else if err != nil {
		return
	}
	for {
		time.Sleep(2 * time.Second)
		mesgType, message, _ := ws.ReadMessage()
		if message != nil && mesgType == websocket.TextMessage {

			var action = string(message)
			switch {
			case action == "AllStep":
				var success bool = false
				success = this.setMicroBOSHDeployment(ws)
				if !success {
					writeStringMessage(ws, "设置deployment出现了错误，需要检查MicroBOSH Deployment的配置")
				}
				if success {
					success = this.deployMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "部署 MicroBOSH 实例出现了错误！需要检查MicroBOSH Deployment的配置")
					}
				}
				if success {
					success = this.targetMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "设置bosh target失败！")
					}
				}

				if success {
					success = this.loginMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "登录失败！")
					}
				}

				if success {
					success = this.statusMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "查看信息失败！")
					}
				}
			case action == "SetDeploy":
				var success bool = false
				success = this.setMicroBOSHDeployment(ws)
				if !success {
					writeStringMessage(ws, "设置deployment出现了错误，需要检查MicroBOSH Deployment的配置")
				}
			case action == "Deploy":
				var success bool = false
				success = this.deployMicroBOSH(ws)
				if !success {
					writeStringMessage(ws, "部署 MicroBOSH 实例出现了错误！需要检查MicroBOSH Deployment的配置")
				}
			case action == "ReDeploy":
				var success bool = false
				success = this.deleteMicroBOSH(ws)
				if !success {
					writeStringMessage(ws, "检查IaaS中是否还有MicroBOSH的实例，如果有，请删除。")
				}
				if success {
					success = this.deployMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "部署 MicroBOSH 实例出现了错误！需要检查MicroBOSH Deployment的配置")
					}
				}
			case action == "Login":
				var success bool = false
				success = this.targetMicroBOSH(ws)
				if !success {
					writeStringMessage(ws, "设置bosh target失败！")
				}
				if success {
					success = this.loginMicroBOSH(ws)
					if !success {
						writeStringMessage(ws, "登录失败！")
					}
				}

			case action == "Status":
				var success bool = false
				success = this.statusMicroBOSH(ws)
				if !success {
					writeStringMessage(ws, "查看信息失败！")
				}
			default:
				writeStringMessage(ws, fmt.Sprintf("未知的执行命令！%s", action))
			}

			//write a message to tell me running over
			writeStringMessage(ws, EndEOF)
		}

	}
}

//set deployment
func (this *MicroBOSHWebSocketController) setMicroBOSHDeployment(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Set MicroBOSH Deployment")

	var out bytes.Buffer
	cmdCommand := utils.Command{Name: "bosh", Args: []string{"micro", "deployment", microManiest}, Dir: workDir, Stdin: ""}

	cmdRunner := utils.NewDeployCmdRunner()
	cmdRunner.RunCommandAsyncCmd(cmdCommand, &out)
	writeBytesBufferMessage(&out, &cmdRunner, ws)

	writeStringMessage(ws, "Finished Set MicroBOSH Deployment")
	writeStringMessage(ws, "============================================")

	return cmdRunner.Success()
}

//deploy microbosh
func (this *MicroBOSHWebSocketController) deployMicroBOSH(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Deploying MicroBOSH instances")
	var out bytes.Buffer
	cmdCommand := utils.Command{Name: "bosh", Args: []string{"micro", "deploy", stemcellRelease}, Dir: workDir, Stdin: "yes"}
	cmdRunner := utils.NewDeployCmdRunner()
	cmdRunner.RunCommandAsyncCmd(cmdCommand, &out)
	writeBytesBufferMessage(&out, &cmdRunner, ws)

	writeStringMessage(ws, "Finished deploying MicroBOSH instances")
	writeStringMessage(ws, "============================================")
	return cmdRunner.Success()
}

//delete microbosh
func (this *MicroBOSHWebSocketController) deleteMicroBOSH(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Deleting MicroBOSH instances")
	var out bytes.Buffer
	cmdCommand := utils.Command{Name: "bosh", Args: []string{"micro", "delete"}, Dir: workDir, Stdin: "yes"}
	cmdRunner := utils.NewDeployCmdRunner()
	cmdRunner.RunCommandAsyncCmd(cmdCommand, &out)
	writeBytesBufferMessage(&out, &cmdRunner, ws)

	cleanCmdCommand := utils.Command{Name: "sh", Args: []string{"rm_bosh_deployment.sh"}, Dir: workDir, Stdin: ""}
	cleanCmdRunner := utils.NewDeployCmdRunner()
	cleanCmdRunner.RunCommandAsyncCmd(cleanCmdCommand, &out)
	writeBytesBufferMessage(&out, &cleanCmdRunner, ws)
	writeStringMessage(ws, "Finished deleting MicroBOSH instances")
	writeStringMessage(ws, "============================================")
	return cmdRunner.Success()
}

//target to microbosh
func (this *MicroBOSHWebSocketController) targetMicroBOSH(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Target to MicroBOSH instances")
	var out bytes.Buffer
	var ip string = mi.NetWork.Vip
	target := fmt.Sprintf("https://%s:25555", ip)

	loginCommand := utils.Command{Name: "bosh", Args: []string{"target", target}, Dir: workDir, Stdin: "admin\nadmin\n"}

	cmdRunner := utils.NewDeployCmdRunner()

	cmdRunner.RunCommandAsyncCmd(loginCommand, &out)

	writeBytesBufferMessage(&out, &cmdRunner, ws)

	writeStringMessage(ws, "Finished target to MicroBOSH instances")
	writeStringMessage(ws, "============================================")
	return cmdRunner.Success()
}

//login into
func (this *MicroBOSHWebSocketController) loginMicroBOSH(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Login MicroBOSH instances")
	var out bytes.Buffer

	loginCommand := utils.Command{Name: "bosh", Args: []string{"login"}, Dir: workDir, Stdin: "admin\nadmin\n"}

	cmdRunner := utils.NewDeployCmdRunner()

	cmdRunner.RunCommandAsyncCmd(loginCommand, &out)

	writeBytesBufferMessage(&out, &cmdRunner, ws)

	writeStringMessage(ws, "Finished login MicroBOSH instances")
	writeStringMessage(ws, "============================================")
	return cmdRunner.Success()
}

func (this *MicroBOSHWebSocketController) statusMicroBOSH(ws *websocket.Conn) bool {
	writeStringMessage(ws, "============================================")
	writeStringMessage(ws, "Status MicroBOSH instances")
	var out bytes.Buffer

	loginCommand := utils.Command{Name: "bosh", Args: []string{"status"}, Dir: workDir, Stdin: ""}

	cmdRunner := utils.NewDeployCmdRunner()

	cmdRunner.RunCommandAsyncCmd(loginCommand, &out)

	writeBytesBufferMessage(&out, &cmdRunner, ws)

	writeStringMessage(ws, "Finished Status MicroBOSH")
	writeStringMessage(ws, "============================================")
	return cmdRunner.Success()
}

func writeStringMessage(ws *websocket.Conn, message string) {
	logger.Debug("[Command INFO]: %s", message)
	ws.WriteMessage(websocket.TextMessage, []byte(message))
}

func writeBytesMessage(ws *websocket.Conn, message []byte) {
	logger.Debug("[Command INFO]: %s", string(message))
	ws.WriteMessage(websocket.TextMessage, message)
}

func writeBytesBufferMessage(out *bytes.Buffer, cmdRunner *utils.DeployCmdRunner, ws *websocket.Conn) {
	for {
	loop:
		if out.Len() == 0 {
			if cmdRunner.Exited() {
				break
			} else {
				time.Sleep(time.Second)
				goto loop
			}
		}
	again:
		line, _ := out.ReadBytes('\n') //按行读取
		if line != nil {
			writeBytesMessage(ws, line)
			goto again
		} else {
			goto loop
		}
	}
}

func writeBufferMessage(ws *websocket.Conn, out *bytes.Buffer) {
	for {
		line, _ := out.ReadBytes('\n') //按行读取
		if line != nil {
			writeBytesMessage(ws, line)
		}
		if out.Len() == 0 {
			break
		}
	}
}

//	if action == "MicroBOSH" {
//		var success bool = false
//		success = this.setMicroBOSHDeployment(ws)
//		if !success {
//			writeStringMessage(ws, "设置deployment出现了错误，需要检查MicroBOSH Deployment的配置")
//		}
//		if success {
//			success = this.deployMicroBOSH(ws)
//			if !success {
//				writeStringMessage(ws, "部署 MicroBOSH 实例出现了错误！需要检查MicroBOSH Deployment的配置")
//			}
//		}

//		if success {
//			success = this.targetMicroBOSH(ws)
//			if !success {
//				writeStringMessage(ws, "设置bosh target失败！")
//			}

//		}

//		if success {
//			success = this.loginMicroBOSH(ws)
//			if !success {
//				writeStringMessage(ws, "登录失败！")
//			}
//		}
//	}
//	if action == "SetDeployment" {
//		var success bool = false
//		success = this.setMicroBOSHDeployment(ws)
//		if !success {
//			writeStringMessage(ws, "设置deployment出现了错误，需要检查MicroBOSH Deployment的配置")
//		}
//	}

//	if action == "Deploy" {
//		var success bool = false
//		success = this.deployMicroBOSH(ws)
//		if !success {
//			writeStringMessage(ws, "部署 MicroBOSH 实例出现了错误！需要检查MicroBOSH Deployment的配置")
//			ws.WriteMessage(websocket.CloseMessage, []byte("Closed"))
//			return
//		}
//	}

//	if action == "Login" {
//		var success bool = false
//		success = this.targetMicroBOSH(ws)
//		if !success {
//			writeStringMessage(ws, "设置bosh target失败！")
//		}
//		if success {
//			success = this.loginMicroBOSH(ws)
//			if !success {
//				writeStringMessage(ws, "登录失败！")
//			}
//		}
//	}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"unicode"

	"github.com/charlievieth/buildutil"
)

type CompLintRequest struct {
	Filename string `json:"filename"`
}

type CompileError struct {
	Row     int    `json:"row"`
	Col     int    `json:"col"`
	File    string `json:"file"`
	Message string `json:"message"`
}

type CompLintReport struct {
	Filename      string         `json:"filename"`
	TopLevelError string         `json:"top_level_error,omitempty"`
	CmdError      string         `json:"cmd_error,omitempty"`
	Errors        []CompileError `json:"errors,omitempty"`
}

func isTestPkg(path string) bool {
	return strings.HasSuffix(path, "_test.go")
}

func isMainPkg(path string) bool {
	name, err := buildutil.ReadPackageName(path, nil)
	return err == nil && name == "main"
}

func firstLine(b []byte) []byte {
	if n := bytes.IndexByte(b, '\n'); n > 0 {
		b = bytes.TrimRightFunc(b[:n], unicode.IsSpace)
	}
	return b
}

var compRe = regexp.MustCompile(`(?m)^(?P<file>[^:#]+\.go)\:(?P<row>\d+)\:(?:(?P<col>\d+)\:)?\s*(?P<msg>.+)$`)

func (r *CompLintReport) ParseErrors(dirname string, out []byte) {
	const (
		FileIndex     = 1
		RowIndex      = 2
		ColIndex      = 3
		MsgIndex      = 4
		SubmatchCount = 5
	)
	out = bytes.TrimSpace(out)

	if first := firstLine(out); len(first) != 0 && first[0] != '#' {
		r.TopLevelError = string(first)
	}

	matches := compRe.FindAllSubmatch(out, -1)
	for _, m := range matches {
		if len(m) != SubmatchCount {
			continue
		}
		row, _ := strconv.Atoi(string(m[RowIndex]))
		col, _ := strconv.Atoi(string(m[ColIndex]))
		file := string(m[FileIndex])
		if !filepath.IsAbs(file) {
			file = filepath.Join(dirname, file)
		}
		r.Errors = append(r.Errors, CompileError{
			Row:     row,
			Col:     col,
			File:    file,
			Message: string(m[MsgIndex]),
		})
	}
}

func (c *CompLintRequest) Compile() *CompLintReport {
	var args []string
	switch {
	case isTestPkg(c.Filename):
		args = []string{"test", "-c", "-i", "-o", os.DevNull}
	case isMainPkg(c.Filename):
		args = []string{"build", "-i"}
	default:
		args = []string{"install", "-i"}
	}
	dirname := filepath.Dir(c.Filename)
	cmd := exec.Command("go", args...)
	cmd.Dir = dirname
	out, err := cmd.CombinedOutput()
	r := &CompLintReport{
		Filename: c.Filename,
	}
	if err != nil {
		r.CmdError = err.Error()
		r.ParseErrors(dirname, out)
	}
	return r
}

func (c *CompLintRequest) Call() (interface{}, string) {
	return c.Compile(), ""
}

func init() {
	registry.Register("comp_lint", func(_ *Broker) Caller {
		return &CompLintRequest{}
	})
}
<commit_msg>m_comp_lint: add support for 'integration' tags<commit_after>package main

import (
	"bytes"
	"context"
	"go/build"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"unicode"

	"github.com/charlievieth/buildutil"
)

type CompLintRequest struct {
	Filename string `json:"filename"`
}

type CompileError struct {
	Row     int    `json:"row"`
	Col     int    `json:"col"`
	File    string `json:"file"`
	Message string `json:"message"`
}

type CompLintReport struct {
	Filename      string         `json:"filename"`
	TopLevelError string         `json:"top_level_error,omitempty"`
	CmdError      string         `json:"cmd_error,omitempty"`
	Errors        []CompileError `json:"errors,omitempty"`
}

func isTestPkg(path string) bool {
	return strings.HasSuffix(path, "_test.go")
}

func isMainPkg(path string) bool {
	name, err := buildutil.ReadPackageName(path, nil)
	return err == nil && name == "main"
}

func firstLine(b []byte) []byte {
	if n := bytes.IndexByte(b, '\n'); n > 0 {
		b = bytes.TrimRightFunc(b[:n], unicode.IsSpace)
	}
	return b
}

var compRe = regexp.MustCompile(`(?m)^(?P<file>[^:#]+\.go)\:(?P<row>\d+)\:(?:(?P<col>\d+)\:)?\s*(?P<msg>.+)$`)

func (r *CompLintReport) ParseErrors(dirname string, out []byte) {
	const (
		FileIndex     = 1
		RowIndex      = 2
		ColIndex      = 3
		MsgIndex      = 4
		SubmatchCount = 5
	)
	out = bytes.TrimSpace(out)

	if first := firstLine(out); len(first) != 0 && first[0] != '#' {
		r.TopLevelError = string(first)
	}

	matches := compRe.FindAllSubmatch(out, -1)
	for _, m := range matches {
		if len(m) != SubmatchCount {
			continue
		}
		row, _ := strconv.Atoi(string(m[RowIndex]))
		col, _ := strconv.Atoi(string(m[ColIndex]))
		file := string(m[FileIndex])
		if !filepath.IsAbs(file) {
			file = filepath.Join(dirname, file)
		}
		r.Errors = append(r.Errors, CompileError{
			Row:     row,
			Col:     col,
			File:    file,
			Message: string(m[MsgIndex]),
		})
	}
}

type CompLintkey struct {
	Filename string
	Modtime  string
}

func (c *CompLintRequest) Compile(ctx context.Context) *CompLintReport {
	tags := make(map[string]bool)
	pkgname, _, _ := buildutil.ReadPackageNameTags(&build.Default, c.Filename, tags)

	var args []string
	switch {
	case isTestPkg(c.Filename):
		args = []string{"test", "-c", "-i", "-o", os.DevNull}
	case pkgname == "main":
		args = []string{"build", "-i"}
	default:
		args = []string{"install", "-i"}
	}

	// only handle the "integration" tag for now
	if tags["integration"] {
		args = append(args, "-tags", "integration")
	}

	dirname := filepath.Dir(c.Filename)
	cmd := exec.CommandContext(ctx, "go", args...)
	cmd.Dir = dirname
	out, err := cmd.CombinedOutput()
	r := &CompLintReport{
		Filename: c.Filename,
	}
	if err != nil {
		r.CmdError = err.Error()
		r.ParseErrors(dirname, out)
	}
	return r
}

func (c *CompLintRequest) Call() (interface{}, string) {
	return c.Compile(context.Background()), ""
}

func init() {
	registry.Register("comp_lint", func(_ *Broker) Caller {
		return &CompLintRequest{}
	})
}
<|endoftext|>


<commit_before>package discovery

import (
	"bytes"
	"sync"
	"time"

	"google.golang.org/grpc"

	"github.com/TheThingsNetwork/ttn/api"
	pb "github.com/TheThingsNetwork/ttn/api/discovery"
	"github.com/TheThingsNetwork/ttn/core/types"
	"golang.org/x/net/context"
)

// BrokerCacheTime indicates how long the BrokerDiscovery should cache the services
var BrokerCacheTime = 30 * time.Minute

// BrokerDiscovery is used as a client to discover Brokers
type BrokerDiscovery interface {
	Discover(devAddr types.DevAddr) ([]*pb.Announcement, error)
	All() ([]*pb.Announcement, error)
}

type brokerDiscovery struct {
	serverAddress   string
	cache           []*pb.Announcement
	cacheLock       sync.RWMutex
	cacheValidUntil time.Time
}

// NewBrokerDiscovery returns a new BrokerDiscovery on top of the given gRPC connection
func NewBrokerDiscovery(serverAddress string) BrokerDiscovery {
	return &brokerDiscovery{serverAddress: serverAddress}
}

func (d *brokerDiscovery) refreshCache() error {
	// Connect to the server
	conn, err := grpc.Dial(d.serverAddress, api.DialOptions...)
	if err != nil {
		return err
	}
	defer conn.Close()
	client := pb.NewDiscoveryClient(conn)
	res, err := client.Discover(context.Background(), &pb.DiscoverRequest{ServiceName: "broker"})
	if err != nil {
		return err
	}
	// TODO: validate response
	d.cacheLock.Lock()
	defer d.cacheLock.Unlock()
	d.cacheValidUntil = time.Now().Add(BrokerCacheTime)
	d.cache = res.Services
	return nil
}

func (d *brokerDiscovery) All() (announcements []*pb.Announcement, err error) {
	d.cacheLock.Lock()
	if time.Now().After(d.cacheValidUntil) {
		d.cacheValidUntil = time.Now().Add(10 * time.Second)
		go d.refreshCache()
	}
	announcements = d.cache
	d.cacheLock.Unlock()
	return
}

func (d *brokerDiscovery) Discover(devAddr types.DevAddr) ([]*pb.Announcement, error) {
	d.cacheLock.Lock()
	if time.Now().After(d.cacheValidUntil) {
		d.cacheValidUntil = time.Now().Add(10 * time.Second)
		go d.refreshCache()
	}
	d.cacheLock.Unlock()
	d.cacheLock.RLock()
	defer d.cacheLock.RUnlock()
	matches := []*pb.Announcement{}
	for _, service := range d.cache {
		for _, meta := range service.Metadata {
			if meta.Key == pb.Metadata_PREFIX && bytes.HasPrefix(devAddr.Bytes(), meta.Value) {
				matches = append(matches, service)
				break
			}
		}
	}
	return matches, nil
}
<commit_msg>Simple locking in Broker Discovery<commit_after>package discovery

import (
	"bytes"
	"sync"
	"time"

	"google.golang.org/grpc"

	"github.com/TheThingsNetwork/ttn/api"
	pb "github.com/TheThingsNetwork/ttn/api/discovery"
	"github.com/TheThingsNetwork/ttn/core/types"
	"golang.org/x/net/context"
)

// BrokerCacheTime indicates how long the BrokerDiscovery should cache the services
var BrokerCacheTime = 30 * time.Minute

// BrokerDiscovery is used as a client to discover Brokers
type BrokerDiscovery interface {
	Discover(devAddr types.DevAddr) ([]*pb.Announcement, error)
	All() ([]*pb.Announcement, error)
}

type brokerDiscovery struct {
	serverAddress   string
	cache           []*pb.Announcement
	cacheLock       sync.RWMutex
	cacheValidUntil time.Time
}

// NewBrokerDiscovery returns a new BrokerDiscovery on top of the given gRPC connection
func NewBrokerDiscovery(serverAddress string) BrokerDiscovery {
	return &brokerDiscovery{serverAddress: serverAddress}
}

func (d *brokerDiscovery) refreshCache() error {
	// Connect to the server
	conn, err := grpc.Dial(d.serverAddress, api.DialOptions...)
	if err != nil {
		return err
	}
	defer conn.Close()
	client := pb.NewDiscoveryClient(conn)
	res, err := client.Discover(context.Background(), &pb.DiscoverRequest{ServiceName: "broker"})
	if err != nil {
		return err
	}
	// TODO: validate response
	d.cacheLock.Lock()
	defer d.cacheLock.Unlock()
	d.cacheValidUntil = time.Now().Add(BrokerCacheTime)
	d.cache = res.Services
	return nil
}

func (d *brokerDiscovery) All() (announcements []*pb.Announcement, err error) {
	d.cacheLock.Lock()
	defer d.cacheLock.Unlock()
	if time.Now().After(d.cacheValidUntil) {
		d.cacheValidUntil = time.Now().Add(10 * time.Second)
		go d.refreshCache()
	}
	announcements = d.cache
	return
}

func (d *brokerDiscovery) Discover(devAddr types.DevAddr) ([]*pb.Announcement, error) {
	d.cacheLock.Lock()
	defer d.cacheLock.Unlock()

	if time.Now().After(d.cacheValidUntil) {
		d.cacheValidUntil = time.Now().Add(10 * time.Second)
		go d.refreshCache()
	}

	matches := []*pb.Announcement{}
	for _, service := range d.cache {
		for _, meta := range service.Metadata {
			if meta.Key == pb.Metadata_PREFIX && bytes.HasPrefix(devAddr.Bytes(), meta.Value) {
				matches = append(matches, service)
				break
			}
		}
	}

	return matches, nil
}
<|endoftext|>


<commit_before>package scaleway

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/log"
	"github.com/docker/machine/libmachine/mcnflag"
	"github.com/docker/machine/libmachine/ssh"
	"github.com/docker/machine/libmachine/state"
	"github.com/moul/anonuuid"
	"github.com/scaleway/scaleway-cli/pkg/api"
	"github.com/scaleway/scaleway-cli/pkg/clilogger"
	"github.com/scaleway/scaleway-cli/pkg/config"
)

const (
	// VERSION represents the semver version of the package
	VERSION           = "v1.3"
	defaultImage      = "ubuntu-xenial"
	defaultBootscript = "docker"
)

var scwAPI *api.ScalewayAPI

// Driver represents the docker driver interface
type Driver struct {
	*drivers.BaseDriver
	ServerID       string
	Organization   string
	IPID           string
	Token          string
	CommercialType string
	Region         string
	name           string
	image          string
	bootscript     string
	ip             string
	volumes        string
	IPPersistant   bool
	stopping       bool
	created        bool
	ipv6           bool
	// userDataFile string
}

// DriverName returns the name of the driver
func (d *Driver) DriverName() string {
	if d.CommercialType == "" {
		return "scaleway"
	}
	return fmt.Sprintf("scaleway(%v)", d.CommercialType)
}

func (d *Driver) getClient(region string) (cl *api.ScalewayAPI, err error) {
	if scwAPI == nil {
		if region == "" {
			region = "par1"
		}
		scwAPI, err = api.NewScalewayAPI(d.Organization, d.Token, "docker-machine-driver-scaleway/"+VERSION, region, clilogger.SetupLogger)
	}
	cl = scwAPI
	return
}

// SetConfigFromFlags sets the flags
func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) (err error) {
	if flags.Bool("scaleway-debug") {
		logrus.SetOutput(os.Stderr)
		logrus.SetLevel(logrus.DebugLevel)
	}

	d.Token, d.Organization = flags.String("scaleway-token"), flags.String("scaleway-organization")
	if d.Token == "" || d.Organization == "" {
		config, cfgErr := config.GetConfig()
		if cfgErr == nil {
			if d.Token == "" {
				d.Token = config.Token
			}
			if d.Organization == "" {
				d.Organization = config.Organization
			}
		} else {
			return fmt.Errorf("You must provide organization and token")
		}
	}
	d.CommercialType = flags.String("scaleway-commercial-type")
	d.Region = flags.String("scaleway-region")
	d.name = flags.String("scaleway-name")
	d.image = flags.String("scaleway-image")
	d.bootscript = flags.String("scaleway-bootscript")
	d.ip = flags.String("scaleway-ip")
	d.volumes = flags.String("scaleway-volumes")
	d.ipv6 = flags.Bool("scaleway-ipv6")
	d.BaseDriver.SSHUser = flags.String("scaleway-user")
	d.BaseDriver.SSHPort = flags.Int("scaleway-port")
	return
}

// NewDriver returns a new driver
func NewDriver(hostName, storePath string) *Driver {
	return &Driver{
		BaseDriver: &drivers.BaseDriver{},
	}
}

// GetCreateFlags registers the flags
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
	return []mcnflag.Flag{
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_TOKEN",
			Name:   "scaleway-token",
			Usage:  "Scaleway token",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_ORGANIZATION",
			Name:   "scaleway-organization",
			Usage:  "Scaleway organization",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_NAME",
			Name:   "scaleway-name",
			Usage:  "Assign a name",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_COMMERCIAL_TYPE",
			Name:   "scaleway-commercial-type",
			Usage:  "Specifies the commercial type",
			Value:  "START1-S",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_REGION",
			Name:   "scaleway-region",
			Usage:  "Specifies the location (par1,ams1)",
			Value:  "par1",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_IMAGE",
			Name:   "scaleway-image",
			Usage:  "Specifies the image",
			Value:  defaultImage,
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_BOOTSCRIPT",
			Name:   "scaleway-bootscript",
			Usage:  "Specifies the bootscript",
			Value:  defaultBootscript,
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_IP",
			Name:   "scaleway-ip",
			Usage:  "Specifies the IP address",
			Value:  "",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_VOLUMES",
			Name:   "scaleway-volumes",
			Usage:  "Attach additional volume (e.g., 50G)",
			Value:  "",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_USER",
			Name:   "scaleway-user",
			Usage:  "Specifies SSH user name",
			Value:  drivers.DefaultSSHUser,
		},
		mcnflag.IntFlag{
			EnvVar: "SCALEWAY_PORT",
			Name:   "scaleway-port",
			Usage:  "Specifies SSH port",
			Value:  drivers.DefaultSSHPort,
		},
		mcnflag.BoolFlag{
			EnvVar: "SCALEWAY_DEBUG",
			Name:   "scaleway-debug",
			Usage:  "Enables Scaleway client debugging",
		},
		mcnflag.BoolFlag{
			EnvVar: "SCALEWAY_IPV6",
			Name:   "scaleway-ipv6",
			Usage:  "Enable ipv6",
		},
		// mcnflag.StringFlag{
		//     EnvVar: "SCALEWAY_USERDATA",
		//     Name:   "scaleway-userdata",
		//     Usage:  "Path to file with user-data",
		// },
	}
}

func (d *Driver) resolveIP(cl *api.ScalewayAPI) (err error) {
	if d.ip != "" {
		var ips *api.ScalewayGetIPS

		d.IPPersistant = true
		ips, err = cl.GetIPS()
		if err != nil {
			return
		}
		if anonuuid.IsUUID(d.ip) == nil {
			d.IPID = d.ip
			for _, ip := range ips.IPS {
				if ip.ID == d.ip {
					d.IPAddress = ip.Address
					break
				}
			}
			if d.IPAddress == "" {
				err = fmt.Errorf("IP UUID %v not found", d.IPID)
				return
			}
		} else {
			d.IPAddress = d.ip
			for _, ip := range ips.IPS {
				if ip.Address == d.ip {
					d.IPID = ip.ID
					break
				}
			}
			if d.IPID == "" {
				err = fmt.Errorf("IP address %v not found", d.ip)
				return
			}
		}
	} else {
		var ip *api.ScalewayGetIP

		ip, err = cl.NewIP()
		if err != nil {
			return
		}
		d.IPAddress = ip.IP.Address
		d.IPID = ip.IP.ID
	}
	return
}

// Create configures and starts a scaleway server
func (d *Driver) Create() (err error) {
	var publicKey []byte
	var cl *api.ScalewayAPI

	log.Infof("Creating SSH key...")
	if err = ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
		return err
	}
	publicKey, err = ioutil.ReadFile(d.GetSSHKeyPath() + ".pub")
	if err != nil {
		return
	}
	log.Infof("Creating server...")
	cl, err = d.getClient(d.Region)
	if err != nil {
		return
	}
	if err = d.resolveIP(cl); err != nil {
		return
	}
	d.ServerID, err = api.CreateServer(cl, &api.ConfigCreateServer{
		ImageName:         d.image,
		CommercialType:    d.CommercialType,
		Name:              d.name,
		Bootscript:        d.bootscript,
		AdditionalVolumes: d.volumes,
		IP:                d.IPID,
		EnableIPV6:        d.ipv6,
		Env: strings.Join([]string{"AUTHORIZED_KEY",
			strings.Replace(string(publicKey[:len(publicKey)-1]), " ", "_", -1)}, "="),
	})
	if err != nil {
		return
	}
	log.Infof("Starting server...")
	err = api.StartServer(cl, d.ServerID, false)
	d.created = true
	return
}

// GetSSHHostname returns the IP of the server
func (d *Driver) GetSSHHostname() (string, error) {
	return d.IPAddress, nil
}

// GetState returns the state of the server
func (d *Driver) GetState() (st state.State, err error) {
	var server *api.ScalewayServer
	var cl *api.ScalewayAPI

	st = state.Error
	cl, err = d.getClient(d.Region)
	if err != nil {
		return
	}
	server, err = cl.GetServer(d.ServerID)
	if err != nil {
		return
	}
	st = state.None
	switch server.State {
	case "starting":
		st = state.Starting
	case "running":
		if d.created {
			time.Sleep(60 * time.Second)
			d.created = false
		}
		st = state.Running
	case "stopping":
		st = state.Stopping
	case "stopped":
		st = state.Stopped
	}
	if d.stopping {
		time.Sleep(5 * time.Second)
	}
	return
}

// GetURL returns IP + docker port
func (d *Driver) GetURL() (string, error) {
	if err := drivers.MustBeRunning(d); err != nil {
		return "", err
	}
	return fmt.Sprintf("tcp://%s", net.JoinHostPort(d.IPAddress, "2376")), nil
}

func (d *Driver) postAction(action string) (err error) {
	var cl *api.ScalewayAPI

	cl, err = d.getClient(d.Region)
	if err != nil {
		return
	}
	err = cl.PostServerAction(d.ServerID, action)
	return
}

// Kill does nothing
func (d *Driver) Kill() error {
	return errors.New("scaleway driver does not support kill")
}

// Remove shutdowns the server and removes the IP
func (d *Driver) Remove() (err error) {
	var cl *api.ScalewayAPI

	cl, err = d.getClient(d.Region)
	if err != nil {
		return
	}
	errRemove := cl.PostServerAction(d.ServerID, "terminate")
	for {
		_, err = cl.GetServer(d.ServerID)
		if err != nil {
			break
		}
	}
	if !d.IPPersistant {
		err = cl.DeleteIP(d.IPID)
	}
	if errRemove != nil {
		err = errRemove
		return
	}
	return
}

// Restart reboots the server
func (d *Driver) Restart() error {
	return d.postAction("reboot")
}

// Start starts the server
func (d *Driver) Start() error {
	return d.postAction("poweron")
}

// Stop stops the server
func (d *Driver) Stop() error {
	d.stopping = true
	return d.postAction("poweroff")
}
<commit_msg>Update default bootscript to x86_64 mainline 4.4.127 rev1<commit_after>package scaleway

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/log"
	"github.com/docker/machine/libmachine/mcnflag"
	"github.com/docker/machine/libmachine/ssh"
	"github.com/docker/machine/libmachine/state"
	"github.com/moul/anonuuid"
	"github.com/scaleway/scaleway-cli/pkg/api"
	"github.com/scaleway/scaleway-cli/pkg/clilogger"
	"github.com/scaleway/scaleway-cli/pkg/config"
)

const (
	// VERSION represents the semver version of the package
	VERSION           = "v1.3"
	defaultImage      = "ubuntu-xenial"
	defaultBootscript = "x86_64 mainline 4.4.127 rev1"
)

var scwAPI *api.ScalewayAPI

// Driver represents the docker driver interface
type Driver struct {
	*drivers.BaseDriver
	ServerID       string
	Organization   string
	IPID           string
	Token          string
	CommercialType string
	Region         string
	name           string
	image          string
	bootscript     string
	ip             string
	volumes        string
	IPPersistant   bool
	stopping       bool
	created        bool
	ipv6           bool
	// userDataFile string
}

// DriverName returns the name of the driver
func (d *Driver) DriverName() string {
	if d.CommercialType == "" {
		return "scaleway"
	}
	return fmt.Sprintf("scaleway(%v)", d.CommercialType)
}

func (d *Driver) getClient(region string) (cl *api.ScalewayAPI, err error) {
	if scwAPI == nil {
		if region == "" {
			region = "par1"
		}
		scwAPI, err = api.NewScalewayAPI(d.Organization, d.Token, "docker-machine-driver-scaleway/"+VERSION, region, clilogger.SetupLogger)
	}
	cl = scwAPI
	return
}

// SetConfigFromFlags sets the flags
func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) (err error) {
	if flags.Bool("scaleway-debug") {
		logrus.SetOutput(os.Stderr)
		logrus.SetLevel(logrus.DebugLevel)
	}

	d.Token, d.Organization = flags.String("scaleway-token"), flags.String("scaleway-organization")
	if d.Token == "" || d.Organization == "" {
		config, cfgErr := config.GetConfig()
		if cfgErr == nil {
			if d.Token == "" {
				d.Token = config.Token
			}
			if d.Organization == "" {
				d.Organization = config.Organization
			}
		} else {
			return fmt.Errorf("You must provide organization and token")
		}
	}
	d.CommercialType = flags.String("scaleway-commercial-type")
	d.Region = flags.String("scaleway-region")
	d.name = flags.String("scaleway-name")
	d.image = flags.String("scaleway-image")
	d.bootscript = flags.String("scaleway-bootscript")
	d.ip = flags.String("scaleway-ip")
	d.volumes = flags.String("scaleway-volumes")
	d.ipv6 = flags.Bool("scaleway-ipv6")
	d.BaseDriver.SSHUser = flags.String("scaleway-user")
	d.BaseDriver.SSHPort = flags.Int("scaleway-port")
	return
}

// NewDriver returns a new driver
func NewDriver(hostName, storePath string) *Driver {
	return &Driver{
		BaseDriver: &drivers.BaseDriver{},
	}
}

// GetCreateFlags registers the flags
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
	return []mcnflag.Flag{
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_TOKEN",
			Name:   "scaleway-token",
			Usage:  "Scaleway token",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_ORGANIZATION",
			Name:   "scaleway-organization",
			Usage:  "Scaleway organization",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_NAME",
			Name:   "scaleway-name",
			Usage:  "Assign a name",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_COMMERCIAL_TYPE",
			Name:   "scaleway-commercial-type",
			Usage:  "Specifies the commercial type",
			Value:  "START1-S",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_REGION",
			Name:   "scaleway-region",
			Usage:  "Specifies the location (par1,ams1)",
			Value:  "par1",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_IMAGE",
			Name:   "scaleway-image",
			Usage:  "Specifies the image",
			Value:  defaultImage,
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_BOOTSCRIPT",
			Name:   "scaleway-bootscript",
			Usage:  "Specifies the bootscript",
			Value:  defaultBootscript,
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_IP",
			Name:   "scaleway-ip",
			Usage:  "Specifies the IP address",
			Value:  "",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_VOLUMES",
			Name:   "scaleway-volumes",
			Usage:  "Attach additional volume (e.g., 50G)",
			Value:  "",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_USER",
			Name:   "scaleway-user",
			Usage:  "Specifies SSH user name",
			Value:  drivers.DefaultSSHUser,
		},
		mcnflag.IntFlag{
			EnvVar: "SCALEWAY_PORT",
			Name:   "scaleway-port",
			Usage:  "Specifies SSH port",
			Value:  drivers.DefaultSSHPort,
		},
		mcnflag.BoolFlag{
			EnvVar: "SCALEWAY_DEBUG",
			Name:   "scaleway-debug",
			Usage:  "Enables Scaleway client debugging",
		},
		mcnflag.BoolFlag{
			EnvVar: "SCALEWAY_IPV6",
			Name:   "scaleway-ipv6",
			Usage:  "Enable ipv6",
		},
		// mcnflag.StringFlag{
		//     EnvVar: "SCALEWAY_USERDATA",
		//     Name:   "scaleway-userdata",
		//     Usage:  "Path to file with user-data",
		// },
	}
}

func (d *Driver) resolveIP(cl *api.ScalewayAPI) (err error) {
	if d.ip != "" {
		var ips *api.ScalewayGetIPS

		d.IPPersistant = true
		ips, err = cl.GetIPS()
		if err != nil {
			return
		}
		if anonuuid.IsUUID(d.ip) == nil {
			d.IPID = d.ip
			for _, ip := range ips.IPS {
				if ip.ID == d.ip {
					d.IPAddress = ip.Address
					break
				}
			}
			if d.IPAddress == "" {
				err = fmt.Errorf("IP UUID %v not found", d.IPID)
				return
			}
		} else {
			d.IPAddress = d.ip
			for _, ip := range ips.IPS {
				if ip.Address == d.ip {
					d.IPID = ip.ID
					break
				}
			}
			if d.IPID == "" {
				err = fmt.Errorf("IP address %v not found", d.ip)
				return
			}
		}
	} else {
		var ip *api.ScalewayGetIP

		ip, err = cl.NewIP()
		if err != nil {
			return
		}
		d.IPAddress = ip.IP.Address
		d.IPID = ip.IP.ID
	}
	return
}

// Create configures and starts a scaleway server
func (d *Driver) Create() (err error) {
	var publicKey []byte
	var cl *api.ScalewayAPI

	log.Infof("Creating SSH key...")
	if err = ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
		return err
	}
	publicKey, err = ioutil.ReadFile(d.GetSSHKeyPath() + ".pub")
	if err != nil {
		return
	}
	log.Infof("Creating server...")
	cl, err = d.getClient(d.Region)
	if err != nil {
		return
	}
	if err = d.resolveIP(cl); err != nil {
		return
	}
	d.ServerID, err = api.CreateServer(cl, &api.ConfigCreateServer{
		ImageName:         d.image,
		CommercialType:    d.CommercialType,
		Name:              d.name,
		Bootscript:        d.bootscript,
		AdditionalVolumes: d.volumes,
		IP:                d.IPID,
		EnableIPV6:        d.ipv6,
		Env: strings.Join([]string{"AUTHORIZED_KEY",
			strings.Replace(string(publicKey[:len(publicKey)-1]), " ", "_", -1)}, "="),
	})
	if err != nil {
		return
	}
	log.Infof("Starting server...")
	err = api.StartServer(cl, d.ServerID, false)
	d.created = true
	return
}

// GetSSHHostname returns the IP of the server
func (d *Driver) GetSSHHostname() (string, error) {
	return d.IPAddress, nil
}

// GetState returns the state of the server
func (d *Driver) GetState() (st state.State, err error) {
	var server *api.ScalewayServer
	var cl *api.ScalewayAPI

	st = state.Error
	cl, err = d.getClient(d.Region)
	if err != nil {
		return
	}
	server, err = cl.GetServer(d.ServerID)
	if err != nil {
		return
	}
	st = state.None
	switch server.State {
	case "starting":
		st = state.Starting
	case "running":
		if d.created {
			time.Sleep(60 * time.Second)
			d.created = false
		}
		st = state.Running
	case "stopping":
		st = state.Stopping
	case "stopped":
		st = state.Stopped
	}
	if d.stopping {
		time.Sleep(5 * time.Second)
	}
	return
}

// GetURL returns IP + docker port
func (d *Driver) GetURL() (string, error) {
	if err := drivers.MustBeRunning(d); err != nil {
		return "", err
	}
	return fmt.Sprintf("tcp://%s", net.JoinHostPort(d.IPAddress, "2376")), nil
}

func (d *Driver) postAction(action string) (err error) {
	var cl *api.ScalewayAPI

	cl, err = d.getClient(d.Region)
	if err != nil {
		return
	}
	err = cl.PostServerAction(d.ServerID, action)
	return
}

// Kill does nothing
func (d *Driver) Kill() error {
	return errors.New("scaleway driver does not support kill")
}

// Remove shutdowns the server and removes the IP
func (d *Driver) Remove() (err error) {
	var cl *api.ScalewayAPI

	cl, err = d.getClient(d.Region)
	if err != nil {
		return
	}
	errRemove := cl.PostServerAction(d.ServerID, "terminate")
	for {
		_, err = cl.GetServer(d.ServerID)
		if err != nil {
			break
		}
	}
	if !d.IPPersistant {
		err = cl.DeleteIP(d.IPID)
	}
	if errRemove != nil {
		err = errRemove
		return
	}
	return
}

// Restart reboots the server
func (d *Driver) Restart() error {
	return d.postAction("reboot")
}

// Start starts the server
func (d *Driver) Start() error {
	return d.postAction("poweron")
}

// Stop stops the server
func (d *Driver) Stop() error {
	d.stopping = true
	return d.postAction("poweroff")
}
<|endoftext|>


<commit_before>package scaleway

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"strings"

	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/log"
	"github.com/docker/machine/libmachine/mcnflag"
	"github.com/docker/machine/libmachine/ssh"
	"github.com/docker/machine/libmachine/state"
	"github.com/scaleway/scaleway-cli/pkg/api"
)

const (
	// VERSION represents the semver version of the package
	VERSION           = "v1.0.0"
	defaultImage      = "ubuntu-trusty"
	defaultBootscript = "docker"
)

var scwAPI *api.ScalewayAPI

type Driver struct {
	*drivers.BaseDriver
	ServerID       string
	Organization   string
	Token          string
	commercialType string
	name           string
	// size         string
	// userDataFile string
	// ipv6         bool
}

func (d *Driver) DriverName() string {
	return "scaleway"
}

func (d *Driver) getClient() (cl *api.ScalewayAPI, err error) {
	if scwAPI == nil {
		scwAPI, err = api.NewScalewayAPI(d.Organization, d.Token, "docker-machine-driver-scaleway/%v"+VERSION)
	}
	cl = scwAPI
	return
}

func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) (err error) {
	d.Token, d.Organization = flags.String("scaleway-token"), flags.String("scaleway-organization")
	if d.Token == "" || d.Organization == "" {
		return fmt.Errorf("You must provide organization and token")
	}
	d.commercialType = flags.String("scaleway-commercial-type")
	d.name = flags.String("scaleway-name")
	return
}

func NewDriver(hostName, storePath string) *Driver {
	return &Driver{
		BaseDriver: &drivers.BaseDriver{},
	}
}

func (d *Driver) GetCreateFlags() []mcnflag.Flag {
	return []mcnflag.Flag{
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_TOKEN",
			Name:   "scaleway-token",
			Usage:  "Scaleway token",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_ORGANIZATION",
			Name:   "scaleway-organization",
			Usage:  "Scaleway organization",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_NAME",
			Name:   "scaleway-name",
			Usage:  "Assign a name",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_COMMERCIAL_TYPE",
			Name:   "scaleway-commercial-type",
			Usage:  "Specifies the commercial type",
			Value:  "VC1S",
		},
		// mcnflag.StringFlag{
		//     EnvVar: "SCALEWAY_USERDATA",
		//     Name:   "scaleway-userdata",
		//     Usage:  "Path to file with user-data",
		// },
		// mcnflag.BoolFlag{
		// 	EnvVar: "SCALEWAY_IPV6",
		// 	Name:   "scaleway-ipv6",
		// 	Usage:  "Enable ipv6",
		// },
	}
}

func (d *Driver) Create() (err error) {
	var publicKey []byte
	var cl *api.ScalewayAPI
	var ip *api.ScalewayGetIP

	log.Infof("Creating SSH key...")
	if err = ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
		return err
	}
	publicKey, err = ioutil.ReadFile(d.GetSSHKeyPath() + ".pub")
	if err != nil {
		return
	}
	log.Infof("Creating server...")
	cl, err = d.getClient()
	if err != nil {
		return
	}
	ip, err = cl.NewIP()
	if err != nil {
		return
	}
	d.IPAddress = ip.IP.Address
	d.ServerID, err = api.CreateServer(cl, &api.ConfigCreateServer{
		ImageName:      defaultImage,
		CommercialType: d.commercialType,
		Name:           d.name,
		Bootscript:     defaultBootscript,
		IP:             ip.IP.ID,
		Env: strings.Join([]string{"AUTHORIZED_KEY",
			strings.Replace(string(publicKey[:len(publicKey)-1]), " ", "_", -1)}, "="),
	})
	if err != nil {
		return
	}
	log.Infof("Starting server...")
	err = api.StartServer(cl, d.ServerID, false)
	return
}

func (d *Driver) GetSSHHostname() (string, error) {
	return d.IPAddress, nil
}

func (d *Driver) GetState() (st state.State, err error) {
	var server *api.ScalewayServer
	var cl *api.ScalewayAPI

	st = state.Error
	cl, err = d.getClient()
	if err != nil {
		return
	}
	server, err = cl.GetServer(d.ServerID)
	if err != nil {
		return
	}
	st = state.None
	switch server.State {
	case "starting":
		st = state.Starting
	case "running":
		st = state.Running
	case "stopping":
		st = state.Stopping
	}
	return
}

func (d *Driver) GetURL() (string, error) {
	if err := drivers.MustBeRunning(d); err != nil {
		return "", err
	}
	return fmt.Sprintf("tcp://%s", net.JoinHostPort(d.IPAddress, "2376")), nil
}

func (d *Driver) Kill() error {
	return errors.New("scaleway driver does not support kill")
}

func (d *Driver) Remove() error {
	log.Info("Remove: not implemented yet")
	return nil
}

func (d *Driver) Restart() error {
	log.Info("Restart: not implemented yet")
	return nil
}

func (d *Driver) Start() error {
	log.Info("Start: not implemented yet")
	return nil
}

func (d *Driver) Stop() error {
	log.Info("Stop: not implemented yet")
	return nil
}
<commit_msg>Driver: add stopped state<commit_after>package scaleway

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"strings"
	"time"

	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/log"
	"github.com/docker/machine/libmachine/mcnflag"
	"github.com/docker/machine/libmachine/ssh"
	"github.com/docker/machine/libmachine/state"
	"github.com/scaleway/scaleway-cli/pkg/api"
)

const (
	// VERSION represents the semver version of the package
	VERSION           = "v1.0.0"
	defaultImage      = "ubuntu-trusty"
	defaultBootscript = "docker"
)

var scwAPI *api.ScalewayAPI

type Driver struct {
	*drivers.BaseDriver
	ServerID       string
	Organization   string
	Token          string
	commercialType string
	name           string
	// size         string
	// userDataFile string
	// ipv6         bool
}

func (d *Driver) DriverName() string {
	return "scaleway"
}

func (d *Driver) getClient() (cl *api.ScalewayAPI, err error) {
	if scwAPI == nil {
		scwAPI, err = api.NewScalewayAPI(d.Organization, d.Token, "docker-machine-driver-scaleway/%v"+VERSION)
	}
	cl = scwAPI
	return
}

func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) (err error) {
	d.Token, d.Organization = flags.String("scaleway-token"), flags.String("scaleway-organization")
	if d.Token == "" || d.Organization == "" {
		return fmt.Errorf("You must provide organization and token")
	}
	d.commercialType = flags.String("scaleway-commercial-type")
	d.name = flags.String("scaleway-name")
	return
}

func NewDriver(hostName, storePath string) *Driver {
	return &Driver{
		BaseDriver: &drivers.BaseDriver{},
	}
}

func (d *Driver) GetCreateFlags() []mcnflag.Flag {
	return []mcnflag.Flag{
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_TOKEN",
			Name:   "scaleway-token",
			Usage:  "Scaleway token",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_ORGANIZATION",
			Name:   "scaleway-organization",
			Usage:  "Scaleway organization",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_NAME",
			Name:   "scaleway-name",
			Usage:  "Assign a name",
		},
		mcnflag.StringFlag{
			EnvVar: "SCALEWAY_COMMERCIAL_TYPE",
			Name:   "scaleway-commercial-type",
			Usage:  "Specifies the commercial type",
			Value:  "VC1S",
		},
		// mcnflag.StringFlag{
		//     EnvVar: "SCALEWAY_USERDATA",
		//     Name:   "scaleway-userdata",
		//     Usage:  "Path to file with user-data",
		// },
		// mcnflag.BoolFlag{
		// 	EnvVar: "SCALEWAY_IPV6",
		// 	Name:   "scaleway-ipv6",
		// 	Usage:  "Enable ipv6",
		// },
	}
}

func (d *Driver) Create() (err error) {
	var publicKey []byte
	var cl *api.ScalewayAPI
	var ip *api.ScalewayGetIP

	log.Infof("Creating SSH key...")
	if err = ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
		return err
	}
	publicKey, err = ioutil.ReadFile(d.GetSSHKeyPath() + ".pub")
	if err != nil {
		return
	}
	log.Infof("Creating server...")
	cl, err = d.getClient()
	if err != nil {
		return
	}
	ip, err = cl.NewIP()
	if err != nil {
		return
	}
	d.IPAddress = ip.IP.Address
	d.ServerID, err = api.CreateServer(cl, &api.ConfigCreateServer{
		ImageName:      defaultImage,
		CommercialType: d.commercialType,
		Name:           d.name,
		Bootscript:     defaultBootscript,
		IP:             ip.IP.ID,
		Env: strings.Join([]string{"AUTHORIZED_KEY",
			strings.Replace(string(publicKey[:len(publicKey)-1]), " ", "_", -1)}, "="),
	})
	if err != nil {
		return
	}
	log.Infof("Starting server...")
	err = api.StartServer(cl, d.ServerID, false)
	return
}

func (d *Driver) GetSSHHostname() (string, error) {
	return d.IPAddress, nil
}

func (d *Driver) GetState() (st state.State, err error) {
	var server *api.ScalewayServer
	var cl *api.ScalewayAPI

	st = state.Error
	cl, err = d.getClient()
	if err != nil {
		return
	}
	server, err = cl.GetServer(d.ServerID)
	if err != nil {
		return
	}
	st = state.None
	switch server.State {
	case "starting":
		st = state.Starting
	case "running":
		st = state.Running
	case "stopping":
		st = state.Stopping
	case "stopped":
		st = state.Stopped
	}
	time.Sleep(5 * time.Second)
	return
}

func (d *Driver) GetURL() (string, error) {
	if err := drivers.MustBeRunning(d); err != nil {
		return "", err
	}
	return fmt.Sprintf("tcp://%s", net.JoinHostPort(d.IPAddress, "2376")), nil
}

func (d *Driver) Kill() error {
	return errors.New("scaleway driver does not support kill")
}

func (d *Driver) Remove() error {
	log.Info("Remove: not implemented yet")
	return nil
}

func (d *Driver) Restart() error {
	log.Info("Restart: not implemented yet")
	return nil
}

func (d *Driver) Start() error {
	log.Info("Start: not implemented yet")
	return nil
}

func (d *Driver) Stop() error {
	log.Info("Stop: not implemented yet")
	return nil
}
<|endoftext|>


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

package gps

import (
	"reflect"
	"testing"
)

func TestRootdataExternalImports(t *testing.T) {
	fix := basicFixtures["shared dependency with overlapping constraints"]

	params := SolveParameters{
		RootDir:         string(fix.ds[0].n),
		RootPackageTree: fix.rootTree(),
		Manifest:        fix.rootmanifest(),
		ProjectAnalyzer: naiveAnalyzer{},
		stdLibFn:        func(string) bool { return false },
		mkBridgeFn:      overrideMkBridge,
	}

	is, err := Prepare(params, newdepspecSM(fix.ds, nil))
	if err != nil {
		t.Fatalf("Unexpected error while prepping solver: %s", err)
	}
	rd := is.(*solver).rd

	want := []string{"a", "b"}
	got := rd.externalImportList(params.stdLibFn)
	if !reflect.DeepEqual(want, got) {
		t.Errorf("Unexpected return from rootdata.externalImportList:\n\t(GOT): %s\n\t(WNT): %s", got, want)
	}

	// Add a require
	rd.req["c"] = true

	want = []string{"a", "b", "c"}
	got = rd.externalImportList(params.stdLibFn)
	if !reflect.DeepEqual(want, got) {
		t.Errorf("Unexpected return from rootdata.externalImportList:\n\t(GOT): %s\n\t(WNT): %s", got, want)
	}

	// Add same path as import
	poe := rd.rpt.Packages["root"]
	poe.P.Imports = []string{"a", "b", "c"}
	rd.rpt.Packages["root"] = poe

	// should still be the same
	got = rd.externalImportList(params.stdLibFn)
	if !reflect.DeepEqual(want, got) {
		t.Errorf("Unexpected return from rootdata.externalImportList:\n\t(GOT): %s\n\t(WNT): %s", got, want)
	}

	// Add an ignore, but not on the required path (Prepare makes that
	// combination impossible)

	rd.ig["b"] = true
	want = []string{"a", "c"}
	got = rd.externalImportList(params.stdLibFn)
	if !reflect.DeepEqual(want, got) {
		t.Errorf("Unexpected return from rootdata.externalImportList:\n\t(GOT): %s\n\t(WNT): %s", got, want)
	}
}

func TestGetApplicableConstraints(t *testing.T) {
	fix := basicFixtures["shared dependency with overlapping constraints"]

	params := SolveParameters{
		RootDir:         string(fix.ds[0].n),
		RootPackageTree: fix.rootTree(),
		Manifest:        fix.rootmanifest(),
		ProjectAnalyzer: naiveAnalyzer{},
		stdLibFn:        func(string) bool { return false },
		mkBridgeFn:      overrideMkBridge,
	}

	is, err := Prepare(params, newdepspecSM(fix.ds, nil))
	if err != nil {
		t.Fatalf("Unexpected error while prepping solver: %s", err)
	}
	rd := is.(*solver).rd

	table := []struct {
		name   string
		mut    func()
		result []workingConstraint
	}{
		{
			name: "base case, two constraints",
			mut:  func() {},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
			},
		},
		{
			name: "with unconstrained require",
			mut: func() {
				// No constraint means it doesn't show up
				rd.req["c"] = true
			},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
			},
		},
		{
			name: "with unconstrained import",
			mut: func() {
				// Again, no constraint means it doesn't show up
				poe := rd.rpt.Packages["root"]
				poe.P.Imports = []string{"a", "b", "d"}
				rd.rpt.Packages["root"] = poe
			},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
			},
		},
		{
			name: "constraint on required",
			mut: func() {
				rd.rm.Deps["c"] = ProjectProperties{
					Constraint: NewBranch("foo"),
				}
			},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("c"),
					Constraint: NewBranch("foo"),
				},
			},
		},
		{
			name: "override on imported",
			mut: func() {
				rd.ovr["d"] = ProjectProperties{
					Constraint: NewBranch("bar"),
				}
			},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("c"),
					Constraint: NewBranch("foo"),
				},
				{
					Ident:           mkPI("d"),
					Constraint:      NewBranch("bar"),
					overrConstraint: true,
				},
			},
		},
		{
			// It is certainly the simplest and most rule-abiding solution to
			// drop the constraint in this case, but is there a chance it would
			// violate the principle of least surprise?
			name: "ignore imported and overridden pkg",
			mut: func() {
				rd.ig["d"] = true
			},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("c"),
					Constraint: NewBranch("foo"),
				},
			},
		},
	}

	for _, fix := range table {
		t.Run(fix.name, func(t *testing.T) {
			fix.mut()

			got := rd.getApplicableConstraints(params.stdLibFn)
			if !reflect.DeepEqual(fix.result, got) {
				t.Errorf("unexpected applicable constraint set:\n\t(GOT): %+v\n\t(WNT): %+v", got, fix.result)
			}
		})
	}
}
<commit_msg>test(gps): add test for rootdata.isIgnored()<commit_after>// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gps

import (
	"reflect"
	"testing"

	"github.com/golang/dep/internal/gps/pkgtree"
)

func TestRootdataExternalImports(t *testing.T) {
	fix := basicFixtures["shared dependency with overlapping constraints"]

	params := SolveParameters{
		RootDir:         string(fix.ds[0].n),
		RootPackageTree: fix.rootTree(),
		Manifest:        fix.rootmanifest(),
		ProjectAnalyzer: naiveAnalyzer{},
		stdLibFn:        func(string) bool { return false },
		mkBridgeFn:      overrideMkBridge,
	}

	is, err := Prepare(params, newdepspecSM(fix.ds, nil))
	if err != nil {
		t.Fatalf("Unexpected error while prepping solver: %s", err)
	}
	rd := is.(*solver).rd

	want := []string{"a", "b"}
	got := rd.externalImportList(params.stdLibFn)
	if !reflect.DeepEqual(want, got) {
		t.Errorf("Unexpected return from rootdata.externalImportList:\n\t(GOT): %s\n\t(WNT): %s", got, want)
	}

	// Add a require
	rd.req["c"] = true

	want = []string{"a", "b", "c"}
	got = rd.externalImportList(params.stdLibFn)
	if !reflect.DeepEqual(want, got) {
		t.Errorf("Unexpected return from rootdata.externalImportList:\n\t(GOT): %s\n\t(WNT): %s", got, want)
	}

	// Add same path as import
	poe := rd.rpt.Packages["root"]
	poe.P.Imports = []string{"a", "b", "c"}
	rd.rpt.Packages["root"] = poe

	// should still be the same
	got = rd.externalImportList(params.stdLibFn)
	if !reflect.DeepEqual(want, got) {
		t.Errorf("Unexpected return from rootdata.externalImportList:\n\t(GOT): %s\n\t(WNT): %s", got, want)
	}

	// Add an ignore, but not on the required path (Prepare makes that
	// combination impossible)

	rd.ig["b"] = true
	want = []string{"a", "c"}
	got = rd.externalImportList(params.stdLibFn)
	if !reflect.DeepEqual(want, got) {
		t.Errorf("Unexpected return from rootdata.externalImportList:\n\t(GOT): %s\n\t(WNT): %s", got, want)
	}
}

func TestGetApplicableConstraints(t *testing.T) {
	fix := basicFixtures["shared dependency with overlapping constraints"]

	params := SolveParameters{
		RootDir:         string(fix.ds[0].n),
		RootPackageTree: fix.rootTree(),
		Manifest:        fix.rootmanifest(),
		ProjectAnalyzer: naiveAnalyzer{},
		stdLibFn:        func(string) bool { return false },
		mkBridgeFn:      overrideMkBridge,
	}

	is, err := Prepare(params, newdepspecSM(fix.ds, nil))
	if err != nil {
		t.Fatalf("Unexpected error while prepping solver: %s", err)
	}
	rd := is.(*solver).rd

	table := []struct {
		name   string
		mut    func()
		result []workingConstraint
	}{
		{
			name: "base case, two constraints",
			mut:  func() {},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
			},
		},
		{
			name: "with unconstrained require",
			mut: func() {
				// No constraint means it doesn't show up
				rd.req["c"] = true
			},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
			},
		},
		{
			name: "with unconstrained import",
			mut: func() {
				// Again, no constraint means it doesn't show up
				poe := rd.rpt.Packages["root"]
				poe.P.Imports = []string{"a", "b", "d"}
				rd.rpt.Packages["root"] = poe
			},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
			},
		},
		{
			name: "constraint on required",
			mut: func() {
				rd.rm.Deps["c"] = ProjectProperties{
					Constraint: NewBranch("foo"),
				}
			},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("c"),
					Constraint: NewBranch("foo"),
				},
			},
		},
		{
			name: "override on imported",
			mut: func() {
				rd.ovr["d"] = ProjectProperties{
					Constraint: NewBranch("bar"),
				}
			},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("c"),
					Constraint: NewBranch("foo"),
				},
				{
					Ident:           mkPI("d"),
					Constraint:      NewBranch("bar"),
					overrConstraint: true,
				},
			},
		},
		{
			// It is certainly the simplest and most rule-abiding solution to
			// drop the constraint in this case, but is there a chance it would
			// violate the principle of least surprise?
			name: "ignore imported and overridden pkg",
			mut: func() {
				rd.ig["d"] = true
			},
			result: []workingConstraint{
				{
					Ident:      mkPI("a"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("b"),
					Constraint: mkSVC("1.0.0"),
				},
				{
					Ident:      mkPI("c"),
					Constraint: NewBranch("foo"),
				},
			},
		},
	}

	for _, fix := range table {
		t.Run(fix.name, func(t *testing.T) {
			fix.mut()

			got := rd.getApplicableConstraints(params.stdLibFn)
			if !reflect.DeepEqual(fix.result, got) {
				t.Errorf("unexpected applicable constraint set:\n\t(GOT): %+v\n\t(WNT): %+v", got, fix.result)
			}
		})
	}
}

func TestIsIgnored(t *testing.T) {
	cases := []struct {
		name           string
		ignorePkgs     map[string]bool
		wantIgnored    []string
		wantNotIgnored []string
	}{
		{
			name: "no ignore",
		},
		{
			name: "ignores without wildcard",
			ignorePkgs: map[string]bool{
				"a/b/c":   true,
				"m/n":     true,
				"gophers": true,
			},
			wantIgnored:    []string{"a/b/c", "m/n", "gophers"},
			wantNotIgnored: []string{"somerandomstring"},
		},
		{
			name: "ignores with wildcard",
			ignorePkgs: map[string]bool{
				"a/b/c*":    true,
				"m/n*/o":    true,
				"*x/y/z":    true,
				"A/B*/C/D*": true,
			},
			wantIgnored:    []string{"a/b/c", "a/b/c/d", "a/b/c-d", "m/n*/o", "*x/y/z", "A/B*/C/D", "A/B*/C/D/E"},
			wantNotIgnored: []string{"m/n/o", "m/n*", "x/y/z", "*x/y/z/a", "*x", "A/B", "A/B*/C"},
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			rd := rootdata{
				ig:    c.ignorePkgs,
				igpfx: pkgtree.CreateIgnorePrefixTree(c.ignorePkgs),
			}

			for _, p := range c.wantIgnored {
				if !rd.isIgnored(p) {
					t.Fatalf("expected %q to be ignored", p)
				}
			}

			for _, p := range c.wantNotIgnored {
				if rd.isIgnored(p) {
					t.Fatalf("expected %q to be not ignored", p)
				}
			}
		})
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 CoreOS, Inc.
//
// 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.

// The gcp provider fetches a remote configuration from the GCE user-data
// metadata service URL.

package gcp

import (
	"net/http"
	"net/url"

	"github.com/coreos/ignition/v2/config/v3_4_experimental/types"
	"github.com/coreos/ignition/v2/internal/providers/util"
	"github.com/coreos/ignition/v2/internal/resource"

	"github.com/coreos/vcontext/report"
)

var (
	userdataUrl = url.URL{
		Scheme: "http",
		Host:   "metadata.google.internal",
		Path:   "computeMetadata/v1/instance/attributes/user-data",
	}
	metadataHeaderKey = "Metadata-Flavor"
	metadataHeaderVal = "Google"
)

func FetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
	headers := make(http.Header)
	headers.Set(metadataHeaderKey, metadataHeaderVal)
	data, err := f.FetchToBuffer(userdataUrl, resource.FetchOptions{
		Headers: headers,
	})
	if err != nil && err != resource.ErrNotFound {
		return types.Config{}, report.Report{}, err
	}

	return util.ParseConfig(f.Logger, data)
}
<commit_msg>providers/gcp: access GCP metadata service by IP address<commit_after>// Copyright 2016 CoreOS, Inc.
//
// 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.

// The gcp provider fetches a remote configuration from the GCE user-data
// metadata service URL.

package gcp

import (
	"net/http"
	"net/url"

	"github.com/coreos/ignition/v2/config/v3_4_experimental/types"
	"github.com/coreos/ignition/v2/internal/providers/util"
	"github.com/coreos/ignition/v2/internal/resource"

	"github.com/coreos/vcontext/report"
)

var (
	userdataUrl = url.URL{
		Scheme: "http",
		Host:   "169.254.169.254",
		Path:   "computeMetadata/v1/instance/attributes/user-data",
	}
	metadataHeaderKey = "Metadata-Flavor"
	metadataHeaderVal = "Google"
)

func FetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
	headers := make(http.Header)
	headers.Set(metadataHeaderKey, metadataHeaderVal)
	data, err := f.FetchToBuffer(userdataUrl, resource.FetchOptions{
		Headers: headers,
	})
	if err != nil && err != resource.ErrNotFound {
		return types.Config{}, report.Report{}, err
	}

	return util.ParseConfig(f.Logger, data)
}
<|endoftext|>


<commit_before>// Copyright 2020 The OPA Authors.  All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.

package opa

import (
	"bytes"
	"context"
	"fmt"
	"sync"

	"github.com/open-policy-agent/opa/metrics"
)

// pool maintains a pool of WebAssemly VM instances.
type pool struct {
	available      chan struct{}
	mutex          sync.Mutex
	initialized    bool
	closed         bool
	policy         []byte
	parsedData     []byte // Parsed parsedData memory segment, used to seed new VM's
	parsedDataAddr int32  // Address for parsedData value root, used to seed new VM's
	memoryMinPages uint32
	memoryMaxPages uint32
	vms            []*vm // All current VM instances, acquired or not.
	acquired       []bool
	pendingReinit  *vm
	blockedReinit  chan struct{}
}

// newPool constructs a new pool with the pool and VM configuration provided.
func newPool(poolSize, memoryMinPages, memoryMaxPages uint32) *pool {
	available := make(chan struct{}, poolSize)
	for i := uint32(0); i < poolSize; i++ {
		available <- struct{}{}
	}

	return &pool{
		memoryMinPages: memoryMinPages,
		memoryMaxPages: memoryMaxPages,
		available:      available,
		vms:            make([]*vm, 0),
		acquired:       make([]bool, 0),
	}
}

// Acquire obtains a VM from the pool, waiting if all VMms are in use
// and building one as necessary. Returns either ErrNotReady or
// ErrInternal if an error.
func (p *pool) Acquire(ctx context.Context, metrics metrics.Metrics) (*vm, error) {
	metrics.Timer("wasm_pool_acquire").Start()
	defer metrics.Timer("wasm_pool_acquire").Stop()

	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	case <-p.available:
	}

	p.mutex.Lock()
	defer p.mutex.Unlock()

	if !p.initialized || p.closed {
		return nil, ErrNotReady
	}

	for i, vm := range p.vms {
		if !p.acquired[i] {
			p.acquired[i] = true
			return vm, nil
		}
	}

	policy, parsedData, parsedDataAddr := p.policy, p.parsedData, p.parsedDataAddr

	p.mutex.Unlock()
	vm, err := newVM(vmOpts{
		policy:         policy,
		data:           nil,
		parsedData:     parsedData,
		parsedDataAddr: parsedDataAddr,
		memoryMin:      p.memoryMinPages,
		memoryMax:      p.memoryMaxPages,
	})
	p.mutex.Lock()

	if err != nil {
		p.available <- struct{}{}
		return nil, fmt.Errorf("%v: %w", err, ErrInternal)
	}

	p.acquired = append(p.acquired, true)
	p.vms = append(p.vms, vm)
	return vm, nil
}

// Release releases the VM back to the pool.
func (p *pool) Release(vm *vm, metrics metrics.Metrics) {
	metrics.Timer("wasm_pool_release").Start()
	defer metrics.Timer("wasm_pool_release").Stop()

	p.mutex.Lock()

	// If the policy data setting is waiting for this one, don't release it back to the general consumption.
	// Note the reinit is responsible for pushing to available channel once done with the VM.
	if vm == p.pendingReinit {
		p.mutex.Unlock()
		p.blockedReinit <- struct{}{}
		return
	}

	for i := range p.vms {
		if p.vms[i] == vm {
			p.acquired[i] = false
			p.mutex.Unlock()
			p.available <- struct{}{}
			return
		}
	}

	// VM instance not found anymore, hence pool reconfigured and can release the VM.

	p.mutex.Unlock()
	p.available <- struct{}{}

	vm.Close()
}

// SetPolicyData re-initializes the vms within the pool with the new policy
// and data. The re-initialization takes place atomically: all new vms
// are constructed in advance before touching the pool.  Returns
// either ErrNotReady, ErrInvalidPolicy or ErrInternal if an error
// occurs.
func (p *pool) SetPolicyData(policy []byte, data []byte) error {
	p.mutex.Lock()

	if !p.initialized {
		var err error
		var parsedData []byte
		var parsedDataAddr int32
		for i := 0; i < len(p.available); i++ {
			var vm *vm
			vm, err = newVM(vmOpts{
				policy:         policy,
				data:           data,
				parsedData:     parsedData,
				parsedDataAddr: parsedDataAddr,
				memoryMin:      p.memoryMinPages,
				memoryMax:      p.memoryMaxPages,
			})

			if err == nil {
				if parsedData == nil {
					parsedDataAddr, parsedData = vm.cloneDataSegment()
					p.memoryMinPages = pages(vm.memory.Length())
				}
				p.vms = append(p.vms, vm)
				p.acquired = append(p.acquired, false)
			} else {
				err = fmt.Errorf("%v: %w", err, ErrInvalidPolicyOrData)
				break
			}
		}

		if err == nil {
			p.initialized = true
			p.policy, p.parsedData, p.parsedDataAddr = policy, parsedData, parsedDataAddr
		}

		p.mutex.Unlock()
		return err
	}

	if p.closed {
		p.mutex.Unlock()
		return ErrNotReady
	}

	currentPolicy, currentData := p.policy, p.parsedData
	p.mutex.Unlock()

	if bytes.Equal(policy, currentPolicy) && bytes.Equal(data, currentData) {
		return nil

	}

	err := p.setPolicyData(policy, data)
	if err != nil {
		return fmt.Errorf("%v: %w", err, ErrInternal)
	}

	return nil
}

func (p *pool) SetDataPath(path []string, value interface{}) error {
	var patchedData []byte
	var patchedDataAddr int32
	var seedMemorySize uint32
	for i, activations := 0, 0; true; i++ {
		vm := p.wait(i)
		if vm == nil {
			// All have been converted.
			return nil
		}

		if err := vm.SetDataPath(path, value); err != nil {
			p.remove(i)
			p.Release(vm, metrics.New())
			if activations == 0 {
				return err
			}
		} else {
			// Before releasing our first succesfully patched VM get a
			// copy of its data memory segment to more quickly seed fresh
			// vm's.
			if patchedData == nil {
				patchedDataAddr, patchedData = vm.cloneDataSegment()
				seedMemorySize = vm.memory.Length()
			}
			p.Release(vm, metrics.New())
		}

		// Activate the policy and data, now that a single VM has been patched without errors.
		if activations == 0 {
			p.activate(p.policy, patchedData, patchedDataAddr, seedMemorySize)
		}

		activations++
	}

	return nil
}

func (p *pool) RemoveDataPath(path []string) error {

	return nil
}

// setPolicyData reinitializes the VMs one at a time.
func (p *pool) setPolicyData(policy []byte, data []byte) error {
	var parsedData []byte
	var parsedDataAddr int32
	seedMemorySize := wasmPageSize * p.memoryMinPages
	for i, activations := 0, 0; true; i++ {
		vm := p.wait(i)
		if vm == nil {
			// All have been converted.
			return nil
		}

		err := vm.SetPolicyData(vmOpts{
			policy:         policy,
			data:           data,
			parsedData:     parsedData,
			parsedDataAddr: parsedDataAddr,
			memoryMin:      pages(seedMemorySize),
			memoryMax:      p.memoryMaxPages,
		})

		if err != nil {
			// No guarantee about the VM state after an error; hence, remove.
			p.remove(i)
			p.Release(vm, metrics.New())

			// After the first successful activation, proceed through all the VMs, ignoring the remaining errors.
			if activations == 0 {
				return err
			}
		} else {
			if parsedData == nil {
				parsedDataAddr, parsedData = vm.cloneDataSegment()
				seedMemorySize = vm.memory.Length()
			}

			p.Release(vm, metrics.New())
		}

		// Activate the policy and data, now that a single VM has been reset without errors.

		if activations == 0 {
			p.activate(policy, parsedData, parsedDataAddr, seedMemorySize)
		}

		activations++
	}

	return nil
}

// Close waits for all the evaluations to finish and then releases the VMs.
func (p *pool) Close() {
	for range p.vms {
		<-p.available
	}

	p.mutex.Lock()
	defer p.mutex.Unlock()

	for _, vm := range p.vms {
		if vm != nil {
			vm.Close()
		}
	}

	p.closed = true
	p.vms = nil
}

// wait steals the i'th VM instance. The VM has to be released afterwards.
func (p *pool) wait(i int) *vm {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	if i == len(p.vms) {
		return nil
	}

	vm := p.vms[i]
	isActive := p.acquired[i]
	p.acquired[i] = true

	if isActive {
		p.blockedReinit = make(chan struct{}, 1)
		p.pendingReinit = vm
	}

	p.mutex.Unlock()

	if isActive {
		<-p.blockedReinit
	} else {
		<-p.available
	}

	p.mutex.Lock()
	p.pendingReinit = nil
	return vm
}

// remove removes the i'th vm.
func (p *pool) remove(i int) {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	n := len(p.vms)
	if n > 1 {
		p.vms[i] = p.vms[n-1]
	}

	p.vms = p.vms[0 : n-1]
	p.acquired = p.acquired[0 : n-1]
}

func (p *pool) activate(policy []byte, data []byte, dataAddr int32, minMemorySize uint32) {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	p.policy, p.parsedData, p.parsedDataAddr, p.memoryMinPages = policy, data, dataAddr, pages(minMemorySize)
}
<commit_msg>internal/wasm/sdk: Revert full pool initialization<commit_after>// Copyright 2020 The OPA Authors.  All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.

package opa

import (
	"bytes"
	"context"
	"fmt"
	"sync"

	"github.com/open-policy-agent/opa/metrics"
)

// pool maintains a pool of WebAssemly VM instances.
type pool struct {
	available      chan struct{}
	mutex          sync.Mutex
	initialized    bool
	closed         bool
	policy         []byte
	parsedData     []byte // Parsed parsedData memory segment, used to seed new VM's
	parsedDataAddr int32  // Address for parsedData value root, used to seed new VM's
	memoryMinPages uint32
	memoryMaxPages uint32
	vms            []*vm // All current VM instances, acquired or not.
	acquired       []bool
	pendingReinit  *vm
	blockedReinit  chan struct{}
}

// newPool constructs a new pool with the pool and VM configuration provided.
func newPool(poolSize, memoryMinPages, memoryMaxPages uint32) *pool {
	available := make(chan struct{}, poolSize)
	for i := uint32(0); i < poolSize; i++ {
		available <- struct{}{}
	}

	return &pool{
		memoryMinPages: memoryMinPages,
		memoryMaxPages: memoryMaxPages,
		available:      available,
		vms:            make([]*vm, 0),
		acquired:       make([]bool, 0),
	}
}

// Acquire obtains a VM from the pool, waiting if all VMms are in use
// and building one as necessary. Returns either ErrNotReady or
// ErrInternal if an error.
func (p *pool) Acquire(ctx context.Context, metrics metrics.Metrics) (*vm, error) {
	metrics.Timer("wasm_pool_acquire").Start()
	defer metrics.Timer("wasm_pool_acquire").Stop()

	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	case <-p.available:
	}

	p.mutex.Lock()
	defer p.mutex.Unlock()

	if !p.initialized || p.closed {
		return nil, ErrNotReady
	}

	for i, vm := range p.vms {
		if !p.acquired[i] {
			p.acquired[i] = true
			return vm, nil
		}
	}

	policy, parsedData, parsedDataAddr := p.policy, p.parsedData, p.parsedDataAddr

	p.mutex.Unlock()
	vm, err := newVM(vmOpts{
		policy:         policy,
		data:           nil,
		parsedData:     parsedData,
		parsedDataAddr: parsedDataAddr,
		memoryMin:      p.memoryMinPages,
		memoryMax:      p.memoryMaxPages,
	})
	p.mutex.Lock()

	if err != nil {
		p.available <- struct{}{}
		return nil, fmt.Errorf("%v: %w", err, ErrInternal)
	}

	p.acquired = append(p.acquired, true)
	p.vms = append(p.vms, vm)
	return vm, nil
}

// Release releases the VM back to the pool.
func (p *pool) Release(vm *vm, metrics metrics.Metrics) {
	metrics.Timer("wasm_pool_release").Start()
	defer metrics.Timer("wasm_pool_release").Stop()

	p.mutex.Lock()

	// If the policy data setting is waiting for this one, don't release it back to the general consumption.
	// Note the reinit is responsible for pushing to available channel once done with the VM.
	if vm == p.pendingReinit {
		p.mutex.Unlock()
		p.blockedReinit <- struct{}{}
		return
	}

	for i := range p.vms {
		if p.vms[i] == vm {
			p.acquired[i] = false
			p.mutex.Unlock()
			p.available <- struct{}{}
			return
		}
	}

	// VM instance not found anymore, hence pool reconfigured and can release the VM.

	p.mutex.Unlock()
	p.available <- struct{}{}

	vm.Close()
}

// SetPolicyData re-initializes the vms within the pool with the new policy
// and data. The re-initialization takes place atomically: all new vms
// are constructed in advance before touching the pool.  Returns
// either ErrNotReady, ErrInvalidPolicy or ErrInternal if an error
// occurs.
func (p *pool) SetPolicyData(policy []byte, data []byte) error {
	p.mutex.Lock()

	if !p.initialized {
		vm, err := newVM(vmOpts{
			policy:         policy,
			data:           data,
			parsedData:     nil,
			parsedDataAddr: 0,
			memoryMin:      p.memoryMinPages,
			memoryMax:      p.memoryMaxPages,
		})

		if err == nil {
			parsedDataAddr, parsedData := vm.cloneDataSegment()
			p.memoryMinPages = pages(vm.memory.Length())
			p.vms = append(p.vms, vm)
			p.acquired = append(p.acquired, false)
			p.initialized = true
			p.policy, p.parsedData, p.parsedDataAddr = policy, parsedData, parsedDataAddr
		} else {
			err = fmt.Errorf("%v: %w", err, ErrInvalidPolicyOrData)
		}

		p.mutex.Unlock()
		return err
	}

	if p.closed {
		p.mutex.Unlock()
		return ErrNotReady
	}

	currentPolicy, currentData := p.policy, p.parsedData
	p.mutex.Unlock()

	if bytes.Equal(policy, currentPolicy) && bytes.Equal(data, currentData) {
		return nil

	}

	err := p.setPolicyData(policy, data)
	if err != nil {
		return fmt.Errorf("%v: %w", err, ErrInternal)
	}

	return nil
}

func (p *pool) SetDataPath(path []string, value interface{}) error {
	var patchedData []byte
	var patchedDataAddr int32
	var seedMemorySize uint32
	for i, activations := 0, 0; true; i++ {
		vm := p.wait(i)
		if vm == nil {
			// All have been converted.
			return nil
		}

		if err := vm.SetDataPath(path, value); err != nil {
			p.remove(i)
			p.Release(vm, metrics.New())
			if activations == 0 {
				return err
			}
		} else {
			// Before releasing our first succesfully patched VM get a
			// copy of its data memory segment to more quickly seed fresh
			// vm's.
			if patchedData == nil {
				patchedDataAddr, patchedData = vm.cloneDataSegment()
				seedMemorySize = vm.memory.Length()
			}
			p.Release(vm, metrics.New())
		}

		// Activate the policy and data, now that a single VM has been patched without errors.
		if activations == 0 {
			p.activate(p.policy, patchedData, patchedDataAddr, seedMemorySize)
		}

		activations++
	}

	return nil
}

func (p *pool) RemoveDataPath(path []string) error {

	return nil
}

// setPolicyData reinitializes the VMs one at a time.
func (p *pool) setPolicyData(policy []byte, data []byte) error {
	var parsedData []byte
	var parsedDataAddr int32
	seedMemorySize := wasmPageSize * p.memoryMinPages
	for i, activations := 0, 0; true; i++ {
		vm := p.wait(i)
		if vm == nil {
			// All have been converted.
			return nil
		}

		err := vm.SetPolicyData(vmOpts{
			policy:         policy,
			data:           data,
			parsedData:     parsedData,
			parsedDataAddr: parsedDataAddr,
			memoryMin:      pages(seedMemorySize),
			memoryMax:      p.memoryMaxPages,
		})

		if err != nil {
			// No guarantee about the VM state after an error; hence, remove.
			p.remove(i)
			p.Release(vm, metrics.New())

			// After the first successful activation, proceed through all the VMs, ignoring the remaining errors.
			if activations == 0 {
				return err
			}
		} else {
			if parsedData == nil {
				parsedDataAddr, parsedData = vm.cloneDataSegment()
				seedMemorySize = vm.memory.Length()
			}

			p.Release(vm, metrics.New())
		}

		// Activate the policy and data, now that a single VM has been reset without errors.

		if activations == 0 {
			p.activate(policy, parsedData, parsedDataAddr, seedMemorySize)
		}

		activations++
	}

	return nil
}

// Close waits for all the evaluations to finish and then releases the VMs.
func (p *pool) Close() {
	for range p.vms {
		<-p.available
	}

	p.mutex.Lock()
	defer p.mutex.Unlock()

	for _, vm := range p.vms {
		if vm != nil {
			vm.Close()
		}
	}

	p.closed = true
	p.vms = nil
}

// wait steals the i'th VM instance. The VM has to be released afterwards.
func (p *pool) wait(i int) *vm {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	if i == len(p.vms) {
		return nil
	}

	vm := p.vms[i]
	isActive := p.acquired[i]
	p.acquired[i] = true

	if isActive {
		p.blockedReinit = make(chan struct{}, 1)
		p.pendingReinit = vm
	}

	p.mutex.Unlock()

	if isActive {
		<-p.blockedReinit
	} else {
		<-p.available
	}

	p.mutex.Lock()
	p.pendingReinit = nil
	return vm
}

// remove removes the i'th vm.
func (p *pool) remove(i int) {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	n := len(p.vms)
	if n > 1 {
		p.vms[i] = p.vms[n-1]
	}

	p.vms = p.vms[0 : n-1]
	p.acquired = p.acquired[0 : n-1]
}

func (p *pool) activate(policy []byte, data []byte, dataAddr int32, minMemorySize uint32) {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	p.policy, p.parsedData, p.parsedDataAddr, p.memoryMinPages = policy, data, dataAddr, pages(minMemorySize)
}
<|endoftext|>