diff --git a/README.md b/README.md index 69ce7e0..2638f90 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ The following driver parameters are common to both instance types, but are not r - `oci_profile_name`, OCI profile to use (default: `DEFAULT`) - `oci_config`, Hash of additional `OCI::Config` settings. Allows you to test without an oci config file [[more](#use-without-oci-config-file)] - `ssh_keypath`, SSH public key (default: `~/.ssh/id_rsa.pub`) + - `ssh_keygen`, Automatically generate the rsa key pair for an instance (default: `false`) [[more](#ssh-keygen)] - `post_create_script`, run a script on an instance after deployment - `post_create_reboot`, reboot the instance after instance creation (default: `false`) - `proxy_url`, Connect via the specified proxy URL [[more](#proxy-support)] @@ -274,6 +275,26 @@ Alternately, if you simply pass a string to the user_data, it will be base64 enc gid: 1000 ``` +## SSH Keygen + +The driver can generate an ssh key pair for an instance during creation. In order to turn this feature on, add the `ssh_keygen` property to the `driver` and set the value to `true`. This can be set in the `driver` section on a +per-platform or per-suite basis, but can also be enabled globally for the entire kitchen.yml in the top-level `driver` section. + +Ensure that the `transport` section does not contain a path to a private key (the `ssh_key` property). If the `transport` has a value in `ssh_key` property, this will mismatch with the key pair that the driver will create causing your +instance creation to be stuck in an endless loop waiting for `transport` to receive a confirmed ssh connection. + +The generated key pair is stored in the `.kitchen/.ssh` directory and is named for the instance that generated it so each instance in your `kitchen.yml` can have its own key pair. + +Upon instance termination (`kitchen destroy`), the generated key pair will be removed from the `.kitchen/.ssh` directory along with the state file as should be expected. + +```yml + driver: + ssh_keygen: true + + transport: + username: opc +``` + ## Proxy support If running Kitchen on a private subnet with no public IPs permitted, it may be necessary to connect to the OCI API via a web proxy. The proxy URL can either be specified on the command line: diff --git a/Rakefile b/Rakefile index 14e825f..b493a52 100644 --- a/Rakefile +++ b/Rakefile @@ -37,7 +37,7 @@ rescue LoadError puts "chefstyle is not available. (sudo) gem install chefstyle to do style checking." end -task build: :default do +task :build do Gem::Package.build(spec, nil, nil, gemfile) end diff --git a/lib/kitchen/driver/oci.rb b/lib/kitchen/driver/oci.rb index 83c8cfa..b058661 100644 --- a/lib/kitchen/driver/oci.rb +++ b/lib/kitchen/driver/oci.rb @@ -35,9 +35,10 @@ module Driver class Oci < Kitchen::Driver::Base # rubocop:disable Metrics/ClassLength require_relative "oci_version" require_relative "oci/models" + require_relative "oci/volumes" plugin_version Kitchen::Driver::OCI_VERSION - kitchen_driver_api_version 1 + kitchen_driver_api_version 2 # required config items required_config :availability_domain @@ -63,6 +64,7 @@ class Oci < Kitchen::Driver::Base # rubocop:disable Metrics/ClassLength default_config :display_name, nil default_keypath = File.expand_path(File.join(%w{~ .ssh id_rsa.pub})) default_config :ssh_keypath, default_keypath + default_config :ssh_keygen, false default_config :post_create_script, nil default_config :proxy_url, nil default_config :user_data, nil @@ -114,6 +116,7 @@ def self.validation_error(message, driver) end include Kitchen::Driver::Oci::Models + include Kitchen::Driver::Oci::Volumes def create(state) return if state[:server_id] @@ -151,33 +154,6 @@ def launch(state, inst) instance.transport.connection(state).wait_until_ready end - def create_and_attach_volumes(config, state, oci, api) - return if config[:volumes].empty? - - volume_state = process_volumes(config, state, oci, api) - state.merge!(volume_state) - end - - def process_volumes(config, state, oci, api) - volume_state = { volumes: [], volume_attachments: [] } - config[:volumes].each do |volume| - vol = volume_class(volume[:type], config, state, oci, api) - volume_details, vol_state = create_volume(vol, volume) - attach_state = vol.attach_volume(volume_details, state[:server_id], volume) - volume_state[:volumes] << vol_state - volume_state[:volume_attachments] << attach_state - end - volume_state - end - - def create_volume(vol, volume) - if volume.key?(:volume_id) - vol.create_clone_volume(volume) - else - vol.create_volume(volume) - end - end - def process_post_script(state) return if config[:post_create_script].nil? @@ -197,7 +173,7 @@ def reboot(state, inst) def detatch_and_delete_volumes(state, oci, api) return unless state[:volumes] - bls = Blockstorage.new(config, state, oci, api, :destroy) + bls = Blockstorage.new(config: config, state: state, oci: oci, api: api, action: :destroy, logger: instance.logger) state[:volume_attachments].each { |att| bls.detatch_volume(att) } state[:volumes].each { |vol| bls.delete_volume(vol) } end @@ -205,6 +181,10 @@ def detatch_and_delete_volumes(state, oci, api) def terminate(state, inst) instance.transport.connection(state).close inst.terminate + if state[:ssh_key] + FileUtils.rm_f(state[:ssh_key]) + FileUtils.rm_f("#{state[:ssh_key]}.pub") + end end end end diff --git a/lib/kitchen/driver/oci/blockstorage.rb b/lib/kitchen/driver/oci/blockstorage.rb index 3e941ea..28879dd 100644 --- a/lib/kitchen/driver/oci/blockstorage.rb +++ b/lib/kitchen/driver/oci/blockstorage.rb @@ -26,15 +26,16 @@ class Blockstorage < Oci # rubocop:disable Metrics/ClassLength require_relative "models/iscsi" require_relative "models/paravirtual" - def initialize(config, state, oci, api, action = :create) + def initialize(opts = {}) super() - @config = config - @state = state - @oci = oci - @api = api + @config = opts[:config] + @state = opts[:state] + @oci = opts[:oci] + @api = opts[:api] + @logger = opts[:logger] @volume_state = {} @volume_attachment_state = {} - oci.compartment if action == :create + oci.compartment if opts[:action] == :create end # @@ -65,6 +66,13 @@ def initialize(config, state, oci, api, action = :create) # attr_accessor :api + # + # The instance of Kitchen::Logger in use by the active Kitchen::Instance + # + # @return [Kitchen::Logger] + # + attr_accessor :logger + # The definition of the state of a volume # # @return [Hash] @@ -78,44 +86,44 @@ def initialize(config, state, oci, api, action = :create) attr_accessor :volume_attachment_state def create_volume(volume) - info("Creating <#{volume[:name]}>...") + logger.info("Creating <#{volume[:name]}>...") result = api.blockstorage.create_volume(volume_details(volume)) response = volume_response(result.data.id) - info("Finished creating <#{volume[:name]}>.") + logger.info("Finished creating <#{volume[:name]}>.") [response, final_state(response)] end def create_clone_volume(volume) clone_volume_name = clone_volume_display_name(volume[:volume_id]) - info("Creating <#{clone_volume_name}>...") + logger.info("Creating <#{clone_volume_name}>...") result = api.blockstorage.create_volume(volume_clone_details(volume, clone_volume_name)) response = volume_response(result.data.id) - info("Finished creating <#{clone_volume_name}>.") + logger.info("Finished creating <#{clone_volume_name}>.") [response, final_state(response)] end def attach_volume(volume_details, server_id, volume_config) - info("Attaching <#{volume_details.display_name}>...") + logger.info("Attaching <#{volume_details.display_name}>...") attach_volume = api.compute.attach_volume(attachment_details(volume_details, server_id, volume_config)) response = attachment_response(attach_volume.data.id) - info("Finished attaching <#{volume_details.display_name}>.") + logger.info("Finished attaching <#{volume_details.display_name}>.") final_state(response) end def delete_volume(volume) - info("Deleting <#{volume[:display_name]}>...") + logger.info("Deleting <#{volume[:display_name]}>...") api.blockstorage.delete_volume(volume[:id]) api.blockstorage.get_volume(volume[:id]) .wait_until(:lifecycle_state, OCI::Core::Models::Volume::LIFECYCLE_STATE_TERMINATED) - info("Finished deleting <#{volume[:display_name]}>.") + logger.info("Finished deleting <#{volume[:display_name]}>.") end def detatch_volume(volume_attachment) - info("Detaching <#{attachment_name(volume_attachment)}>...") + logger.info("Detaching <#{attachment_name(volume_attachment)}>...") api.compute.detach_volume(volume_attachment[:id]) api.compute.get_volume_attachment(volume_attachment[:id]) .wait_until(:lifecycle_state, OCI::Core::Models::VolumeAttachment::LIFECYCLE_STATE_DETACHED) - info("Finished detaching <#{attachment_name(volume_attachment)}>.") + logger.info("Finished detaching <#{attachment_name(volume_attachment)}>.") end def final_state(response) diff --git a/lib/kitchen/driver/oci/instance.rb b/lib/kitchen/driver/oci/instance.rb index b306903..940e1e9 100644 --- a/lib/kitchen/driver/oci/instance.rb +++ b/lib/kitchen/driver/oci/instance.rb @@ -30,12 +30,13 @@ class Instance < Oci # rubocop:disable Metrics/ClassLength include CommonLaunchDetails - def initialize(config, state, oci, api, action) + def initialize(opts = {}) super() - @config = config - @state = state - @oci = oci - @api = api + @config = opts[:config] + @state = opts[:state] + @oci = opts[:oci] + @api = opts[:api] + @logger = opts[:logger] end # @@ -66,6 +67,13 @@ def initialize(config, state, oci, api, action) # attr_accessor :api + # + # The instance of Kitchen::Logger in use by the active Kitchen::Instance + # + # @return [Kitchen::Logger] + # + attr_accessor :logger + def final_state(state, instance_id) state.store(:server_id, instance_id) state.store(:hostname, instance_ip(instance_id)) @@ -88,6 +96,43 @@ def public_ip_allowed? !subnet.prohibit_public_ip_on_vnic end + def public_key_file + if config[:ssh_keygen] + "#{config[:kitchen_root]}/.kitchen/.ssh/#{config[:instance_name]}_rsa.pub" + else + config[:ssh_keypath] + end + end + + def private_key_file + public_key_file.gsub(".pub", "") + end + + def gen_key_pair + FileUtils.mkdir_p("#{config[:kitchen_root]}/.kitchen/.ssh") + rsa_key = OpenSSL::PKey::RSA.new(4096) + write_private_key(rsa_key) + write_public_key(rsa_key) + state.store(:ssh_key, private_key_file) + end + + def write_private_key(rsa_key) + File.open(private_key_file, "wb") { |k| k.write(rsa_key.to_pem) } + File.chmod(0600, private_key_file) + end + + def write_public_key(rsa_key) + File.open(public_key_file, "wb") { |k| k.write("ssh-rsa #{encode_private_key(rsa_key)} #{config[:instance_name]}") } + File.chmod(0600, public_key_file) + end + + def encode_private_key(rsa_key) + prefix = "#{[7].pack("N")}ssh-rsa" + exponent = rsa_key.e.to_s(0) + modulus = rsa_key.n.to_s(0) + ["#{prefix}#{exponent}#{modulus}"].pack("m0") + end + def random_password(special_chars) (Array.new(5) { special_chars.sample } + Array.new(5) { ("a".."z").to_a.sample } + diff --git a/lib/kitchen/driver/oci/instance/dbaas.rb b/lib/kitchen/driver/oci/instance/dbaas.rb index 6a48d3f..3dd6546 100644 --- a/lib/kitchen/driver/oci/instance/dbaas.rb +++ b/lib/kitchen/driver/oci/instance/dbaas.rb @@ -61,7 +61,7 @@ def node_count def pubkey result = [] - result << File.readlines(config[:ssh_keypath]).first.chomp + result << read_public_key launch_details.ssh_public_keys = result end diff --git a/lib/kitchen/driver/oci/models.rb b/lib/kitchen/driver/oci/models.rb index 716f6ef..58e3f86 100644 --- a/lib/kitchen/driver/oci/models.rb +++ b/lib/kitchen/driver/oci/models.rb @@ -26,11 +26,11 @@ module Models require_relative "blockstorage" def instance_class(config, state, oci, api, action) - Oci::Models.const_get(config[:instance_type].capitalize).new(config, state, oci, api, action) + Oci::Models.const_get(config[:instance_type].capitalize).new(config: config, state: state, oci: oci, api: api, action: action, logger: instance.logger) end def volume_class(type, config, state, oci, api) - Oci::Models.const_get(volume_attachment_type(type)).new(config, state, oci, api) + Oci::Models.const_get(volume_attachment_type(type)).new(config: config, state: state, oci: oci, api: api, logger: instance.logger) end private diff --git a/lib/kitchen/driver/oci/models/compute.rb b/lib/kitchen/driver/oci/models/compute.rb index 4cc7d31..0625c4e 100644 --- a/lib/kitchen/driver/oci/models/compute.rb +++ b/lib/kitchen/driver/oci/models/compute.rb @@ -26,7 +26,7 @@ module Models class Compute < Instance # rubocop:disable Metrics/ClassLength include ComputeLaunchDetails - def initialize(config, state, oci, api, action) + def initialize(opts = {}) super @launch_details = OCI::Core::Models::LaunchInstanceDetails.new end @@ -102,10 +102,10 @@ def images(image_list = [], page = nil) end def clone_boot_volume - info("Cloning boot volume...") + logger.info("Cloning boot volume...") cbv = api.blockstorage.create_boot_volume(clone_boot_volume_details) api.blockstorage.get_boot_volume(cbv.data.id).wait_until(:lifecycle_state, OCI::Core::Models::BootVolume::LIFECYCLE_STATE_AVAILABLE) - info("Finished cloning boot volume.") + logger.info("Finished cloning boot volume.") cbv.data.id end @@ -159,15 +159,19 @@ def create_vnic_details(name) end def pubkey - File.readlines(config[:ssh_keypath]).first.chomp + if config[:ssh_keygen] + logger.info("Generating public/private rsa key pair") + gen_key_pair + end + File.readlines(public_key_file).first.chomp end def metadata md = {} inject_powershell config[:custom_metadata]&.each { |k, v| md.store(k, v) } - md.store("ssh_authorized_keys", pubkey) - md.store("user_data", user_data) if config[:user_data] && !config[:user_data].empty? + md.store("ssh_authorized_keys", pubkey) unless config[:setup_winrm] + md.store("user_data", user_data) if user_data? md end @@ -182,6 +186,10 @@ def windows_state? config[:setup_winrm] && config[:password].nil? && state[:password].nil? end + def user_data? + config[:user_data] && !config[:user_data].empty? + end + def winrm_ps1 filename = File.join(__dir__, %w{.. .. .. .. .. tpl setup_winrm.ps1.erb}) tpl = ERB.new(File.read(filename)) diff --git a/lib/kitchen/driver/oci/models/dbaas.rb b/lib/kitchen/driver/oci/models/dbaas.rb index c255883..29f6f78 100644 --- a/lib/kitchen/driver/oci/models/dbaas.rb +++ b/lib/kitchen/driver/oci/models/dbaas.rb @@ -26,7 +26,7 @@ module Models class Dbaas < Instance # rubocop:disable Metrics/ClassLength include DbaasLaunchDetails - def initialize(config, state, oci, api, action) + def initialize(opts = {}) super @launch_details = OCI::Database::Models::LaunchDbSystemDetails.new @database_details = OCI::Database::Models::CreateDatabaseDetails.new @@ -97,6 +97,14 @@ def hostname_prefix def long_hostname_suffix [random_string(25 - hostname_prefix.length), random_string(3)].compact.join("-") end + + def read_public_key + if config[:ssh_keygen] + logger.info("Generating public/private rsa key pair") + gen_key_pair + end + File.readlines(public_key_file).first.chomp + end end end end diff --git a/lib/kitchen/driver/oci/models/iscsi.rb b/lib/kitchen/driver/oci/models/iscsi.rb index e35ae5a..3de37f2 100644 --- a/lib/kitchen/driver/oci/models/iscsi.rb +++ b/lib/kitchen/driver/oci/models/iscsi.rb @@ -23,7 +23,7 @@ class Oci module Models # iscsi volume attachment model class Iscsi < Blockstorage - def initialize(config, state, oci, api) + def initialize(opts = {}) super @attachment_type = "iscsi" end diff --git a/lib/kitchen/driver/oci/models/paravirtual.rb b/lib/kitchen/driver/oci/models/paravirtual.rb index 1aaa0ba..c5663eb 100644 --- a/lib/kitchen/driver/oci/models/paravirtual.rb +++ b/lib/kitchen/driver/oci/models/paravirtual.rb @@ -23,7 +23,7 @@ class Oci module Models # paravirtual attachment model class Paravirtual < Blockstorage - def initialize(config, state, oci, api) + def initialize(opts = {}) super @attachment_type = "paravirtual" end diff --git a/lib/kitchen/driver/oci/volumes.rb b/lib/kitchen/driver/oci/volumes.rb new file mode 100644 index 0000000..20603b0 --- /dev/null +++ b/lib/kitchen/driver/oci/volumes.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Kitchen + module Driver + class Oci + # mixin for working with volumes and attachments + module Volumes + def create_and_attach_volumes(config, state, oci, api) + return if config[:volumes].empty? + + volume_state = process_volumes(config, state, oci, api) + state.merge!(volume_state) + end + + def process_volumes(config, state, oci, api) + volume_state = { volumes: [], volume_attachments: [] } + config[:volumes].each do |volume| + vol = volume_class(volume[:type], config, state, oci, api) + volume_details, vol_state = create_volume(vol, volume) + attach_state = vol.attach_volume(volume_details, state[:server_id], volume) + volume_state[:volumes] << vol_state + volume_state[:volume_attachments] << attach_state + end + volume_state + end + + def create_volume(vol, volume) + if volume.key?(:volume_id) + vol.create_clone_volume(volume) + else + vol.create_volume(volume) + end + end + end + end + end +end diff --git a/lib/kitchen/driver/oci_version.rb b/lib/kitchen/driver/oci_version.rb index 5cca85d..a216574 100644 --- a/lib/kitchen/driver/oci_version.rb +++ b/lib/kitchen/driver/oci_version.rb @@ -20,6 +20,6 @@ module Kitchen module Driver # Version string for Oracle OCI Kitchen driver - OCI_VERSION = "1.25.0" + OCI_VERSION = "1.26.0" end end diff --git a/spec/kitchen/driver/image_id_spec.rb b/spec/kitchen/driver/image_id_spec.rb index dc48776..d2276da 100644 --- a/spec/kitchen/driver/image_id_spec.rb +++ b/spec/kitchen/driver/image_id_spec.rb @@ -26,7 +26,7 @@ shared_examples "image_name provided" do |images| images.each do |display_name, ocid| context display_name do - subject { Kitchen::Driver::Oci::Models::Compute.new(driver_config, state, oci_config, api, :create) } + subject { Kitchen::Driver::Oci::Models::Compute.new(config: driver_config, state: state, oci: oci_config, api: api, action: :create) } let(:state) { {} } let(:api) { Kitchen::Driver::Oci::Api.new(oci, driver_config) } let(:driver_config) do @@ -66,7 +66,7 @@ include_context "create" context "append aarch64 for ARM shapes" do - subject { Kitchen::Driver::Oci::Models::Compute.new(driver_config, state, oci_config, api, :create) } + subject { Kitchen::Driver::Oci::Models::Compute.new(config: driver_config, state: state, oci: oci_config, api: api, action: :create) } let(:state) { {} } let(:api) { Kitchen::Driver::Oci::Api.new(oci, driver_config) } let(:driver_config) do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 31e8649..f154ab1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -81,500 +81,19 @@ config.expose_dsl_globally = true end -RSpec.shared_context "kitchen", :kitchen do - let(:driver) { Kitchen::Driver::Oci.new(driver_config) } - let(:logged_output) { StringIO.new } - let(:logger) { Logger.new(logged_output) } - let(:platform) { Kitchen::Platform.new(name: "fooos-99") } - let(:transport) { Kitchen::Transport::Dummy.new } - let(:provisioner) { Kitchen::Provisioner::Dummy.new } - let(:instance) do - instance_double( - Kitchen::Instance, - name: "kitchen-foo", - logger: logger, - transport: transport, - provisioner: provisioner, - platform: platform, - to_str: "str" - ) - end -end - -RSpec.shared_context "common", :common do - let(:compartment_ocid) { "ocid1.compartment.oc1..aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } - let(:availability_domain) { "abCD:FAKE-AD-1" } - let(:subnet_ocid) { "ocid1.subnet.oc1..aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } - let(:shape) { "VM.Standard2.1" } - let(:image_ocid) { "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } - let(:ssh_pub_key) { "ssh-rsa AAABBBCCCabcdefg1234" } - let(:hostname) { "kitchen-foo-abc123" } - let(:hostname_prefix) { "kitchen-foo" } - # kitchen.yml driver config section - let(:base_driver_config) do - { - hostname_prefix: hostname_prefix, - compartment_id: compartment_ocid, - availability_domain: availability_domain, - subnet_id: subnet_ocid, - shape: shape, - image_id: image_ocid, - } - end - - before do - allow(File).to receive(:readlines).with(anything).and_return([ssh_pub_key]) - allow_any_instance_of(Kitchen::Driver::Oci::Blockstorage).to receive(:info) - allow_any_instance_of(Kitchen::Driver::Oci::Models::Compute).to receive(:info) - allow_any_instance_of(Kitchen::Driver::Oci::Config).to receive(:compartment).and_return(compartment_ocid) - # stubbed for now. the encoding is making spec difficult right now. plan to add specific units for the user data methods. - allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:user_data).and_return("FaKeUsErDaTa") - end -end - -RSpec.shared_context "oci", :oci do - let(:oci_config) { Kitchen::Driver::Oci::Config.new(driver_config) } - let(:oci) { class_double(OCI::Config) } - let(:nil_response) { OCI::Response.new(200, nil, nil) } - let(:compute_client) { instance_double(OCI::Core::ComputeClient) } - let(:dbaas_client) { instance_double(OCI::Database::DatabaseClient) } - let(:net_client) { instance_double(OCI::Core::VirtualNetworkClient) } - let(:blockstorage_client) { instance_double(OCI::Core::BlockstorageClient) } - - before do - allow(driver).to receive(:instance).and_return(instance) - allow(OCI::ConfigFileLoader).to receive(:load_config).and_return(oci) - end -end - -RSpec.shared_context "net", :net do - let(:vnic_ocid) { "ocid1.vnic.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } - let(:private_ip) { "192.168.1.2" } - let(:public_ip) { "123.456.654.321" } - let(:cidr_block) { "192.168.1.0/24" } - let(:vnic_attachments) do - OCI::Response.new(200, nil, [OCI::Core::Models::VnicAttachment.new(vnic_id: vnic_ocid, - subnet_id: subnet_ocid)]) - end - let(:vnic) do - OCI::Response.new(200, nil, OCI::Core::Models::Vnic.new(private_ip: private_ip, - public_ip: public_ip, - is_primary: true)) - end - let(:subnet) do - OCI::Response.new(200, nil, OCI::Core::Models::Subnet.new(cidr_block: cidr_block, - compartment_id: compartment_ocid, - id: subnet_ocid, - prohibit_public_ip_on_vnic: true)) - end - - before do - allow(OCI::Core::VirtualNetworkClient).to receive(:new).with(config: oci).and_return(net_client) - allow(net_client).to receive(:get_vnic).with(vnic_ocid).and_return(vnic) - allow(net_client).to receive(:get_subnet).with(subnet_ocid).and_return(subnet) - end -end - -RSpec.shared_context "blockstorage", :blockstorage do - let(:boot_volume_ocid) { "ocid1.bootvolume.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } - let(:clone_boot_volume_ocid) { "ocid1.bootvolume.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz67890" } - let(:boot_volume_display_name) { "kitchen-foo (Boot Volume)" } - let(:boot_volume_response) do - OCI::Response.new(200, nil, OCI::Core::Models::BootVolume.new(id: boot_volume_ocid, - display_name: boot_volume_display_name)) - end - let(:clone_boot_volume_response) do - OCI::Response.new(200, nil, OCI::Core::Models::BootVolume.new(id: clone_boot_volume_ocid, - lifecycle_state: Lifecycle.volume("available"))) - end - let(:boot_volume_details) do - OCI::Core::Models::CreateBootVolumeDetails.new( - source_details: OCI::Core::Models::BootVolumeSourceFromBootVolumeDetails.new( - id: boot_volume_ocid - ), - display_name: "#{boot_volume_display_name} (Clone)", - compartment_id: compartment_ocid, - defined_tags: {} - ) - end - before do - allow(OCI::Core::BlockstorageClient).to receive(:new).with(config: oci).and_return(blockstorage_client) - allow(pv_attachment_response).to receive(:wait_until).with(:lifecycle_state, - Lifecycle.volume_attachment("detached")).and_return(pv_attachment_response) - allow(pv_blockstorage_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.volume("terminated")).and_return(nil_response) - allow(blockstorage_client).to receive(:delete_volume).with(iscsi_volume_ocid).and_return(nil_response) - allow(blockstorage_client).to receive(:delete_volume).with(pv_volume_ocid).and_return(nil_response) - allow(iscsi_attachment_response).to receive(:wait_until).with(:lifecycle_state, - Lifecycle.volume_attachment("detached")).and_return(iscsi_attachment_response) - allow(iscsi_blockstorage_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.volume("terminated")).and_return(nil_response) - - allow(blockstorage_client).to receive(:create_volume).with(iscsi_volume_details).and_return(iscsi_blockstorage_response) - allow(blockstorage_client).to receive(:get_boot_volume).with(boot_volume_ocid).and_return(boot_volume_response) - allow(blockstorage_client).to receive(:get_boot_volume).with(clone_boot_volume_ocid).and_return(clone_boot_volume_response) - allow(blockstorage_client).to receive(:create_boot_volume).with(boot_volume_details).and_return(clone_boot_volume_response) - allow(blockstorage_client).to receive(:get_volume).with(iscsi_volume_ocid).and_return(iscsi_blockstorage_response) - allow(blockstorage_client).to receive(:get_volume).with(pv_volume_ocid).and_return(pv_blockstorage_response) - allow(blockstorage_client).to receive(:create_volume).with(pv_volume_details).and_return(pv_blockstorage_response) - allow(clone_boot_volume_response).to receive(:wait_until).with(:lifecycle_state, - Lifecycle.volume("available")).and_return(boot_volume_response) - allow(iscsi_blockstorage_response).to receive(:wait_until).with(:lifecycle_state, - Lifecycle.volume("available")).and_return(iscsi_blockstorage_response) - allow(iscsi_attachment_response).to receive(:wait_until).with(:lifecycle_state, - Lifecycle.volume_attachment("attached")).and_return(iscsi_attachment_response) - allow(pv_attachment_response).to receive(:wait_until).with(:lifecycle_state, - Lifecycle.volume_attachment("attached")).and_return(pv_attachment_response) - allow(pv_blockstorage_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.volume("available")).and_return(pv_blockstorage_response) - end -end - -RSpec.shared_context "iscsi", :iscsi do - let(:iscsi_volume_ocid) { "ocid1.volume.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } - let(:iscsi_display_name) { "vol1" } - let(:iscsi_attachment_ocid) { "ocid1.volumeattachment.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } - let(:iscsi_attachment_display_name) { "iscsi-#{iscsi_display_name}" } - let(:ipv4) { "1.1.2.2" } - let(:iqn) { "iqn.2099-13.com.fake" } - let(:port) { "3260" } - let(:iscsi_volume_details) do - OCI::Core::Models::CreateVolumeDetails.new( - compartment_id: compartment_ocid, - availability_domain: availability_domain, - display_name: iscsi_display_name, - size_in_gbs: 10, - vpus_per_gb: 10, - defined_tags: {} - ) - end - let(:iscsi_attachment) do - OCI::Core::Models::AttachIScsiVolumeDetails.new( - display_name: iscsi_attachment_display_name, - volume_id: iscsi_volume_ocid, - instance_id: instance_ocid - ) - end -end - -RSpec.shared_context "paravirtual", :paravirtual do - let(:pv_volume_ocid) { "ocid1.volume.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz67890" } - let(:pv_attachment_ocid) { "ocid1.volumeattachment.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz67890" } - let(:pv_display_name) { "vol2" } - let(:pv_attachment_display_name) { "paravirtual-#{pv_display_name}" } - let(:pv_volume_details) do - OCI::Core::Models::CreateVolumeDetails.new( - compartment_id: compartment_ocid, - availability_domain: availability_domain, - display_name: pv_display_name, - size_in_gbs: 10, - vpus_per_gb: 10, - defined_tags: {} - ) - end - let(:windows_pv_attachment) do - OCI::Core::Models::AttachParavirtualizedVolumeDetails.new( - display_name: pv_attachment_display_name, - volume_id: pv_volume_ocid, - instance_id: instance_ocid - ) - end - let(:pv_attachment) do - OCI::Core::Models::AttachParavirtualizedVolumeDetails.new( - display_name: pv_attachment_display_name, - volume_id: pv_volume_ocid, - instance_id: instance_ocid, - device: "/dev/oracleoci/oraclevde" - ) - end -end - -RSpec.shared_context "compute", :compute do - include_context "common" - include_context "kitchen" - include_context "oci" - include_context "net" - - let(:compute_driver_config) { base_driver_config.merge!({ capacity_reservation_id: capacity_reservation }) } - let(:instance_ocid) { "ocid1.instance.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } - let(:instance_metadata) do - { - "ssh_authorized_keys" => ssh_pub_key, - } - end - let(:capacity_reservation) { "ocid1.capacityreservation.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } - let(:launch_instance_request) do - OCI::Core::Models::LaunchInstanceDetails.new.tap do |l| - l.availability_domain = availability_domain - l.compartment_id = compartment_ocid - l.display_name = hostname - l.source_details = OCI::Core::Models::InstanceSourceViaImageDetails.new( - sourceType: "image", - imageId: image_ocid, - bootVolumeSizeInGBs: nil - ) - l.shape = shape - l.capacity_reservation_id = capacity_reservation - l.create_vnic_details = OCI::Core::Models::CreateVnicDetails.new( - assign_public_ip: false, - display_name: hostname, - hostname_label: hostname, - nsg_ids: driver_config[:nsg_ids], - subnet_id: subnet_ocid - ) - l.freeform_tags = { kitchen: true } - l.defined_tags = {} - l.metadata = instance_metadata - l.agent_config = OCI::Core::Models::LaunchInstanceAgentConfigDetails.new( - is_monitoring_disabled: false, - is_management_disabled: false, - are_all_plugins_disabled: false - ) - end - end - - let(:launch_instance_from_bv_request) do - OCI::Core::Models::LaunchInstanceDetails.new.tap do |l| - l.availability_domain = availability_domain - l.compartment_id = compartment_ocid - l.display_name = hostname - l.source_details = OCI::Core::Models::InstanceSourceViaBootVolumeDetails.new( - sourceType: "bootVolume", - boot_volume_id: clone_boot_volume_ocid - ) - l.shape = shape - l.create_vnic_details = OCI::Core::Models::CreateVnicDetails.new( - assign_public_ip: false, - display_name: hostname, - hostname_label: hostname, - nsg_ids: driver_config[:nsg_ids], - subnet_id: subnet_ocid - ) - l.freeform_tags = { kitchen: true } - l.defined_tags = {} - l.metadata = instance_metadata - l.agent_config = OCI::Core::Models::LaunchInstanceAgentConfigDetails.new( - is_monitoring_disabled: false, - is_management_disabled: false, - are_all_plugins_disabled: false - ) - end - end - - include_context "blockstorage" - include_context "iscsi" - include_context "paravirtual" - - before do - allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(6).and_return("abc123") - allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(4).and_return("a1b2") - allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(20).and_return("a1b2c3d4e5f6g7h8i9j0") - allow(OCI::Core::ComputeClient).to receive(:new).with(config: oci).and_return(compute_client) - allow(compute_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.compute("terminating")) - allow(compute_client).to receive(:get_instance).with(instance_ocid).and_return(compute_response) - allow(compute_client).to receive(:terminate_instance).with(instance_ocid).and_return(nil_response) - allow(compute_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.compute("running")) - allow(compute_client).to receive(:launch_instance).with(anything).and_return(compute_response) - allow(compute_client).to receive(:get_instance).with(instance_ocid).and_return(compute_response) - allow(compute_client).to receive(:list_vnic_attachments).with(compartment_ocid, instance_id: instance_ocid).and_return(vnic_attachments) - allow(compute_client).to receive(:attach_volume).with(iscsi_attachment).and_return(iscsi_attachment_response) - allow(compute_client).to receive(:attach_volume).with(pv_attachment).and_return(pv_attachment_response) - allow(compute_client).to receive(:get_volume_attachment).with(iscsi_attachment_ocid).and_return(iscsi_attachment_response) - allow(compute_client).to receive(:get_volume_attachment).with(pv_attachment_ocid).and_return(pv_attachment_response) - allow(compute_client).to receive(:detach_volume).with(iscsi_attachment_ocid).and_return(nil_response) - allow(compute_client).to receive(:detach_volume).with(pv_attachment_ocid).and_return(nil_response) - end -end - -RSpec.shared_context "dbaas", :dbaas do - include_context "common" - include_context "kitchen" - include_context "oci" - include_context "net" - - # kitchen.yml driver config section - let(:base_dbaas_driver_config) do - base_driver_config.merge!( - { - instance_type: "dbaas", - dbaas: { - cpu_core_count: 16, - db_name: "dbaas1", - pdb_name: "foo001", - db_version: "19.0.0.0", - }, - } - ) - end - let(:hostname) { "kitchen-foo-a1b" } - let(:db_system_ocid) { "ocid1.dbsystem.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } - let(:db_node_ocid) { "ocid1.dbnode.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } - let(:db_system_launch_details) do - OCI::Database::Models::LaunchDbSystemDetails.new.tap do |l| - l.availability_domain = availability_domain - l.compartment_id = compartment_ocid - l.cpu_core_count = driver_config[:dbaas][:cpu_core_count] - l.database_edition = OCI::Database::Models::DbSystem::DATABASE_EDITION_ENTERPRISE_EDITION - l.db_home = OCI::Database::Models::CreateDbHomeDetails.new( - database: OCI::Database::Models::CreateDatabaseDetails.new( - admin_password: "5up3r53cur3!", - character_set: "AL32UTF8", - db_name: driver_config[:dbaas][:db_name], - db_workload: OCI::Database::Models::CreateDatabaseDetails::DB_WORKLOAD_OLTP, - ncharacter_set: "AL16UTF16", - pdb_name: driver_config[:dbaas][:pdb_name], - db_backup_config: OCI::Database::Models::DbBackupConfig.new(auto_backup_enabled: false), - defined_tags: {} - ), - db_version: driver_config[:dbaas][:db_version], - display_name: "dbhome1029384576", - defined_tags: {} - ) - l.display_name = "kitchen-foo-a1b2-12" - l.hostname = hostname - l.shape = shape - l.ssh_public_keys = [ssh_pub_key] - l.cluster_name = "kitchen-a1b" - l.initial_data_storage_size_in_gb = 256 - l.node_count = 1 - l.license_model = OCI::Database::Models::DbSystem::LICENSE_MODEL_BRING_YOUR_OWN_LICENSE - l.subnet_id = subnet_ocid - l.nsg_ids = driver_config[:nsg_ids] - l.freeform_tags = { kitchen: true } - l.defined_tags = {} - end - end - before do - allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_password).and_return("5up3r53cur3!") - allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_number).with(2).and_return(12) - allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_number).with(10).and_return(1_029_384_576) - allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(4).and_return("a1b2") - allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(3).and_return("a1b") - allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(14).and_return("a1b2c3d4e5f6g7") - allow(OCI::Database::DatabaseClient).to receive(:new).with(config: oci).and_return(dbaas_client) - allow(dbaas_client).to receive(:get_db_system).with(db_system_ocid).and_return(dbaas_response) - allow(dbaas_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.dbaas("terminating"), - max_interval_seconds: 900, - max_wait_seconds: 21_600) - allow(dbaas_client).to receive(:terminate_db_system).with(db_system_ocid).and_return(nil_response) - allow(dbaas_client).to receive(:launch_db_system).with(anything).and_return(dbaas_response) - allow(dbaas_client).to receive(:get_db_system).with(db_system_ocid).and_return(dbaas_response) - allow(dbaas_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.dbaas("available"), - max_interval_seconds: 900, - max_wait_seconds: 21_600) - allow(dbaas_client).to receive(:list_db_nodes).with(compartment_ocid, db_system_id: db_system_ocid).and_return(db_nodes_response) - end -end - -RSpec.shared_context "create", :create do - let(:compute_response) do - OCI::Response.new(200, nil, OCI::Core::Models::Instance.new(id: instance_ocid, - image_id: image_ocid, - lifecycle_state: Lifecycle.compute("running"))) - end - let(:dbaas_response) do - OCI::Response.new(200, nil, OCI::Database::Models::DbSystem.new(id: db_system_ocid, lifecycle_state: Lifecycle.dbaas("available"))) - end - let(:db_nodes_response) do - OCI::Response.new(200, nil, [OCI::Database::Models::DbNodeSummary.new(db_system_id: db_system_ocid, - id: db_node_ocid, - vnic_id: vnic_ocid)]) - end - let(:db_node_response) do - OCI::Response.new(200, nil, OCI::Database::Models::DbNode.new(id: db_node_ocid, lifecycle_state: Lifecycle.dbaas("available"))) - end - let(:iscsi_blockstorage_response) do - OCI::Response.new(200, nil, OCI::Core::Models::Volume.new(id: iscsi_volume_ocid, - display_name: iscsi_display_name, - lifecycle_state: Lifecycle.volume("available"))) - end - let(:iscsi_attachment_response) do - OCI::Response.new(200, nil, OCI::Core::Models::IScsiVolumeAttachment.new(id: iscsi_attachment_ocid, - instance_id: instance_ocid, - volume_id: iscsi_volume_ocid, - display_name: iscsi_attachment_display_name, - lifecycle_state: Lifecycle.volume_attachment("attached"), - ipv4: ipv4, - iqn: iqn, - port: port)) - end - let(:pv_blockstorage_response) do - OCI::Response.new(200, nil, OCI::Core::Models::Volume.new(id: pv_volume_ocid, - display_name: pv_display_name, - lifecycle_state: Lifecycle.volume("available"))) - end - let(:pv_attachment_response) do - OCI::Response.new(200, nil, OCI::Core::Models::ParavirtualizedVolumeAttachment.new(id: pv_attachment_ocid, - instance_id: instance_ocid, - volume_id: pv_volume_ocid, - display_name: pv_attachment_display_name, - lifecycle_state: Lifecycle.volume_attachment("attached"))) - end - let(:get_linux_image_response) do - OCI::Response.new(200, nil, OCI::Core::Models::Image.new(id: image_ocid, operating_system: "Oracle Linux")) - end - let(:get_windows_image_response) do - OCI::Response.new(200, nil, OCI::Core::Models::Image.new(id: image_ocid, operating_system: "Windows")) - end - let(:list_images_response) do - OCI::Response.new(200, nil, [ - OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz123456", display_name: "Oracle-Linux-9.3-2024.02.26-0", time_created: DateTime.new(2024, 2, 26, 18, 34, 24)), - OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz456789", display_name: "Oracle-Linux-9.3-2024.01.26-0", time_created: DateTime.new(2024, 1, 26, 18, 34, 24)), - OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz147852", display_name: "Oracle-Linux-9.3-aarch64-2024.02.26-0", time_created: DateTime.new(2024, 2, 26, 18, 34, 24)), - OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz258963", display_name: "Oracle-Linux-9.3-aarch64-2024.01.26-0", time_created: DateTime.new(2024, 1, 26, 18, 34, 24)), - OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz369852", display_name: "Oracle-Linux-9.3-Minimal-2024.02.29-0", time_created: DateTime.new(2024, 2, 29, 18, 34, 24)), - OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz258741", display_name: "Oracle-Linux-8.9-Gen2-GPU-2024.02.26-0", time_created: DateTime.new(2024, 2, 26, 18, 34, 24)), - OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz789654", display_name: "Oracle-Linux-8.9-aarch64-2024.02.26-0", time_created: DateTime.new(2024, 2, 26, 18, 34, 24)), - OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz456321", display_name: "Oracle-Linux-8.9-2024.02.26-0", time_created: DateTime.new(2024, 2, 26, 18, 34, 24)), - OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz145236", display_name: "Oracle-Linux-8.9-Gen2-GPU-2024.01.26-0", time_created: DateTime.new(2024, 1, 26, 18, 34, 24)), - OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz365214", display_name: "Oracle-Linux-8.9-2024.01.26-0", time_created: DateTime.new(2024, 1, 26, 18, 34, 24)), - OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz698547", display_name: "Oracle-Linux-8.9-aarch64-2024.01.26-0", time_created: DateTime.new(2024, 1, 26, 18, 34, 24)), - ]) - end -end - -RSpec.shared_context "destroy", :destroy do - let(:compute_response) do - OCI::Response.new(200, nil, OCI::Core::Models::Instance.new(id: instance_ocid, - lifecycle_state: Lifecycle.compute("terminating"))) - end - let(:dbaas_response) do - OCI::Response.new(200, nil, OCI::Database::Models::DbSystem.new(id: db_system_ocid, lifecycle_state: Lifecycle.dbaas("terminating"))) - end - let(:db_nodes_response) do - OCI::Response.new(200, nil, [OCI::Database::Models::DbNodeSummary.new(db_system_id: db_system_ocid, - id: db_node_ocid, - vnic_id: vnic_ocid)]) - end - let(:iscsi_blockstorage_response) do - OCI::Response.new(200, nil, OCI::Core::Models::Volume.new(id: iscsi_volume_ocid, - display_name: iscsi_display_name, - lifecycle_state: Lifecycle.volume("terminated"))) - end - let(:iscsi_attachment_response) do - OCI::Response.new(200, nil, OCI::Core::Models::IScsiVolumeAttachment.new(id: iscsi_attachment_ocid, - lifecycle_state: Lifecycle.volume_attachment("detached"))) - end - let(:pv_blockstorage_response) do - OCI::Response.new(200, nil, OCI::Core::Models::Volume.new(id: pv_volume_ocid, - display_name: pv_display_name, - lifecycle_state: Lifecycle.volume("terminated"))) - end - let(:pv_attachment_response) do - OCI::Response.new(200, nil, OCI::Core::Models::ParavirtualizedVolumeAttachment.new(id: pv_attachment_ocid, - lifecycle_state: Lifecycle.volume_attachment("detached"))) - end -end - -RSpec.shared_context "proxy", :proxy do |rspec| - before do - stub_const("ENV", ENV.to_hash.merge({ "http_proxy" => "http://myfakeproxy.com", "no_proxy" => ".myfakedomain.com" })) - allow(OCI::ApiClientProxySettings).to receive(:new).with("myfakeproxy.com", 80).and_return(proxy_settings) - end - let(:proxy_settings) { OCI::ApiClientProxySettings.new("myfakeproxy.com", 80) } -end - -RSpec.shared_context "api", :common do |rspec| - let(:oci_config) { class_double(OCI::Config) } - let(:driver_config) { {} } -end +require "spec_helper/kitchen_helper" +require "spec_helper/common_helper" +require "spec_helper/oci_helper" +require "spec_helper/net_helper" +require "spec_helper/blockstorage_helper" +require "spec_helper/iscsi_helper" +require "spec_helper/paravirtual_helper" +require "spec_helper/compute_helper" +require "spec_helper/dbaas_helper" +require "spec_helper/create_helper" +require "spec_helper/destroy_helper" +require "spec_helper/proxy_helper" +require "spec_helper/api_helper" class Lifecycle def self.compute(state) diff --git a/spec/spec_helper/api_helper.rb b/spec/spec_helper/api_helper.rb new file mode 100644 index 0000000..1bbf689 --- /dev/null +++ b/spec/spec_helper/api_helper.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "api", :common do |rspec| + let(:oci_config) { class_double(OCI::Config) } + let(:driver_config) { {} } +end diff --git a/spec/spec_helper/blockstorage_helper.rb b/spec/spec_helper/blockstorage_helper.rb new file mode 100644 index 0000000..7c69701 --- /dev/null +++ b/spec/spec_helper/blockstorage_helper.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "blockstorage", :blockstorage do + let(:boot_volume_ocid) { "ocid1.bootvolume.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } + let(:clone_boot_volume_ocid) { "ocid1.bootvolume.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz67890" } + let(:boot_volume_display_name) { "kitchen-foo (Boot Volume)" } + let(:boot_volume_response) do + OCI::Response.new(200, nil, OCI::Core::Models::BootVolume.new(id: boot_volume_ocid, + display_name: boot_volume_display_name)) + end + let(:clone_boot_volume_response) do + OCI::Response.new(200, nil, OCI::Core::Models::BootVolume.new(id: clone_boot_volume_ocid, + lifecycle_state: Lifecycle.volume("available"))) + end + let(:boot_volume_details) do + OCI::Core::Models::CreateBootVolumeDetails.new( + source_details: OCI::Core::Models::BootVolumeSourceFromBootVolumeDetails.new( + id: boot_volume_ocid + ), + display_name: "#{boot_volume_display_name} (Clone)", + compartment_id: compartment_ocid, + defined_tags: {} + ) + end + before do + allow(OCI::Core::BlockstorageClient).to receive(:new).with(config: oci).and_return(blockstorage_client) + allow(pv_attachment_response).to receive(:wait_until).with(:lifecycle_state, + Lifecycle.volume_attachment("detached")).and_return(pv_attachment_response) + allow(pv_blockstorage_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.volume("terminated")).and_return(nil_response) + allow(blockstorage_client).to receive(:delete_volume).with(iscsi_volume_ocid).and_return(nil_response) + allow(blockstorage_client).to receive(:delete_volume).with(pv_volume_ocid).and_return(nil_response) + allow(iscsi_attachment_response).to receive(:wait_until).with(:lifecycle_state, + Lifecycle.volume_attachment("detached")).and_return(iscsi_attachment_response) + allow(iscsi_blockstorage_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.volume("terminated")).and_return(nil_response) + + allow(blockstorage_client).to receive(:create_volume).with(iscsi_volume_details).and_return(iscsi_blockstorage_response) + allow(blockstorage_client).to receive(:get_boot_volume).with(boot_volume_ocid).and_return(boot_volume_response) + allow(blockstorage_client).to receive(:get_boot_volume).with(clone_boot_volume_ocid).and_return(clone_boot_volume_response) + allow(blockstorage_client).to receive(:create_boot_volume).with(boot_volume_details).and_return(clone_boot_volume_response) + allow(blockstorage_client).to receive(:get_volume).with(iscsi_volume_ocid).and_return(iscsi_blockstorage_response) + allow(blockstorage_client).to receive(:get_volume).with(pv_volume_ocid).and_return(pv_blockstorage_response) + allow(blockstorage_client).to receive(:create_volume).with(pv_volume_details).and_return(pv_blockstorage_response) + allow(clone_boot_volume_response).to receive(:wait_until).with(:lifecycle_state, + Lifecycle.volume("available")).and_return(boot_volume_response) + allow(iscsi_blockstorage_response).to receive(:wait_until).with(:lifecycle_state, + Lifecycle.volume("available")).and_return(iscsi_blockstorage_response) + allow(iscsi_attachment_response).to receive(:wait_until).with(:lifecycle_state, + Lifecycle.volume_attachment("attached")).and_return(iscsi_attachment_response) + allow(pv_attachment_response).to receive(:wait_until).with(:lifecycle_state, + Lifecycle.volume_attachment("attached")).and_return(pv_attachment_response) + allow(pv_blockstorage_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.volume("available")).and_return(pv_blockstorage_response) + end +end diff --git a/spec/spec_helper/common_helper.rb b/spec/spec_helper/common_helper.rb new file mode 100644 index 0000000..f8edd9f --- /dev/null +++ b/spec/spec_helper/common_helper.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "common", :common do + let(:compartment_ocid) { "ocid1.compartment.oc1..aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } + let(:availability_domain) { "abCD:FAKE-AD-1" } + let(:subnet_ocid) { "ocid1.subnet.oc1..aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } + let(:shape) { "VM.Standard2.1" } + let(:image_ocid) { "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } + let(:ssh_pub_key) { "ssh-rsa AAABBBCCCabcdefg1234" } + let(:hostname) { "kitchen-foo-abc123" } + let(:hostname_prefix) { "kitchen-foo" } + # kitchen.yml driver config section + let(:base_driver_config) do + { + hostname_prefix: hostname_prefix, + compartment_id: compartment_ocid, + availability_domain: availability_domain, + subnet_id: subnet_ocid, + shape: shape, + image_id: image_ocid, + } + end + + before do + allow(File).to receive(:readlines).with(anything).and_return([ssh_pub_key]) + allow_any_instance_of(Kitchen::Driver::Oci::Blockstorage).to receive(:info) + allow_any_instance_of(Kitchen::Driver::Oci::Models::Compute).to receive(:info) + allow_any_instance_of(Kitchen::Driver::Oci::Config).to receive(:compartment).and_return(compartment_ocid) + # stubbed for now. the encoding is making spec difficult right now. plan to add specific units for the user data methods. + allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:user_data).and_return("FaKeUsErDaTa") + end +end diff --git a/spec/spec_helper/compute_helper.rb b/spec/spec_helper/compute_helper.rb new file mode 100644 index 0000000..e5f27c2 --- /dev/null +++ b/spec/spec_helper/compute_helper.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "compute", :compute do + include_context "common" + include_context "kitchen" + include_context "oci" + include_context "net" + + let(:compute_driver_config) { base_driver_config.merge!({ capacity_reservation_id: capacity_reservation }) } + let(:instance_ocid) { "ocid1.instance.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } + let(:instance_metadata) do + { + "ssh_authorized_keys" => ssh_pub_key, + } + end + let(:capacity_reservation) { "ocid1.capacityreservation.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } + let(:launch_instance_request) do + OCI::Core::Models::LaunchInstanceDetails.new.tap do |l| + l.availability_domain = availability_domain + l.compartment_id = compartment_ocid + l.display_name = hostname + l.source_details = OCI::Core::Models::InstanceSourceViaImageDetails.new( + sourceType: "image", + imageId: image_ocid, + bootVolumeSizeInGBs: nil + ) + l.shape = shape + l.capacity_reservation_id = capacity_reservation + l.create_vnic_details = OCI::Core::Models::CreateVnicDetails.new( + assign_public_ip: false, + display_name: hostname, + hostname_label: hostname, + nsg_ids: driver_config[:nsg_ids], + subnet_id: subnet_ocid + ) + l.freeform_tags = { kitchen: true } + l.defined_tags = {} + l.metadata = instance_metadata + l.agent_config = OCI::Core::Models::LaunchInstanceAgentConfigDetails.new( + is_monitoring_disabled: false, + is_management_disabled: false, + are_all_plugins_disabled: false + ) + end + end + + let(:launch_instance_from_bv_request) do + OCI::Core::Models::LaunchInstanceDetails.new.tap do |l| + l.availability_domain = availability_domain + l.compartment_id = compartment_ocid + l.display_name = hostname + l.source_details = OCI::Core::Models::InstanceSourceViaBootVolumeDetails.new( + sourceType: "bootVolume", + boot_volume_id: clone_boot_volume_ocid + ) + l.shape = shape + l.create_vnic_details = OCI::Core::Models::CreateVnicDetails.new( + assign_public_ip: false, + display_name: hostname, + hostname_label: hostname, + nsg_ids: driver_config[:nsg_ids], + subnet_id: subnet_ocid + ) + l.freeform_tags = { kitchen: true } + l.defined_tags = {} + l.metadata = instance_metadata + l.agent_config = OCI::Core::Models::LaunchInstanceAgentConfigDetails.new( + is_monitoring_disabled: false, + is_management_disabled: false, + are_all_plugins_disabled: false + ) + end + end + + include_context "blockstorage" + include_context "iscsi" + include_context "paravirtual" + + before do + allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(6).and_return("abc123") + allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(4).and_return("a1b2") + allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(20).and_return("a1b2c3d4e5f6g7h8i9j0") + allow(OCI::Core::ComputeClient).to receive(:new).with(config: oci).and_return(compute_client) + allow(compute_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.compute("terminating")) + allow(compute_client).to receive(:get_instance).with(instance_ocid).and_return(compute_response) + allow(compute_client).to receive(:terminate_instance).with(instance_ocid).and_return(nil_response) + allow(compute_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.compute("running")) + allow(compute_client).to receive(:launch_instance).with(anything).and_return(compute_response) + allow(compute_client).to receive(:get_instance).with(instance_ocid).and_return(compute_response) + allow(compute_client).to receive(:list_vnic_attachments).with(compartment_ocid, instance_id: instance_ocid).and_return(vnic_attachments) + allow(compute_client).to receive(:attach_volume).with(iscsi_attachment).and_return(iscsi_attachment_response) + allow(compute_client).to receive(:attach_volume).with(pv_attachment).and_return(pv_attachment_response) + allow(compute_client).to receive(:get_volume_attachment).with(iscsi_attachment_ocid).and_return(iscsi_attachment_response) + allow(compute_client).to receive(:get_volume_attachment).with(pv_attachment_ocid).and_return(pv_attachment_response) + allow(compute_client).to receive(:detach_volume).with(iscsi_attachment_ocid).and_return(nil_response) + allow(compute_client).to receive(:detach_volume).with(pv_attachment_ocid).and_return(nil_response) + end +end diff --git a/spec/spec_helper/create_helper.rb b/spec/spec_helper/create_helper.rb new file mode 100644 index 0000000..0103c06 --- /dev/null +++ b/spec/spec_helper/create_helper.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "create", :create do + let(:compute_response) do + OCI::Response.new(200, nil, OCI::Core::Models::Instance.new(id: instance_ocid, + image_id: image_ocid, + lifecycle_state: Lifecycle.compute("running"))) + end + let(:dbaas_response) do + OCI::Response.new(200, nil, OCI::Database::Models::DbSystem.new(id: db_system_ocid, lifecycle_state: Lifecycle.dbaas("available"))) + end + let(:db_nodes_response) do + OCI::Response.new(200, nil, [OCI::Database::Models::DbNodeSummary.new(db_system_id: db_system_ocid, + id: db_node_ocid, + vnic_id: vnic_ocid)]) + end + let(:db_node_response) do + OCI::Response.new(200, nil, OCI::Database::Models::DbNode.new(id: db_node_ocid, lifecycle_state: Lifecycle.dbaas("available"))) + end + let(:iscsi_blockstorage_response) do + OCI::Response.new(200, nil, OCI::Core::Models::Volume.new(id: iscsi_volume_ocid, + display_name: iscsi_display_name, + lifecycle_state: Lifecycle.volume("available"))) + end + let(:iscsi_attachment_response) do + OCI::Response.new(200, nil, OCI::Core::Models::IScsiVolumeAttachment.new(id: iscsi_attachment_ocid, + instance_id: instance_ocid, + volume_id: iscsi_volume_ocid, + display_name: iscsi_attachment_display_name, + lifecycle_state: Lifecycle.volume_attachment("attached"), + ipv4: ipv4, + iqn: iqn, + port: port)) + end + let(:pv_blockstorage_response) do + OCI::Response.new(200, nil, OCI::Core::Models::Volume.new(id: pv_volume_ocid, + display_name: pv_display_name, + lifecycle_state: Lifecycle.volume("available"))) + end + let(:pv_attachment_response) do + OCI::Response.new(200, nil, OCI::Core::Models::ParavirtualizedVolumeAttachment.new(id: pv_attachment_ocid, + instance_id: instance_ocid, + volume_id: pv_volume_ocid, + display_name: pv_attachment_display_name, + lifecycle_state: Lifecycle.volume_attachment("attached"))) + end + let(:get_linux_image_response) do + OCI::Response.new(200, nil, OCI::Core::Models::Image.new(id: image_ocid, operating_system: "Oracle Linux")) + end + let(:get_windows_image_response) do + OCI::Response.new(200, nil, OCI::Core::Models::Image.new(id: image_ocid, operating_system: "Windows")) + end + let(:list_images_response) do + OCI::Response.new(200, nil, [ + OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz123456", display_name: "Oracle-Linux-9.3-2024.02.26-0", time_created: DateTime.new(2024, 2, 26, 18, 34, 24)), + OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz456789", display_name: "Oracle-Linux-9.3-2024.01.26-0", time_created: DateTime.new(2024, 1, 26, 18, 34, 24)), + OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz147852", display_name: "Oracle-Linux-9.3-aarch64-2024.02.26-0", time_created: DateTime.new(2024, 2, 26, 18, 34, 24)), + OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz258963", display_name: "Oracle-Linux-9.3-aarch64-2024.01.26-0", time_created: DateTime.new(2024, 1, 26, 18, 34, 24)), + OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz369852", display_name: "Oracle-Linux-9.3-Minimal-2024.02.29-0", time_created: DateTime.new(2024, 2, 29, 18, 34, 24)), + OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz258741", display_name: "Oracle-Linux-8.9-Gen2-GPU-2024.02.26-0", time_created: DateTime.new(2024, 2, 26, 18, 34, 24)), + OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz789654", display_name: "Oracle-Linux-8.9-aarch64-2024.02.26-0", time_created: DateTime.new(2024, 2, 26, 18, 34, 24)), + OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz456321", display_name: "Oracle-Linux-8.9-2024.02.26-0", time_created: DateTime.new(2024, 2, 26, 18, 34, 24)), + OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz145236", display_name: "Oracle-Linux-8.9-Gen2-GPU-2024.01.26-0", time_created: DateTime.new(2024, 1, 26, 18, 34, 24)), + OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz365214", display_name: "Oracle-Linux-8.9-2024.01.26-0", time_created: DateTime.new(2024, 1, 26, 18, 34, 24)), + OCI::Core::Models::Image.new(id: "ocid1.image.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz698547", display_name: "Oracle-Linux-8.9-aarch64-2024.01.26-0", time_created: DateTime.new(2024, 1, 26, 18, 34, 24)), + ]) + end +end diff --git a/spec/spec_helper/dbaas_helper.rb b/spec/spec_helper/dbaas_helper.rb new file mode 100644 index 0000000..c0126e8 --- /dev/null +++ b/spec/spec_helper/dbaas_helper.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "dbaas", :dbaas do + include_context "common" + include_context "kitchen" + include_context "oci" + include_context "net" + + # kitchen.yml driver config section + let(:base_dbaas_driver_config) do + base_driver_config.merge!( + { + instance_type: "dbaas", + dbaas: { + cpu_core_count: 16, + db_name: "dbaas1", + pdb_name: "foo001", + db_version: "19.0.0.0", + }, + } + ) + end + let(:hostname) { "kitchen-foo-a1b" } + let(:db_system_ocid) { "ocid1.dbsystem.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } + let(:db_node_ocid) { "ocid1.dbnode.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } + let(:db_system_launch_details) do + OCI::Database::Models::LaunchDbSystemDetails.new.tap do |l| + l.availability_domain = availability_domain + l.compartment_id = compartment_ocid + l.cpu_core_count = driver_config[:dbaas][:cpu_core_count] + l.database_edition = OCI::Database::Models::DbSystem::DATABASE_EDITION_ENTERPRISE_EDITION + l.db_home = OCI::Database::Models::CreateDbHomeDetails.new( + database: OCI::Database::Models::CreateDatabaseDetails.new( + admin_password: "5up3r53cur3!", + character_set: "AL32UTF8", + db_name: driver_config[:dbaas][:db_name], + db_workload: OCI::Database::Models::CreateDatabaseDetails::DB_WORKLOAD_OLTP, + ncharacter_set: "AL16UTF16", + pdb_name: driver_config[:dbaas][:pdb_name], + db_backup_config: OCI::Database::Models::DbBackupConfig.new(auto_backup_enabled: false), + defined_tags: {} + ), + db_version: driver_config[:dbaas][:db_version], + display_name: "dbhome1029384576", + defined_tags: {} + ) + l.display_name = "kitchen-foo-a1b2-12" + l.hostname = hostname + l.shape = shape + l.ssh_public_keys = [ssh_pub_key] + l.cluster_name = "kitchen-a1b" + l.initial_data_storage_size_in_gb = 256 + l.node_count = 1 + l.license_model = OCI::Database::Models::DbSystem::LICENSE_MODEL_BRING_YOUR_OWN_LICENSE + l.subnet_id = subnet_ocid + l.nsg_ids = driver_config[:nsg_ids] + l.freeform_tags = { kitchen: true } + l.defined_tags = {} + end + end + + before do + allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_password).and_return("5up3r53cur3!") + allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_number).with(2).and_return(12) + allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_number).with(10).and_return(1_029_384_576) + allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(4).and_return("a1b2") + allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(3).and_return("a1b") + allow_any_instance_of(Kitchen::Driver::Oci::Instance).to receive(:random_string).with(14).and_return("a1b2c3d4e5f6g7") + allow(OCI::Database::DatabaseClient).to receive(:new).with(config: oci).and_return(dbaas_client) + allow(dbaas_client).to receive(:get_db_system).with(db_system_ocid).and_return(dbaas_response) + allow(dbaas_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.dbaas("terminating"), + max_interval_seconds: 900, + max_wait_seconds: 21_600) + allow(dbaas_client).to receive(:terminate_db_system).with(db_system_ocid).and_return(nil_response) + allow(dbaas_client).to receive(:launch_db_system).with(anything).and_return(dbaas_response) + allow(dbaas_client).to receive(:get_db_system).with(db_system_ocid).and_return(dbaas_response) + allow(dbaas_response).to receive(:wait_until).with(:lifecycle_state, Lifecycle.dbaas("available"), + max_interval_seconds: 900, + max_wait_seconds: 21_600) + allow(dbaas_client).to receive(:list_db_nodes).with(compartment_ocid, db_system_id: db_system_ocid).and_return(db_nodes_response) + end +end diff --git a/spec/spec_helper/destroy_helper.rb b/spec/spec_helper/destroy_helper.rb new file mode 100644 index 0000000..f22a495 --- /dev/null +++ b/spec/spec_helper/destroy_helper.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "destroy", :destroy do + let(:compute_response) do + OCI::Response.new(200, nil, OCI::Core::Models::Instance.new(id: instance_ocid, + lifecycle_state: Lifecycle.compute("terminating"))) + end + let(:dbaas_response) do + OCI::Response.new(200, nil, OCI::Database::Models::DbSystem.new(id: db_system_ocid, lifecycle_state: Lifecycle.dbaas("terminating"))) + end + let(:db_nodes_response) do + OCI::Response.new(200, nil, [OCI::Database::Models::DbNodeSummary.new(db_system_id: db_system_ocid, + id: db_node_ocid, + vnic_id: vnic_ocid)]) + end + let(:iscsi_blockstorage_response) do + OCI::Response.new(200, nil, OCI::Core::Models::Volume.new(id: iscsi_volume_ocid, + display_name: iscsi_display_name, + lifecycle_state: Lifecycle.volume("terminated"))) + end + let(:iscsi_attachment_response) do + OCI::Response.new(200, nil, OCI::Core::Models::IScsiVolumeAttachment.new(id: iscsi_attachment_ocid, + lifecycle_state: Lifecycle.volume_attachment("detached"))) + end + let(:pv_blockstorage_response) do + OCI::Response.new(200, nil, OCI::Core::Models::Volume.new(id: pv_volume_ocid, + display_name: pv_display_name, + lifecycle_state: Lifecycle.volume("terminated"))) + end + let(:pv_attachment_response) do + OCI::Response.new(200, nil, OCI::Core::Models::ParavirtualizedVolumeAttachment.new(id: pv_attachment_ocid, + lifecycle_state: Lifecycle.volume_attachment("detached"))) + end +end diff --git a/spec/spec_helper/iscsi_helper.rb b/spec/spec_helper/iscsi_helper.rb new file mode 100644 index 0000000..67a8e31 --- /dev/null +++ b/spec/spec_helper/iscsi_helper.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "iscsi", :iscsi do + let(:iscsi_volume_ocid) { "ocid1.volume.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } + let(:iscsi_display_name) { "vol1" } + let(:iscsi_attachment_ocid) { "ocid1.volumeattachment.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } + let(:iscsi_attachment_display_name) { "iscsi-#{iscsi_display_name}" } + let(:ipv4) { "1.1.2.2" } + let(:iqn) { "iqn.2099-13.com.fake" } + let(:port) { "3260" } + let(:iscsi_volume_details) do + OCI::Core::Models::CreateVolumeDetails.new( + compartment_id: compartment_ocid, + availability_domain: availability_domain, + display_name: iscsi_display_name, + size_in_gbs: 10, + vpus_per_gb: 10, + defined_tags: {} + ) + end + let(:iscsi_attachment) do + OCI::Core::Models::AttachIScsiVolumeDetails.new( + display_name: iscsi_attachment_display_name, + volume_id: iscsi_volume_ocid, + instance_id: instance_ocid + ) + end +end diff --git a/spec/spec_helper/kitchen_helper.rb b/spec/spec_helper/kitchen_helper.rb new file mode 100644 index 0000000..ab0da00 --- /dev/null +++ b/spec/spec_helper/kitchen_helper.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "kitchen", :kitchen do + let(:driver) { Kitchen::Driver::Oci.new(driver_config) } + let(:logged_output) { StringIO.new } + let(:logger) { Logger.new(logged_output) } + let(:platform) { Kitchen::Platform.new(name: "fooos-99") } + let(:transport) { Kitchen::Transport::Dummy.new } + let(:provisioner) { Kitchen::Provisioner::Dummy.new } + let(:instance) do + instance_double( + Kitchen::Instance, + name: "kitchen-foo", + logger: logger, + transport: transport, + provisioner: provisioner, + platform: platform, + to_str: "str" + ) + end +end diff --git a/spec/spec_helper/net_helper.rb b/spec/spec_helper/net_helper.rb new file mode 100644 index 0000000..d9c3436 --- /dev/null +++ b/spec/spec_helper/net_helper.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "net", :net do + let(:vnic_ocid) { "ocid1.vnic.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" } + let(:private_ip) { "192.168.1.2" } + let(:public_ip) { "123.456.654.321" } + let(:cidr_block) { "192.168.1.0/24" } + let(:vnic_attachments) do + OCI::Response.new(200, nil, [OCI::Core::Models::VnicAttachment.new(vnic_id: vnic_ocid, + subnet_id: subnet_ocid)]) + end + let(:vnic) do + OCI::Response.new(200, nil, OCI::Core::Models::Vnic.new(private_ip: private_ip, + public_ip: public_ip, + is_primary: true)) + end + let(:subnet) do + OCI::Response.new(200, nil, OCI::Core::Models::Subnet.new(cidr_block: cidr_block, + compartment_id: compartment_ocid, + id: subnet_ocid, + prohibit_public_ip_on_vnic: true)) + end + + before do + allow(OCI::Core::VirtualNetworkClient).to receive(:new).with(config: oci).and_return(net_client) + allow(net_client).to receive(:get_vnic).with(vnic_ocid).and_return(vnic) + allow(net_client).to receive(:get_subnet).with(subnet_ocid).and_return(subnet) + end +end diff --git a/spec/spec_helper/oci_helper.rb b/spec/spec_helper/oci_helper.rb new file mode 100644 index 0000000..2641ea7 --- /dev/null +++ b/spec/spec_helper/oci_helper.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "oci", :oci do + let(:oci_config) { Kitchen::Driver::Oci::Config.new(driver_config) } + let(:oci) { class_double(OCI::Config) } + let(:nil_response) { OCI::Response.new(200, nil, nil) } + let(:compute_client) { instance_double(OCI::Core::ComputeClient) } + let(:dbaas_client) { instance_double(OCI::Database::DatabaseClient) } + let(:net_client) { instance_double(OCI::Core::VirtualNetworkClient) } + let(:blockstorage_client) { instance_double(OCI::Core::BlockstorageClient) } + + before do + allow(driver).to receive(:instance).and_return(instance) + allow(OCI::ConfigFileLoader).to receive(:load_config).and_return(oci) + end +end diff --git a/spec/spec_helper/paravirtual_helper.rb b/spec/spec_helper/paravirtual_helper.rb new file mode 100644 index 0000000..6472bca --- /dev/null +++ b/spec/spec_helper/paravirtual_helper.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "paravirtual", :paravirtual do + let(:pv_volume_ocid) { "ocid1.volume.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz67890" } + let(:pv_attachment_ocid) { "ocid1.volumeattachment.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz67890" } + let(:pv_display_name) { "vol2" } + let(:pv_attachment_display_name) { "paravirtual-#{pv_display_name}" } + let(:pv_volume_details) do + OCI::Core::Models::CreateVolumeDetails.new( + compartment_id: compartment_ocid, + availability_domain: availability_domain, + display_name: pv_display_name, + size_in_gbs: 10, + vpus_per_gb: 10, + defined_tags: {} + ) + end + let(:windows_pv_attachment) do + OCI::Core::Models::AttachParavirtualizedVolumeDetails.new( + display_name: pv_attachment_display_name, + volume_id: pv_volume_ocid, + instance_id: instance_ocid + ) + end + let(:pv_attachment) do + OCI::Core::Models::AttachParavirtualizedVolumeDetails.new( + display_name: pv_attachment_display_name, + volume_id: pv_volume_ocid, + instance_id: instance_ocid, + device: "/dev/oracleoci/oraclevde" + ) + end +end diff --git a/spec/spec_helper/proxy_helper.rb b/spec/spec_helper/proxy_helper.rb new file mode 100644 index 0000000..16a8ef0 --- /dev/null +++ b/spec/spec_helper/proxy_helper.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# +# Author:: Justin Steele () +# +# Copyright (C) 2024, Stephen Pearson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RSpec.shared_context "proxy", :proxy do |rspec| + before do + stub_const("ENV", ENV.to_hash.merge({ "http_proxy" => "http://myfakeproxy.com", "no_proxy" => ".myfakedomain.com" })) + allow(OCI::ApiClientProxySettings).to receive(:new).with("myfakeproxy.com", 80).and_return(proxy_settings) + end + let(:proxy_settings) { OCI::ApiClientProxySettings.new("myfakeproxy.com", 80) } +end