<commit_before>package workflows

import (
	"context"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path"
	"time"

	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	"github.com/hpcloud/tail"
	"github.com/sirupsen/logrus"
	"github.com/supergiant/supergiant/pkg/message"
	"github.com/supergiant/supergiant/pkg/model"
	"github.com/supergiant/supergiant/pkg/runner"
	"github.com/supergiant/supergiant/pkg/runner/ssh"
	"github.com/supergiant/supergiant/pkg/sgerrors"
	"github.com/supergiant/supergiant/pkg/storage"
	"github.com/supergiant/supergiant/pkg/util"
	"github.com/supergiant/supergiant/pkg/workflows/steps"
)

type cloudAccountGetter interface {
	Get(context.Context, string) (*model.CloudAccount, error)
}

type TaskHandler struct {
	runnerFactory  func(config ssh.Config) (runner.Runner, error)
	cloudAccGetter cloudAccountGetter
	repository     storage.Interface
	getWriter      func(string) (io.WriteCloser, error)
}

type RunTaskRequest struct {
	WorkflowName string       `json:"workflowName"`
	Cfg          steps.Config `json:"config"`
}

type BuildTaskRequest struct {
	StepNames []string     `json:"stepNames"`
	Cfg       steps.Config `json:"config"`
	SshConfig ssh.Config   `json:"sshConfig"`
}

type TaskResponse struct {
	ID string `json:"id"`
}

func NewTaskHandler(repository storage.Interface, runnerFactory func(config ssh.Config) (runner.Runner, error), getter cloudAccountGetter) *TaskHandler {
	return &TaskHandler{
		runnerFactory:  runnerFactory,
		repository:     repository,
		cloudAccGetter: getter,
		getWriter: func(name string) (io.WriteCloser, error) {
			// TODO(stgleb): Add log directory to params of supergiant
			return os.OpenFile(path.Join("/tmp", name), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
		},
	}
}

func (h *TaskHandler) Register(m *mux.Router) {
	m.HandleFunc("/tasks", h.RunTask).Methods(http.MethodPost)
	m.HandleFunc("/tasks/{id}", h.GetTask).Methods(http.MethodGet)
	m.HandleFunc("/tasks/{id}/restart", h.RestartTask).Methods(http.MethodPost)
	m.HandleFunc("/tasks/{id}/logs", h.StreamLogs).Methods(http.MethodGet)
	m.HandleFunc("/tasks/{id}/logs/ws", h.GetLogs).Methods(http.MethodGet)
}

func (h *TaskHandler) GetTask(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, ok := vars["id"]

	if !ok {
		http.Error(w, "need id of task", http.StatusBadRequest)
		return
	}

	data, err := h.repository.Get(r.Context(), Prefix, id)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Write(data)
}

func (h *TaskHandler) RunTask(w http.ResponseWriter, r *http.Request) {
	req := &RunTaskRequest{}
	err := json.NewDecoder(r.Body).Decode(req)

	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	acc, err := h.cloudAccGetter.Get(r.Context(), req.Cfg.CloudAccountName)

	if err != nil {
		if sgerrors.IsNotFound(err) {
			http.NotFound(w, r)
			return
		}

		message.SendUnknownError(w, err)
		return
	}

	// Get cloud account fill appropriate config structure with cloud account credentials
	err = util.FillCloudAccountCredentials(r.Context(), acc, &req.Cfg)

	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	task, err := NewTask(req.WorkflowName, h.repository)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	task.Run(context.Background(), req.Cfg, os.Stdout)

	w.WriteHeader(http.StatusAccepted)
	json.NewEncoder(w).Encode(&TaskResponse{
		task.ID,
	})
}

func (h *TaskHandler) RestartTask(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, ok := vars["id"]

	if !ok {
		http.Error(w, "need id of task", http.StatusBadRequest)
		return
	}

	data, err := h.repository.Get(r.Context(), Prefix, id)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	task, err := DeserializeTask(data, h.repository)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	writer, err := h.getWriter(id)

	if err != nil {
		http.Error(w, fmt.Sprintf("get writer %v", err), http.StatusInternalServerError)
		logrus.Errorf("Get writer %v", err)
		return
	}

	task.Restart(context.Background(), id, writer)
	w.WriteHeader(http.StatusAccepted)
}

func (h *TaskHandler) BuildAndRunTask(w http.ResponseWriter, r *http.Request) {
	req := &BuildTaskRequest{}
	err := json.NewDecoder(r.Body).Decode(req)

	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// Create newTask sshRunner with config provided
	sshRunner, err := h.runnerFactory(req.SshConfig)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	req.Cfg.Runner = sshRunner
	s := make([]steps.Step, 0, len(req.StepNames))
	// Get steps for task
	for _, stepName := range req.StepNames {
		s = append(s, steps.GetStep(stepName))
	}

	// TODO(stgleb): pass here workflow type DOMaster or DONode
	task := newTask("", s, h.repository)
	// We ignore cancel function since we cannot get it back
	ctx, _ := context.WithTimeout(context.Background(), req.Cfg.Timeout*time.Second)
	task.Run(ctx, req.Cfg, os.Stdout)

	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(&TaskResponse{
		task.ID,
	})
}

// NOTE(stgleb): This is made for testing purposes and example, remove when UI is done.
func (h *TaskHandler) GetLogs(w http.ResponseWriter, r *http.Request) {
	page := `<!DOCTYPE html>

        <title>WebSocket Example</title>

`
	tpl := template.Must(template.New("").Parse(page))
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	vars := mux.Vars(r)
	id, ok := vars["id"]

	if !ok {
		http.Error(w, "need id of task", http.StatusBadRequest)
		return
	}

	var v = struct {
		Host   string
		TaskID string
	}{
		r.Host,
		id,
	}
	tpl.Execute(w, &v)
}

func (h *TaskHandler) StreamLogs(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, ok := vars["id"]

	if !ok {
		http.Error(w, "need id of task", http.StatusBadRequest)
		return
	}

	var upgrader = websocket.Upgrader{
		HandshakeTimeout: time.Second * 10,
		WriteBufferSize:  1024,
		ReadBufferSize:   0,
	}

	t, err := tail.TailFile(path.Join("/tmp", util.MakeFileName(id)),
		tail.Config{
			Follow:    true,
			MustExist: true,
			Location: &tail.SeekInfo{
				Offset: 0,
				Whence: io.SeekStart,
			},
			Logger:      tail.DiscardingLogger,
			MaxLineSize: 160,
		})

	if os.IsNotExist(err) {
		http.NotFound(w, r)
		logrus.Errorf("Not found %s", util.MakeFileName(id))
		return
	}

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		logrus.Errorf("Open file %s for tail %v", util.MakeFileName(id), err)
		return
	}

	c, err := upgrader.Upgrade(w, r, nil)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		logrus.Errorf("Upgrade connection %v", err)
		return
	}

	go func() {
		pingTicker := time.NewTicker(time.Second * 60)

		for {
			select {
			case line := <-t.Lines:
				c.SetWriteDeadline(time.Now().Add(time.Second * 10))
				err = c.WriteMessage(websocket.TextMessage, []byte(line.Text))

				// Do not log this error, since client can simply disconnect
				if err != nil {
					return
				}
			case <-pingTicker.C:
				c.SetWriteDeadline(time.Now().Add(time.Second * 10))
				if err := c.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
					return
				}
			}
		}
	}()
}
<commit_msg>Turn off check origin for WS upgrader (#686)<commit_after>package workflows

import (
	"context"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path"
	"time"

	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	"github.com/hpcloud/tail"
	"github.com/sirupsen/logrus"
	"github.com/supergiant/supergiant/pkg/message"
	"github.com/supergiant/supergiant/pkg/model"
	"github.com/supergiant/supergiant/pkg/runner"
	"github.com/supergiant/supergiant/pkg/runner/ssh"
	"github.com/supergiant/supergiant/pkg/sgerrors"
	"github.com/supergiant/supergiant/pkg/storage"
	"github.com/supergiant/supergiant/pkg/util"
	"github.com/supergiant/supergiant/pkg/workflows/steps"
)

type cloudAccountGetter interface {
	Get(context.Context, string) (*model.CloudAccount, error)
}

type TaskHandler struct {
	runnerFactory  func(config ssh.Config) (runner.Runner, error)
	cloudAccGetter cloudAccountGetter
	repository     storage.Interface
	getWriter      func(string) (io.WriteCloser, error)
}

type RunTaskRequest struct {
	WorkflowName string       `json:"workflowName"`
	Cfg          steps.Config `json:"config"`
}

type BuildTaskRequest struct {
	StepNames []string     `json:"stepNames"`
	Cfg       steps.Config `json:"config"`
	SshConfig ssh.Config   `json:"sshConfig"`
}

type TaskResponse struct {
	ID string `json:"id"`
}

func NewTaskHandler(repository storage.Interface, runnerFactory func(config ssh.Config) (runner.Runner, error), getter cloudAccountGetter) *TaskHandler {
	return &TaskHandler{
		runnerFactory:  runnerFactory,
		repository:     repository,
		cloudAccGetter: getter,
		getWriter: func(name string) (io.WriteCloser, error) {
			// TODO(stgleb): Add log directory to params of supergiant
			return os.OpenFile(path.Join("/tmp", name), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
		},
	}
}

func (h *TaskHandler) Register(m *mux.Router) {
	m.HandleFunc("/tasks", h.RunTask).Methods(http.MethodPost)
	m.HandleFunc("/tasks/{id}", h.GetTask).Methods(http.MethodGet)
	m.HandleFunc("/tasks/{id}/restart", h.RestartTask).Methods(http.MethodPost)
	m.HandleFunc("/tasks/{id}/logs", h.StreamLogs).Methods(http.MethodGet)
	m.HandleFunc("/tasks/{id}/logs/ws", h.GetLogs).Methods(http.MethodGet)
}

func (h *TaskHandler) GetTask(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, ok := vars["id"]

	if !ok {
		http.Error(w, "need id of task", http.StatusBadRequest)
		return
	}

	data, err := h.repository.Get(r.Context(), Prefix, id)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Write(data)
}

func (h *TaskHandler) RunTask(w http.ResponseWriter, r *http.Request) {
	req := &RunTaskRequest{}
	err := json.NewDecoder(r.Body).Decode(req)

	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	acc, err := h.cloudAccGetter.Get(r.Context(), req.Cfg.CloudAccountName)

	if err != nil {
		if sgerrors.IsNotFound(err) {
			http.NotFound(w, r)
			return
		}

		message.SendUnknownError(w, err)
		return
	}

	// Get cloud account fill appropriate config structure with cloud account credentials
	err = util.FillCloudAccountCredentials(r.Context(), acc, &req.Cfg)

	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	task, err := NewTask(req.WorkflowName, h.repository)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	task.Run(context.Background(), req.Cfg, os.Stdout)

	w.WriteHeader(http.StatusAccepted)
	json.NewEncoder(w).Encode(&TaskResponse{
		task.ID,
	})
}

func (h *TaskHandler) RestartTask(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, ok := vars["id"]

	if !ok {
		http.Error(w, "need id of task", http.StatusBadRequest)
		return
	}

	data, err := h.repository.Get(r.Context(), Prefix, id)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	task, err := DeserializeTask(data, h.repository)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	writer, err := h.getWriter(id)

	if err != nil {
		http.Error(w, fmt.Sprintf("get writer %v", err), http.StatusInternalServerError)
		logrus.Errorf("Get writer %v", err)
		return
	}

	task.Restart(context.Background(), id, writer)
	w.WriteHeader(http.StatusAccepted)
}

func (h *TaskHandler) BuildAndRunTask(w http.ResponseWriter, r *http.Request) {
	req := &BuildTaskRequest{}
	err := json.NewDecoder(r.Body).Decode(req)

	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// Create newTask sshRunner with config provided
	sshRunner, err := h.runnerFactory(req.SshConfig)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	req.Cfg.Runner = sshRunner
	s := make([]steps.Step, 0, len(req.StepNames))
	// Get steps for task
	for _, stepName := range req.StepNames {
		s = append(s, steps.GetStep(stepName))
	}

	// TODO(stgleb): pass here workflow type DOMaster or DONode
	task := newTask("", s, h.repository)
	// We ignore cancel function since we cannot get it back
	ctx, _ := context.WithTimeout(context.Background(), req.Cfg.Timeout*time.Second)
	task.Run(ctx, req.Cfg, os.Stdout)

	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(&TaskResponse{
		task.ID,
	})
}

// NOTE(stgleb): This is made for testing purposes and example, remove when UI is done.
func (h *TaskHandler) GetLogs(w http.ResponseWriter, r *http.Request) {
	page := `<!DOCTYPE html>

        <title>WebSocket Example</title>

`
	tpl := template.Must(template.New("").Parse(page))
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	vars := mux.Vars(r)
	id, ok := vars["id"]

	if !ok {
		http.Error(w, "need id of task", http.StatusBadRequest)
		return
	}

	var v = struct {
		Host   string
		TaskID string
	}{
		r.Host,
		id,
	}
	tpl.Execute(w, &v)
}

func (h *TaskHandler) StreamLogs(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, ok := vars["id"]

	if !ok {
		http.Error(w, "need id of task", http.StatusBadRequest)
		return
	}

	var upgrader = websocket.Upgrader{
		HandshakeTimeout: time.Second * 10,
		WriteBufferSize:  1024,
		ReadBufferSize:   0,
		// TODO(stgleb): Do something more safe in future
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}

	t, err := tail.TailFile(path.Join("/tmp", util.MakeFileName(id)),
		tail.Config{
			Follow:    true,
			MustExist: true,
			Location: &tail.SeekInfo{
				Offset: 0,
				Whence: io.SeekStart,
			},
			Logger:      tail.DiscardingLogger,
			MaxLineSize: 160,
		})

	if os.IsNotExist(err) {
		http.NotFound(w, r)
		logrus.Errorf("Not found %s", util.MakeFileName(id))
		return
	}

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		logrus.Errorf("Open file %s for tail %v", util.MakeFileName(id), err)
		return
	}

	c, err := upgrader.Upgrade(w, r, nil)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		logrus.Errorf("Upgrade connection %v", err)
		return
	}

	go func() {
		pingTicker := time.NewTicker(time.Second * 60)

		for {
			select {
			case line := <-t.Lines:
				c.SetWriteDeadline(time.Now().Add(time.Second * 10))
				err = c.WriteMessage(websocket.TextMessage, []byte(line.Text))

				// Do not log this error, since client can simply disconnect
				if err != nil {
					return
				}
			case <-pingTicker.C:
				c.SetWriteDeadline(time.Now().Add(time.Second * 10))
				if err := c.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
					return
				}
			}
		}
	}()
}
<|endoftext|>


