eye 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec5135b6c531e9451f98ec8b9d36a54d2e57e3f9
4
- data.tar.gz: fd8011e705ee96d0d24dffbe03be598a18b285c4
3
+ metadata.gz: b27fc1f2b1ca918d1dec48f63af0f59446f6a166
4
+ data.tar.gz: b823fadfaea2111a8c57ec07e4b2ae84ad7899a2
5
5
  SHA512:
6
- metadata.gz: 59237be9adb524bdc90ddb9203d3246e7e6fc03f4b9c7398df2380fba2423e68592a996ac720ff04cd8a813b34ed8a27a1c259421907dd1103b18ad508c9decd
7
- data.tar.gz: b741a332f8888e3f5a2dd50af91e0dbafebed8f8dcc406f9f8cab270b565f3ce2624adef2e4b9c2b3be0a01ff18dcbaf28159d4ba55b6d532e37711b8c9fa5cd
6
+ metadata.gz: 6fd472f37e8123f426b45409e2067c1bf901cf265aa37a3480229eda6dcbc8fcdca4c51656580cc756c305da9f565f18a35dda277a48f188da36bb69a02effa0
7
+ data.tar.gz: ce0459d1f8d2d0d236a9ca2586ed44e2e173b6c82bfc6c426c473e9d0c20dce20bf28935af89efc23f2e33a48be9953a61fd0a2e0b5b536ef62e8fbd32a38997
data/README.md CHANGED
@@ -30,7 +30,7 @@ Eye.application "test" do
30
30
  working_dir File.expand_path(File.join(File.dirname(__FILE__), %w[ processes ]))
31
31
  stdall "trash.log" # stdout,err logs for processes by default
32
32
  env "APP_ENV" => "production" # global env for each processes
33
- triggers :flapping, :times => 10, :within => 1.minute #
33
+ triggers :flapping, :times => 10, :within => 1.minute, :retry_in => 10.minutes
34
34
  checks :cpu, :below => 100, :times => 3 # global check for all processes
35
35
 
36
36
  group "samples" do
@@ -14,7 +14,7 @@ Eye.application "test" do
14
14
  working_dir File.expand_path(File.join(File.dirname(__FILE__), %w[ processes ]))
15
15
  stdall "trash.log" # stdout,err logs for processes by default
16
16
  env "APP_ENV" => "production" # global env for each processes
17
- triggers :flapping, :times => 10, :within => 1.minute #
17
+ triggers :flapping, :times => 10, :within => 1.minute, :retry_in => 10.minutes
18
18
  checks :cpu, :below => 100, :times => 3 # global check for all processes
19
19
 
20
20
  group "samples" do
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
8
8
  %q{Process monitoring tool. An alternative to God and Bluepill. With Bluepill like config syntax. Requires MRI Ruby >= 1.9.2. Uses Celluloid and Celluloid::IO.}
9
9
  gem.homepage = "http://github.com/kostya/eye"
10
10
 
11
- gem.files = `git ls-files`.split($\)
11
+ gem.files = `git ls-files`.split($\).reject{|n| n =~ %r[png|gif\z]}
12
12
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
13
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
14
  gem.name = "eye"
@@ -21,7 +21,7 @@ Gem::Specification.new do |gem|
21
21
 
22
22
  gem.add_dependency 'celluloid', '~> 0.12.0'
23
23
  gem.add_dependency 'celluloid-io', '~> 0.12.0'
24
- gem.add_dependency 'state_machine'
24
+ gem.add_dependency 'state_machine', '< 1.2'
25
25
  gem.add_dependency 'activesupport', '~> 3.2.0'
26
26
  gem.add_dependency 'thor'
27
27
 
data/lib/eye.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Eye
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.3"
3
3
  ABOUT = "Eye v#{VERSION} (c) 2012-2013 @kostya"
4
4
 
5
5
  autoload :Process, 'eye/process'
@@ -95,7 +95,7 @@ private
95
95
  Net::HTTP.new(@uri.host, @uri.port).tap do |session|
96
96
  if @uri.scheme == 'https'
97
97
  require 'net/https'
