fog-softlayer 0.2.1 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.travis.yml +0 -1
- data/CHANGELOG.md +25 -9
- data/CONTRIBUTING.md +13 -25
- data/CONTRIBUTORS.md +1 -1
- data/Rakefile +3 -3
- data/examples/compute.md +114 -3
- data/examples/network.md +285 -0
- data/examples/tags.md +76 -1
- data/fog-softlayer.gemspec +2 -3
- data/lib/fog/softlayer.rb +2 -0
- data/lib/fog/softlayer/compute.rb +16 -73
- data/lib/fog/softlayer/core.rb +105 -1
- data/lib/fog/softlayer/models/compute/flavor.rb +1 -0
- data/lib/fog/softlayer/models/compute/flavors.rb +1 -0
- data/lib/fog/softlayer/models/compute/image.rb +1 -0
- data/lib/fog/softlayer/models/compute/images.rb +1 -0
- data/lib/fog/softlayer/models/compute/server.rb +71 -12
- data/lib/fog/softlayer/models/compute/servers.rb +9 -5
- data/lib/fog/softlayer/models/compute/tag.rb +5 -6
- data/lib/fog/softlayer/models/compute/tags.rb +1 -0
- data/lib/fog/softlayer/models/network/datacenter.rb +52 -0
- data/lib/fog/softlayer/models/network/datacenters.rb +42 -0
- data/lib/fog/softlayer/models/network/ip.rb +67 -0
- data/lib/fog/softlayer/models/network/ips.rb +39 -0
- data/lib/fog/softlayer/models/network/network.rb +121 -0
- data/lib/fog/softlayer/models/network/networks.rb +47 -0
- data/lib/fog/softlayer/models/network/subnet.rb +62 -0
- data/lib/fog/softlayer/models/network/subnets.rb +33 -0
- data/lib/fog/softlayer/models/network/tag.rb +54 -0
- data/lib/fog/softlayer/models/network/tags.rb +40 -0
- data/lib/fog/softlayer/models/storage/directories.rb +7 -0
- data/lib/fog/softlayer/models/storage/directory.rb +7 -0
- data/lib/fog/softlayer/models/storage/file.rb +7 -0
- data/lib/fog/softlayer/models/storage/files.rb +7 -0
- data/lib/fog/softlayer/network.rb +139 -0
- data/lib/fog/softlayer/requests/compute/create_bare_metal_server.rb +1 -1
- data/lib/fog/softlayer/requests/compute/get_vms.rb +1 -1
- data/lib/fog/softlayer/requests/network/create_network.rb +488 -0
- data/lib/fog/softlayer/requests/network/create_network_tags.rb +50 -0
- data/lib/fog/softlayer/requests/network/delete_network.rb +37 -0
- data/lib/fog/softlayer/requests/network/delete_network_tags.rb +46 -0
- data/lib/fog/softlayer/requests/network/get_datacenter_routers.rb +41 -0
- data/lib/fog/softlayer/requests/network/get_datacenters.rb +30 -0
- data/lib/fog/softlayer/requests/network/get_ip_address.rb +28 -0
- data/lib/fog/softlayer/requests/network/get_network.rb +34 -0
- data/lib/fog/softlayer/requests/network/get_network_tags.rb +47 -0
- data/lib/fog/softlayer/requests/network/get_private_vlan_price_code.rb +27 -0
- data/lib/fog/softlayer/requests/network/get_public_vlan_price_code.rb +27 -0
- data/lib/fog/softlayer/requests/network/get_references_by_tag_name.rb +42 -0
- data/lib/fog/softlayer/requests/network/get_subnet.rb +28 -0
- data/lib/fog/softlayer/requests/network/get_subnet_package_id.rb +29 -0
- data/lib/fog/softlayer/requests/network/get_subnet_price_code.rb +30 -0
- data/lib/fog/softlayer/requests/network/list_networks.rb +30 -0
- data/lib/fog/softlayer/requests/network/list_subnets.rb +28 -0
- data/lib/fog/softlayer/storage.rb +7 -0
- data/lib/fog/softlayer/version.rb +2 -1
- data/tests/compute/flavors_helper.rb +1 -0
- data/tests/compute/server_helper.rb +1 -0
- data/tests/compute/servers_helper.rb +1 -0
- data/tests/helper.rb +7 -0
- data/tests/helpers/collection_helper.rb +7 -0
- data/tests/helpers/compute/flavors_helper.rb +7 -0
- data/tests/helpers/compute/server_helper.rb +7 -0
- data/tests/helpers/compute/servers_helper.rb +7 -0
- data/tests/helpers/formats_helper.rb +7 -0
- data/tests/helpers/formats_helper_tests.rb +7 -0
- data/tests/helpers/mock_helper.rb +7 -0
- data/tests/helpers/model_helper.rb +7 -0
- data/tests/helpers/responds_to_helper.rb +7 -0
- data/tests/helpers/schema_validator_tests.rb +7 -0
- data/tests/helpers/succeeds_helper.rb +7 -0
- data/tests/softlayer/compute/helper.rb +1 -0
- data/tests/softlayer/compute/schema.rb +1 -0
- data/tests/softlayer/models/compute/server_tests.rb +31 -4
- data/tests/softlayer/requests/compute/bmc_tests.rb +4 -3
- data/tests/softlayer/requests/compute/tag_tests.rb +1 -1
- data/tests/softlayer/requests/network/network_tests.rb +129 -0
- metadata +35 -25
@@ -4,6 +4,7 @@
|
|
4
4
|
#
|
5
5
|
# LICENSE: MIT (http://opensource.org/licenses/MIT)
|
6
6
|
#
|
7
|
+
|
7
8
|
require 'fog/core/collection'
|
8
9
|
require 'fog/softlayer/models/compute/server'
|
9
10
|
|
@@ -22,14 +23,17 @@ module Fog
|
|
22
23
|
|
23
24
|
## Get a SoftLayer server.
|
24
25
|
#
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
|
27
|
+
def get(identifier)
|
28
|
+
return nil if identifier.nil? || identifier == ""
|
29
|
+
response = service.get_vm(identifier)
|
30
|
+
bare_metal = false
|
28
31
|
if response.status == 404 # we didn't find it as a VM, look for a BMC server
|
29
|
-
response = service.get_bare_metal_server(
|
30
|
-
|
32
|
+
response = service.get_bare_metal_server(identifier)
|
33
|
+
bare_metal = true
|
31
34
|
end
|
32
35
|
data = response.body
|
36
|
+
data['bare_metal'] = bare_metal
|
33
37
|
new.merge_attributes(data)
|
34
38
|
rescue Excon::Errors::NotFound
|
35
39
|
nil
|
@@ -4,6 +4,7 @@
|
|
4
4
|
#
|
5
5
|
# LICENSE: MIT (http://opensource.org/licenses/MIT)
|
6
6
|
#
|
7
|
+
|
7
8
|
require 'fog/core/model'
|
8
9
|
|
9
10
|
module Fog
|
@@ -29,12 +30,10 @@ module Fog
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def references
|
32
|
-
service.
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
33
|
+
@servers ||= service.request(:tag, "#{id}", :query => "objectMask=references;references.tagType").body['references'].map do |ref|
|
34
|
+
type = ref['tagType']['keyName']
|
35
|
+
service.servers.get(ref['resourceTableId']) if type == 'GUEST' || type == 'HARDWARE'
|
36
|
+
end.compact
|
38
37
|
end
|
39
38
|
|
40
39
|
def save
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
|
3
|
+
# © Copyright IBM Corporation 2014.
|
4
|
+
#
|
5
|
+
# LICENSE: MIT (http://opensource.org/licenses/MIT)
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'fog/core/model'
|
9
|
+
|
10
|
+
module Fog
|
11
|
+
module Network
|
12
|
+
class Softlayer
|
13
|
+
class Datacenter < Fog::Model
|
14
|
+
identity :id
|
15
|
+
|
16
|
+
attribute :long_name, :aliases => 'longName'
|
17
|
+
attribute :name
|
18
|
+
|
19
|
+
def initialize(attributes)
|
20
|
+
@connection = attributes[:connection]
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def routers
|
25
|
+
requires :id
|
26
|
+
@routers ||= service.get_datacenter_routers(id).body
|
27
|
+
end
|
28
|
+
|
29
|
+
def routable_subnets
|
30
|
+
requires :id
|
31
|
+
@routable_subnets ||= service.request(:location_datacenter, "#{id}/get_bound_subnets").body
|
32
|
+
end
|
33
|
+
|
34
|
+
def save
|
35
|
+
raise "Not possible."
|
36
|
+
end
|
37
|
+
|
38
|
+
def create
|
39
|
+
raise "Not possible."
|
40
|
+
end
|
41
|
+
|
42
|
+
def update
|
43
|
+
raise "Not possible."
|
44
|
+
end
|
45
|
+
|
46
|
+
def destroy
|
47
|
+
raise "Not possible."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
|
3
|
+
# © Copyright IBM Corporation 2014.
|
4
|
+
#
|
5
|
+
# LICENSE: MIT (http://opensource.org/licenses/MIT)
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'fog/core/collection'
|
9
|
+
require 'fog/softlayer/models/network/datacenter'
|
10
|
+
|
11
|
+
module Fog
|
12
|
+
module Network
|
13
|
+
class Softlayer
|
14
|
+
class Datacenters < Fog::Collection
|
15
|
+
attribute :filters
|
16
|
+
|
17
|
+
model Fog::Network::Softlayer::Datacenter
|
18
|
+
|
19
|
+
def initialize(attributes)
|
20
|
+
self.filters ||= {}
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def all(filters = filters)
|
25
|
+
self.filters = filters
|
26
|
+
load(service.get_datacenters.body)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(id)
|
30
|
+
data = service.request(:location_datacenter, "#{id}/get_object").body
|
31
|
+
new.merge_attributes(data)
|
32
|
+
rescue Fog::Network::Softlayer::NotFound
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def by_name(name)
|
37
|
+
all.map { |dc| dc if dc.name == name }.compact.first
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
|
3
|
+
# © Copyright IBM Corporation 2014.
|
4
|
+
#
|
5
|
+
# LICENSE: MIT (http://opensource.org/licenses/MIT)
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'fog/core/model'
|
9
|
+
|
10
|
+
module Fog
|
11
|
+
module Network
|
12
|
+
class Softlayer
|
13
|
+
class Ip < Fog::Model
|
14
|
+
identity :id
|
15
|
+
|
16
|
+
attribute :subnet_id, :aliases => 'subnetId'
|
17
|
+
attribute :address, :aliases => 'ipAddress'
|
18
|
+
attribute :broadcast, :aliases => 'isBroadcast'
|
19
|
+
attribute :gateway, :aliases => 'isGateway'
|
20
|
+
attribute :network, :aliases => 'isNetwork'
|
21
|
+
attribute :reserved, :aliases => 'isReserved'
|
22
|
+
attribute :note
|
23
|
+
attribute :assigned_to, :aliases => ['hardware', 'virtualGuest']
|
24
|
+
|
25
|
+
def initialize(attributes)
|
26
|
+
@connection = attributes[:connection]
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def save
|
31
|
+
requires :subnet_id
|
32
|
+
identity ? update : create
|
33
|
+
end
|
34
|
+
|
35
|
+
def create
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def update
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def destroy
|
44
|
+
requires :id
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def broadcast?
|
49
|
+
attribute[:broadcast]
|
50
|
+
end
|
51
|
+
|
52
|
+
def gateway?
|
53
|
+
attribute[:gateway]
|
54
|
+
end
|
55
|
+
|
56
|
+
def network?
|
57
|
+
attribute[:network]
|
58
|
+
end
|
59
|
+
|
60
|
+
def reserved?
|
61
|
+
attribute[:reserved]
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
|
3
|
+
# © Copyright IBM Corporation 2014.
|
4
|
+
#
|
5
|
+
# LICENSE: MIT (http://opensource.org/licenses/MIT)
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'fog/core/collection'
|
9
|
+
require 'fog/softlayer/models/network/ip'
|
10
|
+
|
11
|
+
module Fog
|
12
|
+
module Network
|
13
|
+
class Softlayer
|
14
|
+
class Ips < Fog::Collection
|
15
|
+
attribute :filters
|
16
|
+
|
17
|
+
model Fog::Network::Softlayer::Ip
|
18
|
+
|
19
|
+
def initialize(attributes)
|
20
|
+
self.filters ||= {}
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def all(filters = filters)
|
25
|
+
self.filters = filters
|
26
|
+
load(service.list_ips(filters).body)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(id)
|
30
|
+
if ip = service.get_ip_address(id).body
|
31
|
+
new(ip)
|
32
|
+
end
|
33
|
+
rescue Fog::Network::Softlayer::NotFound
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
|
3
|
+
# © Copyright IBM Corporation 2014.
|
4
|
+
#
|
5
|
+
# LICENSE: MIT (http://opensource.org/licenses/MIT)
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'fog/core/model'
|
9
|
+
|
10
|
+
module Fog
|
11
|
+
module Network
|
12
|
+
class Softlayer
|
13
|
+
class Network < Fog::Model
|
14
|
+
identity :id
|
15
|
+
|
16
|
+
attribute :name
|
17
|
+
attribute :modify_date, :aliases => 'modifyDate'
|
18
|
+
attribute :note
|
19
|
+
attribute :tags, :aliases => 'tagReferences'
|
20
|
+
attribute :type, :squash => :keyName
|
21
|
+
attribute :datacenter
|
22
|
+
attribute :network_space, :aliases => 'networkSpace'
|
23
|
+
attribute :router, :aliases => 'primaryRouter'
|
24
|
+
#attribute :subnets
|
25
|
+
|
26
|
+
def add_tags(tags)
|
27
|
+
requires :id
|
28
|
+
raise ArgumentError, "Tags argument for #{self.class.name}##{__method__} must be Array." unless tags.is_a?(Array)
|
29
|
+
tags.each do |tag|
|
30
|
+
service.tags.new(:resource_id => self.id, :name => tag).save
|
31
|
+
end
|
32
|
+
self.reload
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def datacenter
|
37
|
+
@datacenter ||= attributes[:datacenter] or (service.datacenters.new(attributes[:router]['datacenter']) if attributes[:router] and attributes[:router]['datacenter'])
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete_tags(tags)
|
41
|
+
requires :id
|
42
|
+
raise ArgumentError, "Tags argument for #{self.class.name}##{__method__} must be Array." unless tags.is_a?(Array)
|
43
|
+
tags.each do |tag|
|
44
|
+
service.tags.new(:resource_id => self.id, :name => tag).destroy
|
45
|
+
end
|
46
|
+
self.reload
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def router=(new_data)
|
51
|
+
raise ArgumentError, "Network Router must be a Hash." unless new_data.is_a?(Hash)
|
52
|
+
attributes[:router] = new_data.select { |k,v| ['id', 'hostname', 'datacenter'].include?(k) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def private?
|
56
|
+
requires :network_space
|
57
|
+
network_space == 'PRIVATE'
|
58
|
+
end
|
59
|
+
|
60
|
+
def public?
|
61
|
+
requires :network_space
|
62
|
+
network_space == 'PUBLIC'
|
63
|
+
end
|
64
|
+
|
65
|
+
def save
|
66
|
+
identity ? update : create
|
67
|
+
end
|
68
|
+
|
69
|
+
def subnets
|
70
|
+
requires :id
|
71
|
+
@subnets ||= attributes['subnets'].map do |subnet|
|
72
|
+
service.subnets.get(subnet['id'])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def create
|
77
|
+
requires :datacenter, :router, :network_space
|
78
|
+
response = service.create_network(build_order).body
|
79
|
+
merge_attributes(response)
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def update
|
84
|
+
requires :id
|
85
|
+
merge_attributes(service.update_network(self.id, self.attributes).body)
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
def destroy
|
90
|
+
requires :id
|
91
|
+
service.delete_network(self.id)
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def tags
|
96
|
+
requires :id
|
97
|
+
attributes[:tags].map { |i| i['tag']['name'] } if attributes[:tags]
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def build_order
|
103
|
+
{
|
104
|
+
'complexType' => 'SoftLayer_Container_Product_Order_Network_Vlan',
|
105
|
+
'name' => name,
|
106
|
+
'routerId' =>router['id'],
|
107
|
+
'router' => router['hostname'],
|
108
|
+
'location' => datacenter.id,
|
109
|
+
'quantity' =>1,
|
110
|
+
'packageId' =>0,
|
111
|
+
'prices' =>[
|
112
|
+
{'id' => public? ? service.get_public_vlan_price_code : service.get_private_vlan_price_code },
|
113
|
+
{'id' => service.get_subnet_price_code }
|
114
|
+
]
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
|
3
|
+
# © Copyright IBM Corporation 2014.
|
4
|
+
#
|
5
|
+
# LICENSE: MIT (http://opensource.org/licenses/MIT)
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'fog/core/collection'
|
9
|
+
require 'fog/softlayer/models/network/network'
|
10
|
+
|
11
|
+
module Fog
|
12
|
+
module Network
|
13
|
+
class Softlayer
|
14
|
+
class Networks < Fog::Collection
|
15
|
+
model Fog::Network::Softlayer::Network
|
16
|
+
|
17
|
+
def all
|
18
|
+
data = service.list_networks.body
|
19
|
+
load(data)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get(id)
|
23
|
+
if network = service.get_network(id).body
|
24
|
+
new(network)
|
25
|
+
end
|
26
|
+
rescue Fog::Network::Softlayer::NotFound
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def tagged_with(tags)
|
31
|
+
raise ArgumentError, "Tags argument for #{self.class.name}##{__method__} must be Array." unless tags.is_a?(Array)
|
32
|
+
ids = service.get_references_by_tag_name(tags.join(',')).body.map do |tag|
|
33
|
+
tag['references'].map do |ref|
|
34
|
+
ref['resourceTableId']
|
35
|
+
end
|
36
|
+
end.flatten.uniq
|
37
|
+
ids.map { |id| get(id) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def by_name(name)
|
41
|
+
all.select { |vlan| vlan.name == name }.first
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
|
3
|
+
# © Copyright IBM Corporation 2014.
|
4
|
+
#
|
5
|
+
# LICENSE: MIT (http://opensource.org/licenses/MIT)
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'fog/core/model'
|
9
|
+
|
10
|
+
module Fog
|
11
|
+
module Network
|
12
|
+
class Softlayer
|
13
|
+
class Subnet < Fog::Model
|
14
|
+
identity :id
|
15
|
+
|
16
|
+
attribute :name, :aliases => 'note'
|
17
|
+
attribute :network_id, :aliases => 'networkIdentifier'
|
18
|
+
attribute :vlan_id, :aliases => 'networkVlanId'
|
19
|
+
attribute :cidr
|
20
|
+
attribute :ip_version, :aliases => 'version'
|
21
|
+
attribute :type, :aliases => 'subnetType'
|
22
|
+
attribute :gateway_ip, :aliases => 'gateway'
|
23
|
+
attribute :broadcast, :aliases => 'broadcastAddress'
|
24
|
+
attribute :gateway
|
25
|
+
attribute :datacenter, :squash => :name
|
26
|
+
|
27
|
+
def addresses
|
28
|
+
@addresses ||= attributes['ipAddresses'].map do |address|
|
29
|
+
service.ips.get(address['id'])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def save
|
34
|
+
requires :network_id, :cidr, :ip_version
|
35
|
+
identity ? update : create
|
36
|
+
end
|
37
|
+
|
38
|
+
def create
|
39
|
+
requires :network_id, :cidr, :ip_version
|
40
|
+
merge_attributes(service.create_subnet(self.network_id,
|
41
|
+
self.cidr,
|
42
|
+
self.ip_version,
|
43
|
+
self.attributes).body['subnet'])
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def update
|
48
|
+
requires :id, :network_id, :cidr, :ip_version
|
49
|
+
merge_attributes(service.update_subnet(self.id,
|
50
|
+
self.attributes).body['subnet'])
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def destroy
|
55
|
+
requires :id
|
56
|
+
service.delete_subnet(self.id)
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|