<commit_before>package discovery

import (
	"io/ioutil"
	"log"
	"path/filepath"
	"runtime"
	"strings"
)

const machineName = runtime.GOOS + "_" + runtime.GOARCH

// FindPlugins looks in the given directories for files whose filenames
// suggest that they are plugins of the given kind (e.g. "provider") and
// returns a PluginMetaSet representing the discovered potential-plugins.
//
// Currently this supports two different naming schemes. The current
// standard naming scheme is a subdirectory called $GOOS-$GOARCH containing
// files named terraform-$KIND-$NAME-V$VERSION. The legacy naming scheme is
// files directly in the given directory whose names are like
// terraform-$KIND-$NAME.
//
// Only one plugin will be returned for each unique plugin (name, version)
// pair, with preference given to files found in earlier directories.
//
// This is a convenience wrapper around FindPluginPaths and ResolvePluginsPaths.
func FindPlugins(kind string, dirs []string) PluginMetaSet {
	return ResolvePluginPaths(FindPluginPaths(kind, dirs))
}

// FindPluginPaths looks in the given directories for files whose filenames
// suggest that they are plugins of the given kind (e.g. "provider").
//
// The return value is a list of absolute paths that appear to refer to
// plugins in the given directories, based only on what can be inferred
// from the naming scheme. The paths returned are ordered such that files
// in later dirs appear after files in earlier dirs in the given directory
// list. Within the same directory plugins are returned in a consistent but
// undefined order.
func FindPluginPaths(kind string, dirs []string) []string {
	// This is just a thin wrapper around findPluginPaths so that we can
	// use the latter in tests with a fake machineName so we can use our
	// test fixtures.
	return findPluginPaths(kind, machineName, dirs)
}

func findPluginPaths(kind string, machineName string, dirs []string) []string {
	prefix := "terraform-" + kind + "-"

	ret := make([]string, 0, len(dirs))

	for _, baseDir := range dirs {
		baseItems, err := ioutil.ReadDir(baseDir)
		if err != nil {
			// Ignore missing dirs, non-dirs, etc
			continue
		}

		log.Printf("[DEBUG] checking for plugins in %q", baseDir)

		for _, item := range baseItems {
			fullName := item.Name()

			if fullName == machineName && item.Mode().IsDir() {
				// Current-style $GOOS-$GOARCH directory prefix
				machineDir := filepath.Join(baseDir, machineName)
				machineItems, err := ioutil.ReadDir(machineDir)
				if err != nil {
					continue
				}

				log.Printf("[DEBUG] checking for plugins in %q", machineDir)

				for _, item := range machineItems {
					fullName := item.Name()

					if !strings.HasPrefix(fullName, prefix) {
						continue
					}

					// New-style paths must have a version segment in filename
					if !strings.Contains(strings.ToLower(fullName), "_v") {
						continue
					}

					absPath, err := filepath.Abs(filepath.Join(machineDir, fullName))
					if err != nil {
						continue
					}

					log.Printf("[DEBUG] found plugin %q", fullName)

					ret = append(ret, filepath.Clean(absPath))
				}

				continue
			}

			if strings.HasPrefix(fullName, prefix) {
				// Legacy style with files directly in the base directory
				absPath, err := filepath.Abs(filepath.Join(baseDir, fullName))
				if err != nil {
					continue
				}

				log.Printf("[DEBUG] found legacy plugin %q", fullName)

				ret = append(ret, filepath.Clean(absPath))
			}
		}
	}

	return ret
}

// ResolvePluginPaths takes a list of paths to plugin executables (as returned
// by e.g. FindPluginPaths) and produces a PluginMetaSet describing the
// referenced plugins.
//
// If the same combination of plugin name and version appears multiple times,
// the earlier reference will be preferred. Several different versions of
// the same plugin name may be returned, in which case the methods of
// PluginMetaSet can be used to filter down.
func ResolvePluginPaths(paths []string) PluginMetaSet {
	s := make(PluginMetaSet)

	type nameVersion struct {
		Name    string
		Version string
	}
	found := make(map[nameVersion]struct{})

	for _, path := range paths {
		baseName := strings.ToLower(filepath.Base(path))
		if !strings.HasPrefix(baseName, "terraform-") {
			// Should never happen with reasonable input
			continue
		}

		baseName = baseName[10:]
		firstDash := strings.Index(baseName, "-")
		if firstDash == -1 {
			// Should never happen with reasonable input
			continue
		}

		baseName = baseName[firstDash+1:]
		if baseName == "" {
			// Should never happen with reasonable input
			continue
		}

		parts := strings.SplitN(baseName, "_v", 2)
		name := parts[0]
		version := "0.0.0"
		if len(parts) == 2 {
			version = parts[1]
		}

		// Auto-installed plugins contain an extra name portion representing
		// the expected plugin version, which we must trim off.
		if underX := strings.Index(version, "_x"); underX != -1 {
			version = version[:underX]
		}

		if _, ok := found[nameVersion{name, version}]; ok {
			// Skip duplicate versions of the same plugin
			// (We do this during this step because after this we will be
			// dealing with sets and thus lose our ordering with which to
			// decide preference.)
			continue
		}

		s.Add(PluginMeta{
			Name:    name,
			Version: VersionStr(version),
			Path:    path,
		})
		found[nameVersion{name, version}] = struct{}{}
	}

	return s
}
<commit_msg>search the vendor directory for plugins<commit_after>package discovery

import (
	"io/ioutil"
	"log"
	"path/filepath"
	"runtime"
	"strings"
)

const machineName = runtime.GOOS + "_" + runtime.GOARCH

// FindPlugins looks in the given directories for files whose filenames
// suggest that they are plugins of the given kind (e.g. "provider") and
// returns a PluginMetaSet representing the discovered potential-plugins.
//
// Currently this supports two different naming schemes. The current
// standard naming scheme is a subdirectory called $GOOS-$GOARCH containing
// files named terraform-$KIND-$NAME-V$VERSION. The legacy naming scheme is
// files directly in the given directory whose names are like
// terraform-$KIND-$NAME.
//
// Only one plugin will be returned for each unique plugin (name, version)
// pair, with preference given to files found in earlier directories.
//
// This is a convenience wrapper around FindPluginPaths and ResolvePluginsPaths.
func FindPlugins(kind string, dirs []string) PluginMetaSet {
	return ResolvePluginPaths(FindPluginPaths(kind, dirs))
}

// FindPluginPaths looks in the given directories for files whose filenames
// suggest that they are plugins of the given kind (e.g. "provider").
//
// The return value is a list of absolute paths that appear to refer to
// plugins in the given directories, based only on what can be inferred
// from the naming scheme. The paths returned are ordered such that files
// in later dirs appear after files in earlier dirs in the given directory
// list. Within the same directory plugins are returned in a consistent but
// undefined order.
func FindPluginPaths(kind string, dirs []string) []string {
	// This is just a thin wrapper around findPluginPaths so that we can
	// use the latter in tests with a fake machineName so we can use our
	// test fixtures.
	return findPluginPaths(kind, machineName, dirs)
}

func findPluginPaths(kind string, machineName string, dirs []string) []string {
	prefix := "terraform-" + kind + "-"

	ret := make([]string, 0, len(dirs))

	for _, baseDir := range dirs {
		baseItems, err := ioutil.ReadDir(baseDir)
		if err != nil {
			// Ignore missing dirs, non-dirs, etc
			continue
		}

		log.Printf("[DEBUG] checking for plugins in %q", baseDir)

		for _, item := range baseItems {
			fullName := item.Name()

			if fullName == machineName && item.Mode().IsDir() {
				// Current-style $GOOS-$GOARCH directory prefix
				machineDir := filepath.Join(baseDir, machineName)
				machineItems, err := ioutil.ReadDir(machineDir)
				if err != nil {
					continue
				}

				log.Printf("[DEBUG] checking for plugins in %q", machineDir)

				for _, item := range machineItems {
					fullName := item.Name()

					if !strings.HasPrefix(fullName, prefix) {
						continue
					}

					// New-style paths must have a version segment in filename
					if !strings.Contains(strings.ToLower(fullName), "_v") {
						continue
					}

					absPath, err := filepath.Abs(filepath.Join(machineDir, fullName))
					if err != nil {
						continue
					}

					log.Printf("[DEBUG] found plugin %q", fullName)

					ret = append(ret, filepath.Clean(absPath))
				}

				continue
			}

			// FIXME: we pass in GOOS_GOARCH paths directly, so these may not be "legacy"
			if strings.HasPrefix(fullName, prefix) {
				// Legacy style with files directly in the base directory
				absPath, err := filepath.Abs(filepath.Join(baseDir, fullName))
				if err != nil {
					continue
				}

				log.Printf("[DEBUG] found legacy plugin %q", fullName)

				ret = append(ret, filepath.Clean(absPath))
			}
		}
	}

	return ret
}

// ResolvePluginPaths takes a list of paths to plugin executables (as returned
// by e.g. FindPluginPaths) and produces a PluginMetaSet describing the
// referenced plugins.
//
// If the same combination of plugin name and version appears multiple times,
// the earlier reference will be preferred. Several different versions of
// the same plugin name may be returned, in which case the methods of
// PluginMetaSet can be used to filter down.
func ResolvePluginPaths(paths []string) PluginMetaSet {
	s := make(PluginMetaSet)

	type nameVersion struct {
		Name    string
		Version string
	}
	found := make(map[nameVersion]struct{})

	for _, path := range paths {
		baseName := strings.ToLower(filepath.Base(path))
		if !strings.HasPrefix(baseName, "terraform-") {
			// Should never happen with reasonable input
			continue
		}

		baseName = baseName[10:]
		firstDash := strings.Index(baseName, "-")
		if firstDash == -1 {
			// Should never happen with reasonable input
			continue
		}

		baseName = baseName[firstDash+1:]
		if baseName == "" {
			// Should never happen with reasonable input
			continue
		}

		parts := strings.SplitN(baseName, "_v", 2)
		name := parts[0]
		version := "0.0.0"
		if len(parts) == 2 {
			version = parts[1]
		}

		// Auto-installed plugins contain an extra name portion representing
		// the expected plugin version, which we must trim off.
		if underX := strings.Index(version, "_x"); underX != -1 {
			version = version[:underX]
		}

		if _, ok := found[nameVersion{name, version}]; ok {
			// Skip duplicate versions of the same plugin
			// (We do this during this step because after this we will be
			// dealing with sets and thus lose our ordering with which to
			// decide preference.)
			continue
		}

		s.Add(PluginMeta{
			Name:    name,
			Version: VersionStr(version),
			Path:    path,
		})
		found[nameVersion{name, version}] = struct{}{}
	}

	return s
}
<|endoftext|>


