Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update coordinators admin interface #1494

Merged
merged 9 commits into from Aug 1, 2022
Binary file added .DS_Store
Binary file not shown.
Binary file added crates/.DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions crates/holochain/CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased

- Adds `AdminRequest::UpdateCoordinators` that allows swapping coordinator zomes for a running happ.
## 0.0.151

- BREAKING CHANGE - Refactor: Property `integrity.uid` of DNA Yaml files renamed to `integrity.network_seed`. Functionality has not changed. [\#1493](https://github.com/holochain/holochain/pull/1493)
Expand Down
Expand Up @@ -55,7 +55,8 @@ pub struct RealAdminInterfaceApi {
}

impl RealAdminInterfaceApi {
pub(crate) fn new(conductor_handle: ConductorHandle) -> Self {
/// Create an admin interface api.
pub fn new(conductor_handle: ConductorHandle) -> Self {
RealAdminInterfaceApi { conductor_handle }
}
}
Expand Down Expand Up @@ -129,6 +130,23 @@ impl AdminInterfaceApi for RealAdminInterfaceApi {
}
Ok(AdminResponse::DnaRegistered(hash))
}
UpdateCoordinators(payload) => {
let UpdateCoordinatorsPayload { dna_hash, source } = *payload;
let (coordinator_zomes, wasms) = match source {
CoordinatorSource::Path(ref path) => {
let bundle = Bundle::read_from_file(path).await?;
let bundle: CoordinatorBundle = bundle.into();
bundle.into_zomes().await?
}
CoordinatorSource::Bundle(bundle) => bundle.into_zomes().await?,
};

self.conductor_handle
.update_coordinators(&dna_hash, coordinator_zomes, wasms)
.await?;

Ok(AdminResponse::CoordinatorsUpdated)
}
CreateCloneCell(payload) => {
let cell_id = payload.cell_id();
self.conductor_handle
Expand Down
8 changes: 4 additions & 4 deletions crates/holochain/src/conductor/handle.rs
Expand Up @@ -138,7 +138,7 @@ pub trait ConductorHandleT: Send + Sync {
async fn register_dna(&self, dna: DnaFile) -> ConductorResult<()>;

/// Hot swap coordinator zomes on an existing dna.
async fn hot_swap_coordinators(
async fn update_coordinators(
&self,
hash: &DnaHash,
coordinator_zomes: CoordinatorZomes,
Expand Down Expand Up @@ -577,13 +577,13 @@ impl ConductorHandleT for ConductorHandleImpl {
Ok(())
}

async fn hot_swap_coordinators(
async fn update_coordinators(
&self,
hash: &DnaHash,
coordinator_zomes: CoordinatorZomes,
wasms: Vec<wasm::DnaWasm>,
) -> ConductorResult<()> {
// Note this isn't really concurrent safe. It would be a race condition to hotswap the
// Note this isn't really concurrent safe. It would be a race condition to update the
// same dna concurrently.
let mut ribosome =
self.conductor
Expand All @@ -594,7 +594,7 @@ impl ConductorHandleT for ConductorHandleImpl {
})?;
let _old_wasms = ribosome
.dna_file
.hot_swap_coordinators(coordinator_zomes.clone(), wasms.clone())
.update_coordinators(coordinator_zomes.clone(), wasms.clone())
.await?;

// Add new wasm code to db.
Expand Down
97 changes: 92 additions & 5 deletions crates/holochain/tests/integrity_zome/mod.rs
@@ -1,7 +1,19 @@
use std::path::PathBuf;

use holo_hash::ActionHash;
use holo_hash::WasmHash;
use holochain::conductor::api::AdminInterfaceApi;
use holochain::conductor::api::RealAdminInterfaceApi;
use holochain::sweettest::*;
use holochain_conductor_api::AdminRequest;
use holochain_conductor_api::AdminResponse;
use holochain_types::dna::CoordinatorBundle;
use holochain_types::dna::CoordinatorManifest;
use holochain_types::dna::ZomeDependency;
use holochain_types::dna::ZomeLocation;
use holochain_types::dna::ZomeManifest;
use holochain_types::prelude::DnaWasm;
use holochain_types::prelude::UpdateCoordinatorsPayload;
use holochain_wasm_test_utils::TestCoordinatorWasm;
use holochain_wasm_test_utils::TestIntegrityWasm;
use holochain_zome_types::CoordinatorZome;
Expand All @@ -11,9 +23,10 @@ use holochain_zome_types::Record;
use holochain_zome_types::WasmZome;
use holochain_zome_types::Zome;
use holochain_zome_types::ZomeDef;
use mr_bundle::Bundle;

#[tokio::test(flavor = "multi_thread")]
async fn test_coordinator_zome_hot_swap() {
async fn test_coordinator_zome_update() {
let mut conductor = SweetConductor::from_config(Default::default()).await;
let (dna, _, _) = SweetDnaFile::unique_from_zomes(
vec![TestIntegrityWasm::IntegrityZome],
Expand Down Expand Up @@ -55,7 +68,7 @@ async fn test_coordinator_zome_hot_swap() {

println!("Hot swap the coordinator zomes for a totally different coordinator zome (conductor is still running)");
conductor
.hot_swap_coordinators(
.update_coordinators(
&dna_hash,
vec![CoordinatorZome::from(TestCoordinatorWasm::CoordinatorZomeUpdate).into_inner()],
vec![TestCoordinatorWasm::CoordinatorZomeUpdate.into()],
Expand All @@ -78,7 +91,7 @@ async fn test_coordinator_zome_hot_swap() {
}

#[tokio::test(flavor = "multi_thread")]
async fn test_coordinator_zome_hot_swap_multi_integrity() {
async fn test_coordinator_zome_update_multi_integrity() {
let mut conductor = SweetConductor::from_config(Default::default()).await;
let mut second_integrity = IntegrityZome::from(TestIntegrityWasm::IntegrityZome);
second_integrity.zome_name_mut().0 = "2".into();
Expand Down Expand Up @@ -156,7 +169,7 @@ async fn test_coordinator_zome_hot_swap_multi_integrity() {

// Add a completely new coordinator with the same dependency
conductor
.hot_swap_coordinators(
.update_coordinators(
&dna_hash,
vec![CoordinatorZome::from(TestCoordinatorWasm::CoordinatorZomeUpdate).into_inner()],
vec![TestCoordinatorWasm::CoordinatorZomeUpdate.into()],
Expand Down Expand Up @@ -184,7 +197,7 @@ async fn test_coordinator_zome_hot_swap_multi_integrity() {
.into();

conductor
.hot_swap_coordinators(
.update_coordinators(
&dna_hash,
vec![("2_coord".into(), new_coordinator)],
vec![TestCoordinatorWasm::CoordinatorZomeUpdate.into()],
Expand All @@ -198,3 +211,77 @@ async fn test_coordinator_zome_hot_swap_multi_integrity() {

assert!(record.is_some());
}

#[tokio::test(flavor = "multi_thread")]
async fn test_update_admin_interface() {
let mut conductor = SweetConductor::from_config(Default::default()).await;
let (dna, _, _) = SweetDnaFile::unique_from_zomes(
vec![TestIntegrityWasm::IntegrityZome],
vec![TestCoordinatorWasm::CoordinatorZome],
vec![
DnaWasm::from(TestIntegrityWasm::IntegrityZome),
DnaWasm::from(TestCoordinatorWasm::CoordinatorZome),
],
)
.await
.unwrap();

let dna_hash = dna.dna_hash().clone();

let app = conductor.setup_app("app", &[dna]).await.unwrap();
let cells = app.into_cells();

let hash: ActionHash = conductor
.call(
&cells[0].zome(TestCoordinatorWasm::CoordinatorZome),
"create_entry",
(),
)
.await;

let admin_api = RealAdminInterfaceApi::new(conductor.clone());

let manifest = CoordinatorManifest {
zomes: vec![ZomeManifest {
name: TestCoordinatorWasm::CoordinatorZomeUpdate.into(),
hash: None,
location: ZomeLocation::Bundled(TestCoordinatorWasm::CoordinatorZomeUpdate.into()),
dependencies: Some(vec![ZomeDependency {
name: TestIntegrityWasm::IntegrityZome.into(),
}]),
}],
};

let code = DnaWasm::from(TestCoordinatorWasm::CoordinatorZomeUpdate)
.code
.to_vec();

let source: CoordinatorBundle = Bundle::new(
manifest,
[(
PathBuf::from(TestCoordinatorWasm::CoordinatorZomeUpdate),
code,
)],
env!("CARGO_MANIFEST_DIR").into(),
)
.unwrap()
.into();

let req = UpdateCoordinatorsPayload {
dna_hash,
source: holochain_types::prelude::CoordinatorSource::Bundle(Box::new(source)),
};
let req = AdminRequest::UpdateCoordinators(Box::new(req));
let r = admin_api.handle_admin_request(req).await;
assert!(matches!(r, AdminResponse::CoordinatorsUpdated));

let record: Option<Record> = conductor
.call(
&cells[0].zome(TestCoordinatorWasm::CoordinatorZomeUpdate),
"get_entry",
hash,
)
.await;

assert!(record.is_some());
}
14 changes: 14 additions & 0 deletions crates/holochain_conductor_api/src/admin_interface.rs
Expand Up @@ -36,6 +36,17 @@ pub enum AdminRequest {
/// [`AdminResponse::DnaRegistered`]
RegisterDna(Box<RegisterDnaPayload>),

/// Update coordinator zomes for an already installed DNA.
///
/// Replaces any installed coordinator zomes with the same zome name.
/// If the zome name doesn't exist then the coordinator zome is appended
/// to the current list of coordinator zomes.
///
/// # Returns
///
/// [`AdminResponse::CoordinatorsUpdated`]
UpdateCoordinators(Box<UpdateCoordinatorsPayload>),

/// Clone a DNA (in the biological sense), thus creating a new `Cell`.
///
/// Using the provided, already-registered DNA, create a new DNA with a unique
Expand Down Expand Up @@ -354,6 +365,9 @@ pub enum AdminResponse {
/// The successful response to an [`AdminRequest::RegisterDna`]
DnaRegistered(DnaHash),

/// The successful response to an [`AdminRequest::UpdateCoordinators`]
CoordinatorsUpdated,

/// The successful response to an [`AdminRequest::InstallApp`].
///
/// The resulting [`InstalledAppInfo`] contains the app ID,
Expand Down
22 changes: 21 additions & 1 deletion crates/holochain_types/src/app.rs
Expand Up @@ -11,7 +11,7 @@ mod app_bundle;
mod app_manifest;
mod dna_gamut;
pub mod error;
use crate::dna::DnaBundle;
use crate::{dna::DnaBundle, prelude::CoordinatorBundle};
pub use app_bundle::*;
pub use app_manifest::app_manifest_validated::*;
pub use app_manifest::*;
Expand Down Expand Up @@ -44,6 +44,16 @@ pub enum DnaSource {
Hash(DnaHash),
}

/// The source of coordinators to be installed, either as binary data, or from a path
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CoordinatorSource {
/// Coordinators loaded from a bundle file on disk
Path(PathBuf),
/// Coordinators provided in the [`CoordinatorBundle`] data structure
Bundle(Box<CoordinatorBundle>),
}

/// The instructions on how to get the DNA to be registered
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct RegisterDnaPayload {
Expand All @@ -56,6 +66,16 @@ pub struct RegisterDnaPayload {
pub source: DnaSource,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
/// The instructions on how to update coordinators for a dna file.
pub struct UpdateCoordinatorsPayload {
/// The hash of the dna to swap coordinators for.
pub dna_hash: DnaHash,
/// Where to find the coordinators.
#[serde(flatten)]
pub source: CoordinatorSource,
}

/// The instructions on how to get the DNA to be registered
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct CreateCloneCellPayload {
Expand Down
2 changes: 2 additions & 0 deletions crates/holochain_types/src/dna.rs
Expand Up @@ -3,6 +3,7 @@
//! It includes utilities for representing dna structures in memory,
//! as well as serializing and deserializing dna, mainly to json format.

mod coordinator_bundle;
mod dna_bundle;
mod dna_file;
mod dna_manifest;
Expand All @@ -11,6 +12,7 @@ mod ribosome_store;
#[allow(missing_docs)]
pub mod error;
pub mod wasm;
pub use coordinator_bundle::*;
pub use dna_bundle::*;
pub use dna_file::*;
pub use dna_manifest::*;
Expand Down
68 changes: 68 additions & 0 deletions crates/holochain_types/src/dna/coordinator_bundle.rs
@@ -0,0 +1,68 @@
use holochain_serialized_bytes::prelude::*;
use holochain_zome_types::CoordinatorZomes;
use holochain_zome_types::WasmZome;
use holochain_zome_types::ZomeDef;
use mr_bundle::Manifest;

use crate::prelude::DnaResult;
use crate::prelude::DnaWasm;

use super::hash_bytes;
use super::CoordinatorManifest;

#[derive(
Debug,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
SerializedBytes,
shrinkwraprs::Shrinkwrap,
derive_more::From,
)]
/// A bundle of coordinator zomes.
pub struct CoordinatorBundle(mr_bundle::Bundle<CoordinatorManifest>);

impl Manifest for CoordinatorManifest {
fn locations(&self) -> Vec<mr_bundle::Location> {
self.zomes
.iter()
.map(|zome| zome.location.clone())
.collect()
}

fn path() -> std::path::PathBuf {
"coordinators.yaml".into()
}

fn bundle_extension() -> &'static str {
"coordinators"
}
}

impl CoordinatorBundle {
/// Convert into zomes and their wasm files.
pub async fn into_zomes(self) -> DnaResult<(CoordinatorZomes, Vec<DnaWasm>)> {
let mut resources = self.resolve_all_cloned().await?;
let coordinator = hash_bytes(self.manifest().zomes.iter().cloned(), &mut resources).await?;
let coordinator_zomes = coordinator
.iter()
.map(|(zome_name, hash, _, dependencies)| {
(
zome_name.clone(),
ZomeDef::Wasm(WasmZome {
wasm_hash: hash.clone(),
dependencies: dependencies.clone(),
})
.into(),
)
})
.collect();
let wasms = coordinator
.into_iter()
.map(|(_, _, wasm, _)| wasm)
.collect();

Ok((coordinator_zomes, wasms))
}
}
2 changes: 1 addition & 1 deletion crates/holochain_types/src/dna/dna_bundle.rs
Expand Up @@ -239,7 +239,7 @@ impl DnaBundle {
}
}

async fn hash_bytes(
pub(super) async fn hash_bytes(
zomes: impl Iterator<Item = ZomeManifest>,
resources: &mut HashMap<Location, ResourceBytes>,
) -> DnaResult<Vec<(ZomeName, WasmHash, DnaWasm, Vec<ZomeName>)>> {
Expand Down