diff --git a/.github/buildomat/jobs/deploy.sh b/.github/buildomat/jobs/deploy.sh index 5e43ff7f7c..c16d523b03 100755 --- a/.github/buildomat/jobs/deploy.sh +++ b/.github/buildomat/jobs/deploy.sh @@ -161,7 +161,7 @@ cd /opt/oxide/work ptime -m tar xvzf /input/package/work/package.tar.gz cp /input/package/work/zones/* out/ -mv out/omicron-nexus-single-sled.tar.gz out/omicron-nexus.tar.gz +mv out/nexus-single-sled.tar.gz out/nexus.tar.gz mkdir tests for p in /input/ci-tools/work/end-to-end-tests/*.gz; do ptime -m gunzip < "$p" > "tests/$(basename "${p%.gz}")" diff --git a/.github/buildomat/jobs/package.sh b/.github/buildomat/jobs/package.sh index 13f374779c..dc89bc787b 100755 --- a/.github/buildomat/jobs/package.sh +++ b/.github/buildomat/jobs/package.sh @@ -84,7 +84,7 @@ stamp_packages() { # Keep the single-sled Nexus zone around for the deploy job. (The global zone # build below overwrites the file.) -mv out/omicron-nexus.tar.gz out/omicron-nexus-single-sled.tar.gz +mv out/nexus.tar.gz out/nexus-single-sled.tar.gz # Build necessary for the global zone ptime -m cargo run --locked --release --bin omicron-package -- \ @@ -115,8 +115,8 @@ zones=( out/crucible-zone.tar.gz out/external-dns.tar.gz out/internal-dns.tar.gz - out/omicron-nexus.tar.gz - out/omicron-nexus-single-sled.tar.gz + out/nexus.tar.gz + out/nexus-single-sled.tar.gz out/oximeter.tar.gz out/propolis-server.tar.gz out/switch-*.tar.gz diff --git a/.github/buildomat/jobs/tuf-repo.sh b/.github/buildomat/jobs/tuf-repo.sh index 14c2293f5b..aca43422d9 100644 --- a/.github/buildomat/jobs/tuf-repo.sh +++ b/.github/buildomat/jobs/tuf-repo.sh @@ -93,7 +93,7 @@ target/release/omicron-package -t default target create -i standard -m gimlet -s ln -s /input/package/work/zones/* out/ rm out/switch-softnpu.tar.gz # not used when target switch=asic rm out/omicron-gateway-softnpu.tar.gz # not used when target switch=asic -rm out/omicron-nexus-single-sled.tar.gz # only used for deploy tests +rm out/nexus-single-sled.tar.gz # only used for deploy tests for zone in out/*.tar.gz; do target/release/omicron-package stamp "$(basename "${zone%.tar.gz}")" "$VERSION" done diff --git a/illumos-utils/src/running_zone.rs b/illumos-utils/src/running_zone.rs index d86a27e3f7..02302347cd 100644 --- a/illumos-utils/src/running_zone.rs +++ b/illumos-utils/src/running_zone.rs @@ -404,7 +404,7 @@ impl RunningZone { /// Returns the filesystem path to the zone's root in the GZ. pub fn root(&self) -> Utf8PathBuf { - self.inner.zonepath.join(Self::ROOT_FS_PATH) + self.inner.root() } pub fn control_interface(&self) -> AddrObject { @@ -1094,6 +1094,9 @@ pub struct InstalledZone { } impl InstalledZone { + /// The path to the zone's root filesystem (i.e., `/`), within zonepath. + pub const ROOT_FS_PATH: &'static str = "root"; + /// Returns the name of a zone, based on the base zone name plus any unique /// identifying info. /// @@ -1135,6 +1138,11 @@ impl InstalledZone { pub fn opte_ports(&self) -> impl Iterator { self.opte_ports.iter().map(|(port, _)| port) } + + /// Returns the filesystem path to the zone's root in the GZ. + pub fn root(&self) -> Utf8PathBuf { + self.zonepath.join(Self::ROOT_FS_PATH) + } } #[derive(Clone)] diff --git a/package-manifest.toml b/package-manifest.toml index 13123df9d0..37c6c56f0c 100644 --- a/package-manifest.toml +++ b/package-manifest.toml @@ -97,9 +97,21 @@ source.paths = [ output.type = "zone" output.intermediate_only = true -[package.omicron-nexus] +[package.nexus] service_name = "nexus" only_for_targets.image = "standard" +source.type = "composite" +source.packages = [ + "omicron-nexus.tar.gz", + "zone-network-setup.tar.gz", + "zone-network-install.tar.gz", + "opte-interface-setup.tar.gz" +] +output.type = "zone" + +[package.omicron-nexus] +service_name = "omicron-nexus" +only_for_targets.image = "standard" source.type = "local" source.rust.binary_names = ["nexus", "schema-updater"] source.rust.release = true @@ -115,6 +127,7 @@ setup_hint = """ - Run `./tools/ci_download_console` to download the web console assets - Run `pkg install library/postgresql-13` to download Postgres libraries """ +output.intermediate_only = true [package.oximeter] service_name = "oximeter" diff --git a/sled-agent/src/services.rs b/sled-agent/src/services.rs index 41acf6c079..c26d6b1191 100644 --- a/sled-agent/src/services.rs +++ b/sled-agent/src/services.rs @@ -2038,6 +2038,155 @@ impl ServiceManager { return Ok(RunningZone::boot(installed_zone).await?); } + ZoneArgs::Omicron(OmicronZoneConfigLocal { + zone: + OmicronZoneConfig { + zone_type: + OmicronZoneType::Nexus { + internal_address, + external_tls, + external_dns_servers, + .. + }, + underlay_address, + id, + }, + .. + }) => { + let Some(info) = self.inner.sled_info.get() else { + return Err(Error::SledAgentNotReady); + }; + + let static_addr = underlay_address.to_string(); + + let nw_setup_service = Self::zone_network_setup_install( + &info, + &installed_zone, + &static_addr.clone(), + )?; + + // While Nexus will be reachable via `external_ip`, it + // communicates atop an OPTE port which operates on a + // VPC private IP. OPTE will map the private IP to the + // external IP automatically. + let opte_interface_setup = + Self::opte_interface_set_up_install(&installed_zone)?; + + let port_idx = 0; + let port = installed_zone + .opte_ports() + .nth(port_idx) + .ok_or_else(|| { + Error::ZoneEnsureAddress( + EnsureAddressError::MissingOptePort { + zone: String::from(installed_zone.name()), + port_idx, + }, + ) + })?; + let opte_ip = port.ip(); + + // Nexus takes a separate config file for parameters + // which cannot be known at packaging time. + let nexus_port = if *external_tls { 443 } else { 80 }; + let deployment_config = DeploymentConfig { + id: *id, + rack_id: info.rack_id, + techport_external_server_port: NEXUS_TECHPORT_EXTERNAL_PORT, + + dropshot_external: ConfigDropshotWithTls { + tls: *external_tls, + dropshot: dropshot::ConfigDropshot { + bind_address: SocketAddr::new(*opte_ip, nexus_port), + // This has to be large enough to support: + // - bulk writes to disks + request_body_max_bytes: 8192 * 1024, + default_handler_task_mode: + HandlerTaskMode::Detached, + }, + }, + dropshot_internal: dropshot::ConfigDropshot { + bind_address: (*internal_address).into(), + // This has to be large enough to support, among + // other things, the initial list of TLS + // certificates provided by the customer during + // rack setup. + request_body_max_bytes: 10 * 1024 * 1024, + default_handler_task_mode: HandlerTaskMode::Detached, + }, + internal_dns: nexus_config::InternalDns::FromSubnet { + subnet: Ipv6Subnet::::new( + info.underlay_address, + ), + }, + database: nexus_config::Database::FromDns, + external_dns_servers: external_dns_servers.clone(), + }; + + // Copy the partial config file to the expected + // location. + let config_dir = Utf8PathBuf::from(format!( + "{}/var/svc/manifest/site/nexus", + installed_zone.root() + )); + // The filename of a half-completed config, in need of + // parameters supplied at runtime. + const PARTIAL_LEDGER_FILENAME: &str = "config-partial.toml"; + // The filename of a completed config, merging the + // partial config with additional appended parameters + // known at runtime. + const COMPLETE_LEDGER_FILENAME: &str = "config.toml"; + let partial_config_path = + config_dir.join(PARTIAL_LEDGER_FILENAME); + let config_path = config_dir.join(COMPLETE_LEDGER_FILENAME); + tokio::fs::copy(partial_config_path, &config_path) + .await + .map_err(|err| Error::io_path(&config_path, err))?; + + // Serialize the configuration and append it into the + // file. + let serialized_cfg = toml::Value::try_from(&deployment_config) + .expect("Cannot serialize config"); + let mut map = toml::map::Map::new(); + map.insert("deployment".to_string(), serialized_cfg); + let config_str = toml::to_string(&map).map_err(|err| { + Error::TomlSerialize { path: config_path.clone(), err } + })?; + let mut file = tokio::fs::OpenOptions::new() + .append(true) + .open(&config_path) + .await + .map_err(|err| Error::io_path(&config_path, err))?; + file.write_all(b"\n\n") + .await + .map_err(|err| Error::io_path(&config_path, err))?; + file.write_all(config_str.as_bytes()) + .await + .map_err(|err| Error::io_path(&config_path, err))?; + + let nexus_config = PropertyGroupBuilder::new("config"); + // .add_property("http_address", "astring", &http_addr) + // .add_property("dns_address", "astring", &dns_addr); + let nexus_service = ServiceBuilder::new("oxide/nexus") + .add_instance( + ServiceInstanceBuilder::new("default") + .add_property_group(nexus_config), + ); + + let profile = ProfileBuilder::new("omicron") + .add_service(nw_setup_service) + .add_service(opte_interface_setup) + .add_service(disabled_ssh_service) + .add_service(nexus_service) + .add_service(disabled_dns_client_service); + profile + .add_to_zone(&self.inner.log, &installed_zone) + .await + .map_err(|err| { + Error::io("Failed to setup Nexus profile", err) + })?; + return Ok(RunningZone::boot(installed_zone).await?); + } _ => {} } @@ -2165,119 +2314,6 @@ impl ServiceManager { smfh.import_manifest()?; match &zone_config.zone.zone_type { - OmicronZoneType::Nexus { - internal_address, - external_tls, - external_dns_servers, - .. - } => { - info!(self.inner.log, "Setting up Nexus service"); - - let sled_info = self - .inner - .sled_info - .get() - .ok_or(Error::SledAgentNotReady)?; - - // While Nexus will be reachable via `external_ip`, it - // communicates atop an OPTE port which operates on a - // VPC private IP. OPTE will map the private IP to the - // external IP automatically. - let port_ip = running_zone - .ensure_address_for_port("public", 0) - .await? - .ip(); - - // Nexus takes a separate config file for parameters - // which cannot be known at packaging time. - let nexus_port = if *external_tls { 443 } else { 80 }; - let deployment_config = DeploymentConfig { - id: zone_config.zone.id, - rack_id: sled_info.rack_id, - techport_external_server_port: - NEXUS_TECHPORT_EXTERNAL_PORT, - - dropshot_external: ConfigDropshotWithTls { - tls: *external_tls, - dropshot: dropshot::ConfigDropshot { - bind_address: SocketAddr::new( - port_ip, nexus_port, - ), - // This has to be large enough to support: - // - bulk writes to disks - request_body_max_bytes: 8192 * 1024, - default_handler_task_mode: - HandlerTaskMode::Detached, - }, - }, - dropshot_internal: dropshot::ConfigDropshot { - bind_address: (*internal_address).into(), - // This has to be large enough to support, among - // other things, the initial list of TLS - // certificates provided by the customer during - // rack setup. - request_body_max_bytes: 10 * 1024 * 1024, - default_handler_task_mode: - HandlerTaskMode::Detached, - }, - internal_dns: - nexus_config::InternalDns::FromSubnet { - subnet: Ipv6Subnet::::new( - sled_info.underlay_address, - ), - }, - database: nexus_config::Database::FromDns, - external_dns_servers: external_dns_servers.clone(), - }; - - // Copy the partial config file to the expected - // location. - let config_dir = Utf8PathBuf::from(format!( - "{}/var/svc/manifest/site/nexus", - running_zone.root() - )); - // The filename of a half-completed config, in need of - // parameters supplied at runtime. - const PARTIAL_LEDGER_FILENAME: &str = - "config-partial.toml"; - // The filename of a completed config, merging the - // partial config with additional appended parameters - // known at runtime. - const COMPLETE_LEDGER_FILENAME: &str = "config.toml"; - let partial_config_path = - config_dir.join(PARTIAL_LEDGER_FILENAME); - let config_path = - config_dir.join(COMPLETE_LEDGER_FILENAME); - tokio::fs::copy(partial_config_path, &config_path) - .await - .map_err(|err| Error::io_path(&config_path, err))?; - - // Serialize the configuration and append it into the - // file. - let serialized_cfg = - toml::Value::try_from(&deployment_config) - .expect("Cannot serialize config"); - let mut map = toml::map::Map::new(); - map.insert("deployment".to_string(), serialized_cfg); - let config_str = - toml::to_string(&map).map_err(|err| { - Error::TomlSerialize { - path: config_path.clone(), - err, - } - })?; - let mut file = tokio::fs::OpenOptions::new() - .append(true) - .open(&config_path) - .await - .map_err(|err| Error::io_path(&config_path, err))?; - file.write_all(b"\n\n") - .await - .map_err(|err| Error::io_path(&config_path, err))?; - file.write_all(config_str.as_bytes()) - .await - .map_err(|err| Error::io_path(&config_path, err))?; - } OmicronZoneType::InternalDns { http_address, dns_address, @@ -2360,6 +2396,7 @@ impl ServiceManager { | OmicronZoneType::CruciblePantry { .. } | OmicronZoneType::ExternalDns { .. } | OmicronZoneType::InternalNtp { .. } + | OmicronZoneType::Nexus { .. } | OmicronZoneType::Oximeter { .. } => { panic!( "{} is a service which exists as part of a \ diff --git a/smf/nexus/manifest.xml b/smf/nexus/manifest.xml index 5a72df8a22..7f1a3e76ff 100644 --- a/smf/nexus/manifest.xml +++ b/smf/nexus/manifest.xml @@ -4,28 +4,41 @@ - + + + + + + + + + + + + diff --git a/tufaceous/README.adoc b/tufaceous/README.adoc index 973a37aa90..86e4cfc4e5 100644 --- a/tufaceous/README.adoc +++ b/tufaceous/README.adoc @@ -31,6 +31,6 @@ tuftool [-r PATH/TO/REPO] add-zone [--name NAME] ZONE_TAR_GZ VERSION Example: ---- -$ tuftool add-zone out/omicron-nexus.tar.gz 0.0.0 -added zone omicron-nexus, version 0.0.0 +$ tuftool add-zone out/nexus.tar.gz 0.0.0 +added zone nexus, version 0.0.0 ---- diff --git a/tufaceous/tests/integration-tests/command_tests.rs b/tufaceous/tests/integration-tests/command_tests.rs index 72c3a1a13a..20c6a06d66 100644 --- a/tufaceous/tests/integration-tests/command_tests.rs +++ b/tufaceous/tests/integration-tests/command_tests.rs @@ -25,7 +25,7 @@ async fn test_init_and_add() -> Result<()> { cmd.assert().success(); // Create a couple of stub files on disk. - let nexus_path = tempdir.path().join("omicron-nexus.tar.gz"); + let nexus_path = tempdir.path().join("nexus.tar.gz"); fs_err::write(&nexus_path, "test")?; let unknown_path = tempdir.path().join("my-unknown-kind.tar.gz"); fs_err::write(&unknown_path, "unknown test")?; @@ -65,7 +65,7 @@ async fn test_init_and_add() -> Result<()> { let mut artifacts_iter = artifacts.artifacts.into_iter(); let artifact = artifacts_iter.next().unwrap(); - assert_eq!(artifact.name, "omicron-nexus", "artifact name"); + assert_eq!(artifact.name, "nexus", "artifact name"); assert_eq!(artifact.version, "42.0.0".parse().unwrap(), "artifact version"); assert_eq!( artifact.kind, @@ -73,7 +73,7 @@ async fn test_init_and_add() -> Result<()> { "artifact kind" ); assert_eq!( - artifact.target, "gimlet_sp-omicron-nexus-42.0.0.tar.gz", + artifact.target, "gimlet_sp-nexus-42.0.0.tar.gz", "artifact target" );