logical-construct 0.0.5 → 0.1.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 (77) hide show
  1. data/bin/flight-deck +3 -0
  2. data/doc/DESIGN +48 -0
  3. data/doc/EC2-baking-notes +70 -0
  4. data/doc/ExampleStartupRakefile +152 -0
  5. data/doc/ExampleTargetRakefile +4 -0
  6. data/doc/TODO +148 -0
  7. data/doc/Vb-EC2-translation-notes +96 -0
  8. data/doc/hating-chef +32 -0
  9. data/lib/logical-construct/archive-tasks.rb +307 -0
  10. data/lib/logical-construct/ground-control.rb +4 -1
  11. data/lib/logical-construct/ground-control/build-plan.rb +95 -0
  12. data/lib/logical-construct/ground-control/core.rb +1 -1
  13. data/lib/logical-construct/ground-control/generate-manifest.rb +67 -0
  14. data/lib/logical-construct/ground-control/provision.rb +73 -168
  15. data/lib/logical-construct/ground-control/run-on-target.rb +1 -1
  16. data/lib/logical-construct/ground-control/setup.rb +1 -4
  17. data/lib/logical-construct/ground-control/setup/copy-files.rb +2 -2
  18. data/lib/logical-construct/ground-control/tools.rb +66 -0
  19. data/lib/logical-construct/node-client.rb +112 -0
  20. data/lib/logical-construct/plan.rb +2 -0
  21. data/lib/logical-construct/plan/core.rb +45 -0
  22. data/lib/logical-construct/plan/standalone-bundle.rb +80 -0
  23. data/lib/logical-construct/port-open-check.rb +41 -0
  24. data/lib/logical-construct/protocol.rb +2 -0
  25. data/lib/logical-construct/protocol/plan-validation.rb +46 -0
  26. data/lib/logical-construct/protocol/ssh-tunnel.rb +127 -0
  27. data/lib/logical-construct/protocol/vocabulary.rb +8 -0
  28. data/lib/logical-construct/target/Implement.rake +8 -0
  29. data/lib/logical-construct/target/command-line.rb +90 -0
  30. data/lib/logical-construct/target/flight-deck.rb +341 -0
  31. data/lib/logical-construct/target/implementation.rb +33 -0
  32. data/lib/logical-construct/target/plan-records.rb +317 -0
  33. data/lib/logical-construct/target/resolution-server.rb +153 -0
  34. data/lib/logical-construct/target/{unpack-cookbook.rb → unpack-plan.rb} +8 -4
  35. data/lib/logical-construct/template-file.rb +41 -0
  36. data/lib/templates/Rakefile.erb +8 -0
  37. data/spec/ground-control/smoke-test.rb +8 -7
  38. data/spec/node_resolution.rb +62 -0
  39. data/spec/target/plan-records.rb +142 -0
  40. data/spec/target/provisioning.rb +21 -0
  41. data/spec_help/file-sandbox.rb +12 -6
  42. data/spec_help/fixtures/Manifest +1 -0
  43. data/spec_help/fixtures/source/one.tbz +1 -0
  44. data/spec_help/fixtures/source/three.tbz +1 -0
  45. data/spec_help/fixtures/source/two.tbz +1 -0
  46. data/spec_help/spec_helper.rb +5 -7
  47. metadata +165 -72
  48. data/lib/logical-construct/ground-control/setup/build-files.rb +0 -93
  49. data/lib/logical-construct/ground-control/setup/create-construct-directory.rb +0 -22
  50. data/lib/logical-construct/ground-control/setup/install-init.rb +0 -32
  51. data/lib/logical-construct/resolving-task.rb +0 -141
  52. data/lib/logical-construct/satisfiable-task.rb +0 -87
  53. data/lib/logical-construct/target.rb +0 -4
  54. data/lib/logical-construct/target/chef-solo.rb +0 -37
  55. data/lib/logical-construct/target/platforms.rb +0 -51
  56. data/lib/logical-construct/target/platforms/aws.rb +0 -8
  57. data/lib/logical-construct/target/platforms/default/chef-config.rb +0 -134
  58. data/lib/logical-construct/target/platforms/default/resolve-configuration.rb +0 -44
  59. data/lib/logical-construct/target/platforms/default/volume.rb +0 -11
  60. data/lib/logical-construct/target/platforms/virtualbox.rb +0 -8
  61. data/lib/logical-construct/target/platforms/virtualbox/volume.rb +0 -15
  62. data/lib/logical-construct/target/provision.rb +0 -36
  63. data/lib/logical-construct/target/sinatra-resolver.rb +0 -99
  64. data/lib/logical-construct/testing/resolve-configuration.rb +0 -32
  65. data/lib/logical-construct/testing/resolving-task.rb +0 -15
  66. data/lib/templates/chef.rb.erb +0 -9
  67. data/lib/templates/construct.init.d.erb +0 -18
  68. data/lib/templates/resolver/finished.html.erb +0 -1
  69. data/lib/templates/resolver/index.html.erb +0 -17
  70. data/lib/templates/resolver/task-file-form.html.erb +0 -6
  71. data/lib/templates/resolver/task-form.html.erb +0 -6
  72. data/spec/resolution.rb +0 -147
  73. data/spec/target/chef-config.rb +0 -67
  74. data/spec/target/chef-solo.rb +0 -55
  75. data/spec/target/platforms.rb +0 -36
  76. data/spec/target/smoke-test.rb +0 -45
  77. data/spec_help/ungemmer.rb +0 -36
