ironfan 3.2.2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/VERSION +1 -1
  3. data/ironfan.gemspec +33 -20
  4. data/lib/chef/knife/cluster_kick.rb +17 -17
  5. data/lib/chef/knife/cluster_kill.rb +13 -7
  6. data/lib/chef/knife/cluster_launch.rb +60 -66
  7. data/lib/chef/knife/cluster_pry.rb +2 -2
  8. data/lib/chef/knife/cluster_show.rb +3 -6
  9. data/lib/chef/knife/cluster_ssh.rb +5 -11
  10. data/lib/chef/knife/cluster_start.rb +2 -4
  11. data/lib/chef/knife/cluster_stop.rb +1 -3
  12. data/lib/chef/knife/cluster_sync.rb +13 -21
  13. data/lib/chef/knife/ironfan_knife_common.rb +11 -9
  14. data/lib/chef/knife/ironfan_script.rb +2 -1
  15. data/lib/gorillib/resolution.rb +119 -0
  16. data/lib/ironfan/broker/computer.rb +316 -0
  17. data/lib/ironfan/broker/drive.rb +21 -0
  18. data/lib/ironfan/broker.rb +37 -0
  19. data/lib/ironfan/builder.rb +14 -0
  20. data/lib/ironfan/deprecated.rb +16 -58
  21. data/lib/ironfan/dsl/cloud.rb +21 -0
  22. data/lib/ironfan/dsl/cluster.rb +27 -0
  23. data/lib/ironfan/dsl/compute.rb +84 -0
  24. data/lib/ironfan/dsl/ec2.rb +260 -0
  25. data/lib/ironfan/dsl/facet.rb +25 -0
  26. data/lib/ironfan/dsl/role.rb +19 -0
  27. data/lib/ironfan/dsl/server.rb +31 -0
  28. data/lib/ironfan/dsl/virtualbox.rb +8 -0
  29. data/lib/ironfan/dsl/volume.rb +45 -0
  30. data/lib/ironfan/dsl.rb +7 -0
  31. data/lib/ironfan/headers.rb +58 -0
  32. data/lib/ironfan/provider/chef/client.rb +77 -0
  33. data/lib/ironfan/provider/chef/node.rb +133 -0
  34. data/lib/ironfan/provider/chef/role.rb +69 -0
  35. data/lib/ironfan/provider/chef.rb +28 -0
  36. data/lib/ironfan/provider/ec2/ebs_volume.rb +137 -0
  37. data/lib/ironfan/provider/ec2/elastic_ip.rb +10 -0
  38. data/lib/ironfan/provider/ec2/key_pair.rb +65 -0
  39. data/lib/ironfan/provider/ec2/machine.rb +258 -0
  40. data/lib/ironfan/provider/ec2/placement_group.rb +24 -0
  41. data/lib/ironfan/provider/ec2/security_group.rb +118 -0
  42. data/lib/ironfan/provider/ec2.rb +47 -0
  43. data/lib/ironfan/provider/virtualbox/machine.rb +10 -0
  44. data/lib/ironfan/provider/virtualbox.rb +8 -0
  45. data/lib/ironfan/provider.rb +139 -0
  46. data/lib/ironfan/requirements.rb +52 -0
  47. data/lib/ironfan.rb +44 -33
  48. metadata +34 -21
  49. data/lib/chef/knife/cluster_vagrant.rb +0 -144
  50. data/lib/chef/knife/vagrant/ironfan_environment.rb +0 -18
  51. data/lib/chef/knife/vagrant/ironfan_provisioners.rb +0 -27
  52. data/lib/chef/knife/vagrant/skeleton_vagrantfile.rb +0 -119
  53. data/lib/ironfan/chef_layer.rb +0 -300
  54. data/lib/ironfan/cloud.rb +0 -323
  55. data/lib/ironfan/cluster.rb +0 -118
  56. data/lib/ironfan/compute.rb +0 -139
  57. data/lib/ironfan/discovery.rb +0 -190
  58. data/lib/ironfan/dsl_builder.rb +0 -99
  59. data/lib/ironfan/facet.rb +0 -143
  60. data/lib/ironfan/fog_layer.rb +0 -196
  61. data/lib/ironfan/private_key.rb +0 -130
  62. data/lib/ironfan/role_implications.rb +0 -58
  63. data/lib/ironfan/security_group.rb +0 -133
  64. data/lib/ironfan/server.rb +0 -291
  65. data/lib/ironfan/server_slice.rb +0 -265
  66. data/lib/ironfan/volume.rb +0 -146
