<commit_before>// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// code for the "vespa-set-node-state" command
// Author: arnej

// utilities to get and manipulate node states in a storage cluster
package clusterstate

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"

	"github.com/fatih/color"
	"github.com/spf13/cobra"
	"github.com/vespa-engine/vespa/client/go/build"
)

const (
	usageSetNodeState = `vespa-set-node-state  [Options] <Wanted State> [Description]

Arguments:
 Wanted State : User state to set. This must be one of up, down, maintenance or retired.
 Description  : Give a reason for why you are altering the user state, which will show up in various admin tools. (Use double quotes to give a reason
                with whitespace in it)`

	longSetNodeState = `Set the user state of a node. This will set the generated state to the user state if the user state is "better" than the generated state that would
have been created if the user state was up. For instance, a node that is currently in initializing state can be forced into down state, while a node
that is currently down can not be forced into retired state, but can be forced into maintenance state.`
)

func NewSetNodeStateCmd() *cobra.Command {
	var (
		curOptions Options
	)
	cmd := &cobra.Command{
		Use:     usageSetNodeState,
		Short:   "vespa-set-node-state [Options] <Wanted State> [Description]",
		Long:    longSetNodeState,
		Version: build.Version,
		Args: func(cmd *cobra.Command, args []string) error {
			switch {
			case len(args) < 1:
				return fmt.Errorf("Missing <Wanted State>")
			case len(args) > 2:
				return fmt.Errorf("Too many arguments, maximum is 2")
			}
			_, err := knownState(args[0])
			return err
		},
		CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
		Run: func(cmd *cobra.Command, args []string) {
			runSetNodeState(&curOptions, args)
		},
	}
	addCommonOptions(cmd, &curOptions)
	cmd.Flags().BoolVarP(&curOptions.NoWait, "no-wait", "n", false,
		"Do not wait for node state changes to be visible in the cluster before returning.")
	cmd.Flags().BoolVarP(&curOptions.SafeMode, "safe", "a", false,
		"Only carries out state changes if deemed safe by the cluster controller.")
	cmd.Flags().StringVarP(&curOptions.NodeType, "type", "t", "",
		"Node type - can either be 'storage' or 'distributor'. If not specified, the operation will set state for both types.")
	cmd.Flags().IntVarP(&curOptions.NodeIndex, "index", "i", OnlyLocalNode,
		"Node index. If not specified, all nodes found running on this host will be used.")
	cmd.Flags().MarkHidden("no-wait")
	return cmd
}

func runSetNodeState(opts *Options, args []string) {
	if opts.Silent {
		currentOutputLevel = levelNone
	}
	if opts.NoColors || os.Getenv("TERM") == "" {
		color.NoColor = true
	}
	wanted, err := knownState(args[0])
	if err != nil {
		panic(err)
	}
	reason := ""
	if len(args) > 1 {
		reason = args[1]
	}
	if !opts.Force && wanted == StateMaintenance && opts.NodeType != "storage" {
		fmt.Println(color.HiYellowString(
			`Setting the distributor to maintenance mode may have severe consequences for feeding!
Please specify -t storage to only set the storage node to maintenance mode, or -f to override this error.`))
		return
	}
	m := detectModel(opts)
	sss := m.findSelectedServices(opts)
	if len(sss) == 0 {
		fmt.Println(color.HiYellowString("Attempted setting of user state for no nodes"))
		return
	}
	for _, s := range sss {
		_, cc := m.getClusterState(s.cluster)
		cc.setNodeUserState(s, wanted, reason, opts)
	}
}

type SetNodeStateJson struct {
	State struct {
		User StateAndReason `json:"user"`
	} `json:"state"`
	ResponseWait string `json:"response-wait,omitempty"`
	Condition    string `json:"condition,omitempty"`
}

func splitResultCode(s string) (int, string) {
	for idx := len(s); idx > 0; {
		idx--
		if s[idx] == '\n' {
			resCode, err := strconv.Atoi(s[idx+1:])
			if err != nil {
				return -1, s
			}
			return resCode, s[:idx]
		}
	}
	return -1, s
}

func (cc *ClusterControllerSpec) setNodeUserState(s serviceSpec, wanted KnownState, reason string, opts *Options) error {
	var request SetNodeStateJson
	request.State.User.State = string(wanted)
	request.State.User.Reason = reason
	if opts.NoWait {
		request.ResponseWait = "no-wait"
	}
	if opts.SafeMode {
		request.Condition = "safe"
	}
	jsonBytes, err := json.Marshal(request)
	if err != nil {
		panic(err)
	}
	url := fmt.Sprintf("http://%s:%d/cluster/v2/%s/%s/%d",
		cc.host, cc.port,
		s.cluster, s.serviceType, s.index)
	result, err := curlPost(url, jsonBytes)
	resCode, output := splitResultCode(result)
	if resCode < 200 || resCode >= 300 {
		fmt.Println(color.HiYellowString("failed with HTTP code %d", resCode))
	}
	fmt.Println(output)
	return err
}
<commit_msg>minor tweaks<commit_after>// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// code for the "vespa-set-node-state" command
// Author: arnej

// utilities to get and manipulate node states in a storage cluster
package clusterstate

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"

	"github.com/fatih/color"
	"github.com/spf13/cobra"
	"github.com/vespa-engine/vespa/client/go/build"
)

const (
	usageSetNodeState = `vespa-set-node-state  [Options] <Wanted State> [Description]

Arguments:
 Wanted State : User state to set. This must be one of up, down, maintenance or retired.
 Description  : Give a reason for why you are altering the user state, which will show up in various admin tools. (Use double quotes to give a reason
                with whitespace in it)`

	longSetNodeState = `Set the user state of a node. This will set the generated state to the user state if the user state is "better" than the generated state that would
have been created if the user state was up. For instance, a node that is currently in initializing state can be forced into down state, while a node
that is currently down can not be forced into retired state, but can be forced into maintenance state.`
)

func NewSetNodeStateCmd() *cobra.Command {
	var (
		curOptions Options
	)
	cmd := &cobra.Command{
		Use:     usageSetNodeState,
		Short:   "vespa-set-node-state [Options] <Wanted State> [Description]",
		Long:    longSetNodeState,
		Version: build.Version,
		Args: func(cmd *cobra.Command, args []string) error {
			switch {
			case len(args) < 1:
				return fmt.Errorf("Missing <Wanted State>")
			case len(args) > 2:
				return fmt.Errorf("Too many arguments, maximum is 2")
			}
			_, err := knownState(args[0])
			return err
		},
		CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
		Run: func(cmd *cobra.Command, args []string) {
			runSetNodeState(&curOptions, args)
		},
	}
	addCommonOptions(cmd, &curOptions)
	cmd.Flags().BoolVarP(&curOptions.Force, "force", "f", false,
		"Force execution")
	cmd.Flags().BoolVarP(&curOptions.NoWait, "no-wait", "n", false,
		"Do not wait for node state changes to be visible in the cluster before returning.")
	cmd.Flags().BoolVarP(&curOptions.SafeMode, "safe", "a", false,
		"Only carries out state changes if deemed safe by the cluster controller.")
	cmd.Flags().StringVarP(&curOptions.NodeType, "type", "t", "",
		"Node type - can either be 'storage' or 'distributor'. If not specified, the operation will set state for both types.")
	cmd.Flags().IntVarP(&curOptions.NodeIndex, "index", "i", OnlyLocalNode,
		"Node index. If not specified, all nodes found running on this host will be used.")
	cmd.Flags().MarkHidden("no-wait")
	return cmd
}

func runSetNodeState(opts *Options, args []string) {
	if opts.Silent {
		currentOutputLevel = levelNone
	}
	if opts.NoColors || os.Getenv("TERM") == "" {
		color.NoColor = true
	}
	wanted, err := knownState(args[0])
	if err != nil {
		panic(err)
	}
	reason := ""
	if len(args) > 1 {
		reason = args[1]
	}
	if !opts.Force && wanted == StateMaintenance && opts.NodeType != "storage" {
		fmt.Println(color.HiYellowString(
			`Setting the distributor to maintenance mode may have severe consequences for feeding!
Please specify -t storage to only set the storage node to maintenance mode, or -f to override this error.`))
		return
	}
	m := detectModel(opts)
	sss := m.findSelectedServices(opts)
	if len(sss) == 0 {
		fmt.Println(color.HiYellowString("Attempted setting of user state for no nodes"))
		return
	}
	for _, s := range sss {
		_, cc := m.getClusterState(s.cluster)
		cc.setNodeUserState(s, wanted, reason, opts)
	}
}

type SetNodeStateJson struct {
	State struct {
		User StateAndReason `json:"user"`
	} `json:"state"`
	ResponseWait string `json:"response-wait,omitempty"`
	Condition    string `json:"condition,omitempty"`
}

func splitResultCode(s string) (int, string) {
	for idx := len(s); idx > 0; {
		idx--
		if s[idx] == '\n' {
			resCode, err := strconv.Atoi(s[idx+1:])
			if err != nil {
				return -1, s
			}
			return resCode, s[:idx]
		}
	}
	return -1, s
}

func (cc *ClusterControllerSpec) setNodeUserState(s serviceSpec, wanted KnownState, reason string, opts *Options) error {
	var request SetNodeStateJson
	request.State.User.State = string(wanted)
	request.State.User.Reason = reason
	if opts.NoWait {
		request.ResponseWait = "no-wait"
	}
	if opts.SafeMode {
		request.Condition = "safe"
	}
	jsonBytes, err := json.Marshal(request)
	if err != nil {
		panic(err)
	}
	url := fmt.Sprintf("http://%s:%d/cluster/v2/%s/%s/%d",
		cc.host, cc.port,
		s.cluster, s.serviceType, s.index)
	result, err := curlPost(url, jsonBytes)
	resCode, output := splitResultCode(result)
	if resCode < 200 || resCode >= 300 {
		fmt.Println(color.HiYellowString("failed with HTTP code %d", resCode))
		fmt.Println(output)
	} else {
		fmt.Print(output, "OK\n")
	}
	return err
}
<|endoftext|>


<commit_before>package hipchat

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"reflect"
	"testing"
)

var (
	mux    *http.ServeMux
	server *httptest.Server
	client *Client
)

// setup sets up a test HTTP server and a hipchat.Client configured to talk
// to that test server.
// Tests should register handlers on mux which provide mock responses for
// the API method being tested.
func setup() {
	// test server
	mux = http.NewServeMux()
	server = httptest.NewServer(mux)

	// github client configured to use test server
	client = NewClient("AuthToken")
	url, _ := url.Parse(server.URL)
	client.baseURL = url
}

// teardown closes the test HTTP server.
func teardown() {
	server.Close()
}

func TestNewClient(t *testing.T) {
	authToken := "AuthToken"

	c := NewClient(authToken)

	if c.authToken != authToken {
		t.Errorf("NewClient authToken %s, want %s", c.authToken, authToken)
	}
	if c.baseURL.String() != defaultBaseURL {
		t.Error("NewClient baseURL %s, want %s", c.baseURL.String(), defaultBaseURL)
	}
}

func TestNewRequest(t *testing.T) {
	c := NewClient("AuthToken")

	inURL, outURL := "foo", defaultBaseURL+"foo"
	inBody, outBody := &NotificationRequest{Message: "Hello"}, `{"message":"Hello"}`+"\n"
	r, _ := c.NewRequest("GET", inURL, inBody)

	if r.URL.String() != outURL {
		t.Errorf("NewRequest URL %s, want %s", r.URL.String(), outURL)
	}
	body, _ := ioutil.ReadAll(r.Body)
	if string(body) != outBody {
		t.Errorf("NewRequest body %s, want %s", body, outBody)
	}
	authorization := r.Header.Get("Authorization")
	if authorization != "Bearer "+c.authToken {
		t.Errorf("NewRequest authorization header %s, want %s", authorization, "Bearer "+c.authToken)
	}
	contentType := r.Header.Get("Content-Type")
	if contentType != "application/json" {
		t.Errorf("NewRequest Content-Type header %s, want application/json", contentType)
	}
}

