Merge "Add ability to set properties with glance_image"

This commit is contained in:
Jenkins 2016-04-05 11:51:17 +00:00 committed by Gerrit Code Review
commit ecfcaa5264
4 changed files with 193 additions and 77 deletions

View File

@ -10,10 +10,9 @@ Puppet::Type.type(:glance_image).provide(
@credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
# glanceclient support `image create` (in v2 API) but not openstackclient
# openstackclient now uses image v2 API by default.
# in the meantime it's implemented in openstackclient, hardcode version
# see https://bugs.launchpad.net/python-openstackclient/+bug/1405562
# TODO(aschultz): v2 is now supported but the options are different and
# it doesn't support the source being remote. We'll have to rework this
# to support v2
ENV['OS_IMAGE_API_VERSION'] = '1'
def initialize(value={})
@ -22,33 +21,31 @@ Puppet::Type.type(:glance_image).provide(
end
def create
if resource[:source]
if @resource[:source]
# copy_from cannot handle file://
if resource[:source] =~ /^\// # local file
location = "--file=#{resource[:source]}"
if @resource[:source] =~ /^\// # local file
location = "--file=#{@resource[:source]}"
else
location = "--copy-from=#{resource[:source]}"
location = "--copy-from=#{@resource[:source]}"
end
# location cannot handle file://
# location does not import, so no sense in doing anything more than this
elsif resource[:location]
location = "--location=#{resource[:location]}"
elsif @resource[:location]
location = "--location=#{@resource[:location]}"
else
raise(Puppet::Error, "Must specify either source or location")
end
properties = [resource[:name]]
if resource[:is_public] == :true
properties << "--public"
else
# This is the default, but it's nice to be verbose
properties << "--private"
end
properties << "--container-format=#{resource[:container_format]}"
properties << "--disk-format=#{resource[:disk_format]}"
properties << "--min-disk=#{resource[:min_disk]}" if resource[:min_disk]
properties << "--min-ram=#{resource[:min_ram]}" if resource[:min_ram]
properties << location
@property_hash = self.class.request('image', 'create', properties)
opts = [@resource[:name]]
opts << (@resource[:is_public] == :true ? '--public' : '--private')
opts << "--container-format=#{@resource[:container_format]}"
opts << "--disk-format=#{@resource[:disk_format]}"
opts << "--min-disk=#{@resource[:min_disk]}" if @resource[:min_disk]
opts << "--min-ram=#{@resource[:min_ram]}" if @resource[:min_ram]
opts << props_to_s(@resource[:properties]) if @resource[:properties]
opts << location
@property_hash = self.class.request('image', 'create', opts)
@property_hash[:ensure] = :present
end
@ -57,10 +54,12 @@ Puppet::Type.type(:glance_image).provide(
end
def destroy
self.class.request('image', 'delete', resource[:name])
self.class.request('image', 'delete', @resource[:name])
@property_hash.clear
end
mk_resource_methods
def is_public=(value)
@property_flush[:is_public] = value
end
@ -73,18 +72,10 @@ Puppet::Type.type(:glance_image).provide(
@property_flush[:disk_format] = value
end
def disk_format
@property_hash[:disk_format]
end
def container_format=(value)
@property_flush[:container_format] = value
end
def container_format
@property_hash[:container_format]
end
def min_ram=(value)
@property_flush[:min_ram] = value
end
@ -93,18 +84,19 @@ Puppet::Type.type(:glance_image).provide(
@property_flush[:min_disk] = value
end
def properties=(value)
@property_flush[:properties] = value
end
def id=(id)
fail('id is read only')
end
def id
@property_hash[:id]
end
def self.instances
list = request('image', 'list', '--long')
list = request('image', 'list')
list.collect do |image|
attrs = request('image', 'show', image[:id])
properties = Hash[attrs[:properties].scan(/(\S+)='([^']*)'/)] rescue nil
new(
:ensure => :present,
:name => attrs[:name],
@ -112,8 +104,9 @@ Puppet::Type.type(:glance_image).provide(
:container_format => attrs[:container_format],
:id => attrs[:id],
:disk_format => attrs[:disk_format],
:min_disk => attrs['min_disk'],
:min_ram => attrs['min_ram']
:min_disk => attrs[:min_disk],
:min_ram => attrs[:min_ram],
:properties => properties
)
end
end
@ -128,17 +121,25 @@ Puppet::Type.type(:glance_image).provide(
end
def flush
properties = [resource[:name]]
if @property_flush
(properties << '--public') if @property_flush[:is_public] == :true
(properties << '--private') if @property_flush[:is_public] == :false
(properties << "--container-format=#{@property_flush[:container_format]}") if @property_flush[:container_format]
(properties << "--disk-format=#{@property_flush[:disk_format]}") if @property_flush[:disk_format]
(properties << "--min-ram=#{@property_flush[:min_ram]}") if @property_flush[:min_ram]
(properties << "--min-disk=#{@property_flush[:min_disk]}") if @property_flush[:min_disk]
self.class.request('image', 'set', properties)
opts = [@resource[:name]]
(opts << '--public') if @property_flush[:is_public] == :true
(opts << '--private') if @property_flush[:is_public] == :false
(opts << "--container-format=#{@property_flush[:container_format]}") if @property_flush[:container_format]
(opts << "--disk-format=#{@property_flush[:disk_format]}") if @property_flush[:disk_format]
(opts << "--min-ram=#{@property_flush[:min_ram]}") if @property_flush[:min_ram]
(opts << "--min-disk=#{@property_flush[:min_disk]}") if @property_flush[:min_disk]
(opts << props_to_s(@property_flush[:properties])) if @property_flush[:properties]
self.class.request('image', 'set', opts)
@property_flush.clear
end
end
private
def props_to_s(props)
props.flat_map{ |k, v| ['--property', "#{k}=#{v}"] }
end
end

View File

@ -13,13 +13,14 @@ Puppet::Type.newtype(:glance_image) do
source => 'http://uec-images.ubuntu.com/releases/precise/release/ubuntu-12.04-server-cloudimg-amd64-disk1.img'
min_ram => 1234,
min_disk => 1234,
properties => { 'img_key' => img_value }
}
Known problems / limitations:
* All images are managed by the glance service.
* All images are managed by the glance service.
This means that since users are unable to manage their own images via this type,
is_public is really of no use. You can probably hide images this way but that's all.
* As glance image names do not have to be unique, you must ensure that your glance
* As glance image names do not have to be unique, you must ensure that your glance
repository does not have any duplicate names prior to using this.
* Ensure this is run on the same server as the glance-api service.
@ -74,21 +75,39 @@ Puppet::Type.newtype(:glance_image) do
newvalues(/\S+/)
end
newparam(:min_ram) do
newproperty(:min_ram) do
desc "The minimal ram size"
newvalues(/\d+/)
end
newparam(:min_disk) do
newproperty(:min_disk) do
desc "The minimal disk size"
newvalues(/\d+/)
end
newproperty(:properties) do
desc "The set of image 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
# Require the Glance service to be running
autorequire(:service) do
['glance']
['glance-api', 'glance-registry']
end
end

View File

@ -47,6 +47,9 @@ describe 'glance class' do
disk_format => 'qcow2',
is_public => 'yes',
source => 'http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-disk.img',
min_ram => '64',
min_disk => '1024',
properties => { 'icanhaz' => 'cheezburger' },
}
EOS
@ -77,11 +80,21 @@ describe 'glance class' do
end
describe 'glance images' do
it 'should create a glance image' do
shell('openstack --os-username glance --os-password a_big_secret --os-tenant-name services --os-auth-url http://127.0.0.1:5000/v2.0 image list') do |r|
it 'should create a glance image with proper attributes' do
glance_env_opts = '--os-username glance --os-password a_big_secret --os-tenant-name services --os-auth-url http://127.0.0.1:5000/v2.0'
shell("openstack #{glance_env_opts} image list") do |r|
expect(r.stdout).to match(/test_image/)
expect(r.stderr).to be_empty
end
shell("openstack #{glance_env_opts} image show test_image --format shell") do |r|
expect(r.stdout).to match(/visibility="public"/)
expect(r.stdout).to match(/container_format="bare"/)
expect(r.stdout).to match(/disk_format="qcow2"/)
expect(r.stdout).to match(/properties="icanhaz='cheezburger'"/)
expect(r.stdout).to match(/min_ram="64"/)
expect(r.stdout).to match(/min_disk="1024"/)
expect(r.stderr).to be_empty
end
end
end
end

View File

@ -39,31 +39,26 @@ describe provider_class do
it_behaves_like 'authenticated with environment variables' do
describe '#create' do
it 'creates an image' do
provider.class.stubs(:openstack)
.with('image', 'list', '--quiet', '--format', 'csv', '--long')
.returns('"ID","Name","Disk Format","Container Format","Size","Status"
"534 5b502-efe4-4852-a45d-edaba3a3acc6","image1","raw","bare",1270,"active"
')
provider.class.stubs(:openstack)
.with('image', 'create', '--format', 'shell', ['image1', '--public', '--container-format=bare', '--disk-format=qcow2', '--min-disk=1024', '--min-ram=1024', '--copy-from=http://example.com/image1.img' ])
.returns('checksum="09b9c392dc1f6e914cea287cb6be34b0"
.returns('checksum="ee1eca47dc88f4879d8a229cc70a07c6"
container_format="bare"
created_at="2015-04-08T18:28:01"
deleted="False"
deleted_at="None"
created_at="2016-03-29T20:52:24Z"
disk_format="qcow2"
id="5345b502-efe4-4852-a45d-edaba3a3acc6"
is_public="True"
file="/v2/images/8801c5b0-c505-4a15-8ca3-1d2383f8c015/file"
id="8801c5b0-c505-4a15-8ca3-1d2383f8c015"
min_disk="1024"
min_ram="1024"
name="image1"
owner="None"
properties="{}"
owner="5a9e521e17014804ab8b4e8b3de488a4"
protected="False"
size="1270"
schema="/v2/schemas/image"
size="13287936"
status="active"
updated_at="2015-04-10T18:18:18"
tags=""
updated_at="2016-03-29T20:52:40Z"
virtual_size="None"
visibility="public"
')
provider.create
expect(provider.exists?).to be_truthy
@ -73,9 +68,6 @@ virtual_size="None"
describe '#destroy' do
it 'destroys an image' do
provider.class.stubs(:openstack)
.with('image', 'list', '--quiet', '--format', 'csv')
.returns('"ID","Name","Disk Format","Container Format","Size","Status"')
provider.class.stubs(:openstack)
.with('image', 'delete', 'image1')
provider.destroy
@ -87,9 +79,9 @@ virtual_size="None"
describe '.instances' do
it 'finds every image' do
provider.class.stubs(:openstack)
.with('image', 'list', '--quiet', '--format', 'csv', '--long')
.returns('"ID","Name","Disk Format","Container Format","Size","Status"
"5345b502-efe4-4852-a45d-edaba3a3acc6","image1","raw","bare",1270,"active"
.with('image', 'list', '--quiet', '--format', 'csv', [])
.returns('"ID","Name","Status"
"5345b502-efe4-4852-a45d-edaba3a3acc6","image1","active"
')
provider.class.stubs(:openstack)
.with('image', 'show', '--format', 'shell', '5345b502-efe4-4852-a45d-edaba3a3acc6')
@ -118,4 +110,95 @@ virtual_size="None"
end
end
describe 'when managing an image with properties' do
let(:tenant_attrs) do
{
:ensure => 'present',
:name => 'image1',
:is_public => 'yes',
:container_format => 'bare',
:disk_format => 'qcow2',
:source => '/var/tmp/image1.img',
:min_ram => 1024,
:min_disk => 1024,
:properties => { 'something' => 'what', 'vmware_disktype' => 'sparse' }
}
end
let(:resource) do
Puppet::Type::Glance_image.new(tenant_attrs)
end
let(:provider) do
provider_class.new(resource)
end
it_behaves_like 'authenticated with environment variables' do
describe '#create' do
it 'creates an image' do
provider.class.stubs(:openstack)
.with('image', 'create', '--format', 'shell', ['image1', '--public', '--container-format=bare', '--disk-format=qcow2', '--min-disk=1024', '--min-ram=1024', ['--property', 'something=what', '--property', 'vmware_disktype=sparse'], '--file=/var/tmp/image1.img' ])
.returns('checksum="ee1eca47dc88f4879d8a229cc70a07c6"
container_format="bare"
created_at="2016-03-29T20:52:24Z"
disk_format="qcow2"
file="/v2/images/8801c5b0-c505-4a15-8ca3-1d2383f8c015/file"
id="8801c5b0-c505-4a15-8ca3-1d2383f8c015"
min_disk="1024"
min_ram="1024"
name="image1"
owner="5a9e521e17014804ab8b4e8b3de488a4"
properties="something=\'what\', vmware_disktype=\'sparse\'"
protected="False"
schema="/v2/schemas/image"
size="13287936"
status="active"
tags=""
updated_at="2016-03-29T20:52:40Z"
virtual_size="None"
visibility="public"
')
provider.create
expect(provider.exists?).to be_truthy
end
end
end
describe '.instances' do
it 'finds every image' do
provider.class.stubs(:openstack)
.with('image', 'list', '--quiet', '--format', 'csv', [])
.returns('"ID","Name","Status"
"5345b502-efe4-4852-a45d-edaba3a3acc6","image1","active"
')
provider.class.stubs(:openstack)
.with('image', 'show', '--format', 'shell', '5345b502-efe4-4852-a45d-edaba3a3acc6')
.returns('checksum="09b9c392dc1f6e914cea287cb6be34b0"
container_format="bare"
created_at="2015-04-08T18:28:01"
deleted="False"
deleted_at="None"
disk_format="qcow2"
id="5345b502-efe4-4852-a45d-edaba3a3acc6"
is_public="True"
min_disk="1024"
min_ram="1024"
name="image1"
owner="None"
properties="something=\'what\', vmware_disktype=\'sparse\'"
protected="False"
size="1270"
status="active"
updated_at="2015-04-10T18:18:18"
virtual_size="None"
')
instances = provider_class.instances
expect(instances.count).to eq(1)
end
end
end
end