deltacloud-core 0.0.1

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 (110) hide show
  1. data/COPYING +502 -0
  2. data/Rakefile +108 -0
  3. data/bin/deltacloudd +88 -0
  4. data/config.ru +5 -0
  5. data/deltacloud.rb +14 -0
  6. data/lib/converters/xml_converter.rb +133 -0
  7. data/lib/deltacloud/base_driver.rb +19 -0
  8. data/lib/deltacloud/base_driver/base_driver.rb +189 -0
  9. data/lib/deltacloud/base_driver/features.rb +144 -0
  10. data/lib/deltacloud/drivers/ec2/ec2_driver.rb +318 -0
  11. data/lib/deltacloud/drivers/ec2/ec2_mock_driver.rb +170 -0
  12. data/lib/deltacloud/drivers/gogrid/gogrid_client.rb +45 -0
  13. data/lib/deltacloud/drivers/gogrid/gogrid_driver.rb +239 -0
  14. data/lib/deltacloud/drivers/mock/mock_driver.rb +275 -0
  15. data/lib/deltacloud/drivers/opennebula/cloud_client.rb +116 -0
  16. data/lib/deltacloud/drivers/opennebula/occi_client.rb +204 -0
  17. data/lib/deltacloud/drivers/opennebula/opennebula_driver.rb +241 -0
  18. data/lib/deltacloud/drivers/rackspace/rackspace_client.rb +129 -0
  19. data/lib/deltacloud/drivers/rackspace/rackspace_driver.rb +150 -0
  20. data/lib/deltacloud/drivers/rhevm/rhevm_driver.rb +254 -0
  21. data/lib/deltacloud/drivers/rimu/rimu_hosting_client.rb +87 -0
  22. data/lib/deltacloud/drivers/rimu/rimu_hosting_driver.rb +143 -0
  23. data/lib/deltacloud/hardware_profile.rb +131 -0
  24. data/lib/deltacloud/helpers.rb +5 -0
  25. data/lib/deltacloud/helpers/application_helper.rb +38 -0
  26. data/lib/deltacloud/helpers/conversion_helper.rb +39 -0
  27. data/lib/deltacloud/helpers/hardware_profiles_helper.rb +35 -0
  28. data/lib/deltacloud/models/base_model.rb +58 -0
  29. data/lib/deltacloud/models/image.rb +26 -0
  30. data/lib/deltacloud/models/instance.rb +37 -0
  31. data/lib/deltacloud/models/instance_profile.rb +47 -0
  32. data/lib/deltacloud/models/realm.rb +25 -0
  33. data/lib/deltacloud/models/storage_snapshot.rb +26 -0
  34. data/lib/deltacloud/models/storage_volume.rb +27 -0
  35. data/lib/deltacloud/state_machine.rb +84 -0
  36. data/lib/deltacloud/validation.rb +70 -0
  37. data/lib/drivers.rb +37 -0
  38. data/lib/sinatra/lazy_auth.rb +56 -0
  39. data/lib/sinatra/rabbit.rb +272 -0
  40. data/lib/sinatra/respond_to.rb +262 -0
  41. data/lib/sinatra/static_assets.rb +83 -0
  42. data/lib/sinatra/url_for.rb +44 -0
  43. data/public/favicon.ico +0 -0
  44. data/public/images/grid.png +0 -0
  45. data/public/images/logo-wide.png +0 -0
  46. data/public/images/rails.png +0 -0
  47. data/public/images/topbar-bg.png +0 -0
  48. data/public/javascripts/application.js +2 -0
  49. data/public/javascripts/controls.js +963 -0
  50. data/public/javascripts/dragdrop.js +973 -0
  51. data/public/javascripts/effects.js +1128 -0
  52. data/public/javascripts/prototype.js +4320 -0
  53. data/public/stylesheets/compiled/application.css +613 -0
  54. data/public/stylesheets/compiled/ie.css +31 -0
  55. data/public/stylesheets/compiled/print.css +27 -0
  56. data/public/stylesheets/compiled/screen.css +456 -0
  57. data/server.rb +340 -0
  58. data/tests/deltacloud_test.rb +60 -0
  59. data/tests/images_test.rb +94 -0
  60. data/tests/instances_test.rb +136 -0
  61. data/tests/realms_test.rb +56 -0
  62. data/tests/storage_snapshots_test.rb +48 -0
  63. data/tests/storage_volumes_test.rb +48 -0
  64. data/views/accounts/index.html.haml +11 -0
  65. data/views/accounts/show.html.haml +30 -0
  66. data/views/api/show.html.haml +15 -0
  67. data/views/api/show.xml.haml +5 -0
  68. data/views/docs/collection.html.haml +37 -0
  69. data/views/docs/collection.xml.haml +14 -0
  70. data/views/docs/index.html.haml +15 -0
  71. data/views/docs/index.xml.haml +5 -0
  72. data/views/docs/operation.html.haml +31 -0
  73. data/views/docs/operation.xml.haml +10 -0
  74. data/views/errors/auth_exception.html.haml +8 -0
  75. data/views/errors/auth_exception.xml.haml +2 -0
  76. data/views/errors/backend_error.html.haml +17 -0
  77. data/views/errors/backend_error.xml.haml +8 -0
  78. data/views/errors/validation_failure.html.haml +11 -0
  79. data/views/errors/validation_failure.xml.haml +7 -0
  80. data/views/hardware_profiles/index.html.haml +25 -0
  81. data/views/hardware_profiles/index.xml.haml +4 -0
  82. data/views/hardware_profiles/show.html.haml +19 -0
  83. data/views/hardware_profiles/show.xml.haml +17 -0
  84. data/views/images/index.html.haml +30 -0
  85. data/views/images/index.xml.haml +7 -0
  86. data/views/images/show.html.haml +21 -0
  87. data/views/images/show.xml.haml +5 -0
  88. data/views/instance_states/show.gv.erb +45 -0
  89. data/views/instance_states/show.html.haml +31 -0
  90. data/views/instance_states/show.xml.haml +8 -0
  91. data/views/instances/index.html.haml +29 -0
  92. data/views/instances/index.xml.haml +23 -0
  93. data/views/instances/new.html.haml +49 -0
  94. data/views/instances/show.html.haml +42 -0
  95. data/views/instances/show.xml.haml +28 -0
  96. data/views/layout.html.haml +23 -0
  97. data/views/realms/index.html.haml +29 -0
  98. data/views/realms/index.xml.haml +12 -0
  99. data/views/realms/show.html.haml +15 -0
  100. data/views/realms/show.xml.haml +10 -0
  101. data/views/root/index.html.haml +4 -0
  102. data/views/storage_snapshots/index.html.haml +20 -0
  103. data/views/storage_snapshots/index.xml.haml +11 -0
  104. data/views/storage_snapshots/show.html.haml +14 -0
  105. data/views/storage_snapshots/show.xml.haml +9 -0
  106. data/views/storage_volumes/index.html.haml +21 -0
  107. data/views/storage_volumes/index.xml.haml +13 -0
  108. data/views/storage_volumes/show.html.haml +20 -0
  109. data/views/storage_volumes/show.xml.haml +13 -0
  110. metadata +311 -0
