nestene 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.
- checksums.yaml +15 -0
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/TODO.txt +7 -0
- data/api.rb +8 -0
- data/autons.rb +13 -0
- data/bin/nestene.rb +5 -0
- data/config.ru +21 -0
- data/features/api/api_create_auton.feature +6 -0
- data/features/api/api_credentials.feature +11 -0
- data/features/api/api_list_of_autons.feature +6 -0
- data/features/callback.feature +13 -0
- data/features/create_auton.feature +11 -0
- data/features/credentials.feature +7 -0
- data/features/execute_step.feature +59 -0
- data/features/restart.feature +7 -0
- data/features/schedule_steps.feature +7 -0
- data/features/step_definitions/api/api_create_auton_steps.rb +16 -0
- data/features/step_definitions/api/api_credentials_steps.rb +22 -0
- data/features/step_definitions/api/api_list_of_autons_steps.rb +8 -0
- data/features/step_definitions/callback_steps.rb +75 -0
- data/features/step_definitions/create_auton_steps.rb +32 -0
- data/features/step_definitions/credentials_steps.rb +24 -0
- data/features/step_definitions/execute_step_steps.rb +177 -0
- data/features/step_definitions/restart_steps.rb +5 -0
- data/features/step_definitions/schedule_steps_steps.rb +29 -0
- data/features/step_definitions/time_delayed_steps_steps.rb +103 -0
- data/features/step_definitions/ui/create_auton_steps.rb +21 -0
- data/features/step_definitions/ui/list_of_autons_steps.rb +18 -0
- data/features/step_definitions/ui/schedule_auton_step_steps.rb +64 -0
- data/features/step_definitions/ui/show_auton_steps.rb +49 -0
- data/features/support/env.rb +36 -0
- data/features/time_delayed_steps.feature +28 -0
- data/features/ui/create_auton.feature +7 -0
- data/features/ui/list_of_autons.feature +7 -0
- data/features/ui/schedule_auton_step.feature +12 -0
- data/features/ui/show_auton.feature +14 -0
- data/lib/nestene/actor/auton_queue.rb +87 -0
- data/lib/nestene/actor/auton_storage.rb +50 -0
- data/lib/nestene/actor/core.rb +150 -0
- data/lib/nestene/actor/delayed_scheduler.rb +76 -0
- data/lib/nestene/auton_context.rb +31 -0
- data/lib/nestene/auton_execution_queue.rb +43 -0
- data/lib/nestene/auton_state.rb +27 -0
- data/lib/nestene/callback.rb +7 -0
- data/lib/nestene/delayed_method.rb +17 -0
- data/lib/nestene/executed_method.rb +29 -0
- data/lib/nestene/executing_method.rb +23 -0
- data/lib/nestene/execution_error.rb +39 -0
- data/lib/nestene/scheduled_method.rb +21 -0
- data/lib/nestene/storage.rb +72 -0
- data/lib/nestene/ui/app.rb +103 -0
- data/lib/nestene/ui/public/app/application.js +75 -0
- data/lib/nestene/ui/public/vendor/angular-ui-sortable/sortable.min.js +1 -0
- data/lib/nestene/ui/public/vendor/angular.min.js +248 -0
- data/lib/nestene/ui/public/vendor/bootstrap/css/bootstrap-theme.css +347 -0
- data/lib/nestene/ui/public/vendor/bootstrap/css/bootstrap-theme.min.css +7 -0
- data/lib/nestene/ui/public/vendor/bootstrap/css/bootstrap.css +5785 -0
- data/lib/nestene/ui/public/vendor/bootstrap/css/bootstrap.min.css +7 -0
- data/lib/nestene/ui/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- data/lib/nestene/ui/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg +229 -0
- data/lib/nestene/ui/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/lib/nestene/ui/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- data/lib/nestene/ui/public/vendor/bootstrap/js/bootstrap.js +1951 -0
- data/lib/nestene/ui/public/vendor/bootstrap/js/bootstrap.min.js +6 -0
- data/lib/nestene/ui/public/vendor/jquery-ui.min.js +13 -0
- data/lib/nestene/ui/public/vendor/jquery.bootstrap-growl.min.js +1 -0
- data/lib/nestene/ui/public/vendor/jquery.min.js +5 -0
- data/lib/nestene/ui/public/vendor/json-editor/directives.js +245 -0
- data/lib/nestene/ui/public/vendor/json-editor/styles.css +165 -0
- data/lib/nestene/ui/views/auton.haml +42 -0
- data/lib/nestene/ui/views/auton_form.haml +8 -0
- data/lib/nestene/ui/views/index.haml +35 -0
- data/lib/nestene/ui/views/layout.haml +40 -0
- data/lib/nestene/ui/views/schedule_step.haml +14 -0
- data/lib/nestene/version.rb +3 -0
- data/lib/nestene.rb +50 -0
- data/nestene.gemspec +39 -0
- data/spec/nestene/auton_execution_queue_spec.rb +78 -0
- data/spec/nestene/nestene_spec.rb +137 -0
- data/spec/nestene/storage_spec.rb +125 -0
- data/spec/spec_helper.rb +2 -0
- metadata +373 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Given(/^I have an Auton that has two steps, first step scheduling the next step$/) do
|
|
2
|
+
class ScheduleSecondStepAuton
|
|
3
|
+
include StructureMapper::Hash
|
|
4
|
+
|
|
5
|
+
def first
|
|
6
|
+
context.schedule_step(:second)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def second
|
|
10
|
+
'ok'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_accessor :context
|
|
14
|
+
|
|
15
|
+
attribute foo: Fixnum
|
|
16
|
+
end
|
|
17
|
+
@auton_type="ScheduleSecondStepAuton"
|
|
18
|
+
@auton_id = Celluloid::Actor[:nestene_core].create_auton(@auton_type)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
When(/^I schedule the first step that returns the uuid of the second scheduled step$/) do
|
|
22
|
+
@step_execution_id = Celluloid::Actor[:nestene_core].schedule_step @auton_id, :first
|
|
23
|
+
@step_execution_id = Celluloid::Actor[:nestene_core].wait_for_execution_result(@auton_id, @step_execution_id)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
When(/^I wait for the second step to finish$/) do
|
|
27
|
+
@execution_result = Celluloid::Actor[:nestene_core].wait_for_execution_result(@auton_id, @step_execution_id)
|
|
28
|
+
end
|
|
29
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Given(/^an Auton that schedules an one off delayed step in (\d+)\.(\d+) second$/) do |arg1, arg2|
|
|
2
|
+
class TimeDelayedStepSchedulingAuton
|
|
3
|
+
include StructureMapper::Hash
|
|
4
|
+
|
|
5
|
+
def first
|
|
6
|
+
context.schedule_delayed_step(0.1, :second)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def second
|
|
10
|
+
'ok'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_accessor :context
|
|
14
|
+
|
|
15
|
+
attribute foo: Fixnum
|
|
16
|
+
end
|
|
17
|
+
@auton_type="TimeDelayedStepSchedulingAuton"
|
|
18
|
+
@auton_id = Celluloid::Actor[:nestene_core].create_auton(@auton_type)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
When(/^I execute the step that schedules the delayed step$/) do
|
|
22
|
+
@start_time = Time.now
|
|
23
|
+
@step_execution_id = Celluloid::Actor[:nestene_core].schedule_step @auton_id, :first
|
|
24
|
+
@second_step_execution_id = Celluloid::Actor[:nestene_core].wait_for_execution_result(@auton_id, @step_execution_id)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Then(/^the elapsed time between first and sencond step should be at least (\d+\.\d+) secods$/) do |seconds_string|
|
|
28
|
+
raise @execution_exception if @execution_exception
|
|
29
|
+
expect(@execution_result).to eq('ok')
|
|
30
|
+
expect(Time.now - @start_time).to be >= seconds_string.to_f
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
When(/^I cancel the time delayed step$/) do
|
|
34
|
+
Celluloid::Actor[:nestene_core].cancel_delayed_step @auton_id, @second_step_execution_id
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
When(/^I wait for (\d+\.\d+) seconds$/) do |seconds|
|
|
38
|
+
sleep seconds.to_f
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
Then(/^the scheduled step should not be executed$/) do
|
|
42
|
+
expect(Celluloid::Actor[:nestene_core].get_state(@auton_id).queue.executed.size).to eq(1)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
Given(/^an Auton that schedules an repeating every (\d+\.\d+) seconds delayed step in (\d+\.\d+) second$/) do |every, delay|
|
|
47
|
+
class RepeatingTimeDelayedStepdSchedulingAuton
|
|
48
|
+
include StructureMapper::Hash
|
|
49
|
+
|
|
50
|
+
def first
|
|
51
|
+
context.schedule_repeating_delayed_step(0.1, 0.1, :second)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def second
|
|
55
|
+
'ok'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
attr_accessor :context
|
|
59
|
+
|
|
60
|
+
attribute foo: Fixnum
|
|
61
|
+
end
|
|
62
|
+
@auton_type="RepeatingTimeDelayedStepdSchedulingAuton"
|
|
63
|
+
@auton_id = Celluloid::Actor[:nestene_core].create_auton(@auton_type)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
Then(/^the scheduled step should be executed twice$/) do
|
|
67
|
+
expect(Celluloid::Actor[:nestene_core].get_state(@auton_id).queue.executed.map{|m| m.name}).to eq([:first,:second,:second])
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
Given(/^an Auton that schedules an one off delayed step in (\d+\.\d+) seconds and has a failing step$/) do |delay|
|
|
72
|
+
class TimeDelayedAndFailingStepSchedulingAuton
|
|
73
|
+
include StructureMapper::Hash
|
|
74
|
+
|
|
75
|
+
def first
|
|
76
|
+
context.schedule_delayed_step(0.1, :second)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def fail
|
|
80
|
+
raise "fail!"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def second
|
|
84
|
+
'ok'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
attr_accessor :context
|
|
88
|
+
|
|
89
|
+
attribute foo: Fixnum
|
|
90
|
+
end
|
|
91
|
+
@auton_type="TimeDelayedAndFailingStepSchedulingAuton"
|
|
92
|
+
@auton_id = Celluloid::Actor[:nestene_core].create_auton(@auton_type)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
When(/^I execute the failing step$/) do
|
|
96
|
+
@step_execution_id = Celluloid::Actor[:nestene_core].schedule_step @auton_id, :fail
|
|
97
|
+
expect {Celluloid::Actor[:nestene_core].wait_for_execution_result(@auton_id, @step_execution_id)}.to raise_error
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
Then(/^the delayed step should not be executed$/) do
|
|
101
|
+
expect(Celluloid::Actor[:nestene_core].get_state(@auton_id).queue.executed.map{|m| m.name}).to eq([:first,:fail])
|
|
102
|
+
end
|
|
103
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Given(/^there is a simple Auton implementation$/) do
|
|
2
|
+
class EmptyAuton
|
|
3
|
+
include StructureMapper::Hash
|
|
4
|
+
|
|
5
|
+
attribute foo: String
|
|
6
|
+
end
|
|
7
|
+
@auton_type="EmptyAuton"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
When(/^I create that simple Auton$/) do
|
|
11
|
+
visit '/'
|
|
12
|
+
click_on 'Create Auton'
|
|
13
|
+
fill_in 'auton_type', with: @auton_type
|
|
14
|
+
click_on 'create'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
Then(/^the list of Autons should contain the auton id$/) do
|
|
18
|
+
get '/autons.json'
|
|
19
|
+
expect(last_response).to be_ok
|
|
20
|
+
expect(JSON.parse(last_response.body)).not_to be_empty
|
|
21
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Given(/^I have one started auton$/) do
|
|
2
|
+
class EmptyAuton
|
|
3
|
+
include StructureMapper::Hash
|
|
4
|
+
|
|
5
|
+
attribute foo: String
|
|
6
|
+
end
|
|
7
|
+
@auton_type="EmptyAuton"
|
|
8
|
+
@auton_id = Celluloid::Actor[:nestene_core].create_auton(@auton_type, 'empty_auton')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
When(/^I visit the index page$/) do
|
|
12
|
+
visit '/'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Then(/^I should see the id of that auton$/) do
|
|
16
|
+
expect(page).to have_content 'empty_auton'
|
|
17
|
+
end
|
|
18
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Given(/^I have a running auton with one defined step without parameters$/) do
|
|
2
|
+
class EmptyAuton
|
|
3
|
+
include StructureMapper::Hash
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def start
|
|
9
|
+
self.started = true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attribute started: Boolean
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
@auton_type="EmptyAuton"
|
|
16
|
+
@auton_id = Celluloid::Actor[:nestene_core].create_auton(@auton_type, 'empty_auton')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
When(/^I schedule the step$/) do
|
|
20
|
+
visit '/auton/%s' % @auton_id
|
|
21
|
+
click_on 'schedule_step'
|
|
22
|
+
select 'start'
|
|
23
|
+
click_on 'schedule'
|
|
24
|
+
@step_execution_id = page.response_headers['X-AUTON-STEP-ID']
|
|
25
|
+
raise 'could not find step id' unless @step_execution_id
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Given(/^I have a running auton with one defined step with two parameters$/) do
|
|
30
|
+
class EmptyAutonWithParams
|
|
31
|
+
include StructureMapper::Hash
|
|
32
|
+
|
|
33
|
+
def initialize
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def start p1, p2
|
|
37
|
+
self.p1 = p1
|
|
38
|
+
self.p2 = p2
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
attribute p1: String
|
|
42
|
+
attribute p2: String
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
@auton_type="EmptyAutonWithParams"
|
|
46
|
+
@auton_id = Celluloid::Actor[:nestene_core].create_auton(@auton_type)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
When(/^I schedule the step with parameters$/) do
|
|
50
|
+
visit '/auton/%s' % @auton_id
|
|
51
|
+
click_on 'schedule_step'
|
|
52
|
+
select 'start'
|
|
53
|
+
click_on 'schedule'
|
|
54
|
+
# select 'start'
|
|
55
|
+
fill_in 'parameters[0]', with: 'p1'
|
|
56
|
+
fill_in 'parameters[1]', with: 'p1'
|
|
57
|
+
click_on 'schedule'
|
|
58
|
+
@step_execution_id = page.response_headers['X-AUTON-STEP-ID']
|
|
59
|
+
raise 'could not find step id' unless @step_execution_id
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
Then(/^the executed steps should include the step with the parameters$/) do
|
|
63
|
+
expect(Celluloid::Actor["storage:%s" % @auton_id].get.queue.executed.first.uuid).to eq(@step_execution_id)
|
|
64
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Given(/^I have one running Auton$/) do
|
|
2
|
+
class SomeCoolAuton
|
|
3
|
+
include StructureMapper::Hash
|
|
4
|
+
|
|
5
|
+
def start
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
attribute foo: String
|
|
9
|
+
end
|
|
10
|
+
@auton_id = Celluloid::Actor[:nestene_core].create_auton "SomeCoolAuton"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
When(/^I view the Auton$/) do
|
|
14
|
+
visit '/'
|
|
15
|
+
click_on @auton_id
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Then(/^I should see Auton's id/) do
|
|
19
|
+
expect(page).to have_content @auton_id
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
Then(/^I should see Auton's status$/) do
|
|
24
|
+
expect(page.find('#auton_state').text).to eq('ready')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Then(/^I should see Auton's state$/) do
|
|
28
|
+
expect(page).to have_css('#current_state')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Then(/^I should see Auton's queue$/) do
|
|
32
|
+
expect(page).to have_css('#queue')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Then(/^I should see Auton's executed steps$/) do
|
|
36
|
+
expect(page).to have_css('#executed')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
Then(/^I should see Auton's current step$/) do
|
|
40
|
+
expect(page).to have_css('#executing')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
Then(/^I should see Auton's scheduled steps$/) do
|
|
44
|
+
expect(page).to have_css('#to_execute')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
Then(/^I should see Auton's delayed steps$/) do
|
|
48
|
+
pending # express the regexp above with the code you wish you had
|
|
49
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
ENV['RACK_ENV'] = 'test'
|
|
2
|
+
require "rack/test"
|
|
3
|
+
require 'nestene'
|
|
4
|
+
require 'capybara/cucumber'
|
|
5
|
+
require 'capybara/poltergeist'
|
|
6
|
+
require 'pry'
|
|
7
|
+
Capybara.javascript_driver = :poltergeist
|
|
8
|
+
|
|
9
|
+
Capybara.app = Nestene::Ui::App
|
|
10
|
+
|
|
11
|
+
Capybara.register_driver :rack_test do |app|
|
|
12
|
+
Capybara::RackTest::Driver.new app, \
|
|
13
|
+
redirect_limit: 15,
|
|
14
|
+
follow_redirects: false,
|
|
15
|
+
respect_data_method: true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Before do
|
|
19
|
+
Celluloid.shutdown
|
|
20
|
+
Celluloid.boot
|
|
21
|
+
@storage = Nestene::MemoryStorage.new
|
|
22
|
+
Nestene::start_nestene(@storage)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class MyWorld
|
|
26
|
+
include Rack::Test::Methods
|
|
27
|
+
def app
|
|
28
|
+
Nestene::Ui::App
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
World do
|
|
33
|
+
MyWorld.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Feature: time delayed steps
|
|
2
|
+
|
|
3
|
+
Scenario: scheduling one off time delayed step
|
|
4
|
+
Given an Auton that schedules an one off delayed step in 0.1 second
|
|
5
|
+
When I execute the step that schedules the delayed step
|
|
6
|
+
And I wait for the second step to execute
|
|
7
|
+
Then the elapsed time between first and sencond step should be at least 0.1 secods
|
|
8
|
+
|
|
9
|
+
Scenario: un-scheduling one off time delayed step
|
|
10
|
+
Given an Auton that schedules an one off delayed step in 0.1 second
|
|
11
|
+
When I execute the step that schedules the delayed step
|
|
12
|
+
And I cancel the time delayed step
|
|
13
|
+
And I wait for 0.15 seconds
|
|
14
|
+
Then the scheduled step should not be executed
|
|
15
|
+
|
|
16
|
+
Scenario: scheduling repeating delayed step
|
|
17
|
+
Given an Auton that schedules an repeating every 0.1 seconds delayed step in 0.1 second
|
|
18
|
+
When I execute the step that schedules the delayed step
|
|
19
|
+
And I wait for 0.21 seconds
|
|
20
|
+
Then the scheduled step should be executed twice
|
|
21
|
+
|
|
22
|
+
Scenario: suspending one off delayed step when auton is in error state
|
|
23
|
+
Given an Auton that schedules an one off delayed step in 0.1 seconds and has a failing step
|
|
24
|
+
When I execute the step that schedules the delayed step
|
|
25
|
+
When I execute the failing step
|
|
26
|
+
And I wait for 0.11 seconds
|
|
27
|
+
Then the delayed step should not be executed
|
|
28
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Feature: schedule auton step
|
|
2
|
+
|
|
3
|
+
Scenario: schedule steps without parameters
|
|
4
|
+
Given I have a running auton with one defined step without parameters
|
|
5
|
+
When I schedule the step
|
|
6
|
+
And I wait for the step to execute
|
|
7
|
+
|
|
8
|
+
Scenario: schedule steps with parameters
|
|
9
|
+
Given I have a running auton with one defined step with two parameters
|
|
10
|
+
When I schedule the step with parameters
|
|
11
|
+
And I wait for the step to execute
|
|
12
|
+
Then the executed steps should include the step with the parameters
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Feature: show auton
|
|
2
|
+
|
|
3
|
+
@javascript
|
|
4
|
+
Scenario: show running Auton
|
|
5
|
+
Given I have one running Auton
|
|
6
|
+
When I view the Auton
|
|
7
|
+
Then I should see Auton's id
|
|
8
|
+
And I should see Auton's status
|
|
9
|
+
And I should see Auton's state
|
|
10
|
+
And I should see Auton's queue
|
|
11
|
+
And I should see Auton's executed steps
|
|
12
|
+
And I should see Auton's current step
|
|
13
|
+
And I should see Auton's scheduled steps
|
|
14
|
+
# And I should see Auton's delayed steps
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require 'celluloid'
|
|
2
|
+
|
|
3
|
+
module Nestene
|
|
4
|
+
module Actor
|
|
5
|
+
class AutonQueue
|
|
6
|
+
include Celluloid
|
|
7
|
+
include Celluloid::Notifications
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
subscribe('state_update', :execute_next_step)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def execute_next_step topic, auton_id, state
|
|
15
|
+
executing = nil
|
|
16
|
+
type = nil
|
|
17
|
+
ser = nil
|
|
18
|
+
running = false
|
|
19
|
+
|
|
20
|
+
Celluloid::Actor["storage:%s" % auton_id].update do |state|
|
|
21
|
+
running = state.state == :ready && !state.queue.to_execute.empty?
|
|
22
|
+
if running
|
|
23
|
+
nx = state.queue.to_execute.shift
|
|
24
|
+
executing = ExecutingMethod.new(nx)
|
|
25
|
+
state.queue.currently_executing = executing
|
|
26
|
+
type = state.type
|
|
27
|
+
ser = state.serialized
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
return unless running
|
|
32
|
+
|
|
33
|
+
instance = Nestene::class_from_string(type).from_structure(ser)
|
|
34
|
+
|
|
35
|
+
if instance.public_methods.include?(:context=)
|
|
36
|
+
instance.public_send(:context=, AutonContext.new(auton_id))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
future = Celluloid::Future.new do
|
|
40
|
+
instance.method(executing.name).call(*executing.parameters)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
result = exception = nil
|
|
44
|
+
|
|
45
|
+
begin
|
|
46
|
+
result = future.value
|
|
47
|
+
rescue Exception => e
|
|
48
|
+
exception = e
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
executed = ExecutedMethod.new(executing)
|
|
52
|
+
|
|
53
|
+
executed.result = result
|
|
54
|
+
|
|
55
|
+
if exception
|
|
56
|
+
executed.error = exception
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if executed.callback
|
|
60
|
+
if exception
|
|
61
|
+
Celluloid::Actor[:nestene_core].async.schedule_step executed.callback.auton_id, ("%s_error" % executed.callback.name.to_s), [exception.class.name, exception.message]
|
|
62
|
+
else
|
|
63
|
+
Celluloid::Actor[:nestene_core].async.schedule_step executed.callback.auton_id, executed.callback.name, result
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
Celluloid::Actor["storage:%s" % auton_id].update do |state|
|
|
68
|
+
state.queue.currently_executing = nil
|
|
69
|
+
state.serialized = instance.to_structure
|
|
70
|
+
if exception
|
|
71
|
+
if instance.methods.include?(:handle_exception) && executing.name != :handle_exception
|
|
72
|
+
method = ScheduledMethod.new
|
|
73
|
+
method.name = :handle_exception
|
|
74
|
+
method.parameters = [exception.class.name, exception.message, executing.name, executing.parameters]
|
|
75
|
+
method.uuid = SecureRandom.uuid
|
|
76
|
+
state.queue.to_execute.unshift method
|
|
77
|
+
else
|
|
78
|
+
state.queue.failed = true
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
state.queue.executed << executed
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'celluloid'
|
|
2
|
+
|
|
3
|
+
module Nestene
|
|
4
|
+
module Actor
|
|
5
|
+
class AutonStorage
|
|
6
|
+
include Celluloid
|
|
7
|
+
include Celluloid::Notifications
|
|
8
|
+
|
|
9
|
+
exclusive :create, :get, :update
|
|
10
|
+
|
|
11
|
+
execute_block_on_receiver :update
|
|
12
|
+
|
|
13
|
+
def initialize(auton_id, storage)
|
|
14
|
+
@auton_id = auton_id
|
|
15
|
+
@storage = storage
|
|
16
|
+
state = get
|
|
17
|
+
publish('state_update', @auton_id, state) if state
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create type
|
|
21
|
+
state = AutonState.new
|
|
22
|
+
state.type = type
|
|
23
|
+
instance = Nestene::class_from_string(type).new
|
|
24
|
+
state.serialized = instance.to_structure
|
|
25
|
+
@storage.store(@auton_id,state.to_structure)
|
|
26
|
+
publish('state_update', @auton_id, state)
|
|
27
|
+
rescue Exception => e
|
|
28
|
+
abort e
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get
|
|
32
|
+
structure = @storage.load(@auton_id)
|
|
33
|
+
structure ? AutonState.from_structure(structure) : nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def update &block
|
|
37
|
+
before = @storage.load(@auton_id)
|
|
38
|
+
state = AutonState.from_structure(@storage.load(@auton_id))
|
|
39
|
+
result = block.call(state)
|
|
40
|
+
after = state.to_structure
|
|
41
|
+
@storage.store(@auton_id,after)
|
|
42
|
+
publish('state_update', @auton_id, state) if before != after
|
|
43
|
+
result
|
|
44
|
+
rescue Exception => e
|
|
45
|
+
abort e
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
require 'celluloid'
|
|
2
|
+
|
|
3
|
+
module Nestene
|
|
4
|
+
module Actor
|
|
5
|
+
class Core
|
|
6
|
+
|
|
7
|
+
include Celluloid
|
|
8
|
+
include Celluloid::Notifications
|
|
9
|
+
|
|
10
|
+
def initialize(storage)
|
|
11
|
+
subscribe('state_update', :notify_waiters)
|
|
12
|
+
@execution_futures={}
|
|
13
|
+
@storage = storage
|
|
14
|
+
storage.list.each do |auton_id|
|
|
15
|
+
Celluloid::Actor["storage:%s" % auton_id] = AutonStorage.new(auton_id, @storage)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def auton_names
|
|
20
|
+
@storage.list
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get_credentials
|
|
24
|
+
@storage.load('__credentials__') || {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def set_credentials credentials
|
|
28
|
+
@storage.store('__credentials__',credentials)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_auton type, auton_id=SecureRandom.uuid
|
|
32
|
+
storage = AutonStorage.new(auton_id, @storage)
|
|
33
|
+
Celluloid::Actor["storage:%s" % auton_id] = storage
|
|
34
|
+
storage.create(type)
|
|
35
|
+
auton_id
|
|
36
|
+
rescue Exception => e
|
|
37
|
+
abort e
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_state auton_id
|
|
41
|
+
Celluloid::Actor["storage:%s" % auton_id].get
|
|
42
|
+
rescue Exception => e
|
|
43
|
+
abort e
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def schedule_step auton_id, step_name, parameters=[], callback_auton_id=nil, callback_method=nil
|
|
47
|
+
step_id = SecureRandom.uuid
|
|
48
|
+
Celluloid::Actor["storage:%s" % auton_id].update do |state|
|
|
49
|
+
method = ScheduledMethod.new
|
|
50
|
+
method.name = step_name
|
|
51
|
+
method.parameters = parameters
|
|
52
|
+
method.uuid = step_id
|
|
53
|
+
if callback_auton_id && callback_method
|
|
54
|
+
method.callback = Callback.new
|
|
55
|
+
method.callback.name = callback_method
|
|
56
|
+
method.callback.auton_id = callback_auton_id
|
|
57
|
+
end
|
|
58
|
+
state.queue.to_execute << method
|
|
59
|
+
end
|
|
60
|
+
step_id
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def cancel_delayed_step auton_id, step_id
|
|
64
|
+
Celluloid::Actor["storage:%s" % auton_id].update do |state|
|
|
65
|
+
delayed = state.queue.remove_delayed_method step_id
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def schedule_delayed_step auton_id, delay , name, parameters=[]
|
|
70
|
+
Celluloid::Actor["storage:%s" % auton_id].update do |state|
|
|
71
|
+
delayed = state.queue.add_delayed_method name, parameters, delay
|
|
72
|
+
delayed.uuid
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def schedule_repeating_delayed_step auton_id, every, delay, name, parameters=[]
|
|
78
|
+
Celluloid::Actor["storage:%s" % auton_id].update do |state|
|
|
79
|
+
delayed = state.queue.add_delayed_method name, parameters, delay, every
|
|
80
|
+
delayed.uuid
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def repeat_step auton_id, method_uuid
|
|
85
|
+
step_id = SecureRandom.uuid
|
|
86
|
+
Celluloid::Actor["storage:%s" % auton_id].update do |state|
|
|
87
|
+
|
|
88
|
+
original_step = state.queue.executed.find{|m| m.uuid == method_uuid}
|
|
89
|
+
|
|
90
|
+
method = ScheduledMethod.new
|
|
91
|
+
method.name = original_step.name
|
|
92
|
+
method.parameters = original_step.parameters
|
|
93
|
+
method.uuid = step_id
|
|
94
|
+
state.queue.to_execute.unshift method
|
|
95
|
+
state.queue.failed = false
|
|
96
|
+
end
|
|
97
|
+
step_id
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def resume auton_id
|
|
101
|
+
Celluloid::Actor["storage:%s" % auton_id].update do |state|
|
|
102
|
+
state.queue.failed = false
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def wait_for_execution_result auton_id, method_uuid
|
|
108
|
+
|
|
109
|
+
future = nil
|
|
110
|
+
|
|
111
|
+
executed = nil
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
exclusive do
|
|
115
|
+
|
|
116
|
+
state = Celluloid::Actor["storage:%s" % auton_id].get
|
|
117
|
+
|
|
118
|
+
unless @execution_futures.has_key?(auton_id)
|
|
119
|
+
@execution_futures[auton_id] = Celluloid::Future.new
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
future = @execution_futures[auton_id]
|
|
123
|
+
|
|
124
|
+
executed = state ? state.queue.executed.find{|m| m.uuid == method_uuid} : nil
|
|
125
|
+
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
until executed
|
|
129
|
+
|
|
130
|
+
executed = future.value
|
|
131
|
+
state = Celluloid::Actor["storage:%s" % auton_id].get
|
|
132
|
+
executed = state.queue.executed.find{|m| m.uuid == method_uuid}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if executed.error
|
|
136
|
+
abort executed.error
|
|
137
|
+
else
|
|
138
|
+
return executed.result
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def notify_waiters topic, auton_id, state
|
|
144
|
+
future = @execution_futures.delete(auton_id)
|
|
145
|
+
future.signal(SelfValue.new) if future
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|