"""
Basic nfs support for Linux host. It can support the remote
nfs mount and the local nfs set up and mount.
"""
import re
import os
import shutil
import logging
import commands
from autotest.client import os_dep
from autotest.client.shared import utils, error
from virttest import utils_misc
from virttest.utils_conn import SSHConnection
from virttest.staging import service
[docs]def nfs_exported():
"""
Get the list for nfs file system already exported
:return: a list of nfs that is already exported in system
:rtype: a lit of nfs file system exported
"""
exportfs = utils.system_output("exportfs -v")
if not exportfs:
return {}
nfs_exported_dict = {}
for fs_info in re.findall("[/\w+]+.*?\(.*?\)", exportfs, re.S):
fs_info = fs_info.strip().split()
if len(fs_info) == 2:
nfs_src = fs_info[0]
access_ip = re.findall(r"(.*)\(", fs_info[1])[0]
if "world" in access_ip:
access_ip = "*"
nfs_tag = "%s_%s" % (nfs_src, access_ip)
permission = re.findall(r"\((.*)\)", fs_info[1])[0]
nfs_exported_dict[nfs_tag] = permission
return nfs_exported_dict
[docs]class Exportfs(object):
"""
Add or remove one entry to exported nfs file system.
"""
def __init__(self, path, client="*", options="", ori_exported=None):
if ori_exported is None:
ori_exported = []
self.path = path
self.client = client
self.options = options.split(",")
self.ori_exported = ori_exported
self.entry_tag = "%s_%s" % (self.path, self.client)
self.already_exported = False
self.ori_options = ""
[docs] def is_exported(self):
"""
Check if the directory is already exported.
:return: If the entry is exported
:rtype: Boolean
"""
ori_exported = self.ori_exported or nfs_exported()
if self.entry_tag in ori_exported.keys():
return True
return False
[docs] def need_reexport(self):
"""
Check if the entry is already exported but the options are not
the same as we required.
:return: Need re export the entry or not
:rtype: Boolean
"""
ori_exported = self.ori_exported or nfs_exported()
if self.is_exported():
exported_options = ori_exported[self.entry_tag]
options = [_ for _ in self.options if _ not in exported_options]
if options:
self.ori_options = exported_options
return True
return False
[docs] def unexport(self):
"""
Unexport an entry.
"""
if self.is_exported():
unexport_cmd = "exportfs -u %s:%s" % (self.client, self.path)
utils.system(unexport_cmd)
else:
logging.warn("Target %s %s is not exported yet."
"Can not unexport it." % (self.client, self.path))
[docs] def reset_export(self):
"""
Reset the exportfs to the original status before we export the
specific entry.
"""
self.unexport()
if self.ori_options:
tmp_options = self.options
self.options = self.ori_options.split(",")
self.export()
self.options = tmp_options
[docs] def export(self):
"""
Export one directory if it is not in exported list.
:return: Export nfs file system succeed or not
"""
if self.is_exported():
if self.need_reexport():
self.unexport()
else:
self.already_exported = True
logging.warn("Already exported target."
" Don't need export it again")
return True
export_cmd = "exportfs"
if self.options:
export_cmd += " -o %s" % ",".join(self.options)
export_cmd += " %s:%s" % (self.client, self.path)
try:
utils.system(export_cmd)
except error.CmdError, export_failed_err:
logging.error("Can not export target: %s" % export_failed_err)
return False
return True
[docs]class Nfs(object):
"""
Nfs class for handle nfs mount and umount. If a local nfs service is
required, it will configure a local nfs server accroding the params.
"""
def __init__(self, params):
self.mount_dir = params.get("nfs_mount_dir")
self.mount_options = params.get("nfs_mount_options")
self.mount_src = params.get("nfs_mount_src")
self.nfs_setup = False
os_dep.command("mount")
self.mk_mount_dir = False
self.unexportfs_in_clean = False
if params.get("setup_local_nfs") == "yes":
self.nfs_setup = True
os_dep.command("service")
os_dep.command("exportfs")
self.nfs_service = service.Factory.create_service("nfs")
self.export_dir = (params.get("export_dir") or
self.mount_src.split(":")[-1])
self.export_ip = params.get("export_ip", "*")
self.export_options = params.get("export_options", "").strip()
self.exportfs = Exportfs(self.export_dir, self.export_ip,
self.export_options)
self.mount_src = "127.0.0.1:%s" % self.export_dir
[docs] def is_mounted(self):
"""
Check the NFS is mounted or not.
:return: If the src is mounted as expect
:rtype: Boolean
"""
return utils_misc.is_mounted(self.mount_src, self.mount_dir, "nfs")
[docs] def mount(self):
"""
Mount source into given mount point.
"""
return utils_misc.mount(self.mount_src, self.mount_dir, "nfs",
perm=self.mount_options)
[docs] def umount(self):
"""
Umount the given mount point.
"""
return utils_misc.umount(self.mount_src, self.mount_dir, "nfs")
[docs] def setup(self):
"""
Setup NFS in host.
Mount NFS as configured. If a local nfs is requested, setup the NFS
service and exportfs too.
"""
if self.nfs_setup:
if not self.nfs_service.status():
logging.debug("Restart NFS service.")
self.nfs_service.restart()
if not os.path.isdir(self.export_dir):
os.makedirs(self.export_dir)
self.exportfs.export()
self.unexportfs_in_clean = not self.exportfs.already_exported
logging.debug("Mount %s to %s" % (self.mount_src, self.mount_dir))
if os.path.exists(self.mount_dir) and not os.path.isdir(self.mount_dir):
raise OSError(
"Mount point %s is not a directory, check your setup." %
self.mount_dir)
if not os.path.isdir(self.mount_dir):
os.makedirs(self.mount_dir)
self.mk_mount_dir = True
self.mount()
[docs] def cleanup(self):
"""
Clean up the host env.
Umount NFS from the mount point. If there has some change for exported
file system in host when setup, also clean up that.
"""
self.umount()
if self.nfs_setup and self.unexportfs_in_clean:
self.exportfs.reset_export()
if self.mk_mount_dir and os.path.isdir(self.mount_dir):
utils.safe_rmdir(self.mount_dir)
[docs]class NFSClient(object):
"""
NFSClient class for handle nfs remotely mount and umount.
"""
def __init__(self, params):
# Setup SSH connection
self.ssh_obj = SSHConnection(params)
self.ssh_obj.conn_setup()
self.mkdir_mount_remote = False
self.mount_dir = params.get("nfs_mount_dir")
self.mount_options = params.get("nfs_mount_options")
self.mount_src = params.get("nfs_mount_src")
self.nfs_client_ip = params.get("nfs_client_ip")
self.nfs_server_ip = params.get("nfs_server_ip")
self.ssh_user = params.get("ssh_username", "root")
self.remote_nfs_mount = params.get("remote_nfs_mount", "yes")
[docs] def is_mounted(self):
"""
Check the NFS is mounted or not.
:return: If the src is mounted as expect
:rtype: Boolean
"""
ssh_cmd = "ssh %s@%s " % (self.ssh_user, self.nfs_client_ip)
find_mountpoint_cmd = "mount | grep -E '.*%s.*%s.*'" % (self.mount_src,
self.mount_dir)
cmd = ssh_cmd + "'%s'" % find_mountpoint_cmd
logging.debug("The command: %s", cmd)
status, output = commands.getstatusoutput(cmd)
if status:
logging.debug("The command result: <%s:%s>", status, output)
return False
return True
[docs] def setup(self):
"""
Setup NFS client.
"""
# Mount sharing directory to local host
# it has been covered by class Nfs
# Mount sharing directory to remote host
if self.remote_nfs_mount == "yes":
self.setup_remote()
[docs] def cleanup(self):
"""
Cleanup NFS client.
"""
ssh_cmd = "ssh %s@%s " % (self.ssh_user, self.nfs_client_ip)
logging.debug("Umount %s from %s" % (self.mount_dir, self.nfs_server_ip))
umount_cmd = ssh_cmd + "'umount -l %s'" % self.mount_dir
try:
utils.system(umount_cmd, verbose=True)
except error.CmdError:
raise error.TestFail("Failed to run: %s" % umount_cmd)
if self.mkdir_mount_remote:
rmdir_cmd = ssh_cmd + "'rm -rf %s'" % self.mount_dir
try:
utils.system(rmdir_cmd, verbose=True)
except error.CmdError:
raise error.TestFail("Failed to run: %s" % rmdir_cmd)
if self.is_mounted():
raise error.TestFail("Failed to umount %s" % self.mount_dir)
# Recover SSH connection
self.ssh_obj.auto_recover = True
del self.ssh_obj
[docs] def setup_remote(self):
"""
Mount sharing directory to remote host.
"""
ssh_cmd = "ssh %s@%s " % (self.ssh_user, self.nfs_client_ip)
check_mount_dir_cmd = ssh_cmd + "'ls -d %s'" % self.mount_dir
logging.debug("To check if the %s exists", self.mount_dir)
output = commands.getoutput(check_mount_dir_cmd)
if re.findall("No such file or directory", output, re.M):
mkdir_cmd = ssh_cmd + "'mkdir -p %s'" % self.mount_dir
logging.debug("Prepare to create %s", self.mount_dir)
s, o = commands.getstatusoutput(mkdir_cmd)
if s != 0:
raise error.TestFail("Failed to run %s: %s" % (mkdir_cmd, o))
self.mkdir_mount_remote = True
self.mount_src = "%s:%s" % (self.nfs_server_ip, self.mount_src)
logging.debug("Mount %s to %s" % (self.mount_src, self.mount_dir))
mount_cmd = ssh_cmd + "'mount -t nfs %s %s -o %s'" % (self.mount_src,
self.mount_dir,
self.mount_options)
try:
utils.system(mount_cmd, verbose=True)
except error.CmdError:
raise error.TestFail("Failed to run: %s" % mount_cmd)
# Check if the sharing directory is mounted
if not self.is_mounted():
raise error.TestFail("Failed to mount from %s to %s"
% (self.mount_src, self.mount_dir))