Revision 8588f9ac
Added by Daniel Lobato Garcia over 10 years ago
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
fixes #3046 - add NIC CRUD, power and boot operations API