miasma 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|