Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generate most of bhyve config #8

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions lib/oozone/brand_config_decorators/bhyve.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

module Oozone
module ConfigDecorator
# The bhyve installer is strongly opinionated. Here it fills in most of
# the zone config file for you.
class Bhyve
def initialize(config, metadata)
@config = config
@metadata = metadata
@bootdisk = File.join('rpool', 'zones', 'bhyve', @metadata[:zone_name])
end

def decorate!
ret = add_zvol_device(@config)
ret = add_cloudinit_cdrom(ret) if @metadata[:cloudinit]
add_boot_attrs(ret)
end

# rubocop:disable Metrics/MethodLength
def add_boot_attrs(config)
to_add = [
['add attr',
['set name=bootrom'],
['set type=string'],
['set value=BHYVE_RELEASE'],
'end'],
['add attr',
['set name=bootdisk'],
['set type=string'],
["set value=#{@bootdisk}"],
'end'],
['add attr',
['set name=acpi'],
['set type=string'],
['set value=false'],
'end']
]

if @metadata[:cloudinit]
to_add << ['add attr',
['set name=cdrom'],
['set type=string'],
["set value=#{@metadata[:cloudinit_iso_file]}"],
'end']
end

config << to_add
end
# rubocop:enable Metrics/MethodLength

def add_cloudinit_cdrom(config)
config << [
['add fs',
["set dir=#{@metadata[:cloudinit_iso_file]}"],
["set special=#{@metadata[:cloudinit_iso_file]}"],
['set type=lofs'],
['set options=ro'],
'end']
]
end

def add_zvol_device(config)
config << [
['add device', ["set match=/dev/zvol/rdsk/#{@bootdisk}"], 'end']
]
end
end
end
end
113 changes: 110 additions & 3 deletions lib/oozone/brand_installers/bhyve.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
# frozen_string_literal: true

require 'erb'
require 'pathname'
require_relative 'ipkg'
require_relative '../constants'

module Oozone
module BrandInstaller
#
# This requires an extra field disk_size
# Build a bhyve zone from a Cloud-init-ready image, configuring the network
# and whatnot from ERB template files.
#
# rubocop:disable Metrics/ClassLength
class Bhyve < Ipkg
# rubocop:disable Metrics/AbcSize
def install!
volume_size = conf.metadata.fetch(:volume_size, nil)
abort 'no volume_size' if volume_size.nil?
Expand All @@ -18,9 +24,11 @@ def install!
src = conf.metadata.fetch(:raw_image, nil)
abort 'No raw_image in zone config' if src.nil?
write_raw_image(src, dataset)

create_cloudinit_iso
execute!("#{ZONEADM} -z #{zone} install")
rewrite_zone_config
end
# rubocop:enable Metrics/AbcSize

def create_volume(size, dataset)
if executes_successfully?("#{ZFS} list #{dataset}")
Expand All @@ -35,8 +43,107 @@ def create_volume(size, dataset)
def write_raw_image(src, dataset)
ds_target = "/dev/zvol/dsk/#{dataset}"
LOG.info("Copying #{src} to #{ds_target}")
execute!("/bin/pv #{src} > #{ds_target}")
execute!("/bin/cat #{src} >#{ds_target}")
end

def rewrite_zone_config
LOG.info('Removing cloud-init CD-ROM from zone')
execute!("#{ZONECFG} -z #{@zone} remove attr name=cdrom")
execute!("#{ZONECFG} -z #{@zone} remove fs type=lofs")
end

def create_cloudinit_iso
img_dir = create_cloudinit_img_dir
create_cloudinit_iso_from_dir(
img_dir,
conf.metadata[:cloudinit_iso_file]
)
end

def fresh_cloudinit_iso_dir(cloudinit_iso_dir = nil)
cloudinit_iso_dir = cloudinit_tmp if cloudinit_iso_dir.nil?

if cloudinit_iso_dir.exist?
LOG.info("Flushing #{cloudinit_iso_dir}")
FileUtils.rm_rf(cloudinit_iso_dir)
end

Pathname.new(FileUtils.mkdir_p(cloudinit_iso_dir).first)
rescue StandardError
LOG.error "Cannot create cloud-init dir #{cloudinit_iso_dir}"
nil
end

# rubocop:disable Metrics/MethodLength
def cloudinit_src_dir(root_dir = Pathname.new('cloud-init'), zone = @zone)
instance_dir = cloudinit_dir(root_dir, zone)
common_dir = cloudinit_dir(root_dir, 'common')

if instance_dir.exist?
LOG.info "Using image-specific cloud-init config in #{instance_dir}"
instance_dir
elsif common_dir.exist?
LOG.info "Using common cloud-init config in #{common_dir}"
common_dir
else
LOG.error "No cloudinit dir. (Tried #{instance_dir}, #{common_dir}"
nil
end
rescue Errno::ENOENT
LOG.error "Failed to resolve #{root_dir}"
nil
end
# rubocop:enable Metrics/MethodLength

def create_cloudinit_img_dir(src_dir = nil, target_dir = nil)
src_dir ||= cloudinit_src_dir
target_dir = fresh_cloudinit_iso_dir(target_dir)

return if target_dir.nil? || src_dir.nil?

LOG.info("Constructing cloud-init CDROM in #{target_dir}")

src_dir.children.each do |f|
render_cloudinit_template(f, target_dir.join(f.basename))
end

target_dir
end

