sapling/eden/mononoke/cmds/segmented_changelog_tailer.rs
Yan Soares Couto 302131bd5f Allow cli commands to build any "repo object"
Summary:
The important change on this diff is in this file: `eden/mononoke/cmdlib/src/args/mod.rs`

On this diff I change that file's repo-building functions to be able to build both `BlobRepo` and `InnerRepo` (added on D28748221 (e4b6fd3751)). In fact, they are now able to build any facet container that can be built by the `RepoFactory` factory, so each binary can specify their own subset of needed "attributes" and only build those ones.

For now, they're all still using BlobRepo, this diff is only a refactor that enables easily changing the repo attributes you need.

The rest of the diff is mostly giving hints to the compiler, as in several places it couldn't infer it should use `BlobRepo` directly, so I had to add type hints.

## High level goal

This is part of the blobrepo refactoring effort.

I am also doing this in order to:
1. Make sure every place that builds `SkiplistIndex` uses `RepoFactory` for that.
2. Then add a `BlobstoreGetOps` trait for blobstores, and use the factory to feed it to skiplist index, so it can query the blobstore while skipping cache. (see [this thread](https://www.internalfb.com/diff/D28681737 (850a1a41b7)?dst_version_fbid=283910610084973&transaction_fbid=106742464866346))

Reviewed By: StanislavGlebik

Differential Revision: D28877887

fbshipit-source-id: b5e0093449aac734591a19d915b6459b1779360a
2021-06-09 05:16:13 -07:00

189 lines
6.6 KiB
Rust

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#![deny(warnings)]
use std::sync::Arc;
use std::time::Duration;
use anyhow::{format_err, Context, Error};
use blobrepo::BlobRepo;
use clap::Arg;
use futures::future::join_all;
use slog::{error, info};
use blobstore_factory::{make_metadata_sql_factory, ReadOnlyStorage};
use bookmarks::{BookmarkName, Bookmarks};
use cmdlib::{
args::{self, MononokeMatches},
helpers,
};
use context::{CoreContext, SessionContainer};
use fbinit::FacebookInit;
use metaconfig_types::MetadataDatabaseConfig;
use segmented_changelog::{SegmentedChangelogSqlConnections, SegmentedChangelogTailer};
use sql_ext::facebook::MyAdmin;
use sql_ext::replication::{NoReplicaLagMonitor, ReplicaLagMonitor};
const ONCE_ARG: &str = "once";
const REPO_ARG: &str = "repo";
#[fbinit::main]
fn main(fb: FacebookInit) -> Result<(), Error> {
let app = args::MononokeAppBuilder::new("Updates segmented changelog assets.")
.with_scuba_logging_args()
.with_advanced_args_hidden()
.with_fb303_args()
.build()
.about("Builds a new version of segmented changelog.")
.arg(
Arg::with_name(REPO_ARG)
.long(REPO_ARG)
.takes_value(true)
.required(true)
.multiple(true)
.help("Repository name to warm-up"),
)
.arg(
Arg::with_name(ONCE_ARG)
.long(ONCE_ARG)
.takes_value(false)
.required(false)
.help("When set, the tailer will perform a single incremental build run."),
);
let matches = app.get_matches(fb)?;
let logger = matches.logger();
let session = SessionContainer::new_with_defaults(fb);
let ctx = session.new_context(logger.clone(), matches.scuba_sample_builder());
helpers::block_execute(
run(ctx, &matches),
fb,
&std::env::var("TW_JOB_NAME").unwrap_or_else(|_| "segmented_changelog_tailer".to_string()),
logger,
&matches,
cmdlib::monitoring::AliveService,
)
}
async fn run<'a>(ctx: CoreContext, matches: &'a MononokeMatches<'a>) -> Result<(), Error> {
let reponames: Vec<_> = matches
.values_of(REPO_ARG)
.ok_or_else(|| format_err!("--{} argument is required", REPO_ARG))?
.map(ToString::to_string)
.collect();
if reponames.is_empty() {
error!(ctx.logger(), "At least one repo had to be specified");
return Ok(());
}
let config_store = matches.config_store();
let mysql_options = matches.mysql_options();
let configs = args::load_repo_configs(config_store, matches)?;
let readonly_storage = ReadOnlyStorage(false);
let mut tasks = Vec::new();
for (index, reponame) in reponames.into_iter().enumerate() {
let config = configs
.repos
.get(&reponame)
.ok_or_else(|| format_err!("unknown repository: {}", reponame))?;
let repo_id = config.repoid;
let bookmark_name = &config.segmented_changelog_config.master_bookmark;
let track_bookmark = BookmarkName::new(bookmark_name).with_context(|| {
format!(
"error parsing the name of the bookmark to track: {}",
bookmark_name,
)
})?;
info!(
ctx.logger(),
"repo name '{}' translates to id {}", reponame, repo_id
);
let storage_config = config.storage_config.clone();
let db_address = match &storage_config.metadata {
MetadataDatabaseConfig::Local(_) => None,
MetadataDatabaseConfig::Remote(remote_config) => {
Some(remote_config.primary.db_address.clone())
}
};
let replica_lag_monitor: Arc<dyn ReplicaLagMonitor> = match db_address {
None => Arc::new(NoReplicaLagMonitor()),
Some(address) => {
let my_admin = MyAdmin::new(ctx.fb).context("building myadmin client")?;
Arc::new(my_admin.single_shard_lag_monitor(address))
}
};
let sql_factory = make_metadata_sql_factory(
ctx.fb,
storage_config.metadata,
mysql_options.clone(),
readonly_storage,
)
.await
.with_context(|| format!("repo {}: constructing metadata sql factory", repo_id))?;
let segmented_changelog_sql_connections = sql_factory
.open::<SegmentedChangelogSqlConnections>()
.with_context(|| {
format!(
"repo {}: error constructing segmented changelog sql connections",
repo_id
)
})?;
// This is a bit weird from the dependency point of view but I think that it is best. The
// BlobRepo may have a SegmentedChangelog attached to it but that doesn't hurt us in any
// way. On the other hand reconstructing the dependencies for SegmentedChangelog without
// BlobRepo is probably prone to more problems from the maintenance perspective.
let blobrepo: BlobRepo =
args::open_repo_with_repo_id(ctx.fb, ctx.logger(), repo_id, matches).await?;
let segmented_changelog_tailer = SegmentedChangelogTailer::new(
repo_id,
segmented_changelog_sql_connections,
replica_lag_monitor,
blobrepo.get_changeset_fetcher(),
Arc::new(blobrepo.get_blobstore()),
Arc::clone(blobrepo.bookmarks()) as Arc<dyn Bookmarks>,
track_bookmark,
None,
);
info!(
ctx.logger(),
"repo {}: SegmentedChangelogTailer initialized", repo_id
);
if matches.is_present(ONCE_ARG) {
segmented_changelog_tailer
.once(&ctx)
.await
.with_context(|| format!("repo {}: incrementally building repo", repo_id))?;
info!(
ctx.logger(),
"repo {}: SegmentedChangelogTailer is done", repo_id,
);
} else if let Some(period) = config.segmented_changelog_config.tailer_update_period {
// spread out update operations, start updates on another repo after 7 seconds
let wait_to_start = Duration::from_secs(7 * index as u64);
let ctx = ctx.clone();
tasks.push(async move {
tokio::time::sleep(wait_to_start).await;
segmented_changelog_tailer.run(&ctx, period).await;
});
}
}
join_all(tasks).await;
Ok(())
}