# frozen_string_literal: true # # Author:: Prabhu Das () # Author:: Mukta Aphale () # Author:: Siddheshwar More () # Author:: Ameya Varade () # Copyright:: Copyright (c) 2013-2014 Chef Software, Inc. # License:: Apache License, Version 2.0 # # 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. require File.expand_path("../../spec_helper", __FILE__) require "chef/knife/openstack_server_create" require "support/shared_examples_for_servercreatecommand" require "support/shared_examples_for_command" describe Chef::Knife::Cloud::OpenstackServerCreate do create_instance = Chef::Knife::Cloud::OpenstackServerCreate.new create_instance.define_singleton_method(:post_connection_validations) {} it_behaves_like Chef::Knife::Cloud::Command, Chef::Knife::Cloud::OpenstackServerCreate.new it_behaves_like Chef::Knife::Cloud::ServerCreateCommand, create_instance describe "#create_service_instance" do before(:each) do @instance = Chef::Knife::Cloud::OpenstackServerCreate.new end it "return OpenstackService instance" do expect(@instance.create_service_instance).to be_an_instance_of(Chef::Knife::Cloud::OpenstackService) end it "has custom_arguments as its option" do expect(@instance.options.include? :custom_attributes).to be true end end describe "#validate_params!" do before(:each) do @instance = Chef::Knife::Cloud::OpenstackServerCreate.new allow(@instance.ui).to receive(:error) Chef::Config[:knife][:bootstrap_protocol] = "ssh" Chef::Config[:knife][:identity_file] = "identity_file" Chef::Config[:knife][:image_os_type] = "linux" Chef::Config[:knife][:openstack_ssh_key_id] = "openstack_ssh_key" Chef::Config[:knife][:openstack_region] = "test-region" end after(:all) do Chef::Config[:knife].delete(:bootstrap_protocol) Chef::Config[:knife].delete(:identity_file) Chef::Config[:knife].delete(:image_os_type) Chef::Config[:knife].delete(:openstack_ssh_key_id) Chef::Config[:knife].delete(:openstack_region) end it "run sucessfully on all params exist" do expect { @instance.validate_params! }.to_not raise_error end end describe "#before_exec_command" do before(:each) do @instance = Chef::Knife::Cloud::OpenstackServerCreate.new allow(@instance.ui).to receive(:error) @instance.config[:chef_node_name] = "chef_node_name" Chef::Config[:knife][:image] = "image" Chef::Config[:knife][:flavor] = "flavor" Chef::Config[:knife][:openstack_security_groups] = "openstack_security_groups" Chef::Config[:knife][:server_create_timeout] = "server_create_timeout" Chef::Config[:knife][:openstack_ssh_key_id] = "openstack_ssh_key" Chef::Config[:knife][:network_ids] = "test_network_id" allow(Chef::Config[:knife][:network_ids]).to receive(:map).and_return(Chef::Config[:knife][:network_ids]) Chef::Config[:knife][:metadata] = "foo=bar" end after(:all) do Chef::Config[:knife].delete(:image) Chef::Config[:knife].delete(:flavor) Chef::Config[:knife].delete(:openstack_ssh_key_id) Chef::Config[:knife].delete(:openstack_security_groups) Chef::Config[:knife].delete(:server_create_timeout) Chef::Config[:knife].delete(:metadata) end it "set create_options" do @instance.service = double allow(@instance.service).to receive(:get_image).and_return(get_mock_resource("image")) allow(@instance.service).to receive(:get_flavor).and_return(get_mock_resource("flavor")) expect(@instance.service).to receive(:create_server_dependencies) expect(@instance).to receive(:post_connection_validations) @instance.before_exec_command expect(@instance.create_options[:server_def][:name]).to be == @instance.config[:chef_node_name] expect(@instance.create_options[:server_def][:image_ref]).to be == Chef::Config[:knife][:image] expect(@instance.create_options[:server_def][:security_groups]).to be == Chef::Config[:knife][:openstack_security_groups] expect(@instance.create_options[:server_def][:flavor_ref]).to be == Chef::Config[:knife][:flavor] expect(@instance.create_options[:server_def][:nics]).to be == Chef::Config[:knife][:network_ids] expect(@instance.create_options[:server_def][:metadata]).to be == Chef::Config[:knife][:metadata] expect(@instance.create_options[:server_def][:region]).to be == Chef::Config[:knife][:openstack_region] expect(@instance.create_options[:server_create_timeout]).to be == Chef::Config[:knife][:server_create_timeout] end it "doesn't set user data in server_def if user_data not specified" do @instance.service = double("Chef::Knife::Cloud::OpenstackService", create_server_dependencies: nil) allow(@instance.service).to receive(:get_image).and_return(get_mock_resource("image")) allow(@instance.service).to receive(:get_flavor).and_return(get_mock_resource("flavor")) expect(@instance).to receive(:post_connection_validations) @instance.before_exec_command expect(@instance.create_options[:server_def]).to_not include(:user_data) end it "sets user data" do user_data = "echo 'hello world' >> /tmp/user_data.txt" Chef::Config[:knife][:user_data] = user_data @instance.service = double("Chef::Knife::Cloud::OpenstackService", create_server_dependencies: nil) allow(@instance.service).to receive(:get_image).and_return(get_mock_resource("image")) allow(@instance.service).to receive(:get_flavor).and_return(get_mock_resource("flavor")) expect(@instance).to receive(:post_connection_validations) @instance.before_exec_command expect(@instance.create_options[:server_def][:user_data]).to be == user_data end context "with multiple network_ids specified" do before(:each) do @instance.service = double allow(@instance.service).to receive(:get_image).and_return(get_mock_resource("image")) allow(@instance.service).to receive(:get_flavor).and_return(get_mock_resource("flavor")) expect(@instance.service).to receive(:create_server_dependencies) Chef::Config[:knife][:network_ids] = "test_network_id1,test_network_id2" allow(Chef::Config[:knife][:network_ids]).to receive(:map).and_return(Chef::Config[:knife][:network_ids].split(",")) expect(@instance).to receive(:post_connection_validations) end it "creates the server_def with multiple nic_ids." do @instance.before_exec_command expect(@instance.create_options[:server_def][:nics]).to be == %w{test_network_id1 test_network_id2} end end it "ensures default value for metadata" do options = @instance.options expect(options[:metadata][:default]).to be_nil end end describe "#after_exec_command" do before(:each) do @instance = Chef::Knife::Cloud::OpenstackServerCreate.new allow(@instance).to receive(:msg_pair) end after(:all) do Chef::Config[:knife].delete(:openstack_floating_ip) end it "don't set openstack_floating_ip on missing openstack_floating_ip option" do # default openstack_floating_ip is '-1' Chef::Config[:knife][:openstack_floating_ip] = "-1" @instance.service = Chef::Knife::Cloud::Service.new @instance.server = double allow(@instance.server).to receive(:addresses).and_return({ "public" => [{ "version" => 4, "addr" => "127.0.1.1" }] }) expect(@instance).to receive(:bootstrap) @instance.after_exec_command end it "set openstack_floating_ip on openstack_floating_ip option" do Chef::Config[:knife][:openstack_floating_ip] = nil @instance.service = Chef::Knife::Cloud::Service.new @instance.server = double @network = double @ports = ["id" => "test", "fixed_ips" => ["ip_address" => "127.0.1.1"]] allow(@network).to receive(:list_ports).and_return(body: { "ports" => @ports }) @floating_ips = ["id" => "test", "fixed_ips" => ["ip_address" => "127.0.1.1"]] allow(@network).to receive(:list_floating_ips).and_return(body: { "floatingips" => @floating_ips }) allow(@network).to receive(:associate_floating_ip) allow(@instance.service).to receive(:network).and_return(@network) allow(@instance.server).to receive(:addresses).and_return({ "public" => [{ "version" => 4, "addr" => "127.0.1.1" }] }) expect(@instance).to receive(:bootstrap) allow(@instance.service).to receive(:connection).and_return(double) free_floating = Object.new free_floating.define_singleton_method(:fixed_ip) { return nil } free_floating.define_singleton_method(:ip) { return "127.0.0.1" } expect(@instance.service.connection).to receive(:addresses).and_return([free_floating]) @instance.after_exec_command end it "raise error on unavailability of free_floating ip" do Chef::Config[:knife][:openstack_floating_ip] = nil @instance.service = Chef::Knife::Cloud::Service.new allow(@instance.ui).to receive(:fatal) @instance.server = double allow(@instance.server).to receive(:addresses).and_return({ "public" => [{ "version" => 4, "addr" => "127.0.1.1" }] }) expect(@instance).to_not receive(:bootstrap) allow(@instance.service).to receive(:connection).and_return(double) free_floating = Object.new free_floating.define_singleton_method(:fixed_ip) { return "127.0.0.1" } expect(@instance.service.connection).to receive(:addresses).and_return([free_floating]) expect { @instance.after_exec_command }.to raise_error(Chef::Knife::Cloud::CloudExceptions::ServerSetupError, "Unable to assign a Floating IP from allocated IPs.") end end describe "#before_bootstrap" do before(:each) do @instance = Chef::Knife::Cloud::OpenstackServerCreate.new @instance.server = double # default bootstrap_network is public @instance.config[:bootstrap_network] = "public" # default no network is true @instance.config[:network] = true Chef::Config[:knife][:ssh_password] = "config_ssh_password" end after(:each) do Chef::Config[:knife].delete(:ssh_password) end context "when no-network option specified" do before(:each) { @instance.config[:network] = false } it "set public ip as a bootstrap ip if both public and private ip available" do allow(@instance.server).to receive(:addresses).and_return({ "private" => [{ "version" => 4, "addr" => "127.0.0.1" }], "public" => [{ "version" => 4, "addr" => "127.0.0.2" }] }) @instance.before_bootstrap expect(@instance.config[:bootstrap_ip_address]).to be == "127.0.0.2" end it "set private-ip as a bootstrap ip if private ip is available" do allow(@instance.server).to receive(:addresses).and_return({ "private" => [{ "version" => 4, "addr" => "127.0.0.1" }] }) @instance.before_bootstrap expect(@instance.config[:bootstrap_ip_address]).to be == "127.0.0.1" end it "set available ip as a bootstrap ip if no public, private ip available" do allow(@instance.server).to receive(:addresses).and_return({ 1 => [{ "version" => 4, "addr" => "127.0.0.1" }] }) @instance.before_bootstrap expect(@instance.config[:bootstrap_ip_address]).to be == "127.0.0.1" end end it "set bootstrap_ip" do allow(@instance.server).to receive(:addresses).and_return({ "public" => [{ "version" => 4, "addr" => "127.0.0.1" }] }) @instance.before_bootstrap expect(@instance.config[:bootstrap_ip_address]).to be == "127.0.0.1" end it "set private-ip as a bootstrap-ip if private-network option set" do allow(@instance.server).to receive(:addresses).and_return({ "private" => [{ "version" => 4, "addr" => "127.0.0.1" }], "public" => [{ "version" => 4, "addr" => "127.0.0.2" }] }) @instance.config[:private_network] = true @instance.before_bootstrap expect(@instance.config[:bootstrap_ip_address]).to be == "127.0.0.1" end it "raise error on nil bootstrap_ip" do allow(@instance.ui).to receive(:error) allow(@instance.server).to receive(:addresses).and_return({ "public" => [{ "version" => 4, "addr" => nil }] }) expect { @instance.before_bootstrap }.to raise_error(Chef::Knife::Cloud::CloudExceptions::BootstrapError, "No IP address available for bootstrapping.") end it "set public ip as default bootstrap network is public" do allow(@instance.server).to receive(:addresses).and_return({ "private" => [{ "version" => 4, "addr" => "127.0.0.1" }], "public" => [{ "version" => 4, "addr" => "127.0.0.2" }] }) @instance.before_bootstrap expect(@instance.config[:bootstrap_ip_address]).to be == "127.0.0.2" end it "configures the bootstrap to use alternate network" do allow(@instance.server).to receive(:addresses).and_return({ "foo" => [{ "version" => 1, "addr" => "127.0.0.1" }], "private" => [{ "version" => 4, "addr" => "127.0.0.2" }], "public" => [{ "version" => 4, "addr" => "127.0.0.3" }] }) @instance.config[:bootstrap_network] = "foo" @instance.before_bootstrap expect(@instance.config[:bootstrap_ip_address]).to be == "127.0.0.1" end it "configures the bootstrap to use the server password" do allow(@instance.server).to receive(:addresses).and_return({ "public" => [{ "version" => 4, "addr" => "127.0.0.1" }] }) Chef::Config[:knife].delete(:ssh_password) server_password = "adFRjk1089" allow(@instance.server).to receive(:password).and_return(server_password) @instance.before_bootstrap expect(@instance.config[:ssh_password]).to be == server_password end it "configures the bootstrap to use the config ssh password" do allow(@instance.server).to receive(:addresses).and_return({ "public" => [{ "version" => 4, "addr" => "127.0.0.1" }] }) server_password = "config_ssh_password" Chef::Config[:knife][:ssh_password] = server_password expect(@instance.server).to_not receive(:password) @instance.before_bootstrap expect(@instance.config[:ssh_password]).to be == server_password end it "configures the default private bootstrap network to use 'private'" do allow(@instance.server).to receive(:addresses).and_return({ "private" => [{ "version" => 4, "addr" => "127.0.0.1" }] }) @instance.config[:private_network] = true @instance.before_bootstrap expect(@instance.config[:bootstrap_network]).to be == "private" end it "configures the bootstrap to use alternate private network" do allow(@instance.server).to receive(:addresses).and_return({ "secure" => [{ "version" => 4, "addr" => "127.0.0.1" }] }) @instance.config[:bootstrap_network] = "secure" @instance.config[:private_network] = true @instance.before_bootstrap expect(@instance.config[:bootstrap_network]).to be == "secure" end it "set openstack ohai hint" do allow(@instance.server).to receive(:addresses).and_return({ "public" => [{ "version" => 4, "addr" => "127.0.0.1" }] }) @instance.before_bootstrap expect(@instance.config[:hints]).to be == { "openstack" => {} } end end describe "#post_connection_validations" do before(:each) do @instance = Chef::Knife::Cloud::OpenstackServerCreate.new allow(@instance.ui).to receive(:error) end it "raise error on invalid image" do allow(@instance).to receive(:is_flavor_valid?).and_return(true) allow(@instance).to receive(:is_floating_ip_valid?).and_return(true) expect(@instance).to receive(:is_image_valid?).and_return(false) expect { @instance.post_connection_validations }.to raise_error(Chef::Knife::Cloud::CloudExceptions::ValidationError, " You have not provided a valid image ID. Please note the options for this value are -I or --image..") end it "raise error on invalid flavor" do allow(@instance).to receive(:is_image_valid?).and_return(true) allow(@instance).to receive(:is_floating_ip_valid?).and_return(true) expect(@instance).to receive(:is_flavor_valid?).and_return(false) expect { @instance.post_connection_validations }.to raise_error(Chef::Knife::Cloud::CloudExceptions::ValidationError, " You have not provided a valid flavor ID. Please note the options for this value are -f or --flavor..") end it "raise error on invalid floating IP" do allow(@instance).to receive(:is_flavor_valid?).and_return(true) allow(@instance).to receive(:is_image_valid?).and_return(true) expect(@instance).to receive(:is_floating_ip_valid?).and_return(false) expect { @instance.post_connection_validations }.to raise_error(Chef::Knife::Cloud::CloudExceptions::ValidationError, " You have either requested an invalid floating IP address or none are available..") end end describe "#is_floating_ip_valid?" do before(:each) do @instance = Chef::Knife::Cloud::OpenstackServerCreate.new # Default value Chef::Config[:knife][:openstack_floating_ip] = "-1" @instance.service = double end after(:all) do Chef::Config[:knife].delete(:openstack_floating_ip) end it "returns true for default" do expect(@instance.is_floating_ip_valid?).to be true end it "returns false if no floating IPs" do Chef::Config[:knife].delete(:openstack_floating_ip) expect(@instance.service).to receive_message_chain(:connection, :addresses).and_return([]) expect(@instance.is_floating_ip_valid?).to be false end context "when floating ip requested without value" do it "returns true if fixed_ip is nil" do Chef::Config[:knife][:openstack_floating_ip] = nil obj = Object.new obj.define_singleton_method(:fixed_ip) { nil } expect(@instance.service).to receive_message_chain(:connection, :addresses).and_return([obj]) expect(@instance.is_floating_ip_valid?).to be true end end context "when floating ip requested with value" do before { Chef::Config[:knife][:openstack_floating_ip] = "127.0.0.1" } after { Chef::Config[:knife].delete(:openstack_floating_ip) } it "returns true if requested floating IP is exist" do obj = Object.new obj.define_singleton_method(:ip) { return "127.0.0.1" } expect(@instance.service).to receive_message_chain(:connection, :addresses).and_return([obj]) expect(@instance.is_floating_ip_valid?).to be true end it "returns false if requested floating IP does not exist" do obj = Object.new obj.define_singleton_method(:ip) { return "127.0.1.1" } expect(@instance.service).to receive_message_chain(:connection, :addresses).and_return([obj]) expect(@instance.is_floating_ip_valid?).to be false end end end describe "#is_image_valid?" do before(:each) do @instance = Chef::Knife::Cloud::OpenstackServerCreate.new @instance.service = double Chef::Config[:knife][:image] = "image_id" end after(:each) do Chef::Config[:knife].delete(:image) end it "returns false on invalid image" do expect(@instance.service).to receive_message_chain(:get_image).and_return(nil) expect(@instance.is_image_valid?).to be false end it "returns true on valid image" do expect(@instance.service).to receive_message_chain(:get_image).and_return("image") expect(@instance.is_image_valid?).to be true end end describe "#is_flavor_valid?" do before(:each) do @instance = Chef::Knife::Cloud::OpenstackServerCreate.new @instance.service = double Chef::Config[:knife][:flavor] = "flavor" end after(:each) do Chef::Config[:knife].delete(:flavor) end it "returns false on invalid flavor" do expect(@instance.service).to receive_message_chain(:get_flavor).and_return(nil) expect(@instance.is_flavor_valid?).to be false end it "returns true on valid flavor" do expect(@instance.service).to receive_message_chain(:get_flavor).and_return("flavor") expect(@instance.is_flavor_valid?).to be true end end end