Container: Multiple subnets per AZ

This change gives the Container driver the ability to create share
servers that span multiple subnets in the same availability zone, where
previously it was only possible to do so with one subnet per
availability zone. It also enables the driver to create new subnets on
share networks that already have shares exported on them.

Some relevant changes in the driver behavior:

- container creation is now separate from its start
- containers no longer use the default Docker network; instead,
  dedicated networks are created (one for each subnet)
- fetch_container_addresses now returns a list of addresses
- find_container_veth, _get_veth_state, _get_corresponding_veth were
  removed
- these new methods were written:

  - start_container
  - fetch_container_interfaces
  - create_network
  - remove_network
  - connect_network
  - disconnect_network
  - get_container_networks
  - get_container_veths
  - get_network_bridge
  - get_veth_from_bridge
  - _setup_server_network

DocImpact
Depends-On: I7de9de4ae509182e9494bba604979cce03acceec
Partially-Implements: blueprint multiple-share-network-subnets
Signed-off-by: Eduardo Santos <eduardo.experimental@gmail.com>
Change-Id: I81a616b57c95508f30d872383f5c2d68718950d7
This commit is contained in:
Eduardo Santos 2022-01-26 10:08:57 -03:00 committed by Fernando Ferraz
parent 2b57d15c64
commit 2dc60a0b4e
9 changed files with 946 additions and 346 deletions

View File

@ -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``

View File

@ -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

View File

@ -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)

View File

@ -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(

View File

@ -66,6 +66,51 @@ FAKE_VSCTL_LIST_INTERFACE_4 = (
'more fake stuff\n'
)
FAKE_IP_LINK_SHOW = (
('1: lo: <LOOPBACK,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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

View File

@ -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)

View File

@ -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))

View File

@ -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")

View File

@ -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.