<commit_before>package discovery

import (
	"io/ioutil"
	"log"
	"path/filepath"
	"strings"
)

// FindPlugins looks in the given directories for files whose filenames
// suggest that they are plugins of the given kind (e.g. "provider") and
// returns a PluginMetaSet representing the discovered potential-plugins.
//
// Currently this supports two different naming schemes. The current
// standard naming scheme is a subdirectory called $GOOS-$GOARCH containing
// files named terraform-$KIND-$NAME-V$VERSION. The legacy naming scheme is
// files directly in the given directory whose names are like
// terraform-$KIND-$NAME.
//
// Only one plugin will be returned for each unique plugin (name, version)
// pair, with preference given to files found in earlier directories.
//
// This is a convenience wrapper around FindPluginPaths and ResolvePluginsPaths.
func FindPlugins(kind string, dirs []string) PluginMetaSet {
	return ResolvePluginPaths(FindPluginPaths(kind, dirs))
}

// FindPluginPaths looks in the given directories for files whose filenames
// suggest that they are plugins of the given kind (e.g. "provider").
//
// The return value is a list of absolute paths that appear to refer to
// plugins in the given directories, based only on what can be inferred
// from the naming scheme. The paths returned are ordered such that files
// in later dirs appear after files in earlier dirs in the given directory
// list. Within the same directory plugins are returned in a consistent but
// undefined order.
func FindPluginPaths(kind string, dirs []string) []string {
	// This is just a thin wrapper around findPluginPaths so that we can
	// use the latter in tests with a fake machineName so we can use our
	// test fixtures.
	return findPluginPaths(kind, dirs)
}

func findPluginPaths(kind string, dirs []string) []string {
	prefix := "terraform-" + kind + "-"

	ret := make([]string, 0, len(dirs))

	for _, dir := range dirs {
		items, err := ioutil.ReadDir(dir)
		if err != nil {
			// Ignore missing dirs, non-dirs, etc
			continue
		}

		log.Printf("[DEBUG] checking for plugins in %q", dir)

		for _, item := range items {
			fullName := item.Name()

			if !strings.HasPrefix(fullName, prefix) {
				log.Printf("[DEBUG] skipping %q, not a plugin", fullName)
				continue
			}

			// New-style paths must have a version segment in filename
			if strings.Contains(strings.ToLower(fullName), "_v") {
				absPath, err := filepath.Abs(filepath.Join(dir, fullName))
				if err != nil {
					log.Printf("[ERROR] plugin filepath error: %s", err)
					continue
				}

				log.Printf("[DEBUG] found plugin %q", fullName)
				ret = append(ret, filepath.Clean(absPath))
				continue
			}

			// Legacy style with files directly in the base directory
			absPath, err := filepath.Abs(filepath.Join(dir, fullName))
			if err != nil {
				log.Printf("[ERROR] plugin filepath error: %s", err)
				continue
			}

			log.Printf("[WARNING] found legacy plugin %q", fullName)

			ret = append(ret, filepath.Clean(absPath))
		}
	}

	return ret
}

// ResolvePluginPaths takes a list of paths to plugin executables (as returned
// by e.g. FindPluginPaths) and produces a PluginMetaSet describing the
// referenced plugins.
//
// If the same combination of plugin name and version appears multiple times,
// the earlier reference will be preferred. Several different versions of
// the same plugin name may be returned, in which case the methods of
// PluginMetaSet can be used to filter down.
func ResolvePluginPaths(paths []string) PluginMetaSet {
	s := make(PluginMetaSet)

	type nameVersion struct {
		Name    string
		Version string
	}
	found := make(map[nameVersion]struct{})

	for _, path := range paths {
		baseName := strings.ToLower(filepath.Base(path))
		if !strings.HasPrefix(baseName, "terraform-") {
			// Should never happen with reasonable input
			continue
		}

		baseName = baseName[10:]
		firstDash := strings.Index(baseName, "-")
		if firstDash == -1 {
			// Should never happen with reasonable input
			continue
		}

		baseName = baseName[firstDash+1:]
		if baseName == "" {
			// Should never happen with reasonable input
			continue
		}

		parts := strings.SplitN(baseName, "_v", 2)
		name := parts[0]
		version := VersionZero
		if len(parts) == 2 {
			version = parts[1]
		}

		// Auto-installed plugins contain an extra name portion representing
		// the expected plugin version, which we must trim off.
		if underX := strings.Index(version, "_x"); underX != -1 {
			version = version[:underX]
		}

		if _, ok := found[nameVersion{name, version}]; ok {
			// Skip duplicate versions of the same plugin
			// (We do this during this step because after this we will be
			// dealing with sets and thus lose our ordering with which to
			// decide preference.)
			continue
		}

		s.Add(PluginMeta{
			Name:    name,
			Version: VersionStr(version),
			Path:    path,
		})
		found[nameVersion{name, version}] = struct{}{}
	}

	return s
}
<commit_msg>improve discovery logging<commit_after>package discovery

import (
	"io/ioutil"
	"log"
	"path/filepath"
	"strings"
)

// FindPlugins looks in the given directories for files whose filenames
// suggest that they are plugins of the given kind (e.g. "provider") and
// returns a PluginMetaSet representing the discovered potential-plugins.
//
// Currently this supports two different naming schemes. The current
// standard naming scheme is a subdirectory called $GOOS-$GOARCH containing
// files named terraform-$KIND-$NAME-V$VERSION. The legacy naming scheme is
// files directly in the given directory whose names are like
// terraform-$KIND-$NAME.
//
// Only one plugin will be returned for each unique plugin (name, version)
// pair, with preference given to files found in earlier directories.
//
// This is a convenience wrapper around FindPluginPaths and ResolvePluginsPaths.
func FindPlugins(kind string, dirs []string) PluginMetaSet {
	return ResolvePluginPaths(FindPluginPaths(kind, dirs))
}

// FindPluginPaths looks in the given directories for files whose filenames
// suggest that they are plugins of the given kind (e.g. "provider").
//
// The return value is a list of absolute paths that appear to refer to
// plugins in the given directories, based only on what can be inferred
// from the naming scheme. The paths returned are ordered such that files
// in later dirs appear after files in earlier dirs in the given directory
// list. Within the same directory plugins are returned in a consistent but
// undefined order.
func FindPluginPaths(kind string, dirs []string) []string {
	// This is just a thin wrapper around findPluginPaths so that we can
	// use the latter in tests with a fake machineName so we can use our
	// test fixtures.
	return findPluginPaths(kind, dirs)
}

func findPluginPaths(kind string, dirs []string) []string {
	prefix := "terraform-" + kind + "-"

	ret := make([]string, 0, len(dirs))

	for _, dir := range dirs {
		items, err := ioutil.ReadDir(dir)
		if err != nil {
			// Ignore missing dirs, non-dirs, etc
			continue
		}

		log.Printf("[DEBUG] checking for %s in %q", kind, dir)

		for _, item := range items {
			fullName := item.Name()

			if !strings.HasPrefix(fullName, prefix) {
				log.Printf("[DEBUG] skipping %q, not a %s", fullName, kind)
				continue
			}

			// New-style paths must have a version segment in filename
			if strings.Contains(strings.ToLower(fullName), "_v") {
				absPath, err := filepath.Abs(filepath.Join(dir, fullName))
				if err != nil {
					log.Printf("[ERROR] plugin filepath error: %s", err)
					continue
				}

				log.Printf("[DEBUG] found %s %q", kind, fullName)
				ret = append(ret, filepath.Clean(absPath))
				continue
			}

			// Legacy style with files directly in the base directory
			absPath, err := filepath.Abs(filepath.Join(dir, fullName))
			if err != nil {
				log.Printf("[ERROR] plugin filepath error: %s", err)
				continue
			}

			log.Printf("[WARNING] found legacy %s %q", kind, fullName)

			ret = append(ret, filepath.Clean(absPath))
		}
	}

	return ret
}

// ResolvePluginPaths takes a list of paths to plugin executables (as returned
// by e.g. FindPluginPaths) and produces a PluginMetaSet describing the
// referenced plugins.
//
// If the same combination of plugin name and version appears multiple times,
// the earlier reference will be preferred. Several different versions of
// the same plugin name may be returned, in which case the methods of
// PluginMetaSet can be used to filter down.
func ResolvePluginPaths(paths []string) PluginMetaSet {
	s := make(PluginMetaSet)

	type nameVersion struct {
		Name    string
		Version string
	}
	found := make(map[nameVersion]struct{})

	for _, path := range paths {
		baseName := strings.ToLower(filepath.Base(path))
		if !strings.HasPrefix(baseName, "terraform-") {
			// Should never happen with reasonable input
			continue
		}

		baseName = baseName[10:]
		firstDash := strings.Index(baseName, "-")
		if firstDash == -1 {
			// Should never happen with reasonable input
			continue
		}

		baseName = baseName[firstDash+1:]
		if baseName == "" {
			// Should never happen with reasonable input
			continue
		}

		parts := strings.SplitN(baseName, "_v", 2)
		name := parts[0]
		version := VersionZero
		if len(parts) == 2 {
			version = parts[1]
		}

		// Auto-installed plugins contain an extra name portion representing
		// the expected plugin version, which we must trim off.
		if underX := strings.Index(version, "_x"); underX != -1 {
			version = version[:underX]
		}

		if _, ok := found[nameVersion{name, version}]; ok {
			// Skip duplicate versions of the same plugin
			// (We do this during this step because after this we will be
			// dealing with sets and thus lose our ordering with which to
			// decide preference.)
			continue
		}

		s.Add(PluginMeta{
			Name:    name,
			Version: VersionStr(version),
			Path:    path,
		})
		found[nameVersion{name, version}] = struct{}{}
	}

	return s
}
<|endoftext|>


<commit_before>// +build acceptance

package v1

import (
	"strings"
	"testing"

	"github.com/rackspace/gophercloud/openstack/storage/v1/accounts"
)

func TestAccounts(t *testing.T) {
	client, err := newClient()
	if err != nil {
		t.Error(err)
		return
	}

	err = accounts.Update(client, accounts.UpdateOpts{
		Metadata: metadata,
	})
	if err != nil {
		t.Error(err)
		return
	}
	defer func() {
		tempMap := make(map[string]string)
		for k := range metadata {
			tempMap[k] = ""
		}
		err = accounts.Update(client, accounts.UpdateOpts{
			Metadata: tempMap,
		})
		if err != nil {
			t.Error(err)
			return
		}
	}()

	gr, err := accounts.Get(client, accounts.GetOpts{})
	if err != nil {
		t.Error(err)
		return
	}
	am := accounts.ExtractMetadata(gr)
	for k := range metadata {
		if am[k] != metadata[strings.Title(k)] {
			t.Errorf("Expected custom metadata with key: %s", k)
			return
		}
	}
}
<commit_msg>accounts acceptance test docs<commit_after>// +build acceptance

package v1

import (
	"strings"
	"testing"

	"github.com/rackspace/gophercloud/openstack/storage/v1/accounts"
)

func TestAccounts(t *testing.T) {
	// Create a provider client for making the HTTP requests.
	// See common.go in this directory for more information.
	client, err := newClient()
	if err != nil {
		t.Error(err)
		return
	}

	// Update an account's metadata.
	err = accounts.Update(client, accounts.UpdateOpts{
		Metadata: metadata,
	})
	if err != nil {
		t.Error(err)
		return
	}
	// Defer the deletion of the metadata set above.
	defer func() {
		tempMap := make(map[string]string)
		for k := range metadata {
			tempMap[k] = ""
		}
		err = accounts.Update(client, accounts.UpdateOpts{
			Metadata: tempMap,
		})
		if err != nil {
			t.Error(err)
			return
		}
	}()

	// Retrieve account metadata.
	gr, err := accounts.Get(client, accounts.GetOpts{})
	if err != nil {
		t.Error(err)
		return
	}
	// Extract the custom metadata from the 'Get' response.
	am := accounts.ExtractMetadata(gr)
	for k := range metadata {
		if am[k] != metadata[strings.Title(k)] {
			t.Errorf("Expected custom metadata with key: %s", k)
			return
		}
	}
}
<|endoftext|>


