<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package util

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	clientsetfake "k8s.io/client-go/kubernetes/fake"
	clienttesting "k8s.io/client-go/testing"
	extenderv1 "k8s.io/kube-scheduler/extender/v1"
)

func TestGetPodFullName(t *testing.T) {
	pod := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "test",
			Name:      "pod",
		},
	}
	got := GetPodFullName(pod)
	expected := fmt.Sprintf("%s_%s", pod.Name, pod.Namespace)
	if got != expected {
		t.Errorf("Got wrong full name, got: %s, expected: %s", got, expected)
	}
}

func newPriorityPodWithStartTime(name string, priority int32, startTime time.Time) *v1.Pod {
	return &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: v1.PodSpec{
			Priority: &priority,
		},
		Status: v1.PodStatus{
			StartTime: &metav1.Time{Time: startTime},
		},
	}
}

func TestGetEarliestPodStartTime(t *testing.T) {
	currentTime := time.Now()
	pod1 := newPriorityPodWithStartTime("pod1", 1, currentTime.Add(time.Second))
	pod2 := newPriorityPodWithStartTime("pod2", 2, currentTime.Add(time.Second))
	pod3 := newPriorityPodWithStartTime("pod3", 2, currentTime)
	victims := &extenderv1.Victims{
		Pods: []*v1.Pod{pod1, pod2, pod3},
	}
	startTime := GetEarliestPodStartTime(victims)
	if !startTime.Equal(pod3.Status.StartTime) {
		t.Errorf("Got wrong earliest pod start time")
	}

	pod1 = newPriorityPodWithStartTime("pod1", 2, currentTime)
	pod2 = newPriorityPodWithStartTime("pod2", 2, currentTime.Add(time.Second))
	pod3 = newPriorityPodWithStartTime("pod3", 2, currentTime.Add(2*time.Second))
	victims = &extenderv1.Victims{
		Pods: []*v1.Pod{pod1, pod2, pod3},
	}
	startTime = GetEarliestPodStartTime(victims)
	if !startTime.Equal(pod1.Status.StartTime) {
		t.Errorf("Got wrong earliest pod start time, got %v, expected %v", startTime, pod1.Status.StartTime)
	}
}

func TestMoreImportantPod(t *testing.T) {
	currentTime := time.Now()
	pod1 := newPriorityPodWithStartTime("pod1", 1, currentTime)
	pod2 := newPriorityPodWithStartTime("pod2", 2, currentTime.Add(time.Second))
	pod3 := newPriorityPodWithStartTime("pod3", 2, currentTime)

	tests := map[string]struct {
		p1       *v1.Pod
		p2       *v1.Pod
		expected bool
	}{
		"Pod with higher priority": {
			p1:       pod1,
			p2:       pod2,
			expected: false,
		},
		"Pod with older created time": {
			p1:       pod2,
			p2:       pod3,
			expected: false,
		},
		"Pods with same start time": {
			p1:       pod3,
			p2:       pod1,
			expected: true,
		},
	}

	for k, v := range tests {
		t.Run(k, func(t *testing.T) {
			got := MoreImportantPod(v.p1, v.p2)
			if got != v.expected {
				t.Errorf("expected %t but got %t", v.expected, got)
			}
		})
	}
}

func TestRemoveNominatedNodeName(t *testing.T) {
	tests := []struct {
		name                     string
		currentNominatedNodeName string
		newNominatedNodeName     string
		expectedPatchRequests    int
		expectedPatchData        string
	}{
		{
			name:                     "Should make patch request to clear node name",
			currentNominatedNodeName: "node1",
			expectedPatchRequests:    1,
			expectedPatchData:        `{"status":{"nominatedNodeName":null}}`,
		},
		{
			name:                     "Should not make patch request if nominated node is already cleared",
			currentNominatedNodeName: "",
			expectedPatchRequests:    0,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actualPatchRequests := 0
			var actualPatchData string
			cs := &clientsetfake.Clientset{}
			cs.AddReactor("patch", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) {
				actualPatchRequests++
				patch := action.(clienttesting.PatchAction)
				actualPatchData = string(patch.GetPatch())
				// For this test, we don't care about the result of the patched pod, just that we got the expected
				// patch request, so just returning &v1.Pod{} here is OK because scheduler doesn't use the response.
				return true, &v1.Pod{}, nil
			})

			pod := &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
				Status:     v1.PodStatus{NominatedNodeName: test.currentNominatedNodeName},
			}

			ctx, cancel := context.WithCancel(context.Background())
			defer cancel()
			if err := ClearNominatedNodeName(ctx, cs, pod); err != nil {
				t.Fatalf("Error calling removeNominatedNodeName: %v", err)
			}

			if actualPatchRequests != test.expectedPatchRequests {
				t.Fatalf("Actual patch requests (%d) dos not equal expected patch requests (%d)", actualPatchRequests, test.expectedPatchRequests)
			}

			if test.expectedPatchRequests > 0 && actualPatchData != test.expectedPatchData {
				t.Fatalf("Patch data mismatch: Actual was %v, but expected %v", actualPatchData, test.expectedPatchData)
			}
		})
	}
}

func TestPatchPodStatus(t *testing.T) {
	tests := []struct {
		name           string
		pod            v1.Pod
		statusToUpdate v1.PodStatus
	}{
		{
			name: "Should update pod conditions successfully",
			pod: v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Namespace: "ns",
					Name:      "pod1",
				},
				Spec: v1.PodSpec{
					ImagePullSecrets: []v1.LocalObjectReference{{Name: "foo"}},
				},
			},
			statusToUpdate: v1.PodStatus{
				Conditions: []v1.PodCondition{
					{
						Type:   v1.PodScheduled,
						Status: v1.ConditionFalse,
					},
				},
			},
		},
		{
			// ref: #101697, #94626 - ImagePullSecrets are allowed to have empty secret names
			// which would fail the 2-way merge patch generation on Pod patches
			// due to the mergeKey being the name field
			name: "Should update pod conditions successfully on a pod Spec with secrets with empty name",
			pod: v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Namespace: "ns",
					Name:      "pod2",
				},
				Spec: v1.PodSpec{
					// this will serialize to imagePullSecrets:[{}]
					ImagePullSecrets: make([]v1.LocalObjectReference, 1),
				},
			},
			statusToUpdate: v1.PodStatus{
				Conditions: []v1.PodCondition{
					{
						Type:   v1.PodScheduled,
						Status: v1.ConditionFalse,
					},
				},
			},
		},
	}

	client := clientsetfake.NewSimpleClientset()
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			_, err := client.CoreV1().Pods(tc.pod.Namespace).Create(context.TODO(), &tc.pod, metav1.CreateOptions{})
			if err != nil {
				t.Fatal(err)
			}

			ctx, cancel := context.WithCancel(context.Background())
			defer cancel()
			err = PatchPodStatus(ctx, client, &tc.pod, &tc.statusToUpdate)
			if err != nil {
				t.Fatal(err)
			}

			retrievedPod, err := client.CoreV1().Pods(tc.pod.Namespace).Get(ctx, tc.pod.Name, metav1.GetOptions{})
			if err != nil {
				t.Fatal(err)
			}

			if diff := cmp.Diff(tc.statusToUpdate, retrievedPod.Status); diff != "" {
				t.Errorf("unexpected pod status (-want,+got):\n%s", diff)
			}
		})
	}
}
<commit_msg>add unit test for pkg/kubelet/types/ func GetPodStartTime()<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package util

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	clientsetfake "k8s.io/client-go/kubernetes/fake"
	clienttesting "k8s.io/client-go/testing"
	extenderv1 "k8s.io/kube-scheduler/extender/v1"
)

func TestGetPodFullName(t *testing.T) {
	pod := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "test",
			Name:      "pod",
		},
	}
	got := GetPodFullName(pod)
	expected := fmt.Sprintf("%s_%s", pod.Name, pod.Namespace)
	if got != expected {
		t.Errorf("Got wrong full name, got: %s, expected: %s", got, expected)
	}
}

func newPriorityPodWithStartTime(name string, priority int32, startTime time.Time) *v1.Pod {
	return &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: v1.PodSpec{
			Priority: &priority,
		},
		Status: v1.PodStatus{
			StartTime: &metav1.Time{Time: startTime},
		},
	}
}

func TestGetEarliestPodStartTime(t *testing.T) {
	var priority int32 = 1
	currentTime := time.Now()
	tests := []struct {
		name              string
		pods              []*v1.Pod
		expectedStartTime *metav1.Time
	}{
		{
			name:              "Pods length is 0",
			pods:              []*v1.Pod{},
			expectedStartTime: nil,
		},
		{
			name: "generate new startTime",
			pods: []*v1.Pod{
				newPriorityPodWithStartTime("pod1", 1, currentTime.Add(-time.Second)),
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "pod2",
					},
					Spec: v1.PodSpec{
						Priority: &priority,
					},
				},
			},
			expectedStartTime: &metav1.Time{Time: currentTime.Add(-time.Second)},
		},
		{
			name: "Pod with earliest start time last in the list",
			pods: []*v1.Pod{
				newPriorityPodWithStartTime("pod1", 1, currentTime.Add(time.Second)),
				newPriorityPodWithStartTime("pod2", 2, currentTime.Add(time.Second)),
				newPriorityPodWithStartTime("pod3", 2, currentTime),
			},
			expectedStartTime: &metav1.Time{Time: currentTime},
		},
		{
			name: "Pod with earliest start time first in the list",
			pods: []*v1.Pod{
				newPriorityPodWithStartTime("pod1", 2, currentTime),
				newPriorityPodWithStartTime("pod2", 2, currentTime.Add(time.Second)),
				newPriorityPodWithStartTime("pod3", 2, currentTime.Add(2*time.Second)),
			},
			expectedStartTime: &metav1.Time{Time: currentTime},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			startTime := GetEarliestPodStartTime(&extenderv1.Victims{Pods: test.pods})
			if !startTime.Equal(test.expectedStartTime) {
				t.Errorf("startTime is not the expected result,got %v, expected %v", startTime, test.expectedStartTime)
			}
		})
	}
}

