diff options
Diffstat (limited to 'pkg')
24 files changed, 479 insertions, 1172 deletions
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index a24dbaa47..4ce31cc83 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -14,8 +14,10 @@ import ( "github.com/containers/podman/v2/pkg/api/handlers" "github.com/containers/podman/v2/pkg/api/handlers/utils" "github.com/containers/podman/v2/pkg/namespaces" + "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/podman/v2/pkg/signal" createconfig "github.com/containers/podman/v2/pkg/spec" + "github.com/containers/podman/v2/pkg/specgen" "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -134,6 +136,11 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input Sysctl: input.HostConfig.Sysctls, } + var netmode namespaces.NetworkMode + if rootless.IsRootless() { + netmode = namespaces.NetworkMode(specgen.Slirp) + } + network := createconfig.NetworkConfig{ DNSOpt: input.HostConfig.DNSOptions, DNSSearch: input.HostConfig.DNSSearch, @@ -144,7 +151,7 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input IPAddress: "", LinkLocalIP: nil, // docker-only MacAddress: input.MacAddress, - // NetMode: nil, + NetMode: netmode, Network: input.HostConfig.NetworkMode.NetworkName(), NetworkAlias: nil, // docker-only now PortBindings: input.HostConfig.PortBindings, diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 9d8bc497a..f49ce59da 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -55,6 +55,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) return } + defer os.Remove(tmpfile.Name()) if err := tmpfile.Close(); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) return @@ -69,7 +70,6 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { return } defer rdr.Close() - defer os.Remove(tmpfile.Name()) utils.WriteResponse(w, http.StatusOK, rdr) } @@ -398,3 +398,43 @@ func LoadImages(w http.ResponseWriter, r *http.Request) { Stream: fmt.Sprintf("Loaded image: %s\n", id), }) } + +func ExportImages(w http.ResponseWriter, r *http.Request) { + // 200 OK + // 500 Error + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + Names string `schema:"names"` + }{ + // This is where you can override the golang default value for one of fields + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + return + } + images := make([]string, 0) + images = append(images, strings.Split(query.Names, ",")...) + tmpfile, err := ioutil.TempFile("", "api.tar") + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + return + } + defer os.Remove(tmpfile.Name()) + if err := tmpfile.Close(); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + return + } + if err := runtime.ImageRuntime().SaveImages(r.Context(), images, "docker-archive", tmpfile.Name(), false); err != nil { + utils.InternalServerError(w, err) + return + } + rdr, err := os.Open(tmpfile.Name()) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) + return + } + defer rdr.Close() + utils.WriteResponse(w, http.StatusOK, rdr) +} diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index c5387b1e9..a46784a6c 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -12,10 +12,10 @@ import ( "github.com/containernetworking/cni/libcni" "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" + "github.com/containers/podman/v2/libpod/network" "github.com/containers/podman/v2/pkg/api/handlers/utils" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/infra/abi" - "github.com/containers/podman/v2/pkg/network" "github.com/docker/docker/api/types" dockerNetwork "github.com/docker/docker/api/types/network" "github.com/gorilla/schema" @@ -210,6 +210,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { report, err := getNetworkResourceByName(name, runtime) if err != nil { utils.InternalServerError(w, err) + return } reports = append(reports, report) } @@ -267,9 +268,9 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { } } ce := abi.ContainerEngine{Libpod: runtime} - _, err := ce.NetworkCreate(r.Context(), name, ncOptions) - if err != nil { + if _, err := ce.NetworkCreate(r.Context(), name, ncOptions); err != nil { utils.InternalServerError(w, err) + return } report := types.NetworkCreate{ CheckDuplicate: networkCreate.CheckDuplicate, @@ -307,6 +308,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) { } if err := network.RemoveNetwork(config, name); err != nil { utils.InternalServerError(w, err) + return } utils.WriteResponse(w, http.StatusNoContent, "") } diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 3aa554171..5422411cf 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -270,7 +270,7 @@ func PodPause(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - responses, err := pod.Pause() + responses, err := pod.Pause(r.Context()) if err != nil && errors.Cause(err) != define.ErrPodPartialFail { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return @@ -294,7 +294,7 @@ func PodUnpause(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - responses, err := pod.Unpause() + responses, err := pod.Unpause(r.Context()) if err != nil && errors.Cause(err) != define.ErrPodPartialFail { utils.Error(w, "failed to pause pod", http.StatusInternalServerError, err) return @@ -402,7 +402,7 @@ func PodKill(w http.ResponseWriter, r *http.Request) { return } - responses, err := pod.Kill(uint(sig)) + responses, err := pod.Kill(r.Context(), uint(sig)) if err != nil && errors.Cause(err) != define.ErrPodPartialFail { utils.Error(w, "failed to kill pod", http.StatusInternalServerError, err) return diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index cb0d26d1e..ad779203d 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -275,6 +275,31 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { r.Handle(VersionedPath("/images/{name:.*}/get"), s.APIHandler(compat.ExportImage)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths r.Handle("/images/{name:.*}/get", s.APIHandler(compat.ExportImage)).Methods(http.MethodGet) + // swagger:operation GET /images/get compat get + // --- + // tags: + // - images (compat) + // summary: Export several images + // description: Get a tarball containing all images and metadata for several image repositories + // parameters: + // - in: query + // name: names + // type: string + // required: true + // description: one or more image names or IDs comma separated + // produces: + // - application/json + // responses: + // 200: + // description: no error + // schema: + // type: string + // format: binary + // 500: + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/images/get"), s.APIHandler(compat.ExportImages)).Methods(http.MethodGet) + // Added non version path to URI to support docker non versioned paths + r.Handle("/images/get", s.APIHandler(compat.ExportImages)).Methods(http.MethodGet) // swagger:operation GET /images/{name:.*}/history compat imageHistory // --- // tags: diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go index 2ba369b83..356e6869d 100644 --- a/pkg/domain/entities/play.go +++ b/pkg/domain/entities/play.go @@ -24,6 +24,8 @@ type PlayKubeOptions struct { // SeccompProfileRoot - path to a directory containing seccomp // profiles. SeccompProfileRoot string + // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. + ConfigMaps []string } // PlayKubePod represents a single pod and associated containers created by play kube diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index d92911e0c..0107e18c4 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -23,7 +23,7 @@ import ( "github.com/containers/podman/v2/pkg/checkpoint" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/infra/abi/terminal" - "github.com/containers/podman/v2/pkg/parallel" + parallelctr "github.com/containers/podman/v2/pkg/parallel/ctr" "github.com/containers/podman/v2/pkg/ps" "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/podman/v2/pkg/signal" @@ -157,7 +157,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) { return nil, err } - errMap, err := parallel.ContainerOp(ctx, ctrs, func(c *libpod.Container) error { + errMap, err := parallelctr.ContainerOp(ctx, ctrs, func(c *libpod.Container) error { var err error if options.Timeout != nil { err = c.StopWithTimeout(*options.Timeout) @@ -321,7 +321,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, return reports, nil } - errMap, err := parallel.ContainerOp(ctx, ctrs, func(c *libpod.Container) error { + errMap, err := parallelctr.ContainerOp(ctx, ctrs, func(c *libpod.Container) error { err := ic.Libpod.RemoveContainer(ctx, c, options.Force, options.Volumes) if err != nil { if options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr { diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index 5acfea853..f40df828a 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -2,19 +2,13 @@ package abi import ( "context" - "encoding/json" "fmt" - "io/ioutil" - "os" - "path/filepath" "strings" "github.com/containernetworking/cni/libcni" - cniversion "github.com/containernetworking/cni/pkg/version" - "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" + "github.com/containers/podman/v2/libpod/network" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/containers/podman/v2/pkg/network" "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" ) @@ -111,173 +105,7 @@ func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, o } func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, options entities.NetworkCreateOptions) (*entities.NetworkCreateReport, error) { - var ( - err error - fileName string - ) - if len(options.MacVLAN) > 0 { - fileName, err = createMacVLAN(ic.Libpod, name, options) - } else { - fileName, err = createBridge(ic.Libpod, name, options) - } - if err != nil { - return nil, err - } - return &entities.NetworkCreateReport{Filename: fileName}, nil -} - -// createBridge creates a CNI network -func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) { - isGateway := true - ipMasq := true - subnet := &options.Subnet - ipRange := options.Range - runtimeConfig, err := r.GetConfig() - if err != nil { - return "", err - } - // if range is provided, make sure it is "in" network - if subnet.IP != nil { - // if network is provided, does it conflict with existing CNI or live networks - err = network.ValidateUserNetworkIsAvailable(runtimeConfig, subnet) - } else { - // if no network is provided, figure out network - subnet, err = network.GetFreeNetwork(runtimeConfig) - } - if err != nil { - return "", err - } - gateway := options.Gateway - if gateway == nil { - // if no gateway is provided, provide it as first ip of network - gateway = network.CalcGatewayIP(subnet) - } - // if network is provided and if gateway is provided, make sure it is "in" network - if options.Subnet.IP != nil && options.Gateway != nil { - if !subnet.Contains(gateway) { - return "", errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String()) - } - } - if options.Internal { - isGateway = false - ipMasq = false - } - - // if a range is given, we need to ensure it is "in" the network range. - if options.Range.IP != nil { - if options.Subnet.IP == nil { - return "", errors.New("you must define a subnet range to define an ip-range") - } - firstIP, err := network.FirstIPInSubnet(&options.Range) - if err != nil { - return "", err - } - lastIP, err := network.LastIPInSubnet(&options.Range) - if err != nil { - return "", err - } - if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) { - return "", errors.Errorf("the ip range %s does not fall within the subnet range %s", options.Range.String(), subnet.String()) - } - } - bridgeDeviceName, err := network.GetFreeDeviceName(runtimeConfig) - if err != nil { - return "", err - } - - if len(name) > 0 { - netNames, err := network.GetNetworkNamesFromFileSystem(runtimeConfig) - if err != nil { - return "", err - } - if util.StringInSlice(name, netNames) { - return "", errors.Errorf("the network name %s is already used", name) - } - } else { - // If no name is given, we give the name of the bridge device - name = bridgeDeviceName - } - - ncList := network.NewNcList(name, cniversion.Current()) - var plugins []network.CNIPlugins - var routes []network.IPAMRoute - - defaultRoute, err := network.NewIPAMDefaultRoute(network.IsIPv6(subnet.IP)) - if err != nil { - return "", err - } - routes = append(routes, defaultRoute) - ipamConfig, err := network.NewIPAMHostLocalConf(subnet, routes, ipRange, gateway) - if err != nil { - return "", err - } - - // TODO need to iron out the role of isDefaultGW and IPMasq - bridge := network.NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig) - plugins = append(plugins, bridge) - plugins = append(plugins, network.NewPortMapPlugin()) - plugins = append(plugins, network.NewFirewallPlugin()) - // if we find the dnsname plugin, we add configuration for it - if network.HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) && !options.DisableDNS { - // Note: in the future we might like to allow for dynamic domain names - plugins = append(plugins, network.NewDNSNamePlugin(network.DefaultPodmanDomainName)) - } - ncList["plugins"] = plugins - b, err := json.MarshalIndent(ncList, "", " ") - if err != nil { - return "", err - } - if err := os.MkdirAll(network.GetCNIConfDir(runtimeConfig), 0755); err != nil { - return "", err - } - cniPathName := filepath.Join(network.GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name)) - err = ioutil.WriteFile(cniPathName, b, 0644) - return cniPathName, err -} - -func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) { - var ( - plugins []network.CNIPlugins - ) - liveNetNames, err := network.GetLiveNetworkNames() - if err != nil { - return "", err - } - - config, err := r.GetConfig() - if err != nil { - return "", err - } - - // Make sure the host-device exists - if !util.StringInSlice(options.MacVLAN, liveNetNames) { - return "", errors.Errorf("failed to find network interface %q", options.MacVLAN) - } - if len(name) > 0 { - netNames, err := network.GetNetworkNamesFromFileSystem(config) - if err != nil { - return "", err - } - if util.StringInSlice(name, netNames) { - return "", errors.Errorf("the network name %s is already used", name) - } - } else { - name, err = network.GetFreeDeviceName(config) - if err != nil { - return "", err - } - } - ncList := network.NewNcList(name, cniversion.Current()) - macvlan := network.NewMacVLANPlugin(options.MacVLAN) - plugins = append(plugins, macvlan) - ncList["plugins"] = plugins - b, err := json.MarshalIndent(ncList, "", " ") - if err != nil { - return "", err - } - cniPathName := filepath.Join(network.GetCNIConfDir(config), fmt.Sprintf("%s.conflist", name)) - err = ioutil.WriteFile(cniPathName, b, 0644) - return cniPathName, err + return network.Create(name, options, ic.Libpod) } func ifPassesFilterTest(netconf *libcni.NetworkConfigList, filter []string) bool { diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 40edc1ae3..2de98d8f5 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -311,6 +311,22 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY ctrRestartPolicy = libpod.RestartPolicyAlways } + configMaps := []v1.ConfigMap{} + for _, p := range options.ConfigMaps { + f, err := os.Open(p) + if err != nil { + return nil, err + } + defer f.Close() + + cm, err := readConfigMapFromFile(f) + if err != nil { + return nil, errors.Wrapf(err, "%q", p) + } + + configMaps = append(configMaps, cm) + } + containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers)) for _, container := range podYAML.Spec.Containers { pullPolicy := util.PullImageMissing @@ -334,7 +350,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY if err != nil { return nil, err } - conf, err := kubeContainerToCreateConfig(ctx, container, newImage, namespaces, volumes, pod.ID(), podName, podInfraID, seccompPaths) + conf, err := kubeContainerToCreateConfig(ctx, container, newImage, namespaces, volumes, pod.ID(), podName, podInfraID, configMaps, seccompPaths) if err != nil { return nil, err } @@ -447,7 +463,7 @@ func setupSecurityContext(securityConfig *createconfig.SecurityConfig, userConfi } // kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container -func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, podName, infraID string, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) { +func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, podName, infraID string, configMaps []v1.ConfigMap, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) { var ( containerConfig createconfig.CreateConfig pidConfig createconfig.PidConfig @@ -572,8 +588,17 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container } envs = imageEnv } - for _, e := range containerYAML.Env { - envs[e.Name] = e.Value + for _, env := range containerYAML.Env { + value := envVarValue(env, configMaps) + + envs[env.Name] = value + } + for _, envFrom := range containerYAML.EnvFrom { + cmEnvs := envVarsFromConfigMap(envFrom, configMaps) + + for k, v := range cmEnvs { + envs[k] = v + } } containerConfig.Env = envs @@ -594,6 +619,62 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container return &containerConfig, nil } +// readConfigMapFromFile returns a kubernetes configMap obtained from --configmap flag +func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) { + var cm v1.ConfigMap + + content, err := ioutil.ReadAll(r) + if err != nil { + return cm, errors.Wrapf(err, "unable to read ConfigMap YAML content") + } + + if err := yaml.Unmarshal(content, &cm); err != nil { + return cm, errors.Wrapf(err, "unable to read YAML as Kube ConfigMap") + } + + if cm.Kind != "ConfigMap" { + return cm, errors.Errorf("invalid YAML kind: %q. [ConfigMap] is the only supported by --configmap", cm.Kind) + } + + return cm, nil +} + +// envVarsFromConfigMap returns all key-value pairs as env vars from a configMap that matches the envFrom setting of a container +func envVarsFromConfigMap(envFrom v1.EnvFromSource, configMaps []v1.ConfigMap) map[string]string { + envs := map[string]string{} + + if envFrom.ConfigMapRef != nil { + cmName := envFrom.ConfigMapRef.Name + + for _, c := range configMaps { + if cmName == c.Name { + envs = c.Data + break + } + } + } + + return envs +} + +// envVarValue returns the environment variable value configured within the container's env setting. +// It gets the value from a configMap if specified, otherwise returns env.Value +func envVarValue(env v1.EnvVar, configMaps []v1.ConfigMap) string { + for _, c := range configMaps { + if env.ValueFrom != nil { + if env.ValueFrom.ConfigMapKeyRef != nil { + if env.ValueFrom.ConfigMapKeyRef.Name == c.Name { + if value, ok := c.Data[env.ValueFrom.ConfigMapKeyRef.Key]; ok { + return value + } + } + } + } + } + + return env.Value +} + // kubeSeccompPaths holds information about a pod YAML's seccomp configuration // it holds both container and pod seccomp paths type kubeSeccompPaths struct { diff --git a/pkg/domain/infra/abi/play_test.go b/pkg/domain/infra/abi/play_test.go new file mode 100644 index 000000000..5595476c3 --- /dev/null +++ b/pkg/domain/infra/abi/play_test.go @@ -0,0 +1,254 @@ +package abi + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var configMapList = []v1.ConfigMap{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + }, + Data: map[string]string{ + "myvar": "bar", + }, + }, + { + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Data: map[string]string{ + "myvar": "foo", + }, + }, +} + +func TestReadConfigMapFromFile(t *testing.T) { + tests := []struct { + name string + configMapContent string + expectError bool + expectedErrorMsg string + expected v1.ConfigMap + }{ + { + "ValidConfigMap", + ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: foo +data: + myvar: foo +`, + false, + "", + v1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Data: map[string]string{ + "myvar": "foo", + }, + }, + }, + { + "InvalidYAML", + ` +Invalid YAML +apiVersion: v1 +kind: ConfigMap +metadata: + name: foo +data: + myvar: foo +`, + true, + "unable to read YAML as Kube ConfigMap", + v1.ConfigMap{}, + }, + { + "InvalidKind", + ` +apiVersion: v1 +kind: InvalidKind +metadata: + name: foo +data: + myvar: foo +`, + true, + "invalid YAML kind", + v1.ConfigMap{}, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + buf := bytes.NewBufferString(test.configMapContent) + cm, err := readConfigMapFromFile(buf) + + if test.expectError { + assert.Error(t, err) + assert.Contains(t, err.Error(), test.expectedErrorMsg) + } else { + assert.NoError(t, err) + assert.Equal(t, test.expected, cm) + } + }) + } +} + +func TestEnvVarsFromConfigMap(t *testing.T) { + tests := []struct { + name string + envFrom v1.EnvFromSource + configMapList []v1.ConfigMap + expected map[string]string + }{ + { + "ConfigMapExists", + v1.EnvFromSource{ + ConfigMapRef: &v1.ConfigMapEnvSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + }, + }, + configMapList, + map[string]string{ + "myvar": "foo", + }, + }, + { + "ConfigMapDoesNotExist", + v1.EnvFromSource{ + ConfigMapRef: &v1.ConfigMapEnvSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "doesnotexist", + }, + }, + }, + configMapList, + map[string]string{}, + }, + { + "EmptyConfigMapList", + v1.EnvFromSource{ + ConfigMapRef: &v1.ConfigMapEnvSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + }, + }, + []v1.ConfigMap{}, + map[string]string{}, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + result := envVarsFromConfigMap(test.envFrom, test.configMapList) + assert.Equal(t, test.expected, result) + }) + } +} + +func TestEnvVarValue(t *testing.T) { + tests := []struct { + name string + envVar v1.EnvVar + configMapList []v1.ConfigMap + expected string + }{ + { + "ConfigMapExists", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ConfigMapKeyRef: &v1.ConfigMapKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + Key: "myvar", + }, + }, + }, + configMapList, + "foo", + }, + { + "ContainerKeyDoesNotExistInConfigMap", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ConfigMapKeyRef: &v1.ConfigMapKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + Key: "doesnotexist", + }, + }, + }, + configMapList, + "", + }, + { + "ConfigMapDoesNotExist", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ConfigMapKeyRef: &v1.ConfigMapKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "doesnotexist", + }, + Key: "myvar", + }, + }, + }, + configMapList, + "", + }, + { + "EmptyConfigMapList", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ConfigMapKeyRef: &v1.ConfigMapKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + Key: "myvar", + }, + }, + }, + []v1.ConfigMap{}, + "", + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + result := envVarValue(test.envVar, test.configMapList) + assert.Equal(t, test.expected, result) + }) + } +} diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index 747da9fd4..258640a81 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -66,7 +66,7 @@ func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, opt for _, p := range pods { report := entities.PodKillReport{Id: p.ID()} - conErrs, err := p.Kill(uint(sig)) + conErrs, err := p.Kill(ctx, uint(sig)) if err != nil && errors.Cause(err) != define.ErrPodPartialFail { report.Errs = []error{err} reports = append(reports, &report) @@ -92,7 +92,7 @@ func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, op } for _, p := range pods { report := entities.PodPauseReport{Id: p.ID()} - errs, err := p.Pause() + errs, err := p.Pause(ctx) if err != nil && errors.Cause(err) != define.ErrPodPartialFail { report.Errs = []error{err} continue @@ -117,7 +117,7 @@ func (ic *ContainerEngine) PodUnpause(ctx context.Context, namesOrIds []string, } for _, p := range pods { report := entities.PodUnpauseReport{Id: p.ID()} - errs, err := p.Unpause() + errs, err := p.Unpause(ctx) if err != nil && errors.Cause(err) != define.ErrPodPartialFail { report.Errs = []error{err} continue diff --git a/pkg/network/config.go b/pkg/network/config.go deleted file mode 100644 index 0115433e1..000000000 --- a/pkg/network/config.go +++ /dev/null @@ -1,130 +0,0 @@ -package network - -import ( - "encoding/json" - "net" -) - -// TODO once the containers.conf file stuff is worked out, this should be modified -// to honor defines in the containers.conf as well as overrides? - -const ( - // CNIConfigDir is the path where CNI config files exist - CNIConfigDir = "/etc/cni/net.d" - // CNIDeviceName is the default network device name and in - // reality should have an int appended to it (cni-podman4) - CNIDeviceName = "cni-podman" - // DefaultPodmanDomainName is used for the dnsname plugin to define - // a localized domain name for a created network - DefaultPodmanDomainName = "dns.podman" -) - -// GetDefaultPodmanNetwork outputs the default network for podman -func GetDefaultPodmanNetwork() (*net.IPNet, error) { - _, n, err := net.ParseCIDR("10.88.1.0/24") - return n, err -} - -// CNIPlugins is a way of marshalling a CNI network configuration to disk -type CNIPlugins interface { - Bytes() ([]byte, error) -} - -// HostLocalBridge describes a configuration for a bridge plugin -// https://github.com/containernetworking/plugins/tree/master/plugins/main/bridge#network-configuration-reference -type HostLocalBridge struct { - PluginType string `json:"type"` - BrName string `json:"bridge,omitempty"` - IsGW bool `json:"isGateway"` - IsDefaultGW bool `json:"isDefaultGateway,omitempty"` - ForceAddress bool `json:"forceAddress,omitempty"` - IPMasq bool `json:"ipMasq,omitempty"` - MTU int `json:"mtu,omitempty"` - HairpinMode bool `json:"hairpinMode,omitempty"` - PromiscMode bool `json:"promiscMode,omitempty"` - Vlan int `json:"vlan,omitempty"` - IPAM IPAMHostLocalConf `json:"ipam"` -} - -// Bytes outputs []byte -func (h *HostLocalBridge) Bytes() ([]byte, error) { - return json.MarshalIndent(h, "", "\t") -} - -// IPAMHostLocalConf describes an IPAM configuration -// https://github.com/containernetworking/plugins/tree/master/plugins/ipam/host-local#network-configuration-reference -type IPAMHostLocalConf struct { - PluginType string `json:"type"` - Routes []IPAMRoute `json:"routes,omitempty"` - ResolveConf string `json:"resolveConf,omitempty"` - DataDir string `json:"dataDir,omitempty"` - Ranges [][]IPAMLocalHostRangeConf `json:"ranges,omitempty"` -} - -// IPAMLocalHostRangeConf describes the new style IPAM ranges -type IPAMLocalHostRangeConf struct { - Subnet string `json:"subnet"` - RangeStart string `json:"rangeStart,omitempty"` - RangeEnd string `json:"rangeEnd,omitempty"` - Gateway string `json:"gateway,omitempty"` -} - -// Bytes outputs the configuration as []byte -func (i IPAMHostLocalConf) Bytes() ([]byte, error) { - return json.MarshalIndent(i, "", "\t") -} - -// IPAMRoute describes a route in an ipam config -type IPAMRoute struct { - Dest string `json:"dst"` -} - -// PortMapConfig describes the default portmapping config -type PortMapConfig struct { - PluginType string `json:"type"` - Capabilities map[string]bool `json:"capabilities"` -} - -// Bytes outputs the configuration as []byte -func (p PortMapConfig) Bytes() ([]byte, error) { - return json.MarshalIndent(p, "", "\t") -} - -// IPAMDHCP describes the ipamdhcp config -type IPAMDHCP struct { - DHCP string `json:"type"` -} - -// MacVLANConfig describes the macvlan config -type MacVLANConfig struct { - PluginType string `json:"type"` - Master string `json:"master"` - IPAM IPAMDHCP `json:"ipam"` -} - -// Bytes outputs the configuration as []byte -func (p MacVLANConfig) Bytes() ([]byte, error) { - return json.MarshalIndent(p, "", "\t") -} - -// FirewallConfig describes the firewall plugin -type FirewallConfig struct { - PluginType string `json:"type"` - Backend string `json:"backend"` -} - -// Bytes outputs the configuration as []byte -func (f FirewallConfig) Bytes() ([]byte, error) { - return json.MarshalIndent(f, "", "\t") -} - -// DNSNameConfig describes the dns container name resolution plugin config -type DNSNameConfig struct { - PluginType string `json:"type"` - DomainName string `json:"domainName"` -} - -// Bytes outputs the configuration as []byte -func (d DNSNameConfig) Bytes() ([]byte, error) { - return json.MarshalIndent(d, "", "\t") -} diff --git a/pkg/network/devices.go b/pkg/network/devices.go deleted file mode 100644 index a5d23fae4..000000000 --- a/pkg/network/devices.go +++ /dev/null @@ -1,63 +0,0 @@ -package network - -import ( - "fmt" - "os/exec" - - "github.com/containers/common/pkg/config" - "github.com/containers/podman/v2/pkg/util" - "github.com/containers/podman/v2/utils" - "github.com/sirupsen/logrus" -) - -// GetFreeDeviceName returns a device name that is unused; used when no network -// name is provided by user -func GetFreeDeviceName(config *config.Config) (string, error) { - var ( - deviceNum uint - deviceName string - ) - networkNames, err := GetNetworkNamesFromFileSystem(config) - if err != nil { - return "", err - } - liveNetworksNames, err := GetLiveNetworkNames() - if err != nil { - return "", err - } - bridgeNames, err := GetBridgeNamesFromFileSystem(config) - if err != nil { - return "", err - } - for { - deviceName = fmt.Sprintf("%s%d", CNIDeviceName, deviceNum) - logrus.Debugf("checking if device name %q exists in other cni networks", deviceName) - if util.StringInSlice(deviceName, networkNames) { - deviceNum++ - continue - } - logrus.Debugf("checking if device name %q exists in live networks", deviceName) - if util.StringInSlice(deviceName, liveNetworksNames) { - deviceNum++ - continue - } - logrus.Debugf("checking if device name %q already exists as a bridge name ", deviceName) - if !util.StringInSlice(deviceName, bridgeNames) { - break - } - deviceNum++ - } - return deviceName, nil -} - -// RemoveInterface removes an interface by the given name -func RemoveInterface(interfaceName string) error { - // Make sure we have the ip command on the system - ipPath, err := exec.LookPath("ip") - if err != nil { - return err - } - // Delete the network interface - _, err = utils.ExecCmd(ipPath, []string{"link", "del", interfaceName}...) - return err -} diff --git a/pkg/network/files.go b/pkg/network/files.go deleted file mode 100644 index a2090491f..000000000 --- a/pkg/network/files.go +++ /dev/null @@ -1,174 +0,0 @@ -package network - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "sort" - "strings" - - "github.com/containernetworking/cni/libcni" - "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" - "github.com/containers/common/pkg/config" - "github.com/containers/podman/v2/libpod/define" - "github.com/pkg/errors" -) - -func GetCNIConfDir(configArg *config.Config) string { - if len(configArg.Network.NetworkConfigDir) < 1 { - dc, err := config.DefaultConfig() - if err != nil { - // Fallback to hard-coded dir - return CNIConfigDir - } - return dc.Network.NetworkConfigDir - } - return configArg.Network.NetworkConfigDir -} - -// LoadCNIConfsFromDir loads all the CNI configurations from a dir -func LoadCNIConfsFromDir(dir string) ([]*libcni.NetworkConfigList, error) { - files, err := libcni.ConfFiles(dir, []string{".conflist"}) - if err != nil { - return nil, err - } - sort.Strings(files) - - configs := make([]*libcni.NetworkConfigList, 0, len(files)) - for _, confFile := range files { - conf, err := libcni.ConfListFromFile(confFile) - if err != nil { - return nil, errors.Wrapf(err, "in %s", confFile) - } - configs = append(configs, conf) - } - return configs, nil -} - -// GetCNIConfigPathByName finds a CNI network by name and -// returns its configuration file path -func GetCNIConfigPathByName(config *config.Config, name string) (string, error) { - files, err := libcni.ConfFiles(GetCNIConfDir(config), []string{".conflist"}) - if err != nil { - return "", err - } - for _, confFile := range files { - conf, err := libcni.ConfListFromFile(confFile) - if err != nil { - return "", errors.Wrapf(err, "in %s", confFile) - } - if conf.Name == name { - return confFile, nil - } - } - return "", errors.Wrap(define.ErrNoSuchNetwork, fmt.Sprintf("unable to find network configuration for %s", name)) -} - -// ReadRawCNIConfByName reads the raw CNI configuration for a CNI -// network by name -func ReadRawCNIConfByName(config *config.Config, name string) ([]byte, error) { - confFile, err := GetCNIConfigPathByName(config, name) - if err != nil { - return nil, err - } - b, err := ioutil.ReadFile(confFile) - return b, err -} - -// GetCNIPlugins returns a list of plugins that a given network -// has in the form of a string -func GetCNIPlugins(list *libcni.NetworkConfigList) string { - plugins := make([]string, 0, len(list.Plugins)) - for _, plug := range list.Plugins { - plugins = append(plugins, plug.Network.Type) - } - return strings.Join(plugins, ",") -} - -// GetNetworksFromFilesystem gets all the networks from the cni configuration -// files -func GetNetworksFromFilesystem(config *config.Config) ([]*allocator.Net, error) { - var cniNetworks []*allocator.Net - - networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config)) - if err != nil { - return nil, err - } - for _, n := range networks { - for _, cniplugin := range n.Plugins { - if cniplugin.Network.Type == "bridge" { - ipamConf := allocator.Net{} - if err := json.Unmarshal(cniplugin.Bytes, &ipamConf); err != nil { - return nil, err - } - cniNetworks = append(cniNetworks, &ipamConf) - break - } - } - } - return cniNetworks, nil -} - -// GetNetworkNamesFromFileSystem gets all the names from the cni network -// configuration files -func GetNetworkNamesFromFileSystem(config *config.Config) ([]string, error) { - networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config)) - if err != nil { - return nil, err - } - networkNames := []string{} - for _, n := range networks { - networkNames = append(networkNames, n.Name) - } - return networkNames, nil -} - -// GetInterfaceNameFromConfig returns the interface name for the bridge plugin -func GetInterfaceNameFromConfig(path string) (string, error) { - var name string - conf, err := libcni.ConfListFromFile(path) - if err != nil { - return "", err - } - for _, cniplugin := range conf.Plugins { - if cniplugin.Network.Type == "bridge" { - plugin := make(map[string]interface{}) - if err := json.Unmarshal(cniplugin.Bytes, &plugin); err != nil { - return "", err - } - name = plugin["bridge"].(string) - break - } - } - if len(name) == 0 { - return "", errors.New("unable to find interface name for network") - } - return name, nil -} - -// GetBridgeNamesFromFileSystem is a convenience function to get all the bridge -// names from the configured networks -func GetBridgeNamesFromFileSystem(config *config.Config) ([]string, error) { - networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config)) - if err != nil { - return nil, err - } - - bridgeNames := []string{} - for _, n := range networks { - var name string - // iterate network conflists - for _, cniplugin := range n.Plugins { - // iterate plugins - if cniplugin.Network.Type == "bridge" { - plugin := make(map[string]interface{}) - if err := json.Unmarshal(cniplugin.Bytes, &plugin); err != nil { - continue - } - name = plugin["bridge"].(string) - } - } - bridgeNames = append(bridgeNames, name) - } - return bridgeNames, nil -} diff --git a/pkg/network/ip.go b/pkg/network/ip.go deleted file mode 100644 index ba93a0d05..000000000 --- a/pkg/network/ip.go +++ /dev/null @@ -1,19 +0,0 @@ -package network - -import ( - "net" - - "github.com/containernetworking/plugins/pkg/ip" -) - -// CalcGatewayIP takes a network and returns the first IP in it. -func CalcGatewayIP(ipn *net.IPNet) net.IP { - // taken from cni bridge plugin as it is not exported - nid := ipn.IP.Mask(ipn.Mask) - return ip.NextIP(nid) -} - -// IsIPv6 returns if netIP is IPv6. -func IsIPv6(netIP net.IP) bool { - return netIP != nil && netIP.To4() == nil -} diff --git a/pkg/network/netconflist.go b/pkg/network/netconflist.go deleted file mode 100644 index 8187fdb39..000000000 --- a/pkg/network/netconflist.go +++ /dev/null @@ -1,155 +0,0 @@ -package network - -import ( - "net" - "os" - "path/filepath" -) - -const ( - defaultIPv4Route = "0.0.0.0/0" - defaultIPv6Route = "::/0" -) - -// NcList describes a generic map -type NcList map[string]interface{} - -// NewNcList creates a generic map of values with string -// keys and adds in version and network name -func NewNcList(name, version string) NcList { - n := NcList{} - n["cniVersion"] = version - n["name"] = name - return n -} - -// NewHostLocalBridge creates a new LocalBridge for host-local -func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, ipamConf IPAMHostLocalConf) *HostLocalBridge { - hostLocalBridge := HostLocalBridge{ - PluginType: "bridge", - BrName: name, - IPMasq: ipMasq, - HairpinMode: true, - IPAM: ipamConf, - } - if isGateWay { - hostLocalBridge.IsGW = true - } - if isDefaultGW { - hostLocalBridge.IsDefaultGW = true - } - return &hostLocalBridge -} - -// NewIPAMHostLocalConf creates a new IPAMHostLocal configfuration -func NewIPAMHostLocalConf(subnet *net.IPNet, routes []IPAMRoute, ipRange net.IPNet, gw net.IP) (IPAMHostLocalConf, error) { - var ipamRanges [][]IPAMLocalHostRangeConf - ipamConf := IPAMHostLocalConf{ - PluginType: "host-local", - Routes: routes, - // Possible future support ? Leaving for clues - //ResolveConf: "", - //DataDir: "" - } - IPAMRange, err := newIPAMLocalHostRange(subnet, &ipRange, &gw) - if err != nil { - return ipamConf, err - } - ipamRanges = append(ipamRanges, IPAMRange) - ipamConf.Ranges = ipamRanges - return ipamConf, nil -} - -func newIPAMLocalHostRange(subnet *net.IPNet, ipRange *net.IPNet, gw *net.IP) ([]IPAMLocalHostRangeConf, error) { //nolint:interfacer - var ranges []IPAMLocalHostRangeConf - hostRange := IPAMLocalHostRangeConf{ - Subnet: subnet.String(), - } - // an user provided a range, we add it here - if ipRange.IP != nil { - first, err := FirstIPInSubnet(ipRange) - if err != nil { - return nil, err - } - last, err := LastIPInSubnet(ipRange) - if err != nil { - return nil, err - } - hostRange.RangeStart = first.String() - hostRange.RangeEnd = last.String() - } - if gw != nil { - hostRange.Gateway = gw.String() - } - ranges = append(ranges, hostRange) - return ranges, nil -} - -// NewIPAMRoute creates a new IPAM route configuration -func NewIPAMRoute(r *net.IPNet) IPAMRoute { //nolint:interfacer - return IPAMRoute{Dest: r.String()} -} - -// NewIPAMDefaultRoute creates a new IPAMDefault route of -// 0.0.0.0/0 for IPv4 or ::/0 for IPv6 -func NewIPAMDefaultRoute(isIPv6 bool) (IPAMRoute, error) { - route := defaultIPv4Route - if isIPv6 { - route = defaultIPv6Route - } - _, n, err := net.ParseCIDR(route) - if err != nil { - return IPAMRoute{}, err - } - return NewIPAMRoute(n), nil -} - -// NewPortMapPlugin creates a predefined, default portmapping -// configuration -func NewPortMapPlugin() PortMapConfig { - caps := make(map[string]bool) - caps["portMappings"] = true - p := PortMapConfig{ - PluginType: "portmap", - Capabilities: caps, - } - return p -} - -// NewFirewallPlugin creates a generic firewall plugin -func NewFirewallPlugin() FirewallConfig { - return FirewallConfig{ - PluginType: "firewall", - } -} - -// NewDNSNamePlugin creates the dnsname config with a given -// domainname -func NewDNSNamePlugin(domainName string) DNSNameConfig { - return DNSNameConfig{ - PluginType: "dnsname", - DomainName: domainName, - } -} - -// HasDNSNamePlugin looks to see if the dnsname cni plugin is present -func HasDNSNamePlugin(paths []string) bool { - for _, p := range paths { - if _, err := os.Stat(filepath.Join(p, "dnsname")); err == nil { - return true - } - } - return false -} - -// NewMacVLANPlugin creates a macvlanconfig with a given device name -func NewMacVLANPlugin(device string) MacVLANConfig { - i := IPAMDHCP{DHCP: "dhcp"} - - m := MacVLANConfig{ - PluginType: "macvlan", - Master: device, - IPAM: i, - } - return m -} diff --git a/pkg/network/netconflist_test.go b/pkg/network/netconflist_test.go deleted file mode 100644 index 5893bf985..000000000 --- a/pkg/network/netconflist_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package network - -import ( - "reflect" - "testing" -) - -func TestNewIPAMDefaultRoute(t *testing.T) { - - tests := []struct { - name string - isIPv6 bool - want IPAMRoute - }{ - { - name: "IPv4 default route", - isIPv6: false, - want: IPAMRoute{defaultIPv4Route}, - }, - { - name: "IPv6 default route", - isIPv6: true, - want: IPAMRoute{defaultIPv6Route}, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - got, err := NewIPAMDefaultRoute(tt.isIPv6) - if err != nil { - t.Errorf("no error expected: %v", err) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewIPAMDefaultRoute() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/network/network.go b/pkg/network/network.go deleted file mode 100644 index c4c1ff67f..000000000 --- a/pkg/network/network.go +++ /dev/null @@ -1,219 +0,0 @@ -package network - -import ( - "encoding/json" - "net" - "os" - - "github.com/containernetworking/cni/pkg/types" - "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" - "github.com/containers/common/pkg/config" - "github.com/containers/podman/v2/libpod/define" - "github.com/containers/podman/v2/pkg/util" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// DefaultNetworkDriver is the default network type used -var DefaultNetworkDriver = "bridge" - -// SupportedNetworkDrivers describes the list of supported drivers -var SupportedNetworkDrivers = []string{DefaultNetworkDriver} - -// IsSupportedDriver checks if the user provided driver is supported -func IsSupportedDriver(driver string) error { - if util.StringInSlice(driver, SupportedNetworkDrivers) { - return nil - } - return errors.Errorf("driver '%s' is not supported", driver) -} - -// GetLiveNetworks returns a slice of networks representing what the system -// has defined as network interfaces -func GetLiveNetworks() ([]*net.IPNet, error) { - addrs, err := net.InterfaceAddrs() - if err != nil { - return nil, err - } - nets := make([]*net.IPNet, 0, len(addrs)) - for _, address := range addrs { - _, n, err := net.ParseCIDR(address.String()) - if err != nil { - return nil, err - } - nets = append(nets, n) - } - return nets, nil -} - -// GetLiveNetworkNames returns a list of network interfaces on the system -func GetLiveNetworkNames() ([]string, error) { - liveInterfaces, err := net.Interfaces() - if err != nil { - return nil, err - } - interfaceNames := make([]string, 0, len(liveInterfaces)) - for _, i := range liveInterfaces { - interfaceNames = append(interfaceNames, i.Name) - } - return interfaceNames, nil -} - -// GetFreeNetwork looks for a free network according to existing cni configuration -// files and network interfaces. -func GetFreeNetwork(config *config.Config) (*net.IPNet, error) { - networks, err := GetNetworksFromFilesystem(config) - if err != nil { - return nil, err - } - liveNetworks, err := GetLiveNetworks() - if err != nil { - return nil, err - } - nextNetwork, err := GetDefaultPodmanNetwork() - if err != nil { - return nil, err - } - logrus.Debugf("default network is %s", nextNetwork.String()) - for { - newNetwork, err := NextSubnet(nextNetwork) - if err != nil { - return nil, err - } - logrus.Debugf("checking if network %s intersects with other cni networks", nextNetwork.String()) - if intersectsConfig, _ := networkIntersectsWithNetworks(newNetwork, allocatorToIPNets(networks)); intersectsConfig { - logrus.Debugf("network %s is already being used by a cni configuration", nextNetwork.String()) - nextNetwork = newNetwork - continue - } - logrus.Debugf("checking if network %s intersects with any network interfaces", nextNetwork.String()) - if intersectsLive, _ := networkIntersectsWithNetworks(newNetwork, liveNetworks); !intersectsLive { - break - } - logrus.Debugf("network %s is being used by a network interface", nextNetwork.String()) - nextNetwork = newNetwork - } - return nextNetwork, nil -} - -func allocatorToIPNets(networks []*allocator.Net) []*net.IPNet { - var nets []*net.IPNet - for _, network := range networks { - if len(network.IPAM.Ranges) > 0 { - // this is the new IPAM range style - // append each subnet from ipam the rangeset - for _, r := range network.IPAM.Ranges[0] { - nets = append(nets, newIPNetFromSubnet(r.Subnet)) - } - } else { - // looks like the old, deprecated style - nets = append(nets, newIPNetFromSubnet(network.IPAM.Subnet)) - } - } - return nets -} - -func newIPNetFromSubnet(subnet types.IPNet) *net.IPNet { - n := net.IPNet{ - IP: subnet.IP, - Mask: subnet.Mask, - } - return &n -} - -func networkIntersectsWithNetworks(n *net.IPNet, networklist []*net.IPNet) (bool, *net.IPNet) { - for _, nw := range networklist { - if networkIntersect(n, nw) { - return true, nw - } - } - return false, nil -} - -func networkIntersect(n1, n2 *net.IPNet) bool { - return n2.Contains(n1.IP) || n1.Contains(n2.IP) -} - -// ValidateUserNetworkIsAvailable returns via an error if a network is available -// to be used -func ValidateUserNetworkIsAvailable(config *config.Config, userNet *net.IPNet) error { - if len(userNet.IP) == 0 || len(userNet.Mask) == 0 { - return errors.Errorf("network %s's ip or mask cannot be empty", userNet.String()) - } - - ones, bit := userNet.Mask.Size() - if ones == 0 || bit == 0 { - return errors.Errorf("network %s's mask is invalid", userNet.String()) - } - - networks, err := GetNetworksFromFilesystem(config) - if err != nil { - return err - } - liveNetworks, err := GetLiveNetworks() - if err != nil { - return err - } - logrus.Debugf("checking if network %s exists in cni networks", userNet.String()) - if intersectsConfig, _ := networkIntersectsWithNetworks(userNet, allocatorToIPNets(networks)); intersectsConfig { - return errors.Errorf("network %s is already being used by a cni configuration", userNet.String()) - } - logrus.Debugf("checking if network %s exists in any network interfaces", userNet.String()) - if intersectsLive, _ := networkIntersectsWithNetworks(userNet, liveNetworks); intersectsLive { - return errors.Errorf("network %s is being used by a network interface", userNet.String()) - } - return nil -} - -// RemoveNetwork removes a given network by name. If the network has container associated with it, that -// must be handled outside the context of this. -func RemoveNetwork(config *config.Config, name string) error { - cniPath, err := GetCNIConfigPathByName(config, name) - if err != nil { - return err - } - // Before we delete the configuration file, we need to make sure we can read and parse - // it to get the network interface name so we can remove that too - interfaceName, err := GetInterfaceNameFromConfig(cniPath) - if err != nil { - return errors.Wrapf(err, "failed to find network interface name in %q", cniPath) - } - liveNetworkNames, err := GetLiveNetworkNames() - if err != nil { - return errors.Wrapf(err, "failed to get live network names") - } - if util.StringInSlice(interfaceName, liveNetworkNames) { - if err := RemoveInterface(interfaceName); err != nil { - return errors.Wrapf(err, "failed to delete the network interface %q", interfaceName) - } - } - // Remove the configuration file - if err := os.Remove(cniPath); err != nil { - return errors.Wrapf(err, "failed to remove network configuration file %q", cniPath) - } - return nil -} - -// InspectNetwork reads a CNI config and returns its configuration -func InspectNetwork(config *config.Config, name string) (map[string]interface{}, error) { - b, err := ReadRawCNIConfByName(config, name) - if err != nil { - return nil, err - } - rawList := make(map[string]interface{}) - err = json.Unmarshal(b, &rawList) - return rawList, err -} - -// Exists says whether a given network exists or not; it meant -// specifically for restful responses so 404s can be used -func Exists(config *config.Config, name string) (bool, error) { - _, err := ReadRawCNIConfByName(config, name) - if err != nil { - if errors.Cause(err) == define.ErrNoSuchNetwork { - return false, nil - } - return false, err - } - return true, nil -} diff --git a/pkg/network/network_test.go b/pkg/network/network_test.go deleted file mode 100644 index 1969e792c..000000000 --- a/pkg/network/network_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package network - -import ( - "net" - "testing" -) - -func parseCIDR(n string) *net.IPNet { - _, parsedNet, _ := net.ParseCIDR(n) - return parsedNet -} - -func Test_networkIntersect(t *testing.T) { - type args struct { - n1 *net.IPNet - n2 *net.IPNet - } - tests := []struct { - name string - args args - want bool - }{ - {"16 and 24 intersects", args{n1: parseCIDR("192.168.0.0/16"), n2: parseCIDR("192.168.1.0/24")}, true}, - {"24 and 25 intersects", args{n1: parseCIDR("192.168.1.0/24"), n2: parseCIDR("192.168.1.0/25")}, true}, - {"Two 24s", args{n1: parseCIDR("192.168.1.0/24"), n2: parseCIDR("192.168.2.0/24")}, false}, - } - for _, tt := range tests { - test := tt - t.Run(tt.name, func(t *testing.T) { - if got := networkIntersect(test.args.n1, test.args.n2); got != test.want { - t.Errorf("networkIntersect() = %v, want %v", got, test.want) - } - }) - } -} diff --git a/pkg/network/subnet.go b/pkg/network/subnet.go deleted file mode 100644 index 90f0cdfce..000000000 --- a/pkg/network/subnet.go +++ /dev/null @@ -1,78 +0,0 @@ -package network - -/* - The code in this was kindly contributed by Dan Williams(dcbw@redhat.com). Many thanks - for his contributions. -*/ - -import ( - "fmt" - "net" -) - -func incByte(subnet *net.IPNet, idx int, shift uint) error { - if idx < 0 { - return fmt.Errorf("no more subnets left") - } - if subnet.IP[idx] == 255 { - subnet.IP[idx] = 0 - return incByte(subnet, idx-1, 0) - } - subnet.IP[idx] += 1 << shift - return nil -} - -// NextSubnet returns subnet incremented by 1 -func NextSubnet(subnet *net.IPNet) (*net.IPNet, error) { - newSubnet := &net.IPNet{ - IP: subnet.IP, - Mask: subnet.Mask, - } - ones, bits := newSubnet.Mask.Size() - if ones == 0 { - return nil, fmt.Errorf("%s has only one subnet", subnet.String()) - } - zeroes := uint(bits - ones) - shift := zeroes % 8 - idx := ones/8 - 1 - if idx < 0 { - idx = 0 - } - if err := incByte(newSubnet, idx, shift); err != nil { - return nil, err - } - return newSubnet, nil -} - -// LastIPInSubnet gets the last IP in a subnet -func LastIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer - // re-parse to ensure clean network address - _, cidr, err := net.ParseCIDR(addr.String()) - if err != nil { - return nil, err - } - - ones, bits := cidr.Mask.Size() - if ones == bits { - return FirstIPInSubnet(cidr) - } - hostStart := ones / 8 - // Handle the first host byte - cidr.IP[hostStart] |= 0xff & cidr.Mask[hostStart] - // Fill the rest with ones - for i := hostStart; i < len(cidr.IP); i++ { - cidr.IP[i] = 0xff - } - return cidr.IP, nil -} - -// FirstIPInSubnet gets the first IP in a subnet -func FirstIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer - // re-parse to ensure clean network address - _, cidr, err := net.ParseCIDR(addr.String()) - if err != nil { - return nil, err - } - cidr.IP[len(cidr.IP)-1]++ - return cidr.IP, nil -} diff --git a/pkg/network/subnet_test.go b/pkg/network/subnet_test.go deleted file mode 100644 index 917c3be88..000000000 --- a/pkg/network/subnet_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package network - -import ( - "net" - "reflect" - "testing" -) - -func TestNextSubnet(t *testing.T) { - type args struct { - subnet *net.IPNet - } - tests := []struct { - name string - args args - want *net.IPNet - wantErr bool - }{ - {"class b", args{subnet: parseCIDR("192.168.0.0/16")}, parseCIDR("192.169.0.0/16"), false}, - {"class c", args{subnet: parseCIDR("192.168.1.0/24")}, parseCIDR("192.168.2.0/24"), false}, - } - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - got, err := NextSubnet(test.args.subnet) - if (err != nil) != test.wantErr { - t.Errorf("NextSubnet() error = %v, wantErr %v", err, test.wantErr) - return - } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("NextSubnet() got = %v, want %v", got, test.want) - } - }) - } -} diff --git a/pkg/parallel/parallel_linux.go b/pkg/parallel/ctr/ctr.go index 442db1502..e8c1292b8 100644 --- a/pkg/parallel/parallel_linux.go +++ b/pkg/parallel/ctr/ctr.go @@ -1,11 +1,10 @@ -package parallel +package ctr import ( "context" - "sync" "github.com/containers/podman/v2/libpod" - "github.com/pkg/errors" + "github.com/containers/podman/v2/pkg/parallel" "github.com/sirupsen/logrus" ) @@ -14,44 +13,28 @@ import ( // If no error is returned, each container specified in ctrs will have an entry // in the resulting map; containers with no error will be set to nil. func ContainerOp(ctx context.Context, ctrs []*libpod.Container, applyFunc func(*libpod.Container) error) (map[*libpod.Container]error, error) { - jobControlLock.RLock() - defer jobControlLock.RUnlock() - // We could use a sync.Map but given Go's lack of generic I'd rather // just use a lock on a normal map... // The expectation is that most of the time is spent in applyFunc // anyways. var ( - errMap = make(map[*libpod.Container]error) - errLock sync.Mutex - allDone sync.WaitGroup + errMap = make(map[*libpod.Container]<-chan error) ) for _, ctr := range ctrs { - // Block until a thread is available - if err := jobControl.Acquire(ctx, 1); err != nil { - return nil, errors.Wrapf(err, "error acquiring job control semaphore") - } - - allDone.Add(1) - c := ctr - go func() { - logrus.Debugf("Launching job on container %s", c.ID()) - - err := applyFunc(c) - errLock.Lock() - errMap[c] = err - errLock.Unlock() - - allDone.Done() - jobControl.Release(1) - }() + logrus.Debugf("Starting parallel job on container %s", c.ID()) + errChan := parallel.Enqueue(ctx, func() error { + return applyFunc(c) + }) + errMap[c] = errChan } - allDone.Wait() + finalErr := make(map[*libpod.Container]error) + for ctr, errChan := range errMap { + err := <-errChan + finalErr[ctr] = err + } - return errMap, nil + return finalErr, nil } - -// TODO: Add an Enqueue() function that returns a promise diff --git a/pkg/parallel/parallel.go b/pkg/parallel/parallel.go index c9e4da50d..4da7e0f89 100644 --- a/pkg/parallel/parallel.go +++ b/pkg/parallel/parallel.go @@ -1,6 +1,7 @@ package parallel import ( + "context" "sync" "github.com/pkg/errors" @@ -42,3 +43,32 @@ func SetMaxThreads(threads uint) error { func GetMaxThreads() uint { return numThreads } + +// Enqueue adds a single function to the parallel jobs queue. This function will +// be run when an unused thread is available. +// Returns a receive-only error channel that will return the error (if any) from +// the provided function fn when fn has finished executing. The channel will be +// closed after this. +func Enqueue(ctx context.Context, fn func() error) <-chan error { + retChan := make(chan error) + + go func() { + jobControlLock.RLock() + defer jobControlLock.RUnlock() + + defer close(retChan) + + if err := jobControl.Acquire(ctx, 1); err != nil { + retChan <- errors.Wrapf(err, "error acquiring job control semaphore") + return + } + + err := fn() + + jobControl.Release(1) + + retChan <- err + }() + + return retChan +} diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index 189434780..6d03afb7a 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -3,6 +3,7 @@ package varlinkapi import ( + "context" "encoding/json" "fmt" "strconv" @@ -207,7 +208,7 @@ func (i *VarlinkAPI) KillPod(call iopodman.VarlinkCall, name string, signal int6 if err != nil { return call.ReplyPodNotFound(name, err.Error()) } - ctrErrs, err := pod.Kill(killSignal) + ctrErrs, err := pod.Kill(context.TODO(), killSignal) callErr := handlePodCall(call, pod, ctrErrs, err) if callErr != nil { return err @@ -221,7 +222,7 @@ func (i *VarlinkAPI) PausePod(call iopodman.VarlinkCall, name string) error { if err != nil { return call.ReplyPodNotFound(name, err.Error()) } - ctrErrs, err := pod.Pause() + ctrErrs, err := pod.Pause(context.TODO()) callErr := handlePodCall(call, pod, ctrErrs, err) if callErr != nil { return err @@ -235,7 +236,7 @@ func (i *VarlinkAPI) UnpausePod(call iopodman.VarlinkCall, name string) error { if err != nil { return call.ReplyPodNotFound(name, err.Error()) } - ctrErrs, err := pod.Unpause() + ctrErrs, err := pod.Unpause(context.TODO()) callErr := handlePodCall(call, pod, ctrErrs, err) if callErr != nil { return err |