<commit_before>package edit

import (
	"fmt"
	"testing"

	"github.com/elves/elvish/pkg/cli/apptest"
	"github.com/elves/elvish/pkg/cli/term"
	"github.com/elves/elvish/pkg/eval"
	"github.com/elves/elvish/pkg/eval/vals"
	"github.com/elves/elvish/pkg/eval/vars"
	"github.com/elves/elvish/pkg/store"
)

var Styles = apptest.Styles

type fixture struct {
	Editor  *Editor
	TTYCtrl apptest.TTYCtrl
	Evaler  *eval.Evaler
	Store   store.Store
	Home    string

	width   int
	codeCh  <-chan string
	errCh   <-chan error
	cleanup func()
}

func rc(codes ...string) func(*fixture) {
	return func(f *fixture) { evals(f.Evaler, codes...) }
}

func assign(name string, val interface{}) func(*fixture) {
	return func(f *fixture) {
		f.Evaler.Global["temp"] = vars.NewReadOnly(val)
		evals(f.Evaler, name+` = $temp`)
	}
}

func storeOp(storeFn func(store.Store)) func(*fixture) {
	return func(f *fixture) {
		storeFn(f.Store)
		// TODO(xiaq): Don't depend on this Elvish API.
		evals(f.Evaler, "edit:history:fast-forward")
	}
}

func setup(fns ...func(*fixture)) *fixture {
	st, cleanupStore := store.MustGetTempStore()
	home, cleanupFs := eval.InTempHome()
	tty, ttyCtrl := apptest.NewFakeTTY()
	ev := eval.NewEvaler()
	ed := NewEditor(tty, ev, st)
	ev.InstallModule("edit", ed.Ns())
	evals(ev,
		`use edit`,
		// This will simplify most tests against the terminal.
		"edit:rprompt = { }")
	f := &fixture{Editor: ed, TTYCtrl: ttyCtrl, Evaler: ev, Store: st, Home: home}
	for _, fn := range fns {
		fn(f)
	}
	_, f.width = tty.Size()
	f.codeCh, f.errCh = apptest.StartReadCode(f.Editor.ReadCode)
	f.cleanup = func() {
		f.Editor.app.CommitEOF()
		f.Wait()
		cleanupFs()
		cleanupStore()
	}
	return f
}

func (f *fixture) Wait() (string, error) {
	return <-f.codeCh, <-f.errCh
}

func (f *fixture) Cleanup() {
	f.cleanup()
}

func (f *fixture) MakeBuffer(args ...interface{}) *term.Buffer {
	return term.NewBufferBuilder(f.width).MarkLines(args...).Buffer()
}

func (f *fixture) TestTTY(t *testing.T, args ...interface{}) {
	t.Helper()
	f.TTYCtrl.TestBuffer(t, f.MakeBuffer(args...))
}

func (f *fixture) TestTTYNotes(t *testing.T, args ...interface{}) {
	t.Helper()
	f.TTYCtrl.TestNotesBuffer(t, f.MakeBuffer(args...))
}

func feedInput(ttyCtrl apptest.TTYCtrl, s string) {
	for _, r := range s {
		ttyCtrl.Inject(term.K(r))
	}
}

func evals(ev *eval.Evaler, codes ...string) {
	for _, code := range codes {
		err := ev.EvalSourceInTTY(eval.NewInteractiveSource(code))
		if err != nil {
			panic(fmt.Errorf("eval %q: %s", code, err))
		}
	}
}

func getGlobal(ev *eval.Evaler, name string) interface{} {
	return ev.Global[name].Get()
}

func testGlobals(t *testing.T, ev *eval.Evaler, wantVals map[string]interface{}) {
	t.Helper()
	for name, wantVal := range wantVals {
		testGlobal(t, ev, name, wantVal)
	}
}

func testGlobal(t *testing.T, ev *eval.Evaler, name string, wantVal interface{}) {
	t.Helper()
	if val := getGlobal(ev, name); !vals.Equal(val, wantVal) {
		t.Errorf("$%s = %s, want %s",
			name, vals.Repr(val, vals.NoPretty), vals.Repr(wantVal, vals.NoPretty))
	}
}
<commit_msg>pkg/edit: Make tests work when run as root.<commit_after>package edit

import (
	"fmt"
	"testing"

	"github.com/elves/elvish/pkg/cli/apptest"
	"github.com/elves/elvish/pkg/cli/term"
	"github.com/elves/elvish/pkg/eval"
	"github.com/elves/elvish/pkg/eval/vals"
	"github.com/elves/elvish/pkg/eval/vars"
	"github.com/elves/elvish/pkg/store"
)

var Styles = apptest.Styles

type fixture struct {
	Editor  *Editor
	TTYCtrl apptest.TTYCtrl
	Evaler  *eval.Evaler
	Store   store.Store
	Home    string

	width   int
	codeCh  <-chan string
	errCh   <-chan error
	cleanup func()
}

func rc(codes ...string) func(*fixture) {
	return func(f *fixture) { evals(f.Evaler, codes...) }
}

func assign(name string, val interface{}) func(*fixture) {
	return func(f *fixture) {
		f.Evaler.Global["temp"] = vars.NewReadOnly(val)
		evals(f.Evaler, name+` = $temp`)
	}
}

func storeOp(storeFn func(store.Store)) func(*fixture) {
	return func(f *fixture) {
		storeFn(f.Store)
		// TODO(xiaq): Don't depend on this Elvish API.
		evals(f.Evaler, "edit:history:fast-forward")
	}
}

func setup(fns ...func(*fixture)) *fixture {
	st, cleanupStore := store.MustGetTempStore()
	home, cleanupFs := eval.InTempHome()
	tty, ttyCtrl := apptest.NewFakeTTY()
	ev := eval.NewEvaler()
	ed := NewEditor(tty, ev, st)
	ev.InstallModule("edit", ed.Ns())
	evals(ev,
		`use edit`,
		// This is the same as the default prompt for non-root users. This makes
		// sure that the tests will work when run as root.
		"edit:prompt = { tilde-abbr $pwd; put '> ' }",
		// This will simplify most tests against the terminal.
		"edit:rprompt = { }")
	f := &fixture{Editor: ed, TTYCtrl: ttyCtrl, Evaler: ev, Store: st, Home: home}
	for _, fn := range fns {
		fn(f)
	}
	_, f.width = tty.Size()
	f.codeCh, f.errCh = apptest.StartReadCode(f.Editor.ReadCode)
	f.cleanup = func() {
		f.Editor.app.CommitEOF()
		f.Wait()
		cleanupFs()
		cleanupStore()
	}
	return f
}

func (f *fixture) Wait() (string, error) {
	return <-f.codeCh, <-f.errCh
}

func (f *fixture) Cleanup() {
	f.cleanup()
}

func (f *fixture) MakeBuffer(args ...interface{}) *term.Buffer {
	return term.NewBufferBuilder(f.width).MarkLines(args...).Buffer()
}

func (f *fixture) TestTTY(t *testing.T, args ...interface{}) {
	t.Helper()
	f.TTYCtrl.TestBuffer(t, f.MakeBuffer(args...))
}

func (f *fixture) TestTTYNotes(t *testing.T, args ...interface{}) {
	t.Helper()
	f.TTYCtrl.TestNotesBuffer(t, f.MakeBuffer(args...))
}

func feedInput(ttyCtrl apptest.TTYCtrl, s string) {
	for _, r := range s {
		ttyCtrl.Inject(term.K(r))
	}
}

func evals(ev *eval.Evaler, codes ...string) {
	for _, code := range codes {
		err := ev.EvalSourceInTTY(eval.NewInteractiveSource(code))
		if err != nil {
			panic(fmt.Errorf("eval %q: %s", code, err))
		}
	}
}

func getGlobal(ev *eval.Evaler, name string) interface{} {
	return ev.Global[name].Get()
}

func testGlobals(t *testing.T, ev *eval.Evaler, wantVals map[string]interface{}) {
	t.Helper()
	for name, wantVal := range wantVals {
		testGlobal(t, ev, name, wantVal)
	}
}

func testGlobal(t *testing.T, ev *eval.Evaler, name string, wantVal interface{}) {
	t.Helper()
	if val := getGlobal(ev, name); !vals.Equal(val, wantVal) {
		t.Errorf("$%s = %s, want %s",
			name, vals.Repr(val, vals.NoPretty), vals.Repr(wantVal, vals.NoPretty))
	}
}
<|endoftext|>


