3 * Bacula Director -- migrate.c -- responsible for doing
6 * Kern Sibbald, September MMIV
8 * Basic tasks done here:
9 * Open DB and create records for this job.
10 * Open Message Channel with Storage daemon to tell him a job will be starting.
11 * Open connection with Storage daemon and pass him commands
13 * When the Storage daemon finishes the job, update the DB.
18 Copyright (C) 2004-2006 Kern Sibbald
20 This program is free software; you can redistribute it and/or
21 modify it under the terms of the GNU General Public License
22 version 2 as amended with additional clauses defined in the
23 file LICENSE in the main source directory.
25 This program is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 the file LICENSE for additional details.
36 #include "lib/bregex.h"
41 static const int dbglevel = 100;
43 static char OKbootstrap[] = "3000 OK bootstrap\n";
44 static bool get_job_to_migrate(JCR *jcr);
46 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
47 const char *query2, const char *type);
48 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
50 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type);
51 static void start_migration_job(JCR *jcr);
52 static int get_next_dbid_from_list(char **p, DBId_t *DBId);
55 * Called here before the job is run to do the job
58 bool do_migration_init(JCR *jcr)
60 /* If we find a job or jobs to migrate it is previous_jr.JobId */
61 if (!get_job_to_migrate(jcr)) {
65 if (jcr->previous_jr.JobId == 0) {
66 return true; /* no work */
69 if (!get_or_create_fileset_record(jcr)) {
73 apply_pool_overrides(jcr);
75 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->hdr.name);
76 if (jcr->jr.PoolId == 0) {
80 /* If pool storage specified, use it instead of job storage */
81 copy_wstorage(jcr, jcr->pool->storage, _("Pool resource"));
83 if (jcr->wstorage->size() == 0) {
84 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
88 create_restore_bootstrap_file(jcr);
93 * Do a Migration of a previous job
95 * Returns: false on failure
98 bool do_migration(JCR *jcr)
105 JCR *mig_jcr; /* newly migrated job */
108 * previous_jr refers to the job DB record of the Job that is
109 * going to be migrated.
110 * prev_job refers to the job resource of the Job that is
111 * going to be migrated.
112 * jcr is the jcr for the current "migration" job. It is a
113 * control job that is put in the DB as a migration job, which
114 * means that this job migrated a previous job to a new job.
115 * No Volume or File data is associated with this control
117 * mig_jcr refers to the newly migrated job that is run by
118 * the current jcr. It is a backup job that moves (migrates) the
119 * data written for the previous_jr into the new pool. This
120 * job (mig_jcr) becomes the new backup job that replaces
121 * the original backup job.
123 if (jcr->previous_jr.JobId == 0 || jcr->ExpectedFiles == 0) {
124 set_jcr_job_status(jcr, JS_Terminated);
125 migration_cleanup(jcr, jcr->JobStatus);
126 return true; /* no work */
129 Dmsg4(dbglevel, "Previous: Name=%s JobId=%d Type=%c Level=%c\n",
130 jcr->previous_jr.Name, jcr->previous_jr.JobId,
131 jcr->previous_jr.JobType, jcr->previous_jr.JobLevel);
133 Dmsg4(dbglevel, "Current: Name=%s JobId=%d Type=%c Level=%c\n",
134 jcr->jr.Name, jcr->jr.JobId,
135 jcr->jr.JobType, jcr->jr.JobLevel);
138 job = (JOB *)GetResWithName(R_JOB, jcr->jr.Name);
139 prev_job = (JOB *)GetResWithName(R_JOB, jcr->previous_jr.Name);
141 if (!job || !prev_job) {
145 /* Create a migation jcr */
146 mig_jcr = jcr->mig_jcr = new_jcr(sizeof(JCR), dird_free_jcr);
147 memcpy(&mig_jcr->previous_jr, &jcr->previous_jr, sizeof(mig_jcr->previous_jr));
150 * Turn the mig_jcr into a "real" job that takes on the aspects of
151 * the previous backup job "prev_job".
153 set_jcr_defaults(mig_jcr, prev_job);
154 if (!setup_job(mig_jcr)) {
158 /* Now reset the job record from the previous job */
159 memcpy(&mig_jcr->jr, &jcr->previous_jr, sizeof(mig_jcr->jr));
160 /* Update the jr to reflect the new values of PoolId, FileSetId, and JobId. */
161 mig_jcr->jr.PoolId = jcr->jr.PoolId;
162 mig_jcr->jr.FileSetId = jcr->jr.FileSetId;
163 mig_jcr->jr.JobId = mig_jcr->JobId;
165 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
166 mig_jcr->jr.Name, mig_jcr->jr.JobId,
167 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
170 * Get the PoolId used with the original job. Then
171 * find the pool name from the database record.
173 memset(&pr, 0, sizeof(pr));
174 pr.PoolId = mig_jcr->previous_jr.PoolId;
175 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
176 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
177 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
180 /* Get the pool resource corresponding to the original job */
181 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
183 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
187 /* If pool storage specified, use it for restore */
188 copy_rstorage(mig_jcr, pool->storage, _("Pool resource"));
189 copy_rstorage(jcr, pool->storage, _("Pool resource"));
192 * If the original backup pool has a NextPool, make sure a
193 * record exists in the database. Note, in this case, we
194 * will be migrating from pool to pool->NextPool.
196 if (pool->NextPool) {
197 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->hdr.name);
198 if (jcr->jr.PoolId == 0) {
202 * put the "NextPool" resource pointer in our jcr so that we
203 * can pull the Storage reference from it.
205 mig_jcr->pool = jcr->pool = pool->NextPool;
206 mig_jcr->jr.PoolId = jcr->jr.PoolId;
207 pm_strcpy(jcr->pool_source, _("NextPool in Pool resource"));
209 Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
214 if (!jcr->pool->storage) {
215 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
216 jcr->pool->hdr.name);
220 /* If pool storage specified, use it instead of job storage for backup */
221 copy_wstorage(jcr, jcr->pool->storage, _("Next pool resource"));
223 /* Print Job Start message */
224 Jmsg(jcr, M_INFO, 0, _("Start Migration JobId %s, Job=%s\n"),
225 edit_uint64(jcr->JobId, ed1), jcr->Job);
227 set_jcr_job_status(jcr, JS_Running);
228 set_jcr_job_status(mig_jcr, JS_Running);
229 Dmsg2(dbglevel, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
231 /* Update job start record for this migration control job */
232 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
233 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
237 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
238 mig_jcr->jr.Name, mig_jcr->jr.JobId,
239 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
241 /* Update job start record for the real migration backup job */
242 if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
243 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
249 * Open a message channel connection with the Storage
250 * daemon. This is to let him know that our client
251 * will be contacting him for a backup session.
254 Dmsg0(110, "Open connection with storage daemon\n");
255 set_jcr_job_status(jcr, JS_WaitSD);
256 set_jcr_job_status(mig_jcr, JS_WaitSD);
258 * Start conversation with Storage daemon
260 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
263 sd = jcr->store_bsock;
265 * Now start a job with the Storage daemon
267 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
268 ((STORE *)jcr->rstorage->first())->name(),
269 ((STORE *)jcr->wstorage->first())->name());
270 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
271 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
272 ((STORE *)jcr->rstorage->first())->name());
274 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
277 Dmsg0(150, "Storage daemon connection OK\n");
279 if (!send_bootstrap_file(jcr, sd) ||
280 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
284 if (!bnet_fsend(sd, "run")) {
289 * Now start a Storage daemon message thread
291 if (!start_storage_daemon_message_thread(jcr)) {
296 set_jcr_job_status(jcr, JS_Running);
297 set_jcr_job_status(mig_jcr, JS_Running);
299 /* Pickup Job termination data */
300 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
301 wait_for_storage_daemon_termination(jcr);
303 set_jcr_job_status(jcr, jcr->SDJobStatus);
304 if (jcr->JobStatus != JS_Terminated) {
307 migration_cleanup(jcr, jcr->JobStatus);
309 UAContext *ua = new_ua_context(jcr);
310 purge_files_from_job(ua, jcr->previous_jr.JobId);
322 * Callback handler make list of DB Ids
324 static int dbid_handler(void *ctx, int num_fields, char **row)
326 idpkt *ids = (idpkt *)ctx;
328 if (ids->count == 0) {
331 pm_strcat(ids->list, ",");
333 pm_strcat(ids->list, row[0]);
335 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
345 static int item_compare(void *item1, void *item2)
347 uitem *i1 = (uitem *)item1;
348 uitem *i2 = (uitem *)item2;
349 return strcmp(i1->item, i2->item);
352 static int unique_name_handler(void *ctx, int num_fields, char **row)
354 dlist *list = (dlist *)ctx;
356 uitem *new_item = (uitem *)malloc(sizeof(uitem));
359 memset(new_item, 0, sizeof(uitem));
360 new_item->item = bstrdup(row[0]);
361 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
362 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
363 if (item != new_item) { /* already in list */
364 free(new_item->item);
365 free((char *)new_item);
371 #ifdef xxx /* in development */
372 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
374 dlist *list = (dlist *)ctx;
376 uitem *new_item = (uitem *)malloc(sizeof(uitem));
379 memset(new_item, 0, sizeof(uitem));
380 new_item->item = bstrdup(row[0]);
381 Dmsg1(dbglevel, "Item=%s\n", row[0]);
382 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
383 if (item != new_item) { /* already in list */
384 free(new_item->item);
385 free((char *)new_item);
394 /* Get Job names in Pool */
395 const char *sql_job =
396 "SELECT DISTINCT Job.Name from Job,Pool"
397 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
399 /* Get JobIds from regex'ed Job names */
400 const char *sql_jobids_from_job =
401 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
402 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
403 " ORDER by Job.StartTime";
405 /* Get Client names in Pool */
406 const char *sql_client =
407 "SELECT DISTINCT Client.Name from Client,Pool,Job"
408 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
409 " Job.PoolId=Pool.PoolId";
411 /* Get JobIds from regex'ed Client names */
412 const char *sql_jobids_from_client =
413 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
414 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
415 " AND Job.ClientId=Client.ClientId "
416 " ORDER by Job.StartTime";
418 /* Get Volume names in Pool */
419 const char *sql_vol =
420 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
421 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
422 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
424 /* Get JobIds from regex'ed Volume names */
425 const char *sql_jobids_from_vol =
426 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
427 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
428 " AND JobMedia.JobId=Job.JobId"
429 " ORDER by Job.StartTime";
432 const char *sql_smallest_vol =
433 "SELECT MediaId FROM Media,Pool WHERE"
434 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
435 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
436 " ORDER BY VolBytes ASC LIMIT 1";
438 const char *sql_oldest_vol =
439 "SELECT MediaId FROM Media,Pool WHERE"
440 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
441 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
442 " ORDER BY LastWritten ASC LIMIT 1";
444 /* Get JobIds when we have selected MediaId */
445 const char *sql_jobids_from_mediaid =
446 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
447 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
448 " ORDER by Job.StartTime";
450 /* Get tne number of bytes in the pool */
451 const char *sql_pool_bytes =
452 "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
453 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
454 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
456 /* Get tne number of bytes in the Jobs */
457 const char *sql_job_bytes =
458 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
461 /* Get Media Ids in Pool */
462 const char *sql_mediaids =
463 "SELECT MediaId FROM Media,Pool WHERE"
464 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
465 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
467 /* Get JobIds in Pool longer than specified time */
468 const char *sql_pool_time =
469 "SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
470 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
471 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
472 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
473 " AND Job.RealEndTime<='%s'";
476 * const char *sql_ujobid =
477 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
478 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
479 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
486 * This is the central piece of code that finds a job or jobs
487 * actually JobIds to migrate. It first looks to see if one
488 * has been "manually" specified in jcr->MigrateJobId, and if
489 * so, it returns that JobId to be run. Otherwise, it
490 * examines the Selection Type to see what kind of migration
491 * we are doing (Volume, Job, Client, ...) and applies any
492 * Selection Pattern if appropriate to obtain a list of JobIds.
493 * Finally, it will loop over all the JobIds found, except the last
494 * one starting a new job with MigrationJobId set to that JobId, and
495 * finally, it returns the last JobId to the caller.
497 * Returns: false on error
498 * true if OK and jcr->previous_jr filled in
500 static bool get_job_to_migrate(JCR *jcr)
503 POOL_MEM query(PM_MESSAGE);
508 idpkt ids, mid, jids;
514 char dt[MAX_TIME_LENGTH];
516 ids.list = get_pool_memory(PM_MESSAGE);
519 mid.list = get_pool_memory(PM_MESSAGE);
522 jids.list = get_pool_memory(PM_MESSAGE);
528 * If MigrateJobId is set, then we migrate only that Job,
529 * otherwise, we go through the full selection of jobs to
532 if (jcr->MigrateJobId != 0) {
533 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
534 edit_uint64(jcr->MigrateJobId, ids.list);
537 switch (jcr->job->selection_type) {
539 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
544 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
549 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
554 if (!jcr->job->selection_pattern) {
555 Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
558 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
559 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
560 dbid_handler, (void *)&ids)) {
561 Jmsg(jcr, M_FATAL, 0,
562 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
566 case MT_SMALLEST_VOL:
567 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
572 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
577 case MT_POOL_OCCUPANCY:
579 /* Find count of bytes in pool */
580 Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
581 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
582 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
585 if (ctx.count == 0) {
586 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
589 pool_bytes = ctx.value;
590 Dmsg2(dbglevel, "highbytes=%d pool=%d\n", (int)jcr->pool->MigrationHighBytes,
592 if (pool_bytes < (int64_t)jcr->pool->MigrationHighBytes) {
593 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
596 Dmsg0(dbglevel, "We should do Occupation migration.\n");
599 /* Find a list of MediaIds that could be migrated */
600 Mmsg(query, sql_mediaids, jcr->pool->hdr.name);
601 // Dmsg1(dbglevel, "query=%s\n", query.c_str());
602 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)&ids)) {
603 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
606 if (ids.count == 0) {
607 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
610 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
613 * Now loop over MediaIds getting more JobIds to migrate until
614 * we reduce the pool occupancy below the low water mark.
617 for (int i=0; i < (int)ids.count; i++) {
618 stat = get_next_dbid_from_list(&p, &MediaId);
619 Dmsg2(dbglevel, "get_next_dbid stat=%d MediaId=%u\n", stat, MediaId);
621 Jmsg(jcr, M_FATAL, 0, _("Invalid MediaId found.\n"));
623 } else if (stat == 0) {
627 Mmsg(mid.list, "%s", edit_int64(MediaId, ed1));
628 ok = find_jobids_from_mediaid_list(jcr, &mid, "Volumes");
633 pm_strcat(jids.list, ",");
635 pm_strcat(jids.list, mid.list);
636 jids.count += mid.count;
638 /* Now get the count of bytes added */
640 /* Find count of bytes from Jobs */
641 Mmsg(query, sql_job_bytes, mid.list);
642 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
643 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
646 pool_bytes -= ctx.value;
647 Dmsg1(dbglevel, "Job bytes=%d\n", (int)ctx.value);
648 Dmsg2(dbglevel, "lowbytes=%d pool=%d\n", (int)jcr->pool->MigrationLowBytes,
650 if (pool_bytes <= (int64_t)jcr->pool->MigrationLowBytes) {
651 Dmsg0(dbglevel, "We should be done.\n");
656 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", jids.count, jids.list);
661 ttime = time(NULL) - (time_t)jcr->pool->MigrationTime;
662 (void)localtime_r(&ttime, &tm);
663 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
666 Mmsg(query, sql_pool_time, jcr->pool->hdr.name, dt);
667 // Dmsg1(000, "query=%s\n", query.c_str());
668 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)&ids)) {
669 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
672 if (ids.count == 0) {
673 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
676 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
680 Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
686 * Loop over all jobids except the last one, sending
687 * them to start_migration_job(), which will start a job
688 * for each of them. For the last JobId, we handle it below.
691 Jmsg(jcr, M_INFO, 0, _("The following %u JobIds will be migrated: %s\n"),
692 ids.count, ids.list);
693 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
694 for (int i=1; i < (int)ids.count; i++) {
696 stat = get_next_jobid_from_list(&p, &JobId);
697 Dmsg3(dbglevel, "get_jobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
698 jcr->MigrateJobId = JobId;
699 start_migration_job(jcr);
700 Dmsg0(dbglevel, "Back from start_migration_job\n");
702 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
704 } else if (stat == 0) {
705 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
710 /* Now get the last JobId and handle it in the current job */
712 stat = get_next_jobid_from_list(&p, &JobId);
713 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, JobId);
715 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
717 } else if (stat == 0) {
718 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
722 jcr->previous_jr.JobId = JobId;
723 Dmsg1(100, "Previous jobid=%d\n", jcr->previous_jr.JobId);
725 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
726 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
727 edit_int64(jcr->previous_jr.JobId, ed1),
728 db_strerror(jcr->db));
731 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
732 jcr->previous_jr.JobId, jcr->previous_jr.Job);
735 free_pool_memory(ids.list);
736 free_pool_memory(mid.list);
737 free_pool_memory(jids.list);
741 free_pool_memory(ids.list);
742 free_pool_memory(mid.list);
743 free_pool_memory(jids.list);
747 static void start_migration_job(JCR *jcr)
749 UAContext *ua = new_ua_context(jcr);
752 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
753 edit_uint64(jcr->MigrateJobId, ed1));
754 Dmsg1(dbglevel, "=============== Migration cmd=%s\n", ua->cmd);
755 parse_ua_args(ua); /* parse command */
756 int stat = run_cmd(ua, ua->cmd);
758 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
760 Jmsg(jcr, M_INFO, 0, _("Migration JobId %d started.\n"), stat);
765 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
769 POOL_MEM query(PM_MESSAGE);
772 /* Basic query for MediaId */
773 Mmsg(query, query1, jcr->pool->hdr.name);
774 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
775 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
778 if (ids->count == 0) {
779 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
781 if (ids->count != 1) {
782 Jmsg(jcr, M_FATAL, 0, _("SQL logic error. Count should be 1 but is %d\n"),
786 Dmsg1(dbglevel, "Smallest Vol Jobids=%s\n", ids->list);
788 ok = find_jobids_from_mediaid_list(jcr, ids, type);
794 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
797 POOL_MEM query(PM_MESSAGE);
799 Mmsg(query, sql_jobids_from_mediaid, ids->list);
801 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
802 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
805 if (ids->count == 0) {
806 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
813 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
814 const char *query2, const char *type)
818 uitem *last_item = NULL;
823 POOL_MEM query(PM_MESSAGE);
825 item_chain = New(dlist(item, &item->link));
826 if (!jcr->job->selection_pattern) {
827 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
831 Dmsg1(dbglevel, "regex=%s\n", jcr->job->selection_pattern);
832 /* Compile regex expression */
833 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
835 regerror(rc, &preg, prbuf, sizeof(prbuf));
836 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
837 jcr->job->selection_pattern, prbuf);
840 /* Basic query for names */
841 Mmsg(query, query1, jcr->pool->hdr.name);
842 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
843 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
844 (void *)item_chain)) {
845 Jmsg(jcr, M_FATAL, 0,
846 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
849 /* Now apply the regex to the names and remove any item not matched */
850 foreach_dlist(item, item_chain) {
851 const int nmatch = 30;
852 regmatch_t pmatch[nmatch];
854 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
855 free(last_item->item);
856 item_chain->remove(last_item);
858 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
859 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
861 last_item = NULL; /* keep this one */
867 free(last_item->item);
868 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
869 item_chain->remove(last_item);
873 * At this point, we have a list of items in item_chain
874 * that have been matched by the regex, so now we need
875 * to look up their jobids.
878 foreach_dlist(item, item_chain) {
879 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
880 Mmsg(query, query2, item->item, jcr->pool->hdr.name);
881 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
882 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
883 Jmsg(jcr, M_FATAL, 0,
884 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
888 if (ids->count == 0) {
889 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
893 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
895 Dmsg0(dbglevel, "After delete item_chain\n");
901 * Release resources allocated during backup.
903 void migration_cleanup(JCR *jcr, int TermCode)
905 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
906 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
907 char ec6[50], ec7[50], ec8[50];
908 char term_code[100], sd_term_msg[100];
909 const char *term_msg;
914 JCR *mig_jcr = jcr->mig_jcr;
915 POOL_MEM query(PM_MESSAGE);
917 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
918 dequeue_messages(jcr); /* display any queued messages */
919 memset(&mr, 0, sizeof(mr));
920 set_jcr_job_status(jcr, TermCode);
921 update_job_end_record(jcr); /* update database */
924 * Check if we actually did something.
925 * mig_jcr is jcr of the newly migrated job.
928 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
929 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
930 mig_jcr->VolSessionId = jcr->VolSessionId;
931 mig_jcr->VolSessionTime = jcr->VolSessionTime;
932 mig_jcr->jr.RealEndTime = 0;
933 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
935 set_jcr_job_status(mig_jcr, TermCode);
938 update_job_end_record(mig_jcr);
940 /* Update final items to set them to the previous job's values */
941 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
942 "JobTDate=%s WHERE JobId=%s",
943 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
944 edit_uint64(jcr->previous_jr.JobTDate, ec1),
945 edit_uint64(mig_jcr->jr.JobId, ec2));
946 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
948 /* Now marke the previous job as migrated */
949 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
950 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
951 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
953 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
954 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
955 db_strerror(jcr->db));
956 set_jcr_job_status(jcr, JS_ErrorTerminated);
959 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
960 if (!db_get_media_record(jcr, jcr->db, &mr)) {
961 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
962 mr.VolumeName, db_strerror(jcr->db));
963 set_jcr_job_status(jcr, JS_ErrorTerminated);
966 update_bootstrap_file(mig_jcr);
968 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
970 * Note, if the job has erred, most likely it did not write any
971 * tape, so suppress this "error" message since in that case
972 * it is normal. Or look at it the other way, only for a
973 * normal exit should we complain about this error.
975 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
976 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
978 mig_jcr->VolumeName[0] = 0; /* none */
982 msg_type = M_INFO; /* by default INFO message */
983 switch (jcr->JobStatus) {
985 if (jcr->Errors || jcr->SDErrors) {
986 term_msg = _("%s OK -- with warnings");
988 term_msg = _("%s OK");
992 case JS_ErrorTerminated:
993 term_msg = _("*** %s Error ***");
994 msg_type = M_ERROR; /* Generate error message */
995 if (jcr->store_bsock) {
996 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
997 if (jcr->SD_msg_chan) {
998 pthread_cancel(jcr->SD_msg_chan);
1003 term_msg = _("%s Canceled");
1004 if (jcr->store_bsock) {
1005 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1006 if (jcr->SD_msg_chan) {
1007 pthread_cancel(jcr->SD_msg_chan);
1012 term_msg = _("Inappropriate %s term code");
1015 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
1016 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1017 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1018 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1022 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1026 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1028 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
1029 " Prev Backup JobId: %s\n"
1030 " New Backup JobId: %s\n"
1031 " Migration JobId: %s\n"
1032 " Migration Job: %s\n"
1033 " Backup Level: %s%s\n"
1035 " FileSet: \"%s\" %s\n"
1036 " Pool: \"%s\" (From %s)\n"
1037 " Storage: \"%s\" (From %s)\n"
1040 " Elapsed time: %s\n"
1042 " SD Files Written: %s\n"
1043 " SD Bytes Written: %s (%sB)\n"
1044 " Rate: %.1f KB/s\n"
1045 " Volume name(s): %s\n"
1046 " Volume Session Id: %d\n"
1047 " Volume Session Time: %d\n"
1048 " Last Volume Bytes: %s (%sB)\n"
1050 " SD termination status: %s\n"
1051 " Termination: %s\n\n"),
1055 mig_jcr ? edit_uint64(jcr->previous_jr.JobId, ec6) : "0",
1056 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1057 edit_uint64(jcr->jr.JobId, ec8),
1059 level_to_str(jcr->JobLevel), jcr->since,
1060 jcr->client->name(),
1061 jcr->fileset->name(), jcr->FSCreateTime,
1062 jcr->pool->name(), jcr->pool_source,
1063 jcr->wstore->name(), jcr->storage_source,
1066 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1068 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1069 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1070 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1072 mig_jcr ? mig_jcr->VolumeName : "",
1074 jcr->VolSessionTime,
1075 edit_uint64_with_commas(mr.VolBytes, ec4),
1076 edit_uint64_with_suffix(mr.VolBytes, ec5),
1081 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1083 free_jcr(jcr->mig_jcr);
1084 jcr->mig_jcr = NULL;
1086 Dmsg0(100, "Leave migrate_cleanup()\n");
1090 * Return next DBId from comma separated list
1093 * 1 if next DBId returned
1094 * 0 if no more DBIds are in list
1095 * -1 there is an error
1097 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1103 for (int i=0; i<(int)sizeof(id); i++) {
1106 } else if (*q == ',') {
1115 } else if (!is_a_number(id)) {
1116 return -1; /* error */
1119 *DBId = str_to_int64(id);