fairchild-poolparty 1.3.5 → 1.3.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/VERSION.yml +1 -1
  2. data/bin/cloud +1 -0
  3. data/bin/cloud-compile +1 -0
  4. data/bin/cloud-misc +34 -0
  5. data/bin/cloud-show +13 -1
  6. data/bin/cloud-ssh +4 -1
  7. data/bin/cloud-thrift +32 -18
  8. data/config/jeweler.rb +5 -3
  9. data/examples/monitored_cloud.rb +1 -1
  10. data/examples/thrift/thrift_example.rb +8 -4
  11. data/examples/vmware.rb +10 -0
  12. data/lib/cloud_providers/cloud_provider.rb +9 -0
  13. data/lib/cloud_providers/cloud_provider_instance.rb +9 -4
  14. data/lib/cloud_providers/connections.rb +9 -5
  15. data/lib/cloud_providers/ec2/ec2.rb +42 -12
  16. data/lib/cloud_providers/ec2/ec2_helpers.rb +62 -5
  17. data/lib/cloud_providers/ec2/ec2_instance.rb +14 -1
  18. data/lib/cloud_providers/vmware/vmware.rb +3 -0
  19. data/lib/core/file.rb +12 -0
  20. data/lib/core/object.rb +1 -1
  21. data/lib/dependency_resolvers/base.rb +2 -9
  22. data/lib/dependency_resolvers/chef.rb +3 -3
  23. data/lib/keypair.rb +5 -3
  24. data/lib/poolparty.rb +3 -1
  25. data/lib/poolparty/base.rb +45 -22
  26. data/lib/poolparty/cloud.rb +70 -15
  27. data/lib/poolparty/default.rb +1 -0
  28. data/lib/poolparty/installer.rb +1 -1
  29. data/lib/poolparty/installers/ec2.rb +30 -16
  30. data/lib/poolparty/monitor.rb +25 -3
  31. data/lib/poolparty/plugin.rb +1 -5
  32. data/lib/poolparty/plugins/apache.rb +37 -11
  33. data/lib/poolparty/plugins/apache2/passenger_site.rb +1 -1
  34. data/lib/poolparty/plugins/apache2/virtual_host.rb +1 -0
  35. data/lib/poolparty/plugins/collectd.rb +29 -0
  36. data/lib/poolparty/plugins/collectd/templates/collectd.conf.erb +369 -0
  37. data/lib/poolparty/plugins/hermes.rb +101 -0
  38. data/lib/poolparty/pool.rb +14 -6
  39. data/lib/poolparty/resource.rb +20 -17
  40. data/lib/poolparty/resources/file.rb +2 -2
  41. data/lib/poolparty/resources/line.rb +1 -1
  42. data/lib/poolparty/resources/link.rb +2 -1
  43. data/lib/proto/command_interface_handler.rb +42 -7
  44. data/lib/proto/command_query_handler.rb +19 -0
  45. data/lib/proto/gen-py/cloudthrift/CommandInterface.pyc +0 -0
  46. data/lib/proto/gen-py/cloudthrift/__init__.pyc +0 -0
  47. data/lib/proto/gen-py/cloudthrift/constants.pyc +0 -0
  48. data/lib/proto/gen-py/cloudthrift/ttypes.pyc +0 -0
  49. data/lib/proto/gen-py/thrift/Thrift.pyc +0 -0
  50. data/lib/proto/gen-py/thrift/__init__.pyc +0 -0
  51. data/lib/proto/gen-py/thrift/protocol/TBinaryProtocol.pyc +0 -0
  52. data/lib/proto/gen-py/thrift/protocol/TProtocol.pyc +0 -0
  53. data/lib/proto/gen-py/thrift/protocol/__init__.pyc +0 -0
  54. data/lib/proto/gen-py/thrift/transport/TSocket.pyc +0 -0
  55. data/lib/proto/gen-py/thrift/transport/TTransport.pyc +0 -0
  56. data/lib/proto/gen-py/thrift/transport/__init__.pyc +0 -0
  57. data/lib/proto/poolparty.thrift +1 -0
  58. data/tasks/poolparty.rake +24 -0
  59. data/test/fixtures/clouds/fake_clouds.rb +2 -2
  60. data/test/fixtures/clouds/simple_cloud.rb +2 -2
  61. data/test/fixtures/resources/fake_plugin.rb +5 -0
  62. data/test/fixtures/resources/fake_subclassed_plugin.rb +19 -0
  63. data/test/lib/cloud_providers/ec2/ec2_instance_test.rb +17 -8
  64. data/test/lib/cloud_providers/ec2/ec2_test.rb +15 -11
  65. data/test/lib/core/array_test.rb +4 -0
  66. data/test/lib/dependency_resolvers/base_test.rb +1 -1
  67. data/test/lib/dependency_resolvers/chef/resources/remote_directory_test.rb +2 -2
  68. data/test/lib/dependency_resolvers/chef/resources/remote_file_test.rb +1 -1
  69. data/test/lib/dependency_resolvers/chef_test.rb +3 -3
  70. data/test/lib/poolparty/base_test.rb +1 -1
  71. data/test/lib/poolparty/cloud_test.rb +140 -33
  72. data/test/lib/poolparty/monitor_test.rb +29 -2
  73. data/test/lib/poolparty/plugins/apache_test.rb +5 -0
  74. data/test/lib/poolparty/pool_test.rb +3 -3
  75. data/test/lib/poolparty/resource_test.rb +14 -2
  76. data/test/lib/poolparty/resources/conditional_test.rb +1 -0
  77. data/test/lib/poolparty/resources/directory_test.rb +1 -1
  78. data/test/lib/poolparty/resources/file_test.rb +1 -1
  79. data/test/lib/poolparty/resources/user_test.rb +1 -1
  80. data/test/lib/proto/command_query_handler_test.rb +11 -0
  81. data/test/lib/provision/bootstrapper_test.rb +0 -25
  82. data/test/test_helper.rb +0 -1
  83. metadata +15 -4
