176 lines
7.6 KiB
Python
176 lines
7.6 KiB
Python
# Copyright (c) 2016 Mirantis, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import re
|
|
import uuid
|
|
|
|
from oslo_log import log
|
|
from oslo_utils import excutils
|
|
|
|
from manila import exception
|
|
from manila.i18n import _
|
|
from manila.share import driver
|
|
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class DockerExecHelper(driver.ExecuteMixin):
|
|
def __init__(self, *args, **kwargs):
|
|
self.configuration = kwargs.pop("configuration", None)
|
|
super(DockerExecHelper, self).__init__(*args, **kwargs)
|
|
self.init_execute_mixin()
|
|
|
|
def start_container(self, name=None):
|
|
name = name or "".join(["manila_cifs_docker_container",
|
|
str(uuid.uuid1()).replace("-", "_")])
|
|
image_name = self.configuration.container_image_name
|
|
LOG.debug("Starting container from image %s.", image_name)
|
|
# (aovchinnikov): --privileged is required for both samba and
|
|
# nfs-ganesha to actually allow access to shared folders.
|
|
#
|
|
# (aovchinnikov): To actually make docker container mount a
|
|
# logical volume created after container start-up to some location
|
|
# inside it, we must share entire /dev with it. While seemingly
|
|
# dangerous it is not and moreover this is apparently the only sane
|
|
# way to do it. The reason is when a logical volume gets created
|
|
# several new things appear in /dev: a new /dev/dm-X and a symlink
|
|
# in /dev/volume_group_name pointing to /dev/dm-X. But to be able
|
|
# to interact with /dev/dm-X, it must be already present inside
|
|
# the container's /dev i.e. it must have been -v shared during
|
|
# container start-up. So we should either precreate an unknown
|
|
# number of /dev/dm-Xs (one per LV), share them all and hope
|
|
# for the best or share the entire /dev and hope for the best.
|
|
#
|
|
# The risk of allowing a container having access to entire host's
|
|
# /dev is not as big as it seems: as long as actual share providers
|
|
# are invulnerable this does not pose any extra risks. If, however,
|
|
# share providers contain vulnerabilities then the driver does not
|
|
# provide any more possibilities for an exploitation than other
|
|
# first-party drivers.
|
|
|
|
cmd = ["docker", "run", "-d", "-i", "-t", "--privileged",
|
|
"-v", "/dev:/dev", "--name=%s" % name,
|
|
"-v", "/tmp/shares:/shares", image_name]
|
|
try:
|
|
result = 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'))
|
|
|
|
def stop_container(self, name):
|
|
LOG.debug("Stopping container %s.", name)
|
|
try:
|
|
self._inner_execute(["docker", "stop", name])
|
|
except (exception.ProcessExecutionError, OSError):
|
|
raise exception.ShareBackendException(
|
|
msg="Container %s has failed to stop properly." % name)
|
|
LOG.info("Container %s is successfully stopped.", name)
|
|
|
|
def execute(self, name=None, cmd=None, ignore_errors=False):
|
|
if name is None:
|
|
raise exception.ManilaException(_("Container name not specified."))
|
|
if cmd is None or (type(cmd) is not list):
|
|
raise exception.ManilaException(_("Missing or malformed command."))
|
|
LOG.debug("Executing inside a container %s.", name)
|
|
cmd = ["docker", "exec", "-i", name] + cmd
|
|
result = self._inner_execute(cmd, ignore_errors=ignore_errors)
|
|
return result
|
|
|
|
def _inner_execute(self, cmd, ignore_errors=False):
|
|
LOG.debug("Executing command: %s.", " ".join(cmd))
|
|
try:
|
|
result = self._execute(*cmd, run_as_root=True)
|
|
except (exception.ProcessExecutionError, OSError) as e:
|
|
with excutils.save_and_reraise_exception(
|
|
reraise=not ignore_errors):
|
|
LOG.warning("Failed to run command %(cmd)s due to "
|
|
"%(reason)s.", {'cmd': cmd, 'reason': e})
|
|
else:
|
|
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 rename_container(self, name, new_name):
|
|
veth_name = self.find_container_veth(name)
|
|
if not veth_name:
|
|
raise exception.ManilaException(
|
|
_("Could not find OVS information related to "
|
|
"container %s.") % name)
|
|
|
|
try:
|
|
self._inner_execute(["docker", "rename", name, new_name])
|
|
except (exception.ProcessExecutionError, OSError):
|
|
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):
|
|
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=\".*\""
|
|
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",
|
|
"--format='{{.Names}}'", run_as_root=True)[0]
|
|
for line in result.split('\n'):
|
|
if name == line.strip("'"):
|
|
return True
|
|
return False
|