<commit_before>/*
Copyright (C) 2016 Red Hat, Inc.

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

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

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

package sshutil

import (
	"bytes"
	"fmt"
	"io"
	"path/filepath"
	"sync"

	"github.com/docker/machine/libmachine/drivers"
	machinessh "github.com/docker/machine/libmachine/ssh"
	"golang.org/x/crypto/ssh"
)

// SSHSession provides methods for running commands on a host.
type SSHSession interface {
	Close() error
	StdinPipe() (io.WriteCloser, error)
	Run(cmd string) error
	Wait() error
}

// NewSSHClient returns an SSH client object for running commands.
func NewSSHClient(d drivers.Driver) (*ssh.Client, error) {
	h, err := newSSHHost(d)
	if err != nil {
		return nil, err

	}
	auth := &machinessh.Auth{}
	if h.SSHKeyPath != "" {
		auth.Keys = []string{h.SSHKeyPath}
	}
	config, err := machinessh.NewNativeConfig(h.Username, auth)
	if err != nil {
		return nil, err
	}

	client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", h.IP, h.Port), &config)
	if err != nil {
		return nil, err
	}
	return client, nil
}

// Transfer uses an SSH session to copy a file to the remote machine.
func Transfer(data []byte, remotedir, filename string, perm string, c *ssh.Client) error {
	// Delete the old file first. This makes sure permissions get reset.
	deleteCmd := fmt.Sprintf("sudo rm -f %s", filepath.Join(remotedir, filename))
	mkdirCmd := fmt.Sprintf("sudo mkdir -p %s", remotedir)
	for _, cmd := range []string{deleteCmd, mkdirCmd} {
		if err := RunCommand(c, cmd); err != nil {
			return err
		}
	}

	s, err := c.NewSession()
	if err != nil {
		return err
	}

	w, err := s.StdinPipe()
	if err != nil {
		return err
	}
	// The scpcmd below *should not* return until all data is copied and the
	// StdinPipe is closed. But let's use a WaitGroup to make it expicit.
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		defer w.Close()
		header := fmt.Sprintf("C%s %d %s\n", perm, len(data), filename)
		fmt.Fprint(w, header)
		reader := bytes.NewReader(data)
		io.Copy(w, reader)
		fmt.Fprint(w, "\x00")
	}()
	scpcmd := fmt.Sprintf("sudo /usr/local/bin/scp -t %s", remotedir)
	if err := s.Run(scpcmd); err != nil {
		return err
	}
	wg.Wait()

	return nil
}

func RunCommand(c *ssh.Client, cmd string) error {
	s, err := c.NewSession()
	defer s.Close()
	if err != nil {
		return err
	}

	return s.Run(cmd)
}

type sshHost struct {
	IP         string
	Port       int
	SSHKeyPath string
	Username   string
}

func newSSHHost(d drivers.Driver) (*sshHost, error) {

	ip, err := d.GetSSHHostname()
	if err != nil {
		return nil, err
	}
	port, err := d.GetSSHPort()
	if err != nil {
		return nil, err
	}
	return &sshHost{
		IP:         ip,
		Port:       port,
		SSHKeyPath: d.GetSSHKeyPath(),
		Username:   d.GetSSHUsername(),
	}, nil
}
<commit_msg>Changed docker-machine and sshutil to support hostnames with colons in them, for example ipv6 addresses<commit_after>/*
Copyright (C) 2016 Red Hat, Inc.

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

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

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

package sshutil

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"path/filepath"
	"strconv"
	"sync"

	"github.com/docker/machine/libmachine/drivers"
	machinessh "github.com/docker/machine/libmachine/ssh"
	"golang.org/x/crypto/ssh"
)

// SSHSession provides methods for running commands on a host.
type SSHSession interface {
	Close() error
	StdinPipe() (io.WriteCloser, error)
	Run(cmd string) error
	Wait() error
}

// NewSSHClient returns an SSH client object for running commands.
func NewSSHClient(d drivers.Driver) (*ssh.Client, error) {
	h, err := newSSHHost(d)
	if err != nil {
		return nil, err

	}
	auth := &machinessh.Auth{}
	if h.SSHKeyPath != "" {
		auth.Keys = []string{h.SSHKeyPath}
	}
	config, err := machinessh.NewNativeConfig(h.Username, auth)
	if err != nil {
		return nil, err
	}

	client, err := ssh.Dial("tcp", net.JoinHostPort(h.IP, strconv.Itoa(h.Port)), &config)
	if err != nil {
		return nil, err
	}
	return client, nil
}

// Transfer uses an SSH session to copy a file to the remote machine.
func Transfer(data []byte, remotedir, filename string, perm string, c *ssh.Client) error {
	// Delete the old file first. This makes sure permissions get reset.
	deleteCmd := fmt.Sprintf("sudo rm -f %s", filepath.Join(remotedir, filename))
	mkdirCmd := fmt.Sprintf("sudo mkdir -p %s", remotedir)
	for _, cmd := range []string{deleteCmd, mkdirCmd} {
		if err := RunCommand(c, cmd); err != nil {
			return err
		}
	}

	s, err := c.NewSession()
	if err != nil {
		return err
	}

	w, err := s.StdinPipe()
	if err != nil {
		return err
	}
	// The scpcmd below *should not* return until all data is copied and the
	// StdinPipe is closed. But let's use a WaitGroup to make it expicit.
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		defer w.Close()
		header := fmt.Sprintf("C%s %d %s\n", perm, len(data), filename)
		fmt.Fprint(w, header)
		reader := bytes.NewReader(data)
		io.Copy(w, reader)
		fmt.Fprint(w, "\x00")
	}()
	scpcmd := fmt.Sprintf("sudo /usr/local/bin/scp -t %s", remotedir)
	if err := s.Run(scpcmd); err != nil {
		return err
	}
	wg.Wait()

	return nil
}

func RunCommand(c *ssh.Client, cmd string) error {
	s, err := c.NewSession()
	defer s.Close()
	if err != nil {
		return err
	}

	return s.Run(cmd)
}

type sshHost struct {
	IP         string
	Port       int
	SSHKeyPath string
	Username   string
}

func newSSHHost(d drivers.Driver) (*sshHost, error) {

	ip, err := d.GetSSHHostname()
	if err != nil {
		return nil, err
	}
	port, err := d.GetSSHPort()
	if err != nil {
		return nil, err
	}
	return &sshHost{
		IP:         ip,
		Port:       port,
		SSHKeyPath: d.GetSSHKeyPath(),
		Username:   d.GetSSHUsername(),
	}, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors All rights reserved.

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

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

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

package node

import (
	"fmt"
	"os"
	"path"
	"runtime"
	"strings"

	"k8s.io/minikube/pkg/minikube/detect"

	"github.com/pkg/errors"
	"github.com/spf13/viper"
	"golang.org/x/sync/errgroup"
	"k8s.io/klog/v2"
	cmdcfg "k8s.io/minikube/cmd/minikube/cmd/config"
	"k8s.io/minikube/pkg/drivers/kic"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/download"
	"k8s.io/minikube/pkg/minikube/driver"
	"k8s.io/minikube/pkg/minikube/exit"
	"k8s.io/minikube/pkg/minikube/image"
	"k8s.io/minikube/pkg/minikube/localpath"
	"k8s.io/minikube/pkg/minikube/machine"
	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/minikube/out/register"
	"k8s.io/minikube/pkg/minikube/reason"
	"k8s.io/minikube/pkg/minikube/style"
)

const (
	cacheImages         = "cache-images"
	cacheImageConfigKey = "cache"
)

// BeginCacheKubernetesImages caches images required for Kubernetes version in the background
func beginCacheKubernetesImages(g *errgroup.Group, imageRepository string, k8sVersion string, cRuntime string, driverName string) {
	// TODO: remove imageRepository check once #7695 is fixed
	if imageRepository == "" && download.PreloadExists(k8sVersion, cRuntime, driverName) {
		klog.Info("Caching tarball of preloaded images")
		err := download.Preload(k8sVersion, cRuntime, driverName)
		if err == nil {
			klog.Infof("Finished verifying existence of preloaded tar for  %s on %s", k8sVersion, cRuntime)
			return // don't cache individual images if preload is successful.
		}
		klog.Warningf("Error downloading preloaded artifacts will continue without preload: %v", err)
	}

	if !viper.GetBool(cacheImages) {
		return
	}

	g.Go(func() error {
		return machine.CacheImagesForBootstrapper(imageRepository, k8sVersion, viper.GetString(cmdcfg.Bootstrapper))
	})
}

// handleDownloadOnly caches appropariate binaries and images
func handleDownloadOnly(cacheGroup, kicGroup *errgroup.Group, k8sVersion, containerRuntime, driverName string) {
	// If --download-only, complete the remaining downloads and exit.
	if !viper.GetBool("download-only") {
		return
	}

	binariesURL := viper.GetString("binary-mirror")
	if err := doCacheBinaries(k8sVersion, containerRuntime, driverName, binariesURL); err != nil {
		exit.Error(reason.InetCacheBinaries, "Failed to cache binaries", err)
	}
	if _, err := CacheKubectlBinary(k8sVersion, binariesURL); err != nil {
		exit.Error(reason.InetCacheKubectl, "Failed to cache kubectl", err)
	}
	waitCacheRequiredImages(cacheGroup)
	waitDownloadKicBaseImage(kicGroup)
	if err := saveImagesToTarFromConfig(); err != nil {
		exit.Error(reason.InetCacheTar, "Failed to cache images to tar", err)
	}
	out.Step(style.Check, "Download complete!")
	os.Exit(0)
}

// CacheKubectlBinary caches the kubectl binary
func CacheKubectlBinary(k8sVersion, binaryURL string) (string, error) {
	binary := "kubectl"
	if runtime.GOOS == "windows" {
		binary = "kubectl.exe"
	}

	return download.Binary(binary, k8sVersion, runtime.GOOS, detect.EffectiveArch(), binaryURL)
}

// doCacheBinaries caches Kubernetes binaries in the foreground
func doCacheBinaries(k8sVersion, containerRuntime, driverName, binariesURL string) error {
	existingBinaries := constants.KubernetesReleaseBinaries
	if !download.PreloadExists(k8sVersion, containerRuntime, driverName) {
		existingBinaries = nil
	}
	return machine.CacheBinariesForBootstrapper(k8sVersion, viper.GetString(cmdcfg.Bootstrapper), existingBinaries, binariesURL)
}

// beginDownloadKicBaseImage downloads the kic image
func beginDownloadKicBaseImage(g *errgroup.Group, cc *config.ClusterConfig, downloadOnly bool) {

	klog.Infof("Beginning downloading kic base image for %s with %s", cc.Driver, cc.KubernetesConfig.ContainerRuntime)
	register.Reg.SetStep(register.PullingBaseImage)
	out.Step(style.Pulling, "Pulling base image ...")
	g.Go(func() error {
		baseImg := cc.KicBaseImage
		if baseImg == kic.BaseImage && len(cc.KubernetesConfig.ImageRepository) != 0 {
			baseImg = updateKicImageRepo(baseImg, cc.KubernetesConfig.ImageRepository)
			cc.KicBaseImage = baseImg
		}
		var finalImg string
		// If we end up using a fallback image, notify the user
		defer func() {
			if finalImg != "" && finalImg != baseImg {
				out.WarningT(fmt.Sprintf("minikube was unable to download %s, but successfully downloaded %s as a fallback image", image.Tag(baseImg), image.Tag(finalImg)))
				cc.KicBaseImage = finalImg
			}
		}()
		for _, img := range append([]string{baseImg}, kic.FallbackImages...) {
			var err error

			if driver.IsDocker(cc.Driver) {
				if download.ImageExistsInDaemon(img) {
					klog.Infof("%s exists in daemon, skipping load", img)
					finalImg = img
					return nil
				}
			}

			klog.Infof("Downloading %s to local cache", img)
			err = download.ImageToCache(img)
			if err == nil {
				klog.Infof("successfully saved %s as a tarball", img)
				finalImg = img
			}
			if downloadOnly {
				return err
			}

			if cc.Driver == driver.Podman {
				return fmt.Errorf("not yet implemented, see issue #8426")
			}
			if driver.IsDocker(cc.Driver) {
				klog.Infof("Loading %s from local cache", img)
				err = download.CacheToDaemon(img)
				if err == nil {
					klog.Infof("successfully loaded %s from cached tarball", img)
					finalImg = img
					return nil
				}
			}

			if driver.IsDocker(cc.Driver) {
				klog.Infof("failed to load %s, will try remote image if available: %v", img, err)

				klog.Infof("Downloading %s to local daemon", img)
				err = download.ImageToDaemon(img)
				if err == nil {
					klog.Infof("successfully downloaded %s", img)
					finalImg = img
					return nil
				}
			}
			klog.Infof("failed to download %s, will try fallback image if available: %v", img, err)
		}
		return fmt.Errorf("failed to download kic base image or any fallback image")
	})
}

// waitDownloadKicBaseImage blocks until the base image for KIC is downloaded.
func waitDownloadKicBaseImage(g *errgroup.Group) {
	if err := g.Wait(); err != nil {
		if err != nil {
			if errors.Is(err, image.ErrGithubNeedsLogin) {
				klog.Warningf("Error downloading kic artifacts: %v", err)
				out.ErrT(style.Connectivity, "Unfortunately, could not download the base image {{.image_name}} ", out.V{"image_name": strings.Split(kic.BaseImage, "@")[0]})
				out.WarningT("In order to use the fall back image, you need to log in to the github packages registry")
				out.Styled(style.Documentation, `Please visit the following link for documentation around this:
	https://help.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages#authenticating-to-github-packages
`)
			}
			if errors.Is(err, image.ErrGithubNeedsLogin) || errors.Is(err, image.ErrNeedsLogin) {
				exit.Message(reason.Usage, `Please either authenticate to the registry or use --base-image flag to use a different registry.`)
			} else {
				klog.Errorln("Error downloading kic artifacts: ", err)
			}

		}
	}
	klog.Info("Successfully downloaded all kic artifacts")
}

// waitCacheRequiredImages blocks until the required images are all cached.
func waitCacheRequiredImages(g *errgroup.Group) {
	if !viper.GetBool(cacheImages) {
		return
	}
	if err := g.Wait(); err != nil {
		klog.Errorln("Error caching images: ", err)
	}
}

// saveImagesToTarFromConfig saves images to tar in cache which specified in config file.
// currently only used by download-only option
func saveImagesToTarFromConfig() error {
	images, err := imagesInConfigFile()
	if err != nil {
		return err
	}
	if len(images) == 0 {
		return nil
	}
	return image.SaveToDir(images, detect.ImageCacheDir(), false)
}

// CacheAndLoadImagesInConfig loads the images currently in the config file
// called by 'start' and 'cache reload' commands.
func CacheAndLoadImagesInConfig(profiles []*config.Profile) error {
	images, err := imagesInConfigFile()
	if err != nil {
		return errors.Wrap(err, "images")
	}
	if len(images) == 0 {
		return nil
	}
	return machine.CacheAndLoadImages(images, profiles, false)
}

func imagesInConfigFile() ([]string, error) {
	configFile, err := config.ReadConfig(localpath.ConfigFile())
	if err != nil {
		return nil, errors.Wrap(err, "read")
	}
	if values, ok := configFile[cacheImageConfigKey]; ok {
		var images []string
		for key := range values.(map[string]interface{}) {
			images = append(images, key)
		}
		return images, nil
	}
	return []string{}, nil
}

func updateKicImageRepo(imgName string, repo string) string {
	image := strings.TrimPrefix(imgName, "gcr.io/")
	if repo == "registry.cn-hangzhou.aliyuncs.com/google_containers" {
		// for aliyun registry must strip namespace from image name, e.g.
		//   registry.cn-hangzhou.aliyuncs.com/google_containers/k8s-minikube/kicbase:v0.0.25 will not work
		//   registry.cn-hangzhou.aliyuncs.com/google_containers/kicbase:v0.0.25 does work
		image = strings.TrimPrefix(image, "k8s-minikube/")
	}
	return path.Join(repo, image)
}
<commit_msg>only skip downloading kic if --download-only=false<commit_after>/*
Copyright 2020 The Kubernetes Authors All rights reserved.

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

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

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

package node

import (
	"fmt"
	"os"
	"path"
	"runtime"
	"strings"

	"k8s.io/minikube/pkg/minikube/detect"

	"github.com/pkg/errors"
	"github.com/spf13/viper"
	"golang.org/x/sync/errgroup"
	"k8s.io/klog/v2"
	cmdcfg "k8s.io/minikube/cmd/minikube/cmd/config"
	"k8s.io/minikube/pkg/drivers/kic"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/download"
	"k8s.io/minikube/pkg/minikube/driver"
	"k8s.io/minikube/pkg/minikube/exit"
	"k8s.io/minikube/pkg/minikube/image"
	"k8s.io/minikube/pkg/minikube/localpath"
	"k8s.io/minikube/pkg/minikube/machine"
	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/minikube/out/register"
	"k8s.io/minikube/pkg/minikube/reason"
	"k8s.io/minikube/pkg/minikube/style"
)

const (
	cacheImages         = "cache-images"
	cacheImageConfigKey = "cache"
)

// BeginCacheKubernetesImages caches images required for Kubernetes version in the background
func beginCacheKubernetesImages(g *errgroup.Group, imageRepository string, k8sVersion string, cRuntime string, driverName string) {
	// TODO: remove imageRepository check once #7695 is fixed
	if imageRepository == "" && download.PreloadExists(k8sVersion, cRuntime, driverName) {
		klog.Info("Caching tarball of preloaded images")
		err := download.Preload(k8sVersion, cRuntime, driverName)
		if err == nil {
			klog.Infof("Finished verifying existence of preloaded tar for  %s on %s", k8sVersion, cRuntime)
			return // don't cache individual images if preload is successful.
		}
		klog.Warningf("Error downloading preloaded artifacts will continue without preload: %v", err)
	}

	if !viper.GetBool(cacheImages) {
		return
	}

	g.Go(func() error {
		return machine.CacheImagesForBootstrapper(imageRepository, k8sVersion, viper.GetString(cmdcfg.Bootstrapper))
	})
}

// handleDownloadOnly caches appropariate binaries and images
func handleDownloadOnly(cacheGroup, kicGroup *errgroup.Group, k8sVersion, containerRuntime, driverName string) {
	// If --download-only, complete the remaining downloads and exit.
	if !viper.GetBool("download-only") {
		return
	}

	binariesURL := viper.GetString("binary-mirror")
	if err := doCacheBinaries(k8sVersion, containerRuntime, driverName, binariesURL); err != nil {
		exit.Error(reason.InetCacheBinaries, "Failed to cache binaries", err)
	}
	if _, err := CacheKubectlBinary(k8sVersion, binariesURL); err != nil {
		exit.Error(reason.InetCacheKubectl, "Failed to cache kubectl", err)
	}
	waitCacheRequiredImages(cacheGroup)
	waitDownloadKicBaseImage(kicGroup)
	if err := saveImagesToTarFromConfig(); err != nil {
		exit.Error(reason.InetCacheTar, "Failed to cache images to tar", err)
	}
	out.Step(style.Check, "Download complete!")
	os.Exit(0)
}

// CacheKubectlBinary caches the kubectl binary
func CacheKubectlBinary(k8sVersion, binaryURL string) (string, error) {
	binary := "kubectl"
	if runtime.GOOS == "windows" {
		binary = "kubectl.exe"
	}

	return download.Binary(binary, k8sVersion, runtime.GOOS, detect.EffectiveArch(), binaryURL)
}

// doCacheBinaries caches Kubernetes binaries in the foreground
func doCacheBinaries(k8sVersion, containerRuntime, driverName, binariesURL string) error {
	existingBinaries := constants.KubernetesReleaseBinaries
	if !download.PreloadExists(k8sVersion, containerRuntime, driverName) {
		existingBinaries = nil
	}
	return machine.CacheBinariesForBootstrapper(k8sVersion, viper.GetString(cmdcfg.Bootstrapper), existingBinaries, binariesURL)
}

// beginDownloadKicBaseImage downloads the kic image
func beginDownloadKicBaseImage(g *errgroup.Group, cc *config.ClusterConfig, downloadOnly bool) {

	klog.Infof("Beginning downloading kic base image for %s with %s", cc.Driver, cc.KubernetesConfig.ContainerRuntime)
	register.Reg.SetStep(register.PullingBaseImage)
	out.Step(style.Pulling, "Pulling base image ...")
	g.Go(func() error {
		baseImg := cc.KicBaseImage
		if baseImg == kic.BaseImage && len(cc.KubernetesConfig.ImageRepository) != 0 {
			baseImg = updateKicImageRepo(baseImg, cc.KubernetesConfig.ImageRepository)
			cc.KicBaseImage = baseImg
		}
		var finalImg string
		// If we end up using a fallback image, notify the user
		defer func() {
			if finalImg != "" && finalImg != baseImg {
				out.WarningT(fmt.Sprintf("minikube was unable to download %s, but successfully downloaded %s as a fallback image", image.Tag(baseImg), image.Tag(finalImg)))
				cc.KicBaseImage = finalImg
			}
		}()
		for _, img := range append([]string{baseImg}, kic.FallbackImages...) {
			var err error

			if driver.IsDocker(cc.Driver) && download.ImageExistsInDaemon(img) && !downloadOnly {
				klog.Infof("%s exists in daemon, skipping load", img)
				finalImg = img
				return nil
			}

			klog.Infof("Downloading %s to local cache", img)
			err = download.ImageToCache(img)
			if err == nil {
				klog.Infof("successfully saved %s as a tarball", img)
				finalImg = img
			}
			if downloadOnly {
				return err
			}

			if cc.Driver == driver.Podman {
				return fmt.Errorf("not yet implemented, see issue #8426")
			}
			if driver.IsDocker(cc.Driver) {
				klog.Infof("Loading %s from local cache", img)
				err = download.CacheToDaemon(img)
				if err == nil {
					klog.Infof("successfully loaded %s from cached tarball", img)
					finalImg = img
					return nil
				}
			}

			if driver.IsDocker(cc.Driver) {
				klog.Infof("failed to load %s, will try remote image if available: %v", img, err)

				klog.Infof("Downloading %s to local daemon", img)
				err = download.ImageToDaemon(img)
				if err == nil {
					klog.Infof("successfully downloaded %s", img)
					finalImg = img
					return nil
				}
			}
			klog.Infof("failed to download %s, will try fallback image if available: %v", img, err)
		}
		return fmt.Errorf("failed to download kic base image or any fallback image")
	})
}

// waitDownloadKicBaseImage blocks until the base image for KIC is downloaded.
func waitDownloadKicBaseImage(g *errgroup.Group) {
	if err := g.Wait(); err != nil {
		if err != nil {
			if errors.Is(err, image.ErrGithubNeedsLogin) {
				klog.Warningf("Error downloading kic artifacts: %v", err)
				out.ErrT(style.Connectivity, "Unfortunately, could not download the base image {{.image_name}} ", out.V{"image_name": strings.Split(kic.BaseImage, "@")[0]})
				out.WarningT("In order to use the fall back image, you need to log in to the github packages registry")
				out.Styled(style.Documentation, `Please visit the following link for documentation around this:
	https://help.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages#authenticating-to-github-packages
`)
			}
			if errors.Is(err, image.ErrGithubNeedsLogin) || errors.Is(err, image.ErrNeedsLogin) {
				exit.Message(reason.Usage, `Please either authenticate to the registry or use --base-image flag to use a different registry.`)
			} else {
				klog.Errorln("Error downloading kic artifacts: ", err)
			}

		}
	}
	klog.Info("Successfully downloaded all kic artifacts")
}

// waitCacheRequiredImages blocks until the required images are all cached.
func waitCacheRequiredImages(g *errgroup.Group) {
	if !viper.GetBool(cacheImages) {
		return
	}
	if err := g.Wait(); err != nil {
		klog.Errorln("Error caching images: ", err)
	}
}

// saveImagesToTarFromConfig saves images to tar in cache which specified in config file.
// currently only used by download-only option
func saveImagesToTarFromConfig() error {
	images, err := imagesInConfigFile()
	if err != nil {
		return err
	}
	if len(images) == 0 {
		return nil
	}
	return image.SaveToDir(images, detect.ImageCacheDir(), false)
}

// CacheAndLoadImagesInConfig loads the images currently in the config file
// called by 'start' and 'cache reload' commands.
func CacheAndLoadImagesInConfig(profiles []*config.Profile) error {
	images, err := imagesInConfigFile()
	if err != nil {
		return errors.Wrap(err, "images")
	}
	if len(images) == 0 {
		return nil
	}
	return machine.CacheAndLoadImages(images, profiles, false)
}

func imagesInConfigFile() ([]string, error) {
	configFile, err := config.ReadConfig(localpath.ConfigFile())
	if err != nil {
		return nil, errors.Wrap(err, "read")
	}
	if values, ok := configFile[cacheImageConfigKey]; ok {
		var images []string
		for key := range values.(map[string]interface{}) {
			images = append(images, key)
		}
		return images, nil
	}
	return []string{}, nil
}

func updateKicImageRepo(imgName string, repo string) string {
	image := strings.TrimPrefix(imgName, "gcr.io/")
	if repo == "registry.cn-hangzhou.aliyuncs.com/google_containers" {
		// for aliyun registry must strip namespace from image name, e.g.
		//   registry.cn-hangzhou.aliyuncs.com/google_containers/k8s-minikube/kicbase:v0.0.25 will not work
		//   registry.cn-hangzhou.aliyuncs.com/google_containers/kicbase:v0.0.25 does work
		image = strings.TrimPrefix(image, "k8s-minikube/")
	}
	return path.Join(repo, image)
}
<|endoftext|>


<commit_before>package mongo

import (
	"github.com/PandoCloud/pando-cloud/pkg/protocol"
	"github.com/PandoCloud/pando-cloud/pkg/rpcs"
	"github.com/PandoCloud/pando-cloud/pkg/tlv"
	"reflect"
	"testing"
	"time"
)

func TestRecorder(t *testing.T) {
	r, err := NewRecorder("localhost", "pandocloud", "commands")
	if err != nil {
		t.Fatal(err)
	}

	tlvs, err := tlv.MakeTLVs([]interface{}{float64(0.1), int64(100), uint64(200)})
	if err != nil {
		t.Error(err)
	}

	deviceid := uint64(12345)
	timestamp := uint64(time.Now().Unix() * 1000)

	subdata := protocol.SubData{
		Head:   protocol.SubDataHead{1, 2, 3},
		Params: tlvs,
	}

	subdatas := []protocol.SubData{}

	subdatas = append(subdatas, subdata)

	data := rpcs.ArgsPutData{
		DeviceId:  deviceid,
		Timestamp: timestamp,
		Subdata:   subdatas,
	}

	err = r.Insert(data)
	if err != nil {
		t.Error(err)
	}

	readData := rpcs.ArgsPutData{}
	err = r.FindLatest(deviceid, &readData)

	if err != nil {
		t.Error(err)
	}

	if !reflect.DeepEqual(data, readData) {
		t.Errorf("read data want %v, but got %v", data, readData)
	}

	readDatas := []rpcs.ArgsPutData{}
	err = r.FindByTimestamp(deviceid, timestamp, timestamp, &readDatas)
	t.Log(readDatas)
	if !reflect.DeepEqual(data, readDatas[0]) {
		t.Errorf("read data by timestamp want %v, but got %v", data, readDatas[0])
	}
}
<commit_msg>fix ci error.<commit_after>package mongo

import (
	"github.com/PandoCloud/pando-cloud/pkg/protocol"
	"github.com/PandoCloud/pando-cloud/pkg/rpcs"
	"github.com/PandoCloud/pando-cloud/pkg/tlv"
	"reflect"
	"testing"
	"time"
)

func TestRecorder(t *testing.T) {
	r, err := NewRecorder("localhost", "pandocloud", "commands")
	if err != nil {
		t.Fatal(err)
	}

	tlvs, err := tlv.MakeTLVs([]interface{}{float64(0.1), int64(100), uint64(200)})
	if err != nil {
		t.Error(err)
	}

	deviceid := uint64(12345)
	timestamp := uint64(time.Now().Unix() * 1000)

	subdata := protocol.SubData{
		Head:   protocol.SubDataHead{1, 2, 3},
		Params: tlvs,
	}

	subdatas := []protocol.SubData{}

	subdatas = append(subdatas, subdata)

	data := rpcs.ArgsOnStatus{
		DeviceId:  deviceid,
		Timestamp: timestamp,
		Subdata:   subdatas,
	}

	err = r.Insert(data)
	if err != nil {
		t.Error(err)
	}

	readData := rpcs.ArgsOnStatus{}
	err = r.FindLatest(deviceid, &readData)

	if err != nil {
		t.Error(err)
	}

	if !reflect.DeepEqual(data, readData) {
		t.Errorf("read data want %v, but got %v", data, readData)
	}

	readDatas := []rpcs.ArgsOnStatus{}
	err = r.FindByTimestamp(deviceid, timestamp, timestamp, &readDatas)
	t.Log(readDatas)
	if !reflect.DeepEqual(data, readDatas[0]) {
		t.Errorf("read data by timestamp want %v, but got %v", data, readDatas[0])
	}
}
<|endoftext|>


<commit_before>package aggregate

import (
	"time"

	"nimona.io/internal/context"
	"nimona.io/internal/errors"
	"nimona.io/internal/store/graph"
	"nimona.io/pkg/object"
	"nimona.io/pkg/object/dag"
	"nimona.io/pkg/object/exchange"
	"nimona.io/pkg/object/mutation"
)

//go:generate go run github.com/cheekybits/genny -in=../../../internal/generator/pubsub/pubsub.go -out=pubsub_generate.go -pkg aggregate gen "ObservableType=*AggregateObject"

type (
	// Manager for object aggregates
	Manager interface {
		Subscriber
		Append(string, ...*mutation.Operation) error
		Get(context.Context, string) (*AggregateObject, error)
	}
	// manager implementation
	manager struct {
		PubSub
		exchange     exchange.Exchange
		store        graph.Store
		dag          dag.Manager
		aggregator   *Aggregator
		graphUpdated chan string
	}
	// options for Manager.Get()
	getOptions struct {
		sync    bool
		timeout time.Duration
	}
	getOption func(*getOptions)
)

// New constructs a new manager given an object store and exchanges
func New(
	store graph.Store,
	exchange exchange.Exchange,
	dag dag.Manager,
) (
	Manager,
	error,
) {
	m := &manager{
		PubSub:       NewPubSub(),
		exchange:     exchange,
		store:        store,
		dag:          dag,
		aggregator:   NewAggregator(),
		graphUpdated: make(chan string, 100),
	}
	dag.Subscribe(m.graphUpdated)
	go m.process()
	return m, nil
}

func WithTimeout(t time.Duration) getOption {
	return func(o *getOptions) {
		o.timeout = t
	}
}

// Get returns an aggregated object given a base object hash.
func (m *manager) Get(ctx context.Context, hash string) (*AggregateObject, error) {
	os, err := m.dag.Get(ctx, hash)
	if err != nil {
		return nil, errors.Wrap(
			errors.Error("could not get complete graph"),
			err,
		)
	}

	var ro *object.Object
	ms := []*mutation.Mutation{}
	for i := range os {
		if os[i].GetType() != mutation.MutationType {
			if ro != nil {
				return nil, errors.Error("more than one basic objects")
			}
			ro = os[i]
			continue
		}
		m := &mutation.Mutation{}
		if mErr := m.FromObject(os[i]); mErr != nil {
			return nil, errors.Wrap(
				errors.New("could not retrieve from object"), mErr)
		}
		ms = append(ms, m)
	}

	ao, err := m.aggregator.Aggregate(ro, ms)
	if err != nil {
		// TODO wrap log and possibly handle error
		return nil, err
	}

	return ao, nil
}

func (m *manager) process() {
	for {
		select {
		case h := <-m.graphUpdated:
			// TODO This is way too expensive if no one actually cares about
			// this aggregate.
			ctx := context.Background()
			a, err := m.Get(ctx, h)
			if err != nil {
				// TODO log error
				continue
			}
			m.Publish(a)
		}
	}
}

// Append looks for the tail of a graph given its root hash, and created a
// mutation using them as parents.
// TODO(geoah) The mutation created using Append in the tests has a different
// hash than expected. Not sure why.
func (m *manager) Append(
	rootHash string,
	ops ...*mutation.Operation,
) error {
	// find root object
	o, err := m.store.Get(rootHash)
	if err != nil {
		return errors.Wrap(
			errors.Error("could not get graph root"),
			err,
		)
	}

	// find graph tail
	ts, err := m.store.Tails(o.HashBase58())
	if err != nil {
		return errors.Wrap(
			errors.Error("could not get graph tails"),
			err,
		)
	}

	if len(ts) == 0 {
		return errors.Error("did not find graph tails")
	}

	// create mutation
	ps := []string{}
	for _, t := range ts {
		ps = append(ps, t.HashBase58())
	}
	u := mutation.New(ops, ps)

	// put mutation
	if err := m.dag.Put(u.ToObject()); err != nil {
		return errors.Wrap(
			errors.Error("could not store mutation"),
			err,
		)
	}

	// TODO(geoah) return aggregate?

	return nil
}
<commit_msg>fix(object/aggregate): fix returned tails when there is only a root object<commit_after>package aggregate

import (
	"time"

	"nimona.io/internal/context"
	"nimona.io/internal/errors"
	"nimona.io/internal/store/graph"
	"nimona.io/pkg/object"
	"nimona.io/pkg/object/dag"
	"nimona.io/pkg/object/exchange"
	"nimona.io/pkg/object/mutation"
)

//go:generate go run github.com/cheekybits/genny -in=../../../internal/generator/pubsub/pubsub.go -out=pubsub_generate.go -pkg aggregate gen "ObservableType=*AggregateObject"

type (
	// Manager for object aggregates
	Manager interface {
		Subscriber
		Append(string, ...*mutation.Operation) error
		Get(context.Context, string) (*AggregateObject, error)
	}
	// manager implementation
	manager struct {
		PubSub
		exchange     exchange.Exchange
		store        graph.Store
		dag          dag.Manager
		aggregator   *Aggregator
		graphUpdated chan string
	}
	// options for Manager.Get()
	getOptions struct {
		sync    bool
		timeout time.Duration
	}
	getOption func(*getOptions)
)

// New constructs a new manager given an object store and exchanges
func New(
	store graph.Store,
	exchange exchange.Exchange,
	dag dag.Manager,
) (
	Manager,
	error,
) {
	m := &manager{
		PubSub:       NewPubSub(),
		exchange:     exchange,
		store:        store,
		dag:          dag,
		aggregator:   NewAggregator(),
		graphUpdated: make(chan string, 100),
	}
	dag.Subscribe(m.graphUpdated)
	go m.process()
	return m, nil
}

func WithTimeout(t time.Duration) getOption {
	return func(o *getOptions) {
		o.timeout = t
	}
}

// Get returns an aggregated object given a base object hash.
func (m *manager) Get(ctx context.Context, hash string) (*AggregateObject, error) {
	os, err := m.dag.Get(ctx, hash)
	if err != nil {
		return nil, errors.Wrap(
			errors.Error("could not get complete graph"),
			err,
		)
	}

	var ro *object.Object
	ms := []*mutation.Mutation{}
	for i := range os {
		if os[i].GetType() != mutation.MutationType {
			if ro != nil {
				return nil, errors.Error("more than one basic objects")
			}
			ro = os[i]
			continue
		}
		m := &mutation.Mutation{}
		if mErr := m.FromObject(os[i]); mErr != nil {
			return nil, errors.Wrap(
				errors.New("could not retrieve from object"), mErr)
		}
		ms = append(ms, m)
	}

	ao, err := m.aggregator.Aggregate(ro, ms)
	if err != nil {
		// TODO wrap log and possibly handle error
		return nil, err
	}

	return ao, nil
}

func (m *manager) process() {
	for {
		select {
		case h := <-m.graphUpdated:
			// TODO This is way too expensive if no one actually cares about
			// this aggregate.
			ctx := context.Background()
			a, err := m.Get(ctx, h)
			if err != nil {
				// TODO log error
				continue
			}
			m.Publish(a)
		}
	}
}

// Append looks for the tail of a graph given its root hash, and created a
// mutation using them as parents.
// TODO(geoah) The mutation created using Append in the tests has a different
// hash than expected. Not sure why.
func (m *manager) Append(
	rootHash string,
	ops ...*mutation.Operation,
) error {
	// find root object
	o, err := m.store.Get(rootHash)
	if err != nil {
		return errors.Wrap(
			errors.Error("could not get graph root"),
			err,
		)
	}

	// find graph tail
	ts, err := m.store.Tails(o.HashBase58())
	if err != nil {
		return errors.Wrap(
			errors.Error("could not get graph tails"),
			err,
		)
	}

	// if there are no tails we just use the root
	if len(ts) == 0 {
		ts = []*object.Object{
			o,
		}
	}

	// create mutation
	ps := []string{}
	for _, t := range ts {
		ps = append(ps, t.HashBase58())
	}
	u := mutation.New(ops, ps)

	// put mutation
	if err := m.dag.Put(u.ToObject()); err != nil {
		return errors.Wrap(
			errors.Error("could not store mutation"),
			err,
		)
	}

	// TODO(geoah) return aggregate?

	return nil
}
<|endoftext|>


<commit_before>package handler

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/sirupsen/logrus"
	"github.com/skygeario/skygear-server/pkg/core/asset"
	"github.com/skygeario/skygear-server/pkg/core/auth"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz/policy"
	"github.com/skygeario/skygear-server/pkg/core/db"
	"github.com/skygeario/skygear-server/pkg/core/handler"
	"github.com/skygeario/skygear-server/pkg/core/inject"
	"github.com/skygeario/skygear-server/pkg/core/model"
	"github.com/skygeario/skygear-server/pkg/core/server"
	recordGear "github.com/skygeario/skygear-server/pkg/record"
	"github.com/skygeario/skygear-server/pkg/record/dependency/record"
	"github.com/skygeario/skygear-server/pkg/record/dependency/recordconv"
	"github.com/skygeario/skygear-server/pkg/server/skyerr"
)

func AttachSaveHandler(
	server *server.Server,
	recordDependency recordGear.DependencyMap,
) *server.Server {
	server.Handle("/save", &SaveHandlerFactory{
		recordDependency,
	}).Methods("POST")
	return server
}

type SaveHandlerFactory struct {
	Dependency recordGear.DependencyMap
}

func (f SaveHandlerFactory) NewHandler(request *http.Request) http.Handler {
	h := &SaveHandler{}
	inject.DefaultInject(h, f.Dependency, request)
	return handler.APIHandlerToHandler(h, h.TxContext)
}

func (f SaveHandlerFactory) ProvideAuthzPolicy() authz.Policy {
	return policy.AllOf(
		authz.PolicyFunc(policy.DenyNoAccessKey),
		authz.PolicyFunc(policy.RequireAuthenticated),
		authz.PolicyFunc(policy.DenyDisabledUser),
	)
}

type SaveRequestPayload struct {
	Atomic bool `json:"atomic"`

	// RawMaps stores the original incoming `records`.
	RawMaps []map[string]interface{} `json:"records"`

	// IncomigItems contains de-serialized recordID or de-serialization error,
	// the item is one-one corresponding to RawMaps.
	IncomingItems []interface{}

	// Records contains the successfully de-serialized record
	Records []*record.Record

	// Errs is the array of de-serialization errors
	Errs []skyerr.Error
}

func (s SaveRequestPayload) Validate() error {
	if len(s.RawMaps) == 0 {
		return skyerr.NewInvalidArgument("expected list of record", []string{"records"})
	}

	return nil
}

func (s SaveRequestPayload) isClean() bool {
	return len(s.Errs) == 0
}

/*
SaveHandler is dummy implementation on save/modify Records
curl -X POST -H "Content-Type: application/json" \
  -d @- http://localhost:3000/save <<EOF
{
    "records": [{
				"_recordType": "note",
				"_recordID": "EA6A3E68-90F3-49B5-B470-5FFDB7A0D4E8",
        "content": "ewdsa",
        "_access": [{
            "role": "admin",
            "level": "write"
        }]
    }]
}
EOF

Save with reference
curl -X POST -H "Content-Type: application/json" \
  -d @- http://localhost:3000/save <<EOF
{
  "records": [
    {
      "collection": {
				"$type": "ref",
				"$recordType": "collection",
        "$recordID": "10"
      },
      "noteOrder": 1,
      "content": "hi",
			"_recordType": "note",
			"_recordID": "71BAE736-E9C5-43CB-ADD1-D8633B80CAFA",
      "_type": "record",
      "_access": [{
          "role": "admin",
          "level": "write"
      }]
    }
  ]
}
EOF
*/
type SaveHandler struct {
	AuthContext auth.ContextGetter `dependency:"AuthContextGetter"`
	TxContext   db.TxContext       `dependency:"TxContext"`
	RecordStore record.Store       `dependency:"RecordStore"`
	Logger      *logrus.Entry      `dependency:"HandlerLogger"`
	AssetStore  asset.Store        `dependency:"AssetStore"`
}