@@ -0,0 +1,254 @@
1
+ #
2
+ # Copyright (C) 2009 Red Hat, Inc.
3
+ #
4
+ # This library is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU Lesser General Public
6
+ # License as published by the Free Software Foundation; either
7
+ # version 2.1 of the License, or (at your option) any later version.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ require 'deltacloud/base_driver'
19
+ require 'yaml'
20
+
21
+ module Deltacloud
22
+ module Drivers
23
+ module RHEVM
24
+
25
+ class RHEVMDriver < Deltacloud::BaseDriver
26
+
27
+ SCRIPT_DIR = File.dirname(__FILE__) + '/scripts'
28
+ CONFIG = YAML.load_file(File.dirname(__FILE__) + '/../../../../config/rhevm_config.yml')
29
+ SCRIPT_DIR_ARG = '"' + SCRIPT_DIR + '"'
30
+ DELIM_BEGIN="<_OUTPUT>"
31
+ DELIM_END="</_OUTPUT>"
32
+ POWERSHELL="c:\\Windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe"
33
+ NO_OWNER=""
34
+
35
+ feature :instances, :user_name
36
+
37
+ #
38
+ # Execute a Powershell command, and convert the output
39
+ # to YAML in order to get back an array of maps.
40
+ #
41
+ def execute(credentials, command, *args)
42
+ args = args.to_a
43
+ argString = genArgString(credentials, args)
44
+ puts argString
45
+ outputMaps = {}
46
+ output = `#{POWERSHELL} -command "&{#{File.join(SCRIPT_DIR, command)} #{argString}; exit $LASTEXITCODE}`
47
+ exitStatus = $?.exitstatus
48
+ puts(output)
49
+ puts("EXITSTATUS #{exitStatus}")
50
+ st = output.index(DELIM_BEGIN)
51
+ if (st)
52
+ st += DELIM_BEGIN.length
53
+ ed = output.index(DELIM_END)
54
+ output = output.slice(st, (ed-st))
55
+ # Lets make it yaml
56
+ output.strip!
57
+ if (output.length > 0)
58
+ outputMaps = YAML.load(self.toYAML(output))
59
+ end
60
+ end
61
+ outputMaps
62
+ end
63
+
64
+ def genArgString(credentials, args)
65
+ commonArgs = [SCRIPT_DIR_ARG, credentials.user, credentials.password, CONFIG["domain"]]
66
+ commonArgs.concat(args)
67
+ commonArgs.join(" ")
68
+ end
69
+
70
+ def toYAML(output)
71
+ yOutput = "- \n" + output
72
+ yOutput.gsub!(/^(\w*)[ ]*:[ ]*([A-Z0-9a-z._ -:{}]*)/,' \1: "\2"')
73
+ yOutput.gsub!(/^[ ]*$/,"- ")
74
+ puts(yOutput)
75
+ yOutput
76
+ end
77
+
78
+ def statify(state)
79
+ st = state.nil? ? "" : state.upcase()
80
+ return "running" if st == "UP"
81
+ return "stopped" if st == "DOWN"
82
+ return "pending" if st == "POWERING UP"
83
+ st
84
+ end
85
+
86
+ define_hardware_profile 'rhevm'
87
+
88
+ #
89
+ # Realms
90
+ #
91
+
92
+ def realms(credentials, opts=nil)
93
+ domains = execute(credentials, "storageDomains.ps1")
94
+ if (!opts.nil? && opts[:id])
95
+ domains = domains.select{|d| opts[:id] == d["StorageId"]}
96
+ end
97
+
98
+ realms = []
99
+ domains.each do |dom|
100
+ realms << domain_to_realm(dom)
101
+ end
102
+ realms
103
+ end
104
+
105
+ def domain_to_realm(dom)
106
+ Realm.new({
107
+ :id => dom["StorageId"],
108
+ :name => dom["Name"],
109
+ :limit => dom["AvailableDiskSize"]
110
+ })
111
+ end
112
+
113
+
114
+
115
+ #
116
+ # Images
117
+ #
118
+
119
+ def images(credentials, opts=nil )
120
+ templates = []
121
+ if (opts.nil?)
122
+ templates = execute(credentials, "templates.ps1")
123
+ else
124
+ if (opts[:id])
125
+ templates = execute(credentials, "templateById.ps1", opts[:id])
126
+ end
127
+ end
128
+ images = []
129
+ templates.each do |templ|
130
+ images << template_to_image(templ)
131
+ end
132
+ images
133
+ end
134
+
135
+ def template_to_image(templ)
136
+ Image.new({
137
+ :id => templ["TemplateId"],
138
+ :name => templ["Name"],
139
+ :description => templ["Description"],
140
+ :architecture => templ["OperatingSystem"],
141
+ :owner_id => NO_OWNER,
142
+ :mem_size_md => templ["MemSizeMb"],
143
+ :instance_count => templ["ChildCount"],
144
+ :state => templ["Status"],
145
+ :capacity => templ["SizeGB"]
146
+ })
147
+ end
148
+
149
+ #
150
+ # Instances
151
+ #
152
+
153
+ define_instance_states do
154
+ start.to(:stopped) .on( :create )
155
+
156
+ pending.to(:shutting_down) .on( :stop )
157
+ pending.to(:running) .automatically
158
+
159
+ running.to(:pending) .on( :reboot )
160
+ running.to(:shutting_down) .on( :stop )
161
+
162
+ shutting_down.to(:stopped) .automatically
163
+ stopped.to(:pending) .on( :start )
164
+ stopped.to(:finish) .on( :destroy )
165
+ end
166
+
167
+ def instances(credentials, opts=nil)
168
+ vms = []
169
+ if (opts.nil?)
170
+ vms = execute(credentials, "vms.ps1")
171
+ else
172
+ if (opts[:id])
173
+ vms = execute(credentials, "vmById.ps1", opts[:id])
174
+ end
175
+ end
176
+ instances = []
177
+ vms.each do |vm|
178
+ instances << vm_to_instance(vm)
179
+ end
180
+ instances = filter_on( instances, :id, opts )
181
+ instances = filter_on( instances, :state, opts )
182
+ instances
183
+ end
184
+
185
+ def vm_to_instance(vm)
186
+ Instance.new({
187
+ :id => vm["VmId"],
188
+ :description => vm["Description"],
189
+ :name => vm["Name"],
190
+ :architecture => vm["OperatingSystem"],
191
+ :owner_id => NO_OWNER,
192
+ :image_id => vm["TemplateId"],
193
+ :state => statify(vm["Status"]),
194
+ :instance_profile => InstanceProfile.new("rhevm"),
195
+ :actions => instance_actions_for(statify(vm["Status"])),
196
+ })
197
+ end
198
+
199
+ def start_instance(credentials, image_id)
200
+ vm = execute(credentials, "startVm.ps1", image_id)
201
+ vm_to_instance(vm[0])
202
+ end
203
+
204
+ def stop_instance(credentials, image_id)
205
+ vm = execute(credentials, "stopVm.ps1", image_id)
206
+ vm_to_instance(vm[0])
207
+ end
208
+
209
+ def create_instance(credentials, image_id, opts)
210
+ name = opts[:name]
211
+ name = "Inst-#{rand(10000)}" if (name.nil? or name.empty?)
212
+ realm_id = opts[:realm_id]
213
+ if (realm_id.nil?)
214
+ realms = filter_on(realms(credentials, opts), :name, :name => "data")
215
+ puts realms[0]
216
+ realm_id = realms[0].id
217
+ end
218
+ vm = execute(credentials, "addVm.ps1", image_id, name, realm_id)
219
+ vm_to_instance(vm[0])
220
+ end
221
+
222
+ def reboot_instance(credentials, image_id)
223
+ vm = execute(credentials, "rebootVm.ps1", image_id)
224
+ vm_to_instance(vm[0])
225
+ end
226
+
227
+ def destroy_instance(credentials, image_id)
228
+ vm = execute(credentials, "deleteVm.ps1", image_id)
229
+ vm_to_instance(vm[0])
230
+ end
231
+
232
+ #
233
+ # Storage Volumes
234
+ #
235
+
236
+ def storage_volumes(credentials, ids=nil)
237
+ volumes = []
238
+ volumes
239
+ end
240
+
241
+ #
242
+ # Storage Snapshots
243
+ #
244
+
245
+ def storage_snapshots(credentials, ids=nil)
246
+ snapshots = []
247
+ snapshots
248
+ end
249
+
250
+ end
251
+
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,87 @@
1
+ #
2
+ # Copyright (C) 2009 RimuHosting Ltd
3
+ # Author: Ivan Meredith <ivan@ivan.net.nz>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "net/http"
20
+ require "net/https"
21
+ require "rubygems"
22
+ require "json"
23
+ require "deltacloud/base_driver"
24
+
25
+ module Deltacloud
26
+ module Drivers
27
+ module Rimu
28
+
29
+ class RimuHostingClient
30
+ def initialize(credentials ,baseuri = 'https://rimuhosting.com/r')
31
+ @uri = URI.parse(baseuri)
32
+ @service = Net::HTTP.new(@uri.host, @uri.port)
33
+ @service.use_ssl = true
34
+ if credentials.provided?
35
+ @auth = "rimuhosting apikey=#{credentials.password}"
36
+ end
37
+
38
+ end
39
+
40
+ def request(resource, data='', method='GET')
41
+ headers = {"Accept" => "application/json", "Content-Type" => "application/json"}
42
+ if(!@auth.nil?)
43
+ headers["Authorization"] = @auth
44
+ end
45
+ r = @service.send_request(method, @uri.path + resource, data, headers)
46
+ puts r.body
47
+ res = JSON.parse(r.body)
48
+ res = res[res.keys[0]]
49
+
50
+ if(res['response_type'] == "ERROR" and res['error_info']['error_class'] == "PermissionException")
51
+ raise DeltaCloud::AuthException.new
52
+ end
53
+ res
54
+ end
55
+
56
+ def list_images
57
+ request('/distributions')["distro_infos"]
58
+ end
59
+
60
+ def list_plans
61
+ puts "testsdasfdsf"
62
+ request('/pricing-plans;server-type=VPS')["pricing_plan_infos"]
63
+ end
64
+
65
+ def list_nodes
66
+ request('/orders;include_inactive=N')["about_orders"]
67
+ end
68
+
69
+ def set_server_state(id, state)
70
+ json = {"reboot_request" => {"running_state" => state}}.to_json
71
+ request("/orders/order-#{id}-a/vps/running-state", json, 'PUT')
72
+ end
73
+
74
+ def delete_server(id)
75
+ request("/orders/order-#{id}-a/vps",'', 'DELETE')
76
+ end
77
+
78
+ def create_server(image_id, plan_code, name)
79
+ json = {:new_vps => {:instantiation_options => {:domain_name => name, :distro => image_id},
80
+ :pricing_plan_code => plan_code}}.to_json
81
+ request('/orders/new-vps',json, 'POST')[:about_order]
82
+ end
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,143 @@
1
+ #
2
+ # Copyright (C) 2009 RimuHosting Ltd
3
+ # Author: Ivan Meredith <ivan@ivan.net.nz>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "deltacloud/base_driver"
20
+ require "deltacloud/drivers/rimu/rimu_hosting_client"
21
+
22
+ module Deltacloud
23
+ module Drivers
24
+ module Rimu
25
+
26
+ class RimuHostingDriver < Deltacloud::BaseDriver
27
+
28
+ feature :instances, :user_name
29
+
30
+ def images(credentails, opts=nil)
31
+ rh = RimuHostingClient.new(credentails)
32
+ images = rh.list_images.map do | image |
33
+ Image.new({
34
+ :id => image["distro_code"].gsub(/\./,"-"),
35
+ :name => image["distro_code"],
36
+ :description => image["distro_description"],
37
+ :owner_id => "root",
38
+ :architecture => "x86"
39
+ })
40
+ end
41
+ images.sort_by{|e| [e.description]}
42
+ images = filter_on( images, :id, opts)
43
+ images
44
+ end
45
+
46
+ def hardware_profiles(credentials, opts = nil)
47
+ rh = RimuHostingClient.new(credentials)
48
+ results = rh.list_plans.map do |plan|
49
+ # FIXME: x86 is not a valid architecture; what is Rimu offering ?
50
+ # FIXME: VPS plans offer a range of memory/storage, but that's
51
+ # not contained in hte pricing_plan_infos
52
+ HardwareProfile.new(plan["pricing_plan_code"]) do
53
+ memory plan["minimum_memory_mb"].to_f
54
+ storage => plan["minimum_disk_gb"].to_i
55
+ architecture => "x86"
56
+ end
57
+ end
58
+ filter_hardware_profiles(results, opts)
59
+ end
60
+
61
+ def realms(credentials, opts=nil)
62
+ [Realm.new( {
63
+ :id=>"rimu",
64
+ :name=>"RimuHosting",
65
+ :state=> "AVAILABLE"
66
+ } )]
67
+ end
68
+
69
+ def instances(credentials, opts=nil)
70
+ rh = RimuHostingClient.new(credentials)
71
+ instances = rh.list_nodes.map do | inst |
72
+ convert_srv_to_instance(inst)
73
+ end
74
+ instances = filter_on( instances, :id, opts)
75
+ instances = filter_on( instances, :state, opts )
76
+ instances
77
+ end
78
+
79
+ def reboot_instance(credentials, id)
80
+ rh = RimuHostingClient.new(credentials)
81
+ rh.set_server_state(id, :RESTARTING)
82
+ end
83
+
84
+ def start_instance(credentials, id)
85
+ rh = RimuHostingClient.new(credentials)
86
+ rh.set_server_state(id, :STARTED)
87
+ end
88
+
89
+ def stop_instance(credentials, id)
90
+ destroy_instance(credentials, id)
91
+ end
92
+
93
+ def destroy_instance(credentials, id)
94
+ rh = RimuHostingClient.new(credentials)
95
+ rh.delete_server(id)
96
+ end
97
+
98
+ def create_instance(credentials, image_id, opts)
99
+ rh = RimuHostingClient.new(credentials)
100
+ # really need to raise an exception here.
101
+ hwp_id = opts[:hwp_id] || 1
102
+ # really bad, but at least its a fqdn
103
+ name = Time.now.to_s + '.com'
104
+ if (opts[:name]) then
105
+ name = opts[:name]
106
+ end
107
+ convert_srv_to_instance(rh.create_server(image_id, hwp_id, name))
108
+
109
+ end
110
+
111
+ def convert_srv_to_instance( inst )
112
+ Instance.new({
113
+ :id => inst["order_oid"].to_s,
114
+ :name => inst["domain_name"],
115
+ :image_id => "lenny",
116
+ :state => "RUNNING",
117
+ :name => inst["domain_name"],
118
+ :realm_id => "RH",
119
+ :owner_id => "root",
120
+ :instance_profile => InstanceProfile.new("none"),
121
+ :actions => instance_actions_for("RUNNING")
122
+ })
123
+ end
124
+
125
+ define_instance_states do
126
+ start.to( :pending ) .automatically
127
+
128
+ pending.to( :running ) .automatically
129
+
130
+ running.to( :running ) .on(:reboot)
131
+ running.to( :shutting_down ) .on(:stop)
132
+
133
+ shutting_down.to( :stopped ) .automatically
134
+
135
+ stopped.to( :finish ) .automatically
136
+ end
137
+
138
+
139
+ end
140
+
141
+ end
142
+ end
143
+ end