<commit_before>package vizzini_test

import (
	"github.com/cloudfoundry-incubator/bbs/models"
	"github.com/cloudfoundry-incubator/receptor"
	. "github.com/pivotal-cf-experimental/vizzini/matchers"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Privileged", func() {
	var task receptor.TaskCreateRequest
	var runUser string
	var containerPrivileged bool

	JustBeforeEach(func() {
		task = TaskWithGuid(guid)
		task.Privileged = containerPrivileged
		task.Action = models.WrapAction(&models.RunAction{
			Path: "bash",
			Args: []string{"-c", "id > /tmp/bar; echo h > /proc/sysrq-trigger ; echo have_real_root=$? >> /tmp/bar"},
			User: runUser,
		})

		Ω(client.CreateTask(task)).Should(Succeed())
		Eventually(TaskGetter(guid)).Should(HaveTaskState(receptor.TaskStateCompleted))
	})

	AfterEach(func() {
		Ω(client.DeleteTask(guid)).Should(Succeed())
	})

	Context("with a privileged container", func() {
		BeforeEach(func() {
			containerPrivileged = true
		})

		//{LOCAL} because: privileged may not be allowed in the remote environment
		Context("{LOCAL} when running a privileged action", func() {
			BeforeEach(func() {
				runUser = "root"
			})

			It("should run as root", func() {
				completedTask, err := client.GetTask(guid)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(completedTask.Result).Should(ContainSubstring("uid=0(root)"), "If this fails, then your executor may not be configured to allow privileged actions")
				Ω(completedTask.Result).Should(MatchRegexp(`groups=.*0\(root\)`))
				Ω(completedTask.Result).Should(ContainSubstring("have_real_root=0"))
			})
		})

		Context("when running a non-privileged action", func() {
			BeforeEach(func() {
				runUser = "vcap"
			})

			It("should run as non-root", func() {
				completedTask, err := client.GetTask(guid)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(completedTask.Result).Should(MatchRegexp(`uid=\d{4,5}\(vcap\)`))
				Ω(completedTask.Result).Should(MatchRegexp(`groups=.*0\(root\)`))
				Ω(completedTask.Result).Should(ContainSubstring("have_real_root=1"))
			})
		})
	})

	Context("with an unprivileged container", func() {
		BeforeEach(func() {
			containerPrivileged = false
		})

		//{LOCAL} because: privileged may not be allowed in the remote environment
		Context("{LOCAL} when running a privileged action", func() {
			BeforeEach(func() {
				runUser = "root"
			})

			It("should run as root", func() {
				completedTask, err := client.GetTask(guid)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(completedTask.Result).Should(ContainSubstring("uid=0(root)"), "If this fails, then your executor may not be configured to allow privileged actions")
				Ω(completedTask.Result).Should(ContainSubstring("have_real_root=1"))
			})
		})

		Context("when running a non-privileged action", func() {
			BeforeEach(func() {
				runUser = "vcap"
			})

			It("should run as non-root", func() {
				completedTask, err := client.GetTask(guid)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(completedTask.Result).Should(MatchRegexp(`uid=\d{4,5}\(vcap\)`))
				Ω(completedTask.Result).Should(ContainSubstring("have_real_root=1"))
			})
		})
	})
})
<commit_msg>Rep always allows privileged actions<commit_after>package vizzini_test

import (
	"github.com/cloudfoundry-incubator/bbs/models"
	"github.com/cloudfoundry-incubator/receptor"
	. "github.com/pivotal-cf-experimental/vizzini/matchers"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Privileged", func() {
	var task receptor.TaskCreateRequest
	var runUser string
	var containerPrivileged bool

	JustBeforeEach(func() {
		task = TaskWithGuid(guid)
		task.Privileged = containerPrivileged
		task.Action = models.WrapAction(&models.RunAction{
			Path: "bash",
			Args: []string{"-c", "id > /tmp/bar; echo h > /proc/sysrq-trigger ; echo have_real_root=$? >> /tmp/bar"},
			User: runUser,
		})

		Ω(client.CreateTask(task)).Should(Succeed())
		Eventually(TaskGetter(guid)).Should(HaveTaskState(receptor.TaskStateCompleted))
	})

	AfterEach(func() {
		Ω(client.DeleteTask(guid)).Should(Succeed())
	})

	Context("with a privileged container", func() {
		BeforeEach(func() {
			containerPrivileged = true
		})

		Context("when running a privileged action", func() {
			BeforeEach(func() {
				runUser = "root"
			})

			It("should run as root", func() {
				completedTask, err := client.GetTask(guid)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(completedTask.Result).Should(ContainSubstring("uid=0(root)"), "If this fails, then your executor may not be configured to allow privileged actions")
				Ω(completedTask.Result).Should(MatchRegexp(`groups=.*0\(root\)`))
				Ω(completedTask.Result).Should(ContainSubstring("have_real_root=0"))
			})
		})

		Context("when running a non-privileged action", func() {
			BeforeEach(func() {
				runUser = "vcap"
			})

			It("should run as non-root", func() {
				completedTask, err := client.GetTask(guid)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(completedTask.Result).Should(MatchRegexp(`uid=\d{4,5}\(vcap\)`))
				Ω(completedTask.Result).Should(MatchRegexp(`groups=.*0\(root\)`))
				Ω(completedTask.Result).Should(ContainSubstring("have_real_root=1"))
			})
		})
	})

	Context("with an unprivileged container", func() {
		BeforeEach(func() {
			containerPrivileged = false
		})

		Context("when running a privileged action", func() {
			BeforeEach(func() {
				runUser = "root"
			})

			It("should run as namespaced root", func() {
				completedTask, err := client.GetTask(guid)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(completedTask.Result).Should(ContainSubstring("uid=0(root)"), "If this fails, then your executor may not be configured to allow privileged actions")
				Ω(completedTask.Result).Should(ContainSubstring("have_real_root=1"))
			})
		})

		Context("when running a non-privileged action", func() {
			BeforeEach(func() {
				runUser = "vcap"
			})

			It("should run as non-root", func() {
				completedTask, err := client.GetTask(guid)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(completedTask.Result).Should(MatchRegexp(`uid=\d{4,5}\(vcap\)`))
				Ω(completedTask.Result).Should(ContainSubstring("have_real_root=1"))
			})
		})
	})
})
<|endoftext|>


<commit_before>// On-disk mutex protecting a resource
//
// A lock is represented on disk by a directory of a particular name,
// containing an information file.  Taking a lock is done by renaming a
// temporary directory into place.  We use temporary directories because for
// all filesystems we believe that exactly one attempt to claim the lock will
// succeed and the others will fail.
package fslock

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"time"

	"launchpad.net/juju-core/log"
)

const (
	nameRegexp      = "^[a-z]+[a-z0-9.-]*$"
	heldFilename    = "held"
	messageFilename = "message"
)

var (
	ErrLockNotHeld = errors.New("lock not held")
	ErrTimeout     = errors.New("lock timeout exceeded")

	validName = regexp.MustCompile(nameRegexp)

	lockWaitDelay = 1 * time.Second
)

type Lock struct {
	name   string
	parent string
	nonce  []byte
}

func generateNonce() ([]byte, error) {
	nonce := make([]byte, 20)
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return nil, err
	}
	return nonce, nil
}

// NewLock returns a new lock with the given name within the given lock
// directory, without acquiring it. The lock name must match the regular
// expression `^[a-z]+[a-z0-9.-]*`.
func NewLock(lockDir, name string) (*Lock, error) {
	if !validName.MatchString(name) {
		return nil, fmt.Errorf("Invalid lock name %q.  Names must match %q", name, nameRegexp)
	}
	nonce, err := generateNonce()
	if err != nil {
		return nil, err
	}
	lock := &Lock{
		name:   name,
		parent: lockDir,
		nonce:  nonce,
	}
	// Ensure the parent exists.
	if err := os.MkdirAll(lock.parent, 0755); err != nil {
		return nil, err
	}
	return lock, nil
}

func (lock *Lock) lockDir() string {
	return path.Join(lock.parent, lock.name)
}

func (lock *Lock) heldFile() string {
	return path.Join(lock.lockDir(), "held")
}

func (lock *Lock) messageFile() string {
	return path.Join(lock.lockDir(), "message")
}

// If message is set, it will write the message to the lock directory as the
// lock is taken.
func (lock *Lock) acquire(message string) (bool, error) {
	// If the lockDir exists, then the lock is held by someone else.
	_, err := os.Stat(lock.lockDir())
	if err == nil {
		return false, nil
	}
	if !os.IsNotExist(err) {
		return false, err
	}
	// Create a temporary directory (in the parent dir), and then move it to
	// the right name.  Using the same directory to make sure the directories
	// are on the same filesystem.  Use a directory name starting with "." as
	// it isn't a valid lock name.
	tempLockName := fmt.Sprintf(".%s", hex.EncodeToString(lock.nonce))
	tempDirName, err := ioutil.TempDir(lock.parent, tempLockName)
	if err != nil {
		return false, err // this shouldn't really fail...
	}
	// write nonce into the temp dir
	err = ioutil.WriteFile(path.Join(tempDirName, heldFilename), lock.nonce, 0755)
	if err != nil {
		return false, err
	}
	if message != "" {
		err = ioutil.WriteFile(path.Join(tempDirName, messageFilename), []byte(message), 0755)
		if err != nil {
			return false, err
		}
	}
	// Now move the temp directory to the lock directory.
	err = os.Rename(tempDirName, lock.lockDir())
	if err != nil {
		// Any error on rename means we failed.
		log.Infof("Lock %q beaten to the dir rename, %s, currently held: %s", lock.name, message, lock.Message())

		// Beaten to it, clean up temporary directory.
		os.RemoveAll(tempDirName)
		return false, nil
	}
	// We now have the lock.
	return true, nil
}

// Lock blocks until it is able to acquire the lock.  Since we are dealing
// with sharing and locking using the filesystem, it is good behaviour to
// provide a message that is saved with the lock.  This is output in debugging
// information, and can be queried by any other Lock dealing with the same
// lock name and lock directory.
func (lock *Lock) Lock(message string) error {
	var heldMessage = ""
	for {
		acquired, err := lock.acquire(message)
		if err != nil {
			return err
		}
		if acquired {
			return nil
		}
		currMessage := lock.Message()
		if currMessage != heldMessage {
			log.Infof("Attempt Lock failed %q, %s, currently held: %s", lock.name, message, currMessage)
			heldMessage = currMessage
		}
		time.Sleep(lockWaitDelay)
	}
	panic("unreachable")
}

// LockWithTimeout tries to acquire the lock. If it cannot acquire the lock
// within the given duration, it returns ErrTimeout.  See `Lock` for
// information about the message.
func (lock *Lock) LockWithTimeout(duration time.Duration, message string) error {
	deadline := time.Now().Add(duration)
	for {
		acquired, err := lock.acquire(message)
		if err != nil {
			return err
		}
		if acquired {
			return nil
		}
		if time.Now().After(deadline) {
			return ErrTimeout
		}
		time.Sleep(lockWaitDelay)
	}
	panic("unreachable")
}

// IsHeld returns whether the lock is currently held by the receiver.
func (lock *Lock) IsLockHeld() bool {
	heldNonce, err := ioutil.ReadFile(lock.heldFile())
	if err != nil {
		return false
	}
	return bytes.Equal(heldNonce, lock.nonce)
}

// Unlock releases a held lock.  If the lock is not held ErrLockNotHeld is
// returned.
func (lock *Lock) Unlock() error {
	if !lock.IsLockHeld() {
		return ErrLockNotHeld
	}
	// To ensure reasonable unlocking, we should rename to a temp name, and delete that.
	tempLockName := fmt.Sprintf(".%s.%s", lock.name, hex.EncodeToString(lock.nonce))
	tempDirName := path.Join(lock.parent, tempLockName)
	// Now move the lock directory to the temp directory to release the lock.
	if err := os.Rename(lock.lockDir(), tempDirName); err != nil {
		return err
	}
	// And now cleanup.
	return os.RemoveAll(tempDirName)
}

// IsLocked returns true if the lock is currently held by anyone.
func (lock *Lock) IsLocked() bool {
	_, err := os.Stat(lock.heldFile())
	return err == nil
}

// BreakLock forcably breaks the lock that is currently being held.
func (lock *Lock) BreakLock() error {
	return os.RemoveAll(lock.lockDir())
}