func (h SaveHandler) WithTx() bool {
	return false
}

func (h SaveHandler) DecodeRequest(request *http.Request) (handler.RequestPayload, error) {
	payload := SaveRequestPayload{}
	if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
		return nil, err
	}

	for _, recordMap := range payload.RawMaps {
		var r record.Record
		if err := (*recordconv.JSONRecord)(&r).FromMap(recordMap); err != nil {
			skyErr := skyerr.NewError(skyerr.InvalidArgument, err.Error())
			payload.Errs = append(payload.Errs, skyErr)
			payload.IncomingItems = append(payload.IncomingItems, skyErr)
		} else {
			r.SanitizeForInput()
			payload.IncomingItems = append(payload.IncomingItems, r.ID)
			payload.Records = append(payload.Records, &r)
		}
	}

	return payload, nil
}

func (h SaveHandler) Handle(req interface{}) (resp interface{}, err error) {
	payload := req.(SaveRequestPayload)

	var resultFilter RecordResultFilter
	err = db.WithTx(h.TxContext, func() (doErr error) {
		resultFilter, doErr = NewRecordResultFilter(
			h.RecordStore,
			h.AssetStore,
			h.AuthContext.AuthInfo(),
			h.AuthContext.AccessKeyType() == model.MasterAccessKey,
		)

		return
	})

	if err != nil {
		return
	}

	modifyReq := RecordModifyRequest{
		RecordStore:   h.RecordStore,
		TxContext:     h.TxContext,
		AssetStore:    h.AssetStore,
		Logger:        h.Logger,
		AuthInfo:      h.AuthContext.AuthInfo(),
		RecordsToSave: payload.Records,
		Atomic:        payload.Atomic,
		WithMasterKey: h.AuthContext.AccessKeyType() == model.MasterAccessKey,
		ModifyAt:      timeNow(),
	}
	modifyResp := RecordModifyResponse{
		ErrMap: map[record.ID]skyerr.Error{},
	}

	// Open transaction for whole operation if atomic save
	if payload.Atomic {
		if err = h.TxContext.BeginTx(); err != nil {
			return
		}
	}

	var opErr error
	defer func() {
		if payload.Atomic {
			if txErr := db.EndTx(h.TxContext, opErr); txErr != nil {
				err = txErr
			}
		} else {
			err = opErr
		}
	}()

	if opErr = h.ExtendRecordSchemaWithTx(payload); opErr != nil {
		return
	}

	if opErr = RecordSaveHandler(&modifyReq, &modifyResp); opErr != nil {
		// Override error in atomic save
		if payload.Atomic && len(modifyResp.ErrMap) > 0 {
			info := map[string]interface{}{}
			for recordID, err := range modifyResp.ErrMap {
				info[recordID.String()] = err
			}

			opErr = skyerr.NewErrorWithInfo(skyerr.AtomicOperationFailure,
				"Atomic Operation rolled back due to one or more errors",
				info)
			return
		}

		return
	}

	results := make([]interface{}, 0, len(payload.RawMaps))
	h.makeResultsFromIncomingItem(payload.IncomingItems, modifyResp, resultFilter, &results)

	resp = results

	return
}

