Skip to content

Commit

Permalink
Merge pull request #1494 from holochain/hot-swap-admin
Browse files Browse the repository at this point in the history
Update coordinators admin interface
  • Loading branch information
freesig committed Aug 1, 2022
2 parents cc5c3c3 + fc4d9cd commit 9b2b53d
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 21 deletions.
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
10 changes: 5 additions & 5 deletions crates/holochain/src/conductor/handle.rs
Expand Up @@ -137,8 +137,8 @@ pub trait ConductorHandleT: Send + Sync {
/// Install a [`DnaFile`](holochain_types::dna::DnaFile) in this Conductor
async fn register_dna(&self, dna: DnaFile) -> ConductorResult<()>;

/// Hot swap coordinator zomes on an existing dna.
async fn hot_swap_coordinators(
/// Update coordinator zomes on an existing dna.
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
99 changes: 93 additions & 6 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 @@ -53,9 +66,9 @@ async fn test_coordinator_zome_hot_swap() {
assert!(record.is_some());
println!("Success!");

println!("Hot swap the coordinator zomes for a totally different coordinator zome (conductor is still running)");
println!("Update 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

0 comments on commit 9b2b53d

Please sign in to comment.