# This file is part of CPEE.
#
# CPEE is free software: you can redistribute it and/or modify it under the terms
# of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
#
# CPEE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# CPEE (file COPYING in the main directory). If not, see
# .
require 'riddl/server'
require 'securerandom'
require 'xml/smart'
require 'base64'
require 'uri'
require 'redis'
require 'json'
require ::File.dirname(__FILE__) + '/value_helper'
module CPEE
module Instantiation
SERVER = File.expand_path(__dir__ + '/../instantiation.xml')
module Helpers #{{{
def load_testset(tdoc,cpee,name=nil,stream=nil) #{{{
ins = -1
uuid = nil
XML::Smart.string(tdoc) do |doc|
doc.register_namespace 'desc', 'http://cpee.org/ns/description/1.0'
doc.register_namespace 'prop', 'http://riddl.org/ns/common-patterns/properties/1.0'
srv = Riddl::Client.new(cpee, cpee + "?riddl-description")
res = srv.resource("/")
if name
doc.find("/testset/attributes/prop:info").each do |e|
e.text = name
end
end
if stream && !stream.empty?
JSON.parse(stream).each do |e|
begin
stream = Typhoeus.get e['url']
if stream.success?
XML::Smart::string(stream.response_body) do |str|
doc.find("//desc:call[@id=\"#{e['id']}\"]/desc:parameters/desc:stream").each do |ele|
ele.replace_by str.root
end
end
end
rescue => e
puts e.message
puts e.backtrace
end
end
end
status, response, headers = res.post Riddl::Parameter::Simple.new("info",doc.find("string(/testset/attributes/prop:info)"))
if status == 200
ins = response.first.value
uuid = headers['CPEE_INSTANCE_UUID']
params = []
res = srv.resource("/#{ins}/properties/values")
["handlerwrapper","positions","dataelements","endpoints","attributes","transformation"].each do |item|
if doc.find("/testset/#{item}").any?
params << Riddl::Parameter::Simple.new("name",item)
params << Riddl::Parameter::Simple.new("content",doc.find("/testset/#{item}").first.dump)
end
end
["description"].each do |item|
if doc.find("/testset/#{item}").any?
params << Riddl::Parameter::Simple.new("name",item)
params << Riddl::Parameter::Simple.new("content","" + doc.find("/testset/#{item}/desc:*").first.dump + "")
end
end
status, response = res.put params
["handlers"].each do |item|
doc.find("/testset/#{item}/handler").each do |han|
#pp han.children.first
url = han.attributes['url']
inp = "url=" + URI.encode_www_form_component(url)
inp = inp + "&topic=" + han.children.first.attributes['topic']
inp = inp + "&" + han.children.first.qname.to_s + "=" + han.children.first.to_s
status,body = Riddl::Client::new(cpee+ins+"/notifications/subscriptions/").post(
[
Riddl::Parameter::Simple.new("url",han.attributes['url']),
Riddl::Parameter::Simple.new("topic",han.children.first.attributes['topic']),
Riddl::Parameter::Simple.new(han.children.first.qname.to_s,han.children.first.to_s)
]
)
end
end
end
end
[ins, uuid]
end #}}}
private :load_testset
def handle_waiting(cpee,instance,uuid,behavior,selfurl,cblist) #{{{
if behavior =~ /^wait/
condition = behavior.match(/_([^_]+)_/)&.[](1) || 'finished'
@headers << Riddl::Header.new('CPEE-CALLBACK','true')
cb = @h['CPEE_CALLBACK']
if cb
cbk = SecureRandom.uuid
srv = Riddl::Client.new(cpee, cpee + "?riddl-description")
status, response = srv.resource("/#{instance}/notifications/subscriptions/").post [
Riddl::Parameter::Simple.new("url",File.join(selfurl,'callback',cbk)),
Riddl::Parameter::Simple.new("topic","state"),
Riddl::Parameter::Simple.new("events","change")
]
cblist.rpush(cbk, cb)
cblist.rpush(cbk, condition)
cblist.rpush(cbk, instance)
cblist.rpush(cbk, uuid)
cblist.rpush(cbk, File.join(cpee,instance))
end
end
end #}}}
private :handle_waiting
def handle_starting(cpee,instance,behavior) #{{{
if behavior =~ /_running$/
srv = Riddl::Client.new(cpee, cpee + "?riddl-description")
res = srv.resource("/#{instance}/properties/values")
status, response = res.put [
Riddl::Parameter::Simple.new('name', 'state'),
Riddl::Parameter::Simple.new('value','running')
]
end
end #}}}
private :handle_starting
def handle_data(cpee,instance,data) #{{{
if data && !data.empty?
content = XML::Smart.string('')
JSON::parse(data).each do |k,v|
content.root.add(k,v)
end
srv = Riddl::Client.new(cpee, cpee + "?riddl-description")
res = srv.resource("/#{instance}/properties/values/dataelements/")
status, response = res.patch [
Riddl::Parameter::Complex.new('content','text/xml',content.to_s)
]
end rescue nil
end #}}}
def handle_endpoints(cpee,instance,data) #{{{
if data && !data.empty?
content = XML::Smart.string('')
JSON::parse(data).each do |k,v|
content.root.add(k,v)
end
srv = Riddl::Client.new(cpee, cpee + "?riddl-description")
res = srv.resource("/#{instance}/properties/values/endpoints/")
status, response = res.patch [
Riddl::Parameter::Complex.new('content','text/xml',content.to_s)
]
end rescue nil
end #}}}
end #}}}
class InstantiateGit < Riddl::Implementation #{{{
include Helpers
def response
cpee = @h['X_CPEE'] || @a[0]
selfurl = @a[1]
cblist = @a[2]
status, res = Riddl::Client.new(File.join(@p[1].value,'raw',@p[2].value,@p[3].value).gsub(/ /,'%20')).get
tdoc = if status >= 200 && status < 300
res[0].value.read
else
(@status = 500) && return
end
stream = @p.find{ |e| e.name == 'stream' }&.value
if (instance, uuid = load_testset(tdoc,cpee,nil,stream)).first == -1
@status = 500
else
handle_data cpee, instance, @p[4]&.value if @p[4]&.name == 'init'
handle_endpoints cpee, instance, @p[4]&.value if @p[4]&.name == 'endpoints'
handle_endpoints cpee, instance, @p[5]&.value if @p[5]&.name == 'endpoints'
handle_waiting cpee, instance, uuid, @p[0].value, selfurl, cblist
handle_starting cpee, instance, @p[0].value
send = {
'CPEE-INSTANCE' => instance,
'CPEE-INSTANCE-URL' => File.join(cpee,instance),
'CPEE-INSTANCE-UUID' => uuid,
'CPEE-BEHAVIOR' => @p[0].value
}
@headers << Riddl::Header.new('CPEE-INSTANTIATION',JSON::generate(send))
Riddl::Parameter::Complex.new('instance','application/json',JSON::generate(send))
end
end
end #}}}
class InstantiateUrl < Riddl::Implementation #{{{
include Helpers
def response
cpee = @h['X_CPEE'] || @a[0]
selfurl = @a[1]
cblist = @a[2]
name = @a[3] ? @p.shift.value : nil
status, res = Riddl::Client.new(@p[1].value.gsub(/ /,'%20')).get
tdoc = if status >= 200 && status < 300
res[0].value.read
else
(@status = 500) && return
end
stream = @p.find{ |e| e.name == 'stream' }&.value
if (instance, uuid = load_testset(tdoc,cpee,name,stream)).first == -1
@status = 500
else
handle_data cpee, instance, @p[2]&.value if @p[2]&.name == 'init'
handle_endpoints cpee, instance, @p[2]&.value if @p[2]&.name == 'endpoints'
handle_endpoints cpee, instance, @p[3]&.value if @p[3]&.name == 'endpoints'
handle_waiting cpee, instance, uuid, @p[0].value, selfurl, cblist
handle_starting cpee, instance, @p[0].value
send = {
'CPEE-INSTANCE' => instance,
'CPEE-INSTANCE-URL' => File.join(cpee,instance),
'CPEE-INSTANCE-UUID' => uuid,
'CPEE-BEHAVIOR' => @p[0].value
}
@headers << Riddl::Header.new('CPEE-INSTANTIATION',JSON::generate(send))
Riddl::Parameter::Complex.new('instance','application/json',JSON::generate(send))
end
end
end #}}}
class InstantiateXML < Riddl::Implementation #{{{
include Helpers
def response
cpee = @h['X_CPEE'] || @a[0]
behavior = @a[1] ? 'fork_ready' : @p[0].value
data = @a[1] ? 0 : 1
selfurl = @a[2]
cblist = @a[3]
tdoc = if @p[data].additional =~ /base64/
Base64.decode64(@p[data].value.read)
else
@p[data].value.read
end
if (instance, uuid = load_testset(tdoc,cpee)).first == -1
@status = 500
else
handle_data cpee, instance, @p[data+1]&.value
handle_waiting cpee, instance, uuid, behavior, selfurl, cblist
handle_starting cpee, instance, behavior
send = {
'CPEE-INSTANCE' => instance,
'CPEE-INSTANCE-URL' => File.join(cpee,instance),
'CPEE-INSTANCE-UUID' => uuid,
'CPEE-BEHAVIOR' => behavior
}
Riddl::Parameter::Complex.new('instance','application/json',JSON::generate(send))
end
end
end #}}}
class HandleInstance < Riddl::Implementation #{{{
include Helpers
def response
cpee = @h['X_CPEE'] || @a[0]
selfurl = @a[1]
cblist = @a[2]
instance = @p[1].value
srv = Riddl::Client.new(cpee, cpee + "?riddl-description")
res = srv.resource("/#{instance}/properties/values/attributes/uuid")
status, response = res.get
if status >= 200 && status < 300
uuid = XML::Smart::string(response.first.value).root.text
handle_data cpee, instance, @p[2]&.value
handle_waiting cpee, instance, uuid, @p[0].value, selfurl, cblist
handle_starting cpee, instance, @p[0].value
return Riddl::Parameter::Simple.new("url",cpee + instance)
end
end
end #}}}
class ContinueTask < Riddl::Implementation #{{{
def response
cblist = @a[1]
topic = @p[1].value
event_name = @p[2].value
notification = JSON.parse(@p[3].value)
key = @r.last
cb, condition, instance, uuid, instance_url = cblist.lrange(key,0,-1)
cpee = File.dirname(instance_url)
orisend = {
'CPEE-INSTANCE' => instance,
'CPEE-INSTANCE-URL' => instance_url,
'CPEE-INSTANCE-UUID' => uuid,
'CPEE-STATE' => notification['state']
}
send = orisend.dup
if notification['state'] == condition
cblist.del(key)
srv = Riddl::Client.new(cpee, cpee + "?riddl-description")
res = srv.resource("/#{instance}/properties/values/dataelements")
status, response = res.get
if status >= 200 && status < 300
doc = XML::Smart.string(response[0].value.read)
doc.register_namespace 'p', 'http://riddl.org/ns/common-patterns/properties/1.0'
doc.find('/p:value/*').each do |e|
send[e.qname.name] = CPEE::ValueHelper::parse(e.text)
end
end
Riddl::Client.new(cb).put Riddl::Parameter::Complex.new('dataelements','application/json',JSON::generate(send))
else
Riddl::Client.new(cb).put [
Riddl::Header.new('CPEE-UPDATE','true'),
Riddl::Parameter::Complex.new('dataelements','application/json',JSON::generate(send))
]
end
end
end #}}}
def self::implementation(opts)
opts[:cpee] ||= 'http://localhost:9298/'
opts[:self] ||= "http#{opts[:secure] ? 's' : ''}://#{opts[:host]}:#{opts[:port]}/"
opts[:cblist] = Redis.new(path: "/tmp/redis.sock", db: 14)
Proc.new do
on resource do
run InstantiateXML, opts[:cpee], true if post 'xmlsimple'
on resource 'xml' do
run InstantiateXML, opts[:cpee], false if post 'xml'
end
on resource 'url' do
run InstantiateUrl, opts[:cpee], opts[:self], opts[:cblist], false if post 'url'
run InstantiateUrl, opts[:cpee], opts[:self], opts[:cblist], true if post 'url_info'
end
on resource 'git' do
run InstantiateGit, opts[:cpee], opts[:self], opts[:cblist] if post 'git'
end
on resource 'instance' do
run HandleInstance, opts[:cpee] if post 'instance'
end
on resource 'callback' do
on resource do
run ContinueTask, opts[:cpee], opts[:cblist] if post
end
end
end
end
end
end
end