mirror of
https://github.com/fog/fog.git
synced 2022-11-09 13:51:43 -05:00
0e1daf3ddd
Unlike last attempt this replaces Fog::Connection with Fog::XML::Connection which should be directly compatible. Fog::Connection is there for old PRs but should be removed real soon. Providers using JSON should be able to replace "XML" with "Core" within their code to cut down on the dependency. If I get the time I may attempt to clean up some but testing with Mock will mean that is mostly educated guesswork.
353 lines
11 KiB
Ruby
353 lines
11 KiB
Ruby
require 'fog/core'
|
|
require 'fog/json'
|
|
require 'fog/hp/simple_http_instrumentor'
|
|
|
|
module Fog
|
|
module HP
|
|
|
|
# define a specific version for the HP Provider
|
|
unless const_defined?(:VERSION)
|
|
VERSION = '0.0.22'
|
|
end
|
|
|
|
extend Fog::Provider
|
|
|
|
module Errors
|
|
class ServiceError < Fog::Errors::Error
|
|
attr_reader :response_data
|
|
|
|
def self.slurp(error)
|
|
if error.response.body.empty?
|
|
data = nil
|
|
message = nil
|
|
else
|
|
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
|
|
rescue Fog::JSON::DecodeError
|
|
message = error.response.body #### body is not in JSON format, so just return as is
|
|
end
|
|
end
|
|
|
|
new_error = super(error, message)
|
|
new_error.instance_variable_set(:@response_data, data)
|
|
new_error
|
|
end
|
|
end
|
|
|
|
class InternalServerError < ServiceError; end
|
|
class Conflict < ServiceError; end
|
|
class NotFound < ServiceError; end
|
|
class Forbidden < ServiceError; end
|
|
class ServiceUnavailable < ServiceError; end
|
|
|
|
class BadRequest < ServiceError
|
|
attr_reader :validation_errors
|
|
|
|
def self.slurp(error)
|
|
new_error = super(error)
|
|
unless new_error.response_data.nil? or new_error.response_data['badRequest'].nil?
|
|
new_error.instance_variable_set(:@validation_errors, new_error.response_data['badRequest']['validationErrors'])
|
|
end
|
|
new_error
|
|
end
|
|
end
|
|
end
|
|
|
|
service(:block_storage, 'BlockStorage')
|
|
service(:block_storage_v2, 'BlockStorageV2')
|
|
service(:cdn, 'CDN')
|
|
service(:compute, 'Compute')
|
|
service(:dns, 'DNS')
|
|
service(:lb, 'LB')
|
|
service(:network, 'Network')
|
|
service(:storage, 'Storage')
|
|
|
|
# legacy swauth 1.0/1.1 style authentication
|
|
def self.authenticate_v1(options, connection_options = {})
|
|
hp_auth_uri = options[:hp_auth_uri] || "https://region-a.geo-1.objects.hpcloudsvc.com/auth/v1.0/"
|
|
endpoint = URI.parse(hp_auth_uri)
|
|
@scheme = endpoint.scheme || "http"
|
|
@host = endpoint.host || "region-a.geo-1.objects.hpcloudsvc.com"
|
|
@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}"
|
|
# Set the User-Agent
|
|
@user_agent = options[:user_agent]
|
|
set_user_agent_header(connection_options, "fog/#{Fog::VERSION}", @user_agent)
|
|
connection = Fog::XML::Connection.new(service_url, false, connection_options)
|
|
@hp_access_key = options[:hp_access_key]
|
|
@hp_secret_key = options[:hp_secret_key]
|
|
response = connection.request({
|
|
:expects => [200, 204],
|
|
:headers => {
|
|
'X-Auth-Key' => @hp_secret_key,
|
|
'X-Auth-User' => @hp_access_key
|
|
},
|
|
:method => 'GET',
|
|
:path => @auth_path
|
|
})
|
|
response.headers.reject do |key, value|
|
|
!['X-Server-Management-Url', 'X-Storage-Url', 'X-CDN-Management-Url', 'X-Auth-Token'].include?(key)
|
|
end
|
|
|
|
return {
|
|
:auth_token => response.headers['X-Auth-Token'],
|
|
:endpoint_url => nil,
|
|
:cdn_endpoint_url => response.headers['X-Storage-Url']
|
|
}
|
|
end
|
|
|
|
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
|
|
|
|
# keystone based control services style authentication
|
|
def self.authenticate_v2(options, connection_options = {})
|
|
unless options[:credentials].nil?
|
|
expires = true
|
|
begin
|
|
expire = DateTime.parse(options[:credentials][:expires])
|
|
expires = false if expire > DateTime.now
|
|
rescue
|
|
end
|
|
if expires
|
|
options = options.clone
|
|
options.delete(:credentials)
|
|
else
|
|
service_catalog = options[:credentials][:service_catalog]
|
|
type = options[:hp_service_type]
|
|
zone = options[:hp_avl_zone]
|
|
begin
|
|
creds = options[:credentials].clone
|
|
creds[:endpoint_url] = get_endpoint_url(service_catalog, type, zone)
|
|
begin
|
|
creds[:cdn_endpoint_url] = get_endpoint_url(service_catalog, "CDN", zone)
|
|
rescue
|
|
end
|
|
return creds
|
|
rescue
|
|
end
|
|
options = options.clone
|
|
options.delete(:credentials)
|
|
end
|
|
end
|
|
hp_auth_uri = options[:hp_auth_uri] || "https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0/tokens"
|
|
# 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)
|
|
@scheme = endpoint.scheme || "https"
|
|
@host = endpoint.host || "region-a.geo-1.identity.hpcloudsvc.com"
|
|
@port = endpoint.port.to_s || "35357"
|
|
if (endpoint.path)
|
|
@auth_path = endpoint.path.slice(1, endpoint.path.length) # remove the leading slash
|
|
else
|
|
@auth_path = "v2.0/tokens"
|
|
end
|
|
service_url = "#{@scheme}://#{@host}:#{@port}"
|
|
# Set the User-Agent. If the caller sets a user_agent, use it.
|
|
@user_agent = options[:user_agent]
|
|
set_user_agent_header(connection_options, "fog/#{Fog::VERSION}", @user_agent)
|
|
connection = Fog::XML::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
|
|
@hp_access_key = options[:hp_access_key]
|
|
@hp_secret_key = options[:hp_secret_key]
|
|
@hp_tenant_id = options[:hp_tenant_id]
|
|
@hp_service_type = options[:hp_service_type]
|
|
@hp_avl_zone = options[:hp_avl_zone]
|
|
|
|
### 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' => {
|
|
'accessKey' => "#{@hp_access_key}",
|
|
'secretKey' => "#{@hp_secret_key}"
|
|
}
|
|
}
|
|
}
|
|
else
|
|
# Otherwise use the Username/Password style
|
|
request_body = {
|
|
'auth' => {
|
|
'passwordCredentials' => {
|
|
'username' => "#{@hp_access_key}",
|
|
'password' => "#{@hp_secret_key}"
|
|
}
|
|
}
|
|
}
|
|
end
|
|
# add tenant_id if specified
|
|
request_body['auth']['tenantId'] = @hp_tenant_id if @hp_tenant_id
|
|
|
|
### Make the call to CS to get auth token and service catalog
|
|
response = connection.request(
|
|
{
|
|
:expects => 200,
|
|
:headers => {
|
|
'Content-Type' => 'application/json'
|
|
},
|
|
:method => 'POST',
|
|
:body => Fog::JSON.encode(request_body),
|
|
:path => @auth_path
|
|
}
|
|
)
|
|
|
|
body = Fog::JSON.decode(response.body)
|
|
|
|
### fish out auth_token and endpoint for the service
|
|
auth_token = body['access']['token']['id']
|
|
expires = body['access']['token']['expires']
|
|
service_catalog = get_service_catalog(body['access']['serviceCatalog'])
|
|
endpoint_url = get_endpoint_url(service_catalog, @hp_service_type, @hp_avl_zone)
|
|
begin
|
|
cdn_endpoint_url = get_endpoint_url(service_catalog, "CDN", @hp_avl_zone)
|
|
rescue
|
|
end
|
|
|
|
creds = {
|
|
:auth_token => auth_token,
|
|
:expires => expires,
|
|
:service_catalog => service_catalog,
|
|
:endpoint_url => endpoint_url,
|
|
:cdn_endpoint_url => cdn_endpoint_url
|
|
}
|
|
return creds
|
|
end
|
|
|
|
# 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
|
|
|
|
# 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
|
|
|
|
private
|
|
|
|
def self.get_service_catalog(body)
|
|
raise "Unable to parse service catalog." unless body
|
|
service_catalog = {}
|
|
body.each do |s|
|
|
name = s["name"]
|
|
next if name.nil?
|
|
name = name.to_sym
|
|
next if s['endpoints'].nil?
|
|
service_catalog[name] = {}
|
|
s['endpoints'].each do |ep|
|
|
next if ep['region'].nil?
|
|
next if ep['publicURL'].nil?
|
|
next if ep['publicURL'].empty?
|
|
service_catalog[name][ep['region'].to_sym] = ep['publicURL']
|
|
end
|
|
end
|
|
return service_catalog
|
|
end
|
|
|
|
def self.get_endpoint_url(service_catalog, service_type, avl_zone)
|
|
return nil if service_type.nil?
|
|
service_type = service_type.to_sym
|
|
avl_zone = avl_zone.to_sym
|
|
unless service_catalog[service_type].nil?
|
|
unless service_catalog[service_type][avl_zone].nil?
|
|
return service_catalog[service_type][avl_zone]
|
|
end
|
|
end
|
|
raise "Unable to retrieve endpoint service url for availability zone '#{avl_zone}' from service catalog. "
|
|
end
|
|
|
|
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
|
|
|
|
class Mock
|
|
def self.etag
|
|
Fog::Mock.random_hex(32)
|
|
end
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
def self.mac_address
|
|
mac_add = []
|
|
6.times do
|
|
mac_add << Fog::Mock.random_hex(2)
|
|
end
|
|
mac_add.join(':')
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
end
|