1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00

[cloudstack|compute] volumes support

This commit is contained in:
Josh Lane & Jason Hansen 2012-05-22 16:47:53 -07:00
parent 4859206f34
commit 54f1c92184
10 changed files with 494 additions and 161 deletions

View file

@ -19,14 +19,16 @@ module Fog
model_path 'fog/cloudstack/models/compute'
model :address
model :flavor
collection :flavors
model :job
collection :jobs
model :server
collection :servers
model :image
collection :images
model :flavor
collection :flavors
model :volume
collection :volumes
model :zone
collection :zones
@ -180,21 +182,21 @@ module Fog
# Decode the login response
response = Fog::JSON.decode(response.body)
user = response['loginresponse']
user.merge!('sessionid' => sessionid)
@cloudstack_session_id = user['sessionid']
@cloudstack_session_key = user['sessionkey']
user
end
def request(params)
params.reject!{|k,v| v.nil?}
params.merge!('response' => 'json')
if has_session?
params, headers = authorize_session(params)
elsif has_keys?
@ -210,20 +212,20 @@ module Fog
def has_session?
@cloudstack_session_id && @cloudstack_session_key
end
def has_keys?
@cloudstack_api_key && @cloudstack_secret_access_key
end
def authorize_session(params)
# set the session id cookie for the request
headers = {'Cookie' => "JSESSIONID=#{@cloudstack_session_id};"}
# set the sesion key for the request, params are not signed using session auth
params.merge!('sessionkey' => @cloudstack_session_key)
return params, headers
end
def authorize_api_keys(params)
headers = {}
# merge the api key into the params
@ -232,10 +234,10 @@ module Fog
signature = Fog::Cloudstack.signed_params(@cloudstack_secret_access_key,params)
# merge signature into request param
params.merge!({'signature' => signature})
return params, headers
end
def issue_request(params={},headers={},method='GET',expects=200)
begin
response = @connection.request({
@ -244,13 +246,13 @@ module Fog
:method => method,
:expects => expects
})
rescue Excon::Errors::HTTPStatusError => error
error_response = Fog::JSON.decode(error.response.body)
error_code = error_response.values.first['errorcode']
error_text = error_response.values.first['errortext']
case error_code
when 401
raise Fog::Compute::Cloudstack::Unauthorized, error_text
@ -285,140 +287,151 @@ module Fog
"haschild" => false,
"path" => "ROOT/accountname"
}
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
}
{
:users => { user_id => user },
: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",
"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",
"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}]
{
"name" => "Dns",
"capability" => [
{
"name" => "AllowDnsSuffixModification",
"value" => "true",
"canchooseservicecapability" => false
}
]
}],
"networkdomain" => "cs1cloud.internal",
"networkdomain" => "cs1cloud.internal",
"physicalnetworkid" => "8f4627c5-1fdd-4504-8a92-f61b4e9cb3e3",
"restartrequired" => false,
"specifyipranges" => true}
"restartrequired" => false,
"specifyipranges" => true}
},
:zones => { zone_id => {
"id" => zone_id,
"name"=> "zone-00",
"domainid" => 1,
"domainname" => "ROOT",
"networktype" => "Advanced",
"id" => zone_id,
"name" => "zone-00",
"domainid" => 1,
"domainname" => "ROOT",
"networktype" => "Advanced",
"securitygroupsenabled" => false,
"allocationstate" => "Enabled",
"zonetoken" => Fog::Cloudstack.uuid,
"dhcpprovider" => "VirtualRouter"}},
"allocationstate" => "Enabled",
"zonetoken" => Fog::Cloudstack.uuid,
"dhcpprovider" => "VirtualRouter"}},
:images => { image_id => {
"id" => image_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,
"id" => image_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"}},
"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"}},
:flavors => { flavor_id => {
"id" => flavor_id,
"name" => "Medium Instance",
"id" => flavor_id,
"name" => "Medium Instance",
"displaytext" => "Medium Instance",
"cpunumber" => 1,
"cpuspeed" => 1000,
"memory" => 1024,
"created" => "2012-05-09T14:48:36-0500",
"cpunumber" => 1,
"cpuspeed" => 1000,
"memory" => 1024,
"created" => "2012-05-09T14:48:36-0500",
"storagetype" => "shared",
"offerha" => false,
"offerha" => false,
"limitcpuuse" => false,
"issystem" => false,
"defaultuse" => 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,
"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,
"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}]}},
"vmstopped" => 0,
"vmrunning" => 0,
"projectlimit" => "Unlimited",
"projecttotal" => 1,
"projectavailable" => "Unlimited",
"networklimit" => "Unlimited",
"networktotal" => 0,
"networkavailable" => "Unlimited",
"state" => "enabled",
"user" => [user]}
},
:domains => { domain_id => domain },
:servers => {}
:servers => {},
:jobs => {},
:volumes => {}
}
end
end