@@ -1,12 +1,58 @@
1
1
  module CloudProviders
2
2
  module Ec2Helpers
3
3
 
4
+ # VOLUMES
5
+ def attach_volume(instance_id, volume_id=next_unused_volume, device="/dev/sdh")
6
+ ec2.attach_volume(volume_id, instance_id, device)
7
+ end
8
+
9
+ def next_unused_volume
10
+ if all_volumes.empty?
11
+ nil
12
+ else
13
+ available_volumes.first
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def all_volumes
20
+ ebs_volumes.empty? ? [] : ec2.describe_volumes.select {|v| ebs_volumes.include?(v[:aws_id]) }
21
+ end
22
+
23
+ def available_volumes
24
+ all_volumes.select {|v| v[:aws_status] == 'available' }
25
+ end
26
+
27
+ def unavailable_volumes
28
+ all_volumes.reject {|v| available_volumes.include?(v) }
29
+ end
30
+
31
+ public
32
+
33
+ # SECURITY GROUPS
34
+ def security_groups(list=[])
35
+ ec2.describe_security_groups(list)
36
+ end
37
+
38
+ public
39
+
40
+ # ELASTIC IPS
41
+
4
42
  # Associate an address with the instance using ec2
5
43
  # Get the next_unused_elastic_ip
6
44
  # and if there is one, associate the instance to the
7
45
  # public ip
8
- def associate_address()
9
- raise StandardError.new('Not Implemented Yet')
46
+ def associate_address(instance_id)
47
+ new_ip = next_unused_elastic_ip
48
+ vputs("Assigning #{new_ip} to the ec2 instance #{instance_id}")
49
+ ec2.associate_address(instance_id, new_ip)
50
+ loop do
51
+ if describe_instance(:instance_id => instance_id).public_ip == new_ip
52
+ return new_ip
53
+ end
54
+ sleep 1
55
+ end
10
56
  end
11
57
 
12
58
  # Get the next usable elastic ip
