<commit_before>package sensitivity

import (
	"os/user"
	"runtime"

	"github.com/intelsdi-x/swan/pkg/conf"
	"github.com/intelsdi-x/swan/pkg/executor"
	"github.com/intelsdi-x/swan/pkg/isolation"
	"github.com/intelsdi-x/swan/pkg/kubernetes"
)

var (
	hpKubernetesCPUResourceFlag    = conf.NewIntFlag("hp_kubernetes_cpu_resource", "set limits and request for HP workloads pods run on kubernetes in CPU millis (default 1000 * number of CPU).", runtime.NumCPU()*1000)
	hpKubernetesMemoryResourceFlag = conf.NewIntFlag("hp_kubernetes_memory_resource", "set memory limits and request for HP pods workloads run on kubernetes in bytes (default 1GB).", 1000000000)

	runOnKubernetesFlag = conf.NewBoolFlag("run_on_kubernetes", "Launch HP and BE tasks on Kubernetes.", false)
)

// NewRemote is helper for creating remotes with default sshConfig.
// TODO: this should be put into swan:/pkg/executors
func NewRemote(ip string) (executor.Executor, error) {
	// TODO: Have ability to choose user using parameter here.
	user, err := user.Current()
	if err != nil {
		return nil, err
	}

	sshConfig, err := executor.NewSSHConfig(ip, executor.DefaultSSHPort, user)
	if err != nil {
		return nil, err
	}

	return executor.NewRemote(sshConfig), nil
}