func TestDo(t *testing.T) {
	setup()
	defer teardown()

	type foo struct {
		Bar int
	}
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if m := "GET"; m != r.Method {
			t.Errorf("Request method = %v, want %v", r.Method, m)
		}
		fmt.Fprintf(w, `{"Bar":1}`)
	})
	req, _ := client.NewRequest("GET", "/", nil)
	body := new(foo)

	_, err := client.Do(req, body)

	if err != nil {
		t.Fatal(err)
	}
	want := &foo{Bar: 1}
	if !reflect.DeepEqual(body, want) {
		t.Errorf("Response body = %v, want %v", body, want)
	}
}
<commit_msg>go vet fix<commit_after>package hipchat

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"reflect"
	"testing"
)

var (
	mux    *http.ServeMux
	server *httptest.Server
	client *Client
)

// setup sets up a test HTTP server and a hipchat.Client configured to talk
// to that test server.
// Tests should register handlers on mux which provide mock responses for
// the API method being tested.
func setup() {
	// test server
	mux = http.NewServeMux()
	server = httptest.NewServer(mux)

	// github client configured to use test server
	client = NewClient("AuthToken")
	url, _ := url.Parse(server.URL)
	client.baseURL = url
}

// teardown closes the test HTTP server.
func teardown() {
	server.Close()
}

func TestNewClient(t *testing.T) {
	authToken := "AuthToken"

	c := NewClient(authToken)

	if c.authToken != authToken {
		t.Errorf("NewClient authToken %s, want %s", c.authToken, authToken)
	}
	if c.baseURL.String() != defaultBaseURL {
		t.Errorf("NewClient baseURL %s, want %s", c.baseURL.String(), defaultBaseURL)
	}
}

func TestNewRequest(t *testing.T) {
	c := NewClient("AuthToken")

	inURL, outURL := "foo", defaultBaseURL+"foo"
	inBody, outBody := &NotificationRequest{Message: "Hello"}, `{"message":"Hello"}`+"\n"
	r, _ := c.NewRequest("GET", inURL, inBody)

	if r.URL.String() != outURL {
		t.Errorf("NewRequest URL %s, want %s", r.URL.String(), outURL)
	}
	body, _ := ioutil.ReadAll(r.Body)
	if string(body) != outBody {
		t.Errorf("NewRequest body %s, want %s", body, outBody)
	}
	authorization := r.Header.Get("Authorization")
	if authorization != "Bearer "+c.authToken {
		t.Errorf("NewRequest authorization header %s, want %s", authorization, "Bearer "+c.authToken)
	}
	contentType := r.Header.Get("Content-Type")
	if contentType != "application/json" {
		t.Errorf("NewRequest Content-Type header %s, want application/json", contentType)
	}
}

func TestDo(t *testing.T) {
	setup()
	defer teardown()

	type foo struct {
		Bar int
	}
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if m := "GET"; m != r.Method {
			t.Errorf("Request method = %v, want %v", r.Method, m)
		}
		fmt.Fprintf(w, `{"Bar":1}`)
	})
	req, _ := client.NewRequest("GET", "/", nil)
	body := new(foo)

	_, err := client.Do(req, body)

	if err != nil {
		t.Fatal(err)
	}
	want := &foo{Bar: 1}
	if !reflect.DeepEqual(body, want) {
		t.Errorf("Response body = %v, want %v", body, want)
	}
}
<|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2019 Red Hat, Inc.
 *
 */

package tests_test

import (
	"encoding/json"
	"fmt"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	k8sv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	v1 "kubevirt.io/client-go/api/v1"
	"kubevirt.io/client-go/kubecli"
	virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
	"kubevirt.io/kubevirt/tests"
)

var _ = Describe("SecurityFeatures", func() {
	tests.FlagParse()

	var originalKubeVirtConfig *k8sv1.ConfigMap
	virtClient, err := kubecli.GetKubevirtClient()
	tests.PanicOnError(err)

	tests.BeforeAll(func() {
		originalKubeVirtConfig, err = virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Get("kubevirt-config", metav1.GetOptions{})
		if err != nil && !errors.IsNotFound(err) {
			Expect(err).ToNot(HaveOccurred())
		}

		if errors.IsNotFound(err) {
			// create an empty kubevirt-config configmap if none exists.
			cfgMap := &k8sv1.ConfigMap{
				ObjectMeta: metav1.ObjectMeta{Name: "kubevirt-config"},
				Data: map[string]string{
					"feature-gates": "",
				},
			}

			originalKubeVirtConfig, err = virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Create(cfgMap)
			Expect(err).ToNot(HaveOccurred())
		}
	})

	AfterEach(func() {
		curKubeVirtConfig, err := virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Get("kubevirt-config", metav1.GetOptions{})
		Expect(err).ToNot(HaveOccurred())

		// if revision changed, revert ConfigMap
		if curKubeVirtConfig.ResourceVersion != originalKubeVirtConfig.ResourceVersion {
			// Add Spec Patch
			newData, err := json.Marshal(originalKubeVirtConfig.Data)
			Expect(err).ToNot(HaveOccurred())
			data := fmt.Sprintf(`[{ "op": "replace", "path": "/data", "value": %s }]`, string(newData))

			originalKubeVirtConfig, err = virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Patch("kubevirt-config", types.JSONPatchType, []byte(data))
			Expect(err).ToNot(HaveOccurred())

			// Allow time for virt-controller's ConfigMap cache to sync
			time.Sleep(3 * time.Second)
		}

	})

	Context("Check virt-launcher securityContext", func() {

		var container k8sv1.Container
		var vmi *v1.VirtualMachineInstance

		Context("With selinuxLauncherType undefined", func() {
			BeforeEach(func() {
				kubeVirtConfig, err := virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Get("kubevirt-config", metav1.GetOptions{})
				Expect(err).ToNot(HaveOccurred())
				// delete selinuxLauncherType if it's set
				_, ok := kubeVirtConfig.Data[virtconfig.SELinuxLauncherTypeKey]
				if ok {
					delete(kubeVirtConfig.Data, virtconfig.SELinuxLauncherTypeKey)

					newData, err := json.Marshal(kubeVirtConfig.Data)
					Expect(err).ToNot(HaveOccurred())
					data := fmt.Sprintf(`[{ "op": "replace", "path": "/data", "value": %s }]`, string(newData))

					kubeVirtConfig, err = virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Patch("kubevirt-config", types.JSONPatchType, []byte(data))
					Expect(err).ToNot(HaveOccurred())

					// Allow time for virt-controller's ConfigMap cache to sync
					time.Sleep(3 * time.Second)
				}

				vmi = tests.NewRandomVMIWithEphemeralDiskAndUserdata(tests.ContainerDiskFor(tests.ContainerDiskCirros), "#!/bin/bash\necho 'hello'\n")
			})

			It("[test_id:2953]Ensure virt-launcher pod securityContext type is not forced", func() {

				By("Starting a VirtualMachineInstance")
				vmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
				Expect(err).ToNot(HaveOccurred())
				tests.WaitForSuccessfulVMIStart(vmi)

				By("Check virt-launcher pod SecurityContext values")
				vmiPod := tests.GetRunningPodByVirtualMachineInstance(vmi, tests.NamespaceTestDefault)
				Expect(vmiPod.Spec.SecurityContext.SELinuxOptions).To(BeNil())
			})

			It("[test_id:2895]Make sure the virt-launcher pod is not priviledged", func() {

				By("Starting a VirtualMachineInstance")
				vmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
				Expect(err).ToNot(HaveOccurred())
				tests.WaitForSuccessfulVMIStart(vmi)

				By("Check virt-launcher pod SecurityContext values")
				vmiPod := tests.GetRunningPodByVirtualMachineInstance(vmi, tests.NamespaceTestDefault)
				for _, containerSpec := range vmiPod.Spec.Containers {
					if containerSpec.Name == "compute" {
						container = containerSpec
						break
					}
				}
				Expect(*container.SecurityContext.Privileged).To(BeFalse())
			})
		})

		Context("With selinuxLauncherType defined", func() {
			var superPrivilegedType string

			BeforeEach(func() {
				superPrivilegedType = "spc_t"
				kubeVirtConfig, err := virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Get("kubevirt-config", metav1.GetOptions{})
				Expect(err).ToNot(HaveOccurred())
				if kubeVirtConfig.Data[virtconfig.SELinuxLauncherTypeKey] != superPrivilegedType {
					kubeVirtConfig.Data[virtconfig.SELinuxLauncherTypeKey] = superPrivilegedType

					newData, err := json.Marshal(kubeVirtConfig.Data)
					Expect(err).ToNot(HaveOccurred())
					data := fmt.Sprintf(`[{ "op": "replace", "path": "/data", "value": %s }]`, string(newData))

					kubeVirtConfig, err = virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Patch("kubevirt-config", types.JSONPatchType, []byte(data))
					Expect(err).ToNot(HaveOccurred())

					// Allow time for virt-controller's ConfigMap cache to sync
					time.Sleep(3 * time.Second)
				}

				vmi = tests.NewRandomVMIWithEphemeralDisk(tests.ContainerDiskFor(tests.ContainerDiskAlpine))
			})

			It("[test_id:3738]Should honor custom SELinux type for virt-launcher", func() {
				By("Starting a New VMI")
				vmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
				Expect(err).ToNot(HaveOccurred())
				tests.WaitForSuccessfulVMIStart(vmi)

				By("Ensuring VMI is running by logging in")
				tests.WaitUntilVMIReady(vmi, tests.LoggedInAlpineExpecter)

				By("Fetching virt-launcher Pod")
				pod := tests.GetPodByVirtualMachineInstance(vmi, tests.NamespaceTestDefault)

				By("Verifying SELinux context contains custom type")
				Expect(pod.Spec.SecurityContext.SELinuxOptions.Type).To(Equal(superPrivilegedType))

				By("Deleting the VMI")
				err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Delete(vmi.Name, &metav1.DeleteOptions{})
				Expect(err).ToNot(HaveOccurred())
			})
		})
	})
})
<commit_msg>Fix the testid for Should honor custom SELinux type for virt-launcher<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2019 Red Hat, Inc.
 *
 */

package tests_test

import (
	"encoding/json"
	"fmt"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	k8sv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	v1 "kubevirt.io/client-go/api/v1"
	"kubevirt.io/client-go/kubecli"
	virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
	"kubevirt.io/kubevirt/tests"
)