@@ -17,13 +63,24 @@ module CloudProviders
17
63
  # intersection of the unused ips and those, find the first one available
18
64
  # and return that.
19
65
  def next_unused_elastic_ip
20
- raise StandardError.new('Not Implemented Yet')
21
- if elastic_ips.empty?
66
+ if unused_elastic_ips.empty?
22
67
  nil
23
68
  else
69
+ vputs("Found an unused elastic ip: #{unused_elastic_ips.first}")
70
+ unused_elastic_ips.first
24
71
  end
25
72
  end
26
-
73
+
74
+ private
75
+
76
+ def all_elastic_ips
77
+ elastic_ips.empty? ? [] : ec2.describe_addresses.map {|a| a[:public_ip]} & elastic_ips
78
+ end
79
+
80
+ def unused_elastic_ips
81
+ all_elastic_ips.select {|i| i[:instance_id] == nil }
82
+ end
83
+
27
84
  # Help create a keypair for the cloud
28
85
  # This is a helper to create the keypair and add them to the cloud for you
29
86
  # def create_keypair
@@ -64,7 +64,7 @@ module CloudProviders
64
64
  FileUtils.mkdir_p(cloud.tmp_path/ec2_dir) unless File.directory?(cloud.tmp_path/ec2_dir)
65
65
  run ["mkdir -p #{ec2_dir}"]
66
66
  # Save a yaml file of aws varibles and send to the instance
67
- File.open(cloud.tmp_path/ec2_dir/'aws.yml', 'w') do |f|
67
+ File.open(cloud.tmp_path/"etc"/"poolparty"/'env.yml', 'w') do |f|
68
68
  f<<YAML::dump(cloud_provider.aws_hash(ec2_dir)) #TODO: don't save sensitive info in /tmp
69
69
  end
70
70
  # We scp these files directly to the instance so to reduce the risk of accidentally leaving them in an insecure location
@@ -85,6 +85,8 @@ module CloudProviders
85
85
  /mnt
86
86
  /proc
87
87
  /sys
88
+ /etc/ssh/ssh_host_*
89
+ /etc/ssh/moduli
88
90
  /etc/udev/rules.d/70-persistent-net.rules
89
91
  /etc/udev/rules.d/z25_persistent-net.rules
90
92
  )
@@ -110,11 +112,22 @@ module CloudProviders
110
112
  cmds << "mkdir -p #{opts[:destination]}/loop"
111
113
  cmds << "mount -o loop #{image_file} #{opts[:destination]}/loop"
112
114
  cmds << "rsync -ax #{rsync_excludes(opts[:exclude])} #{opts[:volume]}/ #{opts[:destination]}/loop/"
115
+ cmds << "if [[ -f /etc/init.d/ec2-ssh-host-key-gen ]]; then chmod u+x /etc/init.d/ec2-ssh-host-key-gen ;fi"
113
116
  cmds << "umount #{opts[:destination]}/loop"
114
117
  self.run cmds
115
118
  image_file
116
119
  end
117
120
 
121
+ #TODO
122
+ # def bundle_and_register(opts={})
123
+ # arch = 'uname'
124
+ # image = make_image(opts)
125
+ # 'ec2-bundle-image' image
126
+ # 'ec2-upload-bundle'
127
+ # 'ec2-register-bundle'
128
+ # return ami
129
+ # end
130
+
118
131
  end
119
132
 
120
133
  end
@@ -122,6 +122,9 @@ module CloudProviders
122
122
  end
123
123
  @vmx_file = "'#{o}'"
124
124
  end
125
+
126
+ def before_compile(args); end
127
+ def after_compile(args); end
125
128
 
126
129
  end
127
130
  end
@@ -0,0 +1,12 @@
1
+ class File
2
+ class << self
3
+ alias_method :old_symlink, :symlink
4
+ def symlink(old_name, new_name)
5
+ begin
6
+ old_symlink(old_name, new_name)
7
+ rescue Errno::EEXIST
8
+ $stderr.puts "warning: symlinking #{old_name} -> #{new_name}. Already exists"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -68,7 +68,7 @@ class Object
68
68
  # MESSAGES
