opennebula 3.9.80.beta
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.
- data/LICENSE +202 -0
- data/NOTICE +47 -0
- data/lib/opennebula.rb +58 -0
- data/lib/opennebula/acl.rb +266 -0
- data/lib/opennebula/acl_pool.rb +55 -0
- data/lib/opennebula/client.rb +119 -0
- data/lib/opennebula/cluster.rb +249 -0
- data/lib/opennebula/cluster_pool.rb +58 -0
- data/lib/opennebula/datastore.rb +171 -0
- data/lib/opennebula/datastore_pool.rb +55 -0
- data/lib/opennebula/document.rb +261 -0
- data/lib/opennebula/document_json.rb +131 -0
- data/lib/opennebula/document_pool.rb +102 -0
- data/lib/opennebula/document_pool_json.rb +58 -0
- data/lib/opennebula/error.rb +52 -0
- data/lib/opennebula/group.rb +163 -0
- data/lib/opennebula/group_pool.rb +56 -0
- data/lib/opennebula/host.rb +201 -0
- data/lib/opennebula/host_pool.rb +93 -0
- data/lib/opennebula/image.rb +297 -0
- data/lib/opennebula/image_pool.rb +79 -0
- data/lib/opennebula/ldap_auth.rb +99 -0
- data/lib/opennebula/ldap_auth_spec.rb +70 -0
- data/lib/opennebula/pool.rb +160 -0
- data/lib/opennebula/pool_element.rb +269 -0
- data/lib/opennebula/server_cipher_auth.rb +148 -0
- data/lib/opennebula/server_x509_auth.rb +104 -0
- data/lib/opennebula/ssh_auth.rb +139 -0
- data/lib/opennebula/system.rb +141 -0
- data/lib/opennebula/template.rb +213 -0
- data/lib/opennebula/template_pool.rb +79 -0
- data/lib/opennebula/user.rb +174 -0
- data/lib/opennebula/user_pool.rb +55 -0
- data/lib/opennebula/virtual_machine.rb +560 -0
- data/lib/opennebula/virtual_machine_pool.rb +323 -0
- data/lib/opennebula/virtual_network.rb +249 -0
- data/lib/opennebula/virtual_network_pool.rb +79 -0
- data/lib/opennebula/x509_auth.rb +288 -0
- data/lib/opennebula/xml_element.rb +427 -0
- data/lib/opennebula/xml_pool.rb +45 -0
- data/lib/opennebula/xml_utils.rb +34 -0
- metadata +118 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
# -------------------------------------------------------------------------- #
|
2
|
+
# Copyright 2002-2013, OpenNebula Project (OpenNebula.org), C12G Labs #
|
3
|
+
# #
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
5
|
+
# not use this file except in compliance with the License. You may obtain #
|
6
|
+
# a copy of the License at #
|
7
|
+
# #
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0 #
|
9
|
+
# #
|
10
|
+
# Unless required by applicable law or agreed to in writing, software #
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
13
|
+
# See the License for the specific language governing permissions and #
|
14
|
+
# limitations under the License. #
|
15
|
+
#--------------------------------------------------------------------------- #
|
16
|
+
|
17
|
+
|
18
|
+
require 'opennebula/pool_element'
|
19
|
+
|
20
|
+
module OpenNebula
|
21
|
+
class Datastore < PoolElement
|
22
|
+
#######################################################################
|
23
|
+
# Constants and Class Methods
|
24
|
+
#######################################################################
|
25
|
+
|
26
|
+
DATASTORE_METHODS = {
|
27
|
+
:info => "datastore.info",
|
28
|
+
:allocate => "datastore.allocate",
|
29
|
+
:delete => "datastore.delete",
|
30
|
+
:update => "datastore.update",
|
31
|
+
:chown => "datastore.chown",
|
32
|
+
:chmod => "datastore.chmod"
|
33
|
+
}
|
34
|
+
|
35
|
+
DATASTORE_TYPES=%w{IMAGE SYSTEM FILE}
|
36
|
+
|
37
|
+
SHORT_DATASTORE_TYPES = {
|
38
|
+
"IMAGE" => "img",
|
39
|
+
"SYSTEM"=> "sys",
|
40
|
+
"FILE" => "fil"
|
41
|
+
}
|
42
|
+
|
43
|
+
# Creates a Datastore description with just its identifier
|
44
|
+
# this method should be used to create plain Datastore objects.
|
45
|
+
# +id+ the id of the user
|
46
|
+
#
|
47
|
+
# Example:
|
48
|
+
# datastore = Datastore.new(Datastore.build_xml(3),rpc_client)
|
49
|
+
#
|
50
|
+
def Datastore.build_xml(pe_id=nil)
|
51
|
+
if pe_id
|
52
|
+
datastore_xml = "<DATASTORE><ID>#{pe_id}</ID></DATASTORE>"
|
53
|
+
else
|
54
|
+
datastore_xml = "<DATASTORE></DATASTORE>"
|
55
|
+
end
|
56
|
+
|
57
|
+
XMLElement.build_xml(datastore_xml,'DATASTORE')
|
58
|
+
end
|
59
|
+
|
60
|
+
# Class constructor
|
61
|
+
def initialize(xml, client)
|
62
|
+
super(xml,client)
|
63
|
+
end
|
64
|
+
|
65
|
+
#######################################################################
|
66
|
+
# XML-RPC Methods for the Datastore Object
|
67
|
+
#######################################################################
|
68
|
+
# Returns the datastore type
|
69
|
+
def type
|
70
|
+
self['TYPE'].to_i
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the datastore type (string value)
|
74
|
+
def type_str
|
75
|
+
DATASTORE_TYPES[type]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the datastore type (string value)
|
79
|
+
def short_type_str
|
80
|
+
SHORT_DATASTORE_TYPES[type_str]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Retrieves the information of the given Datastore.
|
84
|
+
def info()
|
85
|
+
super(DATASTORE_METHODS[:info], 'DATASTORE')
|
86
|
+
end
|
87
|
+
|
88
|
+
alias_method :info!, :info
|
89
|
+
|
90
|
+
# Allocates a new Datastore in OpenNebula
|
91
|
+
#
|
92
|
+
# @param description [String] The template of the Datastore.
|
93
|
+
# @param cluster_id [Integer] Id of the cluster
|
94
|
+
#
|
95
|
+
# @return [Integer, OpenNebula::Error] the new ID in case of
|
96
|
+
# success, error otherwise
|
97
|
+
def allocate(description, cluster_id=ClusterPool::NONE_CLUSTER_ID)
|
98
|
+
super(DATASTORE_METHODS[:allocate], description, cluster_id)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Deletes the Datastore
|
102
|
+
def delete()
|
103
|
+
super(DATASTORE_METHODS[:delete])
|
104
|
+
end
|
105
|
+
|
106
|
+
# Replaces the template contents
|
107
|
+
#
|
108
|
+
# @param new_template [String] New template contents
|
109
|
+
#
|
110
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
111
|
+
# otherwise
|
112
|
+
def update(new_template)
|
113
|
+
super(DATASTORE_METHODS[:update], new_template)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Changes the owner/group
|
117
|
+
#
|
118
|
+
# @param uid [Integer] the new owner id. Set to -1 to leave the current one
|
119
|
+
# @param gid [Integer] the new group id. Set to -1 to leave the current one
|
120
|
+
#
|
121
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
122
|
+
# otherwise
|
123
|
+
def chown(uid, gid)
|
124
|
+
super(DATASTORE_METHODS[:chown], uid, gid)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Changes the datastore permissions.
|
128
|
+
#
|
129
|
+
# @param octet [String] Permissions octed , e.g. 640
|
130
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
131
|
+
# otherwise
|
132
|
+
def chmod_octet(octet)
|
133
|
+
super(DATASTORE_METHODS[:chmod], octet)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Changes the datastore permissions.
|
137
|
+
# Each [Integer] argument must be 1 to allow, 0 deny, -1 do not change
|
138
|
+
#
|
139
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
140
|
+
# otherwise
|
141
|
+
def chmod(owner_u, owner_m, owner_a, group_u, group_m, group_a, other_u,
|
142
|
+
other_m, other_a)
|
143
|
+
super(DATASTORE_METHODS[:chmod], owner_u, owner_m, owner_a, group_u,
|
144
|
+
group_m, group_a, other_u, other_m, other_a)
|
145
|
+
end
|
146
|
+
|
147
|
+
# ---------------------------------------------------------------------
|
148
|
+
# Helpers to get information
|
149
|
+
# ---------------------------------------------------------------------
|
150
|
+
|
151
|
+
# Returns whether or not the image with id 'id' is part of this datastore
|
152
|
+
def contains(id)
|
153
|
+
#This doesn't work in ruby 1.8.5
|
154
|
+
#return self["DATASTORE/ID[.=#{uid}]"] != nil
|
155
|
+
|
156
|
+
id_array = retrieve_elements('IMAGES/ID')
|
157
|
+
return id_array != nil && id_array.include?(uid.to_s)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns an array with the numeric image ids
|
161
|
+
def img_ids
|
162
|
+
array = Array.new
|
163
|
+
|
164
|
+
self.each("IMAGES/ID") do |id|
|
165
|
+
array << id.text.to_i
|
166
|
+
end
|
167
|
+
|
168
|
+
return array
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# -------------------------------------------------------------------------- #
|
2
|
+
# Copyright 2002-2013, OpenNebula Project (OpenNebula.org), C12G Labs #
|
3
|
+
# #
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
5
|
+
# not use this file except in compliance with the License. You may obtain #
|
6
|
+
# a copy of the License at #
|
7
|
+
# #
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0 #
|
9
|
+
# #
|
10
|
+
# Unless required by applicable law or agreed to in writing, software #
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
13
|
+
# See the License for the specific language governing permissions and #
|
14
|
+
# limitations under the License. #
|
15
|
+
#--------------------------------------------------------------------------- #
|
16
|
+
|
17
|
+
|
18
|
+
require 'opennebula/pool'
|
19
|
+
|
20
|
+
module OpenNebula
|
21
|
+
class DatastorePool < Pool
|
22
|
+
#######################################################################
|
23
|
+
# Constants and Class attribute accessors
|
24
|
+
#######################################################################
|
25
|
+
|
26
|
+
DATASTORE_POOL_METHODS = {
|
27
|
+
:info => "datastorepool.info"
|
28
|
+
}
|
29
|
+
|
30
|
+
#######################################################################
|
31
|
+
# Class constructor & Pool Methods
|
32
|
+
#######################################################################
|
33
|
+
|
34
|
+
# +client+ a Client object that represents a XML-RPC connection
|
35
|
+
def initialize(client)
|
36
|
+
super('DATASTORE_POOL','DATASTORE',client)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Factory method to create User objects
|
40
|
+
def factory(element_xml)
|
41
|
+
OpenNebula::Group.new(element_xml,@client)
|
42
|
+
end
|
43
|
+
|
44
|
+
#######################################################################
|
45
|
+
# XML-RPC Methods for the User Object
|
46
|
+
#######################################################################
|
47
|
+
|
48
|
+
# Retrieves all the Groups in the pool.
|
49
|
+
def info()
|
50
|
+
super(DATASTORE_POOL_METHODS[:info])
|
51
|
+
end
|
52
|
+
|
53
|
+
alias_method :info!, :info
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
# -------------------------------------------------------------------------- #
|
2
|
+
# Copyright 2002-2013, OpenNebula Project (OpenNebula.org), C12G Labs #
|
3
|
+
# #
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
5
|
+
# not use this file except in compliance with the License. You may obtain #
|
6
|
+
# a copy of the License at #
|
7
|
+
# #
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0 #
|
9
|
+
# #
|
10
|
+
# Unless required by applicable law or agreed to in writing, software #
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
13
|
+
# See the License for the specific language governing permissions and #
|
14
|
+
# limitations under the License. #
|
15
|
+
#--------------------------------------------------------------------------- #
|
16
|
+
|
17
|
+
require 'opennebula/pool_element'
|
18
|
+
|
19
|
+
module OpenNebula
|
20
|
+
|
21
|
+
# All subclasses must define the DOCUMENT_TYPE constant.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# require 'opennebula/document'
|
25
|
+
#
|
26
|
+
# module OpenNebula
|
27
|
+
# class CustomObject < Document
|
28
|
+
#
|
29
|
+
# DOCUMENT_TYPE = 400
|
30
|
+
#
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
class Document < PoolElement
|
34
|
+
|
35
|
+
#######################################################################
|
36
|
+
# Constants and Class Methods
|
37
|
+
#######################################################################
|
38
|
+
|
39
|
+
DOCUMENT_METHODS = {
|
40
|
+
:allocate => "document.allocate",
|
41
|
+
:delete => "document.delete",
|
42
|
+
:info => "document.info",
|
43
|
+
:update => "document.update",
|
44
|
+
:chown => "document.chown",
|
45
|
+
:chmod => "document.chmod",
|
46
|
+
:clone => "document.clone",
|
47
|
+
:rename => "document.rename"
|
48
|
+
}
|
49
|
+
|
50
|
+
# Creates a Document Object description with just its identifier
|
51
|
+
# this method should be used to create plain Document objects.
|
52
|
+
# @param [Integer] pe_id the id of the object
|
53
|
+
#
|
54
|
+
# @return [Nokogiri::XML::Node, REXML::Element] the empty xml
|
55
|
+
def Document.build_xml(pe_id=nil)
|
56
|
+
if pe_id
|
57
|
+
obj_xml = "<DOCUMENT><ID>#{pe_id}</ID></DOCUMENT>"
|
58
|
+
else
|
59
|
+
obj_xml = "<DOCUMENT></DOCUMENT>"
|
60
|
+
end
|
61
|
+
|
62
|
+
XMLElement.build_xml(obj_xml,'DOCUMENT')
|
63
|
+
end
|
64
|
+
|
65
|
+
# Class constructor
|
66
|
+
#
|
67
|
+
# @param [Nokogiri::XML::Node, REXML::Element] xml string
|
68
|
+
# created by the build_xml() method
|
69
|
+
# @param [OpenNebula::Client] client the xml-rpc client
|
70
|
+
#
|
71
|
+
# @return [Document] the new object
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# doc = Document.new(Document.build_xml(3),rpc_client)
|
75
|
+
def initialize(xml, client)
|
76
|
+
super(xml,client)
|
77
|
+
end
|
78
|
+
|
79
|
+
#######################################################################
|
80
|
+
# XML-RPC Methods for the Document Object
|
81
|
+
#######################################################################
|
82
|
+
|
83
|
+
# Retrieves the information of the given Document.
|
84
|
+
#
|
85
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
86
|
+
# otherwise
|
87
|
+
def info()
|
88
|
+
rc = super(DOCUMENT_METHODS[:info], 'DOCUMENT')
|
89
|
+
|
90
|
+
if !OpenNebula.is_error?(rc) && self['TYPE'].to_i != document_type
|
91
|
+
return OpenNebula::Error.new("[DocumentInfo] Error getting document [#{@pe_id}].")
|
92
|
+
end
|
93
|
+
|
94
|
+
return rc
|
95
|
+
end
|
96
|
+
|
97
|
+
alias_method :info!, :info
|
98
|
+
|
99
|
+
# Allocates a new Document in OpenNebula
|
100
|
+
#
|
101
|
+
# @param description [String] The contents of the Document.
|
102
|
+
#
|
103
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
104
|
+
# otherwise
|
105
|
+
def allocate(description)
|
106
|
+
super(DOCUMENT_METHODS[:allocate], description, document_type)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Deletes the Document
|
110
|
+
#
|
111
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
112
|
+
# otherwise
|
113
|
+
def delete()
|
114
|
+
rc = check_type()
|
115
|
+
return rc if OpenNebula.is_error?(rc)
|
116
|
+
|
117
|
+
return call(DOCUMENT_METHODS[:delete], @pe_id)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Replaces the template contents
|
121
|
+
#
|
122
|
+
# @param [String] new_template new template contents
|
123
|
+
#
|
124
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
125
|
+
# otherwise
|
126
|
+
def update(new_template)
|
127
|
+
rc = check_type()
|
128
|
+
return rc if OpenNebula.is_error?(rc)
|
129
|
+
|
130
|
+
super(DOCUMENT_METHODS[:update], new_template)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Changes the owner/group
|
134
|
+
#
|
135
|
+
# @param [Integer] uid the new owner id. Set to -1 to leave the current one
|
136
|
+
# @param [Integer] gid the new group id. Set to -1 to leave the current one
|
137
|
+
#
|
138
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
139
|
+
# otherwise
|
140
|
+
def chown(uid, gid)
|
141
|
+
rc = check_type()
|
142
|
+
return rc if OpenNebula.is_error?(rc)
|
143
|
+
|
144
|
+
super(DOCUMENT_METHODS[:chown], uid, gid)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Changes the Document permissions.
|
148
|
+
#
|
149
|
+
# @param octet [String] Permissions octed , e.g. 640
|
150
|
+
#
|
151
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
152
|
+
# otherwise
|
153
|
+
def chmod_octet(octet)
|
154
|
+
rc = check_type()
|
155
|
+
return rc if OpenNebula.is_error?(rc)
|
156
|
+
|
157
|
+
super(DOCUMENT_METHODS[:chmod], octet)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Changes the Document permissions.
|
161
|
+
# Each [Integer] argument must be 1 to allow, 0 deny, -1 do not change
|
162
|
+
#
|
163
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
164
|
+
# otherwise
|
165
|
+
def chmod(owner_u, owner_m, owner_a, group_u, group_m, group_a, other_u,
|
166
|
+
other_m, other_a)
|
167
|
+
rc = check_type()
|
168
|
+
return rc if OpenNebula.is_error?(rc)
|
169
|
+
|
170
|
+
super(DOCUMENT_METHODS[:chmod], owner_u, owner_m, owner_a, group_u,
|
171
|
+
group_m, group_a, other_u, other_m, other_a)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Clones this Document into a new one
|
175
|
+
#
|
176
|
+
# @param name [String] Name for the new Document.
|
177
|
+
#
|
178
|
+
# @return [Integer, OpenNebula::Error] The new Document ID in case
|
179
|
+
# of success, Error otherwise
|
180
|
+
def clone(name)
|
181
|
+
rc = check_type()
|
182
|
+
return rc if OpenNebula.is_error?(rc)
|
183
|
+
|
184
|
+
return Error.new('ID not defined') if !@pe_id
|
185
|
+
|
186
|
+
rc = @client.call(DOCUMENT_METHODS[:clone], @pe_id, name)
|
187
|
+
|
188
|
+
return rc
|
189
|
+
end
|
190
|
+
|
191
|
+
# Renames this Document
|
192
|
+
#
|
193
|
+
# @param name [String] New name for the Document.
|
194
|
+
#
|
195
|
+
# @return [nil, OpenNebula::Error] nil in case of success, Error
|
196
|
+
# otherwise
|
197
|
+
def rename(name)
|
198
|
+
return call(DOCUMENT_METHODS[:rename], @pe_id, name)
|
199
|
+
end
|
200
|
+
|
201
|
+
#######################################################################
|
202
|
+
# Helpers to get Document information
|
203
|
+
#######################################################################
|
204
|
+
|
205
|
+
# Returns the group identifier
|
206
|
+
# @return [Integer] the element's group ID
|
207
|
+
def gid
|
208
|
+
self['GID'].to_i
|
209
|
+
end
|
210
|
+
|
211
|
+
# Returns the owner user ID
|
212
|
+
# @return [Integer] the element's owner user ID
|
213
|
+
def owner_id
|
214
|
+
self['UID'].to_i
|
215
|
+
end
|
216
|
+
|
217
|
+
# Returns true if the GROUP_U permission bit is set
|
218
|
+
# @return [true, false] true if the GROUP_U permission bit is set
|
219
|
+
def public?
|
220
|
+
if self['PERMISSIONS/GROUP_U'] == "1" || self['PERMISSIONS/OTHER_U'] == "1"
|
221
|
+
true
|
222
|
+
else
|
223
|
+
false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def document_type
|
228
|
+
self.class::DOCUMENT_TYPE
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
|
233
|
+
def set_publish(published)
|
234
|
+
group_u = published ? 1 : 0
|
235
|
+
|
236
|
+
chmod(-1, -1, -1, group_u, -1, -1, -1, -1, -1)
|
237
|
+
end
|
238
|
+
|
239
|
+
def check_type()
|
240
|
+
type = self['TYPE']
|
241
|
+
|
242
|
+
if type.nil? && @pe_id
|
243
|
+
rc = @client.call(DOCUMENT_METHODS[:info], @pe_id)
|
244
|
+
|
245
|
+
return rc if OpenNebula.is_error?(rc)
|
246
|
+
|
247
|
+
xmldoc = XMLElement.new
|
248
|
+
xmldoc.initialize_xml(rc, 'DOCUMENT')
|
249
|
+
|
250
|
+
type = xmldoc['TYPE']
|
251
|
+
end
|
252
|
+
|
253
|
+
if !type.nil? && type.to_i != document_type
|
254
|
+
return OpenNebula::Error.new(
|
255
|
+
"[DocumentInfo] Error getting document [#{@pe_id}].")
|
256
|
+
end
|
257
|
+
|
258
|
+
return nil
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|