[cloudstack|compute] server abstraction and mocks

This commit is contained in:
Josh Lane & Jason Hansen 2012-05-08 10:01:13 -07:00 committed by Jason Hansen & Josh Lane
parent f1d435bf93
commit e470e6cc66
12 changed files with 456 additions and 46 deletions

View File

@ -17,12 +17,24 @@ module Fog
end
def self.signed_params(key,params)
query = params.to_a.sort.collect{|c| "#{c[0]}=#{escape(c[1].to_s)}"}.join('&').downcase
query = params.map{|k,v| [k.to_s, v]}.sort.collect{|c| "#{c[0]}=#{escape(c[1].to_s)}"}.join('&').downcase
signed_string = Base64.encode64(OpenSSL::HMAC.digest(@@digest,key,query)).strip
signed_string
end
def self.uuid
[8,4,4,4,12].map{|i| Fog::Mock.random_hex(i)}.join("-")
end
def self.ip_address
4.times.map{ Fog::Mock.random_numbers(3) }.join(".")
end
def self.mac_address
6.times.map{ Fog::Mock.random_numbers(2) }.join(":")
end
end
end

View File

@ -15,6 +15,11 @@ module Fog
:cloudstack_port, :cloudstack_path, :cloudstack_scheme, :cloudstack_persistent
request_path 'fog/cloudstack/requests/compute'
model_path 'fog/cloudstack/models/compute'
model :server
collection :servers
request :acquire_ip_address
request :assign_to_load_balancer_rule
@ -135,14 +140,14 @@ module Fog
class Real
def initialize(options={})
@cloudstack_api_key = options[:cloudstack_api_key]
@cloudstack_api_key = options[:cloudstack_api_key]
@cloudstack_secret_access_key = options[:cloudstack_secret_access_key]
@cloudstack_session_id = options[:cloudstack_session_id]
@cloudstack_session_key = options[:cloudstack_session_key]
@host = options[:cloudstack_host]
@path = options[:cloudstack_path] || '/client/api'
@port = options[:cloudstack_port] || 443
@scheme = options[:cloudstack_scheme] || 'https'
@cloudstack_session_id = options[:cloudstack_session_id]
@cloudstack_session_key = options[:cloudstack_session_key]
@host = options[:cloudstack_host]
@path = options[:cloudstack_path] || '/client/api'
@port = options[:cloudstack_port] || 443
@scheme = options[:cloudstack_scheme] || 'https'
@connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}#{@path}", options[:cloudstack_persistent], {:ssl_verify_peer => false})
end
@ -245,9 +250,177 @@ module Fog
raise Fog::Compute::Cloudstack::Error, error_text
end
end
end
end # Real
class Mock
def self.data
@data ||= begin
rc_options = Fog.credentials[:cloudstack] || {}
zone_id = rc_options[:zone_id] ||"c554c592-e09c-9df5-7688-4a32754a4305"
template_id = rc_options[:template_id] || "8a31cf9c-f248-0588-256e-9dbf58785216"
service_offering_id = rc_options[:service_offering_id] || "4437ac6c-9fe3-477a-57ec-60a5a45896a4"
account_id = "8bec6f15-e2b8-44fc-a8f3-a022b2873440"
user_id = Fog::Cloudstack.uuid
domain_id = Fog::Cloudstack.uuid
network_id = (Array(rc_options[:network_ids]) || [Fog::Cloudstack.uuid]).first
domain_name = "exampleorg"
domain = {
"id" => domain_id,
"name" => domain_name,
"level" => 1,
"parentdomainid" => Fog::Cloudstack.uuid,
"parentdomainname" => "ROOT",
"haschild" => false,
"path" => "ROOT/accountname"
}
{
:networks => { network_id => {
"id" => network_id,
"name" => "10.56.23.0/26",
"displaytext" => "10.56.23.0/26",
"broadcastdomaintype" => "Vlan",
"traffictype" => "Guest",
"gateway" => "10.56.23.1",
"netmask" => "255.255.255.192",
"cidr" => "10.56.23.0/26",
"zoneid" => zone_id,
"zonename" => "zone-00",
"networkofferingid" => "af0c9bd5-a1b2-4ad0-bf4b-d6fa9b1b9d5b",
"networkofferingname" => "DefaultSharedNetworkOffering",
"networkofferingdisplaytext" => "Offering for Shared networks",
"networkofferingavailability" => "Optional",
"issystem" => false,
"state" => "Setup",
"related" => "86bbc9fc-d92e-49db-9fdc-296189090017",
"broadcasturi" => "vlan://800",
"dns1" => "10.0.80.11",
"type" => "Shared",
"vlan" => "800",
"acltype" => "Domain",
"subdomainaccess" => true,
"domainid" => domain_id,
"domain" => "ROOT",
"service" => [
{"name" => "UserData"},
{"name" => "Dhcp"},
{"name" => "Dns", "capability" => [
{"name" => "AllowDnsSuffixModification",
"value" => "true",
"canchooseservicecapability" => false}]
}],
"networkdomain" => "cs1cloud.internal",
"physicalnetworkid" => "8f4627c5-1fdd-4504-8a92-f61b4e9cb3e3",
"restartrequired" => false,
"specifyipranges" => true}
},
:zones => { zone_id => {
"id" => zone_id,
"name"=> "zone-00",
"domainid" => 1,
"domainname" => "ROOT",
"networktype" => "Advanced",
"securitygroupsenabled" => false,
"allocationstate" => "Enabled",
"zonetoken" => Fog::Cloudstack.uuid,
"dhcpprovider" => "VirtualRouter"}},
:templates => { template_id => {
"id" => template_id,
"name" => "CentOS 5.6(64-bit) no GUI (XenServer)",
"displaytext" => "CentOS 5.6(64-bit) no GUI (XenServer)",
"ispublic" => true,
"created" => "2012-05-09T15:29:33-0500",
"isready" => true,
"passwordenabled" => false,
"format" => "VHD",
"isfeatured" => true,
"crossZones" => true,
"ostypeid" => "a6a6694a-18f5-4765-8418-2b7a5f37cd0f",
"ostypename" => "CentOS 5.3 (64-bit)",
"account" => "system",
"zoneid" => zone_id,
"zonename" => "zone-00",
"status" => "Download Complete",
"size" => 21474836480,
"templatetype" => "BUILTIN",
"domain" => "ROOT",
"domainid" => "6023b6fe-5bef-4358-bc76-9f4e75afa52f",
"isextractable" => true,
"checksum" => "905cec879afd9c9d22ecc8036131a180"}},
:service_offerings => { service_offering_id => {
"id" => service_offering_id,
"name" => "Medium Instance",
"displaytext" => "Medium Instance",
"cpunumber" => 1,
"cpuspeed" => 1000,
"memory" => 1024,
"created" => "2012-05-09T14:48:36-0500",
"storagetype" => "shared",
"offerha" => false,
"limitcpuuse" => false,
"issystem" => false,
"defaultuse" => false}},
:accounts => { account_id => {
"id" => account_id,
"name" => "accountname",
"accounttype" => 2,
"domainid" => domain_id,
"domain" => domain_name,
"receivedbytes" => 0,
"sentbytes" => 0,
"vmlimit" => "Unlimited",
"vmtotal" => 0,
"vmavailable" => "Unlimited",
"iplimit" => "Unlimited",
"iptotal" => 0,
"ipavailable" => "Unlimited",
"volumelimit" => "Unlimited",
"volumetotal" => 0,
"volumeavailable" => "Unlimited",
"snapshotlimit" => "Unlimited",
"snapshottotal" => 0,
"snapshotavailable" => "Unlimited",
"templatelimit" => "Unlimited",
"templatetotal" => 0,
"templateavailable" => "Unlimited",
"vmstopped" => 0,
"vmrunning" => 0,
"projectlimit" => "Unlimited",
"projecttotal" => 1,
"projectavailable" => "Unlimited",
"networklimit" => "Unlimited",
"networktotal" => 0,
"networkavailable" => "Unlimited",
"state" => "enabled",
"user" =>
[{"id" => user_id,
"username" => "username",
"firstname" => "Bob",
"lastname" => "Lastname",
"email" => "email@example.com",
"created" => "2012-05-14T16:25:17-0500",
"state" => "enabled",
"account" => "accountname",
"accounttype" => 2,
"domainid" => domain_id,
"domain" => domain_name,
"apikey" => Fog::Cloudstack.uuid,
"secretkey" => Fog::Cloudstack.uuid}]}},
:domains => { domain_id => domain },
:servers => {}
}
end
end
def self.reset
@data= nil
end
def data
self.class.data
end
end
end
end
end
end # Cloudstack
end # Compute
end # Fog

