ironfan 3.1.0.rc1

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 (59) hide show
  1. data/.gitignore +51 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +130 -0
  4. data/Gemfile +26 -0
  5. data/LICENSE.md +201 -0
  6. data/README.md +328 -0
  7. data/Rakefile +104 -0
  8. data/TODO.md +16 -0
  9. data/VERSION +1 -0
  10. data/chefignore +41 -0
  11. data/cluster_chef-knife.gemspec +123 -0
  12. data/cluster_chef.gemspec +111 -0
  13. data/config/client.rb +59 -0
  14. data/config/proxy.pac +12 -0
  15. data/config/ubuntu10.04-ironfan.erb +157 -0
  16. data/config/ubuntu11.10-ironfan.erb +145 -0
  17. data/ironfan.gemspec +121 -0
  18. data/lib/chef/knife/bootstrap/ubuntu10.04-ironfan.erb +157 -0
  19. data/lib/chef/knife/bootstrap/ubuntu11.10-ironfan.erb +145 -0
  20. data/lib/chef/knife/cluster_bootstrap.rb +74 -0
  21. data/lib/chef/knife/cluster_kick.rb +94 -0
  22. data/lib/chef/knife/cluster_kill.rb +73 -0
  23. data/lib/chef/knife/cluster_launch.rb +164 -0
  24. data/lib/chef/knife/cluster_list.rb +50 -0
  25. data/lib/chef/knife/cluster_proxy.rb +126 -0
  26. data/lib/chef/knife/cluster_show.rb +61 -0
  27. data/lib/chef/knife/cluster_ssh.rb +141 -0
  28. data/lib/chef/knife/cluster_start.rb +40 -0
  29. data/lib/chef/knife/cluster_stop.rb +43 -0
  30. data/lib/chef/knife/cluster_sync.rb +77 -0
  31. data/lib/chef/knife/generic_command.rb +66 -0
  32. data/lib/chef/knife/knife_common.rb +195 -0
  33. data/lib/ironfan.rb +143 -0
  34. data/lib/ironfan/chef_layer.rb +299 -0
  35. data/lib/ironfan/cloud.rb +412 -0
  36. data/lib/ironfan/cluster.rb +118 -0
  37. data/lib/ironfan/compute.rb +153 -0
  38. data/lib/ironfan/deprecated.rb +33 -0
  39. data/lib/ironfan/discovery.rb +177 -0
  40. data/lib/ironfan/dsl_object.rb +124 -0
  41. data/lib/ironfan/facet.rb +144 -0
  42. data/lib/ironfan/fog_layer.rb +150 -0
  43. data/lib/ironfan/private_key.rb +130 -0
  44. data/lib/ironfan/role_implications.rb +58 -0
  45. data/lib/ironfan/security_group.rb +119 -0
  46. data/lib/ironfan/server.rb +281 -0
  47. data/lib/ironfan/server_slice.rb +260 -0
  48. data/lib/ironfan/volume.rb +157 -0
  49. data/spec/ironfan/cluster_spec.rb +13 -0
  50. data/spec/ironfan/facet_spec.rb +69 -0
  51. data/spec/ironfan/server_slice_spec.rb +19 -0
  52. data/spec/ironfan/server_spec.rb +112 -0
  53. data/spec/ironfan_spec.rb +193 -0
  54. data/spec/spec_helper.rb +50 -0
  55. data/spec/spec_helper/dummy_chef.rb +25 -0
  56. data/spec/test_config.rb +20 -0
  57. data/tasks/chef_config.rake +38 -0
  58. data/tasks/jeweler_use_alt_branch.rake +53 -0
  59. metadata +217 -0
