Use Hash values for properties

Use Hash values instead of Array values to avoid unnecessary conversion
between actual value type and internal data type. This allows us to
avoid potential issues caused by tricky parsing or conversion.

Note that this could not be backword compatible and users have to
update their manifests to adopt to this change.

Change-Id: Id4a32752eb1073c6467d089bc97c8271741feba0
This commit is contained in:
Takashi Kajinami 2024-10-07 12:56:32 +09:00
parent 6df0ae9222
commit bb1eba7195
50 changed files with 126 additions and 114 deletions

View File

@ -16,8 +16,10 @@ Puppet::Type.type(:cinder_qos).provide(
unless resource[:consumer].empty?
properties << '--consumer' << resource[:consumer]
end
resource[:properties].each do |item|
properties << '--property' << item
if resource[:properties]
resource[:properties].each do |k, v|
properties << '--property' << "#{k}=#{v}"
end
end
properties << name
self.class.request('volume qos', 'create', properties)
@ -43,12 +45,14 @@ Puppet::Type.type(:cinder_qos).provide(
end
def properties=(value)
properties = []
(value - @property_hash[:properties]).each do |item|
properties << '--property' << item
added = []
@property_hash[:properties].each do |k, v|
if value[k] != v
added << '--property' << "#{k}=#{value[k]}"
end
end
unless properties.empty?
self.class.request('volume qos', 'set', [properties, name])
unless added.empty?
self.class.request('volume type', 'set', [properties, added])
@property_hash[:properties] = value
end
end
@ -73,7 +77,7 @@ Puppet::Type.type(:cinder_qos).provide(
:name => qos[:name],
:ensure => :present,
:id => qos[:id],
:properties => pythondict2array(properties),
:properties => pythondict2hash(properties),
:consumer => qos[:consumer],
:associations => string2array(qos[:associations])
})
@ -93,12 +97,7 @@ Puppet::Type.type(:cinder_qos).provide(
return input.delete("'").split(/,\s/)
end
def self.pythondict2array(input)
json_input = JSON.parse(input.gsub(/'/, '"'))
output = []
json_input.each do | k, v |
output = output + ["#{k}=#{v}"]
end
return output
def self.pythondict2hash(input)
return JSON.parse(input.gsub(/'/, '"'))
end
end

View File

@ -13,8 +13,10 @@ Puppet::Type.type(:cinder_type).provide(
def create
properties = []
resource[:properties].each do |item|
properties << '--property' << item
if resource[:properties]
resource[:properties].each do |k, v|
properties << '--property' << "#{k}=#{v}"
end
end
properties << (@resource[:is_public] == :true ? '--public' : '--private')
properties << name
@ -40,8 +42,10 @@ Puppet::Type.type(:cinder_type).provide(
def properties=(value)
properties = []
(value - @property_hash[:properties]).each do |item|
properties << '--property' << item
@property_hash[:properties].each do |k, v|
if value[k] != v
properties << '--property' << "#{k}=#{value[k]}"
end
end
unless properties.empty?
self.class.request('volume type', 'set', [properties, name])
@ -93,7 +97,7 @@ Puppet::Type.type(:cinder_type).provide(
:name => type[:name],
:ensure => :present,
:id => type[:id],
:properties => pythondict2array(type[:properties]),
:properties => pythondict2hash(type[:properties]),
:is_public => type[:is_public],
:access_project_ids => type[:access_project_ids]
})
@ -113,12 +117,7 @@ Puppet::Type.type(:cinder_type).provide(
return input.delete("'").split(/,\s/)
end
def self.pythondict2array(input)
json_input = JSON.parse(input.gsub(/'/, '"'))
output = []
json_input.each do | k, v |
output = output + ["#{k}=#{v}"]
end
return output
def self.pythondict2hash(input)
return JSON.parse(input.gsub(/'/, '"'))
end
end

View File

@ -20,15 +20,14 @@ Puppet::Type.newtype(:cinder_qos) do
end
end
newproperty(:properties, :array_matching => :all) do
desc 'The Properties of the cinder qos. Should be an array'
defaultto []
def insync?(is)
return false unless is.is_a? Array
is.sort == should.sort
end
newproperty(:properties) do
desc "The set of volume qos properties"
validate do |value|
raise ArgumentError, "Properties doesn't match" unless value.match(/^\s*[^=\s]+=[^=\s]+$/)
if value.is_a?(Hash)
return true
else
raise ArgumentError, "Invalid properties #{value}. Requires a Hash, not a #{value.class}"
end
end
end

View File

@ -8,15 +8,14 @@ Puppet::Type.newtype(:cinder_type) do
newvalues(/\S+/)
end
newproperty(:properties, :array_matching => :all) do
desc 'The properties of the cinder type. Should be an array, all items should match pattern <key=value>'
defaultto []
def insync?(is)
return false unless is.is_a? Array
is.sort == should.sort
end
newproperty(:properties) do
desc "The set of volume type properties"
validate do |value|
raise ArgumentError, "Properties doesn't match" unless value.match(/^[^=\s]+=[^=]+$/)
if value.is_a?(Hash)
return true
else
raise ArgumentError, "Invalid properties #{value}. Requires a Hash, not a #{value.class}"
end
end
end

View File

@ -172,7 +172,7 @@ define cinder::backend::dellemc_powerflex(
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -138,7 +138,7 @@ define cinder::backend::dellemc_powermax (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -101,7 +101,7 @@ define cinder::backend::dellemc_powerstore (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -170,7 +170,7 @@ define cinder::backend::dellemc_sc (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -109,7 +109,7 @@ define cinder::backend::dellemc_unity (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -136,7 +136,7 @@ define cinder::backend::dellemc_xtremio (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -200,7 +200,7 @@ define cinder::backend::emc_vnx (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -159,7 +159,7 @@ define cinder::backend::gpfs (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -147,7 +147,7 @@ define cinder::backend::hpe3par_iscsi(
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -138,7 +138,7 @@ define cinder::backend::ibm_svf (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -130,7 +130,7 @@ define cinder::backend::iscsi (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -268,7 +268,7 @@ and will be removed in a future release.")
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -124,7 +124,7 @@ define cinder::backend::nexenta (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -160,7 +160,7 @@ define cinder::backend::nfs (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -165,7 +165,7 @@ define cinder::backend::pure(
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -97,7 +97,7 @@ define cinder::backend::quobyte (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -178,7 +178,7 @@ define cinder::backend::rbd (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -140,7 +140,7 @@ define cinder::backend::san (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -181,7 +181,7 @@ define cinder::backend::solidfire(
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ["volume_backend_name=${volume_backend_name}"],
properties => {'volume_backend_name' => $volume_backend_name},
}
}

View File

@ -98,11 +98,11 @@ define cinder::backend::vstorage (
if $manage_volume_type {
cinder_type { $volume_backend_name:
ensure => present,
properties => ['vz:volume_format=qcow2'],
properties => {'vz:volume_format' => 'qcow2'},
}
cinder_type { "${volume_backend_name}-ploop":
ensure => present,
properties => ['vz:volume_format=ploop'],
properties => {'vz:volume_format' => 'ploop'},
}
}

View File

@ -0,0 +1,5 @@
---
upgrade:
- |
The ``properties`` property of ``cinder_type`` resource and ``cinder_qos``
resource now accepts Hash values instead of Array values.

View File

@ -16,7 +16,11 @@ describe 'basic cinder' do
include openstack_integration::cinder
cinder_type { 'testvolumetype' :
properties => ['k=v', 'key1=val1', 'key2=<is> True']
properties => {
'k' => 'v',
'key1' => 'val1',
'key2' => '<is> True'
}
}
EOS

View File

@ -75,7 +75,7 @@ describe 'cinder::backend::dellemc_powerflex' do
it { is_expected.to contain_cinder_type("#{title}").with(
:ensure => 'present',
:properties => ["volume_backend_name=#{title}"]
:properties => {'volume_backend_name' => title}
)}
end
end

View File

@ -121,7 +121,7 @@ describe 'cinder::backend::dellemc_powermax' do
it { is_expected.to contain_cinder_type('dellemc_powermax').with(
:ensure => 'present',
:properties => ['volume_backend_name=dellemc_powermax']
:properties => {'volume_backend_name' => 'dellemc_powermax'}
)}
end
end

View File

@ -107,8 +107,8 @@ describe 'cinder::backend::dellemc_powerstore' do
end
it { is_expected.to contain_cinder_type('dellemc_powerstore').with(
:ensure => 'present',
:properties => ['volume_backend_name=dellemc_powerstore']
:ensure => 'present',
:properties => {'volume_backend_name' => 'dellemc_powerstore'}
)}
end
end

View File

@ -120,7 +120,10 @@ describe 'cinder::backend::dellemc_sc' do
params.merge!({:manage_volume_type => true})
end
it { is_expected.to contain_cinder_type('dellemc_sc').with(:ensure => :present, :properties => ['volume_backend_name=dellemc_sc']) }
it { is_expected.to contain_cinder_type('dellemc_sc').with(
:ensure => :present,
:properties => {'volume_backend_name' => 'dellemc_sc'}
) }
end
end

View File

@ -99,7 +99,7 @@ describe 'cinder::backend::dellemc_unity' do
it { is_expected.to contain_cinder_type('dellemc_unity').with(
:ensure => 'present',
:properties => ['volume_backend_name=dellemc_unity']
:properties => {'volume_backend_name' => 'dellemc_unity'}
)}
end
end

View File

@ -110,8 +110,8 @@ describe 'cinder::backend::dellemc_xtremio' do
end
it { is_expected.to contain_cinder_type('dellemc_xtremio').with(
:ensure => 'present',
:properties => ['volume_backend_name=dellemc_xtremio']
:ensure => 'present',
:properties => {'volume_backend_name' => 'dellemc_xtremio'}
)}
end
end

View File

@ -95,7 +95,7 @@ describe 'cinder::backend::emc_vnx' do
it { is_expected.to contain_cinder_type('emc').with(
:ensure => 'present',
:properties => ['volume_backend_name=emc']
:properties => {'volume_backend_name' => 'emc'}
)}
end

View File

@ -113,7 +113,7 @@ describe 'cinder::backend::gpfs' do
it { is_expected.to contain_cinder_type('gpfs').with(
:ensure => 'present',
:properties => ['volume_backend_name=gpfs']
:properties => {'volume_backend_name' => 'gpfs'}
)}
end

View File

@ -69,7 +69,7 @@ describe 'cinder::backend::hpe3par_iscsi' do
it { is_expected.to contain_cinder_type('hpe3par_iscsi').with(
:ensure => 'present',
:properties => ['volume_backend_name=hpe3par_iscsi']
:properties => {'volume_backend_name' => 'hpe3par_iscsi'}
)}
end
end

View File

@ -91,7 +91,7 @@ describe 'cinder::backend::ibm_svf' do
it { is_expected.to contain_cinder_type('ibm_svf').with(
:ensure => 'present',
:properties => ['volume_backend_name=ibm_svf']
:properties => {'volume_backend_name' => 'ibm_svf'}
)}
end
end

View File

@ -111,7 +111,7 @@ describe 'cinder::backend::iscsi' do
it { is_expected.to contain_cinder_type('hippo').with(
:ensure => 'present',
:properties => ['volume_backend_name=hippo']
:properties => {'volume_backend_name' => 'hippo'}
)}
end

View File

@ -77,7 +77,7 @@ describe 'cinder::backend::netapp' do
it { is_expected.to contain_cinder_type('netapp-cdot-nfs').with(
:ensure => 'present',
:properties => ['volume_backend_name=netapp-cdot-nfs']
:properties => {'volume_backend_name' => 'netapp-cdot-nfs'}
)}
end

View File

@ -59,7 +59,7 @@ describe 'cinder::backend::nexenta' do
it { is_expected.to contain_cinder_type('nexenta').with(
:ensure => 'present',
:properties => ['volume_backend_name=nexenta']
:properties => {'volume_backend_name' => 'nexenta'}
)}
end
end

View File

@ -95,7 +95,7 @@ describe 'cinder::backend::nfs' do
it { is_expected.to contain_cinder_type('hippo').with(
:ensure => 'present',
:properties => ['volume_backend_name=hippo']
:properties => {'volume_backend_name' => 'hippo'}
)}
end
end

View File

@ -126,7 +126,7 @@ describe 'cinder::backend::pure' do
it { is_expected.to contain_cinder_type('pure').with(
:ensure => 'present',
:properties => ['volume_backend_name=pure']
:properties => {'volume_backend_name' => 'pure'}
)}
end

View File

@ -37,7 +37,7 @@ describe 'cinder::backend::quobyte' do
it { is_expected.to contain_cinder_type('myquobyte').with(
:ensure => 'present',
:properties => ['volume_backend_name=myquobyte']
:properties => {'volume_backend_name' => 'myquobyte'}
)}
end
end

View File

@ -133,7 +133,7 @@ describe 'cinder::backend::rbd' do
it { is_expected.to contain_cinder_type('rbd-ssd').with(
:ensure => 'present',
:properties => ['volume_backend_name=rbd-ssd']
:properties => {'volume_backend_name' => 'rbd-ssd'}
)}
end
context 'rbd backend without managing package' do

View File

@ -85,7 +85,7 @@ describe 'cinder::backend::san' do
it { is_expected.to contain_cinder_type('mysan').with(
:ensure => 'present',
:properties => ['volume_backend_name=mysan']
:properties => {'volume_backend_name' => 'mysan'}
)}
end
end

View File

@ -115,7 +115,7 @@ describe 'cinder::backend::solidfire' do
it { is_expected.to contain_cinder_type('solidfire').with(
:ensure => 'present',
:properties => ['volume_backend_name=solidfire']
:properties => {'volume_backend_name' => 'solidfire'}
)}
end
end

View File

@ -53,13 +53,13 @@ describe 'cinder::backend::vstorage' do
end
it { is_expected.to contain_cinder_type('vstorage').with(
:ensure => 'present',
:properties => ['vz:volume_format=qcow2']
:ensure => 'present',
:properties => {'vz:volume_format' => 'qcow2'}
)}
it { is_expected.to contain_cinder_type('vstorage-ploop').with(
:ensure => 'present',
:properties => ['vz:volume_format=ploop']
:properties => {'vz:volume_format' => 'ploop'}
)}
end
end

View File

@ -16,7 +16,7 @@ describe provider_class do
{
:name => 'QoS_1',
:ensure => :present,
:properties => ['key1=value1', 'key2=value2'],
:properties => {'key1' => 'value1', 'key2' => 'value2'},
}
end
@ -66,11 +66,11 @@ properties="{\'key1\': \'value1\', \'key2\': \'value2\'}"
expect(instances[0].name).to eq('qos-1')
expect(instances[0].associations).to eq(['my_type1', 'my_type2'])
expect(instances[0].consumer).to eq('front-end')
expect(instances[0].properties).to eq(['read_iops=value1', 'write_iops=value2'])
expect(instances[0].properties).to eq({'read_iops'=>'value1', 'write_iops'=>'value2'})
expect(instances[1].name).to eq('qos-2')
expect(instances[1].consumer).to eq('both')
expect(instances[1].associations).to eq([])
expect(instances[1].properties).to eq([])
expect(instances[1].properties).to eq({})
end
end
@ -81,10 +81,10 @@ properties="{\'key1\': \'value1\', \'key2\': \'value2\'}"
end
end
describe '#pythondict2array' do
it 'should return an array with key-value when provided with a python dict' do
describe '#pythondict2hash' do
it 'should return a hash when provided with a python dict' do
s = "{'key': 'value', 'key2': 'value2'}"
expect(provider_class.pythondict2array(s)).to eq(['key=value', 'key2=value2'])
expect(provider_class.pythondict2hash(s)).to eq({'key'=>'value', 'key2'=>'value2'})
end
end
end

View File

@ -14,11 +14,15 @@ describe provider_class do
let(:type_attributes) do
{
:name => 'Backend_1',
:ensure => :present,
:properties => ['key=value', 'new_key=a-new_value', 'multiattach="<is> True"'],
:is_public => true,
:access_project_ids => [],
:name => 'Backend_1',
:ensure => :present,
:properties => {
'key' => 'value',
'new_key' => 'a-new_value',
'multiattach' => '<is> True'
},
:is_public => true,
:access_project_ids => [],
}
end
@ -36,7 +40,7 @@ describe provider_class do
describe '#create' do
it 'creates a type' do
expect(provider_class).to receive(:openstack)
.with('volume type', 'create', '--format', 'shell', ['--property', 'key=value', '--property', 'new_key=a-new_value', '--property', 'multiattach="<is> True"', '--public', 'Backend_1'])
.with('volume type', 'create', '--format', 'shell', ['--property', 'key=value', '--property', 'new_key=a-new_value', '--property', 'multiattach=<is> True', '--public', 'Backend_1'])
.and_return('id="90e19aff-1b35-4d60-9ee3-383c530275ab"
name="Backend_1"
properties="{\'key\': \'value\', \'new_key\': \'a-new_value\', \'multiattach\': \'<is> True\'}"
@ -82,8 +86,8 @@ access_project_ids="54f4d231201b4944a5fa4587a09bda23, 54f4d231201b4944a5fa4587a0
expect(instances[1].is_public).to be false
expect(instances[0].access_project_ids).to match_array([])
expect(instances[1].access_project_ids).to match_array(['54f4d231201b4944a5fa4587a09bda23', '54f4d231201b4944a5fa4587a09bda28'])
expect(instances[0].properties).to eq(["key1=value1"])
expect(instances[1].properties).to eq(["key2=value2"])
expect(instances[0].properties).to eq({'key1'=>'value1'})
expect(instances[1].properties).to eq({'key2'=>'value2'})
end
end
@ -94,10 +98,10 @@ access_project_ids="54f4d231201b4944a5fa4587a09bda23, 54f4d231201b4944a5fa4587a0
end
end
describe '#pythondict2array' do
it 'should return an array with key-value when provided with a python dict' do
describe '#pythondict2hash' do
it 'should return a hash when provided with a python dict' do
s = "{'key': 'value', 'key2': 'value2'}"
expect(provider_class.pythondict2array(s)).to eq(['key=value', 'key2=value2'])
expect(provider_class.pythondict2hash(s)).to eq({'key'=>'value', 'key2'=>'value2'})
end
end
end

View File

@ -12,17 +12,17 @@ describe Puppet::Type.type(:cinder_qos) do
:name => 'test_qos',
:properties => ['some_key1 = some_value2']
}
expect { Puppet::Type.type(:cinder_qos).new(incorrect_input) }.to raise_error(Puppet::ResourceError, /Parameter properties failed/)
expect { Puppet::Type.type(:cinder_type).new(incorrect_input) }.to raise_error(Puppet::ResourceError, /Invalid properties/)
end
it 'should default to no properties and no associations' do
catalog = Puppet::Resource::Catalog.new
anchor = Puppet::Type.type(:anchor).new(:name => 'cinder::service::end')
correct_input = {
:name => 'test_qos',
:name => 'test_qos',
}
cinder_qos = Puppet::Type.type(:cinder_qos).new(correct_input)
expect(cinder_qos[:properties]).to eq([])
expect(cinder_qos[:properties]).to eq(nil)
expect(cinder_qos[:associations]).to eq([])
catalog.add_resource anchor, cinder_qos
@ -36,7 +36,7 @@ describe Puppet::Type.type(:cinder_qos) do
it 'should autorequire cinder-api service' do
catalog = Puppet::Resource::Catalog.new
anchor = Puppet::Type.type(:anchor).new(:name => 'cinder::service::end')
properties = ['read_iops=value1', 'write_iops=value2']
properties = {'read_iops' => 'value1', 'write_iops' => 'value2'}
correct_input = {
:name => 'test_qos',
:properties => properties,

View File

@ -12,17 +12,17 @@ describe Puppet::Type.type(:cinder_type) do
:name => 'test_type',
:properties => ['some_key1 = some_value2']
}
expect { Puppet::Type.type(:cinder_type).new(incorrect_input) }.to raise_error(Puppet::ResourceError, /Parameter properties failed/)
expect { Puppet::Type.type(:cinder_type).new(incorrect_input) }.to raise_error(Puppet::ResourceError, /Invalid properties/)
end
it 'should default to no properties' do
catalog = Puppet::Resource::Catalog.new
anchor = Puppet::Type.type(:anchor).new(:name => 'cinder::service::end')
correct_input = {
:name => 'test_type',
:name => 'test_type',
}
cinder_type = Puppet::Type.type(:cinder_type).new(correct_input)
expect(cinder_type[:properties]).to eq([])
expect(cinder_type[:properties]).to eq(nil)
catalog.add_resource anchor, cinder_type
dependency = cinder_type.autorequire
@ -35,7 +35,7 @@ describe Puppet::Type.type(:cinder_type) do
it 'should autorequire cinder-api service' do
catalog = Puppet::Resource::Catalog.new
anchor = Puppet::Type.type(:anchor).new(:name => 'cinder::service::end')
properties = ['some_key1=value', 'some_key2=value1,value2']
properties = {'some_key1' => 'value', 'some_key2' => 'value1,value2'}
correct_input = {
:name => 'test_type',
:properties => properties,