// SetMessage saves the message if and only if the lock is held.
func (lock *Lock) SetMessage(message string) error {
	if !lock.IsLockHeld() {
		return ErrLockNotHeld
	}
	// Since the message can be read by anyone, make this an atomic write by
	// writing to a temp file and renaming.
	tempFile, err := ioutil.TempFile(lock.lockDir(), ".message")
	if err != nil {
		return err
	}
	tempFilename := tempFile.Name()
	fmt.Fprint(tempFile, message)
	tempFile.Close()
	return os.Rename(tempFilename, lock.messageFile())
}

// Message returns the saved message, or the empty string if there is no
// saved message.
func (lock *Lock) Message() string {
	message, err := ioutil.ReadFile(lock.messageFile())
	if err != nil {
		return ""
	}
	return string(message)
}
<commit_msg>Use fmt %x instead of hex.EncodeToString.<commit_after>// On-disk mutex protecting a resource
//
// A lock is represented on disk by a directory of a particular name,
// containing an information file.  Taking a lock is done by renaming a
// temporary directory into place.  We use temporary directories because for
// all filesystems we believe that exactly one attempt to claim the lock will
// succeed and the others will fail.
package fslock

import (
	"bytes"
	"crypto/rand"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"time"

	"launchpad.net/juju-core/log"
)

const (
	nameRegexp      = "^[a-z]+[a-z0-9.-]*$"
	heldFilename    = "held"
	messageFilename = "message"
)

var (
	ErrLockNotHeld = errors.New("lock not held")
	ErrTimeout     = errors.New("lock timeout exceeded")

	validName = regexp.MustCompile(nameRegexp)

	lockWaitDelay = 1 * time.Second
)

type Lock struct {
	name   string
	parent string
	nonce  []byte
}

func generateNonce() ([]byte, error) {
	nonce := make([]byte, 20)
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return nil, err
	}
	return nonce, nil
}

// NewLock returns a new lock with the given name within the given lock
// directory, without acquiring it. The lock name must match the regular
// expression `^[a-z]+[a-z0-9.-]*`.
func NewLock(lockDir, name string) (*Lock, error) {
	if !validName.MatchString(name) {
		return nil, fmt.Errorf("Invalid lock name %q.  Names must match %q", name, nameRegexp)
	}
	nonce, err := generateNonce()
	if err != nil {
		return nil, err
	}
	lock := &Lock{
		name:   name,
		parent: lockDir,
		nonce:  nonce,
	}
	// Ensure the parent exists.
	if err := os.MkdirAll(lock.parent, 0755); err != nil {
		return nil, err
	}
	return lock, nil
}

func (lock *Lock) lockDir() string {
	return path.Join(lock.parent, lock.name)
}

func (lock *Lock) heldFile() string {
	return path.Join(lock.lockDir(), "held")
}

func (lock *Lock) messageFile() string {
	return path.Join(lock.lockDir(), "message")
}

// If message is set, it will write the message to the lock directory as the
// lock is taken.
func (lock *Lock) acquire(message string) (bool, error) {
	// If the lockDir exists, then the lock is held by someone else.
	_, err := os.Stat(lock.lockDir())
	if err == nil {
		return false, nil
	}
	if !os.IsNotExist(err) {
		return false, err
	}
	// Create a temporary directory (in the parent dir), and then move it to
	// the right name.  Using the same directory to make sure the directories
	// are on the same filesystem.  Use a directory name starting with "." as
	// it isn't a valid lock name.
	tempLockName := fmt.Sprintf(".%x", lock.nonce)
	tempDirName, err := ioutil.TempDir(lock.parent, tempLockName)
	if err != nil {
		return false, err // this shouldn't really fail...
	}
	// write nonce into the temp dir
	err = ioutil.WriteFile(path.Join(tempDirName, heldFilename), lock.nonce, 0755)
	if err != nil {
		return false, err
	}
	if message != "" {
		err = ioutil.WriteFile(path.Join(tempDirName, messageFilename), []byte(message), 0755)
		if err != nil {
			return false, err
		}
	}
	// Now move the temp directory to the lock directory.
	err = os.Rename(tempDirName, lock.lockDir())
	if err != nil {
		// Any error on rename means we failed.
		log.Infof("Lock %q beaten to the dir rename, %s, currently held: %s", lock.name, message, lock.Message())

		// Beaten to it, clean up temporary directory.
		os.RemoveAll(tempDirName)
		return false, nil
	}
	// We now have the lock.
	return true, nil
}

// Lock blocks until it is able to acquire the lock.  Since we are dealing
// with sharing and locking using the filesystem, it is good behaviour to
// provide a message that is saved with the lock.  This is output in debugging
// information, and can be queried by any other Lock dealing with the same
// lock name and lock directory.
func (lock *Lock) Lock(message string) error {
	var heldMessage = ""
	for {
		acquired, err := lock.acquire(message)
		if err != nil {
			return err
		}
		if acquired {
			return nil
		}
		currMessage := lock.Message()
		if currMessage != heldMessage {
			log.Infof("Attempt Lock failed %q, %s, currently held: %s", lock.name, message, currMessage)
			heldMessage = currMessage
		}
		time.Sleep(lockWaitDelay)
	}
	panic("unreachable")
}

// LockWithTimeout tries to acquire the lock. If it cannot acquire the lock
// within the given duration, it returns ErrTimeout.  See `Lock` for
// information about the message.
func (lock *Lock) LockWithTimeout(duration time.Duration, message string) error {
	deadline := time.Now().Add(duration)
	for {
		acquired, err := lock.acquire(message)
		if err != nil {
			return err
		}
		if acquired {
			return nil
		}
		if time.Now().After(deadline) {
			return ErrTimeout
		}
		time.Sleep(lockWaitDelay)
	}
	panic("unreachable")
}

// IsHeld returns whether the lock is currently held by the receiver.
func (lock *Lock) IsLockHeld() bool {
	heldNonce, err := ioutil.ReadFile(lock.heldFile())
	if err != nil {
		return false
	}
	return bytes.Equal(heldNonce, lock.nonce)
}

// Unlock releases a held lock.  If the lock is not held ErrLockNotHeld is
// returned.
func (lock *Lock) Unlock() error {
	if !lock.IsLockHeld() {
		return ErrLockNotHeld
	}
	// To ensure reasonable unlocking, we should rename to a temp name, and delete that.
	tempLockName := fmt.Sprintf(".%s.%x", lock.name, lock.nonce)
	tempDirName := path.Join(lock.parent, tempLockName)
	// Now move the lock directory to the temp directory to release the lock.
	if err := os.Rename(lock.lockDir(), tempDirName); err != nil {
		return err
	}
	// And now cleanup.
	return os.RemoveAll(tempDirName)
}

// IsLocked returns true if the lock is currently held by anyone.
func (lock *Lock) IsLocked() bool {
	_, err := os.Stat(lock.heldFile())
	return err == nil
}

// BreakLock forcably breaks the lock that is currently being held.
func (lock *Lock) BreakLock() error {
	return os.RemoveAll(lock.lockDir())
}

// SetMessage saves the message if and only if the lock is held.
func (lock *Lock) SetMessage(message string) error {
	if !lock.IsLockHeld() {
		return ErrLockNotHeld
	}
	// Since the message can be read by anyone, make this an atomic write by
	// writing to a temp file and renaming.
	tempFile, err := ioutil.TempFile(lock.lockDir(), ".message")
	if err != nil {
		return err
	}
	tempFilename := tempFile.Name()
	fmt.Fprint(tempFile, message)
	tempFile.Close()
	return os.Rename(tempFilename, lock.messageFile())
}

// Message returns the saved message, or the empty string if there is no
// saved message.
func (lock *Lock) Message() string {
	message, err := ioutil.ReadFile(lock.messageFile())
	if err != nil {
		return ""
	}
	return string(message)
}
<|endoftext|>


<commit_before>package process

import (
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/resin-io/edge-node-manager/application"
	"github.com/resin-io/edge-node-manager/config"
	deviceStatus "github.com/resin-io/edge-node-manager/device/status"
	processStatus "github.com/resin-io/edge-node-manager/process/status"
	"github.com/resin-io/edge-node-manager/radio/bluetooth"
)

var (
	delay          time.Duration
	CurrentStatus  processStatus.Status
	TargetStatus   processStatus.Status
	UpdatesPending bool
)

// Run processes the application, checking for new commits, provisioning and updating devices
func Run(a *application.Application) []error {
	log.Info("----------------------------------------------------------------------------------------------------")

	// Pause the process if necessary
	if err := pause(); err != nil {
		return []error{err}
	}

	// Validate application to ensure the board type has been set
	if a.BoardType == "" {
		log.WithFields(log.Fields{
			"Application": a.Name,
			"Error":       "Application board type not set",
		}).Warn("Processing application")
		return nil
	}

	// Handle delete flag
	if err := a.HandleDeleteFlag(); err != nil {
		return []error{err}
	}

	// Print application info
	log.WithFields(log.Fields{
		"Application":       a.Name,
		"Number of devices": len(a.Devices),
	}).Info("Processing application")

	// Get all online devices associated with this application
	if err := a.GetOnlineDevices(); err != nil {
		return []error{err}
	}

	// Provision non-provisoned online devices associated with this application
	if errs := a.ProvisionDevices(); errs != nil {
		return errs
	}

	// Set the status of all offline provisioned devices associated with this application to OFFLINE
	if errs := a.SetOfflineDeviceStatus(); errs != nil {
		return errs
	}

	// Update firmware for all online devices associated with this application
	if errs := a.UpdateOnlineDevices(); errs != nil {
		return errs
	}

	// Update config for all online devices associated with this application
	if errs := a.UpdateConfigOnlineDevices(); errs != nil {
		return errs
	}

	// Update environment for all online devices associated with this application
	if errs := a.UpdateEnvironmentOnlineDevices(); errs != nil {
		return errs
	}

	// Handle device flags
	if err := a.HandleFlags(); err != nil {
		return []error{err}
	}

	// Put all provisioned devices associated with this application
	if err := a.PutDevices(); err != nil {
		return []error{err}
	}

	return nil
}

func Pending() {
	for _, a := range application.List {
		for _, d := range a.Devices {
			if d.Commit != d.TargetCommit && d.Status != deviceStatus.OFFLINE {
				UpdatesPending = true
				return
			}
		}
	}

	UpdatesPending = false
	return
}

func init() {
	log.SetLevel(config.GetLogLevel())

	var err error
	if delay, err = config.GetPauseDelay(); err != nil {
		log.WithFields(log.Fields{
			"Error": err,
		}).Fatal("Unable to load pause delay")
	}

	CurrentStatus = processStatus.RUNNING
	TargetStatus = processStatus.RUNNING
	UpdatesPending = false

	log.WithFields(log.Fields{
		"Pause delay": delay,
	}).Debug("Initialise process")
}

func pause() error {
	if TargetStatus != processStatus.PAUSED {
		return nil
	}

	if err := bluetooth.CloseDevice(); err != nil {
		return err
	}

	CurrentStatus = processStatus.PAUSED
	log.WithFields(log.Fields{
		"Status": CurrentStatus,
	}).Info("Process status")

	for TargetStatus == processStatus.PAUSED {
		time.Sleep(delay * time.Second)
	}

	if err := bluetooth.OpenDevice(); err != nil {
		return err
	}

	CurrentStatus = processStatus.RUNNING
	log.WithFields(log.Fields{
		"Status": CurrentStatus,
	}).Info("Process status")

	return nil
}
<commit_msg>Quick hacky temporary fix<commit_after>package process

import (
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/resin-io/edge-node-manager/application"
	"github.com/resin-io/edge-node-manager/config"
	deviceStatus "github.com/resin-io/edge-node-manager/device/status"
	processStatus "github.com/resin-io/edge-node-manager/process/status"
	"github.com/resin-io/edge-node-manager/radio/bluetooth"
)

var (
	delay          time.Duration
	CurrentStatus  processStatus.Status
	TargetStatus   processStatus.Status
	UpdatesPending bool
)

