From 5cfa70c252a7eca4ef31e82d05a455c1a476bef2 Mon Sep 17 00:00:00 2001 From: Marco van Wieringen Date: Thu, 13 Feb 2014 15:01:47 +0100 Subject: [PATCH] Add first prototype for object storage device abstraction. For this we use libdroplet from scality. --- autoconf/configure.in | 60 +++ src/stored/Makefile.in | 9 +- src/stored/backends/object_store_device.c | 475 ++++++++++++++++++++++ src/stored/backends/object_store_device.h | 57 +++ src/stored/dev.c | 12 + src/stored/dev.h | 6 +- src/stored/stored_conf.c | 1 + 7 files changed, 617 insertions(+), 3 deletions(-) create mode 100644 src/stored/backends/object_store_device.c create mode 100644 src/stored/backends/object_store_device.h diff --git a/autoconf/configure.in b/autoconf/configure.in index e460084cf2b..eb0ea492228 100644 --- a/autoconf/configure.in +++ b/autoconf/configure.in @@ -3446,6 +3446,65 @@ fi AC_SUBST(GLUSTER_INC) AC_SUBST(GLUSTER_LIBS) +dnl +dnl Check for Object storage via libdroplet. +dnl +DROPLET_LIBS="-ldroplet" +DROPLET_INC="" +have_droplet=no +AC_ARG_WITH(droplet, + AC_HELP_STRING([--with-droplet@<:@=DIR@:>@], [Directory holding DROPLET includes/libs]), + with_droplet_directory=$withval +) + +if test "x${with_droplet_directory}" != "xyes" && test x"${with_droplet_directory}" != "x"; then + # + # Make sure the $with_droplet_directory also makes sense + # + if test -d "${with_droplet_directory}/lib" -a -d "${with_droplet_directory}/include"; then + DROPLET_LIBS="-L${with_droplet_directory}/lib ${DROPLET_LIBS}" + DROPLET_INC="-I${with_droplet_directory}/include ${DROPLET_INC}" + fi +fi + +saved_LIBS="${LIBS}" +saved_CFLAGS="${CFLAGS}" +saved_CPPFLAGS="${CPPFLAGS}" +LIBS="${saved_LIBS} ${DROPLET_LIBS}" +CFLAGS="${saved_CFLAGS} ${DROPLET_INC}" +CPPFLAGS="${saved_CPPFLAGS} ${DROPLET_INC}" + +AC_CHECK_HEADER(droplet.h) + +AC_MSG_CHECKING(for dpl_ctx_new in libdroplet library) +AC_TRY_LINK( + [ + #include + ], [ + dpl_ctx_new(NULL, NULL); + ], [ + AC_MSG_RESULT(yes) + have_droplet="yes" + ], [ + AC_MSG_RESULT(no) + have_droplet="no" + ] +) + +LIBS="${saved_LIBS}" +CFLAGS="${saved_CFLAGS}" +CPPFLAGS="${saved_CPPFLAGS}" + +if test "x${have_droplet}" = "xyes"; then + AC_DEFINE(HAVE_OBJECTSTORE, 1, [Define to 1 if you have libdroplet lib]) +else + DROPLET_LIBS="" + DROPLET_INC="" +fi + +AC_SUBST(DROPLET_INC) +AC_SUBST(DROPLET_LIBS) + dnl dnl Check for pthread libraries dnl @@ -4042,6 +4101,7 @@ Configuration on `date`: XATTR support: ${have_xattr} SCSI Crypto support: ${have_scsi_crypto} GLUSTERFS support: ${have_glusterfs} + DROPLET support: ${have_droplet} Python support: ${support_python} ${PYTHON_LIBS} systemd support: ${support_systemd} ${SYSTEMD_UNITDIR} Batch insert enabled: ${batch_insert_db_backends} diff --git a/src/stored/Makefile.in b/src/stored/Makefile.in index 0b2abcffb18..55ea1810661 100644 --- a/src/stored/Makefile.in +++ b/src/stored/Makefile.in @@ -28,6 +28,7 @@ first_rule: all dummy: DEVICE_API_SRCS = gfapi_device.c \ + object_store_device.c \ unix_file_device.c \ unix_tape_device.c \ vtape.c @@ -82,7 +83,9 @@ BEXTRACT_LIBS += @LZO_LIBS_NONSHARED@ BEXTRACT_LIBS += @FASTLZ_LIBS_NONSHARED@ GLUSTER_INC = @GLUSTER_INC@ +DROPLET_INC = @DROPLET_INC@ LIBS += @GLUSTER_LIBS@ +LIBS += @DROPLET_LIBS@ INCLUDES += -I$(srcdir) -I$(basedir) -I$(basedir)/include DEBUG = @DEBUG@ @@ -104,12 +107,16 @@ NDMP_LIBS = @NDMP_LIBS@ dev.o: dev.c @echo "Compiling $<" - $(NO_ECHO)$(CXX) $(DEFS) $(DEBUG) $(GLUSTER_INC) -c $(WCFLAGS) $(CPPFLAGS) $(INCLUDES) $(DINCLUDE) $(CXXFLAGS) $< + $(NO_ECHO)$(CXX) $(DEFS) $(DEBUG) $(GLUSTER_INC) $(DROPLET_INC) -c $(WCFLAGS) $(CPPFLAGS) $(INCLUDES) $(DINCLUDE) $(CXXFLAGS) $< gfapi_device.o: gfapi_device.c @echo "Compiling $<" $(NO_ECHO)$(CXX) $(DEFS) $(DEBUG) $(GLUSTER_INC) -c $(WCFLAGS) $(CPPFLAGS) $(INCLUDES) $(DINCLUDE) $(CXXFLAGS) $< +object_storage_device.o: object_storage_device.c + @echo "Compiling $<" + $(NO_ECHO)$(CXX) $(DEFS) $(DEBUG) $(DROPLET_INC) -c $(WCFLAGS) $(CPPFLAGS) $(INCLUDES) $(DINCLUDE) $(CXXFLAGS) $< + all: Makefile bareos-sd @STATIC_SD@ bls bextract bscan btape bcopy @echo "===== Make of stored is good ====" @echo " " diff --git a/src/stored/backends/object_store_device.c b/src/stored/backends/object_store_device.c new file mode 100644 index 00000000000..ba0fe4918f5 --- /dev/null +++ b/src/stored/backends/object_store_device.c @@ -0,0 +1,475 @@ +/* + BAREOSĀ® - Backup Archiving REcovery Open Sourced + + Copyright (C) 2014-2014 Bareos GmbH & Co. KG + + This program is Free Software; you can redistribute it and/or + modify it under the terms of version three of the GNU Affero General Public + License as published by the Free Software Foundation and included + in the file LICENSE. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ +/* + * Object Storage API device abstraction. + * + * Marco van Wieringen, February 2014 + */ + +#include "bareos.h" + +#ifdef HAVE_OBJECTSTORE +#include "stored.h" +#include "object_store_device.h" + +static int droplet_reference_count = 0; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +/* + * Generic log function that glues libdroplet with BAREOS. + */ +static void object_store_logfunc(dpl_ctx_t *ctx, dpl_log_level_t level, const char *message) +{ + switch (level) { + case DPL_DEBUG: + Dmsg1(100, "%s\n", message); + break; + case DPL_INFO: + Emsg1(M_INFO, 0, "%s\n", message); + break; + case DPL_WARNING: + Emsg1(M_WARNING, 0, "%s\n", message); + break; + case DPL_ERROR: + Emsg1(M_ERROR, 0, "%s\n", message); + break; + } +} + +/* + * Map the droplet errno's to system ones. + */ +static inline int droplet_errno_to_system_errno(dpl_status_t status) +{ + switch (status) { + case DPL_ENOENT: + errno = ENOENT; + break; + case DPL_EIO: + errno = EIO; + break; + case DPL_ENAMETOOLONG: + errno = ENAMETOOLONG; + break; + case DPL_EEXIST: + errno = EEXIST; + break; + case DPL_EPERM: + errno = EPERM; + break; + default: + break; + } + + return -1; +} + +/* + * Open a volume using libdroplet. + */ +int object_store_device::d_open(const char *pathname, int flags, int mode) +{ + dpl_status_t status; + dpl_vfile_flag_t dpl_flags; + dpl_option_t dpl_options; + + /* + * Initialize the droplet library when its not done previously. + */ + P(mutex); + if (droplet_reference_count == 0) { + status = dpl_init(); + if (status != DPL_SUCCESS) { + V(mutex); + return -1; + } + + dpl_set_log_func(object_store_logfunc); + droplet_reference_count++; + } + V(mutex); + + if (!m_object_configstring) { + int len; + char *bp; + + m_object_configstring = bstrdup(dev_name); + + /* + * See if there is a bucket defined. + */ + bp = strchr(m_object_configstring, ':'); + if (bp) { + *bp++ = '\0'; + m_object_bucketname = bp; + } + + /* + * Strip any .conf prefix from the libdroplet profile name. + */ + len = strlen(m_object_configstring); + if (bstrcasecmp(m_object_configstring + (len - 5), ".conf")) { + m_object_configstring[len - 5] = '\0'; + } + } + + /* + * See if we need to setup a new context for this device. + */ + if (!m_ctx) { + char *bp; + + /* + * See if this is a path. + */ + bp = strrchr(m_object_configstring, '/'); + if (!bp) { + /* + * Only a profile name. + */ + m_ctx = dpl_ctx_new(NULL, m_object_configstring); + } else { + if (bp == m_object_configstring) { + /* + * Profile in root of filesystem + */ + m_ctx = dpl_ctx_new("/", bp + 1); + } else { + /* + * Profile somewhere else. + */ + *bp++ = '\0'; + m_ctx = dpl_ctx_new(m_object_configstring, bp); + } + } + + /* + * If we failed to allocate a new context fail the open. + */ + if (!m_ctx) { + return -1; + } + + /* + * Login if that is needed for this backend. + */ + status = dpl_login(m_ctx); + switch (status) { + case DPL_SUCCESS: + break; + case DPL_ENOTSUPP: + /* + * Backend doesn't support login which is fine. + */ + break; + default: + Mmsg2(errmsg, _("Failed to login for voume %s using dpl_login(): ERR=%s.\n"), + getVolCatName(), dpl_status_str(status)); + return -1; + } + + /* + * If a bucketname was defined set it in the context. + */ + if (m_object_bucketname) { + m_ctx->cur_bucket = m_object_bucketname; + } + } + + /* + * See if we don't have a file open already. + */ + if (m_vfd) { + dpl_close(m_vfd); + m_vfd = NULL; + } + + /* + * Create some options for libdroplet. + * + * DPL_OPTION_NOALLOC - we provide the buffer to copy the data into + * no need to let the library allocate memory we + * need to free after copying the data. + */ + memset(&dpl_options, 0, sizeof(dpl_options)); + dpl_options.mask |= DPL_OPTION_NOALLOC; + + if (flags & O_CREAT) { + dpl_flags = DPL_VFILE_FLAG_CREAT | DPL_VFILE_FLAG_RDWR; + status = dpl_open(m_ctx, /* context */ + getVolCatName(), /* locator */ + dpl_flags, /* flags */ + &dpl_options, /* options */ + NULL, /* condition */ + NULL, /* metadata */ + NULL, /* sysmd */ + NULL, /* query_params */ + &m_vfd); + } else { + dpl_flags = DPL_VFILE_FLAG_RDWR; + status = dpl_open(m_ctx, /* context */ + getVolCatName(), /* locator */ + dpl_flags, /* flags */ + &dpl_options, /* options */ + NULL, /* condition */ + NULL, /* metadata */ + NULL, /* sysmd */ + NULL, /* query_params */ + &m_vfd); + } + + switch (status) { + case DPL_SUCCESS: + m_offset = 0; + return 0; + default: + Mmsg2(errmsg, _("Failed to open %s using dpl_open(): ERR=%s.\n"), + getVolCatName(), dpl_status_str(status)); + m_vfd = NULL; + return droplet_errno_to_system_errno(status); + } +} + +/* + * Read data from a volume using libdroplet. + */ +ssize_t object_store_device::d_read(int fd, void *buffer, size_t count) +{ + if (m_vfd) { + int buflen; + dpl_status_t status; + + buflen = count; + status = dpl_pread(m_vfd, count, m_offset, (char **)&buffer, &buflen); + + switch (status) { + case DPL_SUCCESS: + m_offset += buflen; + return buflen; + default: + Mmsg2(errmsg, _("Failed to read %s using dpl_read(): ERR=%s.\n"), + getVolCatName(), dpl_status_str(status)); + return droplet_errno_to_system_errno(status); + } + } else { + errno = EBADF; + return -1; + } +} + +/* + * Write data to a volume using libdroplet. + */ +ssize_t object_store_device::d_write(int fd, const void *buffer, size_t count) +{ + if (m_vfd) { + dpl_status_t status; + + status = dpl_pwrite(m_vfd, (char *)buffer, count, m_offset); + switch (status) { + case DPL_SUCCESS: + m_offset += count; + return count; + default: + Mmsg2(errmsg, _("Failed to write %s using dpl_write(): ERR=%s.\n"), + getVolCatName(), dpl_status_str(status)); + return droplet_errno_to_system_errno(status); + } + } else { + errno = EBADF; + return -1; + } +} + +int object_store_device::d_close(int fd) +{ + if (m_vfd) { + dpl_status_t status; + + status = dpl_close(m_vfd); + switch (status) { + case DPL_SUCCESS: + m_vfd = NULL; + return 0; + default: + m_vfd = NULL; + return droplet_errno_to_system_errno(status); + } + } else { + errno = EBADF; + return -1; + } +} + +int object_store_device::d_ioctl(int fd, ioctl_req_t request, char *op) +{ + return -1; +} + +/* + * Open a directory on the object store and find out size information for a file. + */ +static inline size_t object_store_get_file_size(dpl_ctx_t *ctx, const char *filename) +{ + void *dir_hdl; + dpl_status_t status; + dpl_dirent_t dirent; + size_t filesize = -1; + + status = dpl_opendir(ctx, ".", &dir_hdl); + switch (status) { + case DPL_SUCCESS: + break; + default: + return -1; + } + + while (!dpl_eof(dir_hdl)) { + if (bstrcasecmp(dirent.name, filename)) { + filesize = dirent.size; + break; + } + } + + dpl_closedir(dir_hdl); + + return filesize; +} + +boffset_t object_store_device::d_lseek(DCR *dcr, boffset_t offset, int whence) +{ + switch (whence) { + case SEEK_SET: + m_offset = offset; + break; + case SEEK_CUR: + m_offset += offset; + break; + case SEEK_END: { + size_t filesize; + + filesize = object_store_get_file_size(m_ctx, getVolCatName()); + if (filesize >= 0) { + m_offset = filesize + offset; + } else { + return -1; + } + break; + } + default: + return -1; + } + + return m_offset; +} + +bool object_store_device::d_truncate(DCR *dcr) +{ + /* + * libdroplet doesn't have a truncate function so unlink the volume and create a new empty one. + */ + if (m_vfd) { + dpl_status_t status; + dpl_vfile_flag_t dpl_flags; + dpl_option_t dpl_options; + + status = dpl_close(m_vfd); + switch (status) { + case DPL_SUCCESS: + m_vfd = NULL; + break; + default: + Mmsg2(errmsg, _("Failed to close %s using dpl_close(): ERR=%s.\n"), + getVolCatName(), dpl_status_str(status)); + return false; + } + + status = dpl_unlink(m_ctx, getVolCatName()); + switch (status) { + case DPL_SUCCESS: + break; + default: + Mmsg2(errmsg, _("Failed to unlink %s using dpl_unlink(): ERR=%s.\n"), + getVolCatName(), dpl_status_str(status)); + return false; + } + + /* + * Create some options for libdroplet. + * + * DPL_OPTION_NOALLOC - we provide the buffer to copy the data into + * no need to let the library allocate memory we + * need to free after copying the data. + */ + memset(&dpl_options, 0, sizeof(dpl_options)); + dpl_options.mask |= DPL_OPTION_NOALLOC; + + dpl_flags = DPL_VFILE_FLAG_CREAT | DPL_VFILE_FLAG_RDWR; + status = dpl_open(m_ctx, /* context */ + getVolCatName(), /* locator */ + dpl_flags, /* flags */ + &dpl_options, /* options */ + NULL, /* condition */ + NULL, /* metadata */ + NULL, /* sysmd */ + NULL, /* query_params */ + &m_vfd); + + switch (status) { + case DPL_SUCCESS: + break; + default: + Mmsg2(errmsg, _("Failed to open %s using dpl_open(): ERR=%s.\n"), + getVolCatName(), dpl_status_str(status)); + return false; + } + } + + return true; +} + +object_store_device::~object_store_device() +{ + if (m_ctx) { + dpl_ctx_free(m_ctx); + m_ctx = NULL; + } + + if (m_object_configstring) { + free(m_object_configstring); + } + + P(mutex); + droplet_reference_count--; + if (droplet_reference_count == 0) { + dpl_free(); + } + V(mutex); +} + +object_store_device::object_store_device() +{ + m_fd = -1; + m_object_configstring = NULL; + m_object_bucketname = NULL; + m_ctx = NULL; +} +#endif diff --git a/src/stored/backends/object_store_device.h b/src/stored/backends/object_store_device.h new file mode 100644 index 00000000000..3d5b8e0b88c --- /dev/null +++ b/src/stored/backends/object_store_device.h @@ -0,0 +1,57 @@ +/* + BAREOSĀ® - Backup Archiving REcovery Open Sourced + + Copyright (C) 2014-2014 Bareos GmbH & Co. KG + + This program is Free Software; you can redistribute it and/or + modify it under the terms of version three of the GNU Affero General Public + License as published by the Free Software Foundation, which is + listed in the file LICENSE. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ +/* + * Object Storage API device abstraction. + * + * Marco van Wieringen, February 2014 + */ + +#ifndef OBJECTSTORAGE_DEVICE_H +#define OBJECTSTORAGE_DEVICE_H + +#include +#include + +class object_store_device: public DEVICE { +private: + char *m_object_configstring; + char *m_object_bucketname; + dpl_ctx_t *m_ctx; + dpl_vfile_t *m_vfd; + boffset_t m_offset; + +public: + object_store_device(); + ~object_store_device(); + + /* + * Interface from DEVICE + */ + int d_close(int); + int d_open(const char *pathname, int flags, int mode); + int d_ioctl(int fd, ioctl_req_t request, char *mt = NULL); + boffset_t d_lseek(DCR *dcr, boffset_t offset, int whence); + ssize_t d_read(int fd, void *buffer, size_t count); + ssize_t d_write(int fd, const void *buffer, size_t count); + bool d_truncate(DCR *dcr); +}; + +#endif /* OBJECTSTORE_DEVICE_H */ diff --git a/src/stored/dev.c b/src/stored/dev.c index 68be2895e7a..2f006b44afc 100644 --- a/src/stored/dev.c +++ b/src/stored/dev.c @@ -78,6 +78,9 @@ #ifdef HAVE_GFAPI #include "backends/gfapi_device.h" #endif +#ifdef HAVE_OBJECTSTORE +#include "backends/object_store_device.h" +#endif #ifdef HAVE_WIN32 #include "backends/win32_tape_device.h" #include "backends/win32_file_device.h" @@ -169,6 +172,11 @@ m_init_dev(JCR *jcr, DEVRES *device, bool new_init) dev = New(gfapi_device); break; #endif +#ifdef HAVE_OBJECTSTORE + case B_OBJECT_STORE_DEV: + dev = New(object_store_device); + break; +#endif #ifdef HAVE_WIN32 case B_TAPE_DEV: dev = New(win32_tape_device); @@ -1964,6 +1972,8 @@ bool DEVICE::mount(DCR *dcr, int timeout) break; case B_GFAPI_DEV: break; + case B_OBJECT_STORE_DEV: + break; default: break; } @@ -2033,6 +2043,8 @@ bool DEVICE::unmount(DCR *dcr, int timeout) break; case B_GFAPI_DEV: break; + case B_OBJECT_STORE_DEV: + break; default: break; } diff --git a/src/stored/dev.h b/src/stored/dev.h index 53bb730573e..3cc4305b439 100644 --- a/src/stored/dev.h +++ b/src/stored/dev.h @@ -87,7 +87,8 @@ enum { B_FIFO_DEV, B_VTAPE_DEV, B_VTL_DEV, - B_GFAPI_DEV + B_GFAPI_DEV, + B_OBJECT_STORE_DEV }; /* IO directions */ @@ -312,7 +313,8 @@ class DEVICE: public SMARTALLOC { int is_tape() const { return (dev_type == B_TAPE_DEV || dev_type == B_VTAPE_DEV); } int is_file() const { return (dev_type == B_FILE_DEV || - dev_type == B_GFAPI_DEV); } + dev_type == B_GFAPI_DEV || + dev_type == B_OBJECT_STORE_DEV); } int is_fifo() const { return dev_type == B_FIFO_DEV; } int is_vtl() const { return dev_type == B_VTL_DEV; } int is_vtape() const { return dev_type == B_VTAPE_DEV; } diff --git a/src/stored/stored_conf.c b/src/stored/stored_conf.c index 764f239e6c1..f16f7155c3a 100644 --- a/src/stored/stored_conf.c +++ b/src/stored/stored_conf.c @@ -278,6 +278,7 @@ static s_kw dev_types[] = { { "vtl", B_VTL_DEV }, { "vtape", B_VTAPE_DEV }, { "gfapi", B_GFAPI_DEV }, + { "object", B_OBJECT_STORE_DEV }, { NULL, 0 } };