2012-05-14 14:10:36 -04:00
|
|
|
require 'fog/core'
|
2011-06-15 02:12:33 -04:00
|
|
|
|
|
|
|
module Fog
|
|
|
|
module HP
|
2012-08-13 13:07:39 -04:00
|
|
|
|
|
|
|
# define a specific version for the HP Provider
|
|
|
|
unless const_defined?(:VERSION)
|
2013-01-25 14:38:19 -05:00
|
|
|
VERSION = '0.0.20'
|
2012-08-13 13:07:39 -04:00
|
|
|
end
|
|
|
|
|
2011-06-15 02:12:33 -04:00
|
|
|
extend Fog::Provider
|
|
|
|
|
2012-03-20 18:48:43 -04:00
|
|
|
module Errors
|
|
|
|
class ServiceError < Fog::Errors::Error
|
|
|
|
attr_reader :response_data
|
|
|
|
|
|
|
|
def self.slurp(error)
|
|
|
|
if error.response.body.empty?
|
|
|
|
data = nil
|
|
|
|
message = nil
|
|
|
|
else
|
2013-01-22 17:28:33 -05:00
|
|
|
begin
|
|
|
|
data = Fog::JSON.decode(error.response.body)
|
|
|
|
message = data['message']
|
|
|
|
if message.nil? and !data.values.first.nil?
|
|
|
|
message = data.values.first['message']
|
|
|
|
end
|
2013-05-05 14:38:54 -04:00
|
|
|
rescue MultiJson::DecodeError
|
2013-01-22 17:28:33 -05:00
|
|
|
message = error.response.body #### body is not in JSON format, so just return as is
|
2012-10-19 01:29:22 -04:00
|
|
|
end
|
2012-03-20 18:48:43 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
new_error = super(error, message)
|
|
|
|
new_error.instance_variable_set(:@response_data, data)
|
|
|
|
new_error
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class InternalServerError < ServiceError; end
|
|
|
|
class Conflict < ServiceError; end
|
|
|
|
class NotFound < ServiceError; end
|
2012-10-19 01:29:22 -04:00
|
|
|
class Forbidden < ServiceError; end
|
2012-03-20 18:48:43 -04:00
|
|
|
class ServiceUnavailable < ServiceError; end
|
|
|
|
|
|
|
|
class BadRequest < ServiceError
|
|
|
|
attr_reader :validation_errors
|
|
|
|
|
|
|
|
def self.slurp(error)
|
|
|
|
new_error = super(error)
|
2012-12-05 13:53:11 -05:00
|
|
|
unless new_error.response_data.nil? or new_error.response_data['badRequest'].nil?
|
|
|
|
new_error.instance_variable_set(:@validation_errors, new_error.response_data['badRequest']['validationErrors'])
|
2012-03-20 18:48:43 -04:00
|
|
|
end
|
|
|
|
new_error
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-03-21 13:58:11 -04:00
|
|
|
service(:block_storage, 'hp/block_storage', 'BlockStorage')
|
2013-05-17 13:27:40 -04:00
|
|
|
service(:block_storage_v2, 'hp/block_storage_v2', 'BlockStorageV2')
|
2011-10-21 00:11:08 -04:00
|
|
|
service(:cdn, 'hp/cdn', 'CDN')
|
2011-10-17 15:25:07 -04:00
|
|
|
service(:compute, 'hp/compute', 'Compute')
|
2013-03-21 13:58:11 -04:00
|
|
|
service(:network, 'hp/network', 'Network')
|
2011-10-17 15:25:07 -04:00
|
|
|
service(:storage, 'hp/storage', 'Storage')
|
2013-03-21 13:58:11 -04:00
|
|
|
service(:lb, 'hp/lb', 'LB')
|
2011-06-15 02:12:33 -04:00
|
|
|
|
2012-01-09 17:05:51 -05:00
|
|
|
# legacy swauth 1.0/1.1 style authentication
|
|
|
|
def self.authenticate_v1(options, connection_options = {})
|
2011-10-20 22:07:42 -04:00
|
|
|
hp_auth_uri = options[:hp_auth_uri] || "https://region-a.geo-1.objects.hpcloudsvc.com/auth/v1.0/"
|
2011-07-11 10:46:24 -04:00
|
|
|
endpoint = URI.parse(hp_auth_uri)
|
|
|
|
@scheme = endpoint.scheme || "http"
|
2011-10-20 22:07:42 -04:00
|
|
|
@host = endpoint.host || "region-a.geo-1.objects.hpcloudsvc.com"
|
2011-07-11 10:46:24 -04:00
|
|
|
@port = endpoint.port.to_s || "80"
|
|
|
|
if (endpoint.path)
|
|
|
|
@auth_path = endpoint.path.slice(1, endpoint.path.length) # remove the leading slash
|
|
|
|
else
|
|
|
|
@auth_path = "auth/v1.0"
|
|
|
|
end
|
|
|
|
service_url = "#{@scheme}://#{@host}:#{@port}"
|
2012-08-13 13:07:39 -04:00
|
|
|
# Set the User-Agent
|
|
|
|
@user_agent = options[:user_agent]
|
2013-01-25 14:38:19 -05:00
|
|
|
set_user_agent_header(connection_options, "hpfog v1/#{Fog::HP::VERSION}", @user_agent)
|
2011-10-20 22:07:42 -04:00
|
|
|
connection = Fog::Connection.new(service_url, false, connection_options)
|
2013-01-22 17:28:33 -05:00
|
|
|
@hp_access_key = options[:hp_access_key]
|
2011-07-11 10:46:24 -04:00
|
|
|
@hp_secret_key = options[:hp_secret_key]
|
2011-06-15 02:12:33 -04:00
|
|
|
response = connection.request({
|
|
|
|
:expects => [200, 204],
|
|
|
|
:headers => {
|
2011-07-11 10:46:24 -04:00
|
|
|
'X-Auth-Key' => @hp_secret_key,
|
2013-01-22 17:28:33 -05:00
|
|
|
'X-Auth-User' => @hp_access_key
|
2011-06-15 02:12:33 -04:00
|
|
|
},
|
|
|
|
:method => 'GET',
|
2011-07-11 10:46:24 -04:00
|
|
|
:path => @auth_path
|
2011-06-15 02:12:33 -04:00
|
|
|
})
|
|
|
|
response.headers.reject do |key, value|
|
|
|
|
!['X-Server-Management-Url', 'X-Storage-Url', 'X-CDN-Management-Url', 'X-Auth-Token'].include?(key)
|
|
|
|
end
|
2012-01-09 17:05:51 -05:00
|
|
|
|
|
|
|
return {
|
|
|
|
:auth_token => response.headers['X-Auth-Token'],
|
2012-01-18 17:20:24 -05:00
|
|
|
:endpoint_url => nil,
|
|
|
|
:cdn_endpoint_url => response.headers['X-Storage-Url']
|
2012-01-09 17:05:51 -05:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2013-05-03 15:46:14 -04:00
|
|
|
def self.service_catalog(options, connection_options = {})
|
|
|
|
creds = authenticate_v2(options, connection_options)
|
|
|
|
return {} if creds.nil?
|
|
|
|
return {} if creds[:service_catalog].nil?
|
|
|
|
return creds[:service_catalog]
|
|
|
|
end
|
|
|
|
|
2012-01-09 17:05:51 -05:00
|
|
|
# keystone based control services style authentication
|
|
|
|
def self.authenticate_v2(options, connection_options = {})
|
2013-03-27 09:52:52 -04:00
|
|
|
unless options[:credentials].nil?
|
|
|
|
expires = true
|
|
|
|
begin
|
|
|
|
expire = DateTime.parse(options[:credentials][:expires])
|
|
|
|
expires = false if expire > DateTime.now
|
|
|
|
rescue
|
|
|
|
end
|
2013-03-28 13:57:00 -04:00
|
|
|
if expires
|
|
|
|
options = options.clone
|
|
|
|
options.delete(:credentials)
|
|
|
|
else
|
2013-03-28 15:40:20 -04:00
|
|
|
service_catalog = options[:credentials][:service_catalog]
|
2013-03-28 13:57:00 -04:00
|
|
|
type = options[:hp_service_type]
|
|
|
|
zone = options[:hp_avl_zone]
|
|
|
|
begin
|
|
|
|
creds = options[:credentials].clone
|
2013-03-28 15:40:20 -04:00
|
|
|
creds[:endpoint_url] = get_endpoint_url(service_catalog, type, zone)
|
2013-03-28 13:57:00 -04:00
|
|
|
begin
|
2013-03-28 15:40:20 -04:00
|
|
|
creds[:cdn_endpoint_url] = get_endpoint_url(service_catalog, "CDN", zone)
|
2013-03-28 13:57:00 -04:00
|
|
|
rescue
|
|
|
|
end
|
|
|
|
return creds
|
|
|
|
rescue
|
|
|
|
end
|
|
|
|
options = options.clone
|
|
|
|
options.delete(:credentials)
|
|
|
|
end
|
2013-03-27 09:52:52 -04:00
|
|
|
end
|
2012-02-09 18:43:07 -05:00
|
|
|
hp_auth_uri = options[:hp_auth_uri] || "https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0/tokens"
|
2012-02-09 17:59:07 -05:00
|
|
|
# append /tokens if missing from auth uri
|
|
|
|
@hp_auth_uri = hp_auth_uri.include?('tokens')? hp_auth_uri : hp_auth_uri + "tokens"
|
|
|
|
endpoint = URI.parse(@hp_auth_uri)
|
2012-04-11 13:00:47 -04:00
|
|
|
@scheme = endpoint.scheme || "https"
|
2012-01-09 17:05:51 -05:00
|
|
|
@host = endpoint.host || "region-a.geo-1.identity.hpcloudsvc.com"
|
2012-02-09 17:59:07 -05:00
|
|
|
@port = endpoint.port.to_s || "35357"
|
2012-01-09 17:05:51 -05:00
|
|
|
if (endpoint.path)
|
|
|
|
@auth_path = endpoint.path.slice(1, endpoint.path.length) # remove the leading slash
|
|
|
|
else
|
2012-02-09 17:59:07 -05:00
|
|
|
@auth_path = "v2.0/tokens"
|
2012-01-09 17:05:51 -05:00
|
|
|
end
|
|
|
|
service_url = "#{@scheme}://#{@host}:#{@port}"
|
2012-08-13 13:07:39 -04:00
|
|
|
# Set the User-Agent. If the caller sets a user_agent, use it.
|
|
|
|
@user_agent = options[:user_agent]
|
2013-01-25 14:38:19 -05:00
|
|
|
set_user_agent_header(connection_options, "hpfog/#{Fog::HP::VERSION}", @user_agent)
|
2012-01-09 17:05:51 -05:00
|
|
|
connection = Fog::Connection.new(service_url, false, connection_options)
|
|
|
|
|
|
|
|
### Implement HP Control Services Authentication services ###
|
|
|
|
# Get the style of auth credentials passed, defaults to access/secret key style
|
|
|
|
@hp_use_upass_auth_style = options[:hp_use_upass_auth_style] || false
|
2013-01-22 17:28:33 -05:00
|
|
|
@hp_access_key = options[:hp_access_key]
|
2012-01-09 17:05:51 -05:00
|
|
|
@hp_secret_key = options[:hp_secret_key]
|
|
|
|
@hp_tenant_id = options[:hp_tenant_id]
|
2012-01-24 16:33:46 -05:00
|
|
|
@hp_service_type = options[:hp_service_type]
|
2012-04-26 16:49:42 -04:00
|
|
|
@hp_avl_zone = options[:hp_avl_zone]
|
2012-01-09 17:05:51 -05:00
|
|
|
|
|
|
|
### Decide which auth style to use
|
|
|
|
unless (@hp_use_upass_auth_style)
|
|
|
|
# If Access Key style credentials are provided, use that
|
|
|
|
request_body = {
|
|
|
|
'auth' => {
|
|
|
|
'apiAccessKeyCredentials' => {
|
2013-01-22 17:28:33 -05:00
|
|
|
'accessKey' => "#{@hp_access_key}",
|
2012-01-09 17:05:51 -05:00
|
|
|
'secretKey' => "#{@hp_secret_key}"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
# Otherwise use the Username/Password style
|
|
|
|
request_body = {
|
|
|
|
'auth' => {
|
|
|
|
'passwordCredentials' => {
|
2013-01-22 17:28:33 -05:00
|
|
|
'username' => "#{@hp_access_key}",
|
2012-01-09 17:05:51 -05:00
|
|
|
'password' => "#{@hp_secret_key}"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
# add tenant_id if specified
|
2012-08-13 13:07:39 -04:00
|
|
|
request_body['auth']['tenantId'] = @hp_tenant_id if @hp_tenant_id
|
2012-01-09 17:05:51 -05:00
|
|
|
|
|
|
|
### Make the call to CS to get auth token and service catalog
|
|
|
|
response = connection.request(
|
|
|
|
{
|
|
|
|
:expects => 200,
|
|
|
|
:headers => {
|
|
|
|
'Content-Type' => 'application/json'
|
|
|
|
},
|
|
|
|
:method => 'POST',
|
2012-04-25 10:31:28 -04:00
|
|
|
:body => Fog::JSON.encode(request_body),
|
2012-01-09 17:05:51 -05:00
|
|
|
:path => @auth_path
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2012-04-25 10:31:28 -04:00
|
|
|
body = Fog::JSON.decode(response.body)
|
2012-01-18 17:20:24 -05:00
|
|
|
|
2012-01-09 17:05:51 -05:00
|
|
|
### fish out auth_token and endpoint for the service
|
2012-01-18 17:20:24 -05:00
|
|
|
auth_token = body['access']['token']['id']
|
2013-03-27 09:52:52 -04:00
|
|
|
expires = body['access']['token']['expires']
|
2013-03-28 15:40:20 -04:00
|
|
|
service_catalog = get_service_catalog(body['access']['serviceCatalog'])
|
|
|
|
endpoint_url = get_endpoint_url(service_catalog, @hp_service_type, @hp_avl_zone)
|
2013-03-28 13:57:00 -04:00
|
|
|
begin
|
2013-03-28 15:40:20 -04:00
|
|
|
cdn_endpoint_url = get_endpoint_url(service_catalog, "CDN", @hp_avl_zone)
|
2013-03-28 13:57:00 -04:00
|
|
|
rescue
|
2012-01-18 17:20:24 -05:00
|
|
|
end
|
|
|
|
|
2013-03-28 13:57:00 -04:00
|
|
|
creds = {
|
2012-01-18 17:20:24 -05:00
|
|
|
:auth_token => auth_token,
|
2013-03-27 09:52:52 -04:00
|
|
|
:expires => expires,
|
2013-03-28 15:40:20 -04:00
|
|
|
:service_catalog => service_catalog,
|
2012-01-18 17:20:24 -05:00
|
|
|
:endpoint_url => endpoint_url,
|
|
|
|
:cdn_endpoint_url => cdn_endpoint_url
|
|
|
|
}
|
2013-03-28 13:57:00 -04:00
|
|
|
return creds
|
2012-01-18 17:20:24 -05:00
|
|
|
end
|
|
|
|
|
2012-03-21 16:59:08 -04:00
|
|
|
# CGI.escape, but without special treatment on spaces
|
|
|
|
def self.escape(str,extra_exclude_chars = '')
|
|
|
|
str.gsub(/([^a-zA-Z0-9_.-#{extra_exclude_chars}]+)/) do
|
|
|
|
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-05-30 13:56:24 -04:00
|
|
|
# converts any attributes hash from aliased keys to original attribute keys
|
|
|
|
def self.convert_aliased_attributes_to_original(model, attributes)
|
|
|
|
original_attributes = {}
|
|
|
|
attributes.each do |k, v|
|
|
|
|
if orig_key = model.aliases.invert[k]
|
|
|
|
original_attributes[orig_key] = v
|
|
|
|
else
|
|
|
|
original_attributes[k] = v
|
|
|
|
end
|
|
|
|
end
|
|
|
|
original_attributes
|
|
|
|
end
|
|
|
|
|
2012-01-18 17:20:24 -05:00
|
|
|
private
|
|
|
|
|
2013-03-28 15:40:20 -04:00
|
|
|
def self.get_service_catalog(body)
|
|
|
|
raise "Unable to parse service catalog." unless body
|
|
|
|
service_catalog = {}
|
|
|
|
body.each do |s|
|
|
|
|
name = s["name"]
|
2013-03-28 13:57:00 -04:00
|
|
|
next if name.nil?
|
|
|
|
name = name.to_sym
|
|
|
|
next if s['endpoints'].nil?
|
2013-03-28 15:40:20 -04:00
|
|
|
service_catalog[name] = {}
|
2013-03-28 13:57:00 -04:00
|
|
|
s['endpoints'].each do |ep|
|
|
|
|
next if ep['region'].nil?
|
|
|
|
next if ep['publicURL'].nil?
|
|
|
|
next if ep['publicURL'].empty?
|
2013-03-28 15:40:20 -04:00
|
|
|
service_catalog[name][ep['region'].to_sym] = ep['publicURL']
|
2013-03-28 13:57:00 -04:00
|
|
|
end
|
2012-04-26 16:49:42 -04:00
|
|
|
end
|
2013-03-28 15:40:20 -04:00
|
|
|
return service_catalog
|
2013-03-28 13:57:00 -04:00
|
|
|
end
|
|
|
|
|
2013-03-28 15:40:20 -04:00
|
|
|
def self.get_endpoint_url(service_catalog, service_type, avl_zone)
|
2013-04-05 15:59:54 -04:00
|
|
|
return nil if service_type.nil?
|
2013-03-28 13:57:00 -04:00
|
|
|
service_type = service_type.to_sym
|
|
|
|
avl_zone = avl_zone.to_sym
|
2013-03-28 15:40:20 -04:00
|
|
|
unless service_catalog[service_type].nil?
|
|
|
|
unless service_catalog[service_type][avl_zone].nil?
|
|
|
|
return service_catalog[service_type][avl_zone]
|
2012-01-09 17:05:51 -05:00
|
|
|
end
|
|
|
|
end
|
2013-03-28 13:57:00 -04:00
|
|
|
raise "Unable to retrieve endpoint service url for availability zone '#{avl_zone}' from service catalog. "
|
2011-06-15 02:12:33 -04:00
|
|
|
end
|
|
|
|
|
2012-08-13 13:07:39 -04:00
|
|
|
def self.set_user_agent_header(conn_opts, base_str, client_str)
|
|
|
|
if client_str
|
|
|
|
user_agent = {'User-Agent' => base_str + " (#{client_str})"}
|
|
|
|
else
|
|
|
|
user_agent = {'User-Agent' => base_str}
|
|
|
|
end
|
|
|
|
if conn_opts[:headers]
|
|
|
|
conn_opts[:headers] = user_agent.merge!(conn_opts[:headers])
|
|
|
|
else
|
|
|
|
conn_opts[:headers] = user_agent
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-07-29 11:33:28 -04:00
|
|
|
class Mock
|
|
|
|
def self.etag
|
|
|
|
Fog::Mock.random_hex(32)
|
|
|
|
end
|
|
|
|
|
2011-11-23 13:17:43 -05:00
|
|
|
def self.key_fingerprint
|
|
|
|
fingerprint = []
|
|
|
|
20.times do
|
|
|
|
fingerprint << Fog::Mock.random_hex(2)
|
|
|
|
end
|
|
|
|
fingerprint.join(':')
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.key_material
|
|
|
|
private_key = OpenSSL::PKey::RSA.generate(1024)
|
|
|
|
public_key = private_key.public_key
|
|
|
|
return private_key.to_s, public_key.to_s
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.user_id
|
|
|
|
"dev_" + Fog::Mock.random_numbers(14)
|
|
|
|
end
|
2011-12-06 14:09:20 -05:00
|
|
|
|
|
|
|
def self.instance_id
|
|
|
|
Fog::Mock.random_numbers(6)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.ip_address
|
|
|
|
ip = []
|
|
|
|
4.times do
|
|
|
|
ip << Fog::Mock.random_numbers(rand(3) + 1).to_i.to_s # remove leading 0
|
|
|
|
end
|
|
|
|
ip.join('.')
|
|
|
|
end
|
|
|
|
|
2013-03-21 16:06:58 -04:00
|
|
|
def self.uuid
|
|
|
|
# pattern of 8-4-4-4-12 hexadecimal digits
|
|
|
|
uuid = []
|
|
|
|
[8,4,4,4,12].each do |x|
|
|
|
|
uuid << Fog::Mock.random_hex(x)
|
|
|
|
end
|
|
|
|
uuid.join('-')
|
|
|
|
end
|
2013-03-25 19:16:05 -04:00
|
|
|
|
|
|
|
def self.mac_address
|
|
|
|
mac_add = []
|
|
|
|
6.times do
|
|
|
|
mac_add << Fog::Mock.random_hex(2)
|
|
|
|
end
|
|
|
|
mac_add.join(':')
|
|
|
|
end
|
|
|
|
|
2011-07-29 11:33:28 -04:00
|
|
|
end
|
|
|
|
|
2011-06-15 02:12:33 -04:00
|
|
|
end
|
|
|
|
end
|