View File

@ -0,0 +1,62 @@
require 'fog/compute/models/server'
module Fog
module Compute
class Cloudstack
class Server < Fog::Compute::Server
extend Fog::Deprecation
identity :id, :aliases => 'id'
attribute :name
attribute :display_name, :aliases => 'displayname'
attribute :account
attribute :domain_id, :aliases => 'domainid'
attribute :domain
attribute :created
attribute :state
attribute :haenable
attribute :zone_id, :aliases => 'zoneid'
attribute :zone_name, :aliases => 'zonename'
attribute :template_id, :aliases => 'templateid'
attribute :template_name, :aliases => 'templatename'
attribute :templated_display_text, :aliases => 'templatedisplaytext'
attribute :password_enabled, :aliases => 'passwordenabled'
attribute :service_offering_id, :aliases => 'serviceofferingid'
attribute :service_offering_name, :aliases => 'serviceofferingname'
attribute :cpu_number, :aliases => 'cpunumber'
attribute :cpu_speed, :aliases => 'cpuspeed'
attribute :memory
attribute :cpu_used, :aliases => 'cpuused'
attribute :network_kbs_read, :aliases => 'networkkbsread'
attribute :network_kbs_write, :aliases => 'networkkbswrite'
attribute :guest_os_id, :aliases => 'guestosid'
attribute :root_device_id, :aliases => 'rootdeviceid'
attribute :root_device_type, :aliases => 'rootdevicetype'
attribute :security_group, :aliases => 'securitygroup'
attribute :nics, :aliases => 'nic'
attr_accessor :network_ids
def ready?
true
end
def save
requires :template_id, :service_offering_id, :zone_id
data = connection.deploy_virtual_machine(
:template_id => template_id,
:service_offering_id => service_offering_id,
:zone_id => zone_id,
:network_ids => network_ids
)
merge_attributes(data['deployvirtualmachineresponse'])
end
def destroy
requires :id
connection.destroy_virtual_machine(:id => id)
true
end
end # Server
end # Cloudstack
end # Compute
end # Fog