@@ -0,0 +1,119 @@
1
+ require 'gorillib/model/serialization'
2
+ require 'gorillib/serialization/to_wire'
3
+ require 'gorillib/hash/deep_merge'
4
+
5
+ module Gorillib
6
+
7
+ # Make a clean deep-copy of the value, via gorillib semantics if
8
+ # possible, otherwise via marshalling
9
+ def self.deep_copy(value)
10
+ case
11
+ when ( value.respond_to? :to_wire and value.respond_to? :receive )
12
+ return value.class.receive(value.to_wire)
13
+ else
14
+ return Marshal.load(Marshal.dump(value))
15
+ end
16
+ end
17
+
18
+ module Model
19
+ Field.class_eval do
20
+ field :resolver, Symbol, :default => :read_set_or_underlay_attribute
21
+ end
22
+ end
23
+
24
+ # The attribute :underlay provides an object (preferably another
25
+ # Gorillib::Model or the like) that will resolve stacked
26
+ # defaults. If fields are declared with a :resolver, it will
27
+ # apply that call in preference the default rules (self.field
28
+ # -> underlay.field -> self.field.default )
29
+ #
30
+ # To provide resolve cleanly without read-write loops destroying
31
+ # the separation of concerns, the resolve mechanism has been
32
+ # broken from the regular read-write accessors.
33
+ #
34
+ module Resolution
35
+ extend Gorillib::Concern
36
+ include Gorillib::FancyBuilder
37
+ attr_accessor :underlay
38
+
39
+ # Return a fully-resolved copy of this object. All objects
40
+ # referenced will be clean deep_copies, and will lack the
41
+ # :underlay accessor. This is by design, to prevent self-
42
+ # referential loops (parent->collection->child->owner)
43
+ # when deep_coping.
44
+ def resolve
45
+ result = self.class.new
46
+ self.class.fields.each do |field_name, field|
47
+ value = read_resolved_attribute(field_name)
48
+ result.write_attribute(field_name, value) unless value.nil?
49
+ end
50
+ result
51
+ end
52
+
53
+ def deep_resolve(field_name)
54
+ temp = read_set_or_underlay_attribute(field_name)
55
+ return if temp.nil?
56
+ if temp.is_a? Gorillib::Collection
57
+ result = temp.class.new
58
+ temp.each_pair {|k,v| result[k] = resolve_value(v) }
59
+ else
60
+ result = resolve_value(v)
61
+ end
62
+ result
63
+ end
64
+
65
+ def resolve_value(value)
66
+ return if value.nil?
67
+ return value.resolve if value.respond_to? :resolve
68
+ deep_copy(value)
69
+ end
70
+
71
+ def merge_resolve(field_name)
72
+ field = self.class.fields[field_name] or return
73
+ result = field.type.new
74
+ merge_values(result,read_underlay_attribute(field_name))
75
+ merge_values(result,read_set_attribute(field_name))
76
+ result
77
+ end
78
+
79
+ def merge_values(target, value=nil)
80
+ value ||= {}
81
+ if target.is_a? Gorillib::Collection
82
+ value.each_pair do |k,v|
83
+ existing = target[k]
84
+ if existing && existing.respond_to?(:receive!)
85
+ target[k].receive! v
86
+ elsif existing && existing.respond_to?(:merge!)
87
+ target[k].merge! v
88
+ else
89
+ target[k] = v
90
+ end
91
+ end
92
+ else
93
+ target.receive! value
94
+ end
95
+ end
96
+
97
+ def read_resolved_attribute(field_name)
98
+ field = self.class.fields[field_name] or return
99
+ self.send(field.resolver, field_name)
100
+ end
101
+
102
+ def read_set_attribute(field_name)
103
+ attr_name = "@#{field_name}"
104
+ instance_variable_get(attr_name) if instance_variable_defined?(attr_name)
105
+ end
106
+
107
+ def read_underlay_attribute(field_name)
108
+ return if underlay.nil?
109
+ underlay.read_resolved_attribute(field_name)
110
+ end
111
+
112
+ def read_set_or_underlay_attribute(field_name)
113
+ result = read_set_attribute(field_name)
114
+ return result unless result.nil?
115
+ read_underlay_attribute(field_name)
116
+ end
117
+ end
118
+
119
+ end
@@ -0,0 +1,316 @@
1
+ module Ironfan
2
+ class Broker
3
+
4
+ class Computer < Builder
5
+ collection :resources, Whatever
6
+ collection :drives, Ironfan::Broker::Drive
7
+ collection :providers, Ironfan::Provider
8
+ field :server, Ironfan::Dsl::Server
9
+ delegate :[],:[]=,:include?,:delete,
10
+ :to => :resources
11
+
12
+ # Only used for bogus servers
13
+ field :name, String
14
+ field :bogus, Array, :default => []
15
+
16
+ def initialize(*args)
17
+ super
18
+ providers[:chef] ||= Ironfan::Provider::ChefServer
19
+ return unless server
20
+ providers[:iaas] = server.selected_cloud.provider
21
+ volumes = server.volumes.values
22
+ volumes += server.implied_volumes
23
+ volumes.each { |v| self.drive v.name, :volume => v }
24
+ end
25
+
26
+ def name
27
+ return server.fullname if server?
28
+ return @name if @name
29
+ "unnamed:#{object_id}"
30
+ end
31
+
32
+ #
33
+ # Discovery
34
+ #
35
+ def correlate
36
+ chosen_resources.each do |res|
37
+ unless res.respond_to? :expected_ids
38
+ Chef::Log.warn("FIXME: Using correlate! instead of on_correlate in #{res}")
39
+ res.correlate! self
40
+ return
41
+ end
42
+
43
+ res.expected_ids(self).each do |id|
44
+ next unless res.recall? id
45
+
46
+ recalled = res.recall id
47
+ recalled.users << self
48
+
49
+ target = res.resource_type.to_s
50
+ target += "__#{id}" if res.multiple?
51
+ self[target.to_sym] = recalled
52
+
53
+ recalled.on_correlate(self)
54
+ end
55
+ end
56
+ end
57
+
58
+ def correlate_with(res)
59
+ end
60
+
61
+ def validate
62
+ computer = self
63
+ Ironfan.delegate_to(chosen_resources) { validate_computer! computer }
64
+ end
65
+
66
+ #
67
+ # Manipulation
68
+ #
69
+ def kill(options={})
70
+ target_resources = chosen_resources(options)
71
+ resources.each do |res|
72
+ next unless target_resources.include? res.class
73
+ res.destroy unless res.shared?
74
+ end
75
+ end
76
+
77
+ def launch
78
+ ensure_dependencies
79
+ providers[:iaas].machine_class.create! self
80
+ save
81
+ end
82
+
83
+ def stop
84
+ machine.stop
85
+ node.announce_state :stopped
86
+ end
87
+
88
+ def start
89
+ ensure_dependencies
90
+ machine.start
91
+ node.announce_state :started
92
+ save
93
+ end
94
+
95
+ def save(options={})
96
+ chosen_resources(options).each {|res| res.save! self}
97
+ end
98
+
99
+ #
100
+ # Utility
101
+ #
102
+ def chosen_providers(options={})
103
+ case options[:providers]
104
+ when :all,nil then [:chef,:iaas]
105
+ else [ (options[:providers]) ]
106
+ end
107
+ end
108
+
109
+ def chosen_resources(options={})
110
+ chosen_providers(options).map do |name|
111
+ providers[name].resources
112
+ end.flatten
113
+ end
114
+
115
+ def ensure_dependencies
116
+ chosen_resources.each do |res|
117
+ # ensure_resource res unless res < Ironfan::IaasProvider::Machine
118
+ res.create! self unless res < Ironfan::IaasProvider::Machine
119
+ end
120
+ end
121
+
122
+ # def ensure_resource(type)
123
+ # if type.multiple?
124
+ # existing = resources[type.resource_id]
125
+ # else
126
+ #
127
+ # end
128
+ # end
129
+
130
+ #
131
+ # Display
132
+ #
133
+ def to_display(style,values={})
134
+ unless [:minimal,:default,:expanded].include? style
135
+ raise "Bad display style #{style}"
136
+ end
137
+
138
+ values["Name"] = name
139
+ # We expect these to be over-ridden by the contained classes
140
+ values["Chef?"] = "no"
141
+ values["State"] = "not running"
142
+
143
+ delegate_to([ server, node, machine ].compact) do
144
+ to_display style, values
145
+ end
146
+
147
+ if style == :expanded
148
+ values["Startable"] = display_boolean(stopped?)
149
+ values["Launchable"] = display_boolean(launchable?)
150
+ end
151
+ values["Bogus"] = bogus.join(',')
152
+
153
+ # Only show values that actually have something to show
154
+ values.delete_if {|k,v| v.to_s.empty?}
155
+ values
156
+ end
157
+ def display_boolean(value) value ? "yes" : "no"; end
158
+
159
+ #
160
+ # Accessors
161
+ #
162
+ def bogus
163
+ resources.values.map(&:bogus).flatten
164
+ end
165
+ def client
166
+ self[:client]
167
+ end
168
+ def machine
169
+ self[:machine]
170
+ end
171
+ def machine=(value)
172
+ self[:machine] = value
173
+ end
174
+ def node
175
+ self[:node]
176
+ end
177
+ def node=(value)
178
+ self[:node]= value
179
+ end
180
+
181
+ #
182
+ # Status flags
183
+ #
184
+ def bogus?
185
+ resources.values.any?(&:bogus?)
186
+ end
187
+ def client?
188
+ not client.nil?
189
+ end
190
+ def created?
191
+ machine? and machine.created?
192
+ end
193
+ def machine?
194
+ not machine.nil?
195
+ end
196
+ def killable?
197
+ not permanent? and (node? or created?)
198
+ end
199
+ def launchable?
200
+ not bogus? and not created?
201
+ end
202
+ def node?
203
+ not node.nil?
204
+ end
205
+ def permanent?
206
+ return false unless server?
207
+ return false unless server.selected_cloud.respond_to? :permanent
208
+ [true, :true, 'true'].include? server.selected_cloud.permanent
209
+ end
210
+ def running?
211
+ machine? and machine.running?
212
+ end
213
+ def server?
214
+ not server.nil?
215
+ end
216
+ def stopped?
217
+ machine? and machine.stopped?
218
+ end
219
+
220
+ end
221
+
222
+ class Computers < Gorillib::ModelCollection
223
+ self.item_type = Computer
224
+ self.key_method = :object_id
225
+ delegate :first, :map, :any?,
226
+ :to => :values
227
+ attr_accessor :cluster
228
+
229
+ def initialize(*args)
230
+ super
231
+ options = args.pop or return
232
+ self.cluster = options[:cluster]
233
+ create_expected!
234
+ end
235
+
236
+ #
237
+ # Discovery
238
+ #
239
+ def correlate
240
+ values.each {|computer| computer.correlate }
241
+ end
242
+
243
+ def validate
244
+ providers = values.map {|c| c.providers.values}.flatten
245
+ computers = self
246
+
247
+ Ironfan.delegate_to(values) { validate }
248
+ Ironfan.delegate_to(providers) { validate computers }
249
+ end
250
+
251
+ #
252
+ # Manipulation
253
+ #
254
+ def kill(options={})
255
+ Ironfan.delegate_to(values) { kill(options) }
256
+ end
257
+ def launch
258
+ Ironfan.delegate_to(values) { launch }
259
+ end
260
+ def save(options={})
261
+ Ironfan.delegate_to(values) { save(options) }
262
+ end
263
+ def start
264
+ Ironfan.delegate_to(values) { start }
265
+ end
266
+ def stop
267
+ Ironfan.delegate_to(values) { stop }
268
+ end
269
+
270
+ # set up new computers for each server in the cluster definition
271
+ def create_expected!
272
+ self.cluster.servers.each do |server|
273
+ self << Computer.new(:server => server)
274
+ end unless self.cluster.nil?
275
+ end
276
+
277
+ # Return the selection inside another Computers collection
278
+ def select(&block)
279
+ result = empty_copy
280
+ values.select(&block).each{|m| result << m}
281
+ result
282
+ end
283
+
284
+ def empty_copy
285
+ result = self.class.new
286
+ result.cluster = self.cluster unless self.cluster.nil?
287
+ result
288
+ end
289
+
290
+ # Find all selected computers, as well as any bogus computers from discovery
291
+ def slice(facet_name=nil, slice_indexes=nil)
292
+ return self if (facet_name.nil? and slice_indexes.nil?)
293
+ result = empty_copy
294
+ slice_array = build_slice_array(slice_indexes)
295
+ each do |m|
296
+ result << m if (m.bogus? or ( # bogus computer or
297
+ ( m.server.facet_name == facet_name ) and # facet match and
298
+ ( slice_array.include? m.server.index or # index match or
299
+ slice_indexes.nil? ) ) ) # no indexes specified
300
+ end
301
+ result
302
+ end
303
+ def build_slice_array(slice_indexes)
304
+ return [] if slice_indexes.nil?
305
+ raise "Bad slice_indexes: #{slice_indexes}" if slice_indexes =~ /[^0-9\.,]/
306
+ eval("[#{slice_indexes}]").map {|idx| idx.class == Range ? idx.to_a : idx}.flatten
307
+ end
308
+
309
+ # Utility function to provide a human-readable list of names
310
+ def joined_names
311
+ values.map(&:name).join(", ").gsub(/, ([^,]*)$/, ' and \1')
312
+ end
313
+ end
314
+
315
+ end
316
+ end
@@ -0,0 +1,21 @@
1
+ module Ironfan
2
+ class Broker
3
+
4
+ class Drive < Builder
5
+ field :node, Hash, :default => {}
6
+ field :disk, Ironfan::Provider::Resource
7
+ field :volume, Ironfan::Dsl::Volume
8
+
9
+ field :name, String
10
+
11
+ def volume=(value)
12
+ super
13
+ return unless value
14
+ # inscribe the cluster DSL values into chef attributes
15
+ volume.attributes.each_pair {|k,v| node[k.to_s] = v }
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ # This module is intended to read in a cluster DSL description, and broker
2
+ # out to the various cloud providers, to control instance life-cycle and
3
+ # handle provider-specific amenities (SecurityGroup, Volume, etc.) for
4
+ # them.
5
+ module Ironfan
6
+ def self.broker
7
+ @@broker ||= Ironfan::Broker.new
8
+ end
9
+
10
+ class Broker < Builder
11
+ # Take in a Dsl::Cluster, return Computers populated with
12
+ # all discovered resources that correlate, plus bogus computers
13
+ # corresponding to
14
+ def discover!(cluster)
15
+ # Get fully resolved servers, and build Computers using them
16
+ computers = Computers.new(:cluster => cluster.resolve)
17
+ providers = computers.map{|c| c.providers.values}.flatten.uniq
18
+
19
+ delegate_to(providers) { load cluster }
20
+ computers.correlate
21
+ computers.validate
22
+ computers
23
+ end
24
+
25
+ def display(computers,style)
26
+ defined_data = computers.map {|m| m.to_display(style) }
27
+ if defined_data.empty?
28
+ ui.info "Nothing to report"
29
+ else
30
+ headings = defined_data.map{|r| r.keys}.flatten.uniq
31
+ Formatador.display_compact_table(defined_data, headings.to_a)
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,14 @@
1
+ module Ironfan
2
+
3
+ class Builder
4
+ include Gorillib::Builder
5
+
6
+ def self.ui() Ironfan.ui ; end
7
+ def ui() Ironfan.ui ; end
8
+
9
+ def delegate_to(*args,&block)
10
+ Ironfan.delegate_to(*args,&block)
11
+ end
12
+ end
13
+
14
+ end
@@ -1,68 +1,26 @@
1
1
  module Ironfan
