Add nova flavor provider
Nova is not including flavors by default anymore so there needs to be a way to create flavors with puppet. This change adds a nova_flavor provider for adding and removing flavors. Additionally the provider can be used to manage properties on flavors. Since flavor information cannot be updated after creation time, only the properties can be managed after creation via the nova_flavor provider. Change-Id: Ic370ac65990d0b3a360340632ada4016231c8a93 Related-Bug: #1564928 Co-Authored-By: Alex Schultz <aschultz@mirantis.com>
This commit is contained in:
parent
189a662c86
commit
0986087729
122
lib/puppet/provider/nova_flavor/openstack.rb
Normal file
122
lib/puppet/provider/nova_flavor/openstack.rb
Normal file
@ -0,0 +1,122 @@
|
||||
require 'puppet/provider/nova'
|
||||
|
||||
Puppet::Type.type(:nova_flavor).provide(
|
||||
:openstack,
|
||||
:parent => Puppet::Provider::Nova
|
||||
) do
|
||||
desc <<-EOT
|
||||
Manage Nova flavor
|
||||
EOT
|
||||
|
||||
@credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
|
||||
|
||||
def initialize(value={})
|
||||
super(value)
|
||||
@property_flush = {}
|
||||
end
|
||||
|
||||
def create
|
||||
opts = [@resource[:name]]
|
||||
opts << (@resource[:is_public] == :true ? '--public' : '--private')
|
||||
(opts << '--id' << @resource[:id]) if @resource[:id]
|
||||
(opts << '--ram' << @resource[:ram]) if @resource[:ram]
|
||||
(opts << '--disk' << @resource[:disk]) if @resource[:disk]
|
||||
(opts << '--ephemeral' << @resource[:ephemeral]) if @resource[:ephemeral]
|
||||
(opts << '--vcpus' << @resource[:vcpus]) if @resource[:vcpus]
|
||||
(opts << '--swap' << @resource[:swap]) if @resource[:swap]
|
||||
(opts << '--rxtx-factor' << @resource[:rxtx_factor]) if @resource[:rxtx_factor]
|
||||
@property_hash = self.class.request('flavor', 'create', opts)
|
||||
if @resource[:properties]
|
||||
prop_opts = [@resource[:name]]
|
||||
prop_opts << props_to_s(@resource[:properties])
|
||||
self.class.request('flavor', 'set', prop_opts)
|
||||
end
|
||||
@property_hash[:ensure] = :present
|
||||
end
|
||||
|
||||
def exists?
|
||||
@property_hash[:ensure] == :present
|
||||
end
|
||||
|
||||
def destroy
|
||||
self.class.request('flavor', 'delete', @property_hash[:id])
|
||||
end
|
||||
|
||||
mk_resource_methods
|
||||
|
||||
def is_public=(value)
|
||||
fail('is_public is read only')
|
||||
end
|
||||
|
||||
def id=(value)
|
||||
fail('id is read only')
|
||||
end
|
||||
|
||||
def ram=(value)
|
||||
fail('ram is read only')
|
||||
end
|
||||
|
||||
def disk=(value)
|
||||
fail('disk is read only')
|
||||
end
|
||||
|
||||
def vcpus=(value)
|
||||
fail('vcpus is read only')
|
||||
end
|
||||
|
||||
def swap=(value)
|
||||
fail('swap is read only')
|
||||
end
|
||||
|
||||
def rxtx_factor=(value)
|
||||
fail('rxtx_factor is read only')
|
||||
end
|
||||
|
||||
def properties=(value)
|
||||
@property_flush[:properties] = value
|
||||
end
|
||||
|
||||
def self.instances
|
||||
request('flavor', 'list', ['--long', '--all']).collect do |attrs|
|
||||
properties = Hash[attrs[:properties].scan(/(\S+)='([^']*)'/)] rescue nil
|
||||
new(
|
||||
:ensure => :present,
|
||||
:name => attrs[:name],
|
||||
:id => attrs[:id],
|
||||
:ram => attrs[:ram],
|
||||
:disk => attrs[:disk],
|
||||
:ephemeral => attrs[:ephemeral],
|
||||
:vcpus => attrs[:vcpus],
|
||||
:is_public => attrs[:is_public].downcase.chomp == 'true'? true : false,
|
||||
:swap => attrs[:swap],
|
||||
:rxtx_factor => attrs[:rxtx_factor],
|
||||
:properties => properties
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def self.prefetch(resources)
|
||||
flavors = instances
|
||||
resources.keys.each do |name|
|
||||
if provider = flavors.find{ |flavor| flavor.name == name }
|
||||
resources[name].provider = provider
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def flush
|
||||
unless @property_flush.empty?
|
||||
opts = [@resource[:name]]
|
||||
opts << props_to_s(@property_flush[:properties])
|
||||
|
||||
self.class.request('flavor', 'set', opts)
|
||||
@property_flush.clear
|
||||
end
|
||||
end
|
||||
private
|
||||
|
||||
def props_to_s(props)
|
||||
props.flat_map{ |k, v| ['--property', "#{k}=#{v}"] }
|
||||
end
|
||||
end
|
||||
|
144
lib/puppet/type/nova_flavor.rb
Normal file
144
lib/puppet/type/nova_flavor.rb
Normal file
@ -0,0 +1,144 @@
|
||||
# nova_flavor type
|
||||
#
|
||||
# == Parameters
|
||||
# [*name*]
|
||||
# Name for the flavor
|
||||
# Required
|
||||
#
|
||||
# [*id*]
|
||||
# Unique ID (integer or UUID) for the flavor.
|
||||
# Optional
|
||||
#
|
||||
# [*ram*]
|
||||
# Amount of RAM to use (in megabytes).
|
||||
# Optional
|
||||
#
|
||||
# [*disk*]
|
||||
# Amount of disk space (in gigabytes) to use for the root (/) partition.
|
||||
# Optional
|
||||
#
|
||||
# [*vcpus*]
|
||||
# Number of virtual CPUs to use.
|
||||
# Optional
|
||||
#
|
||||
# [*ephemeral*]
|
||||
# Amount of disk space (in gigabytes) to use for the ephemeral partition.
|
||||
# Optional
|
||||
#
|
||||
# [*swap*]
|
||||
# Amount of swap space (in megabytes) to use.
|
||||
# Optional
|
||||
#
|
||||
# [*rxtx_factor*]
|
||||
# The slice of bandwidth that the instances with this flavor can use
|
||||
# (through the Virtual Interface (vif) creation in the hypervisor)
|
||||
# Optional
|
||||
#
|
||||
# [*is_public*]
|
||||
# A boolean to indicate visibility
|
||||
# Optional
|
||||
#
|
||||
# [*properties*]
|
||||
# A key => value hash used to set the properties for the flavor. This is
|
||||
# the only parameter that can be updated after the creation of the flavor.
|
||||
# Optional
|
||||
require 'puppet'
|
||||
|
||||
Puppet::Type.newtype(:nova_flavor) do
|
||||
|
||||
@doc = "Manage creation of nova flavors."
|
||||
|
||||
ensurable
|
||||
|
||||
autorequire(:nova_config) do
|
||||
['auth_uri', 'admin_tenant_name', 'admin_user', 'admin_password']
|
||||
end
|
||||
|
||||
# Require the nova-api service to be running
|
||||
autorequire(:service) do
|
||||
['nova-api']
|
||||
end
|
||||
|
||||
newparam(:name, :namevar => true) do
|
||||
desc 'Name for the flavor'
|
||||
validate do |value|
|
||||
if not value.is_a? String
|
||||
raise ArgumentError, "name parameter must be a String"
|
||||
end
|
||||
unless value =~ /^[a-zA-Z0-9\-\._]+$/
|
||||
raise ArgumentError, "#{value} is not a valid name"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
newparam(:id) do
|
||||
desc 'Unique ID (integer or UUID) for the flavor.'
|
||||
end
|
||||
|
||||
newparam(:ram) do
|
||||
desc 'Amount of RAM to use (in megabytes).'
|
||||
end
|
||||
|
||||
newparam(:disk) do
|
||||
desc 'Amount of disk space (in gigabytes) to use for the root (/) partition.'
|
||||
end
|
||||
|
||||
newparam(:vcpus) do
|
||||
desc 'Number of virtual CPUs to use.'
|
||||
end
|
||||
|
||||
newparam(:ephemeral) do
|
||||
desc 'Amount of disk space (in gigabytes) to use for the ephemeral partition.'
|
||||
end
|
||||
|
||||
newparam(:swap) do
|
||||
desc 'Amount of swap space (in megabytes) to use.'
|
||||
end
|
||||
|
||||
newparam(:rxtx_factor) do
|
||||
desc 'The slice of bandiwdth that the instances with this flavor can use (through the Virtual Interface (vif) creation in the hypervisor)'
|
||||
end
|
||||
|
||||
newparam(:is_public) do
|
||||
desc "Whether the image is public or not. Default true"
|
||||
newvalues(/(y|Y)es/, /(n|N)o/, /(t|T)rue/, /(f|F)alse/, true, false)
|
||||
defaultto(true)
|
||||
munge do |v|
|
||||
if v =~ /^(y|Y)es$/
|
||||
:true
|
||||
elsif v =~ /^(n|N)o$/
|
||||
:false
|
||||
else
|
||||
v.to_s.downcase.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
newproperty(:properties) do
|
||||
desc "The set of flavor properties"
|
||||
|
||||
munge do |value|
|
||||
return value if value.is_a? Hash
|
||||
|
||||
# wrap property value in commas
|
||||
value.gsub!(/=(\w+)/, '=\'\1\'')
|
||||
Hash[value.scan(/(\S+)='([^']*)'/)]
|
||||
end
|
||||
|
||||
validate do |value|
|
||||
return true if value.is_a? Hash
|
||||
|
||||
value.split(',').each do |property|
|
||||
raise ArgumentError, "Key/value pairs should be separated by an =" unless property.include?('=')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
validate do
|
||||
unless self[:name]
|
||||
raise(ArgumentError, 'Name must be set')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- Added flavor provider and type for creating and deleting
|
||||
because nova no longer comes with flavors by default.
|
||||
The provider can be used to add/delete flavors and manage
|
||||
flavor properties.
|
@ -30,7 +30,7 @@ describe 'basic nova' do
|
||||
# Nova resources
|
||||
class { '::nova':
|
||||
database_connection => 'mysql+pymysql://nova:a_big_secret@127.0.0.1/nova?charset=utf8',
|
||||
api_database_connection => 'mysql+pymysql://nova_api:a_big_secret@127.0.0.1/nova_api?charset=utf8',
|
||||
api_database_connection => 'mysql+pymysql://nova_api:a_big_secret@127.0.0.1/nova_api?charset=utf8',
|
||||
rabbit_userid => 'nova',
|
||||
rabbit_password => 'an_even_bigger_secret',
|
||||
image_service => 'nova.image.glance.GlanceImageService',
|
||||
@ -78,6 +78,16 @@ describe 'basic nova' do
|
||||
require => Class['nova::api'],
|
||||
}
|
||||
|
||||
nova_flavor { 'test_flavor':
|
||||
ensure => present,
|
||||
name => 'test_flavor',
|
||||
id => '9999',
|
||||
ram => '512',
|
||||
disk => '1',
|
||||
vcpus => '1',
|
||||
require => [ Class['nova::api'], Class['nova::keystone::auth'] ],
|
||||
}
|
||||
|
||||
# TODO: networking with neutron
|
||||
EOS
|
||||
|
||||
@ -111,5 +121,13 @@ describe 'basic nova' do
|
||||
end
|
||||
end
|
||||
end
|
||||
describe 'nova flavor' do
|
||||
it 'should create new flavor' do
|
||||
shell('openstack --os-username nova --os-password a_big_secret --os-tenant-name services --os-auth-url http://127.0.0.1:5000/v2.0 flavor list') do |r|
|
||||
expect(r.stdout).to match(/test_flavor/)
|
||||
expect(r.stderr).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
60
spec/unit/provider/nova_flavor/openstack_spec.rb
Normal file
60
spec/unit/provider/nova_flavor/openstack_spec.rb
Normal file
@ -0,0 +1,60 @@
|
||||
require 'puppet'
|
||||
require 'spec_helper'
|
||||
require 'puppet/provider/nova_flavor/openstack'
|
||||
|
||||
provider_class = Puppet::Type.type(:nova_flavor).provider(:openstack)
|
||||
|
||||
describe provider_class do
|
||||
|
||||
describe 'managing flavors' do
|
||||
let(:flavor_attrs) do
|
||||
{
|
||||
:name => 'example',
|
||||
:id => '1',
|
||||
:ram => '512',
|
||||
:disk => '1',
|
||||
:vcpus => '1',
|
||||
:ensure => 'present',
|
||||
}
|
||||
end
|
||||
|
||||
let :resource do
|
||||
Puppet::Type::Nova_flavor.new(flavor_attrs)
|
||||
end
|
||||
|
||||
let(:provider) do
|
||||
provider_class.new(resource)
|
||||
end
|
||||
|
||||
describe '#create' do
|
||||
it 'creates flavor' do
|
||||
provider.class.stubs(:openstack)
|
||||
.with('flavor', 'list', ['--long', '--all'])
|
||||
.returns('"ID", "Name", "RAM", "Disk", "Ephemeral", "VCPUs", "Is Public", "Swap", "RXTX Factor", "Properties"')
|
||||
provider.class.stubs(:openstack)
|
||||
.with('flavor', 'create', 'shell', ['example', '--public', '--id', '1', '--ram', '512', '--disk', '1', '--vcpus', '1'])
|
||||
.returns('os-flv-disabled:disabled="False"
|
||||
os-flv-ext-data:ephemeral="0"
|
||||
disk="1"
|
||||
id="1"
|
||||
name="example"
|
||||
os-flavor-access:is_public="True"
|
||||
ram="512"
|
||||
rxtx_factor="1.0"
|
||||
swap=""
|
||||
vcpus="1"')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
it 'removes flavor' do
|
||||
provider_class.expects(:openstack)
|
||||
.with('flavor', 'delete', '1')
|
||||
provider.instance_variable_set(:@property_hash, flavor_attrs)
|
||||
provider.destroy
|
||||
expect(provider.exists?).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user