// ExtendRecordSchemaWithTx ensure the operation is within a transaction.
// When the request is inatomic, the operation would be wrapped in a new transaction.
func (h SaveHandler) ExtendRecordSchemaWithTx(payload SaveRequestPayload) error {
	return executeFuncInTx(h.TxContext, payload.Atomic, func() (err error) {
		// TODO: emit schema updated event
		_, err = ExtendRecordSchema(h.RecordStore, h.Logger, payload.Records)
		if err != nil {
			h.Logger.WithError(err).Errorln("failed to migrate record schema")
			if _, ok := err.(skyerr.Error); !ok {
				err = skyerr.NewError(skyerr.IncompatibleSchema, "failed to migrate record schema")
			}

			return
		}

		return
	})
}

func (h SaveHandler) makeResultsFromIncomingItem(incomingItems []interface{}, resp RecordModifyResponse, resultFilter RecordResultFilter, results *[]interface{}) {
	currRecordIdx := 0
	for _, itemi := range incomingItems {
		var result interface{}

		switch item := itemi.(type) {
		case skyerr.Error:
			result = newSerializedError(item)
		case record.ID:
			if err, ok := resp.ErrMap[item]; ok {
				h.Logger.WithFields(logrus.Fields{
					"recordID": item,
					"err":      err,
				}).Debugln("failed to save record")

				result = serializedError{&item, err}
			} else {
				record := resp.SavedRecords[currRecordIdx]
				currRecordIdx++
				result = resultFilter.JSONResult(record)
			}
		default:
			panic(fmt.Sprintf("unknown type of incoming item: %T", itemi))
		}

		*results = append(*results, result)
	}
}
<commit_msg>Update record save handler comment description<commit_after>package handler

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/sirupsen/logrus"
	"github.com/skygeario/skygear-server/pkg/core/asset"
	"github.com/skygeario/skygear-server/pkg/core/auth"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz/policy"
	"github.com/skygeario/skygear-server/pkg/core/db"
	"github.com/skygeario/skygear-server/pkg/core/handler"
	"github.com/skygeario/skygear-server/pkg/core/inject"
	"github.com/skygeario/skygear-server/pkg/core/model"
	"github.com/skygeario/skygear-server/pkg/core/server"
	recordGear "github.com/skygeario/skygear-server/pkg/record"
	"github.com/skygeario/skygear-server/pkg/record/dependency/record"
	"github.com/skygeario/skygear-server/pkg/record/dependency/recordconv"
	"github.com/skygeario/skygear-server/pkg/server/skyerr"
)