2
2
  def self.deprecated call, replacement=nil
3
- correction = ", use #{replacement} instead" if replacement
4
- ui.warn "The '#{call}' statement is deprecated #{caller(2).first.inspect}#{correction}"
3
+ correction = ", " if replacement
4
+ ui.error "The '#{call}' statement is deprecated#{correction} (in #{caller(2).first.inspect}). It looks like you are using an outdated DSL definition: please see https://github.com/infochimps-labs/ironfan/wiki/Upgrading-to-v4 for all the necessary upgrade steps."
5
+ exit(1)
5
6
  end
6
7
 
7
- class DslBuilder
8
- def to_hash
9
- Ironfan.deprecated 'to_hash', 'attributes'
10
- attributes
11
- end
12
- def to_mash
13
- Ironfan.deprecated 'to_mash', 'attributes'
14
- attributes
15
- end
16
- def reverse_merge!(attrs={})
17
- Ironfan.deprecated 'reverse_merge!', 'receive!'
18
- receive!(attrs)
19
- end
20
- def configure(attrs={},&block)
21
- Ironfan.deprecated 'configure', 'receive!'
22
- receive!(attrs, &block)
23
- end
24
- end
25
-
26
- class ComputeBuilder
27
- def root_volume(attrs={}, &block)
28
- Ironfan.deprecated 'root_volume', 'volume(:root)'
29
- volume(:root, attrs, &block)
30
- end
31
- end
32
-
33
- class Cluster
34
- def use(*clusters)
35
- Ironfan.deprecated 'use', 'underlay'
36
- clusters.each do |c|
37
- other_cluster = Ironfan.load_cluster(c)
38
- cluster.underlay other_cluster
8
+ class Dsl
9
+ class Cloud
10
+ def defaults
11
+ Ironfan.deprecated 'defaults'
39
12
  end