var _ = Describe("SecurityFeatures", func() {
	tests.FlagParse()

	var originalKubeVirtConfig *k8sv1.ConfigMap
	virtClient, err := kubecli.GetKubevirtClient()
	tests.PanicOnError(err)

	tests.BeforeAll(func() {
		originalKubeVirtConfig, err = virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Get("kubevirt-config", metav1.GetOptions{})
		if err != nil && !errors.IsNotFound(err) {
			Expect(err).ToNot(HaveOccurred())
		}

		if errors.IsNotFound(err) {
			// create an empty kubevirt-config configmap if none exists.
			cfgMap := &k8sv1.ConfigMap{
				ObjectMeta: metav1.ObjectMeta{Name: "kubevirt-config"},
				Data: map[string]string{
					"feature-gates": "",
				},
			}

			originalKubeVirtConfig, err = virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Create(cfgMap)
			Expect(err).ToNot(HaveOccurred())
		}
	})

	AfterEach(func() {
		curKubeVirtConfig, err := virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Get("kubevirt-config", metav1.GetOptions{})
		Expect(err).ToNot(HaveOccurred())

		// if revision changed, revert ConfigMap
		if curKubeVirtConfig.ResourceVersion != originalKubeVirtConfig.ResourceVersion {
			// Add Spec Patch
			newData, err := json.Marshal(originalKubeVirtConfig.Data)
			Expect(err).ToNot(HaveOccurred())
			data := fmt.Sprintf(`[{ "op": "replace", "path": "/data", "value": %s }]`, string(newData))

			originalKubeVirtConfig, err = virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Patch("kubevirt-config", types.JSONPatchType, []byte(data))
			Expect(err).ToNot(HaveOccurred())

			// Allow time for virt-controller's ConfigMap cache to sync
			time.Sleep(3 * time.Second)
		}

	})

	Context("Check virt-launcher securityContext", func() {

		var container k8sv1.Container
		var vmi *v1.VirtualMachineInstance

		Context("With selinuxLauncherType undefined", func() {
			BeforeEach(func() {
				kubeVirtConfig, err := virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Get("kubevirt-config", metav1.GetOptions{})
				Expect(err).ToNot(HaveOccurred())
				// delete selinuxLauncherType if it's set
				_, ok := kubeVirtConfig.Data[virtconfig.SELinuxLauncherTypeKey]
				if ok {
					delete(kubeVirtConfig.Data, virtconfig.SELinuxLauncherTypeKey)

					newData, err := json.Marshal(kubeVirtConfig.Data)
					Expect(err).ToNot(HaveOccurred())
					data := fmt.Sprintf(`[{ "op": "replace", "path": "/data", "value": %s }]`, string(newData))

					kubeVirtConfig, err = virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Patch("kubevirt-config", types.JSONPatchType, []byte(data))
					Expect(err).ToNot(HaveOccurred())

					// Allow time for virt-controller's ConfigMap cache to sync
					time.Sleep(3 * time.Second)
				}

				vmi = tests.NewRandomVMIWithEphemeralDiskAndUserdata(tests.ContainerDiskFor(tests.ContainerDiskCirros), "#!/bin/bash\necho 'hello'\n")
			})

			It("[test_id:2953]Ensure virt-launcher pod securityContext type is not forced", func() {

				By("Starting a VirtualMachineInstance")
				vmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
				Expect(err).ToNot(HaveOccurred())
				tests.WaitForSuccessfulVMIStart(vmi)

				By("Check virt-launcher pod SecurityContext values")
				vmiPod := tests.GetRunningPodByVirtualMachineInstance(vmi, tests.NamespaceTestDefault)
				Expect(vmiPod.Spec.SecurityContext.SELinuxOptions).To(BeNil())
			})

			It("[test_id:2895]Make sure the virt-launcher pod is not priviledged", func() {

				By("Starting a VirtualMachineInstance")
				vmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
				Expect(err).ToNot(HaveOccurred())
				tests.WaitForSuccessfulVMIStart(vmi)

				By("Check virt-launcher pod SecurityContext values")
				vmiPod := tests.GetRunningPodByVirtualMachineInstance(vmi, tests.NamespaceTestDefault)
				for _, containerSpec := range vmiPod.Spec.Containers {
					if containerSpec.Name == "compute" {
						container = containerSpec
						break
					}
				}
				Expect(*container.SecurityContext.Privileged).To(BeFalse())
			})
		})

		Context("With selinuxLauncherType defined", func() {
			var superPrivilegedType string

			BeforeEach(func() {
				superPrivilegedType = "spc_t"
				kubeVirtConfig, err := virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Get("kubevirt-config", metav1.GetOptions{})
				Expect(err).ToNot(HaveOccurred())
				if kubeVirtConfig.Data[virtconfig.SELinuxLauncherTypeKey] != superPrivilegedType {
					kubeVirtConfig.Data[virtconfig.SELinuxLauncherTypeKey] = superPrivilegedType

					newData, err := json.Marshal(kubeVirtConfig.Data)
					Expect(err).ToNot(HaveOccurred())
					data := fmt.Sprintf(`[{ "op": "replace", "path": "/data", "value": %s }]`, string(newData))

					kubeVirtConfig, err = virtClient.CoreV1().ConfigMaps(tests.KubeVirtInstallNamespace).Patch("kubevirt-config", types.JSONPatchType, []byte(data))
					Expect(err).ToNot(HaveOccurred())

					// Allow time for virt-controller's ConfigMap cache to sync
					time.Sleep(3 * time.Second)
				}

				vmi = tests.NewRandomVMIWithEphemeralDisk(tests.ContainerDiskFor(tests.ContainerDiskAlpine))
			})

			It("[test_id:3787]Should honor custom SELinux type for virt-launcher", func() {
				By("Starting a New VMI")
				vmi, err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
				Expect(err).ToNot(HaveOccurred())
				tests.WaitForSuccessfulVMIStart(vmi)

				By("Ensuring VMI is running by logging in")
				tests.WaitUntilVMIReady(vmi, tests.LoggedInAlpineExpecter)

				By("Fetching virt-launcher Pod")
				pod := tests.GetPodByVirtualMachineInstance(vmi, tests.NamespaceTestDefault)

				By("Verifying SELinux context contains custom type")
				Expect(pod.Spec.SecurityContext.SELinuxOptions.Type).To(Equal(superPrivilegedType))

				By("Deleting the VMI")
				err = virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Delete(vmi.Name, &metav1.DeleteOptions{})
				Expect(err).ToNot(HaveOccurred())
			})
		})
	})
})
<|endoftext|>


<commit_before>package aci

/*

Image Layout

The on-disk layout of an app container is straightforward.
It includes a rootfs with all of the files that will exist in the root of the app and a manifest describing the image.
The layout MUST contain an image manifest.

/manifest
/rootfs/
/rootfs/usr/bin/mysql

*/

import (
	"archive/tar"
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/appc/spec/schema"
)

const (
	// Path to manifest file inside the layout
	ManifestFile = "manifest"
	// Path to rootfs directory inside the layout
	RootfsDir = "rootfs"
)

var (
	ErrNoRootFS   = errors.New("no rootfs found in layout")
	ErrNoManifest = errors.New("no image manifest found in layout")
)

// ValidateLayout takes a directory and validates that the layout of the directory
// matches that expected by the Application Container Image format.
// If any errors are encountered during the validation, it will abort and
// return the first one.
func ValidateLayout(dir string) error {
	fi, err := os.Stat(dir)
	if err != nil {
		return fmt.Errorf("error accessing layout: %v", err)
	}
	if !fi.IsDir() {
		return fmt.Errorf("given path %q is not a directory", dir)
	}
	var flist []string
	var imOK, rfsOK bool
	var im io.Reader
	walkLayout := func(fpath string, fi os.FileInfo, err error) error {
		rpath, err := filepath.Rel(dir, fpath)
		if err != nil {
			return err
		}
		name := filepath.Base(rpath)
		switch name {
		case ".":
		case ManifestFile:
			im, err = os.Open(fpath)
			if err != nil {
				return err
			}
			imOK = true
		case RootfsDir:
			if !fi.IsDir() {
				return errors.New("rootfs is not a directory")
			}
			rfsOK = true
		default:
			flist = append(flist, rpath)
		}
		return nil
	}
	if err := filepath.Walk(dir, walkLayout); err != nil {
		return err
	}
	return validate(imOK, im, rfsOK, flist)
}

// ValidateLayout takes a *tar.Reader and validates that the layout of the
// filesystem the reader encapsulates matches that expected by the
// Application Container Image format.  If any errors are encountered during
// the validation, it will abort and return the first one.
func ValidateArchive(tr *tar.Reader) error {
	var fseen map[string]bool = make(map[string]bool)
	var imOK, rfsOK bool
	var im bytes.Buffer
Tar:
	for {
		hdr, err := tr.Next()
		switch {
		case err == nil:
		case err == io.EOF:
			break Tar
		default:
			return err
		}
		name := filepath.Clean(hdr.Name)
		switch name {
		case ".":
		case ManifestFile:
			_, err := io.Copy(&im, tr)
			if err != nil {
				return err
			}
			imOK = true
		case RootfsDir:
			if !hdr.FileInfo().IsDir() {
				return fmt.Errorf("rootfs is not a directory")
			}
			rfsOK = true
		default:
			if _, seen := fseen[name]; seen {
				return fmt.Errorf("duplicate file entry in archive: %s", name)
			}
			fseen[name] = true
		}
	}
	var flist []string
	for key := range fseen {
		flist = append(flist, key)
	}
	return validate(imOK, &im, rfsOK, flist)
}

func validate(imOK bool, im io.Reader, rfsOK bool, files []string) error {
	defer func() {
		if rc, ok := im.(io.Closer); ok {
			rc.Close()
		}
	}()
	if !imOK {
		return ErrNoManifest
	}
	if !rfsOK {
		return ErrNoRootFS
	}
	b, err := ioutil.ReadAll(im)
	if err != nil {
		return fmt.Errorf("error reading image manifest: %v", err)
	}
	var a schema.ImageManifest
	if err := a.UnmarshalJSON(b); err != nil {
		return fmt.Errorf("image manifest validation failed: %v", err)
	}
	for _, f := range files {
		if !strings.HasPrefix(f, "rootfs") {
			return fmt.Errorf("unrecognized file path in layout: %q", f)
		}
	}
	return nil
}
<commit_msg>aci: fix mismatching documentation<commit_after>package aci

/*

Image Layout

The on-disk layout of an app container is straightforward.
It includes a rootfs with all of the files that will exist in the root of the app and a manifest describing the image.
The layout MUST contain an image manifest.

/manifest
/rootfs/
/rootfs/usr/bin/mysql

*/

import (
	"archive/tar"
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/appc/spec/schema"
)

const (
	// Path to manifest file inside the layout
	ManifestFile = "manifest"
	// Path to rootfs directory inside the layout
	RootfsDir = "rootfs"
)

var (
	ErrNoRootFS   = errors.New("no rootfs found in layout")
	ErrNoManifest = errors.New("no image manifest found in layout")
)

// ValidateLayout takes a directory and validates that the layout of the directory
// matches that expected by the Application Container Image format.
// If any errors are encountered during the validation, it will abort and
// return the first one.
func ValidateLayout(dir string) error {
	fi, err := os.Stat(dir)
	if err != nil {
		return fmt.Errorf("error accessing layout: %v", err)
	}
	if !fi.IsDir() {
		return fmt.Errorf("given path %q is not a directory", dir)
	}
	var flist []string
	var imOK, rfsOK bool
	var im io.Reader
	walkLayout := func(fpath string, fi os.FileInfo, err error) error {
		rpath, err := filepath.Rel(dir, fpath)
		if err != nil {
			return err
		}
		name := filepath.Base(rpath)
		switch name {
		case ".":
		case ManifestFile:
			im, err = os.Open(fpath)
			if err != nil {
				return err
			}
			imOK = true
		case RootfsDir:
			if !fi.IsDir() {
				return errors.New("rootfs is not a directory")
			}
			rfsOK = true
		default:
			flist = append(flist, rpath)
		}
		return nil
	}
	if err := filepath.Walk(dir, walkLayout); err != nil {
		return err
	}
	return validate(imOK, im, rfsOK, flist)
}

// ValidateArchive takes a *tar.Reader and validates that the layout of the
// filesystem the reader encapsulates matches that expected by the
// Application Container Image format.  If any errors are encountered during
// the validation, it will abort and return the first one.
func ValidateArchive(tr *tar.Reader) error {
	var fseen map[string]bool = make(map[string]bool)
	var imOK, rfsOK bool
	var im bytes.Buffer
Tar:
	for {
		hdr, err := tr.Next()
		switch {
		case err == nil:
		case err == io.EOF:
			break Tar
		default:
			return err
		}
		name := filepath.Clean(hdr.Name)
		switch name {
		case ".":
		case ManifestFile:
			_, err := io.Copy(&im, tr)
			if err != nil {
				return err
			}
			imOK = true
		case RootfsDir:
			if !hdr.FileInfo().IsDir() {
				return fmt.Errorf("rootfs is not a directory")
			}
			rfsOK = true
		default:
			if _, seen := fseen[name]; seen {
				return fmt.Errorf("duplicate file entry in archive: %s", name)
			}
			fseen[name] = true
		}
	}
	var flist []string
	for key := range fseen {
		flist = append(flist, key)
	}
	return validate(imOK, &im, rfsOK, flist)
}