func TestMoreImportantPod(t *testing.T) {
	currentTime := time.Now()
	pod1 := newPriorityPodWithStartTime("pod1", 1, currentTime)
	pod2 := newPriorityPodWithStartTime("pod2", 2, currentTime.Add(time.Second))
	pod3 := newPriorityPodWithStartTime("pod3", 2, currentTime)

	tests := map[string]struct {
		p1       *v1.Pod
		p2       *v1.Pod
		expected bool
	}{
		"Pod with higher priority": {
			p1:       pod1,
			p2:       pod2,
			expected: false,
		},
		"Pod with older created time": {
			p1:       pod2,
			p2:       pod3,
			expected: false,
		},
		"Pods with same start time": {
			p1:       pod3,
			p2:       pod1,
			expected: true,
		},
	}

	for k, v := range tests {
		t.Run(k, func(t *testing.T) {
			got := MoreImportantPod(v.p1, v.p2)
			if got != v.expected {
				t.Errorf("expected %t but got %t", v.expected, got)
			}
		})
	}
}

func TestRemoveNominatedNodeName(t *testing.T) {
	tests := []struct {
		name                     string
		currentNominatedNodeName string
		newNominatedNodeName     string
		expectedPatchRequests    int
		expectedPatchData        string
	}{
		{
			name:                     "Should make patch request to clear node name",
			currentNominatedNodeName: "node1",
			expectedPatchRequests:    1,
			expectedPatchData:        `{"status":{"nominatedNodeName":null}}`,
		},
		{
			name:                     "Should not make patch request if nominated node is already cleared",
			currentNominatedNodeName: "",
			expectedPatchRequests:    0,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actualPatchRequests := 0
			var actualPatchData string
			cs := &clientsetfake.Clientset{}
			cs.AddReactor("patch", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) {
				actualPatchRequests++
				patch := action.(clienttesting.PatchAction)
				actualPatchData = string(patch.GetPatch())
				// For this test, we don't care about the result of the patched pod, just that we got the expected
				// patch request, so just returning &v1.Pod{} here is OK because scheduler doesn't use the response.
				return true, &v1.Pod{}, nil
			})

			pod := &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
				Status:     v1.PodStatus{NominatedNodeName: test.currentNominatedNodeName},
			}

			ctx, cancel := context.WithCancel(context.Background())
			defer cancel()
			if err := ClearNominatedNodeName(ctx, cs, pod); err != nil {
				t.Fatalf("Error calling removeNominatedNodeName: %v", err)
			}

			if actualPatchRequests != test.expectedPatchRequests {
				t.Fatalf("Actual patch requests (%d) dos not equal expected patch requests (%d)", actualPatchRequests, test.expectedPatchRequests)
			}

			if test.expectedPatchRequests > 0 && actualPatchData != test.expectedPatchData {
				t.Fatalf("Patch data mismatch: Actual was %v, but expected %v", actualPatchData, test.expectedPatchData)
			}
		})
	}
}

func TestPatchPodStatus(t *testing.T) {
	tests := []struct {
		name           string
		pod            v1.Pod
		statusToUpdate v1.PodStatus
	}{
		{
			name: "Should update pod conditions successfully",
			pod: v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Namespace: "ns",
					Name:      "pod1",
				},
				Spec: v1.PodSpec{
					ImagePullSecrets: []v1.LocalObjectReference{{Name: "foo"}},
				},
			},
			statusToUpdate: v1.PodStatus{
				Conditions: []v1.PodCondition{
					{
						Type:   v1.PodScheduled,
						Status: v1.ConditionFalse,
					},
				},
			},
		},
		{
			// ref: #101697, #94626 - ImagePullSecrets are allowed to have empty secret names
			// which would fail the 2-way merge patch generation on Pod patches
			// due to the mergeKey being the name field
			name: "Should update pod conditions successfully on a pod Spec with secrets with empty name",
			pod: v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Namespace: "ns",
					Name:      "pod2",
				},
				Spec: v1.PodSpec{
					// this will serialize to imagePullSecrets:[{}]
					ImagePullSecrets: make([]v1.LocalObjectReference, 1),
				},
			},
			statusToUpdate: v1.PodStatus{
				Conditions: []v1.PodCondition{
					{
						Type:   v1.PodScheduled,
						Status: v1.ConditionFalse,
					},
				},
			},
		},
	}

	client := clientsetfake.NewSimpleClientset()
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			_, err := client.CoreV1().Pods(tc.pod.Namespace).Create(context.TODO(), &tc.pod, metav1.CreateOptions{})
			if err != nil {
				t.Fatal(err)
			}

			ctx, cancel := context.WithCancel(context.Background())
			defer cancel()
			err = PatchPodStatus(ctx, client, &tc.pod, &tc.statusToUpdate)
			if err != nil {
				t.Fatal(err)
			}

			retrievedPod, err := client.CoreV1().Pods(tc.pod.Namespace).Get(ctx, tc.pod.Name, metav1.GetOptions{})
			if err != nil {
				t.Fatal(err)
			}

			if diff := cmp.Diff(tc.statusToUpdate, retrievedPod.Status); diff != "" {
				t.Errorf("unexpected pod status (-want,+got):\n%s", diff)
			}
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package server

import (
	"bytes"
	"io"
	"syscall"
	"time"

	"github.com/containerd/containerd"
	containerdio "github.com/containerd/containerd/cio"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/oci"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/context"
	"k8s.io/client-go/tools/remotecommand"
	runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"

	ctrdutil "github.com/containerd/cri/pkg/containerd/util"
	cioutil "github.com/containerd/cri/pkg/ioutil"
	cio "github.com/containerd/cri/pkg/server/io"
	"github.com/containerd/cri/pkg/util"
)

// ExecSync executes a command in the container, and returns the stdout output.
// If command exits with a non-zero exit code, an error is returned.
func (c *criService) ExecSync(ctx context.Context, r *runtime.ExecSyncRequest) (*runtime.ExecSyncResponse, error) {
	var stdout, stderr bytes.Buffer
	exitCode, err := c.execInContainer(ctx, r.GetContainerId(), execOptions{
		cmd:     r.GetCmd(),
		stdout:  cioutil.NewNopWriteCloser(&stdout),
		stderr:  cioutil.NewNopWriteCloser(&stderr),
		timeout: time.Duration(r.GetTimeout()) * time.Second,
	})
	if err != nil {
		return nil, errors.Wrap(err, "failed to exec in container")
	}

	return &runtime.ExecSyncResponse{
		Stdout:   stdout.Bytes(),
		Stderr:   stderr.Bytes(),
		ExitCode: int32(*exitCode),
	}, nil
}

// execOptions specifies how to execute command in container.
type execOptions struct {
	cmd     []string
	stdin   io.Reader
	stdout  io.WriteCloser
	stderr  io.WriteCloser
	tty     bool
	resize  <-chan remotecommand.TerminalSize
	timeout time.Duration
}

