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 #ifdef xxx /* in development */
358 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
360 dlist *list = (dlist *)ctx;
362 uitem *new_item = (uitem *)malloc(sizeof(uitem));
365 memset(new_item, 0, sizeof(uitem));
366 new_item->item = bstrdup(row[0]);
367 Dmsg1(dbglevel, "Item=%s\n", row[0]);
368 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
369 if (item != new_item) { /* already in list */
370 free(new_item->item);
371 free((char *)new_item);
380 /* Get Job names in Pool */
381 const char *sql_job =
382 "SELECT DISTINCT Job.Name from Job,Pool"
383 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
385 /* Get JobIds from regex'ed Job names */
386 const char *sql_jobids_from_job =
387 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
388 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
389 " ORDER by Job.StartTime";
391 /* Get Client names in Pool */
392 const char *sql_client =
393 "SELECT DISTINCT Client.Name from Client,Pool,Job"
394 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
395 " Job.PoolId=Pool.PoolId";
397 /* Get JobIds from regex'ed Client names */
398 const char *sql_jobids_from_client =
399 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
400 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
401 " AND Job.ClientId=Client.ClientId "
402 " ORDER by Job.StartTime";
404 /* Get Volume names in Pool */
405 const char *sql_vol =
406 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
407 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
408 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
410 /* Get JobIds from regex'ed Volume names */
411 const char *sql_jobids_from_vol =
412 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
413 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
414 " AND JobMedia.JobId=Job.JobId"
415 " ORDER by Job.StartTime";
418 const char *sql_smallest_vol =
419 "SELECT MediaId FROM Media,Pool WHERE"
420 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
421 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
422 " ORDER BY VolBytes ASC LIMIT 1";
424 const char *sql_oldest_vol =
425 "SELECT MediaId FROM Media,Pool WHERE"
426 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
427 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
428 " ORDER BY LastWritten ASC LIMIT 1";
430 /* Get JobIds when we have selected MediaId */
431 const char *sql_jobids_from_mediaid =
432 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
433 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
434 " ORDER by Job.StartTime";
436 /* Get tne number of bytes in the pool */
437 const char *sql_pool_bytes =
438 "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
439 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
440 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
442 /* Get tne number of bytes in the Jobs */
443 const char *sql_job_bytes =
444 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
447 /* Get Media Ids in Pool */
448 const char *sql_mediaids =
449 "SELECT MediaId FROM Media,Pool WHERE"
450 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
451 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
453 /* Get JobIds in Pool longer than specified time */
454 const char *sql_pool_time =
455 "SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
456 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
457 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
458 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
459 " AND Job.RealEndTime<='%s'";
462 * const char *sql_ujobid =
463 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
464 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
465 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
472 * This is the central piece of code that finds a job or jobs
473 * actually JobIds to migrate. It first looks to see if one
474 * has been "manually" specified in jcr->MigrateJobId, and if
475 * so, it returns that JobId to be run. Otherwise, it
476 * examines the Selection Type to see what kind of migration
477 * we are doing (Volume, Job, Client, ...) and applies any
478 * Selection Pattern if appropriate to obtain a list of JobIds.
479 * Finally, it will loop over all the JobIds found, except the last
480 * one starting a new job with MigrationJobId set to that JobId, and
481 * finally, it returns the last JobId to the caller.
483 * Returns: false on error
484 * true if OK and jcr->previous_jr filled in
486 static bool get_job_to_migrate(JCR *jcr)
489 POOL_MEM query(PM_MESSAGE);
494 idpkt ids, mid, jids;
500 char dt[MAX_TIME_LENGTH];
502 ids.list = get_pool_memory(PM_MESSAGE);
505 mid.list = get_pool_memory(PM_MESSAGE);
508 jids.list = get_pool_memory(PM_MESSAGE);
514 * If MigrateJobId is set, then we migrate only that Job,
515 * otherwise, we go through the full selection of jobs to
518 if (jcr->MigrateJobId != 0) {
519 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
520 edit_uint64(jcr->MigrateJobId, ids.list);
523 switch (jcr->job->selection_type) {
525 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
530 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
535 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
540 if (!jcr->job->selection_pattern) {
541 Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
544 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
545 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
546 dbid_handler, (void *)&ids)) {
547 Jmsg(jcr, M_FATAL, 0,
548 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
552 case MT_SMALLEST_VOL:
553 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
558 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
563 case MT_POOL_OCCUPANCY:
565 /* Find count of bytes in pool */
566 Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
567 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
568 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
571 if (ctx.count == 0) {
572 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
575 pool_bytes = ctx.value;
576 Dmsg2(dbglevel, "highbytes=%d pool=%d\n", (int)jcr->pool->MigrationHighBytes,
578 if (pool_bytes < (int64_t)jcr->pool->MigrationHighBytes) {
579 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
582 Dmsg0(dbglevel, "We should do Occupation migration.\n");
585 /* Find a list of MediaIds that could be migrated */
586 Mmsg(query, sql_mediaids, jcr->pool->hdr.name);
587 // Dmsg1(dbglevel, "query=%s\n", query.c_str());
588 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)&ids)) {
589 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
592 if (ids.count == 0) {
593 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
596 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
599 * Now loop over MediaIds getting more JobIds to migrate until
600 * we reduce the pool occupancy below the low water mark.
603 for (int i=0; i < (int)ids.count; i++) {
604 stat = get_next_dbid_from_list(&p, &MediaId);
605 Dmsg2(dbglevel, "get_next_dbid stat=%d MediaId=%u\n", stat, MediaId);
607 Jmsg(jcr, M_FATAL, 0, _("Invalid MediaId found.\n"));
609 } else if (stat == 0) {
613 Mmsg(mid.list, "%s", edit_int64(MediaId, ed1));
614 ok = find_jobids_from_mediaid_list(jcr, &mid, "Volumes");
619 pm_strcat(jids.list, ",");
621 pm_strcat(jids.list, mid.list);
622 jids.count += mid.count;
624 /* Now get the count of bytes added */
626 /* Find count of bytes from Jobs */
627 Mmsg(query, sql_job_bytes, mid.list);
628 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
629 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
632 pool_bytes -= ctx.value;
633 Dmsg1(dbglevel, "Job bytes=%d\n", (int)ctx.value);
634 Dmsg2(dbglevel, "lowbytes=%d pool=%d\n", (int)jcr->pool->MigrationLowBytes,
636 if (pool_bytes <= (int64_t)jcr->pool->MigrationLowBytes) {
637 Dmsg0(dbglevel, "We should be done.\n");
642 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", jids.count, jids.list);
647 ttime = time(NULL) - (time_t)jcr->pool->MigrationTime;
648 (void)localtime_r(&ttime, &tm);
649 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
652 Mmsg(query, sql_pool_time, jcr->pool->hdr.name, dt);
653 // Dmsg1(000, "query=%s\n", query.c_str());
654 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)&ids)) {
655 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
658 if (ids.count == 0) {
659 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
662 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
666 Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
672 * Loop over all jobids except the last one, sending
673 * them to start_migration_job(), which will start a job
674 * for each of them. For the last JobId, we handle it below.
677 Jmsg(jcr, M_INFO, 0, _("The following %u JobIds will be migrated: %s\n"),
678 ids.count, ids.list);
679 for (int i=1; i < (int)ids.count; i++) {
681 stat = get_next_jobid_from_list(&p, &JobId);
682 Dmsg2(dbglevel, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
683 jcr->MigrateJobId = JobId;
684 start_migration_job(jcr);
686 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
688 } else if (stat == 0) {
689 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
694 /* Now get the last JobId and handle it in the current job */
696 stat = get_next_jobid_from_list(&p, &JobId);
697 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, JobId);
699 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
701 } else if (stat == 0) {
702 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
706 jcr->previous_jr.JobId = JobId;
707 Dmsg1(100, "Previous jobid=%d\n", jcr->previous_jr.JobId);
709 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
710 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
711 edit_int64(jcr->previous_jr.JobId, ed1),
712 db_strerror(jcr->db));
715 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
716 jcr->previous_jr.JobId, jcr->previous_jr.Job);
719 free_pool_memory(ids.list);
720 free_pool_memory(mid.list);
721 free_pool_memory(jids.list);
725 free_pool_memory(ids.list);
726 free_pool_memory(mid.list);
727 free_pool_memory(jids.list);
731 static void start_migration_job(JCR *jcr)
733 UAContext *ua = new_ua_context(jcr);
736 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
737 edit_uint64(jcr->MigrateJobId, ed1));
738 Dmsg1(dbglevel, "=============== Migration cmd=%s\n", ua->cmd);
739 parse_ua_args(ua); /* parse command */
740 int stat = run_cmd(ua, ua->cmd);
742 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
744 Jmsg(jcr, M_INFO, 0, _("Migration JobId %d started.\n"), stat);
749 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
753 POOL_MEM query(PM_MESSAGE);
756 /* Basic query for MediaId */
757 Mmsg(query, query1, jcr->pool->hdr.name);
758 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
759 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
762 if (ids->count == 0) {
763 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
765 if (ids->count != 1) {
766 Jmsg(jcr, M_FATAL, 0, _("SQL logic error. Count should be 1 but is %d\n"),
770 Dmsg1(dbglevel, "Smallest Vol Jobids=%s\n", ids->list);
772 ok = find_jobids_from_mediaid_list(jcr, ids, type);
778 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
781 POOL_MEM query(PM_MESSAGE);
783 Mmsg(query, sql_jobids_from_mediaid, ids->list);
785 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
786 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
789 if (ids->count == 0) {
790 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
797 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
798 const char *query2, const char *type)
802 uitem *last_item = NULL;
807 POOL_MEM query(PM_MESSAGE);
809 item_chain = New(dlist(item, &item->link));
810 if (!jcr->job->selection_pattern) {
811 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
815 Dmsg1(dbglevel, "regex=%s\n", jcr->job->selection_pattern);
816 /* Compile regex expression */
817 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
819 regerror(rc, &preg, prbuf, sizeof(prbuf));
820 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
821 jcr->job->selection_pattern, prbuf);
824 /* Basic query for names */
825 Mmsg(query, query1, jcr->pool->hdr.name);
826 Dmsg1(dbglevel, "query1=%s\n", query.c_str());
827 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
828 (void *)item_chain)) {
829 Jmsg(jcr, M_FATAL, 0,
830 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
833 /* Now apply the regex to the names and remove any item not matched */
834 foreach_dlist(item, item_chain) {
835 const int nmatch = 30;
836 regmatch_t pmatch[nmatch];
838 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
839 free(last_item->item);
840 item_chain->remove(last_item);
842 Dmsg1(dbglevel, "Item=%s\n", item->item);
843 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
845 last_item = NULL; /* keep this one */
851 free(last_item->item);
852 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
853 item_chain->remove(last_item);
857 * At this point, we have a list of items in item_chain
858 * that have been matched by the regex, so now we need
859 * to look up their jobids.
862 foreach_dlist(item, item_chain) {
863 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
864 Mmsg(query, query2, item->item, jcr->pool->hdr.name);
865 Dmsg1(dbglevel, "query2=%s\n", query.c_str());
866 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
867 Jmsg(jcr, M_FATAL, 0,
868 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
872 if (ids->count == 0) {
873 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
877 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
879 Dmsg0(dbglevel, "After delete item_chain\n");
885 * Release resources allocated during backup.
887 void migration_cleanup(JCR *jcr, int TermCode)
889 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
890 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
891 char ec6[50], ec7[50], ec8[50];
892 char term_code[100], sd_term_msg[100];
893 const char *term_msg;
898 JCR *mig_jcr = jcr->mig_jcr;
899 POOL_MEM query(PM_MESSAGE);
901 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
902 dequeue_messages(jcr); /* display any queued messages */
903 memset(&mr, 0, sizeof(mr));
904 set_jcr_job_status(jcr, TermCode);
905 update_job_end_record(jcr); /* update database */
908 * Check if we actually did something.
909 * mig_jcr is jcr of the newly migrated job.
912 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
913 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
914 mig_jcr->VolSessionId = jcr->VolSessionId;
915 mig_jcr->VolSessionTime = jcr->VolSessionTime;
916 mig_jcr->jr.RealEndTime = 0;
917 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
919 set_jcr_job_status(mig_jcr, TermCode);
922 update_job_end_record(mig_jcr);
924 /* Update final items to set them to the previous job's values */
925 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
926 "JobTDate=%s WHERE JobId=%s",
927 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
928 edit_uint64(jcr->previous_jr.JobTDate, ec1),
929 edit_uint64(mig_jcr->jr.JobId, ec2));
930 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
932 /* Now marke the previous job as migrated */
933 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
934 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
935 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
937 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
938 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
939 db_strerror(jcr->db));
940 set_jcr_job_status(jcr, JS_ErrorTerminated);
943 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
944 if (!db_get_media_record(jcr, jcr->db, &mr)) {
945 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
946 mr.VolumeName, db_strerror(jcr->db));
947 set_jcr_job_status(jcr, JS_ErrorTerminated);
950 update_bootstrap_file(mig_jcr);
952 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
954 * Note, if the job has erred, most likely it did not write any
955 * tape, so suppress this "error" message since in that case
956 * it is normal. Or look at it the other way, only for a
957 * normal exit should we complain about this error.
959 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
960 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
962 mig_jcr->VolumeName[0] = 0; /* none */
966 msg_type = M_INFO; /* by default INFO message */
967 switch (jcr->JobStatus) {
969 if (jcr->Errors || jcr->SDErrors) {
970 term_msg = _("%s OK -- with warnings");
972 term_msg = _("%s OK");
976 case JS_ErrorTerminated:
977 term_msg = _("*** %s Error ***");
978 msg_type = M_ERROR; /* Generate error message */
979 if (jcr->store_bsock) {
980 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
981 if (jcr->SD_msg_chan) {
982 pthread_cancel(jcr->SD_msg_chan);
987 term_msg = _("%s Canceled");
988 if (jcr->store_bsock) {
989 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
990 if (jcr->SD_msg_chan) {
991 pthread_cancel(jcr->SD_msg_chan);
996 term_msg = _("Inappropriate %s term code");
999 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
1000 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1001 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1002 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1006 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1010 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1012 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
1013 " Prev Backup JobId: %s\n"
1014 " New Backup JobId: %s\n"
1015 " Migration JobId: %s\n"
1016 " Migration Job: %s\n"
1017 " Backup Level: %s%s\n"
1019 " FileSet: \"%s\" %s\n"
1020 " Pool: \"%s\" (From %s)\n"
1021 " Storage: \"%s\" (From %s)\n"
1024 " Elapsed time: %s\n"
1026 " SD Files Written: %s\n"
1027 " SD Bytes Written: %s (%sB)\n"
1028 " Rate: %.1f KB/s\n"
1029 " Volume name(s): %s\n"
1030 " Volume Session Id: %d\n"
1031 " Volume Session Time: %d\n"
1032 " Last Volume Bytes: %s (%sB)\n"
1034 " SD termination status: %s\n"
1035 " Termination: %s\n\n"),
1039 mig_jcr ? edit_uint64(jcr->previous_jr.JobId, ec6) : "0",
1040 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1041 edit_uint64(jcr->jr.JobId, ec8),
1043 level_to_str(jcr->JobLevel), jcr->since,
1044 jcr->client->name(),
1045 jcr->fileset->name(), jcr->FSCreateTime,
1046 jcr->pool->name(), jcr->pool_source,
1047 jcr->wstore->name(), jcr->storage_source,
1050 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1052 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1053 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1054 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1056 mig_jcr ? mig_jcr->VolumeName : "",
1058 jcr->VolSessionTime,
1059 edit_uint64_with_commas(mr.VolBytes, ec4),
1060 edit_uint64_with_suffix(mr.VolBytes, ec5),
1065 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1067 free_jcr(jcr->mig_jcr);
1068 jcr->mig_jcr = NULL;
1070 Dmsg0(100, "Leave migrate_cleanup()\n");
1074 * Return next DBId from comma separated list
1077 * 1 if next DBId returned
1078 * 0 if no more DBIds are in list
1079 * -1 there is an error
1081 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1087 for (int i=0; i<(int)sizeof(id); i++) {
1090 } else if (*q == ',') {
1099 } else if (!is_a_number(id)) {
1100 return -1; /* error */
1103 *DBId = str_to_int64(id);