]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
Introduce 'FreeSpace' storage policy
authorMichal Rakowski <michal.rakowski@baculasystems.com>
Tue, 27 Jul 2021 20:58:16 +0000 (22:58 +0200)
committerEric Bollengier <eric@baculasystems.com>
Fri, 30 Jun 2023 16:33:45 +0000 (18:33 +0200)
This policy queries each SD in the list for its FreeSpace
(as a sum of devices specified in the SD config)
and sort the list by FreeSpace returned.

bacula/src/dird/job.c
bacula/src/dird/store_mngr.c
bacula/src/dird/store_mngr.h
bacula/src/lib/parse_conf.c
bacula/src/lib/store_mngr_cmds.h [new file with mode: 0644]
bacula/src/stored/Makefile.in
bacula/src/stored/dircmd.c
bacula/src/stored/store_mngr.c [new file with mode: 0644]
bacula/src/stored/store_mngr.h [new file with mode: 0644]
bacula/src/stored/stored.h

index 160da65b396f7c89202b706b07b686d8eb7ff0d8..274913d5fe1272538d115046c0f2f59582d870f6 100644 (file)
@@ -1661,6 +1661,12 @@ static void init_store_manager(JCR *jcr, const char *policy)
       } else if (strcmp(policy, "ListedOrder") == 0) {
          Dmsg1(dbglvl_store_mngr, "Setting ListedOrder storage group policy for JobId: %d\n", jcr->JobId);
          jcr->store_mngr = New(ListedOrderStore());
+      } else if (strcmp(policy, "FreeSpace") == 0) {
+         Dmsg1(dbglvl_store_mngr, "Setting FreeSpace storage group policy for JobId: %d\n", jcr->JobId);
+         jcr->store_mngr = New(FreeSpaceStore());
+      } else {
+         Dmsg1(dbglvl_store_mngr, "Invalid policy for JobId: %d, setting default (ListedOrder)\n", jcr->JobId);
+         jcr->store_mngr = New(ListedOrderStore());
       }
    } else {
       Dmsg1(dbglvl_store_mngr, "Setting ListedOrder storage group policy for JobId: %d\n", jcr->JobId);
index 032e14824e80813a04aecf5b2528edc3640b3794..141dd7845fa23ab033fbdac462fae6edb1670b82 100644 (file)
@@ -20,6 +20,7 @@
 #include "bacula.h"
 #include "dird.h"
 #include "lib/lockmgr.h"
+#include "lib/store_mngr_cmds.h"
 
 static const int dbglvl = 200;
 
@@ -331,6 +332,131 @@ static void swapit(uint32_t *v1, uint32_t *v2)
    *v2 = temp;
 }
 
