mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
3571898d56
@ -219,13 +219,6 @@ protected:
|
||||
* @return string for logging
|
||||
*/
|
||||
string allocate_error (PoolObjectSQL::ObjectType obj, const string& error);
|
||||
|
||||
/**
|
||||
* Logs allocate errors
|
||||
* @param message with the allocate error details (parsing)
|
||||
* @return string for logging
|
||||
*/
|
||||
string allocate_error (char *error);
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
@ -52,7 +52,7 @@ protected:
|
||||
string& error_str) = 0;
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
string leases_error (char * error);
|
||||
string leases_error (const string& error);
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
@ -70,7 +70,7 @@ public:
|
||||
* Parse a string representing the template, each attribute is inserted
|
||||
* in the template class.
|
||||
* @param parse_str string with template attributes
|
||||
* @param error_msg error string, must be freed by the calling funtion.
|
||||
* @param error_msg error string, must be freed by the calling function.
|
||||
* This string is null if no error occurred.
|
||||
* @return 0 on success.
|
||||
*/
|
||||
@ -79,12 +79,23 @@ public:
|
||||
/**
|
||||
* Parse a template file.
|
||||
* @param filename of the template file
|
||||
* @param error_msg error string, must be freed by the calling funtion.
|
||||
* @param error_msg error string, must be freed by the calling function.
|
||||
* This string is null if no error occurred.
|
||||
* @return 0 on success.
|
||||
*/
|
||||
int parse(const char * filename, char **error_msg);
|
||||
|
||||
/**
|
||||
* Parse a string representing the template, automatically detecting if
|
||||
* it is the default syntax, or an XML template. Each attribute is inserted
|
||||
* in the template class.
|
||||
* @param parse_str string with template attributes, or XML template
|
||||
* @param error_msg error string, must be freed by the calling function.
|
||||
* This string is null if no error occurred.
|
||||
* @return 0 on success.
|
||||
*/
|
||||
int parse_str_or_xml(const string &parse_str, string& error_msg);
|
||||
|
||||
/**
|
||||
* Marshall a template. This function generates a single string with the
|
||||
* template attributes ("VAR=VAL<delim>...").
|
||||
|
@ -818,6 +818,7 @@ ONEDB_MIGRATOR_FILES="src/onedb/2.0_to_2.9.80.rb \
|
||||
src/onedb/3.1.0_to_3.1.80.rb \
|
||||
src/onedb/3.1.80_to_3.2.0.rb \
|
||||
src/onedb/3.2.0_to_3.2.1.rb \
|
||||
src/onedb/3.2.1_to_3.3.0.rb \
|
||||
src/onedb/onedb.rb \
|
||||
src/onedb/onedb_backend.rb"
|
||||
|
||||
|
@ -7,19 +7,12 @@ DEFAULT=%w{optional sunstone quota cloud ozones_server acct auth_ldap}
|
||||
if defined?(RUBY_VERSION) && RUBY_VERSION>="1.8.7"
|
||||
SQLITE='sqlite3'
|
||||
|
||||
# xmlparser gem is not compatible with ruby 1.9
|
||||
OPTIONAL=%w{nokogiri}
|
||||
|
||||
if RUBY_VERSION=='1.8.7'
|
||||
OPTIONAL << 'xmlparser'
|
||||
end
|
||||
else
|
||||
SQLITE='sqlite3-ruby --version 1.2.0'
|
||||
OPTIONAL=%w{nokogiri xmlparser}
|
||||
end
|
||||
|
||||
GROUPS={
|
||||
:optional => OPTIONAL,
|
||||
:optional => ['nokogiri'],
|
||||
:quota => [SQLITE, 'sequel'],
|
||||
:sunstone => ['json', 'rack', 'sinatra', 'thin', 'sequel', SQLITE],
|
||||
:cloud => %w{amazon-ec2 rack sinatra thin uuidtools curb json},
|
||||
|
@ -22,8 +22,8 @@
|
||||
# a given metric.
|
||||
#-------------------------------------------------------------------------------
|
||||
:defaults:
|
||||
:cpu:
|
||||
:memory:
|
||||
:num_vms:
|
||||
:storage:
|
||||
:CPU:
|
||||
:MEMORY:
|
||||
:NUM_VMS:
|
||||
:STORAGE:
|
||||
|
||||
|
@ -364,6 +364,15 @@ EOT
|
||||
end
|
||||
end
|
||||
|
||||
def OpenNebulaHelper.period_to_str(time)
|
||||
seconds=time.to_i
|
||||
minutes, seconds=seconds.divmod(60)
|
||||
hours, minutes=minutes.divmod(60)
|
||||
days, hours=hours.divmod(24)
|
||||
|
||||
"%4dd %02d:%02d:%02d" % [days, hours, minutes, seconds]
|
||||
end
|
||||
|
||||
BinarySufix = ["K", "M", "G", "T" ]
|
||||
|
||||
def OpenNebulaHelper.unit_to_str(value, options, unit="K")
|
||||
|
@ -66,18 +66,18 @@ class AcctHelper
|
||||
|
||||
column :NETRX, "Group of the User", :right, :size=>10 do |d|
|
||||
OpenNebulaHelper.unit_to_str(
|
||||
d[:network][:net_rx]/1024.0,
|
||||
d[:network][:net_rx].to_i/1024.0,
|
||||
{})
|
||||
end
|
||||
|
||||
column :NETTX, "Group of the User", :right, :size=>10 do |d|
|
||||
OpenNebulaHelper.unit_to_str(
|
||||
d[:network][:net_tx]/1024.0,
|
||||
d[:network][:net_tx].to_i/1024.0,
|
||||
{})
|
||||
end
|
||||
|
||||
column :TIME, "Group of the User", :right, :size=>15 do |d|
|
||||
OpenNebulaHelper.time_to_str(d[:time])
|
||||
OpenNebulaHelper.period_to_str(d[:time])
|
||||
end
|
||||
|
||||
default :VMID, :MEMORY, :CPU, :NETRX, :NETTX, :TIME
|
||||
|
@ -61,7 +61,7 @@ module CloudClient
|
||||
# #########################################################################
|
||||
def self.get_one_auth
|
||||
if ENV["ONE_AUTH"] and !ENV["ONE_AUTH"].empty? and
|
||||
File.file?(ENV["ONE_AUTH"])
|
||||
File.file?(ENV["ONE_AUTH"])
|
||||
one_auth=File.read(ENV["ONE_AUTH"]).strip.split(':')
|
||||
elsif File.file?(DEFAULT_AUTH_FILE)
|
||||
one_auth=File.read(DEFAULT_AUTH_FILE).strip.split(':')
|
||||
@ -91,24 +91,30 @@ module CloudClient
|
||||
end
|
||||
|
||||
begin
|
||||
http.start do |connection|
|
||||
res = http.start do |connection|
|
||||
block.call(connection)
|
||||
end
|
||||
rescue Errno::ECONNREFUSED => e
|
||||
str = "Error connecting to server (#{e.to_s}).\n"
|
||||
str << "Server: #{url.host}:#{url.port}"
|
||||
|
||||
return CloudClient::Error.new(str)
|
||||
return CloudClient::Error.new(str,"503")
|
||||
rescue Errno::ETIMEDOUT => e
|
||||
str = "Error timeout connecting to server (#{e.to_s}).\n"
|
||||
str << "Server: #{url.host}:#{url.port}"
|
||||
|
||||
return CloudClient::Error.new(str)
|
||||
return CloudClient::Error.new(str,"504")
|
||||
rescue Timeout::Error => e
|
||||
str = "Error timeout while connected to server (#{e.to_s}).\n"
|
||||
str << "Server: #{url.host}:#{url.port}"
|
||||
|
||||
return CloudClient::Error.new(str)
|
||||
return CloudClient::Error.new(str,"504")
|
||||
end
|
||||
|
||||
if res.is_a?(Net::HTTPSuccess)
|
||||
res
|
||||
else
|
||||
CloudClient::Error.new(res.body, res.code)
|
||||
end
|
||||
end
|
||||
|
||||
@ -118,10 +124,12 @@ module CloudClient
|
||||
# #########################################################################
|
||||
class Error
|
||||
attr_reader :message
|
||||
attr_reader :code
|
||||
|
||||
# +message+ a description of the error
|
||||
def initialize(message=nil)
|
||||
def initialize(message=nil, code="500")
|
||||
@message=message
|
||||
@code=code
|
||||
end
|
||||
|
||||
def to_s()
|
||||
@ -140,7 +148,7 @@ end
|
||||
|
||||
# Command line help functions
|
||||
module CloudCLI
|
||||
def print_xml(xml_text)
|
||||
def print_xml(xml_text)
|
||||
begin
|
||||
doc = REXML::Document.new(xml_text)
|
||||
rescue REXML::ParseException => e
|
||||
|
@ -70,6 +70,7 @@ module EC2QueryClient
|
||||
end
|
||||
|
||||
@uri = URI.parse(endpoint)
|
||||
path = @uri.path.empty? ? '/' : @uri.path
|
||||
|
||||
@ec2_connection = AWS::EC2::Base.new(
|
||||
:access_key_id => @access_key_id,
|
||||
@ -77,7 +78,7 @@ module EC2QueryClient
|
||||
:server => @uri.host,
|
||||
:port => @uri.port,
|
||||
:use_ssl => @uri.scheme == 'https',
|
||||
:path => @uri.path)
|
||||
:path => path)
|
||||
end
|
||||
|
||||
|
||||
|
@ -54,8 +54,8 @@ class EC2QueryServer < CloudServer
|
||||
'save' => :pending,
|
||||
'epil' => :shutdown,
|
||||
'shut' => :shutdown,
|
||||
'clea' => :shutdown,
|
||||
'fail' => :terminated,
|
||||
'dele' => :terminated,
|
||||
'unkn' => :terminated
|
||||
}
|
||||
|
||||
@ -211,7 +211,8 @@ class EC2QueryServer < CloudServer
|
||||
# Helper functions
|
||||
###########################################################################
|
||||
def render_state(vm)
|
||||
ec2_state = EC2_STATES[ONE_STATES[vm.status]]
|
||||
one_state = ONE_STATES[vm.status]
|
||||
ec2_state = EC2_STATES[one_state||:pending]
|
||||
|
||||
return "<code>#{ec2_state[:code]}</code>
|
||||
<name>#{ec2_state[:name]}</name>"
|
||||
|
@ -12,7 +12,6 @@
|
||||
</groupSet>
|
||||
<instancesSet>
|
||||
<% vmpool.each do |vm| %>
|
||||
<% vm.info %>
|
||||
<item>
|
||||
<instanceId>i-<%= vm.id %></instanceId>
|
||||
<imageId><%= vm['TEMPLATE/IMAGE_ID'] %></imageId>
|
||||
|
@ -23,6 +23,8 @@ class ImageOCCI < Image
|
||||
<STORAGE href="<%= base_url %>/storage/<%= self.id.to_s %>">
|
||||
<ID><%= self.id.to_s %></ID>
|
||||
<NAME><%= self.name %></NAME>
|
||||
<USER href="<%= base_url %>/user/<%= self['UID'] %>" name="<%= self['UNAME'] %>"/>
|
||||
<GROUP><%= self['GNAME'] %></GROUP>
|
||||
<STATE><%= self.state_str %></STATE>
|
||||
<% if self['TYPE'] != nil %>
|
||||
<TYPE><%= self.type_str %></TYPE>
|
||||
|
@ -14,20 +14,21 @@
|
||||
# limitations under the License. #
|
||||
#--------------------------------------------------------------------------- #
|
||||
|
||||
require 'OpenNebula'
|
||||
|
||||
include OpenNebula
|
||||
require 'ImageOCCI'
|
||||
|
||||
class ImagePoolOCCI < ImagePool
|
||||
OCCI_IMAGE_POOL = %q{
|
||||
<STORAGE_COLLECTION>
|
||||
<% self.each{ |im| %>
|
||||
<% if verbose %>
|
||||
<%= im.to_occi(base_url) %>
|
||||
<% else %>
|
||||
<STORAGE href="<%= base_url %>/storage/<%= im.id.to_s %>" name="<%= im.name %>"/>
|
||||
<% end %>
|
||||
<% } %>
|
||||
</STORAGE_COLLECTION>
|
||||
}
|
||||
|
||||
|
||||
# Creates the OCCI representation of a Virtual Machine Pool
|
||||
def to_occi(base_url)
|
||||
begin
|
||||
@ -40,5 +41,9 @@ class ImagePoolOCCI < ImagePool
|
||||
|
||||
return occi_text.gsub(/\n\s*/,'')
|
||||
end
|
||||
|
||||
def factory(element_xml)
|
||||
ImageOCCI.new(element_xml,@client)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -30,11 +30,13 @@ module OCCIClient
|
||||
#####################################################################
|
||||
class Client
|
||||
|
||||
attr_accessor :endpoint
|
||||
|
||||
######################################################################
|
||||
# Initialize client library
|
||||
######################################################################
|
||||
def initialize(endpoint_str=nil, user=nil, pass=nil,
|
||||
timeout=nil, debug_flag=true)
|
||||
timeout=nil, debug_flag=true)
|
||||
@debug = debug_flag
|
||||
@timeout = timeout
|
||||
|
||||
@ -65,50 +67,30 @@ module OCCIClient
|
||||
# Pool Resource Request Methods #
|
||||
#################################
|
||||
|
||||
def get_root
|
||||
get('/')
|
||||
end
|
||||
|
||||
######################################################################
|
||||
# Retieves the available Instance types
|
||||
######################################################################
|
||||
def get_instance_types
|
||||
get('/instance_type')
|
||||
end
|
||||
|
||||
######################################################################
|
||||
# Post a new VM to the VM Pool
|
||||
# :instance_type
|
||||
# :xmlfile
|
||||
######################################################################
|
||||
def post_vms(xmlfile)
|
||||
xml=File.read(xmlfile)
|
||||
|
||||
url = URI.parse(@endpoint+"/compute")
|
||||
|
||||
req = Net::HTTP::Post.new(url.path)
|
||||
req.body=xml
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) do |http|
|
||||
http.request(req)
|
||||
end
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
post('/compute', xmlfile)
|
||||
end
|
||||
|
||||
######################################################################
|
||||
# Retieves the pool of Virtual Machines
|
||||
######################################################################
|
||||
def get_vms
|
||||
url = URI.parse(@endpoint+"/compute")
|
||||
req = Net::HTTP::Get.new(url.path)
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) {|http|
|
||||
http.request(req)
|
||||
}
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
get('/compute')
|
||||
end
|
||||
|
||||
######################################################################
|
||||
@ -116,44 +98,14 @@ module OCCIClient
|
||||
# :xmlfile xml description of the Virtual Network
|
||||
######################################################################
|
||||
def post_network(xmlfile)
|
||||
xml=File.read(xmlfile)
|
||||
|
||||
url = URI.parse(@endpoint+"/network")
|
||||
|
||||
req = Net::HTTP::Post.new(url.path)
|
||||
req.body=xml
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) do |http|
|
||||
http.request(req)
|
||||
end
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
post('/network', xmlfile)
|
||||
end
|
||||
|
||||
######################################################################
|
||||
# Retieves the pool of Virtual Networks
|
||||
######################################################################
|
||||
def get_networks
|
||||
url = URI.parse(@endpoint+"/network")
|
||||
req = Net::HTTP::Get.new(url.path)
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) {|http|
|
||||
http.request(req)
|
||||
}
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
get('/network')
|
||||
end
|
||||
|
||||
######################################################################
|
||||
@ -251,22 +203,10 @@ module OCCIClient
|
||||
# Retieves the pool of Images owned by the user
|
||||
######################################################################
|
||||
def get_images
|
||||
url = URI.parse(@endpoint+"/storage")
|
||||
req = Net::HTTP::Get.new(url.path)
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) {|http|
|
||||
http.request(req)
|
||||
}
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
get('/storage')
|
||||
end
|
||||
|
||||
|
||||
####################################
|
||||
# Entity Resource Request Methods #
|
||||
####################################
|
||||
@ -275,20 +215,7 @@ module OCCIClient
|
||||
# :id VM identifier
|
||||
######################################################################
|
||||
def get_vm(id)
|
||||
url = URI.parse(@endpoint+"/compute/" + id.to_s)
|
||||
req = Net::HTTP::Get.new(url.path)
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) {|http|
|
||||
http.request(req)
|
||||
}
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
get('/compute/'+id.to_s)
|
||||
end
|
||||
|
||||
######################################################################
|
||||
@ -296,57 +223,14 @@ module OCCIClient
|
||||
# :xmlfile Compute OCCI xml representation
|
||||
######################################################################
|
||||
def put_vm(xmlfile)
|
||||
xml = File.read(xmlfile)
|
||||
|
||||
begin
|
||||
vm_info = REXML::Document.new(xml).root
|
||||
rescue Exception => e
|
||||
error = CloudClient::Error.new(e.message)
|
||||
return error
|
||||
end
|
||||
|
||||
if vm_info.elements['ID'] == nil
|
||||
return CloudClient::Error.new("Can not find COMPUTE_ID")
|
||||
end
|
||||
|
||||
vm_id = vm_info.elements['ID'].text
|
||||
|
||||
url = URI.parse(@endpoint+'/compute/' + vm_id)
|
||||
|
||||
req = Net::HTTP::Put.new(url.path)
|
||||
req.body = xml
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) do |http|
|
||||
http.request(req)
|
||||
end
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
put('/compute/', xmlfile)
|
||||
end
|
||||
|
||||
####################################################################
|
||||
# :id Compute identifier
|
||||
####################################################################
|
||||
def delete_vm(id)
|
||||
url = URI.parse(@endpoint+"/compute/" + id.to_s)
|
||||
req = Net::HTTP::Delete.new(url.path)
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) {|http|
|
||||
http.request(req)
|
||||
}
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
delete('/compute/'+id.to_s)
|
||||
end
|
||||
|
||||
######################################################################
|
||||
@ -354,20 +238,7 @@ module OCCIClient
|
||||
# :id Virtual Network identifier
|
||||
######################################################################
|
||||
def get_network(id)
|
||||
url = URI.parse(@endpoint+"/network/" + id.to_s)
|
||||
req = Net::HTTP::Get.new(url.path)
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) {|http|
|
||||
http.request(req)
|
||||
}
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
get('/network/'+id.to_s)
|
||||
end
|
||||
|
||||
######################################################################
|
||||
@ -375,78 +246,22 @@ module OCCIClient
|
||||
# :xmlfile Network OCCI xml representation
|
||||
######################################################################
|
||||
def put_network(xmlfile)
|
||||
xml = File.read(xmlfile)
|
||||
|
||||
begin
|
||||
vnet_info = REXML::Document.new(xml).root
|
||||
rescue Exception => e
|
||||
error = CloudClient::Error.new(e.message)
|
||||
return error
|
||||
end
|
||||
|
||||
if vnet_info.elements['ID'] == nil
|
||||
return CloudClient::Error.new("Can not find NETWORK_ID")
|
||||
end
|
||||
|
||||
vnet_id = vnet_info.elements['ID'].text
|
||||
|
||||
url = URI.parse(@endpoint+'/network/' + vnet_id)
|
||||
|
||||
req = Net::HTTP::Put.new(url.path)
|
||||
req.body = xml
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) do |http|
|
||||
http.request(req)
|
||||
end
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
put('/network/', xmlfile)
|
||||
end
|
||||
|
||||
######################################################################
|
||||
# :id VM identifier
|
||||
######################################################################
|
||||
def delete_network(id)
|
||||
url = URI.parse(@endpoint+"/network/" + id.to_s)
|
||||
req = Net::HTTP::Delete.new(url.path)
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) {|http|
|
||||
http.request(req)
|
||||
}
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
delete('/network/'+id.to_s)
|
||||
end
|
||||
|
||||
#######################################################################
|
||||
#######################################################################
|
||||
# Retieves an Image
|
||||
# :image_uuid Image identifier
|
||||
######################################################################
|
||||
def get_image(image_uuid)
|
||||
url = URI.parse(@endpoint+"/storage/"+image_uuid)
|
||||
req = Net::HTTP::Get.new(url.path)
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) {|http|
|
||||
http.request(req)
|
||||
}
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
def get_image(id)
|
||||
get('/storage/'+id.to_s)
|
||||
end
|
||||
|
||||
######################################################################
|
||||
@ -454,26 +269,67 @@ module OCCIClient
|
||||
# :xmlfile Storage OCCI xml representation
|
||||
######################################################################
|
||||
def put_image(xmlfile)
|
||||
xml = File.read(xmlfile)
|
||||
|
||||
put('/storage/', xmlfile)
|
||||
end
|
||||
|
||||
######################################################################
|
||||
# :id VM identifier
|
||||
######################################################################
|
||||
def delete_image(id)
|
||||
delete('/storage/'+id.to_s)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get(path)
|
||||
url = URI.parse(@endpoint+path)
|
||||
req = Net::HTTP::Get.new(url.path)
|
||||
|
||||
do_request(url, req)
|
||||
end
|
||||
|
||||
def post(path, xmlfile)
|
||||
url = URI.parse(@endpoint+path)
|
||||
req = Net::HTTP::Post.new(url.path)
|
||||
|
||||
req.body=File.read(xmlfile)
|
||||
|
||||
do_request(url, req)
|
||||
end
|
||||
|
||||
def delete(path, xmlfile)
|
||||
url = URI.parse(@endpoint+path)
|
||||
req = Net::HTTP::Delete.new(url.path)
|
||||
|
||||
do_request(url, req)
|
||||
end
|
||||
|
||||
def put(path, xmlfile)
|
||||
xml = File.read(xmlfile)
|
||||
|
||||
# Get ID from XML
|
||||
begin
|
||||
image_info = REXML::Document.new(xml).root
|
||||
info = REXML::Document.new(xml).root
|
||||
rescue Exception => e
|
||||
error = OpenNebula::Error.new(e.message)
|
||||
error = CloudClient::Error.new(e.message)
|
||||
return error
|
||||
end
|
||||
|
||||
if image_info.elements['ID'] == nil
|
||||
return CloudClient::Error.new("Can not find STORAGE_ID")
|
||||
|
||||
if info.elements['ID'] == nil
|
||||
return CloudClient::Error.new("Can not find RESOURCE ID")
|
||||
end
|
||||
|
||||
image_id = image_info.elements['ID'].text
|
||||
|
||||
url = URI.parse(@endpoint+'/storage/' + image_id)
|
||||
resource_id = info.elements['ID'].text
|
||||
|
||||
url = URI.parse(@endpoint+path + resource_id)
|
||||
req = Net::HTTP::Put.new(url.path)
|
||||
|
||||
req.body = xml
|
||||
|
||||
do_request(url, req)
|
||||
end
|
||||
|
||||
def do_request(url, req)
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) do |http|
|
||||
@ -486,25 +342,5 @@ module OCCIClient
|
||||
return res.body
|
||||
end
|
||||
end
|
||||
|
||||
######################################################################
|
||||
# :id VM identifier
|
||||
######################################################################
|
||||
def delete_image(id)
|
||||
url = URI.parse(@endpoint+"/storage/" + id.to_s)
|
||||
req = Net::HTTP::Delete.new(url.path)
|
||||
|
||||
req.basic_auth @occiauth[0], @occiauth[1]
|
||||
|
||||
res = CloudClient::http_start(url, @timeout) {|http|
|
||||
http.request(req)
|
||||
}
|
||||
|
||||
if CloudClient::is_error?(res)
|
||||
return res
|
||||
else
|
||||
return res.body
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -64,11 +64,11 @@ class OCCIServer < CloudServer
|
||||
# Prepare the OCCI XML Response
|
||||
# resource:: _Pool_ or _PoolElement_ that represents a OCCI resource
|
||||
# [return] _String_,_Integer_ Resource Representation or error, status code
|
||||
def to_occi_xml(resource, code)
|
||||
xml_response = resource.to_occi(@base_url)
|
||||
def to_occi_xml(resource, opts)
|
||||
xml_response = resource.to_occi(@base_url, opts[:verbose])
|
||||
return xml_response, 500 if OpenNebula.is_error?(xml_response)
|
||||
|
||||
return xml_response, code
|
||||
return xml_response, opts[:code]
|
||||
end
|
||||
|
||||
############################################################################
|
||||
@ -124,7 +124,7 @@ class OCCIServer < CloudServer
|
||||
return rc, CloudServer::HTTP_ERROR_CODE[rc.errno]
|
||||
end
|
||||
|
||||
return to_occi_xml(vmpool, 200)
|
||||
return to_occi_xml(vmpool, :status=>200, :verbose=>request.params['verbose'])
|
||||
end
|
||||
|
||||
|
||||
@ -144,7 +144,7 @@ class OCCIServer < CloudServer
|
||||
return rc, CloudServer::HTTP_ERROR_CODE[rc.errno]
|
||||
end
|
||||
|
||||
return to_occi_xml(network_pool, 200)
|
||||
return to_occi_xml(network_pool, :status=>200, :verbose=>request.params['verbose'])
|
||||
end
|
||||
|
||||
# Gets the pool representation of STORAGES
|
||||
@ -163,7 +163,7 @@ class OCCIServer < CloudServer
|
||||
return rc, CloudServer::HTTP_ERROR_CODE[rc.errno]
|
||||
end
|
||||
|
||||
return to_occi_xml(image_pool, 200)
|
||||
return to_occi_xml(image_pool, :status=>200, :verbose=>request.params['verbose'])
|
||||
end
|
||||
|
||||
# Gets the pool representation of USERs
|
||||
@ -180,7 +180,7 @@ class OCCIServer < CloudServer
|
||||
return rc, CloudServer::HTTP_ERROR_CODE[rc.errno]
|
||||
end
|
||||
|
||||
return to_occi_xml(user_pool, 200)
|
||||
return to_occi_xml(user_pool, :status=>200, :verbose=>request.params['verbose'])
|
||||
end
|
||||
|
||||
############################################################################
|
||||
@ -216,7 +216,7 @@ class OCCIServer < CloudServer
|
||||
|
||||
# --- Prepare XML Response ---
|
||||
vm.info
|
||||
return to_occi_xml(vm, 201)
|
||||
return to_occi_xml(vm, :status=>201)
|
||||
end
|
||||
|
||||
# Get the representation of a COMPUTE resource
|
||||
@ -235,7 +235,7 @@ class OCCIServer < CloudServer
|
||||
return rc, CloudServer::HTTP_ERROR_CODE[rc.errno]
|
||||
end
|
||||
|
||||
return to_occi_xml(vm, 200)
|
||||
return to_occi_xml(vm, :status=>200)
|
||||
end
|
||||
|
||||
# Deletes a COMPUTE resource
|
||||
@ -278,7 +278,7 @@ class OCCIServer < CloudServer
|
||||
return result, code
|
||||
else
|
||||
vm.info
|
||||
return to_occi_xml(vm, code)
|
||||
return to_occi_xml(vm, :status=>code)
|
||||
end
|
||||
end
|
||||
|
||||
@ -308,7 +308,7 @@ class OCCIServer < CloudServer
|
||||
|
||||
# --- Prepare XML Response ---
|
||||
network.info
|
||||
return to_occi_xml(network, 201)
|
||||
return to_occi_xml(network, :status=>201)
|
||||
end
|
||||
|
||||
# Retrieves a NETWORK resource
|
||||
@ -326,7 +326,7 @@ class OCCIServer < CloudServer
|
||||
return rc, CloudServer::HTTP_ERROR_CODE[rc.errno]
|
||||
end
|
||||
|
||||
return to_occi_xml(network, 200)
|
||||
return to_occi_xml(network, :status=>200)
|
||||
end
|
||||
|
||||
# Deletes a NETWORK resource
|
||||
@ -372,7 +372,7 @@ class OCCIServer < CloudServer
|
||||
|
||||
# --- Prepare XML Response ---
|
||||
vnet.info
|
||||
return to_occi_xml(vnet, 202)
|
||||
return to_occi_xml(vnet, :status=>202)
|
||||
end
|
||||
|
||||
############################################################################
|
||||
@ -412,7 +412,7 @@ class OCCIServer < CloudServer
|
||||
|
||||
# --- Prepare XML Response ---
|
||||
image.info
|
||||
return to_occi_xml(image, 201)
|
||||
return to_occi_xml(image, :status=>201)
|
||||
end
|
||||
|
||||
# Get a STORAGE resource
|
||||
@ -431,7 +431,7 @@ class OCCIServer < CloudServer
|
||||
end
|
||||
|
||||
# --- Prepare XML Response ---
|
||||
return to_occi_xml(image, 200)
|
||||
return to_occi_xml(image, :status=>200)
|
||||
end
|
||||
|
||||
# Deletes a STORAGE resource (Not yet implemented)
|
||||
@ -485,7 +485,7 @@ class OCCIServer < CloudServer
|
||||
|
||||
# --- Prepare XML Response ---
|
||||
image.info
|
||||
return to_occi_xml(image, 202)
|
||||
return to_occi_xml(image, :status=>202)
|
||||
end
|
||||
|
||||
# Get the representation of a USER
|
||||
@ -504,7 +504,7 @@ class OCCIServer < CloudServer
|
||||
return rc, CloudServer::HTTP_ERROR_CODE[rc.errno]
|
||||
end
|
||||
|
||||
return to_occi_xml(user, 200)
|
||||
return to_occi_xml(user, :status=>200)
|
||||
end
|
||||
|
||||
############################################################################
|
||||
|
@ -27,6 +27,7 @@ class UserOCCI < User
|
||||
<USER href="<%= base_url %>/user/<%= self.id.to_s %>">
|
||||
<ID><%= self.id.to_s %></ID>
|
||||
<NAME><%= self.name %></NAME>
|
||||
<GROUP><%= self['GNAME'] %></GROUP>
|
||||
<QUOTA>
|
||||
<% user_quota.each { |key,value|
|
||||
key_s = key.to_s.upcase
|
||||
|
@ -14,20 +14,21 @@
|
||||
# limitations under the License. #
|
||||
#--------------------------------------------------------------------------- #
|
||||
|
||||
require 'OpenNebula'
|
||||
|
||||
include OpenNebula
|
||||
require 'UserOCCI'
|
||||
|
||||
class UserPoolOCCI < UserPool
|
||||
OCCI_USER_POOL = %q{
|
||||
<USER_COLLECTION>
|
||||
<% self.each{ |user| %>
|
||||
<% if verbose %>
|
||||
<%= user.to_occi(base_url) %>
|
||||
<% else %>
|
||||
<USER href="<%= base_url %>/user/<%= user.id.to_s %>" name="<%= user.name %>"/>
|
||||
<% end %>
|
||||
<% } %>
|
||||
</USER_COLLECTION>
|
||||
}
|
||||
|
||||
|
||||
# Creates the OCCI representation of a User Pool
|
||||
def to_occi(base_url)
|
||||
begin
|
||||
@ -40,4 +41,8 @@ class UserPoolOCCI < UserPool
|
||||
|
||||
return occi_text.gsub(/\n\s*/,'')
|
||||
end
|
||||
|
||||
def factory(element_xml)
|
||||
UserOCCI.new(element_xml,@client)
|
||||
end
|
||||
end
|
@ -22,6 +22,8 @@ class VirtualMachineOCCI < VirtualMachine
|
||||
OCCI_VM = %q{
|
||||
<COMPUTE href="<%= base_url %>/compute/<%= self.id.to_s %>">
|
||||
<ID><%= self.id.to_s%></ID>
|
||||
<USER href="<%= base_url %>/user/<%= self['UID'] %>" name="<%= self['UNAME'] %>"/>
|
||||
<GROUP><%= self['GNAME'] %></GROUP>
|
||||
<CPU><%= self['TEMPLATE/CPU'] %></CPU>
|
||||
<MEMORY><%= self['TEMPLATE/MEMORY'] %></MEMORY>
|
||||
<NAME><%= self.name%></NAME>
|
||||
|
@ -14,22 +14,24 @@
|
||||
# limitations under the License. #
|
||||
#--------------------------------------------------------------------------- #
|
||||
|
||||
require 'OpenNebula'
|
||||
|
||||
include OpenNebula
|
||||
require 'VirtualMachineOCCI'
|
||||
|
||||
class VirtualMachinePoolOCCI < VirtualMachinePool
|
||||
OCCI_VM_POOL = %q{
|
||||
<COMPUTE_COLLECTION>
|
||||
<% self.each{ |vm| %>
|
||||
<% self.each{ |vm| %>
|
||||
<% if verbose %>
|
||||
<%= vm.to_occi(base_url) %>
|
||||
<% else %>
|
||||
<COMPUTE href="<%= base_url %>/compute/<%= vm.id.to_s %>" name="<%= vm.name %>"/>
|
||||
<% end %>
|
||||
<% } %>
|
||||
</COMPUTE_COLLECTION>
|
||||
}
|
||||
|
||||
|
||||
# Creates the OCCI representation of a Virtual Machine Pool
|
||||
def to_occi(base_url)
|
||||
def to_occi(base_url, verbose=false)
|
||||
begin
|
||||
occi = ERB.new(OCCI_VM_POOL)
|
||||
occi_text = occi.result(binding)
|
||||
@ -40,5 +42,9 @@ class VirtualMachinePoolOCCI < VirtualMachinePool
|
||||
|
||||
return occi_text.gsub(/\n\s*/,'')
|
||||
end
|
||||
|
||||
def factory(element_xml)
|
||||
VirtualMachineOCCI.new(element_xml,@client)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -24,6 +24,8 @@ class VirtualNetworkOCCI < VirtualNetwork
|
||||
<NETWORK href="<%= base_url %>/network/<%= self.id.to_s %>">
|
||||
<ID><%= self.id.to_s %></ID>
|
||||
<NAME><%= self.name %></NAME>
|
||||
<USER href="<%= base_url %>/user/<%= self['UID'] %>" name="<%= self['UNAME'] %>"/>
|
||||
<GROUP><%= self['GNAME'] %></GROUP>
|
||||
<% if self['TEMPLATE/DESCRIPTION'] != nil %>
|
||||
<DESCRIPTION><%= self['TEMPLATE/DESCRIPTION'] %></DESCRIPTION>
|
||||
<% end %>
|
||||
|
@ -14,15 +14,17 @@
|
||||
# limitations under the License. #
|
||||
#--------------------------------------------------------------------------- #
|
||||
|
||||
require 'OpenNebula'
|
||||
|
||||
include OpenNebula
|
||||
require 'VirtualNetworkOCCI'
|
||||
|
||||
class VirtualNetworkPoolOCCI < VirtualNetworkPool
|
||||
OCCI_NETWORK_POOL = %q{
|
||||
<NETWORK_COLLECTION>
|
||||
<% self.each{ |vn| %>
|
||||
<% self.each{ |vn| %>
|
||||
<% if verbose %>
|
||||
<%= vn.to_occi(base_url) %>
|
||||
<% else %>
|
||||
<NETWORK href="<%= base_url %>/network/<%= vn.id.to_s %>" name="<%= vn.name %>"/>
|
||||
<% end %>
|
||||
<% } %>
|
||||
</NETWORK_COLLECTION>
|
||||
}
|
||||
@ -39,4 +41,8 @@ class VirtualNetworkPoolOCCI < VirtualNetworkPool
|
||||
|
||||
return occi_text.gsub(/\n\s*/,'')
|
||||
end
|
||||
|
||||
def factory(element_xml)
|
||||
VirtualNetworkOCCI.new(element_xml,@client)
|
||||
end
|
||||
end
|
@ -65,7 +65,7 @@ echo "$IMAGE_REPOSITORY_PATH/`echo $CANONICAL_MD5 | cut -d ' ' -f1`"
|
||||
|
||||
function fs_du {
|
||||
if [ -d "$1" ]; then
|
||||
SIZE=`du -s "$1" | cut -f1`
|
||||
SIZE=`du -sb "$1" | cut -f1`
|
||||
error=$?
|
||||
else
|
||||
SIZE=`stat -c %s "$1"`
|
||||
|
@ -330,7 +330,7 @@ void LifeCycleManager::deploy_failure_action(int vid)
|
||||
|
||||
vmpool->update_history(vm);
|
||||
|
||||
vm->log("LCM", Log::INFO, "Fail to life migrate VM."
|
||||
vm->log("LCM", Log::INFO, "Fail to live migrate VM."
|
||||
" Assuming that the VM is still RUNNING (will poll VM).");
|
||||
|
||||
//----------------------------------------------------
|
||||
|
@ -162,12 +162,19 @@ module Migrator
|
||||
@db.run "CREATE TABLE IF NOT EXISTS user_pool (oid INTEGER PRIMARY KEY, name VARCHAR(128), body TEXT, uid INTEGER, gid INTEGER, owner_u INTEGER, group_u INTEGER, other_u INTEGER, UNIQUE(name));"
|
||||
|
||||
@db.fetch("SELECT * FROM old_user_pool") do |row|
|
||||
doc = Document.new(row[:body])
|
||||
|
||||
gid = "1"
|
||||
doc.root.each_element("GID") { |e|
|
||||
gid = e.text
|
||||
}
|
||||
|
||||
@db[:user_pool].insert(
|
||||
:oid => row[:oid],
|
||||
:name => row[:name],
|
||||
:body => row[:body],
|
||||
:uid => "0",
|
||||
:gid => row[:oid],
|
||||
:uid => row[:oid],
|
||||
:gid => gid,
|
||||
:owner_u => "1",
|
||||
:group_u => "0",
|
||||
:other_u => "0")
|
||||
|
28
src/onedb/3.2.1_to_3.3.0.rb
Normal file
28
src/onedb/3.2.1_to_3.3.0.rb
Normal file
@ -0,0 +1,28 @@
|
||||
# -------------------------------------------------------------------------- *
|
||||
# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) #
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
# not use this file except in compliance with the License. You may obtain *
|
||||
# a copy of the License at *
|
||||
# *
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
# *
|
||||
# Unless required by applicable law or agreed to in writing, software *
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
# See the License for the specific language governing permissions and *
|
||||
# limitations under the License. *
|
||||
# -------------------------------------------------------------------------- *
|
||||
|
||||
module Migrator
|
||||
def db_version
|
||||
"3.3.0"
|
||||
end
|
||||
|
||||
def one_version
|
||||
"OpenNebula 3.3.0"
|
||||
end
|
||||
|
||||
def up
|
||||
return true
|
||||
end
|
||||
end
|
@ -169,7 +169,6 @@ void PoolObjectSQL::set_template_error_message(const string& message)
|
||||
int PoolObjectSQL::replace_template(const string& tmpl_str, string& error)
|
||||
{
|
||||
Template * new_tmpl = get_new_template();
|
||||
char * error_msg = 0;
|
||||
|
||||
if ( new_tmpl == 0 )
|
||||
{
|
||||
@ -177,21 +176,8 @@ int PoolObjectSQL::replace_template(const string& tmpl_str, string& error)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( new_tmpl->parse(tmpl_str, &error_msg) != 0 )
|
||||
if ( new_tmpl->parse_str_or_xml(tmpl_str, error) != 0 )
|
||||
{
|
||||
ostringstream oss;
|
||||
|
||||
oss << "Parse error";
|
||||
|
||||
if (error_msg != 0)
|
||||
{
|
||||
oss << ": " << error_msg;
|
||||
|
||||
free(error_msg);
|
||||
}
|
||||
|
||||
error = oss.str();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -293,22 +293,3 @@ string Request::allocate_error (const string& error)
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
string Request::allocate_error (char *error)
|
||||
{
|
||||
ostringstream oss;
|
||||
|
||||
oss << "Parse error";
|
||||
|
||||
if ( error != 0 )
|
||||
{
|
||||
oss << ": " << error;
|
||||
free(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
oss << ".";
|
||||
}
|
||||
|
||||
return allocate_error(oss.str());
|
||||
}
|
||||
|
@ -99,16 +99,15 @@ void RequestManagerAllocate::request_execute(xmlrpc_c::paramList const& params,
|
||||
|
||||
if ( do_template == true )
|
||||
{
|
||||
char * error_msg = 0;
|
||||
string str_tmpl = xmlrpc_c::value_string(params.getString(1));
|
||||
|
||||
tmpl = get_object_template();
|
||||
|
||||
rc = tmpl->parse(str_tmpl, &error_msg);
|
||||
rc = tmpl->parse_str_or_xml(str_tmpl, error_str);
|
||||
|
||||
if ( rc != 0 )
|
||||
{
|
||||
failure_response(INTERNAL, allocate_error(error_msg), att);
|
||||
failure_response(INTERNAL, allocate_error(error_str), att);
|
||||
delete tmpl;
|
||||
|
||||
return;
|
||||
|
@ -420,7 +420,6 @@ void VirtualMachineSaveDisk::request_execute(xmlrpc_c::paramList const& paramLis
|
||||
int rc;
|
||||
ostringstream oss;
|
||||
string error_str;
|
||||
char * error_char;
|
||||
|
||||
// ------------------ Template for the new image ------------------
|
||||
|
||||
@ -437,7 +436,7 @@ void VirtualMachineSaveDisk::request_execute(xmlrpc_c::paramList const& paramLis
|
||||
|
||||
itemplate = new ImageTemplate;
|
||||
|
||||
itemplate->parse(oss.str(), &error_char);
|
||||
itemplate->parse_str_or_xml(oss.str(), error_str);
|
||||
|
||||
// ------------------ Authorize the operation ------------------
|
||||
|
||||
|
@ -22,19 +22,9 @@ using namespace std;
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
string RequestManagerVirtualNetwork::leases_error (char *error)
|
||||
string RequestManagerVirtualNetwork::leases_error (const string& error)
|
||||
{
|
||||
ostringstream oss;
|
||||
|
||||
oss << "Parse error.";
|
||||
|
||||
if ( error != 0 )
|
||||
{
|
||||
oss << " " << error;
|
||||
free(error);
|
||||
}
|
||||
|
||||
return request_error("Error modifying network leases",oss.str());
|
||||
return request_error("Error modifying network leases",error);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
@ -50,7 +40,6 @@ void RequestManagerVirtualNetwork::
|
||||
VirtualNetworkTemplate tmpl;
|
||||
VirtualNetwork * vn;
|
||||
|
||||
char * error_msg = 0;
|
||||
string error_str;
|
||||
int rc;
|
||||
|
||||
@ -59,11 +48,11 @@ void RequestManagerVirtualNetwork::
|
||||
return;
|
||||
}
|
||||
|
||||
rc = tmpl.parse(str_tmpl, &error_msg);
|
||||
rc = tmpl.parse_str_or_xml(str_tmpl, error_str);
|
||||
|
||||
if ( rc != 0 )
|
||||
{
|
||||
failure_response(INTERNAL, leases_error(error_msg), att);
|
||||
failure_response(INTERNAL, leases_error(error_str), att);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -135,6 +135,52 @@ error_yy:
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
int Template::parse_str_or_xml(const string &parse_str, string& error_msg)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if ( parse_str[0] == '<' )
|
||||
{
|
||||
rc = from_xml(parse_str);
|
||||
|
||||
if ( rc != 0 )
|
||||
{
|
||||
error_msg = "Parse error: XML Template malformed.";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char * error_char = 0;
|
||||
|
||||
rc = parse(parse_str, &error_char);
|
||||
|
||||
if ( rc != 0 )
|
||||
{
|
||||
ostringstream oss;
|
||||
|
||||
oss << "Parse error";
|
||||
|
||||
if (error_char != 0)
|
||||
{
|
||||
oss << ": " << error_char;
|
||||
|
||||
free(error_char);
|
||||
}
|
||||
else
|
||||
{
|
||||
oss << ".";
|
||||
}
|
||||
|
||||
error_msg = oss.str();
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void Template::marshall(string &str, const char delim)
|
||||
{
|
||||
multimap<string,Attribute *>::iterator it;
|
||||
|
Loading…
x
Reference in New Issue
Block a user