Commit 6e811e59 authored by Samir Noir's avatar Samir Noir 🧀
Browse files

Add root vlan controller for the Kavlan's glue

This allow to list vlans, fetch a specific one and manage the dhcp
server for a vlan
parent 4c6ac11e
......@@ -33,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
......@@ -52,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
......@@ -243,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
if params[:vlan_id]
raise ApplicationController::NotFound, "Vlan #{params[:vlan_id]} does not exist" unless @kavlan.vlan_exist?(params[:vlan_id])
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|
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)
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
end
......@@ -18,19 +18,27 @@ Api::Application.routes.draw do
# The priority is based upon order of creation:
# first created -> highest priority.
get '/versions' => 'versions#index', :via => [:get]
get '/versions/:id' => 'versions#show', :via => [:get]
get '*resource/versions' => 'versions#index', :via => [:get]
get '*resource/versions/:id' => 'versions#show', :via => [:get]
get '/versions' => 'versions#index'
get '/versions/:id' => 'versions#show'
get '*resource/versions' => 'versions#index'
get '*resource/versions/:id' => 'versions#show'
resources :network_equipments, only: %i[index show]
resources :sites, only: %i[index show] do
resources :vlans, only: %i[index show] do
member do
put 'dhcpd' => 'vlans#dhcpd'
match 'dhcpd' => 'errors#err_method_not_allowed', :via => [:all]
end
end
member do
get :status
end
resources :network_equipments, only: %i[index show]
resources :pdus, only: %i[index show]
resources :clusters, only: %i[index show] do
member do
get :status
......
......@@ -113,6 +113,8 @@ module Grid5000
Net::HTTP::Get.new(uri, headers)
when :delete
Net::HTTP::Delete.new(uri, headers)
when :put
Net::HTTP::Put.new(uri, headers)
else
raise "Unknown http method: #{method}"
end
......
......@@ -114,6 +114,15 @@ Listen 8080
ProxyPreserveHost On
ProxyPass https://127.0.0.1:880<%= site_index+1 %>/sid/sites/<%= sites[site_index] %>/internal/kadeployapi retry=0
</Location>
<Location /sites/<%= sites[site_index] %>/internal/kavlanapi>
RequestHeader unset X-Forwarded-Host
RequestHeader unset X-Forwarded-For
RequestHeader unset X-Forwarded-Server
RequestHeader set X-Api-Version "sid"
RequestHeader set Host "api.grid5000.fr"
ProxyPreserveHost On
ProxyPass https://127.0.0.1:880<%= site_index+1 %>/sid/sites/<%= sites[site_index] %>/internal/kavlanapi retry=0
</Location>
<Location /sites/<%= sites[site_index] %>/public/~/>
RequestHeader set X-Api-Version "sid"
RequestHeader set Host "public.<%= sites[site_index] %>.grid5000.fr"
......
# 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.
require 'spec_helper'
describe VlansController do
render_views
before do
@base_expected_uri = 'http://api-out.local/sites/rennes/internal/kavlanapi/'
@headers_return = { 'Content-Type' => 'application/json' }
stub_request(:get, @base_expected_uri).
with(
headers: {
'Accept'=>'application/json',
'Host'=>'api-out.local',
}).
to_return(status: 200, body: fixture('kavlan-rennes-root.json'), headers: @headers_return)
end
describe 'GET /vlans/' do
it 'should return the list of vlans' do
get :index, params: { site_id: 'rennes', format: :json }
expect(response.status).to eq(200)
expect(json['total']).to eq(22)
expect(json['offset']).to eq(0)
expect(json['items'].length).to eq(22)
expect(json['links'].length).to eq(2)
expect(json['items'].first['uid']).to eq('DEFAULT')
expect(json['items'].map { |i| i['uid'].to_i }.sort).to eq (0..21).to_a.sort
expect(json['items'].all? { |i| i.has_key?('links') }).to be true
expect(json['items'][0]['links']).to eq([
{
'rel' => 'dhcpd',
'href' => '/sites/rennes/vlans/DEFAULT/dhcpd',
'type' => api_media_type(:g5kitemjson)
},
{
'rel' => 'nodes',
'href' => '/sites/rennes/vlans/DEFAULT/nodes',
'type' => api_media_type(:g5kitemjson)
},
{
'rel' => 'users',
'href' => '/sites/rennes/vlans/DEFAULT/users',
'type' => api_media_type(:g5kcollectionjson)
},
{
'rel' => 'self',
'href' => '/sites/rennes/vlans/DEFAULT',
'type' => api_media_type(:g5kitemjson)
},
{
'rel' => 'parent',
'href' => '/sites/rennes/vlans',
'type' => api_media_type(:g5kcollectionjson)
}
])
expect(json['links']).to eq([
{
'rel' => 'self',
'href' => '/sites/rennes/vlans',
'type' => api_media_type(:g5kcollectionjson)
},
{
'rel' => 'parent',
'href' => '/sites/rennes',
'type' => api_media_type(:g5kitemjson)
}
])
end
it 'vlan should be valid' do
stub_request(:get, File.join(@base_expected_uri, '3')).
with(
headers: {
'Accept'=>'application/json',
'Host'=>'api-out.local',
}).
to_return(status: 200, body: fixture('kavlan-rennes-vlan.json'), headers: @headers_return)
get :show, params: { site_id: 'rennes', id: '3', format: :json }
expect(response.status).to eq(200)
expect(json.length).to eq(3)
expect(json['uid']).to eq('3')
expect(json['type']).to eq('kavlan-local')
expect(json['links'].length).to eq(5)
expect(json['links']).to eq([
{
'rel' => 'dhcpd',
'href' => '/sites/rennes/vlans/3/dhcpd',
'type' => api_media_type(:g5kitemjson)
},
{
'rel' => 'nodes',
'href' => '/sites/rennes/vlans/3/nodes',
'type' => api_media_type(:g5kitemjson)
},
{
'rel' => 'users',
'href' => '/sites/rennes/vlans/3/users',
'type' => api_media_type(:g5kcollectionjson)
},
{
'rel' => 'self',
'href' => '/sites/rennes/vlans/3',
'type' => api_media_type(:g5kitemjson)
},
{
'rel' => 'parent',
'href' => '/sites/rennes/vlans',
'type' => api_media_type(:g5kcollectionjson)
}
])
end
it 'should return 404 if vlan does not exist' do
get :show, params: { site_id: 'rennes', id: '200', format: :json }
expect(response.status).to eq(404)
expect(response.body).to eq('Vlan 200 does not exist')
end
end
describe 'PUT /vlans/:id/dhcpd' do
it 'should return 422 when wrong or missing dhcpd action' do
stub_request(:put, File.join(@base_expected_uri, '1', 'dhcpd')).
with(
headers: {
'Accept'=>'application/json',
'Host'=>'api-out.local',
}).
to_return(status: 204, headers: @headers_return)
authenticate_as('snoir')
request.content_type = 'application/json'
put :dhcpd, params: { site_id: 'rennes', id: '1', format: :json }
expect(response.status).to eq(422)
expect(response.body).to eq("An action ('start' or 'stop') should be provided")
end
it 'should return 204 when starting dhcpd' do
stub_request(:put, File.join(@base_expected_uri, '1', 'dhcpd')).
with(
headers: {
'Accept'=>'application/json',
'Host'=>'api-out.local',
}).
to_return(status: 204, headers: @headers_return)
authenticate_as('snoir')
request.content_type = 'application/json'
put :dhcpd, params: { site_id: 'rennes', id: '1', action: 'start', format: :json }
expect(response.status).to eq(204)
expect(response.body).to be_empty
end
end
end
{"total":22,"items":[{"uid":"DEFAULT","type":"NULL","links":[{"href":"/3.0/sites/rennes/vlans/DEFAULT","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/DEFAULT/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/DEFAULT/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/DEFAULT/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"1","type":"kavlan-local","links":[{"href":"/3.0/sites/rennes/vlans/1","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/1/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/1/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/1/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"2","type":"kavlan-local","links":[{"href":"/3.0/sites/rennes/vlans/2","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/2/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/2/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/2/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"3","type":"kavlan-local","links":[{"href":"/3.0/sites/rennes/vlans/3","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/3/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/3/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/3/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"4","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/4","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/4/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/4/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/4/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"5","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/5","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/5/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/5/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/5/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"6","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/6","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/6/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/6/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/6/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"7","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/7","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/7/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/7/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/7/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"8","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/8","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/8/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/8/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/8/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"9","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/9","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/9/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/9/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/9/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"16","type":"kavlan-global","links":[{"href":"/3.0/sites/rennes/vlans/16","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/16/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/16/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/16/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"10","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/10","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/10/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/10/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/10/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"11","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/11","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/11/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/11/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/11/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"12","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/12","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/12/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/12/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/12/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"13","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/13","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/13/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/13/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/13/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"14","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/14","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/14/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/14/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/14/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"15","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/15","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/15/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/15/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/15/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"17","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/17","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/17/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/17/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/17/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"18","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/18","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/18/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/18/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/18/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"19","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/19","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/19/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/19/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/19/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"20","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/20","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/20/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/20/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/20/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"21","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/21","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/21/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/21/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/21/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]}],"links":[{"href":"/3.0/sites/rennes/vlans","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes","rel":"parent","type":"application/vnd.grid5000.item+json"}],"offset":0}
\ No newline at end of file
{"total":22,"items":[{"uid":"DEFAULT","type":"NULL","links":[{"href":"/3.0/sites/rennes/vlans/DEFAULT","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/DEFAULT/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/DEFAULT/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/DEFAULT/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"1","type":"kavlan-local","links":[{"href":"/3.0/sites/rennes/vlans/1","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/1/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/1/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/1/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"2","type":"kavlan-local","links":[{"href":"/3.0/sites/rennes/vlans/2","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/2/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/2/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/2/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"3","type":"kavlan-local","links":[{"href":"/3.0/sites/rennes/vlans/3","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/3/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/3/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/3/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"4","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/4","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/4/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/4/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/4/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"5","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/5","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/5/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/5/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/5/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"6","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/6","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/6/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/6/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/6/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"7","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/7","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/7/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/7/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/7/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"8","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/8","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/8/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/8/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/8/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"9","type":"kavlan","links":[{"href":"/3.0/sites/rennes/vlans/9","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/9/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/9/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/9/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"16","type":"kavlan-global","links":[{"href":"/3.0/sites/rennes/vlans/16","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/16/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/16/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/16/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"10","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/10","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/10/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/10/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/10/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"11","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/11","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/11/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/11/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/11/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"12","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/12","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/12/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/12/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/12/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"13","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/13","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/13/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/13/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/13/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"14","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/14","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/14/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/14/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/14/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"15","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/15","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/15/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/15/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/15/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"17","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/17","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/17/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/17/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/17/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"18","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/18","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/18/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/18/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/18/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"19","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/19","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/19/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/19/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/19/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"20","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/20","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/20/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/20/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/20/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]},{"uid":"21","type":"kavlan-remote","links":[{"href":"/3.0/sites/rennes/vlans/21","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/21/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/21/nodes","title":"nodes","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/21/users","title":"users","rel":"member","type":"application/vnd.grid5000.collection+json"}]}],"links":[{"href":"/3.0/sites/rennes/vlans","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes","rel":"parent","type":"application/vnd.grid5000.item+json"}],"offset":0}
\ No newline at end of file
{"uid":"3","type":"kavlan-local","links":[{"href":"/3.0/sites/rennes/vlans/3","rel":"self","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans","rel":"parent","type":"application/vnd.grid5000.collection+json"},{"href":"/3.0/sites/rennes/vlans/3/dhcpd","title":"dhcpd","rel":"member","type":"application/vnd.grid5000.item+json"},{"href":"/3.0/sites/rennes/vlans/3/nodes","title":"nodes","rel":"member",