1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2004-2012 Free Software Foundation Europe e.V.
5 Copyright (C) 2011-2016 Planets Communications B.V.
6 Copyright (C) 2013-2019 Bareos GmbH & Co. KG
7
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation and included
11 in the file LICENSE.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Affero General Public License for more details.
17
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 02110-1301, USA.
22 */
23 /*
24 * BAREOS Director -- migrate.c -- responsible for doing migration and copy jobs.
25 * Also handles Copy jobs (March 2008)
26 * Kern Sibbald, September 2004
27 * SD-SD Migration by Marco van Wieringen, November 2012
28 */
29 /**
30 * @file
31 * responsible for doing migration and copy jobs.
32 *
33 * Also handles Copy jobs
34 *
35 * Basic tasks done here:
36 * Open DB and create records for this job.
37 * Open Message Channel with Storage daemon to tell him a job will be starting.
38 * Open connection with Storage daemon and pass him commands to do the backup.
39 * When the Storage daemon finishes the job, update the DB.
40 */
41
42 #include "include/bareos.h"
43 #include "dird.h"
44 #include "dird/dird_globals.h"
45 #include "dird/backup.h"
46 #include "dird/job.h"
47 #include "dird/migration.h"
48 #include "dird/msgchan.h"
49 #include "dird/sd_cmds.h"
50 #include "dird/storage.h"
51 #include "dird/ua_input.h"
52 #include "dird/ua_server.h"
53 #include "dird/ua_purge.h"
54 #include "dird/ua_run.h"
55 #include "lib/edit.h"
56
57 #include "cats/sql.h"
58
59 #ifndef HAVE_REGEX_H
60 #include "lib/bregex.h"
61 #else
62 #include <regex.h>
63 #endif
64
65 namespace directordaemon {
66
67 /* Commands sent to other storage daemon */
68 static char replicatecmd[] =
69 "replicate JobId=%d Job=%s address=%s port=%d ssl=%d Authorization=%s\n";
70
71 /**
72 * Get Job names in Pool
73 */
74 static const char *sql_job =
75 "SELECT DISTINCT Job.Name from Job,Pool"
76 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
77
78 /**
79 * Get JobIds from regex'ed Job names
80 */
81 static const char *sql_jobids_from_job =
82 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
83 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
84 " ORDER by Job.StartTime";
85
86 /**
87 * Get Client names in Pool
88 */
89 static const char *sql_client =
90 "SELECT DISTINCT Client.Name from Client,Pool,Job"
91 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
92 " Job.PoolId=Pool.PoolId";
93
94 /**
95 * Get JobIds from regex'ed Client names
96 */
97 static const char *sql_jobids_from_client =
98 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
99 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
100 " AND Job.ClientId=Client.ClientId AND Job.Type IN ('B','C')"
101 " AND Job.JobStatus IN ('T','W')"
102 " ORDER by Job.StartTime";
103
104 /**
105 * Get Volume names in Pool
106 */
107 static const char *sql_vol =
108 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
109 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
110 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
111
112 /**
113 * Get JobIds from regex'ed Volume names
114 */
115 static const char *sql_jobids_from_vol =
116 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
117 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
118 " AND JobMedia.JobId=Job.JobId AND Job.Type IN ('B','C')"
119 " AND Job.JobStatus IN ('T','W') AND Media.Enabled=1"
120 " ORDER by Job.StartTime";
121
122 /**
123 * Get JobIds from the smallest volume
124 */
125 static const char *sql_smallest_vol =
126 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
127 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
128 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
129 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
130 " ORDER BY VolBytes ASC LIMIT 1";
131
132 /**
133 * Get JobIds from the oldest volume
134 */
135 static const char *sql_oldest_vol =
136 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
137 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
138 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
139 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
140 " ORDER BY LastWritten ASC LIMIT 1";
141
142 /**
143 * Get JobIds when we have selected MediaId
144 */
145 static const char *sql_jobids_from_mediaid =
146 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
147 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId IN (%s)"
148 " AND Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
149 " ORDER by Job.StartTime";
150
151 /**
152 * Get the number of bytes in the pool
153 */
154 static const char *sql_pool_bytes =
155 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN"
156 " (SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
157 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
158 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
159 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
160 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId)";
161
162 /**
163 * Get the number of bytes in the Jobs
164 */
165 static const char *sql_job_bytes =
166 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
167
168 /**
169 * Get Media Ids in Pool
170 */
171 static const char *sql_mediaids =
172 "SELECT MediaId FROM Media,Pool WHERE"
173 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
174 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
175
176 /**
177 * Get JobIds in Pool longer than specified time
178 */
179 static const char *sql_pool_time =
180 "SELECT DISTINCT Job.JobId FROM Pool,Job,Media,JobMedia WHERE"
181 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
182 " VolStatus IN ('Full','Used','Error') AND Media.Enabled=1 AND"
183 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
184 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
185 " AND Job.RealEndTime<='%s'";
186
187 /**
188 * Get JobIds from successfully completed backup jobs which have not been copied before
189 */
190 static const char *sql_jobids_of_pool_uncopied_jobs =
191 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
192 " WHERE Pool.Name = '%s' AND Pool.PoolId = Job.PoolId"
193 " AND Job.Type = 'B' AND Job.JobStatus IN ('T','W')"
194 " AND Job.jobBytes > 0"
195 " AND Job.JobId NOT IN"
196 " (SELECT PriorJobId FROM Job WHERE"
197 " Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
198 " AND PriorJobId != 0)"
199 " ORDER by Job.StartTime";
200
201 /**
202 * Migrate NDMP Job MetaData.
203 */
204 static const char *sql_migrate_ndmp_metadata =
205 "UPDATE File SET JobId=%s "
206 "WHERE JobId=%s "
207 "AND Name NOT IN ("
208 "SELECT Name "
209 "FROM File "
210 "WHERE JobId=%s)";
211
212 /**
213 * Copy NDMP Job MetaData.
214 */
215 static const char *sql_copy_ndmp_metadata =
216 "INSERT INTO File (FileIndex, JobId, PathId, Name, DeltaSeq, MarkId, LStat, MD5) "
217 "SELECT FileIndex, %s, PathId, Name, DeltaSeq, MarkId, LStat, MD5 "
218 "FROM File "
219 "WHERE JobId=%s "
220 "AND Name NOT IN ("
221 "SELECT Name "
222 "FROM File "
223 "WHERE JobId=%s)";
224
225 static const int dbglevel = 10;
226
227 struct idpkt {
228 POOLMEM *list;
229 uint32_t count;
230 };
231
232 /**
233 * See if two storage definitions point to the same Storage Daemon.
234 *
235 * We compare:
236 * - address
237 * - SDport
238 * - password
239 */
IsSameStorageDaemon(StorageResource * read_storage,StorageResource * write_storage)240 static inline bool IsSameStorageDaemon(StorageResource *read_storage, StorageResource *write_storage)
241 {
242 return read_storage->SDport == write_storage->SDport &&
243 Bstrcasecmp(read_storage->address, write_storage->address) &&
244 Bstrcasecmp(read_storage->password_.value, write_storage->password_.value);
245 }
246
SetMigrationWstorage(JobControlRecord * jcr,PoolResource * pool,PoolResource * next_pool,const char * where)247 bool SetMigrationWstorage(JobControlRecord *jcr, PoolResource *pool, PoolResource *next_pool, const char *where)
248 {
249 if (!next_pool) {
250 Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
251 pool->hdr.name);
252 return false;
253 }
254
255 if (!next_pool->storage || next_pool->storage->size() == 0) {
256 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
257 next_pool->name());
258 return false;
259 }
260
261 CopyWstorage(jcr, next_pool->storage, where);
262
263 return true;
264 }
265
266 /**
267 * SetMigrationNextPool() called by DoMigrationInit()
268 * at differents stages.
269 *
270 * The idea here is to make a common subroutine for the
271 * NextPool's search code and to permit DoMigrationInit()
272 * to return with NextPool set in jcr struct.
273 */
SetMigrationNextPool(JobControlRecord * jcr,PoolResource ** retpool)274 static inline bool SetMigrationNextPool(JobControlRecord *jcr, PoolResource **retpool)
275 {
276 PoolDbRecord pr;
277 char ed1[100];
278 PoolResource *pool;
279 const char *storage_source;
280
281 /*
282 * Get the PoolId used with the original job. Then
283 * find the pool name from the database record.
284 */
285 memset(&pr, 0, sizeof(pr));
286 pr.PoolId = jcr->jr.PoolId;
287 if (!jcr->db->GetPoolRecord(jcr, &pr)) {
288 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
289 edit_int64(pr.PoolId, ed1), jcr->db->strerror());
290 return false;
291 }
292
293 /*
294 * Get the pool resource corresponding to the original job
295 */
296 pool = (PoolResource *)my_config->GetResWithName(R_POOL, pr.Name);
297 *retpool = pool;
298 if (!pool) {
299 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
300 return false;
301 }
302
303 /*
304 * See if there is a next pool override.
305 */
306 if (jcr->res.run_next_pool_override) {
307 PmStrcpy(jcr->res.npool_source, _("Run NextPool override"));
308 PmStrcpy(jcr->res.pool_source, _("Run NextPool override"));
309 storage_source = _("Storage from Run NextPool override");
310 } else {
311 /*
312 * See if there is a next pool override in the Job definition.
313 */
314 if (jcr->res.job->next_pool) {
315 jcr->res.next_pool = jcr->res.job->next_pool;
316 PmStrcpy(jcr->res.npool_source, _("Job's NextPool resource"));
317 PmStrcpy(jcr->res.pool_source, _("Job's NextPool resource"));
318 storage_source = _("Storage from Job's NextPool resource");
319 } else {
320 /*
321 * Fall back to the pool's NextPool definition.
322 */
323 jcr->res.next_pool = pool->NextPool;
324 PmStrcpy(jcr->res.npool_source, _("Job Pool's NextPool resource"));
325 PmStrcpy(jcr->res.pool_source, _("Job Pool's NextPool resource"));
326 storage_source = _("Storage from Pool's NextPool resource");
327 }
328 }
329
330 /*
331 * If the original backup pool has a NextPool, make sure a
332 * record exists in the database. Note, in this case, we
333 * will be migrating from pool to pool->NextPool.
334 */
335 if (jcr->res.next_pool) {
336 jcr->jr.PoolId = GetOrCreatePoolRecord(jcr, jcr->res.next_pool->name());
337 if (jcr->jr.PoolId == 0) {
338 return false;
339 }
340 }
341
342 if (!SetMigrationWstorage(jcr, pool, jcr->res.next_pool, storage_source)) {
343 return false;
344 }
345
346 jcr->res.pool = jcr->res.next_pool;
347 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->res.pool->name(), jcr->res.rpool->name());
348
349 return true;
350 }
351
352 /**
353 * Sanity check that we are not using the same storage for reading and writing.
354 */
SameStorage(JobControlRecord * jcr)355 static inline bool SameStorage(JobControlRecord *jcr)
356 {
357 StorageResource *read_store, *write_store;
358
359 read_store = (StorageResource *)jcr->res.read_storage_list->first();
360 write_store = (StorageResource *)jcr->res.write_storage_list->first();
361
362 if (!read_store->autochanger && !write_store->autochanger &&
363 bstrcmp(read_store->name(), write_store->name())) {
364 return true;
365 }
366
367 return false;
368 }
369
StartNewMigrationJob(JobControlRecord * jcr)370 static inline void StartNewMigrationJob(JobControlRecord *jcr)
371 {
372 char ed1[50];
373 JobId_t jobid;
374 UaContext *ua;
375 PoolMem cmd(PM_MESSAGE);
376
377 ua = new_ua_context(jcr);
378 ua->batch = true;
379 Mmsg(ua->cmd, "run job=\"%s\" jobid=%s ignoreduplicatecheck=yes",
380 jcr->res.job->name(), edit_uint64(jcr->MigrateJobId, ed1));
381
382 /*
383 * Make sure we have something to compare against.
384 */
385 if (jcr->res.pool) {
386 /*
387 * See if there was actually a pool override.
388 */
389 if (jcr->res.pool != jcr->res.job->pool) {
390 Mmsg(cmd, " pool=\"%s\"", jcr->res.pool->name());
391 PmStrcat(ua->cmd, cmd.c_str());
392 }
393
394 /*
395 * See if there was actually a next pool override.
396 */
397 if (jcr->res.next_pool && jcr->res.next_pool != jcr->res.pool->NextPool) {
398 Mmsg(cmd, " nextpool=\"%s\"", jcr->res.next_pool->name());
399 PmStrcat(ua->cmd, cmd.c_str());
400 }
401 }
402
403 Dmsg2(dbglevel, "=============== %s cmd=%s\n", jcr->get_OperationName(), ua->cmd);
404 ParseUaArgs(ua); /* parse command */
405
406 jobid = DoRunCmd(ua, ua->cmd);
407 if (jobid == 0) {
408 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
409 } else {
410 Jmsg(jcr, M_INFO, 0, _("%s JobId %d started.\n"), jcr->get_OperationName(), (int)jobid);
411 }
412
413 FreeUaContext(ua);
414 }
415
416 /**
417 * Return next DBId from comma separated list
418 *
419 * Returns:
420 * 1 if next DBId returned
421 * 0 if no more DBIds are in list
422 * -1 there is an error
423 */
GetNextDbidFromList(char ** p,DBId_t * DBId)424 static inline int GetNextDbidFromList(char **p, DBId_t *DBId)
425 {
426 int i;
427 const int maxlen = 30;
428 char id[maxlen+1];
429 char *q = *p;
430
431 id[0] = 0;
432 for (i = 0; i < maxlen; i++) {
433 if (*q == 0) {
434 break;
435 } else if (*q == ',') {
436 q++;
437 break;
438 }
439 id[i] = *q++;
440 id[i+1] = 0;
441 }
442
443 if (id[0] == 0) {
444 return 0;
445 } else if (!Is_a_number(id)) {
446 return -1; /* error */
447 }
448
449 *p = q;
450 *DBId = str_to_int64(id);
451
452 return 1;
453 }
454
455 /**
456 * Add an item to the list if it is unique
457 */
AddUniqueId(idpkt * ids,char * item)458 static void AddUniqueId(idpkt *ids, char *item)
459 {
460 const int maxlen = 30;
461 char id[maxlen+1];
462 char *q = ids->list;
463
464 /*
465 * Walk through current list to see if each item is the same as item
466 */
467 while (*q) {
468 id[0] = 0;
469 for (int i=0; i<maxlen; i++) {
470 if (*q == 0) {
471 break;
472 } else if (*q == ',') {
473 q++;
474 break;
475 }
476 id[i] = *q++;
477 id[i+1] = 0;
478 }
479 if (bstrcmp(item, id)) {
480 return;
481 }
482 }
483
484 /*
485 * Did not find item, so add it to list
486 */
487 if (ids->count == 0) {
488 ids->list[0] = 0;
489 } else {
490 PmStrcat(ids->list, ",");
491 }
492
493 PmStrcat(ids->list, item);
494 ids->count++;
495
496 return;
497 }
498
499 /**
500 * Callback handler make list of DB Ids
501 */
UniqueDbidHandler(void * ctx,int num_fields,char ** row)502 static int UniqueDbidHandler(void *ctx, int num_fields, char **row)
503 {
504 idpkt *ids = (idpkt *)ctx;
505
506 /*
507 * Sanity check
508 */
509 if (!row || !row[0]) {
510 Dmsg0(dbglevel, "dbid_hdlr error empty row\n");
511 return 1; /* stop calling us */
512 }
513
514 AddUniqueId(ids, row[0]);
515 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
516 return 0;
517 }
518
519 struct uitem {
520 dlink link;
521 char *item;
522 };
523
ItemCompare(void * item1,void * item2)524 static int ItemCompare(void *item1, void *item2)
525 {
526 uitem *i1 = (uitem *)item1;
527 uitem *i2 = (uitem *)item2;
528
529 return strcmp(i1->item, i2->item);
530 }
531
UniqueNameHandler(void * ctx,int num_fields,char ** row)532 static int UniqueNameHandler(void *ctx, int num_fields, char **row)
533 {
534 dlist *list = (dlist *)ctx;
535
536 uitem *new_item = (uitem *)malloc(sizeof(uitem));
537 uitem *item;
538
539 memset(new_item, 0, sizeof(uitem));
540 new_item->item = bstrdup(row[0]);
541 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
542
543 item = (uitem *)list->binary_insert((void *)new_item, ItemCompare);
544 if (item != new_item) { /* already in list */
545 free(new_item->item);
546 free((char *)new_item);
547 return 0;
548 }
549
550 return 0;
551 }
552
553 /**
554 * This routine returns:
555 * false if an error occurred
556 * true otherwise
557 * ids.count number of jobids found (may be zero)
558 */
FindJobidsFromMediaidList(JobControlRecord * jcr,idpkt * ids,const char * type)559 static bool FindJobidsFromMediaidList(JobControlRecord *jcr, idpkt *ids, const char *type)
560 {
561 bool ok = false;
562 PoolMem query(PM_MESSAGE);
563
564 Mmsg(query, sql_jobids_from_mediaid, ids->list);
565 ids->count = 0;
566 if (!jcr->db->SqlQuery(query.c_str(), UniqueDbidHandler, (void *)ids)) {
567 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), jcr->db->strerror());
568 goto bail_out;
569 }
570 if (ids->count == 0) {
571 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName());
572 }
573 ok = true;
574
575 bail_out:
576 return ok;
577 }
578
find_mediaid_then_jobids(JobControlRecord * jcr,idpkt * ids,const char * query1,const char * type)579 static bool find_mediaid_then_jobids(JobControlRecord *jcr, idpkt *ids,
580 const char *query1,
581 const char *type)
582 {
583 bool ok = false;
584 PoolMem query(PM_MESSAGE);
585
586 ids->count = 0;
587
588 /*
589 * Basic query for MediaId
590 */
591 Mmsg(query, query1, jcr->res.rpool->name());
592 if (!jcr->db->SqlQuery(query.c_str(), UniqueDbidHandler, (void *)ids)) {
593 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), jcr->db->strerror());
594 goto bail_out;
595 }
596
597 if (ids->count == 0) {
598 Jmsg(jcr, M_INFO, 0, _("No %s found to %s.\n"), type, jcr->get_ActionName());
599 ok = true; /* Not an error */
600 goto bail_out;
601 } else if (ids->count != 1) {
602 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
603 goto bail_out;
604 }
605
606 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
607
608 ok = FindJobidsFromMediaidList(jcr, ids, type);
609
610 bail_out:
611 return ok;
612 }
613
614 /**
615 * This routine returns:
616 * false if an error occurred
617 * true otherwise
618 * ids.count number of jobids found (may be zero)
619 */
FindJobidsOfPoolUncopiedJobs(JobControlRecord * jcr,idpkt * ids)620 static inline bool FindJobidsOfPoolUncopiedJobs(JobControlRecord *jcr, idpkt *ids)
621 {
622 bool ok = false;
623 PoolMem query(PM_MESSAGE);
624
625 /*
626 * Only a copy job is allowed
627 */
628 if (!jcr->is_JobType(JT_COPY)) {
629 Jmsg(jcr, M_FATAL, 0,
630 _("Selection Type 'pooluncopiedjobs' only applies to Copy Jobs"));
631 goto bail_out;
632 }
633
634 Dmsg1(dbglevel, "copy selection pattern=%s\n", jcr->res.rpool->name());
635 Mmsg(query, sql_jobids_of_pool_uncopied_jobs, jcr->res.rpool->name());
636 Dmsg1(dbglevel, "get uncopied jobs query=%s\n", query.c_str());
637 if (!jcr->db->SqlQuery(query.c_str(), UniqueDbidHandler, (void *)ids)) {
638 Jmsg(jcr, M_FATAL, 0,
639 _("SQL to get uncopied jobs failed. ERR=%s\n"), jcr->db->strerror());
640 goto bail_out;
641 }
642 ok = true;
643
644 bail_out:
645 return ok;
646 }
647
regex_find_jobids(JobControlRecord * jcr,idpkt * ids,const char * query1,const char * query2,const char * type)648 static bool regex_find_jobids(JobControlRecord *jcr, idpkt *ids,
649 const char *query1,
650 const char *query2,
651 const char *type)
652 {
653 dlist *item_chain;
654 uitem *item = NULL;
655 uitem *last_item = NULL;
656 regex_t preg;
657 char prbuf[500];
658 int rc;
659 bool ok = false;
660 PoolMem query(PM_MESSAGE);
661
662 item_chain = New(dlist(item, &item->link));
663 if (!jcr->res.job->selection_pattern) {
664 Jmsg(jcr, M_FATAL, 0, _("No %s %s selection pattern specified.\n"),
665 jcr->get_OperationName(), type);
666 goto bail_out;
667 }
668 Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->res.job->selection_pattern);
669
670 /*
671 * Basic query for names
672 */
673 Mmsg(query, query1, jcr->res.rpool->name());
674 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
675 if (!jcr->db->SqlQuery(query.c_str(), UniqueNameHandler,
676 (void *)item_chain)) {
677 Jmsg(jcr, M_FATAL, 0,
678 _("SQL to get %s failed. ERR=%s\n"), type, jcr->db->strerror());
679 goto bail_out;
680 }
681 Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
682 if (item_chain->size() == 0) {
683 Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to %s.\n"),
684 jcr->res.rpool->name(), jcr->get_ActionName());
685 ok = true;
686 goto bail_out; /* skip regex match */
687 } else {
688 /*
689 * Compile regex expression
690 */
691 rc = regcomp(&preg, jcr->res.job->selection_pattern, REG_EXTENDED);
692 if (rc != 0) {
693 regerror(rc, &preg, prbuf, sizeof(prbuf));
694 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
695 jcr->res.job->selection_pattern, prbuf);
696 goto bail_out;
697 }
698
699 /*
700 * Now apply the regex to the names and remove any item not matched
701 */
702 foreach_dlist(item, item_chain) {
703 if (last_item) {
704 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
705 free(last_item->item);
706 item_chain->remove(last_item);
707 }
708 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
709 rc = regexec(&preg, item->item, 0, NULL, 0);
710 if (rc == 0) {
711 last_item = NULL; /* keep this one */
712 } else {
713 last_item = item;
714 }
715 }
716
717 if (last_item) {
718 free(last_item->item);
719 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
720 item_chain->remove(last_item);
721 }
722
723 regfree(&preg);
724 }
725
726 if (item_chain->size() == 0) {
727 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName());
728 ok = true;
729 goto bail_out; /* skip regex match */
730 }
731
732 /*
733 * At this point, we have a list of items in item_chain
734 * that have been matched by the regex, so now we need
735 * to look up their jobids.
736 */
737 ids->count = 0;
738 foreach_dlist(item, item_chain) {
739 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
740 Mmsg(query, query2, item->item, jcr->res.rpool->name());
741 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
742 if (!jcr->db->SqlQuery(query.c_str(), UniqueDbidHandler, (void *)ids)) {
743 Jmsg(jcr, M_FATAL, 0,
744 _("SQL failed. ERR=%s\n"), jcr->db->strerror());
745 goto bail_out;
746 }
747 }
748
749 if (ids->count == 0) {
750 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName());
751 }
752
753 ok = true;
754
755 bail_out:
756 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
757 foreach_dlist(item, item_chain) {
758 free(item->item);
759 }
760 delete item_chain;
761 return ok;
762 }
763
764 /**
765 * This is the central piece of code that finds jobs actually JobIds to migrate.
766 * It examines the Selection Type to see what kind of migration we are doing
767 * (Volume, Job, Client, ...) and applies any Selection Pattern if appropriate
768 * to obtain a list of JobIds.
769 *
770 * Finally, it will loop over all the JobIds found, starting a new job with
771 * MigrationJobId set to that JobId.
772 *
773 * Returns: false - On error
774 * true - If OK
775 */
getJobs_to_migrate(JobControlRecord * jcr)776 static inline bool getJobs_to_migrate(JobControlRecord *jcr)
777 {
778 char *p;
779 int status;
780 int limit = -1;
781 bool apply_limit = false;
782 bool retval = false;
783 JobId_t JobId;
784 db_int64_ctx ctx;
785 idpkt ids, mid, jids;
786 char ed1[30], ed2[30];
787 PoolMem query(PM_MESSAGE);
788
789 ids.list = GetPoolMemory(PM_MESSAGE);
790 ids.list[0] = 0;
791 ids.count = 0;
792 mid.list = NULL;
793 jids.list = NULL;
794
795 switch (jcr->res.job->selection_type) {
796 case MT_JOB:
797 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
798 goto bail_out;
799 }
800 break;
801 case MT_CLIENT:
802 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
803 goto bail_out;
804 }
805 break;
806 case MT_VOLUME:
807 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
808 goto bail_out;
809 }
810 break;
811 case MT_SQLQUERY:
812 if (!jcr->res.job->selection_pattern) {
813 Jmsg(jcr, M_FATAL, 0, _("No %s SQL selection pattern specified.\n"), jcr->get_OperationName());
814 goto bail_out;
815 }
816 Dmsg1(dbglevel, "SQL=%s\n", jcr->res.job->selection_pattern);
817 if (!jcr->db->SqlQuery(jcr->res.job->selection_pattern,
818 UniqueDbidHandler, (void *)&ids)) {
819 Jmsg(jcr, M_FATAL, 0,
820 _("SQL failed. ERR=%s\n"), jcr->db->strerror());
821 goto bail_out;
822 }
823 break;
824 case MT_SMALLEST_VOL:
825 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
826 goto bail_out;
827 }
828 break;
829 case MT_OLDEST_VOL:
830 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
831 goto bail_out;
832 }
833 break;
834 case MT_POOL_OCCUPANCY: {
835 int64_t pool_bytes;
836 DBId_t DBId = 0;
837
838 mid.list = GetPoolMemory(PM_MESSAGE);
839 mid.list[0] = 0;
840 mid.count = 0;
841 jids.list = GetPoolMemory(PM_MESSAGE);
842 jids.list[0] = 0;
843 jids.count = 0;
844 ctx.count = 0;
845
846 /*
847 * Find count of bytes in pool
848 */
849 Mmsg(query, sql_pool_bytes, jcr->res.rpool->name());
850
851 if (!jcr->db->SqlQuery(query.c_str(), db_int64_handler, (void *)&ctx)) {
852 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), jcr->db->strerror());
853 goto bail_out;
854 }
855
856 if (ctx.count == 0) {
857 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName());
858 retval = true;
859 goto bail_out;
860 }
861
862 pool_bytes = ctx.value;
863 Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->res.rpool->MigrationHighBytes, pool_bytes);
864
865 if (pool_bytes < (int64_t)jcr->res.rpool->MigrationHighBytes) {
866 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName());
867 retval = true;
868 goto bail_out;
869 }
870
871 Dmsg0(dbglevel, "We should do Occupation migration.\n");
872
873 ids.count = 0;
874 /*
875 * Find a list of MediaIds that could be migrated
876 */
877 Mmsg(query, sql_mediaids, jcr->res.rpool->name());
878 Dmsg1(dbglevel, "query=%s\n", query.c_str());
879
880 if (!jcr->db->SqlQuery(query.c_str(), UniqueDbidHandler, (void *)&ids)) {
881 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), jcr->db->strerror());
882 goto bail_out;
883 }
884
885 if (ids.count == 0) {
886 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName());
887 retval = true;
888 goto bail_out;
889 }
890
891 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
892
893 if (!FindJobidsFromMediaidList(jcr, &ids, "Volume")) {
894 goto bail_out;
895 }
896
897 /*
898 * ids == list of jobs
899 */
900 p = ids.list;
901 for (int i = 0; i < (int)ids.count; i++) {
902 status = GetNextDbidFromList(&p, &DBId);
903 Dmsg2(dbglevel, "get_next_dbid status=%d JobId=%u\n", status, (uint32_t)DBId);
904 if (status < 0) {
905 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
906 goto bail_out;
907 } else if (status == 0) {
908 break;
909 }
910
911 mid.count = 1;
912 Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
913 if (jids.count > 0) {
914 PmStrcat(jids.list, ",");
915 }
916 PmStrcat(jids.list, mid.list);
917 jids.count += mid.count;
918
919 /*
920 * Find count of bytes from Jobs
921 */
922 Mmsg(query, sql_job_bytes, mid.list);
923 Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
924 if (!jcr->db->SqlQuery(query.c_str(), db_int64_handler, (void *)&ctx)) {
925 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), jcr->db->strerror());
926 goto bail_out;
927 }
928 pool_bytes -= ctx.value;
929 Dmsg2(dbglevel, "Total %s Job bytes=%s\n", jcr->get_ActionName(),
930 edit_int64_with_commas(ctx.value, ed1));
931 Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n",
932 edit_int64_with_commas(jcr->res.rpool->MigrationLowBytes, ed1),
933 edit_int64_with_commas(pool_bytes, ed2));
934 if (pool_bytes <= (int64_t)jcr->res.rpool->MigrationLowBytes) {
935 Dmsg0(dbglevel, "We should be done.\n");
936 break;
937 }
938 }
939
940 /*
941 * Transfer jids to ids, where the jobs list is expected
942 */
943 ids.count = jids.count;
944 PmStrcpy(ids.list, jids.list);
945 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
946 break;
947 }
948 case MT_POOL_TIME: {
949 time_t ttime;
950 char dt[MAX_TIME_LENGTH];
951
952 ttime = time(NULL) - (time_t)jcr->res.rpool->MigrationTime;
953 bstrutime(dt, sizeof(dt), ttime);
954
955 ids.count = 0;
956 Mmsg(query, sql_pool_time, jcr->res.rpool->name(), dt);
957 Dmsg1(dbglevel, "query=%s\n", query.c_str());
958
959 if (!jcr->db->SqlQuery(query.c_str(), UniqueDbidHandler, (void *)&ids)) {
960 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), jcr->db->strerror());
961 goto bail_out;
962 }
963
964 if (ids.count == 0) {
965 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName());
966 retval = true;
967 goto bail_out;
968 }
969
970 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
971 break;
972 }
973 case MT_POOL_UNCOPIED_JOBS:
974 if (!FindJobidsOfPoolUncopiedJobs(jcr, &ids)) {
975 goto bail_out;
976 }
977 break;
978 default:
979 Jmsg(jcr, M_FATAL, 0, _("Unknown %s Selection Type.\n"), jcr->get_OperationName());
980 goto bail_out;
981 }
982
983 if (ids.count == 0) {
984 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName());
985 retval = true;
986 goto bail_out;
987 }
988
989 Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s chosen to be %s: %s\n"),
990 ids.count, (ids.count < 2) ? _(" was") : _("s were"),
991 jcr->get_ActionName(true), ids.list);
992
993 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
994
995 /*
996 * Note: to not over load the system, limit the number of new jobs started.
997 */
998 if (jcr->res.job->MaxConcurrentCopies) {
999 limit = jcr->res.job->MaxConcurrentCopies;
1000 apply_limit = true;
1001 }
1002
1003 p = ids.list;
1004 for (int i = 0; i < (int)ids.count; i++) {
1005 JobId = 0;
1006 status = GetNextJobidFromList(&p, &JobId);
1007 Dmsg3(dbglevel, "getJobid_no=%d status=%d JobId=%u\n", i, status, JobId);
1008 if (status < 0) {
1009 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
1010 goto bail_out;
1011 } else if (status == 0) {
1012 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName());
1013 retval = true;
1014 goto bail_out;
1015 }
1016 jcr->MigrateJobId = JobId;
1017
1018 if (apply_limit) {
1019 /*
1020 * Don't start any more when limit reaches zero
1021 */
1022 limit--;
1023 if (limit < 0) {
1024 continue;
1025 }
1026 }
1027
1028 StartNewMigrationJob(jcr);
1029 Dmsg0(dbglevel, "Back from StartNewMigrationJob\n");
1030 }
1031
1032 jcr->HasSelectedJobs = true;
1033 retval = true;
1034
1035 bail_out:
1036 FreePoolMemory(ids.list);
1037
1038 if (mid.list) {
1039 FreePoolMemory(mid.list);
1040 }
1041
1042 if (jids.list) {
1043 FreePoolMemory(jids.list);
1044 }
1045
1046 return retval;
1047 }
1048
1049 /**
1050 * Called here before the job is run to do the job
1051 * specific setup. Note, one of the important things to
1052 * complete in this init code is to make the definitive
1053 * choice of input and output storage devices. This is
1054 * because immediately after the init, the job is queued
1055 * in the jobq.c code, and it checks that all the resources
1056 * (storage resources in particular) are available, so these
1057 * must all be properly defined.
1058 */
DoMigrationInit(JobControlRecord * jcr)1059 bool DoMigrationInit(JobControlRecord *jcr)
1060 {
1061 char ed1[100];
1062 PoolResource *pool = NULL;
1063 JobResource *job, *prev_job;
1064 JobControlRecord *mig_jcr = NULL; /* newly migrated job */
1065
1066 ApplyPoolOverrides(jcr);
1067
1068 if (!AllowDuplicateJob(jcr)) {
1069 return false;
1070 }
1071
1072 jcr->jr.PoolId = GetOrCreatePoolRecord(jcr, jcr->res.pool->name());
1073 if (jcr->jr.PoolId == 0) {
1074 Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
1075 Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
1076 return false;
1077 }
1078
1079 /*
1080 * Note, at this point, pool is the pool for this job.
1081 * We transfer it to rpool (read pool), and a bit later,
1082 * pool will be changed to point to the write pool,
1083 * which comes from pool->NextPool.
1084 */
1085 jcr->res.rpool = jcr->res.pool; /* save read pool */
1086 PmStrcpy(jcr->res.rpool_source, jcr->res.pool_source);
1087 Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->res.rpool->name(), jcr->res.rpool_source);
1088
1089 /*
1090 * See if this is a control job e.g. the one that selects the Jobs to Migrate or Copy or
1091 * one of the worker Jobs that do the actual Migration or Copy. If jcr->MigrateJobId is
1092 * set we know that its an actual Migration or Copy Job.
1093 */
1094 if (jcr->MigrateJobId != 0) {
1095 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
1096
1097 jcr->previous_jr.JobId = jcr->MigrateJobId;
1098 Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
1099
1100 if (!jcr->db->GetJobRecord(jcr, &jcr->previous_jr)) {
1101 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
1102 edit_int64(jcr->previous_jr.JobId, ed1),
1103 jcr->get_ActionName(), jcr->db->strerror());
1104 return false;
1105 }
1106
1107 Jmsg(jcr, M_INFO, 0, _("%s using JobId=%s Job=%s\n"),
1108 jcr->get_OperationName(), edit_int64(jcr->previous_jr.JobId, ed1),
1109 jcr->previous_jr.Job);
1110 Dmsg4(dbglevel, "%s JobId=%d using JobId=%s Job=%s\n",
1111 jcr->get_OperationName(), jcr->JobId,
1112 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
1113
1114 if (CreateRestoreBootstrapFile(jcr) < 0) {
1115 Jmsg(jcr, M_FATAL, 0, _("Create bootstrap file failed.\n"));
1116 return false;
1117 }
1118
1119 if (jcr->previous_jr.JobId == 0 || jcr->ExpectedFiles == 0) {
1120 jcr->setJobStatus(JS_Terminated);
1121 Dmsg1(dbglevel, "JobId=%d expected files == 0\n", (int)jcr->JobId);
1122 if (jcr->previous_jr.JobId == 0) {
1123 Jmsg(jcr, M_INFO, 0, _("No previous Job found to %s.\n"), jcr->get_ActionName());
1124 } else {
1125 Jmsg(jcr, M_INFO, 0, _("Previous Job has no data to %s.\n"), jcr->get_ActionName());
1126 }
1127 SetMigrationNextPool(jcr, &pool);
1128 return true; /* no work */
1129 }
1130
1131 Dmsg5(dbglevel, "JobId=%d: Current: Name=%s JobId=%d Type=%c Level=%c\n",
1132 (int)jcr->JobId, jcr->jr.Name, (int)jcr->jr.JobId, jcr->jr.JobType, jcr->jr.JobLevel);
1133
1134 job = (JobResource *)my_config->GetResWithName(R_JOB, jcr->jr.Name);
1135 prev_job = (JobResource *)my_config->GetResWithName(R_JOB, jcr->previous_jr.Name);
1136
1137 if (!job) {
1138 Jmsg(jcr, M_FATAL, 0, _("Job resource not found for \"%s\".\n"), jcr->jr.Name);
1139 return false;
1140 }
1141
1142 if (!prev_job) {
1143 Jmsg(jcr, M_FATAL, 0, _("Previous Job resource not found for \"%s\".\n"),
1144 jcr->previous_jr.Name);
1145 return false;
1146 }
1147
1148 /*
1149 * Copy the actual level setting of the previous Job to this Job.
1150 * This overrides the dummy backup level given to the migrate/copy Job and replaces it
1151 * with the actual level the backup run at.
1152 */
1153 jcr->setJobLevel(prev_job->JobLevel);
1154
1155 /*
1156 * If the current Job has no explicit client set use the client setting of the previous Job.
1157 */
1158 if (!jcr->res.client && prev_job->client) {
1159 jcr->res.client = prev_job->client;
1160 if (!jcr->client_name) {
1161 jcr->client_name = GetPoolMemory(PM_NAME);
1162 }
1163 PmStrcpy(jcr->client_name, jcr->res.client->hdr.name);
1164 }
1165
1166 /*
1167 * If the current Job has no explicit fileset set use the client setting of the previous Job.
1168 */
1169 if (!jcr->res.fileset) {
1170 jcr->res.fileset = prev_job->fileset;
1171 }
1172
1173 /*
1174 * See if spooling data is not enabled yet. If so turn on spooling if requested in job
1175 */
1176 if (!jcr->spool_data) {
1177 jcr->spool_data = job->spool_data;
1178 }
1179
1180 /*
1181 * Create a migration jcr
1182 */
1183 mig_jcr = new_jcr(sizeof(JobControlRecord), DirdFreeJcr);
1184 jcr->mig_jcr = mig_jcr;
1185 memcpy(&mig_jcr->previous_jr, &jcr->previous_jr, sizeof(mig_jcr->previous_jr));
1186
1187 /*
1188 * Turn the mig_jcr into a "real" job that takes on the aspects of
1189 * the previous backup job "prev_job". We only don't want it to
1190 * ever send any messages to the database or mail messages when
1191 * we are doing a migrate or copy to a remote storage daemon. When
1192 * doing such operations the mig_jcr is used for tracking some of
1193 * the remote state and it might want to send some captured state
1194 * info on tear down of the mig_jcr so we call SetupJob with the
1195 * suppress_output argument set to true (e.g. don't init messages
1196 * and set the jcr suppress_output boolean to true).
1197 */
1198 SetJcrDefaults(mig_jcr, prev_job);
1199
1200 /*
1201 * Don't let Watchdog checks Max*Time value on this Job
1202 */
1203 mig_jcr->no_maxtime = true;
1204
1205 /*
1206 * Don't check for duplicates on migration and copy jobs
1207 */
1208 mig_jcr->IgnoreDuplicateJobChecking = true;
1209
1210 /*
1211 * Copy some overwrites back from the Control Job to the migration and copy job.
1212 */
1213 mig_jcr->spool_data = jcr->spool_data;
1214 mig_jcr->spool_size = jcr->spool_size;
1215
1216
1217 if (!SetupJob(mig_jcr, true)) {
1218 Jmsg(jcr, M_FATAL, 0, _("setup job failed.\n"));
1219 return false;
1220 }
1221
1222 /*
1223 * Keep track that the mig_jcr has a controlling JobControlRecord.
1224 */
1225 mig_jcr->cjcr = jcr;
1226
1227 /*
1228 * Now reset the job record from the previous job
1229 */
1230 memcpy(&mig_jcr->jr, &jcr->previous_jr, sizeof(mig_jcr->jr));
1231
1232 /*
1233 * Update the jr to reflect the new values of PoolId and JobId.
1234 */
1235 mig_jcr->jr.PoolId = jcr->jr.PoolId;
1236 mig_jcr->jr.JobId = mig_jcr->JobId;
1237
1238 if (SetMigrationNextPool(jcr, &pool)) {
1239 /*
1240 * If pool storage specified, use it as source
1241 */
1242 CopyRstorage(mig_jcr, pool->storage, _("Pool resource"));
1243 CopyRstorage(jcr, pool->storage, _("Pool resource"));
1244
1245 mig_jcr->res.pool = jcr->res.pool;
1246 mig_jcr->res.next_pool = jcr->res.next_pool;
1247 mig_jcr->jr.PoolId = jcr->jr.PoolId;
1248 }
1249
1250 /*
1251 * Get the storage that was used for the original Job.
1252 * This only happens when the original pool used doesn't have an explicit storage.
1253 */
1254 if (!jcr->res.read_storage_list) {
1255 CopyRstorage(jcr, prev_job->storage, _("previous Job"));
1256 }
1257
1258 /*
1259 * See if the read and write storage is the same.
1260 * When they are we do the migrate/copy over one SD connection
1261 * otherwise we open a connection to the reading SD and a second
1262 * one to the writing SD.
1263 */
1264 jcr->remote_replicate = !IsSameStorageDaemon(jcr->res.read_storage, jcr->res.write_storage);
1265
1266 /*
1267 * set the JobLevel to what the original job was
1268 */
1269 mig_jcr->setJobLevel(mig_jcr->previous_jr.JobLevel);
1270
1271
1272 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
1273 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId, mig_jcr->jr.JobType,
1274 mig_jcr->jr.JobLevel);
1275
1276 }
1277
1278 return true;
1279 }
1280
1281 /**
1282 * Do a Migration of a previous job
1283 *
1284 * - previous_jr refers to the job DB record of the Job that is
1285 * going to be migrated.
1286 * - prev_job refers to the job resource of the Job that is
1287 * going to be migrated.
1288 * - jcr is the jcr for the current "migration" job. It is a
1289 * control job that is put in the DB as a migration job, which
1290 * means that this job migrated a previous job to a new job.
1291 * No Volume or File data is associated with this control
1292 * job.
1293 * - mig_jcr refers to the newly migrated job that is run by
1294 * the current jcr. It is a backup job that moves (migrates) the
1295 * data written for the previous_jr into the new pool. This
1296 * job (mig_jcr) becomes the new backup job that replaces
1297 * the original backup job. Note, when this is a migration
1298 * on a single storage daemon this jcr is not really run. It
1299 * is simply attached to the current jcr. It will show up in
1300 * the Director's status output, but not in the SD or FD, both of
1301 * which deal only with the current migration job (i.e. jcr).
1302 * When this is is a migration between two storage daemon this
1303 * mig_jcr is used to control the second connection to the
1304 * remote storage daemon.
1305 *
1306 * Returns: false on failure
1307 * true on success
1308 */
DoActualMigration(JobControlRecord * jcr)1309 static inline bool DoActualMigration(JobControlRecord *jcr)
1310 {
1311 char ed1[100];
1312 bool retval = false;
1313 JobControlRecord *mig_jcr = jcr->mig_jcr;
1314
1315 ASSERT(mig_jcr);
1316
1317 /*
1318 * Make sure this job was not already migrated
1319 */
1320 if (jcr->previous_jr.JobType != JT_BACKUP &&
1321 jcr->previous_jr.JobType != JT_JOB_COPY) {
1322 Jmsg(jcr, M_INFO, 0, _("JobId %s already %s probably by another Job. %s stopped.\n"),
1323 edit_int64(jcr->previous_jr.JobId, ed1),
1324 jcr->get_ActionName(true),
1325 jcr->get_OperationName());
1326 jcr->setJobStatus(JS_Terminated);
1327 MigrationCleanup(jcr, jcr->JobStatus);
1328 return true;
1329 }
1330
1331 if (SameStorage(jcr)) {
1332 Jmsg(jcr, M_FATAL, 0, _("JobId %s cannot %s using the same read and write storage.\n"),
1333 edit_int64(jcr->previous_jr.JobId, ed1),
1334 jcr->get_OperationName());
1335 jcr->setJobStatus(JS_Terminated);
1336 MigrationCleanup(jcr, jcr->JobStatus);
1337 return true;
1338 }
1339
1340 /*
1341 * Print Job Start message
1342 */
1343 Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
1344 jcr->get_OperationName(), edit_uint64(jcr->JobId, ed1), jcr->Job);
1345
1346 /*
1347 * See if the read storage is paired NDMP storage, if so setup
1348 * the Job to use the native storage instead.
1349 */
1350 if (HasPairedStorage(jcr)) {
1351 SetPairedStorage(jcr);
1352 }
1353
1354 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
1355 ((StorageResource *)jcr->res.read_storage_list->first())->name(),
1356 ((StorageResource *)jcr->res.write_storage_list->first())->name());
1357
1358 if (jcr->remote_replicate) {
1359
1360 alist *write_storage_list;
1361
1362 /*
1363 * See if we need to apply any bandwidth limiting.
1364 * We search the bandwidth limiting in the following way:
1365 * - Job bandwidth limiting
1366 * - Writing Storage Daemon bandwidth limiting
1367 * - Reading Storage Daemon bandwidth limiting
1368 */
1369 if (jcr->res.job->max_bandwidth > 0) {
1370 jcr->max_bandwidth = jcr->res.job->max_bandwidth;
1371 } else if (jcr->res.write_storage->max_bandwidth > 0) {
1372 jcr->max_bandwidth = jcr->res.write_storage->max_bandwidth;
1373 } else if (jcr->res.read_storage->max_bandwidth > 0) {
1374 jcr->max_bandwidth = jcr->res.read_storage->max_bandwidth;
1375 }
1376
1377 /*
1378 * Open a message channel connection to the Reading Storage daemon.
1379 */
1380 Dmsg0(110, "Open connection with reading storage daemon\n");
1381
1382 /*
1383 * Clear the write_storage of the jcr and assign it to the mig_jcr so
1384 * the jcr is connected to the reading storage daemon and the
1385 * mig_jcr to the writing storage daemon.
1386 */
1387 mig_jcr->res.write_storage = jcr->res.write_storage;
1388 jcr->res.write_storage = NULL;
1389
1390 /*
1391 * Swap the write_storage_list between the jcr and the mig_jcr.
1392 */
1393 write_storage_list = mig_jcr->res.write_storage_list;
1394 mig_jcr->res.write_storage_list = jcr->res.write_storage_list;
1395 jcr->res.write_storage_list = write_storage_list;
1396
1397 /*
1398 * Start conversation with Reading Storage daemon
1399 */
1400 jcr->setJobStatus(JS_WaitSD);
1401 if (!ConnectToStorageDaemon(jcr, 10, me->SDConnectTimeout, true)) {
1402 goto bail_out;
1403 }
1404
1405 /*
1406 * Open a message channel connection with the Writing Storage daemon.
1407 */
1408 Dmsg0(110, "Open connection with writing storage daemon\n");
1409
1410 /*
1411 * Start conversation with Writing Storage daemon
1412 */
1413 mig_jcr->setJobStatus(JS_WaitSD);
1414 if (!ConnectToStorageDaemon(mig_jcr, 10, me->SDConnectTimeout, true)) {
1415 goto bail_out;
1416 }
1417
1418 /*
1419 * Now start a job with the Reading Storage daemon
1420 */
1421 if (!StartStorageDaemonJob(jcr, jcr->res.read_storage_list, NULL, /* send_bsr */ true)) {
1422 goto bail_out;
1423 }
1424
1425 Dmsg0(150, "Reading Storage daemon connection OK\n");
1426
1427 /*
1428 * Now start a job with the Writing Storage daemon
1429 */
1430
1431 if (!StartStorageDaemonJob(mig_jcr, NULL, mig_jcr->res.write_storage_list, /* send_bsr */ false)) {
1432 goto bail_out;
1433 }
1434
1435 Dmsg0(150, "Writing Storage daemon connection OK\n");
1436
1437 } else { /* local replicate */
1438
1439 /*
1440 * Open a message channel connection with the Storage daemon.
1441 */
1442 Dmsg0(110, "Open connection with storage daemon\n");
1443 jcr->setJobStatus(JS_WaitSD);
1444 mig_jcr->setJobStatus(JS_WaitSD);
1445
1446 /*
1447 * Start conversation with Storage daemon
1448 */
1449 if (!ConnectToStorageDaemon(jcr, 10, me->SDConnectTimeout, true)) {
1450 FreePairedStorage(jcr);
1451 return false;
1452 }
1453
1454 /*
1455 * Now start a job with the Storage daemon
1456 */
1457 if (!StartStorageDaemonJob(jcr, jcr->res.read_storage_list, jcr->res.write_storage_list, /* send_bsr */ true)) {
1458 FreePairedStorage(jcr);
1459 return false;
1460 }
1461
1462 Dmsg0(150, "Storage daemon connection OK\n");
1463 }
1464
1465 /*
1466 * We re-update the job start record so that the start
1467 * time is set after the run before job. This avoids
1468 * that any files created by the run before job will
1469 * be saved twice. They will be backed up in the current
1470 * job, but not in the next one unless they are changed.
1471 * Without this, they will be backed up in this job and
1472 * in the next job run because in that case, their date
1473 * is after the start of this run.
1474 */
1475 jcr->start_time = time(NULL);
1476 jcr->jr.StartTime = jcr->start_time;
1477 jcr->jr.JobTDate = jcr->start_time;
1478 jcr->setJobStatus(JS_Running);
1479
1480 /*
1481 * Update job start record for this migration control job
1482 */
1483 if (!jcr->db->UpdateJobStartRecord(jcr, &jcr->jr)) {
1484 Jmsg(jcr, M_FATAL, 0, "%s", jcr->db->strerror());
1485 goto bail_out;
1486 }
1487
1488 /*
1489 * Declare the job started to start the MaxRunTime check
1490 */
1491 jcr->setJobStarted();
1492
1493 mig_jcr->start_time = time(NULL);
1494 mig_jcr->jr.StartTime = mig_jcr->start_time;
1495 mig_jcr->jr.JobTDate = mig_jcr->start_time;
1496 mig_jcr->setJobStatus(JS_Running);
1497
1498 /*
1499 * Update job start record for the real migration backup job
1500 */
1501 if (!mig_jcr->db->UpdateJobStartRecord(mig_jcr, &mig_jcr->jr)) {
1502 Jmsg(jcr, M_FATAL, 0, "%s", mig_jcr->db->strerror());
1503 goto bail_out;
1504 }
1505
1506 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
1507 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
1508 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
1509
1510 /*
1511 * If we are connected to two different SDs tell the writing one
1512 * to be ready to receive the data and tell the reading one
1513 * to replicate to the other.
1514 */
1515 if (jcr->remote_replicate) {
1516 StorageResource *write_storage = mig_jcr->res.write_storage;
1517 StorageResource *read_storage = jcr->res.read_storage;
1518 PoolMem command(PM_MESSAGE);
1519 uint32_t tls_need = 0;
1520
1521 if (jcr->max_bandwidth > 0) {
1522 SendBwlimitToSd(jcr, jcr->Job);
1523 }
1524
1525 /*
1526 * Start the job prior to starting the message thread below
1527 * to avoid two threads from using the BareosSocket structure at
1528 * the same time.
1529 */
1530 if (!mig_jcr->store_bsock->fsend("listen")) {
1531 goto bail_out;
1532 }
1533
1534 if (!StartStorageDaemonMessageThread(mig_jcr)) {
1535 goto bail_out;
1536 }
1537
1538 /*
1539 * Send Storage daemon address to the other Storage daemon
1540 */
1541 if (write_storage->SDDport == 0) {
1542 write_storage->SDDport = write_storage->SDport;
1543 }
1544
1545 /*
1546 * TLS Requirement
1547 */
1548 tls_need = write_storage->IsTlsConfigured() ? TlsPolicy::kBnetTlsAuto : TlsPolicy::kBnetTlsNone;
1549
1550 char *connection_target_address = StorageAddressToContact(read_storage, write_storage);
1551
1552 Mmsg(command, replicatecmd, mig_jcr->JobId, mig_jcr->Job, connection_target_address,
1553 write_storage->SDDport, tls_need, mig_jcr->sd_auth_key);
1554
1555 if (!jcr->store_bsock->fsend(command.c_str())) {
1556 goto bail_out;
1557 }
1558
1559 if (jcr->store_bsock->recv() <= 0) {
1560 goto bail_out;
1561 }
1562
1563 std::string OK_replicate {"3000 OK replicate\n"};
1564 std::string received = jcr->store_bsock->msg;
1565 if (received != OK_replicate) {
1566 goto bail_out;
1567 }
1568 }
1569
1570 /*
1571 * Start the job prior to starting the message thread below
1572 * to avoid two threads from using the BareosSocket structure at
1573 * the same time.
1574 */
1575 if (!jcr->store_bsock->fsend("run")) {
1576 goto bail_out;
1577 }
1578
1579 /*
1580 * Now start a Storage daemon message thread
1581 */
1582 if (!StartStorageDaemonMessageThread(jcr)) {
1583 goto bail_out;
1584 }
1585
1586 jcr->setJobStatus(JS_Running);
1587 mig_jcr->setJobStatus(JS_Running);
1588
1589 /*
1590 * Pickup Job termination data
1591 * Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors or
1592 * mig_jcr->JobFiles/ReadBytes/JobBytes/JobErrors when replicating to
1593 * a remote storage daemon.
1594 */
1595 if (jcr->remote_replicate) {
1596 WaitForStorageDaemonTermination(jcr);
1597 WaitForStorageDaemonTermination(mig_jcr);
1598 jcr->setJobStatus(jcr->SDJobStatus);
1599 mig_jcr->db_batch->WriteBatchFileRecords(mig_jcr);
1600 } else {
1601 WaitForStorageDaemonTermination(jcr);
1602 jcr->setJobStatus(jcr->SDJobStatus);
1603 jcr->db_batch->WriteBatchFileRecords(jcr);
1604 }
1605
1606 bail_out:
1607 if (jcr->remote_replicate && mig_jcr) {
1608 alist *write_storage_list;
1609
1610 /*
1611 * Swap the write_storage_list between the jcr and the mig_jcr.
1612 */
1613 write_storage_list = mig_jcr->res.write_storage_list;
1614 mig_jcr->res.write_storage_list = jcr->res.write_storage_list;
1615 jcr->res.write_storage_list = write_storage_list;
1616
1617 /*
1618 * Undo the clear of the write_storage in the jcr and assign the mig_jcr write_storage
1619 * back to the jcr. This is an undo of the clearing we did earlier
1620 * as we want the jcr connected to the reading storage daemon and the
1621 * mig_jcr to the writing jcr. By clearing the write_storage of the jcr the
1622 * ConnectToStorageDaemon function will do the right thing e.g. connect
1623 * the jcrs in the way we want them to.
1624 */
1625 jcr->res.write_storage = mig_jcr->res.write_storage;
1626 mig_jcr->res.write_storage = NULL;
1627 }
1628
1629 FreePairedStorage(jcr);
1630
1631 if (jcr->is_JobStatus(JS_Terminated)) {
1632 MigrationCleanup(jcr, jcr->JobStatus);
1633 retval = true;
1634 }
1635
1636 return retval;
1637 }
1638
1639 /**
1640 * Select the Jobs to Migrate/Copy using the getJobs_to_migrate function and then exit.
1641 */
DoMigrationSelection(JobControlRecord * jcr)1642 static inline bool DoMigrationSelection(JobControlRecord *jcr)
1643 {
1644 bool retval;
1645
1646 retval = getJobs_to_migrate(jcr);
1647 if (retval) {
1648 jcr->setJobStatus(JS_Terminated);
1649 MigrationCleanup(jcr, jcr->JobStatus);
1650 } else {
1651 jcr->setJobStatus(JS_ErrorTerminated);
1652 }
1653
1654 return retval;
1655 }
1656
DoMigration(JobControlRecord * jcr)1657 bool DoMigration(JobControlRecord *jcr)
1658 {
1659 /*
1660 * See if this is a control job e.g. the one that selects the Jobs to Migrate or Copy or
1661 * one of the worker Jobs that do the actual Migration or Copy. If jcr->MigrateJobId is
1662 * unset we know that its the control job.
1663 */
1664 if (jcr->MigrateJobId == 0) {
1665 return DoMigrationSelection(jcr);
1666 } else {
1667 return DoActualMigration(jcr);
1668 }
1669 }
1670
GenerateMigrateSummary(JobControlRecord * jcr,MediaDbRecord * mr,int msg_type,const char * TermMsg)1671 static inline void GenerateMigrateSummary(JobControlRecord *jcr, MediaDbRecord *mr, int msg_type, const char *TermMsg)
1672 {
1673 double kbps;
1674 utime_t RunTime;
1675 JobControlRecord *mig_jcr = jcr->mig_jcr;
1676 char term_code[100], sd_term_msg[100];
1677 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
1678 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
1679 char ec6[50], ec7[50], ec8[50];
1680
1681 Bsnprintf(term_code, sizeof(term_code), TermMsg, jcr->get_OperationName(), jcr->get_ActionName());
1682 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1683 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1684 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1685
1686 JobstatusToAscii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1687 if (jcr->previous_jr.JobId != 0) {
1688 /*
1689 * Copy/Migrate worker Job.
1690 */
1691 if (RunTime <= 0) {
1692 kbps = 0;
1693 } else {
1694 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1695 }
1696
1697 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
1698 " Build OS: %s %s %s\n"
1699 " Prev Backup JobId: %s\n"
1700 " Prev Backup Job: %s\n"
1701 " New Backup JobId: %s\n"
1702 " Current JobId: %s\n"
1703 " Current Job: %s\n"
1704 " Backup Level: %s\n"
1705 " Client: %s\n"
1706 " FileSet: \"%s\"\n"
1707 " Read Pool: \"%s\" (From %s)\n"
1708 " Read Storage: \"%s\" (From %s)\n"
1709 " Write Pool: \"%s\" (From %s)\n"
1710 " Write Storage: \"%s\" (From %s)\n"
1711 " Next Pool: \"%s\" (From %s)\n"
1712 " Catalog: \"%s\" (From %s)\n"
1713 " Start time: %s\n"
1714 " End time: %s\n"
1715 " Elapsed time: %s\n"
1716 " Priority: %d\n"
1717 " SD Files Written: %s\n"
1718 " SD Bytes Written: %s (%sB)\n"
1719 " Rate: %.1f KB/s\n"
1720 " Volume name(s): %s\n"
1721 " Volume Session Id: %d\n"
1722 " Volume Session Time: %d\n"
1723 " Last Volume Bytes: %s (%sB)\n"
1724 " SD Errors: %d\n"
1725 " SD termination status: %s\n"
1726 " Bareos binary info: %s\n"
1727 " Termination: %s\n\n"),
1728 BAREOS, my_name, VERSION, LSMDATE,
1729 HOST_OS, DISTNAME, DISTVER,
1730 edit_uint64(jcr->previous_jr.JobId, ec6),
1731 jcr->previous_jr.Job,
1732 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : _("*None*"),
1733 edit_uint64(jcr->jr.JobId, ec8),
1734 jcr->jr.Job,
1735 level_to_str(jcr->getJobLevel()),
1736 jcr->res.client ? jcr->res.client->name() : _("*None*"),
1737 jcr->res.fileset ? jcr->res.fileset->name() : _("*None*"),
1738 jcr->res.rpool->name(), jcr->res.rpool_source,
1739 jcr->res.read_storage ? jcr->res.read_storage->name() : _("*None*"),
1740 NPRT(jcr->res.rstore_source),
1741 jcr->res.pool->name(), jcr->res.pool_source,
1742 jcr->res.write_storage ? jcr->res.write_storage->name() : _("*None*"),
1743 NPRT(jcr->res.wstore_source),
1744 jcr->res.next_pool ? jcr->res.next_pool->name() : _("*None*"),
1745 NPRT(jcr->res.npool_source),
1746 jcr->res.catalog->name(), jcr->res.catalog_source,
1747 sdt,
1748 edt,
1749 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1750 jcr->JobPriority,
1751 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1752 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1753 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1754 (float)kbps,
1755 mig_jcr ? mig_jcr->VolumeName : _("*None*"),
1756 jcr->VolSessionId,
1757 jcr->VolSessionTime,
1758 edit_uint64_with_commas(mr->VolBytes, ec4),
1759 edit_uint64_with_suffix(mr->VolBytes, ec5),
1760 jcr->SDErrors,
1761 sd_term_msg,
1762 BAREOS_JOBLOG_MESSAGE,
1763 term_code);
1764 } else {
1765 /*
1766 * Copy/Migrate selection only Job.
1767 */
1768 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
1769 " Build OS: %s %s %s\n"
1770 " Current JobId: %s\n"
1771 " Current Job: %s\n"
1772 " Catalog: \"%s\" (From %s)\n"
1773 " Start time: %s\n"
1774 " End time: %s\n"
1775 " Elapsed time: %s\n"
1776 " Priority: %d\n"
1777 " Bareos binary info: %s\n"
1778 " Termination: %s\n\n"),
1779 BAREOS, my_name, VERSION, LSMDATE,
1780 HOST_OS, DISTNAME, DISTVER,
1781 edit_uint64(jcr->jr.JobId, ec8),
1782 jcr->jr.Job,
1783 jcr->res.catalog->name(), jcr->res.catalog_source,
1784 sdt,
1785 edt,
1786 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1787 jcr->JobPriority,
1788 BAREOS_JOBLOG_MESSAGE,
1789 term_code);
1790 }
1791 }
1792
1793 /**
1794 * Release resources allocated during backup.
1795 */
MigrationCleanup(JobControlRecord * jcr,int TermCode)1796 void MigrationCleanup(JobControlRecord *jcr, int TermCode)
1797 {
1798 char ec1[30];
1799 const char *TermMsg;
1800 int msg_type = M_INFO;
1801 MediaDbRecord mr;
1802 JobControlRecord *mig_jcr = jcr->mig_jcr;
1803 PoolMem query(PM_MESSAGE);
1804
1805 memset(&mr, 0, sizeof(mr));
1806 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
1807 UpdateJobEnd(jcr, TermCode);
1808
1809 /*
1810 * Check if we actually did something.
1811 * mig_jcr is jcr of the newly migrated job.
1812 */
1813 if (mig_jcr) {
1814 char old_jobid[50], new_jobid[50];
1815
1816 edit_uint64(jcr->previous_jr.JobId, old_jobid);
1817 edit_uint64(mig_jcr->jr.JobId, new_jobid);
1818
1819 /*
1820 * See if we used a remote SD if so the mig_jcr contains
1821 * the jobfiles and jobbytes and the new volsessionid
1822 * and volsessiontime as the writing SD generates this info.
1823 */
1824 if (jcr->remote_replicate) {
1825 mig_jcr->JobFiles = jcr->JobFiles = mig_jcr->SDJobFiles;
1826 mig_jcr->JobBytes = jcr->JobBytes = mig_jcr->SDJobBytes;
1827 } else {
1828 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
1829 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
1830 mig_jcr->VolSessionId = jcr->VolSessionId;
1831 mig_jcr->VolSessionTime = jcr->VolSessionTime;
1832 }
1833 mig_jcr->jr.RealEndTime = 0;
1834 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
1835
1836 if (jcr->is_JobStatus(JS_Terminated) &&
1837 (jcr->JobErrors || jcr->SDErrors)) {
1838 TermCode = JS_Warnings;
1839 }
1840
1841 UpdateJobEnd(mig_jcr, TermCode);
1842
1843 /*
1844 * Update final items to set them to the previous job's values
1845 */
1846 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
1847 "JobTDate=%s WHERE JobId=%s",
1848 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
1849 edit_uint64(jcr->previous_jr.JobTDate, ec1),
1850 new_jobid);
1851 jcr->db->SqlQuery(query.c_str());
1852
1853 if (jcr->IsTerminatedOk()) {
1854 UaContext *ua;
1855
1856 switch (jcr->getJobType()) {
1857 case JT_MIGRATE:
1858 /*
1859 * If we terminated a Migration Job successfully we should:
1860 * - Mark the previous job as migrated
1861 * - Move any Log records to the new JobId
1862 * - Move any MetaData of a NDMP backup
1863 * - Purge the File records from the previous job
1864 */
1865 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1866 (char)JT_MIGRATED_JOB, old_jobid);
1867 mig_jcr->db->SqlQuery(query.c_str());
1868
1869 /*
1870 * Move JobLog to new JobId
1871 */
1872 Mmsg(query, "UPDATE Log SET JobId=%s WHERE JobId=%s",
1873 new_jobid, old_jobid);
1874 mig_jcr->db->SqlQuery(query.c_str());
1875
1876 /*
1877 * If we just migrated a NDMP job, we need to move the file MetaData
1878 * to the new job. The file MetaData is stored as hardlinks to the
1879 * NDMP archive itself. And as we only clone the actual data in the
1880 * storage daemon we need to add data normally send to the director
1881 * via the FHDB interface here.
1882 */
1883 switch (jcr->res.client->Protocol) {
1884 case APT_NDMPV2:
1885 case APT_NDMPV3:
1886 case APT_NDMPV4:
1887 Mmsg(query, sql_migrate_ndmp_metadata, new_jobid, old_jobid, new_jobid);
1888 mig_jcr->db->SqlQuery(query.c_str());
1889 break;
1890 default:
1891 break;
1892 }
1893
1894 ua = new_ua_context(jcr);
1895 if (jcr->res.job->PurgeMigrateJob) {
1896 /*
1897 * Purge old Job record
1898 */
1899 PurgeJobsFromCatalog(ua, old_jobid);
1900 } else {
1901 /*
1902 * Purge all old file records, but leave Job record
1903 */
1904 PurgeFilesFromJobs(ua, old_jobid);
1905 }
1906
1907 FreeUaContext(ua);
1908 break;
1909 case JT_COPY:
1910 /*
1911 * If we terminated a Copy Job successfully we should:
1912 * - Copy any Log records to the new JobId
1913 * - Copy any MetaData of a NDMP backup
1914 * - Set type="Job Copy" for the new job
1915 */
1916 Mmsg(query, "INSERT INTO Log (JobId, Time, LogText ) "
1917 "SELECT %s, Time, LogText FROM Log WHERE JobId=%s",
1918 new_jobid, old_jobid);
1919 mig_jcr->db->SqlQuery(query.c_str());
1920
1921 /*
1922 * If we just copied a NDMP job, we need to copy the file MetaData
1923 * to the new job. The file MetaData is stored as hardlinks to the
1924 * NDMP archive itself. And as we only clone the actual data in the
1925 * storage daemon we need to add data normally send to the director
1926 * via the FHDB interface here.
1927 */
1928 switch (jcr->res.client->Protocol) {
1929 case APT_NDMPV2:
1930 case APT_NDMPV3:
1931 case APT_NDMPV4:
1932 Mmsg(query, sql_copy_ndmp_metadata, new_jobid, old_jobid, new_jobid);
1933 mig_jcr->db->SqlQuery(query.c_str());
1934 break;
1935 default:
1936 break;
1937 }
1938
1939 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1940 (char)JT_JOB_COPY, new_jobid);
1941 mig_jcr->db->SqlQuery(query.c_str());
1942 break;
1943 default:
1944 break;
1945 }
1946 }
1947
1948 if (!jcr->db->GetJobRecord(jcr, &jcr->jr)) {
1949 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"), jcr->db->strerror());
1950 jcr->setJobStatus(JS_ErrorTerminated);
1951 }
1952
1953 UpdateBootstrapFile(mig_jcr);
1954
1955 if (!mig_jcr->db->GetJobVolumeNames(mig_jcr, mig_jcr->jr.JobId, mig_jcr->VolumeName)) {
1956 /*
1957 * Note, if the job has failed, most likely it did not write any
1958 * tape, so suppress this "error" message since in that case
1959 * it is normal. Or look at it the other way, only for a
1960 * normal exit should we complain about this error.
1961 */
1962 if (jcr->IsTerminatedOk() && jcr->jr.JobBytes) {
1963 Jmsg(jcr, M_ERROR, 0, "%s", mig_jcr->db->strerror());
1964 }
1965 mig_jcr->VolumeName[0] = 0; /* none */
1966 }
1967
1968 if (mig_jcr->VolumeName[0]) {
1969 /*
1970 * Find last volume name. Multiple vols are separated by |
1971 */
1972 char *p = strrchr(mig_jcr->VolumeName, '|');
1973 if (p) {
1974 p++; /* skip | */
1975 } else {
1976 p = mig_jcr->VolumeName; /* no |, take full name */
1977 }
1978 bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1979 if (!jcr->db->GetMediaRecord(jcr, &mr)) {
1980 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1981 mr.VolumeName, jcr->db->strerror());
1982 }
1983 }
1984
1985 switch (jcr->JobStatus) {
1986 case JS_Terminated:
1987 TermMsg = _("%s OK");
1988 break;
1989 case JS_Warnings:
1990 TermMsg = _("%s OK -- with warnings");
1991 break;
1992 case JS_FatalError:
1993 case JS_ErrorTerminated:
1994 case JS_Canceled:
1995 /*
1996 * We catch any error here as the close of the SD sessions is mandatory for each
1997 * failure path. The termination message and the message type can be different
1998 * so that is why we do a second switch inside the switch on the JobStatus.
1999 */
2000 switch (jcr->JobStatus) {
2001 case JS_Canceled:
2002 TermMsg = _("%s Canceled");
2003 break;
2004 default:
2005 TermMsg = _("*** %s Error ***");
2006 msg_type = M_ERROR; /* Generate error message */
2007 break;
2008 }
2009
2010 /*
2011 * Close connection to Reading SD.
2012 */
2013 if (jcr->store_bsock) {
2014 jcr->store_bsock->signal(BNET_TERMINATE);
2015 if (jcr->SD_msg_chan_started) {
2016 pthread_cancel(jcr->SD_msg_chan);
2017 }
2018 }
2019
2020 /*
2021 * Close connection to Writing SD (if SD-SD replication)
2022 */
2023 if (mig_jcr->store_bsock) {
2024 mig_jcr->store_bsock->signal(BNET_TERMINATE);
2025 if (mig_jcr->SD_msg_chan_started) {
2026 pthread_cancel(mig_jcr->SD_msg_chan);
2027 }
2028 }
2029 break;
2030 default:
2031 TermMsg = _("Inappropriate %s term code");
2032 break;
2033 }
2034 } else if (jcr->HasSelectedJobs) {
2035 switch (jcr->JobStatus) {
2036 case JS_Terminated:
2037 TermMsg = _("%s OK");
2038 break;
2039 case JS_Warnings:
2040 TermMsg = _("%s OK -- with warnings");
2041 break;
2042 case JS_FatalError:
2043 case JS_ErrorTerminated:
2044 TermMsg = _("*** %s Error ***");
2045 msg_type = M_ERROR; /* Generate error message */
2046 break;
2047 case JS_Canceled:
2048 TermMsg = _("%s Canceled");
2049 break;
2050 default:
2051 TermMsg = _("Inappropriate %s term code");
2052 break;
2053 }
2054 } else {
2055 TermMsg = _("%s -- no files to %s");
2056 }
2057
2058 GenerateMigrateSummary(jcr, &mr, msg_type, TermMsg);
2059
2060 Dmsg0(100, "Leave migrate_cleanup()\n");
2061 }
2062 } /* namespace directordaemon */
2063