miasma 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/README.md +179 -0
  4. data/lib/miasma.rb +52 -0
  5. data/lib/miasma/contrib/aws.rb +390 -0
  6. data/lib/miasma/contrib/aws/auto_scale.rb +85 -0
  7. data/lib/miasma/contrib/aws/compute.rb +112 -0
  8. data/lib/miasma/contrib/aws/load_balancer.rb +185 -0
  9. data/lib/miasma/contrib/aws/orchestration.rb +338 -0
  10. data/lib/miasma/contrib/rackspace.rb +164 -0
  11. data/lib/miasma/contrib/rackspace/auto_scale.rb +84 -0
  12. data/lib/miasma/contrib/rackspace/compute.rb +104 -0
  13. data/lib/miasma/contrib/rackspace/load_balancer.rb +117 -0
  14. data/lib/miasma/contrib/rackspace/orchestration.rb +255 -0
  15. data/lib/miasma/error.rb +89 -0
  16. data/lib/miasma/models.rb +14 -0
  17. data/lib/miasma/models/auto_scale.rb +55 -0
  18. data/lib/miasma/models/auto_scale/group.rb +64 -0
  19. data/lib/miasma/models/auto_scale/groups.rb +34 -0
  20. data/lib/miasma/models/block_storage.rb +0 -0
  21. data/lib/miasma/models/compute.rb +70 -0
  22. data/lib/miasma/models/compute/server.rb +71 -0
  23. data/lib/miasma/models/compute/servers.rb +35 -0
  24. data/lib/miasma/models/dns.rb +0 -0
  25. data/lib/miasma/models/load_balancer.rb +55 -0
  26. data/lib/miasma/models/load_balancer/balancer.rb +72 -0
  27. data/lib/miasma/models/load_balancer/balancers.rb +34 -0
  28. data/lib/miasma/models/monitoring.rb +0 -0
  29. data/lib/miasma/models/orchestration.rb +127 -0
  30. data/lib/miasma/models/orchestration/event.rb +38 -0
  31. data/lib/miasma/models/orchestration/events.rb +64 -0
  32. data/lib/miasma/models/orchestration/resource.rb +79 -0
  33. data/lib/miasma/models/orchestration/resources.rb +55 -0
  34. data/lib/miasma/models/orchestration/stack.rb +144 -0
  35. data/lib/miasma/models/orchestration/stacks.rb +46 -0
  36. data/lib/miasma/models/queues.rb +0 -0
  37. data/lib/miasma/models/storage.rb +60 -0
  38. data/lib/miasma/models/storage/bucket.rb +36 -0
  39. data/lib/miasma/models/storage/buckets.rb +41 -0
  40. data/lib/miasma/models/storage/file.rb +45 -0
  41. data/lib/miasma/models/storage/files.rb +52 -0
  42. data/lib/miasma/types.rb +13 -0
  43. data/lib/miasma/types/api.rb +145 -0
  44. data/lib/miasma/types/collection.rb +116 -0
  45. data/lib/miasma/types/data.rb +53 -0
  46. data/lib/miasma/types/model.rb +118 -0
  47. data/lib/miasma/types/thin_model.rb +76 -0
  48. data/lib/miasma/utils.rb +12 -0
  49. data/lib/miasma/utils/animal_strings.rb +29 -0
  50. data/lib/miasma/utils/immutable.rb +36 -0
  51. data/lib/miasma/utils/lazy.rb +231 -0
  52. data/lib/miasma/utils/memoization.rb +55 -0
  53. data/lib/miasma/utils/smash.rb +149 -0
  54. data/lib/miasma/version.rb +4 -0
  55. data/miasma.gemspec +18 -0
  56. 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