ropenstack 1.2.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.md +2 -1
- data/doc/Ropenstack.html +151 -0
- data/doc/Ropenstack/Cinder.html +197 -0
- data/doc/Ropenstack/Glance.html +292 -0
- data/doc/Ropenstack/Keystone.html +737 -0
- data/doc/Ropenstack/MalformedRequestError.html +142 -0
- data/doc/Ropenstack/NotFoundError.html +142 -0
- data/doc/Ropenstack/Nova.html +562 -0
- data/doc/Ropenstack/OpenstackService.html +199 -0
- data/doc/Ropenstack/Quantum.html +830 -0
- data/doc/Ropenstack/Rest.html +483 -0
- data/doc/Ropenstack/RopenstackError.html +143 -0
- data/doc/Ropenstack/TimeoutError.html +142 -0
- data/doc/Ropenstack/UnauthorisedError.html +142 -0
- data/doc/created.rid +10 -0
- data/doc/images/add.png +0 -0
- data/doc/images/brick.png +0 -0
- data/doc/images/brick_link.png +0 -0
- data/doc/images/bug.png +0 -0
- data/doc/images/bullet_black.png +0 -0
- data/doc/images/bullet_toggle_minus.png +0 -0
- data/doc/images/bullet_toggle_plus.png +0 -0
- data/doc/images/date.png +0 -0
- data/doc/images/delete.png +0 -0
- data/doc/images/find.png +0 -0
- data/doc/images/loadingAnimation.gif +0 -0
- data/doc/images/macFFBgHack.png +0 -0
- data/doc/images/package.png +0 -0
- data/doc/images/page_green.png +0 -0
- data/doc/images/page_white_text.png +0 -0
- data/doc/images/page_white_width.png +0 -0
- data/doc/images/plugin.png +0 -0
- data/doc/images/ruby.png +0 -0
- data/doc/images/tag_blue.png +0 -0
- data/doc/images/tag_green.png +0 -0
- data/doc/images/transparent.png +0 -0
- data/doc/images/wrench.png +0 -0
- data/doc/images/wrench_orange.png +0 -0
- data/doc/images/zoom.png +0 -0
- data/doc/index.html +93 -0
- data/doc/js/darkfish.js +153 -0
- data/doc/js/jquery.js +18 -0
- data/doc/js/navigation.js +142 -0
- data/doc/js/search.js +94 -0
- data/doc/js/search_index.js +1 -0
- data/doc/js/searcher.js +228 -0
- data/doc/rdoc.css +543 -0
- data/doc/table_of_contents.html +200 -0
- data/lib/ropenstack.rb +18 -0
- data/lib/ropenstack/blockStorage.rb +25 -0
- data/lib/ropenstack/blockStorage/v1.rb +67 -0
- data/lib/ropenstack/blockStorage/v2.rb +11 -0
- data/lib/ropenstack/common/error.rb +12 -0
- data/lib/ropenstack/common/openstackservice.rb +28 -0
- data/lib/ropenstack/common/rest.rb +138 -0
- data/lib/ropenstack/compute.rb +164 -0
- data/lib/ropenstack/compute/v2.rb +11 -0
- data/lib/ropenstack/compute/v2/extensions.rb +14 -0
- data/lib/ropenstack/compute/v2/extensions/admin.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/agents.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/aggregates.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/certificates.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/cloudpipe.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/console.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/coverage.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/defaultsecuritygroup.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/diagnostics.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/extraspecs.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/fixedips.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/flavors.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/hosts.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/hypervisors.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/images.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/instanceactions.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/interfaces.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/keypairs.rb +20 -0
- data/lib/ropenstack/compute/v2/extensions/limits.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/migrations.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/networks.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/quotasets.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/securitygroups.rb +40 -0
- data/lib/ropenstack/compute/v2/extensions/servergroups.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/serverpassword.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/services.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/shelvedservers.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/usage.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/virtualinterfaces.rb +0 -0
- data/lib/ropenstack/compute/v2/extensions/volumes.rb +20 -0
- data/lib/ropenstack/compute/v3.rb +5 -0
- data/lib/ropenstack/database.rb +24 -0
- data/lib/ropenstack/database/v1.rb +113 -0
- data/lib/ropenstack/identity.rb +28 -0
- data/lib/ropenstack/identity/v2.rb +178 -0
- data/lib/ropenstack/identity/v3.rb +13 -0
- data/lib/ropenstack/image.rb +25 -0
- data/lib/ropenstack/image/v1.rb +107 -0
- data/lib/ropenstack/image/v2.rb +134 -0
- data/lib/ropenstack/networking.rb +160 -0
- data/lib/ropenstack/networking/v2.rb +5 -0
- data/lib/ropenstack/networking/v2/extensions.rb +9 -0
- data/lib/ropenstack/networking/v2/extensions/l3.rb +111 -0
- data/lib/ropenstack/networking/v2/extensions/lbaas.rb +5 -0
- data/lib/ropenstack/networking/v2/extensions/metering.rb +5 -0
- data/lib/ropenstack/networking/v2/extensions/quotas.rb +5 -0
- data/lib/ropenstack/networking/v2/extensions/securitygroups.rb +5 -0
- data/lib/ropenstack/objectStorage.rb +60 -0
- data/lib/ropenstack/orchestration.rb +23 -0
- data/lib/ropenstack/orchestration/v1.rb +162 -0
- data/lib/ropenstack/telemetry.rb +23 -0
- data/lib/ropenstack/telemetry/v2.rb +68 -0
- data/ropenstack.gemspec +13 -0
- metadata +112 -1
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'ropenstack/common/rest'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Ropenstack
|
5
|
+
=begin
|
6
|
+
* Name: Compute
|
7
|
+
* Description: Implementation of the Compute API Client in Ruby
|
8
|
+
* Author: Sam 'Tehsmash' Betts
|
9
|
+
* Date: 01/15/2013
|
10
|
+
=end
|
11
|
+
class Compute < OpenstackService
|
12
|
+
require 'ropenstack/compute/v2'
|
13
|
+
require 'ropenstack/compute/v3'
|
14
|
+
|
15
|
+
include Version2
|
16
|
+
|
17
|
+
##
|
18
|
+
# Gets a list of servers from OpenStack
|
19
|
+
#
|
20
|
+
# :call-seq:
|
21
|
+
# servers(id) => A single server with the id matching the parameter
|
22
|
+
# servers() => All servers visible to the tenant making the request
|
23
|
+
##
|
24
|
+
def servers(id)
|
25
|
+
endpoint = "/servers"
|
26
|
+
unless id.nil?
|
27
|
+
endpoint = endpoint + "/" + id
|
28
|
+
end
|
29
|
+
return get_request(address(endpoint), @token)
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Gets a more detailed list of servers from openstack.
|
34
|
+
##
|
35
|
+
def servers_detailed()
|
36
|
+
return get_request(address("/servers/detail"), @token)
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Creates a server on OpenStack.
|
41
|
+
##
|
42
|
+
def create_server(name, image, flavor, networks = nil, keypair = nil, security_group = nil, metadata = nil)
|
43
|
+
data = {
|
44
|
+
"server" => {
|
45
|
+
"name" => name,
|
46
|
+
"imageRef" => image,
|
47
|
+
"flavorRef" => flavor,
|
48
|
+
}
|
49
|
+
}
|
50
|
+
unless networks.nil?
|
51
|
+
data["server"]["networks"] = networks
|
52
|
+
end
|
53
|
+
unless keypair.nil?
|
54
|
+
data["server"]["key_name"] = keypair
|
55
|
+
end
|
56
|
+
unless security_group.nil?
|
57
|
+
data["server"]["security_group"] = security_group
|
58
|
+
end
|
59
|
+
return post_request(address("/servers"), data, @token)
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Deletes a server from Openstack based on an id
|
64
|
+
##
|
65
|
+
def delete_server(id)
|
66
|
+
return delete_request(address("/servers/" + id), @token)
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Perform an action on a server on Openstack, by passing an id,
|
71
|
+
# and an action, some actions require more data.
|
72
|
+
#
|
73
|
+
# E.g. action(id, "reboot", "hard")
|
74
|
+
##
|
75
|
+
def action(id, act, *args)
|
76
|
+
data = case act
|
77
|
+
when "reboot" then {'reboot' =>{"type" => args[0]}}
|
78
|
+
when "vnc" then {'os-getVNCConsole' => { "type" => "novnc" }}
|
79
|
+
when "stop" then {'os-stop' => 'null'}
|
80
|
+
when "start" then {'os-start' => 'null'}
|
81
|
+
when "pause" then {'pause' => 'null'}
|
82
|
+
when "unpause" then {'unpause' => 'null'}
|
83
|
+
when "suspend" then {'suspend' => 'null'}
|
84
|
+
when "resume" then {'resume' => 'null'}
|
85
|
+
when "create_image" then {'createImage' => {'name' => args[0], 'metadata' => args[1]}}
|
86
|
+
else raise "Invalid Action"
|
87
|
+
end
|
88
|
+
return post_request(address("/servers/" + id + "/action"), data, @token)
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Retrieve a list of images from Openstack through the nova endpoint
|
93
|
+
##
|
94
|
+
def images()
|
95
|
+
uri = URI.parse("http://" + @location.host + ":9292/v2/images")
|
96
|
+
return get_request(uri, @token)
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Delete an image stored on Openstack through the nova endpoint
|
101
|
+
##
|
102
|
+
def delete_image(id)
|
103
|
+
uri = URI.parse("http://" + @location.host + ":" + @location.port.to_s + "/v2/images/" + id)
|
104
|
+
return delete_request(uri, @token)
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Get a list of flavors that Servers can be
|
109
|
+
##
|
110
|
+
def flavors()
|
111
|
+
return get_request(address("/flavors/detail"), @token)
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Get a tenants compute quotas
|
116
|
+
##
|
117
|
+
def limits()
|
118
|
+
return get_request(address("/limits"), @token)
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Get a list of Compute Extensions
|
123
|
+
##
|
124
|
+
def extensions(ali)
|
125
|
+
if ali.nil?
|
126
|
+
return get_request(address("/extensions"), @token)
|
127
|
+
else
|
128
|
+
return get_request(address("/extensions/"+ ali), @token)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def metadata(id, key)
|
133
|
+
if key.nil?
|
134
|
+
return get_request(address("/servers/"+id+"/metadata"), @token)
|
135
|
+
else
|
136
|
+
return get_request(address("/servers/"+id+"/metadata/" + key), @token)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def set_metadata(id, data, key)
|
141
|
+
if key.nil?
|
142
|
+
return put_request(address("/servers/"+id+"/metadata"), data, @token)
|
143
|
+
else
|
144
|
+
return put_request(address("/servers/"+id+"/metadata/"+key), data, @token)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def update_metadata(id, data)
|
149
|
+
return post_request(address("/servers/"+id+"/metadata"), data, @token)
|
150
|
+
end
|
151
|
+
|
152
|
+
def delete_metadata(id, key)
|
153
|
+
return delete_request(address("/servers/"+id+"/metadata/" + key), @token)
|
154
|
+
end
|
155
|
+
|
156
|
+
def ips(id, network)
|
157
|
+
if network.nil?
|
158
|
+
return get_request(address("/servers/"+id+"/ips"), @token)
|
159
|
+
else
|
160
|
+
return get_request(address("/servers/"+id+"/ips/" + network), @token)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Ropenstack
|
2
|
+
module Compute::Version2
|
3
|
+
module Extensions
|
4
|
+
# Pull in module extension files
|
5
|
+
require "ropenstack/compute/v2/extensions/keypairs"
|
6
|
+
require "ropenstack/compute/v2/extensions/securitygroups"
|
7
|
+
|
8
|
+
# Include individual extensions so that if anyone wants to
|
9
|
+
# simply include all extensions they only have to include one file.
|
10
|
+
include Keypairs
|
11
|
+
include SecurityGroups
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Ropenstack::Compute::Version2::Extensions
|
2
|
+
module Keypairs
|
3
|
+
def keypairs(name = nil)
|
4
|
+
endpoint = "/os-keypairs"
|
5
|
+
unless name.nil?
|
6
|
+
endpoint = "#{endpoint}/#{name}"
|
7
|
+
end
|
8
|
+
return get_request(address(endpoint), @token)
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_keypair(name)
|
12
|
+
data = { "keypair" => { "name" => name } }
|
13
|
+
return post_request(address("/os-keypairs"), data, @token)
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete_keypair(name)
|
17
|
+
return delete_request(address("/os-keypairs/#{name}"), @token)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Ropenstack::Compute::Version2::Extensions
|
2
|
+
module SecurityGroups
|
3
|
+
def security_groups(id = nil)
|
4
|
+
endpoint = "/os-security-groups"
|
5
|
+
unless id.nil?
|
6
|
+
endpoint = "#{endpoint}/#{id}"
|
7
|
+
end
|
8
|
+
return get_request(address(endpoint), @token)
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_security_group(name, description)
|
12
|
+
data = { "security_group" => {"name" => name, "description" => description } }
|
13
|
+
return post_request(address("/os-security-groups"), data, @token)
|
14
|
+
end
|
15
|
+
|
16
|
+
def destroy_security_group(id)
|
17
|
+
return post_request(address("/os-security-groups/#{id}"), @token)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_security_group_rule(protocol, from, to, cidr, parent, group = nil)
|
21
|
+
data = {
|
22
|
+
"security_group_rule" => {
|
23
|
+
"ip_protocol" => protocol,
|
24
|
+
"from_port" => from,
|
25
|
+
"to_port" => to,
|
26
|
+
"cidr" => cidr,
|
27
|
+
"parent_group_id" => parent
|
28
|
+
}
|
29
|
+
}
|
30
|
+
unless group.nil?
|
31
|
+
data["security_group_rule"]["group_id"] = group
|
32
|
+
end
|
33
|
+
return post_request(address("/os-security-group-rules"), data, @token)
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy_security_group_rule(id)
|
37
|
+
return delete_request(address("/os-security-group-rules/#{id}"), @token)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Ropenstack::Compute::Version2::Extensions
|
2
|
+
module Volumes
|
3
|
+
##
|
4
|
+
# Attach a cinder volume to a server, by passing the server id and
|
5
|
+
# the volume id.
|
6
|
+
##
|
7
|
+
def attach_volume(id, volume)
|
8
|
+
data = { 'volumeAttachment' => { 'volumeId' => volume, 'device' => "/dev/vdb" } }
|
9
|
+
return post_request(address("/servers/" + id + "/os-volume_attachments"), data, @token)
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Remove a cinder volume from a server, by passing the server id and
|
14
|
+
# the attachment id.
|
15
|
+
##
|
16
|
+
def detach_volume(id, attachment)
|
17
|
+
return delete_request(address("/servers/"+id+"/os-volume_attachments/"+volume), @token)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'ropenstack/common/rest'
|
2
|
+
|
3
|
+
module Ropenstack
|
4
|
+
##
|
5
|
+
# * Name: Database
|
6
|
+
# * Description: Implementation of the Block Storage API Client in Ruby.
|
7
|
+
# * Author: Sam 'Tehsmash' Betts, John Davidge
|
8
|
+
# * Date: 30/06/2014
|
9
|
+
##
|
10
|
+
class Database < OpenstackService
|
11
|
+
# Pull in sub-modules.
|
12
|
+
require 'ropenstack/database/v1'
|
13
|
+
|
14
|
+
def initialize(location, token, type, accountid)
|
15
|
+
super(location, token)
|
16
|
+
@accountid = accountid
|
17
|
+
case type
|
18
|
+
when "database" then extend Version1
|
19
|
+
else
|
20
|
+
raise Ropenstack::RopenstackError, "Invalid type passed to Database"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Ropenstack
|
2
|
+
##
|
3
|
+
# * Name: DatabaseVersion1
|
4
|
+
# * Description: Implementation of the Block Storage V1.0 API Client in Ruby.
|
5
|
+
# * Author: Sam 'Tehsmash' Betts, John Davidge
|
6
|
+
# * Date: 30/06/2014
|
7
|
+
##
|
8
|
+
module Database::Version1
|
9
|
+
## Database Instances
|
10
|
+
def instances(id)
|
11
|
+
if id.nil?
|
12
|
+
get_request(address("/instances"), @token)
|
13
|
+
else
|
14
|
+
get_request(address("/instances/" + id), @token)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def instance_create(databases, flavorRef, name, users, volume)
|
19
|
+
## Construct data
|
20
|
+
data = { :instance => {
|
21
|
+
:databases => databases,
|
22
|
+
:flavorRef => flavorRef,
|
23
|
+
:name => name,
|
24
|
+
:users => users,
|
25
|
+
:volume => volume
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
post_request(address("/instances"), data, @token)
|
30
|
+
end
|
31
|
+
|
32
|
+
def instance_delete(id)
|
33
|
+
delete_request(address("/instances/" + id), @token)
|
34
|
+
end
|
35
|
+
|
36
|
+
def instance_root(id)
|
37
|
+
get_request(address("/instances/" + id + "/root"), @token)
|
38
|
+
end
|
39
|
+
|
40
|
+
def instance_root_enable(id)
|
41
|
+
## Empty data field
|
42
|
+
post_request(address("/instances/" + id + "/root"), {}, @token)
|
43
|
+
end
|
44
|
+
|
45
|
+
## Database Instance Actions
|
46
|
+
def instance_action(id, action, param)
|
47
|
+
case action
|
48
|
+
when "RESTART"
|
49
|
+
post_request(address("/instances/" + id + "/action"), {:restart => {}}, @token)
|
50
|
+
when "RESIZE"
|
51
|
+
if param.is_a? String
|
52
|
+
post_request(address("/instances/" + id + "/action"), {:resize => {:flavorRef => param }}, @token)
|
53
|
+
elsif param.is_a? Int
|
54
|
+
post_request(address("/instances/" + id + "/action"), {:resize => {:volume => {:size => param }}}, @token)
|
55
|
+
else
|
56
|
+
raise Ropenstack::RopenstackError, "Invalid Parameter Passed"
|
57
|
+
end
|
58
|
+
else
|
59
|
+
raise Ropenstack::RopenstackError, "Invalid Action Passed"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
## Databases
|
64
|
+
def databases(instance)
|
65
|
+
get_request(address("/instances/" + instance + "/databases"), @token)
|
66
|
+
end
|
67
|
+
|
68
|
+
def database_create(instance, name)
|
69
|
+
data = { :databases => [
|
70
|
+
{
|
71
|
+
:name => name
|
72
|
+
}
|
73
|
+
]
|
74
|
+
}
|
75
|
+
post_request(address("/instances/" + instance + "/databases"), data, @token)
|
76
|
+
end
|
77
|
+
|
78
|
+
def database_delete(instance, name)
|
79
|
+
delete_request(address("/instances/" + instance + "/databases/" + name), @token)
|
80
|
+
end
|
81
|
+
|
82
|
+
## Users
|
83
|
+
def users(instance)
|
84
|
+
get_request(address("/instances/" + instance + "/users"), @token)
|
85
|
+
end
|
86
|
+
|
87
|
+
def user_create(instance, name, databases)
|
88
|
+
## TODO Make this work
|
89
|
+
post_request(address("/instances/" + instance + "/users"), data, @token)
|
90
|
+
end
|
91
|
+
|
92
|
+
def user_delete(instance, name)
|
93
|
+
post_request(address("/instances/" + instance + "/users/" + name), @token)
|
94
|
+
end
|
95
|
+
|
96
|
+
## Flavors
|
97
|
+
def flavors(id)
|
98
|
+
if id.nil?
|
99
|
+
get_request(address("/flavors"), @token)
|
100
|
+
else
|
101
|
+
get_request(address("/flavors/" + id), @token)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def address(endpoint)
|
106
|
+
super(endpoint) + @accountid
|
107
|
+
end
|
108
|
+
|
109
|
+
def version
|
110
|
+
"V1"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|