View file

@ -7,18 +7,22 @@ module Fog
attribute :user_id, :aliases => 'userid'
attribute :account_id, :aliases => 'accountid'
attribute :cmd
attribute :job_status, :aliases => 'jobstatus'
attribute :job_status, :aliases => 'jobstatus', :type => :integer
attribute :job_result_type, :aliases => 'jobresulttype'
attribute :job_result_code, :aliases => 'jobresultcode'
attribute :job_proc_status, :aliases => 'jobprocstatus'
attribute :job_result_code, :aliases => 'jobresultcode', :type => :integer
attribute :job_proc_status, :aliases => 'jobprocstatus', :type => :integer
attribute :created_at, :aliases => 'created', :type => :time
attribute :created_at, :aliases => 'created', :type => :time
attribute :job_result, :aliases => 'jobresult'
def reload
requires :id
merge_attributes(connection.query_async_job_result('jobid' => self.id)['queryasyncjobresultresponse'])
end
def finished?
self.job_status != 0
end
end # Job
end # Cloudstack
end # Compute

View file

@ -0,0 +1,119 @@
module Fog
module Compute
class Cloudstack
class Volume < Fog::Model
identity :id, :aliases => 'id'
attribute :name, :aliases => 'name'
attribute :zone_id, :aliases => 'zoneid'
attribute :zone_name, :aliases => 'zonename'
attribute :type, :aliases => 'type'
attribute :size, :aliases => 'size'
attribute :created, :aliases => 'created'
attribute :state, :aliases => 'state'
attribute :account, :aliases => 'account'
attribute :domain_id, :aliases => 'domainid'
attribute :domain, :aliases => 'domain'
attribute :storage_type, :aliases => 'storagetype'
attribute :hypervisor, :aliases => 'hypervisor'
attribute :disk_offering_id, :aliases => 'diskofferingid'
attribute :disk_offering_name, :aliases => 'diskofferingname'
attribute :disk_offering_display_text, :aliases => 'diskofferingdisplaytext'
attribute :storage, :aliases => 'storage'
attribute :destroyed, :aliases => 'destroyed'
attribute :is_extractable, :aliases => 'isextractable', :type => :boolean
attribute :server_id, :aliases => 'virtualmachineid'
attribute :server_name, :aliases => 'vmname'
attribute :server_display_name, :aliases => 'vmdisplayname'
attr_accessor :snapshot_id, :project_id
def save
requires :name, :disk_offering_id, :zone_id
options = {
'size' => size,
'name' => name,
'diskofferingid' => disk_offering_id,
'zoneid' => zone_id,
'snapshotid' => snapshot_id,
'projectid' => project_id
}
data = connection.create_volume(options)
merge_attributes(data['createvolumeresponse'])
end
def ready?
state == 'Allocated' || state == 'Ready'
end
def flavor
connection.disk_offerings.get(self.disk_offering_id)
end
alias disk_offering flavor
def server
if server_id
connection.servers.get(server_id)
end
end
def reload
requires :identity
return unless data = begin
collection.get(identity)
rescue Excon::Errors::SocketError
nil
end
new_attributes = {
'virtualmachineid' => nil,
'vmname' => nil,
'vmdisplayname' => nil
}.merge(data.attributes)
merge_attributes(new_attributes)
self
end
def attach(instance_or_id, mountpoint=nil)
requires :id
instance_id = instance_or_id.is_a?(Server) ? instance_or_id.id : instance_or_id
unless instance_id
raise ArgumentError, "Missing required argument: instance_or_id"
end
options = {
'id' => id,
'virtualmachineid' => instance_id,
}
options.merge!('deviceid' => mountpoint) if mountpoint
data = connection.attach_volume(options)
Job.new(data["attachvolumeresponse"]).tap do |job|
job.connection= self.connection
end
end
def detach
requires :id
data = connection.detach_volume('id' => id)
Job.new(data["detachvolumeresponse"]).tap do |job|
job.connection= self.connection
end
end
def destroy
requires :id
connection.delete_volume('id' => id)
true
end
end # Volume
end # Cloudstack
end # Compute
end # Fog

