<commit_before>/*
Copyright 2018 The Rook Authors. All rights reserved.

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

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

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

package nfs

import (
	"fmt"

	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	cephconfig "github.com/rook/rook/pkg/daemon/ceph/config"
	"github.com/rook/rook/pkg/operator/ceph/config/keyring"
	opspec "github.com/rook/rook/pkg/operator/ceph/spec"
	"github.com/rook/rook/pkg/operator/k8sutil"
	apps "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
)

const (
	appName             = "rook-ceph-nfs"
	ganeshaConfigVolume = "ganesha-config"
	nfsPort             = 2049
)

func (c *CephNFSController) createCephNFSService(nfs cephv1.CephNFS, cfg daemonConfig) error {
	labels := getLabels(nfs, cfg.ID)
	svc := &v1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:            instanceName(nfs, cfg.ID),
			Namespace:       nfs.Namespace,
			Labels:          labels,
			OwnerReferences: ownerRefs(nfs),
		},
		Spec: v1.ServiceSpec{
			Selector: labels,
			Ports: []v1.ServicePort{
				{
					Name:       "nfs",
					Port:       nfsPort,
					TargetPort: intstr.FromInt(int(nfsPort)),
					Protocol:   v1.ProtocolTCP,
				},
			},
		},
	}
	if c.clusterSpec.Network.IsHost() {
		svc.Spec.ClusterIP = v1.ClusterIPNone
	}

	svc, err := c.context.Clientset.CoreV1().Services(nfs.Namespace).Create(svc)
	if err != nil {
		if !errors.IsAlreadyExists(err) {
			return fmt.Errorf("failed to create ganesha service. %+v", err)
		}
		logger.Infof("ceph nfs service already created")
		return nil
	}

	logger.Infof("ceph nfs service running at %s:%d", svc.Spec.ClusterIP, nfsPort)
	return nil
}

func (c *CephNFSController) makeDeployment(nfs cephv1.CephNFS, cfg daemonConfig) *apps.Deployment {
	deployment := &apps.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:            instanceName(nfs, cfg.ID),
			Namespace:       nfs.Namespace,
			Labels:          getLabels(nfs, cfg.ID),
			OwnerReferences: ownerRefs(nfs),
		},
	}
	k8sutil.AddRookVersionLabelToDeployment(deployment)
	opspec.AddCephVersionLabelToDeployment(c.clusterInfo.CephVersion, deployment)
	nfs.Spec.Server.Annotations.ApplyToObjectMeta(&deployment.ObjectMeta)

	cephConfigVol, _ := cephConfigVolumeAndMount()
	nfsConfigVol, _ := nfsConfigVolumeAndMount(cfg.ConfigConfigMap)
	dbusVol, _ := dbusVolumeAndMount()
	podSpec := v1.PodSpec{
		InitContainers: []v1.Container{
			c.connectionConfigInitContainer(nfs),
		},
		Containers: []v1.Container{
			c.daemonContainer(nfs, cfg),
			c.dbusContainer(nfs), // dbus sidecar
		},
		RestartPolicy: v1.RestartPolicyAlways,
		Volumes: []v1.Volume{
			// do not mount usual daemon volumes, as no data is stored for this daemon, and the ceph
			// config file is generated by the init container. we don't need to worry about missing
			// override configs, because nfs-ganesha is not a Ceph daemon; it wouldn't observe any
			// overrides anyway
			cephConfigVol,
			keyring.Volume().Admin(),
			nfsConfigVol,
			dbusVol,
		},
		HostNetwork:       c.clusterSpec.Network.IsHost(),
		PriorityClassName: nfs.Spec.Server.PriorityClassName,
	}
	if c.clusterSpec.Network.IsHost() {
		podSpec.DNSPolicy = v1.DNSClusterFirstWithHostNet
	}
	nfs.Spec.Server.Placement.ApplyToPodSpec(&podSpec)

	podTemplateSpec := v1.PodTemplateSpec{
		ObjectMeta: metav1.ObjectMeta{
			Name:   instanceName(nfs, cfg.ID),
			Labels: getLabels(nfs, cfg.ID),
		},
		Spec: podSpec,
	}
	nfs.Spec.Server.Annotations.ApplyToObjectMeta(&podTemplateSpec.ObjectMeta)

	// Multiple replicas of the nfs service would be handled by creating a service and a new deployment for each one, rather than increasing the pod count here
	replicas := int32(1)
	deployment.Spec = apps.DeploymentSpec{
		Selector: &metav1.LabelSelector{
			MatchLabels: getLabels(nfs, cfg.ID),
		},
		Template: podTemplateSpec,
		Replicas: &replicas,
	}
	return deployment
}

func (c *CephNFSController) connectionConfigInitContainer(nfs cephv1.CephNFS) v1.Container {
	_, cephConfigMount := cephConfigVolumeAndMount()

	return v1.Container{
		Name: opspec.ConfigInitContainerName,
		// command is already 'rook'
		Args: []string{
			"ceph", "config-init",
			"--username", "client.admin",
			"--keyring", keyring.VolumeMount().AdminKeyringFilePath(),
		},
		Image: c.rookImage,
		VolumeMounts: []v1.VolumeMount{
			cephConfigMount,
			keyring.VolumeMount().Admin(),
		},
		Env: append(
			opspec.DaemonEnvVars(c.clusterSpec.CephVersion.Image),
		),
		Resources: nfs.Spec.Server.Resources,
	}
}

func (c *CephNFSController) daemonContainer(nfs cephv1.CephNFS, cfg daemonConfig) v1.Container {
	_, cephConfigMount := cephConfigVolumeAndMount()
	_, nfsConfigMount := nfsConfigVolumeAndMount(cfg.ConfigConfigMap)
	_, dbusMount := dbusVolumeAndMount()

	return v1.Container{
		Name: "nfs-ganesha",
		Command: []string{
			"ganesha.nfsd",
		},
		Args: []string{
			"-F",           // foreground
			"-L", "STDERR", // log to stderr
		},
		Image: c.clusterSpec.CephVersion.Image,
		VolumeMounts: []v1.VolumeMount{
			cephConfigMount,
			keyring.VolumeMount().Admin(),
			nfsConfigMount,
			dbusMount,
		},
		Env: append(
			opspec.DaemonEnvVars(c.clusterSpec.CephVersion.Image),
		),
		Resources: nfs.Spec.Server.Resources,
	}
}

func (c *CephNFSController) dbusContainer(nfs cephv1.CephNFS) v1.Container {
	_, dbusMount := dbusVolumeAndMount()
	return v1.Container{
		Name: "dbus-daemon",
		Command: []string{
			"dbus-daemon",
		},
		Args: []string{
			"--nofork",    // run in foreground
			"--system",    // use system config file (uses /run/dbus/system_bus_socket)
			"--nopidfile", // don't write a pid file
			// some dbus-daemon versions have flag --nosyslog to send logs to sterr; not ceph upstream image
		},
		Image: c.clusterSpec.CephVersion.Image,
		VolumeMounts: []v1.VolumeMount{
			dbusMount,
		},
		Env: append(
			// do not need access to Ceph env vars b/c not a Ceph daemon
			k8sutil.ClusterDaemonEnvVars(c.clusterSpec.CephVersion.Image),
		),
		Resources: nfs.Spec.Server.Resources,
	}
}

func getLabels(n cephv1.CephNFS, name string) map[string]string {
	labels := opspec.AppLabels(appName, n.Namespace)
	labels["ceph_nfs"] = n.Name
	labels["instance"] = name
	return labels
}

func cephConfigVolumeAndMount() (v1.Volume, v1.VolumeMount) {
	// nfs ganesha produces its own ceph config file, so cannot use opspec.DaemonVolume or
	// opspec.DaemonVolumeMounts since that will bring in global ceph config file
	cfgDir := cephconfig.DefaultConfigDir
	volName := k8sutil.PathToVolumeName(cfgDir)
	v := v1.Volume{Name: volName, VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}}
	m := v1.VolumeMount{Name: volName, MountPath: cfgDir}
	return v, m
}

func nfsConfigVolumeAndMount(configConfigMap string) (v1.Volume, v1.VolumeMount) {
	cfgDir := "/etc/ganesha" // cfg file: /etc/ganesha/ganesha.conf
	cfgVolName := ganeshaConfigVolume
	configMapSource := &v1.ConfigMapVolumeSource{
		LocalObjectReference: v1.LocalObjectReference{Name: configConfigMap},
		Items:                []v1.KeyToPath{{Key: "config", Path: "ganesha.conf"}},
	}
	v := v1.Volume{Name: cfgVolName, VolumeSource: v1.VolumeSource{ConfigMap: configMapSource}}
	m := v1.VolumeMount{Name: cfgVolName, MountPath: cfgDir}
	return v, m
}

func dbusVolumeAndMount() (v1.Volume, v1.VolumeMount) {
	dbusSocketDir := "/run/dbus" // socket file: /run/dbus/system_bus_socket
	volName := k8sutil.PathToVolumeName(dbusSocketDir)
	v := v1.Volume{Name: volName, VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}}
	m := v1.VolumeMount{Name: volName, MountPath: dbusSocketDir}
	return v, m
}
<commit_msg>ceph: nfs add pod security for openshift<commit_after>/*
Copyright 2018 The Rook Authors. All rights reserved.

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

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

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

package nfs

import (
	"fmt"

	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	cephconfig "github.com/rook/rook/pkg/daemon/ceph/config"
	"github.com/rook/rook/pkg/operator/ceph/cluster/mon"
	"github.com/rook/rook/pkg/operator/ceph/config/keyring"
	opspec "github.com/rook/rook/pkg/operator/ceph/spec"
	"github.com/rook/rook/pkg/operator/k8sutil"
	apps "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
)

const (
	appName             = "rook-ceph-nfs"
	ganeshaConfigVolume = "ganesha-config"
	nfsPort             = 2049
)

func (c *CephNFSController) createCephNFSService(nfs cephv1.CephNFS, cfg daemonConfig) error {
	labels := getLabels(nfs, cfg.ID)
	svc := &v1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:            instanceName(nfs, cfg.ID),
			Namespace:       nfs.Namespace,
			Labels:          labels,
			OwnerReferences: ownerRefs(nfs),
		},
		Spec: v1.ServiceSpec{
			Selector: labels,
			Ports: []v1.ServicePort{
				{
					Name:       "nfs",
					Port:       nfsPort,
					TargetPort: intstr.FromInt(int(nfsPort)),
					Protocol:   v1.ProtocolTCP,
				},
			},
		},
	}
	if c.clusterSpec.Network.IsHost() {
		svc.Spec.ClusterIP = v1.ClusterIPNone
	}

	svc, err := c.context.Clientset.CoreV1().Services(nfs.Namespace).Create(svc)
	if err != nil {
		if !errors.IsAlreadyExists(err) {
			return fmt.Errorf("failed to create ganesha service. %+v", err)
		}
		logger.Infof("ceph nfs service already created")
		return nil
	}

	logger.Infof("ceph nfs service running at %s:%d", svc.Spec.ClusterIP, nfsPort)
	return nil
}

func (c *CephNFSController) makeDeployment(nfs cephv1.CephNFS, cfg daemonConfig) *apps.Deployment {
	deployment := &apps.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:            instanceName(nfs, cfg.ID),
			Namespace:       nfs.Namespace,
			Labels:          getLabels(nfs, cfg.ID),
			OwnerReferences: ownerRefs(nfs),
		},
	}
	k8sutil.AddRookVersionLabelToDeployment(deployment)
	opspec.AddCephVersionLabelToDeployment(c.clusterInfo.CephVersion, deployment)
	nfs.Spec.Server.Annotations.ApplyToObjectMeta(&deployment.ObjectMeta)

	cephConfigVol, _ := cephConfigVolumeAndMount()
	nfsConfigVol, _ := nfsConfigVolumeAndMount(cfg.ConfigConfigMap)
	dbusVol, _ := dbusVolumeAndMount()
	podSpec := v1.PodSpec{
		InitContainers: []v1.Container{
			c.connectionConfigInitContainer(nfs),
		},
		Containers: []v1.Container{
			c.daemonContainer(nfs, cfg),
			c.dbusContainer(nfs), // dbus sidecar
		},
		RestartPolicy: v1.RestartPolicyAlways,
		Volumes: []v1.Volume{
			// do not mount usual daemon volumes, as no data is stored for this daemon, and the ceph
			// config file is generated by the init container. we don't need to worry about missing
			// override configs, because nfs-ganesha is not a Ceph daemon; it wouldn't observe any
			// overrides anyway
			cephConfigVol,
			keyring.Volume().Admin(),
			nfsConfigVol,
			dbusVol,
		},
		HostNetwork:       c.clusterSpec.Network.IsHost(),
		PriorityClassName: nfs.Spec.Server.PriorityClassName,
	}
	if c.clusterSpec.Network.IsHost() {
		podSpec.DNSPolicy = v1.DNSClusterFirstWithHostNet
	}
	nfs.Spec.Server.Placement.ApplyToPodSpec(&podSpec)

	podTemplateSpec := v1.PodTemplateSpec{
		ObjectMeta: metav1.ObjectMeta{
			Name:   instanceName(nfs, cfg.ID),
			Labels: getLabels(nfs, cfg.ID),
		},
		Spec: podSpec,
	}
	nfs.Spec.Server.Annotations.ApplyToObjectMeta(&podTemplateSpec.ObjectMeta)

	// Multiple replicas of the nfs service would be handled by creating a service and a new deployment for each one, rather than increasing the pod count here
	replicas := int32(1)
	deployment.Spec = apps.DeploymentSpec{
		Selector: &metav1.LabelSelector{
			MatchLabels: getLabels(nfs, cfg.ID),
		},
		Template: podTemplateSpec,
		Replicas: &replicas,
	}
	return deployment
}

func (c *CephNFSController) connectionConfigInitContainer(nfs cephv1.CephNFS) v1.Container {
	_, cephConfigMount := cephConfigVolumeAndMount()

	return v1.Container{
		Name: opspec.ConfigInitContainerName,
		// command is already 'rook'
		Args: []string{
			"ceph", "config-init",
			"--username", "client.admin",
			"--keyring", keyring.VolumeMount().AdminKeyringFilePath(),
		},
		Image: c.rookImage,
		VolumeMounts: []v1.VolumeMount{
			cephConfigMount,
			keyring.VolumeMount().Admin(),
		},
		Env: append(
			opspec.DaemonEnvVars(c.clusterSpec.CephVersion.Image),
		),
		Resources: nfs.Spec.Server.Resources,
	}
}

func (c *CephNFSController) daemonContainer(nfs cephv1.CephNFS, cfg daemonConfig) v1.Container {
	_, cephConfigMount := cephConfigVolumeAndMount()
	_, nfsConfigMount := nfsConfigVolumeAndMount(cfg.ConfigConfigMap)
	_, dbusMount := dbusVolumeAndMount()

	return v1.Container{
		Name: "nfs-ganesha",
		Command: []string{
			"ganesha.nfsd",
		},
		Args: []string{
			"-F",           // foreground
			"-L", "STDERR", // log to stderr
		},
		Image: c.clusterSpec.CephVersion.Image,
		VolumeMounts: []v1.VolumeMount{
			cephConfigMount,
			keyring.VolumeMount().Admin(),
			nfsConfigMount,
			dbusMount,
		},
		Env: append(
			opspec.DaemonEnvVars(c.clusterSpec.CephVersion.Image),
		),
		Resources:       nfs.Spec.Server.Resources,
		SecurityContext: mon.PodSecurityContext(),
	}
}

func (c *CephNFSController) dbusContainer(nfs cephv1.CephNFS) v1.Container {
	_, dbusMount := dbusVolumeAndMount()
	return v1.Container{
		Name: "dbus-daemon",
		Command: []string{
			"dbus-daemon",
		},
		Args: []string{
			"--nofork",    // run in foreground
			"--system",    // use system config file (uses /run/dbus/system_bus_socket)
			"--nopidfile", // don't write a pid file
			// some dbus-daemon versions have flag --nosyslog to send logs to sterr; not ceph upstream image
		},
		Image: c.clusterSpec.CephVersion.Image,
		VolumeMounts: []v1.VolumeMount{
			dbusMount,
		},
		Env: append(
			// do not need access to Ceph env vars b/c not a Ceph daemon
			k8sutil.ClusterDaemonEnvVars(c.clusterSpec.CephVersion.Image),
		),
		Resources: nfs.Spec.Server.Resources,
	}
}

func getLabels(n cephv1.CephNFS, name string) map[string]string {
	labels := opspec.AppLabels(appName, n.Namespace)
	labels["ceph_nfs"] = n.Name
	labels["instance"] = name
	return labels
}

func cephConfigVolumeAndMount() (v1.Volume, v1.VolumeMount) {
	// nfs ganesha produces its own ceph config file, so cannot use opspec.DaemonVolume or
	// opspec.DaemonVolumeMounts since that will bring in global ceph config file
	cfgDir := cephconfig.DefaultConfigDir
	volName := k8sutil.PathToVolumeName(cfgDir)
	v := v1.Volume{Name: volName, VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}}
	m := v1.VolumeMount{Name: volName, MountPath: cfgDir}
	return v, m
}

func nfsConfigVolumeAndMount(configConfigMap string) (v1.Volume, v1.VolumeMount) {
	cfgDir := "/etc/ganesha" // cfg file: /etc/ganesha/ganesha.conf
	cfgVolName := ganeshaConfigVolume
	configMapSource := &v1.ConfigMapVolumeSource{
		LocalObjectReference: v1.LocalObjectReference{Name: configConfigMap},
		Items:                []v1.KeyToPath{{Key: "config", Path: "ganesha.conf"}},
	}
	v := v1.Volume{Name: cfgVolName, VolumeSource: v1.VolumeSource{ConfigMap: configMapSource}}
	m := v1.VolumeMount{Name: cfgVolName, MountPath: cfgDir}
	return v, m
}

func dbusVolumeAndMount() (v1.Volume, v1.VolumeMount) {
	dbusSocketDir := "/run/dbus" // socket file: /run/dbus/system_bus_socket
	volName := k8sutil.PathToVolumeName(dbusSocketDir)
	v := v1.Volume{Name: volName, VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}}
	m := v1.VolumeMount{Name: volName, MountPath: dbusSocketDir}
	return v, m
}
<|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 runner

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/signal"
	"syscall"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/sync/errgroup"
)

// SkaffoldRunner is responsible for running the skaffold build and deploy pipeline.
type SkaffoldRunner struct {
	build.Builder
	deploy.Deployer
	tag.Tagger
	watch.WatcherFactory
	build.DependencyMapFactory

	opts   *config.SkaffoldOptions
	config *config.SkaffoldConfig
	builds []build.Build
	out    io.Writer
}

var kubernetesClient = kubernetes.GetClientset

// NewForConfig returns a new SkaffoldRunner for a SkaffoldConfig
func NewForConfig(opts *config.SkaffoldOptions, cfg *config.SkaffoldConfig, out io.Writer) (*SkaffoldRunner, error) {
	kubeContext, err := kubernetes.CurrentContext()
	if err != nil {
		return nil, errors.Wrap(err, "getting current cluster context")
	}
	logrus.Infof("Using kubectl context: %s", kubeContext)

	builder, err := getBuilder(&cfg.Build, kubeContext)
	if err != nil {
		return nil, errors.Wrap(err, "parsing skaffold build config")
	}

	deployer, err := getDeployer(&cfg.Deploy, kubeContext)
	if err != nil {
		return nil, errors.Wrap(err, "parsing skaffold deploy config")
	}

	builder, deployer = WithTimings(builder, deployer)
	if opts.Notification {
		deployer = WithNotification(deployer)
	}

	tagger, err := getTagger(cfg.Build.TagPolicy, opts.CustomTag)
	if err != nil {
		return nil, errors.Wrap(err, "parsing skaffold tag config")
	}

	return &SkaffoldRunner{
		config:               cfg,
		Builder:              builder,
		Deployer:             deployer,
		Tagger:               tagger,
		opts:                 opts,
		WatcherFactory:       watch.NewWatcher,
		DependencyMapFactory: build.NewDependencyMap,
		out:                  out,
	}, nil
}

func getBuilder(cfg *v1alpha2.BuildConfig, kubeContext string) (build.Builder, error) {
	switch {
	case cfg.LocalBuild != nil:
		logrus.Debugf("Using builder: local")
		return build.NewLocalBuilder(cfg, kubeContext)

	case cfg.GoogleCloudBuild != nil:
		logrus.Debugf("Using builder: google cloud")
		return build.NewGoogleCloudBuilder(cfg)

	case cfg.KanikoBuild != nil:
		logrus.Debugf("Using builder: kaniko")
		return build.NewKanikoBuilder(cfg)

	default:
		return nil, fmt.Errorf("Unknown builder for config %+v", cfg)
	}
}

func getDeployer(cfg *v1alpha2.DeployConfig, kubeContext string) (deploy.Deployer, error) {
	switch {
	case cfg.KubectlDeploy != nil:
		return deploy.NewKubectlDeployer(cfg, kubeContext), nil

	case cfg.HelmDeploy != nil:
		return deploy.NewHelmDeployer(cfg, kubeContext), nil

	default:
		return nil, fmt.Errorf("Unknown deployer for config %+v", cfg)
	}
}

func getTagger(t v1alpha2.TagPolicy, customTag string) (tag.Tagger, error) {
	switch {
	case customTag != "":
		return &tag.CustomTag{
			Tag: customTag,
		}, nil

	case t.EnvTemplateTagger != nil:
		return tag.NewEnvTemplateTagger(t.EnvTemplateTagger.Template)

	case t.ShaTagger != nil:
		return &tag.ChecksumTagger{}, nil

	case t.GitTagger != nil:
		return &tag.GitCommit{}, nil

	default:
		return nil, fmt.Errorf("Unknown tagger for strategy %+v", t)
	}
}

// Build builds the artifacts.
func (r *SkaffoldRunner) Build(ctx context.Context) error {
	bRes, err := r.Builder.Build(ctx, r.out, r.Tagger, r.config.Build.Artifacts)
	if err != nil {
		return err
	}

	for _, build := range bRes {
		fmt.Fprintf(r.out, "%s -> %s\n", build.ImageName, build.Tag)
	}

	return nil
}

// Run runs the skaffold build and deploy pipeline.
func (r *SkaffoldRunner) Run(ctx context.Context) error {
	bRes, err := r.Builder.Build(ctx, r.out, r.Tagger, r.config.Build.Artifacts)
	if err != nil {
		return errors.Wrap(err, "build step")
	}

	if err := r.Deploy(ctx, r.out, bRes); err != nil {
		return errors.Wrap(err, "deploy step")
	}

	return nil
}

// Dev watches for changes and runs the skaffold build and deploy
// pipeline until interrrupted by the user.
func (r *SkaffoldRunner) Dev(ctx context.Context) error {
	if r.opts.Cleanup {
		return r.cleanUpOnCtrlC(ctx)
	}
	return r.watchBuildDeploy(ctx)
}

func (r *SkaffoldRunner) watchBuildDeploy(ctx context.Context) error {
	artifacts := r.config.Build.Artifacts

	depMap, err := r.DependencyMapFactory(artifacts)
	if err != nil {
		return errors.Wrap(err, "getting path to dependency map")
	}

	watcher, err := r.WatcherFactory(depMap.Paths())
	if err != nil {
		return errors.Wrap(err, "creating watcher")
	}

	deployDeps, err := r.Dependencies()
	if err != nil {
		return errors.Wrap(err, "getting deploy dependencies")
	}
	logrus.Infof("Deployer dependencies: %s", deployDeps)

	deployWatcher, err := r.WatcherFactory(deployDeps)
	if err != nil {
		return errors.Wrap(err, "creating deploy watcher")
	}

	podSelector := kubernetes.NewImageList()
	colorPicker := kubernetes.NewColorPicker(artifacts)
	logger := kubernetes.NewLogAggregator(r.out, podSelector, colorPicker)

	onChange := func(changedPaths []string) error {
		logger.Mute()
		defer logger.Unmute()

		changedArtifacts := depMap.ArtifactsForPaths(changedPaths)

		bRes, err := r.Builder.Build(ctx, r.out, r.Tagger, changedArtifacts)
		if err != nil {
			if r.builds == nil {
				return errors.Wrap(err, "exiting dev mode because the first build failed")
			}

			logrus.Warnln("Skipping Deploy due to build error:", err)
			return nil
		}

		// Update which images are logged.
		for _, build := range bRes {
			podSelector.AddImage(build.Tag)
		}

		// Make sure all artifacts are redeployed. Not only those that were just rebuilt.
		r.builds = mergeWithPreviousBuilds(bRes, r.builds)

		return r.Deploy(ctx, r.out, r.builds)
	}

	onDeployChange := func(changedPaths []string) error {
		logger.Mute()
		defer logger.Unmute()

		return r.Deploy(ctx, r.out, r.builds)
	}

	if err := onChange(depMap.Paths()); err != nil {
		return errors.Wrap(err, "first build")
	}

	// Start logs
	kubeclient, err := kubernetesClient()
	if err != nil {
		return errors.Wrap(err, "getting k8s client")
	}

	if err = logger.Start(ctx, kubeclient.CoreV1()); err != nil {
		return errors.Wrap(err, "starting logger")
	}

	// Watch files and rebuild
	g, watchCtx := errgroup.WithContext(ctx)
	g.Go(func() error {
		return watcher.Start(watchCtx, r.out, onChange)
	})
	g.Go(func() error {
		return deployWatcher.Start(watchCtx, ioutil.Discard, onDeployChange)
	})

	return g.Wait()
}

func (r *SkaffoldRunner) cleanUpOnCtrlC(ctx context.Context) error {
	ctx, cancel := context.WithCancel(ctx)

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt,
		syscall.SIGTERM,
		syscall.SIGINT,
		syscall.SIGPIPE,
	)

	go func() {
		<-signals
		cancel()
	}()

	errRun := r.watchBuildDeploy(ctx)
	if err := r.Cleanup(ctx, r.out); err != nil {
		logrus.Warnln("cleanup:", err)
	}
	return errRun
}

func mergeWithPreviousBuilds(builds, previous []build.Build) []build.Build {
	updatedBuilds := map[string]bool{}
	for _, build := range builds {
		updatedBuilds[build.ImageName] = true
	}

	var merged []build.Build
	merged = append(merged, builds...)

	for _, b := range previous {
		if !updatedBuilds[b.ImageName] {
			merged = append(merged, b)
		}
	}

	return merged
}
<commit_msg>Cleanup only if something was built<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 runner

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/signal"
	"syscall"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/sync/errgroup"
)

// SkaffoldRunner is responsible for running the skaffold build and deploy pipeline.
type SkaffoldRunner struct {
	build.Builder
	deploy.Deployer
	tag.Tagger
	watch.WatcherFactory
	build.DependencyMapFactory

	opts   *config.SkaffoldOptions
	config *config.SkaffoldConfig
	builds []build.Build
	out    io.Writer
}

var kubernetesClient = kubernetes.GetClientset

// NewForConfig returns a new SkaffoldRunner for a SkaffoldConfig
func NewForConfig(opts *config.SkaffoldOptions, cfg *config.SkaffoldConfig, out io.Writer) (*SkaffoldRunner, error) {
	kubeContext, err := kubernetes.CurrentContext()
	if err != nil {
		return nil, errors.Wrap(err, "getting current cluster context")
	}
	logrus.Infof("Using kubectl context: %s", kubeContext)

	builder, err := getBuilder(&cfg.Build, kubeContext)
	if err != nil {
		return nil, errors.Wrap(err, "parsing skaffold build config")
	}

	deployer, err := getDeployer(&cfg.Deploy, kubeContext)
	if err != nil {
		return nil, errors.Wrap(err, "parsing skaffold deploy config")
	}

	builder, deployer = WithTimings(builder, deployer)
	if opts.Notification {
		deployer = WithNotification(deployer)
	}

	tagger, err := getTagger(cfg.Build.TagPolicy, opts.CustomTag)
	if err != nil {
		return nil, errors.Wrap(err, "parsing skaffold tag config")
	}

	return &SkaffoldRunner{
		config:               cfg,
		Builder:              builder,
		Deployer:             deployer,
		Tagger:               tagger,
		opts:                 opts,
		WatcherFactory:       watch.NewWatcher,
		DependencyMapFactory: build.NewDependencyMap,
		out:                  out,
	}, nil
}

func getBuilder(cfg *v1alpha2.BuildConfig, kubeContext string) (build.Builder, error) {
	switch {
	case cfg.LocalBuild != nil:
		logrus.Debugf("Using builder: local")
		return build.NewLocalBuilder(cfg, kubeContext)

	case cfg.GoogleCloudBuild != nil:
		logrus.Debugf("Using builder: google cloud")
		return build.NewGoogleCloudBuilder(cfg)

	case cfg.KanikoBuild != nil:
		logrus.Debugf("Using builder: kaniko")
		return build.NewKanikoBuilder(cfg)

	default:
		return nil, fmt.Errorf("Unknown builder for config %+v", cfg)
	}
}

func getDeployer(cfg *v1alpha2.DeployConfig, kubeContext string) (deploy.Deployer, error) {
	switch {
	case cfg.KubectlDeploy != nil:
		return deploy.NewKubectlDeployer(cfg, kubeContext), nil

	case cfg.HelmDeploy != nil:
		return deploy.NewHelmDeployer(cfg, kubeContext), nil

	default:
		return nil, fmt.Errorf("Unknown deployer for config %+v", cfg)
	}
}

func getTagger(t v1alpha2.TagPolicy, customTag string) (tag.Tagger, error) {
	switch {
	case customTag != "":
		return &tag.CustomTag{
			Tag: customTag,
		}, nil

	case t.EnvTemplateTagger != nil:
		return tag.NewEnvTemplateTagger(t.EnvTemplateTagger.Template)

	case t.ShaTagger != nil:
		return &tag.ChecksumTagger{}, nil

	case t.GitTagger != nil:
		return &tag.GitCommit{}, nil

	default:
		return nil, fmt.Errorf("Unknown tagger for strategy %+v", t)
	}
}

// Build builds the artifacts.
func (r *SkaffoldRunner) Build(ctx context.Context) error {
	bRes, err := r.Builder.Build(ctx, r.out, r.Tagger, r.config.Build.Artifacts)
	if err != nil {
		return err
	}

	for _, build := range bRes {
		fmt.Fprintf(r.out, "%s -> %s\n", build.ImageName, build.Tag)
	}

	return nil
}

// Run runs the skaffold build and deploy pipeline.
func (r *SkaffoldRunner) Run(ctx context.Context) error {
	bRes, err := r.Builder.Build(ctx, r.out, r.Tagger, r.config.Build.Artifacts)
	if err != nil {
		return errors.Wrap(err, "build step")
	}

	if err := r.Deploy(ctx, r.out, bRes); err != nil {
		return errors.Wrap(err, "deploy step")
	}

	return nil
}

// Dev watches for changes and runs the skaffold build and deploy
// pipeline until interrrupted by the user.
func (r *SkaffoldRunner) Dev(ctx context.Context) error {
	if r.opts.Cleanup {
		return r.cleanUpOnCtrlC(ctx)
	}
	return r.watchBuildDeploy(ctx)
}

func (r *SkaffoldRunner) watchBuildDeploy(ctx context.Context) error {
	artifacts := r.config.Build.Artifacts

	depMap, err := r.DependencyMapFactory(artifacts)
	if err != nil {
		return errors.Wrap(err, "getting path to dependency map")
	}

	watcher, err := r.WatcherFactory(depMap.Paths())
	if err != nil {
		return errors.Wrap(err, "creating watcher")
	}

	deployDeps, err := r.Dependencies()
	if err != nil {
		return errors.Wrap(err, "getting deploy dependencies")
	}
	logrus.Infof("Deployer dependencies: %s", deployDeps)

	deployWatcher, err := r.WatcherFactory(deployDeps)
	if err != nil {
		return errors.Wrap(err, "creating deploy watcher")
	}

	podSelector := kubernetes.NewImageList()
	colorPicker := kubernetes.NewColorPicker(artifacts)
	logger := kubernetes.NewLogAggregator(r.out, podSelector, colorPicker)

	onChange := func(changedPaths []string) error {
		logger.Mute()
		defer logger.Unmute()

		changedArtifacts := depMap.ArtifactsForPaths(changedPaths)

		bRes, err := r.Builder.Build(ctx, r.out, r.Tagger, changedArtifacts)
		if err != nil {
			if r.builds == nil {
				return errors.Wrap(err, "exiting dev mode because the first build failed")
			}

			logrus.Warnln("Skipping Deploy due to build error:", err)
			return nil
		}

		// Update which images are logged.
		for _, build := range bRes {
			podSelector.AddImage(build.Tag)
		}

		// Make sure all artifacts are redeployed. Not only those that were just rebuilt.
		r.builds = mergeWithPreviousBuilds(bRes, r.builds)

		return r.Deploy(ctx, r.out, r.builds)
	}

	onDeployChange := func(changedPaths []string) error {
		logger.Mute()
		defer logger.Unmute()

		return r.Deploy(ctx, r.out, r.builds)
	}

	if err := onChange(depMap.Paths()); err != nil {
		return errors.Wrap(err, "first build")
	}

	// Start logs
	kubeclient, err := kubernetesClient()
	if err != nil {
		return errors.Wrap(err, "getting k8s client")
	}

	if err = logger.Start(ctx, kubeclient.CoreV1()); err != nil {
		return errors.Wrap(err, "starting logger")
	}

	// Watch files and rebuild
	g, watchCtx := errgroup.WithContext(ctx)
	g.Go(func() error {
		return watcher.Start(watchCtx, r.out, onChange)
	})
	g.Go(func() error {
		return deployWatcher.Start(watchCtx, ioutil.Discard, onDeployChange)
	})

	return g.Wait()
}

func (r *SkaffoldRunner) cleanUpOnCtrlC(ctx context.Context) error {
	ctx, cancel := context.WithCancel(ctx)

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt,
		syscall.SIGTERM,
		syscall.SIGINT,
		syscall.SIGPIPE,
	)

	go func() {
		<-signals
		cancel()
	}()

	errRun := r.watchBuildDeploy(ctx)
	// Cleanup only if something was built
	if r.builds != nil {
		if err := r.Cleanup(ctx, r.out); err != nil {
			logrus.Warnln("cleanup:", err)
		}
	}
	return errRun
}

func mergeWithPreviousBuilds(builds, previous []build.Build) []build.Build {
	updatedBuilds := map[string]bool{}
	for _, build := range builds {
		updatedBuilds[build.ImageName] = true
	}

	var merged []build.Build
	merged = append(merged, builds...)

	for _, b := range previous {
		if !updatedBuilds[b.ImageName] {
			merged = append(merged, b)
		}
	}

	return merged
}
<|endoftext|>


<commit_before>package upstream

import (
	"github.com/cheekybits/is"
	"github.com/mrosset/util/console"
	"github.com/mrosset/via/pkg"
	"testing"
)

func TestUpstream(t *testing.T) {
	var (
		is = is.New(t)
	)
	is.NoErr(Upstream())
}

func TestParseName(t *testing.T) {
	var (
		is    = is.New(t)
		names = map[string]string{
			"bash":               "bash-4.4.12.tar.gz",
			"Bash":               "Bash-4.4.12.tar.gz",
			"one-two":            "one-two-4.4.12.tar.gz",
			"one-two-three":      "one-two-three-4.4.12.tar.gz",
			"one-two-three-four": "one-two-three-four-4.4.12.tar.gz",
		}
	)

	for expect, v := range names {
		got := ParseName(v)
		is.Equal(expect, got)
	}
}

func TestParseVersion(t *testing.T) {
	var (
		is   = is.New(t)
		vers = map[string]string{
			"4.4.12":        "bash-4.4.12.tar.gz",
			"4.4":           "Bash-4.4.tar.gz",
			"4.444":         "Bash-4.444.tar.gz",
			"4.444.444":     "Bash-4.444.444.tar.gz",
			"4.444.444.555": "Bash-4.444.444.555.tar.gz",
		}
	)

	for expect, v := range vers {
		is.Equal(expect, ParseVersion(v))
	}

}

func TestEachPlanFile(t *testing.T) {
	var (
		is = is.New(t)
	)
	plans, err := via.GetPlans()
	is.NoErr(err)
	for _, p := range plans {
		if p.Url == "" {
			continue
		}
		// file := path.Base(p.Expand().Url)
		//console.Println(file, ParseName(file), ParseVersion(file))
	}
	console.Flush()
}
<commit_msg>tests: don't test each upstream plan<commit_after>package upstream

import (
	"github.com/cheekybits/is"
	"github.com/mrosset/util/console"
	"github.com/mrosset/via/pkg"
	"testing"
)

func TestUpstream(t *testing.T) {
	var (
		is = is.New(t)
	)
	is.NoErr(Upstream())
}

func TestParseName(t *testing.T) {
	var (
		is    = is.New(t)
		names = map[string]string{
			"bash":               "bash-4.4.12.tar.gz",
			"Bash":               "Bash-4.4.12.tar.gz",
			"one-two":            "one-two-4.4.12.tar.gz",
			"one-two-three":      "one-two-three-4.4.12.tar.gz",
			"one-two-three-four": "one-two-three-four-4.4.12.tar.gz",
		}
	)

	for expect, v := range names {
		got := ParseName(v)
		is.Equal(expect, got)
	}
}

func TestParseVersion(t *testing.T) {
	var (
		is   = is.New(t)
		vers = map[string]string{
			"4.4.12":        "bash-4.4.12.tar.gz",
			"4.4":           "Bash-4.4.tar.gz",
			"4.444":         "Bash-4.444.tar.gz",
			"4.444.444":     "Bash-4.444.444.tar.gz",
			"4.444.444.555": "Bash-4.444.444.555.tar.gz",
		}
	)

	for expect, v := range vers {
		is.Equal(expect, ParseVersion(v))
	}

}

func OTestEachPlanFile(t *testing.T) {
	var (
		is = is.New(t)
	)
	plans, err := via.GetPlans()
	is.NoErr(err)
	for _, p := range plans {
		if p.Url == "" {
			continue
		}
		// file := path.Base(p.Expand().Url)
		//console.Println(file, ParseName(file), ParseVersion(file))
	}
	console.Flush()
}
<|endoftext|>


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

import (
	"context"
	"fmt"
	"os"
	"path"
	"strings"
	"time"

	"github.com/golang/glog"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/kubernetes/pkg/util/mount"
	"k8s.io/kubernetes/pkg/volume"
	volumeutil "k8s.io/kubernetes/pkg/volume/util"
)

type cinderDiskAttacher struct {
	host           volume.VolumeHost
	cinderProvider BlockStorageProvider
}

var _ volume.Attacher = &cinderDiskAttacher{}

var _ volume.AttachableVolumePlugin = &cinderPlugin{}

const (
	probeVolumeInitDelay     = 1 * time.Second
	probeVolumeFactor        = 2.0
	operationFinishInitDelay = 1 * time.Second
	operationFinishFactor    = 1.1
	operationFinishSteps     = 10
	diskAttachInitDelay      = 1 * time.Second
	diskAttachFactor         = 1.2
	diskAttachSteps          = 15
	diskDetachInitDelay      = 1 * time.Second
	diskDetachFactor         = 1.2
	diskDetachSteps          = 13
)

func (plugin *cinderPlugin) NewAttacher() (volume.Attacher, error) {
	cinder, err := plugin.getCloudProvider()
	if err != nil {
		return nil, err
	}
	return &cinderDiskAttacher{
		host:           plugin.host,
		cinderProvider: cinder,
	}, nil
}

func (plugin *cinderPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
	mounter := plugin.host.GetMounter(plugin.GetPluginName())
	return mounter.GetMountRefs(deviceMountPath)
}

func (attacher *cinderDiskAttacher) waitOperationFinished(volumeID string) error {
	backoff := wait.Backoff{
		Duration: operationFinishInitDelay,
		Factor:   operationFinishFactor,
		Steps:    operationFinishSteps,
	}

	var volumeStatus string
	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		var pending bool
		var err error
		pending, volumeStatus, err = attacher.cinderProvider.OperationPending(volumeID)
		if err != nil {
			return false, err
		}
		return !pending, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q is %s, can't finish within the alloted time", volumeID, volumeStatus)
	}

	return err
}

func (attacher *cinderDiskAttacher) waitDiskAttached(instanceID, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskAttachInitDelay,
		Factor:   diskAttachFactor,
		Steps:    diskAttachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := attacher.cinderProvider.DiskIsAttached(instanceID, volumeID)
		if err != nil {
			return false, err
		}
		return attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to be attached within the alloted time", volumeID)
	}

	return err
}

func (attacher *cinderDiskAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) {
	volumeID, _, _, err := getVolumeInfo(spec)
	if err != nil {
		return "", err
	}

	instanceID, err := attacher.nodeInstanceID(nodeName)
	if err != nil {
		return "", err
	}

	if err := attacher.waitOperationFinished(volumeID); err != nil {
		return "", err
	}

	attached, err := attacher.cinderProvider.DiskIsAttached(instanceID, volumeID)
	if err != nil {
		// Log error and continue with attach
		glog.Warningf(
			"Error checking if volume (%q) is already attached to current instance (%q). Will continue and try attach anyway. err=%v",
			volumeID, instanceID, err)
	}

	if err == nil && attached {
		// Volume is already attached to instance.
		glog.Infof("Attach operation is successful. volume %q is already attached to instance %q.", volumeID, instanceID)
	} else {
		_, err = attacher.cinderProvider.AttachDisk(instanceID, volumeID)
		if err == nil {
			if err = attacher.waitDiskAttached(instanceID, volumeID); err != nil {
				glog.Errorf("Error waiting for volume %q to be attached from node %q: %v", volumeID, nodeName, err)
				return "", err
			}
			glog.Infof("Attach operation successful: volume %q attached to instance %q.", volumeID, instanceID)
		} else {
			glog.Infof("Attach volume %q to instance %q failed with: %v", volumeID, instanceID, err)
			return "", err
		}
	}

	devicePath, err := attacher.cinderProvider.GetAttachmentDiskPath(instanceID, volumeID)
	if err != nil {
		glog.Infof("Can not get device path of volume %q which be attached to instance %q, failed with: %v", volumeID, instanceID, err)
		return "", err
	}

	return devicePath, nil
}

func (attacher *cinderDiskAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
	volumesAttachedCheck := make(map[*volume.Spec]bool)
	volumeSpecMap := make(map[string]*volume.Spec)
	volumeIDList := []string{}
	for _, spec := range specs {
		volumeID, _, _, err := getVolumeInfo(spec)
		if err != nil {
			glog.Errorf("Error getting volume (%q) source : %v", spec.Name(), err)
			continue
		}

		volumeIDList = append(volumeIDList, volumeID)
		volumesAttachedCheck[spec] = true
		volumeSpecMap[volumeID] = spec
	}

	attachedResult, err := attacher.cinderProvider.DisksAreAttachedByName(nodeName, volumeIDList)
	if err != nil {
		// Log error and continue with attach
		glog.Errorf(
			"Error checking if Volumes (%v) are already attached to current node (%q). Will continue and try attach anyway. err=%v",
			volumeIDList, nodeName, err)
		return volumesAttachedCheck, err
	}

	for volumeID, attached := range attachedResult {
		if !attached {
			spec := volumeSpecMap[volumeID]
			volumesAttachedCheck[spec] = false
			glog.V(2).Infof("VolumesAreAttached: check volume %q (specName: %q) is no longer attached", volumeID, spec.Name())
		}
	}
	return volumesAttachedCheck, nil
}

func (attacher *cinderDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, timeout time.Duration) (string, error) {
	// NOTE: devicePath is is path as reported by Cinder, which may be incorrect and should not be used. See Issue #33128
	volumeID, _, _, err := getVolumeInfo(spec)
	if err != nil {
		return "", err
	}

	if devicePath == "" {
		return "", fmt.Errorf("WaitForAttach failed for Cinder disk %q: devicePath is empty", volumeID)
	}

	ticker := time.NewTicker(probeVolumeInitDelay)
	defer ticker.Stop()
	timer := time.NewTimer(timeout)
	defer timer.Stop()

	duration := probeVolumeInitDelay
	for {
		select {
		case <-ticker.C:
			glog.V(5).Infof("Checking Cinder disk %q is attached.", volumeID)
			probeAttachedVolume()
			if !attacher.cinderProvider.ShouldTrustDevicePath() {
				// Using the Cinder volume ID, find the real device path (See Issue #33128)
				devicePath, err = attacher.cinderProvider.GetDevicePath(volumeID)
				if err != nil {
					return "", err
				}
			}
			exists, err := volumeutil.PathExists(devicePath)
			if exists && err == nil {
				glog.Infof("Successfully found attached Cinder disk %q at %v.", volumeID, devicePath)
				return devicePath, nil
			}
			// Log an error, and continue checking periodically
			glog.Errorf("Error: could not find attached Cinder disk %q (path: %q): %v", volumeID, devicePath, err)
			// Using exponential backoff instead of linear
			ticker.Stop()
			duration = time.Duration(float64(duration) * probeVolumeFactor)
			ticker = time.NewTicker(duration)
		case <-timer.C:
			return "", fmt.Errorf("could not find attached Cinder disk %q. Timeout waiting for mount paths to be created", volumeID)
		}
	}
}

func (attacher *cinderDiskAttacher) GetDeviceMountPath(
	spec *volume.Spec) (string, error) {
	volumeID, _, _, err := getVolumeInfo(spec)
	if err != nil {
		return "", err
	}

	return makeGlobalPDName(attacher.host, volumeID), nil
}

// FIXME: this method can be further pruned.
func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
	mounter := attacher.host.GetMounter(cinderVolumePluginName)
	notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
	if err != nil {
		if os.IsNotExist(err) {
			if err := os.MkdirAll(deviceMountPath, 0750); err != nil {
				return err
			}
			notMnt = true
		} else {
			return err
		}
	}

	_, volumeFSType, readOnly, err := getVolumeInfo(spec)
	if err != nil {
		return err
	}

	options := []string{}
	if readOnly {
		options = append(options, "ro")
	}
	if notMnt {
		diskMounter := volumeutil.NewSafeFormatAndMountFromHost(cinderVolumePluginName, attacher.host)
		mountOptions := volumeutil.MountOptionFromSpec(spec, options...)
		err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeFSType, mountOptions)
		if err != nil {
			os.Remove(deviceMountPath)
			return err
		}
	}
	return nil
}

type cinderDiskDetacher struct {
	mounter        mount.Interface
	cinderProvider BlockStorageProvider
}

var _ volume.Detacher = &cinderDiskDetacher{}

func (plugin *cinderPlugin) NewDetacher() (volume.Detacher, error) {
	cinder, err := plugin.getCloudProvider()
	if err != nil {
		return nil, err
	}
	return &cinderDiskDetacher{
		mounter:        plugin.host.GetMounter(plugin.GetPluginName()),
		cinderProvider: cinder,
	}, nil
}

func (detacher *cinderDiskDetacher) waitOperationFinished(volumeID string) error {
	backoff := wait.Backoff{
		Duration: operationFinishInitDelay,
		Factor:   operationFinishFactor,
		Steps:    operationFinishSteps,
	}

	var volumeStatus string
	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		var pending bool
		var err error
		pending, volumeStatus, err = detacher.cinderProvider.OperationPending(volumeID)
		if err != nil {
			return false, err
		}
		return !pending, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q is %s, can't finish within the alloted time", volumeID, volumeStatus)
	}

	return err
}

func (detacher *cinderDiskDetacher) waitDiskDetached(instanceID, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskDetachInitDelay,
		Factor:   diskDetachFactor,
		Steps:    diskDetachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := detacher.cinderProvider.DiskIsAttached(instanceID, volumeID)
		if err != nil {
			return false, err
		}
		return !attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to detach within the alloted time", volumeID)
	}

	return err
}

func (detacher *cinderDiskDetacher) Detach(volumeName string, nodeName types.NodeName) error {
	volumeID := path.Base(volumeName)
	if err := detacher.waitOperationFinished(volumeID); err != nil {
		return err
	}
	attached, instanceID, err := detacher.cinderProvider.DiskIsAttachedByName(nodeName, volumeID)
	if err != nil {
		// Log error and continue with detach
		glog.Errorf(
			"Error checking if volume (%q) is already attached to current node (%q). Will continue and try detach anyway. err=%v",
			volumeID, nodeName, err)
	}

	if err == nil && !attached {
		// Volume is already detached from node.
		glog.Infof("detach operation was successful. volume %q is already detached from node %q.", volumeID, nodeName)
		return nil
	}

	if err = detacher.cinderProvider.DetachDisk(instanceID, volumeID); err != nil {
		glog.Errorf("Error detaching volume %q from node %q: %v", volumeID, nodeName, err)
		return err
	}
	if err = detacher.waitDiskDetached(instanceID, volumeID); err != nil {
		glog.Errorf("Error waiting for volume %q to detach from node %q: %v", volumeID, nodeName, err)
		return err
	}
	glog.Infof("detached volume %q from node %q", volumeID, nodeName)
	return nil
}

func (detacher *cinderDiskDetacher) UnmountDevice(deviceMountPath string) error {
	return volumeutil.UnmountPath(deviceMountPath, detacher.mounter)
}

func (attacher *cinderDiskAttacher) nodeInstanceID(nodeName types.NodeName) (string, error) {
	instances, res := attacher.cinderProvider.Instances()
	if !res {
		return "", fmt.Errorf("failed to list openstack instances")
	}
	instanceID, err := instances.InstanceID(context.TODO(), nodeName)
	if err != nil {
		return "", err
	}
	if ind := strings.LastIndex(instanceID, "/"); ind >= 0 {
		instanceID = instanceID[(ind + 1):]
	}
	return instanceID, nil
}
<commit_msg>fix typos issue<commit_after>/*
Copyright 2016 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 cinder

import (
	"context"
	"fmt"
	"os"
	"path"
	"strings"
	"time"

	"github.com/golang/glog"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/kubernetes/pkg/util/mount"
	"k8s.io/kubernetes/pkg/volume"
	volumeutil "k8s.io/kubernetes/pkg/volume/util"
)

type cinderDiskAttacher struct {
	host           volume.VolumeHost
	cinderProvider BlockStorageProvider
}

var _ volume.Attacher = &cinderDiskAttacher{}

var _ volume.AttachableVolumePlugin = &cinderPlugin{}

const (
	probeVolumeInitDelay     = 1 * time.Second
	probeVolumeFactor        = 2.0
	operationFinishInitDelay = 1 * time.Second
	operationFinishFactor    = 1.1
	operationFinishSteps     = 10
	diskAttachInitDelay      = 1 * time.Second
	diskAttachFactor         = 1.2
	diskAttachSteps          = 15
	diskDetachInitDelay      = 1 * time.Second
	diskDetachFactor         = 1.2
	diskDetachSteps          = 13
)

func (plugin *cinderPlugin) NewAttacher() (volume.Attacher, error) {
	cinder, err := plugin.getCloudProvider()
	if err != nil {
		return nil, err
	}
	return &cinderDiskAttacher{
		host:           plugin.host,
		cinderProvider: cinder,
	}, nil
}

func (plugin *cinderPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
	mounter := plugin.host.GetMounter(plugin.GetPluginName())
	return mounter.GetMountRefs(deviceMountPath)
}

func (attacher *cinderDiskAttacher) waitOperationFinished(volumeID string) error {
	backoff := wait.Backoff{
		Duration: operationFinishInitDelay,
		Factor:   operationFinishFactor,
		Steps:    operationFinishSteps,
	}

	var volumeStatus string
	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		var pending bool
		var err error
		pending, volumeStatus, err = attacher.cinderProvider.OperationPending(volumeID)
		if err != nil {
			return false, err
		}
		return !pending, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q is %s, can't finish within the alloted time", volumeID, volumeStatus)
	}

	return err
}

func (attacher *cinderDiskAttacher) waitDiskAttached(instanceID, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskAttachInitDelay,
		Factor:   diskAttachFactor,
		Steps:    diskAttachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := attacher.cinderProvider.DiskIsAttached(instanceID, volumeID)
		if err != nil {
			return false, err
		}
		return attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to be attached within the alloted time", volumeID)
	}

	return err
}

func (attacher *cinderDiskAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) {
	volumeID, _, _, err := getVolumeInfo(spec)
	if err != nil {
		return "", err
	}

	instanceID, err := attacher.nodeInstanceID(nodeName)
	if err != nil {
		return "", err
	}

	if err := attacher.waitOperationFinished(volumeID); err != nil {
		return "", err
	}

	attached, err := attacher.cinderProvider.DiskIsAttached(instanceID, volumeID)
	if err != nil {
		// Log error and continue with attach
		glog.Warningf(
			"Error checking if volume (%q) is already attached to current instance (%q). Will continue and try attach anyway. err=%v",
			volumeID, instanceID, err)
	}

	if err == nil && attached {
		// Volume is already attached to instance.
		glog.Infof("Attach operation is successful. volume %q is already attached to instance %q.", volumeID, instanceID)
	} else {
		_, err = attacher.cinderProvider.AttachDisk(instanceID, volumeID)
		if err == nil {
			if err = attacher.waitDiskAttached(instanceID, volumeID); err != nil {
				glog.Errorf("Error waiting for volume %q to be attached from node %q: %v", volumeID, nodeName, err)
				return "", err
			}
			glog.Infof("Attach operation successful: volume %q attached to instance %q.", volumeID, instanceID)
		} else {
			glog.Infof("Attach volume %q to instance %q failed with: %v", volumeID, instanceID, err)
			return "", err
		}
	}

	devicePath, err := attacher.cinderProvider.GetAttachmentDiskPath(instanceID, volumeID)
	if err != nil {
		glog.Infof("Can not get device path of volume %q which be attached to instance %q, failed with: %v", volumeID, instanceID, err)
		return "", err
	}

	return devicePath, nil
}

func (attacher *cinderDiskAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
	volumesAttachedCheck := make(map[*volume.Spec]bool)
	volumeSpecMap := make(map[string]*volume.Spec)
	volumeIDList := []string{}
	for _, spec := range specs {
		volumeID, _, _, err := getVolumeInfo(spec)
		if err != nil {
			glog.Errorf("Error getting volume (%q) source : %v", spec.Name(), err)
			continue
		}

		volumeIDList = append(volumeIDList, volumeID)
		volumesAttachedCheck[spec] = true
		volumeSpecMap[volumeID] = spec
	}

	attachedResult, err := attacher.cinderProvider.DisksAreAttachedByName(nodeName, volumeIDList)
	if err != nil {
		// Log error and continue with attach
		glog.Errorf(
			"Error checking if Volumes (%v) are already attached to current node (%q). Will continue and try attach anyway. err=%v",
			volumeIDList, nodeName, err)
		return volumesAttachedCheck, err
	}

	for volumeID, attached := range attachedResult {
		if !attached {
			spec := volumeSpecMap[volumeID]
			volumesAttachedCheck[spec] = false
			glog.V(2).Infof("VolumesAreAttached: check volume %q (specName: %q) is no longer attached", volumeID, spec.Name())
		}
	}
	return volumesAttachedCheck, nil
}

func (attacher *cinderDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, timeout time.Duration) (string, error) {
	// NOTE: devicePath is path as reported by Cinder, which may be incorrect and should not be used. See Issue #33128
	volumeID, _, _, err := getVolumeInfo(spec)
	if err != nil {
		return "", err
	}

	if devicePath == "" {
		return "", fmt.Errorf("WaitForAttach failed for Cinder disk %q: devicePath is empty", volumeID)
	}

	ticker := time.NewTicker(probeVolumeInitDelay)
	defer ticker.Stop()
	timer := time.NewTimer(timeout)
	defer timer.Stop()

	duration := probeVolumeInitDelay
	for {
		select {
		case <-ticker.C:
			glog.V(5).Infof("Checking Cinder disk %q is attached.", volumeID)
			probeAttachedVolume()
			if !attacher.cinderProvider.ShouldTrustDevicePath() {
				// Using the Cinder volume ID, find the real device path (See Issue #33128)
				devicePath, err = attacher.cinderProvider.GetDevicePath(volumeID)
				if err != nil {
					return "", err
				}
			}
			exists, err := volumeutil.PathExists(devicePath)
			if exists && err == nil {
				glog.Infof("Successfully found attached Cinder disk %q at %v.", volumeID, devicePath)
				return devicePath, nil
			}
			// Log an error, and continue checking periodically
			glog.Errorf("Error: could not find attached Cinder disk %q (path: %q): %v", volumeID, devicePath, err)
			// Using exponential backoff instead of linear
			ticker.Stop()
			duration = time.Duration(float64(duration) * probeVolumeFactor)
			ticker = time.NewTicker(duration)
		case <-timer.C:
			return "", fmt.Errorf("could not find attached Cinder disk %q. Timeout waiting for mount paths to be created", volumeID)
		}
	}
}

func (attacher *cinderDiskAttacher) GetDeviceMountPath(
	spec *volume.Spec) (string, error) {
	volumeID, _, _, err := getVolumeInfo(spec)
	if err != nil {
		return "", err
	}

	return makeGlobalPDName(attacher.host, volumeID), nil
}

// FIXME: this method can be further pruned.
func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
	mounter := attacher.host.GetMounter(cinderVolumePluginName)
	notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
	if err != nil {
		if os.IsNotExist(err) {
			if err := os.MkdirAll(deviceMountPath, 0750); err != nil {
				return err
			}
			notMnt = true
		} else {
			return err
		}
	}

	_, volumeFSType, readOnly, err := getVolumeInfo(spec)
	if err != nil {
		return err
	}

	options := []string{}
	if readOnly {
		options = append(options, "ro")
	}
	if notMnt {
		diskMounter := volumeutil.NewSafeFormatAndMountFromHost(cinderVolumePluginName, attacher.host)
		mountOptions := volumeutil.MountOptionFromSpec(spec, options...)
		err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeFSType, mountOptions)
		if err != nil {
			os.Remove(deviceMountPath)
			return err
		}
	}
	return nil
}

type cinderDiskDetacher struct {
	mounter        mount.Interface
	cinderProvider BlockStorageProvider
}

var _ volume.Detacher = &cinderDiskDetacher{}

func (plugin *cinderPlugin) NewDetacher() (volume.Detacher, error) {
	cinder, err := plugin.getCloudProvider()
	if err != nil {
		return nil, err
	}
	return &cinderDiskDetacher{
		mounter:        plugin.host.GetMounter(plugin.GetPluginName()),
		cinderProvider: cinder,
	}, nil
}

func (detacher *cinderDiskDetacher) waitOperationFinished(volumeID string) error {
	backoff := wait.Backoff{
		Duration: operationFinishInitDelay,
		Factor:   operationFinishFactor,
		Steps:    operationFinishSteps,
	}

	var volumeStatus string
	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		var pending bool
		var err error
		pending, volumeStatus, err = detacher.cinderProvider.OperationPending(volumeID)
		if err != nil {
			return false, err
		}
		return !pending, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q is %s, can't finish within the alloted time", volumeID, volumeStatus)
	}

	return err
}

func (detacher *cinderDiskDetacher) waitDiskDetached(instanceID, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskDetachInitDelay,
		Factor:   diskDetachFactor,
		Steps:    diskDetachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := detacher.cinderProvider.DiskIsAttached(instanceID, volumeID)
		if err != nil {
			return false, err
		}
		return !attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to detach within the alloted time", volumeID)
	}

	return err
}

func (detacher *cinderDiskDetacher) Detach(volumeName string, nodeName types.NodeName) error {
	volumeID := path.Base(volumeName)
	if err := detacher.waitOperationFinished(volumeID); err != nil {
		return err
	}
	attached, instanceID, err := detacher.cinderProvider.DiskIsAttachedByName(nodeName, volumeID)
	if err != nil {
		// Log error and continue with detach
		glog.Errorf(
			"Error checking if volume (%q) is already attached to current node (%q). Will continue and try detach anyway. err=%v",
			volumeID, nodeName, err)
	}

	if err == nil && !attached {
		// Volume is already detached from node.
		glog.Infof("detach operation was successful. volume %q is already detached from node %q.", volumeID, nodeName)
		return nil
	}

	if err = detacher.cinderProvider.DetachDisk(instanceID, volumeID); err != nil {
		glog.Errorf("Error detaching volume %q from node %q: %v", volumeID, nodeName, err)
		return err
	}
	if err = detacher.waitDiskDetached(instanceID, volumeID); err != nil {
		glog.Errorf("Error waiting for volume %q to detach from node %q: %v", volumeID, nodeName, err)
		return err
	}
	glog.Infof("detached volume %q from node %q", volumeID, nodeName)
	return nil
}

func (detacher *cinderDiskDetacher) UnmountDevice(deviceMountPath string) error {
	return volumeutil.UnmountPath(deviceMountPath, detacher.mounter)
}

func (attacher *cinderDiskAttacher) nodeInstanceID(nodeName types.NodeName) (string, error) {
	instances, res := attacher.cinderProvider.Instances()
	if !res {
		return "", fmt.Errorf("failed to list openstack instances")
	}
	instanceID, err := instances.InstanceID(context.TODO(), nodeName)
	if err != nil {
		return "", err
	}
	if ind := strings.LastIndex(instanceID, "/"); ind >= 0 {
		instanceID = instanceID[(ind + 1):]
	}
	return instanceID, nil
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 probes

import (
	"fmt"
	"io"
	"runtime"
	"sync"
	"sync/atomic"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"github.com/skydive-project/skydive/api"
	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/flow"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/topology/graph"
	"github.com/vishvananda/netns"
)

type packetHandle interface {
	Close()
}

type GoPacketProbe struct {
	handle       packetHandle
	packetSource *gopacket.PacketSource
	NodeUUID     string
	flowTable    *flow.Table
	state        int64
}

type GoPacketProbesHandler struct {
	graph      *graph.Graph
	wg         sync.WaitGroup
	probes     map[string]*GoPacketProbe
	probesLock sync.RWMutex
}

const (
	snaplen int32 = 256
)

func (p *GoPacketProbe) SetProbeNode(flow *flow.Flow) bool {
	flow.NodeUUID = p.NodeUUID
	return true
}

func (p *GoPacketProbe) packetsToChan(ch chan gopacket.Packet) {
	defer close(ch)

	for atomic.LoadInt64(&p.state) == common.RunningState {
		packet, err := p.packetSource.NextPacket()
		if err == io.EOF {
			time.Sleep(20 * time.Millisecond)
		} else if err == nil {
			ch <- packet
		} else {
			// sleep awhile in case of error to reduce the presure on cpu
			time.Sleep(100 * time.Millisecond)
		}
	}

	p.handle.Close()
}

func (p *GoPacketProbe) start() {
	atomic.StoreInt64(&p.state, common.RunningState)

	ch := make(chan gopacket.Packet, 1000)
	go p.packetsToChan(ch)

	timer := time.NewTicker(100 * time.Millisecond)
	feedFlowTable := func() {
		select {
		case packet, ok := <-ch:
			if ok {
				flow.FlowFromGoPacket(p.flowTable, &packet, 0, p)
			}
		case <-timer.C:
		}
	}
	p.flowTable.RegisterDefault(feedFlowTable)
	p.flowTable.Start()
}

func (p *GoPacketProbe) stop() {
	if atomic.CompareAndSwapInt64(&p.state, common.RunningState, common.StoppingState) {
		p.flowTable.Stop()
	}
}

func (p *GoPacketProbesHandler) RegisterProbe(n *graph.Node, capture *api.Capture, ft *flow.Table) error {
	name, ok := n.Metadata()["Name"]
	if !ok || name == "" {
		return fmt.Errorf("No name for node %v", n)
	}

	id := string(n.ID)
	ifName := name.(string)

	if _, ok := p.probes[id]; ok {
		return fmt.Errorf("Already registered %s", ifName)
	}

	nodes := p.graph.LookupShortestPath(n, graph.Metadata{"Type": "host"}, graph.Metadata{"RelationType": "ownership"})
	if len(nodes) == 0 {
		return fmt.Errorf("Failed to determine probePath for %s", ifName)
	}

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	origns, err := netns.Get()
	if err != nil {
		return fmt.Errorf("Error while getting current ns: %s", err.Error())
	}
	defer origns.Close()

	for _, node := range nodes {
		if node.Metadata()["Type"] == "netns" {
			name := node.Metadata()["Name"].(string)
			path := node.Metadata()["Path"].(string)
			logging.GetLogger().Debugf("Switching to namespace %s (path: %s)", name, path)

			newns, err := netns.GetFromPath(path)
			if err != nil {
				return fmt.Errorf("Error while opening ns %s (path: %s): %s", name, path, err.Error())
			}
			defer newns.Close()

			if err := netns.Set(newns); err != nil {
				return fmt.Errorf("Error while switching from root ns to %s (path: %s): %s", name, path, err.Error())
			}
			defer netns.Set(origns)
		}
	}

	probe := &GoPacketProbe{
		NodeUUID:  id,
		state:     common.StoppedState,
		flowTable: ft,
	}

	switch capture.Type {
	case "pcap":
		handle, err := pcap.OpenLive(ifName, snaplen, true, time.Second)
		if err != nil {
			return fmt.Errorf("Error while opening device %s: %s", ifName, err.Error())
		}

		if err := handle.SetBPFFilter(capture.BPFFilter); err != nil {
			return fmt.Errorf("BPF Filter failed: %s", err)
		}

		probe.handle = handle
		probe.packetSource = gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)

		logging.GetLogger().Infof("PCAP Capture type %s started on %s", capture.Type, n.Metadata()["Name"])
	default:
		var handle *AFPacketHandle
		fnc := func() error {
			handle, err = NewAFPacketHandle(ifName, snaplen)
			if err != nil {
				return fmt.Errorf("Error while opening device %s: %s", ifName, err.Error())
			}
			return nil
		}

		if err = common.Retry(fnc, 2, 100*time.Millisecond); err != nil {
			return err
		}

		probe.handle = handle
		probe.packetSource = gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)

		logging.GetLogger().Infof("AfPacket Capture started on %s", n.Metadata()["Name"])
	}

	p.probesLock.Lock()
	p.probes[id] = probe
	p.probesLock.Unlock()
	p.wg.Add(1)

	go func() {
		defer p.wg.Done()

		probe.start()
	}()

	return nil
}

func (p *GoPacketProbesHandler) unregisterProbe(id string) error {
	if probe, ok := p.probes[id]; ok {
		logging.GetLogger().Debugf("Terminating gopacket capture on %s", id)
		probe.stop()
		delete(p.probes, id)
	}

	return nil
}

func (p *GoPacketProbesHandler) UnregisterProbe(n *graph.Node) error {
	p.probesLock.Lock()
	defer p.probesLock.Unlock()

	err := p.unregisterProbe(string(n.ID))
	if err != nil {
		return err
	}

	return nil
}

func (p *GoPacketProbesHandler) Start() {
}

func (p *GoPacketProbesHandler) Stop() {
	p.probesLock.Lock()
	defer p.probesLock.Unlock()

	for id := range p.probes {
		p.unregisterProbe(id)
	}
	p.wg.Wait()
}

func NewGoPacketProbesHandler(g *graph.Graph) *GoPacketProbesHandler {
	handler := &GoPacketProbesHandler{
		graph:  g,
		probes: make(map[string]*GoPacketProbe),
	}
	return handler
}
<commit_msg>[probes] detect first layer for gopacket<commit_after>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 probes

import (
	"fmt"
	"io"
	"runtime"
	"sync"
	"sync/atomic"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"github.com/skydive-project/skydive/api"
	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/flow"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/topology/graph"
	"github.com/vishvananda/netns"
)

type packetHandle interface {
	Close()
}

type GoPacketProbe struct {
	handle       packetHandle
	packetSource *gopacket.PacketSource
	NodeUUID     string
	flowTable    *flow.Table
	state        int64
}

type GoPacketProbesHandler struct {
	graph      *graph.Graph
	wg         sync.WaitGroup
	probes     map[string]*GoPacketProbe
	probesLock sync.RWMutex
}

const (
	snaplen int32 = 256
)

func (p *GoPacketProbe) SetProbeNode(flow *flow.Flow) bool {
	flow.NodeUUID = p.NodeUUID
	return true
}

func (p *GoPacketProbe) packetsToChan(ch chan gopacket.Packet) {
	defer close(ch)

	for atomic.LoadInt64(&p.state) == common.RunningState {
		packet, err := p.packetSource.NextPacket()
		if err == io.EOF {
			time.Sleep(20 * time.Millisecond)
		} else if err == nil {
			ch <- packet
		} else {
			// sleep awhile in case of error to reduce the presure on cpu
			time.Sleep(100 * time.Millisecond)
		}
	}

	p.handle.Close()
}

func (p *GoPacketProbe) start() {
	atomic.StoreInt64(&p.state, common.RunningState)

	ch := make(chan gopacket.Packet, 1000)
	go p.packetsToChan(ch)

	timer := time.NewTicker(100 * time.Millisecond)
	feedFlowTable := func() {
		select {
		case packet, ok := <-ch:
			if ok {
				flow.FlowFromGoPacket(p.flowTable, &packet, 0, p)
			}
		case <-timer.C:
		}
	}
	p.flowTable.RegisterDefault(feedFlowTable)
	p.flowTable.Start()
}

func (p *GoPacketProbe) stop() {
	if atomic.CompareAndSwapInt64(&p.state, common.RunningState, common.StoppingState) {
		p.flowTable.Stop()
	}
}

func getGoPacketFirstLayerType(n *graph.Node) gopacket.LayerType {
	switch n.Metadata()["Type"] {
	case "bridge", "bond", "can", "dummy", "hsr", "ifb", "macvlan", "macvtap",
		"veth", "vlan", "vxlan", "gretap", "ip6gretap", "geneve":
		return layers.LayerTypeEthernet
	case "ipoib", "vcan", "ipip", "ipvlan", "lowpan":
		return layers.LayerTypeIPv4
	case "ip6tnl", "ip6gre", "sit":
		return layers.LayerTypeIPv6
	}
	return layers.LayerTypeEthernet
}

func (p *GoPacketProbesHandler) RegisterProbe(n *graph.Node, capture *api.Capture, ft *flow.Table) error {
	name, ok := n.Metadata()["Name"]
	if !ok || name == "" {
		return fmt.Errorf("No name for node %v", n)
	}

	id := string(n.ID)
	ifName := name.(string)

	if _, ok := p.probes[id]; ok {
		return fmt.Errorf("Already registered %s", ifName)
	}

	nodes := p.graph.LookupShortestPath(n, graph.Metadata{"Type": "host"}, graph.Metadata{"RelationType": "ownership"})
	if len(nodes) == 0 {
		return fmt.Errorf("Failed to determine probePath for %s", ifName)
	}

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	origns, err := netns.Get()
	if err != nil {
		return fmt.Errorf("Error while getting current ns: %s", err.Error())
	}
	defer origns.Close()

	for _, node := range nodes {
		if node.Metadata()["Type"] == "netns" {
			name := node.Metadata()["Name"].(string)
			path := node.Metadata()["Path"].(string)
			logging.GetLogger().Debugf("Switching to namespace %s (path: %s)", name, path)

			newns, err := netns.GetFromPath(path)
			if err != nil {
				return fmt.Errorf("Error while opening ns %s (path: %s): %s", name, path, err.Error())
			}
			defer newns.Close()

			if err := netns.Set(newns); err != nil {
				return fmt.Errorf("Error while switching from root ns to %s (path: %s): %s", name, path, err.Error())
			}
			defer netns.Set(origns)
		}
	}

	probe := &GoPacketProbe{
		NodeUUID:  id,
		state:     common.StoppedState,
		flowTable: ft,
	}

	switch capture.Type {
	case "pcap":
		handle, err := pcap.OpenLive(ifName, snaplen, true, time.Second)
		if err != nil {
			return fmt.Errorf("Error while opening device %s: %s", ifName, err.Error())
		}

		if err := handle.SetBPFFilter(capture.BPFFilter); err != nil {
			return fmt.Errorf("BPF Filter failed: %s", err)
		}

		probe.handle = handle
		probe.packetSource = gopacket.NewPacketSource(handle, getGoPacketFirstLayerType(n))

		logging.GetLogger().Infof("PCAP Capture type %s started on %s", capture.Type, n.Metadata()["Name"])
	default:
		var handle *AFPacketHandle
		fnc := func() error {
			handle, err = NewAFPacketHandle(ifName, snaplen)
			if err != nil {
				return fmt.Errorf("Error while opening device %s: %s", ifName, err.Error())
			}
			return nil
		}

		if err = common.Retry(fnc, 2, 100*time.Millisecond); err != nil {
			return err
		}

		probe.handle = handle
		probe.packetSource = gopacket.NewPacketSource(handle, getGoPacketFirstLayerType(n))

		logging.GetLogger().Infof("AfPacket Capture started on %s", n.Metadata()["Name"])
	}

	p.probesLock.Lock()
	p.probes[id] = probe
	p.probesLock.Unlock()
	p.wg.Add(1)

	go func() {
		defer p.wg.Done()

		probe.start()
	}()

	return nil
}

func (p *GoPacketProbesHandler) unregisterProbe(id string) error {
	if probe, ok := p.probes[id]; ok {
		logging.GetLogger().Debugf("Terminating gopacket capture on %s", id)
		probe.stop()
		delete(p.probes, id)
	}

	return nil
}

func (p *GoPacketProbesHandler) UnregisterProbe(n *graph.Node) error {
	p.probesLock.Lock()
	defer p.probesLock.Unlock()

	err := p.unregisterProbe(string(n.ID))
	if err != nil {
		return err
	}

	return nil
}

func (p *GoPacketProbesHandler) Start() {
}

func (p *GoPacketProbesHandler) Stop() {
	p.probesLock.Lock()
	defer p.probesLock.Unlock()

	for id := range p.probes {
		p.unregisterProbe(id)
	}
	p.wg.Wait()
}

func NewGoPacketProbesHandler(g *graph.Graph) *GoPacketProbesHandler {
	handler := &GoPacketProbesHandler{
		graph:  g,
		probes: make(map[string]*GoPacketProbe),
	}
	return handler
}
<|endoftext|>


<commit_before>package container // import "github.com/docker/docker/integration/container"

import (
	"context"
	"testing"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/integration/internal/container"
	"github.com/docker/docker/integration/internal/request"
	"github.com/docker/docker/internal/testutil"
	"github.com/docker/docker/pkg/stringid"
	"github.com/gotestyourself/gotestyourself/assert"
	is "github.com/gotestyourself/gotestyourself/assert/cmp"
	"github.com/gotestyourself/gotestyourself/poll"
	"github.com/gotestyourself/gotestyourself/skip"
)

// This test simulates the scenario mentioned in #31392:
// Having two linked container, renaming the target and bringing a replacement
// and then deleting and recreating the source container linked to the new target.
// This checks that "rename" updates source container correctly and doesn't set it to null.
func TestRenameLinkedContainer(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	aID := container.Run(t, ctx, client, container.WithName("a0"))
	bID := container.Run(t, ctx, client, container.WithName("b0"), container.WithLinks("a0"))

	err := client.ContainerRename(ctx, aID, "a1")
	assert.NilError(t, err)

	container.Run(t, ctx, client, container.WithName("a0"))

	err = client.ContainerRemove(ctx, bID, types.ContainerRemoveOptions{Force: true})
	assert.NilError(t, err)

	bID = container.Run(t, ctx, client, container.WithName("b0"), container.WithLinks("a0"))

	inspect, err := client.ContainerInspect(ctx, bID)
	assert.NilError(t, err)
	assert.Check(t, is.DeepEqual([]string{"/a0:/b0/a0"}, inspect.HostConfig.Links))
}

func TestRenameStoppedContainer(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	oldName := "first_name"
	cID := container.Run(t, ctx, client, container.WithName(oldName), container.WithCmd("sh"))
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))

	inspect, err := client.ContainerInspect(ctx, cID)
	assert.NilError(t, err)
	assert.Check(t, is.Equal("/"+oldName, inspect.Name))

	newName := "new_name" + stringid.GenerateNonCryptoID()
	err = client.ContainerRename(ctx, oldName, newName)
	assert.NilError(t, err)

	inspect, err = client.ContainerInspect(ctx, cID)
	assert.NilError(t, err)
	assert.Check(t, is.Equal("/"+newName, inspect.Name))
}

func TestRenameRunningContainerAndReuse(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	oldName := "first_name"
	cID := container.Run(t, ctx, client, container.WithName(oldName))
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))

	newName := "new_name" + stringid.GenerateNonCryptoID()
	err := client.ContainerRename(ctx, oldName, newName)
	assert.NilError(t, err)

	inspect, err := client.ContainerInspect(ctx, cID)
	assert.NilError(t, err)
	assert.Check(t, is.Equal("/"+newName, inspect.Name))

	_, err = client.ContainerInspect(ctx, oldName)
	testutil.ErrorContains(t, err, "No such container: "+oldName)

	cID = container.Run(t, ctx, client, container.WithName(oldName))
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))

	inspect, err = client.ContainerInspect(ctx, cID)
	assert.NilError(t, err)
	assert.Check(t, is.Equal("/"+oldName, inspect.Name))
}

func TestRenameInvalidName(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	oldName := "first_name"
	cID := container.Run(t, ctx, client, container.WithName(oldName))
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))

	err := client.ContainerRename(ctx, oldName, "new:invalid")
	testutil.ErrorContains(t, err, "Invalid container name")

	inspect, err := client.ContainerInspect(ctx, oldName)
	assert.NilError(t, err)
	assert.Check(t, is.Equal(cID, inspect.ID))
}

// Test case for GitHub issue 22466
// Docker's service discovery works for named containers so
// ping to a named container should work, and an anonymous
// container without a name does not work with service discovery.
// However, an anonymous could be renamed to a named container.
// This test is to make sure once the container has been renamed,
// the service discovery for the (re)named container works.
func TestRenameAnonymousContainer(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	_, err := client.NetworkCreate(ctx, "network1", types.NetworkCreate{})
	assert.NilError(t, err)
	cID := container.Run(t, ctx, client, func(c *container.TestContainerConfig) {
		c.NetworkingConfig.EndpointsConfig = map[string]*network.EndpointSettings{
			"network1": {},
		}
		c.HostConfig.NetworkMode = "network1"
	})
	err = client.ContainerRename(ctx, cID, "container1")
	assert.NilError(t, err)
	// Stop/Start the container to get registered
	// FIXME(vdemeester) this is a really weird behavior as it fails otherwise
	err = client.ContainerStop(ctx, "container1", nil)
	assert.NilError(t, err)
	err = client.ContainerStart(ctx, "container1", types.ContainerStartOptions{})
	assert.NilError(t, err)

	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))

	count := "-c"
	if testEnv.OSType == "windows" {
		count = "-n"
	}
	cID = container.Run(t, ctx, client, func(c *container.TestContainerConfig) {
		c.NetworkingConfig.EndpointsConfig = map[string]*network.EndpointSettings{
			"network1": {},
		}
		c.HostConfig.NetworkMode = "network1"
	}, container.WithCmd("ping", count, "1", "container1"))
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))

	inspect, err := client.ContainerInspect(ctx, cID)
	assert.NilError(t, err)
	assert.Check(t, is.Equal(0, inspect.State.ExitCode), "container %s exited with the wrong exitcode: %+v", cID, inspect)
}

// TODO: should be a unit test
func TestRenameContainerWithSameName(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	cID := container.Run(t, ctx, client, container.WithName("old"))
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
	err := client.ContainerRename(ctx, "old", "old")
	testutil.ErrorContains(t, err, "Renaming a container with the same name")
	err = client.ContainerRename(ctx, cID, "old")
	testutil.ErrorContains(t, err, "Renaming a container with the same name")
}

// Test case for GitHub issue 23973
// When a container is being renamed, the container might
// be linked to another container. In that case, the meta data
// of the linked container should be updated so that the other
// container could still reference to the container that is renamed.
func TestRenameContainerWithLinkedContainer(t *testing.T) {
	skip.If(t, testEnv.IsRemoteDaemon())

	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	db1ID := container.Run(t, ctx, client, container.WithName("db1"))
	poll.WaitOn(t, container.IsInState(ctx, client, db1ID, "running"), poll.WithDelay(100*time.Millisecond))

	app1ID := container.Run(t, ctx, client, container.WithName("app1"), container.WithLinks("db1:/mysql"))
	poll.WaitOn(t, container.IsInState(ctx, client, app1ID, "running"), poll.WithDelay(100*time.Millisecond))

	err := client.ContainerRename(ctx, "app1", "app2")
	assert.NilError(t, err)

	inspect, err := client.ContainerInspect(ctx, "app2/mysql")
	assert.NilError(t, err)
	assert.Check(t, is.Equal(db1ID, inspect.ID))
}
<commit_msg>Use unique names for container/rename_test.go<commit_after>package container // import "github.com/docker/docker/integration/container"

import (
	"context"
	"testing"
	"time"

	"github.com/docker/docker/api/types"
	containertypes "github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/integration/internal/container"
	"github.com/docker/docker/integration/internal/request"
	"github.com/docker/docker/internal/testutil"
	"github.com/docker/docker/pkg/stringid"
	"github.com/gotestyourself/gotestyourself/assert"
	is "github.com/gotestyourself/gotestyourself/assert/cmp"
	"github.com/gotestyourself/gotestyourself/poll"
	"github.com/gotestyourself/gotestyourself/skip"
)

// This test simulates the scenario mentioned in #31392:
// Having two linked container, renaming the target and bringing a replacement
// and then deleting and recreating the source container linked to the new target.
// This checks that "rename" updates source container correctly and doesn't set it to null.
func TestRenameLinkedContainer(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	aName := "a0" + t.Name()
	bName := "b0" + t.Name()
	aID := container.Run(t, ctx, client, container.WithName(aName))
	bID := container.Run(t, ctx, client, container.WithName(bName), container.WithLinks(aName))

	err := client.ContainerRename(ctx, aID, "a1"+t.Name())
	assert.NilError(t, err)

	container.Run(t, ctx, client, container.WithName(aName))

	err = client.ContainerRemove(ctx, bID, types.ContainerRemoveOptions{Force: true})
	assert.NilError(t, err)

	bID = container.Run(t, ctx, client, container.WithName(bName), container.WithLinks(aName))

	inspect, err := client.ContainerInspect(ctx, bID)
	assert.NilError(t, err)
	assert.Check(t, is.DeepEqual([]string{"/" + aName + ":/" + bName + "/" + aName}, inspect.HostConfig.Links))
}

func TestRenameStoppedContainer(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	oldName := "first_name" + t.Name()
	cID := container.Run(t, ctx, client, container.WithName(oldName), container.WithCmd("sh"))
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))

	inspect, err := client.ContainerInspect(ctx, cID)
	assert.NilError(t, err)
	assert.Check(t, is.Equal("/"+oldName, inspect.Name))

	newName := "new_name" + stringid.GenerateNonCryptoID()
	err = client.ContainerRename(ctx, oldName, newName)
	assert.NilError(t, err)

	inspect, err = client.ContainerInspect(ctx, cID)
	assert.NilError(t, err)
	assert.Check(t, is.Equal("/"+newName, inspect.Name))
}

func TestRenameRunningContainerAndReuse(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	oldName := "first_name" + t.Name()
	cID := container.Run(t, ctx, client, container.WithName(oldName))
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))

	newName := "new_name" + stringid.GenerateNonCryptoID()
	err := client.ContainerRename(ctx, oldName, newName)
	assert.NilError(t, err)

	inspect, err := client.ContainerInspect(ctx, cID)
	assert.NilError(t, err)
	assert.Check(t, is.Equal("/"+newName, inspect.Name))

	_, err = client.ContainerInspect(ctx, oldName)
	testutil.ErrorContains(t, err, "No such container: "+oldName)

	cID = container.Run(t, ctx, client, container.WithName(oldName))
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))

	inspect, err = client.ContainerInspect(ctx, cID)
	assert.NilError(t, err)
	assert.Check(t, is.Equal("/"+oldName, inspect.Name))
}

func TestRenameInvalidName(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	oldName := "first_name" + t.Name()
	cID := container.Run(t, ctx, client, container.WithName(oldName))
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))

	err := client.ContainerRename(ctx, oldName, "new:invalid")
	testutil.ErrorContains(t, err, "Invalid container name")

	inspect, err := client.ContainerInspect(ctx, oldName)
	assert.NilError(t, err)
	assert.Check(t, is.Equal(cID, inspect.ID))
}

// Test case for GitHub issue 22466
// Docker's service discovery works for named containers so
// ping to a named container should work, and an anonymous
// container without a name does not work with service discovery.
// However, an anonymous could be renamed to a named container.
// This test is to make sure once the container has been renamed,
// the service discovery for the (re)named container works.
func TestRenameAnonymousContainer(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	networkName := "network1" + t.Name()
	_, err := client.NetworkCreate(ctx, networkName, types.NetworkCreate{})

	assert.NilError(t, err)
	cID := container.Run(t, ctx, client, func(c *container.TestContainerConfig) {
		c.NetworkingConfig.EndpointsConfig = map[string]*network.EndpointSettings{
			networkName: {},
		}
		c.HostConfig.NetworkMode = containertypes.NetworkMode(networkName)
	})

	container1Name := "container1" + t.Name()
	err = client.ContainerRename(ctx, cID, container1Name)
	assert.NilError(t, err)
	// Stop/Start the container to get registered
	// FIXME(vdemeester) this is a really weird behavior as it fails otherwise
	err = client.ContainerStop(ctx, container1Name, nil)
	assert.NilError(t, err)
	err = client.ContainerStart(ctx, container1Name, types.ContainerStartOptions{})
	assert.NilError(t, err)

	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))

	count := "-c"
	if testEnv.OSType == "windows" {
		count = "-n"
	}
	cID = container.Run(t, ctx, client, func(c *container.TestContainerConfig) {
		c.NetworkingConfig.EndpointsConfig = map[string]*network.EndpointSettings{
			networkName: {},
		}
		c.HostConfig.NetworkMode = containertypes.NetworkMode(networkName)
	}, container.WithCmd("ping", count, "1", container1Name))
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))

	inspect, err := client.ContainerInspect(ctx, cID)
	assert.NilError(t, err)
	assert.Check(t, is.Equal(0, inspect.State.ExitCode), "container %s exited with the wrong exitcode: %+v", cID, inspect)
}

// TODO: should be a unit test
func TestRenameContainerWithSameName(t *testing.T) {
	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	oldName := "old" + t.Name()
	cID := container.Run(t, ctx, client, container.WithName(oldName))

	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
	err := client.ContainerRename(ctx, oldName, oldName)
	testutil.ErrorContains(t, err, "Renaming a container with the same name")
	err = client.ContainerRename(ctx, cID, oldName)
	testutil.ErrorContains(t, err, "Renaming a container with the same name")
}

// Test case for GitHub issue 23973
// When a container is being renamed, the container might
// be linked to another container. In that case, the meta data
// of the linked container should be updated so that the other
// container could still reference to the container that is renamed.
func TestRenameContainerWithLinkedContainer(t *testing.T) {
	skip.If(t, testEnv.IsRemoteDaemon())

	defer setupTest(t)()
	ctx := context.Background()
	client := request.NewAPIClient(t)

	db1Name := "db1" + t.Name()
	db1ID := container.Run(t, ctx, client, container.WithName(db1Name))
	poll.WaitOn(t, container.IsInState(ctx, client, db1ID, "running"), poll.WithDelay(100*time.Millisecond))

	app1Name := "app1" + t.Name()
	app2Name := "app2" + t.Name()
	app1ID := container.Run(t, ctx, client, container.WithName(app1Name), container.WithLinks(db1Name+":/mysql"))
	poll.WaitOn(t, container.IsInState(ctx, client, app1ID, "running"), poll.WithDelay(100*time.Millisecond))

	err := client.ContainerRename(ctx, app1Name, app2Name)
	assert.NilError(t, err)

	inspect, err := client.ContainerInspect(ctx, app2Name+"/mysql")
	assert.NilError(t, err)
	assert.Check(t, is.Equal(db1ID, inspect.ID))
}
<|endoftext|>