// PrepareExecutors gives an executor to deploy your workloads with applied isolation on HP.
func PrepareExecutors(hpIsolation isolation.Decorator) (hpExecutor executor.Executor, beExecutorFactory ExecutorFactoryFunc, cleanup func(), err error) {
	if runOnKubernetesFlag.Value() {
		k8sConfig := kubernetes.DefaultConfig()
		k8sConfig.KubeAPIArgs = "--admission-control=\"AlwaysAdmit,AddToleration\"" // Enable AddToleration path by default.
		k8sLauncher := kubernetes.New(executor.NewLocal(), executor.NewLocal(), k8sConfig)
		k8sClusterTaskHandle, err := k8sLauncher.Launch()
		if err != nil {
			return nil, nil, nil, err
		}

		cleanup = func() { executor.StopCleanAndErase(k8sClusterTaskHandle) }

		// TODO: pass information from k8sConfig to hpExecutor and beExecutor configs.

		// HP executor.
		hpExecutorConfig := executor.DefaultKubernetesConfig()
		hpExecutorConfig.ContainerImage = "centos_swan_image"
		hpExecutorConfig.PodNamePrefix = "swan-hp"
		hpExecutorConfig.Decorators = isolation.Decorators{hpIsolation}
		hpExecutorConfig.HostNetwork = true // requied to have access from mutilate agents run outside a k8s cluster.

		hpExecutorConfig.CPULimit = int64(hpKubernetesCPUResourceFlag.Value())
		hpExecutorConfig.MemoryLimit = int64(hpKubernetesMemoryResourceFlag.Value())
		// "Guranteed" class is when both resources and set for request and limit and equal.
		hpExecutorConfig.CPURequest = hpExecutorConfig.CPULimit
		hpExecutorConfig.MemoryRequest = hpExecutorConfig.MemoryLimit
		hpExecutor, err = executor.NewKubernetes(hpExecutorConfig)
		if err != nil {
			return nil, nil, nil, err
		}

		// BE Executors.
		beExecutorFactory = func(decorators isolation.Decorators) (executor.Executor, error) {
			config := executor.DefaultKubernetesConfig()
			config.PodNamePrefix = "swan-be"
			config.ContainerImage = "centos_swan_image"
			config.Decorators = decorators
			config.Privileged = true // swan aggressor use unshare, which requires sudo.
			return executor.NewKubernetes(config)
		}
	} else {
		hpExecutor = executor.NewLocalIsolated(hpIsolation)
		cleanup = func() {}
		beExecutorFactory = func(decorators isolation.Decorators) (executor.Executor, error) {
			return executor.NewLocalIsolated(decorators), nil
		}
	}
	return
}
<commit_msg>Running k8s master on remote host (#487)<commit_after>package sensitivity

import (
	"fmt"
	"os/user"
	"runtime"

	"github.com/intelsdi-x/swan/pkg/conf"
	"github.com/intelsdi-x/swan/pkg/executor"
	"github.com/intelsdi-x/swan/pkg/isolation"
	"github.com/intelsdi-x/swan/pkg/kubernetes"
)

var (
	hpKubernetesCPUResourceFlag    = conf.NewIntFlag("hp_kubernetes_cpu_resource", "set limits and request for HP workloads pods run on kubernetes in CPU millis (default 1000 * number of CPU).", runtime.NumCPU()*1000)
	hpKubernetesMemoryResourceFlag = conf.NewIntFlag("hp_kubernetes_memory_resource", "set memory limits and request for HP pods workloads run on kubernetes in bytes (default 1GB).", 1000000000)

	runOnKubernetesFlag = conf.NewBoolFlag("run_on_kubernetes", "Launch HP and BE tasks on Kubernetes.", false)
	kubernetesMaster    = conf.NewStringFlag("kubernetes_master", "Address of a host where Kubernetes master components are to be run", "127.0.0.1")
)

// NewRemote is helper for creating remotes with default sshConfig.
// TODO: this should be put into swan:/pkg/executors
func NewRemote(ip string) (executor.Executor, error) {
	// TODO: Have ability to choose user using parameter here.
	user, err := user.Current()
	if err != nil {
		return nil, err
	}

	sshConfig, err := executor.NewSSHConfig(ip, executor.DefaultSSHPort, user)
	if err != nil {
		return nil, err
	}

	return executor.NewRemote(sshConfig), nil
}

// PrepareExecutors gives an executor to deploy your workloads with applied isolation on HP.
func PrepareExecutors(hpIsolation isolation.Decorator) (hpExecutor executor.Executor, beExecutorFactory ExecutorFactoryFunc, cleanup func(), err error) {
	if runOnKubernetesFlag.Value() {
		k8sConfig := kubernetes.DefaultConfig()
		masterAddress := kubernetesMaster.Value()
		apiAddress := fmt.Sprintf("%s:%s", masterAddress, "8080")
		masterExecutor, err := NewRemote(masterAddress)
		if err != nil {
			return nil, nil, nil, err
		}
		k8sLauncher := kubernetes.New(masterExecutor, executor.NewLocal(), k8sConfig)
		k8sClusterTaskHandle, err := k8sLauncher.Launch()
		if err != nil {
			return nil, nil, nil, err
		}

		cleanup = func() { executor.StopCleanAndErase(k8sClusterTaskHandle) }

		// TODO: pass information from k8sConfig to hpExecutor and beExecutor configs.

		// HP executor.
		hpExecutorConfig := executor.DefaultKubernetesConfig()
		hpExecutorConfig.ContainerImage = "centos_swan_image"
		hpExecutorConfig.PodNamePrefix = "swan-hp"
		hpExecutorConfig.Decorators = isolation.Decorators{hpIsolation}
		hpExecutorConfig.HostNetwork = true // requied to have access from mutilate agents run outside a k8s cluster.
		hpExecutorConfig.Address = apiAddress

		hpExecutorConfig.CPULimit = int64(hpKubernetesCPUResourceFlag.Value())
		hpExecutorConfig.MemoryLimit = int64(hpKubernetesMemoryResourceFlag.Value())
		// "Guranteed" class is when both resources and set for request and limit and equal.
		hpExecutorConfig.CPURequest = hpExecutorConfig.CPULimit
		hpExecutorConfig.MemoryRequest = hpExecutorConfig.MemoryLimit
		hpExecutor, err = executor.NewKubernetes(hpExecutorConfig)
		if err != nil {
			return nil, nil, nil, err
		}

		// BE Executors.
		beExecutorFactory = func(decorators isolation.Decorators) (executor.Executor, error) {
			config := executor.DefaultKubernetesConfig()
			config.PodNamePrefix = "swan-be"
			config.ContainerImage = "centos_swan_image"
			config.Decorators = decorators
			config.Privileged = true // swan aggressor use unshare, which requires sudo.
			config.Address = apiAddress
			return executor.NewKubernetes(config)
		}
	} else {
		hpExecutor = executor.NewLocalIsolated(hpIsolation)
		cleanup = func() {}
		beExecutorFactory = func(decorators isolation.Decorators) (executor.Executor, error) {
			return executor.NewLocalIsolated(decorators), nil
		}
	}
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Fission 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 function

import (
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/pkg/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
	"github.com/fission/fission/pkg/fission-cli/cmd"
	flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
)

type GetMetaSubCommand struct {
	cmd.CommandActioner
}

func GetMeta(input cli.Input) error {
	return (&GetMetaSubCommand{}).do(input)
}

func (opts *GetMetaSubCommand) do(input cli.Input) error {
	fn, err := opts.Client().V1().Function().Get(&metav1.ObjectMeta{
		Name:      input.String(flagkey.FnName),
		Namespace: input.String(flagkey.NamespaceFunction),
	})
	if err != nil {
		return errors.Wrap(err, "error getting function")
	}

	w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
	fmt.Fprintf(w, "%v\t%v\n", "NAME", "ENV")
	fmt.Fprintf(w, "%v\t%v\n", fn.ObjectMeta.Name, fn.Spec.Environment.Name)
	w.Flush()

	return nil
}
<commit_msg>Dump labels and annotations for function via getmeta cli (#2525)<commit_after>/*
Copyright 2019 The Fission 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 function

import (
	"fmt"

	"github.com/pkg/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
	"github.com/fission/fission/pkg/fission-cli/cmd"
	flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
)

type GetMetaSubCommand struct {
	cmd.CommandActioner
}

func GetMeta(input cli.Input) error {
	return (&GetMetaSubCommand{}).do(input)
}

func (opts *GetMetaSubCommand) do(input cli.Input) error {
	fn, err := opts.Client().V1().Function().Get(&metav1.ObjectMeta{
		Name:      input.String(flagkey.FnName),
		Namespace: input.String(flagkey.NamespaceFunction),
	})
	if err != nil {
		return errors.Wrap(err, "error getting function")
	}

	fmt.Printf("Name: %v\n", fn.ObjectMeta.Name)
	fmt.Printf("Environment: %v\n", fn.Spec.Environment.Name)
	if len(fn.ObjectMeta.Labels) != 0 {
		fmt.Println("Labels:")
		for k, v := range fn.ObjectMeta.Labels {
			fmt.Printf("  %s=%s\n", k, v)
		}
	}
	if len(fn.ObjectMeta.Annotations) != 0 {
		fmt.Println("Annotations:")
		for k, v := range fn.ObjectMeta.Annotations {
			fmt.Printf("  %s=%s\n", k, v)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The OpenEBS 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 v1alpha1

// TemplateKeyValue represents a key and corresponding value to be used as
// values for templating
type TemplateKeyValue struct {
	Key   string
	Value interface{}
}

type TemplateKeyValueList struct {
	Items []TemplateKeyValue
}

func NewTemplateKeyValueList() TemplateKeyValueList {
	return TemplateKeyValueList{}
}

// AddNamespace adds namespace as a template value
func (l TemplateKeyValueList) AddNamespace(value string) TemplateKeyValueList {
	l.Items = append(l.Items, TemplateKeyValue{Key: "namespace", Value: value})
	return l
}

// AddServiceAccount adds serviceaccount as a template value
func (l TemplateKeyValueList) AddServiceAccount(value string) TemplateKeyValueList {
	l.Items = append(l.Items, TemplateKeyValue{Key: "serviceaccount", Value: value})
	return l
}

// Values creates template values to be applied over install related artifacts
func (l TemplateKeyValueList) Values() (final map[string]interface{}) {
	final = map[string]interface{}{}
	if len(l.Items) == 0 {
		final["installer"] = nil
		return
	}
	nested := map[string]interface{}{}
	for _, kv := range l.Items {
		nested[kv.Key] = kv.Value
	}
	final["installer"] = nested
	return
}
<commit_msg>Fix golint issue in template_values.go<commit_after>/*
Copyright 2018 The OpenEBS 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 v1alpha1

// TemplateKeyValue represents a key and corresponding value to be used as
// values for templating
type TemplateKeyValue struct {
	Key   string
	Value interface{}
}

// TemplateKeyValueList is a list of TemplateKeyValue
type TemplateKeyValueList struct {
	Items []TemplateKeyValue
}

// NewTemplateKeyValueList returns a list of NewTemplateKeyValue
func NewTemplateKeyValueList() TemplateKeyValueList {
	return TemplateKeyValueList{}
}

// AddNamespace adds namespace as a template value
func (l TemplateKeyValueList) AddNamespace(value string) TemplateKeyValueList {
	l.Items = append(l.Items, TemplateKeyValue{Key: "namespace", Value: value})
	return l
}

// AddServiceAccount adds serviceaccount as a template value
func (l TemplateKeyValueList) AddServiceAccount(value string) TemplateKeyValueList {
	l.Items = append(l.Items, TemplateKeyValue{Key: "serviceaccount", Value: value})
	return l
}

// Values creates template values to be applied over install related artifacts
func (l TemplateKeyValueList) Values() (final map[string]interface{}) {
	final = map[string]interface{}{}
	if len(l.Items) == 0 {
		final["installer"] = nil
		return
	}
	nested := map[string]interface{}{}
	for _, kv := range l.Items {
		nested[kv.Key] = kv.Value
	}
	final["installer"] = nested
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2020 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 deployment

import (
	"context"

	"github.com/google/knative-gcp/pkg/apis/duck/v1alpha1"
	"github.com/google/knative-gcp/pkg/reconciler"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/clock"
	"k8s.io/client-go/tools/cache"
	"knative.dev/pkg/client/injection/kube/informers/apps/v1/deployment"
	"knative.dev/pkg/client/injection/kube/informers/core/v1/secret"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"
)

const (
	// ReconcilerName is the name of the reconciler
	ReconcilerName = "Deployment"

	// controllerAgentName is the string used by this controller to identify
	// itself when creating events.
	controllerAgentName = "cloud-run-events-deployment-controller"

	namespace      = "cloud-run-events"
	secretName     = v1alpha1.DefaultSecretName
	deploymentName = "controller"
)

// NewController initializes the controller and is called by the generated code
// Registers event handlers to enqueue events.
// When the secret `google-cloud-key` of namespace `cloud-run-events` gets updated, we will enqueue the deployment `controller` of namespace `cloud-run-events`.
func NewController(
	ctx context.Context,
	cmw configmap.Watcher,
) *controller.Impl {

	deploymentInformer := deployment.Get(ctx)
	secretInformer := secret.Get(ctx)

	r := &Reconciler{
		Base:             reconciler.NewBase(ctx, controllerAgentName, cmw),
		deploymentLister: deploymentInformer.Lister(),
		clock:            clock.RealClock{},
	}

	impl := controller.NewImpl(r, r.Logger, ReconcilerName)

	r.Logger.Info("Setting up event handlers")

	sentinel := impl.EnqueueSentinel(types.NamespacedName{Name: deploymentName, Namespace: namespace})
	secretInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: controller.FilterWithNameAndNamespace(namespace, secretName),
		Handler:    handleUpdateOnly(sentinel),
	})
	return impl
}

func handleUpdateOnly(h func(interface{})) cache.ResourceEventHandler {
	return cache.ResourceEventHandlerFuncs{
		AddFunc:    doNothing,
		UpdateFunc: controller.PassNew(h),
		DeleteFunc: doNothing,
	}
}

func doNothing(obj interface{}) {
}
<commit_msg>Make deployment controller listen to 'Create' and 'Delete' events of Secret (#995)<commit_after>/*
Copyright 2020 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 deployment

import (
	"context"
	"os"

	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/clock"
	"k8s.io/client-go/tools/cache"
	"knative.dev/pkg/client/injection/kube/informers/apps/v1/deployment"
	"knative.dev/pkg/client/injection/kube/informers/core/v1/secret"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"

	"github.com/google/knative-gcp/pkg/apis/duck/v1alpha1"
	"github.com/google/knative-gcp/pkg/reconciler"
)

const (
	// ReconcilerName is the name of the reconciler
	ReconcilerName = "Deployment"

	// controllerAgentName is the string used by this controller to identify
	// itself when creating events.
	controllerAgentName = "cloud-run-events-deployment-controller"

	namespace      = "cloud-run-events"
	secretName     = v1alpha1.DefaultSecretName
	deploymentName = "controller"
	envKey         = "GOOGLE_APPLICATION_CREDENTIALS"
)

// NewController initializes the controller and is called by the generated code
// Registers event handlers to enqueue events.
// When the secret `google-cloud-key` of namespace `cloud-run-events` gets updated, we will enqueue the deployment `controller` of namespace `cloud-run-events`.
func NewController(
	ctx context.Context,
	cmw configmap.Watcher,
) *controller.Impl {

	deploymentInformer := deployment.Get(ctx)
	secretInformer := secret.Get(ctx)

	r := &Reconciler{
		Base:             reconciler.NewBase(ctx, controllerAgentName, cmw),
		deploymentLister: deploymentInformer.Lister(),
		clock:            clock.RealClock{},
	}

	impl := controller.NewImpl(r, r.Logger, ReconcilerName)

	r.Logger.Info("Setting up event handlers")

	sentinel := impl.EnqueueSentinel(types.NamespacedName{Namespace: namespace, Name: deploymentName})
	secretInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: controller.FilterWithNameAndNamespace(namespace, secretName),
		Handler:    handler(sentinel),
	})
	return impl
}

func handler(h func(interface{})) cache.ResourceEventHandler {
	return cache.ResourceEventHandlerFuncs{
		// For AddFunc, only enqueue deployment key when envKey is not set.
		// In such case, the controller pod hasn't restarted before.
		// This helps to avoid infinite loop for restarting controller pod.
		AddFunc: func(obj interface{}) {
			if _, ok := os.LookupEnv(envKey); !ok {
				h(obj)
			}
		},
		UpdateFunc: controller.PassNew(h),
		// If secret is deleted, the controller pod will restart, in order to unset the envKey.
		// This is needed when changing authentication configuration from k8s Secret to Workload Identity.
		DeleteFunc: h,
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 build

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

	cstorage "cloud.google.com/go/storage"
	"golang.org/x/oauth2/google"
	cloudbuild "google.golang.org/api/cloudbuild/v1"
	"google.golang.org/api/googleapi"
	"google.golang.org/api/iterator"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

const (
	// StatusUnknown "STATUS_UNKNOWN" - Status of the build is unknown.
	StatusUnknown = "STATUS_UNKNOWN"

	// StatusQueued "QUEUED" - Build is queued; work has not yet begun.
	StatusQueued = "QUEUED"

	// StatusWorking "WORKING" - Build is being executed.
	StatusWorking = "WORKING"

	// StatusSuccess  "SUCCESS" - Build finished successfully.
	StatusSuccess = "SUCCESS"

	// StatusFailure  "FAILURE" - Build failed to complete successfully.
	StatusFailure = "FAILURE"

	// StatusInternalError  "INTERNAL_ERROR" - Build failed due to an internal cause.
	StatusInternalError = "INTERNAL_ERROR"

	// StatusTimeout  "TIMEOUT" - Build took longer than was allowed.
	StatusTimeout = "TIMEOUT"

	// StatusCancelled  "CANCELLED" - Build was canceled by a user.
	StatusCancelled = "CANCELLED"

	// RetryDelay is the time to wait in between polling the status of the cloud build
	RetryDelay = 1 * time.Second
)

type GoogleCloudBuilder struct {
	*v1alpha2.BuildConfig
}

func NewGoogleCloudBuilder(cfg *v1alpha2.BuildConfig) (*GoogleCloudBuilder, error) {
	return &GoogleCloudBuilder{cfg}, nil
}

func (cb *GoogleCloudBuilder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*v1alpha2.Artifact) (*BuildResult, error) {
	client, err := google.DefaultClient(ctx, cloudbuild.CloudPlatformScope)
	if err != nil {
		return nil, errors.Wrap(err, "getting google client")
	}
	cbclient, err := cloudbuild.New(client)
	if err != nil {
		return nil, errors.Wrap(err, "getting builder")
	}
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "getting cloud storage client")
	}
	defer c.Close()
	builds := []Build{}
	for _, artifact := range artifacts {
		build, err := cb.buildArtifact(ctx, out, tagger, cbclient, c, artifact)
		if err != nil {
			return nil, errors.Wrapf(err, "building artifact %s", artifact.ImageName)
		}
		builds = append(builds, *build)
	}

	return &BuildResult{
		Builds: builds,
	}, nil
}

func (cb *GoogleCloudBuilder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.Tagger, cbclient *cloudbuild.Service, c *cstorage.Client, artifact *v1alpha2.Artifact) (*Build, error) {
	logrus.Infof("Building artifact: %+v", artifact)

	// need to format build args as strings to pass to container builder docker
	var buildArgs []string
	for k, v := range artifact.DockerArtifact.BuildArgs {
		if v != nil {
			buildArgs = append(buildArgs, []string{"--build-arg", fmt.Sprintf("%s=%s", k, *v)}...)
		}
	}
	logrus.Debugf("Build args: %s", buildArgs)

	cbBucket := fmt.Sprintf("%s%s", cb.GoogleCloudBuild.ProjectID, constants.GCSBucketSuffix)
	buildObject := fmt.Sprintf("source/%s-%s.tar.gz", cb.GoogleCloudBuild.ProjectID, util.RandomID())

	if err := cb.createBucketIfNotExists(ctx, cbBucket); err != nil {
		return nil, errors.Wrap(err, "creating bucket if not exists")
	}
	if err := cb.checkBucketProjectCorrect(ctx, cbBucket); err != nil {
		return nil, errors.Wrap(err, "checking bucket is in correct project")
	}

	fmt.Fprintf(out, "Pushing code to gs://%s/%s\n", cbBucket, buildObject)
	if err := cb.uploadTarToGCS(ctx, artifact.DockerArtifact.DockerfilePath, artifact.Workspace, cbBucket, buildObject); err != nil {
		return nil, errors.Wrap(err, "uploading source tarball")
	}

	args := append([]string{"build", "--tag", artifact.ImageName, "-f", artifact.DockerArtifact.DockerfilePath}, buildArgs...)
	args = append(args, ".")
	call := cbclient.Projects.Builds.Create(cb.GoogleCloudBuild.ProjectID, &cloudbuild.Build{
		LogsBucket: cbBucket,
		Source: &cloudbuild.Source{
			StorageSource: &cloudbuild.StorageSource{
				Bucket: cbBucket,
				Object: buildObject,
			},
		},
		Steps: []*cloudbuild.BuildStep{
			{
				Name: "gcr.io/cloud-builders/docker",
				Args: args,
			},
		},
		Images: []string{artifact.ImageName},
	})
	op, err := call.Context(ctx).Do()
	if err != nil {
		return nil, errors.Wrap(err, "could not create build")
	}

	remoteID, err := getBuildID(op)
	if err != nil {
		return nil, errors.Wrapf(err, "getting build ID from op")
	}
	logsObject := fmt.Sprintf("log-%s.txt", remoteID)
	fmt.Fprintf(out, "Logs at available at \nhttps://console.cloud.google.com/m/cloudstorage/b/%s/o/%s\n", cbBucket, logsObject)
	var imageID string
	offset := int64(0)
watch:
	for {
		logrus.Debugf("current offset %d", offset)
		b, err := cbclient.Projects.Builds.Get(cb.GoogleCloudBuild.ProjectID, remoteID).Do()
		if err != nil {
			return nil, errors.Wrap(err, "getting build status")
		}

		r, err := cb.getLogs(ctx, offset, cbBucket, logsObject)
		if err != nil {
			return nil, errors.Wrap(err, "getting logs")
		}
		if r != nil {
			written, err := io.Copy(out, r)
			if err != nil {
				return nil, errors.Wrap(err, "copying logs to stdout")
			}
			offset += written
			r.Close()
		}
		switch b.Status {
		case StatusQueued, StatusWorking, StatusUnknown:
		case StatusSuccess:
			imageID, err = getImageID(b)
			if err != nil {
				return nil, errors.Wrap(err, "getting image id from finished build")
			}
			break watch
		case StatusFailure, StatusInternalError, StatusTimeout, StatusCancelled:
			return nil, fmt.Errorf("cloud build failed: %s", b.Status)
		default:
			return nil, fmt.Errorf("unknown status: %s", b.Status)
		}

		time.Sleep(RetryDelay)
	}

	if err := c.Bucket(cbBucket).Object(buildObject).Delete(ctx); err != nil {
		return nil, errors.Wrap(err, "cleaning up source tar after build")
	}
	logrus.Infof("Deleted object %s", buildObject)
	builtTag := fmt.Sprintf("%s@%s", artifact.ImageName, imageID)
	logrus.Infof("Image built at %s", builtTag)

	newTag, err := tagger.GenerateFullyQualifiedImageName(".", &tag.TagOptions{
		ImageName: artifact.ImageName,
		Digest:    imageID,
	})

	if err != nil {
		return nil, errors.Wrap(err, "generating tag")
	}

	if err := docker.AddTag(builtTag, newTag); err != nil {
		return nil, errors.Wrap(err, "tagging image")
	}

	return &Build{
		ImageName: artifact.ImageName,
		Tag:       newTag,
		Artifact:  artifact,
	}, nil
}

func getBuildID(op *cloudbuild.Operation) (string, error) {
	if op.Metadata == nil {
		return "", errors.New("missing Metadata in operation")
	}
	var buildMeta cloudbuild.BuildOperationMetadata
	if err := json.Unmarshal([]byte(op.Metadata), &buildMeta); err != nil {
		return "", err
	}
	if buildMeta.Build == nil {
		return "", errors.New("missing Build in operation metadata")
	}
	return buildMeta.Build.Id, nil
}

func getImageID(b *cloudbuild.Build) (string, error) {
	if b.Results == nil || len(b.Results.Images) == 0 {
		return "", errors.New("build failed")
	}
	return b.Results.Images[0].Digest, nil
}

func (cb *GoogleCloudBuilder) uploadTarToGCS(ctx context.Context, dockerfilePath, dockerCtx, bucket, objectName string) error {
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return err
	}
	defer c.Close()

	w := c.Bucket(bucket).Object(objectName).NewWriter(ctx)
	if err := docker.CreateDockerTarGzContext(w, dockerfilePath, dockerCtx); err != nil {
		return errors.Wrap(err, "uploading targz to google storage")
	}
	return w.Close()
}

func (cb *GoogleCloudBuilder) getLogs(ctx context.Context, offset int64, bucket, objectName string) (io.ReadCloser, error) {
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "getting storage client")
	}
	defer c.Close()

	r, err := c.Bucket(bucket).Object(objectName).NewRangeReader(ctx, offset, -1)
	if err != nil {
		if gerr, ok := err.(*googleapi.Error); ok {
			switch gerr.Code {
			case 404, 416, 429, 503:
				logrus.Debugf("Status Code: %d, %s", gerr.Code, gerr.Body)
				return nil, nil
			}
		}
		if err == cstorage.ErrObjectNotExist {
			logrus.Debugf("Logs for %s %s not uploaded yet...", bucket, objectName)
			return nil, nil
		}
		return nil, errors.Wrap(err, "unknown error")
	}
	return r, nil
}

func (cb *GoogleCloudBuilder) checkBucketProjectCorrect(ctx context.Context, bucket string) error {
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return errors.Wrap(err, "getting storage client")
	}
	it := c.Buckets(ctx, cb.GoogleCloudBuild.ProjectID)
	// Set the prefix to the bucket we're looking for to only return that bucket and buckets with that prefix
	// that we'll filter further later on
	it.Prefix = bucket
	for {
		attrs, err := it.Next()
		if err == iterator.Done {
			return errors.Wrap(err, "bucket not found")
		}
		if err != nil {
			return errors.Wrap(err, "iterating over buckets")
		}
		// Since we can't filter on bucket name specifically, only prefix, we need to check equality here and not just prefix
		if attrs.Name == bucket {
			return nil
		}
	}

}

func (cb *GoogleCloudBuilder) createBucketIfNotExists(ctx context.Context, bucket string) error {
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return errors.Wrap(err, "getting storage client")
	}
	defer c.Close()

	_, err = c.Bucket(bucket).Attrs(ctx)

	if err == nil {
		// Bucket exists
		return nil
	}

	if err != cstorage.ErrBucketNotExist {
		return errors.Wrapf(err, "getting bucket %s", bucket)
	}

	if err := c.Bucket(bucket).Create(ctx, cb.GoogleCloudBuild.ProjectID, &cstorage.BucketAttrs{
		Name: bucket,
	}); err != nil {
		return err
	}
	logrus.Debugf("Created bucket %s in %s", bucket, cb.GoogleCloudBuild.ProjectID)
	return nil
}
<commit_msg>gcb: make upload to tar public<commit_after>/*
Copyright 2018 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 build

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

	cstorage "cloud.google.com/go/storage"
	"golang.org/x/oauth2/google"
	cloudbuild "google.golang.org/api/cloudbuild/v1"
	"google.golang.org/api/googleapi"
	"google.golang.org/api/iterator"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

const (
	// StatusUnknown "STATUS_UNKNOWN" - Status of the build is unknown.
	StatusUnknown = "STATUS_UNKNOWN"

	// StatusQueued "QUEUED" - Build is queued; work has not yet begun.
	StatusQueued = "QUEUED"

	// StatusWorking "WORKING" - Build is being executed.
	StatusWorking = "WORKING"

	// StatusSuccess  "SUCCESS" - Build finished successfully.
	StatusSuccess = "SUCCESS"

	// StatusFailure  "FAILURE" - Build failed to complete successfully.
	StatusFailure = "FAILURE"

	// StatusInternalError  "INTERNAL_ERROR" - Build failed due to an internal cause.
	StatusInternalError = "INTERNAL_ERROR"

	// StatusTimeout  "TIMEOUT" - Build took longer than was allowed.
	StatusTimeout = "TIMEOUT"

	// StatusCancelled  "CANCELLED" - Build was canceled by a user.
	StatusCancelled = "CANCELLED"

	// RetryDelay is the time to wait in between polling the status of the cloud build
	RetryDelay = 1 * time.Second
)

type GoogleCloudBuilder struct {
	*v1alpha2.BuildConfig
}

func NewGoogleCloudBuilder(cfg *v1alpha2.BuildConfig) (*GoogleCloudBuilder, error) {
	return &GoogleCloudBuilder{cfg}, nil
}

func (cb *GoogleCloudBuilder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*v1alpha2.Artifact) (*BuildResult, error) {
	client, err := google.DefaultClient(ctx, cloudbuild.CloudPlatformScope)
	if err != nil {
		return nil, errors.Wrap(err, "getting google client")
	}
	cbclient, err := cloudbuild.New(client)
	if err != nil {
		return nil, errors.Wrap(err, "getting builder")
	}
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "getting cloud storage client")
	}
	defer c.Close()
	builds := []Build{}
	for _, artifact := range artifacts {
		build, err := cb.buildArtifact(ctx, out, tagger, cbclient, c, artifact)
		if err != nil {
			return nil, errors.Wrapf(err, "building artifact %s", artifact.ImageName)
		}
		builds = append(builds, *build)
	}

	return &BuildResult{
		Builds: builds,
	}, nil
}

func (cb *GoogleCloudBuilder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.Tagger, cbclient *cloudbuild.Service, c *cstorage.Client, artifact *v1alpha2.Artifact) (*Build, error) {
	logrus.Infof("Building artifact: %+v", artifact)

	// need to format build args as strings to pass to container builder docker
	var buildArgs []string
	for k, v := range artifact.DockerArtifact.BuildArgs {
		if v != nil {
			buildArgs = append(buildArgs, []string{"--build-arg", fmt.Sprintf("%s=%s", k, *v)}...)
		}
	}
	logrus.Debugf("Build args: %s", buildArgs)

	cbBucket := fmt.Sprintf("%s%s", cb.GoogleCloudBuild.ProjectID, constants.GCSBucketSuffix)
	buildObject := fmt.Sprintf("source/%s-%s.tar.gz", cb.GoogleCloudBuild.ProjectID, util.RandomID())

	if err := cb.createBucketIfNotExists(ctx, cbBucket); err != nil {
		return nil, errors.Wrap(err, "creating bucket if not exists")
	}
	if err := cb.checkBucketProjectCorrect(ctx, cbBucket); err != nil {
		return nil, errors.Wrap(err, "checking bucket is in correct project")
	}

	fmt.Fprintf(out, "Pushing code to gs://%s/%s\n", cbBucket, buildObject)
	if err := UploadTarToGCS(ctx, artifact.DockerArtifact.DockerfilePath, artifact.Workspace, cbBucket, buildObject); err != nil {
		return nil, errors.Wrap(err, "uploading source tarball")
	}

	args := append([]string{"build", "--tag", artifact.ImageName, "-f", artifact.DockerArtifact.DockerfilePath}, buildArgs...)
	args = append(args, ".")
	call := cbclient.Projects.Builds.Create(cb.GoogleCloudBuild.ProjectID, &cloudbuild.Build{
		LogsBucket: cbBucket,
		Source: &cloudbuild.Source{
			StorageSource: &cloudbuild.StorageSource{
				Bucket: cbBucket,
				Object: buildObject,
			},
		},
		Steps: []*cloudbuild.BuildStep{
			{
				Name: "gcr.io/cloud-builders/docker",
				Args: args,
			},
		},
		Images: []string{artifact.ImageName},
	})
	op, err := call.Context(ctx).Do()
	if err != nil {
		return nil, errors.Wrap(err, "could not create build")
	}

	remoteID, err := getBuildID(op)
	if err != nil {
		return nil, errors.Wrapf(err, "getting build ID from op")
	}
	logsObject := fmt.Sprintf("log-%s.txt", remoteID)
	fmt.Fprintf(out, "Logs at available at \nhttps://console.cloud.google.com/m/cloudstorage/b/%s/o/%s\n", cbBucket, logsObject)
	var imageID string
	offset := int64(0)
watch:
	for {
		logrus.Debugf("current offset %d", offset)
		b, err := cbclient.Projects.Builds.Get(cb.GoogleCloudBuild.ProjectID, remoteID).Do()
		if err != nil {
			return nil, errors.Wrap(err, "getting build status")
		}

		r, err := cb.getLogs(ctx, offset, cbBucket, logsObject)
		if err != nil {
			return nil, errors.Wrap(err, "getting logs")
		}
		if r != nil {
			written, err := io.Copy(out, r)
			if err != nil {
				return nil, errors.Wrap(err, "copying logs to stdout")
			}
			offset += written
			r.Close()
		}
		switch b.Status {
		case StatusQueued, StatusWorking, StatusUnknown:
		case StatusSuccess:
			imageID, err = getImageID(b)
			if err != nil {
				return nil, errors.Wrap(err, "getting image id from finished build")
			}
			break watch
		case StatusFailure, StatusInternalError, StatusTimeout, StatusCancelled:
			return nil, fmt.Errorf("cloud build failed: %s", b.Status)
		default:
			return nil, fmt.Errorf("unknown status: %s", b.Status)
		}

		time.Sleep(RetryDelay)
	}

	if err := c.Bucket(cbBucket).Object(buildObject).Delete(ctx); err != nil {
		return nil, errors.Wrap(err, "cleaning up source tar after build")
	}
	logrus.Infof("Deleted object %s", buildObject)
	builtTag := fmt.Sprintf("%s@%s", artifact.ImageName, imageID)
	logrus.Infof("Image built at %s", builtTag)

	newTag, err := tagger.GenerateFullyQualifiedImageName(".", &tag.TagOptions{
		ImageName: artifact.ImageName,
		Digest:    imageID,
	})

	if err != nil {
		return nil, errors.Wrap(err, "generating tag")
	}

	if err := docker.AddTag(builtTag, newTag); err != nil {
		return nil, errors.Wrap(err, "tagging image")
	}

	return &Build{
		ImageName: artifact.ImageName,
		Tag:       newTag,
		Artifact:  artifact,
	}, nil
}

func getBuildID(op *cloudbuild.Operation) (string, error) {
	if op.Metadata == nil {
		return "", errors.New("missing Metadata in operation")
	}
	var buildMeta cloudbuild.BuildOperationMetadata
	if err := json.Unmarshal([]byte(op.Metadata), &buildMeta); err != nil {
		return "", err
	}
	if buildMeta.Build == nil {
		return "", errors.New("missing Build in operation metadata")
	}
	return buildMeta.Build.Id, nil
}

func getImageID(b *cloudbuild.Build) (string, error) {
	if b.Results == nil || len(b.Results.Images) == 0 {
		return "", errors.New("build failed")
	}
	return b.Results.Images[0].Digest, nil
}

func UploadTarToGCS(ctx context.Context, dockerfilePath, dockerCtx, bucket, objectName string) error {
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return err
	}
	defer c.Close()

	w := c.Bucket(bucket).Object(objectName).NewWriter(ctx)
	if err := docker.CreateDockerTarGzContext(w, dockerfilePath, dockerCtx); err != nil {
		return errors.Wrap(err, "uploading targz to google storage")
	}
	return w.Close()
}

func (cb *GoogleCloudBuilder) getLogs(ctx context.Context, offset int64, bucket, objectName string) (io.ReadCloser, error) {
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "getting storage client")
	}
	defer c.Close()

	r, err := c.Bucket(bucket).Object(objectName).NewRangeReader(ctx, offset, -1)
	if err != nil {
		if gerr, ok := err.(*googleapi.Error); ok {
			switch gerr.Code {
			case 404, 416, 429, 503:
				logrus.Debugf("Status Code: %d, %s", gerr.Code, gerr.Body)
				return nil, nil
			}
		}
		if err == cstorage.ErrObjectNotExist {
			logrus.Debugf("Logs for %s %s not uploaded yet...", bucket, objectName)
			return nil, nil
		}
		return nil, errors.Wrap(err, "unknown error")
	}
	return r, nil
}

func (cb *GoogleCloudBuilder) checkBucketProjectCorrect(ctx context.Context, bucket string) error {
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return errors.Wrap(err, "getting storage client")
	}
	it := c.Buckets(ctx, cb.GoogleCloudBuild.ProjectID)
	// Set the prefix to the bucket we're looking for to only return that bucket and buckets with that prefix
	// that we'll filter further later on
	it.Prefix = bucket
	for {
		attrs, err := it.Next()
		if err == iterator.Done {
			return errors.Wrap(err, "bucket not found")
		}
		if err != nil {
			return errors.Wrap(err, "iterating over buckets")
		}
		// Since we can't filter on bucket name specifically, only prefix, we need to check equality here and not just prefix
		if attrs.Name == bucket {
			return nil
		}
	}

}

func (cb *GoogleCloudBuilder) createBucketIfNotExists(ctx context.Context, bucket string) error {
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return errors.Wrap(err, "getting storage client")
	}
	defer c.Close()

	_, err = c.Bucket(bucket).Attrs(ctx)

	if err == nil {
		// Bucket exists
		return nil
	}

	if err != cstorage.ErrBucketNotExist {
		return errors.Wrapf(err, "getting bucket %s", bucket)
	}

	if err := c.Bucket(bucket).Create(ctx, cb.GoogleCloudBuild.ProjectID, &cstorage.BucketAttrs{
		Name: bucket,
	}); err != nil {
		return err
	}
	logrus.Debugf("Created bucket %s in %s", bucket, cb.GoogleCloudBuild.ProjectID)
	return nil
}
<|endoftext|>


<commit_before>package coordinator

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"

	log "github.com/sirupsen/logrus"
	core_v1 "k8s.io/api/core/v1"

	"github.com/caicloud/cyclone/pkg/apis/cyclone/v1alpha1"
	"github.com/caicloud/cyclone/pkg/k8s/clientset"
	fileutil "github.com/caicloud/cyclone/pkg/util/file"
	"github.com/caicloud/cyclone/pkg/workflow/common"
	"github.com/caicloud/cyclone/pkg/workflow/coordinator/k8sapi"
)

// Coordinator is a struct which contains infomations
// will be used in workflow sidecar named coordinator.
type Coordinator struct {
	runtimeExec RuntimeExecutor
	// workloadContainer represents name of the workload container.
	workloadContainer string
	// Stage related to this pod.
	Stage *v1alpha1.Stage
	// Wfr represents the WorkflowRun which triggered this pod.
	Wfr *v1alpha1.WorkflowRun
	// OutputResources represents output resources the related stage configured.
	OutputResources []*v1alpha1.Resource
}

// RuntimeExecutor is an interface defined some methods
// to communicate with k8s container runtime.
type RuntimeExecutor interface {
	// WaitContainers waits selected containers to state.
	WaitContainers(state common.ContainerState, selectors ...common.ContainerSelector) error
	// CollectLog collects container logs to cyclone server.
	CollectLog(container, wrorkflowrun, stage string) error
	// CopyFromContainer copy a file or directory from container:path to dst.
	CopyFromContainer(container, path, dst string) error
	// GetPod get the stage related pod.
	GetPod() (*core_v1.Pod, error)
	// SetResults set results (key-values) to the pod, workflow controller would sync this result
	// to WorkflowRun status.
	SetResults(values []v1alpha1.KeyValue) error
}

// NewCoordinator create a coordinator instance.
func NewCoordinator(client clientset.Interface) (*Coordinator, error) {
	// Get stage from Env
	var stage *v1alpha1.Stage
	stageInfo := os.Getenv(common.EnvStageInfo)
	if stageInfo == "" {
		return nil, fmt.Errorf("get stage info from env failed")
	}

	err := json.Unmarshal([]byte(stageInfo), &stage)
	if err != nil {
		return nil, fmt.Errorf("unmarshal stage info error %s", err)
	}

	// Get workflowrun from Env
	var wfr *v1alpha1.WorkflowRun
	wfrInfo := os.Getenv(common.EnvWorkflowRunInfo)
	if stageInfo == "" {
		return nil, fmt.Errorf("get workflowrun info from env failed")
	}

	err = json.Unmarshal([]byte(wfrInfo), &wfr)
	if err != nil {
		return nil, fmt.Errorf("unmarshal workflowrun info error %s", err)
	}

	// Get output resources from Env
	var rscs []*v1alpha1.Resource
	rscInfo := os.Getenv(common.EnvOutputResourcesInfo)
	if rscInfo == "" {
		return nil, fmt.Errorf("get output resources info from env failed")
	}

	err = json.Unmarshal([]byte(rscInfo), &rscs)
	if err != nil {
		return nil, fmt.Errorf("unmarshal output resources info error %s", err)
	}

	return &Coordinator{
		runtimeExec:       k8sapi.NewK8sapiExecutor(client, wfr.Namespace, getNamespace(), getPodName(), getCycloneServerAddr()),
		workloadContainer: getWorkloadContainer(),
		Stage:             stage,
		Wfr:               wfr,
		OutputResources:   rscs,
	}, nil
}

// CollectLogs collects all containers' logs.
func (co *Coordinator) CollectLogs() error {
	cs, err := co.getAllContainers()
	if err != nil {
		return err
	}

	for _, c := range cs {
		go func(container, workflowrun, stage string) {
			err := co.runtimeExec.CollectLog(container, workflowrun, stage)
			if err != nil {
				log.Errorf("Collect %s log failed:%v", container, err)
			}
		}(c, co.Wfr.Name, co.Stage.Name)
	}

	return nil
}

// WaitRunning waits all containers to start run.
func (co *Coordinator) WaitRunning() error {
	err := co.runtimeExec.WaitContainers(common.ContainerStateInitialized)
	if err != nil {
		log.Errorf("Wait containers to running error: %v", err)
	}
	return err
}

// WaitWorkloadTerminate waits all workload containers to be Terminated status.
func (co *Coordinator) WaitWorkloadTerminate() error {
	err := co.runtimeExec.WaitContainers(common.ContainerStateTerminated, common.OnlyWorkload)
	if err != nil {
		log.Errorf("Wait containers to completion error: %v", err)
	}
	return err
}

// WaitAllOthersTerminate waits all containers except for
// the coordinator container itself to become Terminated status.
func (co *Coordinator) WaitAllOthersTerminate() error {
	err := co.runtimeExec.WaitContainers(common.ContainerStateTerminated,
		common.NonWorkloadSidecar, common.NonCoordinator, common.NonDockerInDocker)
	if err != nil {
		log.Errorf("Wait containers to completion error: %v", err)
	}
	return err
}

// StageSuccess checks if the workload and resolver containers are succeeded.
func (co *Coordinator) StageSuccess() bool {
	ws, err := co.GetExitCodes(common.NonCoordinator, common.NonWorkloadSidecar)
	if err != nil {
		log.Errorf("Get Exit Codes failed: %v", err)
		return false
	}

	log.WithField("codes", ws).Debug("Get containers exit codes")

	for _, code := range ws {
		if code != 0 {
			return false
		}
	}

	return true
}

// WorkLoadSuccess checks if the workload containers are succeeded.
func (co *Coordinator) WorkLoadSuccess() bool {
	ws, err := co.GetExitCodes(common.OnlyWorkload)
	if err != nil {
		log.Errorf("Get Exit Codes failed: %v", err)
		return false
	}

	log.WithField("codes", ws).Debug("Get containers exit codes")

	for _, code := range ws {
		if code != 0 {
			return false
		}
	}
	return true
}

// GetExitCodes gets exit codes of containers passed the selector
func (co *Coordinator) GetExitCodes(selectors ...common.ContainerSelector) (map[string]int32, error) {
	ws := make(map[string]int32)

	pod, err := co.runtimeExec.GetPod()
	if err != nil {
		return ws, err
	}

	log.WithField("container statuses", pod.Status.ContainerStatuses).Debug()

	for _, cs := range pod.Status.ContainerStatuses {
		if common.Pass(cs.Name, selectors) {
			if cs.State.Terminated == nil {
				log.Warningf("container %s not terminated.", cs.Name)
				continue
			}
			ws[cs.Name] = cs.State.Terminated.ExitCode
		}
	}

	return ws, nil
}

// CollectArtifacts collects workload artifacts.
func (co *Coordinator) CollectArtifacts() error {
	if co.Stage.Spec.Pod == nil {
		return fmt.Errorf("get stage output artifacts failed, stage pod nil")
	}

	artifacts := co.Stage.Spec.Pod.Outputs.Artifacts
	if len(artifacts) == 0 {
		log.Info("output artifacts empty, no need to collect.")
		return nil
	}

	log.WithField("artifacts", artifacts).Info("start to collect.")

	// Create the artifacts directory if not exist.
	fileutil.CreateDirectory(common.CoordinatorArtifactsPath)

	for _, artifact := range artifacts {
		dst := path.Join(common.CoordinatorArtifactsPath, artifact.Name)
		fileutil.CreateDirectory(dst)

		id, err := co.getContainerID(co.workloadContainer)
		if err != nil {
			log.Errorf("get container %s's id failed: %v", co.workloadContainer, err)
			return err
		}

		err = co.runtimeExec.CopyFromContainer(id, artifact.Path, dst)
		if err != nil {
			log.Errorf("Copy container %s artifact %s failed: %v", co.workloadContainer, artifact.Name, err)
			return err
		}
	}

	return nil
}

// CollectResources collects workload resources.
func (co *Coordinator) CollectResources() error {
	if co.Stage.Spec.Pod == nil {
		return fmt.Errorf("get stage output resources failed, stage pod nil")
	}

	resources := co.Stage.Spec.Pod.Outputs.Resources
	if len(resources) == 0 {
		log.Info("output resources empty, no need to collect.")
		return nil
	}

	log.WithField("resources", resources).Info("start to collect.")

	// Create the resources directory if not exist.
	fileutil.CreateDirectory(common.CoordinatorResourcesPath)

	for _, resource := range resources {
		for _, r := range co.OutputResources {
			if r.Name == resource.Name {
				// If the resource is persisted in PVC, no need to copy here, Cyclone
				// will mount it to resolver container directly.
				if r.Spec.Persistent != nil {
					continue
				}
			}
		}

		if len(resource.Path) == 0 {
			continue
		}

		dst := path.Join(common.CoordinatorResourcesPath, resource.Name)
		fileutil.CreateDirectory(dst)

		id, err := co.getContainerID(co.workloadContainer)
		if err != nil {
			log.Errorf("get container %s's id failed: %v", co.workloadContainer, err)
			return err
		}

		err = co.runtimeExec.CopyFromContainer(id, resource.Path, dst)
		if err != nil {
			log.Errorf("Copy container %s resources %s failed: %v", co.workloadContainer, resource.Name, err)
			return err
		}
	}

	return nil
}

// NotifyResolvers create a file to notify output resolvers to start working.
func (co *Coordinator) NotifyResolvers() error {
	if co.Stage.Spec.Pod == nil {
		return fmt.Errorf("get stage output resources failed, stage pod nil")
	}

	resources := co.Stage.Spec.Pod.Outputs.Resources
	if len(resources) == 0 {
		log.Info("output resources empty, no need to notify resolver.")
		return nil
	}

	log.WithField("resources", resources).Info("start to notify resolver.")

	exist := fileutil.CreateDirectory(common.CoordinatorResolverNotifyPath)
	log.WithField("exist", exist).WithField("notifydir", common.CoordinatorResolverNotifyPath).Info()

	_, err := os.Create(common.CoordinatorResolverNotifyOkPath)
	if err != nil {
		log.WithField("file", common.CoordinatorResolverNotifyOkPath).Error("Create ok file error: ", err)
		return err
	}

	return nil
}

func (co *Coordinator) getAllContainers() ([]string, error) {
	var cs []string
	pod, err := co.runtimeExec.GetPod()
	if err != nil {
		return cs, err
	}

	for _, c := range pod.Spec.InitContainers {
		cs = append(cs, c.Name)
	}
	for _, c := range pod.Spec.Containers {
		cs = append(cs, c.Name)
	}

	return cs, nil
}

func (co *Coordinator) getContainerID(name string) (string, error) {
	pod, err := co.runtimeExec.GetPod()
	if err != nil {
		return "", err
	}

	for _, cs := range pod.Status.ContainerStatuses {
		if cs.Name == name {
			return refineContainerID(cs.ContainerID), nil
		}
	}

	return "", fmt.Errorf("container %s not found", name)
}

// CollectExecutionResults collects execution results (key-values) and store them in pod's annotation
func (co *Coordinator) CollectExecutionResults() error {
	pod, err := co.runtimeExec.GetPod()
	if err != nil {
		return err
	}

	for _, c := range pod.Spec.Containers {
		if !common.OnlyWorkload(c.Name) {
			continue
		}

		dst := fmt.Sprintf("/tmp/__result__%s", c.Name)
		containerID, err := co.getContainerID(c.Name)
		if err != nil {
			log.WithField("c", containerID).Error("Get container ID error: ", err)
			return err
		}
		err = co.runtimeExec.CopyFromContainer(containerID, common.ResultFilePath, dst)
		if isFileNotExist(err) {
			continue
		}

		if err != nil {
			return err
		}

		b, err := ioutil.ReadFile(dst)
		if err != nil {
			return err
		}
		log.Info("Result file content: ", string(b))

		var keyValues []v1alpha1.KeyValue
		lines := strings.Split(string(b), "\n")
		for _, line := range lines {
			line = strings.TrimSpace(line)
			if len(line) == 0 {
				continue
			}

			parts := strings.SplitN(line, ":", 2)
			if len(parts) < 2 {
				log.Warn("Invalid result item: ", line)
				continue
			}
			log.Info("Result item: ", line)

			keyValues = append(keyValues, v1alpha1.KeyValue{
				Key:   parts[0],
				Value: parts[1],
			})
		}

		if len(keyValues) > 0 {
			log.Info("To set execution result")
			if err := co.runtimeExec.SetResults(keyValues); err != nil {
				return err
			}
		}
	}

	return nil
}

func isFileNotExist(err error) bool {
	if err == nil {
		return false
	}

	return strings.Contains(err.Error(), "No such container:path")
}
<commit_msg>feat: support collect execution results from init-container (#1015)<commit_after>package coordinator

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"

	log "github.com/sirupsen/logrus"
	core_v1 "k8s.io/api/core/v1"

	"github.com/caicloud/cyclone/pkg/apis/cyclone/v1alpha1"
	"github.com/caicloud/cyclone/pkg/k8s/clientset"
	fileutil "github.com/caicloud/cyclone/pkg/util/file"
	"github.com/caicloud/cyclone/pkg/workflow/common"
	"github.com/caicloud/cyclone/pkg/workflow/coordinator/k8sapi"
)

// Coordinator is a struct which contains infomations
// will be used in workflow sidecar named coordinator.
type Coordinator struct {
	runtimeExec RuntimeExecutor
	// workloadContainer represents name of the workload container.
	workloadContainer string
	// Stage related to this pod.
	Stage *v1alpha1.Stage
	// Wfr represents the WorkflowRun which triggered this pod.
	Wfr *v1alpha1.WorkflowRun
	// OutputResources represents output resources the related stage configured.
	OutputResources []*v1alpha1.Resource
}

// RuntimeExecutor is an interface defined some methods
// to communicate with k8s container runtime.
type RuntimeExecutor interface {
	// WaitContainers waits selected containers to state.
	WaitContainers(state common.ContainerState, selectors ...common.ContainerSelector) error
	// CollectLog collects container logs to cyclone server.
	CollectLog(container, wrorkflowrun, stage string) error
	// CopyFromContainer copy a file or directory from container:path to dst.
	CopyFromContainer(container, path, dst string) error
	// GetPod get the stage related pod.
	GetPod() (*core_v1.Pod, error)
	// SetResults set results (key-values) to the pod, workflow controller would sync this result
	// to WorkflowRun status.
	SetResults(values []v1alpha1.KeyValue) error
}

// NewCoordinator create a coordinator instance.
func NewCoordinator(client clientset.Interface) (*Coordinator, error) {
	// Get stage from Env
	var stage *v1alpha1.Stage
	stageInfo := os.Getenv(common.EnvStageInfo)
	if stageInfo == "" {
		return nil, fmt.Errorf("get stage info from env failed")
	}

	err := json.Unmarshal([]byte(stageInfo), &stage)
	if err != nil {
		return nil, fmt.Errorf("unmarshal stage info error %s", err)
	}

	// Get workflowrun from Env
	var wfr *v1alpha1.WorkflowRun
	wfrInfo := os.Getenv(common.EnvWorkflowRunInfo)
	if stageInfo == "" {
		return nil, fmt.Errorf("get workflowrun info from env failed")
	}

	err = json.Unmarshal([]byte(wfrInfo), &wfr)
	if err != nil {
		return nil, fmt.Errorf("unmarshal workflowrun info error %s", err)
	}

	// Get output resources from Env
	var rscs []*v1alpha1.Resource
	rscInfo := os.Getenv(common.EnvOutputResourcesInfo)
	if rscInfo == "" {
		return nil, fmt.Errorf("get output resources info from env failed")
	}

	err = json.Unmarshal([]byte(rscInfo), &rscs)
	if err != nil {
		return nil, fmt.Errorf("unmarshal output resources info error %s", err)
	}

	return &Coordinator{
		runtimeExec:       k8sapi.NewK8sapiExecutor(client, wfr.Namespace, getNamespace(), getPodName(), getCycloneServerAddr()),
		workloadContainer: getWorkloadContainer(),
		Stage:             stage,
		Wfr:               wfr,
		OutputResources:   rscs,
	}, nil
}

// CollectLogs collects all containers' logs.
func (co *Coordinator) CollectLogs() error {
	cs, err := co.getAllContainers()
	if err != nil {
		return err
	}

	for _, c := range cs {
		go func(container, workflowrun, stage string) {
			err := co.runtimeExec.CollectLog(container, workflowrun, stage)
			if err != nil {
				log.Errorf("Collect %s log failed:%v", container, err)
			}
		}(c, co.Wfr.Name, co.Stage.Name)
	}

	return nil
}

// WaitRunning waits all containers to start run.
func (co *Coordinator) WaitRunning() error {
	err := co.runtimeExec.WaitContainers(common.ContainerStateInitialized)
	if err != nil {
		log.Errorf("Wait containers to running error: %v", err)
	}
	return err
}

// WaitWorkloadTerminate waits all workload containers to be Terminated status.
func (co *Coordinator) WaitWorkloadTerminate() error {
	err := co.runtimeExec.WaitContainers(common.ContainerStateTerminated, common.OnlyWorkload)
	if err != nil {
		log.Errorf("Wait containers to completion error: %v", err)
	}
	return err
}

// WaitAllOthersTerminate waits all containers except for
// the coordinator container itself to become Terminated status.
func (co *Coordinator) WaitAllOthersTerminate() error {
	err := co.runtimeExec.WaitContainers(common.ContainerStateTerminated,
		common.NonWorkloadSidecar, common.NonCoordinator, common.NonDockerInDocker)
	if err != nil {
		log.Errorf("Wait containers to completion error: %v", err)
	}
	return err
}

// StageSuccess checks if the workload and resolver containers are succeeded.
func (co *Coordinator) StageSuccess() bool {
	ws, err := co.GetExitCodes(common.NonCoordinator, common.NonWorkloadSidecar)
	if err != nil {
		log.Errorf("Get Exit Codes failed: %v", err)
		return false
	}

	log.WithField("codes", ws).Debug("Get containers exit codes")

	for _, code := range ws {
		if code != 0 {
			return false
		}
	}

	return true
}

// WorkLoadSuccess checks if the workload containers are succeeded.
func (co *Coordinator) WorkLoadSuccess() bool {
	ws, err := co.GetExitCodes(common.OnlyWorkload)
	if err != nil {
		log.Errorf("Get Exit Codes failed: %v", err)
		return false
	}

	log.WithField("codes", ws).Debug("Get containers exit codes")

	for _, code := range ws {
		if code != 0 {
			return false
		}
	}
	return true
}

// GetExitCodes gets exit codes of containers passed the selector
func (co *Coordinator) GetExitCodes(selectors ...common.ContainerSelector) (map[string]int32, error) {
	ws := make(map[string]int32)

	pod, err := co.runtimeExec.GetPod()
	if err != nil {
		return ws, err
	}

	log.WithField("container statuses", pod.Status.ContainerStatuses).Debug()

	for _, cs := range pod.Status.ContainerStatuses {
		if common.Pass(cs.Name, selectors) {
			if cs.State.Terminated == nil {
				log.Warningf("container %s not terminated.", cs.Name)
				continue
			}
			ws[cs.Name] = cs.State.Terminated.ExitCode
		}
	}

	return ws, nil
}

// CollectArtifacts collects workload artifacts.
func (co *Coordinator) CollectArtifacts() error {
	if co.Stage.Spec.Pod == nil {
		return fmt.Errorf("get stage output artifacts failed, stage pod nil")
	}

	artifacts := co.Stage.Spec.Pod.Outputs.Artifacts
	if len(artifacts) == 0 {
		log.Info("output artifacts empty, no need to collect.")
		return nil
	}

	log.WithField("artifacts", artifacts).Info("start to collect.")

	// Create the artifacts directory if not exist.
	fileutil.CreateDirectory(common.CoordinatorArtifactsPath)

	for _, artifact := range artifacts {
		dst := path.Join(common.CoordinatorArtifactsPath, artifact.Name)
		fileutil.CreateDirectory(dst)

		id, err := co.getContainerID(co.workloadContainer)
		if err != nil {
			log.Errorf("get container %s's id failed: %v", co.workloadContainer, err)
			return err
		}

		err = co.runtimeExec.CopyFromContainer(id, artifact.Path, dst)
		if err != nil {
			log.Errorf("Copy container %s artifact %s failed: %v", co.workloadContainer, artifact.Name, err)
			return err
		}
	}

	return nil
}

// CollectResources collects workload resources.
func (co *Coordinator) CollectResources() error {
	if co.Stage.Spec.Pod == nil {
		return fmt.Errorf("get stage output resources failed, stage pod nil")
	}

	resources := co.Stage.Spec.Pod.Outputs.Resources
	if len(resources) == 0 {
		log.Info("output resources empty, no need to collect.")
		return nil
	}

	log.WithField("resources", resources).Info("start to collect.")

	// Create the resources directory if not exist.
	fileutil.CreateDirectory(common.CoordinatorResourcesPath)

	for _, resource := range resources {
		for _, r := range co.OutputResources {
			if r.Name == resource.Name {
				// If the resource is persisted in PVC, no need to copy here, Cyclone
				// will mount it to resolver container directly.
				if r.Spec.Persistent != nil {
					continue
				}
			}
		}

		if len(resource.Path) == 0 {
			continue
		}

		dst := path.Join(common.CoordinatorResourcesPath, resource.Name)
		fileutil.CreateDirectory(dst)

		id, err := co.getContainerID(co.workloadContainer)
		if err != nil {
			log.Errorf("get container %s's id failed: %v", co.workloadContainer, err)
			return err
		}

		err = co.runtimeExec.CopyFromContainer(id, resource.Path, dst)
		if err != nil {
			log.Errorf("Copy container %s resources %s failed: %v", co.workloadContainer, resource.Name, err)
			return err
		}
	}

	return nil
}

// NotifyResolvers create a file to notify output resolvers to start working.
func (co *Coordinator) NotifyResolvers() error {
	if co.Stage.Spec.Pod == nil {
		return fmt.Errorf("get stage output resources failed, stage pod nil")
	}

	resources := co.Stage.Spec.Pod.Outputs.Resources
	if len(resources) == 0 {
		log.Info("output resources empty, no need to notify resolver.")
		return nil
	}

	log.WithField("resources", resources).Info("start to notify resolver.")

	exist := fileutil.CreateDirectory(common.CoordinatorResolverNotifyPath)
	log.WithField("exist", exist).WithField("notifydir", common.CoordinatorResolverNotifyPath).Info()

	_, err := os.Create(common.CoordinatorResolverNotifyOkPath)
	if err != nil {
		log.WithField("file", common.CoordinatorResolverNotifyOkPath).Error("Create ok file error: ", err)
		return err
	}

	return nil
}

func (co *Coordinator) getAllContainers() ([]string, error) {
	var cs []string
	pod, err := co.runtimeExec.GetPod()
	if err != nil {
		return cs, err
	}

	for _, c := range pod.Spec.InitContainers {
		cs = append(cs, c.Name)
	}
	for _, c := range pod.Spec.Containers {
		cs = append(cs, c.Name)
	}

	return cs, nil
}

func (co *Coordinator) getContainerID(name string) (string, error) {
	pod, err := co.runtimeExec.GetPod()
	if err != nil {
		return "", err
	}

	for _, cs := range pod.Status.ContainerStatuses {
		if cs.Name == name {
			return refineContainerID(cs.ContainerID), nil
		}
	}

	for _, cs := range pod.Status.InitContainerStatuses {
		if cs.Name == name {
			return refineContainerID(cs.ContainerID), nil
		}
	}

	return "", fmt.Errorf("container %s not found", name)
}

// CollectExecutionResults collects execution results (key-values) and store them in pod's annotation
func (co *Coordinator) CollectExecutionResults() error {
	pod, err := co.runtimeExec.GetPod()
	if err != nil {
		return err
	}

	var keyValues []v1alpha1.KeyValue

	for _, c := range pod.Spec.Containers {
		kv, err := co.extractExecutionResults(c.Name)
		if err != nil {
			continue
		}

		keyValues = append(keyValues, kv...)
	}

	for _, c := range pod.Spec.InitContainers {
		kv, err := co.extractExecutionResults(c.Name)
		if err != nil {
			continue
		}

		keyValues = append(keyValues, kv...)
	}

	if len(keyValues) > 0 {
		log.Info("To set execution result")
		if err := co.runtimeExec.SetResults(keyValues); err != nil {
			return err
		}
	}

	return nil
}

func isFileNotExist(err error) bool {
	if err == nil {
		return false
	}

	return strings.Contains(err.Error(), "No such container:path")
}

func (co *Coordinator) extractExecutionResults(containerName string) ([]v1alpha1.KeyValue, error) {
	var keyValues []v1alpha1.KeyValue
	dst := fmt.Sprintf("/tmp/__result__%s", containerName)
	containerID, err := co.getContainerID(containerName)
	if err != nil {
		log.WithField("c", containerID).Error("Get container ID error: ", err)
		return keyValues, err
	}
	err = co.runtimeExec.CopyFromContainer(containerID, common.ResultFilePath, dst)
	if isFileNotExist(err) {
		return keyValues, err
	}

	if err != nil {
		return keyValues, err
	}

	b, err := ioutil.ReadFile(dst)
	if err != nil {
		return keyValues, err
	}
	log.Info("Result file content: ", string(b))

	lines := strings.Split(string(b), "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if len(line) == 0 {
			continue
		}

		parts := strings.SplitN(line, ":", 2)
		if len(parts) < 2 {
			log.Warn("Invalid result item: ", line)
			continue
		}
		log.Info("Result item: ", line)

		keyValues = append(keyValues, v1alpha1.KeyValue{
			Key:   parts[0],
			Value: parts[1],
		})
	}

	return keyValues, nil
}
<|endoftext|>


<commit_before>package mongoproto

import (
	"encoding/json"
	"fmt"
	"io"

	"github.com/mongodb/mongo-tools/common/bsonutil"
	"gopkg.in/mgo.v2/bson"
)

const (
	_ OpQueryFlags = 1 << iota

	OpQueryTailableCursor  // Tailable means cursor is not closed when the last data is retrieved. Rather, the cursor marks the final object’s position. You can resume using the cursor later, from where it was located, if more data were received. Like any “latent cursor”, the cursor may become invalid at some point (CursorNotFound) – for example if the final object it references were deleted.
	OpQuerySlaveOk         // Allow query of replica slave. Normally these return an error except for namespace “local”.
	OpQueryOplogReplay     // Internal replication use only - driver should not set
	OpQueryNoCursorTimeout // The server normally times out idle cursors after an inactivity period (10 minutes) to prevent excess memory use. Set this option to prevent that.
	OpQueryAwaitData       // Use with TailableCursor. If we are at the end of the data, block for a while rather than returning no data. After a timeout period, we do return as normal.
	OpQueryExhaust         // Stream the data down full blast in multiple “more” packages, on the assumption that the client will fully read all data queried. Faster when you are pulling a lot of data and know you want to pull it all down. Note: the client is not allowed to not read all the data unless it closes the connection.
	OpQueryPartial         // Get partial results from a mongos if some shards are down (instead of throwing an error)
)

type OpQueryFlags int32

// OpQuery is used to query the database for documents in a collection.
// http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query
type OpQuery struct {
	Header               MsgHeader
	Flags                OpQueryFlags
	FullCollectionName   string // "dbname.collectionname"
	NumberToSkip         int32  // number of documents to skip
	NumberToReturn       int32  // number of documents to return
	Query                []byte // query object
	ReturnFieldsSelector []byte // Optional. Selector indicating the fields to return
}

func (op *OpQuery) String() string {
	var query interface{}
	if err := bson.Unmarshal(op.Query, &query); err != nil {
		return "(error unmarshalling)"
	}
	queryAsJSON, err := bsonutil.ConvertBSONValueToJSON(query)
	if err != nil {
		return fmt.Sprintf("%#v - %v", op, err)
	}
	asJSON, err := json.Marshal(queryAsJSON)
	if err != nil {
		return fmt.Sprintf("%#v - %v", op, err)
	}
	return fmt.Sprintf("OpQuery %v %v", op.FullCollectionName, string(asJSON))
}

func (op *OpQuery) OpCode() OpCode {
	return OpCodeQuery
}

func (op *OpQuery) FromReader(r io.Reader) error {
	var b [8]byte
	if _, err := io.ReadFull(r, b[:4]); err != nil {
		return err
	}
	op.Flags = OpQueryFlags(getInt32(b[:], 0))
	name, err := readCStringFromReader(r)
	if err != nil {
		return err
	}
	op.FullCollectionName = string(name)

	if _, err := io.ReadFull(r, b[:]); err != nil {
		return err
	}
	op.NumberToSkip = getInt32(b[:], 0)
	op.NumberToReturn = getInt32(b[:], 4)

	op.Query, err = ReadDocument(r)
	if err != nil {
		return err
	}
	currentRead := len(op.Query) + len(op.FullCollectionName) + 1 + 12 + MsgHeaderLen
	if int(op.Header.MessageLength) > currentRead {
		op.ReturnFieldsSelector, err = ReadDocument(r)
		if err != nil {
			return err
		}
	}
	return nil
}

func (op *OpQuery) toWire() []byte {
	return nil
}
<commit_msg>use mongo-tools json marshaling<commit_after>package mongoproto

import (
	"fmt"
	"io"

	"github.com/mongodb/mongo-tools/common/bsonutil"
	"github.com/mongodb/mongo-tools/common/json"
	"gopkg.in/mgo.v2/bson"
)

const (
	_ OpQueryFlags = 1 << iota

	OpQueryTailableCursor  // Tailable means cursor is not closed when the last data is retrieved. Rather, the cursor marks the final object’s position. You can resume using the cursor later, from where it was located, if more data were received. Like any “latent cursor”, the cursor may become invalid at some point (CursorNotFound) – for example if the final object it references were deleted.
	OpQuerySlaveOk         // Allow query of replica slave. Normally these return an error except for namespace “local”.
	OpQueryOplogReplay     // Internal replication use only - driver should not set
	OpQueryNoCursorTimeout // The server normally times out idle cursors after an inactivity period (10 minutes) to prevent excess memory use. Set this option to prevent that.
	OpQueryAwaitData       // Use with TailableCursor. If we are at the end of the data, block for a while rather than returning no data. After a timeout period, we do return as normal.
	OpQueryExhaust         // Stream the data down full blast in multiple “more” packages, on the assumption that the client will fully read all data queried. Faster when you are pulling a lot of data and know you want to pull it all down. Note: the client is not allowed to not read all the data unless it closes the connection.
	OpQueryPartial         // Get partial results from a mongos if some shards are down (instead of throwing an error)
)

type OpQueryFlags int32

// OpQuery is used to query the database for documents in a collection.
// http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query
type OpQuery struct {
	Header               MsgHeader
	Flags                OpQueryFlags
	FullCollectionName   string // "dbname.collectionname"
	NumberToSkip         int32  // number of documents to skip
	NumberToReturn       int32  // number of documents to return
	Query                []byte // query object
	ReturnFieldsSelector []byte // Optional. Selector indicating the fields to return
}

func (op *OpQuery) String() string {
	var query interface{}
	if err := bson.Unmarshal(op.Query, &query); err != nil {
		return "(error unmarshalling)"
	}
	queryAsJSON, err := bsonutil.ConvertBSONValueToJSON(query)
	if err != nil {
		return fmt.Sprintf("ConvertBSONValueToJSON err: %#v - %v", op, err)
	}
	asJSON, err := json.Marshal(queryAsJSON)
	if err != nil {
		return fmt.Sprintf("json marshal err: %#v - %v", op, err)
	}
	return fmt.Sprintf("OpQuery %v %v", op.FullCollectionName, string(asJSON))
}

func (op *OpQuery) OpCode() OpCode {
	return OpCodeQuery
}

func (op *OpQuery) FromReader(r io.Reader) error {
	var b [8]byte
	if _, err := io.ReadFull(r, b[:4]); err != nil {
		return err
	}
	op.Flags = OpQueryFlags(getInt32(b[:], 0))
	name, err := readCStringFromReader(r)
	if err != nil {
		return err
	}
	op.FullCollectionName = string(name)

	if _, err := io.ReadFull(r, b[:]); err != nil {
		return err
	}
	op.NumberToSkip = getInt32(b[:], 0)
	op.NumberToReturn = getInt32(b[:], 4)

	op.Query, err = ReadDocument(r)
	if err != nil {
		return err
	}
	currentRead := len(op.Query) + len(op.FullCollectionName) + 1 + 12 + MsgHeaderLen
	if int(op.Header.MessageLength) > currentRead {
		op.ReturnFieldsSelector, err = ReadDocument(r)
		if err != nil {
			return err
		}
	}
	return nil
}

func (op *OpQuery) toWire() []byte {
	return nil
}
<|endoftext|>


<commit_before>package glusterfs

import (
	"fmt"
	"github.com/Sirupsen/logrus"
	"github.com/rancher/convoy/convoydriver"
	"github.com/rancher/convoy/rancher"
	"github.com/rancher/convoy/util"
	"math/rand"
	"path/filepath"
	"strconv"
	"sync"
)

const (
	DRIVER_NAME        = "glusterfs"
	DRIVER_CONFIG_FILE = "glusterfs.cfg"

	VOLUME_CFG_PREFIX = "volume_"
	DRIVER_CFG_PREFIX = DRIVER_NAME + "_"
	CFG_POSTFIX       = ".json"

	SNAPSHOT_PATH = "snapshots"

	MOUNTS_DIR = "mounts"

	GLUSTERFS_RANCHER_STACK           = "glusterfs.rancherstack"
	GLUSTERFS_RANCHER_GLUSTER_SERVICE = "glusterfs.rancherservice"
	GLUSTERFS_DEFAULT_VOLUME_POOL     = "glusterfs.defaultvolumepool"
)

var (
	log = logrus.WithFields(logrus.Fields{"pkg": "devmapper"})
)

type Driver struct {
	mutex    *sync.RWMutex
	gVolumes map[string]*GlusterFSVolume
	Device
}

func init() {
	convoydriver.Register(DRIVER_NAME, Init)
}

func (d *Driver) Name() string {
	return DRIVER_NAME
}

type Device struct {
	Root              string
	RancherURL        string
	RancherAccessKey  string
	RancherSecretKey  string
	RancherStack      string
	RancherService    string
	DefaultVolumePool string
}

func (dev *Device) ConfigFile() (string, error) {
	if dev.Root == "" {
		return "", fmt.Errorf("BUG: Invalid empty device config path")
	}
	return filepath.Join(dev.Root, DRIVER_CONFIG_FILE), nil
}

type Snapshot struct {
	UUID       string
	VolumeUUID string
}

type Volume struct {
	UUID       string
	Name       string
	Path       string
	MountPoint string
	VolumePool string

	configPath string
}

type GlusterFSVolume struct {
	UUID       string // volume name in fact
	MountPoint string
	ServerIPs  []string

	configPath string
}

func (gv *GlusterFSVolume) GetDevice() (string, error) {
	l := len(gv.ServerIPs)
	if gv.ServerIPs == nil || len(gv.ServerIPs) == 0 {
		return "", fmt.Errorf("No server IP provided for glusterfs")
	}
	ip := gv.ServerIPs[rand.Intn(l)]
	return ip + ":/" + gv.UUID, nil
}

func (gv *GlusterFSVolume) GetMountOpts() []string {
	return []string{"-t", "glusterfs"}
}

func (gv *GlusterFSVolume) GenerateDefaultMountPoint() string {
	return filepath.Join(gv.configPath, MOUNTS_DIR, gv.UUID)
}

func (v *Volume) ConfigFile() (string, error) {
	if v.UUID == "" {
		return "", fmt.Errorf("BUG: Invalid empty volume UUID")
	}
	if v.configPath == "" {
		return "", fmt.Errorf("BUG: Invalid empty volume config path")
	}
	return filepath.Join(v.configPath, DRIVER_CFG_PREFIX+VOLUME_CFG_PREFIX+v.UUID+CFG_POSTFIX), nil
}

func (device *Device) listVolumeIDs() ([]string, error) {
	return util.ListConfigIDs(device.Root, DRIVER_CFG_PREFIX+VOLUME_CFG_PREFIX, CFG_POSTFIX)
}

func Init(root string, config map[string]string) (convoydriver.ConvoyDriver, error) {
	dev := &Device{
		Root: root,
	}
	exists, err := util.ObjectExists(dev)
	if err != nil {
		return nil, err
	}
	if exists {
		if err := util.ObjectLoad(dev); err != nil {
			return nil, err
		}
	} else {
		if err := util.MkdirIfNotExists(root); err != nil {
			return nil, err
		}

		stack := config[GLUSTERFS_RANCHER_STACK]
		if stack == "" {
			return nil, fmt.Errorf("Missing required parameter: %v", GLUSTERFS_RANCHER_STACK)
		}
		service := config[GLUSTERFS_RANCHER_GLUSTER_SERVICE]
		if service == "" {
			return nil, fmt.Errorf("Missing required parameter: %v", GLUSTERFS_RANCHER_GLUSTER_SERVICE)
		}
		defaultVolumePool := config[GLUSTERFS_DEFAULT_VOLUME_POOL]
		if defaultVolumePool == "" {
			return nil, fmt.Errorf("Missing required parameter: %v", GLUSTERFS_DEFAULT_VOLUME_POOL)
		}

		dev = &Device{
			Root:              root,
			RancherStack:      stack,
			RancherService:    service,
			DefaultVolumePool: defaultVolumePool,
		}
	}

	serverIPs, err := rancher.GetIPsForServiceInStack(dev.RancherService, dev.RancherStack)
	if err != nil {
		return nil, err
	}

	d := &Driver{
		mutex:    &sync.RWMutex{},
		gVolumes: map[string]*GlusterFSVolume{},
		Device:   *dev,
	}
	gVolume := &GlusterFSVolume{
		UUID:       dev.DefaultVolumePool,
		ServerIPs:  serverIPs,
		configPath: d.Root,
	}
	// We would always mount the default volume pool
	// TODO: Also need to mount any existing volume's pool
	if _, err := util.VolumeMount(gVolume, "", true); err != nil {
		return nil, err
	}
	d.gVolumes[d.DefaultVolumePool] = gVolume

	if err := util.ObjectSave(dev); err != nil {
		return nil, err
	}
	return d, nil
}

func (d *Driver) Info() (map[string]string, error) {
	return map[string]string{
		"Root":              d.Root,
		"RancherStack":      d.RancherStack,
		"RancherService":    d.RancherService,
		"DefaultVolumePool": d.DefaultVolumePool,
	}, nil
}

func (d *Driver) VolumeOps() (convoydriver.VolumeOperations, error) {
	return d, nil
}

func (d *Driver) blankVolume(id string) *Volume {
	return &Volume{
		configPath: d.Root,
		UUID:       id,
	}
}

func (d *Driver) CreateVolume(id string, opts map[string]string) error {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	volumeName := opts[convoydriver.OPT_VOLUME_NAME]
	if volumeName == "" {
		volumeName = "volume-" + id[:8]
	}

	volume := d.blankVolume(id)
	exists, err := util.ObjectExists(volume)
	if err != nil {
		return err
	}
	if exists {
		return fmt.Errorf("volume %v already exists", id)
	}

	gVolume := d.gVolumes[d.DefaultVolumePool]
	volumePath := filepath.Join(gVolume.MountPoint, volumeName)
	if util.VolumeMountPointDirectoryExists(gVolume, volumeName) {
		log.Debugf("Found existing volume named %v, reuse it", volumeName)
	} else if err := util.VolumeMountPointDirectoryCreate(gVolume, volumeName); err != nil {
		return err
	}
	volume.Name = volumeName
	volume.Path = volumePath
	volume.VolumePool = gVolume.UUID

	return util.ObjectSave(volume)
}

func (d *Driver) DeleteVolume(id string, opts map[string]string) error {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	volume := d.blankVolume(id)
	if err := util.ObjectLoad(volume); err != nil {
		return err
	}

	if volume.MountPoint != "" {
		return fmt.Errorf("Cannot delete volume %v. It is still mounted", id)
	}
	referenceOnly, _ := strconv.ParseBool(opts[convoydriver.OPT_REFERENCE_ONLY])
	if !referenceOnly {
		log.Debugf("Cleaning up volume %v", id)
		gVolume := d.gVolumes[d.DefaultVolumePool]
		if err := util.VolumeMountPointDirectoryRemove(gVolume, volume.Name); err != nil {
			return err
		}
	}
	return util.ObjectDelete(volume)
}

func (d *Driver) MountVolume(id string, opts map[string]string) (string, error) {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	volume := d.blankVolume(id)
	if err := util.ObjectLoad(volume); err != nil {
		return "", err
	}

	specifiedPoint := opts[convoydriver.OPT_MOUNT_POINT]
	if specifiedPoint != "" {
		return "", fmt.Errorf("GlusterFS doesn't support specified mount point")
	}
	if volume.MountPoint == "" {
		volume.MountPoint = volume.Path
	}
	if err := util.ObjectSave(volume); err != nil {
		return "", err
	}
	return volume.MountPoint, nil
}

func (d *Driver) UmountVolume(id string) error {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	volume := d.blankVolume(id)
	if err := util.ObjectLoad(volume); err != nil {
		return err
	}

	if volume.MountPoint != "" {
		volume.MountPoint = ""
	}
	return util.ObjectSave(volume)
}

func (d *Driver) ListVolume(opts map[string]string) (map[string]map[string]string, error) {
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	volumeIDs, err := d.listVolumeIDs()
	if err != nil {
		return nil, err
	}
	result := map[string]map[string]string{}
	for _, id := range volumeIDs {
		result[id], err = d.GetVolumeInfo(id)
		if err != nil {
			return nil, err
		}
	}
	return result, nil
}

func (d *Driver) GetVolumeInfo(id string) (map[string]string, error) {
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	volume := d.blankVolume(id)
	if err := util.ObjectLoad(volume); err != nil {
		return nil, err
	}

	gVolume := d.gVolumes[volume.VolumePool]
	if gVolume == nil {
		return nil, fmt.Errorf("Cannot find volume pool %v", volume.VolumePool)
	}
	return map[string]string{
		"Name": volume.Name,
		"Path": volume.Path,
		convoydriver.OPT_MOUNT_POINT: volume.MountPoint,
		"GlusterFSVolume":            volume.VolumePool,
		"GlusterFSServerIPs":         fmt.Sprintf("%v", gVolume.ServerIPs),
	}, nil
}

func (d *Driver) MountPoint(id string) (string, error) {
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	volume := d.blankVolume(id)
	if err := util.ObjectLoad(volume); err != nil {
		return "", err
	}
	return volume.MountPoint, nil
}

func (d *Driver) SnapshotOps() (convoydriver.SnapshotOperations, error) {
	return nil, fmt.Errorf("Doesn't support snapshot operations")
}

func (d *Driver) BackupOps() (convoydriver.BackupOperations, error) {
	return nil, fmt.Errorf("Doesn't support backup operations")
}
<commit_msg>glusterfs: Fix a typo<commit_after>package glusterfs

import (
	"fmt"
	"github.com/Sirupsen/logrus"
	"github.com/rancher/convoy/convoydriver"
	"github.com/rancher/convoy/rancher"
	"github.com/rancher/convoy/util"
	"math/rand"
	"path/filepath"
	"strconv"
	"sync"
)

const (
	DRIVER_NAME        = "glusterfs"
	DRIVER_CONFIG_FILE = "glusterfs.cfg"

	VOLUME_CFG_PREFIX = "volume_"
	DRIVER_CFG_PREFIX = DRIVER_NAME + "_"
	CFG_POSTFIX       = ".json"

	SNAPSHOT_PATH = "snapshots"

	MOUNTS_DIR = "mounts"

	GLUSTERFS_RANCHER_STACK           = "glusterfs.rancherstack"
	GLUSTERFS_RANCHER_GLUSTER_SERVICE = "glusterfs.rancherservice"
	GLUSTERFS_DEFAULT_VOLUME_POOL     = "glusterfs.defaultvolumepool"
)

var (
	log = logrus.WithFields(logrus.Fields{"pkg": "glusterfs"})
)

type Driver struct {
	mutex    *sync.RWMutex
	gVolumes map[string]*GlusterFSVolume
	Device
}

func init() {
	convoydriver.Register(DRIVER_NAME, Init)
}

func (d *Driver) Name() string {
	return DRIVER_NAME
}

type Device struct {
	Root              string
	RancherURL        string
	RancherAccessKey  string
	RancherSecretKey  string
	RancherStack      string
	RancherService    string
	DefaultVolumePool string
}

func (dev *Device) ConfigFile() (string, error) {
	if dev.Root == "" {
		return "", fmt.Errorf("BUG: Invalid empty device config path")
	}
	return filepath.Join(dev.Root, DRIVER_CONFIG_FILE), nil
}

type Snapshot struct {
	UUID       string
	VolumeUUID string
}

type Volume struct {
	UUID       string
	Name       string
	Path       string
	MountPoint string
	VolumePool string

	configPath string
}

type GlusterFSVolume struct {
	UUID       string // volume name in fact
	MountPoint string
	ServerIPs  []string

	configPath string
}

func (gv *GlusterFSVolume) GetDevice() (string, error) {
	l := len(gv.ServerIPs)
	if gv.ServerIPs == nil || len(gv.ServerIPs) == 0 {
		return "", fmt.Errorf("No server IP provided for glusterfs")
	}
	ip := gv.ServerIPs[rand.Intn(l)]
	return ip + ":/" + gv.UUID, nil
}

func (gv *GlusterFSVolume) GetMountOpts() []string {
	return []string{"-t", "glusterfs"}
}

func (gv *GlusterFSVolume) GenerateDefaultMountPoint() string {
	return filepath.Join(gv.configPath, MOUNTS_DIR, gv.UUID)
}

func (v *Volume) ConfigFile() (string, error) {
	if v.UUID == "" {
		return "", fmt.Errorf("BUG: Invalid empty volume UUID")
	}
	if v.configPath == "" {
		return "", fmt.Errorf("BUG: Invalid empty volume config path")
	}
	return filepath.Join(v.configPath, DRIVER_CFG_PREFIX+VOLUME_CFG_PREFIX+v.UUID+CFG_POSTFIX), nil
}

func (device *Device) listVolumeIDs() ([]string, error) {
	return util.ListConfigIDs(device.Root, DRIVER_CFG_PREFIX+VOLUME_CFG_PREFIX, CFG_POSTFIX)
}

func Init(root string, config map[string]string) (convoydriver.ConvoyDriver, error) {
	dev := &Device{
		Root: root,
	}
	exists, err := util.ObjectExists(dev)
	if err != nil {
		return nil, err
	}
	if exists {
		if err := util.ObjectLoad(dev); err != nil {
			return nil, err
		}
	} else {
		if err := util.MkdirIfNotExists(root); err != nil {
			return nil, err
		}

		stack := config[GLUSTERFS_RANCHER_STACK]
		if stack == "" {
			return nil, fmt.Errorf("Missing required parameter: %v", GLUSTERFS_RANCHER_STACK)
		}
		service := config[GLUSTERFS_RANCHER_GLUSTER_SERVICE]
		if service == "" {
			return nil, fmt.Errorf("Missing required parameter: %v", GLUSTERFS_RANCHER_GLUSTER_SERVICE)
		}
		defaultVolumePool := config[GLUSTERFS_DEFAULT_VOLUME_POOL]
		if defaultVolumePool == "" {
			return nil, fmt.Errorf("Missing required parameter: %v", GLUSTERFS_DEFAULT_VOLUME_POOL)
		}

		dev = &Device{
			Root:              root,
			RancherStack:      stack,
			RancherService:    service,
			DefaultVolumePool: defaultVolumePool,
		}
	}

	serverIPs, err := rancher.GetIPsForServiceInStack(dev.RancherService, dev.RancherStack)
	if err != nil {
		return nil, err
	}

	d := &Driver{
		mutex:    &sync.RWMutex{},
		gVolumes: map[string]*GlusterFSVolume{},
		Device:   *dev,
	}
	gVolume := &GlusterFSVolume{
		UUID:       dev.DefaultVolumePool,
		ServerIPs:  serverIPs,
		configPath: d.Root,
	}
	// We would always mount the default volume pool
	// TODO: Also need to mount any existing volume's pool
	if _, err := util.VolumeMount(gVolume, "", true); err != nil {
		return nil, err
	}
	d.gVolumes[d.DefaultVolumePool] = gVolume

	if err := util.ObjectSave(dev); err != nil {
		return nil, err
	}
	return d, nil
}

func (d *Driver) Info() (map[string]string, error) {
	return map[string]string{
		"Root":              d.Root,
		"RancherStack":      d.RancherStack,
		"RancherService":    d.RancherService,
		"DefaultVolumePool": d.DefaultVolumePool,
	}, nil
}

func (d *Driver) VolumeOps() (convoydriver.VolumeOperations, error) {
	return d, nil
}

func (d *Driver) blankVolume(id string) *Volume {
	return &Volume{
		configPath: d.Root,
		UUID:       id,
	}
}

func (d *Driver) CreateVolume(id string, opts map[string]string) error {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	volumeName := opts[convoydriver.OPT_VOLUME_NAME]
	if volumeName == "" {
		volumeName = "volume-" + id[:8]
	}

	volume := d.blankVolume(id)
	exists, err := util.ObjectExists(volume)
	if err != nil {
		return err
	}
	if exists {
		return fmt.Errorf("volume %v already exists", id)
	}

	gVolume := d.gVolumes[d.DefaultVolumePool]
	volumePath := filepath.Join(gVolume.MountPoint, volumeName)
	if util.VolumeMountPointDirectoryExists(gVolume, volumeName) {
		log.Debugf("Found existing volume named %v, reuse it", volumeName)
	} else if err := util.VolumeMountPointDirectoryCreate(gVolume, volumeName); err != nil {
		return err
	}
	volume.Name = volumeName
	volume.Path = volumePath
	volume.VolumePool = gVolume.UUID

	return util.ObjectSave(volume)
}

func (d *Driver) DeleteVolume(id string, opts map[string]string) error {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	volume := d.blankVolume(id)
	if err := util.ObjectLoad(volume); err != nil {
		return err
	}

	if volume.MountPoint != "" {
		return fmt.Errorf("Cannot delete volume %v. It is still mounted", id)
	}
	referenceOnly, _ := strconv.ParseBool(opts[convoydriver.OPT_REFERENCE_ONLY])
	if !referenceOnly {
		log.Debugf("Cleaning up volume %v", id)
		gVolume := d.gVolumes[d.DefaultVolumePool]
		if err := util.VolumeMountPointDirectoryRemove(gVolume, volume.Name); err != nil {
			return err
		}
	}
	return util.ObjectDelete(volume)
}

func (d *Driver) MountVolume(id string, opts map[string]string) (string, error) {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	volume := d.blankVolume(id)
	if err := util.ObjectLoad(volume); err != nil {
		return "", err
	}

	specifiedPoint := opts[convoydriver.OPT_MOUNT_POINT]
	if specifiedPoint != "" {
		return "", fmt.Errorf("GlusterFS doesn't support specified mount point")
	}
	if volume.MountPoint == "" {
		volume.MountPoint = volume.Path
	}
	if err := util.ObjectSave(volume); err != nil {
		return "", err
	}
	return volume.MountPoint, nil
}

func (d *Driver) UmountVolume(id string) error {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	volume := d.blankVolume(id)
	if err := util.ObjectLoad(volume); err != nil {
		return err
	}

	if volume.MountPoint != "" {
		volume.MountPoint = ""
	}
	return util.ObjectSave(volume)
}

func (d *Driver) ListVolume(opts map[string]string) (map[string]map[string]string, error) {
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	volumeIDs, err := d.listVolumeIDs()
	if err != nil {
		return nil, err
	}
	result := map[string]map[string]string{}
	for _, id := range volumeIDs {
		result[id], err = d.GetVolumeInfo(id)
		if err != nil {
			return nil, err
		}
	}
	return result, nil
}

func (d *Driver) GetVolumeInfo(id string) (map[string]string, error) {
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	volume := d.blankVolume(id)
	if err := util.ObjectLoad(volume); err != nil {
		return nil, err
	}

	gVolume := d.gVolumes[volume.VolumePool]
	if gVolume == nil {
		return nil, fmt.Errorf("Cannot find volume pool %v", volume.VolumePool)
	}
	return map[string]string{
		"Name": volume.Name,
		"Path": volume.Path,
		convoydriver.OPT_MOUNT_POINT: volume.MountPoint,
		"GlusterFSVolume":            volume.VolumePool,
		"GlusterFSServerIPs":         fmt.Sprintf("%v", gVolume.ServerIPs),
	}, nil
}

func (d *Driver) MountPoint(id string) (string, error) {
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	volume := d.blankVolume(id)
	if err := util.ObjectLoad(volume); err != nil {
		return "", err
	}
	return volume.MountPoint, nil
}

func (d *Driver) SnapshotOps() (convoydriver.SnapshotOperations, error) {
	return nil, fmt.Errorf("Doesn't support snapshot operations")
}

func (d *Driver) BackupOps() (convoydriver.BackupOperations, error) {
	return nil, fmt.Errorf("Doesn't support backup operations")
}
<|endoftext|>