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"));
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"));
210 /* If pool storage specified, use it instead of job storage for backup */
211 copy_wstorage(jcr, jcr->pool->storage, _("Pool resource"));
213 /* Print Job Start message */
214 Jmsg(jcr, M_INFO, 0, _("Start Migration JobId %s, Job=%s\n"),
215 edit_uint64(jcr->JobId, ed1), jcr->Job);
217 set_jcr_job_status(jcr, JS_Running);
218 set_jcr_job_status(mig_jcr, JS_Running);
219 Dmsg2(dbglevel, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
221 /* Update job start record for this migration control job */
222 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
223 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
227 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
228 mig_jcr->jr.Name, mig_jcr->jr.JobId,
229 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
231 /* Update job start record for the real migration backup job */
232 if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
233 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
239 * Open a message channel connection with the Storage
240 * daemon. This is to let him know that our client
241 * will be contacting him for a backup session.
244 Dmsg0(110, "Open connection with storage daemon\n");
245 set_jcr_job_status(jcr, JS_WaitSD);
246 set_jcr_job_status(mig_jcr, JS_WaitSD);
248 * Start conversation with Storage daemon
250 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
253 sd = jcr->store_bsock;
255 * Now start a job with the Storage daemon
257 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
258 ((STORE *)jcr->rstorage->first())->name(),
259 ((STORE *)jcr->wstorage->first())->name());
260 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
263 Dmsg0(150, "Storage daemon connection OK\n");
265 if (!send_bootstrap_file(jcr, sd) ||
266 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
270 if (!bnet_fsend(sd, "run")) {
275 * Now start a Storage daemon message thread
277 if (!start_storage_daemon_message_thread(jcr)) {
282 set_jcr_job_status(jcr, JS_Running);
283 set_jcr_job_status(mig_jcr, JS_Running);
285 /* Pickup Job termination data */
286 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
287 wait_for_storage_daemon_termination(jcr);
289 set_jcr_job_status(jcr, jcr->SDJobStatus);
290 if (jcr->JobStatus != JS_Terminated) {
293 migration_cleanup(jcr, jcr->JobStatus);
295 UAContext *ua = new_ua_context(jcr);
296 purge_files_from_job(ua, jcr->previous_jr.JobId);
308 * Callback handler make list of DB Ids
310 static int dbid_handler(void *ctx, int num_fields, char **row)
312 idpkt *ids = (idpkt *)ctx;
314 Dmsg3(dbglevel, "count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
315 if (ids->count == 0) {
318 pm_strcat(ids->list, ",");
320 pm_strcat(ids->list, row[0]);
331 static int item_compare(void *item1, void *item2)
333 uitem *i1 = (uitem *)item1;
334 uitem *i2 = (uitem *)item2;
335 return strcmp(i1->item, i2->item);
338 static int unique_name_handler(void *ctx, int num_fields, char **row)
340 dlist *list = (dlist *)ctx;
342 uitem *new_item = (uitem *)malloc(sizeof(uitem));
345 memset(new_item, 0, sizeof(uitem));
346 new_item->item = bstrdup(row[0]);
347 Dmsg1(dbglevel, "Item=%s\n", row[0]);
348 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
349 if (item != new_item) { /* already in list */
350 free(new_item->item);
351 free((char *)new_item);
357 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
359 dlist *list = (dlist *)ctx;
361 uitem *new_item = (uitem *)malloc(sizeof(uitem));
364 memset(new_item, 0, sizeof(uitem));
365 new_item->item = bstrdup(row[0]);
366 Dmsg1(dbglevel, "Item=%s\n", row[0]);
367 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
368 if (item != new_item) { /* already in list */
369 free(new_item->item);
370 free((char *)new_item);
379 /* Get Job names in Pool */
380 const char *sql_job =
381 "SELECT DISTINCT Job.Name from Job,Pool"
382 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
384 /* Get JobIds from regex'ed Job names */
385 const char *sql_jobids_from_job =
386 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
387 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
388 " ORDER by Job.StartTime";
390 /* Get Client names in Pool */
391 const char *sql_client =
392 "SELECT DISTINCT Client.Name from Client,Pool,Job"
393 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
394 " Job.PoolId=Pool.PoolId";
396 /* Get JobIds from regex'ed Client names */
397 const char *sql_jobids_from_client =
398 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
399 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
400 " AND Job.ClientId=Client.ClientId "
401 " ORDER by Job.StartTime";
403 /* Get Volume names in Pool */
404 const char *sql_vol =
405 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
406 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
407 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
409 /* Get JobIds from regex'ed Volume names */
410 const char *sql_jobids_from_vol =
411 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
412 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
413 " AND JobMedia.JobId=Job.JobId"
414 " ORDER by Job.StartTime";
417 const char *sql_smallest_vol =
418 "SELECT MediaId FROM Media,Pool WHERE"
419 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
420 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
421 " ORDER BY VolBytes ASC LIMIT 1";
423 const char *sql_oldest_vol =
424 "SELECT MediaId FROM Media,Pool WHERE"
425 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
426 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
427 " ORDER BY LastWritten ASC LIMIT 1";
429 /* Get JobIds when we have selected MediaId */
430 const char *sql_jobids_from_mediaid =
431 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
432 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
433 " ORDER by Job.StartTime";
435 /* Get tne number of bytes in the pool */
436 const char *sql_pool_bytes =
437 "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
438 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
439 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
441 /* Get tne number of bytes in the Jobs */
442 const char *sql_job_bytes =
443 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
446 /* Get Media Ids in Pool */
447 const char *sql_mediaids =
448 "SELECT MediaId FROM Media,Pool WHERE"
449 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
450 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
452 /* Get JobIds in Pool longer than specified time */
453 const char *sql_pool_time =
454 "SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
455 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
456 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
457 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
458 " AND Job.RealEndTime<='%s'";
461 * const char *sql_ujobid =
462 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
463 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
464 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
471 * This is the central piece of code that finds a job or jobs
472 * actually JobIds to migrate. It first looks to see if one
473 * has been "manually" specified in jcr->MigrateJobId, and if
474 * so, it returns that JobId to be run. Otherwise, it
475 * examines the Selection Type to see what kind of migration
476 * we are doing (Volume, Job, Client, ...) and applies any
477 * Selection Pattern if appropriate to obtain a list of JobIds.
478 * Finally, it will loop over all the JobIds found, except the last
479 * one starting a new job with MigrationJobId set to that JobId, and
480 * finally, it returns the last JobId to the caller.
482 * Returns: false on error
483 * true if OK and jcr->previous_jr filled in
485 static bool get_job_to_migrate(JCR *jcr)
488 POOL_MEM query(PM_MESSAGE);
493 idpkt ids, mid, jids;
499 char dt[MAX_TIME_LENGTH];
501 ids.list = get_pool_memory(PM_MESSAGE);
504 mid.list = get_pool_memory(PM_MESSAGE);
507 jids.list = get_pool_memory(PM_MESSAGE);
513 * If MigrateJobId is set, then we migrate only that Job,
514 * otherwise, we go through the full selection of jobs to
517 if (jcr->MigrateJobId != 0) {
518 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
519 edit_uint64(jcr->MigrateJobId, ids.list);
522 switch (jcr->job->selection_type) {
524 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
529 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
534 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
539 if (!jcr->job->selection_pattern) {
540 Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
543 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
544 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
545 dbid_handler, (void *)&ids)) {
546 Jmsg(jcr, M_FATAL, 0,
547 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
551 case MT_SMALLEST_VOL:
552 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
557 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
562 case MT_POOL_OCCUPANCY:
564 /* Find count of bytes in pool */
565 Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
566 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
567 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
570 if (ctx.count == 0) {
571 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
574 pool_bytes = ctx.value;
575 Dmsg2(dbglevel, "highbytes=%d pool=%d\n", (int)jcr->pool->MigrationHighBytes,
577 if (pool_bytes < (int64_t)jcr->pool->MigrationHighBytes) {
578 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
581 Dmsg0(dbglevel, "We should do Occupation migration.\n");
584 /* Find a list of MediaIds that could be migrated */
585 Mmsg(query, sql_mediaids, jcr->pool->hdr.name);
586 // Dmsg1(dbglevel, "query=%s\n", query.c_str());
587 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)&ids)) {
588 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
591 if (ids.count == 0) {
592 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
595 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
598 * Now loop over MediaIds getting more JobIds to migrate until
599 * we reduce the pool occupancy below the low water mark.
602 for (int i=0; i < (int)ids.count; i++) {
603 stat = get_next_dbid_from_list(&p, &MediaId);
604 Dmsg2(dbglevel, "get_next_dbid stat=%d MediaId=%u\n", stat, MediaId);
606 Jmsg(jcr, M_FATAL, 0, _("Invalid MediaId found.\n"));
608 } else if (stat == 0) {
612 Mmsg(mid.list, "%s", edit_int64(MediaId, ed1));
613 ok = find_jobids_from_mediaid_list(jcr, &mid, "Volumes");
618 pm_strcat(jids.list, ",");
620 pm_strcat(jids.list, mid.list);
621 jids.count += mid.count;
623 /* Now get the count of bytes added */
625 /* Find count of bytes from Jobs */
626 Mmsg(query, sql_job_bytes, mid.list);
627 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
628 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
631 pool_bytes -= ctx.value;
632 Dmsg1(dbglevel, "Job bytes=%d\n", (int)ctx.value);
633 Dmsg2(dbglevel, "lowbytes=%d pool=%d\n", (int)jcr->pool->MigrationLowBytes,
635 if (pool_bytes <= (int64_t)jcr->pool->MigrationLowBytes) {
636 Dmsg0(dbglevel, "We should be done.\n");
641 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", jids.count, jids.list);
646 ttime = time(NULL) - (time_t)jcr->pool->MigrationTime;
647 (void)localtime_r(&ttime, &tm);
648 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
651 Mmsg(query, sql_pool_time, jcr->pool->hdr.name, dt);
652 // Dmsg1(000, "query=%s\n", query.c_str());
653 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)&ids)) {
654 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
657 if (ids.count == 0) {
658 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
661 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
665 Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
671 * Loop over all jobids except the last one, sending
672 * them to start_migration_job(), which will start a job
673 * for each of them. For the last JobId, we handle it below.
676 Jmsg(jcr, M_INFO, 0, _("The following %u JobIds will be migrated: %s\n"),
677 ids.count, ids.list);
678 for (int i=1; i < (int)ids.count; i++) {
680 stat = get_next_jobid_from_list(&p, &JobId);
681 Dmsg2(dbglevel, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
682 jcr->MigrateJobId = JobId;
683 start_migration_job(jcr);
685 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
687 } else if (stat == 0) {
688 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
693 /* Now get the last JobId and handle it in the current job */
695 stat = get_next_jobid_from_list(&p, &JobId);
696 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, JobId);
698 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
700 } else if (stat == 0) {
701 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
705 jcr->previous_jr.JobId = JobId;
706 Dmsg1(100, "Previous jobid=%d\n", jcr->previous_jr.JobId);
708 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
709 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
710 edit_int64(jcr->previous_jr.JobId, ed1),
711 db_strerror(jcr->db));
714 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
715 jcr->previous_jr.JobId, jcr->previous_jr.Job);
718 free_pool_memory(ids.list);
719 free_pool_memory(mid.list);
720 free_pool_memory(jids.list);
724 free_pool_memory(ids.list);
725 free_pool_memory(mid.list);
726 free_pool_memory(jids.list);
730 static void start_migration_job(JCR *jcr)
732 UAContext *ua = new_ua_context(jcr);
735 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
736 edit_uint64(jcr->MigrateJobId, ed1));
737 Dmsg1(dbglevel, "=============== Migration cmd=%s\n", ua->cmd);
738 parse_ua_args(ua); /* parse command */
739 int stat = run_cmd(ua, ua->cmd);
741 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
743 Jmsg(jcr, M_INFO, 0, _("Migration JobId %d started.\n"), stat);
748 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
752 POOL_MEM query(PM_MESSAGE);
755 /* Basic query for MediaId */
756 Mmsg(query, query1, jcr->pool->hdr.name);
757 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
758 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
761 if (ids->count == 0) {
762 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
764 if (ids->count != 1) {
765 Jmsg(jcr, M_FATAL, 0, _("SQL logic error. Count should be 1 but is %d\n"),
769 Dmsg1(dbglevel, "Smallest Vol Jobids=%s\n", ids->list);
771 ok = find_jobids_from_mediaid_list(jcr, ids, type);
777 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
780 POOL_MEM query(PM_MESSAGE);
782 Mmsg(query, sql_jobids_from_mediaid, ids->list);
784 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
785 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
788 if (ids->count == 0) {
789 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
796 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
797 const char *query2, const char *type)
801 uitem *last_item = NULL;
806 POOL_MEM query(PM_MESSAGE);
808 item_chain = New(dlist(item, &item->link));
809 if (!jcr->job->selection_pattern) {
810 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
814 Dmsg1(dbglevel, "regex=%s\n", jcr->job->selection_pattern);
815 /* Compile regex expression */
816 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
818 regerror(rc, &preg, prbuf, sizeof(prbuf));
819 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
820 jcr->job->selection_pattern, prbuf);
823 /* Basic query for names */
824 Mmsg(query, query1, jcr->pool->hdr.name);
825 Dmsg1(dbglevel, "query1=%s\n", query.c_str());
826 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
827 (void *)item_chain)) {
828 Jmsg(jcr, M_FATAL, 0,
829 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
832 /* Now apply the regex to the names and remove any item not matched */
833 foreach_dlist(item, item_chain) {
834 const int nmatch = 30;
835 regmatch_t pmatch[nmatch];
837 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
838 free(last_item->item);
839 item_chain->remove(last_item);
841 Dmsg1(dbglevel, "Item=%s\n", item->item);
842 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
844 last_item = NULL; /* keep this one */
850 free(last_item->item);
851 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
852 item_chain->remove(last_item);
856 * At this point, we have a list of items in item_chain
857 * that have been matched by the regex, so now we need
858 * to look up their jobids.
861 foreach_dlist(item, item_chain) {
862 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
863 Mmsg(query, query2, item->item, jcr->pool->hdr.name);
864 Dmsg1(dbglevel, "query2=%s\n", query.c_str());
865 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
866 Jmsg(jcr, M_FATAL, 0,
867 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
871 if (ids->count == 0) {
872 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
876 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
878 Dmsg0(dbglevel, "After delete item_chain\n");
884 * Release resources allocated during backup.
886 void migration_cleanup(JCR *jcr, int TermCode)
888 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
889 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
890 char ec6[50], ec7[50], ec8[50];
891 char term_code[100], sd_term_msg[100];
892 const char *term_msg;
897 JCR *mig_jcr = jcr->mig_jcr;
898 POOL_MEM query(PM_MESSAGE);
900 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
901 dequeue_messages(jcr); /* display any queued messages */
902 memset(&mr, 0, sizeof(mr));
903 set_jcr_job_status(jcr, TermCode);
904 update_job_end_record(jcr); /* update database */
907 * Check if we actually did something.
908 * mig_jcr is jcr of the newly migrated job.
911 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
912 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
913 mig_jcr->VolSessionId = jcr->VolSessionId;
914 mig_jcr->VolSessionTime = jcr->VolSessionTime;
915 mig_jcr->jr.RealEndTime = 0;
916 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
918 set_jcr_job_status(mig_jcr, TermCode);
921 update_job_end_record(mig_jcr);
923 /* Update final items to set them to the previous job's values */
924 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
925 "JobTDate=%s WHERE JobId=%s",
926 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
927 edit_uint64(jcr->previous_jr.JobTDate, ec1),
928 edit_uint64(mig_jcr->jr.JobId, ec2));
929 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
931 /* Now marke the previous job as migrated */
932 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
933 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
934 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
936 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
937 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
938 db_strerror(jcr->db));
939 set_jcr_job_status(jcr, JS_ErrorTerminated);
942 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
943 if (!db_get_media_record(jcr, jcr->db, &mr)) {
944 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
945 mr.VolumeName, db_strerror(jcr->db));
946 set_jcr_job_status(jcr, JS_ErrorTerminated);
949 update_bootstrap_file(mig_jcr);
951 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
953 * Note, if the job has erred, most likely it did not write any
954 * tape, so suppress this "error" message since in that case
955 * it is normal. Or look at it the other way, only for a
956 * normal exit should we complain about this error.
958 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
959 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
961 mig_jcr->VolumeName[0] = 0; /* none */
965 msg_type = M_INFO; /* by default INFO message */
966 switch (jcr->JobStatus) {
968 if (jcr->Errors || jcr->SDErrors) {
969 term_msg = _("%s OK -- with warnings");
971 term_msg = _("%s OK");
975 case JS_ErrorTerminated:
976 term_msg = _("*** %s Error ***");
977 msg_type = M_ERROR; /* Generate error message */
978 if (jcr->store_bsock) {
979 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
980 if (jcr->SD_msg_chan) {
981 pthread_cancel(jcr->SD_msg_chan);
986 term_msg = _("%s Canceled");
987 if (jcr->store_bsock) {
988 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
989 if (jcr->SD_msg_chan) {
990 pthread_cancel(jcr->SD_msg_chan);
995 term_msg = _("Inappropriate %s term code");
998 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
999 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1000 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1001 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1005 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1009 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1011 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
1012 " Prev Backup JobId: %s\n"
1013 " New Backup JobId: %s\n"
1014 " Migration JobId: %s\n"
1015 " Migration Job: %s\n"
1016 " Backup Level: %s%s\n"
1018 " FileSet: \"%s\" %s\n"
1019 " Pool: \"%s\" (From %s)\n"
1020 " Storage: \"%s\" (From %s)\n"
1023 " Elapsed time: %s\n"
1025 " SD Files Written: %s\n"
1026 " SD Bytes Written: %s (%sB)\n"
1027 " Rate: %.1f KB/s\n"
1028 " Volume name(s): %s\n"
1029 " Volume Session Id: %d\n"
1030 " Volume Session Time: %d\n"
1031 " Last Volume Bytes: %s (%sB)\n"
1033 " SD termination status: %s\n"
1034 " Termination: %s\n\n"),
1038 mig_jcr ? edit_uint64(jcr->previous_jr.JobId, ec6) : "0",
1039 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1040 edit_uint64(jcr->jr.JobId, ec8),
1042 level_to_str(jcr->JobLevel), jcr->since,
1043 jcr->client->name(),
1044 jcr->fileset->name(), jcr->FSCreateTime,
1045 jcr->pool->name(), jcr->pool_source,
1046 jcr->wstore->name(), jcr->storage_source,
1049 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1051 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1052 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1053 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1055 mig_jcr ? mig_jcr->VolumeName : "",
1057 jcr->VolSessionTime,
1058 edit_uint64_with_commas(mr.VolBytes, ec4),
1059 edit_uint64_with_suffix(mr.VolBytes, ec5),
1064 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1066 free_jcr(jcr->mig_jcr);
1067 jcr->mig_jcr = NULL;
1069 Dmsg0(100, "Leave migrate_cleanup()\n");
1073 * Return next DBId from comma separated list
1076 * 1 if next DBId returned
1077 * 0 if no more DBIds are in list
1078 * -1 there is an error
1080 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1086 for (int i=0; i<(int)sizeof(id); i++) {
1089 } else if (*q == ',') {
1098 } else if (!is_a_number(id)) {
1099 return -1; /* error */
1102 *DBId = str_to_int64(id);