miasma 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/README.md +179 -0
- data/lib/miasma.rb +52 -0
- data/lib/miasma/contrib/aws.rb +390 -0
- data/lib/miasma/contrib/aws/auto_scale.rb +85 -0
- data/lib/miasma/contrib/aws/compute.rb +112 -0
- data/lib/miasma/contrib/aws/load_balancer.rb +185 -0
- data/lib/miasma/contrib/aws/orchestration.rb +338 -0
- data/lib/miasma/contrib/rackspace.rb +164 -0
- data/lib/miasma/contrib/rackspace/auto_scale.rb +84 -0
- data/lib/miasma/contrib/rackspace/compute.rb +104 -0
- data/lib/miasma/contrib/rackspace/load_balancer.rb +117 -0
- data/lib/miasma/contrib/rackspace/orchestration.rb +255 -0
- data/lib/miasma/error.rb +89 -0
- data/lib/miasma/models.rb +14 -0
- data/lib/miasma/models/auto_scale.rb +55 -0
- data/lib/miasma/models/auto_scale/group.rb +64 -0
- data/lib/miasma/models/auto_scale/groups.rb +34 -0
- data/lib/miasma/models/block_storage.rb +0 -0
- data/lib/miasma/models/compute.rb +70 -0
- data/lib/miasma/models/compute/server.rb +71 -0
- data/lib/miasma/models/compute/servers.rb +35 -0
- data/lib/miasma/models/dns.rb +0 -0
- data/lib/miasma/models/load_balancer.rb +55 -0
- data/lib/miasma/models/load_balancer/balancer.rb +72 -0
- data/lib/miasma/models/load_balancer/balancers.rb +34 -0
- data/lib/miasma/models/monitoring.rb +0 -0
- data/lib/miasma/models/orchestration.rb +127 -0
- data/lib/miasma/models/orchestration/event.rb +38 -0
- data/lib/miasma/models/orchestration/events.rb +64 -0
- data/lib/miasma/models/orchestration/resource.rb +79 -0
- data/lib/miasma/models/orchestration/resources.rb +55 -0
- data/lib/miasma/models/orchestration/stack.rb +144 -0
- data/lib/miasma/models/orchestration/stacks.rb +46 -0
- data/lib/miasma/models/queues.rb +0 -0
- data/lib/miasma/models/storage.rb +60 -0
- data/lib/miasma/models/storage/bucket.rb +36 -0
- data/lib/miasma/models/storage/buckets.rb +41 -0
- data/lib/miasma/models/storage/file.rb +45 -0
- data/lib/miasma/models/storage/files.rb +52 -0
- data/lib/miasma/types.rb +13 -0
- data/lib/miasma/types/api.rb +145 -0
- data/lib/miasma/types/collection.rb +116 -0
- data/lib/miasma/types/data.rb +53 -0
- data/lib/miasma/types/model.rb +118 -0
- data/lib/miasma/types/thin_model.rb +76 -0
- data/lib/miasma/utils.rb +12 -0
- data/lib/miasma/utils/animal_strings.rb +29 -0
- data/lib/miasma/utils/immutable.rb +36 -0
- data/lib/miasma/utils/lazy.rb +231 -0
- data/lib/miasma/utils/memoization.rb +55 -0
- data/lib/miasma/utils/smash.rb +149 -0
- data/lib/miasma/version.rb +4 -0
- data/miasma.gemspec +18 -0
- metadata +57 -3
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
require 'miasma/utils/smash'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Miasma
|
6
|
+
module Contrib
|
7
|
+
|
8
|
+
# Rackspace API core helper
|
9
|
+
class RackspaceApiCore
|
10
|
+
|
11
|
+
module ApiCommon
|
12
|
+
|
13
|
+
# Set attributes into model
|
14
|
+
#
|
15
|
+
# @param klass [Class]
|
16
|
+
def self.included(klass)
|
17
|
+
klass.class_eval do
|
18
|
+
attribute :rackspace_api_key, String, :required => true
|
19
|
+
attribute :rackspace_username, String, :required => true
|
20
|
+
attribute :rackspace_region, String, :required => true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [HTTP] with auth token provided
|
25
|
+
def connection
|
26
|
+
super.with_headers('X-Auth-Token' => token)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [String] endpoint URL
|
30
|
+
def endpoint
|
31
|
+
rackspace_api.endpoint_for(
|
32
|
+
Utils.snake(self.class.to_s.split('::')[-2]).to_sym,
|
33
|
+
rackspace_region
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [String] valid API token
|
38
|
+
def token
|
39
|
+
rackspace_api.api_token
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Miasma::Contrib::RackspaceApiCore]
|
43
|
+
def rackspace_api
|
44
|
+
key = "miasma_rackspace_api_#{attributes.checksum}".to_sym
|
45
|
+
memoize(key, :direct) do
|
46
|
+
Miasma::Contrib::RackspaceApiCore.new(attributes)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Smash] Authentication endpoints
|
53
|
+
AUTH_ENDPOINT = Smash.new(
|
54
|
+
:us => 'https://identity.api.rackspacecloud.com/v2.0',
|
55
|
+
:uk => 'https://lon.identity.api.rackspacecloud.com/v2.0'
|
56
|
+
)
|
57
|
+
|
58
|
+
# @return [Smash] Mapping to external service name
|
59
|
+
# @note ["cloudFilesCDN", "cloudFiles", "cloudBlockStorage",
|
60
|
+
# "cloudImages", "cloudQueues", "cloudBigData",
|
61
|
+
# "cloudOrchestration", "cloudServersOpenStack", "autoscale",
|
62
|
+
# "cloudDatabases", "cloudBackup", "cloudMetrics",
|
63
|
+
# "cloudLoadBalancers", "cloudNetworks", "cloudFeeds",
|
64
|
+
# "cloudMonitoring", "cloudDNS"]
|
65
|
+
|
66
|
+
API_MAP = Smash.new(
|
67
|
+
'compute' => 'cloudServersOpenStack',
|
68
|
+
'orchestration' => 'cloudOrchestration',
|
69
|
+
'auto_scale' => 'autoscale',
|
70
|
+
'load_balancer' => 'cloudLoadBalancers'
|
71
|
+
)
|
72
|
+
|
73
|
+
# @return [String] username
|
74
|
+
attr_reader :user
|
75
|
+
# @return [Smash] remote service catalog
|
76
|
+
attr_reader :service_catalog
|
77
|
+
# @return [Smash] token information
|
78
|
+
attr_reader :token
|
79
|
+
# @return [Smash] credentials in use
|
80
|
+
attr_reader :credentials
|
81
|
+
|
82
|
+
# Create a new api instance
|
83
|
+
#
|
84
|
+
# @param creds [Smash] credential hash
|
85
|
+
# @return [self]
|
86
|
+
def initialize(creds)
|
87
|
+
@credentials = creds
|
88
|
+
end
|
89
|
+
|
90
|
+
# Provide end point URL for service
|
91
|
+
#
|
92
|
+
# @param api_name [String] name of api
|
93
|
+
# @param region [String] region in use
|
94
|
+
# @return [String] public URL
|
95
|
+
def endpoint_for(api_name, region)
|
96
|
+
identify_and_load unless service_catalog
|
97
|
+
api = API_MAP[api_name]
|
98
|
+
srv = service_catalog.detect do |info|
|
99
|
+
info[:name] == api
|
100
|
+
end
|
101
|
+
unless(srv)
|
102
|
+
raise NotImplementedError.new("No API mapping found for `#{api_name}`")
|
103
|
+
end
|
104
|
+
region = region.to_s.upcase
|
105
|
+
point = srv[:endpoints].detect do |endpoint|
|
106
|
+
endpoint[:region] == region
|
107
|
+
end
|
108
|
+
if(point)
|
109
|
+
point[:publicURL]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# @return [String] API token
|
114
|
+
def api_token
|
115
|
+
if(token.nil? || Time.now > token[:expires])
|
116
|
+
identify_and_load
|
117
|
+
end
|
118
|
+
token[:id]
|
119
|
+
end
|
120
|
+
|
121
|
+
# @return [String] ID of account
|
122
|
+
def account_id
|
123
|
+
if(token.nil? || Time.now > token[:expires])
|
124
|
+
identify_and_load
|
125
|
+
end
|
126
|
+
token[:tenant][:id]
|
127
|
+
end
|
128
|
+
|
129
|
+
# Identify with authentication service and load
|
130
|
+
# token information and service catalog
|
131
|
+
#
|
132
|
+
# @return [TrueClass]
|
133
|
+
def identify_and_load
|
134
|
+
endpoint = credentials[:rackspace_region].to_s == 'lon' ? AUTH_ENDPOINT[:uk] : AUTH_ENDPOINT[:us]
|
135
|
+
result = HTTP.post(File.join(endpoint, 'tokens'),
|
136
|
+
:json => {
|
137
|
+
'auth' => {
|
138
|
+
'RAX-KSKEY:apiKeyCredentials' => {
|
139
|
+
'username' => credentials[:rackspace_username],
|
140
|
+
'apiKey' => credentials[:rackspace_api_key]
|
141
|
+
}
|
142
|
+
}
|
143
|
+
}
|
144
|
+
)
|
145
|
+
unless(result.status == 200)
|
146
|
+
raise Error::ApiError::AuthenticationError.new('Failed to authenticate', :response => result)
|
147
|
+
end
|
148
|
+
info = MultiJson.load(result.body.to_s).to_smash
|
149
|
+
info = info[:access]
|
150
|
+
@user = info[:user]
|
151
|
+
@service_catalog = info[:serviceCatalog]
|
152
|
+
@token = info[:token]
|
153
|
+
token[:expires] = Time.parse(token[:expires])
|
154
|
+
true
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
Models::Compute.autoload :Rackspace, 'miasma/contrib/rackspace/compute'
|
161
|
+
Models::Orchestration.autoload :Rackspace, 'miasma/contrib/rackspace/orchestration'
|
162
|
+
Models::AutoScale.autoload :Rackspace, 'miasma/contrib/rackspace/auto_scale'
|
163
|
+
Models::LoadBalancer.autoload :Rackspace, 'miasma/contrib/rackspace/load_balancer'
|
164
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class AutoScale
|
6
|
+
class Rackspace < AutoScale
|
7
|
+
|
8
|
+
include Contrib::RackspaceApiCore::ApiCommon
|
9
|
+
|
10
|
+
# Save auto scale group
|
11
|
+
#
|
12
|
+
# @param group [Models::AutoScale::Group]
|
13
|
+
# @return [Models::AutoScale::Group]
|
14
|
+
def group_save(group)
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
# Reload the group data from the API
|
19
|
+
#
|
20
|
+
# @param group [Models::AutoScale::Group]
|
21
|
+
# @return [Models::AutoScale::Group]
|
22
|
+
def group_reload(group)
|
23
|
+
if(group.persisted?)
|
24
|
+
result = request(
|
25
|
+
:method => :get,
|
26
|
+
:path => "/groups/#{group.id}",
|
27
|
+
:expects => 200
|
28
|
+
)
|
29
|
+
grp = result.get(:body, :group)
|
30
|
+
group.load_data(
|
31
|
+
:name => grp.get('groupConfiguration', :name),
|
32
|
+
:minimum_size => grp.get('groupConfiguration', 'minEntities'),
|
33
|
+
:maximum_size => grp.get('groupConfiguration', 'maxEntities'),
|
34
|
+
:desired_size => grp.get(:state, 'desiredCapacity'),
|
35
|
+
:current_size => grp.get(:state, 'activeCapacity'),
|
36
|
+
:servers => grp.get(:state, :active).map{|s| AutoScale::Group::Server.new(self, :id => s[:id])}
|
37
|
+
).valid_state
|
38
|
+
else
|
39
|
+
group
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Delete auto scale group
|
44
|
+
#
|
45
|
+
# @param group [Models::AutoScale::Group]
|
46
|
+
# @return [TrueClass, FalseClass]
|
47
|
+
def group_destroy(group)
|
48
|
+
if(group.persisted?)
|
49
|
+
request(
|
50
|
+
:path => "/groups/#{group.id}",
|
51
|
+
:method => :delete,
|
52
|
+
:expects => 204
|
53
|
+
)
|
54
|
+
true
|
55
|
+
else
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Return all auto scale groups
|
61
|
+
#
|
62
|
+
# @param options [Hash] filter
|
63
|
+
# @return [Array<Models::AutoScale::Group>]
|
64
|
+
def group_all(options={})
|
65
|
+
result = request(
|
66
|
+
:method => :get,
|
67
|
+
:path => '/groups',
|
68
|
+
:expects => 200
|
69
|
+
)
|
70
|
+
result.fetch(:body, 'groups', []).map do |lb|
|
71
|
+
Group.new(
|
72
|
+
self,
|
73
|
+
:id => lb[:id],
|
74
|
+
:name => lb.get(:state, :name),
|
75
|
+
:current_size => lb.get(:state, 'activeCapacity'),
|
76
|
+
:desired_size => lb.get(:state, 'desiredCapacity')
|
77
|
+
).valid_state
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class Compute
|
6
|
+
class Rackspace < Compute
|
7
|
+
|
8
|
+
include Contrib::RackspaceApiCore::ApiCommon
|
9
|
+
|
10
|
+
# @return [Smash] map state to valid internal values
|
11
|
+
SERVER_STATE_MAP = Smash.new(
|
12
|
+
'ACTIVE' => :running,
|
13
|
+
'DELETED' => :terminated,
|
14
|
+
'SUSPENDED' => :stopped,
|
15
|
+
'PASSWORD' => :running
|
16
|
+
)
|
17
|
+
|
18
|
+
def server_save(server)
|
19
|
+
unless(server.persisted?)
|
20
|
+
server.load_data(server.attributes)
|
21
|
+
result = request(
|
22
|
+
:expects => 202,
|
23
|
+
:method => :post,
|
24
|
+
:path => '/servers',
|
25
|
+
:json => {
|
26
|
+
:server => {
|
27
|
+
:flavorRef => server.flavor_id,
|
28
|
+
:name => server.name,
|
29
|
+
:imageRef => server.image_id,
|
30
|
+
:metadata => server.metadata,
|
31
|
+
:personality => server.personality,
|
32
|
+
:key_pair => server.key_name
|
33
|
+
}
|
34
|
+
}
|
35
|
+
)
|
36
|
+
server.id = result.get(:body, :server, :id)
|
37
|
+
else
|
38
|
+
raise "WAT DO I DO!?"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def server_destroy(server)
|
43
|
+
if(server.persisted?)
|
44
|
+
result = request(
|
45
|
+
:expects => 204,
|
46
|
+
:method => :delete,
|
47
|
+
:path => "/servers/#{server.id}"
|
48
|
+
)
|
49
|
+
else
|
50
|
+
raise "this doesn't even exist"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def server_change_state(server, state)
|
55
|
+
end
|
56
|
+
|
57
|
+
def server_reload(server)
|
58
|
+
res = servers.reload.all
|
59
|
+
node = res.detect do |s|
|
60
|
+
s.id == server.id
|
61
|
+
end
|
62
|
+
if(node)
|
63
|
+
server.load_data(node.data.dup)
|
64
|
+
server.valid_state
|
65
|
+
else
|
66
|
+
server.data[:state] = :terminated
|
67
|
+
server.dirty.clear
|
68
|
+
server
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def server_all
|
73
|
+
result = request(
|
74
|
+
:method => :get,
|
75
|
+
:path => '/servers/detail'
|
76
|
+
)
|
77
|
+
result[:body].fetch(:servers, []).map do |srv|
|
78
|
+
Server.new(
|
79
|
+
self,
|
80
|
+
:id => srv[:id],
|
81
|
+
:name => srv[:name],
|
82
|
+
:image_id => srv.get(:image, :id),
|
83
|
+
:flavor_id => srv.get(:flavor, :id),
|
84
|
+
:state => SERVER_STATE_MAP.fetch(srv[:status], :pending),
|
85
|
+
:addresses_private => srv.fetch(:addresses, :private, []).map{|a|
|
86
|
+
Server::Address.new(
|
87
|
+
:version => a[:version].to_i, :address => a[:addr]
|
88
|
+
)
|
89
|
+
},
|
90
|
+
:addresses_public => srv.fetch(:addresses, :public, []).map{|a|
|
91
|
+
Server::Address.new(
|
92
|
+
:version => a[:version].to_i, :address => a[:addr]
|
93
|
+
)
|
94
|
+
},
|
95
|
+
:status => srv[:status],
|
96
|
+
:key_name => srv[:key_name]
|
97
|
+
).valid_state
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class LoadBalancer
|
6
|
+
class Rackspace < LoadBalancer
|
7
|
+
|
8
|
+
include Contrib::RackspaceApiCore::ApiCommon
|
9
|
+
|
10
|
+
# Save load balancer
|
11
|
+
#
|
12
|
+
# @param balancer [Models::LoadBalancer::Balancer]
|
13
|
+
# @return [Models::LoadBalancer::Balancer]
|
14
|
+
def balancer_save(balancer)
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
# Reload the balancer data from the API
|
19
|
+
#
|
20
|
+
# @param balancer [Models::LoadBalancer::Balancer]
|
21
|
+
# @return [Models::LoadBalancer::Balancer]
|
22
|
+
def balancer_reload(balancer)
|
23
|
+
if(balancer.persisted?)
|
24
|
+
result = request(
|
25
|
+
:path => "/loadbalancers/#{balancer.id}",
|
26
|
+
:method => :get,
|
27
|
+
:expects => 200
|
28
|
+
)
|
29
|
+
lb = result.get(:body, 'loadBalancer')
|
30
|
+
balancer.load_data(
|
31
|
+
:name => lb[:name],
|
32
|
+
:name => lb[:name],
|
33
|
+
:state => lb[:status] == 'ACTIVE' ? :active : :pending,
|
34
|
+
:status => lb[:status],
|
35
|
+
:created => lb.get(:created, :time),
|
36
|
+
:updated => lb.get(:updated, :time),
|
37
|
+
:public_addresses => lb['virtualIps'].map{|addr|
|
38
|
+
if(addr[:type] == 'PUBLIC')
|
39
|
+
Balancer::Address.new(
|
40
|
+
:address => addr[:address],
|
41
|
+
:version => addr['ipVersion'].sub('IPV', '').to_i
|
42
|
+
)
|
43
|
+
end
|
44
|
+
}.compact,
|
45
|
+
:private_addresses => lb['virtualIps'].map{|addr|
|
46
|
+
if(addr[:type] != 'PUBLIC')
|
47
|
+
Balancer::Address.new(
|
48
|
+
:address => addr[:address],
|
49
|
+
:version => addr['ipVersion'].sub('IPV', '').to_i
|
50
|
+
)
|
51
|
+
end
|
52
|
+
}.compact,
|
53
|
+
:servers => lb.fetch('nodes', []).map{|s|
|
54
|
+
srv = self.api_for(:compute).servers.all.detect do |csrv|
|
55
|
+
csrv.addresses.map(&:address).include?(s[:address])
|
56
|
+
end
|
57
|
+
if(srv)
|
58
|
+
Balancer::Server.new(self.api_for(:compute), :id => srv.id)
|
59
|
+
end
|
60
|
+
}.compact
|
61
|
+
).valid_state
|
62
|
+
else
|
63
|
+
balancer
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Delete load balancer
|
68
|
+
#
|
69
|
+
# @param balancer [Models::LoadBalancer::Balancer]
|
70
|
+
# @return [TrueClass, FalseClass]
|
71
|
+
def balancer_destroy(balancer)
|
72
|
+
raise NotImplementedError
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return all load balancers
|
76
|
+
#
|
77
|
+
# @param options [Hash] filter
|
78
|
+
# @return [Array<Models::LoadBalancer::Balancer>]
|
79
|
+
def balancer_all(options={})
|
80
|
+
result = request(
|
81
|
+
:path => '/loadbalancers',
|
82
|
+
:method => :get,
|
83
|
+
:expects => 200
|
84
|
+
)
|
85
|
+
result.fetch(:body, 'loadBalancers', []).map do |lb|
|
86
|
+
Balancer.new(
|
87
|
+
self,
|
88
|
+
:id => lb[:id],
|
89
|
+
:name => lb[:name],
|
90
|
+
:state => lb[:status] == 'ACTIVE' ? :active : :pending,
|
91
|
+
:status => lb[:status],
|
92
|
+
:created => lb.get(:created, :time),
|
93
|
+
:updated => lb.get(:updated, :time),
|
94
|
+
:public_addresses => lb['virtualIps'].map{|addr|
|
95
|
+
if(addr[:type] == 'PUBLIC')
|
96
|
+
Balancer::Address.new(
|
97
|
+
:address => addr[:address],
|
98
|
+
:version => addr['ipVersion'].sub('IPV', '').to_i
|
99
|
+
)
|
100
|
+
end
|
101
|
+
}.compact,
|
102
|
+
:private_addresses => lb['virtualIps'].map{|addr|
|
103
|
+
if(addr[:type] != 'PUBLIC')
|
104
|
+
Balancer::Address.new(
|
105
|
+
:address => addr[:address],
|
106
|
+
:version => addr['ipVersion'].sub('IPV', '').to_i
|
107
|
+
)
|
108
|
+
end
|
109
|
+
}.compact
|
110
|
+
).valid_state
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|