<commit_before>// TODO (CM): Take another look at the public exports of this module
// (specifically, `pub mod spec`, and the various `pub use`
// statements. Playing fast-and-loose with our imports has led to code
// that's more confusing that it probably needs to be.

// TODO (CM): Take a deeper look at the direct consumption of
// Prost-generated types (habitat_sup_protocol::types::*) in
// here. Ideally, those would exist only at the periphery of the
// system, and we'd use separate internal types for our core logic.

mod context;
mod health;
mod hook_runner;
mod hooks;
#[cfg(windows)]
mod pipe_hook_client;
mod spawned_future;
pub mod spec;
mod supervisor;
mod terminator;

use self::{context::RenderContext,
           hook_runner::HookRunner,
           hooks::{HookCompileTable,
                   HookTable},
           supervisor::Supervisor};
pub use self::{health::{HealthCheckHookStatus,
                        HealthCheckResult},
               hooks::{HealthCheckHook,
                       ProcessOutput,
                       StandardStreams},
               spec::{DesiredState,
                      ServiceSpec}};
use crate::{census::{CensusGroup,
                     CensusRing,
                     ElectionStatus,
                     ServiceFile},
            error::{Error,
                    Result},
            manager::{sync::GatewayState,
                      FsCfg,
                      ShutdownConfig,
                      Sys},
            sup_futures};
use futures::{future,
              Future,
              IntoFuture};
use habitat_butterfly::rumor::service::Service as ServiceRumor;
#[cfg(windows)]
use habitat_common::templating::package::DEFAULT_USER;
pub use habitat_common::templating::{config::{Cfg,
                                              UserConfigPath},
                                     package::{Env,
                                               Pkg,
                                               PkgProxy}};
use habitat_common::{outputln,
                     templating::{config::CfgRenderer,
                                  hooks::Hook}};
#[cfg(windows)]
use habitat_core::os::users;
use habitat_core::{crypto::hash,
                   fs::{atomic_write,
                        svc_hooks_path,
                        SvcDir,
                        FS_ROOT_PATH},
                   os::process::ShutdownTimeout,
                   package::{metadata::Bind,
                             PackageIdent,
                             PackageInstall},
                   service::{HealthCheckInterval,
                             ServiceBind,
                             ServiceGroup},
                   ChannelIdent};
use habitat_launcher_client::LauncherCli;
use habitat_sup_protocol::types::BindingMode;
pub use habitat_sup_protocol::types::{ProcessState,
                                      Topology,
                                      UpdateStrategy};
use parking_lot::RwLock;
use prometheus::{HistogramTimer,
                 HistogramVec};
use serde::{ser::SerializeStruct,
            Serialize,
            Serializer};
use std::{self,
          collections::HashSet,
          fmt,
          fs,
          ops::Deref,
          path::{Path,
                 PathBuf},
          result,
          sync::{Arc,
                 Mutex}};
use time::Timespec;
use tokio::runtime::TaskExecutor;

static LOGKEY: &str = "SR";

#[cfg(not(windows))]
pub const GOSSIP_FILE_PERMISSIONS: u32 = 0o640;

lazy_static! {
    static ref HOOK_DURATION: HistogramVec =
        register_histogram_vec!("hab_sup_hook_duration_seconds",
                                "The time it takes for a hook to run",
                                &["hook"]).unwrap();
}

/// When evaluating whether a particular service group can satisfy a
/// bind of the Service, there are several states it can be
/// in. Depending on which point in the lifecycle of the Service we
/// are in, we may want to take different actions depending on the
/// current status.
enum BindStatus<'a> {
    /// The bound group is not present in the census
    NotPresent,
    /// The bound group is present in the census, but has no active
    /// members.
    Empty,
    /// The bound group is present in the census, has active members,
    /// but does not satisfy the contract of the bind; the set of
    /// unsatisfied exports is returned.
    Unsatisfied(HashSet<&'a String>),
    /// The bound group is present, has active members, and fully
    /// satisfies the contract of the bind.
    Satisfied,
    /// An error was encountered determining the status
    Unknown(Error),
}

/// Encapsulate changes to `/hooks` and `/config`.
#[derive(Default)]
struct TemplateUpdate {
    hooks:                 HookCompileTable,
    config_changed:        bool,
    have_reconfigure_hook: bool,
}

impl TemplateUpdate {
    fn new(hooks: HookCompileTable, config_changed: bool, have_reconfigure_hook: bool) -> Self {
        Self { hooks,
               config_changed,
               have_reconfigure_hook }
    }

