flapjack 1.0.0rc2 → 1.0.0rc3
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 +4 -4
- data/.travis.yml +7 -3
- data/CHANGELOG.md +5 -0
- data/features/notification_rules.feature +2 -3
- data/features/rollup.feature +18 -0
- data/features/steps/events_steps.rb +8 -8
- data/features/support/env.rb +26 -30
- data/lib/flapjack/data/notification.rb +3 -1
- data/lib/flapjack/gateways/jsonapi/check_presenter.rb +6 -6
- data/lib/flapjack/gateways/jsonapi/report_methods.rb +3 -1
- data/lib/flapjack/gateways/web.rb +8 -6
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/gateways/jsonapi/check_presenter_spec.rb +24 -12
- data/spec/spec_helper.rb +9 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d91c5e561fafc64a7698f898d1c02b6f00386c97
|
4
|
+
data.tar.gz: c7c4e4ba3e84c4a59ff09964fc67278c12b2fb04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6efd501448115a5a97a23df6ece95b2f53bf2b4a01d4fe268c1ae851bdb850aaa5d242497f2c5e4871f5b945076151081ae7df33b6eb15fe6e01f06d99a8cda0
|
7
|
+
data.tar.gz: d11a32ab75ad2cbcf7b9f9d432cd6f5dfd2da498b127a2c653291f55cf08b7f7eafbc9da9894510b94bbde7d9edcc5ac9ede703761c612563d5e32b2851dabad
|
data/.travis.yml
CHANGED
@@ -14,11 +14,15 @@ before_install:
|
|
14
14
|
- gem install bundler
|
15
15
|
script: bundle exec rspec spec && bundle exec cucumber features
|
16
16
|
notifications:
|
17
|
-
irc:
|
17
|
+
irc:
|
18
|
+
channels:
|
19
|
+
- 'irc.freenode.net#flapjack'
|
20
|
+
template:
|
21
|
+
- '%{message} %{repository}#%{build_number} (%{branch} - %{commit} : %{author})'
|
18
22
|
hipchat:
|
19
23
|
template:
|
20
|
-
|
21
|
-
|
24
|
+
- '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}
|
25
|
+
(<a href="%{build_url}">Details</a>/<a href="%{compare_url}">Change view</a>)'
|
22
26
|
format: html
|
23
27
|
rooms:
|
24
28
|
secure: ajMolTKDuprYJ9Fadcjb3evh80MyJSgjW4hl4OWnEHyrjQLnsO0hbAvSrKRFUzorMoi58L8XZXd01gMgRqRxMvwqfoHLv4njw8px4X9+F/hySPZj3aMAFM1HuoTmHqeP+Rl+1Ssg+Kss6N4JkgNS81s+tkRnnoHG1n/EhfH6PkE=
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## Flapjack Changelog
|
2
2
|
|
3
|
+
# 1.0.0rc3 - 2014-07-24
|
4
|
+
- Bug: fix jsonapi methods for report checks/methods, maintenance reports, outage reports (@ali-graham)
|
5
|
+
- Bug: improve tests around notification rules and rollup, tweak rollup behaviour on recovery (@ali-graham)
|
6
|
+
- Bug: fix handling of base_url config warnings in web gateway #560 (@jessereynolds)
|
7
|
+
|
3
8
|
# 1.0.0rc2 - 2014-07-17
|
4
9
|
- Feature: jsonapi GET /checks badab0d (@ali-graham)
|
5
10
|
- Feature: Add HTTP broker, one-off event submitter, general Go support #553 (@auxesis)
|
@@ -96,7 +96,6 @@ Feature: Notification rules on a per contact basis
|
|
96
96
|
When a critical event is received
|
97
97
|
Then 3 email alerts should be queued for malak@example.com
|
98
98
|
When the time is February 1 2013 18:01
|
99
|
-
Then all alert dropping keys for user c1 should have expired
|
100
99
|
When a critical event is received
|
101
100
|
Then 3 email alerts should be queued for malak@example.com
|
102
101
|
|
@@ -210,7 +209,7 @@ Feature: Notification rules on a per contact basis
|
|
210
209
|
When 10 minutes passes
|
211
210
|
And a critical event is received
|
212
211
|
Then 1 email alert should be queued for malak@example.com
|
213
|
-
When
|
212
|
+
When 6 minutes passes
|
214
213
|
And a critical event is received
|
215
214
|
Then 2 email alerts should be queued for malak@example.com
|
216
215
|
|
@@ -226,7 +225,7 @@ Feature: Notification rules on a per contact basis
|
|
226
225
|
When 10 minutes passes
|
227
226
|
And an unknown event is received
|
228
227
|
Then 1 email alert should be queued for malak@example.com
|
229
|
-
When
|
228
|
+
When 6 minutes passes
|
230
229
|
And an unknown event is received
|
231
230
|
Then 2 email alerts should be queued for malak@example.com
|
232
231
|
|
data/features/rollup.feature
CHANGED
@@ -214,3 +214,21 @@ Feature: Rollup on a per contact, per media basis
|
|
214
214
|
Then 2 sms alerts of type problem and rollup none should be queued for +61400000001
|
215
215
|
And 3 sms alerts should be queued for +61400000001
|
216
216
|
|
217
|
+
@time
|
218
|
+
Scenario: Multiple notifications should not occur when in rollup
|
219
|
+
Given check 'ping' for entity 'foo' is in an ok state
|
220
|
+
And check 'ping' for entity 'baz' is in an ok state
|
221
|
+
When a critical event is received for check 'ping' on entity 'foo'
|
222
|
+
And a critical event is received for check 'ping' on entity 'baz'
|
223
|
+
And 1 minute passes
|
224
|
+
And a critical event is received for check 'ping' on entity 'foo'
|
225
|
+
And a critical event is received for check 'ping' on entity 'baz'
|
226
|
+
And 1 minute passes
|
227
|
+
And a critical event is received for check 'ping' on entity 'foo'
|
228
|
+
Then 1 email alert of type problem and rollup problem should be queued for malak@example.com
|
229
|
+
And 19 minutes passes
|
230
|
+
And a critical event is received for check 'ping' on entity 'baz'
|
231
|
+
Then 2 email alerts of type problem and rollup problem should be queued for malak@example.com
|
232
|
+
And 1 minute passes
|
233
|
+
And a critical event is received for check 'ping' on entity 'baz'
|
234
|
+
Then 2 email alerts of type problem and rollup problem should be queued for malak@example.com
|
@@ -395,7 +395,7 @@ Given /^user (\S+) has the following notification rules:$/ do |contact_id, rules
|
|
395
395
|
end
|
396
396
|
|
397
397
|
Then /^all alert dropping keys for user (\S+) should have expired$/ do |contact_id|
|
398
|
-
expect(@redis.keys("drop_alerts_for_contact:#{contact_id}
|
398
|
+
expect(@redis.keys("drop_alerts_for_contact:#{contact_id}:*")).to be_empty
|
399
399
|
end
|
400
400
|
|
401
401
|
Then /^(\w+) (\w+) alert(?:s)?(?: of)?(?: type (\w+))?(?: and)?(?: rollup (\w+))? should be queued for (.*)$/ do |num_queued, media, notification_type, rollup, address|
|
@@ -408,13 +408,13 @@ Then /^(\w+) (\w+) alert(?:s)?(?: of)?(?: type (\w+))?(?: and)?(?: rollup (\w+))
|
|
408
408
|
queue = Resque.peek("#{media}_notifications", 0, 30)
|
409
409
|
queued_length = queue.find_all {|n|
|
410
410
|
type_ok = notification_type ? ( n['args'].first['notification_type'] == notification_type ) : true
|
411
|
-
rollup_ok =
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
411
|
+
rollup_ok = case rollup
|
412
|
+
when 'none'
|
413
|
+
n['args'].first['rollup'].nil?
|
414
|
+
when nil, n['args'].first['rollup']
|
415
|
+
true
|
416
|
+
else
|
417
|
+
false
|
418
418
|
end
|
419
419
|
type_ok && rollup_ok && ( n['args'].first['address'] == address )
|
420
420
|
}.length
|
data/features/support/env.rb
CHANGED
@@ -68,27 +68,31 @@ end
|
|
68
68
|
|
69
69
|
class RedisDelorean
|
70
70
|
|
71
|
-
|
71
|
+
ShiftTTLsScript = <<SHIFT_TTLS
|
72
72
|
local keys = redis.call('keys', KEYS[1])
|
73
|
-
local
|
74
|
-
local
|
75
|
-
local counter = 0
|
73
|
+
local ttl_decrease = tonumber(ARGV[1])
|
74
|
+
local deleted = 0
|
76
75
|
|
77
76
|
for i,k in ipairs(keys) do
|
78
77
|
local key_ttl = redis.call('ttl', k)
|
79
78
|
|
80
79
|
-- key does not have timeout if < 0
|
81
|
-
if
|
82
|
-
|
83
|
-
|
80
|
+
if key_ttl >= 0 then
|
81
|
+
local diff = key_ttl - ttl_decrease
|
82
|
+
if diff <= 0 then
|
83
|
+
redis.call('del', k)
|
84
|
+
deleted = deleted + 1
|
85
|
+
else
|
86
|
+
redis.call('expire', k, diff)
|
87
|
+
end
|
84
88
|
end
|
85
89
|
end
|
86
|
-
return
|
87
|
-
|
90
|
+
return deleted
|
91
|
+
SHIFT_TTLS
|
88
92
|
|
89
93
|
def self.before_all(options = {})
|
90
94
|
redis = options[:redis]
|
91
|
-
@
|
95
|
+
@shift_ttls_sha = redis.script(:load, ShiftTTLsScript)
|
92
96
|
end
|
93
97
|
|
94
98
|
def self.before_each(options = {})
|
@@ -96,28 +100,20 @@ EXPIRE_AS_IF_AT
|
|
96
100
|
end
|
97
101
|
|
98
102
|
def self.time_travel_to(dest_time)
|
99
|
-
#puts "travelling to #{Time.now.in_time_zone}, real time is #{Time.now_without_delorean.in_time_zone}"
|
100
103
|
old_maybe_fake_time = Time.now.in_time_zone
|
101
|
-
|
102
104
|
Delorean.time_travel_to(dest_time)
|
103
|
-
#puts "
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
#
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
real_time = Time.now_without_delorean.to_i
|
116
|
-
#puts "delta #{delta}, expire before real time #{Time.at(real_time + delta)}"
|
117
|
-
|
118
|
-
result = @redis.evalsha(@expire_as_if_at_sha, ['*'],
|
119
|
-
[real_time, real_time + delta])
|
120
|
-
#puts "Expired #{result} key#{(result == 1) ? '' : 's'}"
|
105
|
+
# puts "real time is #{Time.now_without_delorean.in_time_zone}"
|
106
|
+
# puts "old assumed time is #{old_maybe_fake_time}"
|
107
|
+
# puts "new assumed time is #{Time.now.in_time_zone}"
|
108
|
+
|
109
|
+
# keys_prior = @redis.keys('*')
|
110
|
+
|
111
|
+
del = @redis.evalsha(@shift_ttls_sha, ['*'],
|
112
|
+
[(dest_time - old_maybe_fake_time).to_i])
|
113
|
+
|
114
|
+
# keys_after = @redis.keys('*')
|
115
|
+
|
116
|
+
# puts "Expired #{del} key#{(del == 1) ? '' : 's'}, #{(keys_prior - keys_after).inspect}"
|
121
117
|
end
|
122
118
|
|
123
119
|
end
|
@@ -224,6 +224,7 @@ module Flapjack
|
|
224
224
|
unless ['pagerduty'].include?(media)
|
225
225
|
alerting_checks = contact.count_alerting_checks_for_media(media)
|
226
226
|
rollup_threshold = contact.rollup_threshold_for_media(media)
|
227
|
+
|
227
228
|
case
|
228
229
|
when rollup_threshold.nil?
|
229
230
|
# back away slowly
|
@@ -231,8 +232,9 @@ module Flapjack
|
|
231
232
|
next ret if contact.drop_rollup_notifications_for_media?(media)
|
232
233
|
contact.update_sent_rollup_alert_keys_for_media(media, :delete => ok?)
|
233
234
|
rollup_type = 'problem'
|
234
|
-
when (alerting_checks + cleaned >= rollup_threshold
|
235
|
+
when (alerting_checks + cleaned) >= rollup_threshold
|
235
236
|
# alerting checks was just cleaned such that it is now below the rollup threshold
|
237
|
+
contact.update_sent_rollup_alert_keys_for_media(media, :delete => true)
|
236
238
|
rollup_type = 'recovery'
|
237
239
|
end
|
238
240
|
logger.debug "rollup decisions: #{@event_id} #{@state} #{media} #{address} rollup_type: #{rollup_type}"
|
@@ -81,7 +81,7 @@ module Flapjack
|
|
81
81
|
}
|
82
82
|
end
|
83
83
|
|
84
|
-
result
|
84
|
+
{:outages => result}
|
85
85
|
end
|
86
86
|
|
87
87
|
def unscheduled_maintenance(start_time, end_time)
|
@@ -96,7 +96,7 @@ module Flapjack
|
|
96
96
|
pu[:end_time] >= start_time
|
97
97
|
}
|
98
98
|
|
99
|
-
start_in_unsched + unsched_maintenance
|
99
|
+
{:unscheduled_maintenances => (start_in_unsched + unsched_maintenance)}
|
100
100
|
end
|
101
101
|
|
102
102
|
def scheduled_maintenance(start_time, end_time)
|
@@ -111,7 +111,7 @@ module Flapjack
|
|
111
111
|
ps[:end_time] >= start_time
|
112
112
|
}
|
113
113
|
|
114
|
-
start_in_sched + sched_maintenance
|
114
|
+
{:scheduled_maintenances => (start_in_sched + sched_maintenance)}
|
115
115
|
end
|
116
116
|
|
117
117
|
# TODO test whether the below overlapping logic is prone to off-by-one
|
@@ -120,9 +120,7 @@ module Flapjack
|
|
120
120
|
#
|
121
121
|
# TODO test performance with larger data sets
|
122
122
|
def downtime(start_time, end_time)
|
123
|
-
|
124
|
-
|
125
|
-
outs = outage(start_time, end_time)
|
123
|
+
outs = outage(start_time, end_time)[:outages]
|
126
124
|
|
127
125
|
total_secs = {}
|
128
126
|
percentages = {}
|
@@ -139,6 +137,8 @@ module Flapjack
|
|
139
137
|
# We then create two new outage periods to cover the time around
|
140
138
|
# the scheduled maintenance period, and remove the original.
|
141
139
|
|
140
|
+
sched_maintenances = scheduled_maintenance(start_time, end_time)[:scheduled_maintenances]
|
141
|
+
|
142
142
|
sched_maintenances.each do |sm|
|
143
143
|
|
144
144
|
split_outs = []
|
@@ -27,7 +27,9 @@ module Flapjack
|
|
27
27
|
end
|
28
28
|
|
29
29
|
checks = if event_ids.nil?
|
30
|
-
Flapjack::Data::EntityCheck.find_all(:redis => redis)
|
30
|
+
Flapjack::Data::EntityCheck.find_all(:redis => redis).collect {|check_name|
|
31
|
+
find_entity_check_by_name(*check_name.split(':', 2))
|
32
|
+
}
|
31
33
|
elsif !event_ids.empty?
|
32
34
|
event_ids.collect {|event_id| find_entity_check_by_name(*event_id.split(':', 2)) }
|
33
35
|
else
|
@@ -68,12 +68,14 @@ module Flapjack
|
|
68
68
|
end
|
69
69
|
|
70
70
|
@base_url = @config['base_url']
|
71
|
-
|
72
|
-
@base_url
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
71
|
+
unless @base_url
|
72
|
+
@logger.info "base_url is not configured, setting to '/'"
|
73
|
+
@base_url = '/'
|
74
|
+
end
|
75
|
+
|
76
|
+
unless @base_url.match(/^.*\/$/)
|
77
|
+
@logger.warn "base_url must end with a trailing '/', setting to '#{@base_url}/'"
|
78
|
+
@base_url = "#{@base_url}/"
|
77
79
|
end
|
78
80
|
|
79
81
|
# constants won't be exposed to eRb scope
|
data/lib/flapjack/version.rb
CHANGED
@@ -47,8 +47,10 @@ describe 'Flapjack::Gateways::JSONAPI::CheckPresenter' do
|
|
47
47
|
ecp = Flapjack::Gateways::JSONAPI::CheckPresenter.new(entity_check)
|
48
48
|
outages = ecp.outage(time - (5 * 60 * 60), time - (2 * 60 * 60))
|
49
49
|
expect(outages).not_to be_nil
|
50
|
-
expect(outages).to
|
51
|
-
expect(outages
|
50
|
+
expect(outages).to be_a(Hash)
|
51
|
+
expect(outages).to have_key(:outages)
|
52
|
+
expect(outages[:outages]).to be_an(Array)
|
53
|
+
expect(outages[:outages].size).to eq(4)
|
52
54
|
|
53
55
|
# TODO check the data in those hashes
|
54
56
|
end
|
@@ -63,8 +65,10 @@ describe 'Flapjack::Gateways::JSONAPI::CheckPresenter' do
|
|
63
65
|
ecp = Flapjack::Gateways::JSONAPI::CheckPresenter.new(entity_check)
|
64
66
|
outages = ecp.outage(nil, nil)
|
65
67
|
expect(outages).not_to be_nil
|
66
|
-
expect(outages).to
|
67
|
-
expect(outages
|
68
|
+
expect(outages).to be_a(Hash)
|
69
|
+
expect(outages).to have_key(:outages)
|
70
|
+
expect(outages[:outages]).to be_an(Array)
|
71
|
+
expect(outages[:outages].size).to eq(4)
|
68
72
|
|
69
73
|
# TODO check the data in those hashes
|
70
74
|
end
|
@@ -82,8 +86,10 @@ describe 'Flapjack::Gateways::JSONAPI::CheckPresenter' do
|
|
82
86
|
ecp = Flapjack::Gateways::JSONAPI::CheckPresenter.new(entity_check)
|
83
87
|
outages = ecp.outage(nil, nil)
|
84
88
|
expect(outages).not_to be_nil
|
85
|
-
expect(outages).to
|
86
|
-
expect(outages
|
89
|
+
expect(outages).to be_a(Hash)
|
90
|
+
expect(outages).to have_key(:outages)
|
91
|
+
expect(outages[:outages]).to be_an(Array)
|
92
|
+
expect(outages[:outages].size).to eq(3)
|
87
93
|
end
|
88
94
|
|
89
95
|
it "returns a (small) outage hash for a single state change" do
|
@@ -95,8 +101,10 @@ describe 'Flapjack::Gateways::JSONAPI::CheckPresenter' do
|
|
95
101
|
ecp = Flapjack::Gateways::JSONAPI::CheckPresenter.new(entity_check)
|
96
102
|
outages = ecp.outage(nil, nil)
|
97
103
|
expect(outages).not_to be_nil
|
98
|
-
expect(outages).to
|
99
|
-
expect(outages
|
104
|
+
expect(outages).to be_a(Hash)
|
105
|
+
expect(outages).to have_key(:outages)
|
106
|
+
expect(outages[:outages]).to be_an(Array)
|
107
|
+
expect(outages[:outages].size).to eq(1)
|
100
108
|
end
|
101
109
|
|
102
110
|
it "a list of unscheduled maintenances for an entity check" do
|
@@ -109,8 +117,10 @@ describe 'Flapjack::Gateways::JSONAPI::CheckPresenter' do
|
|
109
117
|
ecp = Flapjack::Gateways::JSONAPI::CheckPresenter.new(entity_check)
|
110
118
|
unsched_maint = ecp.unscheduled_maintenance(time - (12 * 60 * 60), time)
|
111
119
|
|
112
|
-
expect(unsched_maint).to
|
113
|
-
expect(unsched_maint
|
120
|
+
expect(unsched_maint).to be_a(Hash)
|
121
|
+
expect(unsched_maint).to have_key(:unscheduled_maintenances)
|
122
|
+
expect(unsched_maint[:unscheduled_maintenances]).to be_an(Array)
|
123
|
+
expect(unsched_maint[:unscheduled_maintenances].size).to eq(4)
|
114
124
|
|
115
125
|
# TODO check the data in those hashes
|
116
126
|
end
|
@@ -125,8 +135,10 @@ describe 'Flapjack::Gateways::JSONAPI::CheckPresenter' do
|
|
125
135
|
ecp = Flapjack::Gateways::JSONAPI::CheckPresenter.new(entity_check)
|
126
136
|
sched_maint = ecp.scheduled_maintenance(time - (12 * 60 * 60), time)
|
127
137
|
|
128
|
-
expect(sched_maint).to
|
129
|
-
expect(sched_maint
|
138
|
+
expect(sched_maint).to be_a(Hash)
|
139
|
+
expect(sched_maint).to have_key(:scheduled_maintenances)
|
140
|
+
expect(sched_maint[:scheduled_maintenances]).to be_an(Array)
|
141
|
+
expect(sched_maint[:scheduled_maintenances].size).to eq(4)
|
130
142
|
|
131
143
|
# TODO check the data in those hashes
|
132
144
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
if ENV['COVERAGE']
|
2
2
|
require 'simplecov'
|
3
|
-
|
4
3
|
SimpleCov.start do
|
5
4
|
add_filter '/spec/'
|
6
5
|
end
|
6
|
+
SimpleCov.at_exit do
|
7
|
+
Oj.default_options = { :mode => :compat }
|
8
|
+
SimpleCov.result.format!
|
9
|
+
end
|
7
10
|
end
|
8
11
|
|
9
12
|
$testing = true
|
@@ -16,16 +19,17 @@ ENV['RACK_ENV'] = ENV["FLAPJACK_ENV"]
|
|
16
19
|
require 'bundler'
|
17
20
|
Bundler.require(:default, :test)
|
18
21
|
|
22
|
+
require 'oj'
|
23
|
+
Oj.default_options = { :indent => 0, :mode => :strict }
|
24
|
+
Oj.mimic_JSON
|
25
|
+
require 'active_support/json'
|
26
|
+
|
19
27
|
require 'webmock/rspec'
|
20
28
|
WebMock.disable_net_connect!
|
21
29
|
|
22
30
|
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
23
31
|
require 'flapjack/configuration'
|
24
32
|
|
25
|
-
require 'oj'
|
26
|
-
Oj.mimic_JSON
|
27
|
-
Oj.default_options = { :indent => 0, :mode => :strict }
|
28
|
-
require 'active_support/json'
|
29
33
|
|
30
34
|
# Requires supporting files with custom matchers and macros, etc,
|
31
35
|
# in ./support/ and its subdirectories.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flapjack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.0rc3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lindsay Holmwood
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-07-
|
13
|
+
date: 2014-07-24 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: dante
|