// execInContainer executes a command inside the container synchronously, and
// redirects stdio stream properly.
func (c *criService) execInContainer(ctx context.Context, id string, opts execOptions) (*uint32, error) {
	// Cancel the context before returning to ensure goroutines are stopped.
	// This is important, because if `Start` returns error, `Wait` will hang
	// forever unless we cancel the context.
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	// Get container from our container store.
	cntr, err := c.containerStore.Get(id)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to find container %q in store", id)
	}
	id = cntr.ID

	state := cntr.Status.Get().State()
	if state != runtime.ContainerState_CONTAINER_RUNNING {
		return nil, errors.Errorf("container is in %s state", criContainerStateToString(state))
	}

	container := cntr.Container
	spec, err := container.Spec(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "failed to get container spec")
	}
	task, err := container.Task(ctx, nil)
	if err != nil {
		return nil, errors.Wrap(err, "failed to load task")
	}
	pspec := spec.Process

	pspec.Terminal = opts.tty
	if opts.tty {
		if err := oci.WithEnv([]string{"TERM=xterm"})(nil, nil, nil, spec); err != nil {
			return nil, errors.Wrap(err, "add TERM env var to spec")
		}
	}

	pspec.Args = opts.cmd

	if opts.stdout == nil {
		opts.stdout = cio.NewDiscardLogger()
	}
	if opts.stderr == nil {
		opts.stderr = cio.NewDiscardLogger()
	}
	execID := util.GenerateID()
	logrus.Debugf("Generated exec id %q for container %q", execID, id)
	volatileRootDir := c.getVolatileContainerRootDir(id)
	var execIO *cio.ExecIO
	process, err := task.Exec(ctx, execID, pspec,
		func(id string) (containerdio.IO, error) {
			var err error
			execIO, err = cio.NewExecIO(id, volatileRootDir, opts.tty, opts.stdin != nil)
			return execIO, err
		},
	)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to create exec %q", execID)
	}
	defer func() {
		deferCtx, deferCancel := ctrdutil.DeferContext()
		defer deferCancel()
		if _, err := process.Delete(deferCtx); err != nil {
			logrus.WithError(err).Errorf("Failed to delete exec process %q for container %q", execID, id)
		}
	}()

	exitCh, err := process.Wait(ctx)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to wait for process %q", execID)
	}
	if err := process.Start(ctx); err != nil {
		return nil, errors.Wrapf(err, "failed to start exec %q", execID)
	}

	handleResizing(opts.resize, func(size remotecommand.TerminalSize) {
		if err := process.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
			logrus.WithError(err).Errorf("Failed to resize process %q console for container %q", execID, id)
		}
	})

	attachDone := execIO.Attach(cio.AttachOptions{
		Stdin:     opts.stdin,
		Stdout:    opts.stdout,
		Stderr:    opts.stderr,
		Tty:       opts.tty,
		StdinOnce: true,
		CloseStdin: func() error {
			return process.CloseIO(ctx, containerd.WithStdinCloser)
		},
	})

	var timeoutCh <-chan time.Time
	if opts.timeout == 0 {
		// Do not set timeout if it's 0.
		timeoutCh = make(chan time.Time)
	} else {
		timeoutCh = time.After(opts.timeout)
	}
	select {
	case <-timeoutCh:
		//TODO(Abhi) Use context.WithDeadline instead of timeout.
		// Ignore the not found error because the process may exit itself before killing.
		if err := process.Kill(ctx, syscall.SIGKILL); err != nil && !errdefs.IsNotFound(err) {
			return nil, errors.Wrapf(err, "failed to kill exec %q", execID)
		}
		// Wait for the process to be killed.
		exitRes := <-exitCh
		logrus.Infof("Timeout received while waiting for exec process kill %q code %d and error %v",
			execID, exitRes.ExitCode(), exitRes.Error())
		<-attachDone
		logrus.Debugf("Stream pipe for exec process %q done", execID)
		return nil, errors.Errorf("timeout %v exceeded", opts.timeout)
	case exitRes := <-exitCh:
		code, _, err := exitRes.Result()
		logrus.Infof("Exec process %q exits with exit code %d and error %v", execID, code, err)
		if err != nil {
			return nil, errors.Wrapf(err, "failed while waiting for exec %q", execID)
		}
		<-attachDone
		logrus.Debugf("Stream pipe for exec process %q done", execID)
		return &code, nil
	}
}
<commit_msg>Make sure exec process is killed when context is canceled.<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package server

import (
	"bytes"
	"io"
	"syscall"
	"time"

	"github.com/containerd/containerd"
	containerdio "github.com/containerd/containerd/cio"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/oci"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/context"
	"k8s.io/client-go/tools/remotecommand"
	runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"

	ctrdutil "github.com/containerd/cri/pkg/containerd/util"
	cioutil "github.com/containerd/cri/pkg/ioutil"
	cio "github.com/containerd/cri/pkg/server/io"
	"github.com/containerd/cri/pkg/util"
)

// ExecSync executes a command in the container, and returns the stdout output.
// If command exits with a non-zero exit code, an error is returned.
func (c *criService) ExecSync(ctx context.Context, r *runtime.ExecSyncRequest) (*runtime.ExecSyncResponse, error) {
	var stdout, stderr bytes.Buffer
	exitCode, err := c.execInContainer(ctx, r.GetContainerId(), execOptions{
		cmd:     r.GetCmd(),
		stdout:  cioutil.NewNopWriteCloser(&stdout),
		stderr:  cioutil.NewNopWriteCloser(&stderr),
		timeout: time.Duration(r.GetTimeout()) * time.Second,
	})
	if err != nil {
		return nil, errors.Wrap(err, "failed to exec in container")
	}

	return &runtime.ExecSyncResponse{
		Stdout:   stdout.Bytes(),
		Stderr:   stderr.Bytes(),
		ExitCode: int32(*exitCode),
	}, nil
}

// execOptions specifies how to execute command in container.
type execOptions struct {
	cmd     []string
	stdin   io.Reader
	stdout  io.WriteCloser
	stderr  io.WriteCloser
	tty     bool
	resize  <-chan remotecommand.TerminalSize
	timeout time.Duration
}

// execInContainer executes a command inside the container synchronously, and
// redirects stdio stream properly.
func (c *criService) execInContainer(ctx context.Context, id string, opts execOptions) (*uint32, error) {
	// Cancel the context before returning to ensure goroutines are stopped.
	// This is important, because if `Start` returns error, `Wait` will hang
	// forever unless we cancel the context.
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	// Get container from our container store.
	cntr, err := c.containerStore.Get(id)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to find container %q in store", id)
	}
	id = cntr.ID

	state := cntr.Status.Get().State()
	if state != runtime.ContainerState_CONTAINER_RUNNING {
		return nil, errors.Errorf("container is in %s state", criContainerStateToString(state))
	}

	container := cntr.Container
	spec, err := container.Spec(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "failed to get container spec")
	}
	task, err := container.Task(ctx, nil)
	if err != nil {
		return nil, errors.Wrap(err, "failed to load task")
	}
	pspec := spec.Process

	pspec.Terminal = opts.tty
	if opts.tty {
		if err := oci.WithEnv([]string{"TERM=xterm"})(nil, nil, nil, spec); err != nil {
			return nil, errors.Wrap(err, "add TERM env var to spec")
		}
	}

	pspec.Args = opts.cmd

	if opts.stdout == nil {
		opts.stdout = cio.NewDiscardLogger()
	}
	if opts.stderr == nil {
		opts.stderr = cio.NewDiscardLogger()
	}
	execID := util.GenerateID()
	logrus.Debugf("Generated exec id %q for container %q", execID, id)
	volatileRootDir := c.getVolatileContainerRootDir(id)
	var execIO *cio.ExecIO
	process, err := task.Exec(ctx, execID, pspec,
		func(id string) (containerdio.IO, error) {
			var err error
			execIO, err = cio.NewExecIO(id, volatileRootDir, opts.tty, opts.stdin != nil)
			return execIO, err
		},
	)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to create exec %q", execID)
	}
	defer func() {
		deferCtx, deferCancel := ctrdutil.DeferContext()
		defer deferCancel()
		if _, err := process.Delete(deferCtx, containerd.WithProcessKill); err != nil {
			logrus.WithError(err).Errorf("Failed to delete exec process %q for container %q", execID, id)
		}
	}()

	exitCh, err := process.Wait(ctx)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to wait for process %q", execID)
	}
	if err := process.Start(ctx); err != nil {
		return nil, errors.Wrapf(err, "failed to start exec %q", execID)
	}

	handleResizing(opts.resize, func(size remotecommand.TerminalSize) {
		if err := process.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
			logrus.WithError(err).Errorf("Failed to resize process %q console for container %q", execID, id)
		}
	})

	attachDone := execIO.Attach(cio.AttachOptions{
		Stdin:     opts.stdin,
		Stdout:    opts.stdout,
		Stderr:    opts.stderr,
		Tty:       opts.tty,
		StdinOnce: true,
		CloseStdin: func() error {
			return process.CloseIO(ctx, containerd.WithStdinCloser)
		},
	})

	var timeoutCh <-chan time.Time
	if opts.timeout == 0 {
		// Do not set timeout if it's 0.
		timeoutCh = make(chan time.Time)
	} else {
		timeoutCh = time.After(opts.timeout)
	}
	select {
	case <-timeoutCh:
		//TODO(Abhi) Use context.WithDeadline instead of timeout.
		// Ignore the not found error because the process may exit itself before killing.
		if err := process.Kill(ctx, syscall.SIGKILL); err != nil && !errdefs.IsNotFound(err) {
			return nil, errors.Wrapf(err, "failed to kill exec %q", execID)
		}
		// Wait for the process to be killed.
		exitRes := <-exitCh
		logrus.Infof("Timeout received while waiting for exec process kill %q code %d and error %v",
			execID, exitRes.ExitCode(), exitRes.Error())
		<-attachDone
		logrus.Debugf("Stream pipe for exec process %q done", execID)
		return nil, errors.Errorf("timeout %v exceeded", opts.timeout)
	case exitRes := <-exitCh:
		code, _, err := exitRes.Result()
		logrus.Infof("Exec process %q exits with exit code %d and error %v", execID, code, err)
		if err != nil {
			return nil, errors.Wrapf(err, "failed while waiting for exec %q", execID)
		}
		<-attachDone
		logrus.Debugf("Stream pipe for exec process %q done", execID)
		return &code, nil
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold Authors

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

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

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

package deploy

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	yaml "gopkg.in/yaml.v2"

	"github.com/segmentio/textio"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/color"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
	deploy "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/kubectl"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/event"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubectl"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
)

// kustomization is the content of a kustomization.yaml file.
type kustomization struct {
	Bases                 []string             `yaml:"bases"`
	Resources             []string             `yaml:"resources"`
	Patches               []string             `yaml:"patches"`
	PatchesStrategicMerge []string             `yaml:"patchesStrategicMerge"`
	CRDs                  []string             `yaml:"crds"`
	PatchesJSON6902       []patchJSON6902      `yaml:"patchesJson6902"`
	ConfigMapGenerator    []configMapGenerator `yaml:"configMapGenerator"`
	SecretGenerator       []secretGenerator    `yaml:"secretGenerator"`
}

type patchJSON6902 struct {
	Path string `yaml:"path"`
}

type configMapGenerator struct {
	Files []string `yaml:"files"`
}

type secretGenerator struct {
	Files []string `yaml:"files"`
}

// KustomizeDeployer deploys workflows using kustomize CLI.
type KustomizeDeployer struct {
	*latest.KustomizeDeploy

	kubectl            deploy.CLI
	insecureRegistries map[string]bool
	BuildArgs          []string
}