// Run processes the application, checking for new commits, provisioning and updating devices
func Run(a *application.Application) []error {
	log.Info("----------------------------------------------------------------------------------------------------")

	// Pause the process if necessary
	if err := pause(); err != nil {
		return []error{err}
	}

	// Validate application to ensure the board type has been set
	if a.BoardType == "" {
		log.WithFields(log.Fields{
			"Application": a.Name,
			"Error":       "Application board type not set",
		}).Warn("Processing application")
		return nil
	}

	// Handle delete flag
	if err := a.HandleDeleteFlag(); err != nil {
		return []error{err}
	}

	// Print application info
	log.WithFields(log.Fields{
		"Application":       a.Name,
		"Number of devices": len(a.Devices),
	}).Info("Processing application")

	// Reset the bluetooth device to clean up any left over go routines etc. Quick fix
	if err := bluetooth.ResetDevice(); err != nil {
		return []error{err}
	}

	// Get all online devices associated with this application
	if err := a.GetOnlineDevices(); err != nil {
		return []error{err}
	}

	// Provision non-provisoned online devices associated with this application
	if errs := a.ProvisionDevices(); errs != nil {
		return errs
	}

	// Set the status of all offline provisioned devices associated with this application to OFFLINE
	if errs := a.SetOfflineDeviceStatus(); errs != nil {
		return errs
	}

	// Update firmware for all online devices associated with this application
	if errs := a.UpdateOnlineDevices(); errs != nil {
		return errs
	}

	// Update config for all online devices associated with this application
	if errs := a.UpdateConfigOnlineDevices(); errs != nil {
		return errs
	}

	// Update environment for all online devices associated with this application
	if errs := a.UpdateEnvironmentOnlineDevices(); errs != nil {
		return errs
	}

	// Handle device flags
	if err := a.HandleFlags(); err != nil {
		return []error{err}
	}

	// Put all provisioned devices associated with this application
	if err := a.PutDevices(); err != nil {
		return []error{err}
	}

	return nil
}

func Pending() {
	for _, a := range application.List {
		for _, d := range a.Devices {
			if d.Commit != d.TargetCommit && d.Status != deviceStatus.OFFLINE {
				UpdatesPending = true
				return
			}
		}
	}

	UpdatesPending = false
	return
}

func init() {
	log.SetLevel(config.GetLogLevel())

	var err error
	if delay, err = config.GetPauseDelay(); err != nil {
		log.WithFields(log.Fields{
			"Error": err,
		}).Fatal("Unable to load pause delay")
	}

	CurrentStatus = processStatus.RUNNING
	TargetStatus = processStatus.RUNNING
	UpdatesPending = false

	log.WithFields(log.Fields{
		"Pause delay": delay,
	}).Debug("Initialise process")
}

func pause() error {
	if TargetStatus != processStatus.PAUSED {
		return nil
	}

	if err := bluetooth.CloseDevice(); err != nil {
		return err
	}

	CurrentStatus = processStatus.PAUSED
	log.WithFields(log.Fields{
		"Status": CurrentStatus,
	}).Info("Process status")

	for TargetStatus == processStatus.PAUSED {
		time.Sleep(delay * time.Second)
	}

	if err := bluetooth.OpenDevice(); err != nil {
		return err
	}

	CurrentStatus = processStatus.RUNNING
	log.WithFields(log.Fields{
		"Status": CurrentStatus,
	}).Info("Process status")

	return nil
}
<|endoftext|>


<commit_before>// +build linux

package util

import (
	"fmt"
	"os"
	"strings"
	"syscall"

	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
)

func getBridgeName(iface string) string {
	return fmt.Sprintf("br%s", iface)
}

func saveIPAddress(iface, bridge netlink.Link, addrs []netlink.Addr) error {
	for i := range addrs {
		addr := addrs[i]

		// Remove from old interface
		if err := netlink.AddrDel(iface, &addr); err != nil {
			logrus.Errorf("Remove addr from %q failed: %v", iface.Attrs().Name, err)
			return err
		}

		// Add to ovs bridge
		addr.Label = bridge.Attrs().Name
		if err := netlink.AddrAdd(bridge, &addr); err != nil {
			logrus.Errorf("Add addr to bridge %q failed: %v", bridge.Attrs().Name, err)
			return err
		}
		logrus.Infof("Successfully saved addr %q to bridge %q", addr.String(), bridge.Attrs().Name)
	}

	return netlink.LinkSetUp(bridge)
}

// delAddRoute removes 'route' from 'iface' and moves to 'bridge'
func delAddRoute(iface, bridge netlink.Link, route netlink.Route) error {
	// Remove route from old interface
	if err := netlink.RouteDel(&route); err != nil && !strings.Contains(err.Error(), "no such process") {
		logrus.Errorf("Remove route from %q failed: %v", iface.Attrs().Name, err)
		return err
	}

	// Add route to ovs bridge
	route.LinkIndex = bridge.Attrs().Index
	if err := netlink.RouteAdd(&route); err != nil && !os.IsExist(err) {
		logrus.Errorf("Add route to bridge %q failed: %v", bridge.Attrs().Name, err)
		return err
	}

	logrus.Infof("Successfully saved route %q", route.String())
	return nil
}