func validate(imOK bool, im io.Reader, rfsOK bool, files []string) error {
	defer func() {
		if rc, ok := im.(io.Closer); ok {
			rc.Close()
		}
	}()
	if !imOK {
		return ErrNoManifest
	}
	if !rfsOK {
		return ErrNoRootFS
	}
	b, err := ioutil.ReadAll(im)
	if err != nil {
		return fmt.Errorf("error reading image manifest: %v", err)
	}
	var a schema.ImageManifest
	if err := a.UnmarshalJSON(b); err != nil {
		return fmt.Errorf("image manifest validation failed: %v", err)
	}
	for _, f := range files {
		if !strings.HasPrefix(f, "rootfs") {
			return fmt.Errorf("unrecognized file path in layout: %q", f)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/*
The MIT License (MIT)

Copyright (c) 2014 Boldly Go Ventures

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package predicate

import (
	"encoding/json"
	"testing"
)

func TestSet_UnmarshalJSON(t *testing.T) {
	var (
		data []byte
		v    Set
	)

	/***** Positive Cases *****/
	data = []byte(`[{"and":[{"x":"a"}]}]`)
	if err := json.Unmarshal(data, &v); err != nil {
		t.Error(err)
	}

	if And(v).P(map[string]interface{}{"x": "a"}) != true {
		t.Fail()
	}

	data = []byte(`[{"or":[{"x":"a"}]}]`)
	if err := json.Unmarshal(data, &v); err != nil {
		t.Error(err)
	}

	if And(v).P(map[string]interface{}{"x": "a"}) != true {
		t.Fail()
	}

	data = []byte(`[{"xor":[{"x":"a"}]}]`)
	if err := json.Unmarshal(data, &v); err != nil {
		t.Error(err)
	}

	if And(v).P(map[string]interface{}{"x": "a"}) != true {
		t.Fail()
	}

	data = []byte(`[{"not":[{"x":"a"}]}]`)
	if err := json.Unmarshal(data, &v); err != nil {
		t.Error(err)
	}

	if And(v).P(map[string]interface{}{"x": "a"}) == true {
		t.Fail()
	}

	/***** Negative Cases *****/
	data = nil
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = make([]byte, 0)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(``)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`"`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`{`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[,]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`{n}`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`{"abc":"xyz",}`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[n]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`{null}`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[abc,]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[null]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[{"and":null}]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[{"or":null}]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[{"xor":null}]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[{"not":null}]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[{"abc":nil}]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}
}
<commit_msg>Added test cases for helper functions<commit_after>/*
The MIT License (MIT)

Copyright (c) 2014 Boldly Go Ventures

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package predicate

import (
	"encoding/json"
	"testing"
)

func TestSet_UnmarshalJSON(t *testing.T) {
	var (
		data []byte
		v    Set
	)

	/***** Positive Cases *****/
	data = []byte(`[{"and":[{"x":"a"}]}]`)
	if err := json.Unmarshal(data, &v); err != nil {
		t.Error(err)
	}

	if And(v).P(map[string]interface{}{"x": "a"}) != true {
		t.Fail()
	}

	data = []byte(`[{"or":[{"x":"a"}]}]`)
	if err := json.Unmarshal(data, &v); err != nil {
		t.Error(err)
	}

	if And(v).P(map[string]interface{}{"x": "a"}) != true {
		t.Fail()
	}

	data = []byte(`[{"xor":[{"x":"a"}]}]`)
	if err := json.Unmarshal(data, &v); err != nil {
		t.Error(err)
	}

	if And(v).P(map[string]interface{}{"x": "a"}) != true {
		t.Fail()
	}

	data = []byte(`[{"not":[{"x":"a"}]}]`)
	if err := json.Unmarshal(data, &v); err != nil {
		t.Error(err)
	}

	if And(v).P(map[string]interface{}{"x": "a"}) == true {
		t.Fail()
	}

	/***** Negative Cases *****/
	data = nil
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = make([]byte, 0)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(``)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`"`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`{`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[,]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`{n}`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`{"abc":"xyz",}`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[n]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`{null}`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[abc,]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[null]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[{"and":null}]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[{"or":null}]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[{"xor":null}]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[{"not":null}]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}

	data = []byte(`[{"abc":nil}]`)
	if err := json.Unmarshal(data, &v); err == nil {
		t.Fail()
	}
}

func TestUnmarshalJSON(t *testing.T) {
	var (
		data []byte
	)

	data = []byte(`[{"abc":nil}]`)
	if _, err := unmarshalJSON(data); err == nil {
		t.Fail()
	}
}

func TestUnmarshalJSONObject(t *testing.T) {
	var (
		data []byte
	)

	data = []byte(`[{"abc":nil}]`)
	if _, err := unmarshalJSONObject(data); err == nil {
		t.Fail()
	}
}

func TestUnmarshalJSONArray(t *testing.T) {
	var (
		data []byte
	)

	data = []byte(`[{"abc":nil}]`)
	if _, err := unmarshalJSONObject(data); err == nil {
		t.Fail()
	}
}
<|endoftext|>


<commit_before>package build

import (
	"fmt"
	"neon/util"
	"sort"
	"strings"
	"unicode/utf8"
)

// Print help on build
func (build *Build) Info() error {
	// print build information
	if build.Doc != "" {
		Message("doc: %s", build.Doc)
	}
	if len(build.Default) > 0 {
		defaults := "[" + strings.Join(build.Default, ", ") + "]"
		Message("default: %s", defaults)
	}
	Message("repository: %s", build.Repository)
	if build.Singleton != 0 {
		Message("singleton: %d", build.Singleton)
	}
	// print parent build files
	if len(build.Parents) > 0 {
		Message("")
		Message("extends:")
		for _, extend := range build.Extends {
			Message("- %s", extend)
		}
	}
	// print configuration files
	if len(build.Config) > 0 {
		Message("")
		Message("configuration:")
		for _, config := range build.Config {
			Message("- %s", config)
		}
	}
	// print context scripts
	if len(build.Config) > 0 {
		Message("")
		Message("context:")
		for _, script := range build.Scripts {
			Message("- %s", script)
		}
	}
	// print build properties
	length := util.MaxLength(build.Context.Properties)
	if len(build.Context.Properties) > 0 {
		Message("")
		Message("properties:")
		for _, name := range build.Context.Properties {
			value, err := build.Context.GetProperty(name)
			if err != nil {
				return fmt.Errorf("getting property '%s': %v", name, err)
			}
			valueStr, err := PropertyToString(value, true)
			if err != nil {
				return fmt.Errorf("formatting property '%s': %v", name, err)
			}
			PrintProperty(name, valueStr, []string{}, length)
		}
	}
	// print build environment
	var names []string
	for name := range build.Context.Environment {
		names = append(names, name)
	}
	length = util.MaxLength(names)
	sort.Strings(names)
	if len(build.Context.Environment) > 0 {
		Message("")
		Message("environment:")
		for _, name := range names {
			value := "\"" + build.Context.Environment[name] + "\""
			PrintProperty(name, value, []string{}, length)
		}
	}
	// print targets documentation
	targets := build.GetTargets()
	names = make([]string, 0)
	for name := range targets {
		names = append(names, name)
	}
	length = util.MaxLength(names)
	sort.Strings(names)
	if len(names) > 0 {
		Message("")
		Message("targets:")
		for _, name := range names {
			target := targets[name]
			PrintProperty(name, target.Doc, target.Depends, length)
		}
	}
	return nil
}

func PrintProperty(name, doc string, depends []string, length int) {
	deps := ""
	if len(depends) > 0 {
		deps = "[" + strings.Join(depends, ", ") + "]"
	}
	if doc != "" {
		deps = " " + deps
	}
	Message("  %s: %s%s%s", name,
		strings.Repeat(" ", length-utf8.RuneCountInString(name)), doc, deps)
}

// Print build targets
func (build *Build) PrintTargets() {
	var targets []string
	for name := range build.GetTargets() {
		targets = append(targets, name)
	}
	sort.Strings(targets)
	Message(strings.Join(targets, " "))
}

// Print tasks
func PrintTasks() {
	var tasks []string
	for name := range TaskMap {
		tasks = append(tasks, name)
	}
	sort.Strings(tasks)
	Message(strings.Join(tasks, " "))
}

// Print help on tasks
func PrintHelpTask(task string) {
	descriptor, found := TaskMap[task]
	if found {
		Message(descriptor.Help)
	} else {
		Message("Task '%s' was not found", task)
	}
}

// Print builtins
func PrintBuiltins() {
	var builtins []string
	for name := range BuiltinMap {
		builtins = append(builtins, name)
	}
	sort.Strings(builtins)
	Message(strings.Join(builtins, " "))
}

// Print help on builtins
func PrintHelpBuiltin(builtin string) {
	descriptor, found := BuiltinMap[builtin]
	if found {
		Message(descriptor.Help)
	} else {
		Message("Builtin '%s' was not found", builtin)
	}
}

// Print markdown reference for tasks and builtins
func PrintReference() {
	Message("Tasks Reference")
	Message("===============")
	Message("")
	var tasks []string
	for name := range TaskMap {
		tasks = append(tasks, name)
	}
	sort.Strings(tasks)
	for _, task := range tasks {
		Message(task)
		Message(strings.Repeat("-", len(task)))
		Message("")
		Message(TaskMap[task].Help)
		Message("")
	}
	Message("")
	Message("Builtins Reference")
	Message("==================")
	Message("")
	var builtins []string
	for name := range BuiltinMap {
		builtins = append(builtins, name)
	}
	sort.Strings(builtins)
	for _, builtin := range builtins {
		Message(builtin)
		Message(strings.Repeat("-", len(builtin)))
		Message("")
		Message(BuiltinMap[builtin].Help)
		Message("")
	}
}
<commit_msg>Fixed bug on help regarding context<commit_after>package build

import (
	"fmt"
	"neon/util"
	"sort"
	"strings"
	"unicode/utf8"
)

// Print help on build
func (build *Build) Info() error {
	// print build information
	if build.Doc != "" {
		Message("doc: %s", build.Doc)
	}
	if len(build.Default) > 0 {
		defaults := "[" + strings.Join(build.Default, ", ") + "]"
		Message("default: %s", defaults)
	}
	Message("repository: %s", build.Repository)
	if build.Singleton != 0 {
		Message("singleton: %d", build.Singleton)
	}
	// print parent build files
	if len(build.Parents) > 0 {
		Message("")
		Message("extends:")
		for _, extend := range build.Extends {
			Message("- %s", extend)
		}
	}
	// print configuration files
	if len(build.Config) > 0 {
		Message("")
		Message("configuration:")
		for _, config := range build.Config {
			Message("- %s", config)
		}
	}
	// print context scripts
	if len(build.Scripts) > 0 {
		Message("")
		Message("context:")
		for _, script := range build.Scripts {
			Message("- %s", script)
		}
	}
	// print build properties
	length := util.MaxLength(build.Context.Properties)
	if len(build.Context.Properties) > 0 {
		Message("")
		Message("properties:")
		for _, name := range build.Context.Properties {
			value, err := build.Context.GetProperty(name)
			if err != nil {
				return fmt.Errorf("getting property '%s': %v", name, err)
			}
			valueStr, err := PropertyToString(value, true)
			if err != nil {
				return fmt.Errorf("formatting property '%s': %v", name, err)
			}
			PrintProperty(name, valueStr, []string{}, length)
		}
	}
	// print build environment
	var names []string
	for name := range build.Context.Environment {
		names = append(names, name)
	}
	length = util.MaxLength(names)
	sort.Strings(names)
	if len(build.Context.Environment) > 0 {
		Message("")
		Message("environment:")
		for _, name := range names {
			value := "\"" + build.Context.Environment[name] + "\""
			PrintProperty(name, value, []string{}, length)
		}
	}
	// print targets documentation
	targets := build.GetTargets()
	names = make([]string, 0)
	for name := range targets {
		names = append(names, name)
	}
	length = util.MaxLength(names)
	sort.Strings(names)
	if len(names) > 0 {
		Message("")
		Message("targets:")
		for _, name := range names {
			target := targets[name]
			PrintProperty(name, target.Doc, target.Depends, length)
		}
	}
	return nil
}

