dynamic dynamic::merge_diff(const dynamic& source, const dynamic& target) { if (!source.isObject() || source.type() != target.type()) { return target; } dynamic diff = object; // added/modified keys for (const auto& pair : target.items()) { auto it = source.find(pair.first); if (it == source.items().end()) { diff[pair.first] = pair.second; } else { diff[pair.first] = merge_diff(source[pair.first], target[pair.first]); } } // removed keys for (const auto& pair : source.items()) { auto it = target.find(pair.first); if (it == target.items().end()) { diff[pair.first] = nullptr; } } return diff; }
Job::Job(const Config& config, const string& name, const dynamic& d) : id_(Job::JobNameTable->insert(name)), name_(name), enabled_(d.getDefault("enabled", true).asBool()), owner_(d.getDefault("owner", "").asString().toStdString()), levelForTasks_(config.levelForTasks), priority_(d.getDefault("priority", 1.0).asDouble()), resources_(config.defaultJobResources), config_(dynamic::object), filters_(config.levels.size()), levelForHostPlacement_(StringTable::NotFound), backoffSettings_(config.defaultBackoffSettings), killOrphanTasksAfter_(config.killOrphanTasksAfter) { try { if (owner_.empty()) { throw BistroException("Job ", name, " missing owner."); } if (const auto* level_for_tasks_ptr = d.get_ptr("level_for_tasks")) { if (!level_for_tasks_ptr->isString()) { throw BistroException("'level_for_tasks' must be a string for ", name); } const auto& str_level_for_tasks = level_for_tasks_ptr->asString().toStdString(); int level_for_tasks = config.levels.lookup(str_level_for_tasks); if (level_for_tasks == StringTable::NotFound) { throw BistroException("Bad level_for_tasks: ", str_level_for_tasks); } levelForTasks_ = level_for_tasks; } auto it = d.find("resources"); if (it != d.items().end()) { if (!it->second.isObject()) { throw BistroException("'resources' must be an object for ", name); } for (const auto& pair : it->second.items()) { const auto& name = pair.first.asString().toStdString(); const int resource_id = config.resourceNames.lookup(name); if (resource_id == StringTable::NotFound) { throw BistroException("Invalid resource: ", name); } resources_[resource_id] = pair.second.asInt(); } } it = d.find("config"); if (it != d.items().end()) { if (!it->second.isObject()) { throw BistroException("'config' must be an object for ", name); } update(config_, it->second); } it = d.find("backoff"); if (it != d.items().end()) { if (!it->second.isArray()) { throw BistroException("'backoff' must be an array for ", name); } backoffSettings_ = JobBackoffSettings(it->second); } it = d.find("filters"); if (it != d.items().end()) { if (!it->second.isObject()) { throw BistroException("'filters' must be an object for ", name); } for (const auto& pair : it->second.items()) { const auto& level = pair.first.asString().toStdString(); const int level_id = config.levels.lookup(level); if (level_id == StringTable::NotFound) { throw BistroException("Invalid level in filters: ", level); } filters_[level_id] = JobFilters(pair.second); } } detail::parseKillOrphanTasksAfter(d, &killOrphanTasksAfter_); if (auto* ptr = d.get_ptr("version_id")) { if (!ptr->isInt()) { throw std::runtime_error("'version_id' must be an integer"); } versionID_ = ptr->getInt(); } if (const auto* host_level_ptr = d.get_ptr("level_for_host_placement")) { if (!host_level_ptr->isString()) { throw BistroException( "'level_for_host_placement' must be a string for ", name ); } const auto& str_host_level = host_level_ptr->asString().toStdString(); int host_level = config.levels.lookup(str_host_level); if (host_level == StringTable::NotFound) { throw BistroException( "Bad level_for_host_placement: ", str_host_level ); } levelForHostPlacement_ = host_level; } if (const auto* host_ptr = d.get_ptr("host_placement")) { if (!host_ptr->isString()) { throw BistroException("'host_placement' must be a string for ", name); } hostPlacement_ = host_ptr->asString().toStdString(); if (!hostPlacement_.empty() && levelForHostPlacement_ != StringTable::NotFound) { throw BistroException( "It makes no sense to specify both 'level_for_host_placement' and " "'host_placement'" ); } } it = d.find("create_time"); // Silently ignore non-integers because this isn't critical configuration if (it != d.items().end() && it->second.isInt()) { createTime_ = it->second.asInt(); } it = d.find("modify_time"); // Silently ignore non-integers because this isn't critical configuration if (it != d.items().end() && it->second.isInt()) { modifyTime_ = it->second.asInt(); } // We don't check that the job names in depends_on are valid jobs. // If an invalid job is specified here, or if there is circular dependency, // this job can never run. it = d.find("depends_on"); if (it != d.items().end()) { if (!it->second.isArray()) { throw BistroException("'depends_on' must be an array for ", name); } for (const auto& job_name : it->second) { dependsOn_.push_back(static_cast<ID>(JobNameTable->insert( job_name.asString().toStdString() ))); } } } catch (const exception& e) { LOG(ERROR) << "Error creating job: " << e.what(); error_ = e.what(); enabled_ = false; } }