69
69
  # Debugging output helpers
70
70
  def vputs(m="")
71
- puts "[INFO] -- #{m}" if verbose?
71
+ puts "[INFO] -- #{m.is_a?(String) ? m : m.inspect}" if verbose?
72
72
  end
73
73
  def dputs(m="")
74
74
  puts "[DEBUG] -- #{m.is_a?(String) ? m : m.inspect}" if debugging?
@@ -89,14 +89,7 @@ module DependencyResolvers
89
89
  "#{obj.to_i}"
90
90
  end
91
91
  when String
92
- case obj
93
- when /^\d{4}$/
94
- "#{obj}"
95
- when /^\d{3}$/
96
- "0#{obj}"
97
- else
98
- "\"#{obj}\""
99
- end
92
+ "\"#{obj}\""
100
93
  when Proc
101
94
  obj.call # eh
102
95
  when Array
@@ -119,4 +112,4 @@ module DependencyResolvers
119
112
 
120
113
  end
121
114
 
122
- end
115
+ end
@@ -109,10 +109,10 @@ module DependencyResolvers
109
109
  end
110
110
 
111
111
  def apply_meta_notifies(resource, add)
112
- # The meta_notifies is a hash that looks like: {:file => [["pool_name", :reload]]}
112
+ # The meta_notifies is a hash that looks like: {:file => [["pool_name", :reload, :immediately]]}
113
113
  resource.meta_notifies.each do |ty, arr|
114
- arr.each do |nm, action|
115
- add << " notifies :#{action}, resources(:#{chef_safe_resource(ty)} => \"#{nm}\")"
114
+ arr.each do |nm, action, at_time|
115
+ add << " notifies :#{action}, resources(:#{chef_safe_resource(ty)} => \"#{nm}\"), :#{at_time}"
116
116
  end
117
117
  end
118
118
  end
@@ -7,10 +7,12 @@ class Keypair
7
7
  has_searchable_paths(:prepend_paths => [Dir.pwd, '/etc/poolparty/keys', "#{ENV["HOME"]}/.ssh/", "#{ENV["HOME"]}/.ec2/", ENV['EC2_CONFIG_DIR']])
8
8
 
9
9
  attr_accessor :filepath
10
+ attr_reader :extra_paths
10
11
 
11
12
  # Create a new key that defaults to id_rsa as the name.
12
- def initialize(fpath)
13
+ def initialize(fpath, extra_paths=[])
13
14
  @filepath = fpath
15
+ @extra_paths = extra_paths.map {|a| File.expand_path(a) }
14
16
  valid?
15
17
  end
16
18
 
@@ -27,10 +29,10 @@ class Keypair
27
29
  # Returns the full_filepath of the key. If a full filepath is passed, we just return the expanded filepath
28
30
  # for the keypair, otherwise query where it is against known locations
29
31
  def full_filepath
30
- @full_filepath ||= if File.file?(::File.expand_path(filepath))
32
+ @full_filepath ||= if File.file?(File.expand_path(filepath))
31
33
  ::File.expand_path(filepath)
32
34
  else
33
- search_in_known_locations(filepath)
35
+ search_in_known_locations(filepath, extra_paths)
34
36
  end
35
37
  end
36
38
  alias :to_s :full_filepath
@@ -43,7 +43,9 @@ require "poolparty/pool_party_error"
43
43
  hash
44
44
  symbol
45
45
  proc
46
- time).each do |lib|
46
+ time
47
+ file
48
+ ).each do |lib|
47
49
  require "core/#{lib}"
48
50
  end
49
51
 
@@ -128,47 +128,70 @@ module PoolParty
128
128
  # to create edges on the graph
129
129
  def resources_graph(force=false)
130
130
  return @resources_graph if @resources_graph && !force
131
- result = Digraph.new
131
+ result = Digraph.new
132
132
 
133
133
  create_graph(resources, nil, result)
134
134
 
135
- add_ordered_resources_to_result(resources, result)
136
-
137
135
  @resources_graph = result