func AttachSaveHandler(
	server *server.Server,
	recordDependency recordGear.DependencyMap,
) *server.Server {
	server.Handle("/save", &SaveHandlerFactory{
		recordDependency,
	}).Methods("POST")
	return server
}

type SaveHandlerFactory struct {
	Dependency recordGear.DependencyMap
}

func (f SaveHandlerFactory) NewHandler(request *http.Request) http.Handler {
	h := &SaveHandler{}
	inject.DefaultInject(h, f.Dependency, request)
	return handler.APIHandlerToHandler(h, h.TxContext)
}

func (f SaveHandlerFactory) ProvideAuthzPolicy() authz.Policy {
	return policy.AllOf(
		authz.PolicyFunc(policy.DenyNoAccessKey),
		authz.PolicyFunc(policy.RequireAuthenticated),
		authz.PolicyFunc(policy.DenyDisabledUser),
	)
}

type SaveRequestPayload struct {
	Atomic bool `json:"atomic"`

	// RawMaps stores the original incoming `records`.
	RawMaps []map[string]interface{} `json:"records"`

	// IncomigItems contains de-serialized recordID or de-serialization error,
	// the item is one-one corresponding to RawMaps.
	IncomingItems []interface{}

	// Records contains the successfully de-serialized record
	Records []*record.Record

	// Errs is the array of de-serialization errors
	Errs []skyerr.Error
}

func (s SaveRequestPayload) Validate() error {
	if len(s.RawMaps) == 0 {
		return skyerr.NewInvalidArgument("expected list of record", []string{"records"})
	}

	return nil
}

func (s SaveRequestPayload) isClean() bool {
	return len(s.Errs) == 0
}

/*
SaveHandler is dummy implementation on save/modify Records
curl -X POST -H "Content-Type: application/json" \
  -d @- http://localhost:3000/save <<EOF
{
    "records": [{
				"_recordType": "note",
				"_recordID": "EA6A3E68-90F3-49B5-B470-5FFDB7A0D4E8",
        "content": "ewdsa",
        "_access": [{
            "role": "admin",
            "level": "write"
        }]
    }]
}
EOF

Save with reference
curl -X POST -H "Content-Type: application/json" \
  -d @- http://localhost:3000/save <<EOF
{
  "records": [
    {
      "collection": {
        "$type": "ref",
        "$recordType": "collection",
        "$recordID": "10"
      },
      "noteOrder": 1,
      "content": "hi",
      "_recordType": "note",
      "_recordID": "71BAE736-E9C5-43CB-ADD1-D8633B80CAFA",
      "_type": "record",
      "_access": [{
        "role": "admin",
        "level": "write"
      }]
    }
  ]
}
EOF
*/
type SaveHandler struct {
	AuthContext auth.ContextGetter `dependency:"AuthContextGetter"`
	TxContext   db.TxContext       `dependency:"TxContext"`
	RecordStore record.Store       `dependency:"RecordStore"`
	Logger      *logrus.Entry      `dependency:"HandlerLogger"`
	AssetStore  asset.Store        `dependency:"AssetStore"`
}

func (h SaveHandler) WithTx() bool {
	return false
}

func (h SaveHandler) DecodeRequest(request *http.Request) (handler.RequestPayload, error) {
	payload := SaveRequestPayload{}
	if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
		return nil, err
	}

	for _, recordMap := range payload.RawMaps {
		var r record.Record
		if err := (*recordconv.JSONRecord)(&r).FromMap(recordMap); err != nil {
			skyErr := skyerr.NewError(skyerr.InvalidArgument, err.Error())
			payload.Errs = append(payload.Errs, skyErr)
			payload.IncomingItems = append(payload.IncomingItems, skyErr)
		} else {
			r.SanitizeForInput()
			payload.IncomingItems = append(payload.IncomingItems, r.ID)
			payload.Records = append(payload.Records, &r)
		}
	}

	return payload, nil
}

func (h SaveHandler) Handle(req interface{}) (resp interface{}, err error) {
	payload := req.(SaveRequestPayload)

	var resultFilter RecordResultFilter
	err = db.WithTx(h.TxContext, func() (doErr error) {
		resultFilter, doErr = NewRecordResultFilter(
			h.RecordStore,
			h.AssetStore,
			h.AuthContext.AuthInfo(),
			h.AuthContext.AccessKeyType() == model.MasterAccessKey,
		)

		return
	})

	if err != nil {
		return
	}

	modifyReq := RecordModifyRequest{
		RecordStore:   h.RecordStore,
		TxContext:     h.TxContext,
		AssetStore:    h.AssetStore,
		Logger:        h.Logger,
		AuthInfo:      h.AuthContext.AuthInfo(),
		RecordsToSave: payload.Records,
		Atomic:        payload.Atomic,
		WithMasterKey: h.AuthContext.AccessKeyType() == model.MasterAccessKey,
		ModifyAt:      timeNow(),
	}
	modifyResp := RecordModifyResponse{
		ErrMap: map[record.ID]skyerr.Error{},
	}

	// Open transaction for whole operation if atomic save
	if payload.Atomic {
		if err = h.TxContext.BeginTx(); err != nil {
			return
		}
	}

	var opErr error
	defer func() {
		if payload.Atomic {
			if txErr := db.EndTx(h.TxContext, opErr); txErr != nil {
				err = txErr
			}
		} else {
			err = opErr
		}
	}()

	if opErr = h.ExtendRecordSchemaWithTx(payload); opErr != nil {
		return
	}

	if opErr = RecordSaveHandler(&modifyReq, &modifyResp); opErr != nil {
		// Override error in atomic save
		if payload.Atomic && len(modifyResp.ErrMap) > 0 {
			info := map[string]interface{}{}
			for recordID, err := range modifyResp.ErrMap {
				info[recordID.String()] = err
			}

			opErr = skyerr.NewErrorWithInfo(skyerr.AtomicOperationFailure,
				"Atomic Operation rolled back due to one or more errors",
				info)
			return
		}

		return
	}

	results := make([]interface{}, 0, len(payload.RawMaps))
	h.makeResultsFromIncomingItem(payload.IncomingItems, modifyResp, resultFilter, &results)

	resp = results

	return
}

// ExtendRecordSchemaWithTx ensure the operation is within a transaction.
// When the request is inatomic, the operation would be wrapped in a new transaction.
func (h SaveHandler) ExtendRecordSchemaWithTx(payload SaveRequestPayload) error {
	return executeFuncInTx(h.TxContext, payload.Atomic, func() (err error) {
		// TODO: emit schema updated event
		_, err = ExtendRecordSchema(h.RecordStore, h.Logger, payload.Records)
		if err != nil {
			h.Logger.WithError(err).Errorln("failed to migrate record schema")
			if _, ok := err.(skyerr.Error); !ok {
				err = skyerr.NewError(skyerr.IncompatibleSchema, "failed to migrate record schema")
			}

			return
		}

		return
	})
}

func (h SaveHandler) makeResultsFromIncomingItem(incomingItems []interface{}, resp RecordModifyResponse, resultFilter RecordResultFilter, results *[]interface{}) {
	currRecordIdx := 0
	for _, itemi := range incomingItems {
		var result interface{}

		switch item := itemi.(type) {
		case skyerr.Error:
			result = newSerializedError(item)
		case record.ID:
			if err, ok := resp.ErrMap[item]; ok {
				h.Logger.WithFields(logrus.Fields{
					"recordID": item,
					"err":      err,
				}).Debugln("failed to save record")

				result = serializedError{&item, err}
			} else {
				record := resp.SavedRecords[currRecordIdx]
				currRecordIdx++
				result = resultFilter.JSONResult(record)
			}
		default:
			panic(fmt.Sprintf("unknown type of incoming item: %T", itemi))
		}

		*results = append(*results, result)
	}
}
<|endoftext|>


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