    /// Returns `true` if the service needs to be restarted.
    ///
    /// A restart is needed under the following conditions:
    /// 1. the `run` or `post-run` hooks have changed. A restart is limited to these hooks
    /// because they are the only hooks that can impact the execution of the service.
    /// 2. `/config` changed and there is no `reconfigure` hook
    fn needs_restart(&self) -> bool {
        self.hooks.run_changed()
        || self.hooks.post_run_changed()
        || (!self.have_reconfigure_hook && self.config_changed)
    }

    /// Returns `true` if the service needs to be reconfigured.
    ///
    /// A reconfigure is needed if `/config` or the `reconfigure` hook changed.
    fn needs_reconfigure(&self) -> bool {
        self.config_changed || self.hooks.reconfigure_changed() || self.hooks.reload_changed()
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
enum InitializationState {
    Uninitialized,
    Initializing,
    InitializerFinished,
    Initialized,
}

#[derive(Debug)]
pub struct Service {
    pub service_group:       ServiceGroup,
    pub bldr_url:            String,
    pub channel:             ChannelIdent,
    pub desired_state:       DesiredState,
    pub spec_file:           PathBuf,
    pub spec_ident:          PackageIdent,
    pub topology:            Topology,
    pub update_strategy:     UpdateStrategy,
    pub cfg:                 Cfg,
    pub pkg:                 Pkg,
    pub sys:                 Arc<Sys>,
    pub user_config_updated: bool,
    pub shutdown_timeout:    Option<ShutdownTimeout>,
    // TODO (DM): This flag is a temporary hack to signal to the `Manager` that this service needs
    // to be restarted. As we continue refactoring lifecycle hooks this flag should be removed.
    pub needs_restart: bool,
    // TODO (DM):
    initialization_state: Arc<RwLock<InitializationState>>,

    config_renderer: CfgRenderer,
    // Note: This field is really only needed for serializing a
    // Service in the gateway (see ServiceProxy's Serialize
    // implementation). Ideally, we could get rid of this, since we're
    // *also* storing the health check result directly (see
    // manager::GatewayState#health_check_data), but because of how<commit_msg>Add comment on need to track initialization_state<commit_after>// TODO (CM): Take another look at the public exports of this module
// (specifically, `pub mod spec`, and the various `pub use`
// statements. Playing fast-and-loose with our imports has led to code
// that's more confusing that it probably needs to be.

// TODO (CM): Take a deeper look at the direct consumption of
// Prost-generated types (habitat_sup_protocol::types::*) in
// here. Ideally, those would exist only at the periphery of the
// system, and we'd use separate internal types for our core logic.

mod context;
mod health;
mod hook_runner;
mod hooks;
#[cfg(windows)]
mod pipe_hook_client;
mod spawned_future;
pub mod spec;
mod supervisor;
mod terminator;

use self::{context::RenderContext,
           hook_runner::HookRunner,
           hooks::{HookCompileTable,
                   HookTable},
           supervisor::Supervisor};
pub use self::{health::{HealthCheckHookStatus,
                        HealthCheckResult},
               hooks::{HealthCheckHook,
                       ProcessOutput,
                       StandardStreams},
               spec::{DesiredState,
                      ServiceSpec}};
use crate::{census::{CensusGroup,
                     CensusRing,
                     ElectionStatus,
                     ServiceFile},
            error::{Error,
                    Result},
            manager::{sync::GatewayState,
                      FsCfg,
                      ShutdownConfig,
                      Sys},
            sup_futures};
use futures::{future,
              Future,
              IntoFuture};
use habitat_butterfly::rumor::service::Service as ServiceRumor;
#[cfg(windows)]
use habitat_common::templating::package::DEFAULT_USER;
pub use habitat_common::templating::{config::{Cfg,
                                              UserConfigPath},
                                     package::{Env,
                                               Pkg,
                                               PkgProxy}};
use habitat_common::{outputln,
                     templating::{config::CfgRenderer,
                                  hooks::Hook}};
#[cfg(windows)]
use habitat_core::os::users;
use habitat_core::{crypto::hash,
                   fs::{atomic_write,
                        svc_hooks_path,
                        SvcDir,
                        FS_ROOT_PATH},
                   os::process::ShutdownTimeout,
                   package::{metadata::Bind,
                             PackageIdent,
                             PackageInstall},
                   service::{HealthCheckInterval,
                             ServiceBind,
                             ServiceGroup},
                   ChannelIdent};
use habitat_launcher_client::LauncherCli;
use habitat_sup_protocol::types::BindingMode;
pub use habitat_sup_protocol::types::{ProcessState,
                                      Topology,
                                      UpdateStrategy};
use parking_lot::RwLock;
use prometheus::{HistogramTimer,
                 HistogramVec};
use serde::{ser::SerializeStruct,
            Serialize,
            Serializer};
use std::{self,
          collections::HashSet,
          fmt,
          fs,
          ops::Deref,
          path::{Path,
                 PathBuf},
          result,
          sync::{Arc,
                 Mutex}};
use time::Timespec;
use tokio::runtime::TaskExecutor;

static LOGKEY: &str = "SR";

#[cfg(not(windows))]
pub const GOSSIP_FILE_PERMISSIONS: u32 = 0o640;

lazy_static! {
    static ref HOOK_DURATION: HistogramVec =
        register_histogram_vec!("hab_sup_hook_duration_seconds",
                                "The time it takes for a hook to run",
                                &["hook"]).unwrap();
}

/// When evaluating whether a particular service group can satisfy a
/// bind of the Service, there are several states it can be
/// in. Depending on which point in the lifecycle of the Service we
/// are in, we may want to take different actions depending on the
/// current status.
enum BindStatus<'a> {
    /// The bound group is not present in the census
    NotPresent,
    /// The bound group is present in the census, but has no active
    /// members.
    Empty,
    /// The bound group is present in the census, has active members,
    /// but does not satisfy the contract of the bind; the set of
    /// unsatisfied exports is returned.
    Unsatisfied(HashSet<&'a String>),
    /// The bound group is present, has active members, and fully
    /// satisfies the contract of the bind.
    Satisfied,
    /// An error was encountered determining the status
    Unknown(Error),
}

/// Encapsulate changes to `/hooks` and `/config`.
#[derive(Default)]
struct TemplateUpdate {
    hooks:                 HookCompileTable,
    config_changed:        bool,
    have_reconfigure_hook: bool,
}

impl TemplateUpdate {
    fn new(hooks: HookCompileTable, config_changed: bool, have_reconfigure_hook: bool) -> Self {
        Self { hooks,
               config_changed,
               have_reconfigure_hook }
    }

    /// Returns `true` if the service needs to be restarted.
    ///
    /// A restart is needed under the following conditions:
    /// 1. the `run` or `post-run` hooks have changed. A restart is limited to these hooks
    /// because they are the only hooks that can impact the execution of the service.
    /// 2. `/config` changed and there is no `reconfigure` hook
    fn needs_restart(&self) -> bool {
        self.hooks.run_changed()
        || self.hooks.post_run_changed()
        || (!self.have_reconfigure_hook && self.config_changed)
    }

    /// Returns `true` if the service needs to be reconfigured.
    ///
    /// A reconfigure is needed if `/config` or the `reconfigure` hook changed.
    fn needs_reconfigure(&self) -> bool {
        self.config_changed || self.hooks.reconfigure_changed() || self.hooks.reload_changed()
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
enum InitializationState {
    Uninitialized,
    Initializing,
    InitializerFinished,
    Initialized,
}

#[derive(Debug)]
pub struct Service {
    pub service_group:       ServiceGroup,
    pub bldr_url:            String,
    pub channel:             ChannelIdent,
    pub desired_state:       DesiredState,
    pub spec_file:           PathBuf,
    pub spec_ident:          PackageIdent,
    pub topology:            Topology,
    pub update_strategy:     UpdateStrategy,
    pub cfg:                 Cfg,
    pub pkg:                 Pkg,
    pub sys:                 Arc<Sys>,
    pub user_config_updated: bool,
    pub shutdown_timeout:    Option<ShutdownTimeout>,
    // TODO (DM): This flag is a temporary hack to signal to the `Manager` that this service needs
    // to be restarted. As we continue refactoring lifecycle hooks this flag should be removed.
    pub needs_restart: bool,
    // TODO (DM): The need to track initialization state across ticks would be removed if we
    // migrated away from the event loop architecture to an architecture that had a top level
    // `Service` future. See https://github.com/habitat-sh/habitat/issues/7112
    initialization_state: Arc<RwLock<InitializationState>>,

    config_renderer: CfgRenderer,
    // Note: This field is really only needed for serializing a
    // Service in the gateway (see ServiceProxy's Serialize
    // implementation). Ideally, we could get rid of this, since we're
    // *also* storing the health check result directly (see
    // manager::GatewayState#health_check_data), but because of how
{'fraction_non_alphanumeric': 0.09059092744649347, 'fraction_numerical': 0.0013460761879122358, 'mean_word_length': 1.9576035031847134, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 21, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5379366', 'n_tokens_mistral': 3815, 'n_tokens_neox': 3782, 'n_words': 1237}