98
- session.use_ssl=true
98
+ session.use_ssl = true
99
99
  session.verify_mode = OpenSSL::SSL::VERIFY_NONE
100
100
  end
101
101
  session.open_timeout = @open_timeout
@@ -39,7 +39,7 @@ class Eye::Controller
39
39
 
40
40
  Eye::SystemResources.setup
41
41
 
42
- info "starting #{Eye::ABOUT}"
42
+ info "starting #{Eye::ABOUT} (#{$$})"
43
43
  end
44
44
 
45
45
  end
@@ -90,7 +90,7 @@ private
90
90
 
91
91
  res = []
92
92
  str = Regexp.escape(mask).gsub('\*', '.*?')
93
- r = %r{\A#{str}\z}
93
+ r = %r{\A#{str}}
94
94
 
95
95
  # find app
96
96
  res = @applications.select{|a| a.name =~ r || a.full_name =~ r }
@@ -6,7 +6,7 @@ gem 'nio4r'
6
6
  gem 'facter'
7
7
  gem 'timers'
8
8
 
9
- gem 'state_machine'
9
+ gem 'state_machine', '< 1.2'
10
10
  gem 'activesupport', '~> 3.2.0'
11
11
 
12
12
  gem 'i18n' # for as
@@ -18,7 +18,7 @@ class Eye::Process
18
18
  autoload :Validate, 'eye/process/validate'
19
19
 
20
20
  attr_accessor :pid, :watchers, :config, :states_history,
21
- :childs, :triggers, :flapping, :name
21
+ :childs, :triggers, :name, :state_reason
22
22
 
23
23
  def initialize(config)
24
24
  raise 'pid file should be' unless config[:pid_file]
@@ -29,7 +29,6 @@ class Eye::Process
29
29
  @watchers = {}
30
30
  @childs = {}
31
31
  @triggers = []
32
- @flapping = false
33
32
  @name = @config[:name]
34
33
 
35
34
  @states_history = Eye::Process::StatesHistory.new(100)
@@ -39,7 +39,8 @@ module Eye::Process::Data
39
39
  end
40
40
 
41
41
  def sub_object?(obj)
42
- # we not recognize childs
42
+ return false if self.class == Eye::ChildProcess
43
+ self.childs.each { |_, child| return true if child == obj }
43
44
  false
44
45
  end
45
46
 
@@ -76,13 +76,15 @@ private
76
76
 
77
77
  def check_crash
78
78
  if down?
79
- if self[:keep_alive] && !@flapping
79
+ if self[:keep_alive]
80
80
  warn 'check crashed: process is down'
81
81
  schedule :start, 'crashed'
82
82
  else
83
- warn 'check crashed: process is down, and flapping happens (or not keep_alive option)'
84
- schedule :unmonitor, 'flapping'
83
+ warn 'check crashed: process without keep_alive'
84
+ schedule :unmonitor, 'crashed'
85
85
  end
86
+ else
87
+ debug 'check crashed: skipped, process is not in down'
86
88
  end
87
89
  end
88
90
 
@@ -3,7 +3,7 @@ module Eye::Process::Scheduler
3
3
  # ex: schedule :update_config, config, "reason: update_config"
4
4
  def schedule(command, *args, &block)
5
5
  if scheduler.alive?
6
- unless self.respond_to?(command)
6
+ unless self.respond_to?(command, true)
7
7
  warn "object not support :#{command} to schedule"
8
8
  return
9
9
  end
@@ -17,6 +17,14 @@ module Eye::Process::Scheduler
17
17
  end
18
18
  end
19
19
 
20
+ def schedule_in(interval, command, *args, &block)
21
+ debug "schedule_in #{interval} :#{command} #{args}"
22
+ after(interval.to_f) do
23
+ debug "scheduled_in #{interval} :#{command} #{args}"
24
+ schedule(command, *args, &block)
25
+ end
26
+ end
27
+
20
28
  def scheduled_action(command, h = {}, &block)
21
29
  reason = h.delete(:reason)
22
30
  info "=> #{command} #{h[:args].present? ? "#{h[:args]*',' }" : nil} #{reason ? "(reason: #{reason})" : nil}"
@@ -52,12 +52,12 @@ class Eye::Process
52
52
  transition any => :unmonitored
53
53
  end
54
54
 
55
- after_transition :on => :crashed, :do => :on_crashed
56
55
  after_transition any => :unmonitored, :do => :on_unmonitored
57
56
  after_transition any-:up => :up, :do => :on_up
58
57
  after_transition :up => any-:up, :do => :from_up
59
58
  after_transition any => any, :do => :log_transition
60
- after_transition any => any, :do => :upd_for_triggers
59
+ after_transition any => any, :do => :check_triggers
60
+ after_transition :on => :crashed, :do => :on_crashed
61
61
  end
62
62
 
63
63
  def on_crashed
@@ -65,7 +65,6 @@ class Eye::Process
65
65
  end
66
66
 
67
67
  def on_unmonitored
68
- self.flapping = false
69
68
  self.pid = nil
70
69
  end
71
70
 
@@ -84,8 +83,4 @@ class Eye::Process
84
83
  info "switch :#{transition.event} [:#{transition.from_name} => :#{transition.to_name}] #{@state_reason ? "(reason: #{@state_reason})" : nil}"
85
84
  end
86
85
 
87
- def upd_for_triggers(transition)
88
- check_triggers
89
- end
90
-
91
86
  end
@@ -8,11 +8,11 @@ class Eye::Process::StatesHistory < Eye::Utils::Tail
8
8
  self.map{|c| c[:state] }
9
9
  end
10
10
 
11
- def states_for_period(period)
11
+ def states_for_period(period, from_time = nil)
12
12
  tm = Time.now - period
13
- self.select do |s|
14
- s[:at] >= tm
15
- end.map{|c| c[:state] }
13
+ tm = [tm, from_time].max if from_time
14
+
15
+ self.select{|s| s[:at] >= tm }.map{|c| c[:state] }
16
16
  end
17
17
 
18
18
  def last_state
@@ -16,9 +16,8 @@ module Eye::Process::Trigger
16
16
  return if unmonitored?
17
17
 
18
18
  self.triggers.each do |trigger|
19
- if !trigger.check(self.states_history)
20
- notify :crit, 'flapping!'
21
- @flapping = true
19
+ unless trigger.check(self.states_history)
20
+ on_flapping(trigger) if trigger.class == Eye::Trigger::Flapping
22
21
  end
23
22
  end
24
23
  end
@@ -26,7 +25,30 @@ module Eye::Process::Trigger
26
25
  private
27
26
 
28
27
  def add_trigger(cfg = {})
29
- self.triggers << Eye::Trigger.create(cfg, logger.prefix)
28
+ trigger = Eye::Trigger.create(cfg, logger.prefix)
29
+ self.triggers << trigger
30
30
  end
31
31
 
32
- end
32
+ def on_flapping(trigger)
33
+ notify :crit, 'flapping!'
34
+ schedule :unmonitor, "flapping"
35
+
36
+ @retry_times ||= 0
37
+ retry_in = trigger.retry_in
38
+
39
+ return unless retry_in
40
+ return if trigger.retry_times && @retry_times >= trigger.retry_times
41
+
42
+ schedule_in(retry_in.to_f, :retry_action)
43
+ end
44
+
45
+ def retry_action
46
+ debug "trigger retry timer"
47
+ return unless unmonitored?
48
+ return unless state_reason.to_s.include?('flapping') # TODO: remove hackety
49
+
50
+ schedule :start, "retry start after flapping"
51
+ @retry_times += 1
52
+ end
53
+
54
+ end
@@ -74,7 +74,7 @@ class Eye::SystemResources
74
74
  private
75
75
 
76
76
  def set
77
- @ps_aux = Eye::System.ps_aux
77
+ @ps_aux = defer{ Eye::System.ps_aux }
78
78
  @at = Time.now
79
79
  end
80
80
 
@@ -27,7 +27,7 @@ class Eye::Trigger
27
27
  @options = options
28
28
  @logger = Eye::Logger.new(logger_prefix, "trigger")
29
29
 
30
- debug "add trigger #{options}"
30
+ debug "add #{options}"
31
31
  end
32
32
 
33
33
  def check(states_history)
@@ -1,20 +1,24 @@
1
1
  class Eye::Trigger::Flapping < Eye::Trigger
2
2
 
3
- # triggers :flapping, :times => 10, :within => 1.minute
3
+ # triggers :flapping, :times => 10, :within => 1.minute,
4
+ # :retry_in => 10.minutes, :retry_times => 15
4
5
 
5
- param :times, [Fixnum], true
6
+ param :times, [Fixnum], true, 5
6
7
  param :within, [Float, Fixnum], true
8
+ param :retry_in, [Float, Fixnum]
9
+ param :retry_times, [Fixnum]
7
10
 
8
- def good?
9
- return true unless within
10
-
11
- states = @states_history.states_for_period( within )
11
+ def initialize(*args)
12
+ super
13
+ @last_at = nil
14
+ end
12
15
 
13
- starting_count = states.count{|st| st == :starting}
14
- down_count = states.count{|st| st == :down}
15
- times_count = times || 5
16
+ def good?
17
+ states = @states_history.states_for_period( within, @last_at )
18
+ down_count = states.count{|st| st == :down }
16
19
 
17
- if (starting_count >= times_count) && (down_count >= times_count)
20
+ if down_count >= times
21
+ @last_at = @states_history.last_state_changed_at
18
22
  false
19
23
  else
20
24
  true
@@ -77,6 +77,26 @@ describe "find_objects" do
77
77
  subject.find_objects("asdfasdf").should == []
78
78
  end
79
79
 
80
+ describe "submatching without * " do
81
+ it "match by start symbols, apps" do
82
+ objs = subject.find_objects("app")
83
+ objs.map{|c| c.class}.should == [Eye::Application, Eye::Application]
84
+ objs.map{|c| c.name}.sort.should == %w{app1 app2}
85
+ end
86
+
87
+ it "match by start symbols, groups" do
88
+ objs = subject.find_objects("gr")
89
+ objs.map{|c| c.class}.should == [Eye::Group, Eye::Group]
90
+ objs.map{|c| c.name}.sort.should == %w{gr1 gr2}
91
+ end
92
+
93
+ it "match by start symbols, process" do
94
+ objs = subject.find_objects("z")
95
+ objs.map{|c| c.class}.should == [Eye::Process]
96
+ objs.map{|c| c.name}.sort.should == %w{z1}
97
+ end
98
+ end
99
+
80
100
  describe "find by routes" do
81
101
  it "group" do
82
102
  objs = subject.find_objects("app1:gr2")
@@ -102,7 +122,7 @@ describe "find_objects" do
102
122
  subject{ c = Eye::Controller.new; c.load(fixture("dsl/load_dubls.eye")); c }
103
123
 
104
124
  it "not found" do
105
- subject.find_objects("z").should == []
125
+ subject.find_objects("zu").should == []
106
126
  end
107
127
 
108
128
  it "found 2 processed" do
@@ -139,11 +159,11 @@ describe "find_objects" do
139
159
 
140
160
  describe "missing" do
141
161
  it "should not found" do
142
- subject.find_objects("app1:gr2:q").should == []
143
- subject.find_objects("gr1:p1").should == []
162
+ subject.find_objects("app1:gr4").should == []
163
+ subject.find_objects("gr:p").should == []
144
164
  subject.find_objects("pp1").should == []
145
165
  subject.find_objects("app1::").should == []
146
- subject.find_objects("app1:").should == []
166
+ subject.find_objects("app1:=").should == []
147
167
  end
148
168
  end
149
169
 
@@ -155,4 +155,14 @@ describe "Scheduler" do
155
155
  @process.test3.should == [1, :bla, 3]
156
156
  end
157
157
  end
158
+
159
+ describe "schedule_in" do
160
+ it "should schedule to future" do
161
+ @process.schedule_in(1.second, :scheduler_test3, 1, 2, 3)
162
+ sleep 0.5
163
+ @process.test3.should == nil
164
+ sleep 0.6
165
+ @process.test3.should == [1,2,3]
166
+ end
167
+ end
158
168
  end
@@ -26,6 +26,12 @@ describe "Eye::Process::StatesHistory" do
26
26
  @h.states_for_period(1.5.minutes).should == [:up, :down]
27
27
  @h.states_for_period(2.5.minutes).should == [:stop, :up, :down]
28
28
  @h.states_for_period(6.minutes).should == [:up, :down, :start, :stop, :up, :down]
29
+
30
+ # with start_point
31
+ @h.states_for_period(2.5.minutes, 5.minutes.ago).should == [:stop, :up, :down]
32
+ @h.states_for_period(2.5.minutes, nil).should == [:stop, :up, :down]
33
+ @h.states_for_period(2.5.minutes, 1.5.minutes.ago).should == [:up, :down]
34
+ @h.states_for_period(2.5.minutes, Time.now).should == []
29
35
  end
30
36
 
31
37
  it "seq?" do
@@ -42,6 +42,7 @@ describe "Flapping" do
42
42
 
43
43
  @process.state_name.should == :unmonitored
44
44
  @process.watchers.keys.should == []
45
+ @process.states_history.states.last(2).should == [:down, :unmonitored]
45
46
  end
46
47
 
47
48
  it "process flapping emulate with kill" do
@@ -49,14 +50,37 @@ describe "Flapping" do
49
50
 
50
51
  @process.start
51
52
 
52
- # 4 times because, flapping flag, check on next switch
53
- 4.times do
53
+ 3.times do
54
54
  die_process!(@process.pid)
55
55
  sleep 3
56
56
  end
57
57
 
58
58
  @process.state_name.should == :unmonitored
59
59
  @process.watchers.keys.should == []
60
+
61
+ # ! should switched to unmonitored from down status
62
+ @process.states_history.states.last(2).should == [:down, :unmonitored]
63
+ end
64
+
65
+ it "process flapping, and then send to start and fast kill, should ok started" do
66
+ @process = process(@c.merge(:triggers => C.flapping(:times => 3, :within => 15)))
67
+
68
+ @process.start
69
+
70
+ 3.times do
71
+ die_process!(@process.pid)
72
+ sleep 3
73
+ end
74
+
75
+ @process.state_name.should == :unmonitored
76
+ @process.watchers.keys.should == []
77
+
78
+ @process.start
79
+ @process.state_name.should == :up
80
+
81
+ die_process!(@process.pid)
82
+ sleep 4
83
+ @process.state_name.should == :up
60
84
  end
61
85
 
62
86
  it "flapping not happens" do
@@ -67,15 +91,152 @@ describe "Flapping" do
67
91
  proxy(@process).schedule(:check_crash, anything)
68
92
  dont_allow(@process).schedule(:unmonitor)
69
93
 
94
+ sleep 2
70
95
 
71
- sleep 5
72
-
73
- # even if process die in middle
74
- die_process!(@process.pid)
96
+ 2.times do
97
+ die_process!(@process.pid)
98
+ sleep 3
99
+ end
75
100
 
76
- sleep 5
101
+ sleep 2
77
102
 
78
103
  @process.state_name.should == :up
79
104
  end
80
105
 
106
+ describe "retry_in, retry_times" do
107
+ before :each do
108
+ @c = C.p1.merge(
109
+ :triggers => C.flapping(:times => 2, :within => 3),
110
+ :start_grace => 0.1, # for fast flapping
111
+ :stop_grace => 0,
112
+ :start_command => @c[:start_command] + " -r"
113
+ )
114
+ end
115
+
116
+ it "flapping than wait for interval and try again" do
117
+ @process = process(@c.merge(:triggers => C.flapping(:times => 2, :within => 3,
118
+ :retry_in => 5.seconds)))
119
+ @process.start!
120
+
121
+ sleep 18
122
+
123
+ h = @process.states_history
124
+
125
+ # был в unmonitored
126
+ h.shift[:state].should == :unmonitored
127
+
128
+ # должен попытаться подняться два раза,
129
+ h.shift(6)
130
+
131
+ # затем перейти в unmonitored с причиной flapping
132
+ flapp1 = h.shift
133
+ flapp1[:state].should == :unmonitored
134
+ flapp1[:reason].should == 'flapping'
135
+
136
+ # затем снова попыться подняться два раза
137
+ h.shift(6)
138
+
139
+ # и снова перейти в unmonitored с причиной flapping
140
+ flapp2 = h.shift
141
+ flapp2[:state].should == :unmonitored
142
+ flapp2[:reason].should == 'flapping'
143
+
144
+ # интервал между переходами во flapping должен быть больше 8 сек
145
+ (flapp2[:at] - flapp1[:at]).should > 5.seconds
146
+
147
+ # тут снова должен пытаться подниматься так как нет лимитов
148
+ h.should_not be_blank
149
+ end
150
+
151
+ it "flapping retry 1 times with retry_times = 1" do
152
+ @process = process(@c.merge(:triggers => C.flapping(:times => 2, :within => 3,
153
+ :retry_in => 5.seconds, :retry_times => 1)))
154
+ @process.start!
155
+
156
+ sleep 18
157
+
158
+ h = @process.states_history
159
+
160
+ # был в unmonitored
161
+ h.shift[:state].should == :unmonitored
162
+
163
+ # должен попытаться подняться два раза,
164
+ h.shift(6)
165
+
166
+ # затем перейти в unmonitored с причиной flapping
167
+ flapp1 = h.shift
168
+ flapp1[:state].should == :unmonitored
169
+ flapp1[:reason].should == 'flapping'
170
+
171
+ # затем снова попыться подняться два раза
172
+ h.shift(6)
173
+
174
+ # и снова перейти в unmonitored с причиной flapping
175
+ flapp2 = h.shift
176
+ flapp2[:state].should == :unmonitored
177
+ flapp2[:reason].should == 'flapping'
178
+
179
+ # интервал между переходами во flapping должен быть больше 8 сек
180
+ (flapp2[:at] - flapp1[:at]).should > 5.seconds
181
+
182
+ # все финал
183
+ h.should be_blank
184
+ end
185
+
186
+ it "flapping than manually doing something, should not retry" do
187
+ @process = process(@c.merge(:triggers => C.flapping(:times => 2, :within => 3,
188
+ :retry_in => 5.seconds)))
189
+ @process.start!
190
+
191
+ sleep 6
192
+ @process.send_command :unmonitor
193
+ sleep 9
194
+
195
+ h = @process.states_history
196
+
197
+ # был в unmonitored
198
+ h.shift[:state].should == :unmonitored
199
+
200
+ # должен попытаться подняться два раза,
201
+ h.shift(6)
202
+
203
+ # затем перейти в unmonitored с причиной flapping
204
+ flapp1 = h.shift
205
+ flapp1[:state].should == :unmonitored
206
+ flapp1[:reason].should == 'flapping'
207
+
208
+ # затем его руками переводят в unmonitored
209
+ unm = h.shift
210
+ unm[:state].should == :unmonitored
211
+ unm[:reason].should == 'unmonitor by user'
212
+
213
+ # все финал
214
+ h.should be_blank
215
+ end
216
+
217
+ it "without retry_in" do
218
+ @process = process(@c.merge(:triggers => C.flapping(:times => 2, :within => 3)))
219
+ @process.start!
220
+
221
+ sleep 10
222
+
223
+ h = @process.states_history
224
+
225
+ # был в unmonitored
226
+ h.shift[:state].should == :unmonitored
227
+
228
+ # должен попытаться подняться два раза,
229
+ h.shift(6)
230
+
231
+ # затем перейти в unmonitored с причиной flapping
232
+ flapp1 = h.shift
233
+ flapp1[:state].should == :unmonitored
234
+ flapp1[:reason].should == 'flapping'
235
+
236
+ # все финал
237
+ h.should be_blank
238
+ end
239
+
240
+ end
241
+
81
242
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eye
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Makarchev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-03-30 00:00:00.000000000 Z
11
+ date: 2013-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: celluloid
@@ -42,16 +42,16 @@ dependencies:
42
42
  name: state_machine
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - <
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '1.2'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - <
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '1.2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: activesupport
57
57
  requirement: !ruby/object:Gem::Requirement