138
136
  end
139
137
 
140
- # Add all the resources as edges of each other
141
- def add_ordered_resources_to_result(resources, result)
142
- arr_of_resources = resources.zip_offset(1)
143
-
144
- arr_of_resources.each do |first, second|
145
- result.add_edge!(first, second) unless result.edge?(first, second) or result.edge?(second, first)
146
- add_ordered_resources_to_result(first.resources, result)
147
- end
148
- end
149
-
150
138
  # Create the graph of resources. Blow up if a resource isn't found
151
139
  # that is required. If it is found, add it as an edge to the
152
140
  # dependency graph
153
141
  def create_graph(resources, on, result)
154
- resources.each_with_index do |resource, idx|
142
+ # add_ordered_resources_to_result(without_dependencies, result)
143
+ first_layer_of_ordered_resources = resources_without_dependencies.zip_offset(1)
144
+ first_layer_of_ordered_resources.each do |first, second|
145
+ result.add_edge!(first, second) unless second.nil? or result.edge?(first, second) or result.edge?(second, first)
146
+ end
147
+
148
+ resources_with_dependencies.each do |r|
155
149
 
156
- resource.dependencies.each do |dep_type, deps_array|
150
+ r.dependencies.each do |dep_type, deps_array|
157
151
  deps_array.each do |dep_name|
158
152
  dep = get_resource(dep_type, dep_name)
159
- raise PoolPartyError.create("ResourceNotFound", "A resource required for #{resource.has_method_name}(#{resource.name}) was not found: #{dep_type}(#{dep_name}). Please make sure you've specified this in your configuration.") unless dep
160
- result.add_edge!(dep, resource, dep.name) unless result.edge?(dep, resource)
153
+ raise PoolPartyError.create("ResourceNotFound", "A resource required for #{dep_type}(#{dep_name}) was not found: #{dep_type}(#{dep_name}). Please make sure you've specified this in your configuration.") unless dep
154
+
155
+ unless result.edge?(dep, r) and result.edge?(r, dep)
156
+ existing_connections = result.adjacent(dep)
157
+ existing_connections.each {|c| result.remove_edge!(r, c) }
158
+
159
+ result.add_edge!(dep, r, dep.name)
160
+
161
+ existing_connections.each {|c| result.add_edge!(dep, c) }
162
+ end
163
+
161
164
  end
162
165
  end
163
-
166
+ end
167
+
168
+ all_resources.each_with_index do |resource, idx|
164
169
  if on
165
- result.add_edge!(resource, on, resource.name) unless result.edge?(resource, on)
170
+ result.add_edge!(resource, on, resource.name) unless result.edge?(resource, on) or result.edge?(on, resource)
166
171
  else
167
- result.add_vertex!(resource)
172
+ result.add_vertex!(resource) unless result.vertex?(resource)
168
173
  end
169
-
170
- create_graph(resource.resources, resource, result)
171
174
  end
175
+
176
+ result
177
+ end
178
+
179
+ # Add all the resources as edges of each other
180
+ def add_ordered_resources_to_result(resources, result)
181
+ arr_of_resources = resources.zip_offset(1)
182
+
183
+ arr_of_resources.each do |first, second|
184
+ result.add_edge!(first, second) unless result.edge?(first, second) or result.edge?(second, first)
185
+ add_ordered_resources_to_result(first.resources, result)
186
+ end
187
+ end
188
+
189
+ def resources_without_dependencies(r=all_resources)
190
+ r.reject {|a| !a.dependencies.empty? }
191
+ end
192
+
193
+ def resources_with_dependencies(r=all_resources)
194
+ r - resources_without_dependencies(r)
172
195
  end
173
196
 
174
197
  # All the dependencies that are required by this resource
@@ -27,7 +27,9 @@ module PoolParty
27
27
  end
28
28
 
29
29
  def before_compile
30
- validate_all_resources
30
+ add_monitoring_stack_if_needed
31
+
32
+ validate_all_resources unless ENV["POOLPARTY_NO_VALIDATION"]
31
33
  end