func PrintProperty(name, doc string, depends []string, length int) {
	deps := ""
	if len(depends) > 0 {
		deps = "[" + strings.Join(depends, ", ") + "]"
	}
	if doc != "" {
		deps = " " + deps
	}
	Message("  %s: %s%s%s", name,
		strings.Repeat(" ", length-utf8.RuneCountInString(name)), doc, deps)
}

// Print build targets
func (build *Build) PrintTargets() {
	var targets []string
	for name := range build.GetTargets() {
		targets = append(targets, name)
	}
	sort.Strings(targets)
	Message(strings.Join(targets, " "))
}

// Print tasks
func PrintTasks() {
	var tasks []string
	for name := range TaskMap {
		tasks = append(tasks, name)
	}
	sort.Strings(tasks)
	Message(strings.Join(tasks, " "))
}

// Print help on tasks
func PrintHelpTask(task string) {
	descriptor, found := TaskMap[task]
	if found {
		Message(descriptor.Help)
	} else {
		Message("Task '%s' was not found", task)
	}
}

// Print builtins
func PrintBuiltins() {
	var builtins []string
	for name := range BuiltinMap {
		builtins = append(builtins, name)
	}
	sort.Strings(builtins)
	Message(strings.Join(builtins, " "))
}

// Print help on builtins
func PrintHelpBuiltin(builtin string) {
	descriptor, found := BuiltinMap[builtin]
	if found {
		Message(descriptor.Help)
	} else {
		Message("Builtin '%s' was not found", builtin)
	}
}

// Print markdown reference for tasks and builtins
func PrintReference() {
	Message("Tasks Reference")
	Message("===============")
	Message("")
	var tasks []string
	for name := range TaskMap {
		tasks = append(tasks, name)
	}
	sort.Strings(tasks)
	for _, task := range tasks {
		Message(task)
		Message(strings.Repeat("-", len(task)))
		Message("")
		Message(TaskMap[task].Help)
		Message("")
	}
	Message("")
	Message("Builtins Reference")
	Message("==================")
	Message("")
	var builtins []string
	for name := range BuiltinMap {
		builtins = append(builtins, name)
	}
	sort.Strings(builtins)
	for _, builtin := range builtins {
		Message(builtin)
		Message(strings.Repeat("-", len(builtin)))
		Message("")
		Message(BuiltinMap[builtin].Help)
		Message("")
	}
}
<|endoftext|>


<commit_before>// +build integration

