diff --git a/fog.gemspec b/fog.gemspec index 6c2705be7..c6a10b6fd 100644 --- a/fog.gemspec +++ b/fog.gemspec @@ -41,7 +41,7 @@ Gem::Specification.new do |s| ## List your runtime dependencies here. Runtime dependencies are those ## that are needed for an end user to actually USE your code. s.add_dependency('builder') - s.add_dependency('excon', '~>0.20') + s.add_dependency('excon', '~>0.22.1') s.add_dependency('formatador', '~>0.2.0') s.add_dependency('multi_json', '~>1.0') s.add_dependency('mime-types') diff --git a/lib/fog.rb b/lib/fog.rb index 3185281b8..d53baf9cd 100644 --- a/lib/fog.rb +++ b/lib/fog.rb @@ -42,3 +42,5 @@ require 'fog/vsphere' require 'fog/voxel' require 'fog/xenserver' require 'fog/zerigo' +require 'fog/cloudsigma' + diff --git a/lib/fog/aws/models/compute/server.rb b/lib/fog/aws/models/compute/server.rb index 9bbf6123e..5e331bb45 100644 --- a/lib/fog/aws/models/compute/server.rb +++ b/lib/fog/aws/models/compute/server.rb @@ -8,46 +8,52 @@ module Fog extend Fog::Deprecation deprecate :ip_address, :public_ip_address - identity :id, :aliases => 'instanceId' + identity :id, :aliases => 'instanceId' attr_accessor :architecture - attribute :ami_launch_index, :aliases => 'amiLaunchIndex' - attribute :availability_zone, :aliases => 'availabilityZone' - attribute :block_device_mapping, :aliases => 'blockDeviceMapping' - attribute :network_interfaces, :aliases => 'networkInterfaces' - attribute :client_token, :aliases => 'clientToken' - attribute :dns_name, :aliases => 'dnsName' - attribute :ebs_optimized, :aliases => 'ebsOptimized' + attribute :ami_launch_index, :aliases => 'amiLaunchIndex' + attribute :availability_zone, :aliases => 'availabilityZone' + attribute :block_device_mapping, :aliases => 'blockDeviceMapping' + attribute :network_interfaces, :aliases => 'networkInterfaces' + attribute :client_token, :aliases => 'clientToken' + attribute :dns_name, :aliases => 'dnsName' + attribute :ebs_optimized, :aliases => 'ebsOptimized' attribute :groups - attribute :flavor_id, :aliases => 'instanceType' - attribute :iam_instance_profile, :aliases => 'iamInstanceProfile' - attribute :image_id, :aliases => 'imageId' + attribute :flavor_id, :aliases => 'instanceType' + attribute :hypervisor + attribute :iam_instance_profile, :aliases => 'iamInstanceProfile' + attribute :image_id, :aliases => 'imageId' attr_accessor :instance_initiated_shutdown_behavior - attribute :kernel_id, :aliases => 'kernelId' - attribute :key_name, :aliases => 'keyName' - attribute :created_at, :aliases => 'launchTime' - attribute :monitoring, :squash => 'state' - attribute :placement_group, :aliases => 'groupName' - attribute :platform, :aliases => 'platform' - attribute :product_codes, :aliases => 'productCodes' - attribute :private_dns_name, :aliases => 'privateDnsName' - attribute :private_ip_address, :aliases => 'privateIpAddress' - attribute :public_ip_address, :aliases => 'ipAddress' - attribute :ramdisk_id, :aliases => 'ramdiskId' + attribute :kernel_id, :aliases => 'kernelId' + attribute :key_name, :aliases => 'keyName' + attribute :created_at, :aliases => 'launchTime' + attribute :lifecycle, :aliases => 'instanceLifecycle' + attribute :monitoring, :squash => 'state' + attribute :placement_group, :aliases => 'groupName' + attribute :platform, :aliases => 'platform' + attribute :product_codes, :aliases => 'productCodes' + attribute :private_dns_name, :aliases => 'privateDnsName' + attribute :private_ip_address, :aliases => 'privateIpAddress' + attribute :public_ip_address, :aliases => 'ipAddress' + attribute :ramdisk_id, :aliases => 'ramdiskId' attribute :reason - attribute :root_device_name, :aliases => 'rootDeviceName' - attribute :root_device_type, :aliases => 'rootDeviceType' - attribute :security_group_ids, :aliases => 'securityGroupIds' - attribute :state, :aliases => 'instanceState', :squash => 'name' - attribute :state_reason, :aliases => 'stateReason' - attribute :subnet_id, :aliases => 'subnetId' + attribute :requester_id, :aliases => 'requesterId' + attribute :root_device_name, :aliases => 'rootDeviceName' + attribute :root_device_type, :aliases => 'rootDeviceType' + attribute :security_group_ids, :aliases => 'securityGroupIds' + attribute :source_dest_check, :aliases => 'sourceDestCheck' + attribute :spot_instance_request_id, :aliases => 'spotInstanceRequestId' + attribute :state, :aliases => 'instanceState', :squash => 'name' + attribute :state_reason, :aliases => 'stateReason' + attribute :subnet_id, :aliases => 'subnetId' attribute :tenancy - attribute :tags, :aliases => 'tagSet' + attribute :tags, :aliases => 'tagSet' attribute :user_data - attribute :vpc_id, :aliases => 'vpcId' + attribute :virtualization_type, :aliases => 'virtualizationType' + attribute :vpc_id, :aliases => 'vpcId' - attr_accessor :password - attr_writer :iam_instance_profile_name, :iam_instance_profile_arn + attr_accessor :password + attr_writer :iam_instance_profile_name, :iam_instance_profile_arn def initialize(attributes={}) diff --git a/lib/fog/aws/models/rds/snapshots.rb b/lib/fog/aws/models/rds/snapshots.rb index e5f0b1775..44fcee37c 100644 --- a/lib/fog/aws/models/rds/snapshots.rb +++ b/lib/fog/aws/models/rds/snapshots.rb @@ -21,10 +21,26 @@ module Fog super end + # This will return a single page based on the current or provided filters, + # updating the filters with the marker for the next page. Calling this repeatedly + # will iterate through pages. def all(filters = filters) - self.filters = filters - data = service.describe_db_snapshots(filters).body['DescribeDBSnapshotsResult']['DBSnapshots'] - load(data) # data is an array of attribute hashes + self.filters.merge!(filters) + + snapshots = service.describe_db_snapshots(filters) + self.filters[:marker] = snapshots.body['DescribeDBSnapshotsResult']['Marker'] + data = snapshots.body['DescribeDBSnapshotsResult']['DBSnapshots'] + load(data) + end + + # This will execute a block for each snapshot, fetching new pages of snapshots as required. + def each(filters = filters) + begin + page = self.all(filters) + # We need to explicitly use the base 'each' method here on the page, otherwise we get infinite recursion + base_each = Fog::Collection.instance_method(:each) + base_each.bind(page).call {|snapshot| yield snapshot} + end while self.filters[:marker] end def get(identity) diff --git a/lib/fog/aws/models/storage/files.rb b/lib/fog/aws/models/storage/files.rb index a2dd3cf18..c23cbf834 100644 --- a/lib/fog/aws/models/storage/files.rb +++ b/lib/fog/aws/models/storage/files.rb @@ -68,7 +68,7 @@ module Fog }) new(file_data) rescue Excon::Errors::NotFound => error - case error.message + case error.response.body when /NoSuchKey<\/Code>/ nil when /NoSuchBucket<\/Code>/ diff --git a/lib/fog/aws/parsers/compute/describe_instances.rb b/lib/fog/aws/parsers/compute/describe_instances.rb index 36783cdd5..a1fb88dbd 100644 --- a/lib/fog/aws/parsers/compute/describe_instances.rb +++ b/lib/fog/aws/parsers/compute/describe_instances.rb @@ -31,10 +31,11 @@ module Fog @instance[@context.last][name] = value when 'availabilityZone', 'tenancy' @instance['placement'][name] = value - when 'architecture', 'clientToken', 'dnsName', 'imageId', - 'instanceId', 'instanceType', 'ipAddress', 'kernelId', - 'keyName', 'platform', 'privateDnsName', 'privateIpAddress', 'ramdiskId', - 'reason', 'rootDeviceType', 'virtualizationType' + when 'architecture', 'clientToken', 'dnsName', 'hypervisor', 'imageId', + 'instanceId', 'instanceType', 'ipAddress', 'kernelId', 'keyName', + 'instanceLifecycle', 'platform', 'privateDnsName', 'privateIpAddress', 'ramdiskId', + 'reason', 'requesterId', 'rootDeviceType', + 'spotInstanceRequestId', 'virtualizationType' @instance[name] = value when 'attachTime' @block_device_mapping[name] = Time.parse(value) @@ -103,6 +104,12 @@ module Fog @instance['monitoring'][name] = (value == 'enabled') when 'ebsOptimized' @instance['ebsOptimized'] = (value == 'true') + when 'sourceDestCheck' + if value == 'true' + @instance[name] = true + else + @instance[name] = false + end # Eucalyptus passes status in schema non conforming way when 'stateCode' @instance['instanceState']['code'] = value diff --git a/lib/fog/aws/parsers/compute/run_instances.rb b/lib/fog/aws/parsers/compute/run_instances.rb index 8bb41708e..dbd82c217 100644 --- a/lib/fog/aws/parsers/compute/run_instances.rb +++ b/lib/fog/aws/parsers/compute/run_instances.rb @@ -24,10 +24,11 @@ module Fog case name when 'amiLaunchIndex' @instance[name] = value.to_i - when 'architecture', 'clientToken', 'dnsName', 'imageId', - 'instanceId', 'instanceType', 'ipAddress', 'kernelId', - 'keyName', 'privateDnsName', 'privateIpAddress', 'ramdiskId', - 'reason', 'rootDeviceType' + when 'architecture', 'clientToken', 'dnsName', 'hypervisor', 'imageId', + 'instanceId', 'instanceType', 'ipAddress', 'kernelId', 'keyName', + 'instanceLifecycle', 'privateDnsName', 'privateIpAddress', 'ramdiskId', + 'reason', 'requesterId', 'rootDeviceType', 'sourceDestCheck', + 'spotInstanceRequestId', 'virtualizationType' @instance[name] = value when 'availabilityZone', 'tenancy' @instance['placement'][name] = value diff --git a/lib/fog/aws/parsers/dns/get_hosted_zone.rb b/lib/fog/aws/parsers/dns/get_hosted_zone.rb index f676b6191..16516750e 100644 --- a/lib/fog/aws/parsers/dns/get_hosted_zone.rb +++ b/lib/fog/aws/parsers/dns/get_hosted_zone.rb @@ -23,6 +23,8 @@ module Fog @response['HostedZone'] = @hosted_zone @hosted_zone = {} @section = :name_servers + when 'ResourceRecordSetCount' + @response['ResourceRecordSetCount'] = value.to_i end elsif @section == :name_servers case name diff --git a/lib/fog/aws/requests/compute/describe_instances.rb b/lib/fog/aws/requests/compute/describe_instances.rb index 9f809c619..4d18dfad8 100644 --- a/lib/fog/aws/requests/compute/describe_instances.rb +++ b/lib/fog/aws/requests/compute/describe_instances.rb @@ -212,7 +212,7 @@ module Fog 'ownerId' => instance['ownerId'], 'reservationId' => instance['reservationId'] } - reservation_set[instance['reservationId']]['instancesSet'] << instance.reject{|key,value| !['amiLaunchIndex', 'architecture', 'blockDeviceMapping', 'clientToken', 'dnsName', 'ebsOptimized', 'iamInstanceProfile', 'imageId', 'instanceId', 'instanceState', 'instanceType', 'ipAddress', 'kernelId', 'keyName', 'launchTime', 'monitoring', 'networkInterfaces', 'ownerId', 'placement', 'platform', 'privateDnsName', 'privateIpAddress', 'productCodes', 'ramdiskId', 'reason', 'rootDeviceType', 'stateReason'].include?(key)}.merge('tagSet' => self.data[:tag_sets][instance['instanceId']]) + reservation_set[instance['reservationId']]['instancesSet'] << instance.reject{|key,value| !['amiLaunchIndex', 'architecture', 'blockDeviceMapping', 'clientToken', 'dnsName', 'ebsOptimized', 'hypervisor', 'iamInstanceProfile', 'imageId', 'instanceId', 'instanceState', 'instanceType', 'ipAddress', 'kernelId', 'keyName', 'launchTime', 'monitoring', 'networkInterfaces', 'ownerId', 'placement', 'platform', 'privateDnsName', 'privateIpAddress', 'productCodes', 'ramdiskId', 'reason', 'rootDeviceType', 'stateReason', 'virtualizationType'].include?(key)}.merge('tagSet' => self.data[:tag_sets][instance['instanceId']]) end end diff --git a/lib/fog/aws/requests/compute/run_instances.rb b/lib/fog/aws/requests/compute/run_instances.rb index 11959586e..aac4dcf5c 100644 --- a/lib/fog/aws/requests/compute/run_instances.rb +++ b/lib/fog/aws/requests/compute/run_instances.rb @@ -143,6 +143,8 @@ module Fog 'blockDeviceMapping' => [], 'clientToken' => options['clientToken'], 'dnsName' => nil, + 'ebsOptimized' => options['EbsOptimized'] || false, + 'hypervisor' => 'xen', 'imageId' => image_id, 'instanceId' => instance_id, 'instanceState' => { 'code' => 0, 'name' => 'pending' }, @@ -156,7 +158,7 @@ module Fog 'productCodes' => [], 'reason' => nil, 'rootDeviceType' => 'instance-store', - 'ebsOptimized' => options['EbsOptimized'] || false + 'virtualizationType' => 'paravirtual' } instances_set << instance self.data[:instances][instance_id] = instance.merge({ diff --git a/lib/fog/aws/storage.rb b/lib/fog/aws/storage.rb index 21da488d8..74b1f3bfa 100644 --- a/lib/fog/aws/storage.rb +++ b/lib/fog/aws/storage.rb @@ -449,7 +449,7 @@ DATA if VALID_QUERY_KEYS.include?(key) value = params[:query][key] if value - query_args << "#{key}=#{Fog::AWS.escape(value.to_s)}" + query_args << "#{key}=#{value}" else query_args << key end diff --git a/lib/fog/bin.rb b/lib/fog/bin.rb index c7a3650ef..8cf85fcf0 100644 --- a/lib/fog/bin.rb +++ b/lib/fog/bin.rb @@ -94,3 +94,4 @@ require 'fog/bin/vsphere' require 'fog/bin/voxel' require 'fog/bin/xenserver' require 'fog/bin/zerigo' +require 'fog/bin/cloudsigma' diff --git a/lib/fog/bin/cloudsigma.rb b/lib/fog/bin/cloudsigma.rb new file mode 100644 index 000000000..9f572c289 --- /dev/null +++ b/lib/fog/bin/cloudsigma.rb @@ -0,0 +1,29 @@ +class CloudSigma < Fog::Bin + class << self + + def class_for(key) + case key + when :compute + Fog::Compute::CloudSigma + else + raise ArgumentError, "Unrecognized service: #{key}" + end + end + + def [](service) + @@connections ||= Hash.new do |hash, key| + hash[key] = case key + when :compute + Fog::Compute.new(:provider => 'CloudSigma') + else + raise ArgumentError, "Unrecognized service: #{key.inspect}" + end + end + @@connections[service] + end + + def services + Fog::CloudSigma.services + end + end +end \ No newline at end of file diff --git a/lib/fog/cloudsigma.rb b/lib/fog/cloudsigma.rb new file mode 100644 index 000000000..201ea2c07 --- /dev/null +++ b/lib/fog/cloudsigma.rb @@ -0,0 +1,9 @@ +require 'fog/core' + +module Fog + module CloudSigma + extend Fog::Provider + + service(:compute, 'cloudsigma/compute', 'Compute') + end +end diff --git a/lib/fog/cloudsigma/compute.rb b/lib/fog/cloudsigma/compute.rb new file mode 100644 index 000000000..38bbe2e0e --- /dev/null +++ b/lib/fog/cloudsigma/compute.rb @@ -0,0 +1,221 @@ +require 'fog/compute' +require 'fog/cloudsigma/connection' + + +module Fog + module Compute + class CloudSigma < Fog::Service + requires :cloudsigma_password, :cloudsigma_username + recognizes :cloudsigma_password, :cloudsigma_username, :cloudsigma_host + + model_path 'fog/cloudsigma/models' + request_path 'fog/cloudsigma/requests' + + model :volume + collection :volumes + request :create_volume + request :get_volume + request :list_volumes + request :update_volume + request :delete_volume + request :clone_volume + + model :lib_volume + collection :lib_volumes + request :get_lib_volume + request :list_lib_volumes + + model :ipconf + model :nic + model :mountpoint + model :server + collection :servers + request :create_server + request :get_server + request :list_servers + request :update_server + request :delete_server + request :start_server + request :stop_server + request :open_vnc + request :close_vnc + request :clone_server + + model :ip + collection :ips + request :list_ips + request :get_ip + + model :vlan + collection :vlans + request :list_vlans + request :get_vlan + request :update_vlan + + model :subscription + collection :subscriptions + request :list_subscriptions + request :get_subscription + request :create_subscription + request :extend_subscription + + model :price_calculation + request :calculate_subscription_price + + model :profile + request :get_profile + request :update_profile + + model :balance + request :get_balance + + model :current_usage + request :get_current_usage + + model :pricing + request :get_pricing + + + module CommonMockAndReal + def initialize(options={}) + @init_options = options + + setup_connection(options) + end + + def profile + response = get_profile + Profile.new(response.body) + end + + def balance + response = get_balance + + Balance.new(response.body) + end + + def current_usage + response = get_current_usage + + CurrentUsage.new(response.body['usage']) + end + + def currency + # Cache since currency does not change + @currency ||= profile.currency + + end + + def pricing + resp = get_princing(currency) + + resp.body['objects'] + end + + def current_pricing_levels + resp = get_pricing(currency) + + resp.body['current'] + end + + def next_pricing_levels + resp = get_pricing(currency) + + resp.body['next'] + end + + def subscription_pricing + resp = get_pricing(currency, true) + + current_levels = resp.body['current'] + current_prices = resp.body['objects'] + + current_pricing_pairs = current_levels.map do |resource, level| + price_for_resource_and_level = current_prices.detect do |price| + price['resource'] == resource + end + price_for_resource_and_level ||= {} + + [resource, price_for_resource_and_level] + end + + Pricing.new(Hash[current_pricing_pairs]) + end + + def current_pricing + resp = get_pricing(currency) + + current_levels = resp.body['current'] + current_prices = resp.body['objects'] + + current_pricing_pairs = current_levels.map do |resource, level| + price_for_resource_and_level = current_prices.detect do |price| + price['level'] == level && price['resource'] == resource + end + price_for_resource_and_level ||= {} + + [resource, price_for_resource_and_level] + end + + Pricing.new(Hash[current_pricing_pairs]) + end + + def next_pricing + resp = get_pricing(currency) + + current_levels = resp.body['next'] + current_prices = resp.body['objects'] + + current_pricing_pairs = current_levels.map do |resource, level| + price_for_resource_and_level = current_prices.detect do |price| + price['level'] == level && price['resource'] == resource + end + price_for_resource_and_level ||= {} + + [resource, price_for_resource_and_level] + end + + Pricing.new(Hash[current_pricing_pairs]) + end + + end + + class Mock + include Collections + include CommonMockAndReal + include Fog::CloudSigma::CloudSigmaConnection::Mock + require 'fog/cloudsigma/mock_data' + + def self.data + @data ||= Hash.new do |hash, key| + hash[key] = mock_data + end + end + + def self.random_uuid + # Insert '4' at 13th position and 'a' at 17th as per uuid4 spec + hex = Fog::Mock.random_hex(30).insert(12,'4').insert(16, 'a') + # Add dashes + "#{hex[0...8]}-#{hex[8...12]}-#{hex[12...16]}-#{hex[16...20]}-#{hex[20..32]}" + end + + def self.random_mac + (0..5).map{Fog::Mock.random_hex(2)}.join(':') + end + + def data + self.class.data[:test] + end + end + + class Real + include Collections + include CommonMockAndReal + include Fog::CloudSigma::CloudSigmaConnection::Real + + end + + end + end + +end diff --git a/lib/fog/cloudsigma/connection.rb b/lib/fog/cloudsigma/connection.rb new file mode 100644 index 000000000..7cb4f59a6 --- /dev/null +++ b/lib/fog/cloudsigma/connection.rb @@ -0,0 +1,197 @@ +require 'fog/cloudsigma/error' + +module Fog + module CloudSigma + module CloudSigmaConnection + + module Real + def auth_header(type = :basic) + case type + when :basic + unless @username and @password + raise ArgumentError, 'Username and password required for basic auth' + end + {'Authorization' => 'Basic ' << Base64.encode64("#{@username}:#{@password}").gsub("\n", '')} + else + unless @username and @password + raise ArgumentError, 'Username and password required for basic auth' + end + {'Authorization' => 'Basic ' << Base64.encode64("#{@username}:#{@password}").gsub("\n", '')} + end + end + + def setup_connection(options) + @persistent = options[:persistent] || false + @connection_options = options[:connection_options] || {} + @connection_options[:ssl_verify_peer] = false + + @auth_type = options[:cloudsigma_auth_type] || :basic + + @username = options[:cloudsigma_username] + @password = options[:cloudsigma_password] + + @scheme = options[:cloudsigma_scheme] || 'https' + @host = options[:cloudsigma_host] || 'lvs.cloudsigma.com' + @port = options[:cloudsigma_port] || '443' + @api_path_prefix = options[:cloudsigma_api_path_prefix] || 'api' + @api_version = options[:cloudsigma_api_version] || '2.0' + @path_prefix = "#{@api_path_prefix}/#{@api_version}/" + + @connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}", @persistent, @connection_options) + end + + def request(params) + params[:headers] = params.fetch(:headers, {}).merge(auth_header(@auth_type)) + params[:headers]['Content-Type'] = 'application/json; charset=utf-8' + + req_path = params[:path] + params[:path] = "#{@path_prefix}#{req_path}" + + + params[:body] = Fog::JSON.encode(params[:body]) if params[:body] + + begin + response = @connection.request(params) + rescue Excon::Errors::HTTPStatusError => e + + e.response.data[:body] = Fog::JSON.decode(e.response[:body]) unless e.response[:body].empty? + err = Fog::CloudSigma::Errors.slurp_http_status_error(e) + + raise err + end + response.body = Fog::JSON.decode(response.body) unless response.body.empty? + + response + end + + def list_request(path, override_params={}) + default_params = {:method => 'GET', :expects => 200, :query => {:limit => 0}} + override_params[:path] = path + params = default_params.merge(override_params) + + request(params) + end + + def get_request(path, override_params={}) + default_params = {:method => 'GET', :expects => 200} + override_params[:path] = path + params = default_params.merge(override_params) + + request(params) + end + + def delete_request(path, override_params={}) + default_params = {:method => 'DELETE', :expects => 204} + override_params[:path] = path + params = default_params.merge(override_params) + + request(params) + end + + def create_request(path, data, override_params={}) + default_params = {:method => 'POST', :expects => [200, 201, 202]} + + override_params[:path] = path + override_params[:body] = data + params = default_params.merge(override_params) + + request(params) + end + + def update_request(path, data, override_params={}) + default_params = {:method => 'PUT', :expects => [200, 202]} + + override_params[:path] = path + override_params[:body] = data + params = default_params.merge(override_params) + + request(params) + end + end + + module Mock + def setup_connection(options) + @username = options[:cloudsigma_username] + @password = options[:cloudsigma_password] + + end + + def mock_get(obj_or_collection, status, key=nil) + data = self.data[obj_or_collection] + if key + data = data[key] + unless data + raise Fog::CloudSigma::Errors::NotFound.new("Object with uuid #{key} does not exist", 'notexist') + end + end + + Excon::Response.new(:body => Fog::JSON.decode(Fog::JSON.encode(data)), :status => status) + end + + def mock_list(collection, status) + data_array = self.data[collection].values + + Excon::Response.new(:body => {'objects' => data_array}, :status => status) + end + + def mock_update(data, obj_or_collection, status, key, &clean_before_update) + data = Fog::JSON.decode(Fog::JSON.encode(data)) + if key + unless self.data[obj_or_collection][key] + raise Fog::CloudSigma::Errors::NotFound.new("Object with uuid #{key} does not exist", 'notexist') + end + if clean_before_update + new_data = clean_before_update.call(self.data[obj_or_collection][key], data) + else + new_data = self.data[obj_or_collection][key].merge(data) + end + + self.data[obj_or_collection][key] = new_data + else + if clean_before_update + new_data = clean_before_update.call(self.data[obj_or_collection], data) + else + new_data = self.data[obj_or_collection].merge(data) + end + + self.data[obj_or_collection] = new_data + end + + Excon::Response.new(:body => Fog::JSON.decode(Fog::JSON.encode(new_data)), :status => status) + end + + def mock_delete(collection, status, key) + self.data[collection].delete(key) + + Excon::Response.new(:body => '', :status => status) + end + + def mock_create(collection, status, data, key, defaults={}, &clean_before_store) + data_with_defaults = data.merge(defaults) {|k, oldval, newval| oldval == nil ? newval: oldval} + + if clean_before_store + cleaned_data = clean_before_store.call(data_with_defaults) + else + cleaned_data = data_with_defaults + end + + # Encode and decode into JSON so that the result is the same as the one returned and parsed from the API + final_data = Fog::JSON.decode(Fog::JSON.encode(cleaned_data)) + + self.data[collection][key] = final_data + + # dup so that stored data is different instance from response data + response_data = final_data.dup + + response = Excon::Response.new + response.body = {'objects' => [response_data]} + response.status = status + + response + end + + end + + end + end +end \ No newline at end of file diff --git a/lib/fog/cloudsigma/error.rb b/lib/fog/cloudsigma/error.rb new file mode 100644 index 000000000..bba86fb58 --- /dev/null +++ b/lib/fog/cloudsigma/error.rb @@ -0,0 +1,45 @@ +module Fog + module CloudSigma + module Errors + class Error < Fog::Errors::Error + attr_accessor :type, :error_point + + def initialize(message, type='n/a', error_point=nil) + @type = type + @error_point = error_point + super(message) + end + + end + + class NotFound < Error; end + class RequestError < Error; end + class ServerError < Error; end + + def self.slurp_http_status_error(error) + error_class = case error.response[:status] + when 404 + NotFound + when 500..599 + ServerError + when 400..499 + RequestError + else + Error + end + + new_error = error_class.new(error.response[:body].first['error_message'], + error.response[:body].first['error_type'], + error.response[:body].first['error_point']) + new_error.set_backtrace(error.backtrace) + new_error.verbose = error.message + new_error + end + + + + end + + + end +end \ No newline at end of file diff --git a/lib/fog/cloudsigma/mock_data.rb b/lib/fog/cloudsigma/mock_data.rb new file mode 100644 index 000000000..dd85aa912 --- /dev/null +++ b/lib/fog/cloudsigma/mock_data.rb @@ -0,0 +1,46 @@ +module Fog + module Compute + class CloudSigma + class Mock + def self.mock_data + { + :volumes => {}, + :servers => {}, + :vlans => {}, + :ips => {}, + :profile => {:login_sms=>false, + :town=>"", + :postcode=>"", + :reseller=>"", + :has_autotopup=>false, + :currency=>"CHF", + :state=>"REGULAR", + :uuid=>"6c2203a1-a2e6-433f-aeab-b976b8cd3d18", + :company=>"", + :api_https_only=>false, + :my_notes=>"", + :key_auth=>false, + :email=>"MyFirstName.MyLasttName@MyCompany.com", + :bank_reference=>"mmlastname278", + :first_name=>"MyFirstName", + :meta =>"", + :phone=>"", + :language=>"EN", + :vat=>"", + :last_name=>"MyLasttName", + :title=>"", + :mailing_list=>true, + :autotopup_amount=>0.0, + :country=>"", + :address=>""}, + :subscriptions => {}, + :current_usage => {}, + :balance => {:balance => 100, :currency => 'CHF'}, + + + } + end + end + end + end +end \ No newline at end of file diff --git a/lib/fog/cloudsigma/models/balance.rb b/lib/fog/cloudsigma/models/balance.rb new file mode 100644 index 000000000..d6e9e74cb --- /dev/null +++ b/lib/fog/cloudsigma/models/balance.rb @@ -0,0 +1,12 @@ +require 'fog/cloudsigma/nested_model' + +module Fog + module Compute + class CloudSigma + class Balance < Fog::CloudSigma::CloudsigmaModel + attribute :balance, :type => :float + attribute :currency, :type => :string + end + end + end +end diff --git a/lib/fog/cloudsigma/models/current_usage.rb b/lib/fog/cloudsigma/models/current_usage.rb new file mode 100644 index 000000000..875744f6e --- /dev/null +++ b/lib/fog/cloudsigma/models/current_usage.rb @@ -0,0 +1,19 @@ +require 'fog/cloudsigma/nested_model' +require 'fog/cloudsigma/models/usage_record' + +module Fog + module Compute + class CloudSigma + class CurrentUsage < Fog::CloudSigma::CloudsigmaModel + model_attribute :cpu, UsageRecord + model_attribute :hdd, UsageRecord + model_attribute :ip, UsageRecord + model_attribute :mem, UsageRecord + model_attribute :sms, UsageRecord + model_attribute :ssd, UsageRecord + model_attribute :tx, UsageRecord + model_attribute :vlan, UsageRecord + end + end + end +end diff --git a/lib/fog/cloudsigma/models/ip.rb b/lib/fog/cloudsigma/models/ip.rb new file mode 100644 index 000000000..a50053dea --- /dev/null +++ b/lib/fog/cloudsigma/models/ip.rb @@ -0,0 +1,22 @@ +require 'fog/cloudsigma/nested_model' + +module Fog + module Compute + class CloudSigma + class IP < Fog::CloudSigma::CloudsigmaModel + identity :uuid + attribute :tags, :type => :array + attribute :nameservers, :type => :array + attribute :server, :type => :string + attribute :netmask, :type => :integer + attribute :meta + attribute :owner + attribute :subscription + attribute :gateway, :type => :string + attribute :resource_uri, :type => :string + + + end + end + end +end diff --git a/lib/fog/cloudsigma/models/ipconf.rb b/lib/fog/cloudsigma/models/ipconf.rb new file mode 100644 index 000000000..73c3898f3 --- /dev/null +++ b/lib/fog/cloudsigma/models/ipconf.rb @@ -0,0 +1,12 @@ +require 'fog/cloudsigma/nested_model' + +module Fog + module Compute + class CloudSigma + class IPConf < Fog::CloudSigma::CloudsigmaModel + attribute :ip + attribute :conf, :type => :string + end + end + end +end diff --git a/lib/fog/cloudsigma/models/ips.rb b/lib/fog/cloudsigma/models/ips.rb new file mode 100644 index 000000000..cfb7906d1 --- /dev/null +++ b/lib/fog/cloudsigma/models/ips.rb @@ -0,0 +1,27 @@ +require 'fog/core/collection' +require 'fog/cloudsigma/models/ip' + +module Fog + module Compute + class CloudSigma + class Ips < Fog::Collection + model Fog::Compute::CloudSigma::IP + + def all + resp = service.list_ips + data = resp.body['objects'] + load(data) + end + + def get(ip) + resp = service.get_ip(ip) + data = resp.body + new(data) + rescue Fog::CloudSigma::Errors::NotFound + return nil + end + + end + end + end +end diff --git a/lib/fog/cloudsigma/models/lib_volume.rb b/lib/fog/cloudsigma/models/lib_volume.rb new file mode 100644 index 000000000..e5d285018 --- /dev/null +++ b/lib/fog/cloudsigma/models/lib_volume.rb @@ -0,0 +1,50 @@ +require 'fog/core/model' + +module Fog + module Compute + class CloudSigma + class LibVolume < Fog::Model + identity :uuid + + attribute :mounted_on, :type => :related + attribute :licenses + attribute :meta + attribute :owner + attribute :affinities + attribute :image_format, :type => :string + attribute :size, :type => :integer + attribute :category + attribute :image_type, :type => :string + attribute :media, :type => :string + attribute :state, :type => :string + attribute :status, :type => :string + attribute :jobs + attribute :description, :type => :string + attribute :tags + attribute :favourite, :type => :boolean + attribute :paid, :type => :boolean + attribute :allow_multimount, :type => :boolean + attribute :install_notes, :type => :string + attribute :arch, :type => :string + attribute :name, :type => :string + attribute :url, :type => :string + attribute :os, :type => :string + attribute :resource_uri, :type => :string + + + + def reload + requires :identity + collection.get(identity) + end + + def clone(clone_params={}) + requires :identity + response = service.clone_volume(identity, clone_params) + + self.class.new(response.body['objects'].first) + end + end + end + end +end \ No newline at end of file diff --git a/lib/fog/cloudsigma/models/lib_volumes.rb b/lib/fog/cloudsigma/models/lib_volumes.rb new file mode 100644 index 000000000..7578ab9f8 --- /dev/null +++ b/lib/fog/cloudsigma/models/lib_volumes.rb @@ -0,0 +1,27 @@ +require 'fog/core/collection' +require 'fog/cloudsigma/models/lib_volume' + +module Fog + module Compute + class CloudSigma + class LibVolumes < Fog::Collection + model Fog::Compute::CloudSigma::LibVolume + + def all + resp = service.list_lib_volumes + data = resp.body['objects'] + load(data) + end + + def get(vol_id) + resp = service.get_lib_volume(vol_id) + data = resp.body + new(data) + rescue Fog::CloudSigma::Errors::NotFound + return nil + end + + end + end + end +end diff --git a/lib/fog/cloudsigma/models/mountpoint.rb b/lib/fog/cloudsigma/models/mountpoint.rb new file mode 100644 index 000000000..3718cd4ec --- /dev/null +++ b/lib/fog/cloudsigma/models/mountpoint.rb @@ -0,0 +1,25 @@ +require 'fog/core/model' + +module Fog + module Compute + class CloudSigma + class MountPoint < Fog::Model + attribute :device, :type => 'string' + attribute :dev_channel, :type => 'string' + attribute :drive + attribute :boot_order, :type => 'integer' + + def drive + drive = attributes[:drive] + + drive.kind_of?(Hash) ? drive['uuid'] : drive + end + + def drive=(new_drive) + attributes[:drive] = new_drive + end + alias :volume :drive + end + end + end +end diff --git a/lib/fog/cloudsigma/models/nic.rb b/lib/fog/cloudsigma/models/nic.rb new file mode 100644 index 000000000..a552e0da1 --- /dev/null +++ b/lib/fog/cloudsigma/models/nic.rb @@ -0,0 +1,22 @@ +require 'fog/core/model' +require 'fog/cloudsigma/nested_model' +require 'fog/cloudsigma/models/ipconf' + + +module Fog + module Compute + class CloudSigma + class Nic < Fog::CloudSigma::CloudsigmaModel + + + attribute :boot_order + attribute :mac, :type => :string + attribute :model, :type => :string + attribute :vlan + model_attribute :ip_v4_conf, IPConf + model_attribute :ip_v6_conf, IPConf + + end + end + end +end diff --git a/lib/fog/cloudsigma/models/obj_ref.rb b/lib/fog/cloudsigma/models/obj_ref.rb new file mode 100644 index 000000000..bb6468b38 --- /dev/null +++ b/lib/fog/cloudsigma/models/obj_ref.rb @@ -0,0 +1,12 @@ +require 'fog/core/model' + +module Fog + module Compute + class CloudSigma + class ObjRef < Fog::Model + attribute :uuid, :type => :string + attribute :resource_uri, :type => :string + end + end + end +end diff --git a/lib/fog/cloudsigma/models/price_calculation.rb b/lib/fog/cloudsigma/models/price_calculation.rb new file mode 100644 index 000000000..d1fa4b2f6 --- /dev/null +++ b/lib/fog/cloudsigma/models/price_calculation.rb @@ -0,0 +1,13 @@ +require 'fog/cloudsigma/nested_model' +require 'fog/cloudsigma/models/subscriptions' + +module Fog + module Compute + class CloudSigma + class PriceCalculation < Fog::CloudSigma::CloudsigmaModel + attribute :price, :type => :float + model_attribute_array :subscriptions, Subscription, :aliases => 'objects' + end + end + end +end diff --git a/lib/fog/cloudsigma/models/price_record.rb b/lib/fog/cloudsigma/models/price_record.rb new file mode 100644 index 000000000..27816a209 --- /dev/null +++ b/lib/fog/cloudsigma/models/price_record.rb @@ -0,0 +1,38 @@ +require 'fog/cloudsigma/nested_model' +require 'bigdecimal' + +module Fog + module Compute + class CloudSigma + class PriceRecord < Fog::CloudSigma::CloudsigmaModel + attribute :resource, :type => :string + attribute :multiplier, :type => :integer + attribute :price, :type => :string + attribute :level, :type => :integer + attribute :currency, :type => :string + attribute :unit, :type => :string + + def price + if attributes[:price] + BigDecimal(attributes[:price]) + else + nil + end + end + + def price=(new_price) + attributes[:price] = new_price.kind_of?(String) ? new_price : new_price.to_s('F') + end + + # The base price of the resource. + # This is the price for the base API unit which is byte for memory, data, etc. and MHz for CPU. + # Also the price is per second for time based resource (basically everything except data transfer which is not + # limited in time) + def base_price + price / multiplier + end + + end + end + end +end \ No newline at end of file diff --git a/lib/fog/cloudsigma/models/pricing.rb b/lib/fog/cloudsigma/models/pricing.rb new file mode 100644 index 000000000..be81d1bf9 --- /dev/null +++ b/lib/fog/cloudsigma/models/pricing.rb @@ -0,0 +1,20 @@ +require 'fog/cloudsigma/nested_model' +require 'fog/cloudsigma/models/price_record' + +module Fog + module Compute + class CloudSigma + class Pricing < Fog::CloudSigma::CloudsigmaModel + model_attribute :cpu, PriceRecord + model_attribute :hdd, PriceRecord + model_attribute :ip, PriceRecord + model_attribute :mem, PriceRecord + model_attribute :sms, PriceRecord + model_attribute :ssd, PriceRecord + model_attribute :tx, PriceRecord + model_attribute :vlan, PriceRecord + + end + end + end +end \ No newline at end of file diff --git a/lib/fog/cloudsigma/models/profile.rb b/lib/fog/cloudsigma/models/profile.rb new file mode 100644 index 000000000..ea4639ec3 --- /dev/null +++ b/lib/fog/cloudsigma/models/profile.rb @@ -0,0 +1,50 @@ +require 'fog/cloudsigma/nested_model' + +module Fog + module Compute + class CloudSigma + class Profile < Fog::CloudSigma::CloudsigmaModel + identity :uuid + + attribute :last_name, :type => :string + attribute :login_sms, :type => :boolean + attribute :currency, :type => :string + attribute :meta + attribute :api_https_only, :type => :boolean + attribute :first_name, :type => :string + attribute :uuid, :type => :string + attribute :title, :type => :string + attribute :state, :type => :string + attribute :email, :type => :string + attribute :vat, :type => :string + attribute :autotopup_amount, :type => :float + attribute :reseller, :type => :string + attribute :company, :type => :string + attribute :key_auth, :type => :boolean + attribute :phone, :type => :string + attribute :address, :type => :string + attribute :mailing_list, :type => :boolean + attribute :town, :type => :string + attribute :has_autotopup, :type => :boolean + attribute :my_notes, :type => :string + attribute :bank_reference, :type => :string + attribute :language, :type => :string + attribute :country, :type => :string + attribute :postcode, :type => :string + + + def save + update + end + + def update + response = service.update_profile(attributes) + self.attribute.merge!(response.body) + + self + end + + end + end + end +end \ No newline at end of file diff --git a/lib/fog/cloudsigma/models/server.rb b/lib/fog/cloudsigma/models/server.rb new file mode 100644 index 000000000..3afbe1233 --- /dev/null +++ b/lib/fog/cloudsigma/models/server.rb @@ -0,0 +1,208 @@ +require 'fog/cloudsigma/nested_model' +require 'fog/cloudsigma/models/mountpoint' +require 'fog/cloudsigma/models/nic' + +module Fog + module Compute + class CloudSigma + class Server < Fog::CloudSigma::CloudsigmaModel + + + identity :uuid + + attribute :status, :type => :string + attribute :vnc_password, :type => :string + attribute :name, :type => :string + attribute :cpus_instead_of_cores, :type => :boolean + attribute :tags + attribute :mem, :type => :integer + attribute :enable_numa, :type => :boolean + attribute :smp + attribute :hv_relaxed, :type => :boolean + attribute :hv_tsc, :type => :boolean + attribute :meta + attribute :owner + attribute :runtime + attribute :cpu, :type => :integer + attribute :resource_uri, :type => :string + model_attribute_array :volumes, MountPoint, :aliases => 'drives' + model_attribute_array :nics, Nic + + + def save + if persisted? + update + else + create + end + end + + def create + requires :name, :cpu, :mem, :vnc_password + data = attributes + + response = service.create_server(data) + new_attributes = response.body['objects'].first + merge_attributes(new_attributes) + end + + def update + requires :identity, :name, :cpu, :mem, :vnc_password + + data = attributes + + response = service.update_server(identity, data) + new_attributes = response.body + merge_attributes(new_attributes) + + end + + def destroy + requires :identity + + service.delete_server(identity) + true + end + + alias :delete :destroy + + def start(start_params={}) + requires :identity + service.start_server(identity, start_params) + end + + def stop + requires :identity + service.stop_server(identity) + end + + def open_vnc + requires :identity + service.open_vnc(identity) + end + + def close_vnc + requires :identity + service.close_vnc(identity) + end + + def clone(clone_params={}) + requires :identity + response = service.clone_server(identity, clone_params) + + self.class.new(response.body) + end + + def mount_volume(volume, device = 'virtio', dev_channel = nil, boot_order = nil) + unless dev_channel + specified_channels = self.volumes.map { |v| v.dev_channel }.sort + if specified_channels + controller, controller_channel = 0, 0 + max_ctlr, max_chnl = case device + when 'ide' + [4, 2] + else + [1024, 5] + end + + dev_channel = "#{controller}:#{controller_channel}" + while specified_channels.include? dev_channel + controller_channel += 1 + if controller_channel >= max_chnl + controller_channel = 0 + controller += 1 + if controller >= max_ctlr + raise Fog::CloudSigma::Errors::Error.new("Max channel reached, cannot attach more") + end + end + dev_channel = "#{controller}:#{controller_channel}" + end + else # no other channels specified + dev_channel = '0:0' + end + end + + + vol_id = volume.kind_of?(String) ? volume : volume.identity + mountpoint_data = { + 'drive' => vol_id, + 'device' => device, + 'dev_channel' => dev_channel, + } + + if boot_order + mountpoint_data['boot_order'] = boot_order + end + + self.volumes = self.volumes << MountPoint.new(mountpoint_data) + end + + def unmount_volume(volume_or_position) + if volume_or_position.kind_of? Fixnum + self.volumes.delete_at(volume_or_position) + # assign to update attributes + return self.volumes = self.volumes + end + + vol_id = volume_or_position.kind_of?(String) ? volume_or_position : volume_or_position.identity + self.volumes = self.volumes.reject do |v| + if v.volume.kind_of? Hash + v.volume['uuid'] == vol_id + else + v.volume == vol_id + end + end + end + + def unmount_all_volumes + self.volumes = [] + end + + def add_nic(vlan=nil, ip_v4_conf=nil, ip_v6_conf=nil, model='virtio', boot_order=nil) + nic_data = { + 'model' => model, + 'vlan' => vlan, + 'ip_v4_conf' => ip_v4_conf, + 'ip_v6_conf' => ip_v6_conf + } + if boot_order + nic_data['boot_order'] = boot_order + end + + self.nics = self.nics << Nic.new(nic_data) + end + + def add_public_nic(ip_or_conf=:dhcp, model='virtio', boot_order=nil) + case ip_or_conf + when :dhcp + add_nic(nil, {:conf => :dhcp}, nil, model, boot_order) + when :manual + add_nic(nil, {:conf => :manual}, nil, model, boot_order) + else + ip = ip_or_conf.kind_of?(String) ? ip_or_conf : ip_or_conf.identity + add_nic(nil, {:conf => :static, :ip => ip}, nil, model, boot_order) + end + end + + def add_private_nic(vlan, model='virtio', boot_order=nil) + vlan = vlan.kind_of?(String) ? vlan : vlan.identity + add_nic(vlan, nil, nil, model, boot_order) + end + + def remove_nic(mac_or_position) + if mac_or_position.kind_of? Fixnum + self.nics.delete_at(mac_or_position) + # assign to update attributes + return self.nics = self.nics + end + + self.nics = self.nics.reject { |n| n.mac == mac_or_position } + end + + def remove_all_nics + self.nics = [] + end + end + end + end +end \ No newline at end of file diff --git a/lib/fog/cloudsigma/models/servers.rb b/lib/fog/cloudsigma/models/servers.rb new file mode 100644 index 000000000..eb68fac6f --- /dev/null +++ b/lib/fog/cloudsigma/models/servers.rb @@ -0,0 +1,27 @@ +require 'fog/core/collection' +require 'fog/cloudsigma/models/server' + +module Fog + module Compute + class CloudSigma + class Servers < Fog::Collection + model Fog::Compute::CloudSigma::Server + + def all + resp = service.list_servers + data = resp.body['objects'] + load(data) + end + + def get(server_id) + resp = service.get_server(server_id) + data = resp.body + new(data) + rescue Fog::CloudSigma::Errors::NotFound + return nil + end + + end + end + end +end diff --git a/lib/fog/cloudsigma/models/subscription.rb b/lib/fog/cloudsigma/models/subscription.rb new file mode 100644 index 000000000..60865270a --- /dev/null +++ b/lib/fog/cloudsigma/models/subscription.rb @@ -0,0 +1,53 @@ +require 'fog/cloudsigma/nested_model' + +module Fog + module Compute + class CloudSigma + class Subscription < Fog::CloudSigma::CloudsigmaModel + identity :id + + attribute :status, :type => :string + attribute :uuid, :type => :string + attribute :resource, :type => :string + attribute :auto_renew, :type => :boolean + attribute :descendants + attribute :start_time, :type => :time + attribute :price, :type => :float + attribute :period, :type => :string + attribute :remaining, :type => :string + attribute :amount, :type => :integer + attribute :end_time, :type => :time + attribute :discount_percent, :type => :float + attribute :subscribed_object, :type => :string + attribute :discount_amount, :type => :float + + def save + create + end + + def create + requires :resource, :amount + data = attributes + + response = service.create_subscription(data) + new_attributes = response.body['objects'].first + merge_attributes(new_attributes) + end + + def extend(period=nil, end_time=nil) + requires :identity + data = {} + if period + data[:period] = period + elsif end_time + data[:end_time] = end_time + end + response = service.extend_subscription(identity, data) + + self.class.new(response.body) + end + + end + end + end +end diff --git a/lib/fog/cloudsigma/models/subscriptions.rb b/lib/fog/cloudsigma/models/subscriptions.rb new file mode 100644 index 000000000..637cbfedc --- /dev/null +++ b/lib/fog/cloudsigma/models/subscriptions.rb @@ -0,0 +1,41 @@ +require 'fog/core/collection' +require 'fog/cloudsigma/models/subscription' + +module Fog + module Compute + class CloudSigma + class Subscriptions < Fog::Collection + model Fog::Compute::CloudSigma::Subscription + + def all + resp = service.list_subscriptions + data = resp.body['objects'] + load(data) + end + + def get(sub_id) + resp = service.get_subscription(sub_id) + data = resp.body + new(data) + rescue Fog::CloudSigma::Errors::NotFound + return nil + end + + def check_price(subscriptions_list) + subscriptions_list = subscriptions_list.map {|s| s.kind_of?(Hash) ? s : s.attributes} + + resp = service.calculate_subscription_price(subscriptions_list) + + PriceCalculation.new(resp.body) + end + + def create_multiple(subscriptions_list) + subscriptions_list = subscriptions_list.map { |s| s.kind_of?(Hash) ? s : s.attributes } + + resp = service.create_subscription(subscriptions_list) + resp.body['objects'].map { |s| Subscription.new(s) } + end + end + end + end +end diff --git a/lib/fog/cloudsigma/models/usage_record.rb b/lib/fog/cloudsigma/models/usage_record.rb new file mode 100644 index 000000000..fd1f064d0 --- /dev/null +++ b/lib/fog/cloudsigma/models/usage_record.rb @@ -0,0 +1,13 @@ +require 'fog/cloudsigma/nested_model' + +module Fog + module Compute + class CloudSigma + class UsageRecord < Fog::CloudSigma::CloudsigmaModel + attribute :burst, :type => :integer + attribute :subscribed, :type => :integer + attribute :using, :type => :integer + end + end + end +end diff --git a/lib/fog/cloudsigma/models/vlan.rb b/lib/fog/cloudsigma/models/vlan.rb new file mode 100644 index 000000000..19290ef7b --- /dev/null +++ b/lib/fog/cloudsigma/models/vlan.rb @@ -0,0 +1,29 @@ +require 'fog/cloudsigma/nested_model' + +module Fog + module Compute + class CloudSigma + class VLAN < Fog::CloudSigma::CloudsigmaModel + identity :uuid + attribute :tags + attribute :servers + attribute :meta + attribute :owner + attribute :resource_uri, :type => :string + attribute :subscription + + def update + requires :identity + data = attributes + + response = service.update_vlan(identity, data) + + new_attributes = response.body + merge_attributes(new_attributes) + end + + alias :save :update + end + end + end +end diff --git a/lib/fog/cloudsigma/models/vlans.rb b/lib/fog/cloudsigma/models/vlans.rb new file mode 100644 index 000000000..b5f67aa03 --- /dev/null +++ b/lib/fog/cloudsigma/models/vlans.rb @@ -0,0 +1,27 @@ +require 'fog/core/collection' +require 'fog/cloudsigma/models/vlan' + +module Fog + module Compute + class CloudSigma + class Vlans < Fog::Collection + model Fog::Compute::CloudSigma::VLAN + + def all + resp = service.list_vlans + data = resp.body['objects'] + load(data) + end + + def get(vlan) + resp = service.get_vlan(vlan) + data = resp.body + new(data) + rescue Fog::CloudSigma::Errors::NotFound + return nil + end + + end + end + end +end diff --git a/lib/fog/cloudsigma/models/volume.rb b/lib/fog/cloudsigma/models/volume.rb new file mode 100644 index 000000000..fee03fba2 --- /dev/null +++ b/lib/fog/cloudsigma/models/volume.rb @@ -0,0 +1,71 @@ +require 'fog/cloudsigma/nested_model' + +module Fog + module Compute + class CloudSigma + class Volume < Fog::CloudSigma::CloudsigmaModel + identity :uuid + + attribute :status, :type => :string + attribute :jobs + attribute :name, :type => :string + attribute :tags + attribute :media, :type => :string + attribute :mounted_on + attribute :owner + attribute :meta + attribute :allow_multimount, :type => :boolean + attribute :licenses + attribute :affinities, :type => :array + attribute :size, :type => :integer + attribute :resource_uri, :type => :string + + + def save + if persisted? + update + else + create + end + end + + def create + requires :name, :size, :media + data = attributes + + response = service.create_volume(data) + new_attributes = response.body['objects'].first + merge_attributes(new_attributes) + end + + def update + requires :identity, :name, :size, :media + + data = attributes() + + response = service.update_volume(identity, data) + new_attributes = response.body + merge_attributes(new_attributes) + + end + + def destroy + requires :identity + + service.delete_volume(identity) + + true + end + + alias :delete :destroy + + def clone(clone_params={}) + requires :identity + response = service.clone_volume(identity, clone_params) + + self.class.new(response.body['objects'].first) + end + end + end + end +end \ No newline at end of file diff --git a/lib/fog/cloudsigma/models/volumes.rb b/lib/fog/cloudsigma/models/volumes.rb new file mode 100644 index 000000000..40881e41e --- /dev/null +++ b/lib/fog/cloudsigma/models/volumes.rb @@ -0,0 +1,27 @@ +require 'fog/core/collection' +require 'fog/cloudsigma/models/volume' + +module Fog + module Compute + class CloudSigma + class Volumes < Fog::Collection + model Fog::Compute::CloudSigma::Volume + + def all + resp = service.list_volumes + data = resp.body['objects'] + load(data) + end + + def get(vol_id) + resp = service.get_volume(vol_id) + data = resp.body + new(data) + rescue Fog::CloudSigma::Errors::NotFound + return nil + end + + end + end + end +end diff --git a/lib/fog/cloudsigma/nested_model.rb b/lib/fog/cloudsigma/nested_model.rb new file mode 100644 index 000000000..1b0ce2118 --- /dev/null +++ b/lib/fog/cloudsigma/nested_model.rb @@ -0,0 +1,60 @@ +module Fog + module CloudSigma + class CloudsigmaModel < Fog::Model + class << self + def model_attribute_array(name, model, options={}) + attributes_key = options[:aliases] || name + class_eval <<-EOS, __FILE__, __LINE__ + def #{name} + #{name}_attrs = attributes[:#{attributes_key}] || [] + refreshed_#{name} = #{name}_attrs.map { |x| #{model}.new(x) } + attributes[:#{attributes_key}] = refreshed_#{name}.map { |x| x.attributes } + + refreshed_#{name} + end + def #{name}=(new_#{name}) + new_#{name} ||= [] + attributes[:#{attributes_key}] = new_#{name}.map { |x| x.kind_of?(Hash) ? x : x.attributes} + end + EOS + + @attributes ||= [] + @attributes |= [name] + for new_alias in [*options[:aliases]] + aliases[new_alias] = name + end + end + + def model_attribute(name, model, options={}) + attributes_key = options[:aliases] || name + class_eval <<-EOS, __FILE__, __LINE__ + def #{name} + #{name}_attrs = attributes[:#{attributes_key}] + if #{name}_attrs + refreshed_#{name} = #{name}_attrs ? #{model}.new(#{name}_attrs) : nil + attributes[:#{attributes_key}] = refreshed_#{name}.attributes + + refreshed_#{name} + else + nil + end + end + def #{name}=(new_#{name}) + if new_#{name} + attributes[:#{attributes_key}] = new_#{name}.kind_of?(Hash) ? new_#{name} : new_#{name}.attributes + else + nil + end + end + EOS + + @attributes ||= [] + @attributes |= [name] + for new_alias in [*options[:aliases]] + aliases[new_alias] = name + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/fog/cloudsigma/requests/calculate_subscription_price.rb b/lib/fog/cloudsigma/requests/calculate_subscription_price.rb new file mode 100644 index 000000000..bd2cfccfa --- /dev/null +++ b/lib/fog/cloudsigma/requests/calculate_subscription_price.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def calculate_subscription_price(data) + create_request("subscriptioncalculator/", data) + end + end + + class Mock + def calculate_subscription_price(data) + + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/clone_libvolume.rb b/lib/fog/cloudsigma/requests/clone_libvolume.rb new file mode 100644 index 000000000..40d66227d --- /dev/null +++ b/lib/fog/cloudsigma/requests/clone_libvolume.rb @@ -0,0 +1,32 @@ +module Fog + module Compute + class CloudSigma + class Real + def clone_libvolume(vol_id, clone_params={}) + request(:path => "libdrives/#{vol_id}/action/", + :method => 'POST', + :query => {:do => :clone}, + :body => clone_params, + :expects => [200, 202]) + end + end + + class Mock + def clone_libvolume(vol_id, clone_params={}) + volume = self.data[:libvolumes][vol_id].dup + uuid = self.class.random_uuid + volume['uuid'] = uuid + + self.data[:volumes][uuid] = volume + + response = Excon::Response.new + response.status = 200 + response.body = volume + + response + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/clone_server.rb b/lib/fog/cloudsigma/requests/clone_server.rb new file mode 100644 index 000000000..da985f764 --- /dev/null +++ b/lib/fog/cloudsigma/requests/clone_server.rb @@ -0,0 +1,32 @@ +module Fog + module Compute + class CloudSigma + class Real + def clone_server(server_id, clone_params={}) + request(:path => "servers/#{server_id}/action/", + :method => 'POST', + :query => {:do => :clone}, + :body => clone_params, + :expects => [200, 202]) + end + end + + class Mock + def clone_server(server_id, clone_params={}) + server = self.data[:servers][server_id].dup + uuid = self.class.random_uuid + server['uuid'] = uuid + + self.data[:servers][uuid] = server + + response = Excon::Response.new + response.status = 200 + response.body = server + + response + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/clone_volume.rb b/lib/fog/cloudsigma/requests/clone_volume.rb new file mode 100644 index 000000000..3a647b914 --- /dev/null +++ b/lib/fog/cloudsigma/requests/clone_volume.rb @@ -0,0 +1,32 @@ +module Fog + module Compute + class CloudSigma + class Real + def clone_volume(vol_id, clone_params={}) + request(:path => "drives/#{vol_id}/action/", + :method => 'POST', + :query => {:do => :clone}, + :body => clone_params, + :expects => [200, 202]) + end + end + + class Mock + def clone_volume(vol_id, clone_params={}) + volume = self.data[:volumes][vol_id].dup + uuid = self.class.random_uuid + volume['uuid'] = uuid + + self.data[:volumes][uuid] = volume + + response = Excon::Response.new + response.status = 200 + response.body = volume + + response + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/close_vnc.rb b/lib/fog/cloudsigma/requests/close_vnc.rb new file mode 100644 index 000000000..e98e58a65 --- /dev/null +++ b/lib/fog/cloudsigma/requests/close_vnc.rb @@ -0,0 +1,30 @@ +module Fog + module Compute + class CloudSigma + class Real + def close_vnc(server_id) + request(:path => "servers/#{server_id}/action/", + :method => 'POST', + :query => {:do => :close_vnc}, + :expects => [200, 202]) + end + end + + class Mock + def close_vnc(server_id) + response = Excon::Response.new + response.status = 200 + + response.body = { + 'action' => 'close_vnc', + 'result' => 'success', + 'uuid' => server_id, + } + + response + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/create_server.rb b/lib/fog/cloudsigma/requests/create_server.rb new file mode 100644 index 000000000..2e8ac5a72 --- /dev/null +++ b/lib/fog/cloudsigma/requests/create_server.rb @@ -0,0 +1,33 @@ +module Fog + module Compute + class CloudSigma + class Real + def create_server(data) + create_request("servers/", data) + end + end + + class Mock + def create_server(data) + uuid = self.class.random_uuid + + defaults = {'uuid' => uuid, + 'status' => 'stopped', + 'smp' => 1, + 'hv_relaxed' => false, + 'hv_tsc' => false, + 'enable_numa' => false, + 'cpus_instead_of_cores' => false, + 'drives' => [], + 'nics' => [], + 'tags' => [] + } + + + mock_create(:servers, 202, data, uuid, defaults) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/create_subscription.rb b/lib/fog/cloudsigma/requests/create_subscription.rb new file mode 100644 index 000000000..80223506a --- /dev/null +++ b/lib/fog/cloudsigma/requests/create_subscription.rb @@ -0,0 +1,40 @@ +module Fog + module Compute + class CloudSigma + class Real + def create_subscription(data) + create_request("subscriptions/", data) + end + end + + class Mock + def create_subscription(data) + + if data[:period] != '1 month' || data[:start_time] || data[:end_time] + raise Fog::Errors::MockNotImplemented.new('Currently only mocks for subscriptions with period 1 month from now are implemented as mock') + end + + id = Fog::Mock.random_numbers(3).to_i + defaults = {'id' => id, + 'start_time' => DateTime.now, + 'end_time' => DateTime.now + 30 * 24 * 60 *60, + 'auto_renew' => false, + 'amount' => 1.0} + + if data[:resource] == 'vlan' + vlan_uuid = self.class.random_uuid + self.data[:vlans][vlan_uuid] = {'uuid' => vlan_uuid, + 'subscription' => {'id' => id}, + 'servers' => [], + 'meta' => {}, + 'tags' => []} + defaults['subscribed_object'] = vlan_uuid + end + + mock_create(:subscriptions, 200, data, id, defaults) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/create_volume.rb b/lib/fog/cloudsigma/requests/create_volume.rb new file mode 100644 index 000000000..185d5b719 --- /dev/null +++ b/lib/fog/cloudsigma/requests/create_volume.rb @@ -0,0 +1,30 @@ +module Fog + module Compute + class CloudSigma + class Real + def create_volume(data) + create_request("drives/", data) + end + end + + class Mock + def create_volume(data) + uuid = self.class.random_uuid + + defaults = {'uuid' => uuid, + 'status' => 'unmounted', + 'tags' => [], + 'mounted_on' => [], + 'affinities' => [], + 'licenses' => [], + 'jobs' => [], + 'allow_multimount' => false, + } + + mock_create(:volumes, 202, data, uuid, defaults) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/delete_server.rb b/lib/fog/cloudsigma/requests/delete_server.rb new file mode 100644 index 000000000..c6e3a39b5 --- /dev/null +++ b/lib/fog/cloudsigma/requests/delete_server.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def delete_server(server_id) + delete_request("servers/#{server_id}/") + end + end + + class Mock + def delete_server(server_id) + mock_delete(:servers, 204, server_id) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/delete_volume.rb b/lib/fog/cloudsigma/requests/delete_volume.rb new file mode 100644 index 000000000..f54f8f092 --- /dev/null +++ b/lib/fog/cloudsigma/requests/delete_volume.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def delete_volume(vol_id) + delete_request("drives/#{vol_id}/") + end + end + + class Mock + def delete_volume(vol_id) + mock_delete(:volumes, 204, vol_id) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/extend_subscription.rb b/lib/fog/cloudsigma/requests/extend_subscription.rb new file mode 100644 index 000000000..87c7d4376 --- /dev/null +++ b/lib/fog/cloudsigma/requests/extend_subscription.rb @@ -0,0 +1,22 @@ +module Fog + module Compute + class CloudSigma + class Real + def extend_subscription(sub_id, data) + request(:path => "subscriptions/#{sub_id}/action/", + :method => 'POST', + :expects => [200, 202], + :query => {:do => :extend}, + :body=>data) + end + end + + class Mock + def extend_subscription(sub_id, data) + + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/get_balance.rb b/lib/fog/cloudsigma/requests/get_balance.rb new file mode 100644 index 000000000..0ffa2afc8 --- /dev/null +++ b/lib/fog/cloudsigma/requests/get_balance.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def get_balance + get_request("balance/") + end + end + + class Mock + def get_balance + mock_get(:balance, 200) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/get_current_usage.rb b/lib/fog/cloudsigma/requests/get_current_usage.rb new file mode 100644 index 000000000..31c78e78f --- /dev/null +++ b/lib/fog/cloudsigma/requests/get_current_usage.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def get_current_usage + get_request("currentusage/") + end + end + + class Mock + def get_current_usage + + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/get_ip.rb b/lib/fog/cloudsigma/requests/get_ip.rb new file mode 100644 index 000000000..58d065baa --- /dev/null +++ b/lib/fog/cloudsigma/requests/get_ip.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def get_ip(ip) + request("ips/#{ip}/") + end + end + + class Mock + def get_ip(ip) + mock_get(:ips, 200, ip) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/get_lib_volume.rb b/lib/fog/cloudsigma/requests/get_lib_volume.rb new file mode 100644 index 000000000..6d0878729 --- /dev/null +++ b/lib/fog/cloudsigma/requests/get_lib_volume.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def get_lib_volume(vol_id) + get_request("libdrives/#{vol_id}/") + end + end + + class Mock + def get_lib_volume(vol_id) + mock_get(:libvolumes, 200, vol_id) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/get_pricing.rb b/lib/fog/cloudsigma/requests/get_pricing.rb new file mode 100644 index 000000000..20e1e9b83 --- /dev/null +++ b/lib/fog/cloudsigma/requests/get_pricing.rb @@ -0,0 +1,28 @@ +module Fog + module Compute + class CloudSigma + class Real + def get_pricing(currency=nil, subscription=false) + query = {:limit => 0} + if currency + query[:currency] = currency + end + if subscription + query[:level] = 0 + end + request(:path => "pricing/", + :method => 'GET', + :expects => 200, + :query => query) + end + end + + class Mock + def get_pricing(currency=nil, subscription=false) + mock_get(:pricing, 200) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/get_profile.rb b/lib/fog/cloudsigma/requests/get_profile.rb new file mode 100644 index 000000000..5b6bf67d4 --- /dev/null +++ b/lib/fog/cloudsigma/requests/get_profile.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def get_profile + get_request("profile/") + end + end + + class Mock + def get_profile + mock_get(:profile, 200) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/get_server.rb b/lib/fog/cloudsigma/requests/get_server.rb new file mode 100644 index 000000000..39b502674 --- /dev/null +++ b/lib/fog/cloudsigma/requests/get_server.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def get_server(server_id) + get_request("servers/#{server_id}/") + end + end + + class Mock + def get_server(server_id) + mock_get(:servers, 200, server_id) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/get_subscription.rb b/lib/fog/cloudsigma/requests/get_subscription.rb new file mode 100644 index 000000000..bd4ff454e --- /dev/null +++ b/lib/fog/cloudsigma/requests/get_subscription.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def get_subscription(sub_id) + get_request("subscriptions/#{sub_id}/") + end + end + + class Mock + def get_subscription(sub_id) + mock_get(:subscriptions, 200, sub_id) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/get_vlan.rb b/lib/fog/cloudsigma/requests/get_vlan.rb new file mode 100644 index 000000000..93d7e2439 --- /dev/null +++ b/lib/fog/cloudsigma/requests/get_vlan.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def get_vlan(vlan) + get_request("vlans/#{vlan}/") + end + end + + class Mock + def get_vlan(vlan) + mock_get(:vlans, 200, vlan) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/get_volume.rb b/lib/fog/cloudsigma/requests/get_volume.rb new file mode 100644 index 000000000..deeefeb4d --- /dev/null +++ b/lib/fog/cloudsigma/requests/get_volume.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def get_volume(vol_id) + get_request("drives/#{vol_id}/") + end + end + + class Mock + def get_volume(vol_id) + mock_get(:volumes, 200, vol_id) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/list_ips.rb b/lib/fog/cloudsigma/requests/list_ips.rb new file mode 100644 index 000000000..a03f119ca --- /dev/null +++ b/lib/fog/cloudsigma/requests/list_ips.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def list_ips + list_request('ips/detail/') + end + end + + class Mock + def list_ips + mock_list(:ips, 200) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/list_lib_volumes.rb b/lib/fog/cloudsigma/requests/list_lib_volumes.rb new file mode 100644 index 000000000..d782ace47 --- /dev/null +++ b/lib/fog/cloudsigma/requests/list_lib_volumes.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def list_lib_volumes + list_request('libdrives/') + end + end + + class Mock + def list_lib_volumes + mock_list(:libvolumes, 200) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/list_servers.rb b/lib/fog/cloudsigma/requests/list_servers.rb new file mode 100644 index 000000000..942ec7d0c --- /dev/null +++ b/lib/fog/cloudsigma/requests/list_servers.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def list_servers + list_request('servers/detail/') + end + end + + class Mock + def list_servers + mock_list(:servers, 200) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/list_subscriptions.rb b/lib/fog/cloudsigma/requests/list_subscriptions.rb new file mode 100644 index 000000000..ccb5355ae --- /dev/null +++ b/lib/fog/cloudsigma/requests/list_subscriptions.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def list_subscriptions + list_request('subscriptions/') + end + end + + class Mock + def list_subscriptions + mock_list(:subscriptions, 200) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/list_vlans.rb b/lib/fog/cloudsigma/requests/list_vlans.rb new file mode 100644 index 000000000..f49b7884d --- /dev/null +++ b/lib/fog/cloudsigma/requests/list_vlans.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def list_vlans + list_request('vlans/detail/') + end + end + + class Mock + def list_vlans + mock_list(:vlans, 200) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/list_volumes.rb b/lib/fog/cloudsigma/requests/list_volumes.rb new file mode 100644 index 000000000..b26998b2b --- /dev/null +++ b/lib/fog/cloudsigma/requests/list_volumes.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def list_volumes + list_request('drives/detail/') + end + end + + class Mock + def list_volumes + mock_list(:volumes, 200) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/open_vnc.rb b/lib/fog/cloudsigma/requests/open_vnc.rb new file mode 100644 index 000000000..831d9b4ae --- /dev/null +++ b/lib/fog/cloudsigma/requests/open_vnc.rb @@ -0,0 +1,34 @@ +module Fog + module Compute + class CloudSigma + class Real + def open_vnc(server_id) + request(:path => "servers/#{server_id}/action/", + :method => 'POST', + :query => {:do => :open_vnc}, + :expects => [200, 202]) + end + end + + class Mock + def open_vnc(server_id) + response = Excon::Response.new + response.status = 200 + host = @init_options[:cloudsigma_host] + port = Fog::Mock.random_number(65000) + vnc_url = "vnc://#{host}:#{port}" + + response.body = { + 'action' => 'open_vnc', + 'result' => 'success', + 'uuid' => server_id, + 'vnc_url' => vnc_url + } + + response + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/start_server.rb b/lib/fog/cloudsigma/requests/start_server.rb new file mode 100644 index 000000000..740502d76 --- /dev/null +++ b/lib/fog/cloudsigma/requests/start_server.rb @@ -0,0 +1,32 @@ +module Fog + module Compute + class CloudSigma + class Real + def start_server(server_id, start_params={}) + request(:path => "servers/#{server_id}/action/", + :method => 'POST', + :query => {:do => :start}.merge!(start_params), + :expects => [200, 202]) + end + end + + class Mock + def start_server(server_id, start_params={}) + server = self.data[:servers][server_id] + server['status'] = 'running' + + response = Excon::Response.new + response.status = 200 + response.body = { + 'action' => 'start', + 'result' => 'success', + 'uuid' => server_id + } + + response + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/stop_server.rb b/lib/fog/cloudsigma/requests/stop_server.rb new file mode 100644 index 000000000..e13f4c58b --- /dev/null +++ b/lib/fog/cloudsigma/requests/stop_server.rb @@ -0,0 +1,33 @@ +module Fog + module Compute + class CloudSigma + class Real + def stop_server(server_id) + request(:path => "servers/#{server_id}/action/", + :method => 'POST', + :query => {:do => :stop}, + :expects => [200, 202]) + end + end + + class Mock + def stop_server(server_id) + server = self.data[:servers][server_id] + server['status'] = 'stopped' + + response = Excon::Response.new + response.status = 200 + response.body = { + 'action' => 'stop', + 'result' => 'success', + 'uuid' => server_id + } + + + response + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/update_profile.rb b/lib/fog/cloudsigma/requests/update_profile.rb new file mode 100644 index 000000000..dc4fd2745 --- /dev/null +++ b/lib/fog/cloudsigma/requests/update_profile.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def update_profile(data) + update_request("profile/", data) + end + end + + class Mock + def update_profile(data) + mock_update(data, :profile, 200) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/update_server.rb b/lib/fog/cloudsigma/requests/update_server.rb new file mode 100644 index 000000000..d485f4337 --- /dev/null +++ b/lib/fog/cloudsigma/requests/update_server.rb @@ -0,0 +1,40 @@ +require 'set' + +module Fog + module Compute + class CloudSigma + class Real + def update_server(server_id, data) + update_request("servers/#{server_id}/", data) + end + end + + class Mock + def update_server(server_id, data) + mock_update(data, :servers, 200, server_id) do |old_data, new_data| + old_nics = old_data['nics'] + new_nics = new_data['nics'] + + old_nics_macs = old_nics.map { |nic| nic['mac'] }.compact + new_nics_macs = new_nics.map { |nic| nic['mac'] }.compact + + newly_created_macs = Set.new(new_nics_macs) - old_nics_macs + unless newly_created_macs.empty? + mac_err = <<-EOS + MAC(s) #{newly_created_macs.to_a} not specified on guest #{server_id}. Nic MACs are automatically assigned at + creation time and cannot be changed. Do not specify MAC to create a new NIC or specify existing MAC to + update existing NIC. + EOS + raise Fog::CloudSigma::Errors::RequestError.new(mac_err, 'permission') + end + + new_nics.each { |nic| nic['mac'] ||= Fog::Compute::CloudSigma::Mock.random_mac } + + old_data.merge(new_data) + end + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/update_vlan.rb b/lib/fog/cloudsigma/requests/update_vlan.rb new file mode 100644 index 000000000..223832fba --- /dev/null +++ b/lib/fog/cloudsigma/requests/update_vlan.rb @@ -0,0 +1,20 @@ +require 'set' + +module Fog + module Compute + class CloudSigma + class Real + def update_vlan(vlan_id, data) + update_request("vlans/#{vlan_id}/", data) + end + end + + class Mock + def update_vlan(vlan_id, data) + mock_update(data, :vlans, 200, vlan_id) + end + end + + end + end +end diff --git a/lib/fog/cloudsigma/requests/update_volume.rb b/lib/fog/cloudsigma/requests/update_volume.rb new file mode 100644 index 000000000..a1bf30c0c --- /dev/null +++ b/lib/fog/cloudsigma/requests/update_volume.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class CloudSigma + class Real + def update_volume(vol_id, data) + update_request("drives/#{vol_id}/", data) + end + end + + class Mock + def update_volume(vol_id, data) + mock_update(data, :volumes, 200, vol_id) + end + end + + end + end +end diff --git a/lib/fog/cloudstack/models/compute/servers.rb b/lib/fog/cloudstack/models/compute/servers.rb index bea87f59a..6940ed234 100644 --- a/lib/fog/cloudstack/models/compute/servers.rb +++ b/lib/fog/cloudstack/models/compute/servers.rb @@ -23,7 +23,7 @@ module Fog def get(server_id) servers = service.list_virtual_machines('id' => server_id)["listvirtualmachinesresponse"]["virtualmachine"] - unless servers.nil? + unless servers.empty? || servers.nil? new(servers.first) end rescue Fog::Compute::Cloudstack::BadRequest diff --git a/lib/fog/internet_archive/models/storage/files.rb b/lib/fog/internet_archive/models/storage/files.rb index d9b82dd63..267db71c8 100644 --- a/lib/fog/internet_archive/models/storage/files.rb +++ b/lib/fog/internet_archive/models/storage/files.rb @@ -68,7 +68,7 @@ module Fog }) new(file_data) rescue Excon::Errors::NotFound => error - case error.message + case error.response.body when /NoSuchKey<\/Code>/ nil when /NoSuchBucket<\/Code>/ diff --git a/lib/fog/joyent/compute.rb b/lib/fog/joyent/compute.rb index 7b77de609..c3138677a 100644 --- a/lib/fog/joyent/compute.rb +++ b/lib/fog/joyent/compute.rb @@ -14,6 +14,7 @@ module Fog recognizes :joyent_keyname recognizes :joyent_keyfile recognizes :joyent_keyphrase + recognizes :joyent_version model_path 'fog/joyent/models/compute' request_path 'fog/joyent/requests/compute' @@ -74,7 +75,10 @@ module Fog request :delete_machine_tag request :delete_all_machine_tags - + # Networks + collection :networks + model :network + request :list_networks class Mock def self.data diff --git a/lib/fog/joyent/models/compute/network.rb b/lib/fog/joyent/models/compute/network.rb new file mode 100644 index 000000000..5647a97fc --- /dev/null +++ b/lib/fog/joyent/models/compute/network.rb @@ -0,0 +1,12 @@ +module Fog + module Compute + class Joyent + class Network < Fog::Model + identity :id + + attribute :name + + end + end + end +end diff --git a/lib/fog/joyent/models/compute/networks.rb b/lib/fog/joyent/models/compute/networks.rb new file mode 100644 index 000000000..521bcc386 --- /dev/null +++ b/lib/fog/joyent/models/compute/networks.rb @@ -0,0 +1,15 @@ +require 'fog/joyent/models/compute/network' +module Fog + module Compute + class Joyent + class Networks < Fog::Collection + + model Fog::Compute::Joyent::Network + + def all + load(service.list_networks.body) + end + end + end + end +end diff --git a/lib/fog/joyent/requests/compute/list_networks.rb b/lib/fog/joyent/requests/compute/list_networks.rb new file mode 100644 index 000000000..97cd22681 --- /dev/null +++ b/lib/fog/joyent/requests/compute/list_networks.rb @@ -0,0 +1,26 @@ +module Fog + module Compute + class Joyent + + class Mock + def list_networks(options={}) + res = Excon::Response.new + res.status = 200 + res.body = self.data[:networks].values + res + end + end + + class Real + def list_networks(options={}) + request( + :path => "/my/networks", + :method => "GET", + :query => options, + :expects => 200 + ) + end + end + end + end +end diff --git a/lib/fog/local/models/storage/files.rb b/lib/fog/local/models/storage/files.rb index a2871e441..498190cb8 100644 --- a/lib/fog/local/models/storage/files.rb +++ b/lib/fog/local/models/storage/files.rb @@ -14,19 +14,19 @@ module Fog def all requires :directory if directory.collection.get(directory.key) - pwd = Dir.pwd - Dir.chdir(service.path_to(directory.key)) - data = Dir.glob('**/*').reject do |file| - ::File.directory?(file) - end.map do |key| - path = file_path(key) - { - :content_length => ::File.size(path), - :key => key, - :last_modified => ::File.mtime(path) - } - end - Dir.chdir(pwd) + data = [] + Dir.chdir(service.path_to(directory.key)) { + data = Dir.glob('**/*').reject do |file| + ::File.directory?(file) + end.map do |key| + path = file_path(key) + { + :content_length => ::File.size(path), + :key => key, + :last_modified => ::File.mtime(path) + } + end + } load(data) else nil diff --git a/lib/fog/openstack/models/image/images.rb b/lib/fog/openstack/models/image/images.rb index cf88f3c4a..1e8062b8a 100644 --- a/lib/fog/openstack/models/image/images.rb +++ b/lib/fog/openstack/models/image/images.rb @@ -18,6 +18,7 @@ module Fog def find_by_id(id) self.find {|image| image.id == id} end + alias_method :get, :find_by_id def public images = load(service.list_public_images_detailed.body['images']) diff --git a/lib/fog/openstack/requests/compute/list_address_pools.rb b/lib/fog/openstack/requests/compute/list_address_pools.rb index ad3b276cd..cfc75fc5c 100644 --- a/lib/fog/openstack/requests/compute/list_address_pools.rb +++ b/lib/fog/openstack/requests/compute/list_address_pools.rb @@ -16,6 +16,16 @@ module Fog class Mock + def list_address_pools + response = Excon::Response.new + response.status = 200 + response.body = { + 'floating_ip_pools' => [ + { 'name' => 'nova' } + ] + } + response + end end end diff --git a/lib/fog/rackspace/docs/storage.md b/lib/fog/rackspace/docs/storage.md index 55223e5d0..99c5069d9 100644 --- a/lib/fog/rackspace/docs/storage.md +++ b/lib/fog/rackspace/docs/storage.md @@ -278,7 +278,7 @@ This returns a `Fog::Storage::Rackspace::Directory` instance: cdn_cname=nil > -## Create Drectory +## Create Directory To create a directory: @@ -335,6 +335,8 @@ To upload a file into a directory: file = directory.files.create :key => 'space.jpg', :body => File.open "space.jpg" +**Note**: For files larger than 5 GB please refer to the [Upload Large Files](#upload_large_files) section. + ### Additional Parameters The `create` method also supports the following key values: @@ -366,6 +368,37 @@ The `create` method also supports the following key values: +## Upload Large Files + +Cloud Files requires files larger than 5 GB to be uploaded into segments along with an accompanying manifest file. All of the segments must be uploaded to the same container. + + SEGMENT_LIMIT = 5368709119.0 # 5GB -1 + BUFFER_SIZE = 1024 * 1024 # 1MB + + File.open("large_file") do |f| + num_segments = (f.size / SEGMENT_LIMIT).round + 1 + 1.upto(num_segments) do |segment| + offset = 0 + read = 0 + # upload segment to cloud files + service.put_object("my_container", "large_file/#{segment}", nil, options = {}) do + if (offset < SEGMENT_LIMIT) && (read.zero? || read == BUFFER_SIZE) + buf = f.sysread(BUFFER_SIZE) + read = buf.size + offset += read + buf + else + "" + end + end + end + end + + # write manifest file + service.put_object_manifest("my_container", "large_file") + +Segmented files are downloaded like ordinary files. See [Download Files](#download-files) section for more information. + ## Download Files The most efficient way to download files from a private or public directory is as follows: diff --git a/lib/fog/rackspace/examples/storage/upload_large_files.rb b/lib/fog/rackspace/examples/storage/upload_large_files.rb new file mode 100644 index 000000000..270d414bb --- /dev/null +++ b/lib/fog/rackspace/examples/storage/upload_large_files.rb @@ -0,0 +1,99 @@ +#!/usr/bin/env ruby + +# This example demonstrates uploading large files in segments + +require 'rubygems' #required for Ruby 1.8.x +require 'fog' + +# Size of segment. The Rackspace cloud currently requires files larger than 5GB to be segmented so we will choose 5GB -1 for a size +# http://docs.rackspace.com/files/api/v1/cf-devguide/content/Large_Object_Creation-d1e2019.html +SEGMENT_LIMIT = 5368709119.0 + +# Size of buffer to use for transfers. Use Excon's default chunk size and if that's not avaliable we will default to 1 MB +BUFFER_SIZE = Excon.defaults[:chunk_size] || 1024 * 1024 + +def get_user_input(prompt) + print "\n#{prompt}: " + gets.chomp +end + +def select_directory(directories) + abort "\nThere are not any directories in the Chicago region. Try running create_private_directory.rb\n\n" if directories.empty? + + puts "\nSelect Directory:\n\n" + directories.each_with_index do |dir, i| + puts "\t #{i}. #{dir.key} [#{dir.count} objects]" + end + + select_str = get_user_input "Enter Directory Number" + directories[select_str.to_i] +end + +# Use username defined in ~/.fog file, if absent prompt for username. +# For more details on ~/.fog refer to http://fog.io/about/getting_started.html +def rackspace_username + Fog.credentials[:rackspace_username] || get_user_input("Enter Rackspace Username") +end + +# Use api key defined in ~/.fog file, if absent prompt for api key +# For more details on ~/.fog refer to http://fog.io/about/getting_started.html +def rackspace_api_key + Fog.credentials[:rackspace_api_key] || get_user_input("Enter Rackspace API key") +end + +# create Cloud Files service +service = Fog::Storage.new({ + :provider => 'Rackspace', + :rackspace_username => rackspace_username, + :rackspace_api_key => rackspace_api_key, + :rackspace_region => :ord + }) + + +# retrieve directories with files +directories = service.directories + +# prompt for directory +directory = select_directory(directories) + +# prompt for file name +file_name = get_user_input "Enter full path of file to upload" +segment_name = File.basename(file_name) + +File.open(file_name) do |f| + num_segments = (f.size / SEGMENT_LIMIT).round + 1 + puts "\nThis upload of '#{file_name}' will require #{num_segments} segment(s) and 1 manifest file\n" + + 1.upto(num_segments) do |segment| + print "\n\tUploading segment #{segment} " + offset = 0 + read = 0 + service.put_object(directory.key, "#{segment_name}/#{segment}", nil, options = {}) do + if (offset < SEGMENT_LIMIT) && (read.zero? || read == BUFFER_SIZE) + print "." + buf = f.sysread(BUFFER_SIZE) + read = buf.size + offset += read + buf + else + "" + end + end + end +end + +puts "\n\n\tWriting manifest #{segment_name}\n\n" +service.put_object_manifest(directory.key, segment_name) + +puts <<-NOTE +You should now be able to download #{segment_name} from the cloud control panel or using the following code: + + directory = service.directories.get('#{directory.key}') + File.open('downloaded_#{segment_name}', 'w') do | f | + directory.files.get(#{segment_name}) do | data, remaining, content_length | + print "." + f.syswrite data + end + end + +NOTE \ No newline at end of file diff --git a/lib/fog/rackspace/models/load_balancers/load_balancer.rb b/lib/fog/rackspace/models/load_balancers/load_balancer.rb index c50fec505..20a883891 100644 --- a/lib/fog/rackspace/models/load_balancers/load_balancer.rb +++ b/lib/fog/rackspace/models/load_balancers/load_balancer.rb @@ -26,6 +26,7 @@ module Fog attribute :updated attribute :name attribute :state, :aliases => 'status' + attribute :timeout attribute :nodes def initialize(attributes) @@ -214,23 +215,22 @@ module Fog def create requires :name, :protocol, :port, :virtual_ips, :nodes - if algorithm - options = { :algorithm => algorithm } - else - options = {} - end + options = {} + options[:algorithm] = algorithm if algorithm + options[:timeout] = timeout if timeout data = service.create_load_balancer(name, protocol, port, virtual_ips_hash, nodes_hash, options) merge_attributes(data.body['loadBalancer']) end def update - requires :name, :protocol, :port, :algorithm + requires :name, :protocol, :port, :algorithm, :timeout options = { :name => name, :algorithm => algorithm, :protocol => protocol, - :port => port} + :port => port, + :timeout => timeout } service.update_load_balancer(identity, options) #TODO - Should this bubble down to nodes? Without tracking changes this would be very inefficient. diff --git a/lib/fog/rackspace/models/storage/file.rb b/lib/fog/rackspace/models/storage/file.rb index 1fe3e2373..87853ac40 100644 --- a/lib/fog/rackspace/models/storage/file.rb +++ b/lib/fog/rackspace/models/storage/file.rb @@ -132,6 +132,24 @@ module Fog end end + # Set last modified + # @param [String, Fog::Time] timestamp + def last_modified=(obj) + if obj.nil? || obj == "" || obj.is_a?(Time) + attributes[:last_modified] = obj + return obj + end + + # This is a work around for swift bug that has existed for 4+ years. The is that fixing the swift bug would cause more problems than its worth. + # For more information refer to https://github.com/fog/fog/pull/1811 + d = Date._strptime(obj,"%Y-%m-%dT%H:%M:%S") + if d + attributes[:last_modified] = Time.utc(d[:year], d[:mon], d[:mday], d[:hour], d[:min], d[:sec], d[:leftover], d[:zone]) + else + attributes[:last_modified] = Time.parse(obj) + end + end + # Is file published to CDN # @return [Boolean] return true if published to CDN # @raise [Fog::Storage::Rackspace::NotFound] - HTTP 404 diff --git a/lib/fog/rackspace/requests/load_balancers/update_load_balancer.rb b/lib/fog/rackspace/requests/load_balancers/update_load_balancer.rb index 9819745aa..2c1ac54d2 100644 --- a/lib/fog/rackspace/requests/load_balancers/update_load_balancer.rb +++ b/lib/fog/rackspace/requests/load_balancers/update_load_balancer.rb @@ -8,7 +8,8 @@ module Fog 'name' => options[:name], 'port' => options[:port], 'protocol' => options[:protocol], - 'algorithm' => options[:algorithm] + 'algorithm' => options[:algorithm], + 'timeout' => options[:timeout] } } request( diff --git a/lib/fog/rackspace/requests/storage/put_object.rb b/lib/fog/rackspace/requests/storage/put_object.rb index dfcc52f4a..9db310de5 100644 --- a/lib/fog/rackspace/requests/storage/put_object.rb +++ b/lib/fog/rackspace/requests/storage/put_object.rb @@ -14,19 +14,22 @@ module Fog # @raise [Fog::Storage::Rackspace::BadRequest] - HTTP 400 # @raise [Fog::Storage::Rackspace::InternalServerError] - HTTP 500 # @raise [Fog::Storage::Rackspace::ServiceError] - def put_object(container, object, data, options = {}) + def put_object(container, object, data, options = {}, &block) data = Fog::Storage.parse_data(data) headers = data[:headers].merge!(options) - request( - :body => data[:body], + + params = block_given? ? { :request_block => block } : { :body => data[:body] } + + params.merge!( :expects => 201, :idempotent => true, :headers => headers, :method => 'PUT', :path => "#{Fog::Rackspace.escape(container)}/#{Fog::Rackspace.escape(object)}" ) - end + request(params) + end end end end diff --git a/lib/fog/vsphere/requests/compute/get_folder.rb b/lib/fog/vsphere/requests/compute/get_folder.rb index 733d44806..2b5da257a 100644 --- a/lib/fog/vsphere/requests/compute/get_folder.rb +++ b/lib/fog/vsphere/requests/compute/get_folder.rb @@ -31,7 +31,7 @@ module Fog dc_root_folder = dc.vmFolder # Filter the root path for this datacenter not to be used." dc_root_folder_path=dc_root_folder.path.map { | id, name | name }.join("/") - paths = path.sub(/^\/?#{dc_root_folder_path}\/?/, '').split('/') + paths = path.sub(/^\/?#{Regex.quote(dc_root_folder_path)}\/?/, '').split('/') return dc_root_folder if paths.empty? # Walk the tree resetting the folder pointer as we go diff --git a/tests/aws/requests/compute/instance_tests.rb b/tests/aws/requests/compute/instance_tests.rb index c3c48e72b..ae477b821 100644 --- a/tests/aws/requests/compute/instance_tests.rb +++ b/tests/aws/requests/compute/instance_tests.rb @@ -3,7 +3,8 @@ Shindo.tests('Fog::Compute[:aws] | instance requests', ['aws']) do @instance_format = { 'architecture' => String, 'amiLaunchIndex' => Integer, - 'blockDeviceMapping' => [], + 'attachmentId' => Fog::Nullable::String, + 'blockDeviceMapping' => [Fog::Nullable::Hash], 'clientToken' => Fog::Nullable::String, 'dnsName' => NilClass, 'ebsOptimized' => Fog::Boolean, @@ -11,24 +12,24 @@ Shindo.tests('Fog::Compute[:aws] | instance requests', ['aws']) do 'instanceId' => String, 'instanceState' => {'code' => Integer, 'name' => String}, 'instanceType' => String, - # 'ipAddress' => String, 'kernelId' => Fog::Nullable::String, 'keyName' => Fog::Nullable::String, 'launchTime' => Time, 'monitoring' => {'state' => Fog::Boolean}, + 'networkInterfaceId' => Fog::Nullable::String, 'placement' => { 'availabilityZone' => String, - 'groupName' => Fog::Nullable::String, - 'tenancy' => String + 'groupName' => Fog::Nullable::String, + 'tenancy' => String }, 'platform' => Fog::Nullable::String, 'privateDnsName' => NilClass, - # 'privateIpAddress' => String, - 'productCodes' => [], - # 'ramdiskId' => Fog::Nullable::String, + 'productCodes' => Array, 'reason' => Fog::Nullable::String, - # 'rootDeviceName' => String, 'rootDeviceType' => String, + 'sourceDestCheck' => Fog::Nullable::Boolean, + 'subnetId' => Fog::Nullable::String, + 'vpcId' => Fog::Nullable::String } @run_instances_format = { @@ -44,16 +45,18 @@ Shindo.tests('Fog::Compute[:aws] | instance requests', ['aws']) do 'groupSet' => [String], 'groupIds' => [String], 'instancesSet' => [@instance_format.merge( - 'architecture' => String, - 'dnsName' => Fog::Nullable::String, - 'iamInstanceProfile' => {}, - 'ipAddress' => Fog::Nullable::String, - 'networkInterfaces' => [], - 'ownerId' => String, - 'privateDnsName' => Fog::Nullable::String, - 'privateIpAddress' => Fog::Nullable::String, - 'stateReason' => Hash, - 'tagSet' => Hash + 'architecture' => String, + 'dnsName' => Fog::Nullable::String, + 'hypervisor' => String, + 'iamInstanceProfile' => Hash, + 'ipAddress' => Fog::Nullable::String, + 'networkInterfaces' => Array, + 'ownerId' => String, + 'privateDnsName' => Fog::Nullable::String, + 'privateIpAddress' => Fog::Nullable::String, + 'stateReason' => Hash, + 'tagSet' => Hash, + 'virtualizationType' => String )], 'ownerId' => Fog::Nullable::String, 'reservationId' => String @@ -98,7 +101,7 @@ Shindo.tests('Fog::Compute[:aws] | instance requests', ['aws']) do 'instanceTenancy' => String, 'currencyCode' => String }], - 'requestId' => String + 'requestId' => String } @purchase_reserved_instances_offering_format = { @@ -130,43 +133,36 @@ Shindo.tests('Fog::Compute[:aws] | instance requests', ['aws']) do } @describe_instance_status_format = { - 'requestId' => String, + 'requestId' => String, 'instanceStatusSet' => [{ - 'instanceId' => String, - 'availabilityZone' => String, - 'instanceState' => { - 'code' => Integer, - 'name' => String - }, - 'systemStatus' => { - 'status' => String, - 'details' => [{ - 'name' => String, - 'status' => String - }] - }, - 'instanceStatus' => { - 'status' => String, - 'details' => [{ - 'name' => String, - 'status' => String - }] - }, - 'eventsSet' => [{ - 'code' => String, - 'description' => String, - 'notBefore' => Time, - 'notAfter' => Time - }] - }] - + 'instanceId' => String, + 'availabilityZone' => String, + 'instanceState' => { + 'code' => Integer, + 'name' => String + }, + 'systemStatus' => { + 'status' => String, + 'details' => [{ + 'name' => String, + 'status' => String + }] + }, + 'instanceStatus' => { + 'status' => String, + 'details' => [{ + 'name' => String, + 'status' => String + }] + }, + 'eventsSet' => [Fog::Nullable::Hash], + }] } - tests('success') do @instance_id = nil @ami = if ENV['FASTER_TEST_PLEASE'] - 'ami-6bbb1302' # ubuntu 12.04 daily build 20120728 + 'ami-79c0ae10' # ubuntu 12.04 daily build 20120728 else # Use a MS Windows AMI to test #get_password_data 'ami-71b50018' # Amazon Public Images - Windows_Server-2008-SP2-English-64Bit-Base-2012.07.11 @@ -191,14 +187,14 @@ Shindo.tests('Fog::Compute[:aws] | instance requests', ['aws']) do server.wait_for { ready? } tests("#describe_instances").formats(@describe_instances_format) do - Fog::Compute[:aws].describe_instances('instance-state-name' => 'running').body + Fog::Compute[:aws].describe_instances('instance-state-name' => 'running').body end - + # Launch another instance to test filters another_server = Fog::Compute[:aws].servers.create - tests("#describe_instances('instance-id' => '#{@instance_id}')").formats(@describe_instances_format) do - body = Fog::Compute[:aws].describe_instances('instance-id' => @instance_id).body + tests("#describe_instances('instance-id' => '#{@instance_id}'").formats(@describe_instances_format) do + body = Fog::Compute[:aws].describe_instances('instance-id' => "#{@instance_id}").body tests("returns 1 instance").returns(1) { body['reservationSet'].size } body end diff --git a/tests/cloudsigma/models/server_tests.rb b/tests/cloudsigma/models/server_tests.rb new file mode 100644 index 000000000..cf9150509 --- /dev/null +++ b/tests/cloudsigma/models/server_tests.rb @@ -0,0 +1,72 @@ +Shindo.tests('Fog::Compute[:cloudsigma] | server model', ['cloudsigma']) do + service = Fog::Compute[:cloudsigma] + servers = Fog::Compute[:cloudsigma].servers + server_create_args = {:name => 'fogtest', :cpu => 2000, :mem => 512*1024**2, :vnc_password => 'myrandompass'} + + model_tests(servers, server_create_args, true) do + tests('start_stop').succeeds do + @instance.start + + @instance.wait_for(timeout=60) { status == 'running' } + + @instance.stop + + @instance.wait_for(timeout=60) { status == 'stopped' } + end + + tests('attach_dhcp_nic').succeeds do + @instance.add_public_nic() + @instance.save + + @instance.reload + + returns('dhcp') { @instance.nics.first.ip_v4_conf.conf } + succeeds {/^([0-9a-f]{2}[:]){5}([0-9a-f]{2})$/ === @instance.nics.first.mac} + end + + tests('attach_vlan') do + if Fog.mocking? + # Do not buy subscription with real account + service.subscriptions.create({:period=>"1 month", :amount=>1, :resource=>"vlan"}) + vlan = service.vlans.first + vlan.meta['name'] = 'fog-test' + vlan.save + end + + vlan = service.vlans.find {|vlan| vlan.meta['name'] == 'fog-test'} + + # Skip if there is no vlan marked for fog tests + pending unless vlan + + @instance.add_private_nic(vlan) + @instance.save + + @instance.reload + + returns(vlan.uuid) { @instance.nics.last.vlan['uuid'] || @instance.nics.last.vlan} + succeeds {/^([0-9a-f]{2}[:]){5}([0-9a-f]{2})$/ === @instance.nics.last.mac} + end + + tests('attach_volume') do + volume_create_args = {:name => 'fogservermodeltest', :size => 1000**3, :media => :cdrom} + v = service.volumes.create(volume_create_args) + volume_uuid = v.uuid + + @instance.mount_volume(v) + @instance.save + @instance.reload + + returns(volume_uuid) { @instance.volumes.first.volume } + + @instance.unmount_volume(v) + @instance.save + @instance.reload + + succeeds { @instance.volumes.empty? } + + v.delete + + end + end + +end \ No newline at end of file diff --git a/tests/cloudsigma/models/servers_tests.rb b/tests/cloudsigma/models/servers_tests.rb new file mode 100644 index 000000000..f8811d92a --- /dev/null +++ b/tests/cloudsigma/models/servers_tests.rb @@ -0,0 +1,7 @@ +Shindo.tests('Fog::Compute[:cloudsigma] | servers collection', ['cloudsigma']) do + servers = Fog::Compute[:cloudsigma].servers + server_create_args = {:name => 'fogtest', :cpu => 2000, :mem => 512*1024**2, :vnc_password => 'myrandompass'} + + collection_tests(servers, server_create_args, true) + +end \ No newline at end of file diff --git a/tests/cloudsigma/models/volume_tests.rb b/tests/cloudsigma/models/volume_tests.rb new file mode 100644 index 000000000..8f970ddda --- /dev/null +++ b/tests/cloudsigma/models/volume_tests.rb @@ -0,0 +1,21 @@ +Shindo.tests('Fog::Compute[:cloudsigma] | volume model', ['cloudsigma']) do + volumes = Fog::Compute[:cloudsigma].volumes + volume_create_args = {:name => 'fogmodeltest', :size => 1000**3, :media => :cdrom} + + model_tests(volumes, volume_create_args, true) do + @instance.wait_for(timeout=60) { status == 'unmounted' } + + tests('#update').succeeds do + @instance.media = 'disk' + #@instance.size = 1024**3 # resizes disk + @instance.save + + @instance.reload + @instance.wait_for(timeout=60) { status == 'unmounted' } + + #returns(1024**3) { @instance.size } + returns('disk') { @instance.media } + end + end + +end \ No newline at end of file diff --git a/tests/cloudsigma/models/volumes_tests.rb b/tests/cloudsigma/models/volumes_tests.rb new file mode 100644 index 000000000..35903ff3b --- /dev/null +++ b/tests/cloudsigma/models/volumes_tests.rb @@ -0,0 +1,9 @@ +Shindo.tests('Fog::Compute[:cloudsigma] | volumes collection', ['cloudsigma']) do + volumes = Fog::Compute[:cloudsigma].volumes + volume_create_args = {:name => 'fogtest', :size => 1024**3, :media => :cdrom} + + collection_tests(volumes, volume_create_args, true) do + @instance.wait_for(timeout=60) { status == 'unmounted' } + end + +end \ No newline at end of file diff --git a/tests/cloudsigma/requests/server_tests.rb b/tests/cloudsigma/requests/server_tests.rb new file mode 100644 index 000000000..7b803c098 --- /dev/null +++ b/tests/cloudsigma/requests/server_tests.rb @@ -0,0 +1,81 @@ +Shindo.tests('Fog::Compute[:cloudsigma] | server requests', ['cloudsigma']) do + + @server_format = { + 'uuid' => String, + 'status' => String, + 'vnc_password' => String, + 'name' => String, + 'cpus_instead_of_cores' => Fog::Boolean, + 'tags' => Array, + 'mem' => Integer, + 'enable_numa' => Fog::Boolean, + 'smp' => Integer, + 'hv_relaxed' => Fog::Boolean, + 'hv_tsc' => Fog::Boolean, + 'meta' => Fog::Nullable::Hash, + 'owner' => Fog::Nullable::Hash, + 'runtime' => Fog::Nullable::Hash, + 'cpu' => Integer, + 'resource_uri' => Fog::Nullable::String, + 'drives' => Array, + 'nics' => Array + } + + @server_create_args = {:name => 'fogtest', :cpu => 2000, :mem => 512*1024**2, :vnc_password => 'myrandompass'} + + tests('success') do + + tests("#create_server(#@server_create_args)").formats(@server_format, false) do + server_def = Fog::Compute[:cloudsigma].create_server(@server_create_args).body['objects'].first + @server_uuid = server_def['uuid'] + + server_def + end + + tests("#get_server(#@server_uuid)").formats(@server_format, false) do + @resp_server = Fog::Compute[:cloudsigma].get_server(@server_uuid).body + end + + tests("#update_server(#@server_uuid)").formats(@server_format, false) do + @resp_server['cpu'] = 1000 + @resp_server = Fog::Compute[:cloudsigma].update_server(@server_uuid, @resp_server).body + + @resp_server + + end + + tests("#start_server(#@server_uuid)").succeeds do + response = Fog::Compute[:cloudsigma].start_server(@server_uuid) + + response.body['result'] == "success" + end + + + server = Fog::Compute[:cloudsigma].servers.get(@server_uuid) + server.wait_for { status == 'running' } + + + tests("#stop_server(#@server_uuid)").succeeds do + response = Fog::Compute[:cloudsigma].stop_server(@server_uuid) + + response.body['result'] == "success" + end + + + server = Fog::Compute[:cloudsigma].servers.get(@server_uuid) + server.wait_for { status == 'stopped' } + + + tests("#delete_server(#@server_uuid)").succeeds do + resp = Fog::Compute[:cloudsigma].delete_server(@server_uuid) + + resp.body.empty? && resp.status == 204 + end + end + + tests('failure') do + tests("#get_server(#@server_uuid)|deleted|").raises(Fog::CloudSigma::Errors::NotFound) do + Fog::Compute[:cloudsigma].get_server(@server_uuid).body + end + end +end diff --git a/tests/cloudsigma/requests/volumes_tests.rb b/tests/cloudsigma/requests/volumes_tests.rb new file mode 100644 index 000000000..c9e6df14d --- /dev/null +++ b/tests/cloudsigma/requests/volumes_tests.rb @@ -0,0 +1,55 @@ +Shindo.tests('Fog::Compute[:cloudsigma] | volume requests', ['cloudsigma']) do + + @volume_format = { + 'uuid' => String, + 'size' => Integer, + 'status' => String, + 'name' => String, + 'tags' => Array, + 'meta' => Fog::Nullable::Hash, + 'owner' => Fog::Nullable::Hash, + 'resource_uri' => Fog::Nullable::String, + 'licenses' => Array, + 'jobs' => Array, + 'affinities' => Array, + 'mounted_on' => Array, + 'media' => String, + 'allow_multimount' => Fog::Boolean + } + + @volume_create_args = {:name => 'fogtest', :size => 1024**3, :media => :cdrom} + + tests('success') do + + tests("#create_volume(#@volume_create_args)").formats(@volume_format, false) do + @resp_volume = Fog::Compute[:cloudsigma].create_volume(@volume_create_args).body['objects'].first + @volume_uuid = @resp_volume['uuid'] + + @resp_volume + end + + volume = Fog::Compute[:cloudsigma].volumes.get(@volume_uuid) + volume.wait_for { status == 'unmounted' } + + tests("#update_volume(#@volume_uuid)").formats(@volume_format, false) do + @resp_volume['media'] = 'disk' + @resp_volume = Fog::Compute[:cloudsigma].update_volume(@volume_uuid, @resp_volume).body + + @resp_volume + end + + tests("#delete_volume(#@volume_uuid)").succeeds do + resp = Fog::Compute[:cloudsigma].delete_volume(@volume_uuid) + + resp.body.empty? && resp.status == 204 + end + + end + + tests('failure') do + tests("#get_volume(#@server_uuid)|deleted|").raises(Fog::CloudSigma::Errors::NotFound) do + Fog::Compute[:cloudsigma].get_volume(@volume_uuid).body + end + end + +end \ No newline at end of file diff --git a/tests/helpers/mock_helper.rb b/tests/helpers/mock_helper.rb index 6a0582585..285f32e67 100644 --- a/tests/helpers/mock_helper.rb +++ b/tests/helpers/mock_helper.rb @@ -93,6 +93,8 @@ if Fog.mock? :vsphere_expected_pubkey_hash => 'abcdef1234567890', :libvirt_uri => 'qemu:///system', :libvirt_username => 'root', - :libvirt_password => 'password' + :libvirt_password => 'password', + :cloudsigma_username => 'csuname', + :cloudsigma_password => 'cspass' }.merge(Fog.credentials) end diff --git a/tests/joyent/requests/compute/networks_tests.rb b/tests/joyent/requests/compute/networks_tests.rb new file mode 100644 index 000000000..6d47932a8 --- /dev/null +++ b/tests/joyent/requests/compute/networks_tests.rb @@ -0,0 +1,39 @@ +Shindo.tests("Fog::Compute[:joyent] | network requests", ["joyent"]) do + @provider = Fog::Compute[:joyent] + @network_format = { + "id" => String, + "name" => String, + "public" => Fog::Boolean + } + + if Fog.mock? + @networks = Fog::Compute[:joyent].data[:networks] = { + "193d6804-256c-4e89-a4cd-46f045959993" => { + "id" => "193d6804-256c-4e89-a4cd-46f045959993", + "name" => "Joyent-SDC-Private", + "public" => false + }, + "1e7bb0e1-25a9-43b6-bb19-f79ae9540b39" => { + "id" => "1e7bb0e1-25a9-43b6-bb19-f79ae9540b39", + "name" => "Joyent-SDC-Public", + "public" => true + } + } + end + + tests("#list_networks") do + if Fog.mock? + returns(@networks.length, "correct number of networks") do + @provider.list_networks.body.length + end + end + + returns(Array, "returns an Array of networks") do + @provider.list_networks.body.class + end + + formats([@network_format]) do + @provider.list_networks.body + end + end +end diff --git a/tests/openstack/models/image/images_tests.rb b/tests/openstack/models/image/images_tests.rb index b20c8445c..2d893c872 100644 --- a/tests/openstack/models/image/images_tests.rb +++ b/tests/openstack/models/image/images_tests.rb @@ -7,6 +7,11 @@ Shindo.tests("Fog::Image[:openstack] | images", ['openstack']) do image.id == @instance['image']['id'] end + tests('#get').succeeds do + image = Fog::Image[:openstack].images.get(@instance['image']['id']) + image.id == @instance['image']['id'] + end + tests('#destroy').succeeds do Fog::Image[:openstack].images.destroy(@instance['image']['id']) end diff --git a/tests/openstack/requests/compute/address_tests.rb b/tests/openstack/requests/compute/address_tests.rb index 85031b4f8..3d02404a2 100644 --- a/tests/openstack/requests/compute/address_tests.rb +++ b/tests/openstack/requests/compute/address_tests.rb @@ -12,6 +12,10 @@ Shindo.tests('Fog::Compute[:openstack] | address requests', ['openstack']) do "pool" => String } + @address_pools_format = { + "name" => String + } + tests('success') do tests('#allocate_address').formats({"floating_ip" => @address_format}) do @@ -29,6 +33,10 @@ Shindo.tests('Fog::Compute[:openstack] | address requests', ['openstack']) do compute.get_address(@address_id).body end + tests('#list_address_pools').formats({"floating_ip_pools" => [@address_pools_format]}) do + compute.list_address_pools.body + end + compute.servers.get(@server_id).wait_for { ready? } tests('#associate_address(server_id, ip_address)').succeeds do diff --git a/tests/rackspace/models/load_balancers/load_balancer_tests.rb b/tests/rackspace/models/load_balancers/load_balancer_tests.rb index 645fb719d..3ee31266d 100644 --- a/tests/rackspace/models/load_balancers/load_balancer_tests.rb +++ b/tests/rackspace/models/load_balancers/load_balancer_tests.rb @@ -167,8 +167,10 @@ Shindo.tests('Fog::Rackspace::LoadBalancers | load_balancer', ['rackspace']) do tests('create(...with algorithm...)') do attributes = LOAD_BALANCER_ATTRIBUTES.clone attributes[:algorithm] = 'LEAST_CONNECTIONS' + attributes[:timeout] = 30 @lb = @service.load_balancers.create attributes returns('LEAST_CONNECTIONS') { @lb.algorithm } + returns(30) { @lb.timeout } @lb.wait_for { ready? } diff --git a/tests/rackspace/models/storage/file_tests.rb b/tests/rackspace/models/storage/file_tests.rb index 399330d54..2a4dfd077 100644 --- a/tests/rackspace/models/storage/file_tests.rb +++ b/tests/rackspace/models/storage/file_tests.rb @@ -1,5 +1,35 @@ +require 'fog/rackspace/models/storage/file' + Shindo.tests('Fog::Rackspace::Storage | file', ['rackspace']) do + tests("last_modified=") do + tests("no timezone") do + file = Fog::Storage::Rackspace::File.new + file.last_modified = "2013-05-09T22:20:59.287990" + returns(Fog::Time.utc(2013, 5, 9, 22, 20, 59, 287990, nil) == file.last_modified) { true } + end + tests("with timezone") do + file = Fog::Storage::Rackspace::File.new + file.last_modified = "Thu, 09 May 2015 22:20:59 GMT" + returns(Fog::Time.utc(2015, 5, 9, 22, 20, 59, 0, nil).to_i == file.last_modified.to_i) { true } + end + tests("with time") do + file = Fog::Storage::Rackspace::File.new + file.last_modified = Fog::Time.utc(2015, 5, 9, 22, 20, 59, 0, nil) + returns(Fog::Time.utc(2015, 5, 9, 22, 20, 59, 0, nil) == file.last_modified) { true } + end + tests("nil") do + file = Fog::Storage::Rackspace::File.new + file.last_modified = nil + returns(nil) { file.last_modified } + end + tests("empty string") do + file = Fog::Storage::Rackspace::File.new + file.last_modified = "" + returns("") { file.last_modified } + end + end + pending if Fog.mocking? def object_attributes(file=@instance) diff --git a/tests/rackspace/requests/load_balancers/helper.rb b/tests/rackspace/requests/load_balancers/helper.rb index ffeb54eb7..7f79f9250 100644 --- a/tests/rackspace/requests/load_balancers/helper.rb +++ b/tests/rackspace/requests/load_balancers/helper.rb @@ -132,6 +132,7 @@ LOAD_BALANCERS_DETAIL_FORMAT = { 'algorithm' => String, 'sourceAddresses' => SOURCE_ADDRESSES, 'status' => String, + 'timeout' => Integer, 'virtualIps' => [VIRTUAL_IP_FORMAT], 'nodes' => [SINGLE_NODE_FORMAT], 'created' => { 'time' => String }, @@ -147,6 +148,7 @@ LOAD_BALANCER_FORMAT = { 'algorithm' => String, 'sourceAddresses' => SOURCE_ADDRESSES, 'status' => String, + 'timeout' => Integer, 'cluster' => { 'name' => String }, 'virtualIps' => [VIRTUAL_IP_FORMAT], 'nodes' => [SINGLE_NODE_FORMAT], diff --git a/tests/rackspace/requests/load_balancers/load_balancer_tests.rb b/tests/rackspace/requests/load_balancers/load_balancer_tests.rb index d912aafca..525a3cee9 100644 --- a/tests/rackspace/requests/load_balancers/load_balancer_tests.rb +++ b/tests/rackspace/requests/load_balancers/load_balancer_tests.rb @@ -17,9 +17,10 @@ Shindo.tests('Fog::Rackspace::LoadBalancers | load_balancer_tests', ['rackspace' tests("#create_load_balancer(#{@lb_name}, 'HTTP', 80,...with algorithm)").formats(LOAD_BALANCER_FORMAT) do data = @service.create_load_balancer(@lb_name, 'HTTP', 80, [{ :type => 'PUBLIC'}], [{ :address => '1.1.1.1', :port => 80, :condition => 'ENABLED'}], - { :algorithm => 'LEAST_CONNECTIONS' }).body + { :algorithm => 'LEAST_CONNECTIONS', :timeout => 30 }).body @lb_id = data['loadBalancer']['id'] returns('LEAST_CONNECTIONS') { data['loadBalancer']['algorithm'] } + returns(30) { data['loadBalancer']['timeout'] } data end diff --git a/tests/rackspace/requests/storage/object_tests.rb b/tests/rackspace/requests/storage/object_tests.rb index cf4854c70..196b60ca3 100644 --- a/tests/rackspace/requests/storage/object_tests.rb +++ b/tests/rackspace/requests/storage/object_tests.rb @@ -17,9 +17,9 @@ Shindo.tests('Fog::Storage[:rackspace] | object requests', ["rackspace"]) do Fog::Storage[:rackspace].put_object('fogobjecttests', 'fog_object', lorem_file) end - tests("#get_object('fogobjectests', 'fog_object')").succeeds do + tests("#get_object('fogobjectests', 'fog_object')").returns(lorem_file.read) do pending if Fog.mocking? - Fog::Storage[:rackspace].get_object('fogobjecttests', 'fog_object') + Fog::Storage[:rackspace].get_object('fogobjecttests', 'fog_object').body end tests("#get_object('fogobjecttests', 'fog_object', &block)").returns(lorem_file.read) do @@ -74,6 +74,36 @@ Shindo.tests('Fog::Storage[:rackspace] | object requests', ["rackspace"]) do object_url =~ /https:\/\/.*clouddrive.com\/[^\/]+\/[^\/]+\/fogobjecttests\/fog%2Dobject\?temp_url_sig=a24dd5fc955a57adce7d1b5bc4ec2c7660ab8396&temp_url_expires=1344149532/ end + tests("put_object with block") do + tests("#put_object('fogobjecttests', 'fog_object', &block)") do + pending if Fog.mocking? + + begin + file = lorem_file + buffer_size = file.size / 2 # chop it up into two buffers + Fog::Storage[:rackspace].put_object('fogobjecttests', 'fog_block_object', nil) do + if file.pos < file.size + file.sysread(buffer_size) + else + "" + end + end + ensure + file.close + end + end + + tests("object successfully uploaded?").returns(lorem_file.read) do + pending if Fog.mocking? + Fog::Storage[:rackspace].get_object('fogobjecttests', 'fog_block_object').body + end + + tests("delete file").succeeds do + pending if Fog.mocking? + Fog::Storage[:rackspace].delete_object('fogobjecttests', 'fog_block_object') + end + end + end tests('failure') do