@@ -0,0 +1,33 @@
1
+ require 'mattock/tasklib'
2
+
3
+ module LogicalConstruct
4
+ module Target
5
+ class Implementation < ::Mattock::Tasklib
6
+ def self.task_list
7
+ [
8
+ :preflight, #Is this node acceptable?
9
+ :settings, #Shared in-memory configuration for the overall setup
10
+ :setup, #write configuration to disk for implementation tools (e.g. Chef, Puppet, apt-get)
11
+ :files, #deliver files from plan to filesystem
12
+ :execute, #run implementation tools
13
+ :configure, #install application configuration files (e.g. /etc/apache/http.conf)
14
+ :complete #All done - depends on everything.
15
+ ]
16
+ end
17
+
18
+ default_namespace :construct
19
+
20
+ def define
21
+ in_namespace do
22
+ task_spine( *self.class.task_list )
23
+
24
+ self.class.task_list.each do |taskname|
25
+ task taskname do
26
+ puts "*** Implementation stage complete: #{taskname}"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,317 @@
1
+ require 'fileutils'
2
+ require 'thread'
3
+ require 'logical-construct/protocol'
4
+
5
+ module LogicalConstruct
6
+ module ResolutionServer
7
+ class PlanRecords
8
+ include Enumerable
9
+
10
+ Record = Struct.new(:name, :filehash)
11
+ Directories = Struct.new(:delivered, :current, :stored)
12
+
13
+ def initialize
14
+ @record_lock = Mutex.new
15
+ @records = []
16
+ @directories = Directories.new(nil, nil, nil)
17
+ end
18
+ attr_reader :directories
19
+
20
+ def reset!
21
+ each do |record|
22
+ record.cancel!
23
+ end
24
+ @records.clear
25
+ clear_files(directories.delivered)
26
+ clear_files(directories.current)
27
+ end
28
+ alias reset reset!
29
+
30
+ def clear_files(directory)
31
+ dirpath = Pathname.new(directory)
32
+ dirpath.mkpath
33
+ dirpath.each_child do |delivered|
34
+ delivered.delete
35
+ end
36
+ end
37
+
38
+ def total_state
39
+ return "no-plans-yet" if @records.empty?
40
+ return "resolved" if @records.all?(&:resolved?)
41
+ return "unresolved"
42
+ end
43
+
44
+ def find(name)
45
+ record = @records.find{|record| record.name == name}
46
+ end
47
+
48
+ def each(&block)
49
+ @records.each(&block)
50
+ end
51
+
52
+ def add(name, hash)
53
+ record = nil
54
+ @record_lock.synchronize do
55
+ if @records.any?{|record| record.name == name}
56
+ raise "Cannot add a second plan requirement for #{name}"
57
+ end
58
+ record = States::Unresolved.new(self, Record.new(name, hash))
59
+ @records << record
60
+ end
61
+ record.resolve
62
+
63
+ return find(name)
64
+ end
65
+
66
+ def change(old_state, new_state_class)
67
+ unless old_state.alive?
68
+ raise "Tried to change from old invalid state: #{old_state}"
69
+ end
70
+ new_state = new_state_class.new(self, old_state.record)
71
+
72
+ @record_lock.synchronize do
73
+ @records.delete(old_state)
74
+ @records << new_state
75
+ old_state.cancel!
76
+ end
77
+ new_state.enter
78
+
79
+ return new_state
80
+ end
81
+ end
82
+
83
+ module States
84
+ class PlanState
85
+ include Protocol::PlanValidation
86
+ include FileTest
87
+
88
+ def initialize(records, record)
89
+ @records, @record = records, record
90
+ end
91
+ attr_reader :record
92
+
93
+ def inspect
94
+ "#<#{self.class.name}:#{"0x%0x"%object_id} #{name||"dead"}:#{filehash}>"
95
+ end
96
+
97
+ def ==(other)
98
+ return true if self.equal?(other)
99
+ return false if !self.alive? or !other.alive?
100
+ return (other.class.equal?(self.class) and
101
+ other.name.equal?(self.name) and
102
+ other.filehash.equal?(self.filehash))
103
+ end
104
+
105
+
106
+ def name
107
+ return nil unless alive?
108
+ @record.name
109
+ end
110
+
111
+ def filehash
112
+ return nil unless alive?
113
+ @record.filehash
114
+ end
115
+
116
+ def enter
117
+ end
118
+
119
+ def cancel!
120
+ @record = nil
121
+ end
122
+
123
+ def alive?
124
+ !@record.nil?
125
+ end
126
+
127
+ def exists?(path)
128
+ super(realpath(path))
129
+ end
130
+
131
+ def delivered_plans_dir
132
+ @records.directories.delivered
133
+ end
134
+
135
+ def current_plans_dir
136
+ @records.directories.current
137
+ end
138
+
139
+ def stored_plans_dir
140
+ @records.directories.stored
141
+ end
142
+
143
+ def resolved?
144
+ false
145
+ end
146
+
147
+ def can_receive?
148
+ false
149
+ end
150
+
151
+ def change(next_state)
152
+ @records.change(self, next_state)
153
+ end
154
+
155
+ def received_path
156
+ @received_path ||= Pathname.new(delivered_plans_dir) + name
157
+ end
158
+
159
+ def storage_path_for(actual_hash)
160
+ @stored_path ||= Pathname.new(stored_plans_dir) + [name, actual_hash].join(".")
161
+ end
162
+
163
+ def current_path
164
+ @current_path ||= Pathname.new(current_plans_dir) + name
165
+ end
166
+
167
+ def resolve
168
+ warn "Cannot resolve plan in current state: #{state}"
169
+ end
170
+
171
+ def receive
172
+ warn "Cannot receive file in current state: #{state}"
173
+ end
174
+
175
+ def state
176
+ self.class.name.sub(/.*::/,'').downcase
177
+ end
178
+
179
+ def join
180
+ return
181
+ end
182
+ end
183
+
184
+ class Unresolved < PlanState
185
+ def can_receive?
186
+ true
187
+ end
188
+
189
+ def store_received_file(actual_hash)
190
+ stored_path = storage_path_for(actual_hash)
191
+ FileUtils.mkdir_p(stored_plans_dir)
192
+ unless exists?(stored_path)
193
+ FileUtils.mv(received_path, stored_path)
194
+ FileUtils.symlink(stored_path, received_path)
195
+ end
196
+ end
197
+
198
+ def enter
199
+ unless alive?
200
+ super
201
+ return false
202
+ end
203
+
204
+ unless exists?(received_path)
205
+ return false
206
+ end
207
+
208
+ actual_hash = file_checksum(received_path)
209
+
210
+ store_received_file(actual_hash)
211
+
212
+ if actual_hash == filehash
213
+ FileUtils.cp(received_path.readlink, current_path.to_s)
214
+ change(Resolved)
215
+ else
216
+ received_path.delete
217
+ return false
218
+ end
219
+ end
220
+ alias receive enter
221
+
222
+ def resolve
223
+ unless alive?
224
+ super
225
+ return false
226
+ end
227
+ change(Resolving)
228
+ end
229
+ end
230
+
231
+ class Resolving < PlanState
232
+ def cancel!
233
+ unless @thread.nil?
234
+ if @working
235
+ @thread.kill
236
+ end
237
+ end
238
+ super
239
+ end
240
+
241
+ def join
242
+ super if @thread.nil?
243
+ @thread.join
244
+ end
245
+
246
+ def enter
247
+ @working = true
248
+ @thread = Thread.new do
249
+ ResolutionMethods.run_all(self)
250
+ @working = false
251
+ change(Unresolved)
252
+ end
253
+ end
254
+ end
255
+
256
+ class Resolved < PlanState
257
+ def resolved?
258
+ true
259
+ end
260
+ end
261
+ end
262
+
263
+ module ResolutionMethods
264
+ class << self
265
+ def resolution_methods
266
+ @methods ||= []
267
+ end
268
+
269
+ def add_method(name, klass)
270
+ resolution_methods << [name, klass]
271
+ end
272
+
273
+ def run_all(state)
274
+ resolution_methods.each do |name, klass|
275
+ klass.new(state).run
276
+ end
277
+ end
278
+ end
279
+
280
+ class ResolutionMethod
281
+ include Protocol::PlanValidation
282
+ include FileTest
283
+
284
+ def self.register(name)
285
+ ResolutionMethods.add_method(name, self)
286
+ end
287
+
288
+ def exists?(path)
289
+ super(realpath(path))
290
+ end
291
+
292
+ def initialize(state)
293
+ @state = state
294
+ end
295
+ attr_reader :state
296
+
297
+ def run
298
+
299
+ end
300
+ end
301
+
302
+ class LocalStorage < ResolutionMethod
303
+ register :local_storage
304
+
305
+ def run
306
+ stored_path = state.storage_path_for(state.record.filehash)
307
+ received_path = state.received_path
308
+
309
+ if exists?(stored_path) and not (exists?(received_path) or symlink?(received_path))
310
+ FileUtils.symlink(stored_path, received_path)
311
+ end
312
+ end
313
+ end
314
+ end
315
+
316
+ end
317
+ end
@@ -0,0 +1,153 @@
1
+ require 'rdf/vocab/skos'
2
+ require 'roadforest/server'
3
+ require 'logical-construct/protocol'
4
+ require 'logical-construct/target/plan-records'
5
+
6
+ module LogicalConstruct
7
+ module ResolutionServer
8
+ class Application < ::RoadForest::Application
9
+ def setup
10
+ router.add :root, [], :read_only, Models::Navigation
11
+ router.add :status, ["status"], :read_only, Models::ServerStatus
12
+ router.add :manifest, ["manifest"], :leaf, Models::ServerManifest
13
+ router.add :unresolved_plans, ["unresolved_plans"], :parent, Models::UnresolvedPlansList
14
+ router.add :full_plans, ["full_plans"], :parent, Models::FullPlansList
15
+ router.add :plan, ["plans",'*'], :leaf, Models::Plan
16
+ router.add :file_content, ["files","*"], :leaf, Models::PlanContent
17
+ end
18
+ end
19
+
20
+ class ServicesHost < ::RoadForest::Application::ServicesHost
21
+ attr_accessor :plan_records
22
+
23
+ def initialize
24
+ @plan_records = PlanRecords.new
25
+ end
26
+
27
+ def destination_dir
28
+ plan_records.directories.delivered
29
+ end
30
+ end
31
+
32
+ module Models
33
+ class Navigation < RoadForest::RDFModel
34
+ def exists?
35
+ true
36
+ end
37
+
38
+ def update(graph)
39
+ return false
40
+ end
41
+
42
+ def nav_entry(graph, name, path)
43
+ graph.add_node([:skos, :hasTopConcept], "#" + name) do |entry|
44
+ entry[:rdf, :type] = [:skos, "Concept"]
45
+ entry[:skos, :prefLabel] = name
46
+ entry[:foaf, "page"] = path
47
+ end
48
+ end
49
+
50
+ def fill_graph(graph)
51
+ graph[:rdf, "type"] = [:skos, "ConceptScheme"]
52
+ nav_entry(graph, "Server Manifest", path_for(:manifest))
53
+ nav_entry(graph, "Unresolved Plans", path_for(:unresolved_plans))
54
+ nav_entry(graph, "All Plans", path_for(:full_plans))
55
+ nav_entry(graph, "Current Status", path_for(:status))
56
+ end
57
+ end
58
+
59
+ class ServerStatus < RoadForest::RDFModel
60
+ def data
61
+ services.plan_records.total_state
62
+ end
63
+
64
+ def fill_graph(graph)
65
+ graph[[:lc, "node-state"]] = data
66
+ end
67
+ end
68
+
69
+ class ServerManifest < RoadForest::RDFModel
70
+ def fill_graph(graph)
71
+ graph.add_list(:lc, "plans") do |list|
72
+ services.plan_records.each do |record|
73
+ list << path_for(:plan, '*' => record.name)
74
+ end
75
+ end
76
+ end
77
+
78
+ def graph_update(graph)
79
+ services.plan_records.reset!
80
+
81
+ graph[:lc, "plans"].as_list.each do |plan|
82
+ services.plan_records.add(plan.first(:lc, "name"), plan.first(:lc, "digest"))
83
+ end
84
+
85
+ new_graph
86
+ end
87
+ end
88
+
89
+ class FullPlansList < RoadForest::RDFModel
90
+ def exists?
91
+ true
92
+ end
93
+
94
+ def update(graph)
95
+ end
96
+
97
+ def add_child(graph)
98
+ services.plan_records.add(graph.first(:lc, "name"), graph.first(:lc, "digest"))
99
+ end
100
+
101
+ def plan_records
102
+ services.plan_records
103
+ end
104
+
105
+ def fill_graph(graph)
106
+ graph.add_list(:lc, "plans") do |list|
107
+ plan_records.each do |record|
108
+ list << path_for(:plan, '*' => record.name)
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ class UnresolvedPlansList < FullPlansList
115
+ def plan_records
116
+ #recheck resolution?
117
+ services.plan_records.find_all{|record| !record.resolved?}
118
+ end
119
+ end
120
+
121
+ class PlanContent < RoadForest::BlobModel
122
+ add_type "text/plain", TypeHandlers::Handler.new
123
+ add_type "application/octet-stream", TypeHandlers::Handler.new
124
+ add_type "application/x-gtar-compressed", TypeHandlers::Handler.new
125
+
126
+ def update(file)
127
+ name = params.remainder
128
+ record = services.plan_records.find(name)
129
+ raise "Unexpected file: #{name}" if record.nil?
130
+ raise "Plan already resolved: #{name}" unless record.can_receive?
131
+
132
+ super(file)
133
+
134
+ record.receive
135
+
136
+ nil
137
+ end
138
+ end
139
+
140
+ class Plan < RoadForest::RDFModel
141
+ def data
142
+ @data = services.plan_records.find(params.remainder)
143
+ end
144
+
145
+ def fill_graph(graph)
146
+ graph[[:lc, "name"]] = data.name
147
+ graph[[:lc, "digest"]] = data.filehash
148
+ graph[[:lc, "contents"]] = path_for(:file_content)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end