32
34
 
33
35
  # Freeze the cloud_name so we can't modify it at all, set the plugin_directory
@@ -48,8 +50,8 @@ module PoolParty
48
50
  # returns an instance of Keypair
49
51
  # You can pass either a filename which will be searched for in ~/.ec2/ and ~/.ssh/
50
52
  # Or you can pass a full filepath
51
- def keypair(n=nil)
52
- @keypair ||= Keypair.new(n)
53
+ def keypair(n=nil, extra_paths=[])
54
+ @keypair ||= Keypair.new(n, extra_paths)
53
55
  end
54
56
 
55
57
  # Declare the CloudProvider for a cloud
@@ -58,10 +60,13 @@ module PoolParty
58
60
  return @cloud_provider if @cloud_provider
59
61
  self.cloud_provider_name = provider_symbol
60
62
  cloud_provider(o, &block)
63
+ cloud_provider.keypair(keypair.full_filepath)
61
64
  end
62
65
 
63
66
  # Cloud provider methods
64
- def nodes(o={}); delayed_action {cloud_provider.nodes(o)}; end
67
+ def nodes(o={})
68
+ delayed_action {cloud_provider.nodes(o).collect{|n| n.cloud = self; n}};
69
+ end
65
70
  def run_instance(o={}); cloud_provider.run_instance(o);end
66
71
  def terminate_instance!(o={}); cloud_provider.terminate_instance!(o);end
67
72
  def describe_instances(o={}); cloud_provider.describe_instances(o);end
@@ -87,7 +92,7 @@ module PoolParty
87
92
 
88
93
  # 1.) Launches a new instance,
89
94
  # 2.) Waits for the instance to get an ip address
90
- # 3.) Waits for port 22 to be open
95
+ # 3.) Waits for port ssh_port to be open
91
96
  # 4.) Calls call_after_launch_instance callbacks
92
97
  # 5.) Executes passed &block, if any
93
98
  # 6.) Returns the new instance object
@@ -98,7 +103,7 @@ module PoolParty
98
103
  instance.cloud = self
99
104
  @instance = instance
100
105
  #wait for an ip and then wait for ssh port, then configure instance
101
- if instance.wait_for_public_ip(timeout) && instance.wait_for_port(22, :timeout=>timeout)
106
+ if instance.wait_for_public_ip(timeout) && instance.wait_for_port(ssh_port, :timeout=>timeout)
102
107
  callback :after_launch_instance
103
108
  instance.callback :before_bootstrap
104
109
  instance.bootstrap!
@@ -109,7 +114,7 @@ module PoolParty
109
114
  block.call(instance) if block
110
115
  instance
111
116
  else
112
- raise StandardError.new("Instance port 22 not available")
117
+ raise StandardError.new("Instance port #{ssh_port} not available")
113
118
  end
114
119
  instance.refresh!
115
120
  instance
@@ -177,16 +182,31 @@ module PoolParty
177
182
  end
178
183
  end
179
184
 
185
+ # Add the monitoring stack
186
+ def add_monitoring_stack_if_needed
187
+ if monitors.size > 0
188
+
189
+ run_in_context do
190
+ %w(collectd hermes).each do |m|
191
+ self.send m.to_sym
192
+ end
193
+ end
194
+
195
+ end
196
+ end
197
+
180
198
  # Take the cloud's resources and compile them down using
181
199
  # the defined (or the default dependency_resolver, chef)
182
200
  def compile(caller=nil)
183
201
  callback :before_compile
202
+ cloud_provider.before_compile(self)
184
203
  FileUtils.mkdir_p tmp_path unless File.directory?(tmp_path)
185
204
  ddputs <<-EOE
186
205
  Compiling cloud #{self.name} to #{tmp_path/"etc"/"#{dependency_resolver_name}"}
187
206
  number of resources: #{ordered_resources.size}
188
207
  EOE
189
208
  out = dependency_resolver.compile_to(ordered_resources, tmp_path/"etc"/"#{dependency_resolver_name}", caller)
