ironfan 3.2.2 → 4.0.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.
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