+void QueryStore::apply_policy(bool write_store) {
+   alist *list = write_store ? wstore.get_list() : rstore.get_list();
+   STORE *store = NULL;
+   JCR *tmp_jcr = new_jcr(sizeof(JCR), dird_free_jcr); // temporary jcr needed for connecting to the SD
+   BSOCK *sd = tmp_jcr->store_bsock;
+   sm_ctx *context = NULL;
+   dlist *d_list = New(dlist(context, &context->link));
+
+   /* Init store mngr so that we can assing current storage to the jcr - needed for the connect call below */
+   tmp_jcr->store_mngr = New(ListedOrderStore());
+
+   /* Query Storage daemon for needed information for each storage in the list */
+   foreach_alist(store, list) {
+      if (write_store) {
+         tmp_jcr->store_mngr->set_wstorage(store, "qeuerystore_applypolicy");
+      } else {
+         tmp_jcr->store_mngr->set_rstore(store, "qeuerystore_applypolicy");
+      }
+
+      if (!connect_to_storage_daemon(tmp_jcr, 1, 1, 0)) {
+         Dmsg1(dbglvl, "Failed to connect to the Storage: %s during policy quering\n", store->name());
+         continue;
+      }
+
+      sd = tmp_jcr->store_bsock;
+
+      /* Init storage querying */
+      if (!sd->fsend(store_query)) {
+         Dmsg2(dbglvl, "Failed to send \"%s\" command to the %s Storage\n", store_query, store->name());
+         continue;
+      }
+
+      sm_ctx *context = New(sm_ctx(store));
+
+      /* Execute the policy-specific querying */
+      int ret = query(sd, d_list, context);
+
+      sd->signal(BNET_TERMINATE);
+      sd->close();
+
+      if(!ret) {
+         Dmsg1(dbglvl, "Failed to query '\%s\' Storage\n", store->name());
+         delete context; /* Free context since it was not added to the list */
+
+         /* Continue in case some more processing is needed - we don't want to work on
+            invalid data */
+         continue;
+      }
+   }
+
+   /* Let the policy reorder storage list according to the information gathered */
+   reorder_list(list, d_list);
+
+   if (d_list) {
+      delete d_list;
+   }
+
+   if (tmp_jcr) {
+      free_jcr(tmp_jcr);
+   }
+}
+
+void FreeSpaceStore::reorder_list(alist *list, dlist *d_list) {
+   sm_ctx *ctx;
+   STORE *store;
+
+   list->destroy();
+   list->init(10, not_owned_by_alist);
+
+   Dmsg0(dbglvl, "Storage List after applying FreeSpace policy:\n");
+   foreach_dlist(ctx, d_list) {
+      store = ctx->store;
+      list->append(store);
+      Dmsg2(dbglvl, "*** store: %s size: %llu\n", ctx->store->name(), ctx->number);
+   }
+}
+
+bool FreeSpaceStore::query(BSOCK *sd, dlist *d_list, sm_ctx *context) {
+   STORE *store = context->store;
+   bool ret = false;
+   POOL_MEM device_name;
+   DEVICE *dev;
+   uint64_t fs;
+   uint64_t *size = (uint64_t *) &context->number;
+
+   if (sd->recv() >= 0) {
+       Dmsg1(dbglvl, "<stored: %s", sd->msg);
+       if (strncmp(sd->msg, OK_store_query, strlen(OK_store_query)) != 0) {
+          Dmsg2(dbglvl, "Failed to query storage: %s msg=%s\n", store->name(), sd->msg);
+          goto bail_out;
+       }
+   } else {
+      Dmsg1(dbglvl, "Failed to query storage: %s\n", store->name());
+      goto bail_out;
+   }
+
+   /* Now go through the list of devices for the storage resource and
+    * query each for available storage space */
+   foreach_alist(dev, store->device) {
+      pm_strcpy(device_name, dev->name());
+      bash_spaces(device_name.c_str());
+      sd->fsend(store_query_freespace, device_name.c_str());
+   }
+
+   sd->signal(BNET_EOD);
+
+   if (sd->recv() >= 0) {
+      ret = sscanf(sd->msg, OK_store_size, &fs) == 1;
+      if (!ret) {
+         Dmsg1(dbglvl, "Failed to get size for storage: %s\n", store->name());
+         goto bail_out;
+      }
+
+      *size = fs;
+   }
+
+   // Insert store size to the list
+   d_list->binary_insert_multiple(context, &FreeSpaceStore::cmp);
+
+   ret = true;
+
+bail_out:
+   return ret;
+}
+
 void LeastUsedStore::apply_policy(bool write_store) {
    alist *store = write_store ? wstore.get_list() : rstore.get_list();
    alist tmp_list(10, not_owned_by_alist);
index 5f7c1a230a8c371571d8cf7fadffae921b2fdf27..2b93894fcba32e81e0874c7fc0667da8d14525c9 100644 (file)
@@ -29,7 +29,6 @@
 /* Forward delcaration */
 class STORE;
 
-
 /*
  * Helper class to make managing each storage type (write/read) easier.
  * It contains storage resource ('store' member) which is currently used as well as list of all
@@ -246,4 +245,87 @@ class ListedOrderStore : public StorageManager {
    }
 };
 
+/* Context with per-policy specific data (as of now there's only single uint64_t value available for each policy)*/
+class sm_ctx : public SMARTALLOC {
+   public:
+      STORE *store;
+      dlink link;
+      uint64_t number; /* Per-policy specific number (e.g. free space, cpu usage...) */
+
+      sm_ctx(STORE *s) {
+         store = s;
+         number = 0;
+      }
+};
+
+/*
+ * Abstract class for policies which require DIR<->SD querying.
+ * Each querying policy has to implement query() and reorder() methods - theese act
+ * as a callbacks for QueryStore's generic methods.
+ */
+class QueryStore : public StorageManager {
+   protected:
+      /* Policy-specific query method */
+      virtual bool query(BSOCK *sd, dlist *d_list, sm_ctx *context) = 0;
+
+      /* Reorder storage list after querying all storages in the list for needed information */
+      virtual void reorder_list(alist *list, dlist *d_list) = 0;
+
+   public:
+      void apply_policy(bool write_store);
+
+   QueryStore (const char *policy="VirtualPolicy_QueryStore"): StorageManager(policy)  {
+   }
+
+   ~QueryStore() {
+   }
+};
+
+/*
+ * Policy which reorders storage list based on FreeSpace available on each Storage
+ */
+class FreeSpaceStore : public QueryStore {
+   private:
+      /* Private helper struct */
+      struct store_free_size {
+         STORE *store;
+         uint64_t free_size;
+         dlink link;
+      };
+
+      bool query(BSOCK *sd, dlist *d_list, sm_ctx *context);
+
+      /* Comparator for easy list ordering */
+      static int cmp(void *item1, void *item2) {
+         sm_ctx *ctx1 = (sm_ctx *) item1;
+         sm_ctx *ctx2 = (sm_ctx *) item2;
+         uint64_t s1 = ctx1->number;
+         uint64_t s2 = ctx2->number;
+
+         if (s1 < s2) {
+            return 1;
+         } else if (s1 > s2) {
+            return -1;
+         } else {
+            return 0;
+         }
+      }
+
+      void reorder_list(alist *list, dlist *d_list);
+
+   public:
+      void apply_write_policy() {
+         return apply_policy(true);
+      }
+      void apply_read_policy() {
+         return apply_policy(false);
+      }
+
+   FreeSpaceStore(): QueryStore("FreeSpace") {
+   }
+
+   ~FreeSpaceStore() {
+   }
+};
+
 #endif // STORE_MNGR_H
index b878657c6ed90b888cb6e046813ffe0a5d71273e..17ef721ad8e427c000f98fdd56531c853297ea94 100644 (file)
@@ -1017,6 +1017,7 @@ void store_coll_type(LEX *lc, RES_ITEM *item, int index, int pass)
 static char const *storage_mngmt_policy[] = {
    "LeastUsed",
    "ListedOrder",
+   "FreeSpace",
    NULL
 };
 
diff --git a/bacula/src/lib/store_mngr_cmds.h b/bacula/src/lib/store_mngr_cmds.h
new file mode 100644 (file)
index 0000000..083a35c
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2023 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Commands defined for the Storage Manager, for communication between the DIR and SD.
+ */
+
+#ifndef STORE_MNGR_CMDS_H
+#define STORE_MNGR_CMDS_H 1
+
+static const char store_query[] = "store_mngr query\n";
+static const char store_query_freespace[] = "store_mngr freespace device=%s\n";
+static char OK_store_query[] = "3000 OK query store\n";
+static char store_query_unsupp_policy[] = "3200 unsupported policy\n";
+static char OK_store_size[]   = "3000 OK size=%llu\n";
+
+#endif // STORE_MNGR_CMDS_H
index 13cc5bdeaed7a1e8f60d1f365059f84ab78d2e15..064f607f642a17ce4fa13488538b2e59e4e1d502 100644 (file)
@@ -49,7 +49,7 @@ SDCORE_OBJS = \
 
 # bacula-sd
 SDOBJS = \
-   stored.o append.o authenticate.o dircmd.o fd_cmds.o job.o \
+   stored.o append.o authenticate.o store_mngr.o dircmd.o fd_cmds.o job.o \
    hello.o status.o vbackup.o \
    sdcollect.o $(SDCORE_OBJS)  $(EXTRA_STORED_OBJS)
 
index 46b6f121dfceae28a3231da690fca6d58c8162a9..6bb2a89d2a0b78b7f891a30b45b425386c1fdba5 100644 (file)
@@ -62,6 +62,7 @@ extern bool run_cmd(JCR *jcr);
 extern bool status_cmd(JCR *sjcr);
 extern bool qstatus_cmd(JCR *jcr);
 extern bool collect_cmd(JCR *jcr);
+extern bool query_store(JCR *jcr);
 //extern bool query_cmd(JCR *jcr);
 
 /* Forward referenced functions */
@@ -146,6 +147,7 @@ static struct dir_cmds cmds[] = {
 #endif
    {"run",         run_cmd,         0},
    {"statistics",  collect_cmd,     0},
+   {"store_mngr",  query_store,     0},
 // {"query",       query_cmd,       0},
    {NULL,        NULL}                      /* list terminator */
 };
diff --git a/bacula/src/stored/store_mngr.c b/bacula/src/stored/store_mngr.c
new file mode 100644 (file)
index 0000000..2799897
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2023 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Storage daemon's part of Storage Manager - responsible for retrieving needed
+ * information in response to Director's query.
+ */
+
+#include "bacula.h"
+#include "stored.h"
+#include "store_mngr.h"
+#include "lib/store_mngr_cmds.h"
+
+static const int dbglvl = 200;
+
+/* Helper Compare method for the FreeSpace policy.
+ * It's purpose is to have list with unique devices. */
+static int query_size_cmp(void *item1, void *item2) {
+   struct query_size *qs1 = (struct query_size *) item1;
+   struct query_size *qs2 = (struct query_size *) item2;
+
+   /* We only want to avoid duplicates, order does not matter at all */
+   if (qs1->devno == qs2->devno) {
+      return 0;
+   }
+
+   return 1;
+}
+
+static bool query_freespace(JCR *jcr, const char *dev_name) {
+   BSOCK *dir = jcr->dir_bsock;
+   DEVRES *device;
+   AUTOCHANGER *changer;
+   uint64_t ts, fs, free_sum = 0;
+   struct query_size *qs;
+   alist dev_list(10, not_owned_by_alist);
+   dlist size_list;
+   bool found;
+
+   /* Device sent can be either single device or an autochanger */
+   foreach_res(changer, R_AUTOCHANGER) {
+      if (strcmp(changer->hdr.name, dev_name) == 0) {
+         /* For now we sum free size of all autochanger's unique devices and report it as a single value
+          * back to the DIR in response to the FreeSpace policy querying.
+          * We may want to change it at some point if we see that some other calculations/reporting make more sense  */
+         foreach_alist(device, changer->device) {
+            /* devno == 0 means that it's just an different directory of a device we already have */
+            if (device && device->dev->devno) {
+               device->dev->get_freespace(&fs, &ts);
+               /* Alloc helper struct, set correct size for this dev and add it to list
+                * so that later we can sum size of all unique devices */
+               qs = (struct query_size *) malloc(sizeof(struct query_size));
+               if (!qs) {
+                  Dmsg1(dbglvl, "Failed to alloc query_size for dev: %s, policy querying stopped\n", device->device_name);
+                  break;
+               } else {
+                  qs->size = fs;
+                  qs->devno = device->dev->devno;
+                  dev_list.append(qs);
+               }
+            }
+         }
+
+         found = true; /* No need to go through the device resource list now */
+         break;
+      }
+   }
+
+   if (!found) {
+      /* Device sent to query is a single device instead of an autochanger */
+      foreach_res(device, R_DEVICE) {
+         if (strcmp(device->hdr.name, dev_name) == 0) {
+            device->dev->get_freespace(&fs, &ts);
+            qs = (struct query_size *) malloc(sizeof(struct query_size));
+            if (!qs) {
+               Dmsg1(dbglvl, "Failed to alloc query_size for dev: %s, policy querying stopped\n", device->device_name);
+               break;
+            } else {
+               qs->size = fs;
+               qs->devno = device->dev->devno;
+            }
+            dev_list.append(qs);
+            found = true;
+            break;
+         }
+      }
+   }
+
+   /* Add free size for each unique device */
+   foreach_alist(qs, &dev_list) {
+      struct query_size *tmp = (struct query_size *) size_list.binary_insert(qs, query_size_cmp);
+      if (tmp) {
+         free_sum+=tmp->size;
+      }
+   }
+
+   dir->fsend(OK_store_size, free_sum);
+
+   return found;
+}
+
+/* Handler for DIR's 'store_mngr query' command */
+bool query_store(JCR *jcr) {
+   BSOCK *dir = jcr->dir_bsock;
+   POOL_MEM dev_name;
+   bool ok = false;
+
+   if (strncmp(dir->msg, store_query, strlen(store_query)) != 0) {
+      return false;
+   }
+
+   dir->fsend(OK_store_query);
+
+   /* Return parameters asked by the DIR */
+   if (dir->recv() >= 0) {
+      if (sscanf(dir->msg, store_query_freespace, dev_name.c_str()) == 1) {
+         unbash_spaces(dev_name.c_str());
+
+         ok = query_freespace(jcr, dev_name.c_str());
+      } else {
+         dir->fsend(store_query_unsupp_policy);
+      }
+   }
+
+   return ok;
+}
diff --git a/bacula/src/stored/store_mngr.h b/bacula/src/stored/store_mngr.h
new file mode 100644 (file)
index 0000000..67c4daa
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2023 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Storage Daemon's part of Storage Manager - responsible for retrieving needed
+ * information in response to Director's query.
+ */
+
+#ifndef STORE_MNGR_H
+#define STORE_MNGR_H 1
+
+#include "bacula.h"
+
+
+/* FreeSpace policy struct to easily get rid of device duplicates in the list */
+struct query_size {
+   struct dlink link;
+   uint64_t devno; /* Unique device number */
+   uint64_t size;  /* Device's freespace in bytes */
+};
+
+/* Main FreeSpace policy handler */
+bool query_store(JCR *jcr);
+
+#endif // STORE_MNGR_H
index ceb0214440ee73279dca6b77236b4a27df9c7854..dde26f7fda521fb1e622854b7e3867930ccf8ab3 100644 (file)
@@ -95,6 +95,7 @@ const int sd_dbglvl = 300;
 #include "vtape_dev.h"
 #include "cloud_dev.h"
 #include "aligned_dev.h"
+#include "store_mngr.h"
 #ifdef SD_DEDUP_SUPPORT
 #include "dedup_dev.h"
 #endif