opennebula_nagios_probe 1.0.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.
Files changed (52) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +6 -0
  4. data/.travis.yml +10 -0
  5. data/Gemfile +18 -0
  6. data/Gemfile.lock +87 -0
  7. data/README.md +36 -0
  8. data/Rakefile +65 -0
  9. data/bin/check_opennebula +70 -0
  10. data/conf/opennebula.cfg.erb +32 -0
  11. data/lib/opennebula_nagios_probe.rb +29 -0
  12. data/lib/probe/occi/client.rb +50 -0
  13. data/lib/probe/occi/nocci/compute.rb +20 -0
  14. data/lib/probe/occi/nocci/network.rb +21 -0
  15. data/lib/probe/occi/nocci/resource.rb +96 -0
  16. data/lib/probe/occi/nocci/storage.rb +22 -0
  17. data/lib/probe/occi/rocci/compute.rb +70 -0
  18. data/lib/probe/occi/rocci/network.rb +21 -0
  19. data/lib/probe/occi/rocci/resource.rb +62 -0
  20. data/lib/probe/occi/rocci/storage.rb +22 -0
  21. data/lib/probe/opennebula_econe_probe.rb +108 -0
  22. data/lib/probe/opennebula_occi_probe.rb +126 -0
  23. data/lib/probe/opennebula_oned_probe.rb +101 -0
  24. data/lib/probe/opennebula_probe.rb +97 -0
  25. data/lib/probe/optparse_nagios_probe.rb +174 -0
  26. data/opennebula_nagios_probe.gemspec +32 -0
  27. data/spec/probe/fixtures/cassettes/econe/econe_critical_existing_resources.yml +75 -0
  28. data/spec/probe/fixtures/cassettes/econe/econe_critical_no_resources.yml +75 -0
  29. data/spec/probe/fixtures/cassettes/econe/econe_critical_nonexisting_resources.yml +75 -0
  30. data/spec/probe/fixtures/cassettes/econe/econe_warning_existing_resources.yml +75 -0
  31. data/spec/probe/fixtures/cassettes/econe/econe_warning_nonexisting_resources.yml +40 -0
  32. data/spec/probe/fixtures/cassettes/occi/occi_critical_existing_resources.yml +143 -0
  33. data/spec/probe/fixtures/cassettes/occi/occi_critical_no_resources.yml +143 -0
  34. data/spec/probe/fixtures/cassettes/occi/occi_critical_nonexisting_resources.yml +143 -0
  35. data/spec/probe/fixtures/cassettes/occi/occi_warning_existing_resources.yml +230 -0
  36. data/spec/probe/fixtures/cassettes/occi/occi_warning_nonexisting_resources.yml +49 -0
  37. data/spec/probe/fixtures/cassettes/oned/oned_critical_existing_resources.yml +131 -0
  38. data/spec/probe/fixtures/cassettes/oned/oned_critical_no_resources.yml +131 -0
  39. data/spec/probe/fixtures/cassettes/oned/oned_critical_nonexisting_resources.yml +131 -0
  40. data/spec/probe/fixtures/cassettes/oned/oned_warning_existing_resources.yml +671 -0
  41. data/spec/probe/fixtures/cassettes/oned/oned_warning_nonexisting_resources.yml +130 -0
  42. data/spec/probe/fixtures/cassettes/rocci/rocci_critical_existing_resources.yml +1220 -0
  43. data/spec/probe/fixtures/cassettes/rocci/rocci_critical_no_resources.yml +1220 -0
  44. data/spec/probe/fixtures/cassettes/rocci/rocci_critical_nonexisting_resources.yml +1220 -0
  45. data/spec/probe/fixtures/cassettes/rocci/rocci_warning_existing_resources.yml +817 -0
  46. data/spec/probe/fixtures/cassettes/rocci/rocci_warning_no_resources.yml +591 -0
  47. data/spec/probe/fixtures/cassettes/rocci/rocci_warning_nonexisting_resources.yml +640 -0
  48. data/spec/probe/opennebula_econe_probe_spec.rb +150 -0
  49. data/spec/probe/opennebula_occi_probe_spec.rb +149 -0
  50. data/spec/probe/opennebula_oned_probe_spec.rb +154 -0
  51. data/spec/probe/opennebula_rocci_probe_spec.rb +156 -0
  52. metadata +280 -0
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ ###########################################################################
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ###########################################################################
17
+
18
+ module Occi
19
+ class Network < Resource
20
+ end
21
+ end
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ ###########################################################################
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ###########################################################################
17
+ require 'httparty'
18
+
19
+ module Occi
20
+ # OCCI Resource class.
21
+ class Resource
22
+ include HTTParty
23
+
24
+ def initialize(connection)
25
+ self.class.base_uri "#{connection[:endpoint]}"
26
+ # nebula OCCI format
27
+ self.class.basic_auth "#{connection[:auth][:username]}", Digest::SHA1.hexdigest(connection[:auth][:password])
28
+
29
+ # Low-level debugging
30
+ # self.class.debug_output
31
+ end
32
+
33
+ # Callback invoked whenever a subclass is created. This method dynamically defines virtual @endpoint
34
+ # attribute located in child instance, which contains backslash + name of inheriting class. It is used
35
+ # for request building.
36
+ def self.inherited(childclass)
37
+ super(childclass)
38
+
39
+ path = childclass.to_s.split('::').last.downcase
40
+
41
+ childclass.send(:define_method, :endpoint) { "/#{path}" }
42
+ end
43
+
44
+ def entity(id)
45
+ "#{endpoint}/#{id}"
46
+ end
47
+
48
+ # Returns the contents of the pool.
49
+ # 200 OK: An XML representation of the pool in the http body.
50
+ # This means query the point /network, /storage etc.
51
+ def all
52
+ begin
53
+ response = self.class.get(endpoint)
54
+ rescue => e
55
+ raise e.class, 'Could not initiate basic endpoint connectivity query, maybe HTTP/SSL server problem?'
56
+ ensure
57
+ if !response.nil?
58
+ fail HTTPResponseError, "Basic pool availibility request failed! #{response.body}" unless response.code.between?(200, 300)
59
+ response.body
60
+ else
61
+ fail HTTPResponseError, 'Basic pool availibility request failed!'
62
+ end
63
+ end
64
+ end
65
+
66
+ # Returns the representation of specific resource identified by +id+.
67
+ # 200 OK: An XML representation of the pool in the http body.
68
+ def find(id)
69
+ begin
70
+ response = self.class.get(entity(id))
71
+ rescue => e
72
+ raise e.class, 'Could not initiate specific resource query, maybe HTTP/SSL server problem?'
73
+ ensure
74
+ if !response.nil?
75
+ fail HTTPResponseError, "Specific resource request failed! #{response.body}" unless response.code.between?(200, 300)
76
+ response.body
77
+ else
78
+ fail HTTPResponseError, 'Specific resource request failed!'
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ # HTTPResponseError class.
85
+ # Slightly modified HTTParty::ResponseError
86
+ # for better cooperation with existing code
87
+
88
+ class HTTPResponseError < HTTParty::ResponseError
89
+ attr_reader :message
90
+
91
+ def initialize(m)
92
+ super(m)
93
+ @message = m
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ ###########################################################################
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ###########################################################################
17
+ module Occi
18
+ # OCCI Storage class.
19
+
20
+ class Storage < Resource
21
+ end
22
+ end
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ ###########################################################################
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ###########################################################################
17
+ require 'occi-cli'
18
+ require 'occi-core'
19
+ require 'occi-api'
20
+ require 'timeout'
21
+
22
+ module Rocci
23
+ # OCCI Compute class.
24
+ class Compute < Resource
25
+ # Create, check and destroy resource
26
+ def create_check_destroy
27
+ # Build resource
28
+ res = @client.get_resource('compute')
29
+ res.model = model
30
+ res.title = @opts.vmname
31
+ res.hostname = res.title
32
+
33
+ # Fill resource mixin
34
+ if @opts.template.include?('http')
35
+ orig_mxn = model.get_by_id(@opts.template)
36
+ else
37
+ orig_mxn = @client.get_mixin(@opts.template, 'os_tpl', true)
38
+ end
39
+
40
+ if orig_mxn.nil?
41
+ fail Occi::Api::Client::Errors::AmbiguousNameError, 'Invalid, non-existing or ambiguous mixin (template) name'
42
+ end
43
+
44
+ res.mixins << orig_mxn
45
+
46
+ # Create and commit resource
47
+ response = create(res)
48
+ new_vm = response.gsub!(@opts.endpoint.chomp('/'), '')
49
+
50
+ # Following block checks out for sucessfull VM deployment
51
+ # and clean up then
52
+ begin
53
+ timeout(@opts.timeout) do
54
+ loop do
55
+ d = describe(new_vm).first
56
+ if d.attributes.occi.compute.state == 'active'
57
+ # puts "OK, resource did enter 'active' state in time"
58
+ break
59
+ end
60
+ sleep @opts.timeout / 5
61
+ end
62
+ end
63
+ rescue Timeout::Error
64
+ raise Timeout::Error, "Resource did not enter 'active' state in time!"
65
+ ensure
66
+ delete new_vm
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ ###########################################################################
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ###########################################################################
17
+
18
+ module Rocci
19
+ class Network < Resource
20
+ end
21
+ end
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ ###########################################################################
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ###########################################################################
17
+ require 'occi-api'
18
+
19
+ module Rocci
20
+ # rOCCI Resource class.
21
+ class Resource
22
+ include Occi::Api::Dsl
23
+
24
+ def initialize(opts)
25
+ @opts = opts
26
+ connect(:http, opts)
27
+ end
28
+
29
+ # Callback invoked whenever a subclass is created. This method dynamically defines virtual @endpoint
30
+ # attribute located in child instance, which contains backslash + name of inheriting class. It is used
31
+ # for request building.
32
+ def self.inherited(childclass)
33
+ super(childclass)
34
+
35
+ path = childclass.to_s.split('::').last.downcase
36
+
37
+ childclass.send(:define_method, :resource_uri) { "#{path}" }
38
+ end
39
+
40
+ def entity(id)
41
+ "/#{resource_uri}/#{id}"
42
+ end
43
+
44
+ # Returns the contents of the pool.
45
+ # 200 OK: An XML representation of the pool in the http body.
46
+ # This means query the point "network", "storage" etc.
47
+ # Please read Occi::Api documentation here https://github.com/arax/rOCCI-api.
48
+ def all
49
+ describe(resource_uri)
50
+ end
51
+
52
+ # Returns the representation of specific resource identified by +id+.
53
+ # 200 OK: An XML representation of the pool in the http body.
54
+ def find(id)
55
+ describe(entity(id))
56
+ end
57
+
58
+ def create_check_destroy
59
+ # Overriden in sibling (compute)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ ###########################################################################
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ###########################################################################
17
+ module Rocci
18
+ # OCCI Storage class.
19
+
20
+ class Storage < Resource
21
+ end
22
+ end
@@ -0,0 +1,108 @@
1
+ # encoding: UTF-8
2
+ ###########################################################################
3
+ ## Licensed under the Apache License, Version 2.0 (the "License");
4
+ ## you may not use this file except in compliance with the License.
5
+ ## You may obtain a copy of the License at
6
+ ##
7
+ ## http://www.apache.org/licenses/LICENSE-2.0
8
+ ##
9
+ ## Unless required by applicable law or agreed to in writing, software
10
+ ## distributed under the License is distributed on an "AS IS" BASIS,
11
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ ## See the License for the specific language governing permissions and
13
+ ## limitations under the License.
14
+ ###########################################################################
15
+
16
+ require 'opennebula_probe'
17
+ require 'AWS'
18
+ require 'digest/sha1'
19
+
20
+ # OpenNebulaEconeProbe - Econe client query service implementation.
21
+
22
+ class OpenNebulaEconeProbe < OpennebulaProbe
23
+ def initialize(opts)
24
+ super(opts)
25
+
26
+ @connection = AWS::EC2::Base.new(
27
+ access_key_id: @opts.username,
28
+ secret_access_key: Digest::SHA1.hexdigest(@opts.password),
29
+ server: @opts.hostname,
30
+ port: @opts.port,
31
+ path: @opts.path,
32
+ use_ssl: @opts.protocol == :https
33
+ )
34
+ end
35
+
36
+ def check_crit
37
+ @logger.info "Checking for basic connectivity at #{@endpoint}"
38
+ begin
39
+ @connection.describe_images
40
+ @connection.describe_instances
41
+ rescue StandardError => e
42
+ @logger.error "Failed to check connectivity: #{e.message}"
43
+ @logger.debug "#{e.backtrace.join("\n")}"
44
+ return true
45
+ end
46
+
47
+ false
48
+ end
49
+
50
+ def check_resources(resources)
51
+ if resources.map { |x| x[:resource] }.reduce(true) { |product, resource| product && resource.nil? }
52
+ @logger.info 'There are no resources to check, for details on how to specify resources see --help'
53
+ return false
54
+ end
55
+
56
+ resources.each do |resource_hash|
57
+ resource = resource_hash[:resource]
58
+
59
+ next unless resource
60
+
61
+ @logger.info "Looking for #{resource_hash[:resource_string]}s: #{resource.inspect}"
62
+ if resource_hash[:resource_type] == :image
63
+ result = @connection.describe_images
64
+ set = 'imagesSet'
65
+ id = 'imageId'
66
+ elsif resource_hash[:resource_type] == :compute
67
+ result = @connection.describe_instances
68
+ result = result['reservationSet']['item'][0] if result['reservationSet'] && result['reservationSet']['item']
69
+ set = 'instancesSet'
70
+ id = 'amiLaunchIndex'
71
+ else
72
+ fail 'Wrong resource definition'
73
+ end
74
+
75
+ @logger.debug result
76
+
77
+ fail "No #{resource_hash[:resource_string].capitalize} found" unless result && result[set]
78
+
79
+ resource.each do |resource_to_look_for|
80
+ found = false
81
+
82
+ result[set]['item'].each { |resource_found| found = true if resource_to_look_for == resource_found[id] }
83
+
84
+ fail "#{resource_hash[:resource_string].capitalize} #{resource_to_look_for} not found" unless found
85
+ end
86
+ end
87
+
88
+ false
89
+ end
90
+
91
+ def check_warn
92
+ @logger.info "Checking for resource availability at #{@endpoint}"
93
+
94
+ # iterate over given resources
95
+ @logger.info "Not looking for networks, since it is not supported by OpenNebula's ECONE server'" if @opts.network
96
+
97
+ resources = []
98
+ resources << { resource_type: :image, resource: @opts.storage, resource_string: 'image' }
99
+ resources << { resource_type: :compute, resource: @opts.compute, resource_string: 'compute instance' }
100
+
101
+ check_resources(resources)
102
+
103
+ rescue StandardError => e
104
+ @logger.error "Failed to check resource availability: #{e.message}"
105
+ @logger.debug "#{e.backtrace.join("\n")}"
106
+ return true
107
+ end
108
+ end
@@ -0,0 +1,126 @@
1
+ # encoding: UTF-8
2
+ ###########################################################################
3
+ ## Licensed under the Apache License, Version 2.0 (the "License");
4
+ ## you may not use this file except in compliance with the License.
5
+ ## You may obtain a copy of the License at
6
+ ##
7
+ ## http://www.apache.org/licenses/LICENSE-2.0
8
+ ##
9
+ ## Unless required by applicable law or agreed to in writing, software
10
+ ## distributed under the License is distributed on an "AS IS" BASIS,
11
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ ## See the License for the specific language governing permissions and
13
+ ## limitations under the License.
14
+ ###########################################################################
15
+
16
+ require 'opennebula_probe'
17
+ require 'occi/client'
18
+
19
+ # OpenNebulaOcciProbe - OCCI client query service implementation.
20
+
21
+ class OpenNebulaOcciProbe < OpennebulaProbe
22
+ def initialize(opts)
23
+ super(opts)
24
+
25
+ if @opts.user_cred
26
+ creds = {
27
+ type: 'x509',
28
+ user_cert: @opts.user_cred,
29
+ user_cert_password: @opts.password,
30
+ ca_path: @opts.ca_path,
31
+ ca_file: @opts.ca_file,
32
+ voms: @opts.voms
33
+ }
34
+ else
35
+ creds = {
36
+ username: @opts.username,
37
+ password: @opts.password,
38
+ type: 'basic'
39
+ }
40
+ end
41
+
42
+ @client = OcciClient.new(
43
+ endpoint: @endpoint,
44
+ auth: creds,
45
+ occi: @opts.service,
46
+ template: @opts.template_uuid,
47
+ vmname: @opts.vmname,
48
+ timeout: @opts.timeout
49
+ )
50
+ end
51
+
52
+ def check_crit
53
+ @logger.info "Checking for basic connectivity at #{@endpoint}"
54
+
55
+ begin
56
+ # make a few simple queries just to be sure that the service is running
57
+ @client.network.all
58
+ # Not supported yet
59
+ @client.compute.all unless @opts.service == 'rocci'
60
+ @client.storage.all
61
+ rescue StandardError => e
62
+ @logger.error "Failed to check connectivity: #{e}"
63
+ @logger.debug "#{e.backtrace.join("\n")}"
64
+ return true
65
+ end
66
+
67
+ false
68
+ end
69
+
70
+ def check_resources(resources)
71
+ # extract key ":resource" from hashes to new array and determine, if any of them are other than nil
72
+ if resources.map { |x| x[:resource] }.reduce(true) { |product, resource| product && resource.nil? }
73
+ @logger.info 'There are no resources to check, for details on how to specify resources see --help'
74
+ return false
75
+ end
76
+
77
+ resources.each do |resource_hash|
78
+ resource = resource_hash[:resource]
79
+ next unless resource
80
+
81
+ begin
82
+ @logger.info "Looking for #{resource_hash[:resource_string]}s: #{resource.inspect}"
83
+ result = resource.map { |id| resource_hash[:resource_connection].find id }
84
+ @logger.debug result
85
+ end
86
+ end
87
+
88
+ false
89
+ end
90
+
91
+ def check_warn
92
+ @logger.info "Checking for resource availability at #{@endpoint}"
93
+
94
+ resources = []
95
+
96
+ # Not supported yet
97
+ unless @opts.service == 'rocci'
98
+ resources << { resource: @opts.storage,
99
+ resource_string: 'image',
100
+ resource_connection: @client.storage
101
+ }
102
+ end
103
+ resources << { resource: @opts.compute,
104
+ resource_string: 'compute instance',
105
+ resource_connection: @client.compute
106
+ }
107
+ resources << { resource: @opts.network,
108
+ resource_string: 'network',
109
+ resource_connection: @client.network
110
+ }
111
+
112
+ # Additionally create VM from template when using rOCCI if needed
113
+ if !@opts.template_uuid.nil?
114
+ @client.compute.create_check_destroy
115
+ else
116
+ check_resources(resources)
117
+ end
118
+
119
+ false
120
+
121
+ rescue StandardError => e
122
+ @logger.error "Failed to check resource availability: #{e.message}"
123
+ @logger.debug "#{e.backtrace.join("\n")}"
124
+ return true
125
+ end
126
+ end