Add Nova Aggregate support
Bring nova aggregate and availability zones support into puppet-nova. - Handle nil values and 0 lengths (Aimon Bustardo) - Remove extraneous whitespace on aggregate-create (Aimon Bustardo) Implements: blueprint aggregate-handling Change-Id: I9125d573a6a3cf4d444300d3570c4ab394c4ecd8
This commit is contained in:
parent
21f5e9a4bc
commit
08578c920e
|
@ -0,0 +1,199 @@
|
|||
# Run test ie with: rspec spec/unit/provider/nova_spec.rb
|
||||
|
||||
require 'puppet/util/inifile'
|
||||
|
||||
class Puppet::Provider::Nova < Puppet::Provider
|
||||
|
||||
def self.conf_filename
|
||||
'/etc/nova/nova.conf'
|
||||
end
|
||||
|
||||
def self.withenv(hash, &block)
|
||||
saved = ENV.to_hash
|
||||
hash.each do |name, val|
|
||||
ENV[name.to_s] = val
|
||||
end
|
||||
|
||||
yield
|
||||
ensure
|
||||
ENV.clear
|
||||
saved.each do |name, val|
|
||||
ENV[name] = val
|
||||
end
|
||||
end
|
||||
|
||||
def self.nova_conf
|
||||
return @nova_conf if @nova_conf
|
||||
@nova_conf = Puppet::Util::IniConfig::File.new
|
||||
@nova_conf.read(conf_filename)
|
||||
@nova_conf
|
||||
end
|
||||
|
||||
def self.nova_credentials
|
||||
@nova_credentials ||= get_nova_credentials
|
||||
end
|
||||
|
||||
def nova_credentials
|
||||
self.class.nova_credentials
|
||||
end
|
||||
|
||||
def self.get_nova_credentials
|
||||
#needed keys for authentication
|
||||
auth_keys = ['auth_host', 'auth_port', 'auth_protocol',
|
||||
'admin_tenant_name', 'admin_user', 'admin_password']
|
||||
conf = nova_conf
|
||||
if conf and conf['keystone_authtoken'] and
|
||||
auth_keys.all?{|k| !conf['keystone_authtoken'][k].nil?}
|
||||
return Hash[ auth_keys.map \
|
||||
{ |k| [k, conf['keystone_authtoken'][k].strip] } ]
|
||||
else
|
||||
raise(Puppet::Error, "File: #{conf_filename} does not contain all " +
|
||||
"required sections. Nova types will not work if nova is not " +
|
||||
"correctly configured.")
|
||||
end
|
||||
end
|
||||
|
||||
def self.get_auth_endpoint
|
||||
q = nova_credentials
|
||||
"#{q['auth_protocol']}://#{q['auth_host']}:#{q['auth_port']}/v2.0/"
|
||||
end
|
||||
|
||||
def self.auth_endpoint
|
||||
@auth_endpoint ||= get_auth_endpoint
|
||||
end
|
||||
|
||||
def self.auth_nova(*args)
|
||||
q = nova_credentials
|
||||
authenv = {
|
||||
:OS_AUTH_URL => self.auth_endpoint,
|
||||
:OS_USERNAME => q['admin_user'],
|
||||
:OS_TENANT_NAME => q['admin_tenant_name'],
|
||||
:OS_PASSWORD => q['admin_password']
|
||||
}
|
||||
begin
|
||||
withenv authenv do
|
||||
nova(args)
|
||||
end
|
||||
rescue Exception => e
|
||||
if (e.message =~ /\[Errno 111\] Connection refused/) or
|
||||
(e.message =~ /\(HTTP 400\)/)
|
||||
sleep 10
|
||||
withenv authenv do
|
||||
nova(args)
|
||||
end
|
||||
else
|
||||
raise(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def auth_nova(*args)
|
||||
self.class.auth_nova(args)
|
||||
end
|
||||
|
||||
def self.reset
|
||||
@nova_conf = nil
|
||||
@nova_credentials = nil
|
||||
end
|
||||
|
||||
def self.str2hash(s)
|
||||
#parse string
|
||||
if s.include? "="
|
||||
k, v = s.split("=", 2)
|
||||
return {k.gsub(/'/, "") => v.gsub(/'/, "")}
|
||||
else
|
||||
return s.gsub(/'/, "")
|
||||
end
|
||||
end
|
||||
|
||||
def self.str2list(s)
|
||||
#parse string
|
||||
if s.include? ","
|
||||
if s.include? "="
|
||||
new = {}
|
||||
else
|
||||
new = []
|
||||
end
|
||||
s.split(",").each do |el|
|
||||
ret = str2hash(el.strip())
|
||||
if s.include? "="
|
||||
new.update(ret)
|
||||
else
|
||||
new.push(ret)
|
||||
end
|
||||
end
|
||||
return new
|
||||
else
|
||||
return str2hash(s.strip())
|
||||
end
|
||||
end
|
||||
|
||||
def self.cliout2list(output)
|
||||
#don't proceed with empty output
|
||||
if output.empty?
|
||||
return []
|
||||
end
|
||||
lines = []
|
||||
output.each_line do |line|
|
||||
#ignore lines starting with '+'
|
||||
if not line.match("^\\+")
|
||||
#split line at '|' and remove useless information
|
||||
line = line.gsub(/^\| /, "").gsub(/ \|$/, "").gsub(/[\n]+/, "")
|
||||
line = line.split("|").map do |el|
|
||||
el.strip().gsub(/^-$/, "")
|
||||
end
|
||||
#check every element for list
|
||||
line = line.map do |el|
|
||||
el = str2list(el)
|
||||
end
|
||||
lines.push(line)
|
||||
end
|
||||
end
|
||||
#create a list of hashes and return the list
|
||||
hash_list = []
|
||||
header = lines[0]
|
||||
lines[1..-1].each do |line|
|
||||
hash_list.push(Hash[header.zip(line)])
|
||||
end
|
||||
return hash_list
|
||||
end
|
||||
|
||||
def self.nova_aggregate_resources_ids
|
||||
#produce a list of hashes with Id=>Name pairs
|
||||
lines = []
|
||||
#run command
|
||||
cmd_output = auth_nova("aggregate-list")
|
||||
#parse output
|
||||
hash_list = cliout2list(cmd_output)
|
||||
#only interessted in Id and Name
|
||||
hash_list.map{ |e| e.delete("Availability Zone")}
|
||||
hash_list.map{ |e| e['Id'] = e['Id'].to_i}
|
||||
return hash_list
|
||||
end
|
||||
|
||||
def self.nova_aggregate_resources_get_name_by_id(name)
|
||||
#find the id by the given name
|
||||
nova_aggregate_resources_ids.each do |entry|
|
||||
if entry["Name"] == name
|
||||
return entry["Id"]
|
||||
end
|
||||
end
|
||||
#name not found
|
||||
return nil
|
||||
end
|
||||
|
||||
def self.nova_aggregate_resources_attr(id)
|
||||
#run command to get details for given Id
|
||||
cmd_output = auth_nova("aggregate-details", id)
|
||||
list = cliout2list(cmd_output)[0]
|
||||
if ! list["Hosts"].is_a?(Array)
|
||||
if list["Hosts"] == ""
|
||||
list["Hosts"] = []
|
||||
else
|
||||
list["Hosts"] = [ list["Hosts"] ]
|
||||
end
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,157 @@
|
|||
require File.join(File.dirname(__FILE__), '..','..','..',
|
||||
'puppet/provider/nova')
|
||||
|
||||
Puppet::Type.type(:nova_aggregate).provide(
|
||||
:nova,
|
||||
:parent => Puppet::Provider::Nova
|
||||
) do
|
||||
|
||||
desc "Manage nova aggregations"
|
||||
|
||||
commands :nova => 'nova'
|
||||
|
||||
mk_resource_methods
|
||||
|
||||
def self.instances
|
||||
nova_aggregate_resources_ids().collect do |el|
|
||||
attrs = nova_aggregate_resources_attr(el['Id'])
|
||||
new(
|
||||
:ensure => :present,
|
||||
:name => attrs['Name'],
|
||||
:id => attrs['Id'],
|
||||
:availability_zone => attrs['Availability Zone'],
|
||||
:metadata => attrs['Metadata'],
|
||||
:hosts => attrs['Hosts']
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def self.prefetch(resources)
|
||||
instances_ = instances
|
||||
resources.keys.each do |name|
|
||||
if provider = instances_.find{ |instance| instance.name == name }
|
||||
resources[name].provider = provider
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def exists?
|
||||
@property_hash[:ensure] == :present
|
||||
end
|
||||
|
||||
def destroy
|
||||
#delete hosts first
|
||||
if not @property_hash[:hosts].nil?
|
||||
@property_hash[:hosts].each do |h|
|
||||
auth_nova("aggregate-remove-host", name, h)
|
||||
end
|
||||
end
|
||||
#now delete aggregate
|
||||
auth_nova("aggregate-delete", name)
|
||||
@property_hash[:ensure] = :absent
|
||||
end
|
||||
|
||||
def create
|
||||
extras = Array.new
|
||||
#check for availability zone
|
||||
if not @resource[:availability_zone].nil? and not @resource[:availability_zone].empty?
|
||||
extras << "#{@resource[:availability_zone]}"
|
||||
end
|
||||
#run the command
|
||||
result = auth_nova("aggregate-create", resource[:name], extras)
|
||||
|
||||
#get Id by Name
|
||||
id = self.class.nova_aggregate_resources_get_name_by_id(resource[:name])
|
||||
|
||||
@property_hash = {
|
||||
:ensure => :present,
|
||||
:name => resource[:name],
|
||||
:id => id,
|
||||
:availability_zone => resource[:availability_zone]
|
||||
}
|
||||
|
||||
#add metadata
|
||||
if not @resource[:metadata].nil? and not @resource[:metadata].empty?
|
||||
@resource[:metadata].each do |key, value|
|
||||
set_metadata_helper(id, key, value)
|
||||
end
|
||||
@property_hash[:metadata] = resource[:metadata]
|
||||
end
|
||||
|
||||
#add hosts - This throws an error if the host is already attached to another aggregate!
|
||||
if not @resource[:hosts].nil? and not @resource[:hosts].empty?
|
||||
@resource[:hosts].each do |host|
|
||||
auth_nova("aggregate-add-host", id, "#{host}")
|
||||
end
|
||||
@property_hash[:hosts] = resource[:hosts]
|
||||
end
|
||||
end
|
||||
|
||||
def hosts=(val)
|
||||
#get current hosts
|
||||
id = self.class.nova_aggregate_resources_get_name_by_id(name)
|
||||
attrs = self.class.nova_aggregate_resources_attr(id)
|
||||
#remove all hosts which are not in new value list
|
||||
attrs['Hosts'].each do |h|
|
||||
if not val.include? h
|
||||
auth_nova("aggregate-remove-host", id, "#{h}")
|
||||
end
|
||||
end
|
||||
|
||||
#add hosts from the value list
|
||||
val.each do |h|
|
||||
if not attrs['Hosts'].include? h
|
||||
auth_nova("aggregate-add-host", id, "#{h}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_metadata_helper(agg_id, key, value)
|
||||
auth_nova("aggregate-set-metadata", agg_id, "#{key}=#{value}")
|
||||
end
|
||||
|
||||
def metadata
|
||||
#get current metadata
|
||||
id = self.class.nova_aggregate_resources_get_name_by_id(name)
|
||||
attrs = self.class.nova_aggregate_resources_attr(id)
|
||||
#just ignore the availability_zone. that's handled directly by nova
|
||||
attrs['Metadata'].delete('availability_zone')
|
||||
return attrs['Metadata']
|
||||
end
|
||||
|
||||
def metadata=(val)
|
||||
#get current metadata
|
||||
id = self.class.nova_aggregate_resources_get_name_by_id(name)
|
||||
attrs = self.class.nova_aggregate_resources_attr(id)
|
||||
#get keys which are in current metadata but not in val. Make sure it has data first!
|
||||
if attrs['Metadata'].length > 0
|
||||
obsolete_keys = attrs['Metadata'].keys - val.keys
|
||||
end
|
||||
# clear obsolete keys. If there are any!
|
||||
if obsolete_keys
|
||||
obsolete_keys.each do |key|
|
||||
if not key.include? 'availability_zone'
|
||||
auth_nova("aggregate-set-metadata", id, "#{key}")
|
||||
end
|
||||
end
|
||||
#handle keys (with obsolete keys)
|
||||
new_keys = val.keys - obsolete_keys
|
||||
else
|
||||
#handle keys (without obsolete keys)
|
||||
new_keys = val.keys
|
||||
end
|
||||
#set new metadata if value changed
|
||||
new_keys.each do |key|
|
||||
if val[key] != attrs['Metadata'][key.to_s]
|
||||
value = val[key]
|
||||
set_metadata_helper(id, key, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def availability_zone=(val)
|
||||
id = self.class.nova_aggregate_resources_get_name_by_id(name)
|
||||
auth_nova("aggregate-set-metadata", id, "availability_zone=#{val}")
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,110 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2014 Deutsche Telekom AG
|
||||
#
|
||||
# Author: Thomas Bechtold <t.bechtold@telekom.de>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# nova_aggregate type
|
||||
#
|
||||
# == Parameters
|
||||
# [*name*]
|
||||
# Name for the new aggregate
|
||||
# Required
|
||||
#
|
||||
# [*availability_zone*]
|
||||
# The availability zone. ie "zone1"
|
||||
# Optional
|
||||
#
|
||||
# [*metadata*]
|
||||
# String with key/value pairs. ie "key=value,key=value"
|
||||
# Optional
|
||||
#
|
||||
# [*hosts*]
|
||||
# A comma seperated list with hosts or a single host. ie "host1,host2"
|
||||
# Optional
|
||||
#
|
||||
|
||||
require 'puppet'
|
||||
|
||||
Puppet::Type.newtype(:nova_aggregate) do
|
||||
|
||||
@doc = "Manage creation of nova aggregations."
|
||||
|
||||
ensurable
|
||||
|
||||
autorequire(:nova_config) do
|
||||
['auth_host', 'auth_port', 'auth_protocol', 'admin_tenant_name', 'admin_user', 'admin_password']
|
||||
end
|
||||
|
||||
newparam(:name, :namevar => true) do
|
||||
desc 'Name for the new aggregate'
|
||||
validate do |value|
|
||||
if not value.is_a? String
|
||||
raise ArgumentError, "name parameter must be a String"
|
||||
end
|
||||
unless value =~ /[a-z0-9]+/
|
||||
raise ArgumentError, "#{value} is not a valid name"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
newproperty(:id) do
|
||||
desc 'The unique Id of the aggregate'
|
||||
validate do |v|
|
||||
raise ArgumentError, 'This is a read only property'
|
||||
end
|
||||
end
|
||||
|
||||
newproperty(:availability_zone) do
|
||||
desc 'The availability zone of the aggregate'
|
||||
validate do |value|
|
||||
if not value.is_a? String
|
||||
raise ArgumentError, "availability zone must be a String"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
newproperty(:metadata) do
|
||||
desc 'The metadata of the aggregate'
|
||||
#convert DSL/string form to internal form which is a single hash
|
||||
munge do |value|
|
||||
internal = Hash.new
|
||||
value.split(",").map{|el| el.strip()}.each do |pair|
|
||||
key, value = pair.split("=", 2)
|
||||
internal[key.strip()] = value.strip()
|
||||
end
|
||||
return internal
|
||||
end
|
||||
|
||||
validate do |value|
|
||||
value.split(",").each do |kv|
|
||||
raise ArgumentError, "Key/value pairs must be separated by an =" unless value.include?("=")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
newproperty(:hosts) do
|
||||
desc 'Single host or comma seperated list of hosts'
|
||||
#convert DSL/string form to internal form
|
||||
munge do |value|
|
||||
return value.split(",").map{|el| el.strip()}
|
||||
end
|
||||
end
|
||||
|
||||
validate do
|
||||
raise ArgumentError, 'Name type must be set' unless self[:name]
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,66 @@
|
|||
# run with: rspec spec/type/nova_aggregate_spec.rb
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
|
||||
describe Puppet::Type.type(:nova_aggregate) do
|
||||
before :each do
|
||||
@provider_class = described_class.provide(:simple) do
|
||||
mk_resource_methods
|
||||
def create; end
|
||||
def delete; end
|
||||
def exists?; get(:ensure) != :absent; end
|
||||
def flush; end
|
||||
def self.instances; []; end
|
||||
end
|
||||
end
|
||||
|
||||
it "should be able to create an instance" do
|
||||
described_class.new(:name => 'agg0').should_not be_nil
|
||||
end
|
||||
|
||||
it "should be able to create an more complex instance" do
|
||||
described_class.new(:name => 'agg0',
|
||||
:availability_zone => 'myzone',
|
||||
:metadata => "a=b, c=d",
|
||||
:hosts => "host1").should_not be_nil
|
||||
end
|
||||
|
||||
it "should be able to create an more complex instance with multiple hosts" do
|
||||
described_class.new(:name => 'agg0',
|
||||
:availability_zone => 'myzone',
|
||||
:metadata => "a=b, c=d",
|
||||
:hosts => "host1, host2").should_not be_nil
|
||||
end
|
||||
|
||||
it "should be able to create a instance and have the default values" do
|
||||
c = described_class.new(:name => 'agg0')
|
||||
c[:name].should == "agg0"
|
||||
c[:availability_zone].should == nil
|
||||
c[:metadata].should == nil
|
||||
c[:hosts].should == nil
|
||||
end
|
||||
|
||||
it "should return the given values" do
|
||||
c = described_class.new(:name => 'agg0',
|
||||
:availability_zone => 'myzone',
|
||||
:metadata => " a = b , c= d ",
|
||||
:hosts => " host1, host2 ")
|
||||
c[:name].should == "agg0"
|
||||
c[:availability_zone].should == "myzone"
|
||||
c[:metadata].should == {"a" => "b", "c" => "d"}
|
||||
c[:hosts].should == ["host1" , "host2"]
|
||||
end
|
||||
|
||||
it "should return the given values" do
|
||||
c = described_class.new(:name => 'agg0',
|
||||
:availability_zone => "",
|
||||
:metadata => "",
|
||||
:hosts => "")
|
||||
c[:name].should == "agg0"
|
||||
c[:availability_zone].should == ""
|
||||
c[:metadata].should == {}
|
||||
c[:hosts].should == []
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,274 @@
|
|||
require 'puppet'
|
||||
require 'spec_helper'
|
||||
require 'puppet/provider/nova'
|
||||
require 'rspec/mocks'
|
||||
|
||||
describe Puppet::Provider::Nova do
|
||||
|
||||
def klass
|
||||
described_class
|
||||
end
|
||||
|
||||
let :credential_hash do
|
||||
{
|
||||
'auth_host' => '192.168.56.210',
|
||||
'auth_port' => '35357',
|
||||
'auth_protocol' => 'https',
|
||||
'admin_tenant_name' => 'admin_tenant',
|
||||
'admin_user' => 'admin',
|
||||
'admin_password' => 'password',
|
||||
}
|
||||
end
|
||||
|
||||
let :auth_endpoint do
|
||||
'https://192.168.56.210:35357/v2.0/'
|
||||
end
|
||||
|
||||
let :credential_error do
|
||||
/Nova types will not work/
|
||||
end
|
||||
|
||||
after :each do
|
||||
klass.reset
|
||||
end
|
||||
|
||||
describe 'when determining credentials' do
|
||||
|
||||
it 'should fail if config is empty' do
|
||||
conf = {}
|
||||
klass.expects(:nova_conf).returns(conf)
|
||||
expect do
|
||||
klass.nova_credentials
|
||||
end.to raise_error(Puppet::Error, credential_error)
|
||||
end
|
||||
|
||||
it 'should fail if config does not have keystone_authtoken section.' do
|
||||
conf = {'foo' => 'bar'}
|
||||
klass.expects(:nova_conf).returns(conf)
|
||||
expect do
|
||||
klass.nova_credentials
|
||||
end.to raise_error(Puppet::Error, credential_error)
|
||||
end
|
||||
|
||||
it 'should fail if config does not contain all auth params' do
|
||||
conf = {'keystone_authtoken' => {'invalid_value' => 'foo'}}
|
||||
klass.expects(:nova_conf).returns(conf)
|
||||
expect do
|
||||
klass.nova_credentials
|
||||
end.to raise_error(Puppet::Error, credential_error)
|
||||
end
|
||||
|
||||
it 'should use specified host/port/protocol in the auth endpoint' do
|
||||
conf = {'keystone_authtoken' => credential_hash}
|
||||
klass.expects(:nova_conf).returns(conf)
|
||||
klass.get_auth_endpoint.should == auth_endpoint
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'when invoking the nova cli' do
|
||||
|
||||
it 'should set auth credentials in the environment' do
|
||||
authenv = {
|
||||
:OS_AUTH_URL => auth_endpoint,
|
||||
:OS_USERNAME => credential_hash['admin_user'],
|
||||
:OS_TENANT_NAME => credential_hash['admin_tenant_name'],
|
||||
:OS_PASSWORD => credential_hash['admin_password'],
|
||||
}
|
||||
klass.expects(:get_nova_credentials).with().returns(credential_hash)
|
||||
klass.expects(:withenv).with(authenv)
|
||||
klass.auth_nova('test_retries')
|
||||
end
|
||||
|
||||
['[Errno 111] Connection refused',
|
||||
'(HTTP 400)'].reverse.each do |valid_message|
|
||||
it "should retry when nova cli returns with error #{valid_message}" do
|
||||
klass.expects(:get_nova_credentials).with().returns({})
|
||||
klass.expects(:sleep).with(10).returns(nil)
|
||||
klass.expects(:nova).twice.with(['test_retries']).raises(
|
||||
Exception, valid_message).then.returns('')
|
||||
klass.auth_nova('test_retries')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'when parse a string line' do
|
||||
it 'should return the same string' do
|
||||
res = klass.str2hash("zone1")
|
||||
res.should == "zone1"
|
||||
end
|
||||
|
||||
it 'should return the string without quotes' do
|
||||
res = klass.str2hash("'zone1'")
|
||||
res.should == "zone1"
|
||||
end
|
||||
|
||||
it 'should return the same string' do
|
||||
res = klass.str2hash("z o n e1")
|
||||
res.should == "z o n e1"
|
||||
end
|
||||
|
||||
it 'should return a hash' do
|
||||
res = klass.str2hash("a=b")
|
||||
res.should == {"a"=>"b"}
|
||||
end
|
||||
|
||||
it 'should return a hash with containing spaces' do
|
||||
res = klass.str2hash("a b = c d")
|
||||
res.should == {"a b "=>" c d"}
|
||||
end
|
||||
|
||||
it 'should return the same string' do
|
||||
res = klass.str2list("zone1")
|
||||
res.should == "zone1"
|
||||
end
|
||||
|
||||
it 'should return a list of strings' do
|
||||
res = klass.str2list("zone1, zone2")
|
||||
res.should == ["zone1", "zone2"]
|
||||
end
|
||||
|
||||
|
||||
it 'should return a list of strings' do
|
||||
res = klass.str2list("zo n e1, zone2 ")
|
||||
res.should == ["zo n e1", "zone2"]
|
||||
end
|
||||
|
||||
it 'should return a hash with multiple keys' do
|
||||
res = klass.str2list("a=b, c=d")
|
||||
res.should == {"a"=>"b", "c"=>"d"}
|
||||
end
|
||||
|
||||
it 'should return a single hash' do
|
||||
res = klass.str2list("a=b")
|
||||
res.should == {"a"=>"b"}
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when parsing cli output' do
|
||||
|
||||
it 'should return a list with hashes' do
|
||||
output = <<-EOT
|
||||
+----+-------+-------------------+
|
||||
| Id | Name | Availability Zone |
|
||||
+----+-------+-------------------+
|
||||
| 1 | haha | haha2 |
|
||||
| 2 | haha2 | - |
|
||||
+----+-------+-------------------+
|
||||
EOT
|
||||
res = klass.cliout2list(output)
|
||||
res.should == [{"Id"=>"1", "Name"=>"haha", "Availability Zone"=>"haha2"},
|
||||
{"Id"=>"2", "Name"=>"haha2", "Availability Zone"=>""}]
|
||||
end
|
||||
|
||||
it 'should return a list with hashes' do
|
||||
output = <<-EOT
|
||||
+----+-------+-------------------+-------+--------------------------------------------------+
|
||||
| Id | Name | Availability Zone | Hosts | Metadata |
|
||||
+----+-------+-------------------+-------+--------------------------------------------------+
|
||||
| 16 | agg94 | my_-zone1 | | 'a=b', 'availability_zone= my_-zone1', 'x_q-r=y' |
|
||||
+----+-------+-------------------+-------+--------------------------------------------------+
|
||||
EOT
|
||||
res = klass.cliout2list(output)
|
||||
res.should == [{"Id"=>"16",
|
||||
"Name"=>"agg94",
|
||||
"Availability Zone"=>"my_-zone1",
|
||||
"Hosts"=>"",
|
||||
"Metadata"=> {
|
||||
"a"=>"b",
|
||||
"availability_zone"=>" my_-zone1",
|
||||
"x_q-r"=>"y"
|
||||
}
|
||||
}]
|
||||
end
|
||||
|
||||
it 'should return a empty list' do
|
||||
output = <<-EOT
|
||||
+----+------+-------------------+
|
||||
| Id | Name | Availability Zone |
|
||||
+----+------+-------------------+
|
||||
+----+------+-------------------+
|
||||
EOT
|
||||
res = klass.cliout2list(output)
|
||||
res.should == []
|
||||
end
|
||||
|
||||
it 'should return a empty list because no input available' do
|
||||
output = <<-EOT
|
||||
EOT
|
||||
res = klass.cliout2list(output)
|
||||
res.should == []
|
||||
end
|
||||
|
||||
it 'should return a list with hashes' do
|
||||
output = <<-EOT
|
||||
+----+----------------+-------------------+
|
||||
| Id | Name | Availability Zone |
|
||||
+----+----------------+-------------------+
|
||||
| 6 | my | zone1 |
|
||||
| 8 | my2 | - |
|
||||
+----+----------------+-------------------+
|
||||
EOT
|
||||
res = klass.cliout2list(output)
|
||||
res.should == [{"Id"=>"6", "Name"=>"my", "Availability Zone"=>"zone1"},
|
||||
{"Id"=>"8", "Name"=>"my2", "Availability Zone"=>""}]
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when handling cli output' do
|
||||
it 'should return the availble Id' do
|
||||
output = <<-EOT
|
||||
+----+-------+-------------------+
|
||||
| Id | Name | Availability Zone |
|
||||
+----+-------+-------------------+
|
||||
| 1 | haha | haha2 |
|
||||
| 2 | haha2 | - |
|
||||
+----+-------+-------------------+
|
||||
EOT
|
||||
klass.expects(:auth_nova).returns(output)
|
||||
res = klass.nova_aggregate_resources_get_name_by_id("haha2")
|
||||
res.should eql(2)
|
||||
end
|
||||
|
||||
it 'should return nil because given name is not available' do
|
||||
output = <<-EOT
|
||||
+----+-------+-------------------+
|
||||
| Id | Name | Availability Zone |
|
||||
+----+-------+-------------------+
|
||||
| 1 | haha | haha2 |
|
||||
| 2 | haha2 | - |
|
||||
+----+-------+-------------------+
|
||||
EOT
|
||||
klass.expects(:auth_nova).returns(output)
|
||||
res = klass.nova_aggregate_resources_get_name_by_id("notavailable")
|
||||
res.should eql(nil)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when getting details for given Id' do
|
||||
it 'should return a Hash with the details' do
|
||||
output = <<-EOT
|
||||
+----+-------+-------------------+-------+--------------------------------------------------+
|
||||
| Id | Name | Availability Zone | Hosts | Metadata |
|
||||
+----+-------+-------------------+-------+--------------------------------------------------+
|
||||
| 16 | agg94 | my_-zone1 | | 'a=b', 'availability_zone= my_-zone1', 'x_q-r=y' |
|
||||
+----+-------+-------------------+-------+--------------------------------------------------+
|
||||
EOT
|
||||
klass.expects(:auth_nova).returns(output)
|
||||
res = klass.nova_aggregate_resources_attr(16)
|
||||
res.should == {
|
||||
"Id"=>"16",
|
||||
"Name"=>"agg94",
|
||||
"Availability Zone"=>"my_-zone1",
|
||||
"Hosts"=>[],
|
||||
"Metadata"=>{
|
||||
"a"=>"b",
|
||||
"availability_zone"=>" my_-zone1",
|
||||
"x_q-r"=>"y"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue