feat(cluster): implement distributed clustering with etcd coordination

- Integrate etcd-client for distributed coordination and leader election
- Add remote client macros with proper formatting for all services
- Implement RequestMetrics for tracking RPC performance and errors
- Add rate limiting mechanism across all service endpoints
- Create ElectionRequest and ElectionResult message types for leader election
- Add role management with primary/replica switching capabilities
- Implement health checker with automatic failover detection
- Add repository count metrics for cluster monitoring
- Update Cargo.toml with etcd-client and dashmap dependencies
- Modify RepoEntry to include read_only flag for replica handling
- Implement should_accept_election logic to prevent duplicate elections
- Add RoleChangedEvent handling for cluster role updates
This commit is contained in:
zhenyi
2026-06-08 14:31:29 +08:00
parent d243dce027
commit 8f472a0443
37 changed files with 4691 additions and 83 deletions
+23 -3
View File
@@ -3,7 +3,11 @@ use crate::pb::*;
use super::{GitksService, cache, into_status};
remote_client!(remote_archive_client, ArchiveServiceClient<tonic::transport::Channel>, "archive");
remote_client!(
remote_archive_client,
ArchiveServiceClient<tonic::transport::Channel>,
"archive"
);
#[tonic::async_trait]
impl archive_service_server::ArchiveService for GitksService {
@@ -14,7 +18,9 @@ impl archive_service_server::ArchiveService for GitksService {
&self,
request: tonic::Request<ArchiveRequest>,
) -> Result<tonic::Response<Self::GetArchiveStream>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.ArchiveService/GetArchive");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("archive.get_archive", %repo);
let _enter = span.enter();
@@ -24,16 +30,22 @@ impl archive_service_server::ArchiveService for GitksService {
if let Some(mut client) =
remote_archive_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
let resp = client.get_archive(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let stream = gb.get_archive_stream(inner)?;
tracing::info!(%repo, "archive streaming started");
m.record("ok");
Ok(tonic::Response::new(stream))
}
@@ -41,7 +53,9 @@ impl archive_service_server::ArchiveService for GitksService {
&self,
request: tonic::Request<ListArchiveEntriesRequest>,
) -> Result<tonic::Response<ListArchiveEntriesResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.ArchiveService/ListArchiveEntries");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("archive.list_archive_entries", %repo);
let _enter = span.enter();
@@ -51,11 +65,16 @@ impl archive_service_server::ArchiveService for GitksService {
if let Some(mut client) =
remote_archive_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.list_archive_entries(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.treeish) {
cache::cached_response("archive.list_archive_entries", &inner, || {
@@ -65,6 +84,7 @@ impl archive_service_server::ArchiveService for GitksService {
gb.list_archive_entries(inner).map_err(into_status)?
};
tracing::info!(%repo, count = resp.entries.len(), "list_archive_entries done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
}
+23 -3
View File
@@ -3,7 +3,11 @@ use crate::pb::*;
use super::{GitksService, cache, into_status, into_stream};
remote_client!(remote_blame_client, BlameServiceClient<tonic::transport::Channel>, "blame");
remote_client!(
remote_blame_client,
BlameServiceClient<tonic::transport::Channel>,
"blame"
);
#[tonic::async_trait]
impl blame_service_server::BlameService for GitksService {
@@ -14,7 +18,9 @@ impl blame_service_server::BlameService for GitksService {
&self,
request: tonic::Request<BlameRequest>,
) -> Result<tonic::Response<BlameResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.BlameService/Blame");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let path = inner.path.clone();
let span = tracing::info_span!("blame.blame", %repo, %path);
@@ -25,11 +31,16 @@ impl blame_service_server::BlameService for GitksService {
if let Some(mut client) =
remote_blame_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.blame(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("blame.blame", &inner, || {
@@ -39,6 +50,7 @@ impl blame_service_server::BlameService for GitksService {
gb.blame(inner).map_err(into_status)?
};
tracing::info!(%repo, %path, hunks = resp.hunks.len(), "blame done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -46,7 +58,9 @@ impl blame_service_server::BlameService for GitksService {
&self,
request: tonic::Request<BlameRequest>,
) -> Result<tonic::Response<Self::StreamBlameStream>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.BlameService/StreamBlame");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let path = inner.path.clone();
let span = tracing::info_span!("blame.stream_blame", %repo, %path);
@@ -57,13 +71,18 @@ impl blame_service_server::BlameService for GitksService {
if let Some(mut client) =
remote_blame_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
let resp = client.stream_blame(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("blame.blame", &inner, || {
@@ -72,6 +91,7 @@ impl blame_service_server::BlameService for GitksService {
} else {
gb.blame(inner).map_err(into_status)?
};
m.record("ok");
Ok(tonic::Response::new(into_stream(resp.hunks)))
}
}
+77 -9
View File
@@ -3,7 +3,11 @@ use crate::pb::*;
use super::{GitksService, into_status};
remote_client!(remote_branch_client, BranchServiceClient<tonic::transport::Channel>, "branch");
remote_client!(
remote_branch_client,
BranchServiceClient<tonic::transport::Channel>,
"branch"
);
#[tonic::async_trait]
impl branch_service_server::BranchService for GitksService {
@@ -11,7 +15,9 @@ impl branch_service_server::BranchService for GitksService {
&self,
request: tonic::Request<ListBranchesRequest>,
) -> Result<tonic::Response<ListBranchesResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.BranchService/ListBranches");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("branch.list_branches", %repo);
let _enter = span.enter();
@@ -21,14 +27,20 @@ impl branch_service_server::BranchService for GitksService {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.list_branches(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.list_branches(inner).map_err(into_status)?;
tracing::info!(%repo, count = resp.branches.len(), "list_branches done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -36,7 +48,9 @@ impl branch_service_server::BranchService for GitksService {
&self,
request: tonic::Request<GetBranchRequest>,
) -> Result<tonic::Response<Branch>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.BranchService/GetBranch");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let name = inner.name.clone();
let span = tracing::info_span!("branch.get_branch", %repo, %name);
@@ -47,13 +61,19 @@ impl branch_service_server::BranchService for GitksService {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.get_branch(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.get_branch(inner).map_err(into_status)?;
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -61,7 +81,9 @@ impl branch_service_server::BranchService for GitksService {
&self,
request: tonic::Request<CreateBranchRequest>,
) -> Result<tonic::Response<Branch>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.BranchService/CreateBranch");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let name = inner.name.clone();
let span = tracing::info_span!("branch.create_branch", %repo, %name);
@@ -72,15 +94,21 @@ impl branch_service_server::BranchService for GitksService {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.create_branch(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.create_branch(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch created");
self.notify_ref_update(&repo, &format!("refs/heads/{}", name), "", "");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -88,7 +116,9 @@ impl branch_service_server::BranchService for GitksService {
&self,
request: tonic::Request<DeleteBranchRequest>,
) -> Result<tonic::Response<()>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.BranchService/DeleteBranch");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let name = inner.name.clone();
let span = tracing::info_span!("branch.delete_branch", %repo, %name);
@@ -99,15 +129,21 @@ impl branch_service_server::BranchService for GitksService {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.delete_branch(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
gb.delete_branch(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch deleted");
self.notify_ref_update(&repo, &format!("refs/heads/{}", name), "", "");
m.record("ok");
Ok(tonic::Response::new(()))
}
@@ -115,7 +151,9 @@ impl branch_service_server::BranchService for GitksService {
&self,
request: tonic::Request<RenameBranchRequest>,
) -> Result<tonic::Response<Branch>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.BranchService/RenameBranch");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let old = inner.old_name.clone();
let new = inner.new_name.clone();
@@ -127,15 +165,21 @@ impl branch_service_server::BranchService for GitksService {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.rename_branch(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.rename_branch(inner).map_err(into_status)?;
tracing::info!(%repo, old = %old, new = %new, "branch renamed");
self.notify_ref_update(&repo, &format!("refs/heads/{}", new), "", "");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -143,7 +187,9 @@ impl branch_service_server::BranchService for GitksService {
&self,
request: tonic::Request<UpdateBranchTargetRequest>,
) -> Result<tonic::Response<Branch>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.BranchService/UpdateBranchTarget");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let name = inner.name.clone();
let span = tracing::info_span!("branch.update_branch_target", %repo, %name);
@@ -154,15 +200,21 @@ impl branch_service_server::BranchService for GitksService {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.update_branch_target(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.update_branch_target(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch target updated");
self.notify_ref_update(&repo, &format!("refs/heads/{}", name), "", "");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -170,7 +222,9 @@ impl branch_service_server::BranchService for GitksService {
&self,
request: tonic::Request<SetBranchUpstreamRequest>,
) -> Result<tonic::Response<Branch>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.BranchService/SetBranchUpstream");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let name = inner.name.clone();
let span = tracing::info_span!("branch.set_branch_upstream", %repo, %name);
@@ -181,15 +235,21 @@ impl branch_service_server::BranchService for GitksService {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.set_branch_upstream(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.set_branch_upstream(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch upstream set");
self.notify_ref_update(&repo, &format!("refs/heads/{}", name), "", "");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -197,7 +257,9 @@ impl branch_service_server::BranchService for GitksService {
&self,
request: tonic::Request<CompareBranchRequest>,
) -> Result<tonic::Response<CompareBranchResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.BranchService/CompareBranch");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let source = inner.source_branch.clone();
let target = inner.target_branch.clone();
@@ -209,14 +271,20 @@ impl branch_service_server::BranchService for GitksService {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.compare_branch(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.compare_branch(inner).map_err(into_status)?;
tracing::info!(%repo, %source, %target, ahead = resp.ahead_by, behind = resp.behind_by, "branch compared");
m.record("ok");
Ok(tonic::Response::new(resp))
}
}
+68 -8
View File
@@ -3,7 +3,11 @@ use crate::pb::*;
use super::{GitksService, cache, into_status};
remote_client!(remote_commit_client, CommitServiceClient<tonic::transport::Channel>, "commit");
remote_client!(
remote_commit_client,
CommitServiceClient<tonic::transport::Channel>,
"commit"
);
#[tonic::async_trait]
impl commit_service_server::CommitService for GitksService {
@@ -11,7 +15,9 @@ impl commit_service_server::CommitService for GitksService {
&self,
request: tonic::Request<ListCommitsRequest>,
) -> Result<tonic::Response<ListCommitsResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.CommitService/ListCommits");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("commit.list_commits", %repo);
let _enter = span.enter();
@@ -21,11 +27,16 @@ impl commit_service_server::CommitService for GitksService {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.list_commits(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if !inner.all && cache::selector_is_oid(&inner.revision) {
cache::cached_response("commit.list_commits", &inner, || {
@@ -35,6 +46,7 @@ impl commit_service_server::CommitService for GitksService {
gb.list_commits(inner).map_err(into_status)?
};
tracing::info!(%repo, count = resp.commits.len(), total = resp.page_info.as_ref().map(|p| p.total_count).unwrap_or(0), "list_commits done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -42,7 +54,9 @@ impl commit_service_server::CommitService for GitksService {
&self,
request: tonic::Request<GetCommitRequest>,
) -> Result<tonic::Response<Commit>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.CommitService/GetCommit");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("commit.get_commit", %repo);
let _enter = span.enter();
@@ -52,11 +66,16 @@ impl commit_service_server::CommitService for GitksService {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.get_commit(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("commit.get_commit", &inner, || {
@@ -65,6 +84,7 @@ impl commit_service_server::CommitService for GitksService {
} else {
gb.get_commit(inner).map_err(into_status)?
};
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -72,7 +92,9 @@ impl commit_service_server::CommitService for GitksService {
&self,
request: tonic::Request<GetCommitAncestorsRequest>,
) -> Result<tonic::Response<GetCommitAncestorsResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.CommitService/GetCommitAncestors");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("commit.get_commit_ancestors", %repo);
let _enter = span.enter();
@@ -82,11 +104,16 @@ impl commit_service_server::CommitService for GitksService {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.get_commit_ancestors(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("commit.get_commit_ancestors", &inner, || {
@@ -96,6 +123,7 @@ impl commit_service_server::CommitService for GitksService {
gb.get_commit_ancestors(inner).map_err(into_status)?
};
tracing::info!(%repo, count = resp.commits.len(), "get_commit_ancestors done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -103,7 +131,9 @@ impl commit_service_server::CommitService for GitksService {
&self,
request: tonic::Request<CreateCommitRequest>,
) -> Result<tonic::Response<CreateCommitResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.CommitService/CreateCommit");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let branch = inner.branch.clone();
let span = tracing::info_span!("commit.create_commit", %repo, %branch);
@@ -114,11 +144,16 @@ impl commit_service_server::CommitService for GitksService {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.create_commit(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.create_commit(inner).map_err(into_status)?;
let commit_hex = resp
@@ -128,6 +163,7 @@ impl commit_service_server::CommitService for GitksService {
.unwrap_or("?");
tracing::info!(%repo, %branch, %commit_hex, "commit created");
self.notify_ref_update(&repo, &format!("refs/heads/{}", branch), "", "");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -135,7 +171,9 @@ impl commit_service_server::CommitService for GitksService {
&self,
request: tonic::Request<RevertCommitRequest>,
) -> Result<tonic::Response<CreateCommitResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.CommitService/RevertCommit");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let branch = inner.branch.clone();
let span = tracing::info_span!("commit.revert_commit", %repo, %branch);
@@ -146,15 +184,21 @@ impl commit_service_server::CommitService for GitksService {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.revert_commit(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.revert_commit(inner).map_err(into_status)?;
tracing::info!(%repo, %branch, "commit reverted");
self.notify_ref_update(&repo, &format!("refs/heads/{}", branch), "", "");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -162,7 +206,9 @@ impl commit_service_server::CommitService for GitksService {
&self,
request: tonic::Request<CherryPickCommitRequest>,
) -> Result<tonic::Response<CreateCommitResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.CommitService/CherryPickCommit");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let branch = inner.branch.clone();
let span = tracing::info_span!("commit.cherry_pick_commit", %repo, %branch);
@@ -173,15 +219,21 @@ impl commit_service_server::CommitService for GitksService {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.cherry_pick_commit(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.cherry_pick_commit(inner).map_err(into_status)?;
tracing::info!(%repo, %branch, "commit cherry-picked");
self.notify_ref_update(&repo, &format!("refs/heads/{}", branch), "", "");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -189,7 +241,9 @@ impl commit_service_server::CommitService for GitksService {
&self,
request: tonic::Request<CompareCommitsRequest>,
) -> Result<tonic::Response<CompareCommitsResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.CommitService/CompareCommits");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("commit.compare_commits", %repo);
let _enter = span.enter();
@@ -199,11 +253,16 @@ impl commit_service_server::CommitService for GitksService {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.compare_commits(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selectors_are_oid(&inner.base, &inner.head) {
cache::cached_response("commit.compare_commits", &inner, || {
@@ -213,6 +272,7 @@ impl commit_service_server::CommitService for GitksService {
gb.compare_commits(inner).map_err(into_status)?
};
tracing::info!(%repo, count = resp.commits.len(), "compare_commits done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
}
+41 -5
View File
@@ -3,7 +3,11 @@ use crate::pb::*;
use super::{GitksService, cache, into_status, into_stream};
remote_client!(remote_diff_client, DiffServiceClient<tonic::transport::Channel>, "diff");
remote_client!(
remote_diff_client,
DiffServiceClient<tonic::transport::Channel>,
"diff"
);
#[tonic::async_trait]
impl diff_service_server::DiffService for GitksService {
@@ -14,7 +18,9 @@ impl diff_service_server::DiffService for GitksService {
&self,
request: tonic::Request<GetDiffRequest>,
) -> Result<tonic::Response<GetDiffResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.DiffService/GetDiff");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("diff.get_diff", %repo);
let _enter = span.enter();
@@ -24,11 +30,16 @@ impl diff_service_server::DiffService for GitksService {
if let Some(mut client) =
remote_diff_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.get_diff(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selectors_are_oid(&inner.base, &inner.head) {
cache::cached_response("diff.get_diff", &inner, || {
@@ -38,6 +49,7 @@ impl diff_service_server::DiffService for GitksService {
gb.get_diff(inner).map_err(into_status)?
};
tracing::info!(%repo, files = resp.files.len(), overflow = resp.overflow, "get_diff done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -45,7 +57,9 @@ impl diff_service_server::DiffService for GitksService {
&self,
request: tonic::Request<GetCommitDiffRequest>,
) -> Result<tonic::Response<GetDiffResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.DiffService/GetCommitDiff");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("diff.get_commit_diff", %repo);
let _enter = span.enter();
@@ -55,11 +69,16 @@ impl diff_service_server::DiffService for GitksService {
if let Some(mut client) =
remote_diff_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.get_commit_diff(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.commit) {
cache::cached_response("diff.get_commit_diff", &inner, || {
@@ -69,6 +88,7 @@ impl diff_service_server::DiffService for GitksService {
gb.get_commit_diff(inner).map_err(into_status)?
};
tracing::info!(%repo, files = resp.files.len(), "get_commit_diff done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -76,7 +96,9 @@ impl diff_service_server::DiffService for GitksService {
&self,
request: tonic::Request<GetPatchRequest>,
) -> Result<tonic::Response<Self::GetPatchStream>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.DiffService/GetPatch");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("diff.get_patch", %repo);
let _enter = span.enter();
@@ -86,13 +108,18 @@ impl diff_service_server::DiffService for GitksService {
if let Some(mut client) =
remote_diff_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
let resp = client.get_patch(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let items = if cache::selectors_are_oid(&inner.base, &inner.head) {
cache::cached_vec_response("diff.get_patch", &inner, || {
@@ -101,6 +128,7 @@ impl diff_service_server::DiffService for GitksService {
} else {
gb.get_patch(inner).map_err(into_status)?
};
m.record("ok");
Ok(tonic::Response::new(into_stream(items)))
}
@@ -108,7 +136,9 @@ impl diff_service_server::DiffService for GitksService {
&self,
request: tonic::Request<GetDiffStatsRequest>,
) -> Result<tonic::Response<DiffStats>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.DiffService/GetDiffStats");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("diff.get_diff_stats", %repo);
let _enter = span.enter();
@@ -118,11 +148,16 @@ impl diff_service_server::DiffService for GitksService {
if let Some(mut client) =
remote_diff_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.get_diff_stats(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selectors_are_oid(&inner.base, &inner.head) {
cache::cached_response("diff.get_diff_stats", &inner, || {
@@ -131,6 +166,7 @@ impl diff_service_server::DiffService for GitksService {
} else {
gb.get_diff_stats(inner).map_err(into_status)?
};
m.record("ok");
Ok(tonic::Response::new(resp))
}
}
+50 -6
View File
@@ -3,7 +3,11 @@ use crate::pb::*;
use super::{GitksService, into_status};
remote_client!(remote_merge_client, MergeServiceClient<tonic::transport::Channel>, "merge");
remote_client!(
remote_merge_client,
MergeServiceClient<tonic::transport::Channel>,
"merge"
);
#[tonic::async_trait]
impl merge_service_server::MergeService for GitksService {
@@ -11,7 +15,9 @@ impl merge_service_server::MergeService for GitksService {
&self,
request: tonic::Request<CheckMergeRequest>,
) -> Result<tonic::Response<MergeResult>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.MergeService/CheckMerge");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("merge.check_merge", %repo);
let _enter = span.enter();
@@ -21,14 +27,20 @@ impl merge_service_server::MergeService for GitksService {
if let Some(mut client) =
remote_merge_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.check_merge(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.check_merge(inner).map_err(into_status)?;
tracing::info!(%repo, status = resp.status, "check_merge done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -36,7 +48,9 @@ impl merge_service_server::MergeService for GitksService {
&self,
request: tonic::Request<MergeRequest>,
) -> Result<tonic::Response<MergeResult>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.MergeService/Merge");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let target = inner.target_branch.clone();
let span = tracing::info_span!("merge.merge", %repo, %target);
@@ -47,15 +61,21 @@ impl merge_service_server::MergeService for GitksService {
if let Some(mut client) =
remote_merge_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.merge(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.merge(inner).map_err(into_status)?;
tracing::info!(%repo, %target, status = resp.status, "merge done");
self.notify_ref_update(&repo, &format!("refs/heads/{}", target), "", "");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -63,7 +83,9 @@ impl merge_service_server::MergeService for GitksService {
&self,
request: tonic::Request<ListMergeConflictsRequest>,
) -> Result<tonic::Response<ListMergeConflictsResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.MergeService/ListMergeConflicts");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("merge.list_merge_conflicts", %repo);
let _enter = span.enter();
@@ -73,14 +95,20 @@ impl merge_service_server::MergeService for GitksService {
if let Some(mut client) =
remote_merge_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.list_merge_conflicts(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.list_merge_conflicts(inner).map_err(into_status)?;
tracing::info!(%repo, conflicts = resp.conflicts.len(), "list_merge_conflicts done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -88,7 +116,9 @@ impl merge_service_server::MergeService for GitksService {
&self,
request: tonic::Request<ResolveMergeConflictsRequest>,
) -> Result<tonic::Response<MergeResult>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.MergeService/ResolveMergeConflicts");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let target = inner.target_branch.clone();
let span = tracing::info_span!("merge.resolve_merge_conflicts", %repo, %target);
@@ -99,15 +129,21 @@ impl merge_service_server::MergeService for GitksService {
if let Some(mut client) =
remote_merge_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.resolve_merge_conflicts(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.resolve_merge_conflicts(inner).map_err(into_status)?;
tracing::info!(%repo, %target, status = resp.status, "merge conflicts resolved");
self.notify_ref_update(&repo, &format!("refs/heads/{}", target), "", "");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -115,7 +151,9 @@ impl merge_service_server::MergeService for GitksService {
&self,
request: tonic::Request<RebaseRequest>,
) -> Result<tonic::Response<RebaseResult>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.MergeService/Rebase");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let branch = inner.branch.clone();
let span = tracing::info_span!("merge.rebase", %repo, %branch);
@@ -126,15 +164,21 @@ impl merge_service_server::MergeService for GitksService {
if let Some(mut client) =
remote_merge_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.rebase(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.rebase(inner).map_err(into_status)?;
tracing::info!(%repo, %branch, status = resp.status, "rebase done");
self.notify_ref_update(&repo, &format!("refs/heads/{}", branch), "", "");
m.record("ok");
Ok(tonic::Response::new(resp))
}
}
+47 -1
View File
@@ -62,6 +62,9 @@ pub struct GitksService {
pub repo_prefix: PathBuf,
pub node_actor: Option<ActorRef<GitNodeMessage>>,
pub grpc_addr: String,
pub disk_cache: Option<crate::disk_cache::DiskCache>,
pub pack_cache: Option<crate::pack_cache::PackCache>,
pub hook_manager: Option<crate::hooks::HookManager>,
}
impl GitksService {
@@ -70,6 +73,9 @@ impl GitksService {
repo_prefix,
node_actor: None,
grpc_addr: String::new(),
disk_cache: None,
pack_cache: None,
hook_manager: None,
}
}
@@ -78,6 +84,21 @@ impl GitksService {
self
}
pub fn with_disk_cache(mut self, dc: crate::disk_cache::DiskCache) -> Self {
self.disk_cache = Some(dc);
self
}
pub fn with_pack_cache(mut self, pc: crate::pack_cache::PackCache) -> Self {
self.pack_cache = Some(pc);
self
}
pub fn with_hook_manager(mut self, hm: crate::hooks::HookManager) -> Self {
self.hook_manager = Some(hm);
self
}
pub fn with_grpc_addr(mut self, grpc_addr: String) -> Self {
self.grpc_addr = grpc_addr;
self
@@ -156,6 +177,26 @@ impl GitksService {
.unwrap_or_else(|| "unknown".into())
}
/// Get the relative path from a repository header, if any.
pub(crate) fn repo_relative_path<'a>(&self, header: Option<&'a crate::pb::RepositoryHeader>) -> Option<&'a str> {
header.and_then(|h| {
if h.relative_path.is_empty() {
None
} else {
Some(h.relative_path.as_str())
}
})
}
/// Acquire a rate-limit permit for the repository in this request.
/// Returns a guard that releases the permit on drop.
pub(crate) async fn acquire_rate_limit(
&self,
header: Option<&crate::pb::RepositoryHeader>,
) -> Result<Option<crate::rate_limit::RateLimitGuard>, tonic::Status> {
crate::rate_limit::acquire_or_reject(self.repo_relative_path(header)).await
}
pub(crate) fn resolve(
&self,
header: Option<&crate::pb::RepositoryHeader>,
@@ -241,9 +282,14 @@ impl GitksService {
old_oid: &str,
new_oid: &str,
) {
// Invalidate caches that depend on this repository
// Invalidate moka caches
crate::server::cache::invalidate_repo(relative_path);
// Invalidate disk cache
if let Some(ref pc) = self.pack_cache {
pc.invalidate_repo(relative_path);
}
if let Some(ref actor) = self.node_actor {
let event = crate::actor::message::RefUpdateEvent {
relative_path: relative_path.to_string(),
+162 -8
View File
@@ -6,7 +6,11 @@ use crate::pb::*;
use super::{GitksService, into_status};
remote_client!(remote_pack_client, PackServiceClient<tonic::transport::Channel>, "pack");
remote_client!(
remote_pack_client,
PackServiceClient<tonic::transport::Channel>,
"pack"
);
#[tonic::async_trait]
impl pack_service_server::PackService for GitksService {
@@ -18,7 +22,9 @@ impl pack_service_server::PackService for GitksService {
&self,
request: tonic::Request<AdvertiseRefsRequest>,
) -> Result<tonic::Response<AdvertiseRefsResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.PackService/AdvertiseRefs");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("pack.advertise_refs", %repo);
let _enter = span.enter();
@@ -28,14 +34,37 @@ impl pack_service_server::PackService for GitksService {
if let Some(mut client) =
remote_pack_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.advertise_refs(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
if let Some(ref pc) = self.pack_cache {
let protocol = inner.service.clone();
if let Ok(digest) = pc.disk_cache().compute_info_refs_key(&repo, &protocol) {
if let Some(cached) = pc.lookup_info_refs::<AdvertiseRefsResponse>(&digest) {
tracing::info!(%repo, refs = cached.references.len(), "advertise_refs done (cached)");
m.record("ok");
return Ok(tonic::Response::new(cached));
}
let resp = gb.advertise_refs(inner).map_err(into_status)?;
pc.store_info_refs(&digest, &resp);
tracing::info!(%repo, refs = resp.references.len(), "advertise_refs done (written to cache)");
m.record("ok");
return Ok(tonic::Response::new(resp));
}
}
let resp = gb.advertise_refs(inner).map_err(into_status)?;
tracing::info!(%repo, refs = resp.references.len(), "advertise_refs done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -43,11 +72,13 @@ impl pack_service_server::PackService for GitksService {
&self,
request: tonic::Request<tonic::Streaming<UploadPackRequest>>,
) -> Result<tonic::Response<Self::UploadPackStream>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.PackService/UploadPack");
let mut stream = request.into_inner();
let first = stream
.next()
.await
.ok_or_else(|| tonic::Status::invalid_argument("empty upload-pack stream"))??;
let _rate = self.acquire_rate_limit(first.repository.as_ref()).await?;
let repo = self.repo_label(first.repository.as_ref());
let span = tracing::info_span!("pack.upload_pack", %repo);
let _enter = span.enter();
@@ -57,6 +88,7 @@ impl pack_service_server::PackService for GitksService {
if let Some(mut client) =
remote_pack_client(self, first.repository.as_ref(), false).await?
{
m.record("ok");
let (tx, rx) = tokio::sync::mpsc::channel(16);
let _ = tx.send(first).await;
tokio::spawn(async move {
@@ -78,9 +110,13 @@ impl pack_service_server::PackService for GitksService {
let out = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(out));
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
tracing::info!(%repo, "upload-pack streaming started");
@@ -97,6 +133,7 @@ impl pack_service_server::PackService for GitksService {
});
let result = gb.upload_pack(ReceiverStream::new(rx)).await?;
m.record("ok");
Ok(tonic::Response::new(result))
}
@@ -104,11 +141,13 @@ impl pack_service_server::PackService for GitksService {
&self,
request: tonic::Request<tonic::Streaming<ReceivePackRequest>>,
) -> Result<tonic::Response<Self::ReceivePackStream>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.PackService/ReceivePack");
let mut stream = request.into_inner();
let first = stream
.next()
.await
.ok_or_else(|| tonic::Status::invalid_argument("empty receive-pack stream"))??;
let _rate = self.acquire_rate_limit(first.repository.as_ref()).await?;
let repo = self.repo_label(first.repository.as_ref());
let span = tracing::info_span!("pack.receive_pack", %repo);
let _enter = span.enter();
@@ -118,6 +157,7 @@ impl pack_service_server::PackService for GitksService {
if let Some(mut client) =
remote_pack_client(self, first.repository.as_ref(), false).await?
{
m.record("ok");
let (tx, rx) = tokio::sync::mpsc::channel(16);
let _ = tx.send(first).await;
tokio::spawn(async move {
@@ -139,9 +179,13 @@ impl pack_service_server::PackService for GitksService {
let out = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(out));
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
tracing::info!(%repo, "receive-pack streaming started");
@@ -158,6 +202,7 @@ impl pack_service_server::PackService for GitksService {
});
let result = gb.receive_pack(ReceiverStream::new(rx)).await?;
m.record("ok");
Ok(tonic::Response::new(result))
}
@@ -165,7 +210,9 @@ impl pack_service_server::PackService for GitksService {
&self,
request: tonic::Request<PackObjectsRequest>,
) -> Result<tonic::Response<Self::PackObjectsStream>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.PackService/PackObjects");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("pack.pack_objects", %repo);
let _enter = span.enter();
@@ -175,16 +222,97 @@ impl pack_service_server::PackService for GitksService {
if let Some(mut client) =
remote_pack_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
let resp = client.pack_objects(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
if let Some(ref pc) = self.pack_cache
&& let Some(opts) = inner.options.as_ref()
{
let wants_hex: Vec<String> = opts.wants.iter().map(|w| w.hex.clone()).collect();
let haves_hex: Vec<String> = opts.haves.iter().map(|h| h.hex.clone()).collect();
if let Ok(digest) = pc.disk_cache().compute_pack_objects_key(
&repo,
&wants_hex,
&haves_hex,
opts.thin_pack,
opts.use_bitmaps,
opts.delta_base_offset,
) {
if let Some(file) = pc
.disk_cache()
.open_stream_read(crate::pack_cache::PACK_CACHE_NAMESPACE, &digest)
.ok()
.flatten()
{
tracing::info!(%repo, digest = %digest, "pack-objects cache hit, streaming from disk");
m.record("ok");
let (tx, rx) = tokio::sync::mpsc::channel(16);
tokio::spawn(async move {
let result = tokio::task::spawn_blocking(move || {
use std::io::Read;
let mut file = file;
let mut buf = vec![0u8; 65536];
let mut chunks = Vec::new();
loop {
match file.read(&mut buf) {
Ok(0) => break,
Ok(n) => chunks.push(Ok(PackfileChunk {
data: buf[..n].to_vec(),
})),
Err(e) => {
chunks.push(Err(tonic::Status::internal(format!(
"cache read error: {e}"
))));
break;
}
}
}
chunks
})
.await;
match result {
Ok(chunks) => {
for chunk in chunks {
if tx.send(chunk).await.is_err() {
break;
}
}
}
Err(e) => {
let _ = tx
.send(Err(tonic::Status::internal(format!(
"cache read task failed: {e}"
))))
.await;
}
}
});
return Ok(tonic::Response::new(ReceiverStream::new(rx)));
}
// Cache miss: execute pack-objects and tee to cache
tracing::info!(%repo, digest = %digest, "pack-objects cache miss");
let stream = gb.pack_objects(inner).await?;
let tee_stream = pc.tee_pack_stream(&digest, stream);
m.record("ok");
return Ok(tonic::Response::new(tee_stream));
}
}
let stream = gb.pack_objects(inner).await?;
tracing::info!(%repo, "pack-objects streaming started");
m.record("ok");
Ok(tonic::Response::new(stream))
}
@@ -192,11 +320,15 @@ impl pack_service_server::PackService for GitksService {
&self,
request: tonic::Request<tonic::Streaming<IndexPackRequest>>,
) -> Result<tonic::Response<IndexPackResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.PackService/IndexPack");
let mut stream = request.into_inner();
let mut inputs = Vec::new();
while let Some(msg) = stream.next().await {
inputs.push(msg?);
}
let _rate = self
.acquire_rate_limit(inputs.first().and_then(|r: &IndexPackRequest| r.repository.as_ref()))
.await?;
let repo = self.repo_label(inputs.first().and_then(|r| r.repository.as_ref()));
let span = tracing::info_span!("pack.index_pack", %repo);
let _enter = span.enter();
@@ -210,14 +342,20 @@ impl pack_service_server::PackService for GitksService {
)
.await?
{
m.record("ok");
return client.index_pack(tokio_stream::iter(inputs)).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.index_pack(inputs).map_err(into_status)?;
tracing::info!(%repo, objects = resp.object_count, "index_pack done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -225,7 +363,9 @@ impl pack_service_server::PackService for GitksService {
&self,
request: tonic::Request<ListPackfilesRequest>,
) -> Result<tonic::Response<ListPackfilesResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.PackService/ListPackfiles");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("pack.list_packfiles", %repo);
let _enter = span.enter();
@@ -235,14 +375,20 @@ impl pack_service_server::PackService for GitksService {
if let Some(mut client) =
remote_pack_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.list_packfiles(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.list_packfiles(inner).map_err(into_status)?;
tracing::info!(%repo, count = resp.packfiles.len(), "list_packfiles done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -250,7 +396,9 @@ impl pack_service_server::PackService for GitksService {
&self,
request: tonic::Request<FsckRequest>,
) -> Result<tonic::Response<FsckResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.PackService/Fsck");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("pack.fsck", %repo);
let _enter = span.enter();
@@ -260,14 +408,20 @@ impl pack_service_server::PackService for GitksService {
if let Some(mut client) =
remote_pack_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.fsck(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.fsck(inner).map_err(into_status)?;
tracing::info!(%repo, ok = resp.ok, errors = resp.errors.len(), warnings = resp.warnings.len(), "fsck done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
}
+287 -2
View File
@@ -2,8 +2,13 @@ use crate::pb::repository_service_client::RepositoryServiceClient;
use crate::pb::*;
use super::{GitksService, git_cmd, into_status, repository_maint};
use tokio_stream::wrappers::ReceiverStream;
remote_client!(remote_repository_client, RepositoryServiceClient<tonic::transport::Channel>, "repository");
remote_client!(
remote_repository_client,
RepositoryServiceClient<tonic::transport::Channel>,
"repository"
);
fn default_branch_name(gb: &crate::bare::GitBare) -> String {
git_cmd(gb, &["symbolic-ref", "HEAD"])
@@ -23,7 +28,9 @@ impl repository_service_server::RepositoryService for GitksService {
&self,
request: tonic::Request<GetRepositoryRequest>,
) -> Result<tonic::Response<Repository>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.RepositoryService/GetRepository");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.get_repository", %repo);
let _enter = span.enter();
@@ -33,14 +40,20 @@ impl repository_service_server::RepositoryService for GitksService {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.get_repository(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let bare = gb.bare_dir.join("HEAD").exists();
let object_format = gb.object_format();
m.record("ok");
Ok(tonic::Response::new(Repository {
header: inner.repository,
bare,
@@ -54,15 +67,21 @@ impl repository_service_server::RepositoryService for GitksService {
&self,
request: tonic::Request<InitRepositoryRequest>,
) -> Result<tonic::Response<Repository>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.RepositoryService/InitRepository");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.init_repository", %repo);
let _enter = span.enter();
let bare_dir = self.resolve_for_init(inner.repository.as_ref())?;
let gb = crate::bare::GitBare::new(bare_dir);
gb.init_repository(inner.bare).map_err(into_status)?;
if let Some(ref hm) = self.hook_manager {
hm.install_hooks(&gb.bare_dir).map_err(into_status)?;
}
tracing::info!(%repo, bare = inner.bare, "repository initialized");
self.notify_ref_update(&repo, "HEAD", "", "");
m.record("ok");
Ok(tonic::Response::new(Repository {
header: inner.repository,
bare: inner.bare,
@@ -74,7 +93,9 @@ impl repository_service_server::RepositoryService for GitksService {
&self,
request: tonic::Request<DeleteRepositoryRequest>,
) -> Result<tonic::Response<()>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.RepositoryService/DeleteRepository");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.delete_repository", %repo);
let _enter = span.enter();
@@ -83,12 +104,15 @@ impl repository_service_server::RepositoryService for GitksService {
&& let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.delete_repository(inner).await;
}
tracing::warn!(%repo, path = %bare_dir.display(), "deleting repository");
std::fs::remove_dir_all(&bare_dir).map_err(|e| tonic::Status::internal(e.to_string()))?;
tracing::info!(%repo, "repository deleted");
self.notify_ref_update(&repo, "", "", "");
crate::rate_limit::remove_repository(&repo);
m.record("ok");
Ok(tonic::Response::new(()))
}
@@ -421,4 +445,265 @@ impl repository_service_server::RepositoryService for GitksService {
tracing::info!(%repo, ok = resp.ok, "commit-graph write done");
Ok(tonic::Response::new(resp))
}
// ── Hooks Management ────────────────────────────────────────────
async fn list_hooks(
&self,
request: tonic::Request<ListHooksRequest>,
) -> Result<tonic::Response<ListHooksResponse>, tonic::Status> {
let inner = request.into_inner();
let gb = self.resolve(inner.repository.as_ref())?;
let hook_mgr = self.hook_manager.as_ref();
let hooks = if let Some(hm) = hook_mgr {
hm.list_hooks(&gb.bare_dir)
.map_err(|e| tonic::Status::internal(e.to_string()))?
} else {
Vec::new()
};
let resp = ListHooksResponse {
hooks: hooks
.into_iter()
.map(|h| crate::pb::HookInfo {
hook_type: h.hook_type,
level: h.level.to_string(),
path: h.path,
})
.collect(),
};
Ok(tonic::Response::new(resp))
}
async fn set_custom_hook(
&self,
request: tonic::Request<SetCustomHookRequest>,
) -> Result<tonic::Response<()>, tonic::Status> {
let inner = request.into_inner();
let gb = self.resolve(inner.repository.as_ref())?;
let hook_mgr = self.hook_manager.as_ref();
if let Some(hm) = hook_mgr {
hm.set_custom_hook(&gb.bare_dir, &inner.hook_name, &inner.content)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
} else {
return Err(tonic::Status::failed_precondition("hooks not enabled"));
}
tracing::info!(repo = %gb.bare_dir.display(), hook = %inner.hook_name, "custom hook set");
Ok(tonic::Response::new(()))
}
async fn remove_custom_hook(
&self,
request: tonic::Request<RemoveCustomHookRequest>,
) -> Result<tonic::Response<()>, tonic::Status> {
let inner = request.into_inner();
let gb = self.resolve(inner.repository.as_ref())?;
let hook_mgr = self.hook_manager.as_ref();
if let Some(hm) = hook_mgr {
hm.remove_custom_hook(&gb.bare_dir, &inner.hook_name)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
} else {
return Err(tonic::Status::failed_precondition("hooks not enabled"));
}
tracing::info!(repo = %gb.bare_dir.display(), hook = %inner.hook_name, "custom hook removed");
Ok(tonic::Response::new(()))
}
// ── Snapshot Operations ──────────────────────────────────────────
async fn create_snapshot(
&self,
request: tonic::Request<CreateSnapshotRequest>,
) -> Result<tonic::Response<CreateSnapshotResponse>, tonic::Status> {
let inner = request.into_inner();
let gb = self.resolve(inner.repository.as_ref())?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.create_snapshot", %repo);
let _enter = span.enter();
let storage = crate::snapshot::storage::LocalSnapshotStorage::new(
self.repo_prefix.join("+gitks-snapshots"),
);
let snapshot_id = crate::snapshot::ops::create_and_store_snapshot(&gb, &repo, &storage)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
let head_oid = crate::snapshot::ops::get_head_oid_internal(&gb)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
use crate::snapshot::storage::SnapshotStorageBackend;
let actual_size = storage
.read_snapshot(&snapshot_id)
.map(|d| d.len() as u64)
.unwrap_or(0);
tracing::info!(%repo, snapshot_id = %snapshot_id, size_bytes = actual_size, "snapshot created");
Ok(tonic::Response::new(CreateSnapshotResponse {
snapshot_id,
size_bytes: actual_size,
head_oid,
}))
}
async fn restore_snapshot(
&self,
request: tonic::Request<RestoreSnapshotRequest>,
) -> Result<tonic::Response<()>, tonic::Status> {
let inner = request.into_inner();
let target_repo = self.repo_label(inner.target_repository.as_ref());
let span = tracing::info_span!("repo.restore_snapshot", %target_repo);
let _enter = span.enter();
let storage = crate::snapshot::storage::LocalSnapshotStorage::new(
self.repo_prefix.join("+gitks-snapshots"),
);
let target_path = self.resolve_for_init(inner.target_repository.as_ref())?;
crate::snapshot::ops::restore_from_storage(&target_path, &inner.snapshot_id, &storage)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
tracing::info!(%target_repo, snapshot_id = %inner.snapshot_id, "snapshot restored");
self.notify_ref_update(&target_repo, "HEAD", "", "");
Ok(tonic::Response::new(()))
}
async fn list_snapshots(
&self,
request: tonic::Request<ListSnapshotsRequest>,
) -> Result<tonic::Response<ListSnapshotsResponse>, tonic::Status> {
let inner = request.into_inner();
let repo = self.repo_label(inner.repository.as_ref());
let storage = crate::snapshot::storage::LocalSnapshotStorage::new(
self.repo_prefix.join("+gitks-snapshots"),
);
use crate::snapshot::storage::SnapshotStorageBackend;
let snapshots = storage
.list_snapshots(&repo)
.map_err(tonic::Status::internal)?;
let resp = ListSnapshotsResponse {
snapshots: snapshots
.into_iter()
.map(|s| crate::pb::SnapshotInfo {
snapshot_id: s.snapshot_id,
relative_path: s.relative_path,
size_bytes: s.size_bytes,
created_at: s.created_at,
head_oid: s.head_oid,
})
.collect(),
};
Ok(tonic::Response::new(resp))
}
async fn delete_snapshot(
&self,
request: tonic::Request<DeleteSnapshotRequest>,
) -> Result<tonic::Response<()>, tonic::Status> {
let inner = request.into_inner();
let storage = crate::snapshot::storage::LocalSnapshotStorage::new(
self.repo_prefix.join("+gitks-snapshots"),
);
use crate::snapshot::storage::SnapshotStorageBackend;
storage
.delete_snapshot(&inner.snapshot_id)
.map_err(tonic::Status::internal)?;
tracing::info!(snapshot_id = %inner.snapshot_id, "snapshot deleted");
Ok(tonic::Response::new(()))
}
// ── Repository Move ──────────────────────────────────────────────
type FetchRepositoryDataStream =
ReceiverStream<Result<FetchRepositoryDataResponse, tonic::Status>>;
async fn move_repository(
&self,
request: tonic::Request<MoveRepositoryRequest>,
) -> Result<tonic::Response<MoveRepositoryResponse>, tonic::Status> {
let inner = request.into_inner();
let source_repo = self.repo_label(inner.source_repository.as_ref());
let span = tracing::info_span!("repo.move_repository", %source_repo);
let _enter = span.enter();
let gb = self.resolve(inner.source_repository.as_ref())?;
let bundle_data = crate::snapshot::ops::create_snapshot(&gb)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
let target_path = self.resolve_for_init(inner.target_repository.as_ref())?;
let target_gb = crate::bare::GitBare::new(target_path.clone());
target_gb
.init_repository(true)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
crate::snapshot::ops::restore_snapshot(&target_path, &bundle_data)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
if let Some(ref hm) = self.hook_manager {
hm.install_hooks(&target_path)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
}
let source_path = gb.bare_dir.clone();
std::fs::remove_dir_all(&source_path)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
self.notify_ref_update(&source_repo, "HEAD", "", "");
tracing::info!(source = %source_repo, "repository moved successfully");
Ok(tonic::Response::new(MoveRepositoryResponse {
state: MoveRepositoryState::MoveStateCompleted as i32,
error_message: String::new(),
}))
}
async fn fetch_repository_data(
&self,
request: tonic::Request<FetchRepositoryDataRequest>,
) -> Result<tonic::Response<Self::FetchRepositoryDataStream>, tonic::Status> {
let inner = request.into_inner();
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.fetch_repository_data", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
let bundle_data = crate::snapshot::ops::create_snapshot(&gb)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
let (tx, rx) = tokio::sync::mpsc::channel(16);
tokio::spawn(async move {
const CHUNK_SIZE: usize = 65536;
let total = bundle_data.len();
if total == 0 {
let _ = tx
.send(Ok(FetchRepositoryDataResponse {
data: vec![],
done: true,
}))
.await;
return;
}
for offset in (0..total).step_by(CHUNK_SIZE) {
let end = (offset + CHUNK_SIZE).min(total);
let chunk_data = bundle_data[offset..end].to_vec();
let is_done = end >= total;
if tx
.send(Ok(FetchRepositoryDataResponse {
data: chunk_data,
done: is_done,
}))
.await
.is_err()
{
break;
}
}
});
Ok(tonic::Response::new(ReceiverStream::new(rx)))
}
}
+50 -6
View File
@@ -3,7 +3,11 @@ use crate::pb::*;
use super::{GitksService, into_status};
remote_client!(remote_tag_client, TagServiceClient<tonic::transport::Channel>, "tag");
remote_client!(
remote_tag_client,
TagServiceClient<tonic::transport::Channel>,
"tag"
);
#[tonic::async_trait]
impl tag_service_server::TagService for GitksService {
@@ -11,7 +15,9 @@ impl tag_service_server::TagService for GitksService {
&self,
request: tonic::Request<ListTagsRequest>,
) -> Result<tonic::Response<ListTagsResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.TagService/ListTags");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tag.list_tags", %repo);
let _enter = span.enter();
@@ -21,14 +27,20 @@ impl tag_service_server::TagService for GitksService {
if let Some(mut client) =
remote_tag_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.list_tags(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.list_tags(inner).map_err(into_status)?;
tracing::info!(%repo, count = resp.tags.len(), "list_tags done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -36,7 +48,9 @@ impl tag_service_server::TagService for GitksService {
&self,
request: tonic::Request<GetTagRequest>,
) -> Result<tonic::Response<Tag>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.TagService/GetTag");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let name = inner.name.clone();
let span = tracing::info_span!("tag.get_tag", %repo, %name);
@@ -47,13 +61,19 @@ impl tag_service_server::TagService for GitksService {
if let Some(mut client) =
remote_tag_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.get_tag(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.get_tag(inner).map_err(into_status)?;
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -61,7 +81,9 @@ impl tag_service_server::TagService for GitksService {
&self,
request: tonic::Request<CreateTagRequest>,
) -> Result<tonic::Response<Tag>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.TagService/CreateTag");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let name = inner.name.clone();
let span = tracing::info_span!("tag.create_tag", %repo, %name);
@@ -72,15 +94,21 @@ impl tag_service_server::TagService for GitksService {
if let Some(mut client) =
remote_tag_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.create_tag(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.create_tag(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "tag created");
self.notify_ref_update(&repo, &format!("refs/tags/{}", name), "", "");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -88,7 +116,9 @@ impl tag_service_server::TagService for GitksService {
&self,
request: tonic::Request<DeleteTagRequest>,
) -> Result<tonic::Response<()>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.TagService/DeleteTag");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let name = inner.name.clone();
let span = tracing::info_span!("tag.delete_tag", %repo, %name);
@@ -99,15 +129,21 @@ impl tag_service_server::TagService for GitksService {
if let Some(mut client) =
remote_tag_client(self, inner.repository.as_ref(), true).await?
{
m.record("ok");
return client.delete_tag(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
gb.delete_tag(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "tag deleted");
self.notify_ref_update(&repo, &format!("refs/tags/{}", name), "", "");
m.record("ok");
Ok(tonic::Response::new(()))
}
@@ -115,7 +151,9 @@ impl tag_service_server::TagService for GitksService {
&self,
request: tonic::Request<VerifyTagRequest>,
) -> Result<tonic::Response<VerifiedSignature>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.TagService/VerifyTag");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let name = inner.name.clone();
let span = tracing::info_span!("tag.verify_tag", %repo, %name);
@@ -126,14 +164,20 @@ impl tag_service_server::TagService for GitksService {
if let Some(mut client) =
remote_tag_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.verify_tag(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.verify_tag(inner).map_err(into_status)?;
tracing::info!(%repo, %name, verified = resp.verified, "tag verified");
m.record("ok");
Ok(tonic::Response::new(resp))
}
}
+59 -7
View File
@@ -3,7 +3,11 @@ use crate::pb::*;
use super::{GitksService, cache, into_status, into_stream};
remote_client!(remote_tree_client, TreeServiceClient<tonic::transport::Channel>, "tree");
remote_client!(
remote_tree_client,
TreeServiceClient<tonic::transport::Channel>,
"tree"
);
#[tonic::async_trait]
impl tree_service_server::TreeService for GitksService {
@@ -14,7 +18,9 @@ impl tree_service_server::TreeService for GitksService {
&self,
request: tonic::Request<ListTreeRequest>,
) -> Result<tonic::Response<ListTreeResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.TreeService/ListTree");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tree.list_tree", %repo);
let _enter = span.enter();
@@ -24,11 +30,16 @@ impl tree_service_server::TreeService for GitksService {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.list_tree(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("tree.list_tree", &inner, || {
@@ -38,6 +49,7 @@ impl tree_service_server::TreeService for GitksService {
gb.list_tree(inner).map_err(into_status)?
};
tracing::info!(%repo, count = resp.entries.len(), "list_tree done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -45,7 +57,9 @@ impl tree_service_server::TreeService for GitksService {
&self,
request: tonic::Request<GetTreeRequest>,
) -> Result<tonic::Response<Tree>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.TreeService/GetTree");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tree.get_tree", %repo);
let _enter = span.enter();
@@ -55,11 +69,16 @@ impl tree_service_server::TreeService for GitksService {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.get_tree(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("tree.get_tree", &inner, || {
@@ -68,6 +87,7 @@ impl tree_service_server::TreeService for GitksService {
} else {
gb.get_tree(inner).map_err(into_status)?
};
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -75,7 +95,9 @@ impl tree_service_server::TreeService for GitksService {
&self,
request: tonic::Request<GetBlobRequest>,
) -> Result<tonic::Response<Blob>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.TreeService/GetBlob");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let path = inner.path.clone();
let span = tracing::info_span!("tree.get_blob", %repo, %path);
@@ -86,11 +108,16 @@ impl tree_service_server::TreeService for GitksService {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.get_blob(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("tree.get_blob", &inner, || {
@@ -99,6 +126,7 @@ impl tree_service_server::TreeService for GitksService {
} else {
gb.get_blob(inner).map_err(into_status)?
};
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -106,7 +134,9 @@ impl tree_service_server::TreeService for GitksService {
&self,
request: tonic::Request<GetRawBlobRequest>,
) -> Result<tonic::Response<Self::GetRawBlobStream>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.TreeService/GetRawBlob");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tree.get_raw_blob", %repo);
let _enter = span.enter();
@@ -116,13 +146,18 @@ impl tree_service_server::TreeService for GitksService {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
let resp = client.get_raw_blob(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let items = if inner.oid.is_some() {
cache::cached_vec_response("tree.get_raw_blob", &inner, || {
@@ -135,6 +170,7 @@ impl tree_service_server::TreeService for GitksService {
} else {
gb.get_raw_blob(inner).map_err(into_status)?
};
m.record("ok");
Ok(tonic::Response::new(into_stream(items)))
}
@@ -142,7 +178,9 @@ impl tree_service_server::TreeService for GitksService {
&self,
request: tonic::Request<GetFileMetadataRequest>,
) -> Result<tonic::Response<FileMetadata>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.TreeService/GetFileMetadata");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tree.get_file_metadata", %repo);
let _enter = span.enter();
@@ -152,11 +190,16 @@ impl tree_service_server::TreeService for GitksService {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.get_file_metadata(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("tree.get_file_metadata", &inner, || {
@@ -165,6 +208,7 @@ impl tree_service_server::TreeService for GitksService {
} else {
gb.get_file_metadata(inner).map_err(into_status)?
};
m.record("ok");
Ok(tonic::Response::new(resp))
}
@@ -172,7 +216,9 @@ impl tree_service_server::TreeService for GitksService {
&self,
request: tonic::Request<FindFilesRequest>,
) -> Result<tonic::Response<FindFilesResponse>, tonic::Status> {
let m = crate::metrics::RequestMetrics::new("gitks.TreeService/FindFiles");
let inner = request.into_inner();
let _rate = self.acquire_rate_limit(inner.repository.as_ref()).await?;
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tree.find_files", %repo);
let _enter = span.enter();
@@ -182,11 +228,16 @@ impl tree_service_server::TreeService for GitksService {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
m.record("ok");
return client.find_files(inner).await;
}
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => {
crate::metrics::record_rpc_error(&m, &err);
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("tree.find_files", &inner, || {
@@ -196,6 +247,7 @@ impl tree_service_server::TreeService for GitksService {
gb.find_files(inner).map_err(into_status)?
};
tracing::info!(%repo, count = resp.files.len(), "find_files done");
m.record("ok");
Ok(tonic::Response::new(resp))
}
}