@@ -0,0 +1,58 @@
1
+ module Ironfan
2
+ ComputeBuilder.class_eval do
3
+
4
+ # organization-wide security group
5
+ role_implication "systemwide" do
6
+ self.cloud.security_group "systemwide" do
7
+ end
8
+ end
9
+
10
+ # NFS server allows access from nfs_clients
11
+ role_implication "nfs_server" do
12
+ self.cloud.security_group "nfs_server" do
13
+ authorize_group "nfs_client"
14
+ end
15
+ end
16
+
17
+ role_implication "nfs_client" do
18
+ self.cloud.security_group "nfs_client"
19
+ end
20
+
21
+ # Opens port 22 to the world
22
+ role_implication "ssh" do
23
+ self.cloud.security_group 'ssh' do
24
+ authorize_port_range 22..22
25
+ end
26
+ end
27
+
28
+ # Open the Chef server API port (4000) and the webui (4040)
29
+ role_implication "chef_server" do
30
+ self.cloud.security_group "chef_server" do
31
+ authorize_port_range 4000..4000 # chef-server-api
32
+ authorize_port_range 4040..4040 # chef-server-webui
33
+ end
34
+ end
35
+
36
+ # web server? add the group "web_server" to open the web holes
37
+ role_implication "web_server" do
38
+ self.cloud.security_group("#{cluster_name}-web_server") do
39
+ authorize_port_range 80..80
40
+ authorize_port_range 443..443
41
+ end
42
+ end
43
+
44
+ # if you're a redis server, open the port and authorize redis clients in your group to talk to you
45
+ role_implication("redis_server") do
46
+ cluster_name = self.cluster_name # hack: put cluster_name is in scope
47
+ self.cloud.security_group("#{cluster_name}-redis_server") do
48
+ authorize_group("#{cluster_name}-redis_client")
49
+ end
50
+ end
51
+
52
+ # redis_clients gain rights to the redis_server
53
+ role_implication("redis_client") do
54
+ self.cloud.security_group("#{cluster_name}-redis_client")
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,119 @@
1
+ module Ironfan
2
+ module Cloud
3
+
4
+ class SecurityGroup < DslObject
5
+ has_keys :name, :description, :owner_id
6
+ attr_reader :group_authorizations
7
+ attr_reader :range_authorizations
8
+
9
+ def initialize cloud, group_name, group_description=nil, group_owner_id=nil
10
+ super()
11
+ set :name, group_name.to_s
12
+ description group_description || "ironfan generated group #{group_name}"
13
+ @cloud = cloud
14
+ @group_authorizations = []
15
+ @group_authorized_by = []
16
+ @range_authorizations = []
17
+ owner_id(group_owner_id || Chef::Config[:knife][:aws_account_id])
18
+ end
19
+
20
+ @@all = nil
21
+ def all
22
+ self.class.all
23
+ end
24
+ def self.all
25
+ return @@all if @@all
26
+ get_all
27
+ end
28
+ def self.get_all
29
+ groups_list = Ironfan.fog_connection.security_groups.all
30
+ @@all = groups_list.inject(Mash.new) do |hsh, fog_group|
31
+ hsh[fog_group.name] = fog_group ; hsh
32
+ end
33
+ end
34
+
35
+ def get
36
+ all[name] || Ironfan.fog_connection.security_groups.get(name)
37
+ end
38
+
39
+ def self.get_or_create(group_name, description)
40
+ # FIXME: the '|| Ironfan.fog' part is probably unnecessary
41
+ fog_group = all[group_name] || Ironfan.fog_connection.security_groups.get(group_name)
42
+ unless fog_group
43
+ self.step(group_name, "creating (#{description})", :green)
44
+ fog_group = all[group_name] = Ironfan.fog_connection.security_groups.new(:name => group_name, :description => description, :connection => Ironfan.fog_connection)
45
+ fog_group.save
46
+ end
47
+ fog_group
48
+ end
49
+
50
+ def authorize_group(group_name, owner_id=nil)
51
+ @group_authorizations << [group_name.to_s, owner_id]
52
+ end
53
+
54
+ def authorized_by_group(other_name)
55
+ @group_authorized_by << other_name.to_s
56
+ end
57
+
58
+ def authorize_port_range(range, cidr_ip = '0.0.0.0/0', ip_protocol = 'tcp')
59
+ range = (range .. range) if range.is_a?(Integer)
60
+ @range_authorizations << [range, cidr_ip, ip_protocol]
61
+ end
62
+
63
+ def group_permission_already_set?(fog_group, other_name, authed_owner)
64
+ return false if fog_group.ip_permissions.nil?
65
+ fog_group.ip_permissions.any? do |existing_permission|
66
+ existing_permission["groups"].include?({"userId" => authed_owner, "groupName" => other_name}) &&
67
+ existing_permission["fromPort"] == 1 &&
68
+ existing_permission["toPort"] == 65535
69
+ end
70
+ end
71
+
72
+ def range_permission_already_set?(fog_group, range, cidr_ip, ip_protocol)
73
+ return false if fog_group.ip_permissions.nil?
74
+ fog_group.ip_permissions.include?(
75
+ { "groups"=>[], "ipRanges"=>[{"cidrIp"=>cidr_ip}],
76
+ "ipProtocol"=>ip_protocol, "fromPort"=>range.first, "toPort"=>range.last})
77
+ end
78
+
79
+ # FIXME: so if you're saying to yourself, "self, this is some soupy gooey
80
+ # code right here" then you and your self are correct. Much of this is to
81
+ # work around old limitations in the EC2 api. You can now treat range and
82
+ # group permissions the same, and we should.
83
+
84
+ def run
85
+ fog_group = self.class.get_or_create(name, description)
86
+ @group_authorizations.uniq.each do |other_name, authed_owner|
87
+ authed_owner ||= self.owner_id
88
+ next if group_permission_already_set?(fog_group, other_name, authed_owner)
89
+ step("authorizing access from all machines in #{other_name} to #{name}", :blue)
90
+ self.class.get_or_create(other_name, "Authorized to access #{name}")
91
+ begin fog_group.authorize_group_and_owner(other_name, authed_owner)
92
+ rescue StandardError => e ; ui.warn e ; end
93
+ end
94
+ @group_authorized_by.uniq.each do |other_name|
95
+ authed_owner = self.owner_id
96
+ other_group = self.class.get_or_create(other_name, "Authorized for access by #{self.name}")
97
+ next if group_permission_already_set?(other_group, self.name, authed_owner)
98
+ step("authorizing access to all machines in #{other_name} from #{name}", :blue)
99
+ begin other_group.authorize_group_and_owner(self.name, authed_owner)
100
+ rescue StandardError => e ; ui.warn e ; end
101
+ end
102
+ @range_authorizations.uniq.each do |range, cidr_ip, ip_protocol|
103
+ next if range_permission_already_set?(fog_group, range, cidr_ip, ip_protocol)
104
+ step("opening #{ip_protocol} ports #{range} to #{cidr_ip}", :blue)
105
+ begin fog_group.authorize_port_range(range, { :cidr_ip => cidr_ip, :ip_protocol => ip_protocol })
106
+ rescue StandardError => e ; ui.warn e ; end
107
+ end
108
+ end
109
+
110
+ def self.step(group_name, desc, *style)
111
+ ui.info(" group #{"%-15s" % (group_name+":")}\t#{ui.color(desc.to_s, *style)}")
112
+ end
113
+ def step(desc, *style)
114
+ self.class.step(self.name, desc, *style)
115
+ end
116
+
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,281 @@
1
+ module Ironfan
2
+
3
+ #
4
+ # A server is a specific (logical) member of a facet within a cluster.
5
+ #
6
+ # It may have extra attributes if it also exists in the Chef server,
7
+ # or if it exists in the real world (as revealed by Fog)
8
+ #
9
+ class Server < Ironfan::ComputeBuilder
10
+ attr_reader :cluster, :facet, :facet_index, :tags
11
+ attr_accessor :chef_node, :fog_server
12
+
13
+ @@all ||= Mash.new
14
+
15
+ def initialize facet, idx
16
+ @cluster = facet.cluster
17
+ @facet = facet
18
+ @facet_index = idx
19
+ @fullname = [cluster_name, facet_name, facet_index].join('-')
20
+ super(@fullname)
21
+ @tags = { "name" => name, "cluster" => cluster_name, "facet" => facet_name, "index" => facet_index, }
22
+ ui.warn("Duplicate server #{[self, facet.name, idx]} vs #{@@all[fullname]}") if @@all[fullname]
23
+ @@all[fullname] = self
24
+ end
25
+
26
+ def fullname fn=nil
27
+ @fullname = fn if fn
28
+ @fullname
29
+ end
30
+
31
+ def cluster_name
32
+ cluster.name
33
+ end
34
+
35
+ def facet_name
36
+ facet.name
37
+ end
38
+
39
+ def servers
40
+ Ironfan::ServerGroup.new(cluster, [self])
41
+ end
42
+
43
+ def bogosity val=nil
44
+ @settings[:bogosity] = val if not val.nil?
45
+ return @settings[:bogosity] if not @settings[:bogosity].nil?
46
+ return :bogus_facet if facet.bogus?
47
+ # return :out_of_range if (self.facet_index.to_i >= facet.instances)
48
+ false
49
+ end
50
+
51
+ def in_cloud?
52
+ !! fog_server
53
+ end
54
+
55
+ def in_chef?
56
+ chef_node || chef_client
57
+ end
58
+
59
+ def has_cloud_state?(*states)
60
+ in_cloud? && states.flatten.include?(fog_server.state)
61
+ end
62
+
63
+ def exists?
64
+ created? || in_chef?
65
+ end
66
+
67
+ def created?
68
+ in_cloud? && (not ['terminated', 'shutting-down'].include?(fog_server.state))
69
+ end
70
+
71
+ def running?
72
+ has_cloud_state?('running')
73
+ end
74
+
75
+ def startable?
76
+ has_cloud_state?('stopped')
77
+ end
78
+
79
+ def launchable?
80
+ not created?
81
+ end
82
+
83
+ def sshable?
84
+ in_chef?
85
+ end
86
+
87
+ def killable?
88
+ in_chef? || created?
89
+ end
90
+
91
+ def to_s
92
+ super[0..-3] + " chef: #{in_chef? && chef_node.name} fog: #{in_cloud? && fog_server.id}}>"
93
+ end
94
+
95
+ #
96
+ # Attributes
97
+ #
98
+
99
+ def tag key, value=nil
100
+ if value then @tags[key] = value ; end
101
+ @tags[key]
102
+ end
103
+
104
+ #
105
+ # Resolve:
106
+ #
107
+ def resolve!
108
+ reverse_merge!(facet)
109
+ reverse_merge!(cluster)
110
+ @settings[:run_list] = combined_run_list
111
+ #
112
+ cloud.reverse_merge!(facet.cloud)
113
+ cloud.reverse_merge!(cluster.cloud)
114
+ #
115
+ cloud.user_data({
116
+ :chef_server => Chef::Config.chef_server_url,
117
+ :validation_client_name => Chef::Config.validation_client_name,
118
+ #
119
+ :node_name => fullname,
120
+ :cluster_name => cluster_name,
121
+ :facet_name => facet_name,
122
+ :facet_index => facet_index,
123
+ #
124
+ :run_list => run_list,
125
+ })
126
+ #
127
+ if client_key.body then cloud.user_data({ :client_key => client_key.body, })
128
+ else cloud.user_data({ :validation_key => cloud.validation_key }) ; end
129
+ cloud.keypair(cluster_name) if cloud.keypair.nil?
130
+ #
131
+ self
132
+ end
133
+
134
+ #
135
+ # Assembles the combined runlist.
136
+ #
137
+ # * run_list :first items -- cluster then facet then server
138
+ # * run_list :normal items -- cluster then facet then server
139
+ # * own roles: cluster_role then facet_role
140
+ # * run_list :last items -- cluster then facet then server
141
+ #
142
+ # Ironfan.cluster(:my_cluster) do
143
+ # role('f', :last)
144
+ # role('c')
145
+ # facet(:my_facet) do
146
+ # role('d')
147
+ # role('e')
148
+ # role('b', :first)
149
+ # role('h', :last)
150
+ # end
151
+ # role('a', :first)
152
+ # role('g', :last)
153
+ # end
154
+ #
155
+ # produces
156
+ # cluster list [a] [c] [cluster_role] [fg]
157
+ # facet list [b] [de] [facet_role] [h]
158
+ #
159
+ # yielding run_list
160
+ # ['a', 'b', 'c', 'd', 'e', 'cr', 'fr', 'f', 'g', 'h']
161
+ #
162
+ # Avoid duplicate conflicting declarations. If you say define things more
163
+ # than once, the *earliest encountered* one wins, even if it is elsewhere
164
+ # marked :last.
165
+ #
166
+ def combined_run_list
167
+ cg = @cluster.run_list_groups
168
+ fg = @facet.run_list_groups
169
+ sg = self.run_list_groups
170
+ [ cg[:first], fg[:first], sg[:first],
171
+ cg[:normal], fg[:normal], sg[:normal],
172
+ cg[:own], fg[:own],
173
+ cg[:last], fg[:last], sg[:last], ].flatten.compact.uniq
174
+ end
175
+
176
+ #
177
+ # This prepares a composited view of the volumes -- it shows the cluster
178
+ # definition overlaid by the facet definition overlaid by the server
179
+ # definition.
180
+ #
181
+ # This method *does* auto-vivify an empty volume declaration on the server,
182
+ # but doesn't modify it.
183
+ #
184
+ # This code is pretty smelly, but so is the resolve! behavior. advice welcome.
185
+ #
186
+ def composite_volumes
187
+ vols = {}
188
+ facet.volumes.each do |vol_name, vol|
189
+ self.volumes[vol_name] ||= Ironfan::Volume.new(:parent => self, :name => vol_name)
190
+ vols[vol_name] ||= self.volumes[vol_name].dup
191
+ vols[vol_name].reverse_merge!(vol)
192
+ end
193
+ cluster.volumes.each do |vol_name, vol|
194
+ self.volumes[vol_name] ||= Ironfan::Volume.new(:parent => self, :name => vol_name)
195
+ vols[vol_name] ||= self.volumes[vol_name].dup
196
+ vols[vol_name].reverse_merge!(vol)
197
+ end
198
+ vols.each{|vol_name, vol| vol.availability_zone self.default_availability_zone }
199
+ vols
200
+ end
201
+
202
+ # FIXME -- this will break on some edge case wehre a bogus node is
203
+ # discovered after everything is resolve!d
204
+ def default_availability_zone
205
+ cloud.default_availability_zone
206
+ end
207
+
208
+ #
209
+ # retrieval
210
+ #
211
+ def self.get(cluster_name, facet_name, facet_index)
212
+ cluster = Ironfan.cluster(cluster_name)
213
+ had_facet = cluster.has_facet?(facet_name)
214
+ facet = cluster.facet(facet_name)
215
+ facet.bogosity true unless had_facet
216
+ had_server = facet.has_server?( facet_index )
217
+ server = facet.server(facet_index)
218
+ server.bogosity :not_defined_in_facet unless had_server
219
+ return server
220
+ end
221
+
222
+ def self.all
223
+ @@all
224
+ end
225
+
226
+ #
227
+ # Actions!
228
+ #
229
+
230
+ def sync_to_cloud
231
+ step "Syncing to cloud"
232
+ attach_volumes
233
+ create_tags
234
+ associate_public_ip
235
+ end
236
+
237
+ def sync_to_chef
238
+ step "Syncing to chef server"
239
+ sync_chef_node
240
+ true
241
+ end
242
+
243
+ # FIXME: a lot of AWS logic in here. This probably lives in the facet.cloud
244
+ # but for the one or two things that come from the facet
245
+ def create_server
246
+ return nil if created? # only create a server if it does not already exist
247
+ fog_create_server
248
+ end
249
+
250
+ def create_tags
251
+ return unless created?
252
+ step(" labeling servers and volumes")
253
+ fog_create_tags(fog_server, self.fullname, tags)
254
+ composite_volumes.each do |vol_name, vol|
255
+ if vol.fog_volume
256
+ fog_create_tags(vol.fog_volume, vol.desc,
257
+ { "server" => self.fullname, "name" => "#{name}-#{vol.name}", "device" => vol.device, "mount_point" => vol.mount_point, "cluster" => cluster_name, "facet" => facet_name, "index" => facet_index, })
258
+ end
259
+ end
260
+ end
261
+
262
+ def block_device_mapping
263
+ composite_volumes.values.map(&:block_device_mapping).compact
264
+ end
265
+
266
+ # ugh. non-dry below.
267
+
268
+ def announce_as_started
269
+ return unless chef_node
270
+ announce_state('start')
271
+ chef_node.save
272
+ end
273
+
274
+ def announce_as_stopped
275
+ return unless chef_node
276
+ announce_state('stop')
277
+ chef_node.save
278
+ end
279
+
280
+ end
281
+ end