Commit f2a007cc authored by Samir Noir's avatar Samir Noir 🧀
Browse files

Merge branch 'features/#12187' into 'master'

Add a glue to wrap kavlan's API

See merge request !80
parents caee9f1c c5839911
Pipeline #183221 waiting for manual action with stages
in 23 minutes and 47 seconds
......@@ -20,7 +20,6 @@ class ApplicationController < ActionController::Base
protect_from_forgery unless: -> { request.format.json? }
before_action :lookup_credentials
before_action :parse_json_payload, only: %i[create update destroy]
before_action :set_default_format
# additional classes introduced to handle all possible exceptions
......@@ -34,6 +33,7 @@ class ApplicationController < ActionController::Base
class MethodNotAllowed < ClientError; end # Error code 405
class NotAcceptable < ClientError; end # Error code 406
class PreconditionFailed < ClientError; end # Error code 412
class UnprocessableEntity < ClientError; end # Error code 422
# class & subclasses to handle server-side exceptions (Error codes 5xx)
class ServerError < ActionController::ActionControllerError; end
......@@ -53,10 +53,11 @@ class ApplicationController < ActionController::Base
rescue_from MethodNotAllowed, with: :method_not_allowed # for 405
rescue_from NotAcceptable, with: :not_acceptable # for 406
rescue_from PreconditionFailed, with: :precondition_failed # for 412
rescue_from UnprocessableEntity, with: :unprocessable_entity # for 422
# exception-handlers for client-side exceptions
rescue_from UnsupportedMediaType, with: :server_error # for 415
# agreed to send exception to server_error (instead of unsupported_media_type)
rescue_from UnsupportedMediaType, with: :server_error # for 415
rescue_from ServerError, with: :server_error # for 500
rescue_from BadGateway, with: :bad_gateway # for 502
rescue_from ServerUnavailable, with: :server_unavailable # for 503
......@@ -177,16 +178,6 @@ class ApplicationController < ActionController::Base
end
end
# Automatically parse JSON payload when request content type is JSON
def parse_json_payload
if request.content_type =~ %r{application/.*json}i
json = JSON.parse(request.body.read)
params.merge!(json)
end
ensure
request.body.rewind
end
def render_error(exception, options = {})
log_exception(exception)
message = options[:message] || exception.message
......@@ -254,6 +245,12 @@ class ApplicationController < ActionController::Base
render_error(exception, opts)
end
def unprocessable_entity(exception)
opts = { status: 422 }
opts[:message] = 'Unprocessable Entity' if exception.message == exception.class.name
render_error(exception, opts)
end
def server_error(exception)
opts = { status: 500 }
opts[:message] = 'Internal Server Error' if exception.message == exception.class.name
......
......@@ -60,10 +60,10 @@ class ClustersController < ResourcesController
links = super(item)
links.push({
'rel' => 'status',
'type' => api_media_type(:g5kitemjson),
'href' => uri_to(File.join(resource_path(item['uid']), 'status'))
})
'rel' => 'status',
'type' => api_media_type(:g5kitemjson),
'href' => uri_to(File.join(resource_path(item['uid']), 'status'))
})
links
end
end
module Vlans
extend ActiveSupport::Concern
included do
before_action :load_kavlan, :vlan_exist
end
private
def api_path(path = '')
uri_to(
File.join(
site_path(params[:site_id]),
'/internal/kavlanapi/',
path
),
:out
)
end
def load_kavlan
@kavlan = Grid5000::Kavlan.new
@kavlan.tls_options = tls_options_for(:out)
@kavlan.base_uri = api_path
end
def vlan_exist
vlan_param = params[:controller] == 'vlans' ? :id : :vlan_id
if params[vlan_param]
raise ApplicationController::NotFound, "Vlan #{params[vlan_param]} does not exist" unless @kavlan.vlan_exist?(params[vlan_param])
end
end
end
# Copyright (c) 2009-2011 Cyril Rohr, INRIA Rennes - Bretagne Atlantique
#
# 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.
class ErrorsController < ApplicationController
def err_method_not_allowed
raise MethodNotAllowed
end
end
......@@ -80,16 +80,16 @@ class SitesController < ResourcesController
links = super(item)
%w[jobs deployments vlans metrics storage].each do |rel|
links.push({
'rel' => rel,
'type' => api_media_type(:g5kcollectionjson),
'href' => uri_to(File.join(resource_path(item['uid']), rel))
})
'rel' => rel,
'type' => api_media_type(:g5kcollectionjson),
'href' => uri_to(File.join(resource_path(item['uid']), rel))
})
end
links.push({
'rel' => 'status',
'type' => api_media_type(:g5kitemjson),
'href' => uri_to(File.join(resource_path(item['uid']), 'status'))
})
'rel' => 'status',
'type' => api_media_type(:g5kitemjson),
'href' => uri_to(File.join(resource_path(item['uid']), 'status'))
})
links
end
end
# Copyright (c) 2009-2011 Cyril Rohr, INRIA Rennes - Bretagne Atlantique
#
# 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.
class VlansController < ApplicationController
include Vlans
# List vlans
def index
allow :get
items = @kavlan.list
total = items.length
result = {
'total' => total,
'offset' => 0,
'items' => items,
'links' => links_for_collection
}
result['items'].each do |item|
replace_kavlan_remote(item)
item['links'] = links_for_item(item)
end
respond_to do |format|
format.g5kcollectionjson { render json: result }
format.json { render json: result }
end
end
# Display the details of a vlan
def show
allow :get
expires_in 60.seconds
result = @kavlan.vlan(params[:id])
result.delete('links')
result['links'] = links_for_item(result)
replace_kavlan_remote(result)
respond_to do |format|
format.g5kitemjson { render json: result }
format.json { render json: result }
end
end
# start/stop dhcpd server for a vlan
def dhcpd
ensure_authenticated!
allow :put
unless request.content_type == "application/json"
raise UnsupportedMediaType, "Content-Type #{request.content_type} not supported"
end
if params[:vlan].nil? || params[:vlan][:action].nil? ||
(params[:vlan][:action] != 'start' &&
params[:vlan][:action] != 'stop')
raise UnprocessableEntity, "An action ('start' or 'stop') should be provided"
end
result = @kavlan.dhcpd(params[:id], params[:vlan])
if result.code.to_i == 403
raise Forbidden, "Not enough privileges on Kavlan resources"
end
render plain: '',
status: result.code
end
def vlan_users
allow :get
expires_in 60.seconds
result = @kavlan.vlan_users(params[:id], params[:user_id])
result['items'].each do |item|
item['links'] = links_for_item(item)
end
result['links'] = links_for_collection
respond_to do |format|
format.g5kcollectionjson { render json: result }
format.json { render json: result }
end
end
protected
def collection_path
site_vlans_path(params[:site_id])
end
def parent_path
site_path(params[:site_id])
end
def resource_path(id)
File.join(collection_path, id.to_s)
end
def links_for_item(item)
links = []
%w[dhcpd nodes].each do |rel|
links.push({
'rel' => rel,
'type' => api_media_type(:g5kitemjson),
'href' => uri_to(File.join(resource_path(item['uid']), rel))
})
end
links.push({
'rel' => 'users',
'type' => api_media_type(:g5kcollectionjson),
'href' => uri_to(File.join(resource_path(item['uid']), 'users'))
},
{
'rel' => 'self',
'href' => uri_to(File.join(collection_path, item['uid'])),
'type' => api_media_type(:g5kitemjson)
},
{
'rel' => 'parent',
'href' => uri_to(collection_path),
'type' => api_media_type(:g5kcollectionjson)
})
links
end
def links_for_collection
[
{
'rel' => 'self',
'href' => uri_to(collection_path),
'type' => api_media_type(:g5kcollectionjson)
},
{
'rel' => 'parent',
'href' => uri_to(parent_path),
'type' => api_media_type(:g5kitemjson)
}
]
end
private
def replace_kavlan_remote(item)
item['type'] = 'kavlan-global-remote' if item['type'] == 'kavlan-remote'
end
end
# Copyright (c) 2009-2011 Cyril Rohr, INRIA Rennes - Bretagne Atlantique
#
# 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.
class VlansNodesAllController < ApplicationController
include Vlans
# Display nodes for a vlan
def index
allow :get
expires_in 60.seconds
nodes = @kavlan.nodes
result = format_nodes(nodes)
respond_to do |format|
format.g5kitemjson { render json: result }
format.json { render json: result }
end
end
def show
allow :get
expires_in 60.seconds
nodes = @kavlan.nodes(params[:node_name])
result = { 'uid' => nodes.first[0], 'vlan' => nodes.first[1] }
raise NotFound, 'Unknown node' if result['vlan'] == 'unknown'
result['links'] = links_for_item(result)
respond_to do |format|
format.g5kitemjson { render json: result }
format.json { render json: result }
end
end
# Get the vlan for a list of nodes
def vlan_for_nodes
ensure_authenticated!
allow :post
unless request.content_type == "application/json"
raise UnsupportedMediaType, "Content-Type #{request.content_type} not supported"
end
if params[:vlans_nodes_all][:_json].blank? ||
!params[:vlans_nodes_all][:_json].is_a?(Array)
raise UnprocessableEntity, "Missing node list"
end
nodes = @kavlan.vlan_for_nodes(params[:vlans_nodes_all][:_json])
nodes = JSON.parse(nodes.body)
nodes.delete_if { |_key, value| value == 'unknown_node' }
result = format_nodes(nodes)
respond_to do |format|
format.g5kitemjson { render json: result }
format.json { render json: result }
end
end
protected
def format_nodes(nodes)
result = {
'total' => nodes.length,
'offset' => 0,
'items' => nodes.map { |n| {'uid' => n[0], 'vlan' => n[1]} },
'links' => links_for_collection
}
result['items'].each do |item|
item['links'] = links_for_item(item)
end
result
end
def collection_path
site_vlans_nodes_path
end
def parent_path
site_vlans_path
end
def links_for_item(item)
links = []
links.push({
'rel' => 'self',
'href' => uri_to(File.join(collection_path, item['uid'])),
'type' => api_media_type(:g5kitemjson)
},
{
'rel' => 'parent',
'href' => uri_to(collection_path),
'type' => api_media_type(:g5kcollectionjson)
})
links
end
def links_for_collection
[
{
'rel' => 'self',
'href' => uri_to(collection_path),
'type' => api_media_type(:g5kcollectionjson)
},
{
'rel' => 'parent',
'href' => uri_to(parent_path),
'type' => api_media_type(:g5kcollectionjson)
}
]
end
end
# Copyright (c) 2009-2011 Cyril Rohr, INRIA Rennes - Bretagne Atlantique
#
# 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.
class VlansNodesController < ApplicationController
include Vlans
# Display nodes for a vlan
def index
allow :get
expires_in 60.seconds
nodes = @kavlan.nodes_vlan(params[:vlan_id])
result = {
'total' => nodes.length,
'offset' => 0,
'items' => nodes.map { |n| {'uid'=> n} },
'links' => links_for_collection
}
result['items'].each do |item|
item['links'] = links_for_item(item)
end
respond_to do |format|
format.g5kitemjson { render json: result }
format.json { render json: result }
end
end
# Update the nodes inside a vlan (add only)
def add
ensure_authenticated!
allow :post
unless request.content_type == "application/json"
raise UnsupportedMediaType, "Content-Type #{request.content_type} not supported"
end
if params[:vlans_node][:_json].blank? || !params[:vlans_node][:_json].is_a?(Array)
raise UnprocessableEntity, "Missing node list"
end
result = @kavlan.update_vlan_nodes(params[:vlan_id], params[:vlans_node][:_json])
if result.code.to_i == 403
raise Forbidden, "Not enough privileges on Kavlan resources"
end
respond_to do |format|
format.g5kitemjson { render json: result.body }
format.json { render json: result.body }
end
end
protected
def collection_path
site_vlan_vlans_nodes_path
end
def parent_path
File.join(site_vlans_path, params[:vlan_id])
end
def links_for_item(item)
links = []
links.push({
'rel' => 'self',
'href' => uri_to(File.join(site_vlans_nodes_path, item['uid'])),
'type' => api_media_type(:g5kitemjson)
},
{
'rel' => 'parent',
'href' => uri_to(site_vlans_nodes_path),
'type' => api_media_type(:g5kcollectionjson)
})
links
end
def links_for_collection
[
{
'rel' => 'self',
'href' => uri_to(collection_path),
'type' => api_media_type(:g5kcollectionjson)
},
{
'rel' => 'parent',
'href' => uri_to(parent_path),
'type' => api_media_type(:g5kitemjson)
}
]
end
end
# Copyright (c) 2009-2011 Cyril Rohr, INRIA Rennes - Bretagne Atlantique
#
# 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.
class VlansUsersAllController < ApplicationController
include Vlans
# List users
def index
allow :get
expires_in 60.seconds
result = @kavlan.users
result['items'].each do |item|
item['links'] = links_for_item(item)
end
result['links'] = links_for_collection
respond_to do |format|
format.g5kcollectionjson { render json: result }
format.json { render json: result }
end
end
# Display the vlans allowed for a user
def show
allow :get
expires_in 60.seconds
result = @kavlan.users(params[:user_id])
result['links'] = links_for_item(result)
respond_to do |format|
format.g5kitemjson { render json: result }
format.json { render json: result }