sensu-plugins-aws 0.0.1.alpha.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,144 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # ec2-count-metrics
4
+ #
5
+ # DESCRIPTION:
6
+ # This plugin retrives number of EC2 status
7
+ #
8
+ # OUTPUT:
9
+ # plain-text
10
+ #
11
+ # PLATFORMS:
12
+ # Linux
13
+ #
14
+ # DEPENDENCIES:
15
+ # gem: aws-sdk
16
+ # gem: sensu-plugin
17
+ #
18
+ # USAGE:
19
+ # #YELLOW
20
+ #
21
+ # NOTES:
22
+ #
23
+ # LICENSE:
24
+ # Copyright (c) 2014, Tim Smith, tim@cozy.co
25
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
26
+ # for details.
27
+ #
28
+
29
+ require 'rubygems' if RUBY_VERSION < '1.9.0'
30
+ require 'sensu-plugin/metric/cli'
31
+ require 'aws-sdk'
32
+
33
+ class EC2Metrics < Sensu::Plugin::Metric::CLI::Graphite
34
+ option :scheme,
35
+ description: 'Metric naming scheme, text to prepend to metric',
36
+ short: '-s SCHEME',
37
+ long: '--scheme SCHEME',
38
+ default: 'sensu.aws.ec2'
39
+
40
+ option :aws_access_key,
41
+ short: '-a AWS_ACCESS_KEY',
42
+ long: '--aws-access-key AWS_ACCESS_KEY',
43
+ description: "AWS Access Key. Either set ENV['AWS_ACCESS_KEY_ID'] or provide it as an option"
44
+
45
+ option :aws_secret_access_key,
46
+ short: '-k AWS_SECRET_ACCESS_KEY',
47
+ long: '--aws-secret-access-key AWS_SECRET_ACCESS_KEY',
48
+ description: "AWS Secret Access Key. Either set ENV['AWS_SECRET_ACCESS_KEY'] or provide it as an option"
49
+
50
+ option :aws_region,
51
+ short: '-r AWS_REGION',
52
+ long: '--aws-region REGION',
53
+ description: 'AWS Region (such as us-east-1).',
54
+ default: 'us-east-1'
55
+
56
+ option :type,
57
+ short: '-t METRIC type',
58
+ long: '--type METRIC type',
59
+ description: 'Count by type: status, instance',
60
+ default: 'instance'
61
+
62
+ def aws_config
63
+ hash = {}
64
+ hash.update access_key_id: config[:access_key_id], secret_access_key: config[:secret_access_key] if config[:access_key_id] && config[:secret_access_key]
65
+ hash.update region: config[:aws_region]
66
+ hash
67
+ end
68
+
69
+ def run
70
+ begin
71
+
72
+ client = AWS::EC2::Client.new aws_config
73
+
74
+ def by_instances_status(client)
75
+ if config[:scheme] == 'sensu.aws.ec2'
76
+ config[:scheme] += '.count'
77
+ end
78
+
79
+ options = { include_all_instances: true }
80
+ data = client.describe_instance_status(options)
81
+
82
+ total = data[:instance_status_set].count
83
+ status = {}
84
+
85
+ unless total.nil?
86
+ data[:instance_status_set].each do |value|
87
+ stat = value[:instance_state][:name]
88
+ if status[stat].nil?
89
+ status[stat] = 1
90
+ else
91
+ status[stat] = status[stat] + 1
92
+ end
93
+ end
94
+ end
95
+
96
+ unless data.nil?
97
+ # We only return data when we have some to return
98
+ output config[:scheme] + '.total', total
99
+ status.each do |name, count|
100
+ output config[:scheme] + ".#{name}", count
101
+ end
102
+ end
103
+ end
104
+
105
+ def by_instances_type(client)
106
+ if config[:scheme] == 'sensu.aws.ec2'
107
+ config[:scheme] += '.types'
108
+ end
109
+
110
+ data = {}
111
+
112
+ instances = client.describe_instances
113
+ instances[:reservation_set].each do |i|
114
+ i[:instances_set].each do |instance|
115
+ type = instance[:instance_type]
116
+ if data[type].nil?
117
+ data[type] = 1
118
+ else
119
+ data[type] = data[type] + 1
120
+ end
121
+ end
122
+ end
123
+
124
+ unless data.nil?
125
+ # We only return data when we have some to return
126
+ data.each do |name, count|
127
+ output config[:scheme] + ".#{name}", count
128
+ end
129
+ end
130
+ end
131
+
132
+ if config[:type] == 'instance'
133
+ by_instances_type(client)
134
+ elsif config[:type] == 'status'
135
+ by_instances_status(client)
136
+ end
137
+
138
+ rescue => e
139
+ puts "Error: exception: #{e}"
140
+ critical
141
+ end
142
+ ok
143
+ end
144
+ end
data/bin/ec2-node.rb ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # CHANGELOG:
4
+ # * 0.4.0:
5
+ # - Adds ability to specify a list of states an individual client can have in
6
+ # EC2. If none is specified, it filters out 'terminated' and 'stopped'
7
+ # instances by default.
8
+ # - Updates how we are "puts"-ing to the log.
9
+ # * 0.3.0:
10
+ # - Updates handler to additionally filter stopped instances.
11
+ # * 0.2.1:
12
+ # - Updates requested configuration snippets so they'll be redacted by
13
+ # default.
14
+ # * 0.2.0:
15
+ # - Renames handler from chef_ec2_node to ec2_node
16
+ # - Removes Chef-related stuff from handler
17
+ # - Updates documentation
18
+ # * 0.1.0:
19
+ # - Initial release
20
+ #
21
+ # This handler deletes a Sensu client if it's been stopped or terminated in EC2.
22
+ # Optionally, you may specify a client attribute `ec2_states`, a list of valid
23
+ # states an instance may have.
24
+ #
25
+ # NOTE: The implementation for correlating Sensu clients to EC2 instances may
26
+ # need to be modified to fit your organization. The current implementation
27
+ # assumes that Sensu clients' names are the same as their instance IDs in EC2.
28
+ # If this is not the case, you can either sub-class this handler and override
29
+ # `ec2_node_exists?` in your own organization-specific handler, or modify this
30
+ # handler to suit your needs.
31
+ #
32
+ # Requires the following Rubygems (`gem install $GEM`):
33
+ # - sensu-plugin
34
+ # - fog
35
+ #
36
+ # Requires a Sensu configuration snippet:
37
+ # {
38
+ # "aws": {
39
+ # "access_key": "adsafdafda",
40
+ # "secret_key": "qwuieohajladsafhj23nm",
41
+ # "region": "us-east-1c"
42
+ # }
43
+ # }
44
+ #
45
+ # Or you can set the following environment variables:
46
+ # - AWS_ACCESS_KEY_ID
47
+ # - AWS_SECRET_ACCESS_KEY
48
+ # - EC2_REGION
49
+ #
50
+ #
51
+ # To use, you can set it as the keepalive handler for a client:
52
+ # {
53
+ # "client": {
54
+ # "name": "i-424242",
55
+ # "address": "127.0.0.1",
56
+ # "keepalive": {
57
+ # "handler": "ec2_node"
58
+ # },
59
+ # "subscriptions": ["all"]
60
+ # }
61
+ # }
62
+ #
63
+ # You can also use this handler with a filter:
64
+ # {
65
+ # "filters": {
66
+ # "ghost_nodes": {
67
+ # "attributes": {
68
+ # "check": {
69
+ # "name": "keepalive",
70
+ # "status": 2
71
+ # },
72
+ # "occurences": "eval: value > 2"
73
+ # }
74
+ # }
75
+ # },
76
+ # "handlers": {
77
+ # "ec2_node": {
78
+ # "type": "pipe",
79
+ # "command": "/etc/sensu/handlers/ec2_node.rb",
80
+ # "severities": ["warning","critical"],
81
+ # "filter": "ghost_nodes"
82
+ # }
83
+ # }
84
+ # }
85
+ #
86
+ # Copyleft 2013 Yet Another Clever Name
87
+ #
88
+ # Based off of the `chef_node` handler by Heavy Water Operations, LLC
89
+ #
90
+ # Released under the same terms as Sensu (the MIT license); see
91
+ # LICENSE for details
92
+
93
+ require 'timeout'
94
+ require 'rubygems' if RUBY_VERSION < '1.9.0'
95
+ require 'sensu-handler'
96
+ require 'fog'
97
+
98
+ class Ec2Node < Sensu::Handler
99
+ def filter; end
100
+
101
+ def handle
102
+ # #YELLOW
103
+ unless ec2_node_exists? # rubocop:disable UnlessElse
104
+ delete_sensu_client!
105
+ else
106
+ puts "[EC2 Node] #{@event['client']['name']} appears to exist in EC2"
107
+ end
108
+ end
109
+
110
+ def delete_sensu_client!
111
+ response = api_request(:DELETE, '/clients/' + @event['client']['name']).code
112
+ deletion_status(response)
113
+ end
114
+
115
+ def ec2_node_exists?
116
+ states = acquire_valid_states
117
+ filtered_instances = ec2.servers.select { |s| states.include?(s.state) }
118
+ instance_ids = filtered_instances.map(&:id)
119
+ instance_ids.each do |id|
120
+ return true if id == @event['client']['name']
121
+ end
122
+ false # no match found, node doesn't exist
123
+ end
124
+
125
+ def ec2
126
+ @ec2 ||= begin
127
+ key = settings['aws']['access_key'] || ENV['AWS_ACCESS_KEY_ID']
128
+ secret = settings['aws']['secret_key'] || ENV['AWS_SECRET_ACCESS_KEY']
129
+ region = settings['aws']['region'] || ENV['EC2_REGION']
130
+ Fog::Compute.new(provider: 'AWS',
131
+ aws_access_key_id: key,
132
+ aws_secret_access_key: secret,
133
+ region: region)
134
+ end
135
+ end
136
+
137
+ def deletion_status(code)
138
+ case code
139
+ when '202'
140
+ puts "[EC2 Node] 202: Successfully deleted Sensu client: #{node}"
141
+ when '404'
142
+ puts "[EC2 Node] 404: Unable to delete #{node}, doesn't exist!"
143
+ when '500'
144
+ puts "[EC2 Node] 500: Miscellaneous error when deleting #{node}"
145
+ else
146
+ puts "[EC2 Node] #{res}: Completely unsure of what happened!"
147
+ end
148
+ end
149
+
150
+ def acquire_valid_states
151
+ if @event['client'].key?('ec2_states')
152
+ return @event['client']['ec2_states']
153
+ else
154
+ return ['running']
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,200 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # elasticache-metrics
4
+ #
5
+ # DESCRIPTION:
6
+ # Fetch Elasticache metrics from CloudWatch
7
+ #
8
+ # OUTPUT:
9
+ # metric-data
10
+ #
11
+ # PLATFORMS:
12
+ # Linux
13
+ #
14
+ # DEPENDENCIES:
15
+ # gem: aws-sdk
16
+ # gem: sensu-plugin
17
+ #
18
+ # needs example command
19
+ # USAGE:
20
+ # #YELLOW
21
+
22
+ # NOTES:
23
+ # Redis: http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CacheMetrics.Redis.html
24
+ # Memcached: http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CacheMetrics.Memcached.html
25
+ #
26
+ # By default fetches all available statistics from one minute ago. You may need to fetch further back than this;
27
+ #
28
+ # LICENSE:
29
+ # Copyright 2014 Yann Verry
30
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
31
+ # for details.
32
+ #
33
+
34
+ require 'rubygems' if RUBY_VERSION < '1.9.0'
35
+ require 'sensu-plugin/metric/cli'
36
+ require 'aws-sdk'
37
+
38
+ class ElastiCacheMetrics < Sensu::Plugin::Metric::CLI::Graphite
39
+ option :cacheclusterid,
40
+ description: 'Name of the Cache Cluster',
41
+ short: '-n ELASTICACHE_NAME',
42
+ long: '--name ELASTICACHE_NAME',
43
+ required: true
44
+
45
+ option :cachenodeid,
46
+ description: 'Cache Node ID',
47
+ short: '-i CACHE_NODE_ID',
48
+ long: '--cache-node-id CACHE_NODE_ID',
49
+ default: '0001'
50
+
51
+ option :elasticachetype,
52
+ description: 'Elasticache type redis or memcached',
53
+ short: '-c TYPE',
54
+ long: '--cachetype TYPE',
55
+ required: true
56
+
57
+ option :scheme,
58
+ description: 'Metric naming scheme, text to prepend to metric',
59
+ short: '-s SCHEME',
60
+ long: '--scheme SCHEME',
61
+ default: ''
62
+
63
+ option :fetch_age,
64
+ description: 'How long ago to fetch metrics for',
65
+ short: '-f AGE',
66
+ long: '--fetch_age',
67
+ default: 60,
68
+ proc: proc(&:to_i)
69
+
70
+ option :aws_access_key,
71
+ short: '-a AWS_ACCESS_KEY',
72
+ long: '--aws-access-key AWS_ACCESS_KEY',
73
+ description: "AWS Access Key. Either set ENV['AWS_ACCESS_KEY_ID'] or provide it as an option"
74
+
75
+ option :aws_secret_access_key,
76
+ short: '-k AWS_SECRET_ACCESS_KEY',
77
+ long: '--aws-secret-access-key AWS_SECRET_ACCESS_KEY',
78
+ description: "AWS Secret Access Key. Either set ENV['AWS_SECRET_ACCESS_KEY'] or provide it as an option"
79
+
80
+ option :aws_region,
81
+ short: '-r AWS_REGION',
82
+ long: '--aws-region REGION',
83
+ description: 'AWS Region (such as us-east-1).',
84
+ default: 'us-east-1'
85
+
86
+ def aws_config
87
+ hash = {}
88
+ hash.update access_key_id: config[:access_key_id], secret_access_key: config[:secret_access_key] if config[:access_key_id] && config[:secret_access_key]
89
+ hash.update region: config[:aws_region]
90
+ hash
91
+ end
92
+
93
+ def run
94
+ if config[:scheme] == ''
95
+ graphitepath = "#{config[:elasticachename]}.#{config[:metric].downcase}"
96
+ else
97
+ graphitepath = config[:scheme]
98
+ end
99
+
100
+ statistic_type = {
101
+ 'redis' => {
102
+ 'CPUUtilization' => 'Percent',
103
+ 'SwapUsage' => 'Bytes',
104
+ 'FreeableMemory' => 'Bytes',
105
+ 'NetworkBytesIn' => 'Bytes',
106
+ 'NetworkBytesOut' => 'Bytes',
107
+ 'GetTypeCmds' => 'Count',
108
+ 'SetTypeCmds' => 'Count',
109
+ 'KeyBasedCmds' => 'Count',
110
+ 'StringBasedCmds' => 'Count',
111
+ 'HashBasedCmds' => 'Count',
112
+ 'ListBasedCmds' => 'Count',
113
+ 'SetBasedCmds' => 'Count',
114
+ 'SortedSetBasedCmds' => 'Count',
115
+ 'CurrItems' => 'Count'
116
+ },
117
+ 'memcached' => {
118
+ 'CPUUtilization' => 'Percent',
119
+ 'SwapUsage' => 'Bytes',
120
+ 'FreeableMemory' => 'Bytes',
121
+ 'NetworkBytesIn' => 'Bytes',
122
+ 'NetworkBytesOut' => 'Bytes',
123
+ 'BytesUsedForCacheItems' => 'Bytes',
124
+ 'BytesReadIntoMemcached' => 'Bytes',
125
+ 'BytesWrittenOutFromMemcached' => 'Bytes',
126
+ 'CasBadval' => 'Count',
127
+ 'CasHits' => 'Count',
128
+ 'CasMisses' => 'Count',
129
+ 'CmdFlush' => 'Count',
130
+ 'CmdGet' => 'Count',
131
+ 'CmdSet' => 'Count',
132
+ 'CurrConnections' => 'Count',
133
+ 'CurrItems' => 'Count',
134
+ 'DecrHits' => 'Count',
135
+ 'DecrMisses' => 'Count',
136
+ 'DeleteHits' => 'Count',
137
+ 'DeleteMisses' => 'Count',
138
+ 'Evictions' => 'Count',
139
+ 'GetHits' => 'Count',
140
+ 'GetMisses' => 'Count',
141
+ 'IncrHits' => 'Count',
142
+ 'IncrMisses' => 'Count',
143
+ 'Reclaimed' => 'Count',
144
+ 'BytesUsedForHash' => 'Bytes',
145
+ 'CmdConfigGet' => 'Count',
146
+ 'CmdConfigSet' => 'Count',
147
+ 'CmdTouch' => 'Count',
148
+ 'CurrConfig' => 'Count',
149
+ 'EvictedUnfetched' => 'Count',
150
+ 'ExpiredUnfetched' => 'Count',
151
+ 'SlabsMoved' => 'Count',
152
+ 'TouchHits' => 'Count',
153
+ 'TouchMisses' => 'Count',
154
+ 'NewConnections' => 'Count',
155
+ 'NewItems' => 'Count',
156
+ 'UnusedMemory' => 'Bytes'
157
+ }
158
+ }
159
+
160
+ begin
161
+ et = Time.now - config[:fetch_age]
162
+ st = et - 60
163
+
164
+ cw = AWS::CloudWatch::Client.new aws_config
165
+
166
+ # define all options
167
+ options = {
168
+ 'namespace' => 'AWS/ElastiCache',
169
+ 'metric_name' => config[:metric],
170
+ 'dimensions' => [
171
+ { 'name' => 'CacheClusterId', 'value' => config[:cacheclusterid] }
172
+ ],
173
+ 'start_time' => st.iso8601,
174
+ 'end_time' => et.iso8601,
175
+ 'period' => 60,
176
+ 'statistics' => ['Average']
177
+ }
178
+
179
+ result = {}
180
+
181
+ # Fetch all metrics by elasticachetype (redis or memcached).
182
+ statistic_type[config[:elasticachetype]].each do |m|
183
+ options['metric_name'] = m[0] # override metric
184
+ r = cw.get_metric_statistics(options)
185
+ result[m[0]] = r[:datapoints][0] unless r[:datapoints][0].nil?
186
+ end
187
+
188
+ unless result.nil?
189
+ result.each do |name, d|
190
+ # We only return data when we have some to return
191
+ output graphitepath + '.' + name.downcase, d[:average], d[:timestamp].to_i
192
+ end
193
+ end
194
+ rescue => e
195
+ puts "Error: exception: #{e}"
196
+ critical
197
+ end
198
+ ok
199
+ end
200
+ end
@@ -0,0 +1,144 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # elb-full-metrics
4
+ #
5
+ # DESCRIPTION:
6
+ # Gets latency metrics from CloudWatch and puts them in Graphite for longer term storage
7
+ #
8
+ # OUTPUT:
9
+ # metric-data
10
+ #
11
+ # PLATFORMS:
12
+ # Linux
13
+ #
14
+ # DEPENDENCIES:
15
+ # gem: sensu-plugin
16
+ #
17
+ # USAGE:
18
+ # #YELLOW
19
+ #
20
+ # NOTES:
21
+ # Returns latency statistics by default. You can specify any valid ELB metric type, see
22
+ # http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html#elb-metricscollected
23
+ #
24
+ # By default fetches statistics from one minute ago. You may need to fetch further back than this;
25
+ # high traffic ELBs can sometimes experience statistic delays of up to 10 minutes. If you experience this,
26
+ # raising a ticket with AWS support should get the problem resolved.
27
+ # As a workaround you can use eg -f 300 to fetch data from 5 minutes ago.
28
+ #
29
+ # LICENSE:
30
+ # Copyright 2013 Bashton Ltd http://www.bashton.com/
31
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
32
+ # for details.
33
+ #
34
+
35
+ require 'rubygems' if RUBY_VERSION < '1.9.0'
36
+ require 'sensu-plugin/metric/cli'
37
+ require 'aws-sdk'
38
+
39
+ class ELBMetrics < Sensu::Plugin::Metric::CLI::Graphite
40
+ option :elbname,
41
+ description: 'Name of the Elastic Load Balancer',
42
+ short: '-n ELB_NAME',
43
+ long: '--name ELB_NAME'
44
+
45
+ option :scheme,
46
+ description: 'Metric naming scheme, text to prepend to metric',
47
+ short: '-s SCHEME',
48
+ long: '--scheme SCHEME',
49
+ default: ''
50
+
51
+ option :fetch_age,
52
+ description: 'How long ago to fetch metrics for',
53
+ short: '-f AGE',
54
+ long: '--fetch_age',
55
+ default: 60,
56
+ proc: proc(&:to_i)
57
+
58
+ option :aws_access_key,
59
+ short: '-a AWS_ACCESS_KEY',
60
+ long: '--aws-access-key AWS_ACCESS_KEY',
61
+ description: "AWS Access Key. Either set ENV['AWS_ACCESS_KEY'] or provide it as an option",
62
+ required: true,
63
+ default: ENV['AWS_ACCESS_KEY']
64
+
65
+ option :aws_secret_access_key,
66
+ short: '-k AWS_SECRET_KEY',
67
+ long: '--aws-secret-access-key AWS_SECRET_KEY',
68
+ description: "AWS Secret Access Key. Either set ENV['AWS_SECRET_KEY'] or provide it as an option",
69
+ required: true,
70
+ default: ENV['AWS_SECRET_KEY']
71
+
72
+ option :aws_region,
73
+ short: '-r AWS_REGION',
74
+ long: '--aws-region REGION',
75
+ description: 'AWS Region (such as eu-west-1).',
76
+ default: 'us-east-1'
77
+
78
+ def aws_config
79
+ hash = {}
80
+ hash.update access_key_id: config[:access_key_id], secret_access_key: config[:secret_access_key] if config[:access_key_id] && config[:secret_access_key]
81
+ hash.update region: config[:aws_region]
82
+ hash
83
+ end
84
+
85
+ def run
86
+ statistic_type = {
87
+ 'Latency' => 'Average',
88
+ 'RequestCount' => 'Sum',
89
+ 'UnHealthyHostCount' => 'Average',
90
+ 'HealthyHostCount' => 'Average',
91
+ 'HTTPCode_Backend_2XX' => 'Sum',
92
+ 'HTTPCode_Backend_4XX' => 'Sum',
93
+ 'HTTPCode_Backend_5XX' => 'Sum',
94
+ 'HTTPCode_ELB_4XX' => 'Sum',
95
+ 'HTTPCode_ELB_5XX' => 'Sum'
96
+ }
97
+
98
+ begin
99
+ et = Time.now - config[:fetch_age]
100
+ st = et - 60
101
+
102
+ cw = AWS::CloudWatch::Client.new aws_config
103
+
104
+ options = {
105
+ 'namespace' => 'AWS/ELB',
106
+ 'dimensions' => [
107
+ {
108
+ 'name' => 'LoadBalancerName',
109
+ 'value' => config[:elbname]
110
+ }
111
+ ],
112
+ 'start_time' => st.iso8601,
113
+ 'end_time' => et.iso8601,
114
+ 'period' => 60
115
+ }
116
+
117
+ result = {}
118
+ graphitepath = config[:scheme]
119
+
120
+ config[:elbname].split(' ').each do |elbname|
121
+ statistic_type.each do |key, value|
122
+ if config[:scheme] == ''
123
+ graphitepath = "#{config[:elbname]}.#{key.downcase}"
124
+ end
125
+ options['metric_name'] = key
126
+ options['dimensions'][0]['value'] = elbname
127
+ options['statistics'] = [value]
128
+ r = cw.get_metric_statistics(options)
129
+ result[key] = r[:datapoints][0] unless r[:datapoints][0].nil?
130
+ end
131
+ unless result.nil?
132
+ # We only return data when we have some to return
133
+ result.each do |key, value|
134
+ puts key, value
135
+ output graphitepath + ".#{key}", value.to_a.last[1], value[:timestamp].to_i
136
+ end
137
+ end
138
+ end
139
+ rescue => e
140
+ critical "Error: exception: #{e}"
141
+ end
142
+ ok
143
+ end
144
+ end