2021-11-20 14:40:07 +00:00
|
|
|
from classes.connection import Connection
|
2021-11-22 10:14:38 +00:00
|
|
|
from classes.database import Database
|
|
|
|
from classes.file import File
|
2021-11-20 14:40:07 +00:00
|
|
|
|
|
|
|
from paramiko.ssh_exception import SSHException
|
|
|
|
|
2021-11-25 15:31:49 +00:00
|
|
|
from configparser import SectionProxy
|
|
|
|
from typing import Optional, Union
|
|
|
|
|
2021-11-22 10:14:38 +00:00
|
|
|
import pathlib
|
|
|
|
|
2021-11-25 15:31:49 +00:00
|
|
|
|
2021-11-20 14:40:07 +00:00
|
|
|
class Vessel:
|
2021-11-25 15:31:49 +00:00
|
|
|
"""Class describing a Vessel (= a replication destination)
|
|
|
|
"""
|
2021-11-20 14:40:07 +00:00
|
|
|
@classmethod
|
2021-11-25 15:31:49 +00:00
|
|
|
def fromConfig(cls, config: SectionProxy):
|
|
|
|
"""Create Vessel object from a Vessel section in the Config file
|
|
|
|
|
|
|
|
Args:
|
|
|
|
config (configparser.SectionProxy): Vessel section defining a
|
|
|
|
Vessel
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
ValueError: Raised if section does not contain Address parameter
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
classes.vessel.Vessel: Vessel object for the vessel specified in
|
|
|
|
the config section
|
|
|
|
"""
|
|
|
|
|
|
|
|
tempdir = None
|
2021-11-26 06:03:13 +00:00
|
|
|
username = None
|
|
|
|
password = None
|
|
|
|
passphrase = None
|
2021-11-26 06:39:21 +00:00
|
|
|
port = 22
|
2021-11-30 16:20:12 +00:00
|
|
|
timeout = None
|
|
|
|
ignoredirs = []
|
2021-11-25 15:31:49 +00:00
|
|
|
|
2021-11-22 10:14:38 +00:00
|
|
|
if "TempDir" in config.keys():
|
|
|
|
tempdir = config["TempDir"]
|
2021-11-25 15:31:49 +00:00
|
|
|
|
2021-11-26 06:03:13 +00:00
|
|
|
if "Username" in config.keys():
|
|
|
|
username = config["Username"]
|
|
|
|
|
|
|
|
if "Password" in config.keys():
|
|
|
|
password = config["Password"]
|
|
|
|
|
|
|
|
if "Passphrase" in config.keys():
|
|
|
|
passphrase = config["Passphrase"]
|
|
|
|
|
2021-11-26 06:39:21 +00:00
|
|
|
if "Port" in config.keys():
|
|
|
|
port = config["Port"]
|
2021-11-30 16:20:12 +00:00
|
|
|
|
|
|
|
if "Timeout" in config.keys():
|
|
|
|
timeout = config["Timeout"]
|
|
|
|
|
2021-11-30 14:44:50 +00:00
|
|
|
if "IgnoreDirs" in config.keys():
|
|
|
|
ignoredirs = [d.strip() for d in config["IgnoreDirs"].split(",")]
|
2021-11-26 06:39:21 +00:00
|
|
|
|
2021-11-20 14:40:07 +00:00
|
|
|
if "Address" in config.keys():
|
2021-11-26 06:52:16 +00:00
|
|
|
return cls(config.name.split()[1], config["Address"], username,
|
2021-11-30 16:20:12 +00:00
|
|
|
password, passphrase, port, timeout, tempdir, ignoredirs)
|
2021-11-20 14:40:07 +00:00
|
|
|
else:
|
2021-11-25 15:31:49 +00:00
|
|
|
raise ValueError("Definition for Vessel " +
|
|
|
|
config.name.split()[1] + " does not contain Address!")
|
2021-11-20 14:40:07 +00:00
|
|
|
|
2021-11-26 06:03:13 +00:00
|
|
|
def __init__(self, name: str, address: str, username: Optional[str] = None,
|
|
|
|
password: Optional[str] = None, passphrase: Optional[str] = None,
|
2021-11-26 10:23:39 +00:00
|
|
|
port: Optional[int] = None, timeout: Optional[int] = None,
|
2021-11-30 14:44:50 +00:00
|
|
|
tempdir: Optional[Union[str, pathlib.Path]] = None,
|
|
|
|
ignoredirs: list[Optional[str]] = []) -> None:
|
2021-11-25 15:31:49 +00:00
|
|
|
"""Initialize new Vessel object
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name (str): Name of the Vessel
|
|
|
|
address (str): Address (IP or resolvable hostname) of the Vessel
|
|
|
|
tempdir (pathlib.Path, optional): Temporary upload location on the
|
|
|
|
Vessel, to store Chunks in
|
|
|
|
"""
|
2021-11-20 14:40:07 +00:00
|
|
|
self.name = name
|
|
|
|
self.address = address
|
2021-11-25 15:31:49 +00:00
|
|
|
self.tempdir = pathlib.Path(tempdir or "/tmp/.ContentMonster/")
|
2021-11-26 06:03:13 +00:00
|
|
|
self.username = username
|
|
|
|
self.password = password
|
|
|
|
self.passphrase = passphrase
|
2021-11-26 06:39:21 +00:00
|
|
|
self.port = port or 22
|
2021-11-26 10:23:39 +00:00
|
|
|
self.timeout = timeout or 10
|
2021-11-20 14:40:07 +00:00
|
|
|
self._connection = None
|
2021-11-25 18:03:58 +00:00
|
|
|
self._uploaded = self.getUploadedFromDB() # Files already uploaded
|
2021-11-30 14:44:50 +00:00
|
|
|
self._ignoredirs = ignoredirs # Directories not replicated to this vessel
|
2021-11-20 14:40:07 +00:00
|
|
|
|
|
|
|
@property
|
2021-11-25 15:31:49 +00:00
|
|
|
def connection(self) -> Connection:
|
|
|
|
"""Get a Connection to the Vessel
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
classes.connection.Connection: SSH/SFTP connection to the Vessel
|
|
|
|
"""
|
|
|
|
# If a connection exists
|
2021-11-20 14:40:07 +00:00
|
|
|
if self._connection:
|
|
|
|
try:
|
2021-11-25 15:31:49 +00:00
|
|
|
# ... check if it is up
|
2021-11-26 10:23:39 +00:00
|
|
|
self._connection._listdir(".")
|
|
|
|
except (SSHException, OSError):
|
2021-11-25 15:31:49 +00:00
|
|
|
# ... and throw it away if it isn't
|
2021-11-20 14:40:07 +00:00
|
|
|
self._connection = None
|
2021-11-25 15:31:49 +00:00
|
|
|
|
|
|
|
# If no connection exists (anymore), set up a new one
|
2021-11-22 10:14:38 +00:00
|
|
|
self._connection = self._connection or Connection(self)
|
|
|
|
return self._connection
|
|
|
|
|
2021-11-25 15:31:49 +00:00
|
|
|
def getUploadedFromDB(self) -> list[str]:
|
|
|
|
"""Get a list of files that have previously been uploaded to the Vessel
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
list: List of UUIDs of Files that have been successfully uploaded
|
|
|
|
"""
|
2021-11-22 10:14:38 +00:00
|
|
|
db = Database()
|
|
|
|
return db.getCompletionForVessel(self)
|
|
|
|
|
2021-11-26 10:23:39 +00:00
|
|
|
def currentUpload(self) -> Optional[tuple[str, str, str]]:
|
2021-11-25 15:31:49 +00:00
|
|
|
"""Get the File that is currently being uploaded to this Vessel
|
|
|
|
|
|
|
|
Returns:
|
2021-11-26 10:23:39 +00:00
|
|
|
tuple: A tuple consisting of (directory, name, checksum), where
|
|
|
|
"directory" is the name of the Directory object the File is
|
|
|
|
located in, "name" is the filename (basename) of the File and
|
|
|
|
checksum is the SHA256 hash of the file at the time of insertion
|
|
|
|
into the database. None is returned if no such record is found.
|
2021-11-25 15:31:49 +00:00
|
|
|
"""
|
2021-11-26 10:23:39 +00:00
|
|
|
self.assertTempDirectory() # After a reboot, the tempdir may be gone
|
|
|
|
|
2021-11-22 10:14:38 +00:00
|
|
|
db = Database()
|
2021-11-26 10:23:39 +00:00
|
|
|
output = db.getFileByUUID(self.connection.getCurrentUploadUUID())
|
|
|
|
del db
|
|
|
|
|
|
|
|
return output
|
2021-11-20 14:40:07 +00:00
|
|
|
|
2021-11-25 15:31:49 +00:00
|
|
|
def clearTempDir(self) -> None:
|
|
|
|
"""Clean up the temporary directory on the Vessel
|
|
|
|
"""
|
|
|
|
self.connection.clearTempDir()
|
2021-11-25 18:03:58 +00:00
|
|
|
|
|
|
|
def pushChunk(self, chunk, path: Optional[Union[str, pathlib.Path]] = None) -> None:
|
|
|
|
"""Push the content of a Chunk object to the Vessel
|
|
|
|
|
|
|
|
Args:
|
|
|
|
chunk (classes.chunk.Chunk): Chunk object containing the data to
|
|
|
|
push to the Vessel
|
|
|
|
path (str, pathlib.Path, optional): Path at which to store the
|
|
|
|
Chunk on the Vessel. If None, use default location provided by
|
|
|
|
Vessel configuration and name provided by Chunk object. Defaults
|
|
|
|
to None.
|
|
|
|
"""
|
2021-11-26 10:23:39 +00:00
|
|
|
self.connection.pushChunk(chunk, str(path) if path else None)
|
2021-11-25 18:03:58 +00:00
|
|
|
|
|
|
|
def compileComplete(self, remotefile) -> None:
|
|
|
|
"""Build a complete File from uploaded Chunks.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
remotefile (classes.remotefile.RemoteFile): RemoteFile object
|
|
|
|
describing the uploaded File
|
|
|
|
"""
|
2021-11-26 06:03:13 +00:00
|
|
|
self.connection.compileComplete(remotefile)
|
2021-11-26 10:23:39 +00:00
|
|
|
|
|
|
|
def assertDirectories(self, directory) -> None:
|
|
|
|
"""Make sure that destination and temp directories exist on the Vessel
|
|
|
|
|
|
|
|
Args:
|
|
|
|
directory (classes.directory.Directory): Directory object
|
|
|
|
representing the directory to check
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
ValueError: Raised if a path is already in use on the vessel but
|
|
|
|
not a directory.
|
|
|
|
IOError: Raised if a directory that does not exist cannot be
|
|
|
|
created.
|
|
|
|
"""
|
|
|
|
self.connection.assertDirectories(directory)
|
|
|
|
|
|
|
|
def assertTempDirectory(self) -> None:
|
|
|
|
"""Make sure that the temp directory exists on the Vessel
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
ValueError: Raised if the path is already in use on the vessel but
|
|
|
|
is not a directory.
|
|
|
|
IOError: Raised if the directory does not exist but cannot be
|
|
|
|
created.
|
|
|
|
"""
|
2021-11-30 14:44:50 +00:00
|
|
|
self.connection.assertTempDirectory()
|