diff --git a/doc/source/admin/share_back_ends_feature_support_mapping.rst b/doc/source/admin/share_back_ends_feature_support_mapping.rst index f6623192d0..6c533371ba 100644 --- a/doc/source/admin/share_back_ends_feature_support_mapping.rst +++ b/doc/source/admin/share_back_ends_feature_support_mapping.rst @@ -243,71 +243,71 @@ Mapping of share drivers and common capabilities More information: :ref:`capabilities_and_extra_specs` -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Driver name | DHSS=True | DHSS=False | dedupe | compression | thin_provisioning | thick_provisioning | qos | create share from snapshot | revert to snapshot | mountable snapshot | ipv4_support | ipv6_support | -+========================================+===========+============+========+=============+===================+====================+=====+============================+====================+====================+==============+==============+ -| ZFSonLinux | \- | M | M | M | M | \- | \- | M | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Container | N | \- | \- | \- | \- | N | \- | \- | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- | J | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| NetApp Clustered Data ONTAP | J | K | M | M | M | L | P | J | O | \- | P | Q | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| EMC VMAX | O | \- | \- | \- | \- | \- | \- | O | \- | \- | P | R | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- | \- | P | Q | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| EMC Unity | N | T | \- | \- | N | \- | \- | N | S | \- | P | Q | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| EMC Isilon | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| GlusterFS | \- | J | \- | \- | \- | L | \- | volume layout (L) | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| GlusterFS-Native | \- | J | \- | \- | \- | L | \- | L | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| HDFS | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Hitachi HNAS | \- | L | N | \- | L | \- | \- | L | O | O | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Hitachi HSP | \- | N | \- | \- | N | \- | \- | \- | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| HPE 3PAR | L | K | L | \- | L | L | \- | K | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Huawei | M | K | L | L | L | L | M | M | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| INFINIDAT | \- | Q | \- | \- | Q | Q | \- | Q | Q | Q | Q | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Infortrend | \- | T | \- | \- | \- | \- | \- | \- | \- | \- | T | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| LVM | \- | M | \- | \- | \- | M | \- | K | O | O | P | P | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Quobyte | \- | K | \- | \- | \- | L | \- | M | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Windows SMB | L | L | \- | \- | \- | L | \- | \- | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| IBM GPFS | \- | K | \- | \- | \- | L | \- | L | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Oracle ZFSSA | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| CephFS | \- | M | \- | \- | \- | M | \- | \- | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Tegile | \- | M | M | M | M | \- | \- | M | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| NexentaStor4 | \- | N | N | N | N | N | \- | N | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| NexentaStor5 | \- | N | \- | N | N | N | \- | N | T | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| MapRFS | \- | N | \- | \- | \- | N | \- | O | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| QNAP | \- | O | Q | Q | O | Q | \- | O | \- | \- | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| INSPUR AS13000 | \- | R | \- | \- | R | \- | \- | R | \- | \- | R | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| INSPUR InStorage | \- | T | \- | \- | \- | T | \- | \- | \- | \- | T | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| Pure Storage FlashBlade | \- | X | \- | \- | X | \- | \- | \- | X | \- | X | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Driver name | DHSS=True | DHSS=False | dedupe | compression | thin_provisioning | thick_provisioning | qos | create share from snapshot | revert to snapshot | mountable snapshot | ipv4_support | ipv6_support | multiple subnets per AZ | ++========================================+===========+============+========+=============+===================+====================+=====+============================+====================+====================+==============+==============+=========================+ +| ZFSonLinux | \- | M | M | M | M | \- | \- | M | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Container | N | \- | \- | \- | \- | N | \- | \- | \- | \- | P | \- | Y | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- | J | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| NetApp Clustered Data ONTAP | J | K | M | M | M | L | P | J | O | \- | P | Q | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| EMC VMAX | O | \- | \- | \- | \- | \- | \- | O | \- | \- | P | R | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- | \- | P | Q | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| EMC Unity | N | T | \- | \- | N | \- | \- | N | S | \- | P | Q | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| EMC Isilon | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| GlusterFS | \- | J | \- | \- | \- | L | \- | volume layout (L) | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| GlusterFS-Native | \- | J | \- | \- | \- | L | \- | L | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| HDFS | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Hitachi HNAS | \- | L | N | \- | L | \- | \- | L | O | O | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Hitachi HSP | \- | N | \- | \- | N | \- | \- | \- | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| HPE 3PAR | L | K | L | \- | L | L | \- | K | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Huawei | M | K | L | L | L | L | M | M | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| INFINIDAT | \- | Q | \- | \- | Q | Q | \- | Q | Q | Q | Q | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Infortrend | \- | T | \- | \- | \- | \- | \- | \- | \- | \- | T | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| LVM | \- | M | \- | \- | \- | M | \- | K | O | O | P | P | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Quobyte | \- | K | \- | \- | \- | L | \- | M | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Windows SMB | L | L | \- | \- | \- | L | \- | \- | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| IBM GPFS | \- | K | \- | \- | \- | L | \- | L | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Oracle ZFSSA | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| CephFS | \- | M | \- | \- | \- | M | \- | \- | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Tegile | \- | M | M | M | M | \- | \- | M | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| NexentaStor4 | \- | N | N | N | N | N | \- | N | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| NexentaStor5 | \- | N | \- | N | N | N | \- | N | T | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| MapRFS | \- | N | \- | \- | \- | N | \- | O | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| QNAP | \- | O | Q | Q | O | Q | \- | O | \- | \- | P | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| INSPUR AS13000 | \- | R | \- | \- | R | \- | \- | R | \- | \- | R | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| INSPUR InStorage | \- | T | \- | \- | \- | T | \- | \- | \- | \- | T | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ +| Pure Storage FlashBlade | \- | X | \- | \- | X | \- | \- | \- | X | \- | X | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ .. note:: @@ -315,3 +315,4 @@ More information: :ref:`capabilities_and_extra_specs` * `DHSS` is reported as ``driver_handles_share_servers`` (See details for :term:`DHSS`) * `create share from snapshot` is reported as ``create_share_from_snapshot_support`` + * `multiple subnets per AZ` is reported as ``multiple_subnets_per_availability_zone`` diff --git a/manila/share/drivers/container/container_helper.py b/manila/share/drivers/container/container_helper.py index 7938431820..24b8d3cf1e 100644 --- a/manila/share/drivers/container/container_helper.py +++ b/manila/share/drivers/container/container_helper.py @@ -17,6 +17,7 @@ import re import uuid from oslo_log import log +from oslo_serialization import jsonutils from oslo_utils import excutils from manila import exception @@ -33,7 +34,7 @@ class DockerExecHelper(driver.ExecuteMixin): super(DockerExecHelper, self).__init__(*args, **kwargs) self.init_execute_mixin() - def start_container(self, name=None): + def create_container(self, name=None): name = name or "".join(["manila_cifs_docker_container", str(uuid.uuid1()).replace("-", "_")]) image_name = self.configuration.container_image_name @@ -60,19 +61,30 @@ class DockerExecHelper(driver.ExecuteMixin): # share providers contain vulnerabilities then the driver does not # provide any more possibilities for an exploitation than other # first-party drivers. - path = "{0}:/shares".format( self.configuration.container_volume_mount_path) - cmd = ["docker", "run", "-d", "-i", "-t", "--privileged", - "-v", "/dev:/dev", "--name=%s" % name, - "-v", path, image_name] + cmd = ["docker", "container", "create", "--name=%s" % name, + "--privileged", "-v", "/dev:/dev", "-v", path, image_name] try: result = self._inner_execute(cmd) + except (exception.ProcessExecutionError, OSError): + raise exception.ShareBackendException( + msg="Container %s failed to be created." % name) + + self.disconnect_network("bridge", name) + LOG.info("A container has been successfully created! Its id is %s.", + result[0].rstrip("\n")) + + def start_container(self, name): + cmd = ["docker", "container", "start", name] + + try: + self._inner_execute(cmd) except (exception.ProcessExecutionError, OSError): raise exception.ShareBackendException( msg="Container %s has failed to start." % name) - LOG.info("A container has been successfully started! Its id is " - "%s.", result[0].rstrip('\n')) + + LOG.info("Container %s successfully started!", name) def stop_container(self, name): LOG.debug("Stopping container %s.", name) @@ -106,20 +118,37 @@ class DockerExecHelper(driver.ExecuteMixin): LOG.debug("Execution result: %s.", result) return result - def fetch_container_address(self, name, address_family="inet6"): - result = self.execute( - name, - ["ip", "-oneline", - "-family", address_family, - "address", "show", "scope", "global", "dev", "eth0"], - ) - address_w_prefix = result[0].split()[3] - address = address_w_prefix.split('/')[0] - return address + def fetch_container_addresses(self, name, address_family="inet6"): + addresses = [] + interfaces = self.fetch_container_interfaces(name) + + for interface in interfaces: + result = self.execute( + name, + ["ip", "-oneline", + "-family", address_family, + "address", "show", "scope", "global", "dev", interface], + ) + address_w_prefix = result[0].split()[3] + addresses.append(address_w_prefix.split("/")[0]) + + return addresses + + def fetch_container_interfaces(self, name): + interfaces = [] + links = self.execute(name, ["ip", "-o", "link", "show"]) + links = links[0].rstrip().split("\n") + links = [link for link in links if link.split()[1].startswith("eth")] + + for link in links: + interface = re.search(" (.+?)@", link).group(1) + interfaces.append(interface) + + return interfaces def rename_container(self, name, new_name): - veth_name = self.find_container_veth(name) - if not veth_name: + veth_names = self.get_container_veths(name) + if not veth_names: raise exception.ManilaException( _("Could not find OVS information related to " "container %s.") % name) @@ -130,43 +159,22 @@ class DockerExecHelper(driver.ExecuteMixin): raise exception.ShareBackendException( msg="Could not rename container %s." % name) - cmd = ["ovs-vsctl", "set", "interface", veth_name, - "external-ids:manila-container=%s" % new_name] - try: - self._inner_execute(cmd) - except (exception.ProcessExecutionError, OSError): + for veth_name in veth_names: + cmd = ["ovs-vsctl", "set", "interface", veth_name, + "external-ids:manila-container=%s" % new_name] try: - self._inner_execute(["docker", "rename", new_name, name]) + self._inner_execute(cmd) except (exception.ProcessExecutionError, OSError): - msg = _("Could not rename back container %s.") % name - LOG.exception(msg) - raise exception.ShareBackendException( - msg="Could not update OVS information %s." % name) + try: + self._inner_execute(["docker", "rename", new_name, name]) + except (exception.ProcessExecutionError, OSError): + msg = _("Could not rename back container %s.") % name + LOG.exception(msg) + raise exception.ShareBackendException( + msg="Could not update OVS information %s." % name) LOG.info("Container %s has been successfully renamed.", name) - def find_container_veth(self, name): - interfaces = self._execute("ovs-vsctl", "list", "interface", - run_as_root=True)[0] - veths = set(re.findall("veth[0-9a-zA-Z]{7}", interfaces)) - manila_re = "manila-container=\"?.{%s}\"?" % len(name) - for veth in veths: - try: - iface_data = self._execute("ovs-vsctl", "list", "interface", - veth, run_as_root=True)[0] - except (exception.ProcessExecutionError, OSError) as e: - LOG.debug("Error listing interface %(veth)s. " - "Reason: %(reason)s", {'veth': veth, - 'reason': e}) - continue - - container_id = re.findall(manila_re, iface_data) - if container_id == []: - continue - elif container_id[0].split("manila-container=")[-1].split( - "manila_")[-1].strip('"') == name.split("manila_")[-1]: - return veth - def container_exists(self, name): result = self._execute("docker", "ps", "--no-trunc", @@ -175,3 +183,104 @@ class DockerExecHelper(driver.ExecuteMixin): if name == line.strip("'"): return True return False + + def create_network(self, network_name): + cmd = ["docker", "network", "create", network_name] + LOG.debug("Creating the %s Docker network.", network_name) + + try: + result = self._inner_execute(cmd) + except (exception.ProcessExecutionError, OSError): + raise exception.ShareBackendException( + msg="Docker network %s could not be created." % network_name) + + LOG.info("The Docker network has been successfully created! Its id is " + "%s.", result[0].rstrip("\n")) + + def remove_network(self, network_name): + cmd = ["docker", "network", "remove", network_name] + LOG.debug("Removing the %s Docker network.", network_name) + + try: + result = self._inner_execute(cmd) + except (exception.ProcessExecutionError, OSError): + raise exception.ShareBackendException( + msg="Docker network %s could not be removed. One or more " + "containers are probably still using it." % network_name) + + LOG.info("The %s Docker network has been successfully removed!", + result[0].rstrip("\n")) + + def connect_network(self, network_name, container_name): + cmd = ["docker", "network", "connect", network_name, container_name] + + try: + self._inner_execute(cmd) + except (exception.ProcessExecutionError, OSError): + raise exception.ShareBackendException( + msg="Could not connect the Docker network %s to container %s." + % (network_name, container_name)) + + LOG.info("Docker network %s has been successfully connected to " + "container %s!", network_name, container_name) + + def disconnect_network(self, network_name, container_name): + cmd = ["docker", "network", "disconnect", network_name, container_name] + + try: + self._inner_execute(cmd) + except (exception.ProcessExecutionError, OSError): + raise exception.ShareBackendException( + msg="Could not disconnect the Docker network %s from " + "container %s." % (network_name, container_name)) + + LOG.debug("Docker network %s has been successfully disconnected from " + "container %s!", network_name, container_name) + + def get_container_networks(self, container_name): + cmd = ["docker", "container", "inspect", "-f", + "'{{json .NetworkSettings.Networks}}'", container_name] + + try: + result = self._inner_execute(cmd) + except (exception.ProcessExecutionError, OSError): + raise exception.ShareBackendException( + msg="Could not find any networks associated with the %s " + "container." % container_name) + + # NOTE(ecsantos): The stdout from _inner_execute comes with extra + # single quotes. + networks = list(jsonutils.loads(result[0].strip("\n'"))) + return networks + + def get_container_veths(self, container_name): + veths = [] + cmd = ["bash", "-c", "cat /sys/class/net/eth*/iflink"] + eths_iflinks = self.execute(container_name, cmd) + + for eth_iflink in eths_iflinks[0].rstrip().split("\n"): + veth = self._execute("bash", "-c", "grep -l %s " + "/sys/class/net/veth*/ifindex" % eth_iflink) + veth = re.search("t/(.+?)/i", veth[0]).group(1) + veths.append(veth) + + return veths + + def get_network_bridge(self, network_name): + cmd = ["docker", "network", "inspect", "-f", "{{.Id}}", network_name] + + try: + network_id = self._inner_execute(cmd) + except (exception.ProcessExecutionError, OSError): + raise exception.ShareBackendException( + msg="Could not find the ID of the %s Docker network." + % network_name) + + # The name of the bridge associated with a given Docker network is + # always "br-" followed by the first 12 digits of that network's ID. + return "br-" + network_id[0][0:12] + + def get_veth_from_bridge(self, bridge): + veth = self._execute("ip", "link", "show", "master", bridge) + veth = re.search(" (.+?)@", veth[0]).group(1) + return veth diff --git a/manila/share/drivers/container/driver.py b/manila/share/drivers/container/driver.py index 9831e2fc54..4b51222f2a 100644 --- a/manila/share/drivers/container/driver.py +++ b/manila/share/drivers/container/driver.py @@ -21,11 +21,12 @@ be plugged into a Linux bridge. Also it is suggested that all interfaces willing to talk to each other reside in an OVS bridge.""" import math -import re from oslo_config import cfg from oslo_log import log +from oslo_serialization import jsonutils from oslo_utils import importutils +from oslo_utils import uuidutils from manila import exception from manila.i18n import _ @@ -99,6 +100,7 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): self.configuration.container_storage_helper)( configuration=self.configuration) self._helpers = {} + self.network_allocation_update_support = True def _get_helper(self, share): if share["share_proto"].upper() == "CIFS": @@ -130,7 +132,8 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): 'create_share_from_snapshot_support': False, 'driver_name': 'ContainerShareDriver', 'pools': self.storage.get_share_server_pools(), - 'security_service_update_support': True + 'security_service_update_support': True, + 'share_server_multiple_subnet_support': True, } super(ContainerShareDriver, self)._update_share_stats(data) @@ -219,23 +222,25 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): LOG.warning("neutron_host_id is not specified. This driver " "might not work as expected without it.") - def _connect_to_network(self, server_id, network_info, host_veth): + def _connect_to_network(self, server_id, network_info, host_veth, + host_bridge, iface): LOG.debug("Attempting to connect container to neutron network.") - network_allocation = network_info['network_allocations'][0] + network_allocation = network_info["network_allocations"][0] port_address = network_allocation.ip_address port_mac = network_allocation.mac_address port_id = network_allocation.id self.container.execute( server_id, - ["ifconfig", "eth0", port_address, "up"] + ["ifconfig", iface, port_address, "up"] ) self.container.execute( server_id, - ["ip", "link", "set", "dev", "eth0", "address", port_mac] + ["ip", "link", "set", "dev", iface, "address", port_mac] ) msg_helper = { - 'id': server_id, 'veth': host_veth, - 'lb': self.configuration.container_linux_bridge_name, + 'id': server_id, + 'veth': host_veth, + 'lb': host_bridge, 'ovsb': self.configuration.container_ovs_bridge_name, 'ip': port_address, 'network': network_info['neutron_net_id'], @@ -243,9 +248,7 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): } LOG.debug("Container %(id)s veth is %(veth)s.", msg_helper) LOG.debug("Removing %(veth)s from %(lb)s.", msg_helper) - self._execute("brctl", "delif", - self.configuration.container_linux_bridge_name, - host_veth, + self._execute("brctl", "delif", host_bridge, host_veth, run_as_root=True) LOG.debug("Plugging %(veth)s into %(ovsb)s.", msg_helper) @@ -264,61 +267,80 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): @utils.synchronized("container_driver_teardown_lock", external=True) def _teardown_server(self, *args, **kwargs): server_id = self._get_container_name(kwargs["server_details"]["id"]) - self.container.stop_container(server_id) - veth = self.container.find_container_veth(server_id) - if veth: + veths = self.container.get_container_veths(server_id) + networks = self.container.get_container_networks(server_id) + + for veth, network in zip(veths, networks): LOG.debug("Deleting veth %s.", veth) try: self._execute("ovs-vsctl", "--", "del-port", self.configuration.container_ovs_bridge_name, veth, run_as_root=True) except exception.ProcessExecutionError as e: - LOG.warning("Failed to delete port %s: port " - "vanished.", veth) + LOG.warning("Failed to delete port %s: port vanished.", veth) LOG.error(e) + self.container.disconnect_network(network, server_id) - def _get_veth_state(self): - result = self._execute("brctl", "show", - self.configuration.container_linux_bridge_name, - run_as_root=True) - veths = re.findall("veth.*\\n", result[0]) - veths = [x.rstrip('\n') for x in veths] - msg = ("The following veth interfaces are plugged into %s now: " % - self.configuration.container_linux_bridge_name) - LOG.debug(msg + ", ".join(veths)) - return veths + if network != "bridge": + self.container.remove_network(network) - def _get_corresponding_veth(self, before, after): - result = list(set(after) ^ set(before)) - if len(result) != 1: - raise exception.ManilaException(_("Multiple veths for container.")) - return result[0] + self.container.stop_container(server_id) + + def _setup_server_network(self, server_id, network_info): + existing_interfaces = self.container.fetch_container_interfaces( + server_id) + new_interfaces = [] + + # If the share server network allocations are being updated, create + # interfaces starting with ethX + 1. + if existing_interfaces: + ifnum_offset = len(existing_interfaces) + for ifnum, subnet in enumerate(network_info): + # TODO(ecsantos): Newer Ubuntu images (systemd >= 197) use + # predictable network interface names (e.g., enp3s0) instead of + # the classical kernel naming scheme (e.g., eth0). The + # Container driver currently uses an Ubuntu Xenial Docker + # image, so if it's updated in the future, these "eth" strings + # should also be updated. + new_interfaces.append("eth" + str(ifnum + ifnum_offset)) + # Otherwise (the share server was just created), create interfaces + # starting with eth0. + else: + for ifnum, subnet in enumerate(network_info): + new_interfaces.append("eth" + str(ifnum)) + + for new_interface, subnet in zip(new_interfaces, network_info): + network_name = "manila-docker-network-" + uuidutils.generate_uuid() + self.container.create_network(network_name) + self.container.connect_network(network_name, server_id) + + bridge = self.container.get_network_bridge(network_name) + veth = self.container.get_veth_from_bridge(bridge) + self._connect_to_network(server_id, subnet, veth, bridge, + new_interface) @utils.synchronized("veth-lock", external=True) def _setup_server(self, network_info, metadata=None): - # NOTE(felipe_rodrigues): keep legacy network_info support as a dict. - network_info = network_info[0] msg = "Creating share server '%s'." - server_id = self._get_container_name(network_info["server_id"]) + common_net_info = network_info[0] + server_id = self._get_container_name(common_net_info["server_id"]) LOG.debug(msg, server_id) - veths_before = self._get_veth_state() try: + self.container.create_container(server_id) self.container.start_container(server_id) except Exception as e: raise exception.ManilaException(_("Cannot create container: %s") % e) - security_services = network_info.get('security_services') + + self._setup_server_network(server_id, network_info) + security_services = common_net_info.get('security_services') if security_services: self.setup_security_services(server_id, security_services) - veths_after = self._get_veth_state() - - veth = self._get_corresponding_veth(veths_before, veths_after) - self._connect_to_network(server_id, network_info, veth) LOG.info("Container %s was created.", server_id) - return {"id": network_info["server_id"]} + return {"id": common_net_info["server_id"]} def _delete_export_and_umount_storage( self, share, server_id, share_name, ignore_errors=False): @@ -382,7 +404,7 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): location = self._create_export_and_mount_storage( share, server_id, new_share_name) - result = {'size': size, 'export_locations': [location]} + result = {'size': size, 'export_locations': location} LOG.info("Successfully managed share %(share)s, returning %(data)s", {'share': share.id, 'data': result}) return result @@ -393,7 +415,7 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): def get_share_server_network_info( self, context, share_server, identifier, driver_options): name = self._get_correct_container_old_name(identifier) - return [self.container.fetch_container_address(name, "inet")] + return self.container.fetch_container_addresses(name, "inet") def manage_server(self, context, share_server, identifier, driver_options): new_name = self._get_container_name(share_server['id']) @@ -470,7 +492,7 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): # Enables the access on the destination container destination_server_id = self._get_container_name( destination_share_server["id"]) - new_export_location = self._mount_storage( + new_export_locations = self._mount_storage( destination_share, destination_server_id, destination_share.share_id) @@ -485,7 +507,7 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): LOG.info(msg, msg_args) return { - 'export_locations': new_export_location, + 'export_locations': new_export_locations, } def share_server_migration_check_compatibility( @@ -547,11 +569,11 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): shares_updates = {} for destination_share in shares: share_id = destination_share.share_id - new_export_location = self._mount_storage( + new_export_locations = self._mount_storage( destination_share, destination_server_id, share_id) shares_updates[destination_share['id']] = { - 'export_locations': new_export_location, + 'export_locations': new_export_locations, 'pool_name': self.storage.get_share_pool_name(share_id), } @@ -661,3 +683,66 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): "'password'.") return False return True + + def _form_share_server_update_return(self, share_server, + current_network_allocations, + new_network_allocations, + share_instances): + server_id = self._get_container_name(share_server["id"]) + addresses = self.container.fetch_container_addresses(server_id, "inet") + share_updates = {} + subnet_allocations = {} + + for share_instance in share_instances: + export_locations = [] + for address in addresses: + # TODO(ecsantos): The Container driver currently only + # supports CIFS. If NFS support is implemented in the + # future, the path should be adjusted accordingly. + export_location = { + "is_admin_only": False, + "path": "//%(ip_address)s/%(share_id)s" % + { + "ip_address": address, + "share_id": share_instance["share_id"] + }, + "preferred": False + } + export_locations.append(export_location) + share_updates[share_instance["id"]] = export_locations + + for subnet in current_network_allocations["subnets"]: + for network_allocation in subnet["network_allocations"]: + subnet_allocations[network_allocation["id"]] = ( + network_allocation["ip_address"]) + + for network_allocation in ( + new_network_allocations["network_allocations"]): + subnet_allocations[network_allocation["id"]] = ( + network_allocation["ip_address"]) + + server_details = { + "subnet_allocations": jsonutils.dumps(subnet_allocations) + } + return { + "share_updates": share_updates, + "server_details": server_details + } + + def check_update_share_server_network_allocations( + self, context, share_server, current_network_allocations, + new_share_network_subnet, security_services, share_instances, + share_instances_rules): + LOG.debug("Share server %(server)s can be updated with allocations " + "from new subnet.", {"server": share_server["id"]}) + return True + + def update_share_server_network_allocations( + self, context, share_server, current_network_allocations, + new_network_allocations, security_services, share_instances, + snapshots): + server_id = self._get_container_name(share_server["id"]) + self._setup_server_network(server_id, [new_network_allocations]) + return self._form_share_server_update_return( + share_server, current_network_allocations, new_network_allocations, + share_instances) diff --git a/manila/share/drivers/container/protocol_helper.py b/manila/share/drivers/container/protocol_helper.py index aff9e6712d..48e47a7700 100644 --- a/manila/share/drivers/container/protocol_helper.py +++ b/manila/share/drivers/container/protocol_helper.py @@ -30,6 +30,7 @@ class DockerCIFSHelper(object): self.container = container_helper def create_share(self, server_id): + export_locations = [] share_name = self.share.share_id cmd = ["net", "conf", "addshare", share_name, "/shares/%s" % share_name, "writeable=y"] @@ -49,9 +50,20 @@ class DockerCIFSHelper(object): ["net", "conf", "setparm", share_name, param, value] ) # TODO(tbarron): pass configured address family when we support IPv6 - address = self.container.fetch_container_address( - server_id, address_family='inet') - return r"//%(addr)s/%(name)s" % {"addr": address, "name": share_name} + addresses = self.container.fetch_container_addresses( + server_id, address_family="inet") + for address in addresses: + export_location = { + "is_admin_only": False, + "path": "//%(ip_address)s/%(share_name)s" % + { + "ip_address": address, + "share_name": share_name + }, + "preferred": False + } + export_locations.append(export_location) + return export_locations def delete_share(self, server_id, share_name, ignore_errors=False): self.container.execute( diff --git a/manila/tests/share/drivers/container/fakes.py b/manila/tests/share/drivers/container/fakes.py index c7e8c375af..4d921801c8 100644 --- a/manila/tests/share/drivers/container/fakes.py +++ b/manila/tests/share/drivers/container/fakes.py @@ -66,6 +66,51 @@ FAKE_VSCTL_LIST_INTERFACE_4 = ( 'more fake stuff\n' ) +FAKE_IP_LINK_SHOW = ( + ('1: lo: mtu 65536 qdisc noqueue state UNKNOWN ' + 'mode DEFAULT group default qlen 1000\\ link/loopback ' + '00:00:00:00:00:00 brd 00:00:00:00:00:00\n' + '13: eth0@if16: mtu 1500 qdisc noqueue ' + 'state UP mode DEFAULT group default \\ link/ether 02:42:ac:15:00:02 ' + 'brd ff:ff:ff:ff:ff:ff\n' + '15: eth1@if14: mtu 1500 qdisc noqueue ' + 'state UP mode DEFAULT group default \\ link/ether 02:42:ac:14:00:02 ' + 'brd ff:ff:ff:ff:ff:ff\n', '') +) + +FAKE_IP_LINK_SHOW_MASTER = ( + ('16: fake_veth@if14: mtu 1500 qdisc ' + 'noqueue master br-a7d71c3e77c2 state UP mode DEFAULT group default\n' + ' link/ether 4a:10:0c:f2:d2:2c brd ff:ff:ff:ff:ff:ff link-netnsid 0\n', + '') +) + +FAKE_IP_ADDR_SHOW = ( + [('283: eth0 inet 192.168.144.19/24 brd 192.168.144.255 scope global ' + 'eth0\\ valid_lft forever preferred_lft forever', ''), + ('287: eth1 inet 10.0.0.131/8 brd 8.255.255.255 scope global eth1\\ ' + ' valid_lft forever preferred_lft forever', '')] +) + +FAKE_DOCKER_INSPECT_NETWORKS = ( + ('{"fake_docker_network_0":{"IPAMConfig":{},"Links":null,"Aliases":' + '["dab16d2703dc"],"NetworkID":' + '"cf8c7cb5cecda1ef8240921d5d09e2a1bf9e308a0261459f5a69114cd4e6283c",' + '"EndpointID":' + '"312a035f32be713c7b56093dde2beec950785ddeb29c9bd18018d43ffd4f64bd",' + '"Gateway":"10.10.10.1","IPAddress":"10.10.10.10","IPPrefixLen":24,' + '"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,' + '"MacAddress":"10:10:10:10:10:10","DriverOpts":{}},' + '"fake_docker_network_1":{"IPAMConfig":{},"Links":null,"Aliases":' + '["dab16d2703dc"],"NetworkID":' + '"e978d91d70c30695557018c8847a551267e99c083063391c07dc9a730bfef9dc",' + '"EndpointID":' + '"8e34044764cd52b9d092ac66af8fb7130cdd423b521c3bf6e57b8095f6f0a085",' + '"Gateway":"20.20.20.1","IPAddress":"20.20.20.20","IPPrefixLen":24,' + '"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,' + '"MacAddress":"20:20:20:20:20:20","DriverOpts":{}}}', '') +) + def fake_share(**kwargs): share = { @@ -81,6 +126,17 @@ def fake_share(**kwargs): return db_fakes.FakeModel(share) +def fake_share_instances(**kwargs): + share_instances = { + 'id': 'fakeid', + 'share_id': 'fakeshareid', + 'host': 'host@backend#vg', + 'export_location': '127.0.0.1:/mnt/nfs/volume-00002', + } + share_instances.update(kwargs) + return [db_fakes.FakeModel(share_instances)] + + def fake_access(**kwargs): access = { 'id': 'fakeaccid', @@ -102,10 +158,31 @@ def fake_network(**kwargs): 'server_id': 'fake_server_id', 'network_allocations': [allocations], 'neutron_net_id': 'fake_net', - 'neutron_subnet_id': 'fake_subnet', + 'neutron_subnet_id': 'fake_subnet' } network.update(kwargs) - return db_fakes.FakeModel(network) + return [db_fakes.FakeModel(network)] + + +def fake_network_with_security_services(**kwargs): + allocations = db_fakes.FakeModel({'id': 'fake_allocation_id', + 'ip_address': '127.0.0.0.1', + 'mac_address': 'fe:16:3e:61:e0:58'}) + security_services = db_fakes.FakeModel({'status': 'fake_status', + 'id': 'fake_security_service_id', + 'project_id': 'fake_project_id', + 'type': 'fake_type', + 'name': 'fake_name'}) + network = { + 'id': 'fake_network_id', + 'server_id': 'fake_server_id', + 'network_allocations': [allocations], + 'neutron_net_id': 'fake_net', + 'neutron_subnet_id': 'fake_subnet', + 'security_services': [security_services], + } + network.update(kwargs) + return [db_fakes.FakeModel(network)] def fake_share_server(**kwargs): @@ -126,3 +203,33 @@ def fake_share_no_export_location(**kwargs): } share.update(kwargs) return db_fakes.FakeModel(share) + + +def fake_current_network_allocations(): + current_network_allocations = { + 'subnets': [ + { + 'network_allocations': [ + { + 'id': 'fake_id_current', + 'ip_address': '192.168.144.100', + } + ] + } + ] + } + + return current_network_allocations + + +def fake_new_network_allocations(): + new_network_allocations = { + 'network_allocations': [ + { + 'id': 'fake_id_new', + 'ip_address': '10.0.0.100', + } + ] + } + + return new_network_allocations diff --git a/manila/tests/share/drivers/container/test_container_helper.py b/manila/tests/share/drivers/container/test_container_helper.py index 8f8ca68cae..cceb71d477 100644 --- a/manila/tests/share/drivers/container/test_container_helper.py +++ b/manila/tests/share/drivers/container/test_container_helper.py @@ -15,7 +15,6 @@ """Unit tests for the Container helper module.""" from unittest import mock -import uuid import ddt @@ -38,28 +37,50 @@ class DockerExecHelperTestCase(test.TestCase): self.DockerExecHelper = container_helper.DockerExecHelper( configuration=self.fake_conf) + def test_create_container(self): + fake_name = 'fake_container' + self.DockerExecHelper.configuration.container_image_name = 'fake_image' + + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(return_value=('fake_container_id', ''))) + self.mock_object(self.DockerExecHelper, 'disconnect_network') + + self.DockerExecHelper.create_container(fake_name) + + self.DockerExecHelper._inner_execute.assert_called_once_with([ + 'docker', 'container', 'create', '--name=%s' % fake_name, + '--privileged', '-v', '/dev:/dev', '-v', '/tmp/shares:/shares', + 'fake_image']) + self.DockerExecHelper.disconnect_network.assert_called_once_with( + 'bridge', fake_name) + + def test_create_container_failure(self): + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(side_effect=OSError())) + + self.assertRaises(exception.ShareBackendException, + self.DockerExecHelper.create_container) + def test_start_container(self): - self.mock_object(self.DockerExecHelper, "_inner_execute", - mock.Mock(return_value=['fake_output', ''])) - uuid.uuid1 = mock.Mock(return_value='') - expected = ['docker', 'run', '-d', '-i', '-t', '--privileged', '-v', - '/dev:/dev', '--name=manila_cifs_docker_container', '-v', - '/tmp/shares:/shares', 'fake_image'] + fake_name = 'fake_container' - self.DockerExecHelper.start_container() + self.mock_object(self.DockerExecHelper, '_inner_execute', mock.Mock()) - self.DockerExecHelper._inner_execute.assert_called_once_with(expected) + self.DockerExecHelper.start_container(fake_name) + + self.DockerExecHelper._inner_execute.assert_called_once_with([ + 'docker', 'container', 'start', 'fake_container']) def test_start_container_impossible_failure(self): self.mock_object(self.DockerExecHelper, "_inner_execute", mock.Mock(side_effect=OSError())) self.assertRaises(exception.ShareBackendException, - self.DockerExecHelper.start_container) + self.DockerExecHelper.start_container, None) def test_stop_container(self): self.mock_object(self.DockerExecHelper, "_inner_execute", - mock.Mock(return_value=['fake_output', ''])) + mock.Mock(return_value=['fake_output', None])) expected = ['docker', 'stop', 'manila-fake-conainer'] self.DockerExecHelper.stop_container("manila-fake-conainer") @@ -120,153 +141,101 @@ class DockerExecHelperTestCase(test.TestCase): self.assertIsNone(result) - @ddt.data(('inet', - "192.168.0.254", - ["5: br0 inet 192.168.0.254/24 brd 192.168.0.255 " - "scope global br0 valid_lft forever preferred_lft forever"]), - ("inet6", - "2001:470:8:c82:6600:6aff:fe84:8dda", - ["5: br0 inet6 2001:470:8:c82:6600:6aff:fe84:8dda/64 " - "scope global valid_lft forever preferred_lft forever"]), - ) - @ddt.unpack - def test_fetch_container_address(self, address_family, expected_address, - return_value): - fake_name = "fakeserver" - mock_execute = self.DockerExecHelper.execute = mock.Mock( - return_value=return_value) + def test_fetch_container_addresses(self): + fake_name = 'fake_container' + fake_addresses = ['192.168.144.19', '10.0.0.131'] + fake_ip_addr_show = fakes.FAKE_IP_ADDR_SHOW + fake_interfaces = ['eth0', 'eth1'] - address = self.DockerExecHelper.fetch_container_address( - fake_name, - address_family) + self.mock_object(self.DockerExecHelper, 'fetch_container_interfaces', + mock.Mock(return_value=fake_interfaces)) + self.mock_object(self.DockerExecHelper, 'execute', + mock.Mock(side_effect=[fake_ip_addr_show[0], + fake_ip_addr_show[1]])) - self.assertEqual(expected_address, address) - mock_execute.assert_called_once_with( - fake_name, ["ip", "-oneline", "-family", address_family, "address", - "show", "scope", "global", "dev", "eth0"] + self.assertEqual(fake_addresses, + self.DockerExecHelper.fetch_container_addresses( + fake_name, 'inet')) + (self.DockerExecHelper.fetch_container_interfaces + .assert_called_once_with(fake_name)) + self.DockerExecHelper.execute.assert_any_call( + fake_name, ['ip', '-oneline', '-family', 'inet', 'address', + 'show', 'scope', 'global', 'dev', 'eth0'] ) + self.DockerExecHelper.execute.assert_any_call( + fake_name, ['ip', '-oneline', '-family', 'inet', 'address', + 'show', 'scope', 'global', 'dev', 'eth1'] + ) + + def test_fetch_container_interfaces(self): + fake_name = 'fake_container' + fake_eths = fakes.FAKE_IP_LINK_SHOW + + self.mock_object(self.DockerExecHelper, 'execute', + mock.Mock(return_value=fake_eths)) + + self.assertEqual( + ['eth0', 'eth1'], + self.DockerExecHelper.fetch_container_interfaces(fake_name)) + self.DockerExecHelper.execute.assert_called_once_with( + fake_name, ['ip', '-o', 'link', 'show']) def test_rename_container(self): + fake_old_name = 'old_name' + fake_new_name = 'new_name' + fake_veth_names = ['fake_veth'] - fake_old_name = "old_name" - fake_new_name = "new_name" - fake_veth_name = "veth_fake" - self.DockerExecHelper.find_container_veth = mock.Mock( - return_value=fake_veth_name) - mock__inner_execute = self.DockerExecHelper._inner_execute = mock.Mock( - return_value=['fake', '']) + self.mock_object(self.DockerExecHelper, 'get_container_veths', + mock.Mock(return_value=fake_veth_names)) + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(side_effect=[None, None])) self.DockerExecHelper.rename_container(fake_old_name, fake_new_name) - self.DockerExecHelper.find_container_veth.assert_called_once_with( - fake_old_name - ) - mock__inner_execute.assert_has_calls([ - mock.call(["docker", "rename", fake_old_name, fake_new_name]), - mock.call(["ovs-vsctl", "set", "interface", fake_veth_name, - "external-ids:manila-container=%s" % fake_new_name]) - ]) + + self.DockerExecHelper.get_container_veths.assert_called_once_with( + fake_old_name) + self.DockerExecHelper._inner_execute.assert_has_calls([ + mock.call(['docker', 'rename', fake_old_name, fake_new_name]), + mock.call(['ovs-vsctl', 'set', 'interface', fake_veth_names[0], + 'external-ids:manila-container=%s' % fake_new_name])]) def test_rename_container_exception_veth(self): + fake_old_name = 'old_name' + fake_new_name = 'new_name' - self.DockerExecHelper.find_container_veth = mock.Mock( - return_value=None) + self.mock_object(self.DockerExecHelper, 'get_container_veths', + mock.Mock(return_value=[])) self.assertRaises(exception.ManilaException, self.DockerExecHelper.rename_container, - "old_name", "new_name") + fake_old_name, fake_new_name) @ddt.data([['fake', ''], OSError, ['fake', '']], [['fake', ''], OSError, OSError], [OSError]) def test_rename_container_exception_cmds(self, side_effect): - fake_old_name = "old_name" - fake_new_name = "new_name" - fake_veth_name = "veth_fake" + fake_old_name = 'old_name' + fake_new_name = 'new_name' + fake_veth_names = ['fake_veth'] - self.DockerExecHelper.find_container_veth = mock.Mock( - return_value=fake_veth_name) - mock__inner_execute = self.DockerExecHelper._inner_execute = mock.Mock( - side_effect=side_effect) + self.mock_object(self.DockerExecHelper, 'get_container_veths', + mock.Mock(return_value=fake_veth_names)) + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(side_effect=side_effect)) self.assertRaises(exception.ShareBackendException, self.DockerExecHelper.rename_container, fake_old_name, fake_new_name) if len(side_effect) > 1: - mock__inner_execute.assert_has_calls([ - mock.call(["docker", "rename", fake_old_name, fake_new_name]), - mock.call(["ovs-vsctl", "set", "interface", fake_veth_name, - "external-ids:manila-container=%s" % fake_new_name]) + self.DockerExecHelper._inner_execute.assert_has_calls([ + mock.call(['docker', 'rename', fake_old_name, fake_new_name]), + mock.call(['ovs-vsctl', 'set', 'interface', fake_veth_names[0], + 'external-ids:manila-container=%s' % fake_new_name]) ]) else: - mock__inner_execute.assert_has_calls([ - mock.call(["docker", "rename", fake_old_name, fake_new_name]), - ]) - - @ddt.data('my_container', 'manila_my_container') - def test_find_container_veth(self, name): - - interfaces = [fakes.FAKE_VSCTL_LIST_INTERFACE_1, - fakes.FAKE_VSCTL_LIST_INTERFACE_2, - fakes.FAKE_VSCTL_LIST_INTERFACE_4] - - if 'manila_' in name: - list_interfaces = [fakes.FAKE_VSCTL_LIST_INTERFACES] - interfaces.append(fakes.FAKE_VSCTL_LIST_INTERFACE_3) - else: - list_interfaces = [fakes.FAKE_VSCTL_LIST_INTERFACES_X] - interfaces.append(fakes.FAKE_VSCTL_LIST_INTERFACE_3_X) - - def get_interface_data_according_to_veth(*args, **kwargs): - if len(args) == 4: - for interface in interfaces: - if args[3] in interface: - return [interface] - else: - return list_interfaces - - self.DockerExecHelper._execute = mock.Mock( - side_effect=get_interface_data_according_to_veth) - - result = self.DockerExecHelper.find_container_veth(name) - - self.assertEqual("veth3jd83j7", result) - - @ddt.data(True, False) - def test_find_container_veth_not_found(self, remove_veth): - - if remove_veth: - list_executes = [[fakes.FAKE_VSCTL_LIST_INTERFACES], - [fakes.FAKE_VSCTL_LIST_INTERFACE_1], - OSError, - [fakes.FAKE_VSCTL_LIST_INTERFACE_3], - [fakes.FAKE_VSCTL_LIST_INTERFACE_4]] - else: - list_executes = [[fakes.FAKE_VSCTL_LIST_INTERFACES], - [fakes.FAKE_VSCTL_LIST_INTERFACE_1], - [fakes.FAKE_VSCTL_LIST_INTERFACE_2], - [fakes.FAKE_VSCTL_LIST_INTERFACE_3], - [fakes.FAKE_VSCTL_LIST_INTERFACE_4]] - - self.DockerExecHelper._execute = mock.Mock( - side_effect=list_executes) - list_veths = ['veth11b2c34', 'veth25f6g7h', 'veth3jd83j7', - 'veth4i9j10k'] - - self.assertIsNone( - self.DockerExecHelper.find_container_veth("foo_bar")) - list_calls = [mock.call("ovs-vsctl", "list", "interface", - run_as_root=True)] - - for veth in list_veths: - list_calls.append( - mock.call("ovs-vsctl", "list", "interface", veth, - run_as_root=True) - ) - - self.DockerExecHelper._execute.assert_has_calls( - list_calls, any_order=True - ) + self.DockerExecHelper._inner_execute.assert_has_calls([ + mock.call(['docker', 'rename', fake_old_name, fake_new_name])]) @ddt.data((["wrong_name\nfake\nfake_container\nfake_name'"], True), (["wrong_name\nfake_container\nfake'"], False), @@ -283,3 +252,162 @@ class DockerExecHelperTestCase(test.TestCase): "docker", "ps", "--no-trunc", "--format='{{.Names}}'", run_as_root=True) self.assertEqual(expected_result, result) + + def test_create_network(self): + fake_network_name = 'fake_network_name' + + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(return_value=('fake_network_id', ''))) + + self.DockerExecHelper.create_network(fake_network_name) + + self.DockerExecHelper._inner_execute.assert_called_once_with([ + 'docker', 'network', 'create', fake_network_name]) + + def test_create_network_failure(self): + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(side_effect=OSError())) + + self.assertRaises(exception.ShareBackendException, + self.DockerExecHelper.create_network, None) + + def test_remove_network(self): + fake_network_name = 'fake_network_name' + + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(return_value=('fake_network_id', ''))) + + self.DockerExecHelper.remove_network(fake_network_name) + + self.DockerExecHelper._inner_execute.assert_called_once_with([ + 'docker', 'network', 'remove', fake_network_name]) + + def test_remove_network_failure(self): + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(side_effect=OSError())) + + self.assertRaises(exception.ShareBackendException, + self.DockerExecHelper.remove_network, None) + + def test_connect_network(self): + fake_network_name = 'fake_network_name' + fake_server_id = 'fake_server_id' + + self.mock_object(self.DockerExecHelper, '_inner_execute') + + self.DockerExecHelper.connect_network(fake_network_name, + fake_server_id) + + self.DockerExecHelper._inner_execute.assert_called_once_with([ + 'docker', 'network', 'connect', fake_network_name, fake_server_id]) + + def test_connect_network_failure(self): + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(side_effect=OSError())) + + self.assertRaises(exception.ShareBackendException, + self.DockerExecHelper.connect_network, None, None) + + def test_disconnect_network(self): + fake_network_name = 'fake_network_name' + fake_server_id = 'fake_server_id' + + self.mock_object(self.DockerExecHelper, '_inner_execute') + + self.DockerExecHelper.disconnect_network(fake_network_name, + fake_server_id) + + self.DockerExecHelper._inner_execute.assert_called_once_with([ + 'docker', 'network', 'disconnect', fake_network_name, + fake_server_id]) + + def test_disconnect_network_failure(self): + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(side_effect=OSError())) + + self.assertRaises(exception.ShareBackendException, + self.DockerExecHelper.disconnect_network, None, None) + + def test_get_container_networks(self): + fake_container_name = 'fake_container_name' + fake_docker_inspect_networks = fakes.FAKE_DOCKER_INSPECT_NETWORKS + fake_networks = ['fake_docker_network_0', 'fake_docker_network_1'] + + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(return_value=fake_docker_inspect_networks)) + + self.assertEqual( + fake_networks, + self.DockerExecHelper.get_container_networks(fake_container_name)) + self.DockerExecHelper._inner_execute.assert_called_once_with([ + 'docker', 'container', 'inspect', '-f', + '\'{{json .NetworkSettings.Networks}}\'', fake_container_name]) + + def test_get_container_networks_failure(self): + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(side_effect=OSError())) + + self.assertRaises(exception.ShareBackendException, + self.DockerExecHelper.get_container_networks, None) + + def test_get_container_veths(self): + fake_container_name = 'fake_container_name' + fake_eths_iflinks = ('10\n11\n', '') + fake_veths = ['fake_veth_0', 'fake_veth_1'] + + self.mock_object(self.DockerExecHelper, 'execute', + mock.Mock(return_value=fake_eths_iflinks)) + self.mock_object( + self.DockerExecHelper, '_execute', + mock.Mock(side_effect=[('/sys/class/net/%s/ifindex' + % fake_veths[0], ''), + ('/sys/class/net/%s/ifindex' + % fake_veths[1], '')])) + + self.assertEqual( + fake_veths, + self.DockerExecHelper.get_container_veths(fake_container_name)) + self.DockerExecHelper.execute.assert_called_once_with( + fake_container_name, + ['bash', '-c', 'cat /sys/class/net/eth*/iflink']) + self.DockerExecHelper._execute.assert_has_calls([ + mock.call('bash', '-c', 'grep -l 10 /sys/class/net/veth*/ifindex'), + mock.call('bash', '-c', 'grep -l 11 /sys/class/net/veth*/ifindex') + ]) + + def test_get_network_bridge(self): + fake_network_name = 'fake_network_name' + fake_network_id = ('012345abcdef', '') + fake_bridge = 'br-' + fake_network_id[0] + + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(return_value=fake_network_id)) + + self.assertEqual( + fake_bridge, + self.DockerExecHelper.get_network_bridge(fake_network_name)) + self.DockerExecHelper._inner_execute.assert_called_once_with([ + 'docker', 'network', 'inspect', '-f', '{{.Id}}', fake_network_name + ]) + + def test_get_network_bridge_failure(self): + self.mock_object(self.DockerExecHelper, '_inner_execute', + mock.Mock(side_effect=OSError())) + + self.assertRaises(exception.ShareBackendException, + self.DockerExecHelper.get_network_bridge, None) + + def test_get_veth_from_bridge(self): + fake_bridge = 'br-012345abcdef' + fake_ip_link_show_master = fakes.FAKE_IP_LINK_SHOW_MASTER + fake_veth = 'fake_veth' + + self.mock_object(self.DockerExecHelper, '_execute', + mock.Mock(return_value=fake_ip_link_show_master)) + + self.assertEqual( + fake_veth, self.DockerExecHelper.get_veth_from_bridge(fake_bridge)) + self.DockerExecHelper._execute.assert_called_once_with('ip', 'link', + 'show', + 'master', + fake_bridge) diff --git a/manila/tests/share/drivers/container/test_driver.py b/manila/tests/share/drivers/container/test_driver.py index 2a3bd78f4c..06e8161f18 100644 --- a/manila/tests/share/drivers/container/test_driver.py +++ b/manila/tests/share/drivers/container/test_driver.py @@ -19,6 +19,7 @@ from unittest import mock import ddt from oslo_config import cfg +from oslo_serialization import jsonutils from manila.common import constants as const from manila import context @@ -305,29 +306,34 @@ class ContainerShareDriverTestCase(test.TestCase): self.assertTrue(driver.LOG.warning.called) def test__connect_to_network(self): - network_info = cont_fakes.fake_network() + network_info = cont_fakes.fake_network()[0] helper = mock.Mock() self.mock_object(self._driver, "_execute", mock.Mock(return_value=helper)) self.mock_object(self._driver.container, "execute") self._driver._connect_to_network("fake-server", network_info, - "fake-veth") + "fake-veth", "fake-host-bridge", + "fake0") - @ddt.data({'veth': "fake_veth", 'exception': None}, - {'veth': "fake_veth", 'exception': + @ddt.data({'veth': ["fake_veth"], 'exception': None}, + {'veth': ["fake_veth"], 'exception': exception.ProcessExecutionError('fake')}, - {'veth': None, 'exception': None}) + {'veth': ["fake_veth"], 'exception': None}) @ddt.unpack def test__teardown_server(self, veth, exception): fake_server_details = {"id": "b5afb5c1-6011-43c4-8a37-29820e6951a7"} + fake_networks = ["fake_docker_network_0"] container_name = self._driver._get_container_name( fake_server_details['id']) mock_stop_container = self.mock_object( self._driver.container, "stop_container") - mock_find_container = self.mock_object( - self._driver.container, "find_container_veth", + mock_get_container_veths = self.mock_object( + self._driver.container, "get_container_veths", mock.Mock(return_value=veth)) + mock_get_container_networks = self.mock_object( + self._driver.container, "get_container_networks", + mock.Mock(return_value=fake_networks)) mock_execute = self.mock_object(self._driver, "_execute", mock.Mock(side_effect=exception)) @@ -337,42 +343,92 @@ class ContainerShareDriverTestCase(test.TestCase): mock_stop_container.assert_called_once_with( container_name ) - mock_find_container.assert_called_once_with( + mock_get_container_veths.assert_called_once_with( + container_name + ) + mock_get_container_networks.assert_called_once_with( container_name ) if exception is None and veth is not None: mock_execute.assert_called_once_with( "ovs-vsctl", "--", "del-port", - self._driver.configuration.container_ovs_bridge_name, veth, + self._driver.configuration.container_ovs_bridge_name, veth[0], run_as_root=True) - def test__get_veth_state(self): - retval = ('veth0000000\n', '') - self.mock_object(self._driver, "_execute", - mock.Mock(return_value=retval)) + def test__setup_server_network(self): + fake_server_id = 'fake_container_id' + fake_network_info = cont_fakes.fake_network() + fake_existing_interfaces = [] + fake_bridge = 'br-012345abcdef' + fake_veth = 'fake_veth' - result = self._driver._get_veth_state() + self.mock_object(self._driver.container, 'fetch_container_interfaces', + mock.Mock(return_value=fake_existing_interfaces)) + self.mock_object(driver.uuidutils, 'generate_uuid', + mock.Mock(return_value='fakeuuid')) + self.mock_object(self._driver.container, 'create_network') + self.mock_object(self._driver.container, 'connect_network') + self.mock_object(self._driver.container, 'get_network_bridge', + mock.Mock(return_value=fake_bridge)) + self.mock_object(self._driver.container, 'get_veth_from_bridge', + mock.Mock(return_value=fake_veth)) + self.mock_object(self._driver, '_connect_to_network') - self.assertEqual(['veth0000000'], result) + self._driver._setup_server_network(fake_server_id, fake_network_info) - def test__get_corresponding_veth_ok(self): - before = ['veth0000000'] - after = ['veth0000000', 'veth0000001'] + (self._driver.container.fetch_container_interfaces + .assert_called_once_with(fake_server_id)) + self._driver.container.create_network.assert_called_with( + 'manila-docker-network-fakeuuid') + self._driver.container.connect_network.assert_called_with( + 'manila-docker-network-fakeuuid', + fake_server_id) + self._driver.container.get_network_bridge.assert_called_with( + 'manila-docker-network-fakeuuid') + self._driver.container.get_veth_from_bridge.assert_called_with( + fake_bridge) + self._driver._connect_to_network.assert_called_with( + fake_server_id, fake_network_info[0], fake_veth, fake_bridge, + 'eth0') - result = self._driver._get_corresponding_veth(before, after) + def test__setup_server_network_existing_interfaces(self): + fake_server_id = 'fake_container_id' + fake_network_info = cont_fakes.fake_network() + fake_existing_interfaces = cont_fakes.FAKE_IP_LINK_SHOW + fake_bridge = 'br-012345abcdef' + fake_veth = 'fake_veth' - self.assertEqual('veth0000001', result) + self.mock_object(self._driver.container, 'fetch_container_interfaces', + mock.Mock(return_value=fake_existing_interfaces)) + self.mock_object(driver.uuidutils, 'generate_uuid', + mock.Mock(return_value='fakeuuid')) + self.mock_object(self._driver.container, 'create_network') + self.mock_object(self._driver.container, 'connect_network') + self.mock_object(self._driver.container, 'get_network_bridge', + mock.Mock(return_value=fake_bridge)) + self.mock_object(self._driver.container, 'get_veth_from_bridge', + mock.Mock(return_value=fake_veth)) + self.mock_object(self._driver, '_connect_to_network') - def test__get_corresponding_veth_raises(self): - before = ['veth0000000'] - after = ['veth0000000', 'veth0000001', 'veth0000002'] + self._driver._setup_server_network(fake_server_id, fake_network_info) - self.assertRaises(exception.ManilaException, - self._driver._get_corresponding_veth, - before, after) + (self._driver.container.fetch_container_interfaces + .assert_called_once_with(fake_server_id)) + self._driver.container.create_network.assert_called_with( + 'manila-docker-network-fakeuuid') + self._driver.container.connect_network.assert_called_with( + 'manila-docker-network-fakeuuid', + fake_server_id) + self._driver.container.get_network_bridge.assert_called_with( + 'manila-docker-network-fakeuuid') + self._driver.container.get_veth_from_bridge.assert_called_with( + fake_bridge) + self._driver._connect_to_network.assert_called_with( + fake_server_id, fake_network_info[0], fake_veth, fake_bridge, + 'eth2') def test__setup_server_container_fails(self): - network_info = [cont_fakes.fake_network()] + network_info = cont_fakes.fake_network() self.mock_object(self._driver.container, 'start_container') self._driver.container.start_container.side_effect = KeyError() @@ -380,21 +436,37 @@ class ContainerShareDriverTestCase(test.TestCase): self._driver._setup_server, network_info) def test__setup_server_ok(self): - network_info = [cont_fakes.fake_network()] - server_id = self._driver._get_container_name( - network_info[0]["server_id"]) - self.mock_object(self._driver.container, 'start_container') - self.mock_object(self._driver, '_get_veth_state') - self.mock_object(self._driver, '_get_corresponding_veth', - mock.Mock(return_value='veth0')) - self.mock_object(self._driver, '_connect_to_network') + fake_network_info = cont_fakes.fake_network() - self.assertEqual(network_info[0]['server_id'], - self._driver._setup_server(network_info)['id']) + self.mock_object(self._driver, '_get_container_name', + mock.Mock(return_value='fake_server_id')) + self.mock_object(self._driver.container, 'create_container') + self.mock_object(self._driver.container, 'start_container') + self.mock_object(self._driver, '_setup_server_network') + + self.assertEqual(fake_network_info[0]['server_id'], + self._driver._setup_server(fake_network_info)['id']) + self._driver._get_container_name.assert_called_once_with( + fake_network_info[0]['server_id']) + self._driver.container.create_container.assert_called_once_with( + 'fake_server_id') self._driver.container.start_container.assert_called_once_with( - server_id) - self._driver._connect_to_network.assert_called_once_with( - server_id, network_info[0], 'veth0') + 'fake_server_id') + self._driver._setup_server_network.assert_called_once_with( + fake_network_info[0]['server_id'], fake_network_info) + + def test__setup_server_security_services(self): + fake_network_info = cont_fakes.fake_network_with_security_services() + + self.mock_object(self._driver, '_get_container_name') + self.mock_object(self._driver.container, 'create_container') + self.mock_object(self._driver.container, 'start_container') + self.mock_object(self._driver, '_setup_server_network') + self.mock_object(self._driver, 'setup_security_services') + + self._driver._setup_server(fake_network_info) + + self._driver.setup_security_services.assert_called_once() def test_manage_existing(self): @@ -402,7 +474,7 @@ class ContainerShareDriverTestCase(test.TestCase): fake_export_location = 'export_location' expected_result = { 'size': 1, - 'export_locations': [fake_export_location] + 'export_locations': fake_export_location } fake_share_server = cont_fakes.fake_share() fake_share_name = self._driver._get_share_name(self.share) @@ -461,13 +533,10 @@ class ContainerShareDriverTestCase(test.TestCase): fake_id = cont_fakes.fake_identifier() expected_result = ['veth11b2c34'] - interfaces = [cont_fakes.FAKE_VSCTL_LIST_INTERFACE_1, - cont_fakes.FAKE_VSCTL_LIST_INTERFACE_2, - cont_fakes.FAKE_VSCTL_LIST_INTERFACE_4, - cont_fakes.FAKE_VSCTL_LIST_INTERFACE_3] - - self.mock_object(self._driver.container, 'execute', - mock.Mock(return_value=interfaces)) + self.mock_object(self._driver, '_get_correct_container_old_name', + mock.Mock(return_value=fake_id)) + self.mock_object(self._driver.container, 'fetch_container_addresses', + mock.Mock(return_value=expected_result)) result = self._driver.get_share_server_network_info(self._context, fake_share_server, @@ -717,3 +786,77 @@ class ContainerShareDriverTestCase(test.TestCase): self._context, share_server, network_info, share_instances, share_instance_access_rules, new_security_service, current_security_service=current_security_service) + + def test__form_share_server_update_return(self): + fake_share_server = cont_fakes.fake_share_server() + fake_current_network_allocations = ( + cont_fakes.fake_current_network_allocations()) + fake_new_network_allocations = ( + cont_fakes.fake_new_network_allocations()) + fake_share_instances = cont_fakes.fake_share_instances() + fake_server_id = 'fake_container_id' + fake_addresses = ['192.168.144.100', '10.0.0.100'] + fake_subnet_allocations = { + 'fake_id_current': '192.168.144.100', + 'fake_id_new': '10.0.0.100' + } + fake_share_updates = { + 'fakeid': [ + { + 'is_admin_only': False, + 'path': '//%s/fakeshareid' % fake_addresses[0], + 'preferred': False + }, + { + 'is_admin_only': False, + 'path': '//%s/fakeshareid' % fake_addresses[1], + 'preferred': False + } + ] + } + fake_server_details = { + 'subnet_allocations': jsonutils.dumps(fake_subnet_allocations) + } + fake_return = { + 'share_updates': fake_share_updates, + 'server_details': fake_server_details + } + + self.mock_object(self._driver, '_get_container_name', + mock.Mock(return_value=fake_server_id)) + self.mock_object(self._driver.container, 'fetch_container_addresses', + mock.Mock(return_value=fake_addresses)) + + self.assertEqual( + fake_return, self._driver._form_share_server_update_return( + fake_share_server, fake_current_network_allocations, + fake_new_network_allocations, fake_share_instances)) + self._driver._get_container_name.assert_called_once_with( + fake_share_server['id']) + (self._driver.container.fetch_container_addresses + .assert_called_once_with(fake_server_id, 'inet')) + + def test_check_update_share_server_network_allocations(self): + fake_share_server = cont_fakes.fake_share_server() + self.mock_object(driver.LOG, 'debug') + + self.assertTrue( + self._driver.check_update_share_server_network_allocations( + None, fake_share_server, None, None, None, None, None)) + self.assertTrue(driver.LOG.debug.called) + + def test_update_share_server_network_allocations(self): + fake_share_server = cont_fakes.fake_share_server() + fake_server_id = 'fake_container_id' + fake_return = 'fake_return' + + self.mock_object(self._driver, '_get_container_name', + mock.Mock(return_value=fake_server_id)) + self.mock_object(self._driver, '_setup_server_network') + self.mock_object(self._driver, '_form_share_server_update_return', + mock.Mock(return_value=fake_return)) + + self.assertEqual(fake_return, + self._driver.update_share_server_network_allocations( + None, fake_share_server, None, None, None, None, + None)) diff --git a/manila/tests/share/drivers/container/test_protocol_helper.py b/manila/tests/share/drivers/container/test_protocol_helper.py index 44c8c03abb..2247118355 100644 --- a/manila/tests/share/drivers/container/test_protocol_helper.py +++ b/manila/tests/share/drivers/container/test_protocol_helper.py @@ -65,6 +65,9 @@ class DockerCIFSHelperTestCase(test.TestCase): self.fake_exec_sync, execute_arguments=actual_arguments, ret_val=" fake 192.0.2.2/24 more fake \n" * 20) self.DockerCIFSHelper.share = fake_share() + self.mock_object(self.DockerCIFSHelper.container, + 'fetch_container_addresses', + mock.Mock(return_value=['192.0.2.2'])) self.DockerCIFSHelper.create_share("fakeserver") @@ -91,6 +94,9 @@ class DockerCIFSHelperTestCase(test.TestCase): self.fake_exec_sync, execute_arguments=actual_arguments, ret_val=" fake 192.0.2.2/24 more fake \n" * 20) self.DockerCIFSHelper.share = fake_share() + self.mock_object(self.DockerCIFSHelper.container, + 'fetch_container_addresses', + mock.Mock(return_value=['192.0.2.2'])) self.DockerCIFSHelper.create_share("fakeserver") diff --git a/releasenotes/notes/container-multiple-subnets-per-az-702aad41d6f91b59.yaml b/releasenotes/notes/container-multiple-subnets-per-az-702aad41d6f91b59.yaml new file mode 100644 index 0000000000..a0ba09a69a --- /dev/null +++ b/releasenotes/notes/container-multiple-subnets-per-az-702aad41d6f91b59.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + The Container driver is now able to: + + - Create shares using share networks that have multiple share network + subnets in the same availability zone. + - Add more network interfaces into share servers that are already + deployed based on the share network subnets within the share network.