diff --git a/.travis.yml b/.travis.yml index dc264d69..f24bad9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,18 +5,7 @@ sudo: false gemfile: - Gemfile - - Gemfile.ohai-7 rvm: - - 1.9.3 - 2.0.0 - - ruby-head - -matrix: - exclude: - - rvm: 1.9.3 - gemfile: Gemfile - - rvm: 2.0.0 - gemfile: Gemfile.ohai-7 - - rvm: ruby-head - gemfile: Gemfile.ohai-7 + - ruby-head \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 437cd096..0de0bc4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -# ?.?.? / ????-??-?? +# 2.0.0 / ????-??-?? + +### New Features + +* Re-written for the new test-kitchen underlying framework +* Windows and WinRM support +* Stole some code from PR [#80][] - from [@jmahowald][] # 1.8.1 / 2015-07-22 diff --git a/Gemfile.ohai-7 b/Gemfile.ohai-7 deleted file mode 100644 index 958162e6..00000000 --- a/Gemfile.ohai-7 +++ /dev/null @@ -1,8 +0,0 @@ -# Encoding: UTF-8 - -source 'https://rubygems.org' - -gem 'ohai', '~> 7.0' - -# Specify your gem's dependencies in kitchen-openstack.gemspec -gemspec diff --git a/README.md b/README.md index 18cdadd4..2e8cc1df 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,9 @@ An OpenStack Nova driver for Test Kitchen 1.0! Shamelessly copied from [Fletcher Nichol](https://github.com/fnichol)'s -awesome work on an [EC2 driver](https://github.com/opscode/kitchen-ec2). +awesome work on an [EC2 driver](https://github.com/test-kitchen/kitchen-ec2), +and [Adam Leff](https://github.com/adamleff)'s +amazing work on an [VRO driver](https://github.com/chef-partners/kitchen-vro). ## Installation @@ -39,18 +41,39 @@ Or if using [chefdk](https://downloads.chef.io/chef-dk) install with: Provide, at a minimum, the required driver options in your `.kitchen.yml` file: - driver: - name: openstack - openstack_username: [YOUR OPENSTACK USERNAME] - openstack_api_key: [YOUR OPENSTACK API KEY] # AKA your OPENSTACK PASSWORD - openstack_auth_url: [YOUR OPENSTACK AUTH URL] - require_chef_omnibus: [e.g. 'true' or a version number if you need Chef] - image_ref: [SERVER IMAGE ID] - flavor_ref: [SERVER FLAVOR ID] +```yaml +driver: + name: openstack + openstack_username: [YOUR OPENSTACK USERNAME] + openstack_api_key: [YOUR OPENSTACK API KEY] # AKA your OPENSTACK PASSWORD + openstack_auth_url: [YOUR OPENSTACK AUTH URL] + require_chef_omnibus: [e.g. 'true' or a version number if you need Chef] + image_ref: [SERVER IMAGE ID] + flavor_ref: [SERVER FLAVOR ID] +``` The `image_ref` and `flavor_ref` options can be specified as an exact id, an exact name, or as a regular expression matching the name of the image or flavor. +Test Kitchen 1.4 supports multiple transports, and transports can be configure globally: + +```yaml +transport: + username: ubuntu + password: mysecretpassword +``` + +... or per-platform: + +```yaml +platforms: + name: ubuntu-14.04 + transport: + password: myrootpassword + name: windows-2012r2 + password: myadministratorpassword +``` + By default, a unique server name will be generated and the current user's SSH key will be used (with an RSA key taking precedence over a DSA), though that behavior can be overridden with additional options: @@ -85,18 +108,25 @@ behavior can be overridden with additional options: availability_zone: [THE BLOCK STORAGE AVAILABILITY ZONE, DEFAULTS TO nova] volume_type: [THE VOLUME TYPE, THIS IS OPTIONAL] delete_on_termination: [WILL DELETE VOLUME ON INSTANCE DESTROY WHEN true, OTHERWISE SET TO false] + winrm_wait: [DEFAULTS TO 0, BUT THIS HELPS CONFIRM WINRM IS IN A GOOD STATE BEFORE TRYING TO CONNECT] If a `server_name_prefix` is specified then this prefix will be used when generating random names of the form `-` e.g. `myproject-asdfghjk`. If both `server_name_prefix` and `server_name` are specified then the `server_name` takes precedence. +`winrm_wait` is a workaround to deal with how WinRM comes up during machine +creation. With `cloud-init` running on most OpenStack instances having this +wait makes sure that the machine is in a good state to work with. + If a `key_name` is provided it will be used instead of any `public_key_path` that is specified. If a `key_name` is provided without any `private_key_path`, unexpected behavior may result if your local RSA/DSA private key doesn't match that -OpenStack key. +OpenStack key. If you do key injection via `cloud-init` like this issue: +[#77](https://github.com/test-kitchen/kitchen-openstack/issues/77) the best +way is to make a "dummy-key." A specific `floating_ip` or the ID of a `floating_ip_pool` can be provided to bind a floating IP to the node. Any floating IP will be the IP used for diff --git a/Rakefile b/Rakefile index 972f2b28..cd0862d1 100644 --- a/Rakefile +++ b/Rakefile @@ -1,12 +1,10 @@ # Encoding: UTF-8 require 'bundler/setup' +require 'bundler/gem_tasks' require 'rubocop/rake_task' -require 'cane/rake_task' require 'rspec/core/rake_task' -Cane::RakeTask.new - RuboCop::RakeTask.new desc 'Display LOC stats' @@ -17,4 +15,4 @@ end RSpec::Core::RakeTask.new(:spec) -task default: [:cane, :rubocop, :loc, :spec] +task default: [:rubocop, :loc, :spec] diff --git a/kitchen-openstack.gemspec b/kitchen-openstack.gemspec index cb6e0e42..1ee18f1d 100644 --- a/kitchen-openstack.gemspec +++ b/kitchen-openstack.gemspec @@ -7,8 +7,8 @@ require 'kitchen/driver/openstack_version' Gem::Specification.new do |spec| spec.name = 'kitchen-openstack' spec.version = Kitchen::Driver::OPENSTACK_VERSION - spec.authors = ['Jonathan Hartman'] - spec.email = ['j@p4nt5.com'] + spec.authors = ['Jonathan Hartman', 'JJ Asghar'] + spec.email = ['j@p4nt5.com', 'jj@chef.io'] spec.description = 'A Test Kitchen OpenStack Nova driver' spec.summary = spec.description spec.homepage = 'https://github.com/test-kitchen/kitchen-openstack' @@ -19,11 +19,10 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] - spec.required_ruby_version = '>= 1.9.3' + spec.required_ruby_version = '>= 2.0.0' - spec.add_dependency 'test-kitchen', '~> 1.2' - spec.add_dependency 'fog', '~> 1.18' - # Newer Fogs throw a warning if unf isn't there :( + spec.add_dependency 'test-kitchen', '~> 1.4', '>= 1.4.1' + spec.add_dependency 'fog', '~> 1.33' spec.add_dependency 'unf' spec.add_dependency 'ohai' diff --git a/lib/kitchen/driver/openstack.rb b/lib/kitchen/driver/openstack.rb index b706c519..ce001b59 100644 --- a/lib/kitchen/driver/openstack.rb +++ b/lib/kitchen/driver/openstack.rb @@ -1,8 +1,10 @@ # Encoding: UTF-8 # # Author:: Jonathan Hartman () +# Author:: JJ Asghar () # # Copyright (C) 2013-2015, Jonathan Hartman +# Copyright (C) 2015, Chef Inc # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,23 +18,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'benchmark' -require 'fog' require 'kitchen' -require 'etc' -require 'ipaddr' -require 'socket' +require 'fog' require 'ohai' require_relative 'openstack/volume' module Kitchen module Driver - # Openstack driver for Kitchen. - # - # @author Jonathan Hartman - class Openstack < Kitchen::Driver::SSHBase + # This takes from the Base Class and creates the OpenStack driver. + class Openstack < Kitchen::Driver::Base # rubocop:disable Metrics/ClassLength, Metrics/LineLength @@ip_pool_lock = Mutex.new + kitchen_driver_api_version 2 + plugin_version Kitchen::Driver::OPENSTACK_VERSION + default_config :server_name, nil default_config :server_name_prefix, nil default_config :key_name, nil @@ -62,6 +61,7 @@ class Openstack < Kitchen::Driver::SSHBase default_config :network_ref, nil default_config :no_ssh_tcp_check, false default_config :no_ssh_tcp_check_sleep, 120 + default_config :winrm_wait, 0 default_config :block_device_mapping, nil required_config :private_key_path @@ -82,22 +82,18 @@ def create(state) config[:server_name] = default_name end end - config[:disable_ssl_validation] && disable_ssl_validation + disable_ssl_validation if config[:disable_ssl_validation] server = create_server state[:server_id] = server.id - info "OpenStack instance <#{state[:server_id]}> created." - server.wait_for do - print '.' - ready? - end - info "\n(server ready)" + info "OpenStack instance with ID of <#{state[:server_id]}> is ready." # rubocop:disable Metrics/LineLength + sleep 30 if config[:floating_ip] attach_ip(server, config[:floating_ip]) elsif config[:floating_ip_pool] attach_ip_from_pool(server, config[:floating_ip_pool]) end - state[:hostname] = get_ip(server) - setup_ssh(server, state) + wait_for_server(state) + setup_ssh(server, state) if bourne_shell? add_ohai_hint(state) rescue Fog::Errors::Error, Excon::Errors::Error => ex raise ActionFailed, ex.message @@ -106,7 +102,7 @@ def create(state) def destroy(state) return if state[:server_id].nil? - config[:disable_ssl_validation] && disable_ssl_validation + disable_ssl_validation if config[:disable_ssl_validation] server = compute.servers.get(state[:server_id]) server.destroy unless server.nil? info "OpenStack instance <#{state[:server_id]}> destroyed." @@ -192,7 +188,7 @@ def optional_config(c) when :security_groups config[c] if config[c].is_a?(Array) when :user_data - File.open(config[c]) { |f| f.read } if File.exist?(config[c]) + File.open(config[c], &:read) if File.exist?(config[c]) else config[c] end @@ -279,7 +275,6 @@ def attach_ip_from_pool(server, pool) def attach_ip(server, ip) info "Attaching floating IP <#{ip}>" server.associate_address ip - (server.addresses['public'] ||= []) << { 'version' => 4, 'addr' => ip } end def get_public_private_ips(server) @@ -324,12 +319,25 @@ def parse_ips(pub, priv) end def add_ohai_hint(state) - info 'Adding OpenStack hint for ohai' - ssh = Fog::SSH.new(*build_ssh_args(state)) - ssh.run([ - %(sudo mkdir -p #{Ohai::Config[:hints_path][0]}), - %(sudo touch #{Ohai::Config[:hints_path][0]}/openstack.json) - ]) + if bourne_shell? + info 'Adding OpenStack hint for ohai' + mkdir_cmd = "sudo mkdir -p #{hints_path}" + touch_cmd = "sudo touch #{hints_path}/openstack.json" + instance.transport.connection(state).execute( + "#{mkdir_cmd} && #{touch_cmd}" + ) + elsif windows_os? + info 'Adding OpenStack hint for ohai' + mkdir_cmd = "mkdir #{hints_path}" + touch_cmd = "'' > #{hints_path}\\openstack.json" + instance.transport.connection(state).execute( + "#{mkdir_cmd} && #{touch_cmd}" + ) + end + end + + def hints_path + Ohai::Config[:hints_path][0] end def setup_ssh(server, state) @@ -366,7 +374,7 @@ def tcp_check(state) config[:username], port: config[:port]) end - info '(ssh ready)' + info "Server #{state[:hostname]} has ssh ready..." end def disable_ssl_validation @@ -374,6 +382,29 @@ def disable_ssl_validation Excon.defaults[:ssl_verify_peer] = false end + def wait_for_server(state) + state[:hostname] = get_ip(state) + if config[:winrm_wait] + info "Sleeping for #{config[:winrm_wait]} seconds to let WinRM start up..." # rubocop:disable Metrics/LineLength + countdown(config[:winrm_wait]) + end + info 'Waiting for server to be ready...' + instance.transport.connection(state).wait_until_ready + rescue + error "Server #{state[:hostname]} (#{state[:server_id]}) not reachable. Destroying server..." # rubocop:disable Metrics/LineLength + destroy(state) + raise + end + + def countdown(seconds) + date1 = Time.now + seconds + while Time.now < date1 + t = Time.at(date1.to_i - Time.now.to_i) + puts t.strftime('%M:%S') + sleep 1 + end + end + def find_matching(collection, name) name = name.to_s if name.start_with?('/') && name.end_with?('/') diff --git a/lib/kitchen/driver/openstack/volume.rb b/lib/kitchen/driver/openstack/volume.rb index 4b3397ac..e8773a8c 100644 --- a/lib/kitchen/driver/openstack/volume.rb +++ b/lib/kitchen/driver/openstack/volume.rb @@ -21,7 +21,7 @@ module Kitchen module Driver - class Openstack < Kitchen::Driver::SSHBase + class Openstack < Kitchen::Driver::Base # A class to allow the Kitchen Openstack driver # to use Openstack volumes # diff --git a/lib/kitchen/driver/openstack_version.rb b/lib/kitchen/driver/openstack_version.rb index 72b35824..7cf3190b 100644 --- a/lib/kitchen/driver/openstack_version.rb +++ b/lib/kitchen/driver/openstack_version.rb @@ -21,6 +21,6 @@ module Kitchen # # @author Jonathan Hartman module Driver - OPENSTACK_VERSION = '1.8.2.dev' + OPENSTACK_VERSION = '2.0.0.dev' end end diff --git a/spec/kitchen/driver/openstack_spec.rb b/spec/kitchen/driver/openstack_spec.rb index 0b56306f..ff817668 100644 --- a/spec/kitchen/driver/openstack_spec.rb +++ b/spec/kitchen/driver/openstack_spec.rb @@ -7,6 +7,10 @@ require 'stringio' require 'rspec' require 'kitchen' +require 'kitchen/driver/openstack' +require 'kitchen/provisioner/dummy' +require 'kitchen/transport/dummy' +require 'kitchen/verifier/dummy' require 'ohai' describe Kitchen::Driver::Openstack do @@ -17,10 +21,17 @@ let(:dsa) { File.expand_path('~/.ssh/id_dsa') } let(:rsa) { File.expand_path('~/.ssh/id_rsa') } let(:instance_name) { 'potatoes' } + let(:transport) { Kitchen::Transport::Dummy.new } + let(:platform) { Kitchen::Platform.new(name: 'fake_platform') } + let(:driver) { Kitchen::Driver::Openstack.new(config) } let(:instance) do double( - name: instance_name, logger: logger, to_str: 'instance' + name: instance_name, + transport: transport, + logger: logger, + platform: platform, + to_str: 'instance' ) end @@ -184,6 +195,7 @@ allow(d).to receive(:get_ip).and_return('1.2.3.4') allow(d).to receive(:add_ohai_hint).and_return(true) allow(d).to receive(:do_ssh_setup).and_return(true) + allow(d).to receive(:sleep) d end @@ -226,6 +238,28 @@ driver.create(state) end end + + context 'when executed with a bourne shell' do + before do + allow(driver).to receive(:bourne_shell?).and_return(true) + end + + it 'executes the ssh setup' do + expect(driver).to receive(:setup_ssh) + driver.create(state) + end + end + + context 'when executed in a non-bourne shell' do + before do + allow(driver).to receive(:bourne_shell?).and_return(false) + end + + it 'does not execute the ssh setup' do + expect(driver).not_to receive(:setup_ssh) + driver.create(state) + end + end end describe '#destroy' do @@ -841,8 +875,7 @@ end it 'associates the IP address with the server' do - expect(driver.send(:attach_ip, server, ip)).to eq( - [{ 'version' => 4, 'addr' => ip }]) + expect(driver.send(:attach_ip, server, ip)).to eq(true) end end @@ -1096,14 +1129,7 @@ s end it 'opens an SSH session to the server' do - allow(Fog::SSH).to receive(:new).with('host', 'root', anything) - .and_return(ssh) - res = driver.send(:add_ohai_hint, state) - expected = [ - "sudo mkdir -p #{Ohai::Config[:hints_path][0]}", - "sudo touch #{Ohai::Config[:hints_path][0]}/openstack.json" - ] - expect(res).to eq(expected) + driver.send(:add_ohai_hint, state) end end