vagrant-node 0.0.2

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.
@@ -0,0 +1,220 @@
1
+ module RestRoutes
2
+ #FIXME Figure how to manage remote port
3
+ # REMOTE_PORT = 3333
4
+
5
+ class RouteManager
6
+
7
+ def self.box_list_route
8
+ BOX_LIST_ROUTE
9
+ end
10
+
11
+ def self.box_delete_route
12
+ BOX_DELETE_ROUTE
13
+ end
14
+
15
+ def self.box_add_route
16
+ BOX_ADD_ROUTE
17
+ end
18
+
19
+ def self.vm_up_route
20
+ VM_UP_ROUTE
21
+ end
22
+
23
+ def self.vm_suspend_route
24
+ VM_SUSPEND_ROUTE
25
+ end
26
+
27
+ def self.vm_resume_route
28
+ VM_RESUME_ROUTE
29
+ end
30
+
31
+ def self.vm_halt_route
32
+ VM_HALT_ROUTE
33
+ end
34
+
35
+ def self.vm_destroy_route
36
+ VM_DESTROY_ROUTE
37
+ end
38
+
39
+ def self.vm_status_route
40
+ VM_STATUS_ROUTE
41
+ end
42
+
43
+ def self.vm_provision_route
44
+ VM_PROVISION_ROUTE
45
+ end
46
+
47
+ def self.vm_status_all_route
48
+ VM_STATUS_ALL_ROUTE
49
+ end
50
+
51
+ def self.vm_sshconfig_route
52
+ SSH_CONFIG_ROUTE
53
+ end
54
+
55
+ def self.snapshots_all_route
56
+ SNAPSHOTS_ALL_ROUTE
57
+ end
58
+
59
+ def self.vm_snapshots_route
60
+ VM_SNAPSHOTS_ROUTE
61
+ end
62
+
63
+ def self.vm_snapshot_take_route
64
+ VM_SNAPSHOT_TAKE_ROUTE
65
+ end
66
+
67
+ def self.vm_snapshot_restore_route
68
+ VM_SNAPSHOT_RESTORE_ROUTE
69
+ end
70
+
71
+ def self.node_backup_log_route
72
+ NODE_BACKUP_LOG_ROUTE
73
+ end
74
+
75
+ def self.vm_backup_log_route
76
+ VM_BACKUP_LOG_ROUTE
77
+ end
78
+
79
+
80
+ def self.box_list_url(host,port)
81
+ "http://#{host}:#{port}#{box_list_route}"
82
+ end
83
+
84
+ def self.box_add_url(host,port)
85
+ "http://#{host}:#{port}#{box_add_route}"
86
+ end
87
+
88
+ def self.box_delete_url(host,port,box,provider)
89
+
90
+ url=String.new(box_delete_route)
91
+ url[":box"]=box
92
+ url[":provider"]=provider
93
+ url="http://#{host}:#{port}#{url}"
94
+
95
+ url
96
+
97
+ end
98
+
99
+
100
+
101
+ def self.vm_up_url(host,port)
102
+ "http://#{host}:#{port}#{vm_up_route}"
103
+ end
104
+
105
+ def self.vm_halt_url(host,port)
106
+ "http://#{host}:#{port}#{vm_halt_route}"
107
+ end
108
+
109
+ def self.vm_destroy_url(host,port)
110
+ "http://#{host}:#{port}#{vm_destroy_route}"
111
+ end
112
+
113
+ def self.vm_suspend_url(host,port)
114
+ "http://#{host}:#{port}#{vm_suspend_route}"
115
+ end
116
+
117
+ def self.vm_resume_url(host,port)
118
+ "http://#{host}:#{port}#{vm_resume_route}"
119
+ end
120
+
121
+ def self.vm_provision_url(host,port)
122
+ "http://#{host}:#{port}#{vm_provision_route}"
123
+ end
124
+
125
+
126
+
127
+
128
+ def self.vm_status_url(host,port,vmname=nil)
129
+ url="http://#{host}:#{port}#{vm_status_all_route}"
130
+
131
+ if (vmname!=nil)
132
+ url=String.new(vm_status_route)
133
+ url[":vm"]=vmname
134
+ url="http://#{host}:#{port}#{url}"
135
+ end
136
+
137
+ url
138
+
139
+ end
140
+
141
+ def self.vm_sshconfig_url(host,port,vmname)
142
+ url=String.new(vm_sshconfig_route)
143
+ url[":vm"]=vmname
144
+
145
+ "http://#{host}:#{port}#{url}"
146
+
147
+ end
148
+
149
+ def self.snapshot_list_url(host,port,vmname=nil)
150
+ url="http://#{host}:#{port}#{snapshots_all_route}"
151
+
152
+ if (vmname!=nil)
153
+ url=String.new(vm_snapshots_route)
154
+ url[":vm"]=vmname
155
+ url="http://#{host}:#{port}#{url}"
156
+ end
157
+
158
+ url
159
+
160
+ end
161
+
162
+ def self.vm_snapshot_take_url(host,port,vmname)
163
+ url=String.new(vm_snapshot_take_route)
164
+ url[":vm"]=vmname
165
+ url="http://#{host}:#{port}#{url}"
166
+
167
+ url
168
+
169
+ end
170
+
171
+ def self.vm_snapshot_restore_url(host,port,vmname)
172
+ url=String.new(vm_snapshot_restore_route)
173
+ url[":vm"]=vmname
174
+ url="http://#{host}:#{port}#{url}"
175
+
176
+ url
177
+
178
+ end
179
+
180
+ def self.backup_log_url(host,port,vmname=nil)
181
+ url="http://#{host}:#{port}#{node_backup_log_route}"
182
+
183
+ if (vmname!=nil)
184
+ url=String.new(vm_backup_log_route)
185
+ url[":vm"]=vmname
186
+ url="http://#{host}:#{port}#{url}"
187
+ end
188
+
189
+ url
190
+
191
+ end
192
+
193
+
194
+
195
+ private
196
+ BOX_LIST_ROUTE = "/api/box/list"
197
+ BOX_DELETE_ROUTE = "/api/box/:box/:provider/delete"
198
+ BOX_ADD_ROUTE = "/api/box/add"
199
+
200
+ VM_UP_ROUTE = "/api/vm/up"
201
+ VM_HALT_ROUTE = "/api/vm/halt"
202
+ VM_DESTROY_ROUTE = "/api/vm/destroy"
203
+ VM_SUSPEND_ROUTE = "/api/vm/suspend"
204
+ VM_RESUME_ROUTE = "/api/vm/resume"
205
+ VM_PROVISION_ROUTE = "/api/vm/provision"
206
+ VM_STATUS_ALL_ROUTE = "/api/vm/status"
207
+ VM_STATUS_ROUTE = "/api/vm/:vm/status"
208
+ SSH_CONFIG_ROUTE = "/api/vm/:vm/sshconfig"
209
+
210
+ SNAPSHOTS_ALL_ROUTE = "/api/vm/snapshots"
211
+ VM_SNAPSHOTS_ROUTE = "/api/vm/:vm/snapshots"
212
+ VM_SNAPSHOT_TAKE_ROUTE = "/api/vm/:vm/take"
213
+ VM_SNAPSHOT_RESTORE_ROUTE = "/api/vm/:vm/restore"
214
+
215
+ VM_BACKUP_LOG_ROUTE = "/api/vm/:vm/backuplog"
216
+ NODE_BACKUP_LOG_ROUTE = "/api/backuplog"
217
+
218
+ end
219
+
220
+ end
@@ -0,0 +1,633 @@
1
+
2
+ require 'vagrant'
3
+ require 'vagrant-node/actions/snapshot'
4
+ require 'vagrant-node/dbmanager'
5
+ #require 'sambal'
6
+
7
+ module Vagrant
8
+ module Node
9
+ class ClientController
10
+
11
+ ################################################################
12
+ ####################### BOX LIST METHOD #######################
13
+ ################################################################
14
+ def self.listboxes
15
+
16
+ ensure_environment
17
+
18
+ boxes = @env.boxes.all.sort
19
+
20
+ fboxes = Array.new
21
+ boxes.each do |name, provider|
22
+ fboxes << {"name" => name,"provider" => provider}
23
+ end
24
+
25
+ fboxes
26
+
27
+ end
28
+
29
+ ################################################################
30
+ ####################### BOX DELETE METHOD #####################
31
+ ################################################################
32
+ def self.box_delete(box,provider)
33
+
34
+ ensure_environment
35
+
36
+ boxes = []
37
+
38
+ box = @env.boxes.find(box,provider.to_sym)
39
+
40
+ if (box)
41
+ boxes << box.name
42
+ box.destroy!
43
+ end
44
+
45
+ boxes
46
+
47
+ end
48
+
49
+ ################################################################
50
+ ######################## BOX ADD METHOD #######################
51
+ ################################################################
52
+ def self.box_add(box,url,user="guest",pass="--no-pass")
53
+
54
+ ensure_environment
55
+
56
+ boxes = []
57
+
58
+ #TODO
59
+
60
+ # Get the provider if one was set
61
+ provider = nil
62
+ # provider = options[:provider].to_sym if options[:provider]
63
+
64
+ begin
65
+
66
+ uri = "\\\\155.54.190.227\\boxtmp\\boxes\\debian_squeeze_32.box"
67
+ #
68
+ # if uri=~ /^\\\\(.*?)\\(.*?)\\(.*?)$/
69
+ # puts "EL HOST ES #{$1}"
70
+ # puts "EL Share ES #{$2}"
71
+ # puts "EL PATH ES #{$3}"
72
+ # host = $1
73
+ # share = $2
74
+ # path = $3
75
+ #
76
+ # Getting and checking box file
77
+ # boxname=File.basename(path.gsub('\\',File::SEPARATOR))
78
+ #
79
+ # raise 'Box file format not supported' if File.extname(boxname)!=".box"
80
+ #
81
+ # samba = nil
82
+ # begin
83
+ # samba = Sambal::Client.new( :host => host,
84
+ # :share => share,
85
+ # :user => user,
86
+ # :password => pass)
87
+ #
88
+ #
89
+ #
90
+ # Get the tmp file name
91
+ # temp_path = @env.tmp_path.join("box" + Time.now.to_i.to_s)
92
+ #
93
+ #
94
+ # response = nil
95
+ #
96
+ # smbclient //155.54.190.227/boxtmp --no-pass -W WORKGROUP -U guest -p 445
97
+ # smbclient //155.54.190.227/boxtmp -D boxes -c "get debian_squeeze_321.box" -N
98
+ #
99
+ # command="smbclient //#{host}/#{share} -D #{dirlocation} -c \"get #{boxname}\" -U #{user} --no-pass"
100
+ #
101
+ #
102
+ # FIXME encontrar si existe algún tipo de notificación por
103
+ # interrupciónde la descarga
104
+ # FIXME a little hack beacuse in version 0.1.2 of sambal there is
105
+ # a timeout that close the download after 10 seconds
106
+ # def samba.ask(cmd)
107
+ # @i.printf("#{cmd}\n")
108
+ # response = @o.expect(/^smb:.*\\>/)[0]
109
+ # end
110
+ #
111
+ # response = samba.get(path, temp_path.to_s)
112
+ # FIXME DELETE
113
+ # pp response.inspect
114
+ #
115
+ # raise response.message if !response.success?
116
+ #
117
+ # if response.success?
118
+ # File download succesfully
119
+ # added_box = nil
120
+ # begin
121
+ # provider=nil
122
+ # force = true
123
+ # added_box = @env.boxes.add(temp_path,box,nil,force)
124
+ # boxes << {:name=>box,:provider=>added_box.provider.to_s}
125
+ # rescue Vagrant::Errors::BoxUpgradeRequired
126
+ # Upgrade the box
127
+ # env.boxes.upgrade(box)
128
+ #
129
+ # Try adding it again
130
+ # retry
131
+ # rescue Exception => e
132
+ # boxes = nil
133
+ # end
134
+ #
135
+ # end
136
+ #
137
+ # rescue Exception => e
138
+ # puts "EXCEPCION de descarga" if response
139
+ # puts "EXCEPCION de conexion" if !response
140
+ # puts e.message
141
+ # boxes=nil
142
+ # end
143
+ #
144
+ #
145
+ # Closing connection
146
+ # samba.close if samba
147
+ #
148
+ #
149
+ # Cleaning
150
+ # if temp_path && File.exist?(temp_path)
151
+ # File.unlink(temp_path)
152
+ # end
153
+ #
154
+ #
155
+ # else
156
+ # FIXME Ver qué poner en los parámetros de la llamada
157
+ provider=nil
158
+ force = true # Always overwrite box if exists
159
+ insecure = true #Don't validate SSL certs
160
+ #Calling original box add action
161
+ @env.action_runner.run(Vagrant::Action.action_box_add, {
162
+ :box_name => box,
163
+ :box_provider => provider,
164
+ :box_url => url,
165
+ :box_force => force,
166
+ :box_download_insecure => insecure,
167
+ })
168
+
169
+ # end
170
+
171
+ rescue =>e
172
+ puts e.message
173
+ end
174
+
175
+
176
+ boxes
177
+
178
+ end
179
+
180
+ ################################################################
181
+ ################## VIRTUAL MACHINE UP METHOD ##################
182
+ ################################################################
183
+ def self.vm_up(vmname)
184
+ ensure_environment
185
+
186
+ machine_names = []
187
+
188
+ begin
189
+
190
+ options = {}
191
+ options[:parallel] = true
192
+
193
+ #Launching machines
194
+ @env.batch(options[:parallel]) do |batch|
195
+ get_vms(vmname).each do |machine|
196
+ machine_names << machine.name
197
+ batch.action(machine, :up, options)
198
+ end
199
+ end
200
+
201
+
202
+ machine_names
203
+
204
+ rescue => e
205
+ # return nil
206
+ end
207
+
208
+ end
209
+
210
+ ################################################################
211
+ ################ VIRTUAL MACHINE DESTROY METHOD ###############
212
+ ################################################################
213
+
214
+ def self.vm_confirmed_destroy(vmname)
215
+ ensure_environment
216
+
217
+ machine_names = []
218
+
219
+ begin
220
+
221
+ get_vms(vmname).each do |machine|
222
+ machine_names << machine.name
223
+ machine.action(:destroy, :force_confirm_destroy => true)
224
+ end
225
+
226
+ machine_names
227
+
228
+ rescue => e
229
+ # return nil
230
+ end
231
+
232
+ end
233
+
234
+ ################################################################
235
+ ################# VIRTUAL MACHINE HALT METHOD #################
236
+ ################################################################
237
+ def self.vm_halt(vmname,force)
238
+ ensure_environment
239
+
240
+ machine_names = []
241
+
242
+ begin
243
+
244
+ get_vms(vmname).each do |machine|
245
+ machine_names << machine.name
246
+ machine.action(:halt, :force_halt => force)
247
+ end
248
+
249
+ machine_names
250
+
251
+ rescue => e
252
+ # return nil
253
+ end
254
+
255
+ end
256
+
257
+ ################################################################
258
+ ################# VIRTUAL MACHINE STATUS METHOD ###############
259
+ ################################################################
260
+ def self.vm_status(vmname)
261
+ ensure_environment
262
+
263
+ begin
264
+
265
+ status = Array.new
266
+
267
+ get_vms(vmname).each do |machine|
268
+
269
+ status << {"name" => machine.name.to_s,
270
+ "status" => machine.state.short_description,
271
+ "provider" => machine.provider_name}
272
+ end
273
+
274
+
275
+ status
276
+
277
+ rescue => e
278
+ puts e.message
279
+ # return nil
280
+ end
281
+
282
+ end
283
+
284
+ ################################################################
285
+ ################## VIRTUAL MACHINE SUSPEND METHOD ##################
286
+ ################################################################
287
+ def self.vm_suspend(vmname)
288
+ ensure_environment
289
+
290
+ machine_names = []
291
+
292
+ begin
293
+
294
+
295
+ #Suspendiing machines
296
+ get_vms(vmname).each do |machine|
297
+ machine_names << machine.name
298
+ machine.action(:suspend)
299
+ end
300
+
301
+
302
+ machine_names
303
+
304
+ rescue => e
305
+ puts e.message
306
+ # return nil
307
+ end
308
+
309
+ end
310
+
311
+ ################################################################
312
+ ################## VIRTUAL MACHINE RESUME METHOD ##################
313
+ ################################################################
314
+ def self.vm_resume(vmname)
315
+ ensure_environment
316
+
317
+ machine_names = []
318
+
319
+
320
+ begin
321
+
322
+ #Launching machines
323
+
324
+ get_vms(vmname).each do |machine|
325
+ machine_names << machine.name
326
+ machine.action(:resume)
327
+ end
328
+
329
+
330
+
331
+ machine_names
332
+
333
+ rescue => e
334
+ puts e.message
335
+ # return nil
336
+ end
337
+
338
+ end
339
+
340
+ ################################################################
341
+ ############ VIRTUAL MACHINE SNAPSHOT LIST METHOD #############
342
+ ################################################################
343
+ def self.vm_snapshots(vmname)
344
+ ensure_environment
345
+
346
+ begin
347
+
348
+ snapshots = {}
349
+
350
+ get_vms(vmname).each do |machine|
351
+
352
+ env =
353
+ {
354
+ :machine => machine,
355
+ :machine_action => SnapshotAction::LIST
356
+ }
357
+
358
+
359
+ res = @env.action_runner.run(SnapshotAction,env)
360
+
361
+ snapshots[machine.name.to_sym]=res[:snapshots_list]
362
+
363
+ end
364
+
365
+
366
+ snapshots
367
+
368
+ rescue => e
369
+ puts e.message
370
+ # return nil
371
+ end
372
+
373
+ end
374
+
375
+ ################################################################
376
+ ############ VIRTUAL MACHINE SNAPSHOT TAKE METHOD #############
377
+ ################################################################
378
+ def self.vm_snapshot_take(vmname,name,desc=" ")
379
+ ensure_environment
380
+
381
+ begin
382
+
383
+ get_vms(vmname).each do |machine|
384
+ env =
385
+ {
386
+ :machine => machine,
387
+ :machine_action => SnapshotAction::TAKE,
388
+ :snapshot_name => name,
389
+ :snapshot_desc => desc
390
+ }
391
+
392
+
393
+ res = @env.action_runner.run(SnapshotAction,env)
394
+
395
+ return res[:last_snapshot]
396
+
397
+ end
398
+
399
+ rescue => e
400
+ puts e.message
401
+ # return nil
402
+ end
403
+
404
+ end
405
+
406
+ ################################################################
407
+ ############ VIRTUAL MACHINE SNAPSHOT RESTORE METHOD #############
408
+ ################################################################
409
+ def self.vm_snapshot_restore(vmname,snapshot_id)
410
+ ensure_environment
411
+
412
+ begin
413
+
414
+ get_vms(vmname).each do |machine|
415
+ prev_state=machine.state.id
416
+ #First, ensure that the machine is in a proper state
417
+ #to restore the snapshot (save, poweroff)
418
+ machine.action(:suspend) if prev_state==:running
419
+
420
+ #Now the machine is ready for restoration
421
+ env =
422
+ {
423
+ :machine => machine,
424
+ :machine_action => SnapshotAction::RESTORE,
425
+ :snapshot_id => snapshot_id
426
+ }
427
+
428
+
429
+ res = @env.action_runner.run(SnapshotAction,env)
430
+
431
+ #Now restore the vm to the previous state if running
432
+ machine.action(:up) if prev_state==:running
433
+
434
+ return res[:restore_result]
435
+
436
+ end
437
+
438
+ rescue => e
439
+ puts e.message
440
+ # return nil
441
+ end
442
+
443
+ end
444
+
445
+
446
+
447
+ ################################################################
448
+ ################## VIRTUAL MACHINE PROVISION METHOD ##################
449
+ ################################################################
450
+ def self.vm_provision(vmname)
451
+ ensure_environment
452
+
453
+ machine_names = []
454
+
455
+
456
+ begin
457
+
458
+ #Provisioning
459
+ get_vms(vmname).each do |machine|
460
+ machine_names << machine.name
461
+ machine.action(:provision)
462
+ end
463
+
464
+
465
+
466
+ machine_names
467
+
468
+ rescue => e
469
+ puts e.message
470
+ # return nil
471
+ end
472
+
473
+ end
474
+
475
+
476
+ ################################################################
477
+ ################### VIRTUAL MACHINE SSHCONFIG## ###############
478
+ ################################################################
479
+ def self.vm_ssh_config(vmname)
480
+ ensure_environment
481
+
482
+
483
+ #Ensure vmname exists and it is not empty
484
+ return nil if vmname.empty?
485
+
486
+
487
+ begin
488
+ info = Array.new
489
+ get_vms(vmname).each do |machine|
490
+ info << machine.ssh_info
491
+ end
492
+
493
+ info[0]
494
+
495
+ rescue => e
496
+ puts e.message
497
+ # return nil
498
+ end
499
+
500
+ end
501
+
502
+ ################################################################
503
+ ############ VIRTUAL MACHINE BACKUP TAKE METHOD #############
504
+ ################################################################
505
+ def self.vm_snapshot_take_file(vmname)
506
+ ensure_environment
507
+
508
+ current_machine = nil
509
+ t = Time.now.strftime "%Y-%m-%d %H:%M:%S"
510
+ begin
511
+
512
+ machines=get_vms(vmname)
513
+
514
+ return [404,"Virtual Machine not found"] if machines.empty?
515
+
516
+ machines.each do |machine|
517
+
518
+ current_machine = machine.name.to_s
519
+
520
+ env =
521
+ {
522
+ :machine => machine,
523
+ :machine_action => SnapshotAction::BACKUP,
524
+ :path => @env.data_dir
525
+ }
526
+
527
+ @db.add_backup_log_entry(t,current_machine,BACKUP_IN_PROGRESS)
528
+
529
+ res = @env.action_runner.run(SnapshotAction,env)
530
+
531
+ if res[:bak_filename] == SnapshotAction::ERROR
532
+ @db.update_backup_log_entry(t,current_machine,BACKUP_ERROR)
533
+ return [500,"Internal Error"] if res[:bak_filename] == SnapshotAction::ERROR
534
+ else
535
+ @db.update_backup_log_entry(t,current_machine,BACKUP_SUCCESS)
536
+ return [200,res[:bak_filename]]
537
+ end
538
+
539
+ end
540
+
541
+ rescue => e
542
+ @db.update_backup_log_entry(t,current_machine,BACKUP_ERROR)
543
+ return [500,"Internal Error"]
544
+ end
545
+
546
+ end
547
+
548
+
549
+
550
+ ################################################################
551
+ ################# BACKUP LOG METHOD ###############
552
+ ################################################################
553
+ def self.backup_log(vmname)
554
+ ensure_environment
555
+
556
+ begin
557
+
558
+ @db.get_backup_log_entries(vmname)
559
+
560
+ rescue => e
561
+ puts e.message
562
+ end
563
+
564
+ end
565
+
566
+
567
+ ################################################################
568
+ ####################### PRIVATE METHODS #######################
569
+ ################################################################
570
+ private
571
+
572
+ BACKUP_ERROR = "ERROR"
573
+ BACKUP_SUCCESS = "OK"
574
+ BACKUP_IN_PROGRESS = "IN PROGRESS"
575
+
576
+ def self.ensure_environment
577
+ #Due to the fact that the enviroment data can change
578
+ #if we always use a stored value of env we won't be
579
+ #able to notice those changes
580
+ # if (!@env)
581
+ # opts = {}
582
+ # @env = Vagrant::Environment.new(opts)
583
+ # end
584
+
585
+ opts = {}
586
+ @env = Vagrant::Environment.new(opts)
587
+ @db = DB::DBManager.new(@env.data_dir) if (!@db)
588
+
589
+ end
590
+
591
+ #FIXME REVISAR Y MEJORAR, LO HE HECHO DEPRISA PERO SE
592
+ #PUEDE OPTIMIZAR
593
+ def self.get_vms(vmname)
594
+ machines = []
595
+ provider=@env.default_provider
596
+
597
+ if (vmname && !vmname.empty?)
598
+ #If a machine was specified launch only that machine
599
+ name=vmname.to_sym
600
+ if (@env.machine_names.index(name)!=nil)
601
+
602
+ @env.active_machines.each do |active_name, active_provider|
603
+
604
+ if name==active_name
605
+ provider=active_provider
606
+ break
607
+ end
608
+
609
+ end
610
+ machines << @env.machine(name,provider)
611
+ end
612
+
613
+ else
614
+ #If no machine was specified launch all
615
+ @env.machine_names.each do |machine_name|
616
+ @env.active_machines.each do |active_name, active_provider|
617
+ if active_name==machine_name
618
+ provider=active_provider
619
+ break
620
+ end
621
+
622
+ end
623
+ machines << @env.machine(machine_name,provider)
624
+ end
625
+ end
626
+
627
+ machines
628
+
629
+ end
630
+
631
+ end
632
+ end
633
+ end