View File

@ -0,0 +1,35 @@
require 'fog/core/collection'
require 'fog/cloudstack/models/compute/server'
module Fog
module Compute
class Cloudstack
class Servers < Fog::Collection
model Fog::Compute::Cloudstack::Server
def all
data = connection.list_virtual_machines["listvirtualmachinesresponse"]["virtualmachine"] || []
load(data)
end
def bootstrap(new_attributes = {})
server = create(new_attributes)
server.wait_for { ready? }
server.setup(:password => server.password)
server
end
def get(server_id)
if server = connection.list_virtual_machines('id' => server_id)["listvirtualmachinesresponse"]["virtualmachine"].first
new(server)
end
rescue Fog::Compute::Cloudstack::BadRequest
nil
end
end
end
end
end

View File

@ -11,23 +11,125 @@ module Fog
'command' => 'deployVirtualMachine'
)
if ( securitygroupids = options.delete('securitygroupids') ).is_a?(Array)
options.merge!('securitygroupids' => securitygroupids.join(','))
security_group_ids = options.delete(:security_group_ids)
if security_group_ids
options.merge!('securitygroupids' => Array(security_group_ids).join(','))
end
if ( securitygroupnames = options.delete('securitygroupnames') ).is_a?(Array)
options.merge!('securitygroupnames' => securitygroupnames.join(','))
security_group_names = options.delete(:security_group_names)
if security_group_names
options.merge!('securitygroupnames' => Array(security_group_names).join(','))
end
if ( networkids = options.delete('networkids') ).is_a?(Array)
options.merge!('networkids' => networkids.join(','))
network_ids = options.delete(:network_ids)
if network_ids
options.merge!('networkids' => Array(network_ids).join(','))
end
options["zoneid"]= options.delete(:zone_id) if options.key?(:zone_id)
options["templateid"]= options.delete(:template_id) if options.key?(:template_id)
options["serviceofferingid"]= options.delete(:service_offering_id) if options.key?(:service_offering_id)
request(options)
end
end # Real
end
end
end
end
class Mock
def deploy_virtual_machine(options={})
zone_id = options[:zone_id]
unless zone_id
raise Fog::Compute::Cloudstack::BadRequest.new('Unable to execute API command deployvirtualmachine due to missing parameter zoneid')
end
unless zone = self.data[:zones][zone_id]
raise Fog::Compute::Cloudstack::BadRequest.new("Unable to execute API command deployvirtualmachine due to invalid value. Object networks(uuid: #{zone_id}) does not exist.")
end
zone_name = zone[:name]
template_id = options[:template_id]
unless template = self.data[:templates][options[:template_id]]
raise Fog::Compute::Cloudstack::BadRequest.new('Unable to execute API command deployvirtualmachine due to missing parameter templateid')
end
template_name = template[:name]
template_display_text = template[:display_text]
service_offering_id = options[:service_offering_id]
unless service_offering = self.data[:service_offerings][options[:service_offering_id]]
raise Fog::Compute::Cloudstack::BadRequest.new('Unable to execute API command deployvirtualmachine due to missing parameter serviceofferingid')
end
service_offering_name = service_offering[:name]
service_offering_cpu_number = service_offering[:cpunumber]
service_offering_cpu_speed = service_offering[:cpuspeed]
service_offering_memory = service_offering[:cpumemory]
identity = Fog::Cloudstack.uuid
name = options[:name] || Fog::Cloudstack.uuid
display_name = options[:display_name] || name
account_name = options[:account] || self.data[:accounts].first[1]["name"]
domain = options[:domain_id] ? self.data[:domains][options[:domain_id]] : self.data[:domains].first[1]
domain_id = domain[:id]
domain_name = domain[:name]
# how is this setup
password = nil
password_enabled = false
guest_os_id = Fog::Cloudstack.uuid
security_group_ids = options[:security_group_ids] || [] # TODO: for now
network_ids = Array(options[:network_ids]) || [self.data[:networks].first[1]["id"]]
networks = network_ids.map{|nid| self.data[:networks][nid]}
nic = networks.map do |network|
{
"id" => Fog::Cloudstack.uuid,
"networkid" => network["id"],
"netmask" => Fog::Cloudstack.ip_address,
"gateway" => network["gateway"],
"ipaddress" => Fog::Cloudstack.ip_address,
"traffictype" => "Guest", # TODO: ?
"type" => network["type"],
"isdefault" => true, # TODO: ?
"macaddress" => Fog::Cloudstack.mac_address
}
end
virtual_machine = {
"id" => identity,
"name" => name,
"displayname" => display_name,
"account" => account_name,
"domainid" => domain_id,
"domain" => domain_name,
"created" => Time.now.to_s,
"state" => "Running",
"haenable" => false,
"zoneid" => zone_id,
"zonename" => zone_name,
"templateid" => template_id,
"templatename" => template_name,
"templatedisplaytext" => template_display_text,
"passwordenabled" => false,
"serviceofferingid" => service_offering_id,
"serviceofferingname" => service_offering_name,
"cpunumber" => service_offering_cpu_number,
"cpuspeed" => service_offering_cpu_speed,
"memory" => service_offering_memory,
"cpuused" => "0%",
"networkkbsread" => 0,
"networkkbswrite" => 0,
"guestosid" => guest_os_id,
"rootdeviceid" => 0,
"rootdevicetype" => "NetworkFilesystem",
"securitygroup" => security_group_ids, # TODO: mayhaps?
"nic" => nic
}
self.data[:servers][identity]= virtual_machine
{'deployvirtualmachineresponse' => virtual_machine}
end
end # Mock
end # Cloudstack
end # Compute
end # Fog