/*
Copyright 2016 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 integration

import (
	"context"
	"encoding/json"
	"fmt"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"testing"

	"github.com/docker/machine/libmachine/state"
	"github.com/google/go-cmp/cmp"
	"k8s.io/minikube/pkg/minikube/bootstrapper/images"
	"k8s.io/minikube/pkg/minikube/constants"
)

func TestStartStop(t *testing.T) {
	MaybeParallel(t)

	t.Run("group", func(t *testing.T) {
		tests := []struct {
			name    string
			version string
			args    []string
		}{
			{"old-docker", constants.OldestKubernetesVersion, []string{
				// default is the network created by libvirt, if we change the name minikube won't boot
				// because the given network doesn't exist
				"--kvm-network=default",
				"--kvm-qemu-uri=qemu:///system",
				"--disable-driver-mounts",
				"--keep-context=false",
				"--container-runtime=docker",
			}},
			{"newest-cni", constants.NewestKubernetesVersion, []string{
				"--feature-gates",
				"ServerSideApply=true",
				"--network-plugin=cni",
				"--extra-config=kubelet.network-plugin=cni",
				"--extra-config=kubeadm.pod-network-cidr=192.168.111.111/16",
			}},
			{"containerd", constants.DefaultKubernetesVersion, []string{
				"--container-runtime=containerd",
				"--docker-opt",
				"containerd=/var/run/containerd/containerd.sock",
				"--apiserver-port=8444",
			}},
			{"crio", "v1.15.7", []string{
				"--container-runtime=crio",
				"--disable-driver-mounts",
				"--extra-config=kubeadm.ignore-preflight-errors=SystemVerification",
			}},
		}

		for _, tc := range tests {
			tc := tc
			t.Run(tc.name, func(t *testing.T) {
				MaybeParallel(t)

				if !strings.Contains(tc.name, "docker") && NoneDriver() {
					t.Skipf("skipping %s - incompatible with none driver", t.Name())
				}

				profile := UniqueProfileName(tc.name)
				ctx, cancel := context.WithTimeout(context.Background(), Minutes(40))
				defer CleanupWithLogs(t, profile, cancel)

				startArgs := append([]string{"start", "-p", profile, "--memory=2200", "--alsologtostderr", "-v=3", "--wait=true"}, tc.args...)
				startArgs = append(startArgs, StartArgs()...)
				startArgs = append(startArgs, fmt.Sprintf("--kubernetes-version=%s", tc.version))

				rr, err := Run(t, exec.CommandContext(ctx, Target(), startArgs...))
				if err != nil {
					t.Fatalf("%s failed: %v", rr.Args, err)
				}

				if !strings.Contains(tc.name, "cni") {
					testPodScheduling(ctx, t, profile)
				}

				rr, err = Run(t, exec.CommandContext(ctx, Target(), "stop", "-p", profile, "--alsologtostderr", "-v=3"))
				if err != nil {
					t.Errorf("%s failed: %v", rr.Args, err)
				}

				// The none driver never really stops
				if !NoneDriver() {
					got := Status(ctx, t, Target(), profile, "Host")
					if got != state.Stopped.String() {
						t.Errorf("post-stop host status = %q; want = %q", got, state.Stopped)
					}
				}

				// Enable an addon to assert it comes up afterwards
				rr, err = Run(t, exec.CommandContext(ctx, Target(), "addons", "enable", "dashboard", "-p", profile))
				if err != nil {
					t.Errorf("%s failed: %v", rr.Args, err)
				}

				rr, err = Run(t, exec.CommandContext(ctx, Target(), startArgs...))
				if err != nil {
					// Explicit fatal so that failures don't move directly to deletion
					t.Fatalf("%s failed: %v", rr.Args, err)
				}

				if strings.Contains(tc.name, "cni") {
					t.Logf("WARNING: cni mode requires additional setup before pods can schedule :(")
				} else {
					if _, err := PodWait(ctx, t, profile, "default", "integration-test=busybox", Minutes(4)); err != nil {
						t.Fatalf("post-stop-start pod wait: %v", err)
					}
					if _, err := PodWait(ctx, t, profile, "kubernetes-dashboard", "k8s-app=kubernetes-dashboard", Minutes(4)); err != nil {
						t.Fatalf("post-stop-start addon wait: %v", err)
					}
				}

				got := Status(ctx, t, Target(), profile, "Host")
				if got != state.Running.String() {
					t.Errorf("post-start host status = %q; want = %q", got, state.Running)
				}

				if !NoneDriver() {
					testPulledImages(ctx, t, profile, tc.version)
				}

				testPause(ctx, t, profile)

				if *cleanup {
					// Normally handled by cleanuprofile, but not fatal there
					rr, err = Run(t, exec.CommandContext(ctx, Target(), "delete", "-p", profile))
					if err != nil {
						t.Errorf("%s failed: %v", rr.Args, err)
					}

					rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "config", "get-contexts", profile))
					if err != nil {
						t.Logf("config context error: %v (may be ok)", err)
					}
					if rr.ExitCode != 1 {
						t.Errorf("wanted exit code 1, got %d. output: %s", rr.ExitCode, rr.Output())
					}
				}
			})
		}
	})
}

func TestStartStopWithPreload(t *testing.T) {
	if NoneDriver() {
		t.Skipf("skipping %s - incompatible with none driver", t.Name())
	}

	profile := UniqueProfileName("test-preload")
	ctx, cancel := context.WithTimeout(context.Background(), Minutes(40))
	defer CleanupWithLogs(t, profile, cancel)

	startArgs := []string{"start", "-p", profile, "--memory=2200", "--alsologtostderr", "-v=3", "--wait=true", "--preload=false"}
	startArgs = append(startArgs, StartArgs()...)
	k8sVersion := "v1.17.0"
	startArgs = append(startArgs, fmt.Sprintf("--kubernetes-version=%s", k8sVersion))

	rr, err := Run(t, exec.CommandContext(ctx, Target(), startArgs...))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}

	// Now, pull the busybox image into the VMs docker daemon
	image := "busybox"
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "docker", "pull", image))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}
	// Restart minikube with v1.17.3, which has a preloaded tarball
	startArgs = []string{"start", "-p", profile, "--memory=2200", "--alsologtostderr", "-v=3", "--wait=true"}
	startArgs = append(startArgs, StartArgs()...)
	k8sVersion = "v1.17.3"
	startArgs = append(startArgs, fmt.Sprintf("--kubernetes-version=%s", k8sVersion))
	rr, err = Run(t, exec.CommandContext(ctx, Target(), startArgs...))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}

	// Ensure that busybox still exists in the daemon
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "docker", "images"))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}
	if !strings.Contains(rr.Output(), image) {
		t.Fatalf("Expected to find %s in output of `docker images`, instead got %s", image, rr.Output())
	}
}

// testPodScheduling asserts that this configuration can schedule new pods
func testPodScheduling(ctx context.Context, t *testing.T, profile string) {
	t.Helper()

	// schedule a pod to assert persistence
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "create", "-f", filepath.Join(*testdataDir, "busybox.yaml")))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}

	// 8 minutes, because 4 is not enough for images to pull in all cases.
	names, err := PodWait(ctx, t, profile, "default", "integration-test=busybox", Minutes(8))
	if err != nil {
		t.Fatalf("wait: %v", err)
	}

	// Use this pod to confirm that the runtime resource limits are sane
	rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "exec", names[0], "--", "/bin/sh", "-c", "ulimit -n"))
	if err != nil {
		t.Fatalf("ulimit: %v", err)
	}

	got, err := strconv.ParseInt(strings.TrimSpace(rr.Stdout.String()), 10, 64)
	if err != nil {
		t.Errorf("ParseInt(%q): %v", rr.Stdout.String(), err)
	}

	// Arbitrary value set by some container runtimes. If higher, apps like MySQL may make bad decisions.
	expected := int64(1048576)
	if got != expected {
		t.Errorf("'ulimit -n' returned %d, expected %d", got, expected)
	}
}

// testPulledImages asserts that this configuration pulls only expected images
func testPulledImages(ctx context.Context, t *testing.T, profile string, version string) {
	t.Helper()

	rr, err := Run(t, exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "sudo crictl images -o json"))
	if err != nil {
		t.Errorf("%s failed: %v", rr.Args, err)
	}
	jv := map[string][]struct {
		Tags []string `json:"repoTags"`
	}{}
	err = json.Unmarshal(rr.Stdout.Bytes(), &jv)
	if err != nil {
		t.Errorf("images unmarshal: %v", err)
	}
	found := map[string]bool{}
	for _, img := range jv["images"] {
		for _, i := range img.Tags {
			// Remove container-specific prefixes for naming consistency
			i = strings.TrimPrefix(i, "docker.io/")
			i = strings.TrimPrefix(i, "localhost/")
			if defaultImage(i) {
				found[i] = true
			} else {
				t.Logf("Found non-minikube image: %s", i)
			}
		}
	}
	want, err := images.Kubeadm("", version)
	if err != nil {
		t.Errorf("kubeadm images: %v", version)
	}
	gotImages := []string{}
	for k := range found {
		gotImages = append(gotImages, k)
	}
	sort.Strings(want)
	sort.Strings(gotImages)
	if diff := cmp.Diff(want, gotImages); diff != "" {
		t.Errorf("%s images mismatch (-want +got):\n%s", version, diff)
	}
}

// testPause asserts that this configuration can be paused and unpaused
func testPause(ctx context.Context, t *testing.T, profile string) {
	t.Helper()

	rr, err := Run(t, exec.CommandContext(ctx, Target(), "pause", "-p", profile, "--alsologtostderr", "-v=1"))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}

	got := Status(ctx, t, Target(), profile, "APIServer")
	if got != state.Paused.String() {
		t.Errorf("post-pause apiserver status = %q; want = %q", got, state.Paused)
	}

	got = Status(ctx, t, Target(), profile, "Kubelet")
	if got != state.Stopped.String() {
		t.Errorf("post-pause kubelet status = %q; want = %q", got, state.Stopped)
	}

	rr, err = Run(t, exec.CommandContext(ctx, Target(), "unpause", "-p", profile, "--alsologtostderr", "-v=1"))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}

	got = Status(ctx, t, Target(), profile, "APIServer")
	if got != state.Running.String() {
		t.Errorf("post-unpause apiserver status = %q; want = %q", got, state.Running)
	}

	got = Status(ctx, t, Target(), profile, "Kubelet")
	if got != state.Running.String() {
		t.Errorf("post-unpause kubelet status = %q; want = %q", got, state.Running)
	}

}

// defaultImage returns true if this image is expected in a default minikube install
func defaultImage(name string) bool {
	if strings.Contains(name, ":latest") {
		return false
	}
	if strings.Contains(name, "k8s.gcr.io") || strings.Contains(name, "kubernetesui") || strings.Contains(name, "storage-provisioner") {
		return true
	}
	return false
}
<commit_msg>remove preload flag<commit_after>// +build integration

/*
Copyright 2016 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 integration

import (
	"context"
	"encoding/json"
	"fmt"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"testing"

	"github.com/docker/machine/libmachine/state"
	"github.com/google/go-cmp/cmp"
	"k8s.io/minikube/pkg/minikube/bootstrapper/images"
	"k8s.io/minikube/pkg/minikube/constants"
)

func TestStartStop(t *testing.T) {
	MaybeParallel(t)

	t.Run("group", func(t *testing.T) {
		tests := []struct {
			name    string
			version string
			args    []string
		}{
			{"old-docker", constants.OldestKubernetesVersion, []string{
				// default is the network created by libvirt, if we change the name minikube won't boot
				// because the given network doesn't exist
				"--kvm-network=default",
				"--kvm-qemu-uri=qemu:///system",
				"--disable-driver-mounts",
				"--keep-context=false",
				"--container-runtime=docker",
			}},
			{"newest-cni", constants.NewestKubernetesVersion, []string{
				"--feature-gates",
				"ServerSideApply=true",
				"--network-plugin=cni",
				"--extra-config=kubelet.network-plugin=cni",
				"--extra-config=kubeadm.pod-network-cidr=192.168.111.111/16",
			}},
			{"containerd", constants.DefaultKubernetesVersion, []string{
				"--container-runtime=containerd",
				"--docker-opt",
				"containerd=/var/run/containerd/containerd.sock",
				"--apiserver-port=8444",
			}},
			{"crio", "v1.15.7", []string{
				"--container-runtime=crio",
				"--disable-driver-mounts",
				"--extra-config=kubeadm.ignore-preflight-errors=SystemVerification",
			}},
		}

		for _, tc := range tests {
			tc := tc
			t.Run(tc.name, func(t *testing.T) {
				MaybeParallel(t)

				if !strings.Contains(tc.name, "docker") && NoneDriver() {
					t.Skipf("skipping %s - incompatible with none driver", t.Name())
				}

				profile := UniqueProfileName(tc.name)
				ctx, cancel := context.WithTimeout(context.Background(), Minutes(40))
				defer CleanupWithLogs(t, profile, cancel)

				startArgs := append([]string{"start", "-p", profile, "--memory=2200", "--alsologtostderr", "-v=3", "--wait=true"}, tc.args...)
				startArgs = append(startArgs, StartArgs()...)
				startArgs = append(startArgs, fmt.Sprintf("--kubernetes-version=%s", tc.version))

				rr, err := Run(t, exec.CommandContext(ctx, Target(), startArgs...))
				if err != nil {
					t.Fatalf("%s failed: %v", rr.Args, err)
				}

				if !strings.Contains(tc.name, "cni") {
					testPodScheduling(ctx, t, profile)
				}

				rr, err = Run(t, exec.CommandContext(ctx, Target(), "stop", "-p", profile, "--alsologtostderr", "-v=3"))
				if err != nil {
					t.Errorf("%s failed: %v", rr.Args, err)
				}

				// The none driver never really stops
				if !NoneDriver() {
					got := Status(ctx, t, Target(), profile, "Host")
					if got != state.Stopped.String() {
						t.Errorf("post-stop host status = %q; want = %q", got, state.Stopped)
					}
				}

				// Enable an addon to assert it comes up afterwards
				rr, err = Run(t, exec.CommandContext(ctx, Target(), "addons", "enable", "dashboard", "-p", profile))
				if err != nil {
					t.Errorf("%s failed: %v", rr.Args, err)
				}

				rr, err = Run(t, exec.CommandContext(ctx, Target(), startArgs...))
				if err != nil {
					// Explicit fatal so that failures don't move directly to deletion
					t.Fatalf("%s failed: %v", rr.Args, err)
				}

				if strings.Contains(tc.name, "cni") {
					t.Logf("WARNING: cni mode requires additional setup before pods can schedule :(")
				} else {
					if _, err := PodWait(ctx, t, profile, "default", "integration-test=busybox", Minutes(4)); err != nil {
						t.Fatalf("post-stop-start pod wait: %v", err)
					}
					if _, err := PodWait(ctx, t, profile, "kubernetes-dashboard", "k8s-app=kubernetes-dashboard", Minutes(4)); err != nil {
						t.Fatalf("post-stop-start addon wait: %v", err)
					}
				}

				got := Status(ctx, t, Target(), profile, "Host")
				if got != state.Running.String() {
					t.Errorf("post-start host status = %q; want = %q", got, state.Running)
				}

				if !NoneDriver() {
					testPulledImages(ctx, t, profile, tc.version)
				}

				testPause(ctx, t, profile)

				if *cleanup {
					// Normally handled by cleanuprofile, but not fatal there
					rr, err = Run(t, exec.CommandContext(ctx, Target(), "delete", "-p", profile))
					if err != nil {
						t.Errorf("%s failed: %v", rr.Args, err)
					}

					rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "config", "get-contexts", profile))
					if err != nil {
						t.Logf("config context error: %v (may be ok)", err)
					}
					if rr.ExitCode != 1 {
						t.Errorf("wanted exit code 1, got %d. output: %s", rr.ExitCode, rr.Output())
					}
				}
			})
		}
	})
}

func TestStartStopWithPreload(t *testing.T) {
	if NoneDriver() {
		t.Skipf("skipping %s - incompatible with none driver", t.Name())
	}

	profile := UniqueProfileName("test-preload")
	ctx, cancel := context.WithTimeout(context.Background(), Minutes(40))
	defer CleanupWithLogs(t, profile, cancel)

	startArgs := []string{"start", "-p", profile, "--memory=2200", "--alsologtostderr", "-v=3", "--wait=true"}
	startArgs = append(startArgs, StartArgs()...)
	k8sVersion := "v1.17.0"
	startArgs = append(startArgs, fmt.Sprintf("--kubernetes-version=%s", k8sVersion))

	rr, err := Run(t, exec.CommandContext(ctx, Target(), startArgs...))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}

	// Now, pull the busybox image into the VMs docker daemon
	image := "busybox"
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "docker", "pull", image))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}
	// Restart minikube with v1.17.3, which has a preloaded tarball
	startArgs = []string{"start", "-p", profile, "--memory=2200", "--alsologtostderr", "-v=3", "--wait=true"}
	startArgs = append(startArgs, StartArgs()...)
	k8sVersion = "v1.17.3"
	startArgs = append(startArgs, fmt.Sprintf("--kubernetes-version=%s", k8sVersion))
	rr, err = Run(t, exec.CommandContext(ctx, Target(), startArgs...))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}

	// Ensure that busybox still exists in the daemon
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "docker", "images"))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}
	if !strings.Contains(rr.Output(), image) {
		t.Fatalf("Expected to find %s in output of `docker images`, instead got %s", image, rr.Output())
	}
}

// testPodScheduling asserts that this configuration can schedule new pods
func testPodScheduling(ctx context.Context, t *testing.T, profile string) {
	t.Helper()

	// schedule a pod to assert persistence
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "create", "-f", filepath.Join(*testdataDir, "busybox.yaml")))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}

	// 8 minutes, because 4 is not enough for images to pull in all cases.
	names, err := PodWait(ctx, t, profile, "default", "integration-test=busybox", Minutes(8))
	if err != nil {
		t.Fatalf("wait: %v", err)
	}

	// Use this pod to confirm that the runtime resource limits are sane
	rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "exec", names[0], "--", "/bin/sh", "-c", "ulimit -n"))
	if err != nil {
		t.Fatalf("ulimit: %v", err)
	}

	got, err := strconv.ParseInt(strings.TrimSpace(rr.Stdout.String()), 10, 64)
	if err != nil {
		t.Errorf("ParseInt(%q): %v", rr.Stdout.String(), err)
	}

	// Arbitrary value set by some container runtimes. If higher, apps like MySQL may make bad decisions.
	expected := int64(1048576)
	if got != expected {
		t.Errorf("'ulimit -n' returned %d, expected %d", got, expected)
	}
}

// testPulledImages asserts that this configuration pulls only expected images
func testPulledImages(ctx context.Context, t *testing.T, profile string, version string) {
	t.Helper()

	rr, err := Run(t, exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "sudo crictl images -o json"))
	if err != nil {
		t.Errorf("%s failed: %v", rr.Args, err)
	}
	jv := map[string][]struct {
		Tags []string `json:"repoTags"`
	}{}
	err = json.Unmarshal(rr.Stdout.Bytes(), &jv)
	if err != nil {
		t.Errorf("images unmarshal: %v", err)
	}
	found := map[string]bool{}
	for _, img := range jv["images"] {
		for _, i := range img.Tags {
			// Remove container-specific prefixes for naming consistency
			i = strings.TrimPrefix(i, "docker.io/")
			i = strings.TrimPrefix(i, "localhost/")
			if defaultImage(i) {
				found[i] = true
			} else {
				t.Logf("Found non-minikube image: %s", i)
			}
		}
	}
	want, err := images.Kubeadm("", version)
	if err != nil {
		t.Errorf("kubeadm images: %v", version)
	}
	gotImages := []string{}
	for k := range found {
		gotImages = append(gotImages, k)
	}
	sort.Strings(want)
	sort.Strings(gotImages)
	if diff := cmp.Diff(want, gotImages); diff != "" {
		t.Errorf("%s images mismatch (-want +got):\n%s", version, diff)
	}
}

// testPause asserts that this configuration can be paused and unpaused
func testPause(ctx context.Context, t *testing.T, profile string) {
	t.Helper()

	rr, err := Run(t, exec.CommandContext(ctx, Target(), "pause", "-p", profile, "--alsologtostderr", "-v=1"))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}

	got := Status(ctx, t, Target(), profile, "APIServer")
	if got != state.Paused.String() {
		t.Errorf("post-pause apiserver status = %q; want = %q", got, state.Paused)
	}

	got = Status(ctx, t, Target(), profile, "Kubelet")
	if got != state.Stopped.String() {
		t.Errorf("post-pause kubelet status = %q; want = %q", got, state.Stopped)
	}

	rr, err = Run(t, exec.CommandContext(ctx, Target(), "unpause", "-p", profile, "--alsologtostderr", "-v=1"))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Args, err)
	}

	got = Status(ctx, t, Target(), profile, "APIServer")
	if got != state.Running.String() {
		t.Errorf("post-unpause apiserver status = %q; want = %q", got, state.Running)
	}

	got = Status(ctx, t, Target(), profile, "Kubelet")
	if got != state.Running.String() {
		t.Errorf("post-unpause kubelet status = %q; want = %q", got, state.Running)
	}

}

// defaultImage returns true if this image is expected in a default minikube install
func defaultImage(name string) bool {
	if strings.Contains(name, ":latest") {
		return false
	}
	if strings.Contains(name, "k8s.gcr.io") || strings.Contains(name, "kubernetesui") || strings.Contains(name, "storage-provisioner") {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package admin

import "path"

type Menu struct {
	Name      string
	Link      string
	Ancestors []string
	subMenus  []*Menu
	rawPath   string
}

func (admin Admin) GetMenus() []*Menu {
	return admin.menus
}

func (menu *Menu) GetSubMenus() []*Menu {
	return menu.subMenus
}

func (admin *Admin) AddMenu(menu *Menu) {
	admin.menus = appendMenu(admin.menus, menu.Ancestors, menu)
}

// TODO: this is not used ?
func (admin Admin) GetMenu(name string) *Menu {
	return getMenu(admin.menus, name)
}

func getMenu(menus []*Menu, name string) *Menu {
	for _, m := range menus {
		if m.Name == name {
			return m
		}

		if len(m.subMenus) > 0 {
			if mc := getMenu(m.subMenus, name); mc != nil {
				return mc
			}
		}
	}

	return nil
}

// Generate menu links by current route. e.g "/products" to "/admin/products"
func (admin *Admin) generateMenuLinks() {
	prefixMenuLinks(admin.menus, admin.router.Prefix)
}

func prefixMenuLinks(menus []*Menu, prefix string) {
	for _, m := range menus {
		if m.rawPath != "" {
			m.Link = path.Join(prefix, m.rawPath)
		}
		if len(m.subMenus) > 0 {
			prefixMenuLinks(m.subMenus, prefix)
		}
	}
}

func newMenu(menus []string, menu *Menu) *Menu {
	menuCount := len(menus)
	for index, _ := range menus {
		menu = &Menu{Name: menus[menuCount-index-1], subMenus: []*Menu{menu}}
	}

	return menu
}

func appendMenu(menus []*Menu, ancestors []string, menu *Menu) []*Menu {
	if len(ancestors) > 0 {
		for _, m := range menus {
			if m.Name != ancestors[0] {
				continue
			}

			if len(ancestors) > 1 {
				m.subMenus = appendMenu(m.subMenus, ancestors[1:], menu)
			} else {
				m.subMenus = append(m.subMenus, newMenu(nil, menu))
			}
			return menus
		}
	}

	return append(menus, newMenu(ancestors, menu))
}
<commit_msg>Remove meaning less TODO.<commit_after>package admin

import "path"

type Menu struct {
	Name      string
	Link      string
	Ancestors []string
	subMenus  []*Menu
	rawPath   string
}

func (admin Admin) GetMenus() []*Menu {
	return admin.menus
}

func (menu *Menu) GetSubMenus() []*Menu {
	return menu.subMenus
}

func (admin *Admin) AddMenu(menu *Menu) {
	admin.menus = appendMenu(admin.menus, menu.Ancestors, menu)
}

func (admin Admin) GetMenu(name string) *Menu {
	return getMenu(admin.menus, name)
}

func getMenu(menus []*Menu, name string) *Menu {
	for _, m := range menus {
		if m.Name == name {
			return m
		}

		if len(m.subMenus) > 0 {
			if mc := getMenu(m.subMenus, name); mc != nil {
				return mc
			}
		}
	}

	return nil
}

// Generate menu links by current route. e.g "/products" to "/admin/products"
func (admin *Admin) generateMenuLinks() {
	prefixMenuLinks(admin.menus, admin.router.Prefix)
}

func prefixMenuLinks(menus []*Menu, prefix string) {
	for _, m := range menus {
		if m.rawPath != "" {
			m.Link = path.Join(prefix, m.rawPath)
		}
		if len(m.subMenus) > 0 {
			prefixMenuLinks(m.subMenus, prefix)
		}
	}
}

func newMenu(menus []string, menu *Menu) *Menu {
	menuCount := len(menus)
	for index, _ := range menus {
		menu = &Menu{Name: menus[menuCount-index-1], subMenus: []*Menu{menu}}
	}

	return menu
}

func appendMenu(menus []*Menu, ancestors []string, menu *Menu) []*Menu {
	if len(ancestors) > 0 {
		for _, m := range menus {
			if m.Name != ancestors[0] {
				continue
			}

			if len(ancestors) > 1 {
				m.subMenus = appendMenu(m.subMenus, ancestors[1:], menu)
			} else {
				m.subMenus = append(m.subMenus, newMenu(nil, menu))
			}
			return menus
		}
	}

	return append(menus, newMenu(ancestors, menu))
}
<|endoftext|>


<commit_before>package upload

import (
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"time"
)

type Session struct {
	id string

	// Start and end time of a session. Session is
	// considered expired if now > end.
	start, end time.Time

	// chunks is a slice of chunk paths to be combined
	// into a single file
	chunks []string

	// Offset is the current offset in bytes that
	// represents the amount of data written
	offset int64

	// path is a directory where where chunks
	// for an upload session are stored
	path string
	// rootpath is a path relative to which session
	// directories are created
	rootpath string
}

func NewSession(storagepath string) *Session {
	return &Session{rootpath: storagepath}
}

// ID returns the id of current upload session.
func (s *Session) ID() string {
	return s.id
}

func (s *Session) Offset() int64 {
	return s.offset
}

// OffsetStr returns string representation of Offset.
func (s *Session) OffsetStr() string {
	return fmt.Sprintf("%d", s.offset)
}

// Init assings id, start/end time of a session and
// creates directory where chunks will be stored.
func (s *Session) Init() error {
	now := time.Now()
	s.id = fmt.Sprintf("%d", now.UnixNano())
	s.start = now
	s.end = now.AddDate(0, 0, 1)
	s.path = path.Join(s.rootpath, s.id)

	if err := os.Mkdir(s.path, 0775); err != nil {
		return err
	}
	return nil
}

func (s *Session) Expired() bool {
	return time.Now().After(s.end)
}

// Put writes a file chunk to disk in a separate file.
func (s *Session) Put(chunk io.Reader) error {
	tmppath := path.Join(s.path, fmt.Sprintf("%d.tmp", s.offset))
	chunkpath := path.Join(s.path, fmt.Sprintf("%d.chunk", s.offset))

	if err := s.write(tmppath, chunk); err != nil {
		return err
	}
	if err := os.Rename(tmppath, chunkpath); err != nil {
		return err
	}

	s.chunks = append(s.chunks, chunkpath)
	return nil
}

// Commit finishes an upload session by combining all its chunk into
// final destination file.
func (s *Session) Commit(filepath string) error {
	if file, err := os.Create(filepath); err != nil {
		return err
	} else {
		file.Close()
	}
	dst, err := os.OpenFile(filepath, os.O_APPEND|os.O_WRONLY, 0755)

	if err != nil {
		return err
	}
	defer dst.Close()

	for _, chunk := range s.chunks {
		if file, err := os.Open(chunk); err != nil {
			return err
		} else {
			io.Copy(dst, file)
			if err := file.Close(); err != nil {
				return err
			}
		}
	}
	if err := s.cleanup(); err != nil {
		return err
	}
	return nil
}

// cleanup removes all files and directories associated with a session
func (s *Session) cleanup() error {
	return os.RemoveAll(s.path)
}

func (s *Session) write(fpath string, data io.Reader) error {
	if file, err := os.Create(fpath); err != nil {
		return err
	} else {
		defer file.Close()

		if n, err := io.Copy(file, data); err != nil {
			return err
		} else {
			s.offset += int64(n)
			log.Printf("[Session %s] %d bytes written\n", s.id, n)
		}
	}
	return nil
}
<commit_msg>session: remove log statement<commit_after>package upload

import (
	"fmt"
	"io"
	"os"
	"path"
	"time"
)

type Session struct {
	id string

	// Start and end time of a session. Session is
	// considered expired if now > end.
	start, end time.Time

	// chunks is a slice of chunk paths to be combined
	// into a single file
	chunks []string

	// Offset is the current offset in bytes that
	// represents the amount of data written
	offset int64

	// path is a directory where where chunks
	// for an upload session are stored
	path string
	// rootpath is a path relative to which session
	// directories are created
	rootpath string
}

func NewSession(storagepath string) *Session {
	return &Session{rootpath: storagepath}
}

// ID returns the id of current upload session.
func (s *Session) ID() string {
	return s.id
}

func (s *Session) Offset() int64 {
	return s.offset
}

// OffsetStr returns string representation of Offset.
func (s *Session) OffsetStr() string {
	return fmt.Sprintf("%d", s.offset)
}

// Init assings id, start/end time of a session and
// creates directory where chunks will be stored.
func (s *Session) Init() error {
	now := time.Now()
	s.id = fmt.Sprintf("%d", now.UnixNano())
	s.start = now
	s.end = now.AddDate(0, 0, 1)
	s.path = path.Join(s.rootpath, s.id)

	if err := os.Mkdir(s.path, 0775); err != nil {
		return err
	}
	return nil
}

func (s *Session) Expired() bool {
	return time.Now().After(s.end)
}

// Put writes a file chunk to disk in a separate file.
func (s *Session) Put(chunk io.Reader) error {
	tmppath := path.Join(s.path, fmt.Sprintf("%d.tmp", s.offset))
	chunkpath := path.Join(s.path, fmt.Sprintf("%d.chunk", s.offset))

	if err := s.write(tmppath, chunk); err != nil {
		return err
	}
	if err := os.Rename(tmppath, chunkpath); err != nil {
		return err
	}

	s.chunks = append(s.chunks, chunkpath)
	return nil
}

// Commit finishes an upload session by combining all its chunk into
// final destination file.
func (s *Session) Commit(filepath string) error {
	if file, err := os.Create(filepath); err != nil {
		return err
	} else {
		file.Close()
	}
	dst, err := os.OpenFile(filepath, os.O_APPEND|os.O_WRONLY, 0755)

	if err != nil {
		return err
	}
	defer dst.Close()

	for _, chunk := range s.chunks {
		if file, err := os.Open(chunk); err != nil {
			return err
		} else {
			io.Copy(dst, file)
			if err := file.Close(); err != nil {
				return err
			}
		}
	}
	if err := s.cleanup(); err != nil {
		return err
	}
	return nil
}

// cleanup removes all files and directories associated with a session
func (s *Session) cleanup() error {
	return os.RemoveAll(s.path)
}

func (s *Session) write(fpath string, data io.Reader) error {
	if file, err := os.Create(fpath); err != nil {
		return err
	} else {
		defer file.Close()

		if n, err := io.Copy(file, data); err != nil {
			return err
		} else {
			s.offset += int64(n)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package ai

import (
	"bytes"
	"log"
	"math/rand"
	"time"

	"github.com/nelhage/taktician/bitboard"
	"github.com/nelhage/taktician/ptn"
	"github.com/nelhage/taktician/tak"
)

const (
	maxEval      int64 = 1 << 30
	minEval            = -maxEval
	winThreshold       = 1 << 29
)

type MinimaxAI struct {
	cfg  MinimaxConfig
	rand *rand.Rand

	st      Stats
	c       bitboard.Constants
	regions []uint64
}

type Stats struct {
	Generated uint64
	Evaluated uint64
	Cutoffs   uint64
}

type MinimaxConfig struct {
	Size  int
	Depth int
	Debug int
	Seed  int64
}

func NewMinimax(cfg MinimaxConfig) *MinimaxAI {
	m := &MinimaxAI{cfg: cfg}
	m.precompute()
	return m
}

func (m *MinimaxAI) precompute() {
	s := uint(m.cfg.Size)
	m.c = bitboard.Precompute(s)
	switch m.cfg.Size {
	// TODO(board-size)
	case 5:
		br := uint64((1 << 3) - 1)
		br |= br<<s | br<<(2*s)
		m.regions = []uint64{
			br, br << 2,
			br << (2 * s), br << (2*s + 2),
		}
	case 6:
		br := uint64((1 << 3) - 1)
		br |= br<<s | br<<(2*s)
		m.regions = []uint64{
			br, br << 3,
			br << (3 * s), br << (3*s + 3),
		}
	}
}

func formatpv(ms []tak.Move) string {
	var out bytes.Buffer
	out.WriteString("[")
	for i, m := range ms {
		if i != 0 {
			out.WriteString(" ")
		}
		out.WriteString(ptn.FormatMove(&m))
	}
	out.WriteString("]")
	return out.String()
}

func (m *MinimaxAI) GetMove(p *tak.Position, limit time.Duration) tak.Move {
	ms, _, _ := m.Analyze(p, limit)
	return ms[0]
}

func (m *MinimaxAI) Analyze(p *tak.Position, limit time.Duration) ([]tak.Move, int64, Stats) {
	if m.cfg.Size != p.Size() {
		panic("Analyze: wrong size")
	}

	var seed = m.cfg.Seed
	if seed == 0 {
		seed = time.Now().Unix()
	}
	m.rand = rand.New(rand.NewSource(seed))
	if m.cfg.Debug > 0 {
		log.Printf("seed=%d", seed)
	}

	var ms []tak.Move
	var v int64
	top := time.Now()
	var prevEval uint64
	var branchSum uint64
	for i := 1; i <= m.cfg.Depth; i++ {
		m.st = Stats{}
		start := time.Now()
		ms, v = m.minimax(p, 0, i, ms, minEval-1, maxEval+1)
		timeUsed := time.Now().Sub(top)
		timeMove := time.Now().Sub(start)
		if m.cfg.Debug > 0 {
			log.Printf("[minimax] deepen: depth=%d val=%d pv=%s time=%s total=%s evaluated=%d branch=%d",
				i, v, formatpv(ms),
				timeMove,
				timeUsed,
				m.st.Evaluated,
				m.st.Evaluated/(prevEval+1),
			)
		}
		if i > 1 {
			branchSum += m.st.Evaluated / (prevEval + 1)
		}
		prevEval = m.st.Evaluated
		if v > winThreshold || v < -winThreshold {
			break
		}
		if i > 2 && i != m.cfg.Depth {
			estimate := timeUsed + time.Now().Sub(start)*time.Duration(branchSum/uint64(i-1))
			if estimate > limit {
				if m.cfg.Debug > 0 {
					log.Printf("[minimax] time cutoff: depth=%d used=%s estimate=%s",
						i, timeUsed, estimate)
				}
				break
			}
		}
	}
	return ms, v, m.st
}

func (ai *MinimaxAI) minimax(
	p *tak.Position,
	ply, depth int,
	pv []tak.Move,
	α, β int64) ([]tak.Move, int64) {
	over, _ := p.GameOver()
	if depth == 0 || over {
		ai.st.Evaluated++
		return nil, ai.evaluate(p)
	}

	if p.MoveNumber() < 2 {
		for _, c := range [][]int{{0, 0}, {p.Size() - 1, 0}, {0, p.Size() - 1}, {p.Size() - 1, p.Size() - 1}} {
			x, y := c[0], c[1]
			if len(p.At(x, y)) == 0 {
				return []tak.Move{{X: x, Y: y, Type: tak.PlaceFlat}}, 0
			}
		}
	}
	moves := p.AllMoves()
	ai.st.Generated += uint64(len(moves))
	if ply == 0 {
		for i := len(moves) - 1; i > 0; i-- {
			j := ai.rand.Int31n(int32(i))
			moves[j], moves[i] = moves[i], moves[j]
		}
	}
	if len(pv) > 0 {
		for i, m := range moves {
			if m.Equal(&pv[0]) {
				moves[0], moves[i] = moves[i], moves[0]
				break
			}
		}
	}

	best := make([]tak.Move, 0, depth)
	best = append(best, pv...)
	max := minEval - 1
	for _, m := range moves {
		child, e := p.Move(&m)
		if e != nil {
			continue
		}
		var ms []tak.Move
		var newpv []tak.Move
		var v int64
		if len(best) != 0 {
			newpv = best[1:]
		}
		ms, v = ai.minimax(child, ply+1, depth-1, newpv, -β, -α)
		v = -v
		if ai.cfg.Debug > 2 && ply == 0 {
			log.Printf("[minimax] search: depth=%d ply=%d m=%s pv=%s window=(%d,%d) ms=%s v=%d evaluated=%d",
				depth, ply, ptn.FormatMove(&m), formatpv(newpv), α, β, formatpv(ms), v, ai.st.Evaluated)
		}
		if v > max {
			max = v
			best = append(best[:0], m)
			best = append(best, ms...)
		}
		if v > α {
			α = v
			if α >= β {
				ai.st.Cutoffs++
				break
			}
		}
	}
	return best, max
}
<commit_msg>remove the hardcoded opening<commit_after>package ai

import (
	"bytes"
	"log"
	"math/rand"
	"time"

	"github.com/nelhage/taktician/bitboard"
	"github.com/nelhage/taktician/ptn"
	"github.com/nelhage/taktician/tak"
)

const (
	maxEval      int64 = 1 << 30
	minEval            = -maxEval
	winThreshold       = 1 << 29
)

type MinimaxAI struct {
	cfg  MinimaxConfig
	rand *rand.Rand

	st      Stats
	c       bitboard.Constants
	regions []uint64
}

type Stats struct {
	Generated uint64
	Evaluated uint64
	Cutoffs   uint64
}

type MinimaxConfig struct {
	Size  int
	Depth int
	Debug int
	Seed  int64
}

func NewMinimax(cfg MinimaxConfig) *MinimaxAI {
	m := &MinimaxAI{cfg: cfg}
	m.precompute()
	return m
}

func (m *MinimaxAI) precompute() {
	s := uint(m.cfg.Size)
	m.c = bitboard.Precompute(s)
	switch m.cfg.Size {
	// TODO(board-size)
	case 5:
		br := uint64((1 << 3) - 1)
		br |= br<<s | br<<(2*s)
		m.regions = []uint64{
			br, br << 2,
			br << (2 * s), br << (2*s + 2),
		}
	case 6:
		br := uint64((1 << 3) - 1)
		br |= br<<s | br<<(2*s)
		m.regions = []uint64{
			br, br << 3,
			br << (3 * s), br << (3*s + 3),
		}
	}
}

func formatpv(ms []tak.Move) string {
	var out bytes.Buffer
	out.WriteString("[")
	for i, m := range ms {
		if i != 0 {
			out.WriteString(" ")
		}
		out.WriteString(ptn.FormatMove(&m))
	}
	out.WriteString("]")
	return out.String()
}

func (m *MinimaxAI) GetMove(p *tak.Position, limit time.Duration) tak.Move {
	ms, _, _ := m.Analyze(p, limit)
	return ms[0]
}

func (m *MinimaxAI) Analyze(p *tak.Position, limit time.Duration) ([]tak.Move, int64, Stats) {
	if m.cfg.Size != p.Size() {
		panic("Analyze: wrong size")
	}

	var seed = m.cfg.Seed
	if seed == 0 {
		seed = time.Now().Unix()
	}
	m.rand = rand.New(rand.NewSource(seed))
	if m.cfg.Debug > 0 {
		log.Printf("seed=%d", seed)
	}

	var ms []tak.Move
	var v int64
	top := time.Now()
	var prevEval uint64
	var branchSum uint64
	for i := 1; i <= m.cfg.Depth; i++ {
		m.st = Stats{}
		start := time.Now()
		ms, v = m.minimax(p, 0, i, ms, minEval-1, maxEval+1)
		timeUsed := time.Now().Sub(top)
		timeMove := time.Now().Sub(start)
		if m.cfg.Debug > 0 {
			log.Printf("[minimax] deepen: depth=%d val=%d pv=%s time=%s total=%s evaluated=%d branch=%d",
				i, v, formatpv(ms),
				timeMove,
				timeUsed,
				m.st.Evaluated,
				m.st.Evaluated/(prevEval+1),
			)
		}
		if i > 1 {
			branchSum += m.st.Evaluated / (prevEval + 1)
		}
		prevEval = m.st.Evaluated
		if v > winThreshold || v < -winThreshold {
			break
		}
		if i > 2 && i != m.cfg.Depth {
			estimate := timeUsed + time.Now().Sub(start)*time.Duration(branchSum/uint64(i-1))
			if estimate > limit {
				if m.cfg.Debug > 0 {
					log.Printf("[minimax] time cutoff: depth=%d used=%s estimate=%s",
						i, timeUsed, estimate)
				}
				break
			}
		}
	}
	return ms, v, m.st
}

func (ai *MinimaxAI) minimax(
	p *tak.Position,
	ply, depth int,
	pv []tak.Move,
	α, β int64) ([]tak.Move, int64) {
	over, _ := p.GameOver()
	if depth == 0 || over {
		ai.st.Evaluated++
		return nil, ai.evaluate(p)
	}

	moves := p.AllMoves()
	ai.st.Generated += uint64(len(moves))
	if ply == 0 {
		for i := len(moves) - 1; i > 0; i-- {
			j := ai.rand.Int31n(int32(i))
			moves[j], moves[i] = moves[i], moves[j]
		}
	}
	if len(pv) > 0 {
		for i, m := range moves {
			if m.Equal(&pv[0]) {
				moves[0], moves[i] = moves[i], moves[0]
				break
			}
		}
	}

	best := make([]tak.Move, 0, depth)
	best = append(best, pv...)
	max := minEval - 1
	for _, m := range moves {
		child, e := p.Move(&m)
		if e != nil {
			continue
		}
		var ms []tak.Move
		var newpv []tak.Move
		var v int64
		if len(best) != 0 {
			newpv = best[1:]
		}
		ms, v = ai.minimax(child, ply+1, depth-1, newpv, -β, -α)
		v = -v
		if ai.cfg.Debug > 2 && ply == 0 {
			log.Printf("[minimax] search: depth=%d ply=%d m=%s pv=%s window=(%d,%d) ms=%s v=%d evaluated=%d",
				depth, ply, ptn.FormatMove(&m), formatpv(newpv), α, β, formatpv(ms), v, ai.st.Evaluated)
		}
		if v > max {
			max = v
			best = append(best[:0], m)
			best = append(best, ms...)
		}
		if v > α {
			α = v
			if α >= β {
				ai.st.Cutoffs++
				break
			}
		}
	}
	return best, max
}
<|endoftext|>