func NewKustomizeDeployer(runCtx *runcontext.RunContext) *KustomizeDeployer {
	return &KustomizeDeployer{
		KustomizeDeploy: runCtx.Cfg.Deploy.KustomizeDeploy,
		kubectl: deploy.CLI{
			CLI:         kubectl.NewFromRunContext(runCtx),
			Flags:       runCtx.Cfg.Deploy.KustomizeDeploy.Flags,
			ForceDeploy: runCtx.Opts.Force,
		},
		insecureRegistries: runCtx.InsecureRegistries,
		BuildArgs:          runCtx.Cfg.Deploy.KustomizeDeploy.BuildArgs,
	}
}

// Labels returns the labels specific to kustomize.
func (k *KustomizeDeployer) Labels() map[string]string {
	return map[string]string{
		constants.Labels.Deployer: "kustomize",
	}
}

// Deploy runs `kubectl apply` on the manifest generated by kustomize.
func (k *KustomizeDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) *Result {
	if err := k.kubectl.CheckVersion(ctx); err != nil {
		color.Default.Fprintln(out, "kubectl client version:", k.kubectl.Version(ctx))
		color.Default.Fprintln(out, err)
	}

	manifests, err := k.readManifests(ctx)
	if err != nil {
		event.DeployFailed(err)
		return NewDeployErrorResult(errors.Wrap(err, "reading manifests"))
	}

	if len(manifests) == 0 {
		return NewDeploySuccessResult(nil)
	}

	event.DeployInProgress()

	namespaces, err := manifests.CollectNamespaces()
	if err != nil {
		event.DeployInfoEvent(errors.Wrap(err, "could not fetch deployed resource namespace."+
			"This might cause port-forward and deploy health-check to fail."))
	}
	manifests, err = manifests.ReplaceImages(builds)
	if err != nil {
		event.DeployFailed(err)
		return NewDeployErrorResult(errors.Wrap(err, "replacing images in manifests"))
	}

	manifests, err = manifests.SetLabels(merge(labellers...))
	if err != nil {
		event.DeployFailed(err)
		return NewDeployErrorResult(errors.Wrap(err, "setting labels in manifests"))
	}

	for _, transform := range manifestTransforms {
		manifests, err = transform(manifests, builds, k.insecureRegistries)
		if err != nil {
			event.DeployFailed(err)
			return NewDeployErrorResult(errors.Wrap(err, "unable to transform manifests"))
		}
	}

	if err := k.kubectl.Apply(ctx, textio.NewPrefixWriter(out, " - "), manifests); err != nil {
		event.DeployFailed(err)
		return NewDeployErrorResult(errors.Wrap(err, "kubectl error"))
	}

	event.DeployComplete()
	return NewDeploySuccessResult(namespaces)
}

// Cleanup deletes what was deployed by calling Deploy.
func (k *KustomizeDeployer) Cleanup(ctx context.Context, out io.Writer) error {
	manifests, err := k.readManifests(ctx)
	if err != nil {
		return errors.Wrap(err, "reading manifests")
	}

	if err := k.kubectl.Delete(ctx, textio.NewPrefixWriter(out, " - "), manifests); err != nil {
		return errors.Wrap(err, "delete")
	}

	return nil
}

// Dependencies lists all the files that can change what needs to be deployed.
func (k *KustomizeDeployer) Dependencies() ([]string, error) {
	return dependenciesForKustomization(k.KustomizePath)
}

func (k *KustomizeDeployer) Render(context.Context, io.Writer, []build.Artifact, string) error {
	return errors.New("not yet implemented")
}

func dependenciesForKustomization(dir string) ([]string, error) {
	var deps []string

	path, err := findKustomizationConfig(dir)
	if err != nil {
		// No kustomization config found so assume it's remote and stop traversing
		return deps, nil
	}

	buf, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}

	content := kustomization{}
	if err := yaml.Unmarshal(buf, &content); err != nil {
		return nil, err
	}

	deps = append(deps, path)

	candidates := append(content.Bases, content.Resources...)

	for _, candidate := range candidates {
		// If the file  doesn't exist locally, we can assume it's a remote file and
		// skip it, since we can't monitor remote files. Kustomize itself will
		// handle invalid/missing files.
		local, mode := pathExistsLocally(candidate, dir)
		if !local {
			continue
		}

		if mode.IsDir() {
			candidateDeps, err := dependenciesForKustomization(filepath.Join(dir, candidate))
			if err != nil {
				return nil, err
			}
			deps = append(deps, candidateDeps...)
		} else {
			deps = append(deps, filepath.Join(dir, candidate))
		}
	}

	deps = append(deps, util.AbsolutePaths(dir, content.Patches)...)
	deps = append(deps, util.AbsolutePaths(dir, content.PatchesStrategicMerge)...)
	deps = append(deps, util.AbsolutePaths(dir, content.CRDs)...)
	for _, patch := range content.PatchesJSON6902 {
		deps = append(deps, filepath.Join(dir, patch.Path))
	}
	for _, generator := range content.ConfigMapGenerator {
		deps = append(deps, util.AbsolutePaths(dir, generator.Files)...)
	}
	for _, generator := range content.SecretGenerator {
		deps = append(deps, util.AbsolutePaths(dir, generator.Files)...)
	}

	return deps, nil
}

// A Kustomization config must be at the root of the directory. Kustomize will
// error if more than one of these files exists so order doesn't matter.
func findKustomizationConfig(dir string) (string, error) {
	candidates := []string{"kustomization.yaml", "kustomization.yml", "Kustomization"}
	for _, candidate := range candidates {
		if local, _ := pathExistsLocally(candidate, dir); local {
			return filepath.Join(dir, candidate), nil
		}
	}
	return "", fmt.Errorf("no Kustomization configuration found in directory: %s", dir)
}

func pathExistsLocally(filename string, workingDir string) (bool, os.FileMode) {
	path := filename
	if !filepath.IsAbs(filename) {
		path = filepath.Join(workingDir, filename)
	}
	if f, err := os.Stat(path); err == nil {
		return true, f.Mode()
	}
	return false, 0
}

func (k *KustomizeDeployer) readManifests(ctx context.Context) (deploy.ManifestList, error) {
	cmd := exec.CommandContext(ctx, "kustomize", buildCommandArgs(k.BuildArgs, k.KustomizePath)...)
	out, err := util.RunCmdOut(cmd)
	if err != nil {
		return nil, errors.Wrap(err, "kustomize build")
	}

	if len(out) == 0 {
		return nil, nil
	}

	var manifests deploy.ManifestList
	manifests.Append(out)
	return manifests, nil
}