View file

@ -0,0 +1,28 @@
require 'fog/core/collection'
require 'fog/cloudstack/models/compute/volume'
module Fog
module Compute
class Cloudstack
class Volumes < Fog::Collection
model Fog::Compute::Cloudstack::Volume
def all
data = connection.list_volumes["listvolumesresponse"]["volume"] || []
load(data)
end
def get(volume_id)
if volume = connection.list_volumes('id' => volume_id)["listvolumesresponse"]["volume"].first
new(volume)
end
rescue Fog::Compute::Cloudstack::BadRequest
nil
end
end
end
end
end

View file

@ -6,18 +6,65 @@ module Fog
# Attaches a disk volume to a virtual machine.
#
# {CloudStack API Reference}[http://http://download.cloud.com/releases/2.2.0/api_2.2.12/global_admin/attachVolume.html]
def attach_volume(id,virtualmachineid,deviceid=nil)
options = {
def attach_volume(options={})
options.merge!(
'command' => 'attachVolume',
'id' => id,
'virtualmachineid' => virtualmachineid,
'deviceid' => deviceid
}
)
request(options)
end
end
end
end
end
end # Real
class Mock
def attach_volume(options={})
volume_id = options['id']
server_id = options['virtualmachineid']
volume = self.data[:volumes][volume_id]
unless volume
raise Fog::Compute::Cloudstack::BadRequest.new("Unable to execute API command attachvolume due to invalid value. Object volumes(uuid: #{volume_id}) does not exist.")
end
server = self.data[:servers][server_id]
unless server
raise Fog::Compute::Cloudstack::BadRequest.new("Unable to execute API command attachvolume due to invalid value. Object vm_instance(uuid: #{server_id}) does not exist.")
end
volume['virtualmachineid']= server['id']
volume['vmname']= server['name']
volume['vmdisplayname']= server['displayname']
job_id = Fog::Cloudstack.uuid
# FIXME: need to determine current user
account_id = self.data[:accounts].first
user_id = self.data[:users].first
job = {
"accountid" => account_id,
"userid" => user_id,
"cmd" => "com.cloud.api.commands.AttachVolumeCmd",
"created" => Time.now.iso8601,
"jobid" => job_id,
"jobstatus" => 1,
"jobprocstatus" => 0,
"jobresultcode" => 0,
"jobresulttype" => "object",
"jobresult" =>
{"volume" => volume}
}
self.data[:jobs][job_id]= job
{
"attachvolumeresponse" => {
"jobid" => job_id
}
}
end
end # Mock
end # Cloudstack
end # Compute
end # Fog

View file