// Package watchdog is responsible for monitoring the sentry for tasks that may
// potentially be stuck or looping inderterminally causing hard to debug hungs in
// the untrusted app.
//
// It works by periodically querying all tasks to check whether they are in user
// mode (RunUser), kernel mode (RunSys), or blocked in the kernel (OffCPU). Tasks
// that have been running in kernel mode for a long time in the same syscall
// without blocking are considered stuck and are reported.
//
// When a stuck task is detected, the watchdog can take one of the following actions:
//		1. LogWarning: Logs a warning message followed by a stack dump of all goroutines.
//			 If a tasks continues to be stuck, the message will repeat every minute, unless
//			 a new stuck task is detected
//		2. Panic: same as above, followed by panic()
//
package watchdog

import (
	"bytes"
	"fmt"
	"time"

	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/log"
	"gvisor.dev/gvisor/pkg/metric"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
	"gvisor.dev/gvisor/pkg/sync"
)

// Opts configures the watchdog.
type Opts struct {
	// TaskTimeout is the amount of time to allow a task to execute the
	// same syscall without blocking before it's declared stuck.
	TaskTimeout time.Duration

	// TaskTimeoutAction indicates what action to take when a stuck tasks
	// is detected.
	TaskTimeoutAction Action

	// StartupTimeout is the amount of time to allow between watchdog
	// creation and calling watchdog.Start.
	StartupTimeout time.Duration

	// StartupTimeoutAction indicates what action to take when
	// watchdog.Start is not called within the timeout.
	StartupTimeoutAction Action
}

// DefaultOpts is a default set of options for the watchdog.
var DefaultOpts = Opts{
	// Task timeout.
	TaskTimeout:       3 * time.Minute,
	TaskTimeoutAction: LogWarning,

	// Startup timeout.
	StartupTimeout:       30 * time.Second,
	StartupTimeoutAction: LogWarning,
}

// descheduleThreshold is the amount of time scheduling needs to be off before the entire wait period
// is discounted from task's last update time. It's set high enough that small scheduling delays won't
// trigger it.
const descheduleThreshold = 1 * time.Second

var (
	stuckStartup = metric.MustCreateNewUint64Metric("/watchdog/stuck_startup_detected", true /* sync */, "Incremented once on startup watchdog timeout")
	stuckTasks   = metric.MustCreateNewUint64Metric("/watchdog/stuck_tasks_detected", true /* sync */, "Cumulative count of stuck tasks detected")
)

// Amount of time to wait before dumping the stack to the log again when the same task(s) remains stuck.
var stackDumpSameTaskPeriod = time.Minute

// Action defines what action to take when a stuck task is detected.
type Action int

const (
	// LogWarning logs warning message followed by stack trace.
	LogWarning Action = iota

	// Panic will do the same logging as LogWarning and panic().
	Panic
)

// Set implements flag.Value.
func (a *Action) Set(v string) error {
	switch v {
	case "log", "logwarning":
		*a = LogWarning
	case "panic":
		*a = Panic
	default:
		return fmt.Errorf("invalid watchdog action %q", v)
	}
	return nil
}

// Get implements flag.Value.
func (a *Action) Get() interface{} {
	return *a
}

// String returns Action's string representation.
func (a *Action) String() string {
	switch *a {
	case LogWarning:
		return "logWarning"
	case Panic:
		return "panic"
	default:
		panic(fmt.Sprintf("Invalid watchdog action: %d", *a))
	}
}

// Watchdog is the main watchdog class. It controls a goroutine that periodically
// analyses all tasks and reports if any of them appear to be stuck.
type Watchdog struct {
	// Configuration options are embedded.
	Opts

	// period indicates how often to check all tasks. It's calculated based on
	// opts.TaskTimeout.
	period time.Duration

	// k is where the tasks come from.
	k *kernel.Kernel

	// stop is used to notify to watchdog should stop.
	stop chan struct{}

	// done is used to notify when the watchdog has stopped.
	done chan struct{}

	// offenders map contains all tasks that are currently stuck.
	offenders map[*kernel.Task]*offender

	// lastStackDump tracks the last time a stack dump was generated to prevent
	// spamming the log.
	lastStackDump time.Time

	// lastRun is set to the last time the watchdog executed a monitoring loop.
	lastRun ktime.Time

	// mu protects the fields below.
	mu sync.Mutex

	// running is true if the watchdog is running.
	running bool

	// startCalled is true if Start has ever been called. It remains true
	// even if Stop is called.
	startCalled bool
}

type offender struct {
	lastUpdateTime ktime.Time
}

// New creates a new watchdog.
func New(k *kernel.Kernel, opts Opts) *Watchdog {
	// 4 is arbitrary, just don't want to prolong 'TaskTimeout' too much.
	period := opts.TaskTimeout / 4
	w := &Watchdog{
		Opts:      opts,
		k:         k,
		period:    period,
		offenders: make(map[*kernel.Task]*offender),
		stop:      make(chan struct{}),
		done:      make(chan struct{}),
	}

	// Handle StartupTimeout if it exists.
	if w.StartupTimeout > 0 {
		log.Infof("Watchdog waiting %v for startup", w.StartupTimeout)
		go w.waitForStart() // S/R-SAFE: watchdog is stopped buring save and restarted after restore.
	}

	return w
}

// Start starts the watchdog.
func (w *Watchdog) Start() {
	w.mu.Lock()
	defer w.mu.Unlock()
	w.startCalled = true

	if w.running {
		return
	}

	if w.TaskTimeout == 0 {
		log.Infof("Watchdog task timeout disabled")
		return
	}
	w.lastRun = w.k.MonotonicClock().Now()

	log.Infof("Starting watchdog, period: %v, timeout: %v, action: %v", w.period, w.TaskTimeout, w.TaskTimeoutAction)
	go w.loop() // S/R-SAFE: watchdog is stopped during save and restarted after restore.
	w.running = true
}

// Stop requests the watchdog to stop and wait for it.
func (w *Watchdog) Stop() {
	if w.TaskTimeout == 0 {
		return
	}

	w.mu.Lock()
	defer w.mu.Unlock()
	if !w.running {
		return
	}
	log.Infof("Stopping watchdog")
	w.stop <- struct{}{}
	<-w.done
	w.running = false
	log.Infof("Watchdog stopped")
}

// waitForStart waits for Start to be called and takes action if it does not
// happen within the startup timeout.
func (w *Watchdog) waitForStart() {
	<-time.After(w.StartupTimeout)
	w.mu.Lock()
	defer w.mu.Unlock()
	if w.startCalled {
		// We are fine.
		return
	}

	stuckStartup.Increment()

	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("Watchdog.Start() not called within %s", w.StartupTimeout))
	w.doAction(w.StartupTimeoutAction, false, &buf)
}

// loop is the main watchdog routine. It only returns when 'Stop()' is called.
func (w *Watchdog) loop() {
	// Loop until someone stops it.
	for {
		select {
		case <-w.stop:
			w.done <- struct{}{}
			return
		case <-time.After(w.period):
			w.runTurn()
		}
	}
}

// runTurn runs a single pass over all tasks and reports anything it finds.
func (w *Watchdog) runTurn() {
	// Someone needs to watch the watchdog. The call below can get stuck if there
	// is a deadlock affecting root's PID namespace mutex. Run it in a goroutine
	// and report if it takes too long to return.
	var tasks []*kernel.Task
	done := make(chan struct{})
	go func() { // S/R-SAFE: watchdog is stopped and restarted during S/R.
		tasks = w.k.TaskSet().Root.Tasks()
		close(done)
	}()

	select {
	case <-done:
	case <-time.After(w.TaskTimeout):
		// Report if the watchdog is not making progress.
		// No one is watching the watchdog watcher though.
		w.reportStuckWatchdog()
		<-done
	}

	newOffenders := make(map[*kernel.Task]*offender)
	newTaskFound := false
	now := ktime.FromNanoseconds(int64(w.k.CPUClockNow() * uint64(linux.ClockTick)))

	// The process may be running with low CPU limit making tasks appear stuck because
	// are starved of CPU cycles. An estimate is that Tasks could have been starved
	// since the last time the watchdog run. If the watchdog detects that scheduling
	// is off, it will discount the entire duration since last run from 'lastUpdateTime'.
	discount := time.Duration(0)
	if now.Sub(w.lastRun.Add(w.period)) > descheduleThreshold {
		discount = now.Sub(w.lastRun)
	}
	w.lastRun = now

	log.Infof("Watchdog starting loop, tasks: %d, discount: %v", len(tasks), discount)
	for _, t := range tasks {
		tsched := t.TaskGoroutineSchedInfo()

		// An offender is a task running inside the kernel for longer than the specified timeout.
		if tsched.State == kernel.TaskGoroutineRunningSys {
			lastUpdateTime := ktime.FromNanoseconds(int64(tsched.Timestamp * uint64(linux.ClockTick)))
			elapsed := now.Sub(lastUpdateTime) - discount
			if elapsed > w.TaskTimeout {
				tc, ok := w.offenders[t]
				if !ok {
					// New stuck task detected.
					//
					// Note that tasks blocked doing IO may be considered stuck in kernel,
					// unless they are surrounded b
					// Task.UninterruptibleSleepStart/Finish.
					tc = &offender{lastUpdateTime: lastUpdateTime}
					stuckTasks.Increment()
					newTaskFound = true
				}
				newOffenders[t] = tc
			}
		}
	}
	if len(newOffenders) > 0 {
		w.report(newOffenders, newTaskFound, now)
	}

	// Remember which tasks have been reported.
	w.offenders = newOffenders
}

// report takes appropriate action when a stuck task is detected.
func (w *Watchdog) report(offenders map[*kernel.Task]*offender, newTaskFound bool, now ktime.Time) {
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("Sentry detected %d stuck task(s):\n", len(offenders)))
	for t, o := range offenders {
		tid := w.k.TaskSet().Root.IDOfTask(t)
		buf.WriteString(fmt.Sprintf("\tTask tid: %v (goroutine %d), entered RunSys state %v ago.\n", tid, t.GoroutineID(), now.Sub(o.lastUpdateTime)))
	}

	// Force stack dump only if a new task is detected.
	w.doAction(w.TaskTimeoutAction, newTaskFound, &buf)
}

func (w *Watchdog) reportStuckWatchdog() {
	var buf bytes.Buffer
	buf.WriteString("Watchdog goroutine is stuck")
	w.doAction(w.TaskTimeoutAction, false, &buf)
}

