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 /* Get Job names in Pool */
358 const char *sql_job =
359 "SELECT DISTINCT Job.Name from Job,Pool"
360 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
362 /* Get JobIds from regex'ed Job names */
363 const char *sql_jobids_from_job =
364 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
365 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
366 " ORDER by Job.StartTime";
368 /* Get Client names in Pool */
369 const char *sql_client =
370 "SELECT DISTINCT Client.Name from Client,Pool,Job"
371 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
372 " Job.PoolId=Pool.PoolId";
374 /* Get JobIds from regex'ed Client names */
375 const char *sql_jobids_from_client =
376 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
377 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
378 " AND Job.ClientId=Client.ClientId "
379 " ORDER by Job.StartTime";
381 /* Get Volume names in Pool */
382 const char *sql_vol =
383 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
384 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
385 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
387 /* Get JobIds from regex'ed Volume names */
388 const char *sql_jobids_from_vol =
389 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
390 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
391 " AND JobMedia.JobId=Job.JobId"
392 " ORDER by Job.StartTime";
395 const char *sql_smallest_vol =
396 "SELECT MediaId FROM Media,Pool WHERE"
397 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
398 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
399 " ORDER BY VolBytes ASC LIMIT 1";
401 const char *sql_oldest_vol =
402 "SELECT MediaId FROM Media,Pool WHERE"
403 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
404 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
405 " ORDER BY LastWritten ASC LIMIT 1";
407 /* Get JobIds when we have selected MediaId */
408 const char *sql_jobids_from_mediaid =
409 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
410 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
411 " ORDER by Job.StartTime";
413 /* Get tne number of bytes in the pool */
414 const char *sql_pool_bytes =
415 "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
416 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
417 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
419 /* Get tne number of bytes in the Jobs */
420 const char *sql_job_bytes =
421 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
424 /* Get Media Ids in Pool */
425 const char *sql_mediaids =
426 "SELECT MediaId FROM Media,Pool WHERE"
427 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
428 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
430 /* Get JobIds in Pool longer than specified time */
431 const char *sql_pool_time =
432 "SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
433 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
434 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
435 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
436 " AND Job.RealEndTime<='%s'";
439 * const char *sql_ujobid =
440 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
441 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
442 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
449 * This is the central piece of code that finds a job or jobs
450 * actually JobIds to migrate. It first looks to see if one
451 * has been "manually" specified in jcr->MigrateJobId, and if
452 * so, it returns that JobId to be run. Otherwise, it
453 * examines the Selection Type to see what kind of migration
454 * we are doing (Volume, Job, Client, ...) and applies any
455 * Selection Pattern if appropriate to obtain a list of JobIds.
456 * Finally, it will loop over all the JobIds found, except the last
457 * one starting a new job with MigrationJobId set to that JobId, and
458 * finally, it returns the last JobId to the caller.
460 * Returns: false on error
461 * true if OK and jcr->previous_jr filled in
463 static bool get_job_to_migrate(JCR *jcr)
466 POOL_MEM query(PM_MESSAGE);
471 idpkt ids, mid, jids;
477 char dt[MAX_TIME_LENGTH];
479 ids.list = get_pool_memory(PM_MESSAGE);
482 mid.list = get_pool_memory(PM_MESSAGE);
485 jids.list = get_pool_memory(PM_MESSAGE);
491 * If MigrateJobId is set, then we migrate only that Job,
492 * otherwise, we go through the full selection of jobs to
495 if (jcr->MigrateJobId != 0) {
496 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
497 edit_uint64(jcr->MigrateJobId, ids.list);
500 switch (jcr->job->selection_type) {
502 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
507 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
512 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
517 if (!jcr->job->selection_pattern) {
518 Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
521 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
522 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
523 dbid_handler, (void *)&ids)) {
524 Jmsg(jcr, M_FATAL, 0,
525 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
529 case MT_SMALLEST_VOL:
530 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
535 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
540 case MT_POOL_OCCUPANCY:
542 /* Find count of bytes in pool */
543 Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
544 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
545 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
548 if (ctx.count == 0) {
549 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
552 pool_bytes = ctx.value;
553 Dmsg2(dbglevel, "highbytes=%d pool=%d\n", (int)jcr->pool->MigrationHighBytes,
555 if (pool_bytes < (int64_t)jcr->pool->MigrationHighBytes) {
556 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
559 Dmsg0(dbglevel, "We should do Occupation migration.\n");
562 /* Find a list of MediaIds that could be migrated */
563 Mmsg(query, sql_mediaids, jcr->pool->hdr.name);
564 // Dmsg1(dbglevel, "query=%s\n", query.c_str());
565 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)&ids)) {
566 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
569 if (ids.count == 0) {
570 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
573 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
576 * Now loop over MediaIds getting more JobIds to migrate until
577 * we reduce the pool occupancy below the low water mark.
580 for (int i=0; i < (int)ids.count; i++) {
581 stat = get_next_dbid_from_list(&p, &MediaId);
582 Dmsg2(dbglevel, "get_next_dbid stat=%d MediaId=%u\n", stat, MediaId);
584 Jmsg(jcr, M_FATAL, 0, _("Invalid MediaId found.\n"));
586 } else if (stat == 0) {
590 Mmsg(mid.list, "%s", edit_int64(MediaId, ed1));
591 ok = find_jobids_from_mediaid_list(jcr, &mid, "Volumes");
596 pm_strcat(jids.list, ",");
598 pm_strcat(jids.list, mid.list);
599 jids.count += mid.count;
601 /* Now get the count of bytes added */
603 /* Find count of bytes from Jobs */
604 Mmsg(query, sql_job_bytes, mid.list);
605 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
606 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
609 pool_bytes -= ctx.value;
610 Dmsg1(dbglevel, "Job bytes=%d\n", (int)ctx.value);
611 Dmsg2(dbglevel, "lowbytes=%d pool=%d\n", (int)jcr->pool->MigrationLowBytes,
613 if (pool_bytes <= (int64_t)jcr->pool->MigrationLowBytes) {
614 Dmsg0(dbglevel, "We should be done.\n");
619 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", jids.count, jids.list);
624 ttime = time(NULL) - (time_t)jcr->pool->MigrationTime;
625 (void)localtime_r(&ttime, &tm);
626 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
629 Mmsg(query, sql_pool_time, jcr->pool->hdr.name, dt);
630 // Dmsg1(000, "query=%s\n", query.c_str());
631 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)&ids)) {
632 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
635 if (ids.count == 0) {
636 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
639 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
643 Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
649 * Loop over all jobids except the last one, sending
650 * them to start_migration_job(), which will start a job
651 * for each of them. For the last JobId, we handle it below.
654 Jmsg(jcr, M_INFO, 0, _("The following %u JobIds will be migrated: %s\n"),
655 ids.count, ids.list);
656 for (int i=1; i < (int)ids.count; i++) {
658 stat = get_next_jobid_from_list(&p, &JobId);
659 Dmsg2(dbglevel, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
660 jcr->MigrateJobId = JobId;
661 start_migration_job(jcr);
663 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
665 } else if (stat == 0) {
666 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
671 /* Now get the last JobId and handle it in the current job */
673 stat = get_next_jobid_from_list(&p, &JobId);
674 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, JobId);
676 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
678 } else if (stat == 0) {
679 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
683 jcr->previous_jr.JobId = JobId;
684 Dmsg1(100, "Previous jobid=%d\n", jcr->previous_jr.JobId);
686 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
687 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
688 edit_int64(jcr->previous_jr.JobId, ed1),
689 db_strerror(jcr->db));
692 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
693 jcr->previous_jr.JobId, jcr->previous_jr.Job);
696 free_pool_memory(ids.list);
697 free_pool_memory(mid.list);
698 free_pool_memory(jids.list);
702 free_pool_memory(ids.list);
703 free_pool_memory(mid.list);
704 free_pool_memory(jids.list);
708 static void start_migration_job(JCR *jcr)
710 UAContext *ua = new_ua_context(jcr);
713 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
714 edit_uint64(jcr->MigrateJobId, ed1));
715 Dmsg1(dbglevel, "=============== Migration cmd=%s\n", ua->cmd);
716 parse_ua_args(ua); /* parse command */
717 int stat = run_cmd(ua, ua->cmd);
719 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
721 Jmsg(jcr, M_INFO, 0, _("Migration JobId %d started.\n"), stat);
726 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
730 POOL_MEM query(PM_MESSAGE);
733 /* Basic query for MediaId */
734 Mmsg(query, query1, jcr->pool->hdr.name);
735 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
736 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
739 if (ids->count == 0) {
740 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
742 if (ids->count != 1) {
743 Jmsg(jcr, M_FATAL, 0, _("SQL logic error. Count should be 1 but is %d\n"),
747 Dmsg1(dbglevel, "Smallest Vol Jobids=%s\n", ids->list);
749 ok = find_jobids_from_mediaid_list(jcr, ids, type);
755 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
758 POOL_MEM query(PM_MESSAGE);
760 Mmsg(query, sql_jobids_from_mediaid, ids->list);
762 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
763 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
766 if (ids->count == 0) {
767 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
774 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
775 const char *query2, const char *type)
779 uitem *last_item = NULL;
784 POOL_MEM query(PM_MESSAGE);
786 item_chain = New(dlist(item, &item->link));
787 if (!jcr->job->selection_pattern) {
788 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
792 Dmsg1(dbglevel, "regex=%s\n", jcr->job->selection_pattern);
793 /* Compile regex expression */
794 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
796 regerror(rc, &preg, prbuf, sizeof(prbuf));
797 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
798 jcr->job->selection_pattern, prbuf);
801 /* Basic query for names */
802 Mmsg(query, query1, jcr->pool->hdr.name);
803 Dmsg1(dbglevel, "query1=%s\n", query.c_str());
804 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
805 (void *)item_chain)) {
806 Jmsg(jcr, M_FATAL, 0,
807 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
810 /* Now apply the regex to the names and remove any item not matched */
811 foreach_dlist(item, item_chain) {
812 const int nmatch = 30;
813 regmatch_t pmatch[nmatch];
815 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
816 free(last_item->item);
817 item_chain->remove(last_item);
819 Dmsg1(dbglevel, "Item=%s\n", item->item);
820 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
822 last_item = NULL; /* keep this one */
828 free(last_item->item);
829 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
830 item_chain->remove(last_item);
834 * At this point, we have a list of items in item_chain
835 * that have been matched by the regex, so now we need
836 * to look up their jobids.
839 foreach_dlist(item, item_chain) {
840 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
841 Mmsg(query, query2, item->item, jcr->pool->hdr.name);
842 Dmsg1(dbglevel, "query2=%s\n", query.c_str());
843 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
844 Jmsg(jcr, M_FATAL, 0,
845 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
849 if (ids->count == 0) {
850 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
854 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
856 Dmsg0(dbglevel, "After delete item_chain\n");
862 * Release resources allocated during backup.
864 void migration_cleanup(JCR *jcr, int TermCode)
866 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
867 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
868 char ec6[50], ec7[50], ec8[50];
869 char term_code[100], sd_term_msg[100];
870 const char *term_msg;
875 JCR *mig_jcr = jcr->mig_jcr;
876 POOL_MEM query(PM_MESSAGE);
878 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
879 dequeue_messages(jcr); /* display any queued messages */
880 memset(&mr, 0, sizeof(mr));
881 set_jcr_job_status(jcr, TermCode);
882 update_job_end_record(jcr); /* update database */
885 * Check if we actually did something.
886 * mig_jcr is jcr of the newly migrated job.
889 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
890 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
891 mig_jcr->VolSessionId = jcr->VolSessionId;
892 mig_jcr->VolSessionTime = jcr->VolSessionTime;
893 mig_jcr->jr.RealEndTime = 0;
894 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
896 set_jcr_job_status(mig_jcr, TermCode);
899 update_job_end_record(mig_jcr);
901 /* Update final items to set them to the previous job's values */
902 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
903 "JobTDate=%s WHERE JobId=%s",
904 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
905 edit_uint64(jcr->previous_jr.JobTDate, ec1),
906 edit_uint64(mig_jcr->jr.JobId, ec2));
907 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
909 /* Now marke the previous job as migrated */
910 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
911 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
912 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
914 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
915 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
916 db_strerror(jcr->db));
917 set_jcr_job_status(jcr, JS_ErrorTerminated);
920 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
921 if (!db_get_media_record(jcr, jcr->db, &mr)) {
922 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
923 mr.VolumeName, db_strerror(jcr->db));
924 set_jcr_job_status(jcr, JS_ErrorTerminated);
927 update_bootstrap_file(mig_jcr);
929 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
931 * Note, if the job has erred, most likely it did not write any
932 * tape, so suppress this "error" message since in that case
933 * it is normal. Or look at it the other way, only for a
934 * normal exit should we complain about this error.
936 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
937 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
939 mig_jcr->VolumeName[0] = 0; /* none */
943 msg_type = M_INFO; /* by default INFO message */
944 switch (jcr->JobStatus) {
946 if (jcr->Errors || jcr->SDErrors) {
947 term_msg = _("%s OK -- with warnings");
949 term_msg = _("%s OK");
953 case JS_ErrorTerminated:
954 term_msg = _("*** %s Error ***");
955 msg_type = M_ERROR; /* Generate error message */
956 if (jcr->store_bsock) {
957 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
958 if (jcr->SD_msg_chan) {
959 pthread_cancel(jcr->SD_msg_chan);
964 term_msg = _("%s Canceled");
965 if (jcr->store_bsock) {
966 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
967 if (jcr->SD_msg_chan) {
968 pthread_cancel(jcr->SD_msg_chan);
973 term_msg = _("Inappropriate %s term code");
976 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
977 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
978 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
979 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
983 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
987 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
989 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
990 " Prev Backup JobId: %s\n"
991 " New Backup JobId: %s\n"
992 " Migration JobId: %s\n"
993 " Migration Job: %s\n"
994 " Backup Level: %s%s\n"
996 " FileSet: \"%s\" %s\n"
997 " Pool: \"%s\" (From %s)\n"
998 " Storage: \"%s\" (From %s)\n"
1001 " Elapsed time: %s\n"
1003 " SD Files Written: %s\n"
1004 " SD Bytes Written: %s (%sB)\n"
1005 " Rate: %.1f KB/s\n"
1006 " Volume name(s): %s\n"
1007 " Volume Session Id: %d\n"
1008 " Volume Session Time: %d\n"
1009 " Last Volume Bytes: %s (%sB)\n"
1011 " SD termination status: %s\n"
1012 " Termination: %s\n\n"),
1016 mig_jcr ? edit_uint64(jcr->previous_jr.JobId, ec6) : "0",
1017 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1018 edit_uint64(jcr->jr.JobId, ec8),
1020 level_to_str(jcr->JobLevel), jcr->since,
1021 jcr->client->name(),
1022 jcr->fileset->name(), jcr->FSCreateTime,
1023 jcr->pool->name(), jcr->pool_source,
1024 jcr->wstore->name(), jcr->storage_source,
1027 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1029 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1030 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1031 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1033 mig_jcr ? mig_jcr->VolumeName : "",
1035 jcr->VolSessionTime,
1036 edit_uint64_with_commas(mr.VolBytes, ec4),
1037 edit_uint64_with_suffix(mr.VolBytes, ec5),
1042 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1044 free_jcr(jcr->mig_jcr);
1045 jcr->mig_jcr = NULL;
1047 Dmsg0(100, "Leave migrate_cleanup()\n");
1051 * Return next DBId from comma separated list
1054 * 1 if next DBId returned
1055 * 0 if no more DBIds are in list
1056 * -1 there is an error
1058 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1064 for (int i=0; i<(int)sizeof(id); i++) {
1067 } else if (*q == ',') {
1076 } else if (!is_a_number(id)) {
1077 return -1; /* error */
1080 *DBId = str_to_int64(id);