Project

General

Profile

« Previous | Next » 

Revision 8588f9ac

Added by Daniel Lobato Garcia over 10 years ago

fixes #3046 - add NIC CRUD, power and boot operations API

View differences:

app/controllers/api/v2/hosts_controller.rb
include Api::Version2
include Foreman::Controller::SmartProxyAuth
before_filter :find_resource, :only => :puppetrun
before_filter :find_resource, :except => :facts
add_puppetmaster_filters :facts
api :GET, "/hosts/:id/puppetrun", "Force a puppet run on the agent."
......
process_response @host.puppetrun!
end
api :PUT, "/hosts/:id/power", "Run power operation on host."
param :id, :identifier_dottable, :required => true
param :power_action, String, :required => true, :desc => "power action, valid actions are ('on', 'start')', ('off', 'stop'), ('soft', 'reboot'), ('cycle', 'reset'), ('state', 'status')"
def power
valid_actions = PowerManager::SUPPORTED_ACTIONS
if valid_actions.include? params[:power_action]
render :json => { :power => @host.power.send(params[:power_action]) } , :status => 200
else
render :json => { :error => "Unknown power action: Available methods are #{valid_actions.join(', ')}" }, :status => 422
end
end
api :PUT, "/hosts/:id/boot", "Boot host from specified device."
param :id, :identifier_dottable, :required => true
param :device, String, :required => true, :desc => "boot device, valid devices are disk, cdrom, pxe, bios"
def boot
valid_devices = ProxyAPI::BMC::SUPPORTED_BOOT_DEVICES
if valid_devices.include? params[:device]
render :json => { :boot => @host.ipmi_boot(params[:device]) }, :status => 200
else
render :json => { :error => "Unknown device: Available devices are #{valid_devices.join(', ')}" }, :status => 422
end
end
api :POST, "/hosts/facts", "Upload facts for a host, creating the host if required."
param :name, String, :required => true, :desc => "hostname of the host"
param :facts, Hash, :required => true, :desc => "hash containing the facts for the host"
app/controllers/api/v2/interfaces_controller.rb
module Api
module V2
class InterfacesController < V2::BaseController
include Api::Version2
include Api::TaxonomyScope
before_filter :find_resource, :only => [:show, :update, :destroy]
before_filter :find_required_nested_object, :only => [:index, :show, :create]
api :GET, '/hosts/:host_id/interfaces', 'List all interfaces for host'
param :host_id, String, :required => true, :desc => 'id or name of host'
def index
@interfaces = @nested_obj.interfaces.paginate(paginate_options)
end
api :GET, '/hosts/:host_id/interfaces/:id', 'Show an interface for host'
param :host_id, String, :required => true, :desc => 'id or name of nested host'
param :id, String, :required => true, :desc => 'id or name of interface'
def show
end
api :POST, '/hosts/:host_id/interfaces', 'Create an interface linked to a host'
param :host_id, String, :required => true, :desc => 'id or name of host'
param :interface, Hash, :required => true, :desc => 'interface information' do
param :mac, String, :required => true, :desc => 'MAC address of interface'
param :ip, String, :required => true, :desc => 'IP address of interface'
param :type, String, :required => true, :desc => 'Interface type, i.e: Nic::BMC'
param :name, String, :required => true, :desc => 'Interface name'
param :subnet_id, Fixnum, :desc => 'Foreman subnet id of interface'
param :domain_id, Fixnum, :desc => 'Foreman domain id of interface'
param :username, String
param :password, String
param :provider, String, :desc => 'Interface provider, i.e: IPMI'
end
def create
interface = @nested_obj.interfaces.new(params[:interface], :without_protection => true)
if interface.save
render :json => interface, :status => 201
else
render :json => { :errors => interface.errors.full_messages }, :status => 422
end
end
api :PUT, "/hosts/:host_id/interfaces/:id", "Update host interface"
param :host_id, String, :required => true, :desc => 'id or name of host'
param :interface, Hash, :required => true, :desc => 'interface information' do
param :mac, String, :desc => 'MAC address of interface'
param :ip, String, :desc => 'IP address of interface'
param :type, String, :desc => 'Interface type, i.e: Nic::BMC'
param :name, String, :desc => 'Interface name'
param :subnet_id, Fixnum, :desc => 'Foreman subnet id of interface'
param :domain_id, Fixnum, :desc => 'Foreman domain id of interface'
param :username, String
param :password, String
param :provider, String, :desc => 'Interface provider, i.e: IPMI'
end
def update
process_response @interface.update_attributes(params[:interface], :without_protection => true)
end
api :DELETE, "/hosts/:host_id/interfaces/:id", "Delete a host interface"
param :id, String, :required => true, :desc => "id of interface"
def destroy
process_response @interface.destroy
end
private
def allowed_nested_id
%w(host_id)
end
def resource_class
Nic::Base
end
end
end
end
app/services/foreman/access_permissions.rb
:dashboard => [:OutOfSync, :errors, :active],
:unattended => :template,
:"api/v1/hosts" => [:index, :show, :status],
:"api/v2/hosts" => [:index, :show, :status]
:"api/v2/hosts" => [:index, :show, :status],
:"api/v2/interfaces" => [:index, :show]
}
map.permission :create_hosts, {:hosts => [:new, :create, :clone].push(*ajax_actions),
:compute_resources => cr_ajax_actions,
:puppetclasses => pc_ajax_actions,
:subnets => subnets_ajax_actions,
:"api/v1/hosts" => [:create],
:"api/v2/hosts" => [:create]
:"api/v2/hosts" => [:create],
:"api/v2/interfaces" => [:create]
}
map.permission :edit_hosts, {:hosts => [:edit, :update, :multiple_actions, :reset_multiple, :submit_multiple_enable,
:select_multiple_hostgroup, :select_multiple_environment, :submit_multiple_disable,
......
:puppetclasses => pc_ajax_actions,
:subnets => subnets_ajax_actions,
:"api/v1/hosts" => [:update],
:"api/v2/hosts" => [:update]
:"api/v2/hosts" => [:update],
:"api/v2/interfaces" => [:create, :update, :destroy]
}
map.permission :destroy_hosts, {:hosts => [:destroy, :multiple_actions, :reset_multiple, :multiple_destroy, :submit_multiple_destroy],
:"api/v1/hosts" => [:destroy],
:"api/v2/hosts" => [:destroy]
:"api/v2/hosts" => [:destroy],
:"api/v2/interfaces" => [:destroy]
}
map.permission :build_hosts, {:hosts => [:setBuild, :cancelBuild, :multiple_build, :submit_multiple_build],
:tasks => tasks_ajax_actions}
map.permission :power_hosts, {:hosts => [:power]}
map.permission :console_hosts, {:hosts => [:console]}
map.permission :ipmi_boot, {:hosts => [:ipmi_boot]}
map.permission :power_hosts, {:hosts => [:power],
:"api/v2/hosts" => [:power] }
map.permission :console_hosts, {:hosts => [:console] }
map.permission :ipmi_boot, { :hosts => [:ipmi_boot],
:"api/v2/hosts" => [:boot] }
map.permission :puppetrun_hosts, {:hosts => [:puppetrun, :multiple_puppetrun, :update_multiple_puppetrun],
:"api/v2/hosts" => [:puppetrun]
}
:"api/v2/hosts" => [:puppetrun] }
end
map.security_block :host_editing do |map|
app/services/power_manager.rb
module PowerManager
SUPPORTED_ACTIONS = [N_('start'), N_('stop'), N_('poweroff'), N_('reboot'), N_('reset'), N_('state')]
SUPPORTED_ACTIONS = [N_('start'), N_('stop'), N_('poweroff'), N_('reboot'), N_('reset'), N_('state'),
N_('on'), N_('off'), N_('soft'), N_('cycle'), N_('status')]
end
app/services/power_manager/bmc.rb
:poweroff => 'off',
:reboot => 'soft',
:reset => 'cycle',
:state => 'status'
:state => 'status',
:on => 'on',
:off => 'off',
:soft => 'soft',
:cycle => 'cycle',
:status => 'status'
}
end
app/services/power_manager/virt.rb
vm.state
end
(SUPPORTED_ACTIONS - ['state']).each do |method|
(SUPPORTED_ACTIONS - ['state', 'status']).each do |method|
define_method method do
vm.send(method.to_sym)
vm.send(action_map[method.to_sym])
end
end
private
attr_reader :vm
def action_map
{
:on => 'start',
:off => 'stop',
:soft => 'reboot',
:cycle => 'reset',
:status => 'state',
:start => 'start',
:stop => 'stop',
:poweroff => 'poweroff',
:reset => 'reset',
:state => 'state'
}
end
end
end
app/views/api/v2/interfaces/create.json.rabl
object @interface => :interface
extends "api/v2/interfaces/show"
app/views/api/v2/interfaces/index.json.rabl
collection @interfaces, :object_root => :interface
extends "api/v2/interfaces/show"
app/views/api/v2/interfaces/show.json.rabl
object @interface => :interface
attributes :id, :ip, :mac, :host_id, :name, :type, :username, :password, :provider, :subnet_id, :domain_id, :created_at, :updated_at
app/views/api/v2/interfaces/update.json.rabl
object @interface => :interface
extends "api/v2/interfaces/show"
config/routes/api/v2.rb
resources :hosts, :only => [] do
get :puppetrun, :on => :member
post :facts, :on => :collection
put 'power', :on => :member
put 'boot' , :on => :member
resources :parameters, :except => [:new, :edit] do
collection do
delete '/', :to => :reset
......
resources :host_classes, :path => :puppetclass_ids, :only => [:index, :create, :destroy]
match '/smart_parameters', :to => 'lookup_keys#host_or_hostgroup_smart_parameters'
match '/smart_class_parameters', :to => 'lookup_keys#host_or_hostgroup_smart_class_parameters'
resources :interfaces, :except => [:new, :edit]
end
resources :domains, :only => [] do
lib/proxy_api/bmc.rb
module ProxyAPI
class BMC < ProxyAPI::Resource
SUPPORTED_BOOT_DEVICES = %w[disk cdrom pxe bios]
def initialize args
@target = args[:host_ip] || '127.0.0.1'
......
# Perform a boot operation on the bmc device
def boot args
valid_boot_devices = %w[disk cdrom pxe bios]
# valid additional arguments args[:reboot] = true|false, args[:persistent] = true|false
# put "/bmc/:host/chassis/config/?:function?/?:action?" do
case args[:function]
when "bootdevice"
if valid_boot_devices.include?(args[:device])
if SUPPORTED_BOOT_DEVICES.include?(args[:device])
parse put(args, bmc_url_for('config',"#{args[:function]}/#{args[:device]}"))
else
raise NoMethodError
test/fixtures/nics.yml
bmc:
ip: 10.0.0.1
mac: AA:AA:AA:AA:AA:AA
type: Nic::BMC
name: host-bmc.domain.com
host: one
attrs:
:username: foo
:password: bar
:provider: IPMI
created_at: <%= Time.now %>
updated_at: <%= Time.now %>
test/fixtures/smart_proxies.yml
four:
name: Unused Proxy
url: http://else.where:4567
bmc:
name: BMC proxy
url: http://else.where:4567
features: bmc
test/functional/api/v2/hosts_controller_test.rb
@json ||= JSON.parse(Pathname.new("#{Rails.root}/test/fixtures/brslc022.facts.json").read)
end
def setup
User.current = users(:one) #use an unpriviledged user, not apiadmin
end
fixtures
test "should run puppet for specific host" do
......
end
test 'when ":restrict_registered_puppetmasters" is false, HTTP requests should be able to import facts' do
User.current = users(:one) #use an unprivileged user, not apiadmin
Setting[:restrict_registered_puppetmasters] = false
SETTINGS[:require_ssl] = false
......
end
test 'hosts with a registered smart proxy on should import facts successfully' do
User.current = users(:one) #use an unprivileged user, not apiadmin
Setting[:restrict_registered_puppetmasters] = true
Setting[:require_ssl_puppetmasters] = false
......
end
test 'hosts without a registered smart proxy on should not be able to import facts' do
User.current = users(:one) #use an unprivileged user, not apiadmin
Setting[:restrict_registered_puppetmasters] = true
Setting[:require_ssl_puppetmasters] = false
......
end
test 'hosts with a registered smart proxy and SSL cert should import facts successfully' do
User.current = users(:one) #use an unprivileged user, not apiadmin
Setting[:restrict_registered_puppetmasters] = true
Setting[:require_ssl_puppetmasters] = true
......
end
test 'hosts without a registered smart proxy but with an SSL cert should not be able to import facts' do
User.current = users(:one) #use an unprivileged user, not apiadmin
Setting[:restrict_registered_puppetmasters] = true
Setting[:require_ssl_puppetmasters] = true
......
end
test 'hosts with an unverified SSL cert should not be able to import facts' do
User.current = users(:one) #use an unprivileged user, not apiadmin
Setting[:restrict_registered_puppetmasters] = true
Setting[:require_ssl_puppetmasters] = true
......
end
test 'when "require_ssl_puppetmasters" and "require_ssl" are true, HTTP requests should not be able to import facts' do
User.current = users(:one) #use an unprivileged user, not apiadmin
Setting[:restrict_registered_puppetmasters] = true
Setting[:require_ssl_puppetmasters] = true
SETTINGS[:require_ssl] = true
......
end
test 'when "require_ssl_puppetmasters" is true and "require_ssl" is false, HTTP requests should be able to import facts' do
User.current = users(:one) #use an unprivileged user, not apiadmin
# since require_ssl_puppetmasters is only applicable to HTTPS connections, both should be set
Setting[:restrict_registered_puppetmasters] = true
Setting[:require_ssl_puppetmasters] = true
......
assert_equal 'A stub failure', JSON.parse(response.body)['host']['errors']['foo'].first
end
context 'BMC proxy operations' do
setup :initialize_proxy_ops
def initialize_proxy_ops
User.current = users(:apiadmin)
nics(:bmc).update_attribute(:host_id, hosts(:one).id)
end
test "power call to interface" do
ProxyAPI::BMC.any_instance.stubs(:power).with(:action => 'status').returns("on")
put :power, { :id => hosts(:one).to_param, :power_action => 'status' }
assert_response :success
assert @response.body =~ /on/
end
test "wrong power call fails gracefully" do
put :power, { :id => hosts(:one).to_param, :power_action => 'wrongmethod' }
assert_response 422
assert @response.body =~ /Available methods are/
end
test "boot call to interface" do
ProxyAPI::BMC.any_instance.stubs(:boot).with(:function => 'bootdevice', :device => 'bios').
returns( { "action" => "bios", "result" => true } .to_json)
put :boot, { :id => hosts(:one).to_param, :device => 'bios' }
assert_response :success
assert @response.body =~ /true/
end
test "wrong boot call to interface fails gracefully" do
put :boot, { :id => hosts(:one).to_param, :device => 'wrongbootdevice' }
assert_response 422
assert @response.body =~ /Available devices are/
end
end
end
test/functional/api/v2/interfaces_controller_test.rb
require 'test_helper'
class Api::V2::InterfacesControllerTest < ActionController::TestCase
valid_attrs = { 'name' => "test.foreman.com", 'ip' => "10.0.1.1", 'mac' => "AA:AA:AA:AA:AA:AA",
'username' => "foo", 'password' => "bar", 'provider' => "IPMI" ,
'type' => "Nic::BMC" }
test "get index for specific host" do
get :index, {:host_id => hosts(:one).name }
assert_response :success
assert_not_nil assigns(:interfaces)
interfaces = ActiveSupport::JSON.decode(@response.body)
assert !interfaces.empty?
end
test "show an interface" do
get :show, { :host_id => hosts(:one).to_param, :id => nics(:bmc).to_param }
assert_response :success
show_response = ActiveSupport::JSON.decode(@response.body)
assert !show_response.empty?
end
test "create interface" do
host = hosts(:one)
assert_difference('host.interfaces.count') do
post :create, { :host_id => host.to_param, :interface => valid_attrs }
end
assert_response 201
end
test "username and password are set on POST (create)" do
host = hosts(:one)
post :create, { :host_id => host.to_param, :interface => valid_attrs }
assert_equal Nic::BMC.find_by_host_id(host.id).attrs[:password], valid_attrs['password']
end
test "update a host interface" do
nics(:bmc).update_attribute(:host_id, hosts(:one).id)
put :update, { :host_id => hosts(:one).to_param,
:id => nics(:bmc).to_param,
:interface => valid_attrs.merge( { :host_id => hosts(:one).id } ) }
assert_response :success
assert_equal Host.find_by_name(hosts(:one).name).interfaces.order("nics.updated_at").last.ip, valid_attrs['ip']
end
test "destroy interface" do
assert_difference('Nic::BMC.count', -1) do
delete :destroy, { :host_id => hosts(:one).to_param, :id => nics(:bmc).to_param }
end
assert_response :success
end
end
test/test_helper.rb
fixtures :all
set_fixture_class({ :hosts => Host::Base })
set_fixture_class :nics => Nic::BMC
# for backwards compatibility to between Minitest syntax
alias_method :assert_not, :refute

Also available in: Unified diff