@ -14,6 +14,49 @@ module Fog
request(options)
end
end # Real
class Mock
def create_volume(options={})
volume_id = Fog::Cloudstack.uuid
unless volume_name = options['name']
raise Fog::Compute::Cloudstack::BadRequest.new('Unable to execute API command createvolume due to missing parameter name')
end
unless zone_id = options['zoneid']
raise Fog::Compute::Cloudstack::BadRequest.new('Unable to execute API command createvolume due to missing parameter zoneid')
end
unless disk_offering_id = options['diskofferingid']
raise Fog::Compute::Cloudstack::BadRequest.new('Unable to execute API command createvolume due to missing parameter diskofferingid')
end
volume = {
"id" => volume_id,
"name" => volume_name,
"zoneid" => zone_id,
"zonename" => "ey-wdc-00",
"type" => "DATADISK",
"size" => 5368709120,
"created" => "2012-05-22T14:52:55-0500",
"state" => "Allocated",
"account" => "accountname",
"domainid" => "6023b6fe-5bef-4358-bc76-9f4e75afa52f",
"domain" => "ROOT",
"storagetype" => "shared",
"hypervisor" => "None",
"diskofferingid" => disk_offering_id,
"diskofferingname" => "Small",
"diskofferingdisplaytext" => "Small Disk, 5 GB",
"storage" => "none",
"destroyed" => false,
"isextractable" => false
}
self.data[:volumes][volume_id]= volume
{'createvolumeresponse' => volume}
end
end
end
end

View file

@ -14,7 +14,27 @@ module Fog
request(options)
end
end
end
end
end
end # Real
class Mock
def delete_volume(options={})
volume_id = options['id']
if self.data[:volumes][volume_id]
self.data[:volumes].delete(volume_id)
{
"deletevolumeresponse" => {
"success" => "true"
}
}
else # FIXME: mayhaps
self.data[:volumes].delete(volume_id)
{
"deletevolumeresponse" => {
"success" => "false"
}
}
end
end
end # Mock
end # Cloudstack
end # Compute
end # Fog

View file

@ -6,16 +6,57 @@ module Fog
# Deletes a specified domain.
#
# {CloudStack API Reference}[http://download.cloud.com/releases/2.2.0/api_2.2.4/global_admin/detachVolume.html]
def detach_volume(id)
options = {
'command' => 'detachVolume',
'id' => id
}
def detach_volume(options={})
options.merge!(
'command' => 'detachVolume'
)
request(options)
end
end
end # Real
class Mock
def detach_volume(options={})
volume_id = options['id']
volume = self.data[:volumes][volume_id]
unless volume
raise Fog::Compute::Cloudstack::BadRequest.new("Unable to execute API command attachvolume due to invalid value. Object volumes(uuid: #{volume_id}) does not exist.")
end
volume['virtualmachineid']= volume['vmname']= volume['vmdisplayname']= nil
job_id = Fog::Cloudstack.uuid
# FIXME: need to determine current user
account_id = self.data[:accounts].first
user_id = self.data[:users].first
job = {
"accountid" => account_id,
"userid" => user_id,
"cmd" => "com.cloud.api.commands.DetachVolumeCmd",
"created" => Time.now.iso8601,
"jobid" => job_id,
"jobstatus" => 1,
"jobprocstatus" => 0,
"jobresultcode" => 0,
"jobresulttype" => "object",
"jobresult" =>
{"volume" => volume}
}
self.data[:jobs][job_id]= job
{
"detachvolumeresponse" => {
"jobid" => job_id
}
}
end
end # Mock
end
end
end

View file

@ -10,11 +10,29 @@ module Fog
options.merge!(
'command' => 'listVolumes'
)
request(options)
end
end
end
end
end
end # Real
class Mock
def list_volumes(options={})
volume_id = options.delete('id')
if volume_id
volumes = [self.data[:volumes][volume_id]]
else
volumes = self.data[:volumes].values
end
{
'listvolumesresponse' => {
'count' => volumes.size,
'volume' => volumes
}
}
end
end # Mock
end # Cloudstack
end # Compute
end #Fog

View file

@ -99,8 +99,8 @@ module Fog
end
def new(attributes = {})
unless attributes.is_a?(Hash)
raise(ArgumentError.new("Initialization parameters must be an attributes hash, got #{attributes.inspect}"))
unless attributes.is_a?(::Hash)
raise(ArgumentError.new("Initialization parameters must be an attributes hash, got #{attributes.class} #{attributes.inspect}"))
end
model.new(
attributes.merge(