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"
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 for (int i=1; i < (int)ids.count; i++) {
656 stat = get_next_jobid_from_list(&p, &JobId);
657 Dmsg2(dbglevel, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
658 jcr->MigrateJobId = JobId;
659 start_migration_job(jcr);
661 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
663 } else if (stat == 0) {
664 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
669 /* Now get the last JobId and handle it in the current job */
671 stat = get_next_jobid_from_list(&p, &JobId);
672 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, JobId);
674 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
676 } else if (stat == 0) {
677 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
681 jcr->previous_jr.JobId = JobId;
682 Dmsg1(100, "Previous jobid=%d\n", jcr->previous_jr.JobId);
684 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
685 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
686 edit_int64(jcr->previous_jr.JobId, ed1),
687 db_strerror(jcr->db));
690 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
691 jcr->previous_jr.JobId, jcr->previous_jr.Job);
694 free_pool_memory(ids.list);
695 free_pool_memory(mid.list);
696 free_pool_memory(jids.list);
700 free_pool_memory(ids.list);
701 free_pool_memory(mid.list);
702 free_pool_memory(jids.list);
706 static void start_migration_job(JCR *jcr)
708 UAContext *ua = new_ua_context(jcr);
711 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
712 edit_uint64(jcr->MigrateJobId, ed1));
713 Dmsg1(dbglevel, "=============== Migration cmd=%s\n", ua->cmd);
714 parse_ua_args(ua); /* parse command */
715 int stat = run_cmd(ua, ua->cmd);
717 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
719 Jmsg(jcr, M_INFO, 0, _("Migration JobId %d started.\n"), stat);
724 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
728 POOL_MEM query(PM_MESSAGE);
731 /* Basic query for MediaId */
732 Mmsg(query, query1, jcr->pool->hdr.name);
733 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
734 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
737 if (ids->count == 0) {
738 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
740 if (ids->count != 1) {
741 Jmsg(jcr, M_FATAL, 0, _("SQL logic error. Count should be 1 but is %d\n"),
745 Dmsg1(dbglevel, "Smallest Vol Jobids=%s\n", ids->list);
747 ok = find_jobids_from_mediaid_list(jcr, ids, type);
753 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
756 POOL_MEM query(PM_MESSAGE);
758 Mmsg(query, sql_jobids_from_mediaid, ids->list);
760 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
761 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
764 if (ids->count == 0) {
765 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
772 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
773 const char *query2, const char *type)
777 uitem *last_item = NULL;
782 POOL_MEM query(PM_MESSAGE);
784 item_chain = New(dlist(item, &item->link));
785 if (!jcr->job->selection_pattern) {
786 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
790 Dmsg1(dbglevel, "regex=%s\n", jcr->job->selection_pattern);
791 /* Compile regex expression */
792 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
794 regerror(rc, &preg, prbuf, sizeof(prbuf));
795 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
796 jcr->job->selection_pattern, prbuf);
799 /* Basic query for names */
800 Mmsg(query, query1, jcr->pool->hdr.name);
801 Dmsg1(dbglevel, "query1=%s\n", query.c_str());
802 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
803 (void *)item_chain)) {
804 Jmsg(jcr, M_FATAL, 0,
805 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
808 /* Now apply the regex to the names and remove any item not matched */
809 foreach_dlist(item, item_chain) {
810 const int nmatch = 30;
811 regmatch_t pmatch[nmatch];
813 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
814 free(last_item->item);
815 item_chain->remove(last_item);
817 Dmsg1(dbglevel, "Item=%s\n", item->item);
818 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
820 last_item = NULL; /* keep this one */
826 free(last_item->item);
827 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
828 item_chain->remove(last_item);
832 * At this point, we have a list of items in item_chain
833 * that have been matched by the regex, so now we need
834 * to look up their jobids.
837 foreach_dlist(item, item_chain) {
838 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
839 Mmsg(query, query2, item->item, jcr->pool->hdr.name);
840 Dmsg1(dbglevel, "query2=%s\n", query.c_str());
841 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
842 Jmsg(jcr, M_FATAL, 0,
843 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
847 if (ids->count == 0) {
848 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
852 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
854 Dmsg0(dbglevel, "After delete item_chain\n");
860 * Release resources allocated during backup.
862 void migration_cleanup(JCR *jcr, int TermCode)
864 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
865 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
866 char ec6[50], ec7[50], ec8[50];
867 char term_code[100], sd_term_msg[100];
868 const char *term_msg;
873 JCR *mig_jcr = jcr->mig_jcr;
874 POOL_MEM query(PM_MESSAGE);
876 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
877 dequeue_messages(jcr); /* display any queued messages */
878 memset(&mr, 0, sizeof(mr));
879 set_jcr_job_status(jcr, TermCode);
880 update_job_end_record(jcr); /* update database */
883 * Check if we actually did something.
884 * mig_jcr is jcr of the newly migrated job.
887 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
888 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
889 mig_jcr->VolSessionId = jcr->VolSessionId;
890 mig_jcr->VolSessionTime = jcr->VolSessionTime;
891 mig_jcr->jr.RealEndTime = 0;
892 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
894 set_jcr_job_status(mig_jcr, TermCode);
897 update_job_end_record(mig_jcr);
899 /* Update final items to set them to the previous job's values */
900 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
901 "JobTDate=%s WHERE JobId=%s",
902 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
903 edit_uint64(jcr->previous_jr.JobTDate, ec1),
904 edit_uint64(mig_jcr->jr.JobId, ec2));
905 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
907 /* Now marke the previous job as migrated */
908 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
909 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
910 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
912 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
913 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
914 db_strerror(jcr->db));
915 set_jcr_job_status(jcr, JS_ErrorTerminated);
918 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
919 if (!db_get_media_record(jcr, jcr->db, &mr)) {
920 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
921 mr.VolumeName, db_strerror(jcr->db));
922 set_jcr_job_status(jcr, JS_ErrorTerminated);
925 update_bootstrap_file(mig_jcr);
927 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
929 * Note, if the job has erred, most likely it did not write any
930 * tape, so suppress this "error" message since in that case
931 * it is normal. Or look at it the other way, only for a
932 * normal exit should we complain about this error.
934 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
935 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
937 mig_jcr->VolumeName[0] = 0; /* none */
941 msg_type = M_INFO; /* by default INFO message */
942 switch (jcr->JobStatus) {
944 if (jcr->Errors || jcr->SDErrors) {
945 term_msg = _("%s OK -- with warnings");
947 term_msg = _("%s OK");
951 case JS_ErrorTerminated:
952 term_msg = _("*** %s Error ***");
953 msg_type = M_ERROR; /* Generate error message */
954 if (jcr->store_bsock) {
955 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
956 if (jcr->SD_msg_chan) {
957 pthread_cancel(jcr->SD_msg_chan);
962 term_msg = _("%s Canceled");
963 if (jcr->store_bsock) {
964 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
965 if (jcr->SD_msg_chan) {
966 pthread_cancel(jcr->SD_msg_chan);
971 term_msg = _("Inappropriate %s term code");
974 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
975 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
976 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
977 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
981 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
985 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
987 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
988 " Prev Backup JobId: %s\n"
989 " New Backup JobId: %s\n"
990 " Migration JobId: %s\n"
991 " Migration Job: %s\n"
992 " Backup Level: %s%s\n"
994 " FileSet: \"%s\" %s\n"
995 " Pool: \"%s\" (From %s)\n"
996 " Storage: \"%s\" (From %s)\n"
999 " Elapsed time: %s\n"
1001 " SD Files Written: %s\n"
1002 " SD Bytes Written: %s (%sB)\n"
1003 " Rate: %.1f KB/s\n"
1004 " Volume name(s): %s\n"
1005 " Volume Session Id: %d\n"
1006 " Volume Session Time: %d\n"
1007 " Last Volume Bytes: %s (%sB)\n"
1009 " SD termination status: %s\n"
1010 " Termination: %s\n\n"),
1014 mig_jcr ? edit_uint64(jcr->previous_jr.JobId, ec6) : "0",
1015 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1016 edit_uint64(jcr->jr.JobId, ec8),
1018 level_to_str(jcr->JobLevel), jcr->since,
1019 jcr->client->name(),
1020 jcr->fileset->name(), jcr->FSCreateTime,
1021 jcr->pool->name(), jcr->pool_source,
1022 jcr->wstore->name(), jcr->storage_source,
1025 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1027 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1028 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1029 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1031 mig_jcr ? mig_jcr->VolumeName : "",
1033 jcr->VolSessionTime,
1034 edit_uint64_with_commas(mr.VolBytes, ec4),
1035 edit_uint64_with_suffix(mr.VolBytes, ec5),
1040 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1042 free_jcr(jcr->mig_jcr);
1043 jcr->mig_jcr = NULL;
1045 Dmsg0(100, "Leave migrate_cleanup()\n");
1049 * Return next DBId from comma separated list
1052 * 1 if next DBId returned
1053 * 0 if no more DBIds are in list
1054 * -1 there is an error
1056 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1062 for (int i=0; i<(int)sizeof(id); i++) {
1065 } else if (*q == ',') {
1074 } else if (!is_a_number(id)) {
1075 return -1; /* error */
1078 *DBId = str_to_int64(id);