View File

@ -14,7 +14,16 @@ module Fog
request(options)
end
end # Real
class Mock
def destroy_virtual_machine(options={})
identity = options[:id]
self.data[:servers].delete(identity)
end
end
end
end
end
end # Cloudstack
end # Compute
end # Fog

View File

@ -10,7 +10,7 @@ module Fog
options.merge!(
'command' => 'listCapabilities'
)
request(options)
end

View File

@ -10,11 +10,17 @@ module Fog
options.merge!(
'command' => 'listVirtualMachines'
)
request(options)
end
end # Real
end
end
end
end
class Mock
def list_virtual_machines(options={})
{"listvirtualmachinesresponse" =>
{"count" => self.data[:servers].values.size, "virtualmachine" => self.data[:servers].values}}
end
end # Mock
end # Cloudstack
end # Compute
end # Fog

View File

@ -4,35 +4,35 @@ Shindo.tests('Fog::Compute[:cloudstack] | virtual machine requests', ['cloudstac
'listvirtualmachinesresponse' => {
'count' => Integer,
'virtualmachine' => [
'id' => Integer,
'id' => String,
'name' => String,
'displayname' => String,
'account' => String,
'domainid' => Integer,
'domainid' => String,
'domain' => String,
'created' => String,
'state' => String,
'haenable' => Fog::Boolean,
'zoneid' => Integer,
'zoneid' => String,
'zonename' => String,
'hostid' => Fog::Nullable::String,
'hostname' => Fog::Nullable::String,
'templateid' => Integer,
'templateid' => String,
'templatename' => String,
'templatedisplaytext' => String,
'passwordenabled' => Fog::Boolean,
'serviceofferingid' => Integer,
'serviceofferingid' => String,
'serviceofferingname' => String,
'cpunumber' => Integer,
'cpuspeed' => Integer,
'networkkbsread' => Fog::Nullable::Integer,
'memory' => Integer,
'cpuused' => Fog::Nullable::String,
'guestosid' => Integer,
'guestosid' => String,
'networkkbswrite' => Fog::Nullable::Integer,
'rootdeviceid' => Integer,
'rootdevicetype' => String,
'hypervisor' => String,
'hypervisor' => Fog::Nullable::String,
'group' => Fog::Nullable::String,
'groupid' => Fog::Nullable::Integer,
'isoname' => Fog::Nullable::String,
@ -43,8 +43,8 @@ Shindo.tests('Fog::Compute[:cloudstack] | virtual machine requests', ['cloudstac
'description' => Fog::Nullable::String
],
'nic' => [
'id' => Integer,
'networkid' => Integer,
'id' => String,
'networkid' => String,
'netmask' => String,
'gateway' => String,
'ipaddress' => String,
@ -68,4 +68,4 @@ Shindo.tests('Fog::Compute[:cloudstack] | virtual machine requests', ['cloudstac
end
end
end

View File

@ -19,6 +19,14 @@ def compute_providers
},
:mocked => false
},
:openstack => { :mocked => true},
:cloudstack => {
:provider_attributes => {
:cloudstack_host => 'http://host.foo'
},
:server_attributes => Fog.credentials[:cloudstack],
:mocked => true
},
:glesys => {
:mocked => false
},

View File

@ -1,4 +1,6 @@
for provider, config in compute_providers
next if ENV['FOG_PROVIDER'] && provider.to_s != ENV['FOG_PROVIDER']
Shindo.tests("Fog::Compute[:#{provider}] | servers", [provider.to_s]) do

View File

@ -15,6 +15,7 @@ if Fog.mock?
:bluebox_customer_id => 'bluebox_customer_id',
:brightbox_client_id => 'brightbox_client_id',
:brightbox_secret => 'brightbox_secret',
:cloudstack_host => 'http://cloudstack.example.org',
:clodo_api_key => 'clodo_api_key',
:clodo_username => 'clodo_username',
:dnsimple_email => 'dnsimple_email',
@ -77,5 +78,5 @@ if Fog.mock?
:libvirt_uri => 'qemu:///system',
:libvirt_username => 'root',
:libvirt_password => 'password'
}
}.merge(Fog.credentials)
end