def create_cloudinit_iso_from_dir(src_dir, target_file)
cmd = "#{MKISOFS} -output #{target_file} -volid cidata " \
"-joliet -rock #{src_dir}/"

execute!(cmd)
end

private

def render_cloudinit_template(src, dest)
File.write(dest, ERB.new(File.read(src)).result_with_hash(erb_binding))
end

# Just to be explicit
def erb_binding
netconf = @conf.raw[:net].first
{
zone: @zone,
ip_address: netconf[:'allowed-address'],
gateway: netconf[:defrouter],
default_router: netconf[:defrouter]
}
rescue StandardError
raise 'Cannot parse network config for ERB binding'
end

def cloudinit_tmp
Pathname.new('/tmp').join(conf.metadata[:zone_name])
end

def cloudinit_dir(root_dir, zone_name)
root_dir.join(zone_name).realdirpath
end
end
# rubocop:enable Metrics/ClassLength
end
end
13 changes: 12 additions & 1 deletion lib/oozone/commands/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def using_ansible?(conf)
end

# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
def action_zone(zone_file)
conf = Oozone::ConfigLoader.new(zone_file)
zone_name = conf.metadata[:zone_name]
Expand All @@ -45,11 +46,21 @@ def action_zone(zone_file)
zone.configure
install_or_clone
zone.boot
zone.wait_for_readiness
wait_for_readiness(zone, conf.raw[:brand])

Oozone::Customizer.new(conf).customize!
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength

def wait_for_readiness(zone, brand)
if brand == 'bhyve'
zone.wait_for_readiness_console
else
zone.wait_for_readiness
end
end

def install_or_clone
@installer.install!
end
Expand Down
41 changes: 37 additions & 4 deletions lib/oozone/config_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'yaml'
require 'pathname'
require_relative 'dataset_manager'
require_relative 'constants'

module Oozone
#
Expand All @@ -16,8 +17,12 @@ class ConfigLoader
def initialize(zone_file)
@file = Pathname.new(zone_file)
@raw = raw_config(zone_file)
@metadata = { zone_name:,
root: Pathname.new(raw[:zonepath]).join('root') }
@metadata = {
zone_name:,
root: Pathname.new(raw[:zonepath]).join('root'),
cloudinit_iso_file:
Pathname.new("/tmp/cloudinit_cdrom-#{zone_name}.iso")
}
@config = parsed_config
end

Expand All @@ -28,6 +33,14 @@ def write!(_target = zone_config_file)

private

def decorator_class
"Oozone::ConfigDecorator::#{@raw[:brand].capitalize}"
end

def decorator_class_path
File.join('brand_config_decorators', @raw[:brand])
end

def zone_config_file
ZCONF_DIR.join(@file.basename.to_s.sub(/.yaml/, '.zone'))
end
Expand All @@ -44,11 +57,26 @@ def raw_config(zone_file)
exit 1
end

def load_decorator
require_relative decorator_class_path
Object.const_get(decorator_class)
rescue LoadError
nil
end

def decorated_input(parsed_input)
decorator = load_decorator

return parsed_input if decorator.nil?

decorator.new(parsed_input, @metadata).decorate!
end

def parsed_config
"#{(config_prelude + parse_input).compact.join("\n")}\n"
"#{(config_prelude + decorated_input(parsed_input)).compact.join("\n")}\n"
end

def parse_input
def parsed_input
@raw.map { |k, v| respond_to?(k, true) ? send(k, v) : simple_conv(k, v) }
end

Expand Down Expand Up @@ -131,6 +159,11 @@ def run_ssh(cmds)
nil
end

def cloudinit(defns)
@metadata[:cloudinit] = defns
nil
end

# Used to specify a bhyve volume
def volume_size(defns)
@metadata[:volume_size] = defns
Expand Down
1 change: 1 addition & 0 deletions lib/oozone/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
PUPPET_SERVER_BIN = Pathname.new('/opt/ooce/bin/puppet')
READY_SVC = 'svc:/milestone/multi-user-server:default'
SU = '/bin/su rob -c'
MKISOFS = Pathname.new('/bin/mkisofs')

VOLUME_ROOT_DS = 'rpool/zones/bhyve'

Expand Down
20 changes: 19 additions & 1 deletion lib/oozone/controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require 'pty'
require_relative 'runner'
require_relative 'constants'

Expand Down Expand Up @@ -70,7 +71,7 @@ def wait_for_readiness
LOG.info 'Waiting for zone to be ready'

loop do
break if ready?
return if ready?

sleep 2
end
Expand All @@ -92,6 +93,23 @@ def ready?
) == 'online'
end

# This is a horrible hack to watch a bhyve zone boot.
#
def wait_for_readiness_console
LOG.info 'Waiting for zone to be ready'

PTY.spawn("#{ZLOGIN} -C #{zone}") do |stdout, stdin, _thr|
stdout.each do |line|
stdin.puts "\n" if line.include?('ttyS0')

if line.include?(' login:')
stdin.puts '~.'
return true
end
end
end
end

# @return [String, Nil] maybe shouldn't be nil?
#
def state
Expand Down
4 changes: 3 additions & 1 deletion lib/oozone/customizer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ def customize!
end

def add_facts
return unless meta.key?(:facts)

mk_fact_dir
write_fact_file
end

def install_packages
return unless meta[:packages]
return unless meta.key?(:packages)

LOG.info "installing packages: #{meta[:packages].join(', ')}"
zexecute!(meta[:zone_name], "#{PKG} install #{meta[:packages].join(' ')}")
Expand Down
Loading
Loading