mirror of
https://github.com/fog/fog.git
synced 2022-11-09 13:51:43 -05:00
[openvz|compute] Initial commit
This commit is contained in:
parent
c716044ed3
commit
b9fdff758a
40 changed files with 1349 additions and 1 deletions
5
Rakefile
5
Rakefile
|
@ -57,6 +57,11 @@ namespace :test do
|
|||
sh("export FOG_MOCK=#{mock} && bundle exec shindont tests/vsphere")
|
||||
end
|
||||
end
|
||||
task :openvz do
|
||||
[true].each do |mock|
|
||||
sh("export FOG_MOCK=#{mock} && bundle exec shindont tests/openvz")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
task :nuke do
|
||||
|
|
|
@ -43,4 +43,5 @@ require 'fog/voxel'
|
|||
require 'fog/xenserver'
|
||||
require 'fog/zerigo'
|
||||
require 'fog/cloudsigma'
|
||||
require 'fog/openvz'
|
||||
|
||||
|
|
|
@ -95,3 +95,4 @@ require 'fog/bin/voxel'
|
|||
require 'fog/bin/xenserver'
|
||||
require 'fog/bin/zerigo'
|
||||
require 'fog/bin/cloudsigma'
|
||||
require 'fog/bin/openvz'
|
||||
|
|
31
lib/fog/bin/openvz.rb
Normal file
31
lib/fog/bin/openvz.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
class Openvz < Fog::Bin
|
||||
class << self
|
||||
|
||||
def class_for(key)
|
||||
case key
|
||||
when :compute
|
||||
Fog::Compute::Openvz
|
||||
else
|
||||
raise ArgumentError, "Unsupported #{self} service: #{key}"
|
||||
end
|
||||
end
|
||||
|
||||
def [](service)
|
||||
@@connections ||= Hash.new do |hash, key|
|
||||
hash[key] = case key
|
||||
when :compute
|
||||
Fog::Logger.warning("Openvz[:compute] is not recommended, use Compute[:openvz] for portability")
|
||||
Fog::Compute.new(:provider => 'Openvz')
|
||||
else
|
||||
raise ArgumentError, "Unrecognized service: #{key.inspect}"
|
||||
end
|
||||
end
|
||||
@@connections[service]
|
||||
end
|
||||
|
||||
def services
|
||||
Fog::Openvz.services
|
||||
end
|
||||
|
||||
end
|
||||
end
|
9
lib/fog/openvz.rb
Normal file
9
lib/fog/openvz.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
require 'fog/core'
|
||||
|
||||
module Fog
|
||||
module Openvz
|
||||
extend Fog::Provider
|
||||
service(:compute, 'openvz/compute', 'Compute')
|
||||
end
|
||||
end
|
||||
|
188
lib/fog/openvz/README.md
Normal file
188
lib/fog/openvz/README.md
Normal file
|
@ -0,0 +1,188 @@
|
|||
# Description
|
||||
The openvz provider implements a simple mapping between openvz commands and fog
|
||||
|
||||
# Usage
|
||||
## Establish a connection
|
||||
openvz = ::Fog::Compute.new( {:provider => 'openvz'})
|
||||
|
||||
Additional option is **:openvz_connect_command**:
|
||||
It allows you to specify connect command to connect to the openvz server, if it's not localhost.
|
||||
|
||||
- This is specified as a string where the '@command@' placeholder will be replaced when the commands are executed
|
||||
- The @command@ will contain double quotes, therefore make sure you use single quotes only inside the placeholder
|
||||
|
||||
To connect to a remote ssh server myopenvzserver and sudo excute the command
|
||||
|
||||
openvz = ::Fog::Compute.new( {
|
||||
:provider => 'openvz',
|
||||
:openvz_connect_command => "ssh myopenvzserver 'sudo @command'"
|
||||
})
|
||||
|
||||
## List servers
|
||||
|
||||
openvz = ::Fog::Compute.new( {:provider => 'openvz'})
|
||||
servers = openvz.servers.all
|
||||
servers.each do |s|
|
||||
puts c.ctid
|
||||
end
|
||||
|
||||
## Server Unique id
|
||||
Servers have the ctid as identity.
|
||||
|
||||
## Get a specific server
|
||||
|
||||
openvz = ::Fog::Compute.new( {:provider => 'openvz'})
|
||||
server = openvz.servers.get(104)
|
||||
|
||||
## Server lifecycle
|
||||
|
||||
openvz = ::Fog::Compute.new( {:provider => 'openvz'})
|
||||
# Create a server
|
||||
server = openvz.servers.create(
|
||||
:ctid => '104',
|
||||
:ostemplate => 'ubuntu-12.04-x86_64',
|
||||
:diskspace => 1024*1024 #in kbyte
|
||||
)
|
||||
server.reload
|
||||
|
||||
# Start a server
|
||||
unless server.status == 'running'
|
||||
server.start
|
||||
end
|
||||
|
||||
server.set({
|
||||
:nameserver => '8.8.8.8',
|
||||
:ipadd => '192.168.8.10',
|
||||
:ram => '380M',
|
||||
:hostname => 'wonderfullserver',
|
||||
:name => 'chef',
|
||||
:description => 'wonderfullserver',
|
||||
:save => true
|
||||
})
|
||||
|
||||
# Reboot a server
|
||||
server.reboot
|
||||
sleep 3
|
||||
|
||||
# Get the ipaddress
|
||||
puts "ipaddress: #{server.public_ip_address}"
|
||||
|
||||
server.wait_for { status == 'running' }
|
||||
|
||||
# Stop the server
|
||||
server.stop
|
||||
|
||||
# Destroy the server
|
||||
server.destroy
|
||||
|
||||
## Models
|
||||
Both compute::server and computer::servers (collections) have been implemented
|
||||
|
||||
Note:
|
||||
- server.save can only be called upon creation, use the server.set command to change the settings
|
||||
- server.public_ip_address will only return the first ip address
|
||||
- TODO: snapshots could be implemented as a collection
|
||||
- server.state has the standard openvz states.
|
||||
- server.ready? assumes server.status == 'running'
|
||||
|
||||
## Requests
|
||||
### Passing parameters
|
||||
The server request are in essence a passthrough to __vzctl__.
|
||||
Just provide the options as a hash in key,value pairs.
|
||||
If it's just a switch (like --save), use a key and a boolean(true).
|
||||
|
||||
The following command in plain cli-vzctl:
|
||||
|
||||
vzctl set 104 --nameserver 8.8.8.8 --ipadd 192.168.8.10 --ram '380M'
|
||||
|
||||
Would be in fog-speak:
|
||||
|
||||
server = openvz.servers.get(104)
|
||||
server.set({
|
||||
:nameserver => '8.8.8.8',
|
||||
:ipadd => '192.168.8.10',
|
||||
:ram => '380M',
|
||||
:save => true
|
||||
})
|
||||
|
||||
To specify multiple values for the same option pass an array
|
||||
|
||||
server.set({
|
||||
:nameserver => ['8.8.8.8','7.7.7.7'],
|
||||
:ipadd => ['192.168.8.10','192.168.4.10'],
|
||||
:ram => '380M',
|
||||
:save => true
|
||||
})
|
||||
|
||||
### Passing arguments
|
||||
both exec, exec2 and runscript take no parameters just arguments
|
||||
|
||||
server = openvz.servers.get(104)
|
||||
uname_output = server.exec("uname -a")
|
||||
|
||||
### Not implemented
|
||||
From all the options to vzctl (see below) the following commands have **NOT** been implemented:
|
||||
|
||||
- console : as it requires direct input
|
||||
|
||||
## VZCTL commands
|
||||
|
||||
the current version of the fog openvz driver is based on the following vzctl syntax
|
||||
|
||||
vzctl version 4.3
|
||||
Copyright (C) 2000-2012, Parallels, Inc.
|
||||
This program may be distributed under the terms of the GNU GPL License.
|
||||
|
||||
Usage: vzctl [options] <command> <ctid> [parameters]
|
||||
|
||||
vzctl create <ctid> [--ostemplate <name>] [--config <name>]
|
||||
[--layout ploop|simfs] [--hostname <name>] [--name <name>] [--ipadd <addr>]
|
||||
[--diskspace <kbytes>] [--private <path>] [--root <path>]
|
||||
[--local_uid <UID>] [--local_gid <GID>]
|
||||
vzctl start <ctid> [--force] [--wait]
|
||||
vzctl destroy | mount | umount | stop | restart | status <ctid>
|
||||
vzctl convert <ctid> [--layout ploop[:mode]] [--diskspace <kbytes>]
|
||||
vzctl compact <ctid>
|
||||
vzctl snapshot <ctid> [--id <uuid>] [--name <name>] [--description <desc>]
|
||||
[--skip-suspend]
|
||||
vzctl snapshot-switch | snapshot-delete <ctid> --id <uuid>
|
||||
vzctl snapshot-mount <ctid> --id <uuid> --target <dir>
|
||||
vzctl snapshot-umount <ctid> --id <uuid>
|
||||
vzctl snapshot-list <ctid> [-H] [-o field[,field...]] [--id <uuid>]
|
||||
vzctl quotaon | quotaoff | quotainit <ctid>
|
||||
vzctl console <ctid> [ttyno]
|
||||
vzctl enter <ctid> [--exec <command> [arg ...]]
|
||||
vzctl exec | exec2 <ctid> <command> [arg ...]
|
||||
vzctl runscript <ctid> <script>
|
||||
vzctl suspend | resume <ctid> [--dumpfile <name>]
|
||||
vzctl set <ctid> [--save] [--force] [--setmode restart|ignore]
|
||||
[--ram <bytes>[KMG]] [--swap <bytes>[KMG]]
|
||||
[--ipadd <addr>] [--ipdel <addr>|all] [--hostname <name>]
|
||||
[--nameserver <addr>] [--searchdomain <name>]
|
||||
[--onboot yes|no] [--bootorder <N>]
|
||||
[--userpasswd <user>:<passwd>]
|
||||
[--cpuunits <N>] [--cpulimit <N>] [--cpus <N>] [--cpumask <cpus>]
|
||||
[--diskspace <soft>[:<hard>]] [--diskinodes <soft>[:<hard>]]
|
||||
[--quotatime <N>] [--quotaugidlimit <N>] [--mount_opts <opt>[,<opt>...]]
|
||||
[--capability <name>:on|off ...]
|
||||
[--devices b|c:major:minor|all:r|w|rw]
|
||||
[--devnodes device:r|w|rw|none]
|
||||
[--netif_add <ifname[,mac,host_ifname,host_mac,bridge]]>]
|
||||
[--netif_del <ifname>]
|
||||
[--applyconfig <name>] [--applyconfig_map <name>]
|
||||
[--features <name:on|off>] [--name <vename>] [--ioprio <N>]
|
||||
[--pci_add [<domain>:]<bus>:<slot>.<func>] [--pci_del <d:b:s.f>]
|
||||
[--iptables <name>] [--disabled <yes|no>]
|
||||
[UBC parameters]
|
||||
|
||||
UBC parameters (N - items, P - pages, B - bytes):
|
||||
Two numbers divided by colon means barrier:limit.
|
||||
In case the limit is not given it is set to the same value as the barrier.
|
||||
--numproc N[:N] --numtcpsock N[:N] --numothersock N[:N]
|
||||
--vmguarpages P[:P] --kmemsize B[:B] --tcpsndbuf B[:B]
|
||||
--tcprcvbuf B[:B] --othersockbuf B[:B] --dgramrcvbuf B[:B]
|
||||
--oomguarpages P[:P] --lockedpages P[:P] --privvmpages P[:P]
|
||||
--shmpages P[:P] --numfile N[:N] --numflock N[:N]
|
||||
--numpty N[:N] --numsiginfo N[:N] --dcachesize N[:N]
|
||||
--numiptent N[:N] --physpages P[:P] --avnumproc N[:N]
|
||||
--swappages P[:P]
|
176
lib/fog/openvz/compute.rb
Normal file
176
lib/fog/openvz/compute.rb
Normal file
|
@ -0,0 +1,176 @@
|
|||
require 'fog/openvz'
|
||||
require 'fog/compute'
|
||||
|
||||
module Fog
|
||||
module Compute
|
||||
class Openvz < Fog::Service
|
||||
|
||||
recognizes :openvz_connect_command
|
||||
|
||||
model_path 'fog/openvz/models/compute'
|
||||
model :server
|
||||
collection :servers
|
||||
|
||||
request_path 'fog/openvz/requests/compute'
|
||||
request :list_servers
|
||||
request :get_server_details
|
||||
|
||||
request :create_server
|
||||
request :start_server
|
||||
request :destroy_server
|
||||
request :mount_server
|
||||
request :umount_server
|
||||
request :stop_server
|
||||
request :restart_server
|
||||
request :status_server
|
||||
request :convert_server
|
||||
request :compact_server
|
||||
request :snapshot_server
|
||||
request :snapshot_switch_server
|
||||
request :snapshot_delete_server
|
||||
request :snapshot_mount_server
|
||||
request :snapshot_umount_server
|
||||
request :snapshot_list_server
|
||||
request :quotaon_server
|
||||
request :quotaoff_server
|
||||
request :quotainit_server
|
||||
request :exec_server
|
||||
request :exec2_server
|
||||
request :runscript_server
|
||||
request :suspend_server
|
||||
request :resume_server
|
||||
request :set_server
|
||||
|
||||
class Mock
|
||||
|
||||
def self.data
|
||||
@data ||= Hash.new do |hash, key|
|
||||
hash[key] = {
|
||||
:servers => [],
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def self.reset
|
||||
@data = nil
|
||||
end
|
||||
|
||||
def initialize(options={})
|
||||
@openvz_connect_command = options[:openvz_connect_command]
|
||||
end
|
||||
|
||||
def data
|
||||
self.class.data[@openvz_connect_command]
|
||||
end
|
||||
|
||||
def reset_data
|
||||
self.class.data.delete(@openvz_connect_command)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Real
|
||||
|
||||
def initialize(options={})
|
||||
@openvz_connect_command = options[:openvz_connect_command]
|
||||
end
|
||||
|
||||
def reload
|
||||
#@connection.reset
|
||||
end
|
||||
|
||||
def expand_commands(commands, params, args)
|
||||
# For all params unless the ctid
|
||||
# pass it to the command
|
||||
params.keys.each do |k|
|
||||
if (params[k]) && (k.to_s != 'ctid')
|
||||
|
||||
if params[k].is_a?(Array)
|
||||
# For arrays we pass the params and key multiple times
|
||||
params[k].each do |p|
|
||||
commands << "--#{k}"
|
||||
commands << "\"#{p}\""
|
||||
end
|
||||
else
|
||||
commands << "--#{k}"
|
||||
# For booleans only pass the options
|
||||
# We put the values of params between doublequotes
|
||||
commands << "\"#{params[k]}\"" unless !!params[k] == params[k]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# These commands will be passed directly
|
||||
args.each do |a|
|
||||
commands << a
|
||||
end
|
||||
|
||||
# Delete empty commands
|
||||
commands.delete("")
|
||||
|
||||
# Now build the full command
|
||||
full_command = "#{commands.join(' ')}"
|
||||
|
||||
# If we have a connect command , expand it
|
||||
if @openvz_connect_command.nil?
|
||||
prefixed_command = "#{full_command}"
|
||||
else
|
||||
prefixed_command = @openvz_connect_command.sub('@command@',"#{full_command}")
|
||||
end
|
||||
return prefixed_command
|
||||
end
|
||||
|
||||
def vzctl(command, params,args = [])
|
||||
commands = [ 'vzctl', command, params['ctid'], params[:ctid] ]
|
||||
prefixed_command = expand_commands(commands, params, args)
|
||||
|
||||
result = `#{prefixed_command}`
|
||||
exitcode = $?.to_i
|
||||
|
||||
# Tofix - we use backticks to get the exitcode
|
||||
# But backticks output stderr
|
||||
arg_commands = [ 'exec', 'exec2', 'runscript' ]
|
||||
if (arg_commands.include?(command))
|
||||
return { :output => result , :exitcode => exitcode }
|
||||
else
|
||||
raise Fog::Errors::Error.new result unless exitcode == 0
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
def vzlist(params,args = [])
|
||||
|
||||
commands = [ 'vzlist', '-a', '-j' , params['ctid'], params[:ctid] ]
|
||||
prefixed_command = expand_commands(commands, params, args)
|
||||
|
||||
# We do some wier stuff here:
|
||||
# - a simple backtick doesn't capture stderr
|
||||
# - popen4 would solve it but would require another external gem dependency
|
||||
# - popen3 doesn't capture exitcode (on ruby 1.8) but is a standard call
|
||||
# - so we resort to checking the stderr instead
|
||||
require 'open3'
|
||||
result = ""
|
||||
error = ""
|
||||
puts prefixed_command
|
||||
Open3.popen3("#{prefixed_command}") { |i,o,e,t|
|
||||
result = result + o.read
|
||||
error = error + e.read
|
||||
}
|
||||
|
||||
if (error.length != 0)
|
||||
if(error.include?("not found"))
|
||||
return []
|
||||
else
|
||||
raise Fog::Errors::Error.new error
|
||||
end
|
||||
else
|
||||
return Fog::JSON.decode(result)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
194
lib/fog/openvz/models/compute/server.rb
Normal file
194
lib/fog/openvz/models/compute/server.rb
Normal file
|
@ -0,0 +1,194 @@
|
|||
require 'fog/compute/models/server'
|
||||
|
||||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
|
||||
class Server < Fog::Compute::Server
|
||||
|
||||
identity :ctid
|
||||
attribute :ostemplate
|
||||
attribute :config
|
||||
attribute :layout
|
||||
attribute :hostname
|
||||
attribute :name
|
||||
attribute :ipadd
|
||||
attribute :diskspace
|
||||
attribute :private
|
||||
attribute :root
|
||||
attribute :local_uid
|
||||
attribute :local_gid
|
||||
|
||||
attribute :veid
|
||||
attribute :vpsid
|
||||
attribute :private
|
||||
attribute :mount_opts
|
||||
attribute :origin_sample
|
||||
attribute :smart_name
|
||||
attribute :description
|
||||
attribute :nameserver
|
||||
attribute :searchdomain
|
||||
attribute :status
|
||||
attribute :simfs
|
||||
attribute :cpus
|
||||
attribute :vswap
|
||||
attribute :disabled
|
||||
attribute :ip
|
||||
|
||||
# vzctl create <ctid> [--ostemplate <name>] [--config <name>]
|
||||
# [--layout ploop|simfs] [--hostname <name>] [--name <name>] [--ipadd <addr>]
|
||||
# [--diskspace <kbytes>] [--private <path>] [--root <path>]
|
||||
# [--local_uid <UID>] [--local_gid <GID>]
|
||||
def save
|
||||
requires :ctid
|
||||
raise Fog::Errors::Error.new('Resaving an existing object may create a duplicate') if persisted?
|
||||
meta_hash = {}
|
||||
create_options = {
|
||||
'ctid' => ctid,
|
||||
'ostemplate' => ostemplate,
|
||||
'layout' => layout ,
|
||||
'hostname' => hostname,
|
||||
'name' => name,
|
||||
'ipadd' => ipadd,
|
||||
'diskspace' => diskspace,
|
||||
'private' => private,
|
||||
'root' => root,
|
||||
'local_uid' => local_uid,
|
||||
'local_gid' => local_gid
|
||||
}
|
||||
data = service.create_server(create_options)
|
||||
reload
|
||||
end
|
||||
|
||||
def persisted?
|
||||
ctid.nil?
|
||||
end
|
||||
|
||||
def public_ip_addresses
|
||||
return ip
|
||||
end
|
||||
|
||||
def public_ip_address
|
||||
if ip.nil?
|
||||
return nil
|
||||
else
|
||||
return ip.first
|
||||
end
|
||||
end
|
||||
|
||||
def start
|
||||
data = service.start_server(ctid)
|
||||
end
|
||||
|
||||
def destroy(options = {})
|
||||
data = service.destroy_server(ctid, options)
|
||||
end
|
||||
|
||||
def mount(options = {})
|
||||
data = service.mount_server(ctid, options)
|
||||
end
|
||||
|
||||
def umount(options = {})
|
||||
data = service.umount_server(ctid, options)
|
||||
end
|
||||
|
||||
def stop(options = {})
|
||||
data = service.stop_server(ctid, options)
|
||||
end
|
||||
|
||||
def reboot(options = {})
|
||||
data = service.restart_server(ctid, options)
|
||||
end
|
||||
|
||||
alias_method :restart, :reboot
|
||||
|
||||
def convert(options = {})
|
||||
data = service.convert_server(ctid, options)
|
||||
end
|
||||
|
||||
def compact(options = {})
|
||||
data = service.compact_server(ctid, options)
|
||||
end
|
||||
|
||||
def snapshot(options = {})
|
||||
data = service.snapshot_server(ctid, options)
|
||||
end
|
||||
|
||||
def snapshot_switch(options = {})
|
||||
data = service.snapshot_switch_server(ctid, options)
|
||||
end
|
||||
|
||||
def snapshot_delete(options = {})
|
||||
data = service.snapshot_delete_server(ctid, options)
|
||||
end
|
||||
|
||||
def snapshot_mount(options = {})
|
||||
data = service.snapshot_mount_server(ctid, options)
|
||||
end
|
||||
|
||||
def snapshot_umount(options = {})
|
||||
data = service.snapshot_umount_server(ctid, options)
|
||||
end
|
||||
|
||||
def snapshot_list(options = {})
|
||||
data = service.snapshot_list_server(ctid, options)
|
||||
end
|
||||
|
||||
def quotaon(options = {})
|
||||
data = service.quotaon_server(ctid, options)
|
||||
end
|
||||
|
||||
def quotaoff(options = {})
|
||||
data = service.quotaoff_server(ctid, options)
|
||||
end
|
||||
|
||||
def quotainit(options = {})
|
||||
data = service.quotainit_server(ctid, options)
|
||||
end
|
||||
|
||||
def exec(args)
|
||||
if args.is_a?(String)
|
||||
data = service.exec_server(ctid,[ args ])
|
||||
else
|
||||
data = service.exec_server(ctid,args)
|
||||
end
|
||||
end
|
||||
|
||||
def exec2(args)
|
||||
if args.is_a?(String)
|
||||
data = service.exec2_server(ctid,[ args ])
|
||||
else
|
||||
data = service.exec2_server(ctid,args)
|
||||
end
|
||||
end
|
||||
|
||||
def runscript(args)
|
||||
if args.is_a?(String)
|
||||
data = service.runscript_server(ctid,[ args ])
|
||||
else
|
||||
data = service.runscript_server(ctid,args)
|
||||
end
|
||||
end
|
||||
|
||||
def suspend(options = {})
|
||||
data = service.suspend_server(ctid, options)
|
||||
end
|
||||
|
||||
def resume(options = {})
|
||||
data = service.resume_server(ctid, options)
|
||||
end
|
||||
|
||||
|
||||
def set(options)
|
||||
data = service.set_server(ctid,options)
|
||||
end
|
||||
|
||||
def ready?
|
||||
status == 'running'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
27
lib/fog/openvz/models/compute/servers.rb
Normal file
27
lib/fog/openvz/models/compute/servers.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
require 'fog/core/collection'
|
||||
require 'fog/openvz/models/compute/server'
|
||||
|
||||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
|
||||
class Servers < Fog::Collection
|
||||
model Fog::Compute::Openvz::Server
|
||||
|
||||
def all(filters = {})
|
||||
load service.list_servers
|
||||
end
|
||||
|
||||
def get(id)
|
||||
if server = service.get_server_details(id)
|
||||
new server
|
||||
end
|
||||
rescue Fog::Errors::NotFound
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/compact_server.rb
Normal file
21
lib/fog/openvz/requests/compute/compact_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def compact_server(id,options = {})
|
||||
vzctl("compact",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def compact_server(id, options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/convert_server.rb
Normal file
21
lib/fog/openvz/requests/compute/convert_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def convert_server(id,options = {})
|
||||
vzctl("convert",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def convert_server(id, options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
23
lib/fog/openvz/requests/compute/create_server.rb
Normal file
23
lib/fog/openvz/requests/compute/create_server.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def create_server(options = {})
|
||||
vzctl("create",options)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def create_server(options = {})
|
||||
# When a new fake server is created we set the status to stopped
|
||||
options['status'] = 'stopped'
|
||||
self.data[:servers] << options
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/destroy_server.rb
Normal file
21
lib/fog/openvz/requests/compute/destroy_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def destroy_server(id, options = {})
|
||||
vzctl("destroy",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def destroy_server(id , options = {})
|
||||
self.data[:servers].reject! { |s| s['ctid'].to_s == id.to_s }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/exec2_server.rb
Normal file
21
lib/fog/openvz/requests/compute/exec2_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def exec2_server(id,args)
|
||||
vzctl("exec2",{:ctid => id},args)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def exec2_server(id, args)
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/exec_server.rb
Normal file
21
lib/fog/openvz/requests/compute/exec_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def exec_server(id,args = [])
|
||||
vzctl("exec",{:ctid => id},args)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def exec_server(id, args = [])
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/get_server_details.rb
Normal file
21
lib/fog/openvz/requests/compute/get_server_details.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def get_server_details(id)
|
||||
vzlist({:ctid => id}).first
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def get_server_details(id)
|
||||
return self.data[:servers].find { |s| s['ctid'].to_s == id.to_s }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/list_servers.rb
Normal file
21
lib/fog/openvz/requests/compute/list_servers.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def list_servers(options = {})
|
||||
vzlist({})
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def list_servers
|
||||
self.data[:servers]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/mount_server.rb
Normal file
21
lib/fog/openvz/requests/compute/mount_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def mount_server(id, options = {})
|
||||
vzctl("mount",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def mount_server(id, options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/quotainit_server.rb
Normal file
21
lib/fog/openvz/requests/compute/quotainit_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def quotainit_server(id, options = {})
|
||||
vzctl("quotainit",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def quotainit_server(id, options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/quotaoff_server.rb
Normal file
21
lib/fog/openvz/requests/compute/quotaoff_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def quotaooff_server(id, options = {})
|
||||
vzctl("quotaoff",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def quotaooff_server(id, options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/quotaon_server.rb
Normal file
21
lib/fog/openvz/requests/compute/quotaon_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def quotaon_server(id, options = {})
|
||||
vzctl("quotaon",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def quotaon_server(id, options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
24
lib/fog/openvz/requests/compute/restart_server.rb
Normal file
24
lib/fog/openvz/requests/compute/restart_server.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def restart_server(id, options = {})
|
||||
vzctl("restart",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def restart_server(id, options = {})
|
||||
server = self.data[:servers].find { |s| s['ctid'] == id.to_s }
|
||||
unless server.nil?
|
||||
server['status'] = 'running'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/resume_server.rb
Normal file
21
lib/fog/openvz/requests/compute/resume_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def resume_server(id, options = {})
|
||||
vzctl("resume",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def resume_server(id, options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/runscript_server.rb
Normal file
21
lib/fog/openvz/requests/compute/runscript_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def runscript_server(id,args = [])
|
||||
vzctl("runscript",{:ctid => id},args)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def runscript_server(id,args = [])
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
27
lib/fog/openvz/requests/compute/set_server.rb
Normal file
27
lib/fog/openvz/requests/compute/set_server.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def set_server(id,options = {})
|
||||
vzctl("set",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def set_server(id, options = {})
|
||||
server = self.data[:servers].find { |s| s['ctid'].to_s == id.to_s }
|
||||
unless server.nil?
|
||||
options.each do |k,v|
|
||||
server[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/snapshot_delete_server.rb
Normal file
21
lib/fog/openvz/requests/compute/snapshot_delete_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def snapshot_delete_server(id,options = {})
|
||||
vzctl("snapshot-delete",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def snapshot_delete_server(id,options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/snapshot_list_server.rb
Normal file
21
lib/fog/openvz/requests/compute/snapshot_list_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def snapshot_list_server(id,options = {})
|
||||
vzctl("snapshot-list",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def snapshot_list_server(id, options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/snapshot_mount_server.rb
Normal file
21
lib/fog/openvz/requests/compute/snapshot_mount_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def snapshot_mount_server(id,options = {})
|
||||
vzctl("snapshot-mount",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def snapshot_mount_server(id,options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/snapshot_server.rb
Normal file
21
lib/fog/openvz/requests/compute/snapshot_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def snapshot_server(id,options = {})
|
||||
vzctl("snapshot",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def snapshot_server(id,options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/snapshot_switch_server.rb
Normal file
21
lib/fog/openvz/requests/compute/snapshot_switch_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def snapshot_switch_server(id,options = {})
|
||||
vzctl("snapshot-switch",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def snapshot_switch_server(id,options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/snapshot_umount_server.rb
Normal file
21
lib/fog/openvz/requests/compute/snapshot_umount_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def snapshot_umount_server(id,options = {})
|
||||
vzctl("snapshot-umount",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def snapshot_umount_server(id,options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
24
lib/fog/openvz/requests/compute/start_server.rb
Normal file
24
lib/fog/openvz/requests/compute/start_server.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def start_server(id,options={})
|
||||
vzctl("start",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def start_server(id,options={})
|
||||
server = self.data[:servers].find { |s| s['ctid'].to_s == id.to_s }
|
||||
unless server.nil?
|
||||
server['status'] = 'running'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/status_server.rb
Normal file
21
lib/fog/openvz/requests/compute/status_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def status_server(id, options = {})
|
||||
vzctl("status",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def status_server(id, options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
24
lib/fog/openvz/requests/compute/stop_server.rb
Normal file
24
lib/fog/openvz/requests/compute/stop_server.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def stop_server(id, options = {})
|
||||
vzctl("stop",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def stop_server(id, options = {})
|
||||
server = self.data[:servers].find { |s| s['ctid'].to_s == id.to_s }
|
||||
unless server.nil?
|
||||
server['status'] = 'stopped'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/suspend_server.rb
Normal file
21
lib/fog/openvz/requests/compute/suspend_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def suspend_server(id, options = {})
|
||||
vzctl("suspend",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def suspend_server(id, options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/fog/openvz/requests/compute/umount_server.rb
Normal file
21
lib/fog/openvz/requests/compute/umount_server.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Fog
|
||||
module Compute
|
||||
class Openvz
|
||||
class Real
|
||||
|
||||
def umount_server(id, options = {})
|
||||
vzctl("umount",{:ctid => id}.merge(options))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Mock
|
||||
|
||||
def umount_server(id, options = {})
|
||||
Fog::Mock.not_implemented
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,7 +18,7 @@ end
|
|||
all_providers = Fog.registered_providers.map {|provider| provider.downcase}
|
||||
|
||||
# Manually remove these providers since they are local applications, not lacking credentials
|
||||
all_providers = all_providers - ["libvirt", "vmfusion"]
|
||||
all_providers = all_providers - ["libvirt", "vmfusion", "openvz"]
|
||||
|
||||
available_providers = Fog.available_providers.map {|provider| provider.downcase}
|
||||
|
||||
|
|
41
tests/openvz/helper.rb
Normal file
41
tests/openvz/helper.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
|
||||
# Shortcut for Fog::Compute[:openvz]
|
||||
def openvz_service
|
||||
Fog::Compute[:openvz]
|
||||
end
|
||||
|
||||
# Create a long lived server for the tests
|
||||
def openvz_fog_test_server
|
||||
server = openvz_service.servers.find { |s| s.ctid == '104' }
|
||||
unless server
|
||||
server = openvz_service.servers.create :ctid => '104'
|
||||
server.start
|
||||
server.reload
|
||||
# Wait for the server to come up
|
||||
begin
|
||||
server.wait_for(120) { server.reload rescue nil; server.ready? }
|
||||
rescue Fog::Errors::TimeoutError
|
||||
# Server bootstrap took more than 120 secs!
|
||||
end
|
||||
end
|
||||
server
|
||||
end
|
||||
|
||||
# Destroy the long lived server
|
||||
def openvz_fog_test_server_destroy
|
||||
server = openvz_service.servers.find { |s| s.ctid == '104' }
|
||||
server.destroy if server
|
||||
end
|
||||
|
||||
at_exit do
|
||||
unless Fog.mocking?
|
||||
server = openvz_service.servers.find { |s| s.name == '104' }
|
||||
if server
|
||||
server.wait_for(120) do
|
||||
reload rescue nil; ready?
|
||||
end
|
||||
end
|
||||
server.stop
|
||||
openvz_fog_test_server_destroy
|
||||
end
|
||||
end
|
56
tests/openvz/models/compute/server_tests.rb
Normal file
56
tests/openvz/models/compute/server_tests.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
Shindo.tests("Fog::Compute[:openvz] | server model", ['openvz', 'compute']) do
|
||||
|
||||
server = openvz_fog_test_server
|
||||
|
||||
tests('The server model should') do
|
||||
|
||||
tests('have the action') do
|
||||
test('reload') { server.respond_to? 'reload' }
|
||||
%w{
|
||||
destroy
|
||||
mount
|
||||
umount
|
||||
restart
|
||||
stop
|
||||
start
|
||||
quotaon
|
||||
quotaoff
|
||||
quotainit
|
||||
suspend
|
||||
resume
|
||||
}.each do |action|
|
||||
test(action) { server.respond_to? action }
|
||||
end
|
||||
end
|
||||
tests('have attributes') do
|
||||
model_attribute_hash = server.attributes
|
||||
attributes = [
|
||||
:ctid,
|
||||
:description
|
||||
]
|
||||
tests("The server model should respond to") do
|
||||
attributes.each do |attribute|
|
||||
test("#{attribute}") { server.respond_to? attribute }
|
||||
end
|
||||
end
|
||||
end
|
||||
test('#stop') do
|
||||
pending if Fog.mocking?
|
||||
server.stop
|
||||
server.wait_for { server.status == 'stopped' }
|
||||
server.status == 'stopped'
|
||||
end
|
||||
test('#start') do
|
||||
pending if Fog.mocking?
|
||||
server.start
|
||||
server.wait_for { ready? }
|
||||
server.ready?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# restore server status
|
||||
server.start
|
||||
|
||||
end
|
||||
|
35
tests/openvz/models/compute/servers_tests.rb
Normal file
35
tests/openvz/models/compute/servers_tests.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
Shindo.tests('Fog::Compute[:openvz] | servers collection', ['openvz']) do
|
||||
|
||||
openvz_service = Fog::Compute[:openvz]
|
||||
|
||||
tests('The servers collection') do
|
||||
servers = openvz_service.servers.all
|
||||
|
||||
server = openvz_fog_test_server
|
||||
|
||||
test('should NOT be empty') do
|
||||
servers.reload
|
||||
!servers.empty?
|
||||
end
|
||||
|
||||
test('should be a kind of Fog::Compute::Openvz::Servers') do
|
||||
servers.kind_of? Fog::Compute::Openvz::Servers
|
||||
end
|
||||
|
||||
tests('should have Fog::Compute::Openvz::Servers inside') do
|
||||
servers.each do |s|
|
||||
test { s.kind_of? Fog::Compute::Openvz::Server }
|
||||
end
|
||||
end
|
||||
|
||||
tests('should be able to reload itself').succeeds { servers.reload }
|
||||
|
||||
tests('should be able to get a model') do
|
||||
test('by instance ctid') do
|
||||
servers.get(server.ctid).kind_of? Fog::Compute::Openvz::Server
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
Loading…
Add table
Reference in a new issue