// doAction will take the given action. If the action is LogWarning, the stack
// is not always dumped to the log to prevent log flooding. "forceStack"
// guarantees that the stack will be dumped regardless.
func (w *Watchdog) doAction(action Action, forceStack bool, msg *bytes.Buffer) {
	switch action {
	case LogWarning:
		// Dump stack only if forced or sometime has passed since the last time a
		// stack dump was generated.
		if !forceStack && time.Since(w.lastStackDump) < stackDumpSameTaskPeriod {
			msg.WriteString("\n...[stack dump skipped]...")
			log.Warningf(msg.String())
			return
		}
		log.TracebackAll(msg.String())
		w.lastStackDump = time.Now()

	case Panic:
		// Panic will skip over running tasks, which is likely the culprit here. So manually
		// dump all stacks before panic'ing.
		log.TracebackAll(msg.String())

		// Attempt to flush metrics, timeout and move on in case metrics are stuck as well.
		metricsEmitted := make(chan struct{}, 1)
		go func() { // S/R-SAFE: watchdog is stopped during save and restarted after restore.
			// Flush metrics before killing process.
			metric.EmitMetricUpdate()
			metricsEmitted <- struct{}{}
		}()
		select {
		case <-metricsEmitted:
		case <-time.After(1 * time.Second):
		}
		panic(fmt.Sprintf("%s\nStack for running G's are skipped while panicking.", msg.String()))

	default:
		panic(fmt.Sprintf("Unknown watchdog action %v", action))

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

// Package watchdog is responsible for monitoring the sentry for tasks that may
// potentially be stuck or looping inderterminally causing hard to debug hungs in
// the untrusted app.
//
// It works by periodically querying all tasks to check whether they are in user
// mode (RunUser), kernel mode (RunSys), or blocked in the kernel (OffCPU). Tasks
// that have been running in kernel mode for a long time in the same syscall
// without blocking are considered stuck and are reported.
//
// When a stuck task is detected, the watchdog can take one of the following actions:
//		1. LogWarning: Logs a warning message followed by a stack dump of all goroutines.
//			 If a tasks continues to be stuck, the message will repeat every minute, unless
//			 a new stuck task is detected
//		2. Panic: same as above, followed by panic()
//
package watchdog

import (
	"bytes"
	"fmt"
	"time"

	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/log"
	"gvisor.dev/gvisor/pkg/metric"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
	"gvisor.dev/gvisor/pkg/sync"
)

// Opts configures the watchdog.
type Opts struct {
	// TaskTimeout is the amount of time to allow a task to execute the
	// same syscall without blocking before it's declared stuck.
	TaskTimeout time.Duration

	// TaskTimeoutAction indicates what action to take when a stuck tasks
	// is detected.
	TaskTimeoutAction Action

	// StartupTimeout is the amount of time to allow between watchdog
	// creation and calling watchdog.Start.
	StartupTimeout time.Duration

	// StartupTimeoutAction indicates what action to take when
	// watchdog.Start is not called within the timeout.
	StartupTimeoutAction Action
}

// DefaultOpts is a default set of options for the watchdog.
var DefaultOpts = Opts{
	// Task timeout.
	TaskTimeout:       3 * time.Minute,
	TaskTimeoutAction: LogWarning,

	// Startup timeout.
	StartupTimeout:       30 * time.Second,
	StartupTimeoutAction: LogWarning,
}

// descheduleThreshold is the amount of time scheduling needs to be off before the entire wait period
// is discounted from task's last update time. It's set high enough that small scheduling delays won't
// trigger it.
const descheduleThreshold = 1 * time.Second

var (
	stuckStartup = metric.MustCreateNewUint64Metric("/watchdog/stuck_startup_detected", true /* sync */, "Incremented once on startup watchdog timeout")
	stuckTasks   = metric.MustCreateNewUint64Metric("/watchdog/stuck_tasks_detected", true /* sync */, "Cumulative count of stuck tasks detected")
)

// Amount of time to wait before dumping the stack to the log again when the same task(s) remains stuck.
var stackDumpSameTaskPeriod = time.Minute

// Action defines what action to take when a stuck task is detected.
type Action int

const (
	// LogWarning logs warning message followed by stack trace.
	LogWarning Action = iota

	// Panic will do the same logging as LogWarning and panic().
	Panic
)

// Set implements flag.Value.
func (a *Action) Set(v string) error {
	switch v {
	case "log", "logwarning":
		*a = LogWarning
	case "panic":
		*a = Panic
	default:
		return fmt.Errorf("invalid watchdog action %q", v)
	}
	return nil
}

// Get implements flag.Value.
func (a *Action) Get() interface{} {
	return *a
}

// String returns Action's string representation.
func (a *Action) String() string {
	switch *a {
	case LogWarning:
		return "logWarning"
	case Panic:
		return "panic"
	default:
		panic(fmt.Sprintf("Invalid watchdog action: %d", *a))
	}
}

// Watchdog is the main watchdog class. It controls a goroutine that periodically
// analyses all tasks and reports if any of them appear to be stuck.
type Watchdog struct {
	// Configuration options are embedded.
	Opts

	// period indicates how often to check all tasks. It's calculated based on
	// opts.TaskTimeout.
	period time.Duration

	// k is where the tasks come from.
	k *kernel.Kernel

	// stop is used to notify to watchdog should stop.
	stop chan struct{}

	// done is used to notify when the watchdog has stopped.
	done chan struct{}

	// offenders map contains all tasks that are currently stuck.
	offenders map[*kernel.Task]*offender

	// lastStackDump tracks the last time a stack dump was generated to prevent
	// spamming the log.
	lastStackDump time.Time

	// lastRun is set to the last time the watchdog executed a monitoring loop.
	lastRun ktime.Time

	// mu protects the fields below.
	mu sync.Mutex

	// running is true if the watchdog is running.
	running bool

	// startCalled is true if Start has ever been called. It remains true
	// even if Stop is called.
	startCalled bool
}

type offender struct {
	lastUpdateTime ktime.Time
}

// New creates a new watchdog.
func New(k *kernel.Kernel, opts Opts) *Watchdog {
	// 4 is arbitrary, just don't want to prolong 'TaskTimeout' too much.
	period := opts.TaskTimeout / 4
	w := &Watchdog{
		Opts:      opts,
		k:         k,
		period:    period,
		offenders: make(map[*kernel.Task]*offender),
		stop:      make(chan struct{}),
		done:      make(chan struct{}),
	}

	// Handle StartupTimeout if it exists.
	if w.StartupTimeout > 0 {
		log.Infof("Watchdog waiting %v for startup", w.StartupTimeout)
		go w.waitForStart() // S/R-SAFE: watchdog is stopped buring save and restarted after restore.
	}

	return w
}

// Start starts the watchdog.
func (w *Watchdog) Start() {
	w.mu.Lock()
	defer w.mu.Unlock()
	w.startCalled = true

	if w.running {
		return
	}

	if w.TaskTimeout == 0 {
		log.Infof("Watchdog task timeout disabled")
		return
	}
	w.lastRun = w.k.MonotonicClock().Now()

	log.Infof("Starting watchdog, period: %v, timeout: %v, action: %v", w.period, w.TaskTimeout, w.TaskTimeoutAction)
	go w.loop() // S/R-SAFE: watchdog is stopped during save and restarted after restore.
	w.running = true
}

// Stop requests the watchdog to stop and wait for it.
func (w *Watchdog) Stop() {
	if w.TaskTimeout == 0 {
		return
	}

	w.mu.Lock()
	defer w.mu.Unlock()
	if !w.running {
		return
	}
	log.Infof("Stopping watchdog")
	w.stop <- struct{}{}
	<-w.done
	w.running = false
	log.Infof("Watchdog stopped")
}

// waitForStart waits for Start to be called and takes action if it does not
// happen within the startup timeout.
func (w *Watchdog) waitForStart() {
	<-time.After(w.StartupTimeout)
	w.mu.Lock()
	defer w.mu.Unlock()
	if w.startCalled {
		// We are fine.
		return
	}

	stuckStartup.Increment()

	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("Watchdog.Start() not called within %s", w.StartupTimeout))
	w.doAction(w.StartupTimeoutAction, false, &buf)
}

// loop is the main watchdog routine. It only returns when 'Stop()' is called.
func (w *Watchdog) loop() {
	// Loop until someone stops it.
	for {
		select {
		case <-w.stop:
			w.done <- struct{}{}
			return
		case <-time.After(w.period):
			w.runTurn()
		}
	}
}

// runTurn runs a single pass over all tasks and reports anything it finds.
func (w *Watchdog) runTurn() {
	// Someone needs to watch the watchdog. The call below can get stuck if there
	// is a deadlock affecting root's PID namespace mutex. Run it in a goroutine
	// and report if it takes too long to return.
	var tasks []*kernel.Task
	done := make(chan struct{})
	go func() { // S/R-SAFE: watchdog is stopped and restarted during S/R.
		tasks = w.k.TaskSet().Root.Tasks()
		close(done)
	}()

	select {
	case <-done:
	case <-time.After(w.TaskTimeout):
		// Report if the watchdog is not making progress.
		// No one is watching the watchdog watcher though.
		w.reportStuckWatchdog()
		<-done
	}

	newOffenders := make(map[*kernel.Task]*offender)
	newTaskFound := false
	now := ktime.FromNanoseconds(int64(w.k.CPUClockNow() * uint64(linux.ClockTick)))

	// The process may be running with low CPU limit making tasks appear stuck because
	// are starved of CPU cycles. An estimate is that Tasks could have been starved
	// since the last time the watchdog run. If the watchdog detects that scheduling
	// is off, it will discount the entire duration since last run from 'lastUpdateTime'.
	discount := time.Duration(0)
	if now.Sub(w.lastRun.Add(w.period)) > descheduleThreshold {
		discount = now.Sub(w.lastRun)
	}
	w.lastRun = now

	log.Infof("Watchdog starting loop, tasks: %d, discount: %v", len(tasks), discount)
	for _, t := range tasks {
		tsched := t.TaskGoroutineSchedInfo()

		// An offender is a task running inside the kernel for longer than the specified timeout.
		if tsched.State == kernel.TaskGoroutineRunningSys {
			lastUpdateTime := ktime.FromNanoseconds(int64(tsched.Timestamp * uint64(linux.ClockTick)))
			elapsed := now.Sub(lastUpdateTime) - discount
			if elapsed > w.TaskTimeout {
				tc, ok := w.offenders[t]
				if !ok {
					// New stuck task detected.
					//
					// Note that tasks blocked doing IO may be considered stuck in kernel,
					// unless they are surrounded b
					// Task.UninterruptibleSleepStart/Finish.
					tc = &offender{lastUpdateTime: lastUpdateTime}
					stuckTasks.Increment()
					newTaskFound = true
				}
				newOffenders[t] = tc
			}
		}
	}
	if len(newOffenders) > 0 {
		w.report(newOffenders, newTaskFound, now)
	}

	// Remember which tasks have been reported.
	w.offenders = newOffenders
}

// report takes appropriate action when a stuck task is detected.
func (w *Watchdog) report(offenders map[*kernel.Task]*offender, newTaskFound bool, now ktime.Time) {
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("Sentry detected %d stuck task(s):\n", len(offenders)))
	for t, o := range offenders {
		tid := w.k.TaskSet().Root.IDOfTask(t)
		buf.WriteString(fmt.Sprintf("\tTask tid: %v (goroutine %d), entered RunSys state %v ago.\n", tid, t.GoroutineID(), now.Sub(o.lastUpdateTime)))
	}
	buf.WriteString("Search for 'goroutine <id>' in the stack dump to find the offending goroutine(s)")

	// Force stack dump only if a new task is detected.
	w.doAction(w.TaskTimeoutAction, newTaskFound, &buf)
}

func (w *Watchdog) reportStuckWatchdog() {
	var buf bytes.Buffer
	buf.WriteString("Watchdog goroutine is stuck")
	w.doAction(w.TaskTimeoutAction, false, &buf)
}

