Skip to content

Commit

Permalink
Merge pull request #31 from Holo-Host/disable-link
Browse files Browse the repository at this point in the history
disable/uninstall when host disabled happ
  • Loading branch information
JettTech committed Nov 21, 2023
2 parents de49a6a + 567fffd commit 99ab67b
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 64 deletions.
8 changes: 8 additions & 0 deletions src/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ pub struct DnaResource {
pub nick: String,
}

#[derive(Debug, Clone, Deserialize)]
pub struct HostSettings {
pub is_enabled: bool,
pub is_host_disabled: bool,
pub is_auto_disabled: bool,
}

#[derive(Deserialize, Debug, Clone)]
pub struct PresentedHappBundle {
pub id: ActionHashB64,
Expand All @@ -34,6 +41,7 @@ pub struct PresentedHappBundle {
pub bundle_url: String,
pub name: String,
pub special_installed_app_id: Option<String>,
pub host_settings: HostSettings,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
Expand Down
6 changes: 4 additions & 2 deletions src/host_zome_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct HappBundle {
pub happ_id: ActionHashB64,
pub bundle_url: String,
pub is_paused: bool,
pub is_host_disabled: bool,
pub special_installed_app_id: Option<String>,
}

Expand Down Expand Up @@ -117,10 +118,10 @@ pub fn fresh_nonce() -> Result<(Nonce256Bits, Timestamp)> {
Ok((nonce, expires))
}

pub async fn get_all_enabled_hosted_happs(
pub async fn get_all_published_hosted_happs(
core_app_client: &mut CoreAppClient,
) -> Result<Vec<HappBundle>> {
trace!("get_all_enabled_hosted_happs");
trace!("get_all_published_hosted_happs");

let happ_bundles: Vec<entries::PresentedHappBundle> = core_app_client
.zome_call(ZomeName::from("hha"), FunctionName::from("get_happs"), ())
Expand All @@ -140,6 +141,7 @@ pub async fn get_all_enabled_hosted_happs(
happ_id: happ.id,
bundle_url: happ.bundle_url,
is_paused: happ.is_paused,
is_host_disabled: happ.host_settings.is_host_disabled,
special_installed_app_id: happ.special_installed_app_id,
}
})
Expand Down
45 changes: 29 additions & 16 deletions src/install_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,62 +48,74 @@ pub async fn install_holo_hosted_happs(
warn!(port = ?config.happ_port, ?error, "failed to start app interface, maybe it's already up?");
}

let active_happs = Arc::new(
let running_happs = Arc::new(
admin_websocket
.list_running_app()
.await
.context("failed to get installed hApps")?,
);

trace!("active_happs {:?}", active_happs);
trace!("running_happs {:?}", running_happs);

let client = reqwest::Client::new();

// iterate through the vec and
// Iterate through the vec and
// Call http://localhost/holochain-api/install_hosted_happ
// for each WrappedActionHash to install the hosted_happ
for HappBundle {
happ_id,
bundle_url,
is_paused,
is_host_disabled,
special_installed_app_id,
} in happs
{
// if special happ is installed and do nothing if it is installed
// Check if special happ is installed and do nothing if it is installed
trace!("Trying to install {}", happ_id);
if special_installed_app_id.is_some()
&& active_happs.contains(&format!("{}::servicelogger", happ_id))
&& running_happs.contains(&format!("{}::servicelogger", happ_id))
{
// We do not need to install bc we never pause this app as we do not want our core-app to be uninstalled ever
trace!(
"Special App {:?} already installed",
special_installed_app_id
);
// We do not pause here because we do not want our core-app to be uninstalled ever
}
// Check if happ is already installed and deactivate it if happ is paused in hha
// This will miss hosted holofuel as that happ is never installed under it's happ_id
// So we will always try and fail to install holofuel again
// Check if happ is already installed and disable it if the publisher has paused happ in hha
// NB: This condition/check will miss hosted holofuel as that happ is never installed under its happ_id
// This means it will always try and fail to install holofuel again
// Right now, we don't care
else if active_happs.contains(&format!("{}", happ_id)) {
else if running_happs.contains(&format!("{}", happ_id)) {
trace!("App {} already installed", happ_id);
if *is_paused {
trace!("Pausing {}", happ_id);
admin_websocket.deactivate_app(&happ_id.to_string()).await?;
admin_websocket.disable_app(&happ_id.to_string()).await?;
} else {
// If a happ is already installed, check if it should be enabled
// Check if installed happ is eligible to be enabled for host and enable, if so
// NB: This check only compares price settings with kyc level for now
if is_kyc_level_2 || is_happ_free(&happ_id.to_string(), core_app_client).await? {
trace!("Enabling {}", happ_id);
admin_websocket.enable_app(&happ_id.to_string()).await?;
} else {
trace!("Not enabling installed app {}", happ_id);
trace!(
"Not enabling installed {} app due to failed price check for kyc level",
happ_id
);
}
}
}
// if kyc_level is not 2 and the happ is not free, we don't instal
// if the expected happ is disabled by the host, we don't install
else if is_host_disabled.to_owned() {
trace!(
"Skipping happ installation due to host's disabled setting for happ {}",
happ_id
);
}
// if kyc_level is not 2 and the happ is not free, we don't install
else if !is_kyc_level_2 && !is_happ_free(&happ_id.to_string(), core_app_client).await? {
trace!("Skipping non-free happ due to kyc level {}", happ_id);
trace!("Skipping paid happ due to kyc level {}", happ_id);
}
// else installed the hosted happ read-only instance
// else install the hosted happ read-only instance
else {
trace!("Load mem-proofs for {}", happ_id);
let mem_proof: HashMap<String, MembraneProof> =
Expand All @@ -113,6 +125,7 @@ pub async fn install_holo_hosted_happs(
happ_id,
mem_proof
);

// We'd like to move the logic from `install_hosted_happ` out of `hpos-holochain-api` and into this service where it belongs
let body = entries::InstallHappBody {
happ_id: happ_id.to_string(),
Expand Down
13 changes: 7 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,30 @@ pub mod websocket;
use anyhow::Result;
pub use websocket::{AdminWebsocket, AppWebsocket};
pub mod host_zome_calls;
use host_zome_calls::get_all_enabled_hosted_happs;
use host_zome_calls::get_all_published_hosted_happs;
mod install_app;
use install_app::install_holo_hosted_happs;
mod uninstall_apps;
use tracing::{debug, info};
use uninstall_apps::uninstall_removed_happs;
use uninstall_apps::uninstall_ineligible_happs;
mod get_kyc_level;
use get_kyc_level::{get_kyc_level, KycLevel};

use crate::host_zome_calls::CoreAppClient;

/// gets all the enabled happs from HHA
/// installs new happs that were enabled or registered by its provider
/// and uninstalles old happs that were disabled or deleted by its provider
/// installs and enables new happs that were registered by a provider and holochain disables those paused by provider in hha
/// then uninstalls happs that are ineligible for host (eg: holo-disabled, unallowed pricing for kyc level)
pub async fn run(core_happ: &config::Happ, config: &config::Config) -> Result<()> {
info!("Activating holo hosted apps");
let kyc_level = get_kyc_level().await?;
debug!("Got kyc level {:?}", &kyc_level);
let is_kyc_level_2 = kyc_level == KycLevel::Level2;

let mut core_app_client = CoreAppClient::connect(core_happ, config).await?;
let list_of_happs = get_all_enabled_hosted_happs(&mut core_app_client).await?;
let list_of_happs = get_all_published_hosted_happs(&mut core_app_client).await?;
install_holo_hosted_happs(&mut core_app_client, config, &list_of_happs, is_kyc_level_2).await?;
uninstall_removed_happs(&mut core_app_client, config, &list_of_happs, is_kyc_level_2).await?;
uninstall_ineligible_happs(&mut core_app_client, config, &list_of_happs, is_kyc_level_2)
.await?;
Ok(())
}
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ async fn spawn() -> Result<()> {
match &core_happ_list {
Some(core) => holo_auto_installer::run(core, &config).await,
None => {
error!("No Core apps found in configuration");
Err(anyhow!("Please check that the happ config file is present. No Core apps found in configuration"))
error!("No core apps found in configuration");
Err(anyhow!("Please check that the happ config file is present. No core apps found in configuration"))
}
}
}
72 changes: 41 additions & 31 deletions src/uninstall_apps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,35 @@ use anyhow::{Context, Result};
use itertools::Itertools;
use tracing::{info, trace, warn};

/// uninstalled old hosted happs
/// Currently this completely removes the happ
/// This will be updated to checked enabled uninstalled and disable the happ accordingly
pub async fn uninstall_removed_happs(
/// Ineligible Happs = old holo-hosted happs, holo-disabled happs, or happs with invalid pricing for kyc level
/// Handles ineligible happs for 2 cases - identified and anonymous hosted agents:
/// - Identified: Uninstalls & removes identified instances of ineligible happs
/// - Anonymous: Disables anonymous instance of ineligible happs
pub async fn uninstall_ineligible_happs(
core_app_client: &mut CoreAppClient,
config: &config::Config,
expected_happs: &[HappBundle],
published_happs: &[HappBundle],
is_kyc_level_2: bool,
) -> Result<()> {
info!("Checking to uninstall happs that were removed from the hosted list....");

let mut admin_websocket = AdminWebsocket::connect(config.admin_port)
.await
.context("failed to connect to holochain's admin interface")?;
.context("Failed to connect to holochain's admin interface")?;

let running_happ_ids = admin_websocket
.list_running_app()
.await
.context("failed to get installed hApps")?;
.context("Failed to get installed and running hApps")?;

let unique_running_happ_ids: Vec<&String> = running_happ_ids.iter().unique().collect();

trace!("unique_running_happ_ids {:?}", unique_running_happ_ids);
trace!("Unique_running_happ_ids {:?}", unique_running_happ_ids);

for happ_id in unique_running_happ_ids {
if should_be_installed(core_app_client, happ_id, expected_happs, is_kyc_level_2).await {
if should_be_installed(core_app_client, happ_id, published_happs, is_kyc_level_2).await {
info!(
"Skipping uninstall of {} as it should be installed",
"Skipping uninstall of {} as it should remain installed",
happ_id
);
continue;
Expand All @@ -47,12 +48,12 @@ pub async fn uninstall_removed_happs(
admin_websocket.uninstall_app(happ_id).await?;
}
}
info!("Done uninstall happs that were removed from the hosted list.");
info!("Done uninstalling happs that were removed from the hosted list.");

Ok(())
}

// There are core infrastructure happs that should never be uninstall. All uninstallable happs start with "uhCkk" and don't contain ::servicelogger
// There are core infrastructure happs that should never be uninstalled. All uninstallable happs start with "uhCkk" and don't contain ::servicelogger
fn is_hosted_happ(app: &str) -> bool {
app.starts_with("uhCkk") && !app.contains("::servicelogger")
}
Expand All @@ -61,30 +62,49 @@ fn is_anonymous_instance(happ_id: &str) -> bool {
happ_id.starts_with("uhCkk") && happ_id.len() == 53
}

/// Returns true if `installed_app_id` represents an anonymous or identified instance of `happ_id`
fn is_instance_of_happ(happ_id: &str, installed_app_id: &str) -> bool {
// An `installed_app_id` is one of
// - A core hApp (e.g. `servicelogger:0_2_1::251e7cc8-9c48-4841-9eb0-435f0bf97373`)
// - An anonymous instance with installed_app_id == happ_id
// - An identified instance matching /happ_id::agent_id/
// - A happ-specific servicelogger instance matching /happ_id::servicelogger/
installed_app_id.starts_with(happ_id) && !installed_app_id.ends_with("servicelogger")
}

pub async fn should_be_installed(
core_app_client: &mut CoreAppClient,
running_happ_id: &String,
expected_happs: &[HappBundle],
published_happs: &[HappBundle],
is_kyc_level_2: bool,
) -> bool {
trace!("should_be_installed {}", running_happ_id);
trace!("`should_be_installed check` for {}", running_happ_id);

if !is_hosted_happ(running_happ_id) {
trace!("keeping infrastructure happ {}", running_happ_id);
trace!("Keeping infrastructure happ {}", running_happ_id);
return true;
}

let expected_happ = expected_happs.iter().find(|expected_happ| {
is_instance_of_happ(&expected_happ.happ_id.to_string(), running_happ_id)
// The running happ is an instance of an expected happ
let expected_happ = published_happs.iter().find(|published_happ| {
is_instance_of_happ(&published_happ.happ_id.to_string(), running_happ_id)
});

trace!(
"found expected_happ {:?}",
"Found expected_happ {:?}",
&expected_happ.map(|eh| &eh.happ_id)
);

if let Some(expected_happ) = expected_happ {
// The running happ is an instance of an expected happ
// if the expected happ is disabled by the host, happ shouldn't be installed
if expected_happ.is_host_disabled {
trace!(
"Disabling happ {} because host was disabled it in hha",
expected_happ.happ_id
);
return false;
}

if is_kyc_level_2 {
// nothing more to check, we should keep this happ
true
Expand All @@ -93,25 +113,15 @@ pub async fn should_be_installed(
match is_happ_free(&expected_happ.happ_id.to_string(), core_app_client).await {
Ok(is_free) => is_free,
Err(e) => {
warn!("is_happ_free failed with {}", e);
warn!("`is_happ_free` check failed with {}", e);
false
}
};
// if kyc is not level 2 and happ isn't free, we should not install
// if kyc is not level 2 and happ isn't free, happ shouldn't be installed
is_free
}
} else {
// The running happ is not an instance of any expected happ, so shouldn't be installed
false
}
}

/// Returns true if `installed_app_id` represents an anonymous or identified instance of `happ_id`
fn is_instance_of_happ(happ_id: &str, installed_app_id: &str) -> bool {
// An `installed_app_id` is one of
// - A core hApp (e.g. `servicelogger:0_2_1::251e7cc8-9c48-4841-9eb0-435f0bf97373`)
// - An anonymous instance with installed_app_id == happ_id
// - An identified instance matching /happ_id::agent_id/
// - A happ-specific servicelogger instance matching /happ_id::servicelogger/
installed_app_id.starts_with(happ_id) && !installed_app_id.ends_with("servicelogger")
}
7 changes: 0 additions & 7 deletions src/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,6 @@ impl AdminWebsocket {
Ok(running)
}

pub async fn deactivate_app(&mut self, installed_app_id: &str) -> Result<AdminResponse> {
let msg = AdminRequest::DisableApp {
installed_app_id: installed_app_id.to_string(),
};
self.send(msg).await
}

pub async fn uninstall_app(&mut self, installed_app_id: &str) -> Result<AdminResponse> {
let msg = AdminRequest::UninstallApp {
installed_app_id: installed_app_id.to_string(),
Expand Down

0 comments on commit 99ab67b

Please sign in to comment.