// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2012 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC. If not, see .
// vdad - volunteer data archival daemon
//
// Enumerates files needing updating from the DB.
// Creates the corresponding tree of META_CHUNKs, CHUNKs,
// and VDA_CHUNK_HOSTs.
// Calls the recovery routines to initiate transfers,
// update the DB, etc.
#include
#include
#include
#include
using std::vector;
using std::set;
#include "boinc_db.h"
#include "sched_config.h"
#include "sched_util.h"
#include "md5_file.h"
#include "error_numbers.h"
#include "util.h"
#include "filesys.h"
#include "vda_lib.h"
// return the name of a file created by Jerasure's encoder
//
// encoder creates files with names of the form
// Coding/fname_k01.ext
// Coding/fname_m01.ext
//
// Assume there's no extension
//
void encoder_filename(
const char* base, const char* ext, CODING& c, int i, char* buf
) {
int ndigits = 1;
if (c.m > 9) ndigits = 2;
else if (c.m > 99) ndigits = 3;
else if (c.m > 999) ndigits = 4;
int j;
char ch;
if (i >= c.n) {
j = i-c.n + 1;
ch = 'm';
} else {
j = i+1;
ch = 'k';
}
sprintf(buf, "%s_%c%0*d.%s", base, ch, ndigits, j, ext);
}
#define DATA_FILENAME "data.vda"
// encode a meta-chunk.
// precondition: "dir" contains a file "data.vda".
// postcondition: dir contains
// a subdir Coding with encoded chunks
// subdirs 0/ .. m/
// each containing a symbolic link "data.vda" to the corresponding chunk
//
// The size of these chunks is returned in "size"
//
int encode(const char* dir, CODING& c, double& size) {
char cmd[1024];
sprintf(cmd,
"cd %s; /mydisks/b/users/boincadm/vda_test/encoder %s %d %d cauchy_good 32 1024 500000",
dir, DATA_FILENAME, c.n, c.k
);
printf("%s\n", cmd);
int s = system(cmd);
if (WIFEXITED(s)) {
int status = WEXITSTATUS(s);
if (status != 32) return -1; // encoder returns 32 for some reason
}
// make symlinks
//
for (int i=0; iname)) {
sprintf(name, "%s.%d", parent->name, index);
} else {
sprintf(name, "%d", index);
}
}
// assign this chunk to a host
//
int CHUNK::assign() {
int host_id = parent->dfile->choose_host();
if (!host_id) {
return ERR_NOT_FOUND;
}
DB_VDA_CHUNK_HOST ch;
ch.create_time = dtime();
ch.vda_file_id = parent->dfile->id;
ch.host_id = host_id;
strcpy(ch.name, name);
ch.size = parent->dfile->policy.chunk_size();
ch.present_on_host = 0;
ch.transfer_in_progress = true;
ch.transfer_wait = true;
ch.transfer_request_time = ch.create_time;
ch.transfer_send_time = 0;
int retval = ch.insert();
if (retval) {
log_messages.printf(MSG_CRITICAL, "ch.insert() failed\n");
return retval;
}
return 0;
}
int CHUNK::start_upload() {
// if no upload of this chunk is in progress, start one.
// NOTE: all instances are inherently present_on_host,
// since this is only called if chunk is not present on server
//
VDA_CHUNK_HOST* chp;
set::iterator i;
for (i=hosts.begin(); i!=hosts.end(); i++) {
chp = *i;
if (chp->transfer_in_progress) return 0;
}
chp = *(hosts.begin());
DB_VDA_CHUNK_HOST dch;
char set_clause[256], where_clause[256];
sprintf(set_clause,
"transfer_in_progress=1, transfer_wait=1, transfer_request_time=%f",
dtime()
);
sprintf(where_clause,
"where vda_file_id=%d and host_id=%d and name='%s'",
chp->vda_file_id,
chp->host_id,
name
);
int retval = dch.update_fields_noid(set_clause, where_clause);
return retval;
}
inline bool alive(DB_HOST& h) {
return (h.rpc_time > dtime()-VDA_HOST_TIMEOUT);
}
char* host_alive_clause() {
static char buf[256];
sprintf(buf, "rpc_time > %f", dtime() - VDA_HOST_TIMEOUT);
return buf;
}
// Pick a host to send a chunk of this file to.
// The host must:
// 1) be alive (recent RPC time)
// 2) not have any chunks of this file
//
// We maintain a cache of such hosts
// The policy is:
//
// - scan the cache, removing hosts that are no longer alive;
// return if find a live host
// - pick a random starting point in host ID space,
// and enumerate 100 live hosts; wrap around if needed.
// Return one and put the rest in cache
//
int VDA_FILE_AUX::choose_host() {
int retval;
DB_HOST host;
return 467;
// replenish cache if needed
//
if (!available_hosts.size()) {
int nhosts_scanned = 0;
int rand_id;
for (int i=0; i<2; i++) {
char buf[256];
if (i == 0) {
retval = host.max_id(rand_id, "");
if (retval) {
log_messages.printf(MSG_CRITICAL, "host.max_id() failed\n");
return 0;
}
rand_id = (int)(((double)id)*drand());
sprintf(buf,
"where %s and id>=%d order by id limit 100",
host_alive_clause(), rand_id
);
} else {
sprintf(buf,
"where %s and id<%d order by id limit %d",
host_alive_clause(), rand_id, 100-nhosts_scanned
);
}
while (1) {
retval = host.enumerate(buf);
if (retval == ERR_DB_NOT_FOUND) break;
if (retval) {
log_messages.printf(MSG_CRITICAL, "host enum failed\n");
return 0;
}
nhosts_scanned++;
DB_VDA_CHUNK_HOST ch;
char buf2[256];
int count;
sprintf(buf2, "where vda_file_id=%d and host_id=%d", id, host.id);
#if 0
retval = ch.count(count, buf2);
if (retval) {
log_messages.printf(MSG_CRITICAL, "ch.count failed\n");
return 0;
}
#else
count = 0;
#endif
if (count == 0) {
available_hosts.push_back(host.id);
}
if (nhosts_scanned == 100) break;
}
if (nhosts_scanned == 100) break;
}
}
while (available_hosts.size()) {
int hostid = available_hosts.back();
available_hosts.pop_back();
retval = host.lookup_id(hostid);
if (retval || !alive(host)) {
continue;
}
return hostid;
}
log_messages.printf(MSG_CRITICAL, "No hosts available\n");
return 0;
}
META_CHUNK::META_CHUNK(VDA_FILE_AUX* d, META_CHUNK* p, int index) {
dfile = d;
parent = p;
if (parent) {
if (strlen(parent->name)) {
sprintf(name, "%s.%d", parent->name, index);
} else {
sprintf(name, "%d", index);
}
} else {
strcpy(name, "");
}
}
// initialize a meta-chunk:
// encode it, then recursively initialize its meta-chunk children
//
int META_CHUNK::init(const char* dir, POLICY& p, int level) {
double size;
char child_dir[1024];
CODING& c = p.codings[level];
int retval = encode(dir, c, size);
if (retval) return retval;
p.chunk_sizes[level] = size;
if (level+1 < p.coding_levels) {
for (int i=0; iinit(child_dir, p, level+1);
if (retval) return retval;
children.push_back(mc);
}
} else {
for (int i=0; iinit(dir, policy, 0);
if (retval) return retval;
sprintf(buf, "%s/chunk_sizes.txt", dir);
FILE* f = fopen(buf, "w");
for (int i=0; iget_state(child_dir, p, level+1);
if (retval) return retval;
children.push_back(mc);
}
} else {
for (int i=0; i& chunk_numbers) {
char* p = vch.name;
while (1) {
p = strchr(p, '.');
if (!p) break;
p++;
int i = atoi(p);
chunk_numbers.push_back(i);
}
return 0;
}
// get the state of an already-initialized file:
// expand the encoding tree,
// enumerate the VDA_HOST_CHUNKs from the DB
// and put them in the appropriate lists
//
int VDA_FILE_AUX::get_state() {
char buf[256];
sprintf(buf, "%s/chunk_sizes.txt", dir);
FILE* f = fopen(buf, "r");
if (!f) return -1;
for (int i=0; iget_state(dir, policy, 0);
if (retval) return retval;
DB_VDA_CHUNK_HOST vch;
sprintf(buf, "where vda_file_id=%d", id);
while (1) {
retval = vch.enumerate(buf);
if (retval == ERR_DB_NOT_FOUND) break;
if (retval) return retval;
vector chunk_numbers;
retval = get_chunk_numbers(vch, chunk_numbers);
if (retval) {
log_messages.printf(MSG_CRITICAL,
"get_chunk_numbers(): %d\n", retval
);
return retval;
}
if ((int)(chunk_numbers.size()) != policy.coding_levels) {
log_messages.printf(MSG_CRITICAL,
"wrong get_chunk_numbers: got %d, expected %d\n",
(int)(chunk_numbers.size()), policy.coding_levels
);
return -1;
}
META_CHUNK* mc = meta_chunk;
for (int i=0; ichildren[chunk_numbers[i]]);
VDA_CHUNK_HOST* vchp = new VDA_CHUNK_HOST();
*vchp = vch;
c->hosts.insert(vchp);
} else {
mc = (META_CHUNK*)(mc->children[chunk_numbers[i]]);
}
}
}
return 0;
}
int handle_file(VDA_FILE_AUX& vf, DB_VDA_FILE& dvf) {
int retval;
char buf[1024];
log_messages.printf(MSG_NORMAL, "processing file %s\n", vf.name);
// read the policy file
//
sprintf(buf, "%s/boinc_meta.txt", vf.dir);
retval = vf.policy.parse(buf);
if (retval) {
log_messages.printf(MSG_CRITICAL, "Can't parse policy file %s\n", buf);
return retval;
}
if (vf.initialized) {
retval = vf.get_state();
if (retval) {
log_messages.printf(MSG_CRITICAL, "vf.get_state failed %d\n", retval);
return retval;
}
} else {
retval = vf.init();
if (retval) {
log_messages.printf(MSG_CRITICAL, "vf.init failed %d\n", retval);
return retval;
}
dvf.update_field("initialized=1");
}
retval = vf.meta_chunk->recovery_plan();
if (retval) {
log_messages.printf(MSG_CRITICAL, "vf.recovery_plan failed %d\n", retval);
return retval;
}
retval = vf.meta_chunk->recovery_action(dtime());
if (retval) {
log_messages.printf(MSG_CRITICAL, "vf.recovery_action failed %d\n", retval);
return retval;
}
return 0;
}
// handle files
//
bool scan_files() {
DB_VDA_FILE vf;
bool found = false;
int retval;
while (1) {
retval = vf.enumerate("where need_update<>0");
if (retval == ERR_DB_NOT_FOUND) break;
if (retval) {
log_messages.printf(MSG_CRITICAL, "VDA_FILE enumerate failed\n");
exit(1);
}
VDA_FILE_AUX vfa(vf);
found = true;
retval = handle_file(vfa, vf);
if (retval) {
log_messages.printf(
MSG_CRITICAL, "handle_file() failed: %d\n", retval
);
exit(1);
} else {
vf.need_update = 0;
vf.update();
}
}
return found;
}
// this host is declared dead; deal with the loss of data
//
int handle_host(DB_HOST& h) {
DB_VDA_CHUNK_HOST ch;
char buf[256];
int retval;
log_messages.printf(MSG_NORMAL, "processing dead host %d\n", h.id);
sprintf(buf, "where host_id=%d", h.id);
while (1) {
retval = ch.enumerate(buf);
if (retval == ERR_DB_NOT_FOUND) break;
if (retval) return retval;
log_messages.printf(MSG_NORMAL, " updating file%d\n", ch.vda_file_id);
DB_VDA_FILE vf;
retval = vf.lookup_id(ch.vda_file_id);
if (retval) {
log_messages.printf(MSG_CRITICAL,
" file lookup failed%d\n", ch.vda_file_id
);
return retval;
}
retval = vf.update_field("need_update=1");
if (retval) {
log_messages.printf(MSG_CRITICAL,
" file update failed%d\n", ch.vda_file_id
);
return retval;
}
}
return 0;
}
// handle timed-out hosts
//
bool scan_hosts() {
DB_HOST h;
char buf[256];
int retval;
bool found = false;
sprintf(buf, "where cpu_efficiency < %f", dtime());
while (1) {
retval = h.enumerate(buf);
if (retval == ERR_DB_NOT_FOUND) break;
if (retval) {
log_messages.printf(MSG_CRITICAL, "host.enumerate() failed\n");
exit(1);
}
found = true;
retval = handle_host(h);
if (retval) {
log_messages.printf(MSG_CRITICAL, "handle_host() failed: %d\n", retval);
exit(1);
}
retval = h.update_field("cpu_efficiency=1e12");
if (retval) {
log_messages.printf(MSG_CRITICAL, "h.update_field() failed: %d\n", retval);
exit(1);
}
}
return found;
}
int main(int argc, char** argv) {
for (int i=1; i