// doAction will take the given action. If the action is LogWarning, the stack
// is not always dumped to the log to prevent log flooding. "forceStack"
// guarantees that the stack will be dumped regardless.
func (w *Watchdog) doAction(action Action, forceStack bool, msg *bytes.Buffer) {
	switch action {
	case LogWarning:
		// Dump stack only if forced or sometime has passed since the last time a
		// stack dump was generated.
		if !forceStack && time.Since(w.lastStackDump) < stackDumpSameTaskPeriod {
			msg.WriteString("\n...[stack dump skipped]...")
			log.Warningf(msg.String())
			return
		}
		log.TracebackAll(msg.String())
		w.lastStackDump = time.Now()

	case Panic:
		// Panic will skip over running tasks, which is likely the culprit here. So manually
		// dump all stacks before panic'ing.
		log.TracebackAll(msg.String())

		// Attempt to flush metrics, timeout and move on in case metrics are stuck as well.
		metricsEmitted := make(chan struct{}, 1)
		go func() { // S/R-SAFE: watchdog is stopped during save and restarted after restore.
			// Flush metrics before killing process.
			metric.EmitMetricUpdate()
			metricsEmitted <- struct{}{}
		}()
		select {
		case <-metricsEmitted:
		case <-time.After(1 * time.Second):
		}
		panic(fmt.Sprintf("%s\nStack for running G's are skipped while panicking.", msg.String()))

	default:
		panic(fmt.Sprintf("Unknown watchdog action %v", action))

	}
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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

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

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

package runtime

type ProtobufMarshaller interface {
	MarshalTo(data []byte) (int, error)
}

// NestedMarshalTo allows a caller to avoid extra allocations during serialization of an Unknown
// that will contain an object that implements ProtobufMarshaller.
func (m *Unknown) NestedMarshalTo(data []byte, b ProtobufMarshaller, size uint64) (int, error) {
	var i int
	_ = i
	var l int
	_ = l
	data[i] = 0xa
	i++
	i = encodeVarintGenerated(data, i, uint64(m.TypeMeta.Size()))
	n1, err := m.TypeMeta.MarshalTo(data[i:])
	if err != nil {
		return 0, err
	}
	i += n1

	if b != nil {
		data[i] = 0x12
		i++
		i = encodeVarintGenerated(data, i, size)
		n2, err := b.MarshalTo(data[i:])
		if err != nil {
			return 0, err
		}
		i += n2
	}

	data[i] = 0x1a
	i++
	i = encodeVarintGenerated(data, i, uint64(len(m.ContentEncoding)))
	i += copy(data[i:], m.ContentEncoding)

	data[i] = 0x22
	i++
	i = encodeVarintGenerated(data, i, uint64(len(m.ContentType)))
	i += copy(data[i:], m.ContentType)
	return i, nil
}
<commit_msg>Add a defensive sanity check to protobuf marshal<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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

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

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

package runtime

import (
	"fmt"
)

type ProtobufMarshaller interface {
	MarshalTo(data []byte) (int, error)
}

// NestedMarshalTo allows a caller to avoid extra allocations during serialization of an Unknown
// that will contain an object that implements ProtobufMarshaller.
func (m *Unknown) NestedMarshalTo(data []byte, b ProtobufMarshaller, size uint64) (int, error) {
	var i int
	_ = i
	var l int
	_ = l
	data[i] = 0xa
	i++
	i = encodeVarintGenerated(data, i, uint64(m.TypeMeta.Size()))
	n1, err := m.TypeMeta.MarshalTo(data[i:])
	if err != nil {
		return 0, err
	}
	i += n1

	if b != nil {
		data[i] = 0x12
		i++
		i = encodeVarintGenerated(data, i, size)
		n2, err := b.MarshalTo(data[i:])
		if err != nil {
			return 0, err
		}
		if uint64(n2) != size {
			// programmer error: the Size() method for protobuf does not match the results of MarshalTo, which means the proto
			// struct returned would be wrong.
			return 0, fmt.Errorf("the Size() value of %T was %d, but NestedMarshalTo wrote %d bytes to data", b, size, n2)
		}
		i += n2
	}

	data[i] = 0x1a
	i++
	i = encodeVarintGenerated(data, i, uint64(len(m.ContentEncoding)))
	i += copy(data[i:], m.ContentEncoding)

	data[i] = 0x22
	i++
	i = encodeVarintGenerated(data, i, uint64(len(m.ContentType)))
	i += copy(data[i:], m.ContentType)
	return i, nil
}
<|endoftext|>


<commit_before>package terraform

import (
	"encoding/json"
	"fmt"
	. "github.com/infradash/dash/pkg/dash"
	"github.com/qorio/maestro/pkg/template"
	. "gopkg.in/check.v1"
	"io/ioutil"
	"os"
	"testing"
)

var (
	tf_config_path     = os.TempDir() + "/tf_config.template"
	zk_template_path   = os.TempDir() + "/zk_conf.json"
	kf_template_path   = os.TempDir() + "/kf_conf.properties"
	kf_properties_path = os.TempDir() + "/kafka.properties"

	tf_config = fmt.Sprintf(`
{
     "ensemble":[ { "ip":"10.40.0.1" }, { "ip":"10.40.0.2" }, { "ip":"10.40.0.3" } ],
     "zookeeper" : {
         "template":"%s",
         "endpoint":"%s"
     },
     "kafka": {
         "template":"%s",
         "endpoint":"%s"
     }
}
`, "file://"+zk_template_path, "http://requestb.in/19qtk3u1", "file://"+kf_template_path, "file://"+kf_properties_path)
)

const (
	zk_config = `
{
      "zookeeperInstallDirectory":"/usr/local/zookeeper",
      "zookeeperDataDirectory":"/var/zookeeper",
      "zookeeperLogDirectory":"",
      "logIndexDirectory":"",
      "autoManageInstancesSettlingPeriodMs":"180000",
      "autoManageInstancesFixedEnsembleSize":"0",
      "autoManageInstancesApplyAllAtOnce":"1",
      "observerThreshold":"999",
      "serversSpec":"{{ zk_servers_spec }}",
      "javaEnvironment":"",
      "log4jProperties":"",
      "clientPort":"2181",
      "connectPort":"2888",
      "electionPort":"3888",
      "checkMs":"30000",
      "cleanupPeriodMs":"43200000",
      "cleanupMaxFiles":"3",
      "backupPeriodMs":"60000",
      "backupMaxStoreMs":"86400000",
      "autoManageInstances":"0",
      "zooCfgExtra":{
          "syncLimit":"5",
	  "tickTime":"2000",
	  "initLimit":"10"
       },
      "backupExtra":{},
      "serverId":-1
}
`

	kf_properties = `
    broker.id={{server_id}}
    port=9093
    log.dir=/tmp/kafka-logs
    zookeeper.connect={{zk_hosts}}
`
)

func TestTerraform(t *testing.T) { TestingT(t) }

type TestSuiteTerraform struct {
	zk_json_url       string
	kf_properties_url string
	tf_config_url     string
}

var _ = Suite(&TestSuiteTerraform{})

func (suite *TestSuiteTerraform) SetUpSuite(c *C) {
	err := ioutil.WriteFile(zk_template_path, []byte(zk_config), 0777)
	c.Assert(err, Equals, nil)
	suite.zk_json_url = "file://" + zk_template_path

	err = ioutil.WriteFile(kf_template_path, []byte(kf_properties), 0777)
	c.Assert(err, Equals, nil)
	suite.kf_properties_url = "file://" + kf_template_path

	err = ioutil.WriteFile(tf_config_path, []byte(tf_config), 0777)
	c.Assert(err, Equals, nil)
	suite.tf_config_url = "file://" + tf_config_path
}

func (suite *TestSuiteTerraform) TearDownSuite(c *C) {
}

func (suite *TestSuiteTerraform) TestApplyConfigJSONTemplate(c *C) {

	config := &TerraformConfig{
		Ensemble: []Server{
			Server{Ip: "10.40.0.1"},
			Server{Ip: "10.40.0.2"},
			Server{Ip: "10.40.0.3"},
		},
		Zookeeper: &Config{
			Template: Url(suite.zk_json_url),
		},
	}

	{
		t := &Terraform{
			Ip:              "10.40.0.1",
			TerraformConfig: *config,
		}

		j, err := config.Zookeeper.execute_template(t.AuthToken, t, t.template_funcs())
		c.Assert(err, Equals, nil)

		m := map[string]interface{}{}

		err = json.Unmarshal(j, &m)
		c.Assert(err, Equals, nil)
		c.Assert(m["serversSpec"], Equals, "S:1:0.0.0.0,S:2:10.40.0.2,S:3:10.40.0.3")
	}

	{
		t := &Terraform{
			Ip:              "10.40.0.2",
			TerraformConfig: *config,
		}

		j, err := config.Zookeeper.execute_template(t.AuthToken, t, t.template_funcs())
		c.Assert(err, Equals, nil)

		m := map[string]interface{}{}

		err = json.Unmarshal(j, &m)
		c.Assert(err, Equals, nil)
		c.Assert(m["serversSpec"], Equals, "S:1:10.40.0.1,S:2:0.0.0.0,S:3:10.40.0.3")
	}

	{
		t := &Terraform{
			Ip:              "10.40.0.3",
			TerraformConfig: *config,
		}

		j, err := config.Zookeeper.execute_template(t.AuthToken, t, t.template_funcs())
		c.Assert(err, Equals, nil)

		m := map[string]interface{}{}

		err = json.Unmarshal(j, &m)
		c.Assert(err, Equals, nil)
		c.Assert(m["serversSpec"], Equals, "S:1:10.40.0.1,S:2:10.40.0.2,S:3:0.0.0.0")
	}
}

func (suite *TestSuiteTerraform) TestExecute(c *C) {

	t := &Terraform{
		Ip: "10.40.0.1",
		Initializer: &ConfigLoader{
			ConfigUrl: suite.tf_config_url,
		},
	}

	err := t.Run()
	c.Assert(err, Equals, nil)

	properties, _, err := template.FetchUrl(t.Kafka.Endpoint.String(), nil)
	c.Assert(err, Equals, nil)
	c.Log(properties)

	_, err = os.Open(kf_properties_path)
	c.Assert(err, Equals, nil)
}
<commit_msg>Disable terraform test where there's no exhibitor backend<commit_after>package terraform

import (
	"encoding/json"
	"fmt"
	. "github.com/infradash/dash/pkg/dash"
	"github.com/qorio/maestro/pkg/template"
	. "gopkg.in/check.v1"
	"io/ioutil"
	"os"
	"testing"
)

var (
	tf_config_path     = os.TempDir() + "/tf_config.template"
	zk_template_path   = os.TempDir() + "/zk_conf.json"
	kf_template_path   = os.TempDir() + "/kf_conf.properties"
	kf_properties_path = os.TempDir() + "/kafka.properties"

	tf_config = fmt.Sprintf(`
{
     "ensemble":[ { "ip":"10.40.0.1" }, { "ip":"10.40.0.2" }, { "ip":"10.40.0.3" } ],
     "zookeeper" : {
         "template":"%s",
         "endpoint":"%s"
     },
     "kafka": {
         "template":"%s",
         "endpoint":"%s"
     }
}
`, "file://"+zk_template_path, "http://requestb.in/19qtk3u1", "file://"+kf_template_path, "file://"+kf_properties_path)
)

const (
	zk_config = `
{
      "zookeeperInstallDirectory":"/usr/local/zookeeper",
      "zookeeperDataDirectory":"/var/zookeeper",
      "zookeeperLogDirectory":"",
      "logIndexDirectory":"",
      "autoManageInstancesSettlingPeriodMs":"180000",
      "autoManageInstancesFixedEnsembleSize":"0",
      "autoManageInstancesApplyAllAtOnce":"1",
      "observerThreshold":"999",
      "serversSpec":"{{ zk_servers_spec }}",
      "javaEnvironment":"",
      "log4jProperties":"",
      "clientPort":"2181",
      "connectPort":"2888",
      "electionPort":"3888",
      "checkMs":"30000",
      "cleanupPeriodMs":"43200000",
      "cleanupMaxFiles":"3",
      "backupPeriodMs":"60000",
      "backupMaxStoreMs":"86400000",
      "autoManageInstances":"0",
      "zooCfgExtra":{
          "syncLimit":"5",
	  "tickTime":"2000",
	  "initLimit":"10"
       },
      "backupExtra":{},
      "serverId":-1
}
`

	kf_properties = `
    broker.id={{server_id}}
    port=9093
    log.dir=/tmp/kafka-logs
    zookeeper.connect={{zk_hosts}}
`
)

func TestTerraform(t *testing.T) { TestingT(t) }

type TestSuiteTerraform struct {
	zk_json_url       string
	kf_properties_url string
	tf_config_url     string
}

var _ = Suite(&TestSuiteTerraform{})

func (suite *TestSuiteTerraform) SetUpSuite(c *C) {
	err := ioutil.WriteFile(zk_template_path, []byte(zk_config), 0777)
	c.Assert(err, Equals, nil)
	suite.zk_json_url = "file://" + zk_template_path

	err = ioutil.WriteFile(kf_template_path, []byte(kf_properties), 0777)
	c.Assert(err, Equals, nil)
	suite.kf_properties_url = "file://" + kf_template_path

	err = ioutil.WriteFile(tf_config_path, []byte(tf_config), 0777)
	c.Assert(err, Equals, nil)
	suite.tf_config_url = "file://" + tf_config_path
}

func (suite *TestSuiteTerraform) TearDownSuite(c *C) {
}

func (suite *TestSuiteTerraform) TestApplyConfigJSONTemplate(c *C) {

	config := &TerraformConfig{
		Ensemble: []Server{
			Server{Ip: "10.40.0.1"},
			Server{Ip: "10.40.0.2"},
			Server{Ip: "10.40.0.3"},
		},
		Zookeeper: &Config{
			Template: Url(suite.zk_json_url),
		},
	}

	{
		t := &Terraform{
			Ip:              "10.40.0.1",
			TerraformConfig: *config,
		}

		j, err := config.Zookeeper.execute_template(t.AuthToken, t, t.template_funcs())
		c.Assert(err, Equals, nil)

		m := map[string]interface{}{}

		err = json.Unmarshal(j, &m)
		c.Assert(err, Equals, nil)
		c.Assert(m["serversSpec"], Equals, "S:1:0.0.0.0,S:2:10.40.0.2,S:3:10.40.0.3")
	}

	{
		t := &Terraform{
			Ip:              "10.40.0.2",
			TerraformConfig: *config,
		}

		j, err := config.Zookeeper.execute_template(t.AuthToken, t, t.template_funcs())
		c.Assert(err, Equals, nil)

		m := map[string]interface{}{}

		err = json.Unmarshal(j, &m)
		c.Assert(err, Equals, nil)
		c.Assert(m["serversSpec"], Equals, "S:1:10.40.0.1,S:2:0.0.0.0,S:3:10.40.0.3")
	}

	{
		t := &Terraform{
			Ip:              "10.40.0.3",
			TerraformConfig: *config,
		}

		j, err := config.Zookeeper.execute_template(t.AuthToken, t, t.template_funcs())
		c.Assert(err, Equals, nil)

		m := map[string]interface{}{}

		err = json.Unmarshal(j, &m)
		c.Assert(err, Equals, nil)
		c.Assert(m["serversSpec"], Equals, "S:1:10.40.0.1,S:2:10.40.0.2,S:3:0.0.0.0")
	}
}

func (suite *TestSuiteTerraform) DISABLED_TestExecute(c *C) {

	t := &Terraform{
		Ip: "10.40.0.1",
		Initializer: &ConfigLoader{
			ConfigUrl: suite.tf_config_url,
		},
	}

	err := t.Run()
	c.Assert(err, Equals, nil)

	properties, _, err := template.FetchUrl(t.Kafka.Endpoint.String(), nil)
	c.Assert(err, Equals, nil)
	c.Log(properties)

	_, err = os.Open(kf_properties_path)
	c.Assert(err, Equals, nil)
}
<|endoftext|>