diff --git a/README.md b/README.md index 24ac3be3..709862dc 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ The current layout is: * bridges - A "Bridge" is basically the thing you plug ports / interfaces into. * ports - A Port is a interface you plug into the bridge (switch). +* configs - Configuration settings, if any ## USAGE: To create a new bridge, use the `vs_bridge` type: @@ -30,6 +31,38 @@ vs_port { 'eth2': } ``` +You can change the vswitch configuration settings using. +``` +vs_config { 'parameter_name': + ensure => present, + value => "some_value" +} +``` +For configuration parameters that are 'hash' data type, the resource name +should be of the following format + +``` +parameter-name:key-name + +Ex. +vs_config { 'external_ids:ovn-remote': + ensure => present, + value => 'tcp:127.0.0.1:6640' +} +``` + +For 'set/array' data types, value should be in the following format + +``` +'[]' + +Ex. +vs_config { 'array_key': + ensure => present, + value => '[2, 1, 6, 4]' +} +``` + ## Beaker-Rspec This module has beaker-rspec tests diff --git a/lib/puppet/provider/vs_config/ovs.rb b/lib/puppet/provider/vs_config/ovs.rb new file mode 100644 index 00000000..91a2f671 --- /dev/null +++ b/lib/puppet/provider/vs_config/ovs.rb @@ -0,0 +1,107 @@ +require 'puppet' + +Puppet::Type.type(:vs_config).provide(:ovs) do + + commands :vsctl => 'ovs-vsctl' + + mk_resource_methods + + def self.munge_array_value(value) + "[#{value[1..-2].split(',').map(&:strip).sort.join(",")}]" + end + + def self.parse_column_value(value) + value = value.chomp + if value[0] == '{' + # hash case, like {system-id=\"some-id\", name=\"some-name\"} + type = 'hash' + res = {} + value[1..-2].gsub('"','').split(', ').map(&:strip).each do |v| + k,val = v.split("=") + res[k] = val + end + elsif value[0] == '[' + # set case, like ['id1', 'id2', 'id3'] + type = 'set' + res = munge_array_value(value) + else + # simple string + type = 'string' + res = value + end + + { + :type => type, + :value => res + } + end + + def self.list_config_entries + open_vs = vsctl("list", "Open_vSwitch", ".").split("\n") + configs = [] + open_vs.each do |line| + key, value = line.split(' : ').map(&:strip) + parsed_value = parse_column_value(value) + if parsed_value[:type] == "hash" + parsed_value[:value].each do |k, v| + configs.push({ + :name => "#{key}:#{k}", + :value => v, + :ensure => :present + }) + end + else + configs.push({ + :name => key, + :ensure => :present, + :value => parsed_value[:value], + }) + end + end + configs + end + + def self.instances() + configs = list_config_entries + configs.collect do |config| + new(config) + end + end + + def self.prefetch(resources) + instances.each do |prov| + if resource = resources[prov.name] + resource.provider = prov + end + end + end + + def initialize(value) + super(value) + end + + def exists? + @property_hash[:ensure] == :present + end + + def destroy + if @resource[:name].include?(':') + name, key = @resource[:name].split(':') + vsctl("remove", "Open_vSwitch", ".", name, key) + else + vsctl("clear", "Open_vSwitch", ".", @resource[:name]) + end + end + + def _set + vsctl("set", "Open_vSwitch", ".", "#{@resource[:name]}=#{@resource[:value]}") + end + + def create + _set + end + + def value=(value) + _set + end +end diff --git a/lib/puppet/type/vs_config.rb b/lib/puppet/type/vs_config.rb new file mode 100644 index 00000000..2660869f --- /dev/null +++ b/lib/puppet/type/vs_config.rb @@ -0,0 +1,36 @@ +require 'puppet' + +Puppet::Type.newtype(:vs_config) do + desc 'Switch configurations' + + ensurable + + newparam(:name, :namevar => true) do + desc 'Configuration parameter whose value need to be set' + + validate do |value| + if !value.is_a?(String) + raise ArgumentError, "Invalid name #{value}. Requires a String, not a #{value.class}" + end + end + end + + newproperty(:value) do + desc 'Configuration value for the paramter' + + validate do |value| + if !value.is_a?(String) + raise ArgumentError, "Invalid external_ids #{value}. Requires a String, not a #{value.class}" + end + end + + munge do |value| + if value[0] == '[' && value[-1] == ']' + "[#{value[1..-2].split(',').map(&:strip).sort.join(",")}]" + else + super(value) + end + end + end +end + diff --git a/spec/acceptance/basic_vswitch_spec.rb b/spec/acceptance/basic_vswitch_spec.rb index 9d1f2886..f0aef05c 100644 --- a/spec/acceptance/basic_vswitch_spec.rb +++ b/spec/acceptance/basic_vswitch_spec.rb @@ -14,6 +14,11 @@ describe 'basic vswitch' do vs_bridge { 'br-beaker': ensure => present, } + + vs_config { 'external_ids:ovn-remote': + ensure => present, + value => 'tcp:127.0.0.1:2300', + } EOS @@ -28,5 +33,12 @@ describe 'basic vswitch' do it { is_expected.to match /br-beaker/ } end end + + describe command('ovs-vsctl get Open_vSwitch . external_ids:ovn-remote') do + describe '#stdout' do + subject { super().stdout } + it { is_expected.to match /\"tcp:127.0.0.1:2300\"/ } + end + end end end diff --git a/spec/unit/puppet/lib/provider/vs_config_ovs_spec.rb b/spec/unit/puppet/lib/provider/vs_config_ovs_spec.rb new file mode 100644 index 00000000..6616eab3 --- /dev/null +++ b/spec/unit/puppet/lib/provider/vs_config_ovs_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +describe Puppet::Type.type(:vs_config).provider(:ovs) do + it 'should have an instance method' do + expect(described_class).to respond_to :instances + end + + it 'should have a prefetch method' do + expect(described_class).to respond_to :prefetch + end + + context "Testing string values" do + before :each do + described_class.expects(:vsctl).with( + "list", "Open_vSwitch", ".").returns 'key1 : value1 +key2 : value2 +key3 : value3' + end + + it "should return three resources" do + expect(described_class.instances.size).to eq(3) + end + + it "should return the appropriate property hash" do + described_class.instances.each do |inst| + _inst = inst.instance_variable_get("@property_hash") + expect(_inst.key?(:name)).to eq true + expect(_inst.key?(:value)).to eq true + expect(_inst.key?(:ensure)).to eq true + end + end + + it "should contain proper values" do + described_class.instances.each do |inst| + _inst = inst.instance_variable_get("@property_hash") + expect(_inst[:name][0..2]).to eq "key" + expect(_inst[:value][0..4]).to eq "value" + expect(_inst[:value][5]).to eq _inst[:name][3] + expect(_inst[:ensure]).to eq :present + end + end + end + + context "Testing array values" do + before :each do + described_class.expects(:vsctl).with( + "list", "Open_vSwitch", ".").returns 'key1 : [abc, def, ghi] +key2 : [def, abc, ghi] +key3 : [1001, 399, 240, 1200]' + end + + it "should return three resources" do + expect(described_class.instances.size).to eq(3) + end + + it "should contain proper values" do + expected_values = { + "key1" => "[abc,def,ghi]", + "key2" => "[abc,def,ghi]", + "key3" => "[1001,1200,240,399]" + } + described_class.instances.each do |inst| + _inst = inst.instance_variable_get("@property_hash") + expect(expected_values.key?(_inst[:name])).to eq true + expect(_inst[:value]).to eq expected_values[_inst[:name]] + expect(_inst[:ensure]).to eq :present + end + end + end + + context "Testing hash values" do + before :each do + described_class.expects(:vsctl).with( + "list", "Open_vSwitch", ".").returns 'key1 : {} +key2 : {"hash21"="value21"} +key3 : {"hash31"="value31", "hash32"="value32", "hash33"=33}' + end + + it "should return three resources" do + expect(described_class.instances.size).to eq(4) + end + + it "should contain valid names and values" do + expected_values = { + "key2:hash21" => "value21", + "key3:hash31" => "value31", + "key3:hash32" => "value32", + "key3:hash33" => "33"} + described_class.instances.each do |inst| + _inst = inst.instance_variable_get("@property_hash") + expect(expected_values.key?(_inst[:name])).to eq true + expect(_inst[:value]).to eq expected_values[_inst[:name]] + expect(_inst[:ensure]).to eq :present + end + end + end + +end diff --git a/spec/unit/puppet/lib/type/vs_config_spec.rb b/spec/unit/puppet/lib/type/vs_config_spec.rb new file mode 100644 index 00000000..0ca37d76 --- /dev/null +++ b/spec/unit/puppet/lib/type/vs_config_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Puppet::Type.type(:vs_config) do + it "should support present as a value for ensure" do + expect do + described_class.new(:name => 'foo', :ensure => :present) + end.to_not raise_error + end + + it "should support absent as a value for ensure" do + expect do + described_class.new(:name => 'foo', :ensure => :absent) + end.to_not raise_error + end + + it "should have a :value parameter" do + expect(described_class.attrtype(:value)).to eq(:property) + end + + it "should accept only string values" do + expect do + described_class.new({:name => "foo", :value => 123, :ensure => :present}) + end.to raise_error(Puppet::Error) + end + + it "should munge array values" do + expect( + described_class.new({:name => "foo", :value => "[2, 1, 3, 0]", :ensure => :present})[:value] + ).to eq "[0,1,2,3]" + end +end