From 41f09986f4e71d14797167628b12e4f758f9c0e5 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 26 Sep 2011 02:41:54 -0400 Subject: [PATCH] Implement fog support for the Openstack Compute API v1.1. Includes support for legacy v1.0 style auth and v2.0 keystone auth. --- lib/fog/compute.rb | 3 + lib/fog/core/errors.rb | 4 + lib/fog/openstack.rb | 112 +++++++++++ lib/fog/openstack/compute.rb | 182 +++++++++++++++++ lib/fog/openstack/models/compute/flavor.rb | 20 ++ lib/fog/openstack/models/compute/flavors.rb | 28 +++ lib/fog/openstack/models/compute/image.rb | 57 ++++++ lib/fog/openstack/models/compute/images.rb | 33 ++++ lib/fog/openstack/models/compute/meta.rb | 29 +++ lib/fog/openstack/models/compute/metadata.rb | 69 +++++++ lib/fog/openstack/models/compute/server.rb | 187 ++++++++++++++++++ lib/fog/openstack/models/compute/servers.rb | 36 ++++ lib/fog/openstack/models/meta_parent.rb | 33 ++++ .../compute/change_password_server.rb | 24 +++ .../compute/confirm_resized_server.rb | 24 +++ .../requests/compute/create_image.rb | 27 +++ .../requests/compute/create_server.rb | 72 +++++++ .../requests/compute/delete_image.rb | 40 ++++ .../openstack/requests/compute/delete_meta.rb | 28 +++ .../requests/compute/delete_server.rb | 38 ++++ .../requests/compute/get_flavor_details.rb | 43 ++++ .../requests/compute/get_image_details.rb | 17 ++ .../openstack/requests/compute/get_meta.rb | 29 +++ .../requests/compute/get_server_details.rb | 32 +++ .../requests/compute/list_addresses.rb | 32 +++ .../requests/compute/list_flavors.rb | 38 ++++ .../requests/compute/list_flavors_detail.rb | 38 ++++ .../openstack/requests/compute/list_images.rb | 33 ++++ .../requests/compute/list_images_detail.rb | 39 ++++ .../requests/compute/list_metadata.rb | 28 +++ .../compute/list_private_addresses.rb | 32 +++ .../requests/compute/list_public_addresses.rb | 32 +++ .../requests/compute/list_servers.rb | 33 ++++ .../requests/compute/list_servers_detail.rb | 39 ++++ .../requests/compute/reboot_server.rb | 24 +++ .../requests/compute/rebuild_server.rb | 30 +++ .../requests/compute/resize_server.rb | 24 +++ .../requests/compute/revert_resized_server.rb | 30 +++ .../requests/compute/server_action.rb | 18 ++ .../requests/compute/set_metadata.rb | 45 +++++ .../openstack/requests/compute/update_meta.rb | 45 +++++ .../requests/compute/update_metadata.rb | 46 +++++ .../requests/compute/update_server.rb | 35 ++++ tests/helpers/mock_helper.rb | 4 + .../requests/compute/flavor_tests.rb | 35 ++++ tests/openstack/requests/compute/helper.rb | 16 ++ .../openstack/requests/compute/image_tests.rb | 64 ++++++ .../requests/compute/server_tests.rb | 89 +++++++++ 48 files changed, 2016 insertions(+) create mode 100644 lib/fog/openstack.rb create mode 100644 lib/fog/openstack/compute.rb create mode 100644 lib/fog/openstack/models/compute/flavor.rb create mode 100644 lib/fog/openstack/models/compute/flavors.rb create mode 100644 lib/fog/openstack/models/compute/image.rb create mode 100644 lib/fog/openstack/models/compute/images.rb create mode 100644 lib/fog/openstack/models/compute/meta.rb create mode 100644 lib/fog/openstack/models/compute/metadata.rb create mode 100644 lib/fog/openstack/models/compute/server.rb create mode 100644 lib/fog/openstack/models/compute/servers.rb create mode 100644 lib/fog/openstack/models/meta_parent.rb create mode 100644 lib/fog/openstack/requests/compute/change_password_server.rb create mode 100644 lib/fog/openstack/requests/compute/confirm_resized_server.rb create mode 100644 lib/fog/openstack/requests/compute/create_image.rb create mode 100644 lib/fog/openstack/requests/compute/create_server.rb create mode 100644 lib/fog/openstack/requests/compute/delete_image.rb create mode 100644 lib/fog/openstack/requests/compute/delete_meta.rb create mode 100644 lib/fog/openstack/requests/compute/delete_server.rb create mode 100644 lib/fog/openstack/requests/compute/get_flavor_details.rb create mode 100644 lib/fog/openstack/requests/compute/get_image_details.rb create mode 100644 lib/fog/openstack/requests/compute/get_meta.rb create mode 100644 lib/fog/openstack/requests/compute/get_server_details.rb create mode 100644 lib/fog/openstack/requests/compute/list_addresses.rb create mode 100644 lib/fog/openstack/requests/compute/list_flavors.rb create mode 100644 lib/fog/openstack/requests/compute/list_flavors_detail.rb create mode 100644 lib/fog/openstack/requests/compute/list_images.rb create mode 100644 lib/fog/openstack/requests/compute/list_images_detail.rb create mode 100644 lib/fog/openstack/requests/compute/list_metadata.rb create mode 100644 lib/fog/openstack/requests/compute/list_private_addresses.rb create mode 100644 lib/fog/openstack/requests/compute/list_public_addresses.rb create mode 100644 lib/fog/openstack/requests/compute/list_servers.rb create mode 100644 lib/fog/openstack/requests/compute/list_servers_detail.rb create mode 100644 lib/fog/openstack/requests/compute/reboot_server.rb create mode 100644 lib/fog/openstack/requests/compute/rebuild_server.rb create mode 100644 lib/fog/openstack/requests/compute/resize_server.rb create mode 100644 lib/fog/openstack/requests/compute/revert_resized_server.rb create mode 100644 lib/fog/openstack/requests/compute/server_action.rb create mode 100644 lib/fog/openstack/requests/compute/set_metadata.rb create mode 100644 lib/fog/openstack/requests/compute/update_meta.rb create mode 100644 lib/fog/openstack/requests/compute/update_metadata.rb create mode 100644 lib/fog/openstack/requests/compute/update_server.rb create mode 100644 tests/openstack/requests/compute/flavor_tests.rb create mode 100644 tests/openstack/requests/compute/helper.rb create mode 100644 tests/openstack/requests/compute/image_tests.rb create mode 100644 tests/openstack/requests/compute/server_tests.rb diff --git a/lib/fog/compute.rb b/lib/fog/compute.rb index 655b99460..6f8b7fa8b 100644 --- a/lib/fog/compute.rb +++ b/lib/fog/compute.rb @@ -38,6 +38,9 @@ module Fog when :ninefold require 'fog/ninefold/compute' Fog::Compute::Ninefold.new(attributes) + when :openstack + require 'fog/openstack/compute' + Fog::Compute::OpenStack.new(attributes) when :rackspace require 'fog/rackspace/compute' Fog::Compute::Rackspace.new(attributes) diff --git a/lib/fog/core/errors.rb b/lib/fog/core/errors.rb index b107bbe7a..8b3144fc8 100644 --- a/lib/fog/core/errors.rb +++ b/lib/fog/core/errors.rb @@ -48,6 +48,10 @@ An alternate file may be used by placing its path in the FOG_RC environment vari :new_servers_username: :public_key_path: :private_key_path: + :openstack_api_key: + :openstack_username: + :openstack_auth_url: + :openstack_tenant: :rackspace_api_key: :rackspace_username: :rackspace_servicenet: diff --git a/lib/fog/openstack.rb b/lib/fog/openstack.rb new file mode 100644 index 000000000..ca58a9703 --- /dev/null +++ b/lib/fog/openstack.rb @@ -0,0 +1,112 @@ +require(File.expand_path(File.join(File.dirname(__FILE__), 'core'))) + +module Fog + module OpenStack + extend Fog::Provider + + module Errors + class ServiceError < Fog::Errors::Error + attr_reader :response_data + + def self.slurp(error) + if error.response.body.empty? + data = nil + message = nil + else + data = MultiJson.decode(error.response.body) + message = data['message'] + end + + new_error = super(error, message) + new_error.instance_variable_set(:@response_data, data) + new_error + end + end + + class InternalServerError < ServiceError; end + class Conflict < ServiceError; end + class NotFound < ServiceError; end + class ServiceUnavailable < ServiceError; end + + class BadRequest < ServiceError + attr_reader :validation_errors + + def self.slurp(error) + new_error = super(error) + unless new_error.response_data.nil? + new_error.instance_variable_set(:@validation_errors, new_error.response_data['validationErrors']) + end + new_error + end + end + end + + service(:compute, 'openstack/compute', 'Compute') + + # legacy v1.0 style auth + def self.authenticate_v1(options, connection_options = {}) + openstack_auth_url = options[:openstack_auth_url] + uri = URI.parse(openstack_auth_url) + connection = Fog::Connection.new(openstack_auth_url, false, connection_options) + @openstack_api_key = options[:openstack_api_key] + @openstack_username = options[:openstack_username] + response = connection.request({ + :expects => [200, 204], + :headers => { + 'X-Auth-Key' => @openstack_api_key, + 'X-Auth-User' => @openstack_username + }, + :host => uri.host, + :method => 'GET', + :path => (uri.path and not uri.path.empty?) ? uri.path : 'v1.0' + }) + + return { + :token => response.headers['X-Auth-Token'], + :server_management_url => response.headers['X-Server-Management-Url'] + } + + end + + # keystone style auth + def self.authenticate_v2(options, connection_options = {}) + openstack_auth_url = options[:openstack_auth_url] + uri = URI.parse(openstack_auth_url) + connection = Fog::Connection.new(openstack_auth_url, false, connection_options) + @openstack_api_key = options[:openstack_api_key] + @openstack_username = options[:openstack_username] + @openstack_tenant = options[:openstack_tenant] + @compute_service_name = options[:openstack_compute_service_name] + + req_body= { + 'passwordCredentials' => { + 'username' => @openstack_username, + 'password' => @openstack_api_key + } + } + req_body['tenantId'] = @openstack_tenant if @openstack_tenant + + response = connection.request({ + :expects => [200, 204], + :body => req_body, + :host => uri.host, + :method => 'GET', + :path => (uri.path and not uri.path.empty?) ? uri.path : 'v2.0' + }) + body=response.body + + if body['auth']['serviceCatalog'] and body['auth']['serviceCatalog'][@compute_service_name] and body['auth']['serviceCatalog'][@compute_service_name][0] then + mgmt_url = body['auth']['serviceCatalog'][@compute_service_name][0]['publicURL'] + token = body['auth']['token']['id'] + return { + :token => token, + :server_management_url => mgmt_url + } + else + raise "Unable to parse service catalog." + end + + end + + end +end diff --git a/lib/fog/openstack/compute.rb b/lib/fog/openstack/compute.rb new file mode 100644 index 000000000..dd3dd7b3a --- /dev/null +++ b/lib/fog/openstack/compute.rb @@ -0,0 +1,182 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'openstack')) +require 'fog/compute' + +module Fog + module Compute + class OpenStack < Fog::Service + + requires :openstack_api_key, :openstack_username, :openstack_auth_url + recognizes :openstack_auth_token, :openstack_management_url, :persistent, :openstack_tenant, :openstack_compute_service_name + + model_path 'fog/openstack/models/compute' + model :flavor + collection :flavors + model :image + collection :images + model :server + collection :servers + model :meta + collection :metadata + + request_path 'fog/openstack/requests/compute' + request :create_server + request :delete_image + request :delete_server + request :get_flavor_details + request :get_image_details + request :get_server_details + request :list_addresses + request :list_private_addresses + request :list_public_addresses + request :list_flavors + request :list_flavors_detail + request :list_images + request :list_images_detail + request :list_servers + request :list_servers_detail + + request :server_action + request :change_password_server + request :reboot_server + request :rebuild_server + request :resize_server + request :confirm_resized_server + request :revert_resized_server + request :create_image + + request :update_server + + request :set_metadata + request :update_metadata + request :list_metadata + + request :get_meta + request :update_meta + request :delete_meta + + class Mock + + def self.data + @data ||= Hash.new do |hash, key| + hash[key] = { + :last_modified => { + :images => {}, + :servers => {} + }, + :images => {}, + :servers => {} + } + end + end + + def self.reset + @data = nil + end + + def initialize(options={}) + require 'multi_json' + @openstack_username = options[:openstack_username] + end + + def data + self.class.data[@openstack_username] + end + + def reset_data + self.class.data.delete(@openstack_username) + end + + end + + class Real + + def initialize(options={}) + require 'multi_json' + @openstack_api_key = options[:openstack_api_key] + @openstack_username = options[:openstack_username] + @openstack_tenant = options[:openstack_tenant] + @openstack_compute_service_name = options[:openstack_compute_service_name] || 'nova' + @openstack_auth_url = options[:openstack_auth_url] + @openstack_auth_token = options[:openstack_auth_token] + @openstack_management_url = options[:openstack_management_url] + @openstack_must_reauthenticate = false + @connection_options = options[:connection_options] || {} + authenticate + @persistent = options[:persistent] || false + @connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}", @persistent, @connection_options) + end + + def reload + @connection.reset + end + + def request(params) + begin + response = @connection.request(params.merge({ + :headers => { + 'Content-Type' => 'application/json', + 'X-Auth-Token' => @auth_token + }.merge!(params[:headers] || {}), + :host => @host, + :path => "#{@path}/#{params[:path]}", + :query => ('ignore_awful_caching' << Time.now.to_i.to_s) + })) + rescue Excon::Errors::Unauthorized => error + if error.response.body != 'Bad username or password' # token expiration + @openstack_must_reauthenticate = true + authenticate + retry + else # bad credentials + raise error + end + rescue Excon::Errors::HTTPStatusError => error + raise case error + when Excon::Errors::NotFound + Fog::Compute::OpenStack::NotFound.slurp(error) + else + error + end + end + unless response.body.empty? + response.body = MultiJson.decode(response.body) + end + response + end + + private + + def authenticate + if @openstack_must_reauthenticate || @openstack_auth_token.nil? + options = { + :openstack_api_key => @openstack_api_key, + :openstack_username => @openstack_username, + :openstack_auth_url => @openstack_auth_url, + :openstack_tenant => @openstack_tenant, + :openstack_compute_service_name => @openstack_compute_service_name + } + if @openstack_auth_url =~ /.*v2.0\/?$/ + credentials = Fog::OpenStack.authenticate_v2(options, @connection_options) + else + credentials = Fog::OpenStack.authenticate_v1(options, @connection_options) + end + @auth_token = credentials[:token] + url = credentials[:server_management_url] + uri = URI.parse(url) + else + @auth_token = @openstack_auth_token + uri = URI.parse(@openstack_management_url) + end + @host = uri.host + @path = uri.path + # Force URL into v1.1 namespace (what this binding supports) + @path.sub!(/\/.*\/?/, '/v1.1/') + # Add tenant + @path += @openstack_tenant if @openstack_tenant + @port = uri.port + @scheme = uri.scheme + end + + end + end + end +end diff --git a/lib/fog/openstack/models/compute/flavor.rb b/lib/fog/openstack/models/compute/flavor.rb new file mode 100644 index 000000000..2e998a61b --- /dev/null +++ b/lib/fog/openstack/models/compute/flavor.rb @@ -0,0 +1,20 @@ +require 'fog/core/model' + +module Fog + module Compute + class OpenStack + + class Flavor < Fog::Model + + identity :id + + attribute :disk + attribute :name + attribute :ram + attribute :links + + end + + end + end +end diff --git a/lib/fog/openstack/models/compute/flavors.rb b/lib/fog/openstack/models/compute/flavors.rb new file mode 100644 index 000000000..6f9d2a768 --- /dev/null +++ b/lib/fog/openstack/models/compute/flavors.rb @@ -0,0 +1,28 @@ +require 'fog/core/collection' +require 'fog/openstack/models/compute/flavor' + +module Fog + module Compute + class OpenStack + + class Flavors < Fog::Collection + + model Fog::Compute::OpenStack::Flavor + + def all + data = connection.list_flavors_detail.body['flavors'] + load(data) + end + + def get(flavor_id) + data = connection.get_flavor_details(flavor_id).body['flavor'] + new(data) + rescue Fog::Compute::OpenStack::NotFound + nil + end + + end + + end + end +end diff --git a/lib/fog/openstack/models/compute/image.rb b/lib/fog/openstack/models/compute/image.rb new file mode 100644 index 000000000..0b5646a99 --- /dev/null +++ b/lib/fog/openstack/models/compute/image.rb @@ -0,0 +1,57 @@ +require 'fog/core/model' +require 'fog/openstack/models/compute/metadata' + +module Fog + module Compute + class OpenStack + + class Image < Fog::Model + + identity :id + + attribute :name + attribute :created_at, :aliases => 'created' + attribute :updated_at, :aliases => 'updated' + attribute :progress + attribute :status + attribute :minDisk + attribute :minRam + attribute :server, :aliases => 'server' + attribute :metadata + attribute :links + + def initialize(attributes) + @connection = attributes[:connection] + super + end + + def metadata + @metadata ||= begin + Fog::Compute::OpenStack::Metadata.new({ + :connection => connection, + :parent => self + }) + end + end + + def metadata=(new_metadata={}) + metas = [] + new_metadata.each_pair {|k,v| metas << {"key" => k, "value" => v} } + metadata.load(metas) + end + + def destroy + requires :id + connection.delete_image(id) + true + end + + def ready? + status == 'ACTIVE' + end + + end + + end + end +end diff --git a/lib/fog/openstack/models/compute/images.rb b/lib/fog/openstack/models/compute/images.rb new file mode 100644 index 000000000..c77a206db --- /dev/null +++ b/lib/fog/openstack/models/compute/images.rb @@ -0,0 +1,33 @@ +require 'fog/core/collection' +require 'fog/openstack/models/compute/image' + +module Fog + module Compute + class OpenStack + + class Images < Fog::Collection + + model Fog::Compute::OpenStack::Image + + attribute :server + + def all + data = connection.list_images_detail.body['images'] + load(data) + if server + self.replace(self.select {|image| image.server_id == server.id}) + end + end + + def get(image_id) + data = connection.get_image_details(image_id).body['image'] + new(data) + rescue Fog::Compute::OpenStack::NotFound + nil + end + + end + + end + end +end diff --git a/lib/fog/openstack/models/compute/meta.rb b/lib/fog/openstack/models/compute/meta.rb new file mode 100644 index 000000000..06d6a2692 --- /dev/null +++ b/lib/fog/openstack/models/compute/meta.rb @@ -0,0 +1,29 @@ +require 'fog/core/model' +require 'fog/openstack/models/meta_parent' + +module Fog + module Compute + class OpenStack + class Meta < Fog::Model + + include Fog::Compute::OpenStack::MetaParent + + identity :key + attribute :value + + def destroy + requires :identity + connection.delete_meta(collection_name, @parent.id, key) + true + end + + def save + requires :identity, :value + connection.update_meta(collection_name, @parent.id, key, value) + true + end + + end + end + end +end diff --git a/lib/fog/openstack/models/compute/metadata.rb b/lib/fog/openstack/models/compute/metadata.rb new file mode 100644 index 000000000..7da2a3388 --- /dev/null +++ b/lib/fog/openstack/models/compute/metadata.rb @@ -0,0 +1,69 @@ +require 'fog/core/collection' +require 'fog/openstack/models/meta_parent' +require 'fog/openstack/models/compute/meta' +require 'fog/openstack/models/compute/image' +require 'fog/openstack/models/compute/server' + +module Fog + module Compute + class OpenStack + + class Metadata < Fog::Collection + + model Fog::Compute::OpenStack::Meta + + include Fog::Compute::OpenStack::MetaParent + + def all + requires :parent + metadata = connection.list_metadata(collection_name, @parent.id).body['metadata'] + metas = [] + metadata.each_pair {|k,v| metas << {"key" => k, "value" => v} } + load(metas) + end + + def get(key) + requires :parent + data = connection.get_meta(collection_name, @parent.id, key).body["meta"] + metas = [] + data.each_pair {|k,v| metas << {"key" => k, "value" => v} } + new(metas[0]) + rescue Fog::Compute::OpenStack::NotFound + nil + end + + def update(data=nil) + requires :parent + connection.update_metadata(collection_name, @parent.id, meta_hash(data)) + end + + def set(data=nil) + requires :parent + connection.set_metadata(collection_name, @parent.id, meta_hash(data)) + end + + def new(attributes = {}) + requires :parent + super({ :parent => @parent }.merge!(attributes)) + end + + private + def meta_hash(data=nil) + if data.nil? + data={} + self.each do |meta| + if meta.is_a?(Fog::Compute::OpenStack::Meta) then + data.store(meta.key, meta.value) + else + data.store(meta["key"], meta["value"]) + end + end + end + data + end + + end + + end + end +end diff --git a/lib/fog/openstack/models/compute/server.rb b/lib/fog/openstack/models/compute/server.rb new file mode 100644 index 000000000..10a7b0efe --- /dev/null +++ b/lib/fog/openstack/models/compute/server.rb @@ -0,0 +1,187 @@ +require 'fog/compute/models/server' +require 'fog/openstack/models/compute/metadata' + +module Fog + module Compute + class OpenStack + + class Server < Fog::Compute::Server + + identity :id + + attribute :addresses + attribute :flavor + attribute :host_id, :aliases => 'hostId' + attribute :image + attribute :metadata + attribute :links + attribute :name + attribute :personality + attribute :progress + attribute :accessIPv4 + attribute :accessIPv6 + attribute :state, :aliases => 'status' + + attr_reader :password + attr_writer :private_key, :private_key_path, :public_key, :public_key_path, :username, :image_ref, :flavor_ref + + def initialize(attributes={}) + @connection = attributes[:connection] + super + end + + def metadata + @metadata ||= begin + Fog::Compute::OpenStack::Metadata.new({ + :connection => connection, + :parent => self + }) + end + end + + def metadata=(new_metadata={}) + metas = [] + new_metadata.each_pair {|k,v| metas << {"key" => k, "value" => v} } + metadata.load(metas) + end + + def destroy + requires :id + connection.delete_server(id) + true + end + + def images + requires :id + connection.images(:server => self) + end + + def private_ip_address + nil + end + + def private_key_path + @private_key_path ||= Fog.credentials[:private_key_path] + @private_key_path &&= File.expand_path(@private_key_path) + end + + def private_key + @private_key ||= private_key_path && File.read(private_key_path) + end + + def public_ip_address + addresses['public'].first + end + + def public_key_path + @public_key_path ||= Fog.credentials[:public_key_path] + @public_key_path &&= File.expand_path(@public_key_path) + end + + def public_key + @public_key ||= public_key_path && File.read(public_key_path) + end + + def image_ref + @image_ref + end + + def image_ref=(new_image_ref) + @image_ref = new_image_ref + end + + def flavor_ref + @flavor_ref + end + + def flavor_ref=(new_flavor_ref) + @flavor_ref = new_flavor_ref + end + + def ready? + self.state == 'ACTIVE' + end + + def change_password(admin_password) + requires :id + connection.change_password_server(id, admin_password) + true + end + + def rebuild + requires :id + connection.rebuild_server(id, name, metadata, personality) + true + end + + def resize(flavor_ref) + requires :id + connection.resize_server(id, flavor_ref) + true + end + + def revert_resize + requires :id + connection.revert_resize_server(id) + true + end + + def confirm_resize + requires :id + connection.confirm_resize_server(id) + true + end + + def reboot(type = 'SOFT') + requires :id + connection.reboot_server(id, type) + true + end + + def save + raise Fog::Errors::Error.new('Resaving an existing object may create a duplicate') if identity + requires :flavor_ref, :image_ref, :name + meta_hash = {} + metadata.each { |meta| meta_hash.store(meta.key, meta.value) } + options = { + 'metadata' => meta_hash, + 'personality' => personality, + 'accessIPv4' => accessIPv4, + 'accessIPv6' => accessIPv6 + } + options = options.reject {|key, value| value.nil?} + data = connection.create_server(name, image_ref, flavor_ref, options) + merge_attributes(data.body['server']) + true + end + + def setup(credentials = {}) + requires :public_ip_address, :identity, :public_key, :username + Fog::SSH.new(public_ip_address, username, credentials).run([ + %{mkdir .ssh}, + %{echo "#{public_key}" >> ~/.ssh/authorized_keys}, + %{passwd -l #{username}}, + %{echo "#{MultiJson.encode(attributes)}" >> ~/attributes.json}, + %{echo "#{MultiJson.encode(metadata)}" >> ~/metadata.json} + ]) + rescue Errno::ECONNREFUSED + sleep(1) + retry + end + + def username + @username ||= 'root' + end + + private + + def adminPass=(new_admin_pass) + @password = new_admin_pass + end + + end + + end + end + +end diff --git a/lib/fog/openstack/models/compute/servers.rb b/lib/fog/openstack/models/compute/servers.rb new file mode 100644 index 000000000..de36198d4 --- /dev/null +++ b/lib/fog/openstack/models/compute/servers.rb @@ -0,0 +1,36 @@ +require 'fog/core/collection' +require 'fog/openstack/models/compute/server' + +module Fog + module Compute + class OpenStack + + class Servers < Fog::Collection + + model Fog::Compute::OpenStack::Server + + def all + data = connection.list_servers_detail.body['servers'] + load(data) + end + + def bootstrap(new_attributes = {}) + server = create(new_attributes) + server.wait_for { ready? } + server.setup(:password => server.password) + server + end + + def get(server_id) + if server = connection.get_server_details(server_id).body['server'] + new(server) + end + rescue Fog::Compute::OpenStack::NotFound + nil + end + + end + + end + end +end diff --git a/lib/fog/openstack/models/meta_parent.rb b/lib/fog/openstack/models/meta_parent.rb new file mode 100644 index 000000000..12f591e86 --- /dev/null +++ b/lib/fog/openstack/models/meta_parent.rb @@ -0,0 +1,33 @@ +module Fog + module Compute + class OpenStack + module MetaParent + + def parent + @parent + end + + def parent=(new_parent) + @parent = new_parent + end + + def collection_name + if @parent.class == Fog::Compute::OpenStack::Image + return "images" + elsif @parent.class == Fog::Compute::OpenStack::Server + return "servers" + else + raise "Metadata is not supported for this model type." + end + end + + def metas_to_hash(metas) + hash = {} + metas.each { |meta| hash.store(meta.key, meta.value) } + hash + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/change_password_server.rb b/lib/fog/openstack/requests/compute/change_password_server.rb new file mode 100644 index 000000000..c28cdaa28 --- /dev/null +++ b/lib/fog/openstack/requests/compute/change_password_server.rb @@ -0,0 +1,24 @@ +module Fog + module Compute + class OpenStack + class Real + + def change_password_server(server_id, admin_password) + body = { 'changePassword' => { 'adminPass' => admin_password }} + server_action(server_id, body) + end + + end + + class Mock + + def change_password_server(server_id, admin_password) + response = Excon::Response.new + response.status = 202 + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/confirm_resized_server.rb b/lib/fog/openstack/requests/compute/confirm_resized_server.rb new file mode 100644 index 000000000..d931d92dc --- /dev/null +++ b/lib/fog/openstack/requests/compute/confirm_resized_server.rb @@ -0,0 +1,24 @@ +module Fog + module Compute + class OpenStack + class Real + + def confirm_resized_server(server_id) + body = { 'confirmResize' => nil } + server_action(server_id, body, 204) + end + + end + + class Mock + + def confirm_resized_server(server_id) + response = Excon::Response.new + response.status = 204 + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/create_image.rb b/lib/fog/openstack/requests/compute/create_image.rb new file mode 100644 index 000000000..6bff6b49e --- /dev/null +++ b/lib/fog/openstack/requests/compute/create_image.rb @@ -0,0 +1,27 @@ +module Fog + module Compute + class OpenStack + class Real + + def create_image(server_id, name, metadata={}) + body = { 'createImage' => { + 'name' => name, + 'metadata' => metadata + }} + server_action(server_id, body) + end + + end + + class Mock + + def create_image(server_id, name, metadata={}) + response = Excon::Response.new + response.status = 202 + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/create_server.rb b/lib/fog/openstack/requests/compute/create_server.rb new file mode 100644 index 000000000..957407b0e --- /dev/null +++ b/lib/fog/openstack/requests/compute/create_server.rb @@ -0,0 +1,72 @@ +module Fog + module Compute + class OpenStack + class Real + + def create_server(name, image_ref, flavor_ref, options = {}) + data = { + 'server' => { + 'flavorRef' => flavor_ref, + 'imageRef' => image_ref, + 'name' => name + } + } + + if options['metadata'] + data['server']['metadata'] = options['metadata'] + end + if options['accessIPv4'] + data['server']['accessIPv4'] = options['accessIPv4'] + end + if options['accessIPv6'] + data['server']['accessIPv6'] = options['accessIPv6'] + end + if options['personality'] + data['server']['personality'] = [] + for file in options['personality'] + data['server']['personality'] << { + 'contents' => Base64.encode64(file['contents']), + 'path' => file['path'] + } + end + end + request( + :body => MultiJson.encode(data), + :expects => [200, 202], + :method => 'POST', + :path => 'servers.json' + ) + end + + end + + class Mock + + def create_server(name, image_ref, flavor_ref, options = {}) + response = Excon::Response.new + response.status = 202 + + data = { + 'addresses' => {}, + 'flavor' => {"id"=>"1", "links"=>[{"href"=>"http://nova1:8774/admin/flavors/1", "rel"=>"bookmark"}]}, + 'id' => Fog::Mock.random_numbers(6).to_s, + 'image' => {"id"=>"3", "links"=>[{"href"=>"http://nova1:8774/admin/images/3", "rel"=>"bookmark"}]}, + 'links' => [{"href"=>"http://nova1:8774/v1.1/admin/servers/5", "rel"=>"self"}, {"href"=>"http://nova1:8774/admin/servers/5", "rel"=>"bookmark"}], + 'hostId' => "123456789ABCDEF01234567890ABCDEF", + 'metadata' => options['metadata'] || {}, + 'name' => options['name'] || "server_#{rand(999)}", + 'accessIPv4' => options['accessIPv4'] || "", + 'accessIPv6' => options['accessIPv6'] || "", + 'progress' => 0, + 'status' => 'BUILD' + } + self.data[:last_modified][:servers][data['id']] = Time.now + self.data[:servers][data['id']] = data + response.body = { 'server' => data.merge({'adminPass' => 'password'}) } + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/delete_image.rb b/lib/fog/openstack/requests/compute/delete_image.rb new file mode 100644 index 000000000..bbae7f219 --- /dev/null +++ b/lib/fog/openstack/requests/compute/delete_image.rb @@ -0,0 +1,40 @@ +module Fog + module Compute + class OpenStack + class Real + + def delete_image(image_id) + request( + :expects => 204, + :method => 'DELETE', + :path => "images/#{image_id}" + ) + end + + end + + class Mock + + def delete_image(image_id) + response = Excon::Response.new + if image = list_images_detail.body['images'].detect {|_| _['id'] == image_id} + if image['status'] == 'SAVING' + response.status = 409 + raise(Excon::Errors.status_error({:expects => 202}, response)) + else + self.data[:last_modified][:images].delete(image_id) + self.data[:images].delete(image_id) + response.status = 202 + end + response + else + response.status = 400 + raise(Excon::Errors.status_error({:expects => 202}, response)) + end + + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/delete_meta.rb b/lib/fog/openstack/requests/compute/delete_meta.rb new file mode 100644 index 000000000..f69c4db7e --- /dev/null +++ b/lib/fog/openstack/requests/compute/delete_meta.rb @@ -0,0 +1,28 @@ +module Fog + module Compute + class OpenStack + class Real + + def delete_meta(collection_name, parent_id, key) + request( + :expects => 204, + :method => 'DELETE', + :path => "#{collection_name}/#{parent_id}/metadata/#{key}" + ) + end + + end + + class Mock + + def delete_meta(collection_name, parent_id, key) + response = Excon::Response.new + response.status = 204 + response + end + + end + + end + end +end diff --git a/lib/fog/openstack/requests/compute/delete_server.rb b/lib/fog/openstack/requests/compute/delete_server.rb new file mode 100644 index 000000000..b6424749a --- /dev/null +++ b/lib/fog/openstack/requests/compute/delete_server.rb @@ -0,0 +1,38 @@ +module Fog + module Compute + class OpenStack + class Real + + def delete_server(server_id) + request( + :expects => 204, + :method => 'DELETE', + :path => "servers/#{server_id}" + ) + end + + end + + class Mock + + def delete_server(server_id) + response = Excon::Response.new + if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id} + if server['status'] == 'BUILD' + response.status = 409 + raise(Excon::Errors.status_error({:expects => 204}, response)) + else + self.data[:last_modified][:servers].delete(server_id) + self.data[:servers].delete(server_id) + response.status = 204 + end + response + else + raise Fog::Compute::OpenStack::NotFound + end + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/get_flavor_details.rb b/lib/fog/openstack/requests/compute/get_flavor_details.rb new file mode 100644 index 000000000..c645572e9 --- /dev/null +++ b/lib/fog/openstack/requests/compute/get_flavor_details.rb @@ -0,0 +1,43 @@ +module Fog + module Compute + class OpenStack + class Real + + def get_flavor_details(flavor_ref) + request( + :expects => [200, 203], + :method => 'GET', + :path => "flavors/#{flavor_ref}.json" + ) + end + + end + + class Mock + + def get_flavor_details(flavor_ref) + response = Excon::Response.new + flavor = { + '1' => { 'id' => '1', 'name' => '256 server', 'ram' => 256, 'disk' => 10, 'links' => [] }, + '2' => { 'id' => '2', 'name' => '512 server', 'ram' => 512, 'disk' => 20, 'links' => [] }, + '3' => { 'id' => '3', 'name' => '1GB server', 'ram' => 1024, 'disk' => 40, 'links' => [] }, + '4' => { 'id' => '4', 'name' => '2GB server', 'ram' => 2048, 'disk' => 80, 'links' => [] }, + '5' => { 'id' => '5', 'name' => '4GB server', 'ram' => 4096, 'disk' => 160, 'links' => [] }, + '6' => { 'id' => '6', 'name' => '8GB server', 'ram' => 8192, 'disk' => 320, 'links' => [] }, + '7' => { 'id' => '7', 'name' => '15.5GB server', 'ram' => 15872, 'disk' => 620, 'links' => [] } + }[flavor_ref] + if flavor + response.status = 200 + response.body = { + 'flavor' => flavor + } + response + else + raise Fog::Compute::OpenStack::NotFound + end + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/get_image_details.rb b/lib/fog/openstack/requests/compute/get_image_details.rb new file mode 100644 index 000000000..292faa9e8 --- /dev/null +++ b/lib/fog/openstack/requests/compute/get_image_details.rb @@ -0,0 +1,17 @@ +module Fog + module Compute + class OpenStack + class Real + + def get_image_details(image_id) + request( + :expects => [200, 203], + :method => 'GET', + :path => "images/#{image_id}.json" + ) + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/get_meta.rb b/lib/fog/openstack/requests/compute/get_meta.rb new file mode 100644 index 000000000..5d8332b2d --- /dev/null +++ b/lib/fog/openstack/requests/compute/get_meta.rb @@ -0,0 +1,29 @@ +module Fog + module Compute + class OpenStack + class Real + + def get_meta(collection_name, parent_id, key) + request( + :expects => [200, 203], + :method => 'GET', + :path => "#{collection_name}/#{parent_id}/metadata/#{key}" + ) + end + + end + + class Mock + + def get_meta(collection_name, parent_id, key) + response = Excon::Response.new + response.status = 200 + response.body = { 'meta' => {} } + response + end + + end + + end + end +end diff --git a/lib/fog/openstack/requests/compute/get_server_details.rb b/lib/fog/openstack/requests/compute/get_server_details.rb new file mode 100644 index 000000000..13bd03652 --- /dev/null +++ b/lib/fog/openstack/requests/compute/get_server_details.rb @@ -0,0 +1,32 @@ +module Fog + module Compute + class OpenStack + class Real + + def get_server_details(server_id) + request( + :expects => [200, 203], + :method => 'GET', + :path => "servers/#{server_id}.json" + ) + end + + end + + class Mock + + def get_server_details(server_id) + response = Excon::Response.new + if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id} + response.status = [200, 203][rand(1)] + response.body = { 'server' => server } + response + else + raise Fog::Compute::OpenStack::NotFound + end + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/list_addresses.rb b/lib/fog/openstack/requests/compute/list_addresses.rb new file mode 100644 index 000000000..7fe046c94 --- /dev/null +++ b/lib/fog/openstack/requests/compute/list_addresses.rb @@ -0,0 +1,32 @@ +module Fog + module Compute + class OpenStack + class Real + + def list_addresses(server_id) + request( + :expects => [200, 203], + :method => 'GET', + :path => "servers/#{server_id}/ips.json" + ) + end + + end + + class Mock + + def list_addresses(server_id) + response = Excon::Response.new + if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id} + response.status = [200, 203][rand(1)] + response.body = { 'addresses' => server['addresses'] } + response + else + raise Fog::Compute::OpenStack::NotFound + end + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/list_flavors.rb b/lib/fog/openstack/requests/compute/list_flavors.rb new file mode 100644 index 000000000..4089c1dc4 --- /dev/null +++ b/lib/fog/openstack/requests/compute/list_flavors.rb @@ -0,0 +1,38 @@ +module Fog + module Compute + class OpenStack + class Real + + def list_flavors + request( + :expects => [200, 203], + :method => 'GET', + :path => 'flavors.json' + ) + end + + end + + class Mock + + def list_flavors + response = Excon::Response.new + response.status = 200 + response.body = { + 'flavors' => [ + { 'name' => '256 server', 'id' => '1' }, + { 'name' => '512 server', 'id' => '2' }, + { 'name' => '1GB server', 'id' => '3' }, + { 'name' => '2GB server', 'id' => '4' }, + { 'name' => '4GB server', 'id' => '5' }, + { 'name' => '8GB server', 'id' => '6' }, + { 'name' => '15.5GB server', 'id' => '7' } + ] + } + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/list_flavors_detail.rb b/lib/fog/openstack/requests/compute/list_flavors_detail.rb new file mode 100644 index 000000000..6ba95bc3a --- /dev/null +++ b/lib/fog/openstack/requests/compute/list_flavors_detail.rb @@ -0,0 +1,38 @@ +module Fog + module Compute + class OpenStack + class Real + + def list_flavors_detail + request( + :expects => [200, 203], + :method => 'GET', + :path => 'flavors/detail.json' + ) + end + + end + + class Mock + + def list_flavors_detail + response = Excon::Response.new + response.status = 200 + response.body = { + 'flavors' => [ + { 'name' => '256 server', 'id' => '1', 'ram' => 256, 'disk' => 10, 'links' => [] }, + { 'name' => '512 server', 'id' => '2', 'ram' => 512, 'disk' => 20, 'links' => [] }, + { 'name' => '1GB server', 'id' => '3', 'ram' => 1024, 'disk' => 40, 'links' => [] }, + { 'name' => '2GB server', 'id' => '4', 'ram' => 2048, 'disk' => 80, 'links' => [] }, + { 'name' => '4GB server', 'id' => '5', 'ram' => 4096, 'disk' => 160, 'links' => [] }, + { 'name' => '8GB server', 'id' => '6', 'ram' => 8192, 'disk' => 320, 'links' => [] }, + { 'name' => '15.5GB server', 'id' => '7', 'ram' => 15872, 'disk' => 620, 'links' => [] } + ] + } + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/list_images.rb b/lib/fog/openstack/requests/compute/list_images.rb new file mode 100644 index 000000000..27d6358c1 --- /dev/null +++ b/lib/fog/openstack/requests/compute/list_images.rb @@ -0,0 +1,33 @@ +module Fog + module Compute + class OpenStack + class Real + + def list_images + request( + :expects => [200, 203], + :method => 'GET', + :path => 'images.json' + ) + end + + end + + class Mock + + def list_images + response = Excon::Response.new + data = list_images_detail.body['images'] + images = [] + for image in data + images << image.reject { |key, value| !['id', 'name'].include?(key) } + end + response.status = [200, 203][rand(1)] + response.body = { 'images' => images } + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/list_images_detail.rb b/lib/fog/openstack/requests/compute/list_images_detail.rb new file mode 100644 index 000000000..7e982bf00 --- /dev/null +++ b/lib/fog/openstack/requests/compute/list_images_detail.rb @@ -0,0 +1,39 @@ +module Fog + module Compute + class OpenStack + class Real + + def list_images_detail + request( + :expects => [200, 203], + :method => 'GET', + :path => 'images/detail.json' + ) + end + + end + + class Mock + + def list_images_detail + response = Excon::Response.new + + images = self.data[:images].values + for image in images + case image['status'] + when 'SAVING' + if Time.now - self.data[:last_modified][:images][image['id']] >= Fog::Mock.delay + image['status'] = 'ACTIVE' + end + end + end + + response.status = [200, 203][rand(1)] + response.body = { 'images' => images.map {|image| image.reject {|key, value| !['id', 'name', 'status', 'updated'].include?(key)}} } + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/list_metadata.rb b/lib/fog/openstack/requests/compute/list_metadata.rb new file mode 100644 index 000000000..7ba1346f7 --- /dev/null +++ b/lib/fog/openstack/requests/compute/list_metadata.rb @@ -0,0 +1,28 @@ +module Fog + module Compute + class OpenStack + class Real + + def list_metadata(collection_name, parent_id) + request( + :expects => [200, 203], + :method => 'GET', + :path => "/#{collection_name}/#{parent_id}/metadata.json" + ) + end + + end + + class Mock + + def list_metadata(collection_name, parent_id) + response = Excon::Response.new + response.status = 200 + response.body = {} + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/list_private_addresses.rb b/lib/fog/openstack/requests/compute/list_private_addresses.rb new file mode 100644 index 000000000..904d46761 --- /dev/null +++ b/lib/fog/openstack/requests/compute/list_private_addresses.rb @@ -0,0 +1,32 @@ +module Fog + module Compute + class OpenStack + class Real + + def list_private_addresses(server_id) + request( + :expects => [200, 203], + :method => 'GET', + :path => "servers/#{server_id}/ips/private.json" + ) + end + + end + + class Mock + + def list_private_addresses(server_id) + response = Excon::Response.new + if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id} + response.status = [200, 203][rand(1)] + response.body = { 'private' => server['addresses']['private'] } + response + else + raise Fog::Compute::Rackspace::NotFound + end + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/list_public_addresses.rb b/lib/fog/openstack/requests/compute/list_public_addresses.rb new file mode 100644 index 000000000..cbb3c3282 --- /dev/null +++ b/lib/fog/openstack/requests/compute/list_public_addresses.rb @@ -0,0 +1,32 @@ +module Fog + module Compute + class OpenStack + class Real + + def list_public_addresses(server_id) + request( + :expects => [200, 203], + :method => 'GET', + :path => "servers/#{server_id}/ips/public.json" + ) + end + + end + + class Mock + + def list_public_addresses(server_id) + response = Excon::Response.new + if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id} + response.status = [200, 203][rand(1)] + response.body = { 'public' => server['addresses']['public'] } + response + else + raise Fog::Compute::Rackspace::NotFound + end + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/list_servers.rb b/lib/fog/openstack/requests/compute/list_servers.rb new file mode 100644 index 000000000..19a0040a7 --- /dev/null +++ b/lib/fog/openstack/requests/compute/list_servers.rb @@ -0,0 +1,33 @@ +module Fog + module Compute + class OpenStack + class Real + + def list_servers + request( + :expects => [200, 203], + :method => 'GET', + :path => 'servers.json' + ) + end + + end + + class Mock + + def list_servers + response = Excon::Response.new + data = list_servers_detail.body['servers'] + servers = [] + for server in data + servers << server.reject { |key, value| !['id', 'name'].include?(key) } + end + response.status = [200, 203][rand(1)] + response.body = { 'servers' => servers } + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/list_servers_detail.rb b/lib/fog/openstack/requests/compute/list_servers_detail.rb new file mode 100644 index 000000000..a6cfe3234 --- /dev/null +++ b/lib/fog/openstack/requests/compute/list_servers_detail.rb @@ -0,0 +1,39 @@ +module Fog + module Compute + class OpenStack + class Real + + def list_servers_detail + request( + :expects => [200, 203], + :method => 'GET', + :path => 'servers/detail.json' + ) + end + + end + + class Mock + + def list_servers_detail + response = Excon::Response.new + + servers = self.data[:servers].values + for server in servers + case server['status'] + when 'BUILD' + if Time.now - self.data[:last_modified][:servers][server['id']] > Fog::Mock.delay * 2 + server['status'] = 'ACTIVE' + end + end + end + + response.status = [200, 203][rand(1)] + response.body = { 'servers' => servers } + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/reboot_server.rb b/lib/fog/openstack/requests/compute/reboot_server.rb new file mode 100644 index 000000000..0cfd72d80 --- /dev/null +++ b/lib/fog/openstack/requests/compute/reboot_server.rb @@ -0,0 +1,24 @@ +module Fog + module Compute + class OpenStack + class Real + + def reboot_server(server_id, type = 'SOFT') + body = { 'reboot' => { 'type' => type }} + server_action(server_id, body) + end + + end + + class Mock + + def reboot_server(server_id, type = 'SOFT') + response = Excon::Response.new + response.status = 202 + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/rebuild_server.rb b/lib/fog/openstack/requests/compute/rebuild_server.rb new file mode 100644 index 000000000..7d3e5d4e7 --- /dev/null +++ b/lib/fog/openstack/requests/compute/rebuild_server.rb @@ -0,0 +1,30 @@ +module Fog + module Compute + class OpenStack + class Real + + def rebuild_server(server_id, name, metadata=nil, personality=nil) + + body = { 'rebuild' => { + 'name' => name + }} + body['rebuild']['metadata'] = metadata if metadata + body['rebuild']['personality'] = personality if personality + #NOTE: the implementation returns 200 on rebuild + server_action(server_id, body, 200) + end + + end + + class Mock + + def rebuild_server(server_id, name, metadata, personality) + response = Excon::Response.new + response.status = 202 + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/resize_server.rb b/lib/fog/openstack/requests/compute/resize_server.rb new file mode 100644 index 000000000..0e4ac4219 --- /dev/null +++ b/lib/fog/openstack/requests/compute/resize_server.rb @@ -0,0 +1,24 @@ +module Fog + module Compute + class OpenStack + class Real + + def resize_server(server_id, flavor_ref) + body = { 'resize' => { 'flavor' => { 'id' => flavor_ref }}} + server_action(server_id, body) + end + + end + + class Mock + + def resize_server(server_id, flavor_ref) + response = Excon::Response.new + response.status = 202 + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/revert_resized_server.rb b/lib/fog/openstack/requests/compute/revert_resized_server.rb new file mode 100644 index 000000000..6b6a7020e --- /dev/null +++ b/lib/fog/openstack/requests/compute/revert_resized_server.rb @@ -0,0 +1,30 @@ +module Fog + module Compute + class OpenStack + class Real + + def revert_resized_server(server_id) + body = { 'revertResize' => nil } + server_action(server_id, body) + end + + end + + class Mock + + def revert_resized_server(server_id) + response = Excon::Response.new + response.status = 202 + + self.data[:servers][server_id]['flavorId'] = self.data[:servers][server_id]['old_flavorId'] + self.data[:servers][server_id].delete('old_flavorId') + self.data[:last_modified][:servers][server_id] = Time.now + self.data[:servers][server_id]['status'] = 'ACTIVE' + + response + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/server_action.rb b/lib/fog/openstack/requests/compute/server_action.rb new file mode 100644 index 000000000..0da8e403b --- /dev/null +++ b/lib/fog/openstack/requests/compute/server_action.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class OpenStack + class Real + + def server_action(server_id, body, expects=202) + request( + :body => MultiJson.encode(body), + :expects => expects, + :method => 'POST', + :path => "servers/#{server_id}/action.json" + ) + end + + end + end + end +end diff --git a/lib/fog/openstack/requests/compute/set_metadata.rb b/lib/fog/openstack/requests/compute/set_metadata.rb new file mode 100644 index 000000000..45c61ae83 --- /dev/null +++ b/lib/fog/openstack/requests/compute/set_metadata.rb @@ -0,0 +1,45 @@ +module Fog + module Compute + class OpenStack + + class Real + + def set_metadata(collection_name, parent_id, metadata = {}) + request( + :body => MultiJson.encode({ 'metadata' => metadata }), + :expects => 200, + :method => 'PUT', + :path => "#{collection_name}/#{parent_id}/metadata" + ) + end + + end + + class Mock + + def set_metadata(collection_name, parent_id, metadata = {}) + + if collection_name == "images" then + if not list_images_detail.body['images'].detect {|_| _['id'] == parent_id} + raise Fog::Compute::OpenStack::NotFound + end + end + + if collection_name == "servers" then + if not list_servers_detail.body['servers'].detect {|_| _['id'] == parent_id} + raise Fog::Compute::OpenStack::NotFound + end + end + + response = Excon::Response.new + response.body = { "metadata" => metadata } + response.status = 200 + response + + end + + end + + end + end +end diff --git a/lib/fog/openstack/requests/compute/update_meta.rb b/lib/fog/openstack/requests/compute/update_meta.rb new file mode 100644 index 000000000..074c95ac5 --- /dev/null +++ b/lib/fog/openstack/requests/compute/update_meta.rb @@ -0,0 +1,45 @@ +module Fog + module Compute + class OpenStack + + class Real + + def update_meta(collection_name, parent_id, key, value) + request( + :body => MultiJson.encode({ 'meta' => { key => value }}), + :expects => 200, + :method => 'PUT', + :path => "#{collection_name}/#{parent_id}/metadata/#{key}" + ) + end + + end + + class Mock + + def update_meta(collection_name, parent_id, key, value) + + if collection_name == "images" then + if not list_images_detail.body['images'].detect {|_| _['id'] == parent_id} + raise Fog::Compute::OpenStack::NotFound + end + end + + if collection_name == "servers" then + if not list_servers_detail.body['servers'].detect {|_| _['id'] == parent_id} + raise Fog::Compute::OpenStack::NotFound + end + end + + response = Excon::Response.new + response.body = { "meta" => { key => value } } + response.status = 200 + response + + end + + end + + end + end +end diff --git a/lib/fog/openstack/requests/compute/update_metadata.rb b/lib/fog/openstack/requests/compute/update_metadata.rb new file mode 100644 index 000000000..b05a3eaac --- /dev/null +++ b/lib/fog/openstack/requests/compute/update_metadata.rb @@ -0,0 +1,46 @@ +module Fog + module Compute + class OpenStack + + class Real + + def update_metadata(collection_name, parent_id, metadata = {}) + request( + :body => MultiJson.encode({ 'metadata' => metadata }), + :expects => 200, + :method => 'POST', + :path => "#{collection_name}/#{parent_id}/metadata.json" + ) + end + + end + + class Mock + + def update_metadata(collection_name, parent_id, metadata = {}) + + if collection_name == "images" then + if not list_images_detail.body['images'].detect {|_| _['id'] == parent_id} + raise Fog::Compute::OpenStack::NotFound + end + end + + if collection_name == "servers" then + if not list_servers_detail.body['servers'].detect {|_| _['id'] == parent_id} + raise Fog::Compute::OpenStack::NotFound + end + end + + #FIXME join w/ existing metadata here + response = Excon::Response.new + response.body = { "metadata" => metadata } + response.status = 200 + response + + end + + end + + end + end +end diff --git a/lib/fog/openstack/requests/compute/update_server.rb b/lib/fog/openstack/requests/compute/update_server.rb new file mode 100644 index 000000000..ea9c1c62e --- /dev/null +++ b/lib/fog/openstack/requests/compute/update_server.rb @@ -0,0 +1,35 @@ +module Fog + module Compute + class OpenStack + class Real + + def update_server(server_id, options = {}) + request( + :body => MultiJson.encode({ 'server' => options }), + :expects => 200, + :method => 'PUT', + :path => "servers/#{server_id}.json" + ) + end + + end + + class Mock + + def update_server(server_id, options) + response = Excon::Response.new + if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id} + if options['name'] + server['name'] = options['name'] + end + response.status = 200 + response + else + raise Fog::Compute::OpenStack::NotFound + end + end + + end + end + end +end diff --git a/tests/helpers/mock_helper.rb b/tests/helpers/mock_helper.rb index 000ff0e71..a5a34d340 100644 --- a/tests/helpers/mock_helper.rb +++ b/tests/helpers/mock_helper.rb @@ -38,6 +38,10 @@ if Fog.mock? :ninefold_storage_token => 'ninefold_storage_token', # :public_key_path => '~/.ssh/id_rsa.pub', # :private_key_path => '~/.ssh/id_rsa', + :openstack_api_key => 'openstack_api_key', + :openstack_username => 'openstack_username', + :openstack_tenant => 'openstack_tenant', + :openstack_auth_url => 'openstack_auth_url', :rackspace_api_key => 'rackspace_api_key', :rackspace_username => 'rackspace_username', :slicehost_password => 'slicehost_password', diff --git a/tests/openstack/requests/compute/flavor_tests.rb b/tests/openstack/requests/compute/flavor_tests.rb new file mode 100644 index 000000000..888c4a151 --- /dev/null +++ b/tests/openstack/requests/compute/flavor_tests.rb @@ -0,0 +1,35 @@ +Shindo.tests('Fog::Compute[:openstack] | flavor requests', ['openstack']) do + + @flavor_format = { + 'disk' => Integer, + 'id' => String, + 'name' => String, + 'ram' => Integer, + 'links' => Array + } + + tests('success') do + + tests('#get_flavor_details(1)').formats(@flavor_format) do + Fog::Compute[:openstack].get_flavor_details("1").body['flavor'] + end + + tests('#list_flavors').formats({'flavors' => [OpenStack::Compute::Formats::SUMMARY]}) do + Fog::Compute[:openstack].list_flavors.body + end + + tests('#list_flavors_detail').formats({'flavors' => [@flavor_format]}) do + Fog::Compute[:openstack].list_flavors_detail.body + end + + end + + tests('failure') do + + tests('#get_flavor_details(0)').raises(Fog::Compute::OpenStack::NotFound) do + Fog::Compute[:openstack].get_flavor_details("0") + end + + end + +end diff --git a/tests/openstack/requests/compute/helper.rb b/tests/openstack/requests/compute/helper.rb new file mode 100644 index 000000000..6c34705bf --- /dev/null +++ b/tests/openstack/requests/compute/helper.rb @@ -0,0 +1,16 @@ +class OpenStack + + module Compute + + module Formats + + SUMMARY = { + 'id' => String, + 'name' => String + } + + end + + end + +end diff --git a/tests/openstack/requests/compute/image_tests.rb b/tests/openstack/requests/compute/image_tests.rb new file mode 100644 index 000000000..78be51702 --- /dev/null +++ b/tests/openstack/requests/compute/image_tests.rb @@ -0,0 +1,64 @@ +Shindo.tests('Fog::Compute[:openstack] | image requests', ['openstack']) do + + @image_format = { + 'created' => Fog::Nullable::String, + 'id' => String, + 'name' => String, + 'progress' => Fog::Nullable::Integer, + 'status' => String, + 'updated' => String, + 'minRam' => String, + 'minDisk' => String, + #'server' => Hash, + 'metadata' => Hash, + 'links' => Array + } + + tests('success') do + + @image_id = 1 + + unless Fog.mocking? + Fog::Compute[:openstack].images.get(@image_id).wait_for { ready? } + end + + tests("#get_image_details(#{@image_id})").formats(@image_format) do + pending if Fog.mocking? + Fog::Compute[:openstack].get_image_details(@image_id).body['image'] + end + + tests('#list_images').formats({'images' => [OpenStack::Compute::Formats::SUMMARY]}) do + Fog::Compute[:openstack].list_images.body + end + + tests('#list_images_detail').formats({'images' => [@image_format]}) do + Fog::Compute[:openstack].list_images_detail.body + end + + unless Fog.mocking? + Fog::Compute[:openstack].images.get(@image_id).wait_for { ready? } + end + + if Fog.mocking? + tests("#delete_image(#{@image_id})").succeeds do + pending if Fog.mocking? # because it will fail without the wait just above here, which won't work + Fog::Compute[:openstack].delete_image(@image_id) + end + end + + end + + tests('failure') do + + tests('#delete_image(0)').raises(Excon::Errors::BadRequest) do + Fog::Compute[:openstack].delete_image(0) + end + + tests('#get_image_details(0)').raises(Fog::Compute::OpenStack::NotFound) do + pending if Fog.mocking? + Fog::Compute[:openstack].get_image_details(0) + end + + end + +end diff --git a/tests/openstack/requests/compute/server_tests.rb b/tests/openstack/requests/compute/server_tests.rb new file mode 100644 index 000000000..9887fd28c --- /dev/null +++ b/tests/openstack/requests/compute/server_tests.rb @@ -0,0 +1,89 @@ +Shindo.tests('Fog::Compute[:openstack] | server requests', ['openstack']) do + + @server_format = { + 'addresses' => Hash, + 'flavor' => Hash, + 'hostId' => String, + 'id' => String, + 'image' => Hash, + 'metadata' => Hash, + 'name' => String, + 'progress' => Integer, + 'status' => String, + 'accessIPv4' => Fog::Nullable::String, + 'accessIPv6' => Fog::Nullable::String, + 'links' => Array + } + + tests('success') do + + @server_id = nil + + tests('#create_server("test", 1, 19)').formats(@server_format.merge('adminPass' => String)) do + data = Fog::Compute[:openstack].create_server("test", 3, 1).body['server'] + @server_id = data['id'] + data + end + + Fog::Compute[:openstack].servers.get(@server_id).wait_for { ready? } + + tests("#get_server_details(#{@server_id})").formats(@server_format) do + Fog::Compute[:openstack].get_server_details(@server_id).body['server'] + end + + tests('#list_servers').formats({'servers' => [OpenStack::Compute::Formats::SUMMARY]}) do + Fog::Compute[:openstack].list_servers.body + end + + tests('#list_servers_detail').formats({'servers' => [@server_format]}) do + Fog::Compute[:openstack].list_servers_detail.body + end + + Fog::Compute[:openstack].servers.get(@server_id).wait_for { ready? } + + tests("#update_server(#{@server_id}, :name => 'fogupdatedserver')").succeeds do + Fog::Compute[:openstack].update_server(@server_id, :name => 'fogupdatedserver') + end + + Fog::Compute[:openstack].servers.get(@server_id).wait_for { ready? } + + tests("#reboot_server(#{@server_id}, 'HARD')").succeeds do + Fog::Compute[:openstack].reboot_server(@server_id, 'HARD') + end + + Fog::Compute[:openstack].servers.get(@server_id).wait_for { ready? } + + tests("#reboot_server(#{@server_id}, 'SOFT')").succeeds do + Fog::Compute[:openstack].reboot_server(@server_id, 'SOFT') + end + + Fog::Compute[:openstack].servers.get(@server_id).wait_for { ready? } + + tests("#delete_server(#{@server_id})").succeeds do + Fog::Compute[:openstack].delete_server(@server_id) + end + + end + + tests('failure') do + + tests('#delete_server(0)').raises(Fog::Compute::OpenStack::NotFound) do + Fog::Compute[:openstack].delete_server(0) + end + + tests('#get_server_details(0)').raises(Fog::Compute::OpenStack::NotFound) do + Fog::Compute[:openstack].get_server_details(0) + end + + tests("#update_server(0, :name => 'fogupdatedserver', :adminPass => 'fogupdatedserver')").raises(Fog::Compute::OpenStack::NotFound) do + Fog::Compute[:openstack].update_server(0, :name => 'fogupdatedserver', :adminPass => 'fogupdatedserver') + end + + tests('#reboot_server(0)').raises(Fog::Compute::OpenStack::NotFound) do + pending if Fog.mocking? + Fog::Compute[:openstack].reboot_server(0) + end + + end + +end