func saveRoute(iface, bridge netlink.Link, routes []netlink.Route) error {
	for i := range routes {
		route := routes[i]

		// Handle routes for default gateway later.  This is a special case for
		// GCE where we have /32 IP addresses and we can't add the default
		// gateway before the route to the gateway.
		if route.Dst == nil && route.Gw != nil && route.LinkIndex > 0 {
			continue
		}

		err := delAddRoute(iface, bridge, route)
		if err != nil {
			return err
		}
	}

	// Now add the default gateway (if any) via this interface.
	for i := range routes {
		route := routes[i]
		if route.Dst == nil && route.Gw != nil && route.LinkIndex > 0 {
			// Remove route from 'iface' and move it to 'bridge'
			err := delAddRoute(iface, bridge, route)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

// NicToBridge creates a OVS bridge for the 'iface' and also moves the IP
// address and routes of 'iface' to OVS bridge.
func NicToBridge(iface string) error {
	ifaceLink, err := netlink.LinkByName(iface)
	if err != nil {
		return err
	}

	bridge := getBridgeName(iface)
	stdout, stderr, err := RunOVSVsctl(
		"--", "--may-exist", "add-br", bridge,
		"--", "br-set-external-id", bridge, "bridge-id", bridge,
		"--", "set", "bridge", bridge, "fail-mode=standalone",
		fmt.Sprintf("other_config:hwaddr=%s", ifaceLink.Attrs().HardwareAddr),
		"--", "--may-exist", "add-port", bridge, iface)
	if err != nil {
		logrus.Errorf("Failed to create OVS bridge, stdout: %q, stderr: %q, error: %v", stdout, stderr, err)
		return err
	}
	logrus.Infof("Successfully created OVS bridge %q", bridge)

	// Get ip addresses and routes before any real operations.
	addrs, err := netlink.AddrList(ifaceLink, syscall.AF_INET)
	if err != nil {
		return err
	}
	routes, err := netlink.RouteList(ifaceLink, syscall.AF_INET)
	if err != nil {
		return err
	}

	bridgeLink, err := netlink.LinkByName(bridge)
	if err != nil {
		return err
	}

	// save ip addresses to bridge.
	if err = saveIPAddress(ifaceLink, bridgeLink, addrs); err != nil {
		return err
	}

	// save routes to bridge.
	if err = saveRoute(ifaceLink, bridgeLink, routes); err != nil {
		return err
	}

	return nil
}
<commit_msg>nicstobridge: Mark the physical interface as transient.<commit_after>// +build linux

package util

import (
	"fmt"
	"os"
	"strings"
	"syscall"

	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
)

func getBridgeName(iface string) string {
	return fmt.Sprintf("br%s", iface)
}

func saveIPAddress(iface, bridge netlink.Link, addrs []netlink.Addr) error {
	for i := range addrs {
		addr := addrs[i]

		// Remove from old interface
		if err := netlink.AddrDel(iface, &addr); err != nil {
			logrus.Errorf("Remove addr from %q failed: %v", iface.Attrs().Name, err)
			return err
		}

		// Add to ovs bridge
		addr.Label = bridge.Attrs().Name
		if err := netlink.AddrAdd(bridge, &addr); err != nil {
			logrus.Errorf("Add addr to bridge %q failed: %v", bridge.Attrs().Name, err)
			return err
		}
		logrus.Infof("Successfully saved addr %q to bridge %q", addr.String(), bridge.Attrs().Name)
	}

	return netlink.LinkSetUp(bridge)
}

// delAddRoute removes 'route' from 'iface' and moves to 'bridge'
func delAddRoute(iface, bridge netlink.Link, route netlink.Route) error {
	// Remove route from old interface
	if err := netlink.RouteDel(&route); err != nil && !strings.Contains(err.Error(), "no such process") {
		logrus.Errorf("Remove route from %q failed: %v", iface.Attrs().Name, err)
		return err
	}

	// Add route to ovs bridge
	route.LinkIndex = bridge.Attrs().Index
	if err := netlink.RouteAdd(&route); err != nil && !os.IsExist(err) {
		logrus.Errorf("Add route to bridge %q failed: %v", bridge.Attrs().Name, err)
		return err
	}

	logrus.Infof("Successfully saved route %q", route.String())
	return nil
}

func saveRoute(iface, bridge netlink.Link, routes []netlink.Route) error {
	for i := range routes {
		route := routes[i]

		// Handle routes for default gateway later.  This is a special case for
		// GCE where we have /32 IP addresses and we can't add the default
		// gateway before the route to the gateway.
		if route.Dst == nil && route.Gw != nil && route.LinkIndex > 0 {
			continue
		}

		err := delAddRoute(iface, bridge, route)
		if err != nil {
			return err
		}
	}

	// Now add the default gateway (if any) via this interface.
	for i := range routes {
		route := routes[i]
		if route.Dst == nil && route.Gw != nil && route.LinkIndex > 0 {
			// Remove route from 'iface' and move it to 'bridge'
			err := delAddRoute(iface, bridge, route)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

// NicToBridge creates a OVS bridge for the 'iface' and also moves the IP
// address and routes of 'iface' to OVS bridge.
func NicToBridge(iface string) error {
	ifaceLink, err := netlink.LinkByName(iface)
	if err != nil {
		return err
	}

	bridge := getBridgeName(iface)
	stdout, stderr, err := RunOVSVsctl(
		"--", "--may-exist", "add-br", bridge,
		"--", "br-set-external-id", bridge, "bridge-id", bridge,
		"--", "set", "bridge", bridge, "fail-mode=standalone",
		fmt.Sprintf("other_config:hwaddr=%s", ifaceLink.Attrs().HardwareAddr),
		"--", "--may-exist", "add-port", bridge, iface,
		"--", "set", "port", iface, "other-config:transient=true")
	if err != nil {
		logrus.Errorf("Failed to create OVS bridge, stdout: %q, stderr: %q, error: %v", stdout, stderr, err)
		return err
	}
	logrus.Infof("Successfully created OVS bridge %q", bridge)

	// Get ip addresses and routes before any real operations.
	addrs, err := netlink.AddrList(ifaceLink, syscall.AF_INET)
	if err != nil {
		return err
	}
	routes, err := netlink.RouteList(ifaceLink, syscall.AF_INET)
	if err != nil {
		return err
	}

	bridgeLink, err := netlink.LinkByName(bridge)
	if err != nil {
		return err
	}

	// save ip addresses to bridge.
	if err = saveIPAddress(ifaceLink, bridgeLink, addrs); err != nil {
		return err
	}

	// save routes to bridge.
	if err = saveRoute(ifaceLink, bridgeLink, routes); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Vitess Authors.

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

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

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

 This file contains integration tests for the go/vt/binlog package.
 It sets up filtered replication between two shards and checks how data flows
 through binlog streamer.
*/

package binlog

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"vitess.io/vitess/go/mysql"
	"vitess.io/vitess/go/sqltypes"
	"vitess.io/vitess/go/test/endtoend/cluster"
	"vitess.io/vitess/go/vt/proto/query"
)

var (
	localCluster *cluster.LocalProcessCluster
	cell         = "zone1"
	hostname     = "localhost"
	keyspaceName = "ks"
	tableName    = "test_table"
	sqlSchema    = `
					create table %s(
					id bigint(20) unsigned auto_increment,
					msg varchar(64),
					primary key (id),
					index by_msg (msg)
					) Engine=InnoDB
`
	insertSql       = `insert into %s(id,msg) values(%d, "%s")`
	commonTabletArg = []string{
		"-vreplication_healthcheck_topology_refresh", "1s",
		"-vreplication_healthcheck_retry_delay", "1s",
		"-vreplication_retry_delay", "1s",
		"-degraded_threshold", "5s",
		"-lock_tables_timeout", "5s",
		"-watch_replication_stream",
		"-enable_replication_reporter",
		"-serving_state_grace_period", "1s",
		"-binlog_player_protocol", "grpc",
		"-enable-autocommit",
	}
	vSchema = `
		{
		  "sharded": false,
		  "vindexes": {
			"hash_index": {
			  "type": "hash"
			}
		  },
		  "tables": {
			"%s": {
			   "column_vindexes": [
				{
				  "column": "id",
				  "name": "hash_index"
				}
			  ]
			}
		  }
		}
`
	srcMaster  *cluster.Vttablet
	srcReplica *cluster.Vttablet
	srcRdonly  *cluster.Vttablet

	destMaster  *cluster.Vttablet
	destReplica *cluster.Vttablet
	destRdonly  *cluster.Vttablet
)

func TestMain(m *testing.M) {
	flag.Parse()

	exitcode, err := func() (int, error) {
		localCluster = cluster.NewCluster(cell, hostname)
		defer localCluster.Teardown()
		os.Setenv("EXTRA_MY_CNF", path.Join(os.Getenv("VTROOT"), "config", "mycnf", "rbr.cnf"))
		localCluster.Keyspaces = append(localCluster.Keyspaces, cluster.Keyspace{
			Name: keyspaceName,
		})

		// Start topo server
		if err := localCluster.StartTopo(); err != nil {
			return 1, err
		}

		srcMaster = localCluster.GetVttabletInstance("master", 0, "")
		srcReplica = localCluster.GetVttabletInstance("replica", 0, "")
		srcRdonly = localCluster.GetVttabletInstance("rdonly", 0, "")

		destMaster = localCluster.GetVttabletInstance("master", 0, "")
		destReplica = localCluster.GetVttabletInstance("replica", 0, "")
		destRdonly = localCluster.GetVttabletInstance("rdonly", 0, "")

		var mysqlProcs []*exec.Cmd
		for _, tablet := range []*cluster.Vttablet{srcMaster, srcReplica, srcRdonly, destMaster, destReplica, destRdonly} {
			tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory)
			tablet.VttabletProcess = cluster.VttabletProcessInstance(tablet.HTTPPort,
				tablet.GrpcPort,
				tablet.TabletUID,
				cell,
				"",
				keyspaceName,
				localCluster.VtctldProcess.Port,
				tablet.Type,
				localCluster.TopoPort,
				hostname,
				localCluster.TmpDirectory,
				commonTabletArg,
				true,
			)
			tablet.VttabletProcess.SupportsBackup = true
			proc, err := tablet.MysqlctlProcess.StartProcess()
			if err != nil {
				return 1, err
			}
			mysqlProcs = append(mysqlProcs, proc)
		}
		for _, proc := range mysqlProcs {
			if err := proc.Wait(); err != nil {
				return 1, err
			}
		}

		if err := localCluster.VtctlProcess.CreateKeyspace(keyspaceName); err != nil {
			return 1, err
		}

		shard1 := cluster.Shard{
			Name:      "0",
			Vttablets: []*cluster.Vttablet{srcMaster, srcReplica, srcRdonly},
		}
		for idx := range shard1.Vttablets {
			shard1.Vttablets[idx].VttabletProcess.Shard = shard1.Name
		}
		localCluster.Keyspaces[0].Shards = append(localCluster.Keyspaces[0].Shards, shard1)

		shard2 := cluster.Shard{
			Name:      "-",
			Vttablets: []*cluster.Vttablet{destMaster, destReplica, destRdonly},
		}
		for idx := range shard2.Vttablets {
			shard2.Vttablets[idx].VttabletProcess.Shard = shard2.Name
		}
		localCluster.Keyspaces[0].Shards = append(localCluster.Keyspaces[0].Shards, shard2)

		for _, tablet := range shard1.Vttablets {
			if err := localCluster.VtctlclientProcess.InitTablet(tablet, cell, keyspaceName, hostname, shard1.Name); err != nil {
				return 1, err
			}
			if err := tablet.VttabletProcess.Setup(); err != nil {
				return 1, err
			}
		}
		if err := localCluster.VtctlclientProcess.InitShardMaster(keyspaceName, shard1.Name, cell, srcMaster.TabletUID); err != nil {
			return 1, err
		}

		if err := localCluster.VtctlclientProcess.ApplySchema(keyspaceName, fmt.Sprintf(sqlSchema, tableName)); err != nil {
			return 1, err
		}
		if err := localCluster.VtctlclientProcess.ApplyVSchema(keyspaceName, fmt.Sprintf(vSchema, tableName)); err != nil {
			return 1, err
		}

		// run a health check on source replica so it responds to discovery
		// (for binlog players) and on the source rdonlys (for workers)
		for _, tablet := range []string{srcReplica.Alias, srcRdonly.Alias} {
			if err := localCluster.VtctlclientProcess.ExecuteCommand("RunHealthCheck", tablet); err != nil {
				return 1, err
			}
		}

		// Create destination shard (won't be serving as there is no DB)
		for _, tablet := range shard2.Vttablets {
			if err := localCluster.VtctlclientProcess.InitTablet(tablet, cell, keyspaceName, hostname, shard2.Name); err != nil {
				return 1, err
			}
			if err := tablet.VttabletProcess.Setup(); err != nil {
				return 1, err
			}
		}

		if err := localCluster.VtctlclientProcess.InitShardMaster(keyspaceName, shard2.Name, cell, destMaster.TabletUID); err != nil {
			return 1, err
		}
		_ = localCluster.VtctlclientProcess.ExecuteCommand("RebuildKeyspaceGraph", keyspaceName)
		// Copy schema
		if err := localCluster.VtctlclientProcess.ExecuteCommand("CopySchemaShard", srcReplica.Alias, fmt.Sprintf("%s/%s", keyspaceName, shard2.Name)); err != nil {
			return 1, err
		}

		// run the clone worker (this is a degenerate case, source and destination
		// both have the full keyrange. Happens to work correctly).
		localCluster.VtworkerProcess = *cluster.VtworkerProcessInstance(localCluster.GetAndReservePort(),
			localCluster.GetAndReservePort(),
			localCluster.TopoPort,
			localCluster.Hostname,
			localCluster.TmpDirectory)
		localCluster.VtworkerProcess.Cell = cell
		if err := localCluster.VtworkerProcess.ExecuteVtworkerCommand(localCluster.VtworkerProcess.Port,
			localCluster.VtworkerProcess.GrpcPort, "--use_v3_resharding_mode=true",
			"SplitClone",
			"--chunk_count", "10",
			"--min_rows_per_chunk", "1",
			"--exclude_tables", "unrelated",
			"--min_healthy_rdonly_tablets", "1",
			fmt.Sprintf("%s/%s", keyspaceName, shard1.Name)); err != nil {
			return 1, err
		}
		if err := destMaster.VttabletProcess.WaitForBinLogPlayerCount(1); err != nil {
			return 1, err
		}
		// Wait for dst_replica to be ready.
		if err := destReplica.VttabletProcess.WaitForBinlogServerState("Enabled"); err != nil {
			return 1, err
		}
		return m.Run(), nil
	}()
	if err != nil {
		fmt.Printf("%v\n", err)
		os.Exit(1)
	} else {
		os.Exit(exitcode)
	}
}

//  Insert something that will replicate incorrectly if the charset is not
//  propagated through binlog streamer to the destination.

//  Vitess tablets default to using utf8, so we insert something crazy and
//  pretend it's latin1. If the binlog player doesn't also pretend it's
//  latin1, it will be inserted as utf8, which will change its value.
func TestCharset(t *testing.T) {
	position, _ := cluster.GetMasterPosition(t, *destReplica, hostname)

	_, err := queryTablet(t, *srcMaster, fmt.Sprintf(insertSql, tableName, 1, "Šṛ́rỏé"), "latin1")
	assert.Nil(t, err)
	println("Waiting to get rows in dest master tablet")
	waitForReplicaEvent(t, position, "1", *destReplica)

	verifyData(t, 1, "latin1", `[UINT64(1) VARCHAR("Šṛ́rỏé")]`)
}

// Enable binlog_checksum, which will also force a log rotation that should
// cause binlog streamer to notice the new checksum setting.
func TestChecksumEnabled(t *testing.T) {
	position, _ := cluster.GetMasterPosition(t, *destReplica, hostname)
	_, err := queryTablet(t, *destReplica, "SET @@global.binlog_checksum=1", "")
	assert.Nil(t, err)

	// Insert something and make sure it comes through intact.
	_, err = queryTablet(t, *srcMaster, fmt.Sprintf(insertSql, tableName, 2, "value - 2"), "")
	assert.Nil(t, err)

	//  Look for it using update stream to see if binlog streamer can talk to
	//  dest_replica, which now has binlog_checksum enabled.
	waitForReplicaEvent(t, position, "2", *destReplica)

	verifyData(t, 2, "", `[UINT64(2) VARCHAR("value - 2")]`)
}

// Disable binlog_checksum to make sure we can also talk to a server without
// checksums enabled, in case they are enabled by default
func TestChecksumDisabled(t *testing.T) {
	position, _ := cluster.GetMasterPosition(t, *destReplica, hostname)

	_, err := queryTablet(t, *destReplica, "SET @@global.binlog_checksum=0", "")
	assert.Nil(t, err)

	// Insert something and make sure it comes through intact.
	_, err = queryTablet(t, *srcMaster, fmt.Sprintf(insertSql, tableName, 3, "value - 3"), "")
	assert.Nil(t, err)

	// Look for it using update stream to see if binlog streamer can talk to
	// dest_replica, which now has binlog_checksum disabled.
	waitForReplicaEvent(t, position, "3", *destReplica)

	verifyData(t, 3, "", `[UINT64(3) VARCHAR("value - 3")]`)
}

// Wait for a replica event with the given SQL string.
func waitForReplicaEvent(t *testing.T, position string, pkKey string, vttablet cluster.Vttablet) {
	timeout := time.Now().Add(10 * time.Second)
	for time.Now().Before(timeout) {
		println("fetching with position " + position)
		output, err := localCluster.VtctlclientProcess.ExecuteCommandWithOutput("VtTabletUpdateStream", "-position", position, "-count", "1", vttablet.Alias)
		assert.Nil(t, err)

		var binlogStreamEvent query.StreamEvent
		err = json.Unmarshal([]byte(output), &binlogStreamEvent)
		assert.Nil(t, err)
		for _, statement := range binlogStreamEvent.Statements {
			if isCurrentRowPresent(*statement, pkKey) {
				return
			}
		}
		time.Sleep(300 * time.Millisecond)
		position = binlogStreamEvent.EventToken.Position
	}
}

func isCurrentRowPresent(statement query.StreamEvent_Statement, pkKey string) bool {
	if statement.TableName == tableName &&
		statement.PrimaryKeyFields[0].Name == "id" &&
		fmt.Sprintf("%s", statement.PrimaryKeyValues[0].Values) == pkKey {
		return true
	}
	return false
}

func verifyData(t *testing.T, id uint64, charset string, expectedOutput string) {
	data, err := queryTablet(t, *destMaster, fmt.Sprintf("select id, msg from %s where id = %d", tableName, id), charset)
	assert.Nil(t, err)
	assert.NotNil(t, data.Rows)
	rowFound := assert.Equal(t, len(data.Rows), 1)
	assert.Equal(t, len(data.Fields), 2)
	if rowFound {
		assert.Equal(t, fmt.Sprintf("%v", data.Rows[0]), expectedOutput)
	}
}

func queryTablet(t *testing.T, vttablet cluster.Vttablet, query string, charset string) (*sqltypes.Result, error) {
	dbParams := mysql.ConnParams{
		Uname:      "vt_dba",
		UnixSocket: path.Join(vttablet.VttabletProcess.Directory, "mysql.sock"),

		DbName: fmt.Sprintf("vt_%s", keyspaceName),
	}
	if charset != "" {
		dbParams.Charset = charset
	}
	ctx := context.Background()
	dbConn, err := mysql.Connect(ctx, &dbParams)
	assert.Nil(t, err)
	defer dbConn.Close()
	return dbConn.ExecuteFetch(query, 1000, true)
}
<commit_msg>fixed binlog test<commit_after>/*
Copyright 2019 The Vitess Authors.

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

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

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

 This file contains integration tests for the go/vt/binlog package.
 It sets up filtered replication between two shards and checks how data flows
 through binlog streamer.
*/

package binlog

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"vitess.io/vitess/go/mysql"
	"vitess.io/vitess/go/sqltypes"
	"vitess.io/vitess/go/test/endtoend/cluster"
	"vitess.io/vitess/go/vt/proto/query"
)

var (
	localCluster *cluster.LocalProcessCluster
	cell         = "zone1"
	hostname     = "localhost"
	keyspaceName = "ks"
	tableName    = "test_table"
	sqlSchema    = `
					create table %s(
					id bigint(20) unsigned auto_increment,
					msg varchar(64),
					primary key (id),
					index by_msg (msg)
					) Engine=InnoDB
`
	insertSql       = `insert into %s(id,msg) values(%d, "%s")`
	commonTabletArg = []string{
		"-vreplication_healthcheck_topology_refresh", "1s",
		"-vreplication_healthcheck_retry_delay", "1s",
		"-vreplication_retry_delay", "1s",
		"-degraded_threshold", "5s",
		"-lock_tables_timeout", "5s",
		"-watch_replication_stream",
		"-enable_replication_reporter",
		"-serving_state_grace_period", "1s",
		"-binlog_player_protocol", "grpc",
		"-enable-autocommit",
	}
	vSchema = `
		{
		  "sharded": false,
		  "vindexes": {
			"hash_index": {
			  "type": "hash"
			}
		  },
		  "tables": {
			"%s": {
			   "column_vindexes": [
				{
				  "column": "id",
				  "name": "hash_index"
				}
			  ]
			}
		  }
		}
`
	srcMaster  *cluster.Vttablet
	srcReplica *cluster.Vttablet
	srcRdonly  *cluster.Vttablet

	destMaster  *cluster.Vttablet
	destReplica *cluster.Vttablet
	destRdonly  *cluster.Vttablet
)

func TestMain(m *testing.M) {
	flag.Parse()

	exitcode, err := func() (int, error) {
		localCluster = cluster.NewCluster(cell, hostname)
		defer localCluster.Teardown()

		localCluster.Keyspaces = append(localCluster.Keyspaces, cluster.Keyspace{
			Name: keyspaceName,
		})

		// Start topo server
		if err := localCluster.StartTopo(); err != nil {
			return 1, err
		}

		srcMaster = localCluster.GetVttabletInstance("master", 0, "")
		srcReplica = localCluster.GetVttabletInstance("replica", 0, "")
		srcRdonly = localCluster.GetVttabletInstance("rdonly", 0, "")

		destMaster = localCluster.GetVttabletInstance("master", 0, "")
		destReplica = localCluster.GetVttabletInstance("replica", 0, "")
		destRdonly = localCluster.GetVttabletInstance("rdonly", 0, "")

		var mysqlProcs []*exec.Cmd
		for _, tablet := range []*cluster.Vttablet{srcMaster, srcReplica, srcRdonly, destMaster, destReplica, destRdonly} {
			tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory)
			tablet.VttabletProcess = cluster.VttabletProcessInstance(tablet.HTTPPort,
				tablet.GrpcPort,
				tablet.TabletUID,
				cell,
				"",
				keyspaceName,
				localCluster.VtctldProcess.Port,
				tablet.Type,
				localCluster.TopoPort,
				hostname,
				localCluster.TmpDirectory,
				commonTabletArg,
				true,
			)
			tablet.VttabletProcess.SupportsBackup = true
			proc, err := tablet.MysqlctlProcess.StartProcess()
			if err != nil {
				return 1, err
			}
			mysqlProcs = append(mysqlProcs, proc)
		}
		for _, proc := range mysqlProcs {
			if err := proc.Wait(); err != nil {
				return 1, err
			}
		}

		if err := localCluster.VtctlProcess.CreateKeyspace(keyspaceName); err != nil {
			return 1, err
		}

		shard1 := cluster.Shard{
			Name:      "0",
			Vttablets: []*cluster.Vttablet{srcMaster, srcReplica, srcRdonly},
		}
		for idx := range shard1.Vttablets {
			shard1.Vttablets[idx].VttabletProcess.Shard = shard1.Name
		}
		localCluster.Keyspaces[0].Shards = append(localCluster.Keyspaces[0].Shards, shard1)

		shard2 := cluster.Shard{
			Name:      "-",
			Vttablets: []*cluster.Vttablet{destMaster, destReplica, destRdonly},
		}
		for idx := range shard2.Vttablets {
			shard2.Vttablets[idx].VttabletProcess.Shard = shard2.Name
		}
		localCluster.Keyspaces[0].Shards = append(localCluster.Keyspaces[0].Shards, shard2)

		for _, tablet := range shard1.Vttablets {
			if err := localCluster.VtctlclientProcess.InitTablet(tablet, cell, keyspaceName, hostname, shard1.Name); err != nil {
				return 1, err
			}
			if err := tablet.VttabletProcess.Setup(); err != nil {
				return 1, err
			}
		}
		if err := localCluster.VtctlclientProcess.InitShardMaster(keyspaceName, shard1.Name, cell, srcMaster.TabletUID); err != nil {
			return 1, err
		}

		if err := localCluster.VtctlclientProcess.ApplySchema(keyspaceName, fmt.Sprintf(sqlSchema, tableName)); err != nil {
			return 1, err
		}
		if err := localCluster.VtctlclientProcess.ApplyVSchema(keyspaceName, fmt.Sprintf(vSchema, tableName)); err != nil {
			return 1, err
		}

		// run a health check on source replica so it responds to discovery
		// (for binlog players) and on the source rdonlys (for workers)
		for _, tablet := range []string{srcReplica.Alias, srcRdonly.Alias} {
			if err := localCluster.VtctlclientProcess.ExecuteCommand("RunHealthCheck", tablet); err != nil {
				return 1, err
			}
		}

		// Create destination shard (won't be serving as there is no DB)
		for _, tablet := range shard2.Vttablets {
			if err := localCluster.VtctlclientProcess.InitTablet(tablet, cell, keyspaceName, hostname, shard2.Name); err != nil {
				return 1, err
			}
			if err := tablet.VttabletProcess.Setup(); err != nil {
				return 1, err
			}
		}

		if err := localCluster.VtctlclientProcess.InitShardMaster(keyspaceName, shard2.Name, cell, destMaster.TabletUID); err != nil {
			return 1, err
		}
		_ = localCluster.VtctlclientProcess.ExecuteCommand("RebuildKeyspaceGraph", keyspaceName)
		// Copy schema
		if err := localCluster.VtctlclientProcess.ExecuteCommand("CopySchemaShard", srcReplica.Alias, fmt.Sprintf("%s/%s", keyspaceName, shard2.Name)); err != nil {
			return 1, err
		}

		// run the clone worker (this is a degenerate case, source and destination
		// both have the full keyrange. Happens to work correctly).
		localCluster.VtworkerProcess = *cluster.VtworkerProcessInstance(localCluster.GetAndReservePort(),
			localCluster.GetAndReservePort(),
			localCluster.TopoPort,
			localCluster.Hostname,
			localCluster.TmpDirectory)
		localCluster.VtworkerProcess.Cell = cell
		if err := localCluster.VtworkerProcess.ExecuteVtworkerCommand(localCluster.VtworkerProcess.Port,
			localCluster.VtworkerProcess.GrpcPort, "--use_v3_resharding_mode=true",
			"SplitClone",
			"--chunk_count", "10",
			"--min_rows_per_chunk", "1",
			"--exclude_tables", "unrelated",
			"--min_healthy_rdonly_tablets", "1",
			fmt.Sprintf("%s/%s", keyspaceName, shard1.Name)); err != nil {
			return 1, err
		}
		if err := destMaster.VttabletProcess.WaitForBinLogPlayerCount(1); err != nil {
			return 1, err
		}
		// Wait for dst_replica to be ready.
		if err := destReplica.VttabletProcess.WaitForBinlogServerState("Enabled"); err != nil {
			return 1, err
		}
		return m.Run(), nil
	}()
	if err != nil {
		fmt.Printf("%v\n", err)
		os.Exit(1)
	} else {
		os.Exit(exitcode)
	}
}

//  Insert something that will replicate incorrectly if the charset is not
//  propagated through binlog streamer to the destination.

//  Vitess tablets default to using utf8, so we insert something crazy and
//  pretend it's latin1. If the binlog player doesn't also pretend it's
//  latin1, it will be inserted as utf8, which will change its value.
func TestCharset(t *testing.T) {
	position, _ := cluster.GetMasterPosition(t, *destReplica, hostname)

	_, err := queryTablet(t, *srcMaster, fmt.Sprintf(insertSql, tableName, 1, "Šṛ́rỏé"), "latin1")
	assert.Nil(t, err)
	println("Waiting to get rows in dest master tablet")
	waitForReplicaEvent(t, position, "1", *destReplica)

	verifyData(t, 1, "latin1", `[UINT64(1) VARCHAR("Šṛ́rỏé")]`)
}

// Enable binlog_checksum, which will also force a log rotation that should
// cause binlog streamer to notice the new checksum setting.
func TestChecksumEnabled(t *testing.T) {
	position, _ := cluster.GetMasterPosition(t, *destReplica, hostname)
	_, err := queryTablet(t, *destReplica, "SET @@global.binlog_checksum=1", "")
	assert.Nil(t, err)

	// Insert something and make sure it comes through intact.
	_, err = queryTablet(t, *srcMaster, fmt.Sprintf(insertSql, tableName, 2, "value - 2"), "")
	assert.Nil(t, err)

	//  Look for it using update stream to see if binlog streamer can talk to
	//  dest_replica, which now has binlog_checksum enabled.
	waitForReplicaEvent(t, position, "2", *destReplica)

	verifyData(t, 2, "", `[UINT64(2) VARCHAR("value - 2")]`)
}

// Disable binlog_checksum to make sure we can also talk to a server without
// checksums enabled, in case they are enabled by default
func TestChecksumDisabled(t *testing.T) {
	position, _ := cluster.GetMasterPosition(t, *destReplica, hostname)

	_, err := queryTablet(t, *destReplica, "SET @@global.binlog_checksum=0", "")
	assert.Nil(t, err)

	// Insert something and make sure it comes through intact.
	_, err = queryTablet(t, *srcMaster, fmt.Sprintf(insertSql, tableName, 3, "value - 3"), "")
	assert.Nil(t, err)

	// Look for it using update stream to see if binlog streamer can talk to
	// dest_replica, which now has binlog_checksum disabled.
	waitForReplicaEvent(t, position, "3", *destReplica)

	verifyData(t, 3, "", `[UINT64(3) VARCHAR("value - 3")]`)
}

// Wait for a replica event with the given SQL string.
func waitForReplicaEvent(t *testing.T, position string, pkKey string, vttablet cluster.Vttablet) {
	timeout := time.Now().Add(10 * time.Second)
	for time.Now().Before(timeout) {
		println("fetching with position " + position)
		output, err := localCluster.VtctlclientProcess.ExecuteCommandWithOutput("VtTabletUpdateStream", "-position", position, "-count", "1", vttablet.Alias)
		assert.Nil(t, err)

		var binlogStreamEvent query.StreamEvent
		err = json.Unmarshal([]byte(output), &binlogStreamEvent)
		assert.Nil(t, err)
		for _, statement := range binlogStreamEvent.Statements {
			if isCurrentRowPresent(*statement, pkKey) {
				return
			}
		}
		time.Sleep(300 * time.Millisecond)
		position = binlogStreamEvent.EventToken.Position
	}
}

func isCurrentRowPresent(statement query.StreamEvent_Statement, pkKey string) bool {
	if statement.TableName == tableName &&
		statement.PrimaryKeyFields[0].Name == "id" &&
		fmt.Sprintf("%s", statement.PrimaryKeyValues[0].Values) == pkKey {
		return true
	}
	return false
}

func verifyData(t *testing.T, id uint64, charset string, expectedOutput string) {
	data, err := queryTablet(t, *destMaster, fmt.Sprintf("select id, msg from %s where id = %d", tableName, id), charset)
	assert.Nil(t, err)
	assert.NotNil(t, data.Rows)
	rowFound := assert.Equal(t, len(data.Rows), 1)
	assert.Equal(t, len(data.Fields), 2)
	if rowFound {
		assert.Equal(t, fmt.Sprintf("%v", data.Rows[0]), expectedOutput)
	}
}

func queryTablet(t *testing.T, vttablet cluster.Vttablet, query string, charset string) (*sqltypes.Result, error) {
	dbParams := mysql.ConnParams{
		Uname:      "vt_dba",
		UnixSocket: path.Join(vttablet.VttabletProcess.Directory, "mysql.sock"),

		DbName: fmt.Sprintf("vt_%s", keyspaceName),
	}
	if charset != "" {
		dbParams.Charset = charset
	}
	ctx := context.Background()
	dbConn, err := mysql.Connect(ctx, &dbParams)
	assert.Nil(t, err)
	defer dbConn.Close()
	return dbConn.ExecuteFetch(query, 1000, true)
}
<|endoftext|>