209
+ cloud_provider.after_compile(self)
190
210
  callback :after_compile
191
211
  out
192
212
  end
@@ -215,14 +235,14 @@ Compiling cloud #{self.name} to #{tmp_path/"etc"/"#{dependency_resolver_name}"}
215
235
  # vote_for(:expand) if v > 0.8
216
236
  # end
217
237
  def monitor(monitor_symbol, &block)
218
- monitors[monitor_symbol] ||= PoolParty::Monitor.new(monitor_symbol, &block)
238
+ monitors[monitor_symbol.to_sym] ||= PoolParty::Monitor.new(monitor_symbol, &block)
219
239
  end
220
240
 
221
241
  # Run the monitor logic
222
242
  def run_monitor(monitor_name, value)
223
243
  mon = monitors[monitor_name.to_sym]
224
244
  if mon
225
- mon.run(value.to_f)
245
+ mon.run(value)
226
246
  else
227
247
  "unhandled monitor"
228
248
  end
@@ -233,6 +253,14 @@ Compiling cloud #{self.name} to #{tmp_path/"etc"/"#{dependency_resolver_name}"}
233
253
  @monitors ||= {}
234
254
  end
235
255
 
256
+ def monitor_format(mon_name, meth=nil, &block)
257
+ if monitors.has_key?(mon_name.to_sym)
258
+ monitors[mon_name.to_sym].format(meth, &block)
259
+ else
260
+ raise PoolPartyError.create("MonitorsFormattingError", "You created a monitor format for an unknown monitor. Please check and try again!")
261
+ end
262
+ end
263
+
236
264
  ##### Internal methods #####
237
265
  # Methods that only the cloud itself will use
238
266
  # and thus are private
@@ -263,17 +291,23 @@ Compiling cloud #{self.name} to #{tmp_path/"etc"/"#{dependency_resolver_name}"}
263
291
  if resources_graph.cyclic?
264
292
  cycles = []
265
293
 
266
- resources_graph.edges.each do |edge|
267
- if resources_graph.adjacent?(edge.source, edge.target) && resources_graph.adjacent?(edge.target, edge.source) && !cycles.include?(edge.source)
268
- cycles << "#{edge.source.class}(#{edge.source.name}) depends on #{edge.target.class}(#{edge.target.name})"
269
- end
294
+ cycles = resources_graph.find_cycle
295
+ cycle_string = cycles.map do |k,v|
296
+ "#{k} -> #{v}"
270
297
  end
298
+
299
+ filepath = "/tmp"
300
+ format = "png"
301
+ dotpath = "#{filepath}/dot.#{format}"
302
+ resources_graph.write_to_graphic_file(format, filepath)
303
+
304
+ `open #{dotpath}`
271
305
  msg =<<-EOE
272
306
 
273
307
  Your resource graph is cyclic. Two resources depend on each other, Cannot decide which resource
274
308
  to go first. Dying instead. Correct this and then try again.
275
309
 
276
- #{cycles.join("\n ")}
310
+ #{dotpath}
277
311
 
278
312
  Hint: You can see the resource graph by generating it with:
279
313
  cloud compile -g name
@@ -298,4 +332,25 @@ Compiling cloud #{self.name} to #{tmp_path/"etc"/"#{dependency_resolver_name}"}
298
332
  end
299
333
 
300
334
  end
301
- end
335
+ end
336
+
337
+ module GRATR
338
+ class Digraph
339
+
340
+ # Crappy n*n
341
+ def find_cycle(from=self)
342
+ return [] unless cyclic?
343
+ cyclic_cycle = []
344
+ forward_edge = Proc.new {|e| }
345
+ back_edge = Proc.new do |b|
346
+ cyclic_cycle = dfs_tree_from_vertex(b)
347
+ end
348
+ from.dfs({
349
+ :forward_edge => forward_edge,
350
+ :back_edge => back_edge
351
+ })
352
+ cyclic_cycle
353
+ end
354
+
355
+ end
356
+ end