feat: OKD bootstrap automation pretty much complete with a few prompt for manual steps
Some checks failed
Run Check Script / check (pull_request) Failing after 1m12s
Some checks failed
Run Check Script / check (pull_request) Failing after 1m12s
This commit is contained in:
parent
6f746d4c88
commit
f1209b3823
@ -54,7 +54,7 @@ pub async fn get_topology() -> HAClusterTopology {
|
||||
name: "master".to_string(),
|
||||
}],
|
||||
bootstrap_host: LogicalHost {
|
||||
ip: ip!("192.168.1.20"),
|
||||
ip: ip!("192.168.1.10"),
|
||||
name: "bootstrap".to_string(),
|
||||
},
|
||||
workers: vec![],
|
||||
|
@ -237,7 +237,11 @@ impl Router for HAClusterTopology {
|
||||
|
||||
#[async_trait]
|
||||
impl HttpServer for HAClusterTopology {
|
||||
async fn serve_files(&self, url: &Url, remote_path: &Option<String>) -> Result<(), ExecutorError> {
|
||||
async fn serve_files(
|
||||
&self,
|
||||
url: &Url,
|
||||
remote_path: &Option<String>,
|
||||
) -> Result<(), ExecutorError> {
|
||||
self.http_server.serve_files(url, remote_path).await
|
||||
}
|
||||
|
||||
@ -397,7 +401,11 @@ impl TftpServer for DummyInfra {
|
||||
|
||||
#[async_trait]
|
||||
impl HttpServer for DummyInfra {
|
||||
async fn serve_files(&self, _url: &Url, _remote_path: &Option<String>) -> Result<(), ExecutorError> {
|
||||
async fn serve_files(
|
||||
&self,
|
||||
_url: &Url,
|
||||
_remote_path: &Option<String>,
|
||||
) -> Result<(), ExecutorError> {
|
||||
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||
}
|
||||
async fn serve_file_content(&self, _file: &FileContent) -> Result<(), ExecutorError> {
|
||||
|
@ -5,7 +5,11 @@ use harmony_types::net::IpAddress;
|
||||
use harmony_types::net::Url;
|
||||
#[async_trait]
|
||||
pub trait HttpServer: Send + Sync {
|
||||
async fn serve_files(&self, url: &Url, remote_path: &Option<String>) -> Result<(), ExecutorError>;
|
||||
async fn serve_files(
|
||||
&self,
|
||||
url: &Url,
|
||||
remote_path: &Option<String>,
|
||||
) -> Result<(), ExecutorError>;
|
||||
async fn serve_file_content(&self, file: &FileContent) -> Result<(), ExecutorError>;
|
||||
fn get_ip(&self) -> IpAddress;
|
||||
|
||||
|
@ -12,21 +12,22 @@ use super::OPNSenseFirewall;
|
||||
#[async_trait]
|
||||
impl DnsServer for OPNSenseFirewall {
|
||||
async fn register_hosts(&self, hosts: Vec<DnsRecord>) -> Result<(), ExecutorError> {
|
||||
let mut writable_opnsense = self.opnsense_config.write().await;
|
||||
let mut dns = writable_opnsense.dns();
|
||||
let hosts = hosts
|
||||
.iter()
|
||||
.map(|h| {
|
||||
Host::new(
|
||||
h.host.clone(),
|
||||
h.domain.clone(),
|
||||
h.record_type.to_string(),
|
||||
h.value.to_string(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
dns.register_hosts(hosts);
|
||||
Ok(())
|
||||
todo!("Refactor this to use dnsmasq")
|
||||
// let mut writable_opnsense = self.opnsense_config.write().await;
|
||||
// let mut dns = writable_opnsense.dns();
|
||||
// let hosts = hosts
|
||||
// .iter()
|
||||
// .map(|h| {
|
||||
// Host::new(
|
||||
// h.host.clone(),
|
||||
// h.domain.clone(),
|
||||
// h.record_type.to_string(),
|
||||
// h.value.to_string(),
|
||||
// )
|
||||
// })
|
||||
// .collect();
|
||||
// dns.add_static_mapping(hosts);
|
||||
// Ok(())
|
||||
}
|
||||
|
||||
fn remove_record(
|
||||
@ -38,25 +39,26 @@ impl DnsServer for OPNSenseFirewall {
|
||||
}
|
||||
|
||||
async fn list_records(&self) -> Vec<crate::topology::DnsRecord> {
|
||||
self.opnsense_config
|
||||
.write()
|
||||
.await
|
||||
.dns()
|
||||
.get_hosts()
|
||||
.iter()
|
||||
.map(|h| DnsRecord {
|
||||
host: h.hostname.clone(),
|
||||
domain: h.domain.clone(),
|
||||
record_type: h
|
||||
.rr
|
||||
.parse()
|
||||
.expect("received invalid record type {h.rr} from opnsense"),
|
||||
value: h
|
||||
.server
|
||||
.parse()
|
||||
.expect("received invalid ipv4 record from opnsense {h.server}"),
|
||||
})
|
||||
.collect()
|
||||
todo!("Refactor this to use dnsmasq")
|
||||
// self.opnsense_config
|
||||
// .write()
|
||||
// .await
|
||||
// .dns()
|
||||
// .get_hosts()
|
||||
// .iter()
|
||||
// .map(|h| DnsRecord {
|
||||
// host: h.hostname.clone(),
|
||||
// domain: h.domain.clone(),
|
||||
// record_type: h
|
||||
// .rr
|
||||
// .parse()
|
||||
// .expect("received invalid record type {h.rr} from opnsense"),
|
||||
// value: h
|
||||
// .server
|
||||
// .parse()
|
||||
// .expect("received invalid ipv4 record from opnsense {h.server}"),
|
||||
// })
|
||||
// .collect()
|
||||
}
|
||||
|
||||
fn get_ip(&self) -> IpAddress {
|
||||
@ -68,11 +70,12 @@ impl DnsServer for OPNSenseFirewall {
|
||||
}
|
||||
|
||||
async fn register_dhcp_leases(&self, register: bool) -> Result<(), ExecutorError> {
|
||||
let mut writable_opnsense = self.opnsense_config.write().await;
|
||||
let mut dns = writable_opnsense.dns();
|
||||
dns.register_dhcp_leases(register);
|
||||
|
||||
Ok(())
|
||||
todo!("Refactor this to use dnsmasq")
|
||||
// let mut writable_opnsense = self.opnsense_config.write().await;
|
||||
// let mut dns = writable_opnsense.dns();
|
||||
// dns.register_dhcp_leases(register);
|
||||
//
|
||||
// Ok(())
|
||||
}
|
||||
|
||||
async fn commit_config(&self) -> Result<(), ExecutorError> {
|
||||
|
@ -24,7 +24,8 @@ use harmony_types::{id::Id, net::MacAddress};
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, new, Clone, Serialize)]
|
||||
pub struct StaticFilesHttpScore { // TODO this should be split in two scores, one for folder and
|
||||
pub struct StaticFilesHttpScore {
|
||||
// TODO this should be split in two scores, one for folder and
|
||||
// other for files
|
||||
pub folder_to_serve: Option<Url>,
|
||||
pub files: Vec<FileContent>,
|
||||
@ -56,7 +57,9 @@ impl<T: Topology + HttpServer> Interpret<T> for StaticFilesHttpInterpret {
|
||||
http_server.ensure_initialized().await?;
|
||||
// http_server.set_ip(topology.router.get_gateway()).await?;
|
||||
if let Some(folder) = self.score.folder_to_serve.as_ref() {
|
||||
http_server.serve_files(folder, &self.score.remote_path).await?;
|
||||
http_server
|
||||
.serve_files(folder, &self.score.remote_path)
|
||||
.await?;
|
||||
}
|
||||
|
||||
for f in self.score.files.iter() {
|
||||
|
@ -54,6 +54,7 @@ impl OKDBootstrapLoadBalancerScore {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn topology_to_backend_server(topology: &HAClusterTopology, port: u16) -> Vec<BackendServer> {
|
||||
let mut backend: Vec<_> = topology
|
||||
.control_plane
|
||||
@ -63,6 +64,14 @@ impl OKDBootstrapLoadBalancerScore {
|
||||
port,
|
||||
})
|
||||
.collect();
|
||||
|
||||
topology.workers.iter().for_each(|worker| {
|
||||
backend.push(BackendServer {
|
||||
address: worker.ip.to_string(),
|
||||
port,
|
||||
})
|
||||
});
|
||||
|
||||
backend.push(BackendServer {
|
||||
address: topology.bootstrap_host.ip.to_string(),
|
||||
port,
|
||||
|
@ -73,7 +73,11 @@ use crate::{
|
||||
dhcp::DhcpHostBindingScore,
|
||||
http::{IPxeMacBootFileScore, StaticFilesHttpScore},
|
||||
inventory::LaunchDiscoverInventoryAgentScore,
|
||||
okd::{dns::OKDDnsScore, templates::InstallConfigYaml},
|
||||
okd::{
|
||||
bootstrap_load_balancer::OKDBootstrapLoadBalancerScore,
|
||||
dns::OKDDnsScore,
|
||||
templates::{BootstrapIpxeTpl, InstallConfigYaml},
|
||||
},
|
||||
},
|
||||
score::Score,
|
||||
topology::{HAClusterTopology, HostBinding},
|
||||
@ -207,7 +211,7 @@ impl Interpret<HAClusterTopology> for OKDInstallationInterpret {
|
||||
|
||||
info!("Starting OKD installation pipeline",);
|
||||
|
||||
// self.run_inventory_phase(inventory, topology).await?;
|
||||
self.run_inventory_phase(inventory, topology).await?;
|
||||
|
||||
self.run_bootstrap_phase(inventory, topology).await?;
|
||||
|
||||
@ -289,9 +293,23 @@ impl Interpret<HAClusterTopology> for OKDSetup01InventoryInterpret {
|
||||
topology: &HAClusterTopology,
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
info!("Setting up base DNS config for OKD");
|
||||
OKDDnsScore::new(topology)
|
||||
.interpret(inventory, topology)
|
||||
.await?;
|
||||
let cluster_domain = &topology.domain_name;
|
||||
let load_balancer_ip = &topology.load_balancer.get_ip();
|
||||
inquire::Confirm::new(&format!(
|
||||
"Set hostnames manually in your opnsense dnsmasq config :
|
||||
*.apps.{cluster_domain} -> {load_balancer_ip}
|
||||
api.{cluster_domain} -> {load_balancer_ip}
|
||||
api-int.{cluster_domain} -> {load_balancer_ip}
|
||||
|
||||
When you can dig them, confirm to continue.
|
||||
"
|
||||
))
|
||||
.prompt()
|
||||
.expect("Prompt error");
|
||||
// TODO reactivate automatic dns config
|
||||
// OKDDnsScore::new(topology)
|
||||
// .interpret(inventory, topology)
|
||||
// .await?;
|
||||
|
||||
info!(
|
||||
"Launching discovery agent, make sure that your nodes are successfully PXE booted and running inventory agent. They should answer on `http://<node_ip>:8080/inventory`"
|
||||
@ -368,7 +386,7 @@ struct OKDSetup02BootstrapScore {}
|
||||
|
||||
impl Score<HAClusterTopology> for OKDSetup02BootstrapScore {
|
||||
fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
|
||||
Box::new(OKDSetup02BootstrapInterpret::new(self.clone()))
|
||||
Box::new(OKDSetup02BootstrapInterpret::new())
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
@ -378,17 +396,15 @@ impl Score<HAClusterTopology> for OKDSetup02BootstrapScore {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct OKDSetup02BootstrapInterpret {
|
||||
score: OKDSetup02BootstrapScore,
|
||||
version: Version,
|
||||
status: InterpretStatus,
|
||||
}
|
||||
|
||||
impl OKDSetup02BootstrapInterpret {
|
||||
pub fn new(score: OKDSetup02BootstrapScore) -> Self {
|
||||
pub fn new() -> Self {
|
||||
let version = Version::from("1.0.0").unwrap();
|
||||
Self {
|
||||
version,
|
||||
score,
|
||||
status: InterpretStatus::QUEUED,
|
||||
}
|
||||
}
|
||||
@ -572,20 +588,30 @@ impl OKDSetup02BootstrapInterpret {
|
||||
Ok(output)
|
||||
};
|
||||
|
||||
info!("Successfully prepared ignition files for OKD installation");
|
||||
// ignition_files_http_path // = PathBuf::from("okd_ignition_files");
|
||||
let scos_http_path = PathBuf::from("scos");
|
||||
info!(
|
||||
r#"Uploading images, they can be refreshed with a command similar to this one: openshift-install coreos print-stream-json | grep -Eo '"https.*(kernel.|initramfs.|rootfs.)\w+(\.img)?"' | grep x86_64 | xargs -n 1 curl -LO"#
|
||||
);
|
||||
StaticFilesHttpScore {
|
||||
folder_to_serve: Some(Url::LocalFolder(okd_images_path.to_string_lossy().to_string())),
|
||||
remote_path: Some(scos_http_path.to_string_lossy().to_string()),
|
||||
files: vec![],
|
||||
}
|
||||
.interpret(inventory, topology)
|
||||
.await?;
|
||||
|
||||
todo!("What's up next?")
|
||||
warn!(
|
||||
"TODO push installer image files with `scp -r data/okd/installer_image/* root@192.168.1.1:/usr/local/http/scos/` until performance issue is resolved"
|
||||
);
|
||||
inquire::Confirm::new(
|
||||
"push installer image files with `scp -r data/okd/installer_image/* root@192.168.1.1:/usr/local/http/scos/` until performance issue is resolved").prompt().expect("Prompt error");
|
||||
|
||||
// let scos_http_path = PathBuf::from("scos");
|
||||
// StaticFilesHttpScore {
|
||||
// folder_to_serve: Some(Url::LocalFolder(
|
||||
// okd_images_path.to_string_lossy().to_string(),
|
||||
// )),
|
||||
// remote_path: Some(scos_http_path.to_string_lossy().to_string()),
|
||||
// files: vec![],
|
||||
// }
|
||||
// .interpret(inventory, topology)
|
||||
// .await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn configure_host_binding(
|
||||
@ -613,12 +639,25 @@ impl OKDSetup02BootstrapInterpret {
|
||||
inventory: &Inventory,
|
||||
topology: &HAClusterTopology,
|
||||
) -> Result<(), InterpretError> {
|
||||
// Placeholder: use Harmony templates to emit {MAC}.ipxe selecting SCOS live + bootstrap ignition.
|
||||
info!("[Bootstrap] Rendering per-MAC PXE for bootstrap node");
|
||||
let content = BootstrapIpxeTpl {
|
||||
http_ip: &topology.http_server.get_ip().to_string(),
|
||||
scos_path: "scos", // TODO use some constant
|
||||
installation_device: "/dev/sda", // TODO do something smart based on the host drives
|
||||
// topology. Something like use the smallest device
|
||||
// above 200G that is an ssd
|
||||
}
|
||||
.to_string();
|
||||
|
||||
let bootstrap_node = self.get_bootstrap_node().await?;
|
||||
let mac_address = bootstrap_node.get_mac_address();
|
||||
|
||||
info!("[Bootstrap] Rendering per-MAC PXE for bootstrap node");
|
||||
debug!("bootstrap ipxe content : {content}");
|
||||
debug!("bootstrap mac addresses : {mac_address:?}");
|
||||
|
||||
IPxeMacBootFileScore {
|
||||
mac_address: bootstrap_node.get_mac_address(),
|
||||
content: todo!("templace for bootstrap node"),
|
||||
mac_address,
|
||||
content,
|
||||
}
|
||||
.interpret(inventory, topology)
|
||||
.await?;
|
||||
@ -630,15 +669,25 @@ impl OKDSetup02BootstrapInterpret {
|
||||
inventory: &Inventory,
|
||||
topology: &HAClusterTopology,
|
||||
) -> Result<(), InterpretError> {
|
||||
todo!(
|
||||
"OKD loadbalancer score already exists, just call it here probably? 6443 22623, 80 and 443 \n\nhttps://docs.okd.io/latest/installing/installing_bare_metal/upi/installing-bare-metal.html#installation-load-balancing-user-infra_installing-bare-metal"
|
||||
);
|
||||
let outcome = OKDBootstrapLoadBalancerScore::new(topology)
|
||||
.interpret(inventory, topology)
|
||||
.await?;
|
||||
info!("Successfully executed OKDBootstrapLoadBalancerScore : {outcome:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reboot_target(&self) -> Result<(), InterpretError> {
|
||||
// Placeholder: ssh reboot using the inventory ephemeral key
|
||||
info!("[Bootstrap] Rebooting bootstrap node via SSH");
|
||||
todo!("[Bootstrap] Rebooting bootstrap node via SSH")
|
||||
// TODO reboot programatically, there are some logical checks and refactoring to do such as
|
||||
// accessing the bootstrap node config (ip address) from the inventory
|
||||
let confirmation = inquire::Confirm::new(
|
||||
"Now reboot the bootstrap node so it picks up its pxe boot file. Press enter when ready.",
|
||||
)
|
||||
.with_default(true)
|
||||
.prompt()
|
||||
.expect("Unexpected prompt error");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn wait_for_bootstrap_complete(&self) -> Result<(), InterpretError> {
|
||||
|
@ -120,6 +120,7 @@ impl<T: Topology + DhcpServer + TftpServer + HttpServer + Router> Interpret<T> f
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
}
|
||||
inquire::Confirm::new("Execute the copy : `scp -r data/pxe/okd/http_files/* root@192.168.1.1:/usr/local/http/` and confirm when done to continue").prompt().expect("Prompt error");
|
||||
|
||||
Ok(Outcome::success("Ipxe installed".to_string()))
|
||||
}
|
||||
|
@ -5,5 +5,5 @@ pub mod dns;
|
||||
pub mod installation;
|
||||
pub mod ipxe;
|
||||
pub mod load_balancer;
|
||||
pub mod upgrade;
|
||||
pub mod templates;
|
||||
pub mod upgrade;
|
||||
|
@ -8,3 +8,11 @@ pub struct InstallConfigYaml<'a> {
|
||||
pub ssh_public_key: &'a str,
|
||||
pub cluster_name: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "okd/bootstrap.ipxe.j2")]
|
||||
pub struct BootstrapIpxeTpl<'a> {
|
||||
pub http_ip: &'a str,
|
||||
pub scos_path: &'a str,
|
||||
pub installation_device: &'a str,
|
||||
}
|
||||
|
7
harmony/templates/okd/bootstrap.ipxe.j2
Normal file
7
harmony/templates/okd/bootstrap.ipxe.j2
Normal file
@ -0,0 +1,7 @@
|
||||
set base-url http://{{ http_ip }}:8080
|
||||
set scos-base-url = ${base-url}/{{ scos_path }}
|
||||
set installation-device = {{ installation_device }}
|
||||
|
||||
kernel ${scos-base-url}/scos-live-kernel.x86_64 initrd=main coreos.live.rootfs_url=${scos-base-url}/scos-live-rootfs.x86_64.img coreos.inst.install_dev=${installation-device} coreos.inst.ignition_url=${base-url}/bootstrap.ign
|
||||
initrd --name main ${scos-base-url}/scos-live-initramfs.x86_64.img
|
||||
boot
|
@ -4,7 +4,7 @@ use crate::{
|
||||
config::{SshConfigManager, SshCredentials, SshOPNSenseShell},
|
||||
error::Error,
|
||||
modules::{
|
||||
caddy::CaddyConfig, dhcp_legacy::DhcpConfigLegacyISC, dns::DnsConfig,
|
||||
caddy::CaddyConfig, dhcp_legacy::DhcpConfigLegacyISC, dns::UnboundDnsConfig,
|
||||
dnsmasq::DhcpConfigDnsMasq, load_balancer::LoadBalancerConfig, tftp::TftpConfig,
|
||||
},
|
||||
};
|
||||
@ -51,8 +51,8 @@ impl Config {
|
||||
DhcpConfigDnsMasq::new(&mut self.opnsense, self.shell.clone())
|
||||
}
|
||||
|
||||
pub fn dns(&mut self) -> DnsConfig<'_> {
|
||||
DnsConfig::new(&mut self.opnsense)
|
||||
pub fn dns(&mut self) -> DhcpConfigDnsMasq<'_> {
|
||||
DhcpConfigDnsMasq::new(&mut self.opnsense, self.shell.clone())
|
||||
}
|
||||
|
||||
pub fn tftp(&mut self) -> TftpConfig<'_> {
|
||||
|
@ -1,10 +1,10 @@
|
||||
use opnsense_config_xml::{Host, OPNsense};
|
||||
|
||||
pub struct DnsConfig<'a> {
|
||||
pub struct UnboundDnsConfig<'a> {
|
||||
opnsense: &'a mut OPNsense,
|
||||
}
|
||||
|
||||
impl<'a> DnsConfig<'a> {
|
||||
impl<'a> UnboundDnsConfig<'a> {
|
||||
pub fn new(opnsense: &'a mut OPNsense) -> Self {
|
||||
Self { opnsense }
|
||||
}
|
||||
|
@ -144,14 +144,16 @@ impl<'a> DhcpConfigDnsMasq<'a> {
|
||||
let host_to_modify_ip = host_to_modify.ip.content_string();
|
||||
if host_to_modify_ip != ip_str {
|
||||
warn!(
|
||||
"Hostname '{}' already exists with a different IP ({}). Appending MAC {}.",
|
||||
"Hostname '{}' already exists with a different IP ({}). Setting new IP {ip_str}. Appending MAC {}.",
|
||||
hostname, host_to_modify_ip, mac
|
||||
);
|
||||
host_to_modify.ip.content = Some(ip_str);
|
||||
} else if host_to_modify.host != hostname {
|
||||
warn!(
|
||||
"IP {} already exists with a different hostname ('{}'). Appending MAC {}.",
|
||||
"IP {} already exists with a different hostname ('{}'). Setting hostname to {hostname}. Appending MAC {}.",
|
||||
ipaddr, host_to_modify.host, mac
|
||||
);
|
||||
host_to_modify.host = hostname.to_string();
|
||||
}
|
||||
|
||||
if !host_to_modify
|
||||
|
Loading…
Reference in New Issue
Block a user