diff options
author | Susant Palai <spalai@redhat.com> | 2018-05-22 19:55:27 +0530 |
---|---|---|
committer | Amar Tumballi <amarts@redhat.com> | 2018-05-30 03:26:25 +0000 |
commit | 62990c30fcec37f643b7631070de8baba276f0ba (patch) | |
tree | 14c46a3b15eacc3df318337da1e8a7d954efbfc6 /xlators/features/cloudsync/src/cloudsync-plugins | |
parent | cb4e0fa2cbd25ef4ab5b65c6a76a6aea90c0f835 (diff) |
cloudsync: Adding s3 plugin for cloudsync
This is a plugin which provides an interface to retrive files from amazon-s3
which are archived in to s3.
Users need to give the above information for cloudsync to retrieve the file
from s3.
TODO:
1- A separate commit in to developer-guide will detail about the usage
of this plugin in more detail.
2- Need to create target file in aws-bucket with "gfid" names. Helps avoiding
name collisions.
Change-Id: I2e4a586f4e3f86164de9178e37673a07f317e7d9
Updates: #387
Signed-off-by: Susant Palai <spalai@redhat.com>
Diffstat (limited to 'xlators/features/cloudsync/src/cloudsync-plugins')
8 files changed, 651 insertions, 0 deletions
diff --git a/xlators/features/cloudsync/src/cloudsync-plugins/Makefile.am b/xlators/features/cloudsync/src/cloudsync-plugins/Makefile.am new file mode 100644 index 00000000000..a985f42a877 --- /dev/null +++ b/xlators/features/cloudsync/src/cloudsync-plugins/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = src + +CLEANFILES = diff --git a/xlators/features/cloudsync/src/cloudsync-plugins/src/Makefile.am b/xlators/features/cloudsync/src/cloudsync-plugins/src/Makefile.am new file mode 100644 index 00000000000..4deefb651eb --- /dev/null +++ b/xlators/features/cloudsync/src/cloudsync-plugins/src/Makefile.am @@ -0,0 +1,7 @@ +if BUILD_AMAZONS3_PLUGIN + AMAZONS3_DIR = cloudsyncs3 +endif + +SUBDIRS = ${AMAZONS3_DIR} + +CLEANFILES = diff --git a/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/Makefile.am b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/Makefile.am new file mode 100644 index 00000000000..a985f42a877 --- /dev/null +++ b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = src + +CLEANFILES = diff --git a/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/Makefile.am b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/Makefile.am new file mode 100644 index 00000000000..93fb2eecbf5 --- /dev/null +++ b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/Makefile.am @@ -0,0 +1,12 @@ +csp_LTLIBRARIES = cloudsyncs3.la +cspdir = $(libdir)/glusterfs/$(PACKAGE_VERSION)/cloudsync-plugins + +cloudsyncs3_la_SOURCES = libcloudsyncs3.c $(top_srcdir)/xlators/features/cloudsync/src/cloudsync-common.c +cloudsyncs3_la_LIBADD = $(top_builddir)/libglusterfs/src/libglusterfs.la +cloudsyncs3_la_LDFLAGS = -module -avoid-version -export-symbols $(top_srcdir)/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3.sym +AM_CPPFLAGS = $(GF_CPPFLAGS) -I$(top_srcdir)/libglusterfs/src -I$(top_srcdir)/rpc/xdr/src -I$(top_builddir)/rpc/xdr/src -lcurlpp -lcryptopp +noinst_HEADERS = libcloudsyncs3.h libcloudsyncs3-mem-types.h +AM_CFLAGS = -Wall -fno-strict-aliasing $(GF_CFLAGS) -lcurl -lcrypto -I$(top_srcdir)/xlators/features/cloudsync/src +CLEANFILES = + +EXTRA_DIST = libcloudsyncs3.sym diff --git a/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3-mem-types.h b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3-mem-types.h new file mode 100644 index 00000000000..dd9314ec8d8 --- /dev/null +++ b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3-mem-types.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Red Hat, Inc. <http://www.redhat.com> + * This file is part of GlusterFS. + * + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 3 or any later version (LGPLv3 or + * later), or the GNU General Public License, version 2 (GPLv2), in all + * cases as published by the Free Software Foundation. + */ + + +#ifndef __LIBAWS_MEM_TYPES_H__ +#define __LIBAWS_MEM_TYPES_H__ + +#include "mem-types.h" +enum libaws_mem_types_ { + gf_libaws_mt_aws_private_t = gf_common_mt_end + 1, + gf_libaws_mt_end +}; +#endif /* __CLOUDSYNC_MEM_TYPES_H__ */ + diff --git a/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3.c b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3.c new file mode 100644 index 00000000000..019f98017dc --- /dev/null +++ b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3.c @@ -0,0 +1,553 @@ +/* + Copyright (c) 2018 Red Hat, Inc. <http://www.redhat.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include <stdlib.h> +#include <openssl/hmac.h> +#include <openssl/evp.h> +#include <openssl/bio.h> +#include <openssl/buffer.h> +#include <openssl/crypto.h> +#include <curl/curl.h> +#include "xlator.h" +#include "glusterfs.h" +#include "libcloudsyncs3.h" +#include "cloudsync-common.h" + +#define RESOURCE_SIZE 4096 + +store_methods_t store_ops = { + .fop_download = aws_download_s3, + .fop_init = aws_init, + .fop_reconfigure = aws_reconfigure, + .fop_fini = aws_fini, +}; + +typedef struct aws_private { + char *hostname; + char *bucketid; + char *awssekey; + char *awskeyid; + gf_boolean_t abortdl; + pthread_spinlock_t lock; +} aws_private_t; + +void * +aws_init (xlator_t *this) +{ + aws_private_t *priv = NULL; + char *temp_str = NULL; + int ret = 0; + + priv = GF_CALLOC (1, sizeof (aws_private_t), + gf_libaws_mt_aws_private_t); + if (!priv) { + gf_msg (this->name, GF_LOG_ERROR, 0, 0, "insufficient memory"); + ret = -1; + goto out; + } + + priv->abortdl = _gf_false; + + pthread_spin_init (&priv->lock, PTHREAD_PROCESS_PRIVATE); + + pthread_spin_lock (&(priv->lock)); + { + if (dict_get_str (this->options, "s3plugin-seckey", + &temp_str) == 0) { + priv->awssekey = gf_strdup (temp_str); + if (!priv->awssekey) { + gf_msg (this->name, GF_LOG_ERROR, ENOMEM, 0, + "initializing aws secret key failed"); + ret = -1; + goto unlock; + } + } + + if (dict_get_str (this->options, "s3plugin-keyid", + &temp_str) == 0) { + priv->awskeyid = gf_strdup (temp_str); + if (!priv->awskeyid) { + gf_msg (this->name, GF_LOG_ERROR, ENOMEM, 0, + "initializing aws key ID failed"); + ret = -1; + goto unlock; + } + } + + if (dict_get_str (this->options, "s3plugin-bucketid", + &temp_str) == 0) { + priv->bucketid = gf_strdup (temp_str); + if (!priv->bucketid) { + gf_msg (this->name, GF_LOG_ERROR, ENOMEM, 0, + "initializing aws bucketid failed"); + + ret = -1; + goto unlock; + } + } + + if (dict_get_str (this->options, "s3plugin-hostname", + &temp_str) == 0) { + priv->hostname = gf_strdup (temp_str); + if (!priv->hostname) { + gf_msg (this->name, GF_LOG_ERROR, ENOMEM, 0, + "initializing aws hostname failed"); + + ret = -1; + goto unlock; + } + } + + gf_msg_debug (this->name, 0, "stored key: %s id: %s " + "bucketid %s hostname: %s", priv->awssekey, + priv->awskeyid, priv->bucketid, priv->hostname); + + } +unlock: + pthread_spin_unlock (&(priv->lock)); + +out: + if (ret == -1) { + GF_FREE (priv->awskeyid); + GF_FREE (priv->awssekey); + GF_FREE (priv->bucketid); + GF_FREE (priv->hostname); + GF_FREE (priv); + priv = NULL; + } + + return (void *)priv; +} + +int +aws_reconfigure (xlator_t *this, dict_t *options) +{ + aws_private_t *priv = NULL; + char *temp_str = NULL; + int ret = 0; + cs_private_t *cspriv = NULL; + + cspriv = this->private; + + priv = cspriv->stores->config; + + if (!priv) { + gf_msg (this->name, GF_LOG_ERROR, 0, 0, "null priv"); + ret = -1; + goto out; + } + + pthread_spin_lock (&(priv->lock)); + { + if (dict_get_str (options, "s3plugin-seckey", + &temp_str) == 0) { + priv->awssekey = gf_strdup (temp_str); + if (!priv->awssekey) { + gf_msg (this->name, GF_LOG_ERROR, ENOMEM, 0, + "initializing aws secret key failed"); + ret = -1; + goto out; + } + } + + if (dict_get_str (options, "s3plugin-keyid", + &temp_str) == 0) { + priv->awskeyid = gf_strdup (temp_str); + if (!priv->awskeyid) { + gf_msg (this->name, GF_LOG_ERROR, ENOMEM, 0, + "initializing aws key ID failed"); + ret = -1; + goto out; + } + } + + if (dict_get_str (options, "s3plugin-bucketid", + &temp_str) == 0) { + priv->bucketid = gf_strdup (temp_str); + if (!priv->bucketid) { + gf_msg (this->name, GF_LOG_ERROR, ENOMEM, 0, + "initializing aws bucketid failed"); + ret = -1; + goto out; + } + } + + if (dict_get_str (options, "s3plugin-hostname", + &temp_str) == 0) { + priv->hostname = gf_strdup (temp_str); + if (!priv->hostname) { + gf_msg (this->name, GF_LOG_ERROR, ENOMEM, 0, + "initializing aws hostname failed"); + ret = -1; + goto out; + } + } + + } +out: + pthread_spin_unlock (&(priv->lock)); + + gf_msg_debug (this->name, 0, "stored key: %s id: %s " + "bucketid %s hostname: %s", priv->awssekey, + priv->awskeyid, priv->bucketid, priv->hostname); + + return ret; +} + +void +aws_fini (void *config) +{ + aws_private_t *priv = NULL; + + priv = (aws_private_t *)priv; + + if (priv) { + GF_FREE (priv->hostname); + GF_FREE (priv->bucketid); + GF_FREE (priv->awssekey); + GF_FREE (priv->awskeyid); + + pthread_spin_destroy (&priv->lock); + GF_FREE (priv); + } +} + +int32_t +mem_acct_init (xlator_t *this) +{ + int ret = -1; + + GF_VALIDATE_OR_GOTO ("dht", this, out); + + ret = xlator_mem_acct_init (this, gf_libaws_mt_end + 1); + + if (ret != 0) { + gf_msg (this->name, GF_LOG_ERROR, 0, 0, + "Memory accounting init failed"); + return ret; + } +out: + return ret; +} +char * +aws_form_request (char *resource, char **date, char *reqtype, char *bucketid, + char *filepath) +{ + char httpdate[256]; + time_t ctime; + struct tm *gtime = NULL; + char *sign_req = NULL; + + ctime = time(NULL); + gtime = gmtime(&ctime); + + memset (httpdate, 0, sizeof(httpdate)); + strftime (httpdate, sizeof(httpdate), "%a, %d %b %Y %H:%M:%S +0000", + gtime); + *date = gf_strdup (httpdate); + + memset (resource, 0, RESOURCE_SIZE); + + snprintf(resource, RESOURCE_SIZE, "%s/%s", bucketid, filepath); + + gf_msg_debug ("CS", 0, "resource %s", resource); + + sign_req = GF_CALLOC (1, 256, gf_common_mt_char); + + snprintf(sign_req, 256, "%s\n\n%s\n%s\n/%s", + reqtype, + "", + *date, + resource); + + return sign_req; +} + +char* +aws_b64_encode(const unsigned char *input, int length) +{ + BIO *bio, *b64; + BUF_MEM *bptr; + char *buff = NULL; + + b64 = BIO_new(BIO_f_base64()); + bio = BIO_new(BIO_s_mem()); + b64 = BIO_push(b64, bio); + BIO_write(b64, input, length); + BIO_flush(b64); + BIO_get_mem_ptr(b64, &bptr); + + buff = GF_CALLOC(1, (bptr->length), gf_common_mt_char); + memcpy(buff, bptr->data, bptr->length - 1); + buff[bptr->length - 1] = 0; + + BIO_free_all(b64); + + return buff; +} + +char * +aws_sign_request(char *const str, char *awssekey) +{ +#if (OPENSSL_VERSION_NUMBER < 0x1010002f) + HMAC_CTX ctx; +#endif + HMAC_CTX *pctx = NULL;; + + unsigned char md[256]; + unsigned len; + char *base64 = NULL; + +#if (OPENSSL_VERSION_NUMBER < 0x1010002f) + HMAC_CTX_init (&ctx); + pctx = &ctx; +#else + pctx = HMAC_CTX_new (); +#endif + HMAC_Init_ex (pctx, awssekey, strlen(awssekey), EVP_sha1(), NULL); + HMAC_Update (pctx, (unsigned char *)str, strlen(str)); + HMAC_Final (pctx, (unsigned char *)md, &len); + +#if (OPENSSL_VERSION_NUMBER < 0x1010002f) + HMAC_CTX_cleanup (pctx); +#else + HMAC_CTX_free (pctx); +#endif + base64 = aws_b64_encode(md, len); + + return base64; +} + +int +aws_dlwritev_cbk (call_frame_t *frame, void *cookie, xlator_t *this, + int op_ret, int op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + aws_private_t *priv = NULL; + + if (op_ret == -1) { + gf_msg (this->name, GF_LOG_ERROR, 0, op_errno, "write failed " + ". Aborting Download"); + + priv = this->private; + pthread_spin_lock (&(priv->lock)); + { + priv->abortdl = _gf_true; + } + pthread_spin_unlock (&(priv->lock)); + } + + CS_STACK_DESTROY (frame); + + return op_ret; +} + +size_t +aws_write_callback (void *dlbuf, size_t size, size_t nitems, void *mainframe) +{ + call_frame_t *frame = NULL; + fd_t *dlfd = NULL; + int ret = 0; + cs_local_t *local = NULL; + struct iovec iov = {0,}; + struct iobref *iobref = NULL; + struct iobuf *iobuf = NULL; + struct iovec dliov = {0, }; + size_t tsize = 0; + xlator_t *this = NULL; + cs_private_t *xl_priv = NULL; + aws_private_t *priv = NULL; + call_frame_t *dlframe = NULL; + + frame = (call_frame_t *)mainframe; + this = frame->this; + xl_priv = this->private; + priv = xl_priv->stores->config; + + pthread_spin_lock (&(priv->lock)); + { + /* returning size other than the size passed from curl will + * abort further download*/ + if (priv->abortdl) { + gf_msg (this->name, GF_LOG_ERROR, 0, 0, + "aborting download"); + pthread_spin_unlock (&(priv->lock)); + return 0; + } + } + pthread_spin_unlock (&(priv->lock)); + + local = frame->local; + dlfd = local->dlfd; + tsize = size * nitems; + + dliov.iov_base = (void *)dlbuf; + dliov.iov_len = tsize; + + ret = iobuf_copy (this->ctx->iobuf_pool, &dliov, 1, &iobref, &iobuf, + &iov); + if (ret) { + gf_msg (this->name, GF_LOG_ERROR, 0, 0, "iobuf_copy failed"); + goto out; + } + + /* copy frame */ + dlframe = copy_frame (frame); + if (!dlframe) { + gf_msg (this->name, GF_LOG_ERROR, 0, 0, "copy_frame failed"); + tsize = 0; + goto out; + } + + STACK_WIND (dlframe, aws_dlwritev_cbk, FIRST_CHILD (this), + FIRST_CHILD (this)->fops->writev, dlfd, + &iov, 1, local->dloffset, 0, iobref, NULL); + + local->dloffset += tsize; + +out: + if (iobuf) + iobuf_unref (iobuf); + if (iobref) + iobref_unref (iobref); + + return tsize; +} + +int +aws_download_s3 (call_frame_t *frame, void *config) +{ + char buf[1024]; + CURL *handle = NULL; + struct curl_slist *slist = NULL; + struct curl_slist *tmp = NULL; + xlator_t *this = NULL; + int ret = 0; + int debug = 1; + CURLcode res; + char errbuf[CURL_ERROR_SIZE]; + size_t len = 0; + long responsecode; + char *sign_req = NULL; + char *date = NULL; + char *const reqtype = "GET"; + char *signature = NULL; + cs_local_t *local = NULL; + char resource[4096] = {0,}; + aws_private_t *priv = NULL; + + local = frame->local; + + priv = (aws_private_t *)config; + + if (!priv->bucketid || !priv->hostname || !priv->awssekey || + !priv->awskeyid) { + ret = -1; + goto out; + } + + sign_req = aws_form_request (resource, &date, reqtype, priv->bucketid, + local->remotepath); + if (!sign_req) { + gf_msg (this->name, GF_LOG_ERROR, 0, 0, "null sign_req, " + "aborting download"); + ret = -1; + goto out; + } + + gf_msg_debug ("CS", 0, "sign_req %s date %s", sign_req, date); + + signature = aws_sign_request (sign_req, priv->awssekey); + if (!signature) { + gf_msg ("CS", GF_LOG_ERROR, 0, 0, "null signature, " + "aborting download"); + ret = -1; + goto out; + } + + handle = curl_easy_init(); + this = frame->this; + + snprintf (buf, 1024, "Date: %s", date); + slist = curl_slist_append(slist, buf); + snprintf (buf, sizeof(buf), "Authorization: AWS %s:%s", priv->awskeyid, + signature); + slist = curl_slist_append(slist, buf); + snprintf(buf, sizeof(buf), "https://%s/%s", priv->hostname, resource); + + if (gf_log_get_loglevel () >= GF_LOG_DEBUG) { + tmp = slist; + while (tmp) { + gf_msg_debug (this->name, 0, "slist for curl - %s", + tmp->data); + tmp = tmp->next; + } + } + + curl_easy_setopt (handle, CURLOPT_HTTPHEADER, slist); + curl_easy_setopt (handle, CURLOPT_URL, buf); + curl_easy_setopt (handle, CURLOPT_WRITEFUNCTION, aws_write_callback); + curl_easy_setopt (handle, CURLOPT_WRITEDATA, frame); + curl_easy_setopt (handle, CURLOPT_VERBOSE, debug); + curl_easy_setopt (handle, CURLOPT_ERRORBUFFER, errbuf); + + res = curl_easy_perform(handle); + if (res != CURLE_OK) { + gf_msg (this->name, GF_LOG_ERROR, 0, 0, + "download failed. err: %s\n", curl_easy_strerror(res)); + ret = -1; + len = strlen(errbuf); + if (len) { + gf_msg (this->name, GF_LOG_ERROR, 0, 0, + "curl failure %s", errbuf); + } else { + gf_msg (this->name, GF_LOG_ERROR, 0, 0, "curl error " + "%s\n", curl_easy_strerror(res)); + } + } + + if (res == CURLE_OK) { + curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, + &responsecode); + gf_msg_debug (this->name, 0, "response code %ld", responsecode); + if (responsecode != 200) { + ret = -1; + gf_msg (this->name, GF_LOG_ERROR, 0, 0, + "curl download failed"); + } + } + + curl_slist_free_all(slist); + curl_easy_cleanup(handle); + +out: + return ret; +} + +struct volume_options cs_options[] = { + { .key = {"s3plugin-seckey"}, + .type = GF_OPTION_TYPE_STR, + .description = "aws secret key" + }, + { .key = {"s3plugin-keyid"}, + .type = GF_OPTION_TYPE_STR, + .description = "aws key ID" + + }, + { .key = {"s3plugin-bucketid"}, + .type = GF_OPTION_TYPE_STR, + .description = "aws bucketid" + }, + { .key = {"s3plugin-hostname"}, + .type = GF_OPTION_TYPE_STR, + .description = "aws hostname e.g. s3.amazonaws.com" + }, + { .key = {NULL} }, +}; diff --git a/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3.h b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3.h new file mode 100644 index 00000000000..c233e1c96f7 --- /dev/null +++ b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2018 Red Hat, Inc. <http://www.redhat.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ +#ifndef _LIBAWS_H +#define _LIBAWS_H + +#include "glusterfs.h" +#include "call-stub.h" +#include "xlator.h" +#include "syncop.h" +#include <curl/curl.h> +#include "cloudsync-common.h" +#include "libcloudsyncs3-mem-types.h" + + +char* +aws_b64_encode(const unsigned char *input, int length); + +size_t +aws_write_callback(void *dlbuf, size_t size, size_t nitems, void *mainframe); + +int +aws_download_s3 (call_frame_t *frame, void *config); + +int +aws_dlwritev_cbk (call_frame_t *frame, void *cookie, xlator_t *this, + int op_ret, int op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata); + +void * +aws_init (xlator_t *this); + +int +aws_reconfigure (xlator_t *this, dict_t *options); + +char * +aws_form_request (char *resource, char **date, char *reqtype, char *bucketid, + char *filepath); +char * +aws_sign_request(char *const str, char *awssekey); + +void +aws_fini (void *config); + +#endif diff --git a/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3.sym b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3.sym new file mode 100644 index 00000000000..0bc273670d5 --- /dev/null +++ b/xlators/features/cloudsync/src/cloudsync-plugins/src/cloudsyncs3/src/libcloudsyncs3.sym @@ -0,0 +1 @@ +store_ops |