func buildCommandArgs(buildArgs []string, kustomizePath string) []string {
	var args []string
	args = append(args, "build")

	if len(buildArgs) > 0 {
		for _, v := range buildArgs {
			parts := strings.Split(v, " ")
			args = append(args, parts...)
		}
	}

	if len(kustomizePath) > 0 {
		args = append(args, kustomizePath)
	}

	return args
}
<commit_msg>Add render for kustomize<commit_after>/*
Copyright 2019 The Skaffold Authors

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

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

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

package deploy

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	yaml "gopkg.in/yaml.v2"

	"github.com/segmentio/textio"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/color"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
	deploy "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/kubectl"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/event"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubectl"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
)

// kustomization is the content of a kustomization.yaml file.
type kustomization struct {
	Bases                 []string             `yaml:"bases"`
	Resources             []string             `yaml:"resources"`
	Patches               []string             `yaml:"patches"`
	PatchesStrategicMerge []string             `yaml:"patchesStrategicMerge"`
	CRDs                  []string             `yaml:"crds"`
	PatchesJSON6902       []patchJSON6902      `yaml:"patchesJson6902"`
	ConfigMapGenerator    []configMapGenerator `yaml:"configMapGenerator"`
	SecretGenerator       []secretGenerator    `yaml:"secretGenerator"`
}

type patchJSON6902 struct {
	Path string `yaml:"path"`
}

type configMapGenerator struct {
	Files []string `yaml:"files"`
}

type secretGenerator struct {
	Files []string `yaml:"files"`
}

// KustomizeDeployer deploys workflows using kustomize CLI.
type KustomizeDeployer struct {
	*latest.KustomizeDeploy

	kubectl            deploy.CLI
	insecureRegistries map[string]bool
	BuildArgs          []string
}

func NewKustomizeDeployer(runCtx *runcontext.RunContext) *KustomizeDeployer {
	return &KustomizeDeployer{
		KustomizeDeploy: runCtx.Cfg.Deploy.KustomizeDeploy,
		kubectl: deploy.CLI{
			CLI:         kubectl.NewFromRunContext(runCtx),
			Flags:       runCtx.Cfg.Deploy.KustomizeDeploy.Flags,
			ForceDeploy: runCtx.Opts.Force,
		},
		insecureRegistries: runCtx.InsecureRegistries,
		BuildArgs:          runCtx.Cfg.Deploy.KustomizeDeploy.BuildArgs,
	}
}

// Labels returns the labels specific to kustomize.
func (k *KustomizeDeployer) Labels() map[string]string {
	return map[string]string{
		constants.Labels.Deployer: "kustomize",
	}
}

// Deploy runs `kubectl apply` on the manifest generated by kustomize.
func (k *KustomizeDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) *Result {
	manifests, err := k.renderManifests(ctx, out, builds, labellers)
	if err != nil {
		event.DeployFailed(err)
		return NewDeployErrorResult(err)
	}

	if len(manifests) == 0 {
		return NewDeploySuccessResult(nil)
	}

	event.DeployInProgress()

	namespaces, err := manifests.CollectNamespaces()
	if err != nil {
		event.DeployInfoEvent(errors.Wrap(err, "could not fetch deployed resource namespace."+
			"This might cause port-forward and deploy health-check to fail."))
	}

	if err := k.kubectl.Apply(ctx, textio.NewPrefixWriter(out, " - "), manifests); err != nil {
		event.DeployFailed(err)
		return NewDeployErrorResult(errors.Wrap(err, "kubectl error"))
	}

	event.DeployComplete()
	return NewDeploySuccessResult(namespaces)
}

func (k *KustomizeDeployer) renderManifests(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) (deploy.ManifestList, error) {
	if err := k.kubectl.CheckVersion(ctx); err != nil {
		color.Default.Fprintln(out, "kubectl client version:", k.kubectl.Version(ctx))
		color.Default.Fprintln(out, err)
	}

	manifests, err := k.readManifests(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "reading manifests")
	}

	if len(manifests) == 0 {
		return nil, nil
	}

	manifests, err = manifests.ReplaceImages(builds)
	if err != nil {
		return nil, errors.Wrap(err, "replacing images in manifests")
	}

	manifests, err = manifests.SetLabels(merge(labellers...))
	if err != nil {
		return nil, errors.Wrap(err, "setting labels in manifests")
	}

	for _, transform := range manifestTransforms {
		manifests, err = transform(manifests, builds, k.insecureRegistries)
		if err != nil {
			return nil, errors.Wrap(err, "unable to transform manifests")
		}
	}
	return manifests, nil
}

// Cleanup deletes what was deployed by calling Deploy.
func (k *KustomizeDeployer) Cleanup(ctx context.Context, out io.Writer) error {
	manifests, err := k.readManifests(ctx)
	if err != nil {
		return errors.Wrap(err, "reading manifests")
	}

	if err := k.kubectl.Delete(ctx, textio.NewPrefixWriter(out, " - "), manifests); err != nil {
		return errors.Wrap(err, "delete")
	}

	return nil
}

// Dependencies lists all the files that can change what needs to be deployed.
func (k *KustomizeDeployer) Dependencies() ([]string, error) {
	return dependenciesForKustomization(k.KustomizePath)
}

func (k *KustomizeDeployer) Render(ctx context.Context, out io.Writer, builds []build.Artifact, filepath string) error {
	manifests, err := k.renderManifests(ctx, out, builds, nil)
	if err != nil {
		return err
	}

	manifestOut := out
	if filepath != "" {
		f, err := os.Open(filepath)
		if err != nil {
			return errors.Wrap(err, "opening file for writing manifests")
		}
		manifestOut = bufio.NewWriter(f)
	}

	fmt.Fprintln(manifestOut, manifests.String())
	return nil
}

func dependenciesForKustomization(dir string) ([]string, error) {
	var deps []string

	path, err := findKustomizationConfig(dir)
	if err != nil {
		// No kustomization config found so assume it's remote and stop traversing
		return deps, nil
	}

	buf, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}

	content := kustomization{}
	if err := yaml.Unmarshal(buf, &content); err != nil {
		return nil, err
	}

	deps = append(deps, path)

	candidates := append(content.Bases, content.Resources...)

	for _, candidate := range candidates {
		// If the file  doesn't exist locally, we can assume it's a remote file and
		// skip it, since we can't monitor remote files. Kustomize itself will
		// handle invalid/missing files.
		local, mode := pathExistsLocally(candidate, dir)
		if !local {
			continue
		}

		if mode.IsDir() {
			candidateDeps, err := dependenciesForKustomization(filepath.Join(dir, candidate))
			if err != nil {
				return nil, err
			}
			deps = append(deps, candidateDeps...)
		} else {
			deps = append(deps, filepath.Join(dir, candidate))
		}
	}

	deps = append(deps, util.AbsolutePaths(dir, content.Patches)...)
	deps = append(deps, util.AbsolutePaths(dir, content.PatchesStrategicMerge)...)
	deps = append(deps, util.AbsolutePaths(dir, content.CRDs)...)
	for _, patch := range content.PatchesJSON6902 {
		deps = append(deps, filepath.Join(dir, patch.Path))
	}
	for _, generator := range content.ConfigMapGenerator {
		deps = append(deps, util.AbsolutePaths(dir, generator.Files)...)
	}
	for _, generator := range content.SecretGenerator {
		deps = append(deps, util.AbsolutePaths(dir, generator.Files)...)
	}

	return deps, nil
}

// A Kustomization config must be at the root of the directory. Kustomize will
// error if more than one of these files exists so order doesn't matter.
func findKustomizationConfig(dir string) (string, error) {
	candidates := []string{"kustomization.yaml", "kustomization.yml", "Kustomization"}
	for _, candidate := range candidates {
		if local, _ := pathExistsLocally(candidate, dir); local {
			return filepath.Join(dir, candidate), nil
		}
	}
	return "", fmt.Errorf("no Kustomization configuration found in directory: %s", dir)
}

func pathExistsLocally(filename string, workingDir string) (bool, os.FileMode) {
	path := filename
	if !filepath.IsAbs(filename) {
		path = filepath.Join(workingDir, filename)
	}
	if f, err := os.Stat(path); err == nil {
		return true, f.Mode()
	}
	return false, 0
}

func (k *KustomizeDeployer) readManifests(ctx context.Context) (deploy.ManifestList, error) {
	cmd := exec.CommandContext(ctx, "kustomize", buildCommandArgs(k.BuildArgs, k.KustomizePath)...)
	out, err := util.RunCmdOut(cmd)
	if err != nil {
		return nil, errors.Wrap(err, "kustomize build")
	}

	if len(out) == 0 {
		return nil, nil
	}

	var manifests deploy.ManifestList
	manifests.Append(out)
	return manifests, nil
}

func buildCommandArgs(buildArgs []string, kustomizePath string) []string {
	var args []string
	args = append(args, "build")

	if len(buildArgs) > 0 {
		for _, v := range buildArgs {
			parts := strings.Split(v, " ")
			args = append(args, parts...)
		}
	}

	if len(kustomizePath) > 0 {
		args = append(args, kustomizePath)
	}

	return args
}
<|endoftext|>


<commit_before>// Copyright 2022 Google LLC
//
// 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 sourcereader

import (
	"hpc-toolkit/pkg/deploymentio"
	"hpc-toolkit/pkg/modulereader"
	"log"
	"strings"
)

const (
	local = iota
	embedded
	github
)

// SourceReader interface for reading modules from a source
type SourceReader interface {
	// GetModuleInfo would leverage modulereader.GetInfo for the given kind.
	// GetModuleInfo would operate over the source without creating a local copy.
	// This would be very dependent on the kind of module.
	GetModuleInfo(modPath string, kind string) (modulereader.ModuleInfo, error)

	// GetModule copies the source to a provided local destination (the deployment directory).
	GetModule(modPath string, copyPath string) error
}

var readers = map[int]SourceReader{
	local:    LocalSourceReader{},
	embedded: EmbeddedSourceReader{},
	github:   GitHubSourceReader{},
}

// IsLocalPath checks if a source path is a local FS path
func IsLocalPath(source string) bool {
	return strings.HasPrefix(source, "./") ||
		strings.HasPrefix(source, "../") ||
		strings.HasPrefix(source, "/")
}

// IsEmbeddedPath checks if a source path points to an embedded modules
func IsEmbeddedPath(source string) bool {
	return strings.HasPrefix(source, "modules/") || strings.HasPrefix(source, "community/modules/")
}

// IsGitHubPath checks if a source path points to GitHub
func IsGitHubPath(source string) bool {
	return strings.HasPrefix(source, "github.com") || strings.HasPrefix(source, "git@github.com")
}

// Factory returns a SourceReader of module path
func Factory(modPath string) SourceReader {
	validPrefixes := []string{
		"/", "./", "../",
		"modules/", "community/modules/",
		"git@", "github.com",
	}
	switch {
	case IsLocalPath(modPath):
		return readers[local]
	case IsEmbeddedPath(modPath):
		return readers[embedded]
	case IsGitHubPath(modPath):
		return readers[github]
	default:
		log.Fatalf(
			"Source (%s) not valid, must begin with one of: %s",
			modPath, strings.Join(validPrefixes, ", "))
	}

	return nil
}

func copyFromPath(modPath string, copyPath string) error {
	// currently supporting only local blueprint directory
	deploymentio := deploymentio.GetDeploymentioLocal()

	if err := deploymentio.CreateDirectory(copyPath); err != nil {
		return err
	}

	return deploymentio.CopyFromPath(modPath, copyPath)
}
<commit_msg>Allow git:: as a valid source prefix<commit_after>// Copyright 2022 Google LLC
//
// 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 sourcereader

import (
	"hpc-toolkit/pkg/deploymentio"
	"hpc-toolkit/pkg/modulereader"
	"log"
	"strings"
)

const (
	local = iota
	embedded
	github
)

// SourceReader interface for reading modules from a source
type SourceReader interface {
	// GetModuleInfo would leverage modulereader.GetInfo for the given kind.
	// GetModuleInfo would operate over the source without creating a local copy.
	// This would be very dependent on the kind of module.
	GetModuleInfo(modPath string, kind string) (modulereader.ModuleInfo, error)

	// GetModule copies the source to a provided local destination (the deployment directory).
	GetModule(modPath string, copyPath string) error
}

var readers = map[int]SourceReader{
	local:    LocalSourceReader{},
	embedded: EmbeddedSourceReader{},
	github:   GitHubSourceReader{},
}

// IsLocalPath checks if a source path is a local FS path
func IsLocalPath(source string) bool {
	return strings.HasPrefix(source, "./") ||
		strings.HasPrefix(source, "../") ||
		strings.HasPrefix(source, "/")
}

// IsEmbeddedPath checks if a source path points to an embedded modules
func IsEmbeddedPath(source string) bool {
	return strings.HasPrefix(source, "modules/") || strings.HasPrefix(source, "community/modules/")
}

// IsGitHubPath checks if a source path points to GitHub
func IsGitHubPath(source string) bool {
	return strings.HasPrefix(source, "github.com") ||
		strings.HasPrefix(source, "git@github.com") ||
		strings.HasPrefix(source, "git::")
}

// Factory returns a SourceReader of module path
func Factory(modPath string) SourceReader {
	validPrefixes := []string{
		"/", "./", "../",
		"modules/", "community/modules/",
		"git@", "github.com",
	}
	switch {
	case IsLocalPath(modPath):
		return readers[local]
	case IsEmbeddedPath(modPath):
		return readers[embedded]
	case IsGitHubPath(modPath):
		return readers[github]
	default:
		log.Fatalf(
			"Source (%s) not valid, must begin with one of: %s",
			modPath, strings.Join(validPrefixes, ", "))
	}

	return nil
}

func copyFromPath(modPath string, copyPath string) error {
	// currently supporting only local blueprint directory
	deploymentio := deploymentio.GetDeploymentioLocal()

	if err := deploymentio.CreateDirectory(copyPath); err != nil {
		return err
	}

	return deploymentio.CopyFromPath(modPath, copyPath)
}
<|endoftext|>


<commit_before>// +build linux

package systemd

import (
	"bytes"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
	securejoin "github.com/cyphar/filepath-securejoin"
	"github.com/opencontainers/runc/libcontainer/cgroups"
	"github.com/opencontainers/runc/libcontainer/cgroups/fs2"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/sirupsen/logrus"
)

type UnifiedManager struct {
	mu      sync.Mutex
	Cgroups *configs.Cgroup
	// path is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
	path     string
	rootless bool
}

func NewUnifiedManager(config *configs.Cgroup, path string, rootless bool) *UnifiedManager {
	return &UnifiedManager{
		Cgroups:  config,
		path:     path,
		rootless: rootless,
	}
}

func (m *UnifiedManager) Apply(pid int) error {
	var (
		c          = m.Cgroups
		unitName   = getUnitName(c)
		slice      = "system.slice"
		properties []systemdDbus.Property
	)

	if c.Paths != nil {
		return cgroups.WriteCgroupProc(m.path, pid)
	}

	if c.Parent != "" {
		slice = c.Parent
	}

	properties = append(properties, systemdDbus.PropDescription("libcontainer container "+c.Name))

	// if we create a slice, the parent is defined via a Wants=
	if strings.HasSuffix(unitName, ".slice") {
		properties = append(properties, systemdDbus.PropWants(slice))
	} else {
		// otherwise, we use Slice=
		properties = append(properties, systemdDbus.PropSlice(slice))
	}

	// only add pid if its valid, -1 is used w/ general slice creation.
	if pid != -1 {
		properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
	}

	// Check if we can delegate. This is only supported on systemd versions 218 and above.
	if !strings.HasSuffix(unitName, ".slice") {
		// Assume scopes always support delegation.
		properties = append(properties, newProp("Delegate", true))
	}

	// Always enable accounting, this gets us the same behaviour as the fs implementation,
	// plus the kernel has some problems with joining the memory cgroup at a later time.
	properties = append(properties,
		newProp("MemoryAccounting", true),
		newProp("CPUAccounting", true),
		newProp("IOAccounting", true))

	// Assume DefaultDependencies= will always work (the check for it was previously broken.)
	properties = append(properties,
		newProp("DefaultDependencies", false))

	if c.Resources.Memory != 0 {
		properties = append(properties,
			newProp("MemoryMax", uint64(c.Resources.Memory)))
	}

	swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(c.Resources.MemorySwap, c.Resources.Memory)
	if err != nil {
		return err
	}
	if swap > 0 {
		properties = append(properties,
			newProp("MemorySwapMax", uint64(swap)))
	}

	if c.Resources.CpuWeight != 0 {
		properties = append(properties,
			newProp("CPUWeight", c.Resources.CpuWeight))
	}

	// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
	if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 {
		// corresponds to USEC_INFINITY in systemd
		// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
		// always setting a property value ensures we can apply a quota and remove it later
		cpuQuotaPerSecUSec := uint64(math.MaxUint64)
		if c.Resources.CpuQuota > 0 {
			// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
			// (integer percentage of CPU) internally.  This means that if a fractional percent of
			// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
			// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
			cpuQuotaPerSecUSec = uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod
			if cpuQuotaPerSecUSec%10000 != 0 {
				cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
			}
		}
		properties = append(properties,
			newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec))
	}

	if c.Resources.PidsLimit > 0 {
		properties = append(properties,
			newProp("TasksAccounting", true),
			newProp("TasksMax", uint64(c.Resources.PidsLimit)))
	}

	properties = append(properties, c.SystemdProps...)

	// ignore c.Resources.KernelMemory

	dbusConnection, err := getDbusConnection()
	if err != nil {
		return err
	}

	statusChan := make(chan string, 1)
	if _, err := dbusConnection.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
		select {
		case <-statusChan:
		case <-time.After(time.Second):
			logrus.Warnf("Timed out while waiting for StartTransientUnit(%s) completion signal from dbus. Continuing...", unitName)
		}
	} else if !isUnitExists(err) {
		return err
	}

	_, err = m.GetUnifiedPath()
	if err != nil {
		return err
	}
	if err := createCgroupsv2Path(m.path); err != nil {
		return err
	}
	return nil
}

func (m *UnifiedManager) Destroy() error {
	if m.Cgroups.Paths != nil {
		return nil
	}
	m.mu.Lock()
	defer m.mu.Unlock()

	dbusConnection, err := getDbusConnection()
	if err != nil {
		return err
	}
	dbusConnection.StopUnit(getUnitName(m.Cgroups), "replace", nil)

	// XXX this is probably not needed, systemd should handle it
	err = os.Remove(m.path)
	if err != nil && !os.IsNotExist(err) {
		return err
	}

	return nil
}

// this method is for v1 backward compatibility and will be removed
func (m *UnifiedManager) GetPaths() map[string]string {
	_, _ = m.GetUnifiedPath()
	paths := map[string]string{
		"pids":    m.path,
		"memory":  m.path,
		"io":      m.path,
		"cpu":     m.path,
		"devices": m.path,
		"cpuset":  m.path,
		"freezer": m.path,
	}
	return paths
}

func (m *UnifiedManager) GetUnifiedPath() (string, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.path != "" {
		return m.path, nil
	}

	c := m.Cgroups
	slice := "system.slice"
	if c.Parent != "" {
		slice = c.Parent
	}

	slice, err := ExpandSlice(slice)
	if err != nil {
		return "", err
	}

	path := filepath.Join(slice, getUnitName(c))
	path, err = securejoin.SecureJoin(fs2.UnifiedMountpoint, path)
	if err != nil {
		return "", err
	}
	m.path = path

	return m.path, nil
}

func createCgroupsv2Path(path string) (Err error) {
	content, err := ioutil.ReadFile("/sys/fs/cgroup/cgroup.controllers")
	if err != nil {
		return err
	}

	ctrs := bytes.Fields(content)
	res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)

	current := "/sys/fs"
	elements := strings.Split(path, "/")
	for i, e := range elements[3:] {
		current = filepath.Join(current, e)
		if i > 0 {
			if err := os.Mkdir(current, 0755); err != nil {
				if !os.IsExist(err) {
					return err
				}
			} else {
				// If the directory was created, be sure it is not left around on errors.
				current := current
				defer func() {
					if Err != nil {
						os.Remove(current)
					}
				}()
			}
		}
		if i < len(elements[3:])-1 {
			if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), res, 0755); err != nil {
				return err
			}
		}
	}
	return nil
}

func (m *UnifiedManager) fsManager() (cgroups.Manager, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return fs2.NewManager(m.Cgroups, path, m.rootless)
}

func (m *UnifiedManager) Freeze(state configs.FreezerState) error {
	fsMgr, err := m.fsManager()
	if err != nil {
		return err
	}
	return fsMgr.Freeze(state)
}

func (m *UnifiedManager) GetPids() ([]int, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return cgroups.GetPids(path)
}

func (m *UnifiedManager) GetAllPids() ([]int, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return cgroups.GetAllPids(path)
}

func (m *UnifiedManager) GetStats() (*cgroups.Stats, error) {
	fsMgr, err := m.fsManager()
	if err != nil {
		return nil, err
	}
	return fsMgr.GetStats()
}

func (m *UnifiedManager) Set(container *configs.Config) error {
	fsMgr, err := m.fsManager()
	if err != nil {
		return err
	}
	return fsMgr.Set(container)
}

func (m *UnifiedManager) GetCgroups() (*configs.Cgroup, error) {
	return m.Cgroups, nil
}
<commit_msg>cgroupv2/systemd: privatize UnifiedManager<commit_after>// +build linux

package systemd

import (
	"bytes"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
	securejoin "github.com/cyphar/filepath-securejoin"
	"github.com/opencontainers/runc/libcontainer/cgroups"
	"github.com/opencontainers/runc/libcontainer/cgroups/fs2"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/sirupsen/logrus"
)

type unifiedManager struct {
	mu      sync.Mutex
	cgroups *configs.Cgroup
	// path is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
	path     string
	rootless bool
}

func NewUnifiedManager(config *configs.Cgroup, path string, rootless bool) *unifiedManager {
	return &unifiedManager{
		cgroups:  config,
		path:     path,
		rootless: rootless,
	}
}

func (m *unifiedManager) Apply(pid int) error {
	var (
		c          = m.cgroups
		unitName   = getUnitName(c)
		slice      = "system.slice"
		properties []systemdDbus.Property
	)

	if c.Paths != nil {
		return cgroups.WriteCgroupProc(m.path, pid)
	}

	if c.Parent != "" {
		slice = c.Parent
	}

	properties = append(properties, systemdDbus.PropDescription("libcontainer container "+c.Name))

	// if we create a slice, the parent is defined via a Wants=
	if strings.HasSuffix(unitName, ".slice") {
		properties = append(properties, systemdDbus.PropWants(slice))
	} else {
		// otherwise, we use Slice=
		properties = append(properties, systemdDbus.PropSlice(slice))
	}

	// only add pid if its valid, -1 is used w/ general slice creation.
	if pid != -1 {
		properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
	}

	// Check if we can delegate. This is only supported on systemd versions 218 and above.
	if !strings.HasSuffix(unitName, ".slice") {
		// Assume scopes always support delegation.
		properties = append(properties, newProp("Delegate", true))
	}

	// Always enable accounting, this gets us the same behaviour as the fs implementation,
	// plus the kernel has some problems with joining the memory cgroup at a later time.
	properties = append(properties,
		newProp("MemoryAccounting", true),
		newProp("CPUAccounting", true),
		newProp("IOAccounting", true))

	// Assume DefaultDependencies= will always work (the check for it was previously broken.)
	properties = append(properties,
		newProp("DefaultDependencies", false))

	if c.Resources.Memory != 0 {
		properties = append(properties,
			newProp("MemoryMax", uint64(c.Resources.Memory)))
	}

	swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(c.Resources.MemorySwap, c.Resources.Memory)
	if err != nil {
		return err
	}
	if swap > 0 {
		properties = append(properties,
			newProp("MemorySwapMax", uint64(swap)))
	}

	if c.Resources.CpuWeight != 0 {
		properties = append(properties,
			newProp("CPUWeight", c.Resources.CpuWeight))
	}

	// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
	if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 {
		// corresponds to USEC_INFINITY in systemd
		// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
		// always setting a property value ensures we can apply a quota and remove it later
		cpuQuotaPerSecUSec := uint64(math.MaxUint64)
		if c.Resources.CpuQuota > 0 {
			// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
			// (integer percentage of CPU) internally.  This means that if a fractional percent of
			// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
			// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
			cpuQuotaPerSecUSec = uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod
			if cpuQuotaPerSecUSec%10000 != 0 {
				cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
			}
		}
		properties = append(properties,
			newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec))
	}

	if c.Resources.PidsLimit > 0 {
		properties = append(properties,
			newProp("TasksAccounting", true),
			newProp("TasksMax", uint64(c.Resources.PidsLimit)))
	}

	properties = append(properties, c.SystemdProps...)

	// ignore c.Resources.KernelMemory

	dbusConnection, err := getDbusConnection()
	if err != nil {
		return err
	}

	statusChan := make(chan string, 1)
	if _, err := dbusConnection.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
		select {
		case <-statusChan:
		case <-time.After(time.Second):
			logrus.Warnf("Timed out while waiting for StartTransientUnit(%s) completion signal from dbus. Continuing...", unitName)
		}
	} else if !isUnitExists(err) {
		return err
	}

	_, err = m.GetUnifiedPath()
	if err != nil {
		return err
	}
	if err := createCgroupsv2Path(m.path); err != nil {
		return err
	}
	return nil
}

func (m *unifiedManager) Destroy() error {
	if m.cgroups.Paths != nil {
		return nil
	}
	m.mu.Lock()
	defer m.mu.Unlock()

	dbusConnection, err := getDbusConnection()
	if err != nil {
		return err
	}
	dbusConnection.StopUnit(getUnitName(m.cgroups), "replace", nil)

	// XXX this is probably not needed, systemd should handle it
	err = os.Remove(m.path)
	if err != nil && !os.IsNotExist(err) {
		return err
	}

	return nil
}

// this method is for v1 backward compatibility and will be removed
func (m *unifiedManager) GetPaths() map[string]string {
	_, _ = m.GetUnifiedPath()
	paths := map[string]string{
		"pids":    m.path,
		"memory":  m.path,
		"io":      m.path,
		"cpu":     m.path,
		"devices": m.path,
		"cpuset":  m.path,
		"freezer": m.path,
	}
	return paths
}

func (m *unifiedManager) GetUnifiedPath() (string, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.path != "" {
		return m.path, nil
	}

	c := m.cgroups
	slice := "system.slice"
	if c.Parent != "" {
		slice = c.Parent
	}

	slice, err := ExpandSlice(slice)
	if err != nil {
		return "", err
	}

	path := filepath.Join(slice, getUnitName(c))
	path, err = securejoin.SecureJoin(fs2.UnifiedMountpoint, path)
	if err != nil {
		return "", err
	}
	m.path = path

	return m.path, nil
}

func createCgroupsv2Path(path string) (Err error) {
	content, err := ioutil.ReadFile("/sys/fs/cgroup/cgroup.controllers")
	if err != nil {
		return err
	}

	ctrs := bytes.Fields(content)
	res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)

	current := "/sys/fs"
	elements := strings.Split(path, "/")
	for i, e := range elements[3:] {
		current = filepath.Join(current, e)
		if i > 0 {
			if err := os.Mkdir(current, 0755); err != nil {
				if !os.IsExist(err) {
					return err
				}
			} else {
				// If the directory was created, be sure it is not left around on errors.
				current := current
				defer func() {
					if Err != nil {
						os.Remove(current)
					}
				}()
			}
		}
		if i < len(elements[3:])-1 {
			if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), res, 0755); err != nil {
				return err
			}
		}
	}
	return nil
}

func (m *unifiedManager) fsManager() (cgroups.Manager, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return fs2.NewManager(m.cgroups, path, m.rootless)
}

func (m *unifiedManager) Freeze(state configs.FreezerState) error {
	fsMgr, err := m.fsManager()
	if err != nil {
		return err
	}
	return fsMgr.Freeze(state)
}

func (m *unifiedManager) GetPids() ([]int, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return cgroups.GetPids(path)
}

func (m *unifiedManager) GetAllPids() ([]int, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return cgroups.GetAllPids(path)
}

func (m *unifiedManager) GetStats() (*cgroups.Stats, error) {
	fsMgr, err := m.fsManager()
	if err != nil {
		return nil, err
	}
	return fsMgr.GetStats()
}

func (m *unifiedManager) Set(container *configs.Config) error {
	fsMgr, err := m.fsManager()
	if err != nil {
		return err
	}
	return fsMgr.Set(container)
}

func (m *unifiedManager) GetCgroups() (*configs.Cgroup, error) {
	return m.cgroups, nil
}
<|endoftext|>


<commit_before>package middleware

import (
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/prasannavl/go-gluons/ansicode"
	"github.com/prasannavl/go-gluons/http/reqcontext"
	"github.com/prasannavl/go-gluons/http/writer"
	"github.com/prasannavl/go-gluons/log"
	"github.com/prasannavl/goerror/errutils"
	"github.com/prasannavl/mchain"
)

func LoggerMiddleware(requestLogLevel log.Level) mchain.Middleware {
	return func(next mchain.Handler) mchain.Handler {
		f := func(w http.ResponseWriter, r *http.Request) error {
			ww := w.(writer.ResponseWriter)
			startTime := time.Now()
			err := next.ServeHTTP(w, r)
			ctx := reqcontext.FromRequest(r)
			logger := &ctx.Logger
			if err != nil {
				LogError(logger, err)
				LogErrorStack(logger, ctx.ErrorStacks...)
			}
			sizeRaw := ww.BytesWritten()
			if sizeRaw > 0 {
				logger.Logf(
					requestLogLevel,
					"%s %v %v %v %s %s",
					r.Method,
					ColoredHttpStatus(ww.Status()),
					ColoredDuration(time.Since(startTime)),
					ColoredTransferSize(sizeRaw),
					r.RequestURI,
					r.RemoteAddr,
				)
			} else {
				logger.Logf(
					requestLogLevel,
					"%s %v %v %s %s",
					r.Method,
					ColoredHttpStatus(ww.Status()),
					ColoredDuration(time.Since(startTime)),
					r.RequestURI,
					r.RemoteAddr)
			}
			return nil
		}
		return mchain.HandlerFunc(f)
	}
}

func LogError(logger *log.Logger, e interface{}) {
	if err, ok := e.(error); ok {
		iter := errutils.MakeIteratorLimited(err, 10)
		for {
			e := iter.Next()
			if e == nil {
				break
			}
			if errutils.HasMessage(e) {
				logger.Errorf("cause: %s => %#v ", e.Error(), e)
			}
		}
	} else {
		logger.Errorf("%#v", e)
	}
}

func LogErrorStack(logger *log.Logger, stacks ...[]byte) {
	var i = 0
	for _, stack := range stacks {
		if len(stack) > 0 {
			logger.Errorf("[[stack-%d]]\r\n%s[[stack:end]]\r\n", i, stack)
			i++
		}
	}
}

type ColoredTransferSize int

var sizeColors = [...]string{
	ansicode.BlackBright,
	ansicode.YellowBright,
	ansicode.RedBright,
}

var sizeColorsLen = len(statusColors)

const (
	sizeKB = 1 * 1000
)

var sizeColorLimits = [...]int{80 * sizeKB, 400 * sizeKB}

func (c ColoredTransferSize) ColorString() string {
	str := sizeString(int(c))
	index := 0
	current := int(c)
	for _, limit := range sizeColorLimits {
		if current > limit {
			index++
		} else {
			break
		}
	}
	if index >= sizeColorsLen {
		index = sizeColorsLen - 1
	}
	return sizeColors[index] + str + ansicode.Reset
}

var sizeSuffixes = [...]string{"b", "kb", "mb", "gb"}

func sizeString(size int) string {
	index := 0
	i := 1000
	for ; size > 1; i *= 1000 {
		index++
	}
	maxLen := len(sizeSuffixes)
	if index >= maxLen {
		size = maxLen - 1
	}
	n := float64(size) / float64(i/1000)
	s := strconv.FormatFloat(n, 'f', -1, 64)
	return s + sizeSuffixes[index]
}

type ColoredHttpStatus int

func (c ColoredHttpStatus) ColorString() string {
	return coloredStatusIntString(int(c))
}

var statusColors = [...]string{
	ansicode.CyanBright,
	ansicode.Green,
	ansicode.MagentaBright,
	ansicode.YellowBright,
	ansicode.RedBright,
}

var statusColorsLen = len(statusColors)

func coloredStatusIntString(code int) string {
	index := code
	if index < 100 {
		return statusColors[0]
	}
	index = index / 100
	index = (index - 1) % statusColorsLen
	return statusColors[index] + strconv.Itoa(code) + ansicode.Reset
}

type ColoredDuration time.Duration

func (c ColoredDuration) ColorString() string {
	return coloredTimeString(time.Duration(c))
}

var timeColors = [...]string{
	ansicode.BlackBright,
	ansicode.YellowBright,
	ansicode.RedBright,
	ansicode.Red,
	ansicode.RedBrightBg + ansicode.White,
	ansicode.RedBg + ansicode.White + ansicode.Bold,
}

var timeColorsLen = len(statusColors)
var timeColorLimits = [...]int64{80, 200, 500, 1000, 2000}

func coloredTimeString(duration time.Duration) string {
	index := 0
	t := duration.Nanoseconds() / 1000
	millis := t / 1000
	for _, limit := range timeColorLimits {
		if millis > limit {
			index++
		} else {
			break
		}
	}
	if index >= timeColorsLen {
		index = timeColorsLen - 1
	}
	return timeColors[index] + fmt.Sprintf("%v", duration) + ansicode.Reset
}
<commit_msg>fix: last refactor artifact<commit_after>package middleware

import (
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/prasannavl/go-gluons/ansicode"
	"github.com/prasannavl/go-gluons/http/reqcontext"
	"github.com/prasannavl/go-gluons/http/writer"
	"github.com/prasannavl/go-gluons/log"
	"github.com/prasannavl/goerror/errutils"
	"github.com/prasannavl/mchain"
)

func LoggerMiddleware(requestLogLevel log.Level) mchain.Middleware {
	return func(next mchain.Handler) mchain.Handler {
		f := func(w http.ResponseWriter, r *http.Request) error {
			ww := w.(writer.ResponseWriter)
			startTime := time.Now()
			err := next.ServeHTTP(w, r)
			ctx := reqcontext.FromRequest(r)
			logger := &ctx.Logger
			if err != nil {
				LogError(logger, err)
				LogErrorStack(logger, ctx.ErrorStacks...)
			}
			sizeRaw := ww.BytesWritten()
			if sizeRaw > 0 {
				logger.Logf(
					requestLogLevel,
					"%s %v %v %v %s %s",
					r.Method,
					ColoredHttpStatus(ww.Status()),
					ColoredDuration(time.Since(startTime)),
					ColoredTransferSize(sizeRaw),
					r.RequestURI,
					r.RemoteAddr,
				)
			} else {
				logger.Logf(
					requestLogLevel,
					"%s %v %v %s %s",
					r.Method,
					ColoredHttpStatus(ww.Status()),
					ColoredDuration(time.Since(startTime)),
					r.RequestURI,
					r.RemoteAddr)
			}
			return nil
		}
		return mchain.HandlerFunc(f)
	}
}

func LogError(logger *log.Logger, e interface{}) {
	if err, ok := e.(error); ok {
		iter := errutils.MakeIteratorLimited(err, 10)
		for {
			e := iter.Next()
			if e == nil {
				break
			}
			if errutils.HasMessage(e) {
				logger.Errorf("cause: %s => %#v ", e.Error(), e)
			}
		}
	} else {
		logger.Errorf("%#v", e)
	}
}

func LogErrorStack(logger *log.Logger, stacks ...[]byte) {
	var i = 0
	for _, stack := range stacks {
		if len(stack) > 0 {
			logger.Errorf("[[stack-%d]]\r\n%s[[stack:end]]\r\n", i, stack)
			i++
		}
	}
}

type ColoredTransferSize int

var sizeColors = [...]string{
	ansicode.BlackBright,
	ansicode.YellowBright,
	ansicode.RedBright,
}

var sizeColorsLen = len(statusColors)

const (
	sizeKB = 1 * 1000
)

var sizeColorLimits = [...]int{80 * sizeKB, 400 * sizeKB}

func (c ColoredTransferSize) ColorString() string {
	str := sizeString(int(c))
	index := 0
	current := int(c)
	for _, limit := range sizeColorLimits {
		if current > limit {
			index++
		} else {
			break
		}
	}
	if index >= sizeColorsLen {
		index = sizeColorsLen - 1
	}
	return sizeColors[index] + str + ansicode.Reset
}

var sizeSuffixes = [...]string{"b", "kb", "mb", "gb"}

func sizeString(size int) string {
	index := 0
	i := 1000
	for ; size > i; i *= 1000 {
		index++
	}
	maxLen := len(sizeSuffixes)
	if index >= maxLen {
		size = maxLen - 1
	}
	n := float64(size) / float64(i/1000)
	s := strconv.FormatFloat(n, 'f', -1, 64)
	return s + sizeSuffixes[index]
}

type ColoredHttpStatus int

func (c ColoredHttpStatus) ColorString() string {
	return coloredStatusIntString(int(c))
}

var statusColors = [...]string{
	ansicode.CyanBright,
	ansicode.Green,
	ansicode.MagentaBright,
	ansicode.YellowBright,
	ansicode.RedBright,
}

var statusColorsLen = len(statusColors)

func coloredStatusIntString(code int) string {
	index := code
	if index < 100 {
		return statusColors[0]
	}
	index = index / 100
	index = (index - 1) % statusColorsLen
	return statusColors[index] + strconv.Itoa(code) + ansicode.Reset
}

type ColoredDuration time.Duration

func (c ColoredDuration) ColorString() string {
	return coloredTimeString(time.Duration(c))
}

var timeColors = [...]string{
	ansicode.BlackBright,
	ansicode.YellowBright,
	ansicode.RedBright,
	ansicode.Red,
	ansicode.RedBrightBg + ansicode.White,
	ansicode.RedBg + ansicode.White + ansicode.Bold,
}

var timeColorsLen = len(statusColors)
var timeColorLimits = [...]int64{80, 200, 500, 1000, 2000}

func coloredTimeString(duration time.Duration) string {
	index := 0
	t := duration.Nanoseconds() / 1000
	millis := t / 1000
	for _, limit := range timeColorLimits {
		if millis > limit {
			index++
		} else {
			break
		}
	}
	if index >= timeColorsLen {
		index = timeColorsLen - 1
	}
	return timeColors[index] + fmt.Sprintf("%v", duration) + ansicode.Reset
}
<|endoftext|>


<commit_before>// Copyright 2018 Amazon.com, Inc. or its affiliates. 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. A copy of the
// License is located at
//
//	http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 task

// Association is a definition of a device (or other potential things) that is
// associated with a task.
type Association struct {
	// Containers are the container names that can use this association.
	Containers []string `json:"containers"`
	// Content describes this association.
	Content EncodedString `json:"content"`
	// Name is a unique identifier of the association within a task.
	Name string `json:"name"`
	// Type specifies the type of this association.
	Type string `json:"type"`
}

// EncodedString is used to describe an association, the consumer of the association
// data should be responsible to decode it. We don't model it in Agent, or check the
// value of the encoded string, we just pass whatever we get from ACS, because
// we don't want Agent to be coupled with a specific association.
type EncodedString struct {
	// Encoding is the encoding type of the value.
	Encoding string `json:"encoding"`
	// Value is the content of the encoded string.
	Value string `json:"value"`
}<commit_msg>run go fmt to fix formatting issues<commit_after>// Copyright 2018 Amazon.com, Inc. or its affiliates. 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. A copy of the
// License is located at
//
//	http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 task

// Association is a definition of a device (or other potential things) that is
// associated with a task.
type Association struct {
	// Containers are the container names that can use this association.
	Containers []string `json:"containers"`
	// Content describes this association.
	Content EncodedString `json:"content"`
	// Name is a unique identifier of the association within a task.
	Name string `json:"name"`
	// Type specifies the type of this association.
	Type string `json:"type"`
}

// EncodedString is used to describe an association, the consumer of the association
// data should be responsible to decode it. We don't model it in Agent, or check the
// value of the encoded string, we just pass whatever we get from ACS, because
// we don't want Agent to be coupled with a specific association.
type EncodedString struct {
	// Encoding is the encoding type of the value.
	Encoding string `json:"encoding"`
	// Value is the content of the encoded string.
	Value string `json:"value"`
}
<|endoftext|>