/*
* Amanda, The Advanced Maryland Automatic Network Disk Archiver
* Copyright (c) 1991-1999 University of Maryland at College Park
* Copyright (c) 2007-2012 Zmanda, Inc. All Rights Reserved.
* Copyright (c) 2013-2016 Carbonite, Inc. All Rights Reserved.
* All Rights Reserved.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of U.M. not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. U.M. makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
* U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Authors: the Amanda Development Team. Its members are listed in a
* file named AUTHORS, in the root directory of this distribution.
*/
/*
* $Id: planner.c 10421 2008-03-06 18:48:30Z martineau $
*
* backup schedule planner for the Amanda backup system.
*/
#include "amanda.h"
#include "find.h"
#include "conffile.h"
#include "diskfile.h"
#include "tapefile.h"
#include "infofile.h"
#include "logfile.h"
#include "clock.h"
#include "packet.h"
#include "security.h"
#include "protocol.h"
#include "version.h"
#include "amfeatures.h"
#include "server_util.h"
#include "holding.h"
#include "timestamp.h"
#include "amxml.h"
#include "cmdfile.h"
#define planner_debug(i,x) do { \
if ((i) <= debug_planner) { \
dbprintf(x); \
} \
} while (0)
#define MAX_LEVELS 3 /* max# of estimates per filesys */
#define RUNS_REDZONE 5 /* should be in conf file? */
#define PROMOTE_THRESHOLD 0.05 /* if <5% unbalanced, don't promote */
#define DEFAULT_DUMPRATE 1024.0 /* K/s */
/* configuration file stuff */
char * conf_tapetype;
gint64 conf_maxdumpsize;
int conf_runtapes;
int conf_dumpcycle;
int conf_runspercycle;
int conf_tapecycle;
time_t conf_etimeout;
int conf_reserve;
int conf_usetimestamps;
#define HOST_READY (0) /* must be 0 */
#define HOST_ACTIVE (1)
#define HOST_DONE (2)
#define DISK_READY 0 /* must be 0 */
#define DISK_ACTIVE 1
#define DISK_PARTIALY_DONE 2
#define DISK_DONE 3
typedef struct one_est_s {
int level;
gint64 nsize; /* native size */
gint64 csize; /* compressed size */
char *dumpdate;
int guessed; /* If server guessed the estimate size */
} one_est_t;
static one_est_t default_one_est = {-1, -1, -1, "INVALID_DATE", 0};
typedef struct est_s {
disk_t *disk;
int state;
int got_estimate;
int dump_priority;
one_est_t *dump_est;
one_est_t *degr_est;
one_est_t estimate[MAX_LEVELS];
gboolean allow_server;
int last_level;
gint64 last_lev0size;
int next_level0;
int level_days;
int promote;
int post_dle;
double fullrate, incrrate;
double fullcomp, incrcomp;
char *errstr;
char *degr_mesg;
info_t *info;
} est_t;
typedef struct estlist_s {
GList *head, *tail;
} estlist_t;
#define get_est(elist) ((est_t *)((elist)->data))
/* pestq = partial estimate */
estlist_t startq; // REQ not yet send.
estlist_t waitq; // REQ send, waiting for PREP or REP.
estlist_t pestq; // PREP received, waiting for PREP or REP
estlist_t estq; // REP received and valid, analyze not done
estlist_t failq; // REP received, failed
estlist_t schedq; // REP received and valid, analyze done.
estlist_t activeq; //
gint64 total_size;
double total_lev0, balanced_size, balance_threshold;
gint64 tape_length;
size_t tape_mark;
tapetype_t *tape;
size_t tt_blocksize;
size_t tt_blocksize_kb;
int runs_per_cycle = 0;
time_t today;
char *planner_timestamp = NULL;
char *log_filename = NULL;
static am_feature_t *our_features = NULL;
static char *our_feature_string = NULL;
/* We keep a LIFO queue of before images for all modifications made
* to schedq in our attempt to make the schedule fit on the tape.
* Enough information is stored to reinstate a dump if it turns out
* that it shouldn't have been touched after all.
*/
typedef struct bi_s {
struct bi_s *next;
struct bi_s *prev;
int deleted; /* 0=modified, 1=deleted */
est_t *ep; /* The disk that was changed */
int level; /* The original level */
gint64 nsize; /* The original native size */
gint64 csize; /* The original compressed size */
char *errstr; /* A message describing why this disk is here */
} bi_t;
typedef struct bilist_s {
bi_t *head, *tail;
} bilist_t;
bilist_t biq; /* The BI queue itself */
typedef struct cmdfile_data_s {
char *ids;
char *holding_file;
} cmdfile_data_t;
/*
* ========================================================================
* MAIN PROGRAM
*
*/
static void setup_estimate(disk_t *dp);
static void get_estimates(void);
static void analyze_estimate(est_t *est);
static void handle_failed(est_t *est);
static void delay_dumps(void);
static int promote_highest_priority_incremental(void);
static int promote_hills(void);
static void output_scheduleline(est_t *est);
static void server_estimate(est_t *est, int i, info_t *info, int level,
tapetype_t *tapetype);
static void cmdfile_flush(gpointer key G_GNUC_UNUSED, gpointer value,
gpointer user_data);
static void enqueue_est(estlist_t *list, est_t *est);
static void insert_est(estlist_t *list, est_t *est, int (*cmp)(est_t *a, est_t *b));
static est_t *find_est_for_dp(disk_t *dp);
static est_t *dequeue_est(estlist_t *list);
static void remove_est(estlist_t *list, est_t * est);
static void est_dump_queue(char *st,
estlist_t q,
int npr,
FILE *f);
int main(int, char **);
int
main(
int argc,
char ** argv)
{
disklist_t origq = { NULL, NULL };
GList *dlist;
disk_t *dp;
int moved_one;
int diskarg_offset;
gint64 initial_size;
char *conf_diskfile;
char *conf_tapelist;
char *conf_cmdfile;
char *conf_infofile;
times_t section_start;
char *qname;
char *errstr = NULL;
GPtrArray *err_array;
guint i;
config_overrides_t *cfg_ovr = NULL;
char *cfg_opt = NULL;
int exit_status = EXIT_SUCCESS;
gboolean no_taper = FALSE;
gboolean no_dump = FALSE;
gboolean no_flush = FALSE;
//gboolean no_vault = FALSE;
gboolean from_client = FALSE;
gboolean exact_match = FALSE;
storage_t *storage;
policy_s *policy;
char *storage_name;
identlist_t il;
cmddatas_t *cmddatas;
if (argc > 1 && argv && argv[1] && g_str_equal(argv[1], "--version")) {
printf("planner-%s\n", VERSION);
return (0);
}
glib_init();
/*
* Configure program for internationalization:
* 1) Only set the message locale for now.
* 2) Set textdomain for all amanda related programs to "amanda"
* We don't want to be forced to support dozens of message catalogs.
*/
setlocale(LC_MESSAGES, "C");
textdomain("amanda");
if (geteuid() != getuid()) {
error(_("planner must not be setuid root"));
}
/* drop root privileges */
set_root_privs(-1);
safe_fd(-1, 0);
set_pname("planner");
dbopen(DBG_SUBDIR_SERVER);
cfg_ovr = extract_commandline_config_overrides(&argc, &argv);
if (argc > 1)
cfg_opt = argv[1];
set_config_overrides(cfg_ovr);
config_init_with_global(CONFIG_INIT_EXPLICIT_NAME, cfg_opt);
/* conf_diskfile is freed later, as it may be used in an error message */
conf_diskfile = config_dir_relative(getconf_str(CNF_DISKFILE));
read_diskfile(conf_diskfile, &origq);
disable_skip_disk(&origq);
/* Don't die when child closes pipe */
signal(SIGPIPE, SIG_IGN);
setvbuf(stderr, (char *)NULL, (int)_IOLBF, 0);
add_amanda_log_handler(amanda_log_stderr);
add_amanda_log_handler(amanda_log_trace_log);
if (config_errors(NULL) >= CFGERR_ERRORS) {
g_critical(_("errors processing config file"));
}
safe_cd();
check_running_as(RUNNING_AS_UID_ONLY);
dbrename(get_config_name(), DBG_SUBDIR_SERVER);
startclock();
section_start = curclock();
our_features = am_init_feature_set();
our_feature_string = am_feature_to_string(our_features);
g_fprintf(stderr, _("%s: pid %ld executable %s version %s\n"),
get_pname(), (long) getpid(), argv[0], VERSION);
for (i = 0; version_info[i] != NULL; i++)
g_fprintf(stderr, _("%s: %s"), get_pname(), version_info[i]);
diskarg_offset = 2;
if (argc - diskarg_offset > 1 && g_str_equal(argv[diskarg_offset],
"--starttime")) {
planner_timestamp = g_strdup(argv[diskarg_offset+1]);
diskarg_offset += 2;
}
if (argc - diskarg_offset > 0 && g_str_equal(argv[diskarg_offset],
"--log-filename")) {
log_filename = g_strdup(argv[diskarg_offset+1]);
set_logname(log_filename);
diskarg_offset += 2;
}
if (argc - diskarg_offset > 0 && g_str_equal(argv[diskarg_offset],
"--no-taper")) {
no_taper = TRUE;
diskarg_offset += 1;
}
if (argc - diskarg_offset > 0 && g_str_equal(argv[diskarg_offset],
"--no-dump")) {
no_dump = TRUE;
diskarg_offset += 1;
}
if (argc - diskarg_offset > 0 && g_str_equal(argv[diskarg_offset],
"--no-flush")) {
no_flush = TRUE;
diskarg_offset += 1;
}
if (argc - diskarg_offset > 0 && g_str_equal(argv[diskarg_offset],
"--no-vault")) {
//no_vault = TRUE;
diskarg_offset += 1;
}
if (argc - diskarg_offset > 0 && g_str_equal(argv[diskarg_offset],
"--from-client")) {
from_client = TRUE;
diskarg_offset += 1;
}
if (argc - diskarg_offset > 0 && g_str_equal(argv[diskarg_offset],
"--exact-match")) {
exact_match = TRUE;
diskarg_offset += 1;
}
log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
run_server_global_scripts(EXECUTE_ON_PRE_ESTIMATE, get_config_name(),
planner_timestamp);
/*
* 1. Networking Setup
*
*/
protocol_init();
/*
* 2. Read in Configuration Information
*
* All the Amanda configuration files are loaded before we begin.
*/
g_fprintf(stderr,_("READING CONF INFO...\n"));
if(origq.head == NULL) {
error(_("empty disklist \"%s\""), conf_diskfile);
/*NOTREACHED*/
}
amfree(conf_diskfile);
conf_tapelist = config_dir_relative(getconf_str(CNF_TAPELIST));
if(read_tapelist(conf_tapelist)) {
error(_("could not load tapelist \"%s\""), conf_tapelist);
/*NOTREACHED*/
}
amfree(conf_tapelist);
conf_infofile = config_dir_relative(getconf_str(CNF_INFOFILE));
if(open_infofile(conf_infofile)) {
error(_("could not open info db \"%s\""), conf_infofile);
/*NOTREACHED*/
}
if (check_infofile(conf_infofile, &origq, &errstr) == -1) {
log_add(L_WARNING, "problem copying infofile: %s", errstr);
amfree(errstr);
}
amfree(conf_infofile);
il = getconf_identlist(CNF_STORAGE);
storage_name = il->data;
storage = lookup_storage(storage_name);
conf_tapetype = storage_get_tapetype(storage);
conf_maxdumpsize = getconf_int64(CNF_MAXDUMPSIZE);
conf_runtapes = storage_get_runtapes(storage);
conf_dumpcycle = getconf_int(CNF_DUMPCYCLE);
conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
policy = lookup_policy(storage_get_policy(storage));
conf_tapecycle = policy_get_retention_tapes(policy);
conf_etimeout = (time_t)getconf_int(CNF_ETIMEOUT);
conf_reserve = getconf_int(CNF_RESERVE);
conf_usetimestamps = getconf_boolean(CNF_USETIMESTAMPS);
today = time(0);
if (planner_timestamp) {
if (conf_usetimestamps == 0) {
planner_timestamp[8] = '\0';
}
} else if(conf_usetimestamps == 0) {
planner_timestamp = get_datestamp_from_time(0);
}
else {
planner_timestamp = get_timestamp_from_time(0);
}
log_add(L_START, _("date %s"), planner_timestamp);
g_printf("DATE %s\n", planner_timestamp);
fflush(stdout);
g_fprintf(stderr, _("%s: timestamp %s\n"),
get_pname(), planner_timestamp);
err_array = match_disklist(&origq, exact_match, argc-diskarg_offset,
argv+diskarg_offset);
if (err_array->len > 0) {
for (i = 0; i < err_array->len; i++) {
char *errstr = g_ptr_array_index(err_array, i);
g_fprintf(stderr, "%s\n", errstr);
g_debug("%s", errstr);
log_add(L_INFO, "%s", errstr);
exit_status = EXIT_FAILURE;
}
}
g_ptr_array_free(err_array, TRUE);
for (dlist = origq.head; dlist != NULL; dlist = dlist->next) {
dp = dlist->data;
if (dp->todo) {
if (from_client) {
if (!dp->dump_limit || !dp->dump_limit->same_host)
dp->todo = 0;
} else {
if (dp->dump_limit && !dp->dump_limit->server)
dp->todo = 0;
}
}
}
if (!no_dump) {
int nb_disk = 0;
for (dlist = origq.head; dlist != NULL; dlist = dlist->next) {
dp = dlist->data;
if (dp->todo) {
qname = quote_string(dp->name);
log_add(L_DISK, "%s %s", dp->host->hostname, qname);
amfree(qname);
nb_disk++;
}
}
if (nb_disk == 0) {
if (errstr) {
error(_("no DLE to backup; %s"), errstr);
} else {
error(_("no DLE to backup"));
}
/*NOTREACHED*/
}
}
if (errstr) {
log_add(L_WARNING, "WARNING: %s", errstr);
}
amfree(errstr);
/* some initializations */
if(conf_runspercycle == 0) {
runs_per_cycle = conf_dumpcycle;
} else if(conf_runspercycle == -1 ) {
runs_per_cycle = guess_runs_from_tapelist();
} else
runs_per_cycle = conf_runspercycle;
if (runs_per_cycle <= 0) {
runs_per_cycle = 1;
}
/*
* do some basic sanity checking
*/
if(conf_tapecycle <= runs_per_cycle) {
log_add(L_WARNING, _("tapecycle (%d) <= runspercycle (%d)"),
conf_tapecycle, runs_per_cycle);
}
tape = lookup_tapetype(conf_tapetype);
if(conf_maxdumpsize > (gint64)0) {
tape_length = conf_maxdumpsize;
g_fprintf(stderr, "planner: tape_length is set from maxdumpsize (%jd KB)\n",
(intmax_t)conf_maxdumpsize);
}
else {
tape_length = tapetype_get_length(tape) * (gint64)conf_runtapes;
g_fprintf(stderr, "planner: tape_length is set from tape length (%jd KB) * runtapes (%d) == %jd KB\n",
(intmax_t)tapetype_get_length(tape),
conf_runtapes,
(intmax_t)tape_length);
}
tape_mark = (size_t)tapetype_get_filemark(tape);
tt_blocksize_kb = (size_t)tapetype_get_blocksize(tape);
tt_blocksize = tt_blocksize_kb * 1024;
g_fprintf(stderr, _("%s: time %s: startup took %s secs\n"),
get_pname(),
walltime_str(curclock()),
walltime_str(timessub(curclock(), section_start)));
/*
* 3. Send autoflush dumps left on the holding disks
*
* This should give us something to do while we generate the new
* dump schedule.
*/
g_fprintf(stderr,_("\nSENDING FLUSHES...\n"));
if (!no_taper && !no_flush) {
dumpfile_t file;
GSList *holding_list, *holding_file;
char *qdisk, *qhname;
cmdfile_data_t data;
GPtrArray *flush_ptr = g_ptr_array_sized_new(100);
char *line;
gpointer *xline;
conf_cmdfile = config_dir_relative(getconf_str(CNF_CMDFILE));
cmddatas = read_cmdfile(conf_cmdfile);
g_free(conf_cmdfile);
holding_cleanup(NULL, stderr);
/* get *all* flushable files in holding, without checking against
* the disklist (which may not contain some of the dumps) */
holding_list = holding_get_files_for_flush(NULL);
for(holding_file=holding_list; holding_file != NULL;
holding_file = holding_file->next) {
if (!holding_file_get_dumpfile((char *)holding_file->data, &file)) {
continue;
}
if (holding_file_size((char *)holding_file->data, 1) <= 0) {
log_add(L_INFO, "%s: removing file with no data.",
(char *)holding_file->data);
holding_file_unlink((char *)holding_file->data);
dumpfile_free_data(&file);
continue;
}
/* see if this matches the command-line arguments */
if (!match_dumpfile(&file, exact_match, argc-diskarg_offset,
argv+diskarg_offset)) {
dumpfile_free_data(&file);
continue;
}
// rename .tmp holding files
{
int l = strlen(holding_file->data) - 4;
char *s = holding_file->data;
if (strcmp(s+4, ".tmp") == 0) {
s[l] = '\0';
}
}
/* find all cmdfile for that dump */
data.ids = NULL;
data.holding_file = (char *)holding_file->data;
g_hash_table_foreach(cmddatas->cmdfile, &cmdfile_flush, &data);
if (!data.ids) {
identlist_t il = getconf_identlist(CNF_STORAGE);
storage_t *storage = lookup_storage((char *)il->data);
if (storage_get_autoflush(storage)) {
cmddata_t *cmddata = g_new0(cmddata_t, 1);
cmddata->operation = CMD_FLUSH;
cmddata->config = g_strdup(get_config_name());
cmddata->holding_file = g_strdup(data.holding_file);
cmddata->hostname = g_strdup(file.name);
cmddata->diskname = g_strdup(file.disk);
cmddata->dump_timestamp = g_strdup(file.datestamp);
/* add to the first storage only */
cmddata->dst_storage = g_strdup((char *)il->data);
cmddata->working_pid = getpid();
cmddata->status = CMD_TODO;
cmddatas->max_id++;
cmddata->id = cmddatas->max_id;
g_hash_table_insert(cmddatas->cmdfile,
GINT_TO_POINTER(cmddatas->max_id), cmddata);
data.ids = g_strdup_printf("%d;%s", cmddata->id, (char *)il->data);
}
}
if (data.ids && *data.ids) {
qdisk = quote_string(file.disk);
qhname = quote_string((char *)holding_file->data);
line = g_strdup_printf("FLUSH %s %s %s %s %d %s",
data.ids,
file.name,
qdisk,
file.datestamp,
file.dumplevel,
qhname);
g_ptr_array_add(flush_ptr, line);
log_add(L_DISK, "%s %s", file.name, qdisk);
amfree(qdisk);
amfree(qhname);
}
g_free(data.ids);
dumpfile_free_data(&file);
}
// NULL terminate the array
g_ptr_array_add(flush_ptr, NULL);
slist_free_full(holding_list, g_free);
holding_list = NULL;
write_cmdfile(cmddatas);
close_cmdfile(cmddatas); cmddatas = NULL;
for (xline = flush_ptr->pdata; *xline != NULL; xline++) {
g_fprintf(stderr, "%s\n", (char *)*xline);
g_fprintf(stdout, "%s\n", (char *)*xline);
}
g_ptr_array_free_full(flush_ptr);
}
g_fprintf(stderr, _("ENDFLUSH\n"));
g_fprintf(stdout, _("ENDFLUSH\n"));
fflush(stdout);
/*
* 4. Calculate Preliminary Dump Levels
*
* Before we can get estimates from the remote slave hosts, we make a
* first attempt at guessing what dump levels we will be dumping at
* based on the curinfo database.
*/
if (!no_dump) {
g_fprintf(stderr,_("\nSETTING UP FOR ESTIMATES...\n"));
section_start = curclock();
startq.head = startq.tail = NULL;
while(!empty(origq)) {
disk_t *dp = dequeue_disk(&origq);
if(dp->todo == 1) {
setup_estimate(dp);
}
}
g_fprintf(stderr, _("%s: time %s: setting up estimates took %s secs\n"),
get_pname(),
walltime_str(curclock()),
walltime_str(timessub(curclock(), section_start)));
/*
* 5. Get Dump Size Estimates from Remote Client Hosts
*
* Each host is queried (in parallel) for dump size information on all
* of its disks, and the results gathered as they come in.
*/
/* go out and get the dump estimates */
g_fprintf(stderr,_("\nGETTING ESTIMATES...\n"));
section_start = curclock();
estq.head = estq.tail = NULL;
pestq.head = pestq.tail = NULL;
waitq.head = waitq.tail = NULL;
failq.head = failq.tail = NULL;
get_estimates();
g_fprintf(stderr, _("%s: time %s: getting estimates took %s secs\n"),
get_pname(),
walltime_str(curclock()),
walltime_str(timessub(curclock(), section_start)));
/*
* At this point, all disks with estimates are in estq, and
* all the disks on hosts that didn't respond to our inquiry
* are in failq.
*/
est_dump_queue("FAILED", failq, 15, stderr);
est_dump_queue("DONE", estq, 15, stderr);
if (!empty(failq)) {
exit_status = EXIT_FAILURE;
}
/*
* 6. Analyze Dump Estimates
*
* Each disk's estimates are looked at to determine what level it
* should dump at, and to calculate the expected size and time taking
* historical dump rates and compression ratios into account. The
* total expected size is accumulated as well.
*/
g_fprintf(stderr,_("\nANALYZING ESTIMATES...\n"));
section_start = curclock();
/* an empty tape still has a label and an endmark */
total_size = ((gint64)tt_blocksize_kb + (gint64)tape_mark) * (gint64)2;
total_lev0 = 0.0;
balanced_size = 0.0;
schedq.head = schedq.tail = NULL;
while(!empty(estq)) analyze_estimate(dequeue_est(&estq));
while(!empty(failq)) handle_failed(dequeue_est(&failq));
run_server_global_scripts(EXECUTE_ON_POST_ESTIMATE, get_config_name(),
planner_timestamp);
/*
* At this point, all the disks are on schedq sorted by priority.
* The total estimated size of the backups is in total_size.
*/
{
GList *elist;
g_fprintf(stderr, _("INITIAL SCHEDULE (size %lld):\n"),
(long long)total_size);
for (elist = schedq.head; elist != NULL; elist = elist->next) {
est_t *est = get_est(elist);
disk_t *dp = est->disk;
qname = quote_string(dp->name);
g_fprintf(stderr, _(" %s %s pri %d lev %d nsize %lld csize %lld\n"),
dp->host->hostname, qname, est->dump_priority,
est->dump_est->level,
(long long)est->dump_est->nsize,
(long long)est->dump_est->csize);
amfree(qname);
}
}
/*
* 7. Delay Dumps if Schedule Too Big
*
* If the generated schedule is too big to fit on the tape, we need to
* delay some full dumps to make room. Incrementals will be done
* instead (except for new or forced disks).
*
* In extreme cases, delaying all the full dumps is not even enough.
* If so, some low-priority incrementals will be skipped completely
* until the dumps fit on the tape.
*/
g_fprintf(stderr, _("\nDELAYING DUMPS IF NEEDED, total_size %lld, tape length %lld mark %zu\n"),
(long long)total_size,
(long long)tape_length,
tape_mark);
initial_size = total_size;
delay_dumps();
/* XXX - why bother checking this? */
if(empty(schedq) && total_size < initial_size) {
error(_("cannot fit anything on tape, bailing out"));
/*NOTREACHED*/
}
/*
* 8. Promote Dumps if Schedule Too Small
*
* Amanda attempts to balance the full dumps over the length of the
* dump cycle. If this night's full dumps are too small relative to
* the other nights, promote some high-priority full dumps that will be
* due for the next run, to full dumps for tonight, taking care not to
* overflow the tape size.
*
* This doesn't work too well for small sites. For these we scan ahead
* looking for nights that have an excessive number of dumps and promote
* one of them.
*
* Amanda never delays full dumps just for the sake of balancing the
* schedule, so it can take a full cycle to balance the schedule after
* a big bump.
*/
g_fprintf(stderr,
_("\nPROMOTING DUMPS IF NEEDED, total_lev0 %1.0lf, balanced_size %1.0lf...\n"),
total_lev0, balanced_size);
balance_threshold = balanced_size * PROMOTE_THRESHOLD;
moved_one = 1;
while((balanced_size - total_lev0) > balance_threshold && moved_one)
moved_one = promote_highest_priority_incremental();
moved_one = promote_hills();
g_fprintf(stderr, _("%s: time %s: analysis took %s secs\n"),
get_pname(),
walltime_str(curclock()),
walltime_str(timessub(curclock(), section_start)));
/*
* 9. Output Schedule
*
* The schedule goes to stdout, presumably to driver. A copy is written
* on stderr for the debug file.
*/
g_fprintf(stderr,_("\nGENERATING SCHEDULE:\n--------\n"));
if (empty(schedq)) {
exit_status = EXIT_FAILURE;
g_fprintf(stderr, _("--> Generated empty schedule! <--\n"));
} else {
while(!empty(schedq)) output_scheduleline(dequeue_est(&schedq));
}
g_fprintf(stderr, _("--------\n"));
}
close_infofile();
log_add(L_FINISH, _("date %s time %s"), planner_timestamp, walltime_str(curclock()));
log_add(L_INFO, "pid-done %ld", (long)getpid());
clear_tapelist();
amfree(planner_timestamp);
amfree(our_feature_string);
am_release_feature_set(our_features);
our_features = NULL;
dbclose();
return exit_status;
}
/*
* ========================================================================
* SETUP FOR ESTIMATES
*
*/
static void askfor(est_t *, int, int, info_t *);
static int last_level(info_t *info); /* subroutines */
static one_est_t *est_for_level(est_t *ep, int level);
static void est_csize(est_t *ep, one_est_t *one_est);
static gint64 est_tape_size(est_t *ep, int level);
static int next_level0(disk_t *dp, info_t *info);
static int runs_at(info_t *info, int lev);
static gint64 bump_thresh(int level, gint64 size_level_0, int bumppercent, gint64 bumpsize, double bumpmult);
static int when_overwrite(char *label);
static void askfor(
est_t *ep, /* esimate data block */
int seq, /* sequence number of request */
int lev, /* dump level being requested */
info_t *info) /* info block for disk */
{
if(seq < 0 || seq >= MAX_LEVELS) {
error(_("error [planner askfor: seq out of range 0..%d: %d]"),
MAX_LEVELS, seq);
/*NOTREACHED*/
}
if(lev < -1 || lev >= DUMP_LEVELS) {
error(_("error [planner askfor: lev out of range -1..%d: %d]"),
DUMP_LEVELS, lev);
/*NOTREACHED*/
}
if (lev == -1) {
ep->estimate[seq].level = -1;
ep->estimate[seq].dumpdate = (char *)0;
ep->estimate[seq].nsize = (gint64)-3;
ep->estimate[seq].csize = (gint64)-3;
ep->estimate[seq].guessed = 0;
return;
}
ep->estimate[seq].level = lev;
ep->estimate[seq].dumpdate = g_strdup(get_dumpdate(info,lev));
ep->estimate[seq].nsize = (gint64)-3;
ep->estimate[seq].csize = (gint64)-3;
ep->estimate[seq].guessed = 0;
return;
}
static void
setup_estimate(
disk_t *dp)
{
est_t *ep;
info_t *info;
int i;
char *qname;
int overwrite_runs;
assert(dp && dp->host);
compute_retention();
qname = quote_string(dp->name);
g_fprintf(stderr, _("%s: time %s: setting up estimates for %s:%s\n"),
get_pname(), walltime_str(curclock()),
dp->host->hostname, qname);
/* get current information about disk */
info = g_new0(info_t, 1);
if(get_info(dp->host->hostname, dp->name, info)) {
/* no record for this disk, make a note of it */
log_add(L_INFO, _("Adding new disk %s:%s."), dp->host->hostname, qname);
}
if (dp->data_path == DATA_PATH_DIRECTTCP) {
if (dp->compress != COMP_NONE) {
log_add(L_FAIL, _("%s %s %s 0 [Can't compress directtcp data-path]"),
dp->host->hostname, qname, planner_timestamp);
g_fprintf(stderr,_("%s:%s lev 0 skipped can't compress directtcp data-path\n"),
dp->host->hostname, qname);
amfree(qname);
dp->todo = 0;
return;
}
if (dp->encrypt != ENCRYPT_NONE) {
log_add(L_FAIL, _("%s %s %s 0 [Can't encrypt directtcp data-path]"),
dp->host->hostname, qname, planner_timestamp);
g_fprintf(stderr,_("%s:%s lev 0 skipped can't encrypt directtcp data-path\n"),
dp->host->hostname, qname);
amfree(qname);
dp->todo = 0;
return;
}
if (dp->to_holdingdisk == HOLD_REQUIRED) {
log_add(L_FAIL, _("%s %s %s 0 [Holding disk can't be use for directtcp data-path]"),
dp->host->hostname, qname, planner_timestamp);
g_fprintf(stderr,_("%s:%s lev 0 skipped Holding disk can't be use for directtcp data-path\n"),
dp->host->hostname, qname);
amfree(qname);
dp->todo = 0;
return;
} else if (dp->to_holdingdisk == HOLD_AUTO) {
g_fprintf(stderr,_("%s:%s Disabling holding disk\n"),
dp->host->hostname, qname);
dp->to_holdingdisk = HOLD_NEVER;
}
}
/* setup working data struct for disk */
ep = g_malloc(sizeof(est_t));
ep->disk = dp;
ep->info = info;
ep->state = DISK_READY;
ep->dump_priority = dp->priority;
ep->errstr = 0;
ep->promote = 0;
ep->post_dle = 0;
ep->degr_mesg = NULL;
ep->dump_est = &default_one_est;
ep->degr_est = &default_one_est;
/* calculated fields */
if (ISSET(info->command, FORCE_FULL)) {
/* force a level 0, kind of like a new disk */
if(dp->strategy == DS_NOFULL) {
/*
* XXX - Not sure what it means to force a no-full disk. The
* purpose of no-full is to just dump changes relative to a
* stable base, for example root partitions that vary only
* slightly from a site-wide prototype. Only the variations
* are dumped.
*
* If we allow a level 0 onto the Amanda cycle, then we are
* hosed when that tape gets re-used next. Disallow this for
* now.
*/
log_add(L_WARNING,
_("Cannot force full dump of %s:%s with no-full option."),
dp->host->hostname, qname);
/* clear force command */
CLR(info->command, FORCE_FULL);
ep->last_level = last_level(info);
ep->next_level0 = next_level0(dp, info);
} else if (dp->strategy == DS_INCRONLY) {
log_add(L_WARNING,
_("Cannot force full dump of %s:%s with incronly option."),
dp->host->hostname, qname);
/* clear force command */
CLR(info->command, FORCE_FULL);
ep->last_level = last_level(info);
ep->next_level0 = next_level0(dp, info);
} else {
ep->degr_mesg = _("Skipping: force-full disk can't be dumped in degraded mode");
ep->last_level = -1;
ep->next_level0 = -conf_dumpcycle;
log_add(L_INFO, _("Forcing full dump of %s:%s as directed."),
dp->host->hostname, qname);
}
}
else if (ISSET(info->command, FORCE_LEVEL_1)) {
/* force a level 1 */
if (dp->strategy == DS_NOINC) {
log_add(L_WARNING,
_("Cannot force level 1 dump of %s:%s with no-incr option."),
dp->host->hostname, qname);
/* clear force-level-1 command */
CLR(info->command, FORCE_LEVEL_1);
ep->last_level = last_level(info);
ep->next_level0 = next_level0(dp, info);
} else {
ep->degr_mesg = "";
ep->last_level = 0;
ep->next_level0 = 0;
log_add(L_INFO, _("Forcing level 1 of %s:%s as directed."),
dp->host->hostname, qname);
}
}
else if(dp->strategy == DS_NOFULL) {
/* force estimate of level 1 */
ep->last_level = 1;
ep->next_level0 = next_level0(dp, info);
}
else {
ep->last_level = last_level(info);
ep->next_level0 = next_level0(dp, info);
}
/* adjust priority levels */
/* warn if dump will be overwritten */
if (ep->last_level > -1 && strlen(info->inf[0].label) > 0) {
overwrite_runs = when_overwrite(info->inf[0].label);
if(overwrite_runs == 0) {
log_add(L_WARNING, _("Last full dump of %s:%s "
"on tape %s overwritten on this run."),
dp->host->hostname, qname, info->inf[0].label);
} else if(overwrite_runs <= RUNS_REDZONE) {
log_add(L_WARNING,
plural(_("Last full dump of %s:%s on tape %s overwritten in %d run."),
_("Last full dump of %s:%s on tape %s overwritten in %d runs."), overwrite_runs),
dp->host->hostname, qname, info->inf[0].label,
overwrite_runs);
}
}
/* warn if last level 1 will be overwritten */
if (ep->last_level > 1 && strlen(info->inf[1].label) > 0) {
overwrite_runs = when_overwrite(info->inf[1].label);
if(overwrite_runs == 0) {
log_add(L_WARNING, _("Last level 1 dump of %s:%s "
"on tape %s overwritten on this run, resetting to level 1"),
dp->host->hostname, qname, info->inf[1].label);
ep->last_level = 0;
} else if(overwrite_runs <= RUNS_REDZONE) {
log_add(L_WARNING,
plural(_("Last level 1 dump of %s:%s on tape %s overwritten in %d run."),
_("Last level 1 dump of %s:%s on tape %s overwritten in %d runs."), overwrite_runs),
dp->host->hostname, qname, info->inf[1].label,
overwrite_runs);
}
}
if(ep->next_level0 < 0) {
g_fprintf(stderr,plural(_("%s:%s overdue %d day for level 0\n"),
_("%s:%s overdue %d days for level 0\n"),
(-ep->next_level0)),
dp->host->hostname, qname, (-ep->next_level0));
ep->dump_priority -= ep->next_level0;
}
else if (ISSET(info->command, FORCE_FULL))
ep->dump_priority += 1;
/* else XXX bump up the priority of incrementals that failed last night */
/* handle external level 0 dumps */
if(dp->skip_full && dp->strategy != DS_NOINC) {
if(ep->next_level0 <= 0) {
/* update the date field */
info->inf[0].date = today;
CLR(info->command, FORCE_FULL);
ep->next_level0 += conf_dumpcycle;
ep->last_level = 0;
if(put_info(dp->host->hostname, dp->name, info)) {
error(_("could not put info record for %s:%s: %s"),
dp->host->hostname, qname, strerror(errno));
/*NOTREACHED*/
}
log_add(L_INFO, _("Skipping full dump of %s:%s today."),
dp->host->hostname, qname);
g_fprintf(stderr,_("%s:%s lev 0 skipped due to skip-full flag\n"),
dp->host->hostname, qname);
/* don't enqueue the disk */
askfor(ep, 0, -1, info);
askfor(ep, 1, -1, info);
askfor(ep, 2, -1, info);
g_fprintf(stderr, _("%s: SKIPPED %s %s 0 [skip-full]\n"),
get_pname(), dp->host->hostname, qname);
log_add(L_SUCCESS, _("%s %s %s 0 [skipped: skip-full]"),
dp->host->hostname, qname, planner_timestamp);
amfree(qname);
dp->todo = 0;
return;
}
if(ep->last_level == -1) {
/* probably a new disk, but skip-full means no full! */
ep->last_level = 0;
}
if(ep->next_level0 == 1) {
log_add(L_WARNING, _("Skipping full dump of %s:%s tomorrow."),
dp->host->hostname, qname);
}
}
if(dp->strategy == DS_INCRONLY && ep->last_level == -1 && !ISSET(info->command, FORCE_FULL)) {
/* don't enqueue the disk */
askfor(ep, 0, -1, info);
askfor(ep, 1, -1, info);
askfor(ep, 2, -1, info);
log_add(L_FAIL, _("%s %s 19000101 1 [Skipping incronly because no full dump were done]"),
dp->host->hostname, qname);
g_fprintf(stderr,_("%s:%s lev 1 skipped due to strategy incronly and no full dump were done\n"),
dp->host->hostname, qname);
amfree(qname);
dp->todo = 0;
return;
}
/* handle "skip-incr" type archives */
if(dp->skip_incr && ep->next_level0 > 0) {
g_fprintf(stderr,_("%s:%s lev 1 skipped due to skip-incr flag\n"),
dp->host->hostname, qname);
/* don't enqueue the disk */
askfor(ep, 0, -1, info);
askfor(ep, 1, -1, info);
askfor(ep, 2, -1, info);
g_fprintf(stderr, _("%s: SKIPPED %s %s 1 [skip-incr]\n"),
get_pname(), dp->host->hostname, qname);
log_add(L_SUCCESS, _("%s %s %s 1 [skipped: skip-incr]"),
dp->host->hostname, qname, planner_timestamp);
amfree(qname);
dp->todo = 0;
return;
}
if (ep->last_level == -1 && ep->next_level0 > 0 &&
dp->strategy != DS_NOFULL && dp->strategy != DS_INCRONLY &&
conf_reserve == 100) {
log_add(L_WARNING, _("%s:%s mismatch: no tapelist record, "
"but curinfo next_level0: %d."),
dp->host->hostname, qname, ep->next_level0);
ep->next_level0 = 0;
}
//if(ep->last_level == 0) ep->level_days = 0;
//else ep->level_days = runs_at(info, ep->last_level);
ep->level_days = runs_at(info, ep->last_level);
ep->last_lev0size = info->inf[0].csize;
ep->fullrate = perf_average(info->full.rate, 0.0);
ep->incrrate = perf_average(info->incr.rate, 0.0);
ep->fullcomp = perf_average(info->full.comp, dp->comprate[0]);
ep->incrcomp = perf_average(info->incr.comp, dp->comprate[1]);
/* determine which estimates to get */
i = 0;
if (dp->strategy == DS_NOINC ||
(!dp->skip_full &&
!ISSET(info->command, FORCE_LEVEL_1) &&
(!ISSET(info->command, FORCE_BUMP) ||
dp->skip_incr ||
ep->last_level == -1))) {
if(ISSET(info->command, FORCE_BUMP) && ep->last_level == -1) {
log_add(L_INFO,
_("Remove force-bump command of %s:%s because it's a new disk."),
dp->host->hostname, qname);
}
switch (dp->strategy) {
case DS_STANDARD:
case DS_NOINC:
askfor(ep, i++, 0, info);
if (ep->last_level == -1)
if (ISSET(info->command, FORCE_FULL)) {
ep->degr_mesg = _("Skipping: Can't bump a 'force' dle in degraded mode");
} else {
ep->degr_mesg = _("Skipping: new disk can't be dumped in degraded mode");
}
else if (dp->strategy == DS_NOINC)
ep->degr_mesg = _("Skipping: strategy NOINC can't be dumped in degraded mode");
if(dp->skip_full) {
log_add(L_INFO, _("Ignoring skip-full for %s:%s "
"because the strategy is NOINC."),
dp->host->hostname, qname);
}
if(ISSET(info->command, FORCE_BUMP)) {
log_add(L_INFO,
_("Ignoring FORCE_BUMP for %s:%s because the strategy is NOINC."),
dp->host->hostname, qname);
}
break;
case DS_NOFULL:
break;
case DS_INCRONLY:
if (ISSET(info->command, FORCE_FULL))
ep->last_level = 0;
break;
}
}
if(!dp->skip_incr && !(dp->strategy == DS_NOINC)) {
if(ep->last_level == -1) { /* a new disk */
if (ep->degr_mesg == NULL)
ep->degr_mesg = _("Skipping: new disk can't be dumped in degraded mode");
if(dp->strategy == DS_NOFULL || dp->strategy == DS_INCRONLY) {
askfor(ep, i++, 1, info);
} else {
assert(!dp->skip_full); /* should be handled above */
}
} else { /* not new, pick normally */
int curr_level;
curr_level = ep->last_level;
if (ISSET(info->command, FORCE_LEVEL_1)) {
askfor(ep, i++, 1, info);
ep->degr_mesg = _("Skipping: force-level-1 disk can't be dumped in degraded mode");
} else if (ISSET(info->command, FORCE_NO_BUMP)) {
if(curr_level > 0) { /* level 0 already asked for */
askfor(ep, i++, curr_level, info);
}
log_add(L_INFO,_("Preventing bump of %s:%s as directed."),
dp->host->hostname, qname);
ep->degr_mesg = _("Skipping: force-no-bump disk can't be dumped in degraded mode");
} else if (ISSET(info->command, FORCE_BUMP)
&& curr_level + 1 < DUMP_LEVELS) {
askfor(ep, i++, curr_level+1, info);
log_add(L_INFO,_("Bumping of %s:%s at level %d as directed."),
dp->host->hostname, qname, curr_level+1);
ep->degr_mesg = _("Skipping: force-bump disk can't be dumped in degraded mode");
} else if (curr_level == 0) {
askfor(ep, i++, 1, info);
} else {
askfor(ep, i++, curr_level, info);
/*
* If last time we dumped less than the threshold, then this
* time we will too, OR the extra size will be charged to both
* cur_level and cur_level + 1, so we will never bump. Also,
* if we haven't been at this level 2 days, or the dump failed
* last night, we can't bump.
*/
if((info->inf[curr_level].size == (gint64)0 || /* no data, try it anyway */
(((info->inf[curr_level].size > bump_thresh(curr_level, info->inf[0].size,dp->bumppercent, dp->bumpsize, dp->bumpmult)))
&& ep->level_days >= dp->bumpdays))
&& curr_level + 1 < DUMP_LEVELS) {
askfor(ep, i++, curr_level+1, info);
}
}
}
}
while(i < MAX_LEVELS) /* mark end of estimates */
askfor(ep, i++, -1, info);
/* debug output */
g_fprintf(stderr, _("setup_estimate: %s:%s: command %u, options: %s "
"last_level %d next_level0 %d level_days %d getting estimates "
"%d (%lld) %d (%lld) %d (%lld)\n"),
dp->host->hostname, qname, info->command,
dp->strategy == DS_NOFULL ? "no-full" :
dp->strategy == DS_INCRONLY ? "incr-only" :
dp->skip_full ? "skip-full" :
dp->skip_incr ? "skip-incr" : "none",
ep->last_level, ep->next_level0, ep->level_days,
ep->estimate[0].level, (long long)ep->estimate[0].nsize,
ep->estimate[1].level, (long long)ep->estimate[1].nsize,
ep->estimate[2].level, (long long)ep->estimate[2].nsize);
assert(ep->estimate[0].level != -1);
enqueue_est(&startq, ep);
amfree(qname);
}
static int when_overwrite(
char *label)
{
tape_t *tp;
int nb;
int nb_in_storage;
int runtapes;
storage_t *st;
policy_s *po;
int retention_tapes;
if ((tp = lookup_tapelabel(label)) == NULL)
return 1; /* "shouldn't happen", but trigger warning message */
if (tp->when_overwrite > -1)
return tp->when_overwrite;
if (tp->reuse == 0)
return 1024;
if (!tp->storage) {
st = lookup_storage(get_config_name());
} else {
st = lookup_storage(tp->storage);
}
if (!st)
return 1;
po = lookup_policy(storage_get_policy(st));
if (!po)
return 1;
retention_tapes = policy_get_retention_tapes(po);
runtapes = storage_get_runtapes(st);
if (runtapes == 0) runtapes = 1;
nb = tape_overwrite(st, tp);
nb_in_storage = nb_tape_in_storage(st);
if (retention_tapes > nb_in_storage)
nb += (retention_tapes - nb_in_storage + 1);
tp->when_overwrite = (nb - 1) / runtapes;
if (tp->when_overwrite < 0)
tp->when_overwrite = 0;
return tp->when_overwrite;
}
/* Return the estimated size for a particular dump */
static one_est_t *
est_for_level(
est_t *ep,
int level)
{
int i;
if (level < 0 || level >= DUMP_LEVELS)
return &default_one_est;
for (i = 0; i < MAX_LEVELS; i++) {
if (level == ep->estimate[i].level) {
if (ep->estimate[i].csize <= -1) {
est_csize(ep, &ep->estimate[i]);
}
return &ep->estimate[i];
}
}
return &default_one_est;
}
/* Return the estimated on-tape size of a particular dump */
static void
est_csize(
est_t *ep,
one_est_t *one_est)
{
gint64 size = one_est->nsize;
double ratio;
if (ep->disk->compress == COMP_NONE) {
one_est->csize = one_est->nsize;
return;
}
if (one_est->level == 0) ratio = ep->fullcomp;
else ratio = ep->incrcomp;
/*
* make sure over-inflated compression ratios don't throw off the
* estimates, this is mostly for when you have a small dump getting
* compressed which takes up alot more disk/tape space relatively due
* to the overhead of the compression. This is specifically for
* Digital Unix vdump. This patch is courtesy of Rudolf Gabler
* (RUG@USM.Uni-Muenchen.DE)
*/
if (ratio > 1.1) ratio = 1.1;
size = (gint64)((double)size * ratio);
/*
* Ratio can be very small in some error situations, so make sure
* size goes back greater than zero. It may not be right, but
* indicates we did get an estimate.
*/
if (size <= (gint64)0) {
size = (gint64)1;
}
one_est->csize = size;
}
static gint64 est_tape_size(
est_t *ep,
int level)
{
one_est_t *dump_est;
dump_est = est_for_level(ep, level);
if (dump_est->level >= 0 && dump_est->csize <= -1)
est_csize(ep, dump_est);
return dump_est->csize;
}
/* what was the level of the last successful dump to tape? */
static int last_level(
info_t *info)
{
int min_pos, min_level, i;
time_t lev0_date, last_date;
tape_t *tp;
if(info->last_level != -1)
return info->last_level;
/* to keep compatibility with old infofile */
min_pos = 1000000000;
min_level = -1;
lev0_date = EPOCH;
last_date = EPOCH;
for(i = 0; i < 9; i++) {
if(conf_reserve < 100) {
if(i == 0) lev0_date = info->inf[0].date;
else if(info->inf[i].date < lev0_date) continue;
if(info->inf[i].date > last_date) {
last_date = info->inf[i].date;
min_level = i;
}
}
else {
if((tp = lookup_tapelabel(info->inf[i].label)) == NULL) continue;
/* cull any entries from previous cycles */
if(i == 0) lev0_date = info->inf[0].date;
else if(info->inf[i].date < lev0_date) continue;
if(tp->position < min_pos) {
min_pos = tp->position;
min_level = i;
}
}
}
info->last_level = i;
return min_level;
}
/* when is next level 0 due? 0 = today, 1 = tomorrow, etc*/
static int
next_level0(
disk_t *dp,
info_t *info)
{
if(dp->strategy == DS_NOFULL || dp->strategy == DS_INCRONLY)
return 1; /* fake it */
else if (dp->strategy == DS_NOINC)
return 0;
else if(info->inf[0].date < (time_t)0)
return -days_diff(EPOCH, today); /* new disk */
else
return dp->dumpcycle - days_diff(info->inf[0].date, today);
}
/* how many runs at current level? */
static int runs_at(
info_t *info,
int lev)
{
tape_t *cur_tape, *old_tape;
int last, nb_runs;
last = last_level(info);
if(lev != last) return 0;
if(info->consecutive_runs != -1)
return info->consecutive_runs;
if(lev == 0) return 1;
/* to keep compatibility with old infofile */
cur_tape = lookup_tapelabel(info->inf[lev].label);
old_tape = lookup_tapelabel(info->inf[lev-1].label);
if(cur_tape == NULL || old_tape == NULL) return 0;
if(conf_runtapes == 0)
nb_runs = (old_tape->position - cur_tape->position) / 1;
else
nb_runs = (old_tape->position - cur_tape->position) / conf_runtapes;
info->consecutive_runs = nb_runs;
return nb_runs;
}
static gint64 bump_thresh(
int level,
gint64 size_level_0,
int bumppercent,
gint64 bumpsize,
double bumpmult)
{
double bump;
if ((bumppercent != 0) && (size_level_0 > (gint64)1024)) {
bump = ((double)size_level_0 * (double)bumppercent) / 100.0;
}
else {
bump = (double)bumpsize;
}
while(--level) bump = bump * bumpmult;
return (gint64)bump;
}
/*
* ========================================================================
* GET REMOTE DUMP SIZE ESTIMATES
*
*/
static void getsize(am_host_t *hostp);
static disk_t *lookup_hostdisk(am_host_t *hp, char *str);
static void handle_result(void *datap, pkt_t *pkt, security_handle_t *sech);
static void get_estimates(void)
{
int something_started;
GList *elist;
something_started = 1;
while (something_started) {
something_started = 0;
for (elist = startq.head; elist != NULL; elist = elist->next) {
est_t *ep = get_est(elist);
disk_t *dp = ep->disk;
disk_t *dp1;
am_host_t *hostp = dp->host;
if (hostp->status == HOST_READY) {
something_started = 1;
run_server_host_scripts(EXECUTE_ON_PRE_HOST_ESTIMATE,
get_config_name(), planner_timestamp,
hostp);
for(dp1 = hostp->disks; dp1 != NULL; dp1 = dp1->hostnext) {
if (dp1->todo) {
est_t *ep1;
ep1 = find_est_for_dp(dp1);
run_server_dle_scripts(EXECUTE_ON_PRE_DLE_ESTIMATE,
get_config_name(), planner_timestamp,
dp1, ep1->estimate[0].level);
}
}
getsize(hostp);
protocol_check();
/*
* dp is no longer on startq, so elist is not valid
* and we have to start all over.
*/
break;
}
}
}
protocol_run();
while(!empty(waitq)) {
est_t *ep = dequeue_est(&waitq);
ep->errstr = _("hmm, disk was stranded on waitq");
enqueue_est(&failq, ep);
}
while(!empty(pestq)) {
est_t *ep = dequeue_est(&pestq);
disk_t *dp = ep->disk;
char * qname = quote_string(dp->name);
int i;
for (i=0; i < MAX_LEVELS; i++) {
if (ep->estimate[i].level != -1 &&
ep->estimate[i].nsize < (gint64)0) {
if (ep->estimate[i].nsize == (gint64)-3) {
log_add(L_WARNING,
_("disk %s:%s, estimate of level %d timed out."),
dp->host->hostname, qname, ep->estimate[i].level);
}
ep->estimate[i].level = -1;
}
}
if ((ep->estimate[0].level != -1 &&
ep->estimate[0].nsize > (gint64)0) ||
(ep->estimate[1].level != -1 &&
ep->estimate[1].nsize > (gint64)0) ||
(ep->estimate[2].level != -1 &&
ep->estimate[2].nsize > (gint64)0)) {
enqueue_est(&estq, ep);
}
else {
ep->errstr = g_strjoin(NULL, "disk ", qname,
_(", all estimate timed out"), NULL);
enqueue_est(&failq, ep);
}
amfree(qname);
}
}
/*
* Build an XML fragment representing a client estimate for a DLE. Return that
* fragment to the caller. It is up to the caller to free the returned string.
*
* @param dp: the DLE.
* @param features: advertised server features.
* @param nr_estimates: number of estimates the client has to do for this DLE.
* @returns: the XML fragment.
*/
static char *client_estimate_as_xml(est_t *ep, am_feature_t *features,
int nr_estimates)
{
info_t info;
int i;
char *tmp;
gboolean server_side_xml, legacy_api;
GString *strbuf = g_string_new("<dle>\n <program>");
GPtrArray *array = g_ptr_array_new();
GString *tmpbuf;
gchar **strings;
disk_t *dp = ep->disk;
/*
* Initialize our helper variables
*/
tmp = dp->program;
legacy_api = (g_str_equal(tmp, "DUMP") || g_str_equal(tmp, "GNUTAR"));
server_side_xml = am_has_feature(features, fe_xml_level_server);
get_info(dp->host->hostname, dp->name, &info);
/*
* First, of all, build the estimate level XML fragments in an array.
* Joining its elements will be done when it's time to append to the string
* buffer.
*/
for (i = 0; i < nr_estimates; i++) {
int level = ep->estimate[i].level;
tmpbuf = g_string_new(" <level>");
g_string_append_printf(tmpbuf, "%d", level);
if (server_side_xml && server_can_do_estimate(dp, &info, level, tape))
g_string_append(tmpbuf, "<server>YES</server>");
g_string_append(tmpbuf, "</level>");
g_ptr_array_add(array, g_string_free(tmpbuf, FALSE));
}
/*
* We want a separator (ie, \n) terminated string, we must therefore append
* an empty string to the array before NULL.
*/
g_ptr_array_add(array, g_strdup(""));
g_ptr_array_add(array, NULL);
g_string_append_printf(strbuf, "%s</program>\n", (legacy_api) ? tmp
: "APPLICATION");
if (!legacy_api) {
application_t *application = lookup_application(dp->application);
g_assert(application != NULL);
tmp = xml_application(dp, application, features);
g_string_append(strbuf, tmp);
g_free(tmp);
}
tmp = xml_estimate(dp->estimatelist, features);
g_string_append_printf(strbuf, "%s\n", tmp);
g_free(tmp);
tmp = amxml_format_tag("disk", dp->name);
g_string_append_printf(strbuf, " %s\n", tmp);
g_free(tmp);
if (dp->device) {
tmp = amxml_format_tag("diskdevice", dp->device);
g_string_append_printf(strbuf, " %s\n", tmp);
g_free(tmp);
}
strings = (gchar **)g_ptr_array_free(array, FALSE);
tmp = g_strjoinv("\n", strings);
g_strfreev(strings);
g_string_append(strbuf, tmp);
g_free(tmp);
g_string_append_printf(strbuf, " <spindle>%d</spindle>\n", dp->spindle);
tmp = xml_optionstr(dp, 0);
g_string_append_printf(strbuf, "%s</dle>", tmp);
g_free(tmp);
return g_string_free(strbuf, FALSE);
}
/**
* Return the estimate string for one estimate level when the client does not
* support XML. It is up to the caller to free the resulting string.
*
* @dp: the DLE
* @est: the estimate object for this level
* @use_calcsize: does the client support calcsize?
* @option_string: the option string for the client
*/
static char *client_lvl_estimate_as_text(disk_t *dp, one_est_t *est,
gboolean use_calcsize, char *option_string)
{
int level = est->level;
GString *strbuf;
char *tmp;
strbuf = g_string_new(use_calcsize ? "CALCSIZE" : NULL);
tmp = quote_string(dp->name);
g_string_append_printf(strbuf, "%s %s ", dp->program, tmp);
g_free(tmp);
if (dp->device) {
tmp = quote_string(dp->device);
g_string_append(strbuf, tmp);
g_free(tmp);
}
g_string_append_printf(strbuf, " %d %s %d ", level, est->dumpdate,
dp->spindle);
if (option_string) {
g_string_append_printf(strbuf, " OPTIONS |%s", option_string);
goto out;
}
if (dp->exclude_file && dp->exclude_file->nb_element == 1) {
tmp = quote_string(dp->exclude_file->first->name);
g_string_append_printf(strbuf, " exclude-file=%s", tmp);
g_free(tmp);
} else if (dp->exclude_list && dp->exclude_list->nb_element == 1) {
tmp = quote_string(dp->exclude_list->first->name);
g_string_append_printf(strbuf, " exclude-list=%s", tmp);
g_free(tmp);
}
if (dp->include_file && dp->include_file->nb_element == 1) {
tmp = quote_string(dp->include_file->first->name);
g_string_append_printf(strbuf, " include-file=%s", tmp);
g_free(tmp);
} else if (dp->include_list && dp->include_list->nb_element == 1) {
tmp = quote_string(dp->include_list->first->name);
g_string_append_printf(strbuf, " include-list=%s", tmp);
g_free(tmp);
}
out:
return g_string_free(strbuf, FALSE);
}
static void getsize(am_host_t *hostp)
{
disk_t * dp;
est_t * ep;
int i;
time_t timeout;
const security_driver_t *secdrv;
estimate_t estimate;
estimatelist_t el;
int nb_client = 0, nb_server = 0;
gboolean has_features, has_maxdumps, has_hostname, has_config;
gboolean has_timestamp;
am_feature_t *features;
GString *reqbuf;
char *req;
int total_estimates = 0;
g_assert(hostp->disks != NULL);
if (hostp->status != HOST_READY)
return;
reqbuf = g_string_new(NULL);
/*
* The first time through here we send a "noop" request. This will
* return the feature list from the client if it supports that.
* If it does not, handle_result() will set the feature list to an
* empty structure. In either case, we do the disks on the second
* (and subsequent) pass(es).
*/
if (!hostp->features) { /* noop service */
g_string_append_printf(reqbuf, "SERVICE noop\nOPTIONS features=%s;\n",
our_feature_string);
/*
* We use ctimeout for the "noop" request because it should be
* very fast and etimeout has other side effects.
*/
timeout = (time_t)getconf_int(CNF_CTIMEOUT);
goto send;
}
features = hostp->features;
has_features = am_has_feature(features, fe_req_options_features);
has_maxdumps = am_has_feature(features, fe_req_options_maxdumps);
has_hostname = am_has_feature(features, fe_req_options_hostname);
has_config = am_has_feature(features, fe_req_options_config);
has_timestamp = am_has_feature(features, fe_req_options_timestamp);
g_string_append(reqbuf, "SERVICE sendsize\nOPTIONS ");
if (has_features)
g_string_append_printf(reqbuf, "features=%s;", our_feature_string);
if (has_maxdumps)
g_string_append_printf(reqbuf, "maxdumps=%d;", hostp->maxdumps);
if (has_hostname)
g_string_append_printf(reqbuf, "hostname=%s;", hostp->hostname);
if (has_config)
g_string_append_printf(reqbuf, "config=%s;", get_config_name());
if (has_timestamp)
g_string_append_printf(reqbuf, "timestamp=%s;", planner_timestamp);
g_string_append_c(reqbuf, '\n');
for (dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
char *tmp;
gchar **errors;
GPtrArray *array;
gchar **strings;
/*
* Record the number of client-side estimates we have to do. We use i to
* go over all levels and record the result in estimates_for_client when
* we're done.
*/
int estimates_for_client = 0;
if(dp->todo == 0) continue;
ep = find_est_for_dp(dp);
if(ep->state != DISK_READY) continue;
ep->got_estimate = 0;
if (ep->estimate[0].level == -1) {
ep->state = DISK_DONE;
continue;
}
errors = validate_optionstr(dp);
if (errors) {
gchar **ptr;
tmp = quote_string(dp->name);
for (ptr = errors; *ptr; ptr++)
log_add(L_FAIL, "%s %s %s 0 [%s]", dp->host->hostname,
tmp, planner_timestamp, *ptr);
g_strfreev(errors);
g_free(tmp);
ep->state = DISK_DONE;
continue;
}
estimate = ES_CLIENT;
for (el = dp->estimatelist; el != NULL; el = el->next) {
estimate = (estimate_t)GPOINTER_TO_INT(el->data);
if (estimate == ES_SERVER)
break;
}
ep->allow_server = estimate == ES_SERVER;
if (ep->allow_server) {
info_t info;
tmp = quote_string(dp->name);
nb_server++;
get_info(dp->host->hostname, dp->name, &info);
for(i = 0; i < MAX_LEVELS; i++) {
int lev = ep->estimate[i].level;
if(lev == -1) break;
server_estimate(ep, i, &info, lev, tape);
}
g_fprintf(stderr,_("%s time %s: got result for host %s disk %s:"),
get_pname(), walltime_str(curclock()),
dp->host->hostname, tmp);
g_fprintf(stderr,_(" %d -> %lldK, %d -> %lldK, %d -> %lldK\n"),
ep->estimate[0].level,
(long long)ep->estimate[0].nsize,
ep->estimate[1].level,
(long long)ep->estimate[1].nsize,
ep->estimate[2].level,
(long long)ep->estimate[2].nsize);
if (!am_has_feature(features, fe_xml_estimate)) {
ep->state = DISK_DONE;
remove_est(&startq, ep);
enqueue_est(&estq, ep);
}
g_free(tmp);
}
estimate = ES_SERVER;
for (el = dp->estimatelist; el != NULL; el = el->next) {
estimate = (estimate_t)GPOINTER_TO_INT(el->data);
if (estimate == ES_CLIENT || estimate == ES_CALCSIZE)
break;
}
if (estimate != ES_CLIENT && estimate != ES_CALCSIZE
&& !(am_has_feature(features, fe_req_xml)
|| am_has_feature(features, fe_xml_estimate)))
continue;
/*
* We have one client estimate to deal with
*/
nb_client++;
array = g_ptr_array_new();
if (am_has_feature(features, fe_req_xml)) {
for(i = 0; i < MAX_LEVELS; i++) {
int level = ep->estimate[i].level;
if (level == -1)
break;
}
estimates_for_client = i;
g_ptr_array_add(array, client_estimate_as_xml(ep, features, estimates_for_client));
} else if (!g_str_equal(dp->program, "DUMP") &&
!g_str_equal(dp->program, "GNUTAR")) {
g_free(ep->errstr);
ep->errstr = g_strdup("does not support application-api");
} else {
char *option_string = NULL;
if (am_has_feature(features, fe_sendsize_req_options)) {
option_string = optionstr(dp);
if (!option_string)
error("problem with option string, check the dumptype definition.\n");
}
for (i = 0; i < MAX_LEVELS; i++) {
one_est_t *est = &(ep->estimate[i]);
int level = est->level;
if(level == -1)
break;
if (estimate == ES_CALCSIZE && !am_has_feature(features, fe_calcsize_estimate)) {
tmp = quote_string(dp->name);
log_add(L_WARNING, "%s:%s does not support CALCSIZE for estimate, "
"using CLIENT.\n", hostp->hostname, tmp);
g_free(tmp);
estimate = ES_CLIENT;
}
g_ptr_array_add(array, client_lvl_estimate_as_text(dp, est,
(estimate == ES_CALCSIZE), option_string));
}
g_free(option_string);
estimates_for_client = i;
}
/*
* We want a separator-terminated string here.
*/
g_ptr_array_add(array, g_strdup(""));
g_ptr_array_add(array, NULL);
strings = (gchar **)g_ptr_array_free(array, FALSE);
if (estimates_for_client) {
tmp = g_strjoinv("\n", strings);
total_estimates += estimates_for_client;
g_string_append(reqbuf, tmp);
g_free(tmp);
if (ep->state == DISK_DONE) {
remove_est(&estq, ep);
ep->state = DISK_PARTIALY_DONE;
enqueue_est(&pestq, ep);
} else {
remove_est(&startq, ep);
ep->state = DISK_ACTIVE;
enqueue_est(&activeq, ep);
}
} else if (ep->state != DISK_DONE) {
remove_est(&startq, ep);
ep->state = DISK_DONE;
if (ep->errstr == NULL) {
ep->errstr = g_strdup(
_("Can't request estimate"));
}
enqueue_est(&failq, ep);
}
g_strfreev(strings);
}
if(total_estimates == 0) {
g_string_free(reqbuf, TRUE);
hostp->status = HOST_DONE;
return;
}
if (conf_etimeout < 0) {
timeout = - conf_etimeout;
} else {
timeout = total_estimates * conf_etimeout;
}
send:
/* find a dp with a valid auth */
for (dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
if (dp->todo && dp->auth) break;
}
if (!dp)
return;
req = g_string_free(reqbuf, FALSE);
dbprintf("send request to %s:%s:(%s):\n----\n%s\n----\n\n", hostp->hostname, hostp->disks->name, dp->name, req);
secdrv = security_getdriver(dp->auth);
if (secdrv == NULL) {
hostp->status = HOST_DONE;
log_add(L_ERROR,
_("Could not find security driver '%s' for host '%s'"),
hostp->disks->auth, hostp->hostname);
g_free(req);
return;
}
hostp->status = HOST_ACTIVE;
while ((ep = dequeue_est(&activeq))) {
ep->errstr = NULL;
enqueue_est(&waitq, ep);
}
protocol_sendreq(hostp->hostname, secdrv, amhost_get_security_conf,
req, timeout, handle_result, hostp);
g_free(req);
}
static disk_t *lookup_hostdisk(
/*@keep@*/ am_host_t *hp,
char *str)
{
disk_t *dp;
for(dp = hp->disks; dp != NULL; dp = dp->hostnext)
if(g_str_equal(str, dp->name)) return dp;
return NULL;
}
static void handle_result(
void *datap,
pkt_t *pkt,
security_handle_t *sech)
{
int level, i;
gint64 size;
est_t *ep;
disk_t *dp;
am_host_t *hostp;
char *msg, msg_undo;
char *remoterr, *errbuf = NULL;
char *s;
char *t;
char *fp;
char *line;
int ch;
int tch;
char *qname;
char *disk = NULL;
long long size_;
hostp = (am_host_t *)datap;
hostp->status = HOST_READY;
if (pkt == NULL) {
if (g_str_equal(security_geterror(sech), "timeout waiting for REP")) {
errbuf = g_strdup_printf("Some estimate timeout on %s", hostp->hostname);
} else {
errbuf = g_strdup_printf(_("Request to %s failed: %s"),
hostp->hostname, security_geterror(sech));
}
goto error_return;
}
if (pkt->type == P_NAK) {
s = pkt->body;
if(strncmp_const_skip(s, "ERROR ", s, ch) == 0) {
ch = *s++;
} else {
goto NAK_parse_failed;
}
skip_whitespace(s, ch);
if(ch == '\0') goto NAK_parse_failed;
remoterr = s - 1;
if((s = strchr(remoterr, '\n')) != NULL) {
if(s == remoterr) goto NAK_parse_failed;
*s = '\0';
}
if (!g_str_equal(remoterr, "unknown service: noop")
&& !g_str_equal(remoterr, "noop: invalid service")) {
errbuf = g_strjoin(NULL, hostp->hostname, " NAK: ", remoterr, NULL);
if(s) *s = '\n';
goto error_return;
}
}
dbprintf(_("got reply:\n----\n%s\n----\n\n"), pkt->body);
s = pkt->body;
ch = *s++;
while(ch) {
line = s - 1;
if(strncmp_const(line, "OPTIONS ") == 0) {
t = strstr(line, "features=");
if(t != NULL && (g_ascii_isspace((int)t[-1]) || t[-1] == ';')) {
char *u = strchr(t, ';');
if (u)
*u = '\0';
t += sizeof("features=")-1;
am_release_feature_set(hostp->features);
if((hostp->features = am_string_to_feature(t)) == NULL) {
errbuf = g_strdup_printf(hostp->hostname,
_(": bad features value: %s\n"), line);
goto error_return;
}
if (u)
*u = ';';
}
skip_quoted_line(s, ch);
continue;
}
t = line;
if ((strncmp_const_skip(t, "ERROR ", t, tch) == 0) ||
(strncmp_const_skip(t, "WARNING ", t, tch) == 0)) {
fp = t - 1;
skip_whitespace(t, tch);
if (tch == '\n') {
t[-1] = '\0';
}
/*
* If the "error" is that the "noop" service is unknown, it
* just means the client is "old" (does not support the service).
* We can ignore this.
*/
if(hostp->features == NULL
&& pkt->type == P_NAK
&& (g_str_equal(t - 1, "unknown service: noop")
|| g_str_equal(t - 1, "noop: invalid service"))) {
skip_quoted_line(s, ch);
continue;
}
t = strchr(t,'\n');
if (t) /* truncate after the first line */
*t = '\0';
errbuf = g_strjoin(NULL, hostp->hostname,
(pkt->type == P_NAK) ? "NAK " : "",
": ",
fp,
NULL);
goto next_line;
}
msg = t = line;
tch = *(t++);
skip_quoted_string(t, tch);
t[-1] = '\0';
disk = unquote_string(msg);
skip_whitespace(t, tch);
if (sscanf(t - 1, "%d", &level) != 1) {
amfree(disk);
goto bad_msg;
}
skip_integer(t, tch);
skip_whitespace(t, tch);
dp = lookup_hostdisk(hostp, disk);
amfree(disk);
if(dp == NULL) {
log_add(L_ERROR, _("%s: invalid reply from sendsize: `%s'\n"),
hostp->hostname, line);
amfree(disk);
goto bad_msg;
}
ep = find_est_for_dp(dp);
size = (gint64)-1;
if (strncmp_const(t-1,"SIZE ") == 0) {
if (sscanf(t - 1, "SIZE %lld", &size_) != 1) {
amfree(disk);
goto bad_msg;
}
size = (gint64)size_;
} else if ((strncmp_const(t-1,"ERROR ") == 0) ||
(strncmp_const(t-1,"WARNING ") == 0)) {
skip_non_whitespace(t, tch);
skip_whitespace(t, tch);
msg = t-1;
skip_quoted_string(t,tch);
msg_undo = t[-1];
t[-1] = '\0';
if (pkt->type == P_REP && !ep->errstr) {
ep->errstr = unquote_string(msg);
}
t[-1] = msg_undo;
} else {
amfree(disk);
goto bad_msg;
}
for (i = 0; i < MAX_LEVELS; i++) {
if (ep->estimate[i].level == level) {
if (size == (gint64)-2) {
ep->estimate[i].nsize = -1; /* remove estimate */
ep->estimate[i].guessed = 0;
} else if (size > (gint64)-1) {
/* take the size returned by the client */
ep->estimate[i].nsize = size;
ep->estimate[i].guessed = 0;
}
break;
}
}
if (i == MAX_LEVELS && level > 0) {
/* client always report level 0 for some error */
goto bad_msg; /* this est wasn't requested */
}
ep->got_estimate++;
s = t;
ch = tch;
next_line:
skip_quoted_line(s, ch);
}
if(hostp->status == HOST_READY && hostp->features == NULL) {
/*
* The client does not support the features list, so give it an
* empty one.
*/
dbprintf(_("no feature set from host %s\n"), hostp->hostname);
hostp->features = am_set_default_feature_set();
}
/* XXX what about disks that only got some estimates... do we care? */
/* XXX amanda 2.1 treated that case as a bad msg */
for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
if (dp->todo == 0) continue;
ep = find_est_for_dp(dp);
if(ep->state != DISK_ACTIVE &&
ep->state != DISK_PARTIALY_DONE) continue;
if(ep->state == DISK_ACTIVE) {
remove_est(&waitq, ep);
}
else if(ep->state == DISK_PARTIALY_DONE) {
remove_est(&pestq, ep);
}
if(pkt->type == P_REP) {
ep->state = DISK_DONE;
}
else if(pkt->type == P_PREP) {
ep->state = DISK_PARTIALY_DONE;
}
if (ep->estimate[0].level == -1) continue; /* ignore this disk */
qname = quote_string(dp->name);
if(pkt->type == P_PREP) {
g_fprintf(stderr,_("%s: time %s: got partial result for host %s disk %s:"),
get_pname(), walltime_str(curclock()),
dp->host->hostname, qname);
g_fprintf(stderr,_(" %d -> %lldK, %d -> %lldK, %d -> %lldK\n"),
ep->estimate[0].level,
(long long)ep->estimate[0].nsize,
ep->estimate[1].level,
(long long)ep->estimate[1].nsize,
ep->estimate[2].level,
(long long)ep->estimate[2].nsize);
enqueue_est(&pestq, ep);
}
else if(pkt->type == P_REP) {
g_fprintf(stderr,_("%s: time %s: got result for host %s disk %s:"),
get_pname(), walltime_str(curclock()),
dp->host->hostname, qname);
g_fprintf(stderr,_(" %d -> %lldK, %d -> %lldK, %d -> %lldK\n"),
ep->estimate[0].level,
(long long)ep->estimate[0].nsize,
ep->estimate[1].level,
(long long)ep->estimate[1].nsize,
ep->estimate[2].level,
(long long)ep->estimate[2].nsize);
if ((ep->estimate[0].level != -1 &&
ep->estimate[0].nsize > (gint64)0) ||
(ep->estimate[1].level != -1 &&
ep->estimate[1].nsize > (gint64)0) ||
(ep->estimate[2].level != -1 &&
ep->estimate[2].nsize > (gint64)0)) {
for (i=MAX_LEVELS-1; i >=0; i--) {
if (ep->estimate[i].level != -1 &&
ep->estimate[i].nsize < (gint64)0) {
ep->estimate[i].level = -1;
}
}
enqueue_est(&estq, ep);
}
else {
enqueue_est(&failq, ep);
if(ep->got_estimate && !ep->errstr) {
ep->errstr = g_strdup_printf("disk %s, all estimate failed",
qname);
}
else {
g_fprintf(stderr,
_("error result for host %s disk %s: missing estimate\n"),
dp->host->hostname, qname);
if (ep->errstr == NULL) {
ep->errstr = g_strdup_printf(_("missing result for %s in %s response"),
qname, dp->host->hostname);
}
}
}
hostp->status = HOST_DONE;
}
if (ep->post_dle == 0 &&
(pkt->type == P_REP ||
((ep->estimate[0].level == -1 ||
ep->estimate[0].nsize > (gint64)0) &&
(ep->estimate[1].level == -1 ||
ep->estimate[1].nsize > (gint64)0) &&
(ep->estimate[2].level == -1 ||
ep->estimate[2].nsize > (gint64)0)))) {
run_server_dle_scripts(EXECUTE_ON_POST_DLE_ESTIMATE,
get_config_name(), planner_timestamp, dp,
ep->estimate[0].level);
ep->post_dle = 1;
}
amfree(qname);
}
if(hostp->status == HOST_DONE) {
if (pkt->type == P_REP) {
security_close_connection(sech, hostp->hostname);
run_server_host_scripts(EXECUTE_ON_POST_HOST_ESTIMATE,
get_config_name(), planner_timestamp,
hostp);
}
}
getsize(hostp);
/* try to clean up any defunct processes, since Amanda doesn't wait() for
them explicitly */
while(waitpid(-1, NULL, WNOHANG)> 0);
if (errbuf)
goto error_return;
return;
NAK_parse_failed:
errbuf = g_strdup_printf(_("%s NAK: [NAK parse failed]"), hostp->hostname);
g_fprintf(stderr, _("got strange nak from %s:\n----\n%s----\n\n"),
hostp->hostname, pkt->body);
goto error_return;
bad_msg:
g_fprintf(stderr,_("got a bad message, stopped at:\n"));
/*@ignore@*/
g_fprintf(stderr,_("----\n%s----\n\n"), line);
errbuf = g_strconcat(_("badly formatted response from "),
hostp->hostname, NULL);
/*@end@*/
error_return:
i = 0;
for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
if (dp->todo) {
ep = find_est_for_dp(dp);
if (ep->state == DISK_ACTIVE ||
ep->state == DISK_PARTIALY_DONE) {
char *tt;
qname = quote_string(dp->name);
if (ep->state == DISK_ACTIVE) {
remove_est(&waitq, ep);
} else if(ep->state == DISK_PARTIALY_DONE) {
remove_est(&pestq, ep);
}
ep->state = DISK_DONE;
if (ep->allow_server) {
enqueue_est(&estq, ep);
tt = ", using server estimate";
} else {
enqueue_est(&failq, ep);
tt = "";
}
i++;
ep->errstr = g_strdup_printf("%s%s", errbuf, tt);
g_fprintf(stderr, _("error result for host %s disk %s: %s\n"),
dp->host->hostname, qname, ep->errstr);
amfree(qname);
}
}
}
if(i == 0) {
/*
* If there were no disks involved, make sure the error gets
* reported.
*/
log_add(L_ERROR, "%s", errbuf);
for (dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
if (dp->todo) {
ep = find_est_for_dp(dp);
qname = quote_string(dp->name);
if(ep->state == DISK_ACTIVE) {
remove_est(&waitq, ep);
} else if(ep->state == DISK_PARTIALY_DONE) {
remove_est(&pestq, ep);
} else {
remove_est(&startq, ep);
}
ep->state = DISK_DONE;
enqueue_est(&failq, ep);
ep->errstr = g_strdup(errbuf);
g_fprintf(stderr, _("error result for host %s disk %s: %s\n"),
dp->host->hostname, qname, errbuf);
amfree(qname);
}
}
}
hostp->status = HOST_DONE;
amfree(errbuf);
/* try to clean up any defunct processes, since Amanda doesn't wait() for
them explicitly */
while(waitpid(-1, NULL, WNOHANG)> 0);
}
/*
* ========================================================================
* ANALYSE ESTIMATES
*
*/
static int schedule_order(est_t *a, est_t *b); /* subroutines */
static one_est_t *pick_inclevel(est_t *ep);
static void analyze_estimate(
est_t *ep)
{
disk_t *dp = ep->disk;
info_t info;
int have_info = 0;
char *qname = quote_string(dp->name);
g_fprintf(stderr, _("pondering %s:%s... "),
dp->host->hostname, qname);
g_fprintf(stderr, _("next_level0 %d last_level %d "),
ep->next_level0, ep->last_level);
if (get_info(dp->host->hostname, dp->name, &info) == 0) {
have_info = 1;
}
ep->degr_est = &default_one_est;
if (ep->next_level0 <= 0 || (have_info && ep->last_level == 0
&& (ISSET(info.command, FORCE_NO_BUMP)))) {
if (ep->next_level0 <= 0) {
g_fprintf(stderr,_("(due for level 0) "));
}
ep->dump_est = est_for_level(ep, 0);
if (ep->dump_est->csize <= (gint64)0) {
g_fprintf(stderr,
_("(no estimate for level 0, picking an incr level)\n"));
ep->dump_est = pick_inclevel(ep);
if (ep->dump_est->nsize == (gint64)-1) {
ep->dump_est = est_for_level(ep, ep->dump_est->level + 1);
}
}
else {
total_lev0 += (double) ep->dump_est->csize;
if(ep->last_level == -1 || dp->skip_incr) {
g_fprintf(stderr,_("(%s disk, can't switch to degraded mode)\n"),
dp->skip_incr? "skip-incr":_("new"));
if (dp->skip_incr && ep->degr_mesg == NULL) {
ep->degr_mesg = _("Skpping: skip-incr disk can't be dumped in degraded mode");
}
ep->degr_est = &default_one_est;
}
else {
/* fill in degraded mode info */
g_fprintf(stderr,_("(picking inclevel for degraded mode)"));
ep->degr_est = pick_inclevel(ep);
if (ep->degr_est->level >= 0 &&
ep->degr_est->csize == (gint64)-1) {
ep->degr_est = est_for_level(ep, ep->degr_est->level + 1);
}
if (ep->degr_est->csize == (gint64)-1) {
g_fprintf(stderr,_("(no inc estimate)"));
if (ep->degr_mesg == NULL)
ep->degr_mesg = _("Skipping: an incremental estimate could not be performed, so disk cannot be dumped in degraded mode");
ep->degr_est = &default_one_est;
}
g_fprintf(stderr,"\n");
}
}
}
else {
g_fprintf(stderr,_("(not due for a full dump, picking an incr level)\n"));
/* XXX - if this returns -1 may be we should force a total? */
ep->dump_est = pick_inclevel(ep);
if (ep->dump_est->csize == (gint64)-1) {
ep->dump_est = est_for_level(ep, ep->last_level);
}
if (ep->dump_est->csize == (gint64)-1) {
ep->dump_est = est_for_level(ep, ep->last_level + 1);
}
if (ep->dump_est->csize == (gint64)-1) {
ep->dump_est = est_for_level(ep, 0);
}
if (ep->degr_mesg == NULL) {
ep->degr_mesg = _("Skipping: a full is not planned, so can't dump in degraded mode");
}
}
if (ep->dump_est->level < 0) {
int i;
char *q = quote_string("no estimate");
g_fprintf(stderr,_(" no valid estimate\n"));
for(i=0; i<MAX_LEVELS; i++) {
if (ep->estimate[i].level >= 0) {
g_fprintf(stderr,(" level: %d nsize: %lld csize: %lld\n"),
ep->estimate[i].level,
(long long)ep->estimate[i].nsize,
(long long)ep->estimate[i].csize);
}
}
log_add(L_WARNING, _("%s %s %s 0 %s"), dp->host->hostname, qname,
planner_timestamp, q);
amfree(q);
}
g_fprintf(stderr,_(" curr level %d nsize %lld csize %lld "),
ep->dump_est->level, (long long)ep->dump_est->nsize,
(long long)ep->dump_est->csize);
insert_est(&schedq, ep, schedule_order);
total_size += (gint64)tt_blocksize_kb + ep->dump_est->csize + tape_mark;
/* update the balanced size */
if(!(dp->skip_full || dp->strategy == DS_NOFULL ||
dp->strategy == DS_INCRONLY)) {
gint64 lev0size;
lev0size = est_tape_size(ep, 0);
if(lev0size == (gint64)-1) lev0size = ep->last_lev0size;
if (dp->strategy == DS_NOINC) {
balanced_size += (double)lev0size;
} else if (dp->dumpcycle == 0) {
balanced_size += (double)(lev0size * conf_dumpcycle / (gint64)runs_per_cycle);
} else if (dp->dumpcycle != conf_dumpcycle) {
balanced_size += (double)(lev0size * (conf_dumpcycle / dp->dumpcycle) / (gint64)runs_per_cycle);
} else {
balanced_size += (double)(lev0size / (gint64)runs_per_cycle);
}
}
g_fprintf(stderr,_("total size %lld total_lev0 %1.0lf balanced-lev0size %1.0lf\n"),
(long long)total_size, total_lev0, balanced_size);
/* Log errstr even if the estimate succeeded */
/* It can be an error from a script */
if (ep->errstr) {
char *qerrstr = quote_string(ep->errstr);
/* Log only a warning if a server estimate is available */
if (ep->estimate[0].nsize > 0 ||
ep->estimate[1].nsize > 0 ||
ep->estimate[2].nsize > 0) {
log_add(L_WARNING, _("%s %s %s 0 %s"), dp->host->hostname, qname,
planner_timestamp, qerrstr);
} else {
log_add(L_FAIL, _("%s %s %s 0 %s"), dp->host->hostname, qname,
planner_timestamp, qerrstr);
}
amfree(qerrstr);
}
amfree(qname);
}
static void handle_failed(
est_t *ep)
{
disk_t *dp = ep->disk;
char *errstr, *errstr1, *qerrstr;
char *qname = quote_string(dp->name);
errstr = ep->errstr? ep->errstr : _("hmm, no error indicator!");
errstr1 = g_strjoin(NULL, "[",errstr,"]", NULL);
qerrstr = quote_string(errstr1);
amfree(errstr1);
g_fprintf(stderr, _("%s: FAILED %s %s %s 0 %s\n"),
get_pname(), dp->host->hostname, qname, planner_timestamp, qerrstr);
log_add(L_FAIL, _("%s %s %s 0 %s"), dp->host->hostname, qname,
planner_timestamp, qerrstr);
amfree(qerrstr);
amfree(qname);
/* XXX - memory leak with *dp */
}
/*
* insert-sort by decreasing priority, then
* by decreasing size within priority levels.
*/
static int schedule_order(
est_t *a,
est_t *b)
{
int diff;
gint64 ldiff;
diff = b->dump_priority - a->dump_priority;
if(diff != 0) return diff;
ldiff = b->dump_est->csize - a->dump_est->csize;
if(ldiff < (gint64)0) return -1; /* XXX - there has to be a better way to dothis */
if(ldiff > (gint64)0) return 1;
return 0;
}
static one_est_t *pick_inclevel(
est_t *ep)
{
one_est_t *level0_est, *base_est, *bump_est;
gint64 thresh;
char *qname;
disk_t *dp = ep->disk;
level0_est = est_for_level(ep, 0);
base_est = est_for_level(ep, ep->last_level);
/* if last night was level 0, do level 1 tonight, no ifs or buts */
if (base_est->level == 0) {
g_fprintf(stderr,_(" picklev: last night 0, so tonight level 1\n"));
return est_for_level(ep, 1);
}
/* if no-full option set, always do level 1 */
if(dp->strategy == DS_NOFULL) {
g_fprintf(stderr,_(" picklev: no-full set, so always level 1\n"));
return est_for_level(ep, 1);
}
/* if we didn't get an estimate, we can't do an inc */
if (base_est->nsize == (gint64)-1) {
bump_est = est_for_level(ep, ep->last_level + 1);
if (bump_est->nsize > (gint64)0) { /* FORCE_BUMP */
g_fprintf(stderr,_(" picklev: bumping to level %d\n"), bump_est->level);
return bump_est;
}
g_fprintf(stderr,_(" picklev: no estimate for level %d, so no incs\n"), base_est->level);
return base_est;
}
thresh = bump_thresh(base_est->level, level0_est->nsize, dp->bumppercent,
dp->bumpsize, dp->bumpmult);
g_fprintf(stderr,
_(" pick: size %lld level %d days %d (thresh %lldK, %d days)\n"),
(long long)base_est->nsize, base_est->level, ep->level_days,
(long long)thresh, dp->bumpdays);
if(base_est->level == (DUMP_LEVELS - 1)
|| ep->level_days < dp->bumpdays
|| base_est->nsize <= thresh)
return base_est;
bump_est = est_for_level(ep, base_est->level + 1);
if (bump_est->nsize == (gint64)-1)
return base_est;
g_fprintf(stderr, _(" pick: next size %lld... "),
(long long)bump_est->nsize);
if (base_est->nsize - bump_est->nsize < thresh) {
g_fprintf(stderr, _("not bumped\n"));
return base_est;
}
qname = quote_string(dp->name);
g_fprintf(stderr, _("BUMPED\n"));
log_add(L_INFO, _("Incremental of %s:%s bumped to level %d."),
dp->host->hostname, qname, bump_est->level);
amfree(qname);
return bump_est;
}
/*
** ========================================================================
** ADJUST SCHEDULE
**
** We have two strategies here:
**
** 1. Delay dumps
**
** If we are trying to fit too much on the tape something has to go. We
** try to delay totals until tomorrow by converting them into incrementals
** and, if that is not effective enough, dropping incrementals altogether.
** While we are searching for the guilty dump (the one that is really
** causing the schedule to be oversize) we have probably trampled on a lot of
** innocent dumps, so we maintain a "before image" list and use this to
** put back what we can.
**
** 2. Promote dumps.
**
** We try to keep the amount of tape used by total dumps the same each night.
** If there is some spare tape in this run we have a look to see if any of
** tonights incrementals could be promoted to totals and leave us with a
** more balanced cycle.
*/
static void delay_one_dump(est_t *ep, int delete, ...);
static int promote_highest_priority_incremental(void);
static int promote_hills(void);
/* delay any dumps that will not fit */
static void delay_dumps(void)
{
GList *elist, *elist_next, *elist_prev;
est_t * ep;
est_t * preserve_ep;
est_t * delayed_ep;
disk_t * dp;
disk_t * delayed_dp;
bi_t * bi;
bi_t * nbi;
gint64 new_total; /* New total_size */
char est_kb[20]; /* Text formatted dump size */
char tape_kb[20]; /* Text formatted tape size */
int nb_forced_level_0;
info_t info;
int delete;
char * message;
gint64 full_size;
time_t timestamps;
int priority;
biq.head = biq.tail = NULL;
/*
** 1. Delay dumps that are way oversize.
**
** Dumps larger that the size of the tapes we are using are just plain
** not going to fit no matter how many other dumps we drop. Delay
** oversize totals until tomorrow (by which time my owner will have
** resolved the problem!) and drop incrementals altogether. Naturally
** a large total might be delayed into a large incremental so these
** need to be checked for separately.
*/
for (elist = schedq.head; elist != NULL; elist = elist_next) {
int avail_tapes = 1;
elist_next = elist->next;
ep = get_est(elist);
dp = ep->disk;
if (dp->tape_splitsize > (gint64)0 || dp->allow_split)
avail_tapes = conf_runtapes;
full_size = est_tape_size(ep, 0);
if (full_size > tapetype_get_length(tape) * (gint64)avail_tapes) {
char *qname = quote_string(dp->name);
if (conf_runtapes > 1 && (dp->tape_splitsize == 0 ||
!dp->allow_split)) {
log_add(L_WARNING, _("disk %s:%s, full dump (%lldKB) will be larger than available tape space"
", you could allow it to split"),
dp->host->hostname, qname,
(long long)full_size);
} else {
log_add(L_WARNING, _("disk %s:%s, full dump (%lldKB) will be larger than available tape space"),
dp->host->hostname, qname,
(long long)full_size);
}
amfree(qname);
}
if (ep->dump_est->csize == (gint64)-1 ||
ep->dump_est->csize <= tapetype_get_length(tape) * (gint64)avail_tapes) {
continue;
}
/* Format dumpsize for messages */
g_snprintf(est_kb, 20, "%lld KB",
(long long)ep->dump_est->csize);
g_snprintf(tape_kb, 20, "%lld KB",
(long long)tapetype_get_length(tape) * (gint64)avail_tapes);
if(ep->dump_est->level == 0) {
if(dp->skip_incr) {
delete = 1;
message = _("but cannot incremental dump skip-incr disk");
}
else if(ep->last_level < 0) {
delete = 1;
message = _("but cannot incremental dump new disk");
}
else if(ep->degr_est->level < 0) {
delete = 1;
message = _("but no incremental estimate");
}
else if (ep->degr_est->csize > tapetype_get_length(tape)) {
delete = 1;
message = _("incremental dump also larger than tape");
}
else {
delete = 0;
message = _("full dump delayed, doing incremental");
}
}
else {
delete = 1;
message = _("skipping incremental");
}
delay_one_dump(ep, delete, "dump estimate (", est_kb,
") is larger than available tape space (", tape_kb,
"), ", message, NULL);
}
/*
** 2. Delay total dumps.
**
** Delay total dumps until tomorrow (or the day after!). We start with
** the lowest priority (most dispensable) and work forwards. We take
** care not to delay *all* the dumps since this could lead to a stale
** mate [for any one disk there are only three ways tomorrows dump will
** be smaller than todays: 1. we do a level 0 today so tomorows dump
** will be a level 1; 2. the disk gets more data so that it is bumped
** tomorrow (this can be a slow process); and, 3. the disk looses some
** data (when does that ever happen?)].
*/
nb_forced_level_0 = 0;
preserve_ep = NULL;
timestamps = 2147483647;
priority = 0;
for (elist = schedq.head; elist != NULL; elist = elist->next) {
ep = get_est(elist);
dp = ep->disk;
if (ep->dump_est->level == 0) {
if (!preserve_ep ||
ep->dump_priority > priority ||
(ep->dump_priority == priority &&
ep->info->inf[0].date < timestamps)) {
priority = ep->dump_priority;
timestamps = ep->info->inf[0].date;
preserve_ep = ep;
}
}
}
/* 2.a. Do not delay forced full */
delayed_ep = NULL;
delayed_dp = NULL;
do {
delayed_ep = NULL;
delayed_dp = NULL;
timestamps = 0;
for (elist = schedq.tail;
elist != NULL && total_size > tape_length;
elist = elist_prev) {
elist_prev = elist->prev;
ep = get_est(elist);
dp = ep->disk;
if(ep->dump_est->level != 0) continue;
get_info(dp->host->hostname, dp->name, &info);
if(ISSET(info.command, FORCE_FULL)) {
nb_forced_level_0 += 1;
preserve_ep = ep;
continue;
}
if (ep != preserve_ep &&
ep->info->inf[0].date > timestamps) {
delayed_ep = ep;
delayed_dp = dp;
timestamps = ep->info->inf[0].date;
}
}
if (delayed_ep) {
/* Format dumpsize for messages */
g_snprintf(est_kb, 20, "%lld KB,",
(long long)delayed_ep->dump_est->csize);
if(delayed_dp->skip_incr) {
delete = 1;
message = _("but cannot incremental dump skip-incr disk");
}
else if(delayed_ep->last_level < 0) {
delete = 1;
message = _("but cannot incremental dump new disk");
}
else if(delayed_ep->degr_est->level < 0) {
delete = 1;
message = _("but no incremental estimate");
}
else {
delete = 0;
message = _("full dump delayed, doing incremental");
}
delay_one_dump(delayed_ep, delete, _("dumps too big,"), est_kb,
message, NULL);
}
} while (delayed_ep);
/* 2.b. Delay forced full if needed */
if(nb_forced_level_0 > 0 && total_size > tape_length) {
for (elist = schedq.tail;
elist != NULL && total_size > tape_length;
elist = elist_prev) {
elist_prev = elist->prev;
ep = get_est(elist);
dp = ep->disk;
if(ep->dump_est->level == 0 && ep != preserve_ep) {
/* Format dumpsize for messages */
g_snprintf(est_kb, 20, "%lld KB,",
(long long)ep->dump_est->csize);
if(dp->skip_incr) {
delete = 1;
message = _("but cannot incremental dump skip-incr disk");
}
else if(ep->last_level < 0) {
delete = 1;
message = _("but cannot incremental dump new disk");
}
else if(ep->degr_est->level < 0) {
delete = 1;
message = _("but no incremental estimate");
}
else {
delete = 0;
message = _("full dump delayed");
}
delay_one_dump(ep, delete, _("dumps too big,"), est_kb,
message, NULL);
}
}
}
/*
** 3. Delay incremental dumps.
**
** Delay incremental dumps until tomorrow. This is a last ditch attempt
** at making things fit. Again, we start with the lowest priority (most
** dispensable) and work forwards.
*/
for (elist = schedq.tail;
elist != NULL && total_size > tape_length;
elist = elist_prev) {
elist_prev = elist->prev;
ep = get_est(elist);
dp = ep->disk;
if(ep->dump_est->level != 0) {
/* Format dumpsize for messages */
g_snprintf(est_kb, 20, "%lld KB,",
(long long)ep->dump_est->csize);
delay_one_dump(ep, 1,
_("dumps way too big,"),
est_kb,
_("must skip incremental dumps"),
NULL);
}
}
/*
** 4. Reinstate delayed dumps.
**
** We might not have needed to stomp on all of the dumps we have just
** delayed above. Try to reinstate them all starting with the last one
** and working forwards. It is unlikely that the last one will fit back
** in but why complicate the code?
*/
/*@i@*/ for(bi = biq.tail; bi != NULL; bi = nbi) {
int avail_tapes = 1;
nbi = bi->prev;
ep = bi->ep;
dp = ep->disk;
if(dp->tape_splitsize > (gint64)0 || dp->allow_split)
avail_tapes = conf_runtapes;
if(bi->deleted) {
new_total = total_size + (gint64)tt_blocksize_kb +
bi->csize + (gint64)tape_mark;
} else {
new_total = total_size - ep->dump_est->csize + bi->csize;
}
if((new_total <= tape_length) &&
(bi->csize < (tapetype_get_length(tape) * (gint64)avail_tapes))) {
/* reinstate it */
total_size = new_total;
if(bi->deleted) {
if(bi->level == 0) {
total_lev0 += (double) bi->csize;
}
insert_est(&schedq, ep, schedule_order);
}
else {
ep->dump_est = est_for_level(ep, bi->level);
}
/* Keep it clean */
if(bi->next == NULL)
biq.tail = bi->prev;
else
(bi->next)->prev = bi->prev;
if(bi->prev == NULL)
biq.head = bi->next;
else
(bi->prev)->next = bi->next;
amfree(bi->errstr);
amfree(bi);
}
}
/*
** 5. Output messages about what we have done.
**
** We can't output messages while we are delaying dumps because we might
** reinstate them later. We remember all the messages and output them
** now.
*/
/*@i@*/ for(bi = biq.head; bi != NULL; bi = nbi) {
nbi = bi->next;
if(bi->deleted) {
g_fprintf(stderr, "%s: FAILED %s\n", get_pname(), bi->errstr);
log_add(L_FAIL, "%s", bi->errstr);
}
else {
ep = bi->ep;
g_fprintf(stderr, _(" delay: %s now at level %d\n"),
bi->errstr, ep->dump_est->level);
log_add(L_INFO, "%s", bi->errstr);
}
/*@ignore@*/
amfree(bi->errstr);
amfree(bi);
/*@end@*/
}
g_fprintf(stderr, _(" delay: Total size now %lld.\n"),
(long long)total_size);
return;
}
/*
* Remove a dump or modify it from full to incremental.
* Keep track of it on the bi q in case we can add it back later.
*/
static void delay_one_dump(est_t *ep, int delete, ...)
{
bi_t *bi;
va_list argp;
char *next;
GString *errbuf = g_string_new(NULL);
GPtrArray *array = g_ptr_array_new();
gchar **strings;
char *tmp, *qtmp;
one_est_t *estimate = ep->dump_est;
disk_t *dp = ep->disk;
arglist_start(argp, delete);
total_size -= (gint64)tt_blocksize_kb + estimate->csize + (gint64)tape_mark;
if(estimate->level == 0)
total_lev0 -= (double) estimate->csize;
bi = g_new(bi_t, 1);
bi->next = NULL;
bi->prev = biq.tail;
if(biq.tail == NULL)
biq.head = bi;
else
biq.tail->next = bi;
biq.tail = bi;
bi->deleted = delete;
bi->ep = ep;
bi->level = estimate->level;
bi->nsize = estimate->nsize;
bi->csize = estimate->csize;
tmp = quote_string(dp->name);
g_string_append_printf(errbuf, "%s %s %s %d", dp->host->hostname, tmp,
(planner_timestamp) ? planner_timestamp : "?", estimate->level);
g_free(tmp);
while ((next = arglist_val(argp, char *)) != NULL)
g_ptr_array_add(array, next);
g_ptr_array_add(array, NULL);
strings = (gchar **)g_ptr_array_free(array, FALSE);
tmp = g_strjoinv(" ", strings);
g_free(strings); /* We must not free the arguments! */
qtmp = quote_string(tmp);
g_free(tmp);
g_string_append_printf(errbuf, " [%s]", qtmp);
g_free(qtmp);
bi->errstr = g_string_free(errbuf, FALSE);
arglist_end(argp);
if (delete) {
remove_est(&schedq, ep);
} else {
ep->dump_est = ep->degr_est;
total_size += (gint64)tt_blocksize_kb + ep->dump_est->csize + (gint64)tape_mark;
}
return;
}
static int promote_highest_priority_incremental(void)
{
GList *elist, *elist1;
disk_t *dp, *dp1, *dp_promote;
est_t *ep, *ep1, *ep_promote;
gint64 new_total, new_lev0;
int check_days;
int nb_today, nb_same_day, nb_today2;
int nb_disk_today, nb_disk_same_day;
char *qname;
/*
* return 1 if did so; must update total_size correctly; must not
* cause total_size to exceed tape_length
*/
dp_promote = NULL;
ep_promote = NULL;
for (elist = schedq.head; elist != NULL; elist = elist->next) {
one_est_t *level0_est;
ep = get_est(elist);
dp = ep->disk;
level0_est = est_for_level(ep, 0);
ep->promote = -1000;
if (level0_est->nsize <= (gint64)0)
continue;
if(ep->next_level0 <= 0)
continue;
if(ep->next_level0 > dp->maxpromoteday)
continue;
new_total = total_size - ep->dump_est->csize + level0_est->csize;
new_lev0 = (gint64)total_lev0 + level0_est->csize;
/* do not promote if overflow tape */
if(new_total > tape_length)
continue;
nb_today = 0;
nb_same_day = 0;
nb_disk_today = 0;
nb_disk_same_day = 0;
for (elist1 = schedq.head; elist1 != NULL; elist1 = elist1->next) {
ep1 = get_est(elist1);
dp1 = ep1->disk;
if(ep1->dump_est->level == 0)
nb_disk_today++;
else if(ep1->next_level0 == ep->next_level0)
nb_disk_same_day++;
if(g_str_equal(dp->host->hostname, dp1->host->hostname)) {
if(ep1->dump_est->level == 0)
nb_today++;
else if(ep1->next_level0 == ep->next_level0)
nb_same_day++;
}
}
/* do not promote if overflow balanced size and something today */
/* promote if nothing today */
if((new_lev0 > (gint64)(balanced_size + balance_threshold)) &&
(nb_disk_today > 0))
continue;
/* do not promote if only one disk due that day and nothing today */
if(nb_disk_same_day == 1 && nb_disk_today == 0)
continue;
nb_today2 = nb_today*nb_today;
if(nb_today == 0 && nb_same_day > 1)
nb_same_day++;
if(nb_same_day >= nb_today2) {
ep->promote = ((nb_same_day - nb_today2)*(nb_same_day - nb_today2)) +
conf_dumpcycle - ep->next_level0;
}
else {
ep->promote = -nb_today2 +
conf_dumpcycle - ep->next_level0;
}
qname = quote_string(dp->name);
if (!ep_promote || ep_promote->promote < ep->promote) {
ep_promote = ep;
dp_promote = dp;
g_fprintf(stderr," try %s:%s %d %d %d = %d\n",
dp->host->hostname, qname, nb_same_day, nb_today, ep->next_level0, ep->promote);
}
else {
g_fprintf(stderr,"no try %s:%s %d %d %d = %d\n",
dp->host->hostname, qname, nb_same_day, nb_today, ep->next_level0, ep->promote);
}
amfree(qname);
}
if (ep_promote) {
one_est_t *level0_est;
dp = dp_promote;
ep = ep_promote;
level0_est = est_for_level(ep, 0);
qname = quote_string(dp->name);
new_total = total_size - ep->dump_est->csize + level0_est->csize;
new_lev0 = (gint64)total_lev0 + level0_est->csize;
total_size = new_total;
total_lev0 = (double)new_lev0;
check_days = ep->next_level0;
ep->degr_est = ep->dump_est;
ep->dump_est = level0_est;
ep->next_level0 = 0;
g_fprintf(stderr,
_(" promote: moving %s:%s up, total_lev0 %1.0lf, total_size %lld\n"),
dp->host->hostname, qname,
total_lev0, (long long)total_size);
log_add(L_INFO,
plural(_("Full dump of %s:%s promoted from %d day ahead."),
_("Full dump of %s:%s promoted from %d days ahead."),
check_days),
dp->host->hostname, qname, check_days);
amfree(qname);
return 1;
}
return 0;
}
static int promote_hills(void)
{
GList *elist;
est_t *ep;
disk_t *dp;
struct balance_stats {
int disks;
gint64 size;
} *sp = NULL;
int days;
int hill_days = 0;
gint64 hill_size;
gint64 new_total;
int my_dumpcycle;
char *qname;
/* If we are already doing a level 0 don't bother */
if(total_lev0 > 0)
return 0;
/* Do the guts of an "amadmin balance" */
my_dumpcycle = conf_dumpcycle;
if(my_dumpcycle > 10000) my_dumpcycle = 10000;
sp = (struct balance_stats *)
g_malloc(sizeof(struct balance_stats) * my_dumpcycle);
for(days = 0; days < my_dumpcycle; days++) {
sp[days].disks = 0;
sp[days].size = (gint64)0;
}
for (elist = schedq.head; elist != NULL; elist = elist->next) {
ep = get_est(elist);
dp = ep->disk;
days = ep->next_level0;
if (days < 0) days = 0;
if(days<my_dumpcycle && !dp->skip_full && dp->strategy != DS_NOFULL &&
dp->strategy != DS_INCRONLY) {
sp[days].disks++;
sp[days].size += ep->last_lev0size;
}
}
/* Search for a suitable big hill and cut it down */
while(1) {
/* Find the tallest hill */
hill_size = (gint64)0;
for(days = 0; days < my_dumpcycle; days++) {
if(sp[days].disks > 1 && sp[days].size > hill_size) {
hill_size = sp[days].size;
hill_days = days;
}
}
if(hill_size <= (gint64)0) break; /* no suitable hills */
/* Find all the dumps in that hill and try and remove one */
for (elist = schedq.head; elist != NULL; elist = elist->next) {
one_est_t *level0_est;
ep = get_est(elist);
dp = ep->disk;
if(ep->next_level0 != hill_days ||
ep->next_level0 > dp->maxpromoteday ||
dp->skip_full ||
dp->strategy == DS_NOFULL ||
dp->strategy == DS_INCRONLY)
continue;
level0_est = est_for_level(ep, 0);
if (level0_est->nsize <= (gint64)0)
continue;
new_total = total_size - ep->dump_est->csize + level0_est->csize;
if(new_total > tape_length)
continue;
/* We found a disk we can promote */
qname = quote_string(dp->name);
total_size = new_total;
total_lev0 += (double)level0_est->csize;
ep->degr_est = ep->dump_est;
ep->dump_est = level0_est;
ep->next_level0 = 0;
g_fprintf(stderr,
_(" promote: moving %s:%s up, total_lev0 %1.0lf, total_size %lld\n"),
dp->host->hostname, qname,
total_lev0, (long long)total_size);
log_add(L_INFO,
plural(_("Full dump of %s:%s specially promoted from %d day ahead."),
_("Full dump of %s:%s specially promoted from %d days ahead."),
hill_days),
dp->host->hostname, qname, hill_days);
amfree(qname);
amfree(sp);
return 1;
}
/* All the disks in that hill were unsuitable. */
sp[hill_days].disks = 0; /* Don't get tricked again */
}
amfree(sp);
return 0;
}
/*
* ========================================================================
* OUTPUT SCHEDULE
*
* XXX - memory leak - we shouldn't just throw away *dp
*/
static void output_scheduleline(
est_t *ep)
{
disk_t *dp = ep->disk;
time_t dump_time = 0, degr_time = 0;
double dump_kps = 0, degr_kps = 0;
char *schedline = NULL, *degr_str = NULL;
char dump_priority_str[NUM_STR_SIZE];
char dump_level_str[NUM_STR_SIZE];
char dump_nsize_str[NUM_STR_SIZE];
char dump_csize_str[NUM_STR_SIZE];
char dump_time_str[NUM_STR_SIZE];
char dump_kps_str[NUM_STR_SIZE];
char degr_level_str[NUM_STR_SIZE];
char degr_nsize_str[NUM_STR_SIZE];
char degr_csize_str[NUM_STR_SIZE];
char degr_time_str[NUM_STR_SIZE];
char degr_kps_str[NUM_STR_SIZE];
char *dump_date, *degr_date;
char *features;
char *qname = quote_string(dp->name);
if(ep->dump_est->csize == (gint64)-1) {
/* no estimate, fail the disk */
g_fprintf(stderr,
_("%s: FAILED %s %s %s %d \"[no estimate]\"\n"),
get_pname(),
dp->host->hostname, qname, planner_timestamp, ep->dump_est->level);
log_add(L_FAIL, _("%s %s %s %d [no estimate]"),
dp->host->hostname, qname, planner_timestamp, ep->dump_est->level);
amfree(qname);
return;
}
dump_date = ep->dump_est->dumpdate;
degr_date = ep->degr_est->dumpdate;
#define fix_rate(rate) (rate < 1.0 ? DEFAULT_DUMPRATE : rate)
if(ep->dump_est->level == 0) {
dump_kps = fix_rate(ep->fullrate);
dump_time = (time_t)((double)ep->dump_est->csize / dump_kps);
if(ep->degr_est->csize != (gint64)-1) {
degr_kps = fix_rate(ep->incrrate);
degr_time = (time_t)((double)ep->degr_est->csize / degr_kps);
}
}
else {
dump_kps = fix_rate(ep->incrrate);
dump_time = (time_t)((double)ep->dump_est->csize / dump_kps);
}
if(ep->dump_est->level == 0 && ep->degr_est->csize != (gint64)-1) {
g_snprintf(degr_level_str, sizeof(degr_level_str),
"%d", ep->degr_est->level);
g_snprintf(degr_nsize_str, sizeof(degr_nsize_str),
"%lld", (long long)ep->degr_est->nsize);
g_snprintf(degr_csize_str, sizeof(degr_csize_str),
"%lld", (long long)ep->degr_est->csize);
g_snprintf(degr_time_str, sizeof(degr_time_str),
"%lld", (long long)degr_time);
g_snprintf(degr_kps_str, sizeof(degr_kps_str),
"%.0lf", degr_kps);
degr_str = g_strjoin(NULL, " ", degr_level_str,
" ", degr_date,
" ", degr_nsize_str,
" ", degr_csize_str,
" ", degr_time_str,
" ", degr_kps_str,
NULL);
} else {
char *degr_mesg;
if (ep->degr_mesg) {
degr_mesg = quote_string(ep->degr_mesg);
} else {
degr_mesg = quote_string(_("Skipping: cannot dump in degraded mode for unknown reason"));
}
degr_str = g_strjoin(NULL, " ", degr_mesg, NULL);
amfree(degr_mesg);
}
g_snprintf(dump_priority_str, sizeof(dump_priority_str),
"%d", ep->dump_priority);
g_snprintf(dump_level_str, sizeof(dump_level_str),
"%d", ep->dump_est->level);
g_snprintf(dump_nsize_str, sizeof(dump_nsize_str),
"%lld", (long long)ep->dump_est->nsize);
g_snprintf(dump_csize_str, sizeof(dump_csize_str),
"%lld", (long long)ep->dump_est->csize);
g_snprintf(dump_time_str, sizeof(dump_time_str),
"%lld", (long long)dump_time);
g_snprintf(dump_kps_str, sizeof(dump_kps_str),
"%.0lf", dump_kps);
features = am_feature_to_string(dp->host->features);
schedline = g_strjoin(NULL, "DUMP ",dp->host->hostname,
" ", features,
" ", qname,
" ", planner_timestamp,
" ", dump_priority_str,
" ", dump_level_str,
" ", dump_date,
" ", dump_nsize_str,
" ", dump_csize_str,
" ", dump_time_str,
" ", dump_kps_str,
degr_str ? degr_str : "",
"\n", NULL);
if (ep->dump_est->guessed == 1) {
log_add(L_WARNING, _("WARNING: no history available for %s:%s; guessing that size will be %lld KB\n"), dp->host->hostname, qname, (long long)ep->dump_est->csize);
}
fputs(schedline, stdout);
fputs(schedline, stderr);
amfree(features);
amfree(schedline);
amfree(degr_str);
amfree(qname);
}
static void
server_estimate(
est_t *ep,
int i,
info_t *info,
int level,
tapetype_t *tapetype)
{
int stats;
gint64 size;
disk_t *dp = ep->disk;
size = internal_server_estimate(dp, info, level, &stats, tapetype);
ep->dump_est = &ep->estimate[i];
ep->estimate[i].nsize = size;
if (stats == 0) {
ep->estimate[i].guessed = 1;
}
}
static void
cmdfile_flush(
gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data)
{
int id = GPOINTER_TO_INT(key);
cmddata_t *cmddata = value;
cmdfile_data_t *data = user_data;
if (cmddata->operation == CMD_FLUSH &&
g_str_equal(data->holding_file, cmddata->holding_file)) {
storage_t *storage = lookup_storage(cmddata->dst_storage);
if (storage) {
if (storage_get_autoflush(storage)) {
if (data->ids) {
char *ids = g_strdup_printf("%s,%d;%s", data->ids, id, cmddata->dst_storage);
g_free(data->ids);
data->ids = ids;
} else {
data->ids = g_strdup_printf("%d;%s", id, cmddata->dst_storage);
}
cmddata->working_pid = getppid();
} else {
if (!data->ids) {
data->ids = strdup("");
}
}
} else {
if (!data->ids) {
data->ids = strdup("");
}
}
}
}
static void
est_dump_queue(
char * st,
estlist_t q,
int npr, /* we print first npr disks on queue, plus last two */
FILE * f)
{
GList *dl, *pl;
disk_t *d;
int pos;
char *qname;
if (empty(q)) {
g_fprintf(f, _("%s QUEUE: empty\n"), st);
return;
}
g_fprintf(f, _("%s QUEUE:\n"), st);
for (pos = 0, dl = q.head; dl != NULL; dl = dl->next, pos++) {
d = ((est_t *)dl->data)->disk;
qname = quote_string(d->name);
if (pos < npr) g_fprintf(f, "%3d: %-10s %-4s\n",
pos, d->host->hostname, qname);
amfree(qname);
}
if(pos > npr) {
if (pos > npr+2) g_fprintf(f, " ...\n");
pl = q.tail;
if (pos > npr+1) {
dl = pl->prev;
d = ((est_t *)dl->data)->disk;
g_fprintf(f, "%3d: %-10s %-4s\n", pos-2, d->host->hostname, d->name);
}
dl = pl;
d = ((est_t *)dl->data)->disk;
g_fprintf(f, "%3d: %-10s %-4s\n", pos-1, d->host->hostname, d->name);
}
}
/*
* put est on end of queue
*/
static void
enqueue_est(
estlist_t *list,
est_t * est)
{
list->head = g_am_list_insert_after(list->head, list->tail, est);
if (list->tail) {
list->tail = list->tail->next;
} else {
list->tail = list->head;
}
}
/*
* insert in sorted order
*/
static void
insert_est(
estlist_t *list,
est_t * est,
int (*cmp)(est_t *a, est_t *b))
{
GList *ptr;
ptr = list->head;
while(ptr != NULL) {
if(cmp(est, ptr->data) < 0) break;
ptr = ptr->next;
}
if (ptr) {
list->head = g_list_insert_before(list->head, ptr, est);
if (!list->tail) {
list->tail = list->head;
}
} else {
enqueue_est(list, est);
}
}
static est_t *
find_est_for_dp(
disk_t *dp)
{
GList *elist;
est_t *ep;
for (elist = waitq.head; elist != NULL; elist = elist->next) {
ep = get_est(elist);
if (ep->disk == dp) {
return ep;
}
}
for (elist = pestq.head; elist != NULL; elist = elist->next) {
ep = get_est(elist);
if (ep->disk == dp) {
return ep;
}
}
for (elist = estq.head; elist != NULL; elist = elist->next) {
ep = get_est(elist);
if (ep->disk == dp) {
return ep;
}
}
for (elist = startq.head; elist != NULL; elist = elist->next) {
ep = get_est(elist);
if (ep->disk == dp) {
return ep;
}
}
for (elist = failq.head; elist != NULL; elist = elist->next) {
ep = get_est(elist);
if (ep->disk == dp) {
return ep;
}
}
for (elist = schedq.head; elist != NULL; elist = elist->next) {
ep = get_est(elist);
if (ep->disk == dp) {
return ep;
}
}
for (elist = activeq.head; elist != NULL; elist = elist->next) {
ep = get_est(elist);
if (ep->disk == dp) {
return ep;
}
}
g_critical("find_est_for_dp return NULL");
return NULL;
}
/*
* remove est from front of queue
*/
static est_t *
dequeue_est(
estlist_t *list)
{
est_t *est;
if(list->head == NULL) return NULL;
est = list->head->data;
list->head = g_list_delete_link(list->head, list->head);
if(list->head == NULL) list->tail = NULL;
return est;
}
static void
remove_est(
estlist_t *list,
est_t * est)
{
GList *ltail;
if (list->tail && list->tail->data == est) {
ltail = list->tail;
list->tail = list->tail->prev;
list->head = g_list_delete_link(list->head, ltail);
} else {
list->head = g_list_remove(list->head, est);
}
}