40
- self
41
13
  end
42
- end
43
-
44
- class Server
45
- def chef_node_name name
46
- Ironfan.deprecated 'chef_node_name', 'fullname'
47
- fullname name
48
- end
49
-
50
- def composite_volumes
51
- Ironfan.deprecated 'composite_volumes', 'volumes'
52
- volumes
53
- end
54
- end
55
-
56
- class CloudDsl::Ec2
57
- def elastic_ip(*args, &block)
58
- Ironfan.deprecated 'elastic_ip', 'public_ip'
59
- public_ip(*args, &block)
14
+
15
+ class Compute
16
+ def cloud(provider=nil)
17
+ if provider.nil?
18
+ Ironfan.deprecated 'cloud(nil)','use cloud(:ec2) instead'
19
+ provider = :ec2
20
+ end
21
+ super(provider)
22
+ end
60
23
  end
61
- end
62
24
 
63
- class Volume
64
- def defaults
65
- Ironfan.deprecated 'defaults'
66
- end
67
25
  end
68
26
  end
@@ -0,0 +1,21 @@
1
+ module Ironfan
2
+ class Dsl
3
+
4
+ class Cloud < Ironfan::Dsl
5
+ magic :default_cloud, :boolean, :default => false
6
+
7
+ # Factory out to subclasses
8
+ def self.receive(obj,&block)
9
+ obj[:_type] = case obj[:name]
10
+ when :ec2; Ec2
11
+ when :virtualbox; VirtualBox
12
+ else; raise "Unsupported cloud #{obj[:name]}"
13
+ end unless native?(obj)
14
+ super
15
+ end
16
+
17
+ def implied_volumes() Ironfan.noop(self,__method__,*p); end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module Ironfan
2
+ class Dsl
3
+
4
+ class Cluster < Ironfan::Dsl::Compute
5
+ collection :facets, Ironfan::Dsl::Facet, :resolver => :deep_resolve
6
+
7
+ def initialize(attrs={},&block)
8
+ super
9
+ self.cluster_role Ironfan::Dsl::Role.new(:name => "#{attrs[:name]}_cluster")
10
+ self.expand_servers!
11
+ end
12
+
13
+ def expand_servers!
14
+ facets.each {|facet| facet.expand_servers! }
15
+ servers
16
+ end
17
+
18
+ # Utility method to reference all servers from constituent facets
19
+ def servers
20
+ result = Gorillib::ModelCollection.new(:item_type => Ironfan::Dsl::Server, :key_method => :fullname)
21
+ facets.each {|f| f.servers.each {|s| result << s} }
22
+ result